summaryrefslogtreecommitdiffstats
path: root/src
diff options
context:
space:
mode:
Diffstat (limited to 'src')
-rw-r--r--src/AttrList.h62
-rw-r--r--src/DefaultStyle.c504
-rw-r--r--src/GridText.c15054
-rw-r--r--src/GridText.h302
-rw-r--r--src/HTAlert.c1201
-rw-r--r--src/HTAlert.h168
-rw-r--r--src/HTFWriter.c1516
-rw-r--r--src/HTFont.h50
-rw-r--r--src/HTForms.h174
-rw-r--r--src/HTInit.c1503
-rw-r--r--src/HTML.c8198
-rw-r--r--src/HTML.h283
-rw-r--r--src/HTNestedList.h44
-rw-r--r--src/HTSaveToFile.h29
-rw-r--r--src/LYBookmark.c1169
-rw-r--r--src/LYBookmark.h25
-rw-r--r--src/LYCgi.c757
-rw-r--r--src/LYCgi.h16
-rw-r--r--src/LYCharSets.c1157
-rw-r--r--src/LYCharSets.h154
-rw-r--r--src/LYCharUtils.c3419
-rw-r--r--src/LYCharUtils.h109
-rw-r--r--src/LYCharVals.h34
-rw-r--r--src/LYClean.c203
-rw-r--r--src/LYClean.h25
-rw-r--r--src/LYCookie.c2898
-rw-r--r--src/LYCookie.h59
-rw-r--r--src/LYCurses.c3307
-rw-r--r--src/LYCurses.h865
-rw-r--r--src/LYDownload.c591
-rw-r--r--src/LYDownload.h21
-rw-r--r--src/LYEdit.c298
-rw-r--r--src/LYEdit.h18
-rw-r--r--src/LYEditmap.c1931
-rw-r--r--src/LYExtern.c443
-rw-r--r--src/LYExtern.h16
-rw-r--r--src/LYForms.c1086
-rw-r--r--src/LYGCurses.h246
-rw-r--r--src/LYGetFile.c1581
-rw-r--r--src/LYGetFile.h38
-rw-r--r--src/LYGlobalDefs.h740
-rw-r--r--src/LYHash.c144
-rw-r--r--src/LYHash.h67
-rw-r--r--src/LYHistory.c1163
-rw-r--r--src/LYHistory.h39
-rw-r--r--src/LYIcon.rc34
-rw-r--r--src/LYJump.c504
-rw-r--r--src/LYJump.h36
-rw-r--r--src/LYJustify.h83
-rw-r--r--src/LYKeymap.c1545
-rw-r--r--src/LYKeymap.h307
-rw-r--r--src/LYLeaks.c1169
-rw-r--r--src/LYList.c374
-rw-r--r--src/LYList.h16
-rw-r--r--src/LYLocal.c2685
-rw-r--r--src/LYLocal.h36
-rw-r--r--src/LYMail.c1774
-rw-r--r--src/LYMail.h88
-rw-r--r--src/LYMain.c4567
-rw-r--r--src/LYMainLoop.c8208
-rw-r--r--src/LYMainLoop.h34
-rw-r--r--src/LYMap.c646
-rw-r--r--src/LYMap.h29
-rw-r--r--src/LYNews.c509
-rw-r--r--src/LYNews.h19
-rw-r--r--src/LYOptions.c4365
-rw-r--r--src/LYOptions.h46
-rw-r--r--src/LYPrettySrc.c427
-rw-r--r--src/LYPrettySrc.h92
-rw-r--r--src/LYPrint.c1461
-rw-r--r--src/LYPrint.h20
-rw-r--r--src/LYReadCFG.c2682
-rw-r--r--src/LYReadCFG.h75
-rw-r--r--src/LYSearch.c379
-rw-r--r--src/LYSearch.h33
-rw-r--r--src/LYSession.c267
-rw-r--r--src/LYSession.h16
-rw-r--r--src/LYShowInfo.c485
-rw-r--r--src/LYShowInfo.h21
-rw-r--r--src/LYSignal.h31
-rw-r--r--src/LYStrings.c6217
-rw-r--r--src/LYStrings.h402
-rw-r--r--src/LYStructs.h190
-rw-r--r--src/LYStyle.c970
-rw-r--r--src/LYStyle.h88
-rw-r--r--src/LYTraversal.c182
-rw-r--r--src/LYTraversal.h23
-rw-r--r--src/LYUpload.c221
-rw-r--r--src/LYUpload.h17
-rw-r--r--src/LYUtils.c8038
-rw-r--r--src/LYUtils.h578
-rw-r--r--src/LYVMSdef.h18
-rw-r--r--src/LYebcdic.c48
-rw-r--r--src/LYexit.c185
-rw-r--r--src/LYmktime.c364
-rw-r--r--src/LYrcFile.c1140
-rw-r--r--src/LYrcFile.h318
-rw-r--r--src/TRSTable.c2012
-rw-r--r--src/TRSTable.h50
-rw-r--r--src/UCAuto.c816
-rw-r--r--src/UCAuto.h14
-rw-r--r--src/UCAux.c798
-rw-r--r--src/UCdomap.c2524
-rw-r--r--src/UCdomap.h178
-rw-r--r--src/Xsystem.c589
-rw-r--r--src/chrtrans/README.format138
-rw-r--r--src/chrtrans/README.tables76
-rw-r--r--src/chrtrans/UCkd.h54
-rw-r--r--src/chrtrans/build-chrtrans.com142
-rw-r--r--src/chrtrans/build-header.com37
-rw-r--r--src/chrtrans/caselower.h738
-rw-r--r--src/chrtrans/cp1250_uni.tbl172
-rw-r--r--src/chrtrans/cp1251_uni.tbl161
-rw-r--r--src/chrtrans/cp1252_uni.tbl177
-rw-r--r--src/chrtrans/cp1253_uni.tbl161
-rw-r--r--src/chrtrans/cp1255_uni.tbl161
-rw-r--r--src/chrtrans/cp1256_uni.tbl161
-rw-r--r--src/chrtrans/cp1257_uni.tbl162
-rw-r--r--src/chrtrans/cp437_uni.tbl181
-rw-r--r--src/chrtrans/cp737_uni.tbl172
-rw-r--r--src/chrtrans/cp775_uni.tbl159
-rw-r--r--src/chrtrans/cp850_uni.tbl177
-rw-r--r--src/chrtrans/cp852_uni.tbl170
-rw-r--r--src/chrtrans/cp857_uni.tbl159
-rw-r--r--src/chrtrans/cp862_uni.tbl160
-rw-r--r--src/chrtrans/cp864_uni.tbl160
-rw-r--r--src/chrtrans/cp866_uni.tbl159
-rw-r--r--src/chrtrans/cp866u_uni.tbl157
-rw-r--r--src/chrtrans/cp869_uni.tbl160
-rw-r--r--src/chrtrans/def7_uni.tbl2951
-rw-r--r--src/chrtrans/dmcs_uni.tbl233
-rw-r--r--src/chrtrans/entities.h1414
-rw-r--r--src/chrtrans/hp_uni.tbl212
-rw-r--r--src/chrtrans/iso01_uni.tbl334
-rw-r--r--src/chrtrans/iso02_uni.tbl265
-rw-r--r--src/chrtrans/iso03_uni.tbl255
-rw-r--r--src/chrtrans/iso04_uni.tbl252
-rw-r--r--src/chrtrans/iso05_uni.tbl259
-rw-r--r--src/chrtrans/iso06_uni.tbl208
-rw-r--r--src/chrtrans/iso07_uni.tbl275
-rw-r--r--src/chrtrans/iso08_uni.tbl229
-rw-r--r--src/chrtrans/iso09_uni.tbl266
-rw-r--r--src/chrtrans/iso10_uni.tbl153
-rw-r--r--src/chrtrans/iso13_uni.tbl114
-rw-r--r--src/chrtrans/iso14_uni.tbl114
-rw-r--r--src/chrtrans/iso15_uni.tbl216
-rw-r--r--src/chrtrans/iso16_uni.tbl120
-rw-r--r--src/chrtrans/jcuken_kb.h22
-rw-r--r--src/chrtrans/koi8r_uni.tbl147
-rw-r--r--src/chrtrans/koi8u_uni.tbl154
-rw-r--r--src/chrtrans/mac_uni.tbl284
-rw-r--r--src/chrtrans/make-msc.bat6
-rw-r--r--src/chrtrans/makefile.bcb123
-rw-r--r--src/chrtrans/makefile.dos137
-rw-r--r--src/chrtrans/makefile.in201
-rw-r--r--src/chrtrans/makefile.msc139
-rw-r--r--src/chrtrans/makehdrs.bat51
-rw-r--r--src/chrtrans/makeuctb.c914
-rw-r--r--src/chrtrans/makew32.bat13
-rw-r--r--src/chrtrans/mnem2_suni.tbl1865
-rw-r--r--src/chrtrans/mnem_suni.tbl1861
-rw-r--r--src/chrtrans/next_uni.tbl185
-rw-r--r--src/chrtrans/pt154_uni.tbl174
-rw-r--r--src/chrtrans/rfc_suni.tbl1958
-rw-r--r--src/chrtrans/rot13_kb.h22
-rw-r--r--src/chrtrans/utf8_uni.tbl35
-rw-r--r--src/chrtrans/viscii_uni.tbl300
-rw-r--r--src/chrtrans/yawerty_kb.h22
-rw-r--r--src/cmu_tcp.opt1
-rw-r--r--src/decc.opt2
-rw-r--r--src/descrip.mms172
-rw-r--r--src/gnuc.opt3
-rw-r--r--src/makefile.dos115
-rw-r--r--src/makefile.dsl105
-rw-r--r--src/makefile.in242
-rw-r--r--src/makefile.wsl68
-rw-r--r--src/mktime.c71
-rw-r--r--src/multinet.opt1
-rw-r--r--src/multinet_ucx.opt1
-rw-r--r--src/parsdate.c2425
-rw-r--r--src/parsdate.h21
-rw-r--r--src/parsdate.y991
-rw-r--r--src/socketshr_tcp.opt1
-rw-r--r--src/strstr.c60
-rw-r--r--src/structdump.h164
-rw-r--r--src/tcpipolb.opt1
-rw-r--r--src/tcpipshr.opt1
-rw-r--r--src/tcpwareolb.opt1
-rw-r--r--src/tcpwareshr.opt1
-rw-r--r--src/tidy_tls.c707
-rw-r--r--src/ucxolb.opt1
-rw-r--r--src/ucxshr.opt1
-rw-r--r--src/vaxc.opt2
-rw-r--r--src/wcwidth.c709
-rw-r--r--src/wcwidth.h47
-rw-r--r--src/win_tcp.opt1
196 files changed, 137302 insertions, 0 deletions
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 <HText.h>
+#include <HTMLDTD.h>
+
+#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 <HTUtils.h>
+#include <HTFont.h>
+#include <HTStyle.h>
+
+#include <LYGlobalDefs.h>
+#include <LYLeaks.h>
+
+/* 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 <HTUtils.h>
+#include <HTString.h>
+#include <HTAccess.h>
+#include <HTAnchor.h>
+#include <HTParse.h>
+#include <HTTP.h>
+#include <HTAlert.h>
+#include <HTCJK.h>
+#include <HTFile.h>
+#include <UCDefs.h>
+#include <UCAux.h>
+#include <HText.h>
+
+#include <assert.h>
+
+#include <GridText.h>
+#include <LYCurses.h>
+#include <LYUtils.h>
+#include <LYStrings.h>
+#include <LYStructs.h>
+#include <LYGlobalDefs.h>
+#include <LYGetFile.h>
+#include <LYClean.h>
+#include <LYMail.h>
+#include <LYList.h>
+#include <LYCharSets.h>
+#include <LYCharUtils.h> /* LYUCTranslateBack... */
+#include <UCMap.h>
+#include <LYEdit.h>
+#include <LYPrint.h>
+#include <LYPrettySrc.h>
+#include <LYSearch.h>
+#include <TRSTable.h>
+#include <LYHistory.h>
+#ifdef EXP_CHARTRANS_AUTOSWITCH
+#include <UCAuto.h>
+#endif /* EXP_CHARTRANS_AUTOSWITCH */
+
+#include <LYexit.h>
+#include <LYLeaks.h>
+
+#ifdef USE_COLOR_STYLE
+#include <AttrList.h>
+#include <LYHash.h>
+#include <LYStyle.h>
+#endif
+
+#ifdef EXP_WCWIDTH_SUPPORT
+# ifdef HAVE_WCWIDTH
+# ifdef HAVE_WCHAR_H
+# include <wchar.h>
+# endif
+# else
+# include <wcwidth.h>
+# define wcwidth(n) mk_wcwidth(n)
+# endif
+#endif
+
+#include <LYJustify.h>
+
+#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 <li> 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 <lib$routines.h>
+ 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 <a href=".">foo</a>, 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 = &current;
+ break;
+ }
+ } else if (direction > 0 &&
+ same_anchor_as_link(curlink, previous.anc, ta_skip)) {
+ group_to_go = &current;
+ 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 = &current;
+ break;
+ }
+ } else {
+ group_to_go = &current;
+ break;
+ }
+ }
+ }
+ }
+ }
+ if (!group_to_go && curlink < 0 && direction <= 0) {
+ group_to_go = &current;
+ }
+ 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 != &current &&
+ 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 <return> 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) ?
+ "<NULL>" : HTCurSelectGroup),
+ HTCurSelectGroupType,
+ ((HTCurSelectGroupSize == NULL) ?
+ "<NULL>" : HTCurSelectGroupSize)));
+ CTRACE((tfp, "HText_beginSelect: name_cs=%d \"%s\"\n",
+ HTCurSelectGroupCharset,
+ (HTCurSelectGroupCharset >= 0 ?
+ LYCharSet_UC[HTCurSelectGroupCharset].MIMEname : "<UNKNOWN>")));
+}
+
+/*
+ * 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 : "<NULL>")));
+ 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 : "<UNKNOWN>")));
+ 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 : "<UNKNOWN>"),
+ (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 : "<UNKNOWN>"),
+ I->name_cs,
+ (I->name_cs >= 0 ?
+ LYCharSet_UC[I->name_cs].MIMEname : "<UNKNOWN>")));
+ CTRACE((tfp, " value_cs=%d \"%s\" (from %d \"%s\")\n",
+ f->value_cs,
+ (f->value_cs >= 0 ?
+ LYCharSet_UC[f->value_cs].MIMEname : "<UNKNOWN>"),
+ I->value_cs,
+ (I->value_cs >= 0 ?
+ LYCharSet_UC[I->value_cs].MIMEname : "<UNKNOWN>")));
+
+ /*
+ * 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,
+ "<html>\n<head>\n<title>%s</title>\n</head>\n<body>\n",
+ CACHE_JAR_TITLE);
+ PUTS(buf);
+ HTSprintf0(&buf, "<h1>%s (%s)%s<a href=\"%s%s\">%s</a></h1>\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,
+ "<p><em>%d.</em> Title: <a href=\"%s%d\">%s</a><br />URL: <a href=\"%s\">%s</a><br />",
+ 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: <a href=\"file://%s\">%s</a> ",
+ 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: <a href=%s>%s</a> ", 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: <em>%s</em> ", server);
+ PUTS(buf);
+ }
+#ifdef USE_SOURCE_CACHE
+ if (source_cache_file != NULL) {
+ HTSprintf0(&buf,
+ "Source-Cache-File: <a href=\"file://%s\">%s</a>",
+ source_cache_file, source_cache_file);
+ PUTS(buf);
+ }
+#endif
+ HTSprintf0(&buf, "<br />");
+ PUTS(buf);
+ }
+ HTSprintf0(&buf, "</body></html>");
+ 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 <HText.h> /* Superclass */
+
+#ifndef HTFORMS_H
+#include <HTForms.h>
+#endif /* HTFORMS_H */
+
+#include <HTFont.h>
+
+#include <HTCJK.h>
+
+#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 <HTUtils.h>
+#include <HTAlert.h>
+#include <LYGlobalDefs.h>
+#include <LYCurses.h>
+#include <LYStrings.h>
+#include <LYUtils.h>
+#include <LYClean.h>
+#include <GridText.h>
+#include <LYCookie.h>
+#include <LYHistory.h> /* store statusline messages */
+
+#include <LYLeaks.h>
+
+#include <HTParse.h>
+
+#undef timezone /* U/Win defines this in time.h, hides implementation detail */
+
+#if defined(HAVE_FTIME) && defined(HAVE_SYS_TIMEB_H)
+#include <sys/timeb.h>
+#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 <LYCookie.h>
+
+#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 <HTUtils.h>
+#include <LYCurses.h>
+#include <HTFWriter.h>
+#include <HTSaveToFile.h>
+
+#ifdef WIN_EX
+#include <HTParse.h>
+#endif
+
+#include <HTFormat.h>
+#include <UCDefs.h>
+#include <HTAlert.h>
+#include <HTFile.h>
+#include <HTInit.h>
+#include <HTPlain.h>
+
+#include <LYStrings.h>
+#include <LYUtils.h>
+#include <LYGlobalDefs.h>
+#include <LYClean.h>
+#include <GridText.h>
+#include <LYExtern.h>
+#include <LYexit.h>
+#include <LYLeaks.h>
+#include <LYKeymap.h>
+#include <LYGetFile.h>
+#include <LYHistory.h> /* store statusline messages */
+
+#ifdef USE_PERSISTENT_COOKIES
+#include <LYCookie.h>
+#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,
+ "<!-- X-URL: %s -->\n", anchor->address);
+ if (non_empty(anchor->date)) {
+ fprintf(ret_obj->fp,
+ "<!-- Date: %s -->\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,
+ "<!-- Last-Modified: %s -->\n", anchor->last_modified);
+ }
+ }
+ fprintf(ret_obj->fp,
+ "<BASE HREF=\"%s\">\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,
+ "<META HTTP-EQUIV=\"Content-Type\" CONTENT=\"" STR_HTML
+ "; charset=%s\">\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 <fab.h>
+#include <rmsdef.h> /* RMS status codes */
+#include <iodef.h> /* I/O function codes */
+#include <fibdef.h> /* file information block defs */
+#include <atrdef.h> /* attribute request codes */
+#ifdef NOTDEFINED /*** Not all versions of VMS compilers have these. ***/
+#include <fchdef.h> /* file characteristics */
+#include <fatdef.h> /* 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 <LYStructs.h>
+#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 <HTUtils.h>
+
+/* Implements:
+*/
+#include <HTInit.h>
+
+#include <HTML.h>
+#include <HTPlain.h>
+#include <HTMLGen.h>
+#include <HTFile.h>
+#include <HTFormat.h>
+#include <HTMIME.h>
+#include <HTWSRC.h>
+
+#include <HTSaveToFile.h> /* LJM */
+#include <LYStrings.h>
+#include <LYUtils.h>
+#include <LYGlobalDefs.h>
+
+#include <LYexit.h>
+#include <LYLeaks.h>
+
+#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 <HTUtils.h>
+
+#define Lynx_HTML_Handler
+#include <HTChunk.h>
+#include <HText.h>
+#include <HTStyle.h>
+#include <HTML.h>
+
+#include <HTCJK.h>
+#include <HTAtom.h>
+#include <HTAnchor.h>
+#include <HTMLGen.h>
+#include <HTParse.h>
+#include <HTList.h>
+#include <UCMap.h>
+#include <UCDefs.h>
+#include <UCAux.h>
+
+#include <LYGlobalDefs.h>
+#include <LYCharUtils.h>
+#include <LYCharSets.h>
+
+#include <HTAlert.h>
+#include <HTForms.h>
+#include <HTNestedList.h>
+#include <GridText.h>
+#include <LYStrings.h>
+#include <LYUtils.h>
+#include <LYMap.h>
+#include <LYList.h>
+#include <LYBookmark.h>
+#include <LYHistory.h>
+
+#ifdef VMS
+#include <LYCurses.h>
+#endif /* VMS */
+
+#ifdef USE_PRETTYSRC
+#include <LYPrettySrc.h>
+#endif
+
+#ifdef USE_COLOR_STYLE
+#include <SGML.h>
+#include <AttrList.h>
+#include <LYHash.h>
+#include <LYStyle.h>
+#undef SELECTED_STYLES
+#define pHText_changeStyle(X,Y,Z) {}
+#endif /* USE_COLOR_STYLE */
+
+#ifdef USE_SOURCE_CACHE
+#include <HTAccess.h>
+#endif
+
+#include <LYCurses.h>
+#include <LYJustify.h>
+
+#include <LYexit.h>
+#include <LYLeaks.h>
+
+#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 </FONT> end tag, for which some incarnations of SGML.c
+ * would fake a <FONT> 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, "<H2><EM>");
+ StrAllocCat(*include, gettext("Description:"));
+ StrAllocCat(*include, "</EM> ");
+ StrAllocCopy(temp,
+ ((MBM_A_subdescript[i] &&
+ *MBM_A_subdescript[i]) ?
+ MBM_A_subdescript[i] : gettext("(none)")));
+ LYEntify(&temp, TRUE);
+ StrAllocCat(*include, temp);
+ StrAllocCat(*include, "<BR><EM>&nbsp;&nbsp;&nbsp;");
+ StrAllocCat(*include, gettext("Filepath:"));
+ StrAllocCat(*include, "</EM> ");
+ 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, "</H2>");
+ 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, "<!--", 4)) {
+ data = LYFindEndOfComment(cp);
+ cp = data;
+ } else if (s == 0 && !strncasecomp(cp, "<PARAM", 6) &&
+ !IsNmChar(cp[6])) {
+ have_param = TRUE;
+ } else if (!strncasecomp(cp, "<OBJECT", 7) &&
+ !IsNmChar(cp[7])) {
+ if (s == 0)
+ start = cp;
+ s++;
+ } else if (!strncasecomp(cp, "</OBJECT", 8) &&
+ !IsNmChar(cp[8])) {
+ if (e == 0)
+ first_end = cp;
+ last_end = cp;
+ e++;
+ } else if (!strncasecomp(cp, "<MAP", 4) &&
+ !IsNmChar(cp[4])) {
+ if (!first_map)
+ first_map = cp;
+ last_map = cp;
+ } else if (!strncasecomp(cp, "</MAP", 5) &&
+ !IsNmChar(cp[5])) {
+ last_map = cp;
+ }
+ data = ++cp;
+ }
+ if (s < e) {
+ /*
+ * We had more end tags than start tags, so we have bad HTML or
+ * otherwise misparsed. - FM
+ */
+ if (LYBadHTML(me)) {
+ char *msg = NULL;
+
+ HTSprintf0(&msg,
+ "Bad HTML: Unmatched OBJECT start and end tags. Discarding content:\n%s\n",
+ me->object.data);
+ LYShowBadHTML(msg);
+ FREE(msg);
+ }
+ goto End_Object;
+ }
+ if (s > e) {
+ if (!me->object_declare && !me->object_name &&
+ !(me->object_shapes && !LYMapsOnly) &&
+ !(me->object_usemap != NULL && !LYMapsOnly) &&
+ !(clickable_images && !LYMapsOnly &&
+ me->object_data != NULL &&
+ !have_param &&
+ me->object_classid == NULL &&
+ me->object_codebase == NULL &&
+ me->object_codetype == NULL)) {
+ /*
+ * We have nested OBJECT tags, and not yet all of the end
+ * tags, but have a case where the content needs to be
+ * parsed again (not dropped) and where we don't want to
+ * output anything special at the point when we
+ * *do* have accumulated all the end tags. So recycle
+ * the incomplete contents now, and signal the SGML parser
+ * that it should not regard the current OBJECT ended but
+ * should treat its contents as mixed. Normally these
+ * cases would have already handled in the real
+ * start_element call, so this block may not be necessary.
+ * - kw
+ */
+ CTRACE((tfp, "%s:\n%s\n",
+ "HTML: Nested OBJECT tags. Recycling incomplete contents",
+ me->object.data));
+ status = HT_PARSER_OTHER_CONTENT;
+ me->object.size--;
+ HTChunkPuts(&me->object, "</OBJECT>");
+ if (!include) /* error, should not happen */
+ include = &me->xinclude;
+ StrnAllocCat(*include, me->object.data, (size_t) me->object.size);
+ clear_objectdata(me);
+ /* an internal fake call to keep our stack happy: */
+ HTML_start_element(me, HTML_OBJECT, NULL, NULL,
+ me->tag_charset, include);
+ break;
+ }
+ /*
+ * We have nested OBJECT tags, and not yet all of the end tags,
+ * and we want the end tags. So restore an end tag to the
+ * content, and signal to the SGML parser that it should resume
+ * the accumulation of OBJECT content (after calling back to
+ * start_element) in a way that is equivalent to passing it a
+ * dummy start tag. - FM, kw
+ */
+ CTRACE((tfp, "HTML: Nested OBJECT tags. Recycling.\n"));
+ status = HT_PARSER_REOPEN_ELT;
+ me->object.size--;
+ HTChunkPuts(&me->object, "</OBJECT>");
+ if (!LYMapsOnly)
+ change_paragraph_style(me, me->sp->style);
+ break;
+ }
+
+ /*
+ * OBJECT start and end tags are fully matched, assuming we weren't
+ * tripped up by comments or quoted attributes. - FM
+ */
+ CTRACE((tfp, "HTML:OBJECT content:\n%s\n", me->object.data));
+
+ /*
+ * OBJECTs with DECLARE should be saved but not instantiated, and
+ * if nested, can have only other DECLAREd OBJECTs. Until we have
+ * code to handle these, we'll just create an anchor for the ID, if
+ * present, and discard the content (sigh 8-). - FM
+ */
+ if (me->object_declare == TRUE) {
+ if (non_empty(me->object_id) && !LYMapsOnly)
+ LYHandleID(me, me->object_id);
+ CTRACE((tfp, "HTML: DECLAREd OBJECT. Ignoring!\n"));
+ goto End_Object;
+ }
+
+ /*
+ * OBJECTs with NAME are for FORM submissions. We'll just create
+ * an anchor for the ID, if present, and discard the content until
+ * we have code to handle these. (sigh 8-). - FM
+ */
+ if (me->object_name != NULL && !LYMapsOnly) {
+ if (non_empty(me->object_id))
+ LYHandleID(me, me->object_id);
+ CTRACE((tfp, "HTML: NAMEd OBJECT. Ignoring!\n"));
+ goto End_Object;
+ }
+
+ /*
+ * Deal with any nested OBJECTs by descending to the inner-most
+ * OBJECT. - FM
+ */
+ if (s > 0) {
+ if (start != NULL &&
+ first_end != NULL && first_end > start) {
+ /*
+ * Minimum requirements for the ad hoc parsing to have
+ * succeeded are met. We'll hope that it did succeed. -
+ * FM
+ */
+ if (LYMapsOnly) {
+ /*
+ * Well we don't need to do this any more, nested
+ * objects should either not get here any more at all
+ * or can be handled fine by other code below. Leave
+ * in place for now as a special case for LYMapsOnly.
+ * - kw
+ */
+ if (LYMapsOnly && (!last_map || last_map < first_end))
+ *first_end = '\0';
+ else
+ e = 0;
+ data = NULL;
+ if (LYMapsOnly && (!first_map || first_map > start))
+ StrAllocCopy(data, start);
+ else
+ StrAllocCopy(data, me->object.data);
+ if (e > 0) {
+ for (i = e; i > 0; i--) {
+ StrAllocCat(data, "</OBJECT>");
+ }
+ }
+ if (!include) /* error, should not happen */
+ include = &me->xinclude;
+ StrAllocCat(*include, data);
+ CTRACE((tfp, "HTML: Recycling nested OBJECT%s.\n",
+ (s > 1) ? "s" : ""));
+ FREE(data);
+ goto End_Object;
+ }
+ } else {
+ if (LYBadHTML(me)) {
+ LYShowBadHTML("Bad HTML: Unmatched OBJECT start and end tags. Discarding content.\n");
+ }
+ goto End_Object;
+ }
+ }
+
+ /*
+ * If its content has SHAPES, convert it to FIG. - FM
+ *
+ * This is now handled in our start_element without using include
+ * if the SGML parser cooperates, so this block may be unnecessary.
+ * - kw
+ */
+ if (me->object_shapes == TRUE && !LYMapsOnly) {
+ CTRACE((tfp, "HTML: OBJECT has SHAPES. Converting to FIG.\n"));
+ if (!include) /* error, should not happen */
+ include = &me->xinclude;
+ StrAllocCat(*include, "<FIG ISOBJECT IMAGEMAP");
+ if (me->object_ismap == TRUE)
+ StrAllocCat(*include, " IMAGEMAP");
+ if (me->object_id != NULL) {
+ StrAllocCat(*include, " ID=\"");
+ StrAllocCat(*include, me->object_id);
+ StrAllocCat(*include, "\"");
+ }
+ if (me->object_data != NULL &&
+ me->object_classid == NULL) {
+ StrAllocCat(*include, " SRC=\"");
+ StrAllocCat(*include, me->object_data);
+ StrAllocCat(*include, "\"");
+ }
+ StrAllocCat(*include, ">");
+ me->object.size--;
+ HTChunkPuts(&me->object, "</FIG>");
+ HTChunkTerminate(&me->object);
+ StrAllocCat(*include, me->object.data);
+ goto End_Object;
+ }
+
+ /*
+ * If it has a USEMAP attribute and didn't have SHAPES, convert it
+ * to IMG. - FM
+ */
+ if (me->object_usemap != NULL && !LYMapsOnly) {
+ CTRACE((tfp, "HTML: OBJECT has USEMAP. Converting to IMG.\n"));
+
+ if (!include) /* error, should not happen */
+ include = &me->xinclude;
+ StrAllocCat(*include, "<IMG ISOBJECT");
+ if (me->object_id != NULL) {
+ /*
+ * Pass the ID. - FM
+ */
+ StrAllocCat(*include, " ID=\"");
+ StrAllocCat(*include, me->object_id);
+ StrAllocCat(*include, "\"");
+ }
+ if (me->object_data != NULL &&
+ me->object_classid == NULL) {
+ /*
+ * We have DATA with no CLASSID, so let's hope it'
+ * equivalent to an SRC. - FM
+ */
+ StrAllocCat(*include, " SRC=\"");
+ StrAllocCat(*include, me->object_data);
+ StrAllocCat(*include, "\"");
+ }
+ if (me->object_title != NULL) {
+ /*
+ * Use the TITLE for both the MAP and the IMGs ALT. - FM
+ */
+ StrAllocCat(*include, " TITLE=\"");
+ StrAllocCat(*include, me->object_title);
+ StrAllocCat(*include, "\" ALT=\"");
+ StrAllocCat(*include, me->object_title);
+ StrAllocCat(*include, "\"");
+ }
+ /*
+ * Add the USEMAP, and an ISMAP if present. - FM
+ */
+ if (me->object_usemap != NULL) {
+ StrAllocCat(*include, " USEMAP=\"");
+ StrAllocCat(*include, me->object_usemap);
+ if (me->object_ismap == TRUE)
+ StrAllocCat(*include, "\" ISMAP>");
+ else
+ StrAllocCat(*include, "\">");
+ } else {
+ StrAllocCat(*include, ">");
+ }
+ /*
+ * Add the content if it has <MAP, since that may be the MAP
+ * this usemap points to. But if we have nested objects, try
+ * to eliminate portions that cannot contribute to the quest
+ * for MAP. This is not perfect, we may get too much content;
+ * this seems preferable over losing too much. - kw
+ */
+ if (first_map) {
+ if (s == 0) {
+ StrAllocCat(*include, me->object.data);
+ CTRACE((tfp,
+ "HTML: MAP found, recycling object contents.\n"));
+ goto End_Object;
+ }
+ /* s > 0 and s == e */
+ data = NULL;
+ if (last_map < start) {
+ *start = '\0';
+ i = 0;
+ } else if (last_map < first_end) {
+ *first_end = '\0';
+ i = e;
+ } else if (last_map < last_end) {
+ *last_end = '\0';
+ i = 1;
+ } else {
+ i = 0;
+ }
+ if (first_map > last_end) {
+ /* fake empty object to keep stacks stack happy */
+ StrAllocCopy(data, "<OBJECT><");
+ StrAllocCat(data, last_end + 1);
+ i = 0;
+ } else if (first_map > start) {
+ StrAllocCopy(data, start);
+ } else {
+ StrAllocCopy(data, me->object.data);
+ }
+ for (; i > 0; i--) {
+ StrAllocCat(data, "</OBJECT>");
+ }
+ CTRACE((tfp, "%s:\n%s\n",
+ "HTML: MAP and nested OBJECT tags. Recycling parts",
+ data));
+ StrAllocCat(*include, data);
+ FREE(data);
+ }
+ goto End_Object;
+ }
+
+ /*
+ * Add an ID link if needed. - FM
+ */
+ if (non_empty(me->object_id) && !LYMapsOnly)
+ LYHandleID(me, me->object_id);
+
+ /*
+ * Add the OBJECTs content if not empty. - FM
+ */
+ if (me->object.size > 1) {
+ if (!include) /* error, should not happen */
+ include = &me->xinclude;
+ StrAllocCat(*include, me->object.data);
+ }
+
+ /*
+ * Create a link to the DATA, if desired, and we can rule out that
+ * it involves scripting code. This a risky thing to do, but we
+ * can toggle clickable_images mode off if it really screws things
+ * up, and so we may as well give it a try. - FM
+ */
+ if (clickable_images) {
+ if (!LYMapsOnly &&
+ me->object_data != NULL &&
+ !have_param &&
+ me->object_classid == NULL &&
+ me->object_codebase == NULL &&
+ me->object_codetype == NULL) {
+ /*
+ * We have a DATA value and no need for scripting code, so
+ * close the current Anchor, if one is open, and add an
+ * Anchor for this source. If we also have a TYPE value,
+ * check whether it's an image or not, and set the link
+ * name accordingly. - FM
+ */
+ if (!include) /* error, should not happen */
+ include = &me->xinclude;
+ if (me->inA)
+ StrAllocCat(*include, "</A>");
+ StrAllocCat(*include, " -<A HREF=\"");
+ StrAllocCat(*include, me->object_data);
+ StrAllocCat(*include, "\">");
+ if ((me->object_type != NULL) &&
+ !strncasecomp(me->object_type, "image/", 6)) {
+ StrAllocCat(*include, "(IMAGE)");
+ } else {
+ StrAllocCat(*include, "(OBJECT)");
+ }
+ StrAllocCat(*include, "</A> ");
+ }
+ }
+ }
+
+ /*
+ * Re-intialize all of the OBJECT elements. - FM
+ */
+ End_Object:
+ clear_objectdata(me);
+
+ if (!LYMapsOnly)
+ change_paragraph_style(me, me->sp->style); /* Often won't really change */
+ break;
+
+ case HTML_APPLET:
+ if (me->inAPPLETwithP) {
+ LYEnsureDoubleSpace(me);
+ } else {
+ HTML_put_character(me, ' '); /* space char may be ignored */
+ }
+ LYResetParagraphAlignment(me);
+ me->inAPPLETwithP = FALSE;
+ me->inAPPLET = FALSE;
+ change_paragraph_style(me, me->sp->style); /* Often won't really change */
+ break;
+
+ case HTML_CAPTION:
+ LYEnsureDoubleSpace(me);
+ LYResetParagraphAlignment(me);
+ me->inCAPTION = FALSE;
+ change_paragraph_style(me, me->sp->style); /* Often won't really change */
+ me->inLABEL = FALSE;
+ break;
+
+ case HTML_CREDIT:
+ LYEnsureDoubleSpace(me);
+ LYResetParagraphAlignment(me);
+ me->inCREDIT = FALSE;
+ change_paragraph_style(me, me->sp->style); /* Often won't really change */
+ me->inLABEL = FALSE;
+ break;
+
+ case HTML_FORM:
+ /*
+ * Check if we had a FORM start tag, and issue a message if not, but
+ * fall through to check for an open SELECT and ensure that the
+ * FORM-related globals in GridText.c are initialized. - FM
+ */
+ if (!me->inFORM) {
+ if (LYBadHTML(me)) {
+ LYShowBadHTML("Bad HTML: Unmatched FORM end tag\n");
+ }
+ }
+ EMIT_IFDEF_USE_JUSTIFY_ELTS(form_in_htext = FALSE);
+
+ /*
+ * Check if we still have a SELECT element open. FORM may have been
+ * declared SGML_EMPTY in HTMLDTD.c, and in that case SGML_character()
+ * in SGML.c is not able to ensure correct nesting; or it may have
+ * failed to enforce valid nesting. If a SELECT is open, issue a
+ * message, then call HTML_end_element() directly (with a check in that
+ * to bypass decrementing of the HTML parser's stack) to close the
+ * SELECT. - kw
+ */
+ if (me->inSELECT) {
+ if (LYBadHTML(me)) {
+ LYShowBadHTML("Bad HTML: Open SELECT at FORM end. Faking SELECT end tag. *****\n");
+ }
+ if (me->sp->tag_number != HTML_SELECT) {
+ SET_SKIP_STACK(HTML_SELECT);
+ }
+ HTML_end_element(me, HTML_SELECT, include);
+ }
+
+ /*
+ * Set to know that we are no longer in an form.
+ */
+ me->inFORM = FALSE;
+
+ HText_endForm(me->text);
+ /*
+ * If we are in a list and are on the first line with no text following
+ * a bullet or number, don't force a newline. This could happen if we
+ * were called from HTML_start_element() due to a missing FORM end tag.
+ * - FM
+ */
+ if (!(me->List_Nesting_Level >= 0 && !me->inP))
+ LYEnsureSingleSpace(me);
+ break;
+
+ case HTML_FIELDSET:
+ LYEnsureDoubleSpace(me);
+ LYResetParagraphAlignment(me);
+ change_paragraph_style(me, me->sp->style); /* Often won't really change */
+ break;
+
+ case HTML_LEGEND:
+ LYEnsureDoubleSpace(me);
+ LYResetParagraphAlignment(me);
+ change_paragraph_style(me, me->sp->style); /* Often won't really change */
+ break;
+
+ case HTML_LABEL:
+ break;
+
+ case HTML_BUTTON:
+ break;
+
+ case HTML_TEXTAREA:
+ {
+ InputFieldData I;
+ int chars;
+ char *data;
+
+ /*
+ * Make sure we had a textarea start tag.
+ */
+ if (!me->inTEXTAREA) {
+ if (LYBadHTML(me)) {
+ LYShowBadHTML("Bad HTML: Unmatched TEXTAREA end tag\n");
+ }
+ break;
+ }
+
+ /*
+ * Set to know that we are no longer in a textarea tag.
+ */
+ me->inTEXTAREA = FALSE;
+
+ /*
+ * Initialize.
+ */
+ memset(&I, 0, sizeof(I));
+ I.value_cs = current_char_set;
+
+ UPDATE_STYLE;
+ /*
+ * Before any input field add a space if necessary.
+ */
+ HTML_put_character(me, ' ');
+ me->in_word = NO;
+ /*
+ * Add a return.
+ */
+ HText_appendCharacter(me->text, '\r');
+
+ /*
+ * Finish the data off.
+ */
+ HTChunkTerminate(&me->textarea);
+ FREE(temp);
+
+ I.type = "textarea";
+ I.size = me->textarea_cols;
+ I.name = me->textarea_name;
+ I.name_cs = me->textarea_name_cs;
+ I.accept_cs = me->textarea_accept_cs;
+ me->textarea_accept_cs = NULL;
+ I.disabled = me->textarea_disabled;
+ I.readonly = me->textarea_readonly;
+ I.id = me->textarea_id;
+
+ /*
+ * Transform the TEXTAREA content as needed, then parse it into
+ * individual lines to be handled as a series series of INPUT
+ * fields (ugh!). Any raw 8-bit or multibyte characters already
+ * have been handled in relation to the display character set in
+ * SGML_character().
+ *
+ * If TEXTAREA is handled as SGML_LITTERAL (the old way), we need
+ * to SGML-unescape any character references and NCRs here.
+ * Otherwise this will already have happened in the SGML.c parsing.
+ * - kw
+ */
+ me->UsePlainSpace = TRUE;
+
+ if (HTML_dtd.tags[element_number].contents == SGML_LITTERAL) {
+ TRANSLATE_AND_UNESCAPE_ENTITIES6(&me->textarea.data,
+ me->UCLYhndl,
+ current_char_set,
+ NO,
+ me->UsePlainSpace, me->HiddenValue);
+ } else {
+ /*
+ * This shouldn't have anything to do, normally, but just in
+ * case... There shouldn't be lynx special character codes in
+ * the chunk ("DTD" flag Tgf_nolyspcl tells SGML.c not to
+ * generate them). If there were, we could set the last
+ * parameter ('Back') below to YES, which would take them out
+ * of the data. The data may however contain non break space,
+ * soft hyphen, or en space etc., in the me->UCLYhndl character
+ * encoding. If that's a problem, perhaps for the (line or
+ * other) editor, setting 'Back' to YES should also help to
+ * always convert them to plain spaces (or drop them). - kw
+ */
+ TRANSLATE_HTML7(&me->textarea.data,
+ me->UCLYhndl,
+ current_char_set,
+ NO,
+ me->UsePlainSpace, me->HiddenValue,
+ NO);
+ }
+ data = me->textarea.data;
+
+ /*
+ * Trim any trailing newlines and skip any lead newlines. - FM
+ */
+ if (*data != '\0') {
+ cp = (data + strlen(data)) - 1;
+ while (cp >= data && *cp == '\n') {
+ *cp-- = '\0';
+ }
+ while (*data == '\n') {
+ data++;
+ }
+ }
+ /*
+ * Load the first text line, or set up for all blank rows. - FM
+ */
+ if ((cp = StrChr(data, '\n')) != NULL) {
+ *cp = '\0';
+ StrAllocCopy(temp, data);
+ *cp = '\n';
+ data = NULL; /* HTML_put_characters may overwrite this */
+ StrAllocCopy(data, cp + 1);
+ } else {
+ if (*data != '\0') {
+ StrAllocCopy(temp, data);
+ } else {
+ FREE(temp);
+ }
+ data = empty;
+ }
+ /*
+ * Display at least the requested number of text lines and/or blank
+ * rows. - FM
+ */
+ for (i = 0; i < me->textarea_rows; i++) {
+ int j;
+
+ for (j = 0; temp && temp[j]; j++) {
+ if (temp[j] == '\r')
+ temp[j] = (char) (temp[j + 1] ? ' ' : '\0');
+ }
+ I.value = temp;
+ chars = HText_beginInput(me->text, me->inUnderline, &I);
+ for (; chars > 0; chars--)
+ HTML_put_character(me, '_');
+ HText_appendCharacter(me->text, '\r');
+ if (*data != '\0') {
+ if (*data == '\n') {
+ FREE(temp);
+ data++;
+ } else if ((cp = StrChr(data, '\n')) != NULL) {
+ *cp = '\0';
+ StrAllocCopy(temp, data);
+ *cp = '\n';
+ data = (cp + 1);
+ } else {
+ StrAllocCopy(temp, data);
+ data = empty;
+ }
+ } else {
+ FREE(temp);
+ }
+ }
+ /*
+ * Check for more data lines than the rows attribute. We add them
+ * to the display, because we support only horizontal and not also
+ * vertical scrolling. - FM
+ */
+ while (*data != '\0' || temp != NULL) {
+ int j;
+
+ for (j = 0; temp && temp[j]; j++) {
+ if (temp[j] == '\r')
+ temp[j] = (char) (temp[j + 1] ? ' ' : '\0');
+ }
+ I.value = temp;
+ (void) HText_beginInput(me->text, me->inUnderline, &I);
+ for (chars = me->textarea_cols; chars > 0; chars--)
+ HTML_put_character(me, '_');
+ HText_appendCharacter(me->text, '\r');
+ if (*data == '\n') {
+ FREE(temp);
+ data++;
+ } else if ((cp = StrChr(data, '\n')) != NULL) {
+ *cp = '\0';
+ StrAllocCopy(temp, data);
+ *cp = '\n';
+ data = (cp + 1);
+ } else if (*data != '\0') {
+ StrAllocCopy(temp, data);
+ data = empty;
+ } else {
+ FREE(temp);
+ }
+ }
+ if (data != empty) {
+ FREE(data);
+ }
+ FREE(temp);
+ cp = NULL;
+ me->UsePlainSpace = FALSE;
+
+ HTChunkClear(&me->textarea);
+ FREE(me->textarea_name);
+ me->textarea_name_cs = -1;
+ FREE(me->textarea_id);
+ break;
+ }
+
+ case HTML_SELECT:
+ {
+ char *ptr = NULL;
+
+ /*
+ * Make sure we had a select start tag.
+ */
+ if (!me->inSELECT) {
+ if (LYBadHTML(me)) {
+ LYShowBadHTML("Bad HTML: Unmatched SELECT end tag *****\n");
+ }
+ break;
+ }
+
+ /*
+ * Set to know that we are no longer in a select tag.
+ */
+ me->inSELECT = FALSE;
+
+ /*
+ * Clear the disable attribute.
+ */
+ me->select_disabled = FALSE;
+
+ /*
+ * Make sure we're in a form.
+ */
+ if (!me->inFORM) {
+ if (LYBadHTML(me)) {
+ LYShowBadHTML("Bad HTML: SELECT end tag not within FORM element *****\n");
+ }
+ /*
+ * Hopefully won't crash, so we'll ignore it. - kw
+ */
+ }
+
+ /*
+ * Finish the data off.
+ */
+ HTChunkTerminate(&me->option);
+ /*
+ * Finish the previous option.
+ */
+ if (!me->first_option)
+ ptr = HText_setLastOptionValue(me->text,
+ me->option.data,
+ me->LastOptionValue,
+ LAST_ORDER,
+ me->LastOptionChecked,
+ me->UCLYhndl,
+ ATTR_CS_IN);
+ FREE(me->LastOptionValue);
+
+ me->LastOptionChecked = FALSE;
+
+ if (HTCurSelectGroupType == F_CHECKBOX_TYPE ||
+ LYSelectPopups == FALSE) {
+ /*
+ * Start a newline after the last checkbox/button option.
+ */
+ LYEnsureSingleSpace(me);
+ } else {
+ /*
+ * Output popup box with the default option to screen, but use
+ * non-breaking spaces for output.
+ */
+ if (ptr &&
+ (me->sp[0].tag_number == HTML_PRE || me->inPRE == TRUE ||
+ !me->sp->style->freeFormat) &&
+ strlen(ptr) > 6) {
+ /*
+ * The code inadequately handles OPTION fields in PRE tags.
+ * We'll put up a minimum of 6 characters, and if any more
+ * would exceed the wrap column, we'll ignore them.
+ */
+ for (i = 0; i < 6; i++) {
+ if (*ptr == ' ')
+ HText_appendCharacter(me->text, HT_NON_BREAK_SPACE);
+ else
+ HText_appendCharacter(me->text, *ptr);
+ ptr++;
+ }
+ }
+ for (; non_empty(ptr); ptr++) {
+ if (*ptr == ' ')
+ HText_appendCharacter(me->text, HT_NON_BREAK_SPACE);
+ else {
+ HTkcode kcode = NOKANJI;
+ HTkcode specified_kcode = NOKANJI;
+
+ 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);
+ }
+ HText_appendCharacter(me->text, *ptr);
+ if (HTCJK == JAPANESE) {
+ HText_updateKcode(me->text, kcode);
+ HText_updateSpecifiedKcode(me->text, specified_kcode);
+ }
+ }
+ }
+ /*
+ * Add end option character.
+ */
+ if (!me->first_option) {
+ HText_appendCharacter(me->text, ']');
+ HText_endInput(me->text);
+ HText_setLastChar(me->text, ']');
+ me->in_word = YES;
+ }
+ }
+ HTChunkClear(&me->option);
+
+ if (me->Underline_Level > 0 && me->inUnderline == FALSE) {
+ HText_appendCharacter(me->text, LY_UNDERLINE_START_CHAR);
+ me->inUnderline = TRUE;
+ }
+ if (me->needBoldH == TRUE && me->inBoldH == FALSE) {
+ HText_appendCharacter(me->text, LY_BOLD_START_CHAR);
+ me->inBoldH = TRUE;
+ me->needBoldH = FALSE;
+ }
+ }
+ break;
+
+ case HTML_TABLE:
+#ifdef EXP_NESTED_TABLES
+ if (!nested_tables)
+#endif
+ me->inTABLE = FALSE;
+
+ if (me->sp->style->id == ST_Preformatted) {
+ break;
+ }
+ if (me->Division_Level >= 0)
+ me->Division_Level--;
+ if (me->Division_Level >= 0)
+ me->sp->style->alignment =
+ me->DivisionAlignments[me->Division_Level];
+ change_paragraph_style(me, me->sp->style);
+ UPDATE_STYLE;
+
+#ifdef EXP_NESTED_TABLES
+ if (nested_tables) {
+ me->inTABLE = HText_endStblTABLE(me->text);
+ } else {
+ HText_endStblTABLE(me->text);
+ }
+#else
+ HText_endStblTABLE(me->text);
+#endif
+
+ me->current_default_alignment = me->sp->style->alignment;
+ if (me->List_Nesting_Level >= 0)
+ HText_NegateLineOne(me->text);
+ break;
+
+/* These TABLE related elements may now not be SGML_EMPTY. - kw */
+ case HTML_TR:
+ HText_endStblTR(me->text);
+ if (!HText_LastLineEmpty(me->text, FALSE)) {
+ HText_setLastChar(me->text, ' '); /* absorb next white space */
+ HText_appendCharacter(me->text, '\r');
+ }
+ me->in_word = NO;
+ break;
+
+ case HTML_THEAD:
+ case HTML_TFOOT:
+ case HTML_TBODY:
+ break;
+
+ case HTML_COLGROUP:
+ if (me->inTABLE)
+ HText_endStblCOLGROUP(me->text);
+ break;
+
+ case HTML_TH:
+ case HTML_TD:
+ HText_endStblTD(me->text);
+ break;
+
+/* More stuff that may now not be SGML_EMPTY any more: */
+ case HTML_DT:
+ case HTML_DD:
+ case HTML_LH:
+ case HTML_LI:
+ case HTML_OVERLAY:
+ 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
+ */
+ HTChunkPutc(&me->math, ' ');
+ HTChunkTerminate(&me->math);
+ if (me->math.size > 2) {
+ LYEnsureSingleSpace(me);
+ if (me->inUnderline == FALSE)
+ HText_appendCharacter(me->text, LY_UNDERLINE_START_CHAR);
+ HTML_put_string(me, "[MATH:");
+ HText_appendCharacter(me->text, LY_UNDERLINE_END_CHAR);
+ HTML_put_character(me, ' ');
+ HTML_put_string(me, me->math.data);
+ HText_appendCharacter(me->text, LY_UNDERLINE_START_CHAR);
+ HTML_put_string(me, ":MATH]");
+ if (me->inUnderline == FALSE)
+ HText_appendCharacter(me->text, LY_UNDERLINE_END_CHAR);
+ LYEnsureSingleSpace(me);
+ }
+ HTChunkClear(&me->math);
+ break;
+
+ default:
+ change_paragraph_style(me, me->sp->style); /* Often won't really change */
+ break;
+
+ } /* switch */
+
+#ifdef USE_JUSTIFY_ELTS
+ if (reached_awaited_stacked_elt)
+ wait_for_this_stacked_elt = -1;
+#endif
+
+ if (me->xinclude) {
+ HText_appendText(me->text, " *** LYNX ERROR ***\rUnparsed data:\r");
+ HText_appendText(me->text, me->xinclude);
+ FREE(me->xinclude);
+ }
+#ifdef USE_COLOR_STYLE
+ if (!skip_stack_requested) { /*don't emit stylechanges if skipped stack element - VH */
+ FastTrimColorClass(HTML_dtd.tags[element_number].name,
+ HTML_dtd.tags[element_number].name_len,
+ Style_className,
+ &Style_className_end, &hcode);
+
+ if (!ReallyEmptyTagNum(element_number)) {
+ CTRACE2(TRACE_STYLE,
+ (tfp,
+ "STYLE.end_element: ending non-\"EMPTY\" style <%s...>\n",
+ HTML_dtd.tags[element_number].name));
+ HText_characterStyle(me->text, hcode, STACK_OFF);
+ }
+ }
+#endif /* USE_COLOR_STYLE */
+ return status;
+}
+
+/* Expanding entities
+ * ------------------
+ */
+/* (In fact, they all shrink!)
+*/
+int HTML_put_entity(HTStructured * me, int entity_number)
+{
+ int nent = (int) HTML_dtd.number_of_entities;
+
+ if (entity_number < nent) {
+ HTML_put_string(me, p_entity_values[entity_number]);
+ return HT_OK;
+ }
+ return HT_CANNOT_TRANSLATE;
+}
+
+/* Free an HTML object
+ * -------------------
+ *
+ * If the document is empty, the text object will not yet exist.
+ * So we could in fact abandon creating the document and return
+ * an error code. In fact an empty document is an important type
+ * of document, so we don't.
+ *
+ * If non-interactive, everything is freed off. No: crashes -listrefs
+ * Otherwise, the interactive object is left.
+ */
+static void HTML_free(HTStructured * me)
+{
+ char *include = NULL;
+
+ if (LYMapsOnly && !me->text) {
+ /*
+ * We only handled MAP, AREA and BASE tags, and didn't create an HText
+ * structure for the document nor want one now, so just make sure we
+ * free anything that might have been allocated. - FM
+ */
+ FREE(me->base_href);
+ FREE(me->map_address);
+ clear_objectdata(me);
+ FREE(me->xinclude);
+ FREE(me);
+ return;
+ }
+
+ UPDATE_STYLE; /* Creates empty document here! */
+ if (me->comment_end)
+ HTML_put_string(me, me->comment_end);
+ if (me->text) {
+ /*
+ * Emphasis containers, A, FONT, and FORM may be declared SGML_EMPTY in
+ * HTMLDTD.c, and SGML_character() in SGML.c may check for their end
+ * tags to call HTML_end_element() directly (with a check in that to
+ * bypass decrementing of the HTML parser's stack). So if we still
+ * have the emphasis (Underline) on, or any open A, FONT, or FORM
+ * containers, turn it off or close them now. - FM & kw
+ *
+ * IF those tags are not declared SGML_EMPTY, but we let the SGML.c
+ * parser take care of correctly stacked ordering, and of correct
+ * wind-down on end-of-stream (in SGML_free SGML_abort), THEN these and
+ * other checks here in HTML.c should not be necessary. Still it can't
+ * hurt to include them. - kw
+ */
+ if (me->inUnderline) {
+ HText_appendCharacter(me->text, LY_UNDERLINE_END_CHAR);
+ me->inUnderline = FALSE;
+ me->Underline_Level = 0;
+ CTRACE((tfp, "HTML_free: Ending underline\n"));
+ }
+ if (me->inA) {
+ HTML_end_element(me, HTML_A, &include);
+ me->inA = FALSE;
+ CTRACE((tfp, "HTML_free: Ending HTML_A\n"));
+ }
+ if (me->inFONT) {
+ HTML_end_element(me, HTML_FONT, &include);
+ me->inFONT = FALSE;
+ }
+ if (me->inFORM) {
+ HTML_end_element(me, HTML_FORM, &include);
+ me->inFORM = FALSE;
+ }
+ if (me->option.size > 0) {
+ /*
+ * If we still have data in the me->option chunk after forcing a
+ * close of a still-open form, something must have gone very wrong.
+ * - kw
+ */
+ if (LYBadHTML(me)) {
+ LYShowBadHTML("Bad HTML: SELECT or OPTION not ended properly *****\n");
+ }
+ HTChunkTerminate(&me->option);
+ /*
+ * Output the left-over data as text, maybe it was invalid markup
+ * meant to be shown somewhere. - kw
+ */
+ CTRACE((tfp, "HTML_free: ***** leftover option data: %s\n",
+ me->option.data));
+ HTML_put_string(me, me->option.data);
+ HTChunkClear(&me->option);
+ }
+ if (me->textarea.size > 0) {
+ /*
+ * If we still have data in the me->textarea chunk after forcing a
+ * close of a still-open form, something must have gone very wrong.
+ * - kw
+ */
+ if (LYBadHTML(me)) {
+ LYShowBadHTML("Bad HTML: TEXTAREA not used properly *****\n");
+ }
+ HTChunkTerminate(&me->textarea);
+ /*
+ * Output the left-over data as text, maybe it was invalid markup
+ * meant to be shown somewhere. - kw
+ */
+ CTRACE((tfp, "HTML_free: ***** leftover textarea data: %s\n",
+ me->textarea.data));
+ HTML_put_string(me, me->textarea.data);
+ HTChunkClear(&me->textarea);
+ }
+ /*
+ * If we're interactive and have hidden links but no visible links, add
+ * a message informing the user about this and suggesting use of the
+ * 'l'ist command. - FM
+ */
+ if (!dump_output_immediately &&
+ HText_sourceAnchors(me->text) < 1 &&
+ HText_HiddenLinkCount(me->text) > 0) {
+ HTML_start_element(me, HTML_P, 0, 0, -1, &include);
+ HTML_put_character(me, '[');
+ HTML_start_element(me, HTML_EM, 0, 0, -1, &include);
+ HTML_put_string(me,
+ gettext("Document has only hidden links. Use the 'l'ist command."));
+ HTML_end_element(me, HTML_EM, &include);
+ HTML_put_character(me, ']');
+ HTML_end_element(me, HTML_P, &include);
+ }
+ if (me->xinclude) {
+ HText_appendText(me->text, " *** LYNX ERROR ***\rUnparsed data:\r");
+ HText_appendText(me->text, me->xinclude);
+ FREE(me->xinclude);
+ }
+
+ /*
+ * Now call the cleanup function. - FM
+ */
+ HText_endAppend(me->text);
+ }
+ if (me->option.size > 0) {
+ /*
+ * If we still have data in the me->option chunk after forcing a close
+ * of a still-open form, something must have gone very wrong. - kw
+ */
+ if (LYBadHTML(me)) {
+ LYShowBadHTML("Bad HTML: SELECT or OPTION not ended properly *****\n");
+ }
+ if (TRACE) {
+ HTChunkTerminate(&me->option);
+ CTRACE((tfp, "HTML_free: ***** leftover option data: %s\n",
+ me->option.data));
+ }
+ HTChunkClear(&me->option);
+ }
+ if (me->textarea.size > 0) {
+ /*
+ * If we still have data in the me->textarea chunk after forcing a
+ * close of a still-open form, something must have gone very wrong. -
+ * kw
+ */
+ if (LYBadHTML(me)) {
+ LYShowBadHTML("Bad HTML: TEXTAREA not used properly *****\n");
+ }
+ if (TRACE) {
+ HTChunkTerminate(&me->textarea);
+ CTRACE((tfp, "HTML_free: ***** leftover textarea data: %s\n",
+ me->textarea.data));
+ }
+ HTChunkClear(&me->textarea);
+ }
+
+ if (me->target) {
+ (*me->targetClass._free) (me->target);
+ }
+ if (me->sp && me->sp->style && GetHTStyleName(me->sp->style)) {
+ if (me->sp->style->id == ST_DivCenter ||
+ 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_DivRight ||
+ me->sp->style->id == ST_HeadingRight) {
+ me->sp->style->alignment = HT_RIGHT;
+ } else {
+ me->sp->style->alignment = HT_LEFT;
+ }
+ styles[HTML_PRE]->alignment = HT_LEFT;
+ }
+ FREE(me->base_href);
+ FREE(me->map_address);
+ FREE(me->LastOptionValue);
+ clear_objectdata(me);
+ FREE(me);
+}
+
+static void HTML_abort(HTStructured * me, HTError e)
+{
+ char *include = NULL;
+
+ if (me->text) {
+ /*
+ * If we have emphasis on, or open A, FONT, or FORM containers, turn it
+ * off or close them now. - FM
+ */
+ if (me->inUnderline) {
+ HText_appendCharacter(me->text, LY_UNDERLINE_END_CHAR);
+ me->inUnderline = FALSE;
+ me->Underline_Level = 0;
+ }
+ if (me->inA) {
+ HTML_end_element(me, HTML_A, &include);
+ me->inA = FALSE;
+ }
+ if (me->inFONT) {
+ HTML_end_element(me, HTML_FONT, &include);
+ me->inFONT = FALSE;
+ }
+ if (me->inFORM) {
+ HTML_end_element(me, HTML_FORM, &include);
+ me->inFORM = FALSE;
+ }
+
+ /*
+ * Now call the cleanup function. - FM
+ */
+ HText_endAppend(me->text);
+ }
+
+ if (me->option.size > 0) {
+ /*
+ * If we still have data in the me->option chunk after forcing a close
+ * of a still-open form, something must have gone very wrong. - kw
+ */
+ if (TRACE) {
+ CTRACE((tfp,
+ "HTML_abort: SELECT or OPTION not ended properly *****\n"));
+ HTChunkTerminate(&me->option);
+ CTRACE((tfp, "HTML_abort: ***** leftover option data: %s\n",
+ me->option.data));
+ }
+ HTChunkClear(&me->option);
+ }
+ if (me->textarea.size > 0) {
+ /*
+ * If we still have data in the me->textarea chunk after forcing a
+ * close of a still-open form, something must have gone very wrong. -
+ * kw
+ */
+ if (TRACE) {
+ CTRACE((tfp, "HTML_abort: TEXTAREA not used properly *****\n"));
+ HTChunkTerminate(&me->textarea);
+ CTRACE((tfp, "HTML_abort: ***** leftover textarea data: %s\n",
+ me->textarea.data));
+ }
+ HTChunkClear(&me->textarea);
+ }
+
+ if (me->target) {
+ (*me->targetClass._abort) (me->target, e);
+ }
+ if (me->sp && me->sp->style && GetHTStyleName(me->sp->style)) {
+ if (me->sp->style->id == ST_DivCenter ||
+ 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_DivRight ||
+ me->sp->style->id == ST_HeadingRight) {
+ me->sp->style->alignment = HT_RIGHT;
+ } else {
+ me->sp->style->alignment = HT_LEFT;
+ }
+ styles[HTML_PRE]->alignment = HT_LEFT;
+ }
+ FREE(me->base_href);
+ FREE(me->map_address);
+ FREE(me->textarea_name);
+ FREE(me->textarea_accept_cs);
+ FREE(me->textarea_id);
+ FREE(me->LastOptionValue);
+ FREE(me->xinclude);
+ clear_objectdata(me);
+ FREE(me);
+}
+
+/* Get Styles from style sheet
+ * ---------------------------
+ */
+static void get_styles(void)
+{
+ HTStyle **st = NULL;
+
+ styleSheet = DefaultStyle(&st); /* sets st[] array */
+
+ default_style = st[ST_Normal];
+
+ styles[HTML_H1] = st[ST_Heading1];
+ styles[HTML_H2] = st[ST_Heading2];
+ styles[HTML_H3] = st[ST_Heading3];
+ styles[HTML_H4] = st[ST_Heading4];
+ styles[HTML_H5] = st[ST_Heading5];
+ styles[HTML_H6] = st[ST_Heading6];
+ styles[HTML_HCENTER] = st[ST_HeadingCenter];
+ styles[HTML_HLEFT] = st[ST_HeadingLeft];
+ styles[HTML_HRIGHT] = st[ST_HeadingRight];
+
+ styles[HTML_DCENTER] = st[ST_DivCenter];
+ styles[HTML_DLEFT] = st[ST_DivLeft];
+ styles[HTML_DRIGHT] = st[ST_DivRight];
+
+ styles[HTML_DL] = st[ST_Glossary];
+ /* nested list styles */
+ styles[HTML_DL1] = st[ST_Glossary1];
+ styles[HTML_DL2] = st[ST_Glossary2];
+ styles[HTML_DL3] = st[ST_Glossary3];
+ styles[HTML_DL4] = st[ST_Glossary4];
+ styles[HTML_DL5] = st[ST_Glossary5];
+ styles[HTML_DL6] = st[ST_Glossary6];
+
+ styles[HTML_UL] =
+ styles[HTML_OL] = st[ST_List];
+ /* nested list styles */
+ styles[HTML_OL1] = st[ST_List1];
+ styles[HTML_OL2] = st[ST_List2];
+ styles[HTML_OL3] = st[ST_List3];
+ styles[HTML_OL4] = st[ST_List4];
+ styles[HTML_OL5] = st[ST_List5];
+ styles[HTML_OL6] = st[ST_List6];
+
+ styles[HTML_MENU] =
+ styles[HTML_DIR] = st[ST_Menu];
+ /* nested list styles */
+ styles[HTML_MENU1] = st[ST_Menu1];
+ styles[HTML_MENU2] = st[ST_Menu2];
+ styles[HTML_MENU3] = st[ST_Menu3];
+ styles[HTML_MENU4] = st[ST_Menu4];
+ styles[HTML_MENU5] = st[ST_Menu5];
+ styles[HTML_MENU6] = st[ST_Menu6];
+
+ styles[HTML_DLC] = st[ST_GlossaryCompact];
+ /* nested list styles */
+ styles[HTML_DLC1] = st[ST_GlossaryCompact1];
+ styles[HTML_DLC2] = st[ST_GlossaryCompact2];
+ styles[HTML_DLC3] = st[ST_GlossaryCompact3];
+ styles[HTML_DLC4] = st[ST_GlossaryCompact4];
+ styles[HTML_DLC5] = st[ST_GlossaryCompact5];
+ styles[HTML_DLC6] = st[ST_GlossaryCompact6];
+
+ styles[HTML_ADDRESS] = st[ST_Address];
+ styles[HTML_BANNER] = st[ST_Banner];
+ styles[HTML_BLOCKQUOTE] = st[ST_Blockquote];
+ styles[HTML_BQ] = st[ST_Bq];
+ styles[HTML_FN] = st[ST_Footnote];
+ styles[HTML_NOTE] = st[ST_Note];
+ styles[HTML_PLAINTEXT] =
+ styles[HTML_XMP] = st[ST_Example];
+ styles[HTML_PRE] = st[ST_Preformatted];
+ styles[HTML_LISTING] = st[ST_Listing];
+}
+
+/*
+ * If we're called from another module, make sure we've initialized styles
+ * array first.
+ */
+HTStyle *LYstyles(int style_number)
+{
+ if (styles[style_number] == 0)
+ get_styles();
+ return styles[style_number];
+}
+
+/* P U B L I C
+*/
+
+/* Structured Object Class
+ * -----------------------
+ */
+const HTStructuredClass HTMLPresentation = /* As opposed to print etc */
+{
+ "Lynx_HTML_Handler",
+ HTML_free,
+ HTML_abort,
+ HTML_put_character, HTML_put_string, HTML_write,
+ HTML_start_element, HTML_end_element,
+ HTML_put_entity
+};
+
+/* New Structured Text object
+ * --------------------------
+ *
+ * The structured stream can generate either presentation,
+ * or plain text, or HTML.
+ */
+HTStructured *HTML_new(HTParentAnchor *anchor,
+ HTFormat format_out,
+ HTStream *stream)
+{
+
+ HTStructured *me;
+
+ CTRACE((tfp, "start HTML_new(parent %s, format %s)\n",
+ ((anchor)
+ ? NONNULL(anchor->address)
+ : "<NULL>"),
+ HTAtom_name(format_out)));
+
+ if (format_out != WWW_PLAINTEXT && format_out != WWW_PRESENT) {
+ HTStream *intermediate = HTStreamStack(WWW_HTML, format_out,
+ stream, anchor);
+
+ if (intermediate)
+ return HTMLGenerator(intermediate);
+ fprintf(stderr, "\n** Internal error: can't parse HTML to %s\n",
+ HTAtom_name(format_out));
+ exit_immediately(EXIT_FAILURE);
+ }
+
+ me = typecalloc(HTStructured);
+ if (me == NULL)
+ outofmem(__FILE__, "HTML_new");
+
+ /*
+ * This used to call 'get_styles()' only on the first time through this
+ * function. However, if the user reloads a page with ^R, the styles[]
+ * array is not necessarily the same as it was from 'get_styles()'. So
+ * we reinitialize the whole thing.
+ */
+ get_styles();
+
+ me->isa = &HTMLPresentation;
+ me->node_anchor = anchor;
+
+ me->CurrentA = NULL;
+ me->CurrentANum = 0;
+ me->base_href = NULL;
+ me->map_address = NULL;
+
+ HTChunkInit(&me->title, 128);
+
+ HTChunkInit(&me->object, 128);
+ me->object_started = FALSE;
+ me->object_declare = FALSE;
+ me->object_shapes = FALSE;
+ me->object_ismap = FALSE;
+ me->object_id = NULL;
+ me->object_title = NULL;
+ me->object_data = NULL;
+ me->object_type = NULL;
+ me->object_classid = NULL;
+ me->object_codebase = NULL;
+ me->object_codetype = NULL;
+ me->object_usemap = NULL;
+ me->object_name = NULL;
+
+ HTChunkInit(&me->option, 128);
+ me->first_option = TRUE;
+ me->LastOptionValue = NULL;
+ me->LastOptionChecked = FALSE;
+ me->select_disabled = FALSE;
+
+ HTChunkInit(&me->textarea, 128);
+ me->textarea_name = NULL;
+ me->textarea_name_cs = -1;
+ me->textarea_accept_cs = NULL;
+ me->textarea_cols = 0;
+ me->textarea_rows = 4;
+ me->textarea_id = NULL;
+
+ HTChunkInit(&me->math, 128);
+
+ HTChunkInit(&me->style_block, 128);
+
+ HTChunkInit(&me->script, 128);
+
+ me->text = 0;
+ me->style_change = YES; /* Force check leading to text creation */
+ me->new_style = default_style;
+ me->old_style = 0;
+ me->current_default_alignment = HT_LEFT;
+ me->sp = (me->stack + MAX_NESTING - 1);
+ me->skip_stack = 0;
+ me->sp->tag_number = -1; /* INVALID */
+ me->sp->style = default_style; /* INVALID */
+ me->sp->style->alignment = HT_LEFT;
+ me->stack_overrun = FALSE;
+
+ me->Division_Level = -1;
+ me->Underline_Level = 0;
+ me->Quote_Level = 0;
+
+ me->UsePlainSpace = FALSE;
+ me->HiddenValue = FALSE;
+ me->lastraw = -1;
+
+ /*
+ * Used for nested lists. - FM
+ */
+ me->List_Nesting_Level = -1; /* counter for list nesting level */
+ LYZero_OL_Counter(me); /* Initializes OL_Counter[] and OL_Type[] */
+ me->Last_OL_Count = 0; /* last count in ordered lists */
+ me->Last_OL_Type = '1'; /* last type in ordered lists */
+
+ me->inA = FALSE;
+ me->inAPPLET = FALSE;
+ me->inAPPLETwithP = FALSE;
+ me->inBadHREF = FALSE;
+ me->inBadHTML = FALSE;
+ me->inBASE = FALSE;
+ me->node_anchor->inBASE = FALSE;
+ me->inBoldA = FALSE;
+ me->inBoldH = FALSE;
+ me->inCAPTION = FALSE;
+ me->inCREDIT = FALSE;
+ me->inFIG = FALSE;
+ me->inFIGwithP = FALSE;
+ me->inFONT = FALSE;
+ me->inFORM = FALSE;
+ me->inLABEL = FALSE;
+ me->inP = FALSE;
+ me->inPRE = FALSE;
+ me->inSELECT = FALSE;
+ me->inTABLE = FALSE;
+ me->inUnderline = FALSE;
+
+ me->needBoldH = FALSE;
+
+ me->comment_start = NULL;
+ me->comment_end = NULL;
+
+#ifdef USE_COLOR_STYLE
+#ifdef LY_FIND_LEAKS
+ if (Style_className == 0) {
+ atexit(free_Style_className);
+ }
+#endif
+ addClassName("", "", (size_t) 0);
+ class_string[0] = '\0';
+#endif
+
+ /*
+ * Create a chartrans stage info structure for the anchor, if it does not
+ * exist already (in which case the default MIME stage info will be loaded
+ * as well), and load the HTML stage info into me->UCI and me->UCLYhndl. -
+ * FM
+ */
+ LYGetChartransInfo(me);
+ UCTransParams_clear(&me->T);
+
+ /*
+ * Load the existing or default input charset info into the holding
+ * elements. We'll believe what is indicated for UCT_STAGE_PARSER. - FM
+ */
+ me->inUCLYhndl = HTAnchor_getUCLYhndl(me->node_anchor,
+ UCT_STAGE_PARSER);
+ if (me->inUCLYhndl < 0) {
+ me->inUCLYhndl = HTAnchor_getUCLYhndl(me->node_anchor,
+ UCT_STAGE_MIME);
+ me->inUCI = HTAnchor_getUCInfoStage(me->node_anchor,
+ UCT_STAGE_MIME);
+ } else {
+ me->inUCI = HTAnchor_getUCInfoStage(me->node_anchor,
+ UCT_STAGE_PARSER);
+ }
+
+ /*
+ * Load the existing or default output charset info into the holding
+ * elements, UCT_STAGE_STRUCTURED should be the same as UCT_STAGE_TEXT at
+ * this point, but we could check, perhaps. - FM
+ */
+ me->outUCI = HTAnchor_getUCInfoStage(me->node_anchor,
+ UCT_STAGE_STRUCTURED);
+ me->outUCLYhndl = HTAnchor_getUCLYhndl(me->node_anchor,
+ UCT_STAGE_STRUCTURED);
+
+ me->target = stream;
+ if (stream)
+ me->targetClass = *stream->isa; /* Copy pointers */
+
+ return (HTStructured *) me;
+}
+
+#ifdef USE_SOURCE_CACHE
+
+/*
+ * A flag set by a file write error. Used for only generating an alert the
+ * first time such an error happens, since Lynx should still be usable if the
+ * temp space becomes full, and an alert each time a cache file cannot be
+ * written would be annoying. Reset when lynx.cfg is being reloaded (user may
+ * change SOURCE_CACHE setting). - kw
+ */
+BOOLEAN source_cache_file_error = FALSE;
+
+/*
+ * Pass-thru cache HTStream
+ */
+
+static void CacheThru_do_free(HTStream *me)
+{
+ if (me->anchor->source_cache_file) {
+ CTRACE((tfp, "SourceCacheWriter: Removing previous file %s\n",
+ me->anchor->source_cache_file));
+ (void) LYRemoveTemp(me->anchor->source_cache_file);
+ FREE(me->anchor->source_cache_file);
+ }
+ if (me->anchor->source_cache_chunk) {
+ CTRACE((tfp, "SourceCacheWriter: Removing previous memory chunk %p\n",
+ (void *) me->anchor->source_cache_chunk));
+ HTChunkFree(me->anchor->source_cache_chunk);
+ me->anchor->source_cache_chunk = NULL;
+ }
+ if (me->fp) {
+ fflush(me->fp);
+ if (ferror(me->fp))
+ me->status = HT_ERROR;
+ LYCloseTempFP(me->fp);
+ if (me->status == HT_OK) {
+ char *cp_freeme = 0;
+
+ me->anchor->source_cache_file = me->filename;
+ CTRACE((tfp,
+ "SourceCacheWriter: Committing file %s for URL %s to anchor\n",
+ me->filename,
+ cp_freeme = HTAnchor_address((HTAnchor *) me->anchor)));
+ FREE(cp_freeme);
+ } else {
+ if (source_cache_file_error == FALSE) {
+ HTAlert(gettext("Source cache error - disk full?"));
+ source_cache_file_error = TRUE;
+ }
+ (void) LYRemoveTemp(me->filename);
+ me->anchor->source_cache_file = NULL;
+ }
+ } else if (me->status != HT_OK) {
+ if (me->chunk) {
+ CTRACE((tfp, "SourceCacheWriter: memory chunk %p had errors.\n",
+ (void *) me->chunk));
+ HTChunkFree(me->chunk);
+ me->chunk = me->last_chunk = NULL;
+ }
+ HTAlert(gettext("Source cache error - not enough memory!"));
+ }
+ if (me->chunk) {
+ char *cp_freeme = NULL;
+
+ me->anchor->source_cache_chunk = me->chunk;
+ CTRACE((tfp,
+ "SourceCacheWriter: Committing memory chunk %p for URL %s to anchor\n",
+ (void *) me->chunk,
+ cp_freeme = HTAnchor_address((HTAnchor *) me->anchor)));
+ FREE(cp_freeme);
+ }
+}
+
+static void CacheThru_free(HTStream *me)
+{
+ CacheThru_do_free(me);
+ (*me->actions->_free) (me->target);
+ FREE(me);
+}
+
+static void CacheThru_abort(HTStream *me, HTError e)
+{
+ if (me->fp)
+ LYCloseTempFP(me->fp);
+ if (LYCacheSourceForAborted == SOURCE_CACHE_FOR_ABORTED_DROP) {
+ if (me->filename) {
+ CTRACE((tfp, "SourceCacheWriter: Removing active file %s\n",
+ me->filename));
+ (void) LYRemoveTemp(me->filename);
+ FREE(me->filename);
+ }
+ if (me->chunk) {
+ CTRACE((tfp,
+ "SourceCacheWriter: Removing active memory chunk %p\n",
+ (void *) me->chunk));
+ HTChunkFree(me->chunk);
+ }
+ } else {
+ me->status = HT_OK; /*fake it */
+ CacheThru_do_free(me);
+ }
+ (*me->actions->_abort) (me->target, e);
+ FREE(me);
+}
+
+/*
+ * FIXME: never used!
+ */
+static void CacheThru_put_character(HTStream *me, int c_in)
+{
+ if (me->status == HT_OK) {
+ if (me->fp) {
+ fputc(c_in, me->fp);
+ } else if (me->chunk) {
+ me->last_chunk = HTChunkPutc2(me->last_chunk, c_in);
+ if (me->last_chunk == NULL || me->last_chunk->allocated == 0)
+ me->status = HT_ERROR;
+ }
+ }
+ (*me->actions->put_character) (me->target, c_in);
+}
+
+/*
+ * FIXME: never used!
+ */
+static void CacheThru_put_string(HTStream *me, const char *str)
+{
+ if (me->status == HT_OK) {
+ if (me->fp) {
+ fputs(str, me->fp);
+ } else if (me->chunk) {
+ me->last_chunk = HTChunkPuts2(me->last_chunk, str);
+ if (me->last_chunk == NULL || me->last_chunk->allocated == 0)
+ me->status = HT_ERROR;
+ }
+ }
+ (*me->actions->put_string) (me->target, str);
+}
+
+static void CacheThru_write(HTStream *me, const char *str, int l)
+{
+ if (me->status == HT_OK && l != 0) {
+ if (me->fp) {
+ if (fwrite(str, (size_t) 1, (size_t) l, me->fp) < (size_t) l
+ || ferror(me->fp)) {
+ me->status = HT_ERROR;
+ }
+ } else if (me->chunk) {
+ me->last_chunk = HTChunkPutb2(me->last_chunk, str, l);
+ if (me->last_chunk == NULL || me->last_chunk->allocated == 0)
+ me->status = HT_ERROR;
+ }
+ }
+ (*me->actions->put_block) (me->target, str, l);
+}
+
+static const HTStreamClass PassThruCache =
+{
+ "PassThruCache",
+ CacheThru_free,
+ CacheThru_abort,
+ CacheThru_put_character,
+ CacheThru_put_string,
+ CacheThru_write
+};
+
+static HTStream *CacheThru_new(HTParentAnchor *anchor,
+ HTStream *target)
+{
+ char *cp_freeme = NULL;
+ char filename[LY_MAXPATH];
+ HTStream *stream = NULL;
+ HTProtocol *p = (HTProtocol *) anchor->protocol;
+
+ /*
+ * Neatly and transparently vanish if source caching is disabled.
+ */
+ if (LYCacheSource == SOURCE_CACHE_NONE)
+ return target;
+
+#ifndef DEBUG_SOURCE_CACHE
+ /* Only remote HTML documents may benefit from HTreparse_document(), */
+ /* oh, assume http protocol: */
+ if (strcmp(p->name, "http") != 0
+ && strcmp(p->name, "https") != 0) {
+ CTRACE((tfp, "SourceCacheWriter: Protocol is \"%s\"; not cached\n", p->name));
+ return target;
+ }
+#else
+ /* all HTStreams will be cached */
+#endif
+
+ CTRACE((tfp, "start CacheThru_new\n"));
+
+ stream = (HTStream *) malloc(sizeof(*stream));
+ if (!stream)
+ outofmem(__FILE__, "CacheThru_new");
+
+ stream->isa = &PassThruCache;
+ stream->anchor = anchor;
+ stream->fp = NULL;
+ stream->filename = NULL;
+ stream->chunk = NULL;
+ stream->target = target;
+ stream->actions = target->isa;
+ stream->status = HT_OK;
+
+ if (LYCacheSource == SOURCE_CACHE_FILE) {
+
+ if (anchor->source_cache_file) {
+ CTRACE((tfp,
+ "SourceCacheWriter: If successful, will replace source cache file %s\n",
+ anchor->source_cache_file));
+ }
+
+ /*
+ * We open the temp file in binary mode to make sure that
+ * end-of-line stuff and high-bit Latin-1 (or other) characters
+ * don't get munged; this way, the file should (knock on wood)
+ * contain exactly what came in from the network.
+ */
+ if (!(stream->fp = LYOpenTemp(filename, HTML_SUFFIX, BIN_W))) {
+ CTRACE((tfp,
+ "SourceCacheWriter: Cannot open source cache file for URL %s\n",
+ cp_freeme = HTAnchor_address((HTAnchor *) anchor)));
+ FREE(stream);
+ FREE(cp_freeme);
+ return target;
+ }
+
+ StrAllocCopy(stream->filename, filename);
+
+ CTRACE((tfp,
+ "SourceCacheWriter: Caching source for URL %s in file %s\n",
+ cp_freeme = HTAnchor_address((HTAnchor *) anchor),
+ filename));
+ FREE(cp_freeme);
+ }
+
+ if (LYCacheSource == SOURCE_CACHE_MEMORY) {
+ if (anchor->source_cache_chunk) {
+ CTRACE((tfp,
+ "SourceCacheWriter: If successful, will replace memory chunk %p\n",
+ (void *) anchor->source_cache_chunk));
+ }
+ stream->chunk = stream->last_chunk = HTChunkCreateMayFail(4096, 1);
+ if (!stream->chunk) /* failed already? pretty bad... - kw */
+ stream->status = HT_ERROR;
+
+ CTRACE((tfp,
+ "SourceCacheWriter: Caching source for URL %s in memory chunk %p\n",
+ cp_freeme = HTAnchor_address((HTAnchor *) anchor),
+ (void *) stream->chunk));
+ FREE(cp_freeme);
+ }
+
+ return stream;
+}
+#else
+#define CacheThru_new(anchor, target) target
+#endif
+
+/* HTConverter for HTML to plain text
+ * ----------------------------------
+ *
+ * This will convert from HTML to presentation or plain text.
+ *
+ * It is registered in HTInit.c, but never actually used by lynx.
+ * - kw 1999-03-15
+ */
+HTStream *HTMLToPlain(HTPresentation *pres,
+ HTParentAnchor *anchor,
+ HTStream *sink)
+{
+ CTRACE((tfp, "HTMLToPlain calling CacheThru_new\n"));
+ return CacheThru_new(anchor,
+ SGML_new(&HTML_dtd, anchor,
+ HTML_new(anchor, pres->rep_out, sink), FALSE));
+}
+
+/* HTConverter for HTML source to plain text
+ * -----------------------------------------
+ *
+ * This will preparse HTML and convert back to presentation or plain text.
+ *
+ * It is registered in HTInit.c and used by lynx if invoked with
+ * -preparsed. The stream generated here will be fed with HTML text,
+ * It feeds that to the SGML.c parser, which in turn feeds an HTMLGen.c
+ * structured stream for regenerating flat text; the latter should
+ * end up being handled as text/plain. - kw
+ */
+HTStream *HTMLParsedPresent(HTPresentation *pres,
+ HTParentAnchor *anchor,
+ HTStream *sink)
+{
+ HTStream *intermediate = sink;
+
+ if (!intermediate) {
+ /*
+ * Trick to prevent HTPlainPresent from translating again. Temporarily
+ * change UCT_STAGE_PARSER setting in anchor while the HTPlain stream
+ * is initialized, so that HTPlain sees its input and output charsets
+ * as the same. - kw
+ */
+ int old_parser_cset = HTAnchor_getUCLYhndl(anchor, UCT_STAGE_PARSER);
+ int structured_cset = HTAnchor_getUCLYhndl(anchor, UCT_STAGE_STRUCTURED);
+
+ if (structured_cset < 0)
+ structured_cset = HTAnchor_getUCLYhndl(anchor, UCT_STAGE_HTEXT);
+ if (structured_cset < 0)
+ structured_cset = current_char_set;
+ HTAnchor_setUCInfoStage(anchor, structured_cset,
+ UCT_STAGE_PARSER, UCT_SETBY_MIME);
+ if (pres->rep_out == WWW_SOURCE) {
+ /* same effect as
+ intermediate = HTPlainPresent(pres, anchor, NULL);
+ just written in a more general way:
+ */
+ intermediate = HTStreamStack(WWW_PLAINTEXT, WWW_PRESENT,
+ NULL, anchor);
+ } else {
+ /* this too should amount to calling HTPlainPresent: */
+ intermediate = HTStreamStack(WWW_PLAINTEXT, pres->rep_out,
+ NULL, anchor);
+ }
+ if (old_parser_cset != structured_cset) {
+ HTAnchor_resetUCInfoStage(anchor, old_parser_cset,
+ UCT_STAGE_PARSER, UCT_SETBY_NONE);
+ if (old_parser_cset >= 0) {
+ HTAnchor_setUCInfoStage(anchor, old_parser_cset,
+ UCT_STAGE_PARSER,
+ UCT_SETBY_DEFAULT + 1);
+ }
+ }
+ }
+ if (!intermediate)
+ return NULL;
+ CTRACE((tfp, "HTMLParsedPresent calling CacheThru_new\n"));
+ return CacheThru_new(anchor,
+ SGML_new(&HTML_dtd, anchor,
+ HTMLGenerator(intermediate), FALSE));
+}
+
+/* HTConverter for HTML to C code
+ * ------------------------------
+ *
+ * C code is like plain text but all non-preformatted code
+ * is commented out.
+ * This will convert from HTML to presentation or plain text.
+ *
+ * It is registered in HTInit.c, but normally not used by lynx.
+ * - kw 1999-03-15
+ */
+HTStream *HTMLToC(HTPresentation *pres GCC_UNUSED,
+ HTParentAnchor *anchor,
+ HTStream *sink)
+{
+ HTStructured *html;
+
+ if (sink)
+ (*sink->isa->put_string) (sink, "/* "); /* Before even title */
+ html = HTML_new(anchor, WWW_PLAINTEXT, sink);
+ html->comment_start = "/* ";
+ html->comment_end = " */\n"; /* Must start in col 1 for cpp */
+ if (!sink)
+ HTML_put_string(html, html->comment_start);
+ CTRACE((tfp, "HTMLToC calling CacheThru_new\n"));
+ return CacheThru_new(anchor,
+ SGML_new(&HTML_dtd, anchor, html, FALSE));
+}
+
+/* Presenter for HTML
+ * ------------------
+ *
+ * This will convert from HTML to presentation or plain text.
+ *
+ * (Comment from original libwww:)
+ * Override this if you have a windows version
+ */
+#ifndef GUI
+HTStream *HTMLPresent(HTPresentation *pres GCC_UNUSED,
+ HTParentAnchor *anchor,
+ HTStream *sink GCC_UNUSED)
+{
+ CTRACE((tfp, "HTMLPresent calling CacheThru_new\n"));
+ return CacheThru_new(anchor,
+ SGML_new(&HTML_dtd, anchor,
+ HTML_new(anchor, WWW_PRESENT, NULL), FALSE));
+}
+
+HTStream *XHTMLPresent(HTPresentation *pres GCC_UNUSED,
+ HTParentAnchor *anchor,
+ HTStream *sink GCC_UNUSED)
+{
+ CTRACE((tfp, "XHTMLPresent calling CacheThru_new\n"));
+ return CacheThru_new(anchor,
+ SGML_new(&HTML_dtd, anchor,
+ HTML_new(anchor, WWW_PRESENT, NULL), TRUE));
+}
+#endif /* !GUI */
+
+/* (Comments from original libwww:) */
+/* Record error message as a hypertext object
+ * ------------------------------------------
+ *
+ * The error message should be marked as an error so that
+ * it can be reloaded later.
+ * This implementation just throws up an error message
+ * and leaves the document unloaded.
+ * A smarter implementation would load an error document,
+ * marking at such so that it is retried on reload.
+ *
+ * On entry,
+ * sink is a stream to the output device if any
+ * number is the HTTP error number
+ * message is the human readable message.
+ *
+ * On exit,
+ * returns a negative number to indicate lack of success in the load.
+ */
+/* (We don't actually do any of that hypertext stuff for errors,
+ the trivial implementation for lynx just generates a message
+ and returns. - kw 1999-03-15)
+*/
+int HTLoadError(HTStream *sink GCC_UNUSED, int number,
+ const char *message)
+{
+ HTAlert(message); /* @@@@@@@@@@@@@@@@@@@ */
+ return -number;
+}
+
+static char *MakeNewTitle(STRING2PTR value, int src_type)
+{
+ char *ptr;
+ char *newtitle = NULL;
+
+ StrAllocCopy(newtitle, "[");
+ if (value != 0 && value[src_type] != 0) {
+ ptr = strrchr(value[src_type], '/');
+ if (!ptr) {
+ StrAllocCat(newtitle, value[src_type]);
+ } else {
+ StrAllocCat(newtitle, ptr + 1);
+ }
+ } else {
+ ptr = 0;
+ }
+#ifdef SH_EX /* 1998/04/02 (Thu) 16:02:00 */
+
+ /* for proxy server 1998/12/19 (Sat) 11:53:30 */
+ if (AS_casecomp(newtitle + 1, "internal-gopher-menu") == 0) {
+ StrAllocCopy(newtitle, "+");
+ } else if (AS_casecomp(newtitle + 1, "internal-gopher-unknown") == 0) {
+ StrAllocCopy(newtitle, " ");
+ } else {
+ /* normal title */
+ ptr = strrchr(newtitle, '.');
+ if (ptr) {
+ if (AS_casecomp(ptr, ".gif") == 0)
+ *ptr = '\0';
+ else if (AS_casecomp(ptr, ".jpg") == 0)
+ *ptr = '\0';
+ else if (AS_casecomp(ptr, ".jpeg") == 0)
+ *ptr = '\0';
+ }
+ StrAllocCat(newtitle, "]");
+ }
+#else
+ StrAllocCat(newtitle, "]");
+#endif
+ return newtitle;
+}
+
+static char *MakeNewImageValue(STRING2PTR value)
+{
+ char *ptr;
+ char *newtitle = NULL;
+
+ StrAllocCopy(newtitle, "[");
+ ptr = (value[HTML_INPUT_SRC]
+ ? strrchr(value[HTML_INPUT_SRC], '/')
+ : 0);
+ if (!ptr) {
+ StrAllocCat(newtitle, value[HTML_INPUT_SRC]);
+ } else {
+ StrAllocCat(newtitle, ptr + 1);
+ }
+ StrAllocCat(newtitle, "]-Submit");
+ return newtitle;
+}
+
+static char *MakeNewMapValue(STRING2PTR value, const char *mapstr)
+{
+ char *ptr;
+ char *newtitle = NULL;
+
+ StrAllocCopy(newtitle, "[");
+ StrAllocCat(newtitle, mapstr); /* ISMAP or USEMAP */
+ if (verbose_img && non_empty(value[HTML_IMG_SRC])) {
+ StrAllocCat(newtitle, ":");
+ ptr = strrchr(value[HTML_IMG_SRC], '/');
+ if (!ptr) {
+ StrAllocCat(newtitle, value[HTML_IMG_SRC]);
+ } else {
+ StrAllocCat(newtitle, ptr + 1);
+ }
+ }
+ StrAllocCat(newtitle, "]");
+ return newtitle;
+}
diff --git a/src/HTML.h b/src/HTML.h
new file mode 100644
index 0000000..6e5ebc3
--- /dev/null
+++ b/src/HTML.h
@@ -0,0 +1,283 @@
+/*
+ * $LynxId: HTML.h,v 1.36 2022/07/22 20:22:13 tom Exp $
+ *
+ * HTML to rich text converter for libwww
+ *
+ * THE HTML TO RTF OBJECT CONVERTER
+ *
+ * This interprets the HTML semantics.
+ */
+#ifndef HTML_H
+#define HTML_H
+
+#ifndef HTUTILS_H
+#include <HTUtils.h>
+#endif /* HTUTILS_H */
+
+#include <UCDefs.h>
+#include <UCAux.h>
+#include <HTAnchor.h>
+#include <HTMLDTD.h>
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+/* #define ATTR_CS_IN (me->T.output_utf8 ? me->UCLYhndl : 0) */
+#define ATTR_CS_IN me->tag_charset
+#define TRANSLATE_AND_UNESCAPE_ENTITIES(s, p, h) \
+ LYUCTranslateHTMLString(s, ATTR_CS_IN, current_char_set, YES, p, h, st_HTML)
+#define TRANSLATE_AND_UNESCAPE_ENTITIES5(s,cs_from,cs_to,p,h) \
+ LYUCTranslateHTMLString(s, cs_from, cs_to, YES, p, h, st_HTML)
+#define TRANSLATE_AND_UNESCAPE_ENTITIES6(s,cs_from,cs_to,spcls,p,h) \
+ LYUCTranslateHTMLString(s, cs_from, cs_to, spcls, p, h, st_HTML)
+#define TRANSLATE_HTML(s,p,h) \
+ LYUCFullyTranslateString(s, me->UCLYhndl, current_char_set, NO, YES, p, h, NO, st_HTML)
+#define TRANSLATE_HTML5(s,cs_from,cs_to,p,h) \
+ LYUCFullyTranslateString(s, cs_from, cs_to, NO, YES, p, h, NO, st_HTML)
+#define TRANSLATE_HTML7(s,cs_from,cs_to,spcls,p,h,Back) \
+ LYUCFullyTranslateString(s, cs_from, cs_to, NO, spcls, p, h, Back, st_HTML)
+/*
+ * Strings from attributes which should be converted to some kind of "standard"
+ * representation (character encoding), was Latin-1, esp. URLs (incl.
+ * #fragments) and HTML NAME and ID stuff.
+ */
+#define TRANSLATE_AND_UNESCAPE_TO_STD(s) \
+ LYUCTranslateHTMLString(s, ATTR_CS_IN, ATTR_CS_IN, NO, NO, YES, st_URL)
+#define UNESCAPE_FIELDNAME_TO_STD(s) \
+ LYUCTranslateHTMLString(s, ATTR_CS_IN, ATTR_CS_IN, NO, NO, YES, st_HTML)
+ extern const HTStructuredClass HTMLPresentation;
+
+#ifdef Lynx_HTML_Handler
+/*
+ * This section is semi-private to HTML.c and it's helper modules. - FM
+ * --------------------------------------------------------------------
+ */
+
+ typedef struct _stack_element {
+ HTStyle *style;
+ int tag_number;
+ } stack_element;
+
+/* HTML Object
+ * -----------
+ */
+#define MAX_NESTING 800 /* Should be checked by parser */
+
+ struct _HTStructured {
+ const HTStructuredClass *isa;
+ HTParentAnchor *node_anchor;
+ HText *text;
+
+ HTStream *target; /* Output stream */
+ HTStreamClass targetClass; /* Output routines */
+
+ HTChildAnchor *CurrentA; /* current HTML_A anchor */
+ int CurrentANum; /* current HTML_A number */
+ char *base_href; /* current HTML_BASE href */
+ char *map_address; /* current HTML_MAP address */
+
+ HTChunk title; /* Grow by 128 */
+ HTChunk object; /* Grow by 128 */
+ BOOL object_started;
+ BOOL object_declare;
+ BOOL object_shapes;
+ BOOL object_ismap;
+ char *object_usemap;
+ char *object_id;
+ char *object_title;
+ char *object_data;
+ char *object_type;
+ char *object_classid;
+ char *object_codebase;
+ char *object_codetype;
+ char *object_name;
+ int objects_mixed_open, objects_figged_open;
+ HTChunk option; /* Grow by 128 */
+ BOOL first_option; /* First OPTION in SELECT? */
+ char *LastOptionValue;
+ BOOL LastOptionChecked;
+ BOOL select_disabled;
+ HTChunk textarea; /* Grow by 128 */
+ char *textarea_name;
+ int textarea_name_cs;
+ char *textarea_accept_cs;
+ int textarea_cols;
+ int textarea_rows;
+ int textarea_disabled;
+ int textarea_readonly;
+ char *textarea_id;
+ HTChunk math; /* Grow by 128 */
+ HTChunk style_block; /* Grow by 128 */
+ HTChunk script; /* Grow by 128 */
+
+ /*
+ * Used for nested lists. - FM
+ */
+ int List_Nesting_Level; /* counter for list nesting level */
+ int OL_Counter[12]; /* counter for ordered lists */
+ char OL_Type[12]; /* types for ordered lists */
+ int Last_OL_Count; /* last count in ordered lists */
+ char Last_OL_Type; /* last type in ordered lists */
+
+ int Division_Level;
+ short DivisionAlignments[MAX_NESTING];
+ int Underline_Level;
+ int Quote_Level;
+
+ BOOL UsePlainSpace;
+ BOOL HiddenValue;
+ int lastraw;
+
+ const char *comment_start; /* for literate programming */
+ const char *comment_end;
+
+ HTTag *current_tag;
+ BOOL style_change;
+ HTStyle *new_style;
+ HTStyle *old_style;
+ int current_default_alignment;
+ BOOL in_word; /* Have just had a non-white char */
+ stack_element stack[MAX_NESTING];
+ stack_element *sp; /* Style stack pointer */
+ BOOL stack_overrun; /* Was MAX_NESTING exceeded? */
+ int skip_stack; /* flag to skip next style stack operation */
+
+ /*
+ * Track if we are in an anchor, paragraph, address, base, etc.
+ */
+ BOOL inA;
+ BOOL inAPPLET;
+ BOOL inAPPLETwithP;
+ BOOL inBadHREF;
+ BOOL inBadHTML;
+ BOOL inBASE;
+ BOOL inBoldA;
+ BOOL inBoldH;
+ BOOL inCAPTION;
+ BOOL inCREDIT;
+ BOOL inFIG;
+ BOOL inFIGwithP;
+ BOOL inFONT;
+ BOOL inFORM;
+ BOOL inLABEL;
+ BOOL inP;
+ BOOL inPRE;
+ BOOL inSELECT;
+ BOOL inTABLE;
+ BOOL inTEXTAREA;
+ BOOL inUnderline;
+
+ BOOL needBoldH;
+
+ char *xinclude; /* if no include strin address passed */
+ /*
+ * UCI and UCLYhndl give the UCInfo and charset registered for the HTML
+ * parser in the node_anchor's UCStages structure. It indicates what is
+ * fed to the HTML parser as the stream of character data (not necessarily
+ * tags and attributes). It should currently always be set to be the same
+ * as UCI and UCLhndl for the HTEXT stage in the node_anchor's UCStages
+ * structure, since the HTML parser sends its input character data to the
+ * output without further charset translation.
+ */
+ LYUCcharset *UCI;
+ int UCLYhndl;
+ /*
+ * inUCI and inUCLYhndl indicate the UCInfo and charset which the HTML
+ * parser treats at the input charset. It is normally set to the UCI and
+ * UCLhndl for the SGML parser in the node_anchor's UCStages structure
+ * (which may be a dummy, based on the MIME parser's UCI and UCLhndl in
+ * that structure, when we are handling a local file or non-http(s)
+ * gateway). It could be changed temporarily by the HTML parser, for
+ * conversions of attribute strings, but should be reset once done. - FM
+ */
+ LYUCcharset *inUCI;
+ int inUCLYhndl;
+ /*
+ * outUCI and outUCLYhndl indicate the UCInfo and charset which the HTML
+ * parser treats as the output charset. It is normally set to its own UCI
+ * and UCLhndl. It could be changed for conversions of attribute strings,
+ * but should be reset once done. - FM
+ */
+ LYUCcharset *outUCI;
+ int outUCLYhndl;
+ /*
+ * T holds the transformation rules for conversions of strings between the
+ * input and output charsets by the HTML parser. - FM
+ */
+ UCTransParams T;
+
+ int tag_charset; /* charset for attribute values etc. */
+ };
+
+ extern HTStyle *LYstyles(int style_number);
+ extern BOOL LYBadHTML(HTStructured * me);
+ extern void LYShowBadHTML(const char *s);
+
+/*
+ * Semi-Private functions. - FM
+ */
+ extern void HTML_put_character(HTStructured * me, int c);
+ extern void HTML_put_string(HTStructured * me, const char *s);
+ extern void HTML_write(HTStructured * me, const char *s, int l);
+ extern int HTML_put_entity(HTStructured * me, int entity_number);
+ extern void actually_set_style(HTStructured * me);
+
+/* Style buffering avoids dummy paragraph begin/ends.
+*/
+#define UPDATE_STYLE if (me->style_change) { actually_set_style(me); }
+#endif /* Lynx_HTML_Handler */
+
+ extern void strtolower(char *i);
+
+/* P U B L I C
+*/
+
+/*
+ * HTConverter to present HTML
+ */
+ extern HTStream *HTMLToPlain(HTPresentation *pres,
+ HTParentAnchor *anchor,
+ HTStream *sink);
+
+ extern HTStream *HTMLParsedPresent(HTPresentation *pres,
+ HTParentAnchor *anchor,
+ HTStream *sink);
+
+ extern HTStream *HTMLToC(HTPresentation *pres,
+ HTParentAnchor *anchor,
+ HTStream *sink);
+
+ extern HTStream *HTMLPresent(HTPresentation *pres,
+ HTParentAnchor *anchor,
+ HTStream *sink);
+
+ extern HTStream *XHTMLPresent(HTPresentation *pres,
+ HTParentAnchor *anchor,
+ HTStream *sink);
+
+ extern HTStructured *HTML_new(HTParentAnchor *anchor,
+ HTFormat format_out,
+ HTStream *target);
+
+/*
+ * Record error message as a hypertext object.
+ *
+ * The error message should be marked as an error so that it can be reloaded
+ * later. This implementation just throws up an error message and leaves the
+ * document unloaded.
+ *
+ * On entry,
+ * sink is a stream to the output device if any
+ * number is the HTTP error number
+ * message is the human readable message.
+ * On exit,
+ * a return code like HT_LOADED if object exists else 60; 0
+ */
+ extern int HTLoadError(HTStream *sink,
+ int number,
+ const char *message);
+
+#ifdef __cplusplus
+}
+#endif
+#endif /* HTML_H */
diff --git a/src/HTNestedList.h b/src/HTNestedList.h
new file mode 100644
index 0000000..5a5f103
--- /dev/null
+++ b/src/HTNestedList.h
@@ -0,0 +1,44 @@
+#ifndef HTNESTEDLIST_H
+#define HTNESTEDLIST_H
+
+#define HTML_OL1 (HTML_ELEMENTS+1)
+#define HTML_OL2 (HTML_ELEMENTS+2)
+#define HTML_OL3 (HTML_ELEMENTS+3)
+#define HTML_OL4 (HTML_ELEMENTS+4)
+#define HTML_OL5 (HTML_ELEMENTS+5)
+#define HTML_OL6 (HTML_ELEMENTS+6)
+
+#define HTML_MENU1 (HTML_ELEMENTS+7)
+#define HTML_MENU2 (HTML_ELEMENTS+8)
+#define HTML_MENU3 (HTML_ELEMENTS+9)
+#define HTML_MENU4 (HTML_ELEMENTS+10)
+#define HTML_MENU5 (HTML_ELEMENTS+11)
+#define HTML_MENU6 (HTML_ELEMENTS+12)
+
+#define HTML_DL1 (HTML_ELEMENTS+13)
+#define HTML_DL2 (HTML_ELEMENTS+14)
+#define HTML_DL3 (HTML_ELEMENTS+15)
+#define HTML_DL4 (HTML_ELEMENTS+16)
+#define HTML_DL5 (HTML_ELEMENTS+17)
+#define HTML_DL6 (HTML_ELEMENTS+18)
+
+#define HTML_DLC1 (HTML_ELEMENTS+19)
+#define HTML_DLC2 (HTML_ELEMENTS+20)
+#define HTML_DLC3 (HTML_ELEMENTS+21)
+#define HTML_DLC4 (HTML_ELEMENTS+22)
+#define HTML_DLC5 (HTML_ELEMENTS+23)
+#define HTML_DLC6 (HTML_ELEMENTS+24)
+
+#define HTML_HCENTER (HTML_ELEMENTS+25)
+#define HTML_HLEFT (HTML_ELEMENTS+26)
+#define HTML_HRIGHT (HTML_ELEMENTS+27)
+
+#define HTML_DCENTER (HTML_ELEMENTS+28)
+#define HTML_DLEFT (HTML_ELEMENTS+29)
+#define HTML_DRIGHT (HTML_ELEMENTS+30)
+
+#define HTML_OBJECT_M (HTML_ELEMENTS+31)
+
+#define LYNX_HTML_EXTRA_ELEMENTS 31
+
+#endif /* HTNESTEDLIST_H */
diff --git a/src/HTSaveToFile.h b/src/HTSaveToFile.h
new file mode 100644
index 0000000..35ce390
--- /dev/null
+++ b/src/HTSaveToFile.h
@@ -0,0 +1,29 @@
+#ifndef HTSAVETOFILE_H
+#define HTSAVETOFILE_H
+
+#ifndef HTUTILS_H
+#include <HTUtils.h>
+#endif
+
+#include <HTStream.h>
+#include <HTFormat.h>
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+ extern HTStream *HTSaveToFile(HTPresentation *pres,
+ HTParentAnchor *anchor,
+ HTStream *sink);
+
+ extern HTStream *HTDumpToStdout(HTPresentation *pres,
+ HTParentAnchor *anchor,
+ HTStream *sink);
+
+ extern HTStream *HTCompressed(HTPresentation *pres,
+ HTParentAnchor *anchor,
+ HTStream *sink);
+
+#ifdef __cplusplus
+}
+#endif
+#endif /* HTSAVETOFILE_H */
diff --git a/src/LYBookmark.c b/src/LYBookmark.c
new file mode 100644
index 0000000..ee88cf9
--- /dev/null
+++ b/src/LYBookmark.c
@@ -0,0 +1,1169 @@
+/*
+ * $LynxId: LYBookmark.c,v 1.88 2023/01/07 16:09:53 tom Exp $
+ */
+#include <HTUtils.h>
+#include <HTAlert.h>
+#include <HTFile.h>
+#include <LYUtils.h>
+#include <LYStrings.h>
+#include <LYBookmark.h>
+#include <LYGlobalDefs.h>
+#include <LYClean.h>
+#include <LYKeymap.h>
+#include <LYCharUtils.h> /* need for META charset */
+#include <UCAux.h>
+#include <LYCharSets.h> /* need for LYHaveCJKCharacterSet */
+#include <LYCurses.h>
+#include <GridText.h>
+#include <HTCJK.h>
+
+#ifdef _WINDOWS
+#include <io.h> /* for _chsize() */
+#endif
+
+#ifdef VMS
+#include <nam.h>
+#endif /* VMS */
+
+#include <LYLeaks.h>
+
+char *MBM_A_subbookmark[MBM_V_MAXFILES + 1];
+char *MBM_A_subdescript[MBM_V_MAXFILES + 1];
+
+static BOOLEAN is_mosaic_hotlist = FALSE;
+static const char *convert_mosaic_bookmark_file(const char *filename_buffer);
+
+unsigned LYindex2MBM(int n)
+{
+ static char MBMcodes[MBM_V_MAXFILES + 2] = "ABCDEFGHIJKLMNOPQRSTUVWXYZ";
+
+ return n >= 0 && n <= MBM_V_MAXFILES ? UCH(MBMcodes[n]) : UCH('?');
+}
+
+int LYMBM2index(int ch)
+{
+ if ((ch = TOUPPER(ch)) > 0) {
+ const char *letters = "ABCDEFGHIJKLMNOPQRSTUVWXYZ";
+ const char *result = StrChr(letters, ch);
+
+ if (result != 0
+ && (result - letters) <= MBM_V_MAXFILES)
+ return (int) (result - letters);
+ }
+ return -1;
+}
+
+static void show_bookmark_not_defined(void)
+{
+ char *string_buffer = 0;
+
+ HTSprintf0(&string_buffer,
+ BOOKMARK_FILE_NOT_DEFINED,
+ key_for_func(LYK_OPTIONS));
+ LYMBM_statusline(string_buffer);
+ FREE(string_buffer);
+}
+
+/*
+ * Tries to open a bookmark file for reading, which may be the default, or
+ * based on offering the user a choice from the MBM_A_subbookmark[] array. If
+ * successful the file is closed, and the filename in system path specs is
+ * returned, the URL is allocated into *URL, and the MBM_A_subbookmark[]
+ * filepath is allocated into the BookmarkPage global. Returns a zero-length
+ * pointer to flag a cancel, or a space to flag an undefined selection, without
+ * allocating into *URL or BookmarkPage. Returns NULL with allocating into
+ * BookmarkPage but not *URL is the selection is valid but the file doesn't yet
+ * exist. - FM
+ */
+const char *get_bookmark_filename(char **URL)
+{
+ static char filename_buffer[LY_MAXPATH];
+ char *string_buffer = 0;
+ FILE *fp;
+ int MBM_tmp;
+
+ /*
+ * Multi_Bookmarks support. - FMG & FM
+ * Let user select a bookmark file.
+ */
+ MBM_tmp = select_multi_bookmarks();
+ if (MBM_tmp == -2)
+ /*
+ * Zero-length pointer flags a cancel. - FM
+ */
+ return ("");
+ if (MBM_tmp == -1) {
+ show_bookmark_not_defined();
+ /*
+ * Space flags an undefined selection. - FMG
+ */
+ return (" ");
+ } else {
+ /*
+ * Save the filepath as a global. The system path will be loaded into
+ * to the (static) filename_buffer as the return value, the URL will be
+ * allocated into *URL, and we also need the filepath available to
+ * calling functions. This is all pitifully non-reentrant, a la the
+ * original Lynx, and should be redesigned someday. - FM
+ */
+ StrAllocCopy(BookmarkPage, MBM_A_subbookmark[MBM_tmp]);
+ }
+
+ /*
+ * Seek it in the home path. - FM
+ */
+ LYAddPathToHome(filename_buffer,
+ sizeof(filename_buffer),
+ BookmarkPage);
+ CTRACE((tfp, "\nget_bookmark_filename: SEEKING %s\n AS %s\n\n",
+ BookmarkPage, filename_buffer));
+ if ((fp = fopen(filename_buffer, TXT_R)) != NULL) {
+ /*
+ * We now have the file open.
+ * Check if it is a mosaic hotlist.
+ */
+ if (LYSafeGets(&string_buffer, fp) != 0
+ && *LYTrimNewline(string_buffer) != '\0'
+ && !StrNCmp(string_buffer, "ncsa-xmosaic-hotlist-format-1", 29)) {
+ const char *newname;
+
+ /*
+ * It is a mosaic hotlist file.
+ */
+ is_mosaic_hotlist = TRUE;
+ newname = convert_mosaic_bookmark_file(filename_buffer);
+ LYLocalFileToURL(URL, newname);
+ } else {
+ is_mosaic_hotlist = FALSE;
+ LYLocalFileToURL(URL, filename_buffer);
+ }
+ FREE(string_buffer);
+ LYCloseInput(fp);
+
+ return (filename_buffer); /* bookmark file exists */
+ }
+ return (NULL);
+
+} /* big end */
+
+/*
+ * Converts a Mosaic hotlist file into an HTML file for handling as a Lynx
+ * bookmark file. - FM
+ */
+static const char *convert_mosaic_bookmark_file(const char *filename_buffer)
+{
+ static char newfile[LY_MAXPATH];
+ FILE *fp, *nfp;
+ char *buf = NULL;
+ int line = -2;
+
+ (void) LYRemoveTemp(newfile);
+ if ((nfp = LYOpenTemp(newfile, HTML_SUFFIX, "w")) == NULL) {
+ LYMBM_statusline(NO_TEMP_FOR_HOTLIST);
+ LYSleepAlert();
+ return ("");
+ }
+
+ if ((fp = fopen(filename_buffer, TXT_R)) == NULL)
+ return (""); /* should always open */
+
+ fprintf(nfp, "<head>\n<title>%s</title>\n</head>\n", MOSAIC_BOOKMARK_TITLE);
+ fprintf(nfp, "%s\n\n<p>\n<ol>\n", gettext("\
+ This file is an HTML representation of the X Mosaic hotlist file.\n\
+ Outdated or invalid links may be removed by using the\n\
+ remove bookmark command, it is usually the 'R' key but may have\n\
+ been remapped by you or your system administrator."));
+
+ while ((LYSafeGets(&buf, fp)) != NULL) {
+ if (line >= 0) {
+ LYTrimNewline(buf);
+ if ((line % 2) == 0) { /* even lines */
+ if (*buf != '\0') {
+ strtok(buf, " "); /* kill everything after the space */
+ fprintf(nfp, "<li><a href=\"%s\">", buf); /* the URL */
+ }
+ } else { /* odd lines */
+ fprintf(nfp, "%s</a></li>\n", buf); /* the title */
+ }
+ }
+ /* else - ignore the line (this gets rid of first two lines) */
+ line++;
+ }
+ LYCloseTempFP(nfp);
+ LYCloseInput(fp);
+ return (newfile);
+}
+
+static BOOLEAN havevisible(const char *Title);
+static BOOLEAN have8bit(const char *Title);
+static char *title_convert8bit(const char *Title);
+
+#if defined(_WINDOWS) && !defined(ftruncate)
+#define ftruncate(fd, len) _chsize(fd, len)
+#endif
+
+/*
+ * Adds a link to a bookmark file, creating the file if it doesn't already
+ * exist, and making sure that no_cache is set for a pre-existing, cached file,
+ * so that the change will be evident on return to to that file. - FM
+ */
+void save_bookmark_link(const char *address,
+ const char *title)
+{
+ FILE *fp;
+ BOOLEAN first_time = FALSE;
+ const char *filename;
+ char *bookmark_URL = NULL;
+ char filename_buffer[LY_MAXPATH];
+ char *Address = NULL;
+ char *Title = NULL;
+ int i, c;
+ bstring *string_data = NULL;
+ bstring *tmp_data = NULL;
+ DocAddress WWWDoc;
+ HTParentAnchor *tmpanchor;
+ HText *text;
+
+ /*
+ * Make sure we were passed something to save. - FM
+ */
+ if (isEmpty(address)) {
+ HTAlert(MALFORMED_ADDRESS);
+ return;
+ }
+
+ /*
+ * Offer a choice of bookmark files, or get the default. - FMG
+ */
+ filename = get_bookmark_filename(&bookmark_URL);
+
+ /*
+ * If filename is NULL, must create a new file. If filename is a space, an
+ * invalid bookmark file was selected, or if zero-length, the user
+ * cancelled. Ignore request in both cases. Otherwise, make a copy before
+ * anything might change the static get_bookmark_filename() buffer. - FM
+ */
+ if (filename == NULL) {
+ first_time = TRUE;
+ filename_buffer[0] = '\0';
+ } else {
+ if (*filename == '\0' || !strcmp(filename, " ")) {
+ FREE(bookmark_URL);
+ return;
+ }
+ LYStrNCpy(filename_buffer, filename, sizeof(filename_buffer) - 1);
+ }
+
+ /*
+ * If BookmarkPage is NULL, something went wrong, so ignore the request. -
+ * FM
+ */
+ if (isEmpty(BookmarkPage)) {
+ FREE(bookmark_URL);
+ return;
+ }
+
+ /*
+ * If the link will be added to the same bookmark file, get confirmation.
+ * - FM
+ */
+ if (LYMultiBookmarks != MBM_OFF) {
+ const char *url = HTLoadedDocumentURL();
+ const char *page = ((*BookmarkPage == '.')
+ ? (BookmarkPage + 1)
+ : BookmarkPage);
+
+ if (strstr(url, page) != NULL) {
+ LYMBM_statusline(MULTIBOOKMARKS_SELF);
+ c = LYgetch_single();
+ if (c != 'L') {
+ FREE(bookmark_URL);
+ return;
+ }
+ }
+ }
+
+ /*
+ * Allow user to change the title. - FM
+ */
+ do {
+ if (HTCJK == JAPANESE) {
+ switch (kanji_code) {
+ case EUC:
+ BStrAlloc(tmp_data, MAX_LINE + 2 * (int) strlen(title));
+ TO_EUC((const unsigned char *) title, (unsigned char *) tmp_data->str);
+ break;
+ case SJIS:
+ BStrAlloc(tmp_data, MAX_LINE + (int) strlen(title));
+ TO_SJIS((const unsigned char *) title, (unsigned char *) tmp_data->str);
+ break;
+ default:
+ break;
+ }
+ BStrCopy0(string_data, tmp_data ? tmp_data->str : title);
+ } else {
+ BStrCopy0(string_data, title);
+ }
+ LYReduceBlanks(string_data->str);
+ LYMBM_statusline(TITLE_PROMPT);
+ LYgetBString(&string_data, FALSE, 0, NORECALL);
+ if (isBEmpty(string_data)) {
+ LYMBM_statusline(CANCELLED);
+ LYSleepMsg();
+ FREE(bookmark_URL);
+ BStrFree(tmp_data);
+ return;
+ }
+ } while (!havevisible(string_data->str));
+
+ /*
+ * Create the Title with any left-angle-brackets converted to &lt; entities
+ * and any ampersands converted to &amp; entities. - FM
+ *
+ * Convert 8-bit letters to &#xUUUU to avoid dependencies from display
+ * character set which may need changing. Do NOT convert any 8-bit chars
+ * if we have CJK display. - LP
+ */
+ LYformTitle(&Title, string_data->str);
+ LYEntify(&Title, TRUE);
+ if (UCSaveBookmarksInUnicode &&
+ have8bit(Title) && (!LYHaveCJKCharacterSet)) {
+ char *p = title_convert8bit(Title);
+
+ if (p != 0) {
+ FREE(Title);
+ Title = p;
+ }
+ }
+
+ /*
+ * Create the bookmark file, if it doesn't exist already, Otherwise, open
+ * the pre-existing bookmark file. - FM
+ */
+ SetDefaultMode(O_TEXT);
+ if (first_time) {
+ /*
+ * Seek it in the home path. - FM
+ */
+ LYAddPathToHome(filename_buffer,
+ sizeof(filename_buffer),
+ BookmarkPage);
+ }
+ CTRACE((tfp, "\nsave_bookmark_link: SEEKING %s\n AS %s\n\n",
+ BookmarkPage, filename_buffer));
+ if ((fp = fopen(filename_buffer, (first_time ? TXT_W : TXT_A))) == NULL) {
+ LYMBM_statusline(BOOKMARK_OPEN_FAILED);
+ LYSleepAlert();
+ FREE(Title);
+ FREE(bookmark_URL);
+ BStrFree(tmp_data);
+ return;
+ }
+
+ /*
+ * Convert all ampersands in the address to &amp; entities. - FM
+ */
+ StrAllocCopy(Address, address);
+ LYEntify(&Address, FALSE);
+
+ if (!first_time) {
+ BOOLEAN empty_file = TRUE;
+ FILE *bp = tmpfile();
+ char *buffer = NULL;
+
+ rewind(fp);
+ while (LYSafeGets(&buffer, fp)) {
+ empty_file = FALSE;
+ if (LYstrstr(buffer, "</ol>"))
+ break;
+ fprintf(bp, "%s", buffer);
+ }
+
+ fflush(bp);
+ rewind(bp);
+
+ rewind(fp);
+ ftruncate(fileno(fp), 0);
+
+ while (LYSafeGets(&buffer, bp)) {
+ fprintf(fp, "%s", buffer);
+ }
+ fclose(bp);
+
+ if (empty_file)
+ first_time = TRUE;
+ }
+
+ /*
+ * If we created a new bookmark file, write the headers. - FM
+ * Once and forever...
+ */
+ if (first_time) {
+ fprintf(fp, "%s\n", LYNX_DOCTYPE);
+ fprintf(fp, "<html>\n");
+ fprintf(fp, "<head>\n");
+#if defined(SH_EX) && !defined(_WINDOWS) /* 1997/12/11 (Thu) 19:13:40 */
+ if (HTCJK != JAPANESE)
+ LYAddMETAcharsetToFD(fp, -1);
+ else
+ fprintf(fp, "<meta %s %s>\n",
+ "http-equiv=\"content-type\"",
+ "content=\"" STR_HTML ";charset=iso-2022-jp\"");
+#else
+ LYAddMETAcharsetToFD(fp, -1);
+#endif /* !_WINDOWS */
+ fprintf(fp, "<title>%s</title>\n</head>\n", BOOKMARK_TITLE);
+ fprintf(fp, "<body>\n");
+#ifdef _WINDOWS
+ fprintf(fp, "<p>%s",
+ gettext(" You can delete links by the 'R' key<br>\n<ol>\n"));
+#else
+ fprintf(fp, "<p>%s<br>\n%s\n\n<!--\n%s\n--></p>\n\n<ol>\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, "<li><a href=\"%s\">%s</a></li>\n", Address, Title);
+ fprintf(fp, "</ol></body></html>\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, "<ol><li>"))
+ keep_ol = TRUE; /* Do not erase, this corrects a bug in an
+ older version */
+ while (n < cur && (cp = LYstrstr(cp, "<a href="))) {
+ seen++;
+ if (++n == cur) {
+ if (seen != 1 || !LYstrstr(buf, "</a>") ||
+ LYstrstr((cp + 1), "<a href=")) {
+ HTAlert(BOOKMARK_LINK_NOT_ONE_LINE);
+ goto failure;
+ }
+ CTRACE((tfp, "remove_bookmark_link: skipping link %d\n", n));
+ if (keep_ol)
+ fprintf(nfp, "<ol>\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, comment);
+ 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 <LYStructs.h>
+#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 <George.Lindholm@ubc.ca>
+ *
+ * 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 <index> (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 <HTUtils.h>
+#include <HTTP.h>
+#include <HTParse.h>
+#include <HTTCP.h>
+#include <HTFormat.h>
+#include <HTFile.h>
+#include <HTAlert.h>
+#include <HTMIME.h>
+#include <HTAABrow.h>
+
+#include <LYGlobalDefs.h>
+#include <LYUtils.h>
+#include <HTML.h>
+#include <HTInit.h>
+#include <LYGetFile.h>
+#include <LYBookmark.h>
+#include <GridText.h>
+#include <LYCgi.h>
+#include <LYStrings.h>
+#include <LYLocal.h>
+
+#include <LYLeaks.h>
+#include <www_wait.h>
+
+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 <index> 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, "<html>\n<head>\n<title>%s</title>\n</head>\n<body>\n",
+ gettext("Good Advice"));
+ PUTS(buf);
+
+ HTSprintf0(&buf, "<h1>%s</h1>\n", gettext("Good Advice"));
+ PUTS(buf);
+
+ HTSprintf0(&buf, "%s <a\n",
+ gettext("An excellent http server for VMS is available via"));
+ PUTS(buf);
+
+ HTSprintf0(&buf,
+ "href=\"http://www.ecr6.ohio-state.edu/www/doc/serverinfo.html\"\n");
+ PUTS(buf);
+
+ HTSprintf0(&buf, ">%s</a>.\n", gettext("this link"));
+ PUTS(buf);
+
+ HTSprintf0(&buf, "<p>%s\n",
+ gettext("It provides state of the art CGI script support.\n"));
+ PUTS(buf);
+
+ HTSprintf0(&buf, "</body>\n</html>\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 <HTUtils.h>
+#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 <HTUtils.h>
+#include <HTCJK.h>
+#include <HTMLDTD.h>
+
+#include <LYGlobalDefs.h>
+#include <UCMap.h>
+#include <UCdomap.h>
+#include <UCDefs.h>
+#include <LYCharSets.h>
+#include <GridText.h>
+#include <LYCurses.h>
+#include <LYStrings.h>
+
+#include <LYLeaks.h>
+
+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) (&#198;) - AElig */
+ "\301", /* capital A, acute accent (&#193;) - Aacute */
+ "\302", /* capital A, circumflex accent (&#194;) - Acirc */
+ "\300", /* capital A, grave accent (&#192;) - Agrave */
+ "\305", /* capital A, ring - Aring (&#197;) */
+ "\303", /* capital A, tilde - Atilde (&#195;) */
+ "\304", /* capital A, dieresis or umlaut mark (&#196;) - Auml */
+ "\307", /* capital C, cedilla - Ccedil (&#199;) */
+ "\320", /* capital Eth or D with stroke (&#208;) - Dstrok */
+ "\320", /* capital Eth, Icelandic (&#208;) - ETH */
+ "\311", /* capital E, acute accent (&#201;) - Eacute */
+ "\312", /* capital E, circumflex accent (&#202;) - Ecirc */
+ "\310", /* capital E, grave accent (&#200;) - Egrave */
+ "\313", /* capital E, dieresis or umlaut mark (&#203;) - Euml */
+ "\315", /* capital I, acute accent (&#205;) - Iacute */
+ "\316", /* capital I, circumflex accent (&#206;) - Icirc */
+ "\314", /* capital I, grave accent (&#204;) - Igrave */
+ "\317", /* capital I, dieresis or umlaut mark (&#207;) - Iuml */
+ "\321", /* capital N, tilde (&#209;) - Ntilde */
+ "\323", /* capital O, acute accent (&#211;) - Oacute */
+ "\324", /* capital O, circumflex accent (&#212;) - Ocirc */
+ "\322", /* capital O, grave accent (&#210;) - Ograve */
+ "\330", /* capital O, slash (&#216;) - Oslash */
+ "\325", /* capital O, tilde (&#213;) - Otilde */
+ "\326", /* capital O, dieresis or umlaut mark (&#214;) - Ouml */
+ "\336", /* capital THORN, Icelandic (&#222;) - THORN */
+ "\332", /* capital U, acute accent (&#218;) - Uacute */
+ "\333", /* capital U, circumflex accent (&#219;) - Ucirc */
+ "\331", /* capital U, grave accent (&#217;) - Ugrave */
+ "\334", /* capital U, dieresis or umlaut mark (&#220;) - Uuml */
+ "\335", /* capital Y, acute accent (&#221;) - Yacute */
+ "\341", /* small a, acute accent (&#225;) - aacute */
+ "\342", /* small a, circumflex accent (&#226;) - acirc */
+ "\264", /* spacing acute (&#180;) - acute */
+ "\346", /* small ae diphthong (ligature) (&#230;) - aelig */
+ "\340", /* small a, grave accent (&#224;) - agrave */
+ "\046", /* ampersand (&#38;) - amp */
+ "\345", /* small a, ring (&#229;) - aring */
+ "\343", /* small a, tilde (&#227;) - atilde */
+ "\344", /* small a, dieresis or umlaut mark (&#228;) - auml */
+ "\246", /* broken vertical bar (&#166;) - brkbar */
+ "\246", /* broken vertical bar (&#166;) - brvbar */
+ "\347", /* small c, cedilla (&#231;) - ccedil */
+ "\270", /* spacing cedilla (&#184;) - cedil */
+ "\242", /* cent sign (&#162;) - cent */
+ "\251", /* copyright sign (&#169;) - copy */
+ "\244", /* currency sign (&#164;) - curren */
+ "\260", /* degree sign (&#176;) - deg */
+ "\250", /* spacing dieresis (&#168;) - die */
+ "\367", /* division sign (&#247;) - divide */
+ "\351", /* small e, acute accent (&#233;) - eacute */
+ "\352", /* small e, circumflex accent (&#234;) - ecirc */
+ "\350", /* small e, grave accent (&#232;) - 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 (&#240;) - eth */
+ "\353", /* small e, dieresis or umlaut mark (&#235;) - euml */
+ "\275", /* fraction 1/2 (&#189;) - frac12 */
+ "\274", /* fraction 1/4 (&#188;) - frac14 */
+ "\276", /* fraction 3/4 (&#190;) - frac34 */
+ "\076", /* greater than (&#62;) - gt */
+ "\257", /* spacing macron (&#175;) - hibar */
+ "\355", /* small i, acute accent (&#237;) - iacute */
+ "\356", /* small i, circumflex accent (&#238;) - icirc */
+ "\241", /* inverted exclamation mark (&#161;) - iexcl */
+ "\354", /* small i, grave accent (&#236;) - igrave */
+ "\277", /* inverted question mark (&#191;) - iquest */
+ "\357", /* small i, dieresis or umlaut mark (&#239;) - iuml */
+ "\253", /* angle quotation mark, left (&#171;) - laquo */
+ "\074", /* less than (&#60;) - lt */
+ "\257", /* spacing macron (&#175;) - macr */
+ "-", /* dash the width of emsp - mdash */
+ "\265", /* micro sign (&#181;) - micro */
+ "\267", /* middle dot (&#183;) - middot */
+ "\001", /* nbsp non-breaking space NEVER CHANGE THIS - nbsp */
+ "-", /* dash the width of ensp - ndash */
+ "\254", /* negation sign (&#172;) - not */
+ "\361", /* small n, tilde (&#241;) - ntilde */
+ "\363", /* small o, acute accent (&#243;) - oacute */
+ "\364", /* small o, circumflex accent (&#244;) - ocirc */
+ "\362", /* small o, grave accent (&#242;) - ograve */
+ "\252", /* feminine ordinal indicator (&#170;) - ordf */
+ "\272", /* masculine ordinal indicator (&#186;) - ordm */
+ "\370", /* small o, slash (&#248;) - oslash */
+ "\365", /* small o, tilde (&#245;) - otilde */
+ "\366", /* small o, dieresis or umlaut mark (&#246;) - ouml */
+ "\266", /* paragraph sign (&#182;) - para */
+ "\261", /* plus-or-minus sign (&#177;) - plusmn */
+ "\243", /* pound sign (&#163;) - pound */
+ "\042", /* quote '"' (&#34;) - quot */
+ "\273", /* angle quotation mark, right (&#187;) - raquo */
+ "\256", /* circled R registered sign (&#174;) - reg */
+ "\247", /* section sign (&#167;) - sect */
+ "\007", /* soft hyphen (&#173;) NEVER CHANGE THIS - shy */
+ "\271", /* superscript 1 (&#185;) - sup1 */
+ "\262", /* superscript 2 (&#178;) - sup2 */
+ "\263", /* superscript 3 (&#179;) - sup3 */
+ "\337", /* small sharp s, German (sz ligature) (&#223;) - szlig */
+ "\002", /* thin space - not collapsed NEVER CHANGE THIS - thinsp */
+ "\376", /* small thorn, Icelandic (&#254;) - thorn */
+ "\327", /* multiplication sign (&#215;) - times */
+ "(TM)", /* circled TM trade mark sign (&#8482;) - trade */
+ "\372", /* small u, acute accent (&#250;) - uacute */
+ "\373", /* small u, circumflex accent (&#251;) - ucirc */
+ "\371", /* small u, grave accent (&#249;) - ugrave */
+ "\250", /* spacing dieresis (&#168;) - uml */
+ "\374", /* small u, dieresis or umlaut mark (&#252;) - uuml */
+ "\375", /* small y, acute accent (&#253;) - yacute */
+ "\245", /* yen sign (&#165;) - yen */
+ "\377", /* small y, dieresis or umlaut mark (&#255;) - 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) (&#198;) - AElig */
+ "A", /* capital A, acute accent (&#193;) - Aacute */
+ "A", /* capital A, circumflex accent (&#194;) - Acirc */
+ "A", /* capital A, grave accent (&#192;) - Agrave */
+ "A", /* capital A, ring - Aring (&#197;) */
+ "A", /* capital A, tilde - Atilde (&#195;) */
+#ifdef LY_UMLAUT
+ "Ae", /* capital A, dieresis or umlaut mark (&#196;) - Auml */
+#else
+ "A", /* capital A, dieresis or umlaut mark (&#196;) - Auml */
+#endif /* LY_UMLAUT */
+ "C", /* capital C, cedilla (&#199;) - Ccedil */
+ "Dj", /* capital D with stroke (&#208;) - Dstrok */
+ "DH", /* capital Eth, Icelandic (&#208;) - ETH */
+ "E", /* capital E, acute accent (&#201;) - Eacute */
+ "E", /* capital E, circumflex accent (&#202;) - Ecirc */
+ "E", /* capital E, grave accent (&#200;) - Egrave */
+ "E", /* capital E, dieresis or umlaut mark (&#203;) - Euml */
+ "I", /* capital I, acute accent (&#205;) - Iacute */
+ "I", /* capital I, circumflex accent (&#206;) - Icirc */
+ "I", /* capital I, grave accent (&#204;) - Igrave */
+ "I", /* capital I, dieresis or umlaut mark (&#207;) - Iuml */
+ "N", /* capital N, tilde - Ntilde (&#209;) */
+ "O", /* capital O, acute accent (&#211;) - Oacute */
+ "O", /* capital O, circumflex accent (&#212;) - Ocirc */
+ "O", /* capital O, grave accent (&#210;) - Ograve */
+ "O", /* capital O, slash (&#216;) - Oslash */
+ "O", /* capital O, tilde (&#213;) - Otilde */
+#ifdef LY_UMLAUT
+ "Oe", /* capital O, dieresis or umlaut mark (&#214;) - Ouml */
+#else
+ "O", /* capital O, dieresis or umlaut mark (&#214;) - Ouml */
+#endif /* LY_UMLAUT */
+ "P", /* capital THORN, Icelandic (&#222;) - THORN */
+ "U", /* capital U, acute accent (&#218;) - Uacute */
+ "U", /* capital U, circumflex accent (&#219;) - Ucirc */
+ "U", /* capital U, grave accent (&#217;) - Ugrave */
+#ifdef LY_UMLAUT
+ "Ue", /* capital U, dieresis or umlaut mark (&#220;) - Uuml */
+#else
+ "U", /* capital U, dieresis or umlaut mark (&#220;) - Uuml */
+#endif /* LY_UMLAUT */
+ "Y", /* capital Y, acute accent (&#221;) - Yacute */
+ "a", /* small a, acute accent (&#225;) - aacute */
+ "a", /* small a, circumflex accent (&#226;) - acirc */
+ "'", /* spacing acute (&#180;) - acute */
+ "ae", /* small ae diphthong (ligature) (&#230;) - aelig */
+ "`a", /* small a, grave accent (&#232;) - agrave */
+ "&", /* ampersand (&#38;) - amp */
+ "a", /* small a, ring (&#229;) - aring */
+ "a", /* small a, tilde (&#227;) - atilde */
+#ifdef LY_UMLAUT
+ "ae", /* small a, dieresis or umlaut mark (&#228;) - auml */
+#else
+ "a", /* small a, dieresis or umlaut mark (&#228;) - auml */
+#endif /* LY_UMLAUT */
+ "|", /* broken vertical bar (&#166;) - brkbar */
+ "|", /* broken vertical bar (&#166;) - brvbar */
+ "c", /* small c, cedilla (&#231;) - ccedil */
+ ",", /* spacing cedilla (&#184;) - cedil */
+ "-c-", /* cent sign (&#162;) - cent */
+ "(c)", /* copyright sign (&#169;) - copy */
+ "CUR", /* currency sign (&#164;) - curren */
+ "DEG", /* degree sign (&#176;) - deg */
+ "\042", /* spacing dieresis (&#168;) - die */
+ "/", /* division sign (&#247;) - divide */
+ "e", /* small e, acute accent (&#233;) - eacute */
+ "e", /* small e, circumflex accent (&#234;) - ecirc */
+ "e", /* small e, grave accent (&#232;) - 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 (&#240;) */
+ "e", /* small e, dieresis or umlaut mark (&#235;) - euml */
+ " 1/2", /* fraction 1/2 (&#189;) - frac12 */
+ " 1/4", /* fraction 1/4 (&#188;) - frac14 */
+ " 3/4", /* fraction 3/4 (&#190;) - frac34 */
+ ">", /* greater than (&#62;) - gt */
+ "-", /* spacing macron (&#175;) - hibar */
+ "i", /* small i, acute accent (&#237;) - iacute */
+ "i", /* small i, circumflex accent (&#238;) - icirc */
+ "!", /* inverted exclamation mark (&#161;) - iexcl */
+ "`i", /* small i, grave accent (&#236;) - igrave */
+ "?", /* inverted question mark (&#191;) - iquest */
+ "i", /* small i, dieresis or umlaut mark (&#239;) - iuml */
+ "<<", /* angle quotation mark, left (&#171;) - laquo */
+ "<", /* less than - lt (&#60;) */
+ "-", /* spacing macron (&#175;) - macr */
+ "-", /* dash the width of emsp - mdash */
+ "u", /* micro sign (&#181;) - micro */
+ ".", /* middle dot (&#183;) - middot */
+ "\001", /* nbsp non-breaking space NEVER CHANGE THIS - nbsp */
+ "-", /* dash the width of ensp - ndash */
+ "NOT", /* negation sign (&#172;) - not */
+ "n", /* small n, tilde (&#241;) - ntilde */
+ "o", /* small o, acute accent (&#243;) - oacute */
+ "o", /* small o, circumflex accent (&#244;) - ocirc */
+ "o", /* small o, grave accent (&#242;) - ograve */
+ "-a", /* feminine ordinal indicator (&#170;) - ordf */
+ "-o", /* masculine ordinal indicator (&#186;) - ordm */
+ "o", /* small o, slash (&#248;) - oslash */
+ "o", /* small o, tilde (&#245;) - otilde */
+#ifdef LY_UMLAUT
+ "oe", /* small o, dieresis or umlaut mark (&#246;) - ouml */
+#else
+ "o", /* small o, dieresis or umlaut mark (&#246;) - ouml */
+#endif /* LY_UMLAUT */
+ "P:", /* paragraph sign (&#182;) - para */
+ "+-", /* plus-or-minus sign (&#177;) - plusmn */
+ "-L-", /* pound sign (&#163;) - pound */
+ "\"", /* quote '"' (&#34;) - quot */
+ ">>", /* angle quotation mark, right (&#187;) - raquo */
+ "(R)", /* circled R registered sign (&#174;) - reg */
+ "S:", /* section sign (&#167;) - sect */
+ "\007", /* soft hyphen (&#173;) NEVER CHANGE THIS - shy */
+ "^1", /* superscript 1 (&#185;) - sup1 */
+ "^2", /* superscript 2 (&#178;) - sup2 */
+ "^3", /* superscript 3 (&#179;) - sup3 */
+ "ss", /* small sharp s, German (sz ligature) (&#223;) - szlig */
+ "\002", /* thin space - not collapsed NEVER CHANGE THIS - thinsp */
+ "p", /* small thorn, Icelandic (&#254;) - thorn */
+ "*", /* multiplication sign (&#215;) - times */
+ "(TM)", /* circled TM trade mark sign (&#8482;) - trade */
+ "u", /* small u, acute accent (&#250;) - uacute */
+ "u", /* small u, circumflex accent (&#251;) - ucirc */
+ "u", /* small u, grave accent (&#249;) - ugrave */
+ "\042", /* spacing dieresis (&#168;) - uml */
+#ifdef LY_UMLAUT
+ "ue", /* small u, dieresis or umlaut mark (&#252;) - uuml */
+#else
+ "u", /* small u, dieresis or umlaut mark (&#252;) - uuml */
+#endif /* LY_UMLAUT */
+ "y", /* small y, acute accent (&#253;) - yacute */
+ "YEN", /* yen sign (&#165;) - yen */
+ "y", /* small y, dieresis or umlaut mark (&#255;) - 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 <entities.h>
+
+ 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 &#153; 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 <HTUtils.h>
+#endif
+
+#include <UCDefs.h>
+
+#ifndef UCMAP_H
+#include <UCMap.h>
+#endif /* !UCMAP_H */
+
+#include <HTCJK.h>
+
+#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 <HTUtils.h>
+#include <SGML.h>
+
+#define Lynx_HTML_Handler
+#include <HTChunk.h>
+#include <HText.h>
+#include <HTStyle.h>
+#include <HTMIME.h>
+#include <HTML.h>
+
+#include <HTCJK.h>
+#include <HTAtom.h>
+#include <HTMLGen.h>
+#include <HTParse.h>
+#include <UCMap.h>
+#include <UCDefs.h>
+#include <UCAux.h>
+
+#include <LYGlobalDefs.h>
+#include <LYCharUtils.h>
+#include <LYCharSets.h>
+
+#include <HTAlert.h>
+#include <HTForms.h>
+#include <HTNestedList.h>
+#include <GridText.h>
+#include <LYStrings.h>
+#include <LYUtils.h>
+#include <LYMap.h>
+#include <LYBookmark.h>
+#include <LYCurses.h>
+#include <LYCookie.h>
+
+#include <LYexit.h>
+#include <LYLeaks.h>
+
+/*
+ * 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 "&amp;".
+ * If brackets is TRUE, it also converts any angle-brackets to "&lt;" or "&gt;".
+ */
+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, "<!--", 4))
+ /*
+ * We don't have the start of a comment, so return the beginning of the
+ * string. - FM
+ */
+ return str;
+
+ cp = (str + 4);
+ if (*cp == '>')
+ /*
+ * It's an invalid comment, so
+ * return this end character. - FM
+ */
+ return cp;
+
+ if ((cp1 = StrChr(cp, '>')) == NULL)
+ /*
+ * We don't have an end character, so return the beginning of the
+ * string. - FM
+ */
+ return str;
+
+ if (*cp == '-')
+ /*
+ * Ugh, it's a "decorative" series of dashes, so return the next end
+ * character. - FM
+ */
+ return cp1;
+
+ /*
+ * OK, we're ready to start parsing. - FM
+ */
+ state = start2;
+ while (*cp != '\0') {
+ switch (state) {
+ case start1:
+ if (*cp == '-')
+ state = start2;
+ else
+ /*
+ * Invalid comment, so return the first '>' from the start of
+ * the string. - FM
+ */
+ return cp1;
+ break;
+
+ case start2:
+ if (*cp == '-')
+ state = end1;
+ break;
+
+ case end1:
+ if (*cp == '-')
+ state = end2;
+ else
+ /*
+ * Invalid comment, so return the first '>' from the start of
+ * the string. - FM
+ */
+ return cp1;
+ break;
+
+ case end2:
+ if (*cp == '>')
+ /*
+ * Valid comment, so return the end character. - FM
+ */
+ return cp;
+ if (*cp == '-') {
+ state = start1;
+ } else if (!(WHITE(*cp) && UCH(*cp) != UCH(CH_ESC))) { /* S/390 -- gil -- 1686 */
+ /*
+ * Invalid comment, so return the first '>' from the start of
+ * the string. - FM
+ */
+ return cp1;
+ }
+ break;
+
+ default:
+ break;
+ }
+ cp++;
+ }
+
+ /*
+ * Invalid comment, so return the first '>' from the start of the string.
+ * - FM
+ */
+ return cp1;
+}
+
+/*
+ * If an HREF, itself or if resolved against a base,
+ * represents a file URL, and the host is defaulted,
+ * force in "//localhost". We need this until
+ * all the other Lynx code which performs security
+ * checks based on the "localhost" string is changed
+ * to assume "//localhost" when a host field is not
+ * present in file URLs - FM
+ */
+void LYFillLocalFileURL(char **href,
+ const char *base)
+{
+ char *temp = NULL;
+
+ if (isEmpty(*href))
+ return;
+
+ if (!strcmp(*href, "//") || !StrNCmp(*href, "///", 3)) {
+ if (base != NULL && isFILE_URL(base)) {
+ StrAllocCopy(temp, STR_FILE_URL);
+ StrAllocCat(temp, *href);
+ StrAllocCopy(*href, temp);
+ }
+ }
+ if (isFILE_URL(*href)) {
+ if (*(*href + 5) == '\0') {
+ StrAllocCat(*href, "//localhost");
+ } else if (!strcmp(*href, "file://")) {
+ StrAllocCat(*href, "localhost");
+ } else if (!StrNCmp(*href, "file:///", 8)) {
+ StrAllocCopy(temp, (*href + 7));
+ LYLocalFileToURL(href, temp);
+ } else if (!StrNCmp(*href, "file:/", 6) && !LYIsHtmlSep(*(*href + 6))) {
+ StrAllocCopy(temp, (*href + 5));
+ LYLocalFileToURL(href, temp);
+ }
+ }
+#if defined(USE_DOS_DRIVES)
+ if (LYIsDosDrive(*href)) {
+ /*
+ * If it's a local DOS path beginning with drive letter,
+ * add file://localhost/ prefix and go ahead.
+ */
+ StrAllocCopy(temp, *href);
+ LYLocalFileToURL(href, temp);
+ }
+
+ /* use below: strlen("file://localhost/") = 17 */
+ if (!StrNCmp(*href, "file://localhost/", 17)
+ && (strlen(*href) == 19)
+ && LYIsDosDrive(*href + 17)) {
+ /*
+ * Terminate DOS drive letter with a slash to surf root successfully.
+ * Here seems a proper place to do so.
+ */
+ LYAddPathSep(href);
+ }
+#endif /* USE_DOS_DRIVES */
+
+ /*
+ * No path in a file://localhost URL means a
+ * directory listing for the current default. - FM
+ */
+ if (!strcmp(*href, "file://localhost")) {
+ const char *temp2;
+
+#ifdef VMS
+ temp2 = HTVMS_wwwName(LYGetEnv("PATH"));
+#else
+ char curdir[LY_MAXPATH];
+
+ temp2 = wwwName(Current_Dir(curdir));
+#endif /* VMS */
+ if (!LYIsHtmlSep(*temp2))
+ LYAddHtmlSep(href);
+ /*
+ * Check for pathological cases - current dir has chars which MUST BE
+ * URL-escaped - kw
+ */
+ if (StrChr(temp2, '%') != NULL || StrChr(temp2, '#') != NULL) {
+ FREE(temp);
+ temp = HTEscape(temp2, URL_PATH);
+ StrAllocCat(*href, temp);
+ } else {
+ StrAllocCat(*href, temp2);
+ }
+ }
+#ifdef VMS
+ /*
+ * On VMS, a file://localhost/ URL means
+ * a listing for the login directory. - FM
+ */
+ if (!strcmp(*href, "file://localhost/"))
+ StrAllocCat(*href, (HTVMS_wwwName(Home_Dir()) + 1));
+#endif /* VMS */
+
+ FREE(temp);
+ return;
+}
+
+void LYAddMETAcharsetToStream(HTStream *target, int disp_chndl)
+{
+ char *buf = 0;
+
+ if (disp_chndl == -1)
+ /*
+ * -1 means use current_char_set.
+ */
+ disp_chndl = current_char_set;
+
+ if (target != 0 && disp_chndl >= 0) {
+ HTSprintf0(&buf, "<META %s content=\"" STR_HTML ";charset=%s\">\n",
+ "http-equiv=\"content-type\"",
+ LYCharSet_UC[disp_chndl].MIMEname);
+ (*target->isa->put_string) (target, buf);
+ FREE(buf);
+ }
+}
+
+/*
+ * This function writes a line with a META tag to an open file,
+ * which will specify a charset parameter to use when the file is
+ * read back in. It is meant for temporary HTML files used by the
+ * various special pages which may show titles of documents. When those
+ * files are created, the title strings normally have been translated and
+ * expanded to the display character set, so we have to make sure they
+ * don't get translated again.
+ * If the user has changed the display character set during the lifetime
+ * of the Lynx session (or, more exactly, during the time the title
+ * strings to be written were generated), they may now have different
+ * character encodings and there is currently no way to get it all right.
+ * To change this, we would have to add a variable for each string which
+ * keeps track of its character encoding.
+ * But at least we can try to ensure that reading the file after future
+ * display character set changes will give reasonable output.
+ *
+ * The META tag is not written if the display character set (passed as
+ * disp_chndl) already corresponds to the charset assumption that
+ * would be made when the file is read. - KW
+ *
+ * Currently this function is used for temporary files like "Lynx Info Page"
+ * and for one permanent - bookmarks (so it may be a problem if you change
+ * the display charset later: new bookmark entries may be mistranslated).
+ * - LP
+ */
+void LYAddMETAcharsetToFD(FILE *fd, int disp_chndl)
+{
+ if (disp_chndl == -1)
+ /*
+ * -1 means use current_char_set.
+ */
+ disp_chndl = current_char_set;
+
+ if (fd == NULL || disp_chndl < 0)
+ /*
+ * Should not happen.
+ */
+ return;
+
+ if (UCLYhndl_HTFile_for_unspec == disp_chndl)
+ /*
+ * Not need to do, so we don't.
+ */
+ return;
+
+ if (LYCharSet_UC[disp_chndl].enc == UCT_ENC_7BIT)
+ /*
+ * There shouldn't be any 8-bit characters in this case.
+ */
+ return;
+
+ /*
+ * In other cases we don't know because UCLYhndl_for_unspec may change
+ * during the lifetime of the file (by toggling raw mode or changing the
+ * display character set), so proceed.
+ */
+ fprintf(fd, "<META %s content=\"" STR_HTML ";charset=%s\">\n",
+ "http-equiv=\"content-type\"",
+ LYCharSet_UC[disp_chndl].MIMEname);
+}
+
+/*
+ * This function returns OL TYPE="A" strings in
+ * the range of " A." (1) to "ZZZ." (18278). - FM
+ */
+char *LYUppercaseA_OL_String(int seqnum)
+{
+ static char OLstring[8];
+
+ if (seqnum <= 1) {
+ strcpy(OLstring, " A.");
+ return OLstring;
+ }
+ if (seqnum < 27) {
+ sprintf(OLstring, " %c.", (seqnum + 64));
+ return OLstring;
+ }
+ if (seqnum < 703) {
+ sprintf(OLstring, "%c%c.", ((seqnum - 1) / 26 + 64),
+ (seqnum - ((seqnum - 1) / 26) * 26 + 64));
+ return OLstring;
+ }
+ if (seqnum < 18279) {
+ sprintf(OLstring, "%c%c%c.", ((seqnum - 27) / 676 + 64),
+ (((seqnum - ((seqnum - 27) / 676) * 676) - 1) / 26 + 64),
+ (seqnum - ((seqnum - 1) / 26) * 26 + 64));
+ return OLstring;
+ }
+ strcpy(OLstring, "ZZZ.");
+ return OLstring;
+}
+
+/*
+ * This function returns OL TYPE="a" strings in
+ * the range of " a." (1) to "zzz." (18278). - FM
+ */
+char *LYLowercaseA_OL_String(int seqnum)
+{
+ static char OLstring[8];
+
+ if (seqnum <= 1) {
+ strcpy(OLstring, " a.");
+ return OLstring;
+ }
+ if (seqnum < 27) {
+ sprintf(OLstring, " %c.", (seqnum + 96));
+ return OLstring;
+ }
+ if (seqnum < 703) {
+ sprintf(OLstring, "%c%c.", ((seqnum - 1) / 26 + 96),
+ (seqnum - ((seqnum - 1) / 26) * 26 + 96));
+ return OLstring;
+ }
+ if (seqnum < 18279) {
+ sprintf(OLstring, "%c%c%c.", ((seqnum - 27) / 676 + 96),
+ (((seqnum - ((seqnum - 27) / 676) * 676) - 1) / 26 + 96),
+ (seqnum - ((seqnum - 1) / 26) * 26 + 96));
+ return OLstring;
+ }
+ strcpy(OLstring, "zzz.");
+ return OLstring;
+}
+
+/*
+ * This function returns OL TYPE="I" strings in the
+ * range of " I." (1) to "MMM." (3000).- FM
+ * Maximum length: 16 -TD
+ */
+char *LYUppercaseI_OL_String(int seqnum)
+{
+ static char OLstring[20];
+ int Arabic = seqnum;
+
+ if (Arabic >= 3000) {
+ strcpy(OLstring, "MMM.");
+ return OLstring;
+ }
+
+ switch (Arabic) {
+ case 1:
+ strcpy(OLstring, " I.");
+ return OLstring;
+ case 5:
+ strcpy(OLstring, " V.");
+ return OLstring;
+ case 10:
+ strcpy(OLstring, " X.");
+ return OLstring;
+ case 50:
+ strcpy(OLstring, " L.");
+ return OLstring;
+ case 100:
+ strcpy(OLstring, " C.");
+ return OLstring;
+ case 500:
+ strcpy(OLstring, " D.");
+ return OLstring;
+ case 1000:
+ strcpy(OLstring, " M.");
+ return OLstring;
+ default:
+ OLstring[0] = '\0';
+ break;
+ }
+
+ while (Arabic >= 1000) {
+ strcat(OLstring, "M");
+ Arabic -= 1000;
+ }
+
+ if (Arabic >= 900) {
+ strcat(OLstring, "CM");
+ Arabic -= 900;
+ }
+
+ if (Arabic >= 500) {
+ strcat(OLstring, "D");
+ Arabic -= 500;
+ }
+
+ if (Arabic >= 400) {
+ strcat(OLstring, "CD");
+ Arabic -= 400;
+ }
+
+ while (Arabic >= 100) {
+ strcat(OLstring, "C");
+ Arabic -= 100;
+ }
+
+ if (Arabic >= 90) {
+ strcat(OLstring, "XC");
+ Arabic -= 90;
+ }
+
+ if (Arabic >= 50) {
+ strcat(OLstring, "L");
+ Arabic -= 50;
+ }
+
+ if (Arabic >= 40) {
+ strcat(OLstring, "XL");
+ Arabic -= 40;
+ }
+
+ while (Arabic > 10) {
+ strcat(OLstring, "X");
+ Arabic -= 10;
+ }
+
+ switch (Arabic) {
+ case 1:
+ strcat(OLstring, "I.");
+ break;
+ case 2:
+ strcat(OLstring, "II.");
+ break;
+ case 3:
+ strcat(OLstring, "III.");
+ break;
+ case 4:
+ strcat(OLstring, "IV.");
+ break;
+ case 5:
+ strcat(OLstring, "V.");
+ break;
+ case 6:
+ strcat(OLstring, "VI.");
+ break;
+ case 7:
+ strcat(OLstring, "VII.");
+ break;
+ case 8:
+ strcat(OLstring, "VIII.");
+ break;
+ case 9:
+ strcat(OLstring, "IX.");
+ break;
+ case 10:
+ strcat(OLstring, "X.");
+ break;
+ default:
+ strcat(OLstring, ".");
+ break;
+ }
+
+ return OLstring;
+}
+
+/*
+ * This function returns OL TYPE="i" strings in
+ * range of " i." (1) to "mmm." (3000).- FM
+ * Maximum length: 16 -TD
+ */
+char *LYLowercaseI_OL_String(int seqnum)
+{
+ static char OLstring[20];
+ int Arabic = seqnum;
+
+ if (Arabic >= 3000) {
+ strcpy(OLstring, "mmm.");
+ return OLstring;
+ }
+
+ switch (Arabic) {
+ case 1:
+ strcpy(OLstring, " i.");
+ return OLstring;
+ case 5:
+ strcpy(OLstring, " v.");
+ return OLstring;
+ case 10:
+ strcpy(OLstring, " x.");
+ return OLstring;
+ case 50:
+ strcpy(OLstring, " l.");
+ return OLstring;
+ case 100:
+ strcpy(OLstring, " c.");
+ return OLstring;
+ case 500:
+ strcpy(OLstring, " d.");
+ return OLstring;
+ case 1000:
+ strcpy(OLstring, " m.");
+ return OLstring;
+ default:
+ OLstring[0] = '\0';
+ break;
+ }
+
+ while (Arabic >= 1000) {
+ strcat(OLstring, "m");
+ Arabic -= 1000;
+ }
+
+ if (Arabic >= 900) {
+ strcat(OLstring, "cm");
+ Arabic -= 900;
+ }
+
+ if (Arabic >= 500) {
+ strcat(OLstring, "d");
+ Arabic -= 500;
+ }
+
+ if (Arabic >= 400) {
+ strcat(OLstring, "cd");
+ Arabic -= 400;
+ }
+
+ while (Arabic >= 100) {
+ strcat(OLstring, "c");
+ Arabic -= 100;
+ }
+
+ if (Arabic >= 90) {
+ strcat(OLstring, "xc");
+ Arabic -= 90;
+ }
+
+ if (Arabic >= 50) {
+ strcat(OLstring, "l");
+ Arabic -= 50;
+ }
+
+ if (Arabic >= 40) {
+ strcat(OLstring, "xl");
+ Arabic -= 40;
+ }
+
+ while (Arabic > 10) {
+ strcat(OLstring, "x");
+ Arabic -= 10;
+ }
+
+ switch (Arabic) {
+ case 1:
+ strcat(OLstring, "i.");
+ break;
+ case 2:
+ strcat(OLstring, "ii.");
+ break;
+ case 3:
+ strcat(OLstring, "iii.");
+ break;
+ case 4:
+ strcat(OLstring, "iv.");
+ break;
+ case 5:
+ strcat(OLstring, "v.");
+ break;
+ case 6:
+ strcat(OLstring, "vi.");
+ break;
+ case 7:
+ strcat(OLstring, "vii.");
+ break;
+ case 8:
+ strcat(OLstring, "viii.");
+ break;
+ case 9:
+ strcat(OLstring, "ix.");
+ break;
+ case 10:
+ strcat(OLstring, "x.");
+ break;
+ default:
+ strcat(OLstring, ".");
+ break;
+ }
+
+ return OLstring;
+}
+
+/*
+ * This function initializes the Ordered List counter. - FM
+ */
+void LYZero_OL_Counter(HTStructured * me)
+{
+ int i;
+
+ if (!me)
+ return;
+
+ for (i = 0; i < 12; i++) {
+ me->OL_Counter[i] = OL_VOID;
+ me->OL_Type[i] = '1';
+ }
+
+ me->Last_OL_Count = 0;
+ me->Last_OL_Type = '1';
+
+ return;
+}
+
+/*
+ * This function is used by the HTML Structured object. - KW
+ */
+void LYGetChartransInfo(HTStructured * me)
+{
+ me->UCLYhndl = HTAnchor_getUCLYhndl(me->node_anchor,
+ UCT_STAGE_STRUCTURED);
+ if (me->UCLYhndl < 0) {
+ int chndl = HTAnchor_getUCLYhndl(me->node_anchor, UCT_STAGE_HTEXT);
+
+ if (chndl < 0) {
+ chndl = current_char_set;
+ HTAnchor_setUCInfoStage(me->node_anchor, chndl,
+ UCT_STAGE_HTEXT,
+ UCT_SETBY_STRUCTURED);
+ }
+ HTAnchor_setUCInfoStage(me->node_anchor, chndl,
+ UCT_STAGE_STRUCTURED,
+ UCT_SETBY_STRUCTURED);
+ me->UCLYhndl = HTAnchor_getUCLYhndl(me->node_anchor,
+ UCT_STAGE_STRUCTURED);
+ }
+ me->UCI = HTAnchor_getUCInfoStage(me->node_anchor,
+ UCT_STAGE_STRUCTURED);
+}
+
+ /* as in HTParse.c, saves some calls - kw */
+static const char *hex = "0123456789ABCDEF";
+
+/*
+ * Any raw 8-bit or multibyte characters already have been
+ * handled in relation to the display character set
+ * in SGML_character(), including named and numeric entities.
+ *
+ * This function used for translations HTML special fields inside tags
+ * (ALT=, VALUE=, etc.) from charset `cs_from' to charset `cs_to'.
+ * It also unescapes non-ASCII characters from URL (#fragments !)
+ * if st_URL is active.
+ *
+ * If `do_ent' is YES, it converts named entities
+ * and numeric character references (NCRs) to their `cs_to' replacements.
+ *
+ * Named entities converted to unicodes. NCRs (unicodes) converted
+ * by UCdomap.c chartrans functions.
+ * ???NCRs with values in the ISO-8859-1 range 160-255 may be converted
+ * to their HTML entity names (via old-style entities) and then translated
+ * according to the LYCharSets.c array for `cs_out'???.
+ *
+ * Some characters (see descriptions in `put_special_unicodes' from SGML.c)
+ * translated in relation with the state of boolean variables
+ * `use_lynx_specials', `plain_space' and `hidden'. It is not clear yet:
+ *
+ * If plain_space is TRUE, nbsp (160) will be treated as an ASCII
+ * space (32). If hidden is TRUE, entities will be translated
+ * (if `do_ent' is YES) but escape sequences will be passed unaltered.
+ * If `hidden' is FALSE, some characters are converted to Lynx special
+ * codes (see `put_special_unicodes') or ASCII space if `plain_space'
+ * applies). @@ is `use_lynx_specials' needed, does it have any effect? @@
+ * If `use_lynx_specials' is YES, translate byte values 160 and 173
+ * meaning U+00A0 and U+00AD given as or converted from raw char input
+ * are converted to HT_NON_BREAK_SPACE and LY_SOFT_HYPHEN, respectively
+ * (unless input and output charset are both iso-8859-1, for compatibility
+ * with previous usage in HTML.c) even if `hidden' or `plain_space' is set.
+ *
+ * If `Back' is YES, the reverse is done instead i.e., Lynx special codes
+ * in the input are translated back to character values.
+ *
+ * If `Back' is YES, an attempt is made to use UCReverseTransChar() for
+ * back translation which may be more efficient. (?)
+ *
+ * If `stype' is st_URL, non-ASCII characters are URL-encoded instead.
+ * The sequence of bytes being URL-encoded is the raw input character if
+ * we couldn't translate it from `cs_in' (CJK etc.); otherwise it is the
+ * UTF-8 representation if either `cs_to' requires this or if the
+ * character's Unicode value is > 255, otherwise it should be the iso-8859-1
+ * representation.
+ * No general URL-encoding occurs for displayable ASCII characters and
+ * spaces and some C0 controls valid in HTML (LF, TAB), it is expected
+ * that other functions will take care of that as appropriate.
+ *
+ * Escape characters (0x1B, '\033') are
+ * - URL-encoded if `stype' is st_URL, otherwise
+ * - dropped if `stype' is st_other, otherwise (i.e., st_HTML)
+ * - passed if `hidden' is TRUE or HTCJK is set, otherwise
+ * - dropped.
+ *
+ * (If `stype' is st_URL or st_other most of the parameters really predefined:
+ * cs_from=cs_to, use_lynx_specials=plain_space=NO, and hidden=YES)
+ *
+ *
+ * Returns pointer to the char** passed in
+ * if string translated or translation unnecessary,
+ * NULL otherwise
+ * (in which case something probably went wrong.)
+ *
+ *
+ * In general, this somehow ugly function (KW)
+ * cover three functions from v.2.7.2 (FM):
+ * extern void LYExpandString (
+ * HTStructured * me,
+ * char ** str);
+ * extern void LYUnEscapeEntities (
+ * HTStructured * me,
+ * char ** str);
+ * extern void LYUnEscapeToLatinOne (
+ * HTStructured * me,
+ * char ** str,
+ * BOOLEAN isURL);
+ */
+
+char **LYUCFullyTranslateString(char **str,
+ int cs_from,
+ int cs_to,
+ int do_ent,
+ int use_lynx_specials,
+ int plain_space,
+ int hidden,
+ int Back,
+ CharUtil_st stype)
+{
+ char *p;
+ char *q, *qs;
+ HTChunk *chunk = NULL;
+ char *cp = 0;
+ char cpe = 0;
+ char *esc = NULL;
+ char replace_buf[64];
+ int uck;
+ int lowest_8;
+ UCode_t code = 0;
+ BOOL output_utf8 = 0, repl_translated_C0 = 0;
+ size_t len;
+ const char *name = NULL;
+ BOOLEAN no_bytetrans;
+ UCTransParams T;
+ BOOL from_is_utf8 = FALSE;
+ char *puni = 0;
+ enum _state {
+ S_text,
+ S_esc,
+ S_dollar,
+ S_paren,
+ S_nonascii_text,
+ S_dollar_paren,
+ S_trans_byte,
+ S_check_ent,
+ S_ncr,
+ S_check_uni,
+ S_named,
+ S_check_name,
+ S_recover,
+ S_got_oututf8,
+ S_got_outstring,
+ S_put_urlstring,
+ S_got_outchar,
+ S_put_urlchar,
+ S_next_char,
+ S_done
+ } state = S_text;
+ enum _parsing_what {
+ P_text,
+ P_utf8,
+ P_hex,
+ P_decimal,
+ P_named
+ } what = P_text;
+
+#ifdef KANJI_CODE_OVERRIDE
+ static unsigned char sjis_1st = '\0';
+
+ unsigned char sjis_str[3];
+#endif
+
+ /*
+ * Make sure we have a non-empty string. - FM
+ */
+ if (isEmpty(*str))
+ return str;
+
+ if (cs_from < 0 || cs_to < 0) {
+ CTRACE((tfp, "BUG: LYUCFullyTranslateString from=%d, to=%d\n",
+ cs_from, cs_to));
+ return str;
+ }
+
+ /*
+ * FIXME: something's wrong with the limit checks here (clearing the
+ * buffer helps).
+ */
+ memset(replace_buf, 0, sizeof(replace_buf));
+
+ /*
+ * Don't do byte translation if original AND target character sets are both
+ * iso-8859-1 (and we are not called to back-translate), or if we are in
+ * CJK mode.
+ */
+ if (IS_CJK_TTY
+#ifdef USE_JAPANESEUTF8_SUPPORT
+ && (strcmp(LYCharSet_UC[cs_from].MIMEname, "utf-8") != 0)
+ && (strcmp(LYCharSet_UC[cs_to].MIMEname, "utf-8") != 0)
+#endif
+ ) {
+ no_bytetrans = TRUE;
+ } else if (cs_to <= 0 && cs_from == cs_to && (!Back || cs_to < 0)) {
+ no_bytetrans = TRUE;
+ } else {
+ /* No need to translate or examine the string any further */
+ no_bytetrans = (BOOL) (!use_lynx_specials && !Back &&
+ UCNeedNotTranslate(cs_from, cs_to));
+ }
+ /*
+ * Save malloc/calloc overhead in simple case - kw
+ */
+ if (do_ent && hidden && (stype != st_URL) && (StrChr(*str, '&') == NULL))
+ do_ent = FALSE;
+
+ /* Can't do, caller should figure out what to do... */
+ if (!UCCanTranslateFromTo(cs_from, cs_to)) {
+ if (cs_to < 0)
+ return NULL;
+ if (!do_ent && no_bytetrans)
+ return NULL;
+ no_bytetrans = TRUE;
+ } else if (cs_to < 0) {
+ do_ent = FALSE;
+ }
+
+ if (!do_ent && no_bytetrans)
+ return str;
+ p = *str;
+
+ if (!no_bytetrans) {
+ UCTransParams_clear(&T);
+ UCSetTransParams(&T, cs_from, &LYCharSet_UC[cs_from],
+ cs_to, &LYCharSet_UC[cs_to]);
+ from_is_utf8 = (BOOL) (LYCharSet_UC[cs_from].enc == UCT_ENC_UTF8);
+ output_utf8 = T.output_utf8;
+ repl_translated_C0 = T.repl_translated_C0;
+ puni = p;
+ } else if (do_ent) {
+ output_utf8 = (BOOL) (LYCharSet_UC[cs_to].enc == UCT_ENC_UTF8 ||
+ HText_hasUTF8OutputSet(HTMainText));
+ repl_translated_C0 = (BOOL) (LYCharSet_UC[cs_to].enc == UCT_ENC_8BIT_C0);
+ }
+
+ lowest_8 = LYlowest_eightbit[cs_to];
+
+ /*
+ * Create a buffer string seven times the length of the original, so we
+ * have plenty of room for expansions. - FM
+ */
+ len = strlen(p) + 16;
+ q = p;
+
+ qs = q;
+
+/* Create the HTChunk only if we need it */
+#define CHUNK (chunk ? chunk : (chunk = HTChunkCreate2(128, len+1)))
+
+#define REPLACE_STRING(s) \
+ if (q != qs) HTChunkPutb(CHUNK, qs, (int) (q - qs)); \
+ HTChunkPuts(CHUNK, s); \
+ qs = q = *str
+
+#define REPLACE_CHAR(c) if (q > p) { \
+ HTChunkPutb(CHUNK, qs, (int) (q - qs)); \
+ qs = q = *str; \
+ *q++ = c; \
+ } else \
+ *q++ = c
+
+ /*
+ * Loop through string, making conversions as needed.
+ *
+ * The while() checks for a non-'\0' char only for the normal text states
+ * since other states may temporarily modify p or *p (which should be
+ * restored before S_done!) - kw
+ */
+ while (*p || (state != S_text && state != S_nonascii_text)) {
+ switch (state) {
+ case S_text:
+ code = UCH(*p);
+#ifdef KANJI_CODE_OVERRIDE
+ if (HTCJK == JAPANESE && last_kcode == SJIS) {
+ if (sjis_1st == '\0' && (IS_SJIS_HI1(code) || IS_SJIS_HI2(code))) {
+ sjis_1st = UCH(code);
+ } else if (sjis_1st && IS_SJIS_LO(code)) {
+ sjis_1st = '\0';
+ } else {
+ if (conv_jisx0201kana && 0xA1 <= code && code <= 0xDF) {
+ sjis_str[2] = '\0';
+ JISx0201TO0208_SJIS(UCH(code),
+ sjis_str, sjis_str + 1);
+ REPLACE_STRING(sjis_str);
+ p++;
+ continue;
+ }
+ }
+ }
+#endif
+ if (*p == '\033') {
+ if ((IS_CJK_TTY && !hidden) || stype != st_HTML) {
+ state = S_esc;
+ if (stype == st_URL) {
+ REPLACE_STRING("%1B");
+ p++;
+ continue;
+ } else if (stype != st_HTML) {
+ p++;
+ continue;
+ } else {
+ *q++ = *p++;
+ continue;
+ }
+ } else if (!hidden) {
+ /*
+ * CJK handling not on, and not a hidden INPUT, so block
+ * escape. - FM
+ */
+ state = S_next_char;
+ } else {
+ state = S_trans_byte;
+ }
+ } else {
+ state = (do_ent ? S_check_ent : S_trans_byte);
+ }
+ break;
+
+ case S_esc:
+ if (*p == '$') {
+ state = S_dollar;
+ *q++ = *p++;
+ continue;
+ } else if (*p == '(') {
+ state = S_paren;
+ *q++ = *p++;
+ continue;
+ } else {
+ state = S_text;
+ }
+ break;
+
+ case S_dollar:
+ if (*p == '@' || *p == 'B' || *p == 'A') {
+ state = S_nonascii_text;
+ *q++ = *p++;
+ continue;
+ } else if (*p == '(') {
+ state = S_dollar_paren;
+ *q++ = *p++;
+ continue;
+ } else {
+ state = S_text;
+ }
+ break;
+
+ case S_dollar_paren:
+ if (*p == 'C') {
+ state = S_nonascii_text;
+ *q++ = *p++;
+ continue;
+ } else {
+ state = S_text;
+ }
+ break;
+
+ case S_paren:
+ if (*p == 'B' || *p == 'J' || *p == 'T') {
+ state = S_text;
+ *q++ = *p++;
+ continue;
+ } else if (*p == 'I') {
+ state = S_nonascii_text;
+ *q++ = *p++;
+ continue;
+ } else {
+ state = S_text;
+ }
+ break;
+
+ case S_nonascii_text:
+ if (*p == '\033') {
+ if ((IS_CJK_TTY && !hidden) || stype != st_HTML) {
+ state = S_esc;
+ if (stype == st_URL) {
+ REPLACE_STRING("%1B");
+ p++;
+ continue;
+ } else if (stype != st_HTML) {
+ p++;
+ continue;
+ }
+ }
+ }
+ *q++ = *p++;
+ continue;
+
+ case S_trans_byte:
+ /* character translation goes here */
+ /*
+ * Don't do anything if we have no string, or if original AND
+ * target character sets are both iso-8859-1, or if we are in CJK
+ * mode.
+ */
+ if (*p == '\0' || no_bytetrans) {
+ state = S_got_outchar;
+ break;
+ }
+
+ if (Back) {
+ int rev_c;
+
+ if ((*p) == HT_NON_BREAK_SPACE ||
+ (*p) == HT_EN_SPACE) {
+ if (plain_space) {
+ code = *p = ' ';
+ state = S_got_outchar;
+ break;
+ } else {
+ code = 160;
+ if (LYCharSet_UC[cs_to].enc == UCT_ENC_8859 ||
+ (LYCharSet_UC[cs_to].like8859 & UCT_R_8859SPECL)) {
+ state = S_got_outchar;
+ break;
+ } else if (!(LYCharSet_UC[cs_from].enc == UCT_ENC_8859
+ || (LYCharSet_UC[cs_from].like8859 & UCT_R_8859SPECL))) {
+ state = S_check_uni;
+ break;
+ } else {
+ *(unsigned char *) p = UCH(160);
+ }
+ }
+ } else if ((*p) == LY_SOFT_HYPHEN) {
+ code = 173;
+ if (LYCharSet_UC[cs_to].enc == UCT_ENC_8859 ||
+ (LYCharSet_UC[cs_to].like8859 & UCT_R_8859SPECL)) {
+ state = S_got_outchar;
+ break;
+ } else if (!(LYCharSet_UC[cs_from].enc == UCT_ENC_8859
+ || (LYCharSet_UC[cs_from].like8859 & UCT_R_8859SPECL))) {
+ state = S_check_uni;
+ break;
+ } else {
+ *(unsigned char *) p = UCH(173);
+ }
+#ifdef USE_JAPANESEUTF8_SUPPORT
+ } else if (output_utf8) {
+ if ((!strcmp(LYCharSet_UC[cs_from].MIMEname, "euc-jp") &&
+ (IS_EUC((unsigned char) (*p),
+ (unsigned char) (*(p + 1))))) ||
+ (!strcmp(LYCharSet_UC[cs_from].MIMEname, "shift_jis") &&
+ (IS_SJIS_2BYTE((unsigned char) (*p),
+ (unsigned char) (*(p + 1)))))) {
+ code = UCTransJPToUni(p, 2, cs_from);
+ p++;
+ state = S_check_uni;
+ break;
+ }
+#endif
+ } else if (code < 127 || T.transp) {
+ state = S_got_outchar;
+ break;
+ }
+ rev_c = UCReverseTransChar(*p, cs_to, cs_from);
+ if (rev_c > 127) {
+ *p = (char) rev_c;
+ code = rev_c;
+ state = S_got_outchar;
+ break;
+ }
+ } else if (code < 127) {
+ state = S_got_outchar;
+ break;
+ }
+
+ if (from_is_utf8) {
+ if (((*p) & 0xc0) == 0xc0) {
+ const char *pq = p;
+
+ puni = p;
+ code = UCGetUniFromUtf8String(&pq);
+ if (code <= 0) {
+ code = UCH(*p);
+ } else {
+ what = P_utf8;
+ puni += (pq - (const char *) p);
+ }
+ }
+ } else if (use_lynx_specials && !Back &&
+ (code == 160 || code == 173) &&
+ (LYCharSet_UC[cs_from].enc == UCT_ENC_8859 ||
+ (LYCharSet_UC[cs_from].like8859 & UCT_R_8859SPECL))) {
+ if (code == 160)
+ code = *p = HT_NON_BREAK_SPACE;
+ else if (code == 173)
+ code = *p = LY_SOFT_HYPHEN;
+ state = S_got_outchar;
+ break;
+ } else if (T.trans_to_uni) {
+ code = UCTransToUni(*p, cs_from);
+ if (code <= 0) {
+ /* What else can we do? */
+ code = UCH(*p);
+ }
+ } else if (!T.trans_from_uni) {
+ state = S_got_outchar;
+ break;
+ }
+ /*
+ * Substitute Lynx special character for 160 (nbsp) if
+ * use_lynx_specials is set.
+ */
+ if (use_lynx_specials && !Back &&
+ (code == 160 || code == 173)) {
+ code = ((code == 160 ? HT_NON_BREAK_SPACE : LY_SOFT_HYPHEN));
+ state = S_got_outchar;
+ break;
+ }
+
+ state = S_check_uni;
+ break;
+
+ case S_check_ent:
+ if (*p == '&') {
+ char *pp = p + 1;
+
+ len = strlen(pp);
+ /*
+ * Check for a numeric entity. - FM
+ */
+ if (*pp == '#' && len > 2 &&
+ (*(pp + 1) == 'x' || *(pp + 1) == 'X') &&
+ UCH(*(pp + 2)) < 127 &&
+ isxdigit(UCH(*(pp + 2)))) {
+ what = P_hex;
+ state = S_ncr;
+ } else if (*pp == '#' && len > 2 &&
+ UCH(*(pp + 1)) < 127 &&
+ isdigit(UCH(*(pp + 1)))) {
+ what = P_decimal;
+ state = S_ncr;
+ } else if (UCH(*pp) < 127 &&
+ isalpha(UCH(*pp))) {
+ what = P_named;
+ state = S_named;
+ } else {
+ state = S_trans_byte;
+ }
+ } else {
+ state = S_trans_byte;
+ }
+ break;
+
+ case S_ncr:
+ if (what == P_hex) {
+ p += 3;
+ } else { /* P_decimal */
+ p += 2;
+ }
+ cp = p;
+ while (*p && UCH(*p) < 127 &&
+ (what == P_hex ? isxdigit(UCH(*p)) :
+ isdigit(UCH(*p)))) {
+ p++;
+ }
+ /*
+ * Save the terminator and isolate the digit(s). - FM
+ */
+ cpe = *p;
+ if (*p)
+ *p++ = '\0';
+ /*
+ * Show the numeric entity if the value:
+ * (1) Is greater than 255 and unhandled Unicode.
+ * (2) Is less than 32, and not valid and we don't have HTCJK set.
+ * (3) Is 127 and we don't have HTPassHighCtrlRaw or HTCJK set.
+ * (4) Is 128 - 159 and we don't have HTPassHighCtrlNum set.
+ */
+ if (UCScanCode(&code, cp, (BOOL) (what == P_hex))) {
+ code = LYcp1252ToUnicode(code);
+ state = S_check_uni;
+ } else {
+ state = S_recover;
+ break;
+ }
+ break;
+
+ case S_check_uni:
+ /*
+ * Show the numeric entity if the value:
+ * (2) Is less than 32, and not valid and we don't have HTCJK set.
+ * (3) Is 127 and we don't have HTPassHighCtrlRaw or HTCJK set.
+ * (4) Is 128 - 159 and we don't have HTPassHighCtrlNum set.
+ */
+ if ((code < 32 &&
+ code != 9 && code != 10 && code != 13 &&
+ !IS_CJK_TTY) ||
+ (code == 127 &&
+ !(HTPassHighCtrlRaw || IS_CJK_TTY)) ||
+ (code > 127 && code < 160 &&
+ !HTPassHighCtrlNum)) {
+ state = S_recover;
+ break;
+ }
+ /*
+ * Convert the value as an unsigned char, hex escaped if isURL is
+ * set and it's 8-bit, and then recycle the terminator if it is not
+ * a semicolon. - FM
+ */
+ if (code > 159 && stype == st_URL) {
+ state = S_got_oututf8;
+ break;
+ }
+ /*
+ * For 160 (nbsp), use that value if it's a hidden INPUT, otherwise
+ * use an ASCII space (32) if plain_space is TRUE, otherwise use
+ * the Lynx special character. - FM
+ */
+ if (code == 160) {
+ if (plain_space) {
+ code = ' ';
+ state = S_got_outchar;
+ break;
+ } else if (use_lynx_specials) {
+ code = HT_NON_BREAK_SPACE;
+ state = S_got_outchar;
+ break;
+ } else if ((hidden && !Back)
+ || (LYCharSet_UC[cs_to].codepoints & UCT_CP_SUPERSETOF_LAT1)
+ || LYCharSet_UC[cs_to].enc == UCT_ENC_8859
+ || (LYCharSet_UC[cs_to].like8859 &
+ UCT_R_8859SPECL)) {
+ state = S_got_outchar;
+ break;
+ } else if (
+ (LYCharSet_UC[cs_to].repertoire & UCT_REP_SUPERSETOF_LAT1)) {
+ ; /* nothing, may be translated later */
+ } else {
+ code = ' ';
+ state = S_got_outchar;
+ break;
+ }
+ }
+ /*
+ * For 173 (shy), use that value if it's a hidden INPUT, otherwise
+ * ignore it if plain_space is TRUE, otherwise use the Lynx special
+ * character. - FM
+ */
+ if (code == 173) {
+ if (plain_space) {
+ replace_buf[0] = '\0';
+ state = S_got_outstring;
+ break;
+ } else if (Back &&
+ !(LYCharSet_UC[cs_to].enc == UCT_ENC_8859 ||
+ (LYCharSet_UC[cs_to].like8859 &
+ UCT_R_8859SPECL))) {
+ ; /* nothing, may be translated later */
+ } else if (hidden || Back) {
+ state = S_got_outchar;
+ break;
+ } else if (use_lynx_specials) {
+ code = LY_SOFT_HYPHEN;
+ state = S_got_outchar;
+ break;
+ }
+ }
+ /*
+ * Seek a translation from the chartrans tables.
+ */
+ if ((uck = UCTransUniChar(code,
+ cs_to)) >= 32 &&
+ uck < 256 &&
+ (uck < 127 || uck >= lowest_8)) {
+ code = uck;
+ state = S_got_outchar;
+ break;
+ } else if ((uck == -4 ||
+ (repl_translated_C0 &&
+ uck > 0 && uck < 32)) &&
+ /*
+ * Not found; look for replacement string.
+ */
+ UCTransUniCharStr(replace_buf,
+ 60, code,
+ cs_to,
+ 0) >= 0) {
+ state = S_got_outstring;
+ break;
+ }
+ if (output_utf8 &&
+ code > 127 && code < 0x7fffffffL) {
+ state = S_got_oututf8;
+ break;
+ }
+ /*
+ * For 8194 (ensp), 8195 (emsp), or 8201 (thinsp), use the
+ * character reference if it's a hidden INPUT, otherwise use an
+ * ASCII space (32) if plain_space is TRUE, otherwise use the Lynx
+ * special character. - FM
+ */
+ if (code == 8194 || code == 8195 || code == 8201) {
+ if (hidden) {
+ state = S_recover;
+ } else if (plain_space) {
+ code = ' ';
+ state = S_got_outchar;
+ } else {
+ code = HT_EN_SPACE;
+ state = S_got_outchar;
+ }
+ break;
+ /*
+ * Ignore 8204 (zwnj), 8205 (zwj) 8206 (lrm), and 8207 (rlm),
+ * for now, if we got this far without finding a representation
+ * for them.
+ */
+ } else if (code == 8204 || code == 8205 ||
+ code == 8206 || code == 8207) {
+ CTRACE((tfp, "LYUCFullyTranslateString: Ignoring '%"
+ PRI_UCode_t "'.\n", CAST_UCode_t (code)));
+ replace_buf[0] = '\0';
+ state = S_got_outstring;
+ break;
+ /*
+ * Show the numeric entity if the value: (1) Is greater than
+ * 255 and unhandled Unicode.
+ */
+ } else if (code > 255) {
+ /*
+ * Illegal or not yet handled value. Return "&#" verbatim and
+ * continue from there. - FM
+ */
+ state = S_recover;
+ break;
+ /*
+ * If it's ASCII, or is 8-bit but HTPassEightBitNum is set or
+ * the character set is "ISO Latin 1", use it's value. - FM
+ */
+ } else if (code < 161 ||
+ (code < 256 &&
+ (HTPassEightBitNum || cs_to == LATIN1))) {
+ /*
+ * No conversion needed.
+ */
+ state = S_got_outchar;
+ break;
+
+ /* The following disabled section doesn't make sense any more.
+ * It used to make sense in the past, when S_check_named would
+ * look in "old style" tables in addition to what it does now.
+ * Disabling of going to S_check_name here prevents endless
+ * looping between S_check_uni and S_check_names states, which
+ * could occur here for Latin 1 codes for some cs_to if they
+ * had no translation in that cs_to. Normally all cs_to
+ * *should* now have valid translations via UCTransUniChar or
+ * UCTransUniCharStr for all Latin 1 codes, so that we would
+ * not get here anyway, and no loop could occur. Still, if we
+ * *do* get here, FALL THROUGH to case S_recover now. - kw
+ */
+#if 0
+ /*
+ * If we get to here, convert and handle the character as a
+ * named entity. - FM
+ */
+ } else {
+ name = HTMLGetEntityName(code - 160);
+ state = S_check_name;
+ break;
+#endif
+ }
+ /* FALLTHRU */
+
+ case S_recover:
+ if (what == P_decimal || what == P_hex) {
+ /*
+ * Illegal or not yet handled value. Return "&#" verbatim and
+ * continue from there. - FM
+ */
+ *q++ = '&';
+ *q++ = '#';
+ if (what == P_hex)
+ *q++ = 'x';
+ if (cpe != '\0')
+ *(p - 1) = cpe;
+ p = cp;
+ state = S_done;
+ } else if (what == P_named) {
+ *cp = cpe;
+ *q++ = '&';
+ state = S_done;
+ } else if (!T.output_utf8 && stype == st_HTML && !hidden &&
+ !(HTPassEightBitRaw &&
+ UCH(*p) >= lowest_8)) {
+ sprintf(replace_buf, "U%.2" PRI_UCode_t "", CAST_UCode_t (code));
+
+ state = S_got_outstring;
+ } else {
+ puni = p;
+ code = UCH(*p);
+ state = S_got_outchar;
+ }
+ break;
+
+ case S_named:
+ cp = ++p;
+ while (*cp && UCH(*cp) < 127 &&
+ isalnum(UCH(*cp)))
+ cp++;
+ cpe = *cp;
+ *cp = '\0';
+ name = p;
+ state = S_check_name;
+ break;
+
+ case S_check_name:
+ /*
+ * Seek the Unicode value for the named entity.
+ *
+ * !!!! We manually recover the case of '=' terminator which is
+ * commonly found on query to CGI-scripts enclosed as href= URLs
+ * like "somepath/?x=1&yz=2" Without this dirty fix, submission of
+ * such URLs was broken if &yz string happened to be a recognized
+ * entity name. - LP
+ */
+ if (((code = HTMLGetEntityUCValue(name)) > 0) &&
+ !((cpe == '=') && (stype == st_URL))) {
+ state = S_check_uni;
+ break;
+ }
+ /*
+ * Didn't find the entity. Return verbatim.
+ */
+ state = S_recover;
+ break;
+
+ /* * * O U T P U T S T A T E S * * */
+
+ case S_got_oututf8:
+ if (code > 255 ||
+ (code >= 128 && LYCharSet_UC[cs_to].enc == UCT_ENC_UTF8)) {
+ UCConvertUniToUtf8(code, replace_buf);
+ state = S_got_outstring;
+ } else {
+ state = S_got_outchar;
+ }
+ break;
+ case S_got_outstring:
+ if (what == P_decimal || what == P_hex) {
+ if (cpe != ';' && cpe != '\0')
+ *(--p) = cpe;
+ p--;
+ } else if (what == P_named) {
+ *cp = cpe;
+ p = (*cp != ';') ? (cp - 1) : cp;
+ } else if (what == P_utf8) {
+ p = puni;
+ }
+ if (replace_buf[0] == '\0') {
+ state = S_next_char;
+ break;
+ }
+ if (stype == st_URL) {
+ code = replace_buf[0]; /* assume string OK if first char is */
+ if (code >= 127 ||
+ (code < 32 && (code != 9 && code != 10 && code != 0))) {
+ state = S_put_urlstring;
+ break;
+ }
+ }
+ REPLACE_STRING(replace_buf);
+ state = S_next_char;
+ break;
+ case S_put_urlstring:
+ esc = HTEscape(replace_buf, URL_XALPHAS);
+ REPLACE_STRING(esc);
+ FREE(esc);
+ state = S_next_char;
+ break;
+ case S_got_outchar:
+ if (what == P_decimal || what == P_hex) {
+ if (cpe != ';' && cpe != '\0')
+ *(--p) = cpe;
+ p--;
+ } else if (what == P_named) {
+ *cp = cpe;
+ p = (*cp != ';') ? (cp - 1) : cp;
+ } else if (what == P_utf8) {
+ p = puni;
+ }
+ if (stype == st_URL &&
+ /* Not a full HTEscape, only for 8bit and ctrl chars */
+ (TOASCII(code) >= 127 || /* S/390 -- gil -- 1925 */
+ (code < ' ' && (code != '\t' && code != '\n')))) {
+ state = S_put_urlchar;
+ break;
+ } else if (!hidden && code == 10 && *p == 10
+ && q != qs && *(q - 1) == 13) {
+ /*
+ * If this is not a hidden string, and the current char is the
+ * LF ('\n') of a CRLF pair, drop the CR ('\r'). - KW
+ */
+ *(q - 1) = *p++;
+ state = S_done;
+ break;
+ }
+ *q++ = (char) code;
+ state = S_next_char;
+ break;
+ case S_put_urlchar:
+ *q++ = '%';
+ REPLACE_CHAR(hex[(TOASCII(code) >> 4) & 15]); /* S/390 -- gil -- 1944 */
+ REPLACE_CHAR(hex[(TOASCII(code) & 15)]);
+ /* fall through */
+ case S_next_char:
+ p++; /* fall through */
+ case S_done:
+ state = S_text;
+ what = P_text;
+ /* for next round */
+ }
+ }
+
+ *q = '\0';
+ if (chunk) {
+ HTChunkPutb(CHUNK, qs, (int) (q - qs + 1)); /* also terminates */
+ if (stype == st_URL || stype == st_other) {
+ LYTrimHead(chunk->data);
+ LYTrimTail(chunk->data);
+ }
+ StrAllocCopy(*str, chunk->data);
+ HTChunkFree(chunk);
+ } else {
+ if (stype == st_URL || stype == st_other) {
+ LYTrimHead(qs);
+ LYTrimTail(qs);
+ }
+ }
+ return str;
+}
+
+#undef REPLACE_CHAR
+#undef REPLACE_STRING
+
+BOOL LYUCTranslateHTMLString(char **str,
+ int cs_from,
+ int cs_to,
+ int use_lynx_specials,
+ int plain_space,
+ int hidden,
+ CharUtil_st stype)
+{
+ BOOL ret = YES;
+
+ /* May reallocate *str even if cs_to == 0 */
+ if (!LYUCFullyTranslateString(str, cs_from, cs_to, TRUE,
+ use_lynx_specials, plain_space, hidden,
+ NO, stype)) {
+ ret = NO;
+ }
+ return ret;
+}
+
+BOOL LYUCTranslateBackFormData(char **str,
+ int cs_from,
+ int cs_to,
+ int plain_space)
+{
+ char **ret;
+
+ /* May reallocate *str */
+ ret = (LYUCFullyTranslateString(str, cs_from, cs_to, FALSE,
+ NO, plain_space, YES,
+ YES, st_HTML));
+ return (BOOL) (ret != NULL);
+}
+
+/*
+ * Parse a parameter from an HTML META tag, i.e., the CONTENT.
+ */
+char *LYParseTagParam(char *from,
+ const char *name)
+{
+ size_t len = strlen(name);
+ char *result = NULL;
+ char *string = from;
+
+ do {
+ if ((string = StrChr(string, ';')) == NULL)
+ return NULL;
+ while (*string != '\0' && (*string == ';' || isspace(UCH(*string)))) {
+ string++;
+ }
+ if (strlen(string) < len)
+ return NULL;
+ } while (strncasecomp(string, name, (int) len) != 0);
+ string += len;
+ while (*string != '\0' && (isspace(UCH(*string)) || *string == '=')) {
+ string++;
+ }
+
+ StrAllocCopy(result, string);
+ len = 0;
+ while (isprint(UCH(string[len])) && !isspace(UCH(string[len]))) {
+ len++;
+ }
+ result[len] = '\0';
+
+ /*
+ * Strip single quotes, just in case.
+ */
+ if (len > 2 && result[0] == '\'' && result[len - 1] == result[0]) {
+ result[len - 1] = '\0';
+ for (string = result; (string[0] = string[1]) != '\0'; ++string) ;
+ }
+ return result;
+}
+
+/*
+ * Given a refresh-URL content string, parses the delay time and the URL
+ * string. Ignore the remainder of the content.
+ */
+void LYParseRefreshURL(char *content,
+ char **p_seconds,
+ char **p_address)
+{
+ char *cp;
+ char *cp1 = NULL;
+ char *Seconds = NULL;
+
+ /*
+ * Look for the Seconds field. - FM
+ */
+ cp = LYSkipBlanks(content);
+ if (*cp && isdigit(UCH(*cp))) {
+ cp1 = cp;
+ while (*cp1 && isdigit(UCH(*cp1)))
+ cp1++;
+ StrnAllocCopy(Seconds, cp, (size_t) (cp1 - cp));
+ }
+ *p_seconds = Seconds;
+ *p_address = LYParseTagParam(content, "URL");
+
+ CTRACE((tfp,
+ "LYParseRefreshURL\n\tcontent: %s\n\tseconds: %s\n\taddress: %s\n",
+ content, NonNull(*p_seconds), NonNull(*p_address)));
+}
+
+/*
+ * This function processes META tags in HTML streams. - FM
+ */
+void LYHandleMETA(HTStructured * me, const BOOL *present,
+ STRING2PTR value,
+ char **include GCC_UNUSED)
+{
+ char *http_equiv = NULL, *name = NULL, *content = NULL, *charset = NULL;
+ char *href = NULL, *id_string = NULL, *temp = NULL;
+ char *cp, *cp0, *cp1 = NULL;
+ int url_type = 0;
+
+ if (!me || !present)
+ return;
+
+ /*
+ * Load the attributes for possible use by Lynx. - FM
+ */
+ if (present[HTML_META_HTTP_EQUIV] &&
+ non_empty(value[HTML_META_HTTP_EQUIV])) {
+ StrAllocCopy(http_equiv, value[HTML_META_HTTP_EQUIV]);
+ convert_to_spaces(http_equiv, TRUE);
+ LYUCTranslateHTMLString(&http_equiv, me->tag_charset, me->tag_charset,
+ NO, NO, YES, st_other);
+ if (*http_equiv == '\0') {
+ FREE(http_equiv);
+ }
+ }
+ if (present[HTML_META_NAME] &&
+ non_empty(value[HTML_META_NAME])) {
+ StrAllocCopy(name, value[HTML_META_NAME]);
+ convert_to_spaces(name, TRUE);
+ LYUCTranslateHTMLString(&name, me->tag_charset, me->tag_charset,
+ NO, NO, YES, st_other);
+ if (*name == '\0') {
+ FREE(name);
+ }
+ }
+ if (present[HTML_META_CONTENT] &&
+ non_empty(value[HTML_META_CONTENT])) {
+ /*
+ * Technically, we should be creating a comma-separated list, but META
+ * tags come one at a time, and we'll handle (or ignore) them as each
+ * is received. Also, at this point, we only trim leading and trailing
+ * blanks from the CONTENT value, without translating any named
+ * entities or numeric character references, because how we should do
+ * that depends on what type of information it contains, and whether or
+ * not any of it might be sent to the screen. - FM
+ */
+ StrAllocCopy(content, value[HTML_META_CONTENT]);
+ convert_to_spaces(content, FALSE);
+ LYTrimHead(content);
+ LYTrimTail(content);
+ if (*content == '\0') {
+ FREE(content);
+ }
+ }
+ if (present[HTML_META_CHARSET] &&
+ non_empty(value[HTML_META_CHARSET])) {
+ StrAllocCopy(charset, value[HTML_META_CHARSET]);
+ convert_to_spaces(charset, TRUE);
+ LYUCTranslateHTMLString(&charset, me->tag_charset, me->tag_charset,
+ NO, NO, YES, st_other);
+ if (*charset == '\0') {
+ FREE(charset);
+ }
+ }
+ CTRACE((tfp,
+ "LYHandleMETA: HTTP-EQUIV=\"%s\" NAME=\"%s\" CONTENT=\"%s\" CHARSET=\"%s\"\n",
+ NONNULL(http_equiv),
+ NONNULL(name),
+ NONNULL(content),
+ NONNULL(charset)));
+
+ /*
+ * Check for a text/html Content-Type with a charset directive, if we
+ * didn't already set the charset via a server's header. - AAC & FM
+ */
+ if (isEmpty(me->node_anchor->charset) &&
+ (charset ||
+ (!strcasecomp(NonNull(http_equiv), "Content-Type") && content))) {
+ LYUCcharset *p_in = NULL;
+ LYUCcharset *p_out = NULL;
+
+ if (charset) {
+ LYLowerCase(charset);
+ } else {
+ LYUCTranslateHTMLString(&content, me->tag_charset, me->tag_charset,
+ NO, NO, YES, st_other);
+ LYLowerCase(content);
+ }
+
+ if ((cp1 = charset) != NULL ||
+ (cp1 = strstr(content, "charset")) != NULL) {
+ BOOL chartrans_ok = NO;
+ char *cp3 = NULL, *cp4;
+ int chndl;
+
+ if (!charset)
+ cp1 += 7;
+ while (*cp1 == ' ' || *cp1 == '=' || *cp1 == '"')
+ cp1++;
+
+ StrAllocCopy(cp3, cp1); /* 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);
+
+#ifdef CAN_SWITCH_DISPLAY_CHARSET
+ /* Allow a switch to a more suitable display charset */
+ if (Switch_Display_Charset(chndl, SWITCH_DISPLAY_CHARSET_MAYBE)) {
+ /* UCT_STAGE_STRUCTURED and UCT_STAGE_HTEXT
+ should have the same setting for UCInfoStage. */
+ HTAnchor_getUCInfoStage(me->node_anchor, UCT_STAGE_STRUCTURED);
+
+ me->outUCLYhndl = current_char_set;
+ HTAnchor_setUCInfoStage(me->node_anchor,
+ current_char_set,
+ UCT_STAGE_HTEXT,
+ UCT_SETBY_MIME); /* highest priority! */
+ HTAnchor_setUCInfoStage(me->node_anchor,
+ current_char_set,
+ UCT_STAGE_STRUCTURED,
+ UCT_SETBY_MIME); /* highest priority! */
+ me->outUCI = HTAnchor_getUCInfoStage(me->node_anchor,
+ UCT_STAGE_HTEXT);
+ /* The SGML stage will be reset in change_chartrans_handling */
+ }
+#endif
+
+ if (UCCanTranslateFromTo(chndl, current_char_set)) {
+ chartrans_ok = YES;
+ StrAllocCopy(me->node_anchor->charset, cp4);
+ HTAnchor_setUCInfoStage(me->node_anchor, chndl,
+ UCT_STAGE_PARSER,
+ UCT_SETBY_STRUCTURED);
+ } else if (chndl < 0) {
+ /*
+ * Got something but we don't recognize it.
+ */
+ chndl = UCLYhndl_for_unrec;
+ if (chndl < 0) /* UCLYhndl_for_unrec not defined :-( */
+ chndl = UCLYhndl_for_unspec; /* always >= 0 */
+ if (UCCanTranslateFromTo(chndl, current_char_set)) {
+ chartrans_ok = YES;
+ HTAnchor_setUCInfoStage(me->node_anchor, chndl,
+ UCT_STAGE_PARSER,
+ UCT_SETBY_STRUCTURED);
+ }
+ }
+ if (chartrans_ok) {
+ p_in = HTAnchor_getUCInfoStage(me->node_anchor,
+ UCT_STAGE_PARSER);
+ p_out = HTAnchor_setUCInfoStage(me->node_anchor,
+ current_char_set,
+ UCT_STAGE_HTEXT,
+ UCT_SETBY_DEFAULT);
+ if (!p_out) {
+ /*
+ * Try again.
+ */
+ p_out = HTAnchor_getUCInfoStage(me->node_anchor,
+ UCT_STAGE_HTEXT);
+ }
+ if (!strcmp(p_in->MIMEname, "x-transparent")) {
+ HTPassEightBitRaw = TRUE;
+ HTAnchor_setUCInfoStage(me->node_anchor,
+ HTAnchor_getUCLYhndl(me->node_anchor,
+ UCT_STAGE_HTEXT),
+ UCT_STAGE_PARSER,
+ UCT_SETBY_DEFAULT);
+ }
+ if (!strcmp(p_out->MIMEname, "x-transparent")) {
+ HTPassEightBitRaw = TRUE;
+ HTAnchor_setUCInfoStage(me->node_anchor,
+ HTAnchor_getUCLYhndl(me->node_anchor,
+ UCT_STAGE_PARSER),
+ UCT_STAGE_HTEXT,
+ UCT_SETBY_DEFAULT);
+ }
+ if ((p_in->enc != UCT_ENC_CJK)
+#ifdef USE_JAPANESEUTF8_SUPPORT
+ && (p_in->enc != UCT_ENC_UTF8)
+#endif
+ ) {
+ 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);
+ }
+ LYGetChartransInfo(me);
+ /*
+ * Update the chartrans info homologously to a Content-Type
+ * MIME header with a charset parameter. - FM
+ */
+ if (me->UCLYhndl != chndl) {
+ HTAnchor_setUCInfoStage(me->node_anchor, chndl,
+ UCT_STAGE_MIME,
+ UCT_SETBY_STRUCTURED);
+ HTAnchor_setUCInfoStage(me->node_anchor, chndl,
+ UCT_STAGE_PARSER,
+ UCT_SETBY_STRUCTURED);
+ me->inUCLYhndl = HTAnchor_getUCLYhndl(me->node_anchor,
+ UCT_STAGE_PARSER);
+ me->inUCI = HTAnchor_getUCInfoStage(me->node_anchor,
+ UCT_STAGE_PARSER);
+ }
+ UCSetTransParams(&me->T,
+ me->inUCLYhndl, me->inUCI,
+ me->outUCLYhndl, me->outUCI);
+ } 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_is_8859) {
+ cp1 = &cp4[10];
+ while (*cp1 &&
+ isdigit(UCH((*cp1))))
+ cp1++;
+ *cp1 = '\0';
+ }
+ if (given_and_display_8859like) {
+ StrAllocCopy(me->node_anchor->charset, cp4);
+ HTPassEightBitRaw = TRUE;
+ }
+ HTAlert(*cp4 ? cp4 : me->node_anchor->charset);
+
+ }
+ FREE(cp3);
+
+ if (me->node_anchor->charset) {
+ CTRACE((tfp,
+ "LYHandleMETA: New charset: %s\n",
+ me->node_anchor->charset));
+ }
+ }
+ /*
+ * Set the kcode element based on the charset. - FM
+ */
+ HText_setKcode(me->text, me->node_anchor->charset, p_in);
+ }
+
+ /*
+ * Make sure we have META name/value pairs to handle. - FM
+ */
+ if (!(http_equiv || name) || !content)
+ goto free_META_copies;
+
+ /*
+ * Check for a no-cache Pragma
+ * or Cache-Control directive. - FM
+ */
+ if (!strcasecomp(NonNull(http_equiv), "Pragma") ||
+ !strcasecomp(NonNull(http_equiv), "Cache-Control")) {
+ LYUCTranslateHTMLString(&content, me->tag_charset, me->tag_charset,
+ NO, NO, YES, st_other);
+ if (!strcasecomp(content, "no-cache")) {
+ me->node_anchor->no_cache = TRUE;
+ HText_setNoCache(me->text);
+ }
+
+ /*
+ * If we didn't get a Cache-Control MIME header, and the META has one,
+ * convert to lowercase, store it in the anchor element, and if we
+ * haven't yet set no_cache, check whether we should. - FM
+ */
+ if ((!me->node_anchor->cache_control) &&
+ !strcasecomp(NonNull(http_equiv), "Cache-Control")) {
+ LYLowerCase(content);
+ StrAllocCopy(me->node_anchor->cache_control, content);
+ if (me->node_anchor->no_cache == FALSE) {
+ cp0 = content;
+ while ((cp = strstr(cp0, "no-cache")) != NULL) {
+ cp += 8;
+ while (*cp != '\0' && WHITE(*cp))
+ cp++;
+ if (*cp == '\0' || *cp == ';') {
+ me->node_anchor->no_cache = TRUE;
+ HText_setNoCache(me->text);
+ break;
+ }
+ cp0 = cp;
+ }
+ if (me->node_anchor->no_cache == TRUE)
+ goto free_META_copies;
+ cp0 = content;
+ while ((cp = strstr(cp0, "max-age")) != NULL) {
+ cp += 7;
+ while (*cp != '\0' && WHITE(*cp))
+ cp++;
+ if (*cp == '=') {
+ cp++;
+ while (*cp != '\0' && WHITE(*cp))
+ cp++;
+ if (isdigit(UCH(*cp))) {
+ cp0 = cp;
+ while (isdigit(UCH(*cp)))
+ cp++;
+ if (*cp0 == '0' && cp == (cp0 + 1)) {
+ me->node_anchor->no_cache = TRUE;
+ HText_setNoCache(me->text);
+ break;
+ }
+ }
+ }
+ cp0 = cp;
+ }
+ }
+ }
+
+ /*
+ * Check for an Expires directive. - FM
+ */
+ } else if (!strcasecomp(NonNull(http_equiv), "Expires")) {
+ /*
+ * If we didn't get an Expires MIME header, store it in the anchor
+ * element, and if we haven't yet set no_cache, check whether we
+ * should. Note that we don't accept a Date header via META tags,
+ * because it's likely to be untrustworthy, but do check for a Date
+ * header from a server when making the comparison. - FM
+ */
+ LYUCTranslateHTMLString(&content, me->tag_charset, me->tag_charset,
+ NO, NO, YES, st_other);
+ StrAllocCopy(me->node_anchor->expires, content);
+ if (me->node_anchor->no_cache == FALSE) {
+ if (!strcmp(content, "0")) {
+ /*
+ * The value is zero, which we treat as an absolute no-cache
+ * directive. - FM
+ */
+ me->node_anchor->no_cache = TRUE;
+ HText_setNoCache(me->text);
+ } else if (me->node_anchor->date != NULL) {
+ /*
+ * We have a Date header, so check if the value is less than or
+ * equal to that. - FM
+ */
+ if (LYmktime(content, TRUE) <=
+ LYmktime(me->node_anchor->date, TRUE)) {
+ me->node_anchor->no_cache = TRUE;
+ HText_setNoCache(me->text);
+ }
+ } else if (LYmktime(content, FALSE) == 0) {
+ /*
+ * We don't have a Date header, and the value is in past for
+ * us. - FM
+ */
+ me->node_anchor->no_cache = TRUE;
+ HText_setNoCache(me->text);
+ }
+ }
+
+ /*
+ * Check for a Refresh directive. - FM
+ */
+ } else if (!strcasecomp(NonNull(http_equiv), "Refresh")) {
+ char *Seconds = NULL;
+
+ LYUCTranslateHTMLString(&content, me->tag_charset, me->tag_charset,
+ NO, NO, YES, st_other);
+ LYParseRefreshURL(content, &Seconds, &href);
+
+ if (Seconds) {
+ if (href) {
+ /*
+ * We found a URL field, so check it out. - FM
+ */
+ if (!LYLegitimizeHREF(me, &href, TRUE, FALSE)) {
+ /*
+ * The specs require a complete URL, but this is a
+ * Netscapism, so don't expect the author to know that. -
+ * FM
+ */
+ HTUserMsg(REFRESH_URL_NOT_ABSOLUTE);
+ /*
+ * Use the document's address as the base. - FM
+ */
+ if (*href != '\0') {
+ temp = HTParse(href,
+ me->node_anchor->address, PARSE_ALL);
+ StrAllocCopy(href, temp);
+ FREE(temp);
+ } else {
+ StrAllocCopy(href, me->node_anchor->address);
+ HText_setNoCache(me->text);
+ }
+
+ } else {
+ /*
+ * Check whether to fill in localhost. - FM
+ */
+ LYFillLocalFileURL(&href,
+ (me->inBASE ?
+ me->base_href : me->node_anchor->address));
+ }
+
+ /*
+ * Set the no_cache flag if the Refresh URL is the same as the
+ * document's address. - FM
+ */
+ if (!strcmp(href, me->node_anchor->address)) {
+ HText_setNoCache(me->text);
+ }
+ } else {
+ /*
+ * We didn't find a URL field, so use the document's own
+ * address and set the no_cache flag. - FM
+ */
+ StrAllocCopy(href, me->node_anchor->address);
+ HText_setNoCache(me->text);
+ }
+ /*
+ * Check for an anchor in http or https URLs. - FM
+ */
+ cp = NULL;
+ /* id_string seems to be used wrong below if given.
+ not that it matters much. avoid setting it here. - kw */
+ if (track_internal_links &&
+ (StrNCmp(href, "http", 4) == 0) &&
+ (cp = StrChr(href, '#')) != NULL) {
+ StrAllocCopy(id_string, cp);
+ *cp = '\0';
+ }
+ if (me->inA) {
+ /*
+ * Ugh! The META 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
+ */
+ 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->inA = FALSE;
+ me->CurrentANum = 0;
+ }
+ me->CurrentA = HTAnchor_findChildAndLink
+ (
+ me->node_anchor, /* Parent */
+ id_string, /* Tag */
+ href, /* Address */
+ (HTLinkType *) 0); /* Type */
+ if (id_string)
+ *cp = '#';
+ FREE(id_string);
+ LYEnsureSingleSpace(me);
+ if (me->inUnderline == FALSE)
+ HText_appendCharacter(me->text, LY_UNDERLINE_START_CHAR);
+ HTML_put_string(me, "REFRESH(");
+ HTML_put_string(me, Seconds);
+ HTML_put_string(me, " sec):");
+ FREE(Seconds);
+ if (me->inUnderline == FALSE)
+ HText_appendCharacter(me->text, LY_UNDERLINE_END_CHAR);
+ HTML_put_character(me, ' ');
+ me->in_word = NO;
+ HText_beginAnchor(me->text, me->inUnderline, me->CurrentA);
+ if (me->inBoldH == FALSE)
+ HText_appendCharacter(me->text, LY_BOLD_START_CHAR);
+ HTML_put_string(me, href);
+ FREE(href);
+ if (me->inBoldH == FALSE)
+ HText_appendCharacter(me->text, LY_BOLD_END_CHAR);
+ HText_endAnchor(me->text, 0);
+ LYEnsureSingleSpace(me);
+ }
+
+ /*
+ * Check for a suggested filename via a Content-Disposition with a
+ * filename=name.suffix in it, if we don't already have it via a server
+ * header. - FM
+ */
+ } else if (isEmpty(me->node_anchor->SugFname) &&
+ !strcasecomp((http_equiv ?
+ http_equiv : ""), "Content-Disposition")) {
+ cp = content;
+ while (*cp != '\0' && strncasecomp(cp, "filename", 8))
+ cp++;
+ if (*cp != '\0') {
+ cp = LYSkipBlanks(cp + 8);
+ if (*cp == '=')
+ cp++;
+ cp = LYSkipBlanks(cp);
+ if (*cp != '\0') {
+ StrAllocCopy(me->node_anchor->SugFname, cp);
+ if (*me->node_anchor->SugFname == '"') {
+ if ((cp = StrChr((me->node_anchor->SugFname + 1),
+ '"')) != NULL) {
+ *(cp + 1) = '\0';
+ HTMIME_TrimDoubleQuotes(me->node_anchor->SugFname);
+ if (isEmpty(me->node_anchor->SugFname)) {
+ FREE(me->node_anchor->SugFname);
+ }
+ } else {
+ FREE(me->node_anchor->SugFname);
+ }
+ }
+#if defined(UNIX) && !defined(DOSPATH)
+ /*
+ * If blanks are not legal for local filenames, replace them
+ * with underscores.
+ */
+ if ((cp = me->node_anchor->SugFname) != NULL) {
+ while (*cp != '\0') {
+ if (isspace(UCH(*cp)))
+ *cp = '_';
+ ++cp;
+ }
+ }
+#endif
+ }
+ }
+ /*
+ * Check for a Set-Cookie directive. - AK
+ */
+ } else if (!strcasecomp(NonNull(http_equiv), "Set-Cookie")) {
+ /*
+ * This will need to be updated when Set-Cookie/Set-Cookie2 handling is
+ * finalized. For now, we'll still assume "historical" cookies in META
+ * directives. - FM
+ */
+ url_type = is_url(me->inBASE ?
+ me->base_href : me->node_anchor->address);
+ if (url_type == HTTP_URL_TYPE || url_type == HTTPS_URL_TYPE) {
+ LYSetCookie(content,
+ NULL,
+ (me->inBASE ?
+ me->base_href : me->node_anchor->address));
+ }
+ }
+
+ /*
+ * Free the copies. - FM
+ */
+ free_META_copies:
+ FREE(http_equiv);
+ FREE(name);
+ FREE(content);
+ FREE(charset);
+}
+
+/*
+ * This function handles P elements in HTML streams.
+ * If start is TRUE it handles a start tag, and if
+ * FALSE, an end tag. We presently handle start
+ * and end tags identically, but this can lead to
+ * a different number of blank lines between the
+ * current paragraph and subsequent text when a P
+ * end tag is present or not in the markup. - FM
+ */
+void LYHandlePlike(HTStructured * me, const BOOL *present,
+ STRING2PTR value,
+ char **include GCC_UNUSED,
+ int align_idx,
+ int start)
+{
+ /*
+ * FIG content should be a true block, which like P inherits the current
+ * style. APPLET is like character elements or an ALT attribute, unless
+ * its content contains a block element. If we encounter a P in either's
+ * content, we set flags to treat the content as a block - FM
+ */
+ if (start) {
+ if (me->inFIG)
+ me->inFIGwithP = TRUE;
+
+ if (me->inAPPLET)
+ me->inAPPLETwithP = TRUE;
+ }
+
+ UPDATE_STYLE;
+ if (me->List_Nesting_Level >= 0) {
+ /*
+ * We're in a list. Treat P as an instruction to create one blank
+ * line, if not already present, then fall through to handle
+ * attributes, with the "second line" margins - FM
+ */
+ if (me->inP) {
+ if (me->inFIG || me->inAPPLET ||
+ me->inCAPTION || me->inCREDIT ||
+ me->sp->style->spaceAfter > 0 ||
+ (start && me->sp->style->spaceBefore > 0)) {
+ LYEnsureDoubleSpace(me);
+ } else {
+ LYEnsureSingleSpace(me);
+ }
+ }
+ } else if (me->sp[0].tag_number == HTML_ADDRESS) {
+ /*
+ * We're in an ADDRESS. Treat P as an instruction to start a newline,
+ * if needed, then fall through to handle attributes - FM
+ */
+ if (!HText_LastLineEmpty(me->text, FALSE)) {
+ HText_setLastChar(me->text, ' '); /* absorb white space */
+ HText_appendCharacter(me->text, '\r');
+ }
+ } else {
+ if (start) {
+ if (!(me->inLABEL && !me->inP)) {
+ HText_appendParagraph(me->text);
+ }
+ } else if (me->sp->style->spaceAfter > 0) {
+ LYEnsureDoubleSpace(me);
+ } else {
+ LYEnsureSingleSpace(me);
+ }
+ me->inLABEL = FALSE;
+ }
+ me->in_word = NO;
+
+ if (LYoverride_default_alignment(me)) {
+ me->sp->style->alignment = LYstyles(me->sp[0].tag_number)->alignment;
+ } else if ((me->List_Nesting_Level >= 0 &&
+ (me->sp->style->id == ST_DivCenter ||
+ me->sp->style->id == ST_DivLeft ||
+ me->sp->style->id == ST_DivRight)) ||
+ ((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 (start && align_idx >= 0) {
+ if (present && present[align_idx] && value[align_idx]) {
+ if (!strcasecomp(value[align_idx], "center") &&
+ !(me->List_Nesting_Level >= 0 && !me->inP))
+ me->sp->style->alignment = HT_CENTER;
+ else if (!strcasecomp(value[align_idx], "right") &&
+ !(me->List_Nesting_Level >= 0 && !me->inP))
+ me->sp->style->alignment = HT_RIGHT;
+ else if (!strcasecomp(value[align_idx], "left") ||
+ !strcasecomp(value[align_idx], "justify"))
+ me->sp->style->alignment = HT_LEFT;
+ }
+
+ }
+
+ /*
+ * Mark that we are starting a new paragraph and don't have any of its
+ * text yet - FM
+ */
+ me->inP = FALSE;
+
+ return;
+}
+
+/*
+ * This function handles SELECT elements in HTML streams.
+ * If start is TRUE it handles a start tag, and if FALSE,
+ * an end tag. - FM
+ */
+void LYHandleSELECT(HTStructured * me, const BOOL *present,
+ STRING2PTR value,
+ char **include GCC_UNUSED,
+ int start)
+{
+ int i;
+
+ if (start == TRUE) {
+ char *name = NULL;
+ BOOLEAN multiple = NO;
+ char *size = NULL;
+
+ /*
+ * Initialize the disable attribute.
+ */
+ me->select_disabled = FALSE;
+
+ /*
+ * Check for unclosed TEXTAREA.
+ */
+ if (me->inTEXTAREA) {
+ if (LYBadHTML(me)) {
+ LYShowBadHTML("Bad HTML: Missing TEXTAREA end tag\n");
+ }
+ }
+
+ /*
+ * Set to know we are in a select tag.
+ */
+ me->inSELECT = TRUE;
+
+ if (!(present && present[HTML_SELECT_NAME] &&
+ non_empty(value[HTML_SELECT_NAME]))) {
+ StrAllocCopy(name, "");
+ } else if (StrChr(value[HTML_SELECT_NAME], '&') == NULL) {
+ StrAllocCopy(name, value[HTML_SELECT_NAME]);
+ } else {
+ StrAllocCopy(name, value[HTML_SELECT_NAME]);
+ UNESCAPE_FIELDNAME_TO_STD(&name);
+ }
+ if (present && present[HTML_SELECT_MULTIPLE])
+ multiple = YES;
+ if (present && present[HTML_SELECT_DISABLED])
+ me->select_disabled = TRUE;
+ if (present && present[HTML_SELECT_SIZE] &&
+ non_empty(value[HTML_SELECT_SIZE])) {
+ /*
+ * Let the size be determined by the number of OPTIONs. - FM
+ */
+ CTRACE((tfp, "LYHandleSELECT: Ignoring SIZE=\"%s\" for SELECT.\n",
+ value[HTML_SELECT_SIZE]));
+ }
+
+ if (me->inBoldH == TRUE &&
+ (multiple == NO || LYSelectPopups == FALSE)) {
+ HText_appendCharacter(me->text, LY_BOLD_END_CHAR);
+ me->inBoldH = FALSE;
+ me->needBoldH = TRUE;
+ }
+ if (me->inUnderline == TRUE &&
+ (multiple == NO || LYSelectPopups == FALSE)) {
+ HText_appendCharacter(me->text, LY_UNDERLINE_END_CHAR);
+ me->inUnderline = FALSE;
+ }
+
+ if ((multiple == NO && LYSelectPopups == TRUE) &&
+ (me->sp[0].tag_number == HTML_PRE || me->inPRE == TRUE ||
+ !me->sp->style->freeFormat) &&
+ HText_LastLineSize(me->text, FALSE) > (LYcolLimit - 7)) {
+ /*
+ * Force a newline when we're using a popup in a PRE block and are
+ * within 7 columns from the right margin. This will allow for the
+ * '[' popup designator and help avoid a wrap in the underscore
+ * placeholder for the retracted popup entry in the HText
+ * structure. - FM
+ */
+ HTML_put_character(me, '\n');
+ me->in_word = NO;
+ }
+
+ LYCheckForID(me, present, value, (int) HTML_SELECT_ID);
+
+ HText_beginSelect(name, ATTR_CS_IN, multiple, size);
+ FREE(name);
+ FREE(size);
+
+ me->first_option = TRUE;
+ } else {
+ /*
+ * Handle end tag.
+ */
+ char *ptr;
+
+ /*
+ * Make sure we had a select start tag.
+ */
+ if (!me->inSELECT) {
+ if (LYBadHTML(me)) {
+ LYShowBadHTML("Bad HTML: Unmatched SELECT end tag\n");
+ }
+ return;
+ }
+
+ /*
+ * Set to know that we are no longer in a select tag.
+ */
+ me->inSELECT = FALSE;
+
+ /*
+ * Clear the disable attribute.
+ */
+ me->select_disabled = FALSE;
+
+ /*
+ * Finish the data off.
+ */
+ HTChunkTerminate(&me->option);
+ /*
+ * Finish the previous option.
+ */
+ ptr = HText_setLastOptionValue(me->text,
+ me->option.data,
+ me->LastOptionValue,
+ LAST_ORDER,
+ me->LastOptionChecked,
+ me->UCLYhndl,
+ ATTR_CS_IN);
+ FREE(me->LastOptionValue);
+
+ me->LastOptionChecked = FALSE;
+
+ if (HTCurSelectGroupType == F_CHECKBOX_TYPE ||
+ LYSelectPopups == FALSE) {
+ /*
+ * Start a newline after the last checkbox/button option.
+ */
+ LYEnsureSingleSpace(me);
+ } else {
+ /*
+ * Output popup box with the default option to screen, but use
+ * non-breaking spaces for output.
+ */
+ if (ptr &&
+ me->sp[0].tag_number == HTML_PRE && strlen(ptr) > 6) {
+ /*
+ * The code inadequately handles OPTION fields in PRE tags.
+ * We'll put up a minimum of 6 characters, and if any more
+ * would exceed the wrap column, we'll ignore them.
+ */
+ for (i = 0; i < 6; i++) {
+ if (*ptr == ' ')
+ HText_appendCharacter(me->text, HT_NON_BREAK_SPACE);
+ else
+ HText_appendCharacter(me->text, *ptr);
+ ptr++;
+ }
+ }
+ for (; non_empty(ptr); ptr++) {
+ if (*ptr == ' ')
+ HText_appendCharacter(me->text, HT_NON_BREAK_SPACE);
+ else
+ HText_appendCharacter(me->text, *ptr);
+ }
+ /*
+ * Add end option character.
+ */
+ if (!me->first_option) {
+ HText_appendCharacter(me->text, ']');
+ HText_setLastChar(me->text, ']');
+ me->in_word = YES;
+ }
+ }
+ HTChunkClear(&me->option);
+
+ if (me->Underline_Level > 0 && me->inUnderline == FALSE) {
+ HText_appendCharacter(me->text, LY_UNDERLINE_START_CHAR);
+ me->inUnderline = TRUE;
+ }
+ if (me->needBoldH == TRUE && me->inBoldH == FALSE) {
+ HText_appendCharacter(me->text, LY_BOLD_START_CHAR);
+ me->inBoldH = TRUE;
+ me->needBoldH = FALSE;
+ }
+ }
+}
+
+/*
+ * This function strips white characters and
+ * generally fixes up attribute values that
+ * were received from the SGML parser and
+ * are to be treated as partial or absolute
+ * URLs. - FM
+ */
+int LYLegitimizeHREF(HTStructured * me, char **href,
+ int force_slash,
+ int strip_dots)
+{
+ int url_type = 0;
+ char *p = NULL;
+ char *pound = NULL;
+ const char *Base = NULL;
+
+ if (!me || !href || isEmpty(*href))
+ return (url_type);
+
+ if (!LYTrimStartfile(*href)) {
+ /*
+ * Collapse spaces in the actual URL, but just protect against tabs or
+ * newlines in the fragment, if present. This seeks to cope with
+ * atrocities inflicted on the Web by authoring tools such as
+ * Frontpage. - FM
+ */
+
+ /* Before working on spaces check if we have any, usually none. */
+ p = LYSkipNonBlanks(*href);
+
+ if (*p) { /* p == first space character */
+ /* no reallocs below, all converted in place */
+
+ pound = findPoundSelector(*href);
+
+ if (pound != NULL && pound < p) {
+ convert_to_spaces(p, FALSE); /* done */
+
+ } else {
+ if (pound != NULL)
+ *pound = '\0'; /* mark */
+
+ /*
+ * No blanks really belong in the HREF,
+ * but if it refers to an actual file,
+ * it may actually have blanks in the name.
+ * Try to accommodate. See also HTParse().
+ */
+ if (LYRemoveNewlines(p) || StrChr(p, '\t') != 0) {
+ LYRemoveBlanks(p); /* a compromise... */
+ }
+
+ if (pound != NULL) {
+ p = StrChr(p, '\0');
+ *pound = '#'; /* restore */
+ convert_to_spaces(pound, FALSE);
+ if (p < pound) {
+ int n;
+
+ for (n = 0; (p[n] = pound[n]) != '\0'; ++n) ;
+ }
+ }
+ }
+ }
+ }
+ if (**href == '\0')
+ return (url_type);
+
+ TRANSLATE_AND_UNESCAPE_TO_STD(href);
+
+ Base = me->inBASE ?
+ me->base_href : me->node_anchor->address;
+
+ url_type = is_url(*href);
+ if (!url_type && force_slash && **href == '.' &&
+ (!strcmp(*href, ".") || !strcmp(*href, "..")) &&
+ !isFILE_URL(Base)) {
+ /*
+ * The Fielding RFC/ID for resolving partial HREFs says that a slash
+ * should be on the end of the preceding symbolic element for "." and
+ * "..", but all tested browsers only do that for an explicit "./" or
+ * "../", so we'll respect the RFC/ID only if force_slash was TRUE and
+ * it's not a file URL. - FM
+ */
+ StrAllocCat(*href, "/");
+ }
+ if ((!url_type && LYStripDotDotURLs && strip_dots && **href == '.') &&
+ !strncasecomp(Base, "http", 4)) {
+ /*
+ * We will be resolving a partial reference versus an http or https
+ * URL, and it has lead dots, which may be retained when resolving via
+ * HTParse(), but the request would fail if the first element of the
+ * resultant path is two dots, because no http or https server accepts
+ * such paths, and the current URL draft, likely to become an RFC, says
+ * that it's optional for the UA to strip them as a form of error
+ * recovery. So we will, recursively, for http/https URLs, like the
+ * "major market browsers" which made this problem so common on the
+ * Web, but we'll also issue a message about it, such that the bad
+ * partial reference might get corrected by the document provider. -
+ * FM
+ */
+ char *temp = NULL, *path = NULL, *cp;
+ const char *str = "";
+
+ temp = HTParse(*href, Base, PARSE_ALL);
+ path = HTParse(temp, "", PARSE_PATH + PARSE_PUNCTUATION);
+ if (!StrNCmp(path, "/..", 3)) {
+ cp = (path + 3);
+ if (LYIsHtmlSep(*cp) || *cp == '\0') {
+ if (Base[4] == 's') {
+ str = "s";
+ }
+ CTRACE((tfp,
+ "LYLegitimizeHREF: Bad value '%s' for http%s URL.\n",
+ *href, str));
+ CTRACE((tfp, " Stripping lead dots.\n"));
+ if (!me->inBadHREF) {
+ HTUserMsg(BAD_PARTIAL_REFERENCE);
+ me->inBadHREF = TRUE;
+ }
+ }
+ if (*cp == '\0') {
+ StrAllocCopy(*href, "/");
+ } else if (LYIsHtmlSep(*cp)) {
+ while (!StrNCmp(cp, "/..", 3)) {
+ if (*(cp + 3) == '/') {
+ cp += 3;
+ continue;
+ } else if (*(cp + 3) == '\0') {
+ *(cp + 1) = '\0';
+ *(cp + 2) = '\0';
+ }
+ break;
+ }
+ StrAllocCopy(*href, cp);
+ }
+ }
+ FREE(temp);
+ FREE(path);
+ }
+ return (url_type);
+}
+
+/*
+ * This function checks for a Content-Base header,
+ * and if not present, a Content-Location header
+ * which is an absolute URL, and sets the BASE
+ * accordingly. If set, it will be replaced by
+ * any BASE tag in the HTML stream, itself. - FM
+ */
+void LYCheckForContentBase(HTStructured * me)
+{
+ char *cp = NULL;
+ BOOL present[HTML_BASE_ATTRIBUTES];
+ const char *value[HTML_BASE_ATTRIBUTES];
+ int i;
+
+ if (!(me && me->node_anchor))
+ return;
+
+ if (me->node_anchor->content_base != NULL) {
+ /*
+ * We have a Content-Base value. Use it if it's non-zero length. - FM
+ */
+ if (*me->node_anchor->content_base == '\0')
+ return;
+ StrAllocCopy(cp, me->node_anchor->content_base);
+ LYRemoveBlanks(cp);
+ } else if (me->node_anchor->content_location != NULL) {
+ /*
+ * We didn't have a Content-Base value, but do have a Content-Location
+ * value. Use it if it's an absolute URL. - FM
+ */
+ if (*me->node_anchor->content_location == '\0')
+ return;
+ StrAllocCopy(cp, me->node_anchor->content_location);
+ LYRemoveBlanks(cp);
+ if (!is_url(cp)) {
+ FREE(cp);
+ return;
+ }
+ } else {
+ /*
+ * We had neither a Content-Base nor Content-Location value. - FM
+ */
+ return;
+ }
+
+ /*
+ * If we collapsed to a zero-length value, ignore it. - FM
+ */
+ if (*cp == '\0') {
+ FREE(cp);
+ return;
+ }
+
+ /*
+ * Pass the value to HTML_start_element as the HREF of a BASE tag. - FM
+ */
+ for (i = 0; i < HTML_BASE_ATTRIBUTES; i++)
+ present[i] = NO;
+ present[HTML_BASE_HREF] = YES;
+ value[HTML_BASE_HREF] = (const char *) cp;
+ (*me->isa->start_element) (me, HTML_BASE, present, value,
+ 0, 0);
+ FREE(cp);
+}
+
+/*
+ * This function creates NAMEd Anchors if a non-zero-length NAME
+ * or ID attribute was present in the tag. - FM
+ */
+void LYCheckForID(HTStructured * me, const BOOL *present,
+ STRING2PTR value,
+ int attribute)
+{
+ HTChildAnchor *ID_A = NULL;
+ char *temp = NULL;
+
+ if (!(me && me->text))
+ return;
+
+ if (present && present[attribute]
+ && non_empty(value[attribute])) {
+ /*
+ * Translate any named or numeric character references. - FM
+ */
+ StrAllocCopy(temp, value[attribute]);
+ LYUCTranslateHTMLString(&temp, me->tag_charset, me->tag_charset,
+ NO, NO, YES, st_URL);
+
+ /*
+ * Create the link if we still have a non-zero-length string. - FM
+ */
+ if ((temp[0] != '\0') &&
+ (ID_A = HTAnchor_findChildAndLink
+ (
+ me->node_anchor, /* Parent */
+ temp, /* Tag */
+ NULL, /* Address */
+ (HTLinkType *) 0))) { /* Type */
+ HText_beginAnchor(me->text, me->inUnderline, ID_A);
+ HText_endAnchor(me->text, 0);
+ }
+ FREE(temp);
+ }
+}
+
+/*
+ * This function creates a NAMEd Anchor for the ID string
+ * passed to it directly as an argument. It assumes the
+ * does not need checking for character references. - FM
+ */
+void LYHandleID(HTStructured * me, const char *id)
+{
+ HTChildAnchor *ID_A = NULL;
+
+ if (!(me && me->text) ||
+ isEmpty(id))
+ return;
+
+ /*
+ * Create the link if we still have a non-zero-length string. - FM
+ */
+ if ((ID_A = HTAnchor_findChildAndLink
+ (
+ me->node_anchor, /* Parent */
+ id, /* Tag */
+ NULL, /* Address */
+ (HTLinkType *) 0)) != NULL) { /* Type */
+ HText_beginAnchor(me->text, me->inUnderline, ID_A);
+ HText_endAnchor(me->text, 0);
+ }
+}
+
+/*
+ * This function checks whether we want to override
+ * the current default alignment for paragraphs and
+ * instead use that specified in the element's style
+ * sheet. - FM
+ */
+BOOLEAN LYoverride_default_alignment(HTStructured * me)
+{
+ if (!me)
+ return NO;
+
+ switch (me->sp[0].tag_number) {
+ case HTML_BLOCKQUOTE:
+ case HTML_BQ:
+ case HTML_NOTE:
+ case HTML_FN:
+ case HTML_ADDRESS:
+ me->sp->style->alignment = HT_LEFT;
+ return YES;
+
+ default:
+ break;
+ }
+ return NO;
+}
+
+/*
+ * This function inserts newlines if needed to create double spacing,
+ * and sets the left margin for subsequent text to the second line
+ * indentation of the current style. - FM
+ */
+void LYEnsureDoubleSpace(HTStructured * me)
+{
+ if (!me || !me->text)
+ return;
+
+ if (!HText_LastLineEmpty(me->text, FALSE)) {
+ HText_setLastChar(me->text, ' '); /* absorb white space */
+ HText_appendCharacter(me->text, '\r');
+ HText_appendCharacter(me->text, '\r');
+ } else if (!HText_PreviousLineEmpty(me->text, FALSE)) {
+ HText_setLastChar(me->text, ' '); /* absorb white space */
+ HText_appendCharacter(me->text, '\r');
+ } else if (me->List_Nesting_Level >= 0) {
+ HText_NegateLineOne(me->text);
+ }
+ me->in_word = NO;
+ return;
+}
+
+/*
+ * This function inserts a newline if needed to create single spacing,
+ * and sets the left margin for subsequent text to the second line
+ * indentation of the current style. - FM
+ */
+void LYEnsureSingleSpace(HTStructured * me)
+{
+ if (!me || !me->text)
+ return;
+
+ if (!HText_LastLineEmpty(me->text, FALSE)) {
+ HText_setLastChar(me->text, ' '); /* absorb white space */
+ HText_appendCharacter(me->text, '\r');
+ } else if (me->List_Nesting_Level >= 0) {
+ HText_NegateLineOne(me->text);
+ }
+ me->in_word = NO;
+ return;
+}
+
+/*
+ * This function resets paragraph alignments for block
+ * elements which do not have a defined style sheet. - FM
+ */
+void LYResetParagraphAlignment(HTStructured * me)
+{
+ if (!me)
+ return;
+
+ 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;
+ }
+ return;
+}
+
+/*
+ * This example function checks whether the given anchor has
+ * an address with a file scheme, and if so, loads it into the
+ * the SGML parser's context->url element, which was passed as
+ * the second argument. The handle_comment() calling function in
+ * SGML.c then calls LYDoCSI() in LYUtils.c to insert HTML markup
+ * into the corresponding stream, homologously to an SSI by an
+ * HTTP server. - FM
+ *
+ * For functions similar to this but which depend on details of
+ * the HTML handler's internal data, the calling interface should
+ * be changed, and functions in SGML.c would have to make sure not
+ * to call such functions inappropriately (e.g., calling a function
+ * specific to the Lynx_HTML_Handler when SGML.c output goes to
+ * some other HTStructured object like in HTMLGen.c), or the new
+ * functions could be added to the SGML.h interface.
+ */
+BOOLEAN LYCheckForCSI(HTParentAnchor *anchor,
+ char **url)
+{
+ if (!(anchor && anchor->address))
+ return FALSE;
+
+ if (!isFILE_URL(anchor->address))
+ return FALSE;
+
+ if (!LYisLocalHost(anchor->address))
+ return FALSE;
+
+ StrAllocCopy(*url, anchor->address);
+ return TRUE;
+}
+
+/*
+ * This function is called from the SGML parser to look at comments
+ * and see whether we should collect some info from them. Currently
+ * it only looks for comments with Message-Id and Subject info, in the
+ * exact form generated by MHonArc for archived mailing list. If found,
+ * the info is stored in the document's HTParentAnchor. It can later be
+ * used for generating a mail response.
+ *
+ * We are extra picky here because there isn't any official definition
+ * for these kinds of comments - we might (and still can) misinterpret
+ * arbitrary comments as something they aren't.
+ *
+ * If something doesn't look right, for example invalid characters, the
+ * strings are not stored. Mail responses will use something else as
+ * the subject, probably the document URL, and will not have an
+ * In-Reply-To header.
+ *
+ * All this is a hack - to do this the right way, mailing list archivers
+ * would have to agree on some better mechanism to make this kind of info
+ * from original mail headers available, for example using LINK. - kw
+ */
+BOOLEAN LYCommentHacks(HTParentAnchor *anchor,
+ const char *comment)
+{
+ const char *cp;
+ size_t len;
+
+ if (comment == NULL)
+ return FALSE;
+
+ if (!(anchor && anchor->address))
+ return FALSE;
+
+ if (StrNCmp(comment, "!--X-Message-Id: ", 17) == 0) {
+ char *messageid = NULL;
+ char *p;
+
+ for (cp = comment + 17; *cp; cp++) {
+ if (UCH(*cp) >= 127 || !isgraph(UCH(*cp))) {
+ break;
+ }
+ }
+ if (strcmp(cp, " --")) {
+ return FALSE;
+ }
+ cp = comment + 17;
+ StrAllocCopy(messageid, cp);
+ /* This should be ok - message-id should only contain 7-bit ASCII */
+ if (!LYUCTranslateHTMLString(&messageid, 0, 0, NO, NO, YES, st_URL))
+ return FALSE;
+ for (p = messageid; *p; p++) {
+ if (UCH(*p) >= 127 || !isgraph(UCH(*p))) {
+ break;
+ }
+ }
+ if (strcmp(p, " --")) {
+ FREE(messageid);
+ return FALSE;
+ }
+ if ((p = StrChr(messageid, '@')) == NULL || p[1] == '\0') {
+ FREE(messageid);
+ return FALSE;
+ }
+ p = messageid;
+ if ((len = strlen(p)) >= 8 && !strcmp(&p[len - 3], " --")) {
+ p[len - 3] = '\0';
+ } else {
+ FREE(messageid);
+ return FALSE;
+ }
+ if (HTAnchor_setMessageID(anchor, messageid)) {
+ FREE(messageid);
+ return TRUE;
+ } else {
+ FREE(messageid);
+ return FALSE;
+ }
+ }
+ if (StrNCmp(comment, "!--X-Subject: ", 14) == 0) {
+ char *subject = NULL;
+ char *p;
+
+ for (cp = comment + 14; *cp; cp++) {
+ if (UCH(*cp) >= 127 || !isprint(UCH(*cp))) {
+ return FALSE;
+ }
+ }
+ cp = comment + 14;
+ StrAllocCopy(subject, cp);
+ /* @@@
+ * This may not be the right thing for the subject - but mail
+ * subjects shouldn't contain 8-bit characters in raw form anyway.
+ * We have to unescape character entities, since that's what MHonArc
+ * seems to generate. But if after that there are 8-bit characters
+ * the string is rejected. We would probably not know correctly
+ * what charset to assume anyway - the mail sender's can differ from
+ * the archive's. And the code for sending mail cannot deal well
+ * with 8-bit characters - we should not put them in the Subject
+ * header in raw form, but don't have MIME encoding implemented.
+ * Someone may want to do more about this... - kw
+ */
+ if (!LYUCTranslateHTMLString(&subject, 0, 0, NO, YES, NO, st_HTML))
+ return FALSE;
+ for (p = subject; *p; p++) {
+ if (UCH(*p) >= 127 || !isprint(UCH(*p))) {
+ FREE(subject);
+ return FALSE;
+ }
+ }
+ p = subject;
+ if ((len = strlen(p)) >= 4 && !strcmp(&p[len - 3], " --")) {
+ p[len - 3] = '\0';
+ } else {
+ FREE(subject);
+ return FALSE;
+ }
+ if (HTAnchor_setSubject(anchor, subject)) {
+ FREE(subject);
+ return TRUE;
+ } else {
+ FREE(subject);
+ return FALSE;
+ }
+ }
+
+ return FALSE;
+}
+
+ /*
+ * Create the Title with any left-angle-brackets converted to &lt; entities
+ * and any ampersands converted to &amp; entities. - FM
+ *
+ * Convert 8-bit letters to &#xUUUU to avoid dependencies from display
+ * character set which may need changing. Do NOT convert any 8-bit chars
+ * if we have CJK display. - LP
+ */
+void LYformTitle(char **dst,
+ const char *src)
+{
+ if (HTCJK == JAPANESE) {
+ char *tmp_buffer = NULL;
+
+ if ((tmp_buffer = (char *) malloc(strlen(src) + 1)) == 0)
+ outofmem(__FILE__, "LYformTitle");
+
+ switch (kanji_code) { /* 1997/11/22 (Sat) 09:28:00 */
+ case EUC:
+ TO_EUC((const unsigned char *) src, (unsigned char *) tmp_buffer);
+ break;
+ case SJIS:
+ TO_SJIS((const unsigned char *) src, (unsigned char *) tmp_buffer);
+ break;
+ default:
+ CTRACE((tfp, "\nLYformTitle: kanji_code is an unexpected value."));
+ strcpy(tmp_buffer, src);
+ break;
+ }
+ StrAllocCopy(*dst, tmp_buffer);
+ FREE(tmp_buffer);
+ } else {
+ StrAllocCopy(*dst, src);
+ }
+}
diff --git a/src/LYCharUtils.h b/src/LYCharUtils.h
new file mode 100644
index 0000000..2b9ca88
--- /dev/null
+++ b/src/LYCharUtils.h
@@ -0,0 +1,109 @@
+/*
+ * $LynxId: LYCharUtils.h,v 1.28 2012/02/10 18:36:39 tom Exp $
+ */
+#ifndef LYCHARUTILS_H
+#define LYCHARUTILS_H
+
+#ifndef HTUTILS_H
+#include <HTUtils.h>
+#endif /* HTUTILS_H */
+
+#ifndef HTSTREAM_H
+#include <HTStream.h>
+#endif /* HTSTREAM_H */
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+#define CHECK_ID(code) LYCheckForID(me, present, value, (int)code)
+ typedef enum {
+ st_HTML = 0, /* attributes and content found in HTML, probably meant for display */
+ st_URL, /* URLs, fragments, NAME and ID */
+ st_other
+ } CharUtil_st;
+
+ extern char **LYUCFullyTranslateString(char **str,
+ int cs_from,
+ int cs_to,
+ int do_ent,
+ int use_lynx_specials,
+ int plain_space,
+ int hidden,
+ int Back,
+ CharUtil_st stype);
+ extern BOOL LYUCTranslateHTMLString(char **str,
+ int cs_from,
+ int cs_to,
+ int use_lynx_specials,
+ int plain_space,
+ int hidden,
+ CharUtil_st stype);
+ extern BOOL LYUCTranslateBackFormData(char **str,
+ int cs_from,
+ int cs_to,
+ int plain_space);
+ extern void LYEntify(char **str,
+ int isTITLE);
+ extern const char *LYEntifyTitle(char **target, const char *source);
+ extern const char *LYEntifyValue(char **target, const char *source);
+ extern void LYTrimHead(char *str);
+ extern void LYTrimTail(char *str);
+ extern char *LYFindEndOfComment(char *str);
+ extern void LYFillLocalFileURL(char **href,
+ const char *base);
+ extern void LYAddMETAcharsetToFD(FILE *fd,
+ int disp_chndl);
+ extern void LYAddMETAcharsetToStream(HTStream *target,
+ int disp_chndl);
+ extern void LYformTitle(char **dst,
+ const char *src);
+ extern char *LYParseTagParam(char *from,
+ const char *name);
+ extern void LYParseRefreshURL(char *content,
+ char **p_seconds,
+ char **p_address);
+
+#ifdef Lynx_HTML_Handler
+ extern int OL_CONTINUE; /* flag for whether CONTINUE is set */
+ extern int OL_VOID; /* flag for whether a count is set */
+ extern void LYZero_OL_Counter(HTStructured * me);
+ extern char *LYUppercaseA_OL_String(int seqnum);
+ extern char *LYLowercaseA_OL_String(int seqnum);
+ extern char *LYUppercaseI_OL_String(int seqnum);
+ extern char *LYLowercaseI_OL_String(int seqnum);
+ extern void LYGetChartransInfo(HTStructured * me);
+ extern void LYHandleMETA(HTStructured * me, const BOOL *present,
+ STRING2PTR value,
+ char **include);
+ extern void LYHandlePlike(HTStructured * me, const BOOL *present,
+ STRING2PTR value,
+ char **include,
+ int align_idx,
+ int start);
+ extern void LYHandleSELECT(HTStructured * me, const BOOL *present,
+ STRING2PTR value,
+ char **include,
+ int start);
+ extern int LYLegitimizeHREF(HTStructured * me, char **href,
+ int force_slash,
+ int strip_dots);
+ extern void LYCheckForContentBase(HTStructured * me);
+ extern void LYCheckForID(HTStructured * me, const BOOL *present,
+ STRING2PTR value,
+ int attribute);
+ extern void LYHandleID(HTStructured * me, const char *id);
+ extern BOOLEAN LYoverride_default_alignment(HTStructured * me);
+ extern void LYEnsureDoubleSpace(HTStructured * me);
+ extern void LYEnsureSingleSpace(HTStructured * me);
+ extern void LYResetParagraphAlignment(HTStructured * me);
+ extern BOOLEAN LYCheckForCSI(HTParentAnchor *anchor,
+ char **url);
+
+#endif /* Lynx_HTML_Handler */
+
+#define LYUCTranslateBackHeaderText LYUCTranslateBackFormData
+
+#ifdef __cplusplus
+}
+#endif
+#endif /* LYCHARUTILS_H */
diff --git a/src/LYCharVals.h b/src/LYCharVals.h
new file mode 100644
index 0000000..43fac15
--- /dev/null
+++ b/src/LYCharVals.h
@@ -0,0 +1,34 @@
+#ifndef LYCHARVALS_H
+#define LYCHARVALS_H 1
+
+#ifndef HTUTILS_H
+#include <HTUtils.h>
+#endif
+
+/*
+ * Use integer values for character constants rather than '\octal' form, since
+ * not all compilers agree that those will fit in a character, even when cast.
+ */
+#ifndef CH_ESC
+#ifdef EBCDIC
+#define CH_DEL 0x07
+#define CH_ESC 0x27
+#define CH_ESC_PAR 0x27
+#define CH_HICTL 0x3f
+#define CH_NBSP 0x41
+#define CH_SHY 0xca
+#define LYCharINTERRUPT1 0x03 /* Control-C */
+#define LYCharINTERRUPT2 0x2f /* Control-G */
+#else /* EBCDIC */
+#define CH_ESC 0033
+#define CH_DEL 0177
+#define CH_ESC_PAR 0233
+#define CH_HICTL 0237
+#define CH_NBSP 0240
+#define CH_SHY 0255
+#define LYCharINTERRUPT1 0003 /* Control-C */
+#define LYCharINTERRUPT2 0007 /* Control-G */
+#endif /* EBCDIC */
+#endif /* CH_ESC */
+
+#endif /* LYCHARVALS_H */
diff --git a/src/LYClean.c b/src/LYClean.c
new file mode 100644
index 0000000..3679d87
--- /dev/null
+++ b/src/LYClean.c
@@ -0,0 +1,203 @@
+/* $LynxId: LYClean.c,v 1.40 2013/10/10 23:47:25 tom Exp $ */
+#include <HTUtils.h>
+#include <LYCurses.h>
+#include <LYUtils.h>
+#include <LYSignal.h>
+#include <LYClean.h>
+#include <LYMainLoop.h>
+#include <LYGlobalDefs.h>
+#include <LYTraversal.h>
+#include <LYHistory.h>
+#include <LYCookie.h>
+#include <LYSession.h>
+#include <UCAuto.h>
+#include <HTAlert.h>
+
+#include <LYexit.h>
+#include <LYLeaks.h>
+
+#ifdef DJGPP
+extern void sig_handler_watt(int);
+#endif /* DJGPP */
+
+#ifdef VMS
+BOOLEAN HadVMSInterrupt = FALSE;
+#endif /* VMS */
+
+/*
+ * Interrupt handler. Stop curses and exit gracefully.
+ */
+void cleanup_sig(int sig)
+{
+#ifdef IGNORE_CTRL_C
+ if (sig == SIGINT) {
+ /*
+ * Need to rearm the signal.
+ */
+#ifdef DJGPP
+ if (wathndlcbrk) {
+ sig_handler_watt(sig); /* Use WATT-32 signal handler */
+ }
+#endif /* DJGPP */
+ signal(SIGINT, cleanup_sig);
+ sigint = TRUE;
+#ifdef DJGPP
+ _eth_release();
+ _eth_init();
+#endif /* DJGPP */
+ return;
+ }
+#endif /* IGNORE_CTRL_C */
+
+#ifdef VMS
+ if (!dump_output_immediately) {
+
+ /*
+ * Reassert the AST.
+ */
+ (void) signal(SIGINT, cleanup_sig);
+ if (LYCursesON) {
+ lynx_force_repaint(); /* wipe away the "cancel" message */
+ LYrefresh();
+
+ /*
+ * Ask if exit is intended.
+ */
+ if (LYQuitDefaultYes == TRUE) {
+ int Dft = ((LYQuitDefaultYes == TRUE) ? YES : NO);
+ int c = HTConfirmDefault(REALLY_EXIT, Dft);
+
+ HadVMSInterrupt = TRUE;
+ if (c != Dft) {
+ return;
+ }
+ }
+ } else {
+ return;
+ }
+ }
+#endif /* VMS */
+
+ /*
+ * Ignore signals from terminal.
+ */
+#ifndef NOSIGHUP
+ (void) signal(SIGHUP, SIG_IGN);
+#endif /* NOSIGHUP */
+
+#ifdef VMS
+ /*
+ * Use ttclose() from cleanup() for VMS if not dumping.
+ */
+ if (dump_output_immediately)
+ (void) signal(SIGTERM, SIG_IGN);
+#else /* Unix: */
+ (void) signal(SIGINT, SIG_IGN);
+ (void) signal(SIGTERM, SIG_IGN);
+#endif /* VMS */
+
+ if (traversal)
+ dump_traversal_history();
+
+#ifndef NOSIGHUP
+ if (sig != SIGHUP) {
+#endif /* NOSIGHUP */
+
+ if (!dump_output_immediately) {
+ /*
+ * cleanup() also calls cleanup_files().
+ */
+ cleanup();
+ }
+ if (sig != 0) {
+ SetOutputMode(O_TEXT);
+ printf("\n\n%s %d\n\n",
+ gettext("Exiting via interrupt:"),
+ sig);
+ fflush(stdout);
+ }
+#ifndef NOSIGHUP
+ } else {
+#ifdef USE_SESSIONS
+ /*
+ * It is useful to save the session if a user closed lynx in a
+ * nonstandard way, such as closing xterm window or in even a crash.
+ */
+ SaveSession();
+#endif /* USE_SESSIONS */
+ cleanup_files();
+ }
+#endif /* NOSIGHUP */
+ if (sig != 0) {
+ exit_immediately(EXIT_SUCCESS);
+ } else {
+ reset_signals();
+ }
+}
+
+/*
+ * Called by interrupt handler or at quit-time, this erases the temporary files
+ * that lynx created.
+ */
+void cleanup_files(void)
+{
+ LYCleanupTemp();
+ FREE(lynx_temp_space);
+}
+
+void cleanup(void)
+{
+ /*
+ * Ignore signals from terminal.
+ */
+#ifndef NOSIGHUP
+ (void) signal(SIGHUP, SIG_IGN);
+#endif /* NOSIGHUP */
+#ifndef VMS /* use ttclose() from cleanup() for VMS */
+ (void) signal(SIGINT, SIG_IGN);
+#endif /* !VMS */
+ (void) signal(SIGTERM, SIG_IGN);
+
+ if (LYCursesON) {
+ LYParkCursor();
+ lynx_stop_all_colors();
+ LYrefresh();
+
+ stop_curses();
+ }
+#ifdef EXP_CHARTRANS_AUTOSWITCH
+ /*
+ * Currently implemented only for LINUX: Restore original font.
+ */ UCChangeTerminalCodepage(-1, (LYUCcharset *) 0);
+#endif /* EXP_CHARTRANS_AUTOSWITCH */
+
+#ifdef USE_PERSISTENT_COOKIES
+ /*
+ * This can go right here for now. We need to work up a better place
+ * to save cookies for the next release, preferably whenever a new
+ * persistent cookie is received or used. Some sort of protocol to
+ * handle two processes writing to the cookie file needs to be worked
+ * out as well.
+ */
+ if (persistent_cookies)
+ LYStoreCookies(LYCookieSaveFile);
+#endif
+#ifdef USE_SESSIONS
+ SaveSession();
+#endif /* USE_SESSIONS */
+
+ cleanup_files();
+#ifdef VMS
+ ttclose();
+ DidCleanup = TRUE;
+#endif /* VMS */
+
+ /*
+ * If we're looking at memory leaks, hang onto the trace file, since there
+ * is no memory freed in this function, and it is a nuisance to not be able
+ * to trace the cleanup activity -TD
+ */
+#ifndef LY_FIND_LEAKS
+ LYCloseTracelog();
+#endif
+}
diff --git a/src/LYClean.h b/src/LYClean.h
new file mode 100644
index 0000000..d7f54be
--- /dev/null
+++ b/src/LYClean.h
@@ -0,0 +1,25 @@
+#ifndef LYCLEAN_H
+#define LYCLEAN_H
+
+#ifndef HTUTILS_H
+#include <HTUtils.h>
+#endif
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+#ifdef VMS
+ extern BOOLEAN DidCleanup;
+ extern BOOLEAN HadVMSInterrupt;
+#endif
+
+ extern void cleanup_sig(int sig);
+ extern void cleanup(void);
+ extern void cleanup_files(void);
+ extern void set_alarm(int sig);
+ extern void reset_alarm(void);
+
+#ifdef __cplusplus
+}
+#endif
+#endif /* LYCLEAN_H */
diff --git a/src/LYCookie.c b/src/LYCookie.c
new file mode 100644
index 0000000..6755829
--- /dev/null
+++ b/src/LYCookie.c
@@ -0,0 +1,2898 @@
+/*
+ * $LynxId: LYCookie.c,v 1.147 2019/01/26 00:50:13 tom Exp $
+ *
+ * Lynx Cookie Support LYCookie.c
+ * ===================
+ *
+ * Author: AMK A.M. Kuchling (amk@magnet.com) 12/25/96
+ *
+ * Incorporated with mods by FM 01/16/97
+ *
+ * Based on:
+ * http://www.ics.uci.edu/pub/ietf/http/draft-ietf-http-state-mgmt-05.txt
+ *
+ * Updated for:
+ * http://www.ics.uci.edu/pub/ietf/http/draft-ietf-http-state-man-mec-02.txt
+ * - FM 1997-07-09
+ *
+ * Updated for:
+ * ftp://ds.internic.net/internet-drafts/draft-ietf-http-state-man-mec-03.txt
+ * - FM 1997-08-02
+ *
+ * Partially checked against:
+ * http://www.ietf.org/internet-drafts/draft-ietf-http-state-man-mec-10.txt
+ * - kw 1998-12-11
+ *
+ * Modified to follow RFC-6265 regarding leading dot of Domain, and
+ * matching of hostname vs domain (2011/06/10 -TD)
+ *
+ * Modified to address differences between RFC-6262 versus RFC-2109 and
+ * RFC-2965 by making the older behavior optional (2019/01/25 -TD)
+ *
+ * FM's TO DO: (roughly in order of decreasing priority)
+ * Persistent cookies are still experimental. Presently cookies
+ lose many pieces of information that distinguish
+ version 1 from version 0 cookies. There is no easy way around
+ that with the current cookie file format. Ports are currently
+ not stored persistently at all which is clearly wrong.
+ * We currently don't do anything special for unverifiable
+ transactions to third-party hosts.
+ * We currently don't use effective host names or check for
+ Domain=.local.
+ * Hex escaping isn't considered at all. Any semi-colons, commas,
+ or spaces actually in cookie names or values (i.e., not serving
+ as punctuation for the overall Set-Cookie value) should be hex
+ escaped if not quoted, but presumably the server is expecting
+ them to be hex escaped in our Cookie request header as well, so
+ in theory we need not unescape them. We'll see how this works
+ out in practice.
+ * The prompt should show more information about the cookie being
+ set in Novice mode.
+ * The truncation heuristic in HTConfirmCookie should probably be
+ smarter, smart enough to leave a really short name/value string
+ alone.
+ * We protect against denial-of-service attacks (see section 6.3.1 of the
+ draft) by limiting the number of cookies from a domain, limiting the
+ total number of cookies, and limiting the number of bytes from a
+ processed cookie, but we count on the normal garbage collections to
+ bring us back down under the limits, rather than actively removing
+ cookies and/or domains based on age or frequency of use.
+ * If a cookie has the secure flag set, we presently treat only SSL
+ connections as secure. This may need to be expanded for other
+ secure communication protocols that become standardized.
+*/
+
+#include <HTUtils.h>
+#include <HTAccess.h>
+#include <HTParse.h>
+#include <HTAlert.h>
+#include <LYCurses.h>
+#include <LYUtils.h>
+#include <LYCharUtils.h>
+#include <LYClean.h>
+#include <LYGlobalDefs.h>
+#include <LYEdit.h>
+#include <LYStrings.h>
+#include <GridText.h>
+#include <LYCookie.h>
+
+#include <LYLeaks.h>
+
+/* default for new domains, one of the invcheck_behaviour_t values: */
+#define DEFAULT_INVCHECK_BV INVCHECK_QUERY
+
+#define CTrace(p) CTRACE2(TRACE_COOKIES, p)
+
+#define LeadingDot(s) ((s)[0] == '.')
+#define SkipLeadingDot(s) (LeadingDot(s) ? ((s) + 1) : (s))
+
+#define AssumeCookieVersion(p) \
+ if (USE_RFC_2965 && (p)->version < 1) { \
+ (p)->version = 1; \
+ }
+
+/*
+ * The first level of the cookie list is a list indexed by the domain
+ * string; cookies with the same domain will be placed in the same
+ * list. Thus, finding the cookies that apply to a given URL is a
+ * two-level scan; first we check each domain to see if it applies,
+ * and if so, then we check the paths of all the cookies on that
+ * list. We keep a running total of cookies as we add or delete
+ * them
+ */
+static HTList *domain_list = NULL;
+static HTList *cookie_list = NULL;
+static int total_cookies = 0;
+
+struct _cookie {
+ char *lynxID; /* Lynx cookie identifier */
+ char *name; /* Name of this cookie */
+ char *value; /* Value of this cookie */
+ int version; /* Cookie protocol version (=1) */
+ char *comment; /* Comment to show to user */
+ char *commentURL; /* URL for comment to show to user */
+ char *domain; /* Domain for which this cookie is valid */
+ char *ddomain; /* Domain without leading "." */
+ int port; /* Server port from which this cookie was given (usu. 80) */
+ char *PortList; /* List of ports for which cookie can be sent */
+ char *path; /* Path prefix for which this cookie is valid */
+ int pathlen; /* Length of the path */
+ int flags; /* Various flags */
+ time_t expires; /* The time when this cookie expires */
+ BOOL quoted; /* Was a value quoted in the Set-Cookie header? */
+};
+typedef struct _cookie cookie;
+
+#define COOKIE_FLAG_SECURE 1 /* If set, cookie requires secure links */
+#define COOKIE_FLAG_DISCARD 2 /* If set, expire at end of session */
+#define COOKIE_FLAG_EXPIRES_SET 4 /* If set, an expiry date was set */
+#define COOKIE_FLAG_DOMAIN_SET 8 /* If set, an non-default domain was set */
+#define COOKIE_FLAG_PATH_SET 16 /* If set, an non-default path was set */
+#define COOKIE_FLAG_FROM_FILE 32 /* If set, this cookie was persistent */
+
+static void MemAllocCopy(char **dest,
+ const char *start,
+ const char *end)
+{
+ char *temp;
+
+ if (!(start && end) || (end <= start)) {
+ HTSACopy(dest, "");
+ return;
+ }
+
+ temp = typecallocn(char, (unsigned)(end - start) + 1);
+ if (temp == NULL)
+ outofmem(__FILE__, "MemAllocCopy");
+ LYStrNCpy(temp, start, (end - start));
+ HTSACopy(dest, temp);
+ FREE(temp);
+}
+
+static cookie *newCookie(void)
+{
+ cookie *p = typecalloc(cookie);
+
+ if (p == NULL)
+ outofmem(__FILE__, "newCookie");
+
+ HTSprintf0(&(p->lynxID), "%p", (void *) p);
+ p->port = 80;
+ return p;
+}
+
+static void freeCookie(cookie * co)
+{
+ if (co) {
+ FREE(co->lynxID);
+ FREE(co->name);
+ FREE(co->value);
+ FREE(co->comment);
+ FREE(co->commentURL);
+ FREE(co->domain);
+ FREE(co->ddomain);
+ FREE(co->path);
+ FREE(co->PortList);
+ FREE(co);
+ }
+}
+
+static void freeCookies(domain_entry * de)
+{
+ FREE(de->domain);
+ FREE(de->ddomain);
+ HTList_delete(de->cookie_list);
+ de->cookie_list = NULL;
+}
+
+#ifdef LY_FIND_LEAKS
+static void LYCookieJar_free(void)
+{
+ HTList *dl = domain_list;
+ domain_entry *de = NULL;
+ HTList *cl = NULL, *next = NULL;
+ cookie *co = NULL;
+
+ CTrace((tfp, "LYCookieJar_free\n"));
+ while (dl) {
+ if ((de = dl->object) != NULL) {
+ CTrace((tfp, "...LYCookieJar_free domain %s\n", NonNull(de->ddomain)));
+ cl = de->cookie_list;
+ while (cl) {
+ next = cl->next;
+ co = cl->object;
+ if (co) {
+ HTList_removeObject(de->cookie_list, co);
+ freeCookie(co);
+ }
+ cl = next;
+ }
+ freeCookies(de);
+ FREE(dl->object);
+ }
+ dl = dl->next;
+ }
+ cookie_list = NULL;
+ HTList_delete(domain_list);
+ domain_list = NULL;
+}
+#endif /* LY_FIND_LEAKS */
+
+/*
+ * RFC 2109 -
+ * 4.2.2 Set-Cookie Syntax
+ * An explicitly specified domain must always start with a dot.
+ * 4.3.2 Rejecting Cookies
+ * ...rejects a cookie (shall not store its information) if any of the
+ * following is true:
+ * ...
+ * The value for the Domain attribute contains no embedded dots or does not
+ * start with a dot.
+ *
+ * RFC 2965 -
+ * 3.2.2 Set-Cookie2 Syntax
+ * Domain=value
+ * OPTIONAL. The value of the Domain attribute specifies the domain
+ * for which the cookie is valid. If an explicitly specified value
+ * does not start with a dot, the user agent supplies a leading dot.
+ */
+static BOOLEAN has_embedded_dot(const char *value)
+{
+ BOOLEAN leading = NO;
+ BOOLEAN embedded = NO;
+ const char *p;
+
+ for (p = value; *p != '\0'; ++p) {
+ if (*p == '.') {
+ if (p == value) {
+ leading = YES;
+ } else if (p[1] != '\0') {
+ embedded = YES;
+ } else {
+ embedded = NO;
+ }
+ }
+ }
+ return (leading || USE_RFC_2965) && embedded;
+}
+
+/*
+ * RFC 6265 -
+ * 4.1.2.3. The Domain Attribute
+ * (Note that a leading %x2E ("."), if present, is ignored even though that
+ * character is not permitted, but a trailing %x2E ("."), if present, will
+ * cause the user agent to ignore the attribute.)
+ */
+static BOOLEAN has_trailing_dot(const char *value)
+{
+ BOOLEAN result = NO;
+ const char *p;
+
+ for (p = value; *p != '\0'; ++p) {
+ if (*p == '.') {
+ if (p[1] == '\0') {
+ result = YES;
+ break;
+ }
+ }
+ }
+ return result;
+}
+
+/*
+ * Compare a string against a domain as specified in RFC-6265 Section 5.1.3
+ */
+static BOOLEAN domain_matches(const char *value,
+ const char *domain)
+{
+ BOOLEAN result = NO;
+
+ if (isEmpty(value)) {
+ CTrace((tfp, "BUG: comparing empty value in domain_matches\n"));
+ } else if (isEmpty(domain)) {
+ CTrace((tfp, "BUG: comparing empty domain in domain_matches\n"));
+ } else {
+ if (!strcasecomp(value, domain)) {
+ result = YES;
+ } else {
+ int value_len = (int) strlen(value);
+ int suffix_len = (int) strlen(domain);
+ int offset = value_len - suffix_len;
+
+ if (offset > 1
+ && value[offset - 1] == '.'
+ && !strcasecomp(value + offset, domain)) {
+ result = YES;
+ }
+ }
+ }
+ return result;
+}
+
+/*
+ * Compare the current port with a port list as specified in Section 4.3 of:
+ * http://www.ics.uci.edu/pub/ietf/http/draft-ietf-http-state-man-mec-02.txt
+ * - FM
+ */
+static BOOLEAN port_matches(int port,
+ const char *list)
+{
+ const char *number = list;
+
+ if (!(number && isdigit(UCH(*number))))
+ return (FALSE);
+
+ while (*number != '\0') {
+ if (atoi(number) == port) {
+ return (TRUE);
+ }
+ while (isdigit(UCH(*number))) {
+ number++;
+ }
+ while (*number != '\0' && !isdigit(UCH(*number))) {
+ number++;
+ }
+ }
+
+ return (FALSE);
+}
+
+/*
+ * Returns the length of the given path ignoring trailing slashes.
+ */
+static int ignore_trailing_slash(const char *a)
+{
+ int len = (int) strlen(a);
+
+ while (len > 1 && a[len - 1] == '/')
+ --len;
+ return len;
+}
+
+/*
+ * Check if the path 'a' is a prefix of path 'b', ignoring trailing slashes
+ * in either, since they denote an empty component.
+ */
+static BOOL is_prefix(const char *a, const char *b)
+{
+ int len_a = ignore_trailing_slash(a);
+ int len_b = ignore_trailing_slash(b);
+
+ if (len_a > len_b) {
+ return FALSE;
+ } else {
+ if (StrNCmp(a, b, (unsigned) len_a) != 0) {
+ return FALSE;
+ }
+ if (len_a < len_b && (len_a > 1 || a[0] != '/')) {
+ if (b[len_a] != '\0'
+ && b[len_a] != '/') {
+ return FALSE;
+ }
+ }
+ }
+ return TRUE;
+}
+
+/*
+ * Find the domain-entry for the given name.
+ */
+static domain_entry *find_domain_entry(const char *name)
+{
+ HTList *hl;
+ domain_entry *de = NULL;
+ const char *find;
+
+ if (name != 0
+ && *(find = SkipLeadingDot(name)) != '\0') {
+ for (hl = domain_list; hl != NULL; hl = hl->next) {
+ de = (domain_entry *) hl->object;
+ if (de != NULL && de->domain != NULL && de->ddomain != NULL) {
+ CTrace((tfp,
+ "...test_domain_entry(%s) ->(%s) bv:%u, invcheck_bv:%u\n",
+ find,
+ NonNull(de->ddomain),
+ de->bv,
+ de->invcheck_bv));
+ if (!strcasecomp(find, de->ddomain)) {
+ break;
+ }
+ }
+ de = NULL;
+ }
+ }
+ CTrace((tfp, "find_domain_entry(%s) bv:%d, invcheck_bv:%d\n",
+ name,
+ de ? (int) de->bv : -1,
+ de ? (int) de->invcheck_bv : -1));
+ return de;
+}
+
+static void SetCookieDomain(cookie * co, const char *domain)
+{
+ StrAllocCopy(co->ddomain, SkipLeadingDot(domain));
+ CTrace((tfp, "SetCookieDomain(%s)\n", co->ddomain));
+}
+
+/*
+ * Store a cookie somewhere in the domain list. - AK & FM
+ */
+static void store_cookie(cookie * co, const char *hostname,
+ const char *path)
+{
+ HTList *hl, *next;
+ cookie *c2;
+ time_t now = time(NULL);
+ int pos;
+ const char *ptr;
+ domain_entry *de = NULL;
+ BOOL Replacement = FALSE;
+ int invprompt_reasons = 0; /* what is wrong with this cookie - kw */
+
+#define FAILS_COND1 0x01
+#define FAILS_COND4 0x02
+
+ if (co == NULL)
+ return;
+
+ /*
+ * Ensure that the domain list exists.
+ */
+ if (domain_list == NULL) {
+#ifdef LY_FIND_LEAKS
+ atexit(LYCookieJar_free);
+#endif
+ domain_list = HTList_new();
+ total_cookies = 0;
+ }
+
+ /*
+ * Look through domain_list to see if the cookie's domain is already
+ * listed.
+ */
+ cookie_list = NULL;
+ if ((de = find_domain_entry(co->domain)) != NULL)
+ cookie_list = de->cookie_list;
+
+ /*
+ * Apply sanity checks.
+ *
+ * RFC 2109 -
+ * Section 4.3.2, condition 1: The value for the Path attribute is not a
+ * prefix of the request-URI.
+ *
+ * If cookie checking for this domain is set to INVCHECK_LOOSE, then we
+ * want to bypass this check. The user should be queried if set to
+ * INVCHECK_QUERY.
+ *
+ * RFC 6265 -
+ * Section 4.1.2.4 describes Path, but omits any mention of the user agent
+ * rejecting a cookie because of Path. Instead, it deals only with the
+ * cases where a cookie returned by the user agent would be valid, based on
+ * Path. In section 8.6, RFC 6265 presents an example which would not have
+ * been valid with RFC 2109 to claim that the Path attribute is unreliable
+ * from the standpoint of the user agent.
+ *
+ * RFC 6265 does not go into any detail regarding its differences from the
+ * older RFCs 2109 / 2965. The relevant text covering all of these changes
+ * is just this (no case studies are cited):
+ * User agents MUST implement the more liberal processing rules defined in
+ * Section 5, in order to maximize interoperability with existing servers
+ * that do not conform to the well-behaved profile defined in Section 4.
+ */
+ if (!USE_RFC_6265 && !is_prefix(co->path, path)) {
+ invcheck_behaviour_t invcheck_bv = (de ? de->invcheck_bv
+ : DEFAULT_INVCHECK_BV);
+
+ switch (invcheck_bv) {
+ case INVCHECK_LOOSE:
+ break; /* continue as if nothing were wrong */
+
+ case INVCHECK_QUERY:
+ /* will prompt later if we get that far */
+ invprompt_reasons |= FAILS_COND1;
+ break;
+
+ case INVCHECK_STRICT:
+ CTrace((tfp,
+ "store_cookie: Rejecting because '%s' is not a prefix of '%s'.\n",
+ co->path, path));
+ freeCookie(co);
+ return;
+ }
+ }
+
+ /*
+ * The next 4 conditions do NOT apply if the domain is still
+ * the default of request-host. (domains - case insensitive).
+ */
+ if (strcasecomp(co->domain, hostname) != 0) {
+ /*
+ * The hostname does not contain a dot.
+ */
+ if (StrChr(hostname, '.') == NULL) {
+ CTrace((tfp, "store_cookie: Rejecting because '%s' has no dot.\n",
+ hostname));
+ freeCookie(co);
+ return;
+ }
+
+ /*
+ * RFC 2109 -
+ * Section 4.3.2, condition 2: The value for the Domain attribute
+ * contains no embedded dots or does not start with a dot. (A dot is
+ * embedded if it's neither the first nor last character.) Note that we
+ * added a lead dot ourselves if a domain attribute value otherwise
+ * qualified. - FM
+ *
+ * RFC 6265 -
+ * If the first character of the attribute-value string is %x2E ("."):
+ *
+ * Let cookie-domain be the attribute-value without the leading %x2E
+ * (".") character.
+ *
+ * Otherwise:
+ *
+ * Let cookie-domain be the entire attribute-value.
+ *
+ * Convert the cookie-domain to lower case.
+ */
+ SetCookieDomain(co, co->domain);
+ if (isEmpty(co->ddomain)) {
+ CTrace((tfp, "store_cookie: Rejecting domain '%s'.\n", co->ddomain));
+ freeCookie(co);
+ return;
+ }
+ if (!USE_RFC_6265) {
+ if (!has_embedded_dot(co->ddomain)) {
+ CTrace((tfp, "store_cookie: Rejecting domain '%s'.\n", co->ddomain));
+ freeCookie(co);
+ return;
+ }
+ } else {
+ if (has_trailing_dot(co->ddomain)) {
+ CTrace((tfp, "store_cookie: Rejecting domain '%s'.\n", co->ddomain));
+ freeCookie(co);
+ return;
+ }
+ }
+
+ /*
+ * RFC 2109 -
+ * Section 4.3.2, condition 3: The value for the request-host does not
+ * domain-match the Domain attribute.
+ *
+ * RFC 6265 -
+ * Section 4.1.2.3,
+ * The user agent will reject cookies unless the Domain attribute
+ * specifies a scope for the cookie that would include the origin
+ * server.
+ */
+ if (!domain_matches(hostname, co->ddomain)) {
+ CTrace((tfp,
+ "store_cookie: Rejecting domain '%s' for host '%s'.\n",
+ co->ddomain, hostname));
+ freeCookie(co);
+ return;
+ }
+
+ /*
+ * RFC 2109 -
+ * Section 4.3.2, condition 4: The request-host is an HDN (not IP
+ * address) and has the form HD, where D is the value of the Domain
+ * attribute, and H is a string that contains one or more dots.
+ *
+ * If cookie checking for this domain is set to INVCHECK_LOOSE, then we
+ * want to bypass this check. The user should be queried if set to
+ * INVCHECK_QUERY.
+ *
+ * RFC 6265 -
+ * There is nothing comparable in RFC 6265, since this check appears to
+ * have reflected assumptions about how domain names were constructed
+ * when RFC 2109 was written. Section 5.1.3. (Domain Matching) is
+ * loosely related to these assumptions.
+ */
+ if (!USE_RFC_6265) {
+ ptr = ((hostname + strlen(hostname)) - strlen(co->domain));
+ if (StrChr(hostname, '.') < ptr) {
+ invcheck_behaviour_t invcheck_bv = (de ? de->invcheck_bv
+ : DEFAULT_INVCHECK_BV);
+
+ switch (invcheck_bv) {
+ case INVCHECK_LOOSE:
+ break; /* continue as if nothing were wrong */
+
+ case INVCHECK_QUERY:
+ invprompt_reasons |= FAILS_COND4;
+ break; /* will prompt later if we get that far */
+
+ case INVCHECK_STRICT:
+ CTrace((tfp,
+ "store_cookie: Rejecting because '%s' is not a prefix of '%s'.\n",
+ co->path, path));
+ freeCookie(co);
+ return;
+ }
+ }
+ }
+ }
+
+ /*
+ * If we found reasons for issuing an invalid cookie confirmation prompt,
+ * do that now. Rejection by the user here is the last chance to
+ * completely ignore this cookie; after it passes this hurdle, it may at
+ * least supersede a previous cookie (even if it finally gets rejected). -
+ * kw
+ */
+ if (invprompt_reasons) {
+ char *msg = 0;
+
+ if (invprompt_reasons & FAILS_COND4) {
+ HTSprintf0(&msg,
+ INVALID_COOKIE_DOMAIN_CONFIRMATION,
+ co->ddomain,
+ hostname);
+ if (!HTForcedPrompt(cookie_noprompt, msg, NO)) {
+ CTrace((tfp,
+ "store_cookie: Rejecting domain '%s' for host '%s'.\n",
+ co->ddomain,
+ hostname));
+ freeCookie(co);
+ FREE(msg);
+ return;
+ }
+ }
+ if (invprompt_reasons & FAILS_COND1) {
+ HTSprintf0(&msg,
+ INVALID_COOKIE_PATH_CONFIRMATION,
+ co->path, path);
+ if (!HTForcedPrompt(cookie_noprompt, msg, NO)) {
+ CTrace((tfp,
+ "store_cookie: Rejecting because '%s' is not a prefix of '%s'.\n",
+ co->path, path));
+ freeCookie(co);
+ FREE(msg);
+ return;
+ }
+ }
+ FREE(msg);
+ }
+
+ if (de == NULL) {
+ /*
+ * Domain not found; add a new entry for this domain.
+ */
+ de = typecalloc(domain_entry);
+ if (de == NULL)
+ outofmem(__FILE__, "store_cookie");
+
+ de->bv = QUERY_USER;
+ de->invcheck_bv = DEFAULT_INVCHECK_BV; /* should this go here? */
+ cookie_list = de->cookie_list = HTList_new();
+ StrAllocCopy(de->domain, co->domain);
+ StrAllocCopy(de->ddomain, co->ddomain);
+ HTList_appendObject(domain_list, de);
+ }
+
+ /*
+ * Loop over the cookie list, deleting expired and matching cookies.
+ */
+ hl = cookie_list;
+ pos = 0;
+ while (hl) {
+ c2 = (cookie *) hl->object;
+ next = hl->next;
+ /*
+ * Check if this cookie has expired.
+ */
+ if ((c2 != NULL) &&
+ (c2->flags & COOKIE_FLAG_EXPIRES_SET) &&
+ c2->expires <= now) {
+ HTList_removeObject(cookie_list, c2);
+ freeCookie(c2);
+ c2 = NULL;
+ total_cookies--;
+
+ /*
+ * Check if this cookie matches the one we're inserting.
+ */
+ } else if ((c2) &&
+ !strcasecomp(co->ddomain, c2->ddomain) &&
+ !strcmp(co->path, c2->path) &&
+ !strcmp(co->name, c2->name)) {
+ HTList_removeObject(cookie_list, c2);
+ freeCookie(c2);
+ c2 = NULL;
+ total_cookies--;
+ Replacement = TRUE;
+
+ } else if ((c2) && (c2->pathlen) >= (co->pathlen)) {
+ /*
+ * This comparison determines the (tentative) position of the new
+ * cookie in the list such that it comes before existing cookies
+ * with a less specific path, but after existing cookies of equal
+ * (or greater) path length. Thus it should normally preserve the
+ * order of new cookies with the same path as they are received,
+ * although this is not required.
+ *
+ * From RFC 2109 4.3.4:
+ *
+ * If multiple cookies satisfy the criteria above, they are ordered
+ * in the Cookie header such that those with more specific Path
+ * attributes precede those with less specific. Ordering with
+ * respect to other attributes (e.g., Domain) is unspecified.
+ */
+ pos++;
+ }
+ hl = next;
+ }
+
+ /*
+ * Don't bother to add the cookie if it's already expired.
+ */
+ if ((co->flags & COOKIE_FLAG_EXPIRES_SET) && co->expires <= now) {
+ freeCookie(co);
+ co = NULL;
+
+ /*
+ * Don't add the cookie if we're over the domain's limit. - FM
+ */
+ } else if (HTList_count(cookie_list) > max_cookies_domain) {
+ CTrace((tfp,
+ "store_cookie: Domain's cookie limit exceeded! Rejecting cookie.\n"));
+ freeCookie(co);
+ co = NULL;
+
+ /*
+ * Don't add the cookie if we're over the total cookie limit. - FM
+ */
+ } else if (total_cookies > max_cookies_global) {
+ CTrace((tfp,
+ "store_cookie: Total cookie limit exceeded! Rejecting cookie.\n"));
+ freeCookie(co);
+ co = NULL;
+
+ /*
+ * Don't add the cookie if the value is NULL. - BJP
+ */
+ /*
+ * Presence of value is now needed (indicated normally by '='),
+ * but it can now be an empty string.
+ * - kw 1999-06-24
+ */
+ } else if (co->value == NULL) { /* should not happen - kw */
+ CTrace((tfp, "store_cookie: Value is NULL! Not storing cookie.\n"));
+ freeCookie(co);
+ co = NULL;
+
+ /*
+ * If it's a replacement for a cookie that had not expired, and never
+ * allow has not been set, add it again without confirmation. - FM
+ */
+ } else if ((Replacement == TRUE) && de->bv != REJECT_ALWAYS) {
+ HTList_insertObjectAt(cookie_list, co, pos);
+ total_cookies++;
+
+ /*
+ * Get confirmation if we need it, and add cookie if confirmed or
+ * 'allow' is set to always. - FM
+ *
+ * Cookies read from file are accepted without confirmation prompting.
+ * (Prompting may actually not be possible if LYLoadCookies is called
+ * before curses is setup.) Maybe this should instead depend on
+ * LYSetCookies and/or LYCookieAcceptDomains and/or
+ * LYCookieRejectDomains and/or LYAcceptAllCookies and/or some other
+ * settings. -kw
+ */
+ } else if ((co->flags & COOKIE_FLAG_FROM_FILE)
+ || HTConfirmCookie(de, hostname, co->name, co->value)) {
+ /*
+ * Insert the new cookie so that more specific paths (longer
+ * pathlen) come first in the list. - kw
+ */
+ HTList_insertObjectAt(cookie_list, co, pos);
+ total_cookies++;
+ } else {
+ freeCookie(co);
+ co = NULL;
+ }
+}
+
+/*
+ * Scan a domain's cookie_list for any cookies we should
+ * include in a Cookie: request header. - AK & FM
+ */
+static char *scan_cookie_sublist(char *hostname,
+ char *path,
+ int port,
+ HTList *sublist,
+ char *header,
+ int secure)
+{
+ HTList *hl, *next;
+ cookie *co;
+ time_t now = time(NULL);
+ char crlftab[8];
+
+ sprintf(crlftab, "%c%c%c", CR, LF, '\t');
+ for (hl = sublist; hl != NULL; hl = next) {
+ next = hl->next;
+ co = (cookie *) hl->object;
+
+ if (co == NULL) {
+ continue;
+ }
+
+ /* speed-up host_matches() and limit trace output */
+ if (LYstrstr(hostname, co->ddomain) != NULL) {
+ CTrace((tfp, "Checking cookie %p %s=%s\n",
+ (void *) hl,
+ (co->name ? co->name : "(no name)"),
+ (co->value ? co->value : "(no value)")));
+ CTrace((tfp, "\t%s %s %d %s %s %d%s\n",
+ hostname,
+ (co->ddomain ? co->ddomain : "(no domain)"),
+ domain_matches(hostname, co->ddomain),
+ path, co->path,
+ (co->pathlen > 0)
+ ? !is_prefix(co->path, path)
+ : 0,
+ (co->flags & COOKIE_FLAG_SECURE)
+ ? " secure"
+ : ""));
+ }
+ /*
+ * Check if this cookie has expired, and if so, delete it.
+ */
+ if ((co->flags & COOKIE_FLAG_EXPIRES_SET) &&
+ co->expires <= now) {
+ next = hl->next;
+ HTList_removeObject(sublist, co);
+ freeCookie(co);
+ total_cookies--;
+ if (next)
+ continue;
+ break;
+ }
+
+ /*
+ * Check if we have a unexpired match, and handle if we do.
+ */
+ if (co->domain != 0 &&
+ co->name != 0 &&
+ domain_matches(hostname, co->ddomain) &&
+ (co->pathlen == 0 || is_prefix(co->path, path))) {
+ /*
+ * Skip if the secure flag is set and we don't have a secure
+ * connection. HTTP.c presently treats only SSL connections as
+ * secure. - FM
+ */
+ if ((co->flags & COOKIE_FLAG_SECURE) && secure == FALSE) {
+ continue;
+ }
+
+ /*
+ * Skip if we have a port list and the current port is not listed.
+ * - FM
+ */
+ if (USE_RFC_2965
+ && co->PortList
+ && !port_matches(port, co->PortList)) {
+ continue;
+ }
+
+ /*
+ * Start or append to the request header.
+ */
+ if (header == NULL) {
+ if (co->version > 0) {
+ /*
+ * For Version 1 (or greater) cookies, the version number
+ * goes before the first cookie.
+ */
+ HTSprintf0(&header, "$Version=\"%d\"; ", co->version);
+ }
+ } else {
+ /*
+ * There's already cookie data there, so add a separator
+ * (always use a semi-colon for "backward compatibility"). -
+ * FM
+ */
+ StrAllocCat(header, "; ");
+ /*
+ * Check if we should fold the header. - FM
+ */
+
+ /*
+ * Section 2.2 of RFC1945 says:
+ *
+ * HTTP/1.0 headers may be folded onto multiple lines if each
+ * continuation line begins with a space or horizontal tab.
+ * All linear whitespace, including folding, has the same
+ * semantics as SP. [...] However, folding of header lines is
+ * not expected by some applications, and should not be
+ * generated by HTTP/1.0 applications.
+ *
+ * This code was causing problems. Let's not use it. -BJP
+ */
+
+ /* if (len > 800) { */
+ /* StrAllocCat(header, crlftab); */
+ /* } */
+
+ }
+ /*
+ * Include the cookie name=value pair.
+ */
+ StrAllocCat(header, co->name);
+ StrAllocCat(header, "=");
+ if (co->quoted) {
+ StrAllocCat(header, "\"");
+ }
+ StrAllocCat(header, co->value);
+ if (co->quoted) {
+ StrAllocCat(header, "\"");
+ }
+ /*
+ * For Version 1 (or greater) cookies, add $PATH, $PORT and/or
+ * $DOMAIN attributes for the cookie if they were specified via a
+ * server reply header. - FM
+ */
+ if (co->version > 0) {
+ if (co->path && (co->flags & COOKIE_FLAG_PATH_SET)) {
+ HTSprintf(&header, "; $Path=\"%s\"", co->path);
+ }
+ if (co->PortList && isdigit(UCH(*co->PortList))) {
+ HTSprintf(&header, "; $Port=\"%s\"", co->PortList);
+ }
+ if (co->domain && (co->flags & COOKIE_FLAG_DOMAIN_SET)) {
+ HTSprintf(&header, "; $Domain=\"%s\"", co->domain);
+ }
+ }
+ }
+ }
+
+ return (header);
+}
+
+/*
+ * Presence of value is needed (indicated normally by '=') to start a cookie,
+ * but it can be an empty string. - kw 1999-06-24
+ */
+static char *alloc_attr_value(const char *value_start,
+ const char *value_end)
+{
+ char *value = NULL;
+
+ if (value_start && value_end >= value_start) {
+ int value_len = (int) (value_end - value_start);
+
+ if (value_len > max_cookies_buffer) {
+ value_len = max_cookies_buffer;
+ }
+ value = typecallocn(char, (unsigned) value_len + 1);
+
+ if (value == NULL)
+ outofmem(__FILE__, "LYProcessSetCookies");
+ LYStrNCpy(value, value_start, value_len);
+ }
+ return value;
+}
+
+#define FLAGS_INVALID_PORT 1
+#define FLAGS_KNOWN_ATTR 2
+#define FLAGS_MAXAGE_ATTR 4
+
+#define is_attr(s, len) attr_len == len && !strncasecomp(attr_start, s, len)
+
+/*
+ * Attribute-names are matched ignoring case.
+ *
+ * Attribute RFC-2109 (1997) RFC-2965 (2000) RFC-6265 (2011)
+ * ---------------------------------------------------------------
+ * comment yes yes -
+ * commentURL - yes -
+ * discard - yes -
+ * domain yes yes yes
+ * expires yes yes yes
+ * httponly - - yes
+ * max-age yes yes yes
+ * path yes yes yes
+ * port - yes -
+ * secure yes yes yes
+ * version yes yes -
+ * ---------------------------------------------------------------
+ */
+static unsigned parse_attribute(unsigned flags,
+ cookie * cur_cookie,
+ int *cookie_len,
+ const char *attr_start,
+ int attr_len,
+ char *value,
+ const char *address,
+ char *hostname,
+ int port)
+{
+ BOOLEAN known_attr = NO;
+ int url_type;
+
+ CTrace((tfp, "parse_attribute %.*s\n", attr_len, attr_start));
+
+ flags &= (unsigned) (~FLAGS_KNOWN_ATTR);
+ if (is_attr("secure", 6)) {
+ if (value == NULL) {
+ known_attr = YES;
+ if (cur_cookie != NULL) {
+ cur_cookie->flags |= COOKIE_FLAG_SECURE;
+ }
+ } else {
+ /*
+ * If secure has a value, assume someone misused it as cookie name.
+ * - FM
+ */
+ known_attr = NO;
+ }
+ } else if (USE_RFC_6265 && is_attr("httponly", 8)) {
+ if (value == NULL) {
+ known_attr = YES; /* known, but irrelevant to lynx */
+ } else {
+ known_attr = NO;
+ }
+ } else if (USE_RFC_2965 && is_attr("discard", 7)) {
+ if (value == NULL) {
+ known_attr = YES;
+ if (cur_cookie != NULL) {
+ cur_cookie->flags |= COOKIE_FLAG_DISCARD;
+ }
+ } else {
+ /*
+ * If discard has a value, assume someone used it as a cookie name.
+ * - FM
+ */
+ known_attr = NO;
+ }
+ } else if ((USE_RFC_2109 || USE_RFC_2965) && is_attr("comment", 7)) {
+ known_attr = YES;
+ if (cur_cookie != NULL && value &&
+ /*
+ * Don't process a repeat comment. - FM
+ */
+ cur_cookie->comment == NULL) {
+ StrAllocCopy(cur_cookie->comment, value);
+ *cookie_len += (int) strlen(cur_cookie->comment);
+ }
+ } else if (USE_RFC_2965 && is_attr("commentURL", 10)) {
+ known_attr = YES;
+ if (cur_cookie != NULL && value &&
+ /*
+ * Don't process a repeat commentURL. - FM
+ */
+ cur_cookie->commentURL == NULL) {
+ /*
+ * We should get only absolute URLs as values, but will resolve
+ * versus the request's URL just in case. - FM
+ */
+ cur_cookie->commentURL = HTParse(value,
+ address,
+ PARSE_ALL);
+ /*
+ * Accept only URLs for http or https servers. - FM
+ */
+ if ((url_type = is_url(cur_cookie->commentURL)) &&
+ (url_type == HTTP_URL_TYPE ||
+ url_type == HTTPS_URL_TYPE)) {
+ *cookie_len += (int) strlen(cur_cookie->commentURL);
+ } else {
+ CTrace((tfp,
+ "LYProcessSetCookies: Rejecting commentURL value '%s'\n",
+ cur_cookie->commentURL));
+ FREE(cur_cookie->commentURL);
+ }
+ }
+ } else if (is_attr("domain", 6)) {
+ known_attr = YES;
+ if (cur_cookie != NULL && value &&
+ /*
+ * Don't process a repeat domain. - FM
+ */
+ !(cur_cookie->flags & COOKIE_FLAG_DOMAIN_SET)) {
+ *cookie_len -= (int) strlen(cur_cookie->domain);
+ /*
+ * If the value does not have a lead dot, but does have an embedded
+ * dot, and is not an exact match to the hostname, nor is a numeric
+ * IP address, add a lead dot. Otherwise, use the value as is. -
+ * FM (domains - case insensitive).
+ */
+ if (value[0] != '.' && value[0] != '\0' &&
+ value[1] != '\0' && strcasecomp(value, hostname)) {
+ char *ptr = StrChr(value, '.');
+
+ if (ptr != NULL && ptr[1] != '\0') {
+ ptr = value;
+ while (*ptr == '.' ||
+ isdigit(UCH(*ptr)))
+ ptr++;
+ if (*ptr != '\0') {
+ CTrace((tfp,
+ "LYProcessSetCookies: Adding lead dot for domain value '%s'\n",
+ value));
+ HTSprintf0(&(cur_cookie->domain), ".%s", value);
+ } else {
+ StrAllocCopy(cur_cookie->domain, value);
+ }
+ } else {
+ StrAllocCopy(cur_cookie->domain, value);
+ }
+ } else {
+ StrAllocCopy(cur_cookie->domain, value);
+ }
+ *cookie_len += (int) strlen(cur_cookie->domain);
+ cur_cookie->flags |= COOKIE_FLAG_DOMAIN_SET;
+ SetCookieDomain(cur_cookie, cur_cookie->domain);
+ }
+ } else if (is_attr("path", 4)) {
+ known_attr = YES;
+ if (cur_cookie != NULL && value &&
+ /*
+ * Don't process a repeat path. - FM
+ */
+ !(cur_cookie->flags & COOKIE_FLAG_PATH_SET)) {
+ *cookie_len -= (int) strlen(cur_cookie->path);
+ StrAllocCopy(cur_cookie->path, value);
+ *cookie_len += (cur_cookie->pathlen = (int) strlen(cur_cookie->path));
+ cur_cookie->flags |= COOKIE_FLAG_PATH_SET;
+ CTrace((tfp, " ->%.*s\n", cur_cookie->pathlen, cur_cookie->path));
+ }
+ } else if (USE_RFC_2965 && is_attr("port", 4)) {
+ if (cur_cookie != NULL && value &&
+ /*
+ * Don't process a repeat port. - FM
+ */
+ cur_cookie->PortList == NULL) {
+ char *cp = value;
+
+ while ((*cp != '\0') &&
+ (isdigit(UCH(*cp)) ||
+ *cp == ',' || *cp == ' ')) {
+ cp++;
+ }
+ if (*cp == '\0') {
+ if (!port_matches(port, value)) {
+ flags |= FLAGS_INVALID_PORT;
+ } else {
+ StrAllocCopy(cur_cookie->PortList, value);
+ *cookie_len += (int) strlen(cur_cookie->PortList);
+ CTrace((tfp, " ->%s\n", cur_cookie->PortList));
+ }
+ known_attr = YES;
+ } else {
+ known_attr = NO;
+ }
+ } else if (cur_cookie != NULL) {
+ /*
+ * Don't process a repeat port. - FM
+ */
+ if (cur_cookie->PortList == NULL) {
+ HTSprintf0(&(cur_cookie->PortList), "%d", port);
+ *cookie_len += (int) strlen(cur_cookie->PortList);
+ }
+ known_attr = YES;
+ }
+ } else if ((USE_RFC_2109 || USE_RFC_2965) && is_attr("version", 7)) {
+ known_attr = YES;
+ if (cur_cookie != NULL && value &&
+ /*
+ * Don't process a repeat version. - FM
+ */
+ cur_cookie->version < 1) {
+ int temp = (int) strtol(value, NULL, 10);
+
+ if (errno != -ERANGE) {
+ cur_cookie->version = temp;
+ }
+ }
+ } else if (is_attr("max-age", 7)) {
+ known_attr = YES;
+ /*
+ * Don't process a repeat max-age. - FM
+ */
+ if (cur_cookie != NULL && value &&
+ !(flags & FLAGS_MAXAGE_ATTR)) {
+ long temp = strtol(value, NULL, 10);
+
+ cur_cookie->flags |= COOKIE_FLAG_EXPIRES_SET;
+ if (errno == -ERANGE) {
+ cur_cookie->expires = (time_t) 0;
+ } else {
+ cur_cookie->expires = (time(NULL) + temp);
+ CTrace((tfp, "LYSetCookie: expires %" PRI_time_t ", %s",
+ CAST_time_t (cur_cookie->expires),
+ ctime(&cur_cookie->expires)));
+ }
+ flags |= FLAGS_MAXAGE_ATTR;
+ }
+ } else if (is_attr("expires", 7)) {
+ /*
+ * Convert an 'expires' attribute value if we haven't received a
+ * 'max-age'. Note that 'expires' should not be used in Version 1
+ * cookies, but it might be used for "backward compatibility", and, in
+ * turn, ill-informed people surely would start using it instead of,
+ * rather than in addition to, 'max-age'. - FM
+ */
+ known_attr = YES;
+ if ((cur_cookie != NULL && !(flags & FLAGS_MAXAGE_ATTR)) &&
+ !(cur_cookie->flags & COOKIE_FLAG_EXPIRES_SET)) {
+ if (value) {
+ cur_cookie->flags |= COOKIE_FLAG_EXPIRES_SET;
+ cur_cookie->expires = LYmktime(value, FALSE);
+ if (cur_cookie->expires > 0) {
+ CTrace((tfp, "LYSetCookie: expires %" PRI_time_t ", %s",
+ CAST_time_t (cur_cookie->expires),
+ ctime(&cur_cookie->expires)));
+ }
+ }
+ }
+ }
+ if (known_attr)
+ flags |= FLAGS_KNOWN_ATTR;
+ return flags;
+}
+
+/*
+ * Process potentially concatenated Set-Cookie2 and/or Set-Cookie
+ * headers. - FM
+ */
+static void LYProcessSetCookies(const char *SetCookie,
+ const char *SetCookie2,
+ const char *address,
+ char *hostname,
+ char *path,
+ int port)
+{
+ const char *p, *attr_start, *attr_end, *value_start, *value_end;
+ HTList *CombinedCookies = NULL, *cl = NULL;
+ cookie *cur_cookie = NULL, *co = NULL;
+ int cookie_len = 0;
+ int NumCookies = 0;
+ BOOL Quoted = FALSE;
+ unsigned parse_flags = 0;
+
+ if (isEmpty(SetCookie) &&
+ isEmpty(SetCookie2)) {
+ /*
+ * Yuk! Garbage in, so nothing out. - FM
+ */
+ return;
+ }
+
+ /*
+ * If we have both Set-Cookie and Set-Cookie2 headers. process the
+ * Set-Cookie2 header. Otherwise, process whichever of the two headers we
+ * do have. Note that if more than one instance of a valued attribute for
+ * the same cookie is encountered, the value for the first instance is
+ * retained.
+ */
+ CombinedCookies = HTList_new();
+
+ /*
+ * Process the Set-Cookie2 header, if present and not zero-length, adding
+ * each cookie to the CombinedCookies list. - FM
+ */
+ p = NonNull(SetCookie2);
+ if (SetCookie && *p) {
+ CTrace((tfp, "LYProcessSetCookies: Using Set-Cookie2 header.\n"));
+ }
+ while (NumCookies <= max_cookies_domain && *p) {
+ value_start = value_end = NULL;
+ p = LYSkipCBlanks(p);
+ /*
+ * Get the attribute name.
+ */
+ attr_start = p;
+ while (*p != '\0' && !isspace(UCH(*p)) &&
+ *p != '=' && *p != ';' && *p != ',')
+ p++;
+ attr_end = p;
+ p = LYSkipCBlanks(p);
+
+ /*
+ * Check for an '=' delimiter, or an 'expires' name followed by white,
+ * since Netscape's bogus parser doesn't require an '=' delimiter, and
+ * 'expires' attributes are being encountered without them. These
+ * shouldn't be in a Set-Cookie2 header, but we'll assume it's an
+ * expires attribute rather a cookie with that name, since the
+ * attribute mistake rather than name mistake seems more likely to be
+ * made by providers. - FM
+ */
+ if (*p == '=' ||
+ !strncasecomp(attr_start, "Expires", 7)) {
+ /*
+ * Get the value string.
+ */
+ if (*p == '=') {
+ p++;
+ }
+ p = LYSkipCBlanks(p);
+ /*
+ * Hack alert! We must handle Netscape-style cookies with
+ * "Expires=Mon, 01-Jan-96 13:45:35 GMT" or
+ * "Expires=Mon, 1 Jan 1996 13:45:35 GMT".
+ * No quotes, but there are spaces. Argh... Anyway, we know it
+ * will have at least 3 space separators within it, and two dashes
+ * or two more spaces, so this code looks for a space after the 5th
+ * space separator or dash to mark the end of the value. - FM
+ */
+ if ((attr_end - attr_start) == 7 &&
+ !strncasecomp(attr_start, "Expires", 7)) {
+ int spaces = 6;
+
+ value_start = p;
+ if (isdigit(UCH(*p))) {
+ /*
+ * No alphabetic day field. - FM
+ */
+ spaces--;
+ } else {
+ /*
+ * Skip the alphabetic day field. - FM
+ */
+ while (*p != '\0' && isalpha(UCH(*p))) {
+ p++;
+ }
+ while (*p == ',' || isspace(UCH(*p))) {
+ p++;
+ }
+ spaces--;
+ }
+ while (*p != '\0' && *p != ';' && *p != ',' && spaces) {
+ p++;
+ if (isspace(UCH(*p))) {
+ while (isspace(UCH(*(p + 1))))
+ p++;
+ spaces--;
+ } else if (*p == '-') {
+ spaces--;
+ }
+ }
+ value_end = p;
+ /*
+ * Hack Alert! The port attribute can take a comma separated
+ * list of numbers as a value, and such values should be
+ * quoted, but if not, make sure we don't treat a number in the
+ * list as the start of a new cookie. - FM
+ */
+ } else if ((attr_end - attr_start) == 4 &&
+ !strncasecomp(attr_start, "port", 4) &&
+ isdigit(UCH(*p))) {
+ /*
+ * The value starts as an unquoted number.
+ */
+ const char *cp, *cp1;
+
+ value_start = p;
+ while (1) {
+ while (isdigit(UCH(*p)))
+ p++;
+ value_end = p;
+ p = LYSkipCBlanks(p);
+ if (*p == '\0' || *p == ';')
+ break;
+ if (*p == ',') {
+ cp = LYSkipCBlanks(p + 1);
+ if (*cp != '\0' && isdigit(UCH(*cp))) {
+ cp1 = cp;
+ while (isdigit(UCH(*cp1)))
+ cp1++;
+ cp1 = LYSkipCBlanks(cp1);
+ if (*cp1 == '\0' || *cp1 == ',' || *cp1 == ';') {
+ p = cp;
+ continue;
+ }
+ }
+ }
+ while (*p != '\0' && *p != ';' && *p != ',')
+ p++;
+ value_end = p;
+ /*
+ * Trim trailing spaces.
+ */
+ if ((value_end > value_start) &&
+ isspace(UCH(*(value_end - 1)))) {
+ value_end--;
+ while ((value_end > (value_start + 1)) &&
+ isspace(UCH(*value_end)) &&
+ isspace(UCH(*(value_end - 1)))) {
+ value_end--;
+ }
+ }
+ break;
+ }
+ } else if (*p == '"') {
+ BOOLEAN escaped = FALSE;
+
+ /*
+ * It looks like quoted string.
+ */
+ p++;
+ value_start = p;
+ while (*p != '\0' && (*p != '"' || escaped)) {
+ escaped = (BOOL) (!escaped && *p == '\\');
+ p++;
+ }
+ if (p != value_start && *p == '"' && !escaped) {
+ value_end = p;
+ p++;
+ Quoted = TRUE;
+ } else {
+ value_start--;
+ value_end = p;
+ if (*p)
+ p++;
+ Quoted = FALSE;
+ }
+ } else {
+ /*
+ * Otherwise, it's an unquoted string.
+ */
+ value_start = p;
+ while (*p != '\0' && *p != ';' && *p != ',')
+ p++;
+ value_end = p;
+ /*
+ * Trim trailing spaces.
+ */
+ if ((value_end > value_start) &&
+ isspace(UCH(*(value_end - 1)))) {
+ value_end--;
+ while ((value_end > (value_start + 1)) &&
+ isspace(UCH(*value_end)) &&
+ isspace(UCH(*(value_end - 1)))) {
+ value_end--;
+ }
+ }
+ }
+ }
+
+ /*
+ * Check for a separator character, and skip it.
+ */
+ if (*p == ';' || *p == ',')
+ p++;
+
+ /*
+ * Now, we can handle this attribute/value pair.
+ */
+ if (attr_end > attr_start) {
+ char *value = alloc_attr_value(value_start, value_end);
+
+ parse_flags = parse_attribute(parse_flags,
+ cur_cookie,
+ &cookie_len,
+ attr_start,
+ (int) (attr_end - attr_start),
+ value,
+ address,
+ hostname,
+ port);
+
+ /*
+ * Presence of value is needed (indicated normally by '='),
+ * but it can be an empty string. - kw 1999-06-24
+ */
+ if (!(parse_flags & FLAGS_KNOWN_ATTR)
+ && value
+ && value_end >= value_start) {
+ /*
+ * If we've started a cookie, and it's not too big, save it in
+ * the CombinedCookies list. - FM
+ */
+ if (cookie_len <= max_cookies_buffer
+ && cur_cookie != NULL
+ && !(parse_flags & FLAGS_INVALID_PORT)) {
+ AssumeCookieVersion(cur_cookie);
+ HTList_appendObject(CombinedCookies, cur_cookie);
+ } else if (cur_cookie != NULL) {
+ CTrace((tfp,
+ "LYProcessSetCookies: Rejecting Set-Cookie2: %s=%s\n",
+ (cur_cookie->name ?
+ cur_cookie->name : "[no name]"),
+ (cur_cookie->value ?
+ cur_cookie->value : "[no value]")));
+ CTrace((tfp,
+ (parse_flags & FLAGS_INVALID_PORT) ?
+ " due to excessive length!\n"
+ : " due to invalid port!\n"));
+ if (parse_flags & FLAGS_INVALID_PORT) {
+ NumCookies--;
+ }
+ freeCookie(cur_cookie);
+ cur_cookie = NULL;
+ }
+ /*
+ * Start a new cookie. - FM
+ */
+ cur_cookie = newCookie();
+ cookie_len = 0;
+ NumCookies++;
+ MemAllocCopy(&(cur_cookie->name), attr_start, attr_end);
+ cookie_len += (int) strlen(cur_cookie->name);
+ MemAllocCopy(&(cur_cookie->value), value_start, value_end);
+ cookie_len += (int) strlen(cur_cookie->value);
+ StrAllocCopy(cur_cookie->domain, hostname);
+ cookie_len += (int) strlen(hostname);
+ StrAllocCopy(cur_cookie->path, path);
+ cookie_len += (cur_cookie->pathlen = (int) strlen(cur_cookie->path));
+ cur_cookie->port = port;
+ parse_flags = 0;
+ cur_cookie->quoted = TRUE;
+ SetCookieDomain(cur_cookie, hostname);
+ }
+ FREE(value);
+ }
+ }
+
+ /*
+ * Add any final SetCookie2 cookie to the CombinedCookie list if we are
+ * within the length limit. - FM
+ */
+ if (NumCookies <= max_cookies_domain
+ && cookie_len <= max_cookies_buffer
+ && cur_cookie != NULL && !(parse_flags & FLAGS_INVALID_PORT)) {
+ AssumeCookieVersion(cur_cookie);
+ HTList_appendObject(CombinedCookies, cur_cookie);
+ } else if (cur_cookie != NULL && !(parse_flags & FLAGS_INVALID_PORT)) {
+ CTrace((tfp, "LYProcessSetCookies: Rejecting Set-Cookie2: %s=%s\n",
+ (cur_cookie->name ? cur_cookie->name : "[no name]"),
+ (cur_cookie->value ? cur_cookie->value : "[no value]")));
+ CTrace((tfp, " due to excessive %s%s%s\n",
+ (cookie_len > max_cookies_buffer ? "length" : ""),
+ (cookie_len > max_cookies_buffer &&
+ NumCookies > max_cookies_domain
+ ? " and "
+ : ""),
+ (NumCookies > max_cookies_domain ? "number!\n" : "!\n")));
+ freeCookie(cur_cookie);
+ cur_cookie = NULL;
+ } else if (cur_cookie != NULL) { /* invalidport */
+ CTrace((tfp, "LYProcessSetCookies: Rejecting Set-Cookie2: %s=%s\n",
+ (cur_cookie->name ? cur_cookie->name : "[no name]"),
+ (cur_cookie->value ? cur_cookie->value : "[no value]")));
+ CTrace((tfp, " due to invalid port!\n"));
+ NumCookies--;
+ freeCookie(cur_cookie);
+ cur_cookie = NULL;
+ }
+
+ /*
+ * Process the Set-Cookie header, if no non-zero-length Set-Cookie2 header
+ * was present. - FM
+ */
+ cookie_len = 0;
+ NumCookies = 0;
+ cur_cookie = NULL;
+ p = ((SetCookie && isEmpty(SetCookie2)) ? SetCookie : "");
+ if (SetCookie2 && *p) {
+ CTrace((tfp, "LYProcessSetCookies: Using Set-Cookie header.\n"));
+ }
+ while (NumCookies <= max_cookies_domain && *p) {
+ value_start = value_end = NULL;
+ p = LYSkipCBlanks(p);
+ /*
+ * Get the attribute name.
+ */
+ attr_start = p;
+ while (*p != '\0' && !isspace(UCH(*p)) &&
+ *p != '=' && *p != ';' && *p != ',')
+ p++;
+ attr_end = p;
+ p = LYSkipCBlanks(p);
+
+ /*
+ * Check for an '=' delimiter, or an 'expires' name followed by white,
+ * since Netscape's bogus parser doesn't require an '=' delimiter, and
+ * 'expires' attributes are being encountered without them. - FM
+ */
+ if (*p == '=' ||
+ !strncasecomp(attr_start, "Expires", 7)) {
+ /*
+ * Get the value string.
+ */
+ if (*p == '=') {
+ p++;
+ }
+ p = LYSkipCBlanks(p);
+ /*
+ * Hack alert! We must handle Netscape-style cookies with
+ * "Expires=Mon, 01-Jan-96 13:45:35 GMT" or
+ * "Expires=Mon, 1 Jan 1996 13:45:35 GMT".
+ * No quotes, but there are spaces. Argh... Anyway, we know it
+ * will have at least 3 space separators within it, and two dashes
+ * or two more spaces, so this code looks for a space after the 5th
+ * space separator or dash to mark the end of the value. - FM
+ */
+ if ((attr_end - attr_start) == 7 &&
+ !strncasecomp(attr_start, "Expires", 7)) {
+ int spaces = 6;
+
+ value_start = p;
+ if (isdigit(UCH(*p))) {
+ /*
+ * No alphabetic day field. - FM
+ */
+ spaces--;
+ } else {
+ /*
+ * Skip the alphabetic day field. - FM
+ */
+ while (*p != '\0' && isalpha(UCH(*p))) {
+ p++;
+ }
+ while (*p == ',' || isspace(UCH(*p))) {
+ p++;
+ }
+ spaces--;
+ }
+ while (*p != '\0' && *p != ';' && *p != ',' && spaces) {
+ p++;
+ if (isspace(UCH(*p))) {
+ while (isspace(UCH(*(p + 1))))
+ p++;
+ spaces--;
+ } else if (*p == '-') {
+ spaces--;
+ }
+ }
+ value_end = p;
+ /*
+ * Hack Alert! The port attribute can take a comma separated
+ * list of numbers as a value, and such values should be
+ * quoted, but if not, make sure we don't treat a number in the
+ * list as the start of a new cookie. - FM
+ */
+ } else if ((attr_end - attr_start) == 4 &&
+ !strncasecomp(attr_start, "port", 4) &&
+ isdigit(UCH(*p))) {
+ /*
+ * The value starts as an unquoted number.
+ */
+ const char *cp, *cp1;
+
+ value_start = p;
+ while (1) {
+ while (isdigit(UCH(*p)))
+ p++;
+ value_end = p;
+ p = LYSkipCBlanks(p);
+ if (*p == '\0' || *p == ';')
+ break;
+ if (*p == ',') {
+ cp = LYSkipCBlanks(p + 1);
+ if (*cp != '\0' && isdigit(UCH(*cp))) {
+ cp1 = cp;
+ while (isdigit(UCH(*cp1)))
+ cp1++;
+ cp1 = LYSkipCBlanks(cp1);
+ if (*cp1 == '\0' || *cp1 == ',' || *cp1 == ';') {
+ p = cp;
+ continue;
+ }
+ }
+ }
+ while (*p != '\0' && *p != ';' && *p != ',')
+ p++;
+ value_end = p;
+ /*
+ * Trim trailing spaces.
+ */
+ if ((value_end > value_start) &&
+ isspace(UCH(*(value_end - 1)))) {
+ value_end--;
+ while ((value_end > (value_start + 1)) &&
+ isspace(UCH(*value_end)) &&
+ isspace(UCH(*(value_end - 1)))) {
+ value_end--;
+ }
+ }
+ break;
+ }
+ } else if (*p == '"') {
+ BOOLEAN escaped = FALSE;
+
+ /*
+ * It looks like quoted string.
+ */
+ p++;
+ value_start = p;
+ while (*p != '\0' && (*p != '"' || escaped)) {
+ escaped = (BOOL) (!escaped && *p == '\\');
+ p++;
+ }
+ if (p != value_start && *p == '"' && !escaped) {
+ value_end = p;
+ p++;
+ Quoted = TRUE;
+ } else {
+ value_start--;
+ value_end = p;
+ if (*p)
+ p++;
+ Quoted = FALSE;
+ }
+ } else {
+ /*
+ * Otherwise, it's an unquoted string.
+ */
+ value_start = p;
+ while (*p != '\0' && *p != ';' && *p != ',')
+ p++;
+ value_end = p;
+ /*
+ * Trim trailing spaces.
+ */
+ if ((value_end > value_start) &&
+ isspace(UCH(*(value_end - 1)))) {
+ value_end--;
+ while ((value_end > (value_start + 1)) &&
+ isspace(UCH(*value_end)) &&
+ isspace(UCH(*(value_end - 1)))) {
+ value_end--;
+ }
+ }
+ }
+ }
+
+ /*
+ * Check for a separator character, and skip it.
+ */
+ if (*p == ';' || *p == ',')
+ p++;
+
+ /*
+ * Now, we can handle this attribute/value pair.
+ */
+ if (attr_end > attr_start) {
+ char *value = alloc_attr_value(value_start, value_end);
+
+ parse_flags = parse_attribute(parse_flags,
+ cur_cookie,
+ &cookie_len,
+ attr_start,
+ (int) (attr_end - attr_start),
+ value,
+ address,
+ hostname,
+ port);
+
+ /*
+ * Presence of value is needed (indicated normally by '='),
+ * but it can be an empty string. - kw 1999-06-24
+ */
+ if (!(parse_flags & FLAGS_KNOWN_ATTR)
+ && value
+ && value_end >= value_start) {
+ /*
+ * If we've started a cookie, and it's not too big, save it in
+ * the CombinedCookies list. - FM
+ */
+ if (cookie_len <= max_cookies_buffer
+ && cur_cookie != NULL) {
+ /*
+ * If we had a Set-Cookie2 header, make sure the version is
+ * at least 1, and mark it for quoting. - FM
+ */
+ if (SetCookie2 != NULL) {
+ AssumeCookieVersion(cur_cookie);
+ cur_cookie->quoted = TRUE;
+ }
+ HTList_appendObject(CombinedCookies, cur_cookie);
+ } else if (cur_cookie != NULL) {
+ CTrace((tfp,
+ "LYProcessSetCookies: Rejecting Set-Cookie: %s=%s\n",
+ (cur_cookie->name ?
+ cur_cookie->name : "[no name]"),
+ (cur_cookie->value ?
+ cur_cookie->value : "[no value]")));
+ CTrace((tfp,
+ " due to excessive length!\n"));
+ freeCookie(cur_cookie);
+ cur_cookie = NULL;
+ }
+ /*
+ * Start a new cookie. - FM
+ */
+ cur_cookie = newCookie();
+ NumCookies++;
+ cookie_len = 0;
+ MemAllocCopy(&(cur_cookie->name), attr_start, attr_end);
+ cookie_len += (int) strlen(cur_cookie->name);
+ MemAllocCopy(&(cur_cookie->value), value_start, value_end);
+ cookie_len += (int) strlen(cur_cookie->value);
+ StrAllocCopy(cur_cookie->domain, hostname);
+ cookie_len += (int) strlen(hostname);
+ StrAllocCopy(cur_cookie->path, path);
+ cookie_len += (cur_cookie->pathlen = (int) strlen(cur_cookie->path));
+ cur_cookie->port = port;
+ parse_flags = 0;
+ cur_cookie->quoted = Quoted;
+ Quoted = FALSE;
+ SetCookieDomain(cur_cookie, hostname);
+ }
+ FREE(value);
+ }
+ }
+
+ /*
+ * Handle the final Set-Cookie cookie if within length limit. - FM
+ */
+ if (NumCookies <= max_cookies_domain
+ && cookie_len <= max_cookies_buffer
+ && cur_cookie != NULL) {
+ if (SetCookie2 != NULL) {
+ AssumeCookieVersion(cur_cookie);
+ cur_cookie->quoted = TRUE;
+ }
+ HTList_appendObject(CombinedCookies, cur_cookie);
+ } else if (cur_cookie != NULL) {
+ CTrace((tfp, "LYProcessSetCookies: Rejecting Set-Cookie: %s=%s\n",
+ (cur_cookie->name ? cur_cookie->name : "[no name]"),
+ (cur_cookie->value ? cur_cookie->value : "[no value]")));
+ CTrace((tfp, " due to excessive %s%s%s\n",
+ (cookie_len > max_cookies_buffer ? "length" : ""),
+ (cookie_len > max_cookies_buffer && NumCookies > max_cookies_domain
+ ? " and "
+ : ""),
+ (NumCookies > max_cookies_domain ? "number!\n" : "!\n")));
+ freeCookie(cur_cookie);
+ cur_cookie = NULL;
+ }
+
+ /*
+ * OK, now we can actually store any cookies in the CombinedCookies list.
+ * - FM
+ */
+ cl = CombinedCookies;
+ while (NULL != (co = (cookie *) HTList_nextObject(cl))) {
+ CTrace((tfp, "LYProcessSetCookie: attr=value pair: '%s=%s'\n",
+ (co->name ? co->name : "[no name]"),
+ (co->value ? co->value : "[no value]")));
+ if (co->expires > 0) {
+ CTrace((tfp, " expires: %" PRI_time_t ", %s\n",
+ CAST_time_t (co->expires),
+ ctime(&co->expires)));
+ }
+ if (isHTTPS_URL(address) &&
+ LYForceSSLCookiesSecure == TRUE &&
+ !(co->flags & COOKIE_FLAG_SECURE)) {
+ co->flags |= COOKIE_FLAG_SECURE;
+ CTrace((tfp, " Forced the 'secure' flag on.\n"));
+ }
+ store_cookie(co, hostname, path);
+ }
+ HTList_delete(CombinedCookies);
+ CombinedCookies = NULL;
+
+ return;
+}
+
+/*
+ * Entry function for handling Set-Cookie: and/or Set-Cookie2:
+ * reply headers. They may have been concatenated as comma
+ * separated lists in HTTP.c or HTMIME.c. - FM
+ */
+void LYSetCookie(const char *SetCookie,
+ const char *SetCookie2,
+ const char *address)
+{
+ BOOL BadHeaders = FALSE;
+ char *hostname = NULL, *path = NULL, *ptr;
+ int port = 80;
+
+ /*
+ * Get the hostname, port and path of the address, and report the
+ * Set-Cookie and/or Set-Cookie2 header(s) if trace mode is on, but set the
+ * cookie(s) only if LYSetCookies is TRUE. - FM
+ */
+ if (((hostname = HTParse(address, "", PARSE_HOST)) != NULL) &&
+ (ptr = StrChr(hostname, ':')) != NULL) {
+ /*
+ * Replace default port number.
+ */
+ *ptr = '\0';
+ ptr++;
+ port = atoi(ptr);
+ } else if (isHTTPS_URL(address)) {
+ port = 443;
+ }
+
+ /*
+ * Get the path from the request URI.
+ */
+ if ((path = HTParse(address, "", PARSE_PATH | PARSE_PUNCTUATION)) != NULL) {
+ /*
+ * Trim off any parameters to provide something that we can compare
+ * against the cookie's path for verifying if it has the proper prefix.
+ */
+ if ((ptr = StrChr(path, '?')) != NULL) {
+ CTrace((tfp, "discarding params \"%s\" in request URI\n", ptr));
+ *ptr = '\0';
+ }
+ /* trim a trailing slash, unless we have only a "/" */
+ if ((ptr = strrchr(path, '/')) != NULL) {
+ if (ptr == path) {
+ ++ptr;
+ }
+ CTrace((tfp, "discarding \"%s\" from request URI\n", ptr));
+ *ptr = '\0';
+ }
+ }
+
+ if (isEmpty(SetCookie) &&
+ isEmpty(SetCookie2)) {
+ /*
+ * Yuk, something must have gone wrong in HTMIME.c or HTTP.c because
+ * both SetCookie and SetCookie2 are NULL or zero-length. - FM
+ */
+ BadHeaders = TRUE;
+ }
+ CTrace((tfp, "LYSetCookie called with host '%s', path '%s',\n",
+ NonNull(hostname),
+ NonNull(path)));
+ if (SetCookie) {
+ CTrace((tfp, " and Set-Cookie: '%s'\n", SetCookie));
+ }
+ if (SetCookie2) {
+ CTrace((tfp, " and Set-Cookie2: '%s'\n", SetCookie2));
+ }
+ if (LYSetCookies == FALSE || BadHeaders == TRUE) {
+ CTrace((tfp, " Ignoring this Set-Cookie/Set-Cookie2 request.\n"));
+ }
+
+ /*
+ * We're done if LYSetCookies is off or we have bad headers. - FM
+ */
+ if (LYSetCookies == FALSE || BadHeaders == TRUE) {
+ FREE(hostname);
+ FREE(path);
+ return;
+ }
+
+ /*
+ * Process the header(s).
+ */
+ LYProcessSetCookies(SetCookie, SetCookie2, address, hostname, path, port);
+ FREE(hostname);
+ FREE(path);
+ return;
+}
+
+/*
+ * Entry function from creating a Cookie: request header
+ * if needed. - AK & FM
+ */
+char *LYAddCookieHeader(char *hostname,
+ char *path,
+ int port,
+ int secure)
+{
+ char *header = NULL;
+ HTList *hl = domain_list, *next = NULL;
+ domain_entry *de;
+
+ CTrace((tfp, "LYCookie: Searching for '%s:%d', '%s'.\n",
+ NONNULL(hostname),
+ port,
+ NONNULL(path)));
+
+ /*
+ * Search the cookie_list elements in the domain_list for any cookies
+ * associated with the //hostname:port/path
+ */
+ while (hl) {
+ de = (domain_entry *) hl->object;
+ next = hl->next;
+
+ if (de != NULL) {
+ if (!HTList_isEmpty(de->cookie_list)) {
+ /*
+ * Scan the domain's cookie_list for any cookies we should
+ * include in our request header.
+ */
+ header = scan_cookie_sublist(hostname, path, port,
+ de->cookie_list, header, secure);
+ } else if (de->bv == QUERY_USER && de->invcheck_bv == DEFAULT_INVCHECK_BV) {
+ /*
+ * No cookies in this domain, and no default accept/reject
+ * choice was set by the user, so delete the domain. - FM
+ */
+ freeCookies(de);
+ HTList_removeObject(domain_list, de);
+ FREE(de);
+ }
+ }
+ hl = next;
+ }
+ if (header)
+ return (header);
+
+ return (NULL);
+}
+
+#ifdef USE_PERSISTENT_COOKIES
+static int number_of_file_cookies = 0;
+
+/* rjp - cookie loading */
+void LYLoadCookies(char *cookie_file)
+{
+ FILE *cookie_handle;
+ char *buf = NULL;
+ static char domain[256], path[LY_MAXPATH], name[256], value[4100];
+ static char what[8], secure[8], expires_a[16];
+ /* *INDENT-OFF* */
+ static struct {
+ char *s;
+ size_t n;
+ } tok_values[] = {
+ { domain, sizeof(domain) },
+ { what, sizeof(what) },
+ { path, sizeof(path) },
+ { secure, sizeof(secure) },
+ { expires_a, sizeof(expires_a) },
+ { name, sizeof(name) },
+ { value, sizeof(value) },
+ { NULL, 0 }
+ };
+ /* *INDENT-ON* */
+
+ time_t expires;
+
+ cookie_handle = fopen(cookie_file, TXT_R);
+ if (!cookie_handle)
+ return;
+
+ CTrace((tfp, "LYLoadCookies: reading cookies from %s\n", cookie_file));
+
+ number_of_file_cookies = 0;
+ while (LYSafeGets(&buf, cookie_handle) != 0) {
+ cookie *moo;
+ int tok_loop;
+ char *tok_out, *tok_ptr;
+
+ LYTrimNewline(buf);
+ if (buf[0] == '\0' || buf[0] == '#') {
+ continue;
+ }
+
+ number_of_file_cookies++;
+
+ strcat(buf, "\t"); /* add sep after line if enough space - kw */
+
+ /*
+ * Tokenise the cookie line into its component parts -
+ * this only works for Netscape style cookie files at the
+ * moment. It may be worth investigating an alternative
+ * format for Lynx because the Netscape format isn't all
+ * that useful, or future-proof. - RP
+ *
+ * 'fixed' by using strsep instead of strtok. No idea
+ * what kind of platform problems this might introduce. - RP
+ */
+ /*
+ * This fails when the path is blank
+ *
+ * sscanf(buf, "%s\t%s\t%s\t%s\t%d\t%s\t%[ -~]",
+ * domain, what, path, secure, &expires, name, value);
+ */
+ CTrace((tfp, "LYLoadCookies: tokenising %s\n", buf));
+ tok_ptr = buf;
+ tok_out = LYstrsep(&tok_ptr, "\t");
+ for (tok_loop = 0; tok_out && tok_values[tok_loop].s; tok_loop++) {
+ CTrace((tfp, "\t%d:[%03d]:[%s]\n",
+ tok_loop, (int) (tok_out - buf), tok_out));
+ LYStrNCpy(tok_values[tok_loop].s,
+ tok_out,
+ (int) tok_values[tok_loop].n);
+ /*
+ * It looks like strtok ignores a leading delimiter,
+ * which makes things a bit more interesting. Something
+ * like "FALSE\t\tFALSE\t" translates to FALSE,FALSE
+ * instead of FALSE,,FALSE. - RP
+ */
+ tok_out = LYstrsep(&tok_ptr, "\t");
+ }
+
+ if (tok_values[tok_loop].s) {
+ /* tok_out in above loop must have been NULL prematurely - kw */
+ CTrace((tfp,
+ "*** wrong format: not enough tokens, ignoring line!\n"));
+ continue;
+ }
+
+ expires = atol(expires_a);
+ CTrace((tfp, "expires:\t%s\n", ctime(&expires)));
+ moo = newCookie();
+ StrAllocCopy(moo->domain, domain);
+ SetCookieDomain(moo, domain);
+ StrAllocCopy(moo->path, path);
+ StrAllocCopy(moo->name, name);
+ if (value[0] == '"' &&
+ value[1] && value[strlen(value) - 1] == '"' &&
+ value[strlen(value) - 2] != '\\') {
+ value[strlen(value) - 1] = '\0';
+ StrAllocCopy(moo->value, value + 1);
+ moo->quoted = TRUE;
+ } else {
+ StrAllocCopy(moo->value, value);
+ }
+ moo->pathlen = (int) strlen(moo->path);
+ /*
+ * Justification for following flags:
+ * COOKIE_FLAG_FROM_FILE So we know were it comes from.
+ * COOKIE_FLAG_EXPIRES_SET It must have had an explicit
+ * expiration originally, otherwise
+ * it would not be in the file.
+ * COOKIE_FLAG_DOMAIN_SET, We don't know whether these were
+ * COOKIE_FLAG_PATH_SET explicit or implicit, but this
+ * only matters for sending version 1
+ * cookies; the cookies read from the
+ * file are currently treated all like
+ * version 0 (we don't set moo->version)
+ * so $Domain= and $Path= will normally
+ * not be sent to the server. But if
+ * these cookies somehow get mixed with
+ * new version 1 cookies we may end up
+ * sending version 1 to the server, and
+ * in that case we should send $Domain
+ * and $Path. The state-man-mec drafts
+ * and RFC 2109 say that $Domain and
+ * $Path SHOULD be omitted if they were
+ * not given explicitly, but not that
+ * they MUST be omitted.
+ * See 8.2 Cookie Spoofing in draft -10
+ * for a good reason to send them.
+ * However, an explicit domain should be
+ * now prefixed with a dot (unless it is
+ * for a single host), so we check for
+ * that.
+ * COOKIE_FLAG_SECURE Should have "FALSE" for normal,
+ * otherwise set it.
+ */
+ moo->flags |= COOKIE_FLAG_FROM_FILE | COOKIE_FLAG_EXPIRES_SET |
+ COOKIE_FLAG_PATH_SET;
+ if (LeadingDot(domain))
+ moo->flags |= COOKIE_FLAG_DOMAIN_SET;
+ if (secure[0] != 'F')
+ moo->flags |= COOKIE_FLAG_SECURE;
+ /* @@@ Should we set port to 443 if secure is set? @@@ */
+ moo->expires = expires;
+ /*
+ * I don't like using this to store the cookies because it's
+ * designed to store cookies that have been received from an
+ * HTTP request, not from a persistent cookie jar. Hence the
+ * mucking about with the COOKIE_FLAG_FROM_FILE above. - RP
+ */
+ store_cookie(moo, domain, path);
+ }
+ LYCloseInput(cookie_handle);
+}
+
+static FILE *NewCookieFile(char *cookie_file)
+{
+ CTrace((tfp, "LYStoreCookies: save cookies to %s on exit\n", cookie_file));
+ return LYNewTxtFile(cookie_file);
+}
+
+/* rjp - persistent cookie support */
+void LYStoreCookies(char *cookie_file)
+{
+ HTList *dl, *cl;
+ domain_entry *de;
+ cookie *co;
+ FILE *cookie_handle = NULL;
+ time_t now = time(NULL); /* system specific? - RP */
+
+ if (isEmpty(cookie_file) || !strcmp(cookie_file, "/dev/null")) {
+ /* We give /dev/null the Unix meaning, regardless of OS */
+ return;
+ }
+
+ /*
+ * Check whether we have something to do. - FM
+ */
+ if (HTList_isEmpty(domain_list) &&
+ number_of_file_cookies == 0) {
+ /* No cookies now, and haven't read any,
+ * so don't bother updating the file.
+ */
+ return;
+ }
+
+ /* if we read cookies from the file, we'll update it even if now empty */
+ if (number_of_file_cookies != 0) {
+ cookie_handle = NewCookieFile(cookie_file);
+ if (cookie_handle == NULL)
+ return;
+ }
+
+ for (dl = domain_list; dl != NULL; dl = dl->next) {
+ de = (domain_entry *) (dl->object);
+ if (de == NULL)
+ /*
+ * Fote says the first object is NULL. Go with that.
+ */
+ continue;
+
+ /*
+ * Show the domain's cookies. - FM
+ */
+ for (cl = de->cookie_list; cl != NULL; cl = cl->next) {
+ /*
+ * First object is always NULL. - FM
+ */
+ if ((co = (cookie *) cl->object) == NULL)
+ continue;
+
+ CTrace((tfp, "LYStoreCookies: %" PRI_time_t " %s %" PRI_time_t " ",
+ CAST_time_t (now),
+ (now < co->expires) ? "<" : ">",
+ CAST_time_t (co->expires)));
+
+ if ((co->flags & COOKIE_FLAG_DISCARD)) {
+ CTrace((tfp, "not stored - DISCARD\n"));
+ continue;
+ } else if (!(co->flags & COOKIE_FLAG_EXPIRES_SET)) {
+ CTrace((tfp, "not stored - no expiration time\n"));
+ continue;
+ } else if (co->expires <= now) {
+ CTrace((tfp, "not stored - EXPIRED\n"));
+ continue;
+ }
+
+ /* when we're sure we'll write to the file - open it */
+ if (cookie_handle == NULL) {
+ cookie_handle = NewCookieFile(cookie_file);
+ if (cookie_handle == NULL)
+ return;
+ }
+
+ fprintf(cookie_handle, "%s\t%s\t%s\t%s\t%" PRI_time_t
+ "\t%s\t%s%s%s\n",
+ de->ddomain,
+ (co->flags & COOKIE_FLAG_DOMAIN_SET) ? "TRUE" : "FALSE",
+ co->path,
+ (co->flags & COOKIE_FLAG_SECURE) ? "TRUE" : "FALSE",
+ CAST_time_t (co->expires), co->name,
+ (co->quoted ? "\"" : ""),
+ NonNull(co->value),
+ (co->quoted ? "\"" : ""));
+
+ CTrace((tfp, "STORED %s\n", de->ddomain));
+ }
+ }
+ if (cookie_handle != NULL) {
+ LYCloseOutput(cookie_handle);
+ HTSYS_purge(cookie_file);
+ }
+}
+#endif
+
+/*
+ * Check if the given string is completely US-ASCII. If so (and if the
+ * original were hex-encoded), it is likely to be more useful in a decoded
+ * form.
+ */
+static BOOLEAN valueNonAscii(const char *value)
+{
+ BOOLEAN result = FALSE;
+
+ while (*value != '\0') {
+ int ch = UCH(*value++);
+
+ if (ch < 32 || ch > 126) {
+ result = TRUE;
+ break;
+ }
+ }
+
+ return result;
+}
+
+/* LYHandleCookies - F.Macrides (macrides@sci.wfeb.edu)
+ * ---------------
+ *
+ * Lists all cookies by domain, and allows deletions of
+ * individual cookies or entire domains, and changes of
+ * 'allow' settings. The list is invoked via the COOKIE_JAR
+ * command (Ctrl-K), and deletions or changes of 'allow'
+ * settings are done by activating links in that list.
+ * The procedure uses a LYNXCOOKIE: internal URL scheme.
+ *
+ * Semantics:
+ * LYNXCOOKIE:/ Create and load the Cookie Jar Page.
+ * LYNXCOOKIE://domain Manipulate the domain.
+ * LYNXCOOKIE://domain/lynxID Delete cookie with lynxID in domain.
+ *
+ * New functions can be added as extensions to the path, and/or by
+ * assigning meanings to ;parameters, a ?searchpart, and/or #fragments.
+ */
+static int LYHandleCookies(const char *arg,
+ HTParentAnchor *anAnchor,
+ HTFormat format_out,
+ HTStream *sink)
+{
+ HTFormat format_in = WWW_HTML;
+ HTStream *target = NULL;
+ char *buf = NULL;
+ char *domain = NULL;
+ char *lynxID = NULL;
+ HTList *dl, *cl, *next;
+ domain_entry *de;
+ cookie *co;
+ char *name = NULL, *value = NULL, *path = NULL;
+ char *comment = NULL, *Address = NULL, *Title = NULL;
+ int ch;
+
+ /*
+ * Check whether we have something to do. - FM
+ */
+ if (HTList_isEmpty(domain_list)) {
+ HTProgress(COOKIE_JAR_IS_EMPTY);
+ LYSleepMsg();
+ HTNoDataOK = 1;
+ return (HT_NO_DATA);
+ }
+
+ /*
+ * If there's a domain string in the "host" field of the LYNXCOOKIE: URL,
+ * this is a request to delete something or change and 'allow' setting. -
+ * FM
+ */
+ if ((domain = HTParse(arg, "", PARSE_HOST)) != NULL) {
+ if (*domain == '\0') {
+ FREE(domain);
+ } else {
+ /*
+ * If there is a path string (not just a slash) in the LYNXCOOKIE:
+ * URL, that's a cookie's lynxID and this is a request to delete it
+ * from the Cookie Jar. - FM
+ */
+ if ((lynxID = HTParse(arg, "", PARSE_PATH)) != NULL) {
+ if (*lynxID == '\0') {
+ FREE(lynxID);
+ }
+ }
+ }
+ }
+ if (domain) {
+ /*
+ * Seek the domain in the domain_list structure. - FM
+ */
+ if ((de = find_domain_entry(domain)) != NULL) {
+ FREE(domain);
+ /*
+ * We found the domain. Check whether a lynxID is present. - FM
+ */
+ if (lynxID) {
+ /*
+ * Seek and delete the cookie with this lynxID in the domain's
+ * cookie list. - FM
+ */
+ for (cl = de->cookie_list; cl != NULL; cl = cl->next) {
+ if ((co = (cookie *) cl->object) == NULL)
+ /*
+ * First object is always empty. - FM
+ */
+ continue;
+ if (!strcmp(lynxID, co->lynxID)) {
+ /*
+ * We found the cookie. Delete it if confirmed. - FM
+ */
+ if (HTConfirm(DELETE_COOKIE_CONFIRMATION) == FALSE) {
+ FREE(lynxID);
+ HTNoDataOK = 1;
+ return (HT_NO_DATA);
+ }
+ HTList_removeObject(de->cookie_list, co);
+ freeCookie(co);
+ co = NULL;
+ total_cookies--;
+ if ((de->bv == QUERY_USER &&
+ HTList_isEmpty(de->cookie_list)) &&
+ HTConfirm(DELETE_EMPTY_DOMAIN_CONFIRMATION)) {
+ /*
+ * No more cookies in this domain, no default
+ * accept/reject choice was set by the user, and
+ * got confirmation on deleting the domain, so do
+ * it. - FM
+ */
+ freeCookies(de);
+ HTList_removeObject(domain_list, de);
+ FREE(de);
+ HTProgress(DOMAIN_EATEN);
+ } else {
+ HTProgress(COOKIE_EATEN);
+ }
+ LYSleepMsg();
+ HTNoDataOK = 1;
+ break;
+ }
+ }
+ } else {
+ /*
+ * Prompt whether to delete all of the cookies in this domain,
+ * or the domain if no cookies in it, or to change its 'allow'
+ * setting, or to cancel, and then act on the user's response.
+ * - FM
+ */
+ if (HTList_isEmpty(de->cookie_list)) {
+ _statusline(DELETE_DOMAIN_SET_ALLOW_OR_CANCEL);
+ } else {
+ _statusline(DELETE_COOKIES_SET_ALLOW_OR_CANCEL);
+ }
+ HTNoDataOK = 1;
+ while (1) {
+ ch = LYgetch_single();
+#ifdef VMS
+ if (HadVMSInterrupt) {
+ HadVMSInterrupt = FALSE;
+ ch = 'C';
+ }
+#endif /* VMS */
+ switch (ch) {
+ case 'A':
+ /*
+ * Set to accept all cookies from this domain. - FM
+ */
+ de->bv = ACCEPT_ALWAYS;
+ HTUserMsg2(ALWAYS_ALLOWING_COOKIES, de->ddomain);
+ return (HT_NO_DATA);
+
+ case 'C':
+ /*
+ * Cancelled. - FM
+ */
+ reject:
+ HTUserMsg(CANCELLED);
+ return (HT_NO_DATA);
+
+ case 'D':
+ if (HTList_isEmpty(de->cookie_list)) {
+ /*
+ * We had an empty domain, so we were asked to
+ * delete it. - FM
+ */
+ freeCookies(de);
+ HTList_removeObject(domain_list, de);
+ FREE(de);
+ HTProgress(DOMAIN_EATEN);
+ LYSleepMsg();
+ break;
+ }
+ Delete_all_cookies_in_domain:
+ /*
+ * Delete all cookies in this domain. - FM
+ */
+ cl = de->cookie_list;
+ while (cl) {
+ next = cl->next;
+ co = (cookie *) (cl->object);
+ if (co) {
+ HTList_removeObject(de->cookie_list, co);
+ freeCookie(co);
+ co = NULL;
+ total_cookies--;
+ }
+ cl = next;
+ }
+ HTProgress(DOMAIN_COOKIES_EATEN);
+ LYSleepMsg();
+ /*
+ * If a default accept/reject choice is set, we're
+ * done. - FM
+ */
+ if (de->bv != QUERY_USER)
+ return (HT_NO_DATA);
+ /*
+ * Check whether to delete the empty domain. - FM
+ */
+ if (HTConfirm(DELETE_EMPTY_DOMAIN_CONFIRMATION)) {
+ freeCookies(de);
+ HTList_removeObject(domain_list, de);
+ FREE(de);
+ HTProgress(DOMAIN_EATEN);
+ LYSleepMsg();
+ }
+ break;
+
+ case 'P':
+ /*
+ * Set to prompt for cookie acceptance from this
+ * domain. - FM
+ */
+ de->bv = QUERY_USER;
+ HTUserMsg2(PROMPTING_TO_ALLOW_COOKIES, de->ddomain);
+ return (HT_NO_DATA);
+
+ case 'V':
+ /*
+ * Set to reject all cookies from this domain. - FM
+ */
+ de->bv = REJECT_ALWAYS;
+ HTUserMsg2(NEVER_ALLOWING_COOKIES, de->ddomain);
+ if ((!HTList_isEmpty(de->cookie_list)) &&
+ HTConfirm(DELETE_ALL_COOKIES_IN_DOMAIN))
+ goto Delete_all_cookies_in_domain;
+ return (HT_NO_DATA);
+
+ default:
+ if (LYCharIsINTERRUPT(ch))
+ goto reject;
+ continue;
+ }
+ break;
+ }
+ }
+ }
+ if (HTList_isEmpty(domain_list)) {
+ /*
+ * There are no more domains left. Don't delete the domain_list,
+ * otherwise atexit may be called multiple times. - kw
+ */
+ HTProgress(ALL_COOKIES_EATEN);
+ LYSleepMsg();
+ }
+ FREE(domain);
+ FREE(lynxID);
+ return (HT_NO_DATA);
+ }
+
+ /*
+ * If we get to here, it was a LYNXCOOKIE:/ URL for creating and displaying
+ * the Cookie Jar Page, or we didn't find the domain or cookie in a
+ * deletion request. Set up an HTML stream and return an updated Cookie
+ * Jar Page. - FM
+ */
+ 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. - FM
+ */
+#define PUTS(buf) (*target->isa->put_block)(target, buf, (int) strlen(buf))
+
+ WriteStreamTitle(target, COOKIE_JAR_TITLE);
+ HTSprintf0(&buf, "<h1>%s (%s)%s<a href=\"%s%s\">%s</a></h1>\n",
+ LYNX_NAME, LYNX_VERSION,
+ HELP_ON_SEGMENT,
+ helpfilepath, COOKIE_JAR_HELP, COOKIE_JAR_TITLE);
+ PUTS(buf);
+
+ HTSprintf0(&buf, "<div><em>Note:</em> %s\n", ACTIVATE_TO_GOBBLE);
+ PUTS(buf);
+ HTSprintf0(&buf, "%s</div>\n", OR_CHANGE_ALLOW);
+ PUTS(buf);
+
+ HTSprintf0(&buf, "<dl compact>\n");
+ PUTS(buf);
+ for (dl = domain_list; dl != NULL; dl = dl->next) {
+ de = (domain_entry *) (dl->object);
+ if (de == NULL)
+ /*
+ * First object always is NULL. - FM
+ */
+ continue;
+
+ /*
+ * Show the domain link and 'allow' setting. - FM
+ */
+ HTSprintf0(&buf,
+ "<dt>%s<dd><a href=\"%s//%s/\"><em>Domain:</em> %s</a>\n",
+ de->ddomain, STR_LYNXCOOKIE, de->ddomain, de->ddomain);
+ PUTS(buf);
+ switch (de->bv) {
+ case (ACCEPT_ALWAYS):
+ HTSprintf0(&buf, COOKIES_ALWAYS_ALLOWED);
+ break;
+ case (REJECT_ALWAYS):
+ HTSprintf0(&buf, COOKIES_NEVER_ALLOWED);
+ break;
+ case (QUERY_USER):
+ HTSprintf0(&buf, COOKIES_ALLOWED_VIA_PROMPT);
+ break;
+ }
+ PUTS(buf);
+ HTSprintf0(&buf, "\n");
+ PUTS(buf);
+
+ /*
+ * Show the domain's cookies. - FM
+ */
+ for (cl = de->cookie_list; cl != NULL; cl = cl->next) {
+ if ((co = (cookie *) cl->object) == NULL)
+ /*
+ * First object is always NULL. - FM
+ */
+ continue;
+
+ /*
+ * Show the name=value pair. - FM
+ */
+ if (co->name) {
+ StrAllocCopy(name, co->name);
+ LYEntify(&name, TRUE);
+ } else {
+ StrAllocCopy(name, NO_NAME);
+ }
+ if (co->value) {
+ StrAllocCopy(value, co->value);
+ HTUnEscape(value);
+ if (valueNonAscii(value))
+ strcpy(value, co->value);
+ LYEntify(&value, TRUE);
+ } else {
+ StrAllocCopy(value, NO_VALUE);
+ }
+ HTSprintf0(&buf, "<dd><a href=\"%s//%s/%s\"><em>%s</em>=%s</a>\n",
+ STR_LYNXCOOKIE, de->ddomain, co->lynxID, name, value);
+ FREE(name);
+ FREE(value);
+ PUTS(buf);
+
+ if (co->flags & COOKIE_FLAG_FROM_FILE) {
+ HTSprintf0(&buf, "%s\n",
+ gettext("(from a previous session)"));
+ PUTS(buf);
+ }
+
+ /*
+ * Show the path, port, secure and discard setting. - FM
+ */
+ if (co->path) {
+ StrAllocCopy(path, co->path);
+ LYEntify(&path, TRUE);
+ } else {
+ StrAllocCopy(path, "/");
+ }
+ HTSprintf0(&buf,
+ "<dd><em>Path:</em> %s\n<dd><em>Port:</em> %d <em>Secure:</em> %s <em>Discard:</em> %s\n",
+ path, co->port,
+ ((co->flags & COOKIE_FLAG_SECURE) ? "YES" : "NO"),
+ ((co->flags & COOKIE_FLAG_DISCARD) ? "YES" : "NO"));
+ FREE(path);
+ PUTS(buf);
+
+ /*
+ * Show the list of acceptable ports, if present. - FM
+ */
+ if (co->PortList) {
+ HTSprintf0(&buf, "<dd><em>PortList:</em> \"%s\"\n", co->PortList);
+ PUTS(buf);
+ }
+
+ /*
+ * Show the commentURL, if we have one. - FM
+ */
+ if (co->commentURL) {
+ StrAllocCopy(Address, co->commentURL);
+ LYEntify(&Address, FALSE);
+ StrAllocCopy(Title, co->commentURL);
+ LYEntify(&Title, TRUE);
+ HTSprintf0(&buf,
+ "<dd><em>CommentURL:</em> <a href=\"%s\">%s</a>\n",
+ Address,
+ Title);
+ FREE(Address);
+ FREE(Title);
+ PUTS(buf);
+ }
+
+ /*
+ * Show the comment, if we have one. - FM
+ */
+ if (co->comment) {
+ StrAllocCopy(comment, co->comment);
+ LYEntify(&comment, TRUE);
+ HTSprintf0(&buf, "<dd><em>Comment:</em> %s\n", comment);
+ FREE(comment);
+ PUTS(buf);
+ }
+
+ /*
+ * Show the Maximum Gobble Date. - FM
+ */
+ HTSprintf0(&buf, "<dd><em>%s</em> %s%s",
+ gettext("Maximum Gobble Date:"),
+ ((co->flags & COOKIE_FLAG_EXPIRES_SET)
+ ?
+ ctime(&co->expires) : END_OF_SESSION),
+ ((co->flags & COOKIE_FLAG_EXPIRES_SET)
+ ?
+ "" : "\n"));
+ PUTS(buf);
+ }
+ HTSprintf0(&buf, "\n");
+ PUTS(buf);
+ }
+ HTSprintf0(&buf, "</dl>\n</body>\n</html>\n");
+ PUTS(buf);
+
+ /*
+ * Free the target to complete loading of the Cookie Jar Page, and report a
+ * successful load. - FM
+ */
+ (*target->isa->_free) (target);
+ FREE(buf);
+ return (HT_LOADED);
+}
+
+/* cookie_domain_flag_set
+ * ----------------------
+ * All purpose function to handle setting domain flags for a
+ * comma-delimited list of domains. cookie_domain_flags handles
+ * invcheck behavior, as well as accept/reject behavior. - BJP
+ */
+static void cookie_domain_flag_set(char *domainstr,
+ int flag)
+{
+ domain_entry *de = NULL;
+ char **str = typecalloc(char *);
+ char *dstr = NULL;
+ char *strsmall = NULL;
+
+ if (str == NULL) {
+ HTAlwaysAlert(gettext("Internal"),
+ gettext("cookie_domain_flag_set error, aborting program"));
+ exit_immediately(EXIT_FAILURE);
+ }
+
+ /*
+ * Is this the first domain we're handling? If so, initialize domain_list.
+ */
+ if (domain_list == NULL) {
+#ifdef LY_FIND_LEAKS
+ atexit(LYCookieJar_free);
+#endif
+ domain_list = HTList_new();
+ total_cookies = 0;
+ }
+
+ StrAllocCopy(dstr, domainstr);
+
+ *str = dstr;
+
+ while ((strsmall = LYstrsep(str, ",")) != 0) {
+
+ if (*strsmall == '\0')
+ /* Never add a domain for empty string. It would actually
+ * make more sense to use strtok here. - kw */
+ continue;
+
+ /*
+ * Check the list of existing domains to see if this is a
+ * re-setting of an already existing domain -- if so, just
+ * change the behavior, if not, create a new domain entry.
+ */
+
+ if ((de = find_domain_entry(strsmall)) == NULL) {
+ de = typecalloc(domain_entry);
+ if (de == NULL)
+ outofmem(__FILE__, "cookie_domain_flag_set");
+
+ de->bv = ACCEPT_ALWAYS;
+ de->invcheck_bv = INVCHECK_QUERY;
+
+ switch (flag) {
+ case (FLAG_ACCEPT_ALWAYS):
+ de->invcheck_bv = DEFAULT_INVCHECK_BV;
+ break;
+ case (FLAG_REJECT_ALWAYS):
+ de->invcheck_bv = DEFAULT_INVCHECK_BV;
+ break;
+ case (FLAG_QUERY_USER):
+ de->invcheck_bv = DEFAULT_INVCHECK_BV;
+ break;
+ case (FLAG_INVCHECK_QUERY):
+ de->bv = QUERY_USER;
+ break;
+ case (FLAG_INVCHECK_STRICT):
+ de->bv = QUERY_USER;
+ break;
+ case (FLAG_INVCHECK_LOOSE):
+ de->bv = QUERY_USER;
+ break;
+ }
+
+ StrAllocCopy(de->domain, strsmall);
+ StrAllocCopy(de->ddomain, SkipLeadingDot(strsmall));
+ de->cookie_list = HTList_new();
+ HTList_appendObject(domain_list, de);
+ }
+ switch (flag) {
+ case (FLAG_ACCEPT_ALWAYS):
+ de->bv = ACCEPT_ALWAYS;
+ break;
+ case (FLAG_REJECT_ALWAYS):
+ de->bv = REJECT_ALWAYS;
+ break;
+ case (FLAG_QUERY_USER):
+ de->bv = QUERY_USER;
+ break;
+ case (FLAG_INVCHECK_QUERY):
+ de->invcheck_bv = INVCHECK_QUERY;
+ break;
+ case (FLAG_INVCHECK_STRICT):
+ de->invcheck_bv = INVCHECK_STRICT;
+ break;
+ case (FLAG_INVCHECK_LOOSE):
+ de->invcheck_bv = INVCHECK_LOOSE;
+ break;
+ }
+ CTrace((tfp,
+ "cookie_domain_flag_set (%s, bv=%u, invcheck_bv=%u)\n",
+ strsmall, de->bv, de->invcheck_bv));
+ }
+
+ FREE(strsmall);
+ FREE(str);
+ FREE(dstr);
+}
+
+/*
+ * If any COOKIE_{ACCEPT,REJECT}_DOMAINS have been defined, process them.
+ * These are comma delimited lists of domains. - BJP
+ *
+ * And for query/strict/loose invalid cookie checking. - BJP
+ */
+void LYConfigCookies(void)
+{
+ static const struct {
+ char **domain;
+ int flag;
+ int once;
+ } table[] = {
+ /* *INDENT-OFF* */
+ { &LYCookieSAcceptDomains, FLAG_ACCEPT_ALWAYS, TRUE },
+ { &LYCookieSRejectDomains, FLAG_REJECT_ALWAYS, TRUE },
+ { &LYCookieSStrictCheckDomains, FLAG_INVCHECK_STRICT, TRUE },
+ { &LYCookieSLooseCheckDomains, FLAG_INVCHECK_LOOSE, TRUE },
+ { &LYCookieSQueryCheckDomains, FLAG_INVCHECK_QUERY, TRUE },
+ { &LYCookieAcceptDomains, FLAG_ACCEPT_ALWAYS, FALSE },
+ { &LYCookieRejectDomains, FLAG_REJECT_ALWAYS, FALSE },
+ { &LYCookieStrictCheckDomains, FLAG_INVCHECK_STRICT, FALSE },
+ { &LYCookieLooseCheckDomains, FLAG_INVCHECK_LOOSE, FALSE },
+ { &LYCookieQueryCheckDomains, FLAG_INVCHECK_QUERY, FALSE },
+ /* *INDENT-ON* */
+
+ };
+ unsigned n;
+
+ CTrace((tfp, "LYConfigCookies\n"));
+ for (n = 0; n < TABLESIZE(table); n++) {
+ if (*(table[n].domain) != NULL) {
+ cookie_domain_flag_set(*(table[n].domain), table[n].flag);
+ /*
+ * Discard the value for system settings after we've used them.
+ * The local settings will be merged with the contents of .lynxrc
+ */
+ if (table[n].once) {
+ FREE(*(table[n].domain));
+ }
+ }
+ }
+}
+
+#ifdef GLOBALDEF_IS_MACRO
+#define _LYCOOKIE_C_GLOBALDEF_1_INIT { "LYNXCOOKIE",LYHandleCookies,0}
+GLOBALDEF(HTProtocol, LYLynxCookies, _LYCOOKIE_C_GLOBALDEF_1_INIT);
+#else
+GLOBALDEF HTProtocol LYLynxCookies =
+{"LYNXCOOKIE", LYHandleCookies, 0};
+#endif /* GLOBALDEF_IS_MACRO */
diff --git a/src/LYCookie.h b/src/LYCookie.h
new file mode 100644
index 0000000..ecbf930
--- /dev/null
+++ b/src/LYCookie.h
@@ -0,0 +1,59 @@
+/* $LynxId: LYCookie.h,v 1.20 2011/06/07 08:29:39 tom Exp $ */
+#ifndef LYCOOKIES_H
+#define LYCOOKIES_H
+
+#ifndef HTUTILS_H
+#include <HTUtils.h>
+#endif
+
+#include <HTList.h>
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+ typedef enum {
+ ACCEPT_ALWAYS = 0
+ ,REJECT_ALWAYS
+ ,QUERY_USER
+ } behaviour_t;
+
+ typedef enum {
+ INVCHECK_QUERY = 0
+ ,INVCHECK_STRICT
+ ,INVCHECK_LOOSE
+ } invcheck_behaviour_t;
+
+ typedef enum {
+ FLAG_ACCEPT_ALWAYS = 0
+ ,FLAG_REJECT_ALWAYS
+ ,FLAG_QUERY_USER
+ ,FLAG_FROM_FILE
+ ,FLAG_INVCHECK_QUERY
+ ,FLAG_INVCHECK_STRICT
+ ,FLAG_INVCHECK_LOOSE
+ } cookie_domain_flags;
+
+ struct _domain_entry {
+ char *domain; /* Domain for which these cookies are valid */
+ char *ddomain; /* Domain without leading "." */
+ behaviour_t bv;
+ invcheck_behaviour_t invcheck_bv;
+ HTList *cookie_list;
+ };
+ typedef struct _domain_entry domain_entry;
+
+ extern void LYSetCookie(const char *SetCookie,
+ const char *SetCookie2,
+ const char *address);
+ extern char *LYAddCookieHeader(char *hostname,
+ char *partialpath,
+ int port,
+ int secure);
+ extern void LYStoreCookies(char *cookie_file);
+ extern void LYLoadCookies(char *cookie_file);
+ extern void LYConfigCookies(void);
+
+#ifdef __cplusplus
+}
+#endif
+#endif /* LYCOOKIES_H */
diff --git a/src/LYCurses.c b/src/LYCurses.c
new file mode 100644
index 0000000..d1ad337
--- /dev/null
+++ b/src/LYCurses.c
@@ -0,0 +1,3307 @@
+/* $LynxId: LYCurses.c,v 1.207 2024/01/09 00:30:44 tom Exp $ */
+#include <HTUtils.h>
+#include <HTAlert.h>
+
+#ifdef __MINGW32__
+#ifdef UNIX
+#undef UNIX
+#endif /* UNIX */
+#endif /* __MINGW32__ */
+
+#ifdef __DJGPP__
+#include <pc.h>
+#endif /* __DJGPP__ */
+
+#include <LYCurses.h>
+#include <LYStyle.h>
+#include <LYUtils.h>
+#include <LYGlobalDefs.h>
+#include <LYSignal.h>
+#include <LYClean.h>
+#include <LYReadCFG.h>
+#include <LYStrings.h>
+#include <LYCharSets.h>
+#include <UCAux.h>
+#include <HTFont.h>
+
+#include <LYexit.h>
+#include <LYLeaks.h>
+
+#ifdef VMS
+#include <LYMainLoop.h>
+#endif
+
+#if defined(VMS) && defined(__GNUC__)
+#include <gnu_hacks.h>
+#undef LINES
+#undef COLS
+#define LINES lines
+#define COLS cols
+extern int _NOSHARE(LINES);
+extern int _NOSHARE(COLS);
+#endif /* VMS && __GNUC__ */
+
+#ifdef USE_COLOR_STYLE
+#include <AttrList.h>
+#include <LYHash.h>
+#endif
+
+#ifdef NEED_WCHAR_H
+#include <wchar.h>
+#endif
+
+#if defined(COLOR_CURSES)
+int lynx_has_color = FALSE;
+#endif
+
+#ifdef HAVE_XCURSES
+char *XCursesProgramName = "Lynx";
+#endif
+
+#ifdef PDCURSES
+#undef HAVE_NEWTERM /* not needed, since /dev/tty is unused */
+#endif
+
+#if defined(USE_COLOR_STYLE) && !defined(USE_COLOR_TABLE)
+#define COLOR_BKGD ((s_normal != NOSTYLE) ? hashStyles[s_normal].color : A_NORMAL)
+#else
+#define COLOR_BKGD ((COLOR_PAIRS >= 9) ? (chtype) get_color_pair(9) : A_NORMAL)
+#endif
+
+#ifdef USE_CURSES_PADS
+WINDOW *LYwin = 0;
+int LYshiftWin = 0;
+int LYwideLines = FALSE;
+int LYtableCols = 0; /* in 1/12 of screen width */
+BOOLEAN LYuseCursesPads = TRUE; /* use pads for left/right shifting */
+#endif
+
+/*
+ * These are routines to start and stop curses and to cleanup the screen at the
+ * end.
+ */
+
+static int dumbterm(char *terminal);
+BOOLEAN LYCursesON = FALSE;
+
+#if defined(USE_BLINK) && defined(__EMX__)
+static void make_blink_boldbg(void);
+#endif
+
+#if defined(USE_COLOR_TABLE) || defined(USE_SLANG)
+int Current_Attr;
+static int Masked_Attr;
+#endif
+
+#ifdef USE_SLANG
+unsigned Lynx_Color_Flags = 0;
+BOOLEAN FullRefresh = FALSE;
+int curscr = 0;
+
+void LY_SLrefresh(void)
+{
+ if (FullRefresh) {
+ SLsmg_suspend_smg();
+ SLsmg_resume_smg();
+ FullRefresh = FALSE;
+ } else {
+ SLsmg_refresh();
+ }
+
+ return;
+}
+
+/* the following renamed from LY_SLclear since it is more like erase()
+ described in curses man pages than like clear(); but for USE_SLANG
+ clear() is still a macro calling this, and will do the same thing as
+ erase(). - kw */
+void LY_SLerase(void)
+{
+ SLsmg_gotorc(0, 0);
+ SLsmg_erase_eos();
+}
+
+#ifdef VMS
+void VTHome(void)
+{
+ printf("\033[;H");
+
+ return;
+}
+#endif /* VMS */
+
+void LYaddAttr(int a)
+{
+ Current_Attr |= a;
+ SLsmg_set_color((SLsmg_Color_Type) (Current_Attr & ~Masked_Attr));
+}
+
+void LYsubAttr(int a)
+{
+ Current_Attr &= ~a;
+ SLsmg_set_color((SLsmg_Color_Type) (Current_Attr & ~Masked_Attr));
+}
+
+static void lynx_setup_attrs(void)
+{
+ static int monoattr[] =
+ {
+ 0,
+ SLTT_BOLD_MASK,
+ SLTT_REV_MASK,
+ SLTT_REV_MASK | SLTT_BOLD_MASK,
+ SLTT_ULINE_MASK,
+ SLTT_ULINE_MASK | SLTT_BOLD_MASK,
+ SLTT_ULINE_MASK | SLTT_REV_MASK,
+ SLTT_ULINE_MASK | SLTT_BOLD_MASK | SLTT_REV_MASK
+ };
+ int n;
+
+ for (n = 1; n <= 7; n++)
+ SLtt_set_mono(n, NULL, (SLtt_Char_Type) (monoattr[n] & ~Masked_Attr));
+}
+
+void lynx_setup_colors(void)
+{
+ CTRACE((tfp, "lynx_setup_colors\n"));
+ SLtt_set_color(0, NULL, DEFAULT_FG, DEFAULT_BG);
+ SLtt_set_color(1, NULL, "blue", DEFAULT_BG); /* bold */
+ SLtt_set_color(2, NULL, "yellow", "blue"); /* reverse */
+ SLtt_set_color(4, NULL, "magenta", DEFAULT_BG); /* underline */
+ /*
+ * The other objects are '|'ed together to get rest.
+ */
+ SLtt_set_color(3, NULL, "green", DEFAULT_BG); /* bold-reverse */
+ SLtt_set_color(5, NULL, "blue", DEFAULT_BG); /* bold-underline */
+ SLtt_set_color(6, NULL, "red", DEFAULT_BG); /* reverse-underline */
+ SLtt_set_color(7, NULL, "magenta", "cyan"); /* reverse-underline-bold */
+ /*
+ * Now set monochrome attributes.
+ */
+ lynx_setup_attrs();
+}
+
+static void sl_suspend(int sig)
+{
+#ifdef SIGSTOP
+#ifndef VMS
+ int r, c;
+
+ lynx_enable_mouse(0);
+ if (sig == SIGTSTP)
+ SLsmg_suspend_smg();
+ SLang_reset_tty();
+ kill(getpid(), SIGSTOP);
+#if SLANG_VERSION > 9929
+ SLang_init_tty(-1, 0, 1);
+#else
+ SLang_init_tty(3, 0, 1);
+#endif /* SLANG_VERSION > 9929 */
+ signal(SIGTSTP, sl_suspend);
+#if defined(REAL_UNIX_SYSTEM) && !defined(__CYGWIN__)
+ SLtty_set_suspend_state(1);
+#endif
+ if (sig == SIGTSTP)
+ SLsmg_resume_smg();
+ /*
+ * Get new window size in case it changed.
+ */
+ r = SLtt_Screen_Rows;
+ c = SLtt_Screen_Cols;
+ size_change(0);
+ LYGetScreenSize(0);
+ if ((r != SLtt_Screen_Rows) || (c != SLtt_Screen_Cols)) {
+ recent_sizechange = TRUE;
+ }
+ lynx_enable_mouse(1);
+#endif /* !VMS */
+#endif /* SIGSTOP */
+ return;
+}
+#else
+
+#ifdef FANCY_CURSES
+
+#ifndef VMS
+/* *INDENT-OFF* */
+/* definitions for the mono attributes we can use */
+static struct {
+ const char *name;
+ int code;
+} Mono_Attrs[7] =
+{
+ { "normal", A_NORMAL },
+ { "bold", A_BOLD },
+ { "reverse", A_REVERSE },
+ { "underline", A_UNDERLINE },
+ { "standout", A_STANDOUT },
+ { "blink", A_BLINK },
+ { "dim", A_DIM },
+};
+/* *INDENT-ON* */
+
+int string_to_attr(const char *name)
+{
+ unsigned i;
+
+ for (i = 0; i < TABLESIZE(Mono_Attrs); i++) {
+ if (!strcasecomp(Mono_Attrs[i].name, name)) {
+ return Mono_Attrs[i].code;
+ }
+ }
+ return 0;
+}
+#endif /* VMS */
+
+#ifdef USE_COLOR_STYLE
+static char *attr_to_string(int code)
+{
+ static char *result;
+
+ if (code >= 0) {
+ unsigned i;
+ int pair = PAIR_NUMBER((unsigned) code);
+ int bold = (pair != 0 && ((unsigned) code & A_BOLD) != 0);
+
+ StrAllocCopy(result, "");
+
+ if (bold)
+ code &= (int) ~A_BOLD;
+
+ for (i = 0; i < TABLESIZE(Mono_Attrs); i++) {
+ if (Mono_Attrs[i].code & code) {
+ if (non_empty(result))
+ StrAllocCat(result, "+");
+ StrAllocCat(result, Mono_Attrs[i].name);
+ }
+ }
+ if (pair != 0) {
+ short f, b;
+
+ if (pair_content((short) pair, &f, &b) != ERR) {
+ if (non_empty(result))
+ StrAllocCat(result, "+");
+ StrAllocCat(result, lookup_color(bold ? f + COLORS : f));
+ StrAllocCat(result, "/");
+ StrAllocCat(result, lookup_color(b));
+ }
+ }
+ } else {
+ FREE(result);
+ }
+ return result;
+}
+#endif /* USE_COLOR_STYLE */
+#endif /* FANCY_CURSES */
+#endif /* USE_SLANG */
+
+/*
+ * This function boxes windows for (n)curses.
+ */
+void LYbox(WINDOW * win, int formfield GCC_UNUSED)
+{
+#ifdef USE_SLANG
+ SLsmg_draw_box(win->top_y,
+ win->left_x,
+ (unsigned) win->height,
+ (unsigned) win->width + 4);
+#else
+#ifdef VMS
+ /*
+ * This should work for VAX-C and DEC-C, since they both have the same
+ * win._max_y and win._max_x members -TD
+ *
+ * (originally VMSbox by FM)
+ */
+ int i;
+
+ wmove(win, 0, 0);
+ waddstr(win, "\033)0\016l");
+ for (i = 1; i < win->_max_x; i++)
+ waddch(win, 'q');
+ waddch(win, 'k');
+ for (i = 1; i < win->_max_y - 1; i++) {
+ wmove(win, i, 0);
+ waddch(win, 'x');
+ wmove(win, i, win->_max_x - 1);
+ waddch(win, 'x');
+ }
+ wmove(win, i, 0);
+ waddch(win, 'm');
+ for (i = 1; i < win->_max_x; i++)
+ waddch(win, 'q');
+ waddstr(win, "j\017");
+#else /* !VMS */
+ int boxvert, boxhori;
+
+ UCSetBoxChars(current_char_set, &boxvert, &boxhori, BOXVERT, BOXHORI);
+#ifdef CSS
+ if (formfield)
+ wcurses_css(win, "frame", ABS_ON);
+#endif
+ /*
+ * If we don't have explicitly specified characters for either vertical or
+ * horizontal lines, the characters that box() would use for the corners
+ * probably also won't work well. So we specify our own ASCII characters
+ * for the corners and call wborder() instead of box(). - kw
+ */
+ LynxWChangeStyle(win, s_menu_frame, STACK_ON);
+#ifdef HAVE_WBORDER
+ if (!boxvert || !boxhori) {
+ box(win,
+ (chtype) boxvert,
+ (chtype) boxhori);
+ } else if (boxvert == '*' || boxhori == '*') {
+ wborder(win,
+ (chtype) boxvert,
+ (chtype) boxvert,
+ (chtype) boxhori,
+ (chtype) boxhori,
+ '*', '*', '*', '*');
+ } else {
+ wborder(win,
+ (chtype) boxvert,
+ (chtype) boxvert,
+ (chtype) boxhori,
+ (chtype) boxhori,
+ '/', '\\', '\\', '/');
+ }
+#else
+ box(win, boxvert, boxhori);
+#endif
+ LynxWChangeStyle(win, s_menu_frame, STACK_OFF);
+#ifdef CSS
+ if (formfield)
+ wcurses_css(win, "frame", ABS_OFF);
+#endif
+#endif /* VMS */
+ wrefresh(win);
+#endif /* USE_SLANG */
+}
+
+#if defined(USE_COLOR_STYLE)
+/* Ok, explanation of the USE_COLOR_STYLE styles. The basic styles (ie non
+ * HTML) are set the same as the SLANG version for ease of programming. The
+ * other styles are simply the HTML enum from HTMLDTD.h + 16.
+ */
+HTCharStyle displayStyles[DSTYLE_ELEMENTS];
+
+/*
+ * set a style's attributes - RP
+ */
+void setStyle(int style,
+ int color,
+ int cattr,
+ int mono)
+{
+ displayStyles[style].color = color;
+ displayStyles[style].cattr = cattr;
+ displayStyles[style].mono = mono;
+}
+
+void setHashStyle(int style,
+ int color,
+ int cattr,
+ int mono,
+ const char *element)
+{
+ bucket *ds = &hashStyles[style];
+
+ CTRACE2(TRACE_STYLE,
+ (tfp, "CSS(SET): <%s> hash=%d, ca=%#x, ma=%#x\n",
+ element, style, (unsigned) color, (unsigned) mono));
+
+ ds->used = TRUE;
+ ds->color = color;
+ ds->cattr = cattr;
+ ds->mono = mono;
+}
+
+/*
+ * set the curses attributes to be color or mono - RP
+ */
+static void LYAttrset(WINDOW * win, int color,
+ int mono)
+{
+ char *shown = NULL;
+
+ if (lynx_has_color
+ && LYShowColor >= SHOW_COLOR_ON
+ && color >= 0) {
+ CTRACE2(TRACE_STYLE, (tfp, "CSS:LYAttrset color %#x -> (%s)\n",
+ (unsigned) color,
+ shown = attr_to_string(color)));
+ (void) wattrset(win, color);
+ } else if (mono >= 0) {
+ CTRACE2(TRACE_STYLE, (tfp, "CSS:LYAttrset mono %#x -> (%s)\n",
+ (unsigned) mono,
+ shown = attr_to_string(mono)));
+ (void) wattrset(win, mono);
+ } else {
+ CTRACE2(TRACE_STYLE, (tfp, "CSS:LYAttrset (A_NORMAL)\n"));
+ (void) wattrset(win, A_NORMAL);
+ }
+ if (shown != NULL)
+ (void) attr_to_string(-1);
+}
+
+void curses_w_style(WINDOW * win, int style,
+ int dir)
+{
+ int YP, XP;
+ bucket *ds;
+ BOOL free_ds = TRUE;
+
+ switch (style) {
+ case NOSTYLE:
+ ds = nostyle_bucket();
+ break;
+ default:
+ ds = &hashStyles[style];
+ free_ds = FALSE;
+ break;
+ }
+
+ if (!ds->used) {
+ CTRACE2(TRACE_STYLE, (tfp, "CSS.CS:Style %d not configured\n", style));
+ if (free_ds)
+ free(ds);
+ return;
+ }
+
+ CTRACE2(TRACE_STYLE, (tfp, "CSS.CS:<%s%s> style %d color %#x\n",
+ (dir ? "" : "/"),
+ ds->name,
+ style,
+ (unsigned) ds->color));
+
+ getyx(win, YP, XP);
+
+ if (style == s_normal && dir) {
+ LYAttrset(win, ds->color, ds->mono);
+ if (win == LYwin)
+ SetCachedStyle(YP, XP, (unsigned) s_normal);
+ if (free_ds)
+ free(ds);
+ return;
+ }
+
+ switch (dir) {
+ /* ABS_OFF is the same as STACK_OFF for the moment */
+ case STACK_OFF:
+ if (last_colorattr_ptr) {
+ int last_attr = last_styles[--last_colorattr_ptr];
+
+ LYAttrset(win, last_attr, last_attr);
+ } else
+ LYAttrset(win, A_NORMAL, -1);
+ break;
+
+ case STACK_ON: /* remember the current attributes */
+ if (last_colorattr_ptr >= MAX_LAST_STYLES) {
+ CTRACE2(TRACE_STYLE, (tfp, "........... %s (0x%x) %s\r\n",
+ "attribute cache FULL, dropping last",
+ (unsigned) last_styles[last_colorattr_ptr],
+ "in LynxChangeStyle(curses_w_style)"));
+ last_colorattr_ptr = MAX_LAST_STYLES - 1;
+ }
+ last_styles[last_colorattr_ptr++] = (int) LYgetattrs(win);
+ /* don't cache style changes for active links */
+ /* FALL THROUGH */
+ case ABS_ON: /* change without remembering the previous style */
+ /* don't cache style changes for active links and edits */
+ if (style != s_alink
+ && style != s_curedit
+ && style != s_aedit
+ && style != s_aedit_sel
+ && style != s_aedit_pad
+ && style != s_aedit_arr) {
+ CTRACE2(TRACE_STYLE, (tfp, "CACHED: <%s> @(%d,%d)\n",
+ ds->name, YP, XP));
+ if (win == LYwin)
+ SetCachedStyle(YP, XP, (unsigned) style);
+ }
+ LYAttrset(win, ds->color, ds->mono);
+ break;
+ }
+
+ if (free_ds)
+ free(ds);
+
+ return;
+}
+
+/*
+ * wrapper function to set on-screen styles - RP
+ */
+void wcurses_css(WINDOW * win, char *name,
+ int dir)
+{
+ int try_again = 1;
+
+ while (try_again) {
+ int tmpHash = color_style_1(name);
+
+ CTRACE2(TRACE_STYLE, (tfp, "CSSTRIM:trying to set [%s] style - ", name));
+ if (tmpHash == NOSTYLE) {
+ char *pclass = strrchr(name, '.');
+
+ CTRACE2(TRACE_STYLE, (tfp, "undefined, trimming at %p\n", (void *) pclass));
+ if (pclass)
+ *pclass = '\0';
+ else
+ try_again = 0;
+ } else {
+ CTRACE2(TRACE_STYLE, (tfp, "ok (%d)\n", tmpHash));
+ curses_w_style(win, tmpHash, dir);
+ try_again = 0;
+ }
+ }
+}
+
+void curses_css(char *name,
+ int dir)
+{
+ wcurses_css(LYwin, name, dir);
+}
+
+void curses_style(int style,
+ int dir)
+{
+ curses_w_style(LYwin, style, dir);
+}
+#endif /* USE_COLOR_STYLE */
+
+static BOOL lynx_called_initscr = FALSE;
+
+#if defined(USE_COLOR_TABLE) && defined(COLOR_CURSES)
+#define COLOR_CFG_MAX 8
+
+/*
+ * This block of code is designed to produce the same color effects using SVr4
+ * curses as the slang library's implementation in this module. That maps the
+ * SGR codes into a 0-7 index into the color table, with special treatment for
+ * backgrounds. There's a bit of convoluted (but necessary) code handling the
+ * special case of initialization before 'initscr()' is called.
+ * 1997/1/19 - T.E.Dickey <dickey@clark.net>
+ */
+/* *INDENT-OFF* */
+#define COLOR_CFG(c) c, (c) == DEFAULT_COLOR
+static struct {
+ int fg, dft_fg, bg, dft_bg;
+} lynx_color_cfg[] = {
+ /*0*/ { COLOR_CFG(DEFAULT_FG), COLOR_CFG(DEFAULT_BG)},
+ /*1*/ { COLOR_CFG(COLOR_BLUE), COLOR_CFG(DEFAULT_BG)},
+ /*2*/ { COLOR_CFG((COLOR_YELLOW)+8), COLOR_CFG(COLOR_BLUE)},
+ /*3*/ { COLOR_CFG(COLOR_GREEN), COLOR_CFG(DEFAULT_BG)},
+ /*4*/ { COLOR_CFG(COLOR_MAGENTA), COLOR_CFG(DEFAULT_BG)},
+ /*5*/ { COLOR_CFG(COLOR_BLUE), COLOR_CFG(DEFAULT_BG)},
+ /*6*/ { COLOR_CFG(COLOR_RED), COLOR_CFG(DEFAULT_BG)},
+ /*7*/ { COLOR_CFG(COLOR_MAGENTA), COLOR_CFG(COLOR_CYAN)}
+};
+/* *INDENT-ON* */
+
+#define COLOR_PAIRS_MAX (COLOR_CFG_MAX * 3 + 1)
+
+/*
+ * Hold the codes for color-pairs here until 'initscr()' is called.
+ */
+static struct {
+ int fg;
+ int bg;
+} lynx_color_pairs[COLOR_PAIRS_MAX];
+
+/*
+ * If we find an exact match for the given default colors, force curses to use
+ * color pair 0, which corresponds to the terminal's default colors. Normally
+ * curses assumes white-on-black, but we can override the assumption with this
+ * function.
+ */
+static int get_color_pair(int n)
+{
+#ifdef USE_CURSES_PAIR_0
+ if ((n < (int) TABLESIZE(lynx_color_pairs))
+ && lynx_color_pairs[n].fg == default_fg
+ && lynx_color_pairs[n].bg == default_bg)
+ return 0;
+#endif
+ return (int) COLOR_PAIR(n);
+}
+
+/*
+ * Lynx "knows" about 16 colors. ANSI colors (and most color terminal
+ * emulators) only go to 8, though some curses implementations (ncurses and
+ * PDCurses) handle 16. If lynx's configuration calls for a color past the
+ * number of colors that the terminal handles (COLORS), map the extra value
+ * to bold.
+ */
+#define is_boldc(c) ((c) > (COLORS-1))
+#define map2bold(c) (is_boldc(c) ? ((c) & (COLORS-1)) : (c))
+
+/*
+ * Return the extra color as A_BOLD.
+ * If there is no extra color, return A_NORMAL.
+ */
+static int lynx_color_cfg_attr(int code)
+{
+ int result = A_NORMAL;
+
+ if (code >= 0 && code < COLOR_CFG_MAX) {
+ int fg = lynx_color_cfg[code].fg;
+
+ if (is_boldc(fg) && (fg & COLORS))
+ result = A_BOLD;
+ }
+ return result;
+}
+
+static int encode_color_attr(int color_attr)
+{
+ int result;
+ int code = 0;
+ int offs = 1;
+
+ if ((unsigned) color_attr & A_BOLD)
+ code |= 1;
+ if ((unsigned) color_attr & (A_REVERSE | A_DIM))
+ code |= 2;
+ if ((unsigned) color_attr & A_UNDERLINE)
+ code |= 4;
+ result = lynx_color_cfg_attr(code);
+
+ if (code + offs < COLOR_PAIRS) {
+ result |= get_color_pair(code + offs);
+ }
+ return result;
+}
+
+static int decode_mono_code(int mono_code)
+{
+ unsigned result = 0;
+
+ if (mono_code & 1)
+ result |= A_BOLD;
+ if (mono_code & 2)
+ result |= A_REVERSE;
+ if (mono_code & 4)
+ result |= A_UNDERLINE;
+
+ return (int) result;
+}
+
+/*
+ * Map the SGR attributes (0-7) into ANSI colors, modified with the actual BOLD
+ * attribute to get 16 colors.
+ */
+int LYgetTableAttr(void)
+{
+ int result;
+
+ if (lynx_has_color && LYShowColor >= SHOW_COLOR_ON) {
+ result = encode_color_attr(Current_Attr);
+ } else {
+ result = Current_Attr;
+ }
+ return result & ~Masked_Attr;
+}
+
+#ifdef USE_COLOR_STYLE
+/*
+ * Return a string that corresponds to the attributes that would be returned by
+ * LYgetTableAttr().
+ */
+char *LYgetTableString(int code)
+{
+ int mask = decode_mono_code(code);
+ int second = encode_color_attr(mask);
+ int pair = PAIR_NUMBER((unsigned) second);
+ int mono = (int) ((unsigned) mask & A_ATTRIBUTES);
+ int fg = lynx_color_pairs[pair].fg;
+ int bg = lynx_color_pairs[pair].bg;
+ unsigned n;
+ char *result = 0;
+
+ CTRACE((tfp, "LYgetTableString(%d)\n", code));
+
+ if (fg == 0 && bg == 0) {
+ fg = COLOR_WHITE;
+ }
+
+ CTRACE((tfp, "%#x -> %#x (mono %#x pair %d) fg=%d, bg=%d\n",
+ (unsigned) mask,
+ (unsigned) second,
+ (unsigned) mono,
+ pair, fg, bg));
+
+ for (n = 0; n < TABLESIZE(Mono_Attrs); ++n) {
+ if ((Mono_Attrs[n].code & mono) != 0) {
+ if (result != 0)
+ StrAllocCat(result, "+");
+ StrAllocCat(result, Mono_Attrs[n].name);
+ }
+ }
+ if (result == 0)
+ StrAllocCopy(result, "normal");
+ StrAllocCat(result, ":");
+ StrAllocCat(result, lookup_color(fg));
+ if (bg >= 0) {
+ StrAllocCat(result, ":");
+ StrAllocCat(result, lookup_color(bg));
+ }
+ CTRACE((tfp, "->%s\n", result));
+ return result;
+}
+#endif
+
+/*
+ * Initialize a curses color-pair based on our configured color values.
+ */
+static void lynx_init_color_pair(int n)
+{
+#ifdef USE_COLOR_STYLE
+ (void) n; /* we only use lynx_color_pairs[] data */
+#else
+ int m;
+
+ if (lynx_called_initscr) {
+ for (m = 0; m <= 16; m += 8) {
+ int pair = n + m + 1;
+
+ if (pair < COLOR_PAIRS)
+ init_pair((short) pair,
+ (short) map2bold(lynx_color_pairs[pair].fg),
+ (short) map2bold(lynx_color_pairs[pair].bg));
+ }
+ if (n == 0 && LYShowColor >= SHOW_COLOR_ON) {
+ wbkgd(LYwin, COLOR_BKGD | ' ');
+ }
+ }
+#endif
+}
+
+static void lynx_map_color(int n)
+{
+ int j;
+
+ CTRACE((tfp, "lynx_map_color(%d)\n", n));
+
+ if (n + 1 < (int) TABLESIZE(lynx_color_pairs)
+ && n < (int) TABLESIZE(lynx_color_cfg)) {
+ for (j = n + 1; j < COLOR_PAIRS_MAX; j += COLOR_CFG_MAX) {
+ lynx_color_pairs[j].fg = lynx_color_cfg[n].fg;
+ lynx_color_pairs[j].bg = lynx_color_cfg[n].bg;
+ }
+
+ /* special case (does not apply to 3rd set) */
+ lynx_color_pairs[n + 1 + COLOR_CFG_MAX].bg = lynx_color_cfg[0].bg;
+ }
+
+ lynx_init_color_pair(n);
+}
+
+/*
+ * Change a configured color value. This may be called before initscr(), so
+ * we may not be able to call init_pair() to finish the change.
+ */
+int lynx_chg_color(int color,
+ int fg,
+ int bg)
+{
+ CTRACE((tfp, "lynx_chg_color(color=%d, fg=%d, bg=%d)\n", color, fg, bg));
+
+ if (fg == ERR_COLOR || bg == ERR_COLOR)
+ return -1;
+ if (color >= 0 && color < COLOR_CFG_MAX) {
+ lynx_color_cfg[color].fg = fg;
+ lynx_color_cfg[color].bg = bg;
+ lynx_map_color(color);
+ } else {
+ return -1;
+ }
+ return 0;
+}
+
+void lynx_set_color(int a)
+{
+ if (lynx_has_color && LYShowColor >= SHOW_COLOR_ON) {
+ (void) wattrset(LYwin, lynx_color_cfg_attr(a)
+ | (((a + 1) < COLOR_PAIRS)
+ ? get_color_pair(a + 1)
+ : (int) A_NORMAL));
+ }
+}
+
+void lynx_standout(int flag)
+{
+ if (flag)
+ LYaddAttr(A_REVERSE);
+ else
+ LYsubAttr(A_REVERSE);
+}
+
+static void lynx_init_colors(void)
+{
+ if (lynx_has_color) {
+ size_t n;
+
+ CTRACE((tfp, "lynx_init_colors (default %d/%d)\n",
+ default_fg, default_bg));
+
+ lynx_color_cfg[0].fg = default_fg;
+ lynx_color_cfg[0].bg = default_bg;
+
+ for (n = 0; n < TABLESIZE(lynx_color_cfg); n++) {
+ lynx_init_color_pair((int) n);
+ }
+ } else if (LYShowColor != SHOW_COLOR_NEVER) {
+ LYShowColor = SHOW_COLOR_OFF;
+ }
+}
+
+void lynx_setup_colors(void)
+{
+ int n;
+
+ CTRACE((tfp, "lynx_setup_colors\n"));
+#ifdef USE_DEFAULT_COLORS
+ if (!LYuse_default_colors) {
+ for (n = 0; n < COLOR_CFG_MAX; n++) {
+ if (lynx_color_cfg[n].dft_fg)
+ lynx_color_cfg[n].fg = COLOR_BLACK;
+ if (lynx_color_cfg[n].dft_bg)
+ lynx_color_cfg[n].bg = COLOR_WHITE;
+ }
+ }
+#endif
+ for (n = 0; n < COLOR_CFG_MAX; n++)
+ lynx_map_color(n);
+}
+#endif /* USE_COLOR_TABLE */
+
+void LYnoVideo(int a)
+{
+ CTRACE((tfp, "LYnoVideo(%d)\n", a));
+#ifdef USE_SLANG
+ if (a & 1)
+ Masked_Attr |= (int) SLTT_BOLD_MASK;
+ if (a & 2)
+ Masked_Attr |= (int) SLTT_REV_MASK;
+ if (a & 4)
+ Masked_Attr |= (int) SLTT_ULINE_MASK;
+ lynx_setup_attrs();
+#else
+#ifdef USE_COLOR_TABLE
+ Masked_Attr = decode_mono_code(a);
+#endif
+#endif
+}
+
+#define NEWTERM_NAME "newterm"
+
+#ifndef USE_SLANG
+static WINDOW *my_subwindow;
+
+#define delete_subwindow() if (my_subwindow) { delwin(my_subwindow); my_subwindow = NULL; }
+#endif
+
+#ifdef WIDEC_CURSES
+static WINDOW *fake_win;
+static int fake_max;
+
+#define delete_fake_win() if (fake_win) { delwin(fake_win); fake_win = NULL; fake_max = 0; }
+#else
+#define delete_fake_win() /* nothing */
+#endif
+
+#if !defined(VMS) && !defined(USE_SLANG)
+#if defined(NCURSES) && defined(HAVE_RESIZETERM)
+
+static SCREEN *LYscreen = NULL;
+
+static void delete_screen(SCREEN * screen)
+{
+ delete_fake_win();
+ delete_subwindow();
+ delscreen(screen);
+}
+
+#define LYDELSCR() /* ncurses does not need this */
+
+#elif defined(HAVE_NEWTERM) && defined(HAVE_DELSCREEN)
+
+static SCREEN *LYscreen = NULL;
+
+#if defined(USE_DEFAULT_COLORS)
+static void delete_screen(SCREEN * screen)
+{
+ delete_fake_win();
+ delete_subwindow();
+ delscreen(screen);
+}
+#endif
+
+#define LYDELSCR() { \
+CheckScreenSize(); \
+if (recent_sizechange) { \
+ CTRACE((tfp, "Screen size: delscreen()\n")); \
+ delete_screen(LYscreen); \
+ LYscreen = NULL; } }
+
+#else /* HAVE_NEWTERM */
+
+ /*
+ * If newterm is not defined, assume a curses subset which
+ * supports only initscr. --gil
+ */
+static WINDOW *LYscreen = NULL;
+
+#undef NEWTERM_NAME
+#define NEWTERM_NAME "initscr"
+#undef newterm
+#define newterm(type, out, in) (initscr())
+#define LYDELSCR() /* nothing */
+#endif /* HAVE_NEWTERM */
+
+#else /* !defined(VMS) && !defined(USE_SLANG) */
+
+ /*
+ * Provide last recourse definitions of LYscreen and LYDELSCR for
+ * stop_curses, which only tests LYscreen for zero/nonzero but
+ * never uses it as a pointer or L-value.
+ */
+#define LYscreen TRUE
+#define LYDELSCR() /* nothing */
+#endif /* !defined(VMS) && !defined(USE_SLANG) */
+
+#if defined(PDCURSES) && defined(PDC_BUILD) && PDC_BUILD >= 2401
+int saved_scrsize_x = 0;
+int saved_scrsize_y = 0;
+
+int saved_scrsize_x2 = 0;
+int saved_scrsize_y2 = 0;
+int saved_winpos_x2 = 0;
+int saved_winpos_y2 = 0;
+
+static int LYresize_term(int nlines, int ncols)
+{
+#ifdef _WINDOWS
+ HANDLE hConsole;
+ CONSOLE_SCREEN_BUFFER_INFO csbi;
+ SMALL_RECT srWindowRect;
+
+ hConsole = GetStdHandle(STD_OUTPUT_HANDLE);
+ GetConsoleScreenBufferInfo(hConsole, &csbi);
+ srWindowRect.Right = min(ncols, csbi.dwSize.X) - 1;
+ srWindowRect.Bottom = min(nlines, csbi.dwSize.Y) - 1;
+ srWindowRect.Left = srWindowRect.Top = (SHORT) 0;
+ SetConsoleWindowInfo(hConsole, TRUE, &srWindowRect);
+#endif
+ return resize_term(nlines, ncols);
+}
+#endif
+
+#ifdef USE_MAXSCREEN_TOGGLE
+static HWND currentWindowHandle = NULL;
+static char dummyWindowTitle[256];
+
+static int CALLBACK EnumWindowsProc(HWND hwnd, LPARAM lParam)
+{
+ char this_title[256];
+
+ (void) lParam;
+ if (GetWindowText(hwnd, this_title, sizeof(this_title) - 1) &&
+ (strncmp(dummyWindowTitle, this_title, 256) == 0)) {
+ currentWindowHandle = hwnd;
+ return FALSE;
+ }
+ return TRUE;
+}
+
+static void setCurrentWindowHandle(void)
+{
+ char org_title[256];
+ DWORD pid;
+ int i;
+
+ if (currentWindowHandle != NULL) {
+ return;
+ }
+ pid = GetCurrentProcessId();
+ sprintf(dummyWindowTitle, "Lynx for Win32 (pid=%ld)", pid);
+ GetConsoleTitle(org_title, sizeof(org_title) - 1);
+ SetConsoleTitle(dummyWindowTitle);
+ for (i = 0; i < 10; i++) {
+ EnumWindows(EnumWindowsProc, (LPARAM) 0);
+ if (currentWindowHandle != NULL) {
+ break;
+ }
+ CTRACE((tfp,
+ "Failed to get current window handle. Try again...(%d)\n", i));
+ Sleep(100);
+ }
+ SetConsoleTitle(org_title);
+}
+
+static void moveWindowHXY(HWND hwnd, int x, int y)
+{
+ int win_height, win_width;
+ RECT winrect;
+
+ GetWindowRect(hwnd, &winrect);
+ win_width = winrect.right - winrect.left;
+ win_height = winrect.bottom - winrect.top;
+
+ if ((x != winrect.left) || (y != winrect.top)) {
+ MoveWindow(hwnd, x, y, win_width, win_height, TRUE);
+ CTRACE((tfp, "move window from (%d,%d) to (%d,%d).\n",
+ (int) winrect.left,
+ (int) winrect.top, x, y));
+ }
+}
+
+static void adjustWindowPos(void)
+{
+ int disp_height, disp_width, win_height, win_width;
+ int newwin_left, newwin_top;
+ RECT winrect;
+ DWORD pid;
+
+ setCurrentWindowHandle();
+ if (currentWindowHandle == NULL) {
+ return;
+ }
+ GetWindowThreadProcessId(currentWindowHandle, &pid);
+ disp_width = GetSystemMetrics(SM_CXFULLSCREEN);
+ disp_height = GetSystemMetrics(SM_CYFULLSCREEN);
+ Sleep(100); /* If not, GetWindowRect sometimes return wrong value. */
+ GetWindowRect(currentWindowHandle, &winrect);
+ win_width = winrect.right - winrect.left;
+ win_height = winrect.bottom - winrect.top;
+ CTRACE((tfp, "Display Size: (%4d,%3d)\n", disp_width, disp_height));
+ CTRACE((tfp, "Orig WinRect: (%4d,%4d,%3d,%3d), ",
+ (int) winrect.left, (int) winrect.right,
+ (int) winrect.top, (int) winrect.bottom));
+ CTRACE((tfp, "Size: (%4d,%3d)\n", win_width, win_height));
+
+ newwin_left = winrect.left;
+ newwin_top = winrect.top;
+ if (disp_width < winrect.right) {
+ if (win_width <= disp_width) {
+ newwin_left = disp_width - win_width;
+ } else {
+ newwin_left = 0;
+ }
+ }
+ if (disp_height < winrect.bottom) {
+ if (win_height <= disp_height) {
+ newwin_top = disp_height - win_height;
+ } else {
+ newwin_top = 0;
+ }
+ }
+
+ moveWindowHXY(currentWindowHandle, newwin_left, newwin_top);
+}
+
+void maxmizeWindowSize(void)
+{
+ RECT winrect;
+ HANDLE hConsole;
+ COORD coordScreen;
+
+ setCurrentWindowHandle();
+ if (currentWindowHandle == NULL) {
+ return;
+ }
+ GetWindowRect(currentWindowHandle, &winrect);
+ saved_winpos_x2 = winrect.left;
+ saved_winpos_y2 = winrect.top;
+
+ hConsole = GetStdHandle(STD_OUTPUT_HANDLE);
+ coordScreen = GetLargestConsoleWindowSize(hConsole);
+
+ LYcols = scrsize_x = coordScreen.X - 1;
+ LYlines = scrsize_y = coordScreen.Y - 1;
+ LYlines--;
+ CTRACE((tfp, "Request maximum screen size: %dx%d\n",
+ scrsize_x, scrsize_y));
+ LYresize_term(scrsize_y, scrsize_x);
+ Sleep(100);
+ moveWindowHXY(currentWindowHandle, 0, 0);
+ LYlines = LYscreenHeight();
+ LYcols = LYscreenWidth();
+ CTRACE((tfp, "...actual maximum screen size: %dx%d\n",
+ LYcols, LYlines));
+ LYStatusLine = -1;
+ LYGetScreenSize(0);
+ recent_sizechange = TRUE;
+}
+
+void recoverWindowSize(void)
+{
+ if ((0 < saved_scrsize_x2) && (0 < saved_scrsize_y2)) {
+ LYcols = scrsize_x = saved_scrsize_x2;
+ LYlines = scrsize_y = saved_scrsize_y2;
+ LYlines--;
+ LYStatusLine = -1;
+ wclear(curscr);
+ doupdate();
+ LYresize_term(scrsize_y, scrsize_x);
+
+ setCurrentWindowHandle();
+ if (currentWindowHandle != NULL) {
+ Sleep(100);
+ moveWindowHXY(currentWindowHandle, saved_winpos_x2, saved_winpos_y2);
+ }
+ recent_sizechange = TRUE;
+ } else {
+ CTRACE((tfp, "scrsize_{xy} is not saved yet.\n"));
+ }
+}
+#endif
+
+#if defined(USE_DEFAULT_COLORS)
+void restart_curses(void)
+{
+ SCREEN *oldscreen = LYscreen;
+
+ if (!(LYscreen = newterm(NULL, stdout, stdin))) { /* start curses */
+ fprintf(tfp, "%s\n",
+ gettext("Terminal reinitialisation failed - unknown terminal type?"));
+ exit_immediately(EXIT_FAILURE);
+ }
+
+ /* force xterm mouse-mode off in the physical terminal */
+ lynx_enable_mouse(0);
+ keypad(LYwin, FALSE);
+ wrefresh(LYwin);
+
+ LYwin = stdscr;
+ /* re-enable xterm mouse-mode in the new screen */
+ keypad(LYwin, TRUE);
+ lynx_enable_mouse(1);
+
+#if defined(USE_KEYMAPS)
+ if (-1 == lynx_initialize_keymaps()) {
+ endwin();
+ exit_immediately(EXIT_FAILURE);
+ }
+#endif
+ if (has_colors()) {
+ start_color();
+ }
+
+ delete_screen(oldscreen);
+}
+#endif
+
+void start_curses(void)
+{
+#ifdef USE_SLANG
+ static int slinit;
+
+ if (LYCursesON) {
+ CTRACE((tfp, "start_curses: Hmm, already ON.\n"));
+ return;
+ }
+
+ if (slinit == 0) {
+#if defined(HAVE_TTYNAME)
+ if (isatty(fileno(stdout)) && LYReopenInput() < 0) {
+ fprintf(stderr, "Cannot open tty input\n");
+ exit_immediately(EXIT_FAILURE);
+ }
+#endif
+#if defined(USE_KEYMAPS)
+ if (-1 == lynx_initialize_keymaps())
+ exit_immediately(EXIT_FAILURE);
+#else
+ SLtt_get_terminfo();
+#endif
+#if (defined(__DJGPP__) && !defined(DJGPP_KEYHANDLER)) || defined(__CYGWIN__)
+ SLkp_init();
+#endif /* __DJGPP__ && !DJGPP_KEYHANDLER */
+
+#if defined(REAL_UNIX_SYSTEM) && !defined(__CYGWIN__)
+#if SLANG_VERSION >= 9935
+ SLang_TT_Read_FD = fileno(stdin);
+#endif /* SLANG_VERSION >= 9935 */
+#endif /* REAL_UNIX_SYSTEM && !__CYGWIN__ */
+
+#if !defined(USE_KEYMAPS) && defined(ENHANCED_LINEEDIT) && defined(ESCDELAY)
+ /* way to get ESC that's not part of a recognized sequence through */
+ ESCDELAY = 2000;
+#endif
+ /*
+ * Check whether a saved show_color:off override is in effect. - kw
+ */
+ if (LYrcShowColor == SHOW_COLOR_NEVER) {
+ SLtt_Use_Ansi_Colors = 0;
+ }
+ /*
+ * Check whether we're forcing color on. - FM
+ */
+ if ((LYShowColor > 1) && (Lynx_Color_Flags & SL_LYNX_USE_COLOR))
+ SLtt_Use_Ansi_Colors = 1;
+ /*
+ * Check whether a -nocolor override is in effect. - kw
+ */
+ if (Lynx_Color_Flags & SL_LYNX_OVERRIDE_COLOR)
+ SLtt_Use_Ansi_Colors = 0;
+ /*
+ * Make sure our flags are in register. - FM
+ */
+ if (SLtt_Use_Ansi_Colors == 1) {
+ if (LYShowColor != SHOW_COLOR_ALWAYS) {
+ LYShowColor = SHOW_COLOR_ON;
+ }
+ } else {
+ if (LYShowColor != SHOW_COLOR_NEVER) {
+ LYShowColor = SHOW_COLOR_OFF;
+ }
+ }
+ size_change(0);
+
+#if (defined(VMS) || defined(REAL_UNIX_SYSTEM)) && !defined(__CYGWIN__)
+ if ((Masked_Attr & (int) SLTT_ULINE_MASK) == 0) {
+ SLtt_add_color_attribute(4, SLTT_ULINE_MASK);
+ SLtt_add_color_attribute(5, SLTT_ULINE_MASK);
+ }
+ /*
+ * If set, the blink escape sequence will turn on high intensity
+ * background (rxvt and maybe Linux console).
+ */
+ SLtt_Blink_Mode = term_blink_is_boldbg;
+#endif /* (VMS || REAL_UNIX_SYSTEM) && !__CYGWIN__ */
+ }
+#ifdef __DJGPP__
+ _eth_init();
+#endif /* __DJGPP__ */
+
+ slinit = 1;
+ Current_Attr = 0;
+#ifndef VMS
+#if SLANG_VERSION > 9929
+ SLang_init_tty(-1, 0, 1);
+#else
+ SLang_init_tty(3, 0, 1);
+#endif /* SLANG_VERSION > 9929 */
+#endif /* !VMS */
+ SLsmg_init_smg();
+ SLsmg_Display_Eight_Bit = LYlowest_eightbit[current_char_set];
+ if (SLsmg_Display_Eight_Bit > 191)
+ SLsmg_Display_Eight_Bit = 191; /* may print ctrl chars otherwise - kw */
+ scrollok(0, 0);
+ SLsmg_Backspace_Moves = 1;
+#if SLANG_VERSION > 10306
+ SLsmg_touch_screen();
+#endif
+#ifndef VMS
+#if defined(REAL_UNIX_SYSTEM) && !defined(__CYGWIN__)
+ SLtty_set_suspend_state(1);
+#endif /* REAL_UNIX_SYSTEM && !__CYGWIN__ */
+#ifdef SIGTSTP
+ if (!no_suspend)
+ signal(SIGTSTP, sl_suspend);
+#endif /* SIGTSTP */
+ signal(SIGINT, cleanup_sig);
+#endif /* !VMS */
+
+ lynx_enable_mouse(1);
+
+#else /* USE_SLANG; Now using curses: */
+ int keypad_on = 0;
+
+#ifdef VMS
+ /*
+ * If we are VMS then do initscr() every time start_curses() is called!
+ */
+ CTRACE((tfp, "Screen size: initscr()\n"));
+ initscr(); /* start curses */
+#else /* Unix: */
+
+#if defined(HAVE_TTYNAME)
+ if (isatty(fileno(stdout)) && LYReopenInput() < 0) {
+ fprintf(stderr, "Cannot open tty input\n");
+ exit_immediately(EXIT_FAILURE);
+ }
+#endif
+
+#ifdef __CYGWIN__
+ /*
+ * Workaround for buggy Cygwin, which breaks subprocesses of a
+ * full-screen application (tested with cygwin dll, dated
+ * 2002/6/23 -TD)
+ */
+ if (!lynx_called_initscr) {
+ FILE *fp = fopen("/dev/tty", "w");
+
+ if (fp != 0)
+ stdout = fp;
+ }
+#endif
+
+ if (!LYscreen) {
+ /*
+ * If we're not VMS then only do initscr() one time, and one time only!
+ */
+#if defined(HAVE_NEWTERM)
+#if !(defined(NCURSES) && !defined(HAVE_RESIZETERM))
+ BOOLEAN savesize;
+
+ savesize = recent_sizechange;
+ size_change(0);
+ LYGetScreenSize(0);
+ recent_sizechange = savesize; /* avoid extra redraw */
+#if defined(__MVS__)
+ {
+ /*
+ * The requirement to do this may be a bug in OS/390.
+ *
+ * Put screen geometry in environment variables used by
+ * XOpen curses before calling newterm(). I believe this
+ * completes work left unfinished by AJL & FM -- gil
+ */
+ static char lines_putenv[] = "LINES=abcde", cols_putenv[] = "COLUMNS=abcde";
+
+ sprintf(lines_putenv + 6, "%d", LYlines & 0xfff);
+ sprintf(cols_putenv + 8, "%d", LYcols & 0xfff);
+ putenv(lines_putenv);
+ putenv(cols_putenv);
+ CTRACE((tfp, "start_curses putenv %s, %s\n", lines_putenv, cols_putenv));
+ }
+#endif /* defined(__MVS__) */
+#endif /* !(defined(NCURSES) && defined(HAVE_RESIZETERM)) */
+ CTRACE((tfp, "Screen size: %s()\n", NEWTERM_NAME));
+ if (!(LYscreen = newterm(NULL, stdout, stdin))) { /* start curses */
+ fprintf(tfp, "%s\n",
+ gettext("Terminal initialisation failed - unknown terminal type?"));
+ exit_immediately(EXIT_FAILURE);
+ }
+#else
+ CTRACE((tfp, "Screen size: initscr()\n"));
+ initscr();
+#endif /* HAVE_NEWTERM */
+ lynx_called_initscr = TRUE;
+ LYlines = LYscreenHeight();
+ LYcols = LYscreenWidth();
+
+#if defined(NCURSES_VERSION)
+#if defined(SIGWINCH)
+ size_change(0);
+ LYGetScreenSize(0);
+ recent_sizechange = FALSE; /* prevent mainloop drawing 1st doc twice */
+#endif /* SIGWINCH */
+#endif /* NCURSES_VERSION */
+ CTRACE((tfp, "Screen size is now %d x %d\n", LYcols, LYlines));
+
+#ifdef USE_CURSES_PADS
+ if (LYuseCursesPads) {
+ CTRACE((tfp, "using curses-pads\n"));
+ keypad(stdscr, TRUE);
+ LYwin = newpad(LYlines, MAX_COLS);
+ LYshiftWin = 0;
+ LYwideLines = FALSE;
+ } else {
+ LYwin = stdscr;
+ }
+#endif
+
+#if defined(USE_KEYMAPS) && defined(NCURSES_VERSION)
+# ifdef HAVE_KEYPAD
+ /* Need to switch keypad on before initializing keymaps, otherwise
+ when the keypad is switched on, some keybindings may be overridden. */
+ keypad(LYwin, TRUE);
+ keypad_on = 1;
+# endif /* HAVE_KEYPAD */
+
+ if (-1 == lynx_initialize_keymaps()) {
+ endwin();
+ exit_immediately(EXIT_FAILURE);
+ }
+#if defined(CAN_CUT_AND_PASTE) && defined(IXON) && defined(IXOFF)
+ /*
+ * Since the ifdef is enabled, we know that at least the copy/paste
+ * commands are enabled. Binding the copy to ^S is inconvenient, so
+ * ask curses to permit that (saving an external tweak with stty).
+ *
+ * Disable the feature in lynx.cfg with
+ * KEYMAP:^S:UNMAPPED
+ */
+ if (keymap[KTL('S')] != LYK_UNKNOWN) {
+ struct termios alter_tty;
+
+ CTRACE((tfp, "Allowing ^S as key-binding\n"));
+ if (tcgetattr(fileno(stdin), &alter_tty) == 0) {
+ alter_tty.c_iflag &= (unsigned) ~(IXON | IXOFF);
+ tcsetattr(fileno(stdout), TCSAFLUSH, &alter_tty);
+ def_prog_mode();
+ }
+ }
+#endif
+#endif /* ncurses-keymaps */
+
+ /*
+ * This is a workaround for a bug in SVr4 curses, observed on Solaris
+ * 2.4: if your terminal's alternate-character set contains codes in
+ * the range 128-255, they'll be sign-extended in the acs_map[] table,
+ * which in turn causes their values to be emitted as 255 (0xff).
+ * "Fix" this by forcing the table to 8-bit codes (it has to be
+ * anyway).
+ */
+#if defined(ALT_CHAR_SET) && !defined(NCURSES_VERSION)
+ {
+ int n;
+
+ for (n = 0; n < 128; n++)
+ if (ALT_CHAR_SET[n] & 0x80) {
+ ALT_CHAR_SET[n] &= 0xff;
+ ALT_CHAR_SET[n] |= A_ALTCHARSET;
+ }
+ }
+#endif
+
+#if defined(USE_COLOR_STYLE) || defined(USE_COLOR_TABLE)
+ if (has_colors()) {
+ lynx_has_color = TRUE;
+ start_color();
+
+#ifndef COLORS
+ /* map2boldc() relies on COLORS being a power of 2 */
+ if (COLORS > 16)
+ COLORS = 16;
+ if (COLORS < 8)
+ COLORS = 2;
+ if (COLORS > 8 && COLORS != 16)
+ COLORS = 8;
+#endif
+
+#ifdef USE_DEFAULT_COLORS
+ update_default_colors();
+ if (LYuse_default_colors) {
+#if defined(EXP_ASSUMED_COLOR) && defined(USE_COLOR_TABLE)
+ /*
+ * Adjust the color mapping table to match the ASSUMED_COLOR
+ * setting in lynx.cfg
+ */
+ if (assume_default_colors(default_fg, default_bg) != OK) {
+ default_fg = COLOR_WHITE;
+ default_bg = COLOR_BLACK;
+ }
+ CTRACE((tfp, "initializing default colors %d/%d\n",
+ default_fg, default_bg));
+ if (default_fg >= 0 || default_bg >= 0) {
+ unsigned n;
+
+ for (n = 0; n < TABLESIZE(lynx_color_cfg); n++) {
+ if (default_fg >= 0 && lynx_color_cfg[n].fg < 0)
+ lynx_color_cfg[n].fg = default_fg;
+ if (default_bg >= 0 && lynx_color_cfg[n].bg < 0)
+ lynx_color_cfg[n].bg = default_bg;
+ CTRACE((tfp, "color_cfg[%u] = %d/%d\n", n,
+ lynx_color_cfg[n].fg,
+ lynx_color_cfg[n].bg));
+ }
+ lynx_setup_colors();
+ }
+#else
+#if defined(HAVE_USE_DEFAULT_COLORS)
+ if (!default_color_reset) {
+ if (lynx_called_initscr) {
+ if (LYuse_default_colors && (use_default_colors() == OK)) {
+ default_fg = DEFAULT_COLOR;
+ default_bg = DEFAULT_COLOR;
+ } else {
+ default_fg = COLOR_WHITE;
+ default_bg = COLOR_BLACK;
+ default_color_reset = TRUE;
+ }
+ }
+ }
+#endif /* HAVE_USE_DEFAULT_COLORS */
+#endif /* EXP_ASSUMED_COLOR */
+ }
+#endif /* USE_DEFAULT_COLORS */
+ }
+#endif /* USE_COLOR_STYLE || USE_COLOR_TABLE */
+
+#ifdef USE_COLOR_STYLE
+ /* Curses forgets color settings when we call delscreen() */
+ if (non_empty(lynx_lss_file) && LYCanReadFile(lynx_lss_file)) {
+ style_readFromFile(lynx_lss_file);
+ }
+ parse_userstyles();
+#endif
+#ifdef USE_COLOR_TABLE
+ lynx_init_colors();
+#endif
+ }
+#ifdef __DJGPP__
+ _eth_init();
+#endif /* __DJGPP__ */
+#endif /* not VMS */
+
+#ifdef VMS
+ crmode();
+ raw();
+#else
+#ifdef HAVE_CBREAK
+ cbreak();
+#else
+ crmode();
+#endif /* HAVE_CBREAK */
+ signal(SIGINT, cleanup_sig);
+#endif /* VMS */
+
+ noecho();
+
+#ifdef HAVE_KEYPAD
+ if (!keypad_on)
+ keypad(LYwin, TRUE);
+#endif /* HAVE_KEYPAD */
+
+ lynx_enable_mouse(1);
+
+ fflush(stdin);
+ fflush(stdout);
+ fflush(stderr);
+#endif /* USE_SLANG */
+
+#if defined(WIN_EX)
+ LYclear();
+#endif
+
+#if defined(USE_BLINK) && defined(__EMX__)
+ if (term_blink_is_boldbg) /* Now actually make it so! */
+ make_blink_boldbg();
+#endif
+
+ LYCursesON = TRUE;
+#if defined(PDCURSES) && defined(PDC_BUILD) && PDC_BUILD >= 2401
+ if ((scrsize_x != 0) && (scrsize_y != 0)) {
+ if (saved_scrsize_x == 0) {
+ saved_scrsize_x = COLS;
+ saved_scrsize_y = LINES + 1;
+ }
+ CTRACE((tfp, "resize_term: x=%d, y=%d\n", scrsize_x, scrsize_y));
+ CTRACE((tfp, "saved terminal size: x=%d, y=%d\n", saved_scrsize_x, saved_scrsize_y));
+ LYresize_term(scrsize_y, scrsize_x);
+ LYlines = LYscreenHeight();
+ LYcols = LYscreenWidth();
+ LYStatusLine = -1;
+ LYclear();
+#ifdef _WINDOWS
+ adjustWindowPos();
+#endif
+ }
+ if (saved_scrsize_x2 == 0) {
+ if (saved_scrsize_x == 0) {
+ saved_scrsize_x2 = COLS;
+ saved_scrsize_y2 = LINES + 1;
+ } else {
+ saved_scrsize_x2 = scrsize_x;
+ saved_scrsize_y2 = scrsize_y;
+ }
+ }
+#endif
+ CTRACE((tfp, "start_curses: done.\n"));
+} /* end of start_curses() */
+
+void lynx_enable_mouse(int state)
+{
+#ifdef USE_MOUSE
+/***********************************************************************/
+
+#if defined(WIN_EX)
+/* modify lynx_enable_mouse() for pdcurses configuration so that mouse support
+ is disabled unless -use_mouse is specified
+*/
+ HANDLE hConIn = INVALID_HANDLE_VALUE;
+
+ hConIn = GetStdHandle(STD_INPUT_HANDLE);
+ if (LYUseMouse == 0) {
+ SetConsoleMode(hConIn, ENABLE_WINDOW_INPUT);
+ FlushConsoleInputBuffer(hConIn);
+ return;
+ }
+#endif
+
+ (void) state;
+
+ if (LYUseMouse == 0)
+ return;
+
+#if defined(USE_SLANG)
+ SLtt_set_mouse_mode(state, 0);
+ SLtt_flush_output();
+#else
+
+#if defined(WIN_EX) && defined(PDCURSES)
+ if (state) {
+ SetConsoleMode(hConIn, ENABLE_MOUSE_INPUT | ENABLE_WINDOW_INPUT);
+ FlushConsoleInputBuffer(hConIn);
+ }
+#else
+#if defined(NCURSES)
+ if (state) {
+ /* Compensate for small value of maxclick in ncurses. */
+ static int was = 0;
+
+ if (!was) {
+ int old = mouseinterval(-1);
+
+ was++;
+ if (old < 200) /* Default 166 */
+ mouseinterval(300);
+ }
+ /* Inform ncurses which mouse events we're interested in.
+ * We shouldn't need to include BUTTONn_PRESSED and BUTTONn_RELEASED
+ * events, since ncurses should translate them to click events. - kw
+ * However, if we do not include them, then ncurses effectively
+ * ignores mouseinterval(), thus translates *any* sequence of
+ * press/release to a click, which leads to inconveniences.
+ * We special-case these events in LYStrings.c.
+ */
+ mousemask(BUTTON_CTRL | BUTTON_ALT
+ | BUTTON1_PRESSED | BUTTON1_RELEASED
+ | BUTTON1_CLICKED
+ | BUTTON1_DOUBLE_CLICKED | BUTTON1_TRIPLE_CLICKED
+ | BUTTON2_PRESSED | BUTTON2_RELEASED
+ | BUTTON2_CLICKED
+ | BUTTON3_PRESSED | BUTTON3_RELEASED
+ | BUTTON3_CLICKED
+ | BUTTON3_DOUBLE_CLICKED | BUTTON3_TRIPLE_CLICKED
+#if NCURSES_MOUSE_VERSION >= 2
+ | BUTTON4_PRESSED | BUTTON4_RELEASED
+ | BUTTON4_CLICKED
+ | BUTTON4_DOUBLE_CLICKED | BUTTON4_TRIPLE_CLICKED
+ | BUTTON5_PRESSED | BUTTON5_RELEASED
+ | BUTTON5_CLICKED
+ | BUTTON5_DOUBLE_CLICKED | BUTTON5_TRIPLE_CLICKED
+#endif
+ ,NULL);
+ } else
+ mousemask(0, NULL);
+#endif /* NCURSES */
+#endif /* WIN_EX and PDCURSES */
+
+#if defined(PDCURSES)
+ if (state)
+ mouse_set(
+ BUTTON1_CLICKED | BUTTON1_PRESSED | BUTTON1_RELEASED |
+ BUTTON2_CLICKED | BUTTON2_PRESSED | BUTTON2_RELEASED |
+ BUTTON3_CLICKED | BUTTON3_PRESSED | BUTTON3_RELEASED);
+#endif
+#endif /* NOT USE_SLANG */
+
+/***********************************************************************/
+#else
+ (void) state;
+#endif /* USE_MOUSE */
+}
+
+/*
+ * SVr4 curses (and ncurses) initialize the terminal I/O to raw mode, and
+ * simulate other modes in the library. This means that when running, it
+ * simulates the OCRNL setting. Normally that is not a problem. However, when
+ * spawning a subprocess (e.g., xli), the subprocess may write to the screen.
+ * Fine so far - curses resets the terminal I/O to the normal state on exit.
+ * But the subprocess's messages can still be coming to the screen when lynx
+ * returns to the screen mode. This function delays restoring OCRNL until
+ * after the first getch() call.
+ *
+ * The OCRNL setting is controlled by nl()/nonl() of course - but we do not
+ * want to give up that optimization since it would be a bit slower. (Note -
+ * slang does not use this optimization; if it did, the same screen glitch
+ * would occur).
+ *
+ * FIXME: for simplicity, only ncurses is implemented here - the TTY and
+ * SET_TTY definitions are ncurses-specific. The same effect could be done for
+ * other curses implementations, since the "cur_term->Nttyb" part is common to
+ * SVr4 curses.
+ */
+void lynx_nl2crlf(int normal GCC_UNUSED)
+{
+#if defined(NCURSES_VERSION_PATCH) && defined(SET_TTY) && defined(TERMIOS) && defined(ONLCR)
+ static struct termios saved_tty;
+ static int did_save = FALSE;
+ static int waiting = FALSE;
+ static int can_fix = TRUE;
+
+ if (!did_save) {
+ if (cur_term == 0) {
+ can_fix = FALSE;
+ } else {
+ tcgetattr(fileno(stdout), &saved_tty);
+ did_save = TRUE;
+ if ((saved_tty.c_oflag & ONLCR))
+ can_fix = FALSE;
+#if NCURSES_VERSION_PATCH < 20010529
+ /* workaround for optimizer bug with nonl() */
+ if ((tigetstr("cud1") != 0 && *tigetstr("cud1") == '\n')
+ || (tigetstr("ind") != 0 && *tigetstr("ind") == '\n'))
+ can_fix = FALSE;
+#endif
+ }
+ }
+ if (can_fix) {
+ if (normal) {
+ if (!waiting) {
+ struct termios alter_tty = saved_tty;
+
+ alter_tty.c_oflag |= ONLCR;
+ tcsetattr(fileno(stdout), TCSAFLUSH, &alter_tty);
+ def_prog_mode();
+ waiting = TRUE;
+ nonl();
+ }
+ } else {
+ if (waiting) {
+ tcsetattr(fileno(stdout), TCSAFLUSH, &saved_tty);
+ def_prog_mode();
+ waiting = FALSE;
+ nl();
+ LYrefresh();
+ }
+ }
+ }
+#endif
+}
+
+void stop_curses(void)
+{
+ if (LYCursesON) {
+#ifdef USE_COLOR_STYLE
+ FreeCachedStyles();
+#endif
+ echo();
+ }
+#if defined(PDCURSES) && defined(PDC_BUILD) && PDC_BUILD >= 2401
+ resetty();
+#endif
+
+#ifdef __DJGPP__
+ _eth_release();
+#endif /* __DJGPP__ */
+
+/* ifdef's for non-Unix curses or slang */
+#if defined(__MINGW32__)
+ {
+ chtype bb;
+
+ bb = getbkgd(stdscr);
+ bkgdset(0);
+ clear();
+ refresh();
+ bkgdset(bb);
+ }
+#if defined(PDCURSES)
+ endwin();
+#endif /* PDCURSES */
+
+#elif defined(DOSPATH) && !(defined(USE_SLANG) || defined(_WIN_CC))
+
+#if defined(PDCURSES)
+ endwin();
+#endif /* PDCURSES */
+
+#ifdef __DJGPP__
+ ScreenClear();
+#elif !defined(PDCURSES) /* some flavor of win32? */
+ clrscr();
+#endif /* win32 */
+
+#else /* Unix, etc */
+
+ if (LYCursesON == TRUE) {
+ lynx_nl2crlf(TRUE);
+ lynx_enable_mouse(0);
+ if (LYscreen || lynx_called_initscr) {
+ endwin(); /* stop curses */
+ LYDELSCR();
+ }
+ } else {
+#ifdef SH_EX
+ int i;
+
+ for (i = 0; i <= 3; i++) {
+ printf("\r\n");
+ }
+#endif
+ }
+
+ fflush(stdout);
+#endif /* ifdef's for non-Unix curses or slang */
+ fflush(stderr);
+
+ LYCursesON = FALSE;
+ CTRACE((tfp, "stop_curses: done.\n"));
+
+#if defined(SIGTSTP) && defined(USE_SLANG)
+#ifndef VMS
+ if (!no_suspend)
+ signal(SIGTSTP, SIG_DFL);
+#endif /* !VMS */
+#endif /* SIGTSTP && USE_SLANG */
+
+#ifndef VMS
+ signal(SIGINT, SIG_DFL);
+#endif /* !VMS */
+}
+
+#ifdef VMS
+
+#ifdef USE_SLANG
+extern void longname(char *, char *);
+#endif /* USE_SLANG */
+
+/*
+ * Check terminal type, start curses & setup terminal.
+ */
+BOOLEAN setup(char *terminal)
+{
+ int c;
+ int status;
+ char *dummy = 0, *cp, term[81];
+
+ /*
+ * If the display was not set by a command line option then see if it is
+ * available from the environment.
+ */
+ if ((cp = LYgetXDisplay()) != 0) {
+ StrAllocCopy(x_display, cp);
+ } else {
+ FREE(x_display);
+ }
+
+ /*
+ * Get terminal type, and convert to lower case.
+ */
+ term[0] = '\0';
+ longname(dummy, term);
+ if (term[0] == '\0' &&
+ (non_empty(form_get_data) ||
+ non_empty(form_post_data))) {
+ /*
+ * Some yoyo used these under conditions which require -dump, so force
+ * that mode here. - FM
+ */
+ dump_output_immediately = TRUE;
+ LYcols = DFT_COLS;
+ if (keypad_mode == NUMBERS_AS_ARROWS)
+ keypad_mode = LINKS_ARE_NUMBERED;
+ status = mainloop();
+ exit_immediately(status);
+ }
+ LYLowerCase(term);
+
+ printf("%s%s\n", gettext("Terminal ="), term);
+ if ((strlen(term) < 5) ||
+ StrNCmp(term, "vt", 2) || !isdigit(term[2])) {
+ printf("%s\n",
+ gettext("You must use a vt100, 200, etc. terminal with this program."));
+ printf(CONFIRM_PROCEED, "n/y");
+ c = getchar();
+ if (c != 'y' && c != 'Y') {
+ printf("\n");
+ return (FALSE);
+ }
+ strcpy(term, "vt100");
+ }
+
+ ttopen();
+ start_curses();
+
+ LYlines = LYscreenHeight();
+ LYcols = LYscreenWidth();
+
+ return (TRUE);
+}
+
+#else /* Not VMS: */
+
+/*
+ * Check terminal type, start curses & setup terminal.
+ */
+BOOLEAN setup(char *terminal)
+{
+ char *term_putenv = NULL;
+ char *buffer = NULL;
+ char *cp;
+
+ /*
+ * If the display was not set by a command line option then see if it is
+ * available from the environment .
+ */
+ if ((cp = LYgetXDisplay()) != NULL) {
+ StrAllocCopy(x_display, cp);
+ } else {
+ FREE(x_display);
+ }
+
+ if (terminal != NULL) {
+ HTSprintf0(&term_putenv, "TERM=%.106s", terminal);
+ (void) putenv(term_putenv);
+ }
+
+ /*
+ * Query the terminal type.
+ */
+ if (dumbterm(LYGetEnv("TERM"))) {
+ printf("\n\n %s\n\n", gettext("Your Terminal type is unknown!"));
+ printf(" %s [vt100] ", gettext("Enter a terminal type:"));
+
+ if (LYSafeGets(&buffer, stdin) != 0) {
+ LYTrimLeading(buffer);
+ LYTrimTrailing(buffer);
+ }
+
+ if (isEmpty(buffer))
+ StrAllocCopy(buffer, "vt100");
+
+ HTSprintf0(&term_putenv, "TERM=%.106s", buffer);
+ FREE(buffer);
+
+ (void) putenv(term_putenv);
+ printf("\n%s %s\n", gettext("TERMINAL TYPE IS SET TO"),
+ LYGetEnv("TERM"));
+ LYSleepMsg();
+ }
+
+ start_curses();
+
+#ifdef HAVE_TTYTYPE
+ /*
+ * Account for lossage on the 'sun' terminal type (80x24) Sun text console
+ * driver. It only supports reverse video, but all SGR sequences produce
+ * that same reverse video, and the terminfo entry lists different SGRs for
+ * 'bold' and 'rev'. As a result, the current link is indistinguishable
+ * from all other links. The workaround here is to disable the 'rev'
+ * capability.
+ */
+ if ((StrNCmp((const char *) ttytype, "sun", 3) == 0)) {
+ LYnoVideo(2);
+ }
+#endif /* HAVE_TTYTYPE */
+
+ LYlines = LYscreenHeight();
+ LYcols = LYscreenWidth();
+
+ return (1);
+}
+
+static int dumbterm(char *terminal)
+{
+ int dumb = FALSE;
+
+ /*
+ * Began checking for terminal == NULL in case that TERM environment
+ * variable is not set. Thanks to Dick Wesseling (ftu@fi.ruu.nl).
+ */
+ if (terminal == NULL ||
+ !strcasecomp(terminal, "network") ||
+ !strcasecomp(terminal, "unknown") ||
+ !strcasecomp(terminal, "dialup") ||
+ !strcasecomp(terminal, "dumb") ||
+ !strcasecomp(terminal, "switch") ||
+ !strcasecomp(terminal, "ethernet"))
+ dumb = TRUE;
+ return (dumb);
+}
+
+#ifdef FANCY_CURSES
+#ifndef USE_COLOR_STYLE
+#ifdef USE_COLOR_TABLE
+static void LYsetWAttr(WINDOW * win)
+{
+ (void) wattrset(win, LYgetTableAttr());
+}
+
+void LYaddWAttr(WINDOW * win, int a)
+{
+ Current_Attr |= a;
+ LYsetWAttr(win);
+}
+
+void LYaddAttr(int a)
+{
+ LYaddWAttr(LYwin, a);
+}
+
+void LYsubWAttr(WINDOW * win, int a)
+{
+ Current_Attr &= ~a;
+ LYsetWAttr(win);
+}
+
+void LYsubAttr(int a)
+{
+ LYsubWAttr(LYwin, a);
+}
+#endif /* USE_COLOR_TABLE */
+#endif /* !USE_COLOR_STYLE */
+#endif /* FANCY_CURSES */
+#endif /* VMS */
+
+/* Use this rather than the 'wprintw()' function to write a blank-padded
+ * string to the given window, since someone's asserted that printw doesn't
+ * handle 8-bit characters unlike addstr (though more info would be useful).
+ *
+ * We're blank-filling so that with SVr4 curses, it'll show the background
+ * color to a uniform width in the popup-menu.
+ */
+#ifndef USE_SLANG
+void LYpaddstr(WINDOW * the_window, int width, const char *the_string)
+{
+ int y, x1, x2;
+ int length = (int) strlen(the_string);
+
+#ifdef WIDEC_CURSES
+ int actual = (int) LYstrCells(the_string);
+#endif
+
+ getyx(the_window, y, x1);
+ (void) y;
+ if (width + x1 > LYcolLimit)
+ width = LYcolLimit - x1;
+#ifdef WIDEC_CURSES
+ if (actual > width) {
+ actual = width;
+ /* FIXME: a binary search might be faster */
+ while (LYstrExtent(the_string, length, length) > actual) {
+ --length;
+ }
+ }
+#endif
+ LYwaddnstr(the_window, the_string, (size_t) length);
+ getyx(the_window, y, x2);
+ width -= (x2 - x1);
+ while (width-- > 0)
+ waddstr(the_window, " ");
+}
+
+/*
+ * Work around limitation of curses's order-of-refresh by setting a pointer to
+ * the topmost window that should be displayed.
+ *
+ * FIXME: the associated call on 'keypad()' is not needed for Unix, but
+ * something in the OS/2 EMX port requires it.
+ */
+void LYsubwindow(WINDOW * param)
+{
+ if (param != 0) {
+ my_subwindow = param;
+#if defined(NCURSES) || defined(PDCURSES)
+ keypad(my_subwindow, TRUE);
+#if defined(USE_COLOR_STYLE)
+ LynxWChangeStyle(my_subwindow, s_menu_bg, STACK_ON);
+ {
+ long b = LYgetattrs(my_subwindow);
+
+ wbkgd(my_subwindow, (chtype) (b | ' '));
+ }
+ LynxWChangeStyle(my_subwindow, s_menu_bg, STACK_OFF);
+#elif defined(HAVE_GETBKGD) /* not defined in ncurses 1.8.7 */
+ wbkgd(my_subwindow, getbkgd(LYwin));
+#endif
+#endif
+ scrollok(my_subwindow, TRUE);
+ } else {
+ touchwin(LYwin);
+ delwin(my_subwindow);
+ my_subwindow = 0;
+ }
+}
+
+WINDOW *LYtopwindow(void)
+{
+ return (my_subwindow ? my_subwindow : LYwin);
+}
+#endif
+
+WINDOW *LYstartPopup(int *top_y,
+ int *left_x,
+ int *height,
+ int *width)
+{
+ WINDOW *form_window = 0;
+
+#ifdef USE_SLANG
+ static WINDOW fake_window;
+
+ if (*left_x < 1 || (*left_x + *width + 4) >= LYcolLimit) {
+ *left_x = 1;
+ *width = LYcolLimit - 5;
+ }
+
+ SLsmg_fill_region(*top_y,
+ *left_x - 1,
+ (unsigned) *height,
+ (unsigned) *width + 4,
+ ' ');
+ form_window = &fake_window;
+ form_window->top_y = *top_y;
+ form_window->left_x = *left_x;
+ form_window->height = *height;
+ form_window->width = *width;
+#else
+ if (*left_x > 0 && (*left_x + *width + 4) < LYcolLimit)
+ form_window = newwin(*height, *width + 4, *top_y, *left_x - 1);
+ if (form_window == 0) {
+ if (*width > LYcolLimit - 4) {
+ *width = LYcolLimit - 4;
+ *left_x = 1;
+ } else {
+ *left_x = LYcolLimit - 4 - *width;
+ if (*left_x <= 0)
+ *left_x = 1;
+ }
+ form_window = newwin(*height, *width + 4, *top_y, *left_x - 1);
+ }
+ if (form_window == 0) {
+ HTAlert(POPUP_FAILED);
+ } else {
+ LYsubwindow(form_window);
+ }
+#endif /* USE_SLANG */
+ return form_window;
+}
+
+void LYstartTargetEmphasis(void)
+{
+#ifdef USE_COLOR_STYLE
+ if (s_whereis != NOSTYLE) {
+ curses_style(s_whereis, STACK_ON);
+ return;
+ }
+#endif
+#if defined(FANCY_CURSES) || defined(USE_SLANG)
+ lynx_start_bold();
+ lynx_start_reverse();
+#endif /* FANCY_CURSES || USE_SLANG */
+ lynx_start_underline();
+}
+
+void LYstopTargetEmphasis(void)
+{
+#ifdef USE_COLOR_STYLE
+ if (s_whereis != NOSTYLE) {
+ curses_style(s_whereis, STACK_OFF);
+ return;
+ }
+#endif
+ lynx_stop_underline();
+#if defined(FANCY_CURSES) || defined(USE_SLANG)
+ lynx_stop_reverse();
+ lynx_stop_bold();
+#endif /* FANCY_CURSES || USE_SLANG */
+}
+
+/*
+ * Accommodate the different flavors of touchline
+ */
+void LYtouchline(int row)
+{
+#if defined(HAVE_WREDRAWLN) && !defined(NCURSES_VERSION)
+ wredrawln(LYwin, row, 1);
+#else
+#if defined(HAVE_TOUCHLINE)
+ /* touchline() is not available on VMS before version 7.0, and then only on
+ * Alpha, since prior ports of curses were broken. BSD touchline() has a
+ * 4th parameter since it is used internally by touchwin().
+ */
+#if defined(HAVE_BSD_TOUCHLINE)
+ touchline(LYwin, row, 0, COLS);
+#else
+ touchline(LYwin, row, 1);
+#endif
+#else
+#if !defined(USE_SLANG)
+ touchwin(LYwin);
+#else
+ SLsmg_touch_lines(row, 1);
+#endif
+#endif
+#endif
+}
+
+/*
+ * Wrapper for waddnstr().
+ */
+void LYwaddnstr(WINDOW * w GCC_UNUSED,
+ const char *src,
+ size_t len)
+{
+ int y0, x0;
+ int y, x;
+ size_t inx;
+
+ (void) y;
+ (void) y0;
+#ifdef USE_CURSES_PADS
+ /*
+ * If we've configured to use pads for left/right scrolling, that can
+ * interfere with calls to this function that assume they're wrapping.
+ * Writing to a pad which is wider than the screen will simply not wrap.
+ *
+ * Link-highlighting uses wrapping. You can see this by viewing the
+ * options screen in a terminal which is narrower than 80 columns.
+ *
+ * Check for that case, and use curses's wrapping in a derived window to
+ * simplify things, e.g., in case the string contains multibyte or
+ * multicolumn characters.
+ */
+ getyx(LYwin, y0, x0);
+
+ if (LYuseCursesPads
+ && (LYwin == w)
+ && (LYshiftWin == 0)
+ && LYwideLines == FALSE
+ && ((int) len > (LYcolLimit - x0))
+ && (y0 >= 0)
+ && (x0 >= 0)
+ && (x0 < LYcolLimit)) {
+ WINDOW *sub = derwin(LYwin, LYlines, LYcolLimit, 0, 0);
+
+ if (sub != 0) {
+ wmove(sub, y0, x0);
+ LYwideLines = TRUE;
+ LYwaddnstr(sub, src, len);
+ getyx(sub, y0, x0);
+ delwin(sub);
+ wmove(LYwin, y0, x0);
+ }
+ LYwideLines = FALSE;
+
+ return;
+ }
+#endif
+ /*
+ * We only want to trace this function for the color-style code. It would
+ * be too much logging if not needed.
+ */
+#ifdef USE_COLOR_STYLE
+ if (TRACE) {
+ LYGetYX(y, x);
+ CTRACE2(TRACE_STYLE, (tfp, "[%2d,%2d] LYwaddnstr(%.*s, %u)\n",
+ y, x, (int) len, src, (unsigned) len));
+ }
+#endif
+ LYGetYX(y0, x0);
+
+ for (inx = 0; inx < len; ++inx) {
+ /*
+ * Do tab-expansion relative to the base of the string (rather than
+ * the screen) so that tabs in a TEXTAREA will look right.
+ */
+ if (src[inx] == '\t') {
+ LYGetYX(y, x);
+ while ((++x - x0) % 8)
+ waddch(w, ' ');
+ waddch(w, ' ');
+ } else {
+ waddch(w, UCH(src[inx]));
+ }
+ }
+}
+
+/*
+ * Determine the number of cells the given string would take up on the screen,
+ * limited (in the case of wide characters) by the maxCells parameter.
+ *
+ * If the returnCellNum parameter is TRUE, return the number of cells;
+ * otherwise, return the length (limited by the len parameter) of the prefix of
+ * the string that fits in maxCells cells.
+ */
+static
+int LYstrExtent0(const char *string,
+ int len,
+ int maxCells GCC_UNUSED,
+ int retCellNum GCC_UNUSED)
+{
+ int used, result;
+
+ if (isEmpty(string)) {
+ used = ((len > 0) ? len : 0);
+ } else {
+ used = ((len < 0) ? (int) strlen(string) : len);
+ }
+ result = used;
+#ifdef WIDEC_CURSES
+ if (non_empty(string) && used > 0 && lynx_called_initscr) {
+ if (fake_max < maxCells) {
+ fake_max = (maxCells + 1) * 2;
+ if (fake_win != 0) {
+ delwin(fake_win);
+ fake_win = 0;
+ }
+ }
+ if (fake_win == 0) {
+ fake_win = newwin(2, fake_max, 0, 0);
+ }
+ if (fake_win != 0) {
+ int new_x = 0;
+ int new_y = 0;
+ int x = 0;
+ int n;
+
+ wmove(fake_win, 0, 0);
+ for (n = 0; n < used; ++n) {
+ if (IsNormalChar(string[n])) {
+ waddch(fake_win, UCH(string[n]));
+ getyx(fake_win, new_y, new_x);
+ if (new_y > 0 || new_x > maxCells)
+ break;
+ x = new_x;
+ }
+ }
+ result = (retCellNum ? x : n);
+ }
+ }
+#endif
+ return result;
+}
+
+/*
+ * Determine the number of cells the given string would take up on the screen,
+ * limited by the maxCells parameter. This is used for constructing aligned
+ * text in the options and similar forms.
+ *
+ * FIXME: make this account for wrapping, too.
+ * FIXME: make this useful for "lynx -dump", which hasn't initialized curses.
+ */
+int LYstrExtent(const char *string, int len, int maxCells)
+{
+ int result = LYstrExtent0(string, len, maxCells, TRUE);
+
+ return (result > maxCells ? maxCells : result);
+}
+
+/*
+ * Return the number of cells in the first 'len' bytes of the string.
+ *
+ * This relies upon the coincidence that multicell characters use at least as
+ * many bytes as cells. But we have to account for tab, which can use 8, and
+ * control characters which use 2.
+ */
+int LYstrExtent2(const char *string, int len)
+{
+ return LYstrExtent(string, len, 8 * len);
+}
+
+/*
+ * Determine the longest prefix of a string that fits in a given number of
+ * cells and return its length.
+ */
+int LYstrFittable(const char *string, int maxCells)
+{
+ return LYstrExtent0(string, -1, maxCells, FALSE);
+}
+
+/*
+ * Returns the total number of cells that the string would use.
+ */
+int LYstrCells(const char *string)
+{
+ return LYstrExtent2(string, (int) strlen(string));
+}
+
+#ifdef VMS
+/*
+ * Cut-down termio --
+ * Do character-oriented stream input for Jeff.
+ * Code ripped off from Micro-Emacs 3.7 by Daniel Lawrence.
+ *
+ * Ever-so-slightly modified by Kathryn Huxtable. 29-Jan-1991.
+ * Cut down for Lou. 8 Sep 1992.
+ * Cut down farther for Lou. 19 Apr 1993.
+ * We don't set PASSALL or PASTHRU since we don't
+ * want to block CTRL/C, CTRL/Y, CTRL/S or CTRL/Q.
+ * Simply setting NOECHO and doing timed reads
+ * is sufficient.
+ * Further mods by Fote. 29-June-1993
+ * ttopen() and ttclose() are now terminal initialization
+ * and restoration procedures, called once at startup
+ * and at exit, respectively, of the LYNX image.
+ * ttclose() should be called before an exit from LYNX
+ * no matter how the exit is invoked.
+ * setup(terminal) does the ttopen().
+ * cleanup() calls cleanup_files() and ttclose().
+ * ttgetc() now handles NOECHO and NOFLITR (instead of
+ * setting the terminal itself to NOECHO in ttopen()).
+ * VMSsignal() added for handling both Ctrl-C *and* Ctrl-Y
+ * interrupts, and disabling system response to Ctrl-T.
+ * Further mods by Fote. 15-Dec-1993
+ * Added edit handler in ttopen() which will invoke
+ * VMSexit() and behave intelligently on ACCVIO's.
+ * Further mods by Fote. 29-Dec-1993
+ * Simplified ttgetc().
+ * Further mods by Fote. 16-Jan-1994
+ * Added code in ttopen() which will invoke VMSVersion()
+ * to get the version of VMS as VersionVMS for use by
+ * by new or modified interrupt or spawning routines.
+ * Further mods by Fote. 27-Jan-1994
+ * Added back a typeahead() which supports 'z' or 'Z' as
+ * an "Zap transfer" command via HTCheckForInterrupt()
+ * in LYUtils.c.
+ */
+
+#include <descrip.h>
+#include <iodef.h>
+#include <ssdef.h>
+#include <msgdef.h>
+#include <ttdef.h>
+#include <tt2def.h>
+#include <libclidef.h>
+#include <lib$routines.h>
+#include <starlet.h>
+#include <clidef.h>
+#include <syidef.h>
+#ifdef signal
+#undef signal
+#endif /* signal */
+#include <signal.h>
+#ifdef system
+#undef system
+#endif /* system */
+#include <processes.h>
+#include <LYVMSdef.h>
+
+#define EFN 0 /* Event flag */
+
+static unsigned char buffer[20]; /* Input buffer */
+static int in_pos, in_len; /* For escape sequences */
+static int oldmode[3]; /* Old TTY mode bits */
+static int newmode[3]; /* New TTY mode bits */
+static short iochan; /* TTY I/O channel */
+static $DESCRIPTOR(term_nam_dsc, "TT"); /* Descriptor for iochan */
+static unsigned long mask = LIB$M_CLI_CTRLY | LIB$M_CLI_CTRLT; /* ^Y and ^T */
+static unsigned long old_msk; /* Saved control mask */
+static short trap_flag = FALSE; /* TRUE if AST is set */
+BOOLEAN DidCleanup = FALSE; /* Exit handler flag */
+static char VersionVMS[20]; /* Version of VMS */
+
+int VMSVersion(char *VerString,
+ int VerLen)
+{
+ unsigned long status, itm_cod = SYI$_VERSION;
+ int i, verlen = 0;
+ struct dsc$descriptor version;
+ char *m;
+
+ version.dsc$a_pointer = VerString;
+ version.dsc$w_length = VerLen - 1;
+ version.dsc$b_dtype = DSC$K_DTYPE_B;
+ version.dsc$b_class = DSC$K_CLASS_S;
+
+ status = lib$getsyi(&itm_cod, 0, &version, &verlen, 0, 0);
+ if (!(status & 1) || verlen == 0)
+ return 0;
+
+ /*
+ * Cut out trailing spaces
+ */
+ for (m = VerString + verlen, i = verlen - 1; i > 0 && VerString[i] == ' '; --i)
+ *(--m) = '\0';
+
+ return strlen(VerString) + 1; /* Transmit ending 0 too */
+}
+
+void VMSexit(void)
+{
+ /*
+ * If we get here and DidCleanup is not set, it was via an ACCVIO, or
+ * outofmemory forced exit, so make *sure* we attempt a cleanup and reset
+ * the terminal.
+ */
+ if (!DidCleanup) {
+ if (LYOutOfMemory == FALSE) {
+ fprintf(stderr,
+ gettext("\nA Fatal error has occurred in %s Ver. %s\n"),
+ LYNX_NAME, LYNX_VERSION);
+ fprintf(stderr,
+ gettext("\nPlease notify your system administrator to confirm a bug, and if\n\
+confirmed, to notify the lynx-dev list. Bug reports should have concise\n\
+descriptions of the command and/or URL which causes the problem, the\n\
+operating system name with version number, the TCPIP implementation, the\n\
+TRACEBACK if it can be captured, and any other relevant information.\n"));
+
+ if (LYTraceLogFP == NULL) {
+ fprintf(stderr, RETURN_TO_CLEANUP);
+ (void) getchar();
+ }
+ } else if (LYCursesON) {
+ HTAlert(MEMORY_EXHAUSTED_ABORT);
+ }
+ cleanup();
+ }
+ if (LYOutOfMemory == TRUE) {
+ printf("\r\n%s\r\n\r\n", MEMORY_EXHAUSTED_ABORT);
+ fflush(stdout);
+ fflush(stderr);
+ }
+}
+
+/*
+ * TTOPEN --
+ * This function is called once to set up the terminal
+ * device streams. It translates TT until it finds
+ * the terminal, then assigns a channel to it, sets it
+ * to EDIT, and sets up the Ctrl-C and Ctrl-Y interrupt
+ * handling.
+ */
+int ttopen(void)
+{
+ int iosb[2];
+ int status;
+ static unsigned long condition;
+ static struct _exit_block {
+ unsigned long forward;
+ unsigned long address;
+ unsigned long zero;
+ unsigned long condition;
+ } exit_handler_block;
+
+ status = sys$assign(&term_nam_dsc, &iochan, 0, 0);
+ if (status != SS$_NORMAL)
+ exit_immediately(status);
+
+ status = sys$qiow(EFN, iochan, IO$_SENSEMODE, &iosb, 0, 0,
+ &oldmode, sizeof(oldmode), 0, 0, 0, 0);
+ if (status != SS$_NORMAL)
+ exit_immediately(status);
+
+ status = iosb[0] & 0xFFFF;
+ if (status != SS$_NORMAL)
+ exit_immediately(status);
+
+ newmode[0] = oldmode[0];
+ newmode[1] = oldmode[1];
+ newmode[2] = oldmode[2] | TT2$M_EDIT;
+
+ status = sys$qiow(EFN, iochan, IO$_SETMODE, &iosb, 0, 0,
+ &newmode, sizeof(newmode), 0, 0, 0, 0);
+ if (status != SS$_NORMAL)
+ exit_immediately(status);
+
+ status = iosb[0] & 0xFFFF;
+ if (status != SS$_NORMAL)
+ exit_immediately(status);
+
+ /*
+ * Declare the exit handler block.
+ */
+ exit_handler_block.forward = 0;
+ exit_handler_block.address = (unsigned long) &VMSexit;
+ exit_handler_block.zero = 0;
+ exit_handler_block.condition = (unsigned long) &condition;
+ status = sys$dclexh(&exit_handler_block);
+ if (status != SS$_NORMAL)
+ exit_immediately(status);
+
+ /*
+ * Set the AST.
+ */
+ lib$disable_ctrl(&mask, &old_msk);
+ trap_flag = TRUE;
+ status = sys$qiow(EFN, iochan,
+ IO$_SETMODE | IO$M_CTRLCAST | IO$M_CTRLYAST,
+ &iosb, 0, 0,
+ &cleanup_sig, SIGINT, 0, 0, 0, 0);
+ if (status != SS$_NORMAL) {
+ lib$enable_ctrl(&old_msk);
+ exit_immediately(status);
+ }
+
+ /*
+ * Get the version of VMS.
+ */
+ if (VMSVersion(VersionVMS, 20) < 3)
+ /*
+ * Load zeros on error.
+ */
+ strcpy(VersionVMS, "V0.0-0");
+
+ return (0);
+} /* ttopen */
+
+/*
+ * TTCLOSE --
+ * This function gets called just before we go back home
+ * to the command interpreter. It puts the terminal back
+ * in a reasonable state.
+ */
+int ttclose(void)
+{
+ int status;
+ int iosb[1];
+
+ status = sys$qiow(EFN, iochan, IO$_SETMODE, &iosb, 0, 0,
+ &oldmode, sizeof(oldmode), 0, 0, 0, 0);
+
+ if (status != SS$_NORMAL || (iosb[0] & 0xFFFF) != SS$_NORMAL)
+ exit_immediately(status);
+
+ if (trap_flag) {
+ status = sys$dassgn(iochan);
+ status = lib$enable_ctrl(&old_msk);
+ trap_flag = FALSE;
+ }
+ return (0);
+} /* ttclose */
+
+/*
+ * TTGETC --
+ * Read a character from the terminal, with NOECHO and NOFILTR.
+ */
+int ttgetc(void)
+{
+ int status;
+ unsigned short iosb[4];
+
+ if (in_pos < in_len)
+ return (buffer[in_pos++]);
+
+ status = sys$qiow(EFN, iochan,
+ IO$_READVBLK | IO$M_NOECHO | IO$M_NOFILTR,
+ &iosb, 0, 0,
+ &buffer, 1, 0, 0, 0, 0);
+ if ((status & 1) == 1)
+ status = iosb[0];
+ if (status == SS$_PARTESCAPE) {
+ /*
+ * Escape sequence in progress. Fake a successful read.
+ */
+ status = 1;
+ }
+ if ((status & 1) != 1 && status != SS$_DATAOVERUN)
+ exit_immediately(status);
+ in_pos = 1;
+ in_len = iosb[1] + iosb[3];
+ return (buffer[0]);
+}
+
+/*
+ * TYPEAHEAD -- Fote Macrides 27-Jan-1994
+ * Check whether a keystroke has been entered, and return
+ * it, or -1 if none was entered.
+ */
+int typeahead(void)
+{
+ int status;
+ unsigned short iosb[4];
+
+ if (dump_output_immediately)
+ return -1;
+
+ if (in_pos < in_len)
+ return (buffer[in_pos++]);
+
+ again:
+ status = sys$qiow(EFN, iochan,
+ IO$_READVBLK | IO$M_TIMED | IO$M_NOECHO | IO$M_NOFILTR,
+ &iosb, 0, 0,
+ &buffer, 1, 0, 0, 0, 0);
+ if ((status & 1) == 1)
+ status = iosb[0];
+ if (status == SS$_PARTESCAPE) {
+ /*
+ * Escape sequence in progress, finish reading it.
+ */
+ goto again;
+ }
+
+ in_pos = 1;
+ in_len = iosb[1] + iosb[3];
+ if (status == SS$_TIMEOUT || status == SS$_DATAOVERUN)
+ return (-1);
+ return (buffer[0]);
+}
+
+/*
+ * VMSSIGNAL -- Fote Macrides 29-Jun-1993
+ * Sets up AST for both Ctrl-C and Ctrl-Y, with system response
+ * to Ctrl-T disabled. If called with a sig other than SIGINT,
+ * it will use the C library's system(sig, func).
+ * The equivalent of VMSsignal(SIGINT, cleanup_sig) is done on
+ * initialization by ttopen(), so don't do it again.
+ * VMSsignal(SIGINT, SIG_DFL) is treated as a call to ttclose().
+ * Call VMSsignal(SIGINT, SIG_IGN) before system() calls to
+ * enable Ctrl-C and Ctrl-Y in the subprocess, and then call
+ * VMSsignal(SIG_INT, cleanup_sig) on return from the subprocess.
+ * For func's which set flags and do not invoke an exit from
+ * LYNX, the func should reassert itself.
+ * The VMS signal() calls do not fully emulate the Unix calls,
+ * and VMSsignal() is just a "helper", also not a full emulation.
+ */
+
+void VMSsignal(int sig,
+ void (*func) ())
+{
+ int status;
+ short iosb[4];
+ static int SIG_IGN_flag;
+
+ /*
+ * Pass all signals other than SIGINT to signal().
+ * Also pass SIGINT to signal() if we're dumping.
+ */
+ if (sig != SIGINT || dump_output_immediately) {
+ signal(sig, func);
+ return;
+ }
+
+ /*
+ * If func is SIG_DFL, treat it as ttclose().
+ */
+ if (func == SIG_DFL) {
+ ttclose();
+ return;
+ }
+
+ /*
+ * Clear any previous AST.
+ */
+ if (trap_flag) {
+ status = sys$dassgn(iochan);
+ status = lib$enable_ctrl(&old_msk);
+ trap_flag = FALSE;
+ }
+
+ /*
+ * If func is SIG_IGN, leave the TT channel closed and the system response
+ * to interrupts enabled for system() calls.
+ */
+ if (func == SIG_IGN)
+ return;
+
+ /*
+ * If we get to here, we have a LYNX func, so set the AST.
+ */
+ lib$disable_ctrl(&mask, &old_msk);
+ trap_flag = TRUE;
+ status = sys$assign(&term_nam_dsc, &iochan, 0, 0);
+ status = sys$qiow(EFN, iochan,
+ IO$_SETMODE | IO$M_CTRLCAST | IO$M_CTRLYAST,
+ &iosb, 0, 0,
+ func, SIGINT, 0, 0, 0, 0);
+
+} /* VMSsignal */
+
+/*
+ * DCLspawn_exception, spawn_DCLprocess, DCLsystem -- F.Macrides 16-Jan-1994
+ * Exception-handler routines for regulating interrupts and enabling
+ * Control-T during spawns. Includes TRUSTED flag for versions of VMS
+ * which require it in captive accounts. This code should be used
+ * instead of the VAXC or DECC system(), by including LYUtils.h in
+ * modules which have system() calls. It helps ensure that we return
+ * to Lynx instead of breaking out to DCL if a user issues interrupts
+ * or generates an ACCVIO during spawns.
+ */
+#ifdef __DECC
+static unsigned int DCLspawn_exception(void *sigarr,
+ void *mecharr)
+#else
+static int DCLspawn_exception(void *sigarr,
+ void *mecharr)
+#endif /* __DECC */
+{
+ int status;
+
+ status = lib$sig_to_ret(sigarr, mecharr);
+ return (SS$_UNWIND);
+}
+
+static int spawn_DCLprocess(char *command)
+{
+ int status;
+ unsigned long Status = 0;
+
+ /*
+ * Keep DECC from complaining.
+ */
+ struct dsc$descriptor_s command_desc;
+
+ command_desc.dsc$w_length = strlen(command);
+ command_desc.dsc$b_class = DSC$K_CLASS_S;
+ command_desc.dsc$b_dtype = DSC$K_DTYPE_T;
+ command_desc.dsc$a_pointer = command;
+
+ VAXC$ESTABLISH(DCLspawn_exception);
+
+#ifdef __ALPHA /** OpenVMS/AXP lacked the TRUSTED flag before v6.1 **/
+ if (VersionVMS[1] > '6' ||
+ (VersionVMS[1] == '6' && VersionVMS[2] == '.' &&
+ VersionVMS[3] >= '1'))
+#else
+ if (VersionVMS[1] >= '6')
+#endif /* __ALPHA */
+ {
+ /*
+ * Include TRUSTED flag.
+ */
+ unsigned long trusted = CLI$M_TRUSTED;
+
+ status = lib$spawn(&command_desc, 0, 0, &trusted,
+ 0, 0, &Status);
+ /*
+ * If it was invalid, try again without the flag.
+ */
+ if (status == LIB$_INVARG)
+ status = lib$spawn(&command_desc, 0, 0, 0,
+ 0, 0, &Status);
+ } else
+ status = lib$spawn(&command_desc, 0, 0, 0,
+ 0, 0, &Status);
+ /*
+ * Return -1 on error.
+ */
+ if ((status & 1) != 1 || (Status & 1) != 1)
+ return (-1);
+ /*
+ * Return 0 on success.
+ */
+ return (0);
+}
+
+int DCLsystem(char *command)
+{
+ int status;
+
+ VMSsignal(SIGINT, SIG_IGN);
+ status = spawn_DCLprocess(command);
+ VMSsignal(SIGINT, cleanup_sig);
+ /*
+ * Returns 0 on success, -1 any error.
+ */
+ return (status);
+}
+#endif /* VMS */
+
+/*
+ * Return the physical screen dimensions that we're allowed to use.
+ */
+int LYscreenHeight(void)
+{
+ int result = LINES;
+
+ if (result <= 0)
+ result = DFT_ROWS;
+ return result;
+}
+
+int LYscreenWidth(void)
+{
+ int result = COLS;
+
+#if defined(PDCURSES_EXP) && defined(WIN_EX) && defined(CJK_EX) /* 1999/08/26 (Thu) 17:53:38 */
+ {
+ extern int current_codepage; /* PDCurses lib. */
+
+ if (current_codepage == 932)
+ result--;
+ }
+#endif
+ if (result <= 0)
+ result = DFT_COLS;
+ return result;
+}
+
+/*
+ * Set the window's background color (make the pad's color agree), e.g., when
+ * we have just parsed it from the config file, or after clearing the screen.
+ */
+void LYnormalColor(void)
+{
+#if defined(USE_COLOR_STYLE) && defined(USE_CURSES_PADS)
+ if (LYwin != stdscr) {
+ int color = displayStyles[DSTYLE_NORMAL].color;
+
+ if (color >= 0) {
+ wbkgd(LYwin, (chtype) (color | ' '));
+ LYrefresh();
+ }
+ }
+#endif
+}
+
+/*
+ * The functions ifdef'd with USE_CURSES_PADS are implemented that way so we
+ * don't break the slang configuration.
+ */
+void LYclear(void)
+{
+#ifdef USE_CURSES_PADS
+ wclear(LYwin);
+#else
+ clear();
+#endif
+ LYnormalColor();
+}
+
+void LYclrtoeol(void)
+{
+#ifdef USE_CURSES_PADS
+ wclrtoeol(LYwin);
+#else
+ clrtoeol();
+#endif
+}
+
+void LYerase(void)
+{
+#ifdef USE_CURSES_PADS
+ werase(LYwin);
+#else
+ erase();
+#endif
+ LYnormalColor();
+}
+
+void LYmove(int y, int x)
+{
+#ifdef USE_CURSES_PADS
+ wmove(LYwin, y, x);
+#else
+ move(y, x);
+#endif
+}
+
+void LYrefresh(void)
+{
+#ifdef USE_CURSES_PADS
+ if (LYwin != stdscr) {
+ /*
+ * Workaround for special case where lynx is prompting for a mailto,
+ * and has a subject line that is wider than the screen. The
+ * wnoutrefresh() call resets newscr's position to match stdscr's,
+ * which happens to be the window's origin because we were not updating
+ * that, and other stray wmove's in lynx fail because the coordinate
+ * is on/after the right margin. Force things to look ok here.
+ */
+ int y, x;
+
+ getyx(LYwin, y, x);
+ if (y < 0)
+ y = 0;
+ if (x < 0)
+ x = 0;
+ if (x > LYcolLimit)
+ x = LYcolLimit;
+ wmove(stdscr, y, x);
+
+ wnoutrefresh(stdscr);
+ pnoutrefresh(LYwin, 0, LYshiftWin, 0, 0, LYlines, LYscreenWidth() - 1);
+
+ /*
+ * Keep a popup window visible. This can happen if the user presses
+ * '/' to do a search within a popup.
+ */
+ if (my_subwindow != 0) {
+ touchwin(my_subwindow);
+ wnoutrefresh(my_subwindow);
+ }
+ doupdate();
+ } else {
+ refresh();
+ }
+#else
+ refresh();
+#endif
+}
+
+void lynx_force_repaint(void)
+{
+ clearok(curscr, TRUE);
+}
+
+void lynx_start_title_color(void)
+{
+#ifdef SH_EX
+ lynx_start_reverse();
+#endif
+}
+
+void lynx_stop_title_color(void)
+{
+#ifdef SH_EX
+ lynx_stop_reverse();
+#endif
+}
+
+void lynx_start_link_color(int flag,
+ int pending)
+{
+ if (flag) {
+ /* makes some terminals work wrong because
+ * they can't handle two attributes at the
+ * same time
+ */
+ /* lynx_start_bold(); */
+ lynx_start_reverse();
+#if defined(USE_SLANG)
+#ifndef __DJGPP__
+ if (SLtt_Use_Ansi_Colors)
+#endif /* !__DJGPP__ */
+ lynx_start_underline();
+#endif /* USE_SLANG */
+#if defined(FANCY_CURSES) && defined(COLOR_CURSES)
+ if (lynx_has_color && LYShowColor >= SHOW_COLOR_ON)
+ lynx_start_underline();
+#endif /* USE_SLANG */
+ } else {
+ lynx_start_bold();
+ /*
+ * Make sure when flag is OFF that "unhighlighted" links will be
+ * underlined if appropriate. - LE & FM
+ */
+ if (pending)
+ lynx_start_underline();
+ }
+}
+
+void lynx_stop_link_color(int flag,
+ int pending GCC_UNUSED)
+{
+#ifdef USE_COLOR_STYLE
+ LynxChangeStyle(flag == TRUE ? s_alink : s_a, ABS_OFF);
+#else
+ if (flag) {
+ lynx_stop_reverse();
+#if defined(USE_SLANG)
+#ifndef __DJGPP__
+ if (SLtt_Use_Ansi_Colors)
+#endif /* !__DJGPP__ */
+ lynx_stop_underline();
+#endif /* USE_SLANG */
+#if defined(FANCY_CURSES) && defined(COLOR_CURSES)
+ if (lynx_has_color && LYShowColor >= SHOW_COLOR_ON)
+ lynx_stop_underline();
+#endif /* FANCY_CURSES && COLOR_CURSES */
+ } else {
+ lynx_stop_bold();
+ /*
+ * If underlining was turned on above, turn it off. - LE & FM
+ */
+ if (pending)
+ lynx_stop_underline();
+ }
+#endif
+}
+
+/* FIXME: consider inlining these */
+
+void lynx_stop_target_color(void)
+{
+ lynx_stop_underline();
+ lynx_stop_reverse();
+ lynx_stop_bold();
+}
+
+void lynx_start_target_color(void)
+{
+ lynx_start_bold();
+ lynx_start_reverse();
+ lynx_start_underline();
+}
+
+void lynx_start_status_color(void)
+{
+#if defined(USE_COLOR_TABLE) && defined(COLOR_CURSES)
+ if (lynx_has_color && LYShowColor >= SHOW_COLOR_ON)
+ lynx_set_color(2);
+ else
+#endif
+ lynx_start_reverse();
+}
+
+void lynx_stop_status_color(void)
+{
+#if defined(USE_COLOR_TABLE) && defined(COLOR_CURSES)
+ if (lynx_has_color && LYShowColor >= SHOW_COLOR_ON)
+ lynx_set_color(0);
+ else
+#endif
+ lynx_stop_reverse();
+}
+
+void lynx_start_h1_color(void)
+{
+ if (bold_H1 || bold_headers)
+ lynx_start_bold();
+}
+
+void lynx_stop_h1_color(void)
+{
+ if (bold_H1 || bold_headers)
+ lynx_stop_bold();
+}
+
+void lynx_start_prompt_color(void)
+{
+ lynx_start_reverse();
+}
+
+void lynx_stop_prompt_color(void)
+{
+ lynx_stop_reverse();
+}
+
+void lynx_start_radio_color(void)
+{
+ lynx_start_bold();
+}
+
+void lynx_stop_radio_color(void)
+{
+ lynx_stop_bold();
+}
+
+void lynx_stop_all_colors(void)
+{
+ lynx_stop_underline();
+ lynx_stop_reverse();
+ lynx_stop_bold();
+}
+
+/*
+ * Wrappers for LYUnderlineLinks flag.
+ */
+void lynx_start_bold(void)
+{
+ start_bold();
+}
+
+void lynx_start_reverse(void)
+{
+ start_reverse();
+}
+
+void lynx_start_underline(void)
+{
+ start_underline();
+}
+
+void lynx_stop_bold(void)
+{
+ stop_bold();
+}
+
+void lynx_stop_reverse(void)
+{
+ stop_reverse();
+}
+
+void lynx_stop_underline(void)
+{
+ stop_underline();
+}
+
+void LYSetDisplayLines(void)
+{
+ if (!no_title) {
+ if (user_mode == NOVICE_MODE)
+ display_lines = LYlines - 4;
+ else
+ display_lines = LYlines - 2;
+ } else if (user_mode == NOVICE_MODE) {
+ display_lines = LYlines - 3;
+ } else {
+ display_lines = LYlines - 1;
+ }
+}
+
+/*
+ * If LYShowCursor is ON, move the cursor to the left of the current option, so
+ * that blind users, who are most likely to have LYShowCursor ON, will have
+ * it's string spoken or passed to the braille interface as each option is made
+ * current. Otherwise, move it to the bottom, right column of the screen, to
+ * "hide" the cursor as for the main document, and let sighted users rely on
+ * the current option's highlighting or color without the distraction of a
+ * blinking cursor in the window. - FM
+ */
+void LYstowCursor(WINDOW * win, int row, int col)
+{
+ if (LYShowCursor) {
+ wmove(win, row, col);
+ } else {
+ LYHideCursor();
+ }
+#ifdef USE_SLANG
+ SLsmg_refresh();
+#else
+ wrefresh(win);
+#endif /* USE_SLANG */
+}
+
+#if defined(USE_BLINK) && defined(__EMX__) /* Can't put it earlier due to BOOLEAN conflict */
+# define BOOLEAN os2BOOLEAN
+# define INCL_VIO
+# include "os2.h"
+static void make_blink_boldbg(void)
+{
+ VIOINTENSITY buf; /* VIO windows have it anyway, */
+
+ /* but FS session need a switch */
+ buf.cb = sizeof(buf);
+ buf.type = 2; /* VIOINTENSITY request */
+ buf.fs = 1; /* Intensity == boldbg */
+ VioSetState(&buf, 0);
+}
+#endif
+
+#if defined(HAVE_WATTR_GET)
+/*
+ * getattrs() is not in X/Open curses, but it is more convenient than this.
+ */
+long LYgetattrs(WINDOW * win)
+{
+ long result;
+
+#if ( defined(HAVE_GETATTRS) && ( !defined(NCURSES_VERSION_MAJOR) || NCURSES_VERSION_MAJOR < 5 ) )
+
+ result = getattrs(win);
+#else
+ attr_t attrs = 0;
+ short pair = 0;
+
+ /*
+ * FIXME: this ignores the color-pair, which for most implementations is
+ * not stored in the attribute value.
+ */
+ (void) (wattr_get) (win, &attrs, &pair, NULL);
+ result = (long) attrs;
+#endif
+ return result;
+}
+#endif /* HAVE_WATTR_GET */
+
+#if defined(NCURSES_VERSION_PATCH) && NCURSES_VERSION_PATCH > 20021012
+#ifndef HAVE_USE_LEGACY_CODING
+/*
+ * Between ncurses 5.3 and 5.4 as part of fixes for wide-character mode, the
+ * locale support no longer allows characters in the range 128-159 to be
+ * treated as printable characters. Here is a workaround to fool
+ * waddch_nosync() into treating "all" 8-bit characters as printable.
+ */
+NCURSES_CONST char *unctrl(chtype ch)
+{
+ static char result[3];
+ unsigned data = (unsigned char) ch;
+
+ if (data < 32) {
+ result[0] = '^';
+ result[1] = ch | '@';
+ result[2] = 0;
+ } else if (data == 127) {
+ result[0] = '^';
+ result[1] = '?';
+ result[2] = 0;
+ } else {
+ result[0] = data;
+ result[1] = 0;
+ }
+ return result;
+}
+#endif /* HAVE_USE_LEGACY_CODING */
+#endif
diff --git a/src/LYCurses.h b/src/LYCurses.h
new file mode 100644
index 0000000..f152ce2
--- /dev/null
+++ b/src/LYCurses.h
@@ -0,0 +1,865 @@
+/* $LynxId: LYCurses.h,v 1.98 2023/10/23 23:35:36 tom Exp $ */
+#ifndef LYCURSES_H
+#define LYCURSES_H
+
+#ifndef HTUTILS_H
+#include <HTUtils.h>
+#endif
+
+/*
+ * Because we have to configure PDCURSES last, we may get bogus definitions
+ * from the system curses library - cancel these now.
+ */
+#ifdef HAVE_XCURSES
+
+#undef ASSUME_DEFAULT_COLORS
+#undef COLOR_CURSES
+#undef FANCY_CURSES
+#undef HAVE_CBREAK
+#undef HAVE_RESIZETERM
+#undef HAVE_USE_DEFAULT_COLORS
+#undef NCURSES
+#undef USE_DEFAULT_COLORS
+
+#define HAVE_CBREAK 1
+#define COLOR_CURSES 1
+#define FANCY_CURSES 1
+
+#endif
+
+/*
+ * The simple color scheme maps the 8 combinations of bold/underline/reverse
+ * to the standard 8 ANSI colors (with some variations based on context).
+ */
+#undef USE_COLOR_TABLE
+
+#ifdef USE_COLOR_STYLE
+#define USE_COLOR_TABLE 1 /* default color logic is used */
+#else
+#if defined(USE_SLANG) || defined(COLOR_CURSES)
+#define USE_COLOR_TABLE 1
+#endif
+#endif
+
+#ifdef TRUE
+#undef TRUE /* to prevent parse error :( */
+#endif /* TRUE */
+#ifdef FALSE
+#undef FALSE /* to prevent parse error :( */
+#endif /* FALSE */
+
+#ifdef USE_SLANG
+#define ENABLE_SLFUTURE_CONST 1
+#include <slang.h>
+#ifndef SLFUTURE_CONST
+#define SLFUTURE_CONST /* nothing */
+#endif
+typedef unsigned long chtype;
+
+#undef WINDOW
+typedef struct {
+ int top_y;
+ int left_x;
+ int height;
+ int width;
+} WINDOW;
+
+/* slang doesn't really do windows... */
+#define waddch(w,c) LYaddch(c)
+#define waddstr(w,s) addstr(s)
+#define wmove(win, row, col) SLsmg_gotorc(((win)?(win)->top_y:0) + (row), ((win)?(win)->left_x:0) + (col))
+
+#ifndef SLSMG_UARROW_CHAR
+#define SLSMG_UARROW_CHAR '^'
+#endif
+
+#ifndef SLSMG_DARROW_CHAR
+#define SLSMG_DARROW_CHAR 'v'
+#endif
+
+#ifndef SLSMG_LARROW_CHAR
+#define SLSMG_LARROW_CHAR '<'
+#endif
+
+#ifndef SLSMG_RARROW_CHAR
+#define SLSMG_RARROW_CHAR '>'
+#endif
+
+#ifndef SLSMG_CKBRD_CHAR
+#define SLSMG_CKBRD_CHAR '#'
+#endif
+
+#ifndef SLSMG_BLOCK_CHAR
+#define SLSMG_BLOCK_CHAR '#'
+#endif
+
+#ifndef ACS_UARROW
+#define ACS_UARROW SLSMG_UARROW_CHAR
+#endif
+
+#ifndef ACS_DARROW
+#define ACS_DARROW SLSMG_DARROW_CHAR
+#endif
+
+#ifndef ACS_LARROW
+#define ACS_LARROW SLSMG_LARROW_CHAR
+#endif
+
+#ifndef ACS_RARROW
+#define ACS_RARROW SLSMG_RARROW_CHAR
+#endif
+
+#ifndef ACS_CKBOARD
+#define ACS_CKBOARD SLSMG_CKBRD_CHAR
+#endif
+
+#ifndef ACS_BLOCK
+#define ACS_BLOCK SLSMG_BLOCK_CHAR
+#endif
+
+#else /* Using curses: */
+
+#ifdef VMS
+#define FANCY_CURSES
+
+#endif /* VMS */
+
+#ifndef HAVE_TYPE_CHTYPE
+
+#ifdef __PDCURSES__
+#define HAVE_TYPE_CHTYPE 1
+#endif
+
+#if defined(_VMS_CURSES) || defined(VMS)
+typedef char chtype;
+
+#define HAVE_TYPE_CHTYPE 1
+#endif
+
+#endif /* ! HAVE_TYPE_CHTYPE */
+
+/*
+ * CR may be defined before the curses.h include occurs.
+ * There is a conflict between the termcap char *CR and the define.
+ * Assuming that the definition of CR will always be carriage return.
+ * 06-09-94 Lynx 2-3-1 Garrett Arch Blythe
+ */
+#ifdef CR
+#undef CR /* to prevent parse error :( */
+#define REDEFINE_CR
+#endif /* CR */
+
+#ifdef HZ
+#undef HZ /* to prevent parse error :( */
+#endif /* HZ */
+
+/* SunOS 4.x has a redefinition between ioctl.h and termios.h */
+#if defined(sun) && !defined(__SVR4)
+#undef NL0
+#undef NL1
+#undef CR0
+#undef CR1
+#undef CR2
+#undef CR3
+#undef TAB0
+#undef TAB1
+#undef TAB2
+#undef XTABS
+#undef BS0
+#undef BS1
+#undef FF0
+#undef FF1
+#undef ECHO
+#undef NOFLSH
+#undef TOSTOP
+#undef FLUSHO
+#undef PENDIN
+#endif
+
+#if defined(_MSC_VER)
+#undef MOUSE_MOVED /* conflict between PDCURSES and _WIN32 */
+#endif /* _MSC_VER */
+
+/*
+ * Do this to build with glibc 2.1.3 (apparently it was not used to build a
+ * system before release).
+ */
+#include <signal.h>
+
+#undef CS /* some BSD versions of curses use this */
+#define CS curses_CS /* ...but we don't */
+
+#ifdef ERR
+#undef ERR /* all versions of curses define this */
+#endif
+
+#ifdef KEY_EVENT
+#undef KEY_EVENT /* wincon.h or Cygwin's copy of it */
+#endif
+
+#ifdef MOUSE_MOVED
+#undef MOUSE_MOVED /* wincon.h or MINGW32's copy of it */
+#endif
+
+#ifdef HAVE_CONFIG_H
+# ifdef HAVE_NCURSESW_NCURSES_H
+# undef GCC_PRINTFLIKE /* <libutf8.h> may define 'printf' */
+# include <ncursesw/ncurses.h>
+# undef printf /* but we don't want that... */
+# else
+# ifdef HAVE_NCURSES_NCURSES_H
+# include <ncurses/ncurses.h>
+# else
+# ifdef HAVE_NCURSES_H
+# include <ncurses.h>
+# else
+# ifdef HAVE_CURSESX_H
+# include <cursesX.h> /* Ultrix */
+# else
+# ifdef HAVE_JCURSES_H
+# include <jcurses.h> /* sony_news */
+# else
+# ifdef HAVE_XCURSES
+# include <xcurses.h> /* PDCurses' UNIX port */
+# else
+# include <curses.h> /* default */
+# endif
+# endif
+# endif
+# endif
+# endif
+# endif
+
+# if defined(wgetbkgd) && !defined(getbkgd)
+# define getbkgd(w) wgetbkgd(w) /* workaround pre-1.9.9g bug */
+# endif
+
+# ifdef FANCY_CURSES
+# if defined(NCURSES) && defined(HAVE_NCURSESW_TERM_H)
+# include <ncursesw/term.h>
+# else
+# if defined(NCURSES) && defined(HAVE_NCURSES_TERM_H)
+# include <ncurses/term.h>
+# else
+# if defined(HAVE_NCURSESW_NCURSES_H) || defined(HAVE_NCURSES_NCURSES_H) || defined(HAVE_XCURSES)
+# undef HAVE_TERM_H /* only use one in comparable path! */
+# endif
+# if defined(HAVE_TERM_H)
+# include <term.h>
+# endif
+# endif
+# endif
+# endif
+
+# if defined(NCURSES_VERSION) && defined(HAVE_DEFINE_KEY)
+# define USE_KEYMAPS 1
+# endif
+
+#else
+# if defined(VMS) && defined(__GNUC__)
+# include <LYGCurses.h>
+# else
+# include <curses.h> /* everything else */
+# endif /* VMS && __GNUC__ */
+#endif /* HAVE_CONFIG_H */
+
+/*
+ * PDCurses' mouse code does nothing in the DJGPP configuration.
+ */
+#if defined(PDCURSES) && !defined(__DJGPP__) && !defined(HAVE_XCURSES)
+#define USE_MOUSE 1
+#endif
+
+/*
+ * Pick up the native ncurses name:
+ */
+#if defined(NCURSES_MOUSE_VERSION)
+#define USE_MOUSE 1
+#endif
+
+/*
+ * For systems where select() does not work for TTY's, we can poll using
+ * curses.
+ */
+#if defined(_WINDOWS) || defined(__MINGW32__)
+#if defined(PDCURSES) && defined(PDC_BUILD) && PDC_BUILD >= 2401
+#define USE_CURSES_NODELAY 1
+#endif
+
+#if defined(NCURSES_VERSION)
+#define USE_CURSES_NODELAY 1
+#endif
+#endif /* _WINDOWS || __MINGW32__ */
+
+#if defined(NCURSES_VERSION) && defined(__BEOS__)
+#define USE_CURSES_NODELAY 1
+#endif
+
+/*
+ * If we have pads, use them to implement left/right scrolling.
+ */
+#if defined(HAVE_NEWPAD) && defined(HAVE_PNOUTREFRESH) && !defined(PDCURSES)
+#define USE_CURSES_PADS 1
+#endif
+
+/*
+ * ncurses 1.9.9e won't work for pads, but 4.2 does (1.9.9g doesn't have a
+ * convenient ifdef, though it would work).
+ */
+#if defined(NCURSES_VERSION) && !defined(NCURSES_VERSION_MAJOR)
+#undef USE_CURSES_PADS
+#endif
+
+/*
+ * Most implementations of curses treat pair 0 specially, as the default
+ * foreground and background color. Also, the COLORS variable corresponds to
+ * the total number of colors.
+ *
+ * PDCurses does not follow these rules. Its COLORS variable claims it has
+ * 8 colors, but it actually implements 16. That makes it hard to optimize
+ * color settings against color pair 0 in a portable fashion.
+ */
+#if defined(COLOR_CURSES)
+#if defined(PDCURSES) || defined(HAVE_XCURSES)
+#define COLORS 16 /* should be a variable... */
+#else
+#define USE_CURSES_PAIR_0
+#endif
+#endif
+
+#if defined(_WINDOWS) && defined(PDCURSES) && defined(PDC_BUILD) && PDC_BUILD >= 2401
+#define USE_MAXSCREEN_TOGGLE 1
+extern void maxmizeWindowSize(void);
+extern void recoverWindowSize(void);
+#endif
+
+#endif /* USE_SLANG */
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+#ifdef USE_SLANG
+#define LYstopPopup() /* nothing */
+#define LYtopwindow() LYwin
+#else
+ extern void LYsubwindow(WINDOW * param);
+ extern WINDOW *LYtopwindow(void);
+
+#define LYstopPopup() LYsubwindow(0)
+#endif /* NCURSES */
+
+ extern void LYbox(WINDOW * win, int formfield);
+ extern WINDOW *LYstartPopup(int *top_y, int *left_x, int *height, int *width);
+
+/*
+ * Useful macros not in PDCurses or very old ncurses headers.
+ */
+#if !defined(HAVE_GETBEGX) && !defined(getbegx)
+#define getbegx(win) ((win)->_begx)
+#endif
+#if !defined(HAVE_GETBEGY) && !defined(getbegy)
+#define getbegy(win) ((win)->_begy)
+#endif
+#if !defined(HAVE_GETBKGD) && !defined(getbkgd)
+#define getbkgd(win) ((win)->_bkgd)
+#endif
+
+#if defined(HAVE_WATTR_GET)
+ extern long LYgetattrs(WINDOW * win);
+
+#else
+#if defined(HAVE_GETATTRS) || defined(getattrs)
+#define LYgetattrs(win) getattrs(win)
+#else
+#define LYgetattrs(win) ((win)->_attrs)
+#endif
+#endif /* HAVE_WATTR_GET */
+
+#if defined(PDCURSES)
+#define HAVE_GETBKGD 1 /* can use fallback definition */
+#define HAVE_NAPMS 1 /* can use millisecond-delays */
+# if defined(PDC_BUILD) && PDC_BUILD >= 2401
+ extern int saved_scrsize_x;
+ extern int saved_scrsize_y;
+# endif
+#endif
+
+#ifdef HAVE_NAPMS
+#define SECS2Secs(n) (1000 * (n))
+#define Secs2SECS(n) ((n) / 1000.0)
+#define SECS_FMT "%.3f"
+#else
+#define SECS2Secs(n) (n)
+#define Secs2SECS(n) (n)
+#define SECS_FMT "%.0f"
+#endif
+
+#ifdef NCURSES_VERSION
+ extern void _nc_freeall(void); /* HAVE__NC_FREEALL */
+ extern void _nc_free_and_exit(int); /* HAVE__NC_FREE_AND_EXIT */
+#endif
+
+/* Both slang and curses: */
+#ifndef TRUE
+#define TRUE 1
+#endif /* !TRUE */
+#ifndef FALSE
+#define FALSE 0
+#endif /* !FALSE */
+
+#ifdef REDEFINE_CR
+#define CR FROMASCII('\015')
+#endif /* REDEFINE_CR */
+
+#ifdef ALT_CHAR_SET
+#define BOXVERT 0 /* use alt char set for popup window vertical borders */
+#define BOXHORI 0 /* use alt char set for popup window vertical borders */
+#endif
+
+#ifndef BOXVERT
+#define BOXVERT '*' /* character for popup window vertical borders */
+#endif
+#ifndef BOXHORI
+#define BOXHORI '*' /* character for popup window horizontal borders */
+#endif
+
+#ifndef KEY_DOWN
+#undef HAVE_KEYPAD /* avoid confusion with bogus 'keypad()' */
+#endif
+
+ extern int LYlines; /* replaces LINES */
+ extern int LYcols; /* replaces COLS */
+
+/*
+ * Check if the SIGWINCH handler has caught a signal, before testing the
+ * recent_sizechange variable.
+ */
+#ifdef SIGWINCH
+#if defined(KEY_RESIZE) && defined(HAVE_RESIZETERM) && defined(HAVE_WRESIZE)
+#define USE_CURSES_RESIZE 1
+#endif
+
+#if defined(USE_CURSES_RESIZE) || defined(USE_SLANG)
+#define CheckScreenSize() \
+ do { \
+ if (size_is_changed) { \
+ size_is_changed = FALSE; \
+ LYGetScreenSize(SIGWINCH); \
+ } \
+ } while (0)
+#endif
+#endif
+
+#ifndef CheckScreenSize
+#define CheckScreenSize() /* nothing */
+#endif
+
+/*
+ * The scrollbar, if used, occupies the rightmost column.
+ */
+#ifdef USE_SCROLLBAR
+#define LYbarWidth (LYShowScrollbar ? 1 : 0)
+#else
+#define LYbarWidth 0
+#endif
+
+/*
+ * Usable limits for display:
+ */
+#if defined(FANCY_CURSES) || defined(USE_SLANG)
+#if defined(PDCURSES)
+#define LYcolLimit (LYcols - LYbarWidth - 1) /* PDCurses wrapping is buggy */
+#else
+#define LYcolLimit (LYcols - LYbarWidth)
+#endif
+#else
+#define LYcolLimit (LYcols - 1)
+#endif
+
+#ifdef USE_CURSES_PADS
+ extern WINDOW *LYwin;
+ extern int LYshiftWin;
+ extern int LYwideLines;
+ extern int LYtableCols;
+ extern BOOLEAN LYuseCursesPads;
+
+#else
+#define LYwin stdscr
+#define LYshiftWin 0
+#define LYwideLines 0
+#define LYtableCols 0
+#endif
+
+ extern BOOLEAN setup(char *terminal);
+ extern int LYscreenHeight(void);
+ extern int LYscreenWidth(void);
+ extern int LYstrExtent(const char *string, int len, int maxCells);
+ extern int LYstrExtent2(const char *string, int len);
+ extern int LYstrFittable(const char *string, int maxCells);
+ extern int LYstrCells(const char *string);
+ extern void LYclear(void);
+ extern void LYclrtoeol(void);
+ extern void LYerase(void);
+ extern void LYmove(int y, int x);
+ extern void LYnoVideo(int mask);
+ extern void LYnormalColor(void);
+ extern void LYpaddstr(WINDOW * w, int width, const char *s);
+ extern void LYrefresh(void);
+ extern void LYstartTargetEmphasis(void);
+ extern void LYstopTargetEmphasis(void);
+ extern void LYtouchline(int row);
+ extern void LYwaddnstr(WINDOW * w, const char *s, size_t len);
+ extern void start_curses(void);
+ extern void stop_curses(void);
+
+#define LYaddstr(s) LYwaddnstr(LYwin, s, strlen(s))
+
+#ifdef VMS
+ extern int DCLsystem(char *command);
+ extern void VMSexit();
+ extern int ttopen();
+ extern int ttclose();
+ extern int ttgetc();
+ extern void VMSsignal(int sig, void (*func) ());
+#endif /* VMS */
+
+#if defined(USE_COLOR_STYLE)
+ extern void add_to_lss_list(const char *source, const char *resolved);
+ extern void clear_lss_list(void);
+ extern void curses_css(char *name, int dir);
+ extern void curses_style(int style, int dir);
+ extern void curses_w_style(WINDOW * win, int style, int dir);
+ extern void init_color_styles(char **from_cmdline, const char *default_styles);
+ extern void reinit_color_styles(void);
+ extern void setHashStyle(int style, int color, int cattr, int mono, const char *element);
+ extern void setStyle(int style, int color, int cattr, int mono);
+ extern void update_color_style(void);
+ extern void wcurses_css(WINDOW * win, char *name, int dir);
+
+# define LynxChangeStyle(style,dir) curses_style(style,dir)
+# define LynxWChangeStyle(win,style,dir) curses_w_style(win,style,dir)
+#else
+# define LynxWChangeStyle(win,style,dir) (void)1
+#endif /* USE_COLOR_STYLE */
+
+#ifdef USE_COLOR_TABLE
+ extern void LYaddAttr(int a);
+ extern void LYsubAttr(int a);
+ extern void lynx_setup_colors(void);
+ extern unsigned Lynx_Color_Flags;
+#endif
+
+#if defined(USE_COLOR_TABLE) || defined(USE_SLANG)
+ extern int Current_Attr;
+#endif
+
+#ifdef USE_SLANG
+#define SHOW_WHEREIS_TARGETS 1
+
+#if !defined(VMS) && !defined(DJGPP)
+#define USE_MOUSE 1
+#endif
+
+#if !defined(__DJGPP__) && !defined(__CYGWIN__)
+#define USE_KEYMAPS 1
+#endif
+
+#define SL_LYNX_USE_COLOR 1
+#define SL_LYNX_OVERRIDE_COLOR 2
+
+#define start_bold() LYaddAttr(LYUnderlineLinks ? 4 : 1)
+#define start_reverse() LYaddAttr(2)
+#define start_underline() LYaddAttr(LYUnderlineLinks ? 1 : 4)
+#define stop_bold() LYsubAttr(LYUnderlineLinks ? 4 : 1)
+#define stop_reverse() LYsubAttr(2)
+#define stop_underline() LYsubAttr(LYUnderlineLinks ? 1 : 4)
+
+#ifdef FANCY_CURSES
+#undef FANCY_CURSES
+#endif /* FANCY_CURSES */
+
+/*
+ * Map some curses functions to slang functions.
+ */
+#define stdscr ((WINDOW *)0)
+#define COLS SLtt_Screen_Cols
+#define LINES SLtt_Screen_Rows
+#define move SLsmg_gotorc
+#define addstr SLsmg_write_string
+ extern void LY_SLerase(void);
+
+#define erase LY_SLerase
+#define clear LY_SLerase
+#define standout SLsmg_reverse_video
+#define standend SLsmg_normal_video
+#define clrtoeol SLsmg_erase_eol
+
+#ifdef SLSMG_NEWLINE_SCROLLS
+#define scrollok(a,b) SLsmg_Newline_Behavior \
+ = ((b) ? SLSMG_NEWLINE_SCROLLS : SLSMG_NEWLINE_MOVES)
+#else
+#define scrollok(a,b) SLsmg_Newline_Moves = ((b) ? 1 : -1)
+#endif
+
+#define LYaddch(ch) SLsmg_write_char(ch)
+
+#if SLANG_VERSION >= 20000
+#define addch_raw(ch) do { \
+ SLsmg_Char_Type buf; \
+ buf.nchars = 1; \
+ buf.wchars[0] = ch; \
+ buf.color = Current_Attr; \
+ SLsmg_write_raw (&buf, 1); \
+ } while (0)
+#else
+#define addch_raw(ch) do { \
+ SLsmg_Char_Type buf; \
+ buf = (ch) | (Current_Attr << 4); \
+ SLsmg_write_raw (&buf, 1); \
+ } while (0)
+#endif /* SLANG_VERSION >= 20000 */
+
+#define echo()
+#define printw SLsmg_printf
+
+ extern int curscr;
+ extern BOOLEAN FullRefresh;
+
+#ifdef clearok
+#undef clearok
+#endif /* clearok */
+#define clearok(a,b) { FullRefresh = (BOOLEAN)b; }
+ extern void LY_SLrefresh(void);
+
+#ifdef refresh
+#undef refresh
+#endif /* refresh */
+#define refresh LY_SLrefresh
+
+#ifdef VMS
+ extern void VTHome(void);
+
+#define endwin() LYclear(),refresh(),SLsmg_reset_smg(),VTHome()
+#else
+#define endwin SLsmg_reset_smg(),SLang_reset_tty
+#endif /* VMS */
+
+#else /* Define curses functions: */
+
+#ifdef FANCY_CURSES
+#define SHOW_WHEREIS_TARGETS 1
+
+#ifdef VMS
+/*
+ * For VMS curses, [w]setattr() and [w]clrattr()
+ * add and subtract, respectively, the attributes
+ * _UNDERLINE, _BOLD, _REVERSE, and _BLINK. - FM
+ */
+#define start_bold() setattr(LYUnderlineLinks ? _UNDERLINE : _BOLD)
+#define stop_bold() clrattr(LYUnderlineLinks ? _UNDERLINE : _BOLD)
+#define start_underline() setattr(LYUnderlineLinks ? _BOLD : _UNDERLINE)
+#define stop_underline() clrattr(LYUnderlineLinks ? _BOLD : _UNDERLINE)
+#define start_reverse() setattr(_REVERSE)
+#define wstart_reverse(w) wsetattr(w, _REVERSE)
+#define stop_reverse() clrattr(_REVERSE)
+#define wstop_reverse(w) wclrattr(w, _REVERSE)
+
+#else /* Not VMS: */
+
+ extern int string_to_attr(const char *name);
+
+/*
+ * For Unix FANCY_FANCY curses we interpose
+ * our own functions to add or subtract the
+ * A_foo attributes. - FM
+ */
+#if defined(USE_COLOR_TABLE) && !defined(USE_COLOR_STYLE)
+ extern void LYaddWAttr(WINDOW * win, int a);
+ extern void LYsubWAttr(WINDOW * win, int a);
+ extern void LYaddWAttr(WINDOW * win, int a);
+ extern void LYsubWAttr(WINDOW * win, int a);
+
+#undef standout
+#define standout() lynx_standout(TRUE)
+#undef standend
+#define standend() lynx_standout(FALSE)
+#else
+#define LYaddAttr(attr) LYaddWAttr(LYwin,attr)
+#define LYaddWAttr(win,attr) wattron(win,attr)
+#define LYsubAttr(attr) LYsubWAttr(LYwin,attr)
+#define LYsubWAttr(win,attr) wattroff(win,attr)
+#endif
+
+#if defined(USE_COLOR_TABLE)
+ extern void lynx_set_color(int a);
+ extern void lynx_standout(int a);
+ extern char *LYgetTableString(int code);
+ extern int LYgetTableAttr(void);
+ extern int lynx_chg_color(int, int, int);
+#endif
+
+#define start_bold() LYaddAttr(LYUnderlineLinks ? A_UNDERLINE : A_BOLD)
+#define stop_bold() LYsubAttr(LYUnderlineLinks ? A_UNDERLINE : A_BOLD)
+#define start_underline() LYaddAttr(LYUnderlineLinks ? A_BOLD : A_UNDERLINE)
+#define stop_underline() LYsubAttr(LYUnderlineLinks ? A_BOLD : A_UNDERLINE)
+
+#define start_reverse() LYaddAttr(A_REVERSE)
+#define wstart_reverse(w) LYaddWAttr(w, A_REVERSE)
+#define stop_reverse() LYsubAttr(A_REVERSE)
+#define wstop_reverse(w) LYsubWAttr(w, A_REVERSE)
+
+#endif /* VMS */
+
+#else /* Not FANCY_CURSES: */
+/* *INDENT-OFF* */
+#ifdef COLOR_CURSES
+#undef COLOR_CURSES
+Error FANCY_CURSES
+There is a problem with the configuration. We expect to have FANCY_CURSES
+defined when COLOR_CURSES is defined, since we build on the attributes used in
+FANCY_CURSES. Check your config.log to see why the FANCY_CURSES test failed.
+#endif
+/* *INDENT-ON* */
+
+/*
+ * We only have [w]standout() and [w]standin(),
+ * so we'll use them synonymously for bold and
+ * reverse, and ignore underline. - FM
+ */
+#define start_bold() standout()
+#define start_underline() /* nothing */
+#define start_reverse() standout()
+#define wstart_reverse(a) wstandout(a)
+#define stop_bold() standend()
+#define stop_underline() /* nothing */
+#define stop_reverse() standend()
+#define wstop_reverse(a) wstandend(a)
+
+#endif /* FANCY_CURSES */
+
+#ifdef __hpux /* FIXME: configure check */
+#undef ACS_UARROW
+#undef ACS_DARROW
+#undef ACS_LARROW
+#undef ACS_RARROW
+#undef ACS_BLOCK
+#undef ACS_CKBOARD
+#endif
+
+#ifndef ACS_UARROW
+#define ACS_UARROW '^'
+#endif
+
+#ifndef ACS_DARROW
+#define ACS_DARROW 'V'
+#endif
+
+#ifndef ACS_LARROW
+#define ACS_LARROW '{'
+#endif
+
+#ifndef ACS_RARROW
+#define ACS_RARROW '}'
+#endif
+
+#ifndef ACS_BLOCK
+#define ACS_BLOCK '}'
+#endif
+
+#ifndef ACS_CKBOARD
+#define ACS_CKBOARD '}'
+#endif
+
+#define LYaddch(ch) waddch(LYwin, ch)
+
+#define addch_raw(ch) LYaddch(ch)
+
+#endif /* USE_SLANG */
+
+#ifdef USE_SLANG
+#define LYGetYX(y, x) y = SLsmg_get_row(), x = SLsmg_get_column()
+#else
+#ifdef getyx
+#define LYGetYX(y, x) getyx(LYwin, y, x)
+#else
+#define LYGetYX(y, x) y = LYwin->_cury, x = LYwin->_curx
+#endif /* getyx */
+#endif /* USE_SLANG */
+
+/*
+ * If the screen library allows us to specify "default" color, allow user to
+ * control it.
+ */
+#ifdef USE_DEFAULT_COLORS
+#if defined(USE_SLANG) || defined(HAVE_ASSUME_DEFAULT_COLORS)
+#define EXP_ASSUMED_COLOR 1
+#endif
+#endif
+
+ extern void lynx_enable_mouse(int);
+ extern void lynx_force_repaint(void);
+ extern void lynx_nl2crlf(int normal);
+ extern void lynx_start_title_color(void);
+ extern void lynx_stop_title_color(void);
+ extern void lynx_start_link_color(int flag, int pending);
+ extern void lynx_stop_link_color(int flag, int pending);
+ extern void lynx_stop_target_color(void);
+ extern void lynx_start_target_color(void);
+ extern void lynx_start_status_color(void);
+ extern void lynx_stop_status_color(void);
+ extern void lynx_start_h1_color(void);
+ extern void lynx_stop_h1_color(void);
+ extern void lynx_start_prompt_color(void);
+ extern void lynx_stop_prompt_color(void);
+ extern void lynx_start_radio_color(void);
+ extern void lynx_stop_radio_color(void);
+ extern void lynx_stop_all_colors(void);
+
+ extern void lynx_start_bold(void);
+ extern void lynx_start_reverse(void);
+ extern void lynx_start_underline(void);
+ extern void lynx_stop_bold(void);
+ extern void lynx_stop_reverse(void);
+ extern void lynx_stop_underline(void);
+
+ extern void restart_curses(void);
+
+/*
+ * To prevent corrupting binary data on DOS, MS-WINDOWS or OS/2 we open files
+ * and stdout in BINARY mode by default. Where necessary we should open and
+ * (close!) TEXT mode.
+ *
+ * Note: EMX has no corresponding variable like _fmode on DOS, but it does
+ * have setmode.
+ */
+#if defined(_WINDOWS) || defined(DJGPP) || defined(__EMX__) || defined(WIN_EX)
+#define SetOutputMode(mode) fflush(stdout), setmode(fileno(stdout), mode)
+#else
+#define SetOutputMode(mode) /* nothing */
+#endif
+
+#if defined(_WINDOWS) || defined(DJGPP)
+#define SetDefaultMode(mode) _fmode = mode
+#else
+#define SetDefaultMode(mode) /* nothing */
+#endif
+
+/*
+ * Very old versions of curses cannot put the cursor on the lower right corner.
+ * Adjust our "hidden" cursor position accordingly.
+ */
+#if defined(FANCY_CURSES) || defined(USE_SLANG)
+#define LYHideCursor() LYmove((LYlines - 1), (LYcolLimit - 1))
+#else
+#define LYHideCursor() LYmove((LYlines - 1), (LYcolLimit - 2))
+#endif
+
+#define LYParkCursor() LYmove((LYlines - 1), 0); LYclrtoeol()
+
+ extern void LYstowCursor(WINDOW * win, int row, int col);
+ extern void LYSetDisplayLines(void);
+
+#ifdef __cplusplus
+}
+#endif
+#endif /* LYCURSES_H */
diff --git a/src/LYDownload.c b/src/LYDownload.c
new file mode 100644
index 0000000..afd6638
--- /dev/null
+++ b/src/LYDownload.c
@@ -0,0 +1,591 @@
+/* $LynxId: LYDownload.c,v 1.72 2021/07/29 20:30:00 tom Exp $ */
+#include <HTUtils.h>
+#include <HTParse.h>
+#include <HTList.h>
+#include <HTAlert.h>
+#include <LYCurses.h>
+#include <LYUtils.h>
+#include <LYGlobalDefs.h>
+#include <LYStrings.h>
+#include <LYDownload.h>
+
+#include <LYLeaks.h>
+
+/*
+ * LYDownload takes a URL and downloads it using a user selected download
+ * program
+ *
+ * It parses an incoming link that looks like
+ *
+ * LYNXDOWNLOAD://Method=<#>/File=<STRING>/SugFile=<STRING>
+ */
+#ifdef VMS
+BOOLEAN LYDidRename = FALSE;
+#endif /* VMS */
+
+static char LYValidDownloadFile[LY_MAXPATH] = "\0";
+
+void LYDownload(char *line)
+{
+ char *Line = NULL, *method, *file, *sug_file = NULL;
+ int method_number;
+ int count;
+ char *the_command = 0;
+ bstring *buffer = NULL;
+ bstring *command = NULL;
+ char *cp;
+ lynx_list_item_type *download_command = 0;
+ int ch;
+ RecallType recall;
+ int FnameTotal;
+ int FnameNum;
+ BOOLEAN FirstRecall = TRUE;
+ BOOLEAN SecondS = FALSE;
+
+#ifdef VMS
+ LYDidRename = FALSE;
+#endif /* VMS */
+
+ /*
+ * Make sure we have a valid download file comparison string loaded via the
+ * download options menu. - FM
+ */
+ if (LYValidDownloadFile[0] == '\0') {
+ goto failed;
+ }
+
+ /*
+ * Make a copy of the LYNXDOWNLOAD internal URL for parsing. - FM
+ */
+ if (StrAllocCopy(Line, line) == 0)
+ goto failed;
+
+ /*
+ * Parse out the File, sug_file, and the Method.
+ */
+ if ((file = LYstrstr(Line, "/File=")) == NULL)
+ goto failed;
+ *file = '\0';
+ /*
+ * Go past "File=".
+ */
+ file += 6;
+
+ if ((sug_file = LYstrstr(file + 1, "/SugFile=")) != NULL) {
+ *sug_file = '\0';
+ /*
+ * Go past "SugFile=".
+ */
+ sug_file += 9;
+ HTUnEscape(sug_file);
+ }
+
+ /*
+ * Make sure that the file string is the one from the last displayed
+ * download options menu. - FM
+ */
+ if (strcmp(file, LYValidDownloadFile)) {
+ goto failed;
+ }
+#if defined(DIRED_SUPPORT)
+ /* FIXME: use HTLocalName */
+ if (!StrNCmp(file, "file://localhost", 16)) {
+#ifdef __DJGPP__
+ if (!StrNCmp(file + 16, "/dev/", 5))
+ file += 16;
+ else {
+ file += 17;
+ file = HTDOS_name(file);
+ }
+#else
+ file += 16;
+#endif /* __DJGPP__ */
+ } else if (isFILE_URL(file))
+ file += LEN_FILE_URL;
+ HTUnEscape(file);
+#else
+#if defined(_WINDOWS) /* 1997/10/15 (Wed) 16:27:38 */
+ if (!StrNCmp(file, "file://localhost/", 17))
+ file += 17;
+ else if (!StrNCmp(file, "file:/", 6))
+ file += 6;
+ HTUnEscape(file);
+#endif /* _WINDOWS */
+#endif /* DIRED_SUPPORT */
+
+ if ((method = LYstrstr(Line, "Method=")) == NULL)
+ goto failed;
+ /*
+ * Go past "Method=".
+ */
+ method += 7;
+ method_number = atoi(method);
+
+ /*
+ * Set up the sug_filenames recall buffer.
+ */
+ FnameTotal = (sug_filenames ? HTList_count(sug_filenames) : 0);
+ recall = ((FnameTotal >= 1) ? RECALL_URL : NORECALL);
+ FnameNum = FnameTotal;
+
+ if (method_number < 0) {
+ /*
+ * Write to local file.
+ */
+ _statusline(FILENAME_PROMPT);
+ retry:
+ if (sug_file) {
+ BStrCopy0(buffer, sug_file);
+ } else {
+ BStrCopy0(buffer, "");
+ }
+
+ check_recall:
+ if ((ch = LYgetBString(&buffer, FALSE, 0, recall)) < 0 ||
+ isBEmpty(buffer) ||
+ ch == UPARROW_KEY ||
+ ch == DNARROW_KEY) {
+
+ if (recall && ch == UPARROW_KEY) {
+ if (FirstRecall) {
+ FirstRecall = FALSE;
+ /*
+ * Use the last Fname in the list. - FM
+ */
+ FnameNum = 0;
+ } else {
+ /*
+ * Go back to the previous Fname in the list. - FM
+ */
+ FnameNum++;
+ }
+ if (FnameNum >= FnameTotal) {
+ /*
+ * Reset the FirstRecall flag, and use sug_file or a blank.
+ * - FM
+ */
+ FirstRecall = TRUE;
+ FnameNum = FnameTotal;
+ _statusline(FILENAME_PROMPT);
+ goto retry;
+ } else if ((cp = (char *) HTList_objectAt(sug_filenames,
+ FnameNum)) != NULL) {
+ BStrCopy0(buffer, cp);
+ if (FnameTotal == 1) {
+ _statusline(EDIT_THE_PREV_FILENAME);
+ } else {
+ _statusline(EDIT_A_PREV_FILENAME);
+ }
+ goto check_recall;
+ }
+ } else if (recall && ch == DNARROW_KEY) {
+ if (FirstRecall) {
+ FirstRecall = FALSE;
+ /*
+ * Use the first Fname in the list. - FM
+ */
+ FnameNum = FnameTotal - 1;
+ } else {
+ /*
+ * Advance to the next Fname in the list. - FM
+ */
+ FnameNum--;
+ }
+ if (FnameNum < 0) {
+ /*
+ * Set the FirstRecall flag, and use sug_file or a blank.
+ * - FM
+ */
+ FirstRecall = TRUE;
+ FnameNum = FnameTotal;
+ _statusline(FILENAME_PROMPT);
+ goto retry;
+ } else if ((cp = (char *) HTList_objectAt(sug_filenames,
+ FnameNum)) != NULL) {
+ BStrCopy0(buffer, cp);
+ if (FnameTotal == 1) {
+ _statusline(EDIT_THE_PREV_FILENAME);
+ } else {
+ _statusline(EDIT_A_PREV_FILENAME);
+ }
+ goto check_recall;
+ }
+ }
+
+ /*
+ * Save cancelled.
+ */
+ goto cancelled;
+ }
+
+ BStrCopy(command, buffer);
+ if (!LYValidateFilename(&buffer, &command))
+ goto cancelled;
+#ifdef HAVE_POPEN
+ else if (LYIsPipeCommand(buffer->str)) {
+ /* I don't know how to download to a pipe */
+ HTAlert(CANNOT_WRITE_TO_FILE);
+ _statusline(NEW_FILENAME_PROMPT);
+ FirstRecall = TRUE;
+ FnameNum = FnameTotal;
+ goto retry;
+ }
+#endif
+
+ /*
+ * See if it already exists.
+ */
+ switch (LYValidateOutput(buffer->str)) {
+ case 'Y':
+ break;
+ case 'N':
+ _statusline(NEW_FILENAME_PROMPT);
+ FirstRecall = TRUE;
+ FnameNum = FnameTotal;
+ goto retry;
+ default:
+ goto cleanup;
+ }
+
+ /*
+ * See if we can write to it.
+ */
+ CTRACE((tfp, "LYDownload: filename is %s\n", buffer->str));
+
+ SecondS = TRUE;
+
+ HTInfoMsg(SAVING);
+#ifdef VMS
+ /*
+ * Try rename() first. - FM
+ */
+ CTRACE((tfp, "command: rename(%s, %s)\n", file, buffer->str));
+ if (rename(file, buffer->str)) {
+ /*
+ * Failed. Use spawned COPY_COMMAND. - FM
+ */
+ CTRACE((tfp, " FAILED!\n"));
+ LYCopyFile(file, buffer->str);
+ } else {
+ /*
+ * We don't have the temporary file (it was renamed to a permanent
+ * file), so set a flag to pop out of the download menu. - FM
+ */
+ LYDidRename = TRUE;
+ }
+ chmod(buffer->str, HIDE_CHMOD);
+#else /* Unix: */
+
+ LYCopyFile(file, buffer->str);
+ LYRelaxFilePermissions(buffer->str);
+#endif /* VMS */
+
+ } else {
+ /*
+ * Use configured download commands.
+ */
+ BStrCopy0(buffer, "");
+ for (count = 0, download_command = downloaders;
+ count < method_number;
+ count++, download_command = download_command->next) ; /* null body */
+
+ /*
+ * Commands have the form "command %s [etc]" where %s is the filename.
+ */
+ if (download_command->command != NULL) {
+ /*
+ * Check for two '%s' and ask for the local filename if there is.
+ */
+ if (HTCountCommandArgs(download_command->command) >= 2) {
+ _statusline(FILENAME_PROMPT);
+
+ again:
+ if (sug_file) {
+ BStrCopy0(buffer, sug_file);
+ } else {
+ BStrCopy0(buffer, "");
+ }
+
+ check_again:
+ if ((ch = LYgetBString(&buffer, FALSE, 0, recall)) < 0 ||
+ isBEmpty(buffer) ||
+ ch == UPARROW_KEY ||
+ ch == DNARROW_KEY) {
+
+ if (recall && ch == UPARROW_KEY) {
+ if (FirstRecall) {
+ FirstRecall = FALSE;
+ /*
+ * Use the last Fname in the list. - FM
+ */
+ FnameNum = 0;
+ } else {
+ /*
+ * Go back to the previous Fname in the list. - FM
+ */
+ FnameNum++;
+ }
+ if (FnameNum >= FnameTotal) {
+ /*
+ * Reset the FirstRecall flag, and use sug_file or
+ * a blank. - FM
+ */
+ FirstRecall = TRUE;
+ FnameNum = FnameTotal;
+ _statusline(FILENAME_PROMPT);
+ goto again;
+ } else if ((cp = (char *) HTList_objectAt(sug_filenames,
+ FnameNum))
+ != NULL) {
+ BStrCopy0(buffer, cp);
+ if (FnameTotal == 1) {
+ _statusline(EDIT_THE_PREV_FILENAME);
+ } else {
+ _statusline(EDIT_A_PREV_FILENAME);
+ }
+ goto check_again;
+ }
+ } else if (recall && ch == DNARROW_KEY) {
+ if (FirstRecall) {
+ FirstRecall = FALSE;
+ /*
+ * Use the first Fname in the list. - FM
+ */
+ FnameNum = FnameTotal - 1;
+ } else {
+ /*
+ * Advance to the next Fname in the list. - FM
+ */
+ FnameNum--;
+ }
+ if (FnameNum < 0) {
+ /*
+ * Set the FirstRecall flag, and use sug_file or a
+ * blank. - FM
+ */
+ FirstRecall = TRUE;
+ FnameNum = FnameTotal;
+ _statusline(FILENAME_PROMPT);
+ goto again;
+ } else if ((cp = (char *) HTList_objectAt(sug_filenames,
+ FnameNum))
+ != NULL) {
+ BStrCopy0(buffer, cp);
+ if (FnameTotal == 1) {
+ _statusline(EDIT_THE_PREV_FILENAME);
+ } else {
+ _statusline(EDIT_A_PREV_FILENAME);
+ }
+ goto check_again;
+ }
+ }
+
+ /*
+ * Download cancelled.
+ */
+ goto cancelled;
+ }
+
+ if (no_dotfiles || !show_dotfiles) {
+ if (*LYPathLeaf(buffer->str) == '.') {
+ HTAlert(FILENAME_CANNOT_BE_DOT);
+ _statusline(NEW_FILENAME_PROMPT);
+ goto again;
+ }
+ }
+ /*
+ * Cancel if the user entered "/dev/null" on Unix, or an "nl:"
+ * path on VMS. - FM
+ */
+ if (LYIsNullDevice(buffer->str)) {
+ goto cancelled;
+ }
+ SecondS = TRUE;
+ }
+
+ /*
+ * The following is considered a bug by the community. If the
+ * command only takes one argument on the command line, then the
+ * suggested file name is not used. It actually is not a bug at
+ * all and does as it should, putting both names on the command
+ * line.
+ */
+ count = 1;
+ HTAddParam(&the_command, download_command->command, count, file);
+ if (HTCountCommandArgs(download_command->command) > 1)
+ HTAddParam(&the_command, download_command->command, ++count, buffer->str);
+ HTEndParam(&the_command, download_command->command, count);
+
+ } else {
+ HTAlert(MISCONF_DOWNLOAD_COMMAND);
+ goto failed;
+ }
+
+ CTRACE((tfp, "command: %s\n", the_command));
+ stop_curses();
+ LYSystem(the_command);
+ FREE(the_command);
+ start_curses();
+ /* don't remove(file); */
+ }
+
+ if (SecondS == TRUE) {
+#ifdef VMS
+ if (0 == strncasecomp(buffer->str, "sys$disk:", 9)) {
+ if (0 == StrNCmp((buffer->str + 9), "[]", 2)) {
+ HTAddSugFilename(buffer->str + 11);
+ } else {
+ HTAddSugFilename(buffer->str + 9);
+ }
+ } else {
+ HTAddSugFilename(buffer->str);
+ }
+#else
+ HTAddSugFilename(buffer->str);
+#endif /* VMS */
+ }
+ goto cleanup;
+
+ failed:
+ HTAlert(CANNOT_DOWNLOAD_FILE);
+ goto cleanup;
+
+ cancelled:
+ HTInfoMsg(CANCELLING);
+
+ cleanup:
+ FREE(Line);
+ BStrFree(buffer);
+ BStrFree(command);
+ return;
+}
+
+/*
+ * Compare a filename with a given suffix, which we have set to give a rough
+ * idea of its content.
+ */
+static int SuffixIs(char *filename, const char *suffix)
+{
+ size_t have = strlen(filename);
+ size_t need = strlen(suffix);
+
+ return have > need && !strcmp(filename + have - need, suffix);
+}
+
+/*
+ * LYdownload_options writes out the current download choices to a file so that
+ * the user can select downloaders in the same way that they select all other
+ * links. Download links look like:
+ * LYNXDOWNLOAD://Method=<#>/File=<STRING>/SugFile=<STRING>
+ */
+int LYdownload_options(char **newfile, char *data_file)
+{
+ static char tempfile[LY_MAXPATH] = "\0";
+ char *downloaded_url = NULL;
+ char *sug_filename = NULL;
+ FILE *fp0;
+ lynx_list_item_type *cur_download;
+ int count;
+
+ /*
+ * Get a suggested filename.
+ */
+ StrAllocCopy(sug_filename, *newfile);
+ change_sug_filename(sug_filename);
+
+ if ((fp0 = InternalPageFP(tempfile, TRUE)) == 0)
+ return (-1);
+
+ StrAllocCopy(downloaded_url, *newfile);
+ LYLocalFileToURL(newfile, tempfile);
+
+ LYStrNCpy(LYValidDownloadFile,
+ data_file,
+ (sizeof(LYValidDownloadFile) - 1));
+ LYforce_no_cache = TRUE; /* don't cache this doc */
+
+ BeginInternalPage(fp0, DOWNLOAD_OPTIONS_TITLE, DOWNLOAD_OPTIONS_HELP);
+
+ fprintf(fp0, "<pre>\n");
+ fprintf(fp0, "<em>%s</em> %s\n",
+ gettext("Downloaded link:"),
+ downloaded_url);
+ FREE(downloaded_url);
+
+ fprintf(fp0, "<em>%s</em> %s\n",
+ gettext("Suggested file name:"),
+ sug_filename);
+
+ fprintf(fp0, "\n%s\n",
+ (user_mode == NOVICE_MODE)
+ ? gettext("Standard download options:")
+ : gettext("Download options:"));
+
+ if (!no_disk_save) {
+#if defined(DIRED_SUPPORT)
+ /*
+ * Disable save to disk option for local files.
+ */
+ if (!lynx_edit_mode)
+#endif /* DIRED_SUPPORT */
+ {
+ fprintf(fp0,
+ " <a href=\"%s//Method=-1/File=%s/SugFile=%s%s\">%s</a>\n",
+ STR_LYNXDOWNLOAD,
+ data_file,
+ NonNull(lynx_save_space),
+ sug_filename,
+ gettext("Save to disk"));
+ /*
+ * If it is not a binary file, offer the opportunity to view the
+ * downloaded temporary file (see HTSaveToFile).
+ */
+ if (SuffixIs(data_file, HTML_SUFFIX)
+ || SuffixIs(data_file, TEXT_SUFFIX)) {
+ char *target = NULL;
+ char *source = LYAddPathToSave(data_file);
+
+ LYLocalFileToURL(&target, source);
+ fprintf(fp0,
+ " <a href=\"%s\">%s</a>\n",
+ target,
+ gettext("View temporary file"));
+
+ FREE(source);
+ FREE(target);
+ }
+ }
+ } else {
+ fprintf(fp0, " <em>%s</em>\n", gettext("Save to disk disabled."));
+ }
+
+ if (user_mode == NOVICE_MODE)
+ fprintf(fp0, "\n%s\n", gettext("Local additions:"));
+
+ if (downloaders != NULL) {
+ for (count = 0, cur_download = downloaders; cur_download != NULL;
+ cur_download = cur_download->next, count++) {
+ if (!no_download || cur_download->always_enabled) {
+ fprintf(fp0,
+ " <a href=\"%s//Method=%d/File=%s/SugFile=%s\">",
+ STR_LYNXDOWNLOAD, count, data_file, sug_filename);
+ fprintf(fp0, "%s", (cur_download->name
+ ? cur_download->name
+ : gettext("No Name Given")));
+ fprintf(fp0, "</a>\n");
+ }
+ }
+ }
+
+ fprintf(fp0, "</pre>\n");
+ EndInternalPage(fp0);
+ LYCloseTempFP(fp0);
+ LYRegisterUIPage(*newfile, UIP_DOWNLOAD_OPTIONS);
+
+ /*
+ * Free off temp copy.
+ */
+ FREE(sug_filename);
+
+ return (0);
+}
diff --git a/src/LYDownload.h b/src/LYDownload.h
new file mode 100644
index 0000000..5926df8
--- /dev/null
+++ b/src/LYDownload.h
@@ -0,0 +1,21 @@
+#ifndef LYDOWNLOAD_H
+#define LYDOWNLOAD_H
+
+#ifndef LYSTRUCTS_H
+#include <LYStructs.h>
+#endif /* LYSTRUCTS_H */
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+ extern void LYDownload(char *line);
+ extern int LYdownload_options(char **newfile, char *data_file);
+
+#ifdef VMS
+ extern BOOLEAN LYDidRename;
+#endif
+
+#ifdef __cplusplus
+}
+#endif
+#endif /* LYDOWNLOAD_H */
diff --git a/src/LYEdit.c b/src/LYEdit.c
new file mode 100644
index 0000000..304bb4e
--- /dev/null
+++ b/src/LYEdit.c
@@ -0,0 +1,298 @@
+/* $LynxId: LYEdit.c,v 1.43 2021/06/09 21:39:57 tom Exp $ */
+#include <HTUtils.h>
+#include <HTParse.h>
+#include <HTAlert.h>
+#include <LYCurses.h>
+#include <LYUtils.h>
+#include <LYGlobalDefs.h>
+#include <LYStrings.h>
+#include <LYEdit.h>
+#ifdef VMS
+#include <unixio.h>
+#endif /* VMS */
+
+#include <LYLeaks.h>
+#include <www_wait.h>
+
+BOOLEAN editor_can_position(void)
+{
+ char *value;
+ HTList *p = positionable_editor;
+ static const char *table[] =
+ {
+#ifdef VMS
+ "sedt",
+#else
+ "emacs", /* + xemacs */
+ "jed",
+ "jmacs",
+ "joe", /* + rjoe */
+ "jove",
+ "jstar",
+ "nano",
+ "pico", /* + jpico */
+ "vi" /* + vim, xvi, vile, elvis, view... + likely false matches */
+#endif
+ };
+ unsigned n;
+
+ for (n = 0; n < TABLESIZE(table); n++) {
+ if (LYstrstr(editor, table[n]) != 0) {
+ return TRUE;
+ }
+ }
+ /*
+ * This really isn't right. LYstrstr() might be too lax,
+ * but this should at least match basename to basename...
+ */
+ if (positionable_editor != NULL) {
+ while ((value = (char *) HTList_nextObject(p)) != NULL) {
+ if (strcmp(editor, value) == 0) {
+ return TRUE;
+ }
+ }
+ }
+ return FALSE;
+}
+
+/*
+ * In edit mode invoke the given (or default) editor to display and edit the
+ * current file. For editors listed in 'editor_can_position()', Lynx will open
+ * the file to the same line that the screen cursor is on (or close...) when
+ * editing is invoked.
+ *
+ * Returns FALSE if file is uneditable.
+ */
+int edit_current_file(char *newfile,
+ int cur,
+ int lineno)
+{
+ int result = FALSE;
+ char *filename = NULL;
+
+#if !(defined(VMS) || defined(USE_DOS_DRIVES))
+ char *colon;
+#endif
+ char *number_sign;
+ char position[80];
+
+#if defined(VMS) || defined(CANT_EDIT_UNWRITABLE_FILES)
+ FILE *fp;
+#endif
+
+ CTRACE((tfp, "edit_current_file(newfile=%s, cur=%d, lineno=%d)\n",
+ newfile, cur, lineno));
+
+ /*
+ * If it's a remote file then we can't edit it.
+ */
+ if (!LYisLocalFile(newfile)) {
+ HTUserMsg(CANNOT_EDIT_REMOTE_FILES);
+ return FALSE;
+ }
+
+ /*
+ * If there's a fragment, trim it. - FM
+ */
+ number_sign = trimPoundSelector(newfile);
+
+ /*
+ * On Unix, first try to open it as a completely referenced file, then via
+ * the path alone.
+ *
+ * On VMS, only try the path.
+ */
+#if defined (VMS) || defined (USE_DOS_DRIVES)
+ filename = HTParse(newfile, "", PARSE_PATH + PARSE_PUNCTUATION);
+ HTUnEscape(filename);
+ StrAllocCopy(filename, HTSYS_name(filename));
+ if (!LYCanReadFile(filename)) {
+#ifdef SH_EX
+ HTUserMsg2(COULD_NOT_EDIT_FILE, filename);
+#else
+ HTAlert(COULD_NOT_ACCESS_FILE);
+#endif
+ CTRACE((tfp, "filename: '%s'\n", filename));
+ goto done;
+ }
+#else /* something like UNIX */
+ if (StrNCmp(newfile, "file://localhost/", 16) == 0)
+ colon = newfile + 16;
+ else
+ colon = StrChr(newfile, ':');
+ StrAllocCopy(filename, (colon + 1));
+ HTUnEscape(filename);
+ if (!LYCanReadFile(filename)) {
+ FREE(filename);
+ filename = HTParse(newfile, "", PARSE_PATH + PARSE_PUNCTUATION);
+ HTUnEscape(filename);
+ if (!LYCanReadFile(HTSYS_name(filename))) {
+ HTAlert(COULD_NOT_ACCESS_FILE);
+ goto done;
+ }
+ }
+#endif
+
+#if defined(VMS) || defined(CANT_EDIT_UNWRITABLE_FILES)
+ /*
+ * Don't allow editing if user lacks append access.
+ */
+ if ((fp = fopen(filename, TXT_A)) == NULL) {
+ HTUserMsg(NOAUTH_TO_EDIT_FILE);
+ goto done;
+ }
+ fclose(fp);
+#endif /* VMS || CANT_EDIT_UNWRITABLE_FILES */
+
+ /*
+ * Make sure cur is at least zero. - FM
+ */
+ if (cur < 0) {
+ cur = 0;
+ }
+
+ /*
+ * Set up the command for the editor. - FM
+ */
+ if (lineno >= 0) {
+ *position = 0;
+#ifdef VMS
+ lineno--;
+#endif
+ lineno += (nlinks ? links[cur].ly : 0);
+ if (lineno > 0)
+ sprintf(position, "%d", lineno);
+ } else {
+ *position = '\0';
+ }
+
+ edit_temporary_file(filename, position, NULL);
+ result = TRUE;
+
+ done:
+ /*
+ * Restore the fragment if there was one. - FM
+ */
+ restorePoundSelector(number_sign);
+
+ FREE(filename);
+ CTRACE((tfp, "edit_current_file returns %d\n", result));
+ return (result);
+}
+
+void edit_temporary_file(char *filename,
+ const char *position,
+ const char *message)
+{
+#ifdef UNIX
+ struct stat stat_info;
+#endif
+ const char *format = "%s %s";
+ char *command = NULL;
+ const char *editor_arg = "";
+ int params = 1;
+ int rv;
+
+ if (LYstrstr(editor, "pico")) {
+ editor_arg = " -t"; /* No prompt for filename to use */
+ }
+ if (editor_can_position() && *position) {
+#ifdef VMS
+ format = "%s %s -%s%s";
+ HTAddXpand(&command, format, params++, editor);
+ HTAddParam(&command, format, params++, filename);
+ HTAddParam(&command, format, params++, position);
+ HTAddParam(&command, format, params++, editor_arg);
+ HTEndParam(&command, format, params);
+#else
+ format = "%s +%s%s %s";
+ HTAddXpand(&command, format, params++, editor);
+ HTAddParam(&command, format, params++, position);
+ HTAddParam(&command, format, params++, editor_arg);
+ HTAddParam(&command, format, params++, filename);
+ HTEndParam(&command, format, params);
+#endif
+ }
+#ifdef DOSPATH
+ else if (StrNCmp(editor, "VZ", 2) == 0) {
+ /* for Vz editor */
+ format = "%s %s -%s";
+ HTAddXpand(&command, format, params++, editor);
+ HTAddParam(&command, format, params++, HTDOS_short_name(filename));
+ HTAddParam(&command, format, params++, position);
+ HTEndParam(&command, format, params);
+ } else if (StrNCmp(editor, "edit", 4) == 0) {
+ /* for standard editor */
+ HTAddXpand(&command, format, params++, editor);
+ HTAddParam(&command, format, params++, HTDOS_short_name(filename));
+ HTEndParam(&command, format, params);
+ }
+#endif
+ else {
+#ifdef _WINDOWS
+ if (StrChr(editor, ' '))
+ HTAddXpand(&command, format, params++, HTDOS_short_name(editor));
+ else
+ HTAddXpand(&command, format, params++, editor);
+#else
+ HTAddXpand(&command, format, params++, editor);
+#endif
+ HTAddParam(&command, format, params++, filename);
+ HTEndParam(&command, format, params);
+ }
+ if (message != NULL) {
+ _statusline(message);
+ }
+
+ CTRACE((tfp, "LYEdit: %s\n", command));
+ CTRACE_SLEEP(MessageSecs);
+
+ stop_curses();
+
+#ifdef UNIX
+ set_errno(0);
+#endif
+ if ((rv = LYSystem(command)) != 0) { /* Spawn Editor */
+ start_curses();
+ /*
+ * If something went wrong, we should probably return soon; currently
+ * we don't, but at least put out a message. - kw
+ */
+ {
+#if defined(UNIX) && defined(WIFEXITED)
+ int save_err = errno;
+
+ CTRACE((tfp, "ExtEditForm: system() returned %d (0x%x), %s\n",
+ rv, (unsigned) rv,
+ (save_err
+ ? LYStrerror(save_err)
+ : "reason unknown")));
+ LYFixCursesOn("show error warning:");
+ if (rv == -1) {
+ HTUserMsg2(gettext("Error starting editor, %s"),
+ LYStrerror(save_err));
+ } else if (WIFSIGNALED(rv)) {
+ HTAlwaysAlert(NULL, gettext("Editor killed by signal"));
+ } else if (WIFEXITED(rv) && WEXITSTATUS(rv) != 127) {
+ char exitcode[80];
+
+ sprintf(exitcode, "%d", WEXITSTATUS(rv));
+ HTUserMsg2(gettext("Editor returned with error status %s"),
+ exitcode);
+ } else
+#endif
+ HTAlwaysAlert(NULL, ERROR_SPAWNING_EDITOR);
+ }
+ } else {
+ start_curses();
+ }
+#ifdef UNIX
+ /*
+ * Delete backup file, if that's your style.
+ */
+ HTSprintf0(&command, "%s~", filename);
+ if (stat(command, &stat_info) == 0)
+ remove(command);
+#endif
+ FREE(command);
+}
diff --git a/src/LYEdit.h b/src/LYEdit.h
new file mode 100644
index 0000000..3c07062
--- /dev/null
+++ b/src/LYEdit.h
@@ -0,0 +1,18 @@
+#ifndef LYEDIT_H
+#define LYEDIT_H
+
+#ifndef HTUTILS_H
+#include <HTUtils.h>
+#endif
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+ extern BOOLEAN editor_can_position(void);
+ extern int edit_current_file(char *newfile, int cur, int lineno);
+ extern void edit_temporary_file(char *filename, const char *position, const char *message);
+
+#ifdef __cplusplus
+}
+#endif
+#endif /* LYEDIT_H */
diff --git a/src/LYEditmap.c b/src/LYEditmap.c
new file mode 100644
index 0000000..dec2ac9
--- /dev/null
+++ b/src/LYEditmap.c
@@ -0,0 +1,1931 @@
+/*
+ * $LynxId: LYEditmap.c,v 1.76 2018/12/27 10:33:52 tom Exp $
+ *
+ * LYEditMap.c
+ * Keybindings for line and form editing.
+ */
+
+#include <HTUtils.h>
+#include <HTAlert.h>
+#include <HTFile.h>
+#include <LYGlobalDefs.h>
+#include <LYCharUtils.h>
+#include <LYStrings.h>
+#include <LYKeymap.h> /* KEYMAP_SIZE, LKC_*, LYK_* - kw */
+#include <LYLeaks.h>
+
+#define PUTS(buf) (*target->isa->put_string)(target, buf)
+
+/* * * * * LynxEditactionCodes * * * * */
+#ifdef USE_ALT_BINDINGS
+
+/* Last valid index for the (lynxkeycode+modifier -> lynxeditactioncode)
+ * tables. Currently all three tables are the same. - kw
+ */
+#define LAST_MOD1_LKC 0x111
+#define LAST_MOD2_LKC 0x111
+#define LAST_MOD3_LKC 0x111
+
+/* Get (lynxkeycode+modifier -> lynxeditactioncode) mapping, intermediate.
+ */
+#define LKC_TO_LEC_M1(c) ((c)>LAST_MOD1_LKC? (int)LYE_UNMOD: Mod1Binding[c])
+#define LKC_TO_LEC_M2(c) ((c)>LAST_MOD2_LKC? (int)LYE_UNMOD: Mod2Binding[c])
+#define LKC_TO_LEC_M3(c) ((c)>LAST_MOD3_LKC? (int)LYE_UNMOD: Mod3Binding[c])
+
+#endif /* USE_ALT_BINDINGS */
+
+int current_lineedit = 0; /* Index into LYLineEditors[] */
+
+int escape_bound = 0; /* User wanted Escape to perform actions? */
+
+/*
+ * See LYStrings.h for the LYE definitions.
+ */
+/* *INDENT-OFF* */
+struct emap {
+ const char *name;
+ const int code;
+ const char *descr;
+};
+
+#define SEPARATOR {"", -1, ""}
+
+static struct emap ekmap[] = {
+ {"NOP", LYE_NOP, "Do Nothing"},
+ {"CHAR", LYE_CHAR, "Insert printable char"},
+ SEPARATOR,
+ {"ENTER", LYE_ENTER, "Input complete, return char"},
+ {"TAB", LYE_TAB, "Input complete, return TAB"},
+ {"STOP", LYE_STOP, "Input deactivated"},
+ {"ABORT", LYE_ABORT, "Input cancelled"},
+ SEPARATOR,
+ {"PASS", LYE_FORM_PASS, "Fields only: input complete"},
+ SEPARATOR,
+ {"DELBL", LYE_DELBL, "Delete back to BOL"},
+ {"DELEL", LYE_DELEL, "Delete thru EOL"},
+ {"DELN", LYE_DELN, "Delete next/curr char"},
+ {"DELP", LYE_DELP, "Delete prev char"},
+ {"DELNW", LYE_DELNW, "Delete next word"},
+ {"DELPW", LYE_DELPW, "Delete prev word"},
+ SEPARATOR,
+
+ {"ERASE", LYE_ERASE, "Erase the line"},
+ SEPARATOR,
+ {"BOL", LYE_BOL, "Go to begin of line"},
+ {"EOL", LYE_EOL, "Go to end of line"},
+ {"FORW", LYE_FORW, "Cursor forwards"},
+ {"FORW_RL", LYE_FORW_RL, "Cursor forwards or right link"},
+ {"BACK", LYE_BACK, "Cursor backwards"},
+ {"BACK_LL", LYE_BACK_LL, "Cursor backwards or left link"},
+ {"FORWW", LYE_FORWW, "Word forward"},
+ {"BACKW", LYE_BACKW, "Word back"},
+ SEPARATOR,
+ {"LOWER", LYE_LOWER, "Lower case the line"},
+ {"UPPER", LYE_UPPER, "Upper case the line"},
+ SEPARATOR,
+ {"LKCMD", LYE_LKCMD, "Invoke command prompt"},
+ {"SWMAP", LYE_SWMAP, "Switch input keymap"},
+ SEPARATOR,
+ {"C1CHAR", LYE_C1CHAR, "Insert C1 char if printable"},
+ {"SETM1", LYE_SETM1, "Set modifier 1 flag"},
+ {"SETM2", LYE_SETM2, "Set modifier 2 flag"},
+ {"UNMOD", LYE_UNMOD, "Fall back to no-modifier command"},
+ SEPARATOR,
+ {"TPOS", LYE_TPOS, "Transpose characters"},
+ {"SETMARK", LYE_SETMARK, "emacs-like set-mark-command"},
+ {"XPMARK", LYE_XPMARK, "emacs-like exchange-point-and-mark"},
+ {"KILLREG", LYE_KILLREG, "emacs-like kill-region"},
+ {"YANK", LYE_YANK, "emacs-like yank"},
+#ifdef CAN_CUT_AND_PASTE
+ SEPARATOR,
+ {"PASTE", LYE_PASTE, "ClipBoard to Lynx"},
+#endif
+ SEPARATOR,
+ {"AIX", LYE_AIX, "Hex 97"},
+ {0, -1, 0},
+};
+#undef SEPARATOR
+/* *INDENT-ON* */
+
+static LYEditCode DefaultEditBinding[KEYMAP_SIZE];
+
+#ifdef USE_ALT_BINDINGS
+static LYEditCode BetterEditBinding[KEYMAP_SIZE];
+static LYEditCode BashlikeEditBinding[KEYMAP_SIZE];
+
+/*
+ * If a modifier bit is set in a lynxkeycode, it is first looked up here.
+ *
+ * Currently this table isn't specific to the current_lineedit value, it is
+ * shared by all alternative "Bindings" to save space. However, if the
+ * modifier flag is set only by a LYE_SETMn lynxeditaction, this table can have
+ * effect only for those Bindings that map a lynxkeycode to LYE_SETMn. ( This
+ * doesn't apply if the modifier is already being set in LYgetch(). ) - kw
+ */
+static LYEditCode Mod1Binding[KEYMAP_SIZE];
+
+/* Two more tables here, but currently they are all the same.
+ In other words, we are cheating to save space, until there
+ is a need for different tables. - kw */
+static LYEditCode *Mod2Binding = Mod1Binding;
+static LYEditCode *Mod3Binding = Mod1Binding;
+
+static const LYEditInit initMod1Binding[] =
+{
+ {CTL('A'), LYE_BOL},
+ {CTL('B'), LYE_BACKW},
+ {CTL('C'), LYE_UNMOD},
+ {CTL('D'), LYK_NEXT_LINK | LYE_FORM_LAC},
+ {CTL('E'), LYK_EDITTEXTAREA | LYE_FORM_LAC},
+ {CTL('F'), LYE_FORWW},
+ {CTL('G'), LYE_ABORT},
+ {CTL('H'), LYE_DELPW},
+ {CTL('I'), LYE_UNMOD},
+ {CTL('J'), LYE_ENTER},
+ {CTL('K'), LYK_LPOS_NEXT_LINK | LYE_FORM_LAC},
+ {CTL('L'), LYE_FORM_PASS},
+ {CTL('M'), LYE_ENTER},
+ {CTL('N'), LYE_FORWW},
+ {CTL('O'), LYE_UNMOD},
+ {CTL('P'), LYE_BACKW},
+ {CTL('R'), LYE_BACKW},
+ {CTL('U'), LYE_FORM_PASS},
+ {CTL('W'), LYE_KILLREG},
+ {CTL('X'), LYE_XPMARK},
+ {CTL('Y'), LYE_UNMOD},
+ {CTL('Z'), LYE_FORM_PASS},
+ {CTL('\\'), LYE_FORM_PASS},
+ {CTL(']'), LYE_FORM_PASS},
+ {CTL('^'), LYE_UNMOD},
+ {' ', LYE_UNMOD},
+ {'!', LYE_UNMOD},
+ {'"', LYE_UNMOD},
+ {'#', LYE_UNMOD},
+ {'$', LYE_UNMOD},
+ {'%', LYE_UNMOD},
+ {'&', LYE_UNMOD},
+ {'\'', LYE_UNMOD},
+ {'(', LYE_UNMOD},
+ {')', LYE_UNMOD},
+ {'*', LYE_UNMOD},
+ {'+', LYE_UNMOD},
+ {',', LYE_UNMOD},
+ {'-', LYE_UNMOD},
+ {'.', LYE_UNMOD},
+ {'/', LYE_FORM_PASS},
+ {'0', LYE_UNMOD},
+ {'1', LYE_UNMOD},
+ {'2', LYE_UNMOD},
+ {'3', LYE_UNMOD},
+ {'4', LYE_UNMOD},
+ {'5', LYE_UNMOD},
+ {'6', LYE_UNMOD},
+ {'7', LYE_UNMOD},
+ {'8', LYE_UNMOD},
+ {'9', LYE_UNMOD},
+ {':', LYE_UNMOD},
+ {';', LYE_UNMOD},
+ {'<', LYK_HOME | LYE_FORM_LAC},
+ {'=', LYE_UNMOD},
+ {'>', LYK_END | LYE_FORM_LAC},
+ {'?', LYE_UNMOD},
+ {'@', LYE_C1CHAR},
+ {'A', LYE_C1CHAR},
+ {'B', LYE_C1CHAR},
+ {'C', LYE_C1CHAR},
+ {'D', LYE_C1CHAR},
+ {'E', LYE_C1CHAR},
+ {'F', LYE_C1CHAR},
+ {'G', LYE_C1CHAR},
+ {'H', LYE_C1CHAR},
+ {'I', LYE_C1CHAR},
+ {'J', LYE_C1CHAR},
+ {'K', LYE_C1CHAR},
+ {'L', LYE_C1CHAR},
+ {'M', LYE_C1CHAR},
+ {'N', LYE_C1CHAR},
+ {'O', LYE_C1CHAR},
+ {'P', LYE_C1CHAR},
+ {'Q', LYE_C1CHAR},
+ {'R', LYE_C1CHAR},
+ {'S', LYE_C1CHAR},
+ {'T', LYE_C1CHAR},
+ {'U', LYE_C1CHAR},
+ {'V', LYE_C1CHAR},
+ {'W', LYE_C1CHAR},
+ {'X', LYE_C1CHAR},
+ {'Y', LYE_C1CHAR},
+ {'Z', LYE_C1CHAR},
+ {'[', LYE_C1CHAR},
+ {'\\', LYE_C1CHAR},
+ {']', LYE_C1CHAR},
+ {'^', LYE_C1CHAR},
+ {'_', LYE_C1CHAR},
+ {'`', LYE_UNMOD},
+ {'a', LYE_BOL},
+ {'b', LYE_BACKW},
+ {'c', LYE_UNMOD},
+ {'d', LYE_DELNW},
+ {'e', LYK_EDITTEXTAREA | LYE_FORM_LAC},
+ {'f', LYE_FORWW},
+ {'g', LYK_GROWTEXTAREA | LYE_FORM_LAC},
+ {'h', LYE_CHAR},
+ {'i', LYK_INSERTFILE | LYE_FORM_LAC},
+ {'j', LYE_CHAR},
+ {'k', LYE_ERASE},
+ {'l', LYE_LOWER},
+ {'m', LYE_CHAR},
+ {'n', LYE_FORM_PASS},
+ {'o', LYE_UNMOD},
+ {'p', LYE_CHAR},
+ {'u', LYE_UPPER},
+ {'z', LYE_UNMOD},
+ {'{', LYE_UNMOD},
+ {'|', LYE_UNMOD},
+ {'}', LYE_UNMOD},
+ {'~', LYE_UNMOD},
+ {DEL_KEY, LYE_DELPW},
+ {160, LYE_UNMOD},
+ {161, LYE_UNMOD},
+ {162, LYE_UNMOD},
+ {163, LYE_UNMOD},
+ {164, LYE_UNMOD},
+ {165, LYE_UNMOD},
+ {166, LYE_UNMOD},
+ {167, LYE_UNMOD},
+ {168, LYE_UNMOD},
+ {169, LYE_UNMOD},
+ {170, LYE_UNMOD},
+ {171, LYE_UNMOD},
+ {172, LYE_UNMOD},
+ {173, LYE_UNMOD},
+ {174, LYE_UNMOD},
+ {175, LYE_UNMOD},
+ {176, LYE_UNMOD},
+ {177, LYE_UNMOD},
+ {178, LYE_UNMOD},
+ {179, LYE_UNMOD},
+ {180, LYE_UNMOD},
+ {181, LYE_UNMOD},
+ {182, LYE_UNMOD},
+ {183, LYE_UNMOD},
+ {184, LYE_UNMOD},
+ {185, LYE_UNMOD},
+ {186, LYE_UNMOD},
+ {187, LYE_UNMOD},
+ {188, LYE_UNMOD},
+ {189, LYE_UNMOD},
+ {190, LYE_UNMOD},
+ {191, LYE_UNMOD},
+ {192, LYE_UNMOD},
+ {193, LYE_UNMOD},
+ {194, LYE_UNMOD},
+ {195, LYE_UNMOD},
+ {196, LYE_UNMOD},
+ {197, LYE_UNMOD},
+ {198, LYE_UNMOD},
+ {199, LYE_UNMOD},
+ {200, LYE_UNMOD},
+ {201, LYE_UNMOD},
+ {202, LYE_UNMOD},
+ {203, LYE_UNMOD},
+ {204, LYE_UNMOD},
+ {205, LYE_UNMOD},
+ {206, LYE_UNMOD},
+ {207, LYE_UNMOD},
+ {208, LYE_UNMOD},
+ {209, LYE_UNMOD},
+ {210, LYE_UNMOD},
+ {211, LYE_UNMOD},
+ {212, LYE_UNMOD},
+ {213, LYE_UNMOD},
+ {214, LYE_UNMOD},
+ {215, LYE_UNMOD},
+ {216, LYE_UNMOD},
+ {217, LYE_UNMOD},
+ {218, LYE_UNMOD},
+ {219, LYE_UNMOD},
+ {220, LYE_UNMOD},
+ {221, LYE_UNMOD},
+ {222, LYE_UNMOD},
+ {223, LYE_UNMOD},
+ {224, LYE_UNMOD},
+ {225, LYE_UNMOD},
+ {226, LYE_UNMOD},
+ {227, LYE_UNMOD},
+ {228, LYE_UNMOD},
+ {229, LYE_UNMOD},
+ {230, LYE_UNMOD},
+ {231, LYE_UNMOD},
+ {232, LYE_UNMOD},
+ {233, LYE_UNMOD},
+ {234, LYE_UNMOD},
+ {235, LYE_UNMOD},
+ {236, LYE_UNMOD},
+ {237, LYE_UNMOD},
+ {238, LYE_UNMOD},
+ {239, LYE_UNMOD},
+ {240, LYE_UNMOD},
+ {241, LYE_UNMOD},
+ {242, LYE_UNMOD},
+ {243, LYE_UNMOD},
+ {244, LYE_UNMOD},
+ {245, LYE_UNMOD},
+ {246, LYE_UNMOD},
+ {247, LYE_UNMOD},
+ {248, LYE_UNMOD},
+ {249, LYE_UNMOD},
+ {250, LYE_UNMOD},
+ {251, LYE_UNMOD},
+ {252, LYE_UNMOD},
+ {253, LYE_UNMOD},
+ {254, LYE_UNMOD},
+ {255, LYE_UNMOD},
+ {UPARROW_KEY, LYE_UNMOD},
+ {DNARROW_KEY, LYE_UNMOD},
+ {RTARROW_KEY, LYE_UNMOD},
+ {LTARROW_KEY, LYE_UNMOD},
+ {PGDOWN_KEY, LYE_UNMOD},
+ {PGUP_KEY, LYE_UNMOD},
+ {HOME_KEY, LYE_FORM_PASS},
+ {END_KEY, LYE_FORM_PASS},
+ {F1_KEY, LYK_DWIMHELP | LYE_FORM_LAC},
+ {DO_KEY, LYE_UNMOD},
+#if (defined(_WINDOWS) || defined(__DJGPP__))
+ {FIND_KEY, LYE_UNMOD},
+ {SELECT_KEY, LYE_UNMOD},
+#else
+ {FIND_KEY, LYK_WHEREIS | LYE_FORM_LAC},
+ {SELECT_KEY, LYK_NEXT | LYE_FORM_LAC},
+#endif
+ {INSERT_KEY, LYE_UNMOD},
+ {DO_NOTHING, LYE_UNMOD},
+ {BACKTAB_KEY, LYE_UNMOD},
+#if (defined(_WINDOWS) || defined(__DJGPP__)) && defined(USE_SLANG) && !defined(DJGPP_KEYHANDLER)
+ {272, LYE_DELPW},
+#else
+ {272, LYE_UNMOD},
+#endif
+ {273, LYE_UNMOD},
+ {-1, LYE_UNKNOWN}
+};
+
+LYEditConfig LYModifierBindings[] =
+{
+ {"Modifier Binding", initMod1Binding, Mod1Binding},
+};
+
+#endif /* USE_ALT_BINDINGS */
+
+static const LYEditInit initDefaultEditor[] =
+{
+ {CTL('A'), LYE_BOL},
+ {CTL('B'), LYE_DELPW},
+ {CTL('C'), LYE_ABORT},
+ {CTL('D'), LYE_DELN},
+ {CTL('E'), LYE_EOL},
+ {CTL('F'), LYE_DELNW},
+ {CTL('G'), LYE_ABORT},
+ {CTL('H'), LYE_DELP},
+ {CTL('I'), LYE_TAB},
+ {CTL('J'), LYE_ENTER},
+ {CTL('K'), LYE_LOWER},
+ {CTL('M'), LYE_ENTER},
+ {CTL('N'), LYE_FORWW},
+ {CTL('O'), LYE_ABORT},
+ {CTL('P'), LYE_BACKW},
+ {CTL('R'), LYE_DELN},
+ {CTL('T'), LYE_UPPER},
+ {CTL('U'), LYE_ERASE},
+ {CTL('V'), LYE_LKCMD},
+#ifdef CAN_CUT_AND_PASTE
+ {CTL('W'), LYE_PASTE},
+#endif
+ {CTL('X'), LYE_SETM1},
+ {CTL('^'), LYE_SWMAP},
+ {CTL('_'), LYE_DELEL},
+ {' ', LYE_CHAR},
+ {'!', LYE_CHAR},
+ {'"', LYE_CHAR},
+ {'#', LYE_CHAR},
+ {'$', LYE_CHAR},
+ {'%', LYE_CHAR},
+ {'&', LYE_CHAR},
+ {'\'', LYE_CHAR},
+ {'(', LYE_CHAR},
+ {')', LYE_CHAR},
+ {'*', LYE_CHAR},
+ {'+', LYE_CHAR},
+ {',', LYE_CHAR},
+ {'-', LYE_CHAR},
+ {'.', LYE_CHAR},
+ {'/', LYE_CHAR},
+ {'0', LYE_CHAR},
+ {'1', LYE_CHAR},
+ {'2', LYE_CHAR},
+ {'3', LYE_CHAR},
+ {'4', LYE_CHAR},
+ {'5', LYE_CHAR},
+ {'6', LYE_CHAR},
+ {'7', LYE_CHAR},
+ {'8', LYE_CHAR},
+ {'9', LYE_CHAR},
+ {':', LYE_CHAR},
+ {';', LYE_CHAR},
+ {'<', LYE_CHAR},
+ {'=', LYE_CHAR},
+ {'>', LYE_CHAR},
+ {'?', LYE_CHAR},
+ {'@', LYE_CHAR},
+ {'A', LYE_CHAR},
+ {'B', LYE_CHAR},
+ {'C', LYE_CHAR},
+ {'D', LYE_CHAR},
+ {'E', LYE_CHAR},
+ {'F', LYE_CHAR},
+ {'G', LYE_CHAR},
+ {'H', LYE_CHAR},
+ {'I', LYE_CHAR},
+ {'J', LYE_CHAR},
+ {'K', LYE_CHAR},
+ {'L', LYE_CHAR},
+ {'M', LYE_CHAR},
+ {'N', LYE_CHAR},
+ {'O', LYE_CHAR},
+ {'P', LYE_CHAR},
+ {'Q', LYE_CHAR},
+ {'R', LYE_CHAR},
+ {'S', LYE_CHAR},
+ {'T', LYE_CHAR},
+ {'U', LYE_CHAR},
+ {'V', LYE_CHAR},
+ {'W', LYE_CHAR},
+ {'X', LYE_CHAR},
+ {'Y', LYE_CHAR},
+ {'Z', LYE_CHAR},
+ {'[', LYE_CHAR},
+ {'\\', LYE_CHAR},
+ {']', LYE_CHAR},
+ {'^', LYE_CHAR},
+ {'_', LYE_CHAR},
+ {'`', LYE_CHAR},
+ {'a', LYE_CHAR},
+ {'b', LYE_CHAR},
+ {'c', LYE_CHAR},
+ {'d', LYE_CHAR},
+ {'e', LYE_CHAR},
+ {'f', LYE_CHAR},
+ {'g', LYE_CHAR},
+ {'h', LYE_CHAR},
+ {'i', LYE_CHAR},
+ {'j', LYE_CHAR},
+ {'k', LYE_CHAR},
+ {'l', LYE_CHAR},
+ {'m', LYE_CHAR},
+ {'n', LYE_CHAR},
+ {'o', LYE_CHAR},
+ {'p', LYE_CHAR},
+ {'q', LYE_CHAR},
+ {'r', LYE_CHAR},
+ {'s', LYE_CHAR},
+ {'t', LYE_CHAR},
+ {'u', LYE_CHAR},
+ {'v', LYE_CHAR},
+ {'w', LYE_CHAR},
+ {'x', LYE_CHAR},
+ {'y', LYE_CHAR},
+ {'z', LYE_CHAR},
+ {'{', LYE_CHAR},
+ {'|', LYE_CHAR},
+ {'}', LYE_CHAR},
+ {'~', LYE_CHAR},
+ {DEL_KEY, LYE_DELP},
+ {128, LYE_CHAR},
+ {129, LYE_CHAR},
+ {130, LYE_CHAR},
+ {131, LYE_CHAR},
+ {132, LYE_CHAR},
+ {133, LYE_CHAR},
+ {134, LYE_CHAR},
+ {135, LYE_CHAR},
+ {136, LYE_CHAR},
+ {137, LYE_CHAR},
+ {138, LYE_CHAR},
+ {139, LYE_CHAR},
+ {140, LYE_CHAR},
+ {141, LYE_CHAR},
+#ifdef CJK_EX /* 1997/11/03 (Mon) 20:30:54 */
+ {142, LYE_CHAR},
+#else
+ {142, LYE_AIX},
+#endif
+ {143, LYE_CHAR},
+ {144, LYE_CHAR},
+ {145, LYE_CHAR},
+ {146, LYE_CHAR},
+ {147, LYE_CHAR},
+ {148, LYE_CHAR},
+ {149, LYE_CHAR},
+ {150, LYE_CHAR},
+ {151, LYE_CHAR},
+ {152, LYE_CHAR},
+ {153, LYE_CHAR},
+ {154, LYE_CHAR},
+ {155, LYE_CHAR},
+ {156, LYE_CHAR},
+ {157, LYE_CHAR},
+ {158, LYE_CHAR},
+ {159, LYE_CHAR},
+ {160, LYE_CHAR},
+ {161, LYE_CHAR},
+ {162, LYE_CHAR},
+ {163, LYE_CHAR},
+ {164, LYE_CHAR},
+ {165, LYE_CHAR},
+ {166, LYE_CHAR},
+ {167, LYE_CHAR},
+ {168, LYE_CHAR},
+ {169, LYE_CHAR},
+ {170, LYE_CHAR},
+ {171, LYE_CHAR},
+ {172, LYE_CHAR},
+ {173, LYE_CHAR},
+ {174, LYE_CHAR},
+ {175, LYE_CHAR},
+ {176, LYE_CHAR},
+ {177, LYE_CHAR},
+ {178, LYE_CHAR},
+ {179, LYE_CHAR},
+ {180, LYE_CHAR},
+ {181, LYE_CHAR},
+ {182, LYE_CHAR},
+ {183, LYE_CHAR},
+ {184, LYE_CHAR},
+ {185, LYE_CHAR},
+ {186, LYE_CHAR},
+ {187, LYE_CHAR},
+ {188, LYE_CHAR},
+ {189, LYE_CHAR},
+ {190, LYE_CHAR},
+ {191, LYE_CHAR},
+ {192, LYE_CHAR},
+ {193, LYE_CHAR},
+ {194, LYE_CHAR},
+ {195, LYE_CHAR},
+ {196, LYE_CHAR},
+ {197, LYE_CHAR},
+ {198, LYE_CHAR},
+ {199, LYE_CHAR},
+ {200, LYE_CHAR},
+ {201, LYE_CHAR},
+ {202, LYE_CHAR},
+ {203, LYE_CHAR},
+ {204, LYE_CHAR},
+ {205, LYE_CHAR},
+ {206, LYE_CHAR},
+ {207, LYE_CHAR},
+ {208, LYE_CHAR},
+ {209, LYE_CHAR},
+ {210, LYE_CHAR},
+ {211, LYE_CHAR},
+ {212, LYE_CHAR},
+ {213, LYE_CHAR},
+ {214, LYE_CHAR},
+ {215, LYE_CHAR},
+ {216, LYE_CHAR},
+ {217, LYE_CHAR},
+ {218, LYE_CHAR},
+ {219, LYE_CHAR},
+ {220, LYE_CHAR},
+ {221, LYE_CHAR},
+ {222, LYE_CHAR},
+ {223, LYE_CHAR},
+ {224, LYE_CHAR},
+ {225, LYE_CHAR},
+ {226, LYE_CHAR},
+ {227, LYE_CHAR},
+ {228, LYE_CHAR},
+ {229, LYE_CHAR},
+ {230, LYE_CHAR},
+ {231, LYE_CHAR},
+ {232, LYE_CHAR},
+ {233, LYE_CHAR},
+ {234, LYE_CHAR},
+ {235, LYE_CHAR},
+ {236, LYE_CHAR},
+ {237, LYE_CHAR},
+ {238, LYE_CHAR},
+ {239, LYE_CHAR},
+ {240, LYE_CHAR},
+ {241, LYE_CHAR},
+ {242, LYE_CHAR},
+ {243, LYE_CHAR},
+ {244, LYE_CHAR},
+ {245, LYE_CHAR},
+ {246, LYE_CHAR},
+ {247, LYE_CHAR},
+ {248, LYE_CHAR},
+ {249, LYE_CHAR},
+ {250, LYE_CHAR},
+ {251, LYE_CHAR},
+ {252, LYE_CHAR},
+ {253, LYE_CHAR},
+ {254, LYE_CHAR},
+ {255, LYE_CHAR},
+ {UPARROW_KEY, LYE_FORM_PASS},
+ {DNARROW_KEY, LYE_FORM_PASS},
+ {RTARROW_KEY, LYE_FORW},
+ {LTARROW_KEY, LYE_BACK},
+ {PGDOWN_KEY, LYE_FORM_PASS},
+ {PGUP_KEY, LYE_FORM_PASS},
+ {HOME_KEY, LYE_BOL},
+ {END_KEY, LYE_EOL},
+ {F1_KEY, LYE_FORM_PASS},
+#if !(defined(_WINDOWS) || defined(__DJGPP__))
+ {DO_KEY, LYE_TAB},
+ {FIND_KEY, LYE_BOL},
+ {SELECT_KEY, LYE_EOL},
+#endif
+ {REMOVE_KEY, LYE_DELP},
+ {BACKTAB_KEY, LYE_FORM_PASS},
+#if (defined(_WINDOWS) || defined(__DJGPP__)) && defined(USE_SLANG) && !defined(DJGPP_KEYHANDLER)
+ {272, LYE_DELP},
+ {273, LYE_ENTER},
+#endif
+ {-1, LYE_UNKNOWN}
+};
+
+#ifdef USE_ALT_BINDINGS
+static const LYEditInit initBetterEditor[] =
+{
+ {CTL('A'), LYE_BOL},
+ {CTL('B'), LYE_BACK},
+ {CTL('C'), LYE_ABORT},
+ {CTL('D'), LYE_DELN},
+ {CTL('E'), LYE_EOL},
+ {CTL('F'), LYE_FORW},
+ {CTL('G'), LYE_ABORT},
+ {CTL('H'), LYE_DELP},
+ {CTL('I'), LYE_ENTER},
+ {CTL('J'), LYE_ENTER},
+ {CTL('K'), LYE_DELEL},
+ {CTL('M'), LYE_ENTER},
+ {CTL('N'), LYE_FORWW},
+ {CTL('O'), LYE_ABORT},
+ {CTL('P'), LYE_BACKW},
+ {CTL('R'), LYE_DELPW},
+ {CTL('T'), LYE_DELNW},
+ {CTL('U'), LYE_ERASE},
+ {CTL('V'), LYE_LKCMD},
+#ifdef CAN_CUT_AND_PASTE
+ {CTL('W'), LYE_PASTE},
+#endif
+ {CTL('X'), LYE_SETM1},
+ {CTL('^'), LYE_UPPER},
+ {CTL('_'), LYE_LOWER},
+ {' ', LYE_CHAR},
+ {'!', LYE_CHAR},
+ {'"', LYE_CHAR},
+ {'#', LYE_CHAR},
+ {'$', LYE_CHAR},
+ {'%', LYE_CHAR},
+ {'&', LYE_CHAR},
+ {'\'', LYE_CHAR},
+ {'(', LYE_CHAR},
+ {')', LYE_CHAR},
+ {'*', LYE_CHAR},
+ {'+', LYE_CHAR},
+ {',', LYE_CHAR},
+ {'-', LYE_CHAR},
+ {'.', LYE_CHAR},
+ {'/', LYE_CHAR},
+ {'0', LYE_CHAR},
+ {'1', LYE_CHAR},
+ {'2', LYE_CHAR},
+ {'3', LYE_CHAR},
+ {'4', LYE_CHAR},
+ {'5', LYE_CHAR},
+ {'6', LYE_CHAR},
+ {'7', LYE_CHAR},
+ {'8', LYE_CHAR},
+ {'9', LYE_CHAR},
+ {':', LYE_CHAR},
+ {';', LYE_CHAR},
+ {'<', LYE_CHAR},
+ {'=', LYE_CHAR},
+ {'>', LYE_CHAR},
+ {'?', LYE_CHAR},
+ {'@', LYE_CHAR},
+ {'A', LYE_CHAR},
+ {'B', LYE_CHAR},
+ {'C', LYE_CHAR},
+ {'D', LYE_CHAR},
+ {'E', LYE_CHAR},
+ {'F', LYE_CHAR},
+ {'G', LYE_CHAR},
+ {'H', LYE_CHAR},
+ {'I', LYE_CHAR},
+ {'J', LYE_CHAR},
+ {'K', LYE_CHAR},
+ {'L', LYE_CHAR},
+ {'M', LYE_CHAR},
+ {'N', LYE_CHAR},
+ {'O', LYE_CHAR},
+ {'P', LYE_CHAR},
+ {'Q', LYE_CHAR},
+ {'R', LYE_CHAR},
+ {'S', LYE_CHAR},
+ {'T', LYE_CHAR},
+ {'U', LYE_CHAR},
+ {'V', LYE_CHAR},
+ {'W', LYE_CHAR},
+ {'X', LYE_CHAR},
+ {'Y', LYE_CHAR},
+ {'Z', LYE_CHAR},
+ {'[', LYE_CHAR},
+ {'\\', LYE_CHAR},
+ {']', LYE_CHAR},
+ {'^', LYE_CHAR},
+ {'_', LYE_CHAR},
+ {'`', LYE_CHAR},
+ {'a', LYE_CHAR},
+ {'b', LYE_CHAR},
+ {'c', LYE_CHAR},
+ {'d', LYE_CHAR},
+ {'e', LYE_CHAR},
+ {'f', LYE_CHAR},
+ {'g', LYE_CHAR},
+ {'h', LYE_CHAR},
+ {'i', LYE_CHAR},
+ {'j', LYE_CHAR},
+ {'k', LYE_CHAR},
+ {'l', LYE_CHAR},
+ {'m', LYE_CHAR},
+ {'n', LYE_CHAR},
+ {'o', LYE_CHAR},
+ {'p', LYE_CHAR},
+ {'q', LYE_CHAR},
+ {'r', LYE_CHAR},
+ {'s', LYE_CHAR},
+ {'t', LYE_CHAR},
+ {'u', LYE_CHAR},
+ {'v', LYE_CHAR},
+ {'w', LYE_CHAR},
+ {'x', LYE_CHAR},
+ {'y', LYE_CHAR},
+ {'z', LYE_CHAR},
+ {'{', LYE_CHAR},
+ {'|', LYE_CHAR},
+ {'}', LYE_CHAR},
+ {'~', LYE_CHAR},
+ {DEL_KEY, LYE_DELP},
+ {128, LYE_CHAR},
+ {129, LYE_CHAR},
+ {130, LYE_CHAR},
+ {131, LYE_CHAR},
+ {132, LYE_CHAR},
+ {133, LYE_CHAR},
+ {134, LYE_CHAR},
+ {135, LYE_CHAR},
+ {136, LYE_CHAR},
+ {137, LYE_CHAR},
+ {138, LYE_CHAR},
+ {139, LYE_CHAR},
+ {140, LYE_CHAR},
+ {141, LYE_CHAR},
+#ifdef CJK_EX /* 1997/11/03 (Mon) 20:30:54 */
+ {142, LYE_CHAR},
+#else
+ {142, LYE_AIX},
+#endif
+ {143, LYE_CHAR},
+ {144, LYE_CHAR},
+ {145, LYE_CHAR},
+ {146, LYE_CHAR},
+ {147, LYE_CHAR},
+ {148, LYE_CHAR},
+ {149, LYE_CHAR},
+ {150, LYE_CHAR},
+ {151, LYE_CHAR},
+ {152, LYE_CHAR},
+ {153, LYE_CHAR},
+ {154, LYE_CHAR},
+ {155, LYE_CHAR},
+ {156, LYE_CHAR},
+ {157, LYE_CHAR},
+ {158, LYE_CHAR},
+ {159, LYE_CHAR},
+ {160, LYE_CHAR},
+ {161, LYE_CHAR},
+ {162, LYE_CHAR},
+ {163, LYE_CHAR},
+ {164, LYE_CHAR},
+ {165, LYE_CHAR},
+ {166, LYE_CHAR},
+ {167, LYE_CHAR},
+ {168, LYE_CHAR},
+ {169, LYE_CHAR},
+ {170, LYE_CHAR},
+ {171, LYE_CHAR},
+ {172, LYE_CHAR},
+ {173, LYE_CHAR},
+ {174, LYE_CHAR},
+ {175, LYE_CHAR},
+ {176, LYE_CHAR},
+ {177, LYE_CHAR},
+ {178, LYE_CHAR},
+ {179, LYE_CHAR},
+ {180, LYE_CHAR},
+ {181, LYE_CHAR},
+ {182, LYE_CHAR},
+ {183, LYE_CHAR},
+ {184, LYE_CHAR},
+ {185, LYE_CHAR},
+ {186, LYE_CHAR},
+ {187, LYE_CHAR},
+ {188, LYE_CHAR},
+ {189, LYE_CHAR},
+ {190, LYE_CHAR},
+ {191, LYE_CHAR},
+ {192, LYE_CHAR},
+ {193, LYE_CHAR},
+ {194, LYE_CHAR},
+ {195, LYE_CHAR},
+ {196, LYE_CHAR},
+ {197, LYE_CHAR},
+ {198, LYE_CHAR},
+ {199, LYE_CHAR},
+ {200, LYE_CHAR},
+ {201, LYE_CHAR},
+ {202, LYE_CHAR},
+ {203, LYE_CHAR},
+ {204, LYE_CHAR},
+ {205, LYE_CHAR},
+ {206, LYE_CHAR},
+ {207, LYE_CHAR},
+ {208, LYE_CHAR},
+ {209, LYE_CHAR},
+ {210, LYE_CHAR},
+ {211, LYE_CHAR},
+ {212, LYE_CHAR},
+ {213, LYE_CHAR},
+ {214, LYE_CHAR},
+ {215, LYE_CHAR},
+ {216, LYE_CHAR},
+ {217, LYE_CHAR},
+ {218, LYE_CHAR},
+ {219, LYE_CHAR},
+ {220, LYE_CHAR},
+ {221, LYE_CHAR},
+ {222, LYE_CHAR},
+ {223, LYE_CHAR},
+ {224, LYE_CHAR},
+ {225, LYE_CHAR},
+ {226, LYE_CHAR},
+ {227, LYE_CHAR},
+ {228, LYE_CHAR},
+ {229, LYE_CHAR},
+ {230, LYE_CHAR},
+ {231, LYE_CHAR},
+ {232, LYE_CHAR},
+ {233, LYE_CHAR},
+ {234, LYE_CHAR},
+ {235, LYE_CHAR},
+ {236, LYE_CHAR},
+ {237, LYE_CHAR},
+ {238, LYE_CHAR},
+ {239, LYE_CHAR},
+ {240, LYE_CHAR},
+ {241, LYE_CHAR},
+ {242, LYE_CHAR},
+ {243, LYE_CHAR},
+ {244, LYE_CHAR},
+ {245, LYE_CHAR},
+ {246, LYE_CHAR},
+ {247, LYE_CHAR},
+ {248, LYE_CHAR},
+ {249, LYE_CHAR},
+ {250, LYE_CHAR},
+ {251, LYE_CHAR},
+ {252, LYE_CHAR},
+ {253, LYE_CHAR},
+ {254, LYE_CHAR},
+ {255, LYE_CHAR},
+ {UPARROW_KEY, LYE_FORM_PASS},
+ {DNARROW_KEY, LYE_FORM_PASS},
+ {RTARROW_KEY, LYE_FORW},
+ {LTARROW_KEY, LYE_BACK},
+ {PGDOWN_KEY, LYE_FORM_PASS},
+ {PGUP_KEY, LYE_FORM_PASS},
+ {HOME_KEY, LYE_BOL},
+ {END_KEY, LYE_EOL},
+ {F1_KEY, LYE_FORM_PASS},
+#if !(defined(_WINDOWS) || defined(__DJGPP__))
+ {DO_KEY, LYE_TAB},
+ {FIND_KEY, LYE_BOL},
+ {SELECT_KEY, LYE_EOL},
+#endif
+ {REMOVE_KEY, LYE_DELP},
+ {BACKTAB_KEY, LYE_FORM_PASS},
+#if (defined(_WINDOWS) || defined(__DJGPP__)) && defined(USE_SLANG) && !defined(DJGPP_KEYHANDLER)
+ {272, LYE_DELP},
+ {273, LYE_ENTER},
+#endif
+ {-1, LYE_UNKNOWN}
+};
+
+static const LYEditInit initBashlikeEditor[] =
+{
+ {CTL('@'), LYE_SETMARK},
+ {CTL('A'), LYE_BOL},
+ {CTL('B'), LYE_BACK},
+ {CTL('C'), LYE_ABORT},
+ {CTL('D'), LYE_DELN},
+ {CTL('E'), LYE_EOL | LYE_DF},
+ {CTL('F'), LYE_FORW},
+ {CTL('G'), LYE_ABORT},
+ {CTL('H'), LYE_DELP},
+ {CTL('I'), LYE_TAB},
+ {CTL('J'), LYE_ENTER},
+ {CTL('K'), LYE_DELEL | LYE_DF},
+ {CTL('L'), LYE_FORM_PASS},
+ {CTL('M'), LYE_ENTER},
+ {CTL('N'), LYE_FORM_PASS},
+ {CTL('O'), LYE_FORM_PASS},
+ {CTL('P'), LYE_FORM_PASS},
+ {CTL('R'), LYE_BACKW},
+ {CTL('S'), LYE_FORWW},
+ {CTL('T'), LYE_TPOS},
+ {CTL('U'), LYE_DELBL},
+ {CTL('V'), LYE_LKCMD},
+ {CTL('W'), LYE_DELPW},
+ {CTL('X'), LYE_SETM1},
+ {CTL('Y'), LYE_YANK},
+ {CTL('Z'), LYE_FORM_PASS},
+ {CTL('['), LYE_SETM2},
+ {CTL('\\'), LYE_FORM_PASS},
+ {CTL(']'), LYE_FORM_PASS},
+ {CTL('^'), LYE_SWMAP},
+ {CTL('_'), LYE_ABORT},
+ {' ', LYE_CHAR},
+ {'!', LYE_CHAR},
+ {'"', LYE_CHAR},
+ {'#', LYE_CHAR},
+ {'$', LYE_CHAR},
+ {'%', LYE_CHAR},
+ {'&', LYE_CHAR},
+ {'\'', LYE_CHAR},
+ {'(', LYE_CHAR},
+ {')', LYE_CHAR},
+ {'*', LYE_CHAR},
+ {'+', LYE_CHAR},
+ {',', LYE_CHAR},
+ {'-', LYE_CHAR},
+ {'.', LYE_CHAR},
+ {'/', LYE_CHAR},
+ {'0', LYE_CHAR},
+ {'1', LYE_CHAR},
+ {'2', LYE_CHAR},
+ {'3', LYE_CHAR},
+ {'4', LYE_CHAR},
+ {'5', LYE_CHAR},
+ {'6', LYE_CHAR},
+ {'7', LYE_CHAR},
+ {'8', LYE_CHAR},
+ {'9', LYE_CHAR},
+ {':', LYE_CHAR},
+ {';', LYE_CHAR},
+ {'<', LYE_CHAR},
+ {'=', LYE_CHAR},
+ {'>', LYE_CHAR},
+ {'?', LYE_CHAR},
+ {'@', LYE_CHAR},
+ {'A', LYE_CHAR},
+ {'B', LYE_CHAR},
+ {'C', LYE_CHAR},
+ {'D', LYE_CHAR},
+ {'E', LYE_CHAR},
+ {'F', LYE_CHAR},
+ {'G', LYE_CHAR},
+ {'H', LYE_CHAR},
+ {'I', LYE_CHAR},
+ {'J', LYE_CHAR},
+ {'K', LYE_CHAR},
+ {'L', LYE_CHAR},
+ {'M', LYE_CHAR},
+ {'N', LYE_CHAR},
+ {'O', LYE_CHAR},
+ {'P', LYE_CHAR},
+ {'Q', LYE_CHAR},
+ {'R', LYE_CHAR},
+ {'S', LYE_CHAR},
+ {'T', LYE_CHAR},
+ {'U', LYE_CHAR},
+ {'V', LYE_CHAR},
+ {'W', LYE_CHAR},
+ {'X', LYE_CHAR},
+ {'Y', LYE_CHAR},
+ {'Z', LYE_CHAR},
+ {'[', LYE_CHAR},
+ {'\\', LYE_CHAR},
+ {']', LYE_CHAR},
+ {'^', LYE_CHAR},
+ {'_', LYE_CHAR},
+ {'`', LYE_CHAR},
+ {'a', LYE_CHAR},
+ {'b', LYE_CHAR},
+ {'c', LYE_CHAR},
+ {'d', LYE_CHAR},
+ {'e', LYE_CHAR},
+ {'f', LYE_CHAR},
+ {'g', LYE_CHAR},
+ {'h', LYE_CHAR},
+ {'i', LYE_CHAR},
+ {'j', LYE_CHAR},
+ {'k', LYE_CHAR},
+ {'l', LYE_CHAR},
+ {'m', LYE_CHAR},
+ {'n', LYE_CHAR},
+ {'o', LYE_CHAR},
+ {'p', LYE_CHAR},
+ {'q', LYE_CHAR},
+ {'r', LYE_CHAR},
+ {'s', LYE_CHAR},
+ {'t', LYE_CHAR},
+ {'u', LYE_CHAR},
+ {'v', LYE_CHAR},
+ {'w', LYE_CHAR},
+ {'x', LYE_CHAR},
+ {'y', LYE_CHAR},
+ {'z', LYE_CHAR},
+ {'{', LYE_CHAR},
+ {'|', LYE_CHAR},
+ {'}', LYE_CHAR},
+ {'~', LYE_CHAR},
+ {DEL_KEY, LYE_DELP},
+ {128, LYE_CHAR},
+ {129, LYE_CHAR},
+ {130, LYE_CHAR},
+ {131, LYE_CHAR},
+ {132, LYE_CHAR},
+ {133, LYE_CHAR},
+ {134, LYE_CHAR},
+ {135, LYE_CHAR},
+ {136, LYE_CHAR},
+ {137, LYE_CHAR},
+ {138, LYE_CHAR},
+ {139, LYE_CHAR},
+ {140, LYE_CHAR},
+ {141, LYE_CHAR},
+ {142, LYE_CHAR},
+ {143, LYE_CHAR},
+ {144, LYE_CHAR},
+ {145, LYE_CHAR},
+ {146, LYE_CHAR},
+ {147, LYE_CHAR},
+ {148, LYE_CHAR},
+ {149, LYE_CHAR},
+ {150, LYE_CHAR},
+ {151, LYE_AIX},
+ {152, LYE_CHAR},
+ {153, LYE_CHAR},
+ {154, LYE_CHAR},
+ {155, LYE_CHAR},
+ {156, LYE_CHAR},
+ {157, LYE_CHAR},
+ {158, LYE_CHAR},
+ {159, LYE_CHAR},
+ {160, LYE_CHAR},
+ {161, LYE_CHAR},
+ {162, LYE_CHAR},
+ {163, LYE_CHAR},
+ {164, LYE_CHAR},
+ {165, LYE_CHAR},
+ {166, LYE_CHAR},
+ {167, LYE_CHAR},
+ {168, LYE_CHAR},
+ {169, LYE_CHAR},
+ {170, LYE_CHAR},
+ {171, LYE_CHAR},
+ {172, LYE_CHAR},
+ {173, LYE_CHAR},
+ {174, LYE_CHAR},
+ {175, LYE_CHAR},
+ {176, LYE_CHAR},
+ {177, LYE_CHAR},
+ {178, LYE_CHAR},
+ {179, LYE_CHAR},
+ {180, LYE_CHAR},
+ {181, LYE_CHAR},
+ {182, LYE_CHAR},
+ {183, LYE_CHAR},
+ {184, LYE_CHAR},
+ {185, LYE_CHAR},
+ {186, LYE_CHAR},
+ {187, LYE_CHAR},
+ {188, LYE_CHAR},
+ {189, LYE_CHAR},
+ {190, LYE_CHAR},
+ {191, LYE_CHAR},
+ {192, LYE_CHAR},
+ {193, LYE_CHAR},
+ {194, LYE_CHAR},
+ {195, LYE_CHAR},
+ {196, LYE_CHAR},
+ {197, LYE_CHAR},
+ {198, LYE_CHAR},
+ {199, LYE_CHAR},
+ {200, LYE_CHAR},
+ {201, LYE_CHAR},
+ {202, LYE_CHAR},
+ {203, LYE_CHAR},
+ {204, LYE_CHAR},
+ {205, LYE_CHAR},
+ {206, LYE_CHAR},
+ {207, LYE_CHAR},
+ {208, LYE_CHAR},
+ {209, LYE_CHAR},
+ {210, LYE_CHAR},
+ {211, LYE_CHAR},
+ {212, LYE_CHAR},
+ {213, LYE_CHAR},
+ {214, LYE_CHAR},
+ {215, LYE_CHAR},
+ {216, LYE_CHAR},
+ {217, LYE_CHAR},
+ {218, LYE_CHAR},
+ {219, LYE_CHAR},
+ {220, LYE_CHAR},
+ {221, LYE_CHAR},
+ {222, LYE_CHAR},
+ {223, LYE_CHAR},
+ {224, LYE_CHAR},
+ {225, LYE_CHAR},
+ {226, LYE_CHAR},
+ {227, LYE_CHAR},
+ {228, LYE_CHAR},
+ {229, LYE_CHAR},
+ {230, LYE_CHAR},
+ {231, LYE_CHAR},
+ {232, LYE_CHAR},
+ {233, LYE_CHAR},
+ {234, LYE_CHAR},
+ {235, LYE_CHAR},
+ {236, LYE_CHAR},
+ {237, LYE_CHAR},
+ {238, LYE_CHAR},
+ {239, LYE_CHAR},
+ {240, LYE_CHAR},
+ {241, LYE_CHAR},
+ {242, LYE_CHAR},
+ {243, LYE_CHAR},
+ {244, LYE_CHAR},
+ {245, LYE_CHAR},
+ {246, LYE_CHAR},
+ {247, LYE_CHAR},
+ {248, LYE_CHAR},
+ {249, LYE_CHAR},
+ {250, LYE_CHAR},
+ {251, LYE_CHAR},
+ {252, LYE_CHAR},
+ {253, LYE_CHAR},
+ {254, LYE_CHAR},
+ {255, LYE_CHAR},
+ {UPARROW_KEY, LYE_FORM_PASS},
+ {DNARROW_KEY, LYE_FORM_PASS},
+ {RTARROW_KEY, LYE_FORW},
+ {LTARROW_KEY, LYE_BACK},
+ {PGDOWN_KEY, LYE_FORM_PASS},
+ {PGUP_KEY, LYE_FORM_PASS},
+ {HOME_KEY, LYE_BOL},
+ {END_KEY, LYE_EOL},
+ {F1_KEY, LYE_FORM_PASS},
+#if !(defined(_WINDOWS) || defined(__DJGPP__))
+ {DO_KEY, LYE_TAB},
+ {FIND_KEY, LYE_BOL},
+ {SELECT_KEY, LYE_EOL},
+#endif
+ {REMOVE_KEY, LYE_DELN},
+ {BACKTAB_KEY, LYE_FORM_PASS},
+#if (defined(_WINDOWS) || defined(__DJGPP__)) && defined(USE_SLANG) && !defined(DJGPP_KEYHANDLER)
+ {272, LYE_DELP},
+ {273, LYE_ENTER},
+#endif
+ {-1, LYE_UNKNOWN}
+};
+#endif /* USE_ALT_BINDINGS */
+
+LYEditConfig LYLineEditors[] =
+{
+ {"Default Binding", initDefaultEditor, DefaultEditBinding},
+#ifdef USE_ALT_BINDINGS
+ {"Alternate Bindings", initBetterEditor, BetterEditBinding},
+ {"Bash-like Bindings", initBashlikeEditor, BashlikeEditBinding},
+#endif
+};
+
+const char *LYEditorNames[TABLESIZE(LYLineEditors) + 1];
+
+/*
+ * Add the URL (relative to helpfilepath) used for context-dependent
+ * help on form field editing.
+ *
+ * The order must correspond to that of LYLineditNames.
+ */
+const char *LYLineeditHelpURLs[] =
+{
+ EDIT_HELP,
+#ifdef USE_ALT_BINDINGS
+ ALT_EDIT_HELP,
+ BASHLIKE_EDIT_HELP,
+#endif
+ (char *) 0
+};
+
+static struct emap *name2emap(const char *name)
+{
+ struct emap *mp;
+ struct emap *result = 0;
+
+ if (non_empty(name)) {
+ for (mp = ekmap; mp->name != NULL; mp++) {
+ if (strcasecomp(mp->name, name) == 0) {
+ result = mp;
+ break;
+ }
+ }
+ }
+ return result;
+}
+
+static struct emap *code2emap(int code)
+{
+ struct emap *mp;
+ struct emap *result = 0;
+
+ for (mp = ekmap; mp->name != NULL; mp++) {
+ if (mp->code == code) {
+ result = mp;
+ break;
+ }
+ }
+ return result;
+}
+
+/*
+ * Return editactioncode whose name is the string func. func must be present
+ * in the ekmap table. returns -1 if not found. - kw
+ */
+int lecname_to_lec(const char *func)
+{
+ struct emap *mp;
+ int result = -1;
+
+ if ((mp = name2emap(func)) != 0) {
+ result = mp->code;
+ }
+ return result;
+}
+
+const char *lec_to_lecname(int code)
+{
+ struct emap *mp;
+ const char *result = 0;
+
+ if ((mp = code2emap(code)) != 0) {
+ result = mp->name;
+ }
+ return result;
+}
+
+int EditBinding(int xlkc)
+{
+ int editaction, xleac = LYE_UNMOD;
+ int c = xlkc & LKC_MASK;
+
+ if (xlkc == -1)
+ return LYE_NOP; /* maybe LYE_ABORT? or LYE_FORM_LAC|LYK_UNKNOWN? */
+#ifdef NOT_ASCII
+ if (c < 256) {
+ c = TOASCII(c);
+ }
+#endif
+#ifdef USE_ALT_BINDINGS
+ /*
+ * Get intermediate code from one of the lynxkeycode+modifier tables if
+ * applicable, otherwise get the lynxeditactioncode directly. If we have
+ * more than one modifier bits, the first currently wins. - kw
+ */
+ if (xlkc & LKC_ISLECLAC) {
+ return LKC2_TO_LEC(xlkc);
+ } else if (xlkc & LKC_MOD1) {
+ xleac = LKC_TO_LEC_M1(c);
+ } else if (xlkc & LKC_MOD2) {
+ xleac = LKC_TO_LEC_M2(c);
+ } else if (xlkc & LKC_MOD3) {
+ xleac = LKC_TO_LEC_M3(c);
+ } else {
+ xleac = UCH(CurrentLineEditor()[c]);
+ }
+#endif
+ /*
+ * If we have an intermediate code that says "same as without modifier",
+ * look that up now; otherwise we are already done. - kw
+ */
+ if (xleac == LYE_UNMOD) {
+ editaction = CurrentLineEditor()[c];
+ } else {
+ editaction = xleac;
+ }
+ return editaction;
+}
+
+/*
+ * Install lec as the lynxeditaction for lynxkeycode xlkc. func must be
+ * present in the revmap table. For normal (non-modifier) lynxkeycodes,
+ * select_edi selects which of the alternative line-editor binding tables is
+ * modified. If select_edi is positive, only the table given by it is modified
+ * (the DefaultEditBinding table is numbered 1). If select_edi is 0, all
+ * tables are modified. If select_edi is negative, all tables except the one
+ * given by abs(select_edi) are modified. returns TRUE if the mapping was
+ * made, FALSE if not. Note that this remapping cannot be undone (as might be
+ * desirable as a result of re-parsing lynx.cfg), we don't remember the
+ * original editaction from the Bindings tables anywhere. - kw
+ */
+BOOL LYRemapEditBinding(int xlkc,
+ int lec,
+ int select_edi)
+{
+ int j;
+ int c = xlkc & LKC_MASK;
+ BOOLEAN success = FALSE;
+
+ if (xlkc >= 0 && !(xlkc & LKC_ISLAC) && (c < KEYMAP_SIZE)) {
+ LYEditCode code = (LYEditCode) lec;
+
+#ifdef USE_ALT_BINDINGS
+ if (xlkc & LKC_MOD1) {
+ if (c <= LAST_MOD1_LKC) {
+ Mod1Binding[c] = code;
+ success = TRUE;
+ }
+ } else if (xlkc & LKC_MOD2) {
+ if (c <= LAST_MOD2_LKC) {
+ Mod2Binding[c] = code;
+ success = TRUE;
+ }
+ } else if (xlkc & LKC_MOD3) {
+ if (c <= LAST_MOD3_LKC) {
+ Mod3Binding[c] = code;
+ success = TRUE;
+ }
+ } else
+#endif /* USE_ALT_BINDINGS */
+ {
+#ifndef UCHAR_MAX
+#define UCHAR_MAX 255
+#endif
+ if ((unsigned int) lec <= UCHAR_MAX) {
+ if (select_edi > 0) {
+ if ((unsigned int) select_edi < TABLESIZE(LYLineEditors)) {
+ LYLineEditors[select_edi - 1].used[c] = code;
+ success = TRUE;
+ }
+ } else {
+ for (j = 0; j < (int) TABLESIZE(LYLineEditors); j++) {
+ success = TRUE;
+ if ((select_edi < 0) && ((j + 1 + select_edi) == 0))
+ continue;
+ LYLineEditors[j].used[c] = code;
+ }
+ }
+ }
+ }
+ }
+ return success;
+}
+
+/*
+ * Macro to walk through lkc-indexed tables up to imax, in the (ASCII) order
+ * 97 - 122 ('a' - 'z'),
+ * 32 - 96 (' ' - '`', includes 'A' - 'Z'),
+ * 123 - 126 ('{' - '~'),
+ * 0 - 31 (^@ - ^_),
+ * 256 - imax,
+ * 127 - 255
+ */
+#define NEXT_I(i,imax) ((i==122) ? 32 : (i==96) ? 123 : (i==126) ? 0 :\
+ (i==31) ? 256 : (i==imax) ? 127 :\
+ (i==255) ? (-1) :i+1)
+#define FIRST_I 97
+
+int LYKeyForEditAction(int lec)
+{
+ int editaction, i;
+
+ for (i = FIRST_I; i >= 0; i = NEXT_I(i, KEYMAP_SIZE - 1)) {
+ editaction = CurrentLineEditor()[i];
+ if (editaction == lec) {
+#ifdef NOT_ASCII
+ if (i < 256) {
+ return FROMASCII(i);
+ } else
+#endif
+ return i;
+ }
+ }
+ return (-1);
+}
+
+/*
+ * Given a lynxactioncode, return a key (lynxkeycode) or sequence of two keys
+ * that results in the given action while forms-editing. The main keycode is
+ * returned as function value, possibly with modifier bits set; in addition, if
+ * applicable, a key that sets the required modifier flag is returned in
+ * *pmodkey if (pmodkey!=NULL). Non-lineediting bindings that would require
+ * typing LYE_LKCMD (default ^V) to activate are not checked here, the caller
+ * should do that separately if required. If no key is bound by current
+ * line-editor bindings to the action, -1 is returned.
+ *
+ * This is all a bit long - it is general enough to continue to work should the
+ * three Mod<N>Binding[] become different tables. - kw
+ */
+int LYEditKeyForAction(int lac,
+ int *pmodkey)
+{
+ int editaction, i, c;
+ int mod1found = -1, mod2found = -1, mod3found = -1;
+
+ if (pmodkey)
+ *pmodkey = -1;
+ for (i = FIRST_I; i >= 0; i = NEXT_I(i, KEYMAP_SIZE - 1)) {
+ editaction = CurrentLineEditor()[i];
+#ifdef NOT_ASCII
+ if (i < 256) {
+ c = FROMASCII(i);
+ } else
+#endif
+ c = i;
+ if (editaction == (lac | LYE_FORM_LAC))
+ return c;
+ if (editaction == LYE_FORM_PASS) {
+#if defined(DIRED_SUPPORT) && defined(OK_OVERRIDE)
+ if (lynx_edit_mode && !no_dired_support && lac &&
+ LKC_TO_LAC(key_override, c) == lac)
+ return c;
+#endif /* DIRED_SUPPORT && OK_OVERRIDE */
+ if (LKC_TO_LAC(keymap, c) == lac)
+ return c;
+ }
+ if (editaction == LYE_TAB) {
+#if defined(DIRED_SUPPORT) && defined(OK_OVERRIDE)
+ if (lynx_edit_mode && !no_dired_support && lac &&
+ LKC_TO_LAC(key_override, '\t') == lac)
+ return c;
+#endif /* DIRED_SUPPORT && OK_OVERRIDE */
+ if (LKC_TO_LAC(keymap, '\t') == lac)
+ return c;
+ }
+ if (editaction == LYE_SETM1 && mod1found < 0)
+ mod1found = i;
+ if (editaction == LYE_SETM2 && mod2found < 0)
+ mod2found = i;
+ if ((editaction & LYE_DF) && mod3found < 0)
+ mod3found = i;
+ }
+#ifdef USE_ALT_BINDINGS
+ if (mod3found >= 0) {
+ for (i = mod3found; i >= 0; i = NEXT_I(i, LAST_MOD3_LKC)) {
+ editaction = CurrentLineEditor()[i];
+ if (!(editaction & LYE_DF))
+ continue;
+ editaction = Mod3Binding[i];
+#ifdef NOT_ASCII
+ if (i < 256) {
+ c = FROMASCII(i);
+ } else
+#endif
+ c = i;
+ if (pmodkey)
+ *pmodkey = c;
+ if (editaction == (lac | LYE_FORM_LAC))
+ return (c | LKC_MOD3);
+ if (editaction == LYE_FORM_PASS) {
+#if defined(DIRED_SUPPORT) && defined(OK_OVERRIDE)
+ if (lynx_edit_mode && !no_dired_support && lac &&
+ LKC_TO_LAC(key_override, c) == lac)
+ return (c | LKC_MOD3);
+#endif /* DIRED_SUPPORT && OK_OVERRIDE */
+ if (LKC_TO_LAC(keymap, c) == lac)
+ return (c | LKC_MOD3);
+ }
+ if (editaction == LYE_TAB) {
+#if defined(DIRED_SUPPORT) && defined(OK_OVERRIDE)
+ if (lynx_edit_mode && !no_dired_support && lac &&
+ LKC_TO_LAC(key_override, '\t') == lac)
+ return (c | LKC_MOD3);
+#endif /* DIRED_SUPPORT && OK_OVERRIDE */
+ if (LKC_TO_LAC(keymap, '\t') == lac)
+ return (c | LKC_MOD3);
+ }
+ }
+ }
+ if (mod1found >= 0) {
+ if (pmodkey) {
+#ifdef NOT_ASCII
+ if (mod1found < 256) {
+ *pmodkey = FROMASCII(mod1found);
+ } else
+#endif
+ *pmodkey = mod1found;
+ }
+ for (i = FIRST_I; i >= 0; i = NEXT_I(i, LAST_MOD1_LKC)) {
+ editaction = Mod1Binding[i];
+#ifdef NOT_ASCII
+ if (i < 256) {
+ c = FROMASCII(i);
+ } else
+#endif
+ c = i;
+ if (editaction == (lac | LYE_FORM_LAC))
+ return (c | LKC_MOD1);
+ if (editaction == LYE_FORM_PASS) {
+#if defined(DIRED_SUPPORT) && defined(OK_OVERRIDE)
+ if (lynx_edit_mode && !no_dired_support && lac &&
+ LKC_TO_LAC(key_override, c) == lac)
+ return (c | LKC_MOD1);
+#endif /* DIRED_SUPPORT && OK_OVERRIDE */
+ if (LKC_TO_LAC(keymap, c) == lac)
+ return (c | LKC_MOD1);
+ }
+ if (editaction == LYE_TAB) {
+#if defined(DIRED_SUPPORT) && defined(OK_OVERRIDE)
+ if (lynx_edit_mode && !no_dired_support && lac &&
+ LKC_TO_LAC(key_override, '\t') == lac)
+ return (c | LKC_MOD1);
+#endif /* DIRED_SUPPORT && OK_OVERRIDE */
+ if (LKC_TO_LAC(keymap, '\t') == lac)
+ return (c | LKC_MOD1);
+ }
+ }
+ }
+ if (mod2found >= 0) {
+ if (pmodkey) {
+#ifdef NOT_ASCII
+ if (mod1found < 256) {
+ *pmodkey = FROMASCII(mod1found);
+ } else
+#endif
+ *pmodkey = mod1found;
+ }
+ for (i = FIRST_I; i >= 0; i = NEXT_I(i, LAST_MOD2_LKC)) {
+ editaction = Mod2Binding[i];
+#ifdef NOT_ASCII
+ if (i < 256) {
+ c = FROMASCII(i);
+ } else
+#endif
+ c = i;
+ if (editaction == (lac | LYE_FORM_LAC))
+ return (c | LKC_MOD2);
+ if (editaction == LYE_FORM_PASS) {
+#if defined(DIRED_SUPPORT) && defined(OK_OVERRIDE)
+ if (lynx_edit_mode && !no_dired_support && lac &&
+ LKC_TO_LAC(key_override, c) == lac)
+ return (c | LKC_MOD2);
+#endif /* DIRED_SUPPORT && OK_OVERRIDE */
+ if (LKC_TO_LAC(keymap, c) == lac)
+ return (c | LKC_MOD2);
+ }
+ if (editaction == LYE_TAB) {
+#if defined(DIRED_SUPPORT) && defined(OK_OVERRIDE)
+ if (lynx_edit_mode && !no_dired_support && lac &&
+ LKC_TO_LAC(key_override, '\t') == lac)
+ return (c | LKC_MOD2);
+#endif /* DIRED_SUPPORT && OK_OVERRIDE */
+ if (LKC_TO_LAC(keymap, '\t') == lac)
+ return (c | LKC_MOD2);
+ }
+ }
+ }
+#endif /* USE_ALT_BINDINGS */
+ if (pmodkey)
+ *pmodkey = -1;
+ return (-1);
+}
+
+#if 0
+/*
+ * This function was useful in converting the hand-crafted key-bindings to
+ * their reusable form in 2.8.8 -TD
+ */
+static void checkEditMap(LYEditConfig * table)
+{
+ unsigned j, k;
+ char comment[80];
+ int first = TRUE;
+
+ for (j = 0; table->init[j].code >= 0; ++j) {
+ int code = table->init[j].code;
+
+ if (table->init[j].edit != table->used[code]) {
+ if (first) {
+ printf("TABLE %s\n", table->name);
+ first = FALSE;
+ }
+ printf("%u: init %d vs used %d\n",
+ j,
+ table->init[j].edit,
+ table->used[code]);
+ }
+ }
+ for (j = 0; j < KEYMAP_SIZE; ++j) {
+ int code = (int) j;
+ BOOL found = FALSE;
+
+ for (k = 0; table->init[k].code >= 0; ++k) {
+ if (code == table->init[k].code) {
+ found = TRUE;
+ break;
+ }
+ }
+ if (!found) {
+ if (table->used[j] != 0) {
+ int edit = table->used[j];
+ int has_DF = (edit & LYE_DF);
+ int has_LAC = (edit & LYE_FORM_LAC);
+ const char *prefix = "LYE_";
+ const char *name = 0;
+
+ edit &= 0x7f;
+ if (has_LAC) {
+ Kcmd *cmd = LYKeycodeToKcmd(edit);
+
+ if (cmd != 0) {
+ prefix = "LYK_";
+ name = cmd->name;
+ }
+ } else {
+ name = lec_to_lecname(edit);
+ }
+
+ if (j < 32) {
+ char temp[80];
+ const char *what = 0;
+
+ switch (j) {
+ case 0:
+ what = "nul";
+ break;
+ case 17:
+ what = "XON";
+ break;
+ case 19:
+ what = "XOFF";
+ break;
+ default:
+ sprintf(temp, "^%c", j + 'A');
+ what = temp;
+ break;
+ }
+ sprintf(comment, "\t/* %s */", what);
+ } else if (j < 127) {
+ sprintf(comment, "\t/* %c */", j);
+ } else {
+ const char *what = LYextraKeysToName(j);
+
+ if (Non_Empty(what)) {
+ sprintf(comment, "\t/* %s%s */", what,
+ ((StrChr(what, '_') != 0)
+ ? ""
+ : "_KEY"));
+ } else {
+ strcpy(comment, "");
+ }
+ }
+ if (name == 0) {
+ name = "XXX";
+ } else if (!strcasecomp(name, "PASS")) {
+ name = "FORM_PASS";
+ }
+ if (first) {
+ printf("TABLE %s\n", table->name);
+ first = FALSE;
+ }
+ printf("\t{ %d, %s%s%s%s },%s\n", code, prefix, name,
+ has_DF ? "|LYE_DF" : "",
+ has_LAC ? "|LYE_FORM_LAC" : "",
+ comment);
+ }
+ }
+ }
+}
+
+#else
+#define checkEditMap(table) /* nothing */
+#endif
+
+static void initLineEditor(LYEditConfig * table)
+{
+ unsigned k;
+ LYEditCode *used = table->used;
+ const LYEditInit *init = table->init;
+
+ memset(used, 0, sizeof(LYEditCode) * KEYMAP_SIZE);
+
+ for (k = 0; init[k].code >= 0; ++k) {
+ int code = init[k].code;
+
+ used[code] = init[k].edit;
+ }
+ checkEditMap(table);
+}
+
+/*
+ * Reset the editor bindings to their default values.
+ */
+void LYinitEditmap(void)
+{
+ unsigned j;
+
+ for (j = 0; j < TABLESIZE(LYLineEditors); ++j) {
+ LYEditorNames[j] = LYLineEditors[j].name;
+ initLineEditor(&LYLineEditors[j]);
+ }
+#ifdef USE_ALT_BINDINGS
+ for (j = 0; j < TABLESIZE(LYModifierBindings); ++j) {
+ initLineEditor(&LYModifierBindings[j]);
+ }
+#endif
+}
+
+static char *showRanges(int *state)
+{
+ char *result = 0;
+ int range[2];
+ int i;
+
+ range[0] = range[1] = -1;
+ for (i = 0; i < KEYMAP_SIZE; ++i) {
+ if (!state[i]) {
+ int code = CurrentLineEditor()[i];
+
+ if (code == LYE_CHAR) {
+ if (range[0] < 0)
+ range[0] = i;
+ range[1] = i;
+ state[i] = 3;
+ } else if (range[0] >= 0) {
+ if (non_empty(result))
+ StrAllocCat(result, ", ");
+ HTSprintf(&result, "%d-%d", range[0], range[1]);
+ range[0] = range[1] = -1;
+ }
+ }
+ }
+ return result;
+}
+
+static int LYLoadEditmap(const char *arg GCC_UNUSED,
+ HTParentAnchor *anAnchor,
+ HTFormat format_out,
+ HTStream *sink)
+{
+#define FORMAT " %-*s %-*s - %s\n"
+ HTFormat format_in = WWW_HTML;
+ HTStream *target;
+ int state[KEYMAP_SIZE];
+ int width[2];
+ char *buf = 0;
+ char *ranges = 0;
+ struct emap *mp;
+ int i;
+ int hanging;
+ int wrapped;
+ int had_output = FALSE;
+ int result;
+
+ if ((target = HTStreamStack(format_in, format_out, sink, anAnchor)) != 0) {
+ anAnchor->no_cache = TRUE;
+
+ HTSprintf0(&buf,
+ "<html>\n<head>\n<title>%s</title>\n</head>\n<body>\n",
+ CURRENT_EDITMAP_TITLE);
+ PUTS(buf);
+ HTSprintf0(&buf, "<pre>\n");
+ PUTS(buf);
+
+ /* determine the column-widths we will use for showing bindings */
+ width[0] = 0;
+ width[1] = 0;
+ for (i = 0; i < KEYMAP_SIZE; ++i) {
+ int code = CurrentLineEditor()[i];
+
+ if (code == LYE_NOP) {
+ state[i] = 1;
+ } else {
+ int need;
+
+ if ((mp = code2emap(code)) != 0) {
+ state[i] = 0;
+ if ((need = (int) strlen(mp->name)) > width[0])
+ width[0] = need;
+ if ((need = (int) strlen(mp->descr)) > width[1])
+ width[1] = need;
+ } else {
+ state[i] = 2;
+ }
+ }
+ }
+ hanging = 2 + width[0] + 2 + width[1] + 5;
+ wrapped = hanging;
+
+ /*
+ * Tell which set of bindings we are showing, and link to the
+ * handcrafted page, which adds explanations.
+ */
+ PUTS(gettext("These are the current edit-bindings:"));
+ HTSprintf0(&buf,
+ " <a href=\"%s\">%s</a>\n\n",
+ LYLineeditHelpURL(),
+ LYEditorNames[current_lineedit]);
+ PUTS(buf);
+
+ /* Show by groups to match the arrangement in the handmade files. */
+ for (mp = ekmap; mp->name != 0; ++mp) {
+ if (isEmpty(mp->name)) {
+ if (had_output) {
+ PUTS("\n");
+ had_output = FALSE;
+ }
+ } else if (mp->code == LYE_CHAR) {
+ ranges = showRanges(state);
+ HTSprintf0(&buf, FORMAT,
+ width[0], mp->name,
+ width[1], mp->descr,
+ ranges);
+ FREE(ranges);
+ PUTS(buf);
+ had_output = TRUE;
+ } else {
+ for (i = 0; i < KEYMAP_SIZE; ++i) {
+ int code = CurrentLineEditor()[i];
+
+ if ((code == mp->code) && !state[i]) {
+ char *value = LYKeycodeToString(i, (i >= 160 &&
+ i <= 255));
+ int before = wrapped + (ranges ? ((int)
+ strlen(ranges)) : 0);
+ int after = before;
+
+ if (non_empty(ranges)) {
+ StrAllocCat(ranges, ", ");
+ after += 2;
+ }
+ after += (int) strlen(value) + 2;
+ if ((before / LYcols) != (after / LYcols)) {
+ wrapped += (LYcols - (before % LYcols));
+ HTSprintf(&ranges, "\n%-*s", hanging, " ");
+ }
+ StrAllocCat(ranges, value);
+ }
+ }
+ if (non_empty(ranges)) {
+ LYEntify(&ranges, TRUE);
+ HTSprintf0(&buf, FORMAT,
+ width[0], mp->name,
+ width[1], mp->descr,
+ ranges);
+ PUTS(buf);
+ FREE(ranges);
+ had_output = TRUE;
+ }
+ }
+ }
+
+ HTSprintf0(&buf, "</pre>\n</body>\n</html>\n");
+ PUTS(buf);
+
+ (*target->isa->_free) (target);
+ result = HT_LOADED;
+ } else {
+ HTSprintf0(&buf, CANNOT_CONVERT_I_TO_O,
+ HTAtom_name(format_in), HTAtom_name(format_out));
+ HTAlert(buf);
+ result = HT_NOT_LOADED;
+ }
+ FREE(ranges);
+ FREE(buf);
+ return result;
+#undef FORMAT
+}
+
+#ifdef GLOBALDEF_IS_MACRO
+#define _LYEDITMAP_C_GLOBALDEF_1_INIT { "LYNXEDITMAP", LYLoadEditmap, 0}
+GLOBALDEF(HTProtocol, LYLynxEditmap, _LYEDITMAP_C_GLOBALDEF_1_INIT);
+#else
+GLOBALDEF HTProtocol LYLynxEditmap =
+{
+ "LYNXEDITMAP", LYLoadEditmap, 0
+};
+#endif /* GLOBALDEF_IS_MACRO */
diff --git a/src/LYExtern.c b/src/LYExtern.c
new file mode 100644
index 0000000..d36ba0b
--- /dev/null
+++ b/src/LYExtern.c
@@ -0,0 +1,443 @@
+/*
+ * $LynxId: LYExtern.c,v 1.55 2018/02/15 01:53:07 tom Exp $
+ *
+ External application support.
+ This feature allows lynx to pass a given URL to an external program.
+ It was written for three reasons.
+ 1) To overcome the deficiency of Lynx_386 not supporting ftp and news.
+ External programs can be used instead by passing the URL.
+
+ 2) To allow for background transfers in multitasking systems.
+ I use wget for http and ftp transfers via the external command.
+
+ 3) To allow for new URLs to be used through lynx.
+ URLs can be made up such as mymail: to spawn desired applications
+ via the external command.
+
+ See lynx.cfg for other info.
+*/
+
+#include <LYUtils.h>
+
+#ifdef USE_EXTERNALS
+
+#include <HTAlert.h>
+#include <LYGlobalDefs.h>
+#include <LYExtern.h>
+#include <LYLeaks.h>
+#include <LYCurses.h>
+#include <LYReadCFG.h>
+#include <LYStrings.h>
+
+#ifdef WIN_EX
+/* ASCII char -> HEX digit */
+#define ASC2HEXD(x) ((UCH(x) >= '0' && UCH(x) <= '9') ? \
+ (UCH(x) - '0') : (toupper(UCH(x)) - 'A' + 10))
+
+/* Decodes the forms %xy in a URL to the character the hexadecimal
+ code of which is xy. xy are hexadecimal digits from
+ [0123456789ABCDEF] (case-insensitive). If x or y are not hex-digits
+ or '%' is near '\0', the whole sequence is inserted literally. */
+
+static char *decode_string(char *s)
+{
+ char *save_s;
+ char *p = s;
+
+ save_s = s;
+ for (; *s; s++, p++) {
+ if (*s != '%')
+ *p = *s;
+ else {
+ /* Do nothing if at the end of the string. Or if the chars
+ are not hex-digits. */
+ if (!*(s + 1) || !*(s + 2)
+ || !(isxdigit(UCH(*(s + 1))) && isxdigit(UCH(*(s + 2))))) {
+ *p = *s;
+ continue;
+ }
+ *p = (char) ((ASC2HEXD(*(s + 1)) << 4) + ASC2HEXD(*(s + 2)));
+ s += 2;
+ }
+ }
+ *p = '\0';
+ return save_s;
+}
+#endif /* WIN_EX */
+
+#ifdef WIN_EX
+/*
+ * Delete dangerous characters as local path.
+ * We delete '<>|' and also '%"'.
+ * '%' should be deleted because it's difficut to escape for all cases.
+ * So we can't treat paths which include '%'.
+ * '"' should be deleted because it's a obstacle to quote whole path.
+ */
+static void delete_danger_characters(char *src)
+{
+ char *dst;
+
+ for (dst = src; *src != '\0'; src++) {
+ if (StrChr("<>|%\"", *src) == NULL) {
+ *dst = *src;
+ dst++;
+ }
+ }
+ *dst = '\0';
+}
+
+static char *escapeParameter(CONST char *parameter)
+{
+ size_t i;
+ size_t last = strlen(parameter);
+ size_t n = 0;
+ size_t encoded = 0;
+ size_t escaped = 0;
+ char *result;
+ char *needs_encoded = "<>|";
+ char *needs_escaped = "%";
+ char *needs_escaped_NT = "%&^";
+
+ for (i = 0; i < last; ++i) {
+ if (StrChr(needs_encoded, parameter[i]) != NULL) {
+ ++encoded;
+ }
+ if (system_is_NT) {
+ if (StrChr(needs_escaped_NT, parameter[i]) != NULL) {
+ ++escaped;
+ }
+ } else if (StrChr(needs_escaped, parameter[i]) != NULL) {
+ ++escaped;
+ }
+ }
+
+ result = (char *) malloc(last + encoded * 2 + escaped + 1);
+ if (result == NULL)
+ outofmem(__FILE__, "escapeParameter");
+
+ n = 0;
+ for (i = 0; i < last; i++) {
+ if (StrChr(needs_encoded, parameter[i]) != NULL) {
+ sprintf(result + n, "%%%02X", (unsigned char) parameter[i]);
+ n += 3;
+ continue;
+ }
+ if (system_is_NT) {
+ if (StrChr(needs_escaped_NT, parameter[i]) != NULL) {
+ result[n++] = '^';
+ result[n++] = parameter[i];
+ continue;
+ }
+ } else if (StrChr(needs_escaped, parameter[i]) != NULL) {
+ result[n++] = '%'; /* parameter[i] is '%' */
+ result[n++] = parameter[i];
+ continue;
+ }
+ result[n++] = parameter[i];
+ }
+ result[n] = '\0';
+
+ return result;
+}
+#endif /* WIN_EX */
+
+static void format(char **result,
+ char *fmt,
+ char *parm)
+{
+ *result = NULL;
+ HTAddParam(result, fmt, 1, parm);
+ HTEndParam(result, fmt, 1);
+}
+
+/*
+ * Format the given command into a buffer, returning the resulting string.
+ *
+ * It is too dangerous to leave any URL that may come along unquoted. They
+ * often contain '&', ';', and '?' chars, and who knows what else may occur.
+ * Prevent spoofing of the shell. Dunno how this needs to be modified for VMS
+ * or DOS. - kw
+ */
+static char *format_command(char *command,
+ char *param)
+{
+ char *cmdbuf = NULL;
+
+#if defined(WIN_EX)
+ char pram_string[LY_MAXPATH];
+ char *escaped = NULL;
+
+ if (strncasecomp("file://localhost/", param, 17) == 0) {
+ /* decode local path parameter for programs to be
+ able to interpret - TH */
+ LYStrNCpy(pram_string, param, sizeof(pram_string) - 1);
+ decode_string(pram_string);
+ param = pram_string;
+ } else {
+ /* encode or escape URL parameter - TH */
+ escaped = escapeParameter(param);
+ param = escaped;
+ }
+
+ if (isMAILTO_URL(param)) {
+ format(&cmdbuf, command, param + 7);
+ } else if (strncasecomp("telnet://", param, 9) == 0) {
+ char host[sizeof(pram_string)];
+ int last_pos;
+
+ LYStrNCpy(host, param + 9, sizeof(host));
+ last_pos = (int) strlen(host) - 1;
+ if (last_pos > 1 && host[last_pos] == '/')
+ host[last_pos] = '\0';
+
+ format(&cmdbuf, command, host);
+ } else if (strncasecomp("file://localhost/", param, 17) == 0) {
+ char e_buff[LY_MAXPATH], *p;
+
+ p = param + 17;
+ delete_danger_characters(p);
+ *e_buff = 0;
+ if (StrChr(p, ':') == NULL) {
+ sprintf(e_buff, "%.3s/", windows_drive);
+ }
+ strncat(e_buff, p, sizeof(e_buff) - strlen(e_buff) - 1);
+ p = strrchr(e_buff, '.');
+ if (p) {
+ trimPoundSelector(p);
+ }
+
+ /* Less ==> short filename with backslashes,
+ * less ==> long filename with forward slashes, may be quoted
+ */
+ if (ISUPPER(command[0])) {
+ char *short_name = HTDOS_short_name(e_buff);
+
+ p = quote_pathname(short_name);
+ format(&cmdbuf, command, p);
+ FREE(p);
+ } else {
+ p = quote_pathname(e_buff);
+ format(&cmdbuf, command, p);
+ FREE(p);
+ }
+ } else {
+ format(&cmdbuf, command, param);
+ }
+ FREE(escaped);
+#else
+ format(&cmdbuf, command, param);
+#endif
+ return cmdbuf;
+}
+
+/*
+ * Find the EXTERNAL command which matches the given name 'param'. If there is
+ * more than one possibility, make a popup menu of the matching commands and
+ * allow the user to select one. Return the selected command.
+ */
+static char *lookup_external(char *param,
+ int only_overriders)
+{
+ int pass, num_disabled, num_matched, num_choices, cur_choice;
+ size_t length = 0;
+ char *cmdbuf = NULL;
+ char **actions = 0;
+ char **choices = 0;
+ lynx_list_item_type *ptr = 0;
+
+ for (pass = 0; pass < 2; pass++) {
+ num_disabled = 0;
+ num_matched = 0;
+ num_choices = 0;
+ for (ptr = externals; ptr != 0; ptr = ptr->next) {
+
+ if (match_item_by_name(ptr, param, only_overriders)) {
+ ++num_matched;
+ CTRACE((tfp, "EXTERNAL: '%s' <==> '%s'\n", ptr->name, param));
+ if (no_externals && !ptr->always_enabled && !only_overriders) {
+ ++num_disabled;
+ } else {
+ if (pass == 0) {
+ length++;
+ } else if (pass != 0) {
+ cmdbuf = format_command(ptr->command, param);
+ if (length > 1) {
+ actions[num_choices] = cmdbuf;
+ choices[num_choices] =
+ format_command(ptr->menu_name, param);
+ }
+ }
+ num_choices++;
+ }
+ }
+ }
+ if (length > 1) {
+ if (pass == 0) {
+ actions = typecallocn(char *, length + 1);
+ choices = typecallocn(char *, length + 1);
+
+ if (actions == 0 || choices == 0)
+ outofmem(__FILE__, "lookup_external");
+ } else {
+ actions[num_choices] = 0;
+ choices[num_choices] = 0;
+ }
+ }
+ }
+
+ if (num_disabled != 0
+ && num_disabled == num_matched) {
+ HTUserMsg(EXTERNALS_DISABLED);
+ } else if (num_choices > 1) {
+ int old_y, old_x;
+
+ LYGetYX(old_y, old_x);
+ cur_choice = LYhandlePopupList(-1,
+ 0,
+ old_x,
+ (STRING2PTR) choices,
+ -1,
+ -1,
+ FALSE,
+ TRUE);
+ wmove(LYwin, old_y, old_x);
+ CTRACE((tfp, "selected choice %d of %d\n", cur_choice, num_choices));
+ if (cur_choice < 0) {
+ HTInfoMsg(CANCELLED);
+ cmdbuf = 0;
+ }
+ for (pass = 0; choices[pass] != 0; pass++) {
+ if (pass == cur_choice) {
+ cmdbuf = actions[pass];
+ } else {
+ FREE(actions[pass]);
+ }
+ FREE(choices[pass]);
+ }
+ }
+
+ if (actions) {
+ for (pass = 0; actions[pass] != 0; ++pass) {
+ if (actions[pass] != cmdbuf)
+ FREE(actions[pass]);
+ }
+ FREE(actions);
+ }
+
+ if (choices) {
+ for (pass = 0; choices[pass] != 0; ++pass) {
+ FREE(choices[pass]);
+ }
+ FREE(choices);
+ }
+
+ return cmdbuf;
+}
+
+BOOL run_external(char *param,
+ int only_overriders)
+{
+#ifdef WIN_EX
+ int status;
+#endif
+ int redraw_flag = TRUE;
+ char *cmdbuf = NULL;
+ BOOL found = FALSE;
+ int confirmed = TRUE;
+
+ if (externals == NULL)
+ return 0;
+
+#ifdef WIN_EX /* 1998/01/26 (Mon) 09:16:13 */
+ if (param == NULL) {
+ HTInfoMsg(gettext("External command is null"));
+ return 0;
+ }
+#endif
+
+ cmdbuf = lookup_external(param, only_overriders);
+ if (non_empty(cmdbuf)) {
+#ifdef WIN_EX /* 1997/10/17 (Fri) 14:07:50 */
+ int len;
+ char buff[LY_MAXPATH];
+
+ CTRACE((tfp, "Lynx EXTERNAL: '%s'\n", cmdbuf));
+#ifdef WIN_GUI /* 1997/11/06 (Thu) 14:17:15 */
+ confirmed = MessageBox(GetForegroundWindow(), cmdbuf,
+ "Lynx (EXTERNAL COMMAND EXEC)",
+ MB_ICONQUESTION | MB_SETFOREGROUND | MB_OKCANCEL)
+ != IDCANCEL;
+#else
+ confirmed = HTConfirm(LYElideString(cmdbuf, 40)) != NO;
+#endif
+ if (confirmed) {
+ len = (int) strlen(cmdbuf);
+ if (len > 255) {
+ sprintf(buff, "Lynx: command line too long (%d > 255)", len);
+#ifdef WIN_GUI /* 1997/11/06 (Thu) 14:17:02 */
+ MessageBox(GetForegroundWindow(), buff,
+ "Lynx (EXTERNAL COMMAND EXEC)",
+ MB_ICONEXCLAMATION | MB_SETFOREGROUND | MB_OK);
+ SetConsoleTitle("Lynx for Win32");
+#else
+ HTConfirm(LYElideString(buff, 40));
+#endif
+ confirmed = FALSE;
+ } else {
+ SetConsoleTitle(cmdbuf);
+ }
+ }
+
+ if (strncasecomp(cmdbuf, "start ", 6) == 0)
+ redraw_flag = FALSE;
+ else
+ redraw_flag = TRUE;
+#else
+ HTUserMsg(cmdbuf);
+#endif
+ found = TRUE;
+ if (confirmed) {
+ if (redraw_flag) {
+ stop_curses();
+ fflush(stdout);
+ }
+
+ /* command running. */
+#ifdef WIN_EX /* 1997/10/17 (Fri) 14:07:50 */
+#if defined(__CYGWIN__) || defined(__MINGW32__)
+ status = system(cmdbuf);
+#else
+ status = xsystem(cmdbuf);
+#endif
+ if (status != 0) {
+ sprintf(buff,
+ "EXEC code = %04x (%2d, %2d)\r\n"
+ "'%s'",
+ status, (status / 256), (status & 0xff),
+ cmdbuf);
+#ifdef SH_EX /* WIN_GUI for ERROR only */
+ MessageBox(GetForegroundWindow(), buff,
+ "Lynx (EXTERNAL COMMAND EXEC)",
+ MB_ICONSTOP | MB_SETFOREGROUND | MB_OK);
+#else
+ HTConfirm(LYElideString(buff, 40));
+#endif /* 1 */
+ }
+#else /* Not WIN_EX */
+ LYSystem(cmdbuf);
+#endif /* WIN_EX */
+
+#if defined(WIN_EX)
+ SetConsoleTitle("Lynx for Win32");
+#endif
+ if (redraw_flag) {
+ fflush(stdout);
+ start_curses();
+ }
+ }
+ }
+
+ FREE(cmdbuf);
+ return found;
+}
+#endif /* USE_EXTERNALS */
diff --git a/src/LYExtern.h b/src/LYExtern.h
new file mode 100644
index 0000000..89c84c9
--- /dev/null
+++ b/src/LYExtern.h
@@ -0,0 +1,16 @@
+/* $LynxId: LYExtern.h,v 1.14 2010/09/24 09:39:20 tom Exp $ */
+#ifndef EXTERNALS_H
+#define EXTERNALS_H
+
+#ifndef LYSTRUCTS_H
+#include <LYStructs.h>
+#endif /* LYSTRUCTS_H */
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+ extern BOOL run_external(char *c, int only_overriders);
+#ifdef __cplusplus
+}
+#endif
+#endif /* EXTERNALS_H */
diff --git a/src/LYForms.c b/src/LYForms.c
new file mode 100644
index 0000000..3f9c111
--- /dev/null
+++ b/src/LYForms.c
@@ -0,0 +1,1086 @@
+/* $LynxId: LYForms.c,v 1.119 2022/04/02 00:13:32 Paul.G.Fox Exp $ */
+#include <HTUtils.h>
+#include <HTCJK.h>
+#include <HTTP.h>
+#include <HTAlert.h>
+#include <LYCurses.h>
+#include <GridText.h>
+#include <LYCharSets.h>
+#include <UCAux.h>
+#include <LYGlobalDefs.h>
+#include <LYUtils.h>
+#include <LYStrings.h>
+#include <LYKeymap.h>
+#include <LYClean.h>
+
+#include <LYLeaks.h>
+
+#ifdef USE_COLOR_STYLE
+#include <AttrList.h>
+#include <LYHash.h>
+#endif
+
+#if defined(VMS) && !defined(USE_SLANG)
+#define RepaintKey() 12 /* CTRL-L for repaint */
+#else
+#define RepaintKey() ((!enable_scrollback) ? 23 : 12) /* CTRL-W or CTRL-L */
+#endif /* VMS && !USE_SLANG */
+
+static int form_getstr(int cur,
+ int use_last_tfpos,
+ int redraw_only);
+
+/*
+ * Returns an array of pointers to the given list
+ */
+static char **options_list(OptionType * opt_ptr)
+{
+ char **result = 0;
+ size_t len;
+ int pass;
+ OptionType *tmp_ptr;
+
+ for (pass = 0; pass < 2; pass++) {
+ for (tmp_ptr = opt_ptr, len = 0; tmp_ptr != 0; tmp_ptr = tmp_ptr->next) {
+ if (pass != 0)
+ result[len] = tmp_ptr->name;
+ len++;
+ }
+ if (pass == 0) {
+ len++;
+ result = typecallocn(char *, len);
+
+ if (result == 0)
+ outofmem(__FILE__, "options_list");
+ } else {
+ result[len] = 0;
+ }
+ }
+
+ return result;
+}
+
+int change_form_link_ex(int cur,
+ DocInfo *newdoc,
+ BOOLEAN *refresh_screen,
+ int use_last_tfpos,
+ int immediate_submit,
+ int redraw_only)
+{
+ FormInfo *form = links[cur].l_form;
+ char *link_name;
+ char *link_value;
+ int newdoc_changed = 0;
+ int c = DO_NOTHING;
+ int title_adjust = (no_title ? -TITLE_LINES : 0);
+ char **my_data = 0;
+
+ /*
+ * If there is no form to perform action on, don't do anything.
+ */
+ if (form == NULL) {
+ return (c);
+ }
+ link_name = form->name;
+ link_value = form->value;
+ my_data = options_list(form->select_list);
+
+ /*
+ * Move to the link position.
+ */
+ LYmove(links[cur].ly + title_adjust, links[cur].lx);
+
+ switch (form->type) {
+ case F_CHECKBOX_TYPE:
+ if (FormIsReadonly(form))
+ break;
+ LYSetHilite(cur, form->num_value ? unchecked_box : checked_box);
+ form->num_value = !form->num_value;
+ break;
+
+ case F_OPTION_LIST_TYPE:
+ if (form->select_list == 0) {
+ HTAlert(BAD_HTML_NO_POPUP);
+ c = DO_NOTHING;
+ break;
+ }
+
+ if (FormIsReadonly(form)) {
+ (void) LYhandlePopupList(form->num_value,
+ links[cur].ly,
+ links[cur].lx,
+ (STRING2PTR) my_data,
+ form->size,
+ form->size_l,
+ FormIsReadonly(form),
+ FALSE);
+ c = RepaintKey();
+ break;
+ }
+ form->num_value = LYhandlePopupList(form->num_value,
+ links[cur].ly,
+ links[cur].lx,
+ (STRING2PTR) my_data,
+ form->size,
+ form->size_l,
+ FormIsReadonly(form),
+ FALSE);
+ {
+ OptionType *opt_ptr = form->select_list;
+ int i;
+
+ for (i = 0; i < form->num_value; i++, opt_ptr = opt_ptr->next) ; /* null body */
+ /*
+ * Set the name.
+ */
+ form->value = opt_ptr->name;
+ /*
+ * Set the value.
+ */
+ form->cp_submit_value = opt_ptr->cp_submit_value;
+ /*
+ * Set charset in which we have the submit value. - kw
+ */
+ form->value_cs = opt_ptr->value_cs;
+ }
+ c = RepaintKey();
+ break;
+
+ case F_RADIO_TYPE:
+ if (FormIsReadonly(form))
+ break;
+ /*
+ * Radio buttons must have one and only one down at a time!
+ */
+ if (form->num_value) {
+ if (user_mode == NOVICE_MODE) {
+ HTUserMsg(NEED_CHECKED_RADIO_BUTTON);
+ }
+ } else {
+ int i;
+
+ /*
+ * Run though list of the links on the screen and unselect any that
+ * are selected. :)
+ */
+ lynx_start_radio_color();
+ for (i = 0; i < nlinks; i++) {
+ if (links[i].type == WWW_FORM_LINK_TYPE
+ && links[i].l_form->type == F_RADIO_TYPE
+ && links[i].l_form->number == form->number
+ /*
+ * If it has the same name and its on...
+ */
+ && !strcmp(links[i].l_form->name, form->name)
+ && links[i].l_form->num_value) {
+ LYmove(links[i].ly, links[i].lx);
+ LYaddstr(unchecked_radio);
+ LYSetHilite(i, unchecked_radio);
+ }
+ }
+ lynx_stop_radio_color();
+ /*
+ * Will unselect other button and select this one.
+ */
+ HText_activateRadioButton(form);
+ /*
+ * Now highlight this one.
+ */
+ LYSetHilite(cur, checked_radio);
+ }
+ break;
+
+ case F_FILE_TYPE:
+ case F_TEXT_TYPE:
+ case F_TEXTAREA_TYPE:
+ case F_PASSWORD_TYPE:
+ c = form_getstr(cur, use_last_tfpos, redraw_only);
+ LYSetHilite(cur, ((form->type == F_PASSWORD_TYPE)
+ ? STARS(LYstrCells(form->value))
+ : form->value));
+ break;
+
+ case F_RESET_TYPE:
+ if (FormIsReadonly(form))
+ break;
+ HText_ResetForm(form);
+ *refresh_screen = TRUE;
+ break;
+
+ case F_TEXT_SUBMIT_TYPE:
+ if (redraw_only) {
+ c = form_getstr(cur, use_last_tfpos, TRUE);
+ break;
+ }
+ if (!immediate_submit)
+ c = form_getstr(cur, use_last_tfpos, FALSE);
+ if (FormIsReadonly(form) &&
+ (c == '\r' || c == '\n' || immediate_submit)) {
+ if (peek_mouse_link() >= 0)
+ c = LAC_TO_LKC0(LYK_ACTIVATE);
+ else
+ c = '\t';
+ break;
+ }
+ /*
+ * If immediate_submit is set, we didn't enter the line editor above,
+ * and will now try to call HText_SubmitForm() directly. If
+ * immediate_submit is not set, c is the lynxkeycode returned from line
+ * editing. Then if c indicates that a key was pressed that means we
+ * should submit, but with some extra considerations (i.e. NOCACHE,
+ * DOWNLOAD, different from simple Enter), or if we should act on some
+ * *other* link selected with the mouse, we'll just return c and leave
+ * it to mainloop() to do the right thing; if everything checks out, it
+ * should call this function again, with immediate_submit set.
+ *
+ * If c indicates that line editing ended with Enter, we still defer to
+ * mainloop() for further checking if the submit action URL could
+ * require more checks than we do here. Only in the remaining cases do
+ * we proceed to call HText_SubmitForm() directly before returning. -
+ * kw
+ */
+ if (immediate_submit ||
+ ((c == '\r' ||
+ c == '\n' ||
+ c == LAC_TO_LKC0(LYK_MOUSE_SUBMIT)) &&
+ peek_mouse_link() == -1)) {
+ LYSetHilite(cur, form->value);
+#ifdef TEXT_SUBMIT_CONFIRM_WANTED
+ if (!immediate_submit && (c == '\r' || c == '\n') &&
+ !HTConfirmDefault(NO_SUBMIT_BUTTON_QUERY, YES)) {
+ /* User was prompted and declined; if canceled with ^G
+ * let mainloop stay on this field, otherwise move on to
+ * the next field or link. - kw
+ */
+ if (HTLastConfirmCancelled())
+ c = DO_NOTHING;
+ else
+ c = LAC_TO_LKC(LYK_NEXT_LINK);
+ break;
+ }
+#endif
+ if (isEmpty(form->submit_action)) {
+ HTUserMsg(NO_FORM_ACTION);
+ c = DO_NOTHING;
+ break;
+ } else if (form->submit_method == URL_MAIL_METHOD && no_mail) {
+ HTAlert(FORM_MAILTO_DISALLOWED);
+ c = DO_NOTHING;
+ break;
+ } else if (!immediate_submit &&
+ ((no_file_url &&
+ isFILE_URL(form->submit_action)) ||
+ !strncasecomp(form->submit_action, "lynx", 4))) {
+ c = LAC_TO_LKC0(LYK_MOUSE_SUBMIT);
+ break;
+ } else {
+ if (form->no_cache &&
+ form->submit_method != URL_MAIL_METHOD) {
+ LYforce_no_cache = TRUE;
+ reloading = TRUE;
+ }
+ newdoc_changed =
+ HText_SubmitForm(form, newdoc, link_name, form->value);
+ }
+ if (form->submit_method == URL_MAIL_METHOD) {
+ *refresh_screen = TRUE;
+ } else {
+ /*
+ * Returns new document URL.
+ */
+ newdoc->link = 0;
+ newdoc->internal_link = FALSE;
+ }
+ c = DO_NOTHING;
+ break;
+ } else {
+ LYSetHilite(cur, form->value);
+ }
+ break;
+
+ case F_SUBMIT_TYPE:
+ case F_IMAGE_SUBMIT_TYPE:
+ if (FormIsReadonly(form))
+ break;
+ if (form->no_cache &&
+ form->submit_method != URL_MAIL_METHOD) {
+ LYforce_no_cache = TRUE;
+ reloading = TRUE;
+ }
+ newdoc_changed =
+ HText_SubmitForm(form, newdoc, link_name, link_value);
+ if (form->submit_method == URL_MAIL_METHOD)
+ *refresh_screen = TRUE;
+ else {
+ /* returns new document URL */
+ newdoc->link = 0;
+ newdoc->internal_link = FALSE;
+ }
+ break;
+
+ }
+
+ if (newdoc_changed) {
+ c = LKC_DONE;
+ } else {
+ /*
+ * These flags may have been set in mainloop, anticipating that a
+ * request will be submitted. But if we haven't filled in newdoc, that
+ * won't actually be the case, so unset them. - kw
+ */
+ LYforce_no_cache = FALSE;
+ reloading = FALSE;
+ }
+ FREE(my_data);
+ return (c);
+}
+
+int change_form_link(int cur,
+ DocInfo *newdoc,
+ BOOLEAN *refresh_screen,
+ int use_last_tfpos,
+ int immediate_submit)
+{
+ /*pass all our args and FALSE as last arg */
+ return change_form_link_ex(cur,
+ newdoc,
+ refresh_screen,
+ use_last_tfpos,
+ immediate_submit,
+ FALSE /*redraw_only */ );
+}
+
+static int LastTFPos = -1; /* remember last text field position */
+
+static void LYSetLastTFPos(int pos)
+{
+ LastTFPos = pos;
+}
+
+static int form_getstr(int cur,
+ int use_last_tfpos,
+ int redraw_only)
+{
+ FormInfo *form = links[cur].l_form;
+ char *link_value = form->value;
+ int ch;
+ int far_col;
+ int startcol, startline;
+ int action, repeat;
+ int last_xlkc = -1;
+
+#ifdef SUPPORT_MULTIBYTE_EDIT
+ BOOL refresh_mb = TRUE;
+#endif
+
+ FieldEditor MyEdit, *edit = &MyEdit;
+ BOOLEAN Edited = FALSE; /* Value might be updated? */
+
+ /*
+ * Get the initial position of the cursor.
+ */
+ LYGetYX(startline, startcol);
+ if (startline < 0)
+ startline = 0;
+ if (startcol < 0)
+ startcol = 0;
+ if ((startcol + form->size) > LYcolLimit)
+ far_col = LYcolLimit;
+ else
+ far_col = (startcol + form->size);
+
+ /*
+ * Make sure the form field value does not exceed our buffer. - FM
+ */
+ if (form->maxlength != 0 &&
+ strlen(form->value) > form->maxlength) {
+ /*
+ * We can't fit the entire value into the editing buffer, so enter as
+ * much of the tail as fits. - FM
+ */
+ link_value += (strlen(form->value) - form->maxlength);
+ if (!FormIsReadonly(form) &&
+ !(form->submit_method == URL_MAIL_METHOD && no_mail)) {
+ /*
+ * If we can edit it, report that we are using the tail. - FM
+ */
+ HTUserMsg(FORM_VALUE_TOO_LONG);
+ show_formlink_statusline(form, redraw_only ? FOR_PANEL : FOR_INPUT);
+ LYmove(startline, startcol);
+ }
+ }
+
+ /*
+ * Print panned line
+ */
+ LYSetupEdit(edit, link_value, form->maxlength, (far_col - startcol));
+ edit->efPadChar = '_';
+ edit->efIsMasked = (BOOL) (form->type == F_PASSWORD_TYPE);
+ if (use_last_tfpos &&
+ LastTFPos >= 0 &&
+ LastTFPos < (int) edit->efBufInUse) {
+#if defined(TEXTFIELDS_MAY_NEED_ACTIVATION) && defined(INACTIVE_INPUT_STYLE_VH)
+ if (redraw_only) {
+ if (!((int) edit->efBufInUse >= edit->efWidth &&
+ LastTFPos >= edit->efWidth - edit->efPanMargin)) {
+ edit->efEditAt = LastTFPos;
+ if ((int) edit->efBufInUse >= edit->efWidth)
+ textinput_redrawn = FALSE;
+ }
+ } else
+#endif /* TEXTFIELDS_MAY_NEED_ACTIVATION && INACTIVE_INPUT_STYLE_VH */
+ edit->efEditAt = LastTFPos;
+#ifdef ENHANCED_LINEEDIT
+ if (edit->efEditAt == 0)
+ /* Do not show the region. */
+ edit->efEditMark = -(int) (1 + edit->efBufInUse);
+#endif
+ }
+ /* Try to prepare for setting position based on the last mouse event */
+#if defined(TEXTFIELDS_MAY_NEED_ACTIVATION) && defined(INACTIVE_INPUT_STYLE_VH)
+ if (!redraw_only) {
+ if (peek_mouse_levent()) {
+ if (!use_last_tfpos && !textinput_redrawn) {
+ edit->efEditAt = 0;
+ }
+ }
+ textinput_redrawn = FALSE;
+ }
+#else
+ if (peek_mouse_levent()) {
+ if (!use_last_tfpos)
+ edit->efEditAt = 0;
+ }
+#endif /* TEXTFIELDS_MAY_NEED_ACTIVATION && INACTIVE_INPUT_STYLE_VH */
+ LYRefreshEdit(edit);
+ if (redraw_only) {
+ LYFinishEdit(edit);
+ return 0; /*return value won't be analysed */
+ }
+#ifdef FEPCTRL
+ fep_on();
+#endif
+ /*
+ * And go for it!
+ */
+ for (;;) {
+ again:
+ repeat = -1;
+ get_mouse_link(); /* Reset mouse_link. */
+
+ ch = LYgetch_input();
+#ifdef SUPPORT_MULTIBYTE_EDIT
+ if (!refresh_mb
+ && (EditBinding(ch) != LYE_CHAR)
+#ifndef WIN_EX
+ && (EditBinding(ch) != LYE_AIX)
+#endif
+ )
+ goto again;
+#endif /* SUPPORT_MULTIBYTE_EDIT */
+#ifdef VMS
+ if (HadVMSInterrupt) {
+ HadVMSInterrupt = FALSE;
+ ch = LYCharINTERRUPT2;
+ }
+#endif /* VMS */
+
+ action = 0;
+#ifdef USE_MOUSE
+# if defined(NCURSES) || defined(PDCURSES)
+ if (ch != -1 && (ch & LKC_ISLAC) && !(ch & LKC_ISLECLAC)) /* already lynxactioncode? */
+ break; /* @@@ maybe move these 2 lines outside ifdef -kw */
+ if (ch == MOUSE_KEY) { /* Need to process ourselves */
+#if defined(PDCURSES)
+ int curx, cury;
+
+ request_mouse_pos();
+ LYGetYX(cury, curx);
+ if (MOUSE_Y_POS == cury) {
+ repeat = MOUSE_X_POS - curx;
+ if (repeat < 0) {
+ action = LYE_BACK;
+ repeat = -repeat;
+ } else
+ action = LYE_FORW;
+ }
+#else
+ MEVENT event;
+ int curx, cury;
+
+ getmouse(&event);
+ LYGetYX(cury, curx);
+ if (event.y == cury) {
+ repeat = event.x - curx;
+ if (repeat < 0) {
+ action = LYE_BACK;
+ repeat = -repeat;
+ } else
+ action = LYE_FORW;
+ }
+#endif /* PDCURSES */
+ else {
+ /* Mouse event passed to us as MOUSE_KEY, and apparently not on
+ * this field's line? Something is not as it should be...
+ *
+ * A call to statusline() may have happened, possibly from
+ * within a mouse menu. Let's at least make sure here that the
+ * cursor position gets restored. - kw
+ */
+ edit->efIsDirty = TRUE;
+ }
+ } else
+# endif /* NCURSES || PDCURSES */
+#endif /* USE_MOUSE */
+
+ {
+ if (!(ch & LKC_ISLECLAC))
+ ch |= edit->efInputMods;
+ edit->efInputMods = 0;
+ if (last_xlkc != -1) {
+ if (ch == last_xlkc)
+ ch |= LKC_MOD3;
+ }
+ }
+ if (peek_mouse_link() != -1)
+ break;
+
+ if (!action)
+ action = EditBinding(ch);
+ if ((action & LYE_DF) && !(action & LYE_FORM_LAC)) {
+ last_xlkc = ch;
+ action &= ~LYE_DF;
+ } else {
+ last_xlkc = -1;
+ }
+
+ if (action == LYE_SETM1) {
+ /*
+ * Set flag for modifier 1.
+ */
+ edit->efInputMods |= LKC_MOD1;
+ continue;
+ }
+ if (action == LYE_SETM2) {
+ /*
+ * Set flag for modifier 2.
+ */
+ edit->efInputMods |= LKC_MOD2;
+ continue;
+ }
+ /*
+ * Filter out global navigation keys that should not be passed to line
+ * editor, and LYK_REFRESH.
+ */
+ if (action == LYE_ENTER)
+ break;
+ if (action == LYE_FORM_PASS)
+ break;
+ if (action & LYE_FORM_LAC) {
+ ch = (action & LAC_MASK) | LKC_ISLAC;
+ break;
+ }
+ if (action == LYE_LKCMD) {
+ _statusline(ENTER_LYNX_COMMAND);
+ ch = LYgetch();
+#ifdef VMS
+ if (HadVMSInterrupt) {
+ HadVMSInterrupt = FALSE;
+ ch = LYCharINTERRUPT2;
+ }
+#endif /* VMS */
+ break;
+ }
+#ifdef CAN_CUT_AND_PASTE /* 1998/10/01 (Thu) 19:19:22 */
+ if (action == LYE_PASTE) {
+ unsigned char *s = (unsigned char *) get_clip_grab(), *e;
+ char *buf = NULL;
+ int len;
+
+ if (!s)
+ break;
+ len = (int) strlen((const char *) s);
+ e = s + len;
+
+ if (len > 0) {
+ unsigned char *e1 = s;
+
+ while (e1 < e) {
+ if (*e1 < ' ') { /* Stop here? */
+ if (e1 > s)
+ LYEditInsert(edit, s, (int) (e1 - s), -1, TRUE);
+ s = e1;
+ if (*e1 == '\t') { /* Replace by space */
+ LYEditInsert(edit, (unsigned const char *) " ", 1,
+ -1, TRUE);
+ s = ++e1;
+ } else
+ break;
+ } else
+ ++e1;
+ }
+ if (e1 > s)
+ LYEditInsert(edit, s, (int) (e1 - s), -1, TRUE);
+ while (e1 < e && *e1 == '\r')
+ e1++;
+ if (e1 + 1 < e && *e1 == '\n')
+ StrAllocCopy(buf, (char *) e1 + 1); /* Survive _release() */
+ get_clip_release();
+ _statusline(ENTER_TEXT_ARROWS_OR_TAB);
+ if (strcmp(link_value, edit->efBuffer) != 0) {
+ Edited = TRUE;
+ }
+ if (buf) {
+ put_clip(buf);
+ FREE(buf);
+ ch = '\n'; /* Sometimes moves to the next line */
+ break;
+ }
+ LYRefreshEdit(edit);
+ } else {
+ HTInfoMsg(gettext("Clipboard empty or Not text data."));
+#ifdef FEPCTRL
+ fep_off();
+#endif
+ continue;
+ }
+ }
+#endif
+#ifndef WIN_EX
+ if (action == LYE_AIX &&
+ (!IS_CJK_TTY && LYlowest_eightbit[current_char_set] > 0x97))
+ break;
+#endif
+ if (action == LYE_TAB) {
+ ch = (int) ('\t');
+ break;
+ }
+ if (action == LYE_ABORT) {
+#ifdef FEPCTRL
+ fep_off();
+#endif
+ LYFinishEdit(edit);
+ return (DO_NOTHING);
+ }
+ if (action == LYE_STOP) {
+#ifdef TEXTFIELDS_MAY_NEED_ACTIVATION
+ textfields_need_activation = TRUE;
+ break;
+#else
+#ifdef ENHANCED_LINEEDIT
+ if (edit->efEditMark >= 0)
+ /* Disable. */
+ edit->efEditMark = -(int) (1 + edit->efBufInUse);
+#endif
+#endif
+ }
+ if (action == LYE_NOP && LKC_TO_LAC(keymap, ch) == LYK_REFRESH)
+ break;
+#ifdef SH_EX
+/* ASATAKU emacskey hack 1997/08/26 (Tue) 09:19:23 */
+ if (emacs_keys &&
+ (EditBinding(ch) == LYE_FORWW || EditBinding(ch) == LYE_BACKW))
+ goto breakfor;
+/* ASATAKU emacskey hack */
+#endif
+ switch (ch) {
+ default:
+ /* [ 1999/04/14 (Wed) 15:01:33 ]
+ * Left arrow in column 0 deserves special treatment here, else
+ * you can get trapped in a form without submit button!
+ */
+ if (action == LYE_BACK && edit->efEditAt == 0 && repeat == -1) {
+ int c = YES; /* Go back immediately if no changes */
+
+ if (textfield_prompt_at_left_edge) {
+ c = HTConfirmDefault(PREV_DOC_QUERY, NO);
+ } else if (strcmp(edit->efBuffer, link_value)) {
+ c = HTConfirmDefault(PREV_DOC_QUERY, NO);
+ }
+ if (c == YES) {
+#ifdef FEPCTRL
+ fep_off();
+#endif
+ LYFinishEdit(edit);
+ return (ch);
+ } else {
+ if (FormIsReadonly(form))
+ _statusline(ARROWS_OR_TAB_TO_MOVE);
+ else
+ _statusline(ENTER_TEXT_ARROWS_OR_TAB);
+ }
+ }
+ if (FormIsReadonly(form)) {
+ /*
+ * Allow actions that don't modify the contents even in
+ * disabled form fields, so the user can scroll through the
+ * line for reading if necessary. - kw
+ */
+ switch (action) {
+ case LYE_BOL:
+ case LYE_EOL:
+ case LYE_FORW:
+ case LYE_FORW_RL:
+ case LYE_BACK:
+ case LYE_BACK_LL:
+ case LYE_FORWW:
+ case LYE_BACKW:
+#ifdef EXP_KEYBOARD_LAYOUT
+ case LYE_SWMAP:
+#endif
+#ifdef ENHANCED_LINEEDIT
+ case LYE_SETMARK:
+ case LYE_XPMARK:
+#endif
+ break;
+ default:
+ goto again;
+ }
+ }
+ /*
+ * Make sure the statusline uses editmode help.
+ */
+ if (repeat < 0)
+ repeat = 1;
+ while (repeat--) {
+ int rc = LYDoEdit(edit, ch, action & ~LYE_DF, TRUE);
+
+ if (rc < 0) {
+ ch = -rc;
+ /* FORW_RL and BACK_LL may require special attention.
+ BACK_LL wanted to switch to the previous link on
+ the same line. However, if there is no such link,
+ then we would either disactivate the form
+ (with -tna), or will reenter the form, thus we jump
+ to the end of the line; both are counterintuitive.
+ Unfortunately, we do not have access to curdoc.link,
+ so we deduce it ourselves. We don't have the info
+ to do it inside LYLineEdit().
+ This should work for prompts too. */
+ switch (action) {
+ case LYE_BACK_LL:
+ if (cur > 0
+ && links[cur - 1].ly == links[cur].ly) {
+ goto breakfor;
+ }
+ break;
+ case LYE_FORW_RL:
+ if (cur >= 0
+ && cur < nlinks - 1
+ && links[cur + 1].ly == links[cur].ly) {
+ goto breakfor;
+ }
+ break;
+ default:
+ goto breakfor;
+ }
+ }
+#ifdef SUPPORT_MULTIBYTE_EDIT
+ if (rc == 0) {
+ if (IS_CJK_TTY && (0x80 <= ch)
+ && (ch <= 0xfe) && refresh_mb)
+ refresh_mb = FALSE;
+ else
+ refresh_mb = TRUE;
+ } else {
+ if (!refresh_mb) {
+ LYDoEdit(edit, 0, LYE_DELP, TRUE);
+ }
+ }
+#endif /* SUPPORT_MULTIBYTE_EDIT */
+ }
+ _statusline(ENTER_TEXT_ARROWS_OR_TAB);
+ if (strcmp(link_value, edit->efBuffer)) {
+ Edited = TRUE;
+ }
+#ifdef SUPPORT_MULTIBYTE_EDIT
+ if (refresh_mb)
+#endif
+ LYRefreshEdit(edit);
+ LYSetLastTFPos(edit->efEditAt);
+ }
+ }
+ breakfor:
+ if (Edited) {
+
+ /*
+ * Load the new value.
+ */
+ if (link_value == form->value) {
+ /*
+ * The previous value did fit in the line buffer, so replace it
+ * with the new value. - FM
+ */
+ StrAllocCopy(form->value, edit->efBuffer);
+ } else {
+ int old_len = (int) strlen(form->value);
+ int new_len = (int) strlen(link_value);
+
+ /*
+ * Combine the modified tail with the unmodified head. - FM
+ */
+ form->value[(old_len > new_len) ? (old_len - new_len) : 0] = '\0';
+ StrAllocCat(form->value, edit->efBuffer);
+ HTUserMsg(FORM_TAIL_COMBINED_WITH_HEAD);
+ }
+
+ /* 2.8.4pre.3 - most browsers appear to preserve trailing spaces -VH */
+ /*
+ * Remove trailing spaces
+ *
+ * Do we really need to do that here? Trailing spaces will only be
+ * there if user keyed them in. Rather rude to throw away their hard
+ * earned spaces. Better deal with trailing spaces when submitting the
+ * form????
+ */
+ if (LYtrimInputFields) {
+ LYTrimTrailing(form->value);
+ }
+
+ /*
+ * If the field has been changed, assume that it is now in current
+ * display character set, even if for some reason it wasn't! Hopefully
+ * a user will only submit the form if the non-ASCII characters are
+ * displayed correctly, which means (assuming that the display
+ * character set has been set truthfully) the user confirms by changing
+ * the field that the character encoding is right. - kw
+ */
+ if (non_empty(form->value))
+ form->value_cs = current_char_set;
+ }
+#ifdef FEPCTRL
+ fep_off();
+#endif
+ LYFinishEdit(edit);
+ return (ch);
+}
+
+#ifdef TEXTFIELDS_MAY_NEED_ACTIVATION
+#define TMA_PANEL(fp,np) ((for_what == FOR_PANEL) ? fp : np)
+#else
+#define TMA_PANEL(fp,np) np
+#endif
+
+/*
+ * Display statusline info tailored for the current form field.
+ */
+void show_formlink_statusline(const FormInfo * form,
+ int for_what)
+{
+ switch (form->type) {
+ case F_PASSWORD_TYPE:
+ if (FormIsReadonly(form))
+ statusline(FORM_LINK_PASSWORD_UNM_MSG);
+ else
+ statusline(TMA_PANEL(FORM_LINK_PASSWORD_MESSAGE_INA,
+ FORM_LINK_PASSWORD_MESSAGE));
+ break;
+ case F_OPTION_LIST_TYPE:
+ if (FormIsReadonly(form)) {
+ statusline(FORM_LINK_OPTION_LIST_UNM_MSG);
+ } else if (fields_are_named()) {
+ char *submit_str = NULL;
+
+ HTSprintf0(&submit_str, FORM_LINK_OPTION_LIST_ADV_MSG, form->name);
+ statusline(submit_str);
+ FREE(submit_str);
+ } else {
+ statusline(FORM_LINK_OPTION_LIST_MESSAGE);
+ }
+ break;
+ case F_CHECKBOX_TYPE:
+ if (FormIsReadonly(form)) {
+ statusline(FORM_LINK_CHECKBOX_UNM_MSG);
+ } else if (fields_are_named()) {
+ char *submit_str = NULL;
+
+ HTSprintf0(&submit_str, FORM_LINK_CHECKBOX_ADV_MSG, form->name);
+ statusline(submit_str);
+ FREE(submit_str);
+ } else {
+ statusline(FORM_LINK_CHECKBOX_MESSAGE);
+ }
+ break;
+ case F_RADIO_TYPE:
+ if (FormIsReadonly(form)) {
+ statusline(FORM_LINK_RADIO_UNM_MSG);
+ } else if (fields_are_named()) {
+ char *submit_str = NULL;
+
+ HTSprintf0(&submit_str, FORM_LINK_RADIO_ADV_MSG, form->name);
+ statusline(submit_str);
+ FREE(submit_str);
+ } else {
+ statusline(FORM_LINK_RADIO_MESSAGE);
+ }
+ break;
+ case F_TEXT_SUBMIT_TYPE:
+ if (FormIsReadonly(form)) {
+ statusline(FORM_LINK_TEXT_SUBMIT_UNM_MSG);
+ } else if (form->submit_method ==
+ URL_MAIL_METHOD) {
+ if (no_mail)
+ statusline(FORM_LINK_TEXT_SUBMIT_MAILTO_DIS_MSG);
+ else
+ statusline(TMA_PANEL(FORM_TEXT_SUBMIT_MAILTO_MSG_INA,
+ FORM_LINK_TEXT_SUBMIT_MAILTO_MSG));
+ } else if (form->no_cache) {
+ statusline(TMA_PANEL(FORM_TEXT_RESUBMIT_MESSAGE_INA,
+ FORM_LINK_TEXT_RESUBMIT_MESSAGE));
+ } else {
+ char *submit_str = NULL;
+ char *xkey_info = key_for_func_ext(LYK_NOCACHE, for_what);
+
+ if (non_empty(xkey_info)) {
+ HTSprintf0(&submit_str,
+ TMA_PANEL(FORM_TEXT_SUBMIT_MESSAGE_INA_X,
+ FORM_LINK_TEXT_SUBMIT_MESSAGE_X),
+ xkey_info);
+ statusline(submit_str);
+ FREE(submit_str);
+ } else {
+ statusline(TMA_PANEL(FORM_LINK_TEXT_SUBMIT_MESSAGE_INA,
+ FORM_LINK_TEXT_SUBMIT_MESSAGE));
+ }
+ FREE(xkey_info);
+ }
+ break;
+ case F_SUBMIT_TYPE:
+ case F_IMAGE_SUBMIT_TYPE:
+ if (FormIsReadonly(form)) {
+ statusline(FORM_LINK_SUBMIT_DIS_MSG);
+ } else if (form->submit_method ==
+ URL_MAIL_METHOD) {
+ if (no_mail) {
+ statusline(FORM_LINK_SUBMIT_MAILTO_DIS_MSG);
+ } else {
+ if (user_mode == MINIMAL_MODE) {
+ statusline("");
+ } else if (user_mode == ADVANCED_MODE) {
+ char *submit_str = NULL;
+
+ StrAllocCopy(submit_str, FORM_LINK_SUBMIT_MAILTO_PREFIX);
+ StrAllocCat(submit_str, form->submit_action);
+ statusline(submit_str);
+ FREE(submit_str);
+ } else {
+ statusline(FORM_LINK_SUBMIT_MAILTO_MSG);
+ }
+ }
+ } else if (form->no_cache) {
+ if (user_mode == MINIMAL_MODE) {
+ statusline("");
+ } else if (user_mode == ADVANCED_MODE) {
+ char *submit_str = NULL;
+
+ StrAllocCopy(submit_str, FORM_LINK_RESUBMIT_PREFIX);
+ StrAllocCat(submit_str, form->submit_action);
+ statusline(submit_str);
+ FREE(submit_str);
+ } else {
+ statusline(FORM_LINK_RESUBMIT_MESSAGE);
+ }
+ } else {
+ if (user_mode == MINIMAL_MODE) {
+ statusline("");
+ } else if (user_mode == ADVANCED_MODE) {
+ char *submit_str = NULL;
+
+ StrAllocCopy(submit_str, FORM_LINK_SUBMIT_PREFIX);
+ StrAllocCat(submit_str, form->submit_action);
+ statusline(submit_str);
+ FREE(submit_str);
+ } else {
+ statusline(FORM_LINK_SUBMIT_MESSAGE);
+ }
+ }
+ break;
+ case F_RESET_TYPE:
+ if (FormIsReadonly(form))
+ statusline(FORM_LINK_RESET_DIS_MSG);
+ else
+ statusline(FORM_LINK_RESET_MESSAGE);
+ break;
+ case F_BUTTON_TYPE:
+ if (FormIsReadonly(form)) {
+ statusline(FORM_LINK_BUTTON_DIS_MSG);
+ } else if (fields_are_named()) {
+ char *submit_str = NULL;
+
+ HTSprintf0(&submit_str, FORM_LINK_BUTTON_ADV_MSG, form->name);
+ statusline(submit_str);
+ FREE(submit_str);
+ } else {
+ statusline(FORM_LINK_BUTTON_MESSAGE);
+ }
+ break;
+ case F_FILE_TYPE:
+ if (FormIsReadonly(form))
+ statusline(FORM_LINK_FILE_UNM_MSG);
+ else
+ statusline(FORM_LINK_FILE_MESSAGE);
+ break;
+ case F_TEXT_TYPE:
+ if (FormIsReadonly(form)) {
+ statusline(FORM_LINK_TEXT_UNM_MSG);
+ } else if (fields_are_named()) {
+ char *submit_str = NULL;
+
+ HTSprintf0(&submit_str,
+ TMA_PANEL(FORM_LINK_TEXT_ADV_MSG_INA,
+ FORM_LINK_TEXT_ADV_MSG),
+ form->name);
+ statusline(submit_str);
+ FREE(submit_str);
+ } else {
+ statusline(TMA_PANEL(FORM_LINK_TEXT_MESSAGE_INA,
+ FORM_LINK_TEXT_MESSAGE));
+ }
+ break;
+ case F_TEXTAREA_TYPE:
+ if (FormIsReadonly(form)) {
+ statusline(FORM_LINK_TEXT_UNM_MSG);
+ } else {
+ char *submit_str = NULL;
+ char *xkey_info = NULL;
+
+ if (!no_editor && non_empty(editor)) {
+ xkey_info = key_for_func_ext(LYK_EDITTEXTAREA, for_what);
+#ifdef TEXTAREA_AUTOEXTEDIT
+ if (!xkey_info)
+ xkey_info = key_for_func_ext(LYK_DWIMEDIT, for_what);
+#endif
+ }
+ if (non_empty(xkey_info)) {
+ if (fields_are_named()) {
+ HTSprintf0(&submit_str,
+ TMA_PANEL(FORM_LINK_TEXTAREA_ADV_MSG_INA_E,
+ FORM_LINK_TEXTAREA_ADV_MSG_E),
+ form->name,
+ xkey_info);
+ } else {
+ HTSprintf0(&submit_str,
+ TMA_PANEL(FORM_LINK_TEXTAREA_MESSAGE_INA_E,
+ FORM_LINK_TEXTAREA_MESSAGE_E),
+ xkey_info);
+ }
+ statusline(submit_str);
+ FREE(submit_str);
+ } else {
+ if (fields_are_named()) {
+ HTSprintf0(&submit_str,
+ TMA_PANEL(FORM_LINK_TEXTAREA_ADV_MSG_INA,
+ FORM_LINK_TEXTAREA_ADV_MSG),
+ form->name);
+ statusline(submit_str);
+ FREE(submit_str);
+ } else {
+ statusline(TMA_PANEL(FORM_LINK_TEXTAREA_MESSAGE_INA,
+ FORM_LINK_TEXTAREA_MESSAGE));
+ }
+ }
+ FREE(xkey_info);
+ }
+ break;
+ }
+}
diff --git a/src/LYGCurses.h b/src/LYGCurses.h
new file mode 100644
index 0000000..ce5303c
--- /dev/null
+++ b/src/LYGCurses.h
@@ -0,0 +1,246 @@
+#ifndef __CURSES_LOADED
+#define __CURSES_LOADED 1
+
+#include <ssdef.h>
+#include <stdio.h>
+#include <smgdef.h>
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+#define reg register
+#ifndef TRUE
+#define TRUE (1)
+#define FALSE (0)
+#endif
+#define ERR (0)
+#define OK (1)
+#define _SUBWIN 0001
+#define _ENDLINE 0002
+#define _FULLWIN 0004
+#define _SCROLLWIN 0010
+#define _FLUSH 0020
+#define _STANDOUT 0200
+#define _NOECHO 001
+#define _NONL 002
+#define _NOCRMODE 004
+#define _NORAW 010
+#define _BLINK SMG$M_BLINK
+#define _BOLD SMG$M_BOLD
+#define _REVERSE SMG$M_REVERSE
+#define _UNDERLINE SMG$M_UNDERLINE
+ struct _win_st {
+ int _cur_y, _cur_x;
+ int _max_y, _max_x;
+ int _beg_y, _beg_x;
+ short _flags;
+ char _clear, _leave, _scroll, _wrap;
+ char **_y;
+ short *_firstch, *_lastch;
+ struct _win_st *_next, *_parent, *_child;
+ int _id;
+ };
+
+ struct _kb_st {
+ int _id;
+ unsigned char _flags;
+ struct {
+ unsigned short length;
+ unsigned char type;
+ unsigned char pclass;
+ char *address;
+ } _buffer_desc;
+ int _count;
+ char *_ptr;
+ };
+
+ struct _pb_st {
+ int _id;
+ int _rows, _cols;
+ union SMGDEF *_attr;
+ int _attr_size;
+ };
+
+#define _KEYBOARD struct _kb_st
+#define WINDOW struct _win_st
+#define _PASTEBOARD struct _pb_st
+
+ extern int LINES __asm("_$$PsectAttributes_NOSHR$$LINES");
+ extern int COLS __asm("_$$PsectAttributes_NOSHR$$COLS");
+ extern WINDOW *stdscr __asm("_$$PsectAttributes_NOSHR$$stdscr");
+ extern WINDOW *curscr __asm("_$$PsectAttributes_NOSHR$$curscr");
+ extern _KEYBOARD *stdkb __asm("_$$PsectAttributes_NOSHR$$stdkb");
+ extern _PASTEBOARD *stdpb __asm("_$$PsectAttributes_NOSHR$$stdpb");
+
+#define getch() wgetch (stdscr)
+#define addch(ch) waddch (stdscr, ch)
+#define addstr(string) waddstr (stdscr, string)
+#define move(y, x) wmove (stdscr, y, x)
+#define refresh() wrefresh (stdscr)
+#define clear() wclear (stdscr)
+#define clrtobot() wclrtobot (stdscr)
+#define clrtoeol() wclrtoeol (stdscr)
+#define delch() wdelch (stdscr)
+#define erase() werase (stdscr)
+#define insch(ch) winsch (stdscr, ch)
+#define insertln() winsertln (stdscr)
+#define standout() wstandout (stdscr)
+#define standend() wstandend (stdscr)
+#define getstr(string) wgetstr (stdscr, string)
+#define inch() winch (stdscr)
+#define setattr(attr) wsetattr (stdscr, attr)
+#define clrattr(attr) wclrattr (stdscr, attr)
+#define deleteln() wdeleteln (stdscr)
+#define insstr(string) winsstr (stdscr, string)
+
+#define mvwaddch(win,y,x,ch) (wmove(win,y,x)==ERR)?ERR:waddch(win,ch)
+#define mvwgetch(win,y,x) (wmove(win,y,x)==ERR)?ERR:wgetch(win)
+#define mvwaddstr(win,y,x,str) (wmove(win,y,x)==ERR)?ERR:waddstr(win,str)
+#define mvwinsstr(win,y,x,str) (wmove(win,y,x)==ERR)?ERR:winsstr(win,str)
+#define mvwgetstr(win,y,x,str) (wmove(win,y,x)==ERR)?ERR:wgetstr(win,str)
+#define mvwinch(win,y,x) (wmove(win,y,x)==ERR)?ERR:winch(win)
+#define mvwdelch(win,y,x) (wmove(win,y,x)==ERR)?ERR:wdelch(win)
+#define mvwinsch(win,y,x,ch) (wmove(win,y,x)==ERR)?ERR:winsch(win,ch)
+#define mvwdeleteln(win,y,x) (wmove(win,y,x)==ERR)?ERR:wdeleteln(win)
+#define mvaddch(y,x,ch) mvwaddch (stdscr, y, x, ch)
+#define mvgetch(y,x) mvwgetch (stdscr, y, x)
+#define mvaddstr(y,x,str) mvwaddstr (stdscr, y, x, str)
+#define mvinsstr(y,x,str) mvwinsstr (stdscr, y, x, str)
+#define mvgetstr(y,x,str) mvwgetstr (stdscr, y, x, str)
+#define mvinch(y,x) mvwinch (stdscr, y, x)
+#define mvdelch(y,x) mvwdelch (stdscr, y, x)
+#define mvinsch(y,x,ch) mvwinsch (stdscr, y, x, ch)
+#define mvdeleteln(y,x) mvwdeleteln (stdscr, y, x)
+#define mvcur(ly,lx,ny,nx) wmove (stdscr, ny, nx)
+#pragma standard
+
+#define clearok(win, bf) (win->_clear = bf)
+#define leaveok(win, bf) (win->_leave = bf)
+#define scrollok(win, bf) (win->_scroll = bf)
+#define wrapok(win, bf) (win->_wrap = bf)
+#define flushok(win,bf) (bf ? win->_flags |= _FLUSH : (win->_flags &= ~_FLUSH))
+#define getyx(win,y,x) y = win->_cur_y, x = win->_cur_x
+
+#define echo() (stdkb->_flags &= ~_NOECHO)
+#define noecho() (stdkb->_flags |= _NOECHO)
+#define nl() (stdkb->_flags &= ~_NONL)
+#define nonl() (stdkb->_flags |= _NONL)
+#define crmode() ((stdkb->_flags &= ~_NOCRMODE), nonl ())
+#define nocrmode() (stdkb->_flags |= _NOCRMODE)
+#define raw() (stdkb->_flags &= ~_NORAW)
+#define noraw() (stdkb->_flags |= _NORAW)
+
+#define check(status) if (!(status & SS$_NORMAL)) \
+ { c$$translate (status); \
+ return ERR; \
+ }
+
+#define bool int
+
+ int waddch(WINDOW * win, char ch);
+
+ int waddstr(WINDOW * win, char *str);
+
+ int box(WINDOW * win, char vert, char hor);
+
+ int wclear(WINDOW * win);
+
+ int wclrattr(WINDOW * win, int attr);
+
+ int wclrtobot(WINDOW * win);
+
+ int wclrtoeol(WINDOW * win);
+
+ int wdelch(WINDOW * win);
+
+ int wdeleteln(WINDOW * win);
+
+ int delwin(WINDOW * win);
+
+ int endwin(void);
+
+ int werase(WINDOW * win);
+
+ int wgetch(WINDOW * win);
+
+ int wgetstr(WINDOW * win, char *str);
+
+ char winch(WINDOW * win);
+
+ WINDOW *initscr(void);
+
+ int winsch(WINDOW * win, char ch);
+
+ int winsertln(WINDOW * win);
+
+ int winsstr(WINDOW * win, char *str);
+
+ int longname(char *termbuf, char *name);
+
+ int mvwin(WINDOW * win, int st_row, int st_col);
+
+ int wmove(WINDOW * win, int y, int x);
+
+ WINDOW *newwin(int numlines, int numcols, int begin_y, int begin_x);
+
+ int overlay(WINDOW * win1, WINDOW * win2);
+
+ int overwrite(WINDOW * win1, WINDOW * win2);
+
+#pragma NOSTANDARD
+#undef printw
+#undef wprintw
+#undef wscanw
+#undef scanw
+#pragma STANDARD
+
+ int printw(char *format_spec, ...);
+
+ int wprintw(WINDOW * win, char *format_spec, ...);
+
+ int wrefresh(WINDOW * win);
+
+ int wscanw(WINDOW * win, char *format_spec, ...);
+
+ int scanw(char *fmt, int arg1);
+
+ int scroll(WINDOW * win);
+
+ int wsetattr(WINDOW * win, int attr);
+
+ WINDOW *subwin(WINDOW * win, int numlines, int numcols,
+ int begin_y, int begin_x);
+
+ int wstandend(WINDOW * win);
+
+ int wstandout(WINDOW * win);
+
+ int touchwin(WINDOW * win);
+
+#if defined(CC$mixed_float) || defined(CC$VAXCSHR)
+
+#ifndef CC$gfloat
+#define CC$gfloat 0
+#endif
+
+#if CC$gfloat
+
+#define printw vaxc$gprintw
+#define scanw vaxc$gscanw
+#define wprintw vaxc$gwprintw
+#define wscanw vaxc$gwscanw
+
+#else
+
+#define printw vaxc$dprintw
+#define scanw vaxc$dscanw
+#define wprintw vaxc$dwprintw
+#define wscanw vaxc$dwscanw
+
+#endif
+#endif
+
+#ifdef __cplusplus
+}
+#endif
+#endif /* __CURSES_LOADED */
diff --git a/src/LYGetFile.c b/src/LYGetFile.c
new file mode 100644
index 0000000..57e7f30
--- /dev/null
+++ b/src/LYGetFile.c
@@ -0,0 +1,1581 @@
+/* $LynxId: LYGetFile.c,v 1.97 2024/01/14 20:02:21 Viatrix Exp $ */
+#include <HTUtils.h>
+#include <HTTP.h>
+#include <HTAnchor.h> /* Anchor class */
+#include <HTAccess.h>
+#include <HTParse.h>
+#include <LYCurses.h>
+#include <GridText.h>
+#include <LYGlobalDefs.h>
+#include <LYUtils.h>
+#include <LYCharSets.h>
+#include <LYCharUtils.h>
+#include <HTAlert.h>
+#include <LYSignal.h>
+#include <LYGetFile.h>
+#include <LYPrint.h>
+#include <LYOptions.h>
+#include <LYStrings.h>
+#include <LYClean.h>
+#include <LYDownload.h>
+#include <LYNews.h>
+#include <LYMail.h>
+#include <LYKeymap.h>
+#include <LYBookmark.h>
+#include <LYMap.h>
+#include <LYList.h>
+#ifdef DIRED_SUPPORT
+#include <LYLocal.h>
+#endif /* DIRED_SUPPORT */
+#include <LYReadCFG.h>
+#include <LYHistory.h>
+#include <LYPrettySrc.h>
+
+#include <LYexit.h>
+#include <LYLeaks.h>
+
+static int fix_httplike_urls(DocInfo *doc, UrlTypes type);
+
+#ifdef VMS
+#define STRNADDRCOMP strncasecomp
+#else
+#define STRNADDRCOMP strncmp
+#endif /* !VMS */
+
+int HTNoDataOK = 0;
+
+/*
+ * getfile is the main mechanism to load a new document (or a previously loaded
+ * one whose rendering is cached in a HText structure) from mainloop, nearly
+ * everything goes through it.
+ * It should return one of the values
+ * NORMAL - requested document loaded successfully, usually [always?]
+ * its rendering is available as HTMainText. It can be an
+ * HTTP error message page or similar, we make no
+ * distinction here.
+ * NOT_FOUND - requested document cannot be accessed, and the reason
+ * is a real error (as may be caused by an invalid link),
+ * not just that lynx disallows access because of some
+ * permission restrictions, and we have no error page
+ * to show for it either.
+ * NULLFILE - requested document not loaded into HTMainText, either
+ * some interactive protocol was requested (like telnet),
+ * or lynx does not allow access.
+ * The distinction between NOT_FOUND and NULLFILE is not very crucial, but
+ * getting it right prevents mainloop from exiting with the wrong message if it
+ * happens for the first file, and from logging (or not logging) errors
+ * inappropriately with -traversal, and from sending bogus error mail with
+ * MAIL_SYSTEM_ERROR_LOGGING:TRUE. - kw
+ */
+int getfile(DocInfo *doc, int *target)
+{
+ UrlTypes url_type = NOT_A_URL_TYPE;
+ char *pound;
+ char *cp = NULL;
+ char *temp = NULL;
+ DocAddress WWWDoc; /* a WWW absolute doc address struct */
+
+ /*
+ * Reset LYCancelDownload to prevent unwanted delayed effect. - KW
+ */
+ if (LYCancelDownload) {
+ CTRACE((tfp, "getfile: resetting LYCancelDownload to FALSE\n"));
+ LYCancelDownload = FALSE;
+ }
+
+ /*
+ * Reset fake 'Z' to prevent unwanted delayed effect. - kw
+ */
+ LYFakeZap(NO);
+
+ /*
+ * Reset redirection counter to prevent bogus TOO_MANY_REDIRECTIONS in rare
+ * situations if the previous cycle got to the limit, but did not fail for
+ * that reason because the URL of the final location was handled specially,
+ * not via HTLoadAbsolute. - kw
+ */
+ redirection_attempts = 0;
+
+ Try_Redirected_URL:
+ /*
+ * Load the WWWDoc struct in case we need to use it.
+ */
+ WWWDoc.address = doc->address;
+ WWWDoc.post_data = doc->post_data;
+ WWWDoc.post_content_type = doc->post_content_type;
+ WWWDoc.bookmark = doc->bookmark;
+ WWWDoc.isHEAD = doc->isHEAD;
+ WWWDoc.safe = doc->safe;
+
+ /*
+ * Reset HTPermitRedir, it has done its job if it was set. - kw
+ */
+ HTPermitRedir = FALSE;
+
+ /*
+ * Reset WWW_Download_File just in case.
+ */
+ FREE(WWW_Download_File);
+
+ /*
+ * Reset redirect_post_content just in case.
+ */
+ redirect_post_content = FALSE;
+
+ /*
+ * This flag is a hack to allow us to pass on the fact that 'no data' may
+ * not really be an error although HTLoadAbsolute returned NO. There
+ * should be a better way... HT_NO_DATA should always mean 'not data but
+ * not an error', and be passed on to us as that, but current usage if
+ * HT_NO_DATA vs HT_NOT_LOADED has to be reviewed everywhere. Anyway, some
+ * protocol module can set it to say 'I really mean it', we have to reset
+ * it here. - kw
+ */
+ HTNoDataOK = 0;
+
+ CTRACE((tfp, "getfile: getting %s\n\n", doc->address));
+
+ /*
+ * Protect against denial of service attacks via the port 19 CHARGEN
+ * service, and block connections to the port 25 ESMTP service. Also
+ * reject any likely spoof attempts via wrap arounds at 65536. - FM
+ */
+ if ((temp = HTParse(doc->address, "", PARSE_HOST)) != NULL &&
+ strlen(temp) > 3) {
+ char *cp1;
+
+ if ((cp1 = StrChr(temp, '@')) == NULL)
+ cp1 = temp;
+ if ((cp = strrchr(cp1, ':')) != NULL) {
+ long int value;
+
+ cp++;
+ if (sscanf(cp, "%ld", &value) == 1) {
+ if (value == 19 || value == 65555) {
+ HTAlert(PORT_NINETEEN_INVALID);
+ FREE(temp);
+ return (NULLFILE);
+ } else if (value == 25 || value == 65561) {
+ HTAlert(PORT_TWENTYFIVE_INVALID);
+ FREE(temp);
+ return (NULLFILE);
+ } else if (value > 65535 || value < 0) {
+ char *msg = 0;
+
+ HTSprintf0(&msg, PORT_INVALID, (unsigned long) value);
+ HTAlert(msg);
+ FREE(msg);
+ FREE(temp);
+ return (NULLFILE);
+ }
+ } else if (isdigit(UCH(*cp))) {
+ HTAlert(URL_PORT_BAD);
+ FREE(temp);
+ return (NULLFILE);
+ }
+ }
+ }
+ cp = NULL;
+ FREE(temp);
+
+ /*
+ * Check to see if this is a universal document ID that lib WWW wants to
+ * handle.
+ *
+ * Some special URL's we handle ourselves. :)
+ */
+ if ((url_type = is_url(doc->address)) != 0) {
+ if (LYValidate && !LYPermitURL) {
+ if (!(url_type == HTTP_URL_TYPE ||
+ url_type == HTTPS_URL_TYPE ||
+ url_type == LYNXHIST_URL_TYPE ||
+ url_type == LYNXEDITMAP_URL_TYPE ||
+ url_type == LYNXKEYMAP_URL_TYPE ||
+ url_type == LYNXIMGMAP_URL_TYPE ||
+ url_type == LYNXCOOKIE_URL_TYPE ||
+#ifdef USE_CACHEJAR
+ url_type == LYNXCACHE_URL_TYPE ||
+#endif
+ url_type == LYNXMESSAGES_URL_TYPE ||
+ (url_type == LYNXOPTIONS_URL_TYPE &&
+ WWWDoc.post_data) ||
+ (non_empty(helpfilepath) &&
+ 0 == STRNADDRCOMP(WWWDoc.address, helpfilepath,
+ strlen(helpfilepath))) ||
+ (non_empty(lynxlistfile) &&
+ 0 == STRNADDRCOMP(WWWDoc.address, lynxlistfile,
+ strlen(lynxlistfile))) ||
+ (non_empty(lynxlinksfile) &&
+ 0 == STRNADDRCOMP(WWWDoc.address, lynxlinksfile,
+ strlen(lynxlinksfile))) ||
+ (non_empty(lynxjumpfile) &&
+ 0 == STRNADDRCOMP(WWWDoc.address, lynxjumpfile,
+ strlen(lynxjumpfile))))) {
+ HTUserMsg(NOT_HTTP_URL_OR_ACTION);
+ return (NULLFILE);
+ }
+ }
+ if (traversal) {
+ /*
+ * Only traverse http URLs.
+ */
+ if (url_type != HTTP_URL_TYPE &&
+ url_type != LYNXIMGMAP_URL_TYPE) {
+ return (NULLFILE);
+ }
+ } else if (check_realm && !LYPermitURL && !LYJumpFileURL) {
+ if (!(0 == StrNCmp(startrealm, WWWDoc.address,
+ strlen(startrealm)) ||
+ url_type == LYNXHIST_URL_TYPE ||
+ url_type == LYNXEDITMAP_URL_TYPE ||
+ url_type == LYNXKEYMAP_URL_TYPE ||
+ url_type == LYNXIMGMAP_URL_TYPE ||
+ url_type == LYNXCOOKIE_URL_TYPE ||
+#ifdef USE_CACHEJAR
+ url_type == LYNXCACHE_URL_TYPE ||
+#endif
+ url_type == LYNXPRINT_URL_TYPE ||
+ url_type == LYNXOPTIONS_URL_TYPE ||
+ url_type == LYNXCFG_URL_TYPE ||
+ url_type == LYNXCOMPILE_OPTS_URL_TYPE ||
+ url_type == LYNXMESSAGES_URL_TYPE ||
+ url_type == LYNXDOWNLOAD_URL_TYPE ||
+ url_type == MAILTO_URL_TYPE ||
+ url_type == NEWSPOST_URL_TYPE ||
+ url_type == NEWSREPLY_URL_TYPE ||
+ url_type == SNEWSPOST_URL_TYPE ||
+ url_type == SNEWSREPLY_URL_TYPE ||
+ (!LYUserSpecifiedURL &&
+ (url_type == LYNXEXEC_URL_TYPE ||
+ url_type == LYNXPROG_URL_TYPE ||
+ url_type == LYNXCGI_URL_TYPE)) ||
+ (WWWDoc.bookmark != NULL &&
+ *WWWDoc.bookmark != '\0') ||
+ 0 == STRNADDRCOMP(WWWDoc.address, helpfilepath,
+ strlen(helpfilepath)) ||
+ (lynxlistfile != NULL &&
+ 0 == STRNADDRCOMP(WWWDoc.address, lynxlistfile,
+ strlen(lynxlistfile))) ||
+ (lynxjumpfile != NULL &&
+ 0 == STRNADDRCOMP(WWWDoc.address, lynxjumpfile,
+ strlen(lynxjumpfile))))) {
+ HTUserMsg(NOT_IN_STARTING_REALM);
+ return (NULLFILE);
+ }
+ }
+ if (WWWDoc.post_data &&
+ url_type != HTTP_URL_TYPE &&
+ url_type != HTTPS_URL_TYPE &&
+ url_type != LYNXCGI_URL_TYPE &&
+ url_type != LYNXIMGMAP_URL_TYPE &&
+ url_type != GOPHER_URL_TYPE &&
+ url_type != CSO_URL_TYPE &&
+ url_type != PROXY_URL_TYPE &&
+ url_type != LYNXOPTIONS_URL_TYPE &&
+ !(url_type == FILE_URL_TYPE &&
+ (LYIsUIPage(WWWDoc.address, UIP_LIST_PAGE) ||
+ LYIsUIPage(WWWDoc.address, UIP_ADDRLIST_PAGE)))) {
+ CTRACE((tfp, "getfile: dropping post_data!\n"));
+ HTAlert(IGNORED_POST);
+ LYFreePostData(doc);
+ WWWDoc.post_data = NULL;
+ WWWDoc.post_content_type = NULL;
+ }
+#ifdef SYSLOG_REQUESTED_URLS
+ LYSyslog(doc->address);
+#endif
+ if (url_type == UNKNOWN_URL_TYPE ||
+ url_type == AFS_URL_TYPE ||
+ url_type == PROSPERO_URL_TYPE) {
+ HTAlert(UNSUPPORTED_URL_SCHEME);
+ return (NULLFILE);
+
+ } else if (url_type == DATA_URL_TYPE) {
+ HTAlert(UNSUPPORTED_DATA_URL);
+ return (NULLFILE);
+
+ } else if (url_type == LYNXPRINT_URL_TYPE) {
+ return (printfile(doc));
+
+#ifndef NO_OPTION_FORMS
+ } else if (url_type == LYNXOPTIONS_URL_TYPE) {
+ /* proceed forms-based options menu */
+ return (postoptions(doc));
+#endif
+
+ } else if (url_type == LYNXCFG_URL_TYPE &&
+ !no_lynxcfg_info) {
+ /* @@@ maybe we should generate a specific error message
+ if attempted but restricted. - kw */
+ /* show/change/reload lynx.cfg settings */
+ return (lynx_cfg_infopage(doc));
+
+#if defined(HAVE_CONFIG_H) && !defined(NO_CONFIG_INFO)
+ } else if (url_type == LYNXCOMPILE_OPTS_URL_TYPE &&
+ !no_compileopts_info) {
+ /* @@@ maybe we should generate a specific error message
+ if attempted but restricted or not supported. - kw */
+ /* show compile-time settings */
+ return (lynx_compile_opts(doc));
+#endif
+
+#ifndef DISABLE_NEWS
+ } else if (url_type == NEWSPOST_URL_TYPE ||
+ url_type == NEWSREPLY_URL_TYPE ||
+ url_type == SNEWSPOST_URL_TYPE ||
+ url_type == SNEWSREPLY_URL_TYPE) {
+
+ if (no_newspost) {
+ HTUserMsg(NEWSPOSTING_DISABLED);
+ return (NULLFILE);
+ } else if (!news_ok && (url_type == NEWSPOST_URL_TYPE ||
+ url_type == NEWSREPLY_URL_TYPE)) {
+ HTUserMsg(NEWS_DISABLED);
+ return (NULLFILE);
+ } else {
+ HTLoadAbsolute(&WWWDoc);
+ return (NULLFILE);
+ }
+#endif
+
+ } else if (url_type == LYNXDOWNLOAD_URL_TYPE) {
+ LYDownload(doc->address);
+#ifdef VMS
+ if (LYDidRename) {
+ /*
+ * The temporary file was saved to disk via a rename(), so we
+ * can't access the temporary file again via the download menu.
+ * Clear the flag, and return NULLFILE to pop. - FM
+ */
+ LYDidRename = FALSE;
+ return (NULLFILE);
+ }
+#endif /* VMS */
+ return (NORMAL);
+ } else if (url_type == LYNXDIRED_URL_TYPE) {
+#ifdef DIRED_SUPPORT
+ if (!no_dired_support) {
+ local_dired(doc);
+ WWWDoc.address = doc->address;
+ WWWDoc.post_data = doc->post_data;
+ WWWDoc.post_content_type = doc->post_content_type;
+ WWWDoc.bookmark = doc->bookmark;
+ WWWDoc.isHEAD = doc->isHEAD;
+ WWWDoc.safe = doc->safe;
+
+ if (!HTLoadAbsolute(&WWWDoc)) {
+ return (NOT_FOUND);
+ }
+ return (NORMAL);
+ }
+#endif /* DIRED_SUPPORT */
+ HTUserMsg(DIRED_DISABLED);
+ return (NULLFILE);
+ }
+
+ if (LYNoRefererHeader == FALSE &&
+ LYNoRefererForThis == FALSE) {
+ const char *ref_url = HTLoadedDocumentURL();
+
+ if (isLYNXIMGMAP(ref_url))
+ ref_url += LEN_LYNXIMGMAP;
+ if (no_filereferer == TRUE && isFILE_URL(ref_url)) {
+ LYNoRefererForThis = TRUE;
+ }
+ if (LYNoRefererForThis == FALSE &&
+ (cp = StrChr(ref_url, '?')) != NULL &&
+ StrChr(cp, '=') != NULL) {
+ /*
+ * Don't send a Referer header if the URL is the reply from a
+ * form with method GET, in case the content has personal data
+ * (e.g., a password or credit card number) which would become
+ * visible in logs. - FM
+ *
+ * Changed 1999-11-01 to be controlled by REFERER_WITH_QUERY
+ * option. - kw
+ */
+ if (LYRefererWithQuery == 'S') { /* SEND */
+ StrAllocCopy(LYRequestReferer, ref_url);
+ } else if (LYRefererWithQuery == 'P') { /* PARTIAL */
+ FREE(LYRequestReferer); /* just to be sure */
+ LYRequestReferer = HTParse(ref_url, "",
+ PARSE_ACCESS
+ | PARSE_HOST
+ | PARSE_STRICTPATH
+ | PARSE_PUNCTUATION);
+ } else { /* Everything else - don't send Referer */
+ LYNoRefererForThis = TRUE;
+ }
+ cp = NULL;
+ } else if (LYNoRefererForThis == FALSE) {
+ StrAllocCopy(LYRequestReferer, ref_url);
+ }
+ } else {
+ StrAllocCopy(LYRequestReferer, HTLoadedDocumentURL());
+ }
+ if (url_type == LYNXHIST_URL_TYPE) {
+ /*
+ * 'doc' will change to the new file if we had a successful
+ * LYpop_num(), and the return value will be FALSE if we had a
+ * cancel. - FM
+ */
+ if ((historytarget(doc) == FALSE) ||
+ !doc || !doc->address) {
+ return (NOT_FOUND);
+ }
+
+ /*
+ * We changed it so reload.
+ */
+ WWWDoc.address = doc->address;
+ WWWDoc.post_data = doc->post_data;
+ WWWDoc.post_content_type = doc->post_content_type;
+ WWWDoc.bookmark = doc->bookmark;
+ WWWDoc.isHEAD = doc->isHEAD;
+ WWWDoc.safe = doc->safe;
+ if (track_internal_links && doc->internal_link && !reloading) {
+ LYinternal_flag = TRUE;
+ }
+#ifdef DIRED_SUPPORT
+ lynx_edit_mode = FALSE;
+#endif /* DIRED_SUPPORT */
+ if (!HTLoadAbsolute(&WWWDoc)) {
+ return (NOT_FOUND);
+ }
+ return (NORMAL);
+
+ } else if (url_type == LYNXEXEC_URL_TYPE ||
+ url_type == LYNXPROG_URL_TYPE) {
+#ifdef EXEC_LINKS
+ if (no_exec &&
+ !exec_ok(HTLoadedDocumentURL(),
+ doc->address + 9, ALWAYS_EXEC_PATH)) {
+ HTUserMsg(EXECUTION_DISABLED);
+ } else if (no_bookmark_exec &&
+ HTLoadedDocumentBookmark()) {
+ HTUserMsg(BOOKMARK_EXEC_DISABLED);
+ } else if (local_exec || (local_exec_on_local_files &&
+ exec_ok(HTLoadedDocumentURL(),
+ doc->address + 9, EXEC_PATH))) {
+
+ char *p = NULL;
+
+ /*
+ * Bug puts slash on end if none is in the string.
+ */
+ char *last_slash = strrchr(doc->address, '/');
+
+ if (last_slash - doc->address
+ == (int) strlen(doc->address) - 1)
+ doc->address[strlen(doc->address) - 1] = '\0';
+
+ /*
+ * Convert '~' to $HOME.
+ */
+ StrAllocCopy(p, doc->address);
+ LYTildeExpand(&p, TRUE);
+
+ /*
+ * Show URL before executing it.
+ */
+ HTInfoMsg(doc->address);
+ stop_curses();
+ /*
+ * Run the command.
+ */
+ if (strstr(p, "//") == p + 9)
+ LYSystem(p + 11);
+ else
+ LYSystem(p + 9);
+ FREE(p);
+
+ if (url_type != LYNXPROG_URL_TYPE) {
+ /*
+ * Make sure user gets to see screen output.
+ */
+#ifndef VMS
+ signal(SIGINT, SIG_IGN);
+#endif /* !VMS */
+ printf("\n%s", RETURN_TO_LYNX);
+ fflush(stdout);
+ (void) LYgetch();
+#ifdef VMS
+ HadVMSInterrupt = FALSE;
+#endif /* VMS */
+ }
+ if (!dump_output_immediately) {
+ start_curses();
+ LYAddVisitedLink(doc);
+ }
+
+ } else {
+ char *buf = 0;
+
+ HTSprintf0(&buf,
+ EXECUTION_DISABLED_FOR_FILE,
+ key_for_func(LYK_OPTIONS));
+ HTAlert(buf);
+ FREE(buf);
+ }
+#else /* no exec_links */
+ HTUserMsg(EXECUTION_NOT_COMPILED);
+#endif /* EXEC_LINKS */
+ return (NULLFILE);
+
+ } else if (url_type == MAILTO_URL_TYPE) {
+ if (no_mail) {
+ HTUserMsg(MAIL_DISABLED);
+ } else if (!dump_output_immediately) {
+ HTParentAnchor *tmpanchor = HTAnchor_findAddress(&WWWDoc);
+ const char *title;
+ char *tmptitle = NULL;
+
+ title = "";
+ if (HTAnchor_title(tmpanchor)) {
+ title = HTAnchor_title(tmpanchor);
+ } else if (HTMainAnchor && !LYUserSpecifiedURL) {
+ title = HTAnchor_subject(HTMainAnchor);
+ if (non_empty(title)) {
+ if (strncasecomp(title, "Re:", 3)) {
+ StrAllocCopy(tmptitle, "Re: ");
+ StrAllocCat(tmptitle, title);
+ title = tmptitle;
+ }
+ } else {
+ title = "";
+ }
+ }
+ cp = StrChr(doc->address, ':') + 1;
+ reply_by_mail(cp,
+ ((HTMainAnchor && !LYUserSpecifiedURL)
+ ? (char *) HTMainAnchor->address
+ : (char *) doc->address),
+ title,
+ (HTMainAnchor && !LYUserSpecifiedURL)
+ ? HTMainAnchor->message_id
+ : NULL);
+ FREE(tmptitle);
+ }
+ return (NULLFILE);
+
+ /*
+ * From here on we could have a remote host, so check if that's
+ * allowed.
+ */
+ } else if (local_host_only &&
+ url_type != LYNXEDITMAP_URL_TYPE &&
+ url_type != LYNXKEYMAP_URL_TYPE &&
+ url_type != LYNXIMGMAP_URL_TYPE &&
+ url_type != LYNXCOOKIE_URL_TYPE &&
+ url_type != LYNXMESSAGES_URL_TYPE &&
+#ifdef USE_CACHEJAR
+ url_type != LYNXCACHE_URL_TYPE &&
+#endif
+ url_type != LYNXCGI_URL_TYPE &&
+ !(url_type == NEWS_URL_TYPE &&
+ StrNCmp(doc->address, "news://", 7)) &&
+ !(LYisLocalHost(doc->address) ||
+ LYisLocalAlias(doc->address))) {
+ HTUserMsg(ACCESS_ONLY_LOCALHOST);
+ return (NULLFILE);
+
+ /*
+ * Disable www telnet access if not telnet_ok.
+ */
+ } else if (url_type == TELNET_URL_TYPE ||
+ url_type == TN3270_URL_TYPE ||
+ url_type == TELNET_GOPHER_URL_TYPE) {
+ char *proxy;
+
+ if (!telnet_ok) {
+ HTUserMsg(TELNET_DISABLED);
+ return (NULLFILE);
+ } else if (no_telnet_port && StrChr(doc->address + 7, ':')) {
+ HTUserMsg(TELNET_PORT_SPECS_DISABLED);
+ return (NULLFILE);
+ /*
+ * Detect weird case where interactive protocol would be
+ * proxied, and to a non-interactive protocol at that.
+ */
+ } else if ((proxy = LYGetEnv(((url_type == TN3270_URL_TYPE)
+ ? "tn3270_proxy"
+ :
+ ((url_type == TELNET_GOPHER_URL_TYPE)
+ ? "gopher_proxy"
+ : "telnet_proxy")))) != NULL &&
+ !override_proxy(doc->address) &&
+ (!isTELNET_URL(proxy) &&
+ !isTN3270_URL(proxy) &&
+ !isRLOGIN_URL(proxy))) {
+ /* Do nothing, fall through to generic code - kw */
+ } else {
+ stop_curses();
+ HTLoadAbsolute(&WWWDoc);
+ if (!dump_output_immediately) {
+ start_curses();
+ fflush(stdout);
+ LYAddVisitedLink(doc);
+ }
+ return (NULLFILE);
+ }
+
+ /*
+ * Disable www news access if not news_ok.
+ */
+#ifndef DISABLE_NEWS
+ } else if (!news_ok && (url_type == NEWS_URL_TYPE ||
+ url_type == NNTP_URL_TYPE)) {
+ HTUserMsg(NEWS_DISABLED);
+ return (NULLFILE);
+#endif
+
+ } else if (url_type == RLOGIN_URL_TYPE) {
+ char *proxy;
+
+ if (!rlogin_ok) {
+ HTUserMsg(RLOGIN_DISABLED);
+ return (NULLFILE);
+ /*
+ * Detect weird case where interactive protocol would be
+ * proxied, and to a non-interactive protocol at that.
+ */
+ } else if ((proxy = LYGetEnv("rlogin_proxy")) != NULL &&
+ !override_proxy(doc->address) &&
+ (!isTELNET_URL(proxy) &&
+ !isTN3270_URL(proxy) &&
+ !isRLOGIN_URL(proxy))) {
+ /* Do nothing, fall through to generic code - kw */
+ } else {
+ stop_curses();
+ HTLoadAbsolute(&WWWDoc);
+ fflush(stdout);
+ if (!dump_output_immediately) {
+ start_curses();
+ LYAddVisitedLink(doc);
+ }
+ return (NULLFILE);
+ }
+
+ /*
+ * If it's a gopher index type and there isn't a search term
+ * already attached then do this. Otherwise just load it!
+ */
+ } else if (url_type == INDEX_GOPHER_URL_TYPE &&
+ StrChr(doc->address, '?') == NULL) {
+ int status;
+
+ /*
+ * Make sure we don't have a gopher+ escaped tab instead of a
+ * gopher0 question mark delimiting the search term. - FM
+ */
+ if ((cp = strstr(doc->address, "%09")) != NULL) {
+ *cp = '\0';
+ StrAllocCopy(temp, doc->address);
+ cp += 3;
+ if (*cp && StrNCmp(cp, "%09", 3)) {
+ StrAllocCat(temp, "?");
+ StrAllocCat(temp, cp);
+ if ((cp = strstr(temp, "%09")) != NULL) {
+ *cp = '\0';
+ }
+ }
+ StrAllocCopy(doc->address, temp);
+ FREE(temp);
+ goto Try_Redirected_URL;
+ }
+ /*
+ * Load it because the do_www_search routine uses the base url of
+ * the currently loaded document :(
+ */
+ if (!HTLoadAbsolute(&WWWDoc)) {
+ return (NOT_FOUND);
+ }
+ status = do_www_search(doc);
+ if (status == NULLFILE) {
+ LYpop(doc);
+ WWWDoc.address = doc->address;
+ WWWDoc.post_data = doc->post_data;
+ WWWDoc.post_content_type = doc->post_content_type;
+ WWWDoc.bookmark = doc->bookmark;
+ WWWDoc.isHEAD = doc->isHEAD;
+ WWWDoc.safe = doc->safe;
+ status = HTLoadAbsolute(&WWWDoc);
+#ifdef DIRED_SUPPORT
+ } else {
+ lynx_edit_mode = FALSE;
+#endif /* DIRED_SUPPORT */
+ }
+ return (status);
+ }
+
+ if (!ftp_ok
+ && (url_type == FTP_URL_TYPE
+ || url_type == NCFTP_URL_TYPE)) {
+ HTUserMsg(FTP_DISABLED);
+ return (NULLFILE);
+ } else if (url_type == HTML_GOPHER_URL_TYPE) {
+ char *tmp = NULL;
+
+ /*
+ * If tuple's Path=GET%20/... convert to an http URL.
+ */
+ if ((cp = StrChr(doc->address + 9, '/')) != NULL &&
+ 0 == StrNCmp(++cp, "hGET%20/", 8)) {
+ StrAllocCopy(tmp, "http://");
+ CTRACE((tfp, "getfile: URL '%s'\n",
+ doc->address));
+ *cp = '\0';
+ StrAllocCat(tmp, doc->address + 9);
+ /*
+ * If the port is defaulted, it should stay 70.
+ */
+ if (StrChr(tmp + 6, ':') == NULL) {
+ StrAllocCat(tmp, "70/");
+ tmp[strlen(tmp) - 4] = ':';
+ }
+ if (strlen(cp + 7) > 1)
+ StrAllocCat(tmp, cp + 8);
+ StrAllocCopy(doc->address, tmp);
+ CTRACE((tfp, " changed to '%s'\n",
+ doc->address));
+ FREE(tmp);
+ url_type = HTTP_URL_TYPE;
+ } else if ((cp = StrChr(doc->address + 9, '/')) != NULL &&
+ 0 == StrNCmp(++cp, "hURL", 4)) {
+ StrAllocCopy(tmp, cp + 4);
+ HTUnEscape(tmp);
+ if (*tmp == ':') {
+ CTRACE((tfp, "getfile: URL '%s'\n", doc->address));
+ StrAllocCopy(doc->address, tmp + 1);
+ FREE(tmp);
+ CTRACE((tfp, " changed to '%s'\n", doc->address));
+ return getfile(doc, target);
+ } else
+ FREE(tmp);
+ }
+ }
+
+ if (url_type == HTTP_URL_TYPE ||
+ url_type == HTTPS_URL_TYPE ||
+ url_type == FTP_URL_TYPE ||
+ url_type == NCFTP_URL_TYPE ||
+ url_type == CSO_URL_TYPE) {
+ fix_httplike_urls(doc, url_type);
+ }
+
+ WWWDoc.address = doc->address; /* possible reload */
+#ifdef DIRED_SUPPORT
+ lynx_edit_mode = FALSE;
+#endif /* DIRED_SUPPORT */
+
+#ifndef DISABLE_BIBP
+ if (url_type == BIBP_URL_TYPE) {
+ char *bibpTmp = NULL;
+
+ if (!BibP_bibhost_checked)
+ LYCheckBibHost();
+ if (BibP_bibhost_available) {
+ StrAllocCopy(bibpTmp, BibP_bibhost);
+ } else if (HTMainAnchor && HTAnchor_citehost(HTMainAnchor)) {
+ StrAllocCopy(bibpTmp, HTAnchor_citehost(HTMainAnchor));
+ } else {
+ StrAllocCopy(bibpTmp, BibP_globalserver);
+ }
+ if (HTMainAnchor && HTAnchor_citehost(HTMainAnchor)) {
+ StrAllocCat(bibpTmp, "bibp1.0/resolve?citehost=");
+ StrAllocCat(bibpTmp, HTAnchor_citehost(HTMainAnchor));
+ StrAllocCat(bibpTmp, "&usin=");
+ } else {
+ StrAllocCat(bibpTmp, "bibp1.0/resolve?usin=");
+ }
+ StrAllocCat(bibpTmp, doc->address + 5); /* USIN after bibp: */
+ StrAllocCopy(doc->address, bibpTmp);
+ WWWDoc.address = doc->address;
+ FREE(bibpTmp);
+ }
+#endif /* !DISABLE_BIBP */
+
+ if (url_type == FILE_URL_TYPE) {
+ /*
+ * If a file URL has a '~' as the lead character of its first
+ * symbolic element, convert the '~' to Home_Dir(), then append
+ * the rest of of path, if present, skipping "user" if "~user"
+ * was entered, simplifying, and eliminating any residual
+ * relative elements. - FM
+ */
+ LYTildeExpand(&(doc->address), TRUE);
+ WWWDoc.address = doc->address;
+ }
+ CTRACE_SLEEP(MessageSecs);
+ user_message(WWW_WAIT_MESSAGE, doc->address);
+
+ if (TRACE) {
+#ifdef USE_SLANG
+ if (LYCursesON) {
+ LYaddstr("*\n");
+ LYrefresh();
+ }
+#endif /* USE_SLANG */
+ CTRACE((tfp, "\n"));
+ }
+
+ if (!HTLoadAbsolute(&WWWDoc)) {
+ /*
+ * Check for redirection.
+ */
+ if (use_this_url_instead != NULL) {
+ if (!is_url(use_this_url_instead)) {
+ /*
+ * The server did not return a complete URL in its
+ * Location: header, probably due to a FORM or other
+ * CGI script written by someone who doesn't know that
+ * the http protocol requires that it be a complete
+ * URL, or using a server which does not treat such a
+ * redirect string from the script as an instruction to
+ * resolve it versus the initial request, check
+ * authentication with that URL, and then act on it
+ * without returning redirection to us. We'll violate
+ * the http protocol and resolve it ourselves using the
+ * URL of the original request as the BASE, rather than
+ * doing the RIGHT thing and returning an invalid
+ * address message. - FM
+ */
+ HTUserMsg(LOCATION_NOT_ABSOLUTE);
+ temp = HTParse(use_this_url_instead,
+ WWWDoc.address,
+ PARSE_ALL);
+ if (non_empty(temp)) {
+ StrAllocCopy(use_this_url_instead, temp);
+ }
+ FREE(temp);
+ }
+ url_type = is_url(use_this_url_instead);
+ if (!HTPermitRedir &&
+ (url_type == LYNXDOWNLOAD_URL_TYPE ||
+ url_type == LYNXEXEC_URL_TYPE ||
+ url_type == LYNXPROG_URL_TYPE ||
+#ifdef DIRED_SUPPORT
+ url_type == LYNXDIRED_URL_TYPE ||
+#endif /* DIRED_SUPPORT */
+ url_type == LYNXPRINT_URL_TYPE ||
+ url_type == LYNXOPTIONS_URL_TYPE ||
+ url_type == LYNXCFG_URL_TYPE ||
+ url_type == LYNXCOMPILE_OPTS_URL_TYPE ||
+ url_type == LYNXHIST_URL_TYPE ||
+ url_type == LYNXCOOKIE_URL_TYPE ||
+#ifdef USE_CACHEJAR
+ url_type == LYNXCACHE_URL_TYPE ||
+#endif
+ url_type == LYNXMESSAGES_URL_TYPE ||
+ (LYValidate &&
+ url_type != HTTP_URL_TYPE &&
+ url_type != HTTPS_URL_TYPE) ||
+ ((no_file_url || no_goto_file) &&
+ url_type == FILE_URL_TYPE) ||
+ (no_goto_lynxcgi &&
+ url_type == LYNXCGI_URL_TYPE) ||
+#ifndef DISABLE_BIBP
+ (no_goto_bibp &&
+ url_type == BIBP_URL_TYPE) ||
+#endif
+ (no_goto_cso &&
+ url_type == CSO_URL_TYPE) ||
+ (no_goto_finger &&
+ url_type == FINGER_URL_TYPE) ||
+ (no_goto_ftp &&
+ (url_type == FTP_URL_TYPE ||
+ url_type == NCFTP_URL_TYPE)) ||
+ (no_goto_gopher &&
+ url_type == GOPHER_URL_TYPE) ||
+ (no_goto_http &&
+ url_type == HTTP_URL_TYPE) ||
+ (no_goto_https &&
+ url_type == HTTPS_URL_TYPE) ||
+ (no_goto_mailto &&
+ url_type == MAILTO_URL_TYPE) ||
+#ifndef DISABLE_NEWS
+ (no_goto_news &&
+ url_type == NEWS_URL_TYPE) ||
+ (no_goto_nntp &&
+ url_type == NNTP_URL_TYPE) ||
+#endif
+ (no_goto_rlogin &&
+ url_type == RLOGIN_URL_TYPE) ||
+#ifndef DISABLE_NEWS
+ (no_goto_snews &&
+ url_type == SNEWS_URL_TYPE) ||
+#endif
+ (no_goto_telnet &&
+ url_type == TELNET_URL_TYPE) ||
+ (no_goto_tn3270 &&
+ url_type == TN3270_URL_TYPE) ||
+ (no_goto_wais &&
+ url_type == WAIS_URL_TYPE))) {
+ /*
+ * Some schemes are not acceptable from server
+ * redirections. - KW & FM
+ */
+ HTAlert(ILLEGAL_REDIRECTION_URL);
+ if (LYCursesON) {
+ HTUserMsg2(WWW_ILLEGAL_URL_MESSAGE,
+ use_this_url_instead);
+ } else {
+ fprintf(stderr,
+ WWW_ILLEGAL_URL_MESSAGE,
+ use_this_url_instead);
+ }
+ FREE(use_this_url_instead);
+ return (NULLFILE);
+ }
+ if ((pound = findPoundSelector(doc->address)) != NULL
+ && findPoundSelector(use_this_url_instead) == NULL) {
+ /*
+ * Our requested URL had a fragment associated with it,
+ * and the redirection URL doesn't, so we'll append the
+ * fragment associated with the original request. If
+ * it's bogus for the redirection URL, we'll be
+ * positioned at the top of that document, so there's
+ * no harm done. - FM
+ */
+ CTRACE((tfp,
+ "getfile: Adding fragment '%s' to redirection URL.\n",
+ pound));
+ StrAllocCat(use_this_url_instead, pound);
+ doc->link = -1;
+ }
+ CTRACE_SLEEP(MessageSecs);
+ HTUserMsg2(WWW_USING_MESSAGE, use_this_url_instead);
+ CTRACE((tfp, "\n"));
+ StrAllocCopy(doc->address,
+ use_this_url_instead);
+ FREE(use_this_url_instead);
+ if (redirect_post_content == FALSE) {
+ /*
+ * Freeing the content also yields a GET request. - FM
+ */
+ LYFreePostData(doc);
+ }
+ /*
+ * Go to top to check for URL's which get special handling
+ * and/or security checks in Lynx. - FM
+ */
+ goto Try_Redirected_URL;
+ }
+ if (HTNoDataOK) {
+ return (NULLFILE);
+ } else {
+ return (NOT_FOUND);
+ }
+ } else {
+
+ lynx_mode = NORMAL_LYNX_MODE;
+
+ /*
+ * Some URL's don't actually return a document; compare
+ * doc->address with the document that is actually loaded and
+ * return NULLFILE if not loaded. If www_search_result is not -1
+ * then this is a reference to a named anchor within the same
+ * document; do NOT return NULLFILE in that case.
+ */
+
+ /*
+ * Check for a #fragment selector.
+ */
+ pound = findPoundSelector(doc->address);
+
+ /*
+ * Check to see if there is a temp file waiting for us to
+ * download.
+ */
+ if (WWW_Download_File) {
+ HTParentAnchor *tmpanchor = HTAnchor_findAddress(&WWWDoc);
+ char *fname = NULL;
+
+ /*
+ * Check for a suggested filename from the
+ * Content-Disposition header. - FM
+ */
+ if (HTAnchor_SugFname(tmpanchor) != NULL) {
+ StrAllocCopy(fname, HTAnchor_SugFname(tmpanchor));
+ } else {
+ StrAllocCopy(fname, doc->address);
+ }
+ /*
+ * Check whether this is a compressed file, which we don't
+ * uncompress for downloads, and adjust any suffix
+ * appropriately. - FM
+ */
+ HTCheckFnameForCompression(&fname, tmpanchor, FALSE);
+
+ if (LYdownload_options(&fname,
+ WWW_Download_File) < 0) {
+ FREE(fname);
+ return (NOT_FOUND);
+ }
+ LYAddVisitedLink(doc);
+ StrAllocCopy(doc->address, fname);
+ FREE(fname);
+ doc->internal_link = FALSE;
+ WWWDoc.address = doc->address;
+ LYFreePostData(doc);
+ WWWDoc.post_data = NULL;
+ WWWDoc.post_content_type = NULL;
+ WWWDoc.bookmark = doc->bookmark = FALSE;
+ WWWDoc.isHEAD = doc->isHEAD = FALSE;
+ WWWDoc.safe = doc->safe = FALSE;
+ HTOutputFormat = WWW_PRESENT;
+ if (!HTLoadAbsolute(&WWWDoc)) {
+ return (NOT_FOUND);
+ } else {
+ return (NORMAL);
+ }
+
+ } else if (pound == NULL &&
+ /*
+ * HTAnchor hash-table searches are now case-sensitive
+ * (hopefully, without anchor deletion problems), so this
+ * is too. - FM
+ */
+ (strcmp(doc->address,
+ HTLoadedDocumentURL()) ||
+ /*
+ * Also check the post_data elements. - FM
+ */
+ !BINEQ(doc->post_data,
+ HTLoadedDocumentPost_data()) ||
+ /*
+ * Also check the isHEAD element. - FM
+ */
+ doc->isHEAD != HTLoadedDocumentIsHEAD())) {
+ /*
+ * Nothing needed to be shown.
+ */
+ LYAddVisitedLink(doc);
+ return (NULLFILE);
+
+ } else {
+ if (pound != NULL) {
+ if (!HTMainText) { /* this should not happen... */
+ return (NULLFILE); /* but it can. - kw */
+ }
+ /*
+ * May set www_search_result.
+ */
+ if (HTFindPoundSelector(pound + 1)) {
+ *target = www_search_result;
+ doc->link = -1;
+ }
+ }
+ return (NORMAL);
+ }
+ }
+ } else {
+ CTRACE_SLEEP(MessageSecs);
+ HTUserMsg2(WWW_BAD_ADDR_MESSAGE, doc->address);
+ CTRACE((tfp, "\n"));
+ return (NULLFILE);
+ }
+}
+
+/*
+ * Set source mode for the next retrieval via getfile or HTreparse_document.
+ * mode == -1: force normal presentation
+ * mode == 1: force source presentation
+ * mode == 0: reset to normal if it was set to source
+ * - kw
+ */
+void srcmode_for_next_retrieval(int mode)
+{
+ if (mode < 0) {
+ HTOutputFormat = WWW_PRESENT;
+#ifdef USE_PRETTYSRC
+ psrc_view = FALSE;
+#endif
+
+ } else if (mode == 0) {
+ if (HTOutputFormat == WWW_SOURCE)
+ HTOutputFormat = WWW_PRESENT;
+#ifdef USE_PRETTYSRC
+ else if (LYpsrc)
+ psrc_view = FALSE;
+#endif
+
+ } else {
+#ifdef USE_PRETTYSRC
+ if (LYpsrc)
+ psrc_view = TRUE;
+ else
+ HTOutputFormat = WWW_SOURCE;
+#else
+ HTOutputFormat = WWW_SOURCE;
+#endif
+ }
+}
+
+/*
+ * The user wants to select a link or page by number.
+ *
+ * If follow_link_number returns DO_LINK_STUFF do_link will be run immediately
+ * following its execution.
+ *
+ * If follow_link_number returns DO_GOTOLINK_STUFF it has updated the passed in
+ * doc for positioning on a link.
+ *
+ * If follow_link_number returns DO_GOTOPAGE_STUFF it has set doc->line to the
+ * top line of the desired page for displaying that page.
+ *
+ * If follow_link_number returns PRINT_ERROR an error message will be given to
+ * the user.
+ *
+ * If follow_link_number returns DO_FORMS_STUFF some forms stuff will be done.
+ * (Not yet implemented.)
+ *
+ * If follow_link_number returns DO_NOTHING nothing special will run after it.
+ */
+int follow_link_number(int c,
+ int cur,
+ DocInfo *doc,
+ int *num)
+{
+ bstring *temp = NULL;
+ char *p;
+ int rel = 0;
+ int new_top, new_link;
+ BOOL want_go;
+ int curline = *num; /* passed in from mainloop() */
+ int code;
+
+ CTRACE((tfp, "follow_link_number(%d,%d,...)\n", c, cur));
+ BStrCopy0(temp, "?");
+ temp->str[0] = (char) c;
+ *num = -1;
+ _statusline(FOLLOW_LINK_NUMBER);
+
+ /*
+ * Get the number, possibly with a letter suffix, from the user.
+ */
+ if (LYgetBString(&temp, FALSE, 120, NORECALL) < 0 ||
+ isBEmpty(temp)) {
+ HTInfoMsg(CANCELLED);
+ return (DO_NOTHING);
+ }
+
+ p = temp->str;
+ *num = atoi(p);
+ while (isdigit(UCH(*p)))
+ ++p;
+ c = *p; /* reuse c; 0 or g or p or + or - */
+ switch (c) {
+ case '+':
+ case '-':
+ /* 123+ or 123- */
+ rel = c;
+ c = *++p;
+ break;
+ default:
+ rel = *++p;
+ break;
+ case 0:
+ break;
+ }
+ /* don't currently check for errors typing suffix */
+
+ CTRACE((tfp, " temp=%s, *num=%d, rel='%c'\n", temp->str, *num, rel));
+ /*
+ * Check if we had a 'p' or 'P' following the number as a flag for
+ * displaying the page with that number. - FM
+ */
+ if ((c == 'p' || c == 'P') && display_lines == 0) {
+ CTRACE((tfp, " curline=%d, LYlines=%d, display too small!\n",
+ curline, LYlines));
+ code = PRINT_ERROR;
+ } else if (c == 'p' || c == 'P') {
+ int nlines = HText_getNumOfLines();
+ int npages = (((nlines + 1) > display_lines)
+ ? (((nlines + 1) + (display_lines - 1)) / (display_lines))
+ : 1);
+ int curpage = (((curline + 1) > display_lines)
+ ? (((curline + 1) + (display_lines - 1)) / (display_lines))
+ : 1);
+
+ CTRACE((tfp, " nlines=%d, npages=%d, curline=%d, curpage=%d\n",
+ nlines, npages, curline, curpage));
+ if (*num < 1)
+ *num = rel ? 0 : 1;
+ if (rel == '+')
+ *num = curpage + *num;
+ else if (rel == '-')
+ *num = curpage - *num;
+ doc->line = ((npages <= 1)
+ ? 1
+ : ((*num <= npages)
+ ? (((*num - 1) * display_lines) + 1)
+ : (((npages - 1) * display_lines) + 1)));
+ code = DO_GOTOPAGE_STUFF;
+ } else {
+
+ /*
+ * Check if we want to make the link corresponding to the number the
+ * current link, rather than ACTIVATE-ing it.
+ */
+ want_go = (BOOL) (c == 'g' || c == 'G');
+
+ /* If rel, add or subtract num from current link, or
+ * nearest previous/subsequent link if current link is not on screen.
+ */
+ if (rel)
+ *num = HTGetRelLinkNum(*num, rel, cur);
+ /*
+ * If we have a valid number, act on it.
+ */
+ if (*num > 0) {
+ int info;
+ char *text = NULL;
+
+ /*
+ * Get the lname, and hightext, directly from www structures and
+ * add it to the cur link so that we can pass it transparently on
+ * to getfile(), and load new_top and new_link if we instead want
+ * to make the link number current. These things are done so that
+ * a link can be selected anywhere in the current document, whether
+ * it is displayed on the screen or not!
+ */
+ info = HTGetLinkInfo(*num,
+ want_go,
+ &new_top,
+ &new_link,
+ &text,
+ &links[cur].lname);
+ if (text != NULL)
+ LYSetHilite(cur, text);
+
+ if (info == WWW_INTERN_LINK_TYPE) {
+ links[cur].type = WWW_INTERN_LINK_TYPE;
+ code = DO_LINK_STUFF;
+ } else if (info == LINK_LINE_FOUND) {
+ doc->line = new_top + 1;
+ doc->link = new_link;
+ code = DO_GOTOLINK_STUFF;
+ } else if (info) {
+ links[cur].type = WWW_LINK_TYPE;
+ code = DO_LINK_STUFF;
+ } else {
+ code = PRINT_ERROR;
+ }
+ } else {
+ code = PRINT_ERROR;
+ }
+ }
+ BStrFree(temp);
+ return code;
+}
+
+#if defined(EXEC_LINKS) || defined(LYNXCGI_LINKS)
+
+struct trust {
+ char *src;
+ char *path;
+ int type;
+ struct trust *next;
+};
+
+static struct trust *trusted_exec = 0;
+static struct trust *always_trusted_exec;
+static struct trust *trusted_cgi = 0;
+
+static struct trust *new_trust(const char *src, const char *path, int type)
+{
+ struct trust *tp;
+
+ tp = typecalloc(struct trust);
+
+ if (tp == NULL)
+ outofmem(__FILE__, "new_trust");
+
+ tp->type = type;
+ StrAllocCopy(tp->src, src);
+ StrAllocCopy(tp->path, path);
+
+ return tp;
+}
+
+static struct trust *get_trust(struct trust **table, const char *src, int type)
+{
+ if (*table == 0) {
+ *table = new_trust(src, "", type);
+ }
+ return *table;
+}
+
+#ifdef LY_FIND_LEAKS
+static void free_data(struct trust **data)
+{
+ struct trust *cur = (*data);
+ struct trust *next;
+
+ while (cur) {
+ FREE(cur->src);
+ FREE(cur->path);
+ next = cur->next;
+ FREE(cur);
+ cur = next;
+ }
+ *data = NULL;
+}
+
+static void LYTrusted_free(void)
+{
+ free_data(&trusted_exec);
+ free_data(&always_trusted_exec);
+ free_data(&trusted_cgi);
+
+ return;
+}
+#endif /* LY_FIND_LEAKS */
+
+void add_trusted(char *str,
+ int type)
+{
+ struct trust *tp;
+ char *path;
+ char *src = str;
+ const char *after_tab;
+ int Type = type;
+ static BOOLEAN first = TRUE;
+
+ if (!src)
+ return;
+ if (first) {
+#ifdef LY_FIND_LEAKS
+ atexit(LYTrusted_free);
+#endif
+ first = FALSE;
+ }
+
+ path = StrChr(src, '\t');
+ if (path) {
+ *path++ = '\0';
+ after_tab = path;
+ } else {
+ after_tab = "";
+ }
+
+ tp = new_trust(src, after_tab, Type);
+
+ if (Type == EXEC_PATH) {
+ tp->next = trusted_exec;
+ trusted_exec = tp;
+ } else if (Type == ALWAYS_EXEC_PATH) {
+ tp->next = always_trusted_exec;
+ always_trusted_exec = tp;
+ } else if (Type == CGI_PATH) {
+ tp->next = trusted_cgi;
+ trusted_cgi = tp;
+ }
+}
+
+/*
+ * Check to see if the supplied paths is allowed to be executed.
+ */
+BOOLEAN exec_ok(const char *source,
+ const char *linktext,
+ int type)
+{
+ struct trust *tp;
+ const char *cp;
+ const char *allowed_extra_chars;
+ int Type = type;
+
+ /*
+ * Always OK if it is a jump file shortcut.
+ */
+ if (LYJumpFileURL)
+ return TRUE;
+
+ /*
+ * Choose the trust structure based on the type.
+ */
+ if (Type == EXEC_PATH) {
+ tp = get_trust(&trusted_exec, "file://localhost/", EXEC_PATH);
+ } else if (Type == ALWAYS_EXEC_PATH) {
+ tp = get_trust(&always_trusted_exec, "none", ALWAYS_EXEC_PATH);
+ } else if (Type == CGI_PATH) {
+ tp = get_trust(&trusted_cgi, "none", CGI_PATH);
+ } else {
+ HTAlert(MALFORMED_EXEC_REQUEST);
+ return FALSE;
+ }
+
+#ifdef VMS
+ /*
+ * Security: reject on relative path.
+ */
+ if ((cp = StrChr(linktext, '[')) != NULL) {
+ char *cp1;
+
+ if (((cp1 = StrChr(cp, '-')) != NULL) &&
+ StrChr(cp1, ']') != NULL) {
+ while (cp1[1] == '-')
+ cp1++;
+ if (cp1[1] == ']' ||
+ cp1[1] == '.') {
+ HTAlert(RELPATH_IN_EXEC_LINK);
+ return FALSE;
+ }
+ }
+ }
+#else
+ /*
+ * Security: reject on relative path.
+ */
+ if (strstr(linktext, "../") != NULL) {
+ HTAlert(RELPATH_IN_EXEC_LINK);
+ return FALSE;
+ }
+
+ /*
+ * Security: reject on strange character.
+ */
+ if (Type == CGI_PATH)
+ allowed_extra_chars = " _-:./@~$&+=\t";
+ else
+ allowed_extra_chars = " _-:./@~$+=\t";
+ for (cp = linktext; *cp != '\0'; cp++) {
+ if (!isalnum(UCH(*cp)) && !StrChr(allowed_extra_chars, *cp)) {
+ char *buf = 0;
+
+ HTSprintf0(&buf,
+ BADCHAR_IN_EXEC_LINK,
+ *cp);
+ HTAlert(buf);
+ FREE(buf);
+ return FALSE;
+ }
+ }
+#endif /* VMS */
+
+ check_tp_for_entry:
+ while (tp) {
+ if (tp->type == Type) {
+ char const *command = linktext;
+
+ if (strstr(command, "//") == linktext) {
+ command += 2;
+ }
+ CTRACE((tfp, "comparing source\n\t'%s'\n\t'%s'\n", source, tp->src));
+ CTRACE((tfp, "comparing command\n\t'%s'\n\t'%s'\n", command, tp->path));
+ if (STRNADDRCOMP(source, tp->src, strlen(tp->src)) == 0 &&
+ STRNADDRCOMP(command, tp->path, strlen(tp->path)) == 0)
+ return TRUE;
+ }
+ tp = tp->next;
+ }
+ if (Type == EXEC_PATH &&
+ always_trusted_exec->next != 0) {
+ Type = ALWAYS_EXEC_PATH;
+ tp = always_trusted_exec;
+ goto check_tp_for_entry;
+ }
+ if (!(no_exec && type == ALWAYS_EXEC_PATH))
+ HTAlert(BADLOCPATH_IN_EXEC_LINK);
+ return FALSE;
+}
+#endif /* EXEC_LINKS || LYNXCGI_LINKS */
+
+static int fix_httplike_urls(DocInfo *doc, UrlTypes type)
+{
+ char *slash;
+
+ /*
+ * If there's a fragment present, our simplistic methods won't work. - kw
+ */
+ if (findPoundSelector(doc->address) != NULL)
+ return 0;
+
+#ifndef DISABLE_FTP
+ /*
+ * If it's an ftp URL with a trailing slash, trim it off.
+ */
+ if (type == FTP_URL_TYPE &&
+ LYIsHtmlSep(doc->address[strlen(doc->address) - 1])) {
+ char *path = HTParse(doc->address, "", PARSE_PATH | PARSE_PUNCTUATION);
+
+ /*
+ * If the path is a lone slash, we're done. - FM
+ */
+ if (path) {
+ if (LYIsHtmlSep(path[0]) && path[1] == '\0') {
+ FREE(path);
+ return 0;
+ }
+ FREE(path);
+ }
+
+ /*
+ * If we're proxying ftp, don't trim anything. - KW
+ */
+ if ((LYGetEnv("ftp_proxy") != NULL) &&
+ !override_proxy(doc->address))
+ return 0;
+
+ /*
+ * If we get to here, trim the trailing slash. - FM
+ */
+ CTRACE((tfp, "fix_httplike_urls: URL '%s'\n", doc->address));
+ LYTrimHtmlSep(doc->address);
+ CTRACE((tfp, " changed to '%s'\n", doc->address));
+ CTRACE_SLEEP(MessageSecs);
+ } else if (type == NCFTP_URL_TYPE) {
+ char *path = NULL;
+ char *first = doc->address;
+ char *second = StrChr(first, ':');
+
+ CTRACE((tfp, "fix_httplike_urls: URL '%s'\n", doc->address));
+ if (second == 0)
+ second = first + strlen(first);
+ else
+ *second++ = '\0';
+ HTSprintf0(&path, "%s//%s%s", STR_FTP_URL, first, second);
+ FREE(doc->address);
+ doc->address = path;
+
+ CTRACE((tfp, " changed to '%s'\n", doc->address));
+ CTRACE_SLEEP(MessageSecs);
+ }
+#endif /* DISABLE_FTP */
+
+ /*
+ * If there isn't a slash besides the two at the beginning, append one.
+ */
+ if ((slash = strrchr(doc->address, '/')) != NULL) {
+ if (!LYIsHtmlSep(*(slash - 1)) || *(slash - 2) != ':') {
+ return (0);
+ }
+ if (type == HTTP_URL_TYPE ||
+ type == HTTPS_URL_TYPE) {
+ if ((slash - 2) != StrChr(doc->address, ':')) {
+ /*
+ * Turns out we were not looking at the right slash after all,
+ * there must have been more than one "://" which is valid at
+ * least for http URLs (later occurrences can be part of a
+ * query string, for example), so leave this alone, too. - kw
+ */
+ return (0);
+ }
+ if (StrChr(doc->address, '?')) {
+ /*
+ * If there is a question mark that appears to be part of the
+ * hostname, don't append anything either. Leave it to HTParse
+ * to interpret the question mark as ending the hostname. - kw
+ */
+ return (0);
+ }
+ }
+ }
+ CTRACE((tfp, "fix_httplike_urls: URL '%s'\n", doc->address));
+ LYAddHtmlSep(&(doc->address));
+ CTRACE((tfp, " changed to '%s'\n", doc->address));
+ CTRACE_SLEEP(MessageSecs);
+
+ return (1);
+}
diff --git a/src/LYGetFile.h b/src/LYGetFile.h
new file mode 100644
index 0000000..f204d07
--- /dev/null
+++ b/src/LYGetFile.h
@@ -0,0 +1,38 @@
+#ifndef LYGETFILE_H
+#define LYGETFILE_H
+
+#include <LYStructs.h>
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+#define NOT_FOUND 0
+#define NORMAL 1
+#define NULLFILE 3
+ extern int getfile(DocInfo *doc, int *target);
+ extern void srcmode_for_next_retrieval(int);
+ extern int follow_link_number(int c,
+ int cur,
+ DocInfo *doc,
+ int *num);
+ extern void add_trusted(char *str, int type);
+ extern BOOLEAN exec_ok(const char *source, const char *linkpath, int type);
+
+ extern char *WWW_Download_File;
+
+/* values for follow_link_number() */
+#define DO_LINK_STUFF 1
+#define DO_GOTOLINK_STUFF 2
+#define DO_GOTOPAGE_STUFF 3
+#define DO_FORMS_STUFF 4
+#define PRINT_ERROR 5
+
+/* values for add_trusted() and exec_ok() */
+#define EXEC_PATH 0
+#define ALWAYS_EXEC_PATH 1
+#define CGI_PATH 2
+
+#ifdef __cplusplus
+}
+#endif
+#endif /* LYGETFILE_H */
diff --git a/src/LYGlobalDefs.h b/src/LYGlobalDefs.h
new file mode 100644
index 0000000..37e30ba
--- /dev/null
+++ b/src/LYGlobalDefs.h
@@ -0,0 +1,740 @@
+/*
+ * $LynxId: LYGlobalDefs.h,v 1.155 2023/10/23 08:04:43 tom Exp $
+ *
+ * global variable definitions
+ */
+
+#ifndef LYGLOBALDEFS_H
+#define LYGLOBALDEFS_H
+
+#ifndef HTUTILS_H
+#include <HTUtils.h>
+#endif /* HTUTILS_H */
+
+#ifndef LYSTRUCTS_H
+#include <LYStructs.h>
+#endif /* LYSTRUCTS_H */
+
+/* Of the following definitions, currently unused are and could
+ be removed (at least):
+ CURRENT_KEYMAP_HELP
+*/
+#if defined(HAVE_CONFIG_H) && defined(HAVE_LYHELP_H)
+#include <LYHelp.h>
+#else
+#define ALT_EDIT_HELP "keystrokes/alt_edit_help.html"
+#define BASHLIKE_EDIT_HELP "keystrokes/bashlike_edit_help.html"
+#define COOKIE_JAR_HELP "Lynx_users_guide.html#Cookies"
+#define CACHE_JAR_HELP "Lynx_users_guide.html#Cache"
+#define CURRENT_KEYMAP_HELP "keystrokes/keystroke_help.html"
+#define DIRED_MENU_HELP "keystrokes/dired_help.html"
+#define DOWNLOAD_OPTIONS_HELP "Lynx_users_guide.html#RemoteSource"
+#define EDIT_HELP "keystrokes/edit_help.html"
+#define HISTORY_PAGE_HELP "keystrokes/history_help.html"
+#define LIST_PAGE_HELP "keystrokes/follow_help.html"
+#define LYNXCFG_HELP "lynx.cfg"
+#define OPTIONS_HELP "keystrokes/option_help.html"
+#define PRINT_OPTIONS_HELP "keystrokes/print_help.html"
+#define UPLOAD_OPTIONS_HELP "Lynx_users_guide.html#DirEd"
+#define VISITED_LINKS_HELP "keystrokes/visited_help.html"
+#endif /* LYHELP_H */
+
+#ifdef USE_SOURCE_CACHE
+#include <HTChunk.h>
+#endif
+
+#include <LYMail.h> /* to get ifdef's for mail-variables */
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+#ifdef SOCKS
+ extern BOOLEAN socks_flag;
+ extern unsigned long socks_bind_remoteAddr;
+#endif /* SOCKS */
+
+#ifdef IGNORE_CTRL_C
+ extern BOOLEAN sigint;
+#endif /* IGNORE_CTRL_C */
+
+#if USE_VMS_MAILER
+ extern char *mail_adrs;
+ extern BOOLEAN UseFixedRecords; /* convert binary files to FIXED 512 records */
+#endif /* VMS */
+
+#ifndef VMS
+ extern char *list_format;
+#endif /* !VMS */
+ extern char *ftp_format;
+
+ typedef enum {
+ BAD_HTML_IGNORE = 0
+ ,BAD_HTML_TRACE
+ ,BAD_HTML_MESSAGE
+ ,BAD_HTML_WARN
+ } enumBadHtml;
+
+ extern int cfg_bad_html; /* enumBadHtml */
+
+#ifdef DIRED_SUPPORT
+
+ typedef enum {
+ DIRS_FIRST = 0
+ ,FILES_FIRST
+ ,MIXED_STYLE
+ } enumDirListStyle;
+
+ typedef enum {
+ ORDER_BY_NAME
+ ,ORDER_BY_SIZE
+ ,ORDER_BY_DATE
+ ,ORDER_BY_MODE
+ ,ORDER_BY_TYPE
+ ,ORDER_BY_USER
+ ,ORDER_BY_GROUP
+ } enumDirListOrder;
+
+ extern BOOLEAN lynx_edit_mode;
+ extern BOOLEAN no_dired_support;
+ extern HTList *tagged;
+ extern int LYAutoUncacheDirLists;
+ extern int dir_list_style; /* enumDirListStyle */
+ extern int dir_list_order; /* enumDirListOrder */
+
+#ifdef OK_OVERRIDE
+ extern BOOLEAN prev_lynx_edit_mode;
+#endif /* OK_OVERRIDE */
+
+#ifdef OK_PERMIT
+ extern BOOLEAN no_change_exec_perms;
+#endif /* OK_PERMIT */
+
+#endif /* DIRED_SUPPORT */
+
+ extern int HTCacheSize; /* the number of documents cached in memory */
+
+#if defined(VMS) && defined(VAXC) && !defined(__DECC)
+ extern int HTVirtualMemorySize; /* bytes allocated and not yet freed */
+#endif /* VMS && VAXC && !__DECC */
+
+#if defined(EXEC_LINKS) || defined(EXEC_SCRIPTS)
+ extern BOOLEAN local_exec; /* TRUE to enable local program execution */
+ extern BOOLEAN local_exec_on_local_files; /* TRUE to enable local program *
+
+ * execution in local files only */
+#endif /* defined(EXEC_LINKS) || defined(EXEC_SCRIPTS) */
+
+#if defined(LYNXCGI_LINKS) && !defined(VMS) /* WebSter Mods -jkt */
+ extern char *LYCgiDocumentRoot; /* DOCUMENT_ROOT in the lynxcgi env */
+#endif /* LYNXCGI_LINKS */
+
+/* Values to which keypad_mode can be set */
+#define NUMBERS_AS_ARROWS 0
+#define LINKS_ARE_NUMBERED 1
+#define LINKS_AND_FIELDS_ARE_NUMBERED 2
+#define FIELDS_ARE_NUMBERED 3
+
+#define links_are_numbered() \
+ (keypad_mode == LINKS_ARE_NUMBERED || \
+ keypad_mode == LINKS_AND_FIELDS_ARE_NUMBERED)
+
+#define fields_are_numbered() \
+ (keypad_mode == FIELDS_ARE_NUMBERED || \
+ keypad_mode == LINKS_AND_FIELDS_ARE_NUMBERED)
+#define fields_are_named() \
+ (user_mode == ADVANCED_MODE)
+
+#define HIDDENLINKS_MERGE 0
+#define HIDDENLINKS_SEPARATE 1
+#define HIDDENLINKS_IGNORE 2
+
+#define NOVICE_MODE 0
+#define INTERMEDIATE_MODE 1
+#define ADVANCED_MODE 2
+#define MINIMAL_MODE 3
+ extern BOOLEAN LYUseNoviceLineTwo; /* True if TOGGLE_HELP is not mapped */
+
+#define MAX_LINE 1024 /* No window can be wider than this */
+#define MAX_COLS (MAX_LINE-10) /* we don't expect wider than this */
+#define DFT_COLS 80 /* ...and normally only this */
+#define DFT_ROWS 24 /* ...corresponding nominal height */
+
+ extern char star_string[MAX_LINE + 1]; /* from GridText.c */
+
+#define STARS(n) \
+ ((n) >= MAX_LINE ? star_string : &star_string[(MAX_LINE-1)] - (n))
+
+ typedef enum {
+ SHOW_COLOR_UNKNOWN = -1
+ ,SHOW_COLOR_NEVER = 0 /* positive numbers are index in LYOptions.c */
+ ,SHOW_COLOR_OFF
+ ,SHOW_COLOR_ON
+ ,SHOW_COLOR_ALWAYS
+ } enumShowColor;
+
+ extern int LYShowColor; /* Show color or monochrome? */
+ extern int LYrcShowColor; /* ... as read or last written */
+
+ typedef enum {
+ MBM_OFF = 0
+ ,MBM_STANDARD
+ ,MBM_ADVANCED
+ } enumMultiBookmarks;
+
+#if !defined(NO_OPTION_FORMS) && !defined(NO_OPTION_MENU)
+ extern BOOLEAN LYUseFormsOptions; /* use Forms-based options menu */
+
+#else
+#define LYUseFormsOptions FALSE /* simplify ifdef'ing in LYMainLoop.c */
+#endif
+
+ typedef enum {
+ rateOFF = 0
+ ,rateBYTES = 1
+ ,rateKB
+#ifdef USE_READPROGRESS
+ ,rateEtaBYTES
+ ,rateEtaKB
+ ,rateEtaBYTES2
+ ,rateEtaKB2
+#endif
+#ifdef USE_PROGRESSBAR
+ ,rateBAR
+#endif
+ } TransferRate;
+
+#ifdef USE_READPROGRESS
+# define isRateInKB(n) ((n) == rateKB || (n) == rateEtaKB || (n) == rateEtaKB2)
+#else
+# define isRateInKB(n) ((n) == rateKB)
+#endif
+
+#define TITLE_LINES 1
+
+ extern BOOLEAN LYCursesON; /* start_curses()->TRUE, stop_curses()->FALSE */
+ extern BOOLEAN LYJumpFileURL; /* URL from the jump file shortcuts? */
+ extern BOOLEAN LYNewsPosting; /* News posting supported if TRUE */
+ extern BOOLEAN LYAutoSession; /* Auto restore/save session? */
+ extern BOOLEAN LYShowCursor; /* Show the cursor or hide it? */
+ extern BOOLEAN LYShowTransferRate;
+ extern BOOLEAN LYUnderlineLinks; /* Show the links underlined vs bold */
+ extern BOOLEAN LYUseDefShoCur; /* Command line -show_cursor toggle */
+ extern BOOLEAN LYUserSpecifiedURL; /* URL from a goto or document? */
+ extern BOOLEAN LYfind_leaks;
+ extern BOOLEAN LYforce_HTML_mode;
+ extern BOOLEAN LYforce_no_cache;
+ extern BOOLEAN LYinternal_flag; /* don't need fresh copy, was internal link */
+ extern BOOLEAN LYoverride_no_cache; /* don't need fresh copy, from history */
+ extern BOOLEAN LYresubmit_posts;
+ extern BOOLEAN LYtrimBlankLines;
+ extern BOOLEAN LYtrimInputFields;
+ extern BOOLEAN LYxhtml_parsing;
+ extern BOOLEAN bold_H1;
+ extern BOOLEAN bold_headers;
+ extern BOOLEAN bold_name_anchors;
+ extern BOOLEAN LYcase_sensitive; /* TRUE to turn on case sensitive search */
+ extern BOOLEAN check_mail; /* TRUE to report unread/new mail messages */
+ extern BOOLEAN child_lynx; /* TRUE to exit with an arrow */
+ extern BOOLEAN dump_links_decoded;
+ extern BOOLEAN dump_links_inline;
+ extern BOOLEAN dump_links_only;
+ extern BOOLEAN dump_output_immediately;
+ extern BOOLEAN dump_to_stderr;
+ extern BOOLEAN emacs_keys; /* TRUE to turn on emacs-like key movement */
+ extern BOOLEAN error_logging; /* TRUE to mail error messages */
+ extern BOOLEAN ftp_ok;
+ extern BOOLEAN goto_buffer; /* TRUE if offering default goto URL */
+ extern BOOLEAN is_www_index;
+ extern BOOLEAN jump_buffer; /* TRUE if offering default shortcut */
+ extern BOOLEAN long_url_ok;
+ extern BOOLEAN lynx_mode;
+ extern BOOLEAN more_text; /* is there more document to display? */
+ extern BOOLEAN news_ok;
+ extern BOOLEAN number_fields_on_left;
+ extern BOOLEAN number_links_on_left;
+ extern BOOLEAN recent_sizechange; /* valid size_is_changed */
+ extern BOOLEAN rlogin_ok;
+ extern BOOLEAN size_is_changed; /* SIGWINCH was caught */
+ extern BOOLEAN syslog_requested_urls;
+ extern BOOLEAN system_editor; /* True if locked-down editor */
+ extern BOOLEAN telnet_ok;
+ extern BOOLEAN verbose_img; /* display filenames of images? */
+ extern BOOLEAN vi_keys; /* TRUE to turn on vi-like key movement */
+
+ extern HTList *Goto_URLs;
+ extern HTList *positionable_editor;
+
+ extern char *LYRequestReferer; /* Referer, may be set in getfile() */
+ extern char *LYRequestTitle; /* newdoc.title in calls to getfile() */
+ extern char *LYTransferName; /* abbreviation for Kilobytes */
+ extern char *LynxHome;
+#ifdef USE_SESSIONS
+ extern char *LYSessionFile; /* file for auto-session */
+ extern char *session_file; /* file for -session= */
+ extern char *sessionin_file; /* file for -sessionin= */
+ extern char *sessionout_file; /* file for -sessionout= */
+#endif
+ extern char *LynxSigFile; /* Signature file, in or off home */
+ extern char *helpfile;
+ extern char *helpfilepath;
+ extern char *jumpprompt; /* The default jump statusline prompt */
+ extern char *language;
+ extern char *lynx_cfg_file; /* location of active lynx.cfg file */
+ extern char *lynx_cmd_logfile; /* file to write keystroke commands, if any */
+ extern char *lynx_cmd_script; /* file to read keystroke commands, if any */
+ extern char *lynx_save_space;
+ extern char *lynx_temp_space;
+ extern char *lynxjumpfile;
+ extern char *lynxlinksfile;
+ extern char *lynxlistfile;
+ extern char *original_dir;
+ extern char *pref_charset; /* Lynx's preferred character set - MM */
+ extern char *startfile;
+ extern char *syslog_txt; /* syslog arb text for session */
+ extern char *system_mail;
+ extern char *system_mail_flags;
+ extern char *x_display;
+ extern char empty_string[];
+
+ extern const char *checked_box; /* form boxes */
+ extern const char *checked_radio; /* form radio buttons */
+ extern const char *unchecked_box; /* form boxes */
+ extern const char *unchecked_radio; /* form radio buttons */
+
+ extern int LYAcceptEncoding;
+ extern int LYAcceptMedia;
+ extern int LYContentType;
+ extern const char *ContentTypes[];
+ extern int LYTransferRate; /* see enum TransferRate */
+ extern int display_lines; /* number of lines in the display */
+ extern int dump_output_width;
+ extern int dump_server_status;
+ extern int keypad_mode; /* NUMBERS_AS_ARROWS or LINKS_ARE_NUMBERED */
+ extern int lynx_temp_subspace;
+ extern int max_cookies_buffer;
+ extern int max_cookies_domain;
+ extern int max_cookies_global;
+ extern int max_uri_size;
+#ifdef USE_SESSIONS
+ extern short session_limit; /* maximal entries saved/restored
+ in session file */
+#endif
+ extern int user_mode; /* novice or advanced */
+ extern int www_search_result;
+
+ extern BOOLEAN exec_frozen;
+ extern BOOLEAN had_restrictions_all; /* parsed these restriction options */
+ extern BOOLEAN had_restrictions_default; /* flags to note whether we have... */
+ extern BOOLEAN no_bookmark;
+ extern BOOLEAN no_bookmark_exec;
+ extern BOOLEAN no_chdir;
+ extern BOOLEAN no_compileopts_info;
+ extern BOOLEAN no_disk_save;
+ extern BOOLEAN no_dotfiles;
+ extern BOOLEAN no_download;
+ extern BOOLEAN no_editor;
+ extern BOOLEAN no_exec;
+ extern BOOLEAN no_file_url;
+ extern BOOLEAN no_goto;
+ extern BOOLEAN no_goto_configinfo;
+ extern BOOLEAN no_goto_cso;
+ extern BOOLEAN no_goto_file;
+ extern BOOLEAN no_goto_finger;
+ extern BOOLEAN no_goto_ftp;
+ extern BOOLEAN no_goto_gopher;
+ extern BOOLEAN no_goto_http;
+ extern BOOLEAN no_goto_https;
+ extern BOOLEAN no_goto_lynxcgi;
+ extern BOOLEAN no_goto_lynxexec;
+ extern BOOLEAN no_goto_lynxprog;
+ extern BOOLEAN no_goto_mailto;
+ extern BOOLEAN no_goto_news;
+ extern BOOLEAN no_goto_nntp;
+ extern BOOLEAN no_goto_rlogin;
+ extern BOOLEAN no_goto_snews;
+ extern BOOLEAN no_goto_telnet;
+ extern BOOLEAN no_goto_tn3270;
+ extern BOOLEAN no_goto_wais;
+ extern BOOLEAN no_inside_ftp;
+ extern BOOLEAN no_inside_news;
+ extern BOOLEAN no_inside_rlogin;
+ extern BOOLEAN no_inside_telnet; /* this and following are restrictions */
+ extern BOOLEAN no_jump;
+ extern BOOLEAN no_lynxcfg_info;
+ extern BOOLEAN no_lynxcfg_xinfo;
+ extern BOOLEAN no_lynxcgi;
+ extern BOOLEAN no_mail;
+ extern BOOLEAN no_multibook;
+ extern BOOLEAN no_newspost;
+ extern BOOLEAN no_option_save;
+ extern BOOLEAN no_outside_ftp;
+ extern BOOLEAN no_outside_news;
+ extern BOOLEAN no_outside_rlogin;
+ extern BOOLEAN no_outside_telnet;
+ extern BOOLEAN no_print; /* TRUE to disable printing */
+ extern BOOLEAN no_shell;
+ extern BOOLEAN no_suspend;
+ extern BOOLEAN no_telnet_port;
+ extern BOOLEAN no_useragent;
+
+ extern BOOLEAN no_statusline;
+ extern BOOLEAN no_filereferer;
+ extern char LYRefererWithQuery; /* 'S', 'P', or 'D' */
+ extern BOOLEAN local_host_only;
+ extern BOOLEAN override_no_download;
+ extern BOOLEAN show_dotfiles; /* From rcfile if no_dotfiles is false */
+ extern char *indexfile;
+ extern char *anonftp_password;
+ extern char *personal_mail_address;
+ extern char *personal_mail_name;
+ extern char *homepage; /* startfile or command line argument */
+ extern char *editor; /* if non empty it enables edit mode with
+
+ * the editor that is named */
+ extern char *jumpfile;
+ extern char *bookmark_page;
+ extern char *BookmarkPage;
+ extern char *personal_type_map;
+ extern char *global_type_map;
+ extern char *global_extension_map;
+ extern char *personal_extension_map;
+ extern char *LYHostName;
+ extern char *LYLocalDomain;
+ extern BOOLEAN LYGuessScheme;
+ extern BOOLEAN unique_urls;
+ extern BOOLEAN use_underscore;
+ extern BOOLEAN no_list;
+ extern BOOLEAN no_margins;
+ extern BOOLEAN no_pause;
+ extern BOOLEAN no_title;
+ extern BOOLEAN update_term_title;
+ extern BOOLEAN historical_comments;
+ extern BOOLEAN html5_charsets;
+ extern BOOLEAN minimal_comments;
+ extern BOOLEAN soft_dquotes;
+
+#ifdef USE_SOURCE_CACHE
+ extern BOOLEAN source_cache_file_error;
+ extern int LYCacheSource;
+
+#define SOURCE_CACHE_NONE 0
+#define SOURCE_CACHE_FILE 1
+#define SOURCE_CACHE_MEMORY 2
+
+ extern int LYCacheSourceForAborted;
+
+#define SOURCE_CACHE_FOR_ABORTED_KEEP 1
+#define SOURCE_CACHE_FOR_ABORTED_DROP 0
+#endif
+
+ extern BOOLEAN LYCancelDownload;
+ extern BOOLEAN LYRestricted; /* whether we had -anonymous option */
+ extern BOOLEAN LYValidate;
+ extern BOOLEAN LYPermitURL;
+ extern BOOLEAN track_internal_links;
+ extern BOOLEAN enable_scrollback; /* Clear screen before displaying new page */
+ extern BOOLEAN keep_mime_headers; /* Include mime headers and *
+
+ * force source dump */
+ extern BOOLEAN no_url_redirection; /* Don't follow URL redirections */
+
+#ifdef DISP_PARTIAL
+ extern BOOLEAN display_partial; /* Display document while loading */
+ extern int NumOfLines_partial; /* -//- "current" number of lines */
+ extern int partial_threshold;
+ extern BOOLEAN debug_display_partial; /* show with MessageSecs delay */
+ extern BOOLEAN display_partial_flag; /* permanent flag, not mutable */
+#endif
+ extern char *socks5_proxy;
+ extern char *form_post_data; /* User data for post form */
+ extern char *form_get_data; /* User data for get form */
+ extern char *http_error_file; /* Place HTTP status code in this file */
+ extern char *authentication_info[2]; /* Id:Password for protected documents */
+ extern char *proxyauth_info[2]; /* Id:Password for protected proxy server */
+ extern BOOLEAN HEAD_request; /* Do a HEAD request */
+ extern BOOLEAN scan_for_buried_news_references;
+ extern BOOLEAN bookmark_start; /* Use bookmarks as startfile */
+ extern BOOLEAN clickable_images;
+ extern BOOLEAN nested_tables;
+ extern BOOLEAN pseudo_inline_alts;
+ extern BOOLEAN crawl;
+ extern BOOLEAN traversal;
+ extern BOOLEAN check_realm;
+ extern char *startrealm;
+ extern BOOLEAN more_links;
+ extern int crawl_count;
+ extern BOOLEAN LYCancelledFetch;
+ extern const char *LYToolbarName;
+
+ extern BOOLEAN nomore;
+ extern int AlertSecs;
+ extern int InfoSecs;
+ extern int MessageSecs;
+ extern int DelaySecs;
+ extern int ReplaySecs;
+
+ extern char *LYUserAgent; /* Lynx User-Agent header */
+ extern char *LYUserAgentDefault; /* Lynx default User-Agent header */
+ extern BOOLEAN LYNoRefererHeader; /* Never send Referer header? */
+ extern BOOLEAN LYNoRefererForThis; /* No Referer header for this URL? */
+ extern BOOLEAN LYNoFromHeader; /* Never send From header? */
+ extern BOOLEAN LYSendUserAgent; /* send Lynx User-Agent header? */
+ extern BOOLEAN LYListNewsNumbers;
+ extern BOOLEAN LYUseMouse;
+ extern BOOLEAN LYListNewsDates;
+
+ extern BOOLEAN LYRawMode;
+ extern BOOLEAN LYDefaultRawMode;
+ extern BOOLEAN LYUseDefaultRawMode;
+ extern char *UCAssume_MIMEcharset;
+ extern BOOLEAN UCSaveBookmarksInUnicode; /* in titles, chars >127 save as &#xUUUU */
+ extern BOOLEAN UCForce8bitTOUPPER; /* disable locale case-conversion for >127 */
+ extern int outgoing_mail_charset; /* translate outgoing mail to this charset */
+
+ extern BOOLEAN LYisConfiguredForX;
+ extern char *URLDomainPrefixes;
+ extern char *URLDomainSuffixes;
+ extern BOOLEAN startfile_ok;
+ extern BOOLEAN LYSelectPopups; /* Cast popups to radio buttons? */
+ extern BOOLEAN LYUseDefSelPop; /* Command line -popup toggle */
+ extern int LYMultiBookmarks; /* Multi bookmark support on? */
+ extern BOOLEAN LYMBMBlocked; /* Force MBM support off? */
+ extern int LYStatusLine; /* Line for statusline() or -1 */
+ extern BOOLEAN LYCollapseBRs; /* Collapse serial BRs? */
+ extern BOOLEAN LYSetCookies; /* Process Set-Cookie headers? */
+ extern BOOLEAN LYAcceptAllCookies; /* accept ALL cookies? */
+
+ extern char *LYCookieAcceptDomains; /* domains to accept all cookies */
+ extern char *LYCookieRejectDomains; /* domains to reject all cookies */
+ extern char *LYCookieStrictCheckDomains; /* domains to check strictly */
+ extern char *LYCookieLooseCheckDomains; /* domains to check loosely */
+ extern char *LYCookieQueryCheckDomains; /* domains to check w/a query */
+ extern char *LYCookieSAcceptDomains; /* domains to accept all cookies */
+ extern char *LYCookieSRejectDomains; /* domains to reject all cookies */
+ extern char *LYCookieSStrictCheckDomains; /* domains to check strictly */
+ extern char *LYCookieSLooseCheckDomains; /* domains to check loosely */
+ extern char *LYCookieSQueryCheckDomains; /* domains to check w/a query */
+
+#ifndef DISABLE_BIBP
+ extern BOOLEAN no_goto_bibp;
+ extern char *BibP_globalserver; /* global server for bibp: links */
+ extern char *BibP_bibhost; /* local server for bibp: links */
+ extern BOOLEAN BibP_bibhost_checked; /* bibhost has been checked */
+ extern BOOLEAN BibP_bibhost_available; /* bibhost is responding */
+#endif
+
+#ifndef DISABLE_FTP
+ extern BOOLEAN ftp_local_passive;
+ extern BOOLEAN ftp_passive; /* TRUE if we want to use passive mode ftp */
+ extern HTList *broken_ftp_epsv;
+ extern HTList *broken_ftp_retr;
+ extern char *ftp_lasthost;
+#endif
+
+#ifdef USE_PERSISTENT_COOKIES
+ extern BOOLEAN persistent_cookies;
+ extern char *LYCookieFile; /* cookie read file */
+ extern char *LYCookieSaveFile; /* cookie save file */
+#endif /* USE_PERSISTENT_COOKIES */
+
+ extern char *XLoadImageCommand; /* Default image viewer for X */
+
+#ifdef USE_EXTERNALS
+ extern BOOLEAN no_externals; /* don't allow the use of externals */
+#endif
+
+ extern BOOLEAN LYNoISMAPifUSEMAP; /* Omit ISMAP link if MAP present? */
+ extern int LYHiddenLinks;
+
+ extern char *SSL_cert_file; /* Default CA CERT file */
+ extern char *SSL_client_cert_file; /* Default client CERT file */
+ extern char *SSL_client_key_file; /* Default client key file */
+
+ typedef enum {
+ HTTP_1_0
+ ,HTTP_1_1
+ } HTTP_LEVEL;
+
+ extern int HTprotocolLevel;
+
+ extern int Old_DTD;
+
+#define MBM_V_MAXFILES 25 /* Max number of sub-bookmark files */
+
+/*
+ * Arrays that holds the names of sub-bookmark files
+ * and their descriptions.
+ */
+ extern char *MBM_A_subbookmark[MBM_V_MAXFILES + 1];
+ extern char *MBM_A_subdescript[MBM_V_MAXFILES + 1];
+
+ extern BOOLEAN LYForceSSLCookiesSecure;
+ extern BOOLEAN LYNoCc;
+ extern BOOLEAN LYNonRestartingSIGWINCH;
+ extern BOOLEAN LYPreparsedSource; /* Show source as preparsed? */
+ extern BOOLEAN LYPrependBaseToSource;
+ extern BOOLEAN LYPrependCharsetToSource;
+ extern BOOLEAN LYQuitDefaultYes;
+ extern BOOLEAN LYReuseTempfiles;
+ extern BOOLEAN LYSeekFragAREAinCur;
+ extern BOOLEAN LYSeekFragMAPinCur;
+ extern BOOLEAN LYStripDotDotURLs; /* Try to fix ../ in some URLs? */
+ extern BOOLEAN LYUseBuiltinSuffixes;
+ extern BOOLEAN dont_wrap_pre;
+
+ extern int cookie_noprompt;
+
+ typedef enum {
+ FORCE_PROMPT_DFT /* force a prompt, use the result */
+ ,FORCE_PROMPT_YES /* assume "yes" where a prompt would be used */
+ ,FORCE_PROMPT_NO /* assume "no" where a prompt would be used */
+ } FORCE_PROMPT;
+
+ extern int cookie_version;
+
+ typedef enum {
+ COOKIES_RFC_2109
+ ,COOKIES_RFC_2965
+ ,COOKIES_RFC_6265
+ } COOKIES_VERSION;
+
+#define USE_RFC_2109 (cookie_version == (COOKIES_RFC_2109))
+#define USE_RFC_2965 (cookie_version == (COOKIES_RFC_2965))
+#define USE_RFC_6265 (cookie_version == (COOKIES_RFC_6265))
+
+#ifdef USE_SSL
+ extern int ssl_noprompt;
+#endif
+
+ extern int LYNoZapKey; /* 0: off (do 'z' checking), 1: full, 2: initially */
+
+#ifdef USE_JUSTIFY_ELTS
+ extern BOOLEAN ok_justify;
+ extern int justify_max_void_percent;
+#endif
+
+#ifdef USE_LOCALE_CHARSET
+ extern BOOLEAN LYLocaleCharset;
+#endif
+ extern BOOLEAN assumed_charset;
+
+#ifndef NO_DUMP_WITH_BACKSPACES
+ extern BOOLEAN with_backspaces;
+#endif
+
+#if defined(PDCURSES) && defined(PDC_BUILD) && PDC_BUILD >= 2401
+ extern int scrsize_x;
+ extern int scrsize_y;
+#endif
+
+ extern BOOLEAN conv_jisx0201kana;
+ extern BOOLEAN wait_viewer_termination;
+
+#ifndef NO_LYNX_TRACE
+ extern FILE *LYTraceLogFP; /* Pointer for TRACE log */
+ extern char *LYTraceLogPath; /* Path for TRACE log */
+#endif
+ extern BOOLEAN LYUseTraceLog; /* Use a TRACE log? */
+
+ extern BOOLEAN force_empty_hrefless_a;
+ extern int connect_timeout;
+ extern int reading_timeout;
+
+#ifdef TEXTFIELDS_MAY_NEED_ACTIVATION
+ extern BOOL textfields_need_activation;
+ extern BOOLEAN textfields_activation_option;
+
+#ifdef INACTIVE_INPUT_STYLE_VH
+ extern BOOL textinput_redrawn;
+#endif
+#else
+#define textfields_need_activation FALSE
+#endif /* TEXTFIELDS_MAY_NEED_ACTIVATION */
+
+ extern BOOLEAN textfield_prompt_at_left_edge;
+
+#ifndef VMS
+ extern BOOLEAN LYNoCore;
+ extern BOOLEAN restore_sigpipe_for_children;
+#endif /* !VMS */
+
+#if defined(USE_COLOR_STYLE)
+ extern int LYuse_color_style; /* color-style vs oldlynx */
+ extern char *lynx_lss_file;
+#endif
+
+#ifdef USE_DEFAULT_COLORS
+ extern BOOLEAN LYuse_default_colors;
+#endif
+
+ extern int HTNoDataOK; /* HT_NO_DATA-is-ok hack */
+ extern BOOLEAN FileInitAlreadyDone;
+
+#ifdef USE_PROGRAM_DIR
+ extern char *program_dir;
+#endif
+
+#ifdef __DJGPP__
+ extern BOOLEAN watt_debug;
+ extern BOOLEAN dj_is_bash;
+#endif /* __DJGPP__ */
+
+#ifdef WIN_EX
+/* LYMain.c */
+ extern BOOLEAN focus_window;
+ extern BOOLEAN system_is_NT;
+ extern char windows_drive[4];
+ extern int lynx_timeout;
+ extern CRITICAL_SECTION critSec_READ;
+#endif /* _WINDOWS */
+
+ extern BOOLEAN show_cfg;
+ extern BOOLEAN no_table_center;
+
+#if USE_BLAT_MAILER
+ extern BOOLEAN mail_is_altblat;
+ extern BOOLEAN mail_is_blat;
+#endif
+
+#if defined(__CYGWIN__)
+#include <io.h>
+#endif
+
+#if !defined(__CYGWIN__) && defined(__CYGWIN32__)
+#define __CYGWIN__
+
+#define cygwin_conv_to_full_win32_path(p, q) \
+ cygwin32_conv_to_full_win32_path(p, q)
+
+#define cygwin_conv_to_full_posix_path(p, q) \
+ cygwin32_conv_to_full_posix_path(p, q)
+#endif
+
+#ifdef __CYGWIN__
+#define ConvertToWin32Path(p, q) \
+ cygwin_conv_to_full_win32_path(p, q);
+#else
+#define ConvertToWin32Path(p, q) \
+ q = p
+#endif
+
+#ifdef USE_SCROLLBAR
+/* GridText.c */
+ extern BOOLEAN LYShowScrollbar;
+ extern BOOLEAN LYsb_arrow;
+ extern int LYsb_begin;
+ extern int LYsb_end;
+#endif
+
+#ifdef MARK_HIDDEN_LINKS
+ extern char *hidden_link_marker;
+#endif
+
+#ifdef USE_BLINK
+ extern BOOLEAN term_blink_is_boldbg;
+#endif
+
+#ifdef __cplusplus
+}
+#endif
+#endif /* LYGLOBALDEFS_H */
diff --git a/src/LYHash.c b/src/LYHash.c
new file mode 100644
index 0000000..f419c7e
--- /dev/null
+++ b/src/LYHash.c
@@ -0,0 +1,144 @@
+/*
+ * $LynxId: LYHash.c,v 1.39 2018/03/29 00:38:59 tom Exp $
+ *
+ * A hash table for the (fake) CSS support in Lynx-rp
+ * (c) 1996 Rob Partington
+ * rewritten 1997 by Klaus Weide.
+ * rewritten 2018 -TD
+ */
+#include <LYHash.h>
+#include <LYUtils.h>
+#include <LYLeaks.h>
+#include <LYStrings.h>
+#include <LYGlobalDefs.h>
+
+#ifdef USE_COLOR_STYLE
+
+#undef HASH_TYPE
+
+#define HASH_SIZE CSHASHSIZE
+#define HASH_TYPE int
+#define HASH_OF(h, v) ((HASH_TYPE)((h) * 3 + UCH(v)) % HASH_SIZE)
+
+static int count_bump;
+static size_t limit;
+static char *buffer;
+
+static char *get_buffer(size_t need)
+{
+ if (++need > limit) {
+ char *test = realloc(buffer, (limit = (1 + need) * 2));
+
+ if (test == 0)
+ outofmem(__FILE__, "LYHash");
+ buffer = test;
+ }
+ return buffer;
+}
+
+/*
+ * This is the same algorithm as the private anchor_hash() in HTAnchor.c, but
+ * with a different value for HASH_SIZE.
+ */
+static HASH_TYPE cs_hash(const char *string)
+{
+ HASH_TYPE hash = 0;
+ HASH_TYPE best, n;
+ bucket *data;
+ const char *p;
+
+ for (p = string; *p; p++)
+ hash = HASH_OF(hash, *p);
+
+ /*
+ * The computed hash-code is only a starting point. Check for collision.
+ */
+ best = hash;
+ for (n = 0; n < HASH_SIZE; n++) {
+ int nn = (n + hash) % HASH_SIZE;
+
+ data = &hashStyles[nn];
+ if (data->name == 0 || !strcmp(string, data->name)) {
+ best = nn;
+ hash = nn;
+ break;
+ }
+ ++count_bump;
+ }
+ data = &hashStyles[best];
+ if (data->name != 0) {
+ if (strcmp(string, data->name)) {
+ CTRACE_STYLE((tfp, "cs_hash(%s) overwriting %d\n", string, data->name));
+ FREE(data->name);
+ StrAllocCopy(data->name, string);
+ }
+ } else {
+ StrAllocCopy(data->name, string);
+ }
+
+ CTRACE_STYLE((tfp, "cs_hash(%s) = %d\n", string, hash));
+ return hash;
+}
+
+int color_style_1(const char *string)
+{
+ int hash;
+
+ if (dump_output_immediately) {
+ hash = 0;
+ } else {
+ get_buffer(strlen(string));
+ strcpy(buffer, string);
+ LYLowerCase(buffer);
+ hash = cs_hash(buffer);
+ }
+ return hash;
+}
+
+int color_style_3(const char *p, const char *q, const char *r)
+{
+ int hash;
+
+ if (dump_output_immediately) {
+ hash = 0;
+ } else {
+ get_buffer(strlen(p) + strlen(q) + strlen(r));
+ strcpy(buffer, p);
+ strcat(buffer, q);
+ strcat(buffer, r);
+ LYLowerCase(buffer);
+ hash = cs_hash(buffer);
+ }
+ return hash;
+}
+
+void report_hashStyles(void)
+{
+ int i;
+ int count_name = 0;
+ int count_used = 0;
+
+ for (i = 0; i < CSHASHSIZE; i++) {
+ count_name += (hashStyles[i].name != 0);
+ count_used += (hashStyles[i].used != 0);
+ }
+ CTRACE((tfp, "Style hash:\n"));
+ CTRACE((tfp, "%5d names allocated\n", count_name));
+ CTRACE((tfp, "%5d buckets used\n", count_used));
+ CTRACE((tfp, "%5d hash collisions\n", count_bump));
+}
+
+void free_hashStyles(void)
+{
+ int i;
+
+ for (i = 0; i < CSHASHSIZE; i++) {
+ FREE(hashStyles[i].name);
+ hashStyles[i].used = FALSE;
+ }
+ FREE(buffer);
+ limit = 0;
+ count_bump = 0;
+}
+
+#endif /* USE_COLOR_STYLE */
diff --git a/src/LYHash.h b/src/LYHash.h
new file mode 100644
index 0000000..e0e369b
--- /dev/null
+++ b/src/LYHash.h
@@ -0,0 +1,67 @@
+/* $LynxId: LYHash.h,v 1.40 2018/03/10 01:47:33 tom Exp $ */
+#ifndef _LYHASH_H_
+#define _LYHASH_H_ 1
+
+#ifndef HTUTILS_H
+#include <HTUtils.h>
+#endif
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+ typedef struct {
+ char *name; /* name of this item */
+ BOOL used; /* color/attributes have been assigned */
+ int color; /* color highlighting to be done */
+ int mono; /* mono highlighting to be done */
+ int cattr; /* attributes to go with the color */
+ } bucket;
+
+#define CSHASHSIZE 9973 /* Arbitrary prime. Memory/speed tradeoff */
+
+#define NOSTYLE -1
+
+ extern bucket hashStyles[CSHASHSIZE];
+ extern bucket *nostyle_bucket(void);
+
+ extern int color_style_1(const char *string);
+ extern int color_style_3(const char *p, const char *q, const char *r);
+ extern void free_hashStyles(void);
+ extern void report_hashStyles(void);
+
+ extern int s_a;
+ extern int s_aedit;
+ extern int s_aedit_arr;
+ extern int s_aedit_pad;
+ extern int s_aedit_sel;
+ extern int s_alert;
+ extern int s_alink;
+ extern int s_curedit;
+ extern int s_forw_backw;
+ extern int s_hot_paste;
+ extern int s_menu_active;
+ extern int s_menu_bg;
+ extern int s_menu_entry;
+ extern int s_menu_frame;
+ extern int s_menu_number;
+ extern int s_menu_sb;
+ extern int s_normal;
+ extern int s_prompt_edit;
+ extern int s_prompt_edit_arr;
+ extern int s_prompt_edit_pad;
+ extern int s_prompt_sel;
+ extern int s_status;
+ extern int s_title;
+ extern int s_whereis;
+
+#ifdef USE_SCROLLBAR
+ extern int s_sb_aa;
+ extern int s_sb_bar;
+ extern int s_sb_bg;
+ extern int s_sb_naa;
+#endif
+
+#ifdef __cplusplus
+}
+#endif
+#endif /* _LYHASH_H_ */
diff --git a/src/LYHistory.c b/src/LYHistory.c
new file mode 100644
index 0000000..4fd5567
--- /dev/null
+++ b/src/LYHistory.c
@@ -0,0 +1,1163 @@
+/*
+ * $LynxId: LYHistory.c,v 1.94 2021/06/09 22:55:43 tom Exp $
+ */
+#include <HTUtils.h>
+#include <HTTP.h>
+#include <GridText.h>
+#include <HTAlert.h>
+#include <HText.h>
+#include <LYGlobalDefs.h>
+#include <LYUtils.h>
+#include <LYHistory.h>
+#include <LYPrint.h>
+#include <LYDownload.h>
+#include <LYOptions.h>
+#include <LYKeymap.h>
+#include <LYList.h>
+#include <LYShowInfo.h>
+#include <LYStrings.h>
+#include <LYCharUtils.h>
+#include <LYCharSets.h>
+#include <LYrcFile.h>
+#ifdef DISP_PARTIAL
+#include <LYMainLoop.h>
+#endif
+
+#ifdef DIRED_SUPPORT
+#include <LYUpload.h>
+#include <LYLocal.h>
+#endif /* DIRED_SUPPORT */
+
+#include <LYexit.h>
+#include <LYLeaks.h>
+#include <HTCJK.h>
+
+HTList *Visited_Links = NULL; /* List of safe popped docs. */
+int Visited_Links_As = VISITED_LINKS_AS_LATEST | VISITED_LINKS_REVERSE;
+
+static VisitedLink *PrevVisitedLink = NULL; /* NULL on auxiliary */
+static VisitedLink *PrevActiveVisitedLink = NULL; /* Last non-auxillary */
+static VisitedLink Latest_first;
+static VisitedLink Latest_last;
+static VisitedLink *Latest_tree;
+static VisitedLink *First_tree;
+static VisitedLink *Last_by_first;
+
+int nhist_extra;
+
+#ifdef LY_FIND_LEAKS
+static int already_registered_free_messages_stack = 0;
+static int already_registered_clean_all_history = 0;
+#endif
+
+#ifdef LY_FIND_LEAKS
+/*
+ * Utility for freeing the list of visited links. - FM
+ */
+static void Visited_Links_free(void)
+{
+ VisitedLink *vl;
+ HTList *cur = Visited_Links;
+
+ PrevVisitedLink = NULL;
+ PrevActiveVisitedLink = NULL;
+ if (!cur)
+ return;
+
+ while (NULL != (vl = (VisitedLink *) HTList_nextObject(cur))) {
+ FREE(vl->address);
+ FREE(vl->title);
+ FREE(vl);
+ }
+ HTList_delete(Visited_Links);
+ Visited_Links = NULL;
+ Latest_last.prev_latest = &Latest_first;
+ Latest_first.next_latest = &Latest_last;
+ Last_by_first = Latest_tree = First_tree = 0;
+ return;
+}
+#endif /* LY_FIND_LEAKS */
+
+#ifdef DEBUG
+static void trace_history(const char *tag)
+{
+ if (TRACE) {
+ CTRACE((tfp, "HISTORY %s %d/%u (%d extra)\n",
+ tag, nhist, size_history, nhist_extra));
+ CTRACE_FLUSH(tfp);
+ }
+}
+#else
+#define trace_history(tag) /* nothing */
+#endif /* DEBUG */
+
+/*
+ * Utility for listing visited links, making any repeated links the most
+ * current in the list. - FM
+ */
+void LYAddVisitedLink(DocInfo *doc)
+{
+ VisitedLink *tmp;
+ HTList *cur;
+ const char *title = (doc->title ? doc->title : NO_TITLE);
+
+ if (isEmpty(doc->address)) {
+ PrevVisitedLink = NULL;
+ return;
+ }
+
+ /*
+ * Exclude POST or HEAD replies, and bookmark, menu or list files. - FM
+ */
+ if (doc->post_data || doc->isHEAD || doc->bookmark ||
+ ( /* special url or a temp file */
+ (!StrNCmp(doc->address, "LYNX", 4) ||
+ !StrNCmp(doc->address, "file://localhost/", 17)))) {
+ int related = 1; /* First approximation only */
+
+ if (LYIsUIPage(doc->address, UIP_HISTORY) ||
+ LYIsUIPage(doc->address, UIP_VLINKS) ||
+ LYIsUIPage(doc->address, UIP_SHOWINFO) ||
+ isLYNXMESSAGES(doc->address) ||
+ ((related = 0) != 0) ||
+#ifdef DIRED_SUPPORT
+ LYIsUIPage(doc->address, UIP_DIRED_MENU) ||
+ LYIsUIPage(doc->address, UIP_UPLOAD_OPTIONS) ||
+ LYIsUIPage(doc->address, UIP_PERMIT_OPTIONS) ||
+#endif /* DIRED_SUPPORT */
+ LYIsUIPage(doc->address, UIP_PRINT_OPTIONS) ||
+ LYIsUIPage(doc->address, UIP_DOWNLOAD_OPTIONS) ||
+ LYIsUIPage(doc->address, UIP_OPTIONS_MENU) ||
+ isLYNXEDITMAP(doc->address) ||
+ isLYNXKEYMAP(doc->address) ||
+ LYIsUIPage(doc->address, UIP_LIST_PAGE) ||
+#ifdef USE_ADDRLIST_PAGE
+ LYIsUIPage(doc->address, UIP_ADDRLIST_PAGE) ||
+#endif
+ LYIsUIPage(doc->address, UIP_CONFIG_DEF) ||
+ LYIsUIPage(doc->address, UIP_LYNXCFG) ||
+ isLYNXCOOKIE(doc->address) ||
+ LYIsUIPage(doc->address, UIP_TRACELOG)) {
+ if (!related)
+ PrevVisitedLink = NULL;
+ return;
+ }
+ }
+
+ if (!Visited_Links) {
+ Visited_Links = HTList_new();
+#ifdef LY_FIND_LEAKS
+ atexit(Visited_Links_free);
+#endif
+ Latest_last.prev_latest = &Latest_first;
+ Latest_first.next_latest = &Latest_last;
+ Latest_last.next_latest = NULL; /* Find bugs quick! */
+ Latest_first.prev_latest = NULL;
+ Last_by_first = Latest_tree = First_tree = NULL;
+ }
+
+ cur = Visited_Links;
+ while (NULL != (tmp = (VisitedLink *) HTList_nextObject(cur))) {
+ if (!strcmp(NonNull(tmp->address),
+ NonNull(doc->address))) {
+ PrevVisitedLink = PrevActiveVisitedLink = tmp;
+ /* Already visited. Update the last-visited info. */
+ if (tmp->next_latest == &Latest_last) /* optimization */
+ return;
+
+ /* Remove from "latest" chain */
+ tmp->prev_latest->next_latest = tmp->next_latest;
+ tmp->next_latest->prev_latest = tmp->prev_latest;
+
+ /* Insert at the end of the "latest" chain */
+ Latest_last.prev_latest->next_latest = tmp;
+ tmp->prev_latest = Latest_last.prev_latest;
+ tmp->next_latest = &Latest_last;
+ Latest_last.prev_latest = tmp;
+ return;
+ }
+ }
+
+ if ((tmp = typecalloc(VisitedLink)) == NULL)
+ outofmem(__FILE__, "LYAddVisitedLink");
+
+ StrAllocCopy(tmp->address, doc->address);
+ LYformTitle(&(tmp->title), title);
+
+ /* First-visited chain */
+ HTList_appendObject(Visited_Links, tmp); /* At end */
+ tmp->prev_first = Last_by_first;
+ Last_by_first = tmp;
+
+ /* Tree structure */
+ if (PrevVisitedLink) {
+ VisitedLink *a = PrevVisitedLink;
+ VisitedLink *b = a->next_tree;
+ int l = PrevVisitedLink->level;
+
+ /* Find last on the deeper levels */
+ while (b && b->level > l)
+ a = b, b = b->next_tree;
+
+ if (!b) /* a == Latest_tree */
+ Latest_tree = tmp;
+ tmp->next_tree = a->next_tree;
+ a->next_tree = tmp;
+
+ tmp->level = PrevVisitedLink->level + 1;
+ } else {
+ if (Latest_tree)
+ Latest_tree->next_tree = tmp;
+ tmp->level = 0;
+ tmp->next_tree = NULL;
+ Latest_tree = tmp;
+ }
+ PrevVisitedLink = PrevActiveVisitedLink = tmp;
+ if (!First_tree)
+ First_tree = tmp;
+
+ /* "latest" chain */
+ Latest_last.prev_latest->next_latest = tmp;
+ tmp->prev_latest = Latest_last.prev_latest;
+ tmp->next_latest = &Latest_last;
+ Latest_last.prev_latest = tmp;
+
+ return;
+}
+
+/*
+ * Returns true if this is a page that we would push onto the stack if not
+ * forced. If docurl is NULL, only the title is considered; otherwise also
+ * check the URL whether it is (likely to be) a generated special page.
+ */
+BOOLEAN LYwouldPush(const char *title,
+ const char *docurl)
+{
+ BOOLEAN rc = FALSE;
+
+ /*
+ * All non-pushable generated pages have URLs that begin with
+ * "file://localhost/" and end with HTML_SUFFIX. - kw
+ */
+ if (docurl) {
+ size_t ulen;
+
+ if (StrNCmp(docurl, "file://localhost/", 17) != 0 ||
+ (ulen = strlen(docurl)) <= strlen(HTML_SUFFIX) ||
+ strcmp(docurl + ulen - strlen(HTML_SUFFIX), HTML_SUFFIX) != 0) {
+ /*
+ * If it is not a local HTML file, it may be a Web page that
+ * accidentally has the same title. So return TRUE now. - kw
+ */
+ return TRUE;
+ }
+ }
+
+ if (docurl) {
+ rc = (BOOLEAN)
+ !(LYIsUIPage(docurl, UIP_HISTORY)
+ || LYIsUIPage(docurl, UIP_PRINT_OPTIONS)
+#ifdef DIRED_SUPPORT
+ || LYIsUIPage(docurl, UIP_DIRED_MENU)
+ || LYIsUIPage(docurl, UIP_UPLOAD_OPTIONS)
+ || LYIsUIPage(docurl, UIP_PERMIT_OPTIONS)
+#endif /* DIRED_SUPPORT */
+ );
+ } else {
+ rc = (BOOLEAN)
+ !(!strcmp(title, HISTORY_PAGE_TITLE)
+ || !strcmp(title, PRINT_OPTIONS_TITLE)
+#ifdef DIRED_SUPPORT
+ || !strcmp(title, DIRED_MENU_TITLE)
+ || !strcmp(title, UPLOAD_OPTIONS_TITLE)
+ || !strcmp(title, PERMIT_OPTIONS_TITLE)
+#endif /* DIRED_SUPPORT */
+ );
+ }
+ return rc;
+}
+
+/*
+ * Free post-data for 'DocInfo'
+ */
+void LYFreePostData(DocInfo *doc)
+{
+ BStrFree(doc->post_data);
+ FREE(doc->post_content_type);
+}
+
+/*
+ * Free strings associated with a 'DocInfo' struct.
+ */
+void LYFreeDocInfo(DocInfo *doc)
+{
+ FREE(doc->title);
+ FREE(doc->address);
+ FREE(doc->bookmark);
+ LYFreePostData(doc);
+}
+
+/*
+ * Free the information in the last history entry.
+ */
+static void clean_extra_history(void)
+{
+ trace_history("clean_extra_history");
+ nhist += nhist_extra;
+ while (nhist_extra > 0) {
+ nhist--;
+ LYFreeDocInfo(&HDOC(nhist));
+ nhist_extra--;
+ }
+ trace_history("...clean_extra_history");
+}
+
+/*
+ * Free the entire history stack, for auditing memory leaks.
+ */
+#ifdef LY_FIND_LEAKS
+static void clean_all_history(void)
+{
+ trace_history("clean_all_history");
+ clean_extra_history();
+ while (nhist > 0) {
+ nhist--;
+ LYFreeDocInfo(&HDOC(nhist));
+ }
+ trace_history("...clean_all_history");
+}
+#endif
+
+/* FIXME What is the relationship to are_different() from the mainloop?! */
+static int are_identical(HistInfo * doc, DocInfo *doc1)
+{
+ return (STREQ(doc1->address, doc->hdoc.address)
+ && BINEQ(doc1->post_data, doc->hdoc.post_data)
+ && !strcmp(NonNull(doc1->bookmark),
+ NonNull(doc->hdoc.bookmark))
+ && doc1->isHEAD == doc->hdoc.isHEAD);
+}
+
+void LYAllocHistory(unsigned entries)
+{
+ CTRACE((tfp, "LYAllocHistory %u vs %u\n", entries, size_history));
+ if (entries + 1 >= size_history) {
+ size_t want;
+ unsigned save = size_history;
+
+ size_history += (entries + 2) * 2;
+ want = ((size_t) size_history) * sizeof(*history);
+
+ if (history == 0) {
+ history = typecallocn(HistInfo, want);
+ } else {
+ history = typeRealloc(HistInfo, history, want);
+ memset(&history[save], 0, size_history - save);
+ }
+ if (history == 0)
+ outofmem(__FILE__, "LYAllocHistory");
+ }
+ CTRACE((tfp, "...LYAllocHistory %u vs %u\n", entries, size_history));
+}
+
+/*
+ * Push the current filename, link and line number onto the history list.
+ */
+int LYpush(DocInfo *doc, int force_push)
+{
+ /*
+ * Don't push NULL file names.
+ */
+ if (*doc->address == '\0')
+ return 0;
+
+ /*
+ * Check whether this is a document we don't push unless forced. - FM
+ */
+ if (!force_push) {
+ /*
+ * Don't push the history, printer, or download lists.
+ */
+ if (!LYwouldPush(doc->title, doc->address)) {
+ if (!LYforce_no_cache)
+ LYoverride_no_cache = TRUE;
+ return 0;
+ }
+ }
+
+ /*
+ * If file is identical to one before it, don't push it.
+ * But do not duplicate it if there is only one on the stack,
+ * note that HDOC() starts from 0, so nhist should be > 0.
+ */
+ if (nhist >= 1 && are_identical(&(history[nhist - 1]), doc)) {
+ if (HDOC(nhist - 1).internal_link == doc->internal_link) {
+ /* But it is nice to have the last position remembered!
+ - kw */
+ HDOC(nhist - 1).link = doc->link;
+ HDOC(nhist - 1).line = doc->line;
+ return 0;
+ }
+ }
+
+ /*
+ * If file is identical to the current document, just move the pointer.
+ */
+ if (nhist_extra >= 1 && are_identical(&(history[nhist]), doc)) {
+ HDOC(nhist).link = doc->link;
+ HDOC(nhist).line = doc->line;
+ nhist_extra--;
+ LYAllocHistory((unsigned) nhist);
+ nhist++;
+ trace_history("LYpush: just move the cursor");
+ return 1;
+ }
+
+ clean_extra_history();
+#ifdef LY_FIND_LEAKS
+ if (!already_registered_clean_all_history) {
+ already_registered_clean_all_history = 1;
+ atexit(clean_all_history);
+ }
+#endif
+
+ /*
+ * OK, push it...
+ */
+ LYAllocHistory((unsigned) nhist);
+ HDOC(nhist).link = doc->link;
+ HDOC(nhist).line = doc->line;
+
+ HDOC(nhist).title = NULL;
+ LYformTitle(&(HDOC(nhist).title), doc->title);
+
+ HDOC(nhist).address = NULL;
+ StrAllocCopy(HDOC(nhist).address, doc->address);
+
+ HDOC(nhist).post_data = NULL;
+ BStrCopy(HDOC(nhist).post_data, doc->post_data);
+
+ HDOC(nhist).post_content_type = NULL;
+ StrAllocCopy(HDOC(nhist).post_content_type, doc->post_content_type);
+
+ HDOC(nhist).bookmark = NULL;
+ StrAllocCopy(HDOC(nhist).bookmark, doc->bookmark);
+
+ HDOC(nhist).isHEAD = doc->isHEAD;
+ HDOC(nhist).safe = doc->safe;
+
+ HDOC(nhist).internal_link = FALSE; /* by default */
+ history[nhist].intern_seq_start = -1; /* by default */
+ if (doc->internal_link) {
+ /* Now some tricky stuff: if the caller thinks that the doc
+ to push was the result of following an internal
+ (fragment) link, we check whether we believe it.
+ It is only accepted as valid if the immediately preceding
+ item on the history stack is actually the same document
+ except for fragment and location info. I.e. the Parent
+ Anchors are the same.
+ Also of course this requires that this is not the first
+ history item. - kw */
+ if (nhist > 0) {
+ DocAddress WWWDoc;
+ HTParentAnchor *thisparent, *thatparent = NULL;
+
+ WWWDoc.address = doc->address;
+ WWWDoc.post_data = doc->post_data;
+ WWWDoc.post_content_type = doc->post_content_type;
+ WWWDoc.bookmark = doc->bookmark;
+ WWWDoc.isHEAD = doc->isHEAD;
+ WWWDoc.safe = doc->safe;
+ thisparent =
+ HTAnchor_findAddress(&WWWDoc);
+ /* Now find the ParentAnchor for the previous history
+ * item - kw
+ */
+ if (thisparent) {
+ /* If the last-pushed item is a LYNXIMGMAP but THIS one
+ * isn't, compare the physical URLs instead. - kw
+ */
+ if (isLYNXIMGMAP(HDOC(nhist - 1).address) &&
+ !isLYNXIMGMAP(doc->address)) {
+ WWWDoc.address = HDOC(nhist - 1).address + LEN_LYNXIMGMAP;
+ /*
+ * If THIS item is a LYNXIMGMAP but the last-pushed one
+ * isn't, fake it by using THIS item's address for
+ * thatparent... - kw
+ */
+ } else if (isLYNXIMGMAP(doc->address) &&
+ !isLYNXIMGMAP(HDOC(nhist - 1).address)) {
+ char *temp = NULL;
+
+ StrAllocCopy(temp, STR_LYNXIMGMAP);
+ StrAllocCat(temp, doc->address + LEN_LYNXIMGMAP);
+ WWWDoc.address = temp;
+ 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;
+ thatparent =
+ HTAnchor_findAddress(&WWWDoc);
+ FREE(temp);
+ } else {
+ WWWDoc.address = HDOC(nhist - 1).address;
+ }
+ if (!thatparent) { /* if not yet done */
+ 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;
+ thatparent =
+ HTAnchor_findAddress(&WWWDoc);
+ }
+ /* In addition to equality of the ParentAnchors, require
+ * that IF we have a HTMainText (i.e., it wasn't just
+ * HTuncache'd by mainloop), THEN it has to be consistent
+ * with what we are trying to push.
+ *
+ * This may be overkill... - kw
+ */
+ if (thatparent == thisparent &&
+ (!HTMainText || HTMainAnchor == thisparent)
+ ) {
+ HDOC(nhist).internal_link = TRUE;
+ history[nhist].intern_seq_start =
+ history[nhist - 1].intern_seq_start >= 0 ?
+ history[nhist - 1].intern_seq_start : nhist - 1;
+ CTRACE((tfp, "\nLYpush: pushed as internal link, OK\n"));
+ }
+ }
+ }
+ if (!HDOC(nhist).internal_link) {
+ CTRACE((tfp, "\nLYpush: push as internal link requested, %s\n",
+ "but didn't check out!"));
+ }
+ }
+ CTRACE((tfp, "\nLYpush[%d]: address:%s\n title:%s\n",
+ nhist, doc->address, doc->title));
+ nhist++;
+ return 1;
+}
+
+/*
+ * Pop the previous filename, link and line number from the history list.
+ */
+void LYpop(DocInfo *doc)
+{
+ if (nhist > 0) {
+ clean_extra_history();
+ nhist--;
+
+ LYFreeDocInfo(doc);
+
+ *doc = HDOC(nhist);
+
+#ifdef DISP_PARTIAL
+ /* assume we pop the 'doc' to show it soon... */
+ LYSetNewline(doc->line); /* reinitialize */
+#endif /* DISP_PARTIAL */
+ CTRACE((tfp, "LYpop[%d]: address:%s\n title:%s\n",
+ nhist, doc->address, doc->title));
+ }
+}
+
+/*
+ * Move to the previous filename, link and line number from the history list.
+ */
+void LYhist_prev(DocInfo *doc)
+{
+ trace_history("LYhist_prev");
+ if (nhist > 0 && (nhist_extra || (unsigned) nhist < size_history)) {
+ nhist--;
+ nhist_extra++;
+ LYpop_num(nhist, doc);
+ trace_history("...LYhist_prev");
+ }
+}
+
+/*
+ * Called before calling LYhist_prev().
+ */
+void LYhist_prev_register(DocInfo *doc)
+{
+ trace_history("LYhist_prev_register");
+ if (nhist > 1) {
+ if (nhist_extra) { /* Make something to return back */
+ /* Store the new position */
+ HDOC(nhist).link = doc->link;
+ HDOC(nhist).line = doc->line;
+ } else if (LYpush(doc, 0)) {
+ nhist--;
+ nhist_extra++;
+ }
+ trace_history("...LYhist_prev_register");
+ }
+}
+
+/*
+ * Move to the next filename, link and line number from the history.
+ */
+int LYhist_next(DocInfo *doc, DocInfo *newdoc)
+{
+ if (nhist_extra <= 1) /* == 1 when we are the last one */
+ return 0;
+ /* Store the new position */
+ HDOC(nhist).link = doc->link;
+ HDOC(nhist).line = doc->line;
+ LYAllocHistory((unsigned) nhist);
+ nhist++;
+ nhist_extra--;
+ LYpop_num(nhist, newdoc);
+ return 1;
+}
+
+/*
+ * Pop the specified hist entry, link and line number from the history list but
+ * don't actually remove the entry, just return it.
+ * (This procedure is badly named :)
+ */
+void LYpop_num(int number,
+ DocInfo *doc)
+{
+ if (number >= 0 && (nhist + nhist_extra) > number) {
+ doc->link = HDOC(number).link;
+ doc->line = HDOC(number).line;
+ StrAllocCopy(doc->title, HDOC(number).title);
+ StrAllocCopy(doc->address, HDOC(number).address);
+ BStrCopy(doc->post_data, HDOC(number).post_data);
+ StrAllocCopy(doc->post_content_type, HDOC(number).post_content_type);
+ StrAllocCopy(doc->bookmark, HDOC(number).bookmark);
+ doc->isHEAD = HDOC(number).isHEAD;
+ doc->safe = HDOC(number).safe;
+ doc->internal_link = HDOC(number).internal_link; /* ?? */
+#ifdef DISP_PARTIAL
+ /* assume we pop the 'doc' to show it soon... */
+ LYSetNewline(doc->line); /* reinitialize */
+#endif /* DISP_PARTIAL */
+ if (TRACE) {
+ CTRACE((tfp, "LYpop_num(%d)\n", number));
+ CTRACE((tfp, " link %d\n", doc->link));
+ CTRACE((tfp, " line %d\n", doc->line));
+ CTRACE((tfp, " title %s\n", NonNull(doc->title)));
+ CTRACE((tfp, " address %s\n", NonNull(doc->address)));
+ }
+ }
+}
+
+/*
+ * This procedure outputs the history buffer into a temporary file.
+ */
+int showhistory(char **newfile)
+{
+ static char tempfile[LY_MAXPATH] = "\0";
+ char *Title = NULL;
+ int x = 0;
+ FILE *fp0;
+
+ if ((fp0 = InternalPageFP(tempfile, TRUE)) == 0)
+ return (-1);
+
+ LYLocalFileToURL(newfile, tempfile);
+
+ LYforce_HTML_mode = TRUE; /* force this file to be HTML */
+ LYforce_no_cache = TRUE; /* force this file to be new */
+
+ BeginInternalPage(fp0, HISTORY_PAGE_TITLE, HISTORY_PAGE_HELP);
+
+ fprintf(fp0, "<p align=right> <a href=\"%s\">[%s]</a>\n",
+ STR_LYNXMESSAGES, STATUSLINES_TITLE);
+
+ fprintf(fp0, "<pre>\n");
+
+ fprintf(fp0, "<em>%s</em>\n", gettext("You selected:"));
+ for (x = nhist + nhist_extra - 1; x >= 0; x--) {
+ /*
+ * The number of the document in the hist stack, its title in a link,
+ * and its address. - FM
+ */
+ if (HDOC(x).title != NULL) {
+ StrAllocCopy(Title, HDOC(x).title);
+ LYEntify(&Title, TRUE);
+ LYTrimLeading(Title);
+ LYTrimTrailing(Title);
+ if (*Title == '\0')
+ StrAllocCopy(Title, NO_TITLE);
+ } else {
+ StrAllocCopy(Title, NO_TITLE);
+ }
+ fprintf(fp0,
+ "%s<em>%d</em>. <tab id=t%d><a href=\"%s%d\">%s</a>\n",
+ (x > 99 ? "" : x < 10 ? " " : " "),
+ x, x, STR_LYNXHIST, x, Title);
+ if (HDOC(x).address != NULL) {
+ StrAllocCopy(Title, HDOC(x).address);
+ LYEntify(&Title, TRUE);
+ } else {
+ StrAllocCopy(Title, gettext("(no address)"));
+ }
+ if (HDOC(x).internal_link) {
+ if (history[x].intern_seq_start == history[nhist - 1].intern_seq_start)
+ StrAllocCat(Title, gettext(" (internal)"));
+ else
+ StrAllocCat(Title, gettext(" (was internal)"));
+ }
+ fprintf(fp0, "<tab to=t%d>%s\n", x, Title);
+ }
+ fprintf(fp0, "</pre>\n");
+ EndInternalPage(fp0);
+
+ LYCloseTempFP(fp0);
+ FREE(Title);
+ return (0);
+}
+
+/*
+ * This function makes the history page seem like any other type of file since
+ * more info is needed than can be provided by the normal link structure. We
+ * saved out the history number to a special URL.
+ *
+ * The info looks like: LYNXHIST:#
+ */
+BOOLEAN historytarget(DocInfo *newdoc)
+{
+ int number;
+ DocAddress WWWDoc;
+ HTParentAnchor *tmpanchor;
+ HText *text;
+ BOOLEAN treat_as_intern = FALSE;
+
+ if ((!newdoc || !newdoc->address) ||
+ strlen(newdoc->address) < 10 || !isdigit(UCH(*(newdoc->address + 9))))
+ return (FALSE);
+
+ if ((number = atoi(newdoc->address + 9)) > nhist + nhist_extra || number < 0)
+ return (FALSE);
+
+ /*
+ * Optimization: assume we came from the History Page,
+ * so never return back - always a new version next time.
+ * But check first whether HTMainText is really the History
+ * Page document - in some obscure situations this may not be
+ * the case. If HTMainText seems to be a History Page document,
+ * also check that it really hasn't been pushed. - LP, kw
+ */
+ if (HTMainText && nhist > 0 &&
+ !strcmp(HTLoadedDocumentTitle(), HISTORY_PAGE_TITLE) &&
+ LYIsUIPage3(HTLoadedDocumentURL(), UIP_HISTORY, 0) &&
+ strcmp(HTLoadedDocumentURL(), HDOC(nhist - 1).address)) {
+ HTuncache_current_document(); /* don't waste the cache */
+ }
+
+ LYpop_num(number, newdoc);
+ if (((newdoc->internal_link &&
+ history[number].intern_seq_start == history[nhist - 1].intern_seq_start)
+ || (number < nhist - 1 &&
+ HDOC(nhist - 1).internal_link &&
+ number == history[nhist - 1].intern_seq_start))
+ && !(LYforce_no_cache == TRUE && LYoverride_no_cache == FALSE)) {
+ if (track_internal_links) {
+ LYforce_no_cache = FALSE;
+ LYinternal_flag = TRUE;
+ newdoc->internal_link = TRUE;
+ treat_as_intern = TRUE;
+ }
+ } else {
+ newdoc->internal_link = FALSE;
+ }
+ /*
+ * If we have POST content, and have LYresubmit_posts set or have no_cache
+ * set or do not still have the text cached, ask the user whether to
+ * resubmit the form. - FM
+ */
+ if (newdoc->post_data != NULL) {
+ 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);
+ text = (HText *) HTAnchor_document(tmpanchor);
+ if (((((LYresubmit_posts == TRUE) ||
+ (LYforce_no_cache == TRUE &&
+ LYoverride_no_cache == FALSE)) &&
+ !(treat_as_intern && !reloading)) ||
+ text == NULL) &&
+ (isLYNXIMGMAP(newdoc->address) ||
+ HTConfirm(CONFIRM_POST_RESUBMISSION) == TRUE)) {
+ LYforce_no_cache = TRUE;
+ LYoverride_no_cache = FALSE;
+ } else if (text != NULL) {
+ LYforce_no_cache = FALSE;
+ LYoverride_no_cache = TRUE;
+ } else {
+ HTInfoMsg(CANCELLED);
+ return (FALSE);
+ }
+ }
+
+ if (number != 0)
+ StrAllocCat(newdoc->title, gettext(" (From History)"));
+ return (TRUE);
+}
+
+/*
+ * This procedure outputs the Visited Links list into a temporary file. - FM
+ * Returns links's number to make active (1-based), or 0 if not required.
+ */
+int LYShowVisitedLinks(char **newfile)
+{
+ static char tempfile[LY_MAXPATH] = "\0";
+ char *Title = NULL;
+ char *Address = NULL;
+ int x, tot;
+ FILE *fp0;
+ VisitedLink *vl;
+ HTList *cur = Visited_Links;
+ int offset;
+ int ret = 0;
+ const char *arrow, *post_arrow;
+
+ if (!cur)
+ return (-1);
+
+ if ((fp0 = InternalPageFP(tempfile, TRUE)) == 0)
+ return (-1);
+
+ LYLocalFileToURL(newfile, tempfile);
+ LYRegisterUIPage(*newfile, UIP_VLINKS);
+
+ LYforce_HTML_mode = TRUE; /* force this file to be HTML */
+ LYforce_no_cache = TRUE; /* force this file to be new */
+
+ BeginInternalPage(fp0, VISITED_LINKS_TITLE, VISITED_LINKS_HELP);
+
+#ifndef NO_OPTION_FORMS
+ fprintf(fp0, "<form action=\"%s\" method=\"post\">\n", STR_LYNXOPTIONS);
+ LYMenuVisitedLinks(fp0, FALSE);
+ fprintf(fp0, "<input type=\"submit\" value=\"Accept Changes\">\n");
+ fprintf(fp0, "</form>\n");
+ fprintf(fp0, "<P>\n");
+#endif
+
+ fprintf(fp0, "<pre>\n");
+ fprintf(fp0, "<em>%s</em>\n",
+ gettext("You visited (POSTs, bookmark, menu and list files excluded):"));
+ if (Visited_Links_As & VISITED_LINKS_REVERSE)
+ tot = x = HTList_count(Visited_Links);
+ else
+ tot = x = -1;
+
+ if (Visited_Links_As & VISITED_LINKS_AS_TREE) {
+ vl = First_tree;
+ } else if (Visited_Links_As & VISITED_LINKS_AS_LATEST) {
+ if (Visited_Links_As & VISITED_LINKS_REVERSE)
+ vl = Latest_last.prev_latest;
+ else
+ vl = Latest_first.next_latest;
+ if (vl == &Latest_last || vl == &Latest_first)
+ vl = NULL;
+ } else {
+ if (Visited_Links_As & VISITED_LINKS_REVERSE)
+ vl = Last_by_first;
+ else
+ vl = (VisitedLink *) HTList_nextObject(cur);
+ }
+ while (NULL != vl) {
+ /*
+ * The number of the document (most recent highest), its title in a
+ * link, and its address. - FM
+ */
+ post_arrow = arrow = "";
+ if (Visited_Links_As & VISITED_LINKS_REVERSE)
+ x--;
+ else
+ x++;
+ if (vl == PrevActiveVisitedLink) {
+ if (Visited_Links_As & VISITED_LINKS_REVERSE)
+ ret = tot - x + 2;
+ else
+ ret = x + 3;
+ }
+ if (vl == PrevActiveVisitedLink) {
+ post_arrow = "<A NAME=current></A>";
+ /* Otherwise levels 0 and 1 look the same when with arrow: */
+ arrow = (vl->level && (Visited_Links_As & VISITED_LINKS_AS_TREE))
+ ? "==>" : "=>";
+ StrAllocCat(*newfile, "#current");
+ }
+ if (Visited_Links_As & VISITED_LINKS_AS_TREE) {
+ offset = 2 * vl->level;
+ if (offset > 24)
+ offset = (offset + 24) / 2;
+ if (offset > LYcols * 3 / 4)
+ offset = LYcols * 3 / 4;
+ } else
+ offset = (x > 99 ? 0 : x < 10 ? 2 : 1);
+ if (non_empty(vl->title)) {
+ StrAllocCopy(Title, vl->title);
+ LYEntify(&Title, TRUE);
+ LYTrimLeading(Title);
+ LYTrimTrailing(Title);
+ if (*Title == '\0')
+ StrAllocCopy(Title, NO_TITLE);
+ } else {
+ StrAllocCopy(Title, NO_TITLE);
+ }
+ if (non_empty(vl->address)) {
+ StrAllocCopy(Address, vl->address);
+ LYEntify(&Address, FALSE);
+ fprintf(fp0,
+ "%-*s%s<em>%d</em>. <tab id=t%d><a href=\"%s\">%s</a>\n",
+ offset, arrow, post_arrow,
+ x, x, Address, Title);
+ } else {
+ fprintf(fp0,
+ "%-*s%s<em>%d</em>. <tab id=t%d><em>%s</em>\n",
+ offset, arrow, post_arrow,
+ x, x, Title);
+ }
+ if (Address != NULL) {
+ StrAllocCopy(Address, vl->address);
+ LYEntify(&Address, TRUE);
+ }
+ fprintf(fp0, "<tab to=t%d>%s\n", x,
+ ((Address != NULL) ? Address : gettext("(no address)")));
+ if (Visited_Links_As & VISITED_LINKS_AS_TREE)
+ vl = vl->next_tree;
+ else if (Visited_Links_As & VISITED_LINKS_AS_LATEST) {
+ if (Visited_Links_As & VISITED_LINKS_REVERSE)
+ vl = vl->prev_latest;
+ else
+ vl = vl->next_latest;
+ if (vl == &Latest_last || vl == &Latest_first)
+ vl = NULL;
+ } else {
+ if (Visited_Links_As & VISITED_LINKS_REVERSE)
+ vl = vl->prev_first;
+ else
+ vl = (VisitedLink *) HTList_nextObject(cur);
+ }
+ }
+ fprintf(fp0, "</pre>\n");
+ EndInternalPage(fp0);
+
+ LYCloseTempFP(fp0);
+ FREE(Title);
+ FREE(Address);
+ return (ret);
+}
+
+/*
+ * Keep cycled buffer for statusline messages.
+ * But allow user to change how big it will be from userdefs.h
+ */
+#ifndef STATUSBUFSIZE
+#define STATUSBUFSIZE 40
+#endif
+
+int status_buf_size = STATUSBUFSIZE;
+
+static char **buffstack;
+static int topOfStack = 0;
+
+#ifdef LY_FIND_LEAKS
+static void free_messages_stack(void)
+{
+ if (buffstack != 0) {
+ topOfStack = status_buf_size;
+
+ while (--topOfStack >= 0) {
+ FREE(buffstack[topOfStack]);
+ }
+ FREE(buffstack);
+ }
+}
+#endif
+
+static void to_stack(char *str)
+{
+ /*
+ * Cycle buffer:
+ */
+ if (topOfStack >= status_buf_size) {
+ topOfStack = 0;
+ }
+
+ /*
+ * Register string.
+ */
+ if (buffstack == 0)
+ buffstack = typecallocn(char *, (size_t) status_buf_size);
+
+ FREE(buffstack[topOfStack]);
+ buffstack[topOfStack] = str;
+ topOfStack++;
+#ifdef LY_FIND_LEAKS
+ if (!already_registered_free_messages_stack) {
+ already_registered_free_messages_stack = 1;
+ atexit(free_messages_stack);
+ }
+#endif
+ if (topOfStack >= status_buf_size) {
+ topOfStack = 0;
+ }
+}
+
+/*
+ * Dump statusline messages into the buffer.
+ * Called from mainloop() when exit immediately with an error:
+ * can not access startfile (first_file) so a couple of alert messages
+ * will be very useful on exit.
+ * (Don't expect everyone will look a trace log in case of difficulties:))
+ */
+void LYstatusline_messages_on_exit(char **buf)
+{
+ int i;
+
+ if (buffstack != 0) {
+ StrAllocCat(*buf, "\n");
+ /* print messages in chronological order:
+ * probably a single message but let's do it.
+ */
+ i = topOfStack - 1;
+ while (++i < status_buf_size) {
+ if (buffstack[i] != NULL) {
+ StrAllocCat(*buf, buffstack[i]);
+ StrAllocCat(*buf, "\n");
+ }
+ }
+ i = -1;
+ while (++i < topOfStack) {
+ if (buffstack[i] != NULL) {
+ StrAllocCat(*buf, buffstack[i]);
+ StrAllocCat(*buf, "\n");
+ }
+ }
+ }
+}
+
+void LYstore_message2(const char *message,
+ const char *argument)
+{
+
+ if (message != NULL) {
+ char *temp = NULL;
+
+ HTSprintf0(&temp, message, NonNull(argument));
+ to_stack(temp);
+ }
+}
+
+void LYstore_message(const char *message)
+{
+ if (message != NULL) {
+ char *temp = NULL;
+
+ StrAllocCopy(temp, message);
+ to_stack(temp);
+ }
+}
+
+/* LYLoadMESSAGES
+ * --------------
+ * Create a text/html stream with a list of recent statusline messages.
+ * LYNXMESSAGES:/ internal page.
+ * [implementation based on LYLoadKeymap()].
+ */
+
+static int LYLoadMESSAGES(const char *arg GCC_UNUSED,
+ HTParentAnchor *anAnchor,
+ HTFormat format_out,
+ HTStream *sink)
+{
+ HTFormat format_in = WWW_HTML;
+ HTStream *target = NULL;
+ char *buf = NULL;
+ int nummsg = 0;
+
+ int i;
+ char *temp = NULL;
+
+ if (buffstack != 0) {
+ i = status_buf_size;
+ while (--i >= 0) {
+ if (buffstack[i] != NULL)
+ nummsg++;
+ }
+ }
+
+ /*
+ * Set up the stream. - FM
+ */
+ 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);
+ }
+ anAnchor->no_cache = TRUE;
+
+#define PUTS(buf) (*target->isa->put_block)(target, buf, (int) strlen(buf))
+
+ HTSprintf0(&buf, "<html>\n<head>\n");
+ PUTS(buf);
+ /*
+ * This page is a list of messages in display character set.
+ */
+ HTSprintf0(&buf, "<META %s content=\"" STR_HTML ";charset=%s\">\n",
+ "http-equiv=\"content-type\"",
+ LYCharSet_UC[current_char_set].MIMEname);
+ PUTS(buf);
+ HTSprintf0(&buf, "<title>%s</title>\n</head>\n<body>\n",
+ STATUSLINES_TITLE);
+ PUTS(buf);
+
+ if (nummsg != 0) {
+ HTSprintf0(&buf, "<ol>\n");
+ PUTS(buf);
+ /* print messages in reverse order: */
+ i = topOfStack;
+ while (--i >= 0) {
+ if (buffstack[i] != NULL) {
+ StrAllocCopy(temp, buffstack[i]);
+ LYEntify(&temp, TRUE);
+ HTSprintf0(&buf, "<li value=%d> <em>%s</em>\n", nummsg, temp);
+ nummsg--;
+ PUTS(buf);
+ }
+ }
+ i = status_buf_size;
+ while (--i >= topOfStack) {
+ if (buffstack[i] != NULL) {
+ StrAllocCopy(temp, buffstack[i]);
+ LYEntify(&temp, TRUE);
+ HTSprintf0(&buf, "<li value=%d> <em>%s</em>\n", nummsg, temp);
+ nummsg--;
+ PUTS(buf);
+ }
+ }
+ FREE(temp);
+ HTSprintf0(&buf, "</ol>\n</body>\n</html>\n");
+ } else {
+ HTSprintf0(&buf, "<p>%s\n</body>\n</html>\n",
+ gettext("(No messages yet)"));
+ }
+ PUTS(buf);
+
+ (*target->isa->_free) (target);
+ FREE(buf);
+ return (HT_LOADED);
+}
+
+#ifdef GLOBALDEF_IS_MACRO
+#define _LYMESSAGES_C_GLOBALDEF_1_INIT { "LYNXMESSAGES", LYLoadMESSAGES, 0}
+GLOBALDEF(HTProtocol, LYLynxStatusMessages, _LYMESSAGES_C_GLOBALDEF_1_INIT);
+#else
+GLOBALDEF HTProtocol LYLynxStatusMessages =
+{"LYNXMESSAGES", LYLoadMESSAGES, 0};
+#endif /* GLOBALDEF_IS_MACRO */
diff --git a/src/LYHistory.h b/src/LYHistory.h
new file mode 100644
index 0000000..a8f2b1c
--- /dev/null
+++ b/src/LYHistory.h
@@ -0,0 +1,39 @@
+/*
+ * $LynxId: LYHistory.h,v 1.22 2021/06/09 22:33:06 tom Exp $
+ */
+#ifndef LYHISTORY_H
+#define LYHISTORY_H
+
+#ifndef LYSTRUCTS_H
+#include <LYStructs.h>
+#endif /* LYSTRUCTS_H */
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+ extern BOOLEAN LYwouldPush(const char *title, const char *docurl);
+ extern BOOLEAN historytarget(DocInfo *newdoc);
+ extern int LYShowVisitedLinks(char **newfile);
+ extern int LYhist_next(DocInfo *doc, DocInfo *newdoc);
+ extern int LYpush(DocInfo *doc, int force_push);
+ extern int showhistory(char **newfile);
+ extern void LYAddVisitedLink(DocInfo *doc);
+ extern void LYAllocHistory(unsigned entries);
+ extern void LYFreePostData(DocInfo *data);
+ extern void LYFreeDocInfo(DocInfo *data);
+ extern void LYhist_prev(DocInfo *doc);
+ extern void LYhist_prev_register(DocInfo *doc);
+ extern void LYpop(DocInfo *doc);
+ extern void LYpop_num(int number, DocInfo *doc);
+ extern void LYstatusline_messages_on_exit(char **buf);
+ extern void LYstore_message(const char *message);
+ extern void LYstore_message2(const char *message, const char *argument);
+
+ extern HTList *Visited_Links;
+ extern int nhist_extra;
+ extern int status_buf_size;
+
+#ifdef __cplusplus
+}
+#endif
+#endif /* LYHISTORY_H */
diff --git a/src/LYIcon.rc b/src/LYIcon.rc
new file mode 100644
index 0000000..001af56
--- /dev/null
+++ b/src/LYIcon.rc
@@ -0,0 +1,34 @@
+// $LynxId: LYIcon.rc,v 1.57 2024/01/07 11:13:00 tom Exp $
+
+#include <windows.h>
+
+100 ICON "../samples/lynx.ico"
+
+VS_VERSION_INFO VERSIONINFO
+FILEVERSION 2,9,0,1013
+PRODUCTVERSION 2,9,0,1013
+FILEFLAGSMASK VS_FFI_FILEFLAGSMASK
+FILEFLAGS 0
+FILEOS VOS_NT_WINDOWS32
+FILETYPE VFT_APP
+FILESUBTYPE VFT2_UNKNOWN
+BEGIN
+ BLOCK "StringFileInfo"
+ BEGIN
+ BLOCK "040904B0"
+ BEGIN
+ VALUE "CompanyName", "https://invisible-island.net/lynx"
+ VALUE "FileDescription", "Lynx - web browser"
+ VALUE "FileVersion", "2.9.0.1013"
+ VALUE "InternalName", "Lynx"
+ VALUE "LegalCopyright", "©1997-2024 Thomas E. Dickey"
+ VALUE "OriginalFilename", "lynx.exe"
+ VALUE "ProductName", "Lynx - web browser"
+ VALUE "ProductVersion", "2.9.0.1013"
+ END
+ END
+ BLOCK "VarFileInfo"
+ BEGIN
+ VALUE "Translation", 0x409, 1200
+ END
+END
diff --git a/src/LYJump.c b/src/LYJump.c
new file mode 100644
index 0000000..877d532
--- /dev/null
+++ b/src/LYJump.c
@@ -0,0 +1,504 @@
+/*
+ * $LynxId: LYJump.c,v 1.52 2018/03/18 19:17:00 tom Exp $
+ */
+#include <HTUtils.h>
+#include <HTAlert.h>
+#include <LYUtils.h>
+#include <LYStrings.h>
+#include <LYGlobalDefs.h>
+#include <LYJump.h>
+#include <LYKeymap.h>
+#include <GridText.h>
+
+#include <LYLeaks.h>
+
+#ifdef _WINDOWS
+#include <stdlib.h> /* bsearch() */
+#endif
+
+#ifdef VMS
+#include <fab.h>
+#endif /* VMS */
+
+struct JumpTable *JThead = NULL;
+
+static int LYCompare(const void *e1, const void *e2);
+static unsigned LYRead_Jumpfile(struct JumpTable *jtp);
+
+void LYJumpTable_free(void)
+{
+ struct JumpTable *cur = JThead;
+ struct JumpTable *next;
+
+ while (cur) {
+ next = cur->next;
+ FREE(cur->msg);
+ FREE(cur->file);
+ FREE(cur->shortcut);
+ if (cur->history) {
+ LYFreeStringList(cur->history);
+ cur->history = NULL;
+ }
+ FREE(cur->table);
+ FREE(cur->mp);
+ FREE(cur);
+ cur = next;
+ }
+ JThead = NULL;
+ return;
+}
+
+/*
+ * Utility for listing shortcuts, making any repeated
+ * shortcut the most current in the list. - FM
+ */
+void LYAddJumpShortcut(HTList *historyp, char *shortcut)
+{
+ char *tmp = NULL;
+ char *old;
+ HTList *cur = historyp;
+
+ if (!historyp || isEmpty(shortcut))
+ return;
+
+ StrAllocCopy(tmp, shortcut);
+
+ while (NULL != (old = (char *) HTList_nextObject(cur))) {
+ if (!strcmp(old, tmp)) {
+ HTList_removeObject(historyp, old);
+ FREE(old);
+ break;
+ }
+ }
+ HTList_addObject(historyp, tmp);
+
+ return;
+}
+
+BOOL LYJumpInit(char *config)
+{
+ struct JumpTable *jtp;
+ char *cp;
+
+ /*
+ * Create a JumpTable structure.
+ */
+ jtp = typecalloc(struct JumpTable);
+
+ if (jtp == NULL) {
+ outofmem(__FILE__, "LYJumpInit");
+ }
+
+ /*
+ * config is JUMPFILE:path[:optional_key[:optional_prompt]]
+ *
+ * Skip JUMPFILE.
+ */
+ cp = strtok(config, ":\n");
+ if (!cp) {
+ FREE(jtp);
+ return FALSE;
+ }
+
+ /*
+ * Get the path.
+ */
+ cp = strtok(NULL, ":\n");
+ if (!cp) {
+ FREE(jtp);
+ return FALSE;
+ }
+ StrAllocCopy(jtp->file, cp);
+#ifdef LY_FIND_LEAKS
+ if (!JThead)
+ atexit(LYJumpTable_free);
+#endif /* LY_FIND_LEAKS */
+
+ /*
+ * Get the key, if present.
+ */
+ cp = strtok(NULL, ":\n");
+
+ /*
+ * If no key, check whether we are resetting the default jumps file.
+ */
+ if (!cp && JThead) {
+ struct JumpTable *jtptmp = JThead;
+
+ jumpfile = jtp->file;
+ FREE(jtp);
+ while (jtptmp && jtptmp->key)
+ jtptmp = jtptmp->next;
+ if (!jtptmp)
+ return FALSE;
+ StrAllocCopy(jtptmp->file, jumpfile);
+ StrAllocCopy(jtptmp->msg, jumpprompt);
+ return TRUE;
+ }
+
+ /*
+ * If a key is present and we have no default, create one,
+ * using the path from config, and the current jumpprompt.
+ */
+ if (cp && !JThead) {
+ JThead = jtp;
+ StrAllocCopy(JThead->msg, jumpprompt);
+ if (isEmpty(jumpfile))
+ StrAllocCopy(jumpfile, JThead->file);
+ jtp = typecalloc(struct JumpTable);
+
+ if (jtp == NULL) {
+ outofmem(__FILE__, "LYJumpInit");
+ }
+
+ StrAllocCopy(jtp->file, JThead->file);
+ }
+
+ /*
+ * Complete the initialization of config.
+ */
+ if (cp) {
+ jtp->key = remap(cp, "JUMP", FALSE); /* key is present, (re)map it */
+ cp = strtok(NULL, "\n"); /* get prompt, if present */
+ if (non_empty(cp))
+ StrAllocCopy(jtp->msg, cp); /* prompt is present, load it */
+ else
+ cp = NULL;
+ }
+ if (!cp) /* no prompt, use default */
+ StrAllocCopy(jtp->msg, jumpprompt);
+ if (jtp->msg[strlen(jtp->msg) - 1] != ' ') /* ensure a trailing space */
+ StrAllocCat(jtp->msg, " ");
+ jtp->history = HTList_new();
+ jtp->next = JThead;
+ JThead = jtp;
+ return TRUE;
+}
+
+char *LYJump(int key)
+{
+ static bstring *buf = NULL;
+
+ JumpDatum seeking;
+ JumpDatum *found;
+ char *bp, *cp;
+ struct JumpTable *jtp;
+ int ch;
+ RecallType recall;
+ int ShortcutTotal;
+ int ShortcutNum;
+ BOOLEAN FirstShortcutRecall = TRUE;
+
+ if (!JThead)
+ return NULL;
+ jtp = JThead;
+ while (jtp && jtp->key && jtp->key != key)
+ jtp = jtp->next;
+ if (!jtp) {
+ char *msg = 0;
+
+ HTSprintf0(&msg, KEY_NOT_MAPPED_TO_JUMP_FILE, key);
+ HTAlert(msg);
+ FREE(msg);
+ return NULL;
+ }
+ if (!jtp->table)
+ jtp->nel = LYRead_Jumpfile(jtp);
+ if (jtp->nel == 0)
+ return NULL;
+
+ if (!jump_buffer || isEmpty(jtp->shortcut)) {
+ BStrCopy0(buf, "");
+ } else if (non_empty(jtp->shortcut)) {
+ size_t len = (size_t) BStrLen(buf);
+
+ if (strlen(jtp->shortcut) > len) {
+ jtp->shortcut[len] = '\0';
+ BStrCopy0(buf, jtp->shortcut);
+ }
+ }
+
+ ShortcutTotal = (jtp->history ? HTList_count(jtp->history) : 0);
+ if (jump_buffer && !isBEmpty(buf)) {
+ recall = ((ShortcutTotal > 1) ? RECALL_URL : NORECALL);
+ ShortcutNum = 0;
+ FirstShortcutRecall = FALSE;
+ } else {
+ recall = ((ShortcutTotal >= 1) ? RECALL_URL : NORECALL);
+ ShortcutNum = ShortcutTotal;
+ FirstShortcutRecall = TRUE;
+ }
+
+ statusline(jtp->msg);
+ if ((ch = LYgetBString(&buf, FALSE, 0, recall)) < 0) {
+ /*
+ * User cancelled the Jump via ^G. - FM
+ */
+ HTInfoMsg(CANCELLED);
+ return NULL;
+ }
+
+ check_recall:
+ bp = buf->str;
+ if (TOUPPER(key) == 'G' && StrNCmp(buf->str, "o ", 2) == 0)
+ bp++;
+ bp = LYSkipBlanks(bp);
+ if (*bp == '\0' &&
+ !(recall && (ch == UPARROW_KEY || ch == DNARROW_KEY))) {
+ /*
+ * User cancelled the Jump via a zero-length string. - FM
+ */
+ BStrCopy0(buf, "");
+ StrAllocCopy(jtp->shortcut, buf->str);
+ HTInfoMsg(CANCELLED);
+ return NULL;
+ }
+#ifdef PERMIT_GOTO_FROM_JUMP
+ if (StrChr(bp, ':') || StrChr(bp, '/')) {
+ char *temp = NULL;
+
+ LYJumpFileURL = FALSE;
+ if (no_goto) {
+ BStrCopy0(buf, "");
+ StrAllocCopy(jtp->shortcut, buf->str);
+ HTUserMsg(RANDOM_URL_DISALLOWED);
+ return NULL;
+ }
+ HTSprintf0(&temp, "Go %s", bp);
+ BStrCopy0(buf, temp);
+ FREE(temp);
+ return (bp = buf->str);
+ }
+#endif /* PERMIT_GOTO_FROM_JUMP */
+
+ if (recall && ch == UPARROW_KEY) {
+ if (FirstShortcutRecall) {
+ /*
+ * Use last Shortcut in the list. - FM
+ */
+ FirstShortcutRecall = FALSE;
+ ShortcutNum = 0;
+ } else {
+ /*
+ * Go back to the previous Shortcut in the list. - FM
+ */
+ ShortcutNum++;
+ }
+ if (ShortcutNum >= ShortcutTotal)
+ /*
+ * Roll around to the last Shortcut in the list. - FM
+ */
+ ShortcutNum = 0;
+ if ((cp = (char *) HTList_objectAt(jtp->history,
+ ShortcutNum)) != NULL) {
+ BStrCopy0(buf, cp);
+ if (jump_buffer && jtp->shortcut &&
+ !strcmp(buf->str, jtp->shortcut)) {
+ _statusline(EDIT_CURRENT_SHORTCUT);
+ } else if ((jump_buffer && ShortcutTotal == 2) ||
+ (!jump_buffer && ShortcutTotal == 1)) {
+ _statusline(EDIT_THE_PREV_SHORTCUT);
+ } else {
+ _statusline(EDIT_A_PREV_SHORTCUT);
+ }
+ if ((ch = LYgetBString(&buf, FALSE, 0, recall)) < 0) {
+ /*
+ * User cancelled the jump via ^G.
+ */
+ HTInfoMsg(CANCELLED);
+ return NULL;
+ }
+ goto check_recall;
+ }
+ } else if (recall && ch == DNARROW_KEY) {
+ if (FirstShortcutRecall) {
+ /*
+ * Use the first Shortcut in the list. - FM
+ */
+ FirstShortcutRecall = FALSE;
+ ShortcutNum = ShortcutTotal - 1;
+ } else {
+ /*
+ * Advance to the next Shortcut in the list. - FM
+ */
+ ShortcutNum--;
+ }
+ if (ShortcutNum < 0)
+ /*
+ * Roll around to the first Shortcut in the list. - FM
+ */
+ ShortcutNum = ShortcutTotal - 1;
+ if ((cp = (char *) HTList_objectAt(jtp->history,
+ ShortcutNum)) != NULL) {
+ BStrCopy0(buf, cp);
+ if (jump_buffer && jtp->shortcut &&
+ !strcmp(buf->str, jtp->shortcut)) {
+ _statusline(EDIT_CURRENT_SHORTCUT);
+ } else if ((jump_buffer && ShortcutTotal == 2) ||
+ (!jump_buffer && ShortcutTotal == 1)) {
+ _statusline(EDIT_THE_PREV_SHORTCUT);
+ } else {
+ _statusline(EDIT_A_PREV_SHORTCUT);
+ }
+ if ((ch = LYgetBString(&buf, FALSE, 0, recall)) < 0) {
+ /*
+ * User cancelled the jump via ^G.
+ */
+ HTInfoMsg(CANCELLED);
+ return NULL;
+ }
+ goto check_recall;
+ }
+ }
+
+ seeking.key = bp;
+ found = (JumpDatum *) bsearch((char *) &seeking, (char *) jtp->table,
+ (size_t) jtp->nel, sizeof(JumpDatum), LYCompare);
+ if (!found) {
+ user_message("Unknown target '%s'", buf->str);
+ LYSleepAlert();
+ }
+
+ StrAllocCopy(jtp->shortcut, bp);
+ LYAddJumpShortcut(jtp->history, jtp->shortcut);
+ return found ? found->url : NULL;
+}
+
+static unsigned LYRead_Jumpfile(struct JumpTable *jtp)
+{
+ struct stat st;
+ unsigned int nel;
+ char *mp;
+ int fd;
+
+#ifdef VMS
+ int blocksize = 1024;
+ FILE *fp;
+ BOOL IsStream_LF = TRUE;
+#endif /* VMS */
+ char *cp;
+ unsigned i;
+
+ if (isEmpty(jtp->file))
+ return 0;
+
+ CTRACE((tfp, "Read Jumpfile %s\n", jtp->file));
+ if (stat(jtp->file, &st) < 0) {
+ HTAlert(CANNOT_LOCATE_JUMP_FILE);
+ return 0;
+ }
+
+ /* allocate storage to read entire file */
+ if ((mp = typecallocn(char, (size_t) st.st_size + 1)) == NULL) {
+ HTAlert(OUTOF_MEM_FOR_JUMP_FILE);
+ return 0;
+ }
+#ifdef VMS
+ if (st.st_fab_rfm != (char) FAB$C_STMLF) {
+ /** It's a record-oriented file. **/
+ IsStream_LF = FALSE;
+ if ((fp = fopen(jtp->file, "r", "mbc=32")) == NULL) {
+ HTAlert(CANNOT_OPEN_JUMP_FILE);
+ FREE(mp);
+ return 0;
+ }
+ } else if ((fd = open(jtp->file, O_RDONLY, "mbc=32")) < 0)
+#else
+ if ((fd = open(jtp->file, O_RDONLY)) < 0)
+#endif /* VMS */
+ {
+ HTAlert(CANNOT_OPEN_JUMP_FILE);
+ FREE(mp);
+ return 0;
+ }
+#ifdef VMS
+ if (IsStream_LF) {
+ /** Handle as a stream. **/
+#endif /* VMS */
+ if (read(fd, mp, (size_t) st.st_size) != st.st_size) {
+ HTAlert(ERROR_READING_JUMP_FILE);
+ FREE(mp);
+ close(fd);
+ return 0;
+ }
+ mp[st.st_size] = '\0';
+ close(fd);
+#ifdef VMS
+ } else {
+ /** Handle as a series of records. **/
+ if (fgets(mp, blocksize, fp) == NULL) {
+ HTAlert(ERROR_READING_JUMP_FILE);
+ FREE(mp);
+ close(fd);
+ return 0;
+ } else {
+ while (fgets(mp + strlen(mp), blocksize, fp) != NULL) {
+ ;
+ }
+ }
+ LYCloseInput(fp);
+ close(fd);
+ }
+#endif /* VMS */
+
+ /* quick scan for approximate number of entries */
+ nel = 0;
+ cp = mp;
+ while ((cp = StrChr(cp, '\n')) != NULL) {
+ nel++;
+ cp++;
+ }
+
+ jtp->table = (JumpDatum *) malloc((nel + 1) * sizeof(JumpDatum));
+ if (jtp->table == NULL) {
+ HTAlert(OUTOF_MEM_FOR_JUMP_TABLE);
+ FREE(mp);
+ return 0;
+ }
+
+ cp = jtp->mp = mp;
+ for (i = 0; i < nel;) {
+ if (StrNCmp(cp, "<!--", 4) == 0 || StrNCmp(cp, "<dl>", 4) == 0) {
+ cp = StrChr(cp, '\n');
+ if (cp == NULL)
+ break;
+ cp++;
+ continue;
+ }
+ cp = LYstrstr(cp, "<dt>");
+ if (cp == NULL)
+ break;
+ cp += 4;
+ jtp->table[i].key = cp;
+ cp = LYstrstr(cp, "<dd>");
+ if (cp == NULL)
+ break;
+ *cp = '\0';
+ cp += 4;
+ cp = LYstrstr(cp, "href=\"");
+ if (cp == NULL)
+ break;
+ cp += 6;
+ jtp->table[i].url = cp;
+ cp = StrChr(cp, '"');
+ if (cp == NULL)
+ break;
+ *cp = '\0';
+ cp++;
+ cp = StrChr(cp, '\n');
+ if (cp == NULL)
+ break;
+ cp++;
+ CTRACE((tfp, "Read jumpfile[%u] key='%s', url='%s'\n",
+ i, jtp->table[i].key, jtp->table[i].url));
+ i++;
+ }
+
+ return i;
+}
+
+static int LYCompare(const void *e1, const void *e2)
+{
+ return strcasecomp(((const JumpDatum *) e1)->key,
+ ((const JumpDatum *) e2)->key);
+}
diff --git a/src/LYJump.h b/src/LYJump.h
new file mode 100644
index 0000000..159bdd6
--- /dev/null
+++ b/src/LYJump.h
@@ -0,0 +1,36 @@
+/* $LynxId: LYJump.h,v 1.8 2009/01/01 22:41:42 tom Exp $ */
+#ifndef LYJUMP_H
+#define LYJUMP_H
+
+#include <HTList.h>
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+ typedef struct _JumpDatum {
+ char *key;
+ char *url;
+ } JumpDatum;
+
+ struct JumpTable {
+ int key;
+ unsigned nel;
+ char *msg;
+ char *file;
+ char *shortcut;
+ HTList *history;
+ JumpDatum *table;
+ struct JumpTable *next;
+ char *mp;
+ };
+
+ extern struct JumpTable *JThead;
+ extern void LYJumpTable_free(void);
+ extern void LYAddJumpShortcut(HTList *the_history, char *shortcut);
+ extern BOOL LYJumpInit(char *config);
+ extern char *LYJump(int key);
+
+#ifdef __cplusplus
+}
+#endif
+#endif /* LYJUMP_H */
diff --git a/src/LYJustify.h b/src/LYJustify.h
new file mode 100644
index 0000000..997cd05
--- /dev/null
+++ b/src/LYJustify.h
@@ -0,0 +1,83 @@
+/*
+ * $LynxId: LYJustify.h,v 1.8 2009/11/21 15:24:48 tom Exp $
+ *
+ * Justification for lynx - implemented by Vlad Harchev <hvv@hippo.ru>
+ * 11 July 1999
+ */
+
+#ifndef LYJUSTIFY_H
+#define LYJUSTIFY_H
+
+#include <HTUtils.h>
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+#ifdef USE_JUSTIFY_ELTS
+ extern BOOL can_justify_here;
+ extern BOOL can_justify_here_saved;
+
+ extern BOOL can_justify_this_line;
+ extern int wait_for_this_stacked_elt;
+ extern BOOL form_in_htext;
+
+/* this is the element with SGML_EMPTY content, so it won't get on the stack,
+ * so we can't trap it with wait_for_this_stacked_elt
+ */
+ extern BOOL in_DT;
+
+/*disabled by default*/
+/*#define DEBUG_JUSTIFY*/
+#ifdef DEBUG_JUSTIFY
+ extern BOOL can_justify_stack_depth; /* can be 0 or 1 if all code is correct */
+
+# define CAN_JUSTIFY_STACK_INC ++can_justify_stack_depth;\
+ assert(can_justify_stack_depth < 2 && can_justify_stack_depth >=0 );
+# define CAN_JUSTIFY_STACK_DEC --can_justify_stack_depth;\
+ assert(can_justify_stack_depth < 2 && can_justify_stack_depth >=0 );
+#else
+# define CAN_JUSTIFY_STACK_INC /* nothing */
+# define CAN_JUSTIFY_STACK_DEC /* nothing */
+#endif
+
+#define CAN_JUSTIFY_PUSH(x) can_justify_here_saved=can_justify_here;\
+ can_justify_here=(x); CAN_JUSTIFY_STACK_INC
+#define CAN_JUSTIFY_POP can_justify_here=can_justify_here_saved;\
+ CAN_JUSTIFY_STACK_INC
+#define CAN_JUSTIFY_SET(x) can_justify_here=(x);
+
+/*
+ * This is used to indicate that starting from the current offset in current
+ * line justification can take place (in order the gap between some prefix and
+ * the word not to be enlarged.
+ * For example, when forming OL,
+ * 1.21 foo
+ * ^justification can start here so that gap between 1.21 and "foo"
+ * will not be enlarged.
+ * This is a macro (that uses 'me').
+ */
+#define CAN_JUSTIFY_START mark_justify_start_position(me->text);
+#define CANT_JUSTIFY_THIS_LINE can_justify_this_line = FALSE
+#define EMIT_IFDEF_USE_JUSTIFY_ELTS(x) x
+ /*defined in order not to wrap single line of code into #ifdef/#endif */
+
+ extern void ht_justify_cleanup(void);
+ extern void mark_justify_start_position(void *text);
+
+#else /* ! USE_JUSTIFY_ELTS */
+/*
+ * define empty macros so that they can be used without wrapping them in
+ * #ifdef USE_JUSTIFY_ELTS/#endif
+ */
+#define CAN_JUSTIFY_PUSH(x)
+#define CAN_JUSTIFY_POP
+#define CAN_JUSTIFY_SET(x)
+#define CAN_JUSTIFY_START
+#define CANT_JUSTIFY_THIS_LINE
+#define EMIT_IFDEF_USE_JUSTIFY_ELTS(x)
+#endif /* USE_JUSTIFY_ELTS */
+#define CAN_JUSTIFY_PUSH_F CAN_JUSTIFY_PUSH(FALSE)
+#ifdef __cplusplus
+}
+#endif
+#endif /* LYJUSTIFY_H */
diff --git a/src/LYKeymap.c b/src/LYKeymap.c
new file mode 100644
index 0000000..164fa23
--- /dev/null
+++ b/src/LYKeymap.c
@@ -0,0 +1,1545 @@
+/* $LynxId: LYKeymap.c,v 1.125 2024/01/10 08:53:58 tom Exp $ */
+#include <HTUtils.h>
+#include <LYUtils.h>
+#include <LYGlobalDefs.h>
+#include <LYKeymap.h>
+#include <LYCharSets.h> /* for LYlowest_eightbit - kw */
+#include <HTAccess.h>
+#include <HTFormat.h>
+#include <HTAlert.h>
+#include <LYStrings.h> /* for USE_KEYMAP stuff - kw */
+
+#include <LYLeaks.h>
+
+#ifdef EXP_KEYBOARD_LAYOUT
+#include <jcuken_kb.h>
+#include <yawerty_kb.h>
+#include <rot13_kb.h>
+#endif
+
+#define PUTS(buf) (*target->isa->put_block)(target, buf, (int) strlen(buf))
+
+#ifdef EXP_KEYBOARD_LAYOUT
+int current_layout = 0; /* Index into LYKbLayouts[] */
+
+LYKbLayout_t *LYKbLayouts[] =
+{
+ kb_layout_rot13,
+ kb_layout_jcuken,
+ kb_layout_yawerty
+};
+
+const char *LYKbLayoutNames[] =
+{
+ "ROT13'd keyboard layout",
+ "JCUKEN Cyrillic, for AT 101-key kbd",
+ "YAWERTY Cyrillic, for DEC LK201 kbd",
+ (char *) 0
+};
+#endif /* EXP_KEYBOARD_LAYOUT */
+
+/* * * Tables mapping LynxKeyCodes to LynxActionCodes * * */
+
+/*
+ * Lynxkeycodes include all single-byte keys as well as codes for function keys
+ * and some special purposes. See LYStrings.h. Extended lynxkeycode values
+ * can also contain flags for modifiers and other purposes, but here only the
+ * base values are mapped to lynxactioncodes. They are called `keystrokes' in
+ * lynx.cfg.
+ *
+ * Lynxactioncodes (confusingly, constants are named LYK_foo and typed as
+ * specify key `functions', see LYKeymap.h.
+ */
+
+/* the character gets 1 added to it before lookup,
+ * so that EOF maps to 0
+ */
+LYKeymap_t keymap[KEYMAP_SIZE];
+
+static const LYEditInit initKeymapData[] =
+{
+ {KTL('@'), LYK_DO_NOTHING},
+ {KTL('A'), LYK_HOME},
+ {KTL('B'), LYK_PREV_PAGE},
+ {KTL('D'), LYK_ABORT},
+ {KTL('E'), LYK_END},
+ {KTL('F'), LYK_NEXT_PAGE},
+ {KTL('H'), LYK_HISTORY},
+ {KTL('I'), LYK_FASTFORW_LINK},
+ {KTL('J'), LYK_ACTIVATE},
+ {KTL('K'), LYK_COOKIE_JAR},
+ {KTL('L'), LYK_REFRESH},
+ {KTL('M'), LYK_ACTIVATE},
+ {KTL('N'), LYK_DOWN_TWO},
+ {KTL('P'), LYK_UP_TWO},
+ {KTL('Q'), LYK_CHANGE_CENTER},
+ {KTL('R'), LYK_RELOAD},
+#ifdef CAN_CUT_AND_PASTE
+ {KTL('S'), LYK_TO_CLIPBOARD},
+#endif
+ {KTL('T'), LYK_TRACE_TOGGLE},
+ {KTL('U'), LYK_NEXT_DOC},
+ {KTL('V'), LYK_SWITCH_DTD},
+ {KTL('W'), LYK_REFRESH},
+ {KTL('X'), LYK_CACHE_JAR},
+ {KTL('Z'), LYK_MAXSCREEN_TOGGLE},
+ {KHR(' '), LYK_NEXT_PAGE},
+ {KHR('!'), LYK_SHELL},
+ {KHR('"'), LYK_SOFT_DQUOTES},
+ {KHR('#'), LYK_TOOLBAR},
+ {KHR('$'), LYK_LAST_LINK},
+ {KHR('\''), LYK_HISTORICAL},
+ {KHR('('), LYK_UP_HALF},
+ {KHR(')'), LYK_DOWN_HALF},
+ {KHR('*'), LYK_IMAGE_TOGGLE},
+ {KHR('+'), LYK_NEXT_PAGE},
+ {KHR(','), LYK_EXTERN_PAGE},
+ {KHR('-'), LYK_PREV_PAGE},
+ {KHR('.'), LYK_EXTERN_LINK},
+ {KHR('/'), LYK_WHEREIS},
+ {KHR('0'), LYK_F_LINK_NUM},
+ {KHR('1'), LYK_1},
+ {KHR('2'), LYK_2},
+ {KHR('3'), LYK_3},
+ {KHR('4'), LYK_4},
+ {KHR('5'), LYK_5},
+ {KHR('6'), LYK_6},
+ {KHR('7'), LYK_7},
+ {KHR('8'), LYK_8},
+ {KHR('9'), LYK_9},
+ {KHR(':'), LYK_COMMAND},
+ {KHR(';'), LYK_TRACE_LOG},
+ {KHR('<'), LYK_UP_LINK},
+ {KHR('='), LYK_INFO},
+ {KHR('>'), LYK_DOWN_LINK},
+ {KHR('?'), LYK_HELP},
+ {KHR('@'), LYK_RAW_TOGGLE},
+ {KHR('A'), LYK_ADDRLIST},
+ {KHR('B'), LYK_PREV_PAGE},
+#ifdef SUPPORT_CHDIR
+ {KHR('C'), LYK_CHDIR},
+#else
+ {KHR('C'), LYK_COMMENT},
+#endif
+ {KHR('D'), LYK_DOWNLOAD},
+ {KHR('E'), LYK_ELGOTO},
+ {KHR('F'), LYK_DIRED_MENU},
+ {KHR('G'), LYK_ECGOTO},
+ {KHR('H'), LYK_HELP},
+ {KHR('I'), LYK_INDEX},
+#ifdef KANJI_CODE_OVERRIDE
+ {KHR('J'), LYK_CHANGE_KCODE},
+#else
+ {KHR('J'), LYK_JUMP},
+#endif
+ {KHR('K'), LYK_KEYMAP},
+ {KHR('L'), LYK_LIST},
+ {KHR('M'), LYK_MAIN_MENU},
+ {KHR('N'), LYK_PREV},
+ {KHR('O'), LYK_OPTIONS},
+ {KHR('P'), LYK_PRINT},
+ {KHR('Q'), LYK_ABORT},
+ {KHR('R'), LYK_DEL_BOOKMARK},
+ {KHR('S'), LYK_INDEX_SEARCH},
+ {KHR('T'), LYK_TAG_LINK},
+ {KHR('U'), LYK_PREV_DOC},
+ {KHR('V'), LYK_VLINKS},
+ {KHR('X'), LYK_NOCACHE},
+ {KHR('Z'), LYK_INTERRUPT},
+ {KHR('['), LYK_INLINE_TOGGLE},
+ {KHR('\\'), LYK_SOURCE},
+ {KHR(']'), LYK_HEAD},
+ {KHR('^'), LYK_FIRST_LINK},
+ {KHR('_'), LYK_CLEAR_AUTH},
+ {KHR('`'), LYK_MINIMAL},
+ {KHR('a'), LYK_ADD_BOOKMARK},
+ {KHR('b'), LYK_PREV_PAGE},
+ {KHR('c'), LYK_COMMENT},
+ {KHR('d'), LYK_DOWNLOAD},
+ {KHR('e'), LYK_EDIT},
+ {KHR('f'), LYK_DIRED_MENU},
+ {KHR('g'), LYK_GOTO},
+ {KHR('h'), LYK_HELP},
+ {KHR('i'), LYK_INDEX},
+ {KHR('j'), LYK_JUMP},
+ {KHR('k'), LYK_KEYMAP},
+ {KHR('l'), LYK_LIST},
+ {KHR('m'), LYK_MAIN_MENU},
+ {KHR('n'), LYK_NEXT},
+ {KHR('o'), LYK_OPTIONS},
+ {KHR('p'), LYK_PRINT},
+ {KHR('q'), LYK_QUIT},
+ {KHR('r'), LYK_DEL_BOOKMARK},
+ {KHR('s'), LYK_INDEX_SEARCH},
+ {KHR('t'), LYK_TAG_LINK},
+ {KHR('u'), LYK_PREV_DOC},
+ {KHR('v'), LYK_VIEW_BOOKMARK},
+ {KHR('x'), LYK_NOCACHE},
+ {KHR('z'), LYK_INTERRUPT},
+ {KHR('{'), LYK_SHIFT_LEFT},
+ {KHR('|'), LYK_LINEWRAP_TOGGLE},
+ {KHR('}'), LYK_SHIFT_RIGHT},
+ {KHR('~'), LYK_NESTED_TABLES},
+ {KHR(DEL_KEY), LYK_HISTORY},
+ {KHR(UPARROW_KEY), LYK_PREV_LINK},
+ {KHR(DNARROW_KEY), LYK_NEXT_LINK},
+ {KHR(RTARROW_KEY), LYK_ACTIVATE},
+ {KHR(LTARROW_KEY), LYK_PREV_DOC},
+ {KHR(PGDOWN_KEY), LYK_NEXT_PAGE},
+ {KHR(PGUP_KEY), LYK_PREV_PAGE},
+ {KHR(HOME_KEY), LYK_HOME},
+ {KHR(END_KEY), LYK_END},
+ {KHR(F1_KEY), LYK_DWIMHELP},
+#if !(defined(_WINDOWS) || defined(__DJGPP__))
+ {KHR(DO_KEY), LYK_ACTIVATE},
+ {KHR(FIND_KEY), LYK_HOME},
+ {KHR(SELECT_KEY), LYK_END},
+#endif
+ {KHR(INSERT_KEY), LYK_UP_TWO},
+ {KHR(REMOVE_KEY), LYK_DOWN_TWO},
+ {KHR(DO_NOTHING), LYK_DO_NOTHING},
+ {KHR(BACKTAB_KEY), LYK_FASTBACKW_LINK},
+ {KHR(F11_KEY), LYK_DO_NOTHING},
+#ifdef DJGPP_KEYHANDLER
+ {302, LYK_ABORT},
+#endif /* DJGPP_KEYHANDLER */
+#if (defined(_WINDOWS) || defined(__DJGPP__) || defined(__CYGWIN__)) && !defined(USE_SLANG) /* PDCurses */
+ {441, LYK_ABORT}, /* ALT_X */
+ {459, LYK_WHEREIS}, /* KP_SLASH */
+ {464, LYK_IMAGE_TOGGLE}, /* KP_* */
+ {465, LYK_PREV_PAGE}, /* KP_- */
+ {466, LYK_NEXT_PAGE}, /* KP_+ */
+#endif
+ {657, LYK_CHANGE_LINK},
+ {-1, LYE_UNKNOWN}
+};
+
+static LYEditConfig myKeymapData =
+{
+ "Key Map", initKeymapData, keymap
+};
+
+#if defined(DIRED_SUPPORT) && defined(OK_OVERRIDE)
+/*
+ * This table is used to override the standard keyboard assignments
+ * when lynx_edit_mode is in effect and keyboard overrides have been
+ * allowed at compile time.
+ */
+
+LYKeymap_t key_override[KEYMAP_SIZE];
+
+#define EDIT_INIT(c,l) {KHR(c), (l)}
+static const LYEditInit initOverrideData[] =
+{
+ EDIT_INIT(CTL('V'), LYK_NEXT_DOC),
+ EDIT_INIT('.', LYK_TAG_LINK),
+#ifndef SUPPORT_CHDIR
+ EDIT_INIT('C', LYK_CREATE),
+#else
+ EDIT_INIT('C', LYK_CHDIR),
+#endif
+ EDIT_INIT('F', LYK_DIRED_MENU),
+ EDIT_INIT('M', LYK_MODIFY),
+ EDIT_INIT('R', LYK_REMOVE),
+ EDIT_INIT('T', LYK_TAG_LINK),
+ EDIT_INIT('U', LYK_UPLOAD),
+ EDIT_INIT('c', LYK_CREATE),
+ EDIT_INIT('f', LYK_DIRED_MENU),
+ EDIT_INIT('m', LYK_MODIFY),
+ EDIT_INIT('r', LYK_REMOVE),
+ EDIT_INIT('t', LYK_TAG_LINK),
+ EDIT_INIT('u', LYK_UPLOAD),
+ EDIT_INIT(DO_NOTHING, LYK_DO_NOTHING),
+ {-1, LYE_UNKNOWN}
+};
+
+static LYEditConfig myOverrideData =
+{
+ "Key Override", initOverrideData, key_override
+};
+#endif /* DIRED_SUPPORT && OK_OVERRIDE */
+
+#define DATA(code, name, doc) { code, name, doc }
+/* The order of this array must match the LYKeymapCode enum in LYKeymap.h */
+static Kcmd revmap[] =
+{
+ DATA(
+ LYK_UNKNOWN, "UNMAPPED",
+ NULL),
+ DATA(
+ LYK_COMMAND, "COMMAND",
+ "prompt for, execute a command"),
+ DATA(
+ LYK_1, "1",
+ NULL),
+ DATA(
+ LYK_2, "2",
+ NULL),
+ DATA(
+ LYK_3, "3",
+ NULL),
+ DATA(
+ LYK_4, "4",
+ NULL),
+ DATA(
+ LYK_5, "5",
+ NULL),
+ DATA(
+ LYK_6, "6",
+ NULL),
+ DATA(
+ LYK_7, "7",
+ NULL),
+ DATA(
+ LYK_8, "8",
+ NULL),
+ DATA(
+ LYK_9, "9",
+ NULL),
+ DATA(
+ LYK_SOURCE, "SOURCE",
+ "toggle source/presentation for current document"),
+ DATA(
+ LYK_RELOAD, "RELOAD",
+ "reload the current document"),
+ DATA(
+ LYK_QUIT, "QUIT",
+ "quit the browser"),
+ DATA(
+ LYK_ABORT, "ABORT",
+ "quit the browser unconditionally"),
+ DATA(
+ LYK_NEXT_PAGE, "NEXT_PAGE",
+ "view the next page of the document"),
+ DATA(
+ LYK_PREV_PAGE, "PREV_PAGE",
+ "view the previous page of the document"),
+ DATA(
+ LYK_UP_TWO, "UP_TWO",
+ "go back two lines in the document"),
+ DATA(
+ LYK_DOWN_TWO, "DOWN_TWO",
+ "go forward two lines in the document"),
+ DATA(
+ LYK_UP_HALF, "UP_HALF",
+ "go back half a page in the document"),
+ DATA(
+ LYK_DOWN_HALF, "DOWN_HALF",
+ "go forward half a page in the document"),
+ DATA(
+ LYK_REFRESH, "REFRESH",
+ "refresh the screen to clear garbled text"),
+ DATA(
+ LYK_HOME, "HOME",
+ "go to the beginning of the current document"),
+ DATA(
+ LYK_END, "END",
+ "go to the end of the current document"),
+ DATA(
+ LYK_FIRST_LINK, "FIRST_LINK",
+ "make the first link on the line current"),
+ DATA(
+ LYK_LAST_LINK, "LAST_LINK",
+ "make the last link on the line current"),
+ DATA(
+ LYK_PREV_LINK, "PREV_LINK",
+ "make the previous link current"),
+ DATA(
+ LYK_NEXT_LINK, "NEXT_LINK",
+ "make the next link current"),
+ DATA(
+ LYK_LPOS_PREV_LINK, "LPOS_PREV_LINK",
+ "make previous link current, same column for input"),
+ DATA(
+ LYK_LPOS_NEXT_LINK, "LPOS_NEXT_LINK",
+ "make next link current, same column for input"),
+ DATA(
+ LYK_FASTBACKW_LINK, "FASTBACKW_LINK",
+ "previous link or text area, only stops on links"),
+ DATA(
+ LYK_FASTFORW_LINK, "FASTFORW_LINK",
+ "next link or text area, only stops on links"),
+ DATA(
+ LYK_UP_LINK, "UP_LINK",
+ "move up the page to a previous link"),
+ DATA(
+ LYK_DOWN_LINK, "DOWN_LINK",
+ "move down the page to another link"),
+ DATA(
+ LYK_RIGHT_LINK, "RIGHT_LINK",
+ "move right to another link"),
+ DATA(
+ LYK_LEFT_LINK, "LEFT_LINK",
+ "move left to a previous link"),
+ DATA(
+ LYK_HISTORY, "HISTORY",
+ "display stack of currently-suspended documents"),
+ DATA(
+ LYK_PREV_DOC, "PREV_DOC",
+ "go back to the previous document"),
+ DATA(
+ LYK_NEXT_DOC, "NEXT_DOC",
+ "undo going back to the previous document"),
+ DATA(
+ LYK_ACTIVATE, "ACTIVATE",
+ "go to the document given by the current link"),
+ DATA(
+ LYK_MOUSE_SUBMIT, "MOUSE_SUBMIT",
+ "DO NOT MAP: follow current link, submit"),
+ DATA(
+ LYK_SUBMIT, "SUBMIT",
+ "prompt and submit form"),
+ DATA(
+ LYK_RESET, "RESET",
+ "reset fields on current form"),
+ DATA(
+ LYK_GOTO, "GOTO",
+ "go to a document given as a URL"),
+ DATA(
+ LYK_ECGOTO, "ECGOTO",
+ "edit the current document's URL and go to it"),
+ DATA(
+ LYK_HELP, "HELP",
+ "display help on using the browser"),
+ DATA(
+ LYK_DWIMHELP, "DWIMHELP",
+ "display help page that may depend on context"),
+ DATA(
+ LYK_INDEX, "INDEX",
+ "display an index of potentially useful documents"),
+ DATA(
+ LYK_NOCACHE, "NOCACHE",
+ "force submission of form or link with no-cache"),
+ DATA(
+ LYK_INTERRUPT, "INTERRUPT",
+ "interrupt network connection or transmission"),
+ DATA(
+ LYK_MAIN_MENU, "MAIN_MENU",
+ "return to the first screen (home page)"),
+ DATA(
+ LYK_OPTIONS, "OPTIONS",
+ "display and change option settings"),
+ DATA(
+ LYK_INDEX_SEARCH, "INDEX_SEARCH",
+ "allow searching of an index"),
+ DATA(
+ LYK_WHEREIS, "WHEREIS",
+ "search within the current document"),
+ DATA(
+ LYK_PREV, "PREV",
+ "search for the previous occurrence"),
+ DATA(
+ LYK_NEXT, "NEXT",
+ "search for the next occurrence"),
+ DATA(
+ LYK_COMMENT, "COMMENT",
+ "send a comment to the author of the current document"),
+ DATA(
+ LYK_EDIT, "EDIT",
+ "edit the current document or a form's textarea"),
+ DATA(
+ LYK_INFO, "INFO",
+ "display information on the current document and link"),
+ DATA(
+ LYK_PRINT, "PRINT",
+ "display choices for printing the current document"),
+ DATA(
+ LYK_ADD_BOOKMARK, "ADD_BOOKMARK",
+ "add to your personal bookmark list"),
+ DATA(
+ LYK_DEL_BOOKMARK, "DEL_BOOKMARK",
+ "delete from your personal bookmark list"),
+ DATA(
+ LYK_VIEW_BOOKMARK, "VIEW_BOOKMARK",
+ "view your personal bookmark list"),
+ DATA(
+ LYK_VLINKS, "VLINKS",
+ "list links visited during the current Lynx session"),
+ DATA(
+ LYK_SHELL, "SHELL",
+ "escape from the browser to the system"),
+ DATA(
+ LYK_DOWNLOAD, "DOWNLOAD",
+ "download the current link to your computer"),
+ DATA(
+ LYK_TRACE_TOGGLE, "TRACE_TOGGLE",
+ "toggle tracing of browser operations"),
+ DATA(
+ LYK_TRACE_LOG, "TRACE_LOG",
+ "view trace log if started in the current session"),
+ DATA(
+ LYK_IMAGE_TOGGLE, "IMAGE_TOGGLE",
+ "toggle handling of all images as links"),
+ DATA(
+ LYK_INLINE_TOGGLE, "INLINE_TOGGLE",
+ "toggle pseudo-ALTs for inlines with no ALT string"),
+ DATA(
+ LYK_HEAD, "HEAD",
+ "send a HEAD request for the current document or link"),
+ DATA(
+ LYK_DO_NOTHING, "DO_NOTHING",
+ NULL),
+ DATA(
+ LYK_TOGGLE_HELP, "TOGGLE_HELP",
+ "show other commands in the novice help menu"),
+ DATA(
+ LYK_JUMP, "JUMP",
+ "go directly to a target document or action"),
+ DATA(
+ LYK_EDITMAP, "EDITMAP",
+ "display the current edit-key map"),
+ DATA(
+ LYK_KEYMAP, "KEYMAP",
+ "display the current key map"),
+ DATA(
+ LYK_LIST, "LIST",
+ "list the references (links) in the current document"),
+ DATA(
+ LYK_TOOLBAR, "TOOLBAR",
+ "go to Toolbar or Banner in the current document"),
+ DATA(
+ LYK_HISTORICAL, "HISTORICAL",
+ "toggle historical vs. valid/minimal comment parsing"),
+ DATA(
+ LYK_MINIMAL, "MINIMAL",
+ "toggle minimal vs. valid comment parsing"),
+ DATA(
+ LYK_SOFT_DQUOTES, "SOFT_DQUOTES",
+ "toggle valid vs. soft double-quote parsing"),
+ DATA(
+ LYK_RAW_TOGGLE, "RAW_TOGGLE",
+ "toggle raw 8-bit translations or CJK mode ON or OFF"),
+ DATA(
+ LYK_COOKIE_JAR, "COOKIE_JAR",
+ "examine the Cookie Jar"),
+ DATA(
+ LYK_F_LINK_NUM, "F_LINK_NUM",
+ "invoke the 'Follow link (or page) number:' prompt"),
+ DATA(
+ LYK_CLEAR_AUTH, "CLEAR_AUTH",
+ "clear all authorization info for this session"),
+ DATA(
+ LYK_SWITCH_DTD, "SWITCH_DTD",
+ "switch between two ways of parsing HTML"),
+ DATA(
+ LYK_ELGOTO, "ELGOTO",
+ "edit the current link's URL or ACTION and go to it"),
+ DATA(
+ LYK_CHANGE_LINK, "CHANGE_LINK",
+ "force reset of the current link on the page"),
+ DATA(
+ LYK_DWIMEDIT, "DWIMEDIT",
+ "use external editor for context-dependent purpose"),
+ DATA(
+ LYK_EDITTEXTAREA, "EDITTEXTAREA",
+ "use an external editor to edit a form's textarea"),
+ DATA(
+ LYK_GROWTEXTAREA, "GROWTEXTAREA",
+ "add 5 new blank lines to the bottom of a textarea"),
+ DATA(
+ LYK_INSERTFILE, "INSERTFILE",
+ "insert file into a textarea (just above cursorline)"),
+#ifdef USE_ADDRLIST_PAGE
+ DATA(
+ LYK_ADDRLIST, "ADDRLIST",
+ "like LIST command, but always shows the links' URLs"),
+#endif
+#ifdef USE_EXTERNALS
+ DATA(
+ LYK_EXTERN_LINK, "EXTERN_LINK",
+ "run external program with current link"),
+ DATA(
+ LYK_EXTERN_PAGE, "EXTERN_PAGE",
+ "run external program with current page"),
+#endif
+#ifdef VMS
+ DATA(
+ LYK_DIRED_MENU, "DIRED_MENU",
+ "invoke File/Directory Manager, if available"),
+#else
+#ifdef DIRED_SUPPORT
+ DATA(
+ LYK_DIRED_MENU, "DIRED_MENU",
+ "display a full menu of file operations"),
+ DATA(
+ LYK_CREATE, "CREATE",
+ "create a new file or directory"),
+ DATA(
+ LYK_REMOVE, "REMOVE",
+ "remove a file or directory"),
+ DATA(
+ LYK_MODIFY, "MODIFY",
+ "modify the name or location of a file or directory"),
+ DATA(
+ LYK_TAG_LINK, "TAG_LINK",
+ "tag a file or directory for later action"),
+ DATA(
+ LYK_UPLOAD, "UPLOAD",
+ "upload from your computer to the current directory"),
+ DATA(
+ LYK_INSTALL, "INSTALL",
+ "install file or tagged files into a system area"),
+#endif /* DIRED_SUPPORT */
+ DATA(
+ LYK_CHANGE_CENTER, "CHANGE_CENTER",
+ "toggle center alignment in HTML TABLE"),
+#ifdef KANJI_CODE_OVERRIDE
+ DATA(
+ LYK_CHANGE_KCODE, "CHANGE_KCODE",
+ "Change Kanji code"),
+#endif
+#endif /* VMS */
+#ifdef SUPPORT_CHDIR
+ DATA(
+ LYK_CHDIR, "CHDIR",
+ "change current directory"),
+ DATA(
+ LYK_PWD, "PWD",
+ "print current directory"),
+#endif
+#ifdef USE_CURSES_PADS
+ DATA(
+ LYK_SHIFT_LEFT, "SHIFT_LEFT",
+ "shift the screen left"),
+ DATA(
+ LYK_SHIFT_RIGHT, "SHIFT_RIGHT",
+ "shift the screen right"),
+ DATA(
+ LYK_LINEWRAP_TOGGLE, "LINEWRAP_TOGGLE",
+ "toggle linewrap on/off"),
+#endif
+#ifdef CAN_CUT_AND_PASTE
+ DATA(
+ LYK_PASTE_URL, "PASTE_URL",
+ "Goto the URL in the clipboard"),
+ DATA(
+ LYK_TO_CLIPBOARD, "TO_CLIPBOARD",
+ "link's URL to Clip Board"),
+#endif
+#ifdef EXP_NESTED_TABLES
+ DATA(
+ LYK_NESTED_TABLES, "NESTED_TABLES",
+ "toggle nested-table parsing on/off"),
+#endif
+#ifdef USE_CACHEJAR
+ DATA(
+ LYK_CACHE_JAR, "CACHE_JAR",
+ "examine list of cached documents"),
+#endif
+#ifdef USE_MAXSCREEN_TOGGLE
+ DATA(
+ LYK_MAXSCREEN_TOGGLE, "MAXSCREEN_TOGGLE",
+ "toggle max screen and normal"),
+#endif
+ DATA(
+ LYK_UNKNOWN, NULL,
+ "")
+};
+
+
+#undef DATA
+/* *INDENT-OFF* */
+static const struct {
+ int key;
+ const char *name;
+} named_keys[] = {
+ { '\t', "<tab>" },
+ { '\r', "<return>" },
+ { CH_ESC, "ESC" },
+ { ' ', "<space>" },
+ { '<', "<" },
+ { '>', ">" },
+ /* LYExtraKeys */
+ { CH_DEL, "<delete>" },
+ { UPARROW_KEY, "Up Arrow" },
+ { DNARROW_KEY, "Down Arrow" },
+ { RTARROW_KEY, "Right Arrow" },
+ { LTARROW_KEY, "Left Arrow" },
+ { PGDOWN_KEY, "Page Down" },
+ { PGUP_KEY, "Page Up" },
+ { HOME_KEY, "Home" },
+ { END_KEY, "End" },
+ { F1_KEY, "F1" },
+ { F2_KEY, "F2" },
+ { F3_KEY, "F3" },
+ { F4_KEY, "F4" },
+ { F5_KEY, "F5" },
+ { F6_KEY, "F6" },
+ { F7_KEY, "F7" },
+ { F8_KEY, "F8" },
+ { F9_KEY, "F9" },
+ { F10_KEY, "F10" },
+ { F11_KEY, "F11" },
+ { F12_KEY, "F12" },
+ { DO_KEY, "Do key" },
+ { FIND_KEY, "Find key" },
+ { SELECT_KEY, "Select key" },
+ { INSERT_KEY, "Insert key" },
+ { REMOVE_KEY, "Remove key" },
+ { DO_NOTHING, "(DO_NOTHING)" },
+ { BACKTAB_KEY, "Back Tab" },
+ { MOUSE_KEY, "mouse pseudo key" },
+};
+/* *INDENT-ON* */
+
+/*
+ * Build a list of Lynx's commands, for use in the tab-completion in LYgetstr.
+ */
+HTList *LYcommandList(void)
+{
+ static HTList *myList = NULL;
+
+ if (myList == NULL) {
+ unsigned j;
+
+ myList = HTList_new();
+ for (j = 0; revmap[j].name != 0; j++) {
+ if (revmap[j].doc != 0) {
+ char *data = NULL;
+
+ StrAllocCopy(data, revmap[j].name);
+ HTList_addObject(myList, data);
+ }
+ }
+ }
+ return myList;
+}
+
+/*
+ * Find the given keycode.
+ */
+Kcmd *LYKeycodeToKcmd(LYKeymapCode code)
+{
+ unsigned j;
+ Kcmd *result = 0;
+
+ if (code > LYK_UNKNOWN) {
+ for (j = 0; revmap[j].name != 0; j++) {
+ if (revmap[j].code == code) {
+ result = revmap + j;
+ break;
+ }
+ }
+ }
+ return result;
+}
+
+/*
+ * Find the given command-name, accepting an abbreviation if it is unique.
+ */
+Kcmd *LYStringToKcmd(const char *name)
+{
+ size_t need = strlen(name);
+ size_t j;
+ BOOL exact = FALSE;
+ Kcmd *result = 0;
+ Kcmd *maybe = 0;
+
+ if (non_empty(name)) {
+ for (j = 0; revmap[j].name != 0; j++) {
+ if (!strcasecomp(revmap[j].name, name)) {
+ result = revmap + j;
+ break;
+ } else if (!exact
+ && !strncasecomp(revmap[j].name, name, (int) need)) {
+ if (maybe == 0) {
+ maybe = revmap + j;
+ } else {
+ if (revmap[j].name[need] != 0
+ && maybe->name[need] != 0) {
+ maybe = 0;
+ exact = TRUE;
+ }
+ }
+ }
+ }
+ }
+ return (result != 0) ? result : maybe;
+}
+
+char *LYKeycodeToString(int c,
+ int upper8)
+{
+ static char buf[30];
+ unsigned n;
+ BOOLEAN named = FALSE;
+
+ for (n = 0; n < TABLESIZE(named_keys); n++) {
+ if (named_keys[n].key == c) {
+ named = TRUE;
+ LYStrNCpy(buf, named_keys[n].name, sizeof(buf) - 1);
+ break;
+ }
+ }
+
+ if (!named) {
+ if (c <= 0377
+ && TOASCII(c) > TOASCII(' ')
+ && TOASCII(c) < 0177)
+ sprintf(buf, "%c", c);
+ else if (upper8
+ && TOASCII(c) > TOASCII(' ')
+ && c <= 0377
+ && c <= LYlowest_eightbit[current_char_set])
+ sprintf(buf, "%c", c);
+ else if (TOASCII(c) < TOASCII(' '))
+ sprintf(buf, "^%c", FROMASCII(TOASCII(c) | 0100));
+ else if (c >= 0400)
+ sprintf(buf, "key-0x%x", (unsigned) c);
+ else
+ sprintf(buf, "0x%x", (unsigned) c);
+ }
+ return buf;
+}
+
+int LYStringToKeycode(char *src)
+{
+ unsigned n;
+ int key = -1;
+ int len = (int) strlen(src);
+
+ if (len == 1) {
+ key = *src;
+ } else if (len == 2 && *src == '^') {
+ key = src[1] & 0x1f;
+ } else if (len > 2 && !strncasecomp(src, "0x", 2)) {
+ char *dst = 0;
+
+ key = (int) strtol(src, &dst, 0);
+ if (non_empty(dst))
+ key = -1;
+ } else if (len > 6 && !strncasecomp(src, "key-", 4)) {
+ char *dst = 0;
+
+ key = (int) strtol(src + 4, &dst, 0);
+ if (isEmpty(dst))
+ key = -1;
+ }
+ if (key < 0) {
+ for (n = 0; n < TABLESIZE(named_keys); n++) {
+ if (!strcasecomp(named_keys[n].name, src)) {
+ key = named_keys[n].key;
+ break;
+ }
+ }
+ }
+ return key;
+}
+
+#define PRETTY_LEN 11
+
+static char *pretty_html(int c)
+{
+ char *src = LYKeycodeToString(c, TRUE);
+
+
+ if (src != 0) {
+ /* *INDENT-OFF* */
+ static const struct {
+ int code;
+ const char *name;
+ } table[] = {
+ { '<', "&lt;" },
+ { '>', "&gt;" },
+ { '"', "&quot;" },
+ { '&', "&amp;" }
+ };
+ /* *INDENT-ON* */
+
+ static char buf[30];
+ char *dst = buf;
+ int adj = 0;
+ unsigned n;
+ BOOLEAN found;
+
+ while ((c = *src++) != 0) {
+ found = FALSE;
+ for (n = 0; n < TABLESIZE(table); n++) {
+ if (c == table[n].code) {
+ found = TRUE;
+ LYStrNCpy(dst,
+ table[n].name,
+ sizeof(buf) - (size_t) ((dst - buf) - 1));
+ adj += (int) strlen(dst) - 1;
+ dst += (int) strlen(dst);
+ break;
+ }
+ }
+ if (!found) {
+ *dst++ = (char) c;
+ }
+ }
+ adj -= (int) (dst - buf) - PRETTY_LEN;
+ while (adj-- > 0)
+ *dst++ = ' ';
+ *dst = 0;
+ return buf;
+ }
+
+ return 0;
+}
+
+static char *format_binding(LYKeymap_t *table, int i)
+{
+ LYKeymap_t the_key = table[i];
+ char *buf = 0;
+ char *formatted;
+ Kcmd *rmap = LYKeycodeToKcmd((LYKeymapCode) the_key);
+
+ if (rmap != 0
+ && rmap->name != 0
+ && rmap->doc != 0
+ && (formatted = pretty_html(i - 1)) != 0) {
+ HTSprintf0(&buf, "%-*s %-13s %s\n",
+ PRETTY_LEN, formatted,
+ rmap->name,
+ rmap->doc);
+ return buf;
+ }
+ return 0;
+}
+
+/* if both is true, produce an additional line for the corresponding
+ uppercase key if its binding is different. - kw */
+static void print_binding(HTStream *target, int i, int both)
+{
+ char *buf;
+ LYKeymap_t lac1 = LYK_UNKNOWN; /* 0 */
+
+#if defined(DIRED_SUPPORT) && defined(OK_OVERRIDE)
+ if (prev_lynx_edit_mode && !no_dired_support &&
+ (lac1 = key_override[i]) != LYK_UNKNOWN) {
+ if ((buf = format_binding(key_override, i)) != 0) {
+ PUTS(buf);
+ FREE(buf);
+ }
+ } else
+#endif /* DIRED_SUPPORT && OK_OVERRIDE */
+ if ((buf = format_binding(keymap, i)) != 0) {
+ lac1 = keymap[i];
+ PUTS(buf);
+ FREE(buf);
+ }
+
+ if (!both)
+ return;
+ i -= ' '; /* corresponding uppercase key */
+
+#if defined(DIRED_SUPPORT) && defined(OK_OVERRIDE)
+ if (prev_lynx_edit_mode && !no_dired_support && key_override[i]) {
+ if (key_override[i] != lac1 &&
+ (buf = format_binding(key_override, i)) != 0) {
+ PUTS(buf);
+ FREE(buf);
+ }
+ } else
+#endif /* DIRED_SUPPORT && OK_OVERRIDE */
+ if (keymap[i] != lac1 && (buf = format_binding(keymap, i)) != 0) {
+ PUTS(buf);
+ FREE(buf);
+ }
+}
+
+/*
+ * Return lynxactioncode whose name is the string func. returns -1 if not
+ * found. - kw
+ */
+int lacname_to_lac(const char *func)
+{
+ Kcmd *mp = LYStringToKcmd(func);
+
+ return (mp != 0) ? (int) mp->code : -1;
+}
+
+/*
+ * Return lynxkeycode represented by string src. returns -1 if not valid.
+ *
+ * This is simpler than what map_string_to_keysym() does for USE_KEYMAP, but
+ * compatible with revmap() used for processing KEYMAP options in the
+ * configuration file. - kw
+ */
+int lkcstring_to_lkc(const char *src)
+{
+ int c = -1;
+
+ if (strlen(src) == 1) {
+ c = *src;
+ } else if (strlen(src) == 2 && *src == '^') {
+ c = src[1] & 037;
+ } else if (strlen(src) >= 2 && isdigit(UCH(*src))) {
+ char *next = 0;
+
+ c = (int) strtol(src, &next, 0);
+ if (next != 0 && *next != '\0')
+ c = (-1);
+#ifdef USE_KEYMAPS
+ } else {
+ map_string_to_keysym(src, &c, TRUE);
+#ifndef USE_SLANG
+ if (c >= 0) {
+ /* make curses-keys mapped from Keysym_Strings[] available here */
+ if ((c & LKC_MASK) > 255)
+ c &= ~LKC_ISLKC;
+ }
+#endif
+#endif
+ }
+
+ if (c == CH_ESC) {
+ escape_bound = 1;
+ } else if (c < -1) {
+ c = (-1);
+ }
+
+ return c;
+}
+
+static int LYLoadKeymap(const char *arg GCC_UNUSED,
+ HTParentAnchor *anAnchor,
+ HTFormat format_out,
+ HTStream *sink)
+{
+ HTFormat format_in = WWW_HTML;
+ HTStream *target;
+ char *buf = 0;
+ int i;
+
+ /*
+ * Set up the stream. - FM
+ */
+ 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);
+ }
+ anAnchor->no_cache = TRUE;
+
+ HTSprintf0(&buf, "<html>\n<head>\n<title>%s</title>\n</head>\n<body>\n",
+ CURRENT_KEYMAP_TITLE);
+ PUTS(buf);
+ HTSprintf0(&buf, "<pre>\n");
+ PUTS(buf);
+
+ for (i = KHR('a'); i <= KHR('z'); i++) {
+ print_binding(target, i, TRUE);
+ }
+ for (i = 1; i < KEYMAP_SIZE; i++) {
+ /*
+ * Don't show CHANGE_LINK if mouse not enabled.
+ */
+ if ((i >= 0200 || i <= ' ' || !isalpha(i - 1)) &&
+ (LYUseMouse || (keymap[i] != LYK_CHANGE_LINK))) {
+ print_binding(target, i, FALSE);
+ }
+ }
+
+ HTSprintf0(&buf, "</pre>\n</body>\n</html>\n");
+ PUTS(buf);
+
+ (*target->isa->_free) (target);
+ FREE(buf);
+ return (HT_LOADED);
+}
+
+#ifdef GLOBALDEF_IS_MACRO
+#define _LYKEYMAP_C_GLOBALDEF_1_INIT { "LYNXKEYMAP", LYLoadKeymap, 0}
+GLOBALDEF(HTProtocol, LYLynxKeymap, _LYKEYMAP_C_GLOBALDEF_1_INIT);
+#else
+GLOBALDEF HTProtocol LYLynxKeymap =
+{"LYNXKEYMAP", LYLoadKeymap, 0};
+#endif /* GLOBALDEF_IS_MACRO */
+
+/*
+ * Install func as the mapping for key.
+ * If for_dired is TRUE, install it in the key_override[] table
+ * for Dired mode, otherwise in the general keymap[] table.
+ * If DIRED_SUPPORT or OK_OVERRIDE is not defined, don't do anything
+ * when for_dired is requested.
+ * returns lynxkeycode value != 0 if the mapping was made, 0 if not.
+ */
+int remap(char *key,
+ const char *func,
+ int for_dired)
+{
+ Kcmd *mp;
+ int c;
+ int result = 0;
+
+#if !defined(DIRED_SUPPORT) || !defined(OK_OVERRIDE)
+ if (for_dired) {
+ return result;
+ }
+#endif
+ if (func != NULL) {
+ c = lkcstring_to_lkc(key);
+ if ((c >= 0) && (c < KEYMAP_SIZE)) {
+ /* Remapping of key actions is supported only for basic
+ * lynxkeycodes, without modifiers etc.! If we get somehow
+ * called for an invalid lynxkeycode, fail or silently ignore
+ * modifiers -KW
+ */
+ if (!(c & (LKC_ISLECLAC | LKC_ISLAC))) {
+ c &= LKC_MASK;
+ if ((mp = LYStringToKcmd(func)) != 0) {
+#if defined(DIRED_SUPPORT) && defined(OK_OVERRIDE)
+ if (for_dired)
+ key_override[KHR(c)] = mp->code;
+ else
+#endif
+ keymap[KHR(c)] = (LYKeymap_t) mp->code;
+ /* don't return 0, successful */
+ result = (c ? c : (int) LAC_TO_LKC0(mp->code));
+ }
+ }
+ }
+ }
+ return result;
+}
+
+typedef struct {
+ int code;
+ LYKeymap_t map;
+ LYKeymap_t save;
+} ANY_KEYS;
+
+/*
+ * Save the given keys in the table, setting them to the map'd value.
+ */
+static void set_any_keys(ANY_KEYS * table, size_t size)
+{
+ size_t j;
+
+ for (j = 0; j < size; ++j) {
+ int c = KHR(table[j].code);
+
+ table[j].save = keymap[c];
+ keymap[c] = table[j].map;
+ }
+}
+
+/*
+ * Restore the given keys from the table.
+ */
+static void reset_any_keys(ANY_KEYS * table, size_t size)
+{
+ size_t j;
+
+ for (j = 0; j < size; ++j) {
+ int c = KHR(table[j].code);
+
+ keymap[c] = table[j].save;
+ }
+}
+
+static ANY_KEYS vms_keys_table[] =
+{
+ {26, LYK_ABORT, 0}, /* control-Z */
+ {'$', LYK_SHELL, 0},
+};
+
+void set_vms_keys(void)
+{
+ set_any_keys(vms_keys_table, TABLESIZE(vms_keys_table));
+}
+
+static ANY_KEYS vi_keys_table[] =
+{
+ {'h', LYK_PREV_DOC, 0},
+ {'j', LYK_NEXT_LINK, 0},
+ {'k', LYK_PREV_LINK, 0},
+ {'l', LYK_ACTIVATE, 0},
+};
+
+static BOOLEAN did_vi_keys;
+
+void set_vi_keys(void)
+{
+ set_any_keys(vi_keys_table, TABLESIZE(vi_keys_table));
+ did_vi_keys = TRUE;
+}
+
+void reset_vi_keys(void)
+{
+ if (did_vi_keys) {
+ reset_any_keys(vi_keys_table, TABLESIZE(vi_keys_table));
+ did_vi_keys = FALSE;
+ }
+}
+
+static ANY_KEYS emacs_keys_table[] =
+{
+ {2, LYK_PREV_DOC, 0}, /* ^B */
+ {14, LYK_NEXT_LINK, 0}, /* ^N */
+ {16, LYK_PREV_LINK, 0}, /* ^P */
+ {6, LYK_ACTIVATE, 0}, /* ^F */
+};
+
+static BOOLEAN did_emacs_keys;
+
+void set_emacs_keys(void)
+{
+ set_any_keys(emacs_keys_table, TABLESIZE(emacs_keys_table));
+ did_emacs_keys = TRUE;
+}
+
+void reset_emacs_keys(void)
+{
+ if (did_emacs_keys) {
+ reset_any_keys(emacs_keys_table, TABLESIZE(emacs_keys_table));
+ did_emacs_keys = FALSE;
+ }
+}
+
+/*
+ * Map numbers to functions as labeled on the IBM Enhanced keypad, and save
+ * their original mapping for reset_numbers_as_arrows(). - FM
+ */
+static ANY_KEYS number_keys_table[] =
+{
+ {'1', LYK_END, 0},
+ {'2', LYK_NEXT_LINK, 0},
+ {'3', LYK_NEXT_PAGE, 0},
+ {'4', LYK_PREV_DOC, 0},
+ {'5', LYK_DO_NOTHING, 0},
+ {'6', LYK_ACTIVATE, 0},
+ {'7', LYK_HOME, 0},
+ {'8', LYK_PREV_LINK, 0},
+ {'9', LYK_PREV_PAGE, 0},
+};
+
+static BOOLEAN did_number_keys;
+
+void set_numbers_as_arrows(void)
+{
+ set_any_keys(number_keys_table, TABLESIZE(number_keys_table));
+ did_number_keys = TRUE;
+}
+
+void reset_numbers_as_arrows(void)
+{
+ if (did_number_keys) {
+ reset_any_keys(number_keys_table, TABLESIZE(number_keys_table));
+ did_number_keys = FALSE;
+ }
+}
+
+char *key_for_func(int func)
+{
+ static char *buf;
+ int i;
+ char *formatted;
+
+ if ((i = LYReverseKeymap(func)) >= 0) {
+ formatted = LYKeycodeToString(i, TRUE);
+ StrAllocCopy(buf, formatted != 0 ? formatted : "?");
+ } else if (buf == 0) {
+ StrAllocCopy(buf, "");
+ }
+ return buf;
+}
+
+/*
+ * Given one or two keys as lynxkeycodes, returns an allocated string
+ * representing the key(s) suitable for statusline messages, or NULL if no
+ * valid lynxkeycode is passed in (i.e., lkc_first < 0 or some other failure).
+ * The caller must free the string. - kw
+ */
+char *fmt_keys(int lkc_first,
+ int lkc_second)
+{
+ char *buf = NULL;
+ BOOLEAN quotes = FALSE;
+ char *fmt_first;
+ char *fmt_second;
+
+ if (lkc_first < 0)
+ return NULL;
+ fmt_first = LYKeycodeToString(lkc_first, TRUE);
+ if (fmt_first && strlen(fmt_first) == 1 && *fmt_first != '\'') {
+ quotes = TRUE;
+ }
+ if (quotes) {
+ if (lkc_second < 0) {
+ HTSprintf0(&buf, "'%s'", fmt_first);
+ return buf;
+ } else {
+ HTSprintf0(&buf, "'%s", fmt_first);
+ }
+ } else {
+ StrAllocCopy(buf, fmt_first);
+ }
+ if (lkc_second >= 0) {
+ fmt_second = LYKeycodeToString(lkc_second, TRUE);
+ if (!fmt_second) {
+ FREE(buf);
+ return NULL;
+ }
+ HTSprintf(&buf, "%s%s%s",
+ (((strlen(fmt_second) > 2 && *fmt_second != '<') ||
+ (strlen(buf) > 2 && buf[strlen(buf) - 1] != '>'))
+ ? " "
+ : ""),
+ fmt_second, quotes ? "'" : "");
+ }
+ return buf;
+}
+
+/*
+ * This function returns the (int)ch mapped to the LYK_foo value passed to it
+ * as an argument. It is like LYReverseKeymap, only the order of search is
+ * different; e.g., small ASCII letters will be returned in preference to
+ * capital ones. Cf. LYKeyForEditAction, LYEditKeyForAction in LYEditmap.c
+ * which use the same order to find a best key. In addition, this function
+ * takes the dired override map into account while LYReverseKeymap doesn't.
+ * The caller must free the returned string. - kw
+ */
+#define FIRST_I 97
+#define NEXT_I(i,imax) ((i==122) ? 32 : (i==96) ? 123 : (i==126) ? 0 :\
+ (i==31) ? 256 : (i==imax) ? 127 :\
+ (i==255) ? (-1) :i+1)
+static int best_reverse_keymap(int lac)
+{
+ int i, c;
+
+ for (i = FIRST_I; i >= 0; i = NEXT_I(i, KEYMAP_SIZE - 1)) {
+#ifdef NOT_ASCII
+ if (i < 256) {
+ c = FROMASCII(i);
+ } else
+#endif
+ c = i;
+#if defined(DIRED_SUPPORT) && defined(OK_OVERRIDE)
+ if (lynx_edit_mode && !no_dired_support && lac &&
+ LKC_TO_LAC(key_override, c) == lac)
+ return c;
+#endif /* DIRED_SUPPORT && OK_OVERRIDE */
+ if (LKC_TO_LAC(keymap, c) == lac) {
+ return c;
+ }
+ }
+
+ return (-1);
+}
+
+/*
+ * This function returns a string representing a key mapped to a LYK_foo
+ * function, or NULL if not found. The string may represent a pair of keys.
+ * if context_code is FOR_INPUT, an appropriate binding for use while in the
+ * (forms) line editor is sought. - kw
+ */
+char *key_for_func_ext(int lac,
+ int context_code)
+{
+ int lkc, modkey = -1;
+
+ if (context_code == FOR_INPUT) {
+ lkc = LYEditKeyForAction(lac, &modkey);
+ if (lkc >= 0) {
+ if (lkc & (LKC_MOD1 | LKC_MOD2 | LKC_MOD3)) {
+ return fmt_keys(modkey, lkc & ~(LKC_MOD1 | LKC_MOD2 | LKC_MOD3));
+ } else {
+ return fmt_keys(lkc, -1);
+ }
+ }
+ }
+ lkc = best_reverse_keymap(lac);
+ if (lkc < 0)
+ return NULL;
+ if (context_code == FOR_INPUT) {
+ modkey = LYKeyForEditAction(LYE_LKCMD);
+ if (modkey < 0)
+ return NULL;
+ return fmt_keys(modkey, lkc);
+ } else {
+ return fmt_keys(lkc, -1);
+ }
+}
+
+/*
+ * This function returns TRUE if the ch is non-alphanumeric and maps to KeyName
+ * (LYK_foo in the keymap[] array). - FM
+ */
+BOOLEAN LYisNonAlnumKeyname(int ch,
+ int KeyName)
+{
+ BOOLEAN result = FALSE;
+
+ if (ch >= 0 && KHR(ch) < KEYMAP_SIZE) {
+ if ((ch <= 0
+ || StrChr("0123456789"
+ "ABCDEFGHIJKLMNOPQRSTUVWXYZ"
+ "abcdefghijklmnopqrstuvwxyz", ch) == NULL)
+ && (keymap[KHR(ch)] == KeyName)) {
+ result = TRUE;
+ }
+ }
+ return result;
+}
+
+/*
+ * This function returns the (int)ch mapped to the LYK_foo value passed to it
+ * as an argument. - FM
+ */
+int LYReverseKeymap(int KeyName)
+{
+ int i;
+ int result = -1;
+
+ for (i = 1; i < KEYMAP_SIZE; i++) {
+ if (keymap[i] == KeyName) {
+ result = (i - 1);
+ break;
+ }
+ }
+
+ return result;
+}
+
+#ifdef EXP_KEYBOARD_LAYOUT
+BOOLEAN LYSetKbLayout(char *layout_id)
+{
+ int i;
+ BOOLEAN result = FALSE;
+
+ for (i = 0; i < (int) TABLESIZE(LYKbLayoutNames) - 1; i++) {
+ if (!strcasecomp(LYKbLayoutNames[i], layout_id)) {
+ current_layout = i;
+ result = TRUE;
+ break;
+ }
+ }
+
+ return result;
+}
+#endif
+
+#if 0
+/*
+ * This function was useful in converting the hand-crafted key-bindings to
+ * their reusable form in 2.8.8 -TD
+ */
+static void checkKeyMap(LYEditConfig * table)
+{
+ unsigned j, k;
+ char comment[80];
+ int first = TRUE;
+
+ for (j = 0; table->init[j].code >= 0; ++j) {
+ int code = table->init[j].code;
+
+ if (table->init[j].edit != table->used[code]) {
+ if (first) {
+ printf("TABLE %s\n", table->name);
+ first = FALSE;
+ }
+ printf("%u: init %d vs used %d\n",
+ j,
+ table->init[j].edit,
+ table->used[code]);
+ }
+ }
+ for (j = 0; j < KEYMAP_SIZE; ++j) {
+ int code = (int) j;
+ BOOL found = FALSE;
+
+ for (k = 0; table->init[k].code >= 0; ++k) {
+ if (code == table->init[k].code) {
+ found = TRUE;
+ break;
+ }
+ }
+ if (!found) {
+ if (table->used[j] != 0) {
+ unsigned used = (j - 1);
+ int edit = table->used[j];
+ const char *prefix = "LYK_";
+ const char *name = 0;
+ Kcmd *cmd = LYKeycodeToKcmd(edit + 0);
+
+ if (cmd != 0) {
+ name = cmd->name;
+ }
+
+ if (used < 32) {
+ char temp[80];
+ const char *what = 0;
+
+ switch (used) {
+ case 0:
+ what = "nul";
+ break;
+ case 17:
+ what = "XON";
+ break;
+ case 19:
+ what = "XOFF";
+ break;
+ default:
+ sprintf(temp, "^%c", used + 'A');
+ what = temp;
+ break;
+ }
+ sprintf(comment, "\t/* %s */", what);
+ } else if (used < 127) {
+ sprintf(comment, "\t/* %c */", used);
+ } else if (used == 127) {
+ strcpy(comment, "\t/* DEL */");
+ } else {
+ const char *what = LYextraKeysToName(used);
+
+ if (non_empty(what)) {
+ sprintf(comment, "\t/* %s%s */", what,
+ ((StrChr(what, '_') != 0)
+ ? ""
+ : "_KEY"));
+ } else {
+ strcpy(comment, "");
+ }
+ }
+ if (name == 0) {
+ name = "XXX";
+ }
+ if (first) {
+ printf("TABLE %s\n", table->name);
+ first = FALSE;
+ }
+ printf("\t{ %d, %s%s },%s\n", code, prefix, name, comment);
+ }
+ }
+ }
+}
+
+#else
+#define checkKeyMap(table) /* nothing */
+#endif
+
+static void initKeyMap(LYEditConfig * table)
+{
+ unsigned k;
+ LYEditCode *used = table->used;
+ const LYEditInit *init = table->init;
+
+ memset(used, 0, sizeof(LYEditCode) * KEYMAP_SIZE);
+
+ for (k = 0; init[k].code >= 0; ++k) {
+ int code = init[k].code;
+
+ used[code] = init[k].edit;
+ }
+ checkKeyMap(table);
+}
+
+/*
+ * Reset the key bindings to their default values.
+ */
+void LYinitKeymap(void)
+{
+ CTRACE((tfp, "LYinitKeymap\n"));
+ initKeyMap(&myKeymapData);
+#if defined(DIRED_SUPPORT) && defined(OK_OVERRIDE)
+ initKeyMap(&myOverrideData);
+#endif
+}
diff --git a/src/LYKeymap.h b/src/LYKeymap.h
new file mode 100644
index 0000000..a06db04
--- /dev/null
+++ b/src/LYKeymap.h
@@ -0,0 +1,307 @@
+/* $LynxId: LYKeymap.h,v 1.55 2015/10/07 23:34:55 tom Exp $ */
+#ifndef LYKEYMAP_H
+#define LYKEYMAP_H
+
+#include <HTUtils.h>
+#include <HTList.h>
+#include <LYCurses.h>
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+ extern BOOLEAN LYisNonAlnumKeyname(int ch, int KeyName);
+ extern HTList *LYcommandList(void);
+ extern const char *lec_to_lecname(int code);
+ extern char *LYKeycodeToString(int c, int upper8);
+ extern char *fmt_keys(int lkc_first, int lkc_second);
+ extern char *key_for_func(int func);
+ extern char *key_for_func_ext(int lac, int context_code);
+ extern int LYReverseKeymap(int KeyName);
+ extern int LYStringToKeycode(char *src);
+ extern int lacname_to_lac(const char *func);
+ extern int lecname_to_lec(const char *func);
+ extern int lkcstring_to_lkc(const char *src);
+ extern int remap(char *key, const char *func, int for_dired);
+ extern void print_keymap(char **newfile);
+ extern void reset_emacs_keys(void);
+ extern void reset_numbers_as_arrows(void);
+ extern void reset_vi_keys(void);
+ extern void set_emacs_keys(void);
+ extern void set_numbers_as_arrows(void);
+ extern void set_vi_keys(void);
+ extern void set_vms_keys(void);
+
+/* We only use unsigned keycodes; if there's a problem matching with enum
+ * (which is supposed to be 'int'), that would be okay, but not as clean
+ * for type-checking.
+ */
+ typedef short LYKeymap_t;
+
+#define KEYMAP_SIZE 661
+ extern LYKeymap_t keymap[KEYMAP_SIZE]; /* main keymap matrix */
+
+#ifdef EXP_KEYBOARD_LAYOUT
+ typedef unsigned short LYKbLayout_t;
+ extern int current_layout;
+ extern LYKbLayout_t *LYKbLayouts[];
+ extern const char *LYKbLayoutNames[];
+ extern BOOLEAN LYSetKbLayout(char *layout_id);
+#endif
+
+#if defined(DIRED_SUPPORT) && defined(OK_OVERRIDE)
+ extern LYKeymap_t key_override[];
+#endif
+
+/* readable mapping for characters in edit- and key-maps */
+#define CTL(c) ((c) & 0x1f)
+#define KHR(c) ((c) + 1)
+#define KTL(c) (CTL(c) + 1)
+
+/* * * LynxKeyCodes * * */
+#define LKC_ISLECLAC 0x8000 /* flag: contains lynxaction + editaction */
+#define LKC_MOD1 0x4000 /* a modifier bit - currently for ^x-map */
+#define LKC_MOD2 0x2000 /* another one - currently for esc-map */
+#define LKC_MOD3 0x1000 /* another one - currently for double-map */
+#define LKC_ISLAC 0x0800 /* flag: lynxkeycode already lynxactioncode */
+
+/* Used to distinguish internal Lynx keycodes of (say) extended ncurses once. */
+#define LKC_ISLKC 0x0400 /* flag: already lynxkeycode (not native) */
+ /* 0x0400 is MOUSE_KEYSYM for slang in LYStrings.c */
+#define LKC_MASK 0x07FF /* mask for lynxkeycode proper */
+
+#define LKC_DONE 0x07FE /* special value - operation done, not-a-key */
+
+/* * * LynxActionCodes * * */
+#define LAC_SHIFT 8 /* shift for lynxactioncode - must not
+ overwrite any assigned LYK_* values */
+#define LAC_MASK ((1<<LAC_SHIFT)-1)
+ /* mask for lynxactioncode - must cover all
+ assigned LYK_* values */
+
+/* Return lkc masking single actioncode, given an lkc masking a lac + lec */
+#define LKC2_TO_LKC(c) (((c) == -1 || !((c) & LKC_ISLECLAC)) ? (c) : \
+ (((c) & LAC_MASK) | LKC_ISLAC))
+
+/* Return lynxeditactioncode, given an lkc masking a lac + lec */
+#define LKC2_TO_LEC(c) (((c) == -1 || !((c) & LKC_ISLECLAC)) ? (c) : \
+ ((((c)&~LKC_ISLECLAC)>>LAC_SHIFT) & LAC_MASK))
+
+/* Convert lynxkeycode to lynxactioncode. Modifiers are dropped. */
+#define LKC_TO_LAC(ktab,c) (((c) == -1) ? ktab[0] : \
+ ((c) & (LKC_ISLECLAC|LKC_ISLAC)) ? ((c) & LAC_MASK) : \
+ ktab[((c) & LKC_MASK) + 1])
+
+/* Mask lynxactioncode as a lynxkeycode. */
+#define LAC_TO_LKC0(a) ((a)|LKC_ISLAC)
+
+/* Mask a lynxactioncode and an editactioncode as a lynxkeycode. */
+#define LACLEC_TO_LKC0(a,b) ((a)|((b)<<LAC_SHIFT)|LKC_ISLECLAC)
+
+/* Convert lynxactioncode to a lynxkeycode, attempting reverse mapping. */
+#define LAC_TO_LKC(a) ((LYReverseKeymap(a)>=0)?LYReverseKeymap(a):LAC_TO_LKC0(a))
+
+/* Simplify a lynxkeycode:
+ attempt reverse mapping if a single masked lynxactioncode, drop modifiers. */
+#define LKC_TO_C(c) ((c&LKC_ISLECLAC)? c : (c&LKC_ISLAC)? LAC_TO_LKC(c&LAC_MASK) : (c&LKC_MASK))
+
+#define LKC_HAS_ESC_MOD(c) (c >= 0 && !(c&LKC_ISLECLAC) && (c&LKC_MOD2))
+
+/* * The defined LynxActionCodes * */
+
+/* Variables for holding and passing around lynxactioncodes are generally of
+ * type int, the types LYKeymap_t and LYKeymapCodes are currently only used for
+ * the definitions. That could change. - kw
+ *
+ * The values in this enum are indexed against the command names in the
+ * 'revmap[]' array in LYKeymap.c
+ */
+ typedef enum {
+ LYK_UNKNOWN = 0
+ ,LYK_COMMAND
+ ,LYK_1
+ ,LYK_2
+ ,LYK_3
+ ,LYK_4
+ ,LYK_5
+ ,LYK_6
+ ,LYK_7
+ ,LYK_8
+ ,LYK_9
+ ,LYK_SOURCE
+ ,LYK_RELOAD
+ ,LYK_QUIT
+ ,LYK_ABORT
+ ,LYK_NEXT_PAGE
+ ,LYK_PREV_PAGE
+ ,LYK_UP_TWO
+ ,LYK_DOWN_TWO
+ ,LYK_UP_HALF
+ ,LYK_DOWN_HALF
+ ,LYK_REFRESH
+ ,LYK_HOME
+ ,LYK_END
+ ,LYK_FIRST_LINK
+ ,LYK_LAST_LINK
+ ,LYK_PREV_LINK
+ ,LYK_NEXT_LINK
+ ,LYK_LPOS_PREV_LINK
+ ,LYK_LPOS_NEXT_LINK
+ ,LYK_FASTBACKW_LINK
+ ,LYK_FASTFORW_LINK
+ ,LYK_UP_LINK
+ ,LYK_DOWN_LINK
+ ,LYK_RIGHT_LINK
+ ,LYK_LEFT_LINK
+ ,LYK_HISTORY
+ ,LYK_PREV_DOC
+ ,LYK_NEXT_DOC
+ ,LYK_ACTIVATE
+ ,LYK_MOUSE_SUBMIT /* mostly like LYK_ACTIVATE, for mouse use, don't map */
+ ,LYK_SUBMIT
+ ,LYK_RESET
+ ,LYK_GOTO
+ ,LYK_ECGOTO
+ ,LYK_HELP
+ ,LYK_DWIMHELP
+ ,LYK_INDEX
+ ,LYK_NOCACHE
+ ,LYK_INTERRUPT
+ ,LYK_MAIN_MENU
+ ,LYK_OPTIONS
+ ,LYK_INDEX_SEARCH
+ ,LYK_WHEREIS
+ ,LYK_PREV
+ ,LYK_NEXT
+ ,LYK_COMMENT
+ ,LYK_EDIT
+ ,LYK_INFO
+ ,LYK_PRINT
+ ,LYK_ADD_BOOKMARK
+ ,LYK_DEL_BOOKMARK
+ ,LYK_VIEW_BOOKMARK
+ ,LYK_VLINKS
+ ,LYK_SHELL
+ ,LYK_DOWNLOAD
+ ,LYK_TRACE_TOGGLE
+ ,LYK_TRACE_LOG
+ ,LYK_IMAGE_TOGGLE
+ ,LYK_INLINE_TOGGLE
+ ,LYK_HEAD
+ ,LYK_DO_NOTHING
+ ,LYK_TOGGLE_HELP
+ ,LYK_JUMP
+ ,LYK_EDITMAP
+ ,LYK_KEYMAP
+ ,LYK_LIST
+ ,LYK_TOOLBAR
+ ,LYK_HISTORICAL
+ ,LYK_MINIMAL
+ ,LYK_SOFT_DQUOTES
+ ,LYK_RAW_TOGGLE
+ ,LYK_COOKIE_JAR
+ ,LYK_F_LINK_NUM
+ ,LYK_CLEAR_AUTH
+ ,LYK_SWITCH_DTD
+ ,LYK_ELGOTO
+ ,LYK_CHANGE_LINK
+ ,LYK_DWIMEDIT
+ ,LYK_EDITTEXTAREA
+ ,LYK_GROWTEXTAREA
+ ,LYK_INSERTFILE
+
+#ifdef USE_ADDRLIST_PAGE
+ ,LYK_ADDRLIST
+#else
+#define LYK_ADDRLIST LYK_ADD_BOOKMARK
+#endif
+
+#ifdef USE_EXTERNALS
+ ,LYK_EXTERN_LINK
+ ,LYK_EXTERN_PAGE
+#else
+#define LYK_EXTERN_LINK LYK_UNKNOWN
+#define LYK_EXTERN_PAGE LYK_UNKNOWN
+#endif /* !defined(USE_EXTERNALS) */
+
+#if defined(VMS) || defined(DIRED_SUPPORT)
+ ,LYK_DIRED_MENU
+#else
+#define LYK_DIRED_MENU LYK_UNKNOWN
+#endif /* VMS || DIRED_SUPPORT */
+
+#ifdef DIRED_SUPPORT
+ ,LYK_CREATE
+ ,LYK_REMOVE
+ ,LYK_MODIFY
+ ,LYK_TAG_LINK
+ ,LYK_UPLOAD
+ ,LYK_INSTALL
+#else
+#define LYK_TAG_LINK LYK_UNKNOWN
+#endif /* DIRED_SUPPORT */
+
+ ,LYK_CHANGE_CENTER
+
+#ifdef KANJI_CODE_OVERRIDE
+ ,LYK_CHANGE_KCODE
+#endif
+
+#ifdef SUPPORT_CHDIR
+ ,LYK_CHDIR
+ ,LYK_PWD
+#endif
+
+#ifdef USE_CURSES_PADS
+ ,LYK_SHIFT_LEFT
+ ,LYK_SHIFT_RIGHT
+ ,LYK_LINEWRAP_TOGGLE
+#else
+#define LYK_SHIFT_LEFT LYK_UNKNOWN
+#define LYK_SHIFT_RIGHT LYK_UNKNOWN
+#define LYK_LINEWRAP_TOGGLE LYK_UNKNOWN
+#endif
+
+#ifdef CAN_CUT_AND_PASTE
+ ,LYK_PASTE_URL
+ ,LYK_TO_CLIPBOARD
+#else
+#define LYK_PASTE_URL LYK_UNKNOWN
+#define LYK_TO_CLIPBOARD LYK_UNKNOWN
+#endif
+
+#ifdef EXP_NESTED_TABLES
+ ,LYK_NESTED_TABLES
+#else
+#define LYK_NESTED_TABLES LYK_UNKNOWN
+#endif
+
+#ifdef USE_CACHEJAR
+ ,LYK_CACHE_JAR
+#else
+#define LYK_CACHE_JAR LYK_UNKNOWN
+#endif
+
+#ifdef USE_MAXSCREEN_TOGGLE
+ ,LYK_MAXSCREEN_TOGGLE
+#else
+#define LYK_MAXSCREEN_TOGGLE LYK_UNKNOWN
+#endif
+
+ } LYKeymapCode;
+
+/*
+ * Symbol table for internal commands.
+ */
+ typedef struct {
+ LYKeymapCode code;
+ const char *name;
+ const char *doc;
+ } Kcmd;
+
+ extern Kcmd *LYKeycodeToKcmd(LYKeymapCode code);
+ extern Kcmd *LYStringToKcmd(const char *name);
+
+#ifdef __cplusplus
+}
+#endif
+#endif /* LYKEYMAP_H */
diff --git a/src/LYLeaks.c b/src/LYLeaks.c
new file mode 100644
index 0000000..d082a77
--- /dev/null
+++ b/src/LYLeaks.c
@@ -0,0 +1,1169 @@
+/*
+ * $LynxId: LYLeaks.c,v 1.43 2018/12/27 23:48:37 Kamil.Dudka Exp $
+ *
+ * Copyright (c) 1994, University of Kansas, All Rights Reserved
+ * (this file was rewritten twice - 1998/1999 and 2003/2004)
+ *
+ * This code will be used only if LY_FIND_LEAKS is defined.
+ *
+ * Revision History:
+ * 05-26-94 created Lynx 2-3-1 Garrett Arch Blythe
+ * 10-30-97 modified to handle StrAllocCopy() and
+ * StrAllocCat(). - KW & FM
+ * 07-23-07 free leaks of THIS module too -TD
+ * 02-09-12 add bstring functions -TD
+ */
+
+/*
+ * Disable the overriding of the memory routines for this file.
+ */
+#define NO_MEMORY_TRACKING
+
+#include <HTUtils.h>
+#include <LYexit.h>
+#include <LYLeaks.h>
+#include <LYUtils.h>
+#include <LYGlobalDefs.h>
+
+#ifdef LY_FIND_LEAKS
+
+static AllocationList *ALp_RunTimeAllocations = NULL;
+
+#define LEAK_SUMMARY
+
+#ifdef LEAK_SUMMARY
+
+static size_t now_allocated = 0;
+static size_t peak_alloced = 0;
+
+static size_t total_alloced = 0;
+static size_t total_freed = 0;
+
+static long count_mallocs = 0;
+static long count_frees = 0;
+
+static void CountMallocs(size_t size)
+{
+ ++count_mallocs;
+ total_alloced += size;
+ now_allocated += size;
+ if (peak_alloced < now_allocated)
+ peak_alloced = now_allocated;
+}
+
+static void CountFrees(size_t size)
+{
+ ++count_frees;
+ total_freed += size;
+ now_allocated -= size;
+}
+
+#else
+#define CountMallocs(size) ++count_mallocs
+#define CountFrees(size) /* nothing */
+#endif
+
+/*
+ * Purpose: Add a new allocation item to the list.
+ * Arguments: ALp_new The new item to add.
+ * Return Value: void
+ * Remarks/Portability/Dependencies/Restrictions:
+ * Static function made to make code reusable in projects beyond
+ * Lynx (some might ask why not use HTList).
+ * Revision History:
+ * 05-26-94 created Lynx 2-3-1 Garrett Arch Blythe
+ */
+static void AddToList(AllocationList * ALp_new)
+{
+ /*
+ * Just make this the first item in the list.
+ */
+ ALp_new->ALp_Next = ALp_RunTimeAllocations;
+ ALp_RunTimeAllocations = ALp_new;
+}
+
+/*
+ * Purpose: Find the place in the list where vp_find is currently
+ * tracked.
+ * Arguments: vp_find A pointer to look for in the list.
+ * Return Value: AllocationList * Either vp_find's place in the
+ * list or NULL if not found.
+ * Remarks/Portability/Dependencies/Restrictions:
+ * Static function made to make code reusable in projects outside
+ * of Lynx (some might ask why not use HTList).
+ * Revision History:
+ * 05-26-94 created Lynx 2-3-1 Garrett Arch Blythe
+ */
+static AllocationList *FindInList(void *vp_find)
+{
+ AllocationList *ALp_find = ALp_RunTimeAllocations;
+
+ /*
+ * Go through the list of allocated pointers until end of list or vp_find
+ * is found.
+ */
+ while (ALp_find != NULL) {
+ if (ALp_find->vp_Alloced == vp_find) {
+ break;
+ }
+ ALp_find = ALp_find->ALp_Next;
+ }
+
+ return (ALp_find);
+}
+
+/*
+ * Purpose: Remove the specified item from the list.
+ * Arguments: ALp_del The item to remove from the list.
+ * Return Value: void
+ * Remarks/Portability/Dependencies/Restrictions:
+ * Static function made to make code reusable in projects outside
+ * of Lynx (some might ask why not use HTList).
+ * Revision History:
+ * 05-26-94 created Lynx 2-3-1 Garrett Arch Blythe
+ */
+static void RemoveFromList(AllocationList * ALp_del)
+{
+ AllocationList *ALp_findbefore = ALp_RunTimeAllocations;
+
+ /*
+ * There is one special case, where the item to remove is the first in the
+ * list.
+ */
+ if (ALp_del == ALp_findbefore) {
+ ALp_RunTimeAllocations = ALp_del->ALp_Next;
+ } else {
+
+ /*
+ * Loop through checking all of the next values, if a match don't
+ * continue. Always assume the item will be found.
+ */
+ while (ALp_findbefore->ALp_Next != ALp_del) {
+ ALp_findbefore = ALp_findbefore->ALp_Next;
+ }
+
+ /*
+ * We are one item before the one to get rid of. Get rid of it.
+ */
+ ALp_findbefore->ALp_Next = ALp_del->ALp_Next;
+ }
+}
+
+/*
+ * Make the malloc-sequence available for debugging/tracing.
+ */
+#ifndef LYLeakSequence
+long LYLeakSequence(void)
+{
+ return count_mallocs;
+}
+#endif
+
+/*
+ * Purpose: Print a report of all memory left unallocated by
+ * Lynx code or attempted unallocations on
+ * pointers that are not valid and then free
+ * all unfreed memory.
+ * Arguments: void
+ * Return Value: void
+ * Remarks/Portability/Dependencies/Restrictions:
+ * This function should be registered for execution with the
+ * atexit (stdlib.h) function as the first statement
+ * in main.
+ * All output of this function is sent to the file defined in
+ * the header LYLeaks.h (LEAKAGE_SINK).
+ */
+void LYLeaks(void)
+{
+ AllocationList *ALp_head;
+ size_t st_total = (size_t) 0;
+ FILE *Fp_leakagesink;
+
+ CTRACE((tfp, "entering LYLeaks, flag=%d\n", LYfind_leaks));
+
+ if (LYfind_leaks == FALSE) {
+ /*
+ * Free MY leaks too, in case someone else is watching.
+ */
+ while (ALp_RunTimeAllocations != NULL) {
+ ALp_head = ALp_RunTimeAllocations;
+ ALp_RunTimeAllocations = ALp_head->ALp_Next;
+ free(ALp_head);
+ }
+ return;
+ }
+
+ /*
+ * Open the leakage sink to take all the output. Recreate the file each
+ * time. Do nothing if unable to open the file.
+ */
+ Fp_leakagesink = LYNewTxtFile(LYLeaksPath);
+ if (Fp_leakagesink == NULL) {
+ return;
+ }
+
+ while (ALp_RunTimeAllocations != NULL) {
+ /*
+ * Take the head off of the run time allocation list.
+ */
+ ALp_head = ALp_RunTimeAllocations;
+ ALp_RunTimeAllocations = ALp_head->ALp_Next;
+
+ /*
+ * Print the type of leak/error. Release memory when we no longer
+ * need it.
+ */
+ if (ALp_head->vp_Alloced == NULL) {
+ /*
+ * If there is realloc information on the bad request, then it was
+ * a bad pointer value in a realloc statement.
+ */
+ fprintf(Fp_leakagesink, "%s.\n",
+ gettext("Invalid pointer detected."));
+ fprintf(Fp_leakagesink, "%s\t%ld\n",
+ gettext("Sequence:"),
+ ALp_head->st_Sequence);
+ fprintf(Fp_leakagesink, "%s\t%p\n",
+ gettext("Pointer:"), ALp_head->vp_BadRequest);
+
+ /*
+ * Don't free the bad request, it is an invalid pointer. If the
+ * free source information is empty, we should check the realloc
+ * information too since it can get passed bad pointer values also.
+ */
+ if (ALp_head->SL_memory.cp_FileName == NULL) {
+ fprintf(Fp_leakagesink, "%s\t%s\n",
+ gettext("FileName:"),
+ ALp_head->SL_realloc.cp_FileName);
+ fprintf(Fp_leakagesink, "%s\t%d\n",
+ gettext("LineCount:"),
+ ALp_head->SL_realloc.ssi_LineNumber);
+ } else {
+ fprintf(Fp_leakagesink, "%s\t%s\n",
+ gettext("FileName:"),
+ ALp_head->SL_memory.cp_FileName);
+ fprintf(Fp_leakagesink, "%s\t%d\n",
+ gettext("LineCount:"),
+ ALp_head->SL_memory.ssi_LineNumber);
+ }
+ } else {
+ size_t i_counter;
+ char *value = (char *) (ALp_head->vp_Alloced);
+
+ /*
+ * Increment the count of total memory lost and then print the
+ * information.
+ */
+ st_total += ALp_head->st_Bytes;
+
+ fprintf(Fp_leakagesink, "%s\n",
+ gettext("Memory leak detected."));
+ fprintf(Fp_leakagesink, "%s\t%ld\n",
+ gettext("Sequence:"),
+ ALp_head->st_Sequence);
+ fprintf(Fp_leakagesink, "%s\t%p\n",
+ gettext("Pointer:"),
+ ALp_head->vp_Alloced);
+ fprintf(Fp_leakagesink, "%s\t",
+ gettext("Contains:"));
+ for (i_counter = 0;
+ i_counter < ALp_head->st_Bytes &&
+ i_counter < MAX_CONTENT_LENGTH;
+ i_counter++) {
+ if (isprint(UCH(value[i_counter]))) {
+ fprintf(Fp_leakagesink, "%c", value[i_counter]);
+ } else {
+ fprintf(Fp_leakagesink, "|");
+ }
+ }
+ fprintf(Fp_leakagesink, "\n");
+ fprintf(Fp_leakagesink, "%s\t%d\n",
+ gettext("ByteSize:"),
+ (int) (ALp_head->st_Bytes));
+ fprintf(Fp_leakagesink, "%s\t%s\n",
+ gettext("FileName:"),
+ ALp_head->SL_memory.cp_FileName);
+ fprintf(Fp_leakagesink, "%s\t%d\n",
+ gettext("LineCount:"),
+ ALp_head->SL_memory.ssi_LineNumber);
+ /*
+ * Give the last time the pointer was realloced if it happened
+ * also.
+ */
+ if (ALp_head->SL_realloc.cp_FileName != NULL) {
+ fprintf(Fp_leakagesink, "%s\t%s\n",
+ gettext("realloced:"),
+ ALp_head->SL_realloc.cp_FileName);
+ fprintf(Fp_leakagesink, "%s\t%d\n",
+ gettext("LineCount:"),
+ ALp_head->SL_realloc.ssi_LineNumber);
+ }
+ fflush(Fp_leakagesink);
+ FREE(ALp_head->vp_Alloced);
+ }
+
+ /*
+ * Create a blank line and release the memory held by the item.
+ */
+ fprintf(Fp_leakagesink, "\n");
+ FREE(ALp_head);
+ }
+
+ /*
+ * Give a grand total of the leakage. Close the output file.
+ */
+ fprintf(Fp_leakagesink, "%s\t%u\n",
+ gettext("Total memory leakage this run:"),
+ (unsigned) st_total);
+#ifdef LEAK_SUMMARY
+ fprintf(Fp_leakagesink,
+ "%s\t%lu\n", gettext("Peak allocation"), (unsigned long) peak_alloced);
+ fprintf(Fp_leakagesink,
+ "%s\t%lu\n", gettext("Bytes allocated"), (unsigned long) total_alloced);
+ fprintf(Fp_leakagesink,
+ "%s\t%ld\n", gettext("Total mallocs"), count_mallocs);
+ fprintf(Fp_leakagesink,
+ "%s\t%ld\n", gettext("Total frees"), count_frees);
+#endif
+ fclose(Fp_leakagesink);
+
+ HTSYS_purge(LEAKAGE_SINK);
+}
+
+/*
+ * Purpose: Capture allocations using malloc (stdlib.h) and track
+ * the information in a list.
+ * Arguments: st_bytes The size of the allocation requested
+ * in bytes.
+ * cp_File The file from which the request for
+ * allocation came from.
+ * ssi_Line The line number in cp_File where the
+ * allocation request came from.
+ * Return Value: void * A pointer to the allocated memory or NULL on
+ * failure as per malloc (stdlib.h)
+ * Remarks/Portability/Dependencies/Restrictions:
+ * If no memory is allocated, then no entry is added to the
+ * allocation list.
+ * Revision History:
+ * 05-26-94 created Lynx 2-3-1 Garrett Arch Blythe
+ */
+void *LYLeakMalloc(size_t st_bytes, const char *cp_File,
+ const short ssi_Line)
+{
+ void *vp_malloc;
+
+ if (LYfind_leaks == FALSE) {
+ vp_malloc = (void *) malloc(st_bytes);
+ } else {
+
+ /*
+ * Do the actual allocation.
+ */
+ vp_malloc = (void *) malloc(st_bytes);
+ CountMallocs(st_bytes);
+
+ /*
+ * Only on successful allocation do we track any information.
+ */
+ if (vp_malloc != NULL) {
+ /*
+ * Further allocate memory to store the information. Just return
+ * on failure to allocate more.
+ */
+ AllocationList *ALp_new = typecalloc(AllocationList);
+
+ if (ALp_new != NULL) {
+ /*
+ * Copy over the relevant information. There is no need to
+ * allocate more memory for the file name as it is a static
+ * string anyway.
+ */
+ ALp_new->st_Sequence = count_mallocs;
+ ALp_new->vp_Alloced = vp_malloc;
+ ALp_new->st_Bytes = st_bytes;
+ ALp_new->SL_memory.cp_FileName = cp_File;
+ ALp_new->SL_memory.ssi_LineNumber = ssi_Line;
+
+ /*
+ * Add the new item to the allocation list.
+ */
+ AddToList(ALp_new);
+ }
+ }
+ }
+ return (vp_malloc);
+}
+
+/*
+ * Purpose: Add information about new allocation to the list,
+ * after a call to malloc or calloc or an equivalent
+ * function which may or may not have already created
+ * a list entry.
+ * Arguments: vp_malloc The pointer to newly allocated memory.
+ * Arguments: st_bytes The size of the allocation requested
+ * in bytes.
+ * cp_File The file from which the request for
+ * allocation came from.
+ * ssi_Line The line number in cp_File where the
+ * allocation request came from.
+ * Return Value: void * A pointer to the allocated memory or NULL on
+ * failure.
+ * Remarks/Portability/Dependencies/Restrictions:
+ * If no memory is allocated, then no entry is added to the
+ * allocation list.
+ * Revision History:
+ * 1999-02-08 created, modelled after LYLeakMalloc - kw
+ */
+AllocationList *LYLeak_mark_malloced(void *vp_malloced,
+ size_t st_bytes,
+ const char *cp_File,
+ const short ssi_Line)
+{
+ AllocationList *ALp_new = NULL;
+
+ if (LYfind_leaks != FALSE) {
+ /*
+ * The actual allocation has already been done!
+ *
+ * Only on successful allocation do we track any information.
+ */
+ if (vp_malloced != NULL) {
+ /*
+ * See if there is already an entry. If so, just update the source
+ * location info.
+ */
+ ALp_new = FindInList(vp_malloced);
+ if (ALp_new) {
+ ALp_new->SL_memory.cp_FileName = cp_File;
+ ALp_new->SL_memory.ssi_LineNumber = ssi_Line;
+ } else {
+ /*
+ * Further allocate memory to store the information. Just
+ * return on failure to allocate more.
+ */
+ ALp_new = typecalloc(AllocationList);
+ if (ALp_new != NULL) {
+ /*
+ * Copy over the relevant information.
+ */
+ ALp_new->vp_Alloced = vp_malloced;
+ ALp_new->st_Bytes = st_bytes;
+ ALp_new->SL_memory.cp_FileName = cp_File;
+ ALp_new->SL_memory.ssi_LineNumber = ssi_Line;
+
+ /*
+ * Add the new item to the allocation list.
+ */
+ AddToList(ALp_new);
+ CountMallocs(st_bytes);
+ }
+ }
+ }
+ }
+ return (ALp_new);
+}
+
+/*
+ * Purpose: Capture allocations by calloc (stdlib.h) and
+ * save relevant information in a list.
+ * Arguments: st_number The number of items to allocate.
+ * st_bytes The size of each item.
+ * cp_File The file which wants to allocation.
+ * ssi_Line The line number in cp_File requesting
+ * the allocation.
+ * Return Value: void * The allocated memory, or NULL on failure as
+ * per calloc (stdlib.h)
+ * Remarks/Portability/Dependencies/Restrictions:
+ * If no memory can be allocated, then no entry will be added
+ * to the list.
+ * Revision History:
+ * 05-26-94 created Lynx 2-3-1 Garrett Arch Blythe
+ */
+void *LYLeakCalloc(size_t st_number, size_t st_bytes, const char *cp_File,
+ const short ssi_Line)
+{
+ void *vp_calloc;
+
+ if (LYfind_leaks == FALSE) {
+ vp_calloc = (void *) calloc(st_number, st_bytes);
+ } else {
+
+ /*
+ * Allocate the requested memory.
+ */
+ vp_calloc = (void *) calloc(st_number, st_bytes);
+ CountMallocs(st_bytes * st_number);
+
+ /*
+ * Only if the allocation was a success do we track information.
+ */
+ if (vp_calloc != NULL) {
+ /*
+ * Allocate memory for the item to be in the list. If unable, just
+ * return.
+ */
+ AllocationList *ALp_new = typecalloc(AllocationList);
+
+ if (ALp_new != NULL) {
+
+ /*
+ * Copy over the relevant information. There is no need to
+ * allocate memory for the file name as it is a static string
+ * anyway.
+ */
+ ALp_new->st_Sequence = count_mallocs;
+ ALp_new->vp_Alloced = vp_calloc;
+ ALp_new->st_Bytes = (st_number * st_bytes);
+ ALp_new->SL_memory.cp_FileName = cp_File;
+ ALp_new->SL_memory.ssi_LineNumber = ssi_Line;
+
+ /*
+ * Add the item to the allocation list.
+ */
+ AddToList(ALp_new);
+ }
+ }
+ }
+ return (vp_calloc);
+}
+
+/*
+ * Purpose: Capture any realloc (stdlib.h) calls in order to
+ * properly keep track of our run time allocation
+ * table.
+ * Arguments: vp_Alloced The previously allocated block of
+ * memory to resize. If NULL,
+ * realloc works just like
+ * malloc.
+ * st_newBytes The new size of the chunk of memory.
+ * cp_File The file containing the realloc.
+ * ssi_Line The line containing the realloc in cp_File.
+ * Return Value: void * The new pointer value (could be the same) or
+ * NULL if unable to resize (old block
+ * still exists).
+ * Remarks/Portability/Dependencies/Restrictions:
+ * If unable to resize vp_Alloced, then no change in the
+ * allocation list will be made.
+ * If vp_Alloced is an invalid pointer value, the program will
+ * exit after one last entry is added to the allocation list.
+ * Revision History:
+ * 05-26-94 created Lynx 2-3-1 Garrett Arch Blythe
+ */
+void *LYLeakRealloc(void *vp_Alloced,
+ size_t st_newBytes,
+ const char *cp_File,
+ const short ssi_Line)
+{
+ void *vp_realloc;
+ AllocationList *ALp_renew;
+
+ if (LYfind_leaks == FALSE) {
+ vp_realloc = (void *) realloc(vp_Alloced, st_newBytes);
+
+ } else if (vp_Alloced == NULL) {
+ /*
+ * If we are asked to resize a NULL pointer, this is just a malloc
+ * call.
+ */
+ vp_realloc = LYLeakMalloc(st_newBytes, cp_File, ssi_Line);
+
+ } else {
+
+ /*
+ * Find the current vp_Alloced block in the list. If NULL, this is an
+ * invalid pointer value.
+ */
+ ALp_renew = FindInList(vp_Alloced);
+ if (ALp_renew == NULL) {
+ /*
+ * Track the invalid pointer value and then exit. If unable to
+ * allocate, just exit.
+ */
+ AllocationList *ALp_new = typecalloc(AllocationList);
+
+ if (ALp_new == NULL) {
+ exit_immediately(EXIT_FAILURE);
+ }
+
+ /*
+ * Set the information up; no need to allocate file name since it is a
+ * static string.
+ */
+ ALp_new->vp_Alloced = NULL;
+ ALp_new->vp_BadRequest = vp_Alloced;
+ ALp_new->SL_realloc.cp_FileName = cp_File;
+ ALp_new->SL_realloc.ssi_LineNumber = ssi_Line;
+
+ /*
+ * Add the item to the list. Exit.
+ */
+ AddToList(ALp_new);
+ exit_immediately(EXIT_FAILURE);
+ }
+
+ /*
+ * Perform the resize. If not NULL, record the information.
+ */
+ vp_realloc = (void *) realloc(vp_Alloced, st_newBytes);
+ CountFrees(ALp_renew->st_Bytes);
+ CountMallocs(st_newBytes);
+
+ if (vp_realloc != NULL) {
+ ALp_renew->st_Sequence = count_mallocs;
+ ALp_renew->vp_Alloced = vp_realloc;
+ ALp_renew->st_Bytes = st_newBytes;
+
+ /*
+ * Update the realloc information, too. No need to allocate file name,
+ * static string.
+ */
+ ALp_renew->SL_realloc.cp_FileName = cp_File;
+ ALp_renew->SL_realloc.ssi_LineNumber = ssi_Line;
+ }
+ }
+ return (vp_realloc);
+}
+
+/*
+ * Purpose: Add information about reallocated memory to the list,
+ * after a call to realloc or an equivalent
+ * function which has not already created or updated
+ * a list entry.
+ * Arguments: ALp_old List entry for previously allocated
+ * block of memory to resize. If NULL,
+ * mark_realloced works just like
+ * mark_malloced.
+ * vp_realloced The new pointer, after resizing.
+ * st_newBytes The new size of the chunk of memory.
+ * cp_File The file to record.
+ * ssi_Line The line to record.
+ * Return Value: Pointer to new or updated list entry
+ * for this memory block.
+ * NULL on allocation error.
+ * Revision History:
+ * 1999-02-11 created kw
+ */
+#if defined(LY_FIND_LEAKS) && defined(LY_FIND_LEAKS_EXTENDED)
+static AllocationList *mark_realloced(AllocationList * ALp_old, void *vp_realloced,
+ size_t st_newBytes,
+ const char *cp_File,
+ const short ssi_Line)
+{
+ /*
+ * If there is no list entry for the old allocation, treat this as if a new
+ * allocation had happened.
+ */
+ if (ALp_old == NULL) {
+ return (LYLeak_mark_malloced(vp_realloced, st_newBytes, cp_File, ssi_Line));
+ }
+
+ /*
+ * ALp_old represents the memory block before reallocation. Assume that if
+ * we get here, there isn't yet a list entry for the new, possibly
+ * different, address after realloc, that is our list hasn't been updated -
+ * so we're going to do that now.
+ */
+
+ if (vp_realloced != NULL) {
+ ALp_old->vp_Alloced = vp_realloced;
+ ALp_old->st_Bytes = st_newBytes;
+ ALp_old->SL_realloc.cp_FileName = cp_File;
+ ALp_old->SL_realloc.ssi_LineNumber = ssi_Line;
+ }
+
+ return (ALp_old);
+}
+#endif /* not LY_FIND_LEAKS and LY_FIND_LEAKS_EXTENDED */
+
+/*
+ * Purpose: Capture all requests to free information and also
+ * remove items from the allocation list.
+ * Arguments: vp_Alloced The memory to free.
+ * cp_File The file calling free.
+ * ssi_Line The line of cp_File calling free.
+ * Return Value: void
+ * Remarks/Portability/Dependencies/Restrictions:
+ * If the pointer value is invalid, then an item will be added
+ * to the list and nothing else is done.
+ * I really like the name of this function and one day hope
+ * that Lynx is Leak Free.
+ * Revision History:
+ * 05-26-94 created Lynx 2-3-1 Garrett Arch Blythe
+ */
+void LYLeakFree(void *vp_Alloced,
+ const char *cp_File,
+ const short ssi_Line)
+{
+ AllocationList *ALp_free;
+
+ if (LYfind_leaks == FALSE) {
+ free(vp_Alloced);
+ } else {
+
+ /*
+ * Find the pointer in the allocated list. If not found, bad pointer.
+ * If found, free list item and vp_Alloced.
+ */
+ ALp_free = FindInList(vp_Alloced);
+ if (ALp_free == NULL) {
+ /*
+ * Create the final entry before exiting marking this error. If
+ * unable to allocate more memory just exit.
+ */
+ AllocationList *ALp_new = typecalloc(AllocationList);
+
+ if (ALp_new == NULL) {
+ exit_immediately(EXIT_FAILURE);
+ }
+
+ /*
+ * Set up the information, no memory need be allocated for the file
+ * name since it is a static string.
+ */
+ ALp_new->vp_Alloced = NULL;
+ ALp_new->vp_BadRequest = vp_Alloced;
+ ALp_new->SL_memory.cp_FileName = cp_File;
+ ALp_new->SL_memory.ssi_LineNumber = ssi_Line;
+
+ /*
+ * Add the entry to the list and then return.
+ */
+ AddToList(ALp_new);
+ } else {
+ /*
+ * Free off the memory. Take entry out of allocation list.
+ */
+ CountFrees(ALp_free->st_Bytes);
+ RemoveFromList(ALp_free);
+ FREE(ALp_free);
+ free(vp_Alloced);
+ }
+ }
+}
+
+/*
+ * Check for leaked strdup() results -TD
+ */
+char *LYLeakStrdup(const char *source,
+ const char *cp_File,
+ const short ssi_Line)
+{
+ size_t length = strlen(source) + 1;
+ char *target = (char *) LYLeakMalloc(length, cp_File, ssi_Line);
+
+ if (target != 0) {
+ memcpy(target, source, length);
+ }
+ return target;
+}
+
+/*
+ * Allocates a new copy of a string, and returns it.
+ * Tracks allocations by using other LYLeakFoo functions.
+ * Equivalent to HTSACopy in HTString.c - KW
+ */
+char *LYLeakSACopy(char **dest,
+ const char *src,
+ const char *cp_File,
+ const short ssi_Line)
+{
+ if (src != NULL && src == *dest) {
+ CTRACE((tfp,
+ "LYLeakSACopy: *dest equals src, contains \"%s\"\n",
+ src));
+ } else {
+ if (*dest) {
+ LYLeakFree(*dest, cp_File, ssi_Line);
+ *dest = NULL;
+ }
+ if (src) {
+ *dest = (char *) LYLeakMalloc(strlen(src) + 1, cp_File, ssi_Line);
+ if (*dest == NULL)
+ outofmem(__FILE__, "LYLeakSACopy");
+ strcpy(*dest, src);
+ }
+ }
+ return *dest;
+}
+
+/*
+ * String Allocate and Concatenate.
+ * Tracks allocations by using other LYLeakFoo functions.
+ * Equivalent to HTSACat in HTUtils.c - KW
+ */
+char *LYLeakSACat(char **dest,
+ const char *src,
+ const char *cp_File,
+ const short ssi_Line)
+{
+ if (src && *src) {
+ if (src == *dest) {
+ CTRACE((tfp,
+ "LYLeakSACat: *dest equals src, contains \"%s\"\n",
+ src));
+ } else if (*dest) {
+ size_t length = strlen(*dest);
+
+ *dest = (char *) LYLeakRealloc(*dest,
+ (length + strlen(src) + 1),
+ cp_File,
+ ssi_Line);
+ if (*dest == NULL)
+ outofmem(__FILE__, "LYLeakSACat");
+ strcpy(*dest + length, src);
+ } else {
+ *dest = (char *) LYLeakMalloc((strlen(src) + 1),
+ cp_File,
+ ssi_Line);
+ if (*dest == NULL)
+ outofmem(__FILE__, "LYLeakSACat");
+ strcpy(*dest, src);
+ }
+ }
+ return *dest;
+}
+
+/******************************************************************************/
+
+/*
+ * Equivalents for bstring functions in HTString.c -TD
+ */
+/* same as HTSABAlloc */
+void LYLeakSABAlloc(bstring **dest,
+ int len,
+ const char *cp_File,
+ const short ssi_Line)
+{
+ if (*dest == 0) {
+ *dest = LYLeakCalloc(1, sizeof(bstring), cp_File, ssi_Line);
+ }
+
+ if ((*dest)->len != len) {
+ (*dest)->str = (char *) LYLeakRealloc((*dest)->str,
+ (size_t) len,
+ cp_File,
+ ssi_Line);
+ if ((*dest)->str == NULL)
+ outofmem(__FILE__, "LYLeakSABalloc");
+
+ (*dest)->len = len;
+ }
+}
+
+/* same as HTSABCopy */
+void LYLeakSABCopy(bstring **dest,
+ const char *src,
+ int len,
+ const char *cp_File,
+ const short ssi_Line)
+{
+ bstring *t;
+ unsigned need = (unsigned) (len + 1);
+
+ CTRACE2(TRACE_BSTRING,
+ (tfp, "HTSABCopy(%p, %p, %d)\n",
+ (void *) dest, (const void *) src, len));
+ LYLeakSABFree(dest, cp_File, ssi_Line);
+ if (src) {
+ if (TRACE_BSTRING) {
+ CTRACE((tfp, "=== %4d:", len));
+ trace_bstring2(src, len);
+ CTRACE((tfp, "\n"));
+ }
+ if ((t = (bstring *) LYLeakMalloc(sizeof(bstring), cp_File, ssi_Line))
+ == NULL)
+ outofmem(__FILE__, "HTSABCopy");
+
+ if ((t->str = (char *) LYLeakMalloc(need, cp_File, ssi_Line)) == NULL)
+ outofmem(__FILE__, "HTSABCopy");
+
+ MemCpy(t->str, src, len);
+ t->len = len;
+ t->str[t->len] = '\0';
+ *dest = t;
+ }
+ if (TRACE_BSTRING) {
+ CTRACE((tfp, "=> %4d:", BStrLen(*dest)));
+ trace_bstring(*dest);
+ CTRACE((tfp, "\n"));
+ }
+}
+
+/* same as HTSABCopy0 */
+void LYLeakSABCopy0(bstring **dest,
+ const char *src,
+ const char *cp_File,
+ const short ssi_Line)
+{
+ LYLeakSABCopy(dest, src, (int) strlen(src), cp_File, ssi_Line);
+}
+
+/* same as HTSABCat */
+void LYLeakSABCat(bstring **dest,
+ const char *src,
+ int len,
+ const char *cp_File,
+ const short ssi_Line)
+{
+ bstring *t = *dest;
+
+ CTRACE2(TRACE_BSTRING,
+ (tfp, "HTSABCat(%p, %p, %d)\n",
+ (void *) dest, (const void *) src, len));
+ if (src) {
+ unsigned need = (unsigned) (len + 1);
+
+ if (TRACE_BSTRING) {
+ CTRACE((tfp, "=== %4d:", len));
+ trace_bstring2(src, len);
+ CTRACE((tfp, "\n"));
+ }
+ if (t) {
+ unsigned length = (unsigned) t->len + need;
+
+ t->str = (char *) LYLeakRealloc(t->str, length, cp_File, ssi_Line);
+ } else {
+ if ((t = (bstring *) LYLeakCalloc(1, sizeof(bstring), cp_File,
+ ssi_Line)) == NULL)
+ outofmem(__FILE__, "HTSACat");
+
+ t->str = (char *) LYLeakMalloc(need, cp_File, ssi_Line);
+ }
+ if (t->str == NULL)
+ outofmem(__FILE__, "HTSACat");
+
+ MemCpy(t->str + t->len, src, len);
+ t->len += len;
+ t->str[t->len] = '\0';
+ *dest = t;
+ }
+ if (TRACE_BSTRING) {
+ CTRACE((tfp, "=> %4d:", BStrLen(*dest)));
+ trace_bstring(*dest);
+ CTRACE((tfp, "\n"));
+ }
+}
+
+/* same as HTSABCat0 */
+void LYLeakSABCat0(bstring **dest,
+ const char *src,
+ const char *cp_File,
+ const short ssi_Line)
+{
+ LYLeakSABCat(dest, src, (int) strlen(src), cp_File, ssi_Line);
+}
+
+/* same as HTSABFree */
+void LYLeakSABFree(bstring **ptr,
+ const char *cp_File,
+ const short ssi_Line)
+{
+ if (*ptr != NULL) {
+ if ((*ptr)->str)
+ LYLeakFree((*ptr)->str, cp_File, ssi_Line);
+ LYLeakFree(*ptr, cp_File, ssi_Line);
+ *ptr = NULL;
+ }
+}
+
+/******************************************************************************/
+
+#if defined(LY_FIND_LEAKS) && defined(LY_FIND_LEAKS_EXTENDED)
+
+const char *leak_cp_File_hack = __FILE__;
+short leak_ssi_Line_hack = __LINE__;
+
+/*
+ * Purpose: A wrapper around StrAllocVsprintf (the workhorse of
+ * HTSprintf/HTSprintf0, implemented in HTString.c) that
+ * tries to make sure that our allocation list is always
+ * properly updated, whether StrAllocVsprintf itself was
+ * compiled with memory tracking or not (or even a mixture,
+ * like tracking the freeing but not the new allocation).
+ * Some source files can be compiled with LY_FIND_LEAKS_EXTENDED
+ * in effect while others only have LY_FIND_LEAKS in effect,
+ * and as long as HTString.c is complied with memory tracking
+ * (of either kind) string objects allocated by HTSprintf/
+ * HTSprintf0 (or otherwise) can be passed around among them and
+ * manipulated both ways.
+ * Arguments: dest As for StrAllocVsprintf.
+ * cp_File The source file of the caller (i.e. the
+ * caller of HTSprintf/HTSprintf0, hopefully).
+ * ssi_Line The line of cp_File calling.
+ * inuse,fmt,ap As for StrAllocVsprintf.
+ * Return Value: The char pointer to resulting string, as set
+ * by StrAllocVsprintf, or
+ * NULL if dest==0 (wrong use!).
+ * Remarks/Portability/Dependencies/Restrictions:
+ * The price for generality is severe inefficiency: several
+ * list lookups are done to be on the safe side.
+ * We don't get the real allocation size, only a minimum based
+ * on the string length of the result. So the amount of memory
+ * leakage may get underestimated.
+ * If *dest is an invalid pointer value on entry (i.e. was not
+ * tracked), the program will exit after one last entry is added
+ * to the allocation list.
+ * If StrAllocVsprintf fails to return a valid string via the
+ * indirect string pointer (its first parameter), invalid memory
+ * access will result and the program will probably terminate
+ * with a signal. This can happen if, on entry, *dest is NULL
+ * and fmt is empty or NULL, so just Don't Do That.
+ * Revision History:
+ * 1999-02-11 created kw
+ * 1999-10-15 added comments kw
+ */
+static char *LYLeakSAVsprintf(char **dest,
+ const char *cp_File,
+ const short ssi_Line,
+ size_t inuse,
+ const char *fmt,
+ va_list * ap)
+{
+ AllocationList *ALp_old;
+ void *vp_oldAlloced;
+
+ const char *old_cp_File = __FILE__;
+ short old_ssi_Line = __LINE__;
+
+ if (!dest)
+ return NULL;
+
+ if (LYfind_leaks == FALSE) {
+ StrAllocVsprintf(dest, inuse, fmt, ap);
+ return (*dest);
+ }
+
+ vp_oldAlloced = *dest;
+ if (!vp_oldAlloced) {
+ StrAllocVsprintf(dest, inuse, fmt, ap);
+ LYLeak_mark_malloced(*dest, strlen(*dest) + 1, cp_File, ssi_Line);
+ return (*dest);
+ } else {
+ void *vp_realloced;
+
+ ALp_old = FindInList(vp_oldAlloced);
+ if (ALp_old == NULL) {
+ /*
+ * Track the invalid pointer value and then exit. If unable to
+ * allocate, just exit.
+ */
+ AllocationList *ALp_new = typecalloc(AllocationList);
+
+ if (ALp_new == NULL) {
+ exit_immediately(EXIT_FAILURE);
+ }
+
+ /*
+ * Set the information up; no need to allocate file name since it
+ * is a static string.
+ */
+ ALp_new->vp_Alloced = NULL;
+ ALp_new->vp_BadRequest = vp_oldAlloced;
+ ALp_new->SL_realloc.cp_FileName = cp_File;
+ ALp_new->SL_realloc.ssi_LineNumber = ssi_Line;
+
+ /*
+ * Add the item to the list. Exit.
+ */
+ AddToList(ALp_new);
+ exit_immediately(EXIT_FAILURE);
+ }
+
+ old_cp_File = ALp_old->SL_memory.cp_FileName;
+ old_ssi_Line = ALp_old->SL_memory.ssi_LineNumber;
+ /*
+ * DO THE REAL WORK, by calling StrAllocVsprintf. If result is not
+ * NULL, record the information.
+ */
+ StrAllocVsprintf(dest, inuse, fmt, ap);
+ vp_realloced = (void *) *dest;
+ if (vp_realloced != NULL) {
+ AllocationList *ALp_new = FindInList(vp_realloced);
+
+ if (!ALp_new) {
+ /* Look up again, list may have changed! - kw */
+ ALp_old = FindInList(vp_oldAlloced);
+ if (ALp_old == NULL) {
+ LYLeak_mark_malloced(*dest, strlen(*dest) + 1, cp_File, ssi_Line);
+ return (*dest);
+ }
+ mark_realloced(ALp_old, *dest, strlen(*dest) + 1, cp_File, ssi_Line);
+ return (*dest);
+ }
+ ALp_new->SL_memory.cp_FileName = old_cp_File;
+ ALp_new->SL_memory.ssi_LineNumber = old_ssi_Line;
+ ALp_new->SL_realloc.cp_FileName = cp_File;
+ ALp_new->SL_realloc.ssi_LineNumber = ssi_Line;
+ }
+ return (*dest);
+ }
+}
+
+/* Note: the following may need updating if HTSprintf in HTString.c
+ * is changed. - kw */
+static char *LYLeakHTSprintf(char **pstr, const char *fmt, ...)
+{
+ char *str;
+ size_t inuse = 0;
+ va_list ap;
+
+ LYva_start(ap, fmt);
+
+ if (pstr != 0 && *pstr != 0)
+ inuse = strlen(*pstr);
+ str = LYLeakSAVsprintf(pstr, leak_cp_File_hack, leak_ssi_Line_hack,
+ inuse, fmt, &ap);
+
+ va_end(ap);
+ return str;
+}
+
+/* Note: the following may need updating if HTSprintf0 in HTString.c
+ * is changed. - kw */
+static char *LYLeakHTSprintf0(char **pstr, const char *fmt, ...)
+{
+ char *str;
+ va_list ap;
+
+ LYva_start(ap, fmt);
+
+ str = LYLeakSAVsprintf(pstr, leak_cp_File_hack, leak_ssi_Line_hack,
+ 0, fmt, &ap);
+
+ va_end(ap);
+ return str;
+}
+
+/*
+ * HTSprintf and HTSprintf0 will be defined such that they effectively call one
+ * of the following two functions that store away a copy to the File & Line
+ * info in temporary hack variables, and then call the real function (which is
+ * returned here as a function pointer) to the regular HTSprintf/HTSprintf0
+ * arguments. It's probably a bit inefficient, but that shouldn't be
+ * noticeable compared to all the time that memory tracking takes up for list
+ * traversal. - kw
+ */
+HTSprintflike *Get_htsprintf_fn(const char *cp_File,
+ const short ssi_Line)
+{
+ leak_cp_File_hack = cp_File;
+ leak_ssi_Line_hack = ssi_Line;
+ return &LYLeakHTSprintf;
+}
+
+HTSprintflike *Get_htsprintf0_fn(const char *cp_File,
+ const short ssi_Line)
+{
+ leak_cp_File_hack = cp_File;
+ leak_ssi_Line_hack = ssi_Line;
+ return &LYLeakHTSprintf0;
+}
+
+#endif /* LY_FIND_LEAKS and LY_FIND_LEAKS_EXTENDED */
+#else
+/* Standard C forbids an empty file */
+void no_leak_checking(void);
+void no_leak_checking(void)
+{
+}
+#endif /* LY_FIND_LEAKS */
diff --git a/src/LYList.c b/src/LYList.c
new file mode 100644
index 0000000..989b6a3
--- /dev/null
+++ b/src/LYList.c
@@ -0,0 +1,374 @@
+/*
+ * $LynxId: LYList.c,v 1.55 2020/02/23 21:20:05 tom Exp $
+ *
+ * Lynx Document Reference List Support LYList.c
+ * ====================================
+ *
+ * Author: FM Foteos Macrides (macrides@sci.wfbr.edu)
+ *
+ */
+
+#include <HTUtils.h>
+#include <HTAlert.h>
+#include <LYUtils.h>
+#include <GridText.h>
+#include <HTParse.h>
+#include <LYList.h>
+#include <LYMap.h>
+#include <LYClean.h>
+#include <LYGlobalDefs.h>
+#include <LYCharUtils.h>
+#include <LYCharSets.h>
+#include <LYStrings.h>
+#include <LYHistory.h>
+
+#ifdef DIRED_SUPPORT
+#include <LYUpload.h>
+#include <LYLocal.h>
+#endif /* DIRED_SUPPORT */
+
+#include <LYexit.h>
+#include <LYLeaks.h>
+
+/* showlist - F.Macrides (macrides@sci.wfeb.edu)
+ * --------
+ * Create a temporary text/html file with a list of links to
+ * HyperText References in the current document.
+ *
+ * On entry
+ * titles Set: if we want titles where available
+ * Clear: we only get addresses.
+ */
+
+int showlist(DocInfo *newdoc, int titles)
+{
+ int cnt;
+ int refs, hidden_links;
+ int result;
+ static char tempfile[LY_MAXPATH];
+ static BOOLEAN last_titles = TRUE;
+ FILE *fp0;
+ char *Address = NULL, *Title = NULL, *cp = NULL;
+ char *LinkTitle = NULL; /* Rel stored as property of link, not of dest */
+ BOOLEAN intern_w_post = FALSE;
+ const char *desc = "unknown field or link";
+ void *helper;
+
+ refs = HText_sourceAnchors(HTMainText);
+ hidden_links = HText_HiddenLinkCount(HTMainText);
+ if (refs <= 0 && hidden_links > 0 &&
+ LYHiddenLinks != HIDDENLINKS_SEPARATE) {
+ HTUserMsg(NO_VISIBLE_REFS_FROM_DOC);
+ return (-1);
+ }
+ if (refs <= 0 && hidden_links <= 0) {
+ HTUserMsg(NO_REFS_FROM_DOC);
+ return (-1);
+ }
+
+ if ((fp0 = InternalPageFP(tempfile, titles == last_titles)) == 0)
+ return (-1);
+
+ LYLocalFileToURL(&(newdoc->address), tempfile);
+
+ LYRegisterUIPage(newdoc->address,
+ titles ? UIP_LIST_PAGE : UIP_ADDRLIST_PAGE);
+ last_titles = (BOOLEAN) titles;
+ LYforce_HTML_mode = TRUE; /* force this file to be HTML */
+ LYforce_no_cache = TRUE; /* force this file to be new */
+
+#ifdef USE_ADDRLIST_PAGE
+ if (titles != TRUE)
+ BeginInternalPage(fp0, ADDRLIST_PAGE_TITLE, LIST_PAGE_HELP);
+ else
+#endif
+ BeginInternalPage(fp0, LIST_PAGE_TITLE, LIST_PAGE_HELP);
+
+ StrAllocCopy(Address, HTLoadedDocumentURL());
+ LYEntify(&Address, FALSE);
+ fprintf(fp0, "%s%s<p>\n", gettext("References in "),
+ (non_empty(Address)
+ ? Address
+ : gettext("this document:")));
+ FREE(Address);
+ if (refs > 0) {
+ fprintf(fp0, "<%s compact>\n", ((keypad_mode == NUMBERS_AS_ARROWS) ?
+ "ol" : "ul"));
+ if (hidden_links > 0)
+ fprintf(fp0, "<lh><em>%s</em>\n", gettext("Visible links:"));
+ }
+ if (hidden_links > 0) {
+ if (LYHiddenLinks == HIDDENLINKS_IGNORE)
+ hidden_links = 0;
+ }
+ helper = NULL; /* init */
+ result = 1;
+ for (cnt = 1; cnt <= refs; cnt++) {
+ HTChildAnchor *child = HText_childNextNumber(cnt, &helper);
+ int value = HText_findAnchorNumber(helper);
+ HTAnchor *dest_intl = NULL;
+ HTAnchor *dest;
+ HTParentAnchor *parent;
+ char *address;
+ const char *title;
+
+ if (child == 0) {
+ /*
+ * child should not be 0 unless form field numbering is on and cnt
+ * is the number of a form input field. HText_FormDescNumber()
+ * will set desc to a description of what type of input field this
+ * is. We'll list it to ensure that the link numbers on the list
+ * page match the numbering in the original document, but won't
+ * create a forward link to the form. - FM && LE
+ *
+ * Changed to create a fake hidden link, to get the numbering right
+ * in connection with always treating this file as
+ * HIDDENLINKS_MERGE in GridText.c - kw
+ */
+ if (fields_are_numbered()) {
+ HText_FormDescNumber(cnt, &desc);
+ fprintf(fp0,
+ "<li><a id=%d href=\"#%d\">form field</a> = <em>%s</em>\n",
+ cnt, cnt, desc);
+ }
+ } else if (value >= result) {
+ if (track_internal_links)
+ dest_intl = HTAnchor_followTypedLink(child, HTInternalLink);
+ dest = (dest_intl
+ ? dest_intl
+ : HTAnchor_followLink(child));
+ parent = HTAnchor_parent(dest);
+ if (!intern_w_post && dest_intl &&
+ HTMainAnchor &&
+ HTMainAnchor->post_data &&
+ parent->post_data &&
+ BINEQ(HTMainAnchor->post_data, parent->post_data)) {
+ /*
+ * Set flag to note that we had at least one internal link, if
+ * the document from which we are generating the list has
+ * associated POST data; after an extra check that the link
+ * destination really has the same POST data so that we can
+ * believe it is an internal link.
+ */
+ intern_w_post = TRUE;
+ }
+ address = HTAnchor_address(dest);
+ title = titles ? HTAnchor_title(parent) : NULL;
+ if (dest_intl) {
+ HTSprintf0(&LinkTitle, "(internal)");
+ } else if (titles && child->type &&
+ dest == child->dest &&
+ !StrNCmp(HTAtom_name(child->type),
+ "RelTitle: ", 10)) {
+ HTSprintf0(&LinkTitle, "(%s)", HTAtom_name(child->type) + 10);
+ } else {
+ FREE(LinkTitle);
+ }
+ StrAllocCopy(Address, address);
+ FREE(address);
+ LYEntify(&Address, TRUE);
+ if (non_empty(title)) {
+ LYformTitle(&Title, title);
+ LYEntify(&Title, TRUE);
+ if (*Title) {
+ cp = findPoundSelector(Address);
+ } else {
+ FREE(Title);
+ }
+ }
+
+ fprintf(fp0, "<li><a href=\"%s\"%s>%s%s%s%s%s</a>\n", Address,
+ dest_intl ? " TYPE=\"internal link\"" : "",
+ NonNull(LinkTitle),
+ ((HTAnchor *) parent != dest) && Title ? "in " : "",
+ (char *) (Title ? Title : Address),
+ (Title && cp) ? " - " : "",
+ (Title && cp) ? (cp + 1) : "");
+
+ FREE(Address);
+ FREE(Title);
+ }
+ result = value + 1;
+ }
+ FREE(LinkTitle);
+
+ if (hidden_links > 0) {
+ if (refs > 0)
+ fprintf(fp0, "\n</%s>\n\n<p>\n",
+ ((keypad_mode == NUMBERS_AS_ARROWS) ?
+ "ol" : "ul"));
+ fprintf(fp0, "<%s compact>\n", ((keypad_mode == NUMBERS_AS_ARROWS) ?
+ "ol continue" : "ul"));
+ fprintf(fp0, "<lh><em>%s</em>\n", gettext("Hidden links:"));
+ }
+
+ for (cnt = 0; cnt < hidden_links; cnt++) {
+ StrAllocCopy(Address, HText_HiddenLinkAt(HTMainText, cnt));
+ LYEntify(&Address, FALSE);
+ if (isEmpty(Address)) {
+ FREE(Address);
+ continue;
+ }
+ fprintf(fp0, "<li><a href=\"%s\">%s</a>\n", Address, Address);
+
+ FREE(Address);
+ }
+
+ fprintf(fp0, "\n</%s>\n", ((keypad_mode == NUMBERS_AS_ARROWS) ?
+ "ol" : "ul"));
+ EndInternalPage(fp0);
+ LYCloseTempFP(fp0);
+
+ /*
+ * Make necessary changes to newdoc before returning to caller. If the
+ * intern_w_post flag is set, we keep the POST data in newdoc that have
+ * been passed in. They should be the same as in the loaded document for
+ * which we generated the list. In that case the file we have written will
+ * be associated with the same POST data when it is loaded after we are
+ * done here, so that following one of the links we have marked as
+ * "internal link" can lead back to the underlying document with the right
+ * address+post_data combination. - kw
+ */
+ if (intern_w_post) {
+ newdoc->internal_link = TRUE;
+ } else {
+ LYFreePostData(newdoc);
+ newdoc->internal_link = FALSE;
+ }
+ newdoc->isHEAD = FALSE;
+ newdoc->safe = FALSE;
+ return (0);
+}
+
+static int print_refs(FILE *fp, int titles, int refs)
+{
+ int result = 0;
+ int cnt;
+ int value;
+ char *address = NULL;
+ const char *desc = gettext("unknown field or link");
+ void *helper = NULL; /* init */
+
+ for (cnt = 1; cnt <= refs; cnt++) {
+ HTChildAnchor *child = HText_childNextNumber(cnt, &helper);
+ HTAnchor *dest;
+ HTParentAnchor *parent;
+ const char *title;
+ int counter = result + 1;
+
+ if (child == 0) {
+ /*
+ * child should not be 0 unless form field numbering is on and
+ * cnt is the number of a form input field.
+ * HText_FormDescNumber() will set desc to a description of
+ * what type of input field this is. We'll create a
+ * within-document link to ensure that the link numbers on the
+ * list page match the numbering in the original document, but
+ * won't create a forward link to the form. - FM && LE
+ */
+ if (fields_are_numbered()) {
+ HText_FormDescNumber(cnt, &desc);
+ fprintf(fp, "%4d. form field = %s\n", counter, desc);
+ }
+ } else {
+ dest = HTAnchor_followLink(child);
+ /*
+ * Ignore if child anchor points to itself, i.e., we had something
+ * like <A NAME=xyz HREF="#xyz"> and it is not treated as a hidden
+ * link. Useful if someone 'P'rints the List Page (which isn't a
+ * very useful action to do, but anyway...) - kw
+ */
+ if (dest != (HTAnchor *) child) {
+ parent = HTAnchor_parent(dest);
+ title = titles ? HTAnchor_title(parent) : NULL;
+ if (links_are_numbered()) {
+ value = HText_findAnchorNumber(helper);
+ if (value <= result)
+ continue;
+ fprintf(fp, "%4d. ", value);
+ }
+ if (((HTAnchor *) parent != dest) && title) {
+ fprintf(fp, "in ");
+ }
+ if (title) {
+ fprintf(fp, "%s\n", title);
+ } else {
+ address = HTAnchor_short_address(dest);
+ if (dump_links_decoded
+ && LYCharSet_UC[current_char_set].enc == UCT_ENC_UTF8) {
+ (void) HTUnEscape(address);
+ }
+ fprintf(fp, "%s\n", address);
+ FREE(address);
+ }
+ }
+ }
+ if (counter > result)
+ result = counter;
+#ifdef VMS
+ if (HadVMSInterrupt)
+ break;
+#endif /* VMS */
+ }
+ return result;
+}
+
+static void print_hidden_refs(FILE *fp, int refs, int hidden_links)
+{
+ int cnt;
+ char *address = NULL;
+
+ fprintf(fp, "%s %s\n", ((refs > 0) ? "\n" : ""),
+ gettext("Hidden links:"));
+ for (cnt = 0; cnt < hidden_links; cnt++) {
+ StrAllocCopy(address, HText_HiddenLinkAt(HTMainText, cnt));
+ if (isEmpty(address)) {
+ FREE(address);
+ continue;
+ }
+
+ if (links_are_numbered())
+ fprintf(fp, "%4d. ", ((cnt + 1) + refs));
+ fprintf(fp, "%s\n", address);
+ FREE(address);
+#ifdef VMS
+ if (HadVMSInterrupt)
+ break;
+#endif /* VMS */
+ }
+}
+
+/* printlist - F.Macrides (macrides@sci.wfeb.edu)
+ * ---------
+ * Print a text/plain list of HyperText References
+ * in the current document.
+ *
+ * On entry
+ * titles Set: if we want titles where available
+ * Clear: we only get addresses.
+ */
+void printlist(FILE *fp, int titles)
+{
+ int refs, hidden_links;
+
+ refs = HText_sourceAnchors(HTMainText);
+ if (refs > 0 || LYHiddenLinks == HIDDENLINKS_SEPARATE) {
+ hidden_links = HText_HiddenLinkCount(HTMainText);
+ if (refs > 0 || hidden_links > 0) {
+ if (links_are_numbered() || fields_are_numbered())
+ fprintf(fp, "\n%s\n\n", gettext("References"));
+ if (LYHiddenLinks == HIDDENLINKS_IGNORE)
+ hidden_links = 0;
+ if (hidden_links > 0) {
+ fprintf(fp, " %s\n", gettext("Visible links:"));
+ }
+ refs = print_refs(fp, titles, refs) + 1;
+
+ if (hidden_links > 0) {
+ print_hidden_refs(fp, refs, hidden_links);
+ }
+ }
+ }
+ LYPrintImgMaps(fp);
+ return;
+}
diff --git a/src/LYList.h b/src/LYList.h
new file mode 100644
index 0000000..f6afbc5
--- /dev/null
+++ b/src/LYList.h
@@ -0,0 +1,16 @@
+/* $LynxId: LYList.h,v 1.12 2010/09/25 11:35:35 tom Exp $ */
+#ifndef LYLIST_H
+#define LYLIST_H
+
+#include <LYStructs.h>
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+ extern int showlist(DocInfo *newdoc, int titles);
+ extern void printlist(FILE *fp, int titles);
+
+#ifdef __cplusplus
+}
+#endif
+#endif /* LYLIST_H */
diff --git a/src/LYLocal.c b/src/LYLocal.c
new file mode 100644
index 0000000..754a7d4
--- /dev/null
+++ b/src/LYLocal.c
@@ -0,0 +1,2685 @@
+/*
+ * $LynxId: LYLocal.c,v 1.135 2023/01/02 23:52:10 tom Exp $
+ *
+ * Routines to manipulate the local filesystem.
+ * Written by: Rick Mallett, Carleton University
+ * Report problems to rmallett@ccs.carleton.ca
+ * Modified 18-Dec-95 David Trueman (david@cs.dal.ca):
+ * Added OK_PERMIT compilation option.
+ * Support replacement of compiled-in f)ull menu configuration via
+ * DIRED_MENU definitions in lynx.cfg, so that more than one menu
+ * can be driven by the same executable.
+ * Modified Oct-96 Klaus Weide (kweide@tezcat.com):
+ * Changed to use the library's HTList_* functions and macros for
+ * managing the list of tagged file URLs.
+ * Keep track of proper level of URL escaping, so that unusual filenames
+ * which contain #% etc. are handled properly (some HTUnEscapeSome()'s
+ * left in to be conservative, and to document where superfluous
+ * unescaping took place before).
+ * Dynamic memory instead of fixed length buffers in a few cases.
+ * Other minor changes to make things work as intended.
+ * Modified Jun-97 Klaus Weide (kweide@tezcat.com) & FM:
+ * Modified the code handling DIRED_MENU to do more careful
+ * checking of the selected file. In addition to "TAG", "FILE", and
+ * "DIR", DIRED_MENU definitions in lynx.cfg now also recognize LINK as
+ * a type. DIRED_MENU definitions with a type field of "LINK" are only
+ * used if the current selection is a symbolic link ("FILE" and "DIR"
+ * definitions are not used in that case). The default menu
+ * definitions have been updated to reflect this change, and to avoid
+ * the showing of menu items whose action would always fail - KW
+ * Cast all code into the Lynx programming style. - FM
+ */
+
+#include <HTUtils.h>
+#include <HTAAProt.h>
+#include <HTFile.h>
+#include <HTAlert.h>
+#include <HTParse.h>
+#include <LYCurses.h>
+#include <LYGlobalDefs.h>
+#include <LYUtils.h>
+#include <LYStrings.h>
+#include <LYCharUtils.h>
+#include <LYStructs.h>
+#include <LYHistory.h>
+#include <LYUpload.h>
+#include <LYLocal.h>
+#include <LYClean.h>
+#include <www_wait.h>
+
+#ifdef SUPPORT_CHDIR
+#include <LYMainLoop.h>
+#endif
+
+#include <LYLeaks.h>
+
+#undef USE_COMPRESS
+
+#ifdef __DJGPP__
+#define EXT_TAR_GZ ".tgz"
+#define EXT_TAR_Z ".taz"
+#define EXT_Z ".z"
+#else
+#define EXT_TAR_GZ ".tar.gz"
+#define EXT_TAR_Z ".tar.Z"
+#define EXT_Z ".Z"
+#endif
+
+#ifndef DIRED_MAXBUF
+#define DIRED_MAXBUF 512
+#endif
+
+#ifdef DIRED_SUPPORT
+
+#ifdef OK_INSTALL
+#ifdef FNAMES_8_3
+#define INSTALLDIRS_FILE "instdirs.htm"
+#else
+#define INSTALLDIRS_FILE ".installdirs.html"
+#endif /* FNAMES_8_3 */
+#endif /* OK_INSTALL */
+
+static int get_filename(const char *prompt,
+ bstring **buf);
+
+#ifdef OK_PERMIT
+static int permit_location(char *destpath,
+ char *srcpath,
+ char **newpath);
+#endif /* OK_PERMIT */
+/* *INDENT-OFF* */
+static char *render_item ( const char * s,
+ const char * path,
+ const char * dir,
+ char * buf,
+ size_t bufsize,
+ int url_syntax);
+
+struct dired_menu {
+ int cond;
+#define DE_TAG 1
+#define DE_DIR 2
+#define DE_FILE 3
+#define DE_SYMLINK 4
+ char *sfx;
+ const char *c_sfx;
+ char *link;
+ const char *c_link;
+ char *rest;
+ const char *c_rest;
+ char *href;
+ const char *c_href;
+ struct dired_menu *next;
+};
+
+#define GetDiredSuffix(p) ((p)->sfx ? (p)->sfx : (p)->c_sfx)
+#define GetDiredLink(p) ((p)->link ? (p)->link : (p)->c_link)
+#define GetDiredRest(p) ((p)->rest ? (p)->rest : (p)->c_rest)
+#define GetDiredHref(p) ((p)->href ? (p)->href : (p)->c_href)
+
+#undef DATA
+#define DATA(cond, sfx, link, rest, href) { \
+ cond, \
+ NULL, sfx, \
+ NULL, link, \
+ NULL, rest, \
+ NULL, href, \
+ NULL }
+
+static struct dired_menu *menu_head = NULL;
+static struct dired_menu defmenu[] = {
+
+/*
+ * The following initializations determine the contents of the f)ull menu
+ * selection when in dired mode. If any menu entries are defined in the
+ * configuration file via DIRED_MENU lines, then these default entries are
+ * discarded entirely.
+ */
+#ifdef SUPPORT_CHDIR
+DATA( 0, "", "Change directory",
+ "", "LYNXDIRED://CHDIR"),
+#endif
+DATA( 0, "", "New File",
+"(in current directory)", "LYNXDIRED://NEW_FILE%d"),
+
+DATA( 0, "", "New Directory",
+"(in current directory)", "LYNXDIRED://NEW_FOLDER%d"),
+
+#ifdef OK_INSTALL
+DATA( DE_FILE, "", "Install",
+"selected file to new location", "LYNXDIRED://INSTALL_SRC%p"),
+/* The following (installing a directory) doesn't work for me, at least
+ with the "install" from GNU fileutils 4.0. I leave it in anyway, in
+ case one compiles with INSTALL_PATH / INSTALL_ARGS defined to some
+ other command for which it works (like a script, or maybe "cp -a"). - kw
+*/
+DATA( DE_DIR, "", "Install",
+"selected directory to new location", "LYNXDIRED://INSTALL_SRC%p"),
+#endif /* OK_INSTALL */
+
+DATA( DE_FILE, "", "Modify File Name",
+"(of current selection)", "LYNXDIRED://MODIFY_NAME%p"),
+DATA( DE_DIR, "", "Modify Directory Name",
+"(of current selection)", "LYNXDIRED://MODIFY_NAME%p"),
+#ifdef S_IFLNK
+DATA( DE_SYMLINK, "", "Modify Name",
+"(of selected symbolic link)", "LYNXDIRED://MODIFY_NAME%p"),
+#endif /* S_IFLNK */
+
+#ifdef OK_PERMIT
+DATA( DE_FILE, "", "Modify File Permissions",
+"(of current selection)", "LYNXDIRED://PERMIT_SRC%p"),
+DATA( DE_DIR, "", "Modify Directory Permissions",
+"(of current selection)", "LYNXDIRED://PERMIT_SRC%p"),
+#endif /* OK_PERMIT */
+
+DATA( DE_FILE, "", "Change Location",
+"(of selected file)" , "LYNXDIRED://MODIFY_LOCATION%p"),
+DATA( DE_DIR, "", "Change Location",
+"(of selected directory)", "LYNXDIRED://MODIFY_LOCATION%p"),
+#ifdef S_IFLNK
+DATA( DE_SYMLINK, "", "Change Location",
+"(of selected symbolic link)", "LYNXDIRED://MODIFY_LOCATION%p"),
+#endif /* S_IFLNK */
+
+DATA( DE_FILE, "", "Remove File",
+ "(current selection)", "LYNXDIRED://REMOVE_SINGLE%p"),
+DATA( DE_DIR, "", "Remove Directory",
+ "(current selection)", "LYNXDIRED://REMOVE_SINGLE%p"),
+#ifdef S_IFLNK
+DATA( DE_SYMLINK, "", "Remove Symbolic Link",
+ "(current selection)", "LYNXDIRED://REMOVE_SINGLE%p"),
+#endif /* S_IFLNK */
+
+#if defined(OK_UUDECODE) && !defined(ARCHIVE_ONLY)
+DATA( DE_FILE, "", "UUDecode",
+ "(current selection)", "LYNXDIRED://UUDECODE%p"),
+#endif /* OK_UUDECODE && !ARCHIVE_ONLY */
+
+#if defined(OK_TAR) && !defined(ARCHIVE_ONLY)
+DATA( DE_FILE, EXT_TAR_Z, "Expand",
+ "(current selection)", "LYNXDIRED://UNTAR_Z%p"),
+#endif /* OK_TAR && !ARCHIVE_ONLY */
+
+#if defined(OK_TAR) && defined(OK_GZIP) && !defined(ARCHIVE_ONLY)
+DATA( DE_FILE, ".tar.gz", "Expand",
+ "(current selection)", "LYNXDIRED://UNTAR_GZ%p"),
+
+DATA( DE_FILE, ".tgz", "Expand",
+ "(current selection)", "LYNXDIRED://UNTAR_GZ%p"),
+#endif /* OK_TAR && OK_GZIP && !ARCHIVE_ONLY */
+
+#ifndef ARCHIVE_ONLY
+DATA( DE_FILE, EXT_Z, "Uncompress",
+ "(current selection)", "LYNXDIRED://DECOMPRESS%p"),
+#endif /* ARCHIVE_ONLY */
+
+#if defined(OK_GZIP) && !defined(ARCHIVE_ONLY)
+DATA( DE_FILE, ".gz", "Uncompress",
+ "(current selection)", "LYNXDIRED://UNGZIP%p"),
+#endif /* OK_GZIP && !ARCHIVE_ONLY */
+
+#if defined(OK_ZIP) && !defined(ARCHIVE_ONLY)
+DATA( DE_FILE, ".zip", "Uncompress",
+ "(current selection)", "LYNXDIRED://UNZIP%p"),
+#endif /* OK_ZIP && !ARCHIVE_ONLY */
+
+#if defined(OK_TAR) && !defined(ARCHIVE_ONLY)
+DATA( DE_FILE, ".tar", "UnTar",
+ "(current selection)", "LYNXDIRED://UNTAR%p"),
+#endif /* OK_TAR && !ARCHIVE_ONLY */
+
+#ifdef OK_TAR
+DATA( DE_DIR, "", "Tar",
+ "(current selection)", "LYNXDIRED://TAR%p"),
+#endif /* OK_TAR */
+
+#if defined(OK_TAR) && defined(OK_GZIP)
+DATA( DE_DIR, "", "Tar and compress",
+ "(using GNU gzip)", "LYNXDIRED://TAR_GZ%p"),
+#endif /* OK_TAR && OK_GZIP */
+
+#if defined(OK_TAR) && defined(USE_COMPRESS)
+DATA( DE_DIR, "", "Tar and compress",
+ "(using compress)", "LYNXDIRED://TAR_Z%p"),
+#endif /* OK_TAR && USE_COMPRESS */
+
+#ifdef OK_ZIP
+DATA( DE_DIR, "", "Package and compress",
+ "(using zip)", "LYNXDIRED://ZIP%p"),
+#endif /* OK_ZIP */
+
+DATA( DE_FILE, "", "Compress",
+ "(using Unix compress)", "LYNXDIRED://COMPRESS%p"),
+
+#ifdef OK_GZIP
+DATA( DE_FILE, "", "Compress",
+ "(using gzip)", "LYNXDIRED://GZIP%p"),
+#endif /* OK_GZIP */
+
+#ifdef OK_ZIP
+DATA( DE_FILE, "", "Compress",
+ "(using zip)", "LYNXDIRED://ZIP%p"),
+#endif /* OK_ZIP */
+
+DATA( DE_TAG, "", "Move all tagged items to another location.",
+ "", "LYNXDIRED://MOVE_TAGGED%d"),
+
+#ifdef OK_INSTALL
+DATA( DE_TAG, "", "Install tagged files into another directory.",
+ "", "LYNXDIRED://INSTALL_SRC%00"),
+#endif
+
+DATA( DE_TAG, "", "Remove all tagged files and directories.",
+ "", "LYNXDIRED://REMOVE_TAGGED"),
+
+DATA( DE_TAG, "", "Untag all tagged files and directories.",
+ "", "LYNXDIRED://CLEAR_TAGGED"),
+
+DATA( 0, NULL, NULL,
+ NULL, NULL),
+};
+#undef DATA
+/* *INDENT-ON* */
+
+static BOOLEAN cannot_stat(const char *name)
+{
+ char *tmpbuf = 0;
+
+ HTSprintf0(&tmpbuf, gettext("Unable to get status of '%s'."), name);
+ HTAlert(tmpbuf);
+ FREE(tmpbuf);
+ return FALSE;
+}
+
+#define OK_STAT(name, sb) (stat(name, sb) == 0)
+
+static BOOLEAN ok_stat(const char *name, struct stat *sb)
+{
+ BOOLEAN rc = TRUE;
+
+ CTRACE((tfp, "testing ok_stat(%s)\n", name));
+ if (!OK_STAT(name, sb)) {
+#ifdef DOSPATH
+ size_t len = strlen(name);
+
+ /*
+ * If a path ends with '\' or ':', we can guess that it may be
+ * a directory name. Adding a '.' (after a '\') will produce a
+ * pathname that stat() will accept as a directory name.
+ */
+ if (len != 0 && (name[len - 1] == '\\' || name[len - 1] == ':')) {
+ char *temp = malloc(len + 3);
+
+ if (temp != 0) {
+ strcpy(temp, name);
+ if (temp[len - 1] == '\\') {
+ strcpy(temp + len, ".");
+ } else {
+ strcpy(temp + len, "\\.");
+ }
+ rc = OK_STAT(temp, sb);
+ free(temp);
+ } else {
+ rc = FALSE;
+ }
+ } else
+#endif
+ rc = FALSE;
+ }
+
+ if (rc == FALSE)
+ rc = cannot_stat(name);
+
+ return rc;
+}
+
+#ifdef HAVE_LSTAT
+static BOOLEAN ok_lstat(char *name, struct stat *sb)
+{
+ CTRACE((tfp, "testing ok_lstat(%s)\n", name));
+ if (lstat(name, sb) < 0) {
+ return cannot_stat(name);
+ }
+ return TRUE;
+}
+#else
+#define ok_lstat(name,sb) ok_stat(name,sb)
+#endif
+
+static BOOLEAN ok_file_or_dir(struct stat *sb)
+{
+ if (!S_ISDIR(sb->st_mode)
+ && !S_ISREG(sb->st_mode)) {
+ HTAlert(gettext("The selected item is not a file or a directory! Request ignored."));
+ return FALSE;
+ }
+ return TRUE;
+}
+
+#ifdef OK_INSTALL /* currently only used in local_install */
+static BOOLEAN ok_localname(char *dst, const char *src)
+{
+ struct stat dir_info;
+
+ if (!ok_stat(src, &dir_info)
+ || !ok_file_or_dir(&dir_info)) {
+ return FALSE;
+ }
+ if (strlen(src) >= DIRED_MAXBUF) {
+ CTRACE((tfp, "filename too long in ok_localname!\n"));
+ return FALSE;
+ }
+ strcpy(dst, src);
+ return TRUE;
+}
+#endif /* OK_INSTALL */
+
+#define MAX_ARGC 10
+
+static char **make_argv(const char *command, ...)
+{
+ static char *result[MAX_ARGC];
+ int argc = 0;
+ char *value;
+ va_list ap;
+
+ va_start(ap, command);
+ result[0] = 0;
+ StrAllocCopy(result[argc++], command);
+ do {
+ result[argc] = 0;
+ value = (char *) va_arg(ap, char *);
+
+ if (value != 0)
+ StrAllocCopy(result[argc], value);
+ } while (result[argc++] != 0);
+ va_end(ap);
+
+ return result;
+}
+
+static void free_argv(char **argv)
+{
+ int argc;
+
+ for (argc = 0; argv[argc] != 0; ++argc) {
+ free(argv[argc]);
+ }
+}
+
+/*
+ * Execute DIRED command, return -1 or 0 on failure, 1 success.
+ */
+static int LYExecv(const char *path,
+ char **argv,
+ char *msg)
+{
+ int rc = 0;
+
+#if defined(VMS)
+ CTRACE((tfp, "LYExecv: Called inappropriately! (path=%s)\n", path));
+#else
+ int n;
+ char *tmpbuf = 0;
+
+#if defined(__DJGPP__) || defined(_WINDOWS)
+ (void) msg;
+ stop_curses();
+ HTSprintf0(&tmpbuf, "%s", path);
+ for (n = 1; argv[n] != 0; n++)
+ HTSprintf(&tmpbuf, " %s", argv[n]);
+ HTSprintf(&tmpbuf, "\n");
+ rc = LYSystem(tmpbuf) ? 0 : 1;
+#else
+ int pid;
+
+#ifdef HAVE_TYPE_UNIONWAIT
+ union wait wstatus;
+
+#else
+ int wstatus;
+#endif
+
+ if (TRACE) {
+ CTRACE((tfp, "LYExecv path='%s'\n", path));
+ for (n = 0; argv[n] != 0; n++)
+ CTRACE((tfp, "argv[%d] = '%s'\n", n, argv[n]));
+ }
+
+ rc = 1; /* It will work */
+ stop_curses();
+ pid = fork(); /* fork and execute command */
+
+ switch (pid) {
+ case -1:
+ HTSprintf0(&tmpbuf, gettext("Unable to %s due to system error!"), msg);
+ rc = 0;
+ break; /* don't fall thru! - KW */
+
+ case 0: /* child */
+#ifdef USE_EXECVP
+ execvp(path, argv); /* this uses our $PATH */
+#else
+ execv(path, argv);
+#endif
+ exit(EXIT_FAILURE); /* execv failed, give wait() something to look at */
+ /*NOTREACHED */
+
+ default: /* parent */
+#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 */
+ if ((WIFEXITED(wstatus)
+ && (WEXITSTATUS(wstatus) != 0))
+ || (WIFSIGNALED(wstatus)
+ && (WTERMSIG(wstatus) > 0))) { /* error return */
+ HTSprintf0(&tmpbuf,
+ gettext("Probable failure to %s due to system error!"),
+ msg);
+ rc = 0;
+ }
+ }
+#endif /* __DJGPP__ */
+
+ if (rc == 0) {
+ /*
+ * Screen may have message from the failed execv'd command. Give user
+ * time to look at it before screen refresh.
+ */
+ LYSleepAlert();
+ }
+ start_curses();
+ if (tmpbuf != 0) {
+ if (rc == 0)
+ HTAlert(tmpbuf);
+ FREE(tmpbuf);
+ }
+#endif /* VMS || _WINDOWS */
+ CTRACE((tfp, "LYexecv ->%d\n", rc));
+ return (rc);
+}
+
+static int make_directory(char *path)
+{
+ int code;
+ const char *program;
+
+ if ((program = HTGetProgramPath(ppMKDIR)) != NULL) {
+ char **args;
+ char *msg = 0;
+
+ HTSprintf0(&msg, "make directory %s", path);
+ args = make_argv("mkdir",
+ path,
+ NULL);
+ code = (LYExecv(program, args, msg) <= 0) ? -1 : 1;
+ FREE(msg);
+ free_argv(args);
+ } else {
+#ifdef _WINDOWS
+ code = mkdir(path) ? -1 : 1;
+#else
+ code = mkdir(path, 0777) ? -1 : 1;
+#endif
+ CTRACE((tfp, "builtin mkdir ->%d\n\t%s\n", code, path));
+ }
+ return (code);
+}
+
+static int remove_file(char *path)
+{
+ int code;
+ const char *program;
+
+ if ((program = HTGetProgramPath(ppRM)) != NULL) {
+ char **args;
+ char *tmpbuf = NULL;
+
+ args = make_argv("rm",
+ "-f",
+ path,
+ NULL);
+ HTSprintf0(&tmpbuf, gettext("remove %s"), path);
+ code = LYExecv(program, args, tmpbuf);
+ FREE(tmpbuf);
+ free_argv(args);
+ } else {
+ code = remove(path) ? -1 : 1;
+ CTRACE((tfp, "builtin remove ->%d\n\t%s\n", code, path));
+ }
+ return (code);
+}
+
+static int remove_directory(char *path)
+{
+ int code;
+ const char *program;
+
+ if ((program = HTGetProgramPath(ppRMDIR)) != NULL) {
+ char **args;
+ char *tmpbuf = NULL;
+
+ args = make_argv("rmdir",
+ path,
+ NULL);
+ HTSprintf0(&tmpbuf, gettext("remove %s"), path);
+ code = LYExecv(program, args, tmpbuf);
+ FREE(tmpbuf);
+ free_argv(args);
+ } else {
+ code = rmdir(path) ? -1 : 1;
+ CTRACE((tfp, "builtin rmdir ->%d\n\t%s\n", code, path));
+ }
+ return (code);
+}
+
+static int touch_file(char *path)
+{
+ int code;
+ const char *program;
+
+ if ((program = HTGetProgramPath(ppTOUCH)) != NULL) {
+ char **args;
+ char *msg = NULL;
+
+ HTSprintf0(&msg, gettext("touch %s"), path);
+ args = make_argv("touch",
+ path,
+ NULL);
+ code = (LYExecv(program, args, msg) <= 0) ? -1 : 1;
+ FREE(msg);
+ free_argv(args);
+ } else {
+ FILE *fp;
+
+ if ((fp = fopen(path, BIN_W)) != 0) {
+ fclose(fp);
+ code = 1;
+ } else {
+ code = -1;
+ }
+ CTRACE((tfp, "builtin touch ->%d\n\t%s\n", code, path));
+ }
+ return (code);
+}
+
+static int move_file(char *source, char *target)
+{
+ int code;
+ const char *program;
+
+ if ((program = HTGetProgramPath(ppMV)) != NULL) {
+ char *msg = 0;
+ char **args;
+
+ HTSprintf0(&msg, gettext("move %s to %s"), source, target);
+ args = make_argv("mv",
+ source,
+ target,
+ NULL);
+ code = (LYExecv(program, args, msg) <= 0) ? -1 : 1;
+ FREE(msg);
+ free_argv(args);
+ } else {
+ struct stat sb;
+ char *actual = 0;
+
+ /* the caller sets up a target directory; we need a file path */
+ if (stat(target, &sb) == 0
+ && S_ISDIR(sb.st_mode)) {
+ HTSprintf0(&actual, "%s/%s", target, LYPathLeaf(source));
+ CTRACE((tfp, "move_file source=%s, target=%s\n", source, target));
+ target = actual;
+ }
+ code = rename(source, target);
+ CTRACE((tfp, "builtin move ->%d\n\tsource=%s\n\ttarget=%s\n",
+ code, source, target));
+ if (code != 0) { /* it failed */
+ if ((code = LYCopyFile(source, target)) >= 0) {
+ code = remove(source);
+ CTRACE((tfp, "...remove source after copying ->%d\n", code));
+ }
+ }
+ if (code == 0)
+ code = 1;
+ if (actual != target) {
+ FREE(actual);
+ }
+ }
+ return code;
+}
+
+static BOOLEAN not_already_exists(char *name)
+{
+ struct stat dir_info;
+
+ if (!OK_STAT(name, &dir_info)) {
+ if (errno != ENOENT) {
+ cannot_stat(name);
+ } else {
+ return TRUE;
+ }
+ } else if (S_ISDIR(dir_info.st_mode)) {
+ HTAlert(gettext("There is already a directory with that name! Request ignored."));
+ } else if (S_ISREG(dir_info.st_mode)) {
+ HTAlert(gettext("There is already a file with that name! Request ignored."));
+ } else {
+ HTAlert(gettext("The specified name is already in use! Request ignored."));
+ }
+ return FALSE;
+}
+
+static BOOLEAN dir_has_same_owner(struct stat *dst_info,
+ struct stat *src_info)
+{
+ if (S_ISDIR(dst_info->st_mode)) {
+ if (dst_info->st_uid == src_info->st_uid) {
+ return TRUE;
+ } else {
+ HTAlert(gettext("Destination has different owner! Request denied."));
+ }
+ } else {
+ HTAlert(gettext("Destination is not a valid directory! Request denied."));
+ }
+ return FALSE;
+}
+
+/*
+ * Make sure the source and target are not the same location.
+ */
+static BOOLEAN same_location(struct stat *dst_info,
+ struct stat *src_info)
+{
+ BOOLEAN result = FALSE;
+
+#ifdef UNIX
+ if (src_info->st_dev == dst_info->st_dev &&
+ src_info->st_ino == dst_info->st_ino) {
+ HTAlert(gettext("Source and destination are the same location! Request ignored!"));
+ result = TRUE;
+ }
+#endif
+ return result;
+}
+
+/*
+ * Remove all tagged files and directories.
+ */
+static int remove_tagged(void)
+{
+ int ans;
+ BOOL will_clear = TRUE;
+ char *cp;
+ char *tmpbuf = NULL;
+ char *testpath = NULL;
+ struct stat dir_info;
+ int count;
+ HTList *tag;
+
+ if (HTList_isEmpty(tagged)) /* should never happen */
+ return 0;
+
+ ans = HTConfirm(gettext("Remove all tagged files and directories?"));
+
+ count = 0;
+ tag = tagged;
+ while (ans == YES && (cp = (char *) HTList_nextObject(tag)) != NULL) {
+ if (is_url(cp) == FILE_URL_TYPE) { /* unnecessary check */
+ testpath = HTfullURL_toFile(cp);
+ LYTrimPathSep(testpath);
+ will_clear = TRUE;
+
+ /*
+ * Check the current status of the path to be deleted.
+ */
+ if (!ok_stat(testpath, &dir_info)) {
+ will_clear = FALSE;
+ break;
+ } else {
+ if (remove_file(testpath) <= 0) {
+ if (count == 0)
+ count = -1;
+ will_clear = FALSE;
+ break;
+ }
+ ++count;
+ FREE(testpath);
+ }
+ }
+ }
+ FREE(testpath);
+ FREE(tmpbuf);
+ if (will_clear)
+ clear_tags();
+ return count;
+}
+
+static char *parse_directory(char *path)
+{
+ char *result;
+
+ if (path) {
+ path = strip_trailing_slash(path);
+ path = HTParse(".", path, PARSE_PATH + PARSE_PUNCTUATION);
+ result = HTURLPath_toFile(path, TRUE, FALSE);
+ FREE(path);
+ } else { /* Last resort, should never happen. */
+ result = HTURLPath_toFile(".", TRUE, FALSE);
+ }
+ return result;
+}
+
+/*
+ * Move all tagged files and directories to a new location.
+ *
+ * The 'testpath' parameter is the current location, used for resolving
+ * relative target specifications.
+ */
+static int modify_tagged(char *testpath)
+{
+ char *cp;
+ bstring *given_target = NULL;
+ char *dst_path = NULL;
+ char *src_path = NULL;
+ char *old_path = NULL;
+ struct stat src_info;
+ struct stat dst_info;
+ int count = 0;
+ HTList *tag;
+
+ CTRACE((tfp, "modify_tagged(%s)\n", testpath));
+
+ if (HTList_isEmpty(tagged)) /* should never happen */
+ return 0;
+
+ _statusline(gettext("Enter new location for tagged items: "));
+
+ BStrCopy0(given_target, "");
+ (void) LYgetBString(&given_target, FALSE, 0, NORECALL);
+ if (!isBEmpty(given_target)) {
+ /*
+ * Replace ~/ references to the home directory.
+ */
+ if (LYIsTilde(given_target->str[0]) && LYIsPathSep(given_target->str[1])) {
+ char *cp1 = NULL;
+
+ StrAllocCopy(cp1, Home_Dir());
+ StrAllocCat(cp1, (given_target->str + 1));
+ BStrCopy0(given_target, cp1);
+ FREE(cp1);
+ }
+
+ /*
+ * If path is relative, prefix it with current location.
+ */
+ if (!LYIsPathSep(given_target->str[0])) {
+ dst_path = HTLocalName(testpath);
+ LYAddPathSep(&dst_path);
+ StrAllocCat(dst_path, given_target->str);
+ } else {
+ dst_path = HTLocalName(given_target->str);
+ }
+
+ if (!ok_stat(dst_path, &dst_info)) {
+ FREE(dst_path);
+ BStrFree(given_target);
+ return 0;
+ }
+
+ /*
+ * Determine the ownership of the current location, using the directory
+ * containing the file or subdir from each of the tagged files.
+ */
+ for (tag = tagged; (cp = (char *) HTList_nextObject(tag)) != NULL;) {
+ src_path = parse_directory(cp);
+
+ if (isEmpty(old_path) || strcmp(old_path, src_path)) {
+ if (!ok_stat(src_path, &src_info)
+ || same_location(&dst_info, &src_info)
+ || !dir_has_same_owner(&dst_info, &src_info)) {
+ FREE(src_path);
+ BStrFree(given_target);
+ return 0;
+ }
+ }
+ StrAllocCopy(old_path, src_path);
+ FREE(src_path);
+ }
+
+ /*
+ * Move all tagged items to the target location.
+ */
+ for (tag = tagged; (cp = (char *) HTList_nextObject(tag)) != NULL;) {
+ src_path = HTfullURL_toFile(cp);
+
+ if (move_file(src_path, dst_path) < 0) {
+ if (count == 0)
+ count = -1;
+ break;
+ }
+ FREE(src_path);
+ ++count;
+ }
+ clear_tags();
+ FREE(src_path);
+ FREE(dst_path);
+ }
+ BStrFree(given_target);
+ return count;
+}
+
+/*
+ * Modify the name of the specified item.
+ */
+static int modify_name(char *testpath)
+{
+ const char *cp;
+ bstring *tmpbuf = NULL;
+ char *newpath = NULL;
+ struct stat dir_info;
+ int code = 0;
+
+ /*
+ * Determine the status of the selected item.
+ */
+ testpath = strip_trailing_slash(testpath);
+
+ if (ok_stat(testpath, &dir_info)) {
+
+ /*
+ * Change the name of the file or directory.
+ */
+ if (S_ISDIR(dir_info.st_mode)) {
+ cp = gettext("Enter new name for directory: ");
+ } else if (S_ISREG(dir_info.st_mode)) {
+ cp = gettext("Enter new name for file: ");
+ } else {
+ return ok_file_or_dir(&dir_info);
+ }
+
+ BStrCopy0(tmpbuf, LYPathLeaf(testpath));
+ if (get_filename(cp, &tmpbuf)) {
+
+ /*
+ * Do not allow the user to also change the location at this time.
+ */
+ if (LYLastPathSep(tmpbuf->str) != 0) {
+ HTAlert(gettext("Illegal character (path-separator) found! Request ignored."));
+ } else if (strlen(tmpbuf->str)) {
+ if ((cp = LYLastPathSep(testpath)) != NULL) {
+ HTSprintf0(&newpath, "%.*s%s",
+ (int) (cp - testpath + 1),
+ testpath, tmpbuf->str);
+ } else {
+ StrAllocCopy(newpath, tmpbuf->str);
+ }
+
+ /*
+ * Make sure the destination does not already exist.
+ */
+ if (not_already_exists(newpath)) {
+ code = move_file(testpath, newpath);
+ }
+ FREE(newpath);
+ }
+ }
+ }
+ BStrFree(tmpbuf);
+ return code;
+}
+
+/*
+ * Change the location of a file or directory.
+ */
+static int modify_location(char *testpath)
+{
+ const char *cp;
+ char *sp;
+ bstring *tmpbuf = NULL;
+ char *newpath = NULL;
+ char *savepath = NULL;
+ struct stat old_info;
+ struct stat dir_info;
+ int code = 0;
+
+ /*
+ * Determine the status of the selected item.
+ */
+ testpath = strip_trailing_slash(testpath);
+ if (!ok_stat(testpath, &dir_info)) {
+ return 0;
+ }
+
+ /*
+ * Change the location of the file or directory.
+ */
+ if (S_ISDIR(dir_info.st_mode)) {
+ cp = gettext("Enter new location for directory: ");
+ } else if (S_ISREG(dir_info.st_mode)) {
+ cp = gettext("Enter new location for file: ");
+ } else {
+ return ok_file_or_dir(&dir_info);
+ }
+
+ BStrCopy0(tmpbuf, testpath);
+ *LYPathLeaf(tmpbuf->str) = '\0';
+ if (get_filename(cp, &tmpbuf)) {
+ if (strlen(tmpbuf->str)) {
+ StrAllocCopy(savepath, testpath);
+ StrAllocCopy(newpath, testpath);
+
+ /*
+ * Allow ~/ references to the home directory.
+ */
+ if (LYIsTilde(tmpbuf->str[0])
+ && (tmpbuf->str[1] == '\0' || LYIsPathSep(tmpbuf->str[1]))) {
+ StrAllocCopy(newpath, Home_Dir());
+ StrAllocCat(newpath, (tmpbuf->str + 1));
+ BStrCopy0(tmpbuf, newpath);
+ }
+ if (LYisAbsPath(tmpbuf->str)) {
+ StrAllocCopy(newpath, tmpbuf->str);
+ } else if ((sp = LYLastPathSep(newpath)) != NULL) {
+ *++sp = '\0';
+ StrAllocCat(newpath, tmpbuf->str);
+ } else {
+ HTAlert(gettext("Unexpected failure - unable to find trailing path separator"));
+ FREE(newpath);
+ FREE(savepath);
+ BStrFree(tmpbuf);
+ return 0;
+ }
+
+ /*
+ * Make sure the source and target have the same owner (uid).
+ */
+ old_info = dir_info;
+ if (!ok_stat(newpath, &dir_info)) {
+ code = 0;
+ } else if (same_location(&old_info, &dir_info)) {
+ code = 0;
+ } else if (dir_has_same_owner(&dir_info, &old_info)) {
+ code = move_file(savepath, newpath);
+ }
+ FREE(newpath);
+ FREE(savepath);
+ }
+ }
+ BStrFree(tmpbuf);
+ return code;
+}
+
+/*
+ * Modify name or location of a file or directory on localhost.
+ */
+int local_modify(DocInfo *doc, char **newpath)
+{
+ int ans;
+ char *cp;
+ bstring *testpath = NULL;
+ int count;
+ int code = 0;
+
+ if (!HTList_isEmpty(tagged)) {
+ cp = HTpartURL_toFile(doc->address);
+
+ count = modify_tagged(cp);
+ FREE(cp);
+
+ if (doc->link > (nlinks - count - 1))
+ doc->link = (nlinks - count - 1);
+ doc->link = ((doc->link < 0)
+ ? 0
+ : doc->link);
+
+ return count;
+ } else if (doc->link < 0 || doc->link > nlinks) {
+ /*
+ * Added protection.
+ */
+ return 0;
+ }
+
+ /*
+ * Do not allow simultaneous change of name and location as in Unix. This
+ * reduces functionality but reduces difficulty for the novice.
+ */
+#ifdef OK_PERMIT
+ _statusline(gettext("Modify name, location, or permission (n, l, or p): "));
+#else
+ _statusline(gettext("Modify name or location (n or l): "));
+#endif /* OK_PERMIT */
+ ans = LYgetch_single();
+
+ if (StrChr("NLP", ans) != NULL) {
+ cp = HTfullURL_toFile(links[doc->link].lname);
+ if (strlen(cp) >= DIRED_MAXBUF) {
+ FREE(cp);
+ return 0;
+ }
+ BStrCopy0(testpath, cp);
+ FREE(cp);
+
+ if (ans == 'N') {
+ code = modify_name(testpath->str);
+ } else if (ans == 'L') {
+ if (modify_location(testpath->str)) {
+ if (doc->link == (nlinks - 1))
+ --doc->link;
+ code = 1;
+ }
+#ifdef OK_PERMIT
+ } else if (ans == 'P') {
+ code = permit_location(NULL, testpath->str, newpath);
+#endif /* OK_PERMIT */
+ } else {
+ /*
+ * Code for changing ownership needed here.
+ */
+ HTAlert(gettext("This feature not yet implemented!"));
+ }
+ }
+ BStrFree(testpath);
+ return code;
+}
+
+#define BadChars() ((!no_dotfiles && show_dotfiles) \
+ ? "~/" \
+ : ".~/")
+
+/*
+ * Create a new empty file in the current directory.
+ */
+static int create_file(char *current_location)
+{
+ int code = FALSE;
+ bstring *tmpbuf = NULL;
+ char *testpath = NULL;
+
+ BStrCopy0(tmpbuf, "");
+ if (get_filename(gettext("Enter name of file to create: "), &tmpbuf)) {
+
+ if (strstr(tmpbuf->str, "//") != NULL) {
+ HTAlert(gettext("Illegal redirection \"//\" found! Request ignored."));
+ } else if (strlen(tmpbuf->str) &&
+ StrChr(BadChars(), tmpbuf->str[0]) == NULL) {
+ StrAllocCopy(testpath, current_location);
+ LYAddPathSep(&testpath);
+
+ /*
+ * Append the target filename to the current location.
+ */
+ StrAllocCat(testpath, tmpbuf->str);
+
+ /*
+ * Make sure the target does not already exist
+ */
+ if (not_already_exists(testpath)) {
+ code = touch_file(testpath);
+ }
+ FREE(testpath);
+ }
+ }
+ BStrFree(tmpbuf);
+ return code;
+}
+
+/*
+ * Create a new directory in the current directory.
+ */
+static int create_directory(char *current_location)
+{
+ int code = FALSE;
+ bstring *tmpbuf = NULL;
+ char *testpath = NULL;
+
+ BStrCopy0(tmpbuf, "");
+ if (get_filename(gettext("Enter name for new directory: "), &tmpbuf)) {
+
+ if (strstr(tmpbuf->str, "//") != NULL) {
+ HTAlert(gettext("Illegal redirection \"//\" found! Request ignored."));
+ } else if (strlen(tmpbuf->str) &&
+ StrChr(BadChars(), tmpbuf->str[0]) == NULL) {
+ StrAllocCopy(testpath, current_location);
+ LYAddPathSep(&testpath);
+
+ StrAllocCat(testpath, tmpbuf->str);
+
+ /*
+ * Make sure the target does not already exist.
+ */
+ if (not_already_exists(testpath)) {
+ code = make_directory(testpath);
+ }
+ FREE(testpath);
+ }
+ }
+ BStrFree(tmpbuf);
+ return code;
+}
+
+/*
+ * Create a file or a directory at the current location.
+ */
+int local_create(DocInfo *doc)
+{
+ int ans;
+ char *cp;
+ char testpath[DIRED_MAXBUF];
+
+ cp = HTfullURL_toFile(doc->address);
+ if (strlen(cp) >= DIRED_MAXBUF) {
+ FREE(cp);
+ return 0;
+ }
+ strcpy(testpath, cp);
+ FREE(cp);
+
+ _statusline(gettext("Create file or directory (f or d): "));
+ ans = LYgetch_single();
+
+ if (ans == 'F') {
+ return (create_file(testpath));
+ } else if (ans == 'D') {
+ return (create_directory(testpath));
+ } else {
+ return 0;
+ }
+}
+
+/*
+ * Remove a single file or directory.
+ */
+static int remove_single(char *testpath)
+{
+ int code = 0;
+ char *cp;
+ char *tmpbuf = 0;
+ struct stat dir_info;
+ BOOL is_directory = FALSE;
+
+ if (!ok_lstat(testpath, &dir_info)) {
+ return 0;
+ }
+
+ /*
+ * Locate the filename portion of the path.
+ */
+ if ((cp = LYLastPathSep(testpath)) != NULL) {
+ ++cp;
+ } else {
+ cp = testpath;
+ }
+ if (S_ISDIR(dir_info.st_mode)) {
+ /*
+ * This strlen stuff will probably screw up intl translations. Course,
+ * it's probably broken for screen sizes other 80, too -jes
+ */
+ if (strlen(cp) < 37) {
+ HTSprintf0(&tmpbuf,
+ gettext("Remove directory '%s'?"), cp);
+ } else {
+ HTSprintf0(&tmpbuf,
+ gettext("Remove directory?"));
+ }
+ is_directory = TRUE;
+ } else if (S_ISREG(dir_info.st_mode)) {
+ if (strlen(cp) < 60) {
+ HTSprintf0(&tmpbuf, gettext("Remove file '%s'?"), cp);
+ } else {
+ HTSprintf0(&tmpbuf, gettext("Remove file?"));
+ }
+#ifdef S_IFLNK
+ } else if (S_ISLNK(dir_info.st_mode)) {
+ if (strlen(cp) < 50) {
+ HTSprintf0(&tmpbuf, gettext("Remove symbolic link '%s'?"), cp);
+ } else {
+ HTSprintf0(&tmpbuf, gettext("Remove symbolic link?"));
+ }
+#endif
+ } else {
+ cannot_stat(testpath);
+ FREE(tmpbuf);
+ return 0;
+ }
+
+ if (HTConfirm(tmpbuf) == YES) {
+ code = is_directory
+ ? remove_directory(testpath)
+ : remove_file(testpath);
+ }
+ FREE(tmpbuf);
+ return code;
+}
+
+/*
+ * Remove a file or a directory.
+ */
+int local_remove(DocInfo *doc)
+{
+ char *cp, *tp;
+ char testpath[DIRED_MAXBUF];
+ int count, i;
+
+ if (!HTList_isEmpty(tagged)) {
+ count = remove_tagged();
+ if (doc->link > (nlinks - count - 1))
+ doc->link = (nlinks - count - 1);
+ doc->link = ((doc->link < 0)
+ ? 0
+ : doc->link);
+ return count;
+ } else if (doc->link < 0 || doc->link > nlinks) {
+ return 0;
+ }
+ cp = links[doc->link].lname;
+ if (is_url(cp) == FILE_URL_TYPE) {
+ tp = HTfullURL_toFile(cp);
+ if (strlen(tp) >= DIRED_MAXBUF) {
+ FREE(tp);
+ return 0;
+ }
+ strcpy(testpath, tp);
+ FREE(tp);
+
+ if ((i = (int) strlen(testpath)) && testpath[i - 1] == '/')
+ testpath[(i - 1)] = '\0';
+
+ if (remove_single(testpath)) {
+ if (doc->link == (nlinks - 1))
+ --doc->link;
+ return 1;
+ }
+ }
+ return 0;
+}
+
+#ifdef OK_PERMIT
+
+static bstring *LYValidPermitFile = NULL;
+
+static long permit_bits(char *string_mode)
+{
+ if (!strcmp(string_mode, "IRUSR"))
+ return S_IRUSR;
+ if (!strcmp(string_mode, "IWUSR"))
+ return S_IWUSR;
+ if (!strcmp(string_mode, "IXUSR"))
+ return S_IXUSR;
+ if (!strcmp(string_mode, "IRGRP"))
+ return S_IRGRP;
+ if (!strcmp(string_mode, "IWGRP"))
+ return S_IWGRP;
+ if (!strcmp(string_mode, "IXGRP"))
+ return S_IXGRP;
+ if (!strcmp(string_mode, "IROTH"))
+ return S_IROTH;
+ if (!strcmp(string_mode, "IWOTH"))
+ return S_IWOTH;
+ if (!strcmp(string_mode, "IXOTH"))
+ return S_IXOTH;
+ /* Don't include setuid and friends; use shell access for that. */
+ return 0;
+}
+
+/*
+ * Handle DIRED permissions.
+ */
+static int permit_location(char *destpath,
+ char *srcpath,
+ char **newpath)
+{
+ int code = 0;
+
+#ifndef UNIX
+ HTAlert(gettext("Sorry, don't know how to permit non-UNIX files yet."));
+#else
+ static char tempfile[LY_MAXPATH] = "\0";
+ char *cp;
+ char tmpdst[LY_MAXPATH];
+ struct stat dir_info;
+ const char *program;
+
+ if (srcpath) {
+ /*
+ * Create form.
+ */
+ FILE *fp0;
+ char *user_filename;
+ const char *group_name;
+
+ srcpath = strip_trailing_slash(srcpath);
+
+ /*
+ * A couple of sanity tests.
+ */
+ if (!ok_lstat(srcpath, &dir_info)
+ || !ok_file_or_dir(&dir_info))
+ return code;
+
+ user_filename = LYPathLeaf(srcpath);
+
+ (void) LYRemoveTemp(tempfile);
+ if ((fp0 = LYOpenTemp(tempfile, HTML_SUFFIX, "w")) == NULL) {
+ HTAlert(gettext("Unable to open permit options file"));
+ return (code);
+ }
+
+ /*
+ * Make the tempfile a URL.
+ */
+ LYLocalFileToURL(newpath, tempfile);
+ LYRegisterUIPage(*newpath, UIP_PERMIT_OPTIONS);
+
+ group_name = HTAA_GidToName((int) dir_info.st_gid);
+ BStrCopy0(LYValidPermitFile, srcpath);
+
+ fprintf(fp0, "<Html><Head>\n<Title>%s</Title>\n</Head>\n<Body>\n",
+ PERMIT_OPTIONS_TITLE);
+ fprintf(fp0, "<H1>%s%s</H1>\n", PERMISSIONS_SEGMENT, user_filename);
+ {
+ /*
+ * Prevent filenames which include '#' or '?' from messing it up.
+ */
+ char *srcpath_url = HTEscape(srcpath, URL_PATH);
+
+ fprintf(fp0, "<Form Action=\"%s//PERMIT_LOCATION%s\">\n",
+ STR_LYNXDIRED, srcpath_url);
+ FREE(srcpath_url);
+ }
+
+ fprintf(fp0, "<Ol><Li>%s<Br><Br>\n",
+ gettext("Specify permissions below:"));
+ fprintf(fp0, "%s:<Br>\n", gettext("Owner:"));
+ fprintf(fp0,
+ "<Input Type=\"checkbox\" Name=\"mode\" Value=\"IRUSR\" %s> Read<Br>\n",
+ (dir_info.st_mode & S_IRUSR) ? "checked" : "");
+ fprintf(fp0,
+ "<Input Type=\"checkbox\" Name=\"mode\" Value=\"IWUSR\" %s> Write<Br>\n",
+ (dir_info.st_mode & S_IWUSR) ? "checked" : "");
+ /*
+ * If restricted, only change eXecute permissions on directories.
+ */
+ if (!no_change_exec_perms || S_ISDIR(dir_info.st_mode))
+ fprintf(fp0,
+ "<Input Type=\"checkbox\" Name=\"mode\" Value=\"IXUSR\" %s> %s<Br>\n",
+ (dir_info.st_mode & S_IXUSR) ? "checked" : "",
+ S_ISDIR(dir_info.st_mode) ? "Search" : "Execute");
+
+ fprintf(fp0, "%s %s:<Br>\n", gettext("Group"), group_name);
+ fprintf(fp0,
+ "<Input Type=\"checkbox\" Name=\"mode\" Value=\"IRGRP\" %s> Read<Br>\n",
+ (dir_info.st_mode & S_IRGRP) ? "checked" : "");
+ fprintf(fp0,
+ "<Input Type=\"checkbox\" Name=\"mode\" Value=\"IWGRP\" %s> Write<Br>\n",
+ (dir_info.st_mode & S_IWGRP) ? "checked" : "");
+ /*
+ * If restricted, only change eXecute permissions on directories.
+ */
+ if (!no_change_exec_perms || S_ISDIR(dir_info.st_mode))
+ fprintf(fp0,
+ "<Input Type=\"checkbox\" Name=\"mode\" Value=\"IXGRP\" %s> %s<Br>\n",
+ (dir_info.st_mode & S_IXGRP) ? "checked" : "",
+ S_ISDIR(dir_info.st_mode) ? "Search" : "Execute");
+
+ fprintf(fp0, "%s<Br>\n", gettext("Others:"));
+ fprintf(fp0,
+ "<Input Type=\"checkbox\" Name=\"mode\" Value=\"IROTH\" %s> Read<Br>\n",
+ (dir_info.st_mode & S_IROTH) ? "checked" : "");
+ fprintf(fp0,
+ "<Input Type=\"checkbox\" Name=\"mode\" Value=\"IWOTH\" %s> Write<Br>\n",
+ (dir_info.st_mode & S_IWOTH) ? "checked" : "");
+ /*
+ * If restricted, only change eXecute permissions on directories.
+ */
+ if (!no_change_exec_perms || S_ISDIR(dir_info.st_mode))
+ fprintf(fp0,
+ "<Input Type=\"checkbox\" Name=\"mode\" Value=\"IXOTH\" %s> %s<Br>\n",
+ (dir_info.st_mode & S_IXOTH) ? "checked" : "",
+ S_ISDIR(dir_info.st_mode) ? "Search" : "Execute");
+
+ fprintf(fp0,
+ "<Br>\n<Li><Input Type=\"submit\" Value=\"Submit\"> %s %s %s.\n</Ol>\n</Form>\n",
+ gettext("form to permit"),
+ S_ISDIR(dir_info.st_mode) ? "directory" : "file",
+ user_filename);
+ fprintf(fp0, "</Body></Html>");
+ LYCloseTempFP(fp0);
+
+ LYforce_no_cache = TRUE;
+ code = PERMIT_FORM_RESULT; /* Special flag for LYMainLoop */
+
+ } else { /* The form being activated. */
+ mode_t new_mode = 0;
+
+ /*
+ * Make sure we have a valid set-permission file comparison string
+ * loaded via a previous call with srcpath != NULL. - KW
+ */
+ if (isBEmpty(LYValidPermitFile)) {
+ if (LYCursesON)
+ HTAlert(INVALID_PERMIT_URL);
+ else
+ fprintf(stderr, "%s\n", INVALID_PERMIT_URL);
+ CTRACE((tfp, "permit_location: called for <%s>.\n",
+ (destpath ?
+ destpath : "NULL URL pointer")));
+ return code;
+ }
+ cp = destpath;
+ while (*cp != '\0' && *cp != '?') { /* Find filename */
+ cp++;
+ }
+ if (*cp == '\0') {
+ return (code); /* Nothing to permit. */
+ }
+ *cp++ = '\0'; /* Null terminate file name and
+ start working on the masks. */
+
+ /* Will now operate only on filename part. */
+ if ((destpath = HTURLPath_toFile(destpath, TRUE, FALSE)) == 0)
+ return (code);
+ if (strlen(destpath) >= LY_MAXPATH) {
+ FREE(destpath);
+ return (code);
+ }
+ strcpy(tmpdst, destpath);
+ FREE(destpath);
+ destpath = tmpdst;
+
+ /*
+ * Make sure that the file string is the one from the last displayed
+ * File Permissions menu. - KW
+ */
+ if (strcmp(destpath, LYValidPermitFile->str)) {
+ if (LYCursesON)
+ HTAlert(INVALID_PERMIT_URL);
+ else
+ fprintf(stderr, "%s\n", INVALID_PERMIT_URL);
+ CTRACE((tfp, "permit_location: called for file '%s'.\n",
+ destpath));
+ return code;
+ }
+
+ /*
+ * A couple of sanity tests.
+ */
+ destpath = strip_trailing_slash(destpath);
+ if (!ok_stat(destpath, &dir_info)
+ || !ok_file_or_dir(&dir_info)) {
+ return code;
+ }
+
+ /*
+ * Cycle over permission strings.
+ */
+ while (*cp != '\0') {
+ char *cr = cp;
+
+ while (*cr != '\0' && *cr != '&') { /* GET data split by '&'. */
+ cr++;
+ }
+ if (*cr != '\0') {
+ *cr++ = '\0';
+ }
+ if (StrNCmp(cp, "mode=", 5) == 0) { /* Magic string. */
+ long mask = permit_bits(cp + 5);
+
+ if (mask != 0) {
+ /*
+ * If restricted, only change eXecute permissions on
+ * directories.
+ */
+ if (!no_change_exec_perms
+ || StrChr(cp + 5, 'X') == NULL
+ || S_ISDIR(dir_info.st_mode)) {
+ new_mode |= (mode_t) mask;
+ }
+ } else {
+ HTAlert(gettext("Invalid mode format."));
+ return code;
+ }
+ } else {
+ HTAlert(gettext("Invalid syntax format."));
+ return code;
+ }
+
+ cp = cr;
+ }
+
+ /*
+ * Call chmod().
+ */
+ code = 1;
+ if ((program = HTGetProgramPath(ppCHMOD)) != NULL) {
+ char **args;
+ char amode[10];
+ char *tmpbuf = NULL;
+
+ HTSprintf0(&tmpbuf, "chmod %.4o %s", (unsigned) new_mode, destpath);
+ sprintf(amode, "%.4o", (unsigned) new_mode);
+ args = make_argv("chmod",
+ amode,
+ destpath,
+ NULL);
+ if (LYExecv(program, args, tmpbuf) <= 0) {
+ code = -1;
+ }
+ FREE(tmpbuf);
+ free_argv(args);
+ } else {
+ if (chmod(destpath, new_mode) < 0) {
+ code = -1;
+ }
+ CTRACE((tfp, "builtin chmod %.4o ->%d\n\t%s\n",
+ (unsigned) new_mode, code, destpath));
+ }
+ if (code == 1)
+ LYforce_no_cache = TRUE; /* Force update of dired listing. */
+ }
+#endif /* !UNIX */
+ return code;
+}
+#endif /* OK_PERMIT */
+
+/*
+ * Display or remove a tag from a given link.
+ */
+void tagflag(int flag,
+ int cur)
+{
+ if (nlinks > 0) {
+ LYmove(links[cur].ly, 2);
+ lynx_stop_reverse();
+ if (flag == TRUE) {
+ LYaddch('+');
+ } else {
+ LYaddch(' ');
+ }
+
+#if defined(FANCY_CURSES) || defined(USE_SLANG)
+ if (!LYShowCursor)
+ LYHideCursor(); /* get cursor out of the way */
+ else
+#endif /* FANCY CURSES || USE_SLANG */
+ /*
+ * Never hide the cursor if there's no FANCY CURSES.
+ */
+ LYmove(links[cur].ly, links[cur].lx);
+
+ LYrefresh();
+ }
+}
+
+/*
+ * Handle DIRED tags.
+ */
+void showtags(HTList *t)
+{
+ int i;
+ HTList *s;
+ char *name;
+
+ for (i = 0; i < nlinks; i++) {
+ s = t;
+ while ((name = (char *) HTList_nextObject(s)) != NULL) {
+ if (!strcmp(links[i].lname, name)) {
+ tagflag(TRUE, i);
+ break;
+ }
+ }
+ }
+}
+
+static char *DirectoryOf(char *pathname)
+{
+ char *result = 0;
+ char *leaf;
+
+ StrAllocCopy(result, pathname);
+ leaf = LYPathLeaf(result);
+
+ if (leaf != result) {
+ const char *result1 = 0;
+
+ *leaf = '\0';
+ if (!LYisRootPath(result))
+ LYTrimPathSep(result);
+ result1 = wwwName(result);
+ StrAllocCopy(result, result1);
+ }
+ return result;
+}
+
+#ifdef __DJGPP__
+/*
+ * Convert filenames to acceptable 8+3 names when necessary. Make a copy of
+ * the parameter if we must modify it.
+ */
+static char *LYonedot(char *line)
+{
+ char *dot;
+ static char line1[LY_MAXPATH];
+
+ if (pathconf(line, _PC_NAME_MAX) <= 12) {
+ LYStrNCpy(line1, line, sizeof(line1) - 1);
+ for (;;) {
+ if ((dot = strrchr(line1, '.')) == 0
+ || LYLastPathSep(dot) != 0) {
+ break;
+ } else if (strlen(dot) == 1) {
+ *dot = 0;
+ } else {
+ *dot = '_';
+ }
+ }
+ return (line1);
+ }
+ return (line);
+}
+#else
+#define LYonedot(path) path
+#endif /* __DJGPP__ */
+
+static char *match_op(const char *prefix,
+ char *data)
+{
+ size_t len = strlen(prefix);
+
+ if (!StrNCmp("LYNXDIRED://", data, 12)
+ && !strncasecomp(prefix, data + 12, (int) len)) {
+ len += 12;
+#if defined(USE_DOS_DRIVES)
+ if (data[len] == '/') { /* this is normal */
+ len++;
+ }
+#endif
+ return data + len;
+ }
+ return 0;
+}
+
+/*
+ * Construct the appropriate system command taking care to escape all path
+ * references to avoid spoofing the shell.
+ */
+static char *build_command(char *line,
+ char *dirName,
+ char *arg)
+{
+ char *buffer = NULL;
+ const char *program;
+ const char *tar_path = HTGetProgramPath(ppTAR);
+
+ if ((arg = match_op("DECOMPRESS", line)) != 0) {
+#define FMT_UNCOMPRESS "%s %s"
+ if ((program = HTGetProgramPath(ppUNCOMPRESS)) != NULL) {
+ HTAddParam(&buffer, FMT_UNCOMPRESS, 1, program);
+ HTAddParam(&buffer, FMT_UNCOMPRESS, 2, arg);
+ HTEndParam(&buffer, FMT_UNCOMPRESS, 2);
+ }
+ return buffer;
+ }
+#if defined(OK_UUDECODE) && !defined(ARCHIVE_ONLY)
+ if ((arg = match_op("UUDECODE", line)) != 0) {
+#define FMT_UUDECODE "%s %s"
+ if ((program = HTGetProgramPath(ppUUDECODE)) != NULL) {
+ HTAddParam(&buffer, FMT_UUDECODE, 1, program);
+ HTAddParam(&buffer, FMT_UUDECODE, 2, arg);
+ HTEndParam(&buffer, FMT_UUDECODE, 2);
+ HTAlert(gettext("Warning! UUDecoded file will exist in the directory you started Lynx."));
+ }
+ return buffer;
+ }
+#endif /* OK_UUDECODE && !ARCHIVE_ONLY */
+
+#ifdef OK_TAR
+ if (tar_path != NULL) {
+# ifndef ARCHIVE_ONLY
+# ifdef OK_GZIP
+ if ((arg = match_op("UNTAR_GZ", line)) != 0) {
+#define FMT_UNTAR_GZ "cd %s; %s -qdc %s | %s %s %s"
+ if ((program = HTGetProgramPath(ppGZIP)) != NULL) {
+ dirName = DirectoryOf(arg);
+ HTAddParam(&buffer, FMT_UNTAR_GZ, 1, dirName);
+ HTAddParam(&buffer, FMT_UNTAR_GZ, 2, program);
+ HTAddParam(&buffer, FMT_UNTAR_GZ, 3, arg);
+ HTAddParam(&buffer, FMT_UNTAR_GZ, 4, tar_path);
+ HTAddToCmd(&buffer, FMT_UNTAR_GZ, 5, TAR_DOWN_OPTIONS);
+ HTAddToCmd(&buffer, FMT_UNTAR_GZ, 6, TAR_PIPE_OPTIONS);
+ HTEndParam(&buffer, FMT_UNTAR_GZ, 6);
+ }
+ return buffer;
+ }
+# endif /* OK_GZIP */
+ if ((arg = match_op("UNTAR_Z", line)) != 0) {
+#define FMT_UNTAR_Z "cd %s; %s %s | %s %s %s"
+ if ((program = HTGetProgramPath(ppZCAT)) != NULL) {
+ dirName = DirectoryOf(arg);
+ HTAddParam(&buffer, FMT_UNTAR_Z, 1, dirName);
+ HTAddParam(&buffer, FMT_UNTAR_Z, 2, program);
+ HTAddParam(&buffer, FMT_UNTAR_Z, 3, arg);
+ HTAddParam(&buffer, FMT_UNTAR_Z, 4, tar_path);
+ HTAddToCmd(&buffer, FMT_UNTAR_Z, 5, TAR_DOWN_OPTIONS);
+ HTAddToCmd(&buffer, FMT_UNTAR_Z, 6, TAR_PIPE_OPTIONS);
+ HTEndParam(&buffer, FMT_UNTAR_Z, 6);
+ }
+ return buffer;
+ }
+ if ((arg = match_op("UNTAR", line)) != 0) {
+#define FMT_UNTAR "cd %s; %s %s %s"
+ dirName = DirectoryOf(arg);
+ HTAddParam(&buffer, FMT_UNTAR, 1, dirName);
+ HTAddParam(&buffer, FMT_UNTAR, 2, tar_path);
+ HTAddToCmd(&buffer, FMT_UNTAR, 3, TAR_DOWN_OPTIONS);
+ HTAddParam(&buffer, FMT_UNTAR, 4, arg);
+ HTEndParam(&buffer, FMT_UNTAR, 4);
+ return buffer;
+ }
+# endif /* !ARCHIVE_ONLY */
+
+# ifdef OK_GZIP
+ if ((arg = match_op("TAR_GZ", line)) != 0) {
+#define FMT_TAR_GZ "cd %s; %s %s %s %s | %s -qc >%s%s"
+ if ((program = HTGetProgramPath(ppGZIP)) != NULL) {
+ dirName = DirectoryOf(arg);
+ HTAddParam(&buffer, FMT_TAR_GZ, 1, dirName);
+ HTAddParam(&buffer, FMT_TAR_GZ, 2, tar_path);
+ HTAddToCmd(&buffer, FMT_TAR_GZ, 3, TAR_UP_OPTIONS);
+ HTAddToCmd(&buffer, FMT_TAR_GZ, 4, TAR_PIPE_OPTIONS);
+ HTAddParam(&buffer, FMT_TAR_GZ, 5, LYPathLeaf(arg));
+ HTAddParam(&buffer, FMT_TAR_GZ, 6, program);
+ HTAddParam(&buffer, FMT_TAR_GZ, 7, LYonedot(LYPathLeaf(arg)));
+ HTAddParam(&buffer, FMT_TAR_GZ, 8, EXT_TAR_GZ);
+ HTEndParam(&buffer, FMT_TAR_GZ, 8);
+ }
+ return buffer;
+ }
+# endif /* OK_GZIP */
+
+ if ((arg = match_op("TAR_Z", line)) != 0) {
+#define FMT_TAR_Z "cd %s; %s %s %s %s | %s >%s%s"
+ if ((program = HTGetProgramPath(ppCOMPRESS)) != NULL) {
+ dirName = DirectoryOf(arg);
+ HTAddParam(&buffer, FMT_TAR_Z, 1, dirName);
+ HTAddParam(&buffer, FMT_TAR_Z, 2, tar_path);
+ HTAddToCmd(&buffer, FMT_TAR_Z, 3, TAR_UP_OPTIONS);
+ HTAddToCmd(&buffer, FMT_TAR_Z, 4, TAR_PIPE_OPTIONS);
+ HTAddParam(&buffer, FMT_TAR_Z, 5, LYPathLeaf(arg));
+ HTAddParam(&buffer, FMT_TAR_Z, 6, program);
+ HTAddParam(&buffer, FMT_TAR_Z, 7, LYonedot(LYPathLeaf(arg)));
+ HTAddParam(&buffer, FMT_TAR_Z, 8, EXT_TAR_Z);
+ HTEndParam(&buffer, FMT_TAR_Z, 8);
+ }
+ return buffer;
+ }
+
+ if ((arg = match_op("TAR", line)) != 0) {
+#define FMT_TAR "cd %s; %s %s %s %s.tar %s"
+ dirName = DirectoryOf(arg);
+ HTAddParam(&buffer, FMT_TAR, 1, dirName);
+ HTAddParam(&buffer, FMT_TAR, 2, tar_path);
+ HTAddToCmd(&buffer, FMT_TAR, 3, TAR_UP_OPTIONS);
+ HTAddToCmd(&buffer, FMT_TAR, 4, TAR_FILE_OPTIONS);
+ HTAddParam(&buffer, FMT_TAR, 5, LYonedot(LYPathLeaf(arg)));
+ HTAddParam(&buffer, FMT_TAR, 6, LYPathLeaf(arg));
+ HTEndParam(&buffer, FMT_TAR, 6);
+ return buffer;
+ }
+ }
+#endif /* OK_TAR */
+
+#ifdef OK_GZIP
+ if ((arg = match_op("GZIP", line)) != 0) {
+#define FMT_GZIP "%s -q %s"
+ if ((program = HTGetProgramPath(ppGZIP)) != NULL) {
+ HTAddParam(&buffer, FMT_GZIP, 1, program);
+ HTAddParam(&buffer, FMT_GZIP, 2, arg);
+ HTEndParam(&buffer, FMT_GZIP, 2);
+ }
+ return buffer;
+ }
+#ifndef ARCHIVE_ONLY
+ if ((arg = match_op("UNGZIP", line)) != 0) {
+#define FMT_UNGZIP "%s -d %s"
+ if ((program = HTGetProgramPath(ppGZIP)) != NULL) {
+ HTAddParam(&buffer, FMT_UNGZIP, 1, program);
+ HTAddParam(&buffer, FMT_UNGZIP, 2, arg);
+ HTEndParam(&buffer, FMT_UNGZIP, 2);
+ }
+ return buffer;
+ }
+#endif /* !ARCHIVE_ONLY */
+#endif /* OK_GZIP */
+
+#ifdef OK_ZIP
+ if ((arg = match_op("ZIP", line)) != 0) {
+#define FMT_ZIP "cd %s; %s -rq %s.zip %s"
+ if ((program = HTGetProgramPath(ppZIP)) != NULL) {
+ dirName = DirectoryOf(arg);
+ HTAddParam(&buffer, FMT_ZIP, 1, dirName);
+ HTAddParam(&buffer, FMT_ZIP, 2, program);
+ HTAddParam(&buffer, FMT_ZIP, 3, LYonedot(LYPathLeaf(arg)));
+ HTAddParam(&buffer, FMT_ZIP, 4, LYPathLeaf(arg));
+ HTEndParam(&buffer, FMT_ZIP, 4);
+ }
+ return buffer;
+ }
+#if !defined(ARCHIVE_ONLY)
+ if ((arg = match_op("UNZIP", line)) != 0) {
+#define FMT_UNZIP "cd %s; %s -q %s"
+ if ((program = HTGetProgramPath(ppUNZIP)) != NULL) {
+ dirName = DirectoryOf(arg);
+ HTAddParam(&buffer, FMT_UNZIP, 1, dirName);
+ HTAddParam(&buffer, FMT_UNZIP, 2, program);
+ HTAddParam(&buffer, FMT_UNZIP, 3, arg);
+ HTEndParam(&buffer, FMT_UNZIP, 3);
+ }
+ return buffer;
+ }
+# endif /* !ARCHIVE_ONLY */
+#endif /* OK_ZIP */
+
+ if ((arg = match_op("COMPRESS", line)) != 0) {
+#define FMT_COMPRESS "%s %s"
+ if ((program = HTGetProgramPath(ppCOMPRESS)) != NULL) {
+ HTAddParam(&buffer, FMT_COMPRESS, 1, program);
+ HTAddParam(&buffer, FMT_COMPRESS, 2, arg);
+ HTEndParam(&buffer, FMT_COMPRESS, 2);
+ }
+ return buffer;
+ }
+
+ return NULL;
+}
+
+/*
+ * Perform file management operations for LYNXDIRED URL's. Attempt to be
+ * consistent. These are (pseudo) URLs - i.e., they should be in URL syntax:
+ * some bytes will be URL-escaped with '%'. This is necessary because these
+ * (pseudo) URLs will go through some of the same kinds of interpretations and
+ * mutilations as real ones: HTParse, stripping off #fragments etc. (Some
+ * access schemes currently have special rules about not escaping parsing '#'
+ * "the URL way" built into HTParse, but that doesn't look like a clean way.)
+ */
+int local_dired(DocInfo *doc)
+{
+ char *line_url; /* will point to doc's address, which is a URL */
+ char *line = NULL; /* same as line_url, but HTUnEscaped, will be allocated */
+ char *arg = NULL; /* ...will point into line[] */
+ char *tp = NULL;
+ char *tmpbuf = NULL;
+ char *buffer = NULL;
+ char *dirName = NULL;
+ BOOL do_pop_doc = TRUE;
+
+ line_url = doc->address;
+ CTRACE((tfp, "local_dired: called for <%s>.\n",
+ (line_url
+ ? line_url
+ : gettext("NULL URL pointer"))));
+ HTUnEscapeSome(line_url, "/"); /* don't mess too much with *doc */
+
+ StrAllocCopy(line, line_url);
+ HTUnEscape(line); /* _file_ (not URL) syntax, for those functions
+ that need it. Don't forget to FREE it. */
+ if (match_op("CHDIR", line) != 0) {
+#ifdef SUPPORT_CHDIR
+ handle_LYK_CHDIR();
+ do_pop_doc = FALSE;
+#endif
+ arg = 0; /* do something to avoid cc's complaints */
+ } else if ((arg = match_op("NEW_FILE", line)) != 0) {
+ if (create_file(arg) > 0)
+ LYforce_no_cache = TRUE;
+ } else if ((arg = match_op("NEW_FOLDER", line)) != 0) {
+ if (create_directory(arg) > 0)
+ LYforce_no_cache = TRUE;
+#ifdef OK_INSTALL
+ } else if ((arg = match_op("INSTALL_SRC", line)) != 0) {
+ local_install(NULL, arg, &tp);
+ if (tp) {
+ FREE(doc->address);
+ doc->address = tp;
+ }
+ FREE(line);
+ return 0;
+ } else if ((arg = match_op("INSTALL_DEST", line)) != 0) {
+ local_install(arg, NULL, &tp);
+ LYpop(doc);
+#endif /* OK_INSTALL */
+ } else if ((arg = match_op("MODIFY_NAME", line)) != 0) {
+ if (modify_name(arg) > 0)
+ LYforce_no_cache = TRUE;
+ } else if ((arg = match_op("MODIFY_LOCATION", line)) != 0) {
+ if (modify_location(arg) > 0)
+ LYforce_no_cache = TRUE;
+ } else if ((arg = match_op("MOVE_TAGGED", line_url)) != 0) {
+ if (modify_tagged(arg) > 0)
+ LYforce_no_cache = TRUE;
+#ifdef OK_PERMIT
+ } else if ((arg = match_op("PERMIT_SRC", line)) != 0) {
+ permit_location(NULL, arg, &tp);
+ if (tp) {
+ /*
+ * One of the checks may have failed.
+ */
+ FREE(doc->address);
+ doc->address = tp;
+ }
+ FREE(line);
+ return 0;
+ } else if ((arg = match_op("PERMIT_LOCATION", line_url)) != 0) {
+ permit_location(arg, NULL, &tp);
+#endif /* OK_PERMIT */
+ } else if ((arg = match_op("REMOVE_SINGLE", line)) != 0) {
+ if (remove_single(arg) > 0)
+ LYforce_no_cache = TRUE;
+ } else if (match_op("REMOVE_TAGGED", line) != 0) {
+ if (remove_tagged())
+ LYforce_no_cache = TRUE;
+ } else if (match_op("CLEAR_TAGGED", line) != 0) {
+ clear_tags();
+ } else if ((arg = match_op("UPLOAD", line)) != 0) {
+ /*
+ * They're written by LYUpload_options() HTUnEscaped; don't want to
+ * change that for now... so pass through without more unescaping.
+ * Directory names containing '#' will probably fail.
+ */
+ if (LYUpload(line_url))
+ LYforce_no_cache = TRUE;
+ } else {
+ LYTrimPathSep(line);
+ if (LYLastPathSep(line) == NULL) {
+ FREE(line);
+ return 0;
+ }
+
+ buffer = build_command(line, dirName, arg);
+
+ if (buffer != 0) {
+ if ((int) strlen(buffer) < LYcolLimit - 14) {
+ HTSprintf0(&tmpbuf, gettext("Executing %s "), buffer);
+ } else {
+ HTSprintf0(&tmpbuf,
+ gettext("Executing system command. This might take a while."));
+ }
+ _statusline(tmpbuf);
+ stop_curses();
+ printf("%s\r\n", tmpbuf);
+ LYSystem(buffer);
+#ifdef VMS
+ HadVMSInterrupt = FALSE;
+#endif /* VMS */
+ start_curses();
+ LYforce_no_cache = TRUE;
+ }
+ }
+
+ FREE(dirName);
+ FREE(tmpbuf);
+ FREE(buffer);
+ FREE(line);
+ FREE(tp);
+ if (do_pop_doc)
+ LYpop(doc);
+ return 0;
+}
+
+/*
+ * Provide a menu of file management options.
+ */
+int dired_options(DocInfo *doc, char **newfile)
+{
+ static char tempfile[LY_MAXPATH];
+ const char *my_suffix;
+ char *path = NULL;
+ char *dir;
+ lynx_list_item_type *nxt;
+ struct stat dir_info;
+ FILE *fp0;
+ char *dir_url;
+ char *path_url;
+ BOOLEAN nothing_tagged;
+ int count;
+ struct dired_menu *mp;
+ char buf[2048];
+
+ if ((fp0 = InternalPageFP(tempfile, FALSE)) == 0)
+ return (0);
+
+ /*
+ * Make the tempfile a URL.
+ */
+ LYLocalFileToURL(newfile, tempfile);
+ LYRegisterUIPage(*newfile, UIP_DIRED_MENU);
+
+ if (doc->link > -1 && doc->link < (nlinks + 1)) {
+ path = HTfullURL_toFile(links[doc->link].lname);
+ LYTrimPathSep(path);
+
+ if (!ok_lstat(path, &dir_info)) {
+ LYCloseTempFP(fp0);
+ FREE(path);
+ return 0;
+ }
+
+ } else {
+ StrAllocCopy(path, "");
+ memset(&dir_info, 0, sizeof(dir_info));
+ }
+
+ dir = HTfullURL_toFile(doc->address);
+ LYTrimPathSep(dir);
+
+ nothing_tagged = (BOOL) (HTList_isEmpty(tagged));
+
+ BeginInternalPage(fp0, DIRED_MENU_TITLE, DIRED_MENU_HELP);
+
+ fprintf(fp0, "<em>%s</em> %s<br>\n", gettext("Current directory:"), dir);
+
+ if (nothing_tagged) {
+ fprintf(fp0, "<em>%s</em> ", gettext("Current selection:"));
+ if (strlen(path)) {
+ fprintf(fp0, "%s<p>\n", path);
+ } else {
+ fprintf(fp0, "%s.<p>\n", gettext("Nothing currently selected."));
+ }
+ } else {
+ /*
+ * Write out number of tagged items, and names of first few of them
+ * relative to current (in the DIRED sense) directory.
+ */
+ int n = HTList_count(tagged);
+ char *cp1 = NULL;
+ char *cd = NULL;
+ int i, m;
+
+#define NUM_TAGS_TO_WRITE 10
+ fprintf(fp0, "<em>%s</em> %d %s",
+ gettext("Current selection:"),
+ n, ((n == 1)
+ ? gettext("tagged item:")
+ : gettext("tagged items:")));
+ StrAllocCopy(cd, doc->address);
+ HTUnEscapeSome(cd, "/");
+ LYAddHtmlSep(&cd);
+ m = (n < NUM_TAGS_TO_WRITE) ? n : NUM_TAGS_TO_WRITE;
+ for (i = 1; i <= m; i++) {
+ cp1 = HTRelative((char *) HTList_objectAt(tagged, i - 1),
+ (*cd ? cd : "file://localhost"));
+ HTUnEscape(cp1);
+ LYEntify(&cp1, TRUE); /* _should_ do this everywhere... */
+ fprintf(fp0, "%s<br>\n&nbsp;&nbsp;&nbsp;%s",
+ (i == 1 ? "" : " ,"), cp1);
+ FREE(cp1);
+ }
+ if (n > m) {
+ fprintf(fp0, " , ...");
+ }
+ fprintf(fp0, "<p>\n");
+ FREE(cd);
+ }
+
+ /*
+ * If menu_head is NULL then use defaults and link them together now.
+ */
+ if (menu_head == NULL) {
+ for (mp = defmenu; GetDiredHref(mp) != NULL; mp++)
+ mp->next = (mp + 1);
+ (--mp)->next = NULL;
+ menu_head = defmenu;
+ }
+
+ for (mp = menu_head; mp != NULL; mp = mp->next) {
+ if (mp->cond != DE_TAG && !nothing_tagged)
+ continue;
+ if (mp->cond == DE_TAG && nothing_tagged)
+ continue;
+ if (mp->cond == DE_DIR &&
+ (!*path || !S_ISDIR(dir_info.st_mode)))
+ continue;
+ if (mp->cond == DE_FILE &&
+ (!*path || !S_ISREG(dir_info.st_mode)))
+ continue;
+#ifdef S_IFLNK
+ if (mp->cond == DE_SYMLINK &&
+ (!*path || !S_ISLNK(dir_info.st_mode)))
+ continue;
+#endif
+ my_suffix = GetDiredSuffix(mp);
+ if (non_empty(my_suffix) &&
+ (strlen(path) < strlen(my_suffix) ||
+ strcmp(my_suffix, &path[(strlen(path) - strlen(my_suffix))]) != 0))
+ continue;
+ dir_url = HTEscape(dir, URL_PATH);
+ path_url = HTEscape(path, URL_PATH);
+ fprintf(fp0, "<a href=\"%s",
+ render_item(GetDiredHref(mp),
+ path_url, dir_url, buf, sizeof(buf), YES));
+ fprintf(fp0, "\">%s</a> ",
+ render_item(GetDiredLink(mp),
+ path, dir, buf, sizeof(buf), NO));
+ fprintf(fp0, "%s<br>\n",
+ render_item(GetDiredRest(mp),
+ path, dir, buf, sizeof(buf), NO));
+ FREE(dir_url);
+ FREE(path_url);
+ }
+ FREE(path);
+
+ if (uploaders != NULL) {
+ fprintf(fp0, "<p>Upload to current directory:<p>\n");
+ for (count = 0, nxt = uploaders;
+ nxt != NULL;
+ nxt = nxt->next, count++) {
+ fprintf(fp0,
+ "<a href=\"LYNXDIRED://UPLOAD=%d/TO=%s\"> %s </a><br>\n",
+ count, dir, nxt->name);
+ }
+ }
+ FREE(dir);
+
+ EndInternalPage(fp0);
+ LYCloseTempFP(fp0);
+
+ LYforce_no_cache = TRUE;
+
+ return (0);
+}
+
+/*
+ * Check DIRED filename, return true on success
+ */
+static int get_filename(const char *prompt,
+ bstring **bufp)
+{
+ char *cp;
+
+ _statusline(prompt);
+
+ (void) LYgetBString(bufp, FALSE, 0, NORECALL);
+ if (strstr((*bufp)->str, "../") != NULL) {
+ HTAlert(gettext("Illegal filename; request ignored."));
+ return FALSE;
+ } else if (no_dotfiles || !show_dotfiles) {
+ cp = LYLastPathSep((*bufp)->str); /* find last slash */
+ if (cp)
+ cp += 1;
+ else
+ cp = (*bufp)->str;
+ if (*cp == '.') {
+ HTAlert(gettext("Illegal filename; request ignored."));
+ return FALSE;
+ }
+ }
+ return !isBEmpty((*bufp));
+}
+
+#ifdef OK_INSTALL
+
+#define LYEXECV_MAX_ARGC 15
+/* these are quasi-constant once they have been allocated: */
+static char **install_argp = NULL; /* args for execv install */
+static char *install_path = NULL; /* auxiliary */
+
+#ifdef LY_FIND_LEAKS
+static void clear_install_path(void)
+{
+ FREE(install_argp);
+ FREE(install_path);
+}
+#endif /* LY_FIND_LEAKS */
+
+/*
+ * Fill in args array for execv (or execvp etc.) call, after first allocating
+ * it if necessary. No fancy parsing, cmd_args is just split at spaces. Leave
+ * room for reserve additional args to be added by caller.
+ *
+ * On success *argvp points to new args vector, *pathp is auxiliary. On
+ * success returns index of next argument, else -1. This is generic enough
+ * that it could be used for other calls than install, except the atexit call.
+ * Go through this trouble for install because INSTALL_ARGS may be significant,
+ * and someone may configure it with more than one significant flags. - kw
+ */
+static int fill_argv_for_execv(char ***argvp,
+ char **pathp,
+ char *cmd_path,
+ const char *cmd_args,
+ int reserve)
+{
+ int n = 0;
+
+ char **args;
+ char *cp;
+
+ if (*argvp == NULL) {
+ *argvp = typecallocn(char *, LYEXECV_MAX_ARGC + 1);
+
+ if (!*argvp)
+ return (-1);
+#ifdef LY_FIND_LEAKS
+ atexit(clear_install_path);
+#endif
+ }
+ args = *argvp;
+ args[n++] = cmd_path;
+ if (cmd_args) {
+ StrAllocCopy(*pathp, cmd_args);
+ cp = strtok(*pathp, " ");
+ if (cp) {
+ while (cp && (n < LYEXECV_MAX_ARGC - reserve)) {
+ args[n++] = cp;
+ cp = strtok(NULL, " ");
+ }
+ if (cp && (n >= LYEXECV_MAX_ARGC - reserve)) {
+ CTRACE((tfp, "Too many args for '%s' in '%s'!\n",
+ NONNULL(cmd_path), cmd_args));
+ return (-1);
+ }
+ } else {
+ args[n++] = *pathp;
+ }
+ }
+ args[n] = (char *) 0;
+ return (n);
+}
+
+/*
+ * Install the specified file or directory.
+ */
+BOOLEAN local_install(char *destpath,
+ char *srcpath,
+ char **newpath)
+{
+ char *tmpbuf = NULL;
+ static char savepath[DIRED_MAXBUF]; /* This will be the link that
+
+ is to be installed. */
+ struct stat dir_info;
+ char **args;
+ HTList *tag;
+ char *cp = NULL;
+ char *tmpdest = NULL;
+ int count = 0;
+ int n = 0; /* indices into 'args[]' */
+ static int src = -1;
+ const char *program;
+
+ if ((program = HTGetProgramPath(ppINSTALL)) == NULL) {
+ HTAlert(gettext("Install in the selected directory not permitted."));
+ return 0;
+ }
+
+ /*
+ * Determine the status of the selected item.
+ */
+ if (srcpath) {
+ srcpath = strip_trailing_slash(srcpath);
+ if (is_url(srcpath)) {
+ char *local_src = HTfullURL_toFile(srcpath);
+
+ if (!ok_localname(savepath, local_src)) {
+ FREE(local_src);
+ return 0;
+ }
+ FREE(local_src);
+ } else if (!HTList_isEmpty(tagged) &&
+ srcpath[0] == '\0') {
+ savepath[0] = '\0'; /* will always use tagged list - kw */
+ } else if (!ok_localname(savepath, srcpath)) {
+ return 0;
+ }
+ LYforce_no_cache = TRUE;
+ LYLocalFileToURL(newpath, Home_Dir());
+ LYAddHtmlSep(newpath);
+ StrAllocCat(*newpath, INSTALLDIRS_FILE);
+ LYRegisterUIPage(*newpath, UIP_INSTALL);
+ return 0;
+ }
+
+ /* deal with ~/ or /~/ at the beginning - kw */
+ if (LYIsTilde(destpath[0]) &&
+ (LYIsPathSep(destpath[1]) || destpath[1] == '\0')) {
+ cp = &destpath[1];
+ } else if (LYIsPathSep(destpath[0]) && LYIsTilde(destpath[1]) &&
+ (LYIsPathSep(destpath[2]) || destpath[2] == '\0')) {
+ cp = &destpath[2];
+ }
+ if (cp) {
+ /* If found, allocate new string, make destpath point to it - kw */
+ StrAllocCopy(tmpdest, Home_Dir());
+ if (cp[0] && cp[1]) {
+ LYAddPathSep(&tmpdest);
+ StrAllocCat(tmpdest, cp + 1);
+ }
+ destpath = tmpdest;
+ }
+
+ destpath = strip_trailing_slash(destpath);
+
+ if (!ok_stat(destpath, &dir_info)) {
+ FREE(tmpdest);
+ return 0;
+ } else if (!S_ISDIR(dir_info.st_mode)) {
+ HTAlert(gettext("The selected item is not a directory! Request ignored."));
+ FREE(tmpdest);
+ return 0;
+ } else if (0 /*directory not writable */ ) {
+ HTAlert(gettext("Install in the selected directory not permitted."));
+ FREE(tmpdest);
+ return 0;
+ }
+
+ statusline(gettext("Just a moment, ..."));
+
+ /* fill in the fixed args, if not already done - kw */
+ if (src > 0 && install_argp) {
+ n = src;
+ n++;
+ } else {
+ n = fill_argv_for_execv(&install_argp, &install_path,
+ "install",
+#ifdef INSTALL_ARGS
+ INSTALL_ARGS,
+#else
+ NULL,
+#endif /* INSTALL_ARGS */
+ 2);
+ if (n <= 0) {
+ src = 0;
+ HTAlert(gettext("Error building install args"));
+ FREE(tmpdest);
+ return 0;
+ }
+ src = n++;
+ }
+ args = install_argp;
+
+ args[n++] = destpath;
+ args[n] = (char *) 0;
+ tag = tagged;
+
+ if (HTList_isEmpty(tagged)) {
+ /* simplistic detection of identical src and dest - kw */
+ if (!strcmp(savepath, destpath)) {
+ HTUserMsg2(gettext("Source and target are the same: %s"),
+ savepath);
+ FREE(tmpdest);
+ return (-1); /* don't do it */
+ } else if (!StrNCmp(savepath, destpath, strlen(destpath)) &&
+ LYIsPathSep(savepath[strlen(destpath)]) &&
+ LYLastPathSep(savepath + strlen(destpath) + 1) == 0) {
+ HTUserMsg2(gettext("Already in target directory: %s"),
+ savepath);
+ FREE(tmpdest);
+ return 0; /* don't do it */
+ }
+ args[src] = savepath;
+ HTSprintf0(&tmpbuf, "install %s in %s", savepath, destpath);
+ if (LYExecv(program, args, tmpbuf) <= 0) {
+ FREE(tmpbuf);
+ FREE(tmpdest);
+ return (-1);
+ }
+ count++;
+ } else {
+ char *name;
+
+ HTSprintf0(&tmpbuf, "install in %s", destpath);
+ while ((name = (char *) HTList_nextObject(tag))) {
+ int err;
+
+ args[src] = HTfullURL_toFile(name);
+
+ /* simplistic detection of identical src and dest - kw */
+ if (!strcmp(args[src], destpath)) {
+ HTUserMsg2(gettext("Source and target are the same: %s"),
+ args[src]);
+ FREE(args[src]);
+ continue; /* skip this source file */
+ } else if (!StrNCmp(args[src], destpath, strlen(destpath)) &&
+ LYIsPathSep(args[src][strlen(destpath)]) &&
+ LYLastPathSep(args[src] + strlen(destpath) + 1) == 0) {
+ HTUserMsg2(gettext("Already in target directory: %s"),
+ args[src]);
+ FREE(args[src]);
+ continue; /* skip this source file */
+ }
+ err = (LYExecv(program, args, tmpbuf) <= 0);
+ FREE(args[src]);
+ if (err) {
+ FREE(tmpbuf);
+ FREE(tmpdest);
+ return ((count == 0) ? -1 : count);
+ }
+ count++;
+ }
+ clear_tags();
+ }
+ FREE(tmpbuf);
+ FREE(tmpdest);
+ HTInfoMsg(gettext("Installation complete"));
+ return count;
+}
+#endif /* OK_INSTALL */
+
+/*
+ * Clear DIRED tags.
+ */
+void clear_tags(void)
+{
+ char *cp = NULL;
+
+ while ((cp = (char *) HTList_removeLastObject(tagged)) != NULL) {
+ FREE(cp);
+ }
+ if (HTList_isEmpty(tagged))
+ FREE(tagged);
+}
+
+/*
+ * Handle DIRED menu item.
+ */
+void add_menu_item(char *str)
+{
+ struct dired_menu *tmp, *mp;
+ char *cp;
+ BOOL used = FALSE;
+
+ /*
+ * First custom menu definition causes entire default menu to be discarded.
+ */
+ if (menu_head == defmenu)
+ menu_head = NULL;
+
+ tmp = typecalloc(struct dired_menu);
+
+ if (tmp == NULL)
+ outofmem(__FILE__, "add_menu_item");
+
+ /*
+ * Conditional on tagged != NULL ?
+ */
+ if ((cp = StrChr(str, ':')) != 0) {
+ *cp++ = '\0';
+ if (strcasecomp(str, "tag") == 0) {
+ tmp->cond = DE_TAG;
+ } else if (strcasecomp(str, "dir") == 0) {
+ tmp->cond = DE_DIR;
+ } else if (strcasecomp(str, "file") == 0) {
+ tmp->cond = DE_FILE;
+#ifdef S_IFLNK
+ } else if (strcasecomp(str, "link") == 0) {
+ tmp->cond = DE_SYMLINK;
+#endif /* S_IFLNK */
+ }
+
+ /*
+ * Conditional on matching suffix.
+ */
+ str = cp;
+ if ((cp = StrChr(str, ':')) != 0) {
+ *cp++ = '\0';
+ StrAllocCopy(tmp->sfx, str);
+
+ str = cp;
+ if ((cp = StrChr(str, ':')) != 0) {
+ *cp++ = '\0';
+ StrAllocCopy(tmp->link, str);
+
+ str = cp;
+ if ((cp = StrChr(str, ':')) != 0) {
+ *cp++ = '\0';
+ StrAllocCopy(tmp->rest, str);
+
+ StrAllocCopy(tmp->href, cp);
+
+ if (menu_head) {
+ for (mp = menu_head;
+ mp && mp->next != NULL;
+ mp = mp->next) {
+ ;
+ }
+ if (mp != NULL) {
+ mp->next = tmp;
+ used = TRUE;
+ }
+ } else {
+ menu_head = tmp;
+ used = TRUE;
+ }
+ }
+ }
+ }
+ }
+ if (!used)
+ FREE(tmp);
+}
+
+void reset_dired_menu(void)
+{
+ if (menu_head != defmenu) {
+ struct dired_menu *mp, *mp_next = NULL;
+
+ for (mp = menu_head; mp != NULL; mp = mp_next) {
+ FREE(mp->sfx);
+ FREE(mp->link);
+ FREE(mp->rest);
+ FREE(mp->href);
+ mp_next = mp->next;
+ FREE(mp);
+ }
+ menu_head = NULL;
+ }
+}
+
+/*
+ * Create URL for DIRED HREF value.
+ */
+static char *render_item(const char *s,
+ const char *path,
+ const char *dir,
+ char *buf,
+ size_t bufsize,
+ int url_syntax)
+{
+ const char *cp;
+ char *bp;
+ char overrun = '\0';
+ char *taglist = NULL;
+
+#define BP_INC (bp>buf+bufsize-2 ? &overrun : bp++)
+ /* Buffer overrun could happen for very long
+ tag list, if %l or %t are used */
+ bp = buf;
+ while (*s && !overrun) {
+ if (*s == '%') {
+ s++;
+ switch (*s) {
+ case '%':
+ *BP_INC = '%';
+ break;
+ case 'p':
+ cp = path;
+ if (!LYIsHtmlSep(*cp))
+ *BP_INC = '/';
+ while (*cp)
+ *BP_INC = *cp++;
+ break;
+ case 'd':
+ cp = dir;
+ if (!LYIsHtmlSep(*cp))
+ *BP_INC = '/';
+ while (*cp)
+ *BP_INC = *cp++;
+ break;
+ case 'f':
+ cp = LYLastPathSep(path);
+ if (cp)
+ cp++;
+ else
+ cp = path;
+ while (*cp)
+ *BP_INC = *cp++;
+ break;
+ case 'l':
+ case 't':
+ if (!HTList_isEmpty(tagged)) {
+ HTList *cur = tagged;
+ char *name;
+
+ while (!overrun &&
+ (name = (char *) HTList_nextObject(cur)) != NULL) {
+ if (*s == 'l' && (cp = strrchr(name, '/')))
+ cp++;
+ else
+ cp = name;
+ StrAllocCat(taglist, cp);
+ StrAllocCat(taglist, " "); /* should this be %20? */
+ }
+ }
+ if (taglist) {
+ /* could HTUnescape here... */
+ cp = taglist;
+ while (*cp)
+ *BP_INC = *cp++;
+ FREE(taglist);
+ }
+ break;
+ default:
+ *BP_INC = '%';
+ *BP_INC = *s;
+ break;
+ }
+ } else {
+ /*
+ * Other chars come from the lynx.cfg or the default. Let's assume
+ * there isn't anything weird there that needs escaping.
+ */
+ *BP_INC = *s;
+ }
+ s++;
+ }
+ if (overrun & url_syntax) {
+ HTAlert(gettext("Temporary URL or list would be too long."));
+ bp = buf; /* set to start, will return empty string as URL */
+ }
+ *bp = '\0';
+ return buf;
+}
+
+#endif /* DIRED_SUPPORT */
diff --git a/src/LYLocal.h b/src/LYLocal.h
new file mode 100644
index 0000000..9a7fe24
--- /dev/null
+++ b/src/LYLocal.h
@@ -0,0 +1,36 @@
+#ifndef LYLOCAL_H
+#define LYLOCAL_H
+
+#include <HTUtils.h>
+#include <LYStructs.h>
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+#ifdef DIRED_SUPPORT
+/* Special return code for LYMainLoop.c */
+#define PERMIT_FORM_RESULT (-99)
+ extern int local_create(DocInfo *doc);
+ extern int local_modify(DocInfo *doc, char **newpath);
+ extern int local_remove(DocInfo *doc);
+
+#ifdef OK_INSTALL
+ extern BOOLEAN local_install(char *destpath, char *srcpath, char **newpath);
+#endif
+
+/* MainLoop needs to know about this one for atexit cleanup */
+ extern void clear_tags(void);
+
+ extern int dired_options(DocInfo *doc, char **newfile);
+ extern int local_dired(DocInfo *doc);
+ extern void add_menu_item(char *str);
+ extern void reset_dired_menu(void);
+ extern void showtags(HTList *tag);
+ extern void tagflag(int flag, int cur);
+
+#endif /* DIRED_SUPPORT */
+
+#ifdef __cplusplus
+}
+#endif
+#endif /* LYLOCAL_H */
diff --git a/src/LYMail.c b/src/LYMail.c
new file mode 100644
index 0000000..1443f24
--- /dev/null
+++ b/src/LYMail.c
@@ -0,0 +1,1774 @@
+/*
+ * $LynxId: LYMail.c,v 1.100 2020/01/21 21:33:27 tom Exp $
+ */
+#include <HTUtils.h>
+#include <HTParse.h>
+#include <LYGlobalDefs.h>
+#include <HTAlert.h>
+#include <LYCurses.h>
+#include <LYSignal.h>
+#include <LYUtils.h>
+#include <LYClean.h>
+#include <LYStrings.h>
+#include <GridText.h>
+#include <LYMail.h>
+#include <LYEdit.h>
+#include <LYCharSets.h> /* to get current charset for mail header */
+
+#include <LYLeaks.h>
+
+#define MAX_SUBJECT 70
+
+BOOLEAN term_letter; /* Global variable for async i/o. */
+
+static void terminate_letter(int sig GCC_UNUSED)
+{
+ term_letter = TRUE;
+ /* Reassert the AST */
+ signal(SIGINT, terminate_letter);
+#if USE_VMS_MAILER || defined(PDCURSES)
+ /*
+ * Refresh the screen to get rid of the "interrupt" message.
+ */
+ if (!dump_output_immediately) {
+ lynx_force_repaint();
+ LYrefresh();
+ }
+#endif /* VMS */
+}
+
+/* HTUnEscape with control-code nuking */
+static void SafeHTUnEscape(char *string)
+{
+ int i;
+ int flg = FALSE;
+
+ HTUnEscape(string);
+ for (i = 0; string[i] != '\0'; i++) {
+ /* FIXME: this is no longer explicitly 7-bit ASCII,
+ but are there portability problems? */
+ if ((!LYIsASCII(string[i])) || !isprint(UCH(string[i]))) {
+ string[i] = '?';
+ flg = TRUE;
+ }
+ }
+ if (flg)
+ HTAlert(MAILTO_SQUASH_CTL);
+}
+
+static void remove_tildes(char *string)
+{
+ /*
+ * Change the first character to a space if it is a '~'.
+ */
+ if (*string == '~')
+ *string = ' ';
+}
+
+static void comma_append(char **dst,
+ char *src)
+{
+ if (*src) {
+ while (*src == ',' || isspace(UCH(*src)))
+ src++;
+ if (*src) {
+ if (isEmpty(*dst)) {
+ StrAllocCopy(*dst, src);
+ } else {
+ StrAllocCat(*dst, ",");
+ StrAllocCat(*dst, src);
+ }
+ }
+ }
+}
+
+static void extract_field(char **dst,
+ char *src,
+ const char *keyword)
+{
+ int len = (int) strlen(keyword);
+ char *cp, *cp1;
+
+ cp = (src + 1);
+ while (*cp != '\0') {
+ if ((*(cp - 1) == '?' || *(cp - 1) == '&') &&
+ !strncasecomp(cp, keyword, len)) {
+ cp += len;
+ if ((cp1 = StrChr(cp, '&')) != NULL) {
+ *cp1 = '\0';
+ }
+ comma_append(dst, cp);
+ if (cp1) {
+ *cp1 = '&';
+ cp = cp1;
+ cp1 = NULL;
+ } else {
+ break;
+ }
+ }
+ cp++;
+ }
+ CTRACE((tfp, "extract_field(%s) = '%s'\n", keyword, *dst));
+}
+
+/*
+ * Seek and handle a subject=foo. - FM
+ */
+static void extract_subject(char *dst,
+ char *src)
+{
+ const char *keyword = "subject=";
+ int len = (int) strlen(keyword);
+ char *cp, *cp1;
+
+ cp = (src + 1);
+ while (*cp != '\0') {
+ if ((*(cp - 1) == '?' || *(cp - 1) == '&') &&
+ !strncasecomp(cp, keyword, len))
+ break;
+ cp++;
+ }
+ if (*cp) {
+ cp += len;
+ if ((cp1 = StrChr(cp, '&')) != NULL) {
+ *cp1 = '\0';
+ }
+ if (*cp) {
+ LYStrNCpy(dst, cp, MAX_SUBJECT);
+ SafeHTUnEscape(dst);
+ }
+ if (cp1) {
+ *cp1 = '&';
+ cp1 = NULL;
+ }
+ }
+ CTRACE((tfp, "extract_subject(%s) = '%s'\n", keyword, NONNULL(dst)));
+}
+
+/*
+ * Seek and handle body=foo fields. - FM
+ */
+static void extract_body(char **dst,
+ char *src)
+{
+ const char *keyword = "body=";
+ int len = (int) strlen(keyword);
+ int i;
+ char *cp, *cp0, *cp1, *temp = 0;
+
+ cp = (src + 1);
+ while (*cp != '\0') {
+ if ((*(cp - 1) == '?' || *(cp - 1) == '&') &&
+ !strncasecomp(cp, keyword, len)) {
+ cp += len;
+ if ((cp1 = StrChr(cp, '&')) != NULL) {
+ *cp1 = '\0';
+ }
+ if (*cp) {
+ /*
+ * Break up the value into lines with a maximum length of 78.
+ * - FM
+ */
+ StrAllocCopy(temp, cp);
+ HTUnEscape(temp);
+ cp0 = temp;
+ while ((cp = StrChr(cp0, '\n')) != NULL) {
+ *cp = '\0';
+ if (cp > cp0) {
+ if (*(cp - 1) == '\r') {
+ *(cp - 1) = '\0';
+ }
+ }
+ i = 0;
+ len = (int) strlen(cp0);
+ while (len > 78) {
+ HTSprintf(dst, "%.78s\n", &cp0[i]);
+ i += 78;
+ len = (int) strlen(&cp0[i]);
+ }
+ HTSprintf(dst, "%s\n", &cp0[i]);
+ cp0 = (cp + 1);
+ }
+ i = 0;
+ len = (int) strlen(cp0);
+ while (len > 78) {
+ HTSprintf(dst, "%.78s\n", &cp0[i]);
+ i += 78;
+ len = (int) strlen(&cp0[i]);
+ }
+ if (len) {
+ HTSprintf(dst, "%s\n", &cp0[i]);
+ }
+ FREE(temp);
+ }
+ if (cp1) {
+ *cp1 = '&';
+ cp = cp1;
+ cp1 = NULL;
+ } else {
+ break;
+ }
+ }
+ cp++;
+ }
+ CTRACE((tfp, "extract_body(%s) = '%s'\n", keyword, NONNULL(*dst)));
+}
+
+/*
+ * Convert any Explorer semi-colon Internet address separators to commas - FM
+ */
+static BOOLEAN trim_comma(char *address)
+{
+ if (address[(strlen(address) - 1)] == ',')
+ address[(strlen(address) - 1)] = '\0';
+ return (BOOL) (*address == '\0');
+}
+
+/*
+ * Convert any Explorer semi-colon Internet address separators to commas - FM
+ */
+static BOOLEAN convert_explorer(char *address)
+{
+ char *cp = address;
+ char *cp0;
+ char *cp1;
+
+ while ((cp1 = StrChr(cp, '@')) != NULL) {
+ cp1++;
+ if ((cp0 = StrChr(cp1, ';')) != NULL) {
+ *cp0 = ',';
+ cp1 = cp0 + 1;
+ }
+ cp = cp1;
+ }
+ return trim_comma(address);
+}
+
+/*
+ * reply_by_mail() prompts line-by-line for header information, allowing
+ * scrolling of the screen.
+ */
+static int header_prompt(const char *label,
+ char **result,
+ unsigned limit)
+{
+ char buffer[LINESIZE];
+ int ok;
+
+ if (*result != 0) {
+ LYaddstr(CTRL_U_TO_ERASE);
+ LYStrNCpy(buffer, *result, sizeof(buffer) - 1);
+ } else
+ *buffer = 0;
+
+ if (limit > sizeof(buffer))
+ limit = sizeof(buffer);
+
+ LYaddstr(gettext(label));
+ LYaddstr(": ");
+ ok = (LYGetStr(buffer, FALSE, limit, NORECALL) >= 0
+ && !term_letter);
+ LYaddstr("\n");
+
+ if (ok) {
+ remove_tildes(buffer);
+ StrAllocCopy(*result, buffer);
+ }
+ term_letter = FALSE;
+ return ok;
+}
+
+static void show_addresses(char *addresses)
+{
+ char *cp = addresses;
+ char *cp1;
+
+ while ((cp1 = StrChr(cp, ',')) != NULL) {
+ *cp1 = '\0';
+ while (*cp == ' ')
+ cp++;
+ if (*cp) {
+ LYaddstr(cp);
+ LYaddstr(",\n ");
+ }
+ *cp1 = ',';
+ cp = (cp1 + 1);
+ }
+ if (*cp) {
+ LYaddstr(cp);
+ }
+}
+
+#if USE_BLAT_MAILER
+
+/*
+ * blat's options-file parser (see makeargv.cpp) treats backslash and double
+ * quote characters specially. lynx doesn't. Do a conversion as we write the
+ * option.
+ *
+ * Other quirks (reading blat 3.06):
+ * + Whitespace not in quotes terminates a line.
+ * + Blat allows a comment-character to terminate a line. By default, that
+ * is a semicolon.
+ *
+ * Given that, the simplest thing to do is to always quote the string, using
+ * escaping to handle special cases.
+ */
+static void blat_option(FILE *fp, const char *option, const char *value)
+{
+ if (non_empty(value)) {
+ char *result = malloc(strlen(option) + 4 + 4 * strlen(value));
+ char *working = result;
+
+ CTRACE((tfp, "blat_option(opt=\"%s\", value=\"%s\")\n", option, value));
+ sprintf(working, "%s \"", option);
+ working += strlen(working);
+
+ while (*value != '\0') {
+ unsigned ch = UCH(*value);
+
+ switch (ch) {
+ case '\\':
+ *working++ = '\\';
+ *working++ = '\\';
+ break;
+ case '"':
+ *working++ = '\\';
+ *working++ = '"';
+ break;
+ default:
+ if (ch < ' ' || ch > '~') {
+ sprintf(working, "\\%03o", ch);
+ } else {
+ *working++ = ch;
+ }
+ break;
+ }
+ ++value;
+ }
+ *working++ = '"';
+ *working++ = '\n';
+ *working = 0;
+
+ CTRACE((tfp, "->%s", result));
+ fputs(result, fp);
+ FREE(result);
+ }
+}
+
+/*
+syntax for blat 2.6.2:
+Blat <filename> -t <recipient> [optional switches (see below)]
+
+-bodyF <filename> : file with the message body
+-t <recipient> : recipient list (comma separated)
+-s <subj> : subject line
+-f <sender> : overrides the default sender address (must be known to server)
+-i <addr> : a 'From:' address, not necessarily known to the SMTP server.
+-c <recipient> : carbon copy recipient list (comma separated)
+-b <recipient> : blind carbon copy recipient list (comma separated)
+-help : displays the help message.
+-mime : MIME Quoted-Printable Content-Transfer-Encoding.
+-q : suppresses *all* output.
+-server <addr> : overrides the default SMTP server to be used.
+
+*/
+
+static char *blat_cmd(char *filename,
+ char *address,
+ char *subject,
+ char *ccaddr,
+ char *mail_addr)
+{
+ char *b_cmd = NULL;
+
+ if (mail_is_altblat) {
+ const char *format = "%s %s -t %s -s %s %s%s%s";
+
+ HTAddParam(&b_cmd, format, 1, ALTBLAT_MAIL);
+ HTAddParam(&b_cmd, format, 2, filename);
+ HTAddParam(&b_cmd, format, 3, address);
+ HTAddParam(&b_cmd, format, 4, subject);
+ HTAddToCmd(&b_cmd, format, 5, ALTBLAT_MAIL_FLAGS);
+ if (non_empty(ccaddr)) {
+ HTAddToCmd(&b_cmd, format, 6, " -c ");
+ HTAddParam(&b_cmd, format, 7, NonNull(ccaddr));
+ }
+ HTEndParam(&b_cmd, format, 8);
+
+ } else {
+
+ const char *format = "%s -of %s";
+ char bl_cmd_file[LY_MAXPATH];
+ FILE *fp;
+
+#ifdef __CYGWIN__
+ char dosname[LY_MAXPATH];
+
+#else
+ char *dosname;
+#endif
+
+ bl_cmd_file[0] = '\0';
+ if ((fp = LYOpenTemp(bl_cmd_file, ".blt", "w")) == NULL) {
+ HTAlert(FORM_MAILTO_FAILED);
+ return NULL;
+ }
+
+ HTAddParam(&b_cmd, format, 1, BLAT_MAIL);
+
+ ConvertToWin32Path(filename, dosname);
+ blat_option(fp, "-bodyF", dosname);
+ blat_option(fp, "-t", address);
+ blat_option(fp, "-s", subject);
+ blat_option(fp, "-f", mail_addr);
+ blat_option(fp, "-c", ccaddr);
+ LYCloseOutput(fp);
+
+ ConvertToWin32Path(bl_cmd_file, dosname);
+
+ HTAddParam(&b_cmd, format, 2, dosname);
+ HTEndParam(&b_cmd, format, 3);
+
+ }
+
+ return b_cmd;
+}
+
+#endif /* USE_BLAT_MAILER */
+
+#if USE_VMS_MAILER
+BOOLEAN LYMailPMDF(void)
+{
+ return (system_mail != 0)
+ ? !strncasecomp(system_mail, "PMDF SEND", 9)
+ : FALSE;
+}
+
+/*
+ * Add all of the people in the address field to the command
+ */
+static void vms_append_addrs(char **cmd, char *address, char *option)
+{
+ BOOLEAN first = TRUE;
+ char *cp;
+ char *address_ptr1;
+ char *address_ptr2;
+
+ address_ptr1 = address;
+ do {
+ if ((cp = StrChr(address_ptr1, ',')) != NULL) {
+ address_ptr2 = (cp + 1);
+ *cp = '\0';
+ } else {
+ address_ptr2 = NULL;
+ }
+
+ /*
+ * 4 letters is arbitrarily the smallest possible mail address, at
+ * least for lynx. That way extra spaces won't confuse the mailer and
+ * give a blank address.
+ */
+ if (strlen(address_ptr1) > 3) {
+ if (!first) {
+ StrAllocCat(*cmd, ",");
+ }
+ HTSprintf(cmd, mail_adrs, address_ptr1);
+ if (*option && LYMailPMDF())
+ StrAllocCat(*cmd, option);
+ first = FALSE;
+ }
+ address_ptr1 = address_ptr2;
+ } while (address_ptr1 != NULL);
+}
+
+static void remove_quotes(char *string)
+{
+ while (*string != 0) {
+ if (StrChr("\"&|", *string) != 0)
+ *string = ' ';
+ string++;
+ }
+}
+#else
+#if CAN_PIPE_TO_MAILER
+
+/*
+ * Open a pipe to the mailer
+ */
+FILE *LYPipeToMailer(void)
+{
+ char *buffer = NULL;
+ FILE *fp = NULL;
+
+ if (LYSystemMail()) {
+ HTSprintf0(&buffer, "%s %s", system_mail, system_mail_flags);
+ fp = popen(buffer, "w");
+ CTRACE((tfp, "popen(%s) %s\n", buffer, fp != 0 ? "OK" : "FAIL"));
+ FREE(buffer);
+ }
+ return fp;
+}
+#else /* DOS, Win32, etc. */
+
+int LYSendMailFile(char *the_address,
+ char *the_filename,
+ char *the_subject GCC_UNUSED,
+ char *the_ccaddr GCC_UNUSED,
+ char *message)
+{
+ char *cmd = NULL;
+ int code;
+
+ if (!LYSystemMail())
+ return 0;
+
+#if USE_BLAT_MAILER
+ if (mail_is_blat) {
+ cmd = blat_cmd(the_filename,
+ the_address,
+ the_subject,
+ the_ccaddr,
+ personal_mail_address);
+ } else
+#endif
+#ifdef __DJGPP__
+ if (LYGetEnv("SHELL")) {
+ extern char *shell;
+ const char *c_option;
+ const char *format = "%s %s %s -t %s -F %s";
+
+ if (dj_is_bash) {
+ c_option = "-c";
+ } else {
+ c_option = "/c";
+ }
+ HTAddParam(&cmd, format, 1, shell);
+ HTAddParam(&cmd, format, 2, c_option);
+ HTAddParam(&cmd, format, 3, system_mail);
+ HTAddParam(&cmd, format, 4, the_address);
+ HTAddParam(&cmd, format, 5, the_filename);
+ HTEndParam(&cmd, format, 6);
+ } else
+#endif /* __DJGPP__ */
+ {
+ const char *format = "%s -t %s -F %s";
+
+ HTAddParam(&cmd, format, 1, system_mail);
+ HTAddParam(&cmd, format, 2, the_address);
+ HTAddParam(&cmd, format, 3, the_filename);
+ HTEndParam(&cmd, format, 4);
+ }
+
+ stop_curses();
+ SetOutputMode(O_TEXT);
+ printf("%s\n\n$ %s\n\n%s",
+ *message ? message : gettext("Sending"),
+ cmd, PLEASE_WAIT);
+ code = LYSystem(cmd);
+ LYSleepMsg();
+ start_curses();
+ SetOutputMode(O_BINARY);
+
+ FREE(cmd);
+
+ return code;
+}
+#endif /* CAN_PIPE_TO_FILE */
+#endif /* USE_VMS_MAILER */
+
+/*
+ * mailform() sends form content to the mailto address(es). - FM
+ */
+void mailform(const char *mailto_address,
+ const char *mailto_subject,
+ const char *mailto_content,
+ const char *mailto_type)
+{
+ FILE *fd;
+ char *address = NULL;
+ char *ccaddr = NULL;
+ char *keywords = NULL;
+ char *cp = NULL;
+ char self[MAX_SUBJECT + 10];
+ char subject[MAX_SUBJECT + 10];
+ char *searchpart = NULL;
+ char buf[512];
+ int len, i;
+
+#if USE_VMS_MAILER
+ static char *cmd;
+ char *command = NULL;
+ BOOLEAN isPMDF = LYMailPMDF();
+ char hdrfile[LY_MAXPATH];
+#endif
+#if !CAN_PIPE_TO_MAILER
+ char my_tmpfile[LY_MAXPATH];
+#endif
+
+ CTRACE((tfp, "mailto_address: \"%s\"\n", NONNULL(mailto_address)));
+ CTRACE((tfp, "mailto_subject: \"%s\"\n", NONNULL(mailto_subject)));
+ CTRACE((tfp, "mailto_content: \"%s\"\n", NONNULL(mailto_content)));
+ CTRACE((tfp, "mailto_type: \"%s\"\n", NONNULL(mailto_type)));
+
+ if (!LYSystemMail())
+ return;
+
+ if (!mailto_address || !mailto_content) {
+ HTAlert(BAD_FORM_MAILTO);
+ return;
+ }
+ subject[0] = '\0';
+ self[0] = '\0';
+
+ if ((cp = StrChr(mailto_address, '\n')) != NULL)
+ *cp = '\0';
+ StrAllocCopy(address, mailto_address);
+
+ /*
+ * Check for a ?searchpart. - FM
+ */
+ if ((cp = StrChr(address, '?')) != NULL) {
+ StrAllocCopy(searchpart, cp);
+ *cp = '\0';
+ cp = (searchpart + 1);
+ if (*cp != '\0') {
+ /*
+ * Seek and handle a subject=foo. - FM
+ */
+ extract_subject(subject, searchpart);
+
+ /*
+ * Seek and handle to=address(es) fields. Appends to address. -
+ * FM
+ */
+ extract_field(&address, searchpart, "to=");
+
+ /*
+ * Seek and handle cc=address(es) fields. Excludes Bcc=address(es)
+ * as unsafe. We may append our own cc (below) as a list for the
+ * actual mailing. - FM
+ */
+ extract_field(&ccaddr, searchpart, "cc=");
+
+ /*
+ * Seek and handle keywords=term(s) fields. - FM
+ */
+ extract_field(&keywords, searchpart, "keywords=");
+
+ if (keywords != NULL) {
+ if (*keywords != '\0') {
+ SafeHTUnEscape(keywords);
+ } else {
+ FREE(keywords);
+ }
+ }
+
+ FREE(searchpart);
+ }
+ }
+
+ if (convert_explorer(address)) {
+ HTAlert(BAD_FORM_MAILTO);
+ goto cleanup;
+ }
+ if (ccaddr != NULL) {
+ if (convert_explorer(ccaddr)) {
+ FREE(ccaddr);
+ }
+ }
+
+ /*
+ * Unescape the address and ccaddr fields. - FM
+ */
+ SafeHTUnEscape(address);
+ if (ccaddr != NULL) {
+ SafeHTUnEscape(ccaddr);
+ }
+
+ /*
+ * Allow user to edit the default Subject - FM
+ */
+ if (subject[0] == '\0') {
+ if (non_empty(mailto_subject)) {
+ LYStrNCpy(subject, mailto_subject, MAX_SUBJECT);
+ } else {
+ sprintf(subject, "mailto:%.63s", address);
+ }
+ }
+ _statusline(SUBJECT_PROMPT);
+ if (LYGetStr(subject, FALSE, MAX_SUBJECT, NORECALL) < 0) {
+ /*
+ * User cancelled via ^G. - FM
+ */
+ HTInfoMsg(FORM_MAILTO_CANCELLED);
+ goto cleanup;
+ }
+
+ /*
+ * Allow user to specify a self copy via a CC: entry, if permitted. - FM
+ */
+ if (!LYNoCc) {
+ sprintf(self, "%.*s", MAX_SUBJECT,
+ isEmpty(personal_mail_address) ? "" : personal_mail_address);
+ _statusline("Cc: ");
+ if (LYGetStr(self, FALSE, MAX_SUBJECT, NORECALL) < 0) {
+ /*
+ * User cancelled via ^G. - FM
+ */
+ HTInfoMsg(FORM_MAILTO_CANCELLED);
+ goto cleanup;
+ }
+ remove_tildes(self);
+ if (ccaddr == NULL) {
+ StrAllocCopy(ccaddr, self);
+ } else {
+ StrAllocCat(ccaddr, ",");
+ StrAllocCat(ccaddr, self);
+ }
+ }
+#if CAN_PIPE_TO_MAILER
+ if ((fd = LYPipeToMailer()) == 0) {
+ HTAlert(FORM_MAILTO_FAILED);
+ goto cleanup;
+ }
+
+ if (non_empty(mailto_type)) {
+ fprintf(fd, "Mime-Version: 1.0\n");
+ fprintf(fd, "Content-Type: %s\n", mailto_type);
+ }
+ fprintf(fd, "To: %s\n", address);
+ if (non_empty(personal_mail_address))
+ fprintf(fd, "From: %s\n", personal_mail_address);
+ if (non_empty(ccaddr))
+ fprintf(fd, "Cc: %s\n", ccaddr);
+ fprintf(fd, "Subject: %s\n\n", subject);
+ if (non_empty(keywords))
+ fprintf(fd, "Keywords: %s\n", keywords);
+ _statusline(SENDING_FORM_CONTENT);
+#else /* e.g., VMS, DOS */
+ if ((fd = LYOpenTemp(my_tmpfile, ".txt", "w")) == NULL) {
+ HTAlert(FORM_MAILTO_FAILED);
+ goto cleanup;
+ }
+#if USE_VMS_MAILER
+ if (isPMDF) {
+ FILE *hfd;
+
+ if ((hfd = LYOpenTemp(hdrfile, ".txt", "w")) == NULL) {
+ HTAlert(FORM_MAILTO_FAILED);
+ LYCloseTempFP(fd);
+ goto cleanup;
+ }
+ if (non_empty(mailto_type)) {
+ fprintf(hfd, "Mime-Version: 1.0\n");
+ fprintf(hfd, "Content-Type: %s\n", mailto_type);
+ if (non_empty(personal_mail_address))
+ fprintf(hfd, "From: %s\n", personal_mail_address);
+ }
+ /*
+ * For PMDF, put any keywords and the subject in the header file and
+ * close it. - FM
+ */
+ if (non_empty(keywords)) {
+ fprintf(hfd, "Keywords: %s\n", keywords);
+ }
+ fprintf(hfd, "Subject: %s\n\n", subject);
+ LYCloseTempFP(hfd);
+ } else if (mailto_type &&
+ !strncasecomp(mailto_type, "multipart/form-data", 19)) {
+ /*
+ * Ugh! There's no good way to include headers while we're still using
+ * "generic" VMS MAIL, so we'll put this in the body of the message. -
+ * FM
+ */
+ fprintf(fd, "X-Content-Type: %s\n\n", mailto_type);
+ }
+#else /* !VMS (DOS) */
+#if USE_BLAT_MAILER
+ if (mail_is_blat) {
+ if (strlen(subject) > MAX_SUBJECT)
+ subject[MAX_SUBJECT] = '\0';
+ } else
+#endif
+ {
+ if (non_empty(mailto_type)) {
+ fprintf(fd, "Mime-Version: 1.0\n");
+ fprintf(fd, "Content-Type: %s\n", mailto_type);
+ }
+ fprintf(fd, "To: %s\n", address);
+ if (non_empty(personal_mail_address))
+ fprintf(fd, "From: %s\n", personal_mail_address);
+ fprintf(fd, "Subject: %.70s\n\n", subject);
+ }
+#endif /* VMS */
+#endif /* CAN_PIPE_TO_MAILER */
+
+ /*
+ * Break up the content into lines with a maximum length of 78. If the
+ * ENCTYPE was text/plain, we have physical newlines and should take them
+ * into account. Otherwise, the actual newline characters in the content
+ * are hex escaped. - FM
+ */
+ while ((cp = StrChr(mailto_content, '\n')) != NULL) {
+ *cp = '\0';
+ i = 0;
+ len = (int) strlen(mailto_content);
+ while (len > 78) {
+ LYStrNCpy(buf, &mailto_content[i], 78);
+ fprintf(fd, "%s\n", buf);
+ i += 78;
+ len = (int) strlen(&mailto_content[i]);
+ }
+ fprintf(fd, "%s\n", &mailto_content[i]);
+ mailto_content = (cp + 1);
+ }
+ i = 0;
+ len = (int) strlen(mailto_content);
+ while (len > 78) {
+ LYStrNCpy(buf, &mailto_content[i], 78);
+ fprintf(fd, "%s\n", buf);
+ i += 78;
+ len = (int) strlen(&mailto_content[i]);
+ }
+ if (len)
+ fprintf(fd, "%s\n", &mailto_content[i]);
+
+#if CAN_PIPE_TO_MAILER
+ pclose(fd);
+ LYSleepMsg();
+#else
+ LYCloseTempFP(fd);
+#if USE_VMS_MAILER
+ /*
+ * Set the mail command. - FM
+ */
+ if (isPMDF) {
+ /*
+ * Now set up the command. - FM
+ */
+ HTSprintf0(&cmd,
+ "%s %s %s,%s ",
+ system_mail,
+ system_mail_flags,
+ hdrfile,
+ my_tmpfile);
+ } else {
+ /*
+ * For "generic" VMS MAIL, include the subject in the command, and
+ * ignore any keywords to minimize risk of them making the line too
+ * long or having problem characters. - FM
+ */
+ HTSprintf0(&cmd,
+ "%s %s%s/subject=\"%s\" %s ",
+ system_mail,
+ system_mail_flags,
+ (strncasecomp(system_mail, "MAIL", 4) ? "" : "/noself"),
+ subject,
+ my_tmpfile);
+ }
+ StrAllocCopy(command, cmd);
+
+ vms_append_addrs(&command, address, "");
+ if (non_empty(ccaddr)) {
+ vms_append_addrs(&command, ccaddr, "/CC");
+ }
+
+ stop_curses();
+ printf("%s\n\n$ %s\n\n%s", SENDING_FORM_CONTENT, command, PLEASE_WAIT);
+ LYSystem(command); /* Mail (VMS) */
+ FREE(command);
+ LYSleepAlert();
+ start_curses();
+ (void) LYRemoveTemp(my_tmpfile);
+ if (isPMDF)
+ (void) LYRemoveTemp(hdrfile);
+#else /* DOS */
+ LYSendMailFile(address,
+ my_tmpfile,
+ subject,
+ ccaddr,
+ SENDING_FORM_CONTENT);
+ (void) LYRemoveTemp(my_tmpfile);
+#endif /* USE_VMS_MAILER */
+#endif /* CAN_PIPE_TO_MAILER */
+
+ cleanup:
+ FREE(address);
+ FREE(ccaddr);
+ FREE(keywords);
+ return;
+}
+
+/*
+ * mailmsg() sends a message to the owner of the file, if one is defined,
+ * telling of errors (i.e., link not available).
+ */
+void mailmsg(int cur,
+ char *owner_address,
+ char *filename,
+ char *linkname)
+{
+ FILE *fd, *fp;
+ char *address = NULL;
+ char *searchpart = NULL;
+ char *cmd = NULL, *cp;
+
+#ifdef ALERTMAIL
+ BOOLEAN skip_parsing = FALSE;
+#endif
+#if !CAN_PIPE_TO_MAILER
+ char *ccaddr;
+ char subject[128];
+ char my_tmpfile[LY_MAXPATH];
+#endif
+#if USE_VMS_MAILER
+ BOOLEAN isPMDF = LYMailPMDF();
+ char hdrfile[LY_MAXPATH];
+ char *command = NULL;
+
+ CTRACE((tfp, "mailmsg(%d, \"%s\", \"%s\", \"%s\")\n", cur,
+ NONNULL(owner_address),
+ NONNULL(filename),
+ NONNULL(linkname)));
+
+#endif /* VMS */
+
+ if (!LYSystemMail())
+ return;
+
+#ifdef ALERTMAIL
+ if (owner_address == NULL) {
+ owner_address = ALERTMAIL;
+ skip_parsing = TRUE;
+ }
+#endif
+
+ if (isEmpty(owner_address))
+ return;
+ if ((cp = StrChr(owner_address, '\n')) != NULL) {
+#ifdef ALERTMAIL
+ if (skip_parsing)
+ return; /* invalidly defined - ignore - kw */
+#else
+ *cp = '\0';
+#endif
+ }
+ if (!strncasecomp(owner_address, "lynx-dev@", 9)) {
+ /*
+ * Silently refuse sending bad link messages to lynx-dev.
+ */
+ return;
+ }
+ StrAllocCopy(address, owner_address);
+
+#ifdef ALERTMAIL
+ /*
+ * If we are using a fixed address given by ALERTMAIL, it is supposed to
+ * already be in usable form, without URL-isms like ?-searchpart and
+ * URL-escaping. So skip some code. - kw
+ */
+ if (!skip_parsing)
+#endif
+ {
+ /*
+ * Check for a ?searchpart. - FM
+ */
+ if ((cp = StrChr(address, '?')) != NULL) {
+ StrAllocCopy(searchpart, cp);
+ *cp = '\0';
+ cp = (searchpart + 1);
+ if (*cp != '\0') {
+ /*
+ * Seek and handle to=address(es) fields.
+ * Appends to address. We ignore any other
+ * headers in the ?searchpart. - FM
+ */
+ extract_field(&address, searchpart, "to=");
+ }
+ }
+
+ (void) convert_explorer(address);
+
+ /*
+ * Unescape the address field. - FM
+ */
+ SafeHTUnEscape(address);
+ }
+
+ if (trim_comma(address)) {
+ FREE(address);
+ CTRACE((tfp, "mailmsg: No address in '%s'.\n", owner_address));
+ return;
+ }
+#if CAN_PIPE_TO_MAILER
+ if ((fd = LYPipeToMailer()) == 0) {
+ FREE(address);
+ CTRACE((tfp, "mailmsg: '%s' failed.\n", cmd));
+ return;
+ }
+
+ fprintf(fd, "To: %s\n", address);
+ fprintf(fd, "Subject: Lynx Error in %s\n", filename);
+ if (non_empty(personal_mail_address)) {
+ fprintf(fd, "Cc: %s\n", personal_mail_address);
+ }
+ fprintf(fd, "X-URL: %s\n", filename);
+ fprintf(fd, "X-Mailer: %s, Version %s\n\n", LYNX_NAME, LYNX_VERSION);
+#else
+ if ((fd = LYOpenTemp(my_tmpfile, ".txt", "w")) == NULL) {
+ CTRACE((tfp, "mailmsg: Could not fopen '%s'.\n", my_tmpfile));
+ FREE(address);
+ return;
+ }
+ sprintf(subject, "Lynx Error in %.56s", filename);
+ ccaddr = personal_mail_address;
+#if USE_VMS_MAILER
+ if (isPMDF) {
+ FILE *hfd;
+
+ if ((hfd = LYOpenTemp(hdrfile, ".txt", "w")) == NULL) {
+ CTRACE((tfp, "mailmsg: Could not fopen '%s'.\n", hdrfile));
+ FREE(address);
+ return;
+ }
+
+ if (non_empty(personal_mail_address)) {
+ fprintf(fd, "Cc: %s\n", personal_mail_address);
+ }
+ fprintf(fd, "X-URL: %s\n", filename);
+ fprintf(fd, "X-Mailer: %s, Version %s\n\n", LYNX_NAME, LYNX_VERSION);
+ /*
+ * For PMDF, put the subject in the header file and close it. - FM
+ */
+ fprintf(hfd, "Subject: Lynx Error in %.56s\n\n", filename);
+ LYCloseTempFP(hfd);
+ }
+#endif /* USE_VMS_MAILER */
+#endif /* CAN_PIPE_TO_MAILER */
+
+ fprintf(fd, gettext("The link %s :?: %s \n"),
+ links[cur].lname, links[cur].target);
+ fprintf(fd, gettext("called \"%s\"\n"), LYGetHiliteStr(cur, 0));
+ fprintf(fd, gettext("in the file \"%s\" called \"%s\"\n"), filename, linkname);
+ fprintf(fd, "%s\n\n", gettext("was requested but was not available."));
+ fprintf(fd, "%s\n\n", gettext("Thought you might want to know."));
+
+ fprintf(fd, "%s\n", gettext("This message was automatically generated by"));
+ fprintf(fd, "%s %s", LYNX_NAME, LYNX_VERSION);
+ if ((LynxSigFile != NULL) &&
+ (fp = fopen(LynxSigFile, TXT_R)) != NULL) {
+ fputs("-- \n", fd);
+ while (LYSafeGets(&cmd, fp) != NULL)
+ fputs(cmd, fd);
+ LYCloseInput(fp);
+ }
+#if CAN_PIPE_TO_MAILER
+ pclose(fd);
+#else
+ LYCloseTempFP(fd);
+#if USE_VMS_MAILER
+ if (isPMDF) {
+ /*
+ * Now set up the command. - FM
+ */
+ HTSprintf0(&command,
+ "%s %s %s,%s ",
+ system_mail,
+ system_mail_flags,
+ hdrfile,
+ my_tmpfile);
+ } else {
+ /*
+ * For "generic" VMS MAIL, include the subject in the command. - FM
+ */
+ HTSprintf0(&command,
+ "%s %s/self/subject=\"Lynx Error in %.56s\" %s ",
+ system_mail,
+ system_mail_flags,
+ filename,
+ my_tmpfile);
+ }
+ vms_append_addrs(&command, address, "");
+
+ LYSystem(command); /* VMS */
+ FREE(command);
+ FREE(cmd);
+ (void) LYRemoveTemp(my_tmpfile);
+ if (isPMDF) {
+ (void) LYRemoveTemp(hdrfile);
+ }
+#else /* DOS */
+ LYSendMailFile(address,
+ my_tmpfile,
+ subject,
+ ccaddr,
+ "");
+ (void) LYRemoveTemp(my_tmpfile);
+#endif /* USE_VMS_MAILER */
+#endif /* CAN_PIPE_TO_MAILER */
+
+ 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);
+ }
+ }
+
+ fprintf(ofp, "%s\t%s \tin %s\n",
+ links[cur].lname, links[cur].target, filename);
+ LYCloseOutput(ofp);
+ }
+
+ FREE(address);
+ return;
+}
+
+/*
+ * reply_by_mail() invokes sendmail on Unix or mail on VMS to send
+ * a comment from the users to the owner
+ */
+void reply_by_mail(char *mail_address,
+ char *filename,
+ const char *title,
+ const char *refid)
+{
+ char user_input[LINESIZE];
+ FILE *fd, *fp;
+ const char *label = NULL;
+ char *from_address = NULL;
+ char *cc_address = NULL;
+ char *to_address = NULL;
+ char *the_subject = NULL;
+ char *ccaddr = NULL;
+ char *keywords = NULL;
+ char *searchpart = NULL;
+ char *body = NULL;
+ char *cp = NULL, *cp1 = NULL;
+ int i;
+ int c = 0; /* user input */
+ char my_tmpfile[LY_MAXPATH];
+ char default_subject[MAX_SUBJECT + 10];
+
+#if USE_VMS_MAILER
+ char *command = NULL;
+ BOOLEAN isPMDF = LYMailPMDF();
+ char hdrfile[LY_MAXPATH];
+ FILE *hfd = 0;
+
+#else
+#if !CAN_PIPE_TO_MAILER
+ char tmpfile2[LY_MAXPATH];
+#endif
+ char buf[4096]; /* 512 */
+ char *header = NULL;
+ size_t nbytes;
+#endif /* USE_VMS_MAILER */
+
+ CTRACE((tfp, "reply_by_mail(\"%s\", \"%s\", \"%s\", \"%s\")\n",
+ NONNULL(mail_address),
+ NONNULL(filename),
+ NONNULL(title),
+ NONNULL(refid)));
+
+ term_letter = FALSE;
+
+ if (!LYSystemMail())
+ return;
+
+ if (isEmpty(mail_address)) {
+ HTAlert(NO_ADDRESS_IN_MAILTO_URL);
+ return;
+ }
+ StrAllocCopy(to_address, mail_address);
+
+ if ((fd = LYOpenTemp(my_tmpfile, ".txt", "w")) == NULL) {
+ HTAlert(MAILTO_URL_TEMPOPEN_FAILED);
+ return;
+ }
+#if USE_VMS_MAILER
+ if (isPMDF) {
+ if ((hfd = LYOpenTemp(hdrfile, ".txt", "w")) == NULL) {
+ HTAlert(MAILTO_URL_TEMPOPEN_FAILED);
+ return;
+ }
+ }
+#endif /* VMS */
+ default_subject[0] = '\0';
+
+ /*
+ * Check for a ?searchpart. - FM
+ */
+ if ((cp = StrChr(to_address, '?')) != NULL) {
+ StrAllocCopy(searchpart, cp);
+ *cp = '\0';
+ cp = (searchpart + 1);
+ if (*cp != '\0') {
+ /*
+ * Seek and handle a subject=foo. - FM
+ */
+ extract_subject(default_subject, searchpart);
+
+ /*
+ * Seek and handle to=address(es) fields. Appends to address. -
+ * FM
+ */
+ extract_field(&to_address, searchpart, "to=");
+
+ /*
+ * Seek and handle cc=address(es) fields. Excludes Bcc=address(es)
+ * as unsafe. We may append our own cc (below) as a list for the
+ * actual mailing. - FM
+ */
+ extract_field(&ccaddr, searchpart, "cc=");
+
+ /*
+ * Seek and handle keywords=term(s) fields. - FM
+ */
+ extract_field(&keywords, searchpart, "keywords=");
+
+ if (keywords != NULL) {
+ if (*keywords != '\0') {
+ SafeHTUnEscape(keywords);
+ } else {
+ FREE(keywords);
+ }
+ }
+
+ /*
+ * Seek and handle body=foo fields. - FM
+ */
+ extract_body(&body, searchpart);
+
+ FREE(searchpart);
+ }
+ }
+
+ if (convert_explorer(to_address)) {
+ HTAlert(NO_ADDRESS_IN_MAILTO_URL);
+ goto cancelled;
+ }
+ if (ccaddr != NULL) {
+ if (convert_explorer(ccaddr)) {
+ FREE(ccaddr);
+ }
+ }
+
+ /*
+ * Unescape the address and ccaddr fields. - FM
+ */
+ SafeHTUnEscape(to_address);
+ if (ccaddr != NULL) {
+ SafeHTUnEscape(ccaddr);
+ }
+
+ /*
+ * Set the default subject. - FM
+ */
+ if ((default_subject[0] == '\0') && non_empty(title)) {
+ LYStrNCpy(default_subject, title, MAX_SUBJECT);
+ }
+
+ /*
+ * Use ^G to cancel mailing of comment and don't let SIGINTs exit lynx.
+ */
+ signal(SIGINT, terminate_letter);
+
+#if USE_VMS_MAILER
+ if (isPMDF || !body) {
+ /*
+ * Put the X-URL and X-Mailer lines in the hdrfile for PMDF or
+ * my_tmpfile for VMS MAIL. - FM
+ */
+ fprintf((isPMDF ? hfd : fd),
+ "X-URL: %s%s\n",
+ isEmpty(filename) ? STR_MAILTO_URL : filename,
+ isEmpty(filename) ? to_address : "");
+ fprintf((isPMDF ? hfd : fd),
+ "X-Mailer: %s, Version %s\n", LYNX_NAME, LYNX_VERSION);
+#ifdef NO_ANONYMOUS_EMAIL
+ if (!isPMDF) {
+ fprintf(fd, "\n");
+ }
+#endif /* NO_ANONYMOUS_EMAIL */
+ }
+#else /* Unix/DOS/Windows */
+ /*
+ * Put the To: line in the header.
+ */
+#ifndef DOSPATH
+ HTSprintf(&header, "To: %s\n", to_address);
+#endif
+
+ /*
+ * Put the Mime-Version, Content-Type and Content-Transfer-Encoding in the
+ * header. This assumes that the same character set is used for composing
+ * the mail which is currently selected as display character set... Don't
+ * send a charset if we have a CJK character set selected, since it may not
+ * be appropriate for mail... Also don't use an unofficial "x-" charset.
+ * Also if the charset would be "us-ascii" (7-bit replacements selected,
+ * don't send any MIME headers. - kw
+ */
+ if (strncasecomp(LYCharSet_UC[current_char_set].MIMEname,
+ "us-ascii", 8) != 0) {
+ StrAllocCat(header, "Mime-Version: 1.0\n");
+ if (!LYHaveCJKCharacterSet &&
+ strncasecomp(LYCharSet_UC[current_char_set].MIMEname, "x-", 2)
+ != 0) {
+ HTSprintf(&header, "Content-Type: " STR_PLAINTEXT "; charset=%s\n",
+ LYCharSet_UC[current_char_set].MIMEname);
+ }
+ StrAllocCat(header, "Content-Transfer-Encoding: 8bit\n");
+ }
+ /*
+ * Put the X-URL and X-Mailer lines in the header.
+ */
+ if (non_empty(filename)) {
+ HTSprintf(&header, "X-URL: %s\n", filename);
+ } else {
+ HTSprintf(&header, "X-URL: mailto:%s\n", to_address);
+ }
+ HTSprintf(&header, "X-Mailer: %s, Version %s\n", LYNX_NAME, LYNX_VERSION);
+
+ if (non_empty(refid)) {
+ HTSprintf(&header, "In-Reply-To: <%s>\n", refid);
+ }
+#endif /* VMS */
+
+ /*
+ * Clear the screen and inform the user.
+ */
+ LYclear();
+ LYmove(2, 0);
+ scrollok(LYwin, TRUE); /* Enable scrolling. */
+ if (body)
+ LYaddstr(SENDING_MESSAGE_WITH_BODY_TO);
+ else
+ LYaddstr(SENDING_COMMENT_TO);
+ show_addresses(to_address);
+ if (
+#if USE_VMS_MAILER
+ (isPMDF == TRUE) &&
+#endif /* VMS */
+ (cp = ccaddr) != NULL) {
+ if (StrChr(cp, ',') != NULL) {
+ LYaddstr(WITH_COPIES_TO);
+ } else {
+ LYaddstr(WITH_COPY_TO);
+ }
+ show_addresses(ccaddr);
+ }
+ LYaddstr(CTRL_G_TO_CANCEL_SEND);
+
+#if USE_VMS_MAILER
+ if (isPMDF || !body) {
+#endif /* USE_VMS_MAILER */
+#ifndef NO_ANONYMOUS_EMAIL
+ /*
+ * Get the user's personal name.
+ */
+ LYaddstr(ENTER_NAME_OR_BLANK);
+#if USE_VMS_MAILER
+ if (isPMDF) {
+ label = "Personal_name";
+ } else {
+ label = "X-Personal_name";
+ }
+#else
+ label = "X-Personal_Name";
+#endif /* USE_VMS_MAILER */
+ if (!header_prompt(label, &personal_mail_name, LINESIZE)) {
+ goto cancelled;
+ }
+ if (*personal_mail_name) {
+#if USE_VMS_MAILER
+ fprintf((isPMDF ? hfd : fd), "%s: %s\n", label, personal_mail_name);
+#else
+ HTSprintf(&header, "%s: %s\n", label, personal_mail_name);
+#endif /* VMS */
+ }
+
+ /*
+ * Get the user's return address.
+ */
+ LYaddstr(ENTER_MAIL_ADDRESS_OR_OTHER);
+ LYaddstr(MEANS_TO_CONTACT_FOR_RESPONSE);
+#if USE_VMS_MAILER
+ if (isPMDF) {
+ label = "From";
+ } else {
+ label = "X-From";
+ }
+#else
+ label = "From";
+#endif /* VMS */
+ /* Add the personal mail address if there is one. */
+ if (non_empty(personal_mail_address))
+ StrAllocCopy(from_address, personal_mail_address);
+ if (!header_prompt(label, &from_address, LINESIZE)) {
+ goto cancelled;
+ }
+#if USE_VMS_MAILER
+ if (*from_address) {
+ fprintf(isPMDF ? hfd : fd, "%s: %s\n", label, from_address);
+ }
+ if (!isPMDF) {
+ fprintf(fd, "\n");
+ }
+#else
+ HTSprintf(&header, "%s: %s\n", label, from_address);
+#endif /* USE_VMS_MAILER */
+#endif /* !NO_ANONYMOUS_EMAIL */
+#if USE_VMS_MAILER
+ }
+#endif /* USE_VMS_MAILER */
+
+ /*
+ * Get the subject line.
+ */
+ LYaddstr(ENTER_SUBJECT_LINE);
+ label = "Subject";
+ if (*default_subject) {
+ StrAllocCopy(the_subject, default_subject);
+ } else if (non_empty(filename)) {
+ HTSprintf(&the_subject, "%s", filename);
+ } else {
+ HTSprintf(&the_subject, "mailto:%s", to_address);
+ }
+ if (!header_prompt(label, &the_subject, MAX_SUBJECT)) {
+ goto cancelled;
+ }
+
+ /*
+ * Offer a CC line, if permitted. - FM
+ */
+ if (!LYNoCc) {
+ LYaddstr(ENTER_ADDRESS_FOR_CC);
+ LYaddstr(BLANK_FOR_NO_COPY);
+ if (non_empty(personal_mail_address))
+ StrAllocCopy(cc_address, personal_mail_address);
+ if (!header_prompt("Cc", &cc_address, LINESIZE)) {
+ goto cancelled;
+ }
+ comma_append(&ccaddr, cc_address);
+ }
+#if !USE_VMS_MAILER
+ HTSprintf(&header, "%s: %s\n", label, the_subject);
+#if !CAN_PIPE_TO_MAILER
+ if (*to_address) {
+ HTSprintf(&header, "To: %s\n", to_address);
+ }
+#endif
+
+ /*
+ * Add the Cc: header. - FM
+ */
+ if (non_empty(ccaddr)) {
+ HTSprintf(&header, "Cc: %s\n", ccaddr);
+ }
+
+ /*
+ * Add the Keywords: header. - FM
+ */
+ if (non_empty(keywords)) {
+ HTSprintf(&header, "Keywords: %s\n", keywords);
+ }
+
+ /*
+ * Terminate the header.
+ */
+ StrAllocCat(header, "\n");
+ CTRACE((tfp, "**header==\n%s", header));
+#endif /* !VMS */
+
+ if (!no_editor && non_empty(editor)) {
+
+ if (body) {
+ cp1 = body;
+ while ((cp = StrChr(cp1, '\n')) != NULL) {
+ *cp++ = '\0';
+ fprintf(fd, "%s\n", cp1);
+ cp1 = cp;
+ }
+ } else if (strcmp(HTLoadedDocumentURL(), "")) {
+ /*
+ * Ask if the user wants to include the original message.
+ */
+ BOOLEAN is_preparsed = (BOOL) (LYPreparsedSource &&
+ HTisDocumentSource());
+
+ if (HTConfirm(is_preparsed
+ ? INC_PREPARSED_MSG_PROMPT
+ : INC_ORIG_MSG_PROMPT) == YES) {
+ print_wwwfile_to_fd(fd, TRUE, (BOOL) !is_preparsed);
+ }
+ }
+ LYCloseTempFP(fd); /* Close the tmpfile. */
+ scrollok(LYwin, FALSE); /* Stop scrolling. */
+
+ if (term_letter || LYCharIsINTERRUPT(c))
+ goto cleanup;
+
+ /*
+ * Spawn the users editor on the mail file
+ */
+ edit_temporary_file(my_tmpfile, "", SPAWNING_EDITOR_FOR_MAIL);
+
+ } else if (body) {
+ /*
+ * Let user review the body. - FM
+ */
+ LYclear();
+ LYmove(0, 0);
+ LYaddstr(REVIEW_MESSAGE_BODY);
+ LYrefresh();
+ cp1 = body;
+ i = (LYlines - 5);
+ while ((cp = StrChr(cp1, '\n')) != NULL) {
+ if (i <= 0) {
+ LYaddstr(RETURN_TO_CONTINUE);
+ LYrefresh();
+ c = LYgetch();
+ LYaddstr("\n");
+ if (term_letter || LYCharIsINTERRUPT(c)) {
+ goto cancelled;
+ }
+ i = (LYlines - 2);
+ }
+ *cp++ = '\0';
+ fprintf(fd, "%s\n", cp1);
+ LYaddstr(cp1);
+ LYaddstr("\n");
+ cp1 = cp;
+ i--;
+ }
+ while (i >= 0) {
+ LYaddstr("\n");
+ i--;
+ }
+ LYrefresh();
+ LYCloseTempFP(fd); /* Close the tmpfile. */
+ scrollok(LYwin, FALSE); /* Stop scrolling. */
+
+ } else {
+ /*
+ * Use the internal line editor for the message.
+ */
+ LYaddstr(ENTER_MESSAGE_BELOW);
+ LYaddstr(ENTER_PERIOD_WHEN_DONE_A);
+ LYaddstr(ENTER_PERIOD_WHEN_DONE_B);
+ LYaddstr(CTRL_G_TO_CANCEL_SEND);
+ LYaddstr("\n\n");
+ LYrefresh();
+ *user_input = '\0';
+ if (LYGetStr(user_input, FALSE, sizeof(user_input), NORECALL) < 0 ||
+ term_letter || STREQ(user_input, ".")) {
+ goto cancelled;
+ }
+
+ while (!STREQ(user_input, ".") && !term_letter) {
+ LYaddstr("\n");
+ remove_tildes(user_input);
+ fprintf(fd, "%s\n", user_input);
+ *user_input = '\0';
+ if (LYGetStr(user_input, FALSE,
+ sizeof(user_input), NORECALL) < 0) {
+ goto cancelled;
+ }
+ }
+
+ fprintf(fd, "\n"); /* Terminate the message. */
+ LYCloseTempFP(fd); /* Close the tmpfile. */
+ scrollok(LYwin, FALSE); /* Stop scrolling. */
+ }
+
+#if !USE_VMS_MAILER
+ /*
+ * Ignore CTRL-C on this last question.
+ */
+ signal(SIGINT, SIG_IGN);
+#endif /* !VMS */
+ LYStatusLine = (LYlines - 1);
+ c = HTConfirm(body ? SEND_MESSAGE_PROMPT : SEND_COMMENT_PROMPT);
+ LYStatusLine = -1;
+ if (c != YES) {
+ LYclear(); /* clear the screen */
+ goto cleanup;
+ }
+ if ((body == NULL && non_empty(LynxSigFile)) &&
+ (fp = fopen(LynxSigFile, TXT_R)) != NULL) {
+ LYStatusLine = (LYlines - 1);
+ if (term_letter) {
+ _user_message(APPEND_SIG_FILE, LynxSigFile);
+ c = 0;
+ } else {
+ char *msg = NULL;
+
+ HTSprintf0(&msg, APPEND_SIG_FILE, LynxSigFile);
+ c = HTConfirm(msg);
+ FREE(msg);
+ }
+ LYStatusLine = -1;
+ if (c == YES) {
+ if ((fd = fopen(my_tmpfile, TXT_A)) != NULL) {
+ char *buffer = NULL;
+
+ fputs("-- \n", fd);
+ while (LYSafeGets(&buffer, fp) != NULL) {
+ fputs(buffer, fd);
+ }
+ LYCloseOutput(fd);
+ FREE(buffer);
+ }
+ }
+ LYCloseInput(fp);
+ }
+ LYclear(); /* Clear the screen. */
+
+ /*
+ * Send the message.
+ */
+#if USE_VMS_MAILER
+ /*
+ * Set the mail command. - FM
+ */
+ if (isPMDF) {
+ /*
+ * For PMDF, put any keywords and the subject in the header file and
+ * close it. - FM
+ */
+ if (non_empty(keywords)) {
+ fprintf(hfd, "Keywords: %s\n", keywords);
+ }
+ fprintf(hfd, "Subject: %s\n\n", the_subject);
+ LYCloseTempFP(hfd);
+ /*
+ * Now set up the command. - FM
+ */
+ HTSprintf0(&command, "%s %s %s,%s ",
+ system_mail,
+ system_mail_flags,
+ hdrfile,
+ my_tmpfile);
+ } else {
+ /*
+ * For "generic" VMS MAIL, include the subject in the command, and
+ * ignore any keywords to minimize risk of them making the line too
+ * long or having problem characters. - FM
+ */
+ HTSprintf0(&command, "%s %s%s/subject=\"%s\" %s ",
+ system_mail,
+ system_mail_flags,
+ (strncasecomp(system_mail, "MAIL", 4) ? "" : "/noself"),
+ the_subject,
+ my_tmpfile);
+ }
+
+ vms_append_addrs(&command, to_address, "");
+ if (non_empty(ccaddr)) {
+ vms_append_addrs(&command, ccaddr, "/CC");
+ }
+
+ stop_curses();
+ printf("%s\n\n$ %s\n\n%s", SENDING_COMMENT, command, PLEASE_WAIT);
+ LYSystem(command); /* SENDING COMMENT (VMS) */
+ FREE(command);
+ LYSleepAlert();
+ start_curses();
+#else /* Unix/DOS/Windows */
+ /*
+ * Send the tmpfile into sendmail.
+ */
+ _statusline(SENDING_YOUR_MSG);
+#if CAN_PIPE_TO_MAILER
+ signal(SIGINT, SIG_IGN);
+ if ((fp = LYPipeToMailer()) == 0) {
+ HTInfoMsg(CANCELLED);
+ }
+#else
+ if ((fp = LYOpenTemp(tmpfile2, ".txt", "w")) == NULL) {
+ HTAlert(MAILTO_URL_TEMPOPEN_FAILED);
+ }
+#endif /* CAN_PIPE_TO_MAILER */
+ if (fp != 0) {
+ fd = fopen(my_tmpfile, TXT_R);
+ if (fd == NULL) {
+ HTInfoMsg(CANCELLED);
+#if CAN_PIPE_TO_MAILER
+ pclose(fp);
+#else
+ LYCloseTempFP(fp);
+#endif /* CAN_PIPE_TO_MAILER */
+ } else {
+#if USE_BLAT_MAILER
+ if (!mail_is_blat)
+ fputs(header, fp);
+#else
+ fputs(header, fp);
+#endif
+ while ((nbytes = fread(buf, (size_t) 1, sizeof(buf), fd)) != 0) {
+ if (fwrite(buf, (size_t) 1, (size_t) nbytes, fp) < nbytes)
+ break;
+ }
+#if CAN_PIPE_TO_MAILER
+ pclose(fp);
+#else
+ LYCloseTempFP(fp); /* Close the tmpfile. */
+ LYSendMailFile(to_address,
+ tmpfile2,
+ the_subject,
+ ccaddr,
+ SENDING_COMMENT);
+ (void) LYRemoveTemp(tmpfile2); /* Delete the tmpfile. */
+#endif /* CAN_PIPE_TO_MAILER */
+ LYCloseInput(fd); /* Close the tmpfile. */
+ }
+ }
+#endif /* USE_VMS_MAILER */
+ goto cleanup;
+
+ /*
+ * Come here to cleanup and exit.
+ */
+ cancelled:
+ HTInfoMsg(CANCELLED);
+ LYCloseTempFP(fd); /* Close the tmpfile. */
+ scrollok(LYwin, FALSE); /* Stop scrolling. */
+ cleanup:
+ signal(SIGINT, cleanup_sig);
+ term_letter = FALSE;
+
+#if USE_VMS_MAILER
+ while (LYRemoveTemp(my_tmpfile) == 0) ; /* Delete the tmpfile(s). */
+ if (isPMDF) {
+ (void) LYRemoveTemp(hdrfile); /* Delete the hdrfile. */
+ }
+#else
+ FREE(header);
+ (void) LYRemoveTemp(my_tmpfile); /* Delete the tmpfile. */
+#endif /* VMS */
+
+ FREE(from_address);
+ FREE(the_subject);
+ FREE(cc_address);
+ FREE(to_address);
+ FREE(ccaddr);
+ FREE(keywords);
+ FREE(body);
+ return;
+}
+
+/*
+ * Check that we have configured values for system mailer.
+ */
+BOOLEAN LYSystemMail(void)
+{
+ if (isEmpty(system_mail) || !strcmp(system_mail, "unknown")) {
+ HTAlert(gettext("No system mailer configured"));
+ return FALSE;
+ }
+ return TRUE;
+}
diff --git a/src/LYMail.h b/src/LYMail.h
new file mode 100644
index 0000000..f75f686
--- /dev/null
+++ b/src/LYMail.h
@@ -0,0 +1,88 @@
+/*
+ * $LynxId: LYMail.h,v 1.17 2011/06/02 10:37:23 tom Exp $
+ */
+#ifndef LYMAIL_H
+#define LYMAIL_H
+
+#ifndef LYSTRUCTS_H
+#include <LYStructs.h>
+#endif /* LYSTRUCTS_H */
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+#ifdef SH_EX
+#undef USE_BLAT_MAILER
+#define USE_BLAT_MAILER 1
+#endif
+
+#ifndef USE_ALT_BLAT_MAILER
+#define USE_ALT_BLAT_MAILER 0
+#endif
+
+#ifndef USE_BLAT_MAILER
+#define USE_BLAT_MAILER 0
+#endif
+
+#ifndef ALTBLAT_MAIL_FLAGS
+#define ALTBLAT_MAIL_FLAGS ""
+#endif
+
+#ifndef BLAT_MAIL_FLAGS
+#define BLAT_MAIL_FLAGS ""
+#endif
+
+#ifdef VMS
+#define USE_VMS_MAILER 1
+#else
+#define USE_VMS_MAILER 0
+#endif
+
+#ifndef SYSTEM_MAIL
+#define SYSTEM_MAIL "sendmail"
+#endif
+
+#ifndef SYSTEM_MAIL_FLAGS
+#define SYSTEM_MAIL_FLAGS ""
+#endif
+
+/*
+ * Ifdef's in case we have a working popen/pclose, useful for piping to the
+ * mail program.
+ */
+#ifndef CAN_PIPE_TO_MAILER
+#if !defined(HAVE_POPEN) || USE_VMS_MAILER || defined(DOSPATH) || defined(__CYGWIN__)
+#define CAN_PIPE_TO_MAILER 0
+#else
+#define CAN_PIPE_TO_MAILER 1
+#endif
+#endif
+
+ extern BOOLEAN term_letter;
+
+ extern BOOLEAN LYSystemMail(void);
+ extern BOOLEAN LYMailPMDF(void);
+ extern FILE *LYPipeToMailer(void);
+ extern int LYSendMailFile(char *the_address,
+ char *the_filename,
+ char *the_subject,
+ char *the_ccaddr,
+ char *message);
+ extern void mailform(const char *mailto_address,
+ const char *mailto_subject,
+ const char *mailto_content,
+ const char *mailto_type);
+ extern void mailmsg(int cur,
+ char *owner_address,
+ char *filename,
+ char *linkname);
+ extern void reply_by_mail(char *mail_address,
+ char *filename,
+ const char *title,
+ const char *refid);
+
+#ifdef __cplusplus
+}
+#endif
+#endif /* LYMAIL_H */
diff --git a/src/LYMain.c b/src/LYMain.c
new file mode 100644
index 0000000..ab48cae
--- /dev/null
+++ b/src/LYMain.c
@@ -0,0 +1,4567 @@
+/*
+ * $LynxId: LYMain.c,v 1.300 2024/01/07 15:31:25 tom Exp $
+ */
+#include <HTUtils.h>
+#include <HTTP.h>
+#include <HTParse.h>
+#include <HTAccess.h>
+#include <HTList.h>
+#include <HTFile.h>
+#include <UCMap.h>
+#include <UCDefs.h>
+#include <HTInit.h>
+#include <HTAlert.h>
+#include <LYCurses.h>
+#include <LYStyle.h>
+#include <HTML.h>
+#include <LYUtils.h>
+#include <LYGlobalDefs.h>
+#include <LYMail.h>
+#include <LYOptions.h>
+#include <LYSignal.h>
+#include <LYGetFile.h>
+#include <LYStrings.h>
+#include <LYClean.h>
+#include <LYCharSets.h>
+#include <LYCharUtils.h>
+#include <LYReadCFG.h>
+#include <LYrcFile.h>
+#include <LYKeymap.h>
+#include <HTForms.h>
+#include <LYList.h>
+#include <LYJump.h>
+
+#ifdef USE_SESSIONS
+#include <LYSession.h>
+#endif
+
+#include <LYMainLoop.h>
+#include <LYBookmark.h>
+#include <LYCookie.h>
+#include <LYPrettySrc.h>
+#include <LYShowInfo.h>
+#include <LYHistory.h>
+
+#ifdef VMS
+#include <HTFTP.h>
+#endif /* !DECNET */
+
+#ifdef __DJGPP__
+#include <dos.h>
+#include <dpmi.h>
+#include <io.h>
+#include <sys/stat.h>
+#include <sys/exceptn.h>
+#endif /* __DJGPP__ */
+
+#ifdef __EMX__
+#include <io.h>
+#endif
+
+#if defined(LOCALE) && (!defined(HAVE_LIBINTL_H) || !defined(LC_ALL))
+#undef gettext /* Solaris locale.h prototypes gettext() */
+#include <locale.h>
+#ifndef HAVE_GETTEXT
+#define gettext(s) s
+#endif
+#endif /* LOCALE */
+
+#include <LYexit.h>
+#include <LYLeaks.h>
+
+#if defined(HAVE_PWD_H) && defined(HAVE_GETPWUID) && !defined(DONT_HAVE_PW_GECOS)
+#define USE_GETPWUID 1
+#include <pwd.h>
+#else
+#define USE_GETPWUID 0
+#endif
+
+/* ahhhhhhhhhh!! Global variables :-< */
+#ifdef SOCKS
+BOOLEAN socks_flag = TRUE;
+#endif /* SOCKS */
+
+#ifdef IGNORE_CTRL_C
+BOOLEAN sigint = FALSE;
+#endif /* IGNORE_CTRL_C */
+
+#ifdef __DJGPP__
+static char init_ctrl_break[1];
+#endif /* __DJGPP__ */
+
+#if USE_VMS_MAILER
+char *mail_adrs = NULL; /* the mask for a VMS mail transport */
+#endif
+
+#ifdef VMS
+ /* create FIXED 512 binaries */
+BOOLEAN UseFixedRecords = USE_FIXED_RECORDS;
+#endif /* VMS */
+
+#ifndef VMS
+static char *lynx_version_putenv_command = NULL;
+char *list_format = NULL; /* LONG_LIST formatting mask */
+#endif /* !VMS */
+
+char *ftp_format = NULL; /* LONG_LIST formatting mask */
+
+#ifdef SYSLOG_REQUESTED_URLS
+char *syslog_txt = NULL; /* syslog arb text for session */
+BOOLEAN syslog_requested_urls = FALSE;
+#endif
+
+int cfg_bad_html = BAD_HTML_WARN;
+
+#ifdef DIRED_SUPPORT
+BOOLEAN lynx_edit_mode = FALSE;
+BOOLEAN no_dired_support = FALSE;
+HTList *tagged = NULL;
+int LYAutoUncacheDirLists = 2; /* default dired uncaching behavior */
+int dir_list_order = ORDER_BY_NAME;
+int dir_list_style = MIXED_STYLE;
+
+#ifdef OK_OVERRIDE
+BOOLEAN prev_lynx_edit_mode = FALSE;
+#endif /* OK_OVERRIDE */
+
+#ifdef OK_PERMIT
+#ifdef NO_CHANGE_EXECUTE_PERMS
+BOOLEAN no_change_exec_perms = TRUE;
+
+#else
+BOOLEAN no_change_exec_perms = FALSE;
+#endif /* NO_CHANGE_EXECUTE_PERMS */
+#endif /* OK_PERMIT */
+
+#endif /* DIRED_SUPPORT */
+
+ /* Number of docs cached in memory */
+int HTCacheSize = DEFAULT_CACHE_SIZE;
+
+#if defined(VMS) && defined(VAXC) && !defined(__DECC)
+ /* Don't dump doc cache unless this size is exceeded */
+int HTVirtualMemorySize = DEFAULT_VIRTUAL_MEMORY_SIZE;
+#endif /* VMS && VAXC && !_DECC */
+
+#if defined(EXEC_LINKS) || defined(EXEC_SCRIPTS)
+#ifndef NEVER_ALLOW_REMOTE_EXEC
+BOOLEAN local_exec = LOCAL_EXECUTION_LINKS_ALWAYS_ON;
+
+#else
+BOOLEAN local_exec = FALSE;
+#endif /* NEVER_ALLOW_REMOTE_EXEC */
+BOOLEAN local_exec_on_local_files =
+LOCAL_EXECUTION_LINKS_ON_BUT_NOT_REMOTE;
+#endif /* EXEC_LINKS || EXEC_SCRIPTS */
+
+#if defined(LYNXCGI_LINKS) && !defined(VMS) /* WebSter Mods -jkt */
+char *LYCgiDocumentRoot = NULL; /* DOCUMENT_ROOT in the lynxcgi env */
+#endif /* LYNXCGI_LINKS */
+
+#ifdef TRACK_INTERNAL_LINKS
+BOOLEAN track_internal_links = TRUE;
+
+#else
+BOOLEAN track_internal_links = FALSE;
+#endif
+
+BOOLEAN enable_scrollback = FALSE;
+
+char empty_string[] =
+{'\0'};
+
+int display_lines; /* number of lines in display */
+int www_search_result = -1;
+
+ /* linked list of printers */
+lynx_list_item_type *printers = NULL;
+
+ /* linked list of download options */
+lynx_list_item_type *downloaders = NULL;
+
+ /* linked list of upload options */
+#ifdef USE_EXTERNALS
+lynx_list_item_type *externals = NULL;
+
+ /* linked list of external options */
+#endif
+#ifdef USE_IDN2
+int LYidnaMode = LYidnaTR46;
+#endif
+
+lynx_list_item_type *uploaders = NULL;
+int LYShowColor = SHOW_COLOR_UNKNOWN; /* to show or not */
+int LYrcShowColor = SHOW_COLOR_UNKNOWN; /* ... last used */
+
+#if !defined(NO_OPTION_FORMS) && !defined(NO_OPTION_MENU)
+BOOLEAN LYUseFormsOptions = TRUE; /* use forms-based options menu */
+#endif
+
+BOOLEAN LYGuessScheme = FALSE;
+BOOLEAN LYJumpFileURL = FALSE; /* always FALSE the first time */
+BOOLEAN LYPermitURL = FALSE;
+BOOLEAN LYRestricted = FALSE; /* whether we have -anonymous option */
+BOOLEAN LYShowCursor = SHOW_CURSOR; /* to show or not to show */
+BOOLEAN LYUnderlineLinks = UNDERLINE_LINKS; /* Show the links underlined vs bold */
+BOOLEAN LYUseDefShoCur = TRUE; /* Command line -show_cursor toggle */
+BOOLEAN LYUserSpecifiedURL = TRUE; /* always TRUE the first time */
+BOOLEAN LYValidate = FALSE;
+BOOLEAN LYforce_no_cache = FALSE;
+BOOLEAN LYinternal_flag = FALSE; /* override no-cache b/c internal link */
+BOOLEAN LYoverride_no_cache = FALSE; /*override no-cache b/c history etc */
+BOOLEAN LYresubmit_posts = ALWAYS_RESUBMIT_POSTS;
+BOOLEAN LYtrimBlankLines = TRUE;
+BOOLEAN LYtrimInputFields = FALSE;
+BOOLEAN LYxhtml_parsing = FALSE;
+BOOLEAN bold_H1 = FALSE;
+BOOLEAN bold_headers = FALSE;
+BOOLEAN bold_name_anchors = FALSE;
+BOOLEAN LYcase_sensitive = CASE_SENSITIVE_ALWAYS_ON;
+BOOLEAN check_mail = CHECKMAIL;
+BOOLEAN child_lynx = FALSE;
+BOOLEAN dump_links_decoded = TRUE;
+BOOLEAN dump_links_inline = FALSE;
+BOOLEAN dump_links_only = FALSE;
+BOOLEAN dump_output_immediately = FALSE;
+BOOLEAN dump_to_stderr = FALSE;
+BOOLEAN emacs_keys = EMACS_KEYS_ALWAYS_ON;
+BOOLEAN error_logging = MAIL_SYSTEM_ERROR_LOGGING;
+BOOLEAN goto_buffer = GOTOBUFFER; /* TRUE if offering default goto URL */
+BOOLEAN historical_comments = FALSE;
+BOOLEAN html5_charsets = FALSE;
+BOOLEAN is_www_index = FALSE;
+BOOLEAN jump_buffer = JUMPBUFFER; /* TRUE if offering default shortcut */
+BOOLEAN lynx_mode = NORMAL_LYNX_MODE;
+BOOLEAN minimal_comments = FALSE;
+BOOLEAN number_fields_on_left = TRUE;
+BOOLEAN number_links_on_left = TRUE;
+BOOLEAN recent_sizechange = FALSE; /* the window size changed recently? */
+BOOLEAN size_is_changed = FALSE; /* SIGWINCH is caught recently? */
+BOOLEAN soft_dquotes = FALSE;
+BOOLEAN unique_urls = FALSE;
+BOOLEAN use_underscore = SUBSTITUTE_UNDERSCORES;
+BOOLEAN verbose_img = VERBOSE_IMAGES; /* show filenames or not */
+BOOLEAN vi_keys = VI_KEYS_ALWAYS_ON;
+int keypad_mode = DEFAULT_KEYPAD_MODE;
+int user_mode = NOVICE_MODE;
+
+BOOLEAN telnet_ok = TRUE;
+
+#ifndef DISABLE_NEWS
+BOOLEAN news_ok = TRUE;
+#endif
+BOOLEAN rlogin_ok = TRUE;
+BOOLEAN long_url_ok = FALSE;
+BOOLEAN ftp_ok = TRUE;
+BOOLEAN system_editor = FALSE;
+
+BOOLEAN had_restrictions_default = FALSE;
+BOOLEAN had_restrictions_all = FALSE;
+
+BOOLEAN exec_frozen = FALSE;
+BOOLEAN no_bookmark = FALSE;
+BOOLEAN no_bookmark_exec = FALSE;
+BOOLEAN no_chdir = FALSE;
+BOOLEAN no_disk_save = FALSE;
+BOOLEAN no_dotfiles = NO_DOT_FILES;
+BOOLEAN no_download = FALSE;
+BOOLEAN no_editor = FALSE;
+BOOLEAN no_exec = FALSE;
+BOOLEAN no_file_url = FALSE;
+BOOLEAN no_goto = FALSE;
+BOOLEAN no_goto_configinfo = FALSE;
+BOOLEAN no_goto_cso = FALSE;
+BOOLEAN no_goto_file = FALSE;
+BOOLEAN no_goto_finger = FALSE;
+BOOLEAN no_goto_ftp = FALSE;
+BOOLEAN no_goto_gopher = FALSE;
+BOOLEAN no_goto_http = FALSE;
+BOOLEAN no_goto_https = FALSE;
+BOOLEAN no_goto_lynxcgi = FALSE;
+BOOLEAN no_goto_lynxexec = FALSE;
+BOOLEAN no_goto_lynxprog = FALSE;
+BOOLEAN no_goto_mailto = FALSE;
+BOOLEAN no_goto_rlogin = FALSE;
+BOOLEAN no_goto_telnet = FALSE;
+BOOLEAN no_goto_tn3270 = FALSE;
+BOOLEAN no_goto_wais = FALSE;
+BOOLEAN no_inside_ftp = FALSE;
+BOOLEAN no_inside_rlogin = FALSE;
+BOOLEAN no_inside_telnet = FALSE;
+BOOLEAN no_jump = FALSE;
+BOOLEAN no_lynxcfg_info = FALSE;
+BOOLEAN no_lynxcgi = FALSE;
+BOOLEAN no_mail = FALSE;
+BOOLEAN no_multibook = FALSE;
+BOOLEAN no_option_save = FALSE;
+BOOLEAN no_outside_ftp = FALSE;
+BOOLEAN no_outside_rlogin = FALSE;
+BOOLEAN no_outside_telnet = FALSE;
+BOOLEAN no_print = FALSE;
+BOOLEAN no_shell = FALSE;
+BOOLEAN no_suspend = FALSE;
+BOOLEAN no_telnet_port = FALSE;
+BOOLEAN no_useragent = FALSE;
+
+#ifndef DISABLE_FTP
+BOOLEAN ftp_passive = FTP_PASSIVE; /* TRUE if doing ftp in passive mode */
+BOOLEAN ftp_local_passive;
+HTList *broken_ftp_epsv = NULL;
+HTList *broken_ftp_retr = NULL;
+char *ftp_lasthost = NULL;
+#endif
+
+#ifndef DISABLE_NEWS
+BOOLEAN no_goto_news = FALSE;
+BOOLEAN no_goto_nntp = FALSE;
+BOOLEAN no_goto_snews = FALSE;
+BOOLEAN no_inside_news = FALSE;
+BOOLEAN no_newspost = FALSE;
+BOOLEAN no_outside_news = FALSE;
+#endif
+
+#ifdef USE_EXTERNALS
+BOOLEAN no_externals = FALSE;
+#endif
+
+#ifndef NO_CONFIG_INFO
+BOOLEAN no_lynxcfg_xinfo = FALSE;
+
+#ifdef HAVE_CONFIG_H
+BOOLEAN no_compileopts_info = FALSE;
+#endif
+#endif
+
+BOOLEAN no_statusline = FALSE;
+BOOLEAN no_filereferer = TRUE;
+char LYRefererWithQuery = 'D'; /* 'D' for drop */
+BOOLEAN local_host_only = FALSE;
+BOOLEAN override_no_download = FALSE;
+BOOLEAN show_dotfiles = FALSE; /* From rcfile if no_dotfiles is false */
+BOOLEAN LYforce_HTML_mode = FALSE;
+BOOLEAN LYfind_leaks = TRUE;
+
+#ifdef __DJGPP__
+BOOLEAN watt_debug = FALSE; /* WATT-32 debugging */
+BOOLEAN dj_is_bash = FALSE; /* Check for bash shell under DJGPP */
+#endif /* __DJGPP__ */
+
+#ifdef WIN_EX
+BOOLEAN focus_window = FALSE; /* 1998/10/05 (Mon) 17:18:42 */
+char windows_drive[4]; /* 1998/01/13 (Tue) 21:13:24 */
+#endif
+
+#ifdef _WINDOWS
+#define TIMEOUT 180 /* 1998/03/30 (Mon) 14:50:44 */
+int lynx_timeout = TIMEOUT;
+CRITICAL_SECTION critSec_READ; /* 1998/09/03 (Thu) 22:01:56 */
+#endif /* _WINDOWS */
+
+#if defined(WIN_EX)
+BOOLEAN system_is_NT = FALSE;
+#endif
+
+BOOLEAN show_cfg = FALSE;
+
+BOOLEAN no_table_center = FALSE; /* 1998/10/09 (Fri) 15:12:49 */
+
+#if USE_BLAT_MAILER
+BOOLEAN mail_is_blat = TRUE;
+BOOLEAN mail_is_altblat = USE_ALT_BLAT_MAILER;
+
+#if USE_ALT_BLAT_MAILER
+#define THIS_BLAT_MAIL ALTBLAT_MAIL
+#define THAT_BLAT_MAIL BLAT_MAIL
+#else
+#define THIS_BLAT_MAIL BLAT_MAIL
+#define THAT_BLAT_MAIL ALTBLAT_MAIL
+#endif
+#endif
+
+#ifdef USE_BLINK
+# ifdef __EMX__
+BOOLEAN term_blink_is_boldbg = TRUE;
+
+# else
+BOOLEAN term_blink_is_boldbg = FALSE;
+
+# endif
+#endif
+
+BOOLEAN HEAD_request = FALSE;
+BOOLEAN LYAcceptAllCookies = ACCEPT_ALL_COOKIES; /* take all cookies? */
+BOOLEAN LYCancelledFetch = FALSE; /* TRUE if cancelled binary fetch */
+BOOLEAN LYCollapseBRs = COLLAPSE_BR_TAGS; /* Collapse serial BRs? */
+BOOLEAN LYDefaultRawMode;
+BOOLEAN LYListNewsDates = LIST_NEWS_DATES;
+BOOLEAN LYListNewsNumbers = LIST_NEWS_NUMBERS;
+BOOLEAN LYMBMBlocked = BLOCK_MULTI_BOOKMARKS;
+BOOLEAN LYNewsPosting = NEWS_POSTING; /* News posting supported? */
+BOOLEAN LYNoFromHeader = TRUE; /* Never send From header? */
+BOOLEAN LYNoRefererForThis = FALSE; /* No Referer header for this URL? */
+BOOLEAN LYNoRefererHeader = FALSE; /* Never send Referer header? */
+BOOLEAN LYRawMode;
+BOOLEAN LYSelectPopups = USE_SELECT_POPUPS;
+BOOLEAN LYSendUserAgent = SEND_USERAGENT; /* send Lynx User-Agent header? */
+BOOLEAN LYSetCookies = SET_COOKIES; /* Process Set-Cookie headers? */
+BOOLEAN LYUseDefSelPop = TRUE; /* Command line -popup toggle */
+BOOLEAN LYUseDefaultRawMode = TRUE;
+BOOLEAN LYUseMouse = FALSE;
+BOOLEAN LYisConfiguredForX = FALSE;
+BOOLEAN UCForce8bitTOUPPER = FALSE; /* override locale for case-conversion? */
+BOOLEAN UCSaveBookmarksInUnicode = FALSE;
+BOOLEAN bookmark_start = FALSE;
+BOOLEAN check_realm = FALSE; /* Restrict to the starting realm? */
+BOOLEAN clickable_images = MAKE_LINKS_FOR_ALL_IMAGES;
+BOOLEAN crawl = FALSE; /* Do crawl? */
+BOOLEAN keep_mime_headers = FALSE; /* Include mime headers with source dump */
+BOOLEAN more_text = FALSE; /* is there more text to display? */
+BOOLEAN more_links = FALSE; /* Links beyond a displayed page with no links? */
+BOOLEAN no_list = FALSE;
+BOOLEAN no_margins = FALSE;
+BOOLEAN no_pause = FALSE;
+BOOLEAN no_title = FALSE;
+BOOLEAN update_term_title = FALSE;
+BOOLEAN no_url_redirection = FALSE; /* Don't follow URL redirections */
+BOOLEAN pseudo_inline_alts = MAKE_PSEUDO_ALTS_FOR_INLINES;
+BOOLEAN scan_for_buried_news_references = TRUE;
+BOOLEAN startfile_ok = FALSE;
+static BOOLEAN startfile_stdin = FALSE;
+BOOLEAN traversal = FALSE; /* Do traversals? */
+
+char *BookmarkPage = NULL; /* the name of the current bookmark page */
+char *LYCookieAcceptDomains = NULL; /* domains to accept all cookies */
+char *LYCookieLooseCheckDomains = NULL; /* check loosely */
+char *LYCookieQueryCheckDomains = NULL; /* check w/a query */
+char *LYCookieRejectDomains = NULL; /* domains to reject all cookies */
+char *LYCookieSAcceptDomains = NULL; /* domains to accept all cookies */
+char *LYCookieSLooseCheckDomains = NULL; /* check loosely */
+char *LYCookieSQueryCheckDomains = NULL; /* check w/a query */
+char *LYCookieSRejectDomains = NULL; /* domains to reject all cookies */
+char *LYCookieSStrictCheckDomains = NULL; /* check strictly */
+char *LYCookieStrictCheckDomains = NULL; /* check strictly */
+char *LYHostName = NULL; /* treat as a local host name */
+char *LYLocalDomain = NULL; /* treat as a local domain tail */
+char *LYUserAgent = NULL; /* Lynx User-Agent header */
+char *LYUserAgentDefault = NULL; /* Lynx default User-Agent header */
+char *LynxHome = NULL; /* the default Home HREF. */
+char *LynxSigFile = NULL; /* Signature file, in or off home */
+char *UCAssume_MIMEcharset = NULL;
+char *URLDomainPrefixes = NULL;
+char *URLDomainSuffixes = NULL;
+char *anonftp_password = NULL; /* anonymous ftp password (default: email) */
+char *authentication_info[2] =
+{NULL, NULL}; /* Id:Password for protected documents */
+char *bookmark_page = NULL; /* the name of the default bookmark page */
+char *editor = NULL; /* the name of the current editor */
+char *form_get_data = NULL; /* User data for get form */
+char *form_post_data = NULL; /* User data for post form */
+char *global_extension_map = NULL; /* global mime.types */
+char *global_type_map = NULL; /* global mailcap */
+char *helpfile = NULL; /* the main help file */
+char *helpfilepath = NULL; /* the path to the help file set */
+char *homepage = NULL; /* home page or main screen */
+char *http_error_file = NULL; /* Place HTTP status code in this file */
+char *indexfile = NULL; /* an index file if there is one */
+char *jumpfile = NULL; /* the name of the default jumps file */
+char *jumpprompt = NULL; /* the default jumps prompt */
+char *language = NULL; /* preferred language */
+char *lynx_cfg_file = NULL; /* location of active lynx.cfg */
+char *lynx_cmd_logfile; /* file to write keystroke commands, if any */
+char *lynx_cmd_script; /* file to read keystroke commands, if any */
+char *lynx_save_space = NULL; /* The prefix for save to disk paths */
+char *lynx_temp_space = NULL; /* The prefix for temporary file paths */
+char *lynxjumpfile = NULL; /* the current jump file URL */
+char *lynxlinksfile = NULL; /* the current visited links file URL */
+char *lynxlistfile = NULL; /* the current list file URL */
+char *original_dir = NULL; /* the original directory */
+char *personal_extension_map = NULL; /* .mime.types */
+char *personal_mail_address = NULL; /* the user's mail address */
+char *personal_mail_name = NULL; /* the user's personal name mail */
+char *personal_type_map = NULL; /* .mailcap */
+char *pref_charset = NULL; /* preferred character set */
+char *proxyauth_info[2] =
+{NULL, NULL}; /* Id:Password for protected proxy servers */
+
+#ifdef USE_SESSIONS
+BOOLEAN LYAutoSession = FALSE; /* enable/disable auto saving/restoring of */
+
+ /* session */
+char *LYSessionFile = NULL; /* the session file from lynx.cfg */
+char *session_file = NULL; /* the current session file */
+char *sessionin_file = NULL; /* only resume session from this file */
+char *sessionout_file = NULL; /* only save session to this file */
+short session_limit = 250; /* maximal number of entries saved per */
+
+ /* session file, rest will be ignored */
+#endif /* USE_SESSIONS */
+char *startfile = NULL; /* the first file */
+char *startrealm = NULL; /* the startfile realm */
+char *system_mail = NULL; /* The path for sending mail */
+char *system_mail_flags = NULL; /* Flags for sending mail */
+char *x_display = NULL; /* display environment variable */
+
+HistInfo *history;
+int nhist = 0; /* number of used history entries */
+unsigned size_history; /* number of allocated history entries */
+
+LinkInfo links[MAXLINKS];
+
+BOOLEAN nomore = FALSE; /* display -more- string in statusline messages */
+int AlertSecs; /* time-delay for HTAlert() messages */
+int DelaySecs; /* time-delay for HTProgress messages */
+int InfoSecs; /* time-delay for Information messages */
+int LYMultiBookmarks = MULTI_BOOKMARK_SUPPORT;
+int LYStatusLine = -1; /* Line for statusline() if > -1 */
+int LYcols = DFT_COLS;
+int LYlines = DFT_ROWS;
+int MessageSecs; /* time-delay for important Messages */
+int ReplaySecs; /* time-delay for command-scripts */
+int crawl_count = 0; /* Starting number for lnk#.dat files in crawls */
+int dump_output_width = 0;
+int dump_server_status = 0;
+int lynx_temp_subspace = 0; /* > 0 if we made temp-directory */
+int max_cookies_domain = 50;
+int max_cookies_global = 500;
+int max_cookies_buffer = 4096;
+int max_uri_size = 8192;
+int nlinks = 0; /* number of links in memory */
+int outgoing_mail_charset = -1; /* translate mail to this charset */
+
+#ifndef DISABLE_BIBP
+BOOLEAN BibP_bibhost_available = FALSE; /* until check succeeds */
+BOOLEAN BibP_bibhost_checked = FALSE; /* until LYCheckBibHost */
+BOOLEAN no_goto_bibp = FALSE;
+char *BibP_bibhost = NULL; /* local server for bibp: links */
+char *BibP_globalserver = NULL; /* global server for bibp: links */
+#endif
+
+#ifdef USE_PERSISTENT_COOKIES
+BOOLEAN persistent_cookies = FALSE; /* disabled by default! */
+char *LYCookieFile = NULL; /* cookie read file */
+char *LYCookieSaveFile = NULL; /* cookie save file */
+#endif /* USE_PERSISTENT_COOKIES */
+
+#ifdef EXP_NESTED_TABLES
+BOOLEAN nested_tables =
+#if defined(USE_COLOR_STYLE)
+TRUE
+#else
+FALSE /* see 2001-08-15 */
+#endif
+ ;
+#endif
+
+BOOLEAN LYShowTransferRate = TRUE;
+int LYTransferRate = rateKB;
+int LYAcceptEncoding = encodingALL;
+int LYAcceptMedia = mediaOpt1;
+int LYContentType = contentTEXT;
+const char *ContentTypes[] =
+{
+ STR_BINARY,
+ STR_PLAINTEXT,
+ STR_HTML
+};
+char *LYTransferName = NULL;
+
+char *XLoadImageCommand = NULL; /* Default image viewer for X */
+BOOLEAN LYNoISMAPifUSEMAP = FALSE; /* Omit ISMAP link if MAP present? */
+int LYHiddenLinks = HIDDENLINKS_SEPARATE; /* Show hidden links? */
+
+char *SSL_cert_file = NULL; /* Default CA CERT file */
+char *SSL_client_cert_file = NULL;
+char *SSL_client_key_file = NULL;
+
+int HTprotocolLevel = HTTP_1_0;
+
+int Old_DTD = NO;
+static BOOLEAN DTD_recovery = NO;
+
+#ifndef NO_LYNX_TRACE
+FILE *LYTraceLogFP = NULL; /* Pointer for TRACE log */
+#endif
+char *LYTraceLogPath = NULL; /* Path for TRACE log */
+BOOLEAN LYUseTraceLog = USE_TRACE_LOG; /* Use a TRACE log? */
+
+#ifdef LY_FIND_LEAKS
+char LYLeaksPath[LY_MAXPATH];
+#endif
+
+BOOLEAN LYSeekFragMAPinCur = TRUE;
+BOOLEAN LYSeekFragAREAinCur = TRUE;
+BOOLEAN LYStripDotDotURLs = TRUE; /* Try to fix ../ in some URLs? */
+BOOLEAN LYForceSSLCookiesSecure = FALSE;
+BOOLEAN LYNoCc = FALSE;
+BOOLEAN LYPreparsedSource = FALSE; /* Show source as preparsed? */
+BOOLEAN LYPrependBaseToSource = TRUE;
+BOOLEAN LYPrependCharsetToSource = TRUE;
+BOOLEAN LYQuitDefaultYes = QUIT_DEFAULT_YES;
+BOOLEAN dont_wrap_pre = FALSE;
+
+int cookie_noprompt;
+int cookie_version = COOKIES_RFC_6265;
+
+#ifdef USE_SSL
+int ssl_noprompt = FORCE_PROMPT_DFT;
+#endif
+BOOLEAN conv_jisx0201kana = TRUE;
+BOOLEAN wait_viewer_termination = FALSE;
+
+int connect_timeout = 18000; /*=180000*0.1 - used in HTDoConnect.*/
+int reading_timeout = 18000; /*=180000*0.1 - used in HTDoConnect.*/
+
+#ifdef USE_JUSTIFY_ELTS
+BOOLEAN ok_justify = FALSE;
+int justify_max_void_percent = 35;
+#endif
+
+#ifdef USE_LOCALE_CHARSET
+BOOLEAN LYLocaleCharset = FALSE;
+#endif
+BOOLEAN assumed_charset = FALSE;
+
+#ifndef NO_DUMP_WITH_BACKSPACES
+BOOLEAN with_backspaces = FALSE;
+#endif
+
+#if defined(PDCURSES) && defined(PDC_BUILD) && PDC_BUILD >= 2401
+int scrsize_x = 0;
+int scrsize_y = 0;
+#endif
+
+BOOLEAN force_empty_hrefless_a = FALSE;
+
+#ifdef TEXTFIELDS_MAY_NEED_ACTIVATION
+BOOL textfields_need_activation = FALSE;
+BOOLEAN textfields_activation_option = FALSE;
+#endif
+
+BOOLEAN textfield_prompt_at_left_edge = FALSE;
+
+#ifdef MARK_HIDDEN_LINKS
+char *hidden_link_marker = NULL;
+#endif
+
+#ifdef DISP_PARTIAL
+BOOLEAN display_partial_flag = TRUE; /* Display document during download */
+BOOLEAN debug_display_partial = FALSE; /* Show with MessageSecs delay */
+int partial_threshold = -1; /* # of lines to be d/l'ed until we repaint */
+#endif
+
+char *socks5_proxy = NULL;
+
+BOOLEAN LYNonRestartingSIGWINCH = FALSE;
+BOOLEAN LYReuseTempfiles = FALSE;
+BOOLEAN LYUseBuiltinSuffixes = TRUE;
+
+int LYNoZapKey = 0; /* 0: off (do z checking), 1: full, 2: initially */
+
+#ifndef DISABLE_NEWS
+#include <HTNews.h>
+#endif
+
+BOOLEAN FileInitAlreadyDone = FALSE;
+
+#ifdef USE_PROGRAM_DIR
+char *program_dir = NULL;
+#endif
+
+static BOOLEAN stack_dump = FALSE;
+static char *terminal = NULL;
+static const char *pgm;
+static BOOLEAN no_numbers = FALSE;
+static BOOLEAN number_links = FALSE;
+static BOOLEAN number_fields = FALSE;
+static BOOLEAN LYPrependBase = FALSE;
+static HTList *LYStdinArgs = NULL;
+HTList *positionable_editor = NULL;
+
+#ifndef EXTENDED_OPTION_LOGIC
+/* if set then '--' will be recognized as the end of options */
+#define EXTENDED_OPTION_LOGIC 1
+#endif
+
+#ifndef EXTENDED_STARTFILE_RECALL
+/* if set then additional non-option args (before the last one) will be
+ made available for 'g'oto recall - kw */
+#define EXTENDED_STARTFILE_RECALL 1
+#endif
+
+#if EXTENDED_STARTFILE_RECALL
+static char *nonoption = 0;
+#endif
+
+#ifndef OPTNAME_ALLOW_DASHES
+/* if set, then will allow dashes and underscores to be used interchangeable
+ in commandline option's names - VH */
+#define OPTNAME_ALLOW_DASHES 1
+#endif
+
+static BOOL parse_arg(char **arg, unsigned mask, int *countp);
+static GCC_NORETURN void print_help_and_exit(int exit_status);
+static void print_help_strings(const char *name,
+ const char *help,
+ const char *value,
+ int option);
+
+#ifndef VMS
+BOOLEAN LYNoCore = NO_FORCED_CORE_DUMP;
+BOOLEAN restore_sigpipe_for_children = FALSE;
+static void FatalProblem(int sig);
+#endif /* !VMS */
+
+#if defined(USE_COLOR_STYLE)
+int LYuse_color_style = TRUE;
+char *lynx_lss_file = NULL; /* from config-file, etc. */
+static char *lynx_lss_file2 = NULL; /* from command-line options */
+const char *default_color_styles = "\
+lynx.lss;\
+blue-background.lss;\
+bright-blue.lss;\
+midnight.lss;\
+mild-colors.lss;\
+opaque.lss\
+";
+#endif
+
+#ifdef USE_DEFAULT_COLORS
+BOOLEAN LYuse_default_colors = TRUE;
+#endif
+
+#ifdef __DJGPP__
+static void LY_set_ctrl_break(int setting)
+{
+ (void) signal(SIGINT, (setting ? SIG_DFL : SIG_IGN));
+ setcbrk(setting);
+}
+
+static int LY_get_ctrl_break(void)
+{
+ __dpmi_regs regs;
+
+ regs.h.ah = 0x33;
+ regs.h.al = 0x00;
+ __dpmi_int(0x21, &regs);
+ return ((int) regs.h.dl);
+}
+
+static void reset_break(void)
+{
+ LY_set_ctrl_break(init_ctrl_break[0]);
+}
+#endif /* __DJGPP__ */
+
+#if defined(WIN_EX)
+static int is_windows_nt(void)
+{
+ DWORD version;
+
+ version = GetVersion();
+ if ((version & 0x80000000) == 0)
+ return 1;
+ else
+ return 0;
+}
+#endif
+
+#ifdef LY_FIND_LEAKS
+static void free_lynx_globals(void)
+{
+ int i;
+
+#if defined(USE_COLOR_STYLE)
+ clear_lss_list();
+#endif
+ FREE(ftp_format);
+#ifndef VMS
+ FREE(list_format);
+#ifdef LYNXCGI_LINKS /* WebSter Mods -jkt */
+ FREE(LYCgiDocumentRoot);
+#endif /* LYNXCGI_LINKS */
+ free_lynx_cfg();
+#endif /* !VMS */
+
+#ifdef SYSLOG_REQUESTED_URLS
+ FREE(syslog_txt);
+#endif
+
+#ifdef VMS
+ Define_VMSLogical("LYNX_VERSION", "");
+#else
+ (void) putenv("LYNX_VERSION=" LYNX_VERSION);
+#endif /* VMS */
+#ifndef VMS
+ FREE(lynx_version_putenv_command);
+#endif
+
+#if USE_VMS_MAILER
+ FREE(mail_adrs);
+#endif
+
+ FREE(LynxHome);
+ FREE(history);
+ FREE(homepage);
+ FREE(original_dir);
+ FREE(startfile);
+ FREE(helpfile);
+ FREE(helpfilepath);
+ FREE(jumpprompt);
+#ifdef JUMPFILE
+ FREE(jumpfile);
+#endif /* JUMPFILE */
+ FREE(indexfile);
+ FREE(x_display);
+ FREE(global_type_map);
+ FREE(personal_type_map);
+ FREE(global_extension_map);
+ FREE(personal_extension_map);
+ FREE(language);
+ FREE(pref_charset);
+ FREE(LynxSigFile);
+ FREE(system_mail);
+ FREE(system_mail_flags);
+#ifndef DISABLE_BIBP
+ FREE(BibP_bibhost);
+ FREE(BibP_globalserver);
+#endif
+#ifdef USE_PERSISTENT_COOKIES
+ FREE(LYCookieFile);
+ FREE(LYCookieSaveFile);
+#endif
+ FREE(LYCookieAcceptDomains);
+ FREE(LYCookieRejectDomains);
+ FREE(LYCookieLooseCheckDomains);
+ FREE(LYCookieStrictCheckDomains);
+ FREE(LYCookieQueryCheckDomains);
+ FREE(LYUserAgent);
+ FREE(LYUserAgentDefault);
+ FREE(LYHostName);
+ FREE(LYLocalDomain);
+ FREE(lynx_save_space);
+ FREE(bookmark_page);
+ FREE(BookmarkPage);
+ for (i = 0; i <= MBM_V_MAXFILES; i++) {
+ FREE(MBM_A_subbookmark[i]);
+ FREE(MBM_A_subdescript[i]);
+ }
+ FREE(editor);
+ FREE(authentication_info[0]);
+ FREE(authentication_info[1]);
+ FREE(proxyauth_info[0]);
+ FREE(proxyauth_info[1]);
+ FREE(lynxjumpfile);
+#ifndef DISABLE_FTP
+ FREE(ftp_lasthost);
+ LYFreeStringList(broken_ftp_epsv);
+ LYFreeStringList(broken_ftp_retr);
+#endif
+ FREE(startrealm);
+ FREE(personal_mail_address);
+ FREE(personal_mail_name);
+ FREE(anonftp_password);
+ FREE(URLDomainPrefixes);
+ FREE(URLDomainSuffixes);
+ FREE(XLoadImageCommand);
+ FREE(lynx_temp_space);
+ FREE(LYTransferName);
+ FREE(LYTraceLogPath);
+ FREE(lynx_cfg_file);
+ FREE(SSL_cert_file);
+ FREE(SSL_client_cert_file);
+ FREE(SSL_client_key_file);
+#if defined(USE_COLOR_STYLE)
+ FREE(lynx_lss_file2);
+ FREE(lynx_lss_file);
+#endif
+ FREE(UCAssume_MIMEcharset);
+ LYUIPages_free();
+ LYFreeHilites(0, nlinks);
+ nlinks = 0;
+ LYFreeStringList(LYcommandList());
+ HTInitProgramPaths(FALSE);
+#if EXTENDED_STARTFILE_RECALL
+ FREE(nonoption);
+#endif
+ LYFreeStringList(positionable_editor);
+
+ return;
+}
+#endif /* LY_FIND_LEAKS */
+
+/*
+ * This function frees the LYStdinArgs list. - FM
+ */
+static void LYStdinArgs_free(void)
+{
+ LYFreeStringList(LYStdinArgs);
+ LYStdinArgs = NULL;
+}
+
+void reset_signals(void)
+{
+#ifndef NOSIGHUP
+ (void) signal(SIGHUP, SIG_DFL);
+#endif /* NOSIGHUP */
+ (void) signal(SIGTERM, SIG_DFL);
+#ifndef VMS
+ (void) signal(SIGINT, SIG_DFL);
+#endif /* !VMS */
+#ifdef SIGTSTP
+ if (no_suspend)
+ (void) signal(SIGTSTP, SIG_DFL);
+#endif /* SIGTSTP */
+}
+
+void exit_immediately(int code)
+{
+ reset_signals();
+ exit(code);
+}
+
+#ifdef EBCDIC
+static void FixCharacters(void)
+{
+ int c;
+ int work1[256], work2[256];
+
+ for (c = 0; c < 256; c++) {
+ work1[c] = keymap[c + 1];
+ work2[c] = key_override[c + 1];
+ }
+ for (c = 0; c < 256; c++) {
+ keymap[IBM1047[c] + 1] = work1[c];
+ key_override[IBM1047[c] + 1] = work2[c];
+ }
+}
+#endif /* EBCDIC */
+
+static BOOL GetStdin(char **buf,
+ int marker)
+{
+ if (LYSafeGets(buf, stdin) != 0
+ && (!marker || StrNCmp(*buf, "---", 3) != 0)) {
+ LYTrimTrailing(*buf);
+ CTRACE((tfp, "...data: %s\n", *buf));
+ return TRUE;
+ }
+ CTRACE((tfp, "...mark: %s\n", *buf ? *buf : ""));
+ return FALSE;
+}
+
+#ifdef WIN32
+static BOOL cleanup_win32(DWORD fdwCtrlType)
+{
+ switch (fdwCtrlType) {
+ case CTRL_CLOSE_EVENT:
+ cleanup_sig(-1);
+ return TRUE;
+ default:
+ return FALSE;
+ }
+}
+#endif
+
+/*
+ * Append the SSL version to lynx version or user-agent string.
+ */
+#ifdef USE_SSL
+static void append_ssl_version(char **target,
+ const char *separator)
+{
+ char SSLLibraryVersion[256];
+ char *SSLcp;
+
+ HTSprintf(target, " SSL-MM%s1.4.1", separator);
+
+#undef LYNX_SSL_VERSION
+
+#if defined(SSLEAY_VERSION)
+#define LYNX_SSL_VERSION SSLeay_version(SSLEAY_VERSION)
+#elif defined(OPENSSL_VERSION_TEXT)
+#define LYNX_SSL_VERSION OPENSSL_VERSION_TEXT
+#elif defined(GNUTLS_VERSION)
+#define LYNX_SSL_VERSION "GNUTLS " GNUTLS_VERSION " "
+#endif
+
+#ifdef LYNX_SSL_VERSION
+ if (*separator == ' ')
+ StrAllocCat(*target, ",");
+ LYStrNCpy(SSLLibraryVersion, LYNX_SSL_VERSION, sizeof(SSLLibraryVersion) - 1);
+ if ((SSLcp = StrChr(SSLLibraryVersion, ' ')) != NULL) {
+ *SSLcp++ = *separator;
+ if ((SSLcp = StrChr(SSLcp, ' ')) != NULL) {
+ *SSLcp = '\0';
+ StrAllocCat(*target, " ");
+ StrAllocCat(*target, SSLLibraryVersion);
+ }
+ }
+#endif /* LYNX_SSL_VERSION */
+}
+#endif /* USE_SSL */
+
+/* Set the text message domain. */
+void LYSetTextDomain(void)
+{
+#if defined(HAVE_LIBINTL_H) || defined(HAVE_LIBGETTEXT_H)
+ const char *cp;
+
+ if ((cp = LYGetEnv("LYNX_LOCALEDIR")) == 0) {
+#ifdef USE_PROGRAM_DIR
+ char *localedir = NULL;
+
+ HTSprintf0(&localedir, "%s\\locale", program_dir);
+ cp = localedir;
+#else
+ cp = LOCALEDIR;
+#endif
+ }
+ bindtextdomain(NLS_TEXTDOMAIN, cp);
+ textdomain(NLS_TEXTDOMAIN);
+#endif
+}
+
+static void SetLocale(void)
+{
+#ifdef LOCALE
+ /*
+ * LOCALE support for international characters.
+ */
+ setlocale(LC_ALL, "");
+#endif /* LOCALE */
+ LYSetTextDomain();
+}
+
+/*
+ * Wow! Someone wants to start up Lynx.
+ */
+int main(int argc,
+ char **argv)
+{
+ int i; /* indexing variable */
+ int status = 0; /* exit status */
+ char *temp = NULL;
+ const char *ccp;
+ char *cp;
+ FILE *fp;
+ struct stat dir_info;
+ char filename[LY_MAXPATH];
+ BOOL LYGetStdinArgs = FALSE;
+
+#if USE_GETPWUID
+ struct passwd *my_pwd;
+#endif
+
+#ifdef _WINDOWS
+ WSADATA WSAData;
+#endif /* _WINDOWS */
+
+ /*
+ * Just in case someone has the idea to install lynx set-uid, let's try
+ * to discourage it.
+ */
+#if defined(GETUID) && defined(SETUID)
+ setuid(getuid());
+#endif
+
+#ifdef LY_FIND_LEAKS
+ /*
+ * Register the final function to be executed when being exited. Will
+ * display memory leaks if the -find-leaks option is used. This should
+ * be the first call to atexit() for leak-checking, which ensures that
+ * all of the other functions will be invoked before LYLeaks().
+ */
+ atexit(LYLeaks);
+ /*
+ * Register the function which will free our allocated globals.
+ */
+ atexit(free_lynx_globals);
+
+ LYAddPathToHome(LYLeaksPath, (size_t) LY_MAXPATH, LEAKAGE_SINK);
+#endif /* LY_FIND_LEAKS */
+
+#ifdef NOT_ASCII
+ FixCharacters();
+#endif /* NOT_ASCII */
+
+#ifndef DISABLE_FTP
+ /* malloc a sizeof(char) so 1st strcmp() won't dump in HTLoadFile() */
+ ftp_lasthost = typecalloc(char);
+#endif
+
+ LYinitEditmap();
+ LYinitKeymap();
+#ifdef USE_CHARSET_CHOICE
+ memset((char *) charset_subsets, 0, sizeof(charset_subset_t) * MAXCHARSETS);
+#endif
+
+#ifdef _WINDOWS
+ {
+ int err;
+ WORD wVerReq;
+
+ wVerReq = MAKEWORD(1, 1);
+
+ err = WSAStartup(wVerReq, &WSAData);
+ if (err != 0) {
+ puts(gettext("No Winsock found, sorry."));
+ sleep(5);
+ return 1;
+ }
+ }
+
+ /* 1998/09/03 (Thu) 22:02:32 */
+ InitializeCriticalSection(&critSec_READ);
+
+#endif /* _WINDOWS */
+
+#if defined(WIN_EX)
+ /* 1997/10/19 (Sun) 21:40:54 */
+ system_is_NT = (BOOL) is_windows_nt();
+
+ /* 1998/01/13 (Tue) 21:13:47 */
+ GetWindowsDirectory(filename, sizeof filename);
+ windows_drive[0] = filename[0];
+ windows_drive[1] = filename[1];
+ windows_drive[2] = '\0';
+#endif
+
+#ifdef __DJGPP__
+ if (LY_get_ctrl_break() == 0) {
+ LY_set_ctrl_break(TRUE);
+ init_ctrl_break[0] = 0;
+ } else {
+ init_ctrl_break[0] = 1;
+ }
+ __djgpp_set_sigquit_key(0x082D); /* Bind ALT-X to SIGQUIT */
+ signal(SIGQUIT, cleanup_sig);
+ atexit(reset_break);
+
+ if (((ccp = LYGetEnv("SHELL")) != NULL)
+ && (strstr(LYPathLeaf(ccp), "sh") != NULL))
+ dj_is_bash = TRUE;
+#endif /* __DJGPP__ */
+
+ /*
+ * To prevent corrupting binary data on DOS, MS-WINDOWS or OS/2
+ * we open files and stdout in BINARY mode by default.
+ * Where necessary we should open and (close!) TEXT mode.
+ * (use LYNewTxtFile/LYAppendToTxtFile to open text files for writing)
+ */
+ SetDefaultMode(O_BINARY);
+ SetOutputMode(O_BINARY);
+
+#ifdef DOSPATH
+ if (LYGetEnv("TERM") == NULL)
+ putenv("TERM=vt100");
+#endif
+
+ LYShowColor = (SHOW_COLOR ? SHOW_COLOR_ON : SHOW_COLOR_OFF);
+ /*
+ * Set up the argument list.
+ */
+ pgm = argv[0];
+ cp = NULL;
+#ifdef USE_PROGRAM_DIR
+ StrAllocCopy(program_dir, pgm);
+ if ((cp = strrchr(program_dir, '\\')) != NULL) {
+ *cp = '\0';
+ } else {
+ FREE(program_dir);
+ StrAllocCopy(program_dir, ".");
+ }
+#endif
+ if ((cp = LYLastPathSep(pgm)) != NULL) {
+ pgm = cp + 1;
+ }
+
+ /*
+ * Set up trace, the anonymous account defaults, validate restrictions,
+ * and/or the nosocks flag, if requested, and an alternate configuration
+ * file, if specified, NOW. Also, if we only want the help menu, output
+ * that and exit. - FM
+ */
+#ifndef NO_LYNX_TRACE
+ if (LYGetEnv("LYNX_TRACE") != 0) {
+ WWW_TraceFlag = TRUE;
+ }
+#endif
+
+ /*
+ * Set up the TRACE log path, and logging if appropriate. - FM
+ */
+ if ((ccp = LYGetEnv("LYNX_TRACE_FILE")) == 0)
+ ccp = FNAME_LYNX_TRACE;
+ LYTraceLogPath = typeMallocn(char, LY_MAXPATH);
+
+ LYAddPathToHome(LYTraceLogPath, (size_t) LY_MAXPATH, ccp);
+
+ /*
+ * Act on -version, -trace and -trace-mask NOW.
+ */
+ for (i = 1; i < argc; i++) {
+ parse_arg(&argv[i], 1, &i);
+ }
+ LYOpenTraceLog();
+
+ SetLocale();
+
+ /*
+ * Initialize our startup and global variables.
+ */
+#ifdef ULTRIX
+ /*
+ * Need this for Ultrix.
+ */
+ terminal = LYGetEnv("TERM");
+ if ((terminal == NULL) || !strncasecomp(terminal, "xterm", 5))
+ terminal = "vt100";
+#endif /* ULTRIX */
+ /*
+ * Zero the links and history struct arrays.
+ */
+ memset((void *) links, 0, sizeof(LinkInfo) * MAXLINKS);
+ LYAllocHistory(8);
+ /*
+ * Zero the MultiBookmark arrays.
+ */
+ memset((void *) MBM_A_subbookmark, 0, sizeof(char *) * (MBM_V_MAXFILES + 1));
+ memset((void *) MBM_A_subdescript, 0, sizeof(char *) * (MBM_V_MAXFILES + 1));
+
+#ifndef VMS
+ StrAllocCopy(list_format, LIST_FORMAT);
+ StrAllocCopy(ftp_format, FTP_FORMAT);
+#endif /* !VMS */
+
+ AlertSecs = SECS2Secs(ALERTSECS);
+ DelaySecs = SECS2Secs(DEBUGSECS);
+ InfoSecs = SECS2Secs(INFOSECS);
+ MessageSecs = SECS2Secs(MESSAGESECS);
+ ReplaySecs = SECS2Secs(REPLAYSECS);
+
+ StrAllocCopy(LYTransferName, "KiB");
+ StrAllocCopy(helpfile, HELPFILE);
+ StrAllocCopy(startfile, STARTFILE);
+ LYEscapeStartfile(&startfile);
+ StrAllocCopy(indexfile, DEFAULT_INDEX_FILE);
+ StrAllocCopy(global_type_map, GLOBAL_MAILCAP);
+ StrAllocCopy(personal_type_map, PERSONAL_MAILCAP);
+ StrAllocCopy(global_extension_map, GLOBAL_EXTENSION_MAP);
+ StrAllocCopy(personal_extension_map, PERSONAL_EXTENSION_MAP);
+ StrAllocCopy(language, PREFERRED_LANGUAGE);
+ StrAllocCopy(pref_charset, PREFERRED_CHARSET);
+ StrAllocCopy(system_mail, SYSTEM_MAIL);
+ StrAllocCopy(system_mail_flags, SYSTEM_MAIL_FLAGS);
+
+ StrAllocCopy(LYUserAgent, LYNX_NAME);
+ StrAllocCat(LYUserAgent, "/");
+ StrAllocCat(LYUserAgent, LYNX_VERSION);
+ if (HTLibraryVersion) {
+ StrAllocCat(LYUserAgent, " libwww-FM/");
+ StrAllocCat(LYUserAgent, HTLibraryVersion);
+ }
+#ifdef USE_SSL
+ append_ssl_version(&LYUserAgent, "/");
+#endif /* USE_SSL */
+ StrAllocCopy(LYUserAgentDefault, LYUserAgent);
+
+#ifdef VMS
+ Define_VMSLogical("LYNX_VERSION", LYNX_VERSION);
+#else
+ StrAllocCopy(lynx_version_putenv_command, "LYNX_VERSION=");
+ StrAllocCat(lynx_version_putenv_command, LYNX_VERSION);
+ (void) putenv(lynx_version_putenv_command);
+ /* Note: you must not free the data passed to 'putenv()' until you give it
+ * a new value for that variable.
+ */
+#endif /* VMS */
+
+ if ((ccp = LYGetEnv("LYNX_TEMP_SPACE")) != NULL)
+ StrAllocCopy(lynx_temp_space, ccp);
+#if defined (UNIX) || defined (__DJGPP__)
+ else if ((ccp = LYGetEnv("TMPDIR")) != NULL)
+ StrAllocCopy(lynx_temp_space, ccp);
+#endif
+#if defined (DOSPATH) || defined (__EMX__)
+ else if ((ccp = LYGetEnv("TEMP")) != NULL)
+ StrAllocCopy(lynx_temp_space, ccp);
+ else if ((ccp = LYGetEnv("TMP")) != NULL)
+ StrAllocCopy(lynx_temp_space, ccp);
+#endif
+ else {
+#if defined(USE_PROGRAM_DIR)
+ StrAllocCopy(lynx_temp_space, program_dir);
+#elif defined(TEMP_SPACE)
+ StrAllocCopy(lynx_temp_space, TEMP_SPACE);
+#else
+ puts(gettext("You MUST define a valid TMP or TEMP area!"));
+ exit_immediately(EXIT_FAILURE);
+#endif
+ }
+
+#ifdef WIN_EX /* for Windows 2000 ... 1999/08/23 (Mon) 08:24:35 */
+ if (access(lynx_temp_space, 0) != 0)
+#endif
+ LYTildeExpand(&lynx_temp_space, TRUE);
+
+ if ((cp = strstr(lynx_temp_space, "$USER")) != NULL) {
+ char *cp1;
+
+ if ((cp1 = LYGetEnv("USER")) != NULL) {
+ *cp = '\0';
+ StrAllocCopy(temp, lynx_temp_space);
+ *cp = '$';
+ StrAllocCat(temp, cp1);
+ cp += 5;
+ StrAllocCat(temp, cp);
+ StrAllocCopy(lynx_temp_space, temp);
+ FREE(temp);
+ }
+ }
+#ifdef VMS
+ LYLowerCase(lynx_temp_space);
+ if (StrChr(lynx_temp_space, '/') != NULL) {
+ if (strlen(lynx_temp_space) == 1) {
+ StrAllocCopy(lynx_temp_space, "sys$scratch:");
+ } else {
+ LYAddPathSep(&lynx_temp_space);
+ StrAllocCopy(temp, HTVMS_name("", lynx_temp_space));
+ StrAllocCopy(lynx_temp_space, temp);
+ FREE(temp);
+ }
+ }
+ if (StrChr(lynx_temp_space, ':') == NULL &&
+ StrChr(lynx_temp_space, ']') == NULL) {
+ StrAllocCat(lynx_temp_space, ":");
+ }
+#else
+ LYAddPathSep(&lynx_temp_space);
+ StrAllocCopy(lynx_temp_space, HTSYS_name(lynx_temp_space));
+#endif /* VMS */
+
+ if ((HTStat(lynx_temp_space, &dir_info) < 0
+#if defined(MULTI_USER_UNIX)
+ && mkdir(lynx_temp_space, 0700) < 0
+#endif
+ )
+ || !S_ISDIR(dir_info.st_mode)) {
+ fprintf(stderr, "%s: %s\n",
+ lynx_temp_space,
+ gettext("No such directory"));
+ exit_immediately(EXIT_FAILURE);
+ }
+#if USE_VMS_MAILER
+#ifndef MAIL_ADRS
+#define MAIL_ADRS "\"IN%%\"\"%s\"\"\""
+#endif
+ StrAllocCopy(mail_adrs, MAIL_ADRS);
+#endif
+
+#ifdef LYNX_HOST_NAME
+ StrAllocCopy(LYHostName, LYNX_HOST_NAME);
+#else
+ StrAllocCopy(LYHostName, HTHostName());
+#endif /* LYNX_HOST_NAME */
+
+ StrAllocCopy(LYLocalDomain, LOCAL_DOMAIN);
+ StrAllocCopy(URLDomainPrefixes, URL_DOMAIN_PREFIXES);
+ StrAllocCopy(URLDomainSuffixes, URL_DOMAIN_SUFFIXES);
+ StrAllocCopy(XLoadImageCommand, XLOADIMAGE_COMMAND);
+ StrAllocCopy(SSL_cert_file, SSL_CERT_FILE);
+
+#ifndef DISABLE_BIBP
+ StrAllocCopy(BibP_globalserver, BIBP_GLOBAL_SERVER);
+ StrAllocCopy(BibP_bibhost, "http://bibhost/"); /* protocol specified. */
+#endif
+
+ /*
+ * Disable news posting if the compilation-based LYNewsPosting value is
+ * FALSE. This may be changed further down via lynx.cfg or the
+ * -restriction command line switch. - FM
+ */
+#ifndef DISABLE_NEWS
+ no_newspost = (BOOL) (LYNewsPosting == FALSE);
+#endif
+
+ for (i = 1; i < argc; i++) {
+ parse_arg(&argv[i], 2, &i);
+ }
+
+ /*
+ * If we have a lone "-" switch for getting arguments from stdin, get them
+ * NOW, and act on the relevant ones, saving the others into an HTList for
+ * handling after the other initializations. The primary purpose of this
+ * feature is to allow for the potentially very long command line that can
+ * be associated with post or get data. The original implementation
+ * required that the lone "-" be the only command line argument, but that
+ * precluded its use when the lynx command is aliased with other arguments.
+ * When interactive, the stdin input is terminated by by Control-D on Unix
+ * or Control-Z on VMS, and each argument is terminated by a RETURN. When
+ * the argument is -get_data or -post_data, the data are terminated by a
+ * "---" string, alone on the line (also terminated by RETURN). - FM
+ */
+ for (i = 1; i < argc; i++) {
+ if (strcmp(argv[i], "-") == 0) {
+ LYGetStdinArgs = TRUE;
+ break;
+ }
+ }
+ if (LYGetStdinArgs == TRUE) {
+ char *buf = NULL;
+
+ CTRACE((tfp, "processing stdin arguments\n"));
+ while (GetStdin(&buf, TRUE)) {
+ char *noargv[2];
+
+ noargv[0] = buf;
+ noargv[1] = NULL;
+ LYTrimTrailing(buf);
+
+ if (parse_arg(&noargv[0], 2, (int *) 0) == FALSE
+ && buf[0] != '\0') {
+ char *argument = NULL;
+
+ if (LYStdinArgs == NULL) {
+ LYStdinArgs = HTList_new();
+#ifdef LY_FIND_LEAKS
+ atexit(LYStdinArgs_free);
+#endif
+ }
+ StrAllocCopy(argument, buf);
+ HTList_appendObject(LYStdinArgs, argument);
+ CTRACE((tfp, "...StdinArg:%s\n", argument));
+ } else {
+ CTRACE((tfp, "...complete:%s\n", buf));
+ }
+ }
+ CTRACE((tfp, "...done with stdin arguments\n"));
+ FREE(buf);
+ }
+#ifdef SOCKS
+ if (socks_flag)
+ SOCKSinit(argv[0]);
+#endif /* SOCKS */
+
+ /*
+ * If we had -validate set all of the restrictions and disallow a TRACE log
+ * NOW. - FM
+ */
+ if (LYValidate == TRUE) {
+ parse_restrictions("all");
+ LYUseTraceLog = FALSE;
+ }
+
+ /*
+ * If we didn't get and act on a -validate or -anonymous switch, but can
+ * verify that this is the anonymous account, set the default restrictions
+ * for that account and disallow a TRACE log NOW. - FM
+ */
+ if (!LYValidate && !LYRestricted &&
+ strlen(ANONYMOUS_USER) > 0 &&
+#if defined (VMS) || defined (NOUSERS)
+ !strcasecomp((LYGetEnv("USER") == NULL ? " " : LYGetEnv("USER")),
+ ANONYMOUS_USER)
+#elif USE_GETPWUID
+ ((my_pwd = getpwuid(getuid())) == NULL
+ || STREQ(my_pwd->pw_gecos, ANONYMOUS_USER))
+#elif defined(HAVE_CUSERID)
+ STREQ((char *) cuserid(NULL), ANONYMOUS_USER)
+#else
+ STREQ(((char *) getlogin() == NULL ? " " : getlogin()), ANONYMOUS_USER)
+#endif /* checks for user-id */
+ ) {
+ parse_restrictions("default");
+ LYRestricted = TRUE;
+ LYUseTraceLog = FALSE;
+ }
+#ifdef USE_CMD_LOGGING
+ /*
+ * Open command-script, if specified
+ */
+ if (non_empty(lynx_cmd_script)) {
+ LYTildeExpand(&lynx_cmd_script, TRUE);
+ LYOpenCmdScript();
+ }
+ /*
+ * Open command-logging, if specified
+ */
+ if (non_empty(lynx_cmd_logfile)) {
+ LYTildeExpand(&lynx_cmd_logfile, TRUE);
+ LYOpenCmdLogfile(argc, argv);
+ }
+#endif
+
+ /*
+ * Set up the default jump file stuff. - FM
+ */
+ StrAllocCopy(jumpprompt, JUMP_PROMPT);
+#ifdef JUMPFILE
+ StrAllocCopy(jumpfile, JUMPFILE);
+ {
+ temp = NULL;
+ HTSprintf0(&temp, "JUMPFILE:%s", jumpfile);
+ if (!LYJumpInit(temp)) {
+ CTRACE((tfp, "Failed to register %s\n", temp));
+ }
+ FREE(temp);
+ }
+#endif /* JUMPFILE */
+
+ /*
+ * If no alternate configuration file was specified on the command line,
+ * see if it's in the environment.
+ */
+ if (isEmpty(lynx_cfg_file)) {
+ if (((cp = LYGetEnv("LYNX_CFG")) != NULL) ||
+ (cp = LYGetEnv("lynx_cfg")) != NULL)
+ StrAllocCopy(lynx_cfg_file, cp);
+ }
+#ifdef USE_PROGRAM_DIR
+ if (isEmpty(lynx_cfg_file)) {
+ HTSprintf0(&lynx_cfg_file, "%s\\lynx.cfg", program_dir);
+ if (!LYCanReadFile(lynx_cfg_file)) {
+ FREE(lynx_cfg_file);
+ lynx_cfg_file = NULL;
+ }
+ }
+#endif
+
+ /*
+ * If we still don't have a configuration file, use the userdefs.h
+ * definition.
+ */
+ if (isEmpty(lynx_cfg_file))
+ StrAllocCopy(lynx_cfg_file, LYNX_CFG_FILE);
+
+#ifndef _WINDOWS /* avoid the whole ~ thing for now */
+ LYTildeExpand(&lynx_cfg_file, FALSE);
+#endif
+
+ /*
+ * If the configuration file is not available, inform the user and exit.
+ */
+ if (!LYCanReadFile(lynx_cfg_file)) {
+ fprintf(stderr,
+ gettext("\nConfiguration file \"%s\" is not available.\n\n"),
+ lynx_cfg_file);
+ exit_immediately(EXIT_FAILURE);
+ }
+
+ /*
+ * Make sure we have the character sets declared. This will initialize the
+ * CHARTRANS handling. - KW
+ */
+ if (!LYCharSetsDeclared()) {
+ fprintf(stderr, gettext("\nLynx character sets not declared.\n\n"));
+ exit_immediately(EXIT_FAILURE);
+ }
+ /*
+ * (**) in Lynx, UCLYhndl_HTFile_for_unspec and UCLYhndl_for_unrec may be
+ * valid or not, but current_char_set and UCLYhndl_for_unspec SHOULD ALWAYS
+ * be a valid charset. Initialized here and may be changed later from
+ * lynx.cfg/command_line/options_menu. - LP (**)
+ */
+ /*
+ * Set up the compilation default character set. - FM
+ */
+#ifdef CAN_AUTODETECT_DISPLAY_CHARSET
+ if (auto_display_charset >= 0)
+ current_char_set = auto_display_charset;
+ else
+#endif
+ current_char_set = safeUCGetLYhndl_byMIME(CHARACTER_SET);
+ /*
+ * Set up HTTP default for unlabeled charset (iso-8859-1).
+ */
+ UCLYhndl_for_unspec = LATIN1;
+ StrAllocCopy(UCAssume_MIMEcharset,
+ LYCharSet_UC[UCLYhndl_for_unspec].MIMEname);
+
+#ifdef USE_COLOR_TABLE
+ /*
+ * Set up default foreground and background colors.
+ */
+ lynx_setup_colors();
+#endif /* USE_COLOR_TABLE */
+
+ /*
+ * Set the original directory, used for default download
+ */
+ if (!strcmp(Current_Dir(filename), ".")) {
+ if ((cp = LYGetEnv("PWD")) != 0)
+ StrAllocCopy(original_dir, cp);
+ } else {
+ StrAllocCopy(original_dir, filename);
+ }
+
+ /*
+ * Set the compilation default signature file. - FM
+ */
+ LYStrNCpy(filename, LYNX_SIG_FILE, sizeof(filename) - 1);
+ if (LYPathOffHomeOK(filename, sizeof(filename))) {
+ StrAllocCopy(LynxSigFile, filename);
+ LYAddPathToHome(filename, sizeof(filename), LynxSigFile);
+ StrAllocCopy(LynxSigFile, filename);
+ CTRACE((tfp, "LYNX_SIG_FILE set to '%s'\n", LynxSigFile));
+ } else {
+ CTRACE((tfp, "LYNX_SIG_FILE '%s' is bad. Ignoring.\n", LYNX_SIG_FILE));
+ }
+
+#ifdef USE_PRETTYSRC
+ /*this is required for checking the tagspecs when parsing cfg file by
+ LYReadCFG.c:parse_html_src_spec -HV */
+ HTSwitchDTD(TRUE);
+#endif
+ /*
+ * Process the configuration file.
+ */
+ read_cfg(lynx_cfg_file, "main program", 1, (FILE *) 0);
+
+ {
+ static char *client_keyfile = NULL;
+ static char *client_certfile = NULL;
+
+ if ((client_keyfile = LYGetEnv("SSL_CLIENT_KEY_FILE")) != NULL) {
+ CTRACE((tfp,
+ "HTGetSSLHandle: client keyfile is set to %s by SSL_CLIENT_KEY_FILE\n",
+ client_keyfile));
+ StrAllocCopy(SSL_client_key_file, client_keyfile);
+ }
+
+ if ((client_certfile = LYGetEnv("SSL_CLIENT_CERT_FILE")) != NULL) {
+ CTRACE((tfp,
+ "HTGetSSLHandle: client certfile is set to %s by SSL_CLIENT_CERT_FILE\n",
+ client_certfile));
+ StrAllocCopy(SSL_client_cert_file, client_certfile);
+ }
+ }
+
+#if defined(USE_COLOR_STYLE)
+ if (!dump_output_immediately) {
+ init_color_styles(&lynx_lss_file2, default_color_styles);
+ }
+#endif /* USE_COLOR_STYLE */
+
+ /*
+ * Process the RC file.
+ */
+ read_rc(NULL);
+
+#ifdef USE_LOCALE_CHARSET
+ LYFindLocaleCharset();
+#endif
+
+ /*
+ * Get WWW_HOME environment variable if it exists.
+ */
+ if ((cp = LYGetEnv("WWW_HOME")) != NULL) {
+ StrAllocCopy(startfile, cp);
+ LYEscapeStartfile(&startfile);
+ }
+
+ /*
+ * Set the LynxHome URL. If it's a file URL and the
+ * host is defaulted, force in "//localhost", and if
+ * it's not an absolute URL, make it one. - FM
+ */
+ StrAllocCopy(LynxHome, startfile);
+ LYEnsureAbsoluteURL(&LynxHome, "LynxHome", FALSE);
+
+ /*
+ * Process any command line arguments not already handled. - FM
+ * May set startfile as a side-effect.
+ */
+ for (i = 1; i < argc; i++) {
+ parse_arg(&argv[i], 4, &i);
+ }
+
+ /*
+ * Process any stdin-derived arguments for a lone "-" which we've loaded
+ * into LYStdinArgs. - FM
+ */
+ if (LYStdinArgs != NULL) {
+ char *my_args[2];
+ HTList *cur = LYStdinArgs;
+
+ my_args[1] = NULL;
+ while (NULL != (my_args[0] = (char *) HTList_nextObject(cur))) {
+ parse_arg(my_args, 4, (int *) 0);
+ }
+ LYStdinArgs_free();
+ }
+#ifdef HAVE_TTYNAME
+ /*
+ * If the input is not a tty, we are either running in cron, or are
+ * getting input via a pipe:
+ *
+ * a) in cron, none of stdin/stdout/stderr are tty's.
+ * b) from a pipe, we should have either "-" or "-stdin" options.
+ */
+ if (!LYGetStdinArgs
+ && !startfile_stdin
+ && !isatty(fileno(stdin))
+ && (isatty(fileno(stdout) || isatty(fileno(stderr))))) {
+ int ignored = 0;
+
+ while (fgetc(stdin) != EOF) {
+ ++ignored;
+ }
+ if (ignored) {
+ fprintf(stderr,
+ gettext("Ignored %d characters from standard input.\n"), ignored);
+ fprintf(stderr,
+ gettext("Use \"-stdin\" or \"-\" to tell how to handle piped input.\n"));
+ }
+ }
+#endif /* HAVE_TTYNAME */
+
+#ifdef CAN_SWITCH_DISPLAY_CHARSET
+ if (current_char_set == auto_display_charset) /* Better: explicit option */
+ switch_display_charsets = 1;
+#endif
+
+#if defined (TTY_DEVICE) || defined(HAVE_TTYNAME)
+ /*
+ * If we are told to read the startfile from standard input, do it now,
+ * after we have read all of the option data from standard input.
+ * Later we'll use LYReopenInput().
+ */
+ if (startfile_stdin) {
+ char result[LY_MAXPATH];
+ char *buf = NULL;
+
+ CTRACE((tfp, "processing stdin startfile\n"));
+ if ((fp = LYOpenTemp(result, HTML_SUFFIX, "w")) != 0) {
+ StrAllocCopy(startfile, result);
+ while (GetStdin(&buf, FALSE)) {
+ fputs(buf, fp);
+ fputc('\n', fp);
+ }
+ FREE(buf);
+ LYCloseTempFP(fp);
+ }
+ CTRACE((tfp, "...done stdin startfile\n"));
+ }
+#endif
+
+ /*
+ * Initialize other things based on the configuration read.
+ */
+
+#ifdef USE_PRETTYSRC
+ if ((!Old_DTD) != TRUE) /* skip if they are already initialized -HV */
+#endif
+ HTSwitchDTD(!Old_DTD);
+
+ /*
+ * Set up the proper character set with the desired
+ * startup raw 8-bit or CJK mode handling. - FM
+ */
+ HTMLUseCharacterSet(current_char_set);
+
+#ifdef USE_PERSISTENT_COOKIES
+ /*
+ * Sod it, this looks like a reasonable place to load the
+ * cookies file, probably. - RP
+ *
+ * And to set LYCookieSaveFile. - BJP
+ */
+ if (persistent_cookies) {
+ if (LYCookieFile == NULL) {
+ LYCookieFile = typeMallocn(char, LY_MAXPATH);
+
+ LYAddPathToHome(LYCookieFile, (size_t) LY_MAXPATH, FNAME_LYNX_COOKIES);
+ } else {
+ LYTildeExpand(&LYCookieFile, FALSE);
+ }
+ LYLoadCookies(LYCookieFile);
+ }
+
+ /* tilde-expand LYCookieSaveFile */
+ if (non_empty(LYCookieSaveFile)) {
+ LYTildeExpand(&LYCookieSaveFile, FALSE);
+ }
+#ifdef USE_PROGRAM_DIR
+ if (is_url(helpfile) == 0) {
+ char *tmp = NULL;
+
+ HTSprintf0(&tmp, "%s\\%s", program_dir, helpfile);
+ FREE(helpfile);
+ LYLocalFileToURL(&helpfile, tmp);
+ FREE(tmp);
+ }
+#endif
+
+ /*
+ * In dump_output_immediately mode, LYCookieSaveFile defaults to
+ * /dev/null, otherwise it defaults to LYCookieFile.
+ */
+
+ if (LYCookieSaveFile == NULL) {
+ if (dump_output_immediately) {
+ StrAllocCopy(LYCookieSaveFile, "/dev/null");
+ } else {
+ StrAllocCopy(LYCookieSaveFile, LYCookieFile);
+ }
+ }
+#endif
+
+ /*
+ * Check for a help file URL in the environment. Overriding
+ * compiled-in default and configuration file setting, if found.
+ */
+ if ((cp = LYGetEnv("LYNX_HELPFILE")) != NULL)
+ StrAllocCopy(helpfile, cp);
+
+ /*
+ * Set up our help and about file base paths. - FM
+ */
+ StrAllocCopy(helpfilepath, helpfile);
+ if ((cp = LYPathLeaf(helpfilepath)) != helpfilepath)
+ *cp = '\0';
+ LYAddHtmlSep(&helpfilepath);
+
+ /*
+ * Check for a save space path in the environment. If one was set in the
+ * configuration file, that one will be overridden. - FM
+ */
+ if ((cp = LYGetEnv("LYNX_SAVE_SPACE")) != NULL)
+ StrAllocCopy(lynx_save_space, cp);
+
+ /*
+ * We have a save space path, make sure it's valid. - FM
+ */
+ if (isEmpty(lynx_save_space)) {
+ FREE(lynx_save_space);
+ }
+ if (non_empty(lynx_save_space)) {
+ LYTildeExpand(&lynx_save_space, TRUE);
+#ifdef VMS
+ LYLowerCase(lynx_save_space);
+ if (StrChr(lynx_save_space, '/') != NULL) {
+ if (strlen(lynx_save_space) == 1) {
+ StrAllocCopy(lynx_save_space, "sys$login:");
+ } else {
+ LYAddPathSep(&lynx_save_space);
+ StrAllocCopy(temp, HTVMS_name("", lynx_save_space));
+ StrAllocCopy(lynx_save_space, temp);
+ FREE(temp);
+ }
+ }
+ if (StrChr(lynx_save_space, ':') == NULL &&
+ StrChr(lynx_save_space, ']') == NULL) {
+ StrAllocCat(lynx_save_space, ":");
+ }
+#else
+ LYAddPathSep(&lynx_save_space);
+#endif /* VMS */
+ }
+
+ /*
+ * Set up the file extension and mime type maps from src/HTInit.c and the
+ * global and personal mime.types and mailcap files. These will override
+ * any SUFFIX or VIEWER maps in userdefs.h or the configuration file, if
+ * they overlap.
+ */
+ HTFormatInit();
+ if (!FileInitAlreadyDone)
+ HTFileInit();
+
+ if (!LYCheckUserAgent()) {
+ HTAlwaysAlert(gettext("Warning:"), UA_NO_LYNX_WARNING);
+ }
+ if (show_cfg) {
+ cleanup();
+ exit_immediately(EXIT_SUCCESS);
+ }
+#ifdef USE_SLANG
+ if (LYShowColor >= SHOW_COLOR_ON &&
+ !(Lynx_Color_Flags & SL_LYNX_USE_COLOR)) {
+ Lynx_Color_Flags |= SL_LYNX_USE_COLOR;
+ } else if ((Lynx_Color_Flags & SL_LYNX_USE_COLOR) ||
+ LYGetEnv("COLORTERM") != NULL) {
+ if (LYShowColor != SHOW_COLOR_NEVER &&
+ LYShowColor != SHOW_COLOR_ALWAYS) {
+ LYShowColor = SHOW_COLOR_ON;
+ }
+ }
+#endif /* USE_SLANG */
+
+ if (LYPreparsedSource) {
+ HTPreparsedFormatInit();
+ }
+#if defined(EXEC_LINKS) || defined(EXEC_SCRIPTS)
+#ifdef NEVER_ALLOW_REMOTE_EXEC
+ if (local_exec) {
+ local_exec = FALSE;
+ local_exec_on_local_files = TRUE;
+ }
+#endif /* NEVER_ALLOW_REMOTE_EXEC */
+#endif /* EXEC_LINKS || EXEC_SCRIPTS */
+
+ if (emacs_keys)
+ set_emacs_keys();
+
+ if (vi_keys)
+ set_vi_keys();
+
+ if (no_numbers) {
+ number_links = FALSE;
+ number_fields = FALSE;
+ keypad_mode = NUMBERS_AS_ARROWS;
+ set_numbers_as_arrows();
+ }
+
+ if (crawl) {
+ /* No numbered links by default, as documented
+ in CRAWL.announce. - kw */
+ if (!number_links) {
+ keypad_mode = NUMBERS_AS_ARROWS;
+ }
+ }
+
+ if (!links_are_numbered()) {
+ if (number_fields)
+ keypad_mode = LINKS_AND_FIELDS_ARE_NUMBERED;
+ if (number_links)
+ keypad_mode = LINKS_ARE_NUMBERED;
+ set_numbers_as_arrows();
+ }
+
+ /*
+ * Check the -popup command line toggle. - FM
+ */
+ if (LYUseDefSelPop == FALSE) {
+ LYSelectPopups = (BOOLEAN) !LYSelectPopups;
+ }
+
+ /*
+ * Check the -show_cursor command line toggle. - FM
+ */
+ if (LYUseDefShoCur == FALSE) {
+ LYShowCursor = (BOOLEAN) !LYShowCursor;
+ }
+
+ /*
+ * Check the -base command line switch with -source. - FM
+ */
+ if (LYPrependBase && HTOutputFormat == WWW_DOWNLOAD) {
+ LYPrependBaseToSource = TRUE;
+ }
+
+ /*
+ * Disable multiple bookmark support if not interactive, so it doesn't
+ * crash on curses functions, or if the support was blocked via userdefs.h
+ * and/or lynx.cfg, or via command line restrictions. - FM
+ */
+ if (no_multibook)
+ LYMBMBlocked = TRUE;
+ if (dump_output_immediately || LYMBMBlocked || no_multibook) {
+ LYMultiBookmarks = MBM_OFF;
+ LYMBMBlocked = TRUE;
+ no_multibook = TRUE;
+ }
+#ifdef USE_SOURCE_CACHE
+ /*
+ * Disable source caching if not interactive.
+ */
+ if (dump_output_immediately)
+ LYCacheSource = SOURCE_CACHE_NONE;
+#endif
+#ifdef DISP_PARTIAL
+ /*
+ * Disable partial mode if not interactive.
+ */
+ if (dump_output_immediately)
+ display_partial_flag = FALSE;
+#endif
+
+#ifdef VMS
+ set_vms_keys();
+#endif /* VMS */
+
+#if defined (__DJGPP__)
+ if (watt_debug)
+ dbug_init();
+ sock_init();
+
+ __system_flags =
+ __system_emulate_chdir | /* handle `cd' internally */
+ __system_handle_null_commands | /* ignore cmds with no effect */
+ __system_allow_long_cmds | /* handle commands > 126 chars */
+ __system_use_shell | /* use $SHELL if set */
+ __system_allow_multiple_cmds | /* allow `cmd1; cmd2; ...' */
+ __system_redirect; /* redirect internally */
+
+ /* This speeds up stat() tremendously */
+ _djstat_flags |= _STAT_INODE | _STAT_EXEC_MAGIC | _STAT_DIRSIZE;
+#endif /* __DJGPP__ */
+
+ /* trap interrupts */
+#ifdef WIN32
+ SetConsoleCtrlHandler((PHANDLER_ROUTINE) cleanup_win32, TRUE);
+#endif
+
+#ifndef NOSIGHUP
+ if (!dump_output_immediately)
+ (void) signal(SIGHUP, cleanup_sig);
+#endif /* NOSIGHUP */
+
+ (void) signal(SIGTERM, cleanup_sig);
+#ifdef SIGWINCH
+ LYExtSignal(SIGWINCH, size_change);
+#endif /* SIGWINCH */
+#ifndef VMS
+ if (!TRACE && !dump_output_immediately && !stack_dump) {
+ (void) signal(SIGINT, cleanup_sig);
+#ifndef __linux__
+#ifdef SIGBUS
+ (void) signal(SIGBUS, FatalProblem);
+#endif /* SIGBUS */
+#endif /* !__linux__ */
+ (void) signal(SIGSEGV, FatalProblem);
+ (void) signal(SIGILL, FatalProblem);
+ /*
+ * Since we're doing lots of TCP, just ignore SIGPIPE altogether.
+ *
+ * HTTCP.c should deal with a broken pipe for servers. Rick Mallet's
+ * check after c = GetChar() in LYStrings.c should deal with a
+ * disconnected terminal. So the runaway CPU time problem on Unix
+ * should not occur any more.
+ */
+#ifdef SIGPIPE
+ if (signal(SIGPIPE, SIG_IGN) != SIG_IGN)
+ restore_sigpipe_for_children = TRUE;
+#endif /* SIGPIPE */
+ }
+#endif /* !VMS */
+
+#ifdef SIGTSTP
+ /*
+ * Block Control-Z suspending if requested. - FM
+ */
+ if (no_suspend)
+ (void) signal(SIGTSTP, SIG_IGN);
+#endif /* SIGTSTP */
+
+ /*
+ * Check for a valid HEAD request. - FM
+ */
+ if (HEAD_request && LYCanDoHEAD(startfile) != TRUE) {
+ fprintf(stderr,
+ "The '-head' switch is for http HEAD requests and cannot be used for\n'%s'.\n",
+ startfile);
+ exit_immediately(EXIT_FAILURE);
+ }
+
+ /*
+ * Check for a valid MIME headers request. - FM
+ */
+ if (keep_mime_headers && LYCanDoHEAD(startfile) != TRUE) {
+ fprintf(stderr,
+ "The '-mime_header' switch is for http URLs and cannot be used for\n'%s'.\n",
+ startfile);
+ exit_immediately(EXIT_FAILURE);
+ }
+
+ /*
+ * Check for a valid traversal request. - FM
+ */
+ if (traversal && StrNCmp(startfile, "http", 4)) {
+ fprintf(stderr,
+ "The '-traversal' switch is for http URLs and cannot be used for\n'%s'.\n",
+ startfile);
+ exit_immediately(EXIT_FAILURE);
+ }
+
+ /*
+ * Finish setting up for an INTERACTIVE session. Done here so that URL
+ * guessing in LYEnsureAbsoluteURL() can be interruptible (terminal is in
+ * raw mode, select() works). -BL
+ */
+#ifdef USE_PRETTYSRC
+ if (!dump_output_immediately) {
+ HTMLSRC_init_caches(FALSE); /* do it before terminal is initialized */
+ }
+#ifdef LY_FIND_LEAKS
+ atexit(html_src_clean_data);
+#endif
+#endif
+
+ if (!dump_output_immediately) {
+ setup(terminal);
+ }
+ /*
+ * If startfile is a file URL and the host is defaulted, force in
+ * "//localhost", and if it's not an absolute URL, make it one. - FM
+ */
+ LYEnsureAbsoluteURL(&startfile, "STARTFILE", FALSE);
+
+ /*
+ * If homepage was specified and is a file URL with the host defaulted,
+ * force in "//localhost", and if it's not an absolute URL, make it one. -
+ * FM
+ */
+ if (non_empty(homepage)) {
+ LYEnsureAbsoluteURL(&homepage, "HOMEPAGE", FALSE);
+ }
+
+ /*
+ * If we don't have a homepage specified, set it to startfile. Otherwise,
+ * reset LynxHome. - FM
+ */
+ if (isEmpty(homepage)) {
+ StrAllocCopy(homepage, startfile);
+ } else {
+ StrAllocCopy(LynxHome, homepage);
+ }
+
+ /*
+ * Set up the inside/outside domain restriction flags. - FM
+ */
+ if (inlocaldomain()) {
+#if !defined(HAVE_UTMP) || defined(VMS) /* not selective */
+ telnet_ok = (BOOL) (!no_inside_telnet && !no_outside_telnet && telnet_ok);
+#ifndef DISABLE_NEWS
+ news_ok = (BOOL) (!no_inside_news && !no_outside_news && news_ok);
+#endif
+ ftp_ok = (BOOL) (!no_inside_ftp && !no_outside_ftp && ftp_ok);
+ rlogin_ok = (BOOL) (!no_inside_rlogin && !no_outside_rlogin && rlogin_ok);
+#else
+ CTRACE((tfp, "LYMain: User in Local domain\n"));
+ telnet_ok = (BOOL) (!no_inside_telnet && telnet_ok);
+#ifndef DISABLE_NEWS
+ news_ok = (BOOL) (!no_inside_news && news_ok);
+#endif
+ ftp_ok = (BOOL) (!no_inside_ftp && ftp_ok);
+ rlogin_ok = (BOOL) (!no_inside_rlogin && rlogin_ok);
+#endif /* !HAVE_UTMP || VMS */
+ } else {
+ CTRACE((tfp, "LYMain: User in REMOTE domain\n"));
+ telnet_ok = (BOOL) (!no_outside_telnet && telnet_ok);
+#ifndef DISABLE_NEWS
+ news_ok = (BOOL) (!no_outside_news && news_ok);
+#endif
+ ftp_ok = (BOOL) (!no_outside_ftp && ftp_ok);
+ rlogin_ok = (BOOL) (!no_outside_rlogin && rlogin_ok);
+ }
+#ifdef DISABLE_FTP
+ ftp_ok = FALSE;
+#else
+ /* predefine some known broken ftp servers */
+ LYSetConfigValue(RC_BROKEN_FTP_RETR, "ProFTPD 1.2.5");
+ LYSetConfigValue(RC_BROKEN_FTP_RETR, "spftp/");
+ LYSetConfigValue(RC_BROKEN_FTP_EPSV, "(Version wu-2.6.2-12)");
+#endif
+
+ /*
+ * Make sure our bookmark default strings are all allocated and
+ * synchronized. - FM
+ */
+ if (isEmpty(bookmark_page)) {
+ temp = NULL;
+ HTSprintf0(&temp, "lynx_bookmarks%s", HTML_SUFFIX);
+ set_default_bookmark_page(temp);
+ FREE(temp);
+ }
+ if (isEmpty(BookmarkPage)) {
+ set_default_bookmark_page(bookmark_page);
+ }
+#if defined(SYSLOG_REQUESTED_URLS)
+ LYOpenlog(syslog_txt);
+#endif
+
+ if (non_empty(x_display)) {
+ LYisConfiguredForX = TRUE;
+ }
+
+ /*
+ * Here's where we do all the work.
+ */
+ if (dump_output_immediately) {
+ /*
+ * Finish setting up and start a NON-INTERACTIVE session. - FM
+ */
+ if (crawl && !number_links && !number_fields) {
+ keypad_mode = NUMBERS_AS_ARROWS;
+ } else if (no_numbers) {
+ keypad_mode = NUMBERS_AS_ARROWS;
+ } else if (!no_list) {
+ if (!links_are_numbered()) {
+ if (number_fields)
+ keypad_mode = LINKS_AND_FIELDS_ARE_NUMBERED;
+ else
+ keypad_mode = LINKS_ARE_NUMBERED;
+ }
+ }
+ if (dump_output_width > 0) {
+ LYcols = dump_output_width;
+ }
+ /*
+ * Normal argument processing puts non-options (URLs) into the Goto
+ * history. Use this to dump all of the pages listed on the command
+ * line, or (if none are listed) via the startfile mechanism.
+ * history.
+ */
+#ifdef EXTENDED_STARTFILE_RECALL
+ HTAddGotoURL(startfile);
+ for (i = HTList_count(Goto_URLs) - 1; i >= 0; --i) {
+ StrAllocCopy(startfile, (char *) HTList_objectAt(Goto_URLs, i));
+ CTRACE((tfp, "dumping %d:%d %s\n",
+ i + 1, HTList_count(Goto_URLs), startfile));
+ status = mainloop();
+ if (!no_list &&
+ !dump_links_inline &&
+ !crawl) /* For -crawl it has already been done! */
+ printlist(stdout, FALSE);
+ if (i != 0)
+ printf("\n");
+ }
+#else
+ status = mainloop();
+ if (!no_list &&
+ !dump_links_inline &&
+ !crawl && /* For -crawl it has already been done! */
+ links_are_numbered())
+ printlist(stdout, FALSE);
+#endif
+#ifdef USE_PERSISTENT_COOKIES
+ /*
+ * We want to save cookies picked up when in immediate dump mode.
+ * Instead of calling cleanup() here, let's only call this one. - BJP
+ */
+ if (persistent_cookies)
+ LYStoreCookies(LYCookieSaveFile);
+#endif /* USE_PERSISTENT_COOKIES */
+ exit_immediately(status);
+ } else {
+ /*
+ * Start an INTERACTIVE session. - FM
+ */
+#ifdef USE_COLOR_STYLE
+ cache_tag_styles();
+#endif
+
+#ifndef NO_DUMP_WITH_BACKSPACES
+ if (with_backspaces) {
+ /* we should warn about this somehow (nop for now) -VH */
+ with_backspaces = FALSE;
+ }
+#endif
+
+#ifndef ALL_CHARSETS_IN_O_MENU_SCREEN
+ init_charset_subsets();
+#endif
+
+ ena_csi((BOOLEAN) (LYlowest_eightbit[current_char_set] > 155));
+#ifdef USE_SESSIONS
+ RestoreSession();
+#endif /* USE_SESSIONS */
+ status = mainloop();
+ LYCloseCloset(RECALL_URL);
+ LYCloseCloset(RECALL_MAIL);
+#if defined(PDCURSES) && defined(PDC_BUILD) && PDC_BUILD >= 2401
+ if (!isendwin()) {
+ if ((saved_scrsize_x != 0) && (saved_scrsize_y != 0)) {
+ resize_term(saved_scrsize_y, saved_scrsize_x);
+ }
+ }
+#endif
+ cleanup();
+ exit_immediately(status);
+ }
+
+ return (status); /* though redundant, for compiler-warnings */
+}
+
+/*
+ * Called by HTAccessInit to register any protocols supported by lynx.
+ * Protocols added by lynx:
+ * LYNXKEYMAP, lynxcgi, LYNXIMGMAP, LYNXCOOKIE, LYNXCACHE, LYNXMESSAGES
+ */
+#ifdef GLOBALREF_IS_MACRO
+extern GLOBALREF (HTProtocol, LYLynxEditmap);
+extern GLOBALREF (HTProtocol, LYLynxKeymap);
+extern GLOBALREF (HTProtocol, LYLynxCGI);
+extern GLOBALREF (HTProtocol, LYLynxIMGmap);
+extern GLOBALREF (HTProtocol, LYLynxCookies);
+
+#ifdef USE_CACHEJAR
+extern GLOBALREF (HTProtocol, LYLynxCache);
+#endif
+extern GLOBALREF (HTProtocol, LYLynxStatusMessages);
+
+#else
+GLOBALREF HTProtocol LYLynxEditmap;
+GLOBALREF HTProtocol LYLynxKeymap;
+GLOBALREF HTProtocol LYLynxCGI;
+GLOBALREF HTProtocol LYLynxIMGmap;
+GLOBALREF HTProtocol LYLynxCookies;
+
+#ifdef USE_CACHEJAR
+GLOBALREF HTProtocol LYLynxCache;
+#endif
+GLOBALREF HTProtocol LYLynxStatusMessages;
+#endif /* GLOBALREF_IS_MACRO */
+
+void LYRegisterLynxProtocols(void)
+{
+ HTRegisterProtocol(&LYLynxEditmap);
+ HTRegisterProtocol(&LYLynxKeymap);
+ HTRegisterProtocol(&LYLynxCGI);
+ HTRegisterProtocol(&LYLynxIMGmap);
+ HTRegisterProtocol(&LYLynxCookies);
+#ifdef USE_CACHEJAR
+ HTRegisterProtocol(&LYLynxCache);
+#endif
+ HTRegisterProtocol(&LYLynxStatusMessages);
+}
+
+#ifndef NO_CONFIG_INFO
+/*
+ * Some stuff to reload lynx.cfg without restarting new lynx session, also load
+ * options menu items and command-line options to make things consistent.
+ *
+ * Called by user of interactive session by LYNXCFG://reload/ link.
+ *
+ * Warning: experimental, more main() reorganization required.
+ * *Known* exceptions: persistent cookies, cookie files.
+ *
+ * Some aspects of COLOR (with slang?).
+ * Viewer stuff, mailcap files
+ * SUFFIX, mime.types files
+ * RULESFILE/RULE
+ *
+ * All work "somewhat", but not exactly as the first time.
+ */
+void reload_read_cfg(void)
+{
+ char *tempfile;
+ FILE *rcfp;
+
+ /*
+ * no_option_save is always set for -anonymous and -validate. It is better
+ * to check for one or several specific restriction flags than for
+ * 'LYRestricted', which doesn't get set for individual restrictions or for
+ * -validate! However, no_option_save may not be the appropriate one to
+ * check - in that case, a new no_something should be added that gets
+ * automatically set for -anonymous and -validate (and whether it applies
+ * for -anonymous can be made installer- configurable in the usual way at
+ * the bottom of userdefs.h). - kw
+ *
+ */
+ if (no_option_save) {
+ /* current logic requires(?) that saving user preferences is
+ possible. Additional applicable restrictions are already
+ checked by caller. - kw */
+ return;
+ }
+
+ /*
+ * Current user preferences are saved in a temporary file, to be read in
+ * again after lynx.cfg has been read. This avoids accidental changing of
+ * the preferences file. The regular preferences file doesn't even need to
+ * exist, and won't be created as a side effect of this function. Honoring
+ * the no_option_save restriction may thus be unnecessarily restrictive,
+ * but the check is currently still left in place. - kw
+ */
+ tempfile = typecallocn(char, LY_MAXPATH);
+ if (!tempfile) {
+ HTAlwaysAlert(NULL, NOT_ENOUGH_MEMORY);
+ return;
+ }
+ rcfp = LYOpenTemp(tempfile, ".rc", "w");
+ if (rcfp == NULL) {
+ FREE(tempfile);
+ HTAlwaysAlert(NULL, CANNOT_OPEN_TEMP);
+ return;
+ }
+ if (!save_rc(rcfp)) {
+ HTAlwaysAlert(NULL, OPTIONS_NOT_SAVED);
+ (void) LYRemoveTemp(tempfile);
+ FREE(tempfile);
+ return; /* can not write the very own file :( */
+ }
+#ifdef USE_PERSISTENT_COOKIES
+ if (LYCookieFile != NULL && LYCookieSaveFile != NULL) {
+ /* set few safe flags: */
+ BOOLEAN persistent_cookies_flag = persistent_cookies;
+ char *LYCookieFile_flag = NULL;
+ char *LYCookieSaveFile_flag = NULL;
+
+ if (persistent_cookies) {
+ StrAllocCopy(LYCookieFile_flag, LYCookieFile);
+ StrAllocCopy(LYCookieSaveFile_flag, LYCookieSaveFile);
+ }
+#ifdef USE_CHARSET_CHOICE
+ custom_assumed_doc_charset = FALSE;
+ custom_display_charset = FALSE;
+ memset((char *) charset_subsets, 0, sizeof(charset_subset_t) * MAXCHARSETS);
+#endif
+
+#ifdef USE_PRETTYSRC
+ html_src_on_lynxcfg_reload();
+#endif
+ /* free downloaders, printers, environments, dired menu */
+ free_lynx_cfg();
+#ifdef USE_SOURCE_CACHE
+ source_cache_file_error = FALSE; /* reset flag */
+#endif
+
+ /*
+ * Process the configuration file.
+ */
+ read_cfg(lynx_cfg_file, "main program", 1, (FILE *) 0);
+
+ /*
+ * Process the temporary RC file.
+ */
+ rcfp = fopen(tempfile, "r");
+ read_rc(rcfp);
+ (void) LYRemoveTemp(tempfile);
+ FREE(tempfile); /* done with it - kw */
+
+#ifdef USE_CHARSET_CHOICE
+ init_charset_subsets();
+#endif
+
+ /*
+ * Initialize other things based on the configuration read.
+ */
+ LYSetDisplayLines();
+ /* Not implemented yet here,
+ * a major problem: file paths
+ * like lynx_save_space, LYCookieFile etc.
+ */
+ /* restore old settings */
+ if (persistent_cookies != persistent_cookies_flag) {
+ persistent_cookies = persistent_cookies_flag;
+ HTAlert(gettext("persistent cookies state will be changed in next session only."));
+ }
+ if (persistent_cookies && LYCookieFile_flag != NULL) {
+ if (strcmp(LYCookieFile, LYCookieFile_flag)) {
+ StrAllocCopy(LYCookieFile, LYCookieFile_flag);
+ CTRACE((tfp,
+ "cookie file can be changed in next session only, restored.\n"));
+ }
+ if (strcmp(LYCookieSaveFile, LYCookieSaveFile_flag)) {
+ StrAllocCopy(LYCookieSaveFile, LYCookieSaveFile_flag);
+ CTRACE((tfp,
+ "cookie save file can be changed in next session only, restored.\n"));
+ }
+ FREE(LYCookieFile_flag);
+ FREE(LYCookieSaveFile_flag);
+ }
+ }
+#endif /* USE_PERSISTENT_COOKIES */
+}
+#endif /* !NO_CONFIG_INFO */
+
+static void force_dump_mode(void)
+{
+ dump_output_immediately = TRUE;
+ no_pause = TRUE;
+ LYcols = DFT_COLS;
+}
+
+/* There are different ways of setting arguments on the command line, and
+ * there are different types of arguments. These include:
+ *
+ * -set_some_variable ==> some_variable = TRUE
+ * -toggle_some_variable ==> some_variable = !some_variable
+ * -some_variable=value ==> some_variable = value
+ *
+ * Others are complicated and require a function call.
+ */
+
+#define PARSE_SET(n,t,v,h) {n, t, UNION_SET(v), h}
+#define PARSE_INT(n,t,v,h) {n, t, UNION_INT(v), h}
+#define PARSE_STR(n,t,v,h) {n, t, UNION_STR(v), h}
+#define PARSE_FUN(n,t,v,h) {n, t, UNION_FUN(v), h}
+#define PARSE_NIL {NULL, 0, UNION_DEF(0), NULL}
+
+typedef struct parse_args_type {
+ const char *name;
+ int type;
+
+#define TOGGLE_ARG 0x0010
+#define SET_ARG 0x0020
+#define UNSET_ARG 0x0030
+#define FUNCTION_ARG 0x0040
+#define LYSTRING_ARG 0x0050
+#define INT_ARG 0x0060
+#define STRING_ARG 0x0070
+#define TIME_ARG 0x0080
+#define ARG_TYPE_MASK 0x0FF0
+#define NEED_NEXT_ARG 0x1000
+
+#define NEED_INT_ARG (NEED_NEXT_ARG | INT_ARG)
+#define NEED_TIME_ARG (NEED_NEXT_ARG | TIME_ARG)
+#define NEED_LYSTRING_ARG (NEED_NEXT_ARG | LYSTRING_ARG)
+#define NEED_STRING_ARG (NEED_NEXT_ARG | STRING_ARG)
+#define NEED_FUNCTION_ARG (NEED_NEXT_ARG | FUNCTION_ARG)
+
+ /* If the NEED_NEXT_ARG flags is set, and the option was not specified
+ * with an '=' character, then use the next argument in the argv list.
+ */
+
+ ParseData;
+ const char *help_string;
+} Config_Type;
+
+/* -auth, -pauth */
+static int parse_authentication(char *next_arg,
+ char **result)
+{
+ /*
+ * Authentication information for protected documents.
+ */
+ char *auth_info = NULL;
+
+ if (next_arg != NULL) {
+ StrAllocCopy(auth_info, next_arg);
+ memset(next_arg, ' ', strlen(next_arg)); /* Let's not show too much */
+ }
+
+ if (auth_info != NULL) {
+ char *cp;
+
+ if ((cp = StrChr(auth_info, ':')) != NULL) { /* Pw */
+ *cp++ = '\0'; /* Terminate ID */
+ HTUnEscape(cp);
+ StrAllocCopy(result[1], cp);
+ }
+ if (*auth_info) { /* Id */
+ HTUnEscape(auth_info);
+ StrAllocCopy(result[0], auth_info);
+ }
+ FREE(auth_info);
+ }
+ return 0;
+}
+
+/* -anonymous */
+static int anonymous_fun(char *next_arg GCC_UNUSED)
+{
+ if (!LYValidate && !LYRestricted)
+ parse_restrictions("default");
+ LYRestricted = TRUE;
+ return 0;
+}
+
+/* -assume_charset */
+static int assume_charset_fun(char *next_arg)
+{
+ assumed_charset = TRUE;
+ UCLYhndl_for_unspec = safeUCGetLYhndl_byMIME(next_arg);
+ StrAllocCopy(UCAssume_MIMEcharset,
+ LYCharSet_UC[UCLYhndl_for_unspec].MIMEname);
+ CTRACE((tfp, "assume_charset_fun %s ->%d ->%s\n",
+ NonNull(next_arg),
+ UCLYhndl_for_unspec,
+ UCAssume_MIMEcharset));
+ return 0;
+}
+
+/* -assume_local_charset */
+static int assume_local_charset_fun(char *next_arg)
+{
+ UCLYhndl_HTFile_for_unspec = safeUCGetLYhndl_byMIME(next_arg);
+ return 0;
+}
+
+/* -assume_unrec_charset */
+static int assume_unrec_charset_fun(char *next_arg)
+{
+ UCLYhndl_for_unrec = safeUCGetLYhndl_byMIME(next_arg);
+ return 0;
+}
+
+/* -auth */
+static int auth_fun(char *next_arg)
+{
+ parse_authentication(next_arg, authentication_info);
+ return 0;
+}
+
+/* -base */
+static int base_fun(char *next_arg GCC_UNUSED)
+{
+ /*
+ * Treat -source equivalently to an interactive download with
+ * LYPrefixBaseToSource configured to TRUE, so that a BASE tag is prepended
+ * for text/html content types. We normally treat the module-wide global
+ * LYPrefixBaseToSource flag as FALSE with -source, but force it TRUE,
+ * later, if LYPrependBase is set TRUE here. - FM
+ */
+ LYPrependBase = TRUE;
+ if (HTOutputFormat == WWW_DUMP)
+ HTOutputFormat = WWW_DOWNLOAD;
+
+ return 0;
+}
+
+/* -cache */
+static int cache_fun(char *next_arg)
+{
+ if (next_arg != 0)
+ HTCacheSize = atoi(next_arg);
+ /*
+ * Limit size.
+ */
+ if (HTCacheSize < 2)
+ HTCacheSize = 2;
+
+ return 0;
+}
+
+/* -child */
+static int child_fun(char *next_arg GCC_UNUSED)
+{
+ child_lynx = TRUE;
+ no_disk_save = TRUE;
+ no_mail = TRUE;
+ return 0;
+}
+
+/* -child_relaxed */
+static int child_relaxed_fun(char *next_arg GCC_UNUSED)
+{
+ child_lynx = TRUE;
+ return 0;
+}
+
+#ifdef USE_SLANG
+/* -color */
+static int color_fun(char *next_arg GCC_UNUSED)
+{
+ Lynx_Color_Flags |= SL_LYNX_USE_COLOR;
+
+ if (LYShowColor != SHOW_COLOR_ALWAYS)
+ LYShowColor = SHOW_COLOR_ON;
+
+ return 0;
+}
+#endif
+
+/* -convert_to */
+static int convert_to_fun(char *next_arg)
+{
+ if (next_arg != 0) {
+ char *outformat = NULL;
+ char *cp1, *cp2, *cp4;
+ int chndl;
+
+ StrAllocCopy(outformat, next_arg);
+ /* not lowercased, to allow for experimentation - kw */
+ /*LYLowerCase(outformat); */
+ if ((cp1 = StrChr(outformat, ';')) != NULL) {
+ if ((cp2 = LYstrstr(cp1, "charset")) != NULL) {
+ cp2 += 7;
+ while (*cp2 == ' ' || *cp2 == '=' || *cp2 == '"')
+ cp2++;
+ for (cp4 = cp2; (*cp4 != '\0' && *cp4 != '"' &&
+ *cp4 != ';' &&
+ !WHITE(*cp4)); cp4++) ; /* do nothing */
+ *cp4 = '\0';
+ /* This is intentionally not the "safe" version,
+ to allow for experimentation. */
+ chndl = UCGetLYhndl_byMIME(cp2);
+ if (chndl < 0)
+ chndl = UCLYhndl_for_unrec;
+ if (chndl < 0) {
+ fprintf(stderr,
+ gettext("Lynx: ignoring unrecognized charset=%s\n"), cp2);
+ } else {
+ current_char_set = chndl;
+ }
+ *cp1 = '\0'; /* truncate outformat */
+ }
+ }
+ HTOutputFormat = HTAtom_for(outformat);
+ FREE(outformat);
+ } else {
+ HTOutputFormat = NULL;
+ }
+ return 0;
+}
+
+/* -crawl */
+static int crawl_fun(char *next_arg GCC_UNUSED)
+{
+ crawl = TRUE;
+ LYcols = DFT_COLS;
+ return 0;
+}
+
+/* -display */
+static int display_fun(char *next_arg)
+{
+ if (next_arg != 0) {
+ LYsetXDisplay(next_arg);
+ }
+
+ return 0;
+}
+
+/* -display_charset */
+static int display_charset_fun(char *next_arg)
+{
+ int i = UCGetLYhndl_byMIME(next_arg);
+
+#ifdef CAN_AUTODETECT_DISPLAY_CHARSET
+ if (i < 0 && !strcasecomp(next_arg, "auto"))
+ i = auto_display_charset;
+#endif
+ if (i < 0) { /* do nothing here: so fallback to lynx.cfg */
+ fprintf(stderr,
+ gettext("Lynx: ignoring unrecognized charset=%s\n"), next_arg);
+ } else
+ current_char_set = i;
+ return 0;
+}
+
+/* -dump */
+static int dump_output_fun(char *next_arg GCC_UNUSED)
+{
+ force_dump_mode();
+ return 0;
+}
+
+/* -editor */
+static int editor_fun(char *next_arg)
+{
+ if (next_arg != 0)
+ StrAllocCopy(editor, next_arg);
+ system_editor = TRUE;
+ return 0;
+}
+
+/* -error_file */
+static int error_file_fun(char *next_arg)
+{
+ /*
+ * Output return (success/failure) code of an HTTP transaction.
+ */
+ if (next_arg != 0)
+ http_error_file = next_arg;
+ return 0;
+}
+
+#if defined(EXEC_LINKS) || defined(EXEC_SCRIPTS)
+/* -exec */
+static int exec_fun(char *next_arg GCC_UNUSED)
+{
+#ifndef NEVER_ALLOW_REMOTE_EXEC
+ local_exec = TRUE;
+#else
+ local_exec_on_local_files = TRUE;
+#endif /* NEVER_ALLOW_REMOTE_EXEC */
+ return 0;
+}
+#endif
+
+/* -get_data */
+static int get_data_fun(char *next_arg GCC_UNUSED)
+{
+ /*
+ * User data for GET form.
+ */
+ char **get_data;
+ char *buf = NULL;
+
+ /*
+ * On Unix, conflicts with curses when interactive so let's force a dump.
+ * -CL
+ *
+ * On VMS, mods have been made in LYCurses.c to deal with potential
+ * conflicts, so don't force the dump here. - FM
+ */
+#ifndef VMS
+ force_dump_mode();
+#endif /* VMS */
+
+ StrAllocCopy(form_get_data, "?"); /* Prime the pump */
+ get_data = &form_get_data;
+
+ /*
+ * Build GET data for later. Stop reading when we see a line with "---" as
+ * its first three characters.
+ */
+ while (GetStdin(&buf, TRUE)) {
+ StrAllocCat(*get_data, buf);
+ }
+
+ CTRACE((tfp, "get_data:%s\n", *get_data));
+ CTRACE((tfp, "get_data:%s\n", form_get_data));
+ return 0;
+}
+
+/* -help */
+static int help_fun(char *next_arg GCC_UNUSED)
+{
+ print_help_and_exit(0);
+ return 0;
+}
+
+/* -hiddenlinks */
+int hiddenlinks_fun(char *next_arg)
+{
+ /* *INDENT-OFF* */
+ static Config_Enum table[] = {
+ { "merge", HIDDENLINKS_MERGE },
+ { "listonly", HIDDENLINKS_SEPARATE },
+ { "ignore", HIDDENLINKS_IGNORE },
+ { NULL, -1 },
+ };
+ /* *INDENT-ON* */
+
+ if (next_arg != 0) {
+ if (!LYgetEnum(table, next_arg, &LYHiddenLinks))
+ print_help_and_exit(-1);
+ } else {
+ LYHiddenLinks = HIDDENLINKS_MERGE;
+ }
+
+ return 0;
+}
+
+/* -homepage */
+static int homepage_fun(char *next_arg)
+{
+ if (next_arg != 0) {
+ StrAllocCopy(homepage, next_arg);
+ LYEscapeStartfile(&homepage);
+ }
+ return 0;
+}
+
+/* -mime_header */
+static int mime_header_fun(char *next_arg GCC_UNUSED)
+{
+ /*
+ * Include mime headers and force source dump.
+ */
+ keep_mime_headers = TRUE;
+ force_dump_mode();
+ HTOutputFormat = (LYPrependBase ?
+ WWW_DOWNLOAD : WWW_DUMP);
+ LYcols = MAX_COLS;
+ return 0;
+}
+
+#ifndef DISABLE_NEWS
+/* -newschunksize */
+static int newschunksize_fun(char *next_arg)
+{
+ if (next_arg != 0) {
+ HTNewsChunkSize = atoi(next_arg);
+ /*
+ * If the new HTNewsChunkSize exceeds the maximum,
+ * increase HTNewsMaxChunk to this size. - FM
+ */
+ if (HTNewsChunkSize > HTNewsMaxChunk)
+ HTNewsMaxChunk = HTNewsChunkSize;
+ }
+ return 0;
+}
+
+/* -newsmaxchunk */
+static int newsmaxchunk_fun(char *next_arg)
+{
+ if (next_arg) {
+ HTNewsMaxChunk = atoi(next_arg);
+ /*
+ * If HTNewsChunkSize exceeds the new maximum,
+ * reduce HTNewsChunkSize to this maximum. - FM
+ */
+ if (HTNewsChunkSize > HTNewsMaxChunk)
+ HTNewsChunkSize = HTNewsMaxChunk;
+ }
+ return 0;
+}
+#endif /* not DISABLE_NEWS */
+
+/* -nobold */
+static int nobold_fun(char *next_arg GCC_UNUSED)
+{
+ LYnoVideo(1);
+ return 0;
+}
+
+/* -nobrowse */
+static int nobrowse_fun(char *next_arg GCC_UNUSED)
+{
+ HTDirAccess = HT_DIR_FORBID;
+ return 0;
+}
+
+/* -nocolor */
+static int nocolor_fun(char *next_arg GCC_UNUSED)
+{
+ LYShowColor = SHOW_COLOR_NEVER;
+#ifdef USE_SLANG
+ Lynx_Color_Flags &= ~(unsigned) SL_LYNX_USE_COLOR;
+ Lynx_Color_Flags |= SL_LYNX_OVERRIDE_COLOR;
+#endif
+ return 0;
+}
+
+/* -nopause */
+static int nopause_fun(char *next_arg GCC_UNUSED)
+{
+ no_pause = TRUE;
+ return 0;
+}
+
+/* -nomore */
+static int nomore_fun(char *next_arg GCC_UNUSED)
+{
+ nomore = TRUE;
+ return 0;
+}
+
+/* -noreverse */
+static int noreverse_fun(char *next_arg GCC_UNUSED)
+{
+ LYnoVideo(2);
+ return 0;
+}
+
+/* -nounderline */
+static int nounderline_fun(char *next_arg GCC_UNUSED)
+{
+ LYnoVideo(4);
+ return 0;
+}
+
+/* -nozap */
+static int nozap_fun(char *next_arg)
+{
+ LYNoZapKey = 1; /* everything but "initially" treated as "full" - kw */
+ if (next_arg != 0) {
+ if (strcasecomp(next_arg, "initially") == 0)
+ LYNoZapKey = 2;
+
+ }
+ return 0;
+}
+
+/* -pauth */
+static int pauth_fun(char *next_arg)
+{
+ parse_authentication(next_arg, proxyauth_info);
+ return 0;
+}
+
+/* -post_data */
+static int post_data_fun(char *next_arg GCC_UNUSED)
+{
+ /*
+ * User data for POST form.
+ */
+ char **post_data;
+ char *buf = NULL;
+
+ /*
+ * On Unix, conflicts with curses when interactive so let's force a dump.
+ * - CL
+ *
+ * On VMS, mods have been made in LYCurses.c to deal with potential
+ * conflicts, so don't force a dump here. - FM
+ */
+#ifndef VMS
+ force_dump_mode();
+#endif /* VMS */
+
+ post_data = &form_post_data;
+
+ /*
+ * Build post data for later. Stop reading when we see a line with "---"
+ * as its first three characters.
+ */
+ while (GetStdin(&buf, TRUE)) {
+ StrAllocCat(*post_data, buf);
+ }
+ return 0;
+}
+
+static const char *show_restriction(const char *name)
+{
+ const char *value = 0;
+
+ switch (find_restriction(name, -1)) {
+ case TRUE:
+ value = "on";
+ break;
+ case FALSE:
+ value = "off";
+ break;
+ default:
+ value = "?";
+ break;
+ }
+ return value;
+}
+
+/* -restrictions */
+static int restrictions_fun(char *next_arg)
+{
+ /* *INDENT-OFF* */
+ static const struct {
+ const char *name;
+ const char *help;
+ } table[] = {
+ { "all", "restricts all options." },
+ { "bookmark", "disallow changing the location of the bookmark file" },
+ { "bookmark_exec", "disallow execution links via the bookmark file" },
+#if defined(DIRED_SUPPORT) && defined(OK_PERMIT)
+ { "change_exec_perms", "\
+disallow changing the eXecute permission on files\n\
+(but still allow it for directories) when local file\n\
+management is enabled." },
+#endif /* DIRED_SUPPORT && OK_PERMIT */
+#ifdef SUPPORT_CHDIR
+ { "chdir", "\
+disallow changing the working directory of lynx, e.g.,\n\
+to affect the behavior of download command" },
+#endif
+#if defined(HAVE_CONFIG_H) && !defined(NO_CONFIG_INFO)
+ { "compileopts_info", "\
+disable info on options used to compile the binary" },
+#endif
+ { "default", "\
+same as commandline option -anonymous. Sets the\n\
+default service restrictions for anonymous users. Set to\n\
+all restricted, except for: inside_telnet, outside_telnet,\n\
+inside_ftp, outside_ftp, inside_rlogin, outside_rlogin,\n\
+inside_news, outside_news, telnet_port, jump, mail, print,\n\
+exec, and goto. The settings for these, as well as\n\
+additional goto restrictions for specific URL schemes\n\
+that are also applied, are derived from definitions\n\
+within userdefs.h." },
+#ifdef DIRED_SUPPORT
+ { "dired_support", "disallow local file management" },
+#endif /* DIRED_SUPPORT */
+ { "disk_save", "disallow saving to disk in the download and print menus" },
+ { "dotfiles", "disallow access to, or creation of, hidden (dot) files" },
+ { "download", "disallow some downloaders in the download menu" },
+ { "editor", "disallow editing" },
+ { "exec", "disable execution scripts" },
+ { "exec_frozen", "disallow the user from changing the execution link option" },
+#ifdef USE_EXTERNALS
+ { "externals", "disable passing URLs to some external programs" },
+#endif
+ { "file_url", "\
+disallow using G)oto, served links or bookmarks for\n\
+file: URL's" },
+ { "goto", "disable the 'g' (goto) command" },
+#if !defined(HAVE_UTMP) || defined(VMS) /* not selective */
+ { "inside_ftp", "\
+disallow ftps coming from inside your\n\
+domain (utmp required for selectivity)" },
+ { "inside_news", "\
+disallow USENET news reading and posting coming\n\
+from inside your domain (utmp required for selectivity)" },
+ { "inside_rlogin", "\
+disallow rlogins coming from inside your\n\
+domain (utmp required for selectivity)" },
+ { "inside_telnet", "\
+disallow telnets coming from inside your\n\
+domain (utmp required for selectivity)" },
+#else
+ { "inside_ftp", "\
+disallow ftps coming from inside your domain" },
+ { "inside_news", "\
+disallow USENET news reading and posting coming\n\
+from inside your domain" },
+ { "inside_rlogin", "\
+disallow rlogins coming from inside your domain" },
+ { "inside_telnet", "\
+disallow telnets coming from inside your domain" },
+#endif /* HAVE_UTMP || VMS */
+ { "jump", "disable the 'j' (jump) command" },
+ { "lynxcfg_info", "\
+disable viewing of lynx.cfg configuration file info" },
+#ifndef NO_CONFIG_INFO
+ { "lynxcfg_xinfo", "\
+disable extended lynx.cfg viewing and reloading" },
+#endif
+ { "lynxcgi", "\
+disallow execution of Lynx CGI URLs" },
+ { "mail", "disallow mail" },
+ { "multibook", "disallow multiple bookmark files" },
+ { "news_post", "disallow USENET News posting." },
+ { "option_save", "disallow saving options in .lynxrc" },
+#if !defined(HAVE_UTMP) || defined(VMS) /* not selective */
+ { "outside_ftp", "\
+disallow ftps coming from outside your\n\
+domain (utmp required for selectivity)" },
+ { "outside_news", "\
+disallow USENET news reading and posting coming\n\
+from outside your domain (utmp required for selectivity)" },
+ { "outside_rlogin", "\
+disallow rlogins coming from outside your\n\
+domain (utmp required for selectivity)" },
+ { "outside_telnet", "\
+disallow telnets coming from outside your\n\
+domain (utmp required for selectivity)" },
+#else
+ { "outside_ftp", "\
+disallow ftp coming from outside your domain" },
+ { "outside_news", "\
+disallow USENET news reading and posting coming\n\
+from outside your domain" },
+ { "outside_rlogin", "\
+disallow rlogins coming from outside your domain" },
+ { "outside_telnet", "\
+disallow telnets coming from outside your domain" },
+#endif /* !HAVE_UTMP || VMS */
+ { "print", "disallow most print options" },
+ { "shell", "\
+disallow shell escapes, and lynxexec, lynxprog or lynxcgi\n\
+G)oto's" },
+ { "suspend", "disallow Control-Z suspends with escape to shell" },
+ { "telnet_port", "disallow specifying a port in telnet G)oto's" },
+ { "useragent", "disallow modifications of the User-Agent header" },
+ };
+ /* *INDENT-ON* */
+
+ static const char *Usage[] =
+ {
+ ""
+ ,"USAGE: lynx -restrictions=[option][,option][,option]"
+ ,"List of Options:"
+ ," ? when used alone, list restrictions in effect."
+
+ };
+ unsigned j, k, column = 0;
+ const char *name;
+ const char *value;
+ BOOLEAN found, first;
+
+ if (isEmpty(next_arg)) {
+ SetOutputMode(O_TEXT);
+ for (j = 0; j < TABLESIZE(Usage); j++) {
+ printf("%s\n", Usage[j]);
+ }
+ for (j = 0; j < TABLESIZE(table); j++) {
+ if (!strcmp(table[j].name, "all")
+ || !strcmp(table[j].name, "default")) {
+ value = NULL;
+ } else {
+ value = show_restriction(table[j].name);
+ }
+ print_help_strings(table[j].name, table[j].help, value, FALSE);
+ }
+ first = TRUE;
+ for (j = 0; j < TABLESIZE(table); j++) {
+ found = FALSE;
+ if ((name = index_to_restriction(j)) == 0) {
+ break;
+ }
+ for (k = 0; k < TABLESIZE(table); k++) {
+ if (!strcmp(name, table[k].name)) {
+ found = TRUE;
+ }
+ }
+ if (!found) {
+ if (first) {
+ printf("Other restrictions (see the user's guide):\n");
+ }
+ value = show_restriction(table[j].name);
+ printf("%s%s (%s)", column ? ", " : " ", name, value);
+ column += (unsigned) (5 + strlen(name) + strlen(value));
+ if (column > 50) {
+ column = 0;
+ printf("\n");
+ }
+ first = FALSE;
+ }
+ }
+ if (column)
+ printf("\n");
+ SetOutputMode(O_BINARY);
+ exit_immediately(EXIT_SUCCESS);
+ } else if (*next_arg == '?') {
+ SetOutputMode(O_TEXT);
+ print_restrictions_to_fd(stdout);
+ SetOutputMode(O_BINARY);
+ exit_immediately(EXIT_SUCCESS);
+ } else {
+ parse_restrictions(next_arg);
+ }
+ return 0;
+}
+
+/* -selective */
+static int selective_fun(char *next_arg GCC_UNUSED)
+{
+ HTDirAccess = HT_DIR_SELECTIVE;
+ return 0;
+}
+
+/* -source */
+static int source_fun(char *next_arg GCC_UNUSED)
+{
+ force_dump_mode();
+ HTOutputFormat = (LYPrependBase ?
+ WWW_DOWNLOAD : WWW_DUMP);
+ LYcols = MAX_COLS;
+ return 0;
+}
+
+/* -traversal */
+static int traversal_fun(char *next_arg GCC_UNUSED)
+{
+ traversal = TRUE;
+#ifdef USE_SLANG
+ LYcols = DFT_COLS;
+#else
+ LYcols = MAX_COLS;
+#endif /* USE_SLANG */
+
+ return 0;
+}
+
+/* -version */
+static int version_fun(char *next_arg GCC_UNUSED)
+{
+ char *result = NULL;
+
+ SetLocale();
+ SetOutputMode(O_TEXT);
+
+ HTSprintf0(&result, gettext("%s Version %s (%s)"),
+ LYNX_NAME, LYNX_VERSION,
+ LYVersionDate());
+
+ StrAllocCat(result, "\n");
+#ifdef USE_SSL
+ HTSprintf(&result, "libwww-FM %s,", HTLibraryVersion);
+ append_ssl_version(&result, " ");
+#else
+ HTSprintf(&result, "libwww-FM %s", HTLibraryVersion);
+#endif /* USE_SSL */
+
+#if defined(NCURSES) && defined(HAVE_CURSES_VERSION)
+ HTSprintf(&result, ", %s", curses_version());
+#if defined(WIDEC_CURSES)
+ HTSprintf(&result, "(wide)");
+#endif
+#elif defined(PDCURSES) && defined(PDC_BUILD)
+ HTSprintf(&result, ", pdcurses %.3f", PDC_BUILD * 0.001);
+#elif defined(USE_SLANG) && defined(SLANG_VERSION_STRING)
+ HTSprintf(&result, ", s-lang %s", SLANG_VERSION_STRING);
+#endif
+
+ printf("%s\n", result);
+ free(result);
+
+/*
+ * Define NO_BUILDSTAMP if you really want an executable with no timestamp in
+ * the -version message.
+ */
+#ifdef NO_BUILDSTAMP
+#define BUILDSTAMP ""
+#else
+#define BUILDSTAMP " (" __DATE__ " " __TIME__ ")"
+#endif
+
+/*
+ * SYSTEM_NAME is set by the configure script. Show build date/time for other
+ * systems, according to predefined compiler symbols.
+ */
+#ifdef SYSTEM_NAME
+ printf(gettext("Built on %s%s.\n"), SYSTEM_NAME, BUILDSTAMP);
+#elif defined(__CYGWIN__)
+ printf("Compiled by CYGWIN%s.\n", BUILDSTAMP);
+#elif defined(__BORLANDC__)
+ printf("Compiled by Borland C++%s.\n", BUILDSTAMP);
+#elif defined(_MSC_VER)
+ printf("Compiled by Microsoft Visual C++%s.\n", BUILDSTAMP);
+#elif defined(__DJGPP__)
+ printf("Compiled by DJGPP%s.\n", BUILDSTAMP);
+#elif !defined(NO_BUILDSTAMP)
+ printf("Compiled at %s %s.\n", __DATE__, __TIME__);
+#endif
+
+ puts("");
+ puts(gettext("Copyrights held by the Lynx Developers Group,"));
+ puts(gettext("the University of Kansas, CERN, and other contributors."));
+ puts(gettext("Distributed under the GNU General Public License (Version 2)."));
+ puts(gettext("See https://lynx.invisible-island.net/ and the online help for more information."));
+ puts("");
+#ifdef USE_SSL
+#if defined(OPENSSL_VERSION_TEXT) && !defined(LIBGNUTLS_VERSION)
+ puts("See http://www.openssl.org/ for information about OpenSSL.");
+#endif /* OPENSSL_VERSION_TEXT */
+ puts("");
+#endif /* USE_SSL */
+
+ SetOutputMode(O_BINARY);
+
+ exit_immediately(EXIT_SUCCESS);
+ /* NOT REACHED */
+ return 0;
+}
+
+/* -width */
+static int width_fun(char *next_arg)
+{
+ if (next_arg != 0) {
+ int w = atoi(next_arg);
+
+ if (w > 0)
+ dump_output_width = ((w < MAX_COLS) ? w : MAX_COLS);
+ }
+
+ return 0;
+}
+
+#if defined(PDCURSES) && defined(PDC_BUILD) && PDC_BUILD >= 2401
+/* -scrsize */
+static int scrsize_fun(char *next_arg)
+{
+ if (next_arg != 0) {
+ char *cp;
+
+ if ((cp = StrChr(next_arg, ',')) != 0) {
+ *cp++ = '\0'; /* Terminate ID */
+ scrsize_x = atoi(next_arg);
+ scrsize_y = atoi(cp);
+ if ((scrsize_x <= 1) || (scrsize_y <= 1)) {
+ scrsize_x = scrsize_y = 0;
+ }
+ if ((scrsize_x > 0) && (scrsize_x < 40)) {
+ scrsize_x = 40;
+ }
+ if ((scrsize_y > 0) && (scrsize_y < 6)) {
+ scrsize_y = 6;
+ }
+ CTRACE((tfp, "scrsize: x=%d, y=%d\n", scrsize_x, scrsize_y));
+ }
+ }
+ return 0;
+}
+#endif
+
+/* NOTE: This table is sorted by name to make the help message useful */
+/* *INDENT-OFF* */
+static Config_Type Arg_Table [] =
+{
+ PARSE_SET(
+ "accept_all_cookies", 4|SET_ARG, LYAcceptAllCookies,
+ "\naccept cookies without prompting if Set-Cookie handling\nis on"
+ ),
+#if USE_BLAT_MAILER
+ PARSE_SET(
+ "altblat", 4|TOGGLE_ARG, mail_is_altblat,
+ "select mail tool (`"THIS_BLAT_MAIL"' ==> `"THAT_BLAT_MAIL"')"
+ ),
+#endif
+ PARSE_FUN(
+ "anonymous", 2|FUNCTION_ARG, anonymous_fun,
+ "apply restrictions for anonymous account,\nsee also -restrictions"
+ ),
+ PARSE_FUN(
+ "assume_charset", 4|NEED_FUNCTION_ARG, assume_charset_fun,
+ "=MIMEname\ncharset for documents that don't specify it"
+ ),
+ PARSE_FUN(
+ "assume_local_charset", 4|NEED_FUNCTION_ARG, assume_local_charset_fun,
+ "=MIMEname\ncharset assumed for local files"
+ ),
+ PARSE_FUN(
+ "assume_unrec_charset", 4|NEED_FUNCTION_ARG, assume_unrec_charset_fun,
+ "=MIMEname\nuse this instead of unrecognized charsets"
+ ),
+ PARSE_FUN(
+ "auth", 4|NEED_FUNCTION_ARG, auth_fun,
+ "=id:pw\nauthentication information for protected documents"
+ ),
+ PARSE_FUN(
+ "base", 4|FUNCTION_ARG, base_fun,
+ "prepend a request URL comment and BASE tag to " STR_HTML "\n\
+outputs for -source dumps"
+ ),
+#ifndef DISABLE_BIBP
+ PARSE_STR(
+ "bibhost", 4|NEED_LYSTRING_ARG, BibP_bibhost,
+ "=URL\nlocal bibp server (default http://bibhost/)"
+ ),
+#endif
+#ifdef USE_BLINK
+ PARSE_SET(
+ "blink", 4|SET_ARG, term_blink_is_boldbg,
+ "enable bright background via the BLINK terminal attribute"
+ ),
+#endif
+ PARSE_SET(
+ "book", 4|SET_ARG, bookmark_start,
+ "use the bookmark page as the startfile"
+ ),
+ PARSE_SET(
+ "buried_news", 4|TOGGLE_ARG, scan_for_buried_news_references,
+ "toggles scanning of news articles for buried references"
+ ),
+ PARSE_FUN(
+ "cache", 4|NEED_FUNCTION_ARG, cache_fun,
+ "=NUMBER\nNUMBER of documents cached in memory"
+ ),
+ PARSE_SET(
+ "case", 4|SET_ARG, LYcase_sensitive,
+ "enable case sensitive user searching"
+ ),
+ PARSE_SET(
+ "center", 4|TOGGLE_ARG, no_table_center,
+ "toggle center alignment in HTML TABLE"
+ ),
+ PARSE_STR(
+ "cfg", 2|NEED_LYSTRING_ARG, lynx_cfg_file,
+ "=FILENAME\nspecifies a lynx.cfg file other than the default"
+ ),
+ PARSE_FUN(
+ "child", 4|FUNCTION_ARG, child_fun,
+ "exit on left-arrow in startfile, and disable save to disk"
+ ),
+ PARSE_FUN(
+ "child_relaxed", 4|FUNCTION_ARG, child_relaxed_fun,
+ "exit on left-arrow in startfile (allows save to disk)"
+ ),
+#ifdef USE_CMD_LOGGING
+ PARSE_STR(
+ "cmd_log", 2|NEED_LYSTRING_ARG, lynx_cmd_logfile,
+ "=FILENAME\nlog keystroke commands to the given file"
+ ),
+ PARSE_STR(
+ "cmd_script", 2|NEED_LYSTRING_ARG, lynx_cmd_script,
+ "=FILENAME\nread keystroke commands from the given file\n(see -cmd_log)"
+ ),
+#endif
+ PARSE_SET(
+ "collapse_br_tags", 4|TOGGLE_ARG, LYCollapseBRs,
+ "toggles collapsing of BR tags"
+ ),
+#ifdef USE_SLANG
+ PARSE_FUN(
+ "color", 4|FUNCTION_ARG, color_fun,
+ "force color mode on with standard bg colors"
+ ),
+#endif
+ PARSE_INT(
+ "connect_timeout", 4|NEED_INT_ARG, connect_timeout,
+ "=N\nset the N-second connection timeout"
+ ),
+ PARSE_FUN(
+ "convert_to", 4|FUNCTION_ARG, convert_to_fun,
+ "=FORMAT\nconvert input, FORMAT is in MIME type notation\n(experimental)"
+ ),
+#ifdef USE_PERSISTENT_COOKIES
+ PARSE_STR(
+ "cookie_file", 4|LYSTRING_ARG, LYCookieFile,
+ "=FILENAME\nspecifies a file to use to read cookies"
+ ),
+ PARSE_STR(
+ "cookie_save_file", 4|LYSTRING_ARG, LYCookieSaveFile,
+ "=FILENAME\nspecifies a file to use to store cookies"
+ ),
+#endif /* USE_PERSISTENT_COOKIES */
+ PARSE_SET(
+ "cookies", 4|TOGGLE_ARG, LYSetCookies,
+ "toggles handling of Set-Cookie headers"
+ ),
+#ifndef VMS
+ PARSE_SET(
+ "core", 4|TOGGLE_ARG, LYNoCore,
+ "toggles forced core dumps on fatal errors"
+ ),
+#endif
+ PARSE_FUN(
+ "crawl", 4|FUNCTION_ARG, crawl_fun,
+ "with -traversal, output each page to a file\n\
+with -dump, format output as with -traversal, but to stdout"
+ ),
+#ifdef USE_CURSES_PADS
+ PARSE_SET(
+ "curses_pads", 4|TOGGLE_ARG, LYuseCursesPads,
+ "uses curses pad feature to support left/right shifting"
+ ),
+#endif
+#ifdef DISP_PARTIAL
+ PARSE_SET(
+ "debug_partial", 4|TOGGLE_ARG, debug_display_partial,
+ "incremental display stages with MessageSecs delay"
+ ),
+#endif
+#ifdef USE_DEFAULT_COLORS
+ PARSE_SET(
+ "default_colors", 4|TOGGLE_ARG, LYuse_default_colors,
+ "use terminal default foreground/background colors"
+ ),
+#endif
+ PARSE_INT(
+ "delay", 4|NEED_TIME_ARG, DelaySecs,
+ "=NNN\nset NNN-second delay at statusline message"
+ ),
+ PARSE_FUN(
+ "display", 4|NEED_FUNCTION_ARG, display_fun,
+ "=DISPLAY\nset the display variable for X exec'ed programs"
+ ),
+ PARSE_FUN(
+ "display_charset", 4|NEED_FUNCTION_ARG, display_charset_fun,
+ "=MIMEname\ncharset for the terminal output"
+ ),
+ PARSE_SET(
+ "dont_wrap_pre", 4|SET_ARG, dont_wrap_pre,
+ "inhibit wrapping of text in <pre> when -dump'ing and\n\
+-crawl'ing, mark wrapped lines in interactive session"
+ ),
+ PARSE_FUN(
+ "dump", 1|FUNCTION_ARG, dump_output_fun,
+ "dump the first file to stdout and exit"
+ ),
+ PARSE_FUN(
+ "editor", 4|NEED_FUNCTION_ARG, editor_fun,
+ "=EDITOR\nenable edit mode with specified editor"
+ ),
+ PARSE_SET(
+ "emacskeys", 4|SET_ARG, emacs_keys,
+ "enable emacs-like key movement"
+ ),
+ PARSE_SET(
+ "enable_scrollback", 4|TOGGLE_ARG, enable_scrollback,
+ "\ntoggles compatibility with comm programs' scrollback\n\
+keys (may be incompatible with some curses packages)"
+ ),
+ PARSE_FUN(
+ "error_file", 4|NEED_FUNCTION_ARG, error_file_fun,
+ "=FILE\nwrite the HTTP status code here"
+ ),
+#if defined(EXEC_LINKS) || defined(EXEC_SCRIPTS)
+#ifndef NEVER_ALLOW_REMOTE_EXEC
+ PARSE_FUN(
+ "exec", 4|FUNCTION_ARG, exec_fun,
+ "enable local program execution"
+ ),
+#endif
+#endif /* EXEC_LINKS || EXEC_SCRIPTS */
+#ifdef VMS
+ PARSE_SET(
+ "fileversions", 4|SET_ARG, HTVMSFileVersions,
+ "include all versions of files in local VMS directory\nlistings"
+ ),
+#endif
+#ifdef LY_FIND_LEAKS
+ PARSE_SET(
+ "find_leaks", 4|TOGGLE_ARG, LYfind_leaks,
+ "toggles memory-leak checking"
+ ),
+#endif
+ PARSE_SET(
+ "force_empty_hrefless_a", 4|SET_ARG, force_empty_hrefless_a,
+ "\nforce HREF-less 'A' elements to be empty (close them as\n\
+soon as they are seen)"
+ ),
+ PARSE_SET(
+ "force_html", 4|SET_ARG, LYforce_HTML_mode,
+ "forces the first document to be interpreted as HTML"
+ ),
+ PARSE_SET(
+ "force_secure", 4|TOGGLE_ARG, LYForceSSLCookiesSecure,
+ "toggles forcing of the secure flag for SSL cookies"
+ ),
+#if !defined(NO_OPTION_FORMS) && !defined(NO_OPTION_MENU)
+ PARSE_SET(
+ "forms_options", 4|TOGGLE_ARG, LYUseFormsOptions,
+ "toggles forms-based vs old-style options menu"
+ ),
+#endif
+ PARSE_SET(
+ "from", 4|TOGGLE_ARG, LYNoFromHeader,
+ "toggle transmission of From headers"
+ ),
+#ifndef DISABLE_FTP
+ PARSE_SET(
+ "ftp", 4|UNSET_ARG, ftp_ok,
+ "disable ftp access"
+ ),
+#endif
+ PARSE_FUN(
+ "get_data", 2|FUNCTION_ARG, get_data_fun,
+ "user data for get forms, read from stdin,\nterminated by '---' on a line"
+ ),
+ PARSE_SET(
+ "head", 4|SET_ARG, HEAD_request,
+ "send a HEAD request"
+ ),
+ PARSE_FUN(
+ "help", 4|FUNCTION_ARG, help_fun,
+ "print this usage message"
+ ),
+ PARSE_FUN(
+ "hiddenlinks", 4|NEED_FUNCTION_ARG, hiddenlinks_fun,
+ "=[option]\nhidden links: options are merge, listonly, or ignore"
+ ),
+ PARSE_SET(
+ "historical", 4|TOGGLE_ARG, historical_comments,
+ "toggles use of '>' or '-->' 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 <HTUtils.h>
+#include <HTAccess.h>
+#include <HTParse.h>
+#include <HTList.h>
+#include <HTML.h>
+#include <HTFTP.h>
+#include <HTFile.h>
+#include <HTTP.h>
+#include <HTAABrow.h>
+#include <HTNews.h>
+#include <LYCurses.h>
+#include <LYStyle.h>
+#include <LYGlobalDefs.h>
+#include <HTAlert.h>
+#include <LYUtils.h>
+#include <GridText.h>
+#include <LYStrings.h>
+#include <LYOptions.h>
+#include <LYSignal.h>
+#include <LYGetFile.h>
+#include <HTForms.h>
+#include <LYSearch.h>
+#include <LYClean.h>
+#include <LYHistory.h>
+#include <LYPrint.h>
+#include <LYMail.h>
+#include <LYEdit.h>
+#include <LYShowInfo.h>
+#include <LYBookmark.h>
+#include <LYKeymap.h>
+#include <LYJump.h>
+#include <LYDownload.h>
+#include <LYList.h>
+#include <LYMap.h>
+#include <LYTraversal.h>
+#include <LYCharSets.h>
+#include <LYCharUtils.h>
+#include <LYCookie.h>
+#include <LYMainLoop.h>
+#include <LYPrettySrc.h>
+
+#ifdef USE_SESSIONS
+#include <LYSession.h>
+#endif
+
+#ifdef KANJI_CODE_OVERRIDE
+#include <HTCJK.h>
+#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 <LYExtern.h>
+#endif
+
+#ifdef __EMX__
+#include <io.h>
+#endif
+
+#ifdef DIRED_SUPPORT
+#include <LYLocal.h>
+#include <LYUpload.h>
+#endif /* DIRED_SUPPORT */
+
+#include <LYexit.h>
+#include <LYLeaks.h>
+
+/* 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.
+ * <luotonen@dxcern.cern.ch>
+ *
+ * -- 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 + <key> was read (and not recognized as a
+ * terminal escape sequence for another key), ignore the
+ * ESC modifier and act on <key> 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 + <key> acts like
+ * <key>, 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 <HTUtils.h>
+#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 <HTUtils.h>
+#include <HTTP.h>
+#include <HTAnchor.h>
+#include <HTAccess.h>
+#include <HTFormat.h>
+#include <HTParse.h>
+#include <HTAlert.h>
+#include <LYUtils.h>
+#include <LYMap.h>
+#include <GridText.h>
+#include <LYGlobalDefs.h>
+#include <LYKeymap.h>
+#include <LYCharUtils.h>
+#include <LYCharSets.h>
+#include <LYStrings.h>
+
+#ifdef DIRED_SUPPORT
+#include <LYUpload.h>
+#include <LYLocal.h>
+#endif
+
+#include <LYexit.h>
+#include <LYLeaks.h>
+
+#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, "<html>\n<head>\n");
+ PUTS(buf);
+ HTSprintf0(&buf, "<META %s content=\"" STR_HTML ";charset=%s\">\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, "<title>%s</title>\n", MapTitle);
+ PUTS(buf);
+ HTSprintf0(&buf, "</head>\n<body>\n");
+ PUTS(buf);
+
+ HTSprintf0(&buf, "<h1><em>%s</em></h1>\n", MapTitle);
+ PUTS(buf);
+
+ StrAllocCopy(MapAddress, address);
+ LYEntify(&MapAddress, FALSE);
+ HTSprintf0(&buf, "<h2><em>MAP:</em>&nbsp;%s</h2>\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("<li><a href=\"");
+ PUTS(MapAddress);
+ PUTS("\"");
+ if (track_internal_links && tmp->intern_flag) {
+ PUTS(" TYPE=\"internal link\"");
+ }
+ PUTS("\n>");
+ LYformTitle(&MapTitle, tmp->title);
+ LYEntify(&MapTitle, TRUE);
+ PUTS(MapTitle);
+ PUTS("</a>\n");
+ }
+ HTSprintf0(&buf, "</%s>\n</body>\n</html>\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 <HTUtils.h>
+#endif
+
+#include <HTList.h>
+#include <HTAnchor.h>
+
+#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 <HTUtils.h>
+#ifndef DISABLE_NEWS
+#include <HTParse.h>
+#include <HTAccess.h>
+#include <HTCJK.h>
+#include <HTAlert.h>
+#include <LYCurses.h>
+#include <LYSignal.h>
+#include <LYStructs.h>
+#include <LYUtils.h>
+#include <LYClean.h>
+#include <LYStrings.h>
+#include <LYHistory.h>
+#include <GridText.h>
+#include <LYCharSets.h>
+#include <LYNews.h>
+#include <LYEdit.h>
+
+#include <LYGlobalDefs.h>
+
+#include <LYLeaks.h>
+
+/*
+ * 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 <LYStructs.h>
+#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 <HTUtils.h>
+#include <HTFTP.h>
+#include <HTTP.h> /* 'reloading' flag */
+#include <HTML.h>
+#include <LYCurses.h>
+#include <LYUtils.h>
+#include <LYStrings.h>
+#include <LYGlobalDefs.h>
+#include <LYHistory.h>
+#include <LYOptions.h>
+#include <LYSignal.h>
+#include <LYClean.h>
+#include <LYCharSets.h>
+#include <UCMap.h>
+#include <UCAux.h>
+#include <LYKeymap.h>
+#include <LYrcFile.h>
+#include <HTAlert.h>
+#include <LYBookmark.h>
+#include <GridText.h>
+#include <LYGetFile.h>
+#include <LYReadCFG.h>
+#include <LYPrettySrc.h>
+#include <HTFile.h>
+#include <LYCharUtils.h>
+
+#ifdef USE_COLOR_STYLE
+#include <LYStyle.h>
+#endif
+
+#include <LYLeaks.h>
+
+BOOLEAN term_options;
+
+#define TOP_LINK "/"
+#define MBM_LINK "//MBM_MENU"
+
+#define MARGIN_STR (no_margins ? "" : "&nbsp;&nbsp;")
+#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<em>%s</em>\n", MARGIN_STR, LYEntifyTitle(&buffer, Name));
+
+#define PutCheckBox(fp, Name, Value, disable) \
+ fprintf(fp,\
+ "<input type=\"checkbox\" name=\"%s\" %s %s>\n",\
+ Name, Value ? "checked" : "", disable_all?disabled_string:disable)
+
+#define PutTextInput(fp, Name, Value, Size, disable) \
+ fprintf(fp,\
+ "<input size=%d type=\"text\" name=\"%s\" value=\"%s\" %s>\n",\
+ (int) Size, Name, Value, disable_all?disabled_string:disable)
+
+#define PutOption(fp, flag, html, name) \
+ fprintf(fp,"<option value=\"%s\" %s>%s\n", html, SELECTED(flag), gettext(name))
+
+#define BeginSelect(fp, text) \
+ fprintf(fp,"<select name=\"%s\" %s>\n", text, disable_all?disabled_string:"")
+
+#define MaybeSelect(fp, flag, text) \
+ fprintf(fp,"<select name=\"%s\" %s>\n", text, disable_all?disabled_string:DISABLED(flag))
+
+#define EndSelect(fp)\
+ fprintf(fp,"</select>\n")
+
+static void PutOptValues(FILE *fp, int value,
+ OptValues * table)
+{
+ while (table->LongName != 0) {
+ if (table->HtmlName) {
+ PutOption(fp,
+ value == table->value,
+ table->HtmlName,
+ table->LongName);
+ }
+ table++;
+ }
+}
+
+static BOOLEAN GetOptValues(OptValues * table, char *value,
+ int *result)
+{
+ while (table->LongName != 0) {
+ if (table->HtmlName && !strcmp(value, table->HtmlName)) {
+ *result = table->value;
+ return TRUE;
+ }
+ table++;
+ }
+ return FALSE;
+}
+
+#ifdef USE_COLOR_STYLE
+#ifdef LY_FIND_LEAKS
+void free_colorstyle_leaks(void)
+{
+ FREE(color_style_values);
+}
+#endif
+
+void build_lss_enum(HTList *list)
+{
+ int count = HTList_count(list);
+
+#ifdef LY_FIND_LEAKS
+ atexit(free_colorstyle_leaks);
+#endif
+
+ FREE(color_style_values);
+ if (count != 0) {
+ LSS_NAMES *obj;
+ int position = 0;
+
+ color_style_values = typecallocn(OptValues, count + 2);
+
+ if (color_style_values == NULL)
+ outofmem(__FILE__, "build_lss_enum");
+
+ color_style_values[position++] = bool_values[0];
+ while ((obj = HTList_objectAt(list, position - 1)) != 0) {
+ color_style_values[position].value = position;
+ color_style_values[position].LongName = obj->given;
+ color_style_values[position].HtmlName = obj->given;
+ position++;
+ }
+ }
+ color_style_list = list;
+}
+
+/*
+ * Find the current lss-file in the list, to get the default value for the
+ * form.
+ */
+static int get_color_style_value(void)
+{
+ int result = 0;
+
+ if (LYuse_color_style && non_empty(lynx_lss_file)) {
+ LSS_NAMES *obj;
+ int position = 1;
+
+ while ((obj = HTList_objectAt(color_style_list, position - 1)) != 0) {
+ if (obj->actual != 0 && !strcmp(obj->actual, lynx_lss_file)) {
+ result = position;
+ break;
+ } else if (!strcmp(obj->given, lynx_lss_file)) {
+ result = position;
+ break;
+ }
+ ++position;
+ }
+ }
+ return result;
+}
+
+/*
+ * Return the pathname found in the given list-item.
+ */
+static char *get_color_style_config(int code)
+{
+ char *result = 0;
+
+ if (LYuse_color_style) {
+ LSS_NAMES *obj;
+
+ if ((obj = HTList_objectAt(color_style_list, code - 1)) != 0) {
+ result = obj->actual;
+ }
+ }
+ return result;
+}
+#endif
+
+/*
+ * Break cgi line into array of pairs of pointers. Don't bother trying to
+ * be efficient. We're not called all that often.
+ * We come in with a string looking like:
+ * tag1=value1&tag2=value2&...&tagN=valueN
+ * We leave with an array of post_pairs. The last element in the array
+ * will have a tag pointing to NULL.
+ * Not pretty, but works. Hey, if strings can be null terminate arrays...
+ */
+static PostPair *break_data(bstring *data)
+{
+ char *p;
+ PostPair *q = NULL;
+ int count = 0;
+
+ if (isBEmpty(data))
+ return NULL;
+
+ p = BStrData(data);
+ CTRACE((tfp, "break_data %s\n", p));
+
+ q = typecalloc(PostPair);
+ if (q == NULL)
+ outofmem(__FILE__, "break_data(calloc)");
+
+ do {
+ /*
+ * First, break up on '&', sliding 'p' on down the line.
+ */
+ q[count].value = LYstrsep(&p, "&");
+ /*
+ * Then break up on '=', sliding value down, and setting tag.
+ */
+ q[count].tag = LYstrsep(&(q[count].value), "=");
+
+ /*
+ * Clean them up a bit, in case user entered a funky string.
+ */
+ HTUnEscape(q[count].tag);
+
+ /* In the value field we have '+' instead of ' '. So do a simple
+ * find&replace on the value field before UnEscaping() - SKY
+ */
+ {
+ size_t i, len;
+
+ len = strlen(q[count].value);
+ for (i = 0; i < len; i++) {
+ if (q[count].value[i] == '+') {
+#ifdef UNIX
+ /*
+ * Allow for special case of options which begin with a "+" on
+ * Unix - TD
+ */
+ if (i > 0
+ && q[count].value[i + 1] == '+'
+ && isalnum(UCH(q[count].value[i + 2]))) {
+ q[count].value[i++] = ' ';
+ i++;
+ continue;
+ }
+#endif
+ q[count].value[i] = ' ';
+ }
+ }
+ }
+ HTUnEscape(q[count].value);
+ CTRACE((tfp, "...item[%d] tag=%s, value=%s\n",
+ count, q[count].tag, q[count].value));
+
+ count++;
+ /*
+ * Like I said, screw efficiency. Sides, realloc is fast on
+ * Linux ;->
+ */
+ q = typeRealloc(PostPair, q, (unsigned) (count + 1));
+ if (q == NULL)
+ outofmem(__FILE__, "break_data(realloc)");
+
+ q[count].tag = NULL;
+ } while (p != NULL && p[0] != '\0');
+ return q;
+}
+
+static BOOL isLynxOptionsPage(const char *address, const char *portion)
+{
+ BOOL result = FALSE;
+
+ if (!strncasecomp(address, STR_LYNXOPTIONS, LEN_LYNXOPTIONS)) {
+ unsigned len = (unsigned) strlen(portion);
+
+ address += LEN_LYNXOPTIONS;
+ if (!strncasecomp(address, portion, (int) len)
+ && (address[len] == '\0' || LYIsHtmlSep(address[len]))) {
+ result = TRUE;
+ }
+ }
+ return result;
+}
+
+static int gen_options(char **newfile);
+
+/*
+ * Handle options from the pseudo-post. I think we really only need
+ * post_data here, but bring along everything just in case. It's only a
+ * pointer. MRC
+ *
+ * Options are processed in order according to gen_options(), we should not
+ * depend on it and add boolean flags where the order is essential (save,
+ * character sets...)
+ *
+ * Security: some options are disabled in gen_options() under certain
+ * conditions. We *should* duplicate the same conditions here in postoptions()
+ * to prevent user with a limited access from editing HTML options code
+ * manually (e.g., doing 'e'dit in 'o'ptions) and submit it to access the
+ * restricted items. Prevent spoofing attempts from index overrun. - LP
+ *
+ * Exit status: NULLFILE (reload) or NORMAL (use HText cache).
+ *
+ * On exit, got the document which was current before the Options menu:
+ *
+ * (from cache) nothing changed or no visual effect supposed:
+ * editor name, e-mail, etc.
+ *
+ * (reload locally) to see the effect of certain changes:
+ * display_char_set, assume_charset, etc.
+ * (use 'need_reload' flag where necessary).
+ *
+ * (reload from remote server and uncache on a proxy)
+ * few options changes should be transferred to remote server:
+ * preferred language, fake browser name, etc.
+ * (use 'need_end_reload' flag).
+ */
+
+int postoptions(DocInfo *newdoc)
+{
+ PostPair *data = 0;
+ DocAddress WWWDoc; /* need on exit */
+ int i;
+ int code = 0;
+ BOOLEAN save_all = FALSE;
+ int display_char_set_old = current_char_set;
+ int old_media_value = LYAcceptMedia;
+ BOOLEAN raw_mode_old = LYRawMode;
+ BOOLEAN assume_char_set_changed = FALSE;
+ BOOLEAN need_reload = FALSE;
+ BOOLEAN need_end_reload = FALSE;
+
+#if defined(USE_SLANG) || defined(COLOR_CURSES)
+ int CurrentShowColor = LYShowColor;
+#endif
+#ifdef USE_DEFAULT_COLORS
+ BOOLEAN current_default_colors = LYuse_default_colors;
+#endif
+
+ /*-------------------------------------------------
+ * kludge a link from mbm_menu, the URL was:
+ * "<a href=\"" LYNXOPTIONS_PAGE(MBM_MENU) "\">Goto multi-bookmark menu</a>\n"
+ *--------------------------------------------------*/
+
+ if (isLynxOptionsPage(newdoc->address, MBM_LINK)) {
+ FREE(newdoc->post_data);
+ if (no_bookmark) {
+ HTAlert(BOOKMARK_CHANGE_DISALLOWED); /* anonymous */
+ return (NULLFILE);
+ } else if (dump_output_immediately) {
+ return (NOT_FOUND);
+ } else {
+ edit_bookmarks();
+ return (NULLFILE);
+ }
+ } else if (!isLynxOptionsPage(newdoc->address, "/")) {
+ HTAlert(RANDOM_URL_DISALLOWED);
+ return NULLFILE;
+ }
+
+ data = break_data(newdoc->post_data);
+
+ if (!data) {
+ int status;
+
+ /*-------------------------------------------------
+ * kludge gen_options() call:
+ *--------------------------------------------------*/
+ status = gen_options(&newdoc->address);
+ if (status != NORMAL) {
+ HTAlwaysAlert("Unexpected way of accessing", newdoc->address);
+ FREE(newdoc->address);
+ return (status);
+ }
+
+ /* exit to getfile() cycle */
+ 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;
+
+ if (!HTLoadAbsolute(&WWWDoc))
+ return (NOT_FOUND);
+ LYRegisterUIPage(newdoc->address, UIP_OPTIONS_MENU);
+#ifdef DIRED_SUPPORT
+ lynx_edit_mode = FALSE;
+#endif /* DIRED_SUPPORT */
+ return (NORMAL);
+ }
+
+ if (!LYIsUIPage3(HTLoadedDocumentURL(), UIP_OPTIONS_MENU, 0) &&
+ !LYIsUIPage3(HTLoadedDocumentURL(), UIP_VLINKS, 0)) {
+ char *buf = NULL;
+
+ /* We may have been spoofed? */
+ HTSprintf0(&buf,
+ gettext("Use %s to invoke the Options menu!"),
+ key_for_func_ext(LYK_OPTIONS, FOR_PANEL));
+ HTAlert(buf);
+ FREE(buf);
+ FREE(data);
+ return (NOT_FOUND);
+ }
+
+ /*
+ * Checkbox will be missing from data if unchecked.
+ */
+ LYSendUserAgent = FALSE;
+
+ for (i = 0; data[i].tag != NULL; i++) {
+ /*
+ * This isn't really for security, but rather for avoiding that the
+ * user may revisit an older instance from the history stack and submit
+ * stuff which accidentally undoes changes that had been done from a
+ * newer instance. - kw
+ */
+ if (!strcmp(data[i].tag, secure_string)) {
+ if (!secure_value || strcmp(data[i].value, secure_value)) {
+ char *buf = NULL;
+
+ /*
+ * We probably came from an older instance of the Options
+ * page that had been on the history stack. - kw
+ */
+ HTSprintf0(&buf,
+ gettext("Use %s to invoke the Options menu!"),
+ key_for_func_ext(LYK_OPTIONS, FOR_PANEL));
+ HTAlert(buf);
+ FREE(data);
+ return (NULLFILE);
+ }
+ FREE(secure_value);
+ }
+
+ /* Save options */
+ if (!strcmp(data[i].tag, save_options_string) && (!no_option_save)) {
+ save_all = TRUE;
+ }
+
+ /* Cookies: SELECT */
+ if (!strcmp(data[i].tag, cookies_string)) {
+ if (!strcmp(data[i].value, cookies_ignore_all_string)) {
+ LYSetCookies = FALSE;
+ } else if (!strcmp(data[i].value, cookies_up_to_user_string)) {
+ LYSetCookies = TRUE;
+ LYAcceptAllCookies = FALSE;
+ } else if (!strcmp(data[i].value, cookies_accept_all_string)) {
+ LYSetCookies = TRUE;
+ LYAcceptAllCookies = TRUE;
+ }
+ }
+
+ /* X Display: INPUT */
+ if (!strcmp(data[i].tag, x_display_string)) {
+ LYsetXDisplay(data[i].value);
+ validate_x_display();
+ summarize_x_display(data[i].value);
+ }
+
+ /* Editor: INPUT */
+ if (!strcmp(data[i].tag, editor_string)) {
+ FREE(editor);
+ StrAllocCopy(editor, data[i].value);
+ }
+
+ /* Emacs keys: ON/OFF */
+ if (!strcmp(data[i].tag, emacs_keys_string)
+ && GetOptValues(bool_values, data[i].value, &code)) {
+ if ((emacs_keys = (BOOLEAN) code) != FALSE) {
+ set_emacs_keys();
+ } else {
+ reset_emacs_keys();
+ }
+ }
+
+ /* Execution links: SELECT */
+#if defined(ENABLE_OPTS_CHANGE_EXEC) && (defined(EXEC_LINKS) || defined(EXEC_SCRIPTS))
+ if (!strcmp(data[i].tag, exec_links_string)
+ && GetOptValues(exec_links_values, data[i].value, &code)) {
+#ifndef NEVER_ALLOW_REMOTE_EXEC
+ local_exec = (BOOLEAN) (code == EXEC_ALWAYS);
+#endif /* !NEVER_ALLOW_REMOTE_EXEC */
+ local_exec_on_local_files = (BOOLEAN) (code == EXEC_LOCAL);
+ }
+#endif /* ENABLE_OPTS_CHANGE_EXEC */
+
+ /* Keypad Mode: SELECT */
+ if (!strcmp(data[i].tag, keypad_mode_string)) {
+ int newval = 0;
+
+ if (GetOptValues(keypad_mode_values, data[i].value, &newval)
+ && keypad_mode != newval) {
+ keypad_mode = newval;
+ need_reload = TRUE;
+ if (keypad_mode == NUMBERS_AS_ARROWS) {
+ set_numbers_as_arrows();
+ } else {
+ reset_numbers_as_arrows();
+ }
+ }
+ }
+
+ /* Line edit style: SELECT */
+ if (!strcmp(data[i].tag, lineedit_mode_string)) {
+ int newval = atoi(data[i].value);
+ int j;
+
+ /* prevent spoofing attempt */
+ for (j = 0; LYEditorNames[j]; j++) {
+ if (j == newval)
+ current_lineedit = newval;
+ }
+ }
+#ifdef EXP_KEYBOARD_LAYOUT
+ /* Keyboard layout: SELECT */
+ if (!strcmp(data[i].tag, kblayout_string)) {
+ int newval = atoi(data[i].value);
+ int j;
+
+ /* prevent spoofing attempt */
+ for (j = 0; LYKbLayoutNames[j]; j++) {
+ if (j == newval)
+ current_layout = newval;
+ }
+ }
+#endif /* EXP_KEYBOARD_LAYOUT */
+
+ /* Mail Address: INPUT */
+ if (!strcmp(data[i].tag, mail_address_string)) {
+ FREE(personal_mail_address);
+ StrAllocCopy(personal_mail_address, data[i].value);
+ }
+#ifndef NO_ANONYMOUS_EMAIL
+ /* Personal Name: INPUT */
+ if (!strcmp(data[i].tag, personal_name_string)) {
+ FREE(personal_mail_name);
+ StrAllocCopy(personal_mail_name, data[i].value);
+ }
+#endif
+
+ /* Anonymous FTP Password: INPUT */
+#ifndef DISABLE_FTP
+ if (!strcmp(data[i].tag, anonftp_password_string)) {
+ FREE(anonftp_password);
+ StrAllocCopy(anonftp_password, data[i].value);
+ }
+#endif
+
+ /* Search Type: SELECT */
+ if (!strcmp(data[i].tag, search_type_string)
+ && GetOptValues(search_type_values, data[i].value, &code)) {
+ LYcase_sensitive = (BOOLEAN) code;
+ }
+
+ /* HTML error tolerance: SELECT */
+ if (!strcmp(data[i].tag, DTD_recovery_string)
+ && GetOptValues(DTD_type_values, data[i].value, &code)) {
+ if (Old_DTD != code) {
+ Old_DTD = code;
+ HTSwitchDTD(!Old_DTD);
+ need_reload = TRUE;
+ }
+ }
+
+ /* Bad HTML warnings: SELECT */
+ if (!strcmp(data[i].tag, bad_html_string)
+ && GetOptValues(bad_html_values, data[i].value, &code)) {
+ cfg_bad_html = code;
+ }
+
+ /* Select Popups: ON/OFF */
+ if (!strcmp(data[i].tag, select_popups_string)
+ && GetOptValues(bool_values, data[i].value, &code)) {
+ LYSelectPopups = (BOOLEAN) code;
+ }
+#if defined(USE_SLANG) || defined(COLOR_CURSES)
+ /* Show Color: SELECT */
+ if (!strcmp(data[i].tag, show_color_string)
+ && GetOptValues(show_color_values, data[i].value,
+ &LYChosenShowColor)) {
+ if (can_do_colors)
+ LYShowColor = LYChosenShowColor;
+ if (CurrentShowColor != LYShowColor) {
+ lynx_force_repaint();
+ }
+#ifdef USE_SLANG
+ SLtt_Use_Ansi_Colors = (LYShowColor > SHOW_COLOR_OFF ? TRUE : FALSE);
+#endif
+ }
+#endif /* USE_SLANG || COLOR_CURSES */
+
+#ifdef USE_COLOR_STYLE
+ /* Color Style: ON/OFF */
+ if (!strcmp(data[i].tag, color_style_string)
+ && GetOptValues(color_style_values, data[i].value, &code)) {
+ if (code) {
+ LYuse_color_style = TRUE;
+ StrAllocCopy(lynx_lss_file, get_color_style_config(code));
+ reinit_color_styles();
+ } else {
+ LYuse_color_style = FALSE;
+ }
+ update_color_style();
+ lynx_force_repaint();
+ }
+#endif
+
+#ifdef USE_DEFAULT_COLORS
+ /* Default Colors: ON/OFF */
+ if (!strcmp(data[i].tag, default_colors_string)
+ && GetOptValues(bool_values, data[i].value, &code)) {
+ LYuse_default_colors = (BOOLEAN) code;
+ if (current_default_colors != LYuse_default_colors) {
+ CTRACE((tfp, "default_colors changed, updating colors...\n"));
+ if (has_colors()) {
+ if (LYuse_default_colors) {
+ use_default_colors();
+ } else {
+ restart_curses();
+ }
+ update_default_colors();
+ lynx_force_repaint();
+ }
+ }
+ }
+#endif
+
+ /* Show Cursor: ON/OFF */
+ if (!strcmp(data[i].tag, show_cursor_string)
+ && GetOptValues(bool_values, data[i].value, &code)) {
+ LYShowCursor = (BOOLEAN) code;
+ }
+
+ /* Underline links: ON/OFF */
+ if (!strcmp(data[i].tag, underline_links_string)
+ && GetOptValues(bool_values, data[i].value, &code)) {
+ LYUnderlineLinks = (BOOLEAN) code;
+ }
+#ifdef USE_SCROLLBAR
+ /* Show Scrollbar: ON/OFF */
+ if (!strcmp(data[i].tag, show_scrollbar_string)
+ && GetOptValues(bool_values, data[i].value, &code)) {
+ LYShowScrollbar = (BOOLEAN) code;
+ need_reload = TRUE;
+ }
+#endif
+
+ /* Cookie Version: SELECT */
+ if (!strcmp(data[i].tag, cookie_version_string))
+ GetOptValues(cookies_values, data[i].value, &cookie_version);
+
+ /* Cookie Prompting: SELECT */
+ if (!strcmp(data[i].tag, cookie_prompt_string))
+ GetOptValues(prompt_values, data[i].value, &cookie_noprompt);
+
+#ifdef USE_SSL
+ /* SSL Prompting: SELECT */
+ if (!strcmp(data[i].tag, ssl_prompt_string))
+ GetOptValues(prompt_values, data[i].value, &ssl_noprompt);
+#endif
+
+ /* User Mode: SELECT */
+ if (!strcmp(data[i].tag, user_mode_string)
+ && GetOptValues(user_mode_values, data[i].value, &user_mode)) {
+ LYSetDisplayLines();
+ }
+
+ /* Type of visited pages page: SELECT */
+ if (!strcmp(data[i].tag, visited_links_string))
+ GetOptValues(visited_links_values, data[i].value, &Visited_Links_As);
+
+ /* Show Images: SELECT */
+ if (!strcmp(data[i].tag, images_string)) {
+ if (!strcmp(data[i].value, images_ignore_all_string)
+ && !(pseudo_inline_alts == FALSE && clickable_images == FALSE)) {
+ pseudo_inline_alts = FALSE;
+ clickable_images = FALSE;
+ need_reload = TRUE;
+ } else if (!strcmp(data[i].value, images_use_label_string)
+ && !(pseudo_inline_alts == TRUE && clickable_images == FALSE)) {
+ pseudo_inline_alts = TRUE;
+ clickable_images = FALSE;
+ need_reload = TRUE;
+ } else if (!strcmp(data[i].value, images_use_links_string)
+ && !(clickable_images == TRUE)) {
+ clickable_images = TRUE;
+ need_reload = TRUE;
+ }
+ }
+
+ /* Verbose Images: ON/OFF */
+ if (!strcmp(data[i].tag, verbose_images_string)
+ && GetOptValues(verbose_images_type_values, data[i].value, &code)) {
+ if (verbose_img != code) {
+ verbose_img = (BOOLEAN) code;
+ need_reload = TRUE;
+ }
+ }
+
+ /* Collapse BR Tags: ON/OFF */
+ if (!strcmp(data[i].tag, collapse_br_tags_string)
+ && GetOptValues(collapse_br_tags_values, data[i].value, &code)) {
+ if (LYCollapseBRs != code) {
+ LYCollapseBRs = (BOOLEAN) code;
+ need_reload = TRUE;
+ }
+ }
+
+ /* Trim Blank Lines: ON/OFF */
+ if (!strcmp(data[i].tag, trim_blank_lines_string)
+ && GetOptValues(trim_blank_lines_values, data[i].value, &code)) {
+ if (LYtrimBlankLines != code) {
+ LYtrimBlankLines = (BOOLEAN) code;
+ need_reload = TRUE;
+ }
+ }
+
+ /* VI Keys: ON/OFF */
+ if (!strcmp(data[i].tag, vi_keys_string)
+ && GetOptValues(bool_values, data[i].value, &code)) {
+ if ((vi_keys = (BOOLEAN) code) != FALSE) {
+ set_vi_keys();
+ } else {
+ reset_vi_keys();
+ }
+ }
+
+ /* Bookmarks File Menu: SELECT */
+ if (!strcmp(data[i].tag, mbm_string) && (!LYMBMBlocked)) {
+ GetOptValues(mbm_values, data[i].value, &LYMultiBookmarks);
+ }
+
+ /* Default Bookmarks filename: INPUT */
+ if (!strcmp(data[i].tag, single_bookmark_string) && (!no_bookmark)) {
+ if (strcmp(data[i].value, "")) {
+ FREE(bookmark_page);
+ StrAllocCopy(bookmark_page, data[i].value);
+ }
+ }
+#ifdef USE_SESSIONS
+ /* Auto Session: ON/OFF */
+ if (!strcmp(data[i].tag, auto_session_string)
+ && GetOptValues(bool_values, data[i].value, &code)) {
+ LYAutoSession = (BOOLEAN) code;
+ }
+
+ /* Default Session filename: INPUT */
+ if (!strcmp(data[i].tag, single_session_string)) {
+ if (strcmp(data[i].value, "")) {
+ FREE(LYSessionFile);
+ StrAllocCopy(LYSessionFile, data[i].value);
+ }
+ }
+#endif
+
+ /* Assume Character Set: SELECT */
+ if (!strcmp(data[i].tag, assume_char_set_string)) {
+ int newval = UCGetLYhndl_byMIME(data[i].value);
+
+ if (newval >= 0
+ && ((raw_mode_old &&
+ newval != safeUCGetLYhndl_byMIME(UCAssume_MIMEcharset))
+ || (!raw_mode_old &&
+ newval != UCLYhndl_for_unspec)
+ )) {
+
+ UCLYhndl_for_unspec = newval;
+ StrAllocCopy(UCAssume_MIMEcharset, data[i].value);
+ assume_char_set_changed = TRUE;
+ }
+ }
+#ifdef USE_LOCALE_CHARSET
+ /* Use locale-based character set: ON/OFF */
+ if (!strcmp(data[i].tag, locale_charset_string)
+ && GetOptValues(bool_values, data[i].value, &code)) {
+ LYLocaleCharset = (BOOLEAN) code;
+ }
+#endif
+ /* Use HTML5 charset replacements: ON/OFF */
+ if (!strcmp(data[i].tag, html5_charsets_string)
+ && GetOptValues(bool_values, data[i].value, &code)) {
+ html5_charsets = (BOOLEAN) code;
+ assume_char_set_changed = TRUE;
+ }
+
+ /* Display Character Set: SELECT */
+ if (!strcmp(data[i].tag, display_char_set_string)) {
+ int newval = atoi(data[i].value);
+ int j;
+
+ /* prevent spoofing attempt */
+ for (j = 0; LYchar_set_names[j]; j++) {
+ if (j == newval)
+ current_char_set = newval;
+ }
+ }
+#ifdef USE_IDN2
+ /* Internationalized Domain Names: SELECT */
+ if (!strcmp(data[i].tag, idna_mode_string)
+ && GetOptValues(idna_values, data[i].value, &code)) {
+ LYidnaMode = code;
+ }
+#endif
+
+ /* Raw Mode: ON/OFF */
+ if (!strcmp(data[i].tag, raw_mode_string)
+ && GetOptValues(bool_values, data[i].value, &code)) {
+ LYRawMode = (BOOLEAN) code;
+ }
+#ifndef DISABLE_FTP
+ /*
+ * passive ftp: ON/OFF
+ */
+ if (!strcmp(data[i].tag, passive_ftp_string)) {
+ ftp_passive = (BOOLEAN) code;
+ }
+
+ /*
+ * ftp sort: SELECT
+ */
+ if (!strcmp(data[i].tag, ftp_sort_string)) {
+ GetOptValues(ftp_sort_values, data[i].value, &HTfileSortMethod);
+ }
+#endif /* DISABLE_FTP */
+
+#ifdef DIRED_SUPPORT
+ /* Local Directory Style: SELECT */
+ if (!strcmp(data[i].tag, dired_list_string)) {
+ GetOptValues(dired_list_values, data[i].value, &dir_list_style);
+ }
+#ifdef LONG_LIST
+ /* Local Directory Order: SELECT */
+ if (!strcmp(data[i].tag, dired_sort_string)) {
+ GetOptValues(dired_sort_values, data[i].value, &dir_list_order);
+ }
+#endif /* LONG_LIST */
+#endif /* DIRED_SUPPORT */
+
+ /* Show dot files: ON/OFF */
+ if (!strcmp(data[i].tag, show_dotfiles_string) && (!no_dotfiles)
+ && GetOptValues(bool_values, data[i].value, &code)) {
+ show_dotfiles = (BOOLEAN) code;
+ }
+
+ /* Pause when showing messages: ON/OFF */
+ if (!strcmp(data[i].tag, no_pause_string)
+ && GetOptValues(bool_values, data[i].value, &code)) {
+ no_pause = (BOOLEAN) !code;
+ }
+#ifdef USE_READPROGRESS
+ /* Show Transfer Rate: enumerated value */
+ if (!strcmp(data[i].tag, show_rate_string)
+ && GetOptValues(rate_values, data[i].value, &code)) {
+ LYTransferRate = code;
+ }
+#endif /* USE_READPROGRESS */
+
+ /* Preferred Content Type: SELECT */
+ if (!strcmp(data[i].tag, preferred_content_string)) {
+ GetOptValues(content_values, data[i].value, &LYContentType);
+ }
+
+ /* Preferred Media Type: SELECT */
+ if (!strcmp(data[i].tag, preferred_media_string)) {
+ GetOptValues(media_values, data[i].value, &LYAcceptMedia);
+ }
+
+ /* Preferred Encoding: SELECT */
+ if (!strcmp(data[i].tag, preferred_encoding_string)) {
+ GetOptValues(encoding_values, data[i].value, &LYAcceptEncoding);
+ }
+
+ /* Preferred Document Character Set: INPUT */
+ if (!strcmp(data[i].tag, preferred_doc_char_string)) {
+ if (strcmp(pref_charset, data[i].value)) {
+ FREE(pref_charset);
+ StrAllocCopy(pref_charset, data[i].value);
+ need_end_reload = TRUE;
+ }
+ }
+
+ /* Preferred Document Language: INPUT */
+ if (!strcmp(data[i].tag, preferred_doc_lang_string)) {
+ if (strcmp(language, data[i].value)) {
+ FREE(language);
+ StrAllocCopy(language, data[i].value);
+ need_end_reload = TRUE;
+ }
+ }
+
+ /*
+ * HTTP protocol: SELECT
+ */
+ if (!strcmp(data[i].tag, http_protocol_string)) {
+ GetOptValues(http_protocol_values, data[i].value, &HTprotocolLevel);
+ }
+
+ /* Send User Agent: INPUT */
+ if (!strcmp(data[i].tag, send_user_agent_string)) {
+ LYSendUserAgent = (BOOLEAN) !strcasecomp(data[i].value, "ON");
+ }
+
+ if (!strcmp(data[i].tag, ssl_client_certificate_file)) {
+ FREE(SSL_client_cert_file);
+ StrAllocCopy(SSL_client_cert_file, data[i].value);
+ }
+
+ if (!strcmp(data[i].tag, ssl_client_key_file)) {
+ FREE(SSL_client_key_file);
+ StrAllocCopy(SSL_client_key_file, data[i].value);
+ }
+
+ /* User Agent: INPUT */
+ if (!strcmp(data[i].tag, user_agent_string) && (!no_useragent)) {
+ if (strcmp(LYUserAgent, data[i].value)) {
+ need_end_reload = TRUE;
+ FREE(LYUserAgent);
+ /* ignore Copyright warning ? */
+ StrAllocCopy(LYUserAgent,
+ *(data[i].value)
+ ? data[i].value
+ : LYUserAgentDefault);
+ if (!LYCheckUserAgent()) {
+ HTAlert(UA_PLEASE_USE_LYNX);
+ }
+ }
+ }
+ } /* end of loop */
+
+ /*
+ * Process the flags:
+ */
+#ifdef USE_LOCALE_CHARSET
+ LYFindLocaleCharset();
+#endif
+
+ if (old_media_value != LYAcceptMedia)
+ HTFilterPresentations();
+
+ if (display_char_set_old != current_char_set ||
+ raw_mode_old != LYRawMode ||
+ assume_char_set_changed) {
+ /*
+ * charset settings: the order is essential here.
+ */
+ if (display_char_set_old != current_char_set) {
+ /*
+ * Set the LYUseDefaultRawMode value and character handling if
+ * LYRawMode was changed. - FM
+ */
+ LYUseDefaultRawMode = TRUE;
+ HTMLUseCharacterSet(current_char_set);
+#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
+ }
+ if (assume_char_set_changed && HTCJK != JAPANESE) {
+ LYRawMode = (BOOLEAN) (UCLYhndl_for_unspec == current_char_set);
+ }
+ if (raw_mode_old != LYRawMode || assume_char_set_changed) {
+ /*
+ * Set the raw 8-bit or CJK mode defaults and character set if
+ * changed. - FM
+ */
+ HTMLSetUseDefaultRawMode(current_char_set, LYRawMode);
+ HTMLSetCharacterHandling(current_char_set);
+ }
+ need_reload = TRUE;
+ }
+ /* end of charset settings */
+
+ BStrFree(newdoc->post_data);
+ FREE(data);
+ if (save_all) {
+ HTInfoMsg(SAVING_OPTIONS);
+ LYrcShowColor = LYChosenShowColor;
+ if (save_rc(NULL)) {
+ HTInfoMsg(OPTIONS_SAVED);
+ } else {
+ HTAlert(OPTIONS_NOT_SAVED);
+ }
+ }
+
+ /*
+ * Exit: working around the previous document. Being out of
+ * mainloop()/getfile() cycle, do things manually.
+ */
+ CTRACE((tfp, "\nLYOptions.c/postoptions(): exiting...\n"));
+ CTRACE((tfp, " need_reload = %s\n",
+ need_reload ? "TRUE" : "FALSE"));
+ CTRACE((tfp, " need_end_reload = %s\n",
+ need_end_reload ? "TRUE" : "FALSE"));
+
+ /* Options menu was pushed before postoptions(), so pop-up. */
+ LYpop(newdoc);
+ 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;
+ LYforce_no_cache = FALSE; /* ! */
+ LYoverride_no_cache = TRUE; /* ! */
+ /*
+ * Working out of getfile() cycle we reset *no_cache manually here so
+ * HTLoadAbsolute() will return "Document already in memory": it was
+ * forced reloading Options Menu again without this (overhead).
+ *
+ * Probably *no_cache was set in a wrong position because of
+ * the internal page...
+ */
+ if (!HTLoadAbsolute(&WWWDoc))
+ return (NOT_FOUND);
+
+ HTuncache_current_document(); /* will never use again */
+
+ /*
+ * Return to previous doc, not to options menu! Reload the document we had
+ * before the options menu but uncache only when necessary (Hurrah, user!):
+ */
+ LYpop(newdoc);
+ 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;
+ LYforce_no_cache = FALSE; /* see below */
+ LYoverride_no_cache = TRUE; /* see below */
+ /*
+ * Re-setting of *no_cache is probably not required here but this is a
+ * guarantee against _double_ reloading over the net in case prev document
+ * has its own "no cache" attribute and options menu set "need_reload"
+ * also. Force this HTLoadAbsolute() to return "Document already in
+ * memory".
+ */
+ if (!HTLoadAbsolute(&WWWDoc))
+ return (NOT_FOUND);
+
+ /*
+ * Now most interesting part: reload document when necessary.
+ * **********************************************************
+ */
+
+ reloading = FALSE; /* set manually */
+ /* force end-to-end reload from remote server if change LYUserAgent or
+ * language or pref_charset (marked by need_end_reload flag above), from
+ * old-style LYK_OPTIONS (mainloop):
+ */
+ if ((need_end_reload == TRUE &&
+ (StrNCmp(newdoc->address, "http", 4) == 0 ||
+ isLYNXCGI(newdoc->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; /* global flag */
+ need_reload = TRUE; /* this was probably already TRUE, don't worry */
+ }
+
+ if (need_reload == FALSE) {
+ /* no uncache, already loaded */
+ CTRACE((tfp, "LYOptions.c/postoptions(): now really exit.\n\n"));
+ return (NORMAL);
+ } else {
+ /* update HText cache */
+
+ /*
+ * see LYK_RELOAD & LYK_OPTIONS in mainloop for details...
+ */
+ if (HTisDocumentSource()) {
+ srcmode_for_next_retrieval(1);
+ }
+#ifdef USE_SOURCE_CACHE
+ if (reloading == FALSE) {
+ /* one more attempt to be smart enough: */
+ if (HTcan_reparse_document()) {
+ if (!HTreparse_document())
+ srcmode_for_next_retrieval(0);
+ CTRACE((tfp, "LYOptions.c/postoptions(): now really exit.\n\n"));
+ return (NORMAL);
+ }
+ }
+#endif
+ if (newdoc->post_data != NULL && !newdoc->safe &&
+ confirm_post_resub(newdoc->address, newdoc->title, 2, 1) == FALSE) {
+ HTInfoMsg(WILL_NOT_RELOAD_DOC);
+ if (HTisDocumentSource()) {
+ srcmode_for_next_retrieval(0);
+ }
+ return (NORMAL);
+ }
+
+ HEAD_request = HTLoadedDocumentIsHEAD();
+ /* uncache and load again */
+ HTuncache_current_document();
+ LYpush(newdoc, FALSE);
+ CTRACE((tfp, "LYOptions.c/postoptions(): now really exit.\n\n"));
+ return (NULLFILE);
+ }
+
+ /******** Done! **************************************************/
+}
+
+static char *NewSecureValue(void)
+{
+ static char oops[] = "?";
+
+ FREE(secure_value);
+ if ((secure_value = typeMallocn(char, 80)) != 0) {
+#if defined(RAND_MAX)
+ long key = (long) lynx_rand();
+
+#else
+ long key = (long) secure_value + (long) time(0);
+#endif
+ sprintf(secure_value, "%ld", key);
+ return secure_value;
+ }
+ return oops;
+}
+
+#define LABEL_LEN 33
+
+/*
+ * Note: the 'value' we are passing here is a local copy of the "same" string
+ * as is used in LYrcFile.c to index the saveable options.
+ */
+static void PutLabel(FILE *fp, const char *name,
+ const char *value)
+{
+ int have = (int) strlen(name);
+ int want = LABEL_LEN;
+ int need = LYstrExtent(name, have, want);
+ char *buffer = NULL;
+
+ fprintf(fp, "%s%s", MARGIN_STR, LYEntifyTitle(&buffer, NonNull(name)));
+
+ if (will_save_rc(value) && !no_option_save) {
+ while (need++ < want)
+ fprintf(fp, "&nbsp;");
+ } else {
+ want -= 3;
+ if (need < want) {
+ fprintf(fp, "&nbsp;");
+ ++need;
+ }
+ fprintf(fp, "(!)");
+ while (need++ < want) {
+ fprintf(fp, "&nbsp;");
+ }
+ }
+ fprintf(fp, ": ");
+ FREE(buffer);
+}
+
+/*
+ * For given a list of the .lynxrc names for boolean flags that make up a
+ * composite setting, check if any are not writable for the .lynxrc file. If
+ * so, return that name, so the subsequence will_save_rc() check in PutLabel()
+ * will flag the composite as not-saved.
+ */
+static const char *check_if_write_lynxrc(STRING2PTR table)
+{
+ int n;
+ const char *result = NULL;
+
+ for (n = 0; table[n] != 0; ++n) {
+ result = table[n];
+ if (!will_save_rc(result))
+ break;
+ }
+ return result;
+}
+
+/*
+ * The options menu treats "Cookies" as a single enumeration, but it is read
+ * from lynx.cfg (and perhaps .lynxrc) as a set of booleans. Check if any are
+ * not writable to .lynxrc, so we can show the user.
+ */
+static const char *will_save_cookies(void)
+{
+ static const char *table[] =
+ {
+ RC_SET_COOKIES, /* LYSetCookies */
+ RC_ACCEPT_ALL_COOKIES, /* LYAcceptAllCookies */
+ NULL
+ };
+
+ return check_if_write_lynxrc(table);
+}
+
+/*
+ * The options menu treats "Show images" as a single enumeration, but it is
+ * read from lynx.cfg (and perhaps .lynxrc) as a set of booleans. Check if any
+ * are not writable to .lynxrc, so we can show the user.
+ */
+static const char *will_save_images(void)
+{
+ static const char *table[] =
+ {
+ RC_MAKE_PSEUDO_ALTS_FOR_INLINES, /* pseudo_inline_alts */
+ RC_MAKE_LINKS_FOR_ALL_IMAGES, /* clickable_images */
+ NULL
+ };
+
+ return check_if_write_lynxrc(table);
+}
+
+/*
+ * The visited-links menu is used from the visited-links page as well as the
+ * options page.
+ */
+void LYMenuVisitedLinks(FILE *fp0, int disable_all)
+{
+ BeginSelect(fp0, visited_links_string);
+ PutOptValues(fp0, Visited_Links_As, visited_links_values);
+ EndSelect(fp0);
+}
+
+/*
+ * Okay, someone wants to change options. So, let's gen up a form for them
+ * and pass it around. Gor, this is ugly. Be a lot easier in Bourne with
+ * "here" documents. :->
+ * Basic Strategy: For each option, throw up the appropriate type of
+ * control, giving defaults as appropriate. If nothing else, we're
+ * probably going to test every control there is. MRC
+ *
+ * This function is synchronized with postoptions(). Read the comments in
+ * postoptions() header if you change something in gen_options().
+ */
+static int gen_options(char **newfile)
+{
+ static char tempfile[LY_MAXPATH] = "\0";
+
+ int i;
+ char *buffer = NULL;
+ BOOLEAN disable_all = FALSE;
+ FILE *fp0;
+ size_t cset_len = 0;
+ size_t text_len = (size_t) ((LYcolLimit > 45)
+ ? LYcolLimit - (LABEL_LEN + 2 + MARGIN_LEN)
+ : 7); /* cf: PutLabel */
+
+ if ((fp0 = InternalPageFP(tempfile, TRUE)) == 0)
+ return (NOT_FOUND);
+
+ LYLocalFileToURL(newfile, tempfile);
+
+ /* This should not be needed if we regenerate the temp file every time with
+ * a new name, which just happened above in the case
+ * LYReuseTempfiles==FALSE. Even for LYReuseTempfiles=TRUE, code at the
+ * end of postoptions() may remove an older cached version from memory if
+ * that version of the page was left by submitting changes. - kw
+ * 1999-11-27
+ * If access to the actual file via getfile() later fails (maybe because of
+ * some restrictions), mainloop may leave this flag on after popping the
+ * previous doc which is then unnecessarily reloaded. But I changed
+ * mainloop to reset the flag. - kw 1999-05-24
+ */
+ LYforce_no_cache = TRUE;
+
+ /*
+ * Without LYUseFormsOptions set we should maybe not even get here.
+ * However, it's possible we do; disable the form in that case. - kw
+ */
+#ifndef NO_OPTION_MENU
+ if (!LYUseFormsOptions)
+ disable_all = TRUE;
+#endif
+
+ BeginInternalPage(fp0, OPTIONS_TITLE, NULL); /* help link below */
+
+ /*
+ * I do C, not HTML. Feel free to pretty this up.
+ */
+ fprintf(fp0, "<form action=\"%s\" method=\"post\">\n", STR_LYNXOPTIONS);
+ /*
+ * use following with some sort of one shot secret key akin to NCSA
+ * (or was it CUTE?) telnet one shot password to allow ftp to self.
+ * to prevent spoofing.
+ */
+ fprintf(fp0, "<input name=\"%s\" type=\"hidden\" value=\"%s\">\n",
+ secure_string, NewSecureValue());
+
+ /*
+ * visible text begins here
+ */
+
+ /* Submit/Reset/Help */
+ fprintf(fp0, "<p align=center>\n");
+ if (!disable_all) {
+ fprintf(fp0,
+ "<input type=\"submit\" value=\"%s\"> - \n",
+ LYEntifyValue(&buffer, ACCEPT_CHANGES));
+ fprintf(fp0,
+ "<input type=\"reset\" value=\"%s\"> - \n",
+ LYEntifyValue(&buffer, RESET_CHANGES));
+ fprintf(fp0,
+ "%s - \n",
+ LYEntifyTitle(&buffer, CANCEL_CHANGES));
+ }
+ fprintf(fp0, "<a href=\"%s%s\">%s</a>\n",
+ helpfilepath, LYEntifyTitle(&buffer, OPTIONS_HELP), TO_HELP);
+
+ /* Save options */
+ if (!no_option_save) {
+ if (!disable_all) {
+ fprintf(fp0, "<p align=center>%s: ", LYEntifyTitle(&buffer, SAVE_OPTIONS));
+ fprintf(fp0, "<input type=\"checkbox\" name=\"%s\">\n",
+ save_options_string);
+ }
+ fprintf(fp0, "<br>%s\n",
+ LYEntifyTitle(&buffer,
+ gettext("(options marked with (!) will not be saved)")));
+ }
+
+ /*
+ * preformatted text follows
+ */
+ fprintf(fp0, "<pre>\n");
+
+ PutHeader(fp0, gettext("General Preferences"));
+ /*****************************************************************/
+
+ /* User Mode: SELECT */
+ PutLabel(fp0, gettext("User mode"), user_mode_string);
+ BeginSelect(fp0, user_mode_string);
+ PutOptValues(fp0, user_mode, user_mode_values);
+ EndSelect(fp0);
+
+ /* Editor: INPUT */
+ PutLabel(fp0, gettext("Editor"), editor_string);
+ PutTextInput(fp0, editor_string, NonNull(editor), text_len,
+ DISABLED(no_editor || system_editor));
+
+ /* Search Type: SELECT */
+ PutLabel(fp0, gettext("Type of Search"), search_type_string);
+ BeginSelect(fp0, search_type_string);
+ PutOptValues(fp0, LYcase_sensitive, search_type_values);
+ EndSelect(fp0);
+
+ PutHeader(fp0, gettext("Security and Privacy"));
+ /*****************************************************************/
+
+ /* Cookies: SELECT */
+ PutLabel(fp0, gettext("Cookies"), will_save_cookies());
+ BeginSelect(fp0, cookies_string);
+ PutOption(fp0, !LYSetCookies,
+ cookies_ignore_all_string,
+ cookies_ignore_all_string);
+ PutOption(fp0, LYSetCookies && !LYAcceptAllCookies,
+ cookies_up_to_user_string,
+ cookies_up_to_user_string);
+ PutOption(fp0, LYSetCookies && LYAcceptAllCookies,
+ cookies_accept_all_string,
+ cookies_accept_all_string);
+ EndSelect(fp0);
+
+ /* Cookie Version: SELECT */
+ PutLabel(fp0, gettext("Cookie RFC-version"), cookie_version_string);
+ BeginSelect(fp0, cookie_version_string);
+ PutOptValues(fp0, cookie_version, cookies_values);
+ EndSelect(fp0);
+
+ /* Cookie Prompting: SELECT */
+ PutLabel(fp0, gettext("Invalid-Cookie Prompting"), cookie_prompt_string);
+ BeginSelect(fp0, cookie_prompt_string);
+ PutOptValues(fp0, cookie_noprompt, prompt_values);
+ EndSelect(fp0);
+
+#ifdef USE_SSL
+ /* SSL Prompting: SELECT */
+ PutLabel(fp0, gettext("SSL Prompting"), ssl_prompt_string);
+ BeginSelect(fp0, ssl_prompt_string);
+ PutOptValues(fp0, ssl_noprompt, prompt_values);
+ EndSelect(fp0);
+
+ PutLabel(fp0, gettext("SSL client certificate file"), ssl_client_certificate_file);
+ PutTextInput(fp0, ssl_client_certificate_file,
+ NonNull(SSL_client_cert_file), text_len, "");
+
+ PutLabel(fp0, gettext("SSL client key file"), ssl_client_key_file);
+ PutTextInput(fp0, ssl_client_key_file,
+ NonNull(SSL_client_key_file), text_len, "");
+
+#endif
+
+ PutHeader(fp0, gettext("Keyboard Input"));
+ /*****************************************************************/
+
+ /* Keypad Mode: SELECT */
+ PutLabel(fp0, gettext("Keypad mode"), keypad_mode_string);
+ BeginSelect(fp0, keypad_mode_string);
+ PutOptValues(fp0, keypad_mode, keypad_mode_values);
+ EndSelect(fp0);
+
+ /* Emacs keys: ON/OFF */
+ PutLabel(fp0, gettext("Emacs keys"), emacs_keys_string);
+ BeginSelect(fp0, emacs_keys_string);
+ PutOptValues(fp0, emacs_keys, bool_values);
+ EndSelect(fp0);
+
+ /* VI Keys: ON/OFF */
+ PutLabel(fp0, gettext("VI keys"), vi_keys_string);
+ BeginSelect(fp0, vi_keys_string);
+ PutOptValues(fp0, vi_keys, bool_values);
+ EndSelect(fp0);
+
+ /* Line edit style: SELECT */
+ if (LYEditorNames[1]) { /* well, at least 2 line edit styles available */
+ PutLabel(fp0, gettext("Line edit style"), lineedit_mode_string);
+ BeginSelect(fp0, lineedit_mode_string);
+ for (i = 0; LYEditorNames[i]; i++) {
+ char temp[DigitsOf(i) + 3];
+
+ sprintf(temp, "%d", i);
+ PutOption(fp0, i == current_lineedit, temp, LYEditorNames[i]);
+ }
+ EndSelect(fp0);
+ }
+#ifdef EXP_KEYBOARD_LAYOUT
+ /* Keyboard layout: SELECT */
+ PutLabel(fp0, gettext("Keyboard layout"), kblayout_string);
+ BeginSelect(fp0, kblayout_string);
+ for (i = 0; LYKbLayoutNames[i]; i++) {
+ char temp[DigitsOf(i) + 3];
+
+ sprintf(temp, "%d", i);
+ PutOption(fp0, i == current_layout, temp, LYKbLayoutNames[i]);
+ }
+ EndSelect(fp0);
+#endif /* EXP_KEYBOARD_LAYOUT */
+
+ /*
+ * Display and Character Set
+ */
+ PutHeader(fp0, gettext("Display and Character Set"));
+ /*****************************************************************/
+
+#ifdef USE_LOCALE_CHARSET
+ /* Use locale-based character set: ON/OFF */
+ PutLabel(fp0, gettext("Use locale-based character set"), locale_charset_string);
+ BeginSelect(fp0, locale_charset_string);
+ PutOptValues(fp0, LYLocaleCharset, bool_values);
+ EndSelect(fp0);
+#else
+#define LYLocaleCharset FALSE
+#endif
+ PutLabel(fp0, gettext("Use HTML5 charset replacements"), html5_charsets_string);
+ BeginSelect(fp0, html5_charsets_string);
+ PutOptValues(fp0, html5_charsets, bool_values);
+ EndSelect(fp0);
+
+ /* Display Character Set: SELECT */
+ PutLabel(fp0, gettext("Display character set"), display_char_set_string);
+ MaybeSelect(fp0, LYLocaleCharset, display_char_set_string);
+ for (i = 0; LYchar_set_names[i]; i++) {
+ char temp[DigitsOf(i) + 3];
+ size_t len = strlen(LYchar_set_names[i]);
+
+ if (len > cset_len)
+ cset_len = len;
+ sprintf(temp, "%d", i);
+#ifdef USE_CHARSET_CHOICE
+ if (!charset_subsets[i].hide_display)
+#endif
+ PutOption(fp0, i == current_char_set, temp, LYchar_set_names[i]);
+ }
+ EndSelect(fp0);
+
+ /* Assume Character Set: SELECT */
+ {
+ int curval;
+
+ curval = UCLYhndl_for_unspec;
+
+ /*
+ * FIXME: If bogus value in lynx.cfg, then in old way, that is the
+ * string that was displayed. Now, user will never see that. Good
+ * or bad? I don't know. MRC
+ */
+ if (curval == current_char_set) {
+ /* ok, LYRawMode, so use UCAssume_MIMEcharset */
+ curval = safeUCGetLYhndl_byMIME(UCAssume_MIMEcharset);
+ }
+ PutLabel(fp0, gettext("Assumed document character set"), assume_char_set_string);
+ BeginSelect(fp0, assume_char_set_string);
+ for (i = 0; i < LYNumCharsets; i++) {
+#ifdef USE_CHARSET_CHOICE
+ if (!charset_subsets[i].hide_assumed)
+#endif
+ PutOption(fp0, i == curval,
+ LYCharSet_UC[i].MIMEname,
+ LYCharSet_UC[i].MIMEname);
+ }
+ EndSelect(fp0);
+ }
+
+#ifdef USE_IDN2
+ /* Internationalized Domain Names: SELECT */
+ {
+ PutLabel(fp0, gettext("Internationalized domain names"), idna_mode_string);
+ BeginSelect(fp0, idna_mode_string);
+ for (i = 0; idna_values[i].value != 0; i++) {
+ PutOption(fp0, idna_values[i].value == LYidnaMode,
+ idna_values[i].HtmlName,
+ idna_values[i].LongName);
+ }
+ EndSelect(fp0);
+ }
+#endif
+
+ /* Raw Mode: ON/OFF */
+ if (LYHaveCJKCharacterSet) {
+ /*
+ * Since CJK people hardly mixed with other world
+ * we split the header to make it more readable:
+ * "CJK mode" for CJK display charsets, and "Raw 8-bit" for others.
+ */
+ PutLabel(fp0, gettext("CJK mode"), raw_mode_string);
+ } else {
+ PutLabel(fp0, gettext("Raw 8-bit"), raw_mode_string);
+ }
+
+ BeginSelect(fp0, raw_mode_string);
+ PutOptValues(fp0, LYRawMode, bool_values);
+ EndSelect(fp0);
+
+ /* X Display: INPUT */
+ PutLabel(fp0, gettext("X Display"), x_display_string);
+ PutTextInput(fp0, x_display_string, NonNull(x_display), text_len, "");
+
+ /*
+ * Document Appearance
+ */
+ PutHeader(fp0, gettext("Document Appearance"));
+ /*****************************************************************/
+
+ /* Show Color: SELECT */
+#if defined(USE_SLANG) || defined(COLOR_CURSES)
+ SetupChosenShowColor();
+ PutLabel(fp0, gettext("Show color"), show_color_string);
+ if (no_option_save) {
+ MaybeSelect(fp0, !can_do_colors, show_color_string);
+ if (LYShowColor == SHOW_COLOR_NEVER) {
+ LYShowColor = SHOW_COLOR_OFF;
+ } else if (LYShowColor == SHOW_COLOR_ALWAYS) {
+ LYShowColor = SHOW_COLOR_ON;
+ }
+ PutOptValues(fp0, LYShowColor - SHOW_COLOR_OFF, bool_values);
+ } else {
+ BeginSelect(fp0, show_color_string);
+ if (can_do_colors) {
+ show_color_values[2].HtmlName = on_string;
+ show_color_values[3].LongName = always_string;
+ } else {
+ show_color_values[2].HtmlName = NULL; /* suppress "ON" - kw */
+ show_color_values[3].LongName = "Always try";
+ }
+ PutOptValues(fp0, LYChosenShowColor, show_color_values);
+ }
+ EndSelect(fp0);
+#endif /* USE_SLANG || COLOR_CURSES */
+
+#ifdef USE_COLOR_STYLE
+ /* Color style: ON/OFF */
+ if (HasOptValues(color_style_values)) {
+ PutLabel(fp0, gettext("Color style"), color_style_string);
+ BeginSelect(fp0, color_style_string);
+ PutOptValues(fp0, get_color_style_value(), color_style_values);
+ EndSelect(fp0);
+ }
+#endif
+
+#ifdef USE_DEFAULT_COLORS
+ /* Default colors: ON/OFF */
+ if (has_colors()) {
+ PutLabel(fp0, gettext("Default colors"), default_colors_string);
+ BeginSelect(fp0, default_colors_string);
+ PutOptValues(fp0, LYuse_default_colors, bool_values);
+ EndSelect(fp0);
+ }
+#endif
+
+ /* Show cursor: ON/OFF */
+ PutLabel(fp0, gettext("Show cursor"), show_cursor_string);
+ BeginSelect(fp0, show_cursor_string);
+ PutOptValues(fp0, LYShowCursor, bool_values);
+ EndSelect(fp0);
+
+ /* Underline links: ON/OFF */
+ PutLabel(fp0, gettext("Underline links"), underline_links_string);
+ BeginSelect(fp0, underline_links_string);
+ PutOptValues(fp0, LYUnderlineLinks, bool_values);
+ EndSelect(fp0);
+
+#ifdef USE_SCROLLBAR
+ /* Show scrollbar: ON/OFF */
+ PutLabel(fp0, gettext("Show scrollbar"), show_scrollbar_string);
+ BeginSelect(fp0, show_scrollbar_string);
+ PutOptValues(fp0, LYShowScrollbar, bool_values);
+ EndSelect(fp0);
+#endif
+
+ /* Select Popups: ON/OFF */
+ PutLabel(fp0, gettext("Popups for select fields"), select_popups_string);
+ BeginSelect(fp0, select_popups_string);
+ PutOptValues(fp0, LYSelectPopups, bool_values);
+ EndSelect(fp0);
+
+ /* HTML error recovery: SELECT */
+ PutLabel(fp0, gettext("HTML error recovery"), DTD_recovery_string);
+ BeginSelect(fp0, DTD_recovery_string);
+ PutOptValues(fp0, Old_DTD, DTD_type_values);
+ EndSelect(fp0);
+
+ /* Bad HTML messages: SELECT */
+ PutLabel(fp0, gettext("Bad HTML messages"), bad_html_string);
+ BeginSelect(fp0, bad_html_string);
+ PutOptValues(fp0, cfg_bad_html, bad_html_values);
+ EndSelect(fp0);
+
+ /* Show Images: SELECT */
+ PutLabel(fp0, gettext("Show images"), will_save_images());
+ BeginSelect(fp0, images_string);
+ PutOption(fp0, !pseudo_inline_alts && !clickable_images,
+ images_ignore_all_string,
+ images_ignore_all_string);
+ PutOption(fp0, pseudo_inline_alts && !clickable_images,
+ images_use_label_string,
+ images_use_label_string);
+ PutOption(fp0, clickable_images,
+ images_use_links_string,
+ images_use_links_string);
+ EndSelect(fp0);
+
+ /* Verbose Images: ON/OFF */
+ PutLabel(fp0, gettext("Verbose images"), verbose_images_string);
+ BeginSelect(fp0, verbose_images_string);
+ PutOptValues(fp0, verbose_img, verbose_images_type_values);
+ EndSelect(fp0);
+
+ /* Collapse BR Tags: ON/OFF */
+ PutLabel(fp0, gettext("Collapse BR tags"), collapse_br_tags_string);
+ BeginSelect(fp0, collapse_br_tags_string);
+ PutOptValues(fp0, LYCollapseBRs, collapse_br_tags_values);
+ EndSelect(fp0);
+
+ /* Trim blank lines: ON/OFF */
+ PutLabel(fp0, gettext("Trim blank lines"), trim_blank_lines_string);
+ BeginSelect(fp0, trim_blank_lines_string);
+ PutOptValues(fp0, LYtrimBlankLines, trim_blank_lines_values);
+ EndSelect(fp0);
+
+ /*
+ * Headers Transferred to Remote Servers
+ */
+ PutHeader(fp0, gettext("Headers Transferred to Remote Servers"));
+ /*****************************************************************/
+
+ /* Mail Address: INPUT */
+ PutLabel(fp0, gettext("Personal mail address"), mail_address_string);
+ PutTextInput(fp0, mail_address_string,
+ NonNull(personal_mail_address), text_len, "");
+
+#ifndef NO_ANONYMOUS_EMAIL
+ PutLabel(fp0, gettext("Personal name for mail"), personal_name_string);
+ PutTextInput(fp0, personal_name_string,
+ NonNull(personal_mail_name), text_len, "");
+#endif
+
+ /* Anonymous FTP Address: INPUT */
+#ifndef DISABLE_FTP
+ PutLabel(fp0, gettext("Password for anonymous ftp"), anonftp_password_string);
+ PutTextInput(fp0, anonftp_password_string,
+ NonNull(anonftp_password), text_len, "");
+#endif
+
+ /* Preferred content type: SELECT */
+ PutLabel(fp0, gettext("Preferred content type"), preferred_content_string);
+ BeginSelect(fp0, preferred_content_string);
+ PutOptValues(fp0, LYContentType, content_values);
+ EndSelect(fp0);
+
+ /* Preferred media type: SELECT */
+ PutLabel(fp0, gettext("Preferred media type"), preferred_media_string);
+ BeginSelect(fp0, preferred_media_string);
+ PutOptValues(fp0, LYAcceptMedia, media_values);
+ EndSelect(fp0);
+
+ /* Preferred encoding: SELECT */
+ PutLabel(fp0, gettext("Preferred encoding"), preferred_encoding_string);
+ BeginSelect(fp0, preferred_encoding_string);
+ PutOptValues(fp0, LYAcceptEncoding, encoding_values);
+ EndSelect(fp0);
+
+ /* Preferred Document Character Set: INPUT */
+ PutLabel(fp0, gettext("Preferred document character set"), preferred_doc_char_string);
+ PutTextInput(fp0, preferred_doc_char_string,
+ NonNull(pref_charset), cset_len + 2, "");
+
+ /* Preferred Document Language: INPUT */
+ PutLabel(fp0, gettext("Preferred document language"), preferred_doc_lang_string);
+ PutTextInput(fp0, preferred_doc_lang_string,
+ NonNull(language), cset_len + 2, "");
+
+ /* HTTP protocol SELECT */
+ PutLabel(fp0, gettext("HTTP protocol"), http_protocol_string);
+ BeginSelect(fp0, http_protocol_string);
+ PutOptValues(fp0, HTprotocolLevel, http_protocol_values);
+ EndSelect(fp0);
+
+ /* User Agent: INPUT */
+ if (!no_useragent) {
+ PutLabel(fp0, gettext("Send User-Agent header"), send_user_agent_string);
+ PutCheckBox(fp0, send_user_agent_string, LYSendUserAgent, "");
+ PutLabel(fp0, gettext("User-Agent header"), user_agent_string);
+ PutTextInput(fp0, user_agent_string,
+ NonNull(LYUserAgent), text_len, "");
+ }
+
+ /*
+ * Listing and Accessing Files
+ */
+ PutHeader(fp0, gettext("Listing and Accessing Files"));
+ /*****************************************************************/
+
+#ifndef DISABLE_FTP
+ /* FTP sort: SELECT */
+ PutLabel(fp0, gettext("Use Passive FTP"), passive_ftp_string);
+ BeginSelect(fp0, passive_ftp_string);
+ PutOptValues(fp0, ftp_passive, bool_values);
+ EndSelect(fp0);
+
+ /* FTP sort: SELECT */
+ PutLabel(fp0, gettext("FTP sort criteria"), ftp_sort_string);
+ BeginSelect(fp0, ftp_sort_string);
+ PutOptValues(fp0, HTfileSortMethod, ftp_sort_values);
+ EndSelect(fp0);
+#endif /* DISABLE_FTP */
+
+#ifdef DIRED_SUPPORT
+ /* Local Directory Sort: SELECT */
+ PutLabel(fp0, gettext("Local directory sort criteria"), dired_list_string);
+ BeginSelect(fp0, dired_list_string);
+ PutOptValues(fp0, dir_list_style, dired_list_values);
+ EndSelect(fp0);
+#ifdef LONG_LIST
+ /* Local Directory Order: SELECT */
+ PutLabel(fp0, gettext("Local directory sort order"), dired_sort_string);
+ BeginSelect(fp0, dired_sort_string);
+ PutOptValues(fp0, dir_list_order, dired_sort_values);
+ EndSelect(fp0);
+#endif /* LONG_LIST */
+#endif /* DIRED_SUPPORT */
+
+ /* Show dot files: ON/OFF */
+ if (!no_dotfiles) {
+ PutLabel(fp0, gettext("Show dot files"), show_dotfiles_string);
+ BeginSelect(fp0, show_dotfiles_string);
+ PutOptValues(fp0, show_dotfiles, bool_values);
+ EndSelect(fp0);
+ }
+
+ /* Execution links: SELECT */
+#if defined(ENABLE_OPTS_CHANGE_EXEC) && (defined(EXEC_LINKS) || defined(EXEC_SCRIPTS))
+ PutLabel(fp0, gettext("Execution links"), exec_links_string);
+ BeginSelect(fp0, exec_links_string);
+#ifndef NEVER_ALLOW_REMOTE_EXEC
+ PutOptValues(fp0, local_exec
+ ? EXEC_ALWAYS
+ : (local_exec_on_local_files
+ ? EXEC_LOCAL
+ : EXEC_NEVER),
+ exec_links_values);
+#else
+ PutOptValues(fp0, local_exec_on_local_files
+ ? EXEC_LOCAL
+ : EXEC_NEVER,
+ exec_links_values);
+#endif /* !NEVER_ALLOW_REMOTE_EXEC */
+ EndSelect(fp0);
+#endif /* ENABLE_OPTS_CHANGE_EXEC */
+
+ PutLabel(fp0, gettext("Pause when showing message"), no_pause_string);
+ BeginSelect(fp0, no_pause_string);
+ PutOptValues(fp0, !no_pause, bool_values);
+ EndSelect(fp0);
+
+#ifdef USE_READPROGRESS
+ /* Show transfer rate: SELECT */
+ PutLabel(fp0, gettext("Show transfer rate"), show_rate_string);
+ BeginSelect(fp0, show_rate_string);
+ for (i = 0; rate_values[i].LongName != 0; ++i) {
+ char *message = NULL;
+
+ HTSprintf0(&message,
+ rate_values[i].LongName,
+ HTProgressUnits(rate_values[i].value));
+ PutOption(fp0,
+ LYTransferRate == rate_values[i].value,
+ rate_values[i].HtmlName,
+ message);
+ FREE(message);
+ }
+ EndSelect(fp0);
+#endif /* USE_READPROGRESS */
+
+ /*
+ * Special Files and Screens
+ */
+ PutHeader(fp0, gettext("Special Files and Screens"));
+ /*****************************************************************/
+
+ /* Multi-Bookmark Mode: SELECT */
+ if (!LYMBMBlocked) {
+ PutLabel(fp0, gettext("Multi-bookmarks"), mbm_string);
+ BeginSelect(fp0, mbm_string);
+ PutOptValues(fp0, LYMultiBookmarks, mbm_values);
+ EndSelect(fp0);
+ }
+
+ /* Bookmarks File Menu: LINK/INPUT */
+ if (LYMultiBookmarks) {
+ PutLabel(fp0, gettext("Review/edit Bookmarks files"), mbm_string);
+ fprintf(fp0, "<a href=\"%s\">%s</a>\n",
+ LYNXOPTIONS_PAGE(MBM_LINK),
+ LYEntifyTitle(&buffer, gettext("Goto multi-bookmark menu")));
+ } else {
+ PutLabel(fp0, gettext("Bookmarks file"), single_bookmark_string);
+ PutTextInput(fp0, single_bookmark_string,
+ NonNull(bookmark_page), text_len, "");
+ }
+
+#ifdef USE_SESSIONS
+ /* Auto Session: ON/OFF */
+ PutLabel(fp0, gettext("Auto Session"), auto_session_string);
+ BeginSelect(fp0, auto_session_string);
+ PutOptValues(fp0, LYAutoSession, bool_values);
+ EndSelect(fp0);
+
+ /* Session File Menu: INPUT */
+ PutLabel(fp0, gettext("Session file"), single_session_string);
+ PutTextInput(fp0, single_session_string,
+ NonNull(LYSessionFile), text_len, "");
+#endif
+
+ /* Visited Pages: SELECT */
+ PutLabel(fp0, gettext("Visited Pages"), visited_links_string);
+ LYMenuVisitedLinks(fp0, disable_all);
+
+ if (!no_lynxcfg_info) {
+ fprintf(fp0, "\n %s<a href=\"%s\">lynx.cfg</a>.\n",
+ LYEntifyTitle(&buffer, gettext("View the file ")),
+ STR_LYNXCFG);
+ }
+
+ fprintf(fp0, "\n</pre>\n");
+
+ /* Submit/Reset */
+ if (!disable_all) {
+ fprintf(fp0, "<p align=center>\n");
+ fprintf(fp0,
+ "<input type=\"submit\" value=\"%s\"> - \n",
+ LYEntifyValue(&buffer, ACCEPT_CHANGES));
+ fprintf(fp0,
+ "<input type=\"reset\" value=\"%s\"> - \n",
+ LYEntifyValue(&buffer, RESET_CHANGES));
+ fprintf(fp0, "%s\n", LYEntifyTitle(&buffer, CANCEL_CHANGES));
+ }
+
+ /*
+ * close HTML
+ */
+ fprintf(fp0, "</form>\n");
+ EndInternalPage(fp0);
+
+ FREE(buffer);
+
+ LYCloseTempFP(fp0);
+ return (NORMAL);
+}
+
+#endif /* !NO_OPTION_FORMS */
diff --git a/src/LYOptions.h b/src/LYOptions.h
new file mode 100644
index 0000000..2811f2a
--- /dev/null
+++ b/src/LYOptions.h
@@ -0,0 +1,46 @@
+/* $LynxId: LYOptions.h,v 1.32 2017/01/01 01:51:23 tom Exp $ */
+#ifndef LYOPTIONS_H
+#define LYOPTIONS_H
+
+#include <LYStructs.h>
+#include <LYStrings.h>
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+ extern BOOLEAN term_options; /* for LYgetstr() */
+
+ extern BOOLEAN LYCheckUserAgent(void);
+ extern void edit_bookmarks(void);
+ extern int popup_choice(int cur_choice,
+ int line,
+ int column,
+ STRING2PTR choices,
+ int length,
+ int disabled,
+ int mouse);
+
+#define LYChoosePopup(cur, line, column, choices, length, disabled, mouse) \
+ popup_choice(cur, line, column, (STRING2PTR) choices, length, disabled, mouse)
+
+#ifndef NO_OPTION_FORMS
+ extern void LYMenuVisitedLinks(FILE *fp0, int disable_all);
+ extern int postoptions(DocInfo *newdoc);
+#endif /* !NO_OPTION_FORMS */
+
+#ifndef NO_OPTION_MENU
+ extern void LYoptions(void);
+#endif /* !NO_OPTION_MENU */
+
+#ifdef USE_COLOR_STYLE
+ extern void build_lss_enum(HTList *);
+#endif
+
+#if defined(USE_COLOR_STYLE) && defined(LY_FIND_LEAKS)
+ extern void free_colorstyle_leaks(void);
+#endif
+
+#ifdef __cplusplus
+}
+#endif
+#endif /* LYOPTIONS_H */
diff --git a/src/LYPrettySrc.c b/src/LYPrettySrc.c
new file mode 100644
index 0000000..c2ff39e
--- /dev/null
+++ b/src/LYPrettySrc.c
@@ -0,0 +1,427 @@
+/*
+ * $LynxId: LYPrettySrc.c,v 1.36 2018/03/06 10:27:28 tom Exp $
+ *
+ * HTML source syntax highlighting
+ * by Vlad Harchev <hvv@hippo.ru>
+ * March 1999
+ */
+#include <HTUtils.h>
+#include <LYHash.h>
+#include <LYPrettySrc.h>
+#include <LYStrings.h>
+#include <LYLeaks.h>
+
+ /* This file creates too many "leak detected" entries in Lynx.leaks. */
+#define NO_MEMORY_TRACKING
+#include <LYLeaks.h>
+
+#ifdef USE_PRETTYSRC
+BOOL psrc_convert_string = FALSE;
+BOOL psrc_view = FALSE; /* this is read by SGML_put_character - TRUE
+
+ when viewing pretty source */
+BOOLEAN LYpsrc = FALSE; /* this tells what will be shown on '\':
+
+ if TRUE, then pretty source, normal source view otherwise. Toggled by
+ -prettysrc commandline option. */
+BOOL sgml_in_psrc_was_initialized;
+BOOL psrc_nested_call;
+BOOL psrc_first_tag;
+BOOL mark_htext_as_source = FALSE;
+
+ /* tagspecs from lynx.cfg are read here. After .lss file is read (is with lss
+ support), the style cache and markup are created before entering the
+ mainloop. */
+BOOLEAN psrcview_no_anchor_numbering = FALSE;
+static const char *HTL_tagspecs_defaults[HTL_num_lexemes] =
+{
+ /* these values are defaults. They are also listed in comments of distibution's
+ lynx.cfg. */
+#ifdef USE_COLOR_STYLE
+ "span.htmlsrc_comment:!span",
+ "span.htmlsrc_tag:!span",
+ "span.htmlsrc_attrib:!span",
+ "span.htmlsrc_attrval:!span",
+ "span.htmlsrc_abracket:!span",
+ "span.htmlsrc_entity:!span",
+ "span.htmlsrc_href:!span",
+ "span.htmlsrc_entire:!span",
+ "span.htmlsrc_badseq:!span",
+ "span.htmlsrc_badtag:!span",
+ "span.htmlsrc_badattr:!span",
+ "span.htmlsrc_sgmlspecial:!span"
+#else
+ "b:!b", /* comment */
+ "b:!b", /* tag */
+ "b:!b", /* attrib */
+ ":", /* attrval */
+ "b:!b", /* abracket */
+ "b:!b", /* entity */
+ ":", /* href */
+ ":", /* entire */
+ "b:!b", /* badseq */
+ ":", /* badtag */
+ ":", /* badattr */
+ "b:!b" /* sgmlspec */
+#endif
+};
+
+char *HTL_tagspecs[HTL_num_lexemes];
+
+ /* these are pointers since tagspec can be empty (the pointer will be NULL
+ in that case) */
+HT_tagspec *lexeme_start[HTL_num_lexemes];
+HT_tagspec *lexeme_end[HTL_num_lexemes];
+
+int tagname_transform = 2;
+int attrname_transform = 2;
+
+static int html_src_tag_index(const char *tagname)
+{
+ HTTag *tag = SGMLFindTag(&HTML_dtd, tagname);
+
+ return (tag && tag != &HTTag_unrecognized) ? (int) (tag - HTML_dtd.tags) : -1;
+}
+
+typedef enum {
+ HTSRC_CK_normal,
+ HTSRC_CK_seen_excl,
+ HTSRC_CK_after_tagname,
+ HTSRC_CK_seen_dot
+} html_src_check_state;
+
+static void append_close_tag(const char *tagname,
+ HT_tagspec ** head,
+ HT_tagspec ** tail)
+{
+ int idx, nattr;
+ HTTag *tag;
+ HT_tagspec *subj;
+
+ idx = html_src_tag_index(tagname);
+ tag = HTML_dtd.tags + idx;
+ nattr = tag->number_of_attributes;
+
+ if (idx == -1) {
+ fprintf(stderr,
+ "internal error: previous check didn't find bad HTML tag %s", tagname);
+ exit_immediately(EXIT_FAILURE);
+ }
+
+ subj = typecalloc(HT_tagspec);
+ if (subj == 0)
+ outofmem(__FILE__, "append_close_tag");
+
+ subj->element = (HTMLElement) idx;
+
+ subj->present = typecallocn(BOOL, (unsigned) nattr);
+
+ if (subj->present == 0)
+ outofmem(__FILE__, "append_close_tag");
+
+ subj->value = typecallocn(char *, (unsigned) nattr);
+
+ if (subj->value == 0)
+ outofmem(__FILE__, "append_close_tag");
+
+ subj->start = FALSE;
+#ifdef USE_COLOR_STYLE
+ subj->class_name = NULL;
+#endif
+
+ if (!*head) {
+ *head = subj;
+ *tail = subj;
+ } else {
+ (*tail)->next = subj;
+ *tail = subj;
+ }
+}
+
+/* this will allocate node, initialize all members, and node
+ append to the list, possibly modifying head and modifying tail */
+static void append_open_tag(const char *tagname,
+ const char *classname GCC_UNUSED,
+ HT_tagspec ** head,
+ HT_tagspec ** tail)
+{
+ HT_tagspec *subj;
+
+#ifdef USE_COLOR_STYLE
+ int hcode;
+#endif
+
+ append_close_tag(tagname, head, tail); /* initialize common members */
+ subj = *tail;
+ subj->start = TRUE;
+
+#ifdef USE_COLOR_STYLE
+ if (non_empty(classname)) {
+ hcode = color_style_3(tagname, ".", classname);
+ StrAllocCopy(subj->class_name, classname);
+ } else {
+ hcode = color_style_1(tagname);
+ StrAllocCopy(subj->class_name, "");
+ }
+ subj->style = hcode;
+#endif
+}
+
+#define isLeadP(p) ((isalpha(UCH(*p)) || *p == '_'))
+#define isNextP(p) ((isalnum(UCH(*p)) || *p == '_'))
+
+#define FMT_AT " at column %d:\n\t%s\n"
+#define TXT_AT (int) (1 + p - ts), ts
+
+/* returns FALSE if incorrect */
+int html_src_parse_tagspec(char *ts,
+ HTlexeme lexeme,
+ int checkonly,
+ int isstart)
+{
+ BOOL stop = FALSE;
+ BOOL code = FALSE;
+ char *p = ts;
+ char *tagstart = 0;
+ char *tagend = 0;
+ char *classstart;
+ char *classend;
+ char save, save1;
+ char after_excl = FALSE;
+ html_src_check_state state = HTSRC_CK_normal;
+ HT_tagspec *head = NULL;
+ HT_tagspec *tail = NULL;
+ HT_tagspec **slot = (isstart ? lexeme_start : lexeme_end) + lexeme;
+
+ while (!stop) {
+ switch (state) {
+ case HTSRC_CK_normal:
+ case HTSRC_CK_seen_excl:
+ switch (*p) {
+ case '\0':
+ stop = TRUE;
+ code = TRUE;
+ break;
+ case ' ':
+ case '\t':
+ break;
+ case '!':
+ if (state == HTSRC_CK_seen_excl) {
+ CTRACE2(TRACE_CFG,
+ (tfp, "second '!'" FMT_AT,
+ TXT_AT));
+ stop = TRUE;
+ break;
+ }
+ state = HTSRC_CK_seen_excl;
+ after_excl = TRUE;
+ break;
+ default:
+ if (!isLeadP(p)) {
+ CTRACE2(TRACE_CFG,
+ (tfp, "no name starting" FMT_AT,
+ TXT_AT));
+ stop = TRUE;
+ break;
+ }
+ tagstart = p;
+ while (*p && isNextP(p))
+ ++p;
+ tagend = p--;
+ state = HTSRC_CK_after_tagname;
+ }
+ break;
+ case HTSRC_CK_after_tagname:
+ switch (*p) {
+ case '\0':
+ stop = TRUE;
+ code = TRUE;
+ /* FALLTHRU */
+ case ' ':
+ /* FALLTHRU */
+ case '\t':
+ save = *tagend;
+
+ *tagend = '\0';
+ classstart = 0;
+ if (checkonly) {
+ int idx = html_src_tag_index(tagstart);
+
+ CTRACE2(TRACE_CFG,
+ (tfp, "tag index(%s) = %d\n",
+ tagstart, idx));
+
+ *tagend = save;
+ if (idx == -1) {
+ stop = TRUE;
+ break;
+ }
+ } else {
+ if (after_excl)
+ append_close_tag(tagstart, &head, &tail);
+ else
+ append_open_tag(tagstart, NULL, &head, &tail);
+ }
+ state = HTSRC_CK_normal;
+ after_excl = FALSE;
+ break;
+ case '.':
+ if (after_excl) {
+ CTRACE2(TRACE_CFG,
+ (tfp, "dot after '!'" FMT_AT,
+ TXT_AT));
+ stop = TRUE;
+ break;
+ }
+ state = HTSRC_CK_seen_dot;
+ break;
+ default:
+ CTRACE2(TRACE_CFG,
+ (tfp, "unexpected char '%c' after tagname" FMT_AT,
+ *p, TXT_AT));
+ stop = TRUE;
+ break;
+ }
+ break;
+ case HTSRC_CK_seen_dot:
+ switch (*p) {
+ case ' ':
+ case '\t':
+ break;
+ case '\0':
+ CTRACE2(TRACE_CFG,
+ (tfp, "expected text after dot" FMT_AT,
+ TXT_AT));
+ stop = TRUE;
+ break;
+ default:
+ if (!isLeadP(p)) {
+ CTRACE2(TRACE_CFG,
+ (tfp, "no name starting" FMT_AT,
+ TXT_AT));
+ stop = TRUE;
+ break;
+ }
+ classstart = p;
+ while (*p && isNextP(p))
+ ++p;
+ classend = p--;
+ save = *classend;
+ *classend = '\0';
+ save1 = *tagend;
+ *tagend = '\0';
+ if (checkonly) {
+ int idx = html_src_tag_index(tagstart);
+
+ *tagend = save1;
+ *classend = save;
+ if (idx == -1)
+ return FALSE;
+ } else {
+ append_open_tag(tagstart, classstart, &head, &tail);
+ }
+ state = HTSRC_CK_normal;
+ after_excl = FALSE;
+ break;
+ } /* of switch(*p) */
+ break;
+ } /* of switch */
+ ++p;
+ }
+
+ if (code && !checkonly)
+ *slot = head;
+
+ return code;
+}
+
+/*this will clean the data associated with lexeme 'l' */
+void html_src_clean_item(HTlexeme l)
+{
+ int i;
+
+ if (HTL_tagspecs[l])
+ FREE(HTL_tagspecs[l]);
+ for (i = 0; i < 2; ++i) {
+ HT_tagspec *cur;
+ HT_tagspec **pts = (i ? lexeme_start : lexeme_end) + l;
+ HT_tagspec *ts = *pts;
+
+ *pts = NULL;
+ while (ts) {
+ FREE(ts->present);
+ FREE(ts->value);
+#ifdef USE_COLOR_STYLE
+ if (ts->start) {
+ FREE(ts->class_name);
+ }
+#endif
+ cur = ts;
+ ts = ts->next;
+ FREE(cur);
+ }
+ }
+}
+
+/*this will be registered with atexit*/
+void html_src_clean_data(void)
+{
+ int i;
+
+ for (i = 0; i < HTL_num_lexemes; ++i)
+ html_src_clean_item((HTlexeme) i);
+}
+
+void html_src_on_lynxcfg_reload(void)
+{
+ html_src_clean_data();
+ HTMLSRC_init_caches(TRUE);
+}
+
+static void failed_init(const char *tag, int lexeme)
+{
+ fprintf(stderr,
+ gettext("parse-error while caching %s tagspec of lexeme %d\n"),
+ tag, lexeme);
+ fprintf(stderr,
+ gettext("Use -trace -trace-mask=8 to see details in log.\n"));
+ exit_immediately(EXIT_FAILURE);
+}
+
+void HTMLSRC_init_caches(int dont_exit)
+{
+ int i;
+ char *p;
+ char buf[1000];
+ static char empty[] = "";
+
+ CTRACE2(TRACE_CFG, (tfp, "HTMLSRC_init_caches(%d tagspecs)\n", HTL_num_lexemes));
+ for (i = 0; i < HTL_num_lexemes; ++i) {
+ /*we assume that HT_tagspecs was NULLs at when program started */
+ LYStrNCpy(buf,
+ HTL_tagspecs[i]
+ ? HTL_tagspecs[i]
+ : HTL_tagspecs_defaults[i],
+ sizeof(buf) - 1);
+ StrAllocCopy(HTL_tagspecs[i], buf);
+
+ CTRACE2(TRACE_CFG, (tfp, "parsing lexeme %d: %s\n", i + 1, buf));
+
+ if ((p = StrChr(buf, ':')) != 0)
+ *p = '\0';
+ if (!html_src_parse_tagspec(buf,
+ (HTlexeme) i,
+ FALSE,
+ TRUE) && !dont_exit) {
+ failed_init("1st", i);
+ }
+ if (!html_src_parse_tagspec(p ? p + 1 : empty,
+ (HTlexeme) i,
+ FALSE,
+ FALSE) && !dont_exit) {
+ failed_init("2nd", i);
+ }
+ }
+}
+
+#endif /* ifdef USE_PRETTYSRC */
diff --git a/src/LYPrettySrc.h b/src/LYPrettySrc.h
new file mode 100644
index 0000000..aa59063
--- /dev/null
+++ b/src/LYPrettySrc.h
@@ -0,0 +1,92 @@
+/*
+ * $LynxId: LYPrettySrc.h,v 1.13 2020/01/21 21:38:33 tom Exp $
+ */
+#ifndef LYPrettySrc_H
+#define LYPrettySrc_H
+
+#ifdef USE_PRETTYSRC
+
+#include <HTMLDTD.h>
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+ extern BOOL psrc_convert_string;
+
+ /*whether HTML_put_string should convert string passed with
+ TRANSLATE_AND_UNESCAPE_TO_STD */
+ extern BOOL psrc_view;
+ extern BOOLEAN LYpsrc;
+
+/*
+ * This is used for tracking down whether the SGML engine was initialized
+ * ==TRUE if yes. It's value is meaningful if psrc_view = TRUE
+ */
+ extern BOOL sgml_in_psrc_was_initialized;
+
+ extern BOOL psrc_nested_call; /* this is used when distinguishing whether
+
+ the current call is nested or not in HTML.c HTML_{start,end}_element.
+ It ==FALSE if psrc_view==FALSE || sgml_in_psrc_was_initialized==TRUE */
+
+ extern BOOL psrc_first_tag; /* this is also used in HTML.c to trigger the
+
+ 1st tag to perform special.
+ */
+
+ extern BOOL mark_htext_as_source;
+
+/* here is a list of lexeme codes. */
+ typedef enum {
+ HTL_comm = 0,
+ HTL_tag,
+ HTL_attrib,
+ HTL_attrval,
+ HTL_abracket,
+ HTL_entity,
+ HTL_href,
+ HTL_entire,
+ HTL_badseq,
+ HTL_badtag,
+ HTL_badattr,
+ HTL_sgmlspecial,
+ HTL_num_lexemes
+ } HTlexeme;
+
+ typedef struct _HT_tagspec {
+ struct _HT_tagspec *next; /* 0 at the last */
+#ifdef USE_COLOR_STYLE
+ int style; /* precalculated value of the style */
+ char *class_name;
+#endif
+ /* these will be passed to HTML_start_element */
+ HTMLElement element;
+ BOOL *present;
+ char **value;
+
+ BOOL start; /* if true, then this starts element, otherwise - ends */
+ } HT_tagspec;
+
+ extern char *HTL_tagspecs[HTL_num_lexemes];
+ extern HT_tagspec *lexeme_start[HTL_num_lexemes];
+ extern HT_tagspec *lexeme_end[HTL_num_lexemes];
+
+ extern int html_src_parse_tagspec(char *ts, HTlexeme lexeme,
+ int checkonly, int isstart);
+ extern void HTMLSRC_init_caches(int dont_exit);
+ extern void html_src_clean_item(HTlexeme l);
+ extern void html_src_clean_data(void);
+ extern void html_src_on_lynxcfg_reload(void);
+
+/* these 2 vars tell what kind of transform should be applied to tag names
+ and attribute names. 0 - lowercase, 1 - as is, 2 uppercase. */
+ extern int tagname_transform;
+ extern int attrname_transform;
+
+ extern BOOLEAN psrcview_no_anchor_numbering;
+
+#ifdef __cplusplus
+}
+#endif
+#endif /* ifdef USE_PRETTYSRC */
+#endif /* LYPrettySrc_H */
diff --git a/src/LYPrint.c b/src/LYPrint.c
new file mode 100644
index 0000000..58b81a6
--- /dev/null
+++ b/src/LYPrint.c
@@ -0,0 +1,1461 @@
+/*
+ * $LynxId: LYPrint.c,v 1.109 2021/07/29 20:38:35 tom Exp $
+ */
+#include <HTUtils.h>
+#include <HTAccess.h>
+#include <HTList.h>
+#include <HTAlert.h>
+#include <HTFile.h>
+#include <LYCurses.h>
+#include <GridText.h>
+#include <LYUtils.h>
+#include <LYPrint.h>
+#include <LYGlobalDefs.h>
+#include <LYSignal.h>
+#include <LYStrings.h>
+#include <LYClean.h>
+#include <LYGetFile.h>
+#include <LYHistory.h>
+#include <LYList.h>
+#include <LYCharSets.h> /* To get current charset for mail header. */
+
+#include <LYLeaks.h>
+
+#define CancelPrint(msg) HTInfoMsg(msg); goto done
+#define CannotPrint(msg) HTAlert(msg); goto done
+
+/*
+ * printfile prints out the current file minus the links and targets to a
+ * variety of places
+ */
+
+/* it parses an incoming link that looks like
+ *
+ * LYNXPRINT://LOCAL_FILE/lines=##
+ * LYNXPRINT://MAIL_FILE/lines=##
+ * LYNXPRINT://TO_SCREEN/lines=##
+ * LYNXPRINT://LPANSI/lines=##
+ * LYNXPRINT://PRINTER/lines=##/number=#
+ */
+
+#define TO_FILE 1
+#define TO_SCREEN 2
+/*
+ * "lpansi.c"
+ * Original author: Gary Day (gday@comp.uark.edu), 11/30/93
+ * Current version: 2.1 by Noel Hunter (noel@wfu.edu), 10/20/94
+ *
+ * Basic structure based on print -- format files for printing from
+ * _Practical_C_Programming by Steve Oualline, O'Reilly & Associates
+ *
+ * adapted from the README for lpansi.c v2.1, dated 10/20/1994:
+ * Print to ANSI printer on local terminal
+ * The VT100 standard defines printer on and off escape sequences,
+ * esc[5i is printer on, and esc[4i is printer off.
+ *
+ * incorporate the idea of "lpansi" directly into LYPrint.c - HN
+ */
+#define LPANSI 3
+#define MAIL 4
+#define PRINTER 5
+
+#if USE_VMS_MAILER
+static int remove_quotes(char *string);
+#endif /* USE_VMS_MAILER */
+
+static char *subject_translate8bit(char *source);
+
+#define LYNX_PRINT_TITLE 0
+#define LYNX_PRINT_URL 1
+#define LYNX_PRINT_DATE 2
+#define LYNX_PRINT_LASTMOD 3
+
+#define MAX_PUTENV 4
+
+static void set_environ(int name,
+ const char *value,
+ const char *no_value)
+{
+ static const char *names[MAX_PUTENV] =
+ {
+ "LYNX_PRINT_TITLE",
+ "LYNX_PRINT_URL",
+ "LYNX_PRINT_DATE",
+ "LYNX_PRINT_LASTMOD",
+ };
+ static char *pointers[MAX_PUTENV];
+ char *envbuffer = 0;
+
+#ifdef VMS
+#define SET_ENVIRON(name, value, no_value) set_environ(name, value, no_value)
+ char temp[80];
+
+ StrAllocCopy(envbuffer, value);
+ if (isEmpty(envbuffer))
+ StrAllocCopy(envbuffer, no_value);
+ Define_VMSLogical(strcpy(temp, names[name]), envbuffer);
+ FREE(envbuffer);
+#else
+#define SET_ENVIRON(name, value, no_value) set_environ(name, value, "")
+ /*
+ * Once we've given a string to 'putenv()', we must not free it until we
+ * give it a string to replace it.
+ */
+ StrAllocCopy(envbuffer, names[name]);
+ StrAllocCat(envbuffer, "=");
+ StrAllocCat(envbuffer, value ? value : no_value);
+ putenv(envbuffer);
+ FREE(pointers[name]);
+ pointers[name] = envbuffer;
+#endif
+}
+
+static char *suggested_filename(DocInfo *newdoc)
+{
+ char *sug_filename = 0;
+ int rootlen;
+
+ /*
+ * Load the suggested filename string. - FM
+ */
+ if (HText_getSugFname() != 0)
+ StrAllocCopy(sug_filename, HText_getSugFname()); /* must be freed */
+ else
+ StrAllocCopy(sug_filename, newdoc->address); /* must be freed */
+ /*
+ * Strip suffix for compressed-files, if present.
+ */
+ if (HTCompressFileType(sug_filename, ".", &rootlen) != cftNone)
+ sug_filename[rootlen] = '\0';
+
+ CTRACE((tfp, "suggest %s\n", sug_filename));
+ return sug_filename;
+}
+
+static void SetupFilename(bstring **filename,
+ const char *sug_filename)
+{
+ HTFormat format;
+ HTAtom *encoding;
+ char *cp;
+
+ BStrCopy0(*filename, sug_filename); /* add suggestion info */
+ /*
+ * FIXME: the history-recall still uses fixed-size buffers
+ */
+ if ((*filename)->len >= LY_MAXPATH) {
+ (*filename)->str[LY_MAXPATH - 1] = '\0';
+ } else {
+ BStrAlloc(*filename, LY_MAXPATH);
+ }
+ change_sug_filename((*filename)->str);
+ if (!(HTisDocumentSource())
+ && (cp = strrchr((*filename)->str, '.')) != NULL) {
+ format = HTFileFormat((*filename)->str, &encoding, NULL);
+ CTRACE((tfp, "... format %s\n", format->name));
+ if (!strcasecomp(format->name, STR_HTML) ||
+ !IsUnityEnc(encoding)) {
+ (*filename)->len = (int) (cp - (*filename)->str);
+ BStrCat0(*filename, TEXT_SUFFIX);
+ }
+ }
+ CTRACE((tfp, "... result %s\n", (*filename)->str));
+}
+
+#define FN_INIT 0
+#define FN_READ 1
+#define FN_DONE 2
+#define FN_QUIT 3
+
+#define PRINT_FLAG 0
+#define GENERIC_FLAG 1
+
+static int RecallFilename(bstring **filename,
+ BOOLEAN *first,
+ int *now,
+ int *total,
+ int flag)
+{
+ int ch;
+ char *cp;
+ RecallType recall;
+
+ /*
+ * Set up the sug_filenames recall buffer.
+ */
+ if (*now < 0) {
+ *total = (sug_filenames ? HTList_count(sug_filenames) : 0);
+ *now = *total;
+ }
+ recall = ((*total >= 1) ? RECALL_URL : NORECALL);
+
+ if ((ch = LYgetBString(filename, FALSE, 0, recall)) < 0 ||
+ isBEmpty(*filename) || ch == UPARROW_KEY || ch == DNARROW_KEY) {
+ if (recall && ch == UPARROW_KEY) {
+ if (*first) {
+ *first = FALSE;
+ /*
+ * Use the last Fname in the list. - FM
+ */
+ *now = 0;
+ } else {
+ /*
+ * Go back to the previous Fname in the list. - FM
+ */
+ *now += 1;
+ }
+ if (*now >= *total) {
+ /*
+ * Reset the *first flag, and use sug_file or a blank. -
+ * FM
+ */
+ *first = TRUE;
+ *now = *total;
+ _statusline(FILENAME_PROMPT);
+ return FN_INIT;
+ } else if ((cp = (char *) HTList_objectAt(sug_filenames,
+ *now)) != NULL) {
+ BStrCopy0(*filename, cp);
+ if (*total == 1) {
+ _statusline(EDIT_THE_PREV_FILENAME);
+ } else {
+ _statusline(EDIT_A_PREV_FILENAME);
+ }
+ return FN_READ;
+ }
+ } else if (recall && ch == DNARROW_KEY) {
+ if (*first) {
+ *first = FALSE;
+ /*
+ * Use the first Fname in the list. - FM
+ */
+ *now = *total - 1;
+ } else {
+ /*
+ * Advance to the next Fname in the list. - FM
+ */
+ *now -= 1;
+ }
+ if (*now < 0) {
+ /*
+ * Set the *first flag, and use sug_file or a blank. - FM
+ */
+ *first = TRUE;
+ *now = *total;
+ _statusline(FILENAME_PROMPT);
+ return FN_INIT;
+ } else if ((cp = (char *) HTList_objectAt(sug_filenames,
+ *now)) != NULL) {
+ BStrCopy0(*filename, cp);
+ if (*total == 1) {
+ _statusline(EDIT_THE_PREV_FILENAME);
+ } else {
+ _statusline(EDIT_A_PREV_FILENAME);
+ }
+ return FN_READ;
+ }
+ }
+
+ /*
+ * Operation cancelled.
+ */
+ if (flag == PRINT_FLAG)
+ HTInfoMsg(SAVE_REQUEST_CANCELLED);
+ else if (flag == GENERIC_FLAG)
+ return FN_QUIT;
+
+ return FN_QUIT;
+ }
+ return FN_DONE;
+}
+
+static BOOLEAN confirm_by_pages(const char *prompt,
+ int lines_in_file,
+ int lines_per_page)
+{
+ int pages = lines_in_file / (lines_per_page + 1);
+ int c;
+
+ /* count fractional pages ! */
+ if ((lines_in_file % (LYlines + 1)) > 0)
+ pages++;
+
+ if (pages > 4) {
+ char *msg = 0;
+
+ HTSprintf0(&msg, prompt, pages);
+ c = HTConfirmDefault(msg, YES);
+ FREE(msg);
+
+ if (c == YES) {
+ LYaddstr(" Ok...");
+ } else {
+ HTInfoMsg(PRINT_REQUEST_CANCELLED);
+ return FALSE;
+ }
+ }
+ return TRUE;
+}
+
+static void send_file_to_file(DocInfo *newdoc,
+ char *content_base,
+ char *sug_filename)
+{
+ BOOLEAN FirstRecall = TRUE;
+ BOOLEAN use_cte;
+ const char *disp_charset;
+ FILE *outfile_fp;
+ bstring *buffer = NULL;
+ bstring *filename = NULL;
+ int FnameNum = -1;
+ int FnameTotal;
+ int c = 0;
+
+ _statusline(FILENAME_PROMPT);
+
+ retry:
+ SetupFilename(&filename, sug_filename);
+ if (non_empty(lynx_save_space)) {
+ BStrCopy0(buffer, lynx_save_space);
+ BStrCat(buffer, filename);
+ BStrCopy(filename, buffer);
+ } else {
+ BStrCopy0(buffer, "");
+ }
+
+ check_recall:
+ switch (RecallFilename(&filename, &FirstRecall, &FnameNum,
+ &FnameTotal, PRINT_FLAG)) {
+ case FN_INIT:
+ goto retry;
+ case FN_READ:
+ goto check_recall;
+ case FN_QUIT:
+ goto done;
+ default:
+ break;
+ }
+
+ if (!LYValidateFilename(&buffer, &filename)) {
+ CancelPrint(SAVE_REQUEST_CANCELLED);
+ }
+
+ /*
+ * See if it already exists.
+ */
+ switch (c = LYValidateOutput(buffer->str)) {
+ case 'Y':
+ break;
+ case 'N':
+ _statusline(NEW_FILENAME_PROMPT);
+ FirstRecall = TRUE;
+ FnameNum = FnameTotal;
+ goto retry;
+ default:
+ goto done;
+ }
+
+ /*
+ * See if we can write to it.
+ */
+ CTRACE((tfp, "LYPrint: filename is %s, action is `%c'\n", buffer->str, c));
+
+#ifdef HAVE_POPEN
+ if (buffer->str[0] == '|') {
+ if (no_shell) {
+ HTUserMsg(SPAWNING_DISABLED);
+ FirstRecall = TRUE;
+ FnameNum = FnameTotal;
+ goto retry;
+ } else if ((outfile_fp = popen(buffer->str + 1, "w")) == NULL) {
+ CTRACE((tfp, "LYPrint: errno is %d\n", errno));
+ HTAlert(CANNOT_WRITE_TO_FILE);
+ _statusline(NEW_FILENAME_PROMPT);
+ FirstRecall = TRUE;
+ FnameNum = FnameTotal;
+ goto retry;
+ }
+ } else
+#endif
+ if ((outfile_fp = (TOUPPER(c) == 'A'
+ ? LYAppendToTxtFile(buffer->str)
+ : LYNewTxtFile(buffer->str))) == NULL) {
+ CTRACE((tfp, "LYPrint: errno is %d\n", errno));
+ HTAlert(CANNOT_WRITE_TO_FILE);
+ _statusline(NEW_FILENAME_PROMPT);
+ FirstRecall = TRUE;
+ FnameNum = FnameTotal;
+ goto retry;
+ }
+
+ if (LYPrependBaseToSource && HTisDocumentSource()) {
+ /*
+ * Added the document's base as a BASE tag to the top of the file. May
+ * create technically invalid HTML, but will help get any partial or
+ * relative URLs resolved properly if no BASE tag is present to replace
+ * it. - FM
+ *
+ * Add timestamp (last reload).
+ */
+
+ fprintf(outfile_fp,
+ "<!-- X-URL: %s -->\n", newdoc->address);
+ if (HText_getDate() != NULL) {
+ fprintf(outfile_fp,
+ "<!-- Date: %s -->\n", HText_getDate());
+ if (HText_getLastModified() != NULL
+ && strcmp(HText_getLastModified(), HText_getDate())
+ && strcmp(HText_getLastModified(),
+ "Thu, 01 Jan 1970 00:00:01 GMT")) {
+ fprintf(outfile_fp,
+ "<!-- Last-Modified: %s -->\n", HText_getLastModified());
+ }
+ }
+
+ fprintf(outfile_fp,
+ "<BASE HREF=\"%s\">\n", content_base);
+ }
+
+ if (LYPrependCharsetToSource && HTisDocumentSource()) {
+ /*
+ * Added the document's charset as a META CHARSET tag to the top of the
+ * file. May create technically invalid HTML, but will help to resolve
+ * properly the document converted via chartrans: printed document
+ * correspond to a display charset and we *should* override both
+ * assume_local_charset and original document's META CHARSET (if any).
+ *
+ * Currently, if several META CHARSETs are found Lynx uses the first
+ * only, and it is opposite to BASE where the original BASE in the
+ * <HEAD> overrides ones from the top.
+ *
+ * As in print-to-email we write charset only if the document has 8-bit
+ * characters, and we have no CJK or an unofficial "x-" charset.
+ */
+ use_cte = HTLoadedDocumentEightbit();
+ disp_charset = LYCharSet_UC[current_char_set].MIMEname;
+ if (!use_cte || LYHaveCJKCharacterSet ||
+ strncasecomp(disp_charset, "x-", 2) == 0) {
+ } else {
+ fprintf(outfile_fp,
+ "<META HTTP-EQUIV=\"Content-Type\" CONTENT=\"" STR_HTML
+ "; charset=%s\">\n\n",
+ disp_charset);
+ }
+ }
+
+ print_wwwfile_to_fd(outfile_fp, FALSE, FALSE); /* FILE */
+ if (keypad_mode)
+ printlist(outfile_fp, FALSE);
+
+#ifdef HAVE_POPEN
+ if (LYIsPipeCommand(buffer->str))
+ pclose(outfile_fp);
+ else
+#endif
+ LYCloseOutput(outfile_fp);
+
+#ifdef VMS
+ if (0 == strncasecomp(buffer->str, "sys$disk:", 9)) {
+ if (0 == StrNCmp((buffer->str + 9), "[]", 2)) {
+ HTAddSugFilename(buffer->str + 11);
+ } else {
+ HTAddSugFilename(buffer->str + 9);
+ }
+ } else {
+ HTAddSugFilename(buffer->str);
+ }
+#else
+ HTAddSugFilename(buffer->str);
+#endif /* VMS */
+
+ done:
+ BStrFree(buffer);
+ BStrFree(filename);
+ return;
+}
+
+static void send_file_to_mail(DocInfo *newdoc,
+ char *content_base,
+ char *content_location)
+{
+ static BOOLEAN first_mail_preparsed = TRUE;
+
+#if USE_VMS_MAILER
+ BOOLEAN isPMDF = LYMailPMDF();
+ FILE *hfd;
+ char hdrfile[LY_MAXPATH];
+#endif
+ BOOL use_mime;
+
+#if !CAN_PIPE_TO_MAILER
+ char my_temp[LY_MAXPATH];
+#endif
+
+ BOOL use_cte;
+ BOOL use_type;
+ const char *disp_charset;
+ FILE *outfile_fp;
+ char *buffer = NULL;
+ char *subject = NULL;
+ bstring *user_response = NULL;
+
+ if (!LYSystemMail())
+ return;
+
+ if (LYPreparsedSource && first_mail_preparsed &&
+ HTisDocumentSource()) {
+ if (HTConfirmDefault(CONFIRM_MAIL_SOURCE_PREPARSED, NO) == YES) {
+ LYaddstr(" Ok...");
+ first_mail_preparsed = FALSE;
+ } else {
+ CancelPrint(MAIL_REQUEST_CANCELLED);
+ }
+ }
+
+ _statusline(MAIL_ADDRESS_PROMPT);
+ BStrCopy0(user_response, NonNull(personal_mail_address));
+ if (LYgetBString(&user_response, FALSE, 0, RECALL_MAIL) < 0 ||
+ isBEmpty(user_response)) {
+ CancelPrint(MAIL_REQUEST_CANCELLED);
+ }
+
+ /*
+ * Determine which mail headers should be sent. Use Content-Type and
+ * MIME-Version headers only if needed. We need them if we are mailing
+ * HTML source, or if we have 8-bit characters and will be sending
+ * Content-Transfer-Encoding to indicate this. We will append a charset
+ * parameter to the Content-Type if we do not have an "x-" charset, and we
+ * will include the Content-Transfer-Encoding only if we are appending the
+ * charset parameter, because indicating an 8-bit transfer without also
+ * indicating the charset can cause problems with many mailers. - FM & KW
+ */
+ disp_charset = LYCharSet_UC[current_char_set].MIMEname;
+ use_cte = HTLoadedDocumentEightbit();
+ if (!(use_cte && strncasecomp(disp_charset, "x-", 2))) {
+ disp_charset = NULL;
+#if USE_VMS_MAILER
+ use_cte = FALSE;
+#endif
+ }
+#if USE_VMS_MAILER
+ use_type = (BOOL) (disp_charset || HTisDocumentSource());
+#endif
+
+ /*
+ * Use newdoc->title as a subject instead of sug_filename: MORE readable
+ * and 8-bit letters shouldn't be a problem - LP
+ */
+ /* change_sug_filename(sug_filename); */
+ subject = subject_translate8bit(newdoc->title);
+
+ if (newdoc->isHEAD) {
+ /*
+ * Special case for mailing HEAD response: this is rather technical
+ * information, show URL.
+ */
+ FREE(subject);
+ StrAllocCopy(subject, "HEAD ");
+ StrAllocCat(subject, newdoc->address);
+ }
+#if USE_VMS_MAILER
+ if (StrChr(user_response->str, '@') &&
+ !StrChr(user_response->str, ':') &&
+ !StrChr(user_response->str, '%') &&
+ !StrChr(user_response->str, '"')) {
+ char *temp = 0;
+
+ HTSprintf0(&temp, mail_adrs, user_response->str);
+ BStrCopy0(user_response, temp);
+ FREE(temp);
+ }
+
+ outfile_fp = LYOpenTemp(my_temp,
+ (HTisDocumentSource())
+ ? HTML_SUFFIX
+ : TEXT_SUFFIX,
+ "w");
+ if (outfile_fp == NULL) {
+ CannotPrint(UNABLE_TO_OPEN_TEMPFILE);
+ }
+
+ if (isPMDF) {
+ if ((hfd = LYOpenTemp(hdrfile, TEXT_SUFFIX, "w")) == NULL) {
+ CannotPrint(UNABLE_TO_OPEN_TEMPFILE);
+ }
+ if (use_type) {
+ fprintf(hfd, "Mime-Version: 1.0\n");
+ if (use_cte) {
+ fprintf(hfd, "Content-Transfer-Encoding: 8bit\n");
+ }
+ }
+ if (HTisDocumentSource()) {
+ /*
+ * Add Content-Type, Content-Location, and Content-Base headers for
+ * HTML source. - FM
+ */
+ fprintf(hfd, "Content-Type: " STR_HTML);
+ if (disp_charset != NULL) {
+ fprintf(hfd, "; charset=%s\n", disp_charset);
+ } else {
+ fprintf(hfd, "\n");
+ }
+ fprintf(hfd, "Content-Base: %s\n", content_base);
+ fprintf(hfd, "Content-Location: %s\n", content_location);
+ } else {
+ /*
+ * Add Content-Type: text/plain if we have 8-bit characters and a
+ * valid charset for non-source documents. - FM
+ */
+ if (disp_charset != NULL) {
+ fprintf(hfd,
+ "Content-Type: " STR_PLAINTEXT "; charset=%s\n",
+ disp_charset);
+ }
+ }
+ /*
+ * X-URL header. - FM
+ */
+ fprintf(hfd, "X-URL: %s\n", newdoc->address);
+ /*
+ * For PMDF, put the subject in the header file and close it. - FM
+ */
+ fprintf(hfd, "Subject: %.70s\n\n", subject);
+ LYCloseTempFP(hfd);
+ }
+
+ /*
+ * Write the contents to a temp file.
+ */
+ if (LYPrependBaseToSource && HTisDocumentSource()) {
+ /*
+ * Added the document's base as a BASE tag to the top of the message
+ * body. May create technically invalid HTML, but will help get any
+ * partial or relative URLs resolved properly if no BASE tag is present
+ * to replace it. - FM
+ */
+ fprintf(outfile_fp,
+ "<!-- X-URL: %s -->\n<BASE HREF=\"%s\">\n\n",
+ newdoc->address, content_base);
+ } else if (!isPMDF) {
+ fprintf(outfile_fp, "X-URL: %s\n\n", newdoc->address);
+ }
+ print_wwwfile_to_fd(outfile_fp, TRUE, FALSE); /* MAIL */
+ if (keypad_mode)
+ printlist(outfile_fp, FALSE);
+ LYCloseTempFP(outfile_fp);
+
+ buffer = NULL;
+ if (isPMDF) {
+ /*
+ * Now set up the command. - FM
+ */
+ HTSprintf0(&buffer,
+ "%s %s %s,%s %s",
+ system_mail,
+ system_mail_flags,
+ hdrfile,
+ my_temp,
+ user_response->str);
+ } else {
+ /*
+ * For "generic" VMS MAIL, include the subject in the command. - FM
+ */
+ remove_quotes(subject);
+ HTSprintf0(&buffer,
+ "%s %s/subject=\"%.70s\" %s %s",
+ system_mail,
+ system_mail_flags,
+ subject,
+ my_temp,
+ user_response->str);
+ }
+
+ stop_curses();
+ SetOutputMode(O_TEXT);
+ printf(MAILING_FILE);
+ LYSystem(buffer);
+ LYSleepAlert();
+ start_curses();
+ SetOutputMode(O_BINARY);
+
+ if (isPMDF)
+ (void) LYRemoveTemp(hdrfile);
+ (void) LYRemoveTemp(my_temp);
+#else /* !VMS (Unix or DOS) */
+
+#if CAN_PIPE_TO_MAILER
+ outfile_fp = LYPipeToMailer();
+#else
+ outfile_fp = LYOpenTemp(my_temp, TEXT_SUFFIX, "w");
+#endif
+ if (outfile_fp == NULL) {
+ CannotPrint(MAIL_REQUEST_FAILED);
+ }
+
+ /*
+ * Determine which mail headers should be sent. Use Content-Type and
+ * MIME-Version headers only if needed. We need them if we are mailing
+ * HTML source, or if we have 8-bit characters and will be sending
+ * Content-Transfer-Encoding to indicate this.
+ *
+ * Send Content-Transfer-Encoding only if the document has 8-bit
+ * characters. Send a charset parameter only if the document has 8-bit
+ * characters and we seem to have a valid charset. - kw
+ */
+ use_cte = HTLoadedDocumentEightbit();
+ disp_charset = LYCharSet_UC[current_char_set].MIMEname;
+ /*
+ * Don't send a charset if we have a CJK character set selected, since it
+ * may not be appropriate for mail... Also don't use an unofficial "x-"
+ * charset. - kw
+ */
+ if (!use_cte || LYHaveCJKCharacterSet ||
+ strncasecomp(disp_charset, "x-", 2) == 0) {
+ disp_charset = NULL;
+ }
+#ifdef NOTDEFINED
+ /* Enable this if indicating an 8-bit transfer without also indicating the
+ * charset causes problems. - kw */
+ if (use_cte && !disp_charset)
+ use_cte = FALSE;
+#endif /* NOTDEFINED */
+ use_type = (BOOL) (disp_charset || HTisDocumentSource());
+ use_mime = (BOOL) (use_cte || use_type);
+
+ if (use_mime) {
+ fprintf(outfile_fp, "Mime-Version: 1.0\n");
+ if (use_cte) {
+ fprintf(outfile_fp, "Content-Transfer-Encoding: 8bit\n");
+ }
+ }
+
+ if (HTisDocumentSource()) {
+ /*
+ * Add Content-Type, Content-Location, and Content-Base headers for
+ * HTML source. - FM
+ */
+ fprintf(outfile_fp, "Content-Type: " STR_HTML);
+ if (disp_charset != NULL) {
+ fprintf(outfile_fp, "; charset=%s\n", disp_charset);
+ } else {
+ fprintf(outfile_fp, "\n");
+ }
+ } else {
+ /*
+ * Add Content-Type: text/plain if we have 8-bit characters and a
+ * valid charset for non-source documents. - KW
+ */
+ if (disp_charset != NULL) {
+ fprintf(outfile_fp,
+ "Content-Type: " STR_PLAINTEXT "; charset=%s\n",
+ disp_charset);
+ }
+ }
+ /*
+ * If we are using MIME headers, add content-base and content-location if
+ * we have them. This will always be the case if the document is source.
+ * - kw
+ */
+ if (use_mime) {
+ if (content_base)
+ fprintf(outfile_fp, "Content-Base: %s\n", content_base);
+ if (content_location)
+ fprintf(outfile_fp, "Content-Location: %s\n", content_location);
+ }
+
+ /*
+ * Add the To, Subject, and X-URL headers. - FM
+ */
+ fprintf(outfile_fp, "To: %s\nSubject: %s\n", user_response->str, subject);
+ fprintf(outfile_fp, "X-URL: %s\n\n", newdoc->address);
+
+ if (LYPrependBaseToSource && HTisDocumentSource()) {
+ /*
+ * Added the document's base as a BASE tag to the top of the message
+ * body. May create technically invalid HTML, but will help get any
+ * partial or relative URLs resolved properly if no BASE tag is present
+ * to replace it. - FM
+ */
+ fprintf(outfile_fp,
+ "<!-- X-URL: %s -->\n<BASE HREF=\"%s\">\n\n",
+ newdoc->address, content_base);
+ }
+ print_wwwfile_to_fd(outfile_fp, TRUE, FALSE); /* MAIL */
+ if (keypad_mode)
+ printlist(outfile_fp, FALSE);
+
+#if CAN_PIPE_TO_MAILER
+ pclose(outfile_fp);
+#else
+ LYCloseOutput(outfile_fp);
+ LYSendMailFile(user_response->str,
+ my_temp,
+ subject,
+ "",
+ "");
+ (void) LYRemoveTemp(my_temp); /* Delete the tmpfile. */
+#endif /* CAN_PIPE_TO_MAILER */
+#endif /* USE_VMS_MAILER */
+
+ done: /* send_file_to_mail() */
+ BStrFree(user_response);
+ FREE(buffer);
+ FREE(subject);
+ return;
+}
+
+static void send_file_to_printer(DocInfo *newdoc,
+ char *content_base,
+ char *sug_filename,
+ int printer_number)
+{
+ BOOLEAN FirstRecall = TRUE;
+ FILE *outfile_fp;
+ char *the_command = 0;
+ bstring *my_file = NULL;
+ char my_temp[LY_MAXPATH];
+ int FnameTotal, FnameNum = -1;
+ lynx_list_item_type *cur_printer;
+
+ outfile_fp = LYOpenTemp(my_temp,
+ (HTisDocumentSource())
+ ? HTML_SUFFIX
+ : TEXT_SUFFIX,
+ "w");
+ if (outfile_fp == NULL) {
+ CannotPrint(FILE_ALLOC_FAILED);
+ }
+
+ if (LYPrependBaseToSource && HTisDocumentSource()) {
+ /*
+ * Added the document's base as a BASE tag to the top of the file. May
+ * create technically invalid HTML, but will help get any partial or
+ * relative URLs resolved properly if no BASE tag is present to replace
+ * it. - FM
+ */
+ fprintf(outfile_fp,
+ "<!-- X-URL: %s -->\n<BASE HREF=\"%s\">\n\n",
+ newdoc->address, content_base);
+ }
+ print_wwwfile_to_fd(outfile_fp, FALSE, FALSE); /* PRINTER */
+ if (keypad_mode)
+ printlist(outfile_fp, FALSE);
+
+ LYCloseTempFP(outfile_fp);
+
+ /* find the right printer number */
+ {
+ int count = 0;
+
+ for (cur_printer = printers;
+ count < printer_number;
+ count++, cur_printer = cur_printer->next) ; /* null body */
+ }
+
+ /*
+ * Commands have the form "command %s [%s] [etc]" where %s is the filename
+ * and the second optional %s is the suggested filename.
+ */
+ if (cur_printer->command == NULL) {
+ CannotPrint(PRINTER_MISCONF_ERROR);
+ }
+
+ /*
+ * Check for two '%s' and ask for the second filename argument if there
+ * is.
+ */
+ BStrCopy0(my_file, "");
+ if (HTCountCommandArgs(cur_printer->command) >= 2) {
+ _statusline(FILENAME_PROMPT);
+ again:
+ SetupFilename(&my_file, sug_filename);
+ check_again:
+ switch (RecallFilename(&my_file, &FirstRecall, &FnameNum,
+ &FnameTotal, PRINT_FLAG)) {
+ case FN_INIT:
+ goto again;
+ case FN_READ:
+ goto check_again;
+ case FN_QUIT:
+ goto done;
+ default:
+ break;
+ }
+
+ if (no_dotfiles || !show_dotfiles) {
+ if (*LYPathLeaf(my_file->str) == '.') {
+ HTAlert(FILENAME_CANNOT_BE_DOT);
+ _statusline(NEW_FILENAME_PROMPT);
+ FirstRecall = TRUE;
+ FnameNum = FnameTotal;
+ goto again;
+ }
+ }
+ /*
+ * Cancel if the user entered "/dev/null" on Unix, or an "nl:" path
+ * on VMS. - FM
+ */
+ if (LYIsNullDevice(my_file->str)) {
+ CancelPrint(PRINT_REQUEST_CANCELLED);
+ }
+ HTAddSugFilename(my_file->str);
+ }
+#ifdef SH_EX /* 1999/01/04 (Mon) 09:37:03 */
+ HTAddParam(&the_command, cur_printer->command, 1, my_temp);
+ if (!isBEmpty(my_file)) {
+ HTAddParam(&the_command, cur_printer->command, 2, my_file->str);
+ HTEndParam(&the_command, cur_printer->command, 3);
+ } else {
+ HTEndParam(&the_command, cur_printer->command, 2);
+ }
+#else
+ HTAddParam(&the_command, cur_printer->command, 1, my_temp);
+ HTAddParam(&the_command, cur_printer->command, 2, my_file->str);
+ HTEndParam(&the_command, cur_printer->command, 2);
+#endif
+
+ /*
+ * Move the cursor to the top of the screen so that output from system'd
+ * commands don't scroll up the screen.
+ */
+ LYmove(1, 1);
+
+ stop_curses();
+ CTRACE((tfp, "command: %s\n", the_command));
+ SetOutputMode(O_TEXT);
+ printf(PRINTING_FILE);
+ /*
+ * Set various bits of document information as environment variables, for
+ * use by external print scripts/etc. On UNIX, We assume there are values,
+ * and leave NULL value checking up to the external PRINTER: cmd/script -
+ * KED
+ */
+ SET_ENVIRON(LYNX_PRINT_TITLE, HText_getTitle(), "No Title");
+ SET_ENVIRON(LYNX_PRINT_URL, newdoc->address, "No URL");
+ SET_ENVIRON(LYNX_PRINT_DATE, HText_getDate(), "No Date");
+ SET_ENVIRON(LYNX_PRINT_LASTMOD, HText_getLastModified(), "No LastMod");
+
+ LYSystem(the_command);
+ FREE(the_command);
+ (void) LYRemoveTemp(my_temp);
+
+ /*
+ * Remove the various LYNX_PRINT_xxxx logicals. - KED
+ * [could use unsetenv(), but it's not portable]
+ */
+ SET_ENVIRON(LYNX_PRINT_TITLE, "", "");
+ SET_ENVIRON(LYNX_PRINT_URL, "", "");
+ SET_ENVIRON(LYNX_PRINT_DATE, "", "");
+ SET_ENVIRON(LYNX_PRINT_LASTMOD, "", "");
+
+ fflush(stdout);
+#ifndef VMS
+ signal(SIGINT, cleanup_sig);
+#endif /* !VMS */
+#ifdef SH_EX
+ fprintf(stdout, gettext(" Print job complete.\n"));
+ fflush(stdout);
+#endif
+ SetOutputMode(O_BINARY);
+ LYSleepMsg();
+ start_curses();
+
+ done: /* send_file_to_printer() */
+ BStrFree(my_file);
+ return;
+}
+
+static void send_file_to_screen(DocInfo *newdoc,
+ char *content_base,
+ int Lpansi)
+{
+ FILE *outfile_fp;
+ bstring *prompt = NULL;
+
+ if (Lpansi) {
+ _statusline(CHECK_PRINTER);
+ } else {
+ _statusline(PRESS_RETURN_TO_BEGIN);
+ }
+
+ BStrCopy0(prompt, "");
+ if (LYgetBString(&prompt, FALSE, 0, NORECALL) < 0) {
+ CancelPrint(PRINT_REQUEST_CANCELLED);
+ } else {
+ outfile_fp = stdout;
+
+ stop_curses();
+ SetOutputMode(O_TEXT);
+
+#ifndef VMS
+ signal(SIGINT, SIG_IGN);
+#endif /* !VMS */
+
+ if (LYPrependBaseToSource && HTisDocumentSource()) {
+ /*
+ * Added the document's base as a BASE tag to the top of the file. May
+ * create technically invalid HTML, but will help get any partial or
+ * relative URLs resolved properly if no BASE tag is present to replace
+ * it. - FM
+ */
+ fprintf(outfile_fp,
+ "<!-- X-URL: %s -->\n<BASE HREF=\"%s\">\n\n",
+ newdoc->address, content_base);
+ }
+ if (Lpansi)
+ printf("\033[5i");
+ print_wwwfile_to_fd(outfile_fp, FALSE, FALSE); /* SCREEN */
+ if (keypad_mode)
+ printlist(outfile_fp, FALSE);
+
+#ifdef VMS
+ if (HadVMSInterrupt) {
+ HadVMSInterrupt = FALSE;
+ start_curses();
+ CancelPrint(PRINT_REQUEST_CANCELLED);
+ }
+#endif /* VMS */
+ if (Lpansi) {
+ printf("\n\014"); /* Form feed */
+ printf("\033[4i");
+ fflush(stdout); /* refresh to screen */
+ } else {
+ fprintf(stdout, "\n\n%s", PRESS_RETURN_TO_FINISH);
+ fflush(stdout); /* refresh to screen */
+ (void) LYgetch(); /* grab some user input to pause */
+#ifdef VMS
+ HadVMSInterrupt = FALSE;
+#endif /* VMS */
+ }
+#ifdef SH_EX
+ fprintf(stdout, "\n");
+#endif
+ SetOutputMode(O_BINARY);
+ start_curses();
+ }
+
+ done: /* send_file_to_screen() */
+ BStrFree(prompt);
+ return;
+}
+
+int printfile(DocInfo *newdoc)
+{
+ BOOLEAN Lpansi = FALSE;
+ DocAddress WWWDoc;
+ char *content_base = NULL;
+ char *content_location = NULL;
+ char *cp = NULL;
+ char *link_info = NULL;
+ char *sug_filename = NULL;
+ int lines_in_file = 0;
+ int pagelen = 0;
+ int printer_number = 0;
+ int type = 0;
+
+ /*
+ * Extract useful info from URL.
+ */
+ StrAllocCopy(link_info, newdoc->address + 12);
+
+ /*
+ * Reload the file we want to print into memory.
+ */
+ LYpop(newdoc);
+ 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;
+ if (!HTLoadAbsolute(&WWWDoc))
+ return (NOT_FOUND);
+
+ /*
+ * If we have an explicit content-base, we may use it even if not in source
+ * mode. - kw
+ */
+ if (HText_getContentBase()) {
+ StrAllocCopy(content_base, HText_getContentBase());
+ LYRemoveBlanks(content_base);
+ if (isEmpty(content_base)) {
+ FREE(content_base);
+ }
+ }
+ /*
+ * If document is source, load the content_base and content_location
+ * strings. - FM
+ */
+ if (HTisDocumentSource()) {
+ if (HText_getContentLocation()) {
+ StrAllocCopy(content_location, HText_getContentLocation());
+ LYRemoveBlanks(content_location);
+ if (isEmpty(content_location)) {
+ FREE(content_location);
+ }
+ }
+ if (!content_base) {
+ if ((content_location) && is_url(content_location)) {
+ StrAllocCopy(content_base, content_location);
+ } else {
+ StrAllocCopy(content_base, newdoc->address);
+ }
+ }
+ if (!content_location) {
+ StrAllocCopy(content_location, newdoc->address);
+ }
+ }
+
+ sug_filename = suggested_filename(newdoc);
+
+ /*
+ * Get the number of lines in the file.
+ */
+ if ((cp = LYstrstr(link_info, "lines=")) != NULL) {
+ /*
+ * Terminate prev string here.
+ */
+ *cp = '\0';
+ /*
+ * Number of characters in "lines=".
+ */
+ cp += 6;
+
+ lines_in_file = atoi(cp);
+ }
+
+ /*
+ * Determine the type.
+ */
+ if (LYstrstr(link_info, "LOCAL_FILE")) {
+ type = TO_FILE;
+ } else if (LYstrstr(link_info, "TO_SCREEN")) {
+ type = TO_SCREEN;
+ } else if (LYstrstr(link_info, "LPANSI")) {
+ Lpansi = TRUE;
+ type = TO_SCREEN;
+ } else if (LYstrstr(link_info, "MAIL_FILE")) {
+ type = MAIL;
+ } else if (LYstrstr(link_info, "PRINTER")) {
+ type = PRINTER;
+
+ if ((cp = LYstrstr(link_info, "number=")) != NULL) {
+ /* number of characters in "number=" */
+ cp += 7;
+ printer_number = atoi(cp);
+ }
+ if ((cp = LYstrstr(link_info, "pagelen=")) != NULL) {
+ /* number of characters in "pagelen=" */
+ cp += 8;
+ pagelen = atoi(cp);
+ } else {
+ /* default to 66 lines */
+ pagelen = 66;
+ }
+ }
+
+ /*
+ * Act on the request. - FM
+ */
+ switch (type) {
+
+ case TO_FILE:
+ send_file_to_file(newdoc, content_base, sug_filename);
+ break;
+
+ case MAIL:
+ send_file_to_mail(newdoc, content_base, content_location);
+ break;
+
+ case TO_SCREEN:
+ if (confirm_by_pages(CONFIRM_LONG_SCREEN_PRINT, lines_in_file, LYlines))
+ send_file_to_screen(newdoc, content_base, Lpansi);
+ break;
+
+ case PRINTER:
+ if (confirm_by_pages(CONFIRM_LONG_PAGE_PRINT, lines_in_file, pagelen))
+ send_file_to_printer(newdoc, content_base, sug_filename, printer_number);
+ break;
+
+ } /* end switch */
+
+ FREE(link_info);
+ FREE(sug_filename);
+ FREE(content_base);
+ FREE(content_location);
+ return (NORMAL);
+}
+
+#if USE_VMS_MAILER
+static int remove_quotes(char *string)
+{
+ int i;
+
+ for (i = 0; string[i] != '\0'; i++)
+ if (string[i] == '"')
+ string[i] = ' ';
+ else if (string[i] == '&')
+ string[i] = ' ';
+ else if (string[i] == '|')
+ string[i] = ' ';
+
+ return (0);
+}
+#endif /* USE_VMS_MAILER */
+
+/*
+ * Mail subject may have 8-bit characters and they are in display charset.
+ * There is no stable practice for 8-bit subject encodings: MIME defines
+ * "quoted-printable" which holds charset info but most mailers still don't
+ * support it. On the other hand many mailers send open 8-bit subjects without
+ * charset info and use local assumption for certain countries. Besides that,
+ * obsolete SMTP software is not 8bit clean but still in use, it strips the
+ * characters in 128-160 range from subjects which may be a fault outside
+ * iso-8859-XX.
+ *
+ * We translate subject to "outgoing_mail_charset" (defined in lynx.cfg) it may
+ * correspond to US-ASCII as the safest value or any other lynx character
+ * handler, -1 for no translation (so display charset).
+ *
+ * Always returns a new allocated string which has to be freed.
+ */
+#include <LYCharUtils.h>
+static char *subject_translate8bit(char *source)
+{
+ char *target = NULL;
+
+ int charset_in, charset_out;
+
+ int i = outgoing_mail_charset; /* from lynx.cfg, -1 by default */
+
+ StrAllocCopy(target, source);
+ if (i < 0
+ || i == current_char_set
+ || LYCharSet_UC[current_char_set].enc == UCT_ENC_CJK
+ || LYCharSet_UC[i].enc == UCT_ENC_CJK) {
+ return (target); /* OK */
+ } else {
+ charset_out = i;
+ charset_in = current_char_set;
+ }
+
+ LYUCTranslateBackHeaderText(&target, charset_in, charset_out, YES);
+
+ return (target);
+}
+
+/*
+ * print_options writes out the current printer choices to a file
+ * so that the user can select printers in the same way that
+ * they select all other links
+ * printer links look like
+ *
+ * LYNXPRINT://LOCAL_FILE/lines=# print to a local file
+ * LYNXPRINT://TO_SCREEN/lines=# print to the screen
+ * LYNXPRINT://LPANSI/lines=# print to the local terminal
+ * LYNXPRINT://MAIL_FILE/lines=# mail the file
+ * LYNXPRINT://PRINTER/lines=#/number=# print to printer number #
+ */
+int print_options(char **newfile,
+ const char *printed_url,
+ int lines_in_file)
+{
+ static char my_temp[LY_MAXPATH] = "\0";
+ char *buffer = 0;
+ int count;
+ int pages;
+ FILE *fp0;
+ lynx_list_item_type *cur_printer;
+
+ if ((fp0 = InternalPageFP(my_temp, TRUE)) == 0)
+ return (-1);
+
+ LYLocalFileToURL(newfile, my_temp);
+
+ BeginInternalPage(fp0, PRINT_OPTIONS_TITLE, PRINT_OPTIONS_HELP);
+
+ fprintf(fp0, "<pre>\n");
+
+ /* pages = lines_in_file/66 + 1; */
+ pages = (lines_in_file + 65) / 66;
+ HTSprintf0(&buffer,
+ " <em>%s</em> %s\n <em>%s</em> %d\n <em>%s</em> %d %s %s\n",
+ gettext("Document:"), printed_url,
+ gettext("Number of lines:"), lines_in_file,
+ gettext("Number of pages:"), pages,
+ (pages > 1 ? gettext("pages") : gettext("page")),
+ gettext("(approximately)"));
+ fputs(buffer, fp0);
+ FREE(buffer);
+
+ if (no_print || no_disk_save || no_mail)
+ fprintf(fp0,
+ " <em>%s</em>\n",
+ gettext("Some print functions have been disabled!"));
+
+ fprintf(fp0, "\n%s\n",
+ (user_mode == NOVICE_MODE)
+ ? gettext("Standard print options:")
+ : gettext("Print options:"));
+
+ if (no_disk_save == FALSE && no_print == FALSE) {
+ fprintf(fp0,
+ " <a href=\"%s//LOCAL_FILE/lines=%d\">%s</a>\n",
+ STR_LYNXPRINT,
+ lines_in_file,
+ gettext("Save to a local file"));
+ } else {
+ fprintf(fp0, " <em>%s</em>\n", gettext("Save to disk disabled"));
+ }
+ if (no_mail == FALSE && local_host_only == FALSE)
+ fprintf(fp0,
+ " <a href=\"%s//MAIL_FILE/lines=%d\">%s</a>\n",
+ STR_LYNXPRINT,
+ lines_in_file,
+ gettext("Mail the file"));
+
+#if defined(UNIX) || defined(VMS)
+ fprintf(fp0,
+ " <a href=\"%s//TO_SCREEN/lines=%d\">%s</a>\n",
+ STR_LYNXPRINT,
+ lines_in_file,
+ gettext("Print to the screen"));
+ fprintf(fp0,
+ " <a href=\"%s//LPANSI/lines=%d\">%s</a>\n",
+ STR_LYNXPRINT,
+ lines_in_file,
+ gettext("Print out on a printer attached to your vt100 terminal"));
+#endif
+
+ if (user_mode == NOVICE_MODE)
+ fprintf(fp0, "\n%s\n", gettext("Local additions:"));
+
+ for (count = 0, cur_printer = printers; cur_printer != NULL;
+ cur_printer = cur_printer->next, count++)
+ if (no_print == FALSE || cur_printer->always_enabled) {
+ fprintf(fp0,
+ " <a href=\"%s//PRINTER/number=%d/pagelen=%d/lines=%d\">",
+ STR_LYNXPRINT,
+ count, cur_printer->pagelen, lines_in_file);
+ fprintf(fp0, "%s", (cur_printer->name ?
+ cur_printer->name : "No Name Given"));
+ fprintf(fp0, "</a>\n");
+ }
+ fprintf(fp0, "</pre>\n");
+ EndInternalPage(fp0);
+ LYCloseTempFP(fp0);
+
+ LYforce_no_cache = TRUE;
+ return (0);
+}
+
+/*
+ * General purpose filename getter.
+ *
+ * Returns a pointer to an absolute filename string, if the input filename
+ * exists, and is readable. Returns NULL if the input was cancelled (^G, or CR
+ * on empty input).
+ *
+ * The pointer to the filename string needs to be free()'d by the caller (when
+ * non-NULL).
+ *
+ * --KED 02/21/99
+ */
+char *GetFileName(void)
+{
+ struct stat stat_info;
+
+ bstring *fbuf = NULL;
+ bstring *tbuf = NULL;
+ char *result = NULL;
+
+ BOOLEAN FirstRecall = TRUE;
+ int FnameNum = -1;
+ int FnameTotal;
+
+ _statusline(FILENAME_PROMPT);
+
+ retry:
+ /*
+ * No initial filename.
+ */
+ SetupFilename(&fbuf, "");
+
+ check_recall:
+ /*
+ * Go get a filename (it would be nice to do TAB == filename-completion as
+ * the name is entered, but we'll save doing that for another time.
+ */
+ switch (RecallFilename(&fbuf, &FirstRecall, &FnameNum,
+ &FnameTotal, GENERIC_FLAG)) {
+ case FN_INIT:
+ goto retry;
+ case FN_READ:
+ goto check_recall;
+ case FN_QUIT:
+ goto cleanup;
+ default:
+ break;
+ }
+
+ /*
+ * Add raw input form to list ... we may want to reuse/edit it on a
+ * subsequent call, etc.
+ */
+#ifdef VMS
+ if (0 == strncasecomp(fbuf->str, "sys$disk:", 9)) {
+ if (0 == StrNCmp((fbuf->str + 9), "[]", 2)) {
+ HTAddSugFilename(fbuf->str + 11);
+ } else {
+ HTAddSugFilename(fbuf->str + 9);
+ }
+ } else {
+ HTAddSugFilename(fbuf->str);
+ }
+#else
+ HTAddSugFilename(fbuf->str);
+#endif /* VMS */
+
+ /*
+ * Expand tilde's, make filename absolute, etc.
+ */
+ BStrCopy0(tbuf, "");
+ if (!LYValidateFilename(&tbuf, &fbuf))
+ goto cleanup;
+
+ /*
+ * Check for file existence; readability.
+ */
+ if ((stat(tbuf->str, &stat_info) < 0) ||
+ (!(S_ISREG(stat_info.st_mode)
+#ifdef S_IFLNK
+ || S_ISLNK(stat_info.st_mode)
+#endif /* S_IFLNK */
+ ))) {
+ HTInfoMsg(FILE_DOES_NOT_EXIST);
+ _statusline(FILE_DOES_NOT_EXIST_RE);
+ FirstRecall = TRUE;
+ FnameNum = FnameTotal;
+ goto retry;
+ }
+
+ if (!LYCanReadFile(tbuf->str)) {
+ HTInfoMsg(FILE_NOT_READABLE);
+ _statusline(FILE_NOT_READABLE_RE);
+ FirstRecall = TRUE;
+ FnameNum = FnameTotal;
+ goto retry;
+ }
+
+ /*
+ * We have a valid filename, and readable file. Return it to the caller.
+ *
+ * The returned pointer should be free()'d by the caller.
+ */
+ StrAllocCopy(result, tbuf->str);
+
+ cleanup:
+ BStrFree(fbuf);
+ BStrFree(tbuf);
+ return (result);
+}
diff --git a/src/LYPrint.h b/src/LYPrint.h
new file mode 100644
index 0000000..645b874
--- /dev/null
+++ b/src/LYPrint.h
@@ -0,0 +1,20 @@
+#ifndef LYPRINT_H
+#define LYPRINT_H
+
+#ifndef LYSTRUCTS_H
+#include <LYStructs.h>
+#endif /* LYSTRUCTS_H */
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+ extern int printfile(DocInfo *newdoc);
+ extern int print_options(char **newfile,
+ const char *printed_url,
+ int lines_in_file);
+ extern char *GetFileName(void);
+
+#ifdef __cplusplus
+}
+#endif
+#endif /* LYPRINT_H */
diff --git a/src/LYReadCFG.c b/src/LYReadCFG.c
new file mode 100644
index 0000000..e4ab796
--- /dev/null
+++ b/src/LYReadCFG.c
@@ -0,0 +1,2682 @@
+/*
+ * $LynxId: LYReadCFG.c,v 1.200 2021/07/29 22:52:55 tom Exp $
+ */
+#ifndef NO_RULES
+#include <HTRules.h>
+#else
+#include <HTUtils.h>
+#endif
+#include <HTTP.h> /* 'reloading' flag */
+#include <HTFile.h>
+#include <HTInit.h>
+#include <UCMap.h>
+
+#include <LYUtils.h>
+#include <GridText.h>
+#include <LYStrings.h>
+#include <LYStructs.h>
+#include <LYGlobalDefs.h>
+#include <LYCharSets.h>
+#include <LYCharUtils.h>
+#include <LYKeymap.h>
+#include <LYJump.h>
+#include <LYGetFile.h>
+#include <LYCgi.h>
+#include <LYCurses.h>
+#include <LYBookmark.h>
+#include <LYCookie.h>
+#include <LYReadCFG.h>
+#include <HTAlert.h>
+#include <LYHistory.h>
+#include <LYPrettySrc.h>
+#include <LYrcFile.h>
+
+#ifdef DIRED_SUPPORT
+#include <LYLocal.h>
+#endif /* DIRED_SUPPORT */
+
+#include <LYexit.h>
+#include <LYLeaks.h>
+
+#ifndef DISABLE_NEWS
+#include <HTNews.h>
+#endif
+
+BOOLEAN have_read_cfg = FALSE;
+BOOLEAN LYUseNoviceLineTwo = TRUE;
+
+/*
+ * Translate a TRUE/FALSE field in a string buffer.
+ */
+static BOOL is_true(const char *string)
+{
+ if (!strcasecomp(string, "TRUE") || !strcasecomp(string, "ON"))
+ return (TRUE);
+ else
+ return (FALSE);
+}
+
+/*
+ * Find an unescaped colon in a string buffer.
+ */
+static const char *find_colon(const char *buffer)
+{
+ char ch;
+ const char *buf = buffer;
+
+ if (buf == NULL)
+ return NULL;
+
+ while ((ch = *buf) != 0) {
+ if (ch == ':')
+ return buf;
+ if (ch == '\\') {
+ buf++;
+ if (*buf == 0)
+ break;
+ }
+ buf++;
+ }
+ return NULL;
+}
+
+static void free_item_list_item(lynx_list_item_type **list,
+ lynx_list_item_type *ptr)
+{
+ lynx_list_item_type *prev;
+ lynx_list_item_type *cur;
+
+ for (cur = *list, prev = 0; cur != 0; prev = cur, cur = cur->next) {
+ if (cur == ptr) {
+
+ if (prev != 0)
+ prev->next = cur->next;
+ else
+ *list = cur->next;
+
+ FREE(cur->name);
+ FREE(cur->menu_name);
+ FREE(cur->command);
+ FREE(cur);
+ break;
+ }
+ }
+}
+
+static void free_item_list(lynx_list_item_type **ptr)
+{
+ while (*ptr != 0) {
+ free_item_list_item(ptr, *ptr);
+ }
+}
+
+/*
+ * Function for freeing the DOWNLOADER and UPLOADER menus list. - FM
+ */
+static void free_all_item_lists(void)
+{
+ free_item_list(&printers);
+ free_item_list(&downloaders);
+#ifdef DIRED_SUPPORT
+ free_item_list(&uploaders);
+#endif /* DIRED_SUPPORT */
+
+#ifdef USE_EXTERNALS
+ free_item_list(&externals);
+#endif /* USE_EXTERNALS */
+
+ return;
+}
+
+static const char *parse_list_bool(BOOL *target, const char *source)
+{
+ const char *result;
+
+ source = LYSkipCBlanks(source);
+ result = find_colon(source);
+
+ if (*source != '\0') {
+ char temp[20];
+ size_t len = ((result != 0)
+ ? (size_t) (result - source)
+ : strlen(source));
+
+ if (len > sizeof(temp))
+ len = (sizeof(temp) - 1);
+ LYStrNCpy(temp, source, len);
+ *target = is_true(temp);
+ CTRACE2(TRACE_CFG, (tfp, "parse_list_bool(%s) '%d'\n", source, *target));
+ }
+ return result;
+}
+
+static const char *parse_list_int(int *target, const char *source)
+{
+ const char *result;
+
+ source = LYSkipCBlanks(source);
+ result = find_colon(source);
+
+ if (*source != '\0') {
+ *target = atoi(source);
+ CTRACE2(TRACE_CFG, (tfp, "parse_list_int(%s) '%d'\n", source, *target));
+ }
+ return result;
+}
+
+static const char *parse_list_string(char **target, const char *source)
+{
+ const char *result;
+
+ source = LYSkipCBlanks(source);
+ result = find_colon(source);
+
+ if (*source != '\0') {
+ const char *next = ((result == 0)
+ ? (source + strlen(source))
+ : result);
+
+ *target = typecallocn(char, (size_t) (next - source + 1));
+
+ if (*target == NULL)
+ outofmem(__FILE__, "read_cfg");
+ LYStrNCpy(*target, source, (next - source));
+ remove_backslashes(*target);
+
+ CTRACE2(TRACE_CFG, (tfp, "parse_list_string(%s) '%s'\n", source, *target));
+ }
+ return result;
+}
+
+/*
+ * Process string buffer fields for DOWNLOADER or UPLOADER
+ * or PRINTERS or EXTERNALS menus
+ */
+static void add_item_to_list(char *buffer,
+ lynx_list_item_type **list_ptr,
+ int special,
+ int menu_name)
+{
+ const char *colon, *last_colon;
+ lynx_list_item_type *cur_item, *prev_item;
+
+ /*
+ * Check if the XWINDOWS or NON_XWINDOWS keyword is present in the last
+ * field, and act properly when found depending if external environment
+ * $DISPLAY variable is set.
+ */
+ if ((colon = find_colon(buffer)) == 0) {
+ return;
+ }
+ for (last_colon = colon;
+ (colon = find_colon(last_colon + 1)) != 0;
+ last_colon = colon) {
+ ;
+ }
+
+ /*
+ * If colon equals XWINDOWS then only continue
+ * if there is a $DISPLAY variable
+ */
+ if (!strcasecomp(last_colon + 1, "XWINDOWS")) {
+ if (LYgetXDisplay() == NULL)
+ return;
+ }
+ /*
+ * If colon equals NON_XWINDOWS then only continue
+ * if there is no $DISPLAY variable
+ */
+ else if (!strcasecomp(last_colon + 1, "NON_XWINDOWS")) {
+ if (LYgetXDisplay() != NULL)
+ return;
+ }
+
+ /*
+ * Make a linked list
+ */
+ if (*list_ptr == NULL) {
+ /*
+ * First item.
+ */
+ cur_item = typecalloc(lynx_list_item_type);
+
+ if (cur_item == NULL)
+ outofmem(__FILE__, "read_cfg");
+
+ *list_ptr = cur_item;
+#ifdef LY_FIND_LEAKS
+ atexit(free_all_item_lists);
+#endif
+ } else {
+ /*
+ * Find the last item.
+ */
+ for (prev_item = *list_ptr;
+ prev_item->next != NULL;
+ prev_item = prev_item->next) ; /* null body */
+ cur_item = typecalloc(lynx_list_item_type);
+
+ if (cur_item == NULL)
+ outofmem(__FILE__, "read_cfg");
+ else
+ prev_item->next = cur_item;
+ }
+ /* fill-in nonzero default values */
+ cur_item->pagelen = 66;
+
+ /*
+ * Find first unescaped colon and process fields
+ */
+ if (find_colon(buffer) != NULL) {
+ colon = parse_list_string(&(cur_item->name), buffer);
+
+ if (colon && menu_name) {
+ colon = parse_list_string(&(cur_item->menu_name), colon + 1);
+ }
+ if (colon) {
+ colon = parse_list_string(&(cur_item->command), colon + 1);
+ }
+ if (colon) {
+ colon = parse_list_bool(&(cur_item->always_enabled), colon + 1);
+ }
+ if (colon) {
+ if (special) {
+ (void) parse_list_int(&(cur_item->pagelen), colon + 1);
+ } else {
+ (void) parse_list_bool(&(cur_item->override_action), colon + 1);
+ }
+ }
+ }
+
+ /* ignore empty data */
+ if (cur_item->name == NULL
+ || cur_item->command == NULL) {
+ CTRACE2(TRACE_CFG, (tfp, "ignoring incomplete list_item '%s'\n", buffer));
+ free_item_list_item(list_ptr, cur_item);
+ } else if (cur_item->menu_name == NULL) {
+ StrAllocCopy(cur_item->menu_name, cur_item->command);
+ }
+}
+
+lynx_list_item_type *find_item_by_number(lynx_list_item_type *list_ptr,
+ char *number)
+{
+ int value = atoi(number);
+
+ while (value-- >= 0 && list_ptr != 0) {
+ list_ptr = list_ptr->next;
+ }
+ return list_ptr;
+}
+
+int match_item_by_name(lynx_list_item_type *ptr,
+ const char *name,
+ int only_overriders)
+{
+ return
+ (ptr->command != 0
+ && !strncasecomp(ptr->name, name, (int) strlen(ptr->name))
+ && (only_overriders ? ptr->override_action : 1));
+}
+
+#if defined(USE_COLOR_STYLE) || defined(USE_COLOR_TABLE)
+
+#ifndef COLOR_WHITE
+#define COLOR_WHITE 7
+#endif
+
+#ifndef COLOR_BLACK
+#define COLOR_BLACK 0
+#endif
+
+#ifdef USE_DEFAULT_COLORS
+int default_fg = DEFAULT_COLOR;
+int default_bg = DEFAULT_COLOR;
+
+#else
+int default_fg = COLOR_WHITE;
+int default_bg = COLOR_BLACK;
+#endif
+
+static const char *Color_Strings[16] =
+{
+ "black",
+ "red",
+ "green",
+ "brown",
+ "blue",
+ "magenta",
+ "cyan",
+ "lightgray",
+ "gray",
+ "brightred",
+ "brightgreen",
+ "yellow",
+ "brightblue",
+ "brightmagenta",
+ "brightcyan",
+ "white"
+};
+
+#if defined(PDCURSES) && !defined(XCURSES)
+/*
+ * PDCurses (and possibly some other implementations) use a non-ANSI set of
+ * codes for colors.
+ */
+static int ColorCode(int color)
+{
+ /* *INDENT-OFF* */
+ static int map[] =
+ {
+ 0, 4, 2, 6, 1, 5, 3, 7,
+ 8, 12, 10, 14, 9, 13, 11, 15
+ };
+ /* *INDENT-ON* */
+
+ return map[color];
+}
+#else
+#define ColorCode(color) (color)
+#endif
+
+BOOL default_color_reset = FALSE;
+
+/*
+ * Validator for COLOR fields.
+ */
+int check_color(const char *color,
+ int the_default)
+{
+ int i;
+
+ CTRACE2(TRACE_STYLE, (tfp, "check_color(%s,%d)\n", color, the_default));
+ if (!strcasecomp(color, "default")) {
+#ifdef USE_DEFAULT_COLORS
+ if (LYuse_default_colors && !default_color_reset)
+ the_default = DEFAULT_COLOR;
+#endif /* USE_DEFAULT_COLORS */
+ CTRACE2(TRACE_STYLE, (tfp, "=> default %d\n", the_default));
+ return the_default;
+ }
+ if (!strcasecomp(color, "nocolor"))
+ return NO_COLOR;
+
+ for (i = 0; i < 16; i++) {
+ if (!strcasecomp(color, Color_Strings[i])) {
+ int c = ColorCode(i);
+
+ CTRACE2(TRACE_STYLE, (tfp, "=> %d\n", c));
+ return c;
+ }
+ }
+ CTRACE2(TRACE_STYLE, (tfp, "=> ERR_COLOR\n"));
+ return ERR_COLOR;
+}
+
+const char *lookup_color(int code)
+{
+ unsigned n;
+
+ for (n = 0; n < 16; n++) {
+ if ((int) ColorCode(n) == code)
+ return Color_Strings[n];
+ }
+ return "default";
+}
+#endif /* USE_COLOR_STYLE || USE_COLOR_TABLE */
+
+#if defined(USE_COLOR_TABLE) || defined(EXP_ASSUMED_COLOR)
+
+/*
+ * Exit routine for failed COLOR parsing.
+ */
+static void exit_with_color_syntax(char *error_line)
+{
+ unsigned int i;
+
+ fprintf(stderr, gettext("\
+Syntax Error parsing COLOR in configuration file:\n\
+The line must be of the form:\n\
+COLOR:INTEGER:FOREGROUND:BACKGROUND\n\
+\n\
+Here FOREGROUND and BACKGROUND must be one of:\n\
+The special strings 'nocolor' or 'default', or\n")
+ );
+ for (i = 0; i < 16; i += 4) {
+ fprintf(stderr, "%16s %16s %16s %16s\n",
+ Color_Strings[i], Color_Strings[i + 1],
+ Color_Strings[i + 2], Color_Strings[i + 3]);
+ }
+ fprintf(stderr, "%s\nCOLOR:%s\n", gettext("Offending line:"), error_line);
+ exit_immediately(EXIT_FAILURE);
+}
+#endif /* defined(USE_COLOR_TABLE) || defined(EXP_ASSUMED_COLOR) */
+
+#if defined(USE_COLOR_TABLE)
+/*
+ * Process string buffer fields for COLOR setting.
+ */
+static void parse_color(char *buffer)
+{
+ int color;
+ const char *fg, *bg;
+ char *temp_fg = 0;
+
+ /*
+ * We are expecting a line of the form:
+ * INTEGER:FOREGROUND:BACKGROUND
+ */
+ color = atoi(buffer);
+ if (NULL == (fg = find_colon(buffer)))
+ exit_with_color_syntax(buffer);
+
+ if (NULL == (bg = find_colon(++fg)))
+ exit_with_color_syntax(buffer);
+
+ StrAllocCopy(temp_fg, fg);
+ temp_fg[bg++ - fg] = '\0';
+
+#if defined(USE_SLANG)
+ if ((check_color(temp_fg, default_fg) == ERR_COLOR) ||
+ (check_color(bg, default_bg) == ERR_COLOR))
+ exit_with_color_syntax(buffer);
+
+ SLtt_set_color(color, NULL, temp_fg, bg);
+#else
+ if (lynx_chg_color(color,
+ check_color(temp_fg, default_fg),
+ check_color(bg, default_bg)) < 0)
+ exit_with_color_syntax(buffer);
+#endif
+ FREE(temp_fg);
+}
+#endif /* USE_COLOR_TABLE */
+/* *INDENT-OFF* */
+#ifdef USE_SOURCE_CACHE
+static Config_Enum tbl_source_cache[] = {
+ { "FILE", SOURCE_CACHE_FILE },
+ { "MEMORY", SOURCE_CACHE_MEMORY },
+ { "NONE", SOURCE_CACHE_NONE },
+ { NULL, -1 },
+};
+
+static Config_Enum tbl_abort_source_cache[] = {
+ { "KEEP", SOURCE_CACHE_FOR_ABORTED_KEEP },
+ { "DROP", SOURCE_CACHE_FOR_ABORTED_DROP },
+ { NULL, -1 },
+};
+#endif
+/* *INDENT-ON* */
+
+#define PARSE_ADD(n,v) {n, CONF_ADD_ITEM, UNION_ADD(v), 0}
+#define PARSE_SET(n,v) {n, CONF_BOOL, UNION_SET(v), 0}
+#define PARSE_ENU(n,v,t) {n, CONF_ENUM, UNION_INT(v), t}
+#define PARSE_INT(n,v) {n, CONF_INT, UNION_INT(v), 0}
+#define PARSE_TIM(n,v) {n, CONF_TIME, UNION_INT(v), 0}
+#define PARSE_STR(n,v) {n, CONF_STR, UNION_STR(v), 0}
+#define PARSE_PRG(n,v) {n, CONF_PRG, UNION_DEF(v), 0}
+#define PARSE_Env(n,v) {n, CONF_ENV, UNION_ENV(v), 0}
+#define PARSE_ENV(n,v) {n, CONF_ENV2, UNION_ENV(v), 0}
+#define PARSE_FUN(n,v) {n, CONF_FUN, UNION_FUN(v), 0}
+#define PARSE_REQ(n,v) {n, CONF_INCLUDE, UNION_FUN(v), 0}
+#define PARSE_LST(n,v) {n, CONF_ADD_STRING, UNION_LST(v), 0}
+#define PARSE_DEF(n,v) {n, CONF_ADD_TRUSTED, UNION_DEF(v), 0}
+#define PARSE_NIL {NULL, CONF_NIL, UNION_DEF(0), 0}
+
+typedef enum {
+ CONF_NIL = 0
+ ,CONF_BOOL /* BOOLEAN type */
+ ,CONF_FUN
+ ,CONF_TIME
+ ,CONF_ENUM
+ ,CONF_INT
+ ,CONF_STR
+ ,CONF_PRG
+ ,CONF_ENV /* from environment variable */
+ ,CONF_ENV2 /* from environment VARIABLE */
+ ,CONF_INCLUDE /* include file-- handle special */
+ ,CONF_ADD_ITEM
+ ,CONF_ADD_STRING
+ ,CONF_ADD_TRUSTED
+} Conf_Types;
+
+typedef struct {
+ const char *name;
+ Conf_Types type;
+ ParseData;
+ Config_Enum *table;
+} Config_Type;
+
+static int assume_charset_fun(char *value)
+{
+ assumed_charset = TRUE;
+ UCLYhndl_for_unspec = safeUCGetLYhndl_byMIME(value);
+ StrAllocCopy(UCAssume_MIMEcharset,
+ LYCharSet_UC[UCLYhndl_for_unspec].MIMEname);
+ CTRACE((tfp, "assume_charset_fun %s ->%d ->%s\n",
+ NonNull(value),
+ UCLYhndl_for_unspec,
+ UCAssume_MIMEcharset));
+ return 0;
+}
+
+static int assume_local_charset_fun(char *value)
+{
+ UCLYhndl_HTFile_for_unspec = safeUCGetLYhndl_byMIME(value);
+ return 0;
+}
+
+static int assume_unrec_charset_fun(char *value)
+{
+ UCLYhndl_for_unrec = safeUCGetLYhndl_byMIME(value);
+ return 0;
+}
+
+static int character_set_fun(char *value)
+{
+ int i = UCGetLYhndl_byAnyName(value); /* by MIME or full name */
+
+ if (i < 0) {
+#ifdef CAN_AUTODETECT_DISPLAY_CHARSET
+ if (auto_display_charset >= 0
+ && (!strncasecomp(value, "AutoDetect ", 11)
+ || !strncasecomp(value, "AutoDetect-2 ", 13)))
+ current_char_set = auto_display_charset;
+#endif
+ /* do nothing here: so fallback to userdefs.h */
+ } else {
+ current_char_set = i;
+ }
+
+ return 0;
+}
+
+static int outgoing_mail_charset_fun(char *value)
+{
+ outgoing_mail_charset = UCGetLYhndl_byMIME(value);
+ /* -1 if NULL or not recognized value: no translation (compatibility) */
+
+ return 0;
+}
+
+#ifdef EXP_ASSUMED_COLOR
+/*
+ * Process string buffer fields for ASSUMED_COLOR setting.
+ */
+static int assumed_color_fun(char *buffer)
+{
+ const char *fg = buffer, *bg;
+ char *temp_fg = 0;
+
+ if (LYuse_default_colors) {
+
+ /*
+ * We are expecting a line of the form:
+ * FOREGROUND:BACKGROUND
+ */
+ if (NULL == (bg = find_colon(fg)))
+ exit_with_color_syntax(buffer);
+
+ StrAllocCopy(temp_fg, fg);
+ temp_fg[bg++ - fg] = '\0';
+
+ default_fg = check_color(temp_fg, default_fg);
+ default_bg = check_color(bg, default_bg);
+
+ if (default_fg == ERR_COLOR
+ || default_bg == ERR_COLOR)
+ exit_with_color_syntax(buffer);
+ FREE(temp_fg);
+ } else {
+ CTRACE((tfp, "...ignored since DEFAULT_COLORS:off\n"));
+ }
+ return 0;
+}
+#endif /* EXP_ASSUMED_COLOR */
+
+#ifdef USE_COLOR_TABLE
+static int color_fun(char *value)
+{
+ parse_color(value);
+ return 0;
+}
+#endif
+
+#ifdef USE_COLOR_STYLE
+static int lynx_lss_file_fun(char *value)
+{
+ CTRACE((tfp, "lynx_lss_file_fun '%s'\n", NonNull(value)));
+ if (isEmpty(value)) {
+ clear_lss_list();
+ } else {
+ add_to_lss_list(value, NULL);
+ }
+ return 0;
+}
+#endif
+
+#ifdef USE_DEFAULT_COLORS
+void update_default_colors(void)
+{
+ int old_fg = default_fg;
+ int old_bg = default_bg;
+
+ default_color_reset = !LYuse_default_colors;
+ if (LYuse_default_colors) {
+ default_color_reset = FALSE;
+ default_fg = DEFAULT_COLOR;
+ default_bg = DEFAULT_COLOR;
+ } else {
+ default_color_reset = TRUE;
+ default_fg = COLOR_WHITE;
+ default_bg = COLOR_BLACK;
+ }
+ if (old_fg != default_fg || old_bg != default_bg) {
+ lynx_setup_colors();
+#ifdef USE_COLOR_STYLE
+ update_color_style();
+#endif
+ }
+}
+
+static int default_colors_fun(char *value)
+{
+ LYuse_default_colors = is_true(value);
+ update_default_colors();
+ return 0;
+}
+#endif
+
+static int default_bookmark_file_fun(char *value)
+{
+ set_default_bookmark_page(value);
+ return 0;
+}
+
+static int default_cache_size_fun(char *value)
+{
+ HTCacheSize = atoi(value);
+ if (HTCacheSize < 2)
+ HTCacheSize = 2;
+ return 0;
+}
+
+static int default_editor_fun(char *value)
+{
+ if (!system_editor)
+ StrAllocCopy(editor, value);
+ return 0;
+}
+
+static int numbers_as_arrows_fun(char *value)
+{
+ if (is_true(value))
+ keypad_mode = NUMBERS_AS_ARROWS;
+ else
+ keypad_mode = LINKS_ARE_NUMBERED;
+
+ return 0;
+}
+
+#ifdef DIRED_SUPPORT
+static int dired_menu_fun(char *value)
+{
+ add_menu_item(value);
+ return 0;
+}
+#endif
+
+static int jumpfile_fun(char *value)
+{
+ char *buffer = NULL;
+
+ HTSprintf0(&buffer, "JUMPFILE:%s", value);
+ if (!LYJumpInit(buffer))
+ CTRACE((tfp, "Failed to register %s\n", buffer));
+ FREE(buffer);
+
+ return 0;
+}
+
+#ifdef EXP_KEYBOARD_LAYOUT
+static int keyboard_layout_fun(char *key)
+{
+ if (!LYSetKbLayout(key))
+ CTRACE((tfp, "Failed to set keyboard layout %s\n", key));
+ return 0;
+}
+#endif /* EXP_KEYBOARD_LAYOUT */
+
+static int keymap_fun(char *key)
+{
+ char *func, *efunc;
+
+ if ((func = StrChr(key, ':')) != NULL) {
+ *func++ = '\0';
+ efunc = StrChr(func, ':');
+ /* Allow comments on the ends of key remapping lines. - DT */
+ /* Allow third field for line-editor action. - kw */
+ if (efunc == func) { /* have 3rd field, but 2nd field empty */
+ func = NULL;
+ } else if (efunc && strncasecomp(efunc + 1, "DIRED", 5) == 0) {
+ if (!remap(key, strtok(func, " \t\n:#"), TRUE)) {
+ fprintf(stderr,
+ gettext("key remapping of %s to %s for %s failed\n"),
+ key, func, efunc + 1);
+ } else if (!strcmp("TOGGLE_HELP", func)) {
+ LYUseNoviceLineTwo = FALSE;
+ }
+ return 0;
+ } else if (!remap(key, strtok(func, " \t\n:#"), FALSE)) {
+ fprintf(stderr, gettext("key remapping of %s to %s failed\n"),
+ key, func);
+ } else {
+ if (!strcmp("TOGGLE_HELP", func))
+ LYUseNoviceLineTwo = FALSE;
+ }
+ if (efunc) {
+ efunc++;
+ if (efunc == strtok((func ? NULL : efunc), " \t\n:#") && *efunc) {
+ BOOLEAN success = FALSE;
+ int lkc = lkcstring_to_lkc(key);
+ int lec = -1;
+ int select_edi = 0;
+ char *sselect_edi = strtok(NULL, " \t\n:#");
+ char **endp = &sselect_edi;
+
+ if (sselect_edi) {
+ if (*sselect_edi)
+ select_edi = (int) strtol(sselect_edi, endp, 10);
+ if (**endp != '\0') {
+ fprintf(stderr,
+ gettext("invalid line-editor selection %s for key %s, selecting all\n"),
+ sselect_edi, key);
+ select_edi = 0;
+ }
+ }
+ /*
+ * PASS! tries to enter the key into the LYLineEditors
+ * bindings in a different way from PASS, namely as binding
+ * that maps to the specific lynx actioncode (rather than to
+ * LYE_FORM_PASS). That only works for lynx keycodes with
+ * modifier bit set, and we have no documented/official way to
+ * specify this in the KEYMAP directive, although it can be
+ * made to work e.g. by specifying a hex value that has the
+ * modifier bit set. But knowledge about the bit pattern of
+ * modifiers should remain in internal matter subject to
+ * change... At any rate, if PASS! fails try it the same way
+ * as for PASS. - kw
+ */
+ if (!success && strcasecomp(efunc, "PASS!") == 0) {
+ if (func) {
+ lec = LYE_FORM_LAC | lacname_to_lac(func);
+ success = (BOOL) LYRemapEditBinding(lkc, lec, select_edi);
+ }
+ if (!success)
+ fprintf(stderr,
+ gettext("setting of line-editor binding for key %s (0x%x) to 0x%x for %s failed\n"),
+ key,
+ (unsigned) lkc,
+ (unsigned) lec,
+ efunc);
+ else
+ return 0;
+ }
+ if (!success) {
+ lec = lecname_to_lec(efunc);
+ success = (BOOL) LYRemapEditBinding(lkc, lec, select_edi);
+ }
+ if (!success) {
+ if (lec != -1) {
+ fprintf(stderr,
+ gettext("setting of line-editor binding for key %s (0x%x) to 0x%x for %s failed\n"),
+ key,
+ (unsigned) lkc,
+ (unsigned) lec,
+ efunc);
+ } else {
+ fprintf(stderr,
+ gettext("setting of line-editor binding for key %s (0x%x) for %s failed\n"),
+ key,
+ (unsigned) lkc,
+ efunc);
+ }
+ }
+ }
+ }
+ }
+ return 0;
+}
+
+static int localhost_alias_fun(char *value)
+{
+ LYAddLocalhostAlias(value);
+ return 0;
+}
+
+#ifdef LYNXCGI_LINKS
+static int lynxcgi_environment_fun(char *value)
+{
+ add_lynxcgi_environment(value);
+ return 0;
+}
+#endif
+
+static int lynx_sig_file_fun(char *value)
+{
+ char temp[LY_MAXPATH];
+
+ LYStrNCpy(temp, value, sizeof(temp) - 1);
+ if (LYPathOffHomeOK(temp, sizeof(temp))) {
+ StrAllocCopy(LynxSigFile, temp);
+ LYAddPathToHome(temp, sizeof(temp), LynxSigFile);
+ StrAllocCopy(LynxSigFile, temp);
+ CTRACE((tfp, "LYNX_SIG_FILE set to '%s'\n", LynxSigFile));
+ } else {
+ CTRACE((tfp, "LYNX_SIG_FILE '%s' is bad. Ignoring.\n", LYNX_SIG_FILE));
+ }
+ return 0;
+}
+
+#ifndef DISABLE_NEWS
+static int news_chunk_size_fun(char *value)
+{
+ HTNewsChunkSize = atoi(value);
+ /*
+ * If the new HTNewsChunkSize exceeds the maximum,
+ * increase HTNewsMaxChunk to this size. - FM
+ */
+ if (HTNewsChunkSize > HTNewsMaxChunk)
+ HTNewsMaxChunk = HTNewsChunkSize;
+ return 0;
+}
+
+static int news_max_chunk_fun(char *value)
+{
+ HTNewsMaxChunk = atoi(value);
+ /*
+ * If HTNewsChunkSize exceeds the new maximum,
+ * reduce HTNewsChunkSize to this maximum. - FM
+ */
+ if (HTNewsChunkSize > HTNewsMaxChunk)
+ HTNewsChunkSize = HTNewsMaxChunk;
+ return 0;
+}
+
+static int news_posting_fun(char *value)
+{
+ LYNewsPosting = is_true(value);
+ no_newspost = (BOOL) (LYNewsPosting == FALSE);
+ return 0;
+}
+#endif /* DISABLE_NEWS */
+
+#ifndef NO_RULES
+static int cern_rulesfile_fun(char *value)
+{
+ char *rulesfile1 = NULL;
+ char *rulesfile2 = NULL;
+
+ if (HTLoadRules(value) >= 0) {
+ return 0;
+ }
+ StrAllocCopy(rulesfile1, value);
+ LYTrimLeading(value);
+ LYTrimTrailing(value);
+
+ StrAllocCopy(rulesfile2, value);
+ LYTildeExpand(&rulesfile2, FALSE);
+
+ if (strcmp(rulesfile1, rulesfile2) &&
+ HTLoadRules(rulesfile2) >= 0) {
+ FREE(rulesfile1);
+ FREE(rulesfile2);
+ return 0;
+ }
+ fprintf(stderr,
+ gettext("Lynx: cannot start, CERN rules file %s is not available\n"),
+ non_empty(rulesfile2) ? rulesfile2 : gettext("(no name)"));
+ exit_immediately(EXIT_FAILURE);
+ return 0; /* though redundant, for compiler-warnings */
+}
+#endif /* NO_RULES */
+
+static int referer_with_query_fun(char *value)
+{
+ if (!strncasecomp(value, "SEND", 4))
+ LYRefererWithQuery = 'S';
+ else if (!strncasecomp(value, "PARTIAL", 7))
+ LYRefererWithQuery = 'P';
+ else
+ LYRefererWithQuery = 'D';
+ return 0;
+}
+
+static int status_buffer_size_fun(char *value)
+{
+ status_buf_size = atoi(value);
+ if (status_buf_size < 2)
+ status_buf_size = 2;
+ return 0;
+}
+
+static int startfile_fun(char *value)
+{
+ StrAllocCopy(startfile, value);
+
+#ifdef USE_PROGRAM_DIR
+ if (is_url(startfile) == 0) {
+ char *tmp = NULL;
+
+ HTSprintf0(&tmp, "%s\\%s", program_dir, startfile);
+ FREE(startfile);
+ LYLocalFileToURL(&startfile, tmp);
+ FREE(tmp);
+ }
+#endif
+ return 0;
+}
+
+static int suffix_fun(char *value)
+{
+ char *mime_type, *p, *parsed;
+ const char *encoding = NULL;
+ char *sq = NULL;
+ char *description = NULL;
+ double q = 1.0;
+
+ if ((strlen(value) < 3)
+ || (NULL == (mime_type = StrChr(value, ':')))) {
+ CTRACE((tfp, "Invalid SUFFIX:%s ignored.\n", value));
+ return 0;
+ }
+
+ *mime_type++ = '\0';
+ if (*mime_type) {
+ if ((parsed = StrChr(mime_type, ':')) != NULL) {
+ *parsed++ = '\0';
+ if ((sq = StrChr(parsed, ':')) != NULL) {
+ *sq++ = '\0';
+ if ((description = StrChr(sq, ':')) != NULL) {
+ *description++ = '\0';
+ if ((p = StrChr(sq, ':')) != NULL)
+ *p = '\0';
+ LYTrimTail(description);
+ }
+ LYRemoveBlanks(sq);
+ if (!*sq)
+ sq = NULL;
+ }
+ LYRemoveBlanks(parsed);
+ LYLowerCase(parsed);
+ if (!*parsed)
+ parsed = NULL;
+ }
+ encoding = parsed;
+ }
+
+ LYRemoveBlanks(mime_type);
+ /*
+ * mime-type is not converted to lowercase on input, to make it possible to
+ * reproduce the equivalent of some of the HTInit.c defaults that use mixed
+ * case, although that is not recommended. - kw
+ */
+ if (!*mime_type) { /* that's ok now, with an encoding! */
+ CTRACE((tfp, "SUFFIX:%s without MIME type for %s\n", value,
+ encoding ? encoding : "what?"));
+ mime_type = NULL; /* that's ok now, with an encoding! */
+ if (!encoding)
+ return 0;
+ }
+
+ if (!encoding) {
+ if (strstr(mime_type, "tex") != NULL ||
+ strstr(mime_type, "postscript") != NULL ||
+ strstr(mime_type, "sh") != NULL ||
+ strstr(mime_type, "troff") != NULL ||
+ strstr(mime_type, "rtf") != NULL)
+ encoding = "8bit";
+ else
+ encoding = "binary";
+ }
+ if (!sq) {
+ q = 1.0;
+ } else {
+ double df = strtod(sq, &p);
+
+ if (p == sq && df <= 0.0) {
+ CTRACE((tfp, "Invalid q=%s for SUFFIX:%s, using -1.0\n",
+ sq, value));
+ q = -1.0;
+ } else {
+ q = df;
+ }
+ }
+ HTSetSuffix5(value, mime_type, encoding, description, q);
+
+ return 0;
+}
+
+static int suffix_order_fun(char *value)
+{
+ char *p = value;
+ char *optn;
+ BOOLEAN want_file_init_now = FALSE;
+
+ LYUseBuiltinSuffixes = TRUE;
+ while ((optn = HTNextTok(&p, ", ", "", NULL)) != NULL) {
+ if (!strcasecomp(optn, "NO_BUILTIN")) {
+ LYUseBuiltinSuffixes = FALSE;
+ } else if (!strcasecomp(optn, "PRECEDENCE_HERE")) {
+ want_file_init_now = TRUE;
+ } else if (!strcasecomp(optn, "PRECEDENCE_OTHER")) {
+ want_file_init_now = FALSE;
+ } else {
+ CTRACE((tfp, "Invalid SUFFIX_ORDER:%s\n", optn));
+ break;
+ }
+ }
+
+ if (want_file_init_now && !FileInitAlreadyDone) {
+ HTFileInit();
+ FileInitAlreadyDone = TRUE;
+ }
+ return 0;
+}
+
+static int system_editor_fun(char *value)
+{
+ StrAllocCopy(editor, value);
+ system_editor = TRUE;
+ return 0;
+}
+
+#define SetViewer(mime_type, viewer) \
+ HTSetPresentation(mime_type, viewer, 0, 1.0, 3.0, 0.0, 0L, mediaCFG)
+
+static int viewer_fun(char *value)
+{
+ char *mime_type;
+ char *viewer;
+ char *environment;
+
+ mime_type = value;
+
+ if ((strlen(value) < 3)
+ || (NULL == (viewer = StrChr(mime_type, ':'))))
+ return 0;
+
+ *viewer++ = '\0';
+
+ LYRemoveBlanks(mime_type);
+ LYLowerCase(mime_type);
+
+ environment = strrchr(viewer, ':');
+ if ((environment != NULL) &&
+ (strlen(viewer) > 1) && *(environment - 1) != '\\') {
+ *environment++ = '\0';
+ remove_backslashes(viewer);
+ /*
+ * If environment equals xwindows then only assign the presentation if
+ * there is a $DISPLAY variable.
+ */
+ if (!strcasecomp(environment, "XWINDOWS")) {
+ if (LYgetXDisplay() != NULL)
+ SetViewer(mime_type, viewer);
+ } else if (!strcasecomp(environment, "NON_XWINDOWS")) {
+ if (LYgetXDisplay() == NULL)
+ SetViewer(mime_type, viewer);
+ } else {
+ SetViewer(mime_type, viewer);
+ }
+ } else {
+ remove_backslashes(viewer);
+ SetViewer(mime_type, viewer);
+ }
+
+ return 0;
+}
+
+static int nonrest_sigwinch_fun(char *value)
+{
+ if (!strncasecomp(value, "XWINDOWS", 8)) {
+ LYNonRestartingSIGWINCH = (BOOL) (LYgetXDisplay() != NULL);
+ } else {
+ LYNonRestartingSIGWINCH = is_true(value);
+ }
+ return 0;
+}
+
+#ifdef USE_CHARSET_CHOICE
+static void matched_charset_choice(int display_charset,
+ int i)
+{
+ int j;
+
+ if (display_charset && !custom_display_charset) {
+ for (custom_display_charset = TRUE, j = 0; j < LYNumCharsets; ++j)
+ charset_subsets[j].hide_display = TRUE;
+ } else if (!display_charset && !custom_assumed_doc_charset) {
+ for (custom_assumed_doc_charset = TRUE, j = 0; j < LYNumCharsets; ++j)
+ charset_subsets[j].hide_assumed = TRUE;
+ }
+ if (display_charset)
+ charset_subsets[i].hide_display = FALSE;
+ else
+ charset_subsets[i].hide_assumed = FALSE;
+}
+
+static int parse_charset_choice(char *p,
+ int display_charset) /*if FALSE, then assumed doc charset */
+{
+ int len, i;
+ int matches = 0;
+
+ /*only one charset choice is allowed per line! */
+ LYTrimHead(p);
+ LYTrimTail(p);
+ CTRACE((tfp, "parsing charset choice for %s:\"%s\"",
+ (display_charset ? "display charset" : "assumed doc charset"), p));
+ len = (int) strlen(p);
+ if (!len) {
+ CTRACE((tfp, " - EMPTY STRING\n"));
+ return 1;
+ }
+ if (*p == '*' && len == 1) {
+ if (display_charset)
+ for (custom_display_charset = TRUE, i = 0; i < LYNumCharsets; ++i)
+ charset_subsets[i].hide_display = FALSE;
+ else
+ for (custom_assumed_doc_charset = TRUE, i = 0; i < LYNumCharsets; ++i)
+ charset_subsets[i].hide_assumed = FALSE;
+ CTRACE((tfp, " - all unhidden\n"));
+ return 0;
+ }
+ if (p[len - 1] == '*') {
+ --len;
+ for (i = 0; i < LYNumCharsets; ++i) {
+ if ((!strncasecomp(p, LYchar_set_names[i], len)) ||
+ (!strncasecomp(p, LYCharSet_UC[i].MIMEname, len))) {
+ ++matches;
+ matched_charset_choice(display_charset, i);
+ }
+ }
+ CTRACE((tfp, " - %d matches\n", matches));
+ return 0;
+ } else {
+ for (i = 0; i < LYNumCharsets; ++i) {
+ if ((!strcasecomp(p, LYchar_set_names[i])) ||
+ (!strcasecomp(p, LYCharSet_UC[i].MIMEname))) {
+ matched_charset_choice(display_charset, i);
+ ++matches;
+ CTRACE((tfp, " - OK, %d matches\n", matches));
+ return 0;
+ }
+ }
+ CTRACE((tfp, " - NOT recognised\n"));
+ return 1;
+ }
+}
+
+static int parse_display_charset_choice(char *p)
+{
+ return parse_charset_choice(p, 1);
+}
+
+static int parse_assumed_doc_charset_choice(char *p)
+{
+ return parse_charset_choice(p, 0);
+}
+
+#endif /* USE_CHARSET_CHOICE */
+
+#ifdef USE_EXTERNALS
+/*
+ * EXTERNAL and EXTERNAL_MENU share the same list. EXTERNAL_MENU allows
+ * setting a different name than the command string.
+ */
+static int external_fun(char *str)
+{
+ add_item_to_list(str, &externals, FALSE, TRUE);
+ return 0;
+}
+#endif
+
+#ifdef USE_PRETTYSRC
+static void html_src_bad_syntax(char *value,
+ char *option_name)
+{
+ char *buf = 0;
+
+ HTSprintf0(&buf, "HTMLSRC_%s", option_name);
+ LYUpperCase(buf);
+ fprintf(stderr, "Bad syntax in TAGSPEC %s:%s\n", buf, value);
+ exit_immediately(EXIT_FAILURE);
+}
+
+static int parse_html_src_spec(HTlexeme lexeme_code, char *value,
+ char *option_name)
+{
+ /* Now checking the value for being correct. Since HTML_dtd is not
+ * initialized completely (member tags points to non-initiailized data), we
+ * use tags_old. If the syntax is incorrect, then lynx will exit with error
+ * message.
+ */
+ char *ts2;
+
+ if (isEmpty(value))
+ return 0; /* silently ignoring */
+
+#define BS() html_src_bad_syntax(value,option_name)
+
+ ts2 = StrChr(value, ':');
+ if (!ts2)
+ BS();
+
+ *ts2 = '\0';
+
+ CTRACE2(TRACE_CFG, (tfp,
+ "LYReadCFG - parsing tagspec '%s:%s' for option '%s'\n",
+ value, ts2, option_name));
+ html_src_clean_item(lexeme_code);
+ if (!html_src_parse_tagspec(value, lexeme_code, TRUE, TRUE)
+ || !html_src_parse_tagspec(ts2, lexeme_code, TRUE, TRUE)) {
+ *ts2 = ':';
+ BS();
+ }
+
+ *ts2 = ':';
+ StrAllocCopy(HTL_tagspecs[lexeme_code], value);
+#undef BS
+ return 0;
+}
+
+static int psrcspec_fun(char *s)
+{
+ char *e;
+ /* *INDENT-OFF* */
+ static Config_Enum lexemnames[] =
+ {
+ { "comm", HTL_comm },
+ { "tag", HTL_tag },
+ { "attrib", HTL_attrib },
+ { "attrval", HTL_attrval },
+ { "abracket", HTL_abracket },
+ { "entity", HTL_entity },
+ { "href", HTL_href },
+ { "entire", HTL_entire },
+ { "badseq", HTL_badseq },
+ { "badtag", HTL_badtag },
+ { "badattr", HTL_badattr },
+ { "sgmlspecial", HTL_sgmlspecial },
+ { NULL, -1 }
+ };
+ /* *INDENT-ON* */
+
+ int found;
+
+ e = StrChr(s, ':');
+ if (!e) {
+ CTRACE((tfp,
+ "bad format of PRETTYSRC_SPEC setting value, ignored %s\n",
+ s));
+ return 0;
+ }
+ *e = '\0';
+ if (!LYgetEnum(lexemnames, s, &found)) {
+ CTRACE((tfp,
+ "bad format of PRETTYSRC_SPEC setting value, ignored %s:%s\n",
+ s, e + 1));
+ return 0;
+ }
+ parse_html_src_spec((HTlexeme) found, e + 1, s);
+ return 0;
+}
+
+static int read_htmlsrc_attrname_xform(char *str)
+{
+ int val;
+
+ if (1 == sscanf(str, "%d", &val)) {
+ if (val < 0 || val > 2) {
+ CTRACE((tfp,
+ "bad value for htmlsrc_attrname_xform (ignored - must be one of 0,1,2): %d\n",
+ val));
+ } else
+ attrname_transform = val;
+ } else {
+ CTRACE((tfp, "bad value for htmlsrc_attrname_xform (ignored): %s\n",
+ str));
+ }
+ return 0;
+}
+
+static int read_htmlsrc_tagname_xform(char *str)
+{
+ int val;
+
+ if (1 == sscanf(str, "%d", &val)) {
+ if (val < 0 || val > 2) {
+ CTRACE((tfp,
+ "bad value for htmlsrc_tagname_xform (ignored - must be one of 0,1,2): %d\n",
+ val));
+ } else
+ tagname_transform = val;
+ } else {
+ CTRACE((tfp, "bad value for htmlsrc_tagname_xform (ignored): %s\n",
+ str));
+ }
+ return 0;
+}
+#endif
+
+#ifdef USE_SESSIONS
+static int session_limit_fun(char *value)
+{
+ session_limit = (short) atoi(value);
+ if (session_limit < 1)
+ session_limit = 1;
+ else if (session_limit > MAX_SESSIONS)
+ session_limit = MAX_SESSIONS;
+ return 0;
+}
+#endif /* USE_SESSIONS */
+
+#if defined(PDCURSES) && defined(PDC_BUILD) && PDC_BUILD >= 2401
+static int screen_size_fun(char *value)
+{
+ char *cp;
+
+ if ((cp = StrChr(value, ',')) != 0) {
+ *cp++ = '\0'; /* Terminate ID */
+ scrsize_x = atoi(value);
+ scrsize_y = atoi(cp);
+ if ((scrsize_x <= 1) || (scrsize_y <= 1)) {
+ scrsize_x = scrsize_y = 0;
+ }
+ if ((scrsize_x > 0) && (scrsize_x < 80)) {
+ scrsize_x = 80;
+ }
+ if ((scrsize_y > 0) && (scrsize_y < 4)) {
+ scrsize_y = 4;
+ }
+ CTRACE((tfp, "scrsize: x=%d, y=%d\n", scrsize_x, scrsize_y));
+ }
+ return 0;
+}
+#endif
+
+#if defined(HAVE_LIBINTL_H) || defined(HAVE_LIBGETTEXT_H)
+static int message_language_fun(char *value)
+{
+ char *tmp = NULL;
+
+ HTSprintf0(&tmp, "LANG=%s", value);
+ putenv(tmp);
+
+ LYSetTextDomain();
+
+ return 0;
+}
+#endif
+
+/* This table is searched ignoring case */
+/* *INDENT-OFF* */
+static Config_Type Config_Table [] =
+{
+ PARSE_SET(RC_ACCEPT_ALL_COOKIES, LYAcceptAllCookies),
+ PARSE_TIM(RC_ALERTSECS, AlertSecs),
+#if USE_BLAT_MAILER
+ PARSE_SET(RC_ALT_BLAT_MAIL, mail_is_altblat),
+#endif
+ PARSE_SET(RC_ALWAYS_RESUBMIT_POSTS, LYresubmit_posts),
+#ifdef EXEC_LINKS
+ PARSE_DEF(RC_ALWAYS_TRUSTED_EXEC, ALWAYS_EXEC_PATH),
+#endif
+ PARSE_FUN(RC_ASSUME_CHARSET, assume_charset_fun),
+ PARSE_FUN(RC_ASSUME_LOCAL_CHARSET, assume_local_charset_fun),
+ PARSE_FUN(RC_ASSUME_UNREC_CHARSET, assume_unrec_charset_fun),
+#ifdef EXP_ASSUMED_COLOR
+ PARSE_FUN(RC_ASSUMED_COLOR, assumed_color_fun),
+#endif
+#ifdef USE_CHARSET_CHOICE
+ PARSE_FUN(RC_ASSUMED_DOC_CHARSET_CHOICE, parse_assumed_doc_charset_choice),
+#endif
+#ifdef DIRED_SUPPORT
+ PARSE_INT(RC_AUTO_UNCACHE_DIRLISTS, LYAutoUncacheDirLists),
+#endif
+#ifndef DISABLE_BIBP
+ PARSE_STR(RC_BIBP_BIBHOST, BibP_bibhost),
+ PARSE_STR(RC_BIBP_GLOBALSERVER, BibP_globalserver),
+#endif
+#if USE_BLAT_MAILER
+ PARSE_SET(RC_BLAT_MAIL, mail_is_blat),
+#endif
+ PARSE_SET(RC_BLOCK_MULTI_BOOKMARKS, LYMBMBlocked),
+ PARSE_SET(RC_BOLD_H1, bold_H1),
+ PARSE_SET(RC_BOLD_HEADERS, bold_headers),
+ PARSE_SET(RC_BOLD_NAME_ANCHORS, bold_name_anchors),
+#ifndef DISABLE_FTP
+ PARSE_LST(RC_BROKEN_FTP_EPSV, broken_ftp_epsv),
+ PARSE_LST(RC_BROKEN_FTP_RETR, broken_ftp_retr),
+#endif
+ PARSE_PRG(RC_BROTLI_PATH, ppBROTLI),
+ PARSE_PRG(RC_BZIP2_PATH, ppBZIP2),
+ PARSE_SET(RC_CASE_SENSITIVE_ALWAYS_ON, LYcase_sensitive),
+ PARSE_FUN(RC_CHARACTER_SET, character_set_fun),
+#ifdef CAN_SWITCH_DISPLAY_CHARSET
+ PARSE_STR(RC_CHARSET_SWITCH_RULES, charset_switch_rules),
+ PARSE_STR(RC_CHARSETS_DIRECTORY, charsets_directory),
+#endif
+ PARSE_SET(RC_CHECKMAIL, check_mail),
+ PARSE_PRG(RC_CHMOD_PATH, ppCHMOD),
+ PARSE_SET(RC_COLLAPSE_BR_TAGS, LYCollapseBRs),
+#ifdef USE_COLOR_TABLE
+ PARSE_FUN(RC_COLOR, color_fun),
+#endif
+#ifdef USE_COLOR_STYLE
+ PARSE_FUN(RC_COLOR_STYLE, lynx_lss_file_fun),
+#endif
+ PARSE_PRG(RC_COMPRESS_PATH, ppCOMPRESS),
+ PARSE_PRG(RC_COPY_PATH, ppCOPY),
+ PARSE_INT(RC_CONNECT_TIMEOUT, connect_timeout),
+ PARSE_SET(RC_CONV_JISX0201KANA, conv_jisx0201kana),
+ PARSE_STR(RC_COOKIE_ACCEPT_DOMAINS, LYCookieSAcceptDomains),
+#ifdef USE_PERSISTENT_COOKIES
+ PARSE_STR(RC_COOKIE_FILE, LYCookieFile),
+#endif /* USE_PERSISTENT_COOKIES */
+ PARSE_STR(RC_COOKIE_LOOSE_INVALID_DOMAINS, LYCookieSLooseCheckDomains),
+ PARSE_STR(RC_COOKIE_QUERY_INVALID_DOMAINS, LYCookieSQueryCheckDomains),
+ PARSE_STR(RC_COOKIE_REJECT_DOMAINS, LYCookieSRejectDomains),
+#ifdef USE_PERSISTENT_COOKIES
+ PARSE_STR(RC_COOKIE_SAVE_FILE, LYCookieSaveFile),
+#endif /* USE_PERSISTENT_COOKIES */
+ PARSE_STR(RC_COOKIE_STRICT_INVALID_DOMAIN, LYCookieSStrictCheckDomains),
+ PARSE_ENU(RC_COOKIE_VERSION, cookie_version, tbl_cookie_version),
+ PARSE_Env(RC_CSO_PROXY, 0),
+#ifdef VMS
+ PARSE_PRG(RC_CSWING_PATH, ppCSWING),
+#endif
+ PARSE_TIM(RC_DELAYSECS, DelaySecs),
+ PARSE_FUN(RC_DEFAULT_BOOKMARK_FILE, default_bookmark_file_fun),
+ PARSE_FUN(RC_DEFAULT_CACHE_SIZE, default_cache_size_fun),
+#ifdef USE_DEFAULT_COLORS
+ PARSE_FUN(RC_DEFAULT_COLORS, default_colors_fun),
+#endif
+ PARSE_FUN(RC_DEFAULT_EDITOR, default_editor_fun),
+ PARSE_STR(RC_DEFAULT_INDEX_FILE, indexfile),
+ PARSE_ENU(RC_DEFAULT_KEYPAD_MODE, keypad_mode, tbl_keypad_mode),
+ PARSE_FUN(RC_DEFAULT_KEYPAD_MODE_NUMARO, numbers_as_arrows_fun),
+ PARSE_ENU(RC_DEFAULT_USER_MODE, user_mode, tbl_user_mode),
+#if defined(VMS) && defined(VAXC) && !defined(__DECC)
+ PARSE_INT(RC_DEFAULT_VIRTUAL_MEMORY_SIZE, HTVirtualMemorySize),
+#endif
+#ifdef DIRED_SUPPORT
+ PARSE_FUN(RC_DIRED_MENU, dired_menu_fun),
+#endif
+#ifdef USE_CHARSET_CHOICE
+ PARSE_FUN(RC_DISPLAY_CHARSET_CHOICE, parse_display_charset_choice),
+#endif
+ PARSE_SET(RC_DONT_WRAP_PRE, dont_wrap_pre),
+ PARSE_ADD(RC_DOWNLOADER, downloaders),
+ PARSE_SET(RC_EMACS_KEYS_ALWAYS_ON, emacs_keys),
+ PARSE_FUN(RC_ENABLE_LYNXRC, enable_lynxrc),
+ PARSE_SET(RC_ENABLE_SCROLLBACK, enable_scrollback),
+#ifdef USE_EXTERNALS
+ PARSE_ADD(RC_EXTERNAL, externals),
+ PARSE_FUN(RC_EXTERNAL_MENU, external_fun),
+#endif
+ PARSE_Env(RC_FINGER_PROXY, 0),
+#if defined(_WINDOWS) /* 1998/10/05 (Mon) 17:34:15 */
+ PARSE_SET(RC_FOCUS_WINDOW, focus_window),
+#endif
+ PARSE_SET(RC_FORCE_8BIT_TOUPPER, UCForce8bitTOUPPER),
+ PARSE_ENU(RC_FORCE_COOKIE_PROMPT, cookie_noprompt, tbl_force_prompt),
+ PARSE_SET(RC_FORCE_EMPTY_HREFLESS_A, force_empty_hrefless_a),
+ PARSE_SET(RC_FORCE_HTML, LYforce_HTML_mode),
+ PARSE_SET(RC_FORCE_SSL_COOKIES_SECURE, LYForceSSLCookiesSecure),
+#ifdef USE_SSL
+ PARSE_ENU(RC_FORCE_SSL_PROMPT, ssl_noprompt, tbl_force_prompt),
+#endif
+#if !defined(NO_OPTION_FORMS) && !defined(NO_OPTION_MENU)
+ PARSE_SET(RC_FORMS_OPTIONS, LYUseFormsOptions),
+#endif
+ PARSE_STR(RC_FTP_FORMAT, ftp_format),
+#ifndef DISABLE_FTP
+ PARSE_SET(RC_FTP_PASSIVE, ftp_passive),
+#endif
+ PARSE_Env(RC_FTP_PROXY, 0),
+ PARSE_STR(RC_GLOBAL_EXTENSION_MAP, global_extension_map),
+ PARSE_STR(RC_GLOBAL_MAILCAP, global_type_map),
+ PARSE_Env(RC_GOPHER_PROXY, 0),
+ PARSE_SET(RC_GOTOBUFFER, goto_buffer),
+ PARSE_PRG(RC_GZIP_PATH, ppGZIP),
+ PARSE_SET(RC_GUESS_SCHEME, LYGuessScheme),
+ PARSE_STR(RC_HELPFILE, helpfile),
+ PARSE_FUN(RC_HIDDENLINKS, hiddenlinks_fun),
+#ifdef MARK_HIDDEN_LINKS
+ PARSE_STR(RC_HIDDEN_LINK_MARKER, hidden_link_marker),
+#endif
+ PARSE_SET(RC_HISTORICAL_COMMENTS, historical_comments),
+ PARSE_SET(RC_HTML5_CHARSETS, html5_charsets),
+#ifdef USE_PRETTYSRC
+ PARSE_FUN(RC_HTMLSRC_ATTRNAME_XFORM, read_htmlsrc_attrname_xform),
+ PARSE_FUN(RC_HTMLSRC_TAGNAME_XFORM, read_htmlsrc_tagname_xform),
+#endif
+ PARSE_FUN(RC_HTTP_PROTOCOL, get_http_protocol),
+ PARSE_Env(RC_HTTP_PROXY, 0),
+ PARSE_Env(RC_HTTPS_PROXY, 0),
+ PARSE_REQ(RC_INCLUDE, 0),
+ PARSE_PRG(RC_INFLATE_PATH, ppINFLATE),
+ PARSE_TIM(RC_INFOSECS, InfoSecs),
+ PARSE_PRG(RC_INSTALL_PATH, ppINSTALL),
+ PARSE_STR(RC_JUMP_PROMPT, jumpprompt),
+ PARSE_SET(RC_JUMPBUFFER, jump_buffer),
+ PARSE_FUN(RC_JUMPFILE, jumpfile_fun),
+#ifdef USE_JUSTIFY_ELTS
+ PARSE_SET(RC_JUSTIFY, ok_justify),
+ PARSE_INT(RC_JUSTIFY_MAX_VOID_PERCENT, justify_max_void_percent),
+#endif
+#ifdef EXP_KEYBOARD_LAYOUT
+ PARSE_FUN(RC_KEYBOARD_LAYOUT, keyboard_layout_fun),
+#endif
+ PARSE_FUN(RC_KEYMAP, keymap_fun),
+ PARSE_SET(RC_LEFTARROW_IN_TEXTFLD_PROMPT, textfield_prompt_at_left_edge),
+ PARSE_SET(RC_LISTONLY, dump_links_only),
+ PARSE_SET(RC_LIST_DECODED, dump_links_decoded),
+#ifndef VMS
+ PARSE_STR(RC_LIST_FORMAT, list_format),
+#endif
+ PARSE_SET(RC_LIST_INLINE, dump_links_inline),
+#ifndef DISABLE_NEWS
+ PARSE_SET(RC_LIST_NEWS_DATES, LYListNewsDates),
+ PARSE_SET(RC_LIST_NEWS_NUMBERS, LYListNewsNumbers),
+#endif
+#ifdef USE_LOCALE_CHARSET
+ PARSE_SET(RC_LOCALE_CHARSET, LYLocaleCharset),
+#endif
+ PARSE_STR(RC_LOCAL_DOMAIN, LYLocalDomain),
+ PARSE_SET(RC_LOCALHOST, local_host_only),
+ PARSE_FUN(RC_LOCALHOST_ALIAS, localhost_alias_fun),
+#if defined(EXEC_LINKS) || defined(EXEC_SCRIPTS)
+ PARSE_SET(RC_LOCAL_EXECUTION_LINKS_ALWAYS, local_exec),
+ PARSE_SET(RC_LOCAL_EXECUTION_LINKS_LOCAL, local_exec_on_local_files),
+#endif
+ PARSE_STR(RC_LYNX_HOST_NAME, LYHostName),
+ PARSE_FUN(RC_LYNX_SIG_FILE, lynx_sig_file_fun),
+#ifdef LYNXCGI_LINKS
+#ifndef VMS
+ PARSE_STR(RC_LYNXCGI_DOCUMENT_ROOT, LYCgiDocumentRoot),
+#endif
+ PARSE_FUN(RC_LYNXCGI_ENVIRONMENT, lynxcgi_environment_fun),
+#endif
+#if USE_VMS_MAILER
+ PARSE_STR(RC_MAIL_ADRS, mail_adrs),
+#endif
+ PARSE_SET(RC_MAIL_SYSTEM_ERROR_LOGGING, error_logging),
+ PARSE_SET(RC_MAKE_LINKS_FOR_ALL_IMAGES, clickable_images),
+ PARSE_SET(RC_MAKE_PSEUDO_ALTS_FOR_INLINES, pseudo_inline_alts),
+ PARSE_INT(RC_MAX_COOKIES_BUFFER, max_cookies_buffer),
+ PARSE_INT(RC_MAX_COOKIES_DOMAIN, max_cookies_domain),
+ PARSE_INT(RC_MAX_COOKIES_GLOBAL, max_cookies_global),
+ PARSE_INT(RC_MAX_URI_SIZE, max_uri_size),
+ PARSE_TIM(RC_MESSAGESECS, MessageSecs),
+#if defined(HAVE_LIBINTL_H) || defined(HAVE_LIBGETTEXT_H)
+ PARSE_FUN(RC_MESSAGE_LANGUAGE, message_language_fun),
+#endif
+ PARSE_SET(RC_MINIMAL_COMMENTS, minimal_comments),
+ PARSE_PRG(RC_MKDIR_PATH, ppMKDIR),
+ PARSE_ENU(RC_MULTI_BOOKMARK_SUPPORT, LYMultiBookmarks, tbl_multi_bookmarks),
+ PARSE_PRG(RC_MV_PATH, ppMV),
+ PARSE_SET(RC_NCR_IN_BOOKMARKS, UCSaveBookmarksInUnicode),
+#ifdef EXP_NESTED_TABLES
+ PARSE_SET(RC_NESTED_TABLES, nested_tables),
+#endif
+#ifndef DISABLE_NEWS
+ PARSE_FUN(RC_NEWS_CHUNK_SIZE, news_chunk_size_fun),
+ PARSE_FUN(RC_NEWS_MAX_CHUNK, news_max_chunk_fun),
+ PARSE_FUN(RC_NEWS_POSTING, news_posting_fun),
+ PARSE_Env(RC_NEWS_PROXY, 0),
+ PARSE_Env(RC_NEWSPOST_PROXY, 0),
+ PARSE_Env(RC_NEWSREPLY_PROXY, 0),
+ PARSE_Env(RC_NNTP_PROXY, 0),
+ PARSE_ENV(RC_NNTPSERVER, 0), /* actually NNTPSERVER */
+#endif
+ PARSE_SET(RC_NUMBER_FIELDS_ON_LEFT,number_fields_on_left),
+ PARSE_SET(RC_NUMBER_LINKS_ON_LEFT, number_links_on_left),
+ PARSE_SET(RC_NO_DOT_FILES, no_dotfiles),
+ PARSE_SET(RC_NO_FILE_REFERER, no_filereferer),
+#ifndef VMS
+ PARSE_SET(RC_NO_FORCED_CORE_DUMP, LYNoCore),
+#endif
+ PARSE_SET(RC_NO_FROM_HEADER, LYNoFromHeader),
+ PARSE_SET(RC_NO_ISMAP_IF_USEMAP, LYNoISMAPifUSEMAP),
+ PARSE_SET(RC_NO_MARGINS, no_margins),
+ PARSE_SET(RC_NO_PAUSE, no_pause),
+ PARSE_Env(RC_NO_PROXY, 0),
+ PARSE_SET(RC_NO_REFERER_HEADER, LYNoRefererHeader),
+ PARSE_SET(RC_NO_TABLE_CENTER, no_table_center),
+ PARSE_SET(RC_NO_TITLE, no_title),
+ PARSE_SET(RC_UPDATE_TERM_TITLE, update_term_title),
+ PARSE_FUN(RC_NONRESTARTING_SIGWINCH, nonrest_sigwinch_fun),
+ PARSE_FUN(RC_OUTGOING_MAIL_CHARSET, outgoing_mail_charset_fun),
+#ifdef DISP_PARTIAL
+ PARSE_SET(RC_PARTIAL, display_partial_flag),
+ PARSE_INT(RC_PARTIAL_THRES, partial_threshold),
+#endif
+#ifdef USE_PERSISTENT_COOKIES
+ PARSE_SET(RC_PERSISTENT_COOKIES, persistent_cookies),
+#endif /* USE_PERSISTENT_COOKIES */
+ PARSE_STR(RC_PERSONAL_EXTENSION_MAP, personal_extension_map),
+ PARSE_STR(RC_PERSONAL_MAILCAP, personal_type_map),
+ PARSE_LST(RC_POSITIONABLE_EDITOR, positionable_editor),
+ PARSE_STR(RC_PREFERRED_CHARSET, pref_charset),
+ PARSE_ENU(RC_PREFERRED_CONTENT_TYPE, LYContentType, tbl_preferred_content),
+ PARSE_ENU(RC_PREFERRED_ENCODING, LYAcceptEncoding, tbl_preferred_encoding),
+ PARSE_STR(RC_PREFERRED_LANGUAGE, language),
+ PARSE_ENU(RC_PREFERRED_MEDIA_TYPES, LYAcceptMedia, tbl_preferred_media),
+ PARSE_SET(RC_PREPEND_BASE_TO_SOURCE, LYPrependBaseToSource),
+ PARSE_SET(RC_PREPEND_CHARSET_TO_SOURCE, LYPrependCharsetToSource),
+#ifdef USE_PRETTYSRC
+ PARSE_SET(RC_PRETTYSRC, LYpsrc),
+ PARSE_FUN(RC_PRETTYSRC_SPEC, psrcspec_fun),
+ PARSE_SET(RC_PRETTYSRC_VIEW_NO_ANCHOR_NUM, psrcview_no_anchor_numbering),
+#endif
+ PARSE_ADD(RC_PRINTER, printers),
+ PARSE_SET(RC_QUIT_DEFAULT_YES, LYQuitDefaultYes),
+ PARSE_INT(RC_READ_TIMEOUT, reading_timeout),
+ PARSE_INT(RC_REDIRECTION_LIMIT, redirection_limit),
+ PARSE_FUN(RC_REFERER_WITH_QUERY, referer_with_query_fun),
+#ifdef USE_CMD_LOGGING
+ PARSE_TIM(RC_REPLAYSECS, ReplaySecs),
+#endif
+ PARSE_SET(RC_REUSE_TEMPFILES, LYReuseTempfiles),
+ PARSE_PRG(RC_RLOGIN_PATH, ppRLOGIN),
+ PARSE_PRG(RC_RMDIR_PATH, ppRMDIR),
+ PARSE_PRG(RC_RM_PATH, ppRM),
+#ifndef NO_RULES
+ PARSE_FUN(RC_RULE, HTSetConfiguration),
+ PARSE_FUN(RC_RULESFILE, cern_rulesfile_fun),
+#endif /* NO_RULES */
+ PARSE_STR(RC_SAVE_SPACE, lynx_save_space),
+ PARSE_SET(RC_SCAN_FOR_BURIED_NEWS_REFS, scan_for_buried_news_references),
+#if defined(PDCURSES) && defined(PDC_BUILD) && PDC_BUILD >= 2401
+ PARSE_FUN(RC_SCREEN_SIZE, screen_size_fun),
+#endif
+#ifdef USE_SCROLLBAR
+ PARSE_SET(RC_SCROLLBAR, LYShowScrollbar),
+ PARSE_SET(RC_SCROLLBAR_ARROW, LYsb_arrow),
+#endif
+ PARSE_SET(RC_SEEK_FRAG_AREA_IN_CUR, LYSeekFragAREAinCur),
+ PARSE_SET(RC_SEEK_FRAG_MAP_IN_CUR, LYSeekFragMAPinCur),
+#ifdef USE_SESSIONS
+ PARSE_SET(RC_AUTO_SESSION, LYAutoSession),
+ PARSE_STR(RC_SESSION_FILE, LYSessionFile),
+ PARSE_FUN(RC_SESSION_LIMIT, session_limit_fun),
+#endif
+ PARSE_SET(RC_SET_COOKIES, LYSetCookies),
+ PARSE_SET(RC_SHORT_URL, long_url_ok),
+ PARSE_SET(RC_SHOW_CURSOR, LYShowCursor),
+ PARSE_STR(RC_SHOW_KB_NAME, LYTransferName),
+ PARSE_ENU(RC_SHOW_KB_RATE, LYTransferRate, tbl_transfer_rate),
+ PARSE_Env(RC_SNEWS_PROXY, 0),
+ PARSE_Env(RC_SNEWSPOST_PROXY, 0),
+ PARSE_Env(RC_SNEWSREPLY_PROXY, 0),
+ PARSE_SET(RC_SOFT_DQUOTES, soft_dquotes),
+#ifdef USE_SOURCE_CACHE
+ PARSE_ENU(RC_SOURCE_CACHE, LYCacheSource, tbl_source_cache),
+ PARSE_ENU(RC_SOURCE_CACHE_FOR_ABORTED, LYCacheSourceForAborted, tbl_abort_source_cache),
+#endif
+ PARSE_STR(RC_SSL_CERT_FILE, SSL_cert_file),
+ PARSE_STR(RC_SSL_CLIENT_CERT_FILE, SSL_client_cert_file),
+ PARSE_STR(RC_SSL_CLIENT_KEY_FILE, SSL_client_key_file),
+ PARSE_FUN(RC_STARTFILE, startfile_fun),
+ PARSE_FUN(RC_STATUS_BUFFER_SIZE, status_buffer_size_fun),
+ PARSE_SET(RC_STRIP_DOTDOT_URLS, LYStripDotDotURLs),
+ PARSE_SET(RC_SUBSTITUTE_UNDERSCORES, use_underscore),
+ PARSE_FUN(RC_SUFFIX, suffix_fun),
+ PARSE_FUN(RC_SUFFIX_ORDER, suffix_order_fun),
+#ifdef SYSLOG_REQUESTED_URLS
+ PARSE_SET(RC_SYSLOG_REQUESTED_URLS, syslog_requested_urls),
+ PARSE_STR(RC_SYSLOG_TEXT, syslog_txt),
+#endif
+ PARSE_FUN(RC_SYSTEM_EDITOR, system_editor_fun),
+ PARSE_STR(RC_SYSTEM_MAIL, system_mail),
+ PARSE_STR(RC_SYSTEM_MAIL_FLAGS, system_mail_flags),
+ PARSE_FUN(RC_TAGSOUP, get_tagsoup),
+ PARSE_PRG(RC_TAR_PATH, ppTAR),
+ PARSE_PRG(RC_TELNET_PATH, ppTELNET),
+#ifdef TEXTFIELDS_MAY_NEED_ACTIVATION
+ PARSE_SET(RC_TEXTFIELDS_NEED_ACTIVATION, textfields_activation_option),
+#endif
+ PARSE_PRG(RC_TN3270_PATH, ppTN3270),
+#if defined(_WINDOWS)
+ PARSE_INT(RC_TIMEOUT, lynx_timeout),
+#endif
+ PARSE_PRG(RC_TOUCH_PATH, ppTOUCH),
+ PARSE_SET(RC_TRACK_INTERNAL_LINKS, track_internal_links),
+ PARSE_SET(RC_TRIM_BLANK_LINES, LYtrimBlankLines),
+ PARSE_SET(RC_TRIM_INPUT_FIELDS, LYtrimInputFields),
+#ifdef EXEC_LINKS
+ PARSE_DEF(RC_TRUSTED_EXEC, EXEC_PATH),
+#endif
+#ifdef LYNXCGI_LINKS
+ PARSE_DEF(RC_TRUSTED_LYNXCGI, CGI_PATH),
+#endif
+ PARSE_PRG(RC_UNCOMPRESS_PATH, ppUNCOMPRESS),
+ PARSE_SET(RC_UNDERLINE_LINKS, LYUnderlineLinks),
+ PARSE_SET(RC_UNIQUE_URLS, unique_urls),
+ PARSE_PRG(RC_UNZIP_PATH, ppUNZIP),
+#ifdef DIRED_SUPPORT
+ PARSE_ADD(RC_UPLOADER, uploaders),
+#endif
+ PARSE_STR(RC_URL_DOMAIN_PREFIXES, URLDomainPrefixes),
+ PARSE_STR(RC_URL_DOMAIN_SUFFIXES, URLDomainSuffixes),
+#ifdef VMS
+ PARSE_SET(RC_USE_FIXED_RECORDS, UseFixedRecords),
+#endif
+#if defined(USE_MOUSE)
+ PARSE_SET(RC_USE_MOUSE, LYUseMouse),
+#endif
+ PARSE_SET(RC_USE_SELECT_POPUPS, LYSelectPopups),
+ PARSE_PRG(RC_UUDECODE_PATH, ppUUDECODE),
+ PARSE_SET(RC_VERBOSE_IMAGES, verbose_img),
+ PARSE_SET(RC_VI_KEYS_ALWAYS_ON, vi_keys),
+ PARSE_FUN(RC_VIEWER, viewer_fun),
+ PARSE_Env(RC_WAIS_PROXY, 0),
+ PARSE_SET(RC_WAIT_VIEWER_TERMINATION, wait_viewer_termination),
+ PARSE_SET(RC_WITH_BACKSPACES, with_backspaces),
+ PARSE_STR(RC_XLOADIMAGE_COMMAND, XLoadImageCommand),
+ PARSE_SET(RC_XHTML_PARSING, LYxhtml_parsing),
+ PARSE_PRG(RC_ZCAT_PATH, ppZCAT),
+ PARSE_PRG(RC_ZIP_PATH, ppZIP),
+
+ PARSE_NIL
+};
+/* *INDENT-ON* */
+
+static char *lynxcfginfo_url = NULL; /* static */
+
+#if defined(HAVE_CONFIG_H) && !defined(NO_CONFIG_INFO)
+static char *configinfo_url = NULL; /* static */
+#endif
+
+/*
+ * Free memory allocated in 'read_cfg()'
+ */
+void free_lynx_cfg(void)
+{
+ Config_Type *tbl;
+
+ for (tbl = Config_Table; tbl->name != 0; tbl++) {
+ ParseUnionPtr q = ParseUnionOf(tbl);
+
+ switch (tbl->type) {
+ case CONF_ENV:
+ if (q->str_value != 0) {
+ char *name = *(q->str_value);
+ char *eqls = StrChr(name, '=');
+
+ if (eqls != 0) {
+ *eqls = 0;
+#ifdef VMS
+ Define_VMSLogical(name, NULL);
+#else
+# ifdef HAVE_PUTENV
+ if (putenv(name))
+ break;
+# else
+ unsetenv(name);
+# endif
+#endif
+ }
+ FREE(*(q->str_value));
+ FREE(q->str_value);
+ /* is it enough for reload_read_cfg() to clean up
+ * the result of putenv()? No for certain platforms.
+ */
+ }
+ break;
+ default:
+ break;
+ }
+ }
+ free_all_item_lists();
+#ifdef DIRED_SUPPORT
+ reset_dired_menu(); /* frees and resets dired menu items - kw */
+#endif
+ FREE(lynxcfginfo_url);
+#if defined(HAVE_CONFIG_H) && !defined(NO_CONFIG_INFO)
+ FREE(configinfo_url);
+#endif
+}
+
+static Config_Type *lookup_config(const char *name)
+{
+ Config_Type *tbl = Config_Table;
+ char ch = (char) TOUPPER(*name);
+
+ while (tbl->name != 0) {
+ char ch1 = tbl->name[0];
+
+ if ((ch == TOUPPER(ch1))
+ && (0 == strcasecomp(name, tbl->name)))
+ break;
+
+ tbl++;
+ }
+ return tbl;
+}
+
+/*
+ * If the given value is an absolute path (by syntax), or we can read it, use
+ * the value as given. Otherwise, assume it must be in the same place we read
+ * the parent configuration file from.
+ *
+ * Note: only read files from the current directory if there's no parent
+ * filename, otherwise it leads to user surprise.
+ */
+static char *actual_filename(const char *cfg_filename,
+ const char *parent_filename,
+ const char *dft_filename)
+{
+ char *my_filename = NULL;
+
+ if (!LYisAbsPath(cfg_filename)
+ && !(parent_filename == 0 && LYCanReadFile(cfg_filename))) {
+ if (LYIsTilde(cfg_filename[0]) && LYIsPathSep(cfg_filename[1])) {
+ HTSprintf0(&my_filename, "%s%s", Home_Dir(), cfg_filename + 1);
+ } else {
+ if (parent_filename != 0) {
+ StrAllocCopy(my_filename, parent_filename);
+ *LYPathLeaf(my_filename) = '\0';
+ StrAllocCat(my_filename, cfg_filename);
+ }
+ if (my_filename == 0 || !LYCanReadFile(my_filename)) {
+ StrAllocCopy(my_filename, dft_filename);
+ *LYPathLeaf(my_filename) = '\0';
+ StrAllocCat(my_filename, cfg_filename);
+ if (!LYCanReadFile(my_filename)) {
+ StrAllocCopy(my_filename,
+ LYFindConfigFile(cfg_filename,
+ dft_filename));
+ }
+ }
+ }
+ } else {
+ StrAllocCopy(my_filename, cfg_filename);
+ }
+ return my_filename;
+}
+
+FILE *LYOpenCFG(const char *cfg_filename,
+ const char *parent_filename,
+ const char *dft_filename)
+{
+ char *my_file = actual_filename(cfg_filename, parent_filename, dft_filename);
+ FILE *result;
+
+ CTRACE((tfp, "opening config file %s\n", my_file));
+ result = fopen(my_file, TXT_R);
+ FREE(my_file);
+
+ return result;
+}
+
+#define NOPTS_ ( TABLESIZE(Config_Table) - 1 )
+typedef BOOL (optidx_set_t)[NOPTS_];
+
+ /* if element is FALSE, then it's allowed in the current file */
+
+#define optidx_set_AND(r,a,b) \
+ {\
+ unsigned i1;\
+ for (i1 = 0; i1 < NOPTS_; ++i1) \
+ (r)[i1]= (BOOLEAN) ((a)[i1] || (b)[i1]); \
+ }
+
+/*
+ * For simple (boolean, string, integer, time) values, set the corresponding
+ * configuration variable.
+ */
+BOOL LYSetConfigValue(const char *name,
+ const char *param)
+{
+ BOOL changed = TRUE;
+ char *value = NULL;
+ Config_Type *tbl = lookup_config(name);
+ ParseUnionPtr q = ParseUnionOf(tbl);
+ char *temp_name = 0;
+ char *temp_value = 0;
+
+ if (param == NULL)
+ param = "";
+ StrAllocCopy(value, param);
+ switch (tbl->type) {
+ case CONF_BOOL:
+ if (q->set_value != 0)
+ *(q->set_value) = is_true(value);
+ break;
+
+ case CONF_FUN:
+ if (q->fun_value != 0)
+ (*(q->fun_value)) (value);
+ break;
+
+ case CONF_TIME:
+ if (q->int_value != 0) {
+ float ival;
+
+ if (1 == LYscanFloat(value, &ival)) {
+ *(q->int_value) = (int) SECS2Secs(ival);
+ }
+ }
+ break;
+
+ case CONF_ENUM:
+ if (tbl->table != 0)
+ LYgetEnum(tbl->table, value, q->int_value);
+ break;
+
+ case CONF_INT:
+ if (q->int_value != 0) {
+ int ival;
+
+ if (1 == sscanf(value, "%d", &ival))
+ *(q->int_value) = ival;
+ }
+ break;
+
+ case CONF_STR:
+ if (q->str_value != 0)
+ StrAllocCopy(*(q->str_value), value);
+ break;
+
+ case CONF_ENV:
+ case CONF_ENV2:
+
+ if (StrAllocCopy(temp_name, name)) {
+ if (tbl->type == CONF_ENV)
+ LYLowerCase(temp_name);
+ else
+ LYUpperCase(temp_name);
+
+ if (LYGetEnv(temp_name) == 0) {
+#ifdef VMS
+ Define_VMSLogical(temp_name, value);
+#else
+ if (q->str_value == 0) {
+ q->str_value = typecalloc(char *);
+
+ if (q->str_value == 0)
+ outofmem(__FILE__, "LYSetConfigValue");
+ }
+
+ HTSprintf0(q->str_value, "%s=%s", temp_name, value);
+ putenv(*(q->str_value));
+#endif
+ }
+ FREE(temp_name);
+ }
+ break;
+ case CONF_ADD_ITEM:
+ if (q->add_value != 0)
+ add_item_to_list(value,
+ q->add_value,
+ (q->add_value == &printers),
+ FALSE);
+ break;
+
+ case CONF_ADD_STRING:
+ if (*(q->lst_value) == NULL) {
+ *(q->lst_value) = HTList_new();
+ }
+ temp_value = NULL;
+ StrAllocCopy(temp_value, value);
+ HTList_appendObject(*(q->lst_value), temp_value);
+ temp_value = NULL;
+ break;
+
+#if defined(EXEC_LINKS) || defined(LYNXCGI_LINKS)
+ case CONF_ADD_TRUSTED:
+ add_trusted(value, (int) q->def_value);
+ break;
+#endif
+
+ case CONF_PRG:
+ if (isEmpty(value)) {
+ HTSetProgramPath((ProgramPaths) (q->def_value), NULL);
+ } else if (StrAllocCopy(temp_value, value)) {
+ HTSetProgramPath((ProgramPaths) (q->def_value), temp_value);
+ }
+ break;
+
+ default:
+ changed = FALSE;
+ break;
+ }
+ FREE(value);
+
+ return changed;
+}
+
+/*
+ * Process the configuration file (lynx.cfg).
+ *
+ * 'allowed' is a pointer to HTList of allowed options. Since the included
+ * file can also include other files with a list of acceptable options, these
+ * lists are ANDed.
+ */
+static void do_read_cfg(const char *cfg_filename,
+ const char *parent_filename,
+ int nesting_level,
+ FILE *fp0,
+ optidx_set_t *allowed)
+{
+ FILE *fp;
+ char *buffer = 0;
+
+ CTRACE((tfp, "Loading cfg file '%s'.\n", cfg_filename));
+
+ /*
+ * Don't get hung up by an include file loop. Arbitrary max depth
+ * of 10. - BL
+ */
+ if (nesting_level > 10) {
+ fprintf(stderr,
+ gettext("More than %d nested lynx.cfg includes -- perhaps there is a loop?!?\n"),
+ nesting_level - 1);
+ fprintf(stderr, gettext("Last attempted include was '%s',\n"), cfg_filename);
+ fprintf(stderr, gettext("included from '%s'.\n"), parent_filename);
+ exit_immediately(EXIT_FAILURE);
+ }
+ /*
+ * Locate and open the file.
+ */
+ if (!cfg_filename || strlen(cfg_filename) == 0) {
+ CTRACE((tfp, "No filename following -cfg switch!\n"));
+ return;
+ }
+ if ((fp = LYOpenCFG(cfg_filename, parent_filename, LYNX_CFG_FILE)) == 0) {
+ CTRACE((tfp, "lynx.cfg file not found as '%s'\n", cfg_filename));
+ return;
+ }
+ have_read_cfg = TRUE;
+
+ /*
+ * Process each line in the file.
+ */
+ if (show_cfg) {
+ time_t t;
+
+ time(&t);
+ printf("### %s %s, at %s", LYNX_NAME, LYNX_VERSION, ctime(&t));
+ }
+ while (LYSafeGets(&buffer, fp) != 0) {
+ char *name, *value;
+ char *cp;
+ Config_Type *tbl;
+
+ /* Most lines in the config file are comment lines. Weed them out
+ * now. Also, leading whitespace is ok, so trim it.
+ */
+ name = LYSkipBlanks(buffer);
+
+ if (ispunct(UCH(*name)))
+ continue;
+
+ LYTrimTrailing(name);
+
+ if (*name == 0)
+ continue;
+
+ /* Significant lines are of the form KEYWORD:WHATEVER */
+ if ((value = StrChr(name, ':')) == 0) {
+ /* fprintf (stderr, "Bad line-- no :\n"); */
+ CTRACE((tfp, "LYReadCFG: missing ':' %s\n", name));
+ continue;
+ }
+
+ /* skip past colon, but replace ':' with 0 to make name meaningful */
+ *value++ = 0;
+
+ /*
+ * Trim off any trailing comments.
+ *
+ * (Apparently, the original code considers a trailing comment valid
+ * only if preceded by a space character but is not followed by a
+ * colon. -- JED)
+ */
+ if ((cp = strrchr(value, ':')) == 0)
+ cp = value;
+ if ((cp = StrChr(cp, '#')) != 0) {
+ cp--;
+ if (isspace(UCH(*cp)))
+ *cp = 0;
+ }
+
+ CTRACE2(TRACE_CFG, (tfp, "LYReadCFG %s:%s\n", name, value));
+ tbl = lookup_config(name);
+ if (tbl->name == 0) {
+ /* lynx ignores unknown keywords */
+ CTRACE((tfp, "LYReadCFG: ignored %s:%s\n", name, value));
+ continue;
+ }
+ if (show_cfg)
+ printf("%s:%s\n", name, value);
+
+ if (allowed && (*allowed)[tbl - Config_Table]) {
+ if (fp0 == NULL)
+ fprintf(stderr, "%s is not allowed in the %s\n",
+ name, cfg_filename);
+ /*FIXME: we can do something wiser if we are generating
+ the html representation of lynx.cfg - say include this line
+ in bold, or something... */
+
+ continue;
+ }
+
+ (void) ParseUnionOf(tbl);
+ switch ((fp0 != 0 && tbl->type != CONF_INCLUDE)
+ ? CONF_NIL
+ : tbl->type) {
+ case CONF_BOOL:
+ case CONF_FUN:
+ case CONF_TIME:
+ case CONF_ENUM:
+ case CONF_INT:
+ case CONF_STR:
+ case CONF_ENV:
+ case CONF_ENV2:
+ case CONF_PRG:
+ case CONF_ADD_ITEM:
+ case CONF_ADD_STRING:
+ case CONF_ADD_TRUSTED:
+ LYSetConfigValue(name, value);
+ break;
+
+ case CONF_INCLUDE:{
+ /* include another file */
+ optidx_set_t cur_set, anded_set;
+ optidx_set_t *resultant_set = NULL;
+ char *p1, *p2, savechar;
+ BOOL any_optname_found = FALSE;
+
+ char *url = NULL;
+ char *cp1 = NULL;
+ const char *sep = NULL;
+
+ if ((p1 = strstr(value, sep = " for ")) != 0
+#if defined(UNIX) && !defined(USE_DOS_DRIVES)
+ || (p1 = strstr(value, sep = ":")) != 0
+#endif
+ ) {
+ *p1 = '\0';
+ p1 += strlen(sep);
+ }
+#ifndef NO_CONFIG_INFO
+ if (fp0 != 0 && !no_lynxcfg_xinfo) {
+ char *my_file = actual_filename(value, cfg_filename, LYNX_CFG_FILE);
+
+ LYLocalFileToURL(&url, my_file);
+ FREE(my_file);
+ StrAllocCopy(cp1, value);
+ if (StrChr(value, '&') || StrChr(value, '<')) {
+ LYEntify(&cp1, TRUE);
+ }
+
+ fprintf(fp0, "%s:<a href=\"%s\">%s</a>\n\n", name, url, cp1);
+ fprintf(fp0, " #&lt;begin %s&gt;\n", cp1);
+ }
+#endif
+
+ if (p1) {
+ while (*(p1 = LYSkipBlanks(p1)) != 0) {
+ Config_Type *tbl2;
+
+ p2 = LYSkipNonBlanks(p1);
+ savechar = *p2;
+ *p2 = 0;
+
+ tbl2 = lookup_config(p1);
+ if (tbl2->name == 0) {
+ if (fp0 == NULL)
+ fprintf(stderr,
+ "unknown option name %s in %s\n",
+ p1, cfg_filename);
+ } else {
+ unsigned i;
+
+ if (!any_optname_found) {
+ any_optname_found = TRUE;
+ for (i = 0; i < NOPTS_; ++i)
+ cur_set[i] = TRUE;
+ }
+ cur_set[tbl2 - Config_Table] = FALSE;
+ }
+ if (savechar && p2[1])
+ p1 = p2 + 1;
+ else
+ break;
+ }
+ }
+ if (!allowed) {
+ if (!any_optname_found)
+ resultant_set = NULL;
+ else
+ resultant_set = &cur_set;
+ } else {
+ if (!any_optname_found)
+ resultant_set = allowed;
+ else {
+ optidx_set_AND(anded_set, *allowed, cur_set);
+ resultant_set = &anded_set;
+ }
+ }
+
+#ifndef NO_CONFIG_INFO
+ /*
+ * Now list the opts that are allowed in included file. If all
+ * opts are allowed, then emit nothing, else emit an effective set
+ * of allowed options in <ul>. Option names will be uppercased.
+ * FIXME: uppercasing option names can be considered redundant.
+ */
+ if (fp0 != 0 && !no_lynxcfg_xinfo && resultant_set) {
+ char *buf = NULL;
+ unsigned i;
+
+ fprintf(fp0, " Options allowed in this file:\n");
+ for (i = 0; i < NOPTS_; ++i) {
+ if ((*resultant_set)[i])
+ continue;
+ StrAllocCopy(buf, Config_Table[i].name);
+ LYUpperCase(buf);
+ fprintf(fp0, " * %s\n", buf);
+ }
+ FREE(buf);
+ }
+#endif
+ do_read_cfg(value, cfg_filename, nesting_level + 1, fp0, resultant_set);
+
+#ifndef NO_CONFIG_INFO
+ if (fp0 != 0 && !no_lynxcfg_xinfo) {
+ fprintf(fp0, " #&lt;end of %s&gt;\n\n", cp1);
+ FREE(url);
+ FREE(cp1);
+ }
+#endif
+ }
+ break;
+
+ default:
+ if (fp0 != 0) {
+ if (StrChr(value, '&') || StrChr(value, '<')) {
+ char *cp1 = NULL;
+
+ StrAllocCopy(cp1, value);
+ LYEntify(&cp1, TRUE);
+ fprintf(fp0, "%s:%s\n", name, cp1);
+ FREE(cp1);
+ } else {
+ fprintf(fp0, "%s:%s\n", name, value);
+ }
+ }
+ break;
+ }
+ }
+
+ LYCloseInput(fp);
+
+ /*
+ * If any DOWNLOADER: commands have always_enabled set (:TRUE), make
+ * override_no_download TRUE, so that other restriction settings will not
+ * block presentation of a download menu with those always_enabled options
+ * still available. - FM
+ */
+ if (downloaders != 0) {
+ lynx_list_item_type *cur_download;
+
+ cur_download = downloaders;
+ while (cur_download != 0) {
+ if (cur_download->always_enabled) {
+ override_no_download = TRUE;
+ break;
+ }
+ cur_download = cur_download->next;
+ }
+ }
+
+ /*
+ * If any COOKIE_{ACCEPT,REJECT}_DOMAINS have been defined,
+ * process them. These are comma delimited lists of
+ * domains. - BJP
+ *
+ * And for query/strict/loose invalid cookie checking. - BJP
+ */
+ LYConfigCookies();
+
+ /*
+ * Do not allow infinite redirection loops.
+ */
+ if (redirection_limit < 5)
+ redirection_limit = 5;
+ if (redirection_limit > 25)
+ redirection_limit = 25;
+}
+
+/* this is a public interface to do_read_cfg */
+void read_cfg(const char *cfg_filename,
+ const char *parent_filename,
+ int nesting_level,
+ FILE *fp0)
+{
+ HTInitProgramPaths(TRUE);
+ do_read_cfg(cfg_filename, parent_filename, nesting_level, fp0, NULL);
+}
+
+#ifndef NO_CONFIG_INFO
+static void extra_cfg_link(FILE *fp, const char *href,
+ const char *name)
+{
+ fprintf(fp, "<a href=\"%s\">%s</a>",
+ href, name);
+}
+#endif /* NO_CONFIG_INFO */
+
+/*
+ * Show rendered lynx.cfg data without comments, LYNXCFG:/ internal page.
+ * Called from getfile() cycle: we create and load the page just in place and
+ * return to mainloop().
+ */
+int lynx_cfg_infopage(DocInfo *newdoc)
+{
+ static char tempfile[LY_MAXPATH] = "\0";
+ DocAddress WWWDoc; /* need on exit */
+ char *temp = 0;
+ char *cp1 = NULL;
+ FILE *fp0;
+
+#ifndef NO_CONFIG_INFO
+ /*-------------------------------------------------
+ * kludge a link from LYNXCFG:/, the URL was:
+ * " <a href=\"LYNXCFG://reload\">RELOAD THE CHANGES</a>\n"
+ *--------------------------------------------------*/
+
+ if (!no_lynxcfg_xinfo && (strstr(newdoc->address, "LYNXCFG://reload"))) {
+ /*
+ * Some stuff to reload read_cfg(), but also load options menu items
+ * and command-line options to make things consistent. Implemented in
+ * LYMain.c
+ */
+ reload_read_cfg();
+
+ /*
+ * now pop-up and return to updated LYNXCFG:/ page, remind
+ * postoptions() but much simpler:
+ */
+ /*
+ * But check whether the top history document is really the expected
+ * LYNXCFG: page. - kw
+ */
+ if (HTMainText && nhist > 0 &&
+ !strcmp(HTLoadedDocumentTitle(), LYNXCFG_TITLE) &&
+ !strcmp(HTLoadedDocumentURL(), HDOC(nhist - 1).address) &&
+ LYIsUIPage(HDOC(nhist - 1).address, UIP_LYNXCFG) &&
+ (!lynxcfginfo_url ||
+ strcmp(HTLoadedDocumentURL(), lynxcfginfo_url))) {
+ /* the page was pushed, so pop-up. */
+ LYpop(newdoc);
+ 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;
+ LYforce_no_cache = FALSE; /* ! */
+ LYoverride_no_cache = TRUE; /* ! */
+
+ /*
+ * Working out of getfile() cycle we reset *no_cache manually here
+ * so HTLoadAbsolute() will return "Document already in memory":
+ * it was forced reloading obsolete file again without this
+ * (overhead).
+ *
+ * Probably *no_cache was set in a wrong position because of the
+ * internal page...
+ */
+ if (!HTLoadAbsolute(&WWWDoc))
+ return (NOT_FOUND);
+
+ HTuncache_current_document(); /* will never use again */
+ LYUnRegisterUIPage(UIP_LYNXCFG);
+ }
+
+ /* now set up the flag and fall down to create a new LYNXCFG:/ page */
+ FREE(lynxcfginfo_url); /* see below */
+ }
+#endif /* !NO_CONFIG_INFO */
+
+ /*
+ * We regenerate the file if reloading has been requested (with LYK_NOCACHE
+ * key). If we did not regenerate, there would be no way to recover in a
+ * session from a situation where the file is corrupted (for example
+ * truncated because the file system was full when it was first created -
+ * lynx doesn't check for write errors below), short of manual complete
+ * removal or perhaps forcing regeneration with LYNXCFG://reload.
+ * Similarly, there would be no simple way to get a different page if
+ * user_mode has changed to Advanced after the file was first generated in
+ * a non-Advanced mode (the difference being in whether the page includes
+ * the link to LYNXCFG://reload or not).
+ *
+ * We also try to regenerate the file if lynxcfginfo_url is set, indicating
+ * that tempfile is valid, but the file has disappeared anyway. This can
+ * happen to a long-lived lynx process if for example some system script
+ * periodically cleans up old files in the temp file space. - kw
+ */
+
+ if (LYforce_no_cache && reloading) {
+ FREE(lynxcfginfo_url); /* flag to code below to regenerate - kw */
+ } else if (lynxcfginfo_url != NULL) {
+ if (!LYCanReadFile(tempfile)) { /* check existence */
+ FREE(lynxcfginfo_url); /* flag to code below to try again - kw */
+ }
+ }
+ if (lynxcfginfo_url == 0) {
+
+ if ((fp0 = InternalPageFP(tempfile, TRUE)) == 0)
+ return (NOT_FOUND);
+
+ LYLocalFileToURL(&lynxcfginfo_url, tempfile);
+
+ LYforce_no_cache = TRUE; /* don't cache this doc */
+
+ BeginInternalPage(fp0, LYNXCFG_TITLE, NULL);
+ fprintf(fp0, "<pre>\n");
+
+#ifndef NO_CONFIG_INFO
+ if (!no_lynxcfg_xinfo) {
+#if defined(HAVE_CONFIG_H) || defined(VMS)
+ if (strcmp(lynx_cfg_file, LYNX_CFG_FILE)) {
+ fprintf(fp0, "<em>%s\n%s",
+ gettext("The following is read from your lynx.cfg file."),
+ gettext("Please read the distribution"));
+ LYLocalFileToURL(&temp, LYNX_CFG_FILE);
+ fprintf(fp0, " <a href=\"%s\">lynx.cfg</a> ",
+ temp);
+ FREE(temp);
+ fprintf(fp0, "%s</em>\n\n",
+ gettext("for more comments."));
+ } else
+#endif /* HAVE_CONFIG_H */
+ {
+ /* no absolute path... for lynx.cfg on DOS/Win32 */
+ fprintf(fp0, "<em>%s\n%s",
+ gettext("The following is read from your lynx.cfg file."),
+ gettext("Please read the distribution"));
+ fprintf(fp0, " </em>lynx.cfg<em> ");
+ fprintf(fp0, "%s</em>\n",
+ gettext("for more comments."));
+ }
+
+#ifndef NO_CONFIG_INFO
+#if defined(HAVE_CONFIG_H) && defined(USE_COLOR_STYLE)
+ if (!no_compileopts_info && !no_lynxcfg_xinfo) {
+ fprintf(fp0, "%s</pre><ul><li>", SEE_ALSO);
+ extra_cfg_link(fp0, STR_LYNXCFLAGS, COMPILE_OPT_SEGMENT);
+
+ fprintf(fp0, "<li>");
+ LYLocalFileToURL(&temp, lynx_lss_file);
+ extra_cfg_link(fp0, temp, COLOR_STYLE_SEGMENT);
+ fprintf(fp0, "</ul><pre>\n");
+ } else
+#endif
+ {
+ fprintf(fp0, "%s ", SEE_ALSO);
+#if defined(HAVE_CONFIG_H)
+ if (!no_compileopts_info) {
+ extra_cfg_link(fp0, STR_LYNXCFLAGS, COMPILE_OPT_SEGMENT);
+ }
+#endif
+#if defined(USE_COLOR_STYLE)
+ if (!no_lynxcfg_xinfo) {
+ LYLocalFileToURL(&temp, lynx_lss_file);
+ extra_cfg_link(fp0, temp, COLOR_STYLE_SEGMENT);
+ }
+#endif
+ fprintf(fp0, "\n\n");
+ }
+#endif /* NO_CONFIG_INFO */
+
+ /** a new experimental link ... **/
+ if (user_mode == ADVANCED_MODE)
+ fprintf(fp0, " <a href=\"%s//reload\">%s</a>\n",
+ STR_LYNXCFG,
+ gettext("RELOAD THE CHANGES"));
+
+ LYLocalFileToURL(&temp, lynx_cfg_file);
+ StrAllocCopy(cp1, lynx_cfg_file);
+ if (StrChr(lynx_cfg_file, '&') || StrChr(lynx_cfg_file, '<')) {
+ LYEntify(&cp1, TRUE);
+ }
+ fprintf(fp0, "\n #<em>%s <a href=\"%s\">%s</a></em>\n",
+ gettext("Your primary configuration"),
+ temp,
+ cp1);
+ FREE(temp);
+ FREE(cp1);
+
+ } else
+#endif /* !NO_CONFIG_INFO */
+
+ fprintf(fp0, "<em>%s</em>\n\n",
+ gettext("The following is read from your lynx.cfg file."));
+
+ /*
+ * Process the configuration file.
+ */
+ read_cfg(lynx_cfg_file, "main program", 1, fp0);
+
+ fprintf(fp0, "</pre>\n");
+ EndInternalPage(fp0);
+ LYCloseTempFP(fp0);
+ LYRegisterUIPage(lynxcfginfo_url, UIP_LYNXCFG);
+ }
+
+ /* return to getfile() cycle */
+ StrAllocCopy(newdoc->address, lynxcfginfo_url);
+ 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;
+
+ if (!HTLoadAbsolute(&WWWDoc))
+ return (NOT_FOUND);
+#ifdef DIRED_SUPPORT
+ lynx_edit_mode = FALSE;
+#endif /* DIRED_SUPPORT */
+ return (NORMAL);
+}
+
+#if defined(HAVE_CONFIG_H) && !defined(NO_CONFIG_INFO)
+/*
+ * Compile-time definitions info, LYNXCOMPILEOPTS:/ internal page, from
+ * getfile() cycle.
+ */
+int lynx_compile_opts(DocInfo *newdoc)
+{
+ static char tempfile[LY_MAXPATH] = "\0";
+
+#define PutDefs(table, N) fprintf(fp0, "%-35s %s\n", table[N].name, table[N].value)
+#include <cfg_defs.h>
+ unsigned n;
+ DocAddress WWWDoc; /* need on exit */
+ FILE *fp0;
+
+ /* In general, create the page only once - compile-time data will not
+ * change... But we will regenerate the file anyway, in a few situations:
+ *
+ * (a) configinfo_url has been FREEd - this can happen if free_lynx_cfg()
+ * was called as part of a LYNXCFG://reload action.
+ *
+ * (b) reloading has been requested (with LYK_NOCACHE key). If we did not
+ * regenerate, there would be no way to recover in a session from a
+ * situation where the file is corrupted (for example truncated because the
+ * file system was full when it was first created - lynx doesn't check for
+ * write errors below), short of manual complete removal or forcing
+ * regeneration with LYNXCFG://reload.
+ *
+ * (c) configinfo_url is set, indicating that tempfile is valid, but the
+ * file has disappeared anyway. This can happen to a long-lived lynx
+ * process if for example some system script periodically cleans up old
+ * files in the temp file space. - kw
+ */
+
+ if (LYforce_no_cache && reloading) {
+ FREE(configinfo_url); /* flag to code below to regenerate - kw */
+ } else if (configinfo_url != NULL) {
+ if (!LYCanReadFile(tempfile)) { /* check existence */
+ FREE(configinfo_url); /* flag to code below to try again - kw */
+ }
+ }
+ if (configinfo_url == NULL) {
+ if ((fp0 = InternalPageFP(tempfile, TRUE)) == 0)
+ return (NOT_FOUND);
+
+ LYLocalFileToURL(&configinfo_url, tempfile);
+
+ BeginInternalPage(fp0, CONFIG_DEF_TITLE, NULL);
+ fprintf(fp0, "<pre>\n");
+
+ fprintf(fp0, "\n%s<br>\n<em>config.cache</em>\n", AUTOCONF_CONFIG_CACHE);
+ for (n = 0; n < TABLESIZE(config_cache); n++) {
+ PutDefs(config_cache, n);
+ }
+ fprintf(fp0, "\n%s<br>\n<em>lynx_cfg.h</em>\n", AUTOCONF_LYNXCFG_H);
+ for (n = 0; n < TABLESIZE(config_defines); n++) {
+ PutDefs(config_defines, n);
+ }
+ fprintf(fp0, "</pre>\n");
+ EndInternalPage(fp0);
+ LYCloseTempFP(fp0);
+ LYRegisterUIPage(configinfo_url, UIP_CONFIG_DEF);
+ }
+
+ /* exit to getfile() cycle */
+ StrAllocCopy(newdoc->address, configinfo_url);
+ 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;
+
+ if (!HTLoadAbsolute(&WWWDoc))
+ return (NOT_FOUND);
+#ifdef DIRED_SUPPORT
+ lynx_edit_mode = FALSE;
+#endif /* DIRED_SUPPORT */
+ return (NORMAL);
+}
+#endif /* !NO_CONFIG_INFO */
diff --git a/src/LYReadCFG.h b/src/LYReadCFG.h
new file mode 100644
index 0000000..7347f86
--- /dev/null
+++ b/src/LYReadCFG.h
@@ -0,0 +1,75 @@
+/*
+ * $LynxId: LYReadCFG.h,v 1.29 2014/02/12 23:58:37 tom Exp $
+ */
+#ifndef LYREADCFG_H
+#define LYREADCFG_H
+
+#ifndef LYSTRUCTS_H
+#include <LYStructs.h>
+#endif /* LYSTRUCTS_H */
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+#if defined(USE_COLOR_STYLE) || defined(USE_COLOR_TABLE)
+#define DEFAULT_COLOR -1
+#define NO_COLOR -2
+#define ERR_COLOR -3
+/* Note: the sense of colors that Lynx uses for defaults is the reverse of
+ * the standard for color-curses.
+ */
+#ifdef USE_DEFAULT_COLORS
+# ifdef USE_SLANG
+# define DEFAULT_FG "default"
+# define DEFAULT_BG "default"
+# else
+# ifdef HAVE_USE_DEFAULT_COLORS
+# define DEFAULT_FG DEFAULT_COLOR
+# define DEFAULT_BG DEFAULT_COLOR
+# else
+# define DEFAULT_FG COLOR_BLACK
+# define DEFAULT_BG COLOR_WHITE
+# endif
+# endif
+#else
+# ifdef USE_SLANG
+# define DEFAULT_FG "black"
+# define DEFAULT_BG "white"
+# else
+# define DEFAULT_FG COLOR_BLACK
+# define DEFAULT_BG COLOR_WHITE
+# endif
+#endif /* USE_DEFAULT_COLORS */
+ extern int default_fg;
+ extern int default_bg;
+ extern BOOL default_color_reset;
+
+ extern int check_color(const char *color, int the_default);
+ extern const char *lookup_color(int code);
+ extern void update_default_colors(void);
+#endif
+
+ extern void read_cfg(const char *cfg_filename,
+ const char *parent_filename,
+ int nesting_level,
+ FILE *fp0);
+ extern void free_lynx_cfg(void);
+ extern BOOLEAN have_read_cfg;
+
+ extern FILE *LYOpenCFG(const char *cfg_filename, const char
+ *parent_filename, const char *dft_filename);
+ extern int hiddenlinks_fun(char *next_arg);
+ extern int lynx_cfg_infopage(DocInfo *newdoc);
+ extern int lynx_compile_opts(DocInfo *newdoc);
+ extern int match_item_by_name(lynx_list_item_type *ptr, const char *name, int only_overriders);
+ extern lynx_list_item_type *find_item_by_number(lynx_list_item_type *
+ list_ptr,
+ char *number);
+ extern void reload_read_cfg(void); /* implemented in LYMain.c */
+ extern BOOL LYSetConfigValue(const char *name, const char *value);
+ extern void LYSetTextDomain(void);
+
+#ifdef __cplusplus
+}
+#endif
+#endif /* LYREADCFG_H */
diff --git a/src/LYSearch.c b/src/LYSearch.c
new file mode 100644
index 0000000..a56de21
--- /dev/null
+++ b/src/LYSearch.c
@@ -0,0 +1,379 @@
+/*
+ * $LynxId: LYSearch.c,v 1.40 2013/10/13 20:23:07 tom Exp $
+ */
+#include <HTUtils.h>
+#include <HTAlert.h>
+#include <LYUtils.h>
+#include <LYStrings.h>
+#include <LYSearch.h>
+#include <LYGlobalDefs.h>
+#include <GridText.h>
+
+#include <LYLeaks.h>
+
+#define MATCH(a,b) (BOOL)(LYno_attr_strstr(a, b) != 0)
+
+/*
+ * Handle special field-related comparisons for anchor_has_target() and
+ * link_has_target().
+ */
+BOOL field_has_target(FormInfo * field, const char *target)
+{
+ BOOL result = FALSE;
+ OptionType *option;
+ char *stars = NULL;
+ const char *cp;
+
+ if ((field != NULL && field->value != NULL) &&
+ field->type != F_HIDDEN_TYPE) {
+ if (field->type == F_PASSWORD_TYPE) {
+ /*
+ * Check the actual (hidden password), and then the displayed
+ * string - FM
+ */
+ if (MATCH(field->value, target)) {
+ result = TRUE;
+ } else {
+ StrAllocCopy(stars, field->value);
+ memset(stars, '*', strlen(stars));
+ result = MATCH(stars, target);
+ FREE(stars);
+ }
+ } else if (field->type == F_OPTION_LIST_TYPE) {
+ /*
+ * Search the option strings that are displayed when the popup is
+ * invoked - FM
+ */
+ for (option = field->select_list; option != NULL; option = option->next) {
+ if (MATCH(option->name, target)) {
+ result = TRUE;
+ break;
+ }
+ }
+ } else if (field->type == F_RADIO_TYPE) {
+ /*
+ * Search for checked or unchecked parens - FM
+ */
+ cp = ((field->num_value)
+ ? checked_radio
+ : unchecked_radio);
+ result = MATCH(cp, target);
+ } else if (field->type == F_CHECKBOX_TYPE) {
+ /*
+ * Search for checked or unchecked square brackets - FM
+ */
+ cp = ((field->num_value)
+ ? checked_box
+ : unchecked_box);
+ result = MATCH(cp, target);
+ } else {
+ result = MATCH(field->value, target);
+ }
+ }
+ return result;
+}
+
+/*
+ * see also anchor_has_target
+ */
+static BOOL link_has_target(int cur,
+ char *target)
+{
+ LinkInfo *a = &links[cur];
+ 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 = LYGetHiliteStr(cur, count);
+
+ if (part == NULL || part == last) {
+ if (MATCH(text, target)) {
+ return TRUE;
+ }
+ break;
+ }
+ StrAllocCat(text, part);
+ last = part;
+ }
+
+ return field_has_target(a->l_form, target);
+}
+
+/*
+ * Search for the target string inside of the links that are currently
+ * displayed on the screen beginning with the one after the currently selected
+ * one. If found set cur to the new value and return TRUE. If not found do
+ * not reset cur and return FALSE.
+ */
+
+static int check_next_target_in_links(int *cur,
+ char *target)
+{
+ int i;
+
+ if (nlinks != 0) {
+ for (i = *cur + 1; i < nlinks; ++i) {
+ if (link_has_target(i, target)) {
+ *cur = i;
+ return TRUE;
+ }
+ }
+ }
+ return FALSE;
+}
+
+static int check_prev_target_in_links(int *cur,
+ char *target)
+{
+ int i;
+
+ if (nlinks != 0) {
+ for (i = *cur - 1; i >= 0; --i) {
+ if (link_has_target(i, target)) {
+ *cur = i;
+ return TRUE;
+ }
+ }
+ }
+ return FALSE;
+}
+
+/*
+ * Textsearch checks the prev_target variable to see if it is empty. If it is
+ * then it requests a new search string. It then searches the current file for
+ * the next instance of the search string and finds the line number that the
+ * string is on
+ *
+ * This is the primary USER search engine and is case sensitive or case
+ * insensitive depending on the 'LYcase_sensitive' global variable
+ */
+BOOL textsearch(DocInfo *cur_doc,
+ bstring **prev_target,
+ int direction)
+{
+ int offset;
+ int oldcur = cur_doc->link;
+ static bstring *my_prev_target = NULL;
+ static BOOL first = TRUE;
+ char *cp;
+ int ch = 0;
+ RecallType recall;
+ int QueryTotal;
+ int QueryNum;
+ BOOLEAN FirstRecall = TRUE;
+
+ /*
+ * Initialize the search string buffer. - FM
+ */
+ if (first) {
+ BStrCopy0(my_prev_target, "");
+ first = FALSE;
+ }
+
+ QueryTotal = (search_queries ? HTList_count(search_queries) : 0);
+ recall = ((QueryTotal >= 1) ? RECALL_URL : NORECALL);
+ QueryNum = QueryTotal;
+
+ if (direction != 0) {
+ /*
+ * LYK_NEXT or LYK_PREV was pressed, so copy the buffer into
+ * prev_target.
+ */
+ BStrCopy(*prev_target, my_prev_target);
+ } else if (*prev_target == 0) {
+ BStrCopy0(*prev_target, "");
+ }
+
+ if (strlen((*prev_target)->str) == 0) {
+ /*
+ * This is a new WHEREIS search ('/'), or LYK_NEXT was pressed but
+ * there was no previous search, so we need to get a search string from
+ * the user. - FM
+ */
+ _statusline(ENTER_WHEREIS_QUERY);
+
+ ch = LYgetBString(prev_target, FALSE, 0, recall);
+ if (ch < 0) {
+ /*
+ * User cancelled the search via ^G. Restore prev_target and
+ * return. - FM
+ */
+ BStrCopy(*prev_target, my_prev_target);
+ HTInfoMsg(CANCELLED);
+ return (FALSE);
+ }
+ }
+
+ check_recall:
+ if (strlen((*prev_target)->str) == 0 &&
+ !(recall && (ch == UPARROW_KEY || ch == DNARROW_KEY))) {
+ /*
+ * No entry. Simply return, retaining the current buffer. Because
+ * prev_target is now reset, highlighting of the previous search string
+ * will no longer occur, but it can be used again via LYK_NEXT or
+ * LYK_PREV.
+ */
+ HTInfoMsg(CANCELLED);
+ return (FALSE);
+ }
+
+ if (recall && ch == UPARROW_KEY) {
+ if (FirstRecall) {
+ /*
+ * Use the current string or last query in the list. - FM
+ */
+ FirstRecall = FALSE;
+ if (!isBEmpty(my_prev_target)) {
+ for (QueryNum = (QueryTotal - 1); QueryNum > 0; QueryNum--) {
+ if ((cp = (char *) HTList_objectAt(search_queries,
+ QueryNum)) != NULL &&
+ !strcmp(my_prev_target->str, cp)) {
+ break;
+ }
+ }
+ } else {
+ QueryNum = 0;
+ }
+ } 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(*prev_target, cp);
+ if (!isBEmpty(my_prev_target) &&
+ !strcmp(my_prev_target->str, (*prev_target)->str)) {
+ _statusline(EDIT_CURRENT_QUERY);
+ } else if ((!isBEmpty(my_prev_target) && QueryTotal == 2) ||
+ (isBEmpty(my_prev_target) && QueryTotal == 1)) {
+ _statusline(EDIT_THE_PREV_QUERY);
+ } else {
+ _statusline(EDIT_A_PREV_QUERY);
+ }
+ ch = LYgetBString(prev_target, FALSE, 0, recall);
+ if (ch < 0) {
+ /*
+ * User canceled the search via ^G. Restore prev_target and
+ * return. - FM
+ */
+ BStrCopy(*prev_target, my_prev_target);
+ HTInfoMsg(CANCELLED);
+ return (FALSE);
+ }
+ goto check_recall;
+ }
+ } else if (recall && ch == DNARROW_KEY) {
+ if (FirstRecall) {
+ /*
+ * Use the current string or first query in the list. - FM
+ */
+ FirstRecall = FALSE;
+ if (!isBEmpty(my_prev_target)) {
+ for (QueryNum = 0; QueryNum < (QueryTotal - 1); QueryNum++) {
+ if ((cp = (char *) HTList_objectAt(search_queries,
+ QueryNum)) != NULL &&
+ !strcmp(my_prev_target->str, cp)) {
+ break;
+ }
+ }
+ } else {
+ QueryNum = QueryTotal - 1;
+ }
+ } 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(*prev_target, cp);
+ if (!isBEmpty(my_prev_target) &&
+ !strcmp(my_prev_target->str, (*prev_target)->str)) {
+ _statusline(EDIT_CURRENT_QUERY);
+ } else if ((!isBEmpty(my_prev_target) && QueryTotal == 2) ||
+ (isBEmpty(my_prev_target) && QueryTotal == 1)) {
+ _statusline(EDIT_THE_PREV_QUERY);
+ } else {
+ _statusline(EDIT_A_PREV_QUERY);
+ }
+ ch = LYgetBString(prev_target, FALSE, 0, recall);
+ if (ch < 0) {
+ /*
+ * User cancelled the search via ^G. Restore prev_target and
+ * return. - FM
+ */
+ BStrCopy(*prev_target, my_prev_target);
+ HTInfoMsg(CANCELLED);
+ return (FALSE);
+ }
+ goto check_recall;
+ }
+ }
+ /*
+ * Replace the search string buffer with the new target. - FM
+ */
+ BStrCopy(my_prev_target, *prev_target);
+ HTAddSearchQuery(my_prev_target->str);
+
+ if (direction < 0) {
+ offset = 0;
+ if (check_prev_target_in_links(&cur_doc->link, (*prev_target)->str)) {
+ /*
+ * Found in link, changed cur, we're done.
+ */
+ LYhighlight(FALSE, oldcur, (*prev_target)->str);
+ return (TRUE);
+ }
+ } else {
+
+ /*
+ * Search the links on the currently displayed page for the string,
+ * starting after the current link. - FM
+ */
+ if (check_next_target_in_links(&cur_doc->link, (*prev_target)->str)) {
+ /*
+ * Found in link, changed cur, we're done.
+ */
+ LYhighlight(FALSE, oldcur, (*prev_target)->str);
+ return (TRUE);
+ }
+
+ /*
+ * We'll search the text starting from the link we are on, or the next
+ * page.
+ */
+ if (nlinks == 0)
+ offset = (display_lines - 1);
+ else
+ offset = links[cur_doc->link].ly - 1;
+ }
+
+ /*
+ * Resume search, this time for all text. Set www_search_result if string
+ * found, and position the hit near top of screen.
+ */
+ www_user_search((cur_doc->line + offset), cur_doc, (*prev_target)->str, direction);
+ if (cur_doc->link != oldcur) {
+ LYhighlight(FALSE, oldcur, (*prev_target)->str);
+ return (TRUE);
+ }
+ return (BOOL) (www_search_result > 0);
+}
diff --git a/src/LYSearch.h b/src/LYSearch.h
new file mode 100644
index 0000000..17fd5dd
--- /dev/null
+++ b/src/LYSearch.h
@@ -0,0 +1,33 @@
+/*
+ * $LynxId: LYSearch.h,v 1.12 2013/10/03 11:24:06 tom Exp $
+ */
+#ifndef LYSEARCH_H
+#define LYSEARCH_H
+
+#ifndef HTFORMS_H
+#include <HTForms.h>
+#endif
+
+#ifndef LYSTRUCTS_H
+#include <LYStructs.h>
+#endif /* LYSTRUCT_H */
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+ extern BOOL field_has_target(FormInfo * field, const char *target);
+ extern BOOL textsearch(DocInfo *cur_doc,
+ bstring **prev_target,
+ int direction);
+
+#define IN_FILE 1
+#define IN_LINKS 2
+
+#ifndef NOT_FOUND
+#define NOT_FOUND 0
+#endif /* NOT_FOUND */
+
+#ifdef __cplusplus
+}
+#endif
+#endif /* LYSEARCH_H */
diff --git a/src/LYSession.c b/src/LYSession.c
new file mode 100644
index 0000000..a4438bd
--- /dev/null
+++ b/src/LYSession.c
@@ -0,0 +1,267 @@
+/* $LynxId: LYSession.c,v 1.12 2018/07/08 15:22:44 tom Exp $ */
+
+#include <LYSession.h>
+
+#include <LYLeaks.h>
+#include <LYUtils.h>
+#include <LYStrings.h>
+#include <LYHistory.h>
+#include <LYGlobalDefs.h>
+#include <LYMainLoop.h>
+#include <GridText.h>
+
+#ifdef USE_SESSIONS
+
+/* Example of how a session file may look:
+ */
+
+/* # lynx session
+ * / files
+ * / hereby
+ * / reduce
+ * g file://localhost/COPYRIGHT
+ * g https://lynx.invisible-island.net
+ * h 1 -1 file://localhost/COPYRIGHT Entry into main screen
+ * h 1 0 LYNXCACHE:/ Cache Jar
+ * h 1 16 file://localhost/usr/local/share/lynx_help/Lynx_users_guide.html#Cache Lynx Users Guide v2.8.6
+ * h 1 -1 file://localhost/COPYRIGHT Entry into main screen
+ * h 1 2 file://localhost/tmp/lynxmSefvcbXes/L12110-6407TMP.html#current Visited Links Page
+ * h 1 -1 file://localhost/COPYRIGHT Entry into main screen
+ * h 1 -1 LYNXMESSAGES:/ Your recent statusline messages
+ * V 0 file://localhost/COPYRIGHT Entry into main screen
+ * V 3 file://localhost/usr/local/share/lynx_help/Lynx_users_guide.html#Bookmarks Lynx Users Guide v2.8.6
+ */
+
+static char *get_filename(char *given_name)
+{
+ char *actual_filename = given_name;
+
+ /*
+ * If the specific "-sessionin" or "-sessionout" value is not given,
+ * try the "-session" value (if the AUTO_SESSION configuration is set).
+ * Finally try the SESSION_FILE configuration value.
+ */
+ if (isEmpty(actual_filename)) {
+ actual_filename = session_file;
+ if (isEmpty(actual_filename)) {
+ if (LYAutoSession) {
+ actual_filename = LYSessionFile;
+ }
+ }
+ }
+
+ return actual_filename;
+}
+
+/* Restore session from file, pretty slow, but it should be fine
+ * for everyday, normal use.
+ */
+void RestoreSession(void)
+{
+ char *my_filename = get_filename(sessionin_file);
+ FILE *fp;
+ char *buffer = 0;
+ DocInfo doc;
+ VisitedLink *vl;
+ int i = 0;
+ short errors = 10; /* how many syntax errors are allowed in
+ * session file before aborting. */
+ char *value1, *value2, *rsline, *linktext, *rslevel;
+
+ memset(&doc, 0, sizeof(doc));
+
+ /*
+ * This should be done only once, here: iff USE_SESSIONS is defined or:
+ * in mainloop(), otherwise history entries are lost
+ */
+ nhist = 0;
+
+ if (my_filename == NULL) {
+ /* nothing to do, so exit */
+ return;
+ }
+
+ CTRACE((tfp, "RestoreSession %s\n", my_filename));
+ SetDefaultMode(O_TEXT);
+ if ((fp = fopen(my_filename, TXT_R)) != NULL) {
+
+ /*
+ * This should be safe, entries are added to lynx until memory is
+ * exhausted
+ */
+ while (LYSafeGets(&buffer, fp) != 0) {
+ LYTrimNewline(buffer);
+ if (*buffer == '/') {
+#ifdef SEARCH_OUT_SESSION
+ if ((value1 = StrChr(buffer, ' ')) == 0) {
+ continue;
+ } else {
+ value1++;
+ HTAddSearchQuery(value1);
+ }
+#endif /* SEARCH_OUT_SESSION */
+ } else if (*buffer == 'g') {
+#ifdef GOTOURL_OUT_SESSION
+ if ((value1 = StrChr(buffer, ' ')) == 0)
+ continue;
+ else {
+ value1++;
+ HTAddGotoURL(value1);
+ }
+#endif /* GOTOURL_OUT_SESSION */
+ } else if (*buffer == 'h') {
+#ifdef HISTORY_OUT_SESSION
+ if ((rsline = StrChr(buffer, ' ')) == 0)
+ continue;
+ else {
+ rsline++;
+ if ((linktext = StrChr(rsline, ' ')) == 0)
+ continue;
+ else
+ *linktext++ = 0;
+ if ((value1 = StrChr(linktext, ' ')) == 0)
+ continue;
+ else
+ *value1++ = 0;
+ if ((value2 = StrChr(value1, '\t')) != 0) {
+ *value2++ = 0;
+ doc.line = atoi(rsline);
+ doc.link = atoi(linktext);
+ StrAllocCopy(doc.address, value1);
+ StrAllocCopy(doc.title, value2);
+ LYpush(&doc, TRUE);
+ }
+ }
+#endif /* HISTORY_OUT_SESSION */
+ } else if (*buffer == 'V') {
+#ifdef VLINK_OUT_SESSION
+ if ((rslevel = StrChr(buffer, ' ')) == 0)
+ continue;
+ else {
+ rslevel++;
+ if ((value1 = StrChr(rslevel, ' ')) == 0)
+ continue;
+ else
+ *value1++ = 0;
+ if ((value2 = StrChr(value1, '\t')) != 0) {
+ *value2++ = 0;
+ StrAllocCopy(doc.address, value1);
+ StrAllocCopy(doc.title, value2);
+ LYAddVisitedLink(&doc);
+ vl = (VisitedLink *)
+ HTList_objectAt(Visited_Links, i);
+ if (vl != NULL) {
+ vl->level = atoi(rslevel);
+ i++;
+ }
+ }
+ }
+#endif /* VLINK_OUT_SESSION */
+ } else if (*buffer == '#') {
+ /* This is comment; ignore it */
+ continue;
+ } else if (errors-- < 0) {
+ FREE(buffer);
+ break;
+ } else
+ continue;
+ }
+
+ LYCloseOutput(fp);
+ }
+ SetDefaultMode(O_BINARY);
+}
+
+/*
+ * Save session to file, overwriting one.
+ */
+void SaveSession(void)
+{
+ char *my_filename = get_filename(sessionout_file);
+ FILE *fp;
+ VisitedLink *vl;
+ int i, j, k;
+
+ if (my_filename == NULL) {
+ /* nothing to do, so exit */
+ return;
+ }
+
+ CTRACE((tfp, "SaveSession %s\n", my_filename));
+
+ SetDefaultMode(O_TEXT);
+ if ((fp = fopen(my_filename, TXT_W)) != NULL) {
+
+ fprintf(fp, "# lynx session\n"); /* @@@ simple for now */
+
+ /* Note use of session_limit, the most recent entries in list,
+ * from the end of list, are saved.
+ */
+
+#ifdef SEARCH_IN_SESSION
+ k = HTList_count(search_queries);
+ if (k > session_limit)
+ j = k - session_limit;
+ else
+ j = 0;
+ for (i = j; i < k; i++) {
+ fprintf(fp, "/ ");
+ fputs((char *) HTList_objectAt(search_queries, i), fp);
+ fprintf(fp, "\n");
+ }
+#endif /* SEARCH_IN_SESSION */
+
+#ifdef GOTOURL_IN_SESSION
+ k = HTList_count(Goto_URLs);
+ if (k > session_limit)
+ j = k - session_limit;
+ else
+ j = 0;
+ for (i = j; i < k; i++) {
+ fprintf(fp, "g ");
+ fputs((char *) HTList_objectAt(Goto_URLs, i), fp);
+ fprintf(fp, "\n");
+ }
+#endif /* GOTOURL_IN_SESSION */
+
+#ifdef HISTORY_IN_SESSION
+ k = nhist + nhist_extra;
+ if (k > session_limit)
+ j = k - session_limit;
+ else
+ j = 0;
+
+ for (i = j; i < k; i++) {
+ fprintf(fp, "h %d %d ", HDOC(i).line, HDOC(i).link);
+ fputs(HDOC(i).address, fp);
+ fprintf(fp, "\t");
+ fputs(HDOC(i).title, fp);
+ fprintf(fp, "\n");
+ }
+#endif /* HISTORY_IN_SESSION */
+
+#ifdef VLINK_IN_SESSION
+ k = HTList_count(Visited_Links);
+ if (k > session_limit)
+ j = k - session_limit;
+ else
+ j = 0;
+
+ for (i = j; i < k; i++) {
+ vl = (VisitedLink *) HTList_objectAt(Visited_Links, i);
+ if (vl != NULL) {
+ fprintf(fp, "V %d ", vl->level);
+ fputs(vl->address, fp);
+ fprintf(fp, "\t");
+ fputs(vl->title, fp);
+ fprintf(fp, "\n");
+ }
+ }
+#endif /* VLINK_IN_SESSION */
+
+ LYCloseOutput(fp);
+ }
+ SetDefaultMode(O_BINARY);
+}
+
+#endif /* USE_SESSIONS */
diff --git a/src/LYSession.h b/src/LYSession.h
new file mode 100644
index 0000000..90d74fc
--- /dev/null
+++ b/src/LYSession.h
@@ -0,0 +1,16 @@
+/* $LynxId: LYSession.h,v 1.3 2008/02/10 23:47:39 Paul.B.Mahol Exp $ */
+#ifndef LYSESSION_H
+#define LYSESSION_H
+
+#include <HTUtils.h>
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+ extern void RestoreSession(void);
+ extern void SaveSession(void);
+
+#ifdef __cplusplus
+}
+#endif
+#endif /* LYSESSION_H */
diff --git a/src/LYShowInfo.c b/src/LYShowInfo.c
new file mode 100644
index 0000000..129810c
--- /dev/null
+++ b/src/LYShowInfo.c
@@ -0,0 +1,485 @@
+/* $LynxId: LYShowInfo.c,v 1.86 2024/01/15 18:16:29 tom Exp $ */
+#include <HTUtils.h>
+#include <HTFile.h>
+#include <HTParse.h>
+#include <HTAlert.h>
+#include <HTTP.h>
+#include <LYCurses.h>
+#include <LYUtils.h>
+#include <LYStructs.h>
+#include <LYGlobalDefs.h>
+#include <LYShowInfo.h>
+#include <LYCharUtils.h>
+#include <GridText.h>
+#include <LYReadCFG.h>
+#include <LYCharSets.h>
+#include <LYStrings.h>
+
+#include <LYLeaks.h>
+
+#ifdef DIRED_SUPPORT
+#include <HTAAProt.h>
+#include <time.h>
+#include <LYLocal.h>
+#endif /* DIRED_SUPPORT */
+
+#define BEGIN_DL(text) fprintf(fp0, "<h2>%s</h2>\n<dl compact>", LYEntifyTitle(&buffer, text))
+#define END_DL() fprintf(fp0, "\n</dl>\n")
+
+#define ADD_SS(label,value) dt_String(fp0, label, value, 0)
+#define ADD_WW(label,value) dt_String(fp0, label, value, 1)
+#define ADD_NN(label,value,units) dt_Number(fp0, label, (long) value, units)
+
+static int label_columns;
+
+/*
+ * LYNX_VERSION and LYNX_DATE are generated by the configure script.
+ */
+BOOL LYVersionIsRelease(void)
+{
+ return TRUE;
+}
+
+const char *LYVersionStatus(void)
+{
+ return REL_VERSION;
+}
+
+const char *LYVersionDate(void)
+{
+ return LYNX_DATE;
+}
+
+static void dt_String(FILE *fp,
+ const char *label,
+ const char *value,
+ int allow_wide)
+{
+ int have;
+ int need;
+ char *the_label = NULL;
+ char *the_value = NULL;
+
+ StrAllocCopy(the_label, label);
+ StrAllocCopy(the_value, value);
+
+ have = (int) strlen(the_label);
+ need = LYstrExtent(the_label, have, label_columns);
+
+ LYEntify(&the_label, TRUE);
+ LYEntify(&the_value, TRUE);
+
+ fprintf(fp, "<dt>");
+ while (need++ < label_columns)
+ fprintf(fp, "&nbsp;");
+ if (LYwideLines && allow_wide)
+ fprintf(fp, "<em>%s</em> <pre>%s</pre>\n", the_label, the_value);
+ else
+ fprintf(fp, "<em>%s</em> %s\n", the_label, the_value);
+
+ FREE(the_label);
+ FREE(the_value);
+}
+
+static void dt_Number(FILE *fp0,
+ const char *label,
+ long number,
+ const char *units)
+{
+ char *value = NULL;
+ char *buffer = NULL;
+
+ HTSprintf(&value, "%ld %s", number, LYEntifyTitle(&buffer, units));
+ ADD_SS(label, value);
+ FREE(value);
+ FREE(buffer);
+}
+
+static void dt_URL(FILE *fp0, const char *address)
+{
+ if (address == NULL)
+ address = "";
+ ADD_WW(gettext("URL:"), address);
+
+ /*
+ * If the display handles UTF-8, and if the address uses %xy formatted
+ * characters, show the decoded URL on the next line.
+ */
+ if (LYCharSet_UC[current_char_set].enc == UCT_ENC_UTF8) {
+ char *working = NULL;
+
+ StrAllocCopy(working, address);
+ if (strcmp(HTUnEscape(working), address)) {
+ fprintf(fp0, "\n<br>%s\n", working);
+ }
+ free(working);
+ }
+}
+
+/*
+ * LYShowInfo prints a page of info about the current file and the link that
+ * the cursor is on.
+ */
+int LYShowInfo(DocInfo *doc,
+ DocInfo *newdoc,
+ char *owner_address)
+{
+ static char tempfile[LY_MAXPATH] = "\0";
+
+ int url_type;
+ FILE *fp0;
+ char *Title = NULL;
+ const char *cp;
+ char *temp = NULL;
+ char *buffer = NULL;
+
+ BOOLEAN LYInfoAdvanced = (BOOL) (user_mode == ADVANCED_MODE);
+
+#ifdef DIRED_SUPPORT
+ struct stat dir_info;
+ const char *name;
+#endif /* DIRED_SUPPORT */
+
+ if (LYReuseTempfiles) {
+ fp0 = LYOpenTempRewrite(tempfile, HTML_SUFFIX, "w");
+ } else {
+ (void) LYRemoveTemp(tempfile);
+ fp0 = LYOpenTemp(tempfile, HTML_SUFFIX, "w");
+ }
+ if (fp0 == NULL) {
+ HTAlert(CANNOT_OPEN_TEMP);
+ return (-1);
+ }
+
+ /*
+ * Point the address pointer at this Url
+ */
+ LYLocalFileToURL(&newdoc->address, tempfile);
+
+ if (nlinks > 0 && links[doc->link].lname != NULL &&
+ (url_type = is_url(links[doc->link].lname)) != 0 &&
+ (url_type == LYNXEXEC_URL_TYPE ||
+ url_type == LYNXPROG_URL_TYPE)) {
+ char *last_slash = strrchr(links[doc->link].lname, '/');
+ int next_to_last = (int) strlen(links[doc->link].lname) - 1;
+
+ if ((last_slash - links[doc->link].lname) == next_to_last) {
+ links[doc->link].lname[next_to_last] = '\0';
+ }
+ }
+
+ label_columns = 9;
+
+ WriteInternalTitle(fp0, SHOWINFO_TITLE);
+
+ fprintf(fp0, "<h1>%s %s (%s) (<a href=\"%s\">%s</a>)",
+ LYNX_NAME, LYNX_VERSION,
+ LYVersionDate(),
+ (LYVersionIsRelease()? LYNX_WWW_HOME : LYNX_WWW_DIST),
+ LYVersionStatus());
+
+ fprintf(fp0, "</h1>\n"); /* don't forget to close <h1> */
+
+#ifdef DIRED_SUPPORT
+ if (lynx_edit_mode && nlinks > 0) {
+
+ BEGIN_DL(gettext("Directory that you are currently viewing"));
+
+ temp = HTfullURL_toFile(doc->address);
+ ADD_SS(gettext("Name:"), temp);
+ FREE(temp);
+
+ dt_URL(fp0, doc->address);
+
+ END_DL();
+
+ temp = HTfullURL_toFile(links[doc->link].lname);
+
+ if (lstat(temp, &dir_info) == -1) {
+ CTRACE((tfp, "lstat(%s) failed, errno=%d\n", temp, errno));
+ HTAlert(CURRENT_LINK_STATUS_FAILED);
+ } else {
+ char modes[80];
+
+ label_columns = 16;
+ if (S_ISDIR(dir_info.st_mode)) {
+ BEGIN_DL(gettext("Directory that you have currently selected"));
+ } else if (S_ISREG(dir_info.st_mode)) {
+ BEGIN_DL(gettext("File that you have currently selected"));
+#ifdef S_IFLNK
+ } else if (S_ISLNK(dir_info.st_mode)) {
+ BEGIN_DL(gettext("Symbolic link that you have currently selected"));
+#endif
+ } else {
+ BEGIN_DL(gettext("Item that you have currently selected"));
+ }
+ ADD_SS(gettext("Full name:"), temp);
+#ifdef S_IFLNK
+ if (S_ISLNK(dir_info.st_mode)) {
+ char buf[MAX_LINE];
+ int buf_size;
+ size_t limit = sizeof(buf) - 1;
+
+ if ((buf_size = (int) readlink(temp, buf, limit)) != -1) {
+ if (buf_size > (int) limit)
+ buf_size = (int) limit;
+ buf[buf_size] = '\0';
+ } else {
+ sprintf(buf, "%.*s", (int) limit,
+ gettext("Unable to follow link"));
+ }
+ ADD_SS(gettext("Points to file:"), buf);
+ }
+#endif
+ name = HTAA_UidToName((int) dir_info.st_uid);
+ if (*name)
+ ADD_SS(gettext("Name of owner:"), name);
+ name = HTAA_GidToName((int) dir_info.st_gid);
+ if (*name)
+ ADD_SS(gettext("Group name:"), name);
+ if (S_ISREG(dir_info.st_mode)) {
+ ADD_NN(gettext("File size:"),
+ (long) dir_info.st_size,
+ gettext("(bytes)"));
+ }
+ /*
+ * Include date and time information.
+ */
+ ADD_SS(gettext("Creation date:"),
+ ctime(&dir_info.st_ctime));
+
+ ADD_SS(gettext("Last modified:"),
+ ctime(&dir_info.st_mtime));
+
+ ADD_SS(gettext("Last accessed:"),
+ ctime(&dir_info.st_atime));
+
+ END_DL();
+
+ label_columns = 9;
+ BEGIN_DL(gettext("Access Permissions"));
+ modes[0] = '\0';
+ modes[1] = '\0'; /* In case there are no permissions */
+ modes[2] = '\0';
+ if ((dir_info.st_mode & S_IRUSR))
+ strcat(modes, ", read");
+ if ((dir_info.st_mode & S_IWUSR))
+ strcat(modes, ", write");
+ if ((dir_info.st_mode & S_IXUSR)) {
+ if (S_ISDIR(dir_info.st_mode))
+ strcat(modes, ", search");
+ else {
+ strcat(modes, ", execute");
+ if ((dir_info.st_mode & S_ISUID))
+ strcat(modes, ", setuid");
+ }
+ }
+ ADD_SS(gettext("Owner:"), &modes[2]);
+
+ modes[0] = '\0';
+ modes[1] = '\0'; /* In case there are no permissions */
+ modes[2] = '\0';
+ if ((dir_info.st_mode & S_IRGRP))
+ strcat(modes, ", read");
+ if ((dir_info.st_mode & S_IWGRP))
+ strcat(modes, ", write");
+ if ((dir_info.st_mode & S_IXGRP)) {
+ if (S_ISDIR(dir_info.st_mode))
+ strcat(modes, ", search");
+ else {
+ strcat(modes, ", execute");
+ if ((dir_info.st_mode & S_ISGID))
+ strcat(modes, ", setgid");
+ }
+ }
+ ADD_SS(gettext("Group:"), &modes[2]);
+
+ modes[0] = '\0';
+ modes[1] = '\0'; /* In case there are no permissions */
+ modes[2] = '\0';
+ if ((dir_info.st_mode & S_IROTH))
+ strcat(modes, ", read");
+ if ((dir_info.st_mode & S_IWOTH))
+ strcat(modes, ", write");
+ if ((dir_info.st_mode & S_IXOTH)) {
+ if (S_ISDIR(dir_info.st_mode))
+ strcat(modes, ", search");
+ else {
+ strcat(modes, ", execute");
+#ifdef S_ISVTX
+ if ((dir_info.st_mode & S_ISVTX))
+ strcat(modes, ", sticky");
+#endif
+ }
+ }
+ ADD_SS(gettext("World:"), &modes[2]);
+ END_DL();
+ }
+ FREE(temp);
+ } else {
+#endif /* DIRED_SUPPORT */
+
+ BEGIN_DL(gettext("File that you are currently viewing"));
+
+ LYformTitle(&Title, doc->title);
+ HTSprintf(&temp, "%s%s",
+ LYEntifyTitle(&buffer, Title),
+ ((doc->isHEAD &&
+ !strstr(Title, " (HEAD)") &&
+ !strstr(Title, " - HEAD")) ? " (HEAD)" : ""));
+ ADD_SS(gettext("Linkname:"), temp);
+ FREE(temp);
+
+ dt_URL(fp0, doc->address);
+
+ if (HTLoadedDocumentCharset()) {
+ ADD_SS(gettext("Charset:"),
+ HTLoadedDocumentCharset());
+ } else {
+ LYUCcharset *p_in = HTAnchor_getUCInfoStage(HTMainAnchor,
+ UCT_STAGE_PARSER);
+
+ if (!p_in || isEmpty(p_in->MIMEname) ||
+ HTAnchor_getUCLYhndl(HTMainAnchor, UCT_STAGE_PARSER) < 0) {
+ p_in = HTAnchor_getUCInfoStage(HTMainAnchor, UCT_STAGE_MIME);
+ }
+ if (p_in && non_empty(p_in->MIMEname) &&
+ HTAnchor_getUCLYhndl(HTMainAnchor, UCT_STAGE_MIME) >= 0) {
+ HTSprintf(&temp, "%s %s",
+ LYEntifyTitle(&buffer, p_in->MIMEname),
+ gettext("(assumed)"));
+ ADD_SS(gettext("Charset:"), p_in->MIMEname);
+ FREE(temp);
+ }
+ }
+
+ if ((cp = HText_getServer()) != NULL && *cp != '\0')
+ ADD_SS(gettext("Server:"), cp);
+
+ if ((cp = HText_getDate()) != NULL && *cp != '\0')
+ ADD_SS(gettext("Date:"), cp);
+
+ if ((cp = HText_getLastModified()) != NULL && *cp != '\0')
+ ADD_SS(gettext("Last Mod:"), cp);
+
+ if (LYInfoAdvanced) {
+ if (HTMainAnchor && HTMainAnchor->expires) {
+ ADD_SS(gettext("Expires:"), HTMainAnchor->expires);
+ }
+ if (HTMainAnchor && HTMainAnchor->cache_control) {
+ ADD_SS(gettext("Cache-Control:"), HTMainAnchor->cache_control);
+ }
+ if (HTMainAnchor && HTMainAnchor->content_length > 0) {
+ ADD_NN(gettext("Content-Length:"),
+ HTMainAnchor->content_length,
+ gettext("bytes"));
+ } else {
+ ADD_NN(gettext("Length:"),
+ HText_getNumOfBytes(),
+ gettext("bytes"));
+ }
+ if (HTMainAnchor && HTMainAnchor->content_language) {
+ ADD_SS(gettext("Language:"), HTMainAnchor->content_language);
+ }
+ }
+
+ if (doc->post_data) {
+ fprintf(fp0, "<dt><em>%s</em> <xmp>%.*s</xmp>\n",
+ LYEntifyTitle(&buffer, gettext("Post Data:")),
+ BStrLen(doc->post_data),
+ BStrData(doc->post_data));
+ ADD_SS(gettext("Post Content Type:"), doc->post_content_type);
+ }
+
+ ADD_SS(gettext("Owner(s):"),
+ (owner_address
+ ? owner_address
+ : NO_NOTHING));
+
+ ADD_NN(gettext("size:"),
+ HText_getNumOfLines(),
+ gettext("lines"));
+
+ StrAllocCopy(temp,
+ ((lynx_mode == FORMS_LYNX_MODE)
+ ? gettext("forms mode")
+ : (HTisDocumentSource()
+ ? gettext("source")
+ : gettext("normal"))));
+ if (doc->safe)
+ StrAllocCat(temp, gettext(", safe"));
+ if (doc->internal_link)
+ StrAllocCat(temp, gettext(", via internal link"));
+
+ if (LYInfoAdvanced) {
+ if (HText_hasNoCacheSet(HTMainText))
+ StrAllocCat(temp, gettext(", no-cache"));
+ if (HTAnchor_isISMAPScript((HTAnchor *) HTMainAnchor))
+ StrAllocCat(temp, gettext(", ISMAP script"));
+ if (doc->bookmark)
+ StrAllocCat(temp, gettext(", bookmark file"));
+ }
+
+ ADD_SS(gettext("mode:"), temp);
+ FREE(temp);
+
+ END_DL();
+
+ if (nlinks > 0) {
+ BEGIN_DL(gettext("Link that you currently have selected"));
+ ADD_SS(gettext("Linkname:"),
+ LYGetHiliteStr(doc->link, 0));
+ if (lynx_mode == FORMS_LYNX_MODE &&
+ links[doc->link].type == WWW_FORM_LINK_TYPE) {
+ if (links[doc->link].l_form->submit_method) {
+ int method = links[doc->link].l_form->submit_method;
+ char *enctype = links[doc->link].l_form->submit_enctype;
+
+ ADD_SS(gettext("Method:"),
+ ((method == URL_POST_METHOD) ? "POST" :
+ ((method == URL_MAIL_METHOD) ? "(email)" :
+ "GET")));
+ ADD_SS(gettext("Enctype:"),
+ (non_empty(enctype)
+ ? enctype
+ : "application/x-www-form-urlencoded"));
+ }
+ if (links[doc->link].l_form->submit_action) {
+ ADD_SS(gettext("Action:"),
+ links[doc->link].l_form->submit_action);
+ }
+ if (!(links[doc->link].l_form->submit_method &&
+ links[doc->link].l_form->submit_action)) {
+ fprintf(fp0, "<dt>&nbsp;%s\n",
+ LYEntifyTitle(&buffer, gettext("(Form field)")));
+ }
+ } else {
+ dt_URL(fp0, NonNull(links[doc->link].lname));
+ }
+ END_DL();
+
+ } else {
+ fprintf(fp0, "<h2>%s</h2>",
+ LYEntifyTitle(&buffer,
+ gettext("No Links on the current page")));
+ }
+
+ if ((cp = HText_getHttpHeaders()) != 0) {
+ fprintf(fp0, "<h2>%s</h2>",
+ LYEntifyTitle(&buffer, gettext("Server Headers:")));
+ fprintf(fp0, "<pre>%s</pre>",
+ LYEntifyTitle(&buffer, cp));
+ }
+#ifdef DIRED_SUPPORT
+ }
+#endif /* DIRED_SUPPORT */
+ EndInternalPage(fp0);
+
+ LYrefresh();
+
+ LYCloseTemp(tempfile);
+ FREE(Title);
+ FREE(buffer);
+
+ return (0);
+}
diff --git a/src/LYShowInfo.h b/src/LYShowInfo.h
new file mode 100644
index 0000000..443bff3
--- /dev/null
+++ b/src/LYShowInfo.h
@@ -0,0 +1,21 @@
+#ifndef LYSHOWINFO_H
+#define LYSHOWINFO_H
+
+#ifndef LYSTRUCTS_H
+#include <LYStructs.h>
+#endif /* LYSTRUCTS_H */
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+ extern BOOL LYVersionIsRelease(void);
+ extern const char *LYVersionStatus(void);
+ extern const char *LYVersionDate(void);
+ extern int LYShowInfo(DocInfo *doc,
+ DocInfo *newdoc,
+ char *owner_address);
+
+#ifdef __cplusplus
+}
+#endif
+#endif /* LYSHOWINFO_H */
diff --git a/src/LYSignal.h b/src/LYSignal.h
new file mode 100644
index 0000000..627edb3
--- /dev/null
+++ b/src/LYSignal.h
@@ -0,0 +1,31 @@
+#ifndef LYSIGNAL_H
+#define LYSIGNAL_H
+
+#include <signal.h>
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+#ifdef VMS
+ extern void VMSsignal(int sig, void (*func) ());
+
+#ifdef signal
+#undef signal
+#endif /* signal */
+#define signal(a,b) VMSsignal(a,b) /* use LYCurses.c routines for interrupts */
+#endif /* VMS */
+
+#ifdef HAVE_SIGACTION
+ typedef void LYSigHandlerFunc_t (int);
+
+/* implementation in LYUtils.c */
+ extern void LYExtSignal(int sig, LYSigHandlerFunc_t *handler);
+
+#else
+#define LYExtSignal(sig,h) signal(sig, h)
+#endif
+
+#ifdef __cplusplus
+}
+#endif
+#endif /* LYSIGNAL_H */
diff --git a/src/LYStrings.c b/src/LYStrings.c
new file mode 100644
index 0000000..2bbd56d
--- /dev/null
+++ b/src/LYStrings.c
@@ -0,0 +1,6217 @@
+/* $LynxId: LYStrings.c,v 1.282 2023/10/23 23:41:44 tom Exp $ */
+#include <HTUtils.h>
+#include <HTCJK.h>
+#include <UCAux.h>
+#include <LYGlobalDefs.h>
+#include <LYUtils.h>
+#include <LYStrings.h>
+#include <GridText.h>
+#include <LYKeymap.h>
+#include <LYClean.h>
+#include <LYMail.h>
+#include <LYNews.h>
+#include <LYOptions.h>
+#include <LYCharSets.h>
+#include <HTAlert.h>
+#include <HTString.h>
+#include <LYCharUtils.h>
+#include <HTList.h>
+#include <HTParse.h>
+#ifdef USE_MOUSE
+#include <LYMainLoop.h>
+#endif
+
+#ifdef DJGPP_KEYHANDLER
+#include <pc.h>
+#include <keys.h>
+#endif /* DJGPP_KEYHANDLER */
+
+#ifdef USE_COLOR_STYLE
+#include <LYHash.h>
+#include <AttrList.h>
+#endif
+
+#ifdef USE_SCROLLBAR
+#include <LYMainLoop.h>
+#endif
+
+#ifdef USE_CMD_LOGGING
+#include <LYReadCFG.h>
+#include <LYrcFile.h>
+#endif
+
+#include <LYShowInfo.h>
+#include <LYLeaks.h>
+
+#if defined(WIN_EX)
+#undef BUTTON_CTRL
+#define BUTTON_CTRL 0 /* Quick hack */
+#endif
+
+#ifdef DEBUG_EDIT
+#define CTRACE_EDIT(p) CTRACE(p)
+#else
+#define CTRACE_EDIT(p) /*nothing */
+#endif
+
+#ifdef SUPPORT_MULTIBYTE_EDIT
+#define IsWordChar(c) (isalnum(UCH(c)) || is8bits(c))
+#else
+#define IsWordChar(c) isalnum(UCH(c))
+#endif
+
+/*
+ * The edit_history lists allow the user to press tab when entering URL to get
+ * the closest match in the closet
+ */
+#define LYClosetSize 100
+
+static HTList *URL_edit_history;
+static HTList *MAIL_edit_history;
+
+/* If you want to add mouse support for some new platform, it's fairly
+ * simple to do. Once you've determined the X and Y coordinates of
+ * the mouse event, loop through the elements in the links[] array and
+ * see if the coordinates fall within a highlighted link area. If so,
+ * the code must set mouse_link to the index of the chosen link,
+ * and return a key value that corresponds to LYK_ACTIVATE. The
+ * LYK_ACTIVATE code in LYMainLoop.c will then check mouse_link
+ * and activate that link. If the mouse event didn't fall within a
+ * link, the code should just set mouse_link to -1 and return -1. --AMK
+ */
+
+/* The number of the link selected w/ the mouse (-1 if none) */
+static int mouse_link = -1;
+
+static int have_levent;
+
+#if defined(USE_MOUSE) && defined(NCURSES)
+static MEVENT levent;
+#endif
+
+/* Return the value of mouse_link */
+int peek_mouse_levent(void)
+{
+#if defined(USE_MOUSE) && defined(NCURSES)
+ if (have_levent > 0) {
+ ungetmouse(&levent);
+ have_levent--;
+ return 1;
+ }
+#endif
+ return 0;
+}
+
+/* Return the value of mouse_link, erasing it */
+int get_mouse_link(void)
+{
+ int t;
+
+ t = mouse_link;
+ mouse_link = -1;
+ if (t < 0)
+ t = -1; /* Backward compatibility. */
+ return t;
+}
+
+/* Return the value of mouse_link */
+int peek_mouse_link(void)
+{
+ return mouse_link;
+}
+
+int fancy_mouse(WINDOW * win, int row,
+ int *position)
+{
+ int cmd = LYK_DO_NOTHING;
+
+#ifdef USE_MOUSE
+/*********************************************************************/
+
+#if defined(WIN_EX) && defined(PDCURSES)
+
+ request_mouse_pos();
+
+ if (BUTTON_STATUS(1)
+ && (MOUSE_X_POS >= getbegx(win) &&
+ MOUSE_X_POS < (getbegx(win) + getmaxx(win)))) {
+ int mypos = MOUSE_Y_POS - getbegy(win);
+ int delta = mypos - row;
+
+ if (mypos + 1 == getmaxy(win)) {
+ /* At the decorative border: scroll forward */
+ if (BUTTON_STATUS(1) & BUTTON1_TRIPLE_CLICKED)
+ cmd = LYK_END;
+ else if (BUTTON_STATUS(1) & BUTTON1_DOUBLE_CLICKED)
+ cmd = LYK_NEXT_PAGE;
+ else
+ cmd = LYK_NEXT_LINK;
+ } else if (mypos >= getmaxy(win)) {
+ if (BUTTON_STATUS(1) & (BUTTON1_DOUBLE_CLICKED | BUTTON1_TRIPLE_CLICKED))
+ cmd = LYK_END;
+ else
+ cmd = LYK_NEXT_PAGE;
+ } else if (mypos == 0) {
+ /* At the decorative border: scroll back */
+ if (BUTTON_STATUS(1) & BUTTON1_TRIPLE_CLICKED)
+ cmd = LYK_HOME;
+ else if (BUTTON_STATUS(1) & BUTTON1_DOUBLE_CLICKED)
+ cmd = LYK_PREV_PAGE;
+ else
+ cmd = LYK_PREV_LINK;
+ } else if (mypos < 0) {
+ if (BUTTON_STATUS(1) & (BUTTON1_DOUBLE_CLICKED | BUTTON1_TRIPLE_CLICKED))
+ cmd = LYK_HOME;
+ else
+ cmd = LYK_PREV_PAGE;
+#ifdef KNOW_HOW_TO_TOGGLE
+ } else if (BUTTON_STATUS(1) & (BUTTON_CTRL)) {
+ cur_selection += delta;
+ cmd = LYX_TOGGLE;
+#endif
+ } else if (BUTTON_STATUS(1) & (BUTTON_ALT | BUTTON_SHIFT | BUTTON_CTRL)) {
+ /* Probably some unrelated activity, such as selecting some text.
+ * Select, but do nothing else.
+ */
+ *position += delta;
+ cmd = -1;
+ } else {
+ /* No scrolling or overflow checks necessary. */
+ *position += delta;
+ cmd = LYK_ACTIVATE;
+ }
+ } else if (BUTTON_STATUS(1) & (BUTTON3_CLICKED | BUTTON3_DOUBLE_CLICKED | BUTTON3_TRIPLE_CLICKED)) {
+ cmd = LYK_QUIT;
+ }
+#else
+#if defined(NCURSES)
+#define ButtonModifiers (BUTTON_ALT | BUTTON_SHIFT | BUTTON_CTRL)
+ MEVENT event;
+
+ getmouse(&event);
+ if ((event.bstate & (BUTTON1_CLICKED |
+ BUTTON1_DOUBLE_CLICKED |
+ BUTTON1_TRIPLE_CLICKED))) {
+ int mypos = event.y - getbegy(win);
+ int delta = mypos - row;
+
+ if ((event.x < getbegx(win) ||
+ event.x >= (getbegx(win) + getmaxx(win)))
+ && !(event.bstate & ButtonModifiers))
+ return LYK_QUIT; /* User clicked outside, wants to quit? */
+ if (mypos + 1 == getmaxy(win)) {
+ /* At the decorative border: scroll forward */
+ if (event.bstate & BUTTON1_TRIPLE_CLICKED)
+ cmd = LYK_END;
+ else if (event.bstate & BUTTON1_DOUBLE_CLICKED)
+ cmd = LYK_NEXT_PAGE;
+ else
+ cmd = LYK_NEXT_LINK;
+ } else if (mypos >= getmaxy(win)) {
+ if (event.bstate & (BUTTON1_DOUBLE_CLICKED |
+ BUTTON1_TRIPLE_CLICKED))
+ cmd = LYK_END;
+ else
+ cmd = LYK_NEXT_PAGE;
+ } else if (mypos == 0) {
+ /* At the decorative border: scroll back */
+ if (event.bstate & BUTTON1_TRIPLE_CLICKED)
+ cmd = LYK_HOME;
+ else if (event.bstate & BUTTON1_DOUBLE_CLICKED)
+ cmd = LYK_PREV_PAGE;
+ else
+ cmd = LYK_PREV_LINK;
+ } else if (mypos < 0) {
+ if (event.bstate & (BUTTON1_DOUBLE_CLICKED |
+ BUTTON1_TRIPLE_CLICKED))
+ cmd = LYK_HOME;
+ else
+ cmd = LYK_PREV_PAGE;
+#ifdef KNOW_HOW_TO_TOGGLE
+ } else if (event.bstate & (BUTTON_CTRL)) {
+ cur_selection += delta;
+ cmd = LYX_TOGGLE;
+#endif
+ } else if (event.x <= getbegx(win) + 1 ||
+ event.x >= getbegx(win) + getmaxx(win) - 2) {
+ /* Click on left or right border for positioning without
+ * immediate action: select, but do nothing else.
+ * Actually, allow an error of one position inwards. - kw
+ */
+ *position += delta;
+ cmd = -1;
+ } else if (event.bstate & ButtonModifiers) {
+ /* Probably some unrelated activity, such as selecting some text.
+ * Select, but do nothing else.
+ */
+ /* Possibly this is never returned by ncurses, so this case
+ * may be useless depending on situation (kind of mouse support
+ * and library versions). - kw
+ */
+ *position += delta;
+ cmd = -1;
+ } else {
+ /* No scrolling or overflow checks necessary. */
+ *position += delta;
+ cmd = LYK_ACTIVATE;
+ }
+ } else if (event.bstate & (BUTTON3_CLICKED | BUTTON3_DOUBLE_CLICKED | BUTTON3_TRIPLE_CLICKED)) {
+ cmd = LYK_QUIT;
+ }
+#endif /* NCURSES */
+#endif /* PDCURSES */
+
+/************************************************************************/
+#endif /* USE_MOUSE */
+ (void) win;
+ (void) row;
+ (void) position;
+
+ return cmd;
+}
+
+/*
+ * Manage the collection of edit-histories
+ */
+static HTList *whichRecall(RecallType recall)
+{
+ HTList **list;
+
+ switch (recall) {
+ case RECALL_CMD:
+ return LYcommandList();
+ case RECALL_MAIL:
+ list = &MAIL_edit_history;
+ break;
+ default:
+ list = &URL_edit_history;
+ break;
+ }
+ if (*list == 0)
+ *list = HTList_new();
+ return *list;
+}
+
+/*
+ * Remove the oldest item in the closet
+ */
+static void LYRemoveFromCloset(HTList *list)
+{
+ void *data = HTList_removeFirstObject(list);
+
+ if (data != 0)
+ FREE(data);
+}
+
+void LYCloseCloset(RecallType recall)
+{
+ HTList *list = whichRecall(recall);
+
+ while (!HTList_isEmpty(list)) {
+ LYRemoveFromCloset(list);
+ }
+ HTList_delete(list); /* should already be empty */
+}
+
+/*
+ * Strategy: We begin at the top and search downwards. We return the first
+ * match, i.e., the newest since we search from the top. This should be made
+ * more intelligent, but works for now.
+ */
+static char *LYFindInCloset(RecallType recall, char *base)
+{
+ HTList *list = whichRecall(recall);
+ char *data;
+ size_t len = strlen(base);
+
+ while (!HTList_isEmpty(list)) {
+ data = (char *) HTList_nextObject(list);
+ if (data != NULL && !StrNCmp(base, data, len))
+ return (data);
+ }
+
+ return (0);
+}
+
+static void LYAddToCloset(RecallType recall, char *str)
+{
+ HTList *list = whichRecall(recall);
+ char *data = NULL;
+
+ StrAllocCopy(data, str);
+ HTList_addObject(list, data);
+ while (HTList_count(list) > LYClosetSize)
+ LYRemoveFromCloset(list);
+}
+
+#ifdef USE_MOUSE
+static int XYdist(int x1,
+ int y1,
+ int x2,
+ int y2,
+ int dx2)
+{
+ int xerr = 3 * (x2 - x1), yerr = 9 * (y2 - y1);
+
+ if (xerr < 0)
+ xerr = 3 * (x1 - x2 - dx2) + 1; /* pos after string not really in it */
+ if (xerr < 0)
+ xerr = 0;
+ if (yerr < 0)
+ yerr = -yerr;
+ if (!yerr) /* same line is good */
+ return (xerr > 0) ? (xerr * 2 - 1) : 0;
+ if (xerr < 9 && yerr) /* x-dist of 3 cell better than y-dist of 1 cell */
+ yerr += (9 - xerr);
+ return 2 * xerr + yerr; /* Subjective factor; ratio -> approx. 6 / 9 */
+/*
+old: (IZ 1999-07-30)
+ 3 2 2 2 1 1 1 XX XX XX XX XX 0 1 1 1 2 2 2 3 3
+ 4\ 3 3 3 2 2 2 2 2 2 2 2 2 2 2 2 3 3 3/ 4 4
+ 5 4 4 4\ 3 3 3 3 3 3 3 3 3 3 3 3/ 4 4 4 5 5
+ 6 5 5 5 4 4 4 4 4 4 4 4 4 4 4 4 5 5 5 6 5
+now: (kw 1999-10-23)
+41 35 29|23 17 11 5 XX XX XX XX XX 1 7 13 19 25|31 37 43 49
+ 45 39 33\27 24 21 18 18 18 18 18 19 22 25 28/34 40 46 50
+ 48 42 36 33 30\27 27 27 27 27 28/31 34 37 43 49
+ 51 45 42 39 36 36 36 36 36 37 40 43 46 49
+ 51 48 45 45 45 45 45 46 49 52
+*/
+}
+
+/* Given X and Y coordinates of a mouse event, set mouse_link to the
+ * index of the corresponding hyperlink, or set mouse_link to -1 if no
+ * link matches the event. Returns -1 if no link matched the click,
+ * or a keycode that must be returned from LYgetch() to activate the
+ * link.
+ */
+
+static int set_clicked_link(int x,
+ int y,
+ int code,
+ int clicks)
+{
+ int left = 6;
+ int right = LYcolLimit - 5;
+
+ /* yes, I am assuming that my screen will be a certain width. */
+ int i;
+ int c = -1;
+
+ if (y == (LYlines - 1) || y == 0) { /* First or last row */
+ /* XXXX In fact # is not always at x==0? KANJI_CODE_OVERRIDE? */
+ int toolbar = (y == 0 && HText_hasToolbar(HTMainText));
+
+ mouse_link = -2;
+ if (x == 0 && toolbar) /* On '#' */
+ c = LAC_TO_LKC0(LYK_TOOLBAR);
+#if defined(CAN_CUT_AND_PASTE) && defined(USE_COLOR_STYLE)
+ else if (y == 0 && x == LYcolLimit && s_hot_paste != NOSTYLE)
+ c = LAC_TO_LKC0(LYK_PASTE_URL);
+#endif
+ else if (clicks > 1) {
+ if (x < left + toolbar)
+ c = (code == FOR_PROMPT && y)
+ ? HOME_KEY : LAC_TO_LKC0(LYK_MAIN_MENU);
+ else if (x > right)
+ c = (code == FOR_PROMPT && y)
+ ? END_KEY : LAC_TO_LKC0(LYK_VLINKS);
+ else if (y) /* Last row */
+ c = LAC_TO_LKC0(LYK_END);
+ else /* First row */
+ c = LAC_TO_LKC0(LYK_HOME);
+ } else {
+ if (x < left + toolbar)
+ c = (code == FOR_PROMPT && y)
+ ? LTARROW_KEY
+ : (
+#ifdef USE_COLOR_STYLE
+ (s_forw_backw != NOSTYLE && x - toolbar >= 3)
+ ? LAC_TO_LKC0(LYK_NEXT_DOC)
+ : LAC_TO_LKC0(LYK_PREV_DOC)
+#else
+ LAC_TO_LKC0(LYK_NEXT_DOC)
+#endif
+ );
+ else if (x > right)
+ c = (code == FOR_PROMPT && y)
+ ? RTARROW_KEY : LAC_TO_LKC0(LYK_HISTORY);
+ else if (y) /* Last row */
+ c = LAC_TO_LKC0(LYK_NEXT_PAGE);
+ else /* First row */
+ c = LAC_TO_LKC0(LYK_PREV_PAGE);
+ }
+#ifdef USE_SCROLLBAR
+ } else if (x == (LYcols - 1) && LYShowScrollbar && LYsb_begin >= 0) {
+ int h = display_lines - 2 * (LYsb_arrow != 0);
+
+ mouse_link = -2;
+ y -= 1 + (LYsb_arrow != 0);
+ if (y < 0)
+ return LAC_TO_LKC0(LYK_UP_TWO);
+ if (y >= h)
+ return LAC_TO_LKC0(LYK_DOWN_TWO);
+
+ if (clicks >= 2) {
+ double frac = (1. * y) / (h - 1);
+ int l = HText_getNumOfLines() + 1; /* NOL() off by one? */
+
+ l -= display_lines;
+ if (l > 0)
+ LYSetNewline((int) (frac * l + 1 + 0.5));
+ return LYReverseKeymap(LYK_DO_NOTHING);
+ }
+
+ if (y < LYsb_begin)
+ return LAC_TO_LKC0(LYK_PREV_PAGE);
+ if (y >= LYsb_end)
+ return LAC_TO_LKC0(LYK_NEXT_PAGE);
+ mouse_link = -1; /* No action in edit fields */
+#endif
+ } else {
+ int mouse_err = 29, /* subjctv-dist better than this for approx stuff */ cur_err;
+
+ /* Loop over the links and see if we can get a match */
+ for (i = 0; i < nlinks; i++) {
+ int len, lx = links[i].lx, is_text = 0;
+ int count = 0;
+ const char *text = LYGetHiliteStr(i, count);
+
+ if (links[i].type == WWW_FORM_LINK_TYPE
+ && F_TEXTLIKE(links[i].l_form->type))
+ is_text = 1;
+
+ /* Check the first line of the link */
+ if (text != NULL) {
+ if (is_text)
+ len = links[i].l_form->size;
+ else
+ len = (int) LYstrCells(text);
+ cur_err = XYdist(x, y, links[i].lx, links[i].ly, len);
+ /* Check the second line */
+ while (cur_err > 0
+ && (text = LYGetHiliteStr(i, ++count)) != NULL) {
+ /* Note that there is at most one hightext if is_text */
+ int cur_err_2 = XYdist(x, y,
+ LYGetHilitePos(i, count),
+ links[i].ly + count,
+ (int) LYstrCells(text));
+
+ cur_err = HTMIN(cur_err, cur_err_2);
+ }
+ if (cur_err > 0 && is_text)
+ cur_err--; /* a bit of preference for text fields,
+ enter field if hit exactly at end - kw */
+ if (cur_err == 0) {
+ int cury, curx;
+
+ LYGetYX(cury, curx);
+ /* double-click, if we care:
+ submit text submit fields. - kw */
+ if (clicks > 1 && is_text &&
+ links[i].l_form->type == F_TEXT_SUBMIT_TYPE) {
+ if (code != FOR_INPUT
+ /* submit current input field directly */
+ || !(cury == y &&
+ (curx >= lx) &&
+ ((curx - lx) <= len))) {
+ c = LAC_TO_LKC0(LYK_MOUSE_SUBMIT);
+ mouse_link = i;
+ } else {
+ c = LAC_TO_LKC0(LYK_MOUSE_SUBMIT);
+ mouse_link = -1;
+ }
+ mouse_err = 0;
+ break;
+ }
+ if (code != FOR_INPUT
+ /* Do not pick up the current input field */
+ || !((cury == y && (curx >= lx) && ((curx - lx) <= len)))) {
+ if (is_text) {
+ have_levent = 1;
+#if defined(TEXTFIELDS_MAY_NEED_ACTIVATION) && defined(INACTIVE_INPUT_STYLE_VH)
+ if (x == links[i].lx && y == links[i].ly)
+ textinput_redrawn = FALSE;
+#endif /* TEXTFIELDS_MAY_NEED_ACTIVATION && INACTIVE_INPUT_STYLE_VH */
+ }
+ mouse_link = i;
+ } else
+ mouse_link = -1;
+ mouse_err = 0;
+ break;
+ } else if (cur_err < mouse_err) {
+ mouse_err = cur_err;
+ mouse_link = i;
+ }
+ }
+ }
+ /*
+ * If a link was hit, we must look for a key which will activate
+ * LYK_ACTIVATE We expect to find LYK_ACTIVATE (it's usually mapped to
+ * the Enter key).
+ */
+ if (mouse_link >= 0) {
+ if (mouse_err == 0) {
+ if (c == -1)
+ c = LAC_TO_LKC0(LYK_ACTIVATE);
+ } else if (mouse_err >= 0)
+ c = LAC_TO_LKC0(LYK_CHANGE_LINK);
+ } else {
+ if (2 * y > LYlines) { /* Bottom Half of the screen */
+ if (4 * y < 3 * LYlines) {
+ c = LAC_TO_LKC0(LYK_DOWN_TWO); /* Third quarter */
+ } else
+ c = LAC_TO_LKC0(LYK_DOWN_HALF); /* Fourth quarter */
+ } else { /* Upper Half of the screen */
+ if (4 * y < LYlines) {
+ c = LAC_TO_LKC0(LYK_UP_HALF); /* First quarter */
+ } else
+ c = LAC_TO_LKC0(LYK_UP_TWO); /* Second quarter */
+ }
+ }
+ }
+ return c;
+}
+#endif /* USE_MOUSE */
+
+/*
+ * LYstrncpy() ensures that the copied strings end with a nul byte.
+ * The nul is written to the n+1 position of the target.
+ */
+char *LYstrncpy(char *target,
+ const char *source,
+ int n)
+{
+ char *val = target;
+ int len;
+
+ if (source == 0)
+ source = "";
+ len = (int) strlen(source);
+
+ if (n > 0) {
+ if (n > len)
+ n = len;
+ (void) memcpy(target, source, (size_t) n);
+ } else {
+ n = 0;
+ }
+ target[n] = '\0';
+ return val;
+}
+
+#define IS_NEW_GLYPH(ch) (utf_flag && (UCH(ch)&0xc0) != 0x80)
+#define IS_UTF_EXTRA(ch) (utf_flag && (UCH(ch)&0xc0) == 0x80)
+
+/*
+ * LYmbcsstrncpy() terminates strings with a null byte. It takes account of
+ * multibyte characters. The source string is copied until either end of string
+ * or max number of either bytes or glyphs (mbcs sequences) (CJK or UTF8). The
+ * utf_flag argument should be TRUE for UTF8. - KW & FM
+ */
+char *LYmbcsstrncpy(char *target,
+ const char *source,
+ int n_bytes,
+ int n_glyphs,
+ int utf_flag)
+{
+ char *val = target;
+ int i_bytes = 0, i_glyphs = 0;
+
+ if (n_bytes < 0)
+ n_bytes = 0;
+ if (n_glyphs < 0)
+ n_glyphs = 0;
+
+ for (; *source != '\0' && i_bytes < n_bytes; i_bytes++) {
+ if (IS_NEW_GLYPH(*source)) {
+ if (i_glyphs++ >= n_glyphs) {
+ *target = '\0';
+ return val;
+ }
+ }
+ *(target++) = *(source++);
+ }
+ *target = '\0';
+
+ return val;
+}
+
+/*
+ * LYmbcs_skip_glyphs() skips a given number of character positions in a string
+ * and returns the resulting pointer. It takes account of UTF-8 encoded
+ * characters. - KW
+ */
+const char *LYmbcs_skip_glyphs(const char *data,
+ int n_glyphs,
+ int utf_flag)
+{
+ int i_glyphs = 0;
+
+ if (n_glyphs < 0)
+ n_glyphs = 0;
+
+ if (non_empty(data)) {
+ if (!utf_flag) {
+ while (n_glyphs-- > 0) {
+ if (!*++data)
+ break;
+ }
+ } else {
+ while (*data) {
+ if (IS_NEW_GLYPH(*data)) {
+ if (i_glyphs++ >= n_glyphs) {
+ break;
+ }
+ }
+ data++;
+ }
+ }
+ }
+ return data;
+}
+
+/*
+ * LYmbcs_skip_cells() skips a given number of display positions in a string
+ * and returns the resulting pointer. It takes account of UTF-8 encoded
+ * characters. - TD
+ */
+const char *LYmbcs_skip_cells(const char *data,
+ int n_cells,
+ int utf_flag)
+{
+ const char *result;
+ int actual;
+ int target = n_cells;
+
+ do {
+ result = LYmbcs_skip_glyphs(data, target--, utf_flag);
+ actual = LYstrExtent2(data, (int) (result - data));
+ } while ((actual > 0) && (actual > n_cells));
+ return result;
+}
+
+/*
+ * LYmbcsstrlen() returns the printable length of a string that might contain
+ * IsSpecial or multibyte (CJK or UTF8) characters. - FM
+ *
+ * Counts glyph cells if count_gcells is set. (Full-width characters in CJK
+ * mode count as two.) Counts character glyphs if count_gcells is unset.
+ * (Full- width characters in CJK mode count as one.) - kw
+ */
+int LYmbcsstrlen(const char *str,
+ int utf_flag,
+ int count_gcells)
+{
+ int i, j, len = 0;
+
+ if (non_empty(str)) {
+#ifdef WIDEC_CURSES
+ if (count_gcells) {
+ len = LYstrCells(str);
+ } else
+#endif
+ {
+ for (i = 0; str[i] != '\0'; i++) {
+ if (!IsSpecialAttrChar(str[i])) {
+ len++;
+ if (IS_NEW_GLYPH(str[i])) {
+ j = 0;
+ while (IsNormalChar(str[(i + 1)]) &&
+ j < 5 &&
+ IS_UTF_EXTRA(str[(i + 1)])) {
+ i++;
+ j++;
+ }
+ } else if (!utf_flag && IS_CJK_TTY && !count_gcells &&
+ is8bits(str[i]) &&
+ IsNormalChar(str[(i + 1)])) {
+ i++;
+ }
+ }
+ }
+ }
+ }
+ return (len);
+}
+
+#undef GetChar
+#undef USE_CURSES_RESIZE
+
+#ifdef USE_SLANG
+#if defined(VMS)
+#define GetChar() ttgetc()
+#elif defined(__DJGPP__)
+#define GetChar() getxkey() /* HTDos.c */
+#elif defined(__CYGWIN__)
+#define GetChar SLkp_getkey
+#else
+#define GetChar (int)SLang_getkey
+#endif
+#else /* curses */
+#if defined(DJGPP)
+#define GetChar() (djgpp_idle_loop(), wgetch(LYtopwindow()))
+#elif defined(NCURSES_VERSION) && defined(__BEOS__)
+#define GetChar() myGetCharNodelay()
+#endif
+#endif
+
+#ifdef USE_CURSES_RESIZE
+static int myGetCharResize(void)
+{
+ int c;
+ WINDOW *win = LYtopwindow();
+
+ do {
+ wtimeout(win, 20);
+ c = wgetch(win);
+ wtimeout(win, -1);
+ CheckScreenSize();
+ } while (c <= 0);
+ return c;
+}
+#define GetChar() myGetCharResize()
+#endif
+
+#ifdef USE_CURSES_NODELAY
+/* PDCurses - until version 2.7 in 2005 - defined ERR as 0, unlike other
+ * versions of curses. Generally both EOF and ERR are defined as -1's.
+ * However, there is a special case (see HTCheckForInterrupt()) to handle a
+ * case where no select() function is used in the win32 environment.
+ *
+ * HTCheckForInterrupt() uses nodelay() in this special case to check for
+ * pending input. That normally returns ERR. But LYgetch_for() checks the
+ * return value of this function for EOF (to handle some antique runtime
+ * libraries which did not set the state for feof/ferror). Returning a zero
+ * (0) is safer since normally that is not mapped to any commands, and will be
+ * ignored by lynx.
+ */
+static int myGetCharNodelay(void)
+{
+ int c = wgetch(LYwin);
+
+ if (c == -1)
+ c = 0;
+ CheckScreenSize();
+
+ return c;
+}
+#else
+#define myGetCharNodelay() wgetch(LYwin)
+#endif
+
+#if !defined(GetChar) && defined(PDCURSES) && defined(PDC_BUILD) && PDC_BUILD >= 2401
+/* PDCurses sends back key-modifiers that we don't use, but would waste time
+ * upon, e.g., repainting the status line
+ */
+static int myGetChar(void)
+{
+ int c;
+ BOOL done = FALSE;
+
+ do {
+ switch (c = myGetCharNodelay()) {
+ case KEY_SHIFT_L:
+ case KEY_SHIFT_R:
+ case KEY_CONTROL_L:
+ case KEY_CONTROL_R:
+ case KEY_ALT_L:
+ case KEY_ALT_R:
+ case KEY_RESIZE:
+ break;
+ default:
+ done = TRUE;
+ break;
+ }
+ } while (!done);
+
+ return c;
+}
+#define GetChar() myGetChar()
+#endif
+
+#if !defined(GetChar) && defined(VMS)
+#define GetChar() ttgetc()
+#endif
+
+#if !defined(GetChar)
+#ifdef HAVE_KEYPAD
+#define GetChar() getch()
+#else
+#ifndef USE_GETCHAR
+#define USE_GETCHAR
+#endif /* !USE_GETCHAR */
+#define GetChar() getchar() /* used to be "getc(stdin)" and "getch()" */
+#endif /* HAVE_KEYPAD */
+#endif /* !defined(GetChar) */
+
+#if defined(USE_SLANG) && defined(USE_MOUSE)
+static int sl_parse_mouse_event(int *x, int *y, int *button)
+{
+ /* "ESC [ M" has already been processed. Three more characters are
+ * expected: BUTTON X Y
+ */
+ *button = (int) SLang_getkey();
+ switch (*button) {
+ case 040: /* left button */
+ case 041: /* middle button */
+ case 042: /* right button */
+ *button -= 040;
+ break;
+
+ default: /* Hmmm.... */
+ SLang_flush_input();
+ return -1;
+ }
+
+ *x = (int) SLang_getkey();
+ if (*x == CH_ESC) /* Undo 7-bit replace for large x - kw */
+ *x = (int) SLang_getkey() + 64 - 33;
+ else
+ *x -= 33;
+ *y = (int) SLang_getkey();
+ if (*y == CH_ESC) /* Undo 7-bit replace for large y - kw */
+ *y = (int) SLang_getkey() + 64 - 33;
+ else
+ *y -= 33;
+ return 0;
+}
+
+static int sl_read_mouse_event(int code)
+{
+ int mouse_x, mouse_y, button;
+
+ mouse_link = -1;
+ if (-1 != sl_parse_mouse_event(&mouse_x, &mouse_y, &button)) {
+ if (button == 0) /* left */
+ return set_clicked_link(mouse_x, mouse_y, FOR_PANEL, 1);
+
+ if (button == 1) /* middle */
+ return LYReverseKeymap(LYK_VIEW_BOOKMARK);
+
+ if (button == 2) /* right */
+ {
+ /* Right button: go back to prev document.
+ * The problem is that we need to determine
+ * what to return to achieve this.
+ */
+ return LYReverseKeymap(LYK_PREV_DOC);
+ }
+ }
+ if (code == FOR_INPUT || code == FOR_PROMPT)
+ return DO_NOTHING;
+ else
+ return -1;
+}
+#endif /* USE_SLANG and USE_MOUSE */
+
+static BOOLEAN csi_is_csi = TRUE;
+void ena_csi(int flag)
+{
+ csi_is_csi = (BOOLEAN) flag;
+}
+
+#if defined(USE_KEYMAPS)
+
+#ifdef USE_SLANG
+#define define_key(string, code) \
+ SLkm_define_keysym ((SLFUTURE_CONST char*)(string), \
+ (unsigned) code, \
+ Keymap_List)
+#if SLANG_VERSION < 20000
+#define expand_substring(target, first, last, final) \
+ (SLexpand_escaped_string(target, \
+ DeConst(first), \
+ DeConst(last), 1)
+static int SLang_get_error(void)
+{
+ return SLang_Error;
+}
+#else
+int LY_Slang_UTF8_Mode = 0;
+
+#define expand_substring(target, first, last, final) \
+ (SLexpand_escaped_string(target, \
+ DeConst(first), \
+ DeConst(last), \
+ LY_Slang_UTF8_Mode), 1)
+#endif
+
+static SLKeyMap_List_Type *Keymap_List;
+
+/* This value should be larger than anything in LYStrings.h */
+#define MOUSE_KEYSYM 0x0400
+#endif
+
+/*
+ * For ncurses, we use the predefined keysyms, since that lets us also reuse
+ * the CSI logic and other special cases for VMS, NCSA telnet, etc.
+ */
+#ifdef USE_SLANG
+# ifdef VMS
+# define EXTERN_KEY(string,string1,lynx,curses) {string,lynx}
+# else
+# define EXTERN_KEY(string,string1,lynx,curses) {string,lynx},{string1,lynx}
+# endif
+# define INTERN_KEY(string,lynx,curses) {string,lynx,lynx}
+#else
+# define INTERN_KEY(string,lynx,curses) {string,curses,lynx}
+# define EXTERN_KEY(string,string1,lynx,curses) {string,curses,lynx}
+#endif
+
+typedef struct {
+ const char *string;
+ int value;
+ LYExtraKeys internal;
+} Keysym_String_List;
+/* *INDENT-OFF* */
+static Keysym_String_List Keysym_Strings [] =
+{
+ INTERN_KEY( "UPARROW", UPARROW_KEY, KEY_UP ),
+ INTERN_KEY( "DNARROW", DNARROW_KEY, KEY_DOWN ),
+ INTERN_KEY( "RTARROW", RTARROW_KEY, KEY_RIGHT ),
+ INTERN_KEY( "LTARROW", LTARROW_KEY, KEY_LEFT ),
+ INTERN_KEY( "PGDOWN", PGDOWN_KEY, KEY_NPAGE ),
+ INTERN_KEY( "PGUP", PGUP_KEY, KEY_PPAGE ),
+ INTERN_KEY( "HOME", HOME_KEY, KEY_HOME ),
+ INTERN_KEY( "END", END_KEY, KEY_END ),
+ INTERN_KEY( "F1", F1_KEY, KEY_F(1) ),
+ INTERN_KEY( "F2", F2_KEY, KEY_F(2) ),
+ INTERN_KEY( "F3", F3_KEY, KEY_F(3) ),
+ INTERN_KEY( "F4", F4_KEY, KEY_F(4) ),
+ INTERN_KEY( "F5", F5_KEY, KEY_F(5) ),
+ INTERN_KEY( "F6", F6_KEY, KEY_F(7) ),
+ INTERN_KEY( "F7", F7_KEY, KEY_F(7) ),
+ INTERN_KEY( "F8", F8_KEY, KEY_F(8) ),
+ INTERN_KEY( "F9", F9_KEY, KEY_F(9) ),
+ INTERN_KEY( "F10", F10_KEY, KEY_F(10) ),
+ INTERN_KEY( "F11", F11_KEY, KEY_F(11) ),
+ INTERN_KEY( "F12", F12_KEY, KEY_F(12) ),
+ INTERN_KEY( "DO_KEY", DO_KEY, KEY_F(16) ),
+ INTERN_KEY( "FIND_KEY", FIND_KEY, KEY_FIND ),
+ INTERN_KEY( "SELECT_KEY", SELECT_KEY, KEY_SELECT ),
+ INTERN_KEY( "INSERT_KEY", INSERT_KEY, KEY_IC ),
+ INTERN_KEY( "REMOVE_KEY", REMOVE_KEY, KEY_DC ),
+ INTERN_KEY( "DO_NOTHING", DO_NOTHING, DO_NOTHING|LKC_ISLKC ),
+ INTERN_KEY( "BACKTAB_KEY", BACKTAB_KEY, BACKTAB_KEY ),
+ INTERN_KEY( NULL, UNKNOWN_KEY, ERR )
+};
+/* *INDENT-ON* */
+
+#ifdef NCURSES_VERSION
+/*
+ * Ncurses stores the termcap/terminfo names in arrays sorted to match the
+ * array of strings in the TERMTYPE struct.
+ */
+static int lookup_tiname(char *name, NCURSES_CONST char *const *names)
+{
+ int code;
+
+ for (code = 0; names[code] != 0; code++)
+ if (!strcmp(names[code], name))
+ return code;
+ return -1;
+}
+
+static const char *expand_tiname(const char *first, size_t len, char **result, char *final)
+{
+ char name[BUFSIZ];
+ int code;
+ TERMTYPE *tp = (TERMTYPE *) (cur_term);
+
+ LYStrNCpy(name, first, len);
+ **result = '\0';
+ if ((code = lookup_tiname(name, strnames)) >= 0
+ || (code = lookup_tiname(name, strfnames)) >= 0) {
+ if (tp->Strings[code] != 0) {
+ LYStrNCpy(*result, tp->Strings[code], (final - *result));
+ (*result) += strlen(*result);
+ }
+ }
+ return first + len;
+}
+
+static const char *expand_tichar(const char *first, char **result, char *final)
+{
+ int ch;
+ int limit = 0;
+ int radix = 0;
+ int value = 0;
+ const char *name = 0;
+
+ switch (ch = *first++) {
+ case 'E':
+ case 'e':
+ value = 27;
+ break;
+ case 'a':
+ name = "bel";
+ break;
+ case 'b':
+ value = '\b';
+ break;
+ case 'f':
+ value = '\f';
+ break;
+ case 'n':
+ value = '\n';
+ break;
+ case 'r':
+ value = '\r';
+ break;
+ case 't':
+ value = '\t';
+ break;
+ case 'v':
+ value = '\v';
+ break;
+ case 'd':
+ radix = 10;
+ limit = 3;
+ break;
+ case 'x':
+ radix = 16;
+ limit = 2;
+ break;
+ default:
+ if (isdigit(ch)) {
+ radix = 8;
+ limit = 3;
+ first--;
+ } else {
+ value = *first;
+ }
+ break;
+ }
+
+ if (radix != 0) {
+ char *last = 0;
+ char tmp[80];
+
+ LYStrNCpy(tmp, first, limit);
+ value = (int) strtol(tmp, &last, radix);
+ if (last != 0 && last != tmp)
+ first += (last - tmp);
+ }
+
+ if (name != 0) {
+ (void) expand_tiname(name, strlen(name), result, final);
+ } else {
+ **result = (char) value;
+ (*result) += 1;
+ }
+
+ return first;
+}
+
+static BOOLEAN expand_substring(char *target,
+ const char *first,
+ const char *last,
+ char *final)
+{
+ int ch;
+
+ while (first < last) {
+ switch (ch = *first++) {
+ case ESCAPE:
+ first = expand_tichar(first, &target, final);
+ break;
+ case '^':
+ ch = *first++;
+ if (ch == LPAREN) {
+ const char *s = StrChr(first, RPAREN);
+ char *was = target;
+
+ if (s == 0)
+ s = first + strlen(first);
+ first = expand_tiname(first, (size_t) (s - first), &target, final);
+ if (target == was)
+ return FALSE;
+ if (*first)
+ first++;
+ } else if (ch == '?') { /* ASCII delete? */
+ *target++ = 127;
+ } else if ((ch & 0x3f) < 0x20) { /* ASCII control char? */
+ *target++ = (char) (ch & 0x1f);
+ } else {
+ *target++ = '^';
+ first--; /* not legal... */
+ }
+ break;
+ case 0: /* convert nulls for terminfo */
+ ch = 0200;
+ /* FALLTHRU */
+ default:
+ *target++ = (char) ch;
+ break;
+ }
+ }
+ *target = '\0';
+ return TRUE;
+}
+#endif
+
+static void unescaped_char(const char *parse, int *keysym)
+{
+ size_t len = strlen(parse);
+ char buf[BUFSIZ];
+
+ if (len >= 3) {
+ (void) expand_substring(buf,
+ parse + 1,
+ parse + len - 1,
+ buf + sizeof(buf) - 1);
+ if (strlen(buf) == 1)
+ *keysym = *buf;
+ }
+}
+
+static BOOLEAN unescape_string(char *source, char *target, char *final)
+{
+ BOOLEAN ok = FALSE;
+
+ if (*source == SQUOTE) {
+ int keysym = -1;
+
+ unescaped_char(source, &keysym);
+ if (keysym >= 0) {
+ target[0] = (char) keysym;
+ target[1] = '\0';
+ ok = TRUE;
+ }
+ } else if (*source == DQUOTE) {
+ if (expand_substring(target, source + 1, source + strlen(source) - 1, final))
+ ok = TRUE;
+ (void) final;
+ }
+ return ok;
+}
+
+static Keysym_String_List *lookupKeysymByName(const char *name)
+{
+ Keysym_String_List *k;
+ Keysym_String_List *result = 0;
+
+ k = Keysym_Strings;
+ while (k->string != NULL) {
+ if (0 == strcasecomp(k->string, name)) {
+ result = k;
+ break;
+ }
+ k++;
+ }
+ return result;
+}
+
+int map_string_to_keysym(const char *str, int *keysym, int internal)
+{
+ int modifier = 0;
+
+ *keysym = -1;
+
+ if (strncasecomp(str, "LAC:", 4) == 0) {
+ char *other = StrChr(str + 4, ':');
+
+ if (other) {
+ int othersym = lecname_to_lec(other + 1);
+ char buf[BUFSIZ];
+
+ if (othersym >= 0 && other - str - 4 < BUFSIZ) {
+ LYStrNCpy(buf, str + 4, (other - str - 4));
+ *keysym = lacname_to_lac(buf);
+ if (*keysym >= 0) {
+ *keysym = LACLEC_TO_LKC0(*keysym, othersym);
+ return (*keysym);
+ }
+ }
+ }
+ *keysym = lacname_to_lac(str + 4);
+ if (*keysym >= 0) {
+ *keysym = LAC_TO_LKC0(*keysym);
+ return (*keysym);
+ }
+ } else if (strncasecomp(str, "Meta-", 5) == 0) {
+ str += 5;
+ modifier = LKC_MOD2;
+ if (*str) {
+ size_t len = strlen(str);
+
+ if (len == 1) {
+ return (*keysym = (UCH(str[0])) | modifier);
+ } else if (len == 2 && str[0] == '^' &&
+ (isalpha(UCH(str[1])) ||
+ (TOASCII(str[1]) >= '@' && TOASCII(str[1]) <= '_'))) {
+ return (*keysym = FROMASCII(UCH(str[1] & 0x1f)) | modifier);
+ } else if (len == 2 && str[0] == '^' &&
+ str[1] == '?') {
+ return (*keysym = CH_DEL | modifier);
+ }
+ if (*str == '^' || *str == '\\') {
+ char buf[BUFSIZ];
+
+ (void) expand_substring(buf,
+ str,
+ str + HTMIN(len, 28),
+ buf + sizeof(buf) - 1);
+ if (strlen(buf) <= 1)
+ return (*keysym = (UCH(buf[0])) | modifier);
+ }
+ }
+ } else if (*str == SQUOTE) {
+ unescaped_char(str, keysym);
+ } else if (isdigit(UCH(*str))) {
+ char *tmp;
+ long value = strtol(str, &tmp, 0);
+
+ if (!isalnum(UCH(*tmp))) {
+ *keysym = (int) value;
+#ifndef USE_SLANG
+ if (*keysym > 255)
+ *keysym |= LKC_ISLKC; /* caller should remove this flag - kw */
+#endif
+ }
+ } else {
+ Keysym_String_List *k = lookupKeysymByName(str);
+
+ if (k != 0) {
+ *keysym = (internal
+ ? k->internal
+ : k->value);
+ }
+ }
+
+ if (*keysym >= 0)
+ *keysym |= modifier;
+ return (*keysym);
+}
+
+LYExtraKeys LYnameToExtraKeys(const char *name)
+{
+ Keysym_String_List *k = lookupKeysymByName(name);
+ LYExtraKeys result = UNKNOWN_KEY;
+
+ if (k != 0)
+ result = k->internal;
+ return result;
+}
+
+const char *LYextraKeysToName(LYExtraKeys code)
+{
+ Keysym_String_List *k;
+ const char *result = 0;
+
+ k = Keysym_Strings;
+ while (k->string != NULL) {
+ if (k->internal == code) {
+ result = k->string;
+ break;
+ }
+ k++;
+ }
+ return result;
+}
+
+/*
+ * Starting at a nonblank character, skip over a token, counting quoted and
+ * escaped characters.
+ */
+static char *skip_keysym(char *parse)
+{
+ int quoted = 0;
+ int escaped = 0;
+
+ while (*parse) {
+ if (escaped) {
+ escaped = 0;
+ } else if (quoted) {
+ if (*parse == ESCAPE) {
+ escaped = 1;
+ } else if (*parse == quoted) {
+ quoted = 0;
+ }
+ } else if (*parse == ESCAPE) {
+ escaped = 1;
+ } else if (*parse == DQUOTE || *parse == SQUOTE) {
+ quoted = *parse;
+ } else if (isspace(UCH(*parse))) {
+ break;
+ }
+ parse++;
+ }
+ return (quoted || escaped) ? 0 : parse;
+}
+
+/*
+ * The first token is the string to define, the second is the name (of the
+ * keysym) to define it to.
+ */
+#define MY_TRACE(p) CTRACE2(TRACE_CFG, p)
+
+static int setkey_cmd(char *parse)
+{
+ char *s, *t;
+ int keysym;
+ char buf[BUFSIZ];
+
+ MY_TRACE((tfp, "KEYMAP(PA): in=%s", parse)); /* \n-terminated */
+ if ((s = skip_keysym(parse)) != 0) {
+ if (isspace(UCH(*s))) {
+ *s++ = '\0';
+ s = LYSkipBlanks(s);
+ if ((t = skip_keysym(s)) == 0) {
+ MY_TRACE((tfp, "KEYMAP(SKIP) no key expansion found\n"));
+ return -1;
+ }
+ if (t != s)
+ *t = '\0';
+ if (map_string_to_keysym(s, &keysym, FALSE) >= 0) {
+ if (!unescape_string(parse, buf, buf + sizeof(buf) - 1)) {
+ MY_TRACE((tfp, "KEYMAP(SKIP) could unescape key\n"));
+ return 0; /* Trace the failure and continue. */
+ }
+ if (LYTraceLogFP == 0) {
+ MY_TRACE((tfp, "KEYMAP(DEF) keysym=%#x\n",
+ (unsigned) keysym));
+ } else {
+ MY_TRACE((tfp, "KEYMAP(DEF) keysym=%#x, seq='%s'\n",
+ (unsigned) keysym, buf));
+ }
+ return define_key(buf, keysym);
+ } else {
+ MY_TRACE((tfp, "KEYMAP(SKIP) could not map to keysym\n"));
+ }
+ } else {
+ MY_TRACE((tfp, "KEYMAP(SKIP) junk after key description: '%s'\n", s));
+ }
+ } else {
+ MY_TRACE((tfp, "KEYMAP(SKIP) no key description\n"));
+ }
+ return -1;
+}
+#undef MY_TRACE
+
+static int unsetkey_cmd(char *parse)
+{
+ char *s = skip_keysym(parse);
+
+ if (s != parse) {
+ *s = '\0';
+#ifdef NCURSES_VERSION
+ /*
+ * This won't work with Slang. Remove the definition for the given
+ * keysym.
+ */
+ {
+ int keysym;
+
+ if (map_string_to_keysym(parse, &keysym, FALSE) >= 0)
+ define_key((char *) 0, keysym);
+ }
+#endif
+#ifdef USE_SLANG
+ /* Slang implements this, for undefining the string which is associated
+ * with a keysym (the reverse of what we normally want, but may
+ * occasionally find useful).
+ */
+ SLang_undefine_key(parse, Keymap_List);
+ if (SLang_get_error())
+ return -1;
+#endif
+ }
+ return 0;
+}
+
+#ifdef FNAMES_8_3
+#define FNAME_LYNX_KEYMAPS "_lynxkey.map"
+#else
+#define FNAME_LYNX_KEYMAPS ".lynx-keymaps"
+#endif /* FNAMES_8_3 */
+
+static int read_keymap_file(void)
+{
+ /* *INDENT-OFF* */
+ static struct {
+ const char *name;
+ int (*func) (char *s);
+ } table[] = {
+ { "setkey", setkey_cmd },
+ { "unsetkey", unsetkey_cmd },
+ };
+ /* *INDENT-ON* */
+
+ char *line = NULL;
+ FILE *fp;
+ char file[LY_MAXPATH];
+ int linenum;
+ size_t n;
+
+ LYAddPathToHome(file, sizeof(file), FNAME_LYNX_KEYMAPS);
+
+ if ((fp = fopen(file, "r")) == 0)
+ return 0;
+
+ CTRACE((tfp, "read_keymap_file %s\n", file));
+ linenum = 0;
+ while (LYSafeGets(&line, fp) != 0) {
+ char *s = LYSkipBlanks(line);
+
+ linenum++;
+
+ if ((*s == 0) || (*s == '#'))
+ continue;
+
+ for (n = 0; n < TABLESIZE(table); n++) {
+ size_t len = strlen(table[n].name);
+
+ if (strlen(s) > len && !StrNCmp(s, table[n].name, len)
+ && (*(table[n].func)) (LYSkipBlanks(s + len)) < 0)
+ fprintf(stderr, FAILED_READING_KEYMAP, linenum, file);
+ }
+ }
+ FREE(line);
+ LYCloseInput(fp);
+ return 0;
+}
+
+static void setup_vtXXX_keymap(void)
+{
+ /* *INDENT-OFF* */
+ static Keysym_String_List table[] = {
+ INTERN_KEY( "\033[A", UPARROW_KEY, KEY_UP ),
+ INTERN_KEY( "\033OA", UPARROW_KEY, KEY_UP ),
+ INTERN_KEY( "\033[B", DNARROW_KEY, KEY_DOWN ),
+ INTERN_KEY( "\033OB", DNARROW_KEY, KEY_DOWN ),
+ INTERN_KEY( "\033[C", RTARROW_KEY, KEY_RIGHT ),
+ INTERN_KEY( "\033OC", RTARROW_KEY, KEY_RIGHT ),
+ INTERN_KEY( "\033[D", LTARROW_KEY, KEY_LEFT ),
+ INTERN_KEY( "\033OD", LTARROW_KEY, KEY_LEFT ),
+ INTERN_KEY( "\033[1~", FIND_KEY, KEY_FIND ),
+ INTERN_KEY( "\033[2~", INSERT_KEY, KEY_IC ),
+ INTERN_KEY( "\033[3~", REMOVE_KEY, KEY_DC ),
+ INTERN_KEY( "\033[4~", SELECT_KEY, KEY_SELECT ),
+ INTERN_KEY( "\033[5~", PGUP_KEY, KEY_PPAGE ),
+ INTERN_KEY( "\033[6~", PGDOWN_KEY, KEY_NPAGE ),
+ INTERN_KEY( "\033[7~", HOME_KEY, KEY_HOME),
+ INTERN_KEY( "\033[8~", END_KEY, KEY_END ),
+ INTERN_KEY( "\033[11~", F1_KEY, KEY_F(1) ),
+ INTERN_KEY( "\033[28~", F1_KEY, KEY_F(1) ),
+ INTERN_KEY( "\033OP", F1_KEY, KEY_F(1) ),
+ INTERN_KEY( "\033[OP", F1_KEY, KEY_F(1) ),
+ INTERN_KEY( "\033[29~", DO_KEY, KEY_F(16) ),
+#if defined(USE_SLANG)
+#if defined(__WIN32__) || defined(__MINGW32__)
+ INTERN_KEY( "\xE0H", UPARROW_KEY, KEY_UP ),
+ INTERN_KEY( "\xE0P", DNARROW_KEY, KEY_DOWN ),
+ INTERN_KEY( "\xE0M", RTARROW_KEY, KEY_RIGHT ),
+ INTERN_KEY( "\xE0K", LTARROW_KEY, KEY_LEFT ),
+ INTERN_KEY( "\xE0R", INSERT_KEY, KEY_IC ),
+ INTERN_KEY( "\xE0S", REMOVE_KEY, KEY_DC ),
+ INTERN_KEY( "\xE0I", PGUP_KEY, KEY_PPAGE ),
+ INTERN_KEY( "\xE0Q", PGDOWN_KEY, KEY_NPAGE ),
+ INTERN_KEY( "\xE0G", HOME_KEY, KEY_HOME),
+ INTERN_KEY( "\xE0O", END_KEY, KEY_END ),
+#endif
+#if !defined(VMS)
+ INTERN_KEY( "^(ku)", UPARROW_KEY, KEY_UP ),
+ INTERN_KEY( "^(kd)", DNARROW_KEY, KEY_DOWN ),
+ INTERN_KEY( "^(kr)", RTARROW_KEY, KEY_RIGHT ),
+ INTERN_KEY( "^(kl)", LTARROW_KEY, KEY_LEFT ),
+ INTERN_KEY( "^(@0)", FIND_KEY, KEY_FIND ),
+ INTERN_KEY( "^(kI)", INSERT_KEY, KEY_IC ),
+ INTERN_KEY( "^(kD)", REMOVE_KEY, KEY_DC ),
+ INTERN_KEY( "^(*6)", SELECT_KEY, KEY_SELECT ),
+ INTERN_KEY( "^(kP)", PGUP_KEY, KEY_PPAGE ),
+ INTERN_KEY( "^(kN)", PGDOWN_KEY, KEY_NPAGE ),
+ INTERN_KEY( "^(@7)", END_KEY, KEY_END ),
+ INTERN_KEY( "^(kh)", HOME_KEY, KEY_HOME),
+ INTERN_KEY( "^(k1)", F1_KEY, KEY_F(1) ),
+ INTERN_KEY( "^(k2)", F2_KEY, KEY_F(2) ),
+ INTERN_KEY( "^(k3)", F3_KEY, KEY_F(3) ),
+ INTERN_KEY( "^(k4)", F4_KEY, KEY_F(4) ),
+ INTERN_KEY( "^(k5)", F5_KEY, KEY_F(5) ),
+ INTERN_KEY( "^(k6)", F6_KEY, KEY_F(6) ),
+ INTERN_KEY( "^(k7)", F7_KEY, KEY_F(7) ),
+ INTERN_KEY( "^(k8)", F8_KEY, KEY_F(8) ),
+ INTERN_KEY( "^(k9)", F9_KEY, KEY_F(9) ),
+ INTERN_KEY( "^(k;)", F10_KEY, KEY_F(10) ),
+ INTERN_KEY( "^(F1)", F11_KEY, KEY_F(11) ),
+ INTERN_KEY( "^(F2)", F12_KEY, KEY_F(12) ),
+ INTERN_KEY( "^(F6)", DO_KEY, KEY_F(16) ),
+#endif /* !VMS */
+#endif /* SLANG */
+ };
+ /* *INDENT-ON* */
+
+ size_t n;
+
+ for (n = 0; n < TABLESIZE(table); n++)
+ define_key(table[n].string, table[n].value);
+}
+
+int lynx_initialize_keymaps(void)
+{
+#ifdef USE_SLANG
+ int i;
+ char keybuf[2];
+
+ /* The escape sequences may contain embedded termcap strings. Make
+ * sure the library is initialized for that.
+ */
+ SLtt_get_terminfo();
+
+ if (NULL == (Keymap_List = SLang_create_keymap("Lynx", NULL)))
+ return -1;
+
+ keybuf[1] = 0;
+ for (i = 1; i < 256; i++) {
+ keybuf[0] = (char) i;
+ define_key(keybuf, i);
+ }
+
+ setup_vtXXX_keymap();
+ define_key("\033[M", MOUSE_KEYSYM);
+
+ if (SLang_get_error())
+ SLang_exit_error("Unable to initialize keymaps");
+#else
+ setup_vtXXX_keymap();
+#endif
+ return read_keymap_file();
+}
+
+#endif /* USE_KEYMAPS */
+
+#if defined(USE_MOUSE) && (defined(NCURSES))
+static int LYmouse_menu(int x, int y, int atlink, int code)
+{
+#define ENT_ONLY_DOC 1
+#define ENT_ONLY_LINK 2
+ /* *INDENT-OFF* */
+ static const struct {
+ const char *txt;
+ int action;
+ unsigned int flag;
+ } possible_entries[] = {
+ {"Quit", LYK_ABORT, ENT_ONLY_DOC},
+ {"Home page", LYK_MAIN_MENU, ENT_ONLY_DOC},
+ {"Previous document", LYK_PREV_DOC, ENT_ONLY_DOC},
+ {"Beginning of document", LYK_HOME, ENT_ONLY_DOC},
+ {"Page up", LYK_PREV_PAGE, ENT_ONLY_DOC},
+ {"Half page up", LYK_UP_HALF, ENT_ONLY_DOC},
+ {"Two lines up", LYK_UP_TWO, ENT_ONLY_DOC},
+ {"History", LYK_HISTORY, ENT_ONLY_DOC},
+ {"Help", LYK_HELP, 0},
+ {"Do nothing (refresh)", LYK_REFRESH, 0},
+ {"Load again", LYK_RELOAD, ENT_ONLY_DOC},
+ {"Edit Doc URL and load", LYK_ECGOTO, ENT_ONLY_DOC},
+ {"Edit Link URL and load", LYK_ELGOTO, ENT_ONLY_LINK},
+ {"Show info", LYK_INFO, 0},
+ {"Search", LYK_WHEREIS, ENT_ONLY_DOC},
+ {"Print", LYK_PRINT, ENT_ONLY_DOC},
+ {"Two lines down", LYK_DOWN_TWO, ENT_ONLY_DOC},
+ {"Half page down", LYK_DOWN_HALF, ENT_ONLY_DOC},
+ {"Page down", LYK_NEXT_PAGE, ENT_ONLY_DOC},
+ {"End of document", LYK_END, ENT_ONLY_DOC},
+ {"Bookmarks", LYK_VIEW_BOOKMARK, ENT_ONLY_DOC},
+ {"Cookie jar", LYK_COOKIE_JAR, ENT_ONLY_DOC},
+#ifdef USE_CACHEJAR
+ {"Cache jar", LYK_CACHE_JAR, ENT_ONLY_DOC},
+#endif
+ {"Search index", LYK_INDEX_SEARCH, ENT_ONLY_DOC},
+ {"Set Options", LYK_OPTIONS, ENT_ONLY_DOC},
+ {"Activate this link", LYK_MOUSE_SUBMIT, ENT_ONLY_LINK},
+ {"Download", LYK_DOWNLOAD, ENT_ONLY_LINK}
+ };
+ /* *INDENT-ON* */
+
+#define TOTAL_MENUENTRIES TABLESIZE(possible_entries)
+ const char *choices[TOTAL_MENUENTRIES + 1];
+ int actions[TOTAL_MENUENTRIES];
+
+ int c, c1, retlac;
+ unsigned filter_out = (unsigned) (atlink ? ENT_ONLY_DOC : ENT_ONLY_LINK);
+
+ c = c1 = 0;
+ while (c < (int) TOTAL_MENUENTRIES) {
+ if (!(possible_entries[c].flag & filter_out)) {
+ choices[c1] = possible_entries[c].txt;
+ actions[c1++] = possible_entries[c].action;
+ }
+ c++;
+ }
+ choices[c1] = NULL;
+
+ /* Somehow the mouse is over the number instead of being over the
+ name, so we decrease x. */
+ c = LYChoosePopup((atlink ? 2 : 10) - 1, y, (x > 5 ? x - 5 : 1),
+ choices, c1, 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) {
+ retlac = LYK_DO_NOTHING;
+ term_options = FALSE;
+ } else {
+ retlac = actions[c];
+ }
+
+ if (code == FOR_INPUT && mouse_link == -1) {
+ switch (retlac) {
+ case LYK_ABORT:
+ retlac = LYK_QUIT; /* a bit softer... */
+ /* fall through */
+ case LYK_MAIN_MENU:
+ case LYK_PREV_DOC:
+ case LYK_HOME:
+ case LYK_PREV_PAGE:
+ case LYK_UP_HALF:
+ case LYK_UP_TWO:
+ case LYK_HISTORY:
+ case LYK_HELP:
+/* case LYK_REFRESH:*/
+ case LYK_RELOAD:
+ case LYK_ECGOTO:
+ case LYK_INFO:
+ case LYK_WHEREIS:
+ case LYK_PRINT:
+ case LYK_DOWN_TWO:
+ case LYK_DOWN_HALF:
+ case LYK_NEXT_PAGE:
+ case LYK_END:
+ case LYK_VIEW_BOOKMARK:
+ case LYK_COOKIE_JAR:
+#ifdef USE_CACHEJAR
+ case LYK_CACHE_JAR:
+#endif
+ case LYK_INDEX_SEARCH:
+ case LYK_OPTIONS:
+ mouse_link = -3; /* so LYgetch_for() passes it on - kw */
+ }
+ }
+ if (retlac == LYK_DO_NOTHING ||
+ retlac == LYK_REFRESH) {
+ mouse_link = -1; /* mainloop should not change cur link - kw */
+ }
+ if (code == FOR_INPUT && retlac == LYK_DO_NOTHING) {
+ repaint_main_statusline(FOR_INPUT);
+ }
+ return retlac;
+}
+#endif /* USE_MOUSE && (NCURSES || PDCURSES) */
+
+#if defined(USE_KEYMAPS) && defined(USE_SLANG)
+/************************************************************************/
+
+static int current_sl_modifier = 0;
+
+/* We cannot guarantee the type for 'GetChar', and should not use a cast. */
+static int myGetChar(void)
+{
+ int i = GetChar();
+
+ if (i == 0) /* trick to get NUL char through - kw */
+ current_sl_modifier = LKC_ISLKC;
+ return i;
+}
+
+static int LYgetch_for(int code)
+{
+ SLang_Key_Type *key;
+ int keysym;
+
+ current_sl_modifier = 0;
+
+ key = SLang_do_key(Keymap_List, myGetChar);
+ if ((key == NULL) || (key->type != SLKEY_F_KEYSYM)) {
+#if defined(__WIN32__) || defined(__MINGW32__)
+ if ((key == NULL) && (current_sl_modifier == LKC_ISLKC)) {
+ key = SLang_do_key(Keymap_List, myGetChar);
+ keysym = key->f.keysym;
+ switch (keysym) {
+ case 'H':
+ keysym = UPARROW_KEY;
+ break;
+ case 'P':
+ keysym = DNARROW_KEY;
+ break;
+ case 'M':
+ keysym = RTARROW_KEY;
+ break;
+ case 'K':
+ keysym = LTARROW_KEY;
+ break;
+ case 'R':
+ keysym = INSERT_KEY;
+ break;
+ case 'S':
+ keysym = REMOVE_KEY;
+ break;
+ case 'I':
+ keysym = PGUP_KEY;
+ break;
+ case 'Q':
+ keysym = PGDOWN_KEY;
+ break;
+ case 'G':
+ keysym = HOME_KEY;
+ break;
+ case 'O':
+ keysym = END_KEY;
+ break;
+ case ';':
+ keysym = F1_KEY;
+ break;
+ }
+ return (keysym);
+ }
+#endif
+ return (current_sl_modifier ? 0 : DO_NOTHING);
+ } else {
+ keysym = (int) key->f.keysym;
+
+#if defined (USE_MOUSE)
+ if (keysym == MOUSE_KEYSYM)
+ return sl_read_mouse_event(code);
+#endif
+
+ if (keysym < 0) {
+ return 0;
+
+ } else if (keysym & (LKC_ISLECLAC | LKC_ISLAC)) {
+ return (keysym);
+ } else {
+ current_sl_modifier = 0;
+ if (LKC_HAS_ESC_MOD(keysym)) {
+ current_sl_modifier = LKC_MOD2;
+ keysym &= LKC_MASK;
+ }
+
+ if (keysym + 1 >= KEYMAP_SIZE) {
+ return 0;
+ } else {
+ return (keysym | current_sl_modifier);
+ }
+ }
+ }
+}
+
+/************************************************************************/
+#else /* NOT defined(USE_KEYMAPS) && defined(USE_SLANG) */
+
+/*
+ * LYgetch() translates some escape sequences and may fake noecho.
+ */
+#define found_CSI(first,second) ((second) == '[' || (first) == 155)
+#define found_TLD(value) ((value) == '~')
+
+static int LYgetch_for(int code)
+{
+ int a, b, c, d = -1;
+ int current_modifier = 0;
+ BOOLEAN done_esc = FALSE;
+
+ (void) code;
+
+ have_levent = 0;
+
+ re_read:
+#if !defined(UCX) || !defined(VAXC) /* errno not modifiable ? */
+ if (errno == EINTR)
+ set_errno(0); /* reset - kw */
+#endif /* UCX && VAXC */
+#ifndef USE_SLANG
+ clearerr(stdin); /* needed here for ultrix and SOCKETSHR, but why? - FM */
+#endif /* !USE_SLANG */
+#if !defined(USE_SLANG) || defined(VMS) || defined(DJGPP_KEYHANDLER)
+ c = GetChar();
+ lynx_nl2crlf(FALSE);
+#else
+ if (LYCursesON) {
+ c = GetChar();
+ lynx_nl2crlf(FALSE);
+ } else {
+ c = getchar();
+ if (c == EOF && errno == EINTR) /* Ctrl-Z causes EINTR in getchar() */
+ clearerr(stdin);
+ if (feof(stdin) || ferror(stdin) || c == EOF) {
+#ifdef IGNORE_CTRL_C
+ if (sigint)
+ sigint = FALSE;
+#endif /* IGNORE_CTRL_C */
+ CTRACE((tfp, "GETCH: Translate ^C to ^G.\n"));
+ return (LYCharINTERRUPT2); /* use ^G to cancel whatever called us. */
+ }
+ }
+#endif /* !USE_SLANG || VMS */
+
+ CTRACE((tfp, "GETCH%d: Got %#x.\n", code, (unsigned) c));
+ if (LYNoZapKey > 1 && errno != EINTR &&
+ (c == EOF
+#ifdef USE_SLANG
+ || (c == 0xFFFF)
+#endif
+ )) {
+
+ CTRACE((tfp,
+ "nozap: Got EOF, curses %s, stdin is %p, LYNoZapKey reduced from %d to 0.\n",
+ LYCursesON ? "on" : "off", (void *) stdin, LYNoZapKey));
+ LYNoZapKey = 0; /* 2 -> 0 */
+ if (LYReopenInput() > 0) {
+ if (LYCursesON) {
+ stop_curses();
+ start_curses();
+ LYrefresh();
+ }
+ goto re_read;
+ }
+ }
+#ifdef USE_GETCHAR
+ if (c == EOF && errno == EINTR) /* Ctrl-Z causes EINTR in getchar() */
+ goto re_read;
+#else
+ if (c == EOF && errno == EINTR) {
+
+ CTRACE((tfp, "Got EOF with EINTR, recent_sizechange is %d\n",
+ recent_sizechange));
+#if defined(HAVE_SIZECHANGE) || defined(USE_SLANG)
+ CheckScreenSize();
+ if (!recent_sizechange) { /* not yet detected by ourselves */
+ size_change(0);
+ CTRACE((tfp, "Now recent_sizechange is %d\n", recent_sizechange));
+ }
+#elif !defined(UCX) || !defined(VAXC) /* errno not modifiable ? */
+ set_errno(0); /* reset - kw */
+#endif /* UCX && VAXC */
+ return (DO_NOTHING);
+ }
+#endif /* USE_GETCHAR */
+
+#ifdef USE_SLANG
+ if (c == 0xFFFF && LYCursesON) {
+#ifdef IGNORE_CTRL_C
+ if (sigint) {
+ sigint = FALSE;
+ goto re_read;
+ }
+#endif /* IGNORE_CTRL_C */
+ return (LYCharINTERRUPT2); /* use ^G to cancel whatever called us. */
+ }
+#else /* not USE_SLANG: */
+ if (feof(stdin) || ferror(stdin) || c == EOF) {
+ CheckScreenSize();
+ if (recent_sizechange)
+ return (LYCharINTERRUPT2); /* use ^G to cancel whatever called us. */
+#ifdef IGNORE_CTRL_C
+ if (sigint) {
+ sigint = FALSE;
+ /* clearerr(stdin); don't need here if stays above - FM */
+ goto re_read;
+ }
+#endif /* IGNORE_CTRL_C */
+#if !defined(USE_GETCHAR) && !defined(VMS) && !defined(NCURSES)
+ if (c == ERR && errno == EINTR) /* may have been handled signal - kw */
+ goto re_read;
+#endif /* USE_GETCHAR */
+
+ cleanup();
+ exit_immediately(EXIT_SUCCESS);
+ }
+#endif /* USE_SLANG */
+
+ if (!escape_bound
+ && (c == CH_ESC || (csi_is_csi && c == UCH(CH_ESC_PAR)))) {
+ /* handle escape sequence S/390 -- gil -- 2024 */
+ done_esc = TRUE; /* Flag: we did it, not keypad() */
+ b = GetChar();
+
+ if (b == '[' || b == 'O') {
+ a = GetChar();
+ } else {
+ a = b;
+ }
+
+ switch (a) {
+ case 'A':
+ c = UPARROW_KEY;
+ break;
+ case 'B':
+ c = DNARROW_KEY;
+ break;
+ case 'C':
+ c = RTARROW_KEY;
+ break;
+ case 'D':
+ c = LTARROW_KEY;
+ break;
+ case 'q': /* vt100 application keypad 1 */
+ c = END_KEY;
+ break;
+ case 'r': /* vt100 application keypad 2 */
+ c = DNARROW_KEY;
+ break;
+ case 's': /* vt100 application keypad 3 */
+ c = PGDOWN_KEY;
+ break;
+ case 't': /* vt100 application keypad 4 */
+ c = LTARROW_KEY;
+ break;
+ case 'v': /* vt100 application keypad 6 */
+ c = RTARROW_KEY;
+ break;
+ case 'w': /* vt100 application keypad 7 */
+ c = HOME_KEY;
+ break;
+ case 'x': /* vt100 application keypad 8 */
+ c = UPARROW_KEY;
+ break;
+ case 'y': /* vt100 application keypad 9 */
+ c = PGUP_KEY;
+ break;
+ case 'M':
+#if defined(USE_SLANG) && defined(USE_MOUSE)
+ if (found_CSI(c, b)) {
+ c = sl_read_mouse_event(code);
+ } else
+#endif
+ c = '\n'; /* keypad enter on pc ncsa telnet */
+ break;
+
+ case 'm':
+#ifdef VMS
+ if (b != 'O')
+#endif /* VMS */
+ c = '-'; /* keypad on pc ncsa telnet */
+ break;
+ case 'k':
+ if (b == 'O')
+ c = '+'; /* keypad + on my xterminal :) */
+ else
+ done_esc = FALSE; /* we have another look below - kw */
+ break;
+ case 'l':
+#ifdef VMS
+ if (b != 'O')
+#endif /* VMS */
+ c = '+'; /* keypad on pc ncsa telnet */
+ break;
+ case 'P':
+#ifdef VMS
+ if (b != 'O')
+#endif /* VMS */
+ c = F1_KEY;
+ break;
+ case 'u':
+#ifdef VMS
+ if (b != 'O')
+#endif /* VMS */
+ c = F1_KEY; /* macintosh help button */
+ break;
+ case 'p':
+#ifdef VMS
+ if (b == 'O')
+#endif /* VMS */
+ c = '0'; /* keypad 0 */
+ break;
+ case '1': /* VTxxx Find */
+ if (found_CSI(c, b) && found_TLD(d = GetChar()))
+ c = FIND_KEY;
+ else
+ done_esc = FALSE; /* we have another look below - kw */
+ break;
+ case '2':
+ if (found_CSI(c, b)) {
+ if (found_TLD(d = GetChar())) /* VTxxx Insert */
+ c = INSERT_KEY;
+ else if ((d == '8' ||
+ d == '9') &&
+ found_TLD(GetChar())) {
+ if (d == '8') /* VTxxx Help */
+ c = F1_KEY;
+ else if (d == '9') /* VTxxx Do */
+ c = DO_KEY;
+ d = -1;
+ }
+ } else
+ done_esc = FALSE; /* we have another look below - kw */
+ break;
+ case '3': /** VTxxx Delete **/
+ if (found_CSI(c, b) && found_TLD(d = GetChar()))
+ c = REMOVE_KEY;
+ else
+ done_esc = FALSE; /* we have another look below - kw */
+ break;
+ case '4': /** VTxxx Select **/
+ if (found_CSI(c, b) && found_TLD(d = GetChar()))
+ c = SELECT_KEY;
+ else
+ done_esc = FALSE; /* we have another look below - kw */
+ break;
+ case '5': /** VTxxx PrevScreen **/
+ if (found_CSI(c, b) && found_TLD(d = GetChar()))
+ c = PGUP_KEY;
+ else
+ done_esc = FALSE; /* we have another look below - kw */
+ break;
+ case '6': /** VTxxx NextScreen **/
+ if (found_CSI(c, b) && found_TLD(d = GetChar()))
+ c = PGDOWN_KEY;
+ else
+ done_esc = FALSE; /* we have another look below - kw */
+ break;
+ case '[': /** Linux F1-F5: ^[[[A etc. **/
+ if (found_CSI(c, b)) {
+ if ((d = GetChar()) == 'A')
+ c = F1_KEY;
+ break;
+ }
+ /* FALLTHRU */
+ default:
+ if (c == CH_ESC && a == b && !found_CSI(c, b)) {
+ current_modifier = LKC_MOD2;
+ c = a;
+ /* We're not yet done if ESC + curses-keysym: */
+ done_esc = (BOOL) ((a & ~0xFF) == 0);
+ break;
+ }
+ CTRACE((tfp, "Unknown key sequence: %d:%d:%d\n", c, b, a));
+ CTRACE_SLEEP(MessageSecs);
+ break;
+ }
+ if (isdigit(a) && found_CSI(c, b) && d != -1 && !found_TLD(d))
+ d = GetChar();
+ if (!done_esc && (a & ~0xFF) == 0) {
+ if (a == b && !found_CSI(c, b) && c == CH_ESC) {
+ current_modifier = LKC_MOD2;
+ c = a;
+ done_esc = TRUE;
+ } else {
+ done_esc = TRUE;
+ }
+ }
+ }
+#ifdef USE_KEYMAPS
+ /* Extract a single code if two are merged: */
+ if (c >= 0 && (c & LKC_ISLECLAC)) {
+ if (!(code == FOR_INPUT || code == FOR_PROMPT))
+ c = LKC2_TO_LKC(c);
+ } else if (c >= 0 && (c & LKC_ISLKC)) {
+ c &= ~LKC_ISLKC;
+ done_esc = TRUE; /* already a lynxkeycode, skip keypad switches - kw */
+ }
+ if (c >= 0 && LKC_HAS_ESC_MOD(c)) {
+ current_modifier = LKC_MOD2;
+ c &= LKC_MASK;
+ }
+ if (c >= 0 && (c & (LKC_ISLECLAC | LKC_ISLAC))) {
+ done_esc = TRUE; /* already a lynxactioncode, skip keypad switches - iz */
+ }
+#endif
+ if (done_esc) {
+ /* don't do keypad() switches below, we already got it - kw */
+ } else {
+#ifdef HAVE_KEYPAD
+ /*
+ * Convert keypad() mode keys into Lynx defined keys.
+ */
+ switch (c) {
+ case KEY_DOWN: /* The four arrow keys ... */
+ c = DNARROW_KEY;
+ break;
+ case KEY_UP:
+ c = UPARROW_KEY;
+ break;
+ case KEY_LEFT:
+ c = LTARROW_KEY;
+ break;
+ case KEY_RIGHT: /* ... */
+ c = RTARROW_KEY;
+ break;
+#if defined(PDCURSES) /* for NEC PC-9800 1998/08/30 (Sun) 21:50:35 */
+ case KEY_C2:
+ c = DNARROW_KEY;
+ break;
+ case KEY_A2:
+ c = UPARROW_KEY;
+ break;
+ case KEY_B1:
+ c = LTARROW_KEY;
+ break;
+ case KEY_B3:
+ c = RTARROW_KEY;
+ break;
+ case PAD0: /* PC-9800 Ins */
+ c = INSERT_KEY;
+ break;
+ case PADSTOP: /* PC-9800 DEL */
+ c = REMOVE_KEY;
+ break;
+#endif /* PDCURSES */
+ case KEY_HOME: /* Home key (upward+left arrow) */
+ c = HOME_KEY;
+ break;
+ case KEY_CLEAR: /* Clear screen */
+ c = 18; /* CTRL-R */
+ break;
+ case KEY_NPAGE: /* Next page */
+ c = PGDOWN_KEY;
+ break;
+ case KEY_PPAGE: /* Previous page */
+ c = PGUP_KEY;
+ break;
+ case KEY_LL: /* home down or bottom (lower left) */
+ c = END_KEY;
+ break;
+#if defined(KEY_A1) && defined(KEY_C3)
+ /* The keypad is arranged like this: */
+ /* a1 up a3 */
+ /* left b2 right */
+ /* c1 down c3 */
+ case KEY_A1: /* upper left of keypad */
+ c = HOME_KEY;
+ break;
+ case KEY_A3: /* upper right of keypad */
+ c = PGUP_KEY;
+ break;
+ case KEY_B2: /* center of keypad */
+ c = DO_NOTHING;
+ break;
+ case KEY_C1: /* lower left of keypad */
+ c = END_KEY;
+ break;
+ case KEY_C3: /* lower right of keypad */
+ c = PGDOWN_KEY;
+ break;
+#endif /* defined(KEY_A1) && defined(KEY_C3) */
+#ifdef KEY_ENTER
+ case KEY_ENTER: /* enter/return */
+ c = '\n';
+ break;
+#endif /* KEY_ENTER */
+#ifdef PADENTER /* PDCURSES */
+ case PADENTER:
+ c = '\n';
+ break;
+#endif /* PADENTER */
+#ifdef KEY_END
+ case KEY_END: /* end key 001 */
+ c = END_KEY;
+ break;
+#endif /* KEY_END */
+#ifdef KEY_HELP
+ case KEY_HELP: /* help key 001 */
+ c = F1_KEY;
+ break;
+#endif /* KEY_HELP */
+#ifdef KEY_BACKSPACE
+ case KEY_BACKSPACE:
+ c = CH_DEL; /* backspace key (delete, not Ctrl-H) S/390 -- gil -- 2041 */
+ break;
+#endif /* KEY_BACKSPACE */
+ case KEY_F(1):
+ c = F1_KEY; /* VTxxx Help */
+ break;
+#if defined(KEY_F) && !defined(__DJGPP__) && !defined(_WINDOWS)
+ case KEY_F(16):
+ c = DO_KEY; /* VTxxx Do */
+ break;
+#endif /* KEY_F */
+#ifdef KEY_REDO
+ case KEY_REDO: /* VTxxx Do */
+ c = DO_KEY;
+ break;
+#endif /* KEY_REDO */
+#ifdef KEY_FIND
+ case KEY_FIND:
+ c = FIND_KEY; /* VTxxx Find */
+ break;
+#endif /* KEY_FIND */
+#ifdef KEY_SELECT
+ case KEY_SELECT:
+ c = SELECT_KEY; /* VTxxx Select */
+ break;
+#endif /* KEY_SELECT */
+#ifdef KEY_IC
+ case KEY_IC:
+ c = INSERT_KEY; /* VTxxx Insert */
+ break;
+#endif /* KEY_IC */
+#ifdef KEY_DC
+ case KEY_DC:
+ c = REMOVE_KEY; /* VTxxx Remove */
+ break;
+#endif /* KEY_DC */
+#ifdef KEY_BTAB
+ case KEY_BTAB:
+ c = BACKTAB_KEY; /* Back tab, often Shift-Tab */
+ break;
+#endif /* KEY_BTAB */
+#ifdef KEY_RESIZE
+ case KEY_RESIZE:
+ c = DO_NOTHING;
+ break;
+#endif /* KEY_RESIZE */
+
+/* The following maps PDCurses keys away from lynx reserved values */
+#if (defined(_WINDOWS) || defined(__DJGPP__)) && !defined(USE_SLANG)
+ case KEY_F(2):
+ c = 0x213;
+ break;
+ case KEY_F(3):
+ c = 0x214;
+ break;
+ case KEY_F(4):
+ c = 0x215;
+ break;
+ case KEY_F(5):
+ c = 0x216;
+ break;
+ case KEY_F(6):
+ c = 0x217;
+ break;
+ case KEY_F(7):
+ c = 0x218;
+ break;
+#endif /* PDCurses */
+
+#if defined(USE_MOUSE)
+/********************************************************************/
+
+#if defined(NCURSES) || defined(PDCURSES)
+ case KEY_MOUSE:
+ CTRACE((tfp, "KEY_MOUSE\n"));
+ if (code == FOR_CHOICE) {
+ c = MOUSE_KEY; /* Will be processed by the caller */
+ }
+#if defined(NCURSES)
+ else if (code == FOR_SINGLEKEY) {
+ MEVENT event;
+
+ getmouse(&event); /* Completely ignore event - kw */
+ c = DO_NOTHING;
+ }
+#endif
+ else {
+#if defined(NCURSES)
+ MEVENT event;
+ int err;
+ int lac = LYK_UNKNOWN;
+
+ c = -1;
+ mouse_link = -1;
+ err = getmouse(&event);
+ if (err != OK) {
+ CTRACE((tfp, "Mouse error: no event available!\n"));
+ return (code == FOR_PANEL ? 0 : DO_NOTHING);
+ }
+ levent = event; /* Allow setting pos in entry fields */
+ if (event.bstate & BUTTON1_CLICKED) {
+ c = set_clicked_link(event.x, event.y, code, 1);
+ } else if (event.bstate & BUTTON1_DOUBLE_CLICKED) {
+ c = set_clicked_link(event.x, event.y, code, 2);
+ if (c == LAC_TO_LKC0(LYK_MOUSE_SUBMIT) &&
+ code == FOR_INPUT)
+ lac = LYK_MOUSE_SUBMIT;
+ } else if (event.bstate & BUTTON3_CLICKED) {
+ c = LAC_TO_LKC0(LYK_PREV_DOC);
+ } else if (code == FOR_PROMPT
+ /* Cannot ignore: see LYCurses.c */
+ || (event.bstate &
+ (BUTTON1_PRESSED | BUTTON1_RELEASED
+ | BUTTON2_PRESSED | BUTTON2_RELEASED
+ | BUTTON3_PRESSED | BUTTON3_RELEASED))) {
+ /* Completely ignore - don't return anything, to
+ avoid canceling the prompt - kw */
+ goto re_read;
+ } else if (event.bstate & BUTTON2_CLICKED) {
+ int atlink;
+
+ c = set_clicked_link(event.x, event.y, code, 1);
+ atlink = (c == LAC_TO_LKC0(LYK_ACTIVATE));
+ if (!atlink)
+ mouse_link = -1; /* Forget about approx stuff. */
+
+ lac = LYmouse_menu(event.x, event.y, atlink, code);
+ if (lac == LYK_MOUSE_SUBMIT) {
+ if (mouse_link == -1)
+ lac = LYK_ACTIVATE;
+#ifdef TEXTFIELDS_MAY_NEED_ACTIVATION
+ else if (mouse_link >= 0 &&
+ textfields_need_activation &&
+ links[mouse_link].type == WWW_FORM_LINK_TYPE &&
+ F_TEXTLIKE(links[mouse_link].l_form->type))
+ lac = LYK_ACTIVATE;
+#endif
+ }
+ if (lac == LYK_ACTIVATE && mouse_link == -1) {
+ HTAlert(gettext("No link chosen"));
+ lac = LYK_REFRESH;
+ }
+ c = LAC_TO_LKC(lac);
+ }
+#if NCURSES_MOUSE_VERSION > 1
+ else if (event.bstate & BUTTON4_PRESSED) {
+ c = LAC_TO_LKC(LYK_UP_HALF);
+ } else if (event.bstate & BUTTON5_PRESSED) {
+ c = LAC_TO_LKC(LYK_DOWN_HALF);
+ }
+#endif
+ if (code == FOR_INPUT && mouse_link == -1 &&
+ lac != LYK_REFRESH &&
+ lac != LYK_MOUSE_SUBMIT) {
+ ungetmouse(&event); /* Caller will process this. */
+ wgetch(LYwin); /* ungetmouse puts KEY_MOUSE back */
+ c = MOUSE_KEY;
+ }
+#else /* pdcurses version */
+
+#define H_CMD_AREA 6
+#define HIST_CMD_2 12
+#define V_CMD_AREA 1
+
+ int left = H_CMD_AREA;
+ int right = (LYcolLimit - H_CMD_AREA - 1);
+
+ /* yes, I am assuming that my screen will be a certain width. */
+
+ int tick_count;
+ char *p = NULL;
+ char mouse_info[128];
+ static int old_click = 0; /* [m Sec] */
+
+ c = -1;
+ mouse_link = -1;
+
+ if (!system_is_NT) {
+ tick_count = GetTickCount();
+
+ /* Guard Mouse button miss click */
+ if ((tick_count - old_click) < 700) {
+ c = DO_NOTHING;
+ break;
+ } else {
+ old_click = tick_count;
+ }
+ }
+ request_mouse_pos();
+
+ if (BUTTON_STATUS(1) & BUTTON_PRESSED) {
+ if (MOUSE_Y_POS > (LYlines - V_CMD_AREA - 1)) {
+ /* Screen BOTTOM */
+ if (MOUSE_X_POS < left) {
+ c = LTARROW_KEY;
+ p = "<-";
+ } else if (MOUSE_X_POS < HIST_CMD_2) {
+ c = RTARROW_KEY;
+ p = "->";
+ } else if (MOUSE_X_POS > right) {
+ c = 'z';
+ p = "Cancel";
+ } else {
+ c = PGDOWN_KEY;
+ p = "PGDOWN";
+ }
+ } else if (MOUSE_Y_POS < V_CMD_AREA) {
+ /* Screen TOP */
+ if (MOUSE_X_POS < left) {
+ c = LTARROW_KEY;
+ p = "<-";
+ } else if (MOUSE_X_POS < HIST_CMD_2) {
+ c = RTARROW_KEY;
+ p = "->";
+ } else if (MOUSE_X_POS > right) {
+ c = 'z';
+ p = "Cancel";
+ } else {
+ c = PGUP_KEY;
+ p = "PGUP";
+ }
+ } else {
+ c = set_clicked_link(MOUSE_X_POS,
+ MOUSE_Y_POS,
+ FOR_PANEL, 1);
+ }
+ }
+ if (p && c != -1) {
+ sprintf(mouse_info, "Mouse = 0x%x, [%s]", c, p);
+ SetConsoleTitle(mouse_info);
+ }
+#endif /* !(WIN_EX) */
+ if ((c + 1) >= KEYMAP_SIZE && (c & LKC_ISLAC))
+ return (c);
+ }
+ break;
+#endif /* NCURSES || PDCURSES */
+
+/********************************************************************/
+#endif /* USE_MOUSE */
+
+ }
+#endif /* HAVE_KEYPAD */
+#ifdef DJGPP_KEYHANDLER
+ switch (c) {
+ case K_Down: /* The four arrow keys ... */
+ case K_EDown:
+ c = DNARROW_KEY;
+ break;
+ case K_Up:
+ case K_EUp:
+ c = UPARROW_KEY;
+ break;
+ case K_Left:
+ case K_ELeft:
+ c = LTARROW_KEY;
+ break;
+ case K_Right: /* ... */
+ case K_ERight:
+ c = RTARROW_KEY;
+ break;
+ case K_Home: /* Home key (upward+left arrow) */
+ case K_EHome:
+ c = HOME_KEY;
+ break;
+ case K_PageDown: /* Next page */
+ case K_EPageDown:
+ c = PGDOWN_KEY;
+ break;
+ case K_PageUp: /* Previous page */
+ case K_EPageUp:
+ c = PGUP_KEY;
+ break;
+ case K_End: /* home down or bottom (lower left) */
+ case K_EEnd:
+ c = END_KEY;
+ break;
+ case K_F1: /* F1 key */
+ c = F1_KEY;
+ break;
+ case K_Insert: /* Insert key */
+ case K_EInsert:
+ c = INSERT_KEY;
+ break;
+ case K_Delete: /* Delete key */
+ case K_EDelete:
+ c = REMOVE_KEY;
+ break;
+ case K_Alt_Escape: /* Alt-Escape */
+ c = 0x1a7;
+ break;
+ case K_Control_At: /* CTRL-@ */
+ c = 0x1a8;
+ break;
+ case K_Alt_Backspace: /* Alt-Backspace */
+ c = 0x1a9;
+ break;
+ case K_BackTab: /* BackTab */
+ c = BACKTAB_KEY;
+ break;
+ }
+#endif /* DGJPP_KEYHANDLER */
+#if defined(USE_SLANG) && (defined(__DJGPP__) || defined(__CYGWIN__)) && !defined(DJGPP_KEYHANDLER) && !defined(USE_KEYMAPS)
+ switch (c) {
+ case SL_KEY_DOWN: /* The four arrow keys ... */
+ c = DNARROW_KEY;
+ break;
+ case SL_KEY_UP:
+ c = UPARROW_KEY;
+ break;
+ case SL_KEY_LEFT:
+ c = LTARROW_KEY;
+ break;
+ case SL_KEY_RIGHT: /* ... */
+ c = RTARROW_KEY;
+ break;
+ case SL_KEY_HOME: /* Home key (upward+left arrow) */
+ case SL_KEY_A1: /* upper left of keypad */
+ c = HOME_KEY;
+ break;
+ case SL_KEY_NPAGE: /* Next page */
+ case SL_KEY_C3: /* lower right of keypad */
+ c = PGDOWN_KEY;
+ break;
+ case SL_KEY_PPAGE: /* Previous page */
+ case SL_KEY_A3: /* upper right of keypad */
+ c = PGUP_KEY;
+ break;
+ case SL_KEY_END: /* home down or bottom (lower left) */
+ case SL_KEY_C1: /* lower left of keypad */
+ c = END_KEY;
+ break;
+ case SL_KEY_F(1): /* F1 key */
+ c = F1_KEY;
+ break;
+ case SL_KEY_IC: /* Insert key */
+ c = INSERT_KEY;
+ break;
+ case SL_KEY_DELETE: /* Delete key */
+ c = REMOVE_KEY;
+ break;
+ }
+#endif /* USE_SLANG && __DJGPP__ && !DJGPP_KEYHANDLER && !USE_KEYMAPS */
+ }
+
+ if (c & (LKC_ISLAC | LKC_ISLECLAC)) {
+ return (c);
+ } else if ((c + 1) >= KEYMAP_SIZE) {
+ /*
+ * Don't return raw values for KEYPAD symbols which we may have missed
+ * in the switch above if they are obviously invalid when used as an
+ * index into (e.g.) keypad[]. - KW
+ */
+ return (0);
+ } else {
+ return (c | current_modifier);
+ }
+}
+
+/************************************************************************/
+#endif /* NOT defined(USE_KEYMAPS) && defined(USE_SLANG) */
+
+int LYgetch(void)
+{
+ return LYReadCmdKey(FOR_PANEL);
+}
+
+/*
+ * Read a single keystroke, allows mouse-selection.
+ */
+int LYgetch_choice(void)
+{
+ int ch = LYReadCmdKey(FOR_CHOICE);
+
+ if (ch == LYCharINTERRUPT1)
+ ch = LYCharINTERRUPT2; /* treat ^C the same as ^G */
+ return ch;
+}
+
+/*
+ * Read a single keystroke, allows mouse events.
+ */
+int LYgetch_input(void)
+{
+ int ch = LYReadCmdKey(FOR_INPUT);
+
+ if (ch == LYCharINTERRUPT1)
+ ch = LYCharINTERRUPT2; /* treat ^C the same as ^G */
+ return ch;
+}
+
+/*
+ * Read a single keystroke, ignoring case by translating it to uppercase.
+ * Ignore mouse events, if any.
+ */
+int LYgetch_single(void)
+{
+ int ch = LYReadCmdKey(FOR_SINGLEKEY);
+
+ if (ch == LYCharINTERRUPT1)
+ ch = LYCharINTERRUPT2; /* treat ^C the same as ^G */
+ else if (ch > 0 && ch < 256)
+ ch = TOUPPER(ch); /* will ignore case of result */
+ return ch;
+}
+
+/*
+ * Convert a null-terminated string to lowercase
+ */
+void LYLowerCase(char *arg_buffer)
+{
+ register unsigned char *buffer = (unsigned char *) arg_buffer;
+ size_t i;
+
+ for (i = 0; buffer[i]; i++) {
+#ifdef SUPPORT_MULTIBYTE_EDIT /* 1998/11/23 (Mon) 17:04:55 */
+ if ((buffer[i] & 0x80) != 0
+ && buffer[i + 1] != 0) {
+ if ((kanji_code == SJIS) && IS_SJIS_X0201KANA(UCH((buffer[i])))) {
+ continue;
+ }
+ i++;
+ } else {
+ buffer[i] = UCH(TOLOWER(buffer[i]));
+ }
+#else
+ buffer[i] = TOLOWER(buffer[i]);
+#endif
+ }
+}
+
+/*
+ * Convert a null-terminated string to uppercase
+ */
+void LYUpperCase(char *arg_buffer)
+{
+ register unsigned char *buffer = (unsigned char *) arg_buffer;
+ size_t i;
+
+ for (i = 0; buffer[i]; i++) {
+#ifdef SUPPORT_MULTIBYTE_EDIT /* 1998/11/23 (Mon) 17:05:10 */
+ if ((buffer[i] & 0x80) != 0
+ && buffer[i + 1] != 0) {
+ if ((kanji_code == SJIS) && IS_SJIS_X0201KANA(UCH((buffer[i])))) {
+ continue;
+ }
+ i++;
+ } else {
+ buffer[i] = UCH(TOUPPER(buffer[i]));
+ }
+#else
+ buffer[i] = UCH(TOUPPER(buffer[i]));
+#endif
+ }
+}
+
+/*
+ * Remove newlines from a string, returning true if we removed any.
+ */
+BOOLEAN LYRemoveNewlines(char *buffer)
+{
+ BOOLEAN result = FALSE;
+
+ if (buffer != 0) {
+ register char *buf = buffer;
+
+ for (; *buf && *buf != '\n' && *buf != '\r'; buf++) ;
+ if (*buf) {
+ /* runs very seldom */
+ char *old = buf;
+
+ for (; *old; old++) {
+ if (*old != '\n' && *old != '\r')
+ *buf++ = *old;
+ }
+ *buf = '\0';
+ result = TRUE;
+ }
+ }
+ return result;
+}
+
+/*
+ * Remove leading/trailing whitespace from a string, reduce runs of embedded
+ * whitespace to single blanks.
+ */
+char *LYReduceBlanks(char *buffer)
+{
+ if (non_empty(buffer)) {
+ LYTrimLeading(buffer);
+ LYTrimTrailing(buffer);
+ convert_to_spaces(buffer, TRUE);
+ }
+ return buffer;
+}
+
+/*
+ * Remove ALL whitespace from a string (including embedded blanks), and returns
+ * a pointer to the end of the trimmed string.
+ */
+char *LYRemoveBlanks(char *buffer)
+{
+ char *result = NULL;
+
+ if (buffer != 0) {
+ register char *buf = buffer;
+
+ for (; *buf && !isspace(UCH(*buf)); buf++) ;
+ if (*buf) {
+ /* runs very seldom */
+ char *old = buf;
+
+ for (; *old; old++) {
+ if (!isspace(UCH(*old)))
+ *buf++ = *old;
+ }
+ *buf = '\0';
+ }
+ result = buf;
+ }
+ return result;
+}
+
+/*
+ * Skip whitespace
+ */
+char *LYSkipBlanks(char *buffer)
+{
+ if (buffer != NULL) {
+ while (isspace(UCH((*buffer))))
+ buffer++;
+ }
+ return buffer;
+}
+
+/*
+ * Skip non-whitespace
+ */
+char *LYSkipNonBlanks(char *buffer)
+{
+ if (buffer != NULL) {
+ while (*buffer != 0 && !isspace(UCH((*buffer))))
+ buffer++;
+ }
+ return buffer;
+}
+
+/*
+ * Skip const whitespace
+ */
+const char *LYSkipCBlanks(const char *buffer)
+{
+ while (isspace(UCH((*buffer))))
+ buffer++;
+ return buffer;
+}
+
+/*
+ * Skip const non-whitespace
+ */
+const char *LYSkipCNonBlanks(const char *buffer)
+{
+ while (*buffer != 0 && !isspace(UCH((*buffer))))
+ buffer++;
+ return buffer;
+}
+
+/*
+ * Trim leading blanks from a string
+ */
+void LYTrimLeading(char *buffer)
+{
+ char *skipped = LYSkipBlanks(buffer);
+
+ while ((*buffer++ = *skipped++) != 0) ;
+}
+
+/*
+ * Trim trailing newline(s) from a string
+ */
+char *LYTrimNewline(char *buffer)
+{
+ size_t i = strlen(buffer);
+
+ while (i != 0 && (buffer[i - 1] == '\n' || buffer[i - 1] == '\r'))
+ buffer[--i] = 0;
+ return buffer;
+}
+
+/*
+ * Trim trailing blanks from a string
+ */
+void LYTrimTrailing(char *buffer)
+{
+ size_t i = strlen(buffer);
+
+ while (i != 0 && isspace(UCH(buffer[i - 1])))
+ buffer[--i] = 0;
+}
+
+/* 1997/11/10 (Mon) 14:26:10, originally string_short() in LYExterns.c, but
+ * moved here because LYExterns is not always configured.
+ */
+char *LYElideString(char *str,
+ int cut_pos)
+{
+ char buff[MAX_LINE], *s, *d;
+ static char s_str[MAX_LINE];
+ int len;
+
+ LYStrNCpy(buff, str, sizeof(buff) - 1);
+ len = (int) strlen(buff);
+ if (len > (LYcolLimit - 9)) {
+ buff[cut_pos] = '.';
+ buff[cut_pos + 1] = '.';
+ for (s = (buff + len) - (LYcolLimit - 9) + cut_pos + 1,
+ d = (buff + cut_pos) + 2;
+ s >= buff &&
+ d >= buff &&
+ d < buff + LYcols &&
+ (*d++ = *s++) != 0;) ;
+ buff[LYcols] = 0;
+ }
+ strcpy(s_str, buff);
+ return (s_str);
+}
+
+/*
+ * Trim a startfile, returning true if it looks like one of the Lynx tags.
+ */
+BOOLEAN LYTrimStartfile(char *buffer)
+{
+ BOOLEAN result = FALSE;
+
+ LYTrimHead(buffer);
+ if (isLYNXEXEC(buffer) ||
+ isLYNXPROG(buffer)) {
+ /*
+ * The original implementations of these schemes expected white space
+ * without hex escaping, and did not check for hex escaping, so we'll
+ * continue to support that, until that code is redone in conformance
+ * with SGML principles. - FM
+ */
+ HTUnEscapeSome(buffer, " \r\n\t");
+ convert_to_spaces(buffer, TRUE);
+ result = TRUE;
+ }
+ return result;
+}
+
+/*
+ * Escape unsafe characters in startfile, except for lynx internal URLs.
+ */
+void LYEscapeStartfile(char **buffer)
+{
+ if (!LYTrimStartfile(*buffer)) {
+ char *escaped = HTEscapeUnsafe(*buffer);
+
+ StrAllocCopy(*buffer, escaped);
+ FREE(escaped);
+ }
+}
+
+/*
+ * Trim all blanks from startfile, except for lynx internal URLs.
+ */
+void LYTrimAllStartfile(char *buffer)
+{
+ if (!LYTrimStartfile(buffer)) {
+ LYRemoveBlanks(buffer);
+ }
+}
+
+/*
+ * Display the current value of the string and allow the user to edit it.
+ */
+
+/*
+ * Shorthand to get rid of the "edit->suchandsos".
+ */
+#define IsDirty edit->efIsDirty
+#define IsHidden edit->efIsMasked
+#define StartX edit->efStartX
+#define StartY edit->efStartY
+#define Buffer edit->efBuffer
+#define EditAt edit->efEditAt /* current editing position (bytes) */
+#define BufInUse edit->efBufInUse /* length (bytes) */
+#define BufAlloc edit->efBufAlloc
+#define BufLimit edit->efBufLimit
+#define DpyWidth edit->efWidth
+#define DpyStart edit->efDpyStart /* display-start (columns) */
+#define PanMargin edit->efPanMargin
+#define IsPanned edit->efIsPanned
+#define PadChar edit->efPadChar
+#ifdef ENHANCED_LINEEDIT
+#define EditMark edit->efEditMark
+#endif
+#define InputMods edit->efInputMods
+#define Offs2Col edit->efOffs2Col
+
+#define enableEditMark() \
+ if (EditMark < 0) \
+ EditMark = -(1 + EditMark)
+
+#define disableEditMark() \
+ if (EditMark >= 0) \
+ EditMark = -(1 + EditMark)
+
+#ifdef ENHANCED_LINEEDIT
+static bstring *killbuffer;
+#endif
+
+static void updateMargin(FieldEditor * edit)
+{
+ if ((int) BufAlloc > DpyWidth) { /* Need panning? */
+ if (DpyWidth > 4)
+ IsPanned = TRUE;
+
+ /*
+ * Figure out margins. If too big, we do a lot of unnecessary
+ * scrolling. If too small, user doesn't have sufficient look-ahead.
+ * Let's say 25% for each margin, upper bound is 10 columns.
+ */
+ PanMargin = DpyWidth / 4;
+ if (PanMargin > 10)
+ PanMargin = 10;
+ }
+}
+
+/*
+ * Before using an array position, make sure that the array is long enough.
+ * Reallocate if needed.
+ */
+static void ExtendEditor(FieldEditor * edit, int position)
+{
+ size_t need = (size_t) (++position);
+
+ if (need >= BufAlloc && (BufLimit == 0 || need < BufLimit)) {
+ CTRACE((tfp, "ExtendEditor from %lu to %lu\n",
+ (unsigned long) BufAlloc,
+ (unsigned long) need));
+ Buffer = typeRealloc(char, Buffer, need);
+ Offs2Col = typeRealloc(int, Offs2Col, need + 1);
+
+ BufAlloc = need;
+ updateMargin(edit);
+ }
+}
+
+void LYFinishEdit(FieldEditor * edit)
+{
+ CTRACE((tfp, "LYFinishEdit:%s\n", NonNull(Buffer)));
+
+ FREE(Buffer);
+ FREE(Offs2Col);
+}
+
+void LYSetupEdit(FieldEditor * edit, char *old_value, unsigned buffer_limit, int display_limit)
+{
+ CTRACE((tfp, "LYSetupEdit buffer %lu, display %d:%s\n",
+ (unsigned long) buffer_limit,
+ display_limit,
+ old_value));
+
+ BufLimit = buffer_limit;
+ if (buffer_limit == 0)
+ buffer_limit = (unsigned) strlen(old_value) + 1;
+
+ /*
+ * Initialize edit record
+ */
+ LYGetYX(StartY, StartX);
+ PadChar = ' ';
+ IsDirty = TRUE;
+ IsPanned = FALSE;
+ InputMods = 0;
+
+ BufAlloc = buffer_limit;
+ DpyWidth = display_limit;
+ PanMargin = 0;
+ EditAt = (int) strlen(old_value);
+#ifdef ENHANCED_LINEEDIT
+ EditMark = -1; /* pos=0, but do not show it yet */
+#endif
+ DpyStart = 0;
+
+ updateMargin(edit);
+
+ BufInUse = strlen(old_value);
+ Buffer = typecallocn(char, BufAlloc + 1);
+
+ if (Buffer == 0)
+ outofmem(__FILE__, "LYSetupEdit");
+
+ LYStrNCpy(Buffer, old_value, buffer_limit);
+ Offs2Col = typecallocn(int, BufAlloc + 1);
+
+ if (Offs2Col == 0)
+ outofmem(__FILE__, "LYSetupEdit");
+}
+
+#ifdef SUPPORT_MULTIBYTE_EDIT
+
+/*
+ * MBCS positioning routines below are specific to SUPPORT_MULTIBYTE_EDIT code.
+ * Currently they handle UTF-8 and (hopefully) CJK.
+ * Current encoding is recognized using defines below.
+ *
+ * LYmbcs* functions don't look very convenient to use here...
+ * Do we really need utf_flag as an argument?
+ *
+ * It is set (see IS_UTF8_TTY) for every invocation out there, and they use
+ * HTCJK flag internally anyway. Something like LYmbcsstrnlen == mbcs_glyphs
+ * would be useful to work with string slices -Sergej Kvachonok
+ */
+
+#define IS_UTF8_EXTRA(x) (((unsigned char)(x) & 0300) == 0200)
+
+/*
+ * Counts glyphs in a multibyte (sub)string s of length len.
+ */
+static int mbcs_glyphs(char *s, int len)
+{
+ int glyphs = 0;
+ int i;
+
+ if (IS_UTF8_TTY) {
+ for (i = 0; s[i] && i < len; i++)
+ if (!IS_UTF8_EXTRA(s[i]))
+ glyphs++;
+ } else if (IS_CJK_TTY) {
+ for (i = 0; s[i] && i < len; i++, glyphs++)
+ if (is8bits(s[i]))
+ i++;
+ } else {
+ glyphs = len;
+ }
+ return glyphs;
+}
+
+/*
+ * Check if there are no continuation bytes in the multibyte (sub)string of
+ * length len.
+ */
+static int mbcs_valid(char *s, int len, int limit)
+{
+ int i;
+ int result = FALSE;
+
+ if (IS_UTF8_TTY) {
+ for (i = 0; s[i] && i < limit; i++) {
+ if (!IS_UTF8_EXTRA(s[i])) {
+ if ((i + 1) == len) {
+ result = TRUE;
+ break;
+ }
+ }
+ }
+ } else if (IS_CJK_TTY) {
+ for (i = 0; s[i] && i < limit; i++) {
+ if (!is8bits(s[i])) {
+ if ((i + 1) == len) {
+ result = TRUE;
+ break;
+ }
+ }
+ }
+ } else {
+ result = TRUE;
+ }
+ return result;
+}
+
+/*
+ * Calculates offset in bytes of a glyph at cell position pos.
+ */
+static int mbcs_skip(char *s, int pos)
+{
+ int p, i;
+
+ if (IS_UTF8_TTY) {
+ for (i = 0, p = 0; s[i]; i++) {
+ if (!IS_UTF8_EXTRA(s[i]))
+ p++;
+ if (p > pos)
+ break;
+ }
+ } else if (IS_CJK_TTY) {
+ for (p = i = 0; s[i] && p < pos; p++, i++)
+ if (is8bits(s[i]))
+ i++;
+ } else {
+ i = pos;
+ }
+
+ return i;
+}
+
+/*
+ * Given a string that would display (at least) the given number of cells,
+ * determine the number of multibyte characters that comprised those cells.
+ */
+static int cell2char(char *s, int cells)
+{
+ int result = 0;
+ int len = (int) strlen(s);
+ int pos;
+ int have;
+
+ CTRACE_EDIT((tfp, "cell2char(%d) %d:%s\n", cells, len, s));
+ if (len != 0) {
+ int best = -1;
+
+ for (pos = 0; pos <= len; ++pos) {
+ have = LYstrExtent2(s, pos);
+ CTRACE_EDIT((tfp, " %2d:%2d:%.*s\n", pos, have, pos, s));
+ if (have >= cells) {
+ if (cells <= 0)
+ break;
+ /* the best solution is the one with the most bytes */
+ best = pos;
+ if (mbcs_valid(s, pos, len))
+ break;
+ }
+ }
+ if (best >= 0)
+ pos = best;
+ if (pos > len)
+ pos = len;
+ } else {
+ pos = 0;
+ }
+ result = mbcs_glyphs(s, pos);
+ CTRACE_EDIT((tfp, "->%d\n", result));
+ return result;
+}
+
+#endif /* SUPPORT_MULTIBYTE_EDIT */
+
+#ifdef EXP_KEYBOARD_LAYOUT
+static int map_active = 0;
+
+#else
+#define map_active 0
+#endif
+
+int LYEditInsert(FieldEditor * edit, unsigned const char *s,
+ int len,
+ int map GCC_UNUSED,
+ int maxMessage)
+{
+ int length = (int) strlen(Buffer);
+ int remains = (int) BufAlloc - (length + len);
+ int edited = 0, overflow = 0;
+
+ /*
+ * ch is (presumably) printable character.
+ */
+ if (remains < 0) {
+ overflow = 1;
+ len = 0;
+ if ((int) BufAlloc > length) /* Insert as much as we can */
+ len = (int) BufAlloc - length;
+ else
+ goto finish;
+ }
+ ExtendEditor(edit, length + len);
+ Buffer[length + len] = '\0';
+ for (; length >= EditAt; length--) /* Make room */
+ Buffer[length + len] = Buffer[length];
+#ifdef EXP_KEYBOARD_LAYOUT
+ if (map < 0)
+ map = map_active;
+ if (map && IS_UTF8_TTY) {
+ int off = EditAt;
+ unsigned const char *e = s + len;
+ char *tail = 0;
+
+ while (s < e) {
+ char utfbuf[8];
+ int l = 1;
+
+ utfbuf[0] = (char) *s;
+ if (*s < 128 && LYKbLayouts[current_layout][*s]) {
+ UCode_t ucode = LYKbLayouts[current_layout][*s];
+
+ if (ucode > 127) {
+ if (UCConvertUniToUtf8(ucode, utfbuf)) {
+ l = (int) strlen(utfbuf);
+ remains -= l - 1;
+ if (remains < 0) {
+ if (tail)
+ strcpy(Buffer + off, tail);
+ FREE(tail);
+ len = off;
+ overflow = 1;
+ goto finish;
+ }
+ if (l > 1 && !tail)
+ StrAllocCopy(tail, Buffer + EditAt + len);
+ } else
+ utfbuf[0] = '?';
+ } else
+ utfbuf[0] = (char) ucode;
+ }
+ if ((size_t) (off + l) <= BufAlloc) {
+ memcpy(Buffer + off, utfbuf, (size_t) l);
+ edited = 1;
+ off += l;
+ }
+ s++;
+ }
+ if (tail)
+ strcpy(Buffer + off, tail);
+ len = off - EditAt;
+ FREE(tail);
+ } else if (map) {
+ unsigned const char *e = s + len;
+ unsigned char *t = (unsigned char *) Buffer + EditAt;
+
+ while (s < e) {
+ int ch;
+
+ if (*s < 128 && LYKbLayouts[current_layout][*s]) {
+ ch = UCTransUniChar((UCode_t) LYKbLayouts[current_layout][*s],
+ current_char_set);
+ if (ch < 0)
+ ch = '?';
+ } else
+ ch = *s;
+ *t = UCH(ch);
+ t++, s++;
+ }
+ edited = 1;
+ } else
+#endif /* defined EXP_KEYBOARD_LAYOUT */
+ {
+ StrNCpy(Buffer + EditAt, (const char *) s, len);
+ edited = 1;
+ }
+
+ finish:
+ EditAt += len;
+ BufInUse += (size_t) len;
+ if (edited)
+ IsDirty = TRUE;
+ if (overflow && maxMessage)
+ _statusline(MAXLEN_REACHED_DEL_OR_MOV);
+#ifdef ENHANCED_LINEEDIT
+ if (EditMark > EditAt)
+ EditMark += len;
+ else if (EditMark < -(1 + EditAt))
+ EditMark -= len;
+ disableEditMark();
+#endif
+ return edited;
+}
+
+/*
+ * Do one edit-operation, given the input 'ch' and working buffer 'edit'.
+ *
+ * If the input is processed, returns zero.
+ * If the action should be performed outside of line-editing mode, return -ch.
+ * Otherwise, e.g., returns 'ch'.
+ */
+int LYDoEdit(FieldEditor * edit, int ch,
+ int action,
+ int maxMessage)
+{
+ int i;
+ int length;
+ unsigned char uch;
+ int offset;
+
+ if ((int) BufAlloc <= 0)
+ return (0); /* Be defensive */
+
+ BufInUse = strlen(&Buffer[0]);
+ length = (int) BufInUse;
+
+ switch (action) {
+#ifdef EXP_KEYBOARD_LAYOUT
+ case LYE_SWMAP:
+ /*
+ * Turn input character mapping on or off.
+ */
+ map_active = ~map_active;
+ break;
+#endif
+#ifndef CJK_EX
+ case LYE_AIX:
+ /*
+ * Handle CJK characters, or as a valid character in the current
+ * display character set. Otherwise, we treat this as LYE_ENTER.
+ */
+ if (!IS_CJK_TTY && LYlowest_eightbit[current_char_set] > 0x97)
+ return (ch);
+#endif
+ /* FALLTHRU */
+ case LYE_CHAR:
+ uch = UCH(ch);
+ LYEditInsert(edit, &uch, 1, map_active, maxMessage);
+ return 0; /* All changes already registered */
+
+ case LYE_C1CHAR:
+ /*
+ * ch is the second part (in most cases, a capital letter) of a 7-bit
+ * replacement for a character in the 8-bit C1 control range.
+ *
+ * This is meant to undo transformations like 0x81 -> 0x1b 0x41 (ESC A)
+ * etc., done by slang on Unix and possibly some comm programs. It's
+ * an imperfect workaround that doesn't work for all such characters.
+ */
+ ch &= 0xFF;
+ if (ch + 64 >= LYlowest_eightbit[current_char_set])
+ ch += 64;
+
+ if (EditAt <= ((int) BufAlloc) && BufInUse < BufAlloc) {
+#ifdef ENHANCED_LINEEDIT
+ if (EditMark > EditAt)
+ EditMark++;
+ else if (EditMark < -(1 + EditAt))
+ EditMark--;
+ disableEditMark();
+#endif
+ ExtendEditor(edit, length + 1);
+ for (i = length; i >= EditAt; i--) /* Make room */
+ Buffer[i + 1] = Buffer[i];
+ Buffer[length + 1] = '\0';
+ Buffer[EditAt] = (char) ch;
+ EditAt++;
+ } else {
+ if (maxMessage) {
+ _statusline(MAXLEN_REACHED_DEL_OR_MOV);
+ }
+ return (ch);
+ }
+ break;
+
+ case LYE_BACKW: /* go backward one word */
+ while (EditAt && !IsWordChar(Buffer[EditAt - 1]))
+ EditAt--;
+ while (EditAt && IsWordChar(UCH(Buffer[EditAt - 1])))
+ EditAt--;
+ break;
+
+ case LYE_FORWW: /* go forward one word */
+ while (IsWordChar(UCH(Buffer[EditAt])))
+ EditAt++;
+ while (!IsWordChar(Buffer[EditAt]) && Buffer[EditAt])
+ EditAt++;
+ break;
+
+ case LYE_ERASE: /* erase the line */
+ Buffer[0] = '\0';
+#ifdef ENHANCED_LINEEDIT
+ EditMark = -1; /* Do not show the mark */
+#endif
+ /* FALLTHRU */
+
+ case LYE_BOL: /* go to beginning of line */
+ EditAt = 0;
+ break;
+
+ case LYE_EOL: /* go to end of line */
+ EditAt = length;
+ break;
+
+ case LYE_DELNW: /* delete next word */
+ offset = EditAt;
+ LYDoEdit(edit, 0, LYE_FORWW, FALSE);
+ offset = EditAt - offset;
+ EditAt -= offset;
+
+ goto shrink; /* right below */
+
+ case LYE_DELPW: /* delete previous word */
+ offset = EditAt;
+ LYDoEdit(edit, 0, LYE_BACKW, FALSE);
+ offset -= EditAt;
+
+ shrink:
+ for (i = EditAt; i < length - offset + 1; i++)
+ Buffer[i] = Buffer[i + offset];
+#ifdef ENHANCED_LINEEDIT
+ disableEditMark();
+ if (EditMark <= -(1 + EditAt + offset))
+ EditMark += offset; /* Shift it */
+ if (-(1 + EditAt + offset) < EditMark && EditMark < -(1 + EditAt))
+ EditMark = -(1 + EditAt); /* Set to the current position */
+#endif
+
+ break;
+
+ case LYE_DELBL: /* delete from cursor to beginning of line */
+ for (i = EditAt; i < length + 1; i++)
+ Buffer[i - EditAt] = Buffer[i];
+
+#ifdef ENHANCED_LINEEDIT
+ disableEditMark();
+ if (EditMark <= -(1 + EditAt))
+ EditMark += EditAt; /* Shift it */
+ else
+ EditMark = -1; /* Reset it */
+#endif
+ EditAt = 0;
+ break;
+
+ case LYE_DELEL: /* delete from cursor to end of line */
+ Buffer[EditAt] = '\0';
+#ifdef ENHANCED_LINEEDIT
+ disableEditMark();
+ if (EditMark <= -(1 + EditAt))
+ EditMark = -1; /* Reset it */
+#endif
+ break;
+
+ case LYE_DELN: /* delete next character */
+ if (EditAt >= length)
+ break;
+#ifndef SUPPORT_MULTIBYTE_EDIT
+ EditAt++;
+#else
+ EditAt += mbcs_skip(Buffer + EditAt, 1);
+#endif
+ /* FALLTHRU */
+
+ case LYE_DELP: /* delete previous character */
+ if (length == 0 || EditAt == 0)
+ break;
+
+#ifndef SUPPORT_MULTIBYTE_EDIT
+#ifdef ENHANCED_LINEEDIT
+ disableEditMark();
+ if (EditMark <= -(1 + EditAt))
+ EditMark++;
+#endif
+ EditAt--;
+ for (i = EditAt; i < length; i++)
+ Buffer[i] = Buffer[i + 1];
+#else /* SUPPORT_MULTIBYTE_EDIT */
+ offset = EditAt - mbcs_skip(Buffer, mbcs_glyphs(Buffer, EditAt) - 1);
+ EditAt -= offset;
+ for (i = EditAt; i < length - offset + 1; i++)
+ Buffer[i] = Buffer[i + offset];
+
+#ifdef ENHANCED_LINEEDIT
+ disableEditMark();
+ if (EditMark <= -(1 + EditAt))
+ EditMark += offset; /* Shift it */
+#endif
+
+#endif /* SUPPORT_MULTIBYTE_EDIT */
+ break;
+
+ case LYE_FORW_RL:
+ case LYE_FORW: /* move cursor forward */
+#ifndef SUPPORT_MULTIBYTE_EDIT
+ if (EditAt < length)
+ EditAt++;
+#else
+ if (EditAt < length)
+ EditAt += mbcs_skip(Buffer + EditAt, 1);
+#endif
+ else if (action == LYE_FORW_RL)
+ return -ch;
+ break;
+
+ case LYE_BACK_LL:
+ case LYE_BACK: /* move cursor backward */
+#ifndef SUPPORT_MULTIBYTE_EDIT
+ if (EditAt > 0)
+ EditAt--;
+#else
+ if (EditAt > 0)
+ EditAt = mbcs_skip(Buffer, mbcs_glyphs(Buffer, EditAt) - 1);
+#endif
+ else if (action == LYE_BACK_LL)
+ return -ch;
+ break;
+
+#ifdef ENHANCED_LINEEDIT
+ case LYE_TPOS:
+ /*
+ * Transpose characters - bash or ksh(emacs not gmacs) style
+ */
+
+#ifdef SUPPORT_MULTIBYTE_EDIT
+ if (IS_UTF8_TTY || IS_CJK_TTY)
+ break; /* Can't help it now */
+#endif
+
+ if (length <= 1 || EditAt == 0)
+ return (ch);
+ if (EditAt == length)
+ EditAt--;
+ enableEditMark();
+ if (EditMark == EditAt || EditMark == EditAt + 1)
+ EditMark = EditAt - 1;
+ disableEditMark();
+ if (Buffer[EditAt - 1] == Buffer[EditAt]) {
+ EditAt++;
+ break;
+ }
+ i = Buffer[EditAt - 1];
+ Buffer[EditAt - 1] = Buffer[EditAt];
+ Buffer[EditAt++] = (char) i;
+ break;
+
+ case LYE_SETMARK: /* Emacs-like set-mark-command */
+ EditMark = EditAt;
+ return (0);
+
+ case LYE_XPMARK: /* Emacs-like exchange-point-and-mark */
+ enableEditMark();
+ if (EditMark == EditAt)
+ return (0);
+ i = EditAt;
+ EditAt = EditMark;
+ EditMark = i;
+ break;
+
+ case LYE_KILLREG: /* Emacs-like kill-region */
+ enableEditMark();
+ if (EditMark == EditAt) {
+ BStrFree(killbuffer);
+ return (0);
+ }
+ if (EditMark > EditAt)
+ LYDoEdit(edit, 0, LYE_XPMARK, FALSE);
+ {
+ int reglen = EditAt - EditMark;
+
+ BStrCopy1(killbuffer, Buffer + EditMark, reglen);
+ for (i = EditMark; Buffer[i + reglen]; i++)
+ Buffer[i] = Buffer[i + reglen];
+ Buffer[i] = Buffer[i + reglen]; /* terminate */
+ EditAt = EditMark;
+ }
+ disableEditMark();
+ break;
+
+ case LYE_YANK: /* Emacs-like yank */
+ if (!killbuffer) {
+ EditMark = -(1 + EditAt);
+ return (0);
+ } else {
+ int yanklen = killbuffer->len;
+
+ if ((EditAt + yanklen) <= (int) BufAlloc &&
+ BufInUse + (size_t) yanklen <= BufAlloc) {
+
+ ExtendEditor(edit, EditAt + yanklen);
+
+ EditMark = -(1 + EditAt);
+
+ for (i = length; i >= EditAt; i--) /* Make room */
+ Buffer[i + yanklen] = Buffer[i];
+ for (i = 0; i < yanklen; i++)
+ Buffer[EditAt++] = killbuffer->str[i];
+
+ } else if (maxMessage) {
+ _statusline(MAXLEN_REACHED_DEL_OR_MOV);
+ }
+ }
+ break;
+
+#endif /* ENHANCED_LINEEDIT */
+
+ case LYE_UPPER:
+ LYUpperCase(Buffer);
+ break;
+
+ case LYE_LOWER:
+ LYLowerCase(Buffer);
+ break;
+
+ default:
+ return (ch);
+ }
+ IsDirty = TRUE;
+ BufInUse = strlen(&Buffer[0]);
+ return (0);
+}
+
+/*
+ * This function prompts for a choice or page number.
+ * If a 'g' or 'p' suffix is included, that will be
+ * loaded into c. Otherwise, c is zeroed. - FM & LE
+ */
+int get_popup_number(const char *msg,
+ int *c,
+ int *rel)
+{
+ bstring *temp = NULL;
+ int result = 0;
+
+ /*
+ * Load the c argument into the prompt buffer.
+ */
+ BStrCopy0(temp, "?");
+ temp->str[0] = (char) *c;
+
+ _statusline(msg);
+
+ /*
+ * Get the number, possibly with a suffix, from the user.
+ */
+ if (LYgetBString(&temp, FALSE, 0, NORECALL) < 0 || isBEmpty(temp)) {
+ HTInfoMsg(CANCELLED);
+ *c = '\0';
+ *rel = '\0';
+ } else {
+ char *p = temp->str;
+
+ *rel = '\0';
+ result = atoi(p);
+ while (isdigit(UCH(*p)))
+ ++p;
+ switch (*p) {
+ case '+':
+ case '-':
+ /* 123+ or 123- */
+ *rel = *p++;
+ *c = *p;
+ break;
+ default:
+ *c = *p++;
+ *rel = *p;
+ break;
+ case 0:
+ break;
+ }
+
+ /*
+ * If we had a 'g' or 'p' suffix, load it into c. Otherwise, zero c. Then
+ * return the number.
+ */
+ if (*p == 'g' || *p == 'G') {
+ *c = 'g';
+ } else if (*p == 'p' || *p == 'P') {
+ *c = 'p';
+ } else {
+ *c = '\0';
+ }
+ if (*rel != '+' && *rel != '-')
+ *rel = 0;
+ }
+ BStrFree(temp);
+ return result;
+}
+
+#ifdef USE_COLOR_STYLE
+# define TmpStyleOn(s) curses_style((s), STACK_ON)
+# define TmpStyleOff(s) curses_style((s), STACK_OFF)
+#else
+# define TmpStyleOn(s)
+# define TmpStyleOff(s)
+#endif /* defined USE_COLOR_STYLE */
+
+static void remember_column(FieldEditor * edit, int offset)
+{
+ int y0, x0;
+
+#if defined(USE_SLANG)
+ y0 = 0;
+ x0 = SLsmg_get_column();
+#elif defined(USE_CURSES_PADS)
+ getyx(LYwin, y0, x0);
+#else
+ getyx(stdscr, y0, x0);
+#endif
+ Offs2Col[offset] = x0;
+
+ (void) y0;
+ (void) x0;
+}
+
+static void fill_edited_line(int prompting GCC_UNUSED, int length, int ch)
+{
+ int i;
+
+ TmpStyleOn(prompting ? s_prompt_edit_pad : s_aedit_pad);
+
+ for (i = 0; i < length; i++) {
+ LYaddch(UCH(ch));
+ }
+
+ TmpStyleOff(prompting ? s_prompt_edit_pad : s_aedit_pad);
+}
+
+/*
+ * Multibyte string display subroutine.
+ * FieldEditor fields retain their values as byte offsets.
+ * All external logic still works fine with byte values.
+ */
+void LYRefreshEdit(FieldEditor * edit)
+{
+ /* bytes and characters are not the same thing */
+#if defined(DEBUG_EDIT)
+ int all_bytes;
+#endif
+ int pos_bytes = EditAt;
+ int dpy_bytes;
+ int lft_bytes; /* base of string which is displayed */
+
+ /* cells refer to display-columns on the screen */
+ int all_cells; /* total of display-cells in Buffer */
+ int dpy_cells; /* number of cells which are displayed */
+ int lft_cells; /* number of cells before display (on left) */
+ int pos_cells; /* number of display-cells up to EditAt */
+
+#if defined(SUPPORT_MULTIBYTE_EDIT)
+ int dpy_chars;
+ int lft_chars;
+
+#if defined(DEBUG_EDIT)
+ int all_chars;
+ int pos_chars;
+#endif
+#endif
+
+ /* other data */
+ int i;
+ int padsize;
+ char *str;
+ int lft_shift = 0;
+ int rgt_shift = 0;
+
+#ifdef USE_COLOR_STYLE
+ int estyle;
+#endif
+ int prompting = 0;
+
+ (void) pos_bytes;
+
+ /*
+ * If we've made no changes, or if there is nothing to display, just leave.
+ */
+ if (!IsDirty || (DpyWidth == 0))
+ return;
+
+ CTRACE((tfp, "LYRefreshEdit:%s\n", Buffer));
+
+ IsDirty = FALSE;
+
+ BufInUse = strlen(&Buffer[0]);
+
+ all_cells = LYstrCells(Buffer);
+ pos_cells = LYstrExtent2(Buffer, EditAt);
+
+#if defined(SUPPORT_MULTIBYTE_EDIT) && defined(DEBUG_EDIT)
+ all_bytes = (int) BufInUse;
+ lft_chars = mbcs_glyphs(Buffer, DpyStart);
+ pos_chars = mbcs_glyphs(Buffer, EditAt);
+ all_chars = mbcs_glyphs(Buffer, all_bytes);
+#endif
+
+ /*
+ * Now we have:
+ * .--DpyWidth--.
+ * +---------+=============+-----------+
+ * | |M M| | (M=PanMargin)
+ * +---------+=============+-----------+
+ * 0 DpyStart BufInUse
+ *
+ * Insertion point can be anywhere between 0 and stringlength. Calculate
+ * a new display starting point.
+ *
+ * First, make Lynx scroll several columns at a time as needed when
+ * extending the string. Doing this helps with lowspeed connections.
+ */
+
+ lft_bytes = DpyStart;
+ lft_cells = LYstrExtent2(Buffer, DpyStart);
+
+ if ((lft_cells + DpyWidth) <= all_cells) {
+ if (pos_cells >= (lft_cells + DpyWidth) - PanMargin) {
+ lft_cells = (pos_cells - DpyWidth) + PanMargin;
+#ifdef SUPPORT_MULTIBYTE_EDIT
+ lft_chars = cell2char(Buffer, lft_cells);
+ lft_bytes = mbcs_skip(Buffer, lft_chars);
+#else
+ lft_bytes = lft_cells;
+#endif /* SUPPORT_MULTIBYTE_EDIT */
+ }
+ }
+
+ if (pos_cells < lft_cells + PanMargin) {
+ lft_cells = pos_cells - PanMargin;
+ if (lft_cells < 0)
+ lft_cells = 0;
+#ifdef SUPPORT_MULTIBYTE_EDIT
+ lft_chars = cell2char(Buffer, lft_cells);
+ lft_bytes = mbcs_skip(Buffer, lft_chars);
+#else
+ lft_bytes = lft_cells;
+#endif /* SUPPORT_MULTIBYTE_EDIT */
+ }
+
+ LYmove(StartY, StartX);
+
+ /*
+ * Draw the left scrolling-indicator now, to avoid the complication of
+ * overwriting part of a multicolumn character which may lie in the first
+ * position.
+ */
+ if (IsPanned && lft_cells) {
+ CTRACE_EDIT((tfp, "Draw left scroll-indicator\n"));
+ TmpStyleOn(prompting ? s_prompt_edit_arr : s_aedit_arr);
+ LYmove(StartY, StartX);
+ LYaddch(ACS_LARROW);
+ TmpStyleOff(prompting ? s_prompt_edit_arr : s_aedit_arr);
+ lft_shift = 1;
+ }
+
+ str = &Buffer[lft_bytes];
+ DpyStart = lft_bytes;
+
+ dpy_cells = all_cells - lft_cells;
+ CTRACE_EDIT((tfp, "Comparing dpy_cells %d > (%d - %d)\n",
+ dpy_cells, DpyWidth, lft_shift));
+ if (dpy_cells > (DpyWidth - lft_shift)) {
+ rgt_shift = 1;
+ dpy_cells = (DpyWidth - lft_shift - rgt_shift);
+ }
+ for (;;) {
+#ifdef SUPPORT_MULTIBYTE_EDIT
+ dpy_chars = cell2char(str, dpy_cells);
+ dpy_bytes = mbcs_skip(str, dpy_chars);
+#else
+ dpy_bytes = dpy_cells;
+#endif /* SUPPORT_MULTIBYTE_EDIT */
+ /*
+ * The last character on the display may be multicolumn, and if we take
+ * away a single cell for the right scroll-indicator, that would force
+ * us to display fewer characters. Check for that, and recompute.
+ */
+ if (rgt_shift && *str) {
+ int old_cells = dpy_cells;
+
+ dpy_cells = LYstrExtent2(str, dpy_bytes);
+ if (dpy_cells > old_cells)
+ dpy_cells = old_cells - 1;
+
+ CTRACE_EDIT((tfp, "Comparing cells %d vs %d\n", dpy_cells, old_cells));
+ if (dpy_cells < old_cells) {
+ CTRACE_EDIT((tfp, "Recomputing...\n"));
+ continue;
+ }
+ }
+ break;
+ }
+
+ CTRACE_EDIT((tfp, "BYTES left %2d pos %2d dpy %2d all %2d\n",
+ lft_bytes, pos_bytes, dpy_bytes, all_bytes));
+ CTRACE_EDIT((tfp, "CELLS left %2d pos %2d dpy %2d all %2d\n",
+ lft_cells, pos_cells, dpy_cells, all_cells));
+ CTRACE_EDIT((tfp, "CHARS left %2d pos %2d dpy %2d all %2d\n",
+ lft_chars, pos_chars, dpy_chars, all_chars));
+
+#ifdef USE_COLOR_STYLE
+ /*
+ * If this is the last screen line, set attributes to normal, should only
+ * be needed for color styles. The curses function may be used directly to
+ * avoid complications. - kw
+ */
+ if (StartY == (LYlines - 1))
+ prompting = 1;
+ if (prompting) {
+ estyle = s_prompt_edit;
+ } else {
+ estyle = s_aedit;
+ }
+ CTRACE2(TRACE_STYLE,
+ (tfp, "STYLE.getstr: switching to <edit.%s>.\n",
+ prompting ? "prompt" : "active"));
+ if (estyle != NOSTYLE) {
+ curses_style(estyle, STACK_ON);
+ } else {
+ (void) wattrset(LYwin, A_NORMAL); /* need to do something about colors? */
+ }
+#endif
+ if (IsHidden) {
+ BOOL utf_flag = IS_UTF8_TTY;
+ int cell = 0;
+
+ fill_edited_line(0, dpy_cells, '*');
+
+ i = 0;
+ do {
+ const char *last = str + i;
+ const char *next = LYmbcs_skip_glyphs(last, 1, utf_flag);
+ int j = (int) (next - str);
+
+ while (i < j) {
+ Offs2Col[i++] = cell + StartX;
+ }
+ cell += LYstrExtent2(last, (int) (next - last));
+ } while (i < dpy_bytes);
+ Offs2Col[i] = cell + StartX;
+ } else {
+#if defined(ENHANCED_LINEEDIT) && defined(USE_COLOR_STYLE)
+ if (EditMark >= 0 && DpyStart > EditMark)
+ TmpStyleOn(prompting ? s_prompt_sel : s_aedit_sel);
+#endif
+ remember_column(edit, 0);
+ for (i = 0; i < dpy_bytes; i++) {
+#if defined(ENHANCED_LINEEDIT) && defined(USE_COLOR_STYLE)
+ if (EditMark >= 0 && ((DpyStart + i == EditMark && EditAt > EditMark)
+ || (DpyStart + i == EditAt && EditAt < EditMark)))
+ TmpStyleOn(prompting ? s_prompt_sel : s_aedit_sel);
+ if (EditMark >= 0 && ((DpyStart + i == EditMark && EditAt < EditMark)
+ || (DpyStart + i == EditAt && EditAt > EditMark)))
+ TmpStyleOff(prompting ? s_prompt_sel : s_aedit_sel);
+#endif
+ if (str[i] == 1 || str[i] == 2 ||
+ (UCH(str[i]) == 160 &&
+ !(HTPassHighCtrlRaw || IS_CJK_TTY ||
+ (LYCharSet_UC[current_char_set].enc != UCT_ENC_8859 &&
+ !(LYCharSet_UC[current_char_set].like8859
+ & UCT_R_8859SPECL))))) {
+ LYaddch(' ');
+ } else if (str[i] == '\t') {
+ int col = Offs2Col[i] - StartX;
+
+ /*
+ * Like LYwaddnstr(), expand tabs from the beginning of the
+ * field.
+ */
+ while (++col % 8)
+ LYaddch(' ');
+ LYaddch(' ');
+ } else {
+ LYaddch(UCH(str[i]));
+ }
+ remember_column(edit, i + 1);
+ }
+#if defined(ENHANCED_LINEEDIT) && defined(USE_COLOR_STYLE)
+ if (EditMark >= 0 &&
+ ((DpyStart + dpy_bytes <= EditMark && DpyStart + dpy_bytes > EditAt)
+ || (DpyStart + dpy_bytes > EditMark
+ && DpyStart + dpy_bytes <= EditAt))) {
+ TmpStyleOff(prompting ? s_prompt_sel : s_aedit_sel);
+ }
+#endif
+ }
+
+ /*
+ * Erase rest of input area.
+ */
+ padsize = DpyWidth - (Offs2Col[dpy_bytes] - StartX);
+ fill_edited_line(prompting, padsize, PadChar);
+
+ /*
+ * Scrolling indicators.
+ */
+ if (IsPanned && dpy_bytes && rgt_shift) {
+ CTRACE((tfp, "Draw right-scroller offset (%d + %d)\n",
+ dpy_cells, lft_shift));
+ TmpStyleOn(prompting ? s_prompt_edit_arr : s_aedit_arr);
+ LYmove(StartY, StartX + dpy_cells + lft_shift);
+ LYaddch(ACS_RARROW);
+ TmpStyleOff(prompting ? s_prompt_edit_arr : s_aedit_arr);
+ }
+
+ /*
+ * Finally, move the cursor to the point where the next edit will occur.
+ */
+ LYmove(StartY, Offs2Col[EditAt - DpyStart]);
+
+#ifdef USE_COLOR_STYLE
+ if (estyle != NOSTYLE)
+ curses_style(estyle, STACK_OFF);
+#endif
+ LYrefresh();
+}
+
+static void reinsertEdit(FieldEditor * edit, char *result)
+{
+ if (result != 0) {
+ LYDoEdit(edit, '\0', LYE_ERASE, FALSE);
+ while (*result != '\0') {
+ LYLineEdit(edit, (int) (*result), FALSE);
+ result++;
+ }
+ }
+}
+
+static int caselessCmpList(const void *a,
+ const void *b)
+{
+ return strcasecomp(*(STRING2PTR) a, *(STRING2PTR) b);
+}
+
+static int normalCmpList(const void *a,
+ const void *b)
+{
+ return strcmp(*(STRING2PTR) a, *(STRING2PTR) b);
+}
+
+static char **sortedList(HTList *list, int ignorecase)
+{
+ size_t count = (unsigned) HTList_count(list);
+ size_t j = 0;
+ size_t k, jk;
+ char **result = typecallocn(char *, count + 1);
+
+ if (result == 0)
+ outofmem(__FILE__, "sortedList");
+
+ while (!HTList_isEmpty(list))
+ result[j++] = (char *) HTList_nextObject(list);
+
+ if (count > 1) {
+ qsort((char *) result, count, sizeof(*result),
+ ignorecase ? caselessCmpList : normalCmpList);
+
+ /* remove duplicate entries from the sorted index */
+ for (j = 0; result[j] != 0; j++) {
+ k = j;
+ while (result[k] != 0
+ && !strcmp(result[j], result[k])) {
+ k++;
+ }
+ k--;
+ if (j != k) {
+ for (jk = j;; jk++) {
+ result[jk] = result[jk + k - j];
+ if (result[jk] == 0)
+ break;
+ }
+ }
+ }
+ }
+
+ return result;
+}
+
+int LYarrayLength(STRING2PTR list)
+{
+ int result = 0;
+
+ while (*list++ != 0)
+ result++;
+ return result;
+}
+
+int LYarrayWidth(STRING2PTR list)
+{
+ int result = 0;
+ int check;
+
+ while (*list != 0) {
+ check = (int) strlen(*list++);
+ if (check > result)
+ result = check;
+ }
+ return result;
+}
+
+static void FormatChoiceNum(char *target,
+ int num_choices,
+ int choice,
+ const char *value)
+{
+ if (num_choices >= 0) {
+ int digits = (num_choices > 9) ? 2 : 1;
+
+ sprintf(target, "%*d: %.*s",
+ digits, (choice + 1),
+ MAX_LINE - 9 - digits, value);
+ } else {
+ LYStrNCpy(target, value, MAX_LINE - 1);
+ }
+}
+
+static unsigned options_width(STRING2PTR list)
+{
+ unsigned width = 0;
+ int count = 0;
+
+ while (list[count] != 0) {
+ unsigned ncells = (unsigned) LYstrCells(list[count]);
+
+ if (ncells > width) {
+ width = ncells;
+ }
+ count++;
+ }
+ return width;
+}
+
+static void draw_option(WINDOW * win, int entry,
+ int width,
+ int reversed,
+ int num_choices,
+ int number,
+ const char *value)
+{
+ char Cnum[MAX_LINE];
+
+ (void) width;
+
+ FormatChoiceNum(Cnum, num_choices, number, "");
+#ifdef USE_SLANG
+ SLsmg_gotorc(win->top_y + entry, (win->left_x + 2));
+ LYaddstr(Cnum);
+ if (reversed)
+ SLsmg_set_color(2);
+ SLsmg_write_nstring((SLFUTURE_CONST char *) value, (unsigned) win->width);
+ if (reversed)
+ SLsmg_set_color(0);
+#else
+ wmove(win, entry, 1);
+ LynxWChangeStyle(win, s_menu_entry, STACK_ON);
+ waddch(win, ' ');
+ LynxWChangeStyle(win, s_menu_entry, STACK_OFF);
+ LynxWChangeStyle(win, s_menu_number, STACK_ON);
+ waddstr(win, Cnum);
+ LynxWChangeStyle(win, s_menu_number, STACK_OFF);
+#ifdef USE_COLOR_STYLE
+ LynxWChangeStyle(win, reversed ? s_menu_active : s_menu_entry, STACK_ON);
+#else
+ if (reversed)
+ wstart_reverse(win);
+#endif
+ LYpaddstr(win, width, value);
+#ifdef USE_COLOR_STYLE
+ LynxWChangeStyle(win, reversed ? s_menu_active : s_menu_entry, STACK_OFF);
+#else
+ if (reversed)
+ wstop_reverse(win);
+#endif
+ LynxWChangeStyle(win, s_menu_entry, STACK_ON);
+ waddch(win, ' ');
+ LynxWChangeStyle(win, s_menu_entry, STACK_OFF);
+#endif /* USE_SLANG */
+}
+
+static void show_popup_status(int cur_choice,
+ STRING2PTR choices,
+ int disabled,
+ int for_mouse)
+{
+ if (disabled) {
+ _statusline(CHOICE_LIST_UNM_MSG);
+ } else if (!for_mouse) {
+ if (fields_are_named()) {
+ char *status_msg = NULL;
+
+ HTSprintf0(&status_msg, CHOICE_LIST_ADV_MSG, choices[cur_choice]);
+ _statusline(status_msg);
+ FREE(status_msg);
+ } else {
+ _statusline(CHOICE_LIST_MESSAGE);
+ }
+#if defined(USE_MOUSE) && (defined(NCURSES) || defined(PDCURSES))
+ } else {
+ _statusline(MOUSE_CHOICE_MESSAGE);
+#endif
+ }
+}
+
+#define SHOW_POPUP_STATUS() show_popup_status(cur_choice, choices, disabled, for_mouse)
+
+/*
+ * 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 LYhandlePopupList(int cur_choice,
+ int ly,
+ int lx,
+ STRING2PTR choices,
+ int width,
+ int i_length,
+ int disabled,
+ int for_mouse)
+{
+ BOOLEAN numbered = (BOOLEAN) (keypad_mode != NUMBERS_AS_ARROWS);
+ int c = 0, cmd = 0, i = 0, j = 0, rel = 0;
+ int orig_choice;
+ WINDOW *form_window;
+ int num_choices = 0;
+ int max_choices = 0;
+ int top, bottom, length = -1;
+ int window_offset = 0;
+ int lines_to_show;
+ char Cnum[64];
+ int Lnum;
+ int npages;
+ static bstring *prev_target = NULL; /* Search string buffer */
+ static bstring *next_target = NULL; /* Next search buffer */
+ static BOOL first = TRUE;
+ char *cp;
+ int ch = 0;
+ RecallType recall;
+ int QueryTotal;
+ int QueryNum;
+ BOOLEAN FirstRecall = TRUE;
+ BOOLEAN ReDraw = FALSE;
+ int number;
+ char buffer[MAX_LINE];
+ STRING2PTR Cptr = NULL;
+
+#define CAN_SCROLL_DOWN 1
+#define CAN_SCROLL_UP 2
+#define CAN_SCROLL 4
+ int can_scroll = 0, can_scroll_was = 0;
+
+ orig_choice = cur_choice;
+ if (cur_choice < 0)
+ cur_choice = 0;
+
+ /*
+ * Initialize the search string buffer. - FM
+ */
+ if (first) {
+ BStrCopy0(next_target, "");
+ first = FALSE;
+ }
+ BStrCopy0(prev_target, "");
+ QueryTotal = (search_queries ? HTList_count(search_queries) : 0);
+ recall = ((QueryTotal >= 1) ? RECALL_URL : NORECALL);
+ QueryNum = QueryTotal;
+
+ /*
+ * Count the number of choices to be displayed, where num_choices ranges
+ * from 0 to n, and set width to the longest choice string length. Also
+ * set Lnum to the length for the highest choice number, then decrement
+ * num_choices so as to be zero-based. The window width will be based on
+ * the sum of width and Lnum. - FM
+ */
+ num_choices = LYarrayLength(choices) - 1;
+ if (width <= 0)
+ width = (int) options_width(choices);
+ if (numbered) {
+ sprintf(Cnum, "%d: ", num_choices);
+ Lnum = (int) strlen(Cnum);
+ max_choices = num_choices;
+ } else {
+ Lnum = 0;
+ max_choices = -1;
+ }
+
+ /*
+ * Let's assume for the sake of sanity that ly is the number corresponding
+ * to the line the choice is on.
+ *
+ * Let's also assume that cur_choice is the number of the item that should
+ * be initially selected, as 0 being the first item.
+ *
+ * So what we have, is the top equal to the current screen line subtracting
+ * the cur_choice + 1 (the one must be for the top line we will draw in a
+ * box). If the top goes under 0, consider it 0.
+ */
+ top = ly - (cur_choice + 1);
+ if (top < 0)
+ top = 0;
+
+ /*
+ * Check and see if we need to put the i_length parameter up to the number
+ * of real choices.
+ */
+ if (i_length < 1) {
+ i_length = num_choices;
+ } else {
+ /*
+ * Otherwise, it is really one number too high.
+ */
+ i_length--;
+ }
+
+ /*
+ * The bottom is the value of the top plus the number of options to view
+ * plus 3 (one for the top line, one for the bottom line, and one to offset
+ * the 0 counted in the num_choices).
+ */
+ bottom = top + i_length + 3;
+
+ /*
+ * Set lines_to_show based on the user_mode global.
+ */
+ if (user_mode == NOVICE_MODE)
+ lines_to_show = LYlines - 4;
+ else
+ lines_to_show = LYlines - 2;
+
+ if (for_mouse && user_mode == NOVICE_MODE && lines_to_show > 2)
+ lines_to_show--;
+
+ /*
+ * Hmm... If the bottom goes beyond the number of lines available,
+ */
+ if (bottom > lines_to_show) {
+ /*
+ * Position the window at the top if we have more choices than will fit
+ * in the window.
+ */
+ if ((i_length + 3) > lines_to_show) {
+ top = 0;
+ bottom = (top + (i_length + 3));
+ if (bottom > lines_to_show)
+ bottom = (lines_to_show + 1);
+ } else {
+ /*
+ * Try to position the window so that the selected choice will
+ * appear where the selection box currently is positioned. It
+ * could end up too high, at this point, but we'll move it down
+ * latter, if that has happened.
+ */
+ top = (lines_to_show + 1) - (i_length + 3);
+ bottom = (lines_to_show + 1);
+ }
+ }
+
+ /*
+ * This is really fun, when the length is 4, it means 0 to 4, or 5.
+ */
+ length = (bottom - top) - 2;
+ if (length <= num_choices)
+ can_scroll = CAN_SCROLL;
+
+ /*
+ * Move the window down if it's too high.
+ */
+ if (bottom < ly + 2) {
+ bottom = ly + 2;
+ if (bottom > lines_to_show + 1)
+ bottom = lines_to_show + 1;
+ top = bottom - length - 2;
+ }
+
+ if (for_mouse) {
+ int check = (Lnum + (int) width + 4);
+ int limit = LYcols;
+
+ /* shift horizontally to lie within screen width, if possible */
+ if (check < limit) {
+ if (lx - 1 + check > limit)
+ lx = limit + 1 - check;
+ else if (lx <= 0)
+ lx = 1;
+ }
+ }
+
+ /*
+ * Set up the overall window, including the boxing characters ('*'), if it
+ * all fits. Otherwise, set up the widest window possible. - FM
+ */
+ width += Lnum;
+ bottom -= top;
+
+ if (num_choices <= 0
+ || cur_choice > num_choices
+ || (form_window = LYstartPopup(&top,
+ &lx,
+ &bottom,
+ &width)) == 0)
+ return (orig_choice);
+
+ width -= Lnum;
+ bottom += top;
+
+ SHOW_POPUP_STATUS();
+
+ /*
+ * Set up the window_offset for choices.
+ * cur_choice ranges from 0...n
+ * length ranges from 0...m
+ */
+ if (cur_choice >= length) {
+ window_offset = cur_choice - length + 1;
+ }
+
+ /*
+ * Compute the number of popup window pages. - FM
+ */
+ npages = ((num_choices + 1) > length) ?
+ (((num_choices + 1) + (length - 1)) / (length))
+ : 1;
+ /*
+ * OH! I LOVE GOTOs! hack hack hack
+ */
+ redraw:
+
+ /*
+ * Display the boxed choices.
+ */
+ for (i = 0; i <= num_choices; i++) {
+ if (i >= window_offset && i - window_offset < length) {
+ draw_option(form_window, ((i + 1) - window_offset), width, FALSE,
+ max_choices, i, choices[i]);
+ }
+ }
+ LYbox(form_window, !numbered);
+ Cptr = NULL;
+
+ /*
+ * Loop on user input.
+ */
+ while (cmd != LYK_ACTIVATE) {
+ int row = ((i + 1) - window_offset);
+
+ /* Show scroll indicators. */
+ if (can_scroll) {
+ can_scroll = ((window_offset ? CAN_SCROLL_UP : 0)
+ | (num_choices - window_offset >= length
+ ? CAN_SCROLL_DOWN : 0));
+ if (~can_scroll & can_scroll_was) { /* Need to redraw */
+ LYbox(form_window, !numbered);
+ can_scroll_was = 0;
+ }
+ if (can_scroll & ~can_scroll_was & CAN_SCROLL_UP) {
+ wmove(form_window, 1, Lnum + width + 3);
+ LynxWChangeStyle(form_window, s_menu_sb, STACK_ON);
+ waddch(form_window, ACS_UARROW);
+ LynxWChangeStyle(form_window, s_menu_sb, STACK_OFF);
+ }
+ if (can_scroll & ~can_scroll_was & CAN_SCROLL_DOWN) {
+ wmove(form_window, length, Lnum + width + 3);
+ LynxWChangeStyle(form_window, s_menu_sb, STACK_ON);
+ waddch(form_window, ACS_DARROW);
+ LynxWChangeStyle(form_window, s_menu_sb, STACK_OFF);
+ }
+ }
+
+ /*
+ * Unreverse cur choice.
+ */
+ if (Cptr != NULL) {
+ draw_option(form_window, row, width, FALSE,
+ max_choices, i, Cptr[i]);
+ }
+ Cptr = choices;
+ i = cur_choice;
+ row = ((cur_choice + 1) - window_offset);
+ draw_option(form_window, row, width, TRUE,
+ max_choices, cur_choice, Cptr[cur_choice]);
+ LYstowCursor(form_window, row, 1);
+
+ c = LYgetch_choice();
+ if (term_options || LYCharIsINTERRUPT(c)) { /* Control-C or Control-G */
+ cmd = LYK_QUIT;
+#ifndef USE_SLANG
+ } else if (c == MOUSE_KEY) {
+ if ((cmd = fancy_mouse(form_window, row, &cur_choice)) < 0)
+ goto redraw;
+ if (cmd == LYK_ACTIVATE)
+ break;
+#endif
+ } else {
+ cmd = LKC_TO_LAC(keymap, c);
+ }
+#ifdef VMS
+ if (HadVMSInterrupt) {
+ HadVMSInterrupt = FALSE;
+ cmd = LYK_QUIT;
+ }
+#endif /* VMS */
+
+ switch (cmd) {
+ 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:
+ /*
+ * Get a number from the user, possibly with a 'g' or 'p' suffix
+ * (which will be loaded into c). - FM & LE
+ */
+ number = get_popup_number(SELECT_OPTION_NUMBER, &c, &rel);
+
+ /* handle + or - suffix */
+ CTRACE((tfp, "got popup option number %d, ", number));
+ CTRACE((tfp, "rel='%c', c='%c', cur_choice=%d\n",
+ rel, c, cur_choice));
+ if (c == 'p') {
+ int curpage = ((cur_choice + 1) > length) ?
+ (((cur_choice + 1) + (length - 1)) / (length))
+ : 1;
+
+ CTRACE((tfp, " curpage=%d\n", curpage));
+ if (rel == '+')
+ number = curpage + number;
+ else if (rel == '-')
+ number = curpage - number;
+ } else if (rel == '+') {
+ number = cur_choice + number + 1;
+ } else if (rel == '-') {
+ number = cur_choice - number + 1;
+ }
+ if (rel)
+ CTRACE((tfp, "new number=%d\n", number));
+ /*
+ * Check for a 'p' suffix. - FM
+ */
+ if (c == 'p') {
+ /*
+ * Treat 1 or less as the first page. - FM
+ */
+ if (number <= 1) {
+ if (window_offset == 0) {
+ HTUserMsg(ALREADY_AT_OPTION_BEGIN);
+ SHOW_POPUP_STATUS();
+ break;
+ }
+ window_offset = 0;
+ cur_choice = 0;
+ SHOW_POPUP_STATUS();
+ goto redraw;
+ }
+
+ /*
+ * Treat a number equal to or greater than the number of pages
+ * as the last page. - FM
+ */
+ if (number >= npages) {
+ if (window_offset >= ((num_choices - length) + 1)) {
+ HTUserMsg(ALREADY_AT_OPTION_END);
+ SHOW_POPUP_STATUS();
+ break;
+ }
+ window_offset = ((npages - 1) * length);
+ if (window_offset > (num_choices - length)) {
+ window_offset = (num_choices - length + 1);
+ }
+ if (cur_choice < window_offset)
+ cur_choice = window_offset;
+ SHOW_POPUP_STATUS();
+ goto redraw;
+ }
+
+ /*
+ * We want an intermediate page. - FM
+ */
+ if (((number - 1) * length) == window_offset) {
+ char *msg = 0;
+
+ HTSprintf0(&msg, ALREADY_AT_OPTION_PAGE, number);
+ HTUserMsg(msg);
+ FREE(msg);
+ SHOW_POPUP_STATUS();
+ break;
+ }
+ cur_choice = window_offset = ((number - 1) * length);
+ SHOW_POPUP_STATUS();
+ goto redraw;
+
+ }
+
+ /*
+ * Check for a positive number, which signifies that a choice
+ * should be sought. - FM
+ */
+ if (number > 0) {
+ /*
+ * Decrement the number so as to correspond with our cur_choice
+ * values. - FM
+ */
+ number--;
+
+ /*
+ * If the number is in range and had no legal suffix, select
+ * the indicated choice. - FM
+ */
+ if (number <= num_choices && c == '\0') {
+ cur_choice = number;
+ cmd = LYK_ACTIVATE;
+ break;
+ }
+
+ /*
+ * Verify that we had a 'g' suffix, and act on the number. -
+ * FM
+ */
+ if (c == 'g') {
+ if (cur_choice == number) {
+ /*
+ * The choice already is current. - FM
+ */
+ char *msg = 0;
+
+ HTSprintf0(&msg, OPTION_ALREADY_CURRENT, (number + 1));
+ HTUserMsg(msg);
+ FREE(msg);
+ SHOW_POPUP_STATUS();
+ break;
+ }
+
+ if (number <= num_choices) {
+ /*
+ * The number is in range and had a 'g' suffix, so make
+ * it the current option, scrolling if needed. - FM
+ */
+ j = (number - cur_choice);
+ cur_choice = number;
+ if ((j > 0) &&
+ (cur_choice - window_offset) >= length) {
+ window_offset += j;
+ if (window_offset > (num_choices - length + 1))
+ window_offset = (num_choices - length + 1);
+ } else if ((cur_choice - window_offset) < 0) {
+ window_offset -= abs(j);
+ if (window_offset < 0)
+ window_offset = 0;
+ }
+ SHOW_POPUP_STATUS();
+ goto redraw;
+ }
+
+ /*
+ * Not in range. - FM
+ */
+ HTUserMsg(BAD_OPTION_NUM_ENTERED);
+ }
+ }
+
+ /*
+ * Restore the popup statusline. - FM
+ */
+ SHOW_POPUP_STATUS();
+ break;
+
+ case LYK_PREV_LINK:
+ case LYK_LPOS_PREV_LINK:
+ case LYK_FASTBACKW_LINK:
+ case LYK_UP_LINK:
+
+ if (cur_choice > 0)
+ cur_choice--;
+
+ /*
+ * Scroll the window up if necessary.
+ */
+ if ((cur_choice - window_offset) < 0) {
+ window_offset--;
+ goto redraw;
+ }
+ break;
+
+ case LYK_NEXT_LINK:
+ case LYK_LPOS_NEXT_LINK:
+ case LYK_FASTFORW_LINK:
+ case LYK_DOWN_LINK:
+ if (cur_choice < num_choices)
+ cur_choice++;
+
+ /*
+ * Scroll the window down if necessary
+ */
+ if ((cur_choice - window_offset) >= length) {
+ window_offset++;
+ goto redraw;
+ }
+ break;
+
+ case LYK_NEXT_PAGE:
+ /*
+ * Okay, are we on the last page of the list? If not then,
+ */
+ if (window_offset != (num_choices - length + 1)) {
+ /*
+ * Modify the current choice to not be a coordinate in the
+ * list, but a coordinate on the item selected in the window.
+ */
+ cur_choice -= window_offset;
+
+ /*
+ * Page down the proper length for the list. If simply to far,
+ * back up.
+ */
+ window_offset += length;
+ if (window_offset > (num_choices - length)) {
+ window_offset = (num_choices - length + 1);
+ }
+
+ /*
+ * Readjust the current selection to be a list coordinate
+ * rather than window. Redraw this thing.
+ */
+ cur_choice += window_offset;
+ goto redraw;
+ } else if (cur_choice < num_choices) {
+ /*
+ * Already on last page of the list so just redraw it with the
+ * last item selected.
+ */
+ cur_choice = num_choices;
+ }
+ break;
+
+ case LYK_PREV_PAGE:
+ /*
+ * Are we on the first page of the list? If not then,
+ */
+ if (window_offset != 0) {
+ /*
+ * Modify the current selection to not be a list coordinate,
+ * but a window coordinate.
+ */
+ cur_choice -= window_offset;
+
+ /*
+ * Page up the proper length. If too far, back up.
+ */
+ window_offset -= length;
+ if (window_offset < 0) {
+ window_offset = 0;
+ }
+
+ /*
+ * Readjust the current choice.
+ */
+ cur_choice += window_offset;
+ goto redraw;
+ } else if (cur_choice > 0) {
+ /*
+ * Already on the first page so just back up to the first item.
+ */
+ cur_choice = 0;
+ }
+ break;
+
+ case LYK_HOME:
+ cur_choice = 0;
+ if (window_offset > 0) {
+ window_offset = 0;
+ goto redraw;
+ }
+ break;
+
+ case LYK_END:
+ cur_choice = num_choices;
+ if (window_offset != (num_choices - length + 1)) {
+ window_offset = (num_choices - length + 1);
+ goto redraw;
+ }
+ break;
+
+ case LYK_DOWN_TWO:
+ cur_choice += 2;
+ if (cur_choice > num_choices)
+ cur_choice = num_choices;
+
+ /*
+ * Scroll the window down if necessary.
+ */
+ if ((cur_choice - window_offset) >= length) {
+ window_offset += 2;
+ if (window_offset > (num_choices - length + 1))
+ window_offset = (num_choices - length + 1);
+ goto redraw;
+ }
+ break;
+
+ case LYK_UP_TWO:
+ cur_choice -= 2;
+ if (cur_choice < 0)
+ cur_choice = 0;
+
+ /*
+ * Scroll the window up if necessary.
+ */
+ if ((cur_choice - window_offset) < 0) {
+ window_offset -= 2;
+ if (window_offset < 0)
+ window_offset = 0;
+ goto redraw;
+ }
+ break;
+
+ case LYK_DOWN_HALF:
+ cur_choice += (length / 2);
+ if (cur_choice > num_choices)
+ cur_choice = num_choices;
+
+ /*
+ * Scroll the window down if necessary.
+ */
+ if ((cur_choice - window_offset) >= length) {
+ window_offset += (length / 2);
+ if (window_offset > (num_choices - length + 1))
+ window_offset = (num_choices - length + 1);
+ goto redraw;
+ }
+ break;
+
+ case LYK_UP_HALF:
+ cur_choice -= (length / 2);
+ if (cur_choice < 0)
+ cur_choice = 0;
+
+ /*
+ * Scroll the window up if necessary.
+ */
+ if ((cur_choice - window_offset) < 0) {
+ window_offset -= (length / 2);
+ if (window_offset < 0)
+ window_offset = 0;
+ goto redraw;
+ }
+ break;
+
+ case LYK_REFRESH:
+ lynx_force_repaint();
+ LYrefresh();
+ break;
+
+ case LYK_NEXT:
+ if (recall && isBEmpty(next_target)) {
+ /*
+ * We got a 'n'ext command with no prior query specified within
+ * the popup window. See if one was entered when the popup was
+ * retracted, and if so, assume that's what's wanted. Note
+ * that it will become the default within popups, unless
+ * another is entered within a popup. If the within popup
+ * default is to be changed at that point, use WHEREIS ('/')
+ * and enter it, or the up- or down-arrow keys to seek any of
+ * the previously entered queries, regardless of whether they
+ * were entered within or outside of a popup window. - FM
+ */
+ if ((cp = (char *) HTList_objectAt(search_queries,
+ 0)) != NULL) {
+ BStrCopy0(next_target, cp);
+ QueryNum = 0;
+ FirstRecall = FALSE;
+ }
+ }
+ BStrCopy(prev_target, next_target);
+ /* FALLTHRU */
+ case LYK_WHEREIS:
+ if (isBEmpty(prev_target)) {
+ _statusline(ENTER_WHEREIS_QUERY);
+ if ((ch = LYgetBString(&prev_target, FALSE, 0, recall)) < 0) {
+ /*
+ * User cancelled the search via ^G. - FM
+ */
+ HTInfoMsg(CANCELLED);
+ goto restore_popup_statusline;
+ }
+ }
+
+ check_recall:
+ if (isBEmpty(prev_target) &&
+ !(recall && (ch == UPARROW_KEY || ch == DNARROW_KEY))) {
+ /*
+ * No entry. Simply break. - FM
+ */
+ HTInfoMsg(CANCELLED);
+ goto restore_popup_statusline;
+ }
+
+ if (recall && ch == UPARROW_KEY) {
+ if (FirstRecall) {
+ /*
+ * Use the current string or last query in the list. - FM
+ */
+ FirstRecall = FALSE;
+ if (!isBEmpty(next_target)) {
+ for (QueryNum = (QueryTotal - 1);
+ QueryNum > 0; QueryNum--) {
+ if ((cp = (char *) HTList_objectAt(search_queries,
+ QueryNum))
+ != NULL &&
+ !strcmp(next_target->str, cp)) {
+ break;
+ }
+ }
+ } else {
+ QueryNum = 0;
+ }
+ } 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(prev_target, cp);
+ if (!isBEmpty(next_target) &&
+ !strcmp(next_target->str, prev_target->str)) {
+ _statusline(EDIT_CURRENT_QUERY);
+ } else if ((!isBEmpty(next_target) && QueryTotal == 2) ||
+ (isBEmpty(next_target) && QueryTotal == 1)) {
+ _statusline(EDIT_THE_PREV_QUERY);
+ } else {
+ _statusline(EDIT_A_PREV_QUERY);
+ }
+ if ((ch = LYgetBString(&prev_target,
+ FALSE, 0, recall)) < 0) {
+ /*
+ * User cancelled the search via ^G. - FM
+ */
+ HTInfoMsg(CANCELLED);
+ goto restore_popup_statusline;
+ }
+ goto check_recall;
+ }
+ } else if (recall && ch == DNARROW_KEY) {
+ if (FirstRecall) {
+ /*
+ * Use the current string or first query in the list. - FM
+ */
+ FirstRecall = FALSE;
+ if (!isBEmpty(next_target)) {
+ for (QueryNum = 0;
+ QueryNum < (QueryTotal - 1); QueryNum++) {
+ if ((cp = (char *) HTList_objectAt(search_queries,
+ QueryNum))
+ != NULL &&
+ !strcmp(next_target->str, cp)) {
+ break;
+ }
+ }
+ } else {
+ QueryNum = (QueryTotal - 1);
+ }
+ } 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(prev_target, cp);
+ if (isBEmpty(next_target) &&
+ !strcmp(next_target->str, prev_target->str)) {
+ _statusline(EDIT_CURRENT_QUERY);
+ } else if ((!isBEmpty(next_target) && QueryTotal == 2) ||
+ (isBEmpty(next_target) && QueryTotal == 1)) {
+ _statusline(EDIT_THE_PREV_QUERY);
+ } else {
+ _statusline(EDIT_A_PREV_QUERY);
+ }
+ if ((ch = LYgetBString(&prev_target,
+ FALSE, 0, recall)) < 0) {
+ /*
+ * User cancelled the search via ^G. - FM
+ */
+ HTInfoMsg(CANCELLED);
+ goto restore_popup_statusline;
+ }
+ goto check_recall;
+ }
+ }
+ /*
+ * Replace the search string buffer with the new target. - FM
+ */
+ BStrCopy(next_target, prev_target);
+ HTAddSearchQuery(next_target->str);
+
+ /*
+ * Start search at the next choice. - FM
+ */
+ for (j = 1; Cptr[i + j] != NULL; j++) {
+ FormatChoiceNum(buffer, max_choices, (i + j), Cptr[i + j]);
+ if (LYcase_sensitive) {
+ if (strstr(buffer, next_target->str) != NULL)
+ break;
+ } else {
+ if (LYstrstr(buffer, next_target->str) != NULL)
+ break;
+ }
+ }
+ if (Cptr[i + j] != NULL) {
+ /*
+ * We have a hit, so make that choice the current. - FM
+ */
+ cur_choice += j;
+ /*
+ * Scroll the window down if necessary.
+ */
+ if ((cur_choice - window_offset) >= length) {
+ window_offset += j;
+ if (window_offset > (num_choices - length + 1))
+ window_offset = (num_choices - length + 1);
+ ReDraw = TRUE;
+ }
+ goto restore_popup_statusline;
+ }
+
+ /*
+ * If we started at the beginning, it can't be present. - FM
+ */
+ if (cur_choice == 0) {
+ HTUserMsg2(STRING_NOT_FOUND, next_target->str);
+ goto restore_popup_statusline;
+ }
+
+ /*
+ * Search from the beginning to the current choice. - FM
+ */
+ for (j = 0; j < cur_choice; j++) {
+ FormatChoiceNum(buffer, max_choices, (j + 1), Cptr[j]);
+ if (LYcase_sensitive) {
+ if (strstr(buffer, next_target->str) != NULL)
+ break;
+ } else {
+ if (LYstrstr(buffer, next_target->str) != NULL)
+ break;
+ }
+ }
+ if (j < cur_choice) {
+ /*
+ * We have a hit, so make that choice the current. - FM
+ */
+ j = (cur_choice - j);
+ cur_choice -= j;
+ /*
+ * Scroll the window up if necessary.
+ */
+ if ((cur_choice - window_offset) < 0) {
+ window_offset -= j;
+ if (window_offset < 0)
+ window_offset = 0;
+ ReDraw = TRUE;
+ }
+ goto restore_popup_statusline;
+ }
+
+ /*
+ * Didn't find it in the preceding choices either. - FM
+ */
+ HTUserMsg2(STRING_NOT_FOUND, next_target->str);
+
+ restore_popup_statusline:
+ /*
+ * Restore the popup statusline and reset the search variables. -
+ * FM
+ */
+ SHOW_POPUP_STATUS();
+ BStrCopy0(prev_target, "");
+ QueryTotal = (search_queries ? HTList_count(search_queries)
+ : 0);
+ recall = ((QueryTotal >= 1) ? RECALL_URL : NORECALL);
+ QueryNum = QueryTotal;
+ if (ReDraw == TRUE) {
+ ReDraw = FALSE;
+ goto redraw;
+ }
+ break;
+
+ case LYK_QUIT:
+ case LYK_ABORT:
+ case LYK_PREV_DOC:
+ case LYK_INTERRUPT:
+ cur_choice = orig_choice;
+ cmd = LYK_ACTIVATE; /* to exit */
+ break;
+ }
+ }
+ LYstopPopup();
+
+ return (disabled ? orig_choice : cur_choice);
+}
+
+/*
+ * Allow the user to edit a string.
+ */
+int LYgetBString(bstring **inputline,
+ int hidden,
+ unsigned max_cols,
+ RecallType recall)
+{
+ int x, y;
+ int ch;
+ int xlec = -2;
+ int last_xlec = -1;
+ int last_xlkc = -1;
+ FieldEditor MyEdit, *edit = &MyEdit;
+
+#ifdef SUPPORT_MULTIBYTE_EDIT
+ BOOL refresh_mb = TRUE;
+#endif /* SUPPORT_MULTIBYTE_EDIT */
+ BOOL done = FALSE;
+ int result = -1;
+
+ CTRACE((tfp, "called LYgetBString hidden %d, recall %d\n", hidden, (int) recall));
+
+ LYGetYX(y, x); /* Use screen from cursor position to eol */
+
+ (void) y;
+ (void) x;
+
+ if (*inputline == NULL) /* caller may not have initialized this */
+ BStrCopy0(*inputline, "");
+
+ LYSetupEdit(edit, (*inputline)->str, max_cols, LYcolLimit - x);
+ IsHidden = (BOOL) hidden;
+#ifdef FEPCTRL
+ fep_on();
+#endif
+
+ while (!done) {
+ beginning:
+#ifndef SUPPORT_MULTIBYTE_EDIT
+ LYRefreshEdit(edit);
+#else /* SUPPORT_MULTIBYTE_EDIT */
+ if (refresh_mb)
+ LYRefreshEdit(edit);
+#endif /* SUPPORT_MULTIBYTE_EDIT */
+ ch = LYReadCmdKey(FOR_PROMPT);
+#ifdef SUPPORT_MULTIBYTE_EDIT
+#ifdef CJK_EX /* for SJIS code */
+ if (!refresh_mb
+ && (EditBinding(ch) != LYE_CHAR))
+ goto beginning;
+#else
+ if (!refresh_mb
+ && (EditBinding(ch) != LYE_CHAR)
+ && (EditBinding(ch) != LYE_AIX))
+ goto beginning;
+#endif
+#endif /* SUPPORT_MULTIBYTE_EDIT */
+
+ if (term_letter || term_options
+#ifdef VMS
+ || HadVMSInterrupt
+#endif /* VMS */
+#ifndef DISABLE_NEWS
+ || term_message
+#endif
+ ) {
+#ifdef VMS
+ HadVMSInterrupt = FALSE;
+#endif /* VMS */
+ ch = LYCharINTERRUPT2;
+ }
+
+ if (recall != NORECALL && (ch == UPARROW_KEY || ch == DNARROW_KEY)) {
+ BStrCopy0(*inputline, Buffer);
+ LYAddToCloset(recall, Buffer);
+ CTRACE((tfp, "LYgetstr(%s) recall\n", (*inputline)->str));
+#ifdef FEPCTRL
+ fep_off();
+#endif
+ LYFinishEdit(edit);
+ result = ch;
+ done = TRUE;
+ continue;
+ }
+ ch |= InputMods;
+ InputMods = 0;
+ if (last_xlkc != -1) {
+ if (ch == last_xlkc)
+ ch |= LKC_MOD3;
+ last_xlkc = -1; /* consumed */
+ }
+#ifndef WIN_EX
+ if (LKC_TO_LAC(keymap, ch) == LYK_REFRESH)
+ goto beginning;
+#endif
+ last_xlec = xlec;
+ xlec = EditBinding(ch);
+ if ((xlec & LYE_DF) && !(xlec & LYE_FORM_LAC)) {
+ last_xlkc = ch;
+ xlec &= ~LYE_DF;
+ } else {
+ last_xlkc = -1;
+ }
+ switch (xlec) {
+ case LYE_SETM1:
+ InputMods |= LKC_MOD1;
+ break;
+ case LYE_SETM2:
+ InputMods |= LKC_MOD2;
+ break;
+ case LYE_TAB:
+ if (xlec == last_xlec && recall != NORECALL) {
+ HTList *list = whichRecall(recall);
+
+ if (!HTList_isEmpty(list)) {
+ char **data = sortedList(list, (BOOL) (recall == RECALL_CMD));
+ int old_y, old_x;
+ int cur_choice = 0;
+ int num_options = LYarrayLength((STRING2PTR) data);
+
+ while (cur_choice < num_options
+ && strcasecomp(data[cur_choice], Buffer) < 0)
+ cur_choice++;
+
+ LYGetYX(old_y, old_x);
+ cur_choice = LYhandlePopupList(cur_choice,
+ 0,
+ old_x,
+ (STRING2PTR) data,
+ -1,
+ -1,
+ FALSE,
+ FALSE);
+ if (cur_choice >= 0) {
+ if (recall == RECALL_CMD)
+ _statusline(": ");
+ reinsertEdit(edit, data[cur_choice]);
+ }
+ LYmove(old_y, old_x);
+ FREE(data);
+ }
+ } else {
+ reinsertEdit(edit, LYFindInCloset(recall, Buffer));
+ }
+ break;
+
+#ifndef CJK_EX
+ case LYE_AIX:
+ /*
+ * Handle CJK characters, or as a valid character in the current
+ * display character set. Otherwise, we treat this as LYE_ENTER.
+ */
+ if (ch != '\t' &&
+ (IS_CJK_TTY ||
+ LYlowest_eightbit[current_char_set] <= 0x97)) {
+ LYLineEdit(edit, ch, FALSE);
+ break;
+ }
+#endif
+ /* FALLTHRU */
+ case LYE_ENTER:
+ BStrCopy0(*inputline, Buffer);
+ if (!hidden)
+ LYAddToCloset(recall, Buffer);
+ CTRACE((tfp, "LYgetstr(%s) LYE_ENTER\n", (*inputline)->str));
+#ifdef FEPCTRL
+ fep_off();
+#endif
+ LYFinishEdit(edit);
+ result = ch;
+ done = TRUE;
+ continue;
+
+#ifdef CAN_CUT_AND_PASTE
+ case LYE_PASTE:
+ {
+ unsigned char *s = (unsigned char *) get_clip_grab(), *e;
+ size_t len;
+
+ if (!s)
+ break;
+ len = strlen((const char *) s);
+ e = s + len;
+
+ if (len != 0) {
+ unsigned char *e1 = s;
+
+ while (e1 < e) {
+ if (*e1 < ' ') { /* Stop here? */
+ if (e1 > s)
+ LYEditInsert(edit, s, (int) (e1 - s),
+ map_active, TRUE);
+ s = e1;
+ if (*e1 == '\t') { /* Replace by space */
+ LYEditInsert(edit,
+ (unsigned const char *) " ",
+ 1,
+ map_active,
+ TRUE);
+ s = ++e1;
+ } else {
+ break;
+ }
+ } else {
+ ++e1;
+ }
+ }
+ if (e1 > s) {
+ LYEditInsert(edit, s, (int) (e1 - s), map_active, TRUE);
+ }
+ }
+ get_clip_release();
+ break;
+ }
+#endif
+
+ case LYE_ABORT:
+ CTRACE((tfp, "LYgetstr LYE_ABORT\n"));
+#ifdef FEPCTRL
+ fep_off();
+#endif
+ LYFinishEdit(edit);
+ BStrCopy0(*inputline, "");
+ done = TRUE;
+ continue;
+
+ case LYE_STOP:
+ CTRACE((tfp, "LYgetstr LYE_STOP\n"));
+#ifdef TEXTFIELDS_MAY_NEED_ACTIVATION
+ textfields_need_activation = TRUE;
+ LYFinishEdit(edit);
+ BStrCopy0(*inputline, "");
+ done = TRUE;
+ continue;
+#else
+#ifdef ENHANCED_LINEEDIT
+ disableEditMark();
+#endif
+ break;
+#endif
+
+ case LYE_LKCMD:
+ /*
+ * Used only in form_getstr() for invoking the LYK_F_LINK_NUM
+ * prompt when in form text fields. - FM
+ */
+ break;
+
+ case LYE_FORM_PASS:
+ /*
+ * Used in form_getstr() to end line editing and pass on the input
+ * char/lynxkeycode. Here it is just ignored. - kw
+ */
+ break;
+
+ default:
+ if (xlec & LYE_FORM_LAC) {
+ /*
+ * Used in form_getstr() to end line editing and pass on the
+ * lynxkeycode already containing a lynxactioncode. Here it is
+ * just ignored. - kw
+ */
+ break;
+ }
+#ifndef SUPPORT_MULTIBYTE_EDIT
+ LYLineEdit(edit, ch, FALSE);
+#else /* SUPPORT_MULTIBYTE_EDIT */
+ if (LYLineEdit(edit, ch, FALSE) == 0) {
+ if (refresh_mb && IS_CJK_TTY && (0x81 <= ch) && (ch <= 0xfe))
+ refresh_mb = FALSE;
+ else
+ refresh_mb = TRUE;
+ } else {
+ if (!refresh_mb) {
+ LYDoEdit(edit, 0, LYE_DELP, FALSE);
+ }
+ }
+#endif /* SUPPORT_MULTIBYTE_EDIT */
+ }
+ }
+ return result;
+}
+
+/*
+ * Use this for fixed-buffer edits which have not been converted to use
+ * LYgetBString().
+ */
+int LYgetstr(char *inputline, /* fixed-size buffer for input/output */
+ int hidden, /* true to suppress from command-history */
+ unsigned bufsize, /* sizeof(inputline) */
+ RecallType recall) /* type of command-history */
+{
+ int ch;
+ bstring *my_bstring = NULL;
+
+ BStrCopy0(my_bstring, inputline);
+ if (my_bstring != 0) {
+ ch = LYgetBString(&my_bstring, hidden, bufsize, recall);
+ if (ch >= 0 && my_bstring != 0)
+ LYStrNCpy(inputline, my_bstring->str, bufsize);
+ BStrFree(my_bstring);
+ } else {
+ ch = -1;
+ }
+ return ch;
+}
+
+const char *LYLineeditHelpURL(void)
+{
+ static int lasthelp_lineedit = -1;
+ static char helpbuf[LY_MAXPATH] = "\0";
+ static char *phelp = &helpbuf[0];
+ const char *result = NULL;
+
+ if (lasthelp_lineedit == current_lineedit) {
+ result = helpbuf;
+ } else {
+ const char *source = LYLineeditHelpURLs[current_lineedit];
+ size_t available;
+
+ if (lasthelp_lineedit == -1) {
+ LYStrNCpy(helpbuf, helpfilepath, sizeof(helpbuf) - 1);
+ phelp += strlen(helpbuf);
+ }
+ available = (sizeof(helpbuf) - (size_t) (phelp - helpbuf));
+ if (non_empty(source) &&
+ (strlen(source) <= available)) {
+ LYStrNCpy(phelp, source, available);
+ lasthelp_lineedit = current_lineedit;
+ result = helpbuf;
+ }
+ }
+ return result;
+}
+
+/*
+ * Wrapper for sscanf to ensure that lynx can "always" read a POSIX float.
+ * In some locales, the decimal point changes.
+ */
+int LYscanFloat2(const char **source, float *result)
+{
+ int count = 0;
+ char *temp;
+ const char *src = *source;
+
+ src = LYSkipCBlanks(src);
+ *result = 0.0;
+ if (StrChr(src, '.') != 0) {
+ long frc_part = 0;
+ float scale = 1.0;
+
+ if (*src != '.') {
+ temp = NULL;
+ frc_part = strtol(src, &temp, 10);
+ *result = (float) frc_part;
+ src = temp;
+ }
+ if (src != 0 && *src == '.') {
+ ++src;
+ if (isdigit(UCH(*src))) {
+ temp = NULL;
+ frc_part = strtol(src, &temp, 10);
+ if (temp != 0) {
+ int digits = (int) (temp - src);
+
+ while (digits-- > 0)
+ scale *= (float) 10.0;
+ *result += ((float) frc_part / scale);
+ }
+ src = temp;
+ }
+ }
+ if (src != 0 && *src != '\0' && StrChr(" \t+", *src) == 0) {
+ char *extra = (char *) malloc(2 + strlen(src));
+
+ if (extra != 0) {
+ extra[0] = '1';
+ strcpy(extra + 1, src);
+ if (sscanf(extra, "%f", &scale) == 1) {
+ *result *= scale;
+ }
+ FREE(extra);
+ src = LYSkipCNonBlanks(src);
+ } else {
+ src = 0;
+ }
+ }
+ if (src != 0)
+ count = 1;
+ } else {
+ count = sscanf(src, "%f", result);
+ src = LYSkipCNonBlanks(src);
+ }
+ CTRACE2(TRACE_CFG,
+ (tfp, "LYscanFloat \"%s\" -> %f (%s)\n",
+ *source, *result,
+ count ? "ok" : "error"));
+ *source = src;
+ return count;
+}
+
+int LYscanFloat(const char *source, float *result)
+{
+ const char *temp = source;
+
+ return LYscanFloat2(&temp, result);
+}
+
+/*
+ * A replacement for 'strsep()'
+ */
+char *LYstrsep(char **stringp,
+ const char *delim)
+{
+ char *marker;
+ char *result = 0;
+
+ if (non_empty(stringp)) {
+ result = *stringp; /* will return the old value */
+ marker = strpbrk(*stringp, delim);
+ if (marker) {
+ *marker = '\0'; /* terminate the substring */
+ *stringp = ++marker; /* point to the next substring */
+ } else {
+ *stringp = 0; /* this was the last */
+ }
+ }
+ return result;
+}
+
+/*
+ * LYstrstr finds the first occurrence of the string pointed to by needle
+ * in the string pointed to by haystack.
+ *
+ * It returns NULL if the string is not found.
+ *
+ * It is a case insensitive search.
+ */
+char *LYstrstr(char *haystack,
+ const char *needle)
+{
+ int len = (int) strlen(needle);
+ char *result = NULL;
+
+ for (; *haystack != '\0'; haystack++) {
+ if (0 == UPPER8(*haystack, *needle)) {
+ if (0 == strncasecomp8(haystack + 1, needle + 1, len - 1)) {
+ result = haystack;
+ break;
+ }
+ }
+ }
+
+ return (result);
+}
+
+#define SkipSpecialChars(p) \
+ while (IsSpecialAttrChar(*p) && *p != '\0') \
+ p++
+
+/*
+ * LYno_attr_char_case_strstr finds the first occurrence of the
+ * string pointed to by needle in the string pointed to by haystack.
+ *
+ * It ignores special characters, e.g., LY_UNDERLINE_START_CHAR in haystack.
+ *
+ * It is a case insensitive search.
+ */
+const char *LYno_attr_char_case_strstr(const char *haystack,
+ const char *needle)
+{
+ const char *refptr, *tstptr;
+ const char *result = NULL;
+
+ if (haystack != NULL && needle != NULL) {
+
+ SkipSpecialChars(haystack);
+
+ for (; *haystack != '\0' && (result == NULL); haystack++) {
+ if (0 == UPPER8(*haystack, *needle)) {
+ refptr = haystack + 1;
+ tstptr = needle + 1;
+
+ if (*tstptr == '\0') {
+ result = haystack;
+ break;
+ }
+
+ while (1) {
+ if (!IsSpecialAttrChar(*refptr)) {
+ if (0 != UPPER8(*refptr, *tstptr))
+ break;
+ refptr++;
+ tstptr++;
+ } else {
+ refptr++;
+ }
+ if (*tstptr == '\0') {
+ result = haystack;
+ break;
+ }
+ if (*refptr == '\0')
+ break;
+ }
+ }
+ }
+ }
+
+ return (result);
+}
+
+/*
+ * LYno_attr_char_strstr finds the first occurrence of the
+ * string pointed to by needle in the string pointed to by haystack.
+ * It ignores special characters, e.g., LY_UNDERLINE_START_CHAR in haystack.
+ *
+ * It is a case sensitive search.
+ */
+const char *LYno_attr_char_strstr(const char *haystack,
+ const char *needle)
+{
+ const char *refptr, *tstptr;
+ const char *result = NULL;
+
+ if (haystack != NULL && needle != NULL) {
+
+ SkipSpecialChars(haystack);
+
+ for (; *haystack != '\0' && (result == NULL); haystack++) {
+ if ((*haystack) == (*needle)) {
+ refptr = haystack + 1;
+ tstptr = needle + 1;
+
+ if (*tstptr == '\0') {
+ result = haystack;
+ break;
+ }
+
+ while (1) {
+ if (!IsSpecialAttrChar(*refptr)) {
+ if ((*refptr) != (*tstptr))
+ break;
+ refptr++;
+ tstptr++;
+ } else {
+ refptr++;
+ }
+ if (*tstptr == '\0') {
+ result = haystack;
+ break;
+ } else if (*refptr == '\0') {
+ break;
+ }
+ }
+ }
+ }
+ }
+
+ return (result);
+}
+
+/*
+ * LYno_attr_mbcs_case_strstr finds the first occurrence of the string pointed
+ * to by needle in the string pointed to by haystack. It takes account of
+ * MultiByte Character Sequences (UTF8). The physical lengths of the displayed
+ * string up to the start and end (= next position after) of the target string
+ * are returned in *nstartp and *nendp if the search is successful.
+ *
+ * These lengths count glyph cells if count_gcells is set. (Full-width
+ * characters in CJK mode count as two.) Normally that's what we want. They
+ * count actual glyphs if count_gcells is unset. (Full-width characters in CJK
+ * mode count as one.)
+ *
+ * It ignores special characters, e.g., LY_UNDERLINE_START_CHAR in haystack.
+ *
+ * It assumes UTF8 if utf_flag is set.
+ *
+ * It is a case insensitive search.
+ */
+const char *LYno_attr_mbcs_case_strstr(const char *haystack,
+ const char *needle,
+ int utf_flag,
+ int count_gcells,
+ int *nstartp,
+ int *nendp)
+{
+ const char *refptr;
+ const char *tstptr;
+ int len = 0;
+ int offset;
+ const char *result = NULL;
+
+ if (haystack != NULL && needle != NULL) {
+
+ SkipSpecialChars(haystack);
+
+ for (; *haystack != '\0' && (result == NULL); haystack++) {
+ if ((!utf_flag && IS_CJK_TTY && is8bits(*haystack) &&
+ *haystack == *needle &&
+ IsNormalChar(*(haystack + 1))) ||
+ (0 == UPPER8(*haystack, *needle))) {
+ int tarlen = 0;
+
+ offset = len;
+ len++;
+
+ refptr = (haystack + 1);
+ tstptr = (needle + 1);
+
+ if (*tstptr == '\0') {
+ if (nstartp)
+ *nstartp = offset;
+ if (nendp)
+ *nendp = len;
+ result = haystack;
+ break;
+ }
+ if (!utf_flag && IS_CJK_TTY && is8bits(*haystack) &&
+ *haystack == *needle &&
+ IsNormalChar(*refptr)) {
+ /* handle a CJK multibyte string */
+ if (*refptr == *tstptr) {
+ refptr++;
+ tstptr++;
+ if (count_gcells)
+ tarlen++;
+ if (*tstptr == '\0') {
+ if (nstartp)
+ *nstartp = offset;
+ if (nendp)
+ *nendp = len + tarlen;
+ result = haystack;
+ break;
+ }
+ } else {
+ /* not a match */
+ haystack++;
+ if (count_gcells)
+ len++;
+ continue;
+ }
+ }
+ /* compare the remainder of the string */
+ while (1) {
+ if (!IsSpecialAttrChar(*refptr)) {
+ if (!utf_flag && IS_CJK_TTY && is8bits(*refptr)) {
+ if (*refptr == *tstptr &&
+ *(refptr + 1) == *(tstptr + 1) &&
+ !IsSpecialAttrChar(*(refptr + 1))) {
+ refptr++;
+ tstptr++;
+ if (count_gcells)
+ tarlen++;
+ } else {
+ break;
+ }
+ } else if (0 != UPPER8(*refptr, *tstptr)) {
+ break;
+ }
+
+ if (!IS_UTF_EXTRA(*tstptr)) {
+ tarlen++;
+ }
+ refptr++;
+ tstptr++;
+
+ } else {
+ refptr++;
+ }
+
+ if (*tstptr == '\0') {
+ if (nstartp)
+ *nstartp = offset;
+ if (nendp)
+ *nendp = len + tarlen;
+ result = haystack;
+ break;
+ }
+ if (*refptr == '\0')
+ break;
+ }
+ } else if (!(IS_UTF_EXTRA(*haystack) ||
+ IsSpecialAttrChar(*haystack))) {
+ if (!utf_flag && IS_CJK_TTY && is8bits(*haystack) &&
+ IsNormalChar(*(haystack + 1))) {
+ haystack++;
+ if (count_gcells)
+ len++;
+ }
+ len++;
+ }
+ }
+ }
+
+ return (result);
+}
+
+/*
+ * LYno_attr_mbcs_strstr finds the first occurrence of the string pointed
+ * to by needle in the string pointed to by haystack.
+ *
+ * It takes account of CJK and MultiByte Character Sequences (UTF8). The
+ * physical lengths of the displayed string up to the start and end (= next
+ * position after) the target string are returned in *nstartp and *nendp if the
+ * search is successful.
+ *
+ * These lengths count glyph cells if count_gcells is set. (Full-width
+ * characters in CJK mode count as two.) Normally that's what we want. They
+ * count actual glyphs if count_gcells is unset. (Full-width characters in CJK
+ * mode count as one.)
+ *
+ * It ignores special characters, e.g., LY_UNDERLINE_START_CHAR in haystack.
+ *
+ * It assumes UTF8 if utf_flag is set.
+ *
+ * It is a case sensitive search.
+ */
+const char *LYno_attr_mbcs_strstr(const char *haystack,
+ const char *needle,
+ int utf_flag,
+ int count_gcells,
+ int *nstartp,
+ int *nendp)
+{
+ const char *refptr;
+ const char *tstptr;
+ int len = 0;
+ int offset;
+ const char *result = NULL;
+
+ if (haystack != NULL && needle != NULL) {
+
+ SkipSpecialChars(haystack);
+
+ for (; *haystack != '\0' && (result == NULL); haystack++) {
+ if ((*haystack) == (*needle)) {
+ int tarlen = 0;
+
+ offset = len;
+ len++;
+
+ refptr = (haystack + 1);
+ tstptr = (needle + 1);
+
+ if (*tstptr == '\0') {
+ if (nstartp)
+ *nstartp = offset;
+ if (nendp)
+ *nendp = len;
+ result = haystack;
+ break;
+ } else if (!utf_flag &&
+ IS_CJK_TTY &&
+ is8bits(*haystack) &&
+ IsNormalChar(*refptr)) {
+ /* handle a CJK multibyte string */
+ if (*refptr == *tstptr) {
+ /* found match */
+ refptr++;
+ tstptr++;
+ if (count_gcells)
+ tarlen++;
+ if (*tstptr == '\0') {
+ if (nstartp)
+ *nstartp = offset;
+ if (nendp)
+ *nendp = len + tarlen;
+ result = haystack;
+ break;
+ }
+ } else {
+ /* not a match - restart comparison */
+ haystack++;
+ if (count_gcells)
+ len++;
+ continue;
+ }
+ }
+ /* compare the remainder of the string */
+ while (1) {
+ if (!IsSpecialAttrChar(*refptr)) {
+ if (!utf_flag && IS_CJK_TTY && is8bits(*refptr)) {
+ if (*refptr == *tstptr &&
+ *(refptr + 1) == *(tstptr + 1) &&
+ !IsSpecialAttrChar(*(refptr + 1))) {
+ refptr++;
+ tstptr++;
+ if (count_gcells)
+ tarlen++;
+ } else {
+ break;
+ }
+ } else if ((*refptr) != (*tstptr)) {
+ break;
+ }
+
+ if (!IS_UTF_EXTRA(*tstptr)) {
+ tarlen++;
+ }
+ refptr++;
+ tstptr++;
+ } else {
+ refptr++;
+ }
+
+ if (*tstptr == '\0') {
+ if (nstartp)
+ *nstartp = offset;
+ if (nendp)
+ *nendp = len + tarlen;
+ result = haystack;
+ break;
+ }
+ if (*refptr == '\0')
+ break;
+ }
+ } else if (!(IS_UTF_EXTRA(*haystack) ||
+ IsSpecialAttrChar(*haystack))) {
+ if (!utf_flag && IS_CJK_TTY && is8bits(*haystack) &&
+ IsNormalChar(*(haystack + 1))) {
+ haystack++;
+ if (count_gcells)
+ len++;
+ }
+ len++;
+ }
+ }
+ }
+ return (result);
+}
+
+/*
+ * Allocate and return a copy of a string.
+ * see StrAllocCopy
+ */
+char *SNACopy(char **target,
+ const char *source,
+ size_t n)
+{
+ FREE(*target);
+ if (source) {
+ *target = typeMallocn(char, n + 1);
+
+ if (*target == NULL) {
+ CTRACE((tfp, "Tried to malloc %lu bytes\n", (unsigned long) n));
+ outofmem(__FILE__, "SNACopy");
+ }
+ LYStrNCpy(*target, source, n);
+ }
+ return *target;
+}
+
+/*
+ * Combine string allocation and concatenation.
+ * see StrAllocCat
+ */
+char *SNACat(char **target,
+ const char *source,
+ size_t n)
+{
+ if (non_empty(source)) {
+ if (*target) {
+ size_t length = strlen(*target);
+
+ *target = typeRealloc(char, *target, length + n + 1);
+
+ if (*target == NULL)
+ outofmem(__FILE__, "SNACat");
+ LYStrNCpy(*target + length, source, n);
+ } else {
+ *target = typeMallocn(char, n + 1);
+
+ if (*target == NULL)
+ outofmem(__FILE__, "SNACat");
+ MemCpy(*target, source, n);
+ (*target)[n] = '\0'; /* terminate */
+ }
+ }
+ return *target;
+}
+
+#include <caselower.h>
+
+/*
+ * Returns lowercase equivalent for unicode,
+ * transparent output if no equivalent found.
+ */
+static long UniToLowerCase(long upper)
+{
+ size_t i, high, low;
+ long diff = 0;
+ long result = upper;
+
+ if (upper > 0) {
+ /*
+ * Try unicode_to_lower_case[].
+ */
+ low = 0;
+ high = TABLESIZE(unicode_to_lower_case);
+ while (low < high) {
+ /*
+ * Binary search.
+ */
+ i = (low + (high - low) / 2);
+ diff = (unicode_to_lower_case[i].upper - upper);
+ if (diff < 0) {
+ low = i + 1;
+ } else if (diff > 0) {
+ high = i;
+ } else if (diff == 0) {
+ result = unicode_to_lower_case[i].lower;
+ break;
+ }
+ }
+ }
+
+ return result;
+}
+
+/*
+ * UPPER8 ?
+ * it was "TOUPPER(a) - TOUPPER(b)" in its previous life...
+ *
+ * It was realized that case-insensitive user search
+ * got information about upper/lower mapping from TOUPPER
+ * (precisely from "(TOUPPER(a) - TOUPPER(b))==0")
+ * and depends on locale in its 8bit mapping. -
+ * Usually fails with DOS/WINDOWS display charsets
+ * as well as on non-UNIX systems.
+ *
+ * So use unicode case mapping.
+ */
+int UPPER8(int ch1, int ch2)
+{
+ int result = 0;
+
+ if (ch1 == ch2) {
+ result = 0;
+ } else if (!ch2) {
+ result = UCH(ch1);
+ } else if (!ch1) {
+ result = -UCH(ch2);
+ } else if (UCH(TOASCII(ch1)) < 128 && UCH(TOASCII(ch2)) < 128) {
+ /* case-insensitive match for us-ascii */
+ result = (TOUPPER(ch1) - TOUPPER(ch2));
+ } else if (UCH(TOASCII(ch1)) > 127 &&
+ UCH(TOASCII(ch2)) > 127) {
+ /* case-insensitive match for upper half */
+ if (DisplayCharsetMatchLocale) {
+ result = (TOUPPER(ch1) - TOUPPER(ch2)); /* old-style */
+ } else {
+ long uni_ch2 = UCTransToUni((char) ch2, current_char_set);
+ long uni_ch1;
+
+ if (uni_ch2 < 0) {
+ result = UCH(ch1);
+ } else {
+ uni_ch1 = UCTransToUni((char) ch1, current_char_set);
+ result = (int) (UniToLowerCase(uni_ch1) - UniToLowerCase(uni_ch2));
+ }
+ }
+ } else {
+ result = -10; /* mismatch */
+ }
+
+ return result;
+}
+
+/*
+ * Replaces 'fgets()' calls into a fixed-size buffer with reads into a buffer
+ * that is allocated. When an EOF or error is found, the buffer is freed
+ * automatically.
+ */
+char *LYSafeGets(char **target,
+ FILE *fp)
+{
+ char buffer[BUFSIZ];
+ char *result = 0;
+
+ if (target != 0)
+ result = *target;
+ if (result != 0)
+ *result = 0;
+
+ while (fgets(buffer, (int) sizeof(buffer), fp) != NULL) {
+ if (*buffer)
+ result = StrAllocCat(result, buffer);
+ if (StrChr(buffer, '\n') != 0)
+ break;
+ }
+ if (ferror(fp)) {
+ FREE(result);
+ } else if (feof(fp) && result && *result == '\0') {
+ /*
+ * If the file ends in the middle of a line, return the partial line;
+ * if another call is made after this, it will return NULL. - kw
+ */
+ FREE(result);
+ }
+ if (target != 0)
+ *target = result;
+ return result;
+}
+
+#ifdef USE_CMD_LOGGING
+static FILE *cmd_logfile;
+static FILE *cmd_script;
+
+void LYOpenCmdLogfile(int argc,
+ char **argv)
+{
+ int n;
+
+ if (non_empty(lynx_cmd_logfile)) {
+ cmd_logfile = LYNewTxtFile(lynx_cmd_logfile);
+ if (cmd_logfile != 0) {
+ fprintf(cmd_logfile, "# Command logfile created by %s %s (%s)\n",
+ LYNX_NAME, LYNX_VERSION, LYVersionDate());
+ for (n = 0; n < argc; n++) {
+ fprintf(cmd_logfile, "# Arg%d = %s\n", n, argv[n]);
+ }
+ }
+ }
+}
+
+BOOL LYHaveCmdScript(void)
+{
+ return (BOOL) (cmd_script != 0);
+}
+
+void LYOpenCmdScript(void)
+{
+ if (non_empty(lynx_cmd_script)) {
+ cmd_script = fopen(lynx_cmd_script, TXT_R);
+ CTRACE((tfp, "LYOpenCmdScript(%s) %s\n",
+ lynx_cmd_script,
+ cmd_script != 0 ? "SUCCESS" : "FAIL"));
+ }
+}
+
+int LYReadCmdKey(int mode)
+{
+ int ch = -1;
+
+ if (cmd_script != 0) {
+ char *buffer = 0;
+ char *src;
+ char *tmp;
+
+ while ((ch < 0) && LYSafeGets(&buffer, cmd_script) != 0) {
+ LYTrimTrailing(buffer);
+ src = LYSkipBlanks(buffer);
+ tmp = LYSkipNonBlanks(src);
+ switch ((unsigned) (tmp - src)) {
+ case 4:
+ if (!strncasecomp(src, "exit", 4))
+ exit_immediately(EXIT_SUCCESS);
+ break;
+ case 3:
+ if (!strncasecomp(src, "key", 3)) {
+ ch = LYStringToKeycode(LYSkipBlanks(tmp));
+ } else if (!strncasecomp(src, "set", 3)) {
+ src = LYSkipBlanks(tmp);
+ tmp = src;
+ while (*tmp != '\0') {
+ if (isspace(UCH(*tmp)) || *tmp == '=')
+ break;
+ ++tmp;
+ }
+ if (*tmp != '\0') {
+ *tmp++ = '\0';
+ tmp = LYSkipBlanks(tmp);
+ }
+ if (LYSetConfigValue(src, tmp)) {
+ CTRACE((tfp, "LYSetConfigValue(%s, %s)\n", src, tmp));
+ } else if (LYsetRcValue(src, tmp)) {
+ CTRACE((tfp, "LYsetRcValue(%s, %s)\n", src, tmp));
+ } else {
+ CTRACE((tfp, "?? set ignored %s\n", src));
+ }
+ }
+ break;
+ }
+ }
+ if (feof(cmd_script)) {
+ fclose(cmd_script);
+ cmd_script = 0;
+ }
+ if (ch >= 0) {
+ LYSleepReplay();
+ LYrefresh();
+ }
+ FREE(buffer);
+ } else {
+ ch = LYgetch_for(mode);
+ }
+ CTRACE((tfp, "LYReadCmdKey(%d) ->%s (%#x)\n",
+ mode, LYKeycodeToString(ch, TRUE), (unsigned) ch));
+ LYWriteCmdKey(ch);
+ return ch;
+}
+
+/*
+ * Write a LYKeymapCode 'ch' to the logfile.
+ */
+void LYWriteCmdKey(int ch)
+{
+ if (cmd_logfile != 0) {
+ fprintf(cmd_logfile, "key %s\n", LYKeycodeToString(ch, FALSE));
+ }
+}
+
+void LYCloseCmdLogfile(void)
+{
+ if (cmd_logfile != 0) {
+ LYCloseOutput(cmd_logfile);
+ cmd_logfile = 0;
+ }
+ if (cmd_script != 0) {
+ LYCloseInput(cmd_script);
+ cmd_script = 0;
+ }
+ FREE(lynx_cmd_logfile);
+ FREE(lynx_cmd_script);
+}
+#endif /* USE_CMD_LOGGING */
diff --git a/src/LYStrings.h b/src/LYStrings.h
new file mode 100644
index 0000000..a401379
--- /dev/null
+++ b/src/LYStrings.h
@@ -0,0 +1,402 @@
+/*
+ * $LynxId: LYStrings.h,v 1.117 2018/05/04 22:47:10 tom Exp $
+ */
+#ifndef LYSTRINGS_H
+#define LYSTRINGS_H
+
+#include <LYCurses.h>
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+#define SQUOTE '\''
+#define DQUOTE '"'
+#define ESCAPE '\\'
+#define LPAREN '('
+#define RPAREN ')'
+
+ typedef const char *const Const2CharPtr;
+ typedef enum {
+ NORECALL = 0
+ ,RECALL_URL
+ ,RECALL_CMD
+ ,RECALL_MAIL
+ } RecallType;
+
+#define IS_UTF8_TTY (BOOLEAN) (LYCharSet_UC[current_char_set].enc == UCT_ENC_UTF8)
+#define IS_CJK_TTY (BOOLEAN) (HTCJK != NOCJK)
+
+#define is8bits(ch) (BOOLEAN) (UCH(ch) >= 128) /* isascii(ch) is not POSIX */
+
+/* UPPER8(ch1,ch2) is an extension of (TOUPPER(ch1) - TOUPPER(ch2)) */
+ extern int UPPER8(int ch1,
+ int ch2);
+
+ extern int get_mouse_link(void);
+ extern int peek_mouse_link(void);
+ extern int peek_mouse_levent(void);
+ extern int fancy_mouse(WINDOW * win, int row, int *position);
+
+ extern char *LYstrncpy(char *dst,
+ const char *src,
+ int n);
+#define LYStrNCpy(dst,src,n) LYstrncpy(dst,src,(int)(n))
+ extern void ena_csi(int flag);
+ extern int get_popup_number(const char *msg,
+ int *c,
+ int *rel);
+ extern int LYarrayLength(STRING2PTR list);
+ extern int LYarrayWidth(STRING2PTR list);
+ extern int LYgetch(void);
+ extern int LYgetch_choice(void);
+ extern int LYgetch_input(void);
+ extern int LYgetch_single(void);
+ extern int LYgetstr(char *inputline,
+ int masked,
+ unsigned bufsize,
+ RecallType recall);
+#define LYGetStr(input,masked,bufsize,recall) \
+ LYgetstr(input,masked,(unsigned)(bufsize),recall)
+ extern int LYgetBString(bstring **inputline,
+ int masked,
+ unsigned max_cols,
+ RecallType recall);
+ extern int LYscanFloat(const char *source, float *result);
+ extern int LYscanFloat2(const char **source, float *result);
+ extern char *LYstrsep(char **stringp,
+ const char *delim);
+ extern char *LYstrstr(char *chptr,
+ const char *tarptr);
+ extern char *LYmbcsstrncpy(char *dst,
+ const char *src,
+ int n_bytes,
+ int n_glyphs,
+ int utf_flag);
+ extern const char *LYmbcs_skip_cells(const char *data,
+ int n_cells,
+ int utf_flag);
+ extern const char *LYmbcs_skip_glyphs(const char *data,
+ int n_glyphs,
+ int utf_flag);
+ extern int LYmbcsstrlen(const char *str,
+ int utf_flag,
+ int count_gcells);
+
+ extern const char *LYno_attr_mbcs_strstr(const char *chptr,
+ const char *tarptr,
+ int utf_flag,
+ int count_gcells,
+ int *nstartp,
+ int *nendp);
+ extern const char *LYno_attr_mbcs_case_strstr(const char *chptr,
+ const char *tarptr,
+ int utf_flag,
+ int count_gcells,
+ int *nstartp,
+ int *nendp);
+
+#define LYno_attr_mb_strstr(chptr, tarptr, utf_flag, count_gcells, nstartp, nendp) \
+ (LYcase_sensitive \
+ ? LYno_attr_mbcs_strstr(chptr, tarptr, utf_flag, count_gcells, nstartp, nendp) \
+ : LYno_attr_mbcs_case_strstr(chptr, tarptr, utf_flag, count_gcells, nstartp, nendp))
+
+ extern const char *LYno_attr_char_strstr(const char *chptr,
+ const char *tarptr);
+ extern const char *LYno_attr_char_case_strstr(const char *chptr,
+ const char *tarptr);
+
+#define LYno_attr_strstr(chptr, tarptr) \
+ (LYcase_sensitive \
+ ? LYno_attr_char_strstr(chptr, tarptr) \
+ : LYno_attr_char_case_strstr(chptr, tarptr))
+
+ extern char *SNACopy(char **dest,
+ const char *src,
+ size_t n);
+ extern char *SNACat(char **dest,
+ const char *src,
+ size_t n);
+
+#define StrnAllocCopy(dest, src, n) SNACopy (&(dest), src, n)
+#define StrnAllocCat(dest, src, n) SNACat (&(dest), src, n)
+
+ extern char *LYSafeGets(char **src, FILE *fp);
+
+#ifdef USE_CMD_LOGGING
+ extern BOOL LYHaveCmdScript(void);
+ extern int LYReadCmdKey(int mode);
+ extern void LYCloseCmdLogfile(void);
+ extern void LYOpenCmdLogfile(int argc, char **argv);
+ extern void LYOpenCmdScript(void);
+ extern void LYWriteCmdKey(int ch);
+
+#else
+#define LYHaveCmdScript() FALSE
+#define LYReadCmdKey(mode) LYgetch_for(mode)
+#define LYCloseCmdLogfile() /* nothing */
+#endif
+
+/* values for LYgetch */
+ /* The following are lynxkeycodes, not to be confused with
+ * lynxactioncodes (LYK_*) to which they are often mapped.
+ * The lynxkeycodes include all single-byte keys as a subset.
+ * These are "extra" keys which do not fit into a single byte.
+ */
+ typedef enum {
+ UNKNOWN_KEY = -1
+ ,DEL_KEY = 127
+ ,UPARROW_KEY = 256
+ ,DNARROW_KEY
+ ,RTARROW_KEY
+ ,LTARROW_KEY
+ ,PGDOWN_KEY
+ ,PGUP_KEY
+ ,HOME_KEY
+ ,END_KEY
+ ,F1_KEY
+ ,DO_KEY
+ ,FIND_KEY
+ ,SELECT_KEY
+ ,INSERT_KEY
+ ,REMOVE_KEY
+ ,DO_NOTHING
+ ,BACKTAB_KEY
+ /* these should be referenced by name in keymap, e.g., "f2" */
+ ,F2_KEY
+ ,F3_KEY
+ ,F4_KEY
+ ,F5_KEY
+ ,F6_KEY
+ ,F7_KEY
+ ,F8_KEY
+ ,F9_KEY
+ ,F10_KEY
+ ,F11_KEY
+ ,F12_KEY
+ /* this has known value */
+ ,MOUSE_KEY = 285 /* 0x11D */
+ } LYExtraKeys;
+
+/* ***** NOTES: *****
+ If you add definitions for new lynxkeycodes to the above list that need to
+ be mapped to LYK_* lynxactioncodes -
+
+ - AT LEAST the tables keymap[] and key_override[] in LYKeymap.c have to be
+ changed/reviewed, AS WELL AS the lineedit binding tables in LYEditmap.c !
+
+ - KEYMAP_SIZE, defined in LYKeymap.h, may need to be changed !
+
+ - See also table named_keys[] in LYKeymap.c for 'pretty' strings for the
+ keys with codes >= 256 (to appear on the 'K'eymap page). New keycodes
+ should probably be assigned consecutively, so their key names can be
+ easily added to named_keys[] (but see next point). They should also be
+ documented in lynx.cfg.
+
+ - The DOS port uses its own native codes for some keys, unless they are
+ remapped by the code in LYgetch(). See *.key files in docs/ directory.
+ Adding new keys here may conflict with those codes (affecting DOS users),
+ unless/until remapping is added or changed in LYgetch(). (N)curses
+ keypad codes (KEY_* from curses.h) can also directly appear as
+ lynxkeycodes and conflict with our assignments, although that shouldn't
+ happen - the useful ones should be recognized in LYgetch().
+
+ - The actual recognition of raw input keys or escape sequences, and mapping
+ to our lynxkeycodes, take place in LYgetch() and/or its subsidiary
+ functions and/or the curses/slang/etc. libraries.
+
+ The basic lynxkeycodes can appear combined with various flags in
+ higher-order bits as extended lynxkeycodes; see macros in LYKeymap.h. The
+ range of possible basic values is therefore limited, they have to be less
+ than LKC_ISLKC (even if KEYMAP_SIZE is increased).
+*/
+
+# define FOR_PANEL 0 /* normal screen, also LYgetch default */
+# define FOR_CHOICE 1 /* mouse menu */
+# define FOR_INPUT 2 /* form input and textarea field */
+# define FOR_PROMPT 3 /* string prompt editing */
+# define FOR_SINGLEKEY 4 /* single key prompt, confirmation */
+
+#ifdef USE_ALT_BINDINGS
+/* Enable code implementing additional, mostly emacs-like, line-editing
+ functions. - kw */
+#define ENHANCED_LINEEDIT
+#endif
+
+/* FieldEditor preserves state between calls to LYDoEdit
+ */
+ typedef struct {
+
+ int efStartX; /* Origin of edit-field */
+ int efStartY;
+ int efWidth; /* Screen real estate for editing */
+
+ char *efBuffer; /* the buffer which is being edited */
+ size_t efBufInUse; /* current size of string. */
+ size_t efBufAlloc; /* current buffer-size, excluding nul at end */
+ size_t efBufLimit; /* buffer size limit, zero if indefinite */
+
+ char efPadChar; /* Right padding typically ' ' or '_' */
+ BOOL efIsMasked; /* Masked password entry flag */
+
+ BOOL efIsDirty; /* accumulate refresh requests */
+ BOOL efIsPanned; /* Need horizontal scroll indicator */
+ int efDpyStart; /* Horizontal scroll offset */
+ int efEditAt; /* Insertion point in string */
+ int efPanMargin; /* Number of columns look-ahead/look-back */
+ int efInputMods; /* Modifiers for next input lynxkeycode */
+#ifdef ENHANCED_LINEEDIT
+ int efEditMark; /* position of emacs-like mark, or -1-pos to denote
+ unactive mark. */
+#endif
+
+ int *efOffs2Col; /* fixups for multibyte characters */
+
+ } FieldEditor;
+
+/* line-edit action encoding */
+
+ typedef enum {
+ LYE_UNKNOWN = -1 /* no binding */
+ ,LYE_NOP = 0 /* Do Nothing */
+ ,LYE_CHAR /* Insert printable char */
+ ,LYE_ENTER /* Input complete, return char/lynxkeycode */
+ ,LYE_TAB /* Input complete, return TAB */
+ ,LYE_STOP /* Input complete, deactivate */
+ ,LYE_ABORT /* Input cancelled */
+
+ ,LYE_FORM_PASS /* In form fields: input complete,
+ return char / lynxkeycode;
+ Elsewhere: Do Nothing */
+
+ ,LYE_DELN /* Delete next/curr char */
+ ,LYE_DELC /* Obsolete (DELC case was equiv to DELN) */
+ ,LYE_DELP /* Delete prev char */
+ ,LYE_DELNW /* Delete next word */
+ ,LYE_DELPW /* Delete prev word */
+
+ ,LYE_ERASE /* Erase the line */
+
+ ,LYE_BOL /* Go to begin of line */
+ ,LYE_EOL /* Go to end of line */
+ ,LYE_FORW /* Cursor forwards */
+ ,LYE_FORW_RL /* Cursor forwards or right link */
+ ,LYE_BACK /* Cursor backwards */
+ ,LYE_BACK_LL /* Cursor backwards or left link */
+ ,LYE_FORWW /* Word forward */
+ ,LYE_BACKW /* Word back */
+
+ ,LYE_LOWER /* Lower case the line */
+ ,LYE_UPPER /* Upper case the line */
+
+ ,LYE_LKCMD /* Invoke command prompt */
+
+ ,LYE_AIX /* Hex 97 */
+
+ ,LYE_DELBL /* Delete back to BOL */
+ ,LYE_DELEL /* Delete thru EOL */
+
+ ,LYE_SWMAP /* Switch input keymap */
+
+ ,LYE_TPOS /* Transpose characters */
+
+ ,LYE_SETM1 /* Set modifier 1 flag */
+ ,LYE_SETM2 /* Set modifier 2 flag */
+ ,LYE_UNMOD /* Fall back to no-modifier command */
+
+ ,LYE_C1CHAR /* Insert C1 char if printable */
+
+ ,LYE_SETMARK /* emacs-like set-mark-command */
+ ,LYE_XPMARK /* emacs-like exchange-point-and-mark */
+ ,LYE_KILLREG /* emacs-like kill-region */
+ ,LYE_YANK /* emacs-like yank */
+#ifdef CAN_CUT_AND_PASTE
+ ,LYE_PASTE /* ClipBoard to Lynx */
+#endif
+ } LYEditCodes;
+
+/* All preceding values must be within 0x00..0x7f - kw */
+
+/* The following are meant to be bitwise or-ed: */
+#define LYE_DF 0x80 /* Flag to set modifier 3 AND do other
+ action */
+#define LYE_FORM_LAC 0x1000 /* Flag to pass lynxactioncode given by
+ lower bits. Doesn't fit in a char! */
+
+#if defined(USE_KEYMAPS)
+ extern int lynx_initialize_keymaps(void);
+ extern int map_string_to_keysym(const char *src, int *lec, int internal);
+#endif
+
+ extern BOOL LYRemapEditBinding(int xlkc, int lec, int select_edi); /* in LYEditmap.c */
+ extern BOOLEAN LYRemoveNewlines(char *buffer);
+ extern BOOLEAN LYTrimStartfile(char *buffer);
+ extern LYExtraKeys LYnameToExtraKeys(const char *name);
+ extern char *LYElideString(char *str, int cut_pos);
+ extern char *LYReduceBlanks(char *buffer);
+ extern char *LYRemoveBlanks(char *buffer);
+ extern char *LYSkipBlanks(char *buffer);
+ extern char *LYSkipNonBlanks(char *buffer);
+ extern char *LYTrimNewline(char *buffer);
+ extern const char *LYSkipCBlanks(const char *buffer);
+ extern const char *LYSkipCNonBlanks(const char *buffer);
+ extern const char *LYextraKeysToName(LYExtraKeys code);
+ extern int EditBinding(int ch); /* in LYEditmap.c */
+ extern int LYDoEdit(FieldEditor * edit, int ch, int action, int maxMessage);
+ extern int LYEditKeyForAction(int lac, int *pmodkey); /* LYEditmap.c */
+ extern int LYKeyForEditAction(int lec); /* in LYEditmap.c */
+ extern int LYhandlePopupList(int cur_choice, int ly, int lx,
+ STRING2PTR choices,
+ int width,
+ int i_length,
+ int disabled,
+ int for_mouse);
+ extern void LYCloseCloset(RecallType recall);
+ extern void LYEscapeStartfile(char **buffer);
+ extern void LYFinishEdit(FieldEditor * edit);
+ extern void LYLowerCase(char *buffer);
+ extern void LYRefreshEdit(FieldEditor * edit);
+ extern void LYSetupEdit(FieldEditor * edit, char *old,
+ unsigned buffer_limit,
+ int display_limit);
+ extern void LYTrimAllStartfile(char *buffer);
+ extern void LYTrimLeading(char *buffer);
+ extern void LYTrimTrailing(char *buffer);
+ extern void LYUpperCase(char *buffer);
+
+ typedef short LYEditCode;
+
+ typedef struct {
+ int code;
+ LYEditCode edit;
+ } LYEditInit;
+
+ typedef struct {
+ const char *name;
+ const LYEditInit *init;
+ LYEditCode *used;
+ } LYEditConfig;
+
+ extern int current_lineedit;
+ extern const char *LYEditorNames[];
+ extern LYEditConfig LYLineEditors[];
+ extern const char *LYLineeditHelpURLs[];
+
+#define CurrentLineEditor() LYLineEditors[current_lineedit].used
+
+ extern void LYinitEditmap(void);
+ extern void LYinitKeymap(void);
+ extern const char *LYLineeditHelpURL(void);
+
+ extern int escape_bound;
+
+#define LYLineEdit(e,c,m) LYDoEdit(e, c, EditBinding(c) & ~LYE_DF, m)
+
+ extern int LYEditInsert(FieldEditor * edit, unsigned const char *s,
+ int len, int map_active,
+ int maxMessage);
+
+#ifdef __cplusplus
+}
+#endif
+#endif /* LYSTRINGS_H */
diff --git a/src/LYStructs.h b/src/LYStructs.h
new file mode 100644
index 0000000..179914a
--- /dev/null
+++ b/src/LYStructs.h
@@ -0,0 +1,190 @@
+/*
+ * $LynxId: LYStructs.h,v 1.32 2021/06/09 20:56:05 tom Exp $
+ */
+#ifndef LYSTRUCTS_H
+#define LYSTRUCTS_H
+
+#ifndef HTANCHOR_H
+#include <HTAnchor.h>
+#endif /* HTANCHOR_H */
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+ typedef struct {
+ char *hl_text;
+ short hl_x;
+ } HiliteInfo;
+
+ typedef struct {
+ HiliteInfo *hl_info;
+ HiliteInfo hl_base;
+ short hl_len; /* number of strings in this struct */
+ } HiliteList;
+
+ typedef struct {
+ char *lname;
+ char *target;
+ char *l_hightext;
+ BOOL inUnderline; /* TRUE when this link is in underlined context. */
+ int lx;
+ int ly;
+ int type; /* Type of link, Forms, WWW, etc. */
+ int sgml_offset; /* document offset used in reparsing */
+ int anchor_number; /* The anchor number within the HText structure. */
+ int anchor_line_num; /* The anchor line number in the HText structure. */
+ HiliteList list;
+ struct _FormInfo *l_form; /* Pointer to form info. */
+ } LinkInfo;
+ extern LinkInfo links[MAXLINKS];
+ extern int nlinks;
+
+ typedef struct {
+ /* FIXME: see DocAddress */
+ char *title;
+ char *address;
+ bstring *post_data;
+ char *post_content_type;
+ char *bookmark;
+ BOOL isHEAD;
+ BOOL safe;
+
+ int link;
+ int line;
+ BOOL internal_link; /* whether doc was reached via an internal
+ (fragment) link. - kw */
+#ifdef USE_COLOR_STYLE
+ char *style;
+#endif
+ } DocInfo;
+
+ typedef struct {
+ DocInfo hdoc;
+ int intern_seq_start; /* indicates which element on the history
+ is the start of this sequence of
+ "internal links", otherwise -1 */
+ } HistInfo;
+
+#define HDOC(n) history[n].hdoc
+
+ extern int Visited_Links_As;
+
+#define VISITED_LINKS_AS_FIRST_V 0
+#define VISITED_LINKS_AS_TREE 1
+#define VISITED_LINKS_AS_LATEST 2
+#define VISITED_LINKS_REVERSE 4
+
+ typedef struct _VisitedLink {
+ char *title;
+ char *address;
+ int level;
+ struct _VisitedLink *next_tree;
+ struct _VisitedLink *prev_latest;
+ struct _VisitedLink *next_latest;
+ struct _VisitedLink *prev_first;
+ } VisitedLink;
+
+ extern HistInfo *history;
+ extern int nhist;
+ extern unsigned size_history;
+
+/******************************************************************************/
+
+ typedef struct _lynx_list_item_type {
+ struct _lynx_list_item_type *next; /* the next item in the linked list */
+ char *name; /* a description of the item */
+ char *menu_name; /* menu-name for EXTERNAL / EXTERNAL_MENU */
+ char *command; /* the command to execute */
+ BOOL always_enabled; /* a constant to tell whether or
+ * not to disable the printer
+ * when the no_print option is on
+ */
+ /* HTML lists: */
+ BOOL override_action; /* whether primary action will be
+ * overridden by this - e.g. this
+ * allows invoking user's MUA when
+ * mailto: link is activated using
+ * normal "activate" command. This
+ * field is only examined by code that
+ * handles EXTERNAL command.
+ */
+ /* PRINTER lists: */
+ int pagelen; /* an integer to store the printer's
+ * page length
+ */
+ } lynx_list_item_type;
+
+ extern lynx_list_item_type *printers;
+
+/* for download commands */
+ extern lynx_list_item_type *downloaders;
+
+/* for upload commands */
+ extern lynx_list_item_type *uploaders;
+
+#ifdef USE_EXTERNALS
+/* for external commands */
+ extern lynx_list_item_type *externals;
+#endif
+
+/******************************************************************************/
+
+ typedef struct {
+ const char *name;
+ int value;
+ } Config_Enum;
+
+ typedef int (*ParseFunc) (char *);
+
+#define ParseUnionMembers \
+ lynx_list_item_type** add_value; \
+ BOOLEAN * set_value; \
+ int * int_value; \
+ char ** str_value; \
+ ParseFunc fun_value; \
+ long def_value; \
+ HTList** lst_value
+
+ typedef union {
+ ParseUnionMembers;
+ } ParseUnion;
+
+#define PARSE_DEBUG 1
+#ifdef PARSE_DEBUG
+
+#define ParseUnionPtr Config_Type *
+#define ParseUnionOf(tbl) tbl
+#define ParseData ParseUnionMembers
+
+#define UNION_ADD(v) &v, 0, 0, 0, 0, 0, 0
+#define UNION_SET(v) 0, &v, 0, 0, 0, 0, 0
+#define UNION_INT(v) 0, 0, &v, 0, 0, 0, 0
+#define UNION_STR(v) 0, 0, 0, &v, 0, 0, 0
+#define UNION_ENV(v) 0, 0, 0, v, 0, 0, 0
+#define UNION_FUN(v) 0, 0, 0, 0, v, 0, 0
+#define UNION_DEF(v) 0, 0, 0, 0, 0, v, 0
+#define UNION_LST(v) 0, 0, 0, 0, 0, 0, &v
+
+#else
+
+ typedef void *ParseType;
+
+#define ParseUnionPtr ParseUnion *
+#define ParseUnionOf(tbl) (ParseUnionPtr)(&(tbl->value))
+#define ParseData ParseType value
+
+#define UNION_ADD(v) (ParseType)&(v)
+#define UNION_SET(v) (ParseType)&(v)
+#define UNION_INT(v) (ParseType)&(v)
+#define UNION_STR(v) (ParseType)&(v)
+#define UNION_ENV(v) (ParseType) (v)
+#define UNION_FUN(v) (ParseType) (v)
+#define UNION_DEF(v) (ParseType) (v)
+#define UNION_LST(v) (ParseType)&(v)
+
+#endif
+
+#ifdef __cplusplus
+}
+#endif
+#endif /* LYSTRUCTS_H */
diff --git a/src/LYStyle.c b/src/LYStyle.c
new file mode 100644
index 0000000..77be188
--- /dev/null
+++ b/src/LYStyle.c
@@ -0,0 +1,970 @@
+/*
+ * $LynxId: LYStyle.c,v 1.111 2021/06/09 22:00:35 tom Exp $
+ *
+ * character level styles for Lynx
+ * (c) 1996 Rob Partington -- donated to the Lyncei (if they want it :-)
+ */
+#include <HTUtils.h>
+#include <HTML.h>
+#include <LYGlobalDefs.h>
+
+#include <LYStructs.h>
+#include <LYReadCFG.h>
+#include <LYCurses.h>
+#include <LYCharUtils.h>
+#include <LYUtils.h> /* defines TABLESIZE */
+#include <AttrList.h>
+#include <SGML.h>
+#include <HTMLDTD.h>
+
+/* Hash table definitions */
+#include <LYHash.h>
+#include <LYStyle.h>
+
+#include <LYOptions.h>
+#include <LYPrettySrc.h>
+
+#include <LYexit.h>
+#include <LYLeaks.h>
+#include <LYStrings.h>
+#include <LYHash.h>
+
+#define CTRACE1(p) CTRACE2(TRACE_CFG || TRACE_STYLE, p)
+
+#ifdef USE_COLOR_STYLE
+
+static HTList *list_of_lss_files;
+
+/* because curses isn't started when we parse the config file, we
+ * need to remember the STYLE: lines we encounter and parse them
+ * after curses has started
+ */
+static HTList *lss_styles = NULL;
+
+#define CACHEW 128
+#define CACHEH 64
+
+static unsigned *cached_styles_ptr = NULL;
+static int cached_styles_rows = 0;
+static int cached_styles_cols = 0;
+static BOOL empty_lss_list = FALSE; /* true if list explicitly emptied */
+
+/* stack of attributes during page rendering */
+int last_styles[MAX_LAST_STYLES + 1] =
+{0};
+int last_colorattr_ptr = 0;
+
+bucket hashStyles[CSHASHSIZE];
+
+int cached_tag_styles[HTML_ELEMENTS];
+int current_tag_style;
+BOOL force_current_tag_style = FALSE;
+char *forced_classname;
+BOOL force_classname;
+
+/* Remember the hash codes for common elements */
+int s_a = NOSTYLE;
+int s_aedit = NOSTYLE;
+int s_aedit_arr = NOSTYLE;
+int s_aedit_pad = NOSTYLE;
+int s_aedit_sel = NOSTYLE;
+int s_alert = NOSTYLE;
+int s_alink = NOSTYLE;
+int s_curedit = NOSTYLE;
+int s_forw_backw = NOSTYLE;
+int s_hot_paste = NOSTYLE;
+int s_menu_active = NOSTYLE;
+int s_menu_bg = NOSTYLE;
+int s_menu_entry = NOSTYLE;
+int s_menu_frame = NOSTYLE;
+int s_menu_number = NOSTYLE;
+int s_menu_sb = NOSTYLE;
+int s_normal = NOSTYLE;
+int s_prompt_edit = NOSTYLE;
+int s_prompt_edit_arr = NOSTYLE;
+int s_prompt_edit_pad = NOSTYLE;
+int s_prompt_sel = NOSTYLE;
+int s_status = NOSTYLE;
+int s_title = NOSTYLE;
+int s_whereis = NOSTYLE;
+
+#ifdef USE_SCROLLBAR
+int s_sb_aa = NOSTYLE;
+int s_sb_bar = NOSTYLE;
+int s_sb_bg = NOSTYLE;
+int s_sb_naa = NOSTYLE;
+#endif
+
+/* start somewhere safe */
+#define MAX_COLOR 16
+static int colorPairs = 0;
+
+#ifdef USE_BLINK
+# define MAX_BLINK 2
+# define M_BLINK A_BLINK
+#else
+# define MAX_BLINK 1
+# define M_BLINK 0
+#endif
+
+#define MAX_PAIR 255 /* because our_pairs[] type is unsigned-char */
+static unsigned char our_pairs[2]
+[MAX_BLINK]
+[MAX_COLOR + 1]
+[MAX_COLOR + 1];
+
+static void style_initialiseHashTable(void);
+
+static bucket *new_bucket(const char *name)
+{
+ bucket *result = typecalloc(bucket);
+
+ if (!result)
+ outofmem(__FILE__, "new_bucket");
+ StrAllocCopy(result->name, name);
+ return result;
+}
+
+bucket *nostyle_bucket(void)
+{
+ return new_bucket("<NOSTYLE>");
+}
+
+static char *TrimLowercase(char *buffer)
+{
+ LYRemoveBlanks(buffer);
+ strtolower(buffer);
+ return buffer;
+}
+
+/*
+ * Parse a string containing a combination of video attributes and color.
+ */
+static void parse_either(const char *attrs,
+ int dft_color,
+ int *monop,
+ int *colorp)
+{
+ int value;
+ char *temp_attrs = NULL;
+
+ if (StrAllocCopy(temp_attrs, attrs) != NULL) {
+ char *to_free = temp_attrs;
+
+ while (*temp_attrs != '\0') {
+ char *next = StrChr(temp_attrs, '+');
+ char save = (char) ((next != NULL) ? *next : '\0');
+
+ if (next == NULL)
+ next = temp_attrs + strlen(temp_attrs);
+
+ if (save != 0)
+ *next = '\0';
+ if ((value = string_to_attr(temp_attrs)) != 0)
+ *monop |= value;
+ else if (colorp != 0
+ && (value = check_color(temp_attrs, dft_color)) != ERR_COLOR)
+ *colorp = value;
+
+ temp_attrs = next;
+ if (save != '\0')
+ *temp_attrs++ = save;
+ }
+ FREE(to_free);
+ }
+}
+
+/* icky parsing of the style options */
+static void parse_attributes(const char *mono,
+ const char *fg,
+ const char *bg,
+ int style,
+ const char *element)
+{
+ int mA = A_NORMAL;
+ int fA = default_fg;
+ int bA = default_bg;
+ int cA = A_NORMAL;
+ int newstyle = color_style_1(element);
+ int colored_attr;
+
+ CTRACE2(TRACE_STYLE, (tfp, "CSS(PA):style d=%d / h=%d, e=%s\n",
+ style, newstyle, element));
+
+ parse_either(mono, ERR_COLOR, &mA, (int *) 0);
+ parse_either(bg, default_bg, &cA, &bA);
+ parse_either(fg, default_fg, &cA, &fA);
+
+ if (style == -1) { /* default */
+ CTRACE2(TRACE_STYLE, (tfp, "CSS(DEF):default_fg=%d, default_bg=%d\n",
+ fA, bA));
+ default_fg = fA;
+ default_bg = bA;
+ default_color_reset = TRUE;
+ return;
+ }
+ if (fA == NO_COLOR) {
+ bA = NO_COLOR;
+ } else if (COLORS) {
+#ifdef USE_BLINK
+ if (term_blink_is_boldbg) {
+ if (fA >= COLORS)
+ cA = A_BOLD;
+ if (bA >= COLORS)
+ cA |= M_BLINK;
+ } else
+#endif
+ if (fA >= COLORS || bA >= COLORS)
+ cA = A_BOLD;
+ if (fA >= COLORS)
+ fA %= COLORS;
+ if (bA >= COLORS)
+ bA %= COLORS;
+ } else {
+ cA = A_BOLD;
+ fA = NO_COLOR;
+ bA = NO_COLOR;
+ }
+
+ /*
+ * If we have colour, and space to create a new colour attribute,
+ * and we have a valid colour description, then add this style
+ */
+ if (lynx_has_color && colorPairs < COLOR_PAIRS - 1 && fA != NO_COLOR) {
+ int curPair = 0;
+ int iFg = (1 + (fA >= 0 ? fA : 0));
+ int iBg = (1 + (bA >= 0 ? bA : 0));
+ int iBold = !!((unsigned) cA & A_BOLD);
+ int iBlink = !!((unsigned) cA & M_BLINK);
+
+ CTRACE2(TRACE_STYLE, (tfp, "parse_attributes %d/%d %d/%d %#x\n",
+ fA, default_fg, bA, default_bg, (unsigned) cA));
+ if (fA < MAX_COLOR
+ && bA < MAX_COLOR
+#ifdef USE_CURSES_PAIR_0
+ && (cA != A_NORMAL || fA != default_fg || bA != default_bg)
+#endif
+ && curPair < MAX_PAIR) {
+ if (our_pairs[iBold][iBlink][iFg][iBg] != 0) {
+ curPair = our_pairs[iBold][iBlink][iFg][iBg];
+ } else {
+ curPair = ++colorPairs;
+ init_pair((short) curPair, (short) fA, (short) bA);
+ our_pairs[iBold][iBlink][iFg][iBg] = UCH(curPair);
+ }
+ }
+ CTRACE2(TRACE_STYLE, (tfp, "CSS(CURPAIR):%d\n", curPair));
+ colored_attr = ((int) COLOR_PAIR(curPair)) | ((int) cA);
+ if (style < DSTYLE_ELEMENTS)
+ setStyle(style, colored_attr, cA, mA);
+ setHashStyle(newstyle, colored_attr, cA, mA, element);
+ } else {
+ if (lynx_has_color && fA != NO_COLOR) {
+ CTRACE2(TRACE_STYLE,
+ (tfp, "CSS(NC): maximum of %d colorpairs exhausted\n",
+ COLOR_PAIRS - 1));
+ }
+ /* only mono is set */
+ if (style < DSTYLE_ELEMENTS)
+ setStyle(style, -1, -1, mA);
+ setHashStyle(newstyle, -1, -1, mA, element);
+ }
+}
+
+/* parse a style option of the format
+ * STYLE:<OBJECT>:FG:BG
+ */
+static void parse_style(char *param)
+{
+ /* *INDENT-OFF* */
+ static struct {
+ const char *name;
+ int style;
+ int *set_hash;
+ } table[] = {
+ { "default", -1, 0 }, /* default fg/bg */
+ { "alink", DSTYLE_ALINK, 0 }, /* active link */
+ { "a", DSTYLE_LINK, 0 }, /* normal link */
+ { "a", HTML_A, 0 }, /* normal link */
+ { "status", DSTYLE_STATUS, 0 }, /* status bar */
+ { "label", DSTYLE_OPTION, 0 }, /* [INLINE]'s */
+ { "value", DSTYLE_VALUE, 0 }, /* [INLINE]'s */
+ { "normal", DSTYLE_NORMAL, 0 },
+ { "candy", DSTYLE_CANDY, 0 }, /* [INLINE]'s */
+ { "whereis", DSTYLE_WHEREIS, &s_whereis },
+ { "edit.active.pad", DSTYLE_ELEMENTS, &s_aedit_pad },
+ { "edit.active.arrow", DSTYLE_ELEMENTS, &s_aedit_arr },
+ { "edit.active.marked", DSTYLE_ELEMENTS, &s_aedit_sel },
+ { "edit.active", DSTYLE_ELEMENTS, &s_aedit },
+ { "edit.current", DSTYLE_ELEMENTS, &s_curedit },
+ { "edit.prompt.pad", DSTYLE_ELEMENTS, &s_prompt_edit_pad },
+ { "edit.prompt.arrow", DSTYLE_ELEMENTS, &s_prompt_edit_arr },
+ { "edit.prompt.marked", DSTYLE_ELEMENTS, &s_prompt_sel },
+ { "edit.prompt", DSTYLE_ELEMENTS, &s_prompt_edit },
+ { "forwbackw.arrow", DSTYLE_ELEMENTS, &s_forw_backw },
+ { "hot.paste", DSTYLE_ELEMENTS, &s_hot_paste },
+ { "menu.frame", DSTYLE_ELEMENTS, &s_menu_frame },
+ { "menu.bg", DSTYLE_ELEMENTS, &s_menu_bg },
+ { "menu.n", DSTYLE_ELEMENTS, &s_menu_number },
+ { "menu.entry", DSTYLE_ELEMENTS, &s_menu_entry },
+ { "menu.active", DSTYLE_ELEMENTS, &s_menu_active },
+ { "menu.sb", DSTYLE_ELEMENTS, &s_menu_sb },
+ };
+ /* *INDENT-ON* */
+
+ unsigned n;
+ BOOL found = FALSE;
+
+ char *buffer = 0;
+ char *tmp = 0;
+ char *element, *mono;
+ const char *fg, *bg;
+
+ if (param == 0)
+ return;
+ CTRACE2(TRACE_STYLE, (tfp, "parse_style(%s)\n", param));
+ StrAllocCopy(buffer, param);
+ if (buffer == 0)
+ return;
+
+ TrimLowercase(buffer);
+ if ((tmp = StrChr(buffer, ':')) == 0) {
+ fprintf(stderr, gettext("\
+Syntax Error parsing style in lss file:\n\
+[%s]\n\
+The line must be of the form:\n\
+OBJECT:MONO:COLOR (ie em:bold:brightblue:white)\n\
+where OBJECT is one of EM,STRONG,B,I,U,BLINK etc.\n\n"), buffer);
+ exit_immediately(EXIT_FAILURE);
+ }
+ *tmp = '\0';
+ element = buffer;
+
+ mono = tmp + 1;
+ tmp = StrChr(mono, ':');
+
+ if (!tmp) {
+ fg = "nocolor";
+ bg = "nocolor";
+ } else {
+ *tmp = '\0';
+ fg = tmp + 1;
+ tmp = StrChr(fg, ':');
+ if (!tmp)
+ bg = "default";
+ else {
+ *tmp = '\0';
+ bg = tmp + 1;
+ }
+ }
+
+ CTRACE2(TRACE_STYLE, (tfp, "CSSPARSE:%s => %d %s\n",
+ element, color_style_1(element),
+ (hashStyles[color_style_1(element)].used)
+ ? "used"
+ : ""));
+
+ /*
+ * We use some pseudo-elements, so catch these first
+ */
+ for (n = 0; n < TABLESIZE(table); n++) {
+ if (!strcasecomp(element, table[n].name)) {
+ parse_attributes(mono, fg, bg, table[n].style, table[n].name);
+ if (table[n].set_hash != 0)
+ *(table[n].set_hash) = color_style_1(table[n].name);
+ found = TRUE;
+ break;
+ }
+ }
+
+ if (found) {
+ if (!strcasecomp(element, "normal")) {
+ /* added - kw */
+ parse_attributes(mono, fg, bg, DSTYLE_NORMAL, "html");
+ s_normal = color_style_1("html"); /* rather bizarre... - kw */
+
+ LYnormalColor();
+ }
+ } else {
+ /* It must be a HTML element, so look through the list until we find it. */
+ int element_number = -1;
+ HTTag *t = SGMLFindTag(&HTML_dtd, element);
+
+ if (t && t->name) {
+ element_number = (int) (t - HTML_dtd.tags);
+ }
+ if (element_number >= HTML_A &&
+ element_number < HTML_ELEMENTS) {
+ parse_attributes(mono, fg, bg, element_number + STARTAT, element);
+ } else {
+ parse_attributes(mono, fg, bg, DSTYLE_ELEMENTS, element);
+ }
+ }
+ FREE(buffer);
+}
+
+static void style_deleteStyleList(void)
+{
+ LYFreeStringList(lss_styles);
+ lss_styles = NULL;
+}
+
+static void free_lss_list(void)
+{
+ LSS_NAMES *obj;
+
+ while ((obj = HTList_objectAt(list_of_lss_files, 0)) != 0) {
+ FREE(obj->given);
+ FREE(obj->actual);
+ FREE(obj);
+ if (!HTList_removeObject(list_of_lss_files, obj)) {
+ break;
+ }
+ }
+ HTList_delete(list_of_lss_files);
+}
+
+static void free_colorstylestuff(void)
+{
+ if (TRACE_STYLE) {
+ report_hashStyles();
+ }
+ style_initialiseHashTable();
+ free_hashStyles();
+ style_deleteStyleList();
+ memset(our_pairs, 0, sizeof(our_pairs));
+ FreeCachedStyles();
+}
+
+/* Set all the buckets in the hash table to be empty */
+static void style_initialiseHashTable(void)
+{
+ int i;
+ static BOOL firsttime = TRUE;
+
+ for (i = 0; i < CSHASHSIZE; i++) {
+ hashStyles[i].used = FALSE;
+ }
+ if (firsttime) {
+ firsttime = FALSE;
+#ifdef LY_FIND_LEAKS
+ atexit(free_colorstylestuff);
+ atexit(free_colorstyle_leaks);
+#endif
+ }
+ s_alink = color_style_1("alink");
+ s_a = color_style_1("a");
+ s_status = color_style_1("status");
+ s_alert = color_style_1("alert");
+ s_title = color_style_1("title");
+#ifdef USE_SCROLLBAR
+ s_sb_bar = color_style_1("scroll.bar");
+ s_sb_bg = color_style_1("scroll.back");
+ s_sb_aa = color_style_1("scroll.arrow");
+ s_sb_naa = color_style_1("scroll.noarrow");
+#endif
+}
+
+/*
+ * Initialise the default style sheet to match the vanilla-curses lynx.
+ */
+static void initialise_default_stylesheet(void)
+{
+ /* Use the data setup in USE_COLOR_TABLE */
+ /* *INDENT-OFF* */
+ static const struct {
+ int color; /* index into lynx_color_pairs[] */
+ const char *type;
+ } table2[] = {
+ /*
+ * non-color-style colors encode bold/reverse/underline as a 0-7
+ * index like this:
+ * b,r,u 0
+ * b,r,U 1
+ * b,R,u 2
+ * b,R,U 3
+ * B,r,u 4
+ * B,r,U 5
+ * B,R,u 6
+ * B,R,U 7
+ */
+ { 0, "normal" },
+ { 1, "a" },
+ { 2, "status" },
+ { 4, "b" },
+ { 4, "blink" },
+ { 4, "cite" },
+ { 4, "del" },
+ { 4, "em" },
+ { 4, "i" },
+ { 4, "ins" },
+ { 4, "strike" },
+ { 4, "strong" },
+ { 4, "u" },
+ { 5, "input" },
+ { 6, "alink" },
+ { 7, "whereis" },
+#ifdef USE_PRETTYSRC
+ /* FIXME: HTL_tagspecs_defaults[] has similar info */
+ { 4, "span.htmlsrc_comment" },
+ { 4, "span.htmlsrc_tag" },
+ { 4, "span.htmlsrc_attrib" },
+ { 4, "span.htmlsrc_attrval" },
+ { 4, "span.htmlsrc_abracket" },
+ { 4, "span.htmlsrc_entity" },
+ { 4, "span.htmlsrc_href" },
+ { 4, "span.htmlsrc_entire" },
+ { 4, "span.htmlsrc_badseq" },
+ { 4, "span.htmlsrc_badtag" },
+ { 4, "span.htmlsrc_badattr" },
+ { 4, "span.htmlsrc_sgmlspecial" },
+#endif
+ };
+ /* *INDENT-ON* */
+
+ unsigned n;
+ char *normal = LYgetTableString(0);
+ char *strong = LYgetTableString(4);
+
+ CTRACE1((tfp, "initialise_default_stylesheet\n"));
+
+ /*
+ * For debugging this function, create hash codes for all of the tags.
+ * That makes it simpler to find the cases that are overlooked in the
+ * table.
+ */
+ for (n = 0; n < (unsigned) HTML_dtd.number_of_tags; ++n) {
+ char *name = 0;
+
+ HTSprintf0(&name, "%s:%s", HTML_dtd.tags[n].name, normal);
+ parse_style(name);
+ FREE(name);
+ }
+
+ for (n = 0; n < TABLESIZE(table2); ++n) {
+ int code = table2[n].color;
+ char *name = 0;
+ char *value = 0;
+
+ switch (code) {
+ case 0:
+ value = normal;
+ break;
+ case 4:
+ value = strong;
+ break;
+ default:
+ value = LYgetTableString(code);
+ break;
+ }
+ HTSprintf0(&name, "%s:%s", table2[n].type, value);
+ parse_style(name);
+ FREE(name);
+ if (value != normal && value != strong && value != 0)
+ free(value);
+ }
+ FREE(normal);
+ FREE(strong);
+}
+
+void parse_userstyles(void)
+{
+ char *name;
+ HTList *cur = LYuse_color_style ? lss_styles : 0;
+
+ colorPairs = 0;
+ style_initialiseHashTable();
+
+ if (HTList_isEmpty(cur)) {
+ initialise_default_stylesheet();
+ } else {
+ while ((name = (char *) HTList_nextObject(cur)) != NULL) {
+ CTRACE2(TRACE_STYLE, (tfp, "LSS:%s\n",
+ (name
+ ? name
+ : "!?! empty !?!")));
+ if (name != NULL)
+ parse_style(name);
+ }
+ }
+
+#define dft_style(a,b) if (a == NOSTYLE) a = b
+ /* *INDENT-OFF* */
+ dft_style(s_prompt_edit, s_normal);
+ dft_style(s_prompt_edit_arr, s_prompt_edit);
+ dft_style(s_prompt_edit_pad, s_prompt_edit);
+ dft_style(s_prompt_sel, s_prompt_edit);
+ dft_style(s_aedit, s_alink);
+ dft_style(s_aedit_arr, s_aedit);
+ dft_style(s_aedit_pad, s_aedit);
+ dft_style(s_curedit, s_aedit);
+ dft_style(s_aedit_sel, s_aedit);
+ dft_style(s_menu_bg, s_normal);
+ dft_style(s_menu_entry, s_menu_bg);
+ dft_style(s_menu_frame, s_menu_bg);
+ dft_style(s_menu_number, s_menu_bg);
+ dft_style(s_menu_active, s_alink);
+ /* *INDENT-ON* */
+
+}
+
+/* Add a STYLE: option line to our list. Process "default:" early
+ * for it to have the same semantic as other lines: works at any place
+ * of the style file, the first line overrides the later ones.
+ */
+static void HStyle_addStyle(char *buffer)
+{
+ char *name = NULL;
+
+ CTRACE1((tfp, "HStyle_addStyle(%s)\n", buffer));
+
+ StrAllocCopy(name, buffer);
+ TrimLowercase(name);
+
+ if (lss_styles == NULL)
+ lss_styles = HTList_new();
+
+ if (!strncasecomp(name, "default:", 8)) {
+ /* default fg/bg */
+ CTRACE2(TRACE_STYLE, (tfp, "READCSS.default%s:%s\n",
+ (default_color_reset ? ".ignore" : ""),
+ name ? name : "!?! empty !?!"));
+ if (!default_color_reset)
+ parse_style(name);
+ FREE(name);
+ return; /* do not need to process it again */
+ }
+ CTRACE2(TRACE_STYLE, (tfp, "READCSS:%s\n", name ? name : "!?! empty !?!"));
+ HTList_addObject(lss_styles, name);
+}
+
+static int style_readFromFileREC(char *lss_filename,
+ char *parent_filename)
+{
+ FILE *fh;
+ char *buffer = NULL;
+
+ CTRACE2(TRACE_STYLE, (tfp, "CSS:Reading styles from file: %s\n",
+ lss_filename ? lss_filename : "?!? empty ?!?"));
+ if (isEmpty(lss_filename))
+ return -1;
+ if ((fh = LYOpenCFG(lss_filename, parent_filename, LYNX_LSS_FILE)) == 0) {
+ /* this should probably be an alert or something */
+ CTRACE2(TRACE_STYLE, (tfp,
+ "CSS:Can't open style file '%s', using defaults\n", lss_filename));
+ return -1;
+ }
+
+ if (parent_filename == 0) {
+ free_colorstylestuff();
+ }
+
+ while (LYSafeGets(&buffer, fh) != NULL) {
+ LYTrimTrailing(buffer);
+ LYTrimTail(buffer);
+ LYTrimHead(buffer);
+ if (!strncasecomp(buffer, "include:", 8))
+ style_readFromFileREC(LYSkipBlanks(buffer + 8), lss_filename);
+ else if (buffer[0] != '#' && strlen(buffer) != 0)
+ HStyle_addStyle(buffer);
+ }
+
+ LYCloseInput(fh);
+ if ((parent_filename == 0) && LYCursesON)
+ parse_userstyles();
+ return 0;
+}
+
+int style_readFromFile(char *filename)
+{
+ return style_readFromFileREC(filename, (char *) 0);
+}
+
+/* Used in HTStructured methods: - kw */
+
+void TrimColorClass(const char *tagname,
+ char *styleclassname,
+ int *phcode)
+{
+ char *end, *start = NULL, *lookfrom;
+ char tmp[64];
+
+ sprintf(tmp, ";%.*s", (int) sizeof(tmp) - 3, tagname);
+ TrimLowercase(tmp);
+
+ if ((lookfrom = styleclassname) != 0) {
+ do {
+ end = start;
+ start = strstr(lookfrom, tmp);
+ if (start)
+ lookfrom = start + 1;
+ }
+ while (start);
+ /* trim the last matching element off the end
+ * - should match classes here as well (rp)
+ */
+ if (end)
+ *end = '\0';
+ }
+ *phcode = color_style_1(lookfrom && *lookfrom ? lookfrom : &tmp[1]);
+}
+
+/* This function is designed as faster analog to TrimColorClass.
+ * It assumes that tag_name is present in stylename! -HV
+ */
+void FastTrimColorClass(const char *tag_name,
+ unsigned name_len,
+ char *stylename,
+ char **pstylename_end, /*will be modified */
+ int *phcode) /*will be modified */
+{
+ char *tag_start = *pstylename_end;
+ BOOLEAN found = FALSE;
+
+ CTRACE2(TRACE_STYLE,
+ (tfp, "STYLE.fast-trim: [%s] from [%s]: ",
+ tag_name, stylename));
+ while (tag_start >= stylename) {
+ for (; (tag_start >= stylename) && (*tag_start != ';'); --tag_start) ;
+ if (!strncasecomp(tag_start + 1, tag_name, (int) name_len)) {
+ found = TRUE;
+ break;
+ }
+ --tag_start;
+ }
+ if (found) {
+ *tag_start = '\0';
+ *pstylename_end = tag_start;
+ }
+ CTRACE2(TRACE_STYLE, (tfp, found ? "success.\n" : "failed.\n"));
+ *phcode = color_style_1(tag_start + 1);
+}
+
+/* This is called each time lss styles are read. It will fill
+ * each element of 'cached_tag_styles' -HV
+ */
+void cache_tag_styles(void)
+{
+ int i;
+
+ for (i = 0; i < HTML_ELEMENTS; ++i) {
+ cached_tag_styles[i] = color_style_1(HTML_dtd.tags[i].name);
+ }
+}
+
+#define SIZEOF_CACHED_STYLES (unsigned) (cached_styles_rows * cached_styles_cols)
+
+static unsigned *RefCachedStyle(int y, int x)
+{
+ unsigned *result = 0;
+
+ if (cached_styles_ptr == 0) {
+ cached_styles_rows = display_lines;
+ cached_styles_cols = LYcols;
+ cached_styles_ptr = typecallocn(unsigned, SIZEOF_CACHED_STYLES);
+ }
+ if (y >= 0 &&
+ x >= 0 &&
+ y < cached_styles_rows &&
+ x < cached_styles_cols) {
+ result = cached_styles_ptr + (y * cached_styles_cols) + x;
+ }
+ return result;
+}
+
+BOOL ValidCachedStyle(int y, int x)
+{
+ return (BOOL) (RefCachedStyle(y, x) != 0);
+}
+
+unsigned GetCachedStyle(int y, int x)
+{
+ unsigned value = 0;
+ unsigned *cache = RefCachedStyle(y, x);
+
+ if (cache != 0) {
+ value = *cache;
+ }
+ return value;
+}
+
+void SetCachedStyle(int y, int x, unsigned value)
+{
+ unsigned *cache = RefCachedStyle(y, x);
+
+ if (cache != 0) {
+ *cache = value;
+ }
+}
+
+void ResetCachedStyles(void)
+{
+ if (cached_styles_ptr != NULL) {
+ memset(cached_styles_ptr, 0, sizeof(unsigned) * SIZEOF_CACHED_STYLES);
+ }
+}
+
+void FreeCachedStyles(void)
+{
+ if (cached_styles_ptr != NULL) {
+ FREE(cached_styles_ptr);
+ cached_styles_rows = 0;
+ cached_styles_cols = 0;
+ }
+}
+
+/*
+ * Recompute the pairs associated with the color style.
+ */
+void update_color_style(void)
+{
+ CTRACE((tfp, "update_color_style %p\n", (void *) lss_styles));
+ memset(our_pairs, 0, sizeof(our_pairs));
+ parse_userstyles();
+}
+
+static char *find_lss_file(const char *nominal)
+{
+ return LYFindConfigFile(nominal, LYNX_LSS_FILE);
+}
+
+void clear_lss_list(void)
+{
+ CTRACE((tfp, "clear_lss_list()\n"));
+ free_lss_list();
+ empty_lss_list = TRUE;
+}
+
+/*
+ * Add an entry to the lss-list, and cache the resolved filename if known.
+ */
+void add_to_lss_list(const char *source, const char *resolved)
+{
+ LSS_NAMES *obj;
+ LSS_NAMES *chk;
+ BOOLEAN found = FALSE;
+ int position = 0;
+
+#ifdef LY_FIND_LEAKS
+ atexit(free_colorstyle_leaks);
+#endif
+
+ CTRACE((tfp, "add_to_lss_list(\"%s\", \"%s\")\n",
+ NonNull(source),
+ NonNull(resolved)));
+
+ if (list_of_lss_files == 0) {
+ list_of_lss_files = HTList_new();
+ }
+
+ while ((chk = HTList_objectAt(list_of_lss_files, position++)) != 0) {
+ if (!strcmp(source, chk->given)) {
+ found = TRUE;
+ if (resolved && !chk->actual) {
+ StrAllocCopy(chk->actual, resolved);
+ }
+ break;
+ }
+ }
+
+ if (!found) {
+ obj = typecalloc(LSS_NAMES);
+ if (obj == NULL)
+ outofmem(__FILE__, "add_to_lss_list");
+
+ StrAllocCopy(obj->given, source);
+ StrAllocCopy(obj->actual, resolved);
+ HTList_appendObject(list_of_lss_files, obj);
+ empty_lss_list = FALSE;
+ }
+}
+
+/*
+ * This is called after reading lynx.cfg, to set the initial value for the
+ * lss-file, and read its data.
+ */
+void init_color_styles(char **from_cmdline, const char *default_styles)
+{
+ char *user_lss_file = *from_cmdline;
+ char *cp;
+
+ /*
+ * If a command-line "-lss" option was given, or if an environment variable
+ * is found, use that in preference to data from lynx.cfg
+ */
+ if (user_lss_file == 0)
+ user_lss_file = LYGetEnv("LYNX_LSS");
+ if (user_lss_file == 0)
+ user_lss_file = LYGetEnv("lynx_lss");
+ if (user_lss_file != 0)
+ empty_lss_list = (*user_lss_file == '\0');
+
+ /*
+ * If the color-style is explicitly emptied, go no further.
+ */
+ if (empty_lss_list) {
+ CTRACE((tfp, "init_color_styles: overridden/empty\n"));
+ return;
+ } else if (list_of_lss_files == 0) {
+ char *source = 0;
+ char *config;
+
+ StrAllocCopy(source, default_styles);
+ config = source;
+ while ((cp = LYstrsep(&config, ";")) != 0) {
+ char *target;
+
+ target = find_lss_file(LYPathLeaf(cp));
+ if (target != 0) {
+ add_to_lss_list(cp, target);
+ FREE(target);
+ }
+ }
+ FREE(source);
+ }
+
+ if (user_lss_file != 0) {
+ FREE(lynx_lss_file);
+ lynx_lss_file = find_lss_file(cp = user_lss_file);
+ *from_cmdline = 0;
+ } else {
+ lynx_lss_file = find_lss_file(cp = DeConst(LYNX_LSS_FILE));
+ }
+ CTRACE1((tfp, "init_color_styles(%s)\n", NonNull(lynx_lss_file)));
+
+ if (isEmpty(lynx_lss_file))
+ return;
+ /*
+ * If the lynx-style file is not available, inform the user and exit.
+ */
+ if (!LYCanReadFile(lynx_lss_file)) {
+ fprintf(stderr, gettext("\nLynx file \"%s\" is not available.\n\n"),
+ NonNull(cp));
+ exit_immediately(EXIT_FAILURE);
+ }
+
+ /*
+ * Otherwise, load the initial lss-file and add it to the list for the
+ * options menu.
+ */
+ style_readFromFile(lynx_lss_file);
+ add_to_lss_list(LYPathLeaf(lynx_lss_file), lynx_lss_file);
+#ifndef NO_OPTION_FORMS
+ build_lss_enum(list_of_lss_files);
+#endif
+}
+
+void reinit_color_styles(void)
+{
+#ifdef USE_PRETTYSRC
+ int cs;
+
+ for (cs = 0; cs < HTL_num_lexemes; ++cs) {
+ html_src_clean_item((HTlexeme) cs);
+ }
+#endif
+ free_colorstylestuff();
+ style_readFromFile(lynx_lss_file);
+}
+
+#endif /* USE_COLOR_STYLE */
diff --git a/src/LYStyle.h b/src/LYStyle.h
new file mode 100644
index 0000000..a7e5e81
--- /dev/null
+++ b/src/LYStyle.h
@@ -0,0 +1,88 @@
+/* $LynxId: LYStyle.h,v 1.20 2020/01/21 21:35:25 tom Exp $ */
+#ifndef LYSTYLE_H
+#define LYSTYLE_H
+
+#include <HTUtils.h>
+
+#ifdef USE_COLOR_STYLE
+
+#include <AttrList.h>
+#include <HTMLDTD.h>
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+ typedef struct {
+ char *given;
+ char *actual;
+ } LSS_NAMES;
+
+ /* list of elements */ extern const SGML_dtd HTML_dtd;
+
+ /* array of currently set styles */
+ extern HTCharStyle displayStyles[DSTYLE_ELEMENTS];
+
+ /* Set all the buckets in the hash table to be empty */
+ extern void parse_userstyles(void);
+
+ extern void style_defaultStyleSheet(void);
+
+ extern int style_readFromFile(char *file);
+
+ extern void TrimColorClass(const char *tagname,
+ char *styleclassname,
+ int *phcode);
+
+ /* This is an array of styles for tags that don't specify 'class' - the
+ * values from that array will be suggested by SGML.c by setting the
+ * following variable. A value of -1 means that the style value should be
+ * calculated honestly -HV
+ */
+ extern int cached_tag_styles[HTML_ELEMENTS];
+
+ /* The style for current tag is suggested in current_tag_style. If
+ * force_current_tag_style =TRUE, then no attempts to calculate the color
+ * style for current tag should be made - the value of 'current_tag_style'
+ * must be used.
+ */
+ extern int current_tag_style;
+ extern BOOL force_current_tag_style;
+
+ extern BOOL force_classname;
+
+ /* If force_current_tag_style =TRUE, then here will be the classname (this
+ * is done to avoid copying the class name to the buffer class_name.
+ */
+ extern char *forced_classname;
+
+ /* This is called each time lss styles are read. It will fill each element
+ * of 'cached_tag_styles' -HV
+ */
+ extern void cache_tag_styles(void);
+
+ /* use this for reading the end of string found during last invocation of
+ * TrimColorClass.
+ */
+ extern void FastTrimColorClass(const char *tag_name,
+ unsigned name_len,
+ char *stylename,
+ char **pstylename_end,
+ int *hcode);
+
+ /*
+ * Functions for cached-styles
+ */
+ extern BOOL ValidCachedStyle(int y, int x);
+ extern unsigned GetCachedStyle(int y, int x);
+ extern void FreeCachedStyles(void);
+ extern void ResetCachedStyles(void);
+ extern void SetCachedStyle(int y, int x, unsigned value);
+
+#ifdef __cplusplus
+}
+#endif
+#endif /* USE_COLOR_STYLE */
+extern int lynx_has_color;
+
+#endif /* LYSTYLE_H */
diff --git a/src/LYTraversal.c b/src/LYTraversal.c
new file mode 100644
index 0000000..2aa95fa
--- /dev/null
+++ b/src/LYTraversal.c
@@ -0,0 +1,182 @@
+/*
+ * $LynxId: LYTraversal.c,v 1.30 2010/09/24 22:57:01 tom Exp $
+ */
+#include <HTUtils.h>
+#include <LYGlobalDefs.h>
+#include <LYUtils.h>
+#include <LYClean.h>
+#include <LYCurses.h>
+#include <LYStrings.h>
+#include <LYTraversal.h>
+
+#include <LYexit.h>
+#include <LYLeaks.h>
+
+/* routines to handle special traversal feature */
+
+static void final_perror(const char *msg, int clean_flag)
+{
+ int saved_errno = errno;
+
+ if (LYCursesON) {
+ if (clean_flag)
+ cleanup();
+ else
+ stop_curses();
+ }
+ set_errno(saved_errno);
+ perror(msg);
+}
+
+static void exit_with_perror(const char *msg)
+{
+ final_perror(msg, TRUE);
+ exit_immediately(EXIT_FAILURE);
+}
+
+BOOLEAN lookup_link(char *target)
+{
+ FILE *ifp;
+ char *buffer = NULL;
+ char *line = NULL;
+ int result = FALSE;
+
+ if ((ifp = fopen(TRAVERSE_FILE, TXT_R)) == NULL) {
+ if ((ifp = LYNewTxtFile(TRAVERSE_FILE)) == NULL) {
+ exit_with_perror(CANNOT_OPEN_TRAV_FILE);
+ } else {
+ LYCloseOutput(ifp);
+ return (FALSE);
+ }
+ }
+
+ HTSprintf0(&line, "%s\n", target);
+
+ while (LYSafeGets(&buffer, ifp) != NULL) {
+ if (STREQ(line, buffer)) {
+ result = TRUE;
+ break;
+ }
+ } /* end while */
+ FREE(line);
+ FREE(buffer);
+
+ LYCloseInput(ifp);
+ return (BOOL) (result);
+}
+
+void add_to_table(char *target)
+{
+
+ FILE *ifp;
+
+ if ((ifp = LYAppendToTxtFile(TRAVERSE_FILE)) == NULL) {
+ exit_with_perror(CANNOT_OPEN_TRAV_FILE);
+ }
+
+ fprintf(ifp, "%s\n", target);
+
+ LYCloseOutput(ifp);
+}
+
+void add_to_traverse_list(char *fname, char *prev_link_name)
+{
+
+ FILE *ifp;
+
+ if ((ifp = LYAppendToTxtFile(TRAVERSE_FOUND_FILE)) == NULL) {
+ exit_with_perror(CANNOT_OPEN_TRAF_FILE);
+ }
+
+ fprintf(ifp, "%s\t%s\n", fname, prev_link_name);
+
+ LYCloseOutput(ifp);
+}
+
+void dump_traversal_history(void)
+{
+ int x;
+ FILE *ifp;
+
+ if (nhist <= 0)
+ return;
+
+ if ((ifp = LYAppendToTxtFile(TRAVERSE_FILE)) == NULL) {
+ final_perror(CANNOT_OPEN_TRAV_FILE, FALSE);
+ return;
+ }
+
+ fprintf(ifp, "\n\n%s\n\n\t %s\n\n",
+ TRAV_WAS_INTERRUPTED,
+ gettext("here is a list of the history stack so that you may rebuild"));
+
+ for (x = nhist - 1; x >= 0; x--) {
+ fprintf(ifp, "%s\t%s\n", HDOC(x).title, HDOC(x).address);
+ }
+
+ LYCloseOutput(ifp);
+}
+
+void add_to_reject_list(char *target)
+{
+
+ FILE *ifp;
+
+ CTRACE((tfp, "add_to_reject_list(%s)\n", target));
+
+ if ((ifp = LYAppendToTxtFile(TRAVERSE_REJECT_FILE)) == NULL) {
+ exit_with_perror(CANNOT_OPEN_REJ_FILE);
+ }
+
+ fprintf(ifp, "%s\n", target);
+
+ LYCloseOutput(ifp);
+}
+
+/* there need not be a reject file, so if it doesn't open, just return
+ FALSE, meaning "target not in reject file" If the last character in
+ a line in a reject file is "*", then also reject if target matches up to
+ that point in the string
+ Blank lines are ignored
+ Lines that contain just a * are allowed, but since they mean "reject
+ everything" it shouldn't come up much!
+ */
+
+BOOLEAN lookup_reject(char *target)
+{
+ FILE *ifp;
+ char *buffer = NULL;
+ char *line = NULL;
+ size_t len;
+ int result = FALSE;
+
+ if ((ifp = fopen(TRAVERSE_REJECT_FILE, TXT_R)) == NULL) {
+ return (FALSE);
+ }
+
+ HTSprintf0(&line, "%s", target);
+
+ while (LYSafeGets(&buffer, ifp) != NULL && !result) {
+ LYTrimTrailing(buffer);
+ len = strlen(buffer);
+ if (len != 0) { /* if not an empty line */
+ if (buffer[len - 1] == '*') {
+ /* if last char is * and the rest of the chars match */
+ if ((len == 1) || (StrNCmp(line, buffer, len - 1) == 0)) {
+ result = TRUE;
+ }
+ } else {
+ if (STREQ(line, buffer)) {
+ result = TRUE;
+ }
+ }
+ }
+ } /* end while loop over the file */
+ FREE(buffer);
+ FREE(line);
+
+ LYCloseInput(ifp);
+
+ CTRACE((tfp, "lookup_reject(%s) -> %d\n", target, result));
+ return (BOOL) (result);
+}
diff --git a/src/LYTraversal.h b/src/LYTraversal.h
new file mode 100644
index 0000000..8e712e5
--- /dev/null
+++ b/src/LYTraversal.h
@@ -0,0 +1,23 @@
+/* traversal.c function declarations
+*/
+#ifndef TRAVERSAL_H
+#define TRAVERSAL_H
+
+#ifndef HTUTILS_H
+#include <HTUtils.h> /* BOOL, ARGS */
+#endif
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+ extern BOOLEAN lookup_link(char *target);
+ extern void add_to_table(char *target);
+ extern void add_to_traverse_list(char *fname, char *prev_link_name);
+ extern void dump_traversal_history(void);
+ extern void add_to_reject_list(char *target);
+ extern BOOLEAN lookup_reject(char *target);
+
+#ifdef __cplusplus
+}
+#endif
+#endif /* TRAVERSAL_H */
diff --git a/src/LYUpload.c b/src/LYUpload.c
new file mode 100644
index 0000000..a83a103
--- /dev/null
+++ b/src/LYUpload.c
@@ -0,0 +1,221 @@
+/*
+ * $LynxId: LYUpload.c,v 1.41 2021/07/29 20:32:26 tom Exp $
+ *
+ * Routines to upload files to the local filesystem.
+ * Created by: Rick Mallett, Carleton University
+ * Report problems to rmallett@ccs.carleton.ca
+ * Modified 15-Dec-95 George Lindholm (lindholm@ucs.ubc.ca):
+ * Reread the upload menu page every time, in case the "upload" directory
+ * has changed (make the current directory that for the upload process).
+ * Prompt for the upload file name if there is no "%s" in the command
+ * string. Most protocols allow the user to specify the file name
+ * from the client side. Xmodem appears to be the only that can't
+ * figure out the filename from the transfer data so it needs the
+ * information from lynx (or an upload script which prompts for it).
+ * On the other hand, zmodem aborts when you give it a filename on
+ * the command line (great way of bypassing the nodotfile code :=( ).
+ */
+
+#include <HTUtils.h>
+#include <HTFile.h>
+#include <HTParse.h>
+#include <HTAlert.h>
+#include <LYCurses.h>
+#include <LYUtils.h>
+#include <LYGlobalDefs.h>
+#include <LYStrings.h>
+#include <LYClean.h>
+#include <LYGetFile.h>
+#include <LYUpload.h>
+#include <LYLocal.h>
+
+#include <LYexit.h>
+#include <LYLeaks.h>
+
+#define SUBDIR_COMMAND "cd %s ; "
+
+/*
+ * LYUpload uploads a file to a given location using a specified upload method.
+ * It parses an incoming link that looks like:
+ * LYNXDIRED://UPLOAD=<#>/TO=<STRING>
+ */
+int LYUpload(char *line)
+{
+ char *method, *directory;
+ int method_number;
+ int count;
+ char *the_upload = 0;
+ char tmpbuf[LY_MAXPATH];
+ char *filename = NULL;
+ lynx_list_item_type *upload_command = 0;
+ char *the_command = 0;
+
+ /*
+ * Use configured upload commands.
+ */
+ if ((directory = LYstrstr(line, "TO=")) == NULL)
+ goto failed;
+ *(directory - 1) = '\0';
+ /* go past "TO=" */
+ directory += 3;
+
+ if ((method = LYstrstr(line, "UPLOAD=")) == NULL)
+ goto failed;
+ /*
+ * Go past "UPLOAD=".
+ */
+ method += 7;
+ method_number = atoi(method);
+
+ for (count = 0, upload_command = uploaders; count < method_number;
+ count++, upload_command = upload_command->next) ; /* null body */
+
+ /*
+ * Parsed out the Method and the Location?
+ */
+ if (upload_command->command == NULL) {
+ HTAlert(gettext("ERROR! - upload command is misconfigured"));
+ goto failed;
+ }
+
+ /*
+ * Care about the local name?
+ */
+ if (HTCountCommandArgs(upload_command->command)) {
+ /*
+ * Commands have the form "command %s [etc]" where %s is the filename.
+ */
+ _statusline(FILENAME_PROMPT);
+ retry:
+ *tmpbuf = '\0';
+ if (LYGetStr(tmpbuf, FALSE, sizeof(tmpbuf), NORECALL) < 0)
+ goto cancelled;
+
+ if (*tmpbuf == '\0')
+ goto cancelled;
+
+ if (strstr(tmpbuf, "../") != NULL) {
+ HTAlert(gettext("Illegal redirection \"../\" found! Request ignored."));
+ goto cancelled;
+ } else if (StrChr(tmpbuf, '/') != NULL) {
+ HTAlert(gettext("Illegal character \"/\" found! Request ignored."));
+ goto cancelled;
+ } else if (tmpbuf[0] == '~') {
+ HTAlert(gettext("Illegal redirection using \"~\" found! Request ignored."));
+ goto cancelled;
+ }
+ HTSprintf0(&filename, "%s/%s", directory, tmpbuf);
+
+#ifdef HAVE_POPEN
+ if (LYIsPipeCommand(filename)) {
+ HTAlert(CANNOT_WRITE_TO_FILE);
+ _statusline(NEW_FILENAME_PROMPT);
+ goto retry;
+ }
+#endif
+ switch (LYValidateOutput(filename)) {
+ case 'Y':
+ break;
+ case 'N':
+ goto retry;
+ default:
+ goto cancelled;
+ }
+
+ /*
+ * See if we can write to it.
+ */
+ CTRACE((tfp, "LYUpload: filename is %s", filename));
+
+ HTAddParam(&the_upload, upload_command->command, 1, filename);
+ HTEndParam(&the_upload, upload_command->command, 1);
+ } else { /* No substitution, no changes */
+ StrAllocCopy(the_upload, upload_command->command);
+ }
+
+ HTAddParam(&the_command, SUBDIR_COMMAND, 1, directory);
+ HTEndParam(&the_command, SUBDIR_COMMAND, 1);
+ StrAllocCat(the_command, the_upload);
+
+ CTRACE((tfp, "command: %s\n", the_command));
+
+ stop_curses();
+ LYSystem(the_command);
+ start_curses();
+
+ FREE(the_command);
+ FREE(the_upload);
+#if defined(MULTI_USER_UNIX)
+ if (filename != 0)
+ chmod(filename, HIDE_CHMOD);
+#endif /* UNIX */
+ FREE(filename);
+
+ return 1;
+
+ failed:
+ HTAlert(gettext("Unable to upload file."));
+ return 0;
+
+ cancelled:
+ HTInfoMsg(CANCELLING);
+ return 0;
+}
+
+/*
+ * LYUpload_options writes out the current upload choices to a file so that the
+ * user can select printers in the same way that they select all other links.
+ * Upload links look like:
+ * LYNXDIRED://UPLOAD=<#>/TO=<STRING>
+ */
+int LYUpload_options(char **newfile,
+ char *directory)
+{
+ static char tempfile[LY_MAXPATH];
+ FILE *fp0;
+ lynx_list_item_type *cur_upload;
+ int count;
+ char *curloc = NULL;
+
+ if ((fp0 = InternalPageFP(tempfile, TRUE)) == 0)
+ return (-1);
+
+#ifdef VMS
+ StrAllocCopy(curloc, "/sys$login");
+#else
+ StrAllocCopy(curloc, HTfullURL_toFile(directory));
+ LYTrimPathSep(curloc);
+#endif /* VMS */
+
+ LYLocalFileToURL(newfile, tempfile);
+ LYRegisterUIPage(*newfile, UIP_UPLOAD_OPTIONS);
+
+ BeginInternalPage(fp0, UPLOAD_OPTIONS_TITLE, UPLOAD_OPTIONS_HELP);
+
+ fprintf(fp0, "<pre>\n");
+ fprintf(fp0, " <em>%s</em> %s\n", gettext("Upload To:"), curloc);
+ fprintf(fp0, "\n%s\n", gettext("Upload options:"));
+
+ if (uploaders != NULL) {
+ for (count = 0, cur_upload = uploaders;
+ cur_upload != NULL;
+ cur_upload = cur_upload->next, count++) {
+ fprintf(fp0, " <a href=\"LYNXDIRED://UPLOAD=%d/TO=%s\">",
+ count, curloc);
+ fprintf(fp0, "%s", (cur_upload->name ?
+ cur_upload->name : gettext("No Name Given")));
+ fprintf(fp0, "</a>\n");
+ }
+ } else {
+ fprintf(fp0, " &lt;NONE&gt;\n");
+ }
+
+ fprintf(fp0, "</pre>\n");
+ EndInternalPage(fp0);
+ LYCloseTempFP(fp0);
+
+ LYforce_no_cache = TRUE;
+ FREE(curloc);
+
+ return (0);
+}
diff --git a/src/LYUpload.h b/src/LYUpload.h
new file mode 100644
index 0000000..84a71a0
--- /dev/null
+++ b/src/LYUpload.h
@@ -0,0 +1,17 @@
+#ifndef LYUPLOAD_H
+#define LYUPLOAD_H
+
+#ifndef LYSTRUCTS_H
+#include <LYStructs.h>
+#endif /* LYSTRUCTS_H */
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+ extern int LYUpload(char *line);
+ extern int LYUpload_options(char **newfile, char *directory);
+
+#ifdef __cplusplus
+}
+#endif
+#endif /* LYUPLOAD_H */
diff --git a/src/LYUtils.c b/src/LYUtils.c
new file mode 100644
index 0000000..e45e641
--- /dev/null
+++ b/src/LYUtils.c
@@ -0,0 +1,8038 @@
+/*
+ * $LynxId: LYUtils.c,v 1.309 2024/01/15 17:10:52 tom Exp $
+ */
+#include <HTUtils.h>
+#include <HTTCP.h>
+#include <HTParse.h>
+#include <HTAccess.h>
+#include <HTCJK.h>
+#include <HTAlert.h>
+
+#if defined(__MINGW32__)
+
+extern int kbhit(void); /* FIXME: use conio.h */
+
+#undef UNIX
+
+#elif defined(_WINDOWS)
+
+#ifdef DONT_USE_GETTEXT
+#undef gettext
+#elif defined(HAVE_GETTEXT)
+#undef gettext
+#define gettext conio_gettext
+#else
+#undef gettext
+#endif
+
+#include <conio.h>
+
+#ifdef DONT_USE_GETTEXT
+#define gettext(s) s
+#elif defined(HAVE_GETTEXT)
+#undef gettext
+#ifdef _INTL_REDIRECT_MACROS
+#define gettext libintl_gettext /* restore definition from libintl.h */
+#endif
+#else
+#undef gettext
+#define gettext(s) s
+#endif
+
+#if !defined(kbhit) && defined(_WCONIO_DEFINED)
+#define kbhit() _kbhit() /* reasonably recent conio.h */
+#endif
+#elif defined(__minix)
+#include <termios.h> /* for struct winsize */
+
+#endif /* __MINGW32__ */
+
+#include <LYCurses.h>
+#include <LYHistory.h>
+#include <LYStrings.h>
+#include <LYGlobalDefs.h>
+#include <LYUtils.h>
+#include <LYSignal.h>
+#include <GridText.h>
+#include <LYClean.h>
+#include <LYCharSets.h>
+#include <LYCharUtils.h>
+
+#include <LYMainLoop.h>
+#include <LYKeymap.h>
+
+#ifdef __DJGPP__
+#include <go32.h>
+#include <sys/exceptn.h>
+#endif /* __DJGPP__ */
+
+#ifndef NO_GROUPS
+#include <HTFile.h>
+#endif
+
+#ifdef _WINDOWS /* 1998/04/30 (Thu) 19:04:25 */
+#define GETPID() (unsigned) (getpid() & 0xffff)
+#else
+#define GETPID() (unsigned) getpid()
+#endif /* _WINDOWS */
+
+#ifdef FNAMES_8_3
+#define PID_FMT "%04x"
+#else
+#define PID_FMT "%u"
+#endif
+
+#ifdef DJGPP_KEYHANDLER
+#include <bios.h>
+#endif /* DJGPP_KEYHANDLER */
+
+#ifdef __EMX__
+# define BOOLEAN OS2_BOOLEAN /* Conflicts, but is used */
+# undef HT_ERROR /* Conflicts too */
+# define INCL_PM /* I want some PM functions.. */
+# define INCL_DOSPROCESS /* TIB PIB. */
+# include <os2.h>
+# undef BOOLEAN
+#endif
+
+#ifdef VMS
+#include <descrip.h>
+#include <libclidef.h>
+#include <lib$routines.h>
+#endif /* VMS */
+
+#ifdef HAVE_UTMP
+#include <pwd.h>
+#ifdef UTMPX_FOR_UTMP
+#include <utmpx.h>
+#define utmp utmpx
+#ifdef UTMPX_FILE
+#ifdef UTMP_FILE
+#undef UTMP_FILE
+#endif /* UTMP_FILE */
+#define UTMP_FILE UTMPX_FILE
+#else
+#ifdef __UTMPX_FILE
+#define UTMP_FILE __UTMPX_FILE /* at least in OS/390 S/390 -- gil -- 2100 */
+#else
+#ifndef UTMP_FILE
+#define UTMP_FILE "/var/adm/utmpx" /* Digital Unix 4.0 */
+#endif
+#endif
+#endif /* UTMPX_FILE */
+#else
+#include <utmp.h>
+#endif /* UTMPX_FOR_UTMP */
+#endif /* HAVE_UTMP */
+
+#ifdef NEED_PTEM_H
+/* they neglected to define struct winsize in termios.h -- it's only in
+ * termio.h and ptem.h (the former conflicts with other definitions).
+ */
+#include <sys/stream.h>
+#include <sys/ptem.h>
+#endif
+
+#include <LYLeaks.h>
+
+#ifdef USE_COLOR_STYLE
+#include <AttrList.h>
+#include <LYHash.h>
+#include <LYStyle.h>
+#endif
+
+#ifdef SVR4_BSDSELECT
+extern int BSDselect(int nfds, fd_set * readfds, fd_set * writefds,
+ fd_set * exceptfds, struct timeval *timeout);
+
+#ifdef select
+#undef select
+#endif /* select */
+#define select BSDselect
+#ifdef SOCKS
+#ifdef Rselect
+#undef Rselect
+#endif /* Rselect */
+#define Rselect BSDselect
+#endif /* SOCKS */
+#endif /* SVR4_BSDSELECT */
+
+#ifdef __DJGPP__
+#undef select /* defined to select_s in www_tcp.h */
+#endif
+
+#ifndef UTMP_FILE
+#if defined(__FreeBSD__) || defined(__bsdi__)
+#define UTMP_FILE _PATH_UTMP
+#else
+#define UTMP_FILE "/etc/utmp"
+#endif /* __FreeBSD__ || __bsdi__ */
+#endif /* !UTMP_FILE */
+
+/*
+ * experimental - make temporary filenames random to make the scheme less
+ * obvious. However, as noted by KW, there are instances (such as the
+ * 'O'ption page, for which Lynx will store a temporary filename even when
+ * it no longer applies, since it will reuse that filename at a later time.
+ */
+#ifdef USE_RAND_TEMPNAME
+#if defined(LYNX_RAND_MAX)
+#define HAVE_RAND_TEMPNAME 1
+#define MAX_TEMPNAME 10000
+#ifndef BITS_PER_CHAR
+#define BITS_PER_CHAR 8
+#endif
+#endif
+#endif
+
+#define COPY_COMMAND "%s %s %s"
+
+static HTList *localhost_aliases = NULL; /* Hosts to treat as local */
+static char *HomeDir = NULL; /* HOME directory */
+
+HTList *sug_filenames = NULL; /* Suggested filenames */
+
+/*
+ * Maintain a list of all of the temp-files we create so that we can remove
+ * them during the cleanup.
+ */
+typedef struct _LYTemp {
+ struct _LYTemp *next;
+ char *name;
+ BOOLEAN outs;
+ FILE *file;
+} LY_TEMP;
+
+static LY_TEMP *ly_temp;
+
+static LY_TEMP *FindTempfileByName(const char *name)
+{
+ LY_TEMP *p;
+
+ for (p = ly_temp; p != 0; p = p->next) {
+ if (!strcmp(p->name, name)) {
+ break;
+ }
+ }
+ return p;
+}
+
+static LY_TEMP *FindTempfileByFP(FILE *fp)
+{
+ LY_TEMP *p;
+
+ for (p = ly_temp; p != 0; p = p->next) {
+ if (p->file == fp) {
+ break;
+ }
+ }
+ return p;
+}
+
+#if defined(_WIN32)
+/*
+ * Use RegQueryValueExA() rather than RegQueryValueEx() for compatibility
+ * with non-Unicode winvile
+ */
+static int w32_get_reg_sz(HKEY hkey, const char *name, char *value, unsigned length)
+{
+ int result;
+ DWORD dwSzBuffer = length;
+
+ CTRACE((tfp, "w32_get_reg_sz(%s)\n", name));
+ result = RegQueryValueExA(hkey,
+ name,
+ NULL,
+ NULL,
+ (LPBYTE) value,
+ &dwSzBuffer);
+ if (result == ERROR_SUCCESS) {
+ value[dwSzBuffer] = 0;
+ CTRACE((tfp, "->%s\n", value));
+ }
+ return result;
+}
+
+static char *w32_get_shell_folder(const char *name)
+{
+ static HKEY rootkey = HKEY_CURRENT_USER;
+
+ char *result = 0;
+ HKEY hkey;
+ char buffer[LY_MAXPATH];
+
+ if (RegOpenKeyEx(rootkey,
+ W32_STRING("Software"
+ "\\Microsoft"
+ "\\Windows"
+ "\\CurrentVersion"
+ "\\Explorer"
+ "\\Shell Folders"),
+ 0,
+ KEY_READ,
+ &hkey) == ERROR_SUCCESS) {
+ if (w32_get_reg_sz(hkey, name, buffer, sizeof(buffer)) == ERROR_SUCCESS) {
+
+ result = strdup(buffer);
+ (void) RegCloseKey(hkey);
+ }
+
+ (void) RegCloseKey(hkey);
+ }
+ return non_empty(result) ? result : 0;
+}
+#endif
+
+/*
+ * Get an environment variable, rejecting empty strings
+ */
+char *LYGetEnv(const char *name)
+{
+ char *result = getenv(name);
+
+#if defined(_WIN32)
+ if (result == 0) {
+ static HKEY rootkeys[] =
+ {HKEY_CURRENT_USER, HKEY_LOCAL_MACHINE};
+
+ int j;
+ HKEY hkey;
+ char buffer[256];
+
+ for (j = 0; j < (int) TABLESIZE(rootkeys); ++j) {
+ if (RegOpenKeyEx(rootkeys[j],
+ LYNX_SUBKEY W32_STRING("\\Environment"),
+ 0,
+ KEY_READ,
+ &hkey) == ERROR_SUCCESS) {
+ if (w32_get_reg_sz(hkey, name, buffer, sizeof(buffer)) == ERROR_SUCCESS) {
+
+ result = strdup(buffer);
+ (void) RegCloseKey(hkey);
+ break;
+ }
+
+ (void) RegCloseKey(hkey);
+ }
+ }
+ }
+#endif
+ return non_empty(result) ? result : 0;
+}
+
+/*
+ * ascii versions of locale sensitive functions needed because in
+ * Turkish locales tolower("I") is not "i". That's fatal for case
+ * sensitive operations with charset names, HTML tags etc.
+ */
+#ifdef USE_ASCII_CTYPES
+int ascii_tolower(int i)
+{
+ if (91 > i && i > 64)
+ return (i + 32);
+ else
+ return i;
+}
+
+int ascii_toupper(int i)
+{
+ if (123 > i && i > 96)
+ return (i - 32);
+ else
+ return i;
+}
+
+int ascii_isupper(int i)
+{
+ if (91 > i && i > 64)
+ return 1;
+ else
+ return 0;
+}
+#endif /* USE_ASCII_CTYPES */
+
+/*
+ * Check for UTF-8 data, returning the length past the first character.
+ * Return zero if we found an ordinary character rather than UTF-8.
+ */
+size_t utf8_length(int utf_flag,
+ const char *data)
+{
+ size_t utf_extra = 0;
+
+ if (utf_flag && is8bits(*data)) {
+ if ((*data & 0xe0) == 0xc0) {
+ utf_extra = 1;
+ } else if ((*data & 0xf0) == 0xe0) {
+ utf_extra = 2;
+ } else if ((*data & 0xf8) == 0xf0) {
+ utf_extra = 3;
+ } else if ((*data & 0xfc) == 0xf8) {
+ utf_extra = 4;
+ } else if ((*data & 0xfe) == 0xfc) {
+ utf_extra = 5;
+ } else {
+ /*
+ * Garbage.
+ */
+ utf_extra = 0;
+ }
+ if (strlen(data + 1) < utf_extra) {
+ /*
+ * Shouldn't happen.
+ */
+ utf_extra = 0;
+ }
+ }
+ return utf_extra;
+}
+
+/*
+ * Free storage used for the link-highlighting.
+ */
+void LYFreeHilites(int first, int last)
+{
+ int i;
+
+ for (i = first; i < last; i++) {
+ LYSetHilite(i, NULL);
+ FREE(links[i].lname);
+ }
+}
+
+#define LXP (links[cur].lx)
+#define LYP (links[cur].ly)
+
+/*
+ * Set the initial highlight information for a given link.
+ */
+void LYSetHilite(int cur,
+ const char *text)
+{
+ links[cur].list.hl_base.hl_text = DeConst(text);
+ links[cur].list.hl_len = (short) ((text != NULL) ? 1 : 0);
+ FREE(links[cur].list.hl_info);
+}
+
+/*
+ * Add highlight information for the next line of a link.
+ */
+void LYAddHilite(int cur,
+ char *text,
+ int x)
+{
+ HiliteList *list = &(links[cur].list);
+ HiliteInfo *have = list->hl_info;
+ size_t need = (unsigned) (list->hl_len - 1);
+ size_t want;
+
+ list->hl_len = (short) (list->hl_len + 1);
+ want = (size_t) list->hl_len;
+
+ if (have != NULL) {
+ have = typeRealloc(HiliteInfo, have, want);
+ } else {
+ have = typeMallocn(HiliteInfo, want);
+ }
+ list->hl_info = have;
+ have[need].hl_text = text;
+ have[need].hl_x = (short) x;
+}
+
+/*
+ * Get the highlight text, counting from zero.
+ */
+const char *LYGetHiliteStr(int cur,
+ int count)
+{
+ const char *result;
+
+ if (count >= links[cur].list.hl_len)
+ result = NULL;
+ else if (count > 0)
+ result = links[cur].list.hl_info[count - 1].hl_text;
+ else
+ result = links[cur].list.hl_base.hl_text;
+ return result;
+}
+
+/*
+ * Get the X-ordinate at which to draw the corresponding highlight-text
+ */
+int LYGetHilitePos(int cur,
+ int count)
+{
+ int result;
+
+ if (count >= links[cur].list.hl_len)
+ result = -1;
+ else if (count > 0)
+ result = links[cur].list.hl_info[count - 1].hl_x;
+ else
+ result = LXP;
+ return result;
+}
+
+#ifdef SHOW_WHEREIS_TARGETS
+
+#define SKIP_GLYPHS(theFlag, theData, theOffset) \
+ (theFlag \
+ ? LYmbcs_skip_glyphs(theData, (theOffset), theFlag) \
+ : (theData + (theOffset)))
+
+/*
+ * If we have an emphasized WHEREIS hit in the highlighted text, restore the
+ * emphasis. Note that we never emphasize the first and last characters of the
+ * highlighted text when we are making the link current, so the link attributes
+ * for the current link will persist at the beginning and end, providing an
+ * indication to the user that it has been made current. Also note that we use
+ * HText_getFirstTargetInLine() to determine if there's a hit in the HText
+ * structure line containing the link, and if so, get back a copy of the line
+ * starting at that first hit (which might be before or after our link), and
+ * with all IsSpecial characters stripped, so we don't need to deal with them
+ * here. -FM
+ */
+static BOOL show_whereis_targets(int flag,
+ int cur,
+ int count,
+ const char *target,
+ int TargetEmphasisON,
+ int utf_flag)
+{
+ const char *mydata = NULL;
+ const char *cp;
+ char *theData = NULL;
+ char buffer[MAX_LINE];
+ char tmp[7];
+ int HitOffset;
+ int LenNeeded;
+ int Offset;
+ int tLen;
+
+ tmp[0] = tmp[1] = tmp[2] = '\0';
+
+ if (non_empty(target)
+ && (links[cur].type & WWW_LINK_TYPE)
+ && non_empty(LYGetHiliteStr(cur, count))
+ && LYP + count < display_lines
+ && HText_getFirstTargetInLine(HTMainText,
+ links[cur].anchor_line_num + count,
+ utf_flag,
+ &Offset,
+ &tLen,
+ &theData,
+ target)) {
+ int itmp, written, len, y, offset;
+ const char *data;
+ int tlen = (int) strlen(target);
+ int hlen, hLen;
+ int hLine = LYP + count;
+ int hoffset = LYGetHilitePos(cur, count);
+ size_t utf_extra = 0;
+
+ /*
+ * Copy into the buffer only what will fit up to the right border of
+ * the screen. -FM
+ */
+ LYmbcsstrncpy(buffer,
+ NonNull(LYGetHiliteStr(cur, count)),
+ (int) (sizeof(buffer) - 1),
+ (LYcolLimit - LYGetHilitePos(cur, count)),
+ utf_flag);
+ hlen = (int) strlen(buffer);
+ hLen = ((IS_CJK_TTY || utf_flag) ?
+ LYmbcsstrlen(buffer, utf_flag, YES) : hlen);
+
+ /*
+ * Break out if the first hit in the line starts after this link. -FM
+ */
+ if (Offset < (hoffset + hLen)) {
+ /*
+ * Recursively skip hits that end before this link, and break out
+ * if there is no hit beyond those. -FM
+ */
+ mydata = theData;
+ while ((Offset < hoffset) &&
+ ((Offset + tLen) <= hoffset)) {
+ data = (mydata + tlen);
+ offset = (Offset + tLen);
+ if (((cp = LYno_attr_mb_strstr(data,
+ target,
+ utf_flag, YES,
+ &HitOffset,
+ &LenNeeded)) != NULL)
+ && (offset + LenNeeded) < LYcols) {
+ mydata = cp;
+ Offset = (offset + HitOffset);
+ } else {
+ goto highlight_search_done;
+ }
+ }
+ data = buffer;
+ offset = hoffset;
+
+ /*
+ * If the hit starts before the hightext, and ends in or beyond the
+ * hightext, restore the emphasis, skipping the first and last
+ * characters of the hightext if we're making the link current.
+ * -FM
+ */
+ if (offset >= 0 &&
+ (Offset < offset) &&
+ ((Offset + tLen) > offset)) {
+ itmp = 0;
+ written = 0;
+ len = (tlen - (offset - Offset));
+
+ /*
+ * Go to the start of the hightext and handle its first
+ * character. -FM
+ */
+ LYmove(hLine, offset);
+ tmp[0] = data[itmp];
+ utf_extra = utf8_length(utf_flag, data + itmp);
+ if (utf_extra) {
+ LYStrNCpy(&tmp[1], &data[itmp + 1], utf_extra);
+ itmp += (int) utf_extra;
+ /*
+ * Start emphasis immediately if we are making the link
+ * non-current. -FM
+ */
+ if (flag != TRUE) {
+ LYstartTargetEmphasis();
+ TargetEmphasisON = TRUE;
+ LYaddstr(tmp);
+ } else {
+ LYmove(hLine, (offset + 1));
+ }
+ tmp[1] = '\0';
+ written += (int) (utf_extra + 1);
+ } else if (IS_CJK_TTY && is8bits(tmp[0])) {
+ /*
+ * For CJK strings, by Masanobu Kimura.
+ */
+ tmp[1] = data[++itmp];
+ /*
+ * Start emphasis immediately if we are making the link
+ * non-current. -FM
+ */
+ if (flag != TRUE) {
+ LYstartTargetEmphasis();
+ TargetEmphasisON = TRUE;
+ LYaddstr(tmp);
+ } else {
+ LYmove(hLine, (offset + 1));
+ }
+ tmp[1] = '\0';
+ written += 2;
+ } else {
+ /*
+ * Start emphasis immediately if we are making the link
+ * non-current. -FM
+ */
+ if (flag != TRUE) {
+ LYstartTargetEmphasis();
+ TargetEmphasisON = TRUE;
+ LYaddstr(tmp);
+ } else {
+ LYmove(hLine, (offset + 1));
+ }
+ written++;
+ }
+ itmp++;
+ /*
+ * Start emphasis after the first character if we are making
+ * the link current and this is not the last character. -FM
+ */
+ if (!TargetEmphasisON &&
+ data[itmp] != '\0') {
+ LYstartTargetEmphasis();
+ TargetEmphasisON = TRUE;
+ }
+
+ /*
+ * Handle the remaining characters. -FM
+ */
+ for (;
+ written < len && (tmp[0] = data[itmp]) != '\0';
+ itmp++) {
+ /*
+ * Print all the other target chars, except the last
+ * character if it is also the last character of hightext
+ * and we are making the link current. -FM
+ */
+ utf_extra = utf8_length(utf_flag, data + itmp);
+ if (utf_extra) {
+ LYStrNCpy(&tmp[1], &data[itmp + 1], utf_extra);
+ itmp += (int) utf_extra;
+ /*
+ * Make sure we don't restore emphasis to the last
+ * character of hightext if we are making the link
+ * current. -FM
+ */
+ if (flag == TRUE && data[(itmp + 1)] == '\0') {
+ LYstopTargetEmphasis();
+ TargetEmphasisON = FALSE;
+ LYGetYX(y, offset);
+ (void) y;
+ LYmove(hLine, (offset + 1));
+ } else {
+ LYaddstr(tmp);
+ }
+ tmp[1] = '\0';
+ written += (int) (utf_extra + 1);
+ } else if (IS_CJK_TTY && is8bits(tmp[0])) {
+ /*
+ * For CJK strings, by Masanobu Kimura.
+ */
+ tmp[1] = data[++itmp];
+ /*
+ * Make sure we don't restore emphasis to the last
+ * character of hightext if we are making the link
+ * current. -FM
+ */
+ if (flag == TRUE && data[(itmp + 1)] == '\0') {
+ LYstopTargetEmphasis();
+ TargetEmphasisON = FALSE;
+ LYGetYX(y, offset);
+ LYmove(hLine, (offset + 1));
+ } else {
+ LYaddstr(tmp);
+ }
+ tmp[1] = '\0';
+ written += 2;
+ } else {
+ /*
+ * Make sure we don't restore emphasis to the last
+ * character of hightext if we are making the link
+ * current. -FM
+ */
+ if (flag == TRUE && data[(itmp + 1)] == '\0') {
+ LYstopTargetEmphasis();
+ TargetEmphasisON = FALSE;
+ LYGetYX(y, offset);
+ LYmove(hLine, (offset + 1));
+ } else {
+ LYaddstr(tmp);
+ }
+ written++;
+ }
+ }
+
+ /*
+ * Stop the emphasis if we haven't already, then reset the
+ * offset to our current position in the line, and if that is
+ * beyond the link, or or we are making the link current and it
+ * is the last character of the hightext, we are done. -FM
+ */
+ if (TargetEmphasisON) {
+ LYstopTargetEmphasis();
+ TargetEmphasisON = FALSE;
+ }
+ LYGetYX(y, offset);
+ if (offset < (hoffset + (flag == TRUE ? (hLen - 1) : hLen))
+ /*
+ * See if we have another hit that starts within the
+ * hightext. -FM
+ */
+ && ((cp =
+ LYno_attr_mb_strstr(data = SKIP_GLYPHS(utf_flag,
+ mydata,
+ offset - Offset),
+ target,
+ utf_flag, YES,
+ &HitOffset,
+ &LenNeeded)) != NULL)
+ && (offset + LenNeeded) < LYcols
+ /*
+ * If the hit starts after the end of the hightext, or we
+ * are making the link current and the hit starts at its
+ * last character, we are done. -FM
+ */
+ && (HitOffset + offset) <
+ (hoffset +
+ (flag == TRUE ? (hLen - 1) : hLen))) {
+ /*
+ * Set up the data and offset for the hit, and let the code
+ * for within hightext hits handle it. -FM
+ */
+ mydata = cp;
+ Offset = (offset + HitOffset);
+ data = buffer;
+ offset = hoffset;
+ goto highlight_hit_within_hightext;
+ }
+ goto highlight_search_done;
+ }
+
+ highlight_hit_within_hightext:
+ /*
+ * If we get to here, the hit starts within the hightext. If we
+ * are making the link current and it's the last character in the
+ * hightext, we are done. Otherwise, move there and start
+ * restoring the emphasis. -FM
+ */
+ if ((Offset - offset) <= (flag == TRUE ? (hLen - 1) : hLen)) {
+ data = SKIP_GLYPHS(utf_flag, data, Offset - offset);
+ if (utf_flag) {
+ LYrefresh();
+ }
+ offset = Offset;
+ itmp = 0;
+ written = 0;
+ len = tlen;
+
+ /*
+ * Go to the start of the hit and handle its first character.
+ * -FM
+ */
+ LYmove(hLine, offset);
+ tmp[0] = data[itmp];
+ utf_extra = utf8_length(utf_flag, data + itmp);
+ if (utf_extra) {
+ LYStrNCpy(&tmp[1], &data[itmp + 1], utf_extra);
+ itmp += (int) utf_extra;
+ /*
+ * Start emphasis immediately if we are making the link
+ * non-current, or we are making it current but this is not
+ * the first or last character of the hightext. -FM
+ */
+ if (flag != TRUE ||
+ (offset > hoffset && data[itmp + 1] != '\0')) {
+ LYstartTargetEmphasis();
+ TargetEmphasisON = TRUE;
+ LYaddstr(tmp);
+ } else {
+ LYmove(hLine, (offset + 1));
+ }
+ tmp[1] = '\0';
+ written += (int) (utf_extra + 1);
+ } else if (IS_CJK_TTY && is8bits(tmp[0])) {
+ /*
+ * For CJK strings, by Masanobu Kimura.
+ */
+ tmp[1] = data[++itmp];
+ /*
+ * Start emphasis immediately if we are making the link
+ * non-current, or we are making it current but this is not
+ * the first or last character of the hightext. -FM
+ */
+ if (flag != TRUE ||
+ (offset > hoffset && data[itmp + 1] != '\0')) {
+ LYstartTargetEmphasis();
+ TargetEmphasisON = TRUE;
+ LYaddstr(tmp);
+ } else {
+ LYmove(hLine, (offset + 2));
+ }
+ tmp[1] = '\0';
+ written += 2;
+ } else {
+ /*
+ * Start emphasis immediately if we are making the link
+ * non-current, or we are making it current but this is not
+ * the first or last character of the hightext. -FM
+ */
+ if (flag != TRUE ||
+ (offset > hoffset && data[itmp + 1] != '\0')) {
+ LYstartTargetEmphasis();
+ TargetEmphasisON = TRUE;
+ LYaddstr(tmp);
+ } else {
+ LYmove(hLine, (offset + 1));
+ }
+ written++;
+ }
+ itmp++;
+ /*
+ * Start emphasis after the first character if we are making
+ * the link current and this is not the last character. -FM
+ */
+ if (!TargetEmphasisON &&
+ data[itmp] != '\0') {
+ LYstartTargetEmphasis();
+ TargetEmphasisON = TRUE;
+ }
+
+ for (;
+ written < len && (tmp[0] = data[itmp]) != '\0';
+ itmp++) {
+ /*
+ * Print all the other target chars, except the last
+ * character if it is also the last character of hightext
+ * and we are making the link current. -FM
+ */
+ utf_extra = utf8_length(utf_flag, data + itmp);
+ if (utf_extra) {
+ LYStrNCpy(&tmp[1], &data[itmp + 1], utf_extra);
+ itmp += (int) utf_extra;
+ /*
+ * Make sure we don't restore emphasis to the last
+ * character of hightext if we are making the link
+ * current. -FM
+ */
+ if (flag == TRUE && data[(itmp + 1)] == '\0') {
+ LYstopTargetEmphasis();
+ TargetEmphasisON = FALSE;
+ LYGetYX(y, offset);
+ LYmove(hLine, (offset + 1));
+ } else {
+ LYaddstr(tmp);
+ }
+ tmp[1] = '\0';
+ written += (int) (utf_extra + 1);
+ } else if (IS_CJK_TTY && is8bits(tmp[0])) {
+ /*
+ * For CJK strings, by Masanobu Kimura.
+ */
+ tmp[1] = data[++itmp];
+ /*
+ * Make sure we don't restore emphasis to the last
+ * character of hightext if we are making the link
+ * current. -FM
+ */
+ if (flag == TRUE && data[(itmp + 1)] == '\0') {
+ LYstopTargetEmphasis();
+ TargetEmphasisON = FALSE;
+ LYGetYX(y, offset);
+ LYmove(hLine, (offset + 1));
+ } else {
+ LYaddstr(tmp);
+ }
+ tmp[1] = '\0';
+ written += 2;
+ } else {
+ /*
+ * Make sure we don't restore emphasis to the last
+ * character of hightext if we are making the link
+ * current. -FM
+ */
+ if (flag == TRUE && data[(itmp + 1)] == '\0') {
+ LYstopTargetEmphasis();
+ TargetEmphasisON = FALSE;
+ LYGetYX(y, offset);
+ LYmove(hLine, (offset + 1));
+ } else {
+ LYaddstr(tmp);
+ }
+ written++;
+ }
+ }
+
+ /*
+ * Stop the emphasis if we haven't already, then reset the
+ * offset to our current position in the line, and if that is
+ * beyond the link, or we are making the link current and it is
+ * the last character in the hightext, we are done. -FM
+ */
+ if (TargetEmphasisON) {
+ LYstopTargetEmphasis();
+ TargetEmphasisON = FALSE;
+ }
+ LYGetYX(y, offset);
+ if (offset < (hoffset + (flag == TRUE ? (hLen - 1) : hLen))
+ /*
+ * See if we have another hit that starts within the
+ * hightext. -FM
+ */
+ && ((cp =
+ LYno_attr_mb_strstr(data = SKIP_GLYPHS(utf_flag,
+ mydata,
+ offset - Offset),
+ target,
+ utf_flag, YES,
+ &HitOffset,
+ &LenNeeded)) != NULL)
+ && (offset + LenNeeded) < LYcols
+ /*
+ * If the hit starts after the end of the hightext, or we
+ * are making the link current and the hit starts at its
+ * last character, we are done. -FM
+ */
+ && (HitOffset + offset) <
+ (hoffset + (flag == TRUE ? (hLen - 1) : hLen))) {
+ /*
+ * If the target extends beyond our buffer, emphasize
+ * everything in the hightext starting at this hit.
+ * Otherwise, set up the data and offsets, and loop back.
+ * -FM
+ */
+ if ((HitOffset + (offset + tLen)) >= (hoffset + hLen)) {
+ offset = (HitOffset + offset);
+ data = SKIP_GLYPHS(utf_flag, mydata, offset - hoffset);
+ if (utf_flag) {
+ LYrefresh();
+ }
+ LYmove(hLine, offset);
+ itmp = 0;
+ written = 0;
+ len = (int) strlen(data);
+
+ /*
+ * Turn the emphasis back on. -FM
+ */
+ LYstartTargetEmphasis();
+ TargetEmphasisON = TRUE;
+ for (;
+ written < len && (tmp[0] = data[itmp]) != '\0';
+ itmp++) {
+ /*
+ * Print all the other target chars, except the
+ * last character if it is also the last character
+ * of hightext and we are making the link current.
+ * -FM
+ */
+ utf_extra = utf8_length(utf_flag, data + itmp);
+ if (utf_extra) {
+ LYStrNCpy(&tmp[1], &data[itmp + 1], utf_extra);
+ itmp += (int) utf_extra;
+ /*
+ * Make sure we don't restore emphasis to the
+ * last character of hightext if we are making
+ * the link current. -FM
+ */
+ if (flag == TRUE && data[(itmp + 1)] == '\0') {
+ LYstopTargetEmphasis();
+ TargetEmphasisON = FALSE;
+ LYGetYX(y, offset);
+ LYmove(hLine, (offset + 1));
+ } else {
+ LYaddstr(tmp);
+ }
+ tmp[1] = '\0';
+ written += (int) (utf_extra + 1);
+ } else if (IS_CJK_TTY && is8bits(tmp[0])) {
+ /*
+ * For CJK strings, by Masanobu Kimura.
+ */
+ tmp[1] = data[++itmp];
+ /*
+ * Make sure we don't restore emphasis to the
+ * last character of hightext if we are making
+ * the link current. -FM
+ */
+ if (flag == TRUE && data[(itmp + 1)] == '\0') {
+ LYstopTargetEmphasis();
+ TargetEmphasisON = FALSE;
+ } else {
+ LYaddstr(tmp);
+ }
+ tmp[1] = '\0';
+ written += 2;
+ } else {
+ /*
+ * Make sure we don't restore emphasis to the
+ * last character of hightext if we are making
+ * the link current. -FM
+ */
+ if (flag == TRUE && data[(itmp + 1)] == '\0') {
+ LYstopTargetEmphasis();
+ TargetEmphasisON = FALSE;
+ } else {
+ LYaddstr(tmp);
+ }
+ written++;
+ }
+ }
+ /*
+ * Turn off the emphasis if we haven't already, and
+ * then we're done. -FM
+ */
+ if (TargetEmphasisON) {
+ LYstopTargetEmphasis();
+ }
+ } else {
+ mydata = cp;
+ Offset = (offset + HitOffset);
+ data = buffer;
+ offset = hoffset;
+ goto highlight_hit_within_hightext;
+ }
+ }
+ }
+ }
+ }
+ highlight_search_done:
+ FREE(theData);
+ return (BOOLEAN) TargetEmphasisON;
+}
+#endif /* SHOW_WHEREIS_TARGETS */
+
+#ifdef USE_COLOR_STYLE
+static int find_cached_style(int cur,
+ int flag)
+{
+ int s = s_alink;
+
+#ifdef TEXTFIELDS_MAY_NEED_ACTIVATION
+ if (textfields_need_activation
+ && links[cur].type == WWW_FORM_LINK_TYPE
+ && F_TEXTLIKE(links[cur].l_form->type))
+ s = s_curedit;
+#endif
+
+ if (flag != TRUE) {
+ int x;
+
+ /*
+ * This is where we try to restore the original style when a link is
+ * unhighlighted. The cached styles array saves the original style
+ * just for this case. If it doesn't have a color change saved at just
+ * the right position, we look at preceding positions in the same line
+ * until we find one.
+ */
+ if (ValidCachedStyle(LYP, LXP)) {
+ CTRACE2(TRACE_STYLE,
+ (tfp, "STYLE.highlight.off: cached style @(%d,%d): ",
+ LYP, LXP));
+ s = (int) GetCachedStyle(LYP, LXP);
+ if (s == 0) {
+ for (x = LXP - 1; x >= 0; x--) {
+ s = (int) GetCachedStyle(LYP, x);
+ if (s != 0) {
+ SetCachedStyle(LYP, LXP, (unsigned) s);
+ CTRACE2(TRACE_STYLE,
+ (tfp, "found %d, x_offset=%d.\n", s, x - LXP));
+ break;
+ }
+ }
+ if (s == 0) {
+ CTRACE2(TRACE_STYLE, (tfp, "not found, assume <a>.\n"));
+ s = s_a;
+ }
+ } else {
+ CTRACE2(TRACE_STYLE, (tfp, "found %d.\n", s));
+ }
+ } else {
+ CTRACE2(TRACE_STYLE,
+ (tfp, "STYLE.highlight.off: can't use cache.\n"));
+ s = s_a;
+ }
+ } else {
+ CTRACE2(TRACE_STYLE, (tfp, "STYLE.highlight.on: @(%d,%d).\n", LYP, LXP));
+ }
+ return s;
+}
+#endif /* USE_COLOR_STYLE */
+
+/*
+ * Highlight (or unhighlight) a given link.
+ */
+void LYhighlight(int flag,
+ int cur,
+ const char *target)
+{
+ char buffer[MAX_LINE];
+ int i;
+ int hi_count;
+ int hi_offset;
+ int title_adjust = (no_title ? -TITLE_LINES : 0);
+ char tmp[7];
+ const char *hi_string;
+
+#ifdef SHOW_WHEREIS_TARGETS
+ BOOL TargetEmphasisON = FALSE;
+ BOOL target1_drawn = NO;
+#endif
+ BOOL utf_flag = (BOOL) IS_UTF8_TTY;
+ BOOL hl1_drawn = NO;
+
+ tmp[0] = tmp[1] = tmp[2] = '\0';
+
+ /*
+ * Bugs in the history code might cause -1 to be sent for cur, which yields
+ * a crash when LYStrNCpy() is called with a nonsense pointer. As far as I
+ * know, such bugs have been squashed, but if they should reappear, this
+ * works around them. -FM
+ */
+ if (cur < 0) {
+ CTRACE((tfp, "LYhighlight cur %d (bug workaround)\n", cur));
+ cur = 0;
+ }
+
+ CTRACE((tfp, "LYhighlight at(%2d,%2d) %s %d [%d]:%s\n",
+ links[cur].ly, links[cur].lx,
+ (flag
+ ? "on"
+ : "off"),
+ cur,
+ links[cur].anchor_number,
+ NONNULL(target)));
+
+#if defined(TEXTFIELDS_MAY_NEED_ACTIVATION) && defined(INACTIVE_INPUT_STYLE_VH)
+ if (flag == FALSE)
+ textinput_redrawn = FALSE;
+#endif
+
+ if (nlinks > 0) {
+#ifdef USE_COLOR_STYLE
+ if (flag == TRUE || links[cur].type == WWW_FORM_LINK_TYPE) {
+ LYmove(LYP + title_adjust, LXP);
+ LynxChangeStyle(find_cached_style(cur, flag), STACK_ON);
+ }
+#else
+ if (links[cur].type == WWW_FORM_LINK_TYPE
+ || LYGetHiliteStr(cur, 0) == NULL) {
+ LYMoveToLink(cur, target, NULL,
+ flag, links[cur].inUnderline, utf_flag);
+ lynx_start_link_color(flag == TRUE, links[cur].inUnderline);
+ } else {
+ LYMoveToLink(cur, target, LYGetHiliteStr(cur, 0),
+ flag, links[cur].inUnderline, utf_flag);
+ hl1_drawn = YES;
+#ifdef SHOW_WHEREIS_TARGETS
+ target1_drawn = YES;
+#endif
+ }
+#endif
+
+ if (links[cur].type == WWW_FORM_LINK_TYPE) {
+ int len;
+ int avail_space = (LYcolLimit - LXP) + (LYcolLimit * (LYlines - LYP));
+ const char *text = LYGetHiliteStr(cur, 0);
+
+ if (text == 0)
+ text = "";
+
+ if (avail_space > links[cur].l_form->size)
+ avail_space = links[cur].l_form->size;
+
+ len = (int) (LYmbcs_skip_cells(text, avail_space, utf_flag) - text);
+ LYwaddnstr(LYwin, text, (size_t) len);
+ while (len++ < avail_space)
+ LYaddch('_');
+
+#ifdef USE_COLOR_STYLE
+ } else if (flag == FALSE) {
+ redraw_lines_of_link(cur);
+ CTRACE2(TRACE_STYLE,
+ (tfp, "STYLE.highlight.off: NOFIX branch @(%d,%d).\n",
+ LYP, LXP));
+#endif
+ } else if (!hl1_drawn) {
+ /*
+ * Copy into the buffer only what will fit within the width of the
+ * screen.
+ */
+ LYmbcsstrncpy(buffer,
+ NonNull(LYGetHiliteStr(cur, 0)),
+ (int) (sizeof(buffer) - 1),
+ (LYcolLimit - LXP),
+ utf_flag);
+ LYaddstr(buffer);
+ }
+
+ /*
+ * Display a second line as well.
+ */
+ for (hi_count = 1;
+ (hi_string = LYGetHiliteStr(cur, hi_count)) != NULL
+ && LYP + hi_count <= display_lines;
+ ++hi_count) {
+ int row = LYP + hi_count + title_adjust;
+
+ hi_offset = LYGetHilitePos(cur, hi_count);
+ if (hi_offset < 0)
+ continue;
+ lynx_stop_link_color(flag == TRUE, links[cur].inUnderline);
+ LYmove(row, hi_offset);
+
+#ifdef USE_COLOR_STYLE
+ CTRACE2(TRACE_STYLE,
+ (tfp, "STYLE.highlight.line2: @(%d,%d), style=%d.\n",
+ row, hi_offset,
+ flag == TRUE ? s_alink : s_a));
+ LynxChangeStyle(flag == TRUE ? s_alink : s_a, ABS_ON);
+#else
+ lynx_start_link_color(flag == TRUE, links[cur].inUnderline);
+#endif
+
+ for (i = 0; (tmp[0] = hi_string[i]) != '\0'
+ && (i + hi_offset) < LYcols; i++) {
+ if (!IsSpecialAttrChar(hi_string[i])) {
+ /*
+ * For CJK strings, by Masanobu Kimura.
+ */
+ if (IS_CJK_TTY && is8bits(tmp[0])) {
+ tmp[1] = hi_string[++i];
+ LYaddstr(tmp);
+ tmp[1] = '\0';
+ } else {
+ LYaddstr(tmp);
+ }
+ }
+ }
+ }
+ lynx_stop_link_color(flag == TRUE, links[cur].inUnderline);
+#ifdef SHOW_WHEREIS_TARGETS
+ for (hi_count = target1_drawn ? 1 : 0;
+ LYGetHiliteStr(cur, hi_count) != NULL;
+ hi_count++) {
+ TargetEmphasisON = show_whereis_targets(flag,
+ cur,
+ hi_count,
+ target,
+ TargetEmphasisON,
+ utf_flag);
+ }
+
+ if (!LYShowCursor)
+ /*
+ * Get cursor out of the way.
+ */
+ LYHideCursor();
+ else
+#endif /* SHOW_WHEREIS_TARGETS */
+ /*
+ * Never hide the cursor if there's no FANCY CURSES or SLANG.
+ */
+ LYmove(LYP + title_adjust, ((LXP > 0) ? (LXP - 1) : 0));
+
+ if (flag)
+ LYrefresh();
+ }
+ return;
+}
+
+/*
+ * free_and_clear will free a pointer if it is non-zero and then set it to
+ * zero.
+ */
+void free_and_clear(char **pointer)
+{
+ if (*pointer) {
+ FREE(*pointer);
+ *pointer = 0;
+ }
+ return;
+}
+
+/*
+ * Convert single or serial newlines to single spaces throughout a string
+ * (ignore newlines if the preceding character is a space) and convert tabs to
+ * single spaces. Don't ignore any explicit tabs or spaces if the condense
+ * argument is FALSE, otherwise, condense any serial spaces or tabs to one
+ * space. - FM
+ */
+void convert_to_spaces(char *string,
+ int condense)
+{
+ char *s = string;
+ char *ns;
+ BOOL last_is_space = FALSE;
+
+ if (!s)
+ return;
+
+ s = LYSkipNonBlanks(s);
+ ns = s;
+
+ while (*s) {
+ switch (*s) {
+ case ' ':
+ case '\t':
+ if (!(condense && last_is_space))
+ *(ns++) = ' ';
+ last_is_space = TRUE;
+ break;
+
+ case '\r':
+ case '\n':
+ if (!last_is_space) {
+ *(ns++) = ' ';
+ last_is_space = TRUE;
+ }
+ break;
+
+ default:
+ *(ns++) = *s;
+ last_is_space = FALSE;
+ break;
+ }
+ s++;
+ }
+ *ns = '\0';
+ return;
+}
+
+/*
+ * Strip trailing slashes from directory paths.
+ */
+char *strip_trailing_slash(char *dirname)
+{
+ int i;
+
+ i = (int) strlen(dirname) - 1;
+ while (i >= 0 && dirname[i] == '/')
+ dirname[i--] = '\0';
+ return (dirname);
+}
+
+/*
+ * Remove most blanks, but restore one trailing blank to make prompts nicer.
+ */
+static void remove_most_blanks(char *buffer)
+{
+ int length = (int) strlen(buffer);
+ BOOL trailing = (BOOL) ((length != 0) && (buffer[length - 1] == ' '));
+
+ LYReduceBlanks(buffer);
+ if (trailing)
+ strcat(buffer, " ");
+}
+
+/*
+ * Display (or hide) the status line.
+ */
+BOOLEAN mustshow = FALSE;
+
+void statusline(const char *text)
+{
+ char buffer[MAX_LINE];
+ unsigned char *temp = NULL;
+ int max_length, len, i, j;
+ int at_lineno;
+ unsigned char k;
+ char *p;
+ char text_buff[MAX_LINE];
+
+ if (text == NULL)
+ return;
+
+ /*
+ * Don't print statusline messages if dumping to stdout.
+ */
+ if (dump_output_immediately)
+ return;
+
+ /*
+ * Don't print statusline message if turned off.
+ */
+ if (mustshow != TRUE) {
+ if (no_statusline == TRUE) {
+ return;
+ }
+ }
+ mustshow = FALSE;
+
+ /* "LYNXDOWNLOAD://Method=-1/File=%s/SugFile=%s%s\">Save to disk</a>\n" */
+ LYStrNCpy(text_buff, text, sizeof(text_buff) - 1);
+ p = StrChr(text_buff, '\n');
+ if (p)
+ *p = '\0';
+
+ /*
+ * Deal with any CJK escape sequences and Kanji if we have a CJK character
+ * set selected, otherwise, strip any escapes. Also, make sure text is not
+ * longer than the statusline window. - FM
+ */
+ max_length = (((LYcolLimit - 1) < (int) sizeof(buffer))
+ ? (LYcolLimit - 1)
+ : (int) sizeof(buffer) - 1);
+ if ((text_buff[0] != '\0') &&
+ (LYHaveCJKCharacterSet)) {
+ /*
+ * Translate or filter any escape sequences. - FM
+ */
+ if ((temp = typecallocn(unsigned char, strlen(text_buff) + 1)) == NULL)
+ outofmem(__FILE__, "statusline");
+
+ if (kanji_code == EUC) {
+ TO_EUC((const unsigned char *) text_buff, temp);
+ } else if (kanji_code == SJIS) {
+#ifdef KANJI_CODE_OVERRIDE
+ if (!LYRawMode || last_kcode == SJIS)
+ strcpy(temp, text_buff);
+ else
+ TO_SJIS((const unsigned char *) text_buff, temp);
+#else
+ strcpy((char *) temp, text_buff);
+#endif
+ } else {
+ for (i = 0, j = 0; text_buff[i]; i++) {
+ if (text_buff[i] != CH_ESC) { /* S/390 -- gil -- 2119 */
+ temp[j++] = UCH(text_buff[i]);
+ }
+ }
+ temp[j] = '\0';
+ }
+
+ /*
+ * Deal with any newlines or tabs in the string. - FM
+ */
+ remove_most_blanks((char *) temp);
+
+ /*
+ * Handle the Kanji, making sure the text is not longer than the
+ * statusline window. - FM
+ */
+ for (i = 0, j = 0, len = 0, k = '\0';
+ temp[i] != '\0' && len < max_length; i++) {
+ if (k != '\0') {
+ buffer[j++] = (char) k;
+ buffer[j++] = (char) temp[i];
+ k = '\0';
+ len += 2;
+ } else if ((temp[i] & 0200) != 0) {
+ k = temp[i];
+ } else {
+ buffer[j++] = (char) temp[i];
+ len++;
+ }
+ }
+ buffer[j] = '\0';
+ FREE(temp);
+ } else {
+ /*
+ * Deal with any newlines or tabs in the string. - FM
+ */
+ remove_most_blanks(text_buff);
+#ifdef WIDEC_CURSES
+ len = (int) strlen(text_buff);
+ if (len >= (int) (sizeof(buffer) - 1))
+ len = (int) (sizeof(buffer) - 1);
+ LYStrNCpy(buffer, text_buff, len);
+ /* FIXME: a binary search might be faster */
+ while (len > 0 && LYstrExtent(buffer, len, len) > max_length)
+ buffer[--len] = '\0';
+#else
+ /*
+ * Strip any escapes, and shorten text if necessary. Note that we
+ * don't deal with the possibility of UTF-8 characters in the string.
+ * This is unlikely, but if strings with such characters are used in
+ * LYMessages_en.h, a compilation symbol of HAVE_UTF8_STATUSLINES could
+ * be added there, and code added here for determining the displayed
+ * string length, as we do above for CJK. - FM
+ */
+ for (i = 0, len = 0; text_buff[i] != '\0' && len < max_length; i++) {
+ if (text_buff[i] != CH_ESC) { /* S/390 -- gil -- 2119 */
+ buffer[len++] = text_buff[i];
+ }
+ }
+ buffer[len] = '\0';
+#endif
+ }
+
+ /*
+ * Move to the desired statusline window and output the text highlighted.
+ * - FM
+ */
+ if (LYStatusLine >= 0) {
+ if (LYStatusLine < LYlines - 1) {
+ at_lineno = LYStatusLine;
+ } else {
+ at_lineno = LYlines - 1;
+ }
+ } else if (user_mode == NOVICE_MODE) {
+ at_lineno = LYlines - 3;
+ } else {
+ at_lineno = LYlines - 1;
+ }
+ LYmove(at_lineno, 0);
+ LYclrtoeol();
+
+ if (buffer[0] != '\0') {
+ BOOLEAN has_CJK = FALSE;
+
+ if (IS_CJK_TTY) {
+ for (i = 0; buffer[i] != '\0'; i++) {
+ if (buffer[i] & 0x80) {
+ has_CJK = TRUE;
+ break;
+ }
+ }
+ }
+
+ if (has_CJK
+#ifdef HAVE_UTF8_STATUSLINES
+ || IS_UTF8_TTY
+#endif
+ ) {
+ LYrefresh();
+ }
+#ifndef USE_COLOR_STYLE
+ lynx_start_status_color();
+ LYaddstr(buffer);
+ lynx_stop_status_color();
+#else
+ /* draw the status bar in the STATUS style */
+ {
+ int y, x;
+ int a = ((StrNCmp(buffer, ALERT_FORMAT, ALERT_PREFIX_LEN)
+ || !hashStyles[s_alert].used)
+ ? s_status
+ : s_alert);
+
+ LynxChangeStyle(a, STACK_ON);
+ LYaddstr(buffer);
+ wbkgdset(LYwin,
+ ((lynx_has_color && LYShowColor >= SHOW_COLOR_ON)
+ ? (chtype) hashStyles[a].color
+ : A_NORMAL) | ' ');
+ LYGetYX(y, x);
+ (void) x;
+ if (y == at_lineno) {
+ LYclrtoeol();
+ }
+ if (!(lynx_has_color && LYShowColor >= SHOW_COLOR_ON))
+ wbkgdset(LYwin, A_NORMAL | ' ');
+ else if (s_normal != NOSTYLE)
+ wbkgdset(LYwin, (chtype) (hashStyles[s_normal].color | ' '));
+ else
+ wbkgdset(LYwin, (chtype) (displayStyles[DSTYLE_NORMAL].color | ' '));
+ LynxChangeStyle(a, STACK_OFF);
+ }
+#endif
+ }
+ LYrefresh();
+
+ return;
+}
+
+static const char *novice_lines(int lineno)
+{
+ switch (lineno) {
+ case 0:
+ return NOVICE_LINE_TWO_A;
+ case 1:
+ return NOVICE_LINE_TWO_B;
+ case 2:
+ return NOVICE_LINE_TWO_C;
+ default:
+ return "";
+ }
+}
+
+static int lineno = 0;
+
+void toggle_novice_line(void)
+{
+ lineno++;
+ if (*novice_lines(lineno) == '\0')
+ lineno = 0;
+ return;
+}
+
+void noviceline(int more_flag GCC_UNUSED)
+{
+ if (dump_output_immediately)
+ return;
+
+ LYmove(LYlines - 2, 0);
+ LYclrtoeol();
+ LYaddstr(NOVICE_LINE_ONE);
+
+ LYParkCursor();
+#if defined(DIRED_SUPPORT ) && defined(OK_OVERRIDE)
+ if (lynx_edit_mode && !no_dired_support)
+ LYaddstr(DIRED_NOVICELINE);
+ else
+#endif /* DIRED_SUPPORT && OK_OVERRIDE */
+
+ if (LYUseNoviceLineTwo)
+ LYaddstr(NOVICE_LINE_TWO);
+ else
+ LYaddstr(novice_lines(lineno));
+
+ LYrefresh();
+ return;
+}
+
+/*
+ * If the standard input is not a tty, and Lynx is really reading from the
+ * standard input, attempt to reopen it, pointing to a real tty. Normally
+ * this would happen if the user pipes data to Lynx and wants to run
+ * interactively after that.
+ *
+ * Returns:
+ * 1 if successfully reopened
+ * -1 if we cannot reopen
+ * 0 if we do not have to reopen
+ */
+int LYReopenInput(void)
+{
+ int result = 0;
+ int fd;
+
+ if ((fd = fileno(stdin)) == 0
+ && !isatty(fd)
+ && LYConsoleInputFD(FALSE) == fd) {
+ const char *term_name = NULL;
+ int new_fd = -1;
+
+#ifdef HAVE_TTYNAME
+ if (isatty(fileno(stdout)) &&
+ (term_name = ttyname(fileno(stdout))) != NULL)
+ new_fd = open(term_name, O_RDONLY);
+
+ if (new_fd == -1 &&
+ isatty(fileno(stderr)) &&
+ (term_name = ttyname(fileno(stderr))) != NULL)
+ new_fd = open(term_name, O_RDONLY);
+#endif
+
+#ifdef HAVE_CTERMID
+ if (new_fd == -1 &&
+ (term_name = ctermid(NULL)) != NULL)
+ new_fd = open(term_name, O_RDONLY);
+#endif
+
+#ifdef TTY_DEVICE
+ if (new_fd == -1)
+ new_fd = open(term_name = TTY_DEVICE, O_RDONLY);
+#endif
+
+ CTRACE((tfp, "LYReopenInput open(%s) returned %d.\n", term_name, new_fd));
+ if (new_fd >= 0) {
+ FILE *frp;
+
+ close(new_fd);
+ frp = freopen(term_name, "r", stdin);
+ CTRACE((tfp,
+ "LYReopenInput freopen(%s,\"r\",stdin) returned %p, stdin is now %p with fd %d.\n",
+ term_name, (void *) frp, (void *) stdin, fileno(stdin)));
+ result = 1;
+ } else {
+ result = -1;
+ }
+ }
+ return result;
+}
+
+/*
+ * Returns the file descriptor from which keyboard input is expected, or INVSOC
+ * (-1) if not available. If need_selectable is true, returns non-INVSOC fd
+ * only if select() is possible - actually, currently only checks if fd is
+ * connected to a tty. - kw
+ */
+int LYConsoleInputFD(int need_selectable)
+{
+ int fd = INVSOC;
+
+#ifdef USE_SLANG
+ if (!LYCursesON)
+ fd = fileno(stdin);
+#if ((SLANG_VERSION >= 9919) && defined(REAL_UNIX_SYSTEM) && !defined(__CYGWIN__))
+ /* SLang_TT_Read_FD introduced in slang 0.99.19, from its changelog:
+ * SLang_TT_Read_FD variable is now available for unix. This is the file
+ * descriptor used by SLang_getkey. */
+ else
+ fd = SLang_TT_Read_FD;
+#endif /* SLANG_VERSION >= 9919 */
+#else /* !USE_SLANG */
+ fd = fileno(stdin);
+#endif /* !USE_SLANG */
+
+ if (need_selectable && fd != INVSOC) {
+ if (isatty(fd)) {
+ return fd;
+ } else {
+ return INVSOC;
+ }
+ }
+ return fd;
+}
+
+static int fake_zap = 0;
+
+void LYFakeZap(int set)
+{
+ if (set && fake_zap < 1) {
+ CTRACE((tfp, "\r *** Set simulated 'Z'"));
+ if (fake_zap)
+ CTRACE((tfp, ", %d pending", fake_zap));
+ CTRACE((tfp, " ***\n"));
+ fake_zap++;
+ } else if (!set && fake_zap) {
+ CTRACE((tfp, "\r *** Unset simulated 'Z'"));
+ CTRACE((tfp, ", %d pending", fake_zap));
+ CTRACE((tfp, " ***\n"));
+ fake_zap = 0;
+ }
+
+}
+
+static int DontCheck(void)
+{
+ static time_t last;
+ time_t next;
+
+ /** Curses or slang setup was not invoked **/
+ if (dump_output_immediately)
+ return (TRUE);
+
+ if (LYHaveCmdScript()) /* we may be running from a script */
+ return (TRUE);
+
+ if (LYNoZapKey)
+ return (TRUE);
+ /*
+ * Avoid checking interrupts more than one per second, since it is a slow
+ * and expensive operation - TD
+ */
+#ifdef HAVE_GETTIMEOFDAY
+#undef timezone /* U/Win defines a conflicting macro */
+ {
+ struct timeval tv;
+
+ gettimeofday(&tv, (struct timezone *) 0);
+ next = (tv.tv_sec * 10);
+ next += (tv.tv_usec / 100000L); /* 0.1 seconds is a compromise */
+ }
+#else
+ next = time((time_t *) 0);
+#endif
+ if (next == last)
+ return (TRUE);
+
+ last = next;
+ return FALSE;
+}
+
+int HTCheckForInterrupt(void)
+{
+ int c;
+ int cmd;
+
+ if (fake_zap > 0) {
+ fake_zap--;
+ CTRACE((tfp, "\r *** Got simulated 'Z' ***\n"));
+ CTRACE_FLUSH(tfp);
+ CTRACE_SLEEP(AlertSecs);
+ return ((int) TRUE);
+ }
+
+ /** Curses or slang setup was not invoked **/
+ if (DontCheck())
+ return ((int) FALSE);
+
+#ifndef VMS /* UNIX stuff: */
+
+#if !defined(_WINDOWS) || defined(__MINGW32__)
+
+ /*
+ * First, check if there is a character.
+ */
+#ifdef USE_SLANG
+ /** No keystroke was entered
+ Note that this isn't taking possible SOCKSification
+ and the socks_flag into account, and may fail on the
+ slang library's select() when SOCKSified. - FM **/
+#ifdef DJGPP_KEYHANDLER
+ if (0 == _bios_keybrd(_NKEYBRD_READY))
+ return (FALSE);
+#else
+ if (0 == SLang_input_pending(0))
+ return (FALSE);
+#endif /* DJGPP_KEYHANDLER */
+
+#else /* Unix curses: */
+ {
+ struct timeval socket_timeout;
+ int ret = 0;
+ fd_set readfds;
+
+ socket_timeout.tv_sec = 0;
+ socket_timeout.tv_usec = 0;
+ FD_ZERO(&readfds);
+ FD_SET(0, &readfds);
+#ifdef SOCKS
+ if (socks_flag)
+ ret = Rselect(1, &readfds, NULL, NULL, &socket_timeout);
+ else
+#endif /* SOCKS */
+ ret = select(1, &readfds, NULL, NULL, &socket_timeout);
+
+ /** Suspended? **/
+ if ((ret == -1) && (SOCKET_ERRNO == EINTR))
+ return ((int) FALSE);
+
+ /** No keystroke was entered? **/
+ if (!FD_ISSET(0, &readfds))
+ return ((int) FALSE);
+ }
+#endif /* USE_SLANG */
+
+#endif /* !_WINDOWS */
+
+ /*
+ * Now, read the character.
+ */
+#if defined(USE_CURSES_NODELAY)
+ nodelay(LYwin, TRUE);
+ c = LYgetch();
+ nodelay(LYwin, FALSE);
+#elif defined(USE_SLANG) && defined(_WINDOWS)
+ if (!SLang_input_pending(0))
+ return ((int) FALSE);
+ c = LYgetch();
+#else
+ c = LYgetch();
+#endif
+
+#else /* VMS: */
+ extern int typeahead(void);
+
+ /** Control-C or Control-Y and a 'N'o reply to exit query **/
+ if (HadVMSInterrupt) {
+ HadVMSInterrupt = FALSE;
+ return ((int) TRUE);
+ }
+
+ c = typeahead();
+
+#endif /* !VMS */
+
+ /*
+ * 'c' contains whatever character we're able to read from keyboard
+ */
+
+ /** Keyboard 'Z' or 'z', or Control-G or Control-C **/
+ if (LYCharIsINTERRUPT(c))
+ return ((int) TRUE);
+
+ /* There is a subset of mainloop() actions available at this stage: no new
+ * getfile() cycle is possible until the previous finished. Currently we
+ * have scrolling in partial mode, toggling of trace log, and pasting.
+ * User search now in progress...
+ */
+ cmd = (LKC_TO_LAC(keymap, c));
+ switch (cmd) {
+ case LYK_TRACE_TOGGLE: /* Toggle TRACE mode. */
+ handle_LYK_TRACE_TOGGLE();
+ break;
+#ifdef CAN_CUT_AND_PASTE
+ case LYK_TO_CLIPBOARD:{ /* ^S */
+ const char *s = LYDownLoadAddress();
+
+ if (!s || !*s || put_clip(s))
+ HTInfoMsg(gettext("Copy to clipboard failed."));
+ else
+ HTInfoMsg(gettext("Download document URL put to clipboard."));
+ break;
+ }
+#endif /* defined CAN_CUT_AND_PASTE */
+ default:
+#ifdef DISP_PARTIAL
+ /* OK, we got several lines from new document and want to scroll... */
+ if (display_partial && (NumOfLines_partial > 2)) {
+ BOOLEAN do_refresh;
+ int res;
+ int Newline_partial = LYGetNewline();
+
+ switch (cmd) {
+ case LYK_WHEREIS: /* search within the document */
+ case LYK_NEXT: /* search for the next occurrence in the document */
+ case LYK_PREV: /* search for the previous occurrence in the document */
+ handle_LYK_WHEREIS(cmd, &do_refresh);
+ if (www_search_result != -1) {
+ Newline_partial = www_search_result;
+ www_search_result = -1; /* reset */
+ }
+ break;
+
+ case LYK_FASTBACKW_LINK:
+ if (Newline_partial <= (display_lines) + 1) {
+ Newline_partial -= display_lines;
+ } else if ((res =
+ HTGetLinkOrFieldStart(-1,
+ &Newline_partial, NULL,
+ -1, TRUE)) == LINK_LINE_FOUND) {
+ Newline_partial++;
+ } else if (res == LINK_DO_ARROWUP) {
+ Newline_partial -= display_lines;
+ }
+ break;
+ case LYK_FASTFORW_LINK:
+ if (HText_canScrollDown()) {
+ /* This is not an exact science... - kw */
+ if (HTGetLinkOrFieldStart(HText_LinksInLines(HTMainText,
+ Newline_partial,
+ display_lines)
+ - 1,
+ &Newline_partial, NULL,
+ 1, TRUE) == LINK_LINE_FOUND) {
+ Newline_partial++;
+ }
+ }
+ break;
+ case LYK_PREV_PAGE:
+ if (Newline_partial > 1)
+ Newline_partial -= display_lines;
+ break;
+ case LYK_NEXT_PAGE:
+ if (HText_canScrollDown())
+ Newline_partial += display_lines;
+ break;
+ case LYK_UP_HALF:
+ if (Newline_partial > 1)
+ Newline_partial -= (display_lines / 2);
+ break;
+ case LYK_DOWN_HALF:
+ if (HText_canScrollDown())
+ Newline_partial += (display_lines / 2);
+ break;
+ case LYK_UP_TWO:
+ if (Newline_partial > 1)
+ Newline_partial -= 2;
+ break;
+ case LYK_DOWN_TWO:
+ if (HText_canScrollDown())
+ Newline_partial += 2;
+ break;
+ case LYK_HOME:
+ if (Newline_partial > 1)
+ Newline_partial = 1;
+ break;
+ case LYK_END:
+ if (HText_canScrollDown())
+ Newline_partial = HText_getNumOfLines() - display_lines + 1;
+ /* calculate for "current" bottom value */
+ break;
+ case LYK_REFRESH:
+ break;
+ default:
+ /** Other or no keystrokes **/
+ return ((int) FALSE);
+ } /* end switch */
+ if (Newline_partial < 1)
+ Newline_partial = 1;
+ if (LYMainLoop_pageDisplay(Newline_partial))
+ NumOfLines_partial = HText_getNumOfLines();
+ }
+#endif /* DISP_PARTIAL */
+ break;
+ } /* end switch */
+ /** Other or no keystrokes **/
+ return ((int) FALSE);
+}
+
+/*
+ * Check if the given filename looks like it's an absolute pathname, i.e.,
+ * references a directory.
+ */
+BOOLEAN LYisAbsPath(const char *path)
+{
+ BOOLEAN result = FALSE;
+
+ if (non_empty(path)) {
+#ifdef VMS
+ result = TRUE;
+#else
+#if defined(USE_DOS_DRIVES)
+ result = (BOOLEAN) (LYIsPathSep(path[0])
+ || (LYIsDosDrive(path)
+ && LYIsPathSep(path[2])));
+#else
+ result = (BOOLEAN) (LYIsPathSep(path[0]));
+#endif /* USE_DOS_DRIVES */
+#endif
+ }
+ return result;
+}
+
+/*
+ * Check if the given filename is the root path, e.g., "/" on Unix.
+ */
+BOOLEAN LYisRootPath(const char *path)
+{
+#if defined(USE_DOS_DRIVES)
+ if (strlen(path) == 3
+ && LYIsDosDrive(path)
+ && LYIsPathSep(path[2]))
+ return TRUE;
+#endif
+ return (BOOL) ((strlen(path) == 1) && LYIsPathSep(path[0]));
+}
+
+/*
+ * A file URL for a remote host is an obsolete ftp URL.
+ * Return YES only if we're certain it's a local file. - FM
+ */
+BOOLEAN LYisLocalFile(const char *filename)
+{
+ char *host = NULL;
+ char *acc_method = NULL;
+ char *cp;
+
+ if (!filename)
+ return NO;
+ if (!(host = HTParse(filename, "", PARSE_HOST)))
+ return NO;
+ if (!*host) {
+ FREE(host);
+ return NO;
+ }
+
+ if ((cp = StrChr(host, ':')) != NULL)
+ *cp = '\0';
+
+ if ((acc_method = HTParse(filename, "", PARSE_ACCESS))) {
+ if (0 == strcmp("file", acc_method) &&
+ (0 == strcmp(host, "localhost") ||
+ LYSameFilename(host, HTHostName()))) {
+ FREE(host);
+ FREE(acc_method);
+ return YES;
+ }
+ }
+
+ FREE(host);
+ FREE(acc_method);
+ return NO;
+}
+
+/*
+ * Utility for checking URLs with a host field. Return YES only if we're
+ * certain it's the local host. - FM
+ */
+BOOLEAN LYisLocalHost(const char *filename)
+{
+ char *host = NULL;
+ char *cp;
+
+ if (!filename)
+ return NO;
+ if (!(host = HTParse(filename, "", PARSE_HOST)))
+ return NO;
+ if (!*host) {
+ FREE(host);
+ return NO;
+ }
+
+ if ((cp = StrChr(host, ':')) != NULL)
+ *cp = '\0';
+
+ if ((LYSameFilename(host, "localhost") ||
+ LYSameFilename(host, LYHostName) ||
+ LYSameFilename(host, HTHostName()))) {
+ FREE(host);
+ return YES;
+ }
+
+ FREE(host);
+ return NO;
+}
+
+/*
+ * Free an HTList that contains strings.
+ */
+void LYFreeStringList(HTList *list)
+{
+ if (list != NULL) {
+ char *argument;
+ HTList *cur = list;
+
+ while (NULL != (argument = (char *) HTList_nextObject(cur))) {
+ FREE(argument);
+ }
+ HTList_delete(list);
+ }
+}
+
+/*
+ * Utility for freeing the list of local host aliases. - FM
+ */
+void LYLocalhostAliases_free(void)
+{
+ LYFreeStringList(localhost_aliases);
+ localhost_aliases = NULL;
+}
+
+/*
+ * Utility for listing hosts to be treated as local aliases. - FM
+ */
+void LYAddLocalhostAlias(char *alias)
+{
+ char *LocalAlias = NULL;
+
+ if (!non_empty(alias))
+ return;
+
+ if (!localhost_aliases) {
+ localhost_aliases = HTList_new();
+#ifdef LY_FIND_LEAKS
+ atexit(LYLocalhostAliases_free);
+#endif
+ }
+
+ StrAllocCopy(LocalAlias, alias);
+ HTList_addObject(localhost_aliases, LocalAlias);
+
+ return;
+}
+
+/*
+ * Utility for checking URLs with a host field. Return YES only if we've
+ * listed the host as a local alias. - FM
+ */
+BOOLEAN LYisLocalAlias(const char *filename)
+{
+ char *host = NULL;
+ char *alias;
+ char *cp;
+ HTList *cur = localhost_aliases;
+
+ if (!cur || !filename)
+ return NO;
+ if (!(host = HTParse(filename, "", PARSE_HOST)))
+ return NO;
+ if (!(*host)) {
+ FREE(host);
+ return NO;
+ }
+
+ if ((cp = StrChr(host, ':')) != NULL)
+ *cp = '\0';
+
+ while (NULL != (alias = (char *) HTList_nextObject(cur))) {
+ if (LYSameFilename(host, alias)) {
+ FREE(host);
+ return YES;
+ }
+ }
+
+ FREE(host);
+ return NO;
+}
+
+/*
+ * This function checks for a URL with an unknown scheme,
+ * but for which proxying has been set up, and if so,
+ * returns PROXY_URL_TYPE. - FM
+ *
+ * If a colon is present but the string segment which
+ * precedes it is not being proxied, and we can be sure
+ * that what follows the colon is not a port field,
+ * it returns UNKNOWN_URL_TYPE. Otherwise, it returns
+ * 0 (not a URL). - FM
+ */
+UrlTypes LYCheckForProxyURL(char *filename)
+{
+ char *cp = filename;
+ char *cp1;
+ char *cp2 = NULL;
+
+ /*
+ * Don't crash on an empty argument.
+ */
+ if (isEmpty(cp))
+ return (NOT_A_URL_TYPE);
+
+ /* kill beginning spaces */
+ cp = LYSkipBlanks(cp);
+
+ /*
+ * Check for a colon, and if present,
+ * see if we have proxying set up.
+ */
+ if ((cp1 = StrChr((cp + 1), ':')) != NULL) {
+ if ((cp2 = StrChr((cp + 1), '/')) != NULL && cp2 < cp1)
+ return (NOT_A_URL_TYPE);
+ *cp1 = '\0';
+ cp2 = NULL;
+ StrAllocCopy(cp2, cp);
+ *cp1 = ':';
+ StrAllocCat(cp2, "_proxy");
+ if (LYGetEnv(cp2) != NULL) {
+ FREE(cp2);
+ return (PROXY_URL_TYPE);
+ }
+ FREE(cp2);
+#if defined (USE_DOS_DRIVES)
+ if (LYIsDosDrive(cp))
+ return (NOT_A_URL_TYPE);
+#endif
+ cp1++;
+ if (!*cp) {
+ return (NOT_A_URL_TYPE);
+ } else if (isdigit(UCH(*cp1))) {
+ while (*cp1 && isdigit(UCH(*cp1)))
+ cp1++;
+ if (*cp1 && !LYIsHtmlSep(*cp1))
+ return (UNKNOWN_URL_TYPE);
+ } else {
+ return (UNKNOWN_URL_TYPE);
+ }
+ }
+
+ return (NOT_A_URL_TYPE);
+}
+
+/*
+ * Compare a "type:" string, replacing it by the comparison-string if it
+ * matches (and return true in that case).
+ */
+static BOOLEAN compare_type(char *tst,
+ const char *cmp,
+ size_t len)
+{
+ if (!strncasecomp(tst, cmp, (int) len)) {
+ if (StrNCmp(tst, cmp, len)) {
+ size_t i;
+
+ for (i = 0; i < len; i++)
+ tst[i] = cmp[i];
+ }
+ return TRUE;
+ }
+ return FALSE;
+}
+#define CompareType(tst,cmp,len) compare_type((tst),(cmp),(size_t)(len))
+
+#define DoubleHtmlSep(s) (LYIsHtmlSep((s)[0]) && LYIsHtmlSep((s)[1]))
+#define compare_two(tst,cmp,len,limit) \
+ ((len + 2) <= limit \
+ && DoubleHtmlSep(tst + len) \
+ && CompareType(tst, cmp, len))
+
+/*
+ * Must recognize a URL and return the type. If recognized, based on a
+ * case-insensitive analysis of the scheme field, ensures that the scheme field
+ * has the expected case.
+ *
+ * Returns 0 (not a URL) for a NULL argument, one which lacks a colon.
+ *
+ * Chains to LYCheckForProxyURL() if a colon is present but the type is not
+ * recognized.
+ */
+UrlTypes is_url(char *filename)
+{
+ char *cp = filename;
+ char *cp1;
+ UrlTypes result = NOT_A_URL_TYPE;
+ int limit;
+
+ /*
+ * Don't crash on an empty argument.
+ */
+ if (isEmpty(cp))
+ return (result);
+
+ /*
+ * Can't be a URL if it lacks a colon and if it starts with '[' it's
+ * probably IPv6 address.
+ */
+ if (NULL == StrChr(cp, ':') || cp[0] == '[')
+ return (result);
+
+ /*
+ * Kill beginning spaces.
+ */
+ cp = LYSkipBlanks(cp);
+
+ /*
+ * Can't be a URL if it starts with a slash. So return immediately for
+ * this common case, also to avoid false positives if there was a colon
+ * later in the string. Also can't be a URL if it starts with a colon. -
+ * KW
+ */
+ if (*cp == ':' || LYIsHtmlSep(*cp)) {
+ result = NOT_A_URL_TYPE;
+
+ } else {
+ limit = (int) strlen(cp);
+ switch (*cp) {
+ case 'L':
+ case 'l':
+ /*
+ * Lynx internal pages ("LYNXfoo:" or "lynxfoo:") start with 'l' or
+ * 'L', other URLs aren't.
+ */
+ if (CompareType(cp, STR_LYNXEXEC, LEN_LYNXEXEC)) {
+ /*
+ * Special External Lynx type to handle execution of commands
+ * or scripts which require a pause to read the screen upon
+ * completion.
+ */
+ result = LYNXEXEC_URL_TYPE;
+
+ } else if (CompareType(cp, STR_LYNXPROG, LEN_LYNXPROG)) {
+ /*
+ * Special External Lynx type to handle execution of commands,
+ * scripts or programs which do not require a pause to read
+ * screen upon completion.
+ */
+ result = LYNXPROG_URL_TYPE;
+
+ } else if (CompareType(cp, STR_LYNXCGI, LEN_LYNXCGI)) {
+ result = LYNXCGI_URL_TYPE;
+
+ } else if (CompareType(cp, STR_LYNXPRINT, LEN_LYNXPRINT)) {
+ result = LYNXPRINT_URL_TYPE;
+
+ } else if (CompareType(cp, STR_LYNXOPTIONS, LEN_LYNXOPTIONS)) {
+ result = LYNXOPTIONS_URL_TYPE;
+
+ } else if (CompareType(cp, STR_LYNXCFG, LEN_LYNXCFG)) {
+ result = LYNXCFG_URL_TYPE;
+
+ } else if (CompareType(cp, STR_LYNXMESSAGES, LEN_LYNXMESSAGES)) {
+ result = LYNXMESSAGES_URL_TYPE;
+
+ } else if (CompareType(cp, STR_LYNXCFLAGS, LEN_LYNXCFLAGS)) {
+ result = LYNXCOMPILE_OPTS_URL_TYPE;
+
+ } else if (CompareType(cp, STR_LYNXDOWNLOAD, LEN_LYNXDOWNLOAD)) {
+ result = LYNXDOWNLOAD_URL_TYPE;
+
+ } else if (CompareType(cp, STR_LYNXDIRED, LEN_LYNXDIRED)) {
+ result = LYNXDIRED_URL_TYPE;
+
+ } else if (CompareType(cp, STR_LYNXEDITMAP, LEN_LYNXEDITMAP)) {
+ result = LYNXEDITMAP_URL_TYPE;
+
+ } else if (CompareType(cp, STR_LYNXHIST, LEN_LYNXHIST)) {
+ result = LYNXHIST_URL_TYPE;
+
+#ifdef USE_CACHEJAR
+ } else if (CompareType(cp, STR_LYNXCACHE, LEN_LYNXCACHE)) {
+ result = LYNXCACHE_URL_TYPE;
+#endif
+ } else if (CompareType(cp, STR_LYNXKEYMAP, LEN_LYNXKEYMAP)) {
+ result = LYNXKEYMAP_URL_TYPE;
+
+ } else if (CompareType(cp, STR_LYNXIMGMAP, LEN_LYNXIMGMAP)) {
+ /* force lower/uppercase of next part */
+ (void) is_url(&cp[LEN_LYNXIMGMAP]);
+ result = LYNXIMGMAP_URL_TYPE;
+
+ } else if (CompareType(cp, STR_LYNXCOOKIE, LEN_LYNXCOOKIE)) {
+ result = LYNXCOOKIE_URL_TYPE;
+
+ }
+ break;
+#ifndef DISABLE_NEWS
+ /*
+ * NEWSfoo: schemes -
+ */
+ case 'N':
+ case 'n':
+ if (CompareType(cp, STR_NEWS_URL, LEN_NEWS_URL)) {
+ result = NEWS_URL_TYPE;
+
+ } else if (CompareType(cp, STR_NNTP_URL, LEN_NNTP_URL)) {
+ result = NNTP_URL_TYPE;
+
+ } else if (CompareType(cp, "newspost:", 9)) {
+ /*
+ * Special Lynx type to handle news posts.
+ */
+ result = NEWSPOST_URL_TYPE;
+
+ } else if (CompareType(cp, "newsreply:", 10)) {
+ /*
+ * Special Lynx type to handle news replies (followups).
+ */
+ result = NEWSREPLY_URL_TYPE;
+ }
+ break;
+
+ /*
+ * SNEWSfoo: schemes -
+ */
+ case 'S':
+ case 's':
+ if (CompareType(cp, STR_SNEWS_URL, LEN_SNEWS_URL)) {
+ result = SNEWS_URL_TYPE;
+
+ } else if (CompareType(cp, "snewspost:", 10)) {
+ /*
+ * Special Lynx type to handle snews posts.
+ */
+ result = NEWSPOST_URL_TYPE;
+
+ } else if (CompareType(cp, "snewsreply:", 11)) {
+ /*
+ * Special Lynx type to handle snews replies (followups).
+ */
+ result = NEWSREPLY_URL_TYPE;
+ }
+ break;
+#endif
+ case 'M':
+ case 'm':
+ if (CompareType(cp, STR_MAILTO_URL, LEN_MAILTO_URL)) {
+ result = MAILTO_URL_TYPE;
+ }
+ break;
+
+ case 'F':
+ case 'f':
+ if (CompareType(cp, STR_FILE_URL, LEN_FILE_URL)) {
+ if (LYisLocalFile(cp)) {
+ result = FILE_URL_TYPE;
+ } else if (DoubleHtmlSep(cp + LEN_FILE_URL)) {
+ result = FTP_URL_TYPE;
+ }
+ }
+#ifndef DISABLE_FTP
+ else if (compare_two(cp, STR_FTP_URL, LEN_FTP_URL, limit)) {
+ result = FTP_URL_TYPE;
+ }
+#endif
+#ifndef DISABLE_FINGER
+ else if (compare_two(cp, STR_FINGER_URL, LEN_FINGER_URL, limit)) {
+ result = FINGER_URL_TYPE;
+ }
+#endif
+ break;
+
+ case 'B':
+ case 'b':
+#ifndef DISABLE_BIBP
+ if (CompareType(cp, STR_BIBP_URL, LEN_BIBP_URL)) {
+ result = BIBP_URL_TYPE;
+ }
+#endif
+ break;
+
+ case 'D':
+ case 'd':
+ if (CompareType(cp, "data:", 5)) {
+ result = DATA_URL_TYPE;
+ }
+ break;
+
+ default:
+ if (limit >= 3
+ && ((cp1 = StrChr(cp + 3, ':')) == NULL
+ || !DoubleHtmlSep(cp1 + 1))) {
+ /*
+ * If it doesn't contain "://", and it's not one of the the
+ * above, it can't be a URL with a scheme we know, so check if
+ * it's an unknown scheme for which proxying has been set up.
+ * - FM
+ */
+ if (cp1 != NULL
+ && (cp1 - cp) > 1 /* exclude DOS-style device:/path */
+ && LYisAbsPath(cp1 + 1)) {
+ result = NCFTP_URL_TYPE;
+ }
+
+ } else {
+ switch (*cp) {
+ case 'H':
+ case 'h':
+ if (CompareType(cp, STR_HTTP_URL, LEN_HTTP_URL)) {
+ result = HTTP_URL_TYPE;
+
+ } else if (CompareType(cp, STR_HTTPS_URL, LEN_HTTPS_URL)) {
+ result = HTTPS_URL_TYPE;
+ }
+ break;
+
+#ifndef DISABLE_GOPHER
+ case 'G':
+ case 'g':
+ if (CompareType(cp, STR_GOPHER_URL, LEN_GOPHER_URL)) {
+ if (strlen(cp) >= 11
+ && (cp1 = StrChr(cp + 11, '/')) != NULL) {
+
+ if (TOUPPER(*(cp1 + 1)) == 'H' || *(cp1 + 1) == 'w')
+ /* if this is a gopher html type */
+ result = HTML_GOPHER_URL_TYPE;
+ else if (*(cp1 + 1) == 'T' || *(cp1 + 1) == '8')
+ result = TELNET_GOPHER_URL_TYPE;
+ else if (*(cp1 + 1) == '7')
+ result = INDEX_GOPHER_URL_TYPE;
+ else
+ result = GOPHER_URL_TYPE;
+ } else {
+ result = GOPHER_URL_TYPE;
+ }
+ }
+ break;
+#endif
+ case 'W':
+ case 'w':
+ if (CompareType(cp, STR_WAIS_URL, LEN_WAIS_URL)) {
+ result = WAIS_URL_TYPE;
+ }
+ break;
+
+ case 'T':
+ case 't':
+ if (CompareType(cp, STR_TELNET_URL, LEN_TELNET_URL)) {
+ result = TELNET_URL_TYPE;
+
+ } else if (CompareType(cp, STR_TN3270_URL, LEN_TN3270_URL)) {
+ result = TN3270_URL_TYPE;
+ }
+ break;
+
+ case 'R':
+ case 'r':
+ if (CompareType(cp, STR_RLOGIN_URL, LEN_RLOGIN_URL)) {
+ result = RLOGIN_URL_TYPE;
+ }
+ break;
+
+ case 'C':
+ case 'c':
+ if (CompareType(cp, STR_CSO_URL, LEN_CSO_URL)) {
+ result = CSO_URL_TYPE;
+ }
+ break;
+
+ case 'A':
+ case 'a':
+ if (CompareType(cp, "afs:", 4)) {
+ result = AFS_URL_TYPE;
+ }
+ break;
+
+ case 'P':
+ case 'p':
+ if (CompareType(cp, "prospero:", 9)) {
+ result = PROSPERO_URL_TYPE;
+ }
+ break;
+ }
+ }
+ }
+ /*
+ * Check if it is an unknown scheme for which proxying has been set up.
+ */
+ if (result == NOT_A_URL_TYPE)
+ result = LYCheckForProxyURL(filename);
+ }
+ return result;
+}
+
+/*
+ * Sometimes it is just expected that curses is on when an alert or other
+ * statusline message needs to be shown and we are not just dumping
+ * immediately. Calling this will 'fix' it, but may not always be appropriate.
+ * - kw
+ */
+void LYFixCursesOn(const char *reason)
+{
+ if (dump_output_immediately || LYCursesON)
+ return;
+ if (reason) {
+ CTRACE((tfp, "Forcing curses on to %s\n", reason));
+ }
+ start_curses();
+}
+
+/*
+ * Most protocol modules called through HTLoad* expect that curses is on unless
+ * dump_output_immediately is set, so that statusline messages can be shown.
+ * Some protocols expect the opposite, namely telnet and friends. This
+ * function should be called after the 'physical' URL for accessing addr has
+ * been established. It does the right thing to the degree that curses is
+ * turned on for known problem cases. In any normal circumstances this should
+ * never apply, but proxying or rule substitution is not prevented for
+ * telnet-like URLs, and this 'fix' avoids some crashes that can otherwise
+ * occur. - kw
+ */
+BOOLEAN LYFixCursesOnForAccess(const char *addr,
+ const char *physical)
+{
+ /*
+ * If curses is off when maybe it shouldn't...
+ */
+ if (!dump_output_immediately && !LYCursesON && physical) {
+ char *cp1;
+
+ /*
+ * If requested resource wants to be accessed with curses off, and
+ * getfile() would indeed have turned curses off for it...
+ */
+ if (strstr(addr, "://") != NULL &&
+ (isTELNET_URL(addr) ||
+ isRLOGIN_URL(addr) ||
+ isTN3270_URL(addr) ||
+ (!isGOPHER_URL(addr) &&
+ (cp1 = StrChr(addr + 11, '/')) != NULL &&
+ (*(cp1 + 1) == 'T' || *(cp1 + 1) == '8')))) {
+ /*
+ * If actual access that will be done is ok with curses off, then
+ * do nothing special, else force curses on. - kw
+ */
+ if (!isTELNET_URL(physical) &&
+ !isRLOGIN_URL(physical) &&
+ !isTN3270_URL(physical)) {
+ start_curses();
+ HTAlert(gettext("Unexpected access protocol for this URL scheme."));
+ return TRUE;
+ }
+ }
+ }
+ return FALSE;
+}
+
+/*
+ * Determine whether we allow HEAD and related flags for a URL. - kw
+ */
+BOOLEAN LYCanDoHEAD(const char *address)
+{
+ char *temp0 = NULL;
+ int isurl;
+
+ if (!non_empty(address))
+ return FALSE;
+ if (!StrNCmp(address, "http", 4))
+ return TRUE;
+ /* Make copy for is_url() since caller may not care for case changes */
+ StrAllocCopy(temp0, address);
+ isurl = is_url(temp0);
+ if (!isurl) {
+ FREE(temp0);
+ return FALSE;
+ }
+ if (isurl == LYNXCGI_URL_TYPE) {
+ FREE(temp0);
+#if defined(LYNXCGI_LINKS) && !defined(VMS)
+ return TRUE;
+#else
+ return FALSE;
+#endif
+ }
+ /*
+ * The idea of the following is to allow HEAD for news URLs that identify
+ * single articles, not those that identify ranges of articles or groups or
+ * a list of groups. - kw
+ */
+ if (isurl == NEWS_URL_TYPE || isurl == NNTP_URL_TYPE) {
+ char *temp = HTParse(address, "", PARSE_PATH);
+ char *cp = strrchr(temp, '/');
+
+ if (StrChr((cp ? cp : temp), '@') != NULL) {
+ FREE(temp0);
+ FREE(temp);
+ return TRUE;
+ }
+ if (cp && isdigit(UCH(cp[1])) && StrChr(cp, '-') == NULL) {
+ FREE(temp0);
+ FREE(temp);
+ return TRUE;
+ }
+ FREE(temp);
+ }
+#define ALLOW_PROXY_HEAD
+/* If defined, also allow head requests for URLs proxied through the "http" or
+ * "lynxcgi" protocols, which understand HEAD. Only the proxy environment
+ * variables are checked, not the HTRules system. - kw
+ */
+#ifdef ALLOW_PROXY_HEAD
+ if (isurl != FILE_URL_TYPE) {
+ char *acc_method = HTParse(temp0, "", PARSE_ACCESS);
+
+ if (non_empty(acc_method)) {
+ char *proxy;
+
+ StrAllocCat(acc_method, "_proxy");
+ proxy = LYGetEnv(acc_method);
+ if (proxy && (isHTTP_URL(proxy) ||
+ isLYNXCGI(proxy)) &&
+ !override_proxy(temp0)) {
+ FREE(temp0);
+ FREE(acc_method);
+ return TRUE;
+ }
+ }
+ FREE(acc_method);
+ }
+#endif /* ALLOW_PROXY_HEAD */
+
+ FREE(temp0);
+ return FALSE;
+}
+
+/*
+ * Close an input file.
+ */
+BOOLEAN LYCloseInput(FILE *fp)
+{
+ int result = FALSE;
+
+ if (fp != 0) {
+ int err = ferror(fp);
+ LY_TEMP *p = FindTempfileByFP(fp);
+
+ fclose(fp);
+ if (p != 0) {
+ p->file = 0;
+ }
+ if (!err) {
+ result = TRUE;
+ }
+ }
+ return (BOOLEAN) result;
+}
+
+/*
+ * Close an output file, reporting any problems with writing to it.
+ */
+BOOLEAN LYCloseOutput(FILE *fp)
+{
+ int result = FALSE;
+
+ if (fp != 0) {
+ int err = ferror(fp);
+ LY_TEMP *p = FindTempfileByFP(fp);
+
+ fclose(fp);
+ if (p != 0) {
+ p->file = 0;
+ }
+ if (!err) {
+ result = TRUE;
+ }
+ }
+ if (!result) {
+ HTAlert(CANNOT_WRITE_TO_FILE);
+ }
+ return (BOOLEAN) result;
+}
+
+/*
+ * Test if we'll be able to write a file. If not, warn the user.
+ */
+BOOLEAN LYCanWriteFile(const char *filename)
+{
+ BOOLEAN result = FALSE;
+
+ if (LYCloseOutput(fopen(filename, "w"))) {
+ if (remove(filename) == 0) {
+ result = TRUE;
+ }
+ } else {
+ _statusline(NEW_FILENAME_PROMPT);
+ }
+ return result;
+}
+
+/*
+ * Test if we'll be able to read a file.
+ */
+BOOLEAN LYCanReadFile(const char *filename)
+{
+ FILE *fp;
+ BOOLEAN result = FALSE;
+
+ if (non_empty(filename)) {
+ if ((fp = fopen(filename, "r")) != 0) {
+ result = LYCloseInput(fp);
+ }
+ }
+ return result;
+}
+
+char *LYFindConfigFile(const char *nominal, const char *dftfile)
+{
+ char *result = 0;
+ char *path = 0;
+ char *head = 0;
+ char *leaf;
+ char *item;
+
+ if (non_empty(nominal)) {
+ StrAllocCopy(result, nominal);
+
+ /*
+ * Look for it in as-is - first expanding any tilde.
+ */
+ LYTildeExpand(&result, TRUE);
+ if (!LYCanReadFile(result)) {
+ const char *cfg_path;
+ char *list = 0;
+ BOOLEAN found = FALSE;
+
+ /*
+ * Now try in the config-path.
+ */
+ if ((cfg_path = LYGetEnv("LYNX_CFG_PATH")) == NULL)
+ cfg_path = LYNX_CFG_PATH;
+
+ StrAllocCopy(list, cfg_path);
+ path = list;
+ while ((item = LYstrsep(&path, PATH_SEPARATOR)) != 0) {
+ if (isEmpty(item))
+ continue;
+ FREE(result);
+ HTSprintf0(&result, "%s%s%s", item, FILE_SEPARATOR, nominal);
+ LYTildeExpand(&result, TRUE);
+ if (LYCanReadFile(result)) {
+ found = TRUE;
+ break;
+ }
+ }
+ FREE(list);
+
+ if (!found) {
+ /*
+ * If not found, try finding it in the same directory as the
+ * compiled-in location of the default file.
+ */
+ StrAllocCopy(head, dftfile);
+ if (strcmp(nominal, dftfile) &&
+ (leaf = LYPathLeaf(head)) != head) {
+
+ head[leaf - head] = '\0';
+ StrAllocCopy(result, head);
+ StrAllocCat(result, nominal);
+
+ if (!LYCanReadFile(result)) {
+ FREE(result);
+ }
+ }
+#ifdef USE_PROGRAM_DIR
+ else {
+ /*
+ * Finally, try in the same directory as the executable.
+ */
+ StrAllocCopy(result, program_dir);
+ LYAddPathSep(&result);
+ StrAllocCat(result, nominal);
+ LYTildeExpand(&result, TRUE);
+ if (!LYCanReadFile(result)) {
+ FREE(result);
+ }
+ }
+#endif
+ }
+ }
+
+ }
+ FREE(head);
+ return result;
+}
+
+/*
+ * Remove backslashes from any string.
+ */
+void remove_backslashes(char *buf)
+{
+ char *cp;
+
+ for (cp = buf; *cp != '\0'; cp++) {
+
+ if (*cp != '\\') { /* don't print slashes */
+ *buf = *cp;
+ buf++;
+ } else if (*cp == '\\' && /* print one slash if there */
+ *(cp + 1) == '\\') { /* are two in a row */
+ *buf = *cp;
+ buf++;
+ }
+ }
+ *buf = '\0';
+ return;
+}
+
+/*
+ * Checks to see if the current process is attached via a terminal in the local
+ * domain.
+ */
+BOOLEAN inlocaldomain(void)
+{
+ BOOLEAN result = TRUE;
+
+#ifdef HAVE_UTMP
+ int n;
+ FILE *fp;
+ struct utmp me;
+ char *cp, *mytty = NULL;
+
+ if ((cp = ttyname(0))) {
+ mytty = cp;
+ if (!strncmp(mytty, "/dev/", 5)) {
+ mytty += 5; /* pty's can be like "pts/0" in utmp */
+ } else {
+ if ((mytty = LYLastPathSep(cp)) != 0)
+ ++mytty;
+ }
+ }
+
+ result = FALSE;
+ if (mytty && (fp = fopen(UTMP_FILE, "r")) != NULL) {
+ size_t ulen = strlen(mytty);
+
+ if (ulen > sizeof(me.ut_line))
+ ulen = sizeof(me.ut_line);
+ do {
+ n = (int) fread((char *) &me, sizeof(struct utmp), (size_t) 1, fp);
+
+ if (n <= 0)
+ break;
+ } while (memcmp(me.ut_line, mytty, ulen));
+ (void) LYCloseInput(fp);
+
+ if (n > 0) {
+ for (ulen = 0; ulen < sizeof(me.ut_host); ++ulen) {
+ if (me.ut_host[ulen] == '\0')
+ break;
+ }
+ if (ulen > strlen(LYLocalDomain) &&
+ !memcmp(LYLocalDomain,
+ me.ut_host + ulen - strlen(LYLocalDomain),
+ strlen(LYLocalDomain))) {
+ result = TRUE;
+ }
+#ifdef LINUX
+ /* Linux fix to check for local user. J.Cullen 11Jul94 */
+ else if (ulen == 0) {
+ result = TRUE;
+ }
+#endif /* LINUX */
+ }
+
+ } else {
+ CTRACE((tfp,
+ "Could not get ttyname (returned %s) or open UTMP file %s\n",
+ NONNULL(cp), UTMP_FILE));
+ }
+#else
+ CTRACE((tfp, "LYUtils: inlocaldomain() not supported.\n"));
+#endif /* HAVE_UTMP */
+ return (result);
+}
+
+#ifdef HAVE_SIGACTION
+/*
+ * An extended alternative for calling signal(), sets some flags for signal
+ * handler as we want them if that functionality is available. (We don't
+ * return anything from this function since the return value would currently be
+ * ignored anyway.) - kw
+ */
+void LYExtSignal(int sig,
+ LYSigHandlerFunc_t *handler)
+{
+#ifdef SIGWINCH
+ /* add more cases to if(condition) if required... */
+ if (sig == SIGWINCH && LYNonRestartingSIGWINCH) {
+ struct sigaction act;
+
+ act.sa_handler = handler;
+ sigemptyset(&act.sa_mask);
+ act.sa_flags = 0;
+ sigaction(sig, &act, NULL);
+ } else
+#endif /* defined(SIGWINCH) */
+ signal(sig, handler);
+}
+#endif /* HAVE_SIGACTION */
+
+#if defined(SIGTSTP) && !defined(USE_SLANG)
+#ifdef HAVE_SIGACTION
+/*
+ * For switching a signal's handling between SIG_DFL and something (possibly)
+ * different that may have been set up by lynx code or e.g. by curses library.
+ * Uses sigaction to preserve / restore as much state as possible.
+ *
+ * Second arg is where to save or restore from.
+ *
+ * Third arg to_dfl specifies what to do:
+ * 1 Save current state in where, set handling to SIG_DFL
+ * 0 Restore current state to previously saved one in where
+ *
+ * Currently only used for SIGTSTP without SLANG, to prevent (n)curses signal
+ * handler from running while lynx is waiting in system() for an interactive
+ * command like an editor. - kw
+ */
+static BOOLEAN LYToggleSigDfl(int sig,
+ struct sigaction *where,
+ int to_dfl)
+{
+ int rv = -1;
+ struct sigaction oact;
+
+ if (to_dfl == 1) {
+ rv = sigaction(sig, NULL, &oact);
+ if (rv == 0) {
+ if (oact.sa_handler != SIG_DFL) {
+ oact.sa_handler = SIG_DFL;
+ rv = sigaction(sig, &oact, where);
+ } else if (where) {
+ memcpy(where, &oact, sizeof(oact));
+ rv = 0;
+ }
+ }
+ } else {
+ rv = sigaction(sig, where, NULL);
+ }
+ if (rv != 0) {
+ CTRACE((tfp, "Error in LYToggleSigDfl: %s\n", LYStrerror(errno)));
+ return FALSE;
+ } else
+ return TRUE;
+}
+#endif /* HAVE_SIGACTION */
+#endif /* SIGTSTP && !USE_SLANG */
+
+/**************
+ * This bit of code catches window size change signals
+ */
+
+#ifdef HAVE_SYS_IOCTL_H
+#include <sys/ioctl.h>
+#endif
+
+/* For systems that have both, but both can't be included, duh (or neither) */
+/* FIXME: this whole chunk may be redundant */
+#ifdef TERMIO_AND_CURSES
+# ifdef TERMIO_AND_TERMIOS
+# include <termio.h>
+# else
+# ifdef HAVE_TERMIOS_H
+# include <termios.h>
+# else
+# ifdef HAVE_TERMIO_H
+# include <termio.h>
+# endif /* HAVE_TERMIO_H */
+# endif /* HAVE_TERMIOS_H */
+# endif /* TERMIO_AND_TERMIOS */
+#endif /* TERMIO_AND_CURSES */
+
+void LYGetScreenSize(int sig GCC_UNUSED)
+{
+ int old_lines = LYlines;
+ int old_cols = LYcols;
+
+#ifdef USE_SLANG
+#if defined(VMS) || defined(UNIX)
+ SLtt_get_screen_size();
+#endif /* VMS || UNIX */
+ LYlines = SLtt_Screen_Rows;
+ LYcols = SLtt_Screen_Cols;
+ if (sig == 0)
+ /* If called from start_curses(), no need to record size-changed */
+ return;
+#else /* Curses: */
+#ifdef HAVE_SIZECHANGE
+#if defined(TIOCGSIZE)
+ struct ttysize win;
+
+ if (ioctl(0, TIOCGSIZE, &win) == 0) {
+ if (win.ts_lines != 0) {
+ LYlines = win.ts_lines;
+ }
+ if (win.ts_cols != 0) {
+ LYcols = win.ts_cols;
+ }
+ }
+#elif defined(TIOCGWINSZ)
+ struct winsize win;
+
+ if (ioctl(0, (long) TIOCGWINSZ, &win) == 0) {
+ if (win.ws_row != 0) {
+ LYlines = win.ws_row;
+ }
+ if (win.ws_col != 0) {
+ LYcols = win.ws_col;
+ }
+ }
+#elif !defined(PDCURSES)
+#error inconsistent settings for TIOCGSIZE/TIOCGWINSZ
+#endif /* TIOCGSIZE/TIOCGWINSZ */
+#endif /* HAVE_SIZECHANGE */
+
+#ifdef __EMX__
+ {
+ int scrsize[2];
+
+ _scrsize(scrsize);
+ LYcols = scrsize[0];
+ LYlines = scrsize[1];
+ }
+#endif
+
+ if (LYlines <= 0)
+ LYlines = DFT_ROWS;
+ if (LYcols <= 0)
+ LYcols = DFT_COLS;
+#endif /* USE_SLANG */
+
+ /*
+ * Check if the screen size has actually changed. - AJL
+ */
+ if (LYlines != old_lines || LYcols != old_cols) {
+ recent_sizechange = TRUE;
+ CTRACE((tfp, "Window size changed from (%d,%d) to (%d,%d)\n",
+ old_lines, old_cols, LYlines, LYcols));
+#if defined(CAN_SWITCH_DISPLAY_CHARSET) && defined(CAN_AUTODETECT_DISPLAY_CHARSET)
+ /* May need to reload the font due to different char-box size */
+ if (current_char_set != auto_display_charset)
+ Switch_Display_Charset(current_char_set, SWITCH_DISPLAY_CHARSET_RESIZE);
+#endif
+ }
+}
+
+/*
+ * curses/slang functions call this to start catching SIGWINCH when sig==0,
+ * and continue catching when sig!=0.
+ */
+void size_change(int sig GCC_UNUSED)
+{
+#ifdef SIGWINCH
+ if (sig)
+ size_is_changed = TRUE;
+ LYExtSignal(SIGWINCH, size_change);
+#endif /* SIGWINCH */
+}
+
+/*
+ * Utility for freeing the list of previous suggested filenames. - FM
+ */
+void HTSugFilenames_free(void)
+{
+ LYFreeStringList(sug_filenames);
+ sug_filenames = NULL;
+}
+
+/*
+ * Utility for listing suggested filenames, making any repeated filenames the
+ * most current in the list. - FM
+ */
+void HTAddSugFilename(char *fname)
+{
+ char *tmp = NULL;
+ char *old;
+ HTList *cur;
+
+ if (!non_empty(fname))
+ return;
+
+ StrAllocCopy(tmp, fname);
+
+ if (!sug_filenames) {
+ sug_filenames = HTList_new();
+#ifdef LY_FIND_LEAKS
+ atexit(HTSugFilenames_free);
+#endif
+ HTList_addObject(sug_filenames, tmp);
+ return;
+ }
+
+ cur = sug_filenames;
+ while (NULL != (old = (char *) HTList_nextObject(cur))) {
+ if (!strcmp(old, tmp)) {
+ HTList_removeObject(sug_filenames, old);
+ FREE(old);
+ break;
+ }
+ }
+ HTList_addObject(sug_filenames, tmp);
+
+ return;
+}
+
+/*
+ * CHANGE_SUG_FILENAME -- Foteos Macrides 29-Dec-1993 Upgraded for use with
+ * Lynx2.2 - FM 17-Jan-1994
+ */
+void change_sug_filename(char *fname)
+{
+ const char *cp2;
+ char *temp = 0, *cp, *cp1, *end;
+
+#ifdef VMS
+ char *dot;
+ int j, k;
+#endif /* VMS */
+
+ /*
+ * Establish the current end of fname.
+ */
+ end = fname + strlen(fname);
+
+ /*
+ * Unescape fname.
+ */
+ HTUnEscape(fname);
+
+ /*
+ * Rename any temporary files.
+ */
+ cp2 = wwwName(lynx_temp_space);
+ if (LYIsHtmlSep(*cp2)) {
+ HTSprintf0(&temp, "file://localhost%s" PID_FMT, cp2, GETPID());
+ } else {
+ HTSprintf0(&temp, "file://localhost/%s" PID_FMT, cp2, GETPID());
+ }
+ if (!StrNCmp(fname, temp, strlen(temp))) {
+ if ((cp = strrchr(fname, '.')) != 0) {
+ if (strlen(cp) > (strlen(temp) - 4))
+ cp = NULL;
+ }
+ StrAllocCopy(temp, NonNull(cp));
+ sprintf(fname, "temp%.*s", LY_MAXPATH - 10, temp);
+ }
+ FREE(temp);
+
+ if (fname[strlen(fname) - 1] == '/')
+ /*
+ * Hmm... we have a directory name. It is annoying to see a
+ * scheme+host+path name as a suggested one, let's remove the
+ * last_slash and go ahead like we have a file name. - LP
+ */
+ fname[strlen(fname) - 1] = '\0';
+
+ /*
+ * Remove everything up the the last_slash if there is one.
+ */
+ if ((cp = strrchr(fname, '/')) != NULL && strlen(cp) > 1) {
+ cp1 = fname;
+ /*
+ * Go past the slash.
+ */
+ cp++;
+ for (; *cp != '\0'; cp++, cp1++) {
+ *cp1 = *cp;
+ }
+ *cp1 = '\0';
+ }
+#ifdef _WINDOWS /* 1998/05/05 (Tue) 10:08:05 */
+ if ((cp = strrchr(fname, '=')) != NULL && strlen(cp) > 1) {
+ cp1 = fname;
+ /*
+ * Go past the '='.
+ */
+ cp++;
+ for (; *cp != '\0'; cp++, cp1++) {
+ *cp1 = *cp;
+ }
+ *cp1 = '\0';
+ }
+#endif
+
+ /*
+ * Trim off date-size suffix, if present.
+ */
+ if ((*(end - 1) == ']') && ((cp = strrchr(fname, '[')) != NULL) &&
+ (cp > fname) && *(--cp) == ' ') {
+ while (*cp == ' ') {
+ *(cp--) = '\0';
+ }
+ }
+#ifdef VMS
+ /*
+ * Trim off VMS device and/or directory specs, if present.
+ */
+ if ((cp = StrChr(fname, '[')) != NULL &&
+ (cp1 = strrchr(cp, ']')) != NULL && strlen(cp1) > 1) {
+ cp1++;
+ for (cp = fname; *cp1 != '\0'; cp1++) {
+ *(cp++) = *cp1;
+ }
+ *cp = '\0';
+ }
+ /*
+ * Replace illegal or problem characters.
+ */
+ dot = fname + strlen(fname);
+ for (cp = fname; cp < dot; cp++) {
+ /*
+ * Replace with underscores.
+ */
+ if (*cp == ' ' || *cp == '/' || *cp == ':' ||
+ *cp == '[' || *cp == ']' || *cp == '&') {
+ *cp = '_';
+ /*
+ * Replace with dashes.
+ */
+ } else if (*cp == '!' || *cp == '?' || *cp == '\'' ||
+ *cp == ',' || *cp == ':' || *cp == '"' ||
+ *cp == '+' || *cp == '@' || *cp == '\\' ||
+ *cp == '(' || *cp == ')' || *cp == '=' ||
+ *cp == '<' || *cp == '>' || *cp == '#' ||
+ *cp == '%' || *cp == '*' || *cp == '`' ||
+ *cp == '~' || *cp == '^' || *cp == '|' ||
+ *cp < ' ' || (UCH(*cp)) > 126) {
+ *cp = '-';
+ }
+ }
+
+ /*
+ * Collapse any serial underscores.
+ */
+ cp = fname + 1;
+ j = 0;
+ while (cp < dot) {
+ if (fname[j] == '_' && *cp == '_') {
+ cp++;
+ } else {
+ fname[++j] = *cp++;
+ }
+ }
+ fname[++j] = '\0';
+
+ /*
+ * Collapse any serial dashes.
+ */
+ dot = fname + (strlen(fname));
+ cp = fname + 1;
+ j = 0;
+ while (cp < dot) {
+ if (fname[j] == '-' && *cp == '-') {
+ cp++;
+ } else {
+ fname[++j] = *cp++;
+ }
+ }
+ fname[++j] = '\0';
+
+ /*
+ * Trim any trailing or leading underscores or dashes.
+ */
+ cp = fname + (strlen(fname)) - 1;
+ while (*cp == '_' || *cp == '-') {
+ *cp-- = '\0';
+ }
+ if (fname[0] == '_' || fname[0] == '-') {
+ dot = fname + (strlen(fname));
+ cp = fname;
+ while ((*cp == '_' || *cp == '-') && cp < dot) {
+ cp++;
+ }
+ j = 0;
+ while (cp < dot) {
+ fname[j++] = *cp++;
+ }
+ fname[j] = '\0';
+ }
+
+ /*
+ * Replace all but the last period with _'s, or second to last if last is
+ * followed by a terminal Z or z, or GZ or gz,
+ * e.g., convert foo.tar.Z to foo.tar_Z
+ * or, convert foo.tar.gz to foo.tar-gz
+ */
+ j = strlen(fname) - 1;
+ if ((dot = strrchr(fname, '.')) != NULL) {
+ if (TOUPPER(fname[j]) == 'Z') {
+ if ((fname[j - 1] == '.') &&
+ (((cp = StrChr(fname, '.')) != NULL) && cp < dot)) {
+ *dot = '_';
+ dot = strrchr(fname, '.');
+ } else if (((TOUPPER(fname[j - 1]) == 'G') &&
+ fname[j - 2] == '.') &&
+ (((cp = StrChr(fname, '.')) != NULL) && cp < dot)) {
+ *dot = '-';
+ dot = strrchr(fname, '.');
+ }
+ }
+ cp = fname;
+ while ((cp = StrChr(cp, '.')) != NULL && cp < dot) {
+ *cp = '_';
+ }
+
+ /*
+ * But if the root is > 39 characters, move the period appropriately to
+ * the left.
+ */
+ while (dot - fname > 39) {
+ *dot = '\0';
+ if ((cp = strrchr(fname, '_')) != NULL) {
+ *cp = '.';
+ *dot = '_';
+ } else if ((cp = strrchr(fname, '-')) != NULL) {
+ *cp = '.';
+ *dot = '_';
+ } else if (*(dot + 1) == '\0') {
+ j = strlen(fname);
+ while (j > 39) {
+ fname[j] = fname[j - 1];
+ j--;
+ }
+ fname[j] = '.';
+ } else {
+ *dot = '.';
+ j = 39;
+ k = 0;
+ while (dot[k] != '\0') {
+ fname[j++] = dot[k++];
+ }
+ fname[j] = '\0';
+ }
+ dot = strrchr(fname, '.');
+ }
+
+ /*
+ * Make sure the extension is < 40 characters.
+ */
+ if ((fname + strlen(fname) - dot) > 39) {
+ *(dot + 40) = '\0';
+ }
+
+ /*
+ * Trim trailing dashes or underscores.
+ */
+ j = (strlen(fname) - 1);
+ while (fname[j] == '_' || fname[j] == '-') {
+ fname[j--] = '\0';
+ }
+ } else {
+ /*
+ * No period, so put one on the end, or after the 39th character,
+ * trimming trailing dashes or underscores.
+ */
+ if (strlen(fname) > 39) {
+ fname[39] = '\0';
+ }
+ j = (strlen(fname) - 1);
+ while ((fname[j] == '_') || (fname[j] == '-')) {
+ j--;
+ }
+ fname[++j] = '.';
+ fname[++j] = '\0';
+ }
+
+#else /* Not VMS (UNIX): */
+
+ /*
+ * Replace problem characters.
+ */
+ for (cp = fname; *cp != '\0'; cp++) {
+ switch (*cp) {
+ case '\'':
+ case '"':
+ case '/':
+ case ' ':
+ *cp = '-';
+ }
+ }
+#endif /* VMS (UNIX) */
+
+ /*
+ * Make sure the rest of the original string in nulled.
+ */
+ cp = fname + strlen(fname);
+ while (cp < end) {
+ *cp++ = '\0';
+ }
+
+ return;
+}
+
+/*
+ * Construct a temporary-filename. Assumes result is LY_MAXPATH chars long.
+ */
+static int fmt_tempname(char *result,
+ const char *prefix,
+ const char *suffix)
+{
+ int code;
+
+#ifdef HAVE_RAND_TEMPNAME
+#define SIZE_TEMPNAME ((MAX_TEMPNAME / BITS_PER_CHAR) + 1)
+ static BOOL first = TRUE;
+ static int names_used = 0;
+ static unsigned char used_tempname[SIZE_TEMPNAME];
+ unsigned offset, mask;
+#endif
+ static unsigned counter;
+ char leaf[LY_MAXPATH];
+
+ if (prefix == 0)
+ prefix = "";
+ if (suffix == 0)
+ suffix = "";
+ /*
+ * Prefer a random value rather than a counter.
+ */
+#ifdef HAVE_RAND_TEMPNAME
+ if (first) {
+ lynx_srand((unsigned) ((long) time((time_t *) NULL) + (long) result));
+ first = FALSE;
+ }
+
+ /* We don't really need all of the bits from rand(). The high-order bits
+ * are the more-random portion in any case, but limiting the width of the
+ * generated name is done partly to avoid problems on systems that may not
+ * support long filenames.
+ */
+ counter = MAX_TEMPNAME;
+ if (names_used < MAX_TEMPNAME) {
+ long get_rand = (long) lynx_rand();
+ long max_rand = (long) LYNX_RAND_MAX;
+
+ counter = (unsigned) (((float) MAX_TEMPNAME * (float) get_rand) /
+ (float) max_rand + 1);
+ /*
+ * Avoid reusing a temporary name, since there are places in the code
+ * which can refer to a temporary filename even after it has been
+ * closed and removed from the filesystem.
+ */
+ do {
+ counter %= MAX_TEMPNAME;
+ offset = counter / BITS_PER_CHAR;
+ mask = (unsigned) (1 << (counter % BITS_PER_CHAR));
+ if ((used_tempname[offset] & mask) == 0) {
+ names_used++;
+ used_tempname[offset] |= UCH(mask);
+ break;
+ }
+ } while ((used_tempname[offset] & mask) == 0);
+ }
+ if (names_used >= MAX_TEMPNAME)
+ HTAlert(gettext("Too many tempfiles"));
+#else
+ counter++;
+#endif
+
+#ifdef FNAMES_8_3
+ /*
+ * The 'lynx_temp_space' string ends with a '/' or '\\', so we only have to
+ * limit the length of the leaf. As received (e.g., from HTCompressed),
+ * the suffix may contain more than a ".htm", e.g., "-txt.gz", so we trim
+ * off from the filename portion to make room.
+ */
+ sprintf(leaf, PID_FMT PID_FMT, counter, GETPID());
+ if (strlen(leaf) > 8)
+ leaf[8] = 0;
+ if (strlen(suffix) > 4 || *suffix != '.') {
+ const char *tail = StrChr(suffix, '.');
+
+ if (tail == 0)
+ tail = suffix + strlen(suffix);
+ if (8 - (tail - suffix) >= 0)
+ leaf[8 - (tail - suffix)] = 0;
+ }
+ strcat(leaf, suffix);
+#else
+ sprintf(leaf, "L" PID_FMT "-%uTMP%s", GETPID(), counter, suffix);
+#endif
+ /*
+ * Someone could have configured the temporary pathname to be too long.
+ */
+ if ((strlen(prefix) + strlen(leaf)) < LY_MAXPATH) {
+ sprintf(result, "%s%s", prefix, leaf);
+ code = TRUE;
+ } else {
+ sprintf(result, "%.*s", LY_MAXPATH - 1, leaf);
+ code = FALSE;
+ }
+ CTRACE((tfp, "-> '%s'\n", result));
+ return (code);
+}
+
+/*
+ * Convert 4, 6, 2, 8 to left, right, down, up, etc.
+ */
+int number2arrows(int number)
+{
+ switch (number) {
+ case '1':
+ number = END_KEY;
+ break;
+ case '2':
+ number = DNARROW_KEY;
+ break;
+ case '3':
+ number = PGDOWN_KEY;
+ break;
+ case '4':
+ number = LTARROW_KEY;
+ break;
+ case '5':
+ number = DO_NOTHING;
+ break;
+ case '6':
+ number = RTARROW_KEY;
+ break;
+ case '7':
+ number = HOME_KEY;
+ break;
+ case '8':
+ number = UPARROW_KEY;
+ break;
+ case '9':
+ number = PGUP_KEY;
+ break;
+ }
+
+ return (number);
+}
+
+/*
+ * parse_restrictions takes a string of comma-separated restrictions and sets
+ * the corresponding flags to restrict the facilities available.
+ */
+/* The first two are special: we want to record whether "default" or "all"
+ * restrictions were applied, in addition to the detailed effects of those
+ * options. - kw
+ */
+/* skip the special flags when processing "all" and "default": */
+#define N_SPECIAL_RESTRICT_OPTIONS 2
+/* *INDENT-OFF* */
+static const struct {
+ const char *name;
+ BOOLEAN *flag;
+ BOOLEAN can;
+} restrictions[] = {
+ { "default", &had_restrictions_default, TRUE },
+ { "all", &had_restrictions_all, TRUE },
+ { "inside_telnet", &no_inside_telnet, CAN_ANONYMOUS_INSIDE_DOMAIN_TELNET },
+ { "outside_telnet", &no_outside_telnet, CAN_ANONYMOUS_OUTSIDE_DOMAIN_TELNET },
+ { "telnet_port", &no_telnet_port, CAN_ANONYMOUS_GOTO_TELNET_PORT },
+ { "inside_ftp", &no_inside_ftp, CAN_ANONYMOUS_INSIDE_DOMAIN_FTP },
+ { "outside_ftp", &no_outside_ftp, CAN_ANONYMOUS_OUTSIDE_DOMAIN_FTP },
+ { "inside_rlogin", &no_inside_rlogin, CAN_ANONYMOUS_INSIDE_DOMAIN_RLOGIN },
+ { "outside_rlogin", &no_outside_rlogin, CAN_ANONYMOUS_OUTSIDE_DOMAIN_RLOGIN },
+ { "suspend", &no_suspend, FALSE },
+ { "editor", &no_editor, FALSE },
+ { "shell", &no_shell, FALSE },
+ { "bookmark", &no_bookmark, FALSE },
+ { "multibook", &no_multibook, FALSE },
+ { "bookmark_exec", &no_bookmark_exec, FALSE },
+ { "option_save", &no_option_save, FALSE },
+ { "print", &no_print, CAN_ANONYMOUS_PRINT },
+ { "download", &no_download, FALSE },
+ { "disk_save", &no_disk_save, FALSE },
+#if defined(EXEC_LINKS) || defined(EXEC_SCRIPTS)
+ { "exec", &no_exec, LOCAL_EXECUTION_LINKS_ALWAYS_OFF_FOR_ANONYMOUS },
+#endif
+ { "lynxcgi", &no_lynxcgi, FALSE },
+ { "exec_frozen", &exec_frozen, FALSE },
+ { "goto", &no_goto, CAN_ANONYMOUS_GOTO },
+ { "jump", &no_jump, CAN_ANONYMOUS_JUMP },
+ { "file_url", &no_file_url, FALSE },
+#ifndef DISABLE_NEWS
+ { "news_post", &no_newspost, FALSE },
+ { "inside_news", &no_inside_news, CAN_ANONYMOUS_INSIDE_DOMAIN_READ_NEWS },
+ { "outside_news", &no_outside_news, CAN_ANONYMOUS_OUTSIDE_DOMAIN_READ_NEWS },
+#endif
+ { "mail", &no_mail, CAN_ANONYMOUS_MAIL },
+ { "dotfiles", &no_dotfiles, FALSE },
+ { "useragent", &no_useragent, FALSE },
+#ifdef SUPPORT_CHDIR
+ { "chdir", &no_chdir, FALSE },
+#endif
+#ifdef DIRED_SUPPORT
+ { "dired_support", &no_dired_support, FALSE },
+#ifdef OK_PERMIT
+ { "change_exec_perms", &no_change_exec_perms, FALSE },
+#endif /* OK_PERMIT */
+#endif /* DIRED_SUPPORT */
+#ifdef USE_EXTERNALS
+ { "externals", &no_externals, FALSE },
+#endif
+ { "lynxcfg_info", &no_lynxcfg_info, CAN_ANONYMOUS_VIEW_LYNXCFG_INFO },
+#ifndef NO_CONFIG_INFO
+ { "lynxcfg_xinfo", &no_lynxcfg_xinfo, CAN_ANONYMOUS_VIEW_LYNXCFG_EXTENDED_INFO },
+#ifdef HAVE_CONFIG_H
+ { "compileopts_info", &no_compileopts_info, CAN_ANONYMOUS_VIEW_COMPILEOPTS_INFO },
+#endif
+#endif
+ /* put "goto" restrictions on the end, since they are a refinement */
+#ifndef DISABLE_BIBP
+ { "goto_bibp", &no_goto_bibp, CAN_ANONYMOUS_GOTO_BIBP },
+#endif
+#ifdef HAVE_CONFIG_H
+#ifndef NO_CONFIG_INFO
+ { "goto_configinfo", &no_goto_configinfo, CAN_ANONYMOUS_GOTO_CONFIGINFO },
+#endif
+#endif
+ { "goto_cso", &no_goto_cso, CAN_ANONYMOUS_GOTO_CSO },
+ { "goto_file", &no_goto_file, CAN_ANONYMOUS_GOTO_FILE },
+#ifndef DISABLE_FINGER
+ { "goto_finger", &no_goto_finger, CAN_ANONYMOUS_GOTO_FINGER },
+#endif
+ { "goto_ftp", &no_goto_ftp, CAN_ANONYMOUS_GOTO_FTP },
+#ifndef DISABLE_GOPHER
+ { "goto_gopher", &no_goto_gopher, CAN_ANONYMOUS_GOTO_GOPHER },
+#endif
+ { "goto_http", &no_goto_http, CAN_ANONYMOUS_GOTO_HTTP },
+ { "goto_https", &no_goto_https, CAN_ANONYMOUS_GOTO_HTTPS },
+ { "goto_lynxcgi", &no_goto_lynxcgi, CAN_ANONYMOUS_GOTO_LYNXCGI },
+ { "goto_lynxexec", &no_goto_lynxexec, CAN_ANONYMOUS_GOTO_LYNXEXEC },
+ { "goto_lynxprog", &no_goto_lynxprog, CAN_ANONYMOUS_GOTO_LYNXPROG },
+ { "goto_mailto", &no_goto_mailto, CAN_ANONYMOUS_GOTO_MAILTO },
+#ifndef DISABLE_NEWS
+ { "goto_news", &no_goto_news, CAN_ANONYMOUS_GOTO_NEWS },
+ { "goto_nntp", &no_goto_nntp, CAN_ANONYMOUS_GOTO_NNTP },
+#endif
+ { "goto_rlogin", &no_goto_rlogin, CAN_ANONYMOUS_GOTO_RLOGIN },
+#ifndef DISABLE_NEWS
+ { "goto_snews", &no_goto_snews, CAN_ANONYMOUS_GOTO_SNEWS },
+#endif
+ { "goto_telnet", &no_goto_telnet, CAN_ANONYMOUS_GOTO_TELNET },
+ { "goto_tn3270", &no_goto_tn3270, CAN_ANONYMOUS_GOTO_TN3270 },
+ { "goto_wais", &no_goto_wais, CAN_ANONYMOUS_GOTO_WAIS },
+};
+/* *INDENT-ON* */
+
+/* This will make no difference between '-' and '_'. It does only in/equality
+ * compare. It assumes that p2 can't contain dashes, but p1 can. This
+ * function is also used (if macro OPTNAME_ALLOW_DASHES doesn't have value of
+ * zero) for compare of commandline options -VH
+ */
+BOOL strn_dash_equ(const char *p1,
+ const char *p2,
+ int len)
+{
+ while (len--) {
+ if (!*p2)
+ return 0; /* canonical name is shorter */
+ switch (*p1) {
+ case 0:
+ return 0;
+ case '-':
+ case '_':
+ if (*p2 != '_')
+ return 0;
+ else
+ break;
+ default:
+ if (*p1 != *p2)
+ return 0;
+ }
+ ++p1;
+ ++p2;
+ }
+ return 1;
+}
+
+/* Uncomment following lines to allow only exact string matching */
+/* #define RESTRICT_NM_ALLOW_DASHES 0 */
+
+#ifndef RESTRICT_NM_ALLOW_DASHES
+# define RESTRICT_NM_ALLOW_DASHES 1
+#endif
+
+#if RESTRICT_NM_ALLOW_DASHES
+# define RESTRICT_NM_EQU(a,b,len) strn_dash_equ(a,b,len)
+#else
+# define RESTRICT_NM_EQU(a,b,len) STRNEQ(a,b,len)
+#endif
+
+/*
+ * Returns the inx'th name from the restrictions table, or null if inx is
+ * out of range.
+ */
+const char *index_to_restriction(unsigned inx)
+{
+ if (inx < TABLESIZE(restrictions))
+ return restrictions[inx].name;
+ return NULL;
+}
+
+/*
+ * Returns the value TRUE/FALSE of a given restriction, or -1 if it is not
+ * one that we recognize.
+ */
+int find_restriction(const char *name,
+ int len)
+{
+ unsigned i;
+
+ if (len < 0)
+ len = (int) strlen(name);
+ for (i = 0; i < TABLESIZE(restrictions); i++) {
+ if (RESTRICT_NM_EQU(name, restrictions[i].name, len)) {
+ return (*restrictions[i].flag);
+ }
+ }
+ return -1;
+}
+
+void parse_restrictions(const char *s)
+{
+ const char *p;
+ const char *word;
+ unsigned i;
+ BOOLEAN found;
+
+ p = s;
+ while (*p) {
+ p = LYSkipCBlanks(p);
+ if (*p == '\0')
+ break;
+ word = p;
+ while (*p != ',' && *p != '\0')
+ p++;
+
+ found = FALSE;
+ if (RESTRICT_NM_EQU(word, "all", (int) (p - word))) {
+ found = TRUE;
+ for (i = N_SPECIAL_RESTRICT_OPTIONS;
+ i < TABLESIZE(restrictions);
+ i++)
+ *(restrictions[i].flag) = TRUE;
+ } else if (RESTRICT_NM_EQU(word, "default", (int) (p - word))) {
+ found = TRUE;
+ for (i = N_SPECIAL_RESTRICT_OPTIONS;
+ i < TABLESIZE(restrictions);
+ i++)
+ *(restrictions[i].flag) = (BOOLEAN) !restrictions[i].can;
+ } else {
+ for (i = 0; i < TABLESIZE(restrictions); i++) {
+ if (RESTRICT_NM_EQU(word, restrictions[i].name, (int) (p - word))) {
+ *(restrictions[i].flag) = TRUE;
+ found = TRUE;
+ break;
+ }
+ }
+ }
+ if (!found) {
+ printf("%s: %.*s\n", gettext("unknown restriction"),
+ (int) (p - word), word);
+ exit_immediately(EXIT_FAILURE);
+ }
+ if (*p)
+ p++;
+ }
+
+ /*
+ * If shell is restricted, set restrictions on related topics.
+ */
+ if (no_shell) {
+ no_goto_lynxexec = TRUE;
+ no_goto_lynxprog = TRUE;
+ no_goto_lynxcgi = TRUE;
+#ifdef EXEC_LINKS
+ local_exec_on_local_files = TRUE;
+#endif
+ }
+}
+
+void print_restrictions_to_fd(FILE *fp)
+{
+ unsigned i, count = 0;
+
+ for (i = 0; i < TABLESIZE(restrictions); i++) {
+ if (*(restrictions[i].flag) == TRUE) {
+ count++;
+ }
+ }
+ if (!count) {
+ fprintf(fp, gettext("No restrictions set.\n"));
+ return;
+ }
+ fprintf(fp, gettext("Restrictions set:\n"));
+ for (i = 0; i < TABLESIZE(restrictions); i++) {
+ if (*(restrictions[i].flag) == TRUE) {
+ /* if "goto" is restricted, don't bother tell about its
+ * refinements
+ */
+ if (StrNCmp(restrictions[i].name, "goto_", 5)
+ || !no_goto)
+ fprintf(fp, " %s\n", restrictions[i].name);
+ }
+ }
+}
+
+#ifdef VMS
+#include <jpidef.h>
+#include <maildef.h>
+#include <starlet.h>
+
+typedef struct _VMSMailItemList {
+ short buffer_length;
+ short item_code;
+ void *buffer_address;
+ long *return_length_address;
+} VMSMailItemList;
+
+void LYCheckMail(void)
+{
+ static BOOL firsttime = TRUE, failure = FALSE;
+ static char user[13], dir[252];
+ static long userlen = 0, dirlen;
+ static time_t lastcheck = 0;
+ time_t now;
+ static short new, lastcount;
+ long ucontext = 0, status;
+ short flags = MAIL$M_NEWMSG;
+ /* *INDENT-OFF* */
+ VMSMailItemList
+ null_list[] = {{0,0,0,0}},
+ jpi_list[] = {{sizeof(user) - 1,JPI$_USERNAME,(void *)user,&userlen},
+ {0,0,0,0}},
+ uilist[] = {{0,MAIL$_USER_USERNAME,0,0},
+ {0,0,0,0}},
+ uolist[] = {{sizeof(new),MAIL$_USER_NEW_MESSAGES,&new,0},
+ {sizeof(dir),MAIL$_USER_FULL_DIRECTORY,dir,&dirlen},
+ {0,0,0,0}};
+ /* *INDENT-ON* */
+
+ extern long mail$user_begin();
+ extern long mail$user_get_info();
+ extern long mail$user_end();
+
+ if (failure)
+ return;
+
+ if (firsttime) {
+ firsttime = FALSE;
+ /* Get the username. */
+ status = sys$getjpiw(0, 0, 0, jpi_list, 0, 0, 0);
+ if (!(status & 1)) {
+ failure = TRUE;
+ return;
+ }
+ user[userlen] = '\0';
+ LYTrimTrailing(user);
+ }
+
+ /* Minimum report interval is 60 sec. */
+ time(&now);
+ if (now - lastcheck < 60)
+ return;
+ lastcheck = now;
+
+ /* Get the current newmail count. */
+ status = mail$user_begin(&ucontext, null_list, null_list);
+ if (!(status & 1)) {
+ failure = TRUE;
+ return;
+ }
+ uilist[0].buffer_length = strlen(user);
+ uilist[0].buffer_address = user;
+ status = mail$user_get_info(&ucontext, uilist, uolist);
+ if (!(status & 1)) {
+ failure = TRUE;
+ return;
+ }
+
+ /* Should we report anything to the user? */
+ if (new > 0) {
+ if (lastcount == 0)
+ /* Have newmail at startup of Lynx. */
+ HTUserMsg(HAVE_UNREAD_MAIL_MSG);
+ else if (new > lastcount)
+ /* Have additional mail since last report. */
+ HTUserMsg(HAVE_NEW_MAIL_MSG);
+ lastcount = new;
+ return;
+ }
+ lastcount = new;
+
+ /* Clear the context */
+ mail$user_end((long *) &ucontext, null_list, null_list);
+ return;
+}
+#else
+void LYCheckMail(void)
+{
+ static BOOL firsttime = TRUE;
+ static char *mf;
+ static time_t lastcheck;
+ static time_t lasttime;
+ static long lastsize;
+ time_t now;
+ struct stat st;
+
+ if (firsttime) {
+ mf = LYGetEnv("MAIL");
+ firsttime = FALSE;
+ time(&lasttime);
+ }
+
+ if (mf == NULL)
+ return;
+
+ time(&now);
+ if (now - lastcheck < 60)
+ return;
+ lastcheck = now;
+
+ if ((stat(mf, &st) < 0)
+ || !S_ISREG(st.st_mode)) {
+ mf = NULL;
+ return;
+ }
+
+ if (st.st_size > 0) {
+ if (((lasttime != st.st_mtime) && (st.st_mtime > st.st_atime))
+ || ((lastsize != 0) && (st.st_size > lastsize)))
+ HTUserMsg(HAVE_NEW_MAIL_MSG);
+ else if (lastsize == 0)
+ HTUserMsg(HAVE_MAIL_MSG);
+ }
+ lastsize = (long) st.st_size;
+ lasttime = st.st_mtime;
+ return;
+}
+#endif /* VMS */
+
+/*
+ * This function ensures that an href will be
+ * converted to a fully resolved, absolute URL,
+ * with guessing of the host or expansions of
+ * lead tildes via LYConvertToURL() if needed,
+ * and tweaking/simplifying via HTParse(). It
+ * is used for LynxHome, startfile, homepage,
+ * and 'g'oto entries, after they have been
+ * passed to LYFillLocalFileURL(). - FM
+ * Such URLs have no `base' reference to which they
+ * could be resolved. LYLegitimizeHREF could not be used.
+ */
+void LYEnsureAbsoluteURL(char **href,
+ const char *name,
+ int fixit)
+{
+ char *temp = NULL;
+
+ if (isEmpty(*href))
+ return;
+
+ /*
+ * Check whether to fill in localhost. - FM
+ */
+ LYFillLocalFileURL(href, "file://localhost");
+
+ /*
+ * If it is not a URL then make it one.
+ */
+ if (!strcasecomp(*href, STR_NEWS_URL)) {
+ StrAllocCat(*href, "*");
+ } else if (!strcasecomp(*href, STR_SNEWS_URL)) {
+ StrAllocCat(*href, "/*");
+ }
+
+ if (!is_url(*href)) {
+ CTRACE((tfp, "%s%s'%s' is not a URL\n",
+ NonNull(name), (name ? " " : ""), *href));
+ LYConvertToURL(href, fixit);
+ }
+
+ temp = HTParse(*href, "", PARSE_ALL);
+ if (non_empty(temp))
+ StrAllocCopy(*href, temp);
+ FREE(temp);
+}
+
+static const char *default_scheme = "http://";
+
+static const char *guess_scheme(const char *url)
+{
+ const char *scheme = NULL;
+
+ if (LYGuessScheme && non_empty(url)) {
+ if (0 == strncasecomp(url, "www.", 4)) {
+ scheme = "http://";
+ } else if (0 == strncasecomp(url, "ftp.", 4)) {
+ scheme = "ftp://";
+ } else if (0 == strncasecomp(url, "gopher.", 7)) {
+ scheme = "gopher://";
+ } else if (0 == strncasecomp(url, "wais.", 5)) {
+ scheme = "wais://";
+ } else if (0 == strncasecomp(url, "cso.", 4) ||
+ 0 == strncasecomp(url, "ns.", 3) ||
+ 0 == strncasecomp(url, "ph.", 3)) {
+ scheme = "cso://";
+ } else if (0 == strncasecomp(url, "finger.", 7)) {
+ scheme = "finger://";
+ } else if (0 == strncasecomp(url, "news.", 5)) {
+ scheme = "news://";
+ } else if (0 == strncasecomp(url, "nntp.", 5)) {
+ scheme = "nntp://";
+ }
+ }
+ CTRACE((tfp, "guess_scheme(%s) -> '%s'\n", NonNull(url), NonNull(scheme)));
+ return scheme;
+}
+
+/*
+ * This function rewrites and reallocates a previously allocated string that
+ * begins with an Internet host name so that the string begins with its guess
+ * of the scheme based on the first field of the host name, or the default
+ * scheme if no guess was made.
+ */
+static void LYAddSchemeForURL(char **AllocatedString)
+{
+ if (non_empty(*AllocatedString)) {
+ char *Str = NULL;
+ const char *scheme = guess_scheme(*AllocatedString);
+
+ if (scheme == NULL)
+ scheme = default_scheme;
+
+ StrAllocCopy(Str, scheme);
+ StrAllocCat(Str, *AllocatedString);
+ StrAllocCopy(*AllocatedString, Str);
+
+ FREE(Str);
+ }
+}
+
+/*
+ * This function rewrites and reallocates a previously allocated string so that
+ * the first element is a confirmed Internet host, and returns TRUE, otherwise
+ * it does not modify the string and returns FALSE.
+ *
+ * It first tries the element as is, then, if the element does not end with a
+ * dot, it adds prefixes from the (comma separated) prefix list argument, and,
+ * if the element does not begin with a dot, suffixes from the (comma
+ * separated) suffix list arguments (e.g., www.host.com, then www.host,edu,
+ * then www.host.net, then www.host.org).
+ *
+ * The remaining path, if one is present, will be appended to the expanded
+ * host.
+ *
+ * It also takes into account whether a colon is in the element or suffix, and
+ * includes that and what follows as a port field for the expanded host field
+ * (e.g, wfbr:8002/dir/lynx should yield www.wfbr.edu:8002/dir/lynx).
+ */
+static BOOLEAN LYExpandHostForURL(char **AllocatedString,
+ char *prefix_list,
+ char *suffix_list)
+{
+ char *DomainPrefix = NULL;
+ const char *StartP, *EndP;
+ char *DomainSuffix = NULL;
+ const char *StartS, *EndS;
+ char *Str = NULL, *StrColon = NULL, *MsgStr = NULL;
+ char *Host = NULL, *HostColon = NULL, *host = NULL;
+ char *Path = NULL;
+ char *Fragment = NULL;
+ BOOLEAN GotHost = FALSE;
+ BOOLEAN Startup = (BOOL) (helpfilepath == NULL);
+
+ /*
+ * If it's a NULL or zero-length string, or if it begins with a slash or
+ * hash, don't continue pointlessly. - FM
+ */
+ if (isEmpty(*AllocatedString) ||
+ *AllocatedString[0] == '/' ||
+ *AllocatedString[0] == '#') {
+ return GotHost;
+ }
+
+ /*
+ * If it's a partial or relative path, don't continue pointlessly. - FM
+ */
+ if (!StrNCmp(*AllocatedString, "..", 2) ||
+ !StrNCmp(*AllocatedString, "./", 2)) {
+ return GotHost;
+ }
+
+ /*
+ * Make a clean copy of the string, and trim off the path if one is
+ * present, but save the information so we can restore the path after
+ * filling in the Host[:port] field. - FM
+ */
+ StrAllocCopy(Str, *AllocatedString);
+ if ((Path = StrChr(Str, '/')) != NULL) {
+ /*
+ * Have a path. Any fragment should already be included in Path. - FM
+ */
+ *Path = '\0';
+ } else {
+ /*
+ * No path, so check for a fragment and trim that, to be restored after
+ * filling in the Host[:port] field. - FM
+ */
+ Fragment = trimPoundSelector(Str);
+ }
+
+ /*
+ * If the potential host string has a colon, assume it begins a port field,
+ * and trim it off, but save the information so we can restore the port
+ * field after filling in the host field. - FM
+ */
+ if ((StrColon = strrchr(Str, ':')) != NULL &&
+ isdigit(UCH(StrColon[1])) && StrChr(StrColon, ']') == NULL) {
+ if (StrColon == Str) {
+ goto cleanup;
+ }
+ *StrColon = '\0';
+ }
+
+ /*
+ * Do a DNS test on the potential host field as presently trimmed. - FM
+ */
+ StrAllocCopy(host, Str);
+ strip_userid(host, FALSE);
+ HTUnEscape(host);
+ if (LYCursesON) {
+ StrAllocCopy(MsgStr, WWW_FIND_MESSAGE);
+ StrAllocCat(MsgStr, host);
+ StrAllocCat(MsgStr, FIRST_SEGMENT);
+ HTProgress(MsgStr);
+ } else if (Startup && !dump_output_immediately) {
+ fprintf(stdout, "%s '%s'%s\r\n", WWW_FIND_MESSAGE, host, FIRST_SEGMENT);
+ }
+#ifdef INET6
+ if (HTCheckAddrInfo(host, 80))
+#else
+ if (LYCheckHostByName(host))
+#endif /* INET6 */
+ {
+ /*
+ * Clear any residual interrupt. - FM
+ */
+ if (LYCursesON && HTCheckForInterrupt()) {
+ CTRACE((tfp,
+ "LYExpandHostForURL: Ignoring interrupt because '%s' resolved.\n",
+ host));
+ }
+
+ /*
+ * Return success. - FM
+ */
+ GotHost = TRUE;
+ goto cleanup;
+ } else if (LYCursesON && (lynx_nsl_status == HT_INTERRUPTED)) {
+ /*
+ * Give the user chance to interrupt lookup cycles. - KW & FM
+ */
+ CTRACE((tfp,
+ "LYExpandHostForURL: Interrupted while '%s' failed to resolve.\n",
+ host));
+
+ /*
+ * Return failure. - FM
+ */
+ goto cleanup;
+ }
+
+ /*
+ * Set the first prefix, making it a zero-length string if the list is NULL
+ * or if the potential host field ends with a dot. - FM
+ */
+ StartP = ((prefix_list && Str[strlen(Str) - 1] != '.')
+ ? prefix_list
+ : "");
+ /*
+ * If we have a prefix, but the allocated string is one of the common host
+ * prefixes, make our prefix a zero-length string. - FM
+ */
+ if (*StartP && *StartP != '.') {
+ if (guess_scheme(*AllocatedString) != NULL) {
+ StartP = "";
+ }
+ }
+ while ((*StartP) && (WHITE(*StartP) || *StartP == ',')) {
+ StartP++; /* Skip whitespace and separators */
+ }
+ EndP = StartP;
+ while (*EndP && !WHITE(*EndP) && *EndP != ',') {
+ EndP++; /* Find separator */
+ }
+ StrAllocCopy(DomainPrefix, StartP);
+ DomainPrefix[EndP - StartP] = '\0';
+
+ /*
+ * Test each prefix with each suffix. - FM
+ */
+ do {
+ /*
+ * Set the first suffix, making it a zero-length string if the list is
+ * NULL or if the potential host field begins with a dot. - FM
+ */
+ StartS = ((suffix_list && *Str != '.')
+ ? suffix_list
+ : "");
+ while ((*StartS) && (WHITE(*StartS) || *StartS == ',')) {
+ StartS++; /* Skip whitespace and separators */
+ }
+ EndS = StartS;
+ while (*EndS && !WHITE(*EndS) && *EndS != ',') {
+ EndS++; /* Find separator */
+ }
+ StrAllocCopy(DomainSuffix, StartS);
+ DomainSuffix[EndS - StartS] = '\0';
+
+ /*
+ * Create domain names and do DNS tests. - FM
+ */
+ do {
+ StrAllocCopy(Host, DomainPrefix);
+ StrAllocCat(Host, ((*Str == '.') ? (Str + 1) : Str));
+ if (Host[strlen(Host) - 1] == '.') {
+ Host[strlen(Host) - 1] = '\0';
+ }
+ StrAllocCat(Host, DomainSuffix);
+ if ((HostColon = strrchr(Host, ':')) != NULL &&
+ isdigit(UCH(HostColon[1]))) {
+ *HostColon = '\0';
+ }
+ StrAllocCopy(host, Host);
+ HTUnEscape(host);
+ if (LYCursesON) {
+ StrAllocCopy(MsgStr, WWW_FIND_MESSAGE);
+ StrAllocCat(MsgStr, host);
+ StrAllocCat(MsgStr, GUESSING_SEGMENT);
+ HTProgress(MsgStr);
+ } else if (Startup && !dump_output_immediately) {
+ fprintf(stdout, "%s '%s'%s\n", WWW_FIND_MESSAGE, host, GUESSING_SEGMENT);
+ }
+ GotHost = LYCheckHostByName(host);
+ if (HostColon != NULL) {
+ *HostColon = ':';
+ }
+ if (GotHost == FALSE) {
+ /*
+ * Give the user chance to interrupt lookup cycles. - KW
+ */
+ if (LYCursesON && (lynx_nsl_status == HT_INTERRUPTED)) {
+ CTRACE((tfp,
+ "LYExpandHostForURL: Interrupted while '%s' failed to resolve.\n",
+ host));
+ goto cleanup; /* We didn't find a valid name. */
+ }
+
+ /*
+ * Advance to the next suffix, or end of suffix list. - FM
+ */
+ StartS = ((*EndS == '\0') ? EndS : (EndS + 1));
+ while ((*StartS) && (WHITE(*StartS) || *StartS == ',')) {
+ StartS++; /* Skip whitespace and separators */
+ }
+ EndS = StartS;
+ while (*EndS && !WHITE(*EndS) && *EndS != ',') {
+ EndS++; /* Find separator */
+ }
+ LYStrNCpy(DomainSuffix, StartS, (EndS - StartS));
+ }
+ } while ((GotHost == FALSE) && (*DomainSuffix != '\0'));
+
+ if (GotHost == FALSE) {
+ /*
+ * Advance to the next prefix, or end of prefix list. - FM
+ */
+ StartP = ((*EndP == '\0') ? EndP : (EndP + 1));
+ while ((*StartP) && (WHITE(*StartP) || *StartP == ',')) {
+ StartP++; /* Skip whitespace and separators */
+ }
+ EndP = StartP;
+ while (*EndP && !WHITE(*EndP) && *EndP != ',') {
+ EndP++; /* Find separator */
+ }
+ LYStrNCpy(DomainPrefix, StartP, (EndP - StartP));
+ }
+ } while ((GotHost == FALSE) && (*DomainPrefix != '\0'));
+
+ /*
+ * If a test passed, restore the port field if we had one and there is no
+ * colon in the expanded host, and the path if we had one, and reallocate
+ * the original string with the expanded Host[:port] field included. - FM
+ */
+ if (GotHost) {
+ if (StrColon && StrChr(Host, ':') == NULL) {
+ *StrColon = ':';
+ StrAllocCat(Host, StrColon);
+ }
+ if (Path) {
+ *Path = '/';
+ StrAllocCat(Host, Path);
+ } else if (Fragment) {
+ StrAllocCat(Host, "/");
+ restorePoundSelector(Fragment);
+ StrAllocCat(Host, Fragment);
+ }
+ StrAllocCopy(*AllocatedString, Host);
+ }
+
+ /*
+ * Clear any residual interrupt. - FM
+ */
+ if (LYCursesON && HTCheckForInterrupt()) {
+ CTRACE((tfp,
+ "LYExpandHostForURL: Ignoring interrupt because '%s' %s.\n",
+ host,
+ (GotHost ? "resolved" : "timed out")));
+ }
+
+ /*
+ * Clean up and return the last test result. - FM
+ */
+ cleanup:
+ FREE(DomainPrefix);
+ FREE(DomainSuffix);
+ FREE(Str);
+ FREE(MsgStr);
+ FREE(Host);
+ FREE(host);
+ return GotHost;
+}
+/*
+ * Rewrite and reallocate a previously allocated string as a file URL if the
+ * string resolves to a file or directory on the local system, otherwise as an
+ * http URL. - FM
+ */
+void LYConvertToURL(char **AllocatedString,
+ int fixit)
+{
+ char *old_string = *AllocatedString;
+ char *temp = NULL;
+ char *cp = NULL;
+
+#ifndef VMS
+ struct stat st;
+#endif /* !VMS */
+
+ if (isEmpty(old_string))
+ return;
+
+#if defined(USE_DOS_DRIVES)
+ {
+ char *cp_url = *AllocatedString;
+
+ for (; *cp_url != '\0'; cp_url++)
+ if (*cp_url == '\\')
+ *cp_url = '/';
+ cp_url--;
+ if (LYIsDosDrive(*AllocatedString) && *cp_url == ':')
+ LYAddPathSep(AllocatedString);
+ }
+#endif /* USE_DOS_DRIVES */
+
+ *AllocatedString = NULL; /* so StrAllocCopy doesn't free it */
+ StrAllocCopy(*AllocatedString, "file://localhost");
+
+ if (*old_string != '/') {
+ char *fragment = NULL;
+
+#if defined(USE_DOS_DRIVES)
+ StrAllocCat(*AllocatedString, "/");
+#endif /* USE_DOS_DRIVES */
+#ifdef VMS
+ /*
+ * Not a SHELL pathspec. Get the full VMS spec and convert it.
+ */
+ char *cur_dir = NULL;
+ static char url_file[LY_MAXPATH], file_name[LY_MAXPATH], dir_name[LY_MAXPATH];
+ unsigned long context = 0;
+
+ $DESCRIPTOR(url_file_dsc, url_file);
+ $DESCRIPTOR(file_name_dsc, file_name);
+ if (LYIsTilde(*old_string)) {
+ /*
+ * On VMS, we'll accept '~' on the command line as Home_Dir(), and
+ * assume the rest of the path, if any, has SHELL syntax.
+ */
+ StrAllocCat(*AllocatedString, HTVMS_wwwName(Home_Dir()));
+ if ((cp = StrChr(old_string, '/')) != NULL) {
+ /*
+ * Append rest of path, if present, skipping "user" if "~user"
+ * was entered, simplifying, and eliminating any residual
+ * relative elements. - FM
+ */
+ StrAllocCopy(temp, cp);
+ LYTrimRelFromAbsPath(temp);
+ StrAllocCat(*AllocatedString, temp);
+ FREE(temp);
+ }
+ goto have_VMS_URL;
+ } else {
+ fragment = trimPoundSelector(old_string);
+ LYStrNCpy(url_file, old_string, sizeof(url_file) - 1);
+ }
+ url_file_dsc.dsc$w_length = (short) strlen(url_file);
+ if (1 & lib$find_file(&url_file_dsc, &file_name_dsc, &context,
+ 0, 0, 0, 0)) {
+ /*
+ * We found the file. Convert to a URL pathspec.
+ */
+ if ((cp = StrChr(file_name, ';')) != NULL) {
+ *cp = '\0';
+ }
+ LYLowerCase(file_name);
+ StrAllocCat(*AllocatedString, HTVMS_wwwName(file_name));
+ if ((cp = StrChr(old_string, ';')) != NULL) {
+ StrAllocCat(*AllocatedString, cp);
+ }
+ if (fragment != NULL) {
+ restorePoundSelector(fragment);
+ StrAllocCat(*AllocatedString, fragment);
+ fragment = NULL;
+ }
+ } else if ((NULL != getcwd(dir_name, sizeof(dir_name) - 1, 0)) &&
+ 0 == chdir(old_string)) {
+ /*
+ * Probably a directory. Try converting that.
+ */
+ StrAllocCopy(cur_dir, dir_name);
+ restorePoundSelector(fragment);
+ if (NULL != getcwd(dir_name, sizeof(dir_name) - 1, 0)) {
+ /*
+ * Yup, we got it!
+ */
+ LYLowerCase(dir_name);
+ StrAllocCat(*AllocatedString, dir_name);
+ if (fragment != NULL) {
+ StrAllocCat(*AllocatedString, fragment);
+ fragment = NULL;
+ }
+ } else {
+ /*
+ * Nope. Assume it's an http URL with the "http://" defaulted,
+ * if we can't rule out a bad VMS path.
+ */
+ fragment = NULL;
+ if (StrChr(old_string, '[') ||
+ ((cp = StrChr(old_string, ':')) != NULL &&
+ !isdigit(UCH(cp[1]))) ||
+ !LYExpandHostForURL(&old_string,
+ URLDomainPrefixes,
+ URLDomainSuffixes)) {
+ /*
+ * Probably a bad VMS path (but can't be sure). Use
+ * original pathspec for the error message that will
+ * result.
+ */
+ sprintf(url_file, "/%.*s", sizeof(url_file) - 2, old_string);
+ CTRACE((tfp,
+ "Can't find '%s' Will assume it's a bad path.\n",
+ old_string));
+ StrAllocCat(*AllocatedString, url_file);
+ } else {
+ LYAddSchemeForURL(&old_string);
+ StrAllocCopy(*AllocatedString, old_string);
+ }
+ }
+ } else {
+ /*
+ * Nothing found. Assume it's an http URL with the "http://"
+ * defaulted, if we can't rule out a bad VMS path.
+ */
+ restorePoundSelector(fragment);
+ fragment = NULL;
+
+ if (StrChr(old_string, '[') ||
+ ((cp = StrChr(old_string, ':')) != NULL &&
+ !isdigit(UCH(cp[1]))) ||
+ !LYExpandHostForURL(&old_string,
+ URLDomainPrefixes,
+ URLDomainSuffixes)) {
+ /*
+ * Probably a bad VMS path (but can't be sure). Use original
+ * pathspec for the error message that will result.
+ */
+ sprintf(url_file, "/%.*s", sizeof(url_file) - 2, old_string);
+ CTRACE((tfp, "Can't find '%s' Will assume it's a bad path.\n",
+ old_string));
+ StrAllocCat(*AllocatedString, url_file);
+ } else {
+ LYAddSchemeForURL(&old_string);
+ StrAllocCopy(*AllocatedString, old_string);
+ }
+ }
+ lib$find_file_end(&context);
+ FREE(cur_dir);
+ have_VMS_URL:
+ CTRACE((tfp, "Trying: '%s'\n", *AllocatedString));
+#else /* not VMS: */
+#if defined(USE_DOS_DRIVES)
+#ifdef _WINDOWS
+ if (*old_string == '.') {
+ char fullpath[MAX_PATH + 1];
+ char *filepart = NULL;
+ DWORD chk;
+
+ chk = GetFullPathNameA(old_string, MAX_PATH + 1,
+ fullpath, &filepart);
+ if (chk != 0) {
+ StrAllocCopy(temp, wwwName(fullpath));
+ StrAllocCat(*AllocatedString, temp);
+ FREE(temp);
+ CTRACE((tfp, "Converted '%s' to '%s'\n",
+ old_string, *AllocatedString));
+ } else {
+ StrAllocCat(*AllocatedString, old_string);
+ }
+ }
+#else
+ if (strlen(old_string) == 1 && *old_string == '.') {
+ /*
+ * They want .
+ */
+ char curdir[LY_MAXPATH];
+
+ StrAllocCopy(temp, wwwName(Current_Dir(curdir)));
+ StrAllocCat(*AllocatedString, temp);
+ FREE(temp);
+ CTRACE((tfp, "Converted '%s' to '%s'\n",
+ old_string, *AllocatedString));
+ }
+#endif
+ else
+#endif /* USE_DOS_DRIVES */
+ if (LYIsTilde(*old_string)) {
+ char *his_home = NULL;
+
+ StrAllocCopy(his_home, old_string);
+ LYTildeExpand(&his_home, FALSE);
+ StrAllocCat(*AllocatedString, his_home);
+ FREE(his_home);
+
+ CTRACE((tfp, "Converted '%s' to '%s'\n",
+ old_string, *AllocatedString));
+ } else {
+ /*
+ * Create a full path to the current default directory.
+ */
+ char curdir[LY_MAXPATH];
+ char *temp2 = NULL;
+ BOOL is_local = FALSE;
+
+ Current_Dir(curdir);
+ /*
+ * Concatenate and simplify, trimming any residual relative
+ * elements. - FM
+ */
+#if defined (USE_DOS_DRIVES)
+ if (old_string[1] != ':' && old_string[1] != '|') {
+ StrAllocCopy(temp, wwwName(curdir));
+ LYAddHtmlSep(&temp);
+ LYStrNCpy(curdir, temp, (sizeof(curdir) - 1));
+ StrAllocCat(temp, old_string);
+ } else {
+ curdir[0] = '\0';
+ /* 1998/01/13 (Tue) 12:24:33 */
+ if (old_string[1] == '|')
+ old_string[1] = ':';
+ StrAllocCopy(temp, old_string);
+
+ if (strlen(temp) == 2 && LYIsDosDrive(temp))
+ LYAddPathSep(&temp);
+ }
+#else
+ StrAllocCopy(temp, curdir);
+ StrAllocCat(temp, "/");
+ StrAllocCat(temp, old_string);
+#endif /* USE_DOS_DRIVES */
+ LYTrimRelFromAbsPath(temp);
+ CTRACE((tfp, "Converted '%s' to '%s'\n", old_string, temp));
+ if ((stat(temp, &st) > -1) ||
+ LYCanReadFile(temp)) {
+ /*
+ * It is a subdirectory or file on the local system.
+ */
+#if defined (USE_DOS_DRIVES)
+ /* Don't want to see DOS local paths like c: escaped */
+ /* especially when we really have file://localhost/ */
+ /* at the beginning. To avoid any confusion we allow */
+ /* escaping the path if URL specials % or # present. */
+ if (StrChr(temp, '#') == NULL && StrChr(temp, '%') == NULL)
+ StrAllocCopy(cp, temp);
+ else
+ cp = HTEscape(temp, URL_PATH);
+#else
+ cp = HTEscape(temp, URL_PATH);
+#endif /* USE_DOS_DRIVES */
+ StrAllocCat(*AllocatedString, cp);
+ FREE(cp);
+ CTRACE((tfp, "Converted '%s' to '%s'\n",
+ old_string, *AllocatedString));
+ is_local = TRUE;
+ } else {
+ char *cp2 = NULL;
+
+ StrAllocCopy(temp2, curdir);
+ LYAddPathSep(&temp2);
+ StrAllocCopy(cp, old_string);
+ fragment = trimPoundSelector(cp);
+ HTUnEscape(cp); /* unescape given path without fragment */
+ StrAllocCat(temp2, cp); /* append to current dir */
+ StrAllocCopy(cp2, temp2); /* keep a copy in cp2 */
+ LYTrimRelFromAbsPath(temp2);
+#ifdef WIN_EX /* 1998/07/31 (Fri) 09:09:03 */
+ HTUnEscape(temp2); /* for LFN */
+#endif
+
+ if (strcmp(temp2, temp) != 0 &&
+ ((stat(temp2, &st) > -1) ||
+ LYCanReadFile(temp2))) {
+ /*
+ * It is a subdirectory or file on the local system with
+ * escaped characters and/or a fragment to be appended to
+ * the URL. - FM
+ */
+
+ FREE(temp);
+ if (strcmp(cp2, temp2) == 0) {
+ /*
+ * LYTrimRelFromAbsPath did nothing, use old_string as
+ * given. - kw
+ */
+ temp = HTEscape(curdir, URL_PATH);
+ LYAddHtmlSep(&temp);
+ StrAllocCat(temp, old_string);
+ } else {
+ temp = HTEscape(temp2, URL_PATH);
+ if (fragment != NULL) {
+ restorePoundSelector(fragment);
+ StrAllocCat(temp, fragment);
+ }
+ }
+ StrAllocCat(*AllocatedString, temp);
+ CTRACE((tfp, "Converted '%s' to '%s'\n",
+ old_string, *AllocatedString));
+ is_local = TRUE;
+
+ } else if (StrChr(curdir, '#') != NULL ||
+ StrChr(curdir, '%') != NULL) {
+ /*
+ * If PWD has some unusual characters, construct a filename
+ * in temp where those are escaped. This is mostly to
+ * prevent this function from returning with some weird URL
+ * if the LYExpandHostForURL tests further down fail. - kw
+ */
+ FREE(temp);
+ if (strcmp(cp2, temp2) == 0) {
+ /*
+ * LYTrimRelFromAbsPath did nothing, use old_string as
+ * given. - kw
+ */
+ temp = HTEscape(curdir, URL_PATH);
+ LYAddHtmlSep(&temp);
+ StrAllocCat(temp, old_string);
+ } else {
+ temp = HTEscape(temp2, URL_PATH);
+ if (fragment != NULL) {
+ restorePoundSelector(fragment);
+ StrAllocCat(temp, fragment);
+ }
+ }
+ }
+ FREE(cp);
+ FREE(cp2);
+ }
+ if (is_local == FALSE) {
+ /*
+ * It's not an accessible subdirectory or file on the local
+ * system, so assume it's a URL request and guess the scheme
+ * with "http://" as the default.
+ */
+ CTRACE((tfp, "Can't stat() or fopen() '%s'\n",
+ temp2 ? temp2 : temp));
+#ifdef WIN_EX /* 1998/01/13 (Tue) 09:07:37 */
+ {
+ const char *p, *q;
+ char buff[LY_MAXPATH + 128];
+
+ p = Home_Dir();
+ q = temp2 ? temp2 : temp;
+
+ if (strlen(q) == 3 && LYIsDosDrive(q)) {
+ sprintf(buff,
+ "'%s' not exist, Goto LynxHome '%s'.", q, p);
+ _statusline(buff);
+ LYSleepAlert();
+ FREE(temp);
+ StrAllocCat(*AllocatedString, p);
+ goto Retry;
+ }
+ }
+#endif
+ if (LYExpandHostForURL(&old_string,
+ URLDomainPrefixes,
+ URLDomainSuffixes)) {
+ LYAddSchemeForURL(&old_string);
+ StrAllocCopy(*AllocatedString, old_string);
+ } else if (fixit) {
+ StrAllocCopy(*AllocatedString, old_string);
+ } else {
+ /* Return file URL for the file that does not exist */
+ StrAllocCat(*AllocatedString, temp);
+ }
+#ifdef WIN_EX
+ Retry:
+#endif
+ CTRACE((tfp, "Trying: '%s'\n", *AllocatedString));
+ }
+ FREE(temp);
+ FREE(temp2);
+ }
+#endif /* VMS */
+ } else {
+ /*
+ * Path begins with a slash. Simplify and use it.
+ */
+ if (old_string[1] == '\0') {
+ /*
+ * Request for root. Respect it on Unix, but on VMS we treat that
+ * as a listing of the login directory. - FM
+ */
+#ifdef VMS
+ StrAllocCat(*AllocatedString, HTVMS_wwwName(Home_Dir()));
+#else
+ StrAllocCat(*AllocatedString, "/");
+ } else if ((stat(old_string, &st) > -1) ||
+ LYCanReadFile(old_string)) {
+ /*
+ * It is an absolute directory or file on the local system. - KW
+ */
+ StrAllocCopy(temp, old_string);
+ LYTrimRelFromAbsPath(temp);
+ CTRACE((tfp, "Converted '%s' to '%s'\n", old_string, temp));
+ cp = HTEscape(temp, URL_PATH);
+ StrAllocCat(*AllocatedString, cp);
+ FREE(cp);
+ FREE(temp);
+ CTRACE((tfp, "Converted '%s' to '%s'\n",
+ old_string, *AllocatedString));
+#endif /* VMS */
+ } else if (LYIsTilde(old_string[1])) {
+ /*
+ * Has a Home_Dir() reference. Handle it as if there weren't a
+ * lead slash. - FM
+ */
+ StrAllocCat(*AllocatedString, wwwName(Home_Dir()));
+ if ((cp = StrChr((old_string + 1), '/')) != NULL) {
+ /*
+ * Append rest of path, if present, skipping "user" if "~user"
+ * was entered, simplifying, and eliminating any residual
+ * relative elements. - FM
+ */
+ StrAllocCopy(temp, cp);
+ LYTrimRelFromAbsPath(temp);
+ StrAllocCat(*AllocatedString, temp);
+ FREE(temp);
+ }
+ } else {
+ /*
+ * Normal absolute path. Simplify, trim any residual relative
+ * elements, and append it. - FM
+ */
+ StrAllocCopy(temp, old_string);
+ LYTrimRelFromAbsPath(temp);
+ StrAllocCat(*AllocatedString, temp);
+ FREE(temp);
+ }
+ CTRACE((tfp, "Converted '%s' to '%s'\n",
+ old_string, *AllocatedString));
+ }
+ FREE(old_string);
+ /* Pause so we can read the messages before invoking curses */
+ CTRACE_SLEEP(AlertSecs);
+}
+
+#if defined(_WINDOWS) /* 1998/06/23 (Tue) 16:45:20 */
+
+int win32_check_interrupt(void)
+{
+ int c;
+
+ if (kbhit()) {
+ c = LYgetch();
+ /** Keyboard 'Z' or 'z', or Control-G or Control-C **/
+ if (LYCharIsINTERRUPT(c) || c == 0x1b) {
+ return TRUE;
+ }
+ }
+ return FALSE;
+}
+
+#if (!defined(__MINGW32__) && !defined(sleep)) || (defined(__MINGW32__) && !defined(HAVE_SLEEP))
+void sleep(unsigned sec)
+{
+ unsigned int i, j;
+
+ for (j = 0; j < sec; j++) {
+ for (i = 0; i < 10; i++) {
+ Sleep(100);
+ if (kbhit()) {
+ (void) LYgetch();
+ return;
+ }
+ }
+ }
+}
+#endif /* !__MINGW32__ */
+#endif /* _WINDOWS */
+/*
+ * This function expects an absolute Unix or VMS SHELL path spec as an
+ * allocated string, simplifies it, and trims out any residual relative
+ * elements. It also checks whether the path had a terminal slash, and if it
+ * didn't, makes sure that the simplified path doesn't either. If it's a
+ * directory, our convention is to exclude "Up to parent" links when a terminal
+ * slash is present. - FM
+ */
+void LYTrimRelFromAbsPath(char *path)
+{
+ char *cp;
+ int i;
+ BOOL TerminalSlash;
+
+ /*
+ * Make sure we have a pointer to an absolute path. - FM
+ */
+ if (path == NULL || !LYIsPathSep(*path))
+ return;
+
+ /*
+ * Check whether the path has a terminal slash. - FM
+ */
+ TerminalSlash = (BOOL) (LYIsPathSep(path[(strlen(path) - 1)]));
+
+ /*
+ * Simplify the path and then do any necessary trimming. - FM
+ */
+ HTSimplify(path, TRUE);
+ cp = path;
+ while (cp[1] == '.') {
+ if (cp[2] == '\0') {
+ /*
+ * Eliminate trailing dot. - FM
+ */
+ cp[1] = '\0';
+ } else if (LYIsPathSep(cp[2])) {
+ /*
+ * Skip over the "/." of a "/./". - FM
+ */
+ cp += 2;
+ } else if (cp[2] == '.' && cp[3] == '\0') {
+ /*
+ * Eliminate trailing dotdot. - FM
+ */
+ cp[1] = '\0';
+ } else if (cp[2] == '.' && cp[3] == '/') {
+ /*
+ * Skip over the "/.." of a "/../". - FM
+ */
+ cp += 3;
+ } else {
+ /*
+ * Done trimming. - FM
+ */
+ break;
+ }
+ }
+
+ /*
+ * Load any shifts into path, and eliminate any terminal slash created by
+ * HTSimplify() or our walk, but not present originally. - FM
+ */
+ if (cp > path) {
+ for (i = 0; cp[i] != '\0'; i++)
+ path[i] = cp[i];
+ path[i] = '\0';
+ }
+ if (TerminalSlash == FALSE) {
+ LYTrimPathSep(path);
+ }
+}
+
+/*
+ * Example Client-Side Include interface.
+ *
+ * This is called from SGML.c and simply returns markup for reporting the URL
+ * of the document being loaded if a comment begins with "<!--#lynxCSI". The
+ * markup will be included as if it were in the document. Move this function
+ * to a separate module for doing this kind of thing seriously, someday. - FM
+ */
+void LYDoCSI(char *url,
+ const char *comment,
+ char **csi)
+{
+ const char *cp = comment;
+
+ if (cp == NULL)
+ return;
+
+ if (StrNCmp(cp, "!--#", 4))
+ return;
+
+ cp += 4;
+ if (!strncasecomp(cp, "lynxCSI", 7)) {
+ StrAllocCat(*csi, "\n<p align=\"center\">URL: ");
+ StrAllocCat(*csi, url);
+ StrAllocCat(*csi, "</p>\n\n");
+ }
+
+ return;
+}
+
+#ifdef VMS
+/*
+ * Define_VMSLogical -- Fote Macrides 04-Apr-1995
+ * Define VMS logicals in the process table.
+ */
+void Define_VMSLogical(char *LogicalName,
+ char *LogicalValue)
+{
+ $DESCRIPTOR(lname, "");
+ $DESCRIPTOR(lvalue, "");
+ $DESCRIPTOR(ltable, "LNM$PROCESS");
+
+ if (isEmpty(LogicalName))
+ return;
+
+ lname.dsc$w_length = strlen(LogicalName);
+ lname.dsc$a_pointer = LogicalName;
+
+ if (isEmpty(LogicalValue)) {
+ lib$delete_logical(&lname, &ltable);
+ return;
+ }
+
+ lvalue.dsc$w_length = strlen(LogicalValue);
+ lvalue.dsc$a_pointer = LogicalValue;
+ lib$set_logical(&lname, &lvalue, &ltable, 0, 0);
+ return;
+}
+#endif /* VMS */
+
+#ifdef LY_FIND_LEAKS
+static void LYHomeDir_free(void)
+{
+ FREE(HomeDir);
+}
+#endif /* LY_FIND_LEAKS */
+
+char *Current_Dir(char *pathname)
+{
+ char *result;
+
+#ifdef HAVE_GETCWD
+ result = getcwd(pathname, (size_t) LY_MAXPATH);
+#else
+ result = getwd(pathname);
+#endif /* NO_GETCWD */
+ if (result == 0)
+ strcpy(pathname, ".");
+ return pathname;
+}
+
+/*
+ * Verify that the given path refers to an existing directory, returning the
+ * string if the directory exists. If not, return null.
+ */
+static char *CheckDir(char *path)
+{
+ struct stat stat_info;
+
+ if (!LYisAbsPath(path)
+ || (HTStat(path, &stat_info) < 0
+ || !S_ISDIR(stat_info.st_mode))) {
+ path = NULL;
+ }
+ CTRACE((tfp, "CheckDir(%s) %s\n", NonNull(path), path ? "OK" : "ERR"));
+ return path;
+}
+
+/*
+ * Lookup various possibilities for $HOME, and check that the directory exists.
+ */
+static char *HomeEnv(void)
+{
+ char *result = CheckDir(LYGetEnv("HOME"));
+
+#if defined (USE_DOS_DRIVES) && defined(_WIN32)
+ if (result == 0) {
+ char *head;
+ char *leaf;
+ static char *temp = NULL;
+
+ result = w32_get_shell_folder("Personal");
+ if (result == 0) {
+ /* Windows Vista/7 */
+ if ((head = LYGetEnv("USERPROFILE")) != 0) {
+ HTSprintf0(&temp, "%s%sDocuments", head, FILE_SEPARATOR);
+ result = CheckDir(temp);
+ if (result == 0) {
+ /* Windows 2000 */
+ HTSprintf0(&temp, "%s%sMy Documents", head, FILE_SEPARATOR);
+ result = CheckDir(temp);
+ }
+ }
+ }
+ /* NT4 */
+ if (result == 0) {
+ if ((head = LYGetEnv("HOMEDRIVE")) != 0) {
+ if ((leaf = LYGetEnv("HOMEPATH")) != 0) {
+ HTSprintf0(&temp, "%s%s%s", head, FILE_SEPARATOR, leaf);
+ result = CheckDir(temp);
+ }
+ }
+ }
+ /* General M$ */
+#ifdef USE_PROGRAM_DIR
+ if (result == 0)
+ result = CheckDir(program_dir);
+#endif
+ if (result == 0)
+ result = CheckDir(LYGetEnv("TEMP"));
+ if (result == 0)
+ result = CheckDir(LYGetEnv("TMP"));
+ if (result == 0) {
+ if ((head = LYGetEnv("SystemDrive")) != 0) {
+ HTSprintf0(&temp, "%s%s", head, FILE_SEPARATOR);
+ result = CheckDir(temp);
+ }
+ }
+ if (result == 0)
+ result = CheckDir("C:" FILE_SEPARATOR);
+ }
+#endif
+
+ return result;
+}
+
+const char *Home_Dir(void)
+{
+ static const char *homedir = NULL;
+ char *cp = NULL;
+
+ if (homedir == NULL) {
+ if ((cp = HomeEnv()) == NULL) {
+#ifdef VMS
+ if ((cp = LYGetEnv("SYS$LOGIN")) == NULL
+ && (cp = LYGetEnv("SYS$SCRATCH")) == NULL) {
+ cp = "sys$scratch:";
+ }
+ StrAllocCopy(HomeDir, cp);
+#else
+#ifdef UNIX
+#ifdef HAVE_UTMP
+ /*
+ * One could use getlogin() and getpwnam() here instead.
+ */
+ struct passwd *pw = getpwuid(geteuid());
+
+ if (pw && pw->pw_dir) {
+ StrAllocCopy(HomeDir, pw->pw_dir);
+ } else
+#endif
+ {
+ /*
+ * Use /tmp; it should be writable.
+ */
+ StrAllocCopy(HomeDir, "/tmp");
+ }
+#endif
+#endif /* VMS */
+ } else {
+ StrAllocCopy(HomeDir, cp);
+ }
+ homedir = (const char *) HomeDir;
+#ifdef LY_FIND_LEAKS
+ atexit(LYHomeDir_free);
+#endif
+ }
+ if (homedir == NULL) {
+ printf("%s\n", gettext("Cannot find HOME directory"));
+ exit_immediately(EXIT_FAILURE);
+ }
+ return homedir;
+}
+
+/*
+ * Return a pointer to the final leaf of the given pathname, If no pathname
+ * separators are found, returns the original pathname. The leaf may be
+ * empty.
+ */
+char *LYPathLeaf(char *pathname)
+{
+ char *leaf;
+
+#ifdef UNIX
+ if ((leaf = strrchr(pathname, '/')) != 0) {
+ leaf++;
+ }
+#else
+#ifdef VMS
+ if ((leaf = strrchr(pathname, ']')) == 0)
+ leaf = strrchr(pathname, ':');
+ if (leaf != 0)
+ leaf++;
+#else
+ int n;
+
+ for (leaf = 0, n = (int) strlen(pathname) - 1; n >= 0; n--) {
+ if (StrChr("\\/:", pathname[n]) != 0) {
+ leaf = pathname + n + 1;
+ break;
+ }
+ }
+#endif
+#endif
+ return (leaf != 0) ? leaf : pathname;
+}
+
+/*
+ * This function checks the acceptability of file paths that are intended to be
+ * off the home directory. The file path should be passed in fbuffer, together
+ * with the size of the buffer. The function simplifies the file path, and if
+ * it is acceptable, loads it into fbuffer and returns TRUE. Otherwise, it
+ * does not modify fbuffer and returns FALSE. If a subdirectory is present and
+ * the path does not begin with "./", that is prefixed to make the situation
+ * clear. - FM
+ */
+BOOLEAN LYPathOffHomeOK(char *fbuffer,
+ size_t fbuffer_size)
+{
+ char *file = NULL;
+ char *cp, *cp1;
+
+ /*
+ * Make sure we have an fbuffer and a string in it. - FM
+ */
+ if (fbuffer_size < 2 || isEmpty(fbuffer)) {
+ return (FALSE);
+ }
+ StrAllocCopy(file, fbuffer);
+ cp = file;
+
+ /*
+ * Check for an inappropriate reference to the home directory, and correct
+ * it if we can. - FM
+ */
+#ifdef VMS
+ if (!strncasecomp(cp, "sys$login", 9)) {
+ if (*(cp + 9) == '\0') {
+ /*
+ * Reject "sys$login". - FM
+ */
+ FREE(file);
+ return (FALSE);
+ }
+ if (*(cp + 9) == ':') {
+ cp += 10;
+ if (*cp == '\0') {
+ /*
+ * Reject "sys$login:". Otherwise, we have converted
+ * "sys$login:file" to "file", or have left a strange path for
+ * VMS as it was originally. - FM
+ */
+ FREE(file);
+ return (FALSE);
+ }
+ }
+ }
+#endif /* VMS */
+ if (LYIsTilde(cp[0])) {
+ if (LYIsPathSep(cp[1])) {
+ if (cp[2] != '\0') {
+ if (StrChr((cp + 2), '/') != NULL) {
+ /*
+ * Convert "~/subdir(s)/file" to "./subdir(s)/file". - FM
+ */
+ *cp = '.';
+ } else {
+ /*
+ * Convert "~/file" to "file". - FM
+ */
+ cp += 2;
+ }
+ } else {
+ /*
+ * Reject "~/". - FM
+ */
+ FREE(file);
+ return (FALSE);
+ }
+ } else if ((*(cp + 1) != '\0') &&
+ (cp1 = StrChr((cp + 1), '/')) != NULL) {
+ cp = (cp1 - 1);
+ if (*(cp + 2) != '\0') {
+ if (StrChr((cp + 2), '/') != NULL) {
+ /*
+ * Convert "~user/subdir(s)/file" to "./subdir(s)/file".
+ * If user is someone else, we covered a spoof. Otherwise,
+ * we simplified. - FM
+ */
+ *cp = '.';
+ } else {
+ /*
+ * Convert "~user/file" to "file". - FM
+ */
+ cp += 2;
+ }
+ } else {
+ /*
+ * Reject "~user/". - FM
+ */
+ FREE(file);
+ return (FALSE);
+ }
+ } else {
+ /*
+ * Reject "~user". - FM
+ */
+ FREE(file);
+ return (FALSE);
+ }
+ }
+#ifdef VMS
+ /*
+ * Check for VMS path specs, and reject if still present. - FM
+ */
+ if (StrChr(cp, ':') != NULL || StrChr(cp, ']') != NULL) {
+ FREE(file);
+ return (FALSE);
+ }
+#endif /* VMS */
+
+ /*
+ * Check for a URL or absolute path, and reject if present. - FM
+ */
+ if (is_url(cp) || LYIsPathSep(*cp)) {
+ FREE(file);
+ return (FALSE);
+ }
+
+ /*
+ * Simplify it. - FM
+ */
+ HTSimplify(cp, FALSE);
+
+ /*
+ * Check if it has a pointless "./". - FM
+ */
+ if (!StrNCmp(cp, "./", 2)) {
+ if (StrChr((cp + 2), '/') == NULL) {
+ cp += 2;
+ }
+ }
+
+ /*
+ * Check for spoofing. - FM
+ */
+ if (*cp == '\0'
+ || LYIsPathSep(*cp)
+ || LYIsPathSep(cp[(strlen(cp) - 1)])
+ || strstr(cp, "..") != NULL
+ || !strcmp(cp, ".")) {
+ FREE(file);
+ return (FALSE);
+ }
+
+ /*
+ * Load what we have at this point into fbuffer, trimming if too long, and
+ * claim it's OK. - FM
+ */
+ if (fbuffer_size > 3 && StrNCmp(cp, "./", 2) && StrChr(cp, '/')) {
+ /*
+ * We have a subdirectory and no lead "./", so prefix it to make the
+ * situation clear. - FM
+ */
+ strcpy(fbuffer, "./");
+ if (strlen(cp) > (fbuffer_size - 3))
+ cp[(fbuffer_size - 3)] = '\0';
+ strcat(fbuffer, cp);
+ } else {
+ if (strlen(cp) > (fbuffer_size - 1))
+ cp[(fbuffer_size - 1)] = '\0';
+ strcpy(fbuffer, cp);
+ }
+ FREE(file);
+ return (TRUE);
+}
+
+/*
+ * Search for a leading tilde, optionally embedded. If found, return a pointer
+ * to the tilde. If not found, return the original parameter.
+ */
+static char *FindLeadingTilde(char *pathname, int embedded)
+{
+ char *result = pathname;
+
+ if (pathname != NULL) {
+ if (embedded) {
+ while (pathname[0] != '\0') {
+ if (LYIsPathSep(pathname[0])) {
+ if (LYIsTilde(pathname[1])) {
+ ++pathname;
+ break;
+ }
+ }
+ ++pathname;
+ }
+ }
+ if (LYIsTilde(*pathname))
+ result = pathname;
+ }
+ return result;
+}
+
+/*
+ * Convert a non-absolute path to one which is off the home directory. Expand
+ * tildes as a side-effect. Return a pointer to the converted result.
+ */
+char *LYAbsOrHomePath(char **fname)
+{
+ if (*fname && !LYisAbsPath(*fname)) {
+ if (LYIsTilde((*fname)[0])) {
+ LYTildeExpand(fname, FALSE);
+ } else {
+ char temp[LY_MAXPATH];
+
+ LYAddPathToHome(temp, sizeof(temp), *fname);
+ StrAllocCopy(*fname, temp);
+ }
+ }
+ return *fname;
+}
+
+/*
+ * Expand a "leading" tilde into the user's home directory in WWW format. If
+ * "embedded" is true, allow that "leading" tilde to follow a path separator.
+ */
+char *LYTildeExpand(char **pathname,
+ int embedded)
+{
+ char *temp = FindLeadingTilde(*pathname, embedded);
+
+ if (LYIsTilde(temp[0])) {
+
+ CTRACE((tfp, "LYTildeExpand %s\n", *pathname));
+ if (LYIsPathSep(temp[1])) {
+ char *first = NULL;
+ char *second = NULL;
+
+ StrAllocCopy(first, *pathname);
+ first[temp - *pathname] = '\0';
+
+ StrAllocCopy(second, temp + 2);
+
+ StrAllocCopy(*pathname, first);
+ StrAllocCat(*pathname, wwwName(Home_Dir()));
+ LYAddPathSep(pathname);
+ StrAllocCat(*pathname, second);
+
+ FREE(first);
+ FREE(second);
+ } else if (temp[1] == '\0') {
+ StrAllocCopy(*pathname, wwwName(Home_Dir()));
+#ifndef NOUSERS
+ } else {
+ char *save;
+ char saved = '\0';
+ struct passwd *pw;
+
+ for (save = temp; *save != '\0'; ++save) {
+ if (LYIsPathSep(*save)) {
+ saved = *save;
+ *save = '\0';
+ break;
+ }
+ }
+ pw = getpwnam(temp + 1);
+ *save = saved;
+ if (pw != 0 && non_empty(pw->pw_dir)) {
+ temp = NULL;
+ StrAllocCopy(temp, save);
+ StrAllocCopy(*pathname, pw->pw_dir);
+ StrAllocCat(*pathname, temp);
+ FREE(temp);
+ }
+#endif
+ }
+ CTRACE((tfp, "expanded path %s\n", *pathname));
+ }
+ return *pathname;
+}
+
+/*
+ * This function appends fname to the home path and returns the full path and
+ * filename. The fname string can be just a filename (e.g.,
+ * "lynx_bookmarks.html"), or include a subdirectory off the home directory, in
+ * which case fname should begin with "./" (e.g., ./BM/lynx_bookmarks.html) Use
+ * LYPathOffHomeOK() to check and/or fix up fname before calling this function.
+ * On VMS, the resultant full path and filename are converted to VMS syntax. -
+ * FM
+ */
+void LYAddPathToHome(char *fbuffer,
+ size_t fbuffer_size,
+ const char *fname)
+{
+ char *home = NULL;
+ const char *file = fname;
+ int len;
+
+ /*
+ * Make sure we have a buffer. - FM
+ */
+ if (!fbuffer)
+ return;
+ if (fbuffer_size < 2) {
+ fbuffer[0] = '\0';
+ return;
+ }
+ fbuffer[(fbuffer_size - 1)] = '\0';
+
+ /*
+ * Make sure we have a file name. - FM
+ */
+ if (!file)
+ file = "";
+
+ /*
+ * Set up home string and length. - FM
+ */
+ StrAllocCopy(home, Home_Dir());
+
+#ifdef VMS
+#define NO_HOMEPATH "Error:"
+#else
+#define NO_HOMEPATH "/error"
+#endif /* VMS */
+ if (!non_empty(home))
+ /*
+ * Home_Dir() has a bug if this ever happens. - FM
+ */
+ StrAllocCopy(home, NO_HOMEPATH);
+
+ len = (int) fbuffer_size - ((int) strlen(home) + 1);
+ if (len <= 0) {
+ /*
+ * Buffer is smaller than or only big enough for the home path. Load
+ * what fits of the home path and return. This will fail, but we need
+ * something in the buffer. - FM
+ */
+ LYStrNCpy(fbuffer, home, (fbuffer_size - 1));
+ FREE(home);
+ return;
+ }
+#ifdef VMS
+ /*
+ * Check whether we have a subdirectory path or just a filename. - FM
+ */
+ if (!StrNCmp(file, "./", 2)) {
+ /*
+ * We have a subdirectory path. - FM
+ */
+ if (home[strlen(home) - 1] == ']') {
+ /*
+ * We got the home directory, so convert it to SHELL syntax and
+ * append subdirectory path, then convert that to VMS syntax. - FM
+ */
+ char *temp = NULL;
+
+ HTSprintf0(&temp, "%s%s", HTVMS_wwwName(home), (file + 1));
+ sprintf(fbuffer, "%.*s",
+ (fbuffer_size - 1), HTVMS_name("", temp));
+ FREE(temp);
+ } else {
+ /*
+ * This will fail, but we need something in the buffer. - FM
+ */
+ sprintf(fbuffer, "%s%.*s", home, len, file);
+ }
+ } else {
+ /*
+ * We have a file in the home directory. - FM
+ */
+ sprintf(fbuffer, "%s%.*s", home, len, file);
+ }
+#else
+ /*
+ * Check whether we have a subdirectory path or just a filename. - FM
+ */
+ sprintf(fbuffer, "%s/%.*s", home, len,
+ (StrNCmp(file, "./", 2) ? file : (file + 2)));
+#endif /* VMS */
+ FREE(home);
+}
+
+/*
+ * Given a filename, concatenate it to the save-space pathname, unless it is
+ * an absolute pathname. If there is no save-space defined, use the home
+ * directory. Return a new string with the result.
+ */
+char *LYAddPathToSave(char *fname)
+{
+ char *result = NULL;
+
+ if (LYisAbsPath(fname)) {
+ StrAllocCopy(result, fname);
+ } else {
+ if (non_empty(lynx_save_space)) {
+ StrAllocCopy(result, lynx_save_space);
+ } else {
+ char temp[LY_MAXPATH];
+
+ LYAddPathToHome(temp, sizeof(temp), fname);
+ StrAllocCopy(result, temp);
+ }
+ }
+ return result;
+}
+
+#if !defined(HAVE_PUTENV) && !defined(_WINDOWS)
+/*
+ * No putenv on the NeXT so we use this code instead!
+ */
+
+/* Copyright (C) 1991 Free Software Foundation, Inc.
+This file is part of the GNU C Library.
+
+The GNU C Library is free software; you can redistribute it and/or
+modify it under the terms of the GNU Library General Public License as
+published by the Free Software Foundation; either version 2 of the
+License, or (at your option) any later version.
+
+The GNU C Library is distributed in the hope that it will be useful,
+but WITHOUT ANY WARRANTY; without even the implied warranty of
+MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+Library General Public License for more details.
+
+You should have received a copy of the GNU Library General Public
+License along with the GNU C Library; see the file COPYING.LIB. If
+not, write to the Free Software Foundation, Inc., 675 Mass Ave,
+Cambridge, MA 02139, USA. */
+
+#if defined(STDC_HEADERS) || defined(USG)
+#include <string.h>
+#else /* Not (STDC_HEADERS or USG): */
+#include <strings.h>
+#endif /* STDC_HEADERS or USG */
+
+#ifndef NULL
+#define NULL 0
+#endif /* !NULL */
+
+extern char **environ;
+
+/*
+ * Put STRING, which is of the form "NAME=VALUE", in the environment.
+ */
+int putenv(const char *string)
+{
+ char *name_end = StrChr(string, '=');
+ register size_t size;
+ register char **ep;
+
+ if (name_end == NULL) {
+ /* Remove the variable from the environment. */
+ size = strlen(string);
+ for (ep = environ; *ep != NULL; ++ep)
+ if (!StrNCmp(*ep, string, size) && (*ep)[size] == '=') {
+ while (ep[1] != NULL) {
+ ep[0] = ep[1];
+ ++ep;
+ }
+ *ep = NULL;
+ return 0;
+ }
+ }
+
+ size = 0;
+ for (ep = environ; *ep != NULL; ++ep)
+ if (!StrNCmp(*ep, string, name_end - string) &&
+ (*ep)[name_end - string] == '=')
+ break;
+ else
+ ++size;
+
+ if (*ep == NULL) {
+ static char **last_environ = NULL;
+ char **new_environ = (char **) malloc((size + 2) * sizeof(char *));
+
+ if (new_environ == NULL)
+ return -1;
+ (void) memcpy((char *) new_environ, (char *) environ, size * sizeof(char *));
+
+ new_environ[size] = (char *) string;
+ new_environ[size + 1] = NULL;
+ if (last_environ != NULL)
+ FREE(last_environ);
+ last_environ = new_environ;
+ environ = new_environ;
+ } else
+ *ep = (char *) string;
+
+ return 0;
+}
+#endif /* !HAVE_PUTENV */
+
+#ifdef NEED_REMOVE
+int remove(char *name)
+{
+ return unlink(name);
+}
+#endif
+
+#if defined(MULTI_USER_UNIX)
+
+#if defined(HAVE_LSTAT) && defined(S_IFLNK)
+/*
+ * If IsOurFile() is checking a symbolic link, ensure that the target
+ * points to the user's file as well.
+ */
+static BOOL IsOurSymlink(const char *name)
+{
+ BOOL result = FALSE;
+ size_t size = LY_MAXPATH;
+ size_t used;
+ char *buffer = typeMallocn(char, (unsigned) size);
+ char *check;
+
+ if (buffer != 0) {
+ while ((used = (size_t) readlink(name, buffer, (size - 1))) == size - 1) {
+ check = typeRealloc(char, buffer, (unsigned) (size *= 2));
+
+ if (check == 0)
+ break;
+ buffer = check;
+ }
+ if (buffer != 0) {
+ if ((int) used > 0) {
+ buffer[used] = '\0';
+ } else {
+ FREE(buffer);
+ }
+ }
+ }
+ if (buffer != 0) {
+ if (!LYisAbsPath(buffer)) {
+ char *cutoff = LYLastPathSep(name);
+ char *clone = NULL;
+
+ if (cutoff != 0) {
+ HTSprintf0(&clone, "%.*s%s%s",
+ (int) (cutoff - name),
+ name, FILE_SEPARATOR, buffer);
+ FREE(buffer);
+ buffer = clone;
+ }
+ }
+ CTRACE2(TRACE_CFG, (tfp, "IsOurSymlink(%s -> %s)\n", name, buffer));
+ result = IsOurFile(buffer);
+ FREE(buffer);
+ }
+ return result;
+}
+#endif
+
+/*
+ * Verify if this is really a file which is not accessed by a symbolic link,
+ * except for the special case of its directory being pointed to by a link from
+ * a directory owned by root and not writable by other users.
+ */
+BOOL IsOurFile(const char *name)
+{
+ BOOL result = FALSE;
+ struct stat data;
+
+ if (!LYIsTilde(name[0])
+ && lstat(name, &data) == 0
+ && ((S_ISREG(data.st_mode)
+ && (data.st_mode & (S_IWOTH | S_IWGRP)) == 0
+ && data.st_uid == getuid())
+#if defined(HAVE_LSTAT) && defined(S_IFLNK)
+ || (S_ISLNK(data.st_mode) && IsOurSymlink(name))
+#endif
+ )) {
+ int linked = FALSE;
+
+ /*
+ * ( If this is not a single-user system, the other user is presumed by
+ * some people busy trying to use a symlink attack on our files ;-)
+ */
+#if defined(HAVE_LSTAT)
+ char *path = 0;
+ char *leaf;
+
+ StrAllocCopy(path, name);
+ do {
+ if ((leaf = LYPathLeaf(path)) != path)
+ *--leaf = '\0'; /* write a null on the '/' */
+ if (lstat(*path ? path : "/", &data) != 0) {
+ break;
+ }
+ /*
+ * If we find a symbolic link, it has to be in a directory that's
+ * protected. Otherwise someone could have switched it to point
+ * to one of the real user's files.
+ */
+ if (S_ISLNK(data.st_mode)) {
+ linked = TRUE; /* could be link-to-link; doesn't matter */
+ } else if (S_ISDIR(data.st_mode)) {
+ if (linked) {
+ linked = FALSE;
+ /*
+ * We assume that a properly-configured system has the
+ * unwritable directories owned by root. This is not
+ * necessarily so (bin, news, etc., may), but the only
+ * uid we can count on is 0. It would be nice to add a
+ * check for the gid also, but that wouldn't be
+ * portable.
+ *
+ * Likewise, the t-bit would be nice to rely upon. However
+ * it is marked as an extension in POSIX, rather than
+ * required.
+ */
+ if (data.st_uid != 0
+ || (data.st_mode & S_IWOTH) != 0) {
+ linked = TRUE; /* force an error-return */
+ break;
+ }
+ }
+ } else if (linked) {
+ break;
+ }
+ } while (leaf != path);
+ FREE(path);
+#endif
+ result = (BOOLEAN) !linked;
+ }
+ CTRACE2(TRACE_CFG, (tfp, "IsOurFile(%s) %d\n", name, result));
+ return result;
+}
+
+/*
+ * Open a file that we don't want other users to see.
+ */
+static FILE *OpenHiddenFile(const char *name, const char *mode)
+{
+ FILE *fp = 0;
+ struct stat data;
+ BOOLEAN binary = (BOOLEAN) (StrChr(mode, 'b') != 0);
+
+#if defined(O_CREAT) && defined(O_EXCL) /* we have fcntl.h or kindred? */
+ /*
+ * This is the preferred method for creating new files, since it ensures
+ * that no one has an existing file or link that they happen to own.
+ */
+ if (*mode == 'w') {
+ int fd = open(name, O_CREAT | O_EXCL | O_WRONLY, HIDE_CHMOD);
+
+ if (fd < 0
+ && errno == EEXIST
+ && IsOurFile(name)) {
+ if (remove(name) == 0) {
+ /* FIXME: there's a race at this point if directory is open */
+ fd = open(name, O_CREAT | O_EXCL | O_WRONLY, HIDE_CHMOD);
+ }
+ }
+ if (fd >= 0) {
+#if defined(O_BINARY) && defined(__CYGWIN__)
+ if (binary)
+ setmode(fd, O_BINARY);
+#endif
+ fp = fdopen(fd, mode);
+ }
+ } else
+#endif
+ if (*mode == 'a') {
+ if (IsOurFile(name)
+ && chmod(name, HIDE_CHMOD) == 0)
+ fp = fopen(name, mode);
+ else if (lstat(name, &data) != 0)
+ fp = OpenHiddenFile(name, binary ? BIN_W : TXT_W);
+ /*
+ * This is less stringent, but reasonably portable. For new files, the
+ * umask will suffice; however if the file already exists we'll change
+ * permissions first, before opening it. If the chmod fails because of
+ * some reason other than a non-existent file, there's no point in trying
+ * to open it.
+ *
+ * This won't work properly if the user is root, since the chmod succeeds.
+ */
+ } else if (*mode != 'a') {
+ mode_t save = umask(HIDE_UMASK);
+
+ if (chmod(name, HIDE_CHMOD) == 0 || errno == ENOENT)
+ fp = fopen(name, mode);
+ (void) umask(save);
+ }
+ return fp;
+}
+#else
+#define OpenHiddenFile(name, mode) fopen(name, mode)
+#endif /* MULTI_USER_UNIX */
+
+FILE *LYNewBinFile(const char *name)
+{
+#ifdef VMS
+ FILE *fp = fopen(name, BIN_W, "mbc=32");
+
+ (void) chmod(name, HIDE_CHMOD);
+#else
+ FILE *fp = OpenHiddenFile(name, BIN_W);
+#endif
+ return fp;
+}
+
+FILE *LYNewTxtFile(const char *name)
+{
+ FILE *fp;
+
+#ifdef VMS
+ fp = fopen(name, TXT_W, "shr=get");
+ (void) chmod(name, HIDE_CHMOD);
+#else
+ SetDefaultMode(O_TEXT);
+
+ fp = OpenHiddenFile(name, TXT_W);
+
+ SetDefaultMode(O_BINARY);
+#endif
+
+ return fp;
+}
+
+FILE *LYAppendToTxtFile(const char *name)
+{
+ FILE *fp;
+
+#ifdef VMS
+ fp = fopen(name, TXT_A, "shr=get");
+ (void) chmod(name, HIDE_CHMOD);
+#else
+ SetDefaultMode(O_TEXT);
+
+ fp = OpenHiddenFile(name, TXT_A);
+
+ SetDefaultMode(O_BINARY);
+#endif
+ return fp;
+}
+
+#if defined(MULTI_USER_UNIX)
+/*
+ * Restore normal permissions to a copy of a file that we have created with
+ * temp file restricted permissions. The normal umask should apply for user
+ * files. - kw
+ */
+void LYRelaxFilePermissions(const char *name)
+{
+ mode_t mode;
+ struct stat stat_buf;
+
+ if (stat(name, &stat_buf) == 0 &&
+ S_ISREG(stat_buf.st_mode) &&
+ (mode = (stat_buf.st_mode & 0777)) == HIDE_CHMOD) {
+ /*
+ * It looks plausible that this is a file we created with temp file
+ * paranoid permissions (and the umask wasn't even more restrictive
+ * when it was copied). - kw
+ */
+ mode_t save = umask(HIDE_UMASK);
+
+ mode = (mode_t) (((mode & 0700) | 0066) & ~save);
+ (void) umask(save);
+ (void) chmod(name, mode);
+ }
+}
+#endif
+
+/*
+ * Check if the given anchor has an associated file-cache.
+ */
+BOOLEAN LYCachedTemp(char *target,
+ char **cached)
+{
+ BOOLEAN result = FALSE;
+
+ if (*cached) {
+ LYStrNCpy(target, *cached, LY_MAXPATH);
+ FREE(*cached);
+ if (LYCanReadFile(target)) {
+ if (remove(target) != 0) {
+ CTRACE((tfp, "cannot remove %s\n", target));
+ }
+ }
+ result = TRUE;
+ }
+ return result;
+}
+
+#ifndef HAVE_MKDTEMP
+#define mkdtemp(path) ((mktemp(path) != 0) && (mkdir(path, 0700) == 0))
+#endif
+
+/*
+ * Open a temp-file, ensuring that it is unique, and not readable by other
+ * users.
+ *
+ * The mode can be one of: "w", "a", "wb".
+ */
+FILE *LYOpenTemp(char *result,
+ const char *suffix,
+ const char *mode)
+{
+ FILE *fp = 0;
+ BOOL txt = TRUE;
+ char wrt = 'r';
+ LY_TEMP *p;
+
+ CTRACE((tfp, "LYOpenTemp(,%s,%s)\n", suffix, mode));
+ if (result == 0)
+ return 0;
+
+ while (*mode != '\0') {
+ switch (*mode++) {
+ case 'w':
+ wrt = 'w';
+ break;
+ case 'a':
+ wrt = 'a';
+ break;
+ case 'b':
+ txt = FALSE;
+ break;
+ default:
+ CTRACE((tfp, "%s @%d: BUG\n", __FILE__, __LINE__));
+ return 0;
+ }
+ }
+
+ /*
+ * Verify if the given space looks secure enough. Otherwise, make a
+ * secure subdirectory of that.
+ */
+#if defined(MULTI_USER_UNIX) && (defined(HAVE_MKTEMP) || defined(HAVE_MKDTEMP))
+ if (lynx_temp_subspace == 0) {
+ BOOL make_it = FALSE;
+ struct stat sb;
+
+ if (lstat(lynx_temp_space, &sb) == 0
+ && S_ISDIR(sb.st_mode)) {
+ if (sb.st_uid != getuid()
+ || (sb.st_mode & (S_IWOTH | S_IWGRP)) != 0) {
+ make_it = TRUE;
+ CTRACE((tfp,
+ "lynx_temp_space is not our directory %s owner %d mode %03o\n",
+ lynx_temp_space,
+ (int) sb.st_uid,
+ (unsigned) (sb.st_mode & 0777)));
+ }
+ } else {
+ make_it = TRUE;
+ CTRACE((tfp, "lynx_temp_space is not a directory %s\n", lynx_temp_space));
+ }
+ if (make_it) {
+ mode_t old_mask = umask(HIDE_UMASK);
+
+ StrAllocCat(lynx_temp_space, "lynxXXXXXXXXXX");
+ if (mkdtemp(lynx_temp_space) == 0) {
+ printf("%s: %s\n", lynx_temp_space, LYStrerror(errno));
+ exit_immediately(EXIT_FAILURE);
+ }
+ (void) umask(old_mask);
+ lynx_temp_subspace = 1;
+ StrAllocCat(lynx_temp_space, "/");
+ CTRACE((tfp, "made subdirectory %s\n", lynx_temp_space));
+ } else {
+ lynx_temp_subspace = -1;
+ }
+ }
+#endif
+
+ do {
+ if (!fmt_tempname(result, lynx_temp_space, suffix))
+ return 0;
+ if (txt) {
+ switch (wrt) {
+ case 'w':
+ fp = LYNewTxtFile(result);
+ break;
+ case 'a':
+ fp = LYAppendToTxtFile(result);
+ break;
+ }
+ } else {
+ fp = LYNewBinFile(result);
+ }
+ /*
+ * If we get a failure to make a temporary file, don't bother to try a
+ * different name unless the failure was because the file already
+ * exists.
+ */
+#ifdef EEXIST /* FIXME (need a better test) in fcntl.h or unistd.h */
+ if ((fp == 0) && (errno != EEXIST)) {
+ CTRACE((tfp, "... LYOpenTemp(%s) failed: %s\n",
+ result, LYStrerror(errno)));
+ return 0;
+ }
+#endif
+ } while (fp == 0);
+
+ if ((p = typecalloc(LY_TEMP)) != 0) {
+ p->next = ly_temp;
+ StrAllocCopy((p->name), result);
+ p->file = fp;
+ p->outs = (BOOLEAN) (wrt != 'r');
+ ly_temp = p;
+ } else {
+ outofmem(__FILE__, "LYOpenTemp");
+ }
+
+ CTRACE((tfp, "... LYOpenTemp(%s)\n", result));
+ return fp;
+}
+
+/*
+ * Reopen a temporary file
+ */
+FILE *LYReopenTemp(char *name)
+{
+ LY_TEMP *p;
+ FILE *fp = 0;
+
+ LYCloseTemp(name);
+ if ((p = FindTempfileByName(name)) != 0) {
+ fp = p->file = LYAppendToTxtFile(name);
+ }
+ return fp;
+}
+
+/*
+ * Open a temp-file for writing, possibly re-using a previously used
+ * name and file.
+ * If a non-empty fname is given, it is reused if it indicates a file
+ * previously registered as a temp file and, in case the file still
+ * exists, if it looks like we can write to it safely. Otherwise a
+ * new temp file (with new name) will be generated and returned in fname.
+ *
+ * File permissions are set so that the file is not readable by unprivileged
+ * other users.
+ *
+ * Suffix is only used if fname is not being reused.
+ * The mode should be "w", others are possible (they may be passed on)
+ * but probably don't make sense. - kw
+ */
+FILE *LYOpenTempRewrite(char *fname,
+ const char *suffix,
+ const char *mode)
+{
+ FILE *fp = 0;
+ BOOL txt = TRUE;
+ char wrt = 'r';
+ BOOL registered = NO;
+ BOOL writable_exists = NO;
+ BOOL is_ours = NO;
+ BOOL still_open = NO;
+ LY_TEMP *p;
+ struct stat stat_buf;
+
+ CTRACE((tfp, "LYOpenTempRewrite(%s,%s,%s)\n", fname, suffix, mode));
+ if (*fname == '\0') /* first time, no filename yet */
+ return (LYOpenTemp(fname, suffix, mode));
+
+ if ((p = FindTempfileByName(fname)) != 0) {
+ registered = YES;
+ if (p->file != 0)
+ still_open = YES;
+ CTRACE((tfp, "...used before%s\n", still_open ? ", still open!" : "."));
+ }
+
+ if (registered) {
+#ifndef NO_GROUPS
+ writable_exists = HTEditable(fname); /* existing, can write */
+#define CTRACE_EXISTS "exists and is writable, "
+#else
+ writable_exists = (BOOL) (stat(fname, &stat_buf) == 0); /* existing, assume can write */
+#define CTRACE_EXISTS "exists, "
+#endif
+
+ if (writable_exists) {
+ is_ours = IsOurFile(fname);
+ }
+ CTRACE((tfp, "...%s%s\n",
+ writable_exists ? CTRACE_EXISTS : "",
+ is_ours ? "is our file." : "is NOT our file."));
+ }
+
+ /*
+ * Note that in cases where LYOpenTemp is called as fallback below, we
+ * don't call LYRemoveTemp first. That may be appropriate in some cases,
+ * but not trying to remove a weird existing file seems safer and could
+ * help diagnose an unusual situation. (They may be removed anyway later.)
+ */
+ if (still_open) {
+ /*
+ * This should probably not happen. Make a new one.
+ */
+ return (LYOpenTemp(fname, suffix, mode));
+ } else if (!registered) {
+ /*
+ * Not registered. It should have been registered at one point though,
+ * otherwise we wouldn't be called like this.
+ */
+ return (LYOpenTemp(fname, suffix, mode));
+ } else if (writable_exists && !is_ours) {
+ /*
+ * File exists, writable if we checked, but something is wrong with it.
+ */
+ return (LYOpenTemp(fname, suffix, mode));
+#ifndef NO_GROUPS
+ } else if (!is_ours && (lstat(fname, &stat_buf) == 0)) {
+ /*
+ * Exists but not writable, and something is wrong with it.
+ */
+ return (LYOpenTemp(fname, suffix, mode));
+#endif
+ }
+
+ while (*mode != '\0') {
+ switch (*mode++) {
+ case 'w':
+ wrt = 'w';
+ break;
+ case 'a':
+ wrt = 'a';
+ break;
+ case 'b':
+ txt = FALSE;
+ break;
+ default:
+ CTRACE((tfp, "%s @%d: BUG\n", __FILE__, __LINE__));
+ return fp;
+ }
+ }
+
+ if (is_ours) {
+ /*
+ * Yes, it exists, is writable if we checked, and everything looks ok
+ * so far. This should be the most regular case. - kw
+ */
+#ifdef HAVE_TRUNCATE
+ if (txt == TRUE) { /* limitation of LYReopenTemp. shrug */
+ /*
+ * We truncate and then append, this avoids having a small window
+ * in which the file doesn't exist. - kw
+ */
+ if (truncate(fname, (off_t) 0) != 0) {
+ CTRACE((tfp, "... truncate(%s,0) failed: %s\n",
+ fname, LYStrerror(errno)));
+ return (LYOpenTemp(fname, suffix, mode));
+ } else {
+ return (LYReopenTemp(fname));
+ }
+ }
+#endif
+ remove(fname);
+
+ }
+
+ /* We come here in two cases: either the file existed and was ours and we
+ * just got rid of it. Or the file did and does not exist, but is
+ * registered as a temp file. It must have been removed by some means
+ * other than LYRemoveTemp. In both cases, reuse the name! - kw
+ */
+
+ if (txt) {
+ switch (wrt) {
+ case 'w':
+ fp = LYNewTxtFile(fname);
+ break;
+ case 'a':
+ fp = LYAppendToTxtFile(fname);
+ break;
+ }
+ } else {
+ fp = LYNewBinFile(fname);
+ }
+ p->file = fp;
+
+ CTRACE((tfp, "... LYOpenTempRewrite(%s), %s\n", fname,
+ (fp) ? "ok" : "failed"));
+ /*
+ * We could fall back to trying LYOpenTemp() here in case of failure.
+ * After all the checks already done above a filure here should be pretty
+ * unusual though, so maybe it's better to let the user notice that
+ * something went wrong, and not try to fix it up. - kw
+ */
+ return fp;
+}
+
+/*
+ * Special case of LYOpenTemp, used for manipulating bookmark file, i.e., with
+ * renaming.
+ */
+FILE *LYOpenScratch(char *result,
+ const char *prefix)
+{
+ FILE *fp;
+ LY_TEMP *p;
+
+ if (!fmt_tempname(result, prefix, HTML_SUFFIX))
+ return 0;
+
+ if ((fp = LYNewTxtFile(result)) != 0) {
+ if ((p = typecalloc(LY_TEMP)) != 0) {
+ p->next = ly_temp;
+ StrAllocCopy((p->name), result);
+ p->file = fp;
+ ly_temp = p;
+ } else {
+ outofmem(__FILE__, "LYOpenScratch");
+ }
+ }
+ CTRACE((tfp, "LYOpenScratch(%s)\n", result));
+ return fp;
+}
+
+static void LY_close_temp(LY_TEMP * p)
+{
+ if (p->file != 0) {
+ if (p->outs) {
+ LYCloseOutput(p->file);
+ } else {
+ LYCloseInput(p->file);
+ }
+ p->file = 0;
+ }
+}
+
+/*
+ * Close a temp-file, given its name
+ */
+void LYCloseTemp(char *name)
+{
+ LY_TEMP *p;
+
+ CTRACE((tfp, "LYCloseTemp(%s)\n", name));
+ if ((p = FindTempfileByName(name)) != 0) {
+ CTRACE((tfp, "...LYCloseTemp(%s)%s\n", name,
+ (p->file != 0) ? ", closed" : ""));
+ LY_close_temp(p);
+ }
+}
+
+/*
+ * Close a temp-file, given its file-pointer
+ */
+void LYCloseTempFP(FILE *fp)
+{
+ LY_TEMP *p;
+
+ CTRACE((tfp, "LYCloseTempFP\n"));
+ if ((p = FindTempfileByFP(fp)) != 0) {
+ LY_close_temp(p);
+ CTRACE((tfp, "...LYCloseTempFP(%s)\n", p->name));
+ }
+}
+
+/*
+ * Close a temp-file, removing it.
+ */
+int LYRemoveTemp(char *name)
+{
+ LY_TEMP *p, *q;
+ int code = -1;
+
+ if (non_empty(name)) {
+ CTRACE((tfp, "LYRemoveTemp(%s)\n", name));
+ for (p = ly_temp, q = 0; p != 0; q = p, p = p->next) {
+ if (!strcmp(name, p->name)) {
+ if (q != 0) {
+ q->next = p->next;
+ } else {
+ ly_temp = p->next;
+ }
+ LY_close_temp(p);
+ code = HTSYS_remove(name);
+ CTRACE((tfp, "...LYRemoveTemp done(%d)%s\n", code,
+ (p->file != 0) ? ", closed" : ""));
+ CTRACE_FLUSH(tfp);
+ FREE(p->name);
+ FREE(p);
+ break;
+ }
+ }
+ }
+ return code;
+}
+
+/*
+ * Remove all of the temp-files. Note that this assumes that they are closed,
+ * since some systems will not allow us to remove a file which is open.
+ */
+void LYCleanupTemp(void)
+{
+ while (ly_temp != 0) {
+ (void) LYRemoveTemp(ly_temp->name);
+ }
+#if defined(MULTI_USER_UNIX)
+ if (lynx_temp_subspace > 0) {
+ char result[LY_MAXPATH];
+
+ LYStrNCpy(result, lynx_temp_space, sizeof(result) - 1);
+ LYTrimPathSep(result);
+ CTRACE((tfp, "LYCleanupTemp removing %s\n", result));
+ rmdir(result);
+ lynx_temp_subspace = -1;
+ }
+#endif
+}
+
+/*
+ * We renamed a temporary file. Keep track so we can remove it on exit.
+ */
+void LYRenamedTemp(char *oldname,
+ char *newname)
+{
+ LY_TEMP *p;
+
+ CTRACE((tfp, "LYRenamedTemp(old=%s, new=%s)\n", oldname, newname));
+ if ((p = FindTempfileByName(oldname)) != 0) {
+ StrAllocCopy((p->name), newname);
+ }
+}
+
+#ifndef DISABLE_BIBP
+/*
+ * Check that bibhost defines the BibP icon.
+ */
+void LYCheckBibHost(void)
+{
+ DocAddress bibhostIcon;
+ BOOLEAN saveFlag;
+
+ bibhostIcon.address = NULL;
+ StrAllocCopy(bibhostIcon.address, BibP_bibhost);
+ StrAllocCat(bibhostIcon.address, "bibp1.0/bibpicon.jpg");
+ bibhostIcon.post_data = NULL;
+ bibhostIcon.post_content_type = NULL;
+ bibhostIcon.bookmark = FALSE;
+ bibhostIcon.isHEAD = FALSE;
+ bibhostIcon.safe = FALSE;
+ saveFlag = traversal;
+ traversal = TRUE; /* Hack to force error response. */
+ BibP_bibhost_available = (BOOLEAN) (HTLoadAbsolute(&bibhostIcon) == YES);
+ traversal = saveFlag;
+ BibP_bibhost_checked = TRUE;
+}
+#endif /* !DISABLE_BIBP */
+
+/*
+ * Management of User Interface Pages. - kw
+ *
+ * These are mostly temp files. Pages which can be recognized by their special
+ * URL (after having been loaded) need not be tracked here.
+ *
+ * First some private stuff:
+ */
+typedef struct uipage_entry {
+ UIP_t type;
+ unsigned flags;
+ char *url;
+ HTList *alturls;
+ char *file;
+} uip_entry;
+
+#define UIP_F_MULTI 0x0001 /* flag: track multiple instances */
+#define UIP_F_LIMIT 0x0002 /* flag: limit size of alturls list */
+#define UIP_F_LMULTI (UIP_F_MULTI | UIP_F_LIMIT)
+/* *INDENT-OFF* */
+static uip_entry ly_uip[] =
+{
+ { UIP_HISTORY , UIP_F_LMULTI, NULL, NULL, NULL }
+ , { UIP_DOWNLOAD_OPTIONS , 0 , NULL, NULL, NULL }
+ , { UIP_PRINT_OPTIONS , 0 , NULL, NULL, NULL }
+ , { UIP_SHOWINFO , UIP_F_LMULTI, NULL, NULL, NULL }
+ , { UIP_LIST_PAGE , UIP_F_LMULTI, NULL, NULL, NULL }
+ , { UIP_VLINKS , UIP_F_LMULTI, NULL, NULL, NULL }
+#if !defined(NO_OPTION_FORMS)
+ , { UIP_OPTIONS_MENU , UIP_F_LMULTI, NULL, NULL, NULL }
+#endif
+#ifdef DIRED_SUPPORT
+ , { UIP_DIRED_MENU , 0 , NULL, NULL, NULL }
+ , { UIP_PERMIT_OPTIONS , 0 , NULL, NULL, NULL }
+ , { UIP_UPLOAD_OPTIONS , UIP_F_LMULTI, NULL, NULL, NULL }
+#endif
+#ifdef USE_ADDRLIST_PAGE
+ , { UIP_ADDRLIST_PAGE , UIP_F_LMULTI, NULL, NULL, NULL }
+#endif
+ , { UIP_LYNXCFG , UIP_F_LMULTI, NULL, NULL, NULL }
+#if !defined(NO_CONFIG_INFO)
+ , { UIP_CONFIG_DEF , UIP_F_LMULTI, NULL, NULL, NULL }
+#endif
+/* The following are not generated tempfiles: */
+ , { UIP_TRACELOG , 0 , NULL, NULL, NULL }
+#if defined(DIRED_SUPPORT) && defined(OK_INSTALL)
+ , { UIP_INSTALL , 0 , NULL, NULL, NULL }
+#endif
+
+};
+/* *INDENT-ON* */
+
+/* Public entry points for User Interface Page management: */
+
+BOOL LYIsUIPage3(const char *url,
+ UIP_t type,
+ int flagparam)
+{
+ unsigned int i;
+ size_t l;
+ BOOL result = NO;
+
+ if (url) {
+ for (i = 0; i < TABLESIZE(ly_uip); i++) {
+ if (ly_uip[i].type == type) {
+ if (!ly_uip[i].url) {
+ break;
+ } else if ((flagparam & UIP_P_FRAG) ?
+ (!StrNCmp(ly_uip[i].url, url, (l = strlen(ly_uip[i].url)))
+ && (url[l] == '\0' || url[l] == '#')) :
+ !strcmp(ly_uip[i].url, url)) {
+ result = YES;
+ } else if (ly_uip[i].flags & UIP_F_MULTI) {
+ char *p;
+ HTList *l0 = ly_uip[i].alturls;
+
+ while ((p = (char *) HTList_nextObject(l0)) != NULL) {
+ if ((flagparam & UIP_P_FRAG)
+ ? (!StrNCmp(p, url, (l = strlen(p))) &&
+ (url[l] == '\0' || url[l] == '#'))
+ : !strcmp(p, url)) {
+ result = YES;
+ break;
+ }
+ }
+ }
+ break;
+ }
+ }
+ }
+ return result;
+}
+
+void LYRegisterUIPage(const char *url,
+ UIP_t type)
+{
+ unsigned int i;
+
+ for (i = 0; i < TABLESIZE(ly_uip); i++) {
+ if (ly_uip[i].type == type) {
+ if (ly_uip[i].url && url &&
+ !strcmp(ly_uip[i].url, url)) {
+
+ } else if (!ly_uip[i].url || !url ||
+ !(ly_uip[i].flags & UIP_F_MULTI)) {
+ StrAllocCopy(ly_uip[i].url, url);
+
+ } else {
+ char *p;
+ int n = 0;
+ HTList *l0 = ly_uip[i].alturls;
+
+ while ((p = (char *) HTList_nextObject(l0)) != NULL) {
+ if (!strcmp(p, url))
+ return;
+ if (!strcmp(p, ly_uip[i].url)) {
+ StrAllocCopy(ly_uip[i].url, url);
+ return;
+ }
+ n++;
+ }
+ if (!ly_uip[i].alturls)
+ ly_uip[i].alturls = HTList_new();
+
+ if (n >= HTCacheSize && (ly_uip[i].flags & UIP_F_LIMIT))
+ HTList_removeFirstObject(ly_uip[i].alturls);
+ HTList_addObject(ly_uip[i].alturls, ly_uip[i].url);
+ ly_uip[i].url = NULL;
+ StrAllocCopy(ly_uip[i].url, url);
+ }
+
+ return;
+ }
+ }
+}
+
+void LYUIPages_free(void)
+{
+ unsigned int i;
+
+ for (i = 0; i < TABLESIZE(ly_uip); i++) {
+ FREE(ly_uip[i].url);
+ FREE(ly_uip[i].file);
+ LYFreeStringList(ly_uip[i].alturls);
+ ly_uip[i].alturls = NULL;
+ }
+}
+
+/*
+ * Convert local pathname to www name
+ * (do not bother about file://localhost prefix at this point).
+ */
+const char *wwwName(const char *pathname)
+{
+ const char *cp = NULL;
+
+#if defined(USE_DOS_DRIVES)
+ cp = HTDOS_wwwName(pathname);
+#else
+#ifdef VMS
+ cp = HTVMS_wwwName(pathname);
+#else
+ cp = pathname;
+#endif /* VMS */
+#endif
+
+ return cp;
+}
+
+/*
+ * Given a user-specified filename, e.g., for download or print, validate and
+ * expand it. Expand home-directory expressions in the given string. Only
+ * allow pipes if the user can spawn shell commands.
+ */
+BOOLEAN LYValidateFilename(bstring **result,
+ bstring **given)
+{
+ BOOLEAN code = TRUE;
+ char *cp = NULL;
+
+ /*
+ * Cancel if the user entered "/dev/null" on Unix, or an "nl:" path on VMS.
+ * - FM
+ */
+ if (LYIsNullDevice((*given)->str)) {
+ /* just ignore it */
+ code = FALSE;
+#ifdef HAVE_POPEN
+ } else if (LYIsPipeCommand((*given)->str)) {
+ if (no_shell) {
+ HTUserMsg(SPAWNING_DISABLED);
+ code = FALSE;
+ } else {
+ BStrCopy(*result, (*given));
+ }
+#endif
+ } else {
+ if (FindLeadingTilde((*given)->str, TRUE) != 0) {
+ char *cp1 = NULL;
+
+ StrAllocCopy(cp1, (*given)->str);
+ LYTildeExpand(&cp1, TRUE);
+ BStrCopy0(*result, cp1);
+ BStrCopy0(*given, cp1);
+ FREE(cp1);
+ }
+#ifdef VMS
+ if (StrChr((*given)->str, '/') != NULL) {
+ BStrCopy0(*result, HTVMS_name("", (*given)->str));
+ BStrCopy(*given, *result);
+ }
+ if ((*given)->str[0] != '/'
+ && StrChr((*given)->str, ':') == NULL) {
+ BStrCopy0(*result, "sys$disk:");
+ if (StrChr((*given)->str, ']') == NULL)
+ BStrCat0(*result, "[]");
+ BStrCat(*result, (*given));
+ } else {
+ BStrCopy(*result, (*given));
+ }
+#else
+
+#ifndef __EMX__
+ if (!LYisAbsPath((*given)->str)) {
+#if defined(__DJGPP__) || defined(_WINDOWS)
+ if (StrChr((*result)->str, ':') != NULL)
+ cp = NULL;
+ else
+#endif /* __DJGPP__ || _WINDOWS */
+ {
+#ifdef SUPPORT_CHDIR
+ static char buf[LY_MAXPATH];
+
+ cp = Current_Dir(buf);
+#else
+ cp = original_dir;
+#endif
+ }
+ } else
+#endif /* __EMX__ */
+ cp = NULL;
+
+ if (cp) {
+ LYTrimPathSep(cp);
+ BStrCopy0(*result, cp);
+ BStrCat0(*result, "/");
+ } else {
+ BStrCopy0(*result, "");
+ }
+ if (code) {
+ cp = HTSYS_name((*given)->str);
+ BStrCat0(*result, cp);
+ }
+#endif /* VMS */
+ }
+ return code;
+}
+
+/*
+ * Given a valid filename, check if it exists. If so, we'll have to worry
+ * about overwriting it.
+ *
+ * Returns:
+ * 'Y' (yes/success)
+ * 'N' (no/retry)
+ * 3 (cancel)
+ */
+int LYValidateOutput(char *filename)
+{
+ int c;
+
+ CTRACE((tfp, "LYValidateOutput '%s'\n", filename));
+
+ /*
+ * Assume we can write to a pipe
+ */
+#ifdef HAVE_POPEN
+ if (LYIsPipeCommand(filename))
+ return 'Y';
+#endif
+
+ if (no_dotfiles || !show_dotfiles) {
+ if (*LYPathLeaf(filename) == '.') {
+ HTAlert(FILENAME_CANNOT_BE_DOT);
+ return 'N';
+ }
+ }
+
+ /*
+ * See if it already exists.
+ */
+ if (LYCanReadFile(filename)) {
+#ifdef VMS
+ c = HTConfirm(FILE_EXISTS_HPROMPT);
+#else
+ c = HTConfirm(FILE_EXISTS_OPROMPT);
+#endif /* VMS */
+ if (HTLastConfirmCancelled()) {
+ HTInfoMsg(SAVE_REQUEST_CANCELLED);
+ return 3;
+ } else if (c == NO) {
+ return 'N';
+ }
+ } else if (!LYCanWriteFile(filename)) {
+ return 'N';
+ }
+ return 'Y';
+}
+
+/*
+ * Convert a local filename to a URL
+ */
+void LYLocalFileToURL(char **target,
+ const char *source)
+{
+ const char *leaf;
+
+ StrAllocCopy(*target, "file://localhost");
+
+ leaf = wwwName(source);
+
+ if (!LYisAbsPath(source)) {
+ char temp[LY_MAXPATH];
+
+ Current_Dir(temp);
+ if (!LYIsHtmlSep(*temp))
+ LYAddHtmlSep(target);
+ StrAllocCat(*target, temp);
+ }
+ if (leaf && !LYIsHtmlSep(*leaf))
+ LYAddHtmlSep(target);
+ StrAllocCat(*target, leaf);
+}
+
+#define PUT_STRING(buf) (*(target)->isa->put_string)(target, buf)
+
+/*
+ * Like WriteInternalTitle, used for writing title on pages constructed via
+ * streams.
+ */
+void WriteStreamTitle(HTStream *target, const char *Title)
+{
+ char *buf = 0;
+
+ PUT_STRING(LYNX_DOCTYPE);
+ PUT_STRING("<html>\n<head>\n");
+ LYAddMETAcharsetToStream(target, -1);
+ HTSprintf0(&buf, "<title>%s</title>\n</head>\n<body>\n", Title);
+ PUT_STRING(buf);
+ FREE(buf);
+}
+
+/*
+ * Open a temporary file for internal-pages, optionally reusing an existing
+ * filename.
+ */
+FILE *InternalPageFP(char *filename,
+ int reuse_flag)
+{
+ FILE *fp;
+
+ if (LYReuseTempfiles && reuse_flag) {
+ fp = LYOpenTempRewrite(filename, HTML_SUFFIX, BIN_W);
+ } else {
+ (void) LYRemoveTemp(filename);
+ fp = LYOpenTemp(filename, HTML_SUFFIX, BIN_W);
+ }
+ if (fp == NULL) {
+ HTAlert(CANNOT_OPEN_TEMP);
+ }
+ return fp;
+}
+
+/*
+ * This part is shared by all internal pages.
+ */
+void WriteInternalTitle(FILE *fp0, const char *Title)
+{
+ fprintf(fp0, LYNX_DOCTYPE);
+
+ fprintf(fp0, "<html>\n<head>\n");
+ LYAddMETAcharsetToFD(fp0, -1);
+ if (LYIsListpageTitle(Title)) {
+ if (StrChr(HTLoadedDocumentURL(), '"') == NULL) {
+ char *Address = NULL;
+
+ /*
+ * Insert a BASE tag so there is some way to relate the List Page
+ * file to its underlying document after we are done. It won't be
+ * actually used for resolving relative URLs. - kw
+ */
+ StrAllocCopy(Address, HTLoadedDocumentURL());
+ LYEntify(&Address, FALSE);
+ fprintf(fp0, "<base href=\"%s\">\n", Address);
+ FREE(Address);
+ }
+ }
+ fprintf(fp0, "<title>%s</title>\n</head>\n<body>\n", Title);
+}
+
+/*
+ * This is used to start most internal pages, except for special cases where
+ * the embedded HREF's in the title differ.
+ */
+void BeginInternalPage(FILE *fp0, const char *Title,
+ const char *HelpURL)
+{
+ WriteInternalTitle(fp0, Title);
+
+ if ((user_mode == NOVICE_MODE)
+ && LYwouldPush(Title, NULL)
+ && (HelpURL != 0)) {
+ fprintf(fp0, "<h1>%s (%s%s%s), <a href=\"%s%s\">help</a></h1>\n",
+ Title, LYNX_NAME, VERSION_SEGMENT, LYNX_VERSION,
+ helpfilepath, HelpURL);
+ } else {
+ fprintf(fp0, "<h1>%s (%s%s%s)</h1>\n",
+ Title, LYNX_NAME, VERSION_SEGMENT, LYNX_VERSION);
+ }
+}
+
+void EndInternalPage(FILE *fp0)
+{
+ fprintf(fp0, "</body>\n</html>");
+}
+
+char *trimPoundSelector(char *address)
+{
+ char *pound = findPoundSelector(address);
+
+ if (pound != 0)
+ *pound = '\0';
+ return pound;
+}
+
+/*
+ * Trim a trailing path-separator to avoid confusing other programs when we concatenate
+ * to it. This only applies to local filesystems.
+ */
+void LYTrimPathSep(char *path)
+{
+ size_t len;
+
+ if (path != 0
+ && (len = strlen(path)) != 0
+ && LYIsPathSep(path[len - 1]))
+ path[len - 1] = 0;
+}
+
+/*
+ * Add a trailing path-separator to avoid confusing other programs when we concatenate
+ * to it. This only applies to local filesystems.
+ */
+void LYAddPathSep(char **path)
+{
+ size_t len;
+ char *temp;
+
+ if ((path != 0)
+ && ((temp = *path) != 0)
+ && (len = strlen(temp)) != 0
+ && !LYIsPathSep(temp[len - 1])) {
+ StrAllocCat(*path, FILE_SEPARATOR);
+ }
+}
+
+/*
+ * Add a trailing path-separator to avoid confusing other programs when we concatenate
+ * to it. This only applies to local filesystems.
+ */
+void LYAddPathSep0(char *path)
+{
+ size_t len;
+
+ if ((path != 0)
+ && (len = strlen(path)) != 0
+ && (len < LY_MAXPATH - 2)
+ && !LYIsPathSep(path[len - 1])) {
+ strcat(path, FILE_SEPARATOR);
+ }
+}
+
+/*
+ * Check if a given string contains a path separator
+ */
+char *LYLastPathSep(const char *path)
+{
+ char *result;
+
+#if defined(USE_DOS_DRIVES)
+ if ((result = strrchr(path, '\\')) == 0)
+ result = strrchr(path, '/');
+#else
+ result = strrchr(path, '/');
+#endif
+ return result;
+}
+
+/*
+ * Trim a trailing path-separator to avoid confusing other programs when we concatenate
+ * to it. This only applies to HTML paths.
+ */
+void LYTrimHtmlSep(char *path)
+{
+ size_t len;
+
+ if (path != 0
+ && (len = strlen(path)) != 0
+ && LYIsHtmlSep(path[len - 1]))
+ path[len - 1] = 0;
+}
+
+/*
+ * Add a trailing path-separator to avoid confusing other programs when we concatenate
+ * to it. This only applies to HTML paths.
+ */
+void LYAddHtmlSep(char **path)
+{
+ size_t len;
+ char *temp;
+
+ if ((path != 0)
+ && ((temp = *path) != 0)
+ && (len = strlen(temp)) != 0
+ && !LYIsHtmlSep(temp[len - 1])) {
+ StrAllocCat(*path, "/");
+ }
+}
+
+/*
+ * Add a trailing path-separator to avoid confusing other programs when we concatenate
+ * to it. This only applies to HTML paths.
+ */
+void LYAddHtmlSep0(char *path)
+{
+ size_t len;
+
+ if ((path != 0)
+ && (len = strlen(path)) != 0
+ && (len < LY_MAXPATH - 2)
+ && !LYIsHtmlSep(path[len - 1])) {
+ strcat(path, "/");
+ }
+}
+
+/*
+ * Rename a file
+ */
+int LYRenameFile(char *src,
+ char *dst)
+{
+#ifdef _WINDOWS
+ /*
+ * If dest_file exists prior to calling rename(), rename() will fail on Windows platforms.
+ * https://www.securecoding.cert.org/confluence/display/c/FIO10-C.+Take+care+when+using+the+rename%28%29+function
+ */
+ struct stat st;
+
+ if (stat(dst, &st) == 0) {
+ unlink(dst);
+ }
+#endif
+ return rename(src, dst);
+}
+
+/*
+ * Copy a file
+ */
+int LYCopyFile(char *src,
+ char *dst)
+{
+ int code;
+ const char *program;
+
+ if ((program = HTGetProgramPath(ppCOPY)) != NULL) {
+ char *the_command = 0;
+
+ HTAddParam(&the_command, COPY_COMMAND, 1, program);
+ HTAddParam(&the_command, COPY_COMMAND, 2, src);
+ HTAddParam(&the_command, COPY_COMMAND, 3, dst);
+ HTEndParam(&the_command, COPY_COMMAND, 3);
+
+ CTRACE((tfp, "command: %s\n", the_command));
+ stop_curses();
+ code = LYSystem(the_command);
+ start_curses();
+
+ FREE(the_command);
+ } else {
+ FILE *fin, *fout;
+ unsigned char buff[BUFSIZ];
+ size_t len;
+
+ code = EOF;
+ if ((fin = fopen(src, BIN_R)) != 0) {
+ if ((fout = fopen(dst, BIN_W)) != 0) {
+ code = 0;
+ while ((len = fread(buff, (size_t) 1, sizeof(buff), fin)) != 0) {
+ if (fwrite(buff, (size_t) 1, len, fout) < len
+ || ferror(fout)) {
+ code = EOF;
+ break;
+ }
+ }
+ LYCloseOutput(fout);
+ }
+ LYCloseInput(fin);
+ }
+ CTRACE((tfp, "builtin copy ->%d\n\tsource=%s\n\ttarget=%s\n",
+ code, src, dst));
+ }
+
+ if (code) {
+ HTAlert(CANNOT_WRITE_TO_FILE);
+ }
+ return code;
+}
+
+#ifdef __DJGPP__
+static char *escape_backslashes(char *source)
+{
+ char *result = 0;
+ int count = 0;
+ int n;
+
+ for (n = 0; source[n] != '\0'; ++n) {
+ if (source[n] == '\\')
+ ++count;
+ }
+ if (count != 0) {
+ result = malloc(count + n + 1);
+ if (result != 0) {
+ int ch;
+ char *target = result;
+
+ while ((ch = *source++) != '\0') {
+ if (ch == '\\')
+ *target++ = ch;
+ *target++ = ch;
+ }
+ *target = '\0';
+ }
+ }
+ return result;
+}
+#endif /* __DJGPP__ */
+/*
+ * Invoke a shell command, return nonzero on error.
+ */
+int LYSystem(char *command)
+{
+ int code;
+ int do_free = 0;
+
+#if defined(HAVE_SIGACTION) && defined(SIGTSTP) && !defined(USE_SLANG)
+ struct sigaction saved_sigtstp_act;
+ BOOLEAN sigtstp_saved = FALSE;
+#endif
+ int saved_errno = 0;
+
+#ifdef __EMX__
+ int scrsize[4];
+#endif
+
+ fflush(stdout);
+ fflush(stderr);
+ CTRACE((tfp, "LYSystem(%s)\n", command));
+ CTRACE_FLUSH(tfp);
+
+#ifdef __DJGPP__
+ __djgpp_set_ctrl_c(0);
+ _go32_want_ctrl_break(1);
+#endif /* __DJGPP__ */
+
+#ifdef VMS
+ code = DCLsystem(command);
+#else
+# ifdef __EMX__ /* FIXME: Should be LY_CONVERT_SLASH? */
+ /* Configure writes commands which contain direct slashes.
+ Native command-(non)-shell will not tolerate this. */
+ {
+ char *space = command, *slash = command;
+
+ _scrsize(scrsize);
+ while (*space && *space != ' ' && *space != '\t')
+ space++;
+ while (slash < space && *slash != '/')
+ slash++;
+ if (slash != space) {
+ char *old = command;
+
+ command = NULL;
+ StrAllocCopy(command, old);
+ do_free = 1;
+ slash = (slash - old) + command - 1;
+ space = (space - old) + command;
+ while (++slash < space)
+ if (*slash == '/')
+ *slash = '\\';
+ }
+ }
+# endif
+
+ /*
+ * This chunk of code does not work, for two reasons:
+ * a) the Cygwin system() function edits out the backslashes
+ * b) it does not account for more than one parameter, e.g., +number
+ */
+#if defined(__CYGWIN__) && defined(DOSPATH) /* 1999/02/26 (Fri) */
+ {
+ char cmd[LY_MAXPATH];
+ char win32_name[LY_MAXPATH];
+ char new_cmd[LY_MAXPATH];
+ char new_command[LY_MAXPATH * 2 + 10];
+ char *p, *q;
+
+ p = command;
+ q = cmd;
+ while (*p) {
+ if (*p == ' ')
+ break;
+ else
+ *q = *p;
+ p++;
+ q++;
+ }
+ *q = '\0';
+
+ if (cmd[0] == '/')
+ cygwin_conv_to_full_posix_path(cmd, new_cmd);
+ else
+ strcpy(new_cmd, cmd);
+
+ while (*p == ' ')
+ p++;
+
+ if (StrChr(p, '\\') == NULL) {
+ /* for Windows Application */
+ cygwin_conv_to_full_win32_path(p, win32_name);
+ sprintf(new_command, "%.*s \"%.*s\"",
+ LY_MAXPATH, new_cmd, LY_MAXPATH, win32_name);
+ } else {
+ /* for DOS like editor */
+ q = win32_name;
+ while (*p) {
+ if (*p == '\\') {
+ if (*(p + 1) == '\\')
+ p++;
+ }
+ *q = *p;
+ q++, p++;
+ }
+ *q = '\0';
+ sprintf(new_command, "%.*s %.*s", LY_MAXPATH, new_cmd, LY_MAXPATH, win32_name);
+ }
+ command = new_command;
+ }
+#endif
+
+#ifdef __DJGPP__
+ if (dj_is_bash) {
+ char *new_command = escape_backslashes(command);
+
+ if (new_command != 0) {
+ if (do_free)
+ free(command);
+ command = new_command;
+ }
+ }
+#endif /* __DJGPP__ */
+
+#ifdef _WIN_CC
+ code = exec_command(command, TRUE); /* Wait exec */
+#else /* !_WIN_CC */
+#ifdef SIGPIPE
+ if (restore_sigpipe_for_children)
+ signal(SIGPIPE, SIG_DFL); /* Some commands expect the default */
+#endif
+#if defined(HAVE_SIGACTION) && defined(SIGTSTP) && !defined(USE_SLANG)
+ if (!dump_output_immediately && !LYCursesON && !no_suspend)
+ sigtstp_saved = LYToggleSigDfl(SIGTSTP, &saved_sigtstp_act, 1);
+#endif
+ code = system(command);
+ saved_errno = errno;
+#if defined(HAVE_SIGACTION) && defined(SIGTSTP) && !defined(USE_SLANG)
+ if (sigtstp_saved)
+ LYToggleSigDfl(SIGTSTP, &saved_sigtstp_act, 0);
+#endif
+#ifdef SIGPIPE
+ if (restore_sigpipe_for_children)
+ signal(SIGPIPE, SIG_IGN); /* Ignore it again - kw */
+#endif
+#endif
+#endif
+
+#ifdef __DJGPP__
+ __djgpp_set_ctrl_c(1);
+ _go32_want_ctrl_break(0);
+#endif /* __DJGPP__ */
+
+ fflush(stdout);
+ fflush(stderr);
+
+ if (do_free)
+ FREE(command);
+#if !defined(UCX) || !defined(VAXC) /* errno not modifiable ?? */
+ set_errno(saved_errno); /* may have been clobbered */
+#endif
+#ifdef __EMX__ /* Check whether the screen size changed */
+ size_change(0);
+#endif
+ return code;
+}
+
+/*
+ * Return a string which can be used in LYSystem() for spawning a subshell
+ */
+#if defined(__CYGWIN__) /* 1999/02/26 (Fri) */
+int Cygwin_Shell(void)
+{
+ char *shell;
+ int code;
+ STARTUPINFO startUpInfo;
+ PROCESS_INFORMATION procInfo;
+ SECURITY_ATTRIBUTES sa;
+
+ /* Set up security attributes to allow inheritance of the file handle */
+
+ sa.nLength = sizeof(SECURITY_ATTRIBUTES);
+ sa.lpSecurityDescriptor = 0;
+ sa.bInheritHandle = TRUE;
+
+ /* Init a startup structure */
+ GetStartupInfo(&startUpInfo);
+
+ shell = LYGetEnv("COMSPEC");
+
+ /* Create the child process, specifying
+ inherited handles. Pass the value of the
+ handle as a command line parameter */
+ code = 0;
+ if (shell) {
+ code = CreateProcess(0, shell, 0, 0,
+ TRUE, 0,
+ 0, 0, &startUpInfo, &procInfo);
+
+ if (!code) {
+ printf("shell = [%s], code = %ld\n", shell, (long) GetLastError());
+ }
+
+ /* wait for the child to return (this is not a requirement
+ since the child is its own independent process) */
+ WaitForSingleObject(procInfo.hProcess, INFINITE);
+ }
+
+ return code;
+}
+#endif
+
+#ifdef WIN_EX
+/*
+ * Quote the path to make it safe for shell command processing.
+ * We always quote it not only includes spaces in it.
+ * At least we should quote paths which include "&".
+ */
+char *quote_pathname(char *pathname)
+{
+ char *result = NULL;
+
+ HTSprintf0(&result, "\"%s\"", pathname);
+ return result;
+}
+#endif
+
+const char *LYSysShell(void)
+{
+ const char *shell = 0;
+
+#ifdef DOSPATH
+#ifdef WIN_EX
+ shell = LYGetEnv("SHELL");
+ if (shell) {
+ if (access(shell, 0) != 0)
+ shell = LYGetEnv("COMSPEC");
+ } else {
+ shell = LYGetEnv("COMSPEC");
+ }
+ if (shell == NULL) {
+ if (system_is_NT)
+ shell = "cmd.exe";
+ else
+ shell = "command.com";
+ }
+#else
+ shell = LYGetEnv("SHELL");
+ if (shell == NULL) {
+ shell = LYGetEnv("COMSPEC");
+ }
+ if (shell == NULL) {
+ shell = "command.com";
+ }
+#endif /* WIN_EX */
+#else
+#ifdef __EMX__
+ if (LYGetEnv("SHELL") != NULL) {
+ shell = LYGetEnv("SHELL");
+ } else {
+ shell = (LYGetEnv("COMSPEC") == NULL) ? "cmd.exe" : LYGetEnv("COMSPEC");
+ }
+#else
+#ifdef VMS
+ shell = "";
+#else
+ shell = "exec $SHELL";
+#endif /* __EMX__ */
+#endif /* VMS */
+#endif /* DOSPATH */
+ return shell;
+}
+
+#ifdef VMS
+#define DISPLAY "DECW$DISPLAY"
+#else
+#define DISPLAY "DISPLAY"
+#endif /* VMS */
+
+/*
+ * Return the X-Window $DISPLAY string if it is nonnull/nonempty
+ */
+char *LYgetXDisplay(void)
+{
+ return LYGetEnv(DISPLAY);
+}
+
+/*
+ * Set the value of the X-Window $DISPLAY variable (yes it leaks memory, but
+ * that is putenv's fault).
+ */
+void LYsetXDisplay(char *new_display)
+{
+ if (new_display != 0) {
+#ifdef VMS
+ LYUpperCase(new_display);
+ Define_VMSLogical(DISPLAY, new_display);
+#else
+ static char *display_putenv_command;
+
+ display_putenv_command = NULL; /* yes, this is a leak - cannot fix */
+ HTSprintf0(&display_putenv_command, "DISPLAY=%s", new_display);
+ putenv(display_putenv_command);
+#endif /* VMS */
+ if ((new_display = LYgetXDisplay()) != 0) {
+ StrAllocCopy(x_display, new_display);
+ }
+ }
+}
+
+#ifdef CAN_CUT_AND_PASTE
+#ifdef __EMX__
+
+static int proc_type = -1;
+static PPIB pib;
+static HAB hab;
+static HMQ hmq;
+
+static void morph_PM(void)
+{
+ PTIB tib;
+ int first = 0;
+
+ if (proc_type == -1) {
+ DosGetInfoBlocks(&tib, &pib);
+ proc_type = pib->pib_ultype;
+ first = 1;
+ }
+ if (pib->pib_ultype != 3) /* 2 is VIO */
+ pib->pib_ultype = 3; /* 3 is PM */
+ if (first)
+ hab = WinInitialize(0);
+ /* 64 messages if before OS/2 3.0, ignored otherwise */
+ hmq = WinCreateMsgQueue(hab, 64);
+ WinCancelShutdown(hmq, 1); /* Do not inform us on shutdown */
+}
+
+static void unmorph_PM(void)
+{
+ WinDestroyMsgQueue(hmq);
+ pib->pib_ultype = proc_type;
+}
+
+int size_clip(void)
+{
+ return 8192;
+}
+
+/* Code partially stolen from FED editor. */
+
+int put_clip(const char *s)
+{
+ int sz = strlen(s) + 1;
+ int ret = EOF, nl = 0;
+ char *pByte = 0, *s1 = s, c, *t;
+
+ while ((c = *s1++)) {
+ if (c == '\r' && *s1 == '\n')
+ s1++;
+ else if (c == '\n')
+ nl++;
+ }
+ if (DosAllocSharedMem((PPVOID) & pByte, 0, sz + nl,
+ PAG_WRITE | PAG_COMMIT | OBJ_GIVEABLE | OBJ_GETTABLE))
+ return ret;
+
+ if (!nl)
+ memcpy(pByte, s, sz);
+ else {
+ t = pByte;
+ while ((c = *t++ = *s++))
+ if (c == '\n' && (t == pByte + 1 || t[-2] != '\r'))
+ t[-1] = '\r', *t++ = '\n';
+ }
+
+ morph_PM();
+ if (!hab)
+ goto fail;
+
+ WinOpenClipbrd(hab);
+ WinEmptyClipbrd(hab);
+ if (WinSetClipbrdData(hab, (ULONG) pByte, CF_TEXT, CFI_POINTER))
+ ret = 0;
+ WinCloseClipbrd(hab);
+ unmorph_PM();
+ if (ret == 0)
+ return 0;
+ fail:
+ DosFreeMem((PPVOID) & pByte);
+ return EOF;
+}
+
+static int clip_open;
+
+/* get_clip_grab() returns a pointer to the string in the system area.
+ get_clip_release() should be called ASAP after this. */
+
+char *get_clip_grab(void)
+{
+ char *ClipData;
+ ULONG ulFormat;
+ int sz;
+
+ morph_PM();
+ if (!hab)
+ return 0;
+ if (clip_open)
+ get_clip_release();
+
+ WinQueryClipbrdFmtInfo(hab, CF_TEXT, &ulFormat);
+ if (ulFormat != CFI_POINTER) {
+ unmorph_PM();
+ return 0;
+ }
+ WinOpenClipbrd(hab);
+ clip_open = 1;
+ ClipData = (char *) WinQueryClipbrdData(hab, CF_TEXT);
+ sz = strlen(ClipData);
+ if (!ClipData || !sz) {
+ get_clip_release();
+ return 0;
+ }
+ return ClipData;
+}
+
+void get_clip_release(void)
+{
+ if (!clip_open)
+ return;
+ WinCloseClipbrd(hab);
+ clip_open = 0;
+ unmorph_PM();
+}
+
+#elif defined(WIN_EX) /* 1997/10/16 (Thu) 20:13:28 */
+
+int put_clip(const char *szBuffer)
+{
+ HANDLE hWnd;
+ HANDLE m_hLogData;
+ LPTSTR pLogData;
+ HANDLE hClip;
+ int len;
+
+ if (szBuffer == NULL)
+ return EOF;
+
+ len = (int) strlen(szBuffer);
+ if (len == 0)
+ return EOF;
+ else
+ len++;
+
+ m_hLogData = GlobalAlloc(GHND, len);
+ if (m_hLogData == NULL) {
+ return EOF;
+ }
+
+ hWnd = NULL;
+ if (!OpenClipboard(hWnd)) {
+ return EOF;
+ }
+ /* Remove the current Clipboard contents */
+ if (!EmptyClipboard()) {
+ GlobalFree(m_hLogData);
+ return EOF;
+ }
+
+ /* Lock the global memory while we write to it. */
+ pLogData = (LPTSTR) GlobalLock(m_hLogData);
+
+ lstrcpy((LPTSTR) pLogData, szBuffer);
+ GlobalUnlock(m_hLogData);
+
+ /* If there were any lines at all then copy them to clipboard. */
+ hClip = SetClipboardData(CF_TEXT, m_hLogData);
+ if (!hClip) {
+ /* If we couldn't clip the data then free the global handle. */
+ GlobalFree(m_hLogData);
+ }
+
+ CloseClipboard();
+ return 0;
+}
+
+static HANDLE m_hLogData;
+static int m_locked;
+
+/* get_clip_grab() returns a pointer to the string in the system area.
+ get_clip_release() should be called ASAP after this. */
+
+char *get_clip_grab()
+{
+ HANDLE hWnd;
+ LPTSTR pLogData;
+
+ hWnd = NULL;
+ if (!OpenClipboard(hWnd)) {
+ return 0;
+ }
+
+ m_hLogData = GetClipboardData(CF_TEXT);
+
+ if (m_hLogData == NULL) {
+ CloseClipboard();
+ m_locked = 0;
+ return 0;
+ }
+ pLogData = (LPTSTR) GlobalLock(m_hLogData);
+
+ m_locked = 1;
+ return pLogData;
+}
+
+void get_clip_release()
+{
+ if (!m_locked)
+ return;
+ GlobalUnlock(m_hLogData);
+ CloseClipboard();
+ m_locked = 0;
+}
+
+#elif defined(HAVE_POPEN)
+
+static FILE *paste_handle = 0;
+static char *paste_buf = NULL;
+
+void get_clip_release(void)
+{
+ if (paste_handle != 0)
+ pclose(paste_handle);
+ if (paste_buf)
+ FREE(paste_buf);
+}
+
+static int clip_grab(void)
+{
+ char *cmd = LYGetEnv("RL_PASTE_CMD");
+
+ if (paste_handle)
+ pclose(paste_handle);
+ if (!cmd)
+ return 0;
+
+ paste_handle = popen(cmd, TXT_R);
+ if (!paste_handle)
+ return 0;
+ return 1;
+}
+
+#define PASTE_BUFFER 1008
+#define CF_TEXT 0 /* Not used */
+
+char *get_clip_grab(void)
+{
+ int len;
+ unsigned size = PASTE_BUFFER;
+ int off = 0;
+
+ if (!clip_grab())
+ return NULL;
+ if (!paste_handle)
+ return NULL;
+ if (paste_buf)
+ FREE(paste_buf);
+ paste_buf = typeMallocn(char, PASTE_BUFFER);
+
+ while (1) {
+ len = (int) fread(paste_buf + off,
+ (size_t) 1,
+ (size_t) PASTE_BUFFER - 1,
+ paste_handle);
+ paste_buf[off + len] = '\0';
+ if (len < PASTE_BUFFER - 1)
+ break;
+ if (StrChr(paste_buf + off, '\r')
+ || StrChr(paste_buf + off, '\n'))
+ break;
+ paste_buf = typeRealloc(char, paste_buf, size += PASTE_BUFFER - 1);
+
+ off += len;
+ }
+ return paste_buf;
+}
+
+int put_clip(const char *s)
+{
+ char *cmd = LYGetEnv("RL_CLCOPY_CMD");
+ FILE *fh;
+ size_t l = strlen(s), res;
+
+ if (!cmd)
+ return -1;
+
+ fh = popen(cmd, TXT_W);
+ if (!fh)
+ return -1;
+ res = fwrite(s, (size_t) 1, l, fh);
+ if (pclose(fh) != 0 || res != l)
+ return -1;
+ return 0;
+}
+
+#endif /* __EMX__ ... HAVE_POPEN */
+#endif /* CAN_CUT_AND_PASTE */
+
+/*
+ * Sleep for a number of milli-sec.
+ */
+void LYmsec_delay(unsigned msec)
+{
+#if defined(_WINDOWS)
+ Sleep(msec);
+
+#elif defined(HAVE_NAPMS)
+ napms((int) msec);
+
+#elif defined(DJGPP) || defined(HAVE_USLEEP)
+ usleep(1000 * msec);
+
+#else
+ struct timeval tv;
+ unsigned long usec = 1000UL * msec;
+
+ tv.tv_sec = usec / 1000000UL;
+ tv.tv_usec = usec % 1000000UL;
+ select(0, NULL, NULL, NULL, &tv);
+#endif
+}
+
+#if defined(WIN_EX)
+
+#ifndef WSABASEERR
+#define WSABASEERR 10000
+#endif
+
+#ifdef ENABLE_IPV6
+#define WSOCK_NAME "ws2_32"
+#else
+#define WSOCK_NAME "wsock32"
+#endif
+
+/*
+ * Description: the windows32 version of perror()
+ *
+ * Returns: a pointer to a static error
+ *
+ * Notes/Dependencies: I got this from
+ * comp.os.ms-windows.programmer.win32
+ */
+char *w32_strerror(DWORD ercode)
+{
+/* __declspec(thread) necessary if you will use multiple threads */
+#if defined(__CYGWIN__) || defined(__MINGW32__)
+ static char msg_buff[256];
+
+#else
+ __declspec(thread) static char msg_buff[256];
+#endif
+ HMODULE hModule;
+ int i, msg_type;
+ unsigned char *p, *q, tmp_buff[256];
+ DWORD rc;
+
+ hModule = NULL;
+ msg_type = FORMAT_MESSAGE_FROM_SYSTEM;
+ /* Fill message buffer with a default message in
+ * case FormatMessage fails
+ */
+ wsprintf(msg_buff, "Error %ld", ercode);
+
+ /*
+ * Special code for winsock error handling.
+ */
+ if (ercode > WSABASEERR) {
+ hModule = GetModuleHandle(WSOCK_NAME);
+ if (hModule)
+ msg_type = FORMAT_MESSAGE_FROM_HMODULE;
+ }
+ /*
+ * message handling. If not found in module, retry from system.
+ */
+ rc = FormatMessage(msg_type, hModule, ercode, LANG_NEUTRAL,
+ msg_buff, sizeof(msg_buff), NULL);
+
+ if (rc == 0 && msg_type == FORMAT_MESSAGE_FROM_HMODULE) {
+ FormatMessage(FORMAT_MESSAGE_FROM_SYSTEM, NULL, ercode,
+ LANG_NEUTRAL, msg_buff, sizeof(msg_buff), NULL);
+ }
+
+ strcpy((char *) tmp_buff, msg_buff);
+ p = q = tmp_buff;
+ i = 0;
+ while (*p) {
+ if (!(*p == '\n' || *p == '\r'))
+ msg_buff[i++] = *p;
+ p++;
+ }
+ msg_buff[i] = '\0';
+
+ return msg_buff;
+}
+
+#endif
+
+#if defined(SYSLOG_REQUESTED_URLS)
+/*
+ * syslog() interface
+ */
+void LYOpenlog(const char *banner)
+{
+ if (syslog_requested_urls) {
+ CTRACE((tfp, "LYOpenlog(%s)\n", NONNULL(banner)));
+#if defined(DJGPP)
+ openlog("lynx", LOG_PID | LOG_NDELAY, LOG_LOCAL5);
+#else
+ openlog("lynx", LOG_PID, LOG_LOCAL5);
+#endif
+
+ if (banner) {
+ syslog(LOG_INFO, "Session start:%s", banner);
+ } else {
+ syslog(LOG_INFO, "Session start");
+ }
+ }
+}
+
+static BOOLEAN looks_like_password(char *first,
+ char *last)
+{
+ BOOLEAN result = FALSE;
+
+ while (first <= last) {
+ if (*first == '/'
+ || *first == ':') {
+ result = FALSE;
+ break;
+ }
+ result = TRUE;
+ first++;
+ }
+ return result;
+}
+
+void LYSyslog(char *arg)
+{
+ char *colon1;
+ char *colon2;
+ char *atsign;
+
+ if (syslog_requested_urls) {
+
+ CTRACE((tfp, "LYSyslog %s\n", arg));
+
+ if (is_url(arg)) { /* proto://user:password@host/path:port */
+ /* ^this colon */
+ if ((colon1 = StrChr(arg, ':')) != 0
+ && !StrNCmp(colon1, "://", 3)
+ && (colon2 = StrChr(colon1 + 3, ':')) != 0
+ && (atsign = StrChr(colon1, '@')) != 0
+ && (colon2 < atsign)
+ && looks_like_password(colon2 + 1, atsign - 1)) {
+ char *buf = NULL;
+
+ StrAllocCopy(buf, arg);
+ buf[colon2 - arg + 1] = 0;
+ StrAllocCat(buf, "******");
+ StrAllocCat(buf, atsign);
+ syslog(LOG_INFO | LOG_LOCAL5, "%s", buf);
+ CTRACE((tfp, "...alter %s\n", buf));
+ FREE(buf);
+ return;
+ }
+ }
+ syslog(LOG_INFO | LOG_LOCAL5, "%s", NONNULL(arg));
+ }
+}
+
+void LYCloselog(void)
+{
+ if (syslog_requested_urls) {
+ syslog(LOG_INFO, "Session over");
+ closelog();
+ }
+}
+
+#endif /* SYSLOG_REQUESTED_URLS */
+
+#if defined(WIN_EX) || defined(__CYGWIN__) /* 2000/03/07 (Tue) 17:17:46 */
+
+#define IS_SEP(p) ((p == '\\') || (p == '/') || (p == ':'))
+
+static char *black_list[] =
+{
+ "con",
+ "prn",
+ "clock$",
+ "config$",
+ NULL
+};
+
+static int is_device(char *fname)
+{
+ HANDLE fileHandle;
+ DWORD val;
+ int i;
+
+ i = 0;
+ while (black_list[i] != NULL) {
+ if (strcasecomp(fname, black_list[i]) == 0) {
+ return 1; /* device file */
+ }
+ i++;
+ }
+
+ fileHandle = CreateFile(fname, 0, 0, 0, OPEN_EXISTING, 0, 0);
+
+ if (fileHandle == INVALID_HANDLE_VALUE) {
+ return 0; /* normal file */
+ } else {
+ val = GetFileType(fileHandle);
+ switch (val) {
+ case 1:
+ val = 0;
+ break;
+ case 2:
+ val = 1; /* device file */
+ break;
+ default:
+ val = 0;
+ break;
+ }
+
+ CloseHandle(fileHandle);
+ }
+ return val;
+}
+
+static char *device_list[] =
+{
+ "con",
+ "nul",
+ "aux",
+ "prn",
+ NULL
+};
+
+int unsafe_filename(const char *fname)
+{
+ int i, len, sum;
+ char *cp;
+ char *save;
+
+ i = 0;
+ while (device_list[i] != NULL) {
+ if (strcasecomp(fname, device_list[i]) == 0) {
+ return 0; /* device file (open OK) */
+ }
+ i++;
+ }
+
+ save = cp = strdup(fname);
+
+ while (*cp) {
+ if (IS_SJIS_HI1(UCH(*cp)) || IS_SJIS_HI2(UCH(*cp)))
+ cp += 2; /* KANJI skip */
+ if (IS_SEP(*cp)) {
+ *cp = '\0';
+ }
+ cp++;
+ }
+
+ sum = 0;
+ cp = save;
+ len = (int) strlen(fname);
+ while (cp < (save + len)) {
+ if (*cp == '\0') {
+ cp++;
+ } else {
+ char *q;
+
+ q = StrChr(cp, '.');
+ if (q)
+ *q = '\0';
+ if (is_device(cp)) {
+ sum++;
+ break;
+ }
+ if (q)
+ cp = q + 1;
+ while (*cp)
+ cp++;
+ }
+ }
+ free(save);
+
+ return (sum != 0);
+}
+
+FILE *safe_fopen(const char *fname, const char *mode)
+{
+ if (unsafe_filename(fname)) {
+ return (FILE *) NULL;
+ } else {
+ return fopen(fname, mode);
+ }
+}
+
+#endif /* defined(WIN_EX) || defined(__CYGWIN__) */
diff --git a/src/LYUtils.h b/src/LYUtils.h
new file mode 100644
index 0000000..7ad76db
--- /dev/null
+++ b/src/LYUtils.h
@@ -0,0 +1,578 @@
+/* $LynxId: LYUtils.h,v 1.102 2023/10/23 08:04:01 tom Exp $ */
+#ifndef LYUTILS_H
+#define LYUTILS_H
+
+#include <LYCharVals.h> /* S/390 -- gil -- 2149 */
+#include <LYKeymap.h>
+
+#ifndef HTLIST_H
+#include <HTList.h>
+#endif /* HTLIST_H */
+
+#ifndef HTSTREAM_H
+#include <HTStream.h>
+#endif /* HTSTREAM_H */
+
+#ifdef VMS
+#include <HTFTP.h>
+#include <HTVMSUtils.h>
+#endif /* VMS */
+
+#if defined(USE_DOS_DRIVES)
+#include <HTDOS.h>
+#endif
+
+#if defined(SYSLOG_REQUESTED_URLS)
+#include <syslog.h>
+#endif
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+#ifdef VMS
+#define HTSYS_name(path) HTVMS_name("", path)
+#define HTSYS_purge(path) HTVMS_purge(path)
+#define HTSYS_remove(path) HTVMS_remove(path)
+#endif /* VMS */
+
+#if defined(USE_DOS_DRIVES)
+#define HTSYS_name(path) HTDOS_name(path)
+#endif
+
+#ifndef HTSYS_name
+#define HTSYS_name(path) path
+#endif
+
+#ifndef HTSYS_purge
+#define HTSYS_purge(path) /* nothing */
+#endif
+
+#ifndef HTSYS_remove
+#define HTSYS_remove(path) remove(path)
+#endif
+
+#define LYIsPipeCommand(s) ((s)[0] == '|')
+
+#ifdef VMS
+#define TTY_DEVICE "tt:"
+#define NUL_DEVICE "nl:"
+#define LYIsNullDevice(s) (!strncasecomp(s, "nl:", 3) || !strncasecomp(s, "/nl/", 4))
+#define LYSameFilename(a,b) (!strcasecomp(a,b))
+#define LYSameHostname(a,b) (!strcasecomp(a,b))
+#else
+#if defined(DOSPATH) || defined(__EMX__)
+#define TTY_DEVICE "con"
+#define NUL_DEVICE "nul"
+#define LYIsNullDevice(s) LYSameFilename(s,NUL_DEVICE)
+#define LYSameFilename(a,b) (!strcasecomp(a,b))
+#define LYSameHostname(a,b) (!strcasecomp(a,b))
+#else
+#if defined(__CYGWIN__)
+#define TTY_DEVICE "/dev/tty"
+#define NUL_DEVICE "/dev/null"
+#define LYIsNullDevice(s) LYSameFilename(s,NUL_DEVICE)
+#define LYSameFilename(a,b) (!strcasecomp(a,b))
+#define LYSameHostname(a,b) (!strcasecomp(a,b))
+#else
+#define TTY_DEVICE "/dev/tty"
+#define NUL_DEVICE "/dev/null"
+#define LYIsNullDevice(s) LYSameFilename(s,NUL_DEVICE)
+#define LYSameFilename(a,b) (!strcmp(a,b))
+#define LYSameHostname(a,b) (!strcmp(a,b))
+#endif /* __CYGWIN__ */
+#endif /* DOSPATH */
+#endif /* VMS */
+
+/* See definitions in src/LYCharVals.h. The hardcoded values...
+ This prohibits binding C-c and C-g. Maybe it is better to remove this? */
+#define LYCharIsINTERRUPT_HARD(ch) \
+ ((ch) == LYCharINTERRUPT1 || ch == LYCharINTERRUPT2)
+
+#define LYCharIsINTERRUPT(ch) \
+ (LYCharIsINTERRUPT_HARD(ch) || LKC_TO_LAC(keymap,ch) == LYK_INTERRUPT)
+
+#define LYCharIsINTERRUPT_NO_letter(ch) \
+ (LYCharIsINTERRUPT(ch) && !isprint(ch))
+
+#if defined(USE_DOS_DRIVES)
+#define PATH_SEPARATOR ";"
+#define FILE_SEPARATOR "\\"
+#define LYIsPathSep(ch) ((ch) == '/' || (ch) == '\\')
+#define LYIsDosDrive(s) (isalpha(UCH((s)[0])) && (s)[1] == ':')
+#else
+#define PATH_SEPARATOR ":"
+#define FILE_SEPARATOR "/"
+#define LYIsPathSep(ch) ((ch) == '/')
+#define LYIsDosDrive(s) FALSE /* really nothing */
+#endif
+
+#ifdef USE_ADDRLIST_PAGE
+#define LYIsListpageTitle(name) \
+ (!strcmp((name), LIST_PAGE_TITLE) || \
+ !strcmp((name), ADDRLIST_PAGE_TITLE))
+#else
+#define LYIsListpageTitle(name) \
+ (!strcmp((name), LIST_PAGE_TITLE))
+#endif
+
+#define LYIsTilde(ch) ((ch) == '~')
+#define LYIsHtmlSep(ch) ((ch) == '/')
+#define findPoundSelector(address) StrChr(address, '#')
+#define restorePoundSelector(pound) if ((pound) != NULL) *(pound) = '#'
+
+ extern BOOL strn_dash_equ(const char *p1, const char *p2, int len);
+ extern BOOLEAN LYCachedTemp(char *result, char **cached);
+ extern BOOLEAN LYCanDoHEAD(const char *address);
+ extern BOOLEAN LYCanReadFile(const char *name);
+ extern BOOLEAN LYCanWriteFile(const char *name);
+ extern BOOLEAN LYCloseInput(FILE *fp);
+ extern BOOLEAN LYCloseOutput(FILE *fp);
+ extern BOOLEAN LYFixCursesOnForAccess(const char *addr, const char *physical);
+ extern BOOLEAN LYPathOffHomeOK(char *fbuffer, size_t fbuffer_size);
+ extern BOOLEAN LYValidateFilename(bstring **result, bstring **given);
+ extern BOOLEAN LYisAbsPath(const char *path);
+ extern BOOLEAN LYisLocalAlias(const char *filename);
+ extern BOOLEAN LYisLocalFile(const char *filename);
+ extern BOOLEAN LYisLocalHost(const char *filename);
+ extern BOOLEAN LYisRootPath(const char *path);
+ extern BOOLEAN inlocaldomain(void);
+ extern FILE *InternalPageFP(char *filename, int reuse_flag);
+ extern FILE *LYAppendToTxtFile(const char *name);
+ extern FILE *LYNewBinFile(const char *name);
+ extern FILE *LYNewTxtFile(const char *name);
+ extern FILE *LYOpenScratch(char *result, const char *prefix);
+ extern FILE *LYOpenTemp(char *result, const char *suffix, const char *mode);
+ extern FILE *LYOpenTempRewrite(char *result, const char *suffix, const char *mode);
+ extern FILE *LYReopenTemp(char *name);
+ extern char *Current_Dir(char *pathname);
+ extern char *LYAbsOrHomePath(char **fname);
+ extern char *LYAddPathToSave(char *fname);
+ extern char *LYFindConfigFile(const char *nominal, const char *dftfile);
+ extern char *LYGetEnv(const char *name);
+ extern char *LYLastPathSep(const char *path);
+ extern char *LYPathLeaf(char *pathname);
+ extern char *LYTildeExpand(char **pathname, int embedded);
+ extern char *LYgetXDisplay(void);
+ extern char *strip_trailing_slash(char *my_dirname);
+ extern char *trimPoundSelector(char *address);
+ extern const char *Home_Dir(void);
+ extern const char *LYGetHiliteStr(int cur, int count);
+ extern const char *LYSysShell(void);
+ extern const char *index_to_restriction(unsigned inx);
+ extern const char *wwwName(const char *pathname);
+ extern int HTCheckForInterrupt(void);
+ extern int LYConsoleInputFD(int need_selectable);
+ extern int LYRenameFile(char *src, char *dst);
+ extern int LYCopyFile(char *src, char *dst);
+ extern int LYGetHilitePos(int cur, int count);
+ extern int LYRemoveTemp(char *name);
+ extern int LYReopenInput(void);
+ extern int LYSystem(char *command);
+ extern int LYValidateOutput(char *filename);
+ extern int find_restriction(const char *name, int len);
+ extern int number2arrows(int number);
+ extern size_t utf8_length(int utf_flag, const char *data);
+ extern time_t LYmktime(char *string, int absolute);
+ extern void BeginInternalPage(FILE *fp0, const char *Title, const char *HelpURL);
+ extern void EndInternalPage(FILE *fp0);
+ extern void HTAddSugFilename(char *fname);
+ extern void HTSugFilenames_free(void);
+ extern void LYAddHilite(int cur, char *text, int x);
+ extern void LYAddHtmlSep(char **path);
+ extern void LYAddHtmlSep0(char *path);
+ extern void LYAddLocalhostAlias(char *alias);
+ extern void LYAddPathSep(char **path);
+ extern void LYAddPathSep0(char *path);
+ extern void LYAddPathToHome(char *fbuffer, size_t fbuffer_size, const char *fname);
+ extern void LYCheckBibHost(void);
+ extern void LYCheckMail(void);
+ extern void LYCleanupTemp(void);
+ extern void LYCloseTemp(char *name);
+ extern void LYCloseTempFP(FILE *fp);
+ extern void LYConvertToURL(char **AllocatedString, int fixit);
+ extern void LYDoCSI(char *url, const char *comment, char **csi);
+ extern void LYEnsureAbsoluteURL(char **href, const char *name, int fixit);
+ extern void LYFakeZap(int set);
+ extern void LYFixCursesOn(const char *reason);
+ extern void LYFreeHilites(int first, int last);
+ extern void LYFreeStringList(HTList *list);
+ extern void LYGetScreenSize(int sig);
+ extern void LYLocalFileToURL(char **target, const char *source);
+ extern void LYLocalhostAliases_free(void);
+ extern void LYRenamedTemp(char *oldname, char *newname);
+ extern void LYSetHilite(int cur, const char *text);
+ extern void LYTrimHtmlSep(char *path);
+ extern void LYTrimPathSep(char *path);
+ extern void LYTrimRelFromAbsPath(char *path);
+ extern void LYhighlight(int flag, int cur, const char *target);
+ extern void LYmsec_delay(unsigned msec);
+ extern void LYsetXDisplay(char *new_display);
+ extern void WriteInternalTitle(FILE *fp0, const char *Title);
+ extern void WriteStreamTitle(HTStream *target, const char *Title);
+ extern void change_sug_filename(char *fname);
+ extern void convert_to_spaces(char *string, int condense);
+ extern void free_and_clear(char **obj);
+ extern void noviceline(int more_flag);
+ extern void parse_restrictions(const char *s);
+ extern void print_restrictions_to_fd(FILE *fp);
+ extern void remove_backslashes(char *buf);
+ extern void size_change(int sig);
+ extern void statusline(const char *text);
+ extern void toggle_novice_line(void);
+
+#if defined(MULTI_USER_UNIX)
+ extern BOOL IsOurFile(const char *name);
+#else
+#define IsOurFile(name) TRUE
+#endif
+
+#ifdef USE_ASCII_CTYPES
+ extern int ascii_tolower(int i);
+ extern int ascii_toupper(int i);
+ extern int ascii_isupper(int i);
+#endif
+
+#ifdef __CYGWIN__
+ extern int Cygwin_Shell(void);
+#endif
+
+#if defined(_WIN_CC) || defined(WIN_EX)
+ extern int exec_command(char *cmd, int wait_flag); /* xsystem.c */
+ extern char *quote_pathname(char *pathname);
+ extern int xsystem(char *cmd);
+#endif
+
+ /* Keeping track of User Interface Pages: */
+ typedef enum {
+ UIP_UNKNOWN = -1
+ ,UIP_HISTORY = 0
+ ,UIP_DOWNLOAD_OPTIONS
+ ,UIP_PRINT_OPTIONS
+ ,UIP_SHOWINFO
+ ,UIP_LIST_PAGE
+ ,UIP_VLINKS
+ ,UIP_LYNXCFG
+ ,UIP_OPTIONS_MENU
+ ,UIP_DIRED_MENU
+ ,UIP_PERMIT_OPTIONS
+ ,UIP_UPLOAD_OPTIONS
+ ,UIP_ADDRLIST_PAGE
+ ,UIP_CONFIG_DEF
+ ,UIP_TRACELOG
+ ,UIP_INSTALL
+ } UIP_t;
+
+#define UIP_P_FRAG 0x0001 /* flag: consider "url#frag" as matching "url" */
+
+ extern BOOL LYIsUIPage3(const char *url, UIP_t type, int flagparam);
+
+#define LYIsUIPage(url,type) LYIsUIPage3(url, type, UIP_P_FRAG)
+ extern void LYRegisterUIPage(const char *url, UIP_t type);
+
+#define LYUnRegisterUIPage(type) LYRegisterUIPage(NULL, type)
+ extern void LYUIPages_free(void);
+
+#ifdef CAN_CUT_AND_PASTE
+ extern int put_clip(const char *szBuffer);
+
+/* get_clip_grab() returns a pointer to the string in the system area.
+ get_clip_release() should be called ASAP after this. */
+ extern char *get_clip_grab(void);
+ extern void get_clip_release(void);
+
+# ifdef WIN_EX
+# define size_clip() 8192
+# else
+ extern int size_clip(void);
+
+# endif
+#endif
+
+#if defined(WIN_EX) /* 1997/10/16 (Thu) 20:13:28 */
+ extern char *HTDOS_short_name(const char *path);
+ extern char *w32_strerror(DWORD ercode);
+#endif
+
+#if defined(WIN_EX) || defined(__CYGWIN__) /* 2000/03/07 (Tue) 17:17:46 */
+ extern int unsafe_filename(const char *fname);
+ extern FILE *safe_fopen(const char *fname, const char *mode);
+#endif
+
+#ifdef VMS
+ extern void Define_VMSLogical(char *LogicalName, char *LogicalValue);
+#endif /* VMS */
+
+#if !defined(HAVE_PUTENV)
+ extern int putenv(const char *string);
+#endif /* HAVE_PUTENV */
+
+#if defined(MULTI_USER_UNIX)
+ extern void LYRelaxFilePermissions(const char *name);
+
+#else
+#define LYRelaxFilePermissions(name) /* nothing */
+#endif
+
+#if defined(_WINDOWS)
+ extern int win32_check_interrupt(void);
+
+#if (defined(__MINGW32__) && !defined(HAVE_SLEEP))
+#undef sleep
+ void sleep(unsigned sec);
+#endif
+#endif
+
+ /*
+ * Whether or not the status line must be shown.
+ */
+ extern BOOLEAN mustshow;
+
+#define _statusline(msg) mustshow = TRUE, statusline(msg)
+
+ /*
+ * For is_url().
+ *
+ * Universal document id types (see LYCheckForProxyURL)
+ */
+ typedef enum {
+ NOT_A_URL_TYPE = 0,
+ UNKNOWN_URL_TYPE = 1, /* must be nonzero */
+
+ HTTP_URL_TYPE,
+ FILE_URL_TYPE,
+ FTP_URL_TYPE,
+ NCFTP_URL_TYPE,
+ WAIS_URL_TYPE,
+ NEWS_URL_TYPE,
+ NNTP_URL_TYPE,
+ TELNET_URL_TYPE,
+ TN3270_URL_TYPE,
+ RLOGIN_URL_TYPE,
+ GOPHER_URL_TYPE,
+ HTML_GOPHER_URL_TYPE,
+ TELNET_GOPHER_URL_TYPE,
+ INDEX_GOPHER_URL_TYPE,
+ MAILTO_URL_TYPE,
+ BIBP_URL_TYPE,
+ FINGER_URL_TYPE,
+ CSO_URL_TYPE,
+ HTTPS_URL_TYPE,
+ SNEWS_URL_TYPE,
+ PROSPERO_URL_TYPE,
+ AFS_URL_TYPE,
+
+ DATA_URL_TYPE,
+
+ LYNXCGI_URL_TYPE,
+ LYNXEXEC_URL_TYPE,
+ LYNXPROG_URL_TYPE,
+
+ NEWSPOST_URL_TYPE,
+ NEWSREPLY_URL_TYPE,
+ SNEWSPOST_URL_TYPE,
+ SNEWSREPLY_URL_TYPE,
+
+ LYNXCACHE_URL_TYPE,
+ LYNXCFG_URL_TYPE,
+ LYNXCOMPILE_OPTS_URL_TYPE,
+ LYNXCOOKIE_URL_TYPE,
+ LYNXDIRED_URL_TYPE,
+ LYNXDOWNLOAD_URL_TYPE,
+ LYNXEDITMAP_URL_TYPE,
+ LYNXHIST_URL_TYPE,
+ LYNXIMGMAP_URL_TYPE,
+ LYNXKEYMAP_URL_TYPE,
+ LYNXMESSAGES_URL_TYPE,
+ LYNXOPTIONS_URL_TYPE,
+ LYNXPRINT_URL_TYPE,
+
+ PROXY_URL_TYPE
+
+ } UrlTypes;
+
+ extern UrlTypes LYCheckForProxyURL(char *filename);
+ extern UrlTypes is_url(char *filename);
+
+/* common URLs */
+#define STR_BIBP_URL "bibp:"
+#define LEN_BIBP_URL 5
+#define isBIBP_URL(addr) !strncasecomp(addr, STR_BIBP_URL, LEN_BIBP_URL)
+
+#define STR_CSO_URL "cso:"
+#define LEN_CSO_URL 4
+#define isCSO_URL(addr) !strncasecomp(addr, STR_CSO_URL, LEN_CSO_URL)
+
+#define STR_FILE_URL "file:"
+#define LEN_FILE_URL 5
+#define isFILE_URL(addr) ((*addr == 'f' || *addr == 'F') &&\
+ !strncasecomp(addr, STR_FILE_URL, LEN_FILE_URL))
+
+#define STR_FINGER_URL "finger:"
+#define LEN_FINGER_URL 7
+#define isFINGER_URL(addr) !strncasecomp(addr, STR_FINGER_URL, LEN_FINGER_URL)
+
+#define STR_FTP_URL "ftp:"
+#define LEN_FTP_URL 4
+#define isFTP_URL(addr) !strncasecomp(addr, STR_FTP_URL, LEN_FTP_URL)
+
+#define STR_GOPHER_URL "gopher:"
+#define LEN_GOPHER_URL 7
+#define isGOPHER_URL(addr) !strncasecomp(addr, STR_GOPHER_URL, LEN_GOPHER_URL)
+
+#define STR_HTTP_URL "http:"
+#define LEN_HTTP_URL 5
+#define isHTTP_URL(addr) !strncasecomp(addr, STR_HTTP_URL, LEN_HTTP_URL)
+
+#define STR_HTTPS_URL "https:"
+#define LEN_HTTPS_URL 6
+#define isHTTPS_URL(addr) !strncasecomp(addr, STR_HTTPS_URL, LEN_HTTPS_URL)
+
+#define STR_MAILTO_URL "mailto:"
+#define LEN_MAILTO_URL 7
+#define isMAILTO_URL(addr) !strncasecomp(addr, STR_MAILTO_URL, LEN_MAILTO_URL)
+
+#define STR_NEWS_URL "news:"
+#define LEN_NEWS_URL 5
+#define isNEWS_URL(addr) !strncasecomp(addr, STR_NEWS_URL, LEN_NEWS_URL)
+
+#define STR_NNTP_URL "nntp:"
+#define LEN_NNTP_URL 5
+#define isNNTP_URL(addr) !strncasecomp(addr, STR_NNTP_URL, LEN_NNTP_URL)
+
+#define STR_RLOGIN_URL "rlogin:"
+#define LEN_RLOGIN_URL 7
+#define isRLOGIN_URL(addr) !strncasecomp(addr, STR_RLOGIN_URL, LEN_RLOGIN_URL)
+
+#define STR_SNEWS_URL "snews:"
+#define LEN_SNEWS_URL 6
+#define isSNEWS_URL(addr) !strncasecomp(addr, STR_SNEWS_URL, LEN_SNEWS_URL)
+
+#define STR_TELNET_URL "telnet:"
+#define LEN_TELNET_URL 7
+#define isTELNET_URL(addr) !strncasecomp(addr, STR_TELNET_URL, LEN_TELNET_URL)
+
+#define STR_TN3270_URL "tn3270:"
+#define LEN_TN3270_URL 7
+#define isTN3270_URL(addr) !strncasecomp(addr, STR_TN3270_URL, LEN_TN3270_URL)
+
+#define STR_WAIS_URL "wais:"
+#define LEN_WAIS_URL 5
+#define isWAIS_URL(addr) !strncasecomp(addr, STR_WAIS_URL, LEN_WAIS_URL)
+
+/* internal URLs */
+#ifdef USE_CACHEJAR
+#define STR_LYNXCACHE "LYNXCACHE:"
+#define LEN_LYNXCACHE 10
+#define isLYNXCACHE(addr) !strncasecomp(addr, STR_LYNXCACHE, LEN_LYNXCACHE)
+#else
+#define isLYNXCACHE(addr) FALSE
+#endif
+
+#define STR_LYNXCFG "LYNXCFG:"
+#define LEN_LYNXCFG 8
+#define isLYNXCFG(addr) !strncasecomp(addr, STR_LYNXCFG, LEN_LYNXCFG)
+
+#define STR_LYNXCFLAGS "LYNXCOMPILEOPTS:"
+#define LEN_LYNXCFLAGS 16
+#define isLYNXCFLAGS(addr) !strncasecomp(addr, STR_LYNXCFLAGS, LEN_LYNXCFLAGS)
+
+#define STR_LYNXCGI "lynxcgi:"
+#define LEN_LYNXCGI 8
+#define isLYNXCGI(addr) ((*addr == 'l' || *addr == 'L') &&\
+ !strncasecomp(addr, STR_LYNXCGI, LEN_LYNXCGI))
+
+#define STR_LYNXCOOKIE "LYNXCOOKIE:"
+#define LEN_LYNXCOOKIE 11
+#define isLYNXCOOKIE(addr) !strncasecomp(addr, STR_LYNXCOOKIE, LEN_LYNXCOOKIE)
+
+#define STR_LYNXDIRED "LYNXDIRED:"
+#define LEN_LYNXDIRED 10
+#define isLYNXDIRED(addr) !strncasecomp(addr, STR_LYNXDIRED, LEN_LYNXDIRED)
+
+#define STR_LYNXEXEC "lynxexec:"
+#define LEN_LYNXEXEC 9
+#define isLYNXEXEC(addr) ((*addr == 'l' || *addr == 'L') &&\
+ !strncasecomp(addr, STR_LYNXEXEC, LEN_LYNXEXEC))
+
+#define STR_LYNXDOWNLOAD "LYNXDOWNLOAD:"
+#define LEN_LYNXDOWNLOAD 13
+#define isLYNXDOWNLOAD(addr) !strncasecomp(addr, STR_LYNXDOWNLOAD, LEN_LYNXDOWNLOAD)
+
+#define STR_LYNXEDITMAP "LYNXEDITMAP:"
+#define LEN_LYNXEDITMAP 11
+#define isLYNXEDITMAP(addr) !strncasecomp(addr, STR_LYNXEDITMAP, LEN_LYNXEDITMAP)
+
+#define STR_LYNXHIST "LYNXHIST:"
+#define LEN_LYNXHIST 9
+#define isLYNXHIST(addr) !strncasecomp(addr, STR_LYNXHIST, LEN_LYNXHIST)
+
+#define STR_LYNXKEYMAP "LYNXKEYMAP:"
+#define LEN_LYNXKEYMAP 11
+#define isLYNXKEYMAP(addr) !strncasecomp(addr, STR_LYNXKEYMAP, LEN_LYNXKEYMAP)
+
+#define STR_LYNXIMGMAP "LYNXIMGMAP:"
+#define LEN_LYNXIMGMAP 11
+#define isLYNXIMGMAP(addr) !strncasecomp(addr, STR_LYNXIMGMAP, LEN_LYNXIMGMAP)
+
+#define STR_LYNXMESSAGES "LYNXMESSAGES:"
+#define LEN_LYNXMESSAGES 13
+#define isLYNXMESSAGES(addr) !strncasecomp(addr, STR_LYNXMESSAGES, LEN_LYNXMESSAGES)
+
+#define STR_LYNXOPTIONS "LYNXOPTIONS:"
+#define LEN_LYNXOPTIONS 12
+#define isLYNXOPTIONS(addr) !strncasecomp(addr, STR_LYNXOPTIONS, LEN_LYNXOPTIONS)
+
+#define STR_LYNXPRINT "LYNXPRINT:"
+#define LEN_LYNXPRINT 10
+#define isLYNXPRINT(addr) !strncasecomp(addr, STR_LYNXPRINT, LEN_LYNXPRINT)
+
+#define STR_LYNXPROG "lynxprog:"
+#define LEN_LYNXPROG 9
+#define isLYNXPROG(addr) ((*addr == 'l' || *addr == 'L') &&\
+ !strncasecomp(addr, STR_LYNXPROG, LEN_LYNXPROG))
+
+#define LYNXOPTIONS_PAGE(s) STR_LYNXOPTIONS s
+/*
+ * For change_sug_filename().
+ */
+ extern HTList *sug_filenames;
+
+/*
+ * syslog() facility
+ */
+#if defined(SYSLOG_REQUESTED_URLS)
+ extern void LYOpenlog(const char *banner);
+ extern void LYSyslog(char *arg);
+ extern void LYCloselog(void);
+#endif /* SYSLOG_REQUESTED_URLS */
+
+#undef STREQ /* conflict with wais.h */
+
+/*
+ * Miscellaneous.
+ */
+#define STREQ(a,b) (strcmp(a,b) == 0)
+#define STRNEQ(a,b,c) (StrNCmp(a,b,c) == 0)
+
+#define HIDE_CHMOD 0600
+#define HIDE_UMASK 0077
+
+#if defined(DOSPATH) || defined(__CYGWIN__)
+#define TXT_R "rt"
+#define TXT_W "wt"
+#define TXT_A "at+"
+#else
+#define TXT_R "r"
+#define TXT_W "w"
+#define TXT_A "a+"
+#endif
+
+#define BIN_R "rb"
+#define BIN_W "wb"
+#define BIN_A "ab+"
+
+#ifdef __cplusplus
+}
+#endif
+#endif /* LYUTILS_H */
diff --git a/src/LYVMSdef.h b/src/LYVMSdef.h
new file mode 100644
index 0000000..6fde3c3
--- /dev/null
+++ b/src/LYVMSdef.h
@@ -0,0 +1,18 @@
+
+#ifndef LYVMSDEF_H
+#define LYVMSDEF_H
+
+/*
+ * These are VMS system definitions which may not be in the headers
+ * of old VMS compilers and contain non-ANSI extended tokens that
+ * generate warnings by some Unix compilers while looking for the
+ * "#endif" which closes the outer "#ifdef VMS".
+ */
+#ifndef CLI$M_TRUSTED
+#define CLI$M_TRUSTED 64 /* May not be in the compiler's clidef.h */
+#endif /* !CLI$M_TRUSTED */
+#ifndef LIB$_INVARG
+#define LIB$_INVARG 1409588 /* May not be in the compiler's libdef.h */
+#endif /* !LIB$_INVARG */
+
+#endif /* LYVMSDEF_H */
diff --git a/src/LYebcdic.c b/src/LYebcdic.c
new file mode 100644
index 0000000..30c9822
--- /dev/null
+++ b/src/LYebcdic.c
@@ -0,0 +1,48 @@
+/*
+ * $LynxId: LYebcdic.c,v 1.1 2008/12/30 01:03:05 Paul.Gilmartin Exp $
+ */
+#include <HTUtils.h>
+
+#ifdef EBCDIC
+/* *INDENT-OFF* */
+const char un_IBM1047[ 256 ] = /* ETOA OEMVS311 */
+{
+0x00,0x01,0x02,0x03,0x9c,0x09,0x86,0x7f,0x97,0x8d,0x8e,0x0b,0x0c,0x0d,0x0e,0x0f,
+0x10,0x11,0x12,0x13,0x9d,0x0a,0x08,0x87,0x18,0x19,0x92,0x8f,0x1c,0x1d,0x1e,0x1f,
+0x80,0x81,0x82,0x83,0x84,0x85,0x17,0x1b,0x88,0x89,0x8a,0x8b,0x8c,0x05,0x06,0x07,
+0x90,0x91,0x16,0x93,0x94,0x95,0x96,0x04,0x98,0x99,0x9a,0x9b,0x14,0x15,0x9e,0x1a,
+0x20,0xa0,0xe2,0xe4,0xe0,0xe1,0xe3,0xe5,0xe7,0xf1,0xa2,0x2e,0x3c,0x28,0x2b,0x7c,
+0x26,0xe9,0xea,0xeb,0xe8,0xed,0xee,0xef,0xec,0xdf,0x21,0x24,0x2a,0x29,0x3b,0x5e,
+0x2d,0x2f,0xc2,0xc4,0xc0,0xc1,0xc3,0xc5,0xc7,0xd1,0xa6,0x2c,0x25,0x5f,0x3e,0x3f,
+0xf8,0xc9,0xca,0xcb,0xc8,0xcd,0xce,0xcf,0xcc,0x60,0x3a,0x23,0x40,0x27,0x3d,0x22,
+0xd8,0x61,0x62,0x63,0x64,0x65,0x66,0x67,0x68,0x69,0xab,0xbb,0xf0,0xfd,0xfe,0xb1,
+0xb0,0x6a,0x6b,0x6c,0x6d,0x6e,0x6f,0x70,0x71,0x72,0xaa,0xba,0xe6,0xb8,0xc6,0xa4,
+0xb5,0x7e,0x73,0x74,0x75,0x76,0x77,0x78,0x79,0x7a,0xa1,0xbf,0xd0,0x5b,0xde,0xae,
+0xac,0xa3,0xa5,0xb7,0xa9,0xa7,0xb6,0xbc,0xbd,0xbe,0xdd,0xa8,0xaf,0x5d,0xb4,0xd7,
+0x7b,0x41,0x42,0x43,0x44,0x45,0x46,0x47,0x48,0x49,0xad,0xf4,0xf6,0xf2,0xf3,0xf5,
+0x7d,0x4a,0x4b,0x4c,0x4d,0x4e,0x4f,0x50,0x51,0x52,0xb9,0xfb,0xfc,0xf9,0xfa,0xff,
+0x5c,0xf7,0x53,0x54,0x55,0x56,0x57,0x58,0x59,0x5a,0xb2,0xd4,0xd6,0xd2,0xd3,0xd5,
+0x30,0x31,0x32,0x33,0x34,0x35,0x36,0x37,0x38,0x39,0xb3,0xdb,0xdc,0xd9,0xda,0x9f
+} ;
+const unsigned char IBM1047[ 256 ] = /* ATOE OEMVS311 */
+{
+0x00,0x01,0x02,0x03,0x37,0x2d,0x2e,0x2f,0x16,0x05,0x15,0x0b,0x0c,0x0d,0x0e,0x0f,
+0x10,0x11,0x12,0x13,0x3c,0x3d,0x32,0x26,0x18,0x19,0x3f,0x27,0x1c,0x1d,0x1e,0x1f,
+0x40,0x5a,0x7f,0x7b,0x5b,0x6c,0x50,0x7d,0x4d,0x5d,0x5c,0x4e,0x6b,0x60,0x4b,0x61,
+0xf0,0xf1,0xf2,0xf3,0xf4,0xf5,0xf6,0xf7,0xf8,0xf9,0x7a,0x5e,0x4c,0x7e,0x6e,0x6f,
+0x7c,0xc1,0xc2,0xc3,0xc4,0xc5,0xc6,0xc7,0xc8,0xc9,0xd1,0xd2,0xd3,0xd4,0xd5,0xd6,
+0xd7,0xd8,0xd9,0xe2,0xe3,0xe4,0xe5,0xe6,0xe7,0xe8,0xe9,0xad,0xe0,0xbd,0x5f,0x6d,
+0x79,0x81,0x82,0x83,0x84,0x85,0x86,0x87,0x88,0x89,0x91,0x92,0x93,0x94,0x95,0x96,
+0x97,0x98,0x99,0xa2,0xa3,0xa4,0xa5,0xa6,0xa7,0xa8,0xa9,0xc0,0x4f,0xd0,0xa1,0x07,
+0x20,0x21,0x22,0x23,0x24,0x25,0x06,0x17,0x28,0x29,0x2a,0x2b,0x2c,0x09,0x0a,0x1b,
+0x30,0x31,0x1a,0x33,0x34,0x35,0x36,0x08,0x38,0x39,0x3a,0x3b,0x04,0x14,0x3e,0xff,
+0x41,0xaa,0x4a,0xb1,0x9f,0xb2,0x6a,0xb5,0xbb,0xb4,0x9a,0x8a,0xb0,0xca,0xaf,0xbc,
+0x90,0x8f,0xea,0xfa,0xbe,0xa0,0xb6,0xb3,0x9d,0xda,0x9b,0x8b,0xb7,0xb8,0xb9,0xab,
+0x64,0x65,0x62,0x66,0x63,0x67,0x9e,0x68,0x74,0x71,0x72,0x73,0x78,0x75,0x76,0x77,
+0xac,0x69,0xed,0xee,0xeb,0xef,0xec,0xbf,0x80,0xfd,0xfe,0xfb,0xfc,0xba,0xae,0x59,
+0x44,0x45,0x42,0x46,0x43,0x47,0x9c,0x48,0x54,0x51,0x52,0x53,0x58,0x55,0x56,0x57,
+0x8c,0x49,0xcd,0xce,0xcb,0xcf,0xcc,0xe1,0x70,0xdd,0xde,0xdb,0xdc,0x8d,0x8e,0xdf
+} ;
+/* *INDENT-ON* */
+
+#endif /* EBCDIC */
diff --git a/src/LYexit.c b/src/LYexit.c
new file mode 100644
index 0000000..8b94048
--- /dev/null
+++ b/src/LYexit.c
@@ -0,0 +1,185 @@
+/*
+ * $LynxId: LYexit.c,v 1.38 2020/02/25 10:14:48 tom Exp $
+ *
+ * Copyright (c) 1994, University of Kansas, All Rights Reserved
+ * (most of this file was rewritten in 1996 and 2004).
+ */
+#include <HTUtils.h>
+#include <LYexit.h>
+#include <HTAlert.h>
+#ifndef VMS
+#include <LYGlobalDefs.h>
+#include <LYUtils.h>
+#include <LYSignal.h>
+#include <LYMainLoop.h>
+#endif /* !VMS */
+#include <LYStrings.h>
+#include <LYClean.h>
+
+/*
+ * Flag for outofmem macro. - FM
+ */
+BOOL LYOutOfMemory = FALSE;
+
+/*
+ * Stack of functions to call upon exit.
+ */
+static void (*callstack[ATEXITSIZE]) (void);
+static int topOfStack = 0;
+
+/*
+ * Capture "atexit()" calls for our own accounting.
+ */
+int LYatexit(void (*function) (void))
+{
+ int result = 0;
+
+ if (topOfStack >= ATEXITSIZE) {
+ CTRACE((tfp, "(LY)atexit: Too many functions, ignoring one!\n"));
+ result = -1;
+ } else {
+ int n;
+ BOOLEAN found = FALSE;
+
+ for (n = 0; n < topOfStack; ++n) {
+ if (callstack[n] == function) {
+ found = TRUE;
+ break;
+ }
+ }
+ if (!found) {
+ callstack[topOfStack++] = function;
+ }
+ }
+ return result;
+}
+
+/*
+ * Purpose: Call the functions registered with LYatexit
+ * Arguments: void
+ * Return Value: void
+ * Remarks/Portability/Dependencies/Restrictions:
+ * Revision History:
+ * 06-15-94 created Lynx 2-3-1 Garrett Arch Blythe
+ */
+static void LYCompleteExit(void)
+{
+ /*
+ * Just loop through registered functions. This is reentrant if more exits
+ * occur in the registered functions.
+ */
+ while (--topOfStack >= 0) {
+ callstack[topOfStack] ();
+ }
+}
+
+/*
+ * Purpose: Terminates program, reports memory not freed.
+ * Arguments: status Exit code.
+ * Return Value: void
+ * Remarks/Portability/Dependencies/Restrictions:
+ * Function calls stdlib.h exit
+ * Revision History:
+ * 06-15-94 created Lynx 2-3-1 Garrett Arch Blythe
+ */
+void LYexit(int status)
+{
+#ifndef VMS /* On VMS, the VMSexit() handler does these. - FM */
+#ifdef _WINDOWS
+ DeleteCriticalSection(&critSec_READ);
+
+ WSACleanup();
+#endif
+ if (LYOutOfMemory == TRUE) {
+ /*
+ * Ignore further interrupts. - FM
+ */
+#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 (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);
+ }
+#endif /* !VMS */
+
+ /*
+ * Close syslog before doing atexit-cleanup, since it may use a string
+ * that would be freed there.
+ */
+#ifdef SYSLOG_REQUESTED_URLS
+ LYCloselog();
+#endif
+
+ /*
+ * Do functions registered with LYatexit. - GAB
+ */
+ LYCompleteExit();
+
+ LYCloseCmdLogfile();
+
+#ifdef exit
+/* Make sure we use stdlib exit and not LYexit. - GAB
+*/
+#undef exit
+#endif /* exit */
+
+ cleanup_files(); /* if someone starts with LYNXfoo: page */
+#ifndef VMS /* On VMS, the VMSexit() handler does these. - FM */
+ fflush(stderr);
+ if (LYOutOfMemory == TRUE) {
+ LYOutOfMemory = FALSE;
+ printf("\r\n%s\r\n\r\n", MEMORY_EXHAUSTED_ABORT);
+ fflush(stdout);
+ }
+ LYCloseTracelog();
+#endif /* !VMS */
+ show_alloc();
+
+#if defined(NCURSES_VERSION) && defined(LY_FIND_LEAKS)
+#if defined(HAVE_CURSES_EXIT)
+ curses_exit(status);
+#elif defined(HAVE__NC_FREE_AND_EXIT)
+ _nc_free_and_exit(status);
+#elif defined(HAVE__NC_FREEALL)
+ _nc_freeall();
+#endif
+#endif /* NCURSES_VERSION */
+
+ exit(status);
+}
+
+void outofmem(const char *fname,
+ const char *func)
+{
+ fprintf(stderr, "\n\n\n%s %s: %s\n", fname, func, MEMORY_EXHAUSTED_ABORTING);
+ LYOutOfMemory = TRUE;
+ LYexit(-1);
+}
diff --git a/src/LYmktime.c b/src/LYmktime.c
new file mode 100644
index 0000000..5cc1dc4
--- /dev/null
+++ b/src/LYmktime.c
@@ -0,0 +1,364 @@
+/* $LynxId: LYmktime.c,v 1.20 2019/08/28 22:54:45 tom Exp $ */
+
+#include <LYStrings.h>
+#include <LYUtils.h>
+
+#include <parsdate.h>
+
+#ifdef TEST_DRIVER
+
+int ascii_toupper(int i)
+{
+ if (123 > i && i > 96)
+ return (i - 32);
+ else
+ return i;
+}
+
+char *LYstrncpy(char *dst,
+ const char *src,
+ int n)
+{
+ char *val;
+ int len;
+
+ if (src == 0)
+ src = "";
+ len = strlen(src);
+
+ if (n < 0)
+ n = 0;
+
+ val = StrNCpy(dst, src, n);
+ if (len < n)
+ *(dst + len) = '\0';
+ else
+ *(dst + n) = '\0';
+ return val;
+}
+#define strcasecomp strcasecmp
+BOOLEAN WWW_TraceFlag = FALSE;
+FILE *TraceFP(void)
+{
+ return stderr;
+}
+#define USE_PARSDATE 0
+#else
+#define USE_PARSDATE 1
+#endif
+
+/*
+ * This function takes a string in the format
+ * "Mon, 01-Jan-96 13:45:35 GMT" or
+ * "Mon, 1 Jan 1996 13:45:35 GMT" or
+ * "dd-mm-yyyy"
+ * as an argument, and returns its conversion to clock format (seconds since
+ * 00:00:00 Jan 1 1970), or 0 if the string doesn't match the expected pattern.
+ * It also returns 0 if the time is in the past and the "absolute" argument is
+ * FALSE. It is intended for handling 'expires' strings in Version 0 cookies
+ * homologously to 'max-age' strings in Version 1 cookies, for which 0 is the
+ * minimum, and greater values are handled as '[max-age seconds] + time(NULL)'.
+ * If "absolute" is TRUE, we return the clock format value itself, but if
+ * anything goes wrong when parsing the expected patterns, we still return 0.
+ * - FM
+ */
+time_t LYmktime(char *string,
+ int absolute)
+{
+#if USE_PARSDATE
+ time_t result = 0;
+
+ if (non_empty(string)) {
+ CTRACE((tfp, "LYmktime: Parsing '%s'\n", string));
+ if ((result = parsedate(string, 0)) == ((time_t) -1))
+ result = 0;
+
+ if (!absolute) {
+ time_t now = time((time_t *) NULL);
+
+ if (result < now)
+ result = 0;
+ }
+ if (result != 0) {
+ CTRACE((tfp, "LYmktime: clock=%" PRI_time_t ", ctime=%s",
+ CAST_time_t (result),
+ ctime(&result)));
+ }
+ }
+ return result;
+#else
+ char *s;
+ time_t clock2;
+ int day, month, year, hour, minutes, seconds;
+ char *start;
+ char temp[8];
+
+ /*
+ * Make sure we have a string to parse. - FM
+ */
+ if (!non_empty(string))
+ return (0);
+ s = string;
+ CTRACE((tfp, "LYmktime: Parsing '%s'\n", s));
+
+ /*
+ * Skip any lead alphabetic "Day, " field and seek a numeric day field. -
+ * FM
+ */
+ while (*s != '\0' && !isdigit(UCH(*s)))
+ s++;
+ if (*s == '\0')
+ return (0);
+
+ /*
+ * Get the numeric day and convert to an integer. - FM
+ */
+ start = s;
+ while (*s != '\0' && isdigit(UCH(*s)))
+ s++;
+ if (*s == '\0' || (s - start) > 2)
+ return (0);
+ LYStrNCpy(temp, start, (s - start));
+ day = atoi(temp);
+ if (day < 1 || day > 31)
+ return (0);
+
+ /*
+ * Get the month string and convert to an integer. - FM
+ */
+ while (*s != '\0' && !isalnum(UCH(*s)))
+ s++;
+ if (*s == '\0')
+ return (0);
+ start = s;
+ while (*s != '\0' && isalnum(UCH(*s)))
+ s++;
+ if ((*s == '\0') ||
+ (s - start) < (isdigit(UCH(*(s - 1))) ? 2 : 3) ||
+ (s - start) > (isdigit(UCH(*(s - 1))) ? 2 : 9))
+ return (0);
+ LYStrNCpy(temp, start, (isdigit(UCH(*(s - 1))) ? 2 : 3));
+ switch (TOUPPER(temp[0])) {
+ case '0':
+ case '1':
+ month = atoi(temp);
+ if (month < 1 || month > 12) {
+ return (0);
+ }
+ break;
+ case 'A':
+ if (!strcasecomp(temp, "Apr")) {
+ month = 4;
+ } else if (!strcasecomp(temp, "Aug")) {
+ month = 8;
+ } else {
+ return (0);
+ }
+ break;
+ case 'D':
+ if (!strcasecomp(temp, "Dec")) {
+ month = 12;
+ } else {
+ return (0);
+ }
+ break;
+ case 'F':
+ if (!strcasecomp(temp, "Feb")) {
+ month = 2;
+ } else {
+ return (0);
+ }
+ break;
+ case 'J':
+ if (!strcasecomp(temp, "Jan")) {
+ month = 1;
+ } else if (!strcasecomp(temp, "Jun")) {
+ month = 6;
+ } else if (!strcasecomp(temp, "Jul")) {
+ month = 7;
+ } else {
+ return (0);
+ }
+ break;
+ case 'M':
+ if (!strcasecomp(temp, "Mar")) {
+ month = 3;
+ } else if (!strcasecomp(temp, "May")) {
+ month = 5;
+ } else {
+ return (0);
+ }
+ break;
+ case 'N':
+ if (!strcasecomp(temp, "Nov")) {
+ month = 11;
+ } else {
+ return (0);
+ }
+ break;
+ case 'O':
+ if (!strcasecomp(temp, "Oct")) {
+ month = 10;
+ } else {
+ return (0);
+ }
+ break;
+ case 'S':
+ if (!strcasecomp(temp, "Sep")) {
+ month = 9;
+ } else {
+ return (0);
+ }
+ break;
+ default:
+ return (0);
+ }
+
+ /*
+ * Get the numeric year string and convert to an integer. - FM
+ */
+ while (*s != '\0' && !isdigit(UCH(*s)))
+ s++;
+ if (*s == '\0')
+ return (0);
+ start = s;
+ while (*s != '\0' && isdigit(UCH(*s)))
+ s++;
+ if ((s - start) == 4) {
+ LYStrNCpy(temp, start, 4);
+ } else if ((s - start) == 2) {
+ /*
+ * Assume that received 2-digit dates >= 70 are 19xx; others
+ * are 20xx. Only matters when dealing with broken software
+ * (HTTP server or web page) which is not Y2K compliant. The
+ * line is drawn on a best-guess basis; it is impossible for
+ * this to be completely accurate because it depends on what
+ * the broken sender software intends. (This totally breaks
+ * in 2100 -- setting up the next crisis...) - BL
+ */
+ if (atoi(start) >= 70)
+ LYStrNCpy(temp, "19", 2);
+ else
+ LYStrNCpy(temp, "20", 2);
+ strncat(temp, start, 2);
+ temp[4] = '\0';
+ } else {
+ return (0);
+ }
+ year = atoi(temp);
+
+ /*
+ * Get the numeric hour string and convert to an integer. - FM
+ */
+ while (*s != '\0' && !isdigit(UCH(*s)))
+ s++;
+ if (*s == '\0') {
+ hour = 0;
+ minutes = 0;
+ seconds = 0;
+ } else {
+ start = s;
+ while (*s != '\0' && isdigit(UCH(*s)))
+ s++;
+ if (*s != ':' || (s - start) > 2)
+ return (0);
+ LYStrNCpy(temp, start, (s - start));
+ hour = atoi(temp);
+
+ /*
+ * Get the numeric minutes string and convert to an integer. - FM
+ */
+ while (*s != '\0' && !isdigit(UCH(*s)))
+ s++;
+ if (*s == '\0')
+ return (0);
+ start = s;
+ while (*s != '\0' && isdigit(UCH(*s)))
+ s++;
+ if (*s != ':' || (s - start) > 2)
+ return (0);
+ LYStrNCpy(temp, start, (s - start));
+ minutes = atoi(temp);
+
+ /*
+ * Get the numeric seconds string and convert to an integer. - FM
+ */
+ while (*s != '\0' && !isdigit(UCH(*s)))
+ s++;
+ if (*s == '\0')
+ return (0);
+ start = s;
+ while (*s != '\0' && isdigit(UCH(*s)))
+ s++;
+ if (*s == '\0' || (s - start) > 2)
+ return (0);
+ LYStrNCpy(temp, start, (s - start));
+ seconds = atoi(temp);
+ }
+
+ /*
+ * Convert to clock format (seconds since 00:00:00 Jan 1 1970), but then
+ * zero it if it's in the past and "absolute" is not TRUE. - FM
+ */
+ month -= 3;
+ if (month < 0) {
+ month += 12;
+ year--;
+ }
+ day += (year - 1968) * 1461 / 4;
+ day += ((((month * 153) + 2) / 5) - 672);
+ clock2 = (time_t) ((day * 60 * 60 * 24) +
+ (hour * 60 * 60) +
+ (minutes * 60) +
+ seconds);
+ if (absolute == FALSE && (long) (time((time_t *) 0) - clock2) >= 0)
+ clock2 = (time_t) 0;
+ if (clock2 > 0)
+ CTRACE((tfp, "LYmktime: clock=%" PRI_time_t ", ctime=%s",
+ CAST_time_t (clock2),
+ ctime(&clock2)));
+
+ return (clock2);
+#endif
+}
+
+#ifdef TEST_DRIVER
+static void test_mktime(char *source)
+{
+ time_t before = LYmktime(source, TRUE);
+ time_t after = parsedate(source, 0);
+
+ printf("TEST %s\n", source);
+ printf("\t%" PRI_time_t " %s", CAST_time_t (before), ctime(&before));
+ printf("\t%" PRI_time_t " %s", CAST_time_t (after), ctime(&after));
+
+ if (before != after)
+ printf("\t****\n");
+}
+
+int main(void)
+{
+ test_mktime("Mon, 01-Jan-96 13:45:35 GMT");
+ test_mktime("Mon, 1 Jan 1996 13:45:35 GMT");
+ test_mktime("31-12-1999");
+ test_mktime("Wed May 14 22:00:00 2008");
+ test_mktime("Sun, 29-Jun-2008 23:19:30 GMT");
+ test_mktime("Sun Jul 06 07:00:00 2008 GMT");
+ test_mktime("Sun Jul 06 07:00:00 2018 GMT");
+ test_mktime("Sun Jul 06 07:00:00 2028 GMT");
+ test_mktime("Tue Jan 01 07:00:00 2036 GMT");
+ test_mktime("Thu Jan 01 07:00:00 2037 GMT");
+ /* problems with 32-bits */
+ test_mktime("Fri Jan 01 07:00:00 2038 GMT");
+ test_mktime("Sun Jul 06 07:00:00 2038 GMT");
+ test_mktime("Mon, 22-Aug-2039 15:13:56 GMT");
+ test_mktime("Sat, 28 Aug 2066 18:41:53 -0400");
+ test_mktime("Fri, 28 Aug 2099 18:41:53 -0400");
+ test_mktime("Sat, 28 Aug 2100 18:41:53 -0400");
+ test_mktime("Sun Jul 06 07:00:00 2138 GMT");
+ test_mktime("Sat, 28 Aug 2150 18:41:53 -0400");
+ test_mktime("Sat, 28 Aug 2200 18:41:53 -0400");
+ printf("DONE!\n");
+ return 0;
+}
+#endif
diff --git a/src/LYrcFile.c b/src/LYrcFile.c
new file mode 100644
index 0000000..b8cef37
--- /dev/null
+++ b/src/LYrcFile.c
@@ -0,0 +1,1140 @@
+/* $LynxId: LYrcFile.c,v 1.107 2022/04/02 00:12:18 Paul.G.Fox Exp $ */
+#include <HTUtils.h>
+#include <HTFTP.h>
+#include <LYUtils.h>
+#include <LYrcFile.h>
+#include <LYStrings.h>
+#include <LYGlobalDefs.h>
+#include <LYCharSets.h>
+#include <LYBookmark.h>
+#include <LYCookie.h>
+#include <LYKeymap.h>
+#include <HTMLDTD.h>
+
+#include <LYLeaks.h>
+
+#define MSG_ENABLE_LYNXRC N_("Normally disabled. See ENABLE_LYNXRC in lynx.cfg\n")
+#define putBool(value) ((value) ? "on" : "off")
+/* *INDENT-OFF* */
+static Config_Enum tbl_DTD_recovery[] = {
+ { "true", TRUE },
+ { "false", FALSE },
+ { "on", TRUE },
+ { "off", FALSE },
+ { "sortasgml", TRUE },
+ { "tagsoup", FALSE },
+ { NULL, -1 },
+};
+
+static Config_Enum tbl_HTTP_protocol[] = {
+ { "1.0", HTTP_1_0 },
+ { "1.1", HTTP_1_1 },
+ { NULL, -1 },
+};
+
+static Config_Enum tbl_bad_html[] = {
+ { "ignore", BAD_HTML_IGNORE },
+ { "trace", BAD_HTML_TRACE },
+ { "message", BAD_HTML_MESSAGE },
+ { "warn", BAD_HTML_WARN },
+ { NULL, -1 }
+};
+
+#ifdef DIRED_SUPPORT
+static Config_Enum tbl_dir_list_style[] = {
+ { "FILES_FIRST", FILES_FIRST },
+ { "DIRECTORIES_FIRST", DIRS_FIRST },
+ { "MIXED_STYLE", MIXED_STYLE },
+ { NULL, MIXED_STYLE },
+};
+#ifdef LONG_LIST
+static Config_Enum tbl_dir_list_order[] = {
+ { "ORDER_BY_NAME", ORDER_BY_NAME },
+ { "ORDER_BY_TYPE", ORDER_BY_TYPE },
+ { "ORDER_BY_SIZE", ORDER_BY_SIZE },
+ { "ORDER_BY_DATE", ORDER_BY_DATE },
+ { "ORDER_BY_MODE", ORDER_BY_MODE },
+#ifndef NO_GROUPS
+ { "ORDER_BY_USER", ORDER_BY_USER },
+ { "ORDER_BY_GROUP", ORDER_BY_GROUP },
+#endif
+ { NULL, ORDER_BY_NAME },
+};
+#endif /* LONG_LIST */
+#endif /* DIRED_SUPPORT */
+
+static Config_Enum tbl_file_sort[] = {
+ { "BY_FILENAME", FILE_BY_NAME },
+ { "BY_TYPE", FILE_BY_TYPE },
+ { "BY_SIZE", FILE_BY_SIZE },
+ { "BY_DATE", FILE_BY_DATE },
+ { NULL, -1 },
+};
+
+#ifdef USE_IDN2
+static Config_Enum tbl_idna_mode[] = {
+ { "IDNA2003", LYidna2003 },
+ { "IDNA2008", LYidna2008 },
+ { "TR46", LYidnaTR46 },
+ { "Compatible", LYidnaCompat },
+ { NULL, -1 },
+};
+#endif
+
+Config_Enum tbl_keypad_mode[] = {
+ { "FIELDS_ARE_NUMBERED", FIELDS_ARE_NUMBERED },
+ { "LINKS_AND_FIELDS_ARE_NUMBERED", LINKS_AND_FIELDS_ARE_NUMBERED },
+ { "LINKS_ARE_NUMBERED", LINKS_ARE_NUMBERED },
+ { "LINKS_ARE_NOT_NUMBERED", NUMBERS_AS_ARROWS },
+ /* obsolete variations: */
+ { "LINKS_AND_FORM_FIELDS_ARE_NUMBERED", LINKS_AND_FIELDS_ARE_NUMBERED },
+ { "NUMBERS_AS_ARROWS", NUMBERS_AS_ARROWS },
+ { NULL, DEFAULT_KEYPAD_MODE }
+};
+
+Config_Enum tbl_multi_bookmarks[] = {
+ { "OFF", MBM_OFF },
+ { "STANDARD", MBM_STANDARD },
+ { "ON", MBM_STANDARD },
+ { "ADVANCED", MBM_ADVANCED },
+ { NULL, -1 }
+};
+
+Config_Enum tbl_preferred_content[] = {
+ { STR_BINARY, contentBINARY },
+ { STR_PLAINTEXT, contentTEXT },
+ { STR_HTML, contentHTML },
+ { NULL, -1 }
+};
+
+/* the names in this table are used as lowercase in HTTP.c */
+Config_Enum tbl_preferred_encoding[] = {
+ { "none", encodingNONE },
+#if defined(USE_ZLIB) || defined(GZIP_PATH)
+ { "gzip", encodingGZIP },
+ { "deflate", encodingDEFLATE },
+#endif
+#if defined(USE_ZLIB) || defined(COMPRESS_PATH)
+ { "compress", encodingCOMPRESS },
+#endif
+#if defined(USE_BZLIB) || defined(BZIP2_PATH)
+ { "bzip2", encodingBZIP2 },
+#endif
+#if defined(USE_BROTLI) || defined(BROTLI_PATH)
+ { "br", encodingBROTLI },
+#endif
+ { "all", encodingALL },
+ { NULL, -1 }
+};
+
+Config_Enum tbl_preferred_media[] = {
+ { "INTERNAL", mediaOpt1 },
+ { "CONFIGFILE", mediaOpt2 },
+ { "USER", mediaOpt3 },
+ { "SYSTEM", mediaOpt4 },
+ { "ALL", mediaALL },
+ { NULL, -1 }
+};
+
+static Config_Enum tbl_show_colors[] = {
+ { "default", SHOW_COLOR_UNKNOWN },
+ { "default", SHOW_COLOR_OFF },
+ { "default", SHOW_COLOR_ON },
+ { "on", SHOW_COLOR_UNKNOWN },
+ { "off", SHOW_COLOR_UNKNOWN },
+ { "never", SHOW_COLOR_NEVER },
+ { "always", SHOW_COLOR_ALWAYS },
+ { NULL, SHOW_COLOR_UNKNOWN }
+};
+
+Config_Enum tbl_transfer_rate[] = {
+ { "NONE", rateOFF },
+ { "KB", rateKB },
+ { "TRUE", rateKB },
+ { "BYTES", rateBYTES },
+ { "FALSE", rateBYTES },
+#ifdef USE_READPROGRESS
+ { "KB,ETA", rateEtaKB },
+ { "BYTES,ETA", rateEtaBYTES },
+ { "KB2,ETA", rateEtaKB2 },
+ { "BYTES2,ETA", rateEtaBYTES2 },
+#endif
+#ifdef USE_PROGRESSBAR
+ { "METER", rateBAR },
+ { "FALSE", rateBAR },
+#endif
+ { NULL, -1 },
+};
+
+Config_Enum tbl_user_mode[] = {
+ { "MINIMAL", MINIMAL_MODE },
+ { "ADVANCED", ADVANCED_MODE },
+ { "INTERMEDIATE", INTERMEDIATE_MODE },
+ { "NOVICE", NOVICE_MODE },
+ { NULL, NOVICE_MODE }
+};
+
+static Config_Enum tbl_visited_links[] = {
+ { "FIRST_REVERSED", VISITED_LINKS_AS_FIRST_V | VISITED_LINKS_REVERSE },
+ { "FIRST", VISITED_LINKS_AS_FIRST_V },
+ { "TREE", VISITED_LINKS_AS_TREE },
+ { "LAST_REVERSED", VISITED_LINKS_AS_LATEST | VISITED_LINKS_REVERSE },
+ { "LAST", VISITED_LINKS_AS_LATEST },
+ { NULL, DEFAULT_VISITED_LINKS }
+};
+
+Config_Enum tbl_cookie_version[] = {
+ { "RFC-2109", COOKIES_RFC_2109 },
+ { "RFC-2965", COOKIES_RFC_2965 },
+ { "RFC-6265", COOKIES_RFC_6265 },
+ { NULL, -1 }
+};
+
+Config_Enum tbl_force_prompt[] = {
+ { "prompt", FORCE_PROMPT_DFT },
+ { "yes", FORCE_PROMPT_YES },
+ { "no", FORCE_PROMPT_NO },
+ { NULL, -1 }
+};
+/* *INDENT-ON* */
+
+static BOOL getBool(char *src)
+{
+ return (BOOL) (!strncasecomp(src, "on", 2) || !strncasecomp(src, "true", 4));
+}
+
+const char *LYputEnum(Config_Enum * table, int value)
+{
+ while (table->name != 0) {
+ if (table->value == value) {
+ return table->name;
+ }
+ table++;
+ }
+ return "?";
+}
+
+BOOL LYgetEnum(Config_Enum * table, const char *name,
+ int *result)
+{
+ Config_Enum *found = 0;
+ unsigned len = (unsigned) strlen(name);
+ int match = 0;
+
+ if (len != 0) {
+ while (table->name != 0) {
+ if (!strncasecomp(table->name, name, (int) len)) {
+ found = table;
+ if (!strcasecomp(table->name, name)) {
+ match = 1;
+ break;
+ }
+ ++match;
+ }
+ table++;
+ }
+ if (match == 1) { /* if unambiguous */
+ *result = found->value;
+ return TRUE;
+ }
+ }
+ CTRACE((tfp, "LYgetEnum: no match found for \"%s\"\n", name));
+ return FALSE; /* no match */
+}
+
+/* these are for data that are normally not read/written from .lynxrc */
+#define PARSE_SET(n,v,h) {n, 1, CONF_BOOL, UNION_SET(v), 0, 0, 0, h}
+#define PARSE_ARY(n,v,t,h) {n, 1, CONF_ARRAY, UNION_INT(v), t, 0, 0, h}
+#define PARSE_ENU(n,v,t,h) {n, 1, CONF_ENUM, UNION_INT(v), 0, t, 0, h}
+#define PARSE_LIS(n,v,h) {n, 1, CONF_LIS, UNION_STR(v), 0, 0, 0, h}
+#define PARSE_STR(n,v,h) {n, 1, CONF_STR, UNION_STR(v), 0, 0, 0, h}
+#define PARSE_FUN(n,v,w,h) {n, 1, CONF_FUN, UNION_FUN(v), 0, 0, w, h}
+#define PARSE_MBM(n,h) {n, 1, CONF_MBM, UNION_DEF(0), 0, 0, 0, h}
+
+/* these are for data that are optionally read/written from .lynxrc */
+#define MAYBE_SET(n,v,h) {n, 0, CONF_BOOL, UNION_SET(v), 0, 0, 0, h}
+#define MAYBE_ARY(n,v,t,h) {n, 0, CONF_ARRAY, UNION_INT(v), t, 0, 0, h}
+#define MAYBE_ENU(n,v,t,h) {n, 0, CONF_ENUM, UNION_INT(v), 0, t, 0, h}
+#define MAYBE_LIS(n,v,h) {n, 0, CONF_LIS, UNION_STR(v), 0, 0, 0, h}
+#define MAYBE_STR(n,v,h) {n, 0, CONF_STR, UNION_STR(v), 0, 0, 0, h}
+#define MAYBE_FUN(n,v,w,h) {n, 0, CONF_FUN, UNION_FUN(v), 0, 0, w, h}
+#define MAYBE_MBM(n,h) {n, 0, CONF_MBM, UNION_DEF(0), 0, 0, 0, h}
+
+#define PARSE_NIL {NULL, 1, CONF_NIL, UNION_DEF(0), 0, 0, 0, 0}
+
+typedef enum {
+ CONF_NIL = 0
+ ,CONF_ARRAY
+ ,CONF_BOOL
+ ,CONF_FUN
+ ,CONF_INT
+ ,CONF_ENUM
+ ,CONF_LIS
+ ,CONF_MBM
+ ,CONF_STR
+} Conf_Types;
+
+typedef struct config_type {
+ const char *name;
+ int enabled; /* see lynx.cfg ENABLE_LYNXRC "off" lines */
+ Conf_Types type;
+ ParseData;
+ const char **strings;
+ Config_Enum *table;
+ void (*write_it) (FILE *fp, struct config_type *);
+ const char *note;
+} Config_Type;
+
+static int get_assume_charset(char *value)
+{
+ int i;
+
+ for (i = 0; i < LYNumCharsets; ++i) {
+ if (!strcasecomp(value, LYCharSet_UC[i].MIMEname)) {
+ UCLYhndl_for_unspec = i;
+ break;
+ }
+ }
+ return 0;
+}
+
+static void put_assume_charset(FILE *fp, struct config_type *tbl)
+{
+ int i;
+
+ for (i = 0; i < LYNumCharsets; ++i)
+ fprintf(fp, "# %s\n", LYCharSet_UC[i].MIMEname);
+ fprintf(fp, "%s=%s\n\n", tbl->name, LYCharSet_UC[UCLYhndl_for_unspec].MIMEname);
+}
+
+static int get_display_charset(char *value)
+{
+ int i = 0;
+
+ i = UCGetLYhndl_byAnyName(value); /* by MIME or full name */
+ if (i >= 0)
+ current_char_set = i;
+ return 0;
+}
+
+static void put_display_charset(FILE *fp, struct config_type *tbl)
+{
+ int i;
+
+ for (i = 0; LYchar_set_names[i]; i++)
+ fprintf(fp, "# %s\n", LYchar_set_names[i]);
+ fprintf(fp, "%s=%s\n\n", tbl->name, LYchar_set_names[current_char_set]);
+}
+
+static int get_editor(char *value)
+{
+ if (!system_editor)
+ StrAllocCopy(editor, value);
+ return 0;
+}
+
+static void put_editor(FILE *fp, struct config_type *tbl)
+{
+ fprintf(fp, "%s=%s\n\n", tbl->name, NonNull(editor));
+}
+
+int get_http_protocol(char *value)
+{
+ int found = HTprotocolLevel;
+
+ if (LYgetEnum(tbl_HTTP_protocol, value, &found)
+ && HTprotocolLevel != found) {
+ HTprotocolLevel = found;
+ }
+ return 0;
+}
+
+static void put_http_protocol(FILE *fp, struct config_type *tbl)
+{
+ fprintf(fp, "%s=%s\n\n", tbl->name, LYputEnum(tbl_HTTP_protocol, HTprotocolLevel));
+}
+
+int get_tagsoup(char *value)
+{
+ int found = Old_DTD;
+
+ if (LYgetEnum(tbl_DTD_recovery, value, &found)
+ && Old_DTD != found) {
+ Old_DTD = found;
+ HTSwitchDTD(!Old_DTD);
+ }
+ return 0;
+}
+
+static void put_tagsoup(FILE *fp, struct config_type *tbl)
+{
+ fprintf(fp, "%s=%s\n\n", tbl->name, LYputEnum(tbl_DTD_recovery, Old_DTD));
+}
+
+/* This table is searched ignoring case */
+/* *INDENT-OFF* */
+static Config_Type Config_Table [] =
+{
+ PARSE_SET(RC_ACCEPT_ALL_COOKIES, LYAcceptAllCookies, N_("\
+accept_all_cookies allows the user to tell Lynx to automatically\n\
+accept all cookies if desired. The default is \"FALSE\" which will\n\
+prompt for each cookie. Set accept_all_cookies to \"TRUE\" to accept\n\
+all cookies.\n\
+")),
+ MAYBE_FUN(RC_ASSUME_CHARSET, get_assume_charset, put_assume_charset, MSG_ENABLE_LYNXRC),
+#ifndef DISABLE_FTP
+ PARSE_STR(RC_ANONFTP_PASSWORD, anonftp_password, N_("\
+anonftp_password allows the user to tell Lynx to use the personal\n\
+email address as the password for anonymous ftp. If no value is given,\n\
+Lynx will use the personal email address. Set anonftp_password\n\
+to a different value if you choose.\n\
+")),
+#endif
+ MAYBE_ENU(RC_BAD_HTML, cfg_bad_html, tbl_bad_html,
+ MSG_ENABLE_LYNXRC),
+ PARSE_STR(RC_BOOKMARK_FILE, bookmark_page, N_("\
+bookmark_file specifies the name and location of the default bookmark\n\
+file into which the user can paste links for easy access at a later\n\
+date.\n\
+")),
+ PARSE_SET(RC_CASE_SENSITIVE_SEARCHING, LYcase_sensitive, N_("\
+If case_sensitive_searching is \"on\" then when the user invokes a search\n\
+using the 's' or '/' keys, the search performed will be case sensitive\n\
+instead of case INsensitive. The default is usually \"off\".\n\
+")),
+ PARSE_FUN(RC_CHARACTER_SET, get_display_charset, put_display_charset, N_("\
+The character_set definition controls the representation of 8 bit\n\
+characters for your terminal. If 8 bit characters do not show up\n\
+correctly on your screen you may try changing to a different 8 bit\n\
+set or using the 7 bit character approximations.\n\
+Current valid characters sets are:\n\
+")),
+ MAYBE_SET(RC_COLLAPSE_BR_TAGS, LYCollapseBRs, MSG_ENABLE_LYNXRC),
+ PARSE_LIS(RC_COOKIE_ACCEPT_DOMAINS, LYCookieAcceptDomains, N_("\
+cookie_accept_domains and cookie_reject_domains are comma-delimited\n\
+lists of domains from which Lynx should automatically accept or reject\n\
+all cookies. If a domain is specified in both options, rejection will\n\
+take precedence. The accept_all_cookies parameter will override any\n\
+settings made here.\n\
+")),
+#ifdef USE_PERSISTENT_COOKIES
+ PARSE_STR(RC_COOKIE_FILE, LYCookieFile, N_("\
+cookie_file specifies the file from which to read persistent cookies.\n\
+The default is ~/" FNAME_LYNX_COOKIES ".\n\
+")),
+#endif
+ PARSE_STR(RC_COOKIE_LOOSE_INVALID_DOMAINS, LYCookieLooseCheckDomains, N_("\
+cookie_loose_invalid_domains, cookie_strict_invalid_domains, and\n\
+cookie_query_invalid_domains are comma-delimited lists of which domains\n\
+should be subjected to varying degrees of validity checking. If a\n\
+domain is set to strict checking, strict conformance to RFC2109 will\n\
+be applied. A domain with loose checking will be allowed to set cookies\n\
+with an invalid path or domain attribute. All domains will default to\n\
+querying the user for an invalid path or domain.\n\
+")),
+ PARSE_STR(RC_COOKIE_QUERY_INVALID_DOMAINS, LYCookieQueryCheckDomains, NULL),
+ PARSE_LIS(RC_COOKIE_REJECT_DOMAINS, LYCookieRejectDomains, NULL),
+ PARSE_STR(RC_COOKIE_STRICT_INVALID_DOMAIN, LYCookieStrictCheckDomains, NULL),
+#ifdef DIRED_SUPPORT
+#ifdef LONG_LIST
+ PARSE_ENU(RC_DIR_LIST_ORDER, dir_list_order, tbl_dir_list_order, N_("\
+dir_list_order specifies the directory list order under DIRED_SUPPORT\n\
+(if implemented). The default is \"ORDER_BY_NAME\"\n\
+")),
+#endif
+ PARSE_ENU(RC_DIR_LIST_STYLE, dir_list_style, tbl_dir_list_style, N_("\
+dir_list_styles specifies the directory list style under DIRED_SUPPORT\n\
+(if implemented). The default is \"MIXED_STYLE\", which sorts both\n\
+files and directories together. \"FILES_FIRST\" lists files first and\n\
+\"DIRECTORIES_FIRST\" lists directories first.\n\
+")),
+#endif
+ MAYBE_STR(RC_DISPLAY, x_display, MSG_ENABLE_LYNXRC),
+ PARSE_SET(RC_EMACS_KEYS, emacs_keys, N_("\
+If emacs_keys is to \"on\" then the normal EMACS movement keys:\n\
+ ^N = down ^P = up\n\
+ ^B = left ^F = right\n\
+will be enabled.\n\
+")),
+ PARSE_FUN(RC_FILE_EDITOR, get_editor, put_editor, N_("\
+file_editor specifies the editor to be invoked when editing local files\n\
+or sending mail. If no editor is specified, then file editing is disabled\n\
+unless it is activated from the command line, and the built-in line editor\n\
+will be used for sending mail.\n\
+")),
+#ifndef DISABLE_FTP
+ PARSE_ENU(RC_FILE_SORTING_METHOD, HTfileSortMethod, tbl_file_sort, N_("\
+The file_sorting_method specifies which value to sort on when viewing\n\
+file lists such as FTP directories. The options are:\n\
+ BY_FILENAME -- sorts on the name of the file\n\
+ BY_TYPE -- sorts on the type of the file\n\
+ BY_SIZE -- sorts on the size of the file\n\
+ BY_DATE -- sorts on the date of the file\n\
+")),
+#endif
+ MAYBE_ENU(RC_FORCE_COOKIE_PROMPT, cookie_noprompt, tbl_force_prompt,
+ MSG_ENABLE_LYNXRC),
+ MAYBE_ENU(RC_COOKIE_VERSION, cookie_version, tbl_cookie_version,
+ MSG_ENABLE_LYNXRC),
+#ifdef USE_SSL
+ MAYBE_ENU(RC_FORCE_SSL_PROMPT, ssl_noprompt, tbl_force_prompt,
+ MSG_ENABLE_LYNXRC),
+#endif
+#ifndef DISABLE_FTP
+ MAYBE_SET(RC_FTP_PASSIVE, ftp_passive, MSG_ENABLE_LYNXRC),
+#endif
+ MAYBE_SET(RC_HTML5_CHARSETS, html5_charsets, MSG_ENABLE_LYNXRC),
+ MAYBE_FUN(RC_HTTP_PROTOCOL, get_http_protocol, put_http_protocol,
+ MSG_ENABLE_LYNXRC),
+#ifdef USE_IDN2
+ MAYBE_ENU(RC_IDNA_MODE, LYidnaMode, tbl_idna_mode,
+ MSG_ENABLE_LYNXRC),
+#endif
+#ifdef EXP_KEYBOARD_LAYOUT
+ PARSE_ARY(RC_KBLAYOUT, current_layout, LYKbLayoutNames, NULL),
+#endif
+ PARSE_ENU(RC_KEYPAD_MODE, keypad_mode, tbl_keypad_mode, NULL),
+ PARSE_ARY(RC_LINEEDIT_MODE, current_lineedit, LYEditorNames, N_("\
+lineedit_mode specifies the key binding used for inputting strings in\n\
+prompts and forms. If lineedit_mode is set to \"Default Binding\" then\n\
+the following control characters are used for moving and deleting:\n\
+\n\
+ Prev Next Enter = Accept input\n\
+ Move char: <- -> ^G = Cancel input\n\
+ Move word: ^P ^N ^U = Erase line\n\
+ Delete char: ^H ^R ^A = Beginning of line\n\
+ Delete word: ^B ^F ^E = End of line\n\
+\n\
+Current lineedit modes are:\n\
+")),
+#ifdef USE_LOCALE_CHARSET
+ MAYBE_SET(RC_LOCALE_CHARSET, LYLocaleCharset, MSG_ENABLE_LYNXRC),
+#endif
+ MAYBE_SET(RC_MAKE_PSEUDO_ALTS_FOR_INLINES, pseudo_inline_alts, MSG_ENABLE_LYNXRC),
+ MAYBE_SET(RC_MAKE_LINKS_FOR_ALL_IMAGES, clickable_images, MSG_ENABLE_LYNXRC),
+ PARSE_MBM(RC_MULTI_BOOKMARK, N_("\
+The following allow you to define sub-bookmark files and descriptions.\n\
+The format is multi_bookmark<capital_letter>=<filename>,<description>\n\
+Up to 26 bookmark files (for the English capital letters) are allowed.\n\
+We start with \"multi_bookmarkB\" since 'A' is the default (see above).\n\
+")),
+ PARSE_STR(RC_PERSONAL_MAIL_ADDRESS, personal_mail_address, N_("\
+personal_mail_address specifies your personal mail address. The\n\
+address will be sent during HTTP file transfers for authorization and\n\
+logging purposes, and for mailed comments.\n\
+If you do not want this information given out, set the NO_FROM_HEADER\n\
+to TRUE in lynx.cfg, or use the -nofrom command line switch. You also\n\
+could leave this field blank, but then you won't have it included in\n\
+your mailed comments.\n\
+")),
+ PARSE_STR(RC_PERSONAL_MAIL_NAME, personal_mail_name, N_("\
+personal_mail_name specifies your personal name, for mail. The\n\
+name is sent for mailed comments. Lynx will prompt for this,\n\
+showing the configured value as a default when sending mail.\n\
+This is not necessarily the same as a name provided as part of the\n\
+personal_mail_address.\n\
+Lynx does not save your changes to that default value as a side-effect\n\
+of sending email. To update the default value, you must use the options\n\
+menu, or modify this file directly.\n\
+")),
+ PARSE_STR(RC_PREFERRED_CHARSET, pref_charset, N_("\
+preferred_charset specifies the character set in MIME notation (e.g.,\n\
+ISO-8859-2, ISO-8859-5) which Lynx will indicate you prefer in requests\n\
+to http servers using an Accept-Charset header. The value should NOT\n\
+include ISO-8859-1 or US-ASCII, since those values are always assumed\n\
+by default. May be a comma-separated list.\n\
+If a file in that character set is available, the server will send it.\n\
+If no Accept-Charset header is present, the default is that any\n\
+character set is acceptable. If an Accept-Charset header is present,\n\
+and if the server cannot send a response which is acceptable\n\
+according to the Accept-Charset header, then the server SHOULD send\n\
+an error response, though the sending of an unacceptable response\n\
+is also allowed.\n\
+")),
+ MAYBE_ENU(RC_PREFERRED_CONTENT_TYPE, LYContentType, tbl_preferred_content,
+ MSG_ENABLE_LYNXRC),
+ MAYBE_ENU(RC_PREFERRED_ENCODING, LYAcceptEncoding, tbl_preferred_encoding,
+ MSG_ENABLE_LYNXRC),
+ PARSE_STR(RC_PREFERRED_LANGUAGE, language, N_("\
+preferred_language specifies the language in MIME notation (e.g., en,\n\
+fr, may be a comma-separated list in decreasing preference)\n\
+which Lynx will indicate you prefer in requests to http servers.\n\
+If a file in that language is available, the server will send it.\n\
+Otherwise, the server will send the file in its default language.\n\
+")),
+ MAYBE_ENU(RC_PREFERRED_MEDIA_TYPES, LYAcceptMedia, tbl_preferred_media,
+ MSG_ENABLE_LYNXRC),
+ MAYBE_SET(RC_RAW_MODE, LYRawMode, MSG_ENABLE_LYNXRC),
+#if defined(ENABLE_OPTS_CHANGE_EXEC) && (defined(EXEC_LINKS) || defined(EXEC_SCRIPTS))
+ PARSE_SET(RC_RUN_ALL_EXECUTION_LINKS, local_exec, N_("\
+If run_all_execution_links is set \"on\" then all local execution links\n\
+will be executed when they are selected.\n\
+\n\
+WARNING - This is potentially VERY dangerous. Since you may view\n\
+ information that is written by unknown and untrusted sources\n\
+ there exists the possibility that Trojan horse links could be\n\
+ written. Trojan horse links could be written to erase files\n\
+ or compromise security. This should only be set to \"on\" if\n\
+ you are viewing trusted source information.\n\
+")),
+ PARSE_SET(RC_RUN_EXECUTION_LINKS_LOCAL, local_exec_on_local_files, N_("\
+If run_execution_links_on_local_files is set \"on\" then all local\n\
+execution links that are found in LOCAL files will be executed when they\n\
+are selected. This is different from run_all_execution_links in that\n\
+only files that reside on the local system will have execution link\n\
+permissions.\n\
+\n\
+WARNING - This is potentially dangerous. Since you may view\n\
+ information that is written by unknown and untrusted sources\n\
+ there exists the possibility that Trojan horse links could be\n\
+ written. Trojan horse links could be written to erase files\n\
+ or compromise security. This should only be set to \"on\" if\n\
+ you are viewing trusted source information.\n\
+")),
+#endif
+#ifdef USE_SCROLLBAR
+ MAYBE_SET(RC_SCROLLBAR, LYShowScrollbar, MSG_ENABLE_LYNXRC),
+#endif
+ PARSE_SET(RC_SELECT_POPUPS, LYSelectPopups, N_("\
+select_popups specifies whether the OPTIONs in a SELECT block which\n\
+lacks a MULTIPLE attribute are presented as a vertical list of radio\n\
+buttons or via a popup menu. Note that if the MULTIPLE attribute is\n\
+present in the SELECT start tag, Lynx always will create a vertical list\n\
+of checkboxes for the OPTIONs. A value of \"on\" will set popup menus\n\
+as the default while a value of \"off\" will set use of radio boxes.\n\
+The default can be overridden via the -popup command line toggle.\n\
+")),
+ MAYBE_SET(RC_SEND_USERAGENT, LYSendUserAgent, MSG_ENABLE_LYNXRC),
+ MAYBE_SET(RC_SET_COOKIES, LYSetCookies, MSG_ENABLE_LYNXRC),
+ PARSE_ENU(RC_SHOW_COLOR, LYrcShowColor, tbl_show_colors, N_("\
+show_color specifies how to set the color mode at startup. A value of\n\
+\"never\" will force color mode off (treat the terminal as monochrome)\n\
+at startup even if the terminal appears to be color capable. A value of\n\
+\"always\" will force color mode on even if the terminal appears to be\n\
+monochrome, if this is supported by the library used to build lynx.\n\
+A value of \"default\" will yield the behavior of assuming\n\
+a monochrome terminal unless color capability is inferred at startup\n\
+based on the terminal type, or the -color command line switch is used, or\n\
+the COLORTERM environment variable is set. The default behavior always is\n\
+used in anonymous accounts or if the \"option_save\" restriction is set.\n\
+The effect of the saved value can be overridden via\n\
+the -color and -nocolor command line switches.\n\
+The mode set at startup can be changed via the \"show color\" option in\n\
+the 'o'ptions menu. If the option settings are saved, the \"on\" and\n\
+\"off\" \"show color\" settings will be treated as \"default\".\n\
+")),
+ PARSE_SET(RC_SHOW_CURSOR, LYShowCursor, N_("\
+show_cursor specifies whether to 'hide' the cursor to the right (and\n\
+bottom, if possible) of the screen, or to place it to the left of the\n\
+current link in documents, or current option in select popup windows.\n\
+Positioning the cursor to the left of the current link or option is\n\
+helpful for speech or braille interfaces, and when the terminal is\n\
+one which does not distinguish the current link based on highlighting\n\
+or color. A value of \"on\" will set positioning to the left as the\n\
+default while a value of \"off\" will set 'hiding' of the cursor.\n\
+The default can be overridden via the -show_cursor command line toggle.\n\
+")),
+ PARSE_SET(RC_SHOW_DOTFILES, show_dotfiles, N_("\
+show_dotfiles specifies that the directory listing should include\n\
+\"hidden\" (dot) files/directories. If set \"on\", this will be\n\
+honored only if enabled via userdefs.h and/or lynx.cfg, and not\n\
+restricted via a command line switch. If display of hidden files\n\
+is disabled, creation of such files via Lynx also is disabled.\n\
+")),
+#ifdef USE_READPROGRESS
+ MAYBE_ENU(RC_SHOW_KB_RATE, LYTransferRate, tbl_transfer_rate,
+ MSG_ENABLE_LYNXRC),
+#endif
+ PARSE_ENU(RC_SUB_BOOKMARKS, LYMultiBookmarks, tbl_multi_bookmarks, N_("\
+If sub_bookmarks is not turned \"off\", and multiple bookmarks have\n\
+been defined (see below), then all bookmark operations will first\n\
+prompt the user to select an active sub-bookmark file. If the default\n\
+Lynx bookmark_file is defined (see above), it will be used as the\n\
+default selection. When this option is set to \"advanced\", and the\n\
+user mode is advanced, the 'v'iew bookmark command will invoke a\n\
+statusline prompt instead of the menu seen in novice and intermediate\n\
+user modes. When this option is set to \"standard\", the menu will be\n\
+presented regardless of user mode.\n\
+")),
+ MAYBE_FUN(RC_TAGSOUP, get_tagsoup, put_tagsoup,
+ MSG_ENABLE_LYNXRC),
+ MAYBE_SET(RC_TRIM_BLANK_LINES, LYtrimBlankLines, MSG_ENABLE_LYNXRC),
+ MAYBE_SET(RC_UNDERLINE_LINKS, LYUnderlineLinks, MSG_ENABLE_LYNXRC),
+ PARSE_ENU(RC_USER_MODE, user_mode, tbl_user_mode, N_("\
+user_mode specifies the users level of knowledge with Lynx. The\n\
+default is \"NOVICE\" which displays two extra lines of help at the\n\
+bottom of the screen to aid the user in learning the basic Lynx\n\
+commands. Set user_mode to \"INTERMEDIATE\" to turn off the extra info.\n\
+Use \"ADVANCED\" to see the URL of the currently selected link at the\n\
+bottom of the screen.\n\
+")),
+ MAYBE_STR(RC_USERAGENT, LYUserAgent, MSG_ENABLE_LYNXRC),
+ PARSE_SET(RC_VERBOSE_IMAGES, verbose_img, N_("\
+If verbose_images is \"on\", lynx will print the name of the image\n\
+source file in place of [INLINE], [LINK] or [IMAGE]\n\
+See also VERBOSE_IMAGES in lynx.cfg\n\
+")),
+ PARSE_SET(RC_VI_KEYS, vi_keys, N_("\
+If vi_keys is set to \"on\", then the normal VI movement keys:\n\
+ j = down k = up\n\
+ h = left l = right\n\
+will be enabled. These keys are only lower case.\n\
+Capital 'H', 'J' and 'K will still activate help, jump shortcuts,\n\
+and the keymap display, respectively.\n\
+")),
+ PARSE_ENU(RC_VISITED_LINKS, Visited_Links_As, tbl_visited_links, N_("\
+The visited_links setting controls how Lynx organizes the information\n\
+in the Visited Links Page.\n\
+")),
+#ifdef USE_SESSIONS
+ MAYBE_SET(RC_AUTO_SESSION, LYAutoSession, MSG_ENABLE_LYNXRC),
+ MAYBE_STR(RC_SESSION_FILE, LYSessionFile, MSG_ENABLE_LYNXRC),
+#endif
+ MAYBE_SET(RC_NO_PAUSE, no_pause, MSG_ENABLE_LYNXRC),
+
+ PARSE_NIL
+};
+/* *INDENT-ON* */
+
+static Config_Type *lookup_config(const char *name)
+{
+ Config_Type *tbl = Config_Table;
+ char ch = (char) TOUPPER(*name);
+
+ while (tbl->name != 0) {
+ if (tbl->enabled) {
+ char ch1 = tbl->name[0];
+
+ if ((ch == TOUPPER(ch1))
+ && (0 == strcasecomp(name, tbl->name)))
+ break;
+ }
+
+ tbl++;
+ }
+ return tbl;
+}
+
+BOOL LYsetRcValue(const char *name, const char *param)
+{
+ char MBM_line[256];
+ char *notes;
+ int n;
+ Config_Type *tbl;
+ ParseUnionPtr q;
+ BOOL changed = TRUE;
+ char *value = NULL;
+ char *orig_value = NULL;
+
+ if (param == NULL)
+ param = "";
+ StrAllocCopy(value, param);
+ orig_value = value;
+ value = LYSkipBlanks(value);
+ CTRACE2(TRACE_CFG, (tfp, "LYrcFile %s:%s\n", name, value));
+
+ tbl = lookup_config(name);
+ if (tbl->name == 0) {
+ const char *special = RC_MULTI_BOOKMARK;
+
+ if (!strncasecomp(name, special, (int) strlen(special))) {
+ tbl = lookup_config(special);
+ }
+ /*
+ * lynx ignores unknown keywords.
+ * This includes known keywords where there is no ENABLE_LYNXRC.
+ */
+ if (tbl->name == 0) {
+ CTRACE((tfp, "LYrcFile: ignored %s=%s\n", name, value));
+ FREE(orig_value);
+ return FALSE;
+ }
+ }
+
+ q = ParseUnionOf(tbl);
+ switch (tbl->type) {
+ case CONF_BOOL:
+ if (q->set_value != 0)
+ *(q->set_value) = getBool(value);
+ break;
+
+ case CONF_FUN:
+ if (q->fun_value != 0)
+ (*(q->fun_value)) (value);
+ break;
+
+ case CONF_ARRAY:
+ for (n = 0; tbl->strings[n] != 0; ++n) {
+ if (!strcasecomp(value, tbl->strings[n])) {
+ *(q->int_value) = n;
+ break;
+ }
+ }
+ break;
+
+ case CONF_ENUM:
+ if (tbl->table != 0)
+ LYgetEnum(tbl->table, value, q->int_value);
+ break;
+
+ case CONF_INT:
+ if (q->int_value != 0) {
+ int ival;
+
+ if (1 == sscanf(value, "%d", &ival))
+ *(q->int_value) = ival;
+ }
+ break;
+
+ case CONF_LIS:
+ if (q->str_value != 0) {
+ if (*(q->str_value) != NULL)
+ StrAllocCat(*(q->str_value), ",");
+ StrAllocCat(*(q->str_value), value);
+ }
+ break;
+
+ case CONF_MBM:
+ for (n = 1; n <= MBM_V_MAXFILES; n++) {
+ sprintf(MBM_line, "multi_bookmark%c", UCH(LYindex2MBM(n)));
+
+ if (!strcasecomp(name, MBM_line)) {
+ if ((notes = StrChr(value, ',')) != 0) {
+ *notes++ = '\0';
+ LYTrimTrailing(value);
+ notes = LYSkipBlanks(notes);
+ } else {
+ notes = value + strlen(value);
+ }
+ StrAllocCopy(MBM_A_subbookmark[n], value);
+ StrAllocCopy(MBM_A_subdescript[n], notes);
+ break;
+ }
+ }
+ break;
+
+ case CONF_STR:
+ if (q->str_value != 0)
+ StrAllocCopy(*(q->str_value), value);
+ break;
+
+ default:
+ changed = FALSE;
+ break;
+ }
+ FREE(orig_value);
+
+ return changed;
+}
+
+/* Read and process user options. If the passed-in fp is NULL, open the
+ * regular user defaults file for reading, otherwise use fp which has to be a
+ * file open for reading. - kw
+ */
+void read_rc(FILE *fp)
+{
+ char *buffer = NULL;
+ char rcfile[LY_MAXPATH];
+
+ if (!fp) {
+ /*
+ * Make an RC file name, open it for reading.
+ */
+ LYAddPathToHome(rcfile, sizeof(rcfile), FNAME_LYNXRC);
+ if ((fp = fopen(rcfile, TXT_R)) == NULL) {
+ return;
+ }
+ CTRACE((tfp, "read_rc opened %s\n", rcfile));
+ } else {
+ CTRACE((tfp, "read_rc used passed-in stream\n"));
+ }
+
+ /*
+ * Process the entries.
+ */
+ while (LYSafeGets(&buffer, fp) != NULL) {
+ char *name, *value;
+
+ /* Most lines in the config file are comment lines. Weed them out
+ * now. Also, leading whitespace is ok, so trim it.
+ */
+ LYTrimTrailing(buffer);
+ name = LYSkipBlanks(buffer);
+ if (ispunct(UCH(*name)) || *name == '\0')
+ continue;
+
+ /*
+ * Parse the "name=value" strings.
+ */
+ if ((value = StrChr(name, '=')) == 0) {
+ CTRACE((tfp, "LYrcFile: missing '=' %s\n", name));
+ continue;
+ }
+ *value++ = '\0';
+ LYTrimTrailing(name);
+ LYsetRcValue(name, value);
+ }
+
+ LYCloseInput(fp);
+ LYConfigCookies(); /* update cookie settings, if any */
+
+#if defined(USE_SLANG) || defined(COLOR_CURSES)
+ /*
+ * We may override the commandline "-color" option with the .lynxrc file
+ */
+ switch (LYrcShowColor) {
+ case SHOW_COLOR_ALWAYS:
+ if (LYShowColor != SHOW_COLOR_NEVER)
+ LYShowColor = SHOW_COLOR_ALWAYS;
+ break;
+ case SHOW_COLOR_NEVER:
+ if (LYShowColor == SHOW_COLOR_ON)
+ LYShowColor = SHOW_COLOR_OFF;
+ break;
+ default:
+ /* don't override */
+ break;
+ }
+#endif
+ set_default_bookmark_page(bookmark_page);
+}
+
+/*
+ * Write a set of comments. Doing it this way avoids preprocessor problems
+ * with the leading '#', makes it simpler to use gettext.
+ */
+static void write_list(FILE *fp, const char *list)
+{
+ int first = TRUE;
+
+ while (*list != 0) {
+ int ch = *list++;
+
+ if (first) {
+ fputs("# ", fp);
+ first = FALSE;
+ }
+ if (ch == '\n') {
+ first = TRUE;
+ }
+ fputc(ch, fp);
+ }
+}
+
+/*
+ * This is too long for some compilers.
+ */
+static void explain_keypad_mode(FILE *fp)
+{
+ write_list(fp, gettext("\
+If keypad_mode is set to \"NUMBERS_AS_ARROWS\", then the numbers on\n\
+your keypad when the numlock is on will act as arrow keys:\n\
+ 8 = Up Arrow\n\
+ 4 = Left Arrow 6 = Right Arrow\n\
+ 2 = Down Arrow\n\
+and the corresponding keyboard numbers will act as arrow keys,\n\
+regardless of whether numlock is on.\n\
+"));
+ write_list(fp, gettext("\
+If keypad_mode is set to \"LINKS_ARE_NUMBERED\", then numbers will\n\
+appear next to each link and numbers are used to select links.\n\
+"));
+ write_list(fp, gettext("\
+If keypad_mode is set to \"LINKS_AND_FORM_FIELDS_ARE_NUMBERED\", then\n\
+numbers will appear next to each link and visible form input field.\n\
+Numbers are used to select links, or to move the \"current link\" to a\n\
+form input field or button. In addition, options in popup menus are\n\
+indexed so that the user may type an option number to select an option in\n\
+a popup menu, even if the option isn't visible on the screen. Reference\n\
+lists and output from the list command also enumerate form inputs.\n\
+"));
+ write_list(fp, gettext("\
+NOTE: Some fixed format documents may look disfigured when\n\
+\"LINKS_ARE_NUMBERED\" or \"LINKS_AND_FORM_FIELDS_ARE_NUMBERED\" are\n\
+enabled.\n\
+"));
+}
+
+/* Save user options. If the passed-in fp is NULL, open the regular user
+ * defaults file for writing, otherwise use fp which has to be a temp file open
+ * for writing. - kw
+ */
+int save_rc(FILE *fp)
+{
+ Config_Type *tbl = Config_Table;
+ char rcfile[LY_MAXPATH];
+ BOOLEAN is_tempfile = (BOOL) (fp != NULL);
+ int n;
+
+ if (!fp) {
+ /*
+ * Make a name.
+ */
+ LYAddPathToHome(rcfile, sizeof(rcfile), FNAME_LYNXRC);
+
+ /*
+ * Open the file for write.
+ */
+ if ((fp = LYNewTxtFile(rcfile)) == NULL) {
+ return FALSE;
+ }
+ }
+
+ write_list(fp, gettext("\
+Lynx User Defaults File\n\
+\n\
+"));
+
+ /*
+ * We have either the HTML options form, or the older menu, or both.
+ */
+#ifndef NO_OPTION_FORMS
+ write_list(fp, gettext("\
+This file contains options saved from the Lynx Options Screen (normally\n\
+with the 'o' key). To save options with that screen, you must select the\n\
+checkbox:\n\
+"));
+ fprintf(fp, "#\t%s\n", SAVE_OPTIONS);
+ fprintf(fp, "#\n");
+ write_list(fp, gettext("\
+You must then save the settings using the link on the line above the\n\
+checkbox:\n\
+"));
+ fprintf(fp, "#\t%s\n", ACCEPT_CHANGES);
+ fprintf(fp, "#\n");
+#ifndef NO_OPTION_MENU
+ write_list(fp, gettext("\
+You may also use the command-line option \"-forms_options\", which displays\n\
+the simpler Options Menu instead. Save options with that using the '>' key.\n\
+\n\
+"));
+#endif
+#else /* we only have old options-menu */
+ write_list(fp, gettext("\
+This file contains options saved from the Lynx Options Screen (normally\n\
+with the '>' key).\n\
+\n\
+"));
+#endif
+
+ write_list(fp, gettext("\
+There is normally no need to edit this file manually, since the defaults\n\
+here can be controlled from the Options Screen, and the next time options\n\
+are saved from the Options Screen this file will be completely rewritten.\n\
+You have been warned...\n\
+\n\
+If you are looking for the general configuration file - it is normally\n\
+called \"lynx.cfg\". It has different content and a different format.\n\
+It is not this file.\n\
+"));
+ fprintf(fp, "\n");
+
+ while (tbl->name != 0) {
+ ParseUnionPtr q = ParseUnionOf(tbl);
+
+ if (!tbl->enabled) {
+ tbl++;
+ continue;
+ }
+ if (tbl->note != NULL) {
+ write_list(fp, gettext(tbl->note));
+ } else if (tbl->table == tbl_keypad_mode) {
+ explain_keypad_mode(fp);
+ }
+
+ switch (tbl->type) {
+ case CONF_BOOL:
+ fprintf(fp, "%s=%s\n\n", tbl->name, putBool(*(q->set_value)));
+ break;
+
+ case CONF_FUN:
+ if (tbl->write_it != 0)
+ tbl->write_it(fp, tbl);
+ break;
+
+ case CONF_ARRAY:
+ for (n = 0; tbl->strings[n] != 0; ++n)
+ fprintf(fp, "# %s\n", tbl->strings[n]);
+ fprintf(fp, "%s=%s\n\n", tbl->name,
+ tbl->strings[*(q->int_value)]);
+ break;
+
+ case CONF_ENUM:
+ fprintf(fp, "%s=%s\n\n", tbl->name,
+ LYputEnum(tbl->table, *(q->int_value)));
+ break;
+
+ case CONF_INT:
+ fprintf(fp, "%s=%d\n\n", tbl->name, *(q->int_value));
+ break;
+
+ case CONF_MBM:
+ for (n = 1; n <= MBM_V_MAXFILES; n++) {
+ fprintf(fp, "multi_bookmark%c=", UCH(LYindex2MBM(n)));
+
+ fprintf(fp, "%s", NonNull(MBM_A_subbookmark[n]));
+ if (MBM_A_subdescript[n] != 0
+ && *MBM_A_subdescript[n] != 0)
+ fprintf(fp, ",%s", MBM_A_subdescript[n]);
+ fprintf(fp, "\n");
+ }
+ fprintf(fp, "\n");
+ break;
+
+ case CONF_LIS:
+ /* FALLTHRU */
+ case CONF_STR:
+ fprintf(fp, "%s=%s\n\n", tbl->name,
+ (q->str_value != 0 && *(q->str_value) != 0)
+ ? *(q->str_value)
+ : "");
+ break;
+
+ case CONF_NIL:
+ break;
+ }
+ tbl++;
+ }
+
+ /*
+ * Close the RC file.
+ */
+ if (is_tempfile) {
+ LYCloseTempFP(fp);
+ } else {
+ LYCloseOutput(fp);
+ HTSYS_purge(rcfile);
+ }
+
+ return TRUE;
+}
+
+/*
+ * Returns true if the given name would be saved in .lynxrc
+ */
+BOOL will_save_rc(const char *name)
+{
+ Config_Type *tbl = lookup_config(name);
+
+ return (BOOL) (tbl->name != 0);
+}
+
+int enable_lynxrc(char *value)
+{
+ Config_Type *tbl;
+ char *colon = StrChr(value, ':');
+
+ if (colon != 0) {
+ *colon++ = 0;
+ LYTrimLeading(value);
+ LYTrimTrailing(value);
+
+ for (tbl = Config_Table; tbl->name != 0; tbl++) {
+ if (!strcasecomp(value, tbl->name)) {
+ tbl->enabled = getBool(colon);
+ break;
+ }
+ }
+ }
+ return 0;
+}
diff --git a/src/LYrcFile.h b/src/LYrcFile.h
new file mode 100644
index 0000000..1285a21
--- /dev/null
+++ b/src/LYrcFile.h
@@ -0,0 +1,318 @@
+/*
+ * $LynxId: LYrcFile.h,v 1.59 2021/07/29 22:53:37 tom Exp $
+ */
+#ifndef LYRCFILE_H
+#define LYRCFILE_H
+
+#ifndef LYSTRUCTS_H
+#include <LYStructs.h>
+#endif /* LYSTRUCTS_H */
+
+/* configuration-variable names to share with LYReadCFG.c and LYOptions.c */
+#define RC_ACCEPT_ALL_COOKIES "accept_all_cookies"
+#define RC_ALERTSECS "alertsecs"
+#define RC_ALT_BLAT_MAIL "alt_blat_mail"
+#define RC_ALWAYS_RESUBMIT_POSTS "always_resubmit_posts"
+#define RC_ALWAYS_TRUSTED_EXEC "always_trusted_exec"
+#define RC_ANONFTP_PASSWORD "anonftp_password"
+#define RC_ASSUMED_COLOR "assumed_color"
+#define RC_ASSUMED_DOC_CHARSET_CHOICE "assumed_doc_charset_choice"
+#define RC_ASSUME_CHARSET "assume_charset"
+#define RC_ASSUME_LOCAL_CHARSET "assume_local_charset"
+#define RC_ASSUME_UNREC_CHARSET "assume_unrec_charset"
+#define RC_AUTO_SESSION "auto_session"
+#define RC_AUTO_UNCACHE_DIRLISTS "auto_uncache_dirlists"
+#define RC_BAD_HTML "bad_html"
+#define RC_BIBP_BIBHOST "bibp_bibhost"
+#define RC_BIBP_GLOBALSERVER "bibp_globalserver"
+#define RC_BLAT_MAIL "blat_mail"
+#define RC_BLOCK_MULTI_BOOKMARKS "block_multi_bookmarks"
+#define RC_BOLD_H1 "bold_h1"
+#define RC_BOLD_HEADERS "bold_headers"
+#define RC_BOLD_NAME_ANCHORS "bold_name_anchors"
+#define RC_BOOKMARK_FILE "bookmark_file"
+#define RC_BROKEN_FTP_EPSV "broken_ftp_epsv"
+#define RC_BROKEN_FTP_RETR "broken_ftp_retr"
+#define RC_BROTLI_PATH "brotli_path"
+#define RC_BZIP2_PATH "bzip2_path"
+#define RC_CASE_SENSITIVE_ALWAYS_ON "case_sensitive_always_on"
+#define RC_CASE_SENSITIVE_SEARCHING "case_sensitive_searching"
+#define RC_CHARACTER_SET "character_set"
+#define RC_CHARSETS_DIRECTORY "charsets_directory"
+#define RC_CHARSET_SWITCH_RULES "charset_switch_rules"
+#define RC_CHECKMAIL "checkmail"
+#define RC_CHMOD_PATH "chmod_path"
+#define RC_COLLAPSE_BR_TAGS "collapse_br_tags"
+#define RC_COLOR "color"
+#define RC_COLOR_STYLE "color_style"
+#define RC_COMPRESS_PATH "compress_path"
+#define RC_CONNECT_TIMEOUT "connect_timeout"
+#define RC_CONV_JISX0201KANA "conv_jisx0201kana"
+#define RC_COOKIE_ACCEPT_DOMAINS "cookie_accept_domains"
+#define RC_COOKIE_FILE "cookie_file"
+#define RC_COOKIE_LOOSE_INVALID_DOMAINS "cookie_loose_invalid_domains"
+#define RC_COOKIE_QUERY_INVALID_DOMAINS "cookie_query_invalid_domains"
+#define RC_COOKIE_REJECT_DOMAINS "cookie_reject_domains"
+#define RC_COOKIE_SAVE_FILE "cookie_save_file"
+#define RC_COOKIE_STRICT_INVALID_DOMAIN "cookie_strict_invalid_domains"
+#define RC_COOKIE_VERSION "cookie_version"
+#define RC_COPY_PATH "copy_path"
+#define RC_CSO_PROXY "cso_proxy"
+#define RC_CSWING_PATH "cswing_path"
+#define RC_DEFAULT_BOOKMARK_FILE "default_bookmark_file"
+#define RC_DEFAULT_CACHE_SIZE "default_cache_size"
+#define RC_DEFAULT_COLORS "default_colors"
+#define RC_DEFAULT_EDITOR "default_editor"
+#define RC_DEFAULT_INDEX_FILE "default_index_file"
+#define RC_DEFAULT_KEYPAD_MODE "default_keypad_mode"
+#define RC_DEFAULT_KEYPAD_MODE_NUMARO "default_keypad_mode_is_numbers_as_arrows"
+#define RC_DEFAULT_USER_MODE "default_user_mode"
+#define RC_DEFAULT_VIRTUAL_MEMORY_SIZE "default_virtual_memory_size"
+#define RC_DELAYSECS "delaysecs"
+#define RC_DIRED_MENU "dired_menu"
+#define RC_DIR_LIST_ORDER "dir_list_order"
+#define RC_DIR_LIST_STYLE "dir_list_style"
+#define RC_DISPLAY "display"
+#define RC_DISPLAY_CHARSET_CHOICE "display_charset_choice"
+#define RC_DONT_WRAP_PRE "dont_wrap_pre"
+#define RC_DOWNLOADER "downloader"
+#define RC_EMACS_KEYS "emacs_keys"
+#define RC_EMACS_KEYS_ALWAYS_ON "emacs_keys_always_on"
+#define RC_ENABLE_LYNXRC "enable_lynxrc"
+#define RC_ENABLE_SCROLLBACK "enable_scrollback"
+#define RC_EXTERNAL "external"
+#define RC_EXTERNAL_MENU "external_menu"
+#define RC_FILE_EDITOR "file_editor"
+#define RC_FILE_SORTING_METHOD "file_sorting_method"
+#define RC_FINGER_PROXY "finger_proxy"
+#define RC_FOCUS_WINDOW "focus_window"
+#define RC_FORCE_8BIT_TOUPPER "force_8bit_toupper"
+#define RC_FORCE_COOKIE_PROMPT "force_cookie_prompt"
+#define RC_FORCE_EMPTY_HREFLESS_A "force_empty_hrefless_a"
+#define RC_FORCE_HTML "force_html"
+#define RC_FORCE_SSL_COOKIES_SECURE "force_ssl_cookies_secure"
+#define RC_FORCE_SSL_PROMPT "force_ssl_prompt"
+#define RC_FORMS_OPTIONS "forms_options"
+#define RC_FTP_FORMAT "ftp_format"
+#define RC_FTP_PASSIVE "ftp_passive"
+#define RC_FTP_PROXY "ftp_proxy"
+#define RC_GLOBAL_EXTENSION_MAP "global_extension_map"
+#define RC_GLOBAL_MAILCAP "global_mailcap"
+#define RC_GOPHER_PROXY "gopher_proxy"
+#define RC_GOTOBUFFER "gotobuffer"
+#define RC_GUESS_SCHEME "guess_scheme"
+#define RC_GZIP_PATH "gzip_path"
+#define RC_HELPFILE "helpfile"
+#define RC_HIDDENLINKS "hiddenlinks"
+#define RC_HIDDEN_LINK_MARKER "hidden_link_marker"
+#define RC_HISTORICAL_COMMENTS "historical_comments"
+#define RC_HTML5_CHARSETS "html5_charsets"
+#define RC_HTMLSRC_ATTRNAME_XFORM "htmlsrc_attrname_xform"
+#define RC_HTMLSRC_TAGNAME_XFORM "htmlsrc_tagname_xform"
+#define RC_HTTPS_PROXY "https_proxy"
+#define RC_HTTP_PROTOCOL "http_protocol"
+#define RC_HTTP_PROXY "http_proxy"
+#define RC_IDNA_MODE "idna_mode"
+#define RC_INCLUDE "include"
+#define RC_INFLATE_PATH "inflate_path"
+#define RC_INFOSECS "infosecs"
+#define RC_INSTALL_PATH "install_path"
+#define RC_JUMPBUFFER "jumpbuffer"
+#define RC_JUMPFILE "jumpfile"
+#define RC_JUMP_PROMPT "jump_prompt"
+#define RC_JUSTIFY "justify"
+#define RC_JUSTIFY_MAX_VOID_PERCENT "justify_max_void_percent"
+#define RC_KBLAYOUT "kblayout"
+#define RC_KEYBOARD_LAYOUT "keyboard_layout"
+#define RC_KEYMAP "keymap"
+#define RC_KEYPAD_MODE "keypad_mode"
+#define RC_LEFTARROW_IN_TEXTFLD_PROMPT "leftarrow_in_textfield_prompt"
+#define RC_LINEEDIT_MODE "lineedit_mode"
+#define RC_LISTONLY "listonly"
+#define RC_LIST_DECODED "list_decoded"
+#define RC_LIST_FORMAT "list_format"
+#define RC_LIST_INLINE "list_inline"
+#define RC_LIST_NEWS_DATES "list_news_dates"
+#define RC_LIST_NEWS_NUMBERS "list_news_numbers"
+#define RC_LOCALE_CHARSET "locale_charset"
+#define RC_LOCALHOST "localhost"
+#define RC_LOCALHOST_ALIAS "localhost_alias"
+#define RC_LOCAL_DOMAIN "local_domain"
+#define RC_LOCAL_EXECUTION_LINKS_ALWAYS "local_execution_links_always_on"
+#define RC_LOCAL_EXECUTION_LINKS_LOCAL "local_execution_links_on_but_not_remote"
+#define RC_LYNXCGI_DOCUMENT_ROOT "lynxcgi_document_root"
+#define RC_LYNXCGI_ENVIRONMENT "lynxcgi_environment"
+#define RC_LYNX_HOST_NAME "lynx_host_name"
+#define RC_LYNX_SIG_FILE "lynx_sig_file"
+#define RC_MAIL_ADRS "mail_adrs"
+#define RC_MAIL_SYSTEM_ERROR_LOGGING "mail_system_error_logging"
+#define RC_MAKE_LINKS_FOR_ALL_IMAGES "make_links_for_all_images"
+#define RC_MAKE_PSEUDO_ALTS_FOR_INLINES "make_pseudo_alts_for_inlines"
+#define RC_MAX_COOKIES_BUFFER "max_cookies_buffer"
+#define RC_MAX_COOKIES_DOMAIN "max_cookies_domain"
+#define RC_MAX_COOKIES_GLOBAL "max_cookies_global"
+#define RC_MAX_URI_SIZE "max_uri_size"
+#define RC_MESSAGESECS "messagesecs"
+#define RC_MESSAGE_LANGUAGE "message_language"
+#define RC_MINIMAL_COMMENTS "minimal_comments"
+#define RC_MKDIR_PATH "mkdir_path"
+#define RC_MULTI_BOOKMARK "multi_bookmark"
+#define RC_MULTI_BOOKMARK_SUPPORT "multi_bookmark_support"
+#define RC_MV_PATH "mv_path"
+#define RC_NCR_IN_BOOKMARKS "ncr_in_bookmarks"
+#define RC_NESTED_TABLES "nested_tables"
+#define RC_NEWSPOST_PROXY "newspost_proxy"
+#define RC_NEWSREPLY_PROXY "newsreply_proxy"
+#define RC_NEWS_CHUNK_SIZE "news_chunk_size"
+#define RC_NEWS_MAX_CHUNK "news_max_chunk"
+#define RC_NEWS_POSTING "news_posting"
+#define RC_NEWS_PROXY "news_proxy"
+#define RC_NNTPSERVER "nntpserver"
+#define RC_NNTP_PROXY "nntp_proxy"
+#define RC_NONRESTARTING_SIGWINCH "nonrestarting_sigwinch"
+#define RC_NO_DOT_FILES "no_dot_files"
+#define RC_NO_FILE_REFERER "no_file_referer"
+#define RC_NO_FORCED_CORE_DUMP "no_forced_core_dump"
+#define RC_NO_FROM_HEADER "no_from_header"
+#define RC_NO_ISMAP_IF_USEMAP "no_ismap_if_usemap"
+#define RC_NO_MARGINS "no_margins"
+#define RC_NO_PAUSE "no_pause"
+#define RC_NO_PROXY "no_proxy"
+#define RC_NO_REFERER_HEADER "no_referer_header"
+#define RC_NO_TABLE_CENTER "no_table_center"
+#define RC_NO_TITLE "no_title"
+#define RC_NUMBER_FIELDS_ON_LEFT "number_fields_on_left"
+#define RC_NUMBER_LINKS_ON_LEFT "number_links_on_left"
+#define RC_OUTGOING_MAIL_CHARSET "outgoing_mail_charset"
+#define RC_PARTIAL "partial"
+#define RC_PARTIAL_THRES "partial_thres"
+#define RC_PERSISTENT_COOKIES "persistent_cookies"
+#define RC_PERSONAL_EXTENSION_MAP "personal_extension_map"
+#define RC_PERSONAL_MAILCAP "personal_mailcap"
+#define RC_PERSONAL_MAIL_ADDRESS "personal_mail_address"
+#define RC_PERSONAL_MAIL_NAME "personal_mail_name"
+#define RC_POSITIONABLE_EDITOR "positionable_editor"
+#define RC_PREFERRED_CHARSET "preferred_charset"
+#define RC_PREFERRED_CONTENT_TYPE "preferred_content_type"
+#define RC_PREFERRED_ENCODING "preferred_encoding"
+#define RC_PREFERRED_LANGUAGE "preferred_language"
+#define RC_PREFERRED_MEDIA_TYPES "preferred_media_types"
+#define RC_PREPEND_BASE_TO_SOURCE "prepend_base_to_source"
+#define RC_PREPEND_CHARSET_TO_SOURCE "prepend_charset_to_source"
+#define RC_PRETTYSRC "prettysrc"
+#define RC_PRETTYSRC_SPEC "prettysrc_spec"
+#define RC_PRETTYSRC_VIEW_NO_ANCHOR_NUM "prettysrc_view_no_anchor_numbering"
+#define RC_PRINTER "printer"
+#define RC_QUIT_DEFAULT_YES "quit_default_yes"
+#define RC_RAW_MODE "raw_mode"
+#define RC_READ_TIMEOUT "read_timeout"
+#define RC_REDIRECTION_LIMIT "redirection_limit"
+#define RC_REFERER_WITH_QUERY "referer_with_query"
+#define RC_REPLAYSECS "replaysecs"
+#define RC_REUSE_TEMPFILES "reuse_tempfiles"
+#define RC_RLOGIN_PATH "rlogin_path"
+#define RC_RMDIR_PATH "rmdir_path"
+#define RC_RM_PATH "rm_path"
+#define RC_RULE "rule"
+#define RC_RULESFILE "rulesfile"
+#define RC_RUN_ALL_EXECUTION_LINKS "run_all_execution_links"
+#define RC_RUN_EXECUTION_LINKS_LOCAL "run_execution_links_on_local_files"
+#define RC_SAVE_SPACE "save_space"
+#define RC_SCAN_FOR_BURIED_NEWS_REFS "scan_for_buried_news_refs"
+#define RC_SCREEN_SIZE "screen_size"
+#define RC_SCROLLBAR "scrollbar"
+#define RC_SCROLLBAR_ARROW "scrollbar_arrow"
+#define RC_SEEK_FRAG_AREA_IN_CUR "seek_frag_area_in_cur"
+#define RC_SEEK_FRAG_MAP_IN_CUR "seek_frag_map_in_cur"
+#define RC_SELECT_POPUPS "select_popups"
+#define RC_SEND_USERAGENT "send_useragent"
+#define RC_SESSION_FILE "session_file"
+#define RC_SESSION_LIMIT "session_limit"
+#define RC_SET_COOKIES "set_cookies"
+#define RC_SHORT_URL "short_url"
+#define RC_SHOW_COLOR "show_color"
+#define RC_SHOW_CURSOR "show_cursor"
+#define RC_SHOW_DOTFILES "show_dotfiles"
+#define RC_SHOW_KB_NAME "show_kb_name"
+#define RC_SHOW_KB_RATE "show_kb_rate"
+#define RC_SNEWSPOST_PROXY "snewspost_proxy"
+#define RC_SNEWSREPLY_PROXY "snewsreply_proxy"
+#define RC_SNEWS_PROXY "snews_proxy"
+#define RC_SOFT_DQUOTES "soft_dquotes"
+#define RC_SOURCE_CACHE "source_cache"
+#define RC_SOURCE_CACHE_FOR_ABORTED "source_cache_for_aborted"
+#define RC_SSL_CERT_FILE "ssl_cert_file"
+#define RC_SSL_CLIENT_CERT_FILE "ssl_client_cert_file"
+#define RC_SSL_CLIENT_KEY_FILE "ssl_client_key_file"
+#define RC_STARTFILE "startfile"
+#define RC_STATUS_BUFFER_SIZE "status_buffer_size"
+#define RC_STRIP_DOTDOT_URLS "strip_dotdot_urls"
+#define RC_SUBSTITUTE_UNDERSCORES "substitute_underscores"
+#define RC_SUB_BOOKMARKS "sub_bookmarks"
+#define RC_SUFFIX "suffix"
+#define RC_SUFFIX_ORDER "suffix_order"
+#define RC_SYSLOG_REQUESTED_URLS "syslog_requested_urls"
+#define RC_SYSLOG_TEXT "syslog_text"
+#define RC_SYSTEM_EDITOR "system_editor"
+#define RC_SYSTEM_MAIL "system_mail"
+#define RC_SYSTEM_MAIL_FLAGS "system_mail_flags"
+#define RC_TAGSOUP "tagsoup"
+#define RC_TAR_PATH "tar_path"
+#define RC_TELNET_PATH "telnet_path"
+#define RC_TEXTFIELDS_NEED_ACTIVATION "textfields_need_activation"
+#define RC_TIMEOUT "timeout"
+#define RC_TN3270_PATH "tn3270_path"
+#define RC_TOUCH_PATH "touch_path"
+#define RC_TRACK_INTERNAL_LINKS "track_internal_links"
+#define RC_TRIM_BLANK_LINES "trim_blank_lines"
+#define RC_TRIM_INPUT_FIELDS "trim_input_fields"
+#define RC_TRUSTED_EXEC "trusted_exec"
+#define RC_TRUSTED_LYNXCGI "trusted_lynxcgi"
+#define RC_UNCOMPRESS_PATH "uncompress_path"
+#define RC_UNDERLINE_LINKS "underline_links"
+#define RC_UNIQUE_URLS "unique_urls"
+#define RC_UNZIP_PATH "unzip_path"
+#define RC_UPDATE_TERM_TITLE "update_term_title"
+#define RC_UPLOADER "uploader"
+#define RC_URL_DOMAIN_PREFIXES "url_domain_prefixes"
+#define RC_URL_DOMAIN_SUFFIXES "url_domain_suffixes"
+#define RC_USERAGENT "useragent"
+#define RC_USER_MODE "user_mode"
+#define RC_USE_FIXED_RECORDS "use_fixed_records"
+#define RC_USE_MOUSE "use_mouse"
+#define RC_USE_SELECT_POPUPS "use_select_popups"
+#define RC_UUDECODE_PATH "uudecode_path"
+#define RC_VERBOSE_IMAGES "verbose_images"
+#define RC_VIEWER "viewer"
+#define RC_VISITED_LINKS "visited_links"
+#define RC_VI_KEYS "vi_keys"
+#define RC_VI_KEYS_ALWAYS_ON "vi_keys_always_on"
+#define RC_WAIS_PROXY "wais_proxy"
+#define RC_WAIT_VIEWER_TERMINATION "wait_viewer_termination"
+#define RC_WITH_BACKSPACES "with_backspaces"
+#define RC_XHTML_PARSING "xhtml_parsing"
+#define RC_XLOADIMAGE_COMMAND "xloadimage_command"
+#define RC_ZCAT_PATH "zcat_path"
+#define RC_ZIP_PATH "zip_path"
+
+extern Config_Enum tbl_cookie_version[];
+extern Config_Enum tbl_force_prompt[];
+extern Config_Enum tbl_keypad_mode[];
+extern Config_Enum tbl_multi_bookmarks[];
+extern Config_Enum tbl_preferred_content[];
+extern Config_Enum tbl_preferred_encoding[];
+extern Config_Enum tbl_preferred_media[];
+extern Config_Enum tbl_transfer_rate[];
+extern Config_Enum tbl_user_mode[];
+
+extern BOOL LYgetEnum(Config_Enum * table, const char *name, int *result);
+extern BOOL LYsetRcValue(const char *name, const char *param);
+extern BOOL will_save_rc(const char *name);
+extern const char *LYputEnum(Config_Enum * table, int value);
+extern int enable_lynxrc(char *value);
+extern int get_http_protocol(char *value);
+extern int get_tagsoup(char *value);
+extern int save_rc(FILE *);
+extern void read_rc(FILE *);
+
+#endif /* LYRCFILE_H */
diff --git a/src/TRSTable.c b/src/TRSTable.c
new file mode 100644
index 0000000..4123596
--- /dev/null
+++ b/src/TRSTable.c
@@ -0,0 +1,2012 @@
+/*
+ * $LynxId: TRSTable.c,v 1.43 2023/11/10 01:01:54 tom Exp $
+ * Simple table object
+ * ===================
+ * Authors
+ * KW Klaus Weide <kweide@enteract.com>
+ * History:
+ * 2 Jul 1999 KW Created.
+ */
+
+#include <HTUtils.h>
+#include <HTStyle.h> /* for HT_LEFT, HT_CENTER, HT_RIGHT */
+#include <LYCurses.h>
+#include <TRSTable.h>
+#include <LYGlobalDefs.h>
+
+#include <LYLeaks.h>
+
+#ifdef SAVE_TIME_NOT_SPACE
+#define CELLS_GROWBY 16
+#define ROWS_GROWBY 16
+#else
+#define CELLS_GROWBY 2
+#define ROWS_GROWBY 2
+#endif
+
+#ifdef USE_CURSES_PADS
+# define MAX_STBL_POS (LYwideLines ? MAX_COLS - 1 : LYcolLimit)
+#else
+# define MAX_STBL_POS (LYcolLimit)
+#endif
+
+/* must be different from HT_ALIGN_NONE and HT_LEFT, HT_CENTER etc.: */
+#define RESERVEDCELL (-2) /* cell's alignment field is overloaded, this
+ value means cell was reserved by ROWSPAN */
+#define EOCOLG (-2) /* sumcols' Line field isn't used for line info, this
+ special value means end of COLGROUP */
+#ifndef NO_AGGRESSIVE_NEWROW
+# define NO_AGGRESSIVE_NEWROW 0
+#endif
+
+typedef enum {
+ CS_invalid = -1, /* cell "before the first",
+ or empty lines after [ce]bc,
+ or TRST aborted */
+ CS__new = 0,
+ CS__0new, /* new, at BOL */
+ CS__0eb, /* starts at BOL, empty, break */
+ CS__eb, /* empty, break */
+ CS__0cb, /* starts at BOL, content, break */
+ CS__cb, /* content, break */
+ CS__0ef, /* starts at BOL, empty, finished */
+ CS__ef, /* empty, finished */
+ CS__0cf, /* starts at BOL, content, finished */
+ CS__cf, /* content, finished */
+ CS__ebc, /* empty, break, more content (maybe @BOL) */
+ CS__cbc /* content, break, more content (maybe @BOL) */
+} cellstate_t;
+
+typedef struct _STable_states {
+ cellstate_t prev_state; /* Contents type of the previous cell */
+ cellstate_t state; /* Contents type of the worked-on cell */
+ int lineno; /* Start line of the current cell */
+ int icell_core; /* -1 or the 1st cell with <BR></TD> on row */
+ int x_td; /* x start pos of the current cell or -1 */
+ int pending_len; /* For multiline cells, the length of
+ the part on the first line (if
+ state is CS__0?[ec]b) (??), or 0 */
+} STable_states;
+
+typedef struct _STable_cellinfo {
+ int cLine; /* lineno in doc (zero-based): -1 for
+ contentless cells (and cells we do
+ not want to measure and count?),
+ line-of-the-start otherwise. */
+ int pos; /* column where cell starts */
+ int len; /* number of character positions */
+ int colspan; /* number of columns to span */
+ int alignment; /* one of HT_LEFT, HT_CENTER, HT_RIGHT,
+ or RESERVEDCELL */
+} STable_cellinfo;
+
+enum ended_state {
+ ROW_not_ended,
+ ROW_ended_by_endtr,
+ ROW_ended_by_splitline
+};
+
+#define HAS_END_OF_CELL 1
+#define HAS_BEG_OF_CELL 2
+#define IS_CONTINUATION_OF_CELL 4
+#define OFFSET_IS_VALID 8
+#define OFFSET_IS_VALID_LAST_CELL 0x10
+#define BELIEVE_OFFSET 0x20
+
+typedef struct _STable_rowinfo {
+ /* Each row may be displayed on many display lines, but we fix up
+ positions of cells on this display line only: */
+ int Line; /* lineno in doc (zero-based) */
+ int ncells; /* number of table cells */
+
+ /* What is the meaning of this?! It is set if:
+ [search for def of fixed_line below]
+
+ a1) a non-last cell is not at BOL,
+ a2) a non-last cell has something on the first line,
+ b) a >=3-lines-cell not at BOL, the first row non-empty, the 2nd empty;
+ c) a multiline cell not at BOL, the first row non-empty, the rest empty;
+ d) a multiline cell not at BOL, the first row non-empty;
+ e) a singleline non-empty cell not at BOL;
+
+ Summary: have seen a cell which is one of:
+ (Notation: B: at BOL; L: last; E: the first row is non-empty)
+
+ bcde: !B && !E
+ a1: !L && !B
+ a2: !L && !E
+
+ Or: has at least two of !B, !L, !E, or: has at most one of B,L,E.
+
+ REMARK: If this variable is not set, but icell_core is, Line is
+ reset to the line of icell_core.
+ */
+ BOOL fixed_line; /* if we have a 'core' line of cells */
+ enum ended_state ended; /* if we saw </tr> etc */
+ int content; /* Whether contains end-of-cell etc */
+ int offset; /* >=0 after line break in a multiline cell */
+ int allocated; /* number of table cells allocated */
+ STable_cellinfo *cells;
+ int alignment; /* global align attribute for this row */
+} STable_rowinfo;
+
+struct _STable_info {
+#ifdef EXP_NESTED_TABLES
+ struct _STable_info *enclosing; /* The table which contain us */
+ struct _TextAnchor *enclosing_last_anchor_before_stbl;
+#endif
+ int startline; /* lineno where table starts (zero-based) */
+ int nrows; /* number of rows */
+ int ncols; /* number of rows */
+ int maxlen; /* sum of max. cell lengths of any row */
+ int maxpos; /* max. of max. cell pos's of any row */
+ int allocated_rows; /* number of rows allocated */
+ int allocated_sumcols; /* number of sumcols allocated */
+ int ncolinfo; /* number of COL info collected */
+ STable_cellinfo *sumcols; /* for summary (max len/pos) col info */
+ STable_rowinfo *rows;
+ STable_rowinfo rowspans2eog;
+ short alignment; /* global align attribute for this table */
+ short rowgroup_align; /* align default for current group of rows */
+ short pending_colgroup_align;
+ int pending_colgroup_next;
+ STable_states s;
+};
+
+/*
+ * Functions and structures in this source file keep track of positions.
+ * They don't know about the character data in those lines, or about
+ * the HText and HTLine structures. GridText.c doesn't know about our
+ * structures. It should stay that way.
+ *
+ * The basic idea: we let the code in HTML.c/GridText.c produce and format
+ * output "as usual", i.e. as without Simple Table support. We keep track
+ * of the positions in the generated output where cells and rows start (or
+ * end). If all goes well, that preliminary output (stored in HText/HTLine
+ * structures) can be fixed up when the TABLE end tag is processed, by just
+ * inserting spaces in the right places (and possibly changing alignment).
+ * If all goes not well, we already have a safe fallback.
+ *
+ * Note that positions passed to and from these functions should be
+ * in terms of screen positions, not just byte counts in a HTLine.data
+ * (cf. line->data vs. HText_TrueLineSize).
+ *
+ * Memory is allocated dynamically, so we can have tables of arbitrary
+ * length. On allocation error we just return and error indication
+ * instead of outofmem(), so caller can give up table tracking and maybe
+ * recover memory.
+ *
+ * Implemented:
+ * - ALIGN={left,right,center,justify} applied to individual table cells
+ * ("justify" is treated as "left")
+ * - Inheritance of horizontal alignment according to HTML 4.0
+ * - COLSPAN >1 (may work incorrectly for some tables?)
+ * - ROWSPAN >1 (reserving cells in following rows)
+ * - Line breaks at start of first cell or at end of last cell are treated
+ * as if they were not part of the cell and row. This allows us to
+ * cooperate with one way in which tables have been made friendly to
+ * browsers without any table support.
+ * Missing, but can be added:
+ * - Support for COLGROUP/COL
+ * - Tables wider than display. The limitation is not here but in GridText.c
+ * etc. If horizontal scrolling were implemented there, the mechanisms
+ * here could deal with wide tables (just change MAX_STBL_POS code).
+ * Missing, unlikely to add:
+ * - Support for non-LTR directionality. A general problem, support is
+ * lacking throughout the lynx code.
+ * - Support for most other table-related attributes. Most of them are
+ * for decorative purposes.
+ * Impossible or very unlikely (because it doesn't fit the model):
+ * - Any cell contents of more than one line, line breaks within cells.
+ * Anything that requires handling cell contents as paragraphs (block
+ * elements), like reflowing. Vertical alignment.
+ */
+static int Stbl_finishRowInTable(STable_info *me);
+
+static const char *cellstate_s(cellstate_t state)
+{
+ const char *result = "?";
+ /* *INDENT-OFF* */
+ switch (state) {
+ case CS_invalid: result = "CS_invalid"; break;
+ case CS__new: result = "CS__new"; break;
+ case CS__0new: result = "CS__0new"; break;
+ case CS__0eb: result = "CS__0eb"; break;
+ case CS__eb: result = "CS__eb"; break;
+ case CS__0cb: result = "CS__0cb"; break;
+ case CS__cb: result = "CS__cb"; break;
+ case CS__0ef: result = "CS__0ef"; break;
+ case CS__ef: result = "CS__ef"; break;
+ case CS__0cf: result = "CS__0cf"; break;
+ case CS__cf: result = "CS__cf"; break;
+ case CS__ebc: result = "CS__ebc"; break;
+ case CS__cbc: result = "CS__cbc"; break;
+ }
+ /* *INDENT-ON* */
+
+ return result;
+}
+
+struct _STable_info *Stbl_startTABLE(int alignment)
+{
+ STable_info *me = typecalloc(STable_info);
+
+ CTRACE2(TRACE_TRST,
+ (tfp, "TRST:Stbl_startTABLE(align=%d)\n", alignment));
+ if (me) {
+ me->alignment = (short) alignment;
+ me->rowgroup_align = HT_ALIGN_NONE;
+ me->pending_colgroup_align = HT_ALIGN_NONE;
+ me->s.x_td = -1;
+ me->s.icell_core = -1;
+#ifdef EXP_NESTED_TABLES
+ if (nested_tables)
+ me->enclosing = 0;
+#endif
+ }
+ return me;
+}
+
+static void free_rowinfo(STable_rowinfo *me)
+{
+ if (me && me->allocated) {
+ FREE(me->cells);
+ }
+}
+
+void Stbl_free(STable_info *me)
+{
+ CTRACE2(TRACE_TRST,
+ (tfp, "TRST:Stbl_free()\n"));
+ if (me && me->allocated_rows && me->rows) {
+ int i;
+
+ for (i = 0; i < me->allocated_rows; i++)
+ free_rowinfo(me->rows + i);
+ FREE(me->rows);
+ }
+ free_rowinfo(&me->rowspans2eog);
+ if (me)
+ FREE(me->sumcols);
+ FREE(me);
+}
+
+/*
+ * Returns -1 on error, otherwise index of just-added table cell.
+ */
+static int Stbl_addCellToRow(STable_rowinfo *me, STable_cellinfo *colinfo, int ncolinfo,
+ STable_states *s,
+ int colspan,
+ int alignment,
+ int isheader,
+ int lineno,
+ int *ppos)
+{
+ STable_cellinfo *cells;
+ int i;
+ int last_colspan = me->ncells ?
+ me->cells[me->ncells - 1].colspan : 1;
+ cellstate_t newstate;
+ int ret;
+
+ CTRACE2(TRACE_TRST,
+ (tfp, "TRST:Stbl_addCellToRow, line=%d, pos=%d, colspan=%d\n",
+ lineno, *ppos, colspan));
+ CTRACE2(TRACE_TRST,
+ (tfp,
+ " ncells=%d, stateLine=%d, pending_len=%d, pstate=%s, state=%s\n",
+ me->ncells, s->lineno, s->pending_len,
+ cellstate_s(s->prev_state), cellstate_s(s->state)));
+ if (me->ncells == 0)
+ s->prev_state = CS_invalid;
+ else if (s->prev_state == CS_invalid ||
+ (s->state != CS__0new &&
+ s->state != CS__ef && s->state != CS__0ef))
+ s->prev_state = s->state;
+
+ if (me->ncells == 0 || *ppos == 0)
+ newstate = CS__0new;
+ else
+ newstate = CS__new;
+
+ if (me->ncells > 0 && s->pending_len > 0) {
+ if (s->prev_state != CS__cbc)
+ me->cells[me->ncells - 1].len = s->pending_len;
+ s->pending_len = 0;
+ }
+ s->x_td = *ppos;
+
+ if (lineno != s->lineno) {
+ if (!me->fixed_line) {
+ if (me->ncells == 0 || *ppos == 0) {
+ switch (s->prev_state) {
+ case CS_invalid:
+ case CS__0new:
+ case CS__0eb:
+ case CS__0cb:
+ case CS__0ef:
+ case CS__0cf:
+ if (me->ncells > 0)
+ for (i = me->ncells + last_colspan - 2;
+ i >= me->ncells - 1; i--) {
+ me->cells[i].pos = *ppos;
+ me->cells[i].cLine = lineno;
+ }
+ me->Line = lineno;
+ break;
+ case CS__new:
+ case CS__eb:
+ case CS__ef:
+ case CS__cf:
+ default:
+ break;
+ case CS__cb:
+ *ppos = me->cells[me->ncells - 1].pos +
+ me->cells[me->ncells - 1].len;
+ }
+ } else { /* last cell multiline, ncells != 0, pos != 0 */
+ switch (s->prev_state) {
+ case CS__0new:
+ case CS__0eb:
+ case CS__0ef:
+ /* Do not fail, but do not set fixed_line either */
+ break;
+ case CS__cb:
+ goto trace_and_fail;
+ case CS__cf:
+ goto trace_and_fail;
+ case CS__0cb:
+ case CS__0cf:
+ if (*ppos > me->cells[0].pos)
+ me->Line = lineno;
+ me->fixed_line = YES; /* type=a def of fixed_line i */
+ break;
+ case CS__new:
+ case CS__eb:
+ case CS__ef:
+ default:
+ me->fixed_line = YES; /* type=e def of fixed_line ii */
+ break;
+ case CS__cbc:
+ goto trace_and_fail;
+ }
+ }
+ }
+ if (me->fixed_line && lineno != me->Line) {
+ switch (s->prev_state) {
+ case CS__cb:
+ case CS__cf:
+ if (*ppos > 0)
+ goto trace_and_fail;
+ else
+ *ppos = me->cells[me->ncells - 1].pos /* == 0 */ +
+ me->cells[me->ncells - 1].len;
+ break;
+ case CS__0cf:
+ case CS__0cb:
+ if (*ppos == 0 || *ppos <= me->cells[0].pos)
+ *ppos = me->cells[me->ncells - 1].pos /* == 0 */ +
+ me->cells[me->ncells - 1].len;
+ break;
+ case CS__0new:
+ case CS__0ef:
+ case CS__0eb:
+ break;
+ case CS__new:
+ case CS__eb:
+ case CS__ef:
+ default:
+ *ppos = me->cells[me->ncells - 1].pos;
+ break;
+ case CS__cbc:
+ break;
+ case CS_invalid:
+ break;
+ }
+ }
+ s->lineno = lineno;
+ } else { /* lineno == s->lineno: */
+ switch (s->prev_state) {
+ case CS_invalid:
+ case CS__0new:
+ case CS__0eb: /* cannot happen */
+ case CS__0cb: /* cannot happen */
+ case CS__0ef:
+ case CS__0cf: /* ##302?? set icell_core? or only in finish? */
+ break;
+ case CS__eb: /* cannot happen */
+ case CS__cb: /* cannot happen */
+ case CS__ef:
+ break;
+ case CS__ebc: /* should have done smth in finish */
+ case CS__cbc: /* should have done smth in finish */
+ break;
+ case CS__new:
+ case CS__cf:
+ if (me->fixed_line && me->Line != lineno) {
+ goto trace_and_fail;
+ } else {
+ me->fixed_line = YES;
+ me->Line = lineno;
+ }
+ }
+ }
+
+ s->state = newstate;
+
+ if (me->ncells > 0 && me->cells[me->ncells - 1].colspan > 1) {
+ me->ncells += me->cells[me->ncells - 1].colspan - 1;
+ }
+ while (me->ncells < me->allocated &&
+ me->cells[me->ncells].alignment == RESERVEDCELL) {
+ me->ncells++;
+ }
+ {
+ int growby = 0;
+
+ while (me->ncells + colspan + 1 >= me->allocated + growby)
+ growby += CELLS_GROWBY;
+ if (growby) {
+ if (me->allocated == 0 && !me->cells) {
+ cells = typecallocn(STable_cellinfo, (unsigned) growby);
+ } else {
+ cells = typeRealloc(STable_cellinfo, me->cells,
+ (unsigned) (me->allocated + growby));
+
+ for (i = 0; cells && i < growby; i++) {
+ cells[me->allocated + i].alignment = HT_ALIGN_NONE;
+ }
+ }
+ if (cells) {
+ me->allocated += growby;
+ me->cells = cells;
+ } else {
+ goto trace_and_fail;
+ }
+ }
+ }
+
+ me->cells[me->ncells].cLine = lineno;
+ me->cells[me->ncells].pos = *ppos;
+ me->cells[me->ncells].len = -1;
+ me->cells[me->ncells].colspan = colspan;
+
+ if (alignment != HT_ALIGN_NONE)
+ me->cells[me->ncells].alignment = alignment;
+ else {
+ if (ncolinfo >= me->ncells + 1)
+ me->cells[me->ncells].alignment = colinfo[me->ncells].alignment;
+ else
+ me->cells[me->ncells].alignment = me->alignment;
+ if (me->cells[me->ncells].alignment == HT_ALIGN_NONE)
+ me->cells[me->ncells].alignment = me->alignment;
+ if (me->cells[me->ncells].alignment == HT_ALIGN_NONE)
+ me->cells[me->ncells].alignment = isheader ? HT_CENTER : HT_LEFT;
+ }
+ for (i = me->ncells + 1; i < me->ncells + colspan; i++) {
+ me->cells[i].cLine = lineno;
+ me->cells[i].pos = *ppos;
+ me->cells[i].len = -1;
+ me->cells[i].colspan = 0;
+ me->cells[i].alignment = HT_LEFT;
+ }
+ me->cells[me->ncells + colspan].pos = -1; /* not yet used */
+ me->ncells++;
+
+ ret = me->ncells - 1;
+ trace_and_return:
+ CTRACE2(TRACE_TRST,
+ (tfp, " => prev_state=%s, state=%s, ret=%d\n",
+ cellstate_s(s->prev_state), cellstate_s(s->state), ret));
+ return (ret);
+
+ trace_and_fail:
+ ret = -1;
+ goto trace_and_return;
+}
+
+/* returns -1 on error, 0 otherwise */
+/* assumes cells have already been allocated (but may need more) */
+static int Stbl_reserveCellsInRow(STable_rowinfo *me, int icell,
+ int colspan)
+{
+ STable_cellinfo *cells;
+ int i;
+ int growby = 1 + icell + colspan - me->allocated;
+
+ CTRACE2(TRACE_TRST,
+ (tfp,
+ "TRST:Stbl_reserveCellsInRow(icell=%d, colspan=%d) growby=%d\n",
+ icell, colspan, growby));
+ if (growby > 0) {
+ cells = typeRealloc(STable_cellinfo, me->cells,
+ (unsigned) (me->allocated + growby));
+
+ if (cells) {
+ for (i = 0; i < growby; i++) {
+ cells[me->allocated + i].alignment = HT_ALIGN_NONE;
+ }
+ me->allocated += growby;
+ me->cells = cells;
+ } else {
+ return -1;
+ }
+ }
+ for (i = icell; i < icell + colspan; i++) {
+ me->cells[i].cLine = -1;
+ me->cells[i].pos = -1;
+ me->cells[i].len = -1;
+ me->cells[i].colspan = 0;
+ me->cells[i].alignment = RESERVEDCELL;
+ }
+ me->cells[icell].colspan = colspan;
+ return 0;
+}
+
+/* Returns -1 on failure. */
+static int Stbl_finishCellInRow(STable_rowinfo *me, STable_states *s, int end_td,
+ int lineno,
+ int pos)
+{
+ STable_cellinfo *lastcell;
+ cellstate_t newstate = CS_invalid;
+ int multiline = NO, empty;
+ int ret;
+
+ CTRACE2(TRACE_TRST,
+ (tfp,
+ "TRST:Stbl_finishCellInRow line=%d pos=%d end_td=%d ncells=%d pnd_len=%d\n",
+ lineno, pos, end_td, me->ncells, s->pending_len));
+
+ if (me->ncells <= 0)
+ return -1;
+ lastcell = me->cells + (me->ncells - 1);
+ multiline = (lineno != lastcell->cLine || lineno != s->lineno);
+ empty = multiline ? (pos == 0) : (pos <= s->x_td);
+
+ CTRACE2(TRACE_TRST,
+ (tfp,
+ " [lines: lastCell=%d stateLine=%d multi=%d] empty=%d (prev)state=(%s) %s\n",
+ lastcell->cLine, s->lineno, multiline, empty,
+ cellstate_s(s->prev_state), cellstate_s(s->state)));
+
+ if (multiline) {
+ if ((end_td & TRST_ENDCELL_MASK) == TRST_ENDCELL_LINEBREAK) {
+ switch (s->state) {
+ case CS_invalid:
+ newstate = empty ? CS_invalid : CS__cbc;
+ break;
+ case CS__0new:
+ newstate = empty ? CS__0eb : CS__0cb;
+ break;
+ case CS__0eb:
+ newstate = empty ? CS__0eb : CS__ebc;
+ s->state = newstate;
+ if (empty) {
+ ret = (lastcell->len <= 0 ? 0 : lastcell->len);
+ } else {
+ ret = (lastcell->len <= 0 ? 0 : -1);
+ }
+ goto trace_and_return;
+ case CS__0cb:
+ if (!me->fixed_line) {
+ if (!empty) {
+ if (s->icell_core == -1)
+ me->Line = -1;
+ }
+ }
+ if (s->pending_len && empty) { /* First line non-empty */
+ if ((me->fixed_line && me->Line == lastcell->cLine) ||
+ s->icell_core == me->ncells - 1)
+ lastcell->len = s->pending_len;
+ s->pending_len = 0;
+ } /* @@@ for empty do smth. about ->Line / ->icell_core !! */
+ newstate = empty ? CS__0cb : CS__cbc; /* ##474_needs_len!=-1? */
+ break;
+ case CS__0ef:
+ case CS__0cf:
+ break;
+ case CS__new:
+ newstate = empty ? CS__eb : CS__cb;
+ break;
+ case CS__eb: /* ##484_set_pending_ret_0_if_empty? */
+ newstate = empty ? CS__eb : CS__ebc;
+ s->state = newstate;
+ if (empty) {
+ ret = (lastcell->len <= 0 ? 0 : lastcell->len);
+ } else {
+ ret = (lastcell->len <= 0 ? 0 : -1);
+ }
+ goto trace_and_return;
+ case CS__cb:
+ if (s->pending_len && empty) { /* ##496: */
+ lastcell->len = s->pending_len;
+ s->pending_len = 0;
+ } /* @@@ for empty do smth. about ->Line / ->icell_core !! */
+ ret = -1;
+ if (empty) {
+ if (!me->fixed_line) {
+ me->fixed_line = YES; /* type=b def of fixed_line i */
+ me->Line = lastcell->cLine; /* should've happened in break */
+ } else {
+ if (me->Line != lastcell->cLine)
+ goto trace_and_return;
+ }
+ newstate = CS__cb;
+ } else {
+ if (!me->fixed_line) {
+ me->fixed_line = YES; /* type=b def of fixed_line ii */
+ me->Line = lastcell->cLine; /* should've happened in break */
+ }
+ s->state = CS__cbc;
+ goto trace_and_return;
+ }
+ break;
+ case CS__ef:
+ ret = 0;
+ goto trace_and_return;
+ case CS__cf:
+ ret = lastcell->len; /* ##523_change_state? */
+ goto trace_and_return;
+ case CS__cbc:
+ if (!me->fixed_line) {
+ if (empty) {
+ if (s->icell_core == -1) /* ##528??: */
+ me->Line = lineno;
+ /* lastcell->Line = lineno; */
+ } else { /* !empty */
+ if (s->icell_core == -1)
+ me->Line = -1;
+ }
+ }
+ s->pending_len = 0;
+ newstate = empty ? CS_invalid : CS__cbc;
+ break;
+ default:
+ break;
+ }
+ } else { /* multiline cell, processing </TD>: */
+ s->x_td = -1;
+ switch (s->state) {
+ case CS_invalid:
+ /* ##540_return_-1_for_invalid_if_len!: */
+ if (!empty && lastcell->len > 0) {
+ newstate = CS__0cf;
+ s->state = newstate;
+ ret = -1;
+ goto trace_and_return;
+ }
+ /* ##541_set_len_0_Line_-1_sometimes: */
+ lastcell->len = 0;
+ lastcell->cLine = -1;
+ /* fall thru ##546 really fall thru??: */
+ newstate = empty ? CS_invalid : CS__cbc;
+ break;
+ case CS__0new:
+ newstate = empty ? CS__0ef : CS__0cf;
+ break;
+ case CS__0eb:
+ newstate = empty ? CS__0ef : CS__0cf; /* ebc?? */
+ s->state = newstate;
+ if (empty) {
+ ret = (lastcell->len <= 0 ? 0 : lastcell->len);
+ } else {
+ ret = (lastcell->len <= 0 ? 0 : -1);
+ }
+ goto trace_and_return;
+ case CS__0cb:
+ if (s->pending_len) {
+ if (empty)
+ lastcell->len = s->pending_len;
+ else
+ lastcell->len = 0;
+ s->pending_len = 0;
+ }
+ if (!me->fixed_line) {
+ if (empty) {
+ if (s->icell_core == -1)
+ /* first cell before <BR></TD> => the core cell */
+ s->icell_core = me->ncells - 1;
+ /* lastcell->cLine = lineno; */
+ } else { /* !empty */
+ if (s->icell_core == -1)
+ me->Line = -1;
+ }
+ }
+ if (s->pending_len && empty) {
+ lastcell->len = s->pending_len;
+ s->pending_len = 0;
+ } /* @@@ for empty do smth. about ->Line / ->icell_core !! */
+ newstate = empty ? CS__0cf : CS__cbc;
+ break;
+ case CS__0ef:
+ newstate = CS__0ef;
+ /* FALLTHRU */
+ case CS__0cf:
+ break;
+ case CS__new:
+ newstate = empty ? CS__ef : CS__cf;
+ break;
+ case CS__eb:
+ newstate = CS__ef;
+ s->state = newstate;
+ if (empty) {
+ ret = (lastcell->len <= 0 ? 0 : lastcell->len);
+ } else {
+ ret = (lastcell->len <= 0 ? 0 : -1);
+ }
+ goto trace_and_return;
+ case CS__cb:
+ if (s->pending_len && empty) {
+ lastcell->len = s->pending_len;
+ s->pending_len = 0;
+ }
+ ret = -1;
+ if (empty) {
+ if (!me->fixed_line) {
+ me->fixed_line = YES; /* type=c def of fixed_line */
+ me->Line = lastcell->cLine; /* should've happened in break */
+ } else {
+ if (me->Line != lastcell->cLine)
+ goto trace_and_return;
+ }
+ newstate = CS__cf;
+ } else {
+ goto trace_and_return;
+ }
+ break;
+ case CS__ef: /* ignored error */
+ case CS__cf: /* ignored error */
+ break;
+ case CS__ebc: /* ##540_handle_ebc: */
+ lastcell->len = 0;
+ if (!me->fixed_line) {
+ if (!empty) {
+ if (s->icell_core == -1)
+ lastcell->cLine = -1;
+ }
+ }
+ s->pending_len = 0;
+ newstate = empty ? CS_invalid : CS__cbc;
+ break;
+ case CS__cbc: /* ##586 */
+ lastcell->len = 0; /* ##613 */
+ ret = -1;
+ if (me->fixed_line && me->Line == lastcell->cLine)
+ goto trace_and_return;
+ if (!me->fixed_line) {
+ if (empty) {
+ if (s->icell_core == -1)
+ me->Line = lineno;
+ }
+ }
+ s->pending_len = 0; /* ##629 v */
+ newstate = empty ? CS_invalid : CS__cbc;
+ break;
+ }
+ }
+ } else { /* (!multiline) */
+ if ((end_td & TRST_ENDCELL_MASK) == TRST_ENDCELL_LINEBREAK) {
+ switch (s->state) {
+ case CS_invalid:
+ case CS__0new:
+ s->pending_len = empty ? 0 : pos - lastcell->pos;
+ newstate = empty ? CS__0eb : CS__0cb;
+ s->state = newstate;
+ ret = 0; /* or 0 for xlen to s->pending_len?? */
+ goto trace_and_return;
+ case CS__0eb: /* cannot happen */
+ newstate = CS__eb;
+ break;
+ case CS__0cb: /* cannot happen */
+ newstate = CS__cb;
+ break;
+ case CS__0ef:
+ case CS__0cf:
+ break;
+ case CS__new:
+ ret = -1;
+ if (!empty && s->prev_state == CS__cbc) /* ##609: */
+ goto trace_and_return;
+ if (!empty) {
+ if (!me->fixed_line) {
+ me->fixed_line = YES; /* type=d def of fixed_line */
+ me->Line = lineno;
+ } else {
+ if (me->Line != lineno)
+ goto trace_and_return;
+ }
+ }
+ newstate = empty ? CS__eb : CS__cb;
+ s->state = newstate;
+ if (!me->fixed_line) {
+ s->pending_len = empty ? 0 : pos - lastcell->pos;
+ ret = 0;
+ goto trace_and_return;
+ } else {
+ s->pending_len = 0;
+ lastcell->len = empty ? 0 : pos - lastcell->pos;
+ ret = lastcell->len;
+ goto trace_and_return;
+ }
+ case CS__eb: /* cannot happen */
+ newstate = empty ? CS__eb : CS__ebc;
+ break;
+ case CS__cb: /* cannot happen */
+ newstate = empty ? CS__cb : CS__cbc;
+ break;
+ case CS__ef:
+ ret = 0;
+ goto trace_and_return;
+ case CS__cf:
+ ret = lastcell->len;
+ goto trace_and_return;
+ case CS__cbc: /* ??? */
+ break;
+ default:
+ break;
+ }
+ } else { /* !multiline, processing </TD>: */
+ s->x_td = -1;
+ switch (s->state) {
+ case CS_invalid: /* ##691_no_lastcell_len_for_invalid: */
+ if (!(me->fixed_line && me->Line == lastcell->cLine))
+ lastcell->len = 0;
+ /* FALLTHRU */
+ case CS__0new:
+ newstate = empty ? CS__0ef : CS__0cf;
+ break; /* ##630 */
+ case CS__0eb:
+ newstate = CS__0ef;
+ break; /* ??? */
+ case CS__0cb:
+ newstate = empty ? CS__0cf : CS__cbc;
+ break; /* ??? */
+ case CS__0ef:
+ newstate = CS__0ef;
+ break; /* ??? */
+ case CS__0cf:
+ break; /* ??? */
+ case CS__new:
+ ret = -1;
+ if (!empty && s->prev_state == CS__cbc)
+ goto trace_and_return;
+ if (!empty) { /* ##642_set_fixed!: */
+ if (!me->fixed_line) {
+ me->fixed_line = YES; /* type=e def of fixed_line */
+ me->Line = lineno;
+ } else {
+ if (me->Line != lineno)
+ goto trace_and_return;
+ }
+ }
+ if (lastcell->len < 0)
+ lastcell->len = empty ? 0 : pos - lastcell->pos;
+ newstate = empty ? CS__ef : CS__cf;
+ s->state = newstate;
+ ret = ((me->fixed_line && lineno != me->Line)
+ ? -1 : lastcell->len);
+ goto trace_and_return;
+ case CS__eb:
+ newstate = empty ? CS__ef : CS__cf;
+ break; /* ??? */
+ case CS__cb:
+ newstate = CS__cf;
+ break; /* ??? */
+ case CS__ef: /* ignored error */
+ case CS__cf: /* ignored error */
+ default:
+ break;
+ }
+ lastcell->len = pos - lastcell->pos;
+ } /* if (!end_td) ... else */
+ } /* if (multiline) ... else */
+
+ s->state = newstate;
+ ret = lastcell->len;
+#ifdef EXP_NESTED_TABLES
+ if (nested_tables) {
+ if (ret == -1 && pos == 0)
+ ret = 0; /* XXXX Hack to allow trailing <P> in multiline cells. */
+ }
+#endif
+
+ trace_and_return:
+ CTRACE2(TRACE_TRST,
+ (tfp, " => prev_state=%s, state=%s, return=%d\n",
+ cellstate_s(s->prev_state), cellstate_s(s->state), ret));
+ return ret;
+}
+
+/*
+ * Reserve cells, each of given colspan, in (rowspan-1) rows after the current
+ * row of rowspan>1. If rowspan==0, use special 'row' rowspans2eog to keep
+ * track of rowspans that are to remain in effect until the end of the row
+ * group (until next THEAD/TFOOT/TBODY) or table.
+ */
+static int Stbl_reserveCellsInTable(STable_info *me, int icell,
+ int colspan,
+ int rowspan)
+{
+ STable_rowinfo *rows, *row;
+ int growby;
+ int i;
+
+ if (colspan > TRST_MAXCOLSPAN) {
+ CTRACE2(TRACE_TRST,
+ (tfp,
+ "TRST:*** COLSPAN=%d is too large, ignored!\n",
+ colspan));
+ return -1;
+ }
+ if (rowspan > TRST_MAXROWSPAN) {
+ CTRACE2(TRACE_TRST,
+ (tfp,
+ "TRST:*** ROWSPAN=%d is too large, ignored!\n",
+ rowspan));
+ return -1;
+ }
+ if (me->nrows <= 0)
+ return -1; /* must already have at least one row */
+
+ CTRACE2(TRACE_TRST,
+ (tfp,
+ "TRST:Stbl_reserveCellsInTable(icell=%d, colspan=%d, rowspan=%d)\n",
+ icell, colspan, rowspan));
+ if (rowspan == 0) {
+ if (!me->rowspans2eog.cells) {
+ me->rowspans2eog.cells = typecallocn(STable_cellinfo,
+ (unsigned) HTMAX(1, icell + colspan));
+
+ if (!me->rowspans2eog.cells)
+ return 0; /* fail silently */
+ else
+ me->rowspans2eog.allocated = icell + colspan;
+ }
+ Stbl_reserveCellsInRow(&me->rowspans2eog, icell, colspan);
+ }
+
+ growby = me->nrows + rowspan - 1 - me->allocated_rows;
+ if (growby > 0) {
+ rows = typeRealloc(STable_rowinfo, me->rows,
+ (unsigned) (me->allocated_rows + growby));
+
+ if (!rows)
+ return 0; /* ignore silently, no free memory, may be recoverable */
+ for (i = 0; i < growby; i++) {
+ row = rows + me->allocated_rows + i;
+ row->allocated = 0;
+ row->offset = 0;
+ row->content = 0;
+ if (!me->rowspans2eog.allocated) {
+ row->cells = NULL;
+ } else {
+ row->cells = typecallocn(STable_cellinfo,
+ (unsigned) me->rowspans2eog.allocated);
+
+ if (row->cells) {
+ row->allocated = me->rowspans2eog.allocated;
+ memcpy(row->cells, me->rowspans2eog.cells,
+ ((unsigned) row->allocated * sizeof(STable_cellinfo)));
+ }
+ }
+ row->ncells = 0;
+ row->fixed_line = NO;
+ row->alignment = HT_ALIGN_NONE;
+ }
+ me->allocated_rows += growby;
+ me->rows = rows;
+ }
+ for (i = me->nrows;
+ i < (rowspan == 0 ? me->allocated_rows : me->nrows + rowspan - 1);
+ i++) {
+ if (!me->rows[i].allocated) {
+ me->rows[i].cells = typecallocn(STable_cellinfo,
+ (unsigned) HTMAX(1,
+ icell
+ + colspan));
+
+ if (!me->rows[i].cells)
+ return 0; /* fail silently */
+ else
+ me->rows[i].allocated = icell + colspan;
+ }
+ Stbl_reserveCellsInRow(me->rows + i, icell, colspan);
+ }
+ return 0;
+}
+
+/* Remove reserved cells in trailing rows that were added for rowspan,
+ * to be used when a THEAD/TFOOT/TBODY ends. */
+static void Stbl_cancelRowSpans(STable_info *me)
+{
+ int i;
+
+ CTRACE2(TRACE_TRST, (tfp, "TRST:Stbl_cancelRowSpans()"));
+ for (i = me->nrows; i < me->allocated_rows; i++) {
+ if (!me->rows[i].ncells) { /* should always be the case */
+ FREE(me->rows[i].cells);
+ me->rows[i].allocated = 0;
+ }
+ }
+ free_rowinfo(&me->rowspans2eog);
+ me->rowspans2eog.allocated = 0;
+}
+
+/*
+ * Returns -1 on error, otherwise index of just-added table row.
+ */
+int Stbl_addRowToTable(STable_info *me, int alignment,
+ int lineno)
+{
+ STable_rowinfo *rows, *row;
+ STable_states *s = &me->s;
+
+ CTRACE2(TRACE_TRST,
+ (tfp, "TRST:Stbl_addRowToTable(alignment=%d, line=%d)\n",
+ alignment, lineno));
+ if (me->nrows > 0 && me->rows[me->nrows - 1].ncells > 0) {
+ if (s->pending_len > 0)
+ me->rows[me->nrows - 1].cells[
+ me->rows[me->nrows - 1].ncells - 1
+ ].len =
+ s->pending_len;
+ s->pending_len = 0;
+ }
+ Stbl_finishRowInTable(me);
+ if (me->nrows > 0 && me->rows[me->nrows - 1].Line == lineno)
+ me->rows[me->nrows - 1].Line = -1;
+ s->pending_len = 0;
+ s->x_td = -1;
+
+ {
+ int i;
+ int growby = 0;
+
+ while (me->nrows + 2 >= me->allocated_rows + growby)
+ growby += ROWS_GROWBY;
+ if (growby) {
+ if (me->allocated_rows == 0 && !me->rows) {
+ rows = typecallocn(STable_rowinfo, (unsigned) growby);
+ } else {
+ rows = typeRealloc(STable_rowinfo, me->rows,
+ (unsigned) (me->allocated_rows + growby));
+
+ for (i = 0; rows && i < growby; i++) {
+ row = rows + me->allocated_rows + i;
+ if (!me->rowspans2eog.allocated) {
+ row->allocated = 0;
+ row->cells = NULL;
+ } else {
+ row->cells = typecallocn(STable_cellinfo,
+ (unsigned) me->rowspans2eog.allocated);
+
+ if (row->cells) {
+ row->allocated = me->rowspans2eog.allocated;
+ memcpy(row->cells, me->rowspans2eog.cells,
+ (unsigned) row->allocated * sizeof(STable_cellinfo));
+ } else {
+ FREE(rows);
+ break;
+ }
+ }
+ row->ncells = 0;
+ row->fixed_line = NO;
+ row->alignment = HT_ALIGN_NONE;
+ row->offset = 0;
+ row->content = 0;
+ }
+ }
+ if (rows) {
+ me->allocated_rows += growby;
+ me->rows = rows;
+ } else {
+ return -1;
+ }
+ }
+ }
+
+ me->rows[me->nrows].Line = lineno;
+ if (me->nrows == 0)
+ me->startline = lineno;
+ if (alignment != HT_ALIGN_NONE)
+ me->rows[me->nrows].alignment = alignment;
+ else
+ me->rows[me->nrows].alignment =
+ (me->rowgroup_align == HT_ALIGN_NONE) ?
+ me->alignment : me->rowgroup_align;
+ me->nrows++;
+ if (me->pending_colgroup_next > me->ncolinfo) {
+ me->ncolinfo = me->pending_colgroup_next;
+ me->pending_colgroup_next = 0;
+ }
+ me->rows[me->nrows].Line = -1; /* not yet used */
+ me->rows[me->nrows].ended = ROW_not_ended; /* No </tr> yet */
+ return (me->nrows - 1);
+}
+
+/*
+ * Returns -1 on error, otherwise current number of rows.
+ */
+static int Stbl_finishRowInTable(STable_info *me)
+{
+ STable_rowinfo *lastrow;
+ STable_states *s = &me->s;
+
+ CTRACE2(TRACE_TRST, (tfp, "TRST:Stbl_finishRowInTable()\n"));
+ if (!me->rows || !me->nrows)
+ return -1; /* no row started! */
+ lastrow = me->rows + (me->nrows - 1);
+ lastrow->ended = ROW_ended_by_endtr;
+ if (lastrow->ncells > 0) {
+ if (s->pending_len > 0)
+ lastrow->cells[lastrow->ncells - 1].len = s->pending_len;
+ s->pending_len = 0;
+ }
+ s->prev_state = s->state = CS_invalid;
+ s->lineno = -1;
+
+ if (s->icell_core >= 0 && !lastrow->fixed_line &&
+ lastrow->cells[s->icell_core].cLine >= 0)
+ lastrow->Line = lastrow->cells[s->icell_core].cLine;
+ s->icell_core = -1;
+ return (me->nrows);
+}
+
+static void update_sumcols0(STable_cellinfo *sumcols,
+ STable_rowinfo *lastrow,
+ int pos,
+ int len,
+ int icell,
+ int ispan,
+ int allocated_sumcols)
+{
+ int i;
+
+ if (len > 0) {
+ int sumpos = pos;
+ int prevsumpos = sumcols[icell + ispan].pos;
+ int advance;
+
+ if (ispan > 0) {
+ if (lastrow->cells[icell].pos + len > sumpos)
+ sumpos = lastrow->cells[icell].pos + len;
+ if (sumcols[icell + ispan - 1].pos +
+ sumcols[icell + ispan - 1].len >
+ sumpos)
+ sumpos = sumcols[icell + ispan - 1].pos +
+ sumcols[icell + ispan - 1].len;
+ }
+ advance = sumpos - prevsumpos;
+ if (advance > 0) {
+ for (i = icell + ispan; i < allocated_sumcols; i++) {
+ if (ispan > 0 && sumcols[i].colspan < -1) {
+ if (i + sumcols[i].colspan < icell + ispan) {
+ advance = sumpos - sumcols[i].pos;
+ if (i > 0)
+ advance = HTMAX(advance,
+ sumcols[i - 1].pos +
+ sumcols[i - 1].len
+ - (sumcols[i].pos));
+ if (advance <= 0)
+ break;
+ }
+ }
+ if (sumcols[i].pos >= 0)
+ sumcols[i].pos += advance;
+ else {
+ sumcols[i].pos = sumpos;
+ break;
+ }
+ }
+ }
+ }
+}
+
+static int get_remaining_colspan(STable_rowinfo *me,
+ STable_cellinfo *colinfo,
+ int ncolinfo,
+ int colspan,
+ int ncols_sofar)
+{
+ int i;
+ int last_colspan = (me->ncells
+ ? me->cells[me->ncells - 1].colspan
+ : 1);
+
+ if (ncolinfo == 0 || me->ncells + last_colspan > ncolinfo) {
+ colspan = HTMIN(TRST_MAXCOLSPAN,
+ ncols_sofar - (me->ncells + last_colspan - 1));
+ colspan = HTMAX(colspan, 0);
+ } else {
+ for (i = me->ncells + last_colspan - 1; i < ncolinfo - 1; i++)
+ if (colinfo[i].cLine == EOCOLG)
+ break;
+ colspan = i - (me->ncells + last_colspan - 2);
+ }
+ CTRACE2(TRACE_TRST,
+ (tfp, "TRST:get_remaining_colspan; colspan = %d\n",
+ colspan));
+ return colspan;
+}
+
+#ifdef EXP_NESTED_TABLES
+/* Returns -1 on failure, 1 if faking was performed, 0 if not needed. */
+static int Stbl_fakeFinishCellInTable(STable_info *me,
+ STable_rowinfo *lastrow,
+ int lineno,
+ int finishing) /* Processing finish or start */
+{
+ STable_states *s = &me->s;
+ int fake = 0;
+
+ switch (s->state) { /* We care only about trailing <BR> */
+ case CS_invalid:
+ case CS__0new:
+ case CS__0ef:
+ case CS__0cf:
+ case CS__new:
+ case CS__cbc:
+ case CS__ef:
+ case CS__cf:
+ default:
+ /* <BR></TD> may produce these (XXXX instead of CS__cbf?). But if
+ finishing==0, the caller already checked that we are on a
+ different line. */
+ if (finishing == 0)
+ fake = 1;
+ break; /* Either can't happen, or may be ignored */
+ case CS__eb:
+ case CS__0eb:
+ case CS__0cb:
+ case CS__cb:
+ fake = 1;
+ break;
+ }
+ if (fake) {
+ /* The previous action we did was putting a linebreak. Now we
+ want to put another one. Fake necessary
+ </TD></TR><TR><TD></TD><TD> (and possibly </TD>) instead. */
+ int ncells = lastrow->ncells;
+ int i;
+ int al = lastrow->alignment;
+ int cs = lastrow->cells[lastrow->ncells - 1].colspan;
+ int rs = 1; /* XXXX How to find rowspan? */
+ int ih = 0; /* XXXX How to find is_header? */
+ int end_td = (TRST_ENDCELL_ENDTD | TRST_FAKING_CELLS);
+ int need_reserved = 0;
+ int prev_reserved_last = -1;
+ STable_rowinfo *prev_row;
+ int prev_row_n2 = (int) (lastrow - me->rows);
+
+ CTRACE2(TRACE_TRST,
+ (tfp,
+ "TRST:Stbl_fakeFinishCellInTable(line=%d, finishing=%d) START FAKING\n",
+ lineno, finishing));
+
+ /* Although here we use pos=0, this may commit the previous
+ cell which had <BR> as a last element. This may overflow
+ the screen width, so the additional checks performed in
+ Stbl_finishCellInTable (comparing to Stbl_finishCellInRow)
+ are needed. */
+ if (finishing) {
+ /* Fake </TD> at BOL */
+ if (Stbl_finishCellInTable(me, end_td, lineno, 0, 0) < 0) {
+ return -1;
+ }
+ }
+
+ /* Fake <TR> at BOL */
+ if (Stbl_addRowToTable(me, al, lineno) < 0) {
+ return -1;
+ }
+ lastrow = me->rows + (me->nrows - 1);
+ lastrow->content = IS_CONTINUATION_OF_CELL;
+ for (i = 0; i < lastrow->allocated; i++) {
+ if (lastrow->cells[i].alignment == RESERVEDCELL) {
+ need_reserved = 1;
+ break;
+ }
+ }
+
+ prev_row = me->rows + prev_row_n2;
+ for (i = ncells; i < prev_row->allocated; i++) {
+ if (prev_row->cells[i].alignment == RESERVEDCELL)
+ prev_reserved_last = i;
+ }
+ if (need_reserved || prev_reserved_last >= 0) {
+ /* Oups, we are going to stomp over a line which somebody
+ cares about already, or the previous line had reserved
+ cells which were not skipped over.
+
+ Remember that STable_rowinfo is about logical (TR)
+ table lines, not displayed lines. We need to duplicate
+ the reservation structure when we fake new logical lines. */
+ int prev_row_n = (int) (prev_row - me->rows);
+ STable_rowinfo *rows = typeRealloc(STable_rowinfo, me->rows,
+ (unsigned) (me->allocated_rows
+ + 1));
+ int need_cells = prev_reserved_last + 1;
+ int n;
+
+ if (!rows)
+ return -1; /* ignore silently, no free memory, may be recoverable */
+
+ CTRACE2(TRACE_TRST,
+ (tfp, "TRST:Stbl_fakeFinishCellInTable REALLOC ROWSPAN\n"));
+ me->rows = rows;
+ lastrow = me->rows + (me->nrows - 1);
+ prev_row = me->rows + prev_row_n;
+ me->allocated_rows++;
+
+ /* Insert a duplicate row after lastrow */
+ for (n = me->allocated_rows - me->nrows - 1; n >= 0; --n)
+ lastrow[n + 1] = lastrow[n];
+
+ /* Ignore cells, they belong to the next row now */
+ lastrow->allocated = 0;
+ lastrow->cells = 0;
+ if (need_cells) {
+ lastrow->cells = typecallocn(STable_cellinfo, (unsigned) need_cells);
+
+ /* ignore silently, no free memory, may be recoverable */
+ if (!lastrow->cells) {
+ return -1;
+ }
+ lastrow->allocated = need_cells;
+ memcpy(lastrow->cells, prev_row->cells,
+ (unsigned) lastrow->allocated * sizeof(STable_cellinfo));
+
+ i = -1;
+ while (++i < ncells) {
+ /* Stbl_addCellToTable grants RESERVEDCELL, but we do not
+ want this action for fake cells.
+ XXX Maybe always fake RESERVEDCELL instead of explicitly
+ creating/destroying cells? */
+ if (lastrow->cells[i].alignment == RESERVEDCELL)
+ lastrow->cells[i].alignment = HT_LEFT;
+ }
+ }
+ }
+
+ /* Fake <TD></TD>...<TD> (and maybe a </TD>) at BOL. */
+ CTRACE2(TRACE_TRST,
+ (tfp, "TRST:Stbl_fakeFinishCellInTable FAKE %d elts%s\n",
+ ncells, (finishing ? ", last unfinished" : "")));
+ i = 0;
+ while (++i <= ncells) {
+ /* XXXX A lot of args may be wrong... */
+ if (Stbl_addCellToTable(me, (i == ncells ? cs : 1), rs, al,
+ ih, lineno, 0, 0) < 0) {
+ return -1;
+ }
+ lastrow->content &= ~HAS_BEG_OF_CELL; /* BEG_OF_CELL was fake */
+ /* We cannot run out of width here, so it is safe to not
+ call Stbl_finishCellInTable(), but Stbl_finishCellInRow. */
+ if (!finishing || (i != ncells)) {
+ if (Stbl_finishCellInRow(lastrow, s, end_td, lineno, 0) < 0) {
+ return -1;
+ }
+ }
+ }
+ CTRACE2(TRACE_TRST,
+ (tfp,
+ "TRST:Stbl_fakeFinishCellInTable(line=%d) FINISH FAKING\n",
+ lineno));
+ return 1;
+ }
+ return 0;
+}
+#endif
+
+/*
+ * Returns -1 on error, otherwise 0.
+ */
+int Stbl_addCellToTable(STable_info *me, int colspan,
+ int rowspan,
+ int alignment,
+ int isheader,
+ int lineno,
+ int offset_not_used_yet GCC_UNUSED,
+ int pos)
+{
+ STable_states *s = &me->s;
+ STable_rowinfo *lastrow;
+ STable_cellinfo *sumcols, *sumcol;
+ int i, icell, ncells, sumpos;
+
+ CTRACE2(TRACE_TRST,
+ (tfp,
+ "TRST:Stbl_addCellToTable(line=%d, pos=%d, isheader=%d, cs=%d, rs=%d, al=%d)\n",
+ lineno, pos, isheader, colspan, rowspan, alignment));
+ if (!me->rows || !me->nrows)
+ return -1; /* no row started! */
+ /* ##850_fail_if_fail?? */
+ if (me->rows[me->nrows - 1].ended != ROW_not_ended) {
+ Stbl_addRowToTable(me, alignment, lineno);
+ }
+ Stbl_finishCellInTable(me, TRST_ENDCELL_ENDTD, lineno, 0, pos);
+ lastrow = me->rows + (me->nrows - 1);
+
+#ifdef EXP_NESTED_TABLES
+ if (nested_tables) {
+ /* If the last cell was finished by <BR></TD>, we need to fake an
+ appropriate amount of cells */
+ if (!NO_AGGRESSIVE_NEWROW && pos == 0 && lastrow->ncells > 0
+ && lastrow->cells[lastrow->ncells - 1].cLine != lineno) {
+ int rc;
+
+ rc = Stbl_fakeFinishCellInTable(me, lastrow, lineno, 0);
+
+ if (rc < 0)
+ return -1;
+ if (rc)
+ lastrow = me->rows + (me->nrows - 1);
+ }
+ }
+#endif
+ if (colspan == 0) {
+ colspan = get_remaining_colspan(lastrow, me->sumcols, me->ncolinfo,
+ colspan, me->ncols);
+ }
+ ncells = lastrow->ncells; /* remember what it was before adding cell. */
+ icell = Stbl_addCellToRow(lastrow, me->sumcols, me->ncolinfo, s,
+ colspan, alignment, isheader,
+ lineno, &pos);
+ if (icell < 0)
+ return icell;
+ if (me->nrows == 1 && me->startline < lastrow->Line)
+ me->startline = lastrow->Line;
+
+ if (rowspan != 1) {
+ Stbl_reserveCellsInTable(me, icell, colspan, rowspan);
+ /* me->rows may now have been realloc'd, make lastrow valid pointer */
+ lastrow = me->rows + (me->nrows - 1);
+ }
+ lastrow->content |= HAS_BEG_OF_CELL;
+
+ {
+ int growby = 0;
+
+ while (icell + colspan + 1 >= me->allocated_sumcols + growby)
+ growby += CELLS_GROWBY;
+ if (growby) {
+ if (me->allocated_sumcols == 0 && !me->sumcols) {
+ sumcols = typecallocn(STable_cellinfo, (unsigned) growby);
+ } else {
+ sumcols = typeRealloc(STable_cellinfo, me->sumcols,
+ (unsigned) (me->allocated_sumcols + growby));
+
+ for (i = 0; sumcols && i < growby; i++) {
+ sumcol = sumcols + me->allocated_sumcols + i;
+ sumcol->pos = sumcols[me->allocated_sumcols - 1].pos;
+ sumcol->len = 0;
+ sumcol->colspan = 0;
+ sumcol->cLine = 0;
+ sumcol->alignment = HT_ALIGN_NONE;
+ }
+ }
+ if (sumcols) {
+ me->allocated_sumcols += growby;
+ me->sumcols = sumcols;
+ } else {
+ return -1;
+ }
+ }
+ }
+ if (icell + 1 > me->ncols) {
+ me->ncols = icell + 1;
+ }
+ if (colspan > 1 && colspan + me->sumcols[icell + colspan].colspan > 0)
+ me->sumcols[icell + colspan].colspan = -colspan;
+ sumpos = pos;
+ if (ncells > 0)
+ sumpos += me->sumcols[ncells - 1].pos - lastrow->cells[ncells - 1].pos;
+ update_sumcols0(me->sumcols, lastrow, sumpos,
+ sumpos - me->sumcols[icell].pos,
+ icell, 0, me->allocated_sumcols);
+
+ me->maxpos = me->sumcols[me->allocated_sumcols - 1].pos;
+ if (me->maxpos > /* @@@ max. line length we can accept */ MAX_STBL_POS)
+ return -1;
+ return 0;
+}
+
+/*
+ * Returns -1 on error, otherwise 0.
+ */
+int Stbl_finishCellInTable(STable_info *me, int end_td,
+ int lineno,
+ int offset,
+ int pos)
+{
+ STable_states *s = &me->s;
+ STable_rowinfo *lastrow;
+ int len, xlen, icell;
+ int i;
+
+ CTRACE2(TRACE_TRST,
+ (tfp,
+ "TRST:Stbl_finishCellInTable(line=%d, pos=%d, off=%d, end_td=%d)\n",
+ lineno, pos, offset, end_td));
+ if (me->nrows == 0)
+ return -1;
+ lastrow = me->rows + (me->nrows - 1);
+ icell = lastrow->ncells - 1;
+ if (icell < 0)
+ return icell;
+ if (s->x_td == -1) { /* Stray </TD> or just-in-case, as on </TR> */
+ if ((end_td & TRST_ENDCELL_MASK) == TRST_ENDCELL_LINEBREAK)
+ lastrow->ended = ROW_ended_by_splitline;
+ return 0;
+ }
+#ifdef EXP_NESTED_TABLES
+ if (nested_tables) {
+ if (!NO_AGGRESSIVE_NEWROW && !(end_td & TRST_FAKING_CELLS)) {
+ int rc;
+
+ rc = Stbl_fakeFinishCellInTable(me, lastrow, lineno, 1);
+
+ if (rc) {
+ if (rc < 0)
+ return -1;
+ lastrow = me->rows + (me->nrows - 1);
+ icell = lastrow->ncells - 1;
+ }
+ }
+ }
+#endif
+ len = Stbl_finishCellInRow(lastrow, s, end_td, lineno, pos);
+ if (len == -1) {
+ return len;
+ }
+ xlen = (len > 0) ? len : s->pending_len; /* ##890 use xlen if fixed_line?: */
+ if (lastrow->Line == lineno)
+ len = xlen;
+ if (lastrow->cells[icell].colspan > 1) {
+ /*
+ * @@@ This is all a too-complicated mess; do we need
+ * sumcols len at all, or is pos enough??
+ * Answer: sumcols len is at least used for center/right
+ * alignment, and should probably continue to be used there;
+ * all other uses are probably not necessary.
+ */
+ int spanlen = 0, spanlend = 0;
+
+ for (i = icell; i < icell + lastrow->cells[icell].colspan; i++) {
+ if (me->sumcols[i].len > 0) {
+ spanlen += me->sumcols[i].len;
+ if (i > icell)
+ spanlen++;
+ }
+ spanlend = HTMAX(spanlend,
+ me->sumcols[i + 1].pos - me->sumcols[icell].pos);
+ }
+ if (spanlend)
+ spanlend--;
+ if (spanlend > spanlen)
+ spanlen = spanlend;
+ /* @@@ could overcount? */
+ if (len > spanlen)
+ me->maxlen += (len - spanlen);
+ } else if (len > me->sumcols[icell].len) {
+ if (me->sumcols[icell + 1].colspan >= -1)
+ me->maxlen += (len - me->sumcols[icell].len);
+ me->sumcols[icell].len = len;
+ }
+
+ if (len > 0) {
+ update_sumcols0(me->sumcols, lastrow, pos, len,
+ icell, lastrow->cells[icell].colspan,
+ me->allocated_sumcols);
+ me->maxpos = me->sumcols[me->allocated_sumcols - 1].pos;
+ }
+
+ if ((end_td & TRST_ENDCELL_MASK) == TRST_ENDCELL_LINEBREAK) {
+ lastrow->ended = ROW_ended_by_splitline;
+ lastrow->content |= BELIEVE_OFFSET;
+ lastrow->offset = offset;
+ }
+#ifdef EXP_NESTED_TABLES /* maxlen may already include contribution of a cell in this column */
+ if (nested_tables) {
+ if (me->maxlen > MAX_STBL_POS) {
+ return -1;
+ }
+ } else
+#endif
+ {
+ if (me->maxlen + (xlen - len) > MAX_STBL_POS)
+ return -1;
+ }
+ if (me->maxpos > /* @@@ max. line length we can accept */ MAX_STBL_POS) {
+ return -1;
+ }
+
+ if (lineno != lastrow->Line) {
+ /* @@@ Do something here? Or is it taken care of in
+ Stbl_finishCellInRow ? */
+ }
+
+ return 0;
+}
+
+/*
+ * Returns -1 on error, otherwise 0.
+ */
+int Stbl_addColInfo(STable_info *me,
+ int colspan,
+ int alignment,
+ int isgroup)
+{
+ STable_cellinfo *sumcols, *sumcol;
+ int i, icolinfo;
+
+ CTRACE2(TRACE_TRST,
+ (tfp, "TRST:Stbl_addColInfo(cs=%d, al=%d, isgroup=%d)\n",
+ colspan, alignment, isgroup));
+ if (isgroup) {
+ if (me->pending_colgroup_next > me->ncolinfo)
+ me->ncolinfo = me->pending_colgroup_next;
+ me->pending_colgroup_next = me->ncolinfo + colspan;
+ if (me->ncolinfo > 0)
+ me->sumcols[me->ncolinfo - 1].cLine = EOCOLG;
+ me->pending_colgroup_align = (short) alignment;
+ } else {
+ for (i = me->pending_colgroup_next - 1;
+ i >= me->ncolinfo + colspan; i--)
+ me->sumcols[i].alignment = HT_ALIGN_NONE;
+ me->pending_colgroup_next = me->ncolinfo + colspan;
+ }
+ icolinfo = me->ncolinfo;
+ if (!isgroup)
+ me->ncolinfo += colspan;
+
+ {
+ int growby = 0;
+
+ while (icolinfo + colspan + 1 >= me->allocated_sumcols + growby)
+ growby += CELLS_GROWBY;
+ if (growby) {
+ if (me->allocated_sumcols == 0) {
+ sumcols = typecallocn(STable_cellinfo, (unsigned) growby);
+ } else {
+ sumcols = typeRealloc(STable_cellinfo, me->sumcols,
+ (unsigned) (me->allocated_sumcols + growby));
+
+ for (i = 0; sumcols && i < growby; i++) {
+ sumcol = sumcols + me->allocated_sumcols + i;
+ sumcol->pos = sumcols[me->allocated_sumcols - 1].pos;
+ sumcol->len = 0;
+ sumcol->colspan = 0;
+ sumcol->cLine = 0;
+ }
+ }
+ if (sumcols) {
+ me->allocated_sumcols += growby;
+ me->sumcols = sumcols;
+ } else {
+ return -1;
+ }
+ }
+ }
+
+ if (alignment == HT_ALIGN_NONE)
+ alignment = me->pending_colgroup_align;
+ for (i = icolinfo; i < icolinfo + colspan; i++) {
+ me->sumcols[i].alignment = alignment;
+ }
+ return 0;
+}
+
+/*
+ * Returns -1 on error, otherwise 0.
+ */
+int Stbl_finishColGroup(STable_info *me)
+{
+ CTRACE2(TRACE_TRST, (tfp, "TRST:Stbl_finishColGroup()\n"));
+ if (me->pending_colgroup_next >= me->ncolinfo) {
+ me->ncolinfo = me->pending_colgroup_next;
+ if (me->ncolinfo > 0)
+ me->sumcols[me->ncolinfo - 1].cLine = EOCOLG;
+ }
+ me->pending_colgroup_next = 0;
+ me->pending_colgroup_align = HT_ALIGN_NONE;
+ return 0;
+}
+
+int Stbl_addRowGroup(STable_info *me, int alignment)
+{
+ CTRACE2(TRACE_TRST, (tfp, "TRST:Stbl_addRowGroup()\n"));
+ Stbl_cancelRowSpans(me);
+ me->rowgroup_align = (short) alignment;
+ return 0; /* that's all! */
+}
+
+int Stbl_finishTABLE(STable_info *me)
+{
+ STable_states *s = &me->s;
+ int i;
+ int curpos = 0;
+
+ CTRACE2(TRACE_TRST, (tfp, "TRST:Stbl_finishTABLE()\n"));
+ if (!me || me->nrows <= 0 || me->ncols <= 0) {
+ return -1;
+ }
+ if (me->nrows > 0 && me->rows[me->nrows - 1].ncells > 0) {
+ if (s->pending_len > 0)
+ me->rows[me->nrows - 1].cells[
+ me->rows[me->nrows - 1].ncells - 1
+ ].len = s->pending_len;
+ s->pending_len = 0;
+ }
+ Stbl_finishRowInTable(me);
+ /* take into account offsets on multi-line cells.
+ XXX We cannot do it honestly, since two cells on the same row may
+ participate in multi-line table entries, and we preserve only
+ one offset per row. This implementation may ignore
+ horizontal offsets for the last row of a multirow table entry. */
+ for (i = 0; i < me->nrows - 1; i++) {
+ int j = i + 1, leading = i, non_empty = 0;
+ STable_rowinfo *nextrow = me->rows + j;
+ int minoffset, have_offsets;
+ int foundcell = -1, max_width;
+
+ if ((nextrow->content & (IS_CONTINUATION_OF_CELL | HAS_BEG_OF_CELL | BELIEVE_OFFSET))
+ != (IS_CONTINUATION_OF_CELL | BELIEVE_OFFSET))
+ continue; /* Not a continuation line */
+ minoffset = nextrow[-1].offset; /* Line before first continuation */
+ CTRACE2(TRACE_TRST, (tfp,
+ "TRST:Stbl_finishTABLE, l=%d, offset=%d, ended=%u.\n",
+ i, nextrow[-1].offset, nextrow[-1].ended));
+
+ /* Find the common part of the requested offsets */
+ while (j < me->nrows
+ && ((nextrow->content &
+ (IS_CONTINUATION_OF_CELL
+ | HAS_BEG_OF_CELL
+ | BELIEVE_OFFSET))
+ == (IS_CONTINUATION_OF_CELL | BELIEVE_OFFSET))) {
+ if (minoffset > nextrow->offset)
+ minoffset = nextrow->offset;
+ CTRACE2(TRACE_TRST,
+ (tfp,
+ "TRST:Stbl_finishTABLE, l=%d, offset=%d, ended=%u.\n",
+ j, nextrow->offset, nextrow[-1].ended));
+ nextrow++;
+ j++;
+ }
+ i = j - 1; /* Continue after this line */
+ /* Cancel the common part of offsets */
+ j = leading; /* Restart */
+ nextrow = me->rows + j; /* Line before first continuation */
+ have_offsets = 0;
+ nextrow->content |= OFFSET_IS_VALID_LAST_CELL;
+ while (j <= i) { /* A continuation line */
+ nextrow->offset -= minoffset;
+ nextrow->content |= OFFSET_IS_VALID;
+ if (nextrow->offset)
+ have_offsets = 1;
+ nextrow++;
+ j++;
+ }
+ if (!have_offsets)
+ continue; /* No offsets to deal with */
+
+ /* Find the cell number */
+ foundcell = -1;
+ j = leading + 1; /* Restart */
+ nextrow = me->rows + j; /* First continuation line */
+ while (foundcell == -1 && j <= i) { /* A continuation line */
+ int curcell = -1;
+
+ while (foundcell == -1 && ++curcell < nextrow->ncells)
+ if (nextrow->cells[curcell].len)
+ foundcell = curcell, non_empty = j;
+ nextrow++;
+ j++;
+ }
+ if (foundcell == -1) /* Can it happen? */
+ continue;
+ /* Find the max width */
+ max_width = 0;
+ j = leading; /* Restart */
+ nextrow = me->rows + j; /* Include the pre-continuation line */
+ while (j <= i) { /* A continuation line */
+ if (nextrow->ncells > foundcell) {
+ int curwid = nextrow->cells[foundcell].len + nextrow->offset;
+
+ if (curwid > max_width)
+ max_width = curwid;
+ }
+ nextrow++;
+ j++;
+ }
+ /* Update the widths */
+ j = non_empty; /* Restart from the first nonempty */
+ nextrow = me->rows + j;
+ /* Register the increase of the width */
+ update_sumcols0(me->sumcols, me->rows + non_empty,
+ 0 /* width only */ , max_width,
+ foundcell, nextrow->cells[foundcell].colspan,
+ me->allocated_sumcols);
+ j = leading; /* Restart from pre-continuation */
+ nextrow = me->rows + j;
+ while (j <= i) { /* A continuation line */
+ if (nextrow->ncells > foundcell)
+ nextrow->cells[foundcell].len = max_width;
+ nextrow++;
+ j++;
+ }
+ } /* END of Offsets processing */
+
+ for (i = 0; i < me->ncols; i++) {
+ if (me->sumcols[i].pos < curpos) {
+ me->sumcols[i].pos = curpos;
+ } else {
+ curpos = me->sumcols[i].pos;
+ }
+ if (me->sumcols[i].len > 0) {
+ curpos += me->sumcols[i].len;
+ }
+ }
+ /* need to recheck curpos: though it is checked each time a cell
+ is added, sometimes the result is ignored, as in split_line(). */
+ return (curpos > MAX_STBL_POS ? -1 : me->ncols);
+}
+
+short Stbl_getAlignment(STable_info *me)
+{
+ return (short) (me ? me->alignment : HT_ALIGN_NONE);
+}
+
+static int get_fixup_positions(STable_rowinfo *me, int *oldpos,
+ int *newpos,
+ STable_cellinfo *sumcols)
+{
+ int i = 0, ip = 0;
+ int next_i, newlen;
+ int ninserts;
+
+ if (!me)
+ return -1;
+ while (i < me->ncells) {
+ int offset;
+
+ next_i = i + HTMAX(1, me->cells[i].colspan);
+ if (me->cells[i].cLine != me->Line) {
+ if (me->cells[i].cLine > me->Line)
+ break;
+ i = next_i;
+ continue;
+ }
+ oldpos[ip] = me->cells[i].pos;
+ if ((me->content & OFFSET_IS_VALID)
+ && (i == me->ncells - 1
+ || !((me->content & OFFSET_IS_VALID_LAST_CELL))))
+ offset = me->offset;
+ else
+ offset = 0;
+ newpos[ip] = sumcols[i].pos + offset;
+ if ((me->cells[i].alignment == HT_CENTER ||
+ me->cells[i].alignment == HT_RIGHT) &&
+ me->cells[i].len > 0) {
+ newlen = sumcols[next_i].pos - newpos[ip] - 1;
+ newlen = HTMAX(newlen, sumcols[i].len);
+ if (me->cells[i].len < newlen) {
+ if (me->cells[i].alignment == HT_RIGHT) {
+ newpos[ip] += newlen - me->cells[i].len;
+ } else {
+ newpos[ip] += (newlen - me->cells[i].len) / 2;
+ }
+ }
+ }
+ ip++;
+ i = next_i;
+ }
+ ninserts = ip;
+ return ninserts;
+}
+
+/*
+ * Returns -1 if we have no row for this lineno, or for other error,
+ * 0 or greater (number of oldpos/newpos pairs) if we have
+ * a table row.
+ */
+int Stbl_getFixupPositions(STable_info *me, int lineno,
+ int *oldpos,
+ int *newpos)
+{
+ STable_rowinfo *row;
+ int j;
+ int ninserts = -1;
+
+ if (!me || !me->nrows)
+ return -1;
+ for (j = 0; j < me->nrows; j++) {
+ row = me->rows + j;
+ if (row->Line == lineno) {
+ ninserts = get_fixup_positions(row, oldpos, newpos,
+ me->sumcols);
+ break;
+ }
+ }
+ return ninserts;
+}
+
+int Stbl_getStartLine(STable_info *me)
+{
+ if (!me)
+ return -1;
+ else
+ return me->startline;
+}
+
+#ifdef EXP_NESTED_TABLES
+
+int Stbl_getStartLineDeep(STable_info *me)
+{
+ if (!me)
+ return -1;
+ while (me->enclosing)
+ me = me->enclosing;
+ return me->startline;
+}
+
+void Stbl_update_enclosing(STable_info *me, int max_width,
+ int last_lineno)
+{
+ int l;
+
+ if (!me || !me->enclosing || !max_width)
+ return;
+ CTRACE2(TRACE_TRST,
+ (tfp, "TRST:Stbl_update_enclosing, width=%d, lines=%d...%d.\n",
+ max_width, me->startline, last_lineno));
+ for (l = me->startline; l <= last_lineno; l++) {
+ /* Fake <BR> in appropriate positions */
+ if (Stbl_finishCellInTable(me->enclosing,
+ TRST_ENDCELL_LINEBREAK,
+ l,
+ 0,
+ max_width) < 0) {
+ /* It is not handy to let the caller delete me->enclosing,
+ and it does not buy us anything. Do it directly. */
+ STable_info *stbl = me->enclosing;
+
+ CTRACE2(TRACE_TRST, (tfp,
+ "TRST:Stbl_update_enclosing: width too large, aborting enclosing\n"));
+ me->enclosing = 0;
+ while (stbl) {
+ STable_info *enclosing = stbl->enclosing;
+
+ Stbl_free(stbl);
+ stbl = enclosing;
+ }
+ break;
+ }
+ }
+ return;
+}
+
+void Stbl_set_enclosing(STable_info *me, STable_info *enclosing, struct _TextAnchor *enclosing_last_anchor_before_stbl)
+{
+ if (!me)
+ return;
+ me->enclosing = enclosing;
+ me->enclosing_last_anchor_before_stbl = enclosing_last_anchor_before_stbl;
+}
+
+STable_info *Stbl_get_enclosing(STable_info *me)
+{
+ if (!me)
+ return 0;
+ return me->enclosing;
+}
+
+struct _TextAnchor *Stbl_get_last_anchor_before(STable_info *me)
+{
+ if (!me)
+ return 0;
+ return me->enclosing_last_anchor_before_stbl;
+}
+#endif
diff --git a/src/TRSTable.h b/src/TRSTable.h
new file mode 100644
index 0000000..ffebf4a
--- /dev/null
+++ b/src/TRSTable.h
@@ -0,0 +1,50 @@
+/* $LynxId: TRSTable.h,v 1.16 2010/09/25 11:40:05 tom Exp $ */
+#ifndef TRSTABLE_H
+#define TRSTABLE_H
+
+#include <HTUtils.h>
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+/* TRST_MAXCOLSPAN and TRST_MAXCOLSPAN are defined in userdefs.h */ typedef struct _STable_info STable_info;
+ extern STable_info *Stbl_startTABLE(int);
+ extern int Stbl_finishTABLE(STable_info *);
+ extern void Stbl_free(STable_info *);
+ extern int Stbl_addRowToTable(STable_info *, int, int);
+ extern int Stbl_addCellToTable(STable_info *, int, int, int, int, int,
+ int, int);
+ extern int Stbl_finishCellInTable(STable_info *, int, int, int, int);
+ extern int Stbl_addColInfo(STable_info *, int, int, int);
+ extern int Stbl_finishColGroup(STable_info *);
+ extern int Stbl_addRowGroup(STable_info *, int);
+
+#define TRST_ENDCELL_ENDTD 1
+#define TRST_ENDCELL_LINEBREAK 0
+#define TRST_ENDCELL_MASK 1
+#define TRST_FAKING_CELLS 2
+#define Stbl_lineBreak(stbl,l,off,pos) Stbl_finishCellInTable(stbl, TRST_ENDCELL_LINEBREAK, l, off, pos)
+
+ extern int Stbl_getStartLine(STable_info *);
+ extern int Stbl_getFixupPositions(STable_info *me, int lineno,
+ int *oldpos,
+ int *newpos);
+ extern short Stbl_getAlignment(STable_info *);
+
+#ifdef EXP_NESTED_TABLES
+ extern void Stbl_update_enclosing(STable_info *me, int max_width,
+ int last_lineno);
+ struct _TextAnchor;
+ extern void Stbl_set_enclosing(STable_info *me, STable_info *encl, struct _TextAnchor *last_anchor);
+ extern STable_info *Stbl_get_enclosing(STable_info *me);
+ extern struct _TextAnchor *Stbl_get_last_anchor_before(STable_info *me);
+ extern int Stbl_getStartLineDeep(STable_info *);
+
+#else
+#define Stbl_getStartLineDeep(t) Stbl_getStartLine(t)
+#endif
+
+#ifdef __cplusplus
+}
+#endif
+#endif /* TRSTABLE_H */
diff --git a/src/UCAuto.c b/src/UCAuto.c
new file mode 100644
index 0000000..2b17591
--- /dev/null
+++ b/src/UCAuto.c
@@ -0,0 +1,816 @@
+/*
+ * $LynxId: UCAuto.c,v 1.56 2021/06/09 22:29:43 tom Exp $
+ *
+ * This file contains code for changing the Linux console mode.
+ * Currently some names for font files are hardwired in here.
+ * You have to change this code if it needs accommodation for your
+ * system (or get the required files...).
+ *
+ * Depending on the Display Character Set switched to, and the previous
+ * one as far as it is known, system("setfont ...") and/or output of
+ * escape sequences to switch console mode are done. Curses will be
+ * temporarily suspended while that happens.
+ *
+ * NOTE that the setfont calls will also affect all other virtual consoles.
+ *
+ * Any ideas how to do this for other systems?
+ */
+
+#include <HTUtils.h>
+#include <LYUtils.h>
+
+#include <UCMap.h>
+#include <UCDefs.h>
+#include <UCAuto.h>
+#include <LYGlobalDefs.h>
+#include <LYStrings.h>
+#include <LYClean.h>
+#include <LYLeaks.h>
+#include <LYCharSets.h>
+
+#ifdef EXP_CHARTRANS_AUTOSWITCH
+
+#include <HTFile.h>
+#include <www_wait.h>
+
+#ifdef LINUX
+#include <sysexits.h> /* EX_DATAERR, etc. */
+#endif
+
+# ifdef CAN_SWITCH_DISPLAY_CHARSET
+char *charset_switch_rules;
+char *charsets_directory;
+int auto_other_display_charset = -1;
+int codepages[2];
+int real_charsets[2] =
+{-1, -1}; /* Non "auto-" charsets for the cps */
+int switch_display_charsets;
+
+# endif
+
+#ifdef HAVE_USE_LEGACY_CODING
+static int original_coding = 0;
+#endif
+
+# ifdef __EMX__
+/* If we "just include" <os2.h>, BOOLEAN conflicts. */
+# define BOOLEAN OS2_BOOLEAN /* This file doesn't use it, conflicts */
+# define INCL_VIO /* I want some Vio functions.. */
+# define INCL_DOSPROCESS /* TIB PIB. */
+# define INCL_DOSNLS /* DosQueryCp. */
+# include <os2.h> /* Misc stuff.. */
+# include <os2thunk.h> /* 32 bit to 16 bit pointer conv */
+# endif
+
+#ifdef LINUX
+typedef enum {
+ Is_Unset,
+ Is_Set,
+ Dunno,
+ Dont_Care
+} TGen_state_t;
+
+/*
+ * List the states the console has been set to via SCS (select character-set).
+ */
+typedef enum {
+ GN_Blat1, /* Latin-1 */
+ GN_Ucp437, /* PC -> PC */
+ GN_Kuser, /* user-defined */
+ GN_dunno,
+ GN_dontCare
+} TTransT_t;
+
+static char *T_font_fn = NULL; /* font filename */
+static char *T_umap_fn = NULL; /* unicode-map filename */
+
+#define NOOUTPUT "2>/dev/null >/dev/null"
+
+/*
+ * Return the configured path of the setfont/consolechars program.
+ */
+static const char *GetSetfontPath(void)
+{
+ return HTGetProgramPath(ppSETFONT);
+}
+
+/*
+ * setfont and consolechars have different options and available data.
+ */
+static BOOL isSetFont(void)
+{
+ const char *program = GetSetfontPath();
+ const char *slash = strrchr(program, '/');
+ const char *leaf = (slash ? slash + 1 : program);
+
+ return (BOOL) !strcmp(leaf, "setfont");
+}
+
+/*
+ * Here are the differences in options which affect lynx:
+ */
+#define setfont_u() (isSetFont() ? "-u " : "--sfm ")
+#define setfont_o() (isSetFont() ? "-o " : "--old-font-raw ")
+#define setfont_ou() (isSetFont() ? "-ou " : "--old-sfm ")
+#define console_font() (isSetFont() ? "" : "--font ")
+
+/*
+ * call_setfont - execute "setfont" command via system()
+ * returns: 0 ok (as far as we know)
+ * -1 error (assume font and umap are not loaded)
+ * 1 error with umap (assume font loaded but umap empty)
+ */
+static int call_setfont(const char *font,
+ const char *fnsuffix,
+ const char *umap)
+{
+ const char *program = GetSetfontPath();
+ char *T_setfont_cmd = NULL;
+ int rv;
+
+ /*
+ * console-data package has only a few unicode maps.
+ */
+ if (!isSetFont())
+ umap = 0;
+
+ if ((font && T_font_fn && !strcmp(font, T_font_fn))
+ && (umap && T_umap_fn && !strcmp(umap, T_umap_fn))) {
+ /*
+ * No need to repeat.
+ */
+ return 0;
+ }
+ if (font)
+ StrAllocCopy(T_font_fn, font);
+ if (umap)
+ StrAllocCopy(T_umap_fn, umap);
+
+ if (!*fnsuffix)
+ fnsuffix = "";
+
+ if (non_empty(umap) && non_empty(font)) {
+ HTSprintf0(&T_setfont_cmd, "%s %s%s%s %s%s %s",
+ program,
+ console_font(), font, fnsuffix,
+ setfont_u(), umap,
+ NOOUTPUT);
+ } else if (non_empty(font)) {
+ HTSprintf0(&T_setfont_cmd, "%s %s%s%s %s",
+ program,
+ console_font(), font, fnsuffix,
+ NOOUTPUT);
+ } else if (non_empty(umap)) {
+ HTSprintf0(&T_setfont_cmd, "%s %s%s %s",
+ program,
+ setfont_u(), umap,
+ NOOUTPUT);
+ }
+
+ if (T_setfont_cmd) {
+ CTRACE((tfp, "Changing font: '%s'\n", T_setfont_cmd));
+ rv = LYSystem(T_setfont_cmd);
+ FREE(T_setfont_cmd);
+ if (rv) {
+ CTRACE((tfp, "call_setfont: system returned %d (0x%x)!\n",
+ rv, (unsigned) rv));
+ if (rv == -1 || WIFSIGNALED(rv) || !WIFEXITED(rv)) {
+ return -1;
+ } else if ((WEXITSTATUS(rv) == EX_DATAERR ||
+ WEXITSTATUS(rv) == EX_NOINPUT) &&
+ non_empty(umap)) {
+ /*
+ * Check if the font was loaded ok but something was wrong with
+ * the umap file.
+ */
+ return 1;
+ } else {
+ return -1;
+ }
+ }
+ }
+ return 0;
+}
+
+static void write_esc(const char *p)
+{
+ int fd = open("/dev/tty", O_WRONLY);
+
+ if (fd >= 0) {
+ IGNORE_RC(write(fd, p, strlen(p)));
+ close(fd);
+ }
+}
+
+static int nonempty_file(const char *p)
+{
+ struct stat sb;
+
+ return (stat(p, &sb) == 0 &&
+ S_ISREG(sb.st_mode) &&
+ (sb.st_size != 0));
+}
+
+static BOOL on_console(void)
+{
+ if ((non_empty(x_display)) ||
+ LYgetXDisplay() != NULL) {
+ /*
+ * We won't do anything in an xterm. Better that way...
+ */
+ return FALSE;
+ }
+ return TRUE;
+}
+
+/*
+ * This is the thing that actually gets called from display_page().
+ */
+void UCChangeTerminalCodepage(int newcs,
+ LYUCcharset *p)
+{
+ const char *program = GetSetfontPath();
+ static int lastcs = -1;
+ static const char *lastname = NULL;
+ static TTransT_t lastTransT = GN_dunno;
+ static TGen_state_t lastUtf = Dunno;
+ static TGen_state_t lastHasUmap = Dunno;
+
+ static char *old_font = NULL;
+ static char *old_umap = NULL;
+
+ const char *name;
+ TTransT_t TransT = GN_dunno;
+ TGen_state_t Utf = Dunno;
+ TGen_state_t HasUmap = Dunno;
+
+ char *tmpbuf1 = NULL;
+ char *tmpbuf2 = NULL;
+ int status = 0;
+
+ if (!on_console())
+ return;
+
+#ifdef HAVE_USE_LEGACY_CODING
+ if (newcs < 0) {
+ use_legacy_coding(original_coding);
+ } else {
+ original_coding = use_legacy_coding(2);
+ }
+#endif
+
+ /*
+ * Restore the original character set.
+ */
+ if (newcs < 0 || p == 0) {
+ if (non_empty(old_font) &&
+ non_empty(old_umap)) {
+
+ if (nonempty_file(old_font)) {
+ if (nonempty_file(old_umap)) {
+ HTSprintf0(&tmpbuf1, "%s %s%s %s%s %s",
+ program,
+ console_font(), old_font,
+ setfont_u(), old_umap,
+ NOOUTPUT);
+ } else {
+ HTSprintf0(&tmpbuf1, "%s %s%s %s",
+ program,
+ console_font(), old_font,
+ NOOUTPUT);
+ }
+ CTRACE((tfp, "Restoring font: '%s'\n", tmpbuf1));
+ status = LYSystem(tmpbuf1);
+ if (status != 0) {
+ CTRACE((tfp, "...system returned %d (0x%x)\n", status,
+ (unsigned) status));
+ }
+ FREE(tmpbuf1);
+ }
+ }
+ if (newcs < 0 && p == 0) {
+ if (old_font) {
+ (void) LYRemoveTemp(old_font);
+ FREE(old_font);
+ }
+ if (old_umap) {
+ (void) LYRemoveTemp(old_umap);
+ FREE(old_umap);
+ }
+ if (status == 0) {
+ FREE(T_font_fn);
+ FREE(T_umap_fn);
+ }
+ }
+ return;
+ } else if (lastcs < 0 && old_umap == 0 && old_font == 0) {
+ FILE *fp1;
+ FILE *fp2 = NULL;
+
+ if ((old_font = typecallocn(char, LY_MAXPATH)) != 0)
+ old_umap = typecallocn(char, LY_MAXPATH);
+
+ if (old_font == NULL)
+ outofmem(__FILE__, "UCChangeTerminalCodepage");
+
+ if ((fp1 = LYOpenTemp(old_font, ".fnt", BIN_W)) != 0)
+ fp2 = LYOpenTemp(old_umap, ".uni", BIN_W);
+
+ if (fp1 && fp2) {
+ size_t nlen;
+ int rv;
+
+ HTSprintf0(&tmpbuf1, "%s %s%s %s%s %s",
+ program,
+ setfont_o(), old_font,
+ setfont_ou(), old_umap,
+ NOOUTPUT);
+
+ CTRACE((tfp, "Saving font: '%s'\n", tmpbuf1));
+ rv = LYSystem(tmpbuf1);
+ if (rv != 0) {
+ CTRACE((tfp, "...system returned %d (0x%x)\n", rv, (unsigned) rv));
+ }
+ FREE(tmpbuf1);
+ LYCloseTempFP(fp1);
+ LYCloseTempFP(fp2);
+
+ /* free up a few bytes */
+ if ((nlen = strlen(old_font) + 1) < LY_MAXPATH)
+ old_font = typeRealloc(char, old_font, nlen);
+
+ if ((nlen = strlen(old_umap) + 1) < LY_MAXPATH)
+ old_umap = typeRealloc(char, old_umap, nlen);
+ } else {
+ if (fp1)
+ (void) LYRemoveTemp(old_font);
+ FREE(old_font);
+ FREE(old_umap);
+ }
+ }
+
+ name = p->MIMEname;
+
+ /*
+ * Font sizes are currently hardwired here.
+ */
+#define SUFF1 ".f16"
+#define SUFF2 "-16.psf"
+#define SUFF3 "-8x16"
+#define SUFF4 "8x16"
+#define SUFF5 ".cp -16"
+#define SUFF6 "_8x16"
+
+ /* NOTE: `!!umap not in kbd!!' comments below means that the *.uni file
+ * is not found in kbd package. Reference Debian Package: kbd-data,
+ * Version: 0.96a-14. They should be located elsewhere or generated.
+ * Also some cpNNN fonts used below are not in the kbd-data. - kw
+ */
+
+ if (!StrNCmp(name, "iso-8859-1", 10) &&
+ (!name[10] || !isdigit(UCH(name[10])))) {
+ if ((lastHasUmap == Is_Set) && !strcmp(lastname, "cp850")) {
+ /*
+ * cp850 already contains all latin1 characters.
+ */
+ if (lastTransT != GN_Blat1) {
+ TransT = GN_Blat1;
+ }
+ } else {
+ /*
+ * "setfont lat1u-16.psf -u lat1u.uni"
+ */
+ status = call_setfont("lat1u", SUFF2, "lat1u.uni");
+ HasUmap = Is_Set;
+ if (lastTransT != GN_Blat1) {
+ TransT = GN_Blat1;
+ }
+ }
+ Utf = Is_Unset;
+ } else if (!strcmp(name, "iso-8859-2")) {
+ /*
+ * "setfont iso02.f16 -u iso02.uni"
+ */
+ status = call_setfont("iso02", SUFF1, "iso02.uni");
+ TransT = GN_Kuser;
+ HasUmap = Is_Set;
+ Utf = Is_Unset;
+ } else if (!strcmp(name, "iso-8859-15")) {
+ /*
+ * "setfont lat0-16.psf"
+ */
+ status = call_setfont("lat0", SUFF2, NULL);
+ TransT = GN_Blat1; /* bogus! */
+ HasUmap = Dunno; /* distributed lat0 files have bogus map data! */
+ Utf = Is_Unset;
+ } else if (!StrNCmp(name, "iso-8859-", 9)) {
+ if (strlen(name) <= 10 || !isdigit(UCH(name[10])))
+ HTSprintf0(&tmpbuf1, "iso0%s", &name[9]);
+ else
+ HTSprintf0(&tmpbuf1, "iso%s", &name[9]);
+ HTSprintf0(&tmpbuf2, "%s.uni", tmpbuf1);
+ /*
+ * "setfont iso0N.f16 -u iso0N.uni"
+ */
+ status = call_setfont(tmpbuf1, SUFF1, tmpbuf2);
+ FREE(tmpbuf1);
+ FREE(tmpbuf2);
+ TransT = GN_Kuser;
+ HasUmap = Is_Set;
+ Utf = Is_Unset;
+ } else if (!strcmp(name, "koi8-r")) {
+ /*
+ * "setfont koi8-8x16"
+ * !!umap not in kbd!!
+ */
+ status = call_setfont("koi8", SUFF3, "koi8r.uni");
+ TransT = GN_Kuser;
+ HasUmap = Is_Set;
+ Utf = Is_Unset;
+ } else if (!strcmp(name, "koi8-u")) {
+ /*
+ * "setfont koi8u_8x16"
+ * !!umap not in kbd!!
+ */
+ status = call_setfont("koi8u", SUFF6, "koi8u.uni");
+ TransT = GN_Kuser;
+ HasUmap = Is_Set;
+ Utf = Is_Unset;
+ } else if (!strcmp(name, "cp437")) {
+ /*
+ * "setfont default8x16 -u cp437.uni"
+ */
+ status = call_setfont("default", SUFF4, "cp437.uni");
+ if (lastTransT == GN_Kuser || lastTransT == GN_Ucp437)
+ TransT = GN_dontCare;
+ else
+ TransT = GN_Ucp437;
+ HasUmap = Is_Set;
+ Utf = Is_Unset;
+ } else if (!strcmp(name, "cp850")) {
+ /*
+ * "setfont cp850-8x16 -u cp850.uni"
+ * !!umap not in kbd!!
+ */
+ status = call_setfont("cp850", SUFF3, "cp850.uni");
+ TransT = GN_Kuser;
+ HasUmap = Is_Set;
+ Utf = Is_Unset;
+ } else if (!strcmp(name, "cp866") ||
+ !strcmp(name, "cp852") ||
+ !strcmp(name, "cp862")) { /* MS-Kermit has these files */
+ HTSprintf0(&tmpbuf2, "%s.uni", name);
+ /*
+ * "setfont cpNNN.f16"
+ * !!umap not in kbd!!
+ */
+ status = call_setfont(name, SUFF1, tmpbuf2);
+ FREE(tmpbuf2);
+ TransT = GN_Kuser;
+ HasUmap = Is_Set;
+ Utf = Is_Unset;
+ } else if (!strcmp(name, "cp737")) {
+ /*
+ * "setfont cp737.cp"
+ * !!umap not in kbd!!
+ */
+ if (isSetFont()) {
+ status = call_setfont("737", SUFF5, "cp737.uni");
+ } else {
+ status = call_setfont("greek", "", "cp737.uni");
+ }
+ TransT = GN_Kuser;
+ HasUmap = Is_Set;
+ Utf = Is_Unset;
+ } else if (!strcmp(name, "cp857")) {
+ status = call_setfont("cp857", SUFF3, "cp857.uni");
+ TransT = GN_Kuser;
+ HasUmap = Is_Set;
+ Utf = Is_Unset;
+ } else if (!strcmp(name, "x-transparent")) {
+ Utf = Dont_Care;
+ } else if (!strcmp(name, "us-ascii")) {
+ Utf = Dont_Care;
+ } else if (!StrNCmp(name, "mnem", 4)) {
+ Utf = Dont_Care;
+ }
+
+ if (status == 1)
+ HasUmap = Is_Unset;
+ else if (status < 0) {
+ if (HasUmap == Is_Set)
+ HasUmap = Dunno;
+ name = "unknown-8bit";
+ }
+
+ if (TransT != lastTransT) {
+ if (TransT == GN_Blat1) {
+ /*
+ * Switch Linux console to lat1 table.
+ */
+ write_esc("\033(B");
+ } else if (TransT == GN_Ucp437) {
+ /*
+ * Switch Linux console to 437 table?
+ */
+ write_esc("\033(U");
+ } else if (TransT == GN_Kuser) {
+ /*
+ * Switch Linux console to user table.
+ */
+ write_esc("\033(K");
+ }
+ if (TransT != GN_dunno && TransT != GN_dontCare) {
+ lastTransT = TransT;
+ }
+ }
+
+ if (HasUmap != Dont_Care && HasUmap != Dunno)
+ lastHasUmap = HasUmap;
+
+ if (p->enc == UCT_ENC_UTF8) {
+ if (lastUtf != Is_Set) {
+ Utf = Is_Set;
+ /*
+ * Turn Linux console UTF8 mode ON.
+ */
+ write_esc("\033%G");
+ lastUtf = Utf;
+ }
+ return;
+ } else if (lastUtf == Is_Set && Utf != Dont_Care) {
+ Utf = Is_Unset;
+ /*
+ * Turn Linux console UTF8 mode OFF.
+ */
+ write_esc("\033%@");
+ lastUtf = Utf;
+ }
+
+ if (Utf != Dont_Care && Utf != Dunno)
+ lastUtf = Utf;
+
+ lastcs = newcs;
+ lastname = name;
+}
+
+#else /* Not LINUX: */
+/*
+ * This is the thing that actually gets called from display_page().
+ */
+void UCChangeTerminalCodepage(int newcs,
+ LYUCcharset *p)
+{
+#ifdef __EMX__
+ int res = 0;
+
+#ifdef HAVE_USE_LEGACY_CODING
+ if (newcs < 0) {
+ use_legacy_coding(original_coding);
+ } else {
+ original_coding = use_legacy_coding(2);
+ }
+#endif
+
+ if (newcs < 0)
+ newcs = auto_display_charset;
+ res = Switch_Display_Charset(newcs, SWITCH_DISPLAY_CHARSET_REALLY);
+ CTRACE((tfp,
+ "UCChangeTerminalCodepage: Switch_Display_Charset(%d) returned %d\n",
+ newcs, res));
+#else
+ CTRACE((tfp, "UCChangeTerminalCodepage: Called, but not implemented!"));
+#endif
+}
+#endif /* LINUX */
+
+#ifdef CAN_SWITCH_DISPLAY_CHARSET
+
+int Find_Best_Display_Charset(int ord)
+{
+ const char *name = LYCharSet_UC[ord].MIMEname;
+ char *s = charset_switch_rules, *r;
+ char buf[160];
+ static int lowercase;
+ int n = strlen(name), source = 1;
+
+ if (!s || !n)
+ return ord;
+ if (!lowercase++)
+ LYLowerCase(charset_switch_rules);
+ while (1) {
+ while (*s && StrChr(" \t,", *s))
+ s++; /* Go to start of a name or ':' */
+ if (!*s && source)
+ return ord; /* OK to find nothing */
+ if (!*s) {
+ sprintf(buf,
+ gettext("No destination for '%.80s' in CHARSET_SWITCH_RULES"),
+ name);
+ HTInfoMsg(buf);
+ return ord;
+ }
+ if (*s == ':') {
+ /* Before the replacement name */
+ while (*s && StrChr(" \t:", *s))
+ s++; /* Go to the replacement */
+ /* At start of the replacement name */
+ r = s;
+ while (*s && !StrChr(" \t,:", *s))
+ s++; /* Skip the replacement */
+ if (source)
+ continue;
+ break;
+ }
+ /* At start of the source name */
+ if (source && !strncasecomp(name, s, n) && StrChr(" \t,", s[n])) { /* Found! */
+ source = 0;
+ s += n;
+ continue; /* Look for the replacement */
+ }
+ while (*s && !StrChr(" \t,:", *s))
+ s++; /* Skip the other source names */
+ }
+ /* Here r point to the replacement, s to the end of the replacement. */
+ if (s >= r + sizeof(buf)) {
+ HTInfoMsg(gettext("Charset name in CHARSET_SWITCH_RULES too long"));
+ return ord;
+ }
+ LYStrNCpy(buf, r, s - r);
+ n = UCGetLYhndl_byMIME(buf);
+ if (n < 0) {
+ sprintf(buf,
+ gettext("Unknown charset name '%.*s' in CHARSET_SWITCH_RULES"),
+ s - r, r);
+ HTInfoMsg(buf);
+ return ord;
+ }
+ return n;
+}
+
+# ifdef __EMX__
+/* Switch display for the best fit for LYCharSet_UC[ord].
+ If really is MAYBE, the switch is tentative only, another switch may happen
+ before the actual display.
+
+ Returns the charset we switched to. */
+static int _Switch_Display_Charset(int ord, enum switch_display_charset_t really)
+{
+ const char *name;
+ unsigned short cp;
+ static int font_loaded_for = -1, old_h, old_w;
+ int rc, ord1;
+ UCHAR msgbuf[MAXPATHLEN + 80];
+
+ CTRACE((tfp, "_Switch_Display_Charset(cp=%d, really=%d).\n", ord, really));
+ /* Do not trust current_char_set unless REALLY, we fake it if MAYBE! */
+ if (ord == current_char_set && really == SWITCH_DISPLAY_CHARSET_MAYBE)
+ return ord;
+ if (ord == auto_other_display_charset
+ || ord == auto_display_charset || ord == font_loaded_for) {
+ if (really == SWITCH_DISPLAY_CHARSET_MAYBE)
+ return ord; /* Report success, to avoid flicker, switch later */
+ } else /* Currently supports only koi8-r to cp866 translation */
+ ord = Find_Best_Display_Charset(ord);
+
+ /* Ignore sizechange unless the font is loaded */
+ if (ord != font_loaded_for && really == SWITCH_DISPLAY_CHARSET_RESIZE)
+ return ord;
+
+ if (ord == real_charsets[0] || ord == real_charsets[1]) {
+ ord1 = (ord == real_charsets[1]
+ ? auto_other_display_charset : auto_display_charset);
+ if (really == SWITCH_DISPLAY_CHARSET_MAYBE)
+ return ord; /* Can switch later, report success to avoid flicker */
+ } else
+ ord1 = ord;
+ if (ord == current_char_set && really == SWITCH_DISPLAY_CHARSET_MAYBE)
+ return ord;
+
+ name = LYCharSet_UC[ord1].MIMEname;
+ if (ord1 == auto_other_display_charset || ord1 == auto_display_charset) {
+ retry:
+ rc = VioSetCp(0, codepages[ord1 == auto_other_display_charset], 0);
+ if (rc == 0)
+ goto report;
+ err:
+ sprintf(msgbuf, gettext("Can't change to '%s': err=%#x=%d"), name, rc, rc);
+ HTInfoMsg(msgbuf);
+ return -1;
+ }
+
+ /* Not a "prepared" codepage. Need to load the user font. */
+ if (charsets_directory) {
+ TIB *tib; /* Can't load font in a windowed-VIO */
+ PIB *pib;
+ VIOFONTINFO f[2];
+ VIOFONTINFO *font;
+ UCHAR b[1 << 17];
+ UCHAR *buf = b;
+ UCHAR fnamebuf[MAXPATHLEN];
+ FILE *file;
+ APIRET rc;
+ long i, j;
+
+ /* 0 means a FS protected-mode session */
+ if (font_loaded_for == -1 /* Did not try it yet */
+ && (DosGetInfoBlocks(&tib, &pib) || pib->pib_ultype != 0)) {
+ ord = ord1 = auto_display_charset;
+ goto retry;
+ }
+ /* Should not cross 64K boundaries: */
+ font = f;
+ if (((((ULONG) (char *) f) + sizeof(*font)) & 0xFFFF) < sizeof(*font))
+ font++;
+ if (((ULONG) buf) & 0xFFFF)
+ buf += 0x10000 - (((ULONG) buf) & 0xFFFF);
+ font->cb = sizeof(*font); /* How large is this structure */
+ font->type = 0; /* Not the BIOS, the loaded font. */
+ font->cbData = 65535; /* How large is my buffer? */
+ font->pbData = _emx_32to16(buf); /* Wants an 16:16 pointer */
+
+ rc = VioGetFont(font, 0); /* Retrieve data for current font */
+ if (rc) {
+ sprintf(msgbuf,
+ gettext("Can't fetch current font info: err=%#x=%d"), rc, rc);
+ HTInfoMsg(msgbuf);
+ ord = ord1 = auto_display_charset;
+ goto retry;
+ }
+ if (ord1 == font_loaded_for
+ && old_h == font->cyCell && old_w == font->cxCell) {
+ /* The same as the previous font */
+ if ((rc = VioSetCp(0, -1, 0))) /* -1: User font */
+ goto err;
+ goto report;
+ }
+ sprintf(fnamebuf, "%s/%dx%d/%s.fnt",
+ charsets_directory, font->cyCell, font->cxCell, name);
+ file = fopen(fnamebuf, BIN_R);
+ if (!file) {
+ sprintf(msgbuf, gettext("Can't open font file '%s'"), fnamebuf);
+ HTInfoMsg(msgbuf);
+ ord = ord1 = auto_display_charset;
+ goto retry;
+ }
+ i = ftell(file);
+ fseek(file, 0, SEEK_END);
+ if (ftell(file) - i != font->cbData) {
+ fclose(file);
+ sprintf(msgbuf, gettext("Mismatch of size of font file '%s'"), fnamebuf);
+ HTAlert(msgbuf);
+ ord = ord1 = auto_display_charset;
+ goto retry;
+ }
+ fseek(file, i, SEEK_SET);
+ fread(buf, 1, font->cbData, file);
+ fclose(file);
+ rc = VioSetFont(font, 0); /* Put it all back.. */
+ if (rc) {
+ sprintf(msgbuf, gettext("Can't set font: err=%#x=%d"), rc, rc);
+ HTInfoMsg(msgbuf);
+ ord = ord1 = auto_display_charset;
+ font_loaded_for = -1;
+ goto retry;
+ }
+ font_loaded_for = ord1;
+ old_h = font->cyCell;
+ old_w = font->cxCell;
+ } else {
+ ord = ord1 = auto_display_charset;
+ goto retry;
+ }
+ report:
+ CTRACE((tfp, "Display font set to '%s'.\n", name));
+ return ord;
+}
+# endif /* __EMX__ */
+
+int Switch_Display_Charset(const int ord, const enum switch_display_charset_t really)
+{
+ int prev = current_char_set;
+ int res;
+ static int repeated;
+
+ if (!switch_display_charsets)
+ return 0;
+ res = _Switch_Display_Charset(ord, really);
+ if (res < 0 || prev == res) /* No change */
+ return 0;
+ /* Register the change */
+ current_char_set = res;
+ HTMLUseCharacterSet(current_char_set);
+ return 1;
+}
+#endif /* CAN_SWITCH_DISPLAY_CHARSET */
+
+#else /* EXP_CHARTRANS_AUTOSWITCH not defined: */
+/*
+ * This is the thing that actually gets called from display_page().
+ */
+void UCChangeTerminalCodepage(int newcs GCC_UNUSED,
+ LYUCcharset *p GCC_UNUSED)
+{
+ CTRACE((tfp, "UCChangeTerminalCodepage: Called, but not implemented!"));
+}
+#endif /* EXP_CHARTRANS_AUTOSWITCH */
diff --git a/src/UCAuto.h b/src/UCAuto.h
new file mode 100644
index 0000000..98d2243
--- /dev/null
+++ b/src/UCAuto.h
@@ -0,0 +1,14 @@
+#ifndef UCAUTO_H
+#define UCAUTO_H
+
+#include <UCDefs.h>
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+ extern void UCChangeTerminalCodepage(int newcs, LYUCcharset *p);
+
+#ifdef __cplusplus
+}
+#endif
+#endif /* UCAUTO_H */
diff --git a/src/UCAux.c b/src/UCAux.c
new file mode 100644
index 0000000..a10f624
--- /dev/null
+++ b/src/UCAux.c
@@ -0,0 +1,798 @@
+/*
+ * $LynxId: UCAux.c,v 1.59 2024/01/15 11:24:17 tom Exp $
+ */
+#include <HTUtils.h>
+
+#include <HTCJK.h>
+#include <UCMap.h>
+#include <UCDefs.h>
+#include <HTStream.h>
+#include <UCAux.h>
+#include <LYCharSets.h>
+#include <LYCurses.h>
+#include <LYStrings.h>
+
+BOOL UCCanUniTranslateFrom(int from)
+{
+ if (from < 0)
+ return NO;
+#ifndef USE_JAPANESEUTF8_SUPPORT
+ if (LYCharSet_UC[from].enc == UCT_ENC_CJK)
+ return NO;
+#endif
+ if (!strcmp(LYCharSet_UC[from].MIMEname, "x-transparent"))
+ return NO;
+
+ /* others YES */
+ return YES;
+}
+
+BOOL UCCanTranslateUniTo(int to)
+{
+ if (to < 0)
+ return NO;
+
+ return YES; /* well at least some characters... */
+}
+
+BOOL UCCanTranslateFromTo(int from,
+ int to)
+{
+ if (from == to)
+ return YES;
+ if (from < 0 || to < 0)
+ return NO;
+ if (from == LATIN1)
+ return UCCanTranslateUniTo(to);
+ if (to == LATIN1 || LYCharSet_UC[to].enc == UCT_ENC_UTF8)
+ return UCCanUniTranslateFrom(from);
+ {
+ const char *fromname = LYCharSet_UC[from].MIMEname;
+ const char *toname = LYCharSet_UC[to].MIMEname;
+
+ if (!strcmp(fromname, "x-transparent") ||
+ !strcmp(toname, "x-transparent")) {
+ return YES; /* ??? */
+ } else if (!strcmp(fromname, "us-ascii")) {
+ return YES;
+ }
+ if (LYCharSet_UC[from].enc == UCT_ENC_CJK) {
+ /*
+ * CJK mode may be off (i.e., !IS_CJK_TTY) because the current
+ * document is not CJK, but the check may be for capability in
+ * relation to another document, for which CJK mode might be turned
+ * on when retrieved. Thus, when the from charset is CJK, check if
+ * the to charset is CJK, and return NO or YES in relation to that.
+ * - FM
+ */
+ if (LYCharSet_UC[to].enc != UCT_ENC_CJK)
+ return NO;
+ if ((!strcmp(toname, "euc-jp") ||
+ !strcmp(toname, "shift_jis")) &&
+ (!strcmp(fromname, "euc-jp") ||
+ !strcmp(fromname, "shift_jis")))
+ return YES;
+ /*
+ * The euc-cn and euc-kr charsets were handled by the (from == to)
+ * above, so we need not check those. - FM
+ */
+ return NO;
+ }
+ }
+ return YES; /* others YES */
+}
+
+/*
+ * Returns YES if no translation necessary (because
+ * charsets are equal, are equivalent, etc.).
+ */
+BOOL UCNeedNotTranslate(int from,
+ int to)
+{
+ const char *fromname;
+ const char *toname;
+
+ if (from == to)
+ return YES;
+ if (from < 0)
+ return NO; /* ??? */
+ if (LYCharSet_UC[from].enc == UCT_ENC_7BIT) {
+ return YES; /* Only 7bit chars. */
+ }
+ fromname = LYCharSet_UC[from].MIMEname;
+ if (!strcmp(fromname, "x-transparent") ||
+ !strcmp(fromname, "us-ascii")) {
+ return YES;
+ }
+ if (to < 0)
+ return NO; /* ??? */
+ if (to == LATIN1) {
+ if (LYCharSet_UC[from].codepoints & (UCT_CP_SUBSETOF_LAT1))
+ return YES;
+ }
+ toname = LYCharSet_UC[to].MIMEname;
+ if (!strcmp(toname, "x-transparent")) {
+ return YES;
+ }
+ if (LYCharSet_UC[to].enc == UCT_ENC_UTF8) {
+ return NO;
+ }
+ if (from == LATIN1) {
+ if (LYCharSet_UC[from].codepoints & (UCT_CP_SUPERSETOF_LAT1))
+ return YES;
+ }
+ if (LYCharSet_UC[from].enc == UCT_ENC_CJK) {
+ if (!IS_CJK_TTY) /* Use that global flag, for now. */
+ return NO;
+ if (HTCJK == JAPANESE &&
+ (!strcmp(fromname, "euc-jp") ||
+ !strcmp(fromname, "shift_jis")))
+ return YES; /* translate internally by lynx, no unicode */
+ return NO; /* If not handled by (from == to) above. */
+ }
+ return NO;
+}
+
+/*
+ * The idea here is that any stage of the stream pipe which is interested
+ * in some charset dependent processing will call this function.
+ * Given input and output charsets, this function will set various flags
+ * in a UCTransParams structure that _suggest_ to the caller what to do.
+ *
+ * Should be called once when a stage starts processing text (and the
+ * input and output charsets are known), or whenever one of input or
+ * output charsets has changed (e.g., by SGML.c stage after HTML.c stage
+ * has processed a META tag).
+ * The global flags (LYRawMode, HTPassEightBitRaw etc.) are currently
+ * not taken into account here (except for HTCJK, somewhat), it's still
+ * up to the caller to do something about them. - KW
+ */
+void UCSetTransParams(UCTransParams * pT, int cs_in,
+ const LYUCcharset *p_in,
+ int cs_out,
+ const LYUCcharset *p_out)
+{
+ CTRACE((tfp, "UCSetTransParams: from %s(%d) to %s(%d)\n",
+ p_in->MIMEname, UCGetLYhndl_byMIME(p_in->MIMEname),
+ p_out->MIMEname, UCGetLYhndl_byMIME(p_out->MIMEname)));
+
+ /*
+ * Initialize this element to FALSE, and set it TRUE below if we're dealing
+ * with VISCII. - FM
+ */
+ pT->trans_C0_to_uni = FALSE;
+
+ /*
+ * The "transparent" display character set is a "super raw mode". - FM
+ */
+ pT->transp = (BOOL) (!strcmp(p_in->MIMEname, "x-transparent") ||
+ !strcmp(p_out->MIMEname, "x-transparent"));
+
+ /*
+ * UCS-2 is handled as a special case in SGML_write().
+ */
+ pT->ucs_mode = 0;
+
+ if (pT->transp) {
+ /*
+ * Set up the structure for "transparent". - FM
+ */
+ pT->do_cjk = FALSE;
+ pT->decode_utf8 = FALSE;
+ pT->output_utf8 = FALSE; /* We may, but won't know about it. - KW */
+ pT->do_8bitraw = TRUE;
+ pT->use_raw_char_in = TRUE;
+ pT->strip_raw_char_in = FALSE;
+ pT->pass_160_173_raw = TRUE;
+ pT->repl_translated_C0 = (BOOL) (p_out->enc == UCT_ENC_8BIT_C0);
+ pT->trans_C0_to_uni = (BOOL) (p_in->enc == UCT_ENC_8BIT_C0 ||
+ p_out->enc == UCT_ENC_8BIT_C0);
+ } else {
+ /*
+ * Initialize local flags. - FM
+ */
+ BOOL intm_ucs = FALSE;
+ BOOL use_ucs = FALSE;
+
+ /*
+ * Set this element if we want to treat the input as CJK. - FM
+ */
+ pT->do_cjk = (BOOL) ((p_in->enc == UCT_ENC_CJK) &&
+ (
+ IS_CJK_TTY
+#ifdef EXP_CHINESEUTF8_SUPPORT
+ || !strcmp(p_in->MIMEname, "euc-cn")
+ || !strcmp(p_in->MIMEname, "big5")
+ || !strcmp(p_in->MIMEname, "euc-kr")
+#endif
+ )
+ );
+ /*
+ * Set these elements based on whether we are dealing with UTF-8. - FM
+ */
+ pT->decode_utf8 = (BOOL) (p_in->enc == UCT_ENC_UTF8);
+ pT->output_utf8 = (BOOL) (p_out->enc == UCT_ENC_UTF8);
+ if (pT->do_cjk) {
+ /*
+ * Set up the structure for a CJK input with
+ * a CJK output (IS_CJK_TTY). - FM
+ */
+ pT->trans_to_uni = FALSE;
+#ifdef EXP_CHINESEUTF8_SUPPORT
+ if (!strcmp(p_in->MIMEname, "euc-cn") ||
+ !strcmp(p_in->MIMEname, "big5") ||
+ !strcmp(p_in->MIMEname, "euc-kr")) {
+ pT->trans_to_uni = (BOOL) UCCanUniTranslateFrom(cs_in);
+ }
+#endif
+ pT->do_8bitraw = FALSE;
+ pT->pass_160_173_raw = TRUE;
+ pT->use_raw_char_in = FALSE; /* Not used for CJK. - KW */
+ pT->repl_translated_C0 = FALSE;
+ pT->trans_from_uni = FALSE; /* Not used for CJK. - KW */
+ } else {
+ /*
+ * Set up for all other charset combinations. The intm_ucs flag is
+ * set TRUE if the input charset is iso-8859-1 or UTF-8, or largely
+ * equivalent to them, i.e., if we have UCS without having to do a
+ * table translation.
+ */
+ intm_ucs = (BOOL) (cs_in == LATIN1 || pT->decode_utf8 ||
+ (p_in->codepoints &
+ (UCT_CP_SUBSETOF_LAT1 | UCT_CP_SUBSETOF_UCS2)));
+ /*
+ * pT->trans_to_uni is set TRUE if we do not have that as input
+ * already, and we can translate to Unicode. Note that UTF-8
+ * always is converted to Unicode in functions that use the
+ * transformation structure, so it is treated as already Unicode
+ * here.
+ */
+ pT->trans_to_uni = (BOOL) (!intm_ucs &&
+ UCCanUniTranslateFrom(cs_in));
+ /*
+ * We set this if we are translating to Unicode and what normally
+ * are low value control characters in fact are encoding octets for
+ * the input charset (presently, this applies to VISCII). - FM
+ */
+ pT->trans_C0_to_uni = (BOOL) (pT->trans_to_uni &&
+ p_in->enc == UCT_ENC_8BIT_C0);
+ /*
+ * We set this, presently, for VISCII. - FM
+ */
+ pT->repl_translated_C0 = (BOOL) (p_out->enc == UCT_ENC_8BIT_C0);
+ /*
+ * Currently unused for any charset combination.
+ * Should always be FALSE
+ */
+ pT->strip_raw_char_in = FALSE;
+ /*
+ * use_ucs should be set TRUE if we have or will create Unicode
+ * values for input octets or UTF multibytes. - FM
+ */
+ use_ucs = (BOOL) (intm_ucs || pT->trans_to_uni);
+ /*
+ * This is set TRUE if use_ucs was set FALSE. It is complementary
+ * to the HTPassEightBitRaw flag, which is set TRUE or FALSE
+ * elsewhere based on the raw mode setting in relation to the
+ * current Display Character Set. - FM
+ */
+ pT->do_8bitraw = (BOOL) (!use_ucs);
+ /*
+ * This is set TRUE when 160 and 173 should not be treated as nbsp
+ * and shy, respectively. - FM
+ */
+ pT->pass_160_173_raw = (BOOL) (!use_ucs &&
+ !(p_in->like8859 & UCT_R_8859SPECL));
+ /*
+ * This is set when the input and output charsets match, and they
+ * are not ones which should go through a Unicode translation
+ * process anyway. - FM
+ */
+ pT->use_raw_char_in = (BOOL) (!pT->output_utf8 &&
+ cs_in == cs_out &&
+ !pT->trans_C0_to_uni);
+ /*
+ * This should be set TRUE when we expect to have done translation
+ * to Unicode or had the equivalent as input, can translate it to
+ * our output charset, and normally want to do so. The latter
+ * depends on the pT->do_8bitraw and pT->use_raw_char_in values set
+ * above, but also on HTPassEightBitRaw in any functions which use
+ * the transformation structure.. - FM
+ */
+ pT->trans_from_uni = (BOOL) (use_ucs && !pT->do_8bitraw &&
+ !pT->use_raw_char_in &&
+ UCCanTranslateUniTo(cs_out));
+ }
+ }
+ CTRACE((tfp, "UCSetTransParams (done):\n"));
+ CTRACE((tfp, " transp: %d\n", pT->transp));
+ CTRACE((tfp, " do_cjk: %d\n", pT->do_cjk));
+ CTRACE((tfp, " decode_utf8: %d\n", pT->decode_utf8));
+ CTRACE((tfp, " output_utf8: %d\n", pT->output_utf8));
+ CTRACE((tfp, " do_8bitraw: %d\n", pT->do_8bitraw));
+ CTRACE((tfp, " use_raw_char_in: %d\n", pT->use_raw_char_in));
+ CTRACE((tfp, " strip_raw_char_in: %d\n", pT->strip_raw_char_in));
+ CTRACE((tfp, " pass_160_173_raw: %d\n", pT->pass_160_173_raw));
+ CTRACE((tfp, " trans_to_uni: %d\n", pT->trans_to_uni));
+ CTRACE((tfp, " trans_C0_to_uni: %d\n", pT->trans_C0_to_uni));
+ CTRACE((tfp, " repl_translated_C0: %d\n", pT->repl_translated_C0));
+ CTRACE((tfp, " trans_from_uni: %d\n", pT->trans_from_uni));
+}
+
+/*
+ * This function initializes the transformation
+ * structure by setting all its elements to
+ * FALSE. - KW
+ */
+void UCTransParams_clear(UCTransParams * pT)
+{
+ pT->transp = FALSE;
+ pT->do_cjk = FALSE;
+ pT->decode_utf8 = FALSE;
+ pT->output_utf8 = FALSE;
+ pT->do_8bitraw = FALSE;
+ pT->use_raw_char_in = FALSE;
+ pT->strip_raw_char_in = FALSE;
+ pT->pass_160_173_raw = FALSE;
+ pT->trans_to_uni = FALSE;
+ pT->trans_C0_to_uni = FALSE;
+ pT->repl_translated_C0 = FALSE;
+ pT->trans_from_uni = FALSE;
+}
+
+/*
+ * If terminal is in UTF-8 mode, it probably cannot understand box drawing
+ * chars as the 8-bit (n)curses handles them. (This may also be true for other
+ * display character sets, but isn't currently checked.) In that case set the
+ * chars for horizontal and vertical drawing chars to displayable ASCII chars
+ * if '0' was requested. They'll stay as they are otherwise. -KW, TD
+ *
+ * If we're able to obtain a character set based on the locale settings,
+ * assume that the user has setup $TERM and the fonts already so line-drawing
+ * works.
+ */
+void UCSetBoxChars(int cset,
+ int *pvert_out,
+ int *phori_out,
+ int vert_in,
+ int hori_in)
+{
+ BOOL fix_lines = FALSE;
+
+ if (cset >= 0) {
+#ifndef WIDEC_CURSES
+ if (LYCharSet_UC[cset].enc == UCT_ENC_UTF8) {
+ fix_lines = TRUE;
+ }
+#endif
+ /*
+ * If we've identified a charset that works, require it.
+ * This is important if we have loaded a font, which would
+ * confuse curses.
+ */
+ /* US-ASCII vs Latin-1 is safe (usually) */
+ if ((cset == US_ASCII
+ || cset == LATIN1)
+ && (linedrawing_char_set == US_ASCII
+ || linedrawing_char_set == LATIN1)) {
+#if (defined(FANCY_CURSES) && defined(A_ALTCHARSET)) || defined(USE_SLANG)
+ vert_in = 0;
+ hori_in = 0;
+#else
+ ;
+#endif
+ }
+#ifdef EXP_CHARTRANS_AUTOSWITCH
+#if defined(NCURSES_VERSION) || defined(HAVE_TIGETSTR)
+ else {
+ static BOOL first = TRUE;
+ static int last_cset = -99;
+ static BOOL last_result = TRUE;
+ /* *INDENT-OFF* */
+ static struct {
+ int mapping;
+ UCode_t internal;
+ int external;
+ } table[] = {
+ { 'j', 0x2518, 0 }, /* BOX DRAWINGS LIGHT UP AND LEFT */
+ { 'k', 0x2510, 0 }, /* BOX DRAWINGS LIGHT DOWN AND LEFT */
+ { 'l', 0x250c, 0 }, /* BOX DRAWINGS LIGHT DOWN AND RIGHT */
+ { 'm', 0x2514, 0 }, /* BOX DRAWINGS LIGHT UP AND RIGHT */
+ { 'n', 0x253c, 0 }, /* BOX DRAWINGS LIGHT VERTICAL AND HORIZONTAL */
+ { 'q', 0x2500, 0 }, /* BOX DRAWINGS LIGHT HORIZONTAL */
+ { 't', 0x251c, 0 }, /* BOX DRAWINGS LIGHT VERTICAL AND RIGHT */
+ { 'u', 0x2524, 0 }, /* BOX DRAWINGS LIGHT VERTICAL AND LEFT */
+ { 'v', 0x2534, 0 }, /* BOX DRAWINGS LIGHT UP AND HORIZONTAL */
+ { 'w', 0x252c, 0 }, /* BOX DRAWINGS LIGHT DOWN AND HORIZONTAL */
+ { 'x', 0x2502, 0 }, /* BOX DRAWINGS LIGHT VERTICAL */
+ };
+ /* *INDENT-ON* */
+
+ unsigned n;
+
+ if (first) {
+ static char acsc_name[] = "acsc";
+ char *map = tigetstr(acsc_name);
+
+ if (map != 0) {
+ CTRACE((tfp, "build terminal line-drawing map\n"));
+ while (map[0] != 0 && map[1] != 0) {
+ for (n = 0; n < TABLESIZE(table); ++n) {
+ if (table[n].mapping == map[0]) {
+ table[n].external = UCH(map[1]);
+ CTRACE((tfp,
+ " map[%c] %#" PRI_UCode_t " -> %#x\n",
+ table[n].mapping,
+ CAST_UCode_t (table[n].internal),
+ (unsigned)table[n].external));
+ break;
+ }
+ }
+ map += 2;
+ }
+ }
+ first = FALSE;
+ }
+
+ if (cset == last_cset) {
+ fix_lines = last_result;
+ } else if (cset == UTF8_handle) {
+ last_result = FALSE;
+ last_cset = cset;
+ } else {
+ CTRACE((tfp, "check terminal line-drawing map\n"));
+ for (n = 0; n < TABLESIZE(table); ++n) {
+ int test = UCTransUniChar(table[n].internal, cset);
+
+ if (test != table[n].external) {
+ CTRACE((tfp,
+ "line-drawing map %c mismatch (have %#x, want %#x)\n",
+ table[n].mapping,
+ (unsigned) test,
+ (unsigned) table[n].external));
+ fix_lines = TRUE;
+ break;
+ }
+ }
+ last_result = fix_lines;
+ last_cset = cset;
+ }
+ }
+#else
+ else if (cset != linedrawing_char_set && linedrawing_char_set >= 0) {
+ fix_lines = TRUE;
+ }
+#endif
+#endif
+ }
+ if (fix_lines) {
+ if (!vert_in)
+ vert_in = '|';
+ if (!hori_in)
+ hori_in = '-';
+ }
+ *pvert_out = vert_in;
+ *phori_out = hori_in;
+}
+
+/*
+ * Given an output target HTStream* (can also be a HTStructured* via
+ * typecast), the target stream's put_character method, and a Unicode
+ * character, CPutUtf8_charstring() will either output the UTF8
+ * encoding of the Unicode and return YES, or do nothing and return
+ * NO (if conversion would be unnecessary or the Unicode character is
+ * considered invalid).
+ *
+ * [Could be used more generally, but is currently only used for &#nnnnn
+ * stuff - generation of UTF8 from 8-bit encoded charsets not yet done
+ * by SGML.c etc.]
+ */
+#define PUTC(ch) ((*myPutc)(target, (char)(ch)))
+#define PUTC2(ch) ((*myPutc)(target,(char)(0x80|(0x3f &(ch)))))
+
+BOOL UCPutUtf8_charstring(HTStream *target, putc_func_t *myPutc, UCode_t code)
+{
+ if (code < 128)
+ return NO; /* indicate to caller we didn't handle it */
+ else if (code < 0x800L) {
+ PUTC(0xc0 | (code >> 6));
+ PUTC2(code);
+ } else if (code < 0x10000L) {
+ PUTC(0xe0 | (code >> 12));
+ PUTC2(code >> 6);
+ PUTC2(code);
+ } else if (code < 0x200000L) {
+ PUTC(0xf0 | (code >> 18));
+ PUTC2(code >> 12);
+ PUTC2(code >> 6);
+ PUTC2(code);
+ } else if (code < 0x4000000L) {
+ PUTC(0xf8 | (code >> 24));
+ PUTC2(code >> 18);
+ PUTC2(code >> 12);
+ PUTC2(code >> 6);
+ PUTC2(code);
+ } else if (code <= 0x7fffffffL) {
+ PUTC(0xfc | (code >> 30));
+ PUTC2(code >> 24);
+ PUTC2(code >> 18);
+ PUTC2(code >> 12);
+ PUTC2(code >> 6);
+ PUTC2(code);
+ } else
+ return NO;
+ return YES;
+}
+
+/*
+ * This function converts a Unicode (UCode_t) value
+ * to a multibyte UTF-8 character, which is loaded
+ * into the buffer received as an argument. The
+ * buffer should be large enough to hold at least
+ * seven characters (but should be declared as 8
+ * to minimize byte alignment problems with some
+ * compilers). - FM
+ */
+BOOL UCConvertUniToUtf8(UCode_t code, char *buffer)
+{
+ char *ch = buffer;
+
+ if (!ch)
+ return NO;
+
+ if (code <= 0 || code > 0x7fffffffL) {
+ *ch = '\0';
+ return NO;
+ }
+
+ if (code < 0x800L) {
+ *ch++ = (char) (0xc0 | (code >> 6));
+ *ch++ = (char) (0x80 | (0x3f & (code)));
+ *ch = '\0';
+ } else if (code < 0x10000L) {
+ *ch++ = (char) (0xe0 | (code >> 12));
+ *ch++ = (char) (0x80 | (0x3f & (code >> 6)));
+ *ch++ = (char) (0x80 | (0x3f & (code)));
+ *ch = '\0';
+ } else if (code < 0x200000L) {
+ *ch++ = (char) (0xf0 | (code >> 18));
+ *ch++ = (char) (0x80 | (0x3f & (code >> 12)));
+ *ch++ = (char) (0x80 | (0x3f & (code >> 6)));
+ *ch++ = (char) (0x80 | (0x3f & (code)));
+ *ch = '\0';
+ } else if (code < 0x4000000L) {
+ *ch++ = (char) (0xf8 | (code >> 24));
+ *ch++ = (char) (0x80 | (0x3f & (code >> 18)));
+ *ch++ = (char) (0x80 | (0x3f & (code >> 12)));
+ *ch++ = (char) (0x80 | (0x3f & (code >> 6)));
+ *ch++ = (char) (0x80 | (0x3f & (code)));
+ *ch = '\0';
+ } else {
+ *ch++ = (char) (0xfc | (code >> 30));
+ *ch++ = (char) (0x80 | (0x3f & (code >> 24)));
+ *ch++ = (char) (0x80 | (0x3f & (code >> 18)));
+ *ch++ = (char) (0x80 | (0x3f & (code >> 12)));
+ *ch++ = (char) (0x80 | (0x3f & (code >> 6)));
+ *ch++ = (char) (0x80 | (0x3f & (code)));
+ *ch = '\0';
+ }
+ return YES;
+}
+
+/*
+ * Get UCS character code for one character from UTF-8 encoded string.
+ *
+ * On entry:
+ * *ppuni should point to beginning of UTF-8 encoding character
+ * On exit:
+ * *ppuni is advanced to point to the last byte of UTF-8 sequence,
+ * if there was a valid one; otherwise unchanged.
+ * returns the UCS value
+ * returns negative value on error (invalid UTF-8 sequence)
+ */
+UCode_t UCGetUniFromUtf8String(const char **ppuni)
+{
+ UCode_t uc_out = 0;
+ const char *p = *ppuni;
+ int utf_count, i;
+
+ if (!(**ppuni & 0x80))
+ return (UCode_t) **ppuni; /* ASCII range character */
+ else if (!(**ppuni & 0x40))
+ return (-1); /* not a valid UTF-8 start */
+ if ((*p & 0xe0) == 0xc0) {
+ utf_count = 1;
+ } else if ((*p & 0xf0) == 0xe0) {
+ utf_count = 2;
+ } else if ((*p & 0xf8) == 0xf0) {
+ utf_count = 3;
+ } else if ((*p & 0xfc) == 0xf8) {
+ utf_count = 4;
+ } else if ((*p & 0xfe) == 0xfc) {
+ utf_count = 5;
+ } else { /* garbage */
+ return (-1);
+ }
+ for (p = *ppuni, i = 0; i < utf_count; i++) {
+ if ((*(++p) & 0xc0) != 0x80)
+ return (-1);
+ }
+ p = *ppuni;
+ switch (utf_count) {
+ case 1:
+ uc_out = (((*p & 0x1f) << 6) |
+ (*(p + 1) & 0x3f));
+ break;
+ case 2:
+ uc_out = (((((*p & 0x0f) << 6) |
+ (*(p + 1) & 0x3f)) << 6) |
+ (*(p + 2) & 0x3f));
+ break;
+ case 3:
+ uc_out = (((((((*p & 0x07) << 6) |
+ (*(p + 1) & 0x3f)) << 6) |
+ (*(p + 2) & 0x3f)) << 6) |
+ (*(p + 3) & 0x3f));
+ break;
+ case 4:
+ uc_out = (((((((((*p & 0x03) << 6) |
+ (*(p + 1) & 0x3f)) << 6) |
+ (*(p + 2) & 0x3f)) << 6) |
+ (*(p + 3) & 0x3f)) << 6) |
+ (*(p + 4) & 0x3f));
+ break;
+ case 5:
+ uc_out = (((((((((((*p & 0x01) << 6) |
+ (*(p + 1) & 0x3f)) << 6) |
+ (*(p + 2) & 0x3f)) << 6) |
+ (*(p + 3) & 0x3f)) << 6) |
+ (*(p + 4) & 0x3f)) << 6) |
+ (*(p + 5) & 0x3f));
+ break;
+ }
+ *ppuni = p + utf_count;
+ return uc_out;
+}
+
+/*
+ * Combine UTF-8 into Unicode. Incomplete characters are either ignored, or
+ * returned as the UCS replacement character.
+ */
+dUTF8 HTDecodeUTF8(UTFDecodeState * me, int *c_in_out, UCode_t *result)
+{
+ dUTF8 rc = dUTF8_ok;
+ int c = *c_in_out;
+ unsigned uc = UCH(c);
+
+ if (TOASCII(uc) > 127) {
+ /*
+ * continue a multibyte character...
+ */
+ if (me->utf_count > 0 && (TOASCII(c) & 0xc0) == 0x80) {
+ if (me->utf_count == 1) {
+ int limit = (int) (me->utf_buf_p - me->utf_buf) + 1;
+ int maybe = 0;
+
+ /*
+ * Check for overlong sequences (from comment in xterm):
+ * 1100000x 10xxxxxx
+ * 11100000 100xxxxx 10xxxxxx
+ * 11110000 1000xxxx 10xxxxxx 10xxxxxx
+ * 11111000 10000xxx 10xxxxxx 10xxxxxx 10xxxxxx
+ * 11111100 100000xx 10xxxxxx 10xxxxxx 10xxxxxx 10xxxxxx
+ */
+ switch (limit) {
+ case 2:
+ maybe = (UCH(me->utf_buf[0]) & 0xfe) == 0xc0;
+ break;
+ case 3:
+ maybe = ((UCH(me->utf_buf[0]) == 0xe0) &&
+ (UCH(me->utf_buf[1]) & 0xf0) == 0x80);
+ break;
+ case 4:
+ maybe = ((UCH(me->utf_buf[0]) == 0xf0) &&
+ (UCH(me->utf_buf[1]) & 0xf8) == 0x80);
+ break;
+ case 5:
+ maybe = ((UCH(me->utf_buf[0]) == 0xf8) &&
+ (UCH(me->utf_buf[1]) & 0xfd) == 0x80);
+ break;
+ }
+ if (maybe) {
+ while (limit-- > 2) {
+ if ((UCH(me->utf_buf[limit]) & 0xc0) != 0x80) {
+ maybe = 0;
+ break;
+ }
+ }
+ if (maybe) {
+ me->utf_char = UCS_REPL;
+ }
+ }
+ }
+ if (me->utf_char == UCS_REPL) {
+ rc = dUTF8_err;
+ } else if (me->utf_char || ((uc & 0x7f) >> (7 - me->utf_count))) {
+ me->utf_char = (me->utf_char << 6) | (TOASCII(c) & 0x3f);
+ if ((me->utf_char >= 0xd800 &&
+ me->utf_char <= 0xdfff) ||
+ (me->utf_char == 0xfffe) ||
+ (me->utf_char == UCS_HIDE)) {
+ me->utf_char = UCS_REPL;
+ rc = dUTF8_err;
+ }
+ } else {
+ me->utf_char = UCS_REPL;
+ rc = dUTF8_err;
+ }
+ me->utf_count--;
+ *(me->utf_buf_p) = (char) c;
+ (me->utf_buf_p)++;
+
+ if (me->utf_count == 0) {
+ *(me->utf_buf_p) = '\0';
+ *result = me->utf_char;
+ if (*result < 256) {
+ *c_in_out = UCH(*result & 0xff);
+ }
+ switch (*result) {
+ case 0x200e: /* left-to-right mark */
+ case 0x200f: /* right-to-left mark */
+ /* lynx does not use these */
+ *result = '\0';
+ break;
+ }
+ } else {
+ rc = dUTF8_more;
+ }
+ } else {
+ /*
+ * begin a multibyte character
+ */
+ rc = dUTF8_more;
+ me->utf_buf_p = me->utf_buf;
+ *(me->utf_buf_p) = (char) c;
+ (me->utf_buf_p)++;
+ if ((uc & 0xe0) == 0xc0) {
+ me->utf_count = 1;
+ me->utf_char = (uc & 0x1f);
+ } else if ((uc & 0xf0) == 0xe0) {
+ me->utf_count = 2;
+ me->utf_char = (uc & 0x0f);
+ } else if ((uc & 0xf8) == 0xf0) {
+ me->utf_count = 3;
+ me->utf_char = (uc & 0x07);
+ } else if ((uc & 0xfc) == 0xf8) {
+ me->utf_count = 4;
+ me->utf_char = (uc & 0x03);
+ } else if ((uc & 0xfe) == 0xfc) {
+ me->utf_count = 5;
+ me->utf_char = (uc & 0x01);
+ } else {
+ me->utf_count = 0;
+ me->utf_buf_p = me->utf_buf;
+ *(me->utf_buf_p) = '\0';
+ rc = dUTF8_err;
+ }
+ }
+ } else {
+ me->utf_count = 0;
+ me->utf_buf_p = me->utf_buf;
+ *(me->utf_buf_p) = '\0';
+ }
+
+#if 0
+ if (rc != dUTF8_ok) {
+ CTRACE((tfp, "UTF8 %#x ->%#x %s\n",
+ uc, UCH(*c_in_out),
+ (rc == dUTF8_err) ? "err" : "more"));
+ } else {
+ if (*result > 127) {
+ CTRACE((tfp, "UTF8 %#x == %#x\n", uc, (int) *result));
+ } else if (c != UCS_REPL && !isspace(c)) {
+ CTRACE((tfp, "CHAR %#x == %c (%#x)\n", uc, uc, (int) *result));
+ }
+ }
+#endif
+ return rc;
+}
diff --git a/src/UCdomap.c b/src/UCdomap.c
new file mode 100644
index 0000000..0ec239a
--- /dev/null
+++ b/src/UCdomap.c
@@ -0,0 +1,2524 @@
+/*
+ * $LynxId: UCdomap.c,v 1.111 2023/01/05 09:17:16 tom Exp $
+ *
+ * UCdomap.c
+ * =========
+ *
+ * This is a Lynx chartrans engine, its external calls are in UCMap.h
+ *
+ * Derived from code in the Linux kernel console driver.
+ *
+ * The GNU General Public License therefore applies, see the file
+ * COPYING in the top-level directory which should come with every Lynx
+ * distribution.
+ *
+ * [ original comment: - KW ]
+ *
+ * Mapping from internal code (such as Latin-1 or Unicode or IBM PC code)
+ * to font positions.
+ *
+ * aeb, 950210
+ */
+#include <HTUtils.h>
+#include <HTMLDTD.h>
+
+#include <LYGlobalDefs.h>
+#include <UCdomap.h>
+#include <UCMap.h>
+#include <UCAux.h>
+#include <UCDefs.h>
+#include <LYCharSets.h>
+#include <LYStrings.h>
+#include <LYUtils.h>
+
+#if defined(USE_LOCALE_CHARSET) && defined(HAVE_LANGINFO_CODESET)
+#include <langinfo.h>
+#endif
+
+#if defined(USE_JAPANESEUTF8_SUPPORT) || defined(EXP_CHINESEUTF8_SUPPORT)
+#include <iconv.h>
+#endif
+
+#include <LYLeaks.h>
+
+/*
+ * Include chartrans tables:
+ */
+#include <cp1250_uni.h> /* WinLatin2 (cp1250) */
+#include <cp1251_uni.h> /* WinCyrillic (cp1251) */
+#include <cp1252_uni.h> /* WinLatin1 (cp1252) */
+#include <cp1253_uni.h> /* WinGreek (cp1253) */
+#include <cp1255_uni.h> /* WinHebrew (cp1255) */
+#include <cp1256_uni.h> /* WinArabic (cp1256) */
+#include <cp1257_uni.h> /* WinBaltRim (cp1257) */
+#include <cp437_uni.h> /* DosLatinUS (cp437) */
+#include <cp737_uni.h> /* DosGreek (cp737) */
+#include <cp775_uni.h> /* DosBaltRim (cp775) */
+#include <cp850_uni.h> /* DosLatin1 (cp850) */
+#include <cp852_uni.h> /* DosLatin2 (cp852) */
+#include <cp857_uni.h> /* DosTurkish (cp857) */
+#include <cp862_uni.h> /* DosHebrew (cp862) */
+#include <cp864_uni.h> /* DosArabic (cp864) */
+#include <cp866_uni.h> /* DosCyrillic (cp866) */
+#include <cp869_uni.h> /* DosGreek2 (cp869) */
+#include <def7_uni.h> /* 7 bit approximations */
+#include <dmcs_uni.h> /* DEC Multinational */
+#include <hp_uni.h> /* HP Roman8 */
+#include <iso01_uni.h> /* ISO Latin 1 */
+#include <iso02_uni.h> /* ISO Latin 2 */
+#include <iso03_uni.h> /* ISO Latin 3 */
+#include <iso04_uni.h> /* ISO Latin 4 */
+#include <iso05_uni.h> /* ISO 8859-5 Cyrillic */
+#include <iso06_uni.h> /* ISO 8859-6 Arabic */
+#include <iso07_uni.h> /* ISO 8859-7 Greek */
+#include <iso08_uni.h> /* ISO 8859-8 Hebrew */
+#include <iso09_uni.h> /* ISO 8859-9 (Latin 5) */
+#include <iso10_uni.h> /* ISO 8859-10 */
+#include <iso13_uni.h> /* ISO 8859-13 (Latin 7) */
+#include <iso14_uni.h> /* ISO 8859-14 (Latin 8) */
+#include <iso15_uni.h> /* ISO 8859-15 (Latin 9) */
+#include <iso16_uni.h> /* ISO 8859-16 (Latin 10) */
+#include <koi8r_uni.h> /* KOI8-R Cyrillic */
+#include <mac_uni.h> /* Macintosh (8 bit) */
+#include <mnem2_suni.h> /* RFC 1345 Mnemonic */
+#include <next_uni.h> /* NeXT character set */
+#include <rfc_suni.h> /* RFC 1345 w/o Intro */
+/* #include <utf8_uni.h> */ /* UNICODE UTF 8 */
+#include <viscii_uni.h> /* Vietnamese (VISCII) */
+#include <cp866u_uni.h> /* Ukrainian Cyrillic (866) */
+#include <koi8u_uni.h> /* Ukrainian Cyrillic (koi8-u */
+#include <pt154_uni.h> /* Cyrillic-Asian (PT154) */
+
+#ifdef CAN_AUTODETECT_DISPLAY_CHARSET
+int auto_display_charset = -1;
+#endif
+
+static const char *UC_GNsetMIMEnames[4] =
+{
+ "iso-8859-1", "x-dec-graphics", "cp437", "x-transparent"
+};
+
+static int UC_GNhandles[4] =
+{
+ -1, -1, -1, -1
+};
+
+/*
+ * Some of the code below, and some of the comments, are left in for
+ * historical reasons. Not all those tables below are currently
+ * really needed (and what with all those hardwired codepoints),
+ * but let's keep them around for now. They may come in handy if we
+ * decide to make more extended use of the mechanisms (including e.g.
+ * for chars < 127...). - KW
+ */
+
+static u16 translations[][256] =
+{
+ /*
+ * 8-bit Latin-1 mapped to Unicode -- trivial mapping.
+ */
+ {
+ 0x0000, 0x0001, 0x0002, 0x0003, 0x0004, 0x0005, 0x0006, 0x0007,
+ 0x0008, 0x0009, 0x000a, 0x000b, 0x000c, 0x000d, 0x000e, 0x000f,
+ 0x0010, 0x0011, 0x0012, 0x0013, 0x0014, 0x0015, 0x0016, 0x0017,
+ 0x0018, 0x0019, 0x001a, 0x001b, 0x001c, 0x001d, 0x001e, 0x001f,
+ 0x0020, 0x0021, 0x0022, 0x0023, 0x0024, 0x0025, 0x0026, 0x0027,
+ 0x0028, 0x0029, 0x002a, 0x002b, 0x002c, 0x002d, 0x002e, 0x002f,
+ 0x0030, 0x0031, 0x0032, 0x0033, 0x0034, 0x0035, 0x0036, 0x0037,
+ 0x0038, 0x0039, 0x003a, 0x003b, 0x003c, 0x003d, 0x003e, 0x003f,
+ 0x0040, 0x0041, 0x0042, 0x0043, 0x0044, 0x0045, 0x0046, 0x0047,
+ 0x0048, 0x0049, 0x004a, 0x004b, 0x004c, 0x004d, 0x004e, 0x004f,
+ 0x0050, 0x0051, 0x0052, 0x0053, 0x0054, 0x0055, 0x0056, 0x0057,
+ 0x0058, 0x0059, 0x005a, 0x005b, 0x005c, 0x005d, 0x005e, 0x005f,
+ 0x0060, 0x0061, 0x0062, 0x0063, 0x0064, 0x0065, 0x0066, 0x0067,
+ 0x0068, 0x0069, 0x006a, 0x006b, 0x006c, 0x006d, 0x006e, 0x006f,
+ 0x0070, 0x0071, 0x0072, 0x0073, 0x0074, 0x0075, 0x0076, 0x0077,
+ 0x0078, 0x0079, 0x007a, 0x007b, 0x007c, 0x007d, 0x007e, 0x007f,
+ 0x0080, 0x0081, 0x0082, 0x0083, 0x0084, 0x0085, 0x0086, 0x0087,
+ 0x0088, 0x0089, 0x008a, 0x008b, 0x008c, 0x008d, 0x008e, 0x008f,
+ 0x0090, 0x0091, 0x0092, 0x0093, 0x0094, 0x0095, 0x0096, 0x0097,
+ 0x0098, 0x0099, 0x009a, 0x009b, 0x009c, 0x009d, 0x009e, 0x009f,
+ 0x00a0, 0x00a1, 0x00a2, 0x00a3, 0x00a4, 0x00a5, 0x00a6, 0x00a7,
+ 0x00a8, 0x00a9, 0x00aa, 0x00ab, 0x00ac, 0x00ad, 0x00ae, 0x00af,
+ 0x00b0, 0x00b1, 0x00b2, 0x00b3, 0x00b4, 0x00b5, 0x00b6, 0x00b7,
+ 0x00b8, 0x00b9, 0x00ba, 0x00bb, 0x00bc, 0x00bd, 0x00be, 0x00bf,
+ 0x00c0, 0x00c1, 0x00c2, 0x00c3, 0x00c4, 0x00c5, 0x00c6, 0x00c7,
+ 0x00c8, 0x00c9, 0x00ca, 0x00cb, 0x00cc, 0x00cd, 0x00ce, 0x00cf,
+ 0x00d0, 0x00d1, 0x00d2, 0x00d3, 0x00d4, 0x00d5, 0x00d6, 0x00d7,
+ 0x00d8, 0x00d9, 0x00da, 0x00db, 0x00dc, 0x00dd, 0x00de, 0x00df,
+ 0x00e0, 0x00e1, 0x00e2, 0x00e3, 0x00e4, 0x00e5, 0x00e6, 0x00e7,
+ 0x00e8, 0x00e9, 0x00ea, 0x00eb, 0x00ec, 0x00ed, 0x00ee, 0x00ef,
+ 0x00f0, 0x00f1, 0x00f2, 0x00f3, 0x00f4, 0x00f5, 0x00f6, 0x00f7,
+ 0x00f8, 0x00f9, 0x00fa, 0x00fb, 0x00fc, 0x00fd, 0x00fe, 0x00ff
+ },
+ /*
+ * VT100 graphics mapped to Unicode.
+ */
+ {
+ 0x0000, 0x0001, 0x0002, 0x0003, 0x0004, 0x0005, 0x0006, 0x0007,
+ 0x0008, 0x0009, 0x000a, 0x000b, 0x000c, 0x000d, 0x000e, 0x000f,
+ 0x0010, 0x0011, 0x0012, 0x0013, 0x0014, 0x0015, 0x0016, 0x0017,
+ 0x0018, 0x0019, 0x001a, 0x001b, 0x001c, 0x001d, 0x001e, 0x001f,
+ 0x0020, 0x0021, 0x0022, 0x0023, 0x0024, 0x0025, 0x0026, 0x0027,
+ 0x0028, 0x0029, 0x002a, 0x002b, 0x002c, 0x002d, 0x002e, 0x002f,
+ 0x0030, 0x0031, 0x0032, 0x0033, 0x0034, 0x0035, 0x0036, 0x0037,
+ 0x0038, 0x0039, 0x003a, 0x003b, 0x003c, 0x003d, 0x003e, 0x003f,
+ 0x0040, 0x0041, 0x0042, 0x0043, 0x0044, 0x0045, 0x0046, 0x0047,
+ 0x0048, 0x0049, 0x004a, 0x004b, 0x004c, 0x004d, 0x004e, 0x004f,
+ 0x0050, 0x0051, 0x0052, 0x0053, 0x0054, 0x0055, 0x0056, 0x0057,
+ 0x0058, 0x0059, 0x005a, 0x005b, 0x005c, 0x005d, 0x005e, 0x00a0,
+ 0x25c6, 0x2592, 0x2409, 0x240c, 0x240d, 0x240a, 0x00b0, 0x00b1,
+ 0x2424, 0x240b, 0x2518, 0x2510, 0x250c, 0x2514, 0x253c, 0xf800,
+ 0xf801, 0x2500, 0xf803, 0xf804, 0x251c, 0x2524, 0x2534, 0x252c,
+ 0x2502, 0x2264, 0x2265, 0x03c0, 0x2260, 0x00a3, 0x00b7, 0x007f,
+ 0x0080, 0x0081, 0x0082, 0x0083, 0x0084, 0x0085, 0x0086, 0x0087,
+ 0x0088, 0x0089, 0x008a, 0x008b, 0x008c, 0x008d, 0x008e, 0x008f,
+ 0x0090, 0x0091, 0x0092, 0x0093, 0x0094, 0x0095, 0x0096, 0x0097,
+ 0x0098, 0x0099, 0x009a, 0x009b, 0x009c, 0x009d, 0x009e, 0x009f,
+ 0x00a0, 0x00a1, 0x00a2, 0x00a3, 0x00a4, 0x00a5, 0x00a6, 0x00a7,
+ 0x00a8, 0x00a9, 0x00aa, 0x00ab, 0x00ac, 0x00ad, 0x00ae, 0x00af,
+ 0x00b0, 0x00b1, 0x00b2, 0x00b3, 0x00b4, 0x00b5, 0x00b6, 0x00b7,
+ 0x00b8, 0x00b9, 0x00ba, 0x00bb, 0x00bc, 0x00bd, 0x00be, 0x00bf,
+ 0x00c0, 0x00c1, 0x00c2, 0x00c3, 0x00c4, 0x00c5, 0x00c6, 0x00c7,
+ 0x00c8, 0x00c9, 0x00ca, 0x00cb, 0x00cc, 0x00cd, 0x00ce, 0x00cf,
+ 0x00d0, 0x00d1, 0x00d2, 0x00d3, 0x00d4, 0x00d5, 0x00d6, 0x00d7,
+ 0x00d8, 0x00d9, 0x00da, 0x00db, 0x00dc, 0x00dd, 0x00de, 0x00df,
+ 0x00e0, 0x00e1, 0x00e2, 0x00e3, 0x00e4, 0x00e5, 0x00e6, 0x00e7,
+ 0x00e8, 0x00e9, 0x00ea, 0x00eb, 0x00ec, 0x00ed, 0x00ee, 0x00ef,
+ 0x00f0, 0x00f1, 0x00f2, 0x00f3, 0x00f4, 0x00f5, 0x00f6, 0x00f7,
+ 0x00f8, 0x00f9, 0x00fa, 0x00fb, 0x00fc, 0x00fd, 0x00fe, 0x00ff
+ },
+ /*
+ * IBM Codepage 437 mapped to Unicode.
+ */
+ {
+ 0x0000, 0x263a, 0x263b, 0x2665, 0x2666, 0x2663, 0x2660, 0x2022,
+ 0x25d8, 0x25cb, 0x25d9, 0x2642, 0x2640, 0x266a, 0x266b, 0x263c,
+ 0x25ba, 0x25c4, 0x2195, 0x203c, 0x00b6, 0x00a7, 0x25ac, 0x21a8,
+ 0x2191, 0x2193, 0x2192, 0x2190, 0x221f, 0x2194, 0x25b2, 0x25bc,
+ 0x0020, 0x0021, 0x0022, 0x0023, 0x0024, 0x0025, 0x0026, 0x0027,
+ 0x0028, 0x0029, 0x002a, 0x002b, 0x002c, 0x002d, 0x002e, 0x002f,
+ 0x0030, 0x0031, 0x0032, 0x0033, 0x0034, 0x0035, 0x0036, 0x0037,
+ 0x0038, 0x0039, 0x003a, 0x003b, 0x003c, 0x003d, 0x003e, 0x003f,
+ 0x0040, 0x0041, 0x0042, 0x0043, 0x0044, 0x0045, 0x0046, 0x0047,
+ 0x0048, 0x0049, 0x004a, 0x004b, 0x004c, 0x004d, 0x004e, 0x004f,
+ 0x0050, 0x0051, 0x0052, 0x0053, 0x0054, 0x0055, 0x0056, 0x0057,
+ 0x0058, 0x0059, 0x005a, 0x005b, 0x005c, 0x005d, 0x005e, 0x005f,
+ 0x0060, 0x0061, 0x0062, 0x0063, 0x0064, 0x0065, 0x0066, 0x0067,
+ 0x0068, 0x0069, 0x006a, 0x006b, 0x006c, 0x006d, 0x006e, 0x006f,
+ 0x0070, 0x0071, 0x0072, 0x0073, 0x0074, 0x0075, 0x0076, 0x0077,
+ 0x0078, 0x0079, 0x007a, 0x007b, 0x007c, 0x007d, 0x007e, 0x2302,
+ 0x00c7, 0x00fc, 0x00e9, 0x00e2, 0x00e4, 0x00e0, 0x00e5, 0x00e7,
+ 0x00ea, 0x00eb, 0x00e8, 0x00ef, 0x00ee, 0x00ec, 0x00c4, 0x00c5,
+ 0x00c9, 0x00e6, 0x00c6, 0x00f4, 0x00f6, 0x00f2, 0x00fb, 0x00f9,
+ 0x00ff, 0x00d6, 0x00dc, 0x00a2, 0x00a3, 0x00a5, 0x20a7, 0x0192,
+ 0x00e1, 0x00ed, 0x00f3, 0x00fa, 0x00f1, 0x00d1, 0x00aa, 0x00ba,
+ 0x00bf, 0x2310, 0x00ac, 0x00bd, 0x00bc, 0x00a1, 0x00ab, 0x00bb,
+ 0x2591, 0x2592, 0x2593, 0x2502, 0x2524, 0x2561, 0x2562, 0x2556,
+ 0x2555, 0x2563, 0x2551, 0x2557, 0x255d, 0x255c, 0x255b, 0x2510,
+ 0x2514, 0x2534, 0x252c, 0x251c, 0x2500, 0x253c, 0x255e, 0x255f,
+ 0x255a, 0x2554, 0x2569, 0x2566, 0x2560, 0x2550, 0x256c, 0x2567,
+ 0x2568, 0x2564, 0x2565, 0x2559, 0x2558, 0x2552, 0x2553, 0x256b,
+ 0x256a, 0x2518, 0x250c, 0x2588, 0x2584, 0x258c, 0x2590, 0x2580,
+ 0x03b1, 0x00df, 0x0393, 0x03c0, 0x03a3, 0x03c3, 0x00b5, 0x03c4,
+ 0x03a6, 0x0398, 0x03a9, 0x03b4, 0x221e, 0x03c6, 0x03b5, 0x2229,
+ 0x2261, 0x00b1, 0x2265, 0x2264, 0x2320, 0x2321, 0x00f7, 0x2248,
+ 0x00b0, 0x2219, 0x00b7, 0x221a, 0x207f, 0x00b2, 0x25a0, 0x00a0
+ },
+ /*
+ * User mapping -- default to codes for direct font mapping.
+ */
+ {
+ 0xf000, 0xf001, 0xf002, 0xf003, 0xf004, 0xf005, 0xf006, 0xf007,
+ 0xf008, 0xf009, 0xf00a, 0xf00b, 0xf00c, 0xf00d, 0xf00e, 0xf00f,
+ 0xf010, 0xf011, 0xf012, 0xf013, 0xf014, 0xf015, 0xf016, 0xf017,
+ 0xf018, 0xf019, 0xf01a, 0xf01b, 0xf01c, 0xf01d, 0xf01e, 0xf01f,
+ 0xf020, 0xf021, 0xf022, 0xf023, 0xf024, 0xf025, 0xf026, 0xf027,
+ 0xf028, 0xf029, 0xf02a, 0xf02b, 0xf02c, 0xf02d, 0xf02e, 0xf02f,
+ 0xf030, 0xf031, 0xf032, 0xf033, 0xf034, 0xf035, 0xf036, 0xf037,
+ 0xf038, 0xf039, 0xf03a, 0xf03b, 0xf03c, 0xf03d, 0xf03e, 0xf03f,
+ 0xf040, 0xf041, 0xf042, 0xf043, 0xf044, 0xf045, 0xf046, 0xf047,
+ 0xf048, 0xf049, 0xf04a, 0xf04b, 0xf04c, 0xf04d, 0xf04e, 0xf04f,
+ 0xf050, 0xf051, 0xf052, 0xf053, 0xf054, 0xf055, 0xf056, 0xf057,
+ 0xf058, 0xf059, 0xf05a, 0xf05b, 0xf05c, 0xf05d, 0xf05e, 0xf05f,
+ 0xf060, 0xf061, 0xf062, 0xf063, 0xf064, 0xf065, 0xf066, 0xf067,
+ 0xf068, 0xf069, 0xf06a, 0xf06b, 0xf06c, 0xf06d, 0xf06e, 0xf06f,
+ 0xf070, 0xf071, 0xf072, 0xf073, 0xf074, 0xf075, 0xf076, 0xf077,
+ 0xf078, 0xf079, 0xf07a, 0xf07b, 0xf07c, 0xf07d, 0xf07e, 0xf07f,
+ 0xf080, 0xf081, 0xf082, 0xf083, 0xf084, 0xf085, 0xf086, 0xf087,
+ 0xf088, 0xf089, 0xf08a, 0xf08b, 0xf08c, 0xf08d, 0xf08e, 0xf08f,
+ 0xf090, 0xf091, 0xf092, 0xf093, 0xf094, 0xf095, 0xf096, 0xf097,
+ 0xf098, 0xf099, 0xf09a, 0xf09b, 0xf09c, 0xf09d, 0xf09e, 0xf09f,
+ 0xf0a0, 0xf0a1, 0xf0a2, 0xf0a3, 0xf0a4, 0xf0a5, 0xf0a6, 0xf0a7,
+ 0xf0a8, 0xf0a9, 0xf0aa, 0xf0ab, 0xf0ac, 0xf0ad, 0xf0ae, 0xf0af,
+ 0xf0b0, 0xf0b1, 0xf0b2, 0xf0b3, 0xf0b4, 0xf0b5, 0xf0b6, 0xf0b7,
+ 0xf0b8, 0xf0b9, 0xf0ba, 0xf0bb, 0xf0bc, 0xf0bd, 0xf0be, 0xf0bf,
+ 0xf0c0, 0xf0c1, 0xf0c2, 0xf0c3, 0xf0c4, 0xf0c5, 0xf0c6, 0xf0c7,
+ 0xf0c8, 0xf0c9, 0xf0ca, 0xf0cb, 0xf0cc, 0xf0cd, 0xf0ce, 0xf0cf,
+ 0xf0d0, 0xf0d1, 0xf0d2, 0xf0d3, 0xf0d4, 0xf0d5, 0xf0d6, 0xf0d7,
+ 0xf0d8, 0xf0d9, 0xf0da, 0xf0db, 0xf0dc, 0xf0dd, 0xf0de, 0xf0df,
+ 0xf0e0, 0xf0e1, 0xf0e2, 0xf0e3, 0xf0e4, 0xf0e5, 0xf0e6, 0xf0e7,
+ 0xf0e8, 0xf0e9, 0xf0ea, 0xf0eb, 0xf0ec, 0xf0ed, 0xf0ee, 0xf0ef,
+ 0xf0f0, 0xf0f1, 0xf0f2, 0xf0f3, 0xf0f4, 0xf0f5, 0xf0f6, 0xf0f7,
+ 0xf0f8, 0xf0f9, 0xf0fa, 0xf0fb, 0xf0fc, 0xf0fd, 0xf0fe, 0xf0ff
+ }
+};
+static u16 *UC_translate = NULL;
+
+static struct UC_charset UCInfo[MAXCHARSETS];
+
+/*
+ * The standard kernel character-to-font mappings are not invertible
+ * -- this is just a best effort.
+ */
+#define MAX_GLYPH 512 /* Max possible glyph value */
+
+static unsigned char *inv_translate = NULL;
+static unsigned char inv_norm_transl[MAX_GLYPH];
+static unsigned char *inverse_translations[4] =
+{NULL, NULL, NULL, NULL};
+
+static void set_inverse_transl(int i);
+static u16 *set_translate(int m);
+static int UC_valid_UC_charset(int UC_charset_hndl);
+static void UC_con_set_trans(int UC_charset_in_hndl, int Gn, int update_flag);
+static int con_insert_unipair(unsigned unicode, unsigned fontpos, int fordefault);
+static int con_insert_unipair_str(unsigned unicode, const char *replace_str, int fordefault);
+static void con_clear_unimap(int fordefault);
+static void con_clear_unimap_str(int fordefault);
+static void con_set_default_unimap(void);
+static int UC_con_set_unimap(int UC_charset_out_hndl, int update_flag);
+static int UC_con_set_unimap_str(unsigned ct, struct unipair_str *list, int fordefault);
+static int conv_uni_to_pc(long ucs, int usedefault);
+static int conv_uni_to_str(char *outbuf, int buflen, UCode_t ucs, int usedefault);
+static void UCconsole_map_init(void);
+static int UC_MapGN(int UChndl, int update_flag);
+static int UC_FindGN_byMIME(const char *UC_MIMEcharset);
+static void UCreset_allocated_LYCharSets(void);
+static STRING2PTR UC_setup_LYCharSets_repl(int UC_charset_in_hndl, unsigned lowest8);
+static int UC_Register_with_LYCharSets(int s,
+ const char *UC_MIMEcharset,
+ const char *UC_LYNXcharset,
+ int lowest_eightbit);
+
+#ifdef LY_FIND_LEAKS
+static void UCfree_allocated_LYCharSets(void);
+static void UCcleanup_mem(void);
+#endif
+
+static int default_UChndl = -1;
+
+static void set_inverse_transl(int i)
+{
+ int j, glyph;
+ u16 *p = translations[i];
+ unsigned char *q = inverse_translations[i];
+
+ if (!q) {
+ /*
+ * Slightly messy to avoid calling kmalloc too early.
+ */
+ q = inverse_translations[i] = ((i == LAT1_MAP) ?
+ inv_norm_transl :
+ typeMallocn(unsigned char, MAX_GLYPH));
+
+ if (!q)
+ return;
+ }
+ for (j = 0; j < MAX_GLYPH; j++)
+ q[j] = 0;
+
+ for (j = 0; j < E_TABSZ; j++) {
+ glyph = conv_uni_to_pc((long) p[j], 0);
+ if (glyph >= 0 && glyph < MAX_GLYPH && q[glyph] < 32) {
+ /*
+ * Prefer '-' above SHY etc.
+ */
+ q[glyph] = UCH(j);
+ }
+ }
+}
+
+static u16 *set_translate(int m)
+{
+ if (!inverse_translations[m])
+ set_inverse_transl(m);
+ inv_translate = inverse_translations[m];
+ return translations[m];
+}
+
+static int UC_valid_UC_charset(int UC_charset_hndl)
+{
+ return (UC_charset_hndl >= 0 && UC_charset_hndl < UCNumCharsets);
+}
+
+static void UC_con_set_trans(int UC_charset_in_hndl,
+ int Gn,
+ int update_flag)
+{
+ int i, j;
+ const u16 *p;
+ u16 *ptrans;
+
+ if (!UC_valid_UC_charset(UC_charset_in_hndl)) {
+ CTRACE((tfp, "UC_con_set_trans: Invalid charset handle %d.\n",
+ UC_charset_in_hndl));
+ return;
+ }
+ ptrans = translations[Gn];
+ p = UCInfo[UC_charset_in_hndl].unitable;
+#if(0)
+ if (p == UC_current_unitable) { /* test whether pointers are equal */
+ return; /* nothing to be done */
+ }
+ /*
+ * The font is always 256 characters - so far.
+ */
+ con_clear_unimap();
+#endif
+ for (i = 0; i < 256; i++) {
+ if ((j = UCInfo[UC_charset_in_hndl].unicount[i])) {
+ ptrans[i] = *p;
+ for (; j; j--) {
+ p++;
+ }
+ } else {
+ ptrans[i] = UCS_REPL;
+ }
+ }
+ if (update_flag) {
+ set_inverse_transl(Gn); /* Update inverse translation for this one */
+ }
+}
+
+/*
+ * Unicode -> current font conversion
+ *
+ * A font has at most 512 chars, usually 256.
+ * But one font position may represent several Unicode chars.
+ * A hashtable is somewhat of a pain to deal with, so use a
+ * "paged table" instead. Simulation has shown the memory cost of
+ * this 3-level paged table scheme to be comparable to a hash table.
+ */
+static int hashtable_contents_valid = 0; /* Use ASCII-only mode for bootup */
+static int hashtable_str_contents_valid = 0;
+
+static u16 **uni_pagedir[32] =
+{
+ NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL,
+ NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL,
+ NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL,
+ NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL
+};
+
+static char ***uni_pagedir_str[32] =
+{
+ NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL,
+ NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL,
+ NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL,
+ NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL
+};
+
+static const u16 *UC_current_unitable = NULL;
+static struct unimapdesc_str *UC_current_unitable_str = NULL;
+
+/*
+ * Keep a second set of structures for the translation designated
+ * as "default" - kw
+ */
+static int unidefault_contents_valid = 0; /* Use ASCII-only mode for bootup */
+static int unidefault_str_contents_valid = 0;
+
+static u16 **unidefault_pagedir[32] =
+{
+ NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL,
+ NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL,
+ NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL,
+ NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL
+};
+static char ***unidefault_pagedir_str[32] =
+{
+ NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL,
+ NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL,
+ NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL,
+ NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL
+};
+
+static const u16 *UC_default_unitable = 0;
+static const struct unimapdesc_str *UC_default_unitable_str = 0;
+
+static int con_insert_unipair(unsigned unicode, unsigned fontpos, int fordefault)
+{
+ int i;
+ unsigned n;
+ u16 **p1, *p2;
+
+ if (fordefault)
+ p1 = unidefault_pagedir[n = unicode >> 11];
+ else
+ p1 = uni_pagedir[n = unicode >> 11];
+ if (!p1) {
+ if ((p1 = typecallocn(u16 *, 32)) == NULL)
+ return ucError;
+ if (fordefault)
+ unidefault_pagedir[n] = p1;
+ else
+ uni_pagedir[n] = p1;
+ }
+
+ if (!(p2 = p1[n = (unicode >> 6) & 0x1f])) {
+ p2 = p1[n] = (u16 *) malloc(64 * sizeof(u16));
+ if (!p2)
+ return ucError;
+
+ for (i = 0; i < 64; i++) {
+ p2[i] = UCS_HIDE; /* No glyph for this character (yet) */
+ }
+ }
+
+ p2[unicode & 0x3f] = (u16) fontpos;
+
+ return 0;
+}
+
+static int con_insert_unipair_str(unsigned unicode, const char *replace_str,
+ int fordefault)
+{
+ unsigned n;
+ char ***p1;
+ const char **p2;
+
+ if (fordefault)
+ p1 = unidefault_pagedir_str[n = unicode >> 11];
+ else
+ p1 = uni_pagedir_str[n = unicode >> 11];
+ if (!p1) {
+ if ((p1 = typecallocn(char **, 32)) == NULL)
+ return ucError;
+
+ if (fordefault)
+ unidefault_pagedir_str[n] = p1;
+ else
+ uni_pagedir_str[n] = p1;
+ }
+
+ n = ((unicode >> 6) & 0x1f);
+ if (!p1[n]) {
+ if ((p1[n] = typecallocn(char *, 64)) == NULL)
+ return ucError;
+ }
+ p2 = (const char **) p1[n];
+
+ p2[unicode & 0x3f] = replace_str;
+
+ return 0;
+}
+
+/*
+ * ui arg was a leftover, deleted. - KW
+ */
+static void con_clear_unimap(int fordefault)
+{
+ int i, j;
+ u16 **p1;
+
+ if (fordefault) {
+ for (i = 0; i < 32; i++) {
+ if ((p1 = unidefault_pagedir[i]) != NULL) {
+ for (j = 0; j < 32; j++) {
+ FREE(p1[j]);
+ }
+ FREE(p1);
+ }
+ unidefault_pagedir[i] = NULL;
+ }
+
+ unidefault_contents_valid = 1;
+ } else {
+ for (i = 0; i < 32; i++) {
+ if ((p1 = uni_pagedir[i]) != NULL) {
+ for (j = 0; j < 32; j++) {
+ FREE(p1[j]);
+ }
+ FREE(p1);
+ }
+ uni_pagedir[i] = NULL;
+ }
+
+ hashtable_contents_valid = 1;
+ }
+}
+
+static void con_clear_unimap_str(int fordefault)
+{
+ int i, j;
+ char ***p1;
+
+ if (fordefault) {
+ for (i = 0; i < 32; i++) {
+ if ((p1 = unidefault_pagedir_str[i]) != NULL) {
+ for (j = 0; j < 32; j++) {
+ FREE(p1[j]);
+ }
+ FREE(p1);
+ }
+ unidefault_pagedir_str[i] = NULL;
+ }
+
+ unidefault_str_contents_valid = 1; /* ??? probably no use... */
+ } else {
+ for (i = 0; i < 32; i++) {
+ if ((p1 = uni_pagedir_str[i]) != NULL) {
+ for (j = 0; j < 32; j++) {
+ FREE(p1[j]);
+ }
+ FREE(p1);
+ }
+ uni_pagedir_str[i] = NULL;
+ }
+
+ hashtable_str_contents_valid = 1; /* ??? probably no use... */
+ }
+}
+
+/*
+ * Loads the unimap for the hardware font, as defined in uni_hash.tbl.
+ * The representation used was the most compact I could come up
+ * with. This routine is executed at sys_setup time, and when the
+ * PIO_FONTRESET ioctl is called.
+ */
+static void con_set_default_unimap(void)
+{
+ int i, j;
+ const u16 *p;
+
+ /*
+ * The default font is always 256 characters.
+ */
+ con_clear_unimap(1);
+
+ p = dfont_unitable;
+ for (i = 0; i < 256; i++) {
+ for (j = dfont_unicount[i]; j; j--) {
+ con_insert_unipair(*(p++), (u16) i, 1);
+ }
+ }
+
+ UC_default_unitable = dfont_unitable;
+
+ con_clear_unimap_str(1);
+ UC_con_set_unimap_str(dfont_replacedesc.entry_ct, repl_map, 1);
+ UC_default_unitable_str = &dfont_replacedesc;
+}
+
+int UCNumCharsets = 0;
+
+int UCLYhndl_HTFile_for_unspec = -1;
+int UCLYhndl_HTFile_for_unrec = -1;
+int UCLYhndl_for_unspec = -1;
+int UCLYhndl_for_unrec = -1;
+
+/* easy to type, will initialize later */
+int LATIN1 = -1; /* UCGetLYhndl_byMIME("iso-8859-1") */
+int US_ASCII = -1; /* UCGetLYhndl_byMIME("us-ascii") */
+int UTF8_handle = -1; /* UCGetLYhndl_byMIME("utf-8") */
+int TRANSPARENT = -1; /* UCGetLYhndl_byMIME("x-transparent") */
+
+static int UC_con_set_unimap(int UC_charset_out_hndl,
+ int update_flag)
+{
+ int i, j;
+ const u16 *p;
+
+ if (!UC_valid_UC_charset(UC_charset_out_hndl)) {
+ CTRACE((tfp, "UC_con_set_unimap: Invalid charset handle %d.\n",
+ UC_charset_out_hndl));
+ return ucError;
+ }
+
+ p = UCInfo[UC_charset_out_hndl].unitable;
+ if (p == UC_current_unitable) { /* test whether pointers are equal */
+ return update_flag; /* nothing to be done */
+ }
+ UC_current_unitable = p;
+
+ /*
+ * The font is always 256 characters - so far.
+ */
+ con_clear_unimap(0);
+
+ for (i = 0; i < 256; i++) {
+ for (j = UCInfo[UC_charset_out_hndl].unicount[i]; j; j--) {
+ con_insert_unipair(*(p++), (u16) i, 0);
+ }
+ }
+
+ if (update_flag) {
+ for (i = 0; i <= 3; i++) {
+ set_inverse_transl(i); /* Update all inverse translations */
+ }
+ }
+
+ return 0;
+}
+
+static int UC_con_set_unimap_str(unsigned ct, struct unipair_str *list,
+ int fordefault)
+{
+ int err = 0, err1;
+
+ while (ct--) {
+ if ((err1 = con_insert_unipair_str(list->unicode,
+ list->replace_str,
+ fordefault)) != 0) {
+ err = err1;
+ }
+ list++;
+ }
+
+ /*
+ * No inverse translations for replacement strings!
+ */
+ if (!err) {
+ if (fordefault)
+ unidefault_str_contents_valid = 1;
+ else
+ hashtable_str_contents_valid = 1;
+ }
+
+ return err;
+}
+
+static int conv_uni_to_pc(long ucs,
+ int usedefault)
+{
+ int h;
+ u16 **p1, *p2;
+
+ /*
+ * Only 16-bit codes supported at this time.
+ */
+ if (ucs > 0xffff) {
+ /*
+ * U+FFFD: REPLACEMENT CHARACTER.
+ */
+ ucs = UCS_REPL;
+ } else if (ucs < 0x20 || ucs >= 0xfffe) {
+ /*
+ * Not a printable character.
+ */
+ return ucError;
+ } else if (ucs == 0xfeff || (ucs >= 0x200b && ucs <= 0x200f)) {
+ /*
+ * Zero-width space.
+ */
+ return ucZeroWidth;
+ } else if ((ucs & ~UNI_DIRECT_MASK) == UNI_DIRECT_BASE) {
+ /*
+ * UNI_DIRECT_BASE indicates the start of the region in the
+ * User Zone which always has a 1:1 mapping to the currently
+ * loaded font. The UNI_DIRECT_MASK indicates the bit span
+ * of the region.
+ */
+ return (ucs & UNI_DIRECT_MASK);
+ }
+
+ if (usedefault) {
+ if (!unidefault_contents_valid)
+ return ucInvalidHash;
+ p1 = unidefault_pagedir[ucs >> 11];
+ } else {
+ if (!hashtable_contents_valid)
+ return ucInvalidHash;
+ p1 = uni_pagedir[ucs >> 11];
+ }
+
+ if (p1 &&
+ (p2 = p1[(ucs >> 6) & 0x1f]) &&
+ (h = p2[ucs & 0x3f]) < MAX_GLYPH) {
+ return h;
+ }
+
+ /*
+ * Not found.
+ */
+ return ucNotFound;
+}
+
+/*
+ * Note: contents of outbuf is not changes for negative return value!
+ */
+static int conv_uni_to_str(char *outbuf,
+ int buflen,
+ UCode_t ucs,
+ int usedefault)
+{
+ char *h;
+ char ***p1, **p2;
+
+ /*
+ * Only 16-bit codes supported at this time.
+ */
+ if (ucs > 0xffff) {
+ /*
+ * U+FFFD: REPLACEMENT CHARACTER.
+ */
+ ucs = UCS_REPL;
+ /*
+ * Maybe the following two cases should be allowed here?? - KW
+ */
+ } else if (ucs < 0x20 || ucs >= 0xfffe) {
+ /*
+ * Not a printable character.
+ */
+ return ucError;
+ } else if (ucs == 0xfeff || (ucs >= 0x200b && ucs <= 0x200f)) {
+ /*
+ * Zero-width space.
+ */
+ return ucZeroWidth;
+ }
+
+ if (usedefault) {
+ if (!unidefault_str_contents_valid)
+ return ucInvalidHash;
+ p1 = unidefault_pagedir_str[ucs >> 11];
+ } else {
+ if (!hashtable_str_contents_valid)
+ return ucInvalidHash;
+ p1 = uni_pagedir_str[ucs >> 11];
+ }
+
+ if (p1 &&
+ (p2 = p1[(ucs >> 6) & 0x1f]) &&
+ (h = p2[ucs & 0x3f])) {
+ StrNCpy(outbuf, h, (buflen - 1));
+ return 1; /* ok ! */
+ }
+
+ /*
+ * Not found.
+ */
+ return ucNotFound;
+}
+
+int UCInitialized = 0;
+
+/*
+ * [ original comment: - KW ]
+ * This is called at sys_setup time, after memory and the console are
+ * initialized. It must be possible to call kmalloc(..., GFP_KERNEL)
+ * from this function, hence the call from sys_setup.
+ */
+static void UCconsole_map_init(void)
+{
+ con_set_default_unimap();
+ UCInitialized = 1;
+}
+
+/*
+ * OK now, finally, some stuff that is more specifically for Lynx: - KW
+ */
+int UCTransUniChar(UCode_t unicode,
+ int charset_out)
+{
+ int rc = 0;
+ int UChndl_out;
+ int isdefault, trydefault = 0;
+ const u16 *ut;
+
+ if ((UChndl_out = LYCharSet_UC[charset_out].UChndl) < 0) {
+ if (LYCharSet_UC[charset_out].codepage < 0) {
+ if (unicode < 128) {
+ rc = (int) unicode;
+ } else {
+ rc = LYCharSet_UC[charset_out].codepage;
+ }
+ return rc;
+ }
+ if ((UChndl_out = default_UChndl) < 0) {
+ return ucCannotOutput;
+ }
+ isdefault = 1;
+ } else {
+ isdefault = UCInfo[UChndl_out].replacedesc.isdefault;
+ trydefault = UCInfo[UChndl_out].replacedesc.trydefault;
+ }
+
+ if (!isdefault) {
+ ut = UCInfo[UChndl_out].unitable;
+ if (ut != UC_current_unitable) {
+ rc = UC_con_set_unimap(UChndl_out, 1);
+ if (rc < 0) {
+ return rc;
+ }
+ }
+ rc = conv_uni_to_pc(unicode, 0);
+ if (rc >= 0) {
+ return rc;
+ }
+ }
+ if (isdefault || trydefault) {
+ rc = conv_uni_to_pc(unicode, 1);
+ if (rc >= 0) {
+ return rc;
+ }
+ }
+ if (!isdefault && (rc == ucNotFound)) {
+ rc = conv_uni_to_pc(UCS_REPL, 0);
+ }
+ if ((isdefault || trydefault) && (rc == ucNotFound)) {
+ rc = conv_uni_to_pc(UCS_REPL, 1);
+ }
+ return rc;
+}
+
+/*
+ * Returns string length, or negative value for error.
+ */
+int UCTransUniCharStr(char *outbuf,
+ int buflen,
+ UCode_t unicode,
+ int charset_out,
+ int chk_single_flag)
+{
+ int rc = ucUnknown, src = 0;
+ int UChndl_out;
+ int isdefault, trydefault = 0;
+ struct unimapdesc_str *repl;
+ const u16 *ut;
+
+ if (buflen < 2)
+ return ucBufferTooSmall;
+
+ if ((UChndl_out = LYCharSet_UC[charset_out].UChndl) < 0) {
+ if (LYCharSet_UC[charset_out].codepage < 0)
+ return LYCharSet_UC[charset_out].codepage;
+ if ((UChndl_out = default_UChndl) < 0)
+ return ucCannotOutput;
+ isdefault = 1;
+ } else {
+ isdefault = UCInfo[UChndl_out].replacedesc.isdefault;
+ trydefault = UCInfo[UChndl_out].replacedesc.trydefault;
+ }
+
+ if (chk_single_flag) {
+ if (!isdefault) {
+ ut = UCInfo[UChndl_out].unitable;
+ if (ut != UC_current_unitable) {
+ src = UC_con_set_unimap(UChndl_out, 1);
+ if (src < 0) {
+ return src;
+ }
+ }
+ }
+ src = conv_uni_to_pc(unicode, isdefault);
+ if (src >= 32) {
+ outbuf[0] = (char) src;
+ outbuf[1] = '\0';
+ return 1;
+ }
+ }
+
+ repl = &(UCInfo[UChndl_out].replacedesc);
+ if (!isdefault) {
+ if (repl != UC_current_unitable_str) {
+ con_clear_unimap_str(0);
+ (void) UC_con_set_unimap_str(repl->entry_ct, repl->entries, 0);
+ UC_current_unitable_str = repl;
+ }
+ rc = conv_uni_to_str(outbuf, buflen, unicode, 0);
+ if (rc >= 0)
+ return (int) strlen(outbuf);
+ }
+ if (trydefault && chk_single_flag) {
+ src = conv_uni_to_pc(unicode, 1);
+ if (src >= 32) {
+ outbuf[0] = (char) src;
+ outbuf[1] = '\0';
+ return 1;
+ }
+ }
+ if (isdefault || trydefault) {
+#ifdef USE_JAPANESEUTF8_SUPPORT
+ if (LYCharSet_UC[charset_out].codepage == 0 &&
+ LYCharSet_UC[charset_out].codepoints == 0) {
+ iconv_t cd;
+ char str[3], *pin, *pout;
+ size_t inleft, outleft;
+ char *tocode = NULL;
+
+ str[0] = (char) (unicode >> 8);
+ str[1] = (char) (unicode & 0xFF);
+ str[2] = 0;
+ pin = str;
+ inleft = 2;
+ pout = outbuf;
+ outleft = (size_t) buflen;
+ /*
+ * Try TRANSLIT first, since it is an extension which can provide
+ * translations when there is no available exact translation to
+ * the target character set.
+ */
+ HTSprintf0(&tocode, "%s//TRANSLIT", LYCharSet_UC[charset_out].MIMEname);
+ cd = iconv_open(tocode, "UTF-16BE");
+ if (cd == (iconv_t) -1) {
+ /*
+ * Try again, without TRANSLIT
+ */
+ HTSprintf0(&tocode, "%s", LYCharSet_UC[charset_out].MIMEname);
+ cd = iconv_open(tocode, "UTF-16BE");
+
+ if (cd == (iconv_t) -1) {
+ CTRACE((tfp,
+ "Warning: Cannot transcode form charset %s to %s!\n",
+ "UTF-16BE", tocode));
+ }
+ }
+ FREE(tocode);
+
+ if (cd != (iconv_t) -1) {
+ rc = (int) iconv(cd, (ICONV_CONST char **) &pin, &inleft,
+ &pout, &outleft);
+ iconv_close(cd);
+ if ((pout - outbuf) == 3) {
+ CTRACE((tfp,
+ "It seems to be a JIS X 0201 code(%" PRI_UCode_t
+ "). Not supported.\n", CAST_UCode_t (unicode)));
+ pin = str;
+ inleft = 2;
+ pout = outbuf;
+ outleft = (size_t) buflen;
+ } else if (rc >= 0) {
+ *pout = '\0';
+ return (int) strlen(outbuf);
+ }
+ }
+ }
+#endif
+ rc = conv_uni_to_str(outbuf, buflen, unicode, 1);
+ if (rc >= 0)
+ return (int) strlen(outbuf);
+ }
+ if (rc == ucNotFound) {
+ if (!isdefault)
+ rc = conv_uni_to_str(outbuf, buflen, UCS_REPL, 0);
+ if ((rc == ucNotFound) && (isdefault || trydefault))
+ rc = conv_uni_to_str(outbuf, buflen, UCS_REPL, 1);
+ if (rc >= 0)
+ return (int) strlen(outbuf);
+ }
+ if (chk_single_flag && src == ucNotFound) {
+ if (!isdefault)
+ rc = conv_uni_to_pc(UCS_REPL, 0);
+ if ((rc == ucNotFound) && (isdefault || trydefault))
+ rc = conv_uni_to_pc(UCS_REPL, 1);
+ if (rc >= 32) {
+ outbuf[0] = (char) rc;
+ outbuf[1] = '\0';
+ return 1;
+ }
+ return rc;
+ }
+ return ucNotFound;
+}
+
+static int UC_lastautoGN = 0;
+
+static int UC_MapGN(int UChndl,
+ int update_flag)
+{
+ int i, Gn, found, lasthndl;
+
+ found = 0;
+ Gn = -1;
+ for (i = 0; i < 4 && Gn < 0; i++) {
+ if (UC_GNhandles[i] < 0) {
+ Gn = i;
+ } else if (UC_GNhandles[i] == UChndl) {
+ Gn = i;
+ found = 1;
+ }
+ }
+ if (found)
+ return Gn;
+ if (Gn >= 0) {
+ UCInfo[UChndl].GN = Gn;
+ UC_GNhandles[Gn] = UChndl;
+ } else {
+ if (UC_lastautoGN == GRAF_MAP) {
+ Gn = IBMPC_MAP;
+ } else {
+ Gn = GRAF_MAP;
+ }
+ UC_lastautoGN = Gn;
+ lasthndl = UC_GNhandles[Gn];
+ UCInfo[lasthndl].GN = -1;
+ UCInfo[UChndl].GN = Gn;
+ UC_GNhandles[Gn] = UChndl;
+ }
+ CTRACE((tfp, "UC_MapGN: Using %d <- %d (%s)\n",
+ Gn, UChndl, UCInfo[UChndl].MIMEname));
+ UC_con_set_trans(UChndl, Gn, update_flag);
+ return Gn;
+}
+
+int UCTransChar(int ch_in,
+ int charset_in,
+ int charset_out)
+{
+ UCode_t unicode;
+ int Gn;
+ int rc = ucNotFound;
+ int UChndl_in, UChndl_out;
+ int isdefault, trydefault = 0;
+ const u16 *ut;
+ int upd = 0;
+
+ if (charset_in == charset_out)
+ return UCH(ch_in);
+ if (charset_in < 0)
+ return ucCannotConvert;
+ if ((UChndl_in = LYCharSet_UC[charset_in].UChndl) < 0)
+ return ucCannotConvert;
+ if ((UChndl_out = LYCharSet_UC[charset_out].UChndl) < 0) {
+ if (LYCharSet_UC[charset_out].codepage < 0)
+ return LYCharSet_UC[charset_out].codepage;
+ if ((UChndl_out = default_UChndl) < 0)
+ return ucCannotOutput;
+ isdefault = 1;
+ } else {
+ isdefault = UCInfo[UChndl_out].replacedesc.isdefault;
+ trydefault = UCInfo[UChndl_out].replacedesc.trydefault;
+ }
+ if (!UCInfo[UChndl_in].num_uni)
+ return ucCannotConvert;
+ if ((Gn = UCInfo[UChndl_in].GN) < 0) {
+ Gn = UC_MapGN(UChndl_in, 0);
+ upd = 1;
+ }
+
+ ut = UCInfo[UChndl_out].unitable;
+ if (!isdefault) {
+ if (ut == UC_current_unitable) {
+ if (upd) {
+ set_inverse_transl(Gn);
+ }
+ } else {
+ rc = UC_con_set_unimap(UChndl_out, 1);
+ if (rc > 0) {
+ set_inverse_transl(Gn);
+ } else if (rc < 0) {
+ return rc;
+ }
+ }
+ }
+ UC_translate = set_translate(Gn);
+ unicode = UC_translate[UCH(ch_in)];
+ if (!isdefault) {
+ rc = conv_uni_to_pc(unicode, 0);
+ if (rc >= 0)
+ return rc;
+ }
+ if ((rc == ucNotFound) && (isdefault || trydefault)) {
+ rc = conv_uni_to_pc(unicode, 1);
+ }
+ if ((rc == ucNotFound) && !isdefault) {
+ rc = conv_uni_to_pc(UCS_REPL, 0);
+ }
+ if ((rc == ucNotFound) && (isdefault || trydefault)) {
+ rc = conv_uni_to_pc(UCS_REPL, 1);
+ }
+ return rc;
+}
+
+#if defined(USE_JAPANESEUTF8_SUPPORT) || defined(EXP_CHINESEUTF8_SUPPORT)
+UCode_t UCTransJPToUni(char *inbuf,
+ int buflen,
+ int charset_in)
+{
+ char outbuf[3], *pin, *pout;
+ size_t ilen, olen;
+ iconv_t cd;
+
+ pin = inbuf;
+ pout = outbuf;
+ ilen = 2;
+ olen = (size_t) buflen;
+
+ cd = iconv_open("UTF-16BE", LYCharSet_UC[charset_in].MIMEname);
+ (void) iconv(cd, (ICONV_CONST char **) &pin, &ilen, &pout, &olen);
+ iconv_close(cd);
+ if ((ilen == 0) && (olen == 0)) {
+ return (((unsigned char) outbuf[0]) << 8) + (unsigned char) outbuf[1];
+ }
+ return ucCannotConvert;
+}
+#endif
+
+/*
+ * Translate a character to Unicode. If additional bytes are needed, this
+ * returns ucNeedMore, based on its internal state. To reset the state,
+ * call this with charset_in < 0.
+ */
+UCode_t UCTransToUni(int ch_in,
+ int charset_in)
+{
+ static char buffer[10];
+ static unsigned inx = 0;
+
+ UCode_t unicode;
+ int Gn;
+ unsigned char ch_iu = UCH(ch_in);
+ int UChndl_in;
+
+ /*
+ * Reset saved-state.
+ */
+ if (charset_in < 0) {
+ inx = 0;
+ return ucCannotConvert;
+ } else if (charset_in == LATIN1) {
+ return ch_iu;
+ } else if (charset_in == UTF8_handle) {
+ if (is8bits(ch_iu)) {
+ unsigned need;
+ const char *ptr;
+
+ buffer[inx++] = (char) ch_iu;
+ buffer[inx] = '\0';
+ need = (unsigned) utf8_length(TRUE, buffer);
+ if (need && (need + 1) == inx) {
+ inx = 0;
+ ptr = buffer;
+ return UCGetUniFromUtf8String(&ptr);
+ } else if (inx < sizeof(buffer) - 1) {
+ return ucNeedMore;
+ } else {
+ inx = 0;
+ }
+ } else {
+ inx = 0;
+ }
+ }
+#ifdef USE_JAPANESEUTF8_SUPPORT
+ if ((strcmp(LYCharSet_UC[charset_in].MIMEname, "shift_jis") == 0) ||
+ (strcmp(LYCharSet_UC[charset_in].MIMEname, "euc-jp") == 0)) {
+ char obuffer[3], *pin, *pout;
+ size_t ilen, olen;
+ iconv_t cd;
+
+ pin = buffer;
+ pout = obuffer;
+ ilen = olen = 2;
+ if (strcmp(LYCharSet_UC[charset_in].MIMEname, "shift_jis") == 0) {
+ if (inx == 0) {
+ if (IS_SJIS_HI1(ch_iu) ||
+ IS_SJIS_HI2(ch_iu)) {
+ buffer[0] = (char) ch_in;
+ inx = 1;
+ return ucNeedMore;
+ } else if (IS_SJIS_X0201KANA(ch_iu)) {
+ buffer[0] = (char) ch_in;
+ buffer[1] = 0;
+ cd = iconv_open("UTF-16BE", "Shift_JIS");
+ ilen = 1;
+ (void) iconv(cd, (ICONV_CONST char **) &pin, &ilen, &pout, &olen);
+ iconv_close(cd);
+ if ((ilen == 0) && (olen == 0)) {
+ return (UCH(obuffer[0]) << 8) + UCH(obuffer[1]);
+ }
+ }
+ } else {
+ if (IS_SJIS_LO(ch_iu)) {
+ buffer[1] = (char) ch_in;
+ buffer[2] = 0;
+
+ cd = iconv_open("UTF-16BE", "Shift_JIS");
+ (void) iconv(cd, (ICONV_CONST char **) &pin, &ilen, &pout, &olen);
+ iconv_close(cd);
+ inx = 0;
+ if ((ilen == 0) && (olen == 0)) {
+ return (UCH(obuffer[0]) << 8) + UCH(obuffer[1]);
+ }
+ }
+ }
+ }
+ if (strcmp(LYCharSet_UC[charset_in].MIMEname, "euc-jp") == 0) {
+ if (inx == 0) {
+ if (IS_EUC_HI(ch_iu) || ch_iu == 0x8E) {
+ buffer[0] = (char) ch_in;
+ inx = 1;
+ return ucNeedMore;
+ }
+ } else {
+ if (IS_EUC_LOX(ch_iu)) {
+ buffer[1] = (char) ch_in;
+ buffer[2] = 0;
+
+ cd = iconv_open("UTF-16BE", "EUC-JP");
+ (void) iconv(cd, (ICONV_CONST char **) &pin, &ilen, &pout, &olen);
+ iconv_close(cd);
+ inx = 0;
+ if ((ilen == 0) && (olen == 0)) {
+ return (UCH(obuffer[0]) << 8) + UCH(obuffer[1]);
+ }
+ }
+ }
+ }
+ inx = 0;
+ }
+#endif
+ if (ch_iu < 128 && ch_iu >= 32)
+ return ch_iu;
+
+ if (ch_iu < 32 &&
+ LYCharSet_UC[charset_in].enc != UCT_ENC_8BIT_C0) {
+ /*
+ * Don't translate C0 chars except for specific charsets.
+ */
+ return ch_iu;
+ } else if ((UChndl_in = LYCharSet_UC[charset_in].UChndl) < 0) {
+ return ucCannotConvert;
+ } else if (!UCInfo[UChndl_in].num_uni) {
+ return ucCannotConvert;
+ }
+
+ if ((Gn = UCInfo[UChndl_in].GN) < 0) {
+ Gn = UC_MapGN(UChndl_in, 1);
+ }
+
+ UC_translate = set_translate(Gn);
+ unicode = UC_translate[ch_iu];
+
+ return unicode;
+}
+
+int UCReverseTransChar(int ch_out,
+ int charset_in,
+ int charset_out)
+{
+ int Gn;
+ int rc = ucError;
+ int UChndl_in, UChndl_out;
+ int isdefault;
+ int i_ch = UCH(ch_out);
+ const u16 *ut;
+
+ if (charset_in == charset_out)
+ return UCH(ch_out);
+ if (charset_in < 0)
+ return ucCannotConvert;
+ if ((UChndl_in = LYCharSet_UC[charset_in].UChndl) < 0)
+ return ucCannotConvert;
+ if (!UCInfo[UChndl_in].num_uni)
+ return ucCannotConvert;
+ if (charset_out < 0)
+ return ucCannotOutput;
+ if ((UChndl_out = LYCharSet_UC[charset_out].UChndl) < 0) {
+ if (LYCharSet_UC[charset_out].codepage < 0)
+ return LYCharSet_UC[charset_out].codepage;
+ if ((UChndl_out = default_UChndl) < 0)
+ return ucCannotOutput;
+ isdefault = 1;
+ } else {
+ isdefault = UCInfo[UChndl_out].replacedesc.isdefault;
+ }
+
+ if (!isdefault) {
+ /*
+ * Try to use the inverse table if charset_out is not equivalent
+ * to using just the default table. If it is, it should have
+ * just ASCII chars and trying to back-translate those should
+ * not give anything but themselves. - kw
+ */
+ ut = UCInfo[UChndl_out].unitable;
+ if (ut == UC_current_unitable) {
+ if ((Gn = UCInfo[UChndl_in].GN) < 0) {
+ Gn = UC_MapGN(UChndl_in, 1);
+ }
+ UC_translate = set_translate(Gn);
+ if (inv_translate)
+ rc = inv_translate[i_ch];
+ if (rc >= 32) {
+ return rc;
+ }
+ }
+ }
+ return UCTransChar(ch_out, charset_out, charset_in);
+}
+
+/*
+ * Returns string length, or negative value for error.
+ */
+int UCTransCharStr(char *outbuf,
+ int buflen,
+ int ch_in,
+ int charset_in,
+ int charset_out,
+ int chk_single_flag)
+{
+ UCode_t unicode;
+ int Gn;
+ int rc = ucUnknown, src = 0;
+ int UChndl_in, UChndl_out;
+ int isdefault, trydefault = 0;
+ struct unimapdesc_str *repl;
+ const u16 *ut;
+ int upd = 0;
+
+ if (buflen < 2)
+ return ucBufferTooSmall;
+ if (chk_single_flag && charset_in == charset_out) {
+ outbuf[0] = (char) ch_in;
+ outbuf[1] = '\0';
+ return 1;
+ }
+ if (charset_in < 0)
+ return ucCannotConvert;
+ if ((UChndl_in = LYCharSet_UC[charset_in].UChndl) < 0)
+ return ucCannotConvert;
+ if (!UCInfo[UChndl_in].num_uni)
+ return ucCannotConvert;
+ if ((UChndl_out = LYCharSet_UC[charset_out].UChndl) < 0) {
+ if (LYCharSet_UC[charset_out].codepage < 0)
+ return LYCharSet_UC[charset_out].codepage;
+ if ((UChndl_out = default_UChndl) < 0)
+ return ucCannotOutput;
+ isdefault = 1;
+ } else {
+ isdefault = UCInfo[UChndl_out].replacedesc.isdefault;
+ trydefault = UCInfo[UChndl_out].replacedesc.trydefault;
+ }
+ if ((Gn = UCInfo[UChndl_in].GN) < 0) {
+ Gn = UC_MapGN(UChndl_in, !chk_single_flag);
+ upd = chk_single_flag;
+ }
+
+ UC_translate = set_translate(Gn);
+ unicode = UC_translate[UCH(ch_in)];
+
+ if (chk_single_flag) {
+ if (!isdefault) {
+ ut = UCInfo[UChndl_out].unitable;
+ if (ut == UC_current_unitable) {
+ if (upd)
+ set_inverse_transl(Gn);
+ } else {
+ src = UC_con_set_unimap(UChndl_out, 1);
+ if (src > 0) {
+ set_inverse_transl(Gn);
+ } else if (src < 0) {
+ return src;
+ }
+ }
+ }
+ src = conv_uni_to_pc(unicode, isdefault);
+ if (src >= 32) {
+ outbuf[0] = (char) src;
+ outbuf[1] = '\0';
+ return 1;
+ }
+ }
+
+ repl = &(UCInfo[UChndl_out].replacedesc);
+ if (!isdefault) {
+ if (repl != UC_current_unitable_str) {
+ con_clear_unimap_str(0);
+ (void) UC_con_set_unimap_str(repl->entry_ct, repl->entries, 0);
+ UC_current_unitable_str = repl;
+ }
+ rc = conv_uni_to_str(outbuf, buflen, unicode, 0);
+ if (rc >= 0)
+ return (int) strlen(outbuf);
+ }
+ if (trydefault && chk_single_flag) {
+ src = conv_uni_to_pc(unicode, 1);
+ if (src >= 32) {
+ outbuf[0] = (char) src;
+ outbuf[1] = '\0';
+ return 1;
+ }
+ }
+ if (isdefault || trydefault) {
+ rc = conv_uni_to_str(outbuf, buflen, unicode, 1);
+ if (rc >= 0)
+ return (int) strlen(outbuf);
+ }
+ if (rc == ucNotFound) {
+ if (!isdefault)
+ rc = conv_uni_to_str(outbuf, buflen, UCS_REPL, 0);
+ if ((rc == ucNotFound) && (isdefault || trydefault))
+ rc = conv_uni_to_str(outbuf, buflen, UCS_REPL, 1);
+ if (rc >= 0)
+ return (int) strlen(outbuf);
+ }
+ if (chk_single_flag && src == ucNotFound) {
+ if (!isdefault)
+ rc = conv_uni_to_pc(UCS_REPL, 0);
+ if ((rc == ucNotFound) && (isdefault || trydefault))
+ rc = conv_uni_to_pc(UCS_REPL, 1);
+ if (rc >= 32) {
+ outbuf[0] = (char) rc;
+ outbuf[1] = '\0';
+ return 1;
+ } else if (rc <= 0) {
+ outbuf[0] = '\0';
+ return rc;
+ }
+ return rc;
+ }
+ return ucNotFound;
+}
+
+static int UC_FindGN_byMIME(const char *UC_MIMEcharset)
+{
+ int i;
+
+ for (i = 0; i < 4; i++) {
+ if (!strcmp(UC_MIMEcharset, UC_GNsetMIMEnames[i])) {
+ return i;
+ }
+ }
+ return ucError;
+}
+
+int UCGetRawUniMode_byLYhndl(int i)
+{
+ if (i < 0)
+ return 0;
+ return LYCharSet_UC[i].enc;
+}
+
+/*
+ * Construct a new charset name, given prefix and codepage. This introduces
+ * potentially unchecked recursion into UCGetLYhntl_byMIME if neither the "cp"
+ * nor "windows-" prefixes are configured, so we check it here.
+ */
+static int getLYhndl_byCP(const char *prefix,
+ const char *codepage)
+{
+ static int nested;
+ int result = ucError;
+
+ if (!nested++) {
+ char *cptmp = NULL;
+
+ StrAllocCopy(cptmp, prefix);
+ StrAllocCat(cptmp, codepage);
+ result = UCGetLYhndl_byMIME(cptmp);
+ FREE(cptmp);
+ }
+ nested--;
+ return result;
+}
+
+/*
+ * Get Lynx internal charset handler from MIME name,
+ * return -1 if we got NULL or did not recognize value.
+ * According to RFC, MIME headers should match case-insensitively.
+ */
+int UCGetLYhndl_byMIME(const char *value)
+{
+ int i;
+ int LYhndl = -1;
+
+ if (isEmpty(value)) {
+ CTRACE((tfp,
+ "UCGetLYhndl_byMIME: NULL argument instead of MIME name.\n"));
+ return ucError;
+ }
+
+ for (i = 0;
+ (i < MAXCHARSETS && i < LYNumCharsets &&
+ LYchar_set_names[i]); i++) {
+ if (LYCharSet_UC[i].MIMEname &&
+ !strcasecomp(value, LYCharSet_UC[i].MIMEname)) {
+ return i;
+ }
+ }
+
+ /*
+ * Not yet found, try synonyms. - FM
+ */
+#if !NO_CHARSET_utf_8
+ if (!strcasecomp(value, "unicode-1-1-utf-8") ||
+ !strcasecomp(value, "utf8")) {
+ /*
+ * Treat these as synonyms for the IANA registered name. - FM
+ */
+ return UCGetLYhndl_byMIME("utf-8");
+ }
+#endif
+ if (!strncasecomp(value, "iso", 3) && !StrNCmp(value + 3, "8859", 4)) {
+ return getLYhndl_byCP("iso-", value + 3);
+ }
+ if (!strcasecomp(value, "iso-8859-8-i") ||
+ !strcasecomp(value, "iso-8859-8-e")) {
+ return UCGetLYhndl_byMIME("iso-8859-8");
+ }
+#if !NO_CHARSET_euc_jp
+ if (!strcasecomp(value, "x-euc-jp") ||
+ !strcasecomp(value, "eucjp")) {
+ return UCGetLYhndl_byMIME("euc-jp");
+ }
+#endif
+#if !NO_CHARSET_shift_jis
+ if ((!strcasecomp(value, "x-shift-jis")) ||
+ (!strcasecomp(value, "x-sjis")) ||
+ (!strcasecomp(value, "pck"))) {
+ return UCGetLYhndl_byMIME("shift_jis");
+ }
+#endif
+#if !NO_CHARSET_euc_kr
+ if ((!strcasecomp(value, "iso-2022-kr")) ||
+ (!strcasecomp(value, "ks_c_5601-1987"))) {
+ return UCGetLYhndl_byMIME("euc-kr");
+ }
+#endif
+#if !NO_CHARSET_euc_cn
+ if (!strcasecomp(value, "gb2312") ||
+ !strncasecomp(value, "cn-gb", 5) ||
+ !strcasecomp(value, "iso-2022-cn")) {
+ return UCGetLYhndl_byMIME("euc-cn");
+ }
+#endif
+#if !NO_CHARSET_big5
+ if (!strcasecomp(value, "cn-big5")) {
+ return UCGetLYhndl_byMIME("big5");
+ }
+#endif
+#if !NO_CHARSET_macintosh
+ if (!strcasecomp(value, "x-mac-roman") ||
+ !strcasecomp(value, "mac-roman")) {
+ return UCGetLYhndl_byMIME("macintosh");
+ }
+#endif
+#if !NO_CHARSET_next
+ if (!strcasecomp(value, "x-next") ||
+ !strcasecomp(value, "nextstep") ||
+ !strcasecomp(value, "x-nextstep")) {
+ return UCGetLYhndl_byMIME("next");
+ }
+#endif
+#if !NO_CHARSET_windows_1252
+ if (!strcasecomp(value, "iso-8859-1-windows-3.1-latin-1") ||
+ !strcasecomp(value, "cp1252") ||
+ !strcasecomp(value, "cp-1252") ||
+ !strcasecomp(value, "ibm1252") ||
+ !strcasecomp(value, "iso-8859-1-windows-3.0-latin-1")) {
+ /*
+ * Treat these as synonyms for windows-1252, which is more
+ * commonly used than the IANA registered name. - FM
+ */
+ return UCGetLYhndl_byMIME("windows-1252");
+ }
+#endif
+#if !NO_CHARSET_windows_1251
+ if (!strcasecomp(value, "ansi-1251")) {
+ return UCGetLYhndl_byMIME("windows-1251");
+ }
+#endif
+#if !NO_CHARSET_windows_1250
+ if (!strcasecomp(value, "iso-8859-2-windows-latin-2") ||
+ !strcasecomp(value, "cp1250") ||
+ !strcasecomp(value, "cp-1250") ||
+ !strcasecomp(value, "ibm1250")) {
+ /*
+ * Treat these as synonyms for windows-1250. - FM
+ */
+ return UCGetLYhndl_byMIME("windows-1250");
+ }
+#endif
+ if ((!strncasecomp(value, "ibm", 3) ||
+ !strncasecomp(value, "cp-", 3)) &&
+ isdigit(UCH(value[3])) &&
+ isdigit(UCH(value[4])) &&
+ isdigit(UCH(value[5]))) {
+ /*
+ * For "ibmNNN<...>" or "cp-NNN", try "cpNNN<...>"
+ * if not yet found. - KW & FM
+ */
+ if ((LYhndl = getLYhndl_byCP("cp", value + 3)) >= 0)
+ return LYhndl;
+ /*
+ * Try windows-NNN<...> if not yet found. - FM
+ */
+ return getLYhndl_byCP("windows-", value + 3);
+ }
+ if (!strncasecomp(value, "windows-", 8) &&
+ isdigit(UCH(value[8])) &&
+ isdigit(UCH(value[9])) &&
+ isdigit(UCH(value[10]))) {
+ /*
+ * For "windows-NNN<...>", try "cpNNN<...>" - FM
+ */
+ return getLYhndl_byCP("cp", value + 8);
+ }
+#if !NO_CHARSET_koi8_r
+ if (!strcasecomp(value, "koi-8")) { /* accentsoft bugosity */
+ return UCGetLYhndl_byMIME("koi8-r");
+ }
+#endif
+ if (!strcasecomp(value, "ANSI_X3.4-1968")) {
+ return US_ASCII;
+ }
+ /* no more synonyms if come here... */
+
+ CTRACE((tfp, "UCGetLYhndl_byMIME: unrecognized MIME name \"%s\"\n", value));
+ return ucError; /* returns -1 if no charset found by that MIME name */
+}
+
+/*
+ * Function UC_setup_LYCharSets_repl() tries to set up a subtable in
+ * LYCharSets[] appropriate for this new charset, for compatibility with the
+ * "old method". Maybe not nice (maybe not even necessary any more), but it
+ * works (as far as it goes..).
+ *
+ * We try to be conservative and only allocate new memory for this if needed.
+ * If not needed, just point to SevenBitApproximations[i]. [Could do the same
+ * for ISO_Latin1[] if it's identical to that, but would make it even *more*
+ * messy than it already is...] This the only function in this file that knows,
+ * or cares, about the HTMLDTD or details of LYCharSets[] subtables (and
+ * therefore somewhat violates the idea that this file should be independent of
+ * those). As in other places, we rely on ISO_Latin1 being the *first* table
+ * in LYCharSets. - KW
+ */
+
+/*
+ * We need to remember which ones were allocated and which are static.
+ */
+static STRING2PTR remember_allocated_LYCharSets[MAXCHARSETS];
+
+static void UCreset_allocated_LYCharSets(void)
+{
+ int i = 0;
+
+ for (; i < MAXCHARSETS; i++) {
+ remember_allocated_LYCharSets[i] = NULL;
+ }
+}
+
+#ifdef LY_FIND_LEAKS
+static void UCfree_allocated_LYCharSets(void)
+{
+ int i = 0;
+
+ for (; i < MAXCHARSETS; i++) {
+ if (remember_allocated_LYCharSets[i] != NULL) {
+ FREE(remember_allocated_LYCharSets[i]);
+ }
+ }
+}
+#endif
+
+static STRING2PTR UC_setup_LYCharSets_repl(int UC_charset_in_hndl,
+ unsigned lowest8)
+{
+ STRING2PTR ISO_Latin1 = LYCharSets[0];
+ const char **p;
+ char **prepl;
+ const u16 *pp;
+ const char **tp;
+ const char *s7;
+ const char *s8;
+ size_t i;
+ int j, changed;
+ u16 k;
+ u8 *ti;
+
+ /*
+ * Create a temporary table for reverse lookup of latin1 codes:
+ */
+ if ((tp = typecallocn(const char *, 96)) == NULL)
+ return NULL;
+
+ if ((ti = typecallocn(u8, 96)) == NULL) {
+ FREE(tp);
+ return NULL;
+ }
+
+ pp = UCInfo[UC_charset_in_hndl].unitable;
+
+ /*
+ * Determine if we have any mapping of a Unicode in the range 160-255
+ * to an allowed code point > 0x80 in our new charset...
+ * Store any mappings found in ti[].
+ */
+ if (UCInfo[UC_charset_in_hndl].num_uni > 0) {
+ for (i = 0; i < 256; i++) {
+ if ((j = UCInfo[UC_charset_in_hndl].unicount[i])) {
+ if ((k = *pp) >= 160 && k < 256 && i >= lowest8) {
+ ti[k - 160] = UCH(i);
+ }
+ for (; j; j--) {
+ pp++;
+ }
+ }
+ }
+ } {
+ u16 ct;
+ struct unipair_str *list;
+
+ /*
+ * Determine if we have any mapping of a Unicode in the range
+ * 160-255 to a replacement string for our new charset...
+ * Store any mappings found in tp[].
+ */
+ ct = UCInfo[UC_charset_in_hndl].replacedesc.entry_ct;
+ list = UCInfo[UC_charset_in_hndl].replacedesc.entries;
+ while (ct--) {
+ if ((k = list->unicode) >= 160 && k < 256) {
+ tp[k - 160] = list->replace_str;
+ }
+ list++;
+ }
+ }
+ /*
+ * Now allocate a new table compatible with LYCharSets[]
+ * and with the HTMLDTD for entities.
+ * We don't know yet whether we'll keep it around.
+ */
+ prepl = (char **) malloc(HTML_dtd.number_of_entities * sizeof(char *));
+
+ if (!prepl) {
+ FREE(tp);
+ FREE(ti);
+ return 0;
+ }
+
+ p = (const char **) prepl;
+ changed = 0;
+ for (i = 0; i < HTML_dtd.number_of_entities; i++, p++) {
+ /*
+ * For each of those entities, we check what the "old method"
+ * ISO_Latin1[] mapping does with them. If it is nothing we
+ * want to use, just point to the SevenBitApproximations[] string.
+ */
+ s7 = SevenBitApproximations[i];
+ s8 = ISO_Latin1[i];
+ *p = s7;
+ if (s8 && UCH(*s8) >= 160 && s8[1] == '\0') {
+ /*
+ * We have an entity that is mapped to
+ * one valid eightbit latin1 char.
+ */
+ if (ti[UCH(*s8) - 160] >= UCH(lowest8) &&
+ !(UCH(s7[0]) == ti[UCH(*s8) - 160] &&
+ s7[1] == '\0')) {
+ /*
+ * ...which in turn is mapped, by our "new method",
+ * to another valid eightbit char for this new
+ * charset: either to itself...
+ */
+ if (ti[UCH(*s8) - 160] == UCH(*s8)) {
+ *p = s8;
+ } else {
+ /*
+ * make those 1-char strings
+ * into HTAtoms, so they will be cleaned up
+ * at exit... all for the sake of preventing
+ * memory leaks, sigh.
+ */
+ static char dummy[2]; /* one char dummy string */
+
+ dummy[0] = (char) ti[UCH(*s8) - 160];
+ *p = HTAtom_name(HTAtom_for(dummy));
+ }
+ changed = 1;
+ } else if (tp[UCH(*s8) - 160] &&
+ strcmp(s7, tp[UCH(*s8) - 160])) {
+ /*
+ * ...or which is mapped, by our "new method",
+ * to a replacement string for this new charset.
+ */
+ *p = tp[UCH(*s8) - 160];
+ changed = 1;
+ }
+ }
+ }
+ FREE(tp);
+ FREE(ti);
+ if (!changed) {
+ FREE(prepl);
+ return NULL;
+ }
+ return (STRING2PTR) prepl;
+}
+
+/*
+ * "New method" meets "Old method" ...
+ */
+static int UC_Register_with_LYCharSets(int s,
+ const char *UC_MIMEcharset,
+ const char *UC_LYNXcharset,
+ int lowest_eightbit)
+{
+ int i, LYhndl, found;
+ STRING2PTR repl;
+
+ LYhndl = -1;
+ if (LYNumCharsets == 0) {
+ /*
+ * Initialize here; so whoever changes
+ * LYCharSets.c doesn't have to count...
+ */
+ for (i = 0; (i < MAXCHARSETS) && LYchar_set_names[i]; i++) {
+ LYNumCharsets = i + 1;
+ }
+ }
+
+ /*
+ * Search by MIME name, (LYchar_set_names may differ...)
+ */
+ for (i = 0; i < MAXCHARSETS && LYchar_set_names[i] && LYhndl < 0; i++) {
+ if (LYCharSet_UC[i].MIMEname &&
+ !strcmp(UC_MIMEcharset, LYCharSet_UC[i].MIMEname)) {
+ LYhndl = i;
+ }
+ }
+
+ if (LYhndl < 0) { /* not found */
+ found = 0;
+ if (LYNumCharsets >= MAXCHARSETS) {
+ CTRACE((tfp,
+ "UC_Register_with_LYCharSets: Too many. Ignoring %s/%s.",
+ UC_MIMEcharset, UC_LYNXcharset));
+ return ucError;
+ }
+ /*
+ * Add to LYCharSets.c lists.
+ */
+ LYhndl = LYNumCharsets;
+ LYNumCharsets++;
+ LYlowest_eightbit[LYhndl] = 999;
+ LYCharSets[LYhndl] = SevenBitApproximations;
+ /*
+ * Hmm, try to be conservative here.
+ */
+ LYchar_set_names[LYhndl] = UC_LYNXcharset;
+ LYchar_set_names[LYhndl + 1] = NULL;
+ /*
+ * Terminating NULL may be looked for by Lynx code.
+ */
+ } else {
+ found = 1;
+ }
+ LYCharSet_UC[LYhndl].UChndl = s;
+ /*
+ * Can we just copy the pointer? Hope so...
+ */
+ LYCharSet_UC[LYhndl].MIMEname = UC_MIMEcharset;
+ LYCharSet_UC[LYhndl].enc = UCInfo[s].enc;
+ LYCharSet_UC[LYhndl].codepage = UCInfo[s].codepage;
+
+ /*
+ * @@@ We really SHOULD get more info from the table files,
+ * and set relevant flags in the LYCharSet_UC[] entry with
+ * that info... For now, let's try it without. - KW
+ */
+ if (lowest_eightbit < LYlowest_eightbit[LYhndl]) {
+ LYlowest_eightbit[LYhndl] = lowest_eightbit;
+ } else if (lowest_eightbit > LYlowest_eightbit[LYhndl]) {
+ UCInfo[s].lowest_eight = LYlowest_eightbit[LYhndl];
+ }
+
+ if (!found && LYhndl > 0) {
+ repl = UC_setup_LYCharSets_repl(s, (unsigned) UCInfo[s].lowest_eight);
+ if (repl) {
+ LYCharSets[LYhndl] = repl;
+ /*
+ * Remember to FREE at exit.
+ */
+ remember_allocated_LYCharSets[LYhndl] = repl;
+ }
+ }
+ return LYhndl;
+}
+
+/*
+ * This only sets up the structure - no initialization of the tables
+ * is done here yet.
+ */
+void UC_Charset_Setup(const char *UC_MIMEcharset,
+ const char *UC_LYNXcharset,
+ const u8 * unicount,
+ const u16 * unitable,
+ int nnuni,
+ struct unimapdesc_str replacedesc,
+ int lowest_eight,
+ int UC_rawuni,
+ int codepage)
+{
+ int s, Gn;
+ int i, status = 0, found;
+
+ /*
+ * Get (new?) slot.
+ */
+ found = -1;
+ for (i = 0; i < UCNumCharsets && found < 0; i++) {
+ if (!strcmp(UCInfo[i].MIMEname, UC_MIMEcharset)) {
+ found = i;
+ }
+ }
+ if (found >= 0) {
+ s = found;
+ } else {
+ if (UCNumCharsets >= MAXCHARSETS) {
+ CTRACE((tfp, "UC_Charset_Setup: Too many. Ignoring %s/%s.",
+ UC_MIMEcharset, UC_LYNXcharset));
+ return;
+ }
+ s = UCNumCharsets;
+ UCInfo[s].MIMEname = UC_MIMEcharset;
+ }
+ UCInfo[s].LYNXname = UC_LYNXcharset;
+ UCInfo[s].unicount = unicount;
+ UCInfo[s].unitable = unitable;
+ UCInfo[s].num_uni = nnuni;
+ UCInfo[s].replacedesc = replacedesc;
+ if (replacedesc.isdefault) {
+ default_UChndl = s;
+ }
+ Gn = UC_FindGN_byMIME(UC_MIMEcharset);
+ if (Gn >= 0)
+ UC_GNhandles[Gn] = s;
+ UCInfo[s].GN = Gn;
+ if (UC_rawuni == UCT_ENC_UTF8)
+ lowest_eight = 128; /* cheat here */
+ UCInfo[s].lowest_eight = lowest_eight;
+ UCInfo[s].enc = UC_rawuni;
+ UCInfo[s].codepage = codepage;
+ UCInfo[s].LYhndl = UC_Register_with_LYCharSets(s,
+ UC_MIMEcharset,
+ UC_LYNXcharset,
+ lowest_eight);
+ CTRACE2(TRACE_CFG, (tfp, "registered charset %d mime \"%s\" lynx \"%s\"\n",
+ s, UC_MIMEcharset, UC_LYNXcharset));
+ UCInfo[s].uc_status = status;
+ if (found < 0)
+ UCNumCharsets++;
+ return;
+}
+
+/*
+ * UC_NoUctb_Register_with_LYCharSets, UC_Charset_NoUctb_Setup -
+ * Alternative functions for adding character set info to the lists
+ * kept in LYCharSets.c.
+ *
+ * These are for character sets without any real tables of their own.
+ * We don't keep an entry in UCinfo[] for them.
+ */
+static int UC_NoUctb_Register_with_LYCharSets(const char *UC_MIMEcharset,
+ const char *UC_LYNXcharset,
+ int lowest_eightbit,
+ int UC_rawuni,
+ int codepage)
+{
+ int i, LYhndl = -1;
+
+ if (LYNumCharsets == 0) {
+ /*
+ * Initialize here; so whoever changes
+ * LYCharSets.c doesn't have to count...
+ */
+ for (i = 0; (i < MAXCHARSETS) && LYchar_set_names[i]; i++) {
+ LYNumCharsets = i + 1;
+ }
+ }
+
+ /*
+ * Search by MIME name, (LYchar_set_names may differ...)
+ * ignore if already present!
+ */
+ for (i = 0; i < MAXCHARSETS && LYchar_set_names[i] && LYhndl < 0; i++) {
+ if (LYCharSet_UC[i].MIMEname &&
+ !strcmp(UC_MIMEcharset, LYCharSet_UC[i].MIMEname)) {
+ return ucError;
+ }
+ }
+
+ /* not found */
+ if (LYNumCharsets >= MAXCHARSETS) {
+ CTRACE((tfp,
+ "UC_NoUctb_Register_with_LYCharSets: Too many. Ignoring %s/%s.",
+ UC_MIMEcharset, UC_LYNXcharset));
+ return ucError;
+ }
+ /*
+ * Add to LYCharSets.c lists.
+ */
+ LYhndl = LYNumCharsets;
+ LYNumCharsets++;
+ LYlowest_eightbit[LYhndl] = lowest_eightbit;
+ LYCharSets[LYhndl] = SevenBitApproximations;
+ LYchar_set_names[LYhndl] = UC_LYNXcharset;
+ LYchar_set_names[LYhndl + 1] = NULL;
+ /*
+ * Terminating NULL may be looked for by Lynx code.
+ */
+
+ LYCharSet_UC[LYhndl].UChndl = -1; /* no corresponding UChndl ! */
+ LYCharSet_UC[LYhndl].MIMEname = UC_MIMEcharset;
+ LYCharSet_UC[LYhndl].enc = UC_rawuni;
+ LYCharSet_UC[LYhndl].codepage = codepage;
+
+ /*
+ * @@@ We really SHOULD get more info from the table files,
+ * and set relevant flags in the LYCharSet_UC[] entry with
+ * that info... For now, let's try it without. - KW
+ */
+
+ return LYhndl;
+}
+
+/*
+ * A wrapper for the previous function.
+ */
+static void UC_Charset_NoUctb_Setup(const char *UC_MIMEcharset,
+ const char *UC_LYNXcharset,
+ int trydefault,
+ int lowest_eight,
+ int UC_rawuni,
+ int codepage)
+{
+ int i;
+
+ /*
+ * Ignore completely if already in slot.
+ */
+ for (i = 0; i < UCNumCharsets; i++) {
+ if (!strcmp(UCInfo[i].MIMEname, UC_MIMEcharset)) {
+ return;
+ }
+ }
+ if (UC_rawuni == UCT_ENC_UTF8)
+ lowest_eight = 128; /* cheat here */
+ /* 'codepage' doubles as a flag for 'do not try any table
+ * lookup, not even default' when negative. The value will
+ * be returned immediately by UCTrans* functions.
+ */
+ if (!trydefault && codepage == 0)
+ codepage = ucCannotOutput; /* if not already set; any negative should do. */
+ UC_NoUctb_Register_with_LYCharSets(UC_MIMEcharset,
+ UC_LYNXcharset,
+ lowest_eight,
+ UC_rawuni,
+ codepage);
+ return;
+}
+
+#ifdef LY_FIND_LEAKS
+static void UCcleanup_mem(void)
+{
+ int i;
+
+ UCfree_allocated_LYCharSets();
+ con_clear_unimap_str(0);
+ con_clear_unimap_str(1);
+ con_clear_unimap(0);
+ con_clear_unimap(1);
+ for (i = 1; i < 4; i++) { /* first one is static! */
+ FREE(inverse_translations[i]);
+ }
+}
+#endif /* LY_FIND_LEAKS */
+
+#ifdef EXP_CHARTRANS_AUTOSWITCH
+#ifdef CAN_AUTODETECT_DISPLAY_CHARSET
+# ifdef __EMX__
+static int CpOrdinal(const unsigned UCode_t cp, const int other)
+{
+ char lyName[80];
+ char myMimeName[80];
+ char *mimeName, *mName = NULL, *lName = NULL;
+ int s, i, exists = 0, ret;
+
+ CTRACE((tfp, "CpOrdinal(cp=%lu, other=%d).\n", cp, other));
+ sprintf(myMimeName, "auto%s-cp%lu", (other ? "2" : ""), cp);
+ mimeName = myMimeName + 5 + (other != 0);
+ sprintf(lyName, "AutoDetect%s (cp%lu)",
+ (other ? "-2" : ""), cp);
+ /* Find slot. */
+ s = -1;
+ for (i = 0; i < UCNumCharsets; i++) {
+ if (!strcmp(UCInfo[i].LYNXname, lyName))
+ return UCGetLYhndl_byMIME(myMimeName);
+ else if (!strcasecomp(UCInfo[i].MIMEname, mimeName))
+ s = i;
+ }
+ if (s < 0)
+ return ucError;
+ /* Store the "real" charset info */
+ real_charsets[other != 0] = UCGetLYhndl_byMIME(mimeName);
+ /* Duplicate the record. */
+ StrAllocCopy(mName, myMimeName);
+ StrAllocCopy(lName, lyName);
+ UC_Charset_Setup(mName, lName,
+ UCInfo[s].unicount, UCInfo[s].unitable,
+ UCInfo[s].num_uni, UCInfo[s].replacedesc,
+ UCInfo[s].lowest_eight, UCInfo[s].enc,
+ UCInfo[s].codepage);
+ ret = UCGetLYhndl_byMIME(myMimeName);
+ CTRACE((tfp, "Found %i.\n", ret));
+ return ret;
+}
+# endif /* __EMX__ */
+#endif /* CAN_AUTODETECT_DISPLAY_CHARSET */
+#endif /* EXP_CHARTRANS_AUTOSWITCH */
+
+void UCInit(void)
+{
+
+ UCreset_allocated_LYCharSets();
+#ifdef LY_FIND_LEAKS
+ atexit(UCcleanup_mem);
+#endif
+ UCconsole_map_init();
+
+ /*
+ * The order of charset names visible in Lynx Options menu correspond to
+ * the order of lines below, except the first two described in LYCharSet.c
+ *
+ * Entries whose comment is marked with *** are declared in UCdomap.h,
+ * others are based on the included tables - UCdomap.c, near the top.
+ */
+
+ UC_CHARSET_SETUP_iso_8859_1; /* ISO Latin 1 */
+ UC_CHARSET_SETUP_iso_8859_15; /* ISO 8859-15 (Latin 9) */
+ UC_CHARSET_SETUP_cp850; /* DosLatin1 (cp850) */
+ UC_CHARSET_SETUP_windows_1252; /* WinLatin1 (cp1252) */
+ UC_CHARSET_SETUP_cp437; /* DosLatinUS (cp437) */
+
+ UC_CHARSET_SETUP_dec_mcs; /* DEC Multinational */
+ UC_CHARSET_SETUP_macintosh; /* Macintosh (8 bit) */
+ UC_CHARSET_SETUP_next; /* NeXT character set */
+ UC_CHARSET_SETUP_hp_roman8; /* HP Roman8 */
+
+ UC_CHARSET_SETUP_euc_cn; /*** Chinese */
+ UC_CHARSET_SETUP_euc_jp; /*** Japanese (EUC_JP) */
+ UC_CHARSET_SETUP_shift_jis; /*** Japanese (Shift_JIS) */
+ UC_CHARSET_SETUP_euc_kr; /*** Korean */
+ UC_CHARSET_SETUP_big5; /*** Taipei (Big5) */
+
+ UC_CHARSET_SETUP_viscii; /* Vietnamese (VISCII) */
+ UC_CHARSET_SETUP; /* us-ascii */ /* 7 bit approximations */
+
+ UC_CHARSET_SETUP_x_transparent; /*** Transparent */
+
+ UC_CHARSET_SETUP_iso_8859_2; /* ISO Latin 2 */
+ UC_CHARSET_SETUP_cp852; /* DosLatin2 (cp852) */
+ UC_CHARSET_SETUP_windows_1250; /* WinLatin2 (cp1250) */
+
+ UC_CHARSET_SETUP_iso_8859_3; /* ISO Latin 3 */
+ UC_CHARSET_SETUP_iso_8859_4; /* ISO Latin 4 */
+ UC_CHARSET_SETUP_iso_8859_13; /* ISO 8859-13 Baltic Rim */
+ UC_CHARSET_SETUP_cp775; /* DosBaltRim (cp775) */
+ UC_CHARSET_SETUP_windows_1257; /* WinBaltRim (cp1257) */
+ UC_CHARSET_SETUP_iso_8859_5; /* ISO 8859-5 Cyrillic */
+ UC_CHARSET_SETUP_cp866; /* DosCyrillic (cp866) */
+ UC_CHARSET_SETUP_windows_1251; /* WinCyrillic (cp1251) */
+ UC_CHARSET_SETUP_koi8_r; /* KOI8-R Cyrillic */
+ UC_CHARSET_SETUP_iso_8859_6; /* ISO 8869-6 Arabic */
+ UC_CHARSET_SETUP_cp864; /* DosArabic (cp864) */
+ UC_CHARSET_SETUP_windows_1256; /* WinArabic (cp1256) */
+ UC_CHARSET_SETUP_iso_8859_14; /* ISO 8859-14 Celtic */
+ UC_CHARSET_SETUP_iso_8859_7; /* ISO 8859-7 Greek */
+ UC_CHARSET_SETUP_cp737; /* DosGreek (cp737) */
+ UC_CHARSET_SETUP_cp869; /* DosGreek2 (cp869) */
+ UC_CHARSET_SETUP_windows_1253; /* WinGreek (cp1253) */
+ UC_CHARSET_SETUP_iso_8859_8; /* ISO 8859-8 Hebrew */
+ UC_CHARSET_SETUP_cp862; /* DosHebrew (cp862) */
+ UC_CHARSET_SETUP_windows_1255; /* WinHebrew (cp1255) */
+ UC_CHARSET_SETUP_iso_8859_9; /* ISO 8859-9 (Latin 5) */
+ UC_CHARSET_SETUP_cp857; /* DosTurkish (cp857) */
+ UC_CHARSET_SETUP_iso_8859_10; /* ISO 8859-10 North European */
+ UC_CHARSET_SETUP_iso_8859_16; /* ISO 8859-16 (Latin 10) */
+
+ UC_CHARSET_SETUP_utf_8; /*** UNICODE UTF-8 */
+ UC_CHARSET_SETUP_mnemonic_ascii_0; /* RFC 1345 w/o Intro */
+ UC_CHARSET_SETUP_mnemonic; /* RFC 1345 Mnemonic */
+ UC_CHARSET_SETUP_cp866u; /* Ukrainian Cyrillic (866) */
+ UC_CHARSET_SETUP_koi8_u; /* Ukrainian Cyrillic (koi8-u) */
+ UC_CHARSET_SETUP_ptcp154; /* Cyrillic-Asian (PT154) */
+
+#ifdef EXP_CHARTRANS_AUTOSWITCH
+#ifdef CAN_AUTODETECT_DISPLAY_CHARSET
+# ifdef __EMX__
+ {
+ unsigned UCode_t lst[3];
+ unsigned UCode_t len, rc;
+
+ rc = DosQueryCp(sizeof(lst), lst, &len);
+ if (rc == 0) {
+ if (len >= 1)
+ auto_display_charset = CpOrdinal(lst[0], 0);
+# ifdef CAN_SWITCH_DISPLAY_CHARSET
+ if (len >= 3) {
+ codepages[0] = lst[0];
+ codepages[1] = (lst[0] == lst[1] ? lst[2] : lst[1]);
+ auto_other_display_charset = CpOrdinal(codepages[1], 1);
+ }
+# endif
+ } else {
+ CTRACE((tfp, "DosQueryCp() returned %#lx=%lu.\n", rc, rc));
+ }
+ }
+# endif
+#endif
+#endif
+
+/*
+ * To add synonyms for any charset name check function UCGetLYhndl_byMIME in
+ * this file.
+ */
+
+/* for coding/performance - easy to type: */
+ LATIN1 = UCGetLYhndl_byMIME("iso-8859-1");
+ US_ASCII = UCGetLYhndl_byMIME("us-ascii");
+ UTF8_handle = UCGetLYhndl_byMIME("utf-8");
+ TRANSPARENT = UCGetLYhndl_byMIME("x-transparent");
+}
+
+/*
+ * Safe variant of UCGetLYhndl_byMIME, with blind recovery from typo in user
+ * input: lynx.cfg, userdefs.h, command line switches.
+ */
+int safeUCGetLYhndl_byMIME(const char *value)
+{
+ int i = UCGetLYhndl_byMIME(value);
+
+ if (i == -1) { /* was user's typo or not yet recognized value */
+ i = LATIN1; /* error recovery? */
+ CTRACE((tfp, "safeUCGetLYhndl_byMIME: ISO-8859-1 assumed.\n"));
+ }
+
+ return (i);
+}
+
+#ifdef USE_LOCALE_CHARSET
+
+#if defined(USE_LOCALE_CHARSET) && !defined(HAVE_LANGINFO_CODESET)
+/*
+ * This is a quick-and-dirty emulator of the nl_langinfo(CODESET)
+ * function defined in the Single Unix Specification for those systems
+ * (FreeBSD, etc.) that don't have one yet. It behaves as if it had
+ * been called after setlocale(LC_CTYPE, ""), that is it looks at
+ * the locale environment variables.
+ *
+ * http://www.opengroup.org/onlinepubs/7908799/xsh/langinfo.h.html
+ *
+ * Please extend it as needed and suggest improvements to the author.
+ * This emulator will hopefully become redundant soon as
+ * nl_langinfo(CODESET) becomes more widely implemented.
+ *
+ * Since the proposed Li18nux encoding name registry is still not mature,
+ * the output follows the MIME registry where possible:
+ *
+ * http://www.iana.org/assignments/character-sets
+ *
+ * A possible autoconf test for the availability of nl_langinfo(CODESET)
+ * can be found in
+ *
+ * http://www.cl.cam.ac.uk/~mgk25/unicode.html#activate
+ *
+ * Markus.Kuhn@cl.cam.ac.uk -- 2002-03-11
+ * Permission to use, copy, modify, and distribute this software
+ * for any purpose and without fee is hereby granted. The author
+ * disclaims all warranties with regard to this software.
+ *
+ * Latest version:
+ *
+ * http://www.cl.cam.ac.uk/~mgk25/ucs/langinfo.c
+ */
+
+/*
+#include "langinfo.h"
+*/
+typedef int nl_item;
+
+#define CODESET 1
+
+#define C_CODESET "US-ASCII" /* Return this as the encoding of the
+ * C/POSIX locale. Could as well one day
+ * become "UTF-8". */
+
+#define digit(x) ((x) >= '0' && (x) <= '9')
+
+static char buf[16];
+
+static char *nl_langinfo(nl_item item)
+{
+ char *l, *p;
+
+ if (item != CODESET)
+ return NULL;
+
+ if (((l = LYGetEnv("LC_ALL")) != 0) ||
+ ((l = LYGetEnv("LC_CTYPE")) != 0) ||
+ ((l = LYGetEnv("LANG")) != 0)) {
+ /* check standardized locales */
+ if (!strcmp(l, "C") || !strcmp(l, "POSIX"))
+ return C_CODESET;
+ /* check for encoding name fragment */
+ if (strstr(l, "UTF") || strstr(l, "utf"))
+ return "UTF-8";
+ if ((p = strstr(l, "8859-"))) {
+ memcpy(buf, "ISO-8859-\0\0", 12);
+ p += 5;
+ if (digit(*p)) {
+ buf[9] = *p++;
+ if (digit(*p))
+ buf[10] = *p++;
+ return buf;
+ }
+ }
+ if (strstr(l, "KOI8-R"))
+ return "KOI8-R";
+ if (strstr(l, "KOI8-U"))
+ return "KOI8-U";
+ if (strstr(l, "620"))
+ return "TIS-620";
+ if (strstr(l, "2312"))
+ return "GB2312";
+ if (strstr(l, "HKSCS"))
+ return "Big5HKSCS"; /* no MIME charset */
+ if (strstr(l, "Big5") || strstr(l, "BIG5"))
+ return "Big5";
+ if (strstr(l, "GBK"))
+ return "GBK"; /* no MIME charset */
+ if (strstr(l, "18030"))
+ return "GB18030"; /* no MIME charset */
+ if (strstr(l, "Shift_JIS") || strstr(l, "SJIS"))
+ return "Shift_JIS";
+ /* check for conclusive modifier */
+ if (strstr(l, "euro"))
+ return "ISO-8859-15";
+ /* check for language (and perhaps country) codes */
+ if (strstr(l, "zh_TW"))
+ return "Big5";
+ if (strstr(l, "zh_HK"))
+ return "Big5HKSCS"; /* no MIME charset */
+ if (strstr(l, "zh"))
+ return "GB2312";
+ if (strstr(l, "ja"))
+ return "EUC-JP";
+ if (strstr(l, "ko"))
+ return "EUC-KR";
+ if (strstr(l, "ru"))
+ return "KOI8-R";
+ if (strstr(l, "uk"))
+ return "KOI8-U";
+ if (strstr(l, "pl") || strstr(l, "hr") ||
+ strstr(l, "hu") || strstr(l, "cs") ||
+ strstr(l, "sk") || strstr(l, "sl"))
+ return "ISO-8859-2";
+ if (strstr(l, "eo") || strstr(l, "mt"))
+ return "ISO-8859-3";
+ if (strstr(l, "el"))
+ return "ISO-8859-7";
+ if (strstr(l, "he"))
+ return "ISO-8859-8";
+ if (strstr(l, "tr"))
+ return "ISO-8859-9";
+ if (strstr(l, "th"))
+ return "TIS-620"; /* or ISO-8859-11 */
+ if (strstr(l, "lt"))
+ return "ISO-8859-13";
+ if (strstr(l, "cy"))
+ return "ISO-8859-14";
+ if (strstr(l, "ro"))
+ return "ISO-8859-2"; /* or ISO-8859-16 */
+ if (strstr(l, "am") || strstr(l, "vi"))
+ return "UTF-8";
+ /* Send me further rules if you like, but don't forget that we are
+ * *only* interested in locale naming conventions on platforms
+ * that do not already provide an nl_langinfo(CODESET) implementation. */
+ return "ISO-8859-1"; /* should perhaps be "UTF-8" instead */
+ }
+ return C_CODESET;
+}
+#endif /* defined(USE_LOCALE_CHARSET) && !defined(HAVE_LANGINFO_CODESET) */
+
+/*
+ * If LYLocaleCharset is true, use the current locale to lookup a MIME name
+ * that corresponds, and use that as the display charset. This feature is
+ * experimental because while nl_langinfo(CODESET) itself is standardized,
+ * the return values and their relationship to the locale value is not.
+ * GNU libiconv happens to give useful values, but other implementations are
+ * not guaranteed to do this.
+ *
+ * Not all Linux versions provide useful information. GNU libc 2.2 returns
+ * "ANSI_X3.4-1968"
+ * whether locale is POSIX or en_US.UTF-8.
+ *
+ * Another possible thing to investigate is the locale_charset() function
+ * provided in libiconv 1.5.1.
+ */
+void LYFindLocaleCharset(void)
+{
+ char *name;
+
+ CTRACE((tfp, "LYFindLocaleCharset(%d)\n", LYLocaleCharset));
+ name = nl_langinfo(CODESET);
+
+ if (name != 0) {
+ int value = UCGetLYhndl_byMIME(name);
+
+ if (value >= 0) {
+ linedrawing_char_set = value;
+ CTRACE((tfp, "Found name \"%s\" -> %d\n", name, value));
+ /*
+ * If no locale was set, we will get the POSIX character set, which
+ * in Lynx is treated as US-ASCII. However, Lynx's longstanding
+ * behavior has been to default to ISO-8859-1. So we treat that
+ * encoding specially. Otherwise, if LOCALE_CHARSET is set, then
+ * we will use the locale encoding -- unless overridden by the
+ * ASSUME_CHARSET value and/or command-line option.
+ */
+ if (LYLocaleCharset) {
+ CTRACE((tfp, "...prior LocaleCharset '%s'\n", NonNull(UCAssume_MIMEcharset)));
+ if (value == US_ASCII) {
+ CTRACE((tfp, "...prefer existing charset to ASCII\n"));
+ } else if (assumed_charset) {
+ CTRACE((tfp, "...already assumed-charset\n"));
+ } else {
+ current_char_set = linedrawing_char_set;
+ UCLYhndl_for_unspec = current_char_set;
+ StrAllocCopy(UCAssume_MIMEcharset, name);
+ CTRACE((tfp, "...using LocaleCharset '%s'\n", NonNull(UCAssume_MIMEcharset)));
+ }
+ }
+ } else {
+ CTRACE((tfp, "Cannot find a handle for MIME name \"%s\"\n", name));
+ }
+ } else {
+ CTRACE((tfp, "Cannot find a MIME name for locale\n"));
+ }
+}
+#endif /* USE_LOCALE_CHARSET */
+
+BOOL UCScanCode(UCode_t *target, const char *source, BOOL isHex)
+{
+ BOOL status = FALSE;
+ long lcode;
+ char *endptr;
+
+ errno = 0;
+ *target = 0;
+ lcode = strtol(source, &endptr, isHex ? 16 : 10);
+ if (lcode >= 0
+ && (endptr > source)
+#if defined(ERANGE) && defined(LONG_MAX) && defined(LONG_MIN)
+ && (errno != ERANGE || (lcode != LONG_MAX && lcode != LONG_MIN))
+#else
+ && (endptr - source) < (isHex ? 8 : 10)
+#endif
+ && (endptr != 0)
+ && (*endptr == '\0')) {
+ *target = (UCode_t) lcode;
+ status = TRUE;
+ }
+ return status;
+}
diff --git a/src/UCdomap.h b/src/UCdomap.h
new file mode 100644
index 0000000..1a2f00e
--- /dev/null
+++ b/src/UCdomap.h
@@ -0,0 +1,178 @@
+#ifndef UCDOMAP_H
+#define UCDOMAP_H
+
+#ifndef HTUTILS_H
+#include <HTUtils.h>
+#endif
+
+#ifndef ALL_CHARSETS
+#define ALL_CHARSETS 1
+#endif
+
+#include <UCkd.h>
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+/*
+ * [old comments: - KW ]
+ * consolemap.h
+ *
+ * Interface between console.c, selection.c and UCmap.c
+ */
+#define LAT1_MAP 0
+#define GRAF_MAP 1
+#define IBMPC_MAP 2
+#define USER_MAP 3
+/*
+ * Some conventions I try to follow (loosely):
+ * [a-z]* only internal, names from linux driver code.
+ * UC_* to be only known internally.
+ * UC[A-Z]* to be exported to other parts of Lynx. -KW
+ */ extern void UC_Charset_Setup(const char *UC_MIMEcharset,
+ const char *UC_LYNXcharset,
+ const u8 * unicount,
+ const u16 * unitable,
+ int nnuni,
+ struct unimapdesc_str replacedesc,
+ int lowest_eight,
+ int UC_rawuni,
+ int codepage);
+
+ struct UC_charset {
+ const char *MIMEname;
+ const char *LYNXname;
+ const u8 *unicount;
+ const u16 *unitable;
+ int num_uni;
+ struct unimapdesc_str replacedesc;
+ int uc_status;
+ int LYhndl;
+ int GN;
+ int lowest_eight;
+ int enc;
+ int codepage; /* codepage number, used by OS/2 font-switching code */
+ };
+
+ extern int UCNumCharsets;
+ extern int UCInitialized;
+
+ extern void UCInit(void);
+
+/*
+ * INSTRUCTIONS for adding new character sets which do not have Unicode tables.
+ *
+ * Several #defines below are declarations for charsets which need no tables
+ * for mapping to Unicode - CJK multibytes, x-transparent, UTF8 - Lynx takes
+ * care of them internally.
+ *
+ * The declaration's format is kept in chrtrans/XXX_uni.h - keep this in mind
+ * when changing ucmaketbl.c, see also UC_Charset_Setup() above for details.
+ */
+
+ /*
+ * There is no strict correlation for the next five, since the transfer
+ * charset gets decoded into Display Char Set by the CJK code (separate from
+ * Unicode mechanism). For now we use the MIME name that describes what is
+ * output to the terminal. - KW
+ */
+
+/*----------------------------------------------------------------------------*/
+
+#ifndef NO_CHARSET_euc_cn
+#define NO_CHARSET_euc_cn !ALL_CHARSETS
+#endif
+
+#if NO_CHARSET_euc_cn
+#define UC_CHARSET_SETUP_euc_cn /* nothing */
+#else
+#define UC_CHARSET_SETUP_euc_cn UC_Charset_NoUctb_Setup("euc-cn","Chinese",\
+ 1, 128,UCT_ENC_CJK,0)
+#endif
+
+/*----------------------------------------------------------------------------*/
+
+#ifndef NO_CHARSET_euc_jp
+#define NO_CHARSET_euc_jp !ALL_CHARSETS
+#endif
+
+#if NO_CHARSET_euc_jp
+#define UC_CHARSET_SETUP_euc_jp /* nothing */
+#else
+#define UC_CHARSET_SETUP_euc_jp UC_Charset_NoUctb_Setup("euc-jp","Japanese (EUC-JP)",\
+ 1, 128,UCT_ENC_CJK,0)
+#endif
+
+/*----------------------------------------------------------------------------*/
+
+#ifndef NO_CHARSET_shift_jis
+#define NO_CHARSET_shift_jis !ALL_CHARSETS
+#endif
+
+#if NO_CHARSET_shift_jis
+#define UC_CHARSET_SETUP_shift_jis /* nothing */
+#else
+#define UC_CHARSET_SETUP_shift_jis UC_Charset_NoUctb_Setup("shift_jis","Japanese (Shift_JIS)",\
+ 1, 128,UCT_ENC_CJK,0)
+#endif
+
+/*----------------------------------------------------------------------------*/
+
+#ifndef NO_CHARSET_euc_kr
+#define NO_CHARSET_euc_kr !ALL_CHARSETS
+#endif
+
+#if NO_CHARSET_euc_kr
+#define UC_CHARSET_SETUP_euc_kr /* nothing */
+#else
+#define UC_CHARSET_SETUP_euc_kr UC_Charset_NoUctb_Setup("euc-kr","Korean",\
+ 1, 128,UCT_ENC_CJK,0)
+#endif
+
+/*----------------------------------------------------------------------------*/
+
+#ifndef NO_CHARSET_big5
+#define NO_CHARSET_big5 !ALL_CHARSETS
+#endif
+
+#if NO_CHARSET_big5
+#define UC_CHARSET_SETUP_big5 /* nothing */
+#else
+#define UC_CHARSET_SETUP_big5 UC_Charset_NoUctb_Setup("big5","Taipei (Big5)",\
+ 1, 128,UCT_ENC_CJK,0)
+#endif
+
+/*----------------------------------------------------------------------------*/
+
+ /*
+ * Placeholder for non-translation mode. - FM
+ */
+
+#ifndef NO_CHARSET_x_transparent
+#define NO_CHARSET_x_transparent !ALL_CHARSETS
+#endif
+
+#if NO_CHARSET_x_transparent
+#define UC_CHARSET_SETUP_x_transparent /* nothing */
+#else
+#define UC_CHARSET_SETUP_x_transparent UC_Charset_NoUctb_Setup("x-transparent","Transparent",\
+ 0, 128,UCT_ENC_8BIT,0)
+#endif
+
+/*----------------------------------------------------------------------------*/
+
+#ifndef NO_CHARSET_utf_8
+#define NO_CHARSET_utf_8 !ALL_CHARSETS
+#endif
+
+#if NO_CHARSET_utf_8
+#define UC_CHARSET_SETUP_utf_8 /* nothing */
+#else
+#define UC_CHARSET_SETUP_utf_8 UC_Charset_NoUctb_Setup("utf-8","UNICODE (UTF-8)",\
+ 0, 128,UCT_ENC_UTF8,-4)
+#endif
+
+#ifdef __cplusplus
+}
+#endif
+#endif /* UCDOMAP_H */
diff --git a/src/Xsystem.c b/src/Xsystem.c
new file mode 100644
index 0000000..cc6cdd8
--- /dev/null
+++ b/src/Xsystem.c
@@ -0,0 +1,589 @@
+/* $LynxId: Xsystem.c,v 1.29 2018/02/17 14:58:15 tom Exp $
+ * like system("cmd") but return with exit code of "cmd"
+ * for Turbo-C/MS-C/LSI-C
+ * This code is in the public domain.
+ *
+ * @Log: xsystem.c,v @
+ *
+ * Revision 1.14 1997/10/17 (Fri) 16:28:24 senshu
+ * *** for Win32 version ***
+ *
+ * Revision 1.13 1992/02/24 06:59:13 serow
+ * *** empty log message ***
+ *
+ * Revision 1.12 1991/04/09 08:48:20 serow
+ * ignore new line at command line tail
+ *
+ * Revision 1.11 1991/03/12 07:12:50 serow
+ * CMDLINE
+ *
+ * Revision 1.10 91/02/24 05:10:14 serow
+ * 2>&1
+ *
+ * Revision 1.9 91/02/22 07:01:17 serow
+ * NEAR for ms-c
+ *
+ */
+#include <LYUtils.h>
+#include <LYStrings.h>
+#include <LYGlobalDefs.h>
+
+#ifdef DOSPATH
+#include <io.h>
+#else
+extern char *mktemp(char *);
+#endif
+
+#ifndef USECMDLINE
+#define USECMDLINE 0
+#endif
+
+#ifndef TRUE
+#define TRUE 1
+#define FALSE 0
+#endif
+
+#define TABLESIZE(v) (sizeof(v)/sizeof(v[0]))
+
+#define STR_MAX 512 /* MAX command line */
+
+#define isk1(c) ((0x81 <= UCH(c) && UCH(c) <= 0x9F) || (0xE0 <= UCH(c) && UCH(c) <= 0xFC))
+#define isq(c) ((c) == '"')
+#define isspc(c) ((c) == ' ' || (c) == '\t')
+#define issep(c) (isspc(c) || (c) == '"' || (c) == '\'' || (c) == '<' || (c) == '>' || (c) == 0)
+#define issep2(c) (issep(c) || (c) == '.' || (c) == '\\' || (c) == '/')
+#define isdeg(c) ('0' <= (c) && (c) <= '9')
+
+#ifndef NEAR
+#define NEAR
+#endif
+
+#define SAVE_FD 10
+
+typedef struct _proc {
+ struct _proc *next;
+ char *line;
+ char *cmd;
+ char *arg;
+ char *inf;
+ int infmod;
+ char *outf;
+ int outfmod;
+ int ored[SAVE_FD];
+ int sred[SAVE_FD];
+} PRO;
+
+static PRO *p1 = 0;
+
+static char *NEAR xmalloc(size_t n)
+{
+ char *bp;
+
+ if ((bp = typecallocn(char, n)) == 0) {
+ write(2, "xsystem: Out of memory.!\n", 25);
+ exit_immediately(EXIT_FAILURE);
+ }
+ return bp;
+}
+
+static char *NEAR xrealloc(void *p, size_t n)
+{
+ char *bp;
+
+ if ((bp = realloc(p, n)) == (char *) 0) {
+ write(2, "xsystem: Out of memory!.\n", 25);
+ exit_immediately(EXIT_FAILURE);
+ }
+ return bp;
+}
+
+static int NEAR is_builtin_command(char *s)
+{
+ static char *cmdtab[] =
+ {
+ "dir", "type", "rem", "ren", "rename", "erase", "del",
+ "copy", "pause", "date", "time", "ver", "vol", "label",
+ "cd", "chdir", "md", "mkdir", "rd", "rmdir", "break",
+ "verify", "set", "prompt", "path", "exit", "ctty", "echo",
+ "if", "for", "cls", "goto", "shift"
+ ,"start" /* start is NT only */
+ };
+ int i, l, lc, count;
+
+ l = (int) strlen(s);
+ count = TABLESIZE(cmdtab);
+ count--;
+#ifdef WIN_EX
+ if (system_is_NT)
+ count++;
+#endif
+ for (i = 0; i < count; i++) {
+ if (strcasecomp(s, cmdtab[i]) == 0)
+ return 1;
+ lc = (int) strlen(cmdtab[i]);
+ if (lc < l && strncasecomp(s, cmdtab[i], lc) == 0 && issep2(s[lc]))
+ return 1;
+ }
+ return 0;
+}
+
+static int NEAR getswchar(void)
+{
+ int result;
+
+#ifdef __WIN32__
+ result = '/';
+#else
+ union REGS reg;
+
+ reg.x.ax = 0x3700;
+ intdos(&reg, &reg);
+ result = reg.h.dl;
+#endif
+ return result;
+}
+
+#define spawnl_rc(flag, rc) \
+ ((flag == P_WAIT) \
+ ? (int)(rc) \
+ : ((rc) == 0 ? -2 : 0))
+
+static int NEAR csystem(PRO * p, int flag)
+{
+ char *cmp;
+ char SW[3];
+ intptr_t rc;
+
+ if ((cmp = LYGetEnv("COMSPEC")) == 0)
+ return -2;
+ SW[0] = (char) getswchar();
+ SW[1] = 'c';
+ SW[2] = 0;
+ rc = spawnl(flag, cmp, cmp, SW, p->cmd, p->arg, (char *) 0);
+ return spawnl_rc(flag, rc);
+}
+
+static PRO *NEAR pars1c(char *s)
+{
+ PRO *pp;
+ char *fnp;
+ int ms, mi;
+ int fs, fi, inpf;
+ int q;
+
+ pp = (PRO *) xmalloc(sizeof(PRO));
+ for (q = 0; q < (int) TABLESIZE(pp->ored); q++)
+ pp->ored[q] = q;
+ while (isspc(*s))
+ s++;
+ pp->line = strdup(s);
+ pp->cmd = xmalloc(ms = 8);
+ mi = 0;
+ while (!issep(*s)) {
+ if (mi >= ms - 1)
+ pp->cmd = xrealloc(pp->cmd, ms += 8);
+ pp->cmd[mi++] = *s++;
+ }
+ pp->cmd[mi] = 0;
+ q = 0;
+ pp->arg = xmalloc(ms = 32);
+ if (isspc(*s))
+ s++;
+ mi = 0;
+ while (*s) {
+ if (mi >= ms - 1) {
+ pp->arg = xrealloc(pp->arg, ms += 32);
+ }
+ if (q == 0) {
+ inpf = 0;
+ if ((mi == 0 || isspc(s[-1])) &&
+ isdeg(s[0]) && s[1] == '>' &&
+ s[2] == '&' && isdeg(s[3])) {
+
+ pp->ored[s[0] & 15] = s[3] & 15;
+ s += 4;
+ continue;
+ } else if (s[0] == '<') {
+ if (pp->inf == 0) {
+ pp->infmod = O_RDONLY;
+ }
+ inpf = 1;
+ } else if (s[0] == '>' && s[1] == '>') {
+ if (pp->outf == 0) {
+ pp->outfmod = O_WRONLY | O_CREAT | O_APPEND;
+ }
+ s++;
+ } else if (s[0] == '>') {
+ if (pp->outf == 0) {
+ pp->outfmod = O_WRONLY | O_CREAT | O_TRUNC;
+ }
+ } else {
+ if (*s == '"')
+ q = !q;
+ pp->arg[mi++] = *s++;
+ continue;
+ }
+ fnp = xmalloc(fs = 16);
+ fi = 0;
+ s++;
+ while (isspc(*s))
+ s++;
+ while (!issep(*s)) {
+ if (fi >= fs - 1)
+ fnp = xrealloc(fnp, fs += 16);
+ fnp[fi++] = *s++;
+ }
+ fnp[fi] = 0;
+ if (inpf) {
+ if (pp->inf == 0)
+ pp->inf = fnp;
+ } else {
+ if (pp->outf == 0)
+ pp->outf = fnp;
+ }
+ } else if (s[0] == '"') {
+ q = !q;
+ pp->arg[mi++] = *s++;
+ } else {
+ pp->arg[mi++] = *s++;
+ }
+ }
+ pp->arg[mi] = 0;
+ return pp;
+}
+
+static PRO *NEAR pars(char *s)
+{
+ char *lb;
+ int li, ls, q;
+ int c;
+ PRO *pp = 0;
+
+ lb = xmalloc(ls = STR_MAX); /* about */
+ li = q = 0;
+ p1 = 0;
+
+ for (;;) {
+ c = *s++;
+ if (li >= ls - 2)
+ lb = xrealloc(lb, ls += STR_MAX);
+ if (isk1(c) && *s) {
+ lb[li++] = (char) c;
+ lb[li++] = *s++;
+ } else if ((!q && c == '|') || c == 0 || (c == '\n' && *s == 0)) {
+ lb[li++] = 0;
+ if (p1 == 0) {
+ pp = p1 = pars1c(lb);
+ } else {
+ pp->next = pars1c(lb);
+ pp = pp->next;
+ }
+ li = 0;
+ if (c == 0 || (c == '\n' && *s == 0))
+ break;
+ } else if (c == '"') {
+ q = !q;
+ lb[li++] = (char) c;
+ } else {
+ lb[li++] = (char) c;
+ }
+ }
+ free(lb);
+ return p1;
+}
+
+static int NEAR try3(char *cnm, PRO * p, int flag)
+{
+ char cmdb[STR_MAX];
+ int rc;
+ intptr_t rc2;
+
+ sprintf(cmdb, "%.*s.com", (int) sizeof(cmdb) - 5, cnm);
+ if ((rc = open(cmdb, O_RDONLY)) >= 0) {
+ close(rc);
+ rc2 = spawnl(flag, cmdb, cmdb, p->arg, (char *) 0);
+ return spawnl_rc(flag, rc2);
+ }
+ sprintf(cmdb, "%.*s.exe", (int) sizeof(cmdb) - 5, cnm);
+ if ((rc = open(cmdb, O_RDONLY)) >= 0) {
+ close(rc);
+ rc2 = spawnl(flag, cmdb, cmdb, p->arg, (char *) 0);
+ return spawnl_rc(flag, rc2);
+ }
+ sprintf(cmdb, "%.*s.bat", (int) sizeof(cmdb) - 5, cnm);
+ if ((rc = open(cmdb, O_RDONLY)) >= 0) {
+ close(rc);
+ return csystem(p, flag);
+ }
+ return -1;
+}
+
+static int NEAR prog_go(PRO * p, int flag)
+{
+ char *s;
+ char *extp = 0;
+ char cmdb[STR_MAX];
+ char *ep;
+ int rc, lc = 0, cmd_len;
+ intptr_t rc2;
+
+ cmd_len = (int) strlen(p->cmd);
+
+ s = p->cmd + cmd_len - 1;
+ while (cmd_len && (*s != '\\') && (*s != '/') && (*s != ':')) {
+ if (*s == '.')
+ extp = s;
+ cmd_len--;
+ s--;
+ }
+
+ if (is_builtin_command(p->cmd) || (extp && strcasecomp(extp, ".bat") == 0))
+ return csystem(p, flag);
+
+ if (s < p->cmd) { /* cmd has no PATH nor Drive */
+ ep = LYGetEnv("PATH");
+ LYStrNCpy(cmdb, p->cmd, sizeof(cmdb) - 1);
+ for (;;) {
+ if (extp) { /* has extension */
+ if ((rc = open(cmdb, O_RDONLY)) >= 0) {
+ close(rc);
+ rc2 = spawnl(flag, cmdb, cmdb, p->arg, (char *) 0);
+ rc = spawnl_rc(flag, rc2);
+ }
+ } else {
+ rc = try3(cmdb, p, flag);
+ }
+ if (rc >= 0)
+ return rc;
+
+ if (ep && *ep) {
+ int i;
+
+ for (i = 0; *ep != ';' && *ep != '\0'; ep++, i++)
+ lc = cmdb[i] = *ep;
+ if (*ep == ';')
+ ep++;
+ if (i > 0 && lc != ':' && lc != '\\' && lc != '/')
+ cmdb[i++] = '\\';
+ cmdb[i] = 0;
+ LYStrNCpy(cmdb + i, p->cmd, sizeof(cmdb) - 1 - i);
+ } else {
+ if (rc == -2)
+ return rc;
+ return -1;
+ }
+ }
+ } else { /* has PATH or Drive */
+ if (extp) { /* has extension */
+ if ((rc = open(p->cmd, O_RDONLY)) >= 0) {
+ close(rc);
+ rc2 = spawnl(flag, p->cmd, p->cmd, p->arg, (char *) 0);
+ return spawnl_rc(flag, rc2);
+ }
+ return -1;
+ } else {
+ return try3(p->cmd, p, flag);
+ }
+ }
+}
+
+static char *NEAR tmpf(char *tp)
+{
+ char tplate[STR_MAX];
+ char *ev;
+ int i;
+
+ if ((ev = LYGetEnv("TMP")) != 0) {
+ LYStrNCpy(tplate, ev, sizeof(tplate) - 2 - strlen(tp));
+ i = (int) strlen(ev);
+ if (i && ev[i - 1] != '\\' && ev[i - 1] != '/')
+ strcat(tplate, "\\");
+ } else {
+ tplate[0] = 0;
+ }
+ strcat(tplate, tp);
+ return strdup(mktemp(tplate));
+}
+
+static int NEAR redopen(char *fn, int md, int sfd)
+{
+ int rc;
+ int fd;
+
+ if ((fd = open(fn, md, 0666)) != -1) {
+ if (md & O_APPEND)
+ lseek(fd, 0L, SEEK_END);
+ rc = dup(sfd);
+ if (fd != sfd) {
+ dup2(fd, sfd);
+ close(fd);
+ }
+ return rc;
+ }
+ return -1;
+}
+
+static int NEAR redclose(int fd, int sfd)
+{
+ if (fd != -1) {
+ dup2(fd, sfd);
+ close(fd);
+ }
+ return -1;
+}
+
+static void NEAR redswitch(PRO * p)
+{
+ int d;
+
+ for (d = 0; d < (int) TABLESIZE(p->ored); d++) {
+ if (d != p->ored[d]) {
+ p->sred[d] = dup(d);
+ dup2(p->ored[d], d);
+ }
+ }
+}
+
+static void NEAR redunswitch(PRO * p)
+{
+ int d;
+
+ for (d = 0; d < (int) TABLESIZE(p->ored); d++) {
+ if (d != p->ored[d]) {
+ dup2(p->sred[d], d);
+ close(p->sred[d]);
+ }
+ }
+}
+
+int xsystem(char *cmd)
+{
+ PRO *p, *pn;
+ char *pof, *pif, *pxf;
+ int psstdin, psstdout;
+ int rdstdin, rdstdout;
+ int rc = 0;
+
+#if USECMDLINE
+ static char *cmdline = 0;
+#endif
+
+#ifdef SH_EX /* 1997/11/01 (Sat) 10:04:03 add by JH7AYN */
+ pif = cmd;
+ while (*pif++) {
+ if (*pif == '\r') {
+ *pif = '\0';
+ break;
+ } else if (*pif == '\n') {
+ *pif = '\0';
+ break;
+ }
+ }
+#endif
+
+ pof = pif = pxf = 0;
+ p = pars(cmd);
+ pof = tmpf("p1XXXXXX");
+ pif = tmpf("p2XXXXXX");
+ psstdin = psstdout = rdstdin = rdstdout = -1;
+ while (p) {
+#if USECMDLINE
+ if (!LYGetEnv("NOCMDLINE")) {
+ cmdline = xmalloc(strlen(p->cmd) + strlen(p->arg) + 10);
+ sprintf(cmdline, "CMDLINE=%s %s", p->cmd, p->arg);
+ putenv(cmdline);
+ }
+#endif
+ if (p->next)
+ psstdout = redopen(pof, O_WRONLY | O_CREAT | O_TRUNC, 1);
+ if (p->inf)
+ rdstdin = redopen(p->inf, p->infmod, 0);
+ if (p->outf)
+ rdstdout = redopen(p->outf, p->outfmod, 1);
+ redswitch(p);
+ rc = prog_go(p, P_WAIT);
+ redunswitch(p);
+ rdstdin = redclose(rdstdin, 0);
+ rdstdout = redclose(rdstdout, 1);
+ psstdout = redclose(psstdout, 1);
+ psstdin = redclose(psstdin, 0);
+ if ((p = p->next) != 0) {
+ pxf = pif;
+ pif = pof;
+ pof = pxf;
+ psstdin = redopen(pif, O_RDONLY, 0);
+ }
+ }
+ unlink(pif);
+ free(pif);
+ unlink(pof);
+ free(pof);
+ for (pn = p = p1; p; p = pn) {
+ pn = p->next;
+ if (p->line)
+ free(p->line);
+ if (p->cmd)
+ free(p->cmd);
+ if (p->arg)
+ free(p->arg);
+ if (p->inf)
+ free(p->inf);
+ if (p->outf)
+ free(p->outf);
+ free(p);
+ }
+ if (rc == -2)
+ return 127;
+ return rc < 0 ? 0xFF00 : rc;
+}
+
+int exec_command(char *cmd, int wait_flag)
+{
+ int rc;
+
+ PRO *p;
+ char *pif;
+ int cmd_str;
+
+ pif = cmd;
+ while (*pif == ' ')
+ pif++;
+
+ cmd = pif;
+ cmd_str = TRUE;
+
+ while (*pif++) {
+ if (*pif == '\r') {
+ *pif = '\0';
+ break;
+ } else if (*pif == '\n') {
+ *pif = '\0';
+ break;
+ } else if (cmd_str) {
+ if (*pif == '/')
+ *pif = '\\';
+ } else if (cmd_str) {
+ if (*pif == ' ')
+ cmd_str = FALSE;
+ }
+ }
+ p = pars(cmd);
+
+ if (wait_flag)
+ rc = prog_go(p, P_WAIT);
+ else
+ rc = prog_go(p, P_NOWAIT);
+
+ return rc;
+}
+
+#ifdef TEST
+void main()
+{
+ char line_buff[STR_MAX];
+
+ while (gets(line_buff)) {
+ printf("\nreturn %04X\n", xsystem(line_buff));
+ }
+}
+#endif /* TEST */
diff --git a/src/chrtrans/README.format b/src/chrtrans/README.format
new file mode 100644
index 0000000..7437b50
--- /dev/null
+++ b/src/chrtrans/README.format
@@ -0,0 +1,138 @@
+Some notes on the format of table files used here.
+(See README.tables for what to do with them.)
+
+The format is derived from stuff in the console driver of the
+Linux kernel (as are the guts of the chartrans machinery).
+THAT DOES NOT MEAN that anything here is Linux specific - it isn't.
+
+[Note that the format may change, this is still somewhat experimental.]
+
+There are four kinds of lines:
+
+Summary example:
+
+ # This line is a comment, the next line is a directive
+ O Brand new Charset!
+ 0x41 U+0041 U+0391
+ U+00cd:I'
+
+Description:
+
+a) comment lines start with a '#' character.
+ (trailing comments are allowed on some of the other lines, if in doubt
+ check the examples..)
+
+b) directives:
+ start with a keyword which may be abbreviated to one letter (first
+ letter must be capitalized), followed by space and a value.
+ Currently recognized:
+
+ OptionName
+ The name under which this should appear on the O)ptions screen
+ in the list for Display Character Set
+ MIMEName
+ The name for this charset in MIME syntax (one word with digits
+ and some other non-letters allowed, should be IANA registered)
+ Default
+ If "Y[es]" or "1", this is the default (fallback) translation table,
+ it will be used for Unicode -> 8bit (or 7bit) translation if no
+ translation is found in the specific table.
+ FallBack
+ Whether to use the default table if no translation is found in
+ this table. Normally fallback is used, "FallBack NO" or "FallBack 0"
+ disables it (actually, other values than "FallBack Y[es]" or
+ "FallBack 1" disable it).
+
+ RawOrEnc
+ a number which flags some special property (encoding) for this
+ charset [see utf8_uni.tbl for example, see UCDefs.h for details].
+
+ Codepage number (IBM specific)
+ used by OS/2 font-switching code.
+
+c) character translation definitions:
+ they look like
+
+ 0x41 U+0041 U+0391 ...
+
+ and are used for "forward" translation (mapping this charset to Unicode)
+ AS WELL AS "back" translation (mapping Unicodes to an 8-bit
+ [incl. 7-bit ASCII] code).
+
+ For the "forward" direction, only the first Unicode is used; for
+ "back" translation, all listed Unicodes are mapped to the byte (i.e.
+ code point) on the left.
+
+ The above example line would tell the chartrans mechanism:
+ "For this charset, code position 65 [hex 0x41] contains Unicode
+ U+0041 (LATIN CAPITAL LETTER A). For translation of Unicodes to
+ this charset, use byte value 65 [hex 0x41] for U+0041 (LATIN CAPITAL
+ LETTER A) as well as for U+0391 (GREEK CAPITAL LETTER ALPHA)."
+
+ [Note that for bytes in the ASCII range 0x00-0x7F, the forward translations
+ will (probably) not be used by Lynx. It doesn't hurt to list those,
+ too, for completeness.]
+
+ Some other forms are also accepted:
+
+ * Syntax accepted:
+ * <fontpos> <unicode> <unicode> ...
+ * <fontpos> <unicode range> <unicode range> ...
+ * <fontpos> idem
+ * <range> idem
+ * <range> <unicode range>
+ *
+ * where <unicode range> ::= <unicode>-<unicode>
+ * and <unicode> ::= U+<h><h><h><h>
+ * and <h> ::= <hexadecimal digit>
+ *
+ [Note that <fontpos> _without_ targets assumed notdefined,
+ so tables from ftp.unicode.org need no patching.]
+
+
+d) string replacement definitions:
+
+ They look like
+
+ U+00cd:I'
+
+ which would mean "Replace Unicode U+00cd (LATIN CAPITAL LETTER I WITH
+ ACUTE" with the string (consisting of two character) I' (if no other
+ translation is available)." Please note that replacement definitions
+ in certain charset table will override ones from the Default table.
+
+ Note that everything after the ':' is currently taken VERBATIM, so
+ careful with trailing blanks etc. Please use <C replace> syntax below
+ when you need trailing spaces.
+
+ * Syntax accepted:
+ * <unicode> :<replace>
+ * <unicode range> :<replace>
+ * <unicode> "<C replace>"
+ * <unicode range> "<C replace>"
+ *
+ * where <unicode range> ::= <unicode>-<unicode>
+ * and <unicode> ::= U+<h><h><h><h>
+ * and <h> ::= <hexadecimal digit>
+ * and <replace> any string not containing '\n' or '\0', taken verbatim
+ * and <C replace> any string, with backslash having the usual C meaning.
+
+Motivation:
+
+- It is an extension of the format already in use for Linux (kernel,
+ kbd package), those files can be used with some minimal editing.
+
+- It is easy to convert Unicode tables for other charsets, as they
+ are commonly found on ftp sites etc., to this format - the right
+ sed command should do 99% of the work.
+
+- The format is independent of details of other parts of the Lynx code,
+ unlike the "old" LYCharsets.c mechanism. The tables don't have to
+ be changed in synch when e.g., new entities are added to the entities.h.
+
+
+Note: the Default "7bit approximation" table can be used for
+case-insensitive search for non-ascii letters if no upper/lower case
+information provided by other means, e.g., locale. It is assumed that
+upper/lower case letters have their "7bit approximation" images
+in def7_uni.tbl matched case-insensitively.
diff --git a/src/chrtrans/README.tables b/src/chrtrans/README.tables
new file mode 100644
index 0000000..14431da
--- /dev/null
+++ b/src/chrtrans/README.tables
@@ -0,0 +1,76 @@
+The translation table files in this directory were collected from
+several sources (among them ftp://ftp.unicode.org, Linux kbd package,
+ftp://dkuug.dk/) and are believed to be correct in their mappings,
+but not checked in detail. The Unicode/UCS2 values
+for some of the RFC 1345 Mnemonic codes are out of date,
+a cleanup and update would be needed for serious use.
+[See also http://czyborra.com/charsets/iso8859.html for codepages survey.]
+
+These changes were made to all of the files used from ftp.unicode.org:
+
+ a) add the MIME name of the charset.
+ b) add a name for the display charset (used on Options screen)
+ c) add the codepage number
+ d) remove lines for control characters 0x00 to 0x1f, 0x7f to 0x9f.
+ e) comment-out ASCII lines 0x20 to 0x7f
+ f) use idem to represent the commented-out lines
+ g) change C-style 0xNNNN constants to Unicode-style U+NNNN.
+
+Other changes include
+
+ h) add code-points to several lines to provide Unicode equivalents
+ i) add extra mappings at the end of the files
+ j) comment-out other one-one mappings in the 0xa0-0xff range.
+
+More translation files can be easily provided (and new character entities
+added to entities.h), this set is just to test whether the system works
+in principle (and also how it behaves with incomplete data...)
+
+See the file README.format for a brief explanation of what's in the
+table files.
+
+The examples have names *_uni or *_suni with a .tbl suffix, but it
+doesn't really matter. The auxiliary program makeuctb (MAKE UniCode
+TaBle) is used to "compile" them into C header files, which can be
+included by UCdomap.c.
+
+Ideally, this should be taken care of by the Makefiles. On VMS, use
+build-chrtrans.com to compile and link makeuctb.exe and create the
+set of .h files from the current set of .tbl files. Thereafter, use
+build-header.com to update particular .h files.
+
+To make a new chartrans table available to Lynx (and thereby make a new
+charset known to Lynx) you currently have to manually edit UCdomap.c, in
+two places:
+
+a) Near the top, you will find a bunch of lines (some may be commented out)
+
+ #include "<fn>.h"
+
+Add or comment out as you wish. But it is probably safest to leave the
+commonly used ones, referring to "def7_uni.h" and "iso01_uni.h", in place.
+
+b) At the bottom, you will find a bunch of lines (again, some may be
+ commented out by default) of the form
+
+ UC_CHARSET_SETUP_<something>;
+
+which should correspond to the #include lines from a). Again,
+add or subtract as you wish (but preferably consistent with what you
+did under a)...) [The <something> is derived from the charset's MIME name.
+if in doubt, check the last lines of the corresponding ...uni.h file.]
+
+c) To let make automatically notice when you have changed one of the
+ table files, and automatically regenerate the *uni.h file(s),
+you also have to add any new tables to both src/Makefile *and*
+src/chrtrans/Makefile. Or, for auto-config, the equivalent files
+named makefile.in before running ./configure, or makefile after running
+./configure. (That may be inconvenient, but I didn't want to depend
+on features than not all makes may have.) Note that for recompiling
+Lynx, a `make clean' should not be necessary if you have *only* made
+changes to the files in src/chrtrans. On VMS, add entries for new
+tables to build-chrtrans.com, but you can update the particular file
+with build-header.com, then use the top directory's build.com and
+answer 'n' to it's prompts about whether to update the WWWlibrary
+and chrtrans modules.
+
diff --git a/src/chrtrans/UCkd.h b/src/chrtrans/UCkd.h
new file mode 100644
index 0000000..f2de902
--- /dev/null
+++ b/src/chrtrans/UCkd.h
@@ -0,0 +1,54 @@
+#ifndef _UC_KD_H
+#define _UC_KD_H
+
+/*
+ * NOTE: THE FOLLOWING #define MAY NEED ADJUSTMENT.
+ * u16 should be an unsigned type of 16 bit length (two octets).
+ * u8 should be an unsigned type of 8 bit length (one octet).
+ */
+#ifndef u16
+#define u16 unsigned short
+#endif /* u16 */
+
+#ifndef u8
+#define u8 unsigned char
+#endif /* u8 */
+
+typedef char scrnmap_t;
+
+#define E_TABSZ 256
+
+struct unipair {
+ u16 unicode;
+ u16 fontpos;
+};
+struct unipair_str {
+ u16 unicode;
+ const char *replace_str;
+};
+struct unimapdesc {
+ u16 entry_ct;
+ struct unipair *entries;
+};
+struct unimapdesc_str {
+ u16 entry_ct;
+ struct unipair_str *entries;
+ int isdefault;
+ int trydefault;
+};
+
+#define UNI_DIRECT_BASE 0xF000 /* start of Direct Font Region */
+#define UNI_DIRECT_MASK 0x01FF /* Direct Font Region bitmask */
+
+#define UC_MAXLEN_ID_APPEND 20
+#define UC_MAXLEN_MIMECSNAME 40
+#define UC_MAXLEN_LYNXCSNAME 40
+#define UC_LEN_LYNXCSNAME 20
+
+#undef EX_OK /* may be defined in system headers */
+#define EX_OK 0 /* successful termination */
+#define EX_USAGE 64 /* command line usage error */
+#define EX_DATAERR 65 /* data format error */
+#define EX_NOINPUT 66 /* cannot open input */
+
+#endif /* _UC_KD_H */
diff --git a/src/chrtrans/build-chrtrans.com b/src/chrtrans/build-chrtrans.com
new file mode 100644
index 0000000..edbba85
--- /dev/null
+++ b/src/chrtrans/build-chrtrans.com
@@ -0,0 +1,142 @@
+$ v0 = 0
+$ v = f$verify(v0)
+$! BUILD-CHRTRANS.COM
+$!
+$! Command file to build MAKEUCTB.EXE on VMS systems
+$! and then use it to create the chrtrans header files.
+$!
+$! 28-Jun-1997 F.Macrides macrides@sci.wfeb.edu
+$! Initial version, for Lynx v2.7.1+fotemods
+$!
+$ ON CONTROL_Y THEN GOTO CLEANUP
+$ ON ERROR THEN GOTO CLEANUP
+$ CHRproc = f$environment("PROCEDURE")
+$ CHRwhere = f$parse(CHRproc,,,"DEVICE") + f$parse(CHRproc,,,"DIRECTORY")
+$!
+$ if p1 .nes. ""
+$ then
+$ CHRcc_opts = "/DEBUG/NOOPT"
+$ CHRlink_opts = "/DEBUG"
+$ else
+$ CHRcc_opts = ""
+$ CHRlink_opts = ""
+$ endif
+$!
+$ Compile_makeuctb:
+$!================
+$ v1 = f$verify(1)
+$!
+$! Compile the Lynx [.SRC.CHRTRANS]makeuctb module.
+$!
+$ v1 = f$verify(v0)
+$ IF f$getsyi("ARCH_NAME") .eqs. "Alpha" .or. -
+ f$getsyi("ARCH_NAME") .eqs. "IA64" .or. -
+ f$trnlnm("VAXCMSG") .eqs. "DECC$MSG" .or. -
+ f$trnlnm("DECC$CC_DEFAULT") .eqs. "/DECC" .or. -
+ f$trnlnm("DECC$CC_DEFAULT") .eqs. "/VAXC"
+$ THEN
+$ CHRcompiler := "DECC"
+$ v1 = f$verify(1)
+$! DECC:
+$ cc := cc/decc/prefix=all /nomember 'CHRcc_opts'-
+ /INCLUDE=([],[-],[--],[--.WWW.Library.Implementation])
+$ v1 = f$verify(v0)
+$ ELSE
+$ IF f$search("gnu_cc:[000000]gcclib.olb") .nes. ""
+$ THEN
+$ CHRcompiler := "GNUC"
+$ v1 = f$verify(1)
+$! GNUC:
+$ cc := gcc 'CHRcc_opts'/INCLUDE=([],[-],[--],[--.WWW.Library.Implementation])
+$ v1 = f$verify(v0)
+$ ELSE
+$ CHRcompiler := "VAXC"
+$ v1 = f$verify(1)
+$! VAXC:
+$ cc := cc 'CHRcc_opts'/INCLUDE=([],[-],[--],[--.WWW.Library.Implementation])
+$ v1 = f$verify(v0)
+$ ENDIF
+$ ENDIF
+$!
+$ v1 = f$verify(1)
+$ cc makeuctb
+$ v1 = f$verify(v0)
+$!
+$ Link_makeuctb:
+$!=============
+$ v1 = f$verify(1)
+$!
+$! Link the Lynx [.SRC.CHRTRANS]makeuctb module.
+$!
+$ IF f$getsyi("ARCH_NAME") .eqs. "IA64"
+$ THEN
+$ optslibs=""
+$ ELSE
+$ optslibs=", sys$disk:[-]''CHRcompiler'.opt/opt"
+$ ENDIF
+$
+$ link/exe=makeuctb.exe'CHRlink_opts' makeuctb 'optslibs
+$ v1 = f$verify(v0)
+$!
+$ Create_headers:
+$!==============
+$ v1 = f$verify(1)
+$!
+$! Create the Lynx [.SRC.CHRTRANS] header files.
+$!
+$ makeuctb := $'CHRwhere'makeuctb
+$ makeuctb cp1250_uni.tbl
+$ makeuctb cp1251_uni.tbl
+$ makeuctb cp1252_uni.tbl
+$ makeuctb cp1253_uni.tbl
+$ makeuctb cp1255_uni.tbl
+$ makeuctb cp1256_uni.tbl
+$ makeuctb cp1257_uni.tbl
+$ makeuctb cp437_uni.tbl
+$ makeuctb cp737_uni.tbl
+$ makeuctb cp775_uni.tbl
+$ makeuctb cp850_uni.tbl
+$ makeuctb cp852_uni.tbl
+$ makeuctb cp857_uni.tbl
+$ makeuctb cp862_uni.tbl
+$ makeuctb cp864_uni.tbl
+$ makeuctb cp866_uni.tbl
+$ makeuctb cp866u_uni.tbl
+$ makeuctb cp869_uni.tbl
+$ makeuctb def7_uni.tbl
+$ makeuctb dmcs_uni.tbl
+$ makeuctb hp_uni.tbl
+$ makeuctb iso01_uni.tbl
+$ makeuctb iso02_uni.tbl
+$ makeuctb iso03_uni.tbl
+$ makeuctb iso04_uni.tbl
+$ makeuctb iso05_uni.tbl
+$ makeuctb iso06_uni.tbl
+$ makeuctb iso07_uni.tbl
+$ makeuctb iso08_uni.tbl
+$ makeuctb iso09_uni.tbl
+$ makeuctb iso10_uni.tbl
+$ makeuctb iso13_uni.tbl
+$ makeuctb iso14_uni.tbl
+$ makeuctb iso15_uni.tbl
+$ makeuctb iso16_uni.tbl
+$ makeuctb koi8r_uni.tbl
+$ makeuctb koi8u_uni.tbl
+$ makeuctb mac_uni.tbl
+$ makeuctb mnem_suni.tbl
+$ makeuctb mnem2_suni.tbl
+$ makeuctb mnem_suni.tbl
+$ makeuctb next_uni.tbl
+$ makeuctb pt154_uni.tbl
+$ makeuctb rfc_suni.tbl
+$ makeuctb utf8_uni.tbl
+$ makeuctb viscii_uni.tbl
+$ v1 = f$verify(v0)
+$ exit
+$!
+$ CLEANUP:
+$ v1 = f$verify(0)
+$ write sys$output "Default directory:"
+$ show default
+$ v1 = f$verify(v)
+$ exit
diff --git a/src/chrtrans/build-header.com b/src/chrtrans/build-header.com
new file mode 100644
index 0000000..ff8a6f5
--- /dev/null
+++ b/src/chrtrans/build-header.com
@@ -0,0 +1,37 @@
+$ v0 = 0
+$ v = f$verify(v0)
+$! BUILD-HEADER.COM
+$!
+$! Command file to use MAKEUCTB.EXE on VMS systems for creating
+$! a chrtrans header (foo.h) file from a table (foo.tbl) file.
+$! Use the file root as P1, e.g.:
+$!
+$! $ @build-header iso05_uni
+$!
+$! will create iso05_uni.h from iso05_uni.tbl.
+$!
+$! 28-Jun-1997 F.Macrides macrides@sci.wfeb.edu
+$! Initial version, for Lynx v2.7.1+fotemods
+$!
+$ ON CONTROL_Y THEN GOTO CLEANUP
+$ ON ERROR THEN GOTO CLEANUP
+$ CHRproc = f$environment("PROCEDURE")
+$ CHRwhere = f$parse(CHRproc,,,"DEVICE") + f$parse(CHRproc,,,"DIRECTORY")
+$!
+$ Create_header:
+$!=============
+$ v1 = f$verify(1)
+$!
+$! Create a Lynx [.SRC.CHRTRANS] header file.
+$!
+$ makeuctb := $'CHRwhere'makeuctb
+$ makeuctb 'P1'.tbl
+$ v1 = f$verify(v0)
+$ exit
+$!
+$ CLEANUP:
+$ v1 = f$verify(v0)
+$ write sys$output "Default directory:"
+$ show default
+$ v1 = f$verify(v)
+$ exit
diff --git a/src/chrtrans/caselower.h b/src/chrtrans/caselower.h
new file mode 100644
index 0000000..5894a3f
--- /dev/null
+++ b/src/chrtrans/caselower.h
@@ -0,0 +1,738 @@
+/*
+ Lynx uses this info for 8bit case-insensitive user search.
+
+ This table is generated from Unicode Character Database, Version 2.1.5
+ available from ftp.unicode.org, and looks as natural way to get case mapping
+ equivalents for unicodes. (well, too much characters the cost of 3 Kb only).
+ Few words from the original README.txt quoted:
+
+UNICODE 2.1 CHARACTER DATABASE
+
+Copyright (c) 1991-1998 Unicode, Inc.
+All Rights reserved.
+
+CASE MAPPINGS
+
+The case mapping is an informative, default mapping. Certain languages, such
+as Turkish, German, French, or Greek may have small deviations from the
+default mappings listed in the Unicode Character Database.
+
+ */
+
+#include <UCkd.h> /* typedef u16 */
+
+typedef struct {
+ u16 upper;
+ u16 lower;
+} unipair_case;
+
+static const unipair_case unicode_to_lower_case[] =
+/* *INDENT-OFF* */
+{
+ {0x0041, 0x0061}, /* LATIN CAPITAL LETTER A */
+ {0x0042, 0x0062}, /* LATIN CAPITAL LETTER B */
+ {0x0043, 0x0063}, /* LATIN CAPITAL LETTER C */
+ {0x0044, 0x0064}, /* LATIN CAPITAL LETTER D */
+ {0x0045, 0x0065}, /* LATIN CAPITAL LETTER E */
+ {0x0046, 0x0066}, /* LATIN CAPITAL LETTER F */
+ {0x0047, 0x0067}, /* LATIN CAPITAL LETTER G */
+ {0x0048, 0x0068}, /* LATIN CAPITAL LETTER H */
+ {0x0049, 0x0069}, /* LATIN CAPITAL LETTER I */
+ {0x004A, 0x006A}, /* LATIN CAPITAL LETTER J */
+ {0x004B, 0x006B}, /* LATIN CAPITAL LETTER K */
+ {0x004C, 0x006C}, /* LATIN CAPITAL LETTER L */
+ {0x004D, 0x006D}, /* LATIN CAPITAL LETTER M */
+ {0x004E, 0x006E}, /* LATIN CAPITAL LETTER N */
+ {0x004F, 0x006F}, /* LATIN CAPITAL LETTER O */
+ {0x0050, 0x0070}, /* LATIN CAPITAL LETTER P */
+ {0x0051, 0x0071}, /* LATIN CAPITAL LETTER Q */
+ {0x0052, 0x0072}, /* LATIN CAPITAL LETTER R */
+ {0x0053, 0x0073}, /* LATIN CAPITAL LETTER S */
+ {0x0054, 0x0074}, /* LATIN CAPITAL LETTER T */
+ {0x0055, 0x0075}, /* LATIN CAPITAL LETTER U */
+ {0x0056, 0x0076}, /* LATIN CAPITAL LETTER V */
+ {0x0057, 0x0077}, /* LATIN CAPITAL LETTER W */
+ {0x0058, 0x0078}, /* LATIN CAPITAL LETTER X */
+ {0x0059, 0x0079}, /* LATIN CAPITAL LETTER Y */
+ {0x005A, 0x007A}, /* LATIN CAPITAL LETTER Z */
+ {0x00C0, 0x00E0}, /* LATIN CAPITAL LETTER A WITH GRAVE */
+ {0x00C1, 0x00E1}, /* LATIN CAPITAL LETTER A WITH ACUTE */
+ {0x00C2, 0x00E2}, /* LATIN CAPITAL LETTER A WITH CIRCUMFLEX */
+ {0x00C3, 0x00E3}, /* LATIN CAPITAL LETTER A WITH TILDE */
+ {0x00C4, 0x00E4}, /* LATIN CAPITAL LETTER A WITH DIAERESIS */
+ {0x00C5, 0x00E5}, /* LATIN CAPITAL LETTER A WITH RING ABOVE */
+ {0x00C6, 0x00E6}, /* LATIN CAPITAL LETTER AE */
+ {0x00C7, 0x00E7}, /* LATIN CAPITAL LETTER C WITH CEDILLA */
+ {0x00C8, 0x00E8}, /* LATIN CAPITAL LETTER E WITH GRAVE */
+ {0x00C9, 0x00E9}, /* LATIN CAPITAL LETTER E WITH ACUTE */
+ {0x00CA, 0x00EA}, /* LATIN CAPITAL LETTER E WITH CIRCUMFLEX */
+ {0x00CB, 0x00EB}, /* LATIN CAPITAL LETTER E WITH DIAERESIS */
+ {0x00CC, 0x00EC}, /* LATIN CAPITAL LETTER I WITH GRAVE */
+ {0x00CD, 0x00ED}, /* LATIN CAPITAL LETTER I WITH ACUTE */
+ {0x00CE, 0x00EE}, /* LATIN CAPITAL LETTER I WITH CIRCUMFLEX */
+ {0x00CF, 0x00EF}, /* LATIN CAPITAL LETTER I WITH DIAERESIS */
+ {0x00D0, 0x00F0}, /* LATIN CAPITAL LETTER ETH */
+ {0x00D1, 0x00F1}, /* LATIN CAPITAL LETTER N WITH TILDE */
+ {0x00D2, 0x00F2}, /* LATIN CAPITAL LETTER O WITH GRAVE */
+ {0x00D3, 0x00F3}, /* LATIN CAPITAL LETTER O WITH ACUTE */
+ {0x00D4, 0x00F4}, /* LATIN CAPITAL LETTER O WITH CIRCUMFLEX */
+ {0x00D5, 0x00F5}, /* LATIN CAPITAL LETTER O WITH TILDE */
+ {0x00D6, 0x00F6}, /* LATIN CAPITAL LETTER O WITH DIAERESIS */
+ {0x00D8, 0x00F8}, /* LATIN CAPITAL LETTER O WITH STROKE */
+ {0x00D9, 0x00F9}, /* LATIN CAPITAL LETTER U WITH GRAVE */
+ {0x00DA, 0x00FA}, /* LATIN CAPITAL LETTER U WITH ACUTE */
+ {0x00DB, 0x00FB}, /* LATIN CAPITAL LETTER U WITH CIRCUMFLEX */
+ {0x00DC, 0x00FC}, /* LATIN CAPITAL LETTER U WITH DIAERESIS */
+ {0x00DD, 0x00FD}, /* LATIN CAPITAL LETTER Y WITH ACUTE */
+ {0x00DE, 0x00FE}, /* LATIN CAPITAL LETTER THORN */
+ {0x0100, 0x0101}, /* LATIN CAPITAL LETTER A WITH MACRON */
+ {0x0102, 0x0103}, /* LATIN CAPITAL LETTER A WITH BREVE */
+ {0x0104, 0x0105}, /* LATIN CAPITAL LETTER A WITH OGONEK */
+ {0x0106, 0x0107}, /* LATIN CAPITAL LETTER C WITH ACUTE */
+ {0x0108, 0x0109}, /* LATIN CAPITAL LETTER C WITH CIRCUMFLEX */
+ {0x010A, 0x010B}, /* LATIN CAPITAL LETTER C WITH DOT ABOVE */
+ {0x010C, 0x010D}, /* LATIN CAPITAL LETTER C WITH CARON */
+ {0x010E, 0x010F}, /* LATIN CAPITAL LETTER D WITH CARON */
+ {0x0110, 0x0111}, /* LATIN CAPITAL LETTER D WITH STROKE */
+ {0x0112, 0x0113}, /* LATIN CAPITAL LETTER E WITH MACRON */
+ {0x0114, 0x0115}, /* LATIN CAPITAL LETTER E WITH BREVE */
+ {0x0116, 0x0117}, /* LATIN CAPITAL LETTER E WITH DOT ABOVE */
+ {0x0118, 0x0119}, /* LATIN CAPITAL LETTER E WITH OGONEK */
+ {0x011A, 0x011B}, /* LATIN CAPITAL LETTER E WITH CARON */
+ {0x011C, 0x011D}, /* LATIN CAPITAL LETTER G WITH CIRCUMFLEX */
+ {0x011E, 0x011F}, /* LATIN CAPITAL LETTER G WITH BREVE */
+ {0x0120, 0x0121}, /* LATIN CAPITAL LETTER G WITH DOT ABOVE */
+ {0x0122, 0x0123}, /* LATIN CAPITAL LETTER G WITH CEDILLA */
+ {0x0124, 0x0125}, /* LATIN CAPITAL LETTER H WITH CIRCUMFLEX */
+ {0x0126, 0x0127}, /* LATIN CAPITAL LETTER H WITH STROKE */
+ {0x0128, 0x0129}, /* LATIN CAPITAL LETTER I WITH TILDE */
+ {0x012A, 0x012B}, /* LATIN CAPITAL LETTER I WITH MACRON */
+ {0x012C, 0x012D}, /* LATIN CAPITAL LETTER I WITH BREVE */
+ {0x012E, 0x012F}, /* LATIN CAPITAL LETTER I WITH OGONEK */
+ {0x0130, 0x0069}, /* LATIN CAPITAL LETTER I WITH DOT ABOVE */
+ {0x0132, 0x0133}, /* LATIN CAPITAL LIGATURE IJ */
+ {0x0134, 0x0135}, /* LATIN CAPITAL LETTER J WITH CIRCUMFLEX */
+ {0x0136, 0x0137}, /* LATIN CAPITAL LETTER K WITH CEDILLA */
+ {0x0139, 0x013A}, /* LATIN CAPITAL LETTER L WITH ACUTE */
+ {0x013B, 0x013C}, /* LATIN CAPITAL LETTER L WITH CEDILLA */
+ {0x013D, 0x013E}, /* LATIN CAPITAL LETTER L WITH CARON */
+ {0x013F, 0x0140}, /* LATIN CAPITAL LETTER L WITH MIDDLE DOT */
+ {0x0141, 0x0142}, /* LATIN CAPITAL LETTER L WITH STROKE */
+ {0x0143, 0x0144}, /* LATIN CAPITAL LETTER N WITH ACUTE */
+ {0x0145, 0x0146}, /* LATIN CAPITAL LETTER N WITH CEDILLA */
+ {0x0147, 0x0148}, /* LATIN CAPITAL LETTER N WITH CARON */
+ {0x014A, 0x014B}, /* LATIN CAPITAL LETTER ENG */
+ {0x014C, 0x014D}, /* LATIN CAPITAL LETTER O WITH MACRON */
+ {0x014E, 0x014F}, /* LATIN CAPITAL LETTER O WITH BREVE */
+ {0x0150, 0x0151}, /* LATIN CAPITAL LETTER O WITH DOUBLE ACUTE */
+ {0x0152, 0x0153}, /* LATIN CAPITAL LIGATURE OE */
+ {0x0154, 0x0155}, /* LATIN CAPITAL LETTER R WITH ACUTE */
+ {0x0156, 0x0157}, /* LATIN CAPITAL LETTER R WITH CEDILLA */
+ {0x0158, 0x0159}, /* LATIN CAPITAL LETTER R WITH CARON */
+ {0x015A, 0x015B}, /* LATIN CAPITAL LETTER S WITH ACUTE */
+ {0x015C, 0x015D}, /* LATIN CAPITAL LETTER S WITH CIRCUMFLEX */
+ {0x015E, 0x015F}, /* LATIN CAPITAL LETTER S WITH CEDILLA */
+ {0x0160, 0x0161}, /* LATIN CAPITAL LETTER S WITH CARON */
+ {0x0162, 0x0163}, /* LATIN CAPITAL LETTER T WITH CEDILLA */
+ {0x0164, 0x0165}, /* LATIN CAPITAL LETTER T WITH CARON */
+ {0x0166, 0x0167}, /* LATIN CAPITAL LETTER T WITH STROKE */
+ {0x0168, 0x0169}, /* LATIN CAPITAL LETTER U WITH TILDE */
+ {0x016A, 0x016B}, /* LATIN CAPITAL LETTER U WITH MACRON */
+ {0x016C, 0x016D}, /* LATIN CAPITAL LETTER U WITH BREVE */
+ {0x016E, 0x016F}, /* LATIN CAPITAL LETTER U WITH RING ABOVE */
+ {0x0170, 0x0171}, /* LATIN CAPITAL LETTER U WITH DOUBLE ACUTE */
+ {0x0172, 0x0173}, /* LATIN CAPITAL LETTER U WITH OGONEK */
+ {0x0174, 0x0175}, /* LATIN CAPITAL LETTER W WITH CIRCUMFLEX */
+ {0x0176, 0x0177}, /* LATIN CAPITAL LETTER Y WITH CIRCUMFLEX */
+ {0x0178, 0x00FF}, /* LATIN CAPITAL LETTER Y WITH DIAERESIS */
+ {0x0179, 0x017A}, /* LATIN CAPITAL LETTER Z WITH ACUTE */
+ {0x017B, 0x017C}, /* LATIN CAPITAL LETTER Z WITH DOT ABOVE */
+ {0x017D, 0x017E}, /* LATIN CAPITAL LETTER Z WITH CARON */
+ {0x0181, 0x0253}, /* LATIN CAPITAL LETTER B WITH HOOK */
+ {0x0182, 0x0183}, /* LATIN CAPITAL LETTER B WITH TOPBAR */
+ {0x0184, 0x0185}, /* LATIN CAPITAL LETTER TONE SIX */
+ {0x0186, 0x0254}, /* LATIN CAPITAL LETTER OPEN O */
+ {0x0187, 0x0188}, /* LATIN CAPITAL LETTER C WITH HOOK */
+ {0x0189, 0x0256}, /* LATIN CAPITAL LETTER AFRICAN D */
+ {0x018A, 0x0257}, /* LATIN CAPITAL LETTER D WITH HOOK */
+ {0x018B, 0x018C}, /* LATIN CAPITAL LETTER D WITH TOPBAR */
+ {0x018E, 0x01DD}, /* LATIN CAPITAL LETTER REVERSED E */
+ {0x018F, 0x0259}, /* LATIN CAPITAL LETTER SCHWA */
+ {0x0190, 0x025B}, /* LATIN CAPITAL LETTER OPEN E */
+ {0x0191, 0x0192}, /* LATIN CAPITAL LETTER F WITH HOOK */
+ {0x0193, 0x0260}, /* LATIN CAPITAL LETTER G WITH HOOK */
+ {0x0194, 0x0263}, /* LATIN CAPITAL LETTER GAMMA */
+ {0x0196, 0x0269}, /* LATIN CAPITAL LETTER IOTA */
+ {0x0197, 0x0268}, /* LATIN CAPITAL LETTER I WITH STROKE */
+ {0x0198, 0x0199}, /* LATIN CAPITAL LETTER K WITH HOOK */
+ {0x019C, 0x026F}, /* LATIN CAPITAL LETTER TURNED M */
+ {0x019D, 0x0272}, /* LATIN CAPITAL LETTER N WITH LEFT HOOK */
+ {0x019F, 0x0275}, /* LATIN CAPITAL LETTER O WITH MIDDLE TILDE */
+ {0x01A0, 0x01A1}, /* LATIN CAPITAL LETTER O WITH HORN */
+ {0x01A2, 0x01A3}, /* LATIN CAPITAL LETTER OI */
+ {0x01A4, 0x01A5}, /* LATIN CAPITAL LETTER P WITH HOOK */
+ {0x01A7, 0x01A8}, /* LATIN CAPITAL LETTER TONE TWO */
+ {0x01A9, 0x0283}, /* LATIN CAPITAL LETTER ESH */
+ {0x01AC, 0x01AD}, /* LATIN CAPITAL LETTER T WITH HOOK */
+ {0x01AE, 0x0288}, /* LATIN CAPITAL LETTER T WITH RETROFLEX HOOK */
+ {0x01AF, 0x01B0}, /* LATIN CAPITAL LETTER U WITH HORN */
+ {0x01B1, 0x028A}, /* LATIN CAPITAL LETTER UPSILON */
+ {0x01B2, 0x028B}, /* LATIN CAPITAL LETTER V WITH HOOK */
+ {0x01B3, 0x01B4}, /* LATIN CAPITAL LETTER Y WITH HOOK */
+ {0x01B5, 0x01B6}, /* LATIN CAPITAL LETTER Z WITH STROKE */
+ {0x01B7, 0x0292}, /* LATIN CAPITAL LETTER EZH */
+ {0x01B8, 0x01B9}, /* LATIN CAPITAL LETTER EZH REVERSED */
+ {0x01BC, 0x01BD}, /* LATIN CAPITAL LETTER TONE FIVE */
+ {0x01C4, 0x01C6}, /* LATIN CAPITAL LETTER DZ WITH CARON */
+ {0x01C5, 0x01C6}, /* LATIN CAPITAL LETTER D WITH SMALL LETTER Z WITH CARON */
+ {0x01C7, 0x01C9}, /* LATIN CAPITAL LETTER LJ */
+ {0x01C8, 0x01C9}, /* LATIN CAPITAL LETTER L WITH SMALL LETTER J */
+ {0x01CA, 0x01CC}, /* LATIN CAPITAL LETTER NJ */
+ {0x01CB, 0x01CC}, /* LATIN CAPITAL LETTER N WITH SMALL LETTER J */
+ {0x01CD, 0x01CE}, /* LATIN CAPITAL LETTER A WITH CARON */
+ {0x01CF, 0x01D0}, /* LATIN CAPITAL LETTER I WITH CARON */
+ {0x01D1, 0x01D2}, /* LATIN CAPITAL LETTER O WITH CARON */
+ {0x01D3, 0x01D4}, /* LATIN CAPITAL LETTER U WITH CARON */
+ {0x01D5, 0x01D6}, /* LATIN CAPITAL LETTER U WITH DIAERESIS AND MACRON */
+ {0x01D7, 0x01D8}, /* LATIN CAPITAL LETTER U WITH DIAERESIS AND ACUTE */
+ {0x01D9, 0x01DA}, /* LATIN CAPITAL LETTER U WITH DIAERESIS AND CARON */
+ {0x01DB, 0x01DC}, /* LATIN CAPITAL LETTER U WITH DIAERESIS AND GRAVE */
+ {0x01DE, 0x01DF}, /* LATIN CAPITAL LETTER A WITH DIAERESIS AND MACRON */
+ {0x01E0, 0x01E1}, /* LATIN CAPITAL LETTER A WITH DOT ABOVE AND MACRON */
+ {0x01E2, 0x01E3}, /* LATIN CAPITAL LETTER AE WITH MACRON */
+ {0x01E4, 0x01E5}, /* LATIN CAPITAL LETTER G WITH STROKE */
+ {0x01E6, 0x01E7}, /* LATIN CAPITAL LETTER G WITH CARON */
+ {0x01E8, 0x01E9}, /* LATIN CAPITAL LETTER K WITH CARON */
+ {0x01EA, 0x01EB}, /* LATIN CAPITAL LETTER O WITH OGONEK */
+ {0x01EC, 0x01ED}, /* LATIN CAPITAL LETTER O WITH OGONEK AND MACRON */
+ {0x01EE, 0x01EF}, /* LATIN CAPITAL LETTER EZH WITH CARON */
+ {0x01F1, 0x01F3}, /* LATIN CAPITAL LETTER DZ */
+ {0x01F2, 0x01F3}, /* LATIN CAPITAL LETTER D WITH SMALL LETTER Z */
+ {0x01F4, 0x01F5}, /* LATIN CAPITAL LETTER G WITH ACUTE */
+ {0x01FA, 0x01FB}, /* LATIN CAPITAL LETTER A WITH RING ABOVE AND ACUTE */
+ {0x01FC, 0x01FD}, /* LATIN CAPITAL LETTER AE WITH ACUTE */
+ {0x01FE, 0x01FF}, /* LATIN CAPITAL LETTER O WITH STROKE AND ACUTE */
+ {0x0200, 0x0201}, /* LATIN CAPITAL LETTER A WITH DOUBLE GRAVE */
+ {0x0202, 0x0203}, /* LATIN CAPITAL LETTER A WITH INVERTED BREVE */
+ {0x0204, 0x0205}, /* LATIN CAPITAL LETTER E WITH DOUBLE GRAVE */
+ {0x0206, 0x0207}, /* LATIN CAPITAL LETTER E WITH INVERTED BREVE */
+ {0x0208, 0x0209}, /* LATIN CAPITAL LETTER I WITH DOUBLE GRAVE */
+ {0x020A, 0x020B}, /* LATIN CAPITAL LETTER I WITH INVERTED BREVE */
+ {0x020C, 0x020D}, /* LATIN CAPITAL LETTER O WITH DOUBLE GRAVE */
+ {0x020E, 0x020F}, /* LATIN CAPITAL LETTER O WITH INVERTED BREVE */
+ {0x0210, 0x0211}, /* LATIN CAPITAL LETTER R WITH DOUBLE GRAVE */
+ {0x0212, 0x0213}, /* LATIN CAPITAL LETTER R WITH INVERTED BREVE */
+ {0x0214, 0x0215}, /* LATIN CAPITAL LETTER U WITH DOUBLE GRAVE */
+ {0x0216, 0x0217}, /* LATIN CAPITAL LETTER U WITH INVERTED BREVE */
+ {0x0386, 0x03AC}, /* GREEK CAPITAL LETTER ALPHA WITH TONOS */
+ {0x0388, 0x03AD}, /* GREEK CAPITAL LETTER EPSILON WITH TONOS */
+ {0x0389, 0x03AE}, /* GREEK CAPITAL LETTER ETA WITH TONOS */
+ {0x038A, 0x03AF}, /* GREEK CAPITAL LETTER IOTA WITH TONOS */
+ {0x038C, 0x03CC}, /* GREEK CAPITAL LETTER OMICRON WITH TONOS */
+ {0x038E, 0x03CD}, /* GREEK CAPITAL LETTER UPSILON WITH TONOS */
+ {0x038F, 0x03CE}, /* GREEK CAPITAL LETTER OMEGA WITH TONOS */
+ {0x0391, 0x03B1}, /* GREEK CAPITAL LETTER ALPHA */
+ {0x0392, 0x03B2}, /* GREEK CAPITAL LETTER BETA */
+ {0x0393, 0x03B3}, /* GREEK CAPITAL LETTER GAMMA */
+ {0x0394, 0x03B4}, /* GREEK CAPITAL LETTER DELTA */
+ {0x0395, 0x03B5}, /* GREEK CAPITAL LETTER EPSILON */
+ {0x0396, 0x03B6}, /* GREEK CAPITAL LETTER ZETA */
+ {0x0397, 0x03B7}, /* GREEK CAPITAL LETTER ETA */
+ {0x0398, 0x03B8}, /* GREEK CAPITAL LETTER THETA */
+ {0x0399, 0x03B9}, /* GREEK CAPITAL LETTER IOTA */
+ {0x039A, 0x03BA}, /* GREEK CAPITAL LETTER KAPPA */
+ {0x039B, 0x03BB}, /* GREEK CAPITAL LETTER LAMDA */
+ {0x039C, 0x03BC}, /* GREEK CAPITAL LETTER MU */
+ {0x039D, 0x03BD}, /* GREEK CAPITAL LETTER NU */
+ {0x039E, 0x03BE}, /* GREEK CAPITAL LETTER XI */
+ {0x039F, 0x03BF}, /* GREEK CAPITAL LETTER OMICRON */
+ {0x03A0, 0x03C0}, /* GREEK CAPITAL LETTER PI */
+ {0x03A1, 0x03C1}, /* GREEK CAPITAL LETTER RHO */
+ {0x03A3, 0x03C3}, /* GREEK CAPITAL LETTER SIGMA */
+ {0x03A4, 0x03C4}, /* GREEK CAPITAL LETTER TAU */
+ {0x03A5, 0x03C5}, /* GREEK CAPITAL LETTER UPSILON */
+ {0x03A6, 0x03C6}, /* GREEK CAPITAL LETTER PHI */
+ {0x03A7, 0x03C7}, /* GREEK CAPITAL LETTER CHI */
+ {0x03A8, 0x03C8}, /* GREEK CAPITAL LETTER PSI */
+ {0x03A9, 0x03C9}, /* GREEK CAPITAL LETTER OMEGA */
+ {0x03AA, 0x03CA}, /* GREEK CAPITAL LETTER IOTA WITH DIALYTIKA */
+ {0x03AB, 0x03CB}, /* GREEK CAPITAL LETTER UPSILON WITH DIALYTIKA */
+ {0x03E2, 0x03E3}, /* COPTIC CAPITAL LETTER SHEI */
+ {0x03E4, 0x03E5}, /* COPTIC CAPITAL LETTER FEI */
+ {0x03E6, 0x03E7}, /* COPTIC CAPITAL LETTER KHEI */
+ {0x03E8, 0x03E9}, /* COPTIC CAPITAL LETTER HORI */
+ {0x03EA, 0x03EB}, /* COPTIC CAPITAL LETTER GANGIA */
+ {0x03EC, 0x03ED}, /* COPTIC CAPITAL LETTER SHIMA */
+ {0x03EE, 0x03EF}, /* COPTIC CAPITAL LETTER DEI */
+ {0x0401, 0x0451}, /* CYRILLIC CAPITAL LETTER IO */
+ {0x0402, 0x0452}, /* CYRILLIC CAPITAL LETTER DJE */
+ {0x0403, 0x0453}, /* CYRILLIC CAPITAL LETTER GJE */
+ {0x0404, 0x0454}, /* CYRILLIC CAPITAL LETTER UKRAINIAN IE */
+ {0x0405, 0x0455}, /* CYRILLIC CAPITAL LETTER DZE */
+ {0x0406, 0x0456}, /* CYRILLIC CAPITAL LETTER BYELORUSSIAN-UKRAINIAN I */
+ {0x0407, 0x0457}, /* CYRILLIC CAPITAL LETTER YI */
+ {0x0408, 0x0458}, /* CYRILLIC CAPITAL LETTER JE */
+ {0x0409, 0x0459}, /* CYRILLIC CAPITAL LETTER LJE */
+ {0x040A, 0x045A}, /* CYRILLIC CAPITAL LETTER NJE */
+ {0x040B, 0x045B}, /* CYRILLIC CAPITAL LETTER TSHE */
+ {0x040C, 0x045C}, /* CYRILLIC CAPITAL LETTER KJE */
+ {0x040E, 0x045E}, /* CYRILLIC CAPITAL LETTER SHORT U */
+ {0x040F, 0x045F}, /* CYRILLIC CAPITAL LETTER DZHE */
+ {0x0410, 0x0430}, /* CYRILLIC CAPITAL LETTER A */
+ {0x0411, 0x0431}, /* CYRILLIC CAPITAL LETTER BE */
+ {0x0412, 0x0432}, /* CYRILLIC CAPITAL LETTER VE */
+ {0x0413, 0x0433}, /* CYRILLIC CAPITAL LETTER GHE */
+ {0x0414, 0x0434}, /* CYRILLIC CAPITAL LETTER DE */
+ {0x0415, 0x0435}, /* CYRILLIC CAPITAL LETTER IE */
+ {0x0416, 0x0436}, /* CYRILLIC CAPITAL LETTER ZHE */
+ {0x0417, 0x0437}, /* CYRILLIC CAPITAL LETTER ZE */
+ {0x0418, 0x0438}, /* CYRILLIC CAPITAL LETTER I */
+ {0x0419, 0x0439}, /* CYRILLIC CAPITAL LETTER SHORT I */
+ {0x041A, 0x043A}, /* CYRILLIC CAPITAL LETTER KA */
+ {0x041B, 0x043B}, /* CYRILLIC CAPITAL LETTER EL */
+ {0x041C, 0x043C}, /* CYRILLIC CAPITAL LETTER EM */
+ {0x041D, 0x043D}, /* CYRILLIC CAPITAL LETTER EN */
+ {0x041E, 0x043E}, /* CYRILLIC CAPITAL LETTER O */
+ {0x041F, 0x043F}, /* CYRILLIC CAPITAL LETTER PE */
+ {0x0420, 0x0440}, /* CYRILLIC CAPITAL LETTER ER */
+ {0x0421, 0x0441}, /* CYRILLIC CAPITAL LETTER ES */
+ {0x0422, 0x0442}, /* CYRILLIC CAPITAL LETTER TE */
+ {0x0423, 0x0443}, /* CYRILLIC CAPITAL LETTER U */
+ {0x0424, 0x0444}, /* CYRILLIC CAPITAL LETTER EF */
+ {0x0425, 0x0445}, /* CYRILLIC CAPITAL LETTER HA */
+ {0x0426, 0x0446}, /* CYRILLIC CAPITAL LETTER TSE */
+ {0x0427, 0x0447}, /* CYRILLIC CAPITAL LETTER CHE */
+ {0x0428, 0x0448}, /* CYRILLIC CAPITAL LETTER SHA */
+ {0x0429, 0x0449}, /* CYRILLIC CAPITAL LETTER SHCHA */
+ {0x042A, 0x044A}, /* CYRILLIC CAPITAL LETTER HARD SIGN */
+ {0x042B, 0x044B}, /* CYRILLIC CAPITAL LETTER YERU */
+ {0x042C, 0x044C}, /* CYRILLIC CAPITAL LETTER SOFT SIGN */
+ {0x042D, 0x044D}, /* CYRILLIC CAPITAL LETTER E */
+ {0x042E, 0x044E}, /* CYRILLIC CAPITAL LETTER YU */
+ {0x042F, 0x044F}, /* CYRILLIC CAPITAL LETTER YA */
+ {0x0460, 0x0461}, /* CYRILLIC CAPITAL LETTER OMEGA */
+ {0x0462, 0x0463}, /* CYRILLIC CAPITAL LETTER YAT */
+ {0x0464, 0x0465}, /* CYRILLIC CAPITAL LETTER IOTIFIED E */
+ {0x0466, 0x0467}, /* CYRILLIC CAPITAL LETTER LITTLE YUS */
+ {0x0468, 0x0469}, /* CYRILLIC CAPITAL LETTER IOTIFIED LITTLE YUS */
+ {0x046A, 0x046B}, /* CYRILLIC CAPITAL LETTER BIG YUS */
+ {0x046C, 0x046D}, /* CYRILLIC CAPITAL LETTER IOTIFIED BIG YUS */
+ {0x046E, 0x046F}, /* CYRILLIC CAPITAL LETTER KSI */
+ {0x0470, 0x0471}, /* CYRILLIC CAPITAL LETTER PSI */
+ {0x0472, 0x0473}, /* CYRILLIC CAPITAL LETTER FITA */
+ {0x0474, 0x0475}, /* CYRILLIC CAPITAL LETTER IZHITSA */
+ {0x0476, 0x0477}, /* CYRILLIC CAPITAL LETTER IZHITSA WITH DOUBLE GRAVE ACCENT */
+ {0x0478, 0x0479}, /* CYRILLIC CAPITAL LETTER UK */
+ {0x047A, 0x047B}, /* CYRILLIC CAPITAL LETTER ROUND OMEGA */
+ {0x047C, 0x047D}, /* CYRILLIC CAPITAL LETTER OMEGA WITH TITLO */
+ {0x047E, 0x047F}, /* CYRILLIC CAPITAL LETTER OT */
+ {0x0480, 0x0481}, /* CYRILLIC CAPITAL LETTER KOPPA */
+ {0x0490, 0x0491}, /* CYRILLIC CAPITAL LETTER GHE WITH UPTURN */
+ {0x0492, 0x0493}, /* CYRILLIC CAPITAL LETTER GHE WITH STROKE */
+ {0x0494, 0x0495}, /* CYRILLIC CAPITAL LETTER GHE WITH MIDDLE HOOK */
+ {0x0496, 0x0497}, /* CYRILLIC CAPITAL LETTER ZHE WITH DESCENDER */
+ {0x0498, 0x0499}, /* CYRILLIC CAPITAL LETTER ZE WITH DESCENDER */
+ {0x049A, 0x049B}, /* CYRILLIC CAPITAL LETTER KA WITH DESCENDER */
+ {0x049C, 0x049D}, /* CYRILLIC CAPITAL LETTER KA WITH VERTICAL STROKE */
+ {0x049E, 0x049F}, /* CYRILLIC CAPITAL LETTER KA WITH STROKE */
+ {0x04A0, 0x04A1}, /* CYRILLIC CAPITAL LETTER BASHKIR KA */
+ {0x04A2, 0x04A3}, /* CYRILLIC CAPITAL LETTER EN WITH DESCENDER */
+ {0x04A4, 0x04A5}, /* CYRILLIC CAPITAL LIGATURE EN GHE */
+ {0x04A6, 0x04A7}, /* CYRILLIC CAPITAL LETTER PE WITH MIDDLE HOOK */
+ {0x04A8, 0x04A9}, /* CYRILLIC CAPITAL LETTER ABKHASIAN HA */
+ {0x04AA, 0x04AB}, /* CYRILLIC CAPITAL LETTER ES WITH DESCENDER */
+ {0x04AC, 0x04AD}, /* CYRILLIC CAPITAL LETTER TE WITH DESCENDER */
+ {0x04AE, 0x04AF}, /* CYRILLIC CAPITAL LETTER STRAIGHT U */
+ {0x04B0, 0x04B1}, /* CYRILLIC CAPITAL LETTER STRAIGHT U WITH STROKE */
+ {0x04B2, 0x04B3}, /* CYRILLIC CAPITAL LETTER HA WITH DESCENDER */
+ {0x04B4, 0x04B5}, /* CYRILLIC CAPITAL LIGATURE TE TSE */
+ {0x04B6, 0x04B7}, /* CYRILLIC CAPITAL LETTER CHE WITH DESCENDER */
+ {0x04B8, 0x04B9}, /* CYRILLIC CAPITAL LETTER CHE WITH VERTICAL STROKE */
+ {0x04BA, 0x04BB}, /* CYRILLIC CAPITAL LETTER SHHA */
+ {0x04BC, 0x04BD}, /* CYRILLIC CAPITAL LETTER ABKHASIAN CHE */
+ {0x04BE, 0x04BF}, /* CYRILLIC CAPITAL LETTER ABKHASIAN CHE WITH DESCENDER */
+ {0x04C1, 0x04C2}, /* CYRILLIC CAPITAL LETTER ZHE WITH BREVE */
+ {0x04C3, 0x04C4}, /* CYRILLIC CAPITAL LETTER KA WITH HOOK */
+ {0x04C7, 0x04C8}, /* CYRILLIC CAPITAL LETTER EN WITH HOOK */
+ {0x04CB, 0x04CC}, /* CYRILLIC CAPITAL LETTER KHAKASSIAN CHE */
+ {0x04D0, 0x04D1}, /* CYRILLIC CAPITAL LETTER A WITH BREVE */
+ {0x04D2, 0x04D3}, /* CYRILLIC CAPITAL LETTER A WITH DIAERESIS */
+ {0x04D4, 0x04D5}, /* CYRILLIC CAPITAL LIGATURE A IE */
+ {0x04D6, 0x04D7}, /* CYRILLIC CAPITAL LETTER IE WITH BREVE */
+ {0x04D8, 0x04D9}, /* CYRILLIC CAPITAL LETTER SCHWA */
+ {0x04DA, 0x04DB}, /* CYRILLIC CAPITAL LETTER SCHWA WITH DIAERESIS */
+ {0x04DC, 0x04DD}, /* CYRILLIC CAPITAL LETTER ZHE WITH DIAERESIS */
+ {0x04DE, 0x04DF}, /* CYRILLIC CAPITAL LETTER ZE WITH DIAERESIS */
+ {0x04E0, 0x04E1}, /* CYRILLIC CAPITAL LETTER ABKHASIAN DZE */
+ {0x04E2, 0x04E3}, /* CYRILLIC CAPITAL LETTER I WITH MACRON */
+ {0x04E4, 0x04E5}, /* CYRILLIC CAPITAL LETTER I WITH DIAERESIS */
+ {0x04E6, 0x04E7}, /* CYRILLIC CAPITAL LETTER O WITH DIAERESIS */
+ {0x04E8, 0x04E9}, /* CYRILLIC CAPITAL LETTER BARRED O */
+ {0x04EA, 0x04EB}, /* CYRILLIC CAPITAL LETTER BARRED O WITH DIAERESIS */
+ {0x04EE, 0x04EF}, /* CYRILLIC CAPITAL LETTER U WITH MACRON */
+ {0x04F0, 0x04F1}, /* CYRILLIC CAPITAL LETTER U WITH DIAERESIS */
+ {0x04F2, 0x04F3}, /* CYRILLIC CAPITAL LETTER U WITH DOUBLE ACUTE */
+ {0x04F4, 0x04F5}, /* CYRILLIC CAPITAL LETTER CHE WITH DIAERESIS */
+ {0x04F8, 0x04F9}, /* CYRILLIC CAPITAL LETTER YERU WITH DIAERESIS */
+ {0x0531, 0x0561}, /* ARMENIAN CAPITAL LETTER AYB */
+ {0x0532, 0x0562}, /* ARMENIAN CAPITAL LETTER BEN */
+ {0x0533, 0x0563}, /* ARMENIAN CAPITAL LETTER GIM */
+ {0x0534, 0x0564}, /* ARMENIAN CAPITAL LETTER DA */
+ {0x0535, 0x0565}, /* ARMENIAN CAPITAL LETTER ECH */
+ {0x0536, 0x0566}, /* ARMENIAN CAPITAL LETTER ZA */
+ {0x0537, 0x0567}, /* ARMENIAN CAPITAL LETTER EH */
+ {0x0538, 0x0568}, /* ARMENIAN CAPITAL LETTER ET */
+ {0x0539, 0x0569}, /* ARMENIAN CAPITAL LETTER TO */
+ {0x053A, 0x056A}, /* ARMENIAN CAPITAL LETTER ZHE */
+ {0x053B, 0x056B}, /* ARMENIAN CAPITAL LETTER INI */
+ {0x053C, 0x056C}, /* ARMENIAN CAPITAL LETTER LIWN */
+ {0x053D, 0x056D}, /* ARMENIAN CAPITAL LETTER XEH */
+ {0x053E, 0x056E}, /* ARMENIAN CAPITAL LETTER CA */
+ {0x053F, 0x056F}, /* ARMENIAN CAPITAL LETTER KEN */
+ {0x0540, 0x0570}, /* ARMENIAN CAPITAL LETTER HO */
+ {0x0541, 0x0571}, /* ARMENIAN CAPITAL LETTER JA */
+ {0x0542, 0x0572}, /* ARMENIAN CAPITAL LETTER GHAD */
+ {0x0543, 0x0573}, /* ARMENIAN CAPITAL LETTER CHEH */
+ {0x0544, 0x0574}, /* ARMENIAN CAPITAL LETTER MEN */
+ {0x0545, 0x0575}, /* ARMENIAN CAPITAL LETTER YI */
+ {0x0546, 0x0576}, /* ARMENIAN CAPITAL LETTER NOW */
+ {0x0547, 0x0577}, /* ARMENIAN CAPITAL LETTER SHA */
+ {0x0548, 0x0578}, /* ARMENIAN CAPITAL LETTER VO */
+ {0x0549, 0x0579}, /* ARMENIAN CAPITAL LETTER CHA */
+ {0x054A, 0x057A}, /* ARMENIAN CAPITAL LETTER PEH */
+ {0x054B, 0x057B}, /* ARMENIAN CAPITAL LETTER JHEH */
+ {0x054C, 0x057C}, /* ARMENIAN CAPITAL LETTER RA */
+ {0x054D, 0x057D}, /* ARMENIAN CAPITAL LETTER SEH */
+ {0x054E, 0x057E}, /* ARMENIAN CAPITAL LETTER VEW */
+ {0x054F, 0x057F}, /* ARMENIAN CAPITAL LETTER TIWN */
+ {0x0550, 0x0580}, /* ARMENIAN CAPITAL LETTER REH */
+ {0x0551, 0x0581}, /* ARMENIAN CAPITAL LETTER CO */
+ {0x0552, 0x0582}, /* ARMENIAN CAPITAL LETTER YIWN */
+ {0x0553, 0x0583}, /* ARMENIAN CAPITAL LETTER PIWR */
+ {0x0554, 0x0584}, /* ARMENIAN CAPITAL LETTER KEH */
+ {0x0555, 0x0585}, /* ARMENIAN CAPITAL LETTER OH */
+ {0x0556, 0x0586}, /* ARMENIAN CAPITAL LETTER FEH */
+ {0x10A0, 0x10D0}, /* GEORGIAN CAPITAL LETTER AN */
+ {0x10A1, 0x10D1}, /* GEORGIAN CAPITAL LETTER BAN */
+ {0x10A2, 0x10D2}, /* GEORGIAN CAPITAL LETTER GAN */
+ {0x10A3, 0x10D3}, /* GEORGIAN CAPITAL LETTER DON */
+ {0x10A4, 0x10D4}, /* GEORGIAN CAPITAL LETTER EN */
+ {0x10A5, 0x10D5}, /* GEORGIAN CAPITAL LETTER VIN */
+ {0x10A6, 0x10D6}, /* GEORGIAN CAPITAL LETTER ZEN */
+ {0x10A7, 0x10D7}, /* GEORGIAN CAPITAL LETTER TAN */
+ {0x10A8, 0x10D8}, /* GEORGIAN CAPITAL LETTER IN */
+ {0x10A9, 0x10D9}, /* GEORGIAN CAPITAL LETTER KAN */
+ {0x10AA, 0x10DA}, /* GEORGIAN CAPITAL LETTER LAS */
+ {0x10AB, 0x10DB}, /* GEORGIAN CAPITAL LETTER MAN */
+ {0x10AC, 0x10DC}, /* GEORGIAN CAPITAL LETTER NAR */
+ {0x10AD, 0x10DD}, /* GEORGIAN CAPITAL LETTER ON */
+ {0x10AE, 0x10DE}, /* GEORGIAN CAPITAL LETTER PAR */
+ {0x10AF, 0x10DF}, /* GEORGIAN CAPITAL LETTER ZHAR */
+ {0x10B0, 0x10E0}, /* GEORGIAN CAPITAL LETTER RAE */
+ {0x10B1, 0x10E1}, /* GEORGIAN CAPITAL LETTER SAN */
+ {0x10B2, 0x10E2}, /* GEORGIAN CAPITAL LETTER TAR */
+ {0x10B3, 0x10E3}, /* GEORGIAN CAPITAL LETTER UN */
+ {0x10B4, 0x10E4}, /* GEORGIAN CAPITAL LETTER PHAR */
+ {0x10B5, 0x10E5}, /* GEORGIAN CAPITAL LETTER KHAR */
+ {0x10B6, 0x10E6}, /* GEORGIAN CAPITAL LETTER GHAN */
+ {0x10B7, 0x10E7}, /* GEORGIAN CAPITAL LETTER QAR */
+ {0x10B8, 0x10E8}, /* GEORGIAN CAPITAL LETTER SHIN */
+ {0x10B9, 0x10E9}, /* GEORGIAN CAPITAL LETTER CHIN */
+ {0x10BA, 0x10EA}, /* GEORGIAN CAPITAL LETTER CAN */
+ {0x10BB, 0x10EB}, /* GEORGIAN CAPITAL LETTER JIL */
+ {0x10BC, 0x10EC}, /* GEORGIAN CAPITAL LETTER CIL */
+ {0x10BD, 0x10ED}, /* GEORGIAN CAPITAL LETTER CHAR */
+ {0x10BE, 0x10EE}, /* GEORGIAN CAPITAL LETTER XAN */
+ {0x10BF, 0x10EF}, /* GEORGIAN CAPITAL LETTER JHAN */
+ {0x10C0, 0x10F0}, /* GEORGIAN CAPITAL LETTER HAE */
+ {0x10C1, 0x10F1}, /* GEORGIAN CAPITAL LETTER HE */
+ {0x10C2, 0x10F2}, /* GEORGIAN CAPITAL LETTER HIE */
+ {0x10C3, 0x10F3}, /* GEORGIAN CAPITAL LETTER WE */
+ {0x10C4, 0x10F4}, /* GEORGIAN CAPITAL LETTER HAR */
+ {0x10C5, 0x10F5}, /* GEORGIAN CAPITAL LETTER HOE */
+ {0x1E00, 0x1E01}, /* LATIN CAPITAL LETTER A WITH RING BELOW */
+ {0x1E02, 0x1E03}, /* LATIN CAPITAL LETTER B WITH DOT ABOVE */
+ {0x1E04, 0x1E05}, /* LATIN CAPITAL LETTER B WITH DOT BELOW */
+ {0x1E06, 0x1E07}, /* LATIN CAPITAL LETTER B WITH LINE BELOW */
+ {0x1E08, 0x1E09}, /* LATIN CAPITAL LETTER C WITH CEDILLA AND ACUTE */
+ {0x1E0A, 0x1E0B}, /* LATIN CAPITAL LETTER D WITH DOT ABOVE */
+ {0x1E0C, 0x1E0D}, /* LATIN CAPITAL LETTER D WITH DOT BELOW */
+ {0x1E0E, 0x1E0F}, /* LATIN CAPITAL LETTER D WITH LINE BELOW */
+ {0x1E10, 0x1E11}, /* LATIN CAPITAL LETTER D WITH CEDILLA */
+ {0x1E12, 0x1E13}, /* LATIN CAPITAL LETTER D WITH CIRCUMFLEX BELOW */
+ {0x1E14, 0x1E15}, /* LATIN CAPITAL LETTER E WITH MACRON AND GRAVE */
+ {0x1E16, 0x1E17}, /* LATIN CAPITAL LETTER E WITH MACRON AND ACUTE */
+ {0x1E18, 0x1E19}, /* LATIN CAPITAL LETTER E WITH CIRCUMFLEX BELOW */
+ {0x1E1A, 0x1E1B}, /* LATIN CAPITAL LETTER E WITH TILDE BELOW */
+ {0x1E1C, 0x1E1D}, /* LATIN CAPITAL LETTER E WITH CEDILLA AND BREVE */
+ {0x1E1E, 0x1E1F}, /* LATIN CAPITAL LETTER F WITH DOT ABOVE */
+ {0x1E20, 0x1E21}, /* LATIN CAPITAL LETTER G WITH MACRON */
+ {0x1E22, 0x1E23}, /* LATIN CAPITAL LETTER H WITH DOT ABOVE */
+ {0x1E24, 0x1E25}, /* LATIN CAPITAL LETTER H WITH DOT BELOW */
+ {0x1E26, 0x1E27}, /* LATIN CAPITAL LETTER H WITH DIAERESIS */
+ {0x1E28, 0x1E29}, /* LATIN CAPITAL LETTER H WITH CEDILLA */
+ {0x1E2A, 0x1E2B}, /* LATIN CAPITAL LETTER H WITH BREVE BELOW */
+ {0x1E2C, 0x1E2D}, /* LATIN CAPITAL LETTER I WITH TILDE BELOW */
+ {0x1E2E, 0x1E2F}, /* LATIN CAPITAL LETTER I WITH DIAERESIS AND ACUTE */
+ {0x1E30, 0x1E31}, /* LATIN CAPITAL LETTER K WITH ACUTE */
+ {0x1E32, 0x1E33}, /* LATIN CAPITAL LETTER K WITH DOT BELOW */
+ {0x1E34, 0x1E35}, /* LATIN CAPITAL LETTER K WITH LINE BELOW */
+ {0x1E36, 0x1E37}, /* LATIN CAPITAL LETTER L WITH DOT BELOW */
+ {0x1E38, 0x1E39}, /* LATIN CAPITAL LETTER L WITH DOT BELOW AND MACRON */
+ {0x1E3A, 0x1E3B}, /* LATIN CAPITAL LETTER L WITH LINE BELOW */
+ {0x1E3C, 0x1E3D}, /* LATIN CAPITAL LETTER L WITH CIRCUMFLEX BELOW */
+ {0x1E3E, 0x1E3F}, /* LATIN CAPITAL LETTER M WITH ACUTE */
+ {0x1E40, 0x1E41}, /* LATIN CAPITAL LETTER M WITH DOT ABOVE */
+ {0x1E42, 0x1E43}, /* LATIN CAPITAL LETTER M WITH DOT BELOW */
+ {0x1E44, 0x1E45}, /* LATIN CAPITAL LETTER N WITH DOT ABOVE */
+ {0x1E46, 0x1E47}, /* LATIN CAPITAL LETTER N WITH DOT BELOW */
+ {0x1E48, 0x1E49}, /* LATIN CAPITAL LETTER N WITH LINE BELOW */
+ {0x1E4A, 0x1E4B}, /* LATIN CAPITAL LETTER N WITH CIRCUMFLEX BELOW */
+ {0x1E4C, 0x1E4D}, /* LATIN CAPITAL LETTER O WITH TILDE AND ACUTE */
+ {0x1E4E, 0x1E4F}, /* LATIN CAPITAL LETTER O WITH TILDE AND DIAERESIS */
+ {0x1E50, 0x1E51}, /* LATIN CAPITAL LETTER O WITH MACRON AND GRAVE */
+ {0x1E52, 0x1E53}, /* LATIN CAPITAL LETTER O WITH MACRON AND ACUTE */
+ {0x1E54, 0x1E55}, /* LATIN CAPITAL LETTER P WITH ACUTE */
+ {0x1E56, 0x1E57}, /* LATIN CAPITAL LETTER P WITH DOT ABOVE */
+ {0x1E58, 0x1E59}, /* LATIN CAPITAL LETTER R WITH DOT ABOVE */
+ {0x1E5A, 0x1E5B}, /* LATIN CAPITAL LETTER R WITH DOT BELOW */
+ {0x1E5C, 0x1E5D}, /* LATIN CAPITAL LETTER R WITH DOT BELOW AND MACRON */
+ {0x1E5E, 0x1E5F}, /* LATIN CAPITAL LETTER R WITH LINE BELOW */
+ {0x1E60, 0x1E61}, /* LATIN CAPITAL LETTER S WITH DOT ABOVE */
+ {0x1E62, 0x1E63}, /* LATIN CAPITAL LETTER S WITH DOT BELOW */
+ {0x1E64, 0x1E65}, /* LATIN CAPITAL LETTER S WITH ACUTE AND DOT ABOVE */
+ {0x1E66, 0x1E67}, /* LATIN CAPITAL LETTER S WITH CARON AND DOT ABOVE */
+ {0x1E68, 0x1E69}, /* LATIN CAPITAL LETTER S WITH DOT BELOW AND DOT ABOVE */
+ {0x1E6A, 0x1E6B}, /* LATIN CAPITAL LETTER T WITH DOT ABOVE */
+ {0x1E6C, 0x1E6D}, /* LATIN CAPITAL LETTER T WITH DOT BELOW */
+ {0x1E6E, 0x1E6F}, /* LATIN CAPITAL LETTER T WITH LINE BELOW */
+ {0x1E70, 0x1E71}, /* LATIN CAPITAL LETTER T WITH CIRCUMFLEX BELOW */
+ {0x1E72, 0x1E73}, /* LATIN CAPITAL LETTER U WITH DIAERESIS BELOW */
+ {0x1E74, 0x1E75}, /* LATIN CAPITAL LETTER U WITH TILDE BELOW */
+ {0x1E76, 0x1E77}, /* LATIN CAPITAL LETTER U WITH CIRCUMFLEX BELOW */
+ {0x1E78, 0x1E79}, /* LATIN CAPITAL LETTER U WITH TILDE AND ACUTE */
+ {0x1E7A, 0x1E7B}, /* LATIN CAPITAL LETTER U WITH MACRON AND DIAERESIS */
+ {0x1E7C, 0x1E7D}, /* LATIN CAPITAL LETTER V WITH TILDE */
+ {0x1E7E, 0x1E7F}, /* LATIN CAPITAL LETTER V WITH DOT BELOW */
+ {0x1E80, 0x1E81}, /* LATIN CAPITAL LETTER W WITH GRAVE */
+ {0x1E82, 0x1E83}, /* LATIN CAPITAL LETTER W WITH ACUTE */
+ {0x1E84, 0x1E85}, /* LATIN CAPITAL LETTER W WITH DIAERESIS */
+ {0x1E86, 0x1E87}, /* LATIN CAPITAL LETTER W WITH DOT ABOVE */
+ {0x1E88, 0x1E89}, /* LATIN CAPITAL LETTER W WITH DOT BELOW */
+ {0x1E8A, 0x1E8B}, /* LATIN CAPITAL LETTER X WITH DOT ABOVE */
+ {0x1E8C, 0x1E8D}, /* LATIN CAPITAL LETTER X WITH DIAERESIS */
+ {0x1E8E, 0x1E8F}, /* LATIN CAPITAL LETTER Y WITH DOT ABOVE */
+ {0x1E90, 0x1E91}, /* LATIN CAPITAL LETTER Z WITH CIRCUMFLEX */
+ {0x1E92, 0x1E93}, /* LATIN CAPITAL LETTER Z WITH DOT BELOW */
+ {0x1E94, 0x1E95}, /* LATIN CAPITAL LETTER Z WITH LINE BELOW */
+ {0x1EA0, 0x1EA1}, /* LATIN CAPITAL LETTER A WITH DOT BELOW */
+ {0x1EA2, 0x1EA3}, /* LATIN CAPITAL LETTER A WITH HOOK ABOVE */
+ {0x1EA4, 0x1EA5}, /* LATIN CAPITAL LETTER A WITH CIRCUMFLEX AND ACUTE */
+ {0x1EA6, 0x1EA7}, /* LATIN CAPITAL LETTER A WITH CIRCUMFLEX AND GRAVE */
+ {0x1EA8, 0x1EA9}, /* LATIN CAPITAL LETTER A WITH CIRCUMFLEX AND HOOK ABOVE */
+ {0x1EAA, 0x1EAB}, /* LATIN CAPITAL LETTER A WITH CIRCUMFLEX AND TILDE */
+ {0x1EAC, 0x1EAD}, /* LATIN CAPITAL LETTER A WITH CIRCUMFLEX AND DOT BELOW */
+ {0x1EAE, 0x1EAF}, /* LATIN CAPITAL LETTER A WITH BREVE AND ACUTE */
+ {0x1EB0, 0x1EB1}, /* LATIN CAPITAL LETTER A WITH BREVE AND GRAVE */
+ {0x1EB2, 0x1EB3}, /* LATIN CAPITAL LETTER A WITH BREVE AND HOOK ABOVE */
+ {0x1EB4, 0x1EB5}, /* LATIN CAPITAL LETTER A WITH BREVE AND TILDE */
+ {0x1EB6, 0x1EB7}, /* LATIN CAPITAL LETTER A WITH BREVE AND DOT BELOW */
+ {0x1EB8, 0x1EB9}, /* LATIN CAPITAL LETTER E WITH DOT BELOW */
+ {0x1EBA, 0x1EBB}, /* LATIN CAPITAL LETTER E WITH HOOK ABOVE */
+ {0x1EBC, 0x1EBD}, /* LATIN CAPITAL LETTER E WITH TILDE */
+ {0x1EBE, 0x1EBF}, /* LATIN CAPITAL LETTER E WITH CIRCUMFLEX AND ACUTE */
+ {0x1EC0, 0x1EC1}, /* LATIN CAPITAL LETTER E WITH CIRCUMFLEX AND GRAVE */
+ {0x1EC2, 0x1EC3}, /* LATIN CAPITAL LETTER E WITH CIRCUMFLEX AND HOOK ABOVE */
+ {0x1EC4, 0x1EC5}, /* LATIN CAPITAL LETTER E WITH CIRCUMFLEX AND TILDE */
+ {0x1EC6, 0x1EC7}, /* LATIN CAPITAL LETTER E WITH CIRCUMFLEX AND DOT BELOW */
+ {0x1EC8, 0x1EC9}, /* LATIN CAPITAL LETTER I WITH HOOK ABOVE */
+ {0x1ECA, 0x1ECB}, /* LATIN CAPITAL LETTER I WITH DOT BELOW */
+ {0x1ECC, 0x1ECD}, /* LATIN CAPITAL LETTER O WITH DOT BELOW */
+ {0x1ECE, 0x1ECF}, /* LATIN CAPITAL LETTER O WITH HOOK ABOVE */
+ {0x1ED0, 0x1ED1}, /* LATIN CAPITAL LETTER O WITH CIRCUMFLEX AND ACUTE */
+ {0x1ED2, 0x1ED3}, /* LATIN CAPITAL LETTER O WITH CIRCUMFLEX AND GRAVE */
+ {0x1ED4, 0x1ED5}, /* LATIN CAPITAL LETTER O WITH CIRCUMFLEX AND HOOK ABOVE */
+ {0x1ED6, 0x1ED7}, /* LATIN CAPITAL LETTER O WITH CIRCUMFLEX AND TILDE */
+ {0x1ED8, 0x1ED9}, /* LATIN CAPITAL LETTER O WITH CIRCUMFLEX AND DOT BELOW */
+ {0x1EDA, 0x1EDB}, /* LATIN CAPITAL LETTER O WITH HORN AND ACUTE */
+ {0x1EDC, 0x1EDD}, /* LATIN CAPITAL LETTER O WITH HORN AND GRAVE */
+ {0x1EDE, 0x1EDF}, /* LATIN CAPITAL LETTER O WITH HORN AND HOOK ABOVE */
+ {0x1EE0, 0x1EE1}, /* LATIN CAPITAL LETTER O WITH HORN AND TILDE */
+ {0x1EE2, 0x1EE3}, /* LATIN CAPITAL LETTER O WITH HORN AND DOT BELOW */
+ {0x1EE4, 0x1EE5}, /* LATIN CAPITAL LETTER U WITH DOT BELOW */
+ {0x1EE6, 0x1EE7}, /* LATIN CAPITAL LETTER U WITH HOOK ABOVE */
+ {0x1EE8, 0x1EE9}, /* LATIN CAPITAL LETTER U WITH HORN AND ACUTE */
+ {0x1EEA, 0x1EEB}, /* LATIN CAPITAL LETTER U WITH HORN AND GRAVE */
+ {0x1EEC, 0x1EED}, /* LATIN CAPITAL LETTER U WITH HORN AND HOOK ABOVE */
+ {0x1EEE, 0x1EEF}, /* LATIN CAPITAL LETTER U WITH HORN AND TILDE */
+ {0x1EF0, 0x1EF1}, /* LATIN CAPITAL LETTER U WITH HORN AND DOT BELOW */
+ {0x1EF2, 0x1EF3}, /* LATIN CAPITAL LETTER Y WITH GRAVE */
+ {0x1EF4, 0x1EF5}, /* LATIN CAPITAL LETTER Y WITH DOT BELOW */
+ {0x1EF6, 0x1EF7}, /* LATIN CAPITAL LETTER Y WITH HOOK ABOVE */
+ {0x1EF8, 0x1EF9}, /* LATIN CAPITAL LETTER Y WITH TILDE */
+ {0x1F08, 0x1F00}, /* GREEK CAPITAL LETTER ALPHA WITH PSILI */
+ {0x1F09, 0x1F01}, /* GREEK CAPITAL LETTER ALPHA WITH DASIA */
+ {0x1F0A, 0x1F02}, /* GREEK CAPITAL LETTER ALPHA WITH PSILI AND VARIA */
+ {0x1F0B, 0x1F03}, /* GREEK CAPITAL LETTER ALPHA WITH DASIA AND VARIA */
+ {0x1F0C, 0x1F04}, /* GREEK CAPITAL LETTER ALPHA WITH PSILI AND OXIA */
+ {0x1F0D, 0x1F05}, /* GREEK CAPITAL LETTER ALPHA WITH DASIA AND OXIA */
+ {0x1F0E, 0x1F06}, /* GREEK CAPITAL LETTER ALPHA WITH PSILI AND PERISPOMENI */
+ {0x1F0F, 0x1F07}, /* GREEK CAPITAL LETTER ALPHA WITH DASIA AND PERISPOMENI */
+ {0x1F18, 0x1F10}, /* GREEK CAPITAL LETTER EPSILON WITH PSILI */
+ {0x1F19, 0x1F11}, /* GREEK CAPITAL LETTER EPSILON WITH DASIA */
+ {0x1F1A, 0x1F12}, /* GREEK CAPITAL LETTER EPSILON WITH PSILI AND VARIA */
+ {0x1F1B, 0x1F13}, /* GREEK CAPITAL LETTER EPSILON WITH DASIA AND VARIA */
+ {0x1F1C, 0x1F14}, /* GREEK CAPITAL LETTER EPSILON WITH PSILI AND OXIA */
+ {0x1F1D, 0x1F15}, /* GREEK CAPITAL LETTER EPSILON WITH DASIA AND OXIA */
+ {0x1F28, 0x1F20}, /* GREEK CAPITAL LETTER ETA WITH PSILI */
+ {0x1F29, 0x1F21}, /* GREEK CAPITAL LETTER ETA WITH DASIA */
+ {0x1F2A, 0x1F22}, /* GREEK CAPITAL LETTER ETA WITH PSILI AND VARIA */
+ {0x1F2B, 0x1F23}, /* GREEK CAPITAL LETTER ETA WITH DASIA AND VARIA */
+ {0x1F2C, 0x1F24}, /* GREEK CAPITAL LETTER ETA WITH PSILI AND OXIA */
+ {0x1F2D, 0x1F25}, /* GREEK CAPITAL LETTER ETA WITH DASIA AND OXIA */
+ {0x1F2E, 0x1F26}, /* GREEK CAPITAL LETTER ETA WITH PSILI AND PERISPOMENI */
+ {0x1F2F, 0x1F27}, /* GREEK CAPITAL LETTER ETA WITH DASIA AND PERISPOMENI */
+ {0x1F38, 0x1F30}, /* GREEK CAPITAL LETTER IOTA WITH PSILI */
+ {0x1F39, 0x1F31}, /* GREEK CAPITAL LETTER IOTA WITH DASIA */
+ {0x1F3A, 0x1F32}, /* GREEK CAPITAL LETTER IOTA WITH PSILI AND VARIA */
+ {0x1F3B, 0x1F33}, /* GREEK CAPITAL LETTER IOTA WITH DASIA AND VARIA */
+ {0x1F3C, 0x1F34}, /* GREEK CAPITAL LETTER IOTA WITH PSILI AND OXIA */
+ {0x1F3D, 0x1F35}, /* GREEK CAPITAL LETTER IOTA WITH DASIA AND OXIA */
+ {0x1F3E, 0x1F36}, /* GREEK CAPITAL LETTER IOTA WITH PSILI AND PERISPOMENI */
+ {0x1F3F, 0x1F37}, /* GREEK CAPITAL LETTER IOTA WITH DASIA AND PERISPOMENI */
+ {0x1F48, 0x1F40}, /* GREEK CAPITAL LETTER OMICRON WITH PSILI */
+ {0x1F49, 0x1F41}, /* GREEK CAPITAL LETTER OMICRON WITH DASIA */
+ {0x1F4A, 0x1F42}, /* GREEK CAPITAL LETTER OMICRON WITH PSILI AND VARIA */
+ {0x1F4B, 0x1F43}, /* GREEK CAPITAL LETTER OMICRON WITH DASIA AND VARIA */
+ {0x1F4C, 0x1F44}, /* GREEK CAPITAL LETTER OMICRON WITH PSILI AND OXIA */
+ {0x1F4D, 0x1F45}, /* GREEK CAPITAL LETTER OMICRON WITH DASIA AND OXIA */
+ {0x1F59, 0x1F51}, /* GREEK CAPITAL LETTER UPSILON WITH DASIA */
+ {0x1F5B, 0x1F53}, /* GREEK CAPITAL LETTER UPSILON WITH DASIA AND VARIA */
+ {0x1F5D, 0x1F55}, /* GREEK CAPITAL LETTER UPSILON WITH DASIA AND OXIA */
+ {0x1F5F, 0x1F57}, /* GREEK CAPITAL LETTER UPSILON WITH DASIA AND PERISPOMENI */
+ {0x1F68, 0x1F60}, /* GREEK CAPITAL LETTER OMEGA WITH PSILI */
+ {0x1F69, 0x1F61}, /* GREEK CAPITAL LETTER OMEGA WITH DASIA */
+ {0x1F6A, 0x1F62}, /* GREEK CAPITAL LETTER OMEGA WITH PSILI AND VARIA */
+ {0x1F6B, 0x1F63}, /* GREEK CAPITAL LETTER OMEGA WITH DASIA AND VARIA */
+ {0x1F6C, 0x1F64}, /* GREEK CAPITAL LETTER OMEGA WITH PSILI AND OXIA */
+ {0x1F6D, 0x1F65}, /* GREEK CAPITAL LETTER OMEGA WITH DASIA AND OXIA */
+ {0x1F6E, 0x1F66}, /* GREEK CAPITAL LETTER OMEGA WITH PSILI AND PERISPOMENI */
+ {0x1F6F, 0x1F67}, /* GREEK CAPITAL LETTER OMEGA WITH DASIA AND PERISPOMENI */
+ {0x1F88, 0x1F80}, /* GREEK CAPITAL LETTER ALPHA WITH PSILI AND PROSGEGRAMMENI */
+ {0x1F89, 0x1F81}, /* GREEK CAPITAL LETTER ALPHA WITH DASIA AND PROSGEGRAMMENI */
+ {0x1F8A, 0x1F82}, /* GREEK CAPITAL LETTER ALPHA WITH PSILI AND VARIA AND PROSGEGRAMMENI */
+ {0x1F8B, 0x1F83}, /* GREEK CAPITAL LETTER ALPHA WITH DASIA AND VARIA AND PROSGEGRAMMENI */
+ {0x1F8C, 0x1F84}, /* GREEK CAPITAL LETTER ALPHA WITH PSILI AND OXIA AND PROSGEGRAMMENI */
+ {0x1F8D, 0x1F85}, /* GREEK CAPITAL LETTER ALPHA WITH DASIA AND OXIA AND PROSGEGRAMMENI */
+ {0x1F8E, 0x1F86}, /* GREEK CAPITAL LETTER ALPHA WITH PSILI AND PERISPOMENI AND PROSGEGRAMMENI */
+ {0x1F8F, 0x1F87}, /* GREEK CAPITAL LETTER ALPHA WITH DASIA AND PERISPOMENI AND PROSGEGRAMMENI */
+ {0x1F98, 0x1F90}, /* GREEK CAPITAL LETTER ETA WITH PSILI AND PROSGEGRAMMENI */
+ {0x1F99, 0x1F91}, /* GREEK CAPITAL LETTER ETA WITH DASIA AND PROSGEGRAMMENI */
+ {0x1F9A, 0x1F92}, /* GREEK CAPITAL LETTER ETA WITH PSILI AND VARIA AND PROSGEGRAMMENI */
+ {0x1F9B, 0x1F93}, /* GREEK CAPITAL LETTER ETA WITH DASIA AND VARIA AND PROSGEGRAMMENI */
+ {0x1F9C, 0x1F94}, /* GREEK CAPITAL LETTER ETA WITH PSILI AND OXIA AND PROSGEGRAMMENI */
+ {0x1F9D, 0x1F95}, /* GREEK CAPITAL LETTER ETA WITH DASIA AND OXIA AND PROSGEGRAMMENI */
+ {0x1F9E, 0x1F96}, /* GREEK CAPITAL LETTER ETA WITH PSILI AND PERISPOMENI AND PROSGEGRAMMENI */
+ {0x1F9F, 0x1F97}, /* GREEK CAPITAL LETTER ETA WITH DASIA AND PERISPOMENI AND PROSGEGRAMMENI */
+ {0x1FA8, 0x1FA0}, /* GREEK CAPITAL LETTER OMEGA WITH PSILI AND PROSGEGRAMMENI */
+ {0x1FA9, 0x1FA1}, /* GREEK CAPITAL LETTER OMEGA WITH DASIA AND PROSGEGRAMMENI */
+ {0x1FAA, 0x1FA2}, /* GREEK CAPITAL LETTER OMEGA WITH PSILI AND VARIA AND PROSGEGRAMMENI */
+ {0x1FAB, 0x1FA3}, /* GREEK CAPITAL LETTER OMEGA WITH DASIA AND VARIA AND PROSGEGRAMMENI */
+ {0x1FAC, 0x1FA4}, /* GREEK CAPITAL LETTER OMEGA WITH PSILI AND OXIA AND PROSGEGRAMMENI */
+ {0x1FAD, 0x1FA5}, /* GREEK CAPITAL LETTER OMEGA WITH DASIA AND OXIA AND PROSGEGRAMMENI */
+ {0x1FAE, 0x1FA6}, /* GREEK CAPITAL LETTER OMEGA WITH PSILI AND PERISPOMENI AND PROSGEGRAMMENI */
+ {0x1FAF, 0x1FA7}, /* GREEK CAPITAL LETTER OMEGA WITH DASIA AND PERISPOMENI AND PROSGEGRAMMENI */
+ {0x1FB8, 0x1FB0}, /* GREEK CAPITAL LETTER ALPHA WITH VRACHY */
+ {0x1FB9, 0x1FB1}, /* GREEK CAPITAL LETTER ALPHA WITH MACRON */
+ {0x1FBA, 0x1F70}, /* GREEK CAPITAL LETTER ALPHA WITH VARIA */
+ {0x1FBB, 0x1F71}, /* GREEK CAPITAL LETTER ALPHA WITH OXIA */
+ {0x1FBC, 0x1FB3}, /* GREEK CAPITAL LETTER ALPHA WITH PROSGEGRAMMENI */
+ {0x1FC8, 0x1F72}, /* GREEK CAPITAL LETTER EPSILON WITH VARIA */
+ {0x1FC9, 0x1F73}, /* GREEK CAPITAL LETTER EPSILON WITH OXIA */
+ {0x1FCA, 0x1F74}, /* GREEK CAPITAL LETTER ETA WITH VARIA */
+ {0x1FCB, 0x1F75}, /* GREEK CAPITAL LETTER ETA WITH OXIA */
+ {0x1FCC, 0x1FC3}, /* GREEK CAPITAL LETTER ETA WITH PROSGEGRAMMENI */
+ {0x1FD8, 0x1FD0}, /* GREEK CAPITAL LETTER IOTA WITH VRACHY */
+ {0x1FD9, 0x1FD1}, /* GREEK CAPITAL LETTER IOTA WITH MACRON */
+ {0x1FDA, 0x1F76}, /* GREEK CAPITAL LETTER IOTA WITH VARIA */
+ {0x1FDB, 0x1F77}, /* GREEK CAPITAL LETTER IOTA WITH OXIA */
+ {0x1FE8, 0x1FE0}, /* GREEK CAPITAL LETTER UPSILON WITH VRACHY */
+ {0x1FE9, 0x1FE1}, /* GREEK CAPITAL LETTER UPSILON WITH MACRON */
+ {0x1FEA, 0x1F7A}, /* GREEK CAPITAL LETTER UPSILON WITH VARIA */
+ {0x1FEB, 0x1F7B}, /* GREEK CAPITAL LETTER UPSILON WITH OXIA */
+ {0x1FEC, 0x1FE5}, /* GREEK CAPITAL LETTER RHO WITH DASIA */
+ {0x1FF8, 0x1F78}, /* GREEK CAPITAL LETTER OMICRON WITH VARIA */
+ {0x1FF9, 0x1F79}, /* GREEK CAPITAL LETTER OMICRON WITH OXIA */
+ {0x1FFA, 0x1F7C}, /* GREEK CAPITAL LETTER OMEGA WITH VARIA */
+ {0x1FFB, 0x1F7D}, /* GREEK CAPITAL LETTER OMEGA WITH OXIA */
+ {0x1FFC, 0x1FF3}, /* GREEK CAPITAL LETTER OMEGA WITH PROSGEGRAMMENI */
+ {0x2160, 0x2170}, /* ROMAN NUMERAL ONE */
+ {0x2161, 0x2171}, /* ROMAN NUMERAL TWO */
+ {0x2162, 0x2172}, /* ROMAN NUMERAL THREE */
+ {0x2163, 0x2173}, /* ROMAN NUMERAL FOUR */
+ {0x2164, 0x2174}, /* ROMAN NUMERAL FIVE */
+ {0x2165, 0x2175}, /* ROMAN NUMERAL SIX */
+ {0x2166, 0x2176}, /* ROMAN NUMERAL SEVEN */
+ {0x2167, 0x2177}, /* ROMAN NUMERAL EIGHT */
+ {0x2168, 0x2178}, /* ROMAN NUMERAL NINE */
+ {0x2169, 0x2179}, /* ROMAN NUMERAL TEN */
+ {0x216A, 0x217A}, /* ROMAN NUMERAL ELEVEN */
+ {0x216B, 0x217B}, /* ROMAN NUMERAL TWELVE */
+ {0x216C, 0x217C}, /* ROMAN NUMERAL FIFTY */
+ {0x216D, 0x217D}, /* ROMAN NUMERAL ONE HUNDRED */
+ {0x216E, 0x217E}, /* ROMAN NUMERAL FIVE HUNDRED */
+ {0x216F, 0x217F}, /* ROMAN NUMERAL ONE THOUSAND */
+ {0x24B6, 0x24D0}, /* CIRCLED LATIN CAPITAL LETTER A */
+ {0x24B7, 0x24D1}, /* CIRCLED LATIN CAPITAL LETTER B */
+ {0x24B8, 0x24D2}, /* CIRCLED LATIN CAPITAL LETTER C */
+ {0x24B9, 0x24D3}, /* CIRCLED LATIN CAPITAL LETTER D */
+ {0x24BA, 0x24D4}, /* CIRCLED LATIN CAPITAL LETTER E */
+ {0x24BB, 0x24D5}, /* CIRCLED LATIN CAPITAL LETTER F */
+ {0x24BC, 0x24D6}, /* CIRCLED LATIN CAPITAL LETTER G */
+ {0x24BD, 0x24D7}, /* CIRCLED LATIN CAPITAL LETTER H */
+ {0x24BE, 0x24D8}, /* CIRCLED LATIN CAPITAL LETTER I */
+ {0x24BF, 0x24D9}, /* CIRCLED LATIN CAPITAL LETTER J */
+ {0x24C0, 0x24DA}, /* CIRCLED LATIN CAPITAL LETTER K */
+ {0x24C1, 0x24DB}, /* CIRCLED LATIN CAPITAL LETTER L */
+ {0x24C2, 0x24DC}, /* CIRCLED LATIN CAPITAL LETTER M */
+ {0x24C3, 0x24DD}, /* CIRCLED LATIN CAPITAL LETTER N */
+ {0x24C4, 0x24DE}, /* CIRCLED LATIN CAPITAL LETTER O */
+ {0x24C5, 0x24DF}, /* CIRCLED LATIN CAPITAL LETTER P */
+ {0x24C6, 0x24E0}, /* CIRCLED LATIN CAPITAL LETTER Q */
+ {0x24C7, 0x24E1}, /* CIRCLED LATIN CAPITAL LETTER R */
+ {0x24C8, 0x24E2}, /* CIRCLED LATIN CAPITAL LETTER S */
+ {0x24C9, 0x24E3}, /* CIRCLED LATIN CAPITAL LETTER T */
+ {0x24CA, 0x24E4}, /* CIRCLED LATIN CAPITAL LETTER U */
+ {0x24CB, 0x24E5}, /* CIRCLED LATIN CAPITAL LETTER V */
+ {0x24CC, 0x24E6}, /* CIRCLED LATIN CAPITAL LETTER W */
+ {0x24CD, 0x24E7}, /* CIRCLED LATIN CAPITAL LETTER X */
+ {0x24CE, 0x24E8}, /* CIRCLED LATIN CAPITAL LETTER Y */
+ {0x24CF, 0x24E9}, /* CIRCLED LATIN CAPITAL LETTER Z */
+ {0xFF21, 0xFF41}, /* FULLWIDTH LATIN CAPITAL LETTER A */
+ {0xFF22, 0xFF42}, /* FULLWIDTH LATIN CAPITAL LETTER B */
+ {0xFF23, 0xFF43}, /* FULLWIDTH LATIN CAPITAL LETTER C */
+ {0xFF24, 0xFF44}, /* FULLWIDTH LATIN CAPITAL LETTER D */
+ {0xFF25, 0xFF45}, /* FULLWIDTH LATIN CAPITAL LETTER E */
+ {0xFF26, 0xFF46}, /* FULLWIDTH LATIN CAPITAL LETTER F */
+ {0xFF27, 0xFF47}, /* FULLWIDTH LATIN CAPITAL LETTER G */
+ {0xFF28, 0xFF48}, /* FULLWIDTH LATIN CAPITAL LETTER H */
+ {0xFF29, 0xFF49}, /* FULLWIDTH LATIN CAPITAL LETTER I */
+ {0xFF2A, 0xFF4A}, /* FULLWIDTH LATIN CAPITAL LETTER J */
+ {0xFF2B, 0xFF4B}, /* FULLWIDTH LATIN CAPITAL LETTER K */
+ {0xFF2C, 0xFF4C}, /* FULLWIDTH LATIN CAPITAL LETTER L */
+ {0xFF2D, 0xFF4D}, /* FULLWIDTH LATIN CAPITAL LETTER M */
+ {0xFF2E, 0xFF4E}, /* FULLWIDTH LATIN CAPITAL LETTER N */
+ {0xFF2F, 0xFF4F}, /* FULLWIDTH LATIN CAPITAL LETTER O */
+ {0xFF30, 0xFF50}, /* FULLWIDTH LATIN CAPITAL LETTER P */
+ {0xFF31, 0xFF51}, /* FULLWIDTH LATIN CAPITAL LETTER Q */
+ {0xFF32, 0xFF52}, /* FULLWIDTH LATIN CAPITAL LETTER R */
+ {0xFF33, 0xFF53}, /* FULLWIDTH LATIN CAPITAL LETTER S */
+ {0xFF34, 0xFF54}, /* FULLWIDTH LATIN CAPITAL LETTER T */
+ {0xFF35, 0xFF55}, /* FULLWIDTH LATIN CAPITAL LETTER U */
+ {0xFF36, 0xFF56}, /* FULLWIDTH LATIN CAPITAL LETTER V */
+ {0xFF37, 0xFF57}, /* FULLWIDTH LATIN CAPITAL LETTER W */
+ {0xFF38, 0xFF58}, /* FULLWIDTH LATIN CAPITAL LETTER X */
+ {0xFF39, 0xFF59}, /* FULLWIDTH LATIN CAPITAL LETTER Y */
+ {0xFF3A, 0xFF5A} /* FULLWIDTH LATIN CAPITAL LETTER Z */
+};
+/* *INDENT-ON* */
diff --git a/src/chrtrans/cp1250_uni.tbl b/src/chrtrans/cp1250_uni.tbl
new file mode 100644
index 0000000..8a19d55
--- /dev/null
+++ b/src/chrtrans/cp1250_uni.tbl
@@ -0,0 +1,172 @@
+# This file has been modified for lynx (see README.tables)
+
+#The MIME name of this charset.
+Mwindows-1250
+
+#Name as a Display Charset (used on Options screen)
+OEastern European (windows-1250)
+
+#Codepage number
+C1250
+
+#
+# Name: cp1250 to Unicode table
+# Unicode version: 2.0
+# Table version: 2.01
+# Table format: Format A
+# Date: 04/15/98
+#
+# Contact: cpxlate@microsoft.com
+#
+# General notes: none
+#
+# Format: Three tab-separated columns
+# Column #1 is the cp1250 code (in hex)
+# Column #2 is the Unicode (in hex as 0xXXXX)
+# Column #3 is the Unicode name (follows a comment sign, '#')
+#
+# The entries are in cp1250 order
+#
+##################
+# Lines with more than one Unicode (U+XXXX) value contain additional
+# replacement mappings added for lynx. - kw
+
+0x20-0x7e idem
+#
+0x80 U+20AC #EURO SIGN
+0x81 #UNDEFINED
+0x82 U+201A #SINGLE LOW-9 QUOTATION MARK
+0x83 #UNDEFINED
+0x84 U+201E #DOUBLE LOW-9 QUOTATION MARK
+0x85 U+2026 #HORIZONTAL ELLIPSIS
+0x86 U+2020 #DAGGER
+0x87 U+2021 #DOUBLE DAGGER
+0x88 #UNDEFINED
+0x89 U+2030 #PER MILLE SIGN
+0x8A U+0160 U+0428 #LATIN CAPITAL LETTER S WITH CARON
+0x8B U+2039 #SINGLE LEFT-POINTING ANGLE QUOTATION MARK
+0x8C U+015A #LATIN CAPITAL LETTER S WITH ACUTE
+0x8D U+0164 #LATIN CAPITAL LETTER T WITH CARON
+0x8E U+017D U+0416 #LATIN CAPITAL LETTER Z WITH CARON
+0x8F U+0179 #LATIN CAPITAL LETTER Z WITH ACUTE
+0x90 #UNDEFINED
+0x91 U+2018 #LEFT SINGLE QUOTATION MARK
+0x92 U+2019 #RIGHT SINGLE QUOTATION MARK
+0x93 U+201C #LEFT DOUBLE QUOTATION MARK
+0x94 U+201D #RIGHT DOUBLE QUOTATION MARK
+0x95 U+2022 #BULLET
+0x96 U+2013 #EN DASH
+0x97 U+2014 #EM DASH
+0x98 #UNDEFINED
+0x99 U+2122 #TRADE MARK SIGN
+0x9A U+0161 U+0448 #LATIN SMALL LETTER S WITH CARON
+0x9B U+203A #SINGLE RIGHT-POINTING ANGLE QUOTATION MARK
+0x9C U+015B #LATIN SMALL LETTER S WITH ACUTE
+0x9D U+0165 #LATIN SMALL LETTER T WITH CARON
+0x9E U+017E U+0436 #LATIN SMALL LETTER Z WITH CARON
+0x9F U+017A #LATIN SMALL LETTER Z WITH ACUTE
+0xA0 U+00A0 #NO-BREAK SPACE
+0xA1 U+02C7 U+030c #CARON
+0xA2 U+02D8 U+0306 #BREVE
+0xA3 U+0141 #LATIN CAPITAL LETTER L WITH STROKE
+0xA4 U+00A4 #CURRENCY SIGN
+0xA5 U+0104 #LATIN CAPITAL LETTER A WITH OGONEK
+0xA6 U+00A6 #BROKEN BAR
+0xA7 U+00A7 #SECTION SIGN
+0xA8 U+00A8 U+0308 #DIAERESIS
+0xA9 U+00A9 #COPYRIGHT SIGN
+0xAA U+015E #LATIN CAPITAL LETTER S WITH CEDILLA
+0xAB U+00AB #LEFT-POINTING DOUBLE ANGLE QUOTATION MARK
+0xAC U+00AC #NOT SIGN
+0xAD U+00AD #SOFT HYPHEN
+0xAE U+00AE #REGISTERED SIGN
+0xAF U+017B #LATIN CAPITAL LETTER Z WITH DOT ABOVE
+0xB0 U+00B0 U+030a #DEGREE SIGN
+0xB1 U+00B1 #PLUS-MINUS SIGN
+0xB2 U+02DB U+0328 #OGONEK
+0xB3 U+0142 #LATIN SMALL LETTER L WITH STROKE
+0xB4 U+00B4 #ACUTE ACCENT
+0xB5 U+00B5 U+03bc #MICRO SIGN
+0xB6 U+00B6 #PILCROW SIGN
+0xB7 U+00B7 #MIDDLE DOT
+0xB8 U+00B8 U+0327 #CEDILLA
+0xB9 U+0105 #LATIN SMALL LETTER A WITH OGONEK
+0xBA U+015F #LATIN SMALL LETTER S WITH CEDILLA
+0xBB U+00BB #RIGHT-POINTING DOUBLE ANGLE QUOTATION MARK
+0xBC U+013D #LATIN CAPITAL LETTER L WITH CARON
+0xBD U+02DD U+030b #DOUBLE ACUTE ACCENT
+0xBE U+013E #LATIN SMALL LETTER L WITH CARON
+0xBF U+017C #LATIN SMALL LETTER Z WITH DOT ABOVE
+0xC0 U+0154 #LATIN CAPITAL LETTER R WITH ACUTE
+0xC1 U+00C1 #LATIN CAPITAL LETTER A WITH ACUTE
+0xC2 U+00C2 #LATIN CAPITAL LETTER A WITH CIRCUMFLEX
+0xC3 U+0102 #LATIN CAPITAL LETTER A WITH BREVE
+0xC4 U+00C4 #LATIN CAPITAL LETTER A WITH DIAERESIS
+0xC5 U+0139 #LATIN CAPITAL LETTER L WITH ACUTE
+0xC6 U+0106 #LATIN CAPITAL LETTER C WITH ACUTE
+0xC7 U+00C7 #LATIN CAPITAL LETTER C WITH CEDILLA
+0xC8 U+010C U+0427 # LATIN CAPITAL LETTER C WITH CARON
+0xC9 U+00C9 #LATIN CAPITAL LETTER E WITH ACUTE
+0xCA U+0118 #LATIN CAPITAL LETTER E WITH OGONEK
+0xCB U+00CB #LATIN CAPITAL LETTER E WITH DIAERESIS
+0xCC U+011A #LATIN CAPITAL LETTER E WITH CARON
+0xCD U+00CD #LATIN CAPITAL LETTER I WITH ACUTE
+0xCE U+00CE #LATIN CAPITAL LETTER I WITH CIRCUMFLEX
+0xCF U+010E #LATIN CAPITAL LETTER D WITH CARON
+0xD0 U+0110 #LATIN CAPITAL LETTER D WITH STROKE
+0xD1 U+0143 #LATIN CAPITAL LETTER N WITH ACUTE
+0xD2 U+0147 #LATIN CAPITAL LETTER N WITH CARON
+0xD3 U+00D3 #LATIN CAPITAL LETTER O WITH ACUTE
+0xD4 U+00D4 #LATIN CAPITAL LETTER O WITH CIRCUMFLEX
+0xD5 U+0150 #LATIN CAPITAL LETTER O WITH DOUBLE ACUTE
+0xD6 U+00D6 #LATIN CAPITAL LETTER O WITH DIAERESIS
+0xD7 U+00D7 #MULTIPLICATION SIGN
+0xD8 U+0158 #LATIN CAPITAL LETTER R WITH CARON
+0xD9 U+016E #LATIN CAPITAL LETTER U WITH RING ABOVE
+0xDA U+00DA #LATIN CAPITAL LETTER U WITH ACUTE
+0xDB U+0170 #LATIN CAPITAL LETTER U WITH DOUBLE ACUTE
+0xDC U+00DC #LATIN CAPITAL LETTER U WITH DIAERESIS
+0xDD U+00DD #LATIN CAPITAL LETTER Y WITH ACUTE
+0xDE U+0162 #LATIN CAPITAL LETTER T WITH CEDILLA
+0xDF U+00DF #LATIN SMALL LETTER SHARP S
+0xE0 U+0155 #LATIN SMALL LETTER R WITH ACUTE
+0xE1 U+00E1 #LATIN SMALL LETTER A WITH ACUTE
+0xE2 U+00E2 #LATIN SMALL LETTER A WITH CIRCUMFLEX
+0xE3 U+0103 #LATIN SMALL LETTER A WITH BREVE
+0xE4 U+00E4 #LATIN SMALL LETTER A WITH DIAERESIS
+0xE5 U+013A #LATIN SMALL LETTER L WITH ACUTE
+0xE6 U+0107 #LATIN SMALL LETTER C WITH ACUTE
+0xE7 U+00E7 #LATIN SMALL LETTER C WITH CEDILLA
+0xE8 U+010D U+02a7 U+0447 # LATIN SMALL LETTER C WITH CARON
+0xE9 U+00E9 #LATIN SMALL LETTER E WITH ACUTE
+0xEA U+0119 #LATIN SMALL LETTER E WITH OGONEK
+0xEB U+00EB #LATIN SMALL LETTER E WITH DIAERESIS
+0xEC U+011B #LATIN SMALL LETTER E WITH CARON
+0xED U+00ED #LATIN SMALL LETTER I WITH ACUTE
+0xEE U+00EE #LATIN SMALL LETTER I WITH CIRCUMFLEX
+0xEF U+010F #LATIN SMALL LETTER D WITH CARON
+0xF0 U+0111 #LATIN SMALL LETTER D WITH STROKE
+0xF1 U+0144 #LATIN SMALL LETTER N WITH ACUTE
+0xF2 U+0148 #LATIN SMALL LETTER N WITH CARON
+0xF3 U+00F3 #LATIN SMALL LETTER O WITH ACUTE
+0xF4 U+00F4 #LATIN SMALL LETTER O WITH CIRCUMFLEX
+0xF5 U+0151 #LATIN SMALL LETTER O WITH DOUBLE ACUTE
+0xF6 U+00F6 #LATIN SMALL LETTER O WITH DIAERESIS
+0xF7 U+00F7 #DIVISION SIGN
+0xF8 U+0159 #LATIN SMALL LETTER R WITH CARON
+0xF9 U+016F #LATIN SMALL LETTER U WITH RING ABOVE
+0xFA U+00FA #LATIN SMALL LETTER U WITH ACUTE
+0xFB U+0171 #LATIN SMALL LETTER U WITH DOUBLE ACUTE
+0xFC U+00FC #LATIN SMALL LETTER U WITH DIAERESIS
+0xFD U+00FD #LATIN SMALL LETTER Y WITH ACUTE
+0xFE U+0163 #LATIN SMALL LETTER T WITH CEDILLA
+0xFF U+02D9 U+0307 U+0387 #DOT ABOVE
+
+U+2218 " \260 " # RING OPERATOR
+U+2219 " \225 " # BULLET OPERATOR
+U+2297 "(\327)" # CIRCLED TIMES
+U+2299 "(\267)" # CIRCLED DOT OPERATOR
+U+229A "(\260)" # CIRCLED RING OPERATOR
+U+22A0 "[\327]" # SQUARED TIMES
+U+22A1 "[\267]" # SQUARED DOT OPERATOR
+U+22C5 " \267 " # DOT OPERATOR
diff --git a/src/chrtrans/cp1251_uni.tbl b/src/chrtrans/cp1251_uni.tbl
new file mode 100644
index 0000000..0d928f9
--- /dev/null
+++ b/src/chrtrans/cp1251_uni.tbl
@@ -0,0 +1,161 @@
+# This file has been modified for lynx (see README.tables)
+
+#The MIME name of this charset.
+Mwindows-1251
+
+#Name as a Display Charset (used on Options screen)
+OCyrillic (windows-1251)
+
+#Codepage number
+C1251
+
+#
+# Name: cp1251 to Unicode table
+# Unicode version: 2.0
+# Table version: 2.01
+# Table format: Format A
+# Date: 04/15/98
+#
+# Contact: cpxlate@microsoft.com
+#
+# General notes: none
+#
+# Format: Three tab-separated columns
+# Column #1 is the cp1251 code (in hex)
+# Column #2 is the Unicode (in hex as 0xXXXX)
+# Column #3 is the Unicode name (follows a comment sign, '#')
+#
+# The entries are in cp1251 order
+#
+##################
+
+0x20-0x7e idem
+#
+0x80 U+0402 #CYRILLIC CAPITAL LETTER DJE
+0x81 U+0403 #CYRILLIC CAPITAL LETTER GJE
+0x82 U+201A #SINGLE LOW-9 QUOTATION MARK
+0x83 U+0453 #CYRILLIC SMALL LETTER GJE
+0x84 U+201E #DOUBLE LOW-9 QUOTATION MARK
+0x85 U+2026 #HORIZONTAL ELLIPSIS
+0x86 U+2020 #DAGGER
+0x87 U+2021 #DOUBLE DAGGER
+0x88 U+20AC #EURO SIGN
+0x89 U+2030 #PER MILLE SIGN
+0x8A U+0409 #CYRILLIC CAPITAL LETTER LJE
+0x8B U+2039 #SINGLE LEFT-POINTING ANGLE QUOTATION MARK
+0x8C U+040A #CYRILLIC CAPITAL LETTER NJE
+0x8D U+040C #CYRILLIC CAPITAL LETTER KJE
+0x8E U+040B #CYRILLIC CAPITAL LETTER TSHE
+0x8F U+040F #CYRILLIC CAPITAL LETTER DZHE
+0x90 U+0452 #CYRILLIC SMALL LETTER DJE
+0x91 U+2018 #LEFT SINGLE QUOTATION MARK
+0x92 U+2019 #RIGHT SINGLE QUOTATION MARK
+0x93 U+201C #LEFT DOUBLE QUOTATION MARK
+0x94 U+201D #RIGHT DOUBLE QUOTATION MARK
+0x95 U+2022 #BULLET
+0x96 U+2013 #EN DASH
+0x97 U+2014 #EM DASH
+0x98 #UNDEFINED
+0x99 U+2122 #TRADE MARK SIGN
+0x9A U+0459 #CYRILLIC SMALL LETTER LJE
+0x9B U+203A #SINGLE RIGHT-POINTING ANGLE QUOTATION MARK
+0x9C U+045A #CYRILLIC SMALL LETTER NJE
+0x9D U+045C #CYRILLIC SMALL LETTER KJE
+0x9E U+045B #CYRILLIC SMALL LETTER TSHE
+0x9F U+045F #CYRILLIC SMALL LETTER DZHE
+0xA0 U+00A0 #NO-BREAK SPACE
+0xA1 U+040E #CYRILLIC CAPITAL LETTER SHORT U
+0xA2 U+045E #CYRILLIC SMALL LETTER SHORT U
+0xA3 U+0408 #CYRILLIC CAPITAL LETTER JE
+0xA4 U+00A4 #CURRENCY SIGN
+0xA5 U+0490 #CYRILLIC CAPITAL LETTER GHE WITH UPTURN
+0xA6 U+00A6 #BROKEN BAR
+0xA7 U+00A7 #SECTION SIGN
+0xA8 U+0401 #CYRILLIC CAPITAL LETTER IO
+0xA9 U+00A9 #COPYRIGHT SIGN
+0xAA U+0404 #CYRILLIC CAPITAL LETTER UKRAINIAN IE
+0xAB U+00AB #LEFT-POINTING DOUBLE ANGLE QUOTATION MARK
+0xAC U+00AC #NOT SIGN
+0xAD U+00AD #SOFT HYPHEN
+0xAE U+00AE #REGISTERED SIGN
+0xAF U+0407 #CYRILLIC CAPITAL LETTER YI
+0xB0 U+00B0 #DEGREE SIGN
+0xB1 U+00B1 #PLUS-MINUS SIGN
+0xB2 U+0406 #CYRILLIC CAPITAL LETTER BYELORUSSIAN-UKRAINIAN I
+0xB3 U+0456 #CYRILLIC SMALL LETTER BYELORUSSIAN-UKRAINIAN I
+0xB4 U+0491 #CYRILLIC SMALL LETTER GHE WITH UPTURN
+0xB5 U+00B5 #MICRO SIGN
+0xB6 U+00B6 #PILCROW SIGN
+0xB7 U+00B7 #MIDDLE DOT
+0xB8 U+0451 #CYRILLIC SMALL LETTER IO
+0xB9 U+2116 #NUMERO SIGN
+0xBA U+0454 #CYRILLIC SMALL LETTER UKRAINIAN IE
+0xBB U+00BB #RIGHT-POINTING DOUBLE ANGLE QUOTATION MARK
+0xBC U+0458 #CYRILLIC SMALL LETTER JE
+0xBD U+0405 #CYRILLIC CAPITAL LETTER DZE
+0xBE U+0455 #CYRILLIC SMALL LETTER DZE
+0xBF U+0457 #CYRILLIC SMALL LETTER YI
+0xC0 U+0410 #CYRILLIC CAPITAL LETTER A
+0xC1 U+0411 #CYRILLIC CAPITAL LETTER BE
+0xC2 U+0412 #CYRILLIC CAPITAL LETTER VE
+0xC3 U+0413 #CYRILLIC CAPITAL LETTER GHE
+0xC4 U+0414 #CYRILLIC CAPITAL LETTER DE
+0xC5 U+0415 #CYRILLIC CAPITAL LETTER IE
+0xC6 U+0416 #CYRILLIC CAPITAL LETTER ZHE
+0xC7 U+0417 #CYRILLIC CAPITAL LETTER ZE
+0xC8 U+0418 #CYRILLIC CAPITAL LETTER I
+0xC9 U+0419 #CYRILLIC CAPITAL LETTER SHORT I
+0xCA U+041A #CYRILLIC CAPITAL LETTER KA
+0xCB U+041B #CYRILLIC CAPITAL LETTER EL
+0xCC U+041C #CYRILLIC CAPITAL LETTER EM
+0xCD U+041D #CYRILLIC CAPITAL LETTER EN
+0xCE U+041E #CYRILLIC CAPITAL LETTER O
+0xCF U+041F #CYRILLIC CAPITAL LETTER PE
+0xD0 U+0420 #CYRILLIC CAPITAL LETTER ER
+0xD1 U+0421 #CYRILLIC CAPITAL LETTER ES
+0xD2 U+0422 #CYRILLIC CAPITAL LETTER TE
+0xD3 U+0423 #CYRILLIC CAPITAL LETTER U
+0xD4 U+0424 #CYRILLIC CAPITAL LETTER EF
+0xD5 U+0425 #CYRILLIC CAPITAL LETTER HA
+0xD6 U+0426 #CYRILLIC CAPITAL LETTER TSE
+0xD7 U+0427 #CYRILLIC CAPITAL LETTER CHE
+0xD8 U+0428 #CYRILLIC CAPITAL LETTER SHA
+0xD9 U+0429 #CYRILLIC CAPITAL LETTER SHCHA
+0xDA U+042A #CYRILLIC CAPITAL LETTER HARD SIGN
+0xDB U+042B #CYRILLIC CAPITAL LETTER YERU
+0xDC U+042C #CYRILLIC CAPITAL LETTER SOFT SIGN
+0xDD U+042D #CYRILLIC CAPITAL LETTER E
+0xDE U+042E #CYRILLIC CAPITAL LETTER YU
+0xDF U+042F #CYRILLIC CAPITAL LETTER YA
+0xE0 U+0430 #CYRILLIC SMALL LETTER A
+0xE1 U+0431 #CYRILLIC SMALL LETTER BE
+0xE2 U+0432 #CYRILLIC SMALL LETTER VE
+0xE3 U+0433 #CYRILLIC SMALL LETTER GHE
+0xE4 U+0434 #CYRILLIC SMALL LETTER DE
+0xE5 U+0435 #CYRILLIC SMALL LETTER IE
+0xE6 U+0436 #CYRILLIC SMALL LETTER ZHE
+0xE7 U+0437 #CYRILLIC SMALL LETTER ZE
+0xE8 U+0438 #CYRILLIC SMALL LETTER I
+0xE9 U+0439 #CYRILLIC SMALL LETTER SHORT I
+0xEA U+043A #CYRILLIC SMALL LETTER KA
+0xEB U+043B #CYRILLIC SMALL LETTER EL
+0xEC U+043C #CYRILLIC SMALL LETTER EM
+0xED U+043D #CYRILLIC SMALL LETTER EN
+0xEE U+043E #CYRILLIC SMALL LETTER O
+0xEF U+043F #CYRILLIC SMALL LETTER PE
+0xF0 U+0440 #CYRILLIC SMALL LETTER ER
+0xF1 U+0441 #CYRILLIC SMALL LETTER ES
+0xF2 U+0442 #CYRILLIC SMALL LETTER TE
+0xF3 U+0443 #CYRILLIC SMALL LETTER U
+0xF4 U+0444 #CYRILLIC SMALL LETTER EF
+0xF5 U+0445 #CYRILLIC SMALL LETTER HA
+0xF6 U+0446 #CYRILLIC SMALL LETTER TSE
+0xF7 U+0447 #CYRILLIC SMALL LETTER CHE
+0xF8 U+0448 #CYRILLIC SMALL LETTER SHA
+0xF9 U+0449 #CYRILLIC SMALL LETTER SHCHA
+0xFA U+044A #CYRILLIC SMALL LETTER HARD SIGN
+0xFB U+044B #CYRILLIC SMALL LETTER YERU
+0xFC U+044C #CYRILLIC SMALL LETTER SOFT SIGN
+0xFD U+044D #CYRILLIC SMALL LETTER E
+0xFE U+044E #CYRILLIC SMALL LETTER YU
+0xFF U+044F #CYRILLIC SMALL LETTER YA
diff --git a/src/chrtrans/cp1252_uni.tbl b/src/chrtrans/cp1252_uni.tbl
new file mode 100644
index 0000000..50ce967
--- /dev/null
+++ b/src/chrtrans/cp1252_uni.tbl
@@ -0,0 +1,177 @@
+# This file has been modified for lynx (see README.tables)
+
+#Shall this become the "default" translation?
+#There has to be exactly one table marked as "default".
+D0
+#
+#The MIME name of this charset.
+Mwindows-1252
+
+#Name as a Display Charset (used on Options screen)
+OWestern (windows-1252)
+
+#Codepage number
+C1252
+
+#
+# Name: cp1252 to Unicode table
+# Unicode version: 2.0
+# Table version: 2.01
+# Table format: Format A
+# Date: 04/15/98
+#
+# Contact: cpxlate@microsoft.com
+#
+# General notes: none
+#
+# Format: Three tab-separated columns
+# Column #1 is the cp1252 code (in hex)
+# Column #2 is the Unicode (in hex as 0xXXXX)
+# Column #3 is the Unicode name (follows a comment sign, '#')
+#
+# The entries are in cp1252 order
+#
+##################
+# Lines with more than one Unicode (U+XXXX) value contain additional
+# replacement mappings added for lynx. - kw
+
+0x20-0x7e idem
+#
+0x80 U+20AC #EURO SIGN
+0x81 #UNDEFINED
+0x82 U+201A #SINGLE LOW-9 QUOTATION MARK
+0x83 U+0192 #LATIN SMALL LETTER F WITH HOOK
+0x84 U+201E #DOUBLE LOW-9 QUOTATION MARK
+0x85 U+2026 #HORIZONTAL ELLIPSIS
+0x86 U+2020 #DAGGER
+0x87 U+2021 #DOUBLE DAGGER
+0x88 U+02C6 U+0302 #MODIFIER LETTER CIRCUMFLEX ACCENT
+0x89 U+2030 #PER MILLE SIGN
+0x8A U+0160 #LATIN CAPITAL LETTER S WITH CARON
+0x8B U+2039 #SINGLE LEFT-POINTING ANGLE QUOTATION MARK
+0x8C U+0152 #LATIN CAPITAL LIGATURE OE
+0x8D #UNDEFINED
+0x8E U+017D #LATIN CAPITAL LETTER Z WITH CARON
+0x8F #UNDEFINED
+0x90 #UNDEFINED
+0x91 U+2018 #LEFT SINGLE QUOTATION MARK
+0x92 U+2019 #RIGHT SINGLE QUOTATION MARK
+0x93 U+201C #LEFT DOUBLE QUOTATION MARK
+0x94 U+201D U+02dd U+030b #RIGHT DOUBLE QUOTATION MARK
+0x95 U+2022 #BULLET
+0x96 U+2013 #EN DASH
+0x97 U+2014 #EM DASH
+0x98 U+02DC #SMALL TILDE
+0x99 U+2122 #TRADE MARK SIGN
+0x9A U+0161 #LATIN SMALL LETTER S WITH CARON
+0x9B U+203A #SINGLE RIGHT-POINTING ANGLE QUOTATION MARK
+0x9C U+0153 #LATIN SMALL LIGATURE OE
+0x9D #UNDEFINED
+0x9E U+017E #LATIN SMALL LETTER Z WITH CARON
+0x9F U+0178 #LATIN CAPITAL LETTER Y WITH DIAERESIS
+0xA0 U+00A0 #NO-BREAK SPACE
+0xA1 U+00A1 #INVERTED EXCLAMATION MARK
+0xA2 U+00A2 #CENT SIGN
+0xA3 U+00A3 #POUND SIGN
+0xA4 U+00A4 #CURRENCY SIGN
+0xA5 U+00A5 #YEN SIGN
+0xA6 U+00A6 #BROKEN BAR
+0xA7 U+00A7 #SECTION SIGN
+0xA8 U+00A8 U+0308 #DIAERESIS
+0xA9 U+00A9 #COPYRIGHT SIGN
+0xAA U+00AA #FEMININE ORDINAL INDICATOR
+0xAB U+00AB #LEFT-POINTING DOUBLE ANGLE QUOTATION MARK
+0xAC U+00AC #NOT SIGN
+0xAD U+00AD #SOFT HYPHEN
+0xAE U+00AE #REGISTERED SIGN
+0xAF U+00AF U+0304 #MACRON
+0xB0 U+00B0 U+030a #DEGREE SIGN
+0xB1 U+00B1 #PLUS-MINUS SIGN
+0xB2 U+00B2 #SUPERSCRIPT TWO
+0xB3 U+00B3 #SUPERSCRIPT THREE
+0xB4 U+00B4 #ACUTE ACCENT
+0xB5 U+00B5 U+03bc #MICRO SIGN
+0xB6 U+00B6 #PILCROW SIGN
+0xB7 U+00B7 U+0307 U+0387 U+2027 #MIDDLE DOT
+0xB8 U+00B8 U+0327 #CEDILLA
+0xB9 U+00B9 #SUPERSCRIPT ONE
+0xBA U+00BA #MASCULINE ORDINAL INDICATOR
+0xBB U+00BB #RIGHT-POINTING DOUBLE ANGLE QUOTATION MARK
+0xBC U+00BC #VULGAR FRACTION ONE QUARTER
+0xBD U+00BD #VULGAR FRACTION ONE HALF
+0xBE U+00BE #VULGAR FRACTION THREE QUARTERS
+0xBF U+00BF #INVERTED QUESTION MARK
+0xC0 U+00C0 #LATIN CAPITAL LETTER A WITH GRAVE
+0xC1 U+00C1 #LATIN CAPITAL LETTER A WITH ACUTE
+0xC2 U+00C2 #LATIN CAPITAL LETTER A WITH CIRCUMFLEX
+0xC3 U+00C3 #LATIN CAPITAL LETTER A WITH TILDE
+0xC4 U+00C4 #LATIN CAPITAL LETTER A WITH DIAERESIS
+0xC5 U+00C5 #LATIN CAPITAL LETTER A WITH RING ABOVE
+0xC6 U+00C6 #LATIN CAPITAL LETTER AE
+0xC7 U+00C7 #LATIN CAPITAL LETTER C WITH CEDILLA
+0xC8 U+00C8 #LATIN CAPITAL LETTER E WITH GRAVE
+0xC9 U+00C9 #LATIN CAPITAL LETTER E WITH ACUTE
+0xCA U+00CA #LATIN CAPITAL LETTER E WITH CIRCUMFLEX
+0xCB U+00CB #LATIN CAPITAL LETTER E WITH DIAERESIS
+0xCC U+00CC #LATIN CAPITAL LETTER I WITH GRAVE
+0xCD U+00CD #LATIN CAPITAL LETTER I WITH ACUTE
+0xCE U+00CE #LATIN CAPITAL LETTER I WITH CIRCUMFLEX
+0xCF U+00CF #LATIN CAPITAL LETTER I WITH DIAERESIS
+0xD0 U+00D0 #LATIN CAPITAL LETTER ETH
+0xD1 U+00D1 #LATIN CAPITAL LETTER N WITH TILDE
+0xD2 U+00D2 #LATIN CAPITAL LETTER O WITH GRAVE
+0xD3 U+00D3 #LATIN CAPITAL LETTER O WITH ACUTE
+0xD4 U+00D4 #LATIN CAPITAL LETTER O WITH CIRCUMFLEX
+0xD5 U+00D5 #LATIN CAPITAL LETTER O WITH TILDE
+0xD6 U+00D6 #LATIN CAPITAL LETTER O WITH DIAERESIS
+0xD7 U+00D7 #MULTIPLICATION SIGN
+0xD8 U+00D8 #LATIN CAPITAL LETTER O WITH STROKE
+0xD9 U+00D9 #LATIN CAPITAL LETTER U WITH GRAVE
+0xDA U+00DA #LATIN CAPITAL LETTER U WITH ACUTE
+0xDB U+00DB #LATIN CAPITAL LETTER U WITH CIRCUMFLEX
+0xDC U+00DC #LATIN CAPITAL LETTER U WITH DIAERESIS
+0xDD U+00DD #LATIN CAPITAL LETTER Y WITH ACUTE
+0xDE U+00DE #LATIN CAPITAL LETTER THORN
+0xDF U+00DF #LATIN SMALL LETTER SHARP S
+0xE0 U+00E0 #LATIN SMALL LETTER A WITH GRAVE
+0xE1 U+00E1 #LATIN SMALL LETTER A WITH ACUTE
+0xE2 U+00E2 #LATIN SMALL LETTER A WITH CIRCUMFLEX
+0xE3 U+00E3 #LATIN SMALL LETTER A WITH TILDE
+0xE4 U+00E4 #LATIN SMALL LETTER A WITH DIAERESIS
+0xE5 U+00E5 #LATIN SMALL LETTER A WITH RING ABOVE
+0xE6 U+00E6 #LATIN SMALL LETTER AE
+0xE7 U+00E7 #LATIN SMALL LETTER C WITH CEDILLA
+0xE8 U+00E8 #LATIN SMALL LETTER E WITH GRAVE
+0xE9 U+00E9 #LATIN SMALL LETTER E WITH ACUTE
+0xEA U+00EA #LATIN SMALL LETTER E WITH CIRCUMFLEX
+0xEB U+00EB #LATIN SMALL LETTER E WITH DIAERESIS
+0xEC U+00EC #LATIN SMALL LETTER I WITH GRAVE
+0xED U+00ED #LATIN SMALL LETTER I WITH ACUTE
+0xEE U+00EE #LATIN SMALL LETTER I WITH CIRCUMFLEX
+0xEF U+00EF #LATIN SMALL LETTER I WITH DIAERESIS
+0xF0 U+00F0 #LATIN SMALL LETTER ETH
+0xF1 U+00F1 #LATIN SMALL LETTER N WITH TILDE
+0xF2 U+00F2 #LATIN SMALL LETTER O WITH GRAVE
+0xF3 U+00F3 #LATIN SMALL LETTER O WITH ACUTE
+0xF4 U+00F4 #LATIN SMALL LETTER O WITH CIRCUMFLEX
+0xF5 U+00F5 #LATIN SMALL LETTER O WITH TILDE
+0xF6 U+00F6 #LATIN SMALL LETTER O WITH DIAERESIS
+0xF7 U+00F7 #DIVISION SIGN
+0xF8 U+00F8 #LATIN SMALL LETTER O WITH STROKE
+0xF9 U+00F9 #LATIN SMALL LETTER U WITH GRAVE
+0xFA U+00FA #LATIN SMALL LETTER U WITH ACUTE
+0xFB U+00FB #LATIN SMALL LETTER U WITH CIRCUMFLEX
+0xFC U+00FC #LATIN SMALL LETTER U WITH DIAERESIS
+0xFD U+00FD #LATIN SMALL LETTER Y WITH ACUTE
+0xFE U+00FE #LATIN SMALL LETTER THORN
+0xFF U+00FF #LATIN SMALL LETTER Y WITH DIAERESIS
+
+U+2218 " \260 " # RING OPERATOR
+U+2219 " \225 " # BULLET OPERATOR
+U+221b " ROOT\263 "
+U+2297 "(\327)" # CIRCLED TIMES
+U+2299 "(\267)" # CIRCLED DOT OPERATOR
+U+229A "(\260)" # CIRCLED RING OPERATOR
+U+22A0 "[\327]" # SQUARED TIMES
+U+22A1 "[\267]" # SQUARED DOT OPERATOR
+U+22C5 " \267 " # DOT OPERATOR
diff --git a/src/chrtrans/cp1253_uni.tbl b/src/chrtrans/cp1253_uni.tbl
new file mode 100644
index 0000000..49523d4
--- /dev/null
+++ b/src/chrtrans/cp1253_uni.tbl
@@ -0,0 +1,161 @@
+# This file has been modified for lynx (see README.tables)
+
+#The MIME name of this charset.
+Mwindows-1253
+
+#Name as a Display Charset (used on Options screen)
+OGreek (windows-1253)
+
+#Codepage number
+C1253
+
+#
+# Name: cp1253 to Unicode table
+# Unicode version: 2.0
+# Table version: 2.01
+# Table format: Format A
+# Date: 04/15/98
+#
+# Contact: cpxlate@microsoft.com
+#
+# General notes: none
+#
+# Format: Three tab-separated columns
+# Column #1 is the cp1253 code (in hex)
+# Column #2 is the Unicode (in hex as 0xXXXX)
+# Column #3 is the Unicode name (follows a comment sign, '#')
+#
+# The entries are in cp1253 order
+#
+##################
+
+0x20-0x7e idem
+#
+0x80 U+20AC #EURO SIGN
+0x81 #UNDEFINED
+0x82 U+201A #SINGLE LOW-9 QUOTATION MARK
+0x83 U+0192 #LATIN SMALL LETTER F WITH HOOK
+0x84 U+201E #DOUBLE LOW-9 QUOTATION MARK
+0x85 U+2026 #HORIZONTAL ELLIPSIS
+0x86 U+2020 #DAGGER
+0x87 U+2021 #DOUBLE DAGGER
+0x88 #UNDEFINED
+0x89 U+2030 #PER MILLE SIGN
+0x8A #UNDEFINED
+0x8B U+2039 #SINGLE LEFT-POINTING ANGLE QUOTATION MARK
+0x8C #UNDEFINED
+0x8D #UNDEFINED
+0x8E #UNDEFINED
+0x8F #UNDEFINED
+0x90 #UNDEFINED
+0x91 U+2018 U+02bd #LEFT SINGLE QUOTATION MARK
+0x92 U+2019 U+02bc #RIGHT SINGLE QUOTATION MARK
+0x93 U+201C #LEFT DOUBLE QUOTATION MARK
+0x94 U+201D #RIGHT DOUBLE QUOTATION MARK
+0x95 U+2022 #BULLET
+0x96 U+2013 #EN DASH
+0x97 U+2014 #EM DASH
+0x98 #UNDEFINED
+0x99 U+2122 #TRADE MARK SIGN
+0x9A #UNDEFINED
+0x9B U+203A #SINGLE RIGHT-POINTING ANGLE QUOTATION MARK
+0x9C #UNDEFINED
+0x9D #UNDEFINED
+0x9E #UNDEFINED
+0x9F #UNDEFINED
+0xA0 U+00A0 #NO-BREAK SPACE
+0xA1 U+0385 #GREEK DIALYTIKA TONOS
+0xA2 U+0386 #GREEK CAPITAL LETTER ALPHA WITH TONOS
+0xA3 U+00A3 #POUND SIGN
+0xA4 U+00A4 #CURRENCY SIGN
+0xA5 U+00A5 #YEN SIGN
+0xA6 U+00A6 #BROKEN BAR
+0xA7 U+00A7 #SECTION SIGN
+0xA8 U+00A8 #DIAERESIS
+0xA9 U+00A9 #COPYRIGHT SIGN
+0xAA #UNDEFINED
+0xAB U+00AB #LEFT-POINTING DOUBLE ANGLE QUOTATION MARK
+0xAC U+00AC #NOT SIGN
+0xAD U+00AD #SOFT HYPHEN
+0xAE U+00AE #REGISTERED SIGN
+0xAF U+2015 #HORIZONTAL BAR
+0xB0 U+00B0 #DEGREE SIGN
+0xB1 U+00B1 #PLUS-MINUS SIGN
+0xB2 U+00B2 #SUPERSCRIPT TWO
+0xB3 U+00B3 #SUPERSCRIPT THREE
+0xB4 U+0384 #GREEK TONOS
+0xB5 U+00B5 #MICRO SIGN
+0xB6 U+00B6 #PILCROW SIGN
+0xB7 U+00B7 #MIDDLE DOT
+0xB8 U+0388 #GREEK CAPITAL LETTER EPSILON WITH TONOS
+0xB9 U+0389 #GREEK CAPITAL LETTER ETA WITH TONOS
+0xBA U+038A #GREEK CAPITAL LETTER IOTA WITH TONOS
+0xBB U+00BB #RIGHT-POINTING DOUBLE ANGLE QUOTATION MARK
+0xBC U+038C #GREEK CAPITAL LETTER OMICRON WITH TONOS
+0xBD U+00BD #VULGAR FRACTION ONE HALF
+0xBE U+038E #GREEK CAPITAL LETTER UPSILON WITH TONOS
+0xBF U+038F #GREEK CAPITAL LETTER OMEGA WITH TONOS
+0xC0 U+0390 #GREEK SMALL LETTER IOTA WITH DIALYTIKA AND TONOS
+0xC1 U+0391 #GREEK CAPITAL LETTER ALPHA
+0xC2 U+0392 #GREEK CAPITAL LETTER BETA
+0xC3 U+0393 #GREEK CAPITAL LETTER GAMMA
+0xC4 U+0394 #GREEK CAPITAL LETTER DELTA
+0xC5 U+0395 #GREEK CAPITAL LETTER EPSILON
+0xC6 U+0396 #GREEK CAPITAL LETTER ZETA
+0xC7 U+0397 #GREEK CAPITAL LETTER ETA
+0xC8 U+0398 #GREEK CAPITAL LETTER THETA
+0xC9 U+0399 #GREEK CAPITAL LETTER IOTA
+0xCA U+039A #GREEK CAPITAL LETTER KAPPA
+0xCB U+039B #GREEK CAPITAL LETTER LAMDA
+0xCC U+039C #GREEK CAPITAL LETTER MU
+0xCD U+039D #GREEK CAPITAL LETTER NU
+0xCE U+039E #GREEK CAPITAL LETTER XI
+0xCF U+039F #GREEK CAPITAL LETTER OMICRON
+0xD0 U+03A0 #GREEK CAPITAL LETTER PI
+0xD1 U+03A1 #GREEK CAPITAL LETTER RHO
+0xD2 #UNDEFINED
+0xD3 U+03A3 #GREEK CAPITAL LETTER SIGMA
+0xD4 U+03A4 #GREEK CAPITAL LETTER TAU
+0xD5 U+03A5 #GREEK CAPITAL LETTER UPSILON
+0xD6 U+03A6 #GREEK CAPITAL LETTER PHI
+0xD7 U+03A7 #GREEK CAPITAL LETTER CHI
+0xD8 U+03A8 #GREEK CAPITAL LETTER PSI
+0xD9 U+03A9 #GREEK CAPITAL LETTER OMEGA
+0xDA U+03AA #GREEK CAPITAL LETTER IOTA WITH DIALYTIKA
+0xDB U+03AB #GREEK CAPITAL LETTER UPSILON WITH DIALYTIKA
+0xDC U+03AC #GREEK SMALL LETTER ALPHA WITH TONOS
+0xDD U+03AD #GREEK SMALL LETTER EPSILON WITH TONOS
+0xDE U+03AE #GREEK SMALL LETTER ETA WITH TONOS
+0xDF U+03AF #GREEK SMALL LETTER IOTA WITH TONOS
+0xE0 U+03B0 #GREEK SMALL LETTER UPSILON WITH DIALYTIKA AND TONOS
+0xE1 U+03B1 #GREEK SMALL LETTER ALPHA
+0xE2 U+03B2 #GREEK SMALL LETTER BETA
+0xE3 U+03B3 #GREEK SMALL LETTER GAMMA
+0xE4 U+03B4 #GREEK SMALL LETTER DELTA
+0xE5 U+03B5 #GREEK SMALL LETTER EPSILON
+0xE6 U+03B6 #GREEK SMALL LETTER ZETA
+0xE7 U+03B7 #GREEK SMALL LETTER ETA
+0xE8 U+03B8 #GREEK SMALL LETTER THETA
+0xE9 U+03B9 #GREEK SMALL LETTER IOTA
+0xEA U+03BA #GREEK SMALL LETTER KAPPA
+0xEB U+03BB #GREEK SMALL LETTER LAMDA
+0xEC U+03BC #GREEK SMALL LETTER MU
+0xED U+03BD #GREEK SMALL LETTER NU
+0xEE U+03BE #GREEK SMALL LETTER XI
+0xEF U+03BF #GREEK SMALL LETTER OMICRON
+0xF0 U+03C0 #GREEK SMALL LETTER PI
+0xF1 U+03C1 #GREEK SMALL LETTER RHO
+0xF2 U+03C2 #GREEK SMALL LETTER FINAL SIGMA
+0xF3 U+03C3 #GREEK SMALL LETTER SIGMA
+0xF4 U+03C4 #GREEK SMALL LETTER TAU
+0xF5 U+03C5 #GREEK SMALL LETTER UPSILON
+0xF6 U+03C6 #GREEK SMALL LETTER PHI
+0xF7 U+03C7 #GREEK SMALL LETTER CHI
+0xF8 U+03C8 #GREEK SMALL LETTER PSI
+0xF9 U+03C9 #GREEK SMALL LETTER OMEGA
+0xFA U+03CA #GREEK SMALL LETTER IOTA WITH DIALYTIKA
+0xFB U+03CB #GREEK SMALL LETTER UPSILON WITH DIALYTIKA
+0xFC U+03CC #GREEK SMALL LETTER OMICRON WITH TONOS
+0xFD U+03CD #GREEK SMALL LETTER UPSILON WITH TONOS
+0xFE U+03CE #GREEK SMALL LETTER OMEGA WITH TONOS
+0xFF #UNDEFINED
diff --git a/src/chrtrans/cp1255_uni.tbl b/src/chrtrans/cp1255_uni.tbl
new file mode 100644
index 0000000..3f0af9e
--- /dev/null
+++ b/src/chrtrans/cp1255_uni.tbl
@@ -0,0 +1,161 @@
+# This file has been modified for lynx (see README.tables)
+
+#The MIME name of this charset.
+Mwindows-1255
+
+#Name as a Display Charset (used on Options screen).
+OHebrew (windows-1255)
+
+#Codepage number
+C1255
+
+#
+# Name: cp1255 to Unicode table
+# Unicode version: 2.0
+# Table version: 2.01
+# Table format: Format A
+# Date: 04/15/98
+#
+# Contact: cpxlate@microsoft.com
+#
+# General notes: none
+#
+# Format: Three tab-separated columns
+# Column #1 is the cp1255 code (in hex)
+# Column #2 is the Unicode (in hex as 0xXXXX)
+# Column #3 is the Unicode name (follows a comment sign, '#')
+#
+# The entries are in cp1255 order
+#
+##################
+
+0x20-0x7e idem
+#
+0x80 U+20AC #EURO SIGN
+0x81 #UNDEFINED
+0x82 U+201A #SINGLE LOW-9 QUOTATION MARK
+0x83 U+0192 #LATIN SMALL LETTER F WITH HOOK
+0x84 U+201E #DOUBLE LOW-9 QUOTATION MARK
+0x85 U+2026 #HORIZONTAL ELLIPSIS
+0x86 U+2020 #DAGGER
+0x87 U+2021 #DOUBLE DAGGER
+0x88 U+02C6 #MODIFIER LETTER CIRCUMFLEX ACCENT
+0x89 U+2030 #PER MILLE SIGN
+0x8A #UNDEFINED
+0x8B U+2039 #SINGLE LEFT-POINTING ANGLE QUOTATION MARK
+0x8C #UNDEFINED
+0x8D #UNDEFINED
+0x8E #UNDEFINED
+0x8F #UNDEFINED
+0x90 #UNDEFINED
+0x91 U+2018 #LEFT SINGLE QUOTATION MARK
+0x92 U+2019 #RIGHT SINGLE QUOTATION MARK
+0x93 U+201C #LEFT DOUBLE QUOTATION MARK
+0x94 U+201D #RIGHT DOUBLE QUOTATION MARK
+0x95 U+2022 #BULLET
+0x96 U+2013 #EN DASH
+0x97 U+2014 #EM DASH
+0x98 U+02DC #SMALL TILDE
+0x99 U+2122 #TRADE MARK SIGN
+0x9A #UNDEFINED
+0x9B U+203A #SINGLE RIGHT-POINTING ANGLE QUOTATION MARK
+0x9C #UNDEFINED
+0x9D #UNDEFINED
+0x9E #UNDEFINED
+0x9F #UNDEFINED
+0xA0 U+00A0 #NO-BREAK SPACE
+0xA1 U+00A1 #INVERTED EXCLAMATION MARK
+0xA2 U+00A2 #CENT SIGN
+0xA3 U+00A3 #POUND SIGN
+0xA4 U+20AA #NEW SHEQEL SIGN
+0xA5 U+00A5 #YEN SIGN
+0xA6 U+00A6 #BROKEN BAR
+0xA7 U+00A7 #SECTION SIGN
+0xA8 U+00A8 #DIAERESIS
+0xA9 U+00A9 #COPYRIGHT SIGN
+0xAA U+00D7 #MULTIPLICATION SIGN
+0xAB U+00AB #LEFT-POINTING DOUBLE ANGLE QUOTATION MARK
+0xAC U+00AC #NOT SIGN
+0xAD U+00AD #SOFT HYPHEN
+0xAE U+00AE #REGISTERED SIGN
+0xAF U+00AF #MACRON
+0xB0 U+00B0 #DEGREE SIGN
+0xB1 U+00B1 #PLUS-MINUS SIGN
+0xB2 U+00B2 #SUPERSCRIPT TWO
+0xB3 U+00B3 #SUPERSCRIPT THREE
+0xB4 U+00B4 #ACUTE ACCENT
+0xB5 U+00B5 #MICRO SIGN
+0xB6 U+00B6 #PILCROW SIGN
+0xB7 U+00B7 #MIDDLE DOT
+0xB8 U+00B8 #CEDILLA
+0xB9 U+00B9 #SUPERSCRIPT ONE
+0xBA U+00F7 #DIVISION SIGN
+0xBB U+00BB #RIGHT-POINTING DOUBLE ANGLE QUOTATION MARK
+0xBC U+00BC #VULGAR FRACTION ONE QUARTER
+0xBD U+00BD #VULGAR FRACTION ONE HALF
+0xBE U+00BE #VULGAR FRACTION THREE QUARTERS
+0xBF U+00BF #INVERTED QUESTION MARK
+0xC0 U+05B0 #HEBREW POINT SHEVA
+0xC1 U+05B1 #HEBREW POINT HATAF SEGOL
+0xC2 U+05B2 #HEBREW POINT HATAF PATAH
+0xC3 U+05B3 #HEBREW POINT HATAF QAMATS
+0xC4 U+05B4 #HEBREW POINT HIRIQ
+0xC5 U+05B5 #HEBREW POINT TSERE
+0xC6 U+05B6 #HEBREW POINT SEGOL
+0xC7 U+05B7 #HEBREW POINT PATAH
+0xC8 U+05B8 #HEBREW POINT QAMATS
+0xC9 U+05B9 #HEBREW POINT HOLAM
+0xCA #UNDEFINED
+0xCB U+05BB #HEBREW POINT QUBUTS
+0xCC U+05BC #HEBREW POINT DAGESH OR MAPIQ
+0xCD U+05BD #HEBREW POINT METEG
+0xCE U+05BE #HEBREW PUNCTUATION MAQAF
+0xCF U+05BF #HEBREW POINT RAFE
+0xD0 U+05C0 #HEBREW PUNCTUATION PASEQ
+0xD1 U+05C1 #HEBREW POINT SHIN DOT
+0xD2 U+05C2 #HEBREW POINT SIN DOT
+0xD3 U+05C3 #HEBREW PUNCTUATION SOF PASUQ
+0xD4 U+05F0 #HEBREW LIGATURE YIDDISH DOUBLE VAV
+0xD5 U+05F1 #HEBREW LIGATURE YIDDISH VAV YOD
+0xD6 U+05F2 #HEBREW LIGATURE YIDDISH DOUBLE YOD
+0xD7 U+05F3 #HEBREW PUNCTUATION GERESH
+0xD8 U+05F4 #HEBREW PUNCTUATION GERSHAYIM
+0xD9 #UNDEFINED
+0xDA #UNDEFINED
+0xDB #UNDEFINED
+0xDC #UNDEFINED
+0xDD #UNDEFINED
+0xDE #UNDEFINED
+0xDF #UNDEFINED
+0xE0 U+05D0 #HEBREW LETTER ALEF
+0xE1 U+05D1 #HEBREW LETTER BET
+0xE2 U+05D2 #HEBREW LETTER GIMEL
+0xE3 U+05D3 #HEBREW LETTER DALET
+0xE4 U+05D4 #HEBREW LETTER HE
+0xE5 U+05D5 #HEBREW LETTER VAV
+0xE6 U+05D6 #HEBREW LETTER ZAYIN
+0xE7 U+05D7 #HEBREW LETTER HET
+0xE8 U+05D8 #HEBREW LETTER TET
+0xE9 U+05D9 #HEBREW LETTER YOD
+0xEA U+05DA #HEBREW LETTER FINAL KAF
+0xEB U+05DB #HEBREW LETTER KAF
+0xEC U+05DC #HEBREW LETTER LAMED
+0xED U+05DD #HEBREW LETTER FINAL MEM
+0xEE U+05DE #HEBREW LETTER MEM
+0xEF U+05DF #HEBREW LETTER FINAL NUN
+0xF0 U+05E0 #HEBREW LETTER NUN
+0xF1 U+05E1 #HEBREW LETTER SAMEKH
+0xF2 U+05E2 #HEBREW LETTER AYIN
+0xF3 U+05E3 #HEBREW LETTER FINAL PE
+0xF4 U+05E4 #HEBREW LETTER PE
+0xF5 U+05E5 #HEBREW LETTER FINAL TSADI
+0xF6 U+05E6 #HEBREW LETTER TSADI
+0xF7 U+05E7 #HEBREW LETTER QOF
+0xF8 U+05E8 #HEBREW LETTER RESH
+0xF9 U+05E9 #HEBREW LETTER SHIN
+0xFA U+05EA #HEBREW LETTER TAV
+0xFB #UNDEFINED
+0xFC #UNDEFINED
+0xFD U+200E #LEFT-TO-RIGHT MARK
+0xFE U+200F #RIGHT-TO-LEFT MARK
+0xFF #UNDEFINED
diff --git a/src/chrtrans/cp1256_uni.tbl b/src/chrtrans/cp1256_uni.tbl
new file mode 100644
index 0000000..1ab99c0
--- /dev/null
+++ b/src/chrtrans/cp1256_uni.tbl
@@ -0,0 +1,161 @@
+# This file has been modified for lynx (see README.tables)
+
+#The MIME name of this charset.
+Mwindows-1256
+
+#Name as a Display Charset (used on Options screen).
+OArabic (windows-1256)
+
+#Codepage number
+C1256
+
+#
+# Name: cp1256 to Unicode table
+# Unicode version: 2.1
+# Table version: 2.01
+# Table format: Format A
+# Date: 01/5/99
+#
+# Contact: cpxlate@microsoft.com
+#
+# General notes: none
+#
+# Format: Three tab-separated columns
+# Column #1 is the cp1256 code (in hex)
+# Column #2 is the Unicode (in hex as 0xXXXX)
+# Column #3 is the Unicode name (follows a comment sign, '#')
+#
+# The entries are in cp1256 order
+#
+##################
+
+0x20-0x7e idem
+#
+0x80 U+20AC #EURO SIGN
+0x81 U+067E #ARABIC LETTER PEH
+0x82 U+201A #SINGLE LOW-9 QUOTATION MARK
+0x83 U+0192 #LATIN SMALL LETTER F WITH HOOK
+0x84 U+201E #DOUBLE LOW-9 QUOTATION MARK
+0x85 U+2026 #HORIZONTAL ELLIPSIS
+0x86 U+2020 #DAGGER
+0x87 U+2021 #DOUBLE DAGGER
+0x88 U+02C6 #MODIFIER LETTER CIRCUMFLEX ACCENT
+0x89 U+2030 #PER MILLE SIGN
+0x8A U+0679 #ARABIC LETTER TTEH
+0x8B U+2039 #SINGLE LEFT-POINTING ANGLE QUOTATION MARK
+0x8C U+0152 #LATIN CAPITAL LIGATURE OE
+0x8D U+0686 #ARABIC LETTER TCHEH
+0x8E U+0698 #ARABIC LETTER JEH
+0x8F U+0688 #ARABIC LETTER DDAL
+0x90 U+06AF #ARABIC LETTER GAF
+0x91 U+2018 #LEFT SINGLE QUOTATION MARK
+0x92 U+2019 #RIGHT SINGLE QUOTATION MARK
+0x93 U+201C #LEFT DOUBLE QUOTATION MARK
+0x94 U+201D #RIGHT DOUBLE QUOTATION MARK
+0x95 U+2022 #BULLET
+0x96 U+2013 #EN DASH
+0x97 U+2014 #EM DASH
+0x98 U+06A9 #ARABIC LETTER KEHEH
+0x99 U+2122 #TRADE MARK SIGN
+0x9A U+0691 #ARABIC LETTER RREH
+0x9B U+203A #SINGLE RIGHT-POINTING ANGLE QUOTATION MARK
+0x9C U+0153 #LATIN SMALL LIGATURE OE
+0x9D U+200C #ZERO WIDTH NON-JOINER
+0x9E U+200D #ZERO WIDTH JOINER
+0x9F U+06BA #ARABIC LETTER NOON GHUNNA
+0xA0 U+00A0 #NO-BREAK SPACE
+0xA1 U+060C #ARABIC COMMA
+0xA2 U+00A2 #CENT SIGN
+0xA3 U+00A3 #POUND SIGN
+0xA4 U+00A4 #CURRENCY SIGN
+0xA5 U+00A5 #YEN SIGN
+0xA6 U+00A6 #BROKEN BAR
+0xA7 U+00A7 #SECTION SIGN
+0xA8 U+00A8 #DIAERESIS
+0xA9 U+00A9 #COPYRIGHT SIGN
+0xAA U+06BE #ARABIC LETTER HEH DOACHASHMEE
+0xAB U+00AB #LEFT-POINTING DOUBLE ANGLE QUOTATION MARK
+0xAC U+00AC #NOT SIGN
+0xAD U+00AD #SOFT HYPHEN
+0xAE U+00AE #REGISTERED SIGN
+0xAF U+00AF #MACRON
+0xB0 U+00B0 #DEGREE SIGN
+0xB1 U+00B1 #PLUS-MINUS SIGN
+0xB2 U+00B2 #SUPERSCRIPT TWO
+0xB3 U+00B3 #SUPERSCRIPT THREE
+0xB4 U+00B4 #ACUTE ACCENT
+0xB5 U+00B5 #MICRO SIGN
+0xB6 U+00B6 #PILCROW SIGN
+0xB7 U+00B7 #MIDDLE DOT
+0xB8 U+00B8 #CEDILLA
+0xB9 U+00B9 #SUPERSCRIPT ONE
+0xBA U+061B #ARABIC SEMICOLON
+0xBB U+00BB #RIGHT-POINTING DOUBLE ANGLE QUOTATION MARK
+0xBC U+00BC #VULGAR FRACTION ONE QUARTER
+0xBD U+00BD #VULGAR FRACTION ONE HALF
+0xBE U+00BE #VULGAR FRACTION THREE QUARTERS
+0xBF U+061F #ARABIC QUESTION MARK
+0xC0 U+06C1 #ARABIC LETTER HEH GOAL
+0xC1 U+0621 #ARABIC LETTER HAMZA
+0xC2 U+0622 #ARABIC LETTER ALEF WITH MADDA ABOVE
+0xC3 U+0623 #ARABIC LETTER ALEF WITH HAMZA ABOVE
+0xC4 U+0624 #ARABIC LETTER WAW WITH HAMZA ABOVE
+0xC5 U+0625 #ARABIC LETTER ALEF WITH HAMZA BELOW
+0xC6 U+0626 #ARABIC LETTER YEH WITH HAMZA ABOVE
+0xC7 U+0627 #ARABIC LETTER ALEF
+0xC8 U+0628 #ARABIC LETTER BEH
+0xC9 U+0629 #ARABIC LETTER TEH MARBUTA
+0xCA U+062A #ARABIC LETTER TEH
+0xCB U+062B #ARABIC LETTER THEH
+0xCC U+062C #ARABIC LETTER JEEM
+0xCD U+062D #ARABIC LETTER HAH
+0xCE U+062E #ARABIC LETTER KHAH
+0xCF U+062F #ARABIC LETTER DAL
+0xD0 U+0630 #ARABIC LETTER THAL
+0xD1 U+0631 #ARABIC LETTER REH
+0xD2 U+0632 #ARABIC LETTER ZAIN
+0xD3 U+0633 #ARABIC LETTER SEEN
+0xD4 U+0634 #ARABIC LETTER SHEEN
+0xD5 U+0635 #ARABIC LETTER SAD
+0xD6 U+0636 #ARABIC LETTER DAD
+0xD7 U+00D7 #MULTIPLICATION SIGN
+0xD8 U+0637 #ARABIC LETTER TAH
+0xD9 U+0638 #ARABIC LETTER ZAH
+0xDA U+0639 #ARABIC LETTER AIN
+0xDB U+063A #ARABIC LETTER GHAIN
+0xDC U+0640 #ARABIC TATWEEL
+0xDD U+0641 #ARABIC LETTER FEH
+0xDE U+0642 #ARABIC LETTER QAF
+0xDF U+0643 #ARABIC LETTER KAF
+0xE0 U+00E0 #LATIN SMALL LETTER A WITH GRAVE
+0xE1 U+0644 #ARABIC LETTER LAM
+0xE2 U+00E2 #LATIN SMALL LETTER A WITH CIRCUMFLEX
+0xE3 U+0645 #ARABIC LETTER MEEM
+0xE4 U+0646 #ARABIC LETTER NOON
+0xE5 U+0647 #ARABIC LETTER HEH
+0xE6 U+0648 #ARABIC LETTER WAW
+0xE7 U+00E7 #LATIN SMALL LETTER C WITH CEDILLA
+0xE8 U+00E8 #LATIN SMALL LETTER E WITH GRAVE
+0xE9 U+00E9 #LATIN SMALL LETTER E WITH ACUTE
+0xEA U+00EA #LATIN SMALL LETTER E WITH CIRCUMFLEX
+0xEB U+00EB #LATIN SMALL LETTER E WITH DIAERESIS
+0xEC U+0649 #ARABIC LETTER ALEF MAKSURA
+0xED U+064A #ARABIC LETTER YEH
+0xEE U+00EE #LATIN SMALL LETTER I WITH CIRCUMFLEX
+0xEF U+00EF #LATIN SMALL LETTER I WITH DIAERESIS
+0xF0 U+064B #ARABIC FATHATAN
+0xF1 U+064C #ARABIC DAMMATAN
+0xF2 U+064D #ARABIC KASRATAN
+0xF3 U+064E #ARABIC FATHA
+0xF4 U+00F4 #LATIN SMALL LETTER O WITH CIRCUMFLEX
+0xF5 U+064F #ARABIC DAMMA
+0xF6 U+0650 #ARABIC KASRA
+0xF7 U+00F7 #DIVISION SIGN
+0xF8 U+0651 #ARABIC SHADDA
+0xF9 U+00F9 #LATIN SMALL LETTER U WITH GRAVE
+0xFA U+0652 #ARABIC SUKUN
+0xFB U+00FB #LATIN SMALL LETTER U WITH CIRCUMFLEX
+0xFC U+00FC #LATIN SMALL LETTER U WITH DIAERESIS
+0xFD U+200E #LEFT-TO-RIGHT MARK
+0xFE U+200F #RIGHT-TO-LEFT MARK
+0xFF U+06D2 #ARABIC LETTER YEH BARREE
diff --git a/src/chrtrans/cp1257_uni.tbl b/src/chrtrans/cp1257_uni.tbl
new file mode 100644
index 0000000..6cd0081
--- /dev/null
+++ b/src/chrtrans/cp1257_uni.tbl
@@ -0,0 +1,162 @@
+# This file has been modified for lynx (see README.tables)
+
+#The MIME name of this charset.
+Mwindows-1257
+
+#Name as a Display Charset (used on Options screen)
+OBaltic Rim (windows-1257)
+
+#Codepage number
+C1257
+
+#
+# Name: cp1257 to Unicode table
+# Unicode version: 2.0
+# Table version: 2.01
+# Table format: Format A
+# Date: 04/15/98
+#
+# Contact: cpxlate@microsoft.com
+#
+# General notes: none
+#
+# Format: Three tab-separated columns
+# Column #1 is the cp1257 code (in hex)
+# Column #2 is the Unicode (in hex as 0xXXXX)
+# Column #3 is the Unicode name (follows a comment sign, '#')
+#
+# The entries are in cp1257 order
+#
+##################
+
+0x20-0x7e idem
+#
+0x80 U+20AC #EURO SIGN
+0x81 #UNDEFINED
+0x82 U+201A #SINGLE LOW-9 QUOTATION MARK
+0x83 #UNDEFINED
+0x84 U+201E #DOUBLE LOW-9 QUOTATION MARK
+0x85 U+2026 #HORIZONTAL ELLIPSIS
+0x86 U+2020 #DAGGER
+0x87 U+2021 #DOUBLE DAGGER
+0x88 #UNDEFINED
+0x89 U+2030 #PER MILLE SIGN
+0x8A #UNDEFINED
+0x8B U+2039 #SINGLE LEFT-POINTING ANGLE QUOTATION MARK
+0x8C #UNDEFINED
+0x8D U+00A8 #DIAERESIS
+0x8E U+02C7 #CARON
+0x8F U+00B8 #CEDILLA
+0x90 #UNDEFINED
+0x91 U+2018 #LEFT SINGLE QUOTATION MARK
+0x92 U+2019 #RIGHT SINGLE QUOTATION MARK
+0x93 U+201C #LEFT DOUBLE QUOTATION MARK
+0x94 U+201D #RIGHT DOUBLE QUOTATION MARK
+0x95 U+2022 #BULLET
+0x96 U+2013 #EN DASH
+0x97 U+2014 #EM DASH
+0x98 #UNDEFINED
+0x99 U+2122 #TRADE MARK SIGN
+0x9A #UNDEFINED
+0x9B U+203A #SINGLE RIGHT-POINTING ANGLE QUOTATION MARK
+0x9C #UNDEFINED
+0x9D U+00AF #MACRON
+0x9E U+02DB #OGONEK
+0x9F #UNDEFINED
+0xA0 U+00A0 #NO-BREAK SPACE
+0xA1 #UNDEFINED
+0xA2 U+00A2 #CENT SIGN
+0xA3 U+00A3 #POUND SIGN
+0xA4 U+00A4 #CURRENCY SIGN
+0xA5 #UNDEFINED
+0xA6 U+00A6 #BROKEN BAR
+0xA7 U+00A7 #SECTION SIGN
+0xA8 U+00D8 #LATIN CAPITAL LETTER O WITH STROKE
+0xA9 U+00A9 #COPYRIGHT SIGN
+0xAA U+0156 #LATIN CAPITAL LETTER R WITH CEDILLA
+0xAB U+00AB #LEFT-POINTING DOUBLE ANGLE QUOTATION MARK
+0xAC U+00AC #NOT SIGN
+0xAD U+00AD #SOFT HYPHEN
+0xAE U+00AE #REGISTERED SIGN
+0xAF U+00C6 #LATIN CAPITAL LETTER AE
+0xB0 U+00B0 #DEGREE SIGN
+0xB1 U+00B1 #PLUS-MINUS SIGN
+0xB2 U+00B2 #SUPERSCRIPT TWO
+0xB3 U+00B3 #SUPERSCRIPT THREE
+0xB4 U+00B4 #ACUTE ACCENT
+0xB5 U+00B5 #MICRO SIGN
+0xB6 U+00B6 #PILCROW SIGN
+0xB7 U+00B7 #MIDDLE DOT
+0xB8 U+00F8 #LATIN SMALL LETTER O WITH STROKE
+0xB9 U+00B9 #SUPERSCRIPT ONE
+0xBA U+0157 #LATIN SMALL LETTER R WITH CEDILLA
+0xBB U+00BB #RIGHT-POINTING DOUBLE ANGLE QUOTATION MARK
+0xBC U+00BC #VULGAR FRACTION ONE QUARTER
+0xBD U+00BD #VULGAR FRACTION ONE HALF
+0xBE U+00BE #VULGAR FRACTION THREE QUARTERS
+0xBF U+00E6 #LATIN SMALL LETTER AE
+0xC0 U+0104 #LATIN CAPITAL LETTER A WITH OGONEK
+0xC1 U+012E #LATIN CAPITAL LETTER I WITH OGONEK
+0xC2 U+0100 #LATIN CAPITAL LETTER A WITH MACRON
+0xC3 U+0106 #LATIN CAPITAL LETTER C WITH ACUTE
+0xC4 U+00C4 #LATIN CAPITAL LETTER A WITH DIAERESIS
+0xC5 U+00C5 #LATIN CAPITAL LETTER A WITH RING ABOVE
+0xC6 U+0118 #LATIN CAPITAL LETTER E WITH OGONEK
+0xC7 U+0112 #LATIN CAPITAL LETTER E WITH MACRON
+0xC8 U+010C #LATIN CAPITAL LETTER C WITH CARON
+0xC9 U+00C9 #LATIN CAPITAL LETTER E WITH ACUTE
+0xCA U+0179 #LATIN CAPITAL LETTER Z WITH ACUTE
+0xCB U+0116 #LATIN CAPITAL LETTER E WITH DOT ABOVE
+0xCC U+0122 #LATIN CAPITAL LETTER G WITH CEDILLA
+0xCD U+0136 #LATIN CAPITAL LETTER K WITH CEDILLA
+0xCE U+012A #LATIN CAPITAL LETTER I WITH MACRON
+0xCF U+013B #LATIN CAPITAL LETTER L WITH CEDILLA
+0xD0 U+0160 #LATIN CAPITAL LETTER S WITH CARON
+0xD1 U+0143 #LATIN CAPITAL LETTER N WITH ACUTE
+0xD2 U+0145 #LATIN CAPITAL LETTER N WITH CEDILLA
+0xD3 U+00D3 #LATIN CAPITAL LETTER O WITH ACUTE
+0xD4 U+014C #LATIN CAPITAL LETTER O WITH MACRON
+0xD5 U+00D5 #LATIN CAPITAL LETTER O WITH TILDE
+0xD6 U+00D6 #LATIN CAPITAL LETTER O WITH DIAERESIS
+0xD7 U+00D7 #MULTIPLICATION SIGN
+0xD8 U+0172 #LATIN CAPITAL LETTER U WITH OGONEK
+0xD9 U+0141 #LATIN CAPITAL LETTER L WITH STROKE
+0xDA U+015A #LATIN CAPITAL LETTER S WITH ACUTE
+0xDB U+016A #LATIN CAPITAL LETTER U WITH MACRON
+0xDC U+00DC #LATIN CAPITAL LETTER U WITH DIAERESIS
+0xDD U+017B #LATIN CAPITAL LETTER Z WITH DOT ABOVE
+0xDE U+017D #LATIN CAPITAL LETTER Z WITH CARON
+0xDF U+00DF #LATIN SMALL LETTER SHARP S
+0xE0 U+0105 #LATIN SMALL LETTER A WITH OGONEK
+0xE1 U+012F #LATIN SMALL LETTER I WITH OGONEK
+0xE2 U+0101 #LATIN SMALL LETTER A WITH MACRON
+0xE3 U+0107 #LATIN SMALL LETTER C WITH ACUTE
+0xE4 U+00E4 #LATIN SMALL LETTER A WITH DIAERESIS
+0xE5 U+00E5 #LATIN SMALL LETTER A WITH RING ABOVE
+0xE6 U+0119 #LATIN SMALL LETTER E WITH OGONEK
+0xE7 U+0113 #LATIN SMALL LETTER E WITH MACRON
+0xE8 U+010D #LATIN SMALL LETTER C WITH CARON
+0xE9 U+00E9 #LATIN SMALL LETTER E WITH ACUTE
+0xEA U+017A #LATIN SMALL LETTER Z WITH ACUTE
+0xEB U+0117 #LATIN SMALL LETTER E WITH DOT ABOVE
+0xEC U+0123 #LATIN SMALL LETTER G WITH CEDILLA
+0xED U+0137 #LATIN SMALL LETTER K WITH CEDILLA
+0xEE U+012B #LATIN SMALL LETTER I WITH MACRON
+0xEF U+013C #LATIN SMALL LETTER L WITH CEDILLA
+0xF0 U+0161 #LATIN SMALL LETTER S WITH CARON
+0xF1 U+0144 #LATIN SMALL LETTER N WITH ACUTE
+0xF2 U+0146 #LATIN SMALL LETTER N WITH CEDILLA
+0xF3 U+00F3 #LATIN SMALL LETTER O WITH ACUTE
+0xF4 U+014D #LATIN SMALL LETTER O WITH MACRON
+0xF5 U+00F5 #LATIN SMALL LETTER O WITH TILDE
+0xF6 U+00F6 #LATIN SMALL LETTER O WITH DIAERESIS
+0xF7 U+00F7 #DIVISION SIGN
+0xF8 U+0173 #LATIN SMALL LETTER U WITH OGONEK
+0xF9 U+0142 #LATIN SMALL LETTER L WITH STROKE
+0xFA U+015B #LATIN SMALL LETTER S WITH ACUTE
+0xFB U+016B #LATIN SMALL LETTER U WITH MACRON
+0xFC U+00FC #LATIN SMALL LETTER U WITH DIAERESIS
+0xFD U+017C #LATIN SMALL LETTER Z WITH DOT ABOVE
+0xFE U+017E #LATIN SMALL LETTER Z WITH CARON
+0xFF U+02D9 #DOT ABOVE
+
diff --git a/src/chrtrans/cp437_uni.tbl b/src/chrtrans/cp437_uni.tbl
new file mode 100644
index 0000000..4f45ce7
--- /dev/null
+++ b/src/chrtrans/cp437_uni.tbl
@@ -0,0 +1,181 @@
+# This file has been modified for lynx (see README.tables)
+
+#Shall this become the "default" translation?
+#There has to be exactly one table marked as "default".
+D0
+#
+#The MIME name of this charset.
+Mcp437
+
+#Name as a Display Charset (used on Options screen)
+OIBM PC US codepage (cp437)
+
+#Codepage number
+C437
+
+#
+# Name: cp437_DOSLatinUS to Unicode table
+# Unicode version: 2.0
+# Table version: 2.00
+# Table format: Format A
+# Date: 04/24/96
+# Authors: Lori Brownell <loribr@microsoft.com>
+# K.D. Chang <a-kchang@microsoft.com>
+# General notes: none
+#
+# Format: Three tab-separated columns
+# Column #1 is the cp437_DOSLatinUS code (in hex)
+# Column #2 is the Unicode (in hex as 0xXXXX)
+# Column #3 is the Unicode name (follows a comment sign, '#')
+#
+# The entries are in cp437_DosLatinUS order
+#
+# Lines with more than one Unicode (U+XXXX) value contain additional
+# replacement mappings added for lynx. - kw
+#
+#######################################
+
+0x20-0x7f idem
+#
+0x80 U+00c7 #LATIN CAPITAL LETTER C WITH CEDILLA
+0x81 U+00fc U+03cb #LATIN SMALL LETTER U WITH DIAERESIS
+0x82 U+00e9 #LATIN SMALL LETTER E WITH ACUTE
+0x83 U+00e2 #LATIN SMALL LETTER A WITH CIRCUMFLEX
+0x84 U+00e4 #LATIN SMALL LETTER A WITH DIAERESIS
+0x85 U+00e0 #LATIN SMALL LETTER A WITH GRAVE
+0x86 U+00e5 #LATIN SMALL LETTER A WITH RING ABOVE
+0x87 U+00e7 #LATIN SMALL LETTER C WITH CEDILLA
+0x88 U+00ea #LATIN SMALL LETTER E WITH CIRCUMFLEX
+0x89 U+00eb #LATIN SMALL LETTER E WITH DIAERESIS
+0x8a U+00e8 #LATIN SMALL LETTER E WITH GRAVE
+0x8b U+00ef U+03ca #LATIN SMALL LETTER I WITH DIAERESIS
+0x8c U+00ee #LATIN SMALL LETTER I WITH CIRCUMFLEX
+0x8d U+00ec #LATIN SMALL LETTER I WITH GRAVE
+0x8e U+00c4 #LATIN CAPITAL LETTER A WITH DIAERESIS
+0x8f U+00c5 #LATIN CAPITAL LETTER A WITH RING ABOVE
+0x90 U+00c9 U+0388 #LATIN CAPITAL LETTER E WITH ACUTE
+0x91 U+00e6 #LATIN SMALL LIGATURE AE
+0x92 U+00c6 #LATIN CAPITAL LIGATURE AE
+0x93 U+00f4 #LATIN SMALL LETTER O WITH CIRCUMFLEX
+0x94 U+00f6 #LATIN SMALL LETTER O WITH DIAERESIS
+0x95 U+00f2 #LATIN SMALL LETTER O WITH GRAVE
+0x96 U+00fb #LATIN SMALL LETTER U WITH CIRCUMFLEX
+0x97 U+00f9 #LATIN SMALL LETTER U WITH GRAVE
+0x98 U+00ff #LATIN SMALL LETTER Y WITH DIAERESIS
+0x99 U+00d6 #LATIN CAPITAL LETTER O WITH DIAERESIS
+0x9a U+00dc U+03ab #LATIN CAPITAL LETTER U WITH DIAERESIS
+0x9b U+00a2 #CENT SIGN
+0x9c U+00a3 #POUND SIGN
+0x9d U+00a5 #YEN SIGN
+0x9e U+20a7 #PESETA SIGN
+0x9f U+0192 #LATIN SMALL LETTER F WITH HOOK
+0xa0 U+00e1 U+03ac #LATIN SMALL LETTER A WITH ACUTE
+0xa1 U+00ed U+03af #LATIN SMALL LETTER I WITH ACUTE
+0xa2 U+00f3 U+03cc #LATIN SMALL LETTER O WITH ACUTE
+0xa3 U+00fa U+03cd #LATIN SMALL LETTER U WITH ACUTE
+0xa4 U+00f1 #LATIN SMALL LETTER N WITH TILDE
+0xa5 U+00d1 #LATIN CAPITAL LETTER N WITH TILDE
+0xa6 U+00aa #FEMININE ORDINAL INDICATOR
+0xa7 U+00ba #MASCULINE ORDINAL INDICATOR
+0xa8 U+00bf #INVERTED QUESTION MARK
+0xa9 U+2310 #REVERSED NOT SIGN
+0xaa U+00ac #NOT SIGN
+0xab U+00bd #VULGAR FRACTION ONE HALF
+0xac U+00bc #VULGAR FRACTION ONE QUARTER
+0xad U+00a1 #INVERTED EXCLAMATION MARK
+0xae U+00ab #LEFT-POINTING DOUBLE ANGLE QUOTATION MARK
+0xaf U+00bb #RIGHT-POINTING DOUBLE ANGLE QUOTATION MARK
+0xb0 U+2591 #LIGHT SHADE
+0xb1 U+2592 #MEDIUM SHADE
+0xb2 U+2593 #DARK SHADE
+0xb3 U+2502 #BOX DRAWINGS LIGHT VERTICAL
+0xb4 U+2524 #BOX DRAWINGS LIGHT VERTICAL AND LEFT
+0xb5 U+2561 #BOX DRAWINGS VERTICAL SINGLE AND LEFT DOUBLE
+0xb6 U+2562 #BOX DRAWINGS VERTICAL DOUBLE AND LEFT SINGLE
+0xb7 U+2556 #BOX DRAWINGS DOWN DOUBLE AND LEFT SINGLE
+0xb8 U+2555 #BOX DRAWINGS DOWN SINGLE AND LEFT DOUBLE
+0xb9 U+2563 #BOX DRAWINGS DOUBLE VERTICAL AND LEFT
+0xba U+2551 #BOX DRAWINGS DOUBLE VERTICAL
+0xbb U+2557 #BOX DRAWINGS DOUBLE DOWN AND LEFT
+0xbc U+255d #BOX DRAWINGS DOUBLE UP AND LEFT
+0xbd U+255c #BOX DRAWINGS UP DOUBLE AND LEFT SINGLE
+0xbe U+255b #BOX DRAWINGS UP SINGLE AND LEFT DOUBLE
+0xbf U+2510 #BOX DRAWINGS LIGHT DOWN AND LEFT
+0xc0 U+2514 #BOX DRAWINGS LIGHT UP AND RIGHT
+0xc1 U+2534 #BOX DRAWINGS LIGHT UP AND HORIZONTAL
+0xc2 U+252c #BOX DRAWINGS LIGHT DOWN AND HORIZONTAL
+0xc3 U+251c #BOX DRAWINGS LIGHT VERTICAL AND RIGHT
+0xc4 U+2500 #BOX DRAWINGS LIGHT HORIZONTAL
+0xc5 U+253c #BOX DRAWINGS LIGHT VERTICAL AND HORIZONTAL
+0xc6 U+255e #BOX DRAWINGS VERTICAL SINGLE AND RIGHT DOUBLE
+0xc7 U+255f #BOX DRAWINGS VERTICAL DOUBLE AND RIGHT SINGLE
+0xc8 U+255a #BOX DRAWINGS DOUBLE UP AND RIGHT
+0xc9 U+2554 #BOX DRAWINGS DOUBLE DOWN AND RIGHT
+0xca U+2569 #BOX DRAWINGS DOUBLE UP AND HORIZONTAL
+0xcb U+2566 #BOX DRAWINGS DOUBLE DOWN AND HORIZONTAL
+0xcc U+2560 #BOX DRAWINGS DOUBLE VERTICAL AND RIGHT
+0xcd U+2550 #BOX DRAWINGS DOUBLE HORIZONTAL
+0xce U+256c #BOX DRAWINGS DOUBLE VERTICAL AND HORIZONTAL
+0xcf U+2567 #BOX DRAWINGS UP SINGLE AND HORIZONTAL DOUBLE
+0xd0 U+2568 #BOX DRAWINGS UP DOUBLE AND HORIZONTAL SINGLE
+0xd1 U+2564 #BOX DRAWINGS DOWN SINGLE AND HORIZONTAL DOUBLE
+0xd2 U+2565 #BOX DRAWINGS DOWN DOUBLE AND HORIZONTAL SINGLE
+0xd3 U+2559 #BOX DRAWINGS UP DOUBLE AND RIGHT SINGLE
+0xd4 U+2558 #BOX DRAWINGS UP SINGLE AND RIGHT DOUBLE
+0xd5 U+2552 #BOX DRAWINGS DOWN SINGLE AND RIGHT DOUBLE
+0xd6 U+2553 #BOX DRAWINGS DOWN DOUBLE AND RIGHT SINGLE
+0xd7 U+256b #BOX DRAWINGS VERTICAL DOUBLE AND HORIZONTAL SINGLE
+0xd8 U+256a #BOX DRAWINGS VERTICAL SINGLE AND HORIZONTAL DOUBLE
+0xd9 U+2518 #BOX DRAWINGS LIGHT UP AND LEFT
+0xda U+250c #BOX DRAWINGS LIGHT DOWN AND RIGHT
+0xdb U+2588 #FULL BLOCK
+0xdc U+2584 #LOWER HALF BLOCK
+0xdd U+258c #LEFT HALF BLOCK
+0xde U+2590 #RIGHT HALF BLOCK
+0xdf U+2580 #UPPER HALF BLOCK
+0xe0 U+03b1 #GREEK SMALL LETTER ALPHA
+0xe1 U+00df U+03b2 #LATIN SMALL LETTER SHARP S
+0xe2 U+0393 #GREEK CAPITAL LETTER GAMMA
+0xe3 U+03c0 #GREEK SMALL LETTER PI
+0xe4 U+03a3 U+2211 #GREEK CAPITAL LETTER SIGMA
+0xe5 U+03c3 #GREEK SMALL LETTER SIGMA
+0xe6 U+00b5 U+03bc #MICRO SIGN
+0xe7 U+03c4 #GREEK SMALL LETTER TAU
+0xe8 U+03a6 #GREEK CAPITAL LETTER PHI
+0xe9 U+0398 U+03b8 #GREEK CAPITAL LETTER THETA
+0xea U+03a9 U+2126 #GREEK CAPITAL LETTER OMEGA
+0xeb U+03b4 #GREEK SMALL LETTER DELTA
+0xec U+221e #INFINITY
+0xed U+03c6 U+00f8 #GREEK SMALL LETTER PHI
+0xee U+03b5 U+2208 U+220a #GREEK SMALL LETTER EPSILON
+0xef U+2229 #INTERSECTION
+0xf0 U+2261 #IDENTICAL TO
+0xf1 U+00b1 #PLUS-MINUS SIGN
+0xf2 U+2265 U+2267 #GREATER-THAN OR EQUAL TO
+0xf3 U+2264 U+2266 #LESS-THAN OR EQUAL TO
+0xf4 U+2320 U+0283 #TOP HALF INTEGRAL
+0xf5 U+2321 #BOTTOM HALF INTEGRAL
+0xf6 U+00f7 #DIVISION SIGN
+0xf7 U+2248 #ALMOST EQUAL TO
+0xf8 U+00b0 U+030a #DEGREE SIGN
+0xf9 U+2219 U+0307 U+0387 #BULLET OPERATOR
+0xfa U+00b7 U+2027 #MIDDLE DOT
+0xfb U+221a #SQUARE ROOT
+0xfc U+207f #SUPERSCRIPT LATIN SMALL LETTER N
+0xfd U+00b2 #SUPERSCRIPT TWO
+0xfe U+25a0 #BLACK SQUARE
+0xff U+00a0 #NO-BREAK SPACE
+
+U+03ad "\356'" #:î'
+U+03ae:h'
+U+03cd:u'
+U+03ce:w'
+
+U+2209 " !\356 "
+U+221b " 3\373"
+U+221c " 4\373"
+U+2262 " !\360"
+U+2299 "(\372)"
+U+229a "(\370)"
+U+22a1 "[\372]"
+U+02a7 "t\364"
diff --git a/src/chrtrans/cp737_uni.tbl b/src/chrtrans/cp737_uni.tbl
new file mode 100644
index 0000000..e57e261
--- /dev/null
+++ b/src/chrtrans/cp737_uni.tbl
@@ -0,0 +1,172 @@
+# This file has been modified for lynx (see README.tables)
+
+#The MIME name of this charset.
+Mcp737
+
+#Name as a Display Charset (used on Options screen)
+OGreek (cp737)
+
+#Codepage number
+C737
+
+#
+# Name: cp737_DOSGreek to Unicode table
+# Unicode version: 2.0
+# Table version: 2.00
+# Table format: Format A
+# Date: 04/24/96
+# Authors: Lori Brownell <loribr@microsoft.com>
+# K.D. Chang <a-kchang@microsoft.com>
+# General notes: none
+#
+# Format: Three tab-separated columns
+# Column #1 is the cp737_DOSGreek code (in hex)
+# Column #2 is the Unicode (in hex as 0xXXXX)
+# Column #3 is the Unicode name (follows a comment sign, '#')
+#
+# The entries are in cp737_DOSGreek order
+#
+##################
+# Lines with more than one Unicode (U+XXXX) value contain additional
+# replacement mappings added for lynx. - kw
+
+0x20-0x7f idem
+#
+0x80 U+0391 #GREEK CAPITAL LETTER ALPHA
+0x81 U+0392 #GREEK CAPITAL LETTER BETA
+0x82 U+0393 #GREEK CAPITAL LETTER GAMMA
+0x83 U+0394 #GREEK CAPITAL LETTER DELTA
+0x84 U+0395 #GREEK CAPITAL LETTER EPSILON
+0x85 U+0396 #GREEK CAPITAL LETTER ZETA
+0x86 U+0397 #GREEK CAPITAL LETTER ETA
+0x87 U+0398 #GREEK CAPITAL LETTER THETA
+0x88 U+0399 #GREEK CAPITAL LETTER IOTA
+0x89 U+039a #GREEK CAPITAL LETTER KAPPA
+0x8a U+039b #GREEK CAPITAL LETTER LAMDA
+0x8b U+039c #GREEK CAPITAL LETTER MU
+0x8c U+039d #GREEK CAPITAL LETTER NU
+0x8d U+039e #GREEK CAPITAL LETTER XI
+0x8e U+039f #GREEK CAPITAL LETTER OMICRON
+0x8f U+03a0 #GREEK CAPITAL LETTER PI
+0x90 U+03a1 #GREEK CAPITAL LETTER RHO
+0x91 U+03a3 #GREEK CAPITAL LETTER SIGMA
+0x92 U+03a4 #GREEK CAPITAL LETTER TAU
+0x93 U+03a5 #GREEK CAPITAL LETTER UPSILON
+0x94 U+03a6 #GREEK CAPITAL LETTER PHI
+0x95 U+03a7 #GREEK CAPITAL LETTER CHI
+0x96 U+03a8 #GREEK CAPITAL LETTER PSI
+0x97 U+03a9 #GREEK CAPITAL LETTER OMEGA
+0x98 U+03b1 #GREEK SMALL LETTER ALPHA
+0x99 U+03b2 #GREEK SMALL LETTER BETA
+0x9a U+03b3 U+0263 #GREEK SMALL LETTER GAMMA
+0x9b U+03b4 #GREEK SMALL LETTER DELTA
+0x9c U+03b5 #GREEK SMALL LETTER EPSILON
+0x9d U+03b6 #GREEK SMALL LETTER ZETA
+0x9e U+03b7 #GREEK SMALL LETTER ETA
+0x9f U+03b8 #GREEK SMALL LETTER THETA
+0xa0 U+03b9 U+0131 #GREEK SMALL LETTER IOTA
+0xa1 U+03ba #GREEK SMALL LETTER KAPPA
+0xa2 U+03bb #GREEK SMALL LETTER LAMDA
+0xa3 U+03bc U+00b5 #GREEK SMALL LETTER MU
+0xa4 U+03bd #GREEK SMALL LETTER NU
+0xa5 U+03be #GREEK SMALL LETTER XI
+0xa6 U+03bf #GREEK SMALL LETTER OMICRON
+0xa7 U+03c0 #GREEK SMALL LETTER PI
+0xa8 U+03c1 #GREEK SMALL LETTER RHO
+0xa9 U+03c3 #GREEK SMALL LETTER SIGMA
+0xaa U+03c2 #GREEK SMALL LETTER FINAL SIGMA
+0xab U+03c4 #GREEK SMALL LETTER TAU
+0xac U+03c5 U+028a #GREEK SMALL LETTER UPSILON
+0xad U+03c6 #GREEK SMALL LETTER PHI
+0xae U+03c7 #GREEK SMALL LETTER CHI
+0xaf U+03c8 #GREEK SMALL LETTER PSI
+0xb0 U+2591 #LIGHT SHADE
+0xb1 U+2592 #MEDIUM SHADE
+0xb2 U+2593 #DARK SHADE
+0xb3 U+2502 #BOX DRAWINGS LIGHT VERTICAL
+0xb4 U+2524 #BOX DRAWINGS LIGHT VERTICAL AND LEFT
+0xb5 U+2561 #BOX DRAWINGS VERTICAL SINGLE AND LEFT DOUBLE
+0xb6 U+2562 #BOX DRAWINGS VERTICAL DOUBLE AND LEFT SINGLE
+0xb7 U+2556 #BOX DRAWINGS DOWN DOUBLE AND LEFT SINGLE
+0xb8 U+2555 #BOX DRAWINGS DOWN SINGLE AND LEFT DOUBLE
+0xb9 U+2563 #BOX DRAWINGS DOUBLE VERTICAL AND LEFT
+0xba U+2551 #BOX DRAWINGS DOUBLE VERTICAL
+0xbb U+2557 #BOX DRAWINGS DOUBLE DOWN AND LEFT
+0xbc U+255d #BOX DRAWINGS DOUBLE UP AND LEFT
+0xbd U+255c #BOX DRAWINGS UP DOUBLE AND LEFT SINGLE
+0xbe U+255b #BOX DRAWINGS UP SINGLE AND LEFT DOUBLE
+0xbf U+2510 #BOX DRAWINGS LIGHT DOWN AND LEFT
+0xc0 U+2514 #BOX DRAWINGS LIGHT UP AND RIGHT
+0xc1 U+2534 #BOX DRAWINGS LIGHT UP AND HORIZONTAL
+0xc2 U+252c #BOX DRAWINGS LIGHT DOWN AND HORIZONTAL
+0xc3 U+251c #BOX DRAWINGS LIGHT VERTICAL AND RIGHT
+0xc4 U+2500 #BOX DRAWINGS LIGHT HORIZONTAL
+0xc5 U+253c #BOX DRAWINGS LIGHT VERTICAL AND HORIZONTAL
+0xc6 U+255e #BOX DRAWINGS VERTICAL SINGLE AND RIGHT DOUBLE
+0xc7 U+255f #BOX DRAWINGS VERTICAL DOUBLE AND RIGHT SINGLE
+0xc8 U+255a #BOX DRAWINGS DOUBLE UP AND RIGHT
+0xc9 U+2554 #BOX DRAWINGS DOUBLE DOWN AND RIGHT
+0xca U+2569 #BOX DRAWINGS DOUBLE UP AND HORIZONTAL
+0xcb U+2566 #BOX DRAWINGS DOUBLE DOWN AND HORIZONTAL
+0xcc U+2560 #BOX DRAWINGS DOUBLE VERTICAL AND RIGHT
+0xcd U+2550 #BOX DRAWINGS DOUBLE HORIZONTAL
+0xce U+256c #BOX DRAWINGS DOUBLE VERTICAL AND HORIZONTAL
+0xcf U+2567 #BOX DRAWINGS UP SINGLE AND HORIZONTAL DOUBLE
+0xd0 U+2568 #BOX DRAWINGS UP DOUBLE AND HORIZONTAL SINGLE
+0xd1 U+2564 #BOX DRAWINGS DOWN SINGLE AND HORIZONTAL DOUBLE
+0xd2 U+2565 #BOX DRAWINGS DOWN DOUBLE AND HORIZONTAL SINGLE
+0xd3 U+2559 #BOX DRAWINGS UP DOUBLE AND RIGHT SINGLE
+0xd4 U+2558 #BOX DRAWINGS UP SINGLE AND RIGHT DOUBLE
+0xd5 U+2552 #BOX DRAWINGS DOWN SINGLE AND RIGHT DOUBLE
+0xd6 U+2553 #BOX DRAWINGS DOWN DOUBLE AND RIGHT SINGLE
+0xd7 U+256b #BOX DRAWINGS VERTICAL DOUBLE AND HORIZONTAL SINGLE
+0xd8 U+256a #BOX DRAWINGS VERTICAL SINGLE AND HORIZONTAL DOUBLE
+0xd9 U+2518 #BOX DRAWINGS LIGHT UP AND LEFT
+0xda U+250c #BOX DRAWINGS LIGHT DOWN AND RIGHT
+0xdb U+2588 #FULL BLOCK
+0xdc U+2584 #LOWER HALF BLOCK
+0xdd U+258c #LEFT HALF BLOCK
+0xde U+2590 #RIGHT HALF BLOCK
+0xdf U+2580 #UPPER HALF BLOCK
+0xe0 U+03c9 #GREEK SMALL LETTER OMEGA
+0xe1 U+03ac #GREEK SMALL LETTER ALPHA WITH TONOS
+0xe2 U+03ad #GREEK SMALL LETTER EPSILON WITH TONOS
+0xe3 U+03ae #GREEK SMALL LETTER ETA WITH TONOS
+0xe4 U+03ca #GREEK SMALL LETTER IOTA WITH DIALYTIKA
+0xe5 U+03af #GREEK SMALL LETTER IOTA WITH TONOS
+0xe6 U+03cc #GREEK SMALL LETTER OMICRON WITH TONOS
+0xe7 U+03cd #GREEK SMALL LETTER UPSILON WITH TONOS
+0xe8 U+03cb U+00fc #GREEK SMALL LETTER UPSILON WITH DIALYTIKA
+0xe9 U+03ce #GREEK SMALL LETTER OMEGA WITH TONOS
+0xea U+0386 #GREEK CAPITAL LETTER ALPHA WITH TONOS
+0xeb U+0388 #GREEK CAPITAL LETTER EPSILON WITH TONOS
+0xec U+0389 #GREEK CAPITAL LETTER ETA WITH TONOS
+0xed U+038a #GREEK CAPITAL LETTER IOTA WITH TONOS
+0xee U+038c #GREEK CAPITAL LETTER OMICRON WITH TONOS
+0xef U+038e #GREEK CAPITAL LETTER UPSILON WITH TONOS
+0xf0 U+038f #GREEK CAPITAL LETTER OMEGA WITH TONOS
+0xf1 U+00b1 #PLUS-MINUS SIGN
+0xf2 U+2265 #GREATER-THAN OR EQUAL TO
+0xf3 U+2264 #LESS-THAN OR EQUAL TO
+0xf4 U+03aa #GREEK CAPITAL LETTER IOTA WITH DIALYTIKA
+0xf5 U+03ab #GREEK CAPITAL LETTER UPSILON WITH DIALYTIKA
+0xf6 U+00f7 #DIVISION SIGN
+0xf7 U+2248 #ALMOST EQUAL TO
+0xf8 U+00b0 #DEGREE SIGN
+0xf9 U+2219 U+0307 U+0387 #BULLET OPERATOR
+0xfa U+00b7 #MIDDLE DOT
+0xfb U+221a #SQUARE ROOT
+0xfc U+207f #SUPERSCRIPT LATIN SMALL LETTER N
+0xfd U+00b2 #SUPERSCRIPT TWO
+0xfe U+25a0 #BLACK SQUARE
+0xff U+00a0 #NO-BREAK SPACE
+
+U+2209 " !\234 "
+U+2218 " \370 " # RING OPERATOR
+U+221b " 3\373"
+U+221c " 4\373"
+U+2299 "(\372)"
+U+229a "(\370)"
+U+22a1 "[\372]"
+U+02a4 "d\235"
+U+2249 "!\367"
diff --git a/src/chrtrans/cp775_uni.tbl b/src/chrtrans/cp775_uni.tbl
new file mode 100644
index 0000000..26a3ff7
--- /dev/null
+++ b/src/chrtrans/cp775_uni.tbl
@@ -0,0 +1,159 @@
+# This file has been modified for lynx (see README.tables)
+
+#The MIME name of this charset.
+Mcp775
+
+#Name as a Display Charset (used on Options screen)
+OBaltic Rim (cp775)
+
+#Codepage number
+C775
+
+# Name: cp775_DOSBaltRim to Unicode table
+# Unicode version: 2.0
+# Table version: 2.00
+# Table format: Format A
+# Date: 04/24/96
+# Authors: Lori Brownell <loribr@microsoft.com>
+# K.D. Chang <a-kchang@microsoft.com>
+# General notes: none
+#
+# Format: Three tab-separated columns
+# Column #1 is the cp775_DOSBaltRim code (in hex)
+# Column #2 is the Unicode (in hex as 0xXXXX)
+# Column #3 is the Unicode name (follows a comment sign, '#')
+#
+# The entries are in cp775_DOSBaltRim order
+#
+##################
+
+0x20-0x7e idem
+#
+0x80 U+0106 #LATIN CAPITAL LETTER C WITH ACUTE
+0x81 U+00fc #LATIN SMALL LETTER U WITH DIAERESIS
+0x82 U+00e9 #LATIN SMALL LETTER E WITH ACUTE
+0x83 U+0101 #LATIN SMALL LETTER A WITH MACRON
+0x84 U+00e4 #LATIN SMALL LETTER A WITH DIAERESIS
+0x85 U+0123 #LATIN SMALL LETTER G WITH CEDILLA
+0x86 U+00e5 #LATIN SMALL LETTER A WITH RING ABOVE
+0x87 U+0107 #LATIN SMALL LETTER C WITH ACUTE
+0x88 U+0142 #LATIN SMALL LETTER L WITH STROKE
+0x89 U+0113 #LATIN SMALL LETTER E WITH MACRON
+0x8a U+0156 #LATIN CAPITAL LETTER R WITH CEDILLA
+0x8b U+0157 #LATIN SMALL LETTER R WITH CEDILLA
+0x8c U+012b #LATIN SMALL LETTER I WITH MACRON
+0x8d U+0179 #LATIN CAPITAL LETTER Z WITH ACUTE
+0x8e U+00c4 #LATIN CAPITAL LETTER A WITH DIAERESIS
+0x8f U+00c5 #LATIN CAPITAL LETTER A WITH RING ABOVE
+0x90 U+00c9 #LATIN CAPITAL LETTER E WITH ACUTE
+0x91 U+00e6 #LATIN SMALL LIGATURE AE
+0x92 U+00c6 #LATIN CAPITAL LIGATURE AE
+0x93 U+014d #LATIN SMALL LETTER O WITH MACRON
+0x94 U+00f6 #LATIN SMALL LETTER O WITH DIAERESIS
+0x95 U+0122 #LATIN CAPITAL LETTER G WITH CEDILLA
+0x96 U+00a2 #CENT SIGN
+0x97 U+015a #LATIN CAPITAL LETTER S WITH ACUTE
+0x98 U+015b #LATIN SMALL LETTER S WITH ACUTE
+0x99 U+00d6 #LATIN CAPITAL LETTER O WITH DIAERESIS
+0x9a U+00dc #LATIN CAPITAL LETTER U WITH DIAERESIS
+0x9b U+00f8 #LATIN SMALL LETTER O WITH STROKE
+0x9c U+00a3 #POUND SIGN
+0x9d U+00d8 #LATIN CAPITAL LETTER O WITH STROKE
+0x9e U+00d7 #MULTIPLICATION SIGN
+0x9f U+00a4 #CURRENCY SIGN
+0xa0 U+0100 #LATIN CAPITAL LETTER A WITH MACRON
+0xa1 U+012a #LATIN CAPITAL LETTER I WITH MACRON
+0xa2 U+00f3 #LATIN SMALL LETTER O WITH ACUTE
+0xa3 U+017b #LATIN CAPITAL LETTER Z WITH DOT ABOVE
+0xa4 U+017c #LATIN SMALL LETTER Z WITH DOT ABOVE
+0xa5 U+017a #LATIN SMALL LETTER Z WITH ACUTE
+0xa6 U+201d #RIGHT DOUBLE QUOTATION MARK
+0xa7 U+00a6 #BROKEN BAR
+0xa8 U+00a9 #COPYRIGHT SIGN
+0xa9 U+00ae #REGISTERED SIGN
+0xaa U+00ac #NOT SIGN
+0xab U+00bd #VULGAR FRACTION ONE HALF
+0xac U+00bc #VULGAR FRACTION ONE QUARTER
+0xad U+0141 #LATIN CAPITAL LETTER L WITH STROKE
+0xae U+00ab #LEFT-POINTING DOUBLE ANGLE QUOTATION MARK
+0xaf U+00bb #RIGHT-POINTING DOUBLE ANGLE QUOTATION MARK
+0xb0 U+2591 #LIGHT SHADE
+0xb1 U+2592 #MEDIUM SHADE
+0xb2 U+2593 #DARK SHADE
+0xb3 U+2502 #BOX DRAWINGS LIGHT VERTICAL
+0xb4 U+2524 #BOX DRAWINGS LIGHT VERTICAL AND LEFT
+0xb5 U+0104 #LATIN CAPITAL LETTER A WITH OGONEK
+0xb6 U+010c #LATIN CAPITAL LETTER C WITH CARON
+0xb7 U+0118 #LATIN CAPITAL LETTER E WITH OGONEK
+0xb8 U+0116 #LATIN CAPITAL LETTER E WITH DOT ABOVE
+0xb9 U+2563 #BOX DRAWINGS DOUBLE VERTICAL AND LEFT
+0xba U+2551 #BOX DRAWINGS DOUBLE VERTICAL
+0xbb U+2557 #BOX DRAWINGS DOUBLE DOWN AND LEFT
+0xbc U+255d #BOX DRAWINGS DOUBLE UP AND LEFT
+0xbd U+012e #LATIN CAPITAL LETTER I WITH OGONEK
+0xbe U+0160 #LATIN CAPITAL LETTER S WITH CARON
+0xbf U+2510 #BOX DRAWINGS LIGHT DOWN AND LEFT
+0xc0 U+2514 #BOX DRAWINGS LIGHT UP AND RIGHT
+0xc1 U+2534 #BOX DRAWINGS LIGHT UP AND HORIZONTAL
+0xc2 U+252c #BOX DRAWINGS LIGHT DOWN AND HORIZONTAL
+0xc3 U+251c #BOX DRAWINGS LIGHT VERTICAL AND RIGHT
+0xc4 U+2500 #BOX DRAWINGS LIGHT HORIZONTAL
+0xc5 U+253c #BOX DRAWINGS LIGHT VERTICAL AND HORIZONTAL
+0xc6 U+0172 #LATIN CAPITAL LETTER U WITH OGONEK
+0xc7 U+016a #LATIN CAPITAL LETTER U WITH MACRON
+0xc8 U+255a #BOX DRAWINGS DOUBLE UP AND RIGHT
+0xc9 U+2554 #BOX DRAWINGS DOUBLE DOWN AND RIGHT
+0xca U+2569 #BOX DRAWINGS DOUBLE UP AND HORIZONTAL
+0xcb U+2566 #BOX DRAWINGS DOUBLE DOWN AND HORIZONTAL
+0xcc U+2560 #BOX DRAWINGS DOUBLE VERTICAL AND RIGHT
+0xcd U+2550 #BOX DRAWINGS DOUBLE HORIZONTAL
+0xce U+256c #BOX DRAWINGS DOUBLE VERTICAL AND HORIZONTAL
+0xcf U+017d #LATIN CAPITAL LETTER Z WITH CARON
+0xd0 U+0105 #LATIN SMALL LETTER A WITH OGONEK
+0xd1 U+010d #LATIN SMALL LETTER C WITH CARON
+0xd2 U+0119 #LATIN SMALL LETTER E WITH OGONEK
+0xd3 U+0117 #LATIN SMALL LETTER E WITH DOT ABOVE
+0xd4 U+012f #LATIN SMALL LETTER I WITH OGONEK
+0xd5 U+0161 #LATIN SMALL LETTER S WITH CARON
+0xd6 U+0173 #LATIN SMALL LETTER U WITH OGONEK
+0xd7 U+016b #LATIN SMALL LETTER U WITH MACRON
+0xd8 U+017e #LATIN SMALL LETTER Z WITH CARON
+0xd9 U+2518 #BOX DRAWINGS LIGHT UP AND LEFT
+0xda U+250c #BOX DRAWINGS LIGHT DOWN AND RIGHT
+0xdb U+2588 #FULL BLOCK
+0xdc U+2584 #LOWER HALF BLOCK
+0xdd U+258c #LEFT HALF BLOCK
+0xde U+2590 #RIGHT HALF BLOCK
+0xdf U+2580 #UPPER HALF BLOCK
+0xe0 U+00d3 #LATIN CAPITAL LETTER O WITH ACUTE
+0xe1 U+00df #LATIN SMALL LETTER SHARP S (GERMAN)
+0xe2 U+014c #LATIN CAPITAL LETTER O WITH MACRON
+0xe3 U+0143 #LATIN CAPITAL LETTER N WITH ACUTE
+0xe4 U+00f5 #LATIN SMALL LETTER O WITH TILDE
+0xe5 U+00d5 #LATIN CAPITAL LETTER O WITH TILDE
+0xe6 U+00b5 #MICRO SIGN
+0xe7 U+0144 #LATIN SMALL LETTER N WITH ACUTE
+0xe8 U+0136 #LATIN CAPITAL LETTER K WITH CEDILLA
+0xe9 U+0137 #LATIN SMALL LETTER K WITH CEDILLA
+0xea U+013b #LATIN CAPITAL LETTER L WITH CEDILLA
+0xeb U+013c #LATIN SMALL LETTER L WITH CEDILLA
+0xec U+0146 #LATIN SMALL LETTER N WITH CEDILLA
+0xed U+0112 #LATIN CAPITAL LETTER E WITH MACRON
+0xee U+0145 #LATIN CAPITAL LETTER N WITH CEDILLA
+0xef U+2019 #RIGHT SINGLE QUOTATION MARK
+0xf0 U+00ad #SOFT HYPHEN
+0xf1 U+00b1 #PLUS-MINUS SIGN
+0xf2 U+201c #LEFT DOUBLE QUOTATION MARK
+0xf3 U+00be #VULGAR FRACTION THREE QUARTERS
+0xf4 U+00b6 #PILCROW SIGN
+0xf5 U+00a7 #SECTION SIGN
+0xf6 U+00f7 #DIVISION SIGN
+0xf7 U+201e #DOUBLE LOW-9 QUOTATION MARK
+0xf8 U+00b0 #DEGREE SIGN
+0xf9 U+2219 #BULLET OPERATOR
+0xfa U+00b7 #MIDDLE DOT
+0xfb U+00b9 #SUPERSCRIPT ONE
+0xfc U+00b3 #SUPERSCRIPT THREE
+0xfd U+00b2 #SUPERSCRIPT TWO
+0xfe U+25a0 #BLACK SQUARE
+0xff U+00a0 #NO-BREAK SPACE
diff --git a/src/chrtrans/cp850_uni.tbl b/src/chrtrans/cp850_uni.tbl
new file mode 100644
index 0000000..9d05af2
--- /dev/null
+++ b/src/chrtrans/cp850_uni.tbl
@@ -0,0 +1,177 @@
+# This file has been modified for lynx (see README.tables)
+
+#Shall this become the "default" translation?
+#Meaning of that is currently unclear... It's different
+#from the default input or default output charset...
+#but there has to be exactly one table marked as "default".
+D0
+#
+#The MIME name of this charset.
+Mcp850
+
+#Name as a Display Charset (used on Options screen)
+OWestern (cp850)
+
+#Codepage number
+C850
+
+#
+# Name: cp850_DOSLatin1 to Unicode table
+# Unicode version: 2.0
+# Table version: 2.00
+# Table format: Format A
+# Date: 04/24/96
+# Authors: Lori Brownell <loribr@microsoft.com>
+# K.D. Chang <a-kchang@microsoft.com>
+# General notes: none
+#
+# Format: Three tab-separated columns
+# Column #1 is the cp850_DOSLatin1 code (in hex)
+# Column #2 is the Unicode (in hex as 0xXXXX)
+# Column #3 is the Unicode name (follows a comment sign, '#')
+#
+# The entries are in cp850_DOSLatin1 order
+#
+##################
+# Lines with more than one Unicode (U+XXXX) value contain additional
+# replacement mappings added for lynx. - kw
+
+0x20-0x7e idem
+#
+0x80 U+00c7 #LATIN CAPITAL LETTER C WITH CEDILLA
+0x81 U+00fc U+03cb #LATIN SMALL LETTER U WITH DIAERESIS
+0x82 U+00e9 U+03ad #LATIN SMALL LETTER E WITH ACUTE
+0x83 U+00e2 #LATIN SMALL LETTER A WITH CIRCUMFLEX
+0x84 U+00e4 #LATIN SMALL LETTER A WITH DIAERESIS
+0x85 U+00e0 #LATIN SMALL LETTER A WITH GRAVE
+0x86 U+00e5 #LATIN SMALL LETTER A WITH RING ABOVE
+0x87 U+00e7 #LATIN SMALL LETTER C WITH CEDILLA
+0x88 U+00ea #LATIN SMALL LETTER E WITH CIRCUMFLEX
+0x89 U+00eb #LATIN SMALL LETTER E WITH DIAERESIS
+0x8a U+00e8 #LATIN SMALL LETTER E WITH GRAVE
+0x8b U+00ef U+03ca #LATIN SMALL LETTER I WITH DIAERESIS
+0x8c U+00ee #LATIN SMALL LETTER I WITH CIRCUMFLEX
+0x8d U+00ec #LATIN SMALL LETTER I WITH GRAVE
+0x8e U+00c4 #LATIN CAPITAL LETTER A WITH DIAERESIS
+0x8f U+00c5 #LATIN CAPITAL LETTER A WITH RING ABOVE
+0x90 U+00c9 U+0388 #LATIN CAPITAL LETTER E WITH ACUTE
+0x91 U+00e6 #LATIN SMALL LIGATURE AE
+0x92 U+00c6 #LATIN CAPITAL LIGATURE AE
+0x93 U+00f4 #LATIN SMALL LETTER O WITH CIRCUMFLEX
+0x94 U+00f6 #LATIN SMALL LETTER O WITH DIAERESIS
+0x95 U+00f2 #LATIN SMALL LETTER O WITH GRAVE
+0x96 U+00fb #LATIN SMALL LETTER U WITH CIRCUMFLEX
+0x97 U+00f9 #LATIN SMALL LETTER U WITH GRAVE
+0x98 U+00ff #LATIN SMALL LETTER Y WITH DIAERESIS
+0x99 U+00d6 #LATIN CAPITAL LETTER O WITH DIAERESIS
+0x9a U+00dc U+03ab #LATIN CAPITAL LETTER U WITH DIAERESIS
+0x9b U+00f8 #LATIN SMALL LETTER O WITH STROKE
+0x9c U+00a3 #POUND SIGN
+0x9d U+00d8 #LATIN CAPITAL LETTER O WITH STROKE
+0x9e U+00d7 #MULTIPLICATION SIGN
+0x9f U+0192 #LATIN SMALL LETTER F WITH HOOK
+0xa0 U+00e1 U+03ac #LATIN SMALL LETTER A WITH ACUTE
+0xa1 U+00ed U+03af #LATIN SMALL LETTER I WITH ACUTE
+0xa2 U+00f3 U+03cc #LATIN SMALL LETTER O WITH ACUTE
+0xa3 U+00fa U+03cd #LATIN SMALL LETTER U WITH ACUTE
+0xa4 U+00f1 #LATIN SMALL LETTER N WITH TILDE
+0xa5 U+00d1 #LATIN CAPITAL LETTER N WITH TILDE
+0xa6 U+00aa #FEMININE ORDINAL INDICATOR
+0xa7 U+00ba #MASCULINE ORDINAL INDICATOR
+0xa8 U+00bf #INVERTED QUESTION MARK
+0xa9 U+00ae #REGISTERED SIGN
+0xaa U+00ac #NOT SIGN
+0xab U+00bd #VULGAR FRACTION ONE HALF
+0xac U+00bc #VULGAR FRACTION ONE QUARTER
+0xad U+00a1 #INVERTED EXCLAMATION MARK
+0xae U+00ab #LEFT-POINTING DOUBLE ANGLE QUOTATION MARK
+0xaf U+00bb #RIGHT-POINTING DOUBLE ANGLE QUOTATION MARK
+0xb0 U+2591 #LIGHT SHADE
+0xb1 U+2592 #MEDIUM SHADE
+0xb2 U+2593 #DARK SHADE
+0xb3 U+2502 #BOX DRAWINGS LIGHT VERTICAL
+0xb4 U+2524 #BOX DRAWINGS LIGHT VERTICAL AND LEFT
+0xb5 U+00c1 U+0386 #LATIN CAPITAL LETTER A WITH ACUTE
+0xb6 U+00c2 #LATIN CAPITAL LETTER A WITH CIRCUMFLEX
+0xb7 U+00c0 #LATIN CAPITAL LETTER A WITH GRAVE
+0xb8 U+00a9 #COPYRIGHT SIGN
+0xb9 U+2563 #BOX DRAWINGS DOUBLE VERTICAL AND LEFT
+0xba U+2551 #BOX DRAWINGS DOUBLE VERTICAL
+0xbb U+2557 #BOX DRAWINGS DOUBLE DOWN AND LEFT
+0xbc U+255d #BOX DRAWINGS DOUBLE UP AND LEFT
+0xbd U+00a2 #CENT SIGN
+0xbe U+00a5 #YEN SIGN
+0xbf U+2510 #BOX DRAWINGS LIGHT DOWN AND LEFT
+0xc0 U+2514 #BOX DRAWINGS LIGHT UP AND RIGHT
+0xc1 U+2534 #BOX DRAWINGS LIGHT UP AND HORIZONTAL
+0xc2 U+252c #BOX DRAWINGS LIGHT DOWN AND HORIZONTAL
+0xc3 U+251c #BOX DRAWINGS LIGHT VERTICAL AND RIGHT
+0xc4 U+2500 #BOX DRAWINGS LIGHT HORIZONTAL
+0xc5 U+253c #BOX DRAWINGS LIGHT VERTICAL AND HORIZONTAL
+0xc6 U+00e3 #LATIN SMALL LETTER A WITH TILDE
+0xc7 U+00c3 #LATIN CAPITAL LETTER A WITH TILDE
+0xc8 U+255a #BOX DRAWINGS DOUBLE UP AND RIGHT
+0xc9 U+2554 #BOX DRAWINGS DOUBLE DOWN AND RIGHT
+0xca U+2569 #BOX DRAWINGS DOUBLE UP AND HORIZONTAL
+0xcb U+2566 #BOX DRAWINGS DOUBLE DOWN AND HORIZONTAL
+0xcc U+2560 #BOX DRAWINGS DOUBLE VERTICAL AND RIGHT
+0xcd U+2550 #BOX DRAWINGS DOUBLE HORIZONTAL
+0xce U+256c #BOX DRAWINGS DOUBLE VERTICAL AND HORIZONTAL
+0xcf U+00a4 #CURRENCY SIGN
+0xd0 U+00f0 #LATIN SMALL LETTER ETH
+0xd1 U+00d0 #LATIN CAPITAL LETTER ETH
+0xd2 U+00ca #LATIN CAPITAL LETTER E WITH CIRCUMFLEX
+0xd3 U+00cb #LATIN CAPITAL LETTER E WITH DIAERESIS
+0xd4 U+00c8 #LATIN CAPITAL LETTER E WITH GRAVE
+0xd5 U+0131 U+03b9 #LATIN SMALL LETTER DOTLESS I
+0xd6 U+00cd U+038a #LATIN CAPITAL LETTER I WITH ACUTE
+0xd7 U+00ce #LATIN CAPITAL LETTER I WITH CIRCUMFLEX
+0xd8 U+00cf U+03aa #LATIN CAPITAL LETTER I WITH DIAERESIS
+0xd9 U+2518 #BOX DRAWINGS LIGHT UP AND LEFT
+0xda U+250c #BOX DRAWINGS LIGHT DOWN AND RIGHT
+0xdb U+2588 #FULL BLOCK
+0xdc U+2584 #LOWER HALF BLOCK
+0xdd U+00a6 #BROKEN BAR
+0xde U+00cc #LATIN CAPITAL LETTER I WITH GRAVE
+0xdf U+2580 #UPPER HALF BLOCK
+0xe0 U+00d3 U+038c #LATIN CAPITAL LETTER O WITH ACUTE
+0xe1 U+00df #LATIN SMALL LETTER SHARP S
+0xe2 U+00d4 #LATIN CAPITAL LETTER O WITH CIRCUMFLEX
+0xe3 U+00d2 #LATIN CAPITAL LETTER O WITH GRAVE
+0xe4 U+00f5 #LATIN SMALL LETTER O WITH TILDE
+0xe5 U+00d5 #LATIN CAPITAL LETTER O WITH TILDE
+0xe6 U+00b5 U+03bc #MICRO SIGN
+0xe7 U+00fe #LATIN SMALL LETTER THORN
+0xe8 U+00de #LATIN CAPITAL LETTER THORN
+0xe9 U+00da #LATIN CAPITAL LETTER U WITH ACUTE
+0xea U+00db #LATIN CAPITAL LETTER U WITH CIRCUMFLEX
+0xeb U+00d9 #LATIN CAPITAL LETTER U WITH GRAVE
+0xec U+00fd #LATIN SMALL LETTER Y WITH ACUTE
+0xed U+00dd #LATIN CAPITAL LETTER Y WITH ACUTE
+0xee U+00af U+0304 #MACRON
+0xef U+00b4 U+0301 #ACUTE ACCENT
+0xf0 U+00ad #SOFT HYPHEN
+0xf1 U+00b1 #PLUS-MINUS SIGN
+0xf2 U+2017 U+0333 #DOUBLE LOW LINE
+0xf3 U+00be #VULGAR FRACTION THREE QUARTERS
+0xf4 U+00b6 #PILCROW SIGN
+0xf5 U+00a7 #SECTION SIGN
+0xf6 U+00f7 #DIVISION SIGN
+0xf7 U+00b8 U+0327 #CEDILLA
+0xf8 U+00b0 U+030a #DEGREE SIGN
+0xf9 U+00a8 U+0308 #DIAERESIS
+0xfa U+00b7 U+0307 U+0387 U+2027 #MIDDLE DOT
+0xfb U+00b9 #SUPERSCRIPT ONE
+0xfc U+00b3 #SUPERSCRIPT THREE
+0xfd U+00b2 #SUPERSCRIPT TWO
+0xfe U+25a0 #BLACK SQUARE
+0xff U+00a0 #NO-BREAK SPACE
+
+U+2218 " \370 " # RING OPERATOR
+U+221b " ROOT\374 "
+U+2297 "(\236)" # CIRCLED TIMES
+U+2299 "(\372)" # CIRCLED DOT OPERATOR
+U+229A "(\370)" # CIRCLED RING OPERATOR
+U+22A0 "[\236]" # SQUARED TIMES
+U+22A1 "[\372]" # SQUARED DOT OPERATOR
+U+22C5 " \372 " # DOT OPERATOR
diff --git a/src/chrtrans/cp852_uni.tbl b/src/chrtrans/cp852_uni.tbl
new file mode 100644
index 0000000..978cca4
--- /dev/null
+++ b/src/chrtrans/cp852_uni.tbl
@@ -0,0 +1,170 @@
+# This file has been modified for lynx (see README.tables)
+
+#The MIME name of this charset.
+Mcp852
+
+#Name as a Display Charset (used on Options screen)
+OEastern European (cp852)
+
+#Codepage number
+C852
+
+#
+# Name: cp852_DOSLatin2 to Unicode table
+# Unicode version: 2.0
+# Table version: 2.00
+# Table format: Format A
+# Date: 04/24/96
+# Authors: Lori Brownell <loribr@microsoft.com>
+# K.D. Chang <a-kchang@microsoft.com>
+# General notes: none
+#
+# Format: Three tab-separated columns
+# Column #1 is the cp852_DOSLatin2 code (in hex)
+# Column #2 is the Unicode (in hex as 0xXXXX)
+# Column #3 is the Unicode name (follows a comment sign, '#')
+#
+# The entries are in cp852_DOSLatin2 order
+#
+##################
+# Lines with more than one Unicode (U+XXXX) value contain additional
+# replacement mappings added for lynx. - kw
+
+0x20-0x7e idem
+#
+0x80 U+00c7 #LATIN CAPITAL LETTER C WITH CEDILLA
+0x81 U+00fc U+03cb #LATIN SMALL LETTER U WITH DIAERESIS
+0x82 U+00e9 U+03ad #LATIN SMALL LETTER E WITH ACUTE
+0x83 U+00e2 #LATIN SMALL LETTER A WITH CIRCUMFLEX
+0x84 U+00e4 #LATIN SMALL LETTER A WITH DIAERESIS
+0x85 U+016f #LATIN SMALL LETTER U WITH RING ABOVE
+0x86 U+0107 #LATIN SMALL LETTER C WITH ACUTE
+0x87 U+00e7 #LATIN SMALL LETTER C WITH CEDILLA
+0x88 U+0142 #LATIN SMALL LETTER L WITH STROKE
+0x89 U+00eb #LATIN SMALL LETTER E WITH DIAERESIS
+0x8a U+0150 #LATIN CAPITAL LETTER O WITH DOUBLE ACUTE
+0x8b U+0151 #LATIN SMALL LETTER O WITH DOUBLE ACUTE
+0x8c U+00ee #LATIN SMALL LETTER I WITH CIRCUMFLEX
+0x8d U+0179 #LATIN CAPITAL LETTER Z WITH ACUTE
+0x8e U+00c4 #LATIN CAPITAL LETTER A WITH DIAERESIS
+0x8f U+0106 #LATIN CAPITAL LETTER C WITH ACUTE
+0x90 U+00c9 U+0388 #LATIN CAPITAL LETTER E WITH ACUTE
+0x91 U+0139 #LATIN CAPITAL LETTER L WITH ACUTE
+0x92 U+013a #LATIN SMALL LETTER L WITH ACUTE
+0x93 U+00f4 #LATIN SMALL LETTER O WITH CIRCUMFLEX
+0x94 U+00f6 #LATIN SMALL LETTER O WITH DIAERESIS
+0x95 U+013d #LATIN CAPITAL LETTER L WITH CARON
+0x96 U+013e #LATIN SMALL LETTER L WITH CARON
+0x97 U+015a #LATIN CAPITAL LETTER S WITH ACUTE
+0x98 U+015b #LATIN SMALL LETTER S WITH ACUTE
+0x99 U+00d6 #LATIN CAPITAL LETTER O WITH DIAERESIS
+0x9a U+00dc U+03ab #LATIN CAPITAL LETTER U WITH DIAERESIS
+0x9b U+0164 #LATIN CAPITAL LETTER T WITH CARON
+0x9c U+0165 #LATIN SMALL LETTER T WITH CARON
+0x9d U+0141 #LATIN CAPITAL LETTER L WITH STROKE
+0x9e U+00d7 #MULTIPLICATION SIGN
+0x9f U+010d U+02a7 U+0447 #LATIN SMALL LETTER C WITH CARON
+0xa0 U+00e1 U+03ac #LATIN SMALL LETTER A WITH ACUTE
+0xa1 U+00ed U+03af #LATIN SMALL LETTER I WITH ACUTE
+0xa2 U+00f3 U+03cc #LATIN SMALL LETTER O WITH ACUTE
+0xa3 U+00fa U+03cd #LATIN SMALL LETTER U WITH ACUTE
+0xa4 U+0104 #LATIN CAPITAL LETTER A WITH OGONEK
+0xa5 U+0105 #LATIN SMALL LETTER A WITH OGONEK
+0xa6 U+017d U+0416 #LATIN CAPITAL LETTER Z WITH CARON
+0xa7 U+017e U+0436 #LATIN SMALL LETTER Z WITH CARON
+0xa8 U+0118 #LATIN CAPITAL LETTER E WITH OGONEK
+0xa9 U+0119 #LATIN SMALL LETTER E WITH OGONEK
+0xaa U+00ac #NOT SIGN
+0xab U+017a #LATIN SMALL LETTER Z WITH ACUTE
+0xac U+010c U+0427 #LATIN CAPITAL LETTER C WITH CARON
+0xad U+015f #LATIN SMALL LETTER S WITH CEDILLA
+0xae U+00ab #LEFT-POINTING DOUBLE ANGLE QUOTATION MARK
+0xaf U+00bb #RIGHT-POINTING DOUBLE ANGLE QUOTATION MARK
+0xb0 U+2591 #LIGHT SHADE
+0xb1 U+2592 #MEDIUM SHADE
+0xb2 U+2593 #DARK SHADE
+0xb3 U+2502 #BOX DRAWINGS LIGHT VERTICAL
+0xb4 U+2524 #BOX DRAWINGS LIGHT VERTICAL AND LEFT
+0xb5 U+00c1 U+0386 #LATIN CAPITAL LETTER A WITH ACUTE
+0xb6 U+00c2 #LATIN CAPITAL LETTER A WITH CIRCUMFLEX
+0xb7 U+011a #LATIN CAPITAL LETTER E WITH CARON
+0xb8 U+015e #LATIN CAPITAL LETTER S WITH CEDILLA
+0xb9 U+2563 #BOX DRAWINGS DOUBLE VERTICAL AND LEFT
+0xba U+2551 #BOX DRAWINGS DOUBLE VERTICAL
+0xbb U+2557 #BOX DRAWINGS DOUBLE DOWN AND LEFT
+0xbc U+255d #BOX DRAWINGS DOUBLE UP AND LEFT
+0xbd U+017b #LATIN CAPITAL LETTER Z WITH DOT ABOVE
+0xbe U+017c #LATIN SMALL LETTER Z WITH DOT ABOVE
+0xbf U+2510 #BOX DRAWINGS LIGHT DOWN AND LEFT
+0xc0 U+2514 #BOX DRAWINGS LIGHT UP AND RIGHT
+0xc1 U+2534 #BOX DRAWINGS LIGHT UP AND HORIZONTAL
+0xc2 U+252c #BOX DRAWINGS LIGHT DOWN AND HORIZONTAL
+0xc3 U+251c #BOX DRAWINGS LIGHT VERTICAL AND RIGHT
+0xc4 U+2500 #BOX DRAWINGS LIGHT HORIZONTAL
+0xc5 U+253c #BOX DRAWINGS LIGHT VERTICAL AND HORIZONTAL
+0xc6 U+0102 #LATIN CAPITAL LETTER A WITH BREVE
+0xc7 U+0103 #LATIN SMALL LETTER A WITH BREVE
+0xc8 U+255a #BOX DRAWINGS DOUBLE UP AND RIGHT
+0xc9 U+2554 #BOX DRAWINGS DOUBLE DOWN AND RIGHT
+0xca U+2569 #BOX DRAWINGS DOUBLE UP AND HORIZONTAL
+0xcb U+2566 #BOX DRAWINGS DOUBLE DOWN AND HORIZONTAL
+0xcc U+2560 #BOX DRAWINGS DOUBLE VERTICAL AND RIGHT
+0xcd U+2550 #BOX DRAWINGS DOUBLE HORIZONTAL
+0xce U+256c #BOX DRAWINGS DOUBLE VERTICAL AND HORIZONTAL
+0xcf U+00a4 #CURRENCY SIGN
+0xd0 U+0111 #LATIN SMALL LETTER D WITH STROKE
+0xd1 U+0110 #LATIN CAPITAL LETTER D WITH STROKE
+0xd2 U+010e #LATIN CAPITAL LETTER D WITH CARON
+0xd3 U+00cb #LATIN CAPITAL LETTER E WITH DIAERESIS
+0xd4 U+010f #LATIN SMALL LETTER D WITH CARON
+0xd5 U+0147 #LATIN CAPITAL LETTER N WITH CARON
+0xd6 U+00cd U+038a #LATIN CAPITAL LETTER I WITH ACUTE
+0xd7 U+00ce #LATIN CAPITAL LETTER I WITH CIRCUMFLEX
+0xd8 U+011b #LATIN SMALL LETTER E WITH CARON
+0xd9 U+2518 #BOX DRAWINGS LIGHT UP AND LEFT
+0xda U+250c #BOX DRAWINGS LIGHT DOWN AND RIGHT
+0xdb U+2588 #FULL BLOCK
+0xdc U+2584 #LOWER HALF BLOCK
+0xdd U+0162 #LATIN CAPITAL LETTER T WITH CEDILLA
+0xde U+016e #LATIN CAPITAL LETTER U WITH RING ABOVE
+0xdf U+2580 #UPPER HALF BLOCK
+0xe0 U+00d3 U+038c #LATIN CAPITAL LETTER O WITH ACUTE
+0xe1 U+00df #LATIN SMALL LETTER SHARP S
+0xe2 U+00d4 #LATIN CAPITAL LETTER O WITH CIRCUMFLEX
+0xe3 U+0143 #LATIN CAPITAL LETTER N WITH ACUTE
+0xe4 U+0144 #LATIN SMALL LETTER N WITH ACUTE
+0xe5 U+0148 #LATIN SMALL LETTER N WITH CARON
+0xe6 U+0160 U+0428 #LATIN CAPITAL LETTER S WITH CARON
+0xe7 U+0161 U+0448 #LATIN SMALL LETTER S WITH CARON
+0xe8 U+0154 #LATIN CAPITAL LETTER R WITH ACUTE
+0xe9 U+00da #LATIN CAPITAL LETTER U WITH ACUTE
+0xea U+0155 #LATIN SMALL LETTER R WITH ACUTE
+0xeb U+0170 #LATIN CAPITAL LETTER U WITH DOUBLE ACUTE
+0xec U+00fd #LATIN SMALL LETTER Y WITH ACUTE
+0xed U+00dd #LATIN CAPITAL LETTER Y WITH ACUTE
+0xee U+0163 #LATIN SMALL LETTER T WITH CEDILLA
+0xef U+00b4 U+0301 #ACUTE ACCENT
+0xf0 U+00ad #SOFT HYPHEN
+0xf1 U+02dd U+030b #DOUBLE ACUTE ACCENT
+0xf2 U+02db U+0328 #OGONEK
+0xf3 U+02c7 U+030c #CARON
+0xf4 U+02d8 U+0306 #BREVE
+0xf5 U+00a7 #SECTION SIGN
+0xf6 U+00f7 #DIVISION SIGN
+0xf7 U+00b8 U+0327 #CEDILLA
+0xf8 U+00b0 U+030a #DEGREE SIGN
+0xf9 U+00a8 U+0308 #DIAERESIS
+0xfa U+02d9 U+0307 U+0387 #DOT ABOVE
+0xfb U+0171 #LATIN SMALL LETTER U WITH DOUBLE ACUTE
+0xfc U+0158 #LATIN CAPITAL LETTER R WITH CARON
+0xfd U+0159 #LATIN SMALL LETTER R WITH CARON
+0xfe U+25a0 #BLACK SQUARE
+0xff U+00a0 #NO-BREAK SPACE
+
+U+2218 " \370 " # RING OPERATOR
+U+2297 "(\236)" # CIRCLED TIMES
+U+2299 "(\372)" # CIRCLED DOT OPERATOR
+U+229A "(\370)" # CIRCLED RING OPERATOR
+U+22A0 "[\236]" # SQUARED TIMES
+U+22A1 "[\372]" # SQUARED DOT OPERATOR
+U+22C5 " \372 " # DOT OPERATOR
diff --git a/src/chrtrans/cp857_uni.tbl b/src/chrtrans/cp857_uni.tbl
new file mode 100644
index 0000000..bd30996
--- /dev/null
+++ b/src/chrtrans/cp857_uni.tbl
@@ -0,0 +1,159 @@
+# This file has been modified for lynx (see README.tables)
+
+#The MIME name of this charset.
+Mcp857
+
+#Name as a Display Charset (used on Options screen).
+OTurkish (cp857)
+
+#Codepage number
+C857
+
+# Name: cp857_DOSTurkish to Unicode table
+# Unicode version: 2.0
+# Table version: 2.00
+# Table format: Format A
+# Date: 04/24/96
+# Authors: Lori Brownell <loribr@microsoft.com>
+# K.D. Chang <a-kchang@microsoft.com>
+# General notes: none
+#
+# Format: Three tab-separated columns
+# Column #1 is the cp857_DOSTurkish code (in hex)
+# Column #2 is the Unicode (in hex as 0xXXXX)
+# Column #3 is the Unicode name (follows a comment sign, '#')
+#
+# The entries are in cp857_DOSTurkish order
+#
+##################
+
+0x20-0x7f idem
+
+0x80 U+00c7 #LATIN CAPITAL LETTER C WITH CEDILLA
+0x81 U+00fc #LATIN SMALL LETTER U WITH DIAERESIS
+0x82 U+00e9 #LATIN SMALL LETTER E WITH ACUTE
+0x83 U+00e2 #LATIN SMALL LETTER A WITH CIRCUMFLEX
+0x84 U+00e4 #LATIN SMALL LETTER A WITH DIAERESIS
+0x85 U+00e0 #LATIN SMALL LETTER A WITH GRAVE
+0x86 U+00e5 #LATIN SMALL LETTER A WITH RING ABOVE
+0x87 U+00e7 #LATIN SMALL LETTER C WITH CEDILLA
+0x88 U+00ea #LATIN SMALL LETTER E WITH CIRCUMFLEX
+0x89 U+00eb #LATIN SMALL LETTER E WITH DIAERESIS
+0x8a U+00e8 #LATIN SMALL LETTER E WITH GRAVE
+0x8b U+00ef #LATIN SMALL LETTER I WITH DIAERESIS
+0x8c U+00ee #LATIN SMALL LETTER I WITH CIRCUMFLEX
+0x8d U+0131 #LATIN SMALL LETTER DOTLESS I
+0x8e U+00c4 #LATIN CAPITAL LETTER A WITH DIAERESIS
+0x8f U+00c5 #LATIN CAPITAL LETTER A WITH RING ABOVE
+0x90 U+00c9 #LATIN CAPITAL LETTER E WITH ACUTE
+0x91 U+00e6 #LATIN SMALL LIGATURE AE
+0x92 U+00c6 #LATIN CAPITAL LIGATURE AE
+0x93 U+00f4 #LATIN SMALL LETTER O WITH CIRCUMFLEX
+0x94 U+00f6 #LATIN SMALL LETTER O WITH DIAERESIS
+0x95 U+00f2 #LATIN SMALL LETTER O WITH GRAVE
+0x96 U+00fb #LATIN SMALL LETTER U WITH CIRCUMFLEX
+0x97 U+00f9 #LATIN SMALL LETTER U WITH GRAVE
+0x98 U+0130 #LATIN CAPITAL LETTER I WITH DOT ABOVE
+0x99 U+00d6 #LATIN CAPITAL LETTER O WITH DIAERESIS
+0x9a U+00dc #LATIN CAPITAL LETTER U WITH DIAERESIS
+0x9b U+00f8 #LATIN SMALL LETTER O WITH STROKE
+0x9c U+00a3 #POUND SIGN
+0x9d U+00d8 #LATIN CAPITAL LETTER O WITH STROKE
+0x9e U+015e #LATIN CAPITAL LETTER S WITH CEDILLA
+0x9f U+015f #LATIN SMALL LETTER S WITH CEDILLA
+0xa0 U+00e1 #LATIN SMALL LETTER A WITH ACUTE
+0xa1 U+00ed #LATIN SMALL LETTER I WITH ACUTE
+0xa2 U+00f3 #LATIN SMALL LETTER O WITH ACUTE
+0xa3 U+00fa #LATIN SMALL LETTER U WITH ACUTE
+0xa4 U+00f1 #LATIN SMALL LETTER N WITH TILDE
+0xa5 U+00d1 #LATIN CAPITAL LETTER N WITH TILDE
+0xa6 U+011e #LATIN CAPITAL LETTER G WITH BREVE
+0xa7 U+011f #LATIN SMALL LETTER G WITH BREVE
+0xa8 U+00bf #INVERTED QUESTION MARK
+0xa9 U+00ae #REGISTERED SIGN
+0xaa U+00ac #NOT SIGN
+0xab U+00bd #VULGAR FRACTION ONE HALF
+0xac U+00bc #VULGAR FRACTION ONE QUARTER
+0xad U+00a1 #INVERTED EXCLAMATION MARK
+0xae U+00ab #LEFT-POINTING DOUBLE ANGLE QUOTATION MARK
+0xaf U+00bb #RIGHT-POINTING DOUBLE ANGLE QUOTATION MARK
+0xb0 U+2591 #LIGHT SHADE
+0xb1 U+2592 #MEDIUM SHADE
+0xb2 U+2593 #DARK SHADE
+0xb3 U+2502 #BOX DRAWINGS LIGHT VERTICAL
+0xb4 U+2524 #BOX DRAWINGS LIGHT VERTICAL AND LEFT
+0xb5 U+00c1 #LATIN CAPITAL LETTER A WITH ACUTE
+0xb6 U+00c2 #LATIN CAPITAL LETTER A WITH CIRCUMFLEX
+0xb7 U+00c0 #LATIN CAPITAL LETTER A WITH GRAVE
+0xb8 U+00a9 #COPYRIGHT SIGN
+0xb9 U+2563 #BOX DRAWINGS DOUBLE VERTICAL AND LEFT
+0xba U+2551 #BOX DRAWINGS DOUBLE VERTICAL
+0xbb U+2557 #BOX DRAWINGS DOUBLE DOWN AND LEFT
+0xbc U+255d #BOX DRAWINGS DOUBLE UP AND LEFT
+0xbd U+00a2 #CENT SIGN
+0xbe U+00a5 #YEN SIGN
+0xbf U+2510 #BOX DRAWINGS LIGHT DOWN AND LEFT
+0xc0 U+2514 #BOX DRAWINGS LIGHT UP AND RIGHT
+0xc1 U+2534 #BOX DRAWINGS LIGHT UP AND HORIZONTAL
+0xc2 U+252c #BOX DRAWINGS LIGHT DOWN AND HORIZONTAL
+0xc3 U+251c #BOX DRAWINGS LIGHT VERTICAL AND RIGHT
+0xc4 U+2500 #BOX DRAWINGS LIGHT HORIZONTAL
+0xc5 U+253c #BOX DRAWINGS LIGHT VERTICAL AND HORIZONTAL
+0xc6 U+00e3 #LATIN SMALL LETTER A WITH TILDE
+0xc7 U+00c3 #LATIN CAPITAL LETTER A WITH TILDE
+0xc8 U+255a #BOX DRAWINGS DOUBLE UP AND RIGHT
+0xc9 U+2554 #BOX DRAWINGS DOUBLE DOWN AND RIGHT
+0xca U+2569 #BOX DRAWINGS DOUBLE UP AND HORIZONTAL
+0xcb U+2566 #BOX DRAWINGS DOUBLE DOWN AND HORIZONTAL
+0xcc U+2560 #BOX DRAWINGS DOUBLE VERTICAL AND RIGHT
+0xcd U+2550 #BOX DRAWINGS DOUBLE HORIZONTAL
+0xce U+256c #BOX DRAWINGS DOUBLE VERTICAL AND HORIZONTAL
+0xcf U+00a4 #CURRENCY SIGN
+0xd0 U+00ba #MASCULINE ORDINAL INDICATOR
+0xd1 U+00aa #FEMININE ORDINAL INDICATOR
+0xd2 U+00ca #LATIN CAPITAL LETTER E WITH CIRCUMFLEX
+0xd3 U+00cb #LATIN CAPITAL LETTER E WITH DIAERESIS
+0xd4 U+00c8 #LATIN CAPITAL LETTER E WITH GRAVE
+0xd5 #UNDEFINED
+0xd6 U+00cd #LATIN CAPITAL LETTER I WITH ACUTE
+0xd7 U+00ce #LATIN CAPITAL LETTER I WITH CIRCUMFLEX
+0xd8 U+00cf #LATIN CAPITAL LETTER I WITH DIAERESIS
+0xd9 U+2518 #BOX DRAWINGS LIGHT UP AND LEFT
+0xda U+250c #BOX DRAWINGS LIGHT DOWN AND RIGHT
+0xdb U+2588 #FULL BLOCK
+0xdc U+2584 #LOWER HALF BLOCK
+0xdd U+00a6 #BROKEN BAR
+0xde U+00cc #LATIN CAPITAL LETTER I WITH GRAVE
+0xdf U+2580 #UPPER HALF BLOCK
+0xe0 U+00d3 #LATIN CAPITAL LETTER O WITH ACUTE
+0xe1 U+00df #LATIN SMALL LETTER SHARP S
+0xe2 U+00d4 #LATIN CAPITAL LETTER O WITH CIRCUMFLEX
+0xe3 U+00d2 #LATIN CAPITAL LETTER O WITH GRAVE
+0xe4 U+00f5 #LATIN SMALL LETTER O WITH TILDE
+0xe5 U+00d5 #LATIN CAPITAL LETTER O WITH TILDE
+0xe6 U+00b5 #MICRO SIGN
+0xe7 #UNDEFINED
+0xe8 U+00d7 #MULTIPLICATION SIGN
+0xe9 U+00da #LATIN CAPITAL LETTER U WITH ACUTE
+0xea U+00db #LATIN CAPITAL LETTER U WITH CIRCUMFLEX
+0xeb U+00d9 #LATIN CAPITAL LETTER U WITH GRAVE
+0xec U+00ec #LATIN SMALL LETTER I WITH GRAVE
+0xed U+00ff #LATIN SMALL LETTER Y WITH DIAERESIS
+0xee U+00af #MACRON
+0xef U+00b4 #ACUTE ACCENT
+0xf0 U+00ad #SOFT HYPHEN
+0xf1 U+00b1 #PLUS-MINUS SIGN
+0xf2 #UNDEFINED
+0xf3 U+00be #VULGAR FRACTION THREE QUARTERS
+0xf4 U+00b6 #PILCROW SIGN
+0xf5 U+00a7 #SECTION SIGN
+0xf6 U+00f7 #DIVISION SIGN
+0xf7 U+00b8 #CEDILLA
+0xf8 U+00b0 #DEGREE SIGN
+0xf9 U+00a8 #DIAERESIS
+0xfa U+00b7 #MIDDLE DOT
+0xfb U+00b9 #SUPERSCRIPT ONE
+0xfc U+00b3 #SUPERSCRIPT THREE
+0xfd U+00b2 #SUPERSCRIPT TWO
+0xfe U+25a0 #BLACK SQUARE
+0xff U+00a0 #NO-BREAK SPACE
diff --git a/src/chrtrans/cp862_uni.tbl b/src/chrtrans/cp862_uni.tbl
new file mode 100644
index 0000000..ebf1222
--- /dev/null
+++ b/src/chrtrans/cp862_uni.tbl
@@ -0,0 +1,160 @@
+# This file has been modified for lynx (see README.tables)
+
+#The MIME name of this charset.
+Mcp862
+
+#Name as a Display Charset (used on Options screen).
+OHebrew (cp862)
+
+#Codepage number
+C862
+
+# Name: cp862_DOSHebrew to Unicode table
+# Unicode version: 2.0
+# Table version: 2.00
+# Table format: Format A
+# Date: 04/24/96
+# Authors: Lori Brownell <loribr@microsoft.com>
+# K.D. Chang <a-kchang@microsoft.com>
+# General notes: none
+#
+# Format: Three tab-separated columns
+# Column #1 is the cp862_DOSHebrew code (in hex)
+# Column #2 is the Unicode (in hex as 0xXXXX)
+# Column #3 is the Unicode name (follows a comment sign, '#')
+#
+# The entries are in cp862_DOSHebrew order
+#
+##################
+
+0x20-0x7f idem
+#
+0x80 U+05d0 #HEBREW LETTER ALEF
+0x81 U+05d1 #HEBREW LETTER BET
+0x82 U+05d2 #HEBREW LETTER GIMEL
+0x83 U+05d3 #HEBREW LETTER DALET
+0x84 U+05d4 #HEBREW LETTER HE
+0x85 U+05d5 #HEBREW LETTER VAV
+0x86 U+05d6 #HEBREW LETTER ZAYIN
+0x87 U+05d7 #HEBREW LETTER HET
+0x88 U+05d8 #HEBREW LETTER TET
+0x89 U+05d9 #HEBREW LETTER YOD
+0x8a U+05da #HEBREW LETTER FINAL KAF
+0x8b U+05db #HEBREW LETTER KAF
+0x8c U+05dc #HEBREW LETTER LAMED
+0x8d U+05dd #HEBREW LETTER FINAL MEM
+0x8e U+05de #HEBREW LETTER MEM
+0x8f U+05df #HEBREW LETTER FINAL NUN
+0x90 U+05e0 #HEBREW LETTER NUN
+0x91 U+05e1 #HEBREW LETTER SAMEKH
+0x92 U+05e2 #HEBREW LETTER AYIN
+0x93 U+05e3 #HEBREW LETTER FINAL PE
+0x94 U+05e4 #HEBREW LETTER PE
+0x95 U+05e5 #HEBREW LETTER FINAL TSADI
+0x96 U+05e6 #HEBREW LETTER TSADI
+0x97 U+05e7 #HEBREW LETTER QOF
+0x98 U+05e8 #HEBREW LETTER RESH
+0x99 U+05e9 #HEBREW LETTER SHIN
+0x9a U+05ea #HEBREW LETTER TAV
+0x9b U+00a2 #CENT SIGN
+0x9c U+00a3 #POUND SIGN
+0x9d U+00a5 #YEN SIGN
+0x9e U+20a7 #PESETA SIGN
+0x9f U+0192 #LATIN SMALL LETTER F WITH HOOK
+0xa0 U+00e1 #LATIN SMALL LETTER A WITH ACUTE
+0xa1 U+00ed #LATIN SMALL LETTER I WITH ACUTE
+0xa2 U+00f3 #LATIN SMALL LETTER O WITH ACUTE
+0xa3 U+00fa #LATIN SMALL LETTER U WITH ACUTE
+0xa4 U+00f1 #LATIN SMALL LETTER N WITH TILDE
+0xa5 U+00d1 #LATIN CAPITAL LETTER N WITH TILDE
+0xa6 U+00aa #FEMININE ORDINAL INDICATOR
+0xa7 U+00ba #MASCULINE ORDINAL INDICATOR
+0xa8 U+00bf #INVERTED QUESTION MARK
+0xa9 U+2310 #REVERSED NOT SIGN
+0xaa U+00ac #NOT SIGN
+0xab U+00bd #VULGAR FRACTION ONE HALF
+0xac U+00bc #VULGAR FRACTION ONE QUARTER
+0xad U+00a1 #INVERTED EXCLAMATION MARK
+0xae U+00ab #LEFT-POINTING DOUBLE ANGLE QUOTATION MARK
+0xaf U+00bb #RIGHT-POINTING DOUBLE ANGLE QUOTATION MARK
+0xb0 U+2591 #LIGHT SHADE
+0xb1 U+2592 #MEDIUM SHADE
+0xb2 U+2593 #DARK SHADE
+0xb3 U+2502 #BOX DRAWINGS LIGHT VERTICAL
+0xb4 U+2524 #BOX DRAWINGS LIGHT VERTICAL AND LEFT
+0xb5 U+2561 #BOX DRAWINGS VERTICAL SINGLE AND LEFT DOUBLE
+0xb6 U+2562 #BOX DRAWINGS VERTICAL DOUBLE AND LEFT SINGLE
+0xb7 U+2556 #BOX DRAWINGS DOWN DOUBLE AND LEFT SINGLE
+0xb8 U+2555 #BOX DRAWINGS DOWN SINGLE AND LEFT DOUBLE
+0xb9 U+2563 #BOX DRAWINGS DOUBLE VERTICAL AND LEFT
+0xba U+2551 #BOX DRAWINGS DOUBLE VERTICAL
+0xbb U+2557 #BOX DRAWINGS DOUBLE DOWN AND LEFT
+0xbc U+255d #BOX DRAWINGS DOUBLE UP AND LEFT
+0xbd U+255c #BOX DRAWINGS UP DOUBLE AND LEFT SINGLE
+0xbe U+255b #BOX DRAWINGS UP SINGLE AND LEFT DOUBLE
+0xbf U+2510 #BOX DRAWINGS LIGHT DOWN AND LEFT
+0xc0 U+2514 #BOX DRAWINGS LIGHT UP AND RIGHT
+0xc1 U+2534 #BOX DRAWINGS LIGHT UP AND HORIZONTAL
+0xc2 U+252c #BOX DRAWINGS LIGHT DOWN AND HORIZONTAL
+0xc3 U+251c #BOX DRAWINGS LIGHT VERTICAL AND RIGHT
+0xc4 U+2500 #BOX DRAWINGS LIGHT HORIZONTAL
+0xc5 U+253c #BOX DRAWINGS LIGHT VERTICAL AND HORIZONTAL
+0xc6 U+255e #BOX DRAWINGS VERTICAL SINGLE AND RIGHT DOUBLE
+0xc7 U+255f #BOX DRAWINGS VERTICAL DOUBLE AND RIGHT SINGLE
+0xc8 U+255a #BOX DRAWINGS DOUBLE UP AND RIGHT
+0xc9 U+2554 #BOX DRAWINGS DOUBLE DOWN AND RIGHT
+0xca U+2569 #BOX DRAWINGS DOUBLE UP AND HORIZONTAL
+0xcb U+2566 #BOX DRAWINGS DOUBLE DOWN AND HORIZONTAL
+0xcc U+2560 #BOX DRAWINGS DOUBLE VERTICAL AND RIGHT
+0xcd U+2550 #BOX DRAWINGS DOUBLE HORIZONTAL
+0xce U+256c #BOX DRAWINGS DOUBLE VERTICAL AND HORIZONTAL
+0xcf U+2567 #BOX DRAWINGS UP SINGLE AND HORIZONTAL DOUBLE
+0xd0 U+2568 #BOX DRAWINGS UP DOUBLE AND HORIZONTAL SINGLE
+0xd1 U+2564 #BOX DRAWINGS DOWN SINGLE AND HORIZONTAL DOUBLE
+0xd2 U+2565 #BOX DRAWINGS DOWN DOUBLE AND HORIZONTAL SINGLE
+0xd3 U+2559 #BOX DRAWINGS UP DOUBLE AND RIGHT SINGLE
+0xd4 U+2558 #BOX DRAWINGS UP SINGLE AND RIGHT DOUBLE
+0xd5 U+2552 #BOX DRAWINGS DOWN SINGLE AND RIGHT DOUBLE
+0xd6 U+2553 #BOX DRAWINGS DOWN DOUBLE AND RIGHT SINGLE
+0xd7 U+256b #BOX DRAWINGS VERTICAL DOUBLE AND HORIZONTAL SINGLE
+0xd8 U+256a #BOX DRAWINGS VERTICAL SINGLE AND HORIZONTAL DOUBLE
+0xd9 U+2518 #BOX DRAWINGS LIGHT UP AND LEFT
+0xda U+250c #BOX DRAWINGS LIGHT DOWN AND RIGHT
+0xdb U+2588 #FULL BLOCK
+0xdc U+2584 #LOWER HALF BLOCK
+0xdd U+258c #LEFT HALF BLOCK
+0xde U+2590 #RIGHT HALF BLOCK
+0xdf U+2580 #UPPER HALF BLOCK
+0xe0 U+03b1 #GREEK SMALL LETTER ALPHA
+0xe1 U+00df #LATIN SMALL LETTER SHARP S (GERMAN)
+0xe2 U+0393 #GREEK CAPITAL LETTER GAMMA
+0xe3 U+03c0 #GREEK SMALL LETTER PI
+0xe4 U+03a3 #GREEK CAPITAL LETTER SIGMA
+0xe5 U+03c3 #GREEK SMALL LETTER SIGMA
+0xe6 U+00b5 #MICRO SIGN
+0xe7 U+03c4 #GREEK SMALL LETTER TAU
+0xe8 U+03a6 #GREEK CAPITAL LETTER PHI
+0xe9 U+0398 #GREEK CAPITAL LETTER THETA
+0xea U+03a9 #GREEK CAPITAL LETTER OMEGA
+0xeb U+03b4 #GREEK SMALL LETTER DELTA
+0xec U+221e #INFINITY
+0xed U+03c6 #GREEK SMALL LETTER PHI
+0xee U+03b5 #GREEK SMALL LETTER EPSILON
+0xef U+2229 #INTERSECTION
+0xf0 U+2261 #IDENTICAL TO
+0xf1 U+00b1 #PLUS-MINUS SIGN
+0xf2 U+2265 #GREATER-THAN OR EQUAL TO
+0xf3 U+2264 #LESS-THAN OR EQUAL TO
+0xf4 U+2320 #TOP HALF INTEGRAL
+0xf5 U+2321 #BOTTOM HALF INTEGRAL
+0xf6 U+00f7 #DIVISION SIGN
+0xf7 U+2248 #ALMOST EQUAL TO
+0xf8 U+00b0 #DEGREE SIGN
+0xf9 U+2219 #BULLET OPERATOR
+0xfa U+00b7 #MIDDLE DOT
+0xfb U+221a #SQUARE ROOT
+0xfc U+207f #SUPERSCRIPT LATIN SMALL LETTER N
+0xfd U+00b2 #SUPERSCRIPT TWO
+0xfe U+25a0 #BLACK SQUARE
+0xff U+00a0 #NO-BREAK SPACE
+
diff --git a/src/chrtrans/cp864_uni.tbl b/src/chrtrans/cp864_uni.tbl
new file mode 100644
index 0000000..6568577
--- /dev/null
+++ b/src/chrtrans/cp864_uni.tbl
@@ -0,0 +1,160 @@
+# This file has been modified for lynx (see README.tables)
+
+#The MIME name of this charset.
+Mcp864
+
+#Name as a Display Charset (used on Options screen).
+OArabic (cp864)
+
+#Codepage number
+C864
+
+# Name: cp864_DOSArabic to Unicode table
+# Unicode version: 2.0
+# Table version: 2.00
+# Table format: Format A
+# Date: 04/24/96
+# Authors: Lori Brownell <loribr@microsoft.com>
+# K.D. Chang <a-kchang@microsoft.com>
+# General notes: none
+#
+# Format: Three tab-separated columns
+# Column #1 is the cp864_DOSArabic code (in hex)
+# Column #2 is the Unicode (in hex as 0xXXXX)
+# Column #3 is the Unicode name (follows a comment sign, '#')
+#
+# The entries are in cp864_DOSArabic order
+#
+##################
+
+0x20-0x7f idem
+#
+0x80 U+00b0 #DEGREE SIGN
+0x81 U+00b7 #MIDDLE DOT
+0x82 U+2219 #BULLET OPERATOR
+0x83 U+221a #SQUARE ROOT
+0x84 U+2592 #MEDIUM SHADE
+0x85 U+2500 #FORMS LIGHT HORIZONTAL
+0x86 U+2502 #FORMS LIGHT VERTICAL
+0x87 U+253c #FORMS LIGHT VERTICAL AND HORIZONTAL
+0x88 U+2524 #FORMS LIGHT VERTICAL AND LEFT
+0x89 U+252c #FORMS LIGHT DOWN AND HORIZONTAL
+0x8a U+251c #FORMS LIGHT VERTICAL AND RIGHT
+0x8b U+2534 #FORMS LIGHT UP AND HORIZONTAL
+0x8c U+2510 #FORMS LIGHT DOWN AND LEFT
+0x8d U+250c #FORMS LIGHT DOWN AND RIGHT
+0x8e U+2514 #FORMS LIGHT UP AND RIGHT
+0x8f U+2518 #FORMS LIGHT UP AND LEFT
+0x90 U+03b2 #GREEK SMALL BETA
+0x91 U+221e #INFINITY
+0x92 U+03c6 #GREEK SMALL PHI
+0x93 U+00b1 #PLUS-OR-MINUS SIGN
+0x94 U+00bd #FRACTION 1/2
+0x95 U+00bc #FRACTION 1/4
+0x96 U+2248 #ALMOST EQUAL TO
+0x97 U+00ab #LEFT POINTING GUILLEMET
+0x98 U+00bb #RIGHT POINTING GUILLEMET
+0x99 U+fef7 #ARABIC LIGATURE LAM WITH ALEF WITH HAMZA ABOVE ISOLATED FORM
+0x9a U+fef8 #ARABIC LIGATURE LAM WITH ALEF WITH HAMZA ABOVE FINAL FORM
+#0x9b #UNDEFINED
+#0x9c #UNDEFINED
+0x9d U+fefb #ARABIC LIGATURE LAM WITH ALEF ISOLATED FORM
+0x9e U+fefc #ARABIC LIGATURE LAM WITH ALEF FINAL FORM
+#0x9f #UNDEFINED
+0xa0 U+00a0 #NON-BREAKING SPACE
+0xa1 U+00ad #SOFT HYPHEN
+0xa2 U+fe82 #ARABIC LETTER ALEF WITH MADDA ABOVE FINAL FORM
+0xa3 U+00a3 #POUND SIGN
+0xa4 U+00a4 #CURRENCY SIGN
+0xa5 U+fe84 #ARABIC LETTER ALEF WITH HAMZA ABOVE FINAL FORM
+#0xa6 #UNDEFINED
+#0xa7 #UNDEFINED
+0xa8 U+fe8e #ARABIC LETTER ALEF FINAL FORM
+0xa9 U+fe8f #ARABIC LETTER BEH ISOLATED FORM
+0xaa U+fe95 #ARABIC LETTER TEH ISOLATED FORM
+0xab U+fe99 #ARABIC LETTER THEH ISOLATED FORM
+0xac U+060c #ARABIC COMMA
+0xad U+fe9d #ARABIC LETTER JEEM ISOLATED FORM
+0xae U+fea1 #ARABIC LETTER HAH ISOLATED FORM
+0xaf U+fea5 #ARABIC LETTER KHAH ISOLATED FORM
+0xb0 U+0660 #ARABIC-INDIC DIGIT ZERO
+0xb1 U+0661 #ARABIC-INDIC DIGIT ONE
+0xb2 U+0662 #ARABIC-INDIC DIGIT TWO
+0xb3 U+0663 #ARABIC-INDIC DIGIT THREE
+0xb4 U+0664 #ARABIC-INDIC DIGIT FOUR
+0xb5 U+0665 #ARABIC-INDIC DIGIT FIVE
+0xb6 U+0666 #ARABIC-INDIC DIGIT SIX
+0xb7 U+0667 #ARABIC-INDIC DIGIT SEVEN
+0xb8 U+0668 #ARABIC-INDIC DIGIT EIGHT
+0xb9 U+0669 #ARABIC-INDIC DIGIT NINE
+0xba U+fed1 #ARABIC LETTER FEH ISOLATED FORM
+0xbb U+061b #ARABIC SEMICOLON
+0xbc U+feb1 #ARABIC LETTER SEEN ISOLATED FORM
+0xbd U+feb5 #ARABIC LETTER SHEEN ISOLATED FORM
+0xbe U+feb9 #ARABIC LETTER SAD ISOLATED FORM
+0xbf U+061f #ARABIC QUESTION MARK
+0xc0 U+00a2 #CENT SIGN
+0xc1 U+fe80 #ARABIC LETTER HAMZA ISOLATED FORM
+0xc2 U+fe81 #ARABIC LETTER ALEF WITH MADDA ABOVE ISOLATED FORM
+0xc3 U+fe83 #ARABIC LETTER ALEF WITH HAMZA ABOVE ISOLATED FORM
+0xc4 U+fe85 #ARABIC LETTER WAW WITH HAMZA ABOVE ISOLATED FORM
+0xc5 U+feca #ARABIC LETTER AIN FINAL FORM
+0xc6 U+fe8b #ARABIC LETTER YEH WITH HAMZA ABOVE INITIAL FORM
+0xc7 U+fe8d #ARABIC LETTER ALEF ISOLATED FORM
+0xc8 U+fe91 #ARABIC LETTER BEH INITIAL FORM
+0xc9 U+fe93 #ARABIC LETTER TEH MARBUTA ISOLATED FORM
+0xca U+fe97 #ARABIC LETTER TEH INITIAL FORM
+0xcb U+fe9b #ARABIC LETTER THEH INITIAL FORM
+0xcc U+fe9f #ARABIC LETTER JEEM INITIAL FORM
+0xcd U+fea3 #ARABIC LETTER HAH INITIAL FORM
+0xce U+fea7 #ARABIC LETTER KHAH INITIAL FORM
+0xcf U+fea9 #ARABIC LETTER DAL ISOLATED FORM
+0xd0 U+feab #ARABIC LETTER THAL ISOLATED FORM
+0xd1 U+fead #ARABIC LETTER REH ISOLATED FORM
+0xd2 U+feaf #ARABIC LETTER ZAIN ISOLATED FORM
+0xd3 U+feb3 #ARABIC LETTER SEEN INITIAL FORM
+0xd4 U+feb7 #ARABIC LETTER SHEEN INITIAL FORM
+0xd5 U+febb #ARABIC LETTER SAD INITIAL FORM
+0xd6 U+febf #ARABIC LETTER DAD INITIAL FORM
+0xd7 U+fec1 #ARABIC LETTER TAH ISOLATED FORM
+0xd8 U+fec5 #ARABIC LETTER ZAH ISOLATED FORM
+0xd9 U+fecb #ARABIC LETTER AIN INITIAL FORM
+0xda U+fecf #ARABIC LETTER GHAIN INITIAL FORM
+0xdb U+00a6 #BROKEN VERTICAL BAR
+0xdc U+00ac #NOT SIGN
+0xdd U+00f7 #DIVISION SIGN
+0xde U+00d7 #MULTIPLICATION SIGN
+0xdf U+fec9 #ARABIC LETTER AIN ISOLATED FORM
+0xe0 U+0640 #ARABIC TATWEEL
+0xe1 U+fed3 #ARABIC LETTER FEH INITIAL FORM
+0xe2 U+fed7 #ARABIC LETTER QAF INITIAL FORM
+0xe3 U+fedb #ARABIC LETTER KAF INITIAL FORM
+0xe4 U+fedf #ARABIC LETTER LAM INITIAL FORM
+0xe5 U+fee3 #ARABIC LETTER MEEM INITIAL FORM
+0xe6 U+fee7 #ARABIC LETTER NOON INITIAL FORM
+0xe7 U+feeb #ARABIC LETTER HEH INITIAL FORM
+0xe8 U+feed #ARABIC LETTER WAW ISOLATED FORM
+0xe9 U+feef #ARABIC LETTER ALEF MAKSURA ISOLATED FORM
+0xea U+fef3 #ARABIC LETTER YEH INITIAL FORM
+0xeb U+febd #ARABIC LETTER DAD ISOLATED FORM
+0xec U+fecc #ARABIC LETTER AIN MEDIAL FORM
+0xed U+fece #ARABIC LETTER GHAIN FINAL FORM
+0xee U+fecd #ARABIC LETTER GHAIN ISOLATED FORM
+0xef U+fee1 #ARABIC LETTER MEEM ISOLATED FORM
+0xf0 U+fe7d #ARABIC SHADDA MEDIAL FORM
+0xf1 U+0651 #ARABIC SHADDAH
+0xf2 U+fee5 #ARABIC LETTER NOON ISOLATED FORM
+0xf3 U+fee9 #ARABIC LETTER HEH ISOLATED FORM
+0xf4 U+feec #ARABIC LETTER HEH MEDIAL FORM
+0xf5 U+fef0 #ARABIC LETTER ALEF MAKSURA FINAL FORM
+0xf6 U+fef2 #ARABIC LETTER YEH FINAL FORM
+0xf7 U+fed0 #ARABIC LETTER GHAIN MEDIAL FORM
+0xf8 U+fed5 #ARABIC LETTER QAF ISOLATED FORM
+0xf9 U+fef5 #ARABIC LIGATURE LAM WITH ALEF WITH MADDA ABOVE ISOLATED FORM
+0xfa U+fef6 #ARABIC LIGATURE LAM WITH ALEF WITH MADDA ABOVE FINAL FORM
+0xfb U+fedd #ARABIC LETTER LAM ISOLATED FORM
+0xfc U+fed9 #ARABIC LETTER KAF ISOLATED FORM
+0xfd U+fef1 #ARABIC LETTER YEH ISOLATED FORM
+0xfe U+25a0 #BLACK SQUARE
+#0xff #UNDEFINED
+
diff --git a/src/chrtrans/cp866_uni.tbl b/src/chrtrans/cp866_uni.tbl
new file mode 100644
index 0000000..029e025
--- /dev/null
+++ b/src/chrtrans/cp866_uni.tbl
@@ -0,0 +1,159 @@
+# This file has been modified for lynx (see README.tables)
+
+#The MIME name of this charset.
+Mcp866
+
+#Name as a Display Charset (used on Options screen)
+OCyrillic (cp866)
+
+#Codepage number
+C866
+
+#
+# Name: cp866_DOSCyrillicRussian to Unicode table
+# Unicode version: 2.0
+# Table version: 2.00
+# Table format: Format A
+# Date: 04/24/96
+# Authors: Lori Brownell <loribr@microsoft.com>
+# K.D. Chang <a-kchang@microsoft.com>
+# General notes: none
+#
+# Format: Three tab-separated columns
+# Column #1 is the cp866_DOSCyrillicRussian code (in hex)
+# Column #2 is the Unicode (in hex as 0xXXXX)
+# Column #3 is the Unicode name (follows a comment sign, '#')
+#
+# The entries are in cp866_DOSCyrillicRussian order
+#
+0x20-0x7f idem
+#
+0x80 U+0410 #CYRILLIC CAPITAL LETTER A
+0x81 U+0411 #CYRILLIC CAPITAL LETTER BE
+0x82 U+0412 #CYRILLIC CAPITAL LETTER VE
+0x83 U+0413 #CYRILLIC CAPITAL LETTER GHE
+0x84 U+0414 #CYRILLIC CAPITAL LETTER DE
+0x85 U+0415 #CYRILLIC CAPITAL LETTER IE
+0x86 U+0416 #CYRILLIC CAPITAL LETTER ZHE
+0x87 U+0417 #CYRILLIC CAPITAL LETTER ZE
+0x88 U+0418 #CYRILLIC CAPITAL LETTER I
+0x89 U+0419 #CYRILLIC CAPITAL LETTER SHORT I
+0x8a U+041a #CYRILLIC CAPITAL LETTER KA
+0x8b U+041b #CYRILLIC CAPITAL LETTER EL
+0x8c U+041c #CYRILLIC CAPITAL LETTER EM
+0x8d U+041d #CYRILLIC CAPITAL LETTER EN
+0x8e U+041e #CYRILLIC CAPITAL LETTER O
+0x8f U+041f #CYRILLIC CAPITAL LETTER PE
+0x90 U+0420 #CYRILLIC CAPITAL LETTER ER
+0x91 U+0421 #CYRILLIC CAPITAL LETTER ES
+0x92 U+0422 #CYRILLIC CAPITAL LETTER TE
+0x93 U+0423 #CYRILLIC CAPITAL LETTER U
+0x94 U+0424 #CYRILLIC CAPITAL LETTER EF
+0x95 U+0425 #CYRILLIC CAPITAL LETTER HA
+0x96 U+0426 #CYRILLIC CAPITAL LETTER TSE
+0x97 U+0427 #CYRILLIC CAPITAL LETTER CHE
+0x98 U+0428 #CYRILLIC CAPITAL LETTER SHA
+0x99 U+0429 #CYRILLIC CAPITAL LETTER SHCHA
+0x9a U+042a #CYRILLIC CAPITAL LETTER HARD SIGN
+0x9b U+042b #CYRILLIC CAPITAL LETTER YERU
+0x9c U+042c #CYRILLIC CAPITAL LETTER SOFT SIGN
+0x9d U+042d #CYRILLIC CAPITAL LETTER E
+0x9e U+042e #CYRILLIC CAPITAL LETTER YU
+0x9f U+042f #CYRILLIC CAPITAL LETTER YA
+0xa0 U+0430 #CYRILLIC SMALL LETTER A
+0xa1 U+0431 #CYRILLIC SMALL LETTER BE
+0xa2 U+0432 #CYRILLIC SMALL LETTER VE
+0xa3 U+0433 #CYRILLIC SMALL LETTER GHE
+0xa4 U+0434 #CYRILLIC SMALL LETTER DE
+0xa5 U+0435 #CYRILLIC SMALL LETTER IE
+0xa6 U+0436 #CYRILLIC SMALL LETTER ZHE
+0xa7 U+0437 #CYRILLIC SMALL LETTER ZE
+0xa8 U+0438 #CYRILLIC SMALL LETTER I
+0xa9 U+0439 #CYRILLIC SMALL LETTER SHORT I
+0xaa U+043a #CYRILLIC SMALL LETTER KA
+0xab U+043b #CYRILLIC SMALL LETTER EL
+0xac U+043c #CYRILLIC SMALL LETTER EM
+0xad U+043d #CYRILLIC SMALL LETTER EN
+0xae U+043e #CYRILLIC SMALL LETTER O
+0xaf U+043f #CYRILLIC SMALL LETTER PE
+0xb0 U+2591 #LIGHT SHADE
+0xb1 U+2592 #MEDIUM SHADE
+0xb2 U+2593 #DARK SHADE
+0xb3 U+2502 #BOX DRAWINGS LIGHT VERTICAL
+0xb4 U+2524 #BOX DRAWINGS LIGHT VERTICAL AND LEFT
+0xb5 U+2561 #BOX DRAWINGS VERTICAL SINGLE AND LEFT DOUBLE
+0xb6 U+2562 #BOX DRAWINGS VERTICAL DOUBLE AND LEFT SINGLE
+0xb7 U+2556 #BOX DRAWINGS DOWN DOUBLE AND LEFT SINGLE
+0xb8 U+2555 #BOX DRAWINGS DOWN SINGLE AND LEFT DOUBLE
+0xb9 U+2563 #BOX DRAWINGS DOUBLE VERTICAL AND LEFT
+0xba U+2551 #BOX DRAWINGS DOUBLE VERTICAL
+0xbb U+2557 #BOX DRAWINGS DOUBLE DOWN AND LEFT
+0xbc U+255d #BOX DRAWINGS DOUBLE UP AND LEFT
+0xbd U+255c #BOX DRAWINGS UP DOUBLE AND LEFT SINGLE
+0xbe U+255b #BOX DRAWINGS UP SINGLE AND LEFT DOUBLE
+0xbf U+2510 #BOX DRAWINGS LIGHT DOWN AND LEFT
+0xc0 U+2514 #BOX DRAWINGS LIGHT UP AND RIGHT
+0xc1 U+2534 #BOX DRAWINGS LIGHT UP AND HORIZONTAL
+0xc2 U+252c #BOX DRAWINGS LIGHT DOWN AND HORIZONTAL
+0xc3 U+251c #BOX DRAWINGS LIGHT VERTICAL AND RIGHT
+0xc4 U+2500 #BOX DRAWINGS LIGHT HORIZONTAL
+0xc5 U+253c #BOX DRAWINGS LIGHT VERTICAL AND HORIZONTAL
+0xc6 U+255e #BOX DRAWINGS VERTICAL SINGLE AND RIGHT DOUBLE
+0xc7 U+255f #BOX DRAWINGS VERTICAL DOUBLE AND RIGHT SINGLE
+0xc8 U+255a #BOX DRAWINGS DOUBLE UP AND RIGHT
+0xc9 U+2554 #BOX DRAWINGS DOUBLE DOWN AND RIGHT
+0xca U+2569 #BOX DRAWINGS DOUBLE UP AND HORIZONTAL
+0xcb U+2566 #BOX DRAWINGS DOUBLE DOWN AND HORIZONTAL
+0xcc U+2560 #BOX DRAWINGS DOUBLE VERTICAL AND RIGHT
+0xcd U+2550 #BOX DRAWINGS DOUBLE HORIZONTAL
+0xce U+256c #BOX DRAWINGS DOUBLE VERTICAL AND HORIZONTAL
+0xcf U+2567 #BOX DRAWINGS UP SINGLE AND HORIZONTAL DOUBLE
+0xd0 U+2568 #BOX DRAWINGS UP DOUBLE AND HORIZONTAL SINGLE
+0xd1 U+2564 #BOX DRAWINGS DOWN SINGLE AND HORIZONTAL DOUBLE
+0xd2 U+2565 #BOX DRAWINGS DOWN DOUBLE AND HORIZONTAL SINGLE
+0xd3 U+2559 #BOX DRAWINGS UP DOUBLE AND RIGHT SINGLE
+0xd4 U+2558 #BOX DRAWINGS UP SINGLE AND RIGHT DOUBLE
+0xd5 U+2552 #BOX DRAWINGS DOWN SINGLE AND RIGHT DOUBLE
+0xd6 U+2553 #BOX DRAWINGS DOWN DOUBLE AND RIGHT SINGLE
+0xd7 U+256b #BOX DRAWINGS VERTICAL DOUBLE AND HORIZONTAL SINGLE
+0xd8 U+256a #BOX DRAWINGS VERTICAL SINGLE AND HORIZONTAL DOUBLE
+0xd9 U+2518 #BOX DRAWINGS LIGHT UP AND LEFT
+0xda U+250c #BOX DRAWINGS LIGHT DOWN AND RIGHT
+0xdb U+2588 #FULL BLOCK
+0xdc U+2584 #LOWER HALF BLOCK
+0xdd U+258c #LEFT HALF BLOCK
+0xde U+2590 #RIGHT HALF BLOCK
+0xdf U+2580 #UPPER HALF BLOCK
+0xe0 U+0440 #CYRILLIC SMALL LETTER ER
+0xe1 U+0441 #CYRILLIC SMALL LETTER ES
+0xe2 U+0442 #CYRILLIC SMALL LETTER TE
+0xe3 U+0443 #CYRILLIC SMALL LETTER U
+0xe4 U+0444 #CYRILLIC SMALL LETTER EF
+0xe5 U+0445 #CYRILLIC SMALL LETTER HA
+0xe6 U+0446 #CYRILLIC SMALL LETTER TSE
+0xe7 U+0447 #CYRILLIC SMALL LETTER CHE
+0xe8 U+0448 #CYRILLIC SMALL LETTER SHA
+0xe9 U+0449 #CYRILLIC SMALL LETTER SHCHA
+0xea U+044a #CYRILLIC SMALL LETTER HARD SIGN
+0xeb U+044b #CYRILLIC SMALL LETTER YERU
+0xec U+044c #CYRILLIC SMALL LETTER SOFT SIGN
+0xed U+044d #CYRILLIC SMALL LETTER E
+0xee U+044e #CYRILLIC SMALL LETTER YU
+0xef U+044f #CYRILLIC SMALL LETTER YA
+0xf0 U+0401 #CYRILLIC CAPITAL LETTER IO
+0xf1 U+0451 #CYRILLIC SMALL LETTER IO
+0xf2 U+0404 #CYRILLIC CAPITAL LETTER UKRAINIAN IE
+0xf3 U+0454 #CYRILLIC SMALL LETTER UKRAINIAN IE
+0xf4 U+0407 #CYRILLIC CAPITAL LETTER YI
+0xf5 U+0457 #CYRILLIC SMALL LETTER YI
+0xf6 U+040e #CYRILLIC CAPITAL LETTER SHORT U
+0xf7 U+045e #CYRILLIC SMALL LETTER SHORT U
+0xf8 U+00b0 #DEGREE SIGN
+0xf9 U+2219 #BULLET OPERATOR
+0xfa U+00b7 #MIDDLE DOT
+0xfb U+221a #SQUARE ROOT
+0xfc U+2116 #NUMERO SIGN
+0xfd U+00a4 #CURRENCY SIGN
+0xfe U+25a0 #BLACK SQUARE
+0xff U+00a0 #NO-BREAK SPACE
+
diff --git a/src/chrtrans/cp866u_uni.tbl b/src/chrtrans/cp866u_uni.tbl
new file mode 100644
index 0000000..8d2dee0
--- /dev/null
+++ b/src/chrtrans/cp866u_uni.tbl
@@ -0,0 +1,157 @@
+# This file has been modified for lynx (see README.tables)
+
+#The MIME name of this charset.
+Mcp866u
+
+#Name as a Display Charset (used on Options screen)
+OUkrainian Cyrillic (cp866u)
+
+#Codepage number
+#?
+
+#
+# Name: cp866_DOSCyrillicUkrainian to Unicode table
+# Unicode version: 2.0
+# Table version: 2.00
+# Table format: Format A
+# General notes: based on Cyrillic (cp866) table,
+# have different mapping in 0xF2-0xF9 region.
+#
+# Format: Three tab-separated columns
+# Column #1 is the cp866_DOSCyrillicUkrainian code (in hex)
+# Column #2 is the Unicode (in hex as 0xXXXX)
+# Column #3 is the Unicode name (follows a comment sign, '#')
+#
+# The entries are in cp866_DOSCyrillicUkrainian order
+#
+0x20-0x7f idem
+#
+0x80 U+0410 #CYRILLIC CAPITAL LETTER A
+0x81 U+0411 #CYRILLIC CAPITAL LETTER BE
+0x82 U+0412 #CYRILLIC CAPITAL LETTER VE
+0x83 U+0413 #CYRILLIC CAPITAL LETTER GHE
+0x84 U+0414 #CYRILLIC CAPITAL LETTER DE
+0x85 U+0415 #CYRILLIC CAPITAL LETTER IE
+0x86 U+0416 #CYRILLIC CAPITAL LETTER ZHE
+0x87 U+0417 #CYRILLIC CAPITAL LETTER ZE
+0x88 U+0418 #CYRILLIC CAPITAL LETTER I
+0x89 U+0419 #CYRILLIC CAPITAL LETTER SHORT I
+0x8a U+041a #CYRILLIC CAPITAL LETTER KA
+0x8b U+041b #CYRILLIC CAPITAL LETTER EL
+0x8c U+041c #CYRILLIC CAPITAL LETTER EM
+0x8d U+041d #CYRILLIC CAPITAL LETTER EN
+0x8e U+041e #CYRILLIC CAPITAL LETTER O
+0x8f U+041f #CYRILLIC CAPITAL LETTER PE
+0x90 U+0420 #CYRILLIC CAPITAL LETTER ER
+0x91 U+0421 #CYRILLIC CAPITAL LETTER ES
+0x92 U+0422 #CYRILLIC CAPITAL LETTER TE
+0x93 U+0423 #CYRILLIC CAPITAL LETTER U
+0x94 U+0424 #CYRILLIC CAPITAL LETTER EF
+0x95 U+0425 #CYRILLIC CAPITAL LETTER HA
+0x96 U+0426 #CYRILLIC CAPITAL LETTER TSE
+0x97 U+0427 #CYRILLIC CAPITAL LETTER CHE
+0x98 U+0428 #CYRILLIC CAPITAL LETTER SHA
+0x99 U+0429 #CYRILLIC CAPITAL LETTER SHCHA
+0x9a U+042a #CYRILLIC CAPITAL LETTER HARD SIGN
+0x9b U+042b #CYRILLIC CAPITAL LETTER YERU
+0x9c U+042c #CYRILLIC CAPITAL LETTER SOFT SIGN
+0x9d U+042d #CYRILLIC CAPITAL LETTER E
+0x9e U+042e #CYRILLIC CAPITAL LETTER YU
+0x9f U+042f #CYRILLIC CAPITAL LETTER YA
+0xa0 U+0430 #CYRILLIC SMALL LETTER A
+0xa1 U+0431 #CYRILLIC SMALL LETTER BE
+0xa2 U+0432 #CYRILLIC SMALL LETTER VE
+0xa3 U+0433 #CYRILLIC SMALL LETTER GHE
+0xa4 U+0434 #CYRILLIC SMALL LETTER DE
+0xa5 U+0435 #CYRILLIC SMALL LETTER IE
+0xa6 U+0436 #CYRILLIC SMALL LETTER ZHE
+0xa7 U+0437 #CYRILLIC SMALL LETTER ZE
+0xa8 U+0438 #CYRILLIC SMALL LETTER I
+0xa9 U+0439 #CYRILLIC SMALL LETTER SHORT I
+0xaa U+043a #CYRILLIC SMALL LETTER KA
+0xab U+043b #CYRILLIC SMALL LETTER EL
+0xac U+043c #CYRILLIC SMALL LETTER EM
+0xad U+043d #CYRILLIC SMALL LETTER EN
+0xae U+043e #CYRILLIC SMALL LETTER O
+0xaf U+043f #CYRILLIC SMALL LETTER PE
+0xb0 U+2591 #LIGHT SHADE
+0xb1 U+2592 #MEDIUM SHADE
+0xb2 U+2593 #DARK SHADE
+0xb3 U+2502 #BOX DRAWINGS LIGHT VERTICAL
+0xb4 U+2524 #BOX DRAWINGS LIGHT VERTICAL AND LEFT
+0xb5 U+2561 #BOX DRAWINGS VERTICAL SINGLE AND LEFT DOUBLE
+0xb6 U+2562 #BOX DRAWINGS VERTICAL DOUBLE AND LEFT SINGLE
+0xb7 U+2556 #BOX DRAWINGS DOWN DOUBLE AND LEFT SINGLE
+0xb8 U+2555 #BOX DRAWINGS DOWN SINGLE AND LEFT DOUBLE
+0xb9 U+2563 #BOX DRAWINGS DOUBLE VERTICAL AND LEFT
+0xba U+2551 #BOX DRAWINGS DOUBLE VERTICAL
+0xbb U+2557 #BOX DRAWINGS DOUBLE DOWN AND LEFT
+0xbc U+255d #BOX DRAWINGS DOUBLE UP AND LEFT
+0xbd U+255c #BOX DRAWINGS UP DOUBLE AND LEFT SINGLE
+0xbe U+255b #BOX DRAWINGS UP SINGLE AND LEFT DOUBLE
+0xbf U+2510 #BOX DRAWINGS LIGHT DOWN AND LEFT
+0xc0 U+2514 #BOX DRAWINGS LIGHT UP AND RIGHT
+0xc1 U+2534 #BOX DRAWINGS LIGHT UP AND HORIZONTAL
+0xc2 U+252c #BOX DRAWINGS LIGHT DOWN AND HORIZONTAL
+0xc3 U+251c #BOX DRAWINGS LIGHT VERTICAL AND RIGHT
+0xc4 U+2500 #BOX DRAWINGS LIGHT HORIZONTAL
+0xc5 U+253c #BOX DRAWINGS LIGHT VERTICAL AND HORIZONTAL
+0xc6 U+255e #BOX DRAWINGS VERTICAL SINGLE AND RIGHT DOUBLE
+0xc7 U+255f #BOX DRAWINGS VERTICAL DOUBLE AND RIGHT SINGLE
+0xc8 U+255a #BOX DRAWINGS DOUBLE UP AND RIGHT
+0xc9 U+2554 #BOX DRAWINGS DOUBLE DOWN AND RIGHT
+0xca U+2569 #BOX DRAWINGS DOUBLE UP AND HORIZONTAL
+0xcb U+2566 #BOX DRAWINGS DOUBLE DOWN AND HORIZONTAL
+0xcc U+2560 #BOX DRAWINGS DOUBLE VERTICAL AND RIGHT
+0xcd U+2550 #BOX DRAWINGS DOUBLE HORIZONTAL
+0xce U+256c #BOX DRAWINGS DOUBLE VERTICAL AND HORIZONTAL
+0xcf U+2567 #BOX DRAWINGS UP SINGLE AND HORIZONTAL DOUBLE
+0xd0 U+2568 #BOX DRAWINGS UP DOUBLE AND HORIZONTAL SINGLE
+0xd1 U+2564 #BOX DRAWINGS DOWN SINGLE AND HORIZONTAL DOUBLE
+0xd2 U+2565 #BOX DRAWINGS DOWN DOUBLE AND HORIZONTAL SINGLE
+0xd3 U+2559 #BOX DRAWINGS UP DOUBLE AND RIGHT SINGLE
+0xd4 U+2558 #BOX DRAWINGS UP SINGLE AND RIGHT DOUBLE
+0xd5 U+2552 #BOX DRAWINGS DOWN SINGLE AND RIGHT DOUBLE
+0xd6 U+2553 #BOX DRAWINGS DOWN DOUBLE AND RIGHT SINGLE
+0xd7 U+256b #BOX DRAWINGS VERTICAL DOUBLE AND HORIZONTAL SINGLE
+0xd8 U+256a #BOX DRAWINGS VERTICAL SINGLE AND HORIZONTAL DOUBLE
+0xd9 U+2518 #BOX DRAWINGS LIGHT UP AND LEFT
+0xda U+250c #BOX DRAWINGS LIGHT DOWN AND RIGHT
+0xdb U+2588 #FULL BLOCK
+0xdc U+2584 #LOWER HALF BLOCK
+0xdd U+258c #LEFT HALF BLOCK
+0xde U+2590 #RIGHT HALF BLOCK
+0xdf U+2580 #UPPER HALF BLOCK
+0xe0 U+0440 #CYRILLIC SMALL LETTER ER
+0xe1 U+0441 #CYRILLIC SMALL LETTER ES
+0xe2 U+0442 #CYRILLIC SMALL LETTER TE
+0xe3 U+0443 #CYRILLIC SMALL LETTER U
+0xe4 U+0444 #CYRILLIC SMALL LETTER EF
+0xe5 U+0445 #CYRILLIC SMALL LETTER HA
+0xe6 U+0446 #CYRILLIC SMALL LETTER TSE
+0xe7 U+0447 #CYRILLIC SMALL LETTER CHE
+0xe8 U+0448 #CYRILLIC SMALL LETTER SHA
+0xe9 U+0449 #CYRILLIC SMALL LETTER SHCHA
+0xea U+044a #CYRILLIC SMALL LETTER HARD SIGN
+0xeb U+044b #CYRILLIC SMALL LETTER YERU
+0xec U+044c #CYRILLIC SMALL LETTER SOFT SIGN
+0xed U+044d #CYRILLIC SMALL LETTER E
+0xee U+044e #CYRILLIC SMALL LETTER YU
+0xef U+044f #CYRILLIC SMALL LETTER YA
+0xf0 U+0401 #CYRILLIC CAPITAL LETTER IO
+0xf1 U+0451 #CYRILLIC SMALL LETTER IO
+0xf2 U+0490 #CYRILLIC CAPITAL LETTER GHE WITH UPTURN
+0xf3 U+0491 #CYRILLIC SMALL LETTER GHE WITH UPTURN
+0xf4 U+0404 #CYRILLIC CAPITAL LETTER UKRAINIAN IE
+0xf5 U+0454 #CYRILLIC SMALL LETTER UKRAINIAN IE
+0xf6 U+0406 #CYRILLIC CAPITAL LETTER BYELORUSSIAN-UKRAINIAN I
+0xf7 U+0456 #CYRILLIC SMALL LETTER BYELORUSSIAN-UKRAINIAN I
+0xf8 U+0407 #CYRILLIC CAPITAL LETTER YI
+0xf9 U+0457 #CYRILLIC SMALL LETTER YI
+0xfa U+00b7 #MIDDLE DOT
+0xfb U+221a #SQUARE ROOT
+0xfc U+2116 #NUMERO SIGN
+0xfd U+00a4 #CURRENCY SIGN
+0xfe U+25a0 #BLACK SQUARE
+0xff U+00a0 #NO-BREAK SPACE
+
diff --git a/src/chrtrans/cp869_uni.tbl b/src/chrtrans/cp869_uni.tbl
new file mode 100644
index 0000000..d662b83
--- /dev/null
+++ b/src/chrtrans/cp869_uni.tbl
@@ -0,0 +1,160 @@
+# This file has been modified for lynx (see README.tables)
+
+#The MIME name of this charset.
+Mcp869
+
+#Name as a Display Charset (used on Options screen)
+OGreek2 (cp869)
+
+#Codepage number
+C869
+
+# Name: cp869_DOSGreek2 to Unicode table
+# Unicode version: 2.0
+# Table version: 2.00
+# Table format: Format A
+# Date: 04/24/96
+# Authors: Lori Brownell <loribr@microsoft.com>
+# K.D. Chang <a-kchang@microsoft.com>
+# General notes: none
+#
+# Format: Three tab-separated columns
+# Column #1 is the cp869_DOSGreek2 code (in hex)
+# Column #2 is the Unicode (in hex as 0xXXXX)
+# Column #3 is the Unicode name (follows a comment sign, '#')
+#
+# The entries are in cp869_DOSGreek2 order
+#
+##################
+
+0x20-0x7f idem
+#
+#0x80 #UNDEFINED
+#0x81 #UNDEFINED
+#0x82 #UNDEFINED
+#0x83 #UNDEFINED
+#0x84 #UNDEFINED
+#0x85 #UNDEFINED
+0x86 U+0386 #GREEK CAPITAL LETTER ALPHA WITH TONOS
+#0x87 #UNDEFINED
+0x88 U+00b7 #MIDDLE DOT
+0x89 U+00ac #NOT SIGN
+0x8a U+00a6 #BROKEN BAR
+0x8b U+2018 U+02bd #LEFT SINGLE QUOTATION MARK
+0x8c U+2019 U+02bc #RIGHT SINGLE QUOTATION MARK
+0x8d U+0388 #GREEK CAPITAL LETTER EPSILON WITH TONOS
+0x8e U+2015 #HORIZONTAL BAR
+0x8f U+0389 #GREEK CAPITAL LETTER ETA WITH TONOS
+0x90 U+038a #GREEK CAPITAL LETTER IOTA WITH TONOS
+0x91 U+03aa #GREEK CAPITAL LETTER IOTA WITH DIALYTIKA
+0x92 U+038c #GREEK CAPITAL LETTER OMICRON WITH TONOS
+#0x93 #UNDEFINED
+#0x94 #UNDEFINED
+0x95 U+038e #GREEK CAPITAL LETTER UPSILON WITH TONOS
+0x96 U+03ab #GREEK CAPITAL LETTER UPSILON WITH DIALYTIKA
+0x97 U+00a9 #COPYRIGHT SIGN
+0x98 U+038f #GREEK CAPITAL LETTER OMEGA WITH TONOS
+0x99 U+00b2 #SUPERSCRIPT TWO
+0x9a U+00b3 #SUPERSCRIPT THREE
+0x9b U+03ac #GREEK SMALL LETTER ALPHA WITH TONOS
+0x9c U+00a3 #POUND SIGN
+0x9d U+03ad #GREEK SMALL LETTER EPSILON WITH TONOS
+0x9e U+03ae #GREEK SMALL LETTER ETA WITH TONOS
+0x9f U+03af #GREEK SMALL LETTER IOTA WITH TONOS
+0xa0 U+03ca #GREEK SMALL LETTER IOTA WITH DIALYTIKA
+0xa1 U+0390 #GREEK SMALL LETTER IOTA WITH DIALYTIKA AND TONOS
+0xa2 U+03cc #GREEK SMALL LETTER OMICRON WITH TONOS
+0xa3 U+03cd #GREEK SMALL LETTER UPSILON WITH TONOS
+0xa4 U+0391 #GREEK CAPITAL LETTER ALPHA
+0xa5 U+0392 #GREEK CAPITAL LETTER BETA
+0xa6 U+0393 #GREEK CAPITAL LETTER GAMMA
+0xa7 U+0394 #GREEK CAPITAL LETTER DELTA
+0xa8 U+0395 #GREEK CAPITAL LETTER EPSILON
+0xa9 U+0396 #GREEK CAPITAL LETTER ZETA
+0xaa U+0397 #GREEK CAPITAL LETTER ETA
+0xab U+00bd #VULGAR FRACTION ONE HALF
+0xac U+0398 #GREEK CAPITAL LETTER THETA
+0xad U+0399 #GREEK CAPITAL LETTER IOTA
+0xae U+00ab #LEFT-POINTING DOUBLE ANGLE QUOTATION MARK
+0xaf U+00bb #RIGHT-POINTING DOUBLE ANGLE QUOTATION MARK
+0xb0 U+2591 #LIGHT SHADE
+0xb1 U+2592 #MEDIUM SHADE
+0xb2 U+2593 #DARK SHADE
+0xb3 U+2502 #BOX DRAWINGS LIGHT VERTICAL
+0xb4 U+2524 #BOX DRAWINGS LIGHT VERTICAL AND LEFT
+0xb5 U+039a #GREEK CAPITAL LETTER KAPPA
+0xb6 U+039b #GREEK CAPITAL LETTER LAMDA
+0xb7 U+039c #GREEK CAPITAL LETTER MU
+0xb8 U+039d #GREEK CAPITAL LETTER NU
+0xb9 U+2563 #BOX DRAWINGS DOUBLE VERTICAL AND LEFT
+0xba U+2551 #BOX DRAWINGS DOUBLE VERTICAL
+0xbb U+2557 #BOX DRAWINGS DOUBLE DOWN AND LEFT
+0xbc U+255d #BOX DRAWINGS DOUBLE UP AND LEFT
+0xbd U+039e #GREEK CAPITAL LETTER XI
+0xbe U+039f #GREEK CAPITAL LETTER OMICRON
+0xbf U+2510 #BOX DRAWINGS LIGHT DOWN AND LEFT
+0xc0 U+2514 #BOX DRAWINGS LIGHT UP AND RIGHT
+0xc1 U+2534 #BOX DRAWINGS LIGHT UP AND HORIZONTAL
+0xc2 U+252c #BOX DRAWINGS LIGHT DOWN AND HORIZONTAL
+0xc3 U+251c #BOX DRAWINGS LIGHT VERTICAL AND RIGHT
+0xc4 U+2500 #BOX DRAWINGS LIGHT HORIZONTAL
+0xc5 U+253c #BOX DRAWINGS LIGHT VERTICAL AND HORIZONTAL
+0xc6 U+03a0 #GREEK CAPITAL LETTER PI
+0xc7 U+03a1 #GREEK CAPITAL LETTER RHO
+0xc8 U+255a #BOX DRAWINGS DOUBLE UP AND RIGHT
+0xc9 U+2554 #BOX DRAWINGS DOUBLE DOWN AND RIGHT
+0xca U+2569 #BOX DRAWINGS DOUBLE UP AND HORIZONTAL
+0xcb U+2566 #BOX DRAWINGS DOUBLE DOWN AND HORIZONTAL
+0xcc U+2560 #BOX DRAWINGS DOUBLE VERTICAL AND RIGHT
+0xcd U+2550 #BOX DRAWINGS DOUBLE HORIZONTAL
+0xce U+256c #BOX DRAWINGS DOUBLE VERTICAL AND HORIZONTAL
+0xcf U+03a3 #GREEK CAPITAL LETTER SIGMA
+0xd0 U+03a4 #GREEK CAPITAL LETTER TAU
+0xd1 U+03a5 #GREEK CAPITAL LETTER UPSILON
+0xd2 U+03a6 #GREEK CAPITAL LETTER PHI
+0xd3 U+03a7 #GREEK CAPITAL LETTER CHI
+0xd4 U+03a8 #GREEK CAPITAL LETTER PSI
+0xd5 U+03a9 #GREEK CAPITAL LETTER OMEGA
+0xd6 U+03b1 #GREEK SMALL LETTER ALPHA
+0xd7 U+03b2 #GREEK SMALL LETTER BETA
+0xd8 U+03b3 #GREEK SMALL LETTER GAMMA
+0xd9 U+2518 #BOX DRAWINGS LIGHT UP AND LEFT
+0xda U+250c #BOX DRAWINGS LIGHT DOWN AND RIGHT
+0xdb U+2588 #FULL BLOCK
+0xdc U+2584 #LOWER HALF BLOCK
+0xdd U+03b4 #GREEK SMALL LETTER DELTA
+0xde U+03b5 #GREEK SMALL LETTER EPSILON
+0xdf U+2580 #UPPER HALF BLOCK
+0xe0 U+03b6 #GREEK SMALL LETTER ZETA
+0xe1 U+03b7 #GREEK SMALL LETTER ETA
+0xe2 U+03b8 #GREEK SMALL LETTER THETA
+0xe3 U+03b9 #GREEK SMALL LETTER IOTA
+0xe4 U+03ba #GREEK SMALL LETTER KAPPA
+0xe5 U+03bb #GREEK SMALL LETTER LAMDA
+0xe6 U+03bc #GREEK SMALL LETTER MU
+0xe7 U+03bd #GREEK SMALL LETTER NU
+0xe8 U+03be #GREEK SMALL LETTER XI
+0xe9 U+03bf #GREEK SMALL LETTER OMICRON
+0xea U+03c0 #GREEK SMALL LETTER PI
+0xeb U+03c1 #GREEK SMALL LETTER RHO
+0xec U+03c3 #GREEK SMALL LETTER SIGMA
+0xed U+03c2 #GREEK SMALL LETTER FINAL SIGMA
+0xee U+03c4 #GREEK SMALL LETTER TAU
+0xef U+0384 #GREEK TONOS
+0xf0 U+00ad #SOFT HYPHEN
+0xf1 U+00b1 #PLUS-MINUS SIGN
+0xf2 U+03c5 #GREEK SMALL LETTER UPSILON
+0xf3 U+03c6 #GREEK SMALL LETTER PHI
+0xf4 U+03c7 #GREEK SMALL LETTER CHI
+0xf5 U+00a7 #SECTION SIGN
+0xf6 U+03c8 #GREEK SMALL LETTER PSI
+0xf7 U+0385 #GREEK DIALYTIKA TONOS
+0xf8 U+00b0 #DEGREE SIGN
+0xf9 U+00a8 #DIAERESIS
+0xfa U+03c9 #GREEK SMALL LETTER OMEGA
+0xfb U+03cb #GREEK SMALL LETTER UPSILON WITH DIALYTIKA
+0xfc U+03b0 #GREEK SMALL LETTER UPSILON WITH DIALYTIKA AND TONOS
+0xfd U+03ce #GREEK SMALL LETTER OMEGA WITH TONOS
+0xfe U+25a0 #BLACK SQUARE
+0xff U+00a0 #NO-BREAK SPACE
+
diff --git a/src/chrtrans/def7_uni.tbl b/src/chrtrans/def7_uni.tbl
new file mode 100644
index 0000000..14b7ac4
--- /dev/null
+++ b/src/chrtrans/def7_uni.tbl
@@ -0,0 +1,2951 @@
+# $LynxId: def7_uni.tbl,v 1.33 2020/01/21 22:21:19 tom Exp $
+# Default 7bit replacements.
+#
+# This table is very important and should not be excluded from the distribution
+# since this is a default fallback for any 8bit user's "display character set"
+# which (nearly) of 256 chars and could not map a rich Unicode repertoire.
+#
+# Note: there are a few rare replacement strings with trailing spaces
+# which should be enclosed as C strings like "... " to make things obvious
+# (and doing that we should escape \ as \134 and escape " as \" or \042
+# but this is really rare).
+#
+
+#The MIME name of this charset.
+Mus-ascii
+
+# Like any other charset this may be selected as "display character set":
+#Name as a Display Charset (used on Options screen)
+O7 bit approximations (US-ASCII)
+
+# Shall this become the "default" translation table? YES!
+# There has to be exactly one table marked as "default".
+D1
+
+
+# us-ascii characters should not normally pass here,
+# they are always processed directly but let declare them here:
+0x20-0x7e idem
+
+
+# NO-BREAK SPACE,
+# should not happen (processed in the code):
+#U+00a0:NS
+U+00a1:!
+U+00a2:-c-
+U+00a3:-L-
+U+00a4:CUR
+U+00a5:YEN
+U+00a6:|
+U+00a7:S:
+U+00a8:"
+# COPYRIGHT SIGN:
+U+00a9:(c)
+U+00aa:-a
+U+00ab:<<
+U+00ac:NOT
+# SOFT HYPHEN,
+# should not happen (processed in the code):
+#U+00ad:-
+# REGISTERED SIGN:
+U+00ae:(R)
+U+00af:-
+U+00b0:DEG
+U+00b1:+-
+U+00b2:^2
+U+00b3:^3
+U+00b4:'
+# My -> u
+U+00b5:u
+U+00b6:P:
+U+00b7:.
+U+00b8:,
+U+00b9:^1
+U+00ba:-o
+U+00bb:>>
+U+00bc: 1/4
+U+00bd: 1/2
+U+00be: 3/4
+U+00bf:?
+0x41 U+00c0-U+00c3
+U+00c4 "Ae" # &Auml;, not the best choice for some languages.
+U+00c5:AA
+U+00c6:AE
+U+00c7:C,
+0x45 U+00c8-U+00cb
+0x49 U+00cc-U+00cf
+U+00d0:D-
+0x4e U+00d1
+0x4f U+00d2-U+00d5
+U+00d6 "Oe" # &Ouml;, not the best choice for some languages.
+U+00d7: *
+U+00d8:O/
+0x55 U+00d9-U+00db
+U+00dc "Ue" # &Uuml;, not the best choice for some languages.
+0x59 U+00dd
+U+00de:TH
+U+00df:ss
+U+00e0:`a
+0x61 U+00e1-U+00e3
+U+00e4 "ae" # &auml;, not the best choice for some languages.
+U+00e5:aa
+U+00e6:ae
+U+00e7:c,
+0x65 U+00e8-U+00eb
+U+00ec:`i
+0x69 U+00ed-U+00ef
+U+00f0:d-
+0x6e U+00f1
+0x6f U+00f2-U+00f5
+U+00f6 "oe" # &ouml;, not the best choice for some languages.
+U+00f7:-:
+U+00f8:o/
+0x75 U+00f9-U+00fb
+U+00fc "ue" # &uuml;, not the best choice for some languages.
+0x79 U+00fd
+U+00fe:th
+0x79 U+00ff
+# end of latin-1 repertoire
+0x41 U+0100 U+0102 U+0104 # A
+0x61 U+0101 U+0103 U+0105 # a
+0x43 U+0106 U+010a U+010c # C
+U+0108 "Ch"
+# The following line is an example for mapping several accented versions
+# of small letter 'c' to 'c':
+0x63 U+0107 U+010b U+010d # c
+U+0109 "ch"
+0x44 U+010e
+0x64 U+010f
+U+0110:D/
+U+0111:d/
+0x45 U+0112 U+0114 U+0116 U+0118 U+011a # E
+0x65 U+0113 U+0115 U+0117 U+0119 U+011b # e
+0x47 U+011e U+0120 U+0122 # G
+0x67 U+011f U+0121 U+0123 # g
+U+011C "Gh"
+U+011D "gh"
+U+0124 "Hh"
+U+0125" "hh"
+U+0126:H/
+0x48 U+0127 # LATIN SMALL LETTER H BAR -> H
+0x49 U+0128 U+012a U+012c U+012e U+0130 # I
+0x69 U+0129 U+012b U+012d U+012f U+0131 # i
+U+0132:IJ
+U+0133:ij
+U+0134 "Jh"
+U+0135 "jh"
+0x4b U+0136
+0x6b U+0137
+U+0138:kk
+0x4c U+0139 U+013b U+013d # L
+0x6c U+013a U+013c U+013e # l
+U+013f:L.
+U+0140:l.
+U+0141:L/
+U+0142:l/
+0x4e U+0143 U+0145 U+0147 # N
+0x6e U+0144 U+0146 U+0148 # n
+U+0149:'n
+U+014a:NG
+0x4e U+014B # LATIN SMALL LETTER ENG -> N
+0x4f U+014c U+014e # O
+0x6f U+014d U+014f # o
+U+0150:O"
+U+0151:o"
+U+0152:OE
+U+0153:oe
+0x52 U+0154 U+0156 U+0158 # R
+0x72 U+0155 U+0157 U+0159 # r
+0x53 U+015a U+015e U+0160 U+0218 # S
+0x73 U+015b U+015f U+0161 U+0219 # s
+U+015C "Sh"
+U+015d "sh"
+0x54 U+0162 U+0164 U+021a # T
+0x74 U+0163 U+0165 U+021b # t
+U+0166:T/
+U+0167:t/
+0x55 U+0168 U+016a U+016c U+016e U+0172 # U
+0x75 U+0169 U+016b U+016d U+016f U+0173 # u
+U+0170:U"
+U+0171:u"
+0x57 U+0174
+0x77 U+0175
+0x59 U+0176 U+0178
+0x79 U+0177
+0x5a U+0179 U+017b U+017d U+021d
+0x7a U+017a U+017c U+017e
+U+017f:s1
+U+0187:C2
+U+0188:c2
+U+0191:F2
+U+0192: f
+U+0198:K2
+U+0199:k2
+U+01a0:O9
+U+01a1:o9
+U+01a2:OI
+U+01a3:oi
+U+01a6:yr
+U+01af:U9
+U+01b0:u9
+U+01b5:Z/
+U+01b6:z/
+U+01b7:ED
+0x41 U+01cd
+0x61 U+01ce
+0x49 U+01cf
+0x69 U+01d0
+0x4f U+01d1
+0x6f U+01d2
+0x55 U+01d3
+0x75 U+01d4
+U+01d5:U:-
+U+01d6:u:-
+U+01d7:U:'
+U+01d8:u:'
+U+01d9:U:<
+U+01da:u:<
+U+01db:U:!
+U+01dc:u:!
+U+01de:A1
+U+01df:a1
+U+01e0:A7
+U+01e1:a7
+U+01e2:A3
+U+01e3:a3
+U+01e4:G/
+U+01e5:g/
+0x47 U+01e6
+0x67 U+01e7
+0x4b U+01e8
+0x6b U+01e9
+0x4f U+01ea
+0x6f U+01eb
+U+01ec:O1
+U+01ed:o1
+U+01ee:EZ
+U+01ef:ez
+0x6a U+01f0
+0x47 U+01f4
+0x67 U+01f5
+U+01fa:AA'
+U+01fb:aa'
+U+01fc:AE'
+U+01fd:ae'
+U+01fe:O/'
+U+01ff:o/'
+U+0200:A!!
+U+0201:a!!
+U+0202:A)
+U+0203:a)
+U+0204:E!!
+U+0205:e!!
+U+0206:E)
+U+0207:e)
+U+0208:I!!
+U+0209:i!!
+U+020a:I)
+U+020b:i)
+U+020c:O!!
+U+020d:o!!
+U+020e:O)
+U+020f:o)
+U+0210:R!!
+U+0211:r!!
+U+0212:R)
+U+0213:r)
+U+0214:U!!
+U+0215:u!!
+U+0216:U)
+U+0217:u)
+
+# IPA symbols, from
+# Linkname: FAQ: Representing IPA Phonetics in ASCII
+# URL: http://www.hpl.hp.com/personal/Evan_Kirshenbaum/IPA/faq.html
+# (corrected in Russian Cyrillic area).
+# (corrected in Greek area).
+#
+0x41 U+0251 # LATIN SMALL LETTER SCRIPT A -> A
+U+0252:A.
+U+0253:b`
+0x4f U+0254 # LATIN SMALL LETTER OPEN O -> O
+U+0256:d.
+U+0257:d`
+U+0258:@<umd>
+0x40 U+0259 # LATIN SMALL LETTER SCHWA -> @
+0x52 U+025A # LATIN SMALL LETTER SCHWA HOOK -> R
+0x45 U+025B # LATIN SMALL LETTER EPSILON -> E
+U+025c:V"
+U+025d:R<umd>
+U+025e:O"
+0x4a U+025F # LATIN SMALL LETTER DOTLESS J BAR -> J
+U+0260:g`
+0x67 U+0261 # LATIN SMALL LETTER SCRIPT G
+0x47 U+0262 # LATIN LETTER SMALL CAPITAL G
+0x51 U+0263 # LATIN SMALL LETTER GAMMA -> Q
+U+0264:o-
+U+0265:j<rnd>
+U+0266:h<?>
+U+0268:i"
+0x49 U+026A U+0269 # LATIN LETTER SMALL CAPITAL I, LATIN SMALL LETTER IOTA
+0x4c U+026B # LATIN SMALL LETTER L WITH MIDDLE TILDE
+0x4c U+026C # LATIN SMALL LETTER L BELT
+U+026d:l.
+U+026e:z<lat>
+U+026f:u-
+U+0270:j<vel>
+0x4d U+0271 # LATIN SMALL LETTER M HOOK
+U+0273:n.
+U+0274:n"
+U+0275:@.
+U+0276:&.
+0x55 U+0277 # LATIN SMALL LETTER CLOSED OMEGA -> U
+0x72 U+0279 # LATIN SMALL LETTER TURNED R -> r
+U+027a:*<lat>
+U+027b:r.
+U+027d:*.
+0x2a U+027E # LATIN SMALL LETTER FISHHOOK R -> *
+U+0280:r"
+0x52 U+0280 # LATIN LETTER SMALL CAPITAL R -> R
+U+0281:g"
+U+0282:s.
+0x53 U+0283 # LATIN SMALL LETTER ESH -> S
+U+0284:J`
+U+0287:t!
+U+0288:t.
+U+0289:u"
+0x55 U+028A # LATIN SMALL LETTER UPSILON -> U
+U+028b:r<lbd>
+0x56 U+028C # LATIN SMALL LETTER TURNED V -> V
+U+028d:w<vls>
+U+028e:l^
+U+028f:I.
+U+0290:z.
+U+0292:Z
+0x3f U+0294 # LATIN SMALL LETTER GLOTTAL STOP -> ?
+U+0295:H<vcd>
+U+0296:l!
+U+0297:c!
+U+0298:p!
+U+0299:b<trl>
+U+029b:G`
+0x6a U+029d # LATIN SMALL LETTER CROSSED-TAIL J
+U+029e:k!
+0x4c U+029F # LATIN LETTER SMALL CAPITAL L
+U+02a0:q`
+U+02a4:d3
+U+02a6:ts
+U+02a7:tS
+U+02b0:<h>
+U+02b1:<?>
+0x3b U+02b2 U+0321
+U+02b3:<r>
+U+02b7:<w>
+U+02bb:;S
+0x27 U+02bc
+0x27 U+02bd
+U+02c6:^
+U+02c7:'<
+U+02c8:|
+U+02c9:1-
+U+02cb:1!
+0x3a U+02d0
+U+02d1 ":\\"
+0x2b U+02d6
+0x2d U+02d7
+U+02d8:'(
+U+02d9:'.
+U+02da:'0
+U+02db:';
+U+02dc:~
+U+02dd:'"
+U+02e5:_T
+U+02e6:_H
+U+02e7:_M
+U+02e8:_L
+U+02e9:_B
+U+02ec:_v
+U+02ee:''
+0x60 U+0300
+0x27 U+0301
+0x5e U+0302
+0x7e U+0303 U+0334
+U+030b:''
+0x7c U+030d
+U+030e:||
+U+030f:``
+0x2e U+0322 U+0323
+U+0324:<?>
+U+0325:<o>
+0x2c U+0326 U+0327
+0x2d U+0329
+0x5b U+032a
+U+032b:<w>
+U+0334:<H>
+0x2f U+0337 U+0338
+U+0340:`
+U+0341:'
+U+0342:~
+U+0344:'%
+U+0345:j3
+U+0347:=
+U+0360:~~
+U+0374:'
+U+0375:,
+U+037a:j3
+U+037e:?%
+U+0384:'*
+U+0385:'%
+# Greek letters
+U+0386:A'
+U+0387:.*
+U+0388:E'
+U+0389:Y%
+U+038a:I'
+U+038c:O'
+U+038e:U%
+U+038f:W%
+U+0390:i3
+U+0391:A
+U+0392:B
+U+0393:G
+U+0394:D
+U+0395:E
+U+0396:Z
+U+0397:Y
+U+0398:TH
+U+0399:I
+U+039a:K
+U+039b:L
+U+039c:M
+U+039d:N
+U+039e:C
+U+039f:O
+U+03a0:P
+U+03a1:R
+U+03a3:S
+U+03a4:T
+U+03a5:U
+U+03a6:F
+U+03a7:X
+U+03a8:Q
+U+03a9:W*
+U+03aa:J
+U+03ab:V*
+U+03ac:a'
+U+03ad:e'
+U+03ae:y%
+U+03af:i'
+U+03b0:u3
+U+03b1:a
+U+03b2:b
+U+03b3:g
+U+03b4:d
+U+03b5:e
+U+03b6:z
+U+03b7:y
+U+03b8:th
+U+03b9:i
+U+03ba:k
+U+03bb:l
+U+03bc:m
+U+03bd:n
+U+03be:c
+U+03bf:o
+U+03c0:p
+U+03c1:r
+U+03c2:*s
+U+03c3:s
+U+03c4:t
+U+03c5:u
+U+03c6:f
+U+03c7:x
+U+03c8:q
+U+03c9:w
+U+03ca:j
+U+03cb:v*
+U+03cc:o'
+U+03cd:u%
+U+03ce:w%
+# Greek symbols
+U+03d0 "beta "
+U+03d1 "theta "
+U+03d2 "upsi "
+U+03d5 "phi "
+U+03d6 "pi "
+U+03d7:k.
+U+03da:T3
+U+03db:t3
+U+03dc:M3
+U+03dd:m3
+U+03de:K3
+U+03df:k3
+U+03e0:P3
+U+03e1:p3
+U+03f0 "kappa "
+U+03f1 "rho "
+U+03f3:J
+U+03f4:'%
+U+03f5:j3
+# Cyrillic capital letters
+U+0402:D%
+U+0403:G%
+U+0404:IE
+U+0405:DS
+U+0406:II
+U+0407:YI
+U+0408:J%
+U+0409:LJ
+U+040a:NJ
+U+040b:Ts
+U+040c:KJ
+U+040e:V%
+U+040f:DZ
+# Russian Cyrillic letters, transliterated
+U+0401:IO
+U+0410:A
+U+0411:B
+U+0412:V
+U+0413:G
+U+0414:D
+U+0415:E
+U+0416:ZH
+U+0417:Z
+U+0418:I
+U+0419:J
+U+041a:K
+U+041b:L
+U+041c:M
+U+041d:N
+U+041e:O
+U+041f:P
+U+0420:R
+U+0421:S
+U+0422:T
+U+0423:U
+U+0424:F
+U+0425:H
+U+0426:C
+U+0427:CH
+U+0428:SH
+U+0429:SCH
+U+042a:"
+U+042b:Y
+U+042c:'
+U+042d:`E
+U+042e:YU
+U+042f:YA
+U+0430:a
+U+0431:b
+U+0432:v
+U+0433:g
+U+0434:d
+U+0435:e
+U+0436:zh
+U+0437:z
+U+0438:i
+U+0439:j
+U+043a:k
+U+043b:l
+U+043c:m
+U+043d:n
+U+043e:o
+U+043f:p
+U+0440:r
+U+0441:s
+U+0442:t
+U+0443:u
+U+0444:f
+U+0445:h
+U+0446:c
+U+0447:ch
+U+0448:sh
+U+0449:sch
+U+044a:"
+U+044b:y
+U+044c:'
+U+044d:`e
+U+044e:yu
+U+044f:ya
+U+0451:io
+# end of Russian Cyrillic letters.
+# Cyrillic small letters (and some archaic)
+U+0452:d%
+U+0453:g%
+U+0454:ie
+U+0455:ds
+U+0456:ii
+U+0457:yi
+U+0458:j%
+U+0459:lj
+U+045a:nj
+U+045b:ts
+U+045c:kj
+U+045e:v%
+U+045f:dz
+U+0462:Y3
+U+0463:y3
+U+046a:O3
+U+046b:o3
+U+0472:F3
+U+0473:f3
+U+0474:V3
+U+0475:v3
+U+0480:C3
+U+0481:c3
+U+0490:G3
+U+0491:g3
+U+04d4:AE
+U+04d5:ae
+# These may make Yiddish slightly more readable, until we have
+# something better.
+
+0x69 U+05b4 # i
+0x61 U+05b7 # a
+0x6f U+05b8 # o
+0x75 U+05bc # u
+0x68 U+05bf # h
+0x3a U+05c2 # :
+
+0x76 U+05f0 # v
+U+05f1:oy
+U+05f2:ey
+
+# U+05d0:A+
+0x23 U+05d0 # '#'
+
+U+05d1:B+
+U+05d2:G+
+U+05d3:D+
+U+05d4:H+
+U+05d5:W+
+U+05d6:Z+
+U+05d7:X+
+U+05d8:Tj
+U+05d9:J+
+U+05da:K%
+U+05db:K+
+U+05dc:L+
+U+05dd:M%
+U+05de:M+
+U+05df:N%
+U+05e0:N+
+U+05e1:S+
+U+05e2:E+
+U+05e3:P%
+U+05e4:P+
+U+05e5:Zj
+U+05e6:ZJ
+U+05e7:Q+
+U+05e8:R+
+U+05e9:Sh
+U+05ea:T+
+
+U+060c:,+
+U+061b:;+
+U+061f:?+
+U+0621:H'
+U+0622:aM
+U+0623:aH
+U+0624:wH
+U+0625:ah
+U+0626:yH
+U+0627:a+
+U+0628:b+
+U+0629:tm
+U+062a:t+
+U+062b:tk
+U+062c:g+
+U+062d:hk
+U+062e:x+
+U+062f:d+
+U+0630:dk
+U+0631:r+
+U+0632:z+
+U+0633:s+
+U+0634:sn
+U+0635:c+
+U+0636:dd
+U+0637:tj
+U+0638:zH
+U+0639:e+
+U+063a:i+
+U+0640:++
+U+0641:f+
+U+0642:q+
+U+0643:k+
+U+0644:l+
+U+0645:m+
+U+0646:n+
+U+0647:h+
+U+0648:w+
+U+0649:j+
+U+064a:y+
+U+064b::+
+U+064c:"+
+U+064d:=+
+U+064e:/+
+U+064f:'+
+U+0650:1+
+U+0651:3+
+U+0652:0+
+U+0660:0a
+U+0661:1a
+U+0662:2a
+U+0663:3a
+U+0664:4a
+U+0665:5a
+U+0666:6a
+U+0667:7a
+U+0668:8a
+U+0669:9a
+U+0670:aS
+U+067e:p+
+U+0681:hH
+U+0686:tc
+U+0698:zj
+U+06a4:v+
+U+06af:gf
+U+06f0:0a
+U+06f1:1a
+U+06f2:2a
+U+06f3:3a
+U+06f4:4a
+U+06f5:5a
+U+06f6:6a
+U+06f7:7a
+U+06f8:8a
+U+06f9:9a
+
+# Replacement strings for Ethiopic characters
+U+1200:he
+U+1201:hu
+U+1202:hi
+U+1203:ha
+U+1204:hE
+0x68 U+1205 #:h
+U+1206:ho
+U+1208:le
+U+1209:lu
+U+120A:li
+U+120B:la
+U+120C:lE
+0x6c U+120D #:l
+U+120E:lo
+U+120F:lWa
+U+1210:He
+U+1211:Hu
+U+1212:Hi
+U+1213:Ha
+U+1214:HE
+0x48 U+1215 #:H
+U+1216:Ho
+U+1217:HWa
+U+1218:me
+U+1219:mu
+U+121A:mi
+U+121B:ma
+U+121C:mE
+0x6d U+121D #:m
+U+121E:mo
+U+121F:mWa
+U+1220:`se
+U+1221:`su
+U+1222:`si
+U+1223:`sa
+U+1224:`sE
+U+1225:`s
+U+1226:`so
+U+1227:`sWa
+U+1228:re
+U+1229:ru
+U+122A:ri
+U+122B:ra
+U+122C:rE
+0x72 U+122D #:r
+U+122E:ro
+U+122F:rWa
+U+1230:se
+U+1231:su
+U+1232:si
+U+1233:sa
+U+1234:sE
+0x73 U+1235 #:s
+U+1236:so
+U+1237:sWa
+U+1238:xe
+U+1239:xu
+U+123A:xi
+U+123B:xa
+U+123C:xE
+U+123D:xa
+U+123E:xo
+U+123F:xWa
+U+1240:qe
+U+1241:qu
+U+1242:qi
+U+1243:qa
+U+1244:qE
+0x71 U+1245 #:q
+U+1246:qo
+U+1248:qWe
+U+124A:qWi
+U+124B:qWa
+U+124C:qWE
+U+124D:qW
+U+1250:Qe
+U+1251:Qu
+U+1252:Qi
+U+1253:Qa
+U+1254:QE
+0x51 U+1255 #:Q
+U+1256:Qo
+U+1258:QWe
+U+125A:QWi
+U+125B:QWa
+U+125C:QWE
+U+125D:QW
+U+1260:be
+U+1261:bu
+U+1262:bi
+U+1263:ba
+U+1264:bE
+0x62 U+1265 #:b
+U+1266:bo
+U+1267:bWa
+U+1268:ve
+U+1269:vu
+U+126A:vi
+U+126B:va
+U+126C:vE
+0x76 U+126D #:v
+U+126E:vo
+U+126F:vWa
+U+1270:te
+U+1271:tu
+U+1272:ti
+U+1273:ta
+U+1274:tE
+0x74 U+1275 #:t
+U+1276:to
+U+1277:tWa
+U+1278:ce
+U+1279:cu
+U+127A:ci
+U+127B:ca
+U+127C:cE
+0x63 U+127D #:c
+U+127E:co
+U+127F:cWa
+U+1280:`he
+U+1281:`hu
+U+1282:`hi
+U+1283:`ha
+U+1284:`hE
+U+1285:`h
+U+1286:`ho
+U+1288:hWe
+U+128A:hWi
+U+128B:hWa
+U+128C:hWE
+U+128D:hW
+U+1290:na
+U+1291:nu
+U+1292:ni
+U+1293:na
+U+1294:nE
+0x6e U+1295 #:n
+U+1296:no
+U+1297:nWa
+U+1298:Ne
+U+1299:Nu
+U+129A:Ni
+U+129B:Na
+U+129C:NE
+0x4e U+129D #:N
+U+129E:No
+U+129F:NWa
+0x65 U+12A0 #:e
+0x75 U+12A1 #:u
+0x69 U+12A2 #:i
+0x61 U+12A3 #:a
+0x45 U+12A4 #:E
+0x49 U+12A5 #:I
+0x6f U+12A6 #:o
+U+12A7:e3
+U+12A8:ke
+U+12A9:ku
+U+12AA:ki
+U+12AB:ka
+U+12AC:kE
+0x6b U+12AD #:k
+U+12AE:ko
+U+12B0:kWe
+U+12B2:kWi
+U+12B3:kWa
+U+12B4:kWE
+U+12B5:kW
+U+12B8:Ke
+U+12B9:Ku
+U+12BA:Ki
+U+12BB:Ka
+U+12BC:KE
+0x4b U+12BD #:K
+U+12BE:Ko
+U+12C0:KWe
+U+12C2:KWi
+U+12C3:KWa
+U+12C4:KWE
+U+12C5:KW
+U+12C8:we
+U+12C9:wu
+U+12CA:wi
+U+12CB:wa
+U+12CC:wE
+0x77 U+12CD #:w
+U+12CE:wo
+U+12D0:`e
+U+12D1:`u
+U+12D2:`i
+U+12D3:`a
+U+12D4:`E
+U+12D5:`I
+U+12D6:`o
+U+12D8:ze
+U+12D9:zu
+U+12DA:zi
+U+12DB:za
+U+12DC:zE
+0x7a U+12DD #:z
+U+12DE:zo
+U+12DF:zWa
+U+12E0:Ze
+U+12E1:Zu
+U+12E2:Zi
+U+12E3:Za
+U+12E4:ZE
+0x5a U+12E5 #:Z
+U+12E6:Zo
+U+12E7:ZWa
+U+12E8:ye
+U+12E9:yu
+U+12EA:yi
+U+12EB:ya
+U+12EC:yE
+0x79 U+12ED #:y
+U+12EE:yo
+U+12EF:yWa
+U+12F0:de
+U+12F1:du
+U+12F2:di
+U+12F3:da
+U+12F4:dE
+0x64 U+12F5 #:d
+U+12F6:do
+U+12F7:dWa
+U+12F8:De
+U+12F9:Du
+U+12FA:Di
+U+12FB:Da
+U+12FC:DE
+0x44 U+12FD #:D
+U+12FE:Do
+U+12FF:DWa
+U+1300:je
+U+1301:ju
+U+1302:ji
+U+1303:ja
+U+1304:jE
+0x6a U+1305 #:j
+U+1306:jo
+U+1307:jWa
+U+1308:ga
+U+1309:gu
+U+130A:gi
+U+130B:ga
+U+130C:gE
+0x67 U+130D #:g
+U+130E:go
+U+1310:gWu
+U+1312:gWi
+U+1313:gWa
+U+1314:gWE
+U+1315:gW
+U+1318:Ge
+U+1319:Gu
+U+131A:Gi
+U+131B:Ga
+U+131C:GE
+0x47 U+131D #:G
+U+131E:Go
+U+131F:GWa
+U+1320:Te
+U+1321:Tu
+U+1322:Ti
+U+1323:Ta
+U+1324:TE
+0x54 U+1325 #:T
+U+1326:To
+U+1327:TWa
+U+1328:Ce
+U+1329:Ca
+U+132A:Cu
+U+132B:Ca
+U+132C:CE
+0x43 U+132D #:C
+U+132E:Co
+U+132F:CWa
+U+1330:Pe
+U+1331:Pu
+U+1332:Pi
+U+1333:Pa
+U+1334:PE
+0x50 U+1335 #:P
+U+1336:Po
+U+1337:PWa
+U+1338:SWe
+U+1339:SWu
+U+133A:SWi
+U+133B:SWa
+U+133C:SWE
+U+133D:SW
+U+133E:SWo
+U+133F:SWa
+U+1340:`Sa
+U+1341:`Su
+U+1342:`Si
+U+1343:`Sa
+U+1344:`SE
+U+1345:`S
+U+1346:`So
+U+1348:fa
+U+1349:fu
+U+134A:fi
+U+134B:fa
+U+134C:fE
+0x6f U+134D #:f
+U+134E:fo
+U+134F:fWa
+U+1350:pe
+U+1351:pu
+U+1352:pi
+U+1353:pa
+U+1354:pE
+0x70 U+1355 #:p
+U+1356:po
+U+1357:pWa
+U+1358:mYa
+U+1359:rYa
+U+135A:fYa
+# ETHIOPIC SPACE U+1360 mapped to ASCII space
+0x20 U+1360
+0x3a U+1361 #::
+U+1362:::
+0x2c U+1363 #:,
+U+1364:;
+U+1365:-:
+U+1366::-
+U+1367:`?
+U+1368::|:
+U+1369:`1
+U+136A:`2
+U+136B:`3
+U+136C:`4
+U+136D:`5
+U+136E:`6
+U+136F:`7
+U+1370:`8
+U+1371:`9
+U+1372:`10
+U+1373:`20
+U+1374:`30
+U+1375:`40
+U+1376:`50
+U+1377:`60
+U+1378:`70
+U+1379:`80
+U+137A:`90
+U+137B:`100
+U+137C:`10000
+
+
+U+1e00:A-0
+U+1e01:a-0
+U+1e02:B.
+U+1e03:b.
+U+1e04:B-.
+U+1e05:b-.
+U+1e06:B_
+U+1e07:b_
+U+1e08:C,'
+U+1e09:c,'
+U+1e0a:D.
+U+1e0b:d.
+U+1e0c:D-.
+U+1e0d:d-.
+U+1e0e:D_
+U+1e0f:d_
+U+1e10:D,
+U+1e11:d,
+U+1e12:D->
+U+1e13:d->
+U+1e14:E-!
+U+1e15:e-!
+U+1e16:E-'
+U+1e17:e-'
+U+1e18:E->
+U+1e19:e->
+U+1e1a:E-?
+U+1e1b:e-?
+U+1e1c:E,(
+U+1e1d:e,(
+U+1e1e:F.
+U+1e1f:f.
+U+1e20:G-
+U+1e21:g-
+U+1e22:H.
+U+1e23:h.
+U+1e24:H-.
+U+1e25:h-.
+U+1e26:H:
+U+1e27:h:
+U+1e28:H,
+U+1e29:h,
+U+1e2a:H-(
+U+1e2b:h-(
+U+1e2c:I-?
+U+1e2d:i-?
+U+1e2e:I:'
+U+1e2f:i:'
+U+1e30:K'
+U+1e31:k'
+U+1e32:K-.
+U+1e33:k-.
+U+1e34:K_
+U+1e35:k_
+U+1e36:L-.
+U+1e37:l-.
+U+1e38:L--.
+U+1e39:l--.
+U+1e3a:L_
+U+1e3b:l_
+U+1e3c:L->
+U+1e3d:l->
+U+1e3e:M'
+U+1e3f:m'
+U+1e40:M.
+U+1e41:m.
+U+1e42:M-.
+U+1e43:m-.
+U+1e44:N.
+U+1e45:n.
+U+1e46:N-.
+U+1e47:n-.
+U+1e48:N_
+U+1e49:n_
+U+1e4a:N->
+U+1e4b:n->
+U+1e4c:O?'
+U+1e4d:o?'
+U+1e4e:O?:
+U+1e4f:o?:
+U+1e50:O-!
+U+1e51:o-!
+U+1e52:O-'
+U+1e53:o-'
+U+1e54:P'
+U+1e55:p'
+U+1e56:P.
+U+1e57:p.
+U+1e58:R.
+U+1e59:r.
+U+1e5a:R-.
+U+1e5b:r-.
+U+1e5c:R--.
+U+1e5d:r--.
+U+1e5e:R_
+U+1e5f:r_
+U+1e60:S.
+U+1e61:s.
+U+1e62:S-.
+U+1e63:s-.
+U+1e64:S'.
+U+1e65:s'.
+U+1e66:S<.
+U+1e67:s<.
+U+1e68:S.-.
+U+1e69:s.-.
+U+1e6a:T.
+U+1e6b:t.
+U+1e6c:T-.
+U+1e6d:t-.
+U+1e6e:T_
+U+1e6f:t_
+U+1e70:T->
+U+1e71:t->
+U+1e72:U--:
+U+1e73:u--:
+U+1e74:U-?
+U+1e75:u-?
+U+1e76:U->
+U+1e77:u->
+U+1e78:U?'
+U+1e79:u?'
+U+1e7a:U-:
+U+1e7b:u-:
+U+1e7c:V?
+U+1e7d:v?
+U+1e7e:V-.
+U+1e7f:v-.
+U+1e80:W!
+U+1e81:w!
+U+1e82:W'
+U+1e83:w'
+U+1e84:W:
+U+1e85:w:
+U+1e86:W.
+U+1e87:w.
+U+1e88:W-.
+U+1e89:w-.
+U+1e8a:X.
+U+1e8b:x.
+U+1e8c:X:
+U+1e8d:x:
+U+1e8e:Y.
+U+1e8f:y.
+U+1e90:Z>
+U+1e91:z>
+U+1e92:Z-.
+U+1e93:z-.
+U+1e94:Z_
+U+1e95:z_
+U+1e96:h_
+U+1e97:t:
+U+1e98:w0
+U+1e99:y0
+U+1ea0:A-.
+U+1ea1:a-.
+U+1ea2:A2
+U+1ea3:a2
+U+1ea4:A>'
+U+1ea5:a>'
+U+1ea6:A>!
+U+1ea7:a>!
+U+1ea8:A>2
+U+1ea9:a>2
+U+1eaa:A>?
+U+1eab:a>?
+U+1eac:A>-.
+U+1ead:a>-.
+U+1eae:A('
+U+1eaf:a('
+U+1eb0:A(!
+U+1eb1:a(!
+U+1eb2:A(2
+U+1eb3:a(2
+U+1eb4:A(?
+U+1eb5:a(?
+U+1eb6:A(-.
+U+1eb7:a(-.
+U+1eb8:E-.
+U+1eb9:e-.
+U+1eba:E2
+U+1ebb:e2
+U+1ebc:E?
+U+1ebd:e?
+U+1ebe:E>'
+U+1ebf:e>'
+U+1ec0:E>!
+U+1ec1:e>!
+U+1ec2:E>2
+U+1ec3:e>2
+U+1ec4:E>?
+U+1ec5:e>?
+U+1ec6:E>-.
+U+1ec7:e>-.
+U+1ec8:I2
+U+1ec9:i2
+U+1eca:I-.
+U+1ecb:i-.
+U+1ecc:O-.
+U+1ecd:o-.
+U+1ece:O2
+U+1ecf:o2
+U+1ed0:O>'
+U+1ed1:o>'
+U+1ed2:O>!
+U+1ed3:o>!
+U+1ed4:O>2
+U+1ed5:o>2
+U+1ed6:O>?
+U+1ed7:o>?
+U+1ed8:O>-.
+U+1ed9:o>-.
+U+1eda:O9'
+U+1edb:o9'
+U+1edc:O9!
+U+1edd:o9!
+U+1ede:O92
+U+1edf:o92
+U+1ee0:O9?
+U+1ee1:o9?
+U+1ee2:O9-.
+U+1ee3:o9-.
+U+1ee4:U-.
+U+1ee5:u-.
+U+1ee6:U2
+U+1ee7:u2
+U+1ee8:U9'
+U+1ee9:u9'
+U+1eea:U9!
+U+1eeb:u9!
+U+1eec:U92
+U+1eed:u92
+U+1eee:U9?
+U+1eef:u9?
+U+1ef0:U9-.
+U+1ef1:u9-.
+U+1ef2:Y!
+U+1ef3:y!
+U+1ef4:Y-.
+U+1ef5:y-.
+U+1ef6:Y2
+U+1ef7:y2
+U+1ef8:Y?
+U+1ef9:y?
+0x61 U+1f00
+U+1f01:ha
+U+1f02:`a
+U+1f03:h`a
+U+1f04:a'
+U+1f05:ha'
+U+1f06:a~
+U+1f07:ha~
+0x41 U+1f08
+U+1f09:hA
+U+1f0a:`A
+U+1f0b:h`A
+U+1f0c:A'
+U+1f0d:hA'
+U+1f0e:A~
+U+1f0f:hA~
+U+1f11:he
+U+1f19:hE
+U+1f31:hi
+U+1f39:hI
+U+1f41:ho
+U+1f49:hO
+U+1f51:hu
+U+1f59:hU
+U+1fbf:,,
+U+1fc0:?*
+U+1fc1:?:
+U+1fcd:,!
+U+1fce:,'
+U+1fcf:?,
+U+1fdd:;!
+U+1fde:;'
+U+1fdf:?;
+U+1fe5:rh
+U+1fec:Rh
+U+1fed:!:
+U+1fef:!*
+U+1ffe:;;
+# General punctuation:
+0x20 U+2000 U+2002 U+2004-U+200A U+205F # spaces
+U+2001 " "
+U+2003 " "
+U+200e:(->)
+U+200f:(<-)
+U+200a:
+0x2d U+2010 U+2011 U+2013 U+2015 # hyphen-like
+U+2014 "--"
+U+2016:||
+U+2017:=2
+0x60 U+2018 # left single quotation mark <`>
+0x27 U+2019-U+201b # various single quotation marks <'>
+0x22 U+201c-U+201f # various double quotation marks <">
+U+2020:/-
+U+2021:/=
+U+2022 " o "
+U+2023 " > "
+0x2e U+2024
+U+2025:..
+U+2026:...
+U+2027:.
+U+2028 "\015"
+U+2029 "\015\012"
+
+# Don't want to see these:
+# POP DIRECTIONAL FORMATTING 202C
+U+202c:
+# LEFT-TO-RIGHT OVERRIDE 202D
+U+202d:
+
+U+202b ""
+U+202f ""
+U+2030: 0/00
+U+2031: 0/000
+U+2032:'
+U+2033:''
+U+2034:'''
+U+2035:`
+U+2036:``
+U+2037:```
+U+2038:^
+U+2039:<
+U+203a:>
+U+203b::X
+U+203c:!!
+U+203d:?!
+U+203e:'-
+U+2042:***
+U+2043 " - "
+U+2044:/
+U+2045:[-
+U+2046:-]
+U+2047:??
+U+2048:?!
+U+2049:!?
+U+204b:|P
+U+204e:*
+U+2051:**
+U+2052:./.
+U+2053:~
+U+2056 " .: "
+U+2057:''''
+U+2058 " .:. "
+U+2059 " :.: "
+U+205a " : "
+U+205b " .:. "
+U+205c ":+:"
+U+2044:/
+U+2047:??
+U+2048:?!
+U+2049:!?
+# end of General punctuation.
+U+2070:^0
+U+2074:^4
+U+2075:^5
+U+2076:^6
+U+2077:^7
+U+2078:^8
+U+2079:^9
+U+207a:^+
+U+207b:^-
+U+207c:^=
+U+207d:^(
+U+207e:^)
+U+207f:^n
+U+2080:_0
+U+2081:_1
+U+2082:_2
+U+2083:_3
+U+2084:_4
+U+2085:_5
+U+2086:_6
+U+2087:_7
+U+2088:_8
+U+2089:_9
+U+208a:_+
+U+208b:_-
+U+208c:_=
+U+208d:(
+U+208e:)
+# Old euro currency sign glyph:
+#U+20A0:CE
+U+20a1:C//
+U+20a2:Cr
+U+20a3:Ff
+U+20a4:Li
+U+20a5:m/
+U+20a6:N=
+U+20a7:Pt
+U+20a8:Rs
+U+20a9:W=
+U+20aa:rJ
+U+20ab:d_
+# New euro currency sign glyph:
+U+20AC:EUR
+U+20ad:K-
+U+20ae:T//
+U+20af:Dp
+U+20b1:P=
+U+20b2:G|
+U+20b3:A=
+U+20b5:C|
+U+2100:a/c
+U+2101:a/s
+U+2103:oC
+U+2104:CL
+U+2105:c/o
+U+2106:c/u
+U+2109:oF
+0x67 U+210a
+0x68 U+210e
+U+210f "\134hbar "
+U+2111:Im
+U+2113:l
+U+2116:No.
+U+2117:(P)
+U+2118:P
+U+211C:Re
+U+211e:Rx
+U+2120:(SM)
+U+2121:TEL
+# TRADE MARK SIGN:
+U+2122:(TM)
+U+2125:oz.
+U+2126:Ohm
+0x4b U+212A # Kelvin sign - K
+U+212b:Ang.
+U+212E:est.
+0x6f U+2134
+U+2135 "Aleph "
+U+2136 "Bet "
+U+2137 "Gimel "
+U+2138 "Dalet "
+U+213B: FAX
+U+2153: 1/3
+U+2154: 2/3
+U+2155: 1/5
+U+2156: 2/5
+U+2157: 3/5
+U+2158: 4/5
+U+2159: 1/6
+U+215a: 5/6
+U+215b: 1/8
+U+215c: 3/8
+U+215d: 5/8
+U+215e: 7/8
+U+215f: 1/
+U+2160:I
+U+2161:II
+U+2162:III
+U+2163:IV
+U+2164:V
+U+2165:VI
+U+2166:VII
+U+2167:VIII
+U+2168:IX
+U+2169:X
+U+216a:XI
+U+216b:XII
+U+216c:L
+U+216d:C
+U+216e:D
+U+216f:M
+U+2170:i
+U+2171:ii
+U+2172:iii
+U+2173:iv
+U+2174:v
+U+2175:vi
+U+2176:vii
+U+2177:viii
+U+2178:ix
+U+2179:x
+U+217a:xi
+U+217b:xii
+U+217c:l
+U+217d:c
+U+217e:d
+U+217f:m
+U+2180:1000RCD
+U+2181:5000R
+U+2182:10000R
+# Arrows
+U+2190:<-
+U+2191 "^|" # upwards arrow "-^"
+U+2192:->
+U+2193 "|v" # downwards arrow "-v"
+U+2194:<->
+U+2195 "^|v" # up down arrow "UD"
+U+2196:^\
+U+2197:/^
+U+2198:\v
+U+2199:v/
+U+219a:</-
+U+219b:-/>
+U+219c:<~
+U+219d:~>
+U+219e:<<-
+U+219f:^^|
+U+21a0:->>
+U+21a1:|vv
+U+21a2:<-<
+U+21a3:>->
+U+21a4:<-|
+U+21a5:^|_
+U+21a6:|->
+U+21a8 "^|v_" # up down arrow with base "UD-"
+U+21ad:<~>
+U+21ae:<-/->
+U+21af:Nv
+U+21b0:<^|
+U+21b1:|^>
+U+21b2:<v|
+U+21b3:|v>
+U+21b4:-v
+U+21B5:RET
+U+21ba:u<
+U+21bb:>u
+U+21bc:<-
+U+21bd:<-
+U+21be:^|
+U+21bf:^|
+U+21c0:->
+U+21c1:->
+U+21c2:|v
+U+21c3:|v
+U+21c4:<=>
+U+21c5:^||v
+U+21c6:<=>
+U+21c7:<<=
+U+21c8:^|^|
+U+21c9:=>>
+U+21ca:|v|v
+U+21cb:<=>
+U+21cc:<=>
+U+21cd:<=/=
+U+21ce:<=/=>
+U+21cf:=/=>
+U+21d0:<=
+U+21d1 "^||" # upwards double arrow "^^"
+U+21d2:=>
+U+21d3 "||v" # downwards double arrow "vv"
+U+21d4:<=>
+U+21d5:^||v
+U+21d6:^\\
+U+21d7://^
+U+21d8:\\v
+U+21d9:v//
+U+21da:<-=
+U+21db:=->
+U+21dc:<~
+U+21dd:~>
+U+21de:^|=|
+U+21df:|=|v
+U+21e0:<-
+U+21e1:^:
+U+21e2:->
+U+21e3::v
+U+21e4:|<-
+U+21e5:->|
+U+21e6:<-
+U+21e7:^|
+U+21e8:->
+U+21e9:|v
+U+21ea:^!
+U+21eb:^I
+U+21ec:^-I
+U+21ed:^|I
+U+21ee:^^|
+U+21ef:^^I
+U+21f0:|->
+U+21f2:\v_|
+U+21f3:^|v
+U+21f4:-o>
+U+21f5:|v^|
+U+21f6:=->>>
+U+21f7:<-|-
+U+21f8:-|->
+U+21f9:<-|->
+U+21fa:<-||-
+U+21fb:-||->
+U+21fc:<-||->
+U+21fd:<-
+U+21fe:->
+U+21ff:<->
+U+2200:FA
+U+2201:C
+U+2202:\partial
+U+2203:TE
+U+2204:TDNE
+U+2205:{}
+U+2206:Delta
+U+2207:Nabla
+U+2208:(-
+U+2209:!(-
+U+220a:(-
+U+220b:-)
+U+220c:!-)
+U+220d:-)
+U+220e " qed"
+U+220f:\prod
+U+2211:\sum
+U+2212:-
+U+2213:-/+
+U+2214:.+
+0x2f U+2215
+U+2216 " - "
+U+2217:*
+U+2218:Ob
+U+2219:sb
+U+221a " SQRT "
+U+221b " ROOT3 "
+U+221c " ROOT4 "
+U+221d:0(
+U+221e:infty
+U+221f:-L
+U+2220:-V
+U+2225:PP
+U+2226 " !PP "
+U+2227:AND
+U+2228:OR
+U+2229:(U
+U+222a:)U
+U+222b "\134int "
+U+222c "\134int\134int "
+U+222d "\134int\134int\134int "
+U+222e:Io
+U+2234:.:
+U+2235::.
+U+2236::R
+U+2237:::
+U+2238:.-
+U+2239:-:
+U+223c "?1" # Why not use "~" in this and following tilde-like characters? I'll assume someone more math-literate than I did this, and leave them alone.
+U+223e:CG
+U+2241:!~
+U+2242:-~
+U+2243:?-
+U+2244:!~-
+U+2245:?=
+U+2246:~!=
+U+2247:!~=
+# ALMOST EQUAL TO:
+U+2248:~=
+U+2249 " !~= "
+U+224b:~3
+U+224c:=?
+U+2250:=...
+U+2253:HI
+U+2254::=
+U+2255:=:
+U+2260:!=
+U+2261:=3
+U+2262 " !=3 "
+U+2263:=4
+U+2264:<=
+U+2265:>=
+U+2266:.LE.
+U+2267:.GE.
+U+2268:.LT.NOT.EQ.
+U+2269:.GT.NOT.EQ.
+U+226a:<<
+U+226b:>>
+U+226e:!<
+U+226f:!>
+U+2270:!<=
+U+2271:!>=
+U+2272:<~
+U+2273:>~
+U+2274:!<~
+U+2275:!>~
+U+2276 " <> "
+U+2277 " >< "
+U+2278 " !<> "
+U+2279 " !>< "
+U+2282:(C
+U+2283:)C
+U+2284 " !(C "
+U+2285 " !)C "
+U+2286:(_
+U+2287:)_
+U+2288:!(_
+U+2289:!)_
+U+228a:(!_
+U+228b:)!_
+U+228f:[
+U+2290:]
+U+2291:[_
+U+2292:]_
+U+2295 "(+)" # CIRCLED PLUS
+U+2296 "(-)" # CIRCLED MINUS
+U+2297 "(x)" # CIRCLED TIMES
+U+2298 "(/)" # CIRCLED DIVISION SLASH
+U+2299 "(.)" # CIRCLED DOT OPERATOR
+U+229A "(o)" # CIRCLED RING OPERATOR
+U+229B "(*)" # CIRCLED ASTERISK OPERATOR
+U+229C "(=)" # CIRCLED EQUALS
+U+229D "(-)" # CIRCLED DASH
+U+229E "[+]" # SQUARED PLUS
+U+229F "[-]" # SQUARED MINUS
+U+22A0 "[x]" # SQUARED TIMES
+U+22A1 "[.]" # SQUARED DOT OPERATOR
+U+22a5:-T
+U+22A7 " MODELS " # MODELS
+U+22A8 " TRUE " # TRUE
+U+22A9 " FORCES " # FORCES
+U+22AC " !PROVES " # DOES NOT PROVE
+U+22AD " NOT TRUE " # NOT TRUE
+U+22AE " !FORCES " # DOES NOT FORCE
+U+22B2 " NORMAL SUBGROUP OF "
+U+22B3 " CONTAINS AS NORMAL SUBGROUP "
+U+22B4 " NORMAL SUBGROUP OF OR EQUAL TO "
+U+22B5 " CONTAINS AS NORMAL SUBGROUP OR EQUAL TO "
+U+22B8 " MULTIMAP " # MULTIMAP
+U+22BA " INTERCALATE " # INTERCALATE
+U+22BB " XOR " # XOR
+U+22BC " NAND " # NAND
+U+22C5 " DOT " # DOT OPERATOR
+U+22c6 " STAR "
+U+22d6:<.
+U+22d7:>.
+U+22d8:<<<
+U+22d9:>>>
+U+22da:<=|>
+U+22db:>=|<
+U+22dc:=<
+U+22dd:=>
+U+22e2:![_
+U+22e3:!]_
+U+22e4:[!_
+U+22e5:]!_
+U+22e6:<!~
+U+22e7:>!~
+U+22ee::3
+U+22ef:.3
+U+2302:Eh
+U+2303:^
+U+2304:v
+U+2307:~~
+U+2308:<7
+U+2309:>7
+U+230a:7<
+U+230b:7>
+U+2310:NI
+U+2312:(A
+U+2315:TR
+U+2318:88
+U+231a:(-/)
+U+231b " >i< "
+U+2320:Iu
+U+2321:Il
+U+2322::(
+U+2323::)
+U+2324:|^|
+U+2325 " OPT "
+U+2326:[X>
+U+2327:[X]
+U+2328:[kbd]
+U+2329:</
+U+232a:/>
+U+232b:<X]
+U+2387 " ALT "
+U+2397:<-pp
+U+2398:pp->
+U+2399:[PrSc]
+U+239a:[ClSc]
+U+23ce " CR "
+U+23cf:_^_
+U+23da:GROUND
+U+2423:Vs
+U+2440:1h
+U+2441:3h
+U+2442:2h
+U+2443:4h
+U+2446:1j
+U+2447:2j
+U+2448:3j
+U+2449:4j
+U+2460:1-o
+U+2461:2-o
+U+2462:3-o
+U+2463:4-o
+U+2464:5-o
+U+2465:6-o
+U+2466:7-o
+U+2467:8-o
+U+2468:9-o
+U+2469:10-o
+U+246a:11-o
+U+246b:12-o
+U+246c:13-o
+U+246d:14-o
+U+246e:15-o
+U+246f:16-o
+U+2470:17-o
+U+2471:18-o
+U+2472:19-o
+U+2473:20-o
+U+2474:(1)
+U+2475:(2)
+U+2476:(3)
+U+2477:(4)
+U+2478:(5)
+U+2479:(6)
+U+247a:(7)
+U+247b:(8)
+U+247c:(9)
+U+247d:(10)
+U+247e:(11)
+U+247f:(12)
+U+2480:(13)
+U+2481:(14)
+U+2482:(15)
+U+2483:(16)
+U+2484:(17)
+U+2485:(18)
+U+2486:(19)
+U+2487:(20)
+U+2488:1.
+U+2489:2.
+U+248a:3.
+U+248b:4.
+U+248c:5.
+U+248d:6.
+U+248e:7.
+U+248f:8.
+U+2490:9.
+U+2491:10.
+U+2492:11.
+U+2493:12.
+U+2494:13.
+U+2495:14.
+U+2496:15.
+U+2497:16.
+U+2498:17.
+U+2499:18.
+U+249a:19.
+U+249b:20.
+U+249c:(a)
+U+249d:(b)
+U+249e:(c)
+U+249f:(d)
+U+24a0:(e)
+U+24a1:(f)
+U+24a2:(g)
+U+24a3:(h)
+U+24a4:(i)
+U+24a5:(j)
+U+24a6:(k)
+U+24a7:(l)
+U+24a8:(m)
+U+24a9:(n)
+U+24aa:(o)
+U+24ab:(p)
+U+24ac:(q)
+U+24ad:(r)
+U+24ae:(s)
+U+24af:(t)
+U+24b0:(u)
+U+24b1:(v)
+U+24b2:(w)
+U+24b3:(x)
+U+24b4:(y)
+U+24b5:(z)
+U+24b6:A-o
+U+24b7:B-o
+U+24b8:C-o
+U+24b9:D-o
+U+24ba:E-o
+U+24bb:F-o
+U+24bc:G-o
+U+24bd:H-o
+U+24be:I-o
+U+24bf:J-o
+U+24c0:K-o
+U+24c1:L-o
+U+24c2:M-o
+U+24c3:N-o
+U+24c4:O-o
+U+24c5:P-o
+U+24c6:Q-o
+U+24c7:R-o
+U+24c8:S-o
+U+24c9:T-o
+U+24ca:U-o
+U+24cb:V-o
+U+24cc:W-o
+U+24cd:X-o
+U+24ce:Y-o
+U+24cf:Z-o
+U+24d0:a-o
+U+24d1:b-o
+U+24d2:c-o
+U+24d3:d-o
+U+24d4:e-o
+U+24d5:f-o
+U+24d6:g-o
+U+24d7:h-o
+U+24d8:i-o
+U+24d9:j-o
+U+24da:k-o
+U+24db:l-o
+U+24dc:m-o
+U+24dd:n-o
+U+24de:o-o
+U+24df:p-o
+U+24e0:q-o
+U+24e1:r-o
+U+24e2:s-o
+U+24e3:t-o
+U+24e4:u-o
+U+24e5:v-o
+U+24e6:w-o
+U+24e7:x-o
+U+24e8:y-o
+U+24e9:z-o
+U+24ea:0-o
+U+2500:-
+U+2501:=
+U+2502:|
+U+2503:|
+U+2504:-
+U+2505:=
+U+2506:|
+U+2507:|
+U+2508:-
+U+2509:=
+U+250a:|
+U+250b:|
+0x2b U+250c-U+256c # box drawings, use +
+U+2571:/
+U+2572:\
+U+2580:TB
+U+2584:LB
+U+2588:FB
+U+258c:lB
+U+2590:RB
+U+2591:.S
+U+2592::S
+U+2593:?S
+U+25a0:fS
+U+25a1:OS
+U+25a2:RO
+U+25a3:Rr
+U+25a4:RF
+U+25a5:RY
+U+25a6:RH
+U+25a7:RZ
+U+25a8:RK
+U+25a9:RX
+U+25aa:sB
+U+25ac:SR
+U+25ad:Or
+U+25b2:^
+U+25b3:uT
+U+25b6:|>
+U+25b7:Tr
+U+25ba:|>
+U+25bc:v
+U+25bd:dT
+U+25c0:<|
+U+25c1:Tl
+U+25c4:<|
+U+25c6:Db
+U+25c7:Dw
+U+25ca:LZ
+U+25cb:0m
+U+25ce:0o
+U+25cf:0M
+U+25d0:0L
+U+25d1:0R
+U+25d8:Sn
+U+25d9:Ic
+U+25e2:Fd
+U+25e3:Bd
+U+25ef:Ci
+# Miscellaneous Symbols
+U+2600 "SU" # cf. U+263c
+U+2601:cOo
+U+2602:J
+U+2603:"8"
+U+2605:*
+U+2606:*
+U+2607:<v
+U+2608:Rv
+U+2609 "Sol" # cf. astrological symbols U+263c - U+2647, star-like U+2600
+U+260a:Asc.
+U+260b:Desc.
+U+260c:Conj.
+U+260d:Opp.
+U+260e:TEL
+U+260f:tel
+0x58 U+2611 U+2612 # checked ballot boxes -> x
+U+2613:X
+U+2614 "\"J\"" # umbrella with rain drops, quote marks are part of the symbol
+U+2615:Joe
+U+261a:<==
+U+261b:==>
+U+261c:<--
+U+261d:||^
+U+261e:-->
+U+261f:||v
+U+2620 "!X!" # or "POISON ", cf. U+2621
+U+2621 "!Z!" # previously "CAUTION " - or is it better to leave it spelled out in English?
+U+2622 "!R!" # or "RADIOACTIVE ", cf. U+2621
+U+2623 "!B!" # or "BIOHAZARD ", cf. U+2621
+U+2624 "2TS" # cf. U+2695
+U+2626:t
+U+2627:XP
+U+2628:t
+U+2629:+
+U+262a:(*
+U+262d:'\,)
+U+262e:(PEACE)
+U+262f:Pd
+U+2630:-HVN-
+U+2631:-LAK-
+U+2632:-FIR-
+U+2633:-THR-
+U+2634:-WND-
+U+2635:-WTR-
+U+2636:-MTN-
+U+2637:-RTH-
+U+2638:*
+U+2639::-(
+U+263a::-)
+U+263b:(-:
+U+263c "su" # previously "SU" - cf. U+2600, typical to have "dark" character in uppercase, eg. U+260e, U+260f
+U+263d "Lun1" # Luna, 1st quarter
+U+263e "Lun3" # Luna, 3rd quarter
+U+263f:Mer
+U+2640 "Ven" # previously "f." - this section labeled as astrological symbols
+U+2641 "Ter" # Terra, to go with other Latin names
+U+2642 "Mar" # previously "m." - this section labeled as astrological symbols
+U+2643:Jup
+U+2644:Sat
+U+2645:Ura
+U+2646:Nep
+U+2647:Plu
+U+2648 "Ari" # Standard astronomical abbreviation
+U+2649 "Tau"
+U+264a "Gem"
+U+264b "Cnc"
+U+264c "Leo"
+U+264d "Vir"
+U+264e "Lib"
+U+264f "Sco"
+U+2650 "Sgr"
+U+2651 "Cap"
+U+2652 "Aqr"
+U+2653 "Psc"
+U+2654 "k" # white chess king
+U+2655:q
+U+2656:r
+U+2657:b
+U+2658:n
+U+2659:p
+U+265a "K" # black chess king
+U+265b:Q
+U+265c:R
+U+265d:B
+U+265e:N
+U+265f:P
+U+2660 "cS" # black spade suit
+U+2661:ch
+U+2662:cd
+U+2663:cC
+U+2664:cs
+U+2665:cH
+U+2666:cD
+U+2667:cc
+#Musical symbols
+U+2669:d
+U+266a:d`
+U+266b:d-d
+U+266c:d=d
+U+266d:b
+U+266e:N
+U+266f:#
+U+2670:t
+U+2671:t
+#Recycling symbols
+U+2672:/_\
+U+2673:/1\
+U+2674:/2\
+U+2675:/3\
+U+2676:/4\
+U+2677:/5\
+U+2678:/6\
+U+2679:/7\
+U+267a:/_\
+U+267b:/_\
+U+267c:/P\
+U+267d:/p\
+U+267e:(oo)
+U+267f "oL" # wheelchair symbol
+U+2680:d1
+U+2681:d2
+U+2682:d3
+U+2683:d4
+U+2684:d5
+U+2685:d6
+U+2686:(.)
+U+2687:(:)
+U+2688:((.))
+U+2689:((:))
+U+2690 " f "
+U+2691 " F "
+U+2692:'X`
+U+2693 "+-)" # anchor (sideways)
+U+2694:,X,
+U+2695 "$" # cf. U+2624
+U+2696 "^T^" # scales of justice
+U+269a "}T{" # staff of Hermes
+U+269b ":*:" # atom symbol
+U+269c "}|{" # fleur-de-lis
+U+26a0 "!!!" # or "WARNING ", cf. U+2621
+U+26a1 "!V!" # or "VOLTAGE ", cf. U+2621
+U+26a2 "f.f." # two females, lesbian
+U+26a3 "m.m." # two males, homosexual
+U+26a4 "m.f." # male and female, bisexual
+U+26a5 "mf." # cf. U+26A4
+U+26a6 "xm." # transgendered male
+U+26a7 "xmf." # transgendered male/female
+U+26aa:o
+U+26ab:O
+U+26ac:o
+U+26ad:oo
+U+26ae:o|o
+U+26af:o-o
+U+26b0 "/b/" # buried/coffin
+U+26b1 "/c/" # cremated/urn
+U+26b2 "n." # cf. U+26a2 - U+26a7
+# Dingbats
+U+2702:8<
+U+2704:>8
+U+2706:(TEL)
+U+2708:+->-
+U+2709 "[v]" # envelope
+U+270c:mV,
+0x58 U+2713 U+2714 U+2717 U+2718 # check marks -> x
+U+2715: x
+U+2716: X
+U+2719:+
+U+271a:+
+U+271b:+
+U+271c:+
+U+271d:t
+U+271e:t
+U+271f:t
+U+2720:-X
+0x2a U+2721 U+272a U+272b U+272c U+272d U+272e U+272f U+2730 U+2731 U+2732 U+2733 U+2734 U+2735 U+2736 U+2737 U+2738 U+2739 U+273a U+273b U+273c U+273d
+0x2a U+2742 U+2743 U+2744 U+2745 U+2746 U+2747 U+2748 U+2749 U+274a U+274b
+U+2756:<x>
+U+2758:|
+U+2759:|
+U+275a:|
+U+275b:'
+U+275c:'
+U+275d:"
+U+275e:"
+U+2762:!
+U+2763:!
+U+2765:<3
+U+2768:(
+U+2769:)
+U+276a:(
+U+276b:)
+U+276c:<
+U+276d:>
+U+276e:<
+U+276f:>
+U+2770:<
+U+2771:>
+U+2772:[
+U+2773:]
+U+2774:{
+U+2775:}
+U+2776:((1))
+U+2777:((2))
+U+2778:((3))
+U+2779:((4))
+U+277a:((5))
+U+277b:((6))
+U+277c:((7))
+U+277d:((8))
+U+277e:((9))
+U+277f:((10))
+U+2780:(1)
+U+2781:(2)
+U+2782:(3)
+U+2783:(4)
+U+2784:(5)
+U+2785:(6)
+U+2786:(7)
+U+2787:(8)
+U+2788:(9)
+U+2789:(10)
+U+278a:((1))
+U+278b:((2))
+U+278c:((3))
+U+278d:((4))
+U+278e:((5))
+U+278f:((6))
+U+2790:((7))
+U+2791:((8))
+U+2792:((9))
+U+2793:((10))
+U+2794:->
+U+2798:\v
+U+2799:->
+U+279a:/^
+U+279b:->
+U+279c:->
+U+279d:->
+U+279e:->
+U+279f:->
+U+27a0:->
+U+27a1:->
+U+27a2:>
+U+27a3:>
+U+27a4:>
+U+27a5:->
+U+27a6:->
+U+27a7:->
+U+27a8:->
+U+27a9:->
+U+27aa:->
+U+27ab:->
+U+27ac:->
+U+27ad:->
+U+27ae:->
+U+27af:->
+U+27b0:->
+U+27b2:(->)
+U+27b3:>>->
+U+27b4:vv\v
+U+27b5:>>->
+U+27b6:^^/^
+U+27b7:vv\v
+U+27b8:>>->
+U+27b9:^^/^
+U+27ba:->
+U+27bb:->
+U+27bc:->
+U+27bd:->
+U+27be:->
+# Supplemental Arrows A
+U+27f0:^||||
+U+27f1:||||v
+U+27f2:vO
+U+27f3:Ov
+U+27f4:(+)>
+U+27f5:<---
+U+27f6:--->
+U+27f7:<--->
+U+27f8:<===
+U+27f9:===>
+U+27fa:<===>
+U+27fb:<---|
+U+27fc:|--->
+U+27fd:<===|
+U+27fe:|===>
+U+27ff:~~~>
+# Supplemental Arrows B
+U+2900:-|->>
+U+2901:-||->>
+U+2902:<=|=
+U+2903:=|=>
+U+2904:<=|=>
+U+2905:|->>
+U+2906:<=|
+U+2907:|=>
+U+2908:|-|v
+U+2909:^|-|
+U+290a:^|||
+U+290b:|||v
+U+290c:<- -
+U+290d:- ->
+U+290e:<- - -
+U+290f:- - ->
+U+2910:>- - ->
+U+2911:->
+U+2913:|v_
+U+2914:>-|->
+U+2915:>-||->
+U+2916:>->>
+U+2917:>-|->>
+U+2918:>-||->>
+U+2919:-<
+U+291a:>-
+U+291b:-<<
+U+291c:>>-
+U+291d:<><-
+U+291e:-><>
+U+291f:<><-|
+U+2920:|-><>
+U+2921:^\v
+U+2922:v/^
+U+2923:^\,
+U+2924:,/^
+U+2927:^X^
+U+292b:X
+U+292c:X
+U+292d:Xv^
+U+292e:X^v
+U+292f:X ^
+U+2930:X v
+U+2931:^X^
+U+2932:^X^
+U+2933:~>
+U+2934:-^
+U+2935:-v
+U+2938:)v
+U+2939:(v
+U+2945:->+
+U+2946:<-+
+U+2947:-x->
+U+2948:<-o->
+U+2949:^^|o
+U+294a:<->
+U+294b:<->
+U+294c:^|v
+U+294d:^|v
+U+294e:<->
+U+294f:^|v
+U+2950:<->
+U+2951:^|v
+U+2952:|<-
+U+2953:->|
+U+2955:|v_
+U+2956:|<-
+U+2957:->|
+U+2959:|v_
+U+295a:<-|
+U+295b:|->
+U+295c:^|_
+U+295e:<-|
+U+295f:|->
+U+2960:^|_
+U+2962:<=
+U+2963:^||
+U+2964:=>
+U+2965:||v
+U+2970:=)
+U+2a00 "(.)"
+U+2a01 "(+)"
+U+2a02 "(x)"
+U+2a09: *
+U+2a0c "\134int\134int\134int\134int "
+U+2a2f:x
+U+2a30:.x
+U+2a31:x_
+U+2a33:xx
+U+2a34:(x
+U+2a35:x)
+U+2a37:((x))
+U+2a38:(/)
+U+2a39:/+\
+U+2a3a:/-\
+U+2a3b:/x\
+U+2a74:::=
+U+2a75:==
+U+2a76:===
+U+2a77::=:
+#Miscellaneous Symbols and Arrows
+U+2b00:/^
+U+2b01:^\
+U+2b02:\v
+U+2b03:v/
+U+2b04:<->
+U+2b05:<-
+U+2b06:^|
+U+2b07:|v
+U+2b08:/^
+U+2b09:^\
+U+2b0a:\v
+U+2b0b:v/
+U+2b0c:<->
+U+2b0d:^|v
+U+2b0e:-v
+U+2b0f:-^
+U+2b10:v-
+U+2b11:^-
+# Supplemental punctuation
+U+2e0f:__
+U+2e1e:.~
+U+2e1f:~.
+U+2e28:((
+U+2e29:))
+U+2e2a ":."
+U+2e2b ".:"
+U+2e2c "::"
+U+2e2f:~
+# CJK area:
+0x20 U+3000 # ideographic space
+U+3001:,_
+U+3002:._
+U+3003:+"
+U+3004:JIS
+U+3005:*_
+U+3006:;_
+U+3007:0_
+U+300a:<+
+U+300b:>+
+U+300c:<'
+U+300d:>'
+U+300e:<"
+U+300f:>"
+U+3010:("
+U+3011:)"
+U+3012:=T
+U+3013:=_
+U+3014:('
+U+3015:)'
+U+3016:(I
+U+3017:)I
+U+301a:[[
+U+301b:]]
+U+301c:-?
+U+3020:=T:)
+0x20 U+303f
+U+3041:A5
+U+3042:a5
+U+3043:I5
+U+3044:i5
+U+3045:U5
+U+3046:u5
+U+3047:E5
+U+3048:e5
+U+3049:O5
+U+304a:o5
+U+304b:ka
+U+304c:ga
+U+304d:ki
+U+304e:gi
+U+304f:ku
+U+3050:gu
+U+3051:ke
+U+3052:ge
+U+3053:ko
+U+3054:go
+U+3055:sa
+U+3056:za
+U+3057:si
+U+3058:zi
+U+3059:su
+U+305a:zu
+U+305b:se
+U+305c:ze
+U+305d:so
+U+305e:zo
+U+305f:ta
+U+3060:da
+U+3061:ti
+U+3062:di
+U+3063:tU
+U+3064:tu
+U+3065:du
+U+3066:te
+U+3067:de
+U+3068:to
+U+3069:do
+U+306a:na
+U+306b:ni
+U+306c:nu
+U+306d:ne
+U+306e:no
+U+306f:ha
+U+3070:ba
+U+3071:pa
+U+3072:hi
+U+3073:bi
+U+3074:pi
+U+3075:hu
+U+3076:bu
+U+3077:pu
+U+3078:he
+U+3079:be
+U+307a:pe
+U+307b:ho
+U+307c:bo
+U+307d:po
+U+307e:ma
+U+307f:mi
+U+3080:mu
+U+3081:me
+U+3082:mo
+U+3083:yA
+U+3084:ya
+U+3085:yU
+U+3086:yu
+U+3087:yO
+U+3088:yo
+U+3089:ra
+U+308a:ri
+U+308b:ru
+U+308c:re
+U+308d:ro
+U+308e:wA
+U+308f:wa
+U+3090:wi
+U+3091:we
+U+3092:wo
+U+3093:n5
+U+3094:vu
+U+309b:"5
+U+309c:05
+U+309d:*5
+U+309e:+5
+U+30a1:a6
+U+30a2:A6
+U+30a3:i6
+U+30a4:I6
+U+30a5:u6
+U+30a6:U6
+U+30a7:e6
+U+30a8:E6
+U+30a9:o6
+U+30aa:O6
+U+30ab:Ka
+U+30ac:Ga
+U+30ad:Ki
+U+30ae:Gi
+U+30af:Ku
+U+30b0:Gu
+U+30b1:Ke
+U+30b2:Ge
+U+30b3:Ko
+U+30b4:Go
+U+30b5:Sa
+U+30b6:Za
+U+30b7:Si
+U+30b8:Zi
+U+30b9:Su
+U+30ba:Zu
+U+30bb:Se
+U+30bc:Ze
+U+30bd:So
+U+30be:Zo
+U+30bf:Ta
+U+30c0:Da
+U+30c1:Ti
+U+30c2:Di
+U+30c3:TU
+U+30c4:Tu
+U+30c5:Du
+U+30c6:Te
+U+30c7:De
+U+30c8:To
+U+30c9:Do
+U+30ca:Na
+U+30cb:Ni
+U+30cc:Nu
+U+30cd:Ne
+U+30ce:No
+U+30cf:Ha
+U+30d0:Ba
+U+30d1:Pa
+U+30d2:Hi
+U+30d3:Bi
+U+30d4:Pi
+U+30d5:Hu
+U+30d6:Bu
+U+30d7:Pu
+U+30d8:He
+U+30d9:Be
+U+30da:Pe
+U+30db:Ho
+U+30dc:Bo
+U+30dd:Po
+U+30de:Ma
+U+30df:Mi
+U+30e0:Mu
+U+30e1:Me
+U+30e2:Mo
+U+30e3:YA
+U+30e4:Ya
+U+30e5:YU
+U+30e6:Yu
+U+30e7:YO
+U+30e8:Yo
+U+30e9:Ra
+U+30ea:Ri
+U+30eb:Ru
+U+30ec:Re
+U+30ed:Ro
+U+30ee:WA
+U+30ef:Wa
+U+30f0:Wi
+U+30f1:We
+U+30f2:Wo
+U+30f3:N6
+U+30f4:Vu
+U+30f5:KA
+U+30f6:KE
+U+30f7:Va
+U+30f8:Vi
+U+30f9:Ve
+U+30fa:Vo
+U+30fb:.6
+U+30fc:-6
+U+30fd:*6
+U+30fe:+6
+U+3105:b4
+U+3106:p4
+U+3107:m4
+U+3108:f4
+U+3109:d4
+U+310a:t4
+U+310b:n4
+U+310c:l4
+U+310d:g4
+U+310e:k4
+U+310f:h4
+U+3110:j4
+U+3111:q4
+U+3112:x4
+U+3113:zh
+U+3114:ch
+U+3115:sh
+U+3116:r4
+U+3117:z4
+U+3118:c4
+U+3119:s4
+U+311a:a4
+U+311b:o4
+U+311c:e4
+U+311d:eh4
+U+311e:ai
+U+311f:ei
+U+3120:au
+U+3121:ou
+U+3122:an
+U+3123:en
+U+3124:aN
+U+3125:eN
+U+3126:er
+U+3127:i4
+U+3128:u4
+U+3129:iu
+U+312a:v4
+U+312b:nG
+U+312c:gn
+U+321c:(JU)
+U+3220:1c
+U+3221:2c
+U+3222:3c
+U+3223:4c
+U+3224:5c
+U+3225:6c
+U+3226:7c
+U+3227:8c
+U+3228:9c
+U+3229:10c
+U+327f:KSC
+U+33c2:am
+U+33d8:pm
+#
+#
+#There are four special ranges of characters that are represented only by
+#their start and end characters <...>
+#
+# The CJK Ideographs Area (U+4E00 - U+9FFF)
+# The Hangul Syllables Area (U+AC00 - U+D7A3)
+# The Surrogates Area (U+D800 - U+DFFF)
+# The Private Use Area (U+E000 - U+F8FF)
+#
+#
+U+fb00:ff
+U+fb01:fi
+U+fb02:fl
+U+fb03:ffi
+U+fb04:ffl
+U+fb05:St
+U+fb06:st
+U+fe7d:3+;
+U+fe82:aM.
+U+fe84:aH.
+U+fe88:ah.
+U+fe8d:a+-
+U+fe8e:a+.
+U+fe8f:b+-
+U+fe90:b+.
+U+fe91:b+,
+U+fe92:b+;
+U+fe93:tm-
+U+fe94:tm.
+U+fe95:t+-
+U+fe96:t+.
+U+fe97:t+,
+U+fe98:t+;
+U+fe99:tk-
+U+fe9a:tk.
+U+fe9b:tk,
+U+fe9c:tk;
+U+fe9d:g+-
+U+fe9e:g+.
+U+fe9f:g+,
+U+fea0:g+;
+U+fea1:hk-
+U+fea2:hk.
+U+fea3:hk,
+U+fea4:hk;
+U+fea5:x+-
+U+fea6:x+.
+U+fea7:x+,
+U+fea8:x+;
+U+fea9:d+-
+U+feaa:d+.
+U+feab:dk-
+U+feac:dk.
+U+fead:r+-
+U+feae:r+.
+U+feaf:z+-
+U+feb0:z+.
+U+feb1:s+-
+U+feb2:s+.
+U+feb3:s+,
+U+feb4:s+;
+U+feb5:sn-
+U+feb6:sn.
+U+feb7:sn,
+U+feb8:sn;
+U+feb9:c+-
+U+feba:c+.
+U+febb:c+,
+U+febc:c+;
+U+febd:dd-
+U+febe:dd.
+U+febf:dd,
+U+fec0:dd;
+U+fec1:tj-
+U+fec2:tj.
+U+fec3:tj,
+U+fec4:tj;
+U+fec5:zH-
+U+fec6:zH.
+U+fec7:zH,
+U+fec8:zH;
+U+fec9:e+-
+U+feca:e+.
+U+fecb:e+,
+U+fecc:e+;
+U+fecd:i+-
+U+fece:i+.
+U+fecf:i+,
+U+fed0:i+;
+U+fed1:f+-
+U+fed2:f+.
+U+fed3:f+,
+U+fed4:f+;
+U+fed5:q+-
+U+fed6:q+.
+U+fed7:q+,
+U+fed8:q+;
+U+fed9:k+-
+U+feda:k+.
+U+fedb:k+,
+U+fedc:k+;
+U+fedd:l+-
+U+fede:l+.
+U+fedf:l+,
+U+fee0:l+;
+U+fee1:m+-
+U+fee2:m+.
+U+fee3:m+,
+U+fee4:m+;
+U+fee5:n+-
+U+fee6:n+.
+U+fee7:n+,
+U+fee8:n+;
+U+fee9:h+-
+U+feea:h+.
+U+feeb:h+,
+U+feec:h+;
+U+feed:w+-
+U+feee:w+.
+U+feef:j+-
+U+fef0:j+.
+U+fef1:y+-
+U+fef2:y+.
+U+fef3:y+,
+U+fef4:y+;
+U+fef5:lM-
+U+fef6:lM.
+U+fef7:lH-
+U+fef8:lH.
+U+fef9:lh-
+U+fefa:lh.
+U+fefb:la-
+U+fefc:la.
+
+# the reverse byte-order-mark: zero-width non-break space
+U+feff ""
+
+0x21-0x7e U+ff01-U+ff5e
+0x2e U+ff61
+0x22 U+ff62 U+ff63
+0x2c U+ff64
+
+# Symbols for C0 and C1 control characters, in case they get through...
+U+0000:NUL
+U+0001:SH
+U+0002:SX
+U+0003:EX
+U+0004:ET
+U+0005:ENQ
+U+0006:AK
+U+0007:BL
+U+0008:BS
+U+0009:HT
+U+000a:LF
+U+000b:VT
+U+000c:FF
+U+000d:CR
+U+000e:SO
+U+000f:SI
+U+0010:DL
+U+0011:DC1
+U+0012:DC2
+U+0013:DC3
+U+0014:DC4
+U+0015:NAK
+U+0016:SYN
+U+0017:EB
+U+0018:CN
+U+0019:EM
+U+001a:SB
+U+001b:ESC
+U+001c:FS
+U+001d:GS
+U+001e:RS
+U+001f:US
+U+007f:DT
+# Most of these characters (80-9F) may be inflicted on us
+# by MS FrontPages which uses Unicode notation such as &#153;
+# but there are no assigned letters in Unicode 128-159 range.
+# It is assumed in the code that those codepoints are from windows-1252.
+#U+0080:PA
+#U+0081:HO
+#U+0082:BH
+#U+0083:NH
+#U+0084:IN
+#U+0085:NL
+U+0085 "\012"
+#U+0086:SA
+#U+0087:ES
+#U+0088:HS
+#U+0089:HJ
+#U+008a:VS
+#U+008b:PD
+#U+008c:PU
+#U+008d:RI
+#U+008e:SS2
+#U+008f:SS3
+#U+0090:DCS
+#U+0091:P1
+#U+0092:P2
+#U+0093:TS
+#U+0094:CC
+#U+0095:MW
+#U+0096:SG
+#U+0097:EG
+#U+0098:SS
+#U+0099:GC
+#U+009a:SC
+#U+009b:CSI
+#U+009c:ST
+#U+009d:OC
+#U+009e:PM
+#U+009f:AC
+
+# Let's try to show a question mark for character that cannot
+# be shown. U+fffd is used for invalid characters.
+# It works, but let's stick with UHHH representation. - FM
+#U+fffd "?"
diff --git a/src/chrtrans/dmcs_uni.tbl b/src/chrtrans/dmcs_uni.tbl
new file mode 100644
index 0000000..ab9edb2
--- /dev/null
+++ b/src/chrtrans/dmcs_uni.tbl
@@ -0,0 +1,233 @@
+#The MIME name of this charset.
+Mdec-mcs
+
+#Name as a Display Charset (used on Options screen)
+ODEC Multinational
+
+#
+# Name: DEC Multinational (dec-mcs) [to unicode]
+# Date: 29 October 1997
+# Author: Fote
+#
+# 1999-01-01 various corrections, verified against actual DEC VT220
+# Christian "naddy" Weisgerber <naddy@mips.rhein-neckar.de>
+#
+##################
+
+#0x20 U+0020 # SPACE
+#0x21 U+0021 # EXCLAMATION MARK
+#0x22 U+0022 # QUOTATION MARK
+#0x23 U+0023 # NUMBER SIGN
+#0x24 U+0024 # DOLLAR SIGN
+#0x25 U+0025 # PERCENT SIGN
+#0x26 U+0026 # AMPERSAND
+#0x27 U+0027 # APOSTROPHE
+#0x28 U+0028 # LEFT PARENTHESIS
+#0x29 U+0029 # RIGHT PARENTHESIS
+#0x2A U+002A # ASTERISK
+#0x2B U+002B # PLUS SIGN
+#0x2C U+002C # COMMA
+#0x2D U+002D # HYPHEN-MINUS
+#0x2E U+002E # FULL STOP
+#0x2F U+002F # SOLIDUS
+#0x30 U+0030 # DIGIT ZERO
+#0x31 U+0031 # DIGIT ONE
+#0x32 U+0032 # DIGIT TWO
+#0x33 U+0033 # DIGIT THREE
+#0x34 U+0034 # DIGIT FOUR
+#0x35 U+0035 # DIGIT FIVE
+#0x36 U+0036 # DIGIT SIX
+#0x37 U+0037 # DIGIT SEVEN
+#0x38 U+0038 # DIGIT EIGHT
+#0x39 U+0039 # DIGIT NINE
+#0x3A U+003A # COLON
+#0x3B U+003B # SEMICOLON
+#0x3C U+003C # LESS-THAN SIGN
+#0x3D U+003D # EQUALS SIGN
+#0x3E U+003E # GREATER-THAN SIGN
+#0x3F U+003F # QUESTION MARK
+#0x40 U+0040 # COMMERCIAL AT
+#0x41 U+0041 # LATIN CAPITAL LETTER A
+#0x42 U+0042 # LATIN CAPITAL LETTER B
+#0x43 U+0043 # LATIN CAPITAL LETTER C
+#0x44 U+0044 # LATIN CAPITAL LETTER D
+#0x45 U+0045 # LATIN CAPITAL LETTER E
+#0x46 U+0046 # LATIN CAPITAL LETTER F
+#0x47 U+0047 # LATIN CAPITAL LETTER G
+#0x48 U+0048 # LATIN CAPITAL LETTER H
+#0x49 U+0049 # LATIN CAPITAL LETTER I
+#0x4A U+004A # LATIN CAPITAL LETTER J
+#0x4B U+004B # LATIN CAPITAL LETTER K
+#0x4C U+004C # LATIN CAPITAL LETTER L
+#0x4D U+004D # LATIN CAPITAL LETTER M
+#0x4E U+004E # LATIN CAPITAL LETTER N
+#0x4F U+004F # LATIN CAPITAL LETTER O
+#0x50 U+0050 # LATIN CAPITAL LETTER P
+#0x51 U+0051 # LATIN CAPITAL LETTER Q
+#0x52 U+0052 # LATIN CAPITAL LETTER R
+#0x53 U+0053 # LATIN CAPITAL LETTER S
+#0x54 U+0054 # LATIN CAPITAL LETTER T
+#0x55 U+0055 # LATIN CAPITAL LETTER U
+#0x56 U+0056 # LATIN CAPITAL LETTER V
+#0x57 U+0057 # LATIN CAPITAL LETTER W
+#0x58 U+0058 # LATIN CAPITAL LETTER X
+#0x59 U+0059 # LATIN CAPITAL LETTER Y
+#0x5A U+005A # LATIN CAPITAL LETTER Z
+#0x5B U+005B # LEFT SQUARE BRACKET
+#0x5C U+005C # REVERSE SOLIDUS
+#0x5D U+005D # RIGHT SQUARE BRACKET
+#0x5E U+005E # CIRCUMFLEX ACCENT
+#0x5F U+005F # LOW LINE
+#0x60 U+0060 # GRAVE ACCENT
+#0x61 U+0061 # LATIN SMALL LETTER A
+#0x62 U+0062 # LATIN SMALL LETTER B
+#0x63 U+0063 # LATIN SMALL LETTER C
+#0x64 U+0064 # LATIN SMALL LETTER D
+#0x65 U+0065 # LATIN SMALL LETTER E
+#0x66 U+0066 # LATIN SMALL LETTER F
+#0x67 U+0067 # LATIN SMALL LETTER G
+#0x68 U+0068 # LATIN SMALL LETTER H
+#0x69 U+0069 # LATIN SMALL LETTER I
+#0x6A U+006A # LATIN SMALL LETTER J
+#0x6B U+006B # LATIN SMALL LETTER K
+#0x6C U+006C # LATIN SMALL LETTER L
+#0x6D U+006D # LATIN SMALL LETTER M
+#0x6E U+006E # LATIN SMALL LETTER N
+#0x6F U+006F # LATIN SMALL LETTER O
+#0x70 U+0070 # LATIN SMALL LETTER P
+#0x71 U+0071 # LATIN SMALL LETTER Q
+#0x72 U+0072 # LATIN SMALL LETTER R
+#0x73 U+0073 # LATIN SMALL LETTER S
+#0x74 U+0074 # LATIN SMALL LETTER T
+#0x75 U+0075 # LATIN SMALL LETTER U
+#0x76 U+0076 # LATIN SMALL LETTER V
+#0x77 U+0077 # LATIN SMALL LETTER W
+#0x78 U+0078 # LATIN SMALL LETTER X
+#0x79 U+0079 # LATIN SMALL LETTER Y
+#0x7A U+007A # LATIN SMALL LETTER Z
+#0x7B U+007B # LEFT CURLY BRACKET
+#0x7C U+007C # VERTICAL LINE
+#0x7D U+007D # RIGHT CURLY BRACKET
+#0x7E U+007E # TILDE
+#
+0x20-0x7E idem # ASCII
+
+0xA1 U+00A1 # inverted exclamation mark (&#161;) - iexcl
+0xA2 U+00A2 # cent sign (&#162;) - cent
+0xA3 U+00A3 # pound sign (&#163;) - pound
+0xA5 U+00A5 # yen sign (&#165;) - yen
+# broken vertical bar (&#166;) - brvbar, brkbar
+U+00A6:|
+0xA7 U+00A7 # section sign (&#167;) - sect
+0xA8 U+00A4 # currency sign (&#164;) - curren
+# spacing diaeresis (&#168;) - uml, die
+U+00A8:"
+0xA9 U+00A9 # copyright sign (&#169;) - copy
+0xAA U+00AA # feminine ordinal indicator (&#170;) - ordf
+0xAB U+00AB # angle quotation mark, left (&#171;) - laquo
+# negation sign (&#172); - not
+U+00AC:NOT
+# soft hyphen (&#173;) - shy
+#U+00AD
+# circled R registered sign (&#174;) - reg
+U+00AE:(R)
+# spacing macron (&#175;) - hibar, macr
+U+00AF:-
+0xB0 U+00B0 # degree sign (&#176;) - deg
+0xB1 U+00B1 # plus-or-minus sign (&#177;) - plusmn
+0xB2 U+00B2 # superscript 2 (&#178;) - sup2
+0xB3 U+00B3 # superscript 3 (&#179;) - sup3
+#spacing acute (&#180;) - acute
+U+00B4:'
+0xB5 U+00B5 # micro sign (&#181;) - micro
+0xB6 U+00B6 # paragraph sign (&#182;) - para
+0xB7 U+00B7 # middle dot (&#183;) - middot
+# spacing cedilla (&#184;) - cedil
+U+00B8:,
+0xB9 U+00B9 # superscript 1 (&#185;) - sup1
+0xBA U+00BA # masculine ordinal indicator (&#186;) - ordm
+0xBB U+00BB # angle quotation mark, right (&#187;) - raquo
+0xBC U+00BC # fraction 1/4 (&#188;) - frac14
+0xBD U+00BD # fraction 1/2 (&#189;) - frac12
+# fraction 3/4 (&#190;) - frac34
+U+00BE: 3/4
+0xBF U+00BF # inverted question mark (&#191;) - iquest
+0xC0 U+00C0 # capital A, grave accent (&#192;) - Agrave
+0xC1 U+00C1 # capital A, acute accent (&#193;) - Aacute
+0xC2 U+00C2 # capital A, circumflex accent (&#194;) - Acirc
+0xC3 U+00C3 # capital A, tilde (&#195;) - Atilde
+0xC4 U+00C4 # capital A, dieresis or umlaut mark (&#196;) - Auml
+0xC5 U+00C5 # capital A, ring (&#197;) - Aring
+0xC6 U+00C6 # capital AE diphthong (ligature) (&#198;) - AElig
+0xC7 U+00C7 # capital C, cedilla (&#199;) - Ccedil
+0xC8 U+00C8 # capital E, grave accent (&#200;) - Egrave
+0xC9 U+00C9 # capital E, acute accent (&#201;) - Eacute
+0xCA U+00CA # capital E, circumflex accent (&#202;) - Ecirc
+0xCB U+00CB # capital E, dieresis or umlaut mark (&#203;) - Euml
+0xCC U+00CC # capital I, grave accent (&#204;) - Igrave
+0xCD U+00CD # capital I, acute accent (&#205;) - Iacute
+0xCE U+00CE # capital I, circumflex accent (&#206;) - Icirc
+0xCF U+00CF # capital I, dieresis or umlaut mark (&#207;) - Iuml
+# capital Eth, Icelandic (&#208;) - ETH */
+U+00D0:DH
+# Dj # capital D with stroke - Dstrok
+0xD1 U+00D1 # capital N, tilde (&#209;) - Ntilde
+0xD2 U+00D2 # capital O, grave accent (&#210;) - Ograve
+0xD3 U+00D3 # capital O, acute accent (&#211;) - Oacute
+0xD4 U+00D4 # capital O, circumflex accent (&#212;) - Ocirc
+0xD5 U+00D5 # capital O, tilde (&#213;) - Otilde
+0xD6 U+00D6 # capital O, dieresis or umlaut mark (&#214;) - Ouml
+0xD7 U+0152 # capital OE ligature (&#338;) - OElig
+# multiplication sign (&#215;) - times
+U+00D7:*
+0xD8 U+00D8 # capital O, slash (&#216;) - Oslash
+0xD9 U+00D9 # capital U, grave accent (&#217;) - Ugrave
+0xDA U+00DA # capital U, acute accent (&#218;) - Uacute
+0xDB U+00DB # capital U, circumflex accent (&#219;) - Ucirc
+0xDC U+00DC # capital U, dieresis or umlaut mark (&#220;) - Uuml
+0xDD U+0178 # capital Y, dieresis or umlaut mark (&#376;) - Yuml
+# capital Y, acute accent (&#221;) - Yacute
+U+00DD:Y'
+# capital THORN, Icelandic (&#222;) - THORN */
+U+00DE:TH
+0xDF U+00DF # small sharp s, German (sz ligature) (&#223;) - szlig
+0xE0 U+00E0 # small a, grave accent (&#224;) - agrave
+0xE1 U+00E1 # small a, acute accent (&#225;) - aacute
+0xE2 U+00E2 # small a, circumflex accent (&#226;) - acirc
+0xE3 U+00E3 # small a, tilde (&#227;) - atilde
+0xE4 U+00E4 # small a, dieresis or umlaut mark (&#228;) - auml
+0xE5 U+00E5 # small a, ring (&#229;) - aring
+0xE6 U+00E6 # small ae diphthong (ligature) (&#230;) - aelig
+0xE7 U+00E7 # small c, cedilla (&#231;) - ccedil
+0xE8 U+00E8 # small e, grave accent (&#232;) - egrave
+0xE9 U+00E9 # small e, acute accent (&#233;) - eacute
+0xEA U+00EA # small e, circumflex accent (&#234;) - ecirc
+0xEB U+00EB # small e, dieresis or umlaut mark (&#235;) - euml
+0xEC U+00EC # small i, grave accent (&#236;) - igrave
+0xED U+00ED # small i, acute accent (&#237;) - iacute
+0xEE U+00EE # small i, circumflex accent (&#238;) - icirc
+0xEF U+00EF # small i, dieresis or umlaut mark (&#239;) - iuml
+# small eth, Icelandic (&#240;) - eth
+U+00F0:dh
+0xF1 U+00F1 # small n, tilde (&#241;) - ntilde
+0xF2 U+00F2 # small o, grave accent (&#242;) - ograve
+0xF3 U+00F3 # small o, acute accent (&#243;) - oacute
+0xF4 U+00F4 # small o, circumflex accent (&#244;) - ocirc
+0xF5 U+00F5 # small o, tilde (&#245;) - otilde
+0xF6 U+00F6 # small o, dieresis or umlaut mark (&#246;) - ouml
+0xF7 U+0153 # small oe ligature (&#339;) - oelig
+# division sign (&#247;) - divide
+U+00F7:/
+0xF8 U+00F8 # small o, slash (&#248;) - oslash
+0xF9 U+00F9 # small u, grave accent (&#249;) - ugrave
+0xFA U+00FA # small u, acute accent (&#250;) - uacute
+0xFB U+00FB # small u, circumflex accent (&#251;) - ucirc
+0xFC U+00FC # small u, dieresis or umlaut mark (&#252;) - uuml
+0xFD U+00FF # small y, dieresis or umlaut mark (&#255;) - yuml
+# small y, acute accent (&#253;) - yacute
+U+00FD:y'
+# small thorn, Icelandic (&#254;) - thorn
+U+00FE:th
+#
+# TRADE MARK SIGN
+U+2122:(TM)
diff --git a/src/chrtrans/entities.h b/src/chrtrans/entities.h
new file mode 100644
index 0000000..66f66ab
--- /dev/null
+++ b/src/chrtrans/entities.h
@@ -0,0 +1,1414 @@
+/*
+ * $LynxId: entities.h,v 1.6 2020/01/21 21:31:36 tom Exp $
+ *
+ * Entity Names to Unicode table
+ * -----------------------------
+ *
+ * This is a one-way mapping to Unicode so chartrans implementation
+ * now process character entities like &nbsp the similar way it handles
+ * the numeric entities like &#123.
+ * The only call to this structure is via HTMLGetEntityUCValue().
+ *
+
+Unlike the numeric entities &#234 which may be for any Unicode character, the
+character references should be defined within HTML standards to get a
+compatibility between browsers.
+
+Now we have a choice: use clean HTML4.0 entities list (and reject everything
+others), or use a relaxed list with lots of synonyms and new symbols found at
+
+ftp://ftp.unicode.org/Public/MAPPINGS/VENDORS/MISC/SGML.TXT
+
+We hold both: #define ENTITIES_HTML40_ONLY for strict version,
+otherwise relaxed.
+
+ */
+
+#include <UCkd.h> /* typedef u16 */
+typedef struct {
+ const char *name; /* sorted alphabetically (case-sensitive) */
+ u16 code;
+} UC_entity_info;
+
+static const UC_entity_info unicode_entities[] =
+/* *INDENT-OFF* */
+#ifdef ENTITIES_HTML40_ONLY
+/*********************************************************************
+
+ The full list of character references defined as part of HTML 4.0.
+ http://www.w3.org/TR/PR-html40/sgml/entities.html
+
+ Informal history:
+ * ISO Latin 1 entities for 160-255 range were introduced in HTML 2.0
+ * few important entities were added, including &lt, &gt, &amp.
+ * Greek letters and some math symbols were finally added in HTML 4.0
+
+ Totally 252 entries (Nov 1997 HTML 4.0 draft), it is 1:1 mapping.
+ Please do not add more unless a new HTML version will be released,
+ try the #else table for experiments and fun...
+
+****/
+{
+ {"AElig", 198}, /* latin capital letter AE = latin capital ligature AE, U+00C6 ISOlat1 */
+ {"Aacute", 193}, /* latin capital letter A with acute, U+00C1 ISOlat1 */
+ {"Acirc", 194}, /* latin capital letter A with circumflex, U+00C2 ISOlat1 */
+ {"Agrave", 192}, /* latin capital letter A with grave = latin capital letter A grave, U+00C0 ISOlat1 */
+ {"Alpha", 913}, /* greek capital letter alpha, U+0391 */
+ {"Aring", 197}, /* latin capital letter A with ring above = latin capital letter A ring, U+00C5 ISOlat1 */
+ {"Atilde", 195}, /* latin capital letter A with tilde, U+00C3 ISOlat1 */
+ {"Auml", 196}, /* latin capital letter A with diaeresis, U+00C4 ISOlat1 */
+ {"Beta", 914}, /* greek capital letter beta, U+0392 */
+ {"Ccedil", 199}, /* latin capital letter C with cedilla, U+00C7 ISOlat1 */
+ {"Chi", 935}, /* greek capital letter chi, U+03A7 */
+ {"Dagger", 8225}, /* double dagger, U+2021 ISOpub */
+ {"Delta", 916}, /* greek capital letter delta, U+0394 ISOgrk3 */
+ {"ETH", 208}, /* latin capital letter ETH, U+00D0 ISOlat1 */
+ {"Eacute", 201}, /* latin capital letter E with acute, U+00C9 ISOlat1 */
+ {"Ecirc", 202}, /* latin capital letter E with circumflex, U+00CA ISOlat1 */
+ {"Egrave", 200}, /* latin capital letter E with grave, U+00C8 ISOlat1 */
+ {"Epsilon", 917}, /* greek capital letter epsilon, U+0395 */
+ {"Eta", 919}, /* greek capital letter eta, U+0397 */
+ {"Euml", 203}, /* latin capital letter E with diaeresis, U+00CB ISOlat1 */
+ {"Gamma", 915}, /* greek capital letter gamma, U+0393 ISOgrk3 */
+ {"Iacute", 205}, /* latin capital letter I with acute, U+00CD ISOlat1 */
+ {"Icirc", 206}, /* latin capital letter I with circumflex, U+00CE ISOlat1 */
+ {"Igrave", 204}, /* latin capital letter I with grave, U+00CC ISOlat1 */
+ {"Iota", 921}, /* greek capital letter iota, U+0399 */
+ {"Iuml", 207}, /* latin capital letter I with diaeresis, U+00CF ISOlat1 */
+ {"Kappa", 922}, /* greek capital letter kappa, U+039A */
+ {"Lambda", 923}, /* greek capital letter lambda, U+039B ISOgrk3 */
+ {"Mu", 924}, /* greek capital letter mu, U+039C */
+ {"Ntilde", 209}, /* latin capital letter N with tilde, U+00D1 ISOlat1 */
+ {"Nu", 925}, /* greek capital letter nu, U+039D */
+ {"OElig", 338}, /* latin capital ligature OE, U+0152 ISOlat2 */
+ {"Oacute", 211}, /* latin capital letter O with acute, U+00D3 ISOlat1 */
+ {"Ocirc", 212}, /* latin capital letter O with circumflex, U+00D4 ISOlat1 */
+ {"Ograve", 210}, /* latin capital letter O with grave, U+00D2 ISOlat1 */
+ {"Omega", 937}, /* greek capital letter omega, U+03A9 ISOgrk3 */
+ {"Omicron", 927}, /* greek capital letter omicron, U+039F */
+ {"Oslash", 216}, /* latin capital letter O with stroke = latin capital letter O slash, U+00D8 ISOlat1 */
+ {"Otilde", 213}, /* latin capital letter O with tilde, U+00D5 ISOlat1 */
+ {"Ouml", 214}, /* latin capital letter O with diaeresis, U+00D6 ISOlat1 */
+ {"Phi", 934}, /* greek capital letter phi, U+03A6 ISOgrk3 */
+ {"Pi", 928}, /* greek capital letter pi, U+03A0 ISOgrk3 */
+ {"Prime", 8243}, /* double prime = seconds = inches, U+2033 ISOtech */
+ {"Psi", 936}, /* greek capital letter psi, U+03A8 ISOgrk3 */
+ {"Rho", 929}, /* greek capital letter rho, U+03A1 */
+ {"Scaron", 352}, /* latin capital letter S with caron, U+0160 ISOlat2 */
+/* there is no Sigmaf, and no U+03A2 character either */
+ {"Sigma", 931}, /* greek capital letter sigma, U+03A3 ISOgrk3 */
+ {"THORN", 222}, /* latin capital letter THORN, U+00DE ISOlat1 */
+ {"Tau", 932}, /* greek capital letter tau, U+03A4 */
+ {"Theta", 920}, /* greek capital letter theta, U+0398 ISOgrk3 */
+ {"Uacute", 218}, /* latin capital letter U with acute, U+00DA ISOlat1 */
+ {"Ucirc", 219}, /* latin capital letter U with circumflex, U+00DB ISOlat1 */
+ {"Ugrave", 217}, /* latin capital letter U with grave, U+00D9 ISOlat1 */
+ {"Upsilon", 933}, /* greek capital letter upsilon, U+03A5 ISOgrk3 */
+ {"Uuml", 220}, /* latin capital letter U with diaeresis, U+00DC ISOlat1 */
+ {"Xi", 926}, /* greek capital letter xi, U+039E ISOgrk3 */
+ {"Yacute", 221}, /* latin capital letter Y with acute, U+00DD ISOlat1 */
+ {"Yuml", 376}, /* latin capital letter Y with diaeresis, U+0178 ISOlat2 */
+ {"Zeta", 918}, /* greek capital letter zeta, U+0396 */
+ {"aacute", 225}, /* latin small letter a with acute, U+00E1 ISOlat1 */
+ {"acirc", 226}, /* latin small letter a with circumflex, U+00E2 ISOlat1 */
+ {"acute", 180}, /* acute accent = spacing acute, U+00B4 ISOdia */
+ {"aelig", 230}, /* latin small letter ae = latin small ligature ae, U+00E6 ISOlat1 */
+ {"agrave", 224}, /* latin small letter a with grave = latin small letter a grave, U+00E0 ISOlat1 */
+ {"alefsym", 8501}, /* alef symbol = first transfinite cardinal, U+2135 NEW */
+/* alef symbol is NOT the same as hebrew letter alef, U+05D0 although the same glyph could be used to depict both characters */
+ {"alpha", 945}, /* greek small letter alpha, U+03B1 ISOgrk3 */
+ {"amp", 38}, /* ampersand, U+0026 ISOnum */
+ {"and", 8743}, /* logical and = wedge, U+2227 ISOtech */
+ {"ang", 8736}, /* angle, U+2220 ISOamso */
+ {"aring", 229}, /* latin small letter a with ring above = latin small letter a ring, U+00E5 ISOlat1 */
+ {"asymp", 8776}, /* almost equal to = asymptotic to, U+2248 ISOamsr */
+ {"atilde", 227}, /* latin small letter a with tilde, U+00E3 ISOlat1 */
+ {"auml", 228}, /* latin small letter a with diaeresis, U+00E4 ISOlat1 */
+ {"bdquo", 8222}, /* double low-9 quotation mark, U+201E NEW */
+ {"beta", 946}, /* greek small letter beta, U+03B2 ISOgrk3 */
+ {"brvbar", 166}, /* broken bar = broken vertical bar, U+00A6 ISOnum */
+ {"bull", 8226}, /* bullet = black small circle, U+2022 ISOpub */
+/* bullet is NOT the same as bullet operator, U+2219 */
+ {"cap", 8745}, /* intersection = cap, U+2229 ISOtech */
+ {"ccedil", 231}, /* latin small letter c with cedilla, U+00E7 ISOlat1 */
+ {"cedil", 184}, /* cedilla = spacing cedilla, U+00B8 ISOdia */
+ {"cent", 162}, /* cent sign, U+00A2 ISOnum */
+ {"chi", 967}, /* greek small letter chi, U+03C7 ISOgrk3 */
+ {"circ", 710}, /* modifier letter circumflex accent, U+02C6 ISOpub */
+ {"clubs", 9827}, /* black club suit = shamrock, U+2663 ISOpub */
+ {"cong", 8773}, /* approximately equal to, U+2245 ISOtech */
+ {"copy", 169}, /* copyright sign, U+00A9 ISOnum */
+ {"crarr", 8629}, /* downwards arrow with corner leftwards = carriage return, U+21B5 NEW */
+ {"cup", 8746}, /* union = cup, U+222A ISOtech */
+ {"curren", 164}, /* currency sign, U+00A4 ISOnum */
+ {"dArr", 8659}, /* downwards double arrow, U+21D3 ISOamsa */
+ {"dagger", 8224}, /* dagger, U+2020 ISOpub */
+ {"darr", 8595}, /* downwards arrow, U+2193 ISOnum */
+ {"deg", 176}, /* degree sign, U+00B0 ISOnum */
+ {"delta", 948}, /* greek small letter delta, U+03B4 ISOgrk3 */
+ {"diams", 9830}, /* black diamond suit, U+2666 ISOpub */
+ {"divide", 247}, /* division sign, U+00F7 ISOnum */
+ {"eacute", 233}, /* latin small letter e with acute, U+00E9 ISOlat1 */
+ {"ecirc", 234}, /* latin small letter e with circumflex, U+00EA ISOlat1 */
+ {"egrave", 232}, /* latin small letter e with grave, U+00E8 ISOlat1 */
+ {"empty", 8709}, /* empty set = null set = diameter, U+2205 ISOamso */
+ {"emsp", 8195}, /* em space, U+2003 ISOpub */
+ {"ensp", 8194}, /* en space, U+2002 ISOpub */
+ {"epsilon", 949}, /* greek small letter epsilon, U+03B5 ISOgrk3 */
+ {"equiv", 8801}, /* identical to, U+2261 ISOtech */
+ {"eta", 951}, /* greek small letter eta, U+03B7 ISOgrk3 */
+ {"eth", 240}, /* latin small letter eth, U+00F0 ISOlat1 */
+ {"euml", 235}, /* latin small letter e with diaeresis, U+00EB ISOlat1 */
+ {"euro", 8364}, /* euro sign, U+20AC NEW */
+ {"exist", 8707}, /* there exists, U+2203 ISOtech */
+ {"fnof", 402}, /* latin small f with hook = function = florin, U+0192 ISOtech */
+ {"forall", 8704}, /* for all, U+2200 ISOtech */
+ {"frac12", 189}, /* vulgar fraction one half = fraction one half, U+00BD ISOnum */
+ {"frac14", 188}, /* vulgar fraction one quarter = fraction one quarter, U+00BC ISOnum */
+ {"frac34", 190}, /* vulgar fraction three quarters = fraction three quarters, U+00BE ISOnum */
+ {"frasl", 8260}, /* fraction slash, U+2044 NEW */
+ {"gamma", 947}, /* greek small letter gamma, U+03B3 ISOgrk3 */
+ {"ge", 8805}, /* greater-than or equal to, U+2265 ISOtech */
+ {"gt", 62}, /* greater-than sign, U+003E ISOnum */
+ {"hArr", 8660}, /* left right double arrow, U+21D4 ISOamsa */
+ {"harr", 8596}, /* left right arrow, U+2194 ISOamsa */
+ {"hearts", 9829}, /* black heart suit = valentine, U+2665 ISOpub */
+ {"hellip", 8230}, /* horizontal ellipsis = three dot leader, U+2026 ISOpub */
+ {"iacute", 237}, /* latin small letter i with acute, U+00ED ISOlat1 */
+ {"icirc", 238}, /* latin small letter i with circumflex, U+00EE ISOlat1 */
+ {"iexcl", 161}, /* inverted exclamation mark, U+00A1 ISOnum */
+ {"igrave", 236}, /* latin small letter i with grave, U+00EC ISOlat1 */
+ {"image", 8465}, /* blackletter capital I = imaginary part, U+2111 ISOamso */
+ {"infin", 8734}, /* infinity, U+221E ISOtech */
+ {"int", 8747}, /* integral, U+222B ISOtech */
+ {"iota", 953}, /* greek small letter iota, U+03B9 ISOgrk3 */
+ {"iquest", 191}, /* inverted question mark = turned question mark, U+00BF ISOnum */
+ {"isin", 8712}, /* element of, U+2208 ISOtech */
+ {"iuml", 239}, /* latin small letter i with diaeresis, U+00EF ISOlat1 */
+ {"kappa", 954}, /* greek small letter kappa, U+03BA ISOgrk3 */
+ {"lArr", 8656}, /* leftwards double arrow, U+21D0 ISOtech */
+/* Unicode does not say that lArr is the same as the 'is implied by' arrow
+ but also does not have any other character for that function. So ? lArr can
+ be used for 'is implied by' as ISOtech suggests */
+ {"lambda", 955}, /* greek small letter lambda, U+03BB ISOgrk3 */
+ {"lang", 9001}, /* left-pointing angle bracket = bra, U+2329 ISOtech */
+/* lang is NOT the same character as U+003C 'less than' or U+2039 'single left-pointing angle quotation mark' */
+ {"laquo", 171}, /* left-pointing double angle quotation mark = left pointing guillemet, U+00AB ISOnum */
+ {"larr", 8592}, /* leftwards arrow, U+2190 ISOnum */
+ {"lceil", 8968}, /* left ceiling = apl upstile, U+2308 ISOamsc */
+ {"ldquo", 8220}, /* left double quotation mark, U+201C ISOnum */
+ {"le", 8804}, /* less-than or equal to, U+2264 ISOtech */
+ {"lfloor", 8970}, /* left floor = apl downstile, U+230A ISOamsc */
+ {"lowast", 8727}, /* asterisk operator, U+2217 ISOtech */
+ {"loz", 9674}, /* lozenge, U+25CA ISOpub */
+ {"lrm", 8206}, /* left-to-right mark, U+200E NEW RFC 2070 */
+ {"lsaquo", 8249}, /* single left-pointing angle quotation mark, U+2039 ISO proposed */
+/* lsaquo is proposed but not yet ISO standardised */
+ {"lsquo", 8216}, /* left single quotation mark, U+2018 ISOnum */
+ {"lt", 60}, /* less-than sign, U+003C ISOnum */
+ {"macr", 175}, /* macron = spacing macron = overline = APL overbar, U+00AF ISOdia */
+ {"mdash", 8212}, /* em dash, U+2014 ISOpub */
+ {"micro", 181}, /* micro sign, U+00B5 ISOnum */
+ {"middot", 183}, /* middle dot = Georgian comma = Greek middle dot, U+00B7 ISOnum */
+ {"minus", 8722}, /* minus sign, U+2212 ISOtech */
+ {"mu", 956}, /* greek small letter mu, U+03BC ISOgrk3 */
+ {"nabla", 8711}, /* nabla = backward difference, U+2207 ISOtech */
+ {"nbsp", 160}, /* no-break space = non-breaking space, U+00A0 ISOnum */
+ {"ndash", 8211}, /* en dash, U+2013 ISOpub */
+ {"ne", 8800}, /* not equal to, U+2260 ISOtech */
+ {"ni", 8715}, /* contains as member, U+220B ISOtech */
+/* should there be a more memorable name than 'ni'? */
+ {"not", 172}, /* not sign = discretionary hyphen, U+00AC ISOnum */
+ {"notin", 8713}, /* not an element of, U+2209 ISOtech */
+ {"nsub", 8836}, /* not a subset of, U+2284 ISOamsn */
+ {"ntilde", 241}, /* latin small letter n with tilde, U+00F1 ISOlat1 */
+ {"nu", 957}, /* greek small letter nu, U+03BD ISOgrk3 */
+ {"oacute", 243}, /* latin small letter o with acute, U+00F3 ISOlat1 */
+ {"ocirc", 244}, /* latin small letter o with circumflex, U+00F4 ISOlat1 */
+ {"oelig", 339}, /* latin small ligature oe, U+0153 ISOlat2 */
+ {"ograve", 242}, /* latin small letter o with grave, U+00F2 ISOlat1 */
+ {"oline", 8254}, /* overline = spacing overscore, U+203E NEW */
+ {"omega", 969}, /* greek small letter omega, U+03C9 ISOgrk3 */
+ {"omicron", 959}, /* greek small letter omicron, U+03BF NEW */
+ {"oplus", 8853}, /* circled plus = direct sum, U+2295 ISOamsb */
+ {"or", 8744}, /* logical or = vee, U+2228 ISOtech */
+ {"ordf", 170}, /* feminine ordinal indicator, U+00AA ISOnum */
+ {"ordm", 186}, /* masculine ordinal indicator, U+00BA ISOnum */
+ {"oslash", 248}, /* latin small letter o with stroke, = latin small letter o slash, U+00F8 ISOlat1 */
+ {"otilde", 245}, /* latin small letter o with tilde, U+00F5 ISOlat1 */
+ {"otimes", 8855}, /* circled times = vector product, U+2297 ISOamsb */
+ {"ouml", 246}, /* latin small letter o with diaeresis, U+00F6 ISOlat1 */
+ {"para", 182}, /* pilcrow sign = paragraph sign, U+00B6 ISOnum */
+ {"part", 8706}, /* partial differential, U+2202 ISOtech */
+ {"permil", 8240}, /* per mille sign, U+2030 ISOtech */
+ {"perp", 8869}, /* up tack = orthogonal to = perpendicular, U+22A5 ISOtech */
+ {"phi", 966}, /* greek small letter phi, U+03C6 ISOgrk3 */
+ {"pi", 960}, /* greek small letter pi, U+03C0 ISOgrk3 */
+ {"piv", 982}, /* greek pi symbol, U+03D6 ISOgrk3 */
+ {"plusmn", 177}, /* plus-minus sign = plus-or-minus sign, U+00B1 ISOnum */
+ {"pound", 163}, /* pound sign, U+00A3 ISOnum */
+ {"prime", 8242}, /* prime = minutes = feet, U+2032 ISOtech */
+ {"prod", 8719}, /* n-ary product = product sign, U+220F ISOamsb */
+/* prod is NOT the same character as U+03A0 'greek capital letter pi' though the same glyph might be used for both */
+ {"prop", 8733}, /* proportional to, U+221D ISOtech */
+ {"psi", 968}, /* greek small letter psi, U+03C8 ISOgrk3 */
+ {"quot", 34}, /* quotation mark = APL quote, U+0022 ISOnum */
+ {"rArr", 8658}, /* rightwards double arrow, U+21D2 ISOtech */
+/* Unicode does not say this is the 'implies' character but does not have
+ another character with this function so ?
+ rArr can be used for 'implies' as ISOtech suggests */
+ {"radic", 8730}, /* square root = radical sign, U+221A ISOtech */
+ {"rang", 9002}, /* right-pointing angle bracket = ket, U+232A ISOtech */
+/* rang is NOT the same character as U+003E 'greater than' or U+203A 'single right-pointing angle quotation mark' */
+ {"raquo", 187}, /* right-pointing double angle quotation mark = right pointing guillemet, U+00BB ISOnum */
+ {"rarr", 8594}, /* rightwards arrow, U+2192 ISOnum */
+ {"rceil", 8969}, /* right ceiling, U+2309 ISOamsc */
+ {"rdquo", 8221}, /* right double quotation mark, U+201D ISOnum */
+ {"real", 8476}, /* blackletter capital R = real part symbol, U+211C ISOamso */
+ {"reg", 174}, /* registered sign = registered trade mark sign, U+00AE ISOnum */
+ {"rfloor", 8971}, /* right floor, U+230B ISOamsc */
+ {"rho", 961}, /* greek small letter rho, U+03C1 ISOgrk3 */
+ {"rlm", 8207}, /* right-to-left mark, U+200F NEW RFC 2070 */
+ {"rsaquo", 8250}, /* single right-pointing angle quotation mark, U+203A ISO proposed */
+/* rsaquo is proposed but not yet ISO standardised */
+ {"rsquo", 8217}, /* right single quotation mark, U+2019 ISOnum */
+ {"sbquo", 8218}, /* single low-9 quotation mark, U+201A NEW */
+ {"scaron", 353}, /* latin small letter s with caron, U+0161 ISOlat2 */
+ {"sdot", 8901}, /* dot operator, U+22C5 ISOamsb */
+/* dot operator is NOT the same character as U+00B7 middle dot */
+ {"sect", 167}, /* section sign, U+00A7 ISOnum */
+ {"shy", 173}, /* soft hyphen = discretionary hyphen, U+00AD ISOnum */
+ {"sigma", 963}, /* greek small letter sigma, U+03C3 ISOgrk3 */
+ {"sigmaf", 962}, /* greek small letter final sigma, U+03C2 ISOgrk3 */
+ {"sim", 8764}, /* tilde operator = varies with = similar to, U+223C ISOtech */
+/* tilde operator is NOT the same character as the tilde, U+007E, although the same glyph might be used to represent both */
+ {"spades", 9824}, /* black spade suit, U+2660 ISOpub */
+/* black here seems to mean filled as opposed to hollow */
+ {"sub", 8834}, /* subset of, U+2282 ISOtech */
+ {"sube", 8838}, /* subset of or equal to, U+2286 ISOtech */
+ {"sum", 8721}, /* n-ary sumation, U+2211 ISOamsb */
+/* sum is NOT the same character as U+03A3 'greek capital letter sigma' though the same glyph might be used for both */
+ {"sup", 8835}, /* superset of, U+2283 ISOtech */
+/* note that nsup, 'not a superset of, U+2283' is not covered by the Symbol
+ font encoding and is not included. Should it be, for symmetry?
+ It is in ISOamsn */
+ {"sup1", 185}, /* superscript one = superscript digit one, U+00B9 ISOnum */
+ {"sup2", 178}, /* superscript two = superscript digit two = squared, U+00B2 ISOnum */
+ {"sup3", 179}, /* superscript three = superscript digit three = cubed, U+00B3 ISOnum */
+ {"supe", 8839}, /* superset of or equal to, U+2287 ISOtech */
+ {"szlig", 223}, /* latin small letter sharp s = ess-zed, U+00DF ISOlat1 */
+ {"tau", 964}, /* greek small letter tau, U+03C4 ISOgrk3 */
+ {"there4", 8756}, /* therefore, U+2234 ISOtech */
+ {"theta", 952}, /* greek small letter theta, U+03B8 ISOgrk3 */
+ {"thetasym", 977}, /* greek small letter theta symbol, U+03D1 NEW */
+ {"thinsp", 8201}, /* thin space, U+2009 ISOpub */
+ {"thorn", 254}, /* latin small letter thorn with, U+00FE ISOlat1 */
+ {"tilde", 732}, /* small tilde, U+02DC ISOdia */
+ {"times", 215}, /* multiplication sign, U+00D7 ISOnum */
+ {"trade", 8482}, /* trade mark sign, U+2122 ISOnum */
+ {"uArr", 8657}, /* upwards double arrow, U+21D1 ISOamsa */
+ {"uacute", 250}, /* latin small letter u with acute, U+00FA ISOlat1 */
+ {"uarr", 8593}, /* upwards arrow, U+2191 ISOnum */
+ {"ucirc", 251}, /* latin small letter u with circumflex, U+00FB ISOlat1 */
+ {"ugrave", 249}, /* latin small letter u with grave, U+00F9 ISOlat1 */
+ {"uml", 168}, /* diaeresis = spacing diaeresis, U+00A8 ISOdia */
+ {"upsih", 978}, /* greek upsilon with hook symbol, U+03D2 NEW */
+ {"upsilon", 965}, /* greek small letter upsilon, U+03C5 ISOgrk3 */
+ {"uuml", 252}, /* latin small letter u with diaeresis, U+00FC ISOlat1 */
+ {"weierp", 8472}, /* script capital P = power set = Weierstrass p, U+2118 ISOamso */
+ {"xi", 958}, /* greek small letter xi, U+03BE ISOgrk3 */
+ {"yacute", 253}, /* latin small letter y with acute, U+00FD ISOlat1 */
+ {"yen", 165}, /* yen sign = yuan sign, U+00A5 ISOnum */
+ {"yuml", 255}, /* latin small letter y with diaeresis, U+00FF ISOlat1 */
+ {"zeta", 950}, /* greek small letter zeta, U+03B6 ISOgrk3 */
+ {"zwj", 8205}, /* zero width joiner, U+200D NEW RFC 2070 */
+ {"zwnj", 8204}, /* zero width non-joiner, U+200C NEW RFC 2070 */
+};
+
+#else /* not ENTITIES_HTML40_ONLY: */
+/***************************************************************************
+
+This table prepared from ftp://ftp.unicode.org/MAPPINGS/VENDORS/MISC/SGML.TXT
+original comment follows:
+
+
+# Author: John Cowan <cowan@ccil.org>
+# Date: 25 July 1997
+#
+# The following table maps SGML character entities from various
+# public sets (namely, ISOamsa, ISOamsb, ISOamsc, ISOamsn, ISOamso,
+# ISOamsr, ISObox, ISOcyr1, ISOcyr2, ISOdia, ISOgrk1, ISOgrk2,
+# ISOgrk3, ISOgrk4, ISOlat1, ISOlat2, ISOnum, ISOpub, ISOtech,
+# HTMLspecial, HTMLsymbol) to corresponding Unicode characters.
+#
+# The table has four tab-separated columns:
+# Column 1: SGML character entity name
+# Column 2: SGML public entity set
+# Column 3: Unicode 2.0 character code
+# Column 4: Unicode 2.0 character name (UPPER CASE)
+# Entries which don't have Unicode equivalents have "0x????"
+# in Column 3 and a lower case description (from the public entity
+# set DTD) in Column 4. The mapping is not reversible, because many
+# distinctions are unified away in Unicode, particularly between
+# mathematical symbols.
+
+
+ We just sort it and move column 2 away (line too long, sorry;
+ look at sgml.html in test/ directory for details).
+
+Changes:
+ * Add few (obsolete) synonyms for compatibility with Lynx/2.5 and up:
+ "brkbar" for "brvbar" 0x00A6
+ "emdash" for "mdash" 0x2014
+ "endash" for "ndash" 0x2013
+ "hibar" for "macr" 0x00AF
+ BTW, lots of synonyms found in this table, we shouldn't worry about...
+ Totally around 1000 entries.
+
+
+Modified by Jacob Poon <jacob.poon@utoronto.ca>
+
+This table is modified improve support of HTML 4.0 character entity references,
+including Euro symbol support ("euro" 0x20AC).
+
+Known issues:
+
+The original table includes two different definitions of &loz; reference.
+Since HTML 4.0 only uses U+25CA, the U+2727 definition is commented out,
+until there is a good reason to put it back in.
+
+"b.delta" mapping fixed (was 0x03B3 = small gamma).
+
+At the end of the table, there are several unnumbered, commented references.
+These are not defined in HTML 4.0, and will remain so until they are defined
+in future SGML/HTML standards.
+
+The support for obsolete references are for backwards compatibility only. New
+SGML/HTML documents should not depend on these references just because Lynx can
+display them.
+
+****/
+{
+ {"AElig", 0x00C6}, /* LATIN CAPITAL LETTER AE */
+ {"Aacgr", 0x0386}, /* GREEK CAPITAL LETTER ALPHA WITH TONOS */
+ {"Aacute", 0x00C1}, /* LATIN CAPITAL LETTER A WITH ACUTE */
+ {"Abreve", 0x0102}, /* LATIN CAPITAL LETTER A WITH BREVE */
+ {"Acirc", 0x00C2}, /* LATIN CAPITAL LETTER A WITH CIRCUMFLEX */
+ {"Acy", 0x0410}, /* CYRILLIC CAPITAL LETTER A */
+ {"Agr", 0x0391}, /* GREEK CAPITAL LETTER ALPHA */
+ {"Agrave", 0x00C0}, /* LATIN CAPITAL LETTER A WITH GRAVE */
+ {"Alpha", 0x0391}, /* GREEK CAPITAL LETTER ALPHA */
+ {"Amacr", 0x0100}, /* LATIN CAPITAL LETTER A WITH MACRON */
+ {"Aogon", 0x0104}, /* LATIN CAPITAL LETTER A WITH OGONEK */
+ {"Aring", 0x00C5}, /* LATIN CAPITAL LETTER A WITH RING ABOVE */
+ {"Atilde", 0x00C3}, /* LATIN CAPITAL LETTER A WITH TILDE */
+ {"Auml", 0x00C4}, /* LATIN CAPITAL LETTER A WITH DIAERESIS */
+ {"Barwed", 0x2306}, /* PERSPECTIVE */
+ {"Bcy", 0x0411}, /* CYRILLIC CAPITAL LETTER BE */
+ {"Beta", 0x0392}, /* GREEK CAPITAL LETTER BETA */
+ {"Bgr", 0x0392}, /* GREEK CAPITAL LETTER BETA */
+ {"CHcy", 0x0427}, /* CYRILLIC CAPITAL LETTER CHE */
+ {"Cacute", 0x0106}, /* LATIN CAPITAL LETTER C WITH ACUTE */
+ {"Cap", 0x22D2}, /* DOUBLE INTERSECTION */
+ {"Ccaron", 0x010C}, /* LATIN CAPITAL LETTER C WITH CARON */
+ {"Ccedil", 0x00C7}, /* LATIN CAPITAL LETTER C WITH CEDILLA */
+ {"Ccirc", 0x0108}, /* LATIN CAPITAL LETTER C WITH CIRCUMFLEX */
+ {"Cdot", 0x010A}, /* LATIN CAPITAL LETTER C WITH DOT ABOVE */
+ {"Chi", 0x03A7}, /* GREEK CAPITAL LETTER CHI */
+ {"Cup", 0x22D3}, /* DOUBLE UNION */
+ {"DJcy", 0x0402}, /* CYRILLIC CAPITAL LETTER DJE */
+ {"DScy", 0x0405}, /* CYRILLIC CAPITAL LETTER DZE */
+ {"DZcy", 0x040F}, /* CYRILLIC CAPITAL LETTER DZHE */
+ {"Dagger", 0x2021}, /* DOUBLE DAGGER */
+ {"Dcaron", 0x010E}, /* LATIN CAPITAL LETTER D WITH CARON */
+ {"Dcy", 0x0414}, /* CYRILLIC CAPITAL LETTER DE */
+ {"Delta", 0x0394}, /* GREEK CAPITAL LETTER DELTA */
+ {"Dgr", 0x0394}, /* GREEK CAPITAL LETTER DELTA */
+ {"Dot", 0x00A8}, /* DIAERESIS */
+ {"DotDot", 0x20DC}, /* COMBINING FOUR DOTS ABOVE */
+ {"Dstrok", 0x0110}, /* LATIN CAPITAL LETTER D WITH STROKE */
+ {"EEacgr", 0x0389}, /* GREEK CAPITAL LETTER ETA WITH TONOS */
+ {"EEgr", 0x0397}, /* GREEK CAPITAL LETTER ETA */
+ {"ENG", 0x014A}, /* LATIN CAPITAL LETTER ENG */
+ {"ETH", 0x00D0}, /* LATIN CAPITAL LETTER ETH */
+ {"Eacgr", 0x0388}, /* GREEK CAPITAL LETTER EPSILON WITH TONOS */
+ {"Eacute", 0x00C9}, /* LATIN CAPITAL LETTER E WITH ACUTE */
+ {"Ecaron", 0x011A}, /* LATIN CAPITAL LETTER E WITH CARON */
+ {"Ecirc", 0x00CA}, /* LATIN CAPITAL LETTER E WITH CIRCUMFLEX */
+ {"Ecy", 0x042D}, /* CYRILLIC CAPITAL LETTER E */
+ {"Edot", 0x0116}, /* LATIN CAPITAL LETTER E WITH DOT ABOVE */
+ {"Egr", 0x0395}, /* GREEK CAPITAL LETTER EPSILON */
+ {"Egrave", 0x00C8}, /* LATIN CAPITAL LETTER E WITH GRAVE */
+ {"Emacr", 0x0112}, /* LATIN CAPITAL LETTER E WITH MACRON */
+ {"Eogon", 0x0118}, /* LATIN CAPITAL LETTER E WITH OGONEK */
+ {"Epsilon", 0x0395}, /* GREEK CAPITAL LETTER EPSILON */
+ {"Eta", 0x0397}, /* GREEK CAPITAL LETTER ETA */
+ {"Euml", 0x00CB}, /* LATIN CAPITAL LETTER E WITH DIAERESIS */
+ {"Fcy", 0x0424}, /* CYRILLIC CAPITAL LETTER EF */
+ {"GJcy", 0x0403}, /* CYRILLIC CAPITAL LETTER GJE */
+ {"Gamma", 0x0393}, /* GREEK CAPITAL LETTER GAMMA */
+ {"Gbreve", 0x011E}, /* LATIN CAPITAL LETTER G WITH BREVE */
+ {"Gcedil", 0x0122}, /* LATIN CAPITAL LETTER G WITH CEDILLA */
+ {"Gcirc", 0x011C}, /* LATIN CAPITAL LETTER G WITH CIRCUMFLEX */
+ {"Gcy", 0x0413}, /* CYRILLIC CAPITAL LETTER GHE */
+ {"Gdot", 0x0120}, /* LATIN CAPITAL LETTER G WITH DOT ABOVE */
+ {"Gg", 0x22D9}, /* VERY MUCH GREATER-THAN */
+ {"Ggr", 0x0393}, /* GREEK CAPITAL LETTER GAMMA */
+ {"Gt", 0x226B}, /* MUCH GREATER-THAN */
+ {"HARDcy", 0x042A}, /* CYRILLIC CAPITAL LETTER HARD SIGN */
+ {"Hcirc", 0x0124}, /* LATIN CAPITAL LETTER H WITH CIRCUMFLEX */
+ {"Hstrok", 0x0126}, /* LATIN CAPITAL LETTER H WITH STROKE */
+ {"IEcy", 0x0415}, /* CYRILLIC CAPITAL LETTER IE */
+ {"IJlig", 0x0132}, /* LATIN CAPITAL LIGATURE IJ */
+ {"IOcy", 0x0401}, /* CYRILLIC CAPITAL LETTER IO */
+ {"Iacgr", 0x038A}, /* GREEK CAPITAL LETTER IOTA WITH TONOS */
+ {"Iacute", 0x00CD}, /* LATIN CAPITAL LETTER I WITH ACUTE */
+ {"Icirc", 0x00CE}, /* LATIN CAPITAL LETTER I WITH CIRCUMFLEX */
+ {"Icy", 0x0418}, /* CYRILLIC CAPITAL LETTER I */
+ {"Idigr", 0x03AA}, /* GREEK CAPITAL LETTER IOTA WITH DIALYTIKA */
+ {"Idot", 0x0130}, /* LATIN CAPITAL LETTER I WITH DOT ABOVE */
+ {"Igr", 0x0399}, /* GREEK CAPITAL LETTER IOTA */
+ {"Igrave", 0x00CC}, /* LATIN CAPITAL LETTER I WITH GRAVE */
+ {"Imacr", 0x012A}, /* LATIN CAPITAL LETTER I WITH MACRON */
+ {"Iogon", 0x012E}, /* LATIN CAPITAL LETTER I WITH OGONEK */
+ {"Iota", 0x0399}, /* GREEK CAPITAL LETTER IOTA */
+ {"Itilde", 0x0128}, /* LATIN CAPITAL LETTER I WITH TILDE */
+ {"Iukcy", 0x0406}, /* CYRILLIC CAPITAL LETTER BYELORUSSIAN-UKRAINIAN*/
+ {"Iuml", 0x00CF}, /* LATIN CAPITAL LETTER I WITH DIAERESIS */
+ {"Jcirc", 0x0134}, /* LATIN CAPITAL LETTER J WITH CIRCUMFLEX */
+ {"Jcy", 0x0419}, /* CYRILLIC CAPITAL LETTER SHORT I */
+ {"Jsercy", 0x0408}, /* CYRILLIC CAPITAL LETTER JE */
+ {"Jukcy", 0x0404}, /* CYRILLIC CAPITAL LETTER UKRAINIAN IE */
+ {"KHcy", 0x0425}, /* CYRILLIC CAPITAL LETTER HA */
+ {"KHgr", 0x03A7}, /* GREEK CAPITAL LETTER CHI */
+ {"KJcy", 0x040C}, /* CYRILLIC CAPITAL LETTER KJE */
+ {"Kappa", 0x039A}, /* GREEK CAPITAL LETTER KAPPA */
+ {"Kcedil", 0x0136}, /* LATIN CAPITAL LETTER K WITH CEDILLA */
+ {"Kcy", 0x041A}, /* CYRILLIC CAPITAL LETTER KA */
+ {"Kgr", 0x039A}, /* GREEK CAPITAL LETTER KAPPA */
+ {"LJcy", 0x0409}, /* CYRILLIC CAPITAL LETTER LJE */
+ {"Lacute", 0x0139}, /* LATIN CAPITAL LETTER L WITH ACUTE */
+ {"Lambda", 0x039B}, /* GREEK CAPITAL LETTER LAMDA */
+ {"Larr", 0x219E}, /* LEFTWARDS TWO HEADED ARROW */
+ {"Lcaron", 0x013D}, /* LATIN CAPITAL LETTER L WITH CARON */
+ {"Lcedil", 0x013B}, /* LATIN CAPITAL LETTER L WITH CEDILLA */
+ {"Lcy", 0x041B}, /* CYRILLIC CAPITAL LETTER EL */
+ {"Lgr", 0x039B}, /* GREEK CAPITAL LETTER LAMDA */
+ {"Ll", 0x22D8}, /* VERY MUCH LESS-THAN */
+ {"Lmidot", 0x013F}, /* LATIN CAPITAL LETTER L WITH MIDDLE DOT */
+ {"Lstrok", 0x0141}, /* LATIN CAPITAL LETTER L WITH STROKE */
+ {"Lt", 0x226A}, /* MUCH LESS-THAN */
+ {"Mcy", 0x041C}, /* CYRILLIC CAPITAL LETTER EM */
+ {"Mgr", 0x039C}, /* GREEK CAPITAL LETTER MU */
+ {"Mu", 0x039C}, /* GREEK CAPITAL LETTER MU */
+ {"NJcy", 0x040A}, /* CYRILLIC CAPITAL LETTER NJE */
+ {"Nacute", 0x0143}, /* LATIN CAPITAL LETTER N WITH ACUTE */
+ {"Ncaron", 0x0147}, /* LATIN CAPITAL LETTER N WITH CARON */
+ {"Ncedil", 0x0145}, /* LATIN CAPITAL LETTER N WITH CEDILLA */
+ {"Ncy", 0x041D}, /* CYRILLIC CAPITAL LETTER EN */
+ {"Ngr", 0x039D}, /* GREEK CAPITAL LETTER NU */
+ {"Ntilde", 0x00D1}, /* LATIN CAPITAL LETTER N WITH TILDE */
+ {"Nu", 0x039D}, /* GREEK CAPITAL LETTER NU */
+ {"OElig", 0x0152}, /* LATIN CAPITAL LIGATURE OE */
+ {"OHacgr", 0x038F}, /* GREEK CAPITAL LETTER OMEGA WITH TONOS */
+ {"OHgr", 0x03A9}, /* GREEK CAPITAL LETTER OMEGA */
+ {"Oacgr", 0x038C}, /* GREEK CAPITAL LETTER OMICRON WITH TONOS */
+ {"Oacute", 0x00D3}, /* LATIN CAPITAL LETTER O WITH ACUTE */
+ {"Ocirc", 0x00D4}, /* LATIN CAPITAL LETTER O WITH CIRCUMFLEX */
+ {"Ocy", 0x041E}, /* CYRILLIC CAPITAL LETTER O */
+ {"Odblac", 0x0150}, /* LATIN CAPITAL LETTER O WITH DOUBLE ACUTE */
+ {"Ogr", 0x039F}, /* GREEK CAPITAL LETTER OMICRON */
+ {"Ograve", 0x00D2}, /* LATIN CAPITAL LETTER O WITH GRAVE */
+ {"Omacr", 0x014C}, /* LATIN CAPITAL LETTER O WITH MACRON */
+ {"Omega", 0x03A9}, /* GREEK CAPITAL LETTER OMEGA */
+ {"Omicron", 0x039F}, /* GREEK CAPITAL LETTER OMICRON */
+ {"Oslash", 0x00D8}, /* LATIN CAPITAL LETTER O WITH STROKE */
+ {"Otilde", 0x00D5}, /* LATIN CAPITAL LETTER O WITH TILDE */
+ {"Ouml", 0x00D6}, /* LATIN CAPITAL LETTER O WITH DIAERESIS */
+ {"PHgr", 0x03A6}, /* GREEK CAPITAL LETTER PHI */
+ {"PSgr", 0x03A8}, /* GREEK CAPITAL LETTER PSI */
+ {"Pcy", 0x041F}, /* CYRILLIC CAPITAL LETTER PE */
+ {"Pgr", 0x03A0}, /* GREEK CAPITAL LETTER PI */
+ {"Phi", 0x03A6}, /* GREEK CAPITAL LETTER PHI */
+ {"Pi", 0x03A0}, /* GREEK CAPITAL LETTER PI */
+ {"Prime", 0x2033}, /* DOUBLE PRIME */
+ {"Psi", 0x03A8}, /* GREEK CAPITAL LETTER PSI */
+ {"Racute", 0x0154}, /* LATIN CAPITAL LETTER R WITH ACUTE */
+ {"Rarr", 0x21A0}, /* RIGHTWARDS TWO HEADED ARROW */
+ {"Rcaron", 0x0158}, /* LATIN CAPITAL LETTER R WITH CARON */
+ {"Rcedil", 0x0156}, /* LATIN CAPITAL LETTER R WITH CEDILLA */
+ {"Rcy", 0x0420}, /* CYRILLIC CAPITAL LETTER ER */
+ {"Rgr", 0x03A1}, /* GREEK CAPITAL LETTER RHO */
+ {"Rho", 0x03A1}, /* GREEK CAPITAL LETTER RHO */
+ {"SHCHcy", 0x0429}, /* CYRILLIC CAPITAL LETTER SHCHA */
+ {"SHcy", 0x0428}, /* CYRILLIC CAPITAL LETTER SHA */
+ {"SOFTcy", 0x042C}, /* CYRILLIC CAPITAL LETTER SOFT SIGN */
+ {"Sacute", 0x015A}, /* LATIN CAPITAL LETTER S WITH ACUTE */
+ {"Scaron", 0x0160}, /* LATIN CAPITAL LETTER S WITH CARON */
+ {"Scedil", 0x015E}, /* LATIN CAPITAL LETTER S WITH CEDILLA */
+ {"Scirc", 0x015C}, /* LATIN CAPITAL LETTER S WITH CIRCUMFLEX */
+ {"Scy", 0x0421}, /* CYRILLIC CAPITAL LETTER ES */
+ {"Sgr", 0x03A3}, /* GREEK CAPITAL LETTER SIGMA */
+ {"Sigma", 0x03A3}, /* GREEK CAPITAL LETTER SIGMA */
+ {"Sub", 0x22D0}, /* DOUBLE SUBSET */
+ {"Sup", 0x22D1}, /* DOUBLE SUPERSET */
+ {"THORN", 0x00DE}, /* LATIN CAPITAL LETTER THORN */
+ {"THgr", 0x0398}, /* GREEK CAPITAL LETTER THETA */
+ {"TSHcy", 0x040B}, /* CYRILLIC CAPITAL LETTER TSHE */
+ {"TScy", 0x0426}, /* CYRILLIC CAPITAL LETTER TSE */
+ {"Tau", 0x03A4}, /* GREEK CAPITAL LETTER TAU */
+ {"Tcaron", 0x0164}, /* LATIN CAPITAL LETTER T WITH CARON */
+ {"Tcedil", 0x0162}, /* LATIN CAPITAL LETTER T WITH CEDILLA */
+ {"Tcy", 0x0422}, /* CYRILLIC CAPITAL LETTER TE */
+ {"Tgr", 0x03A4}, /* GREEK CAPITAL LETTER TAU */
+ {"Theta", 0x0398}, /* GREEK CAPITAL LETTER THETA */
+ {"Tstrok", 0x0166}, /* LATIN CAPITAL LETTER T WITH STROKE */
+ {"Uacgr", 0x038E}, /* GREEK CAPITAL LETTER UPSILON WITH TONOS */
+ {"Uacute", 0x00DA}, /* LATIN CAPITAL LETTER U WITH ACUTE */
+ {"Ubrcy", 0x040E}, /* CYRILLIC CAPITAL LETTER SHORT U */
+ {"Ubreve", 0x016C}, /* LATIN CAPITAL LETTER U WITH BREVE */
+ {"Ucirc", 0x00DB}, /* LATIN CAPITAL LETTER U WITH CIRCUMFLEX */
+ {"Ucy", 0x0423}, /* CYRILLIC CAPITAL LETTER U */
+ {"Udblac", 0x0170}, /* LATIN CAPITAL LETTER U WITH DOUBLE ACUTE */
+ {"Udigr", 0x03AB}, /* GREEK CAPITAL LETTER UPSILON WITH DIALYTIKA */
+ {"Ugr", 0x03A5}, /* GREEK CAPITAL LETTER UPSILON */
+ {"Ugrave", 0x00D9}, /* LATIN CAPITAL LETTER U WITH GRAVE */
+ {"Umacr", 0x016A}, /* LATIN CAPITAL LETTER U WITH MACRON */
+ {"Uogon", 0x0172}, /* LATIN CAPITAL LETTER U WITH OGONEK */
+ {"Upsi", 0x03A5}, /* GREEK CAPITAL LETTER UPSILON */
+ {"Upsilon", 0x03A5}, /* GREEK CAPITAL LETTER UPSILON */
+ {"Uring", 0x016E}, /* LATIN CAPITAL LETTER U WITH RING ABOVE */
+ {"Utilde", 0x0168}, /* LATIN CAPITAL LETTER U WITH TILDE */
+ {"Uuml", 0x00DC}, /* LATIN CAPITAL LETTER U WITH DIAERESIS */
+ {"Vcy", 0x0412}, /* CYRILLIC CAPITAL LETTER VE */
+ {"Vdash", 0x22A9}, /* FORCES */
+ {"Verbar", 0x2016}, /* DOUBLE VERTICAL LINE */
+ {"Vvdash", 0x22AA}, /* TRIPLE VERTICAL BAR RIGHT TURNSTILE */
+ {"Wcirc", 0x0174}, /* LATIN CAPITAL LETTER W WITH CIRCUMFLEX */
+ {"Xgr", 0x039E}, /* GREEK CAPITAL LETTER XI */
+ {"Xi", 0x039E}, /* GREEK CAPITAL LETTER XI */
+ {"YAcy", 0x042F}, /* CYRILLIC CAPITAL LETTER YA */
+ {"YIcy", 0x0407}, /* CYRILLIC CAPITAL LETTER YI */
+ {"YUcy", 0x042E}, /* CYRILLIC CAPITAL LETTER YU */
+ {"Yacute", 0x00DD}, /* LATIN CAPITAL LETTER Y WITH ACUTE */
+ {"Ycirc", 0x0176}, /* LATIN CAPITAL LETTER Y WITH CIRCUMFLEX */
+ {"Ycy", 0x042B}, /* CYRILLIC CAPITAL LETTER YERU */
+ {"Yuml", 0x0178}, /* LATIN CAPITAL LETTER Y WITH DIAERESIS */
+ {"ZHcy", 0x0416}, /* CYRILLIC CAPITAL LETTER ZHE */
+ {"Zacute", 0x0179}, /* LATIN CAPITAL LETTER Z WITH ACUTE */
+ {"Zcaron", 0x017D}, /* LATIN CAPITAL LETTER Z WITH CARON */
+ {"Zcy", 0x0417}, /* CYRILLIC CAPITAL LETTER ZE */
+ {"Zdot", 0x017B}, /* LATIN CAPITAL LETTER Z WITH DOT ABOVE */
+ {"Zeta", 0x0396}, /* GREEK CAPITAL LETTER ZETA */
+ {"Zgr", 0x0396}, /* GREEK CAPITAL LETTER ZETA */
+ {"aacgr", 0x03AC}, /* GREEK SMALL LETTER ALPHA WITH TONOS */
+ {"aacute", 0x00E1}, /* LATIN SMALL LETTER A WITH ACUTE */
+ {"abreve", 0x0103}, /* LATIN SMALL LETTER A WITH BREVE */
+ {"acirc", 0x00E2}, /* LATIN SMALL LETTER A WITH CIRCUMFLEX */
+ {"acute", 0x00B4}, /* ACUTE ACCENT */
+ {"acy", 0x0430}, /* CYRILLIC SMALL LETTER A */
+ {"aelig", 0x00E6}, /* LATIN SMALL LETTER AE */
+ {"agr", 0x03B1}, /* GREEK SMALL LETTER ALPHA */
+ {"agrave", 0x00E0}, /* LATIN SMALL LETTER A WITH GRAVE */
+ {"alefsym", 0x2135}, /* ALEF SYMBOL */
+ {"aleph", 0x2135}, /* ALEF SYMBOL */
+ {"alpha", 0x03B1}, /* GREEK SMALL LETTER ALPHA */
+ {"amacr", 0x0101}, /* LATIN SMALL LETTER A WITH MACRON */
+ {"amalg", 0x2210}, /* N-ARY COPRODUCT */
+ {"amp", 0x0026}, /* AMPERSAND */
+ {"and", 0x2227}, /* LOGICAL AND */
+ {"ang", 0x2220}, /* ANGLE */
+ {"ang90", 0x221F}, /* RIGHT ANGLE */
+ {"angmsd", 0x2221}, /* MEASURED ANGLE */
+ {"angsph", 0x2222}, /* SPHERICAL ANGLE */
+ {"angst", 0x212B}, /* ANGSTROM SIGN */
+ {"aogon", 0x0105}, /* LATIN SMALL LETTER A WITH OGONEK */
+ {"ap", 0x2248}, /* ALMOST EQUAL TO */
+ {"ape", 0x224A}, /* ALMOST EQUAL OR EQUAL TO */
+ {"apos", 0x02BC}, /* MODIFIER LETTER APOSTROPHE */
+ {"aring", 0x00E5}, /* LATIN SMALL LETTER A WITH RING ABOVE */
+ {"ast", 0x002A}, /* ASTERISK */
+ {"asymp", 0x2248}, /* ALMOST EQUAL TO */
+ {"atilde", 0x00E3}, /* LATIN SMALL LETTER A WITH TILDE */
+ {"auml", 0x00E4}, /* LATIN SMALL LETTER A WITH DIAERESIS */
+ {"b.Delta", 0x0394}, /* GREEK CAPITAL LETTER DELTA */
+ {"b.Gamma", 0x0393}, /* GREEK CAPITAL LETTER GAMMA */
+ {"b.Lambda", 0x039B}, /* GREEK CAPITAL LETTER LAMDA */
+ {"b.Omega", 0x03A9}, /* GREEK CAPITAL LETTER OMEGA */
+ {"b.Phi", 0x03A6}, /* GREEK CAPITAL LETTER PHI */
+ {"b.Pi", 0x03A0}, /* GREEK CAPITAL LETTER PI */
+ {"b.Psi", 0x03A8}, /* GREEK CAPITAL LETTER PSI */
+ {"b.Sigma", 0x03A3}, /* GREEK CAPITAL LETTER SIGMA */
+ {"b.Theta", 0x0398}, /* GREEK CAPITAL LETTER THETA */
+ {"b.Upsi", 0x03A5}, /* GREEK CAPITAL LETTER UPSILON */
+ {"b.Xi", 0x039E}, /* GREEK CAPITAL LETTER XI */
+ {"b.alpha", 0x03B1}, /* GREEK SMALL LETTER ALPHA */
+ {"b.beta", 0x03B2}, /* GREEK SMALL LETTER BETA */
+ {"b.chi", 0x03C7}, /* GREEK SMALL LETTER CHI */
+ {"b.delta", 0x03B4}, /* GREEK SMALL LETTER DELTA */
+ {"b.epsi", 0x03B5}, /* GREEK SMALL LETTER EPSILON */
+ {"b.epsis", 0x03B5}, /* GREEK SMALL LETTER EPSILON */
+ {"b.epsiv", 0x03B5}, /* GREEK SMALL LETTER EPSILON */
+ {"b.eta", 0x03B7}, /* GREEK SMALL LETTER ETA */
+ {"b.gamma", 0x03B3}, /* GREEK SMALL LETTER GAMMA */
+ {"b.gammad", 0x03DC}, /* GREEK LETTER DIGAMMA */
+ {"b.iota", 0x03B9}, /* GREEK SMALL LETTER IOTA */
+ {"b.kappa", 0x03BA}, /* GREEK SMALL LETTER KAPPA */
+ {"b.kappav", 0x03F0}, /* GREEK KAPPA SYMBOL */
+ {"b.lambda", 0x03BB}, /* GREEK SMALL LETTER LAMDA */
+ {"b.mu", 0x03BC}, /* GREEK SMALL LETTER MU */
+ {"b.nu", 0x03BD}, /* GREEK SMALL LETTER NU */
+ {"b.omega", 0x03CE}, /* GREEK SMALL LETTER OMEGA WITH TONOS */
+ {"b.phis", 0x03C6}, /* GREEK SMALL LETTER PHI */
+ {"b.phiv", 0x03D5}, /* GREEK PHI SYMBOL */
+ {"b.pi", 0x03C0}, /* GREEK SMALL LETTER PI */
+ {"b.piv", 0x03D6}, /* GREEK PI SYMBOL */
+ {"b.psi", 0x03C8}, /* GREEK SMALL LETTER PSI */
+ {"b.rho", 0x03C1}, /* GREEK SMALL LETTER RHO */
+ {"b.rhov", 0x03F1}, /* GREEK RHO SYMBOL */
+ {"b.sigma", 0x03C3}, /* GREEK SMALL LETTER SIGMA */
+ {"b.sigmav", 0x03C2}, /* GREEK SMALL LETTER FINAL SIGMA */
+ {"b.tau", 0x03C4}, /* GREEK SMALL LETTER TAU */
+ {"b.thetas", 0x03B8}, /* GREEK SMALL LETTER THETA */
+ {"b.thetav", 0x03D1}, /* GREEK THETA SYMBOL */
+ {"b.upsi", 0x03C5}, /* GREEK SMALL LETTER UPSILON */
+ {"b.xi", 0x03BE}, /* GREEK SMALL LETTER XI */
+ {"b.zeta", 0x03B6}, /* GREEK SMALL LETTER ZETA */
+ {"barwed", 0x22BC}, /* NAND */
+ {"bcong", 0x224C}, /* ALL EQUAL TO */
+ {"bcy", 0x0431}, /* CYRILLIC SMALL LETTER BE */
+ {"bdquo", 0x201E}, /* DOUBLE LOW-9 QUOTATION MARK */
+ {"becaus", 0x2235}, /* BECAUSE */
+ {"bepsi", 0x220D}, /* SMALL CONTAINS AS MEMBER */
+ {"bernou", 0x212C}, /* SCRIPT CAPITAL B */
+ {"beta", 0x03B2}, /* GREEK SMALL LETTER BETA */
+ {"beth", 0x2136}, /* BET SYMBOL */
+ {"bgr", 0x03B2}, /* GREEK SMALL LETTER BETA */
+ {"blank", 0x2423}, /* OPEN BOX */
+ {"blk12", 0x2592}, /* MEDIUM SHADE */
+ {"blk14", 0x2591}, /* LIGHT SHADE */
+ {"blk34", 0x2593}, /* DARK SHADE */
+ {"block", 0x2588}, /* FULL BLOCK */
+ {"bottom", 0x22A5}, /* UP TACK */
+ {"bowtie", 0x22C8}, /* BOWTIE */
+ {"boxDL", 0x2557}, /* BOX DRAWINGS DOUBLE DOWN AND LEFT */
+ {"boxDR", 0x2554}, /* BOX DRAWINGS DOUBLE DOWN AND RIGHT */
+ {"boxDl", 0x2556}, /* BOX DRAWINGS DOWN DOUBLE AND LEFT SINGLE */
+ {"boxDr", 0x2553}, /* BOX DRAWINGS DOWN DOUBLE AND RIGHT SINGLE */
+ {"boxH", 0x2550}, /* BOX DRAWINGS DOUBLE HORIZONTAL */
+ {"boxHD", 0x2566}, /* BOX DRAWINGS DOUBLE DOWN AND HORIZONTAL */
+ {"boxHU", 0x2569}, /* BOX DRAWINGS DOUBLE UP AND HORIZONTAL */
+ {"boxHd", 0x2564}, /* BOX DRAWINGS DOWN SINGLE AND HORIZONTAL DOUBLE*/
+ {"boxHu", 0x2567}, /* BOX DRAWINGS UP SINGLE AND HORIZONTAL DOUBLE */
+ {"boxUL", 0x255D}, /* BOX DRAWINGS DOUBLE UP AND LEFT */
+ {"boxUR", 0x255A}, /* BOX DRAWINGS DOUBLE UP AND RIGHT */
+ {"boxUl", 0x255C}, /* BOX DRAWINGS UP DOUBLE AND LEFT SINGLE */
+ {"boxUr", 0x2559}, /* BOX DRAWINGS UP DOUBLE AND RIGHT SINGLE */
+ {"boxV", 0x2551}, /* BOX DRAWINGS DOUBLE VERTICAL */
+ {"boxVH", 0x256C}, /* BOX DRAWINGS DOUBLE VERTICAL AND HORIZONTAL */
+ {"boxVL", 0x2563}, /* BOX DRAWINGS DOUBLE VERTICAL AND LEFT */
+ {"boxVR", 0x2560}, /* BOX DRAWINGS DOUBLE VERTICAL AND RIGHT */
+ {"boxVh", 0x256B}, /* BOX DRAWINGS VERTICAL DOUBLE AND HORIZONTAL SI*/
+ {"boxVl", 0x2562}, /* BOX DRAWINGS VERTICAL DOUBLE AND LEFT SINGLE */
+ {"boxVr", 0x255F}, /* BOX DRAWINGS VERTICAL DOUBLE AND RIGHT SINGLE */
+ {"boxdL", 0x2555}, /* BOX DRAWINGS DOWN SINGLE AND LEFT DOUBLE */
+ {"boxdR", 0x2552}, /* BOX DRAWINGS DOWN SINGLE AND RIGHT DOUBLE */
+ {"boxdl", 0x2510}, /* BOX DRAWINGS LIGHT DOWN AND LEFT */
+ {"boxdr", 0x250C}, /* BOX DRAWINGS LIGHT DOWN AND RIGHT */
+ {"boxh", 0x2500}, /* BOX DRAWINGS LIGHT HORIZONTAL */
+ {"boxhD", 0x2565}, /* BOX DRAWINGS DOWN DOUBLE AND HORIZONTAL SINGLE*/
+ {"boxhU", 0x2568}, /* BOX DRAWINGS UP DOUBLE AND HORIZONTAL SINGLE */
+ {"boxhd", 0x252C}, /* BOX DRAWINGS LIGHT DOWN AND HORIZONTAL */
+ {"boxhu", 0x2534}, /* BOX DRAWINGS LIGHT UP AND HORIZONTAL */
+ {"boxuL", 0x255B}, /* BOX DRAWINGS UP SINGLE AND LEFT DOUBLE */
+ {"boxuR", 0x2558}, /* BOX DRAWINGS UP SINGLE AND RIGHT DOUBLE */
+ {"boxul", 0x2518}, /* BOX DRAWINGS LIGHT UP AND LEFT */
+ {"boxur", 0x2514}, /* BOX DRAWINGS LIGHT UP AND RIGHT */
+ {"boxv", 0x2502}, /* BOX DRAWINGS LIGHT VERTICAL */
+ {"boxvH", 0x256A}, /* BOX DRAWINGS VERTICAL SINGLE AND HORIZONTAL DO*/
+ {"boxvL", 0x2561}, /* BOX DRAWINGS VERTICAL SINGLE AND LEFT DOUBLE */
+ {"boxvR", 0x255E}, /* BOX DRAWINGS VERTICAL SINGLE AND RIGHT DOUBLE */
+ {"boxvh", 0x253C}, /* BOX DRAWINGS LIGHT VERTICAL AND HORIZONTAL */
+ {"boxvl", 0x2524}, /* BOX DRAWINGS LIGHT VERTICAL AND LEFT */
+ {"boxvr", 0x251C}, /* BOX DRAWINGS LIGHT VERTICAL AND RIGHT */
+ {"bprime", 0x2035}, /* REVERSED PRIME */
+ {"breve", 0x02D8}, /* BREVE */
+ {"brkbar", 0x00A6}, /* obsolete synonym for "brvbar" 0x00A6 */
+ {"brvbar", 0x00A6}, /* BROKEN BAR */
+ {"bsim", 0x223D}, /* REVERSED TILDE */
+ {"bsime", 0x22CD}, /* REVERSED TILDE EQUALS */
+ {"bsol", 0x005C}, /* REVERSE SOLIDUS */
+ {"bull", 0x2022}, /* BULLET */
+ {"bump", 0x224E}, /* GEOMETRICALLY EQUIVALENT TO */
+ {"bumpe", 0x224F}, /* DIFFERENCE BETWEEN */
+ {"cacute", 0x0107}, /* LATIN SMALL LETTER C WITH ACUTE */
+ {"cap", 0x2229}, /* INTERSECTION */
+ {"caret", 0x2041}, /* CARET INSERTION POINT */
+ {"caron", 0x02C7}, /* CARON */
+ {"ccaron", 0x010D}, /* LATIN SMALL LETTER C WITH CARON */
+ {"ccedil", 0x00E7}, /* LATIN SMALL LETTER C WITH CEDILLA */
+ {"ccirc", 0x0109}, /* LATIN SMALL LETTER C WITH CIRCUMFLEX */
+ {"cdot", 0x010B}, /* LATIN SMALL LETTER C WITH DOT ABOVE */
+ {"cedil", 0x00B8}, /* CEDILLA */
+ {"cent", 0x00A2}, /* CENT SIGN */
+ {"chcy", 0x0447}, /* CYRILLIC SMALL LETTER CHE */
+ {"check", 0x2713}, /* CHECK MARK */
+ {"chi", 0x03C7}, /* GREEK SMALL LETTER CHI */
+ {"cir", 0x25CB}, /* WHITE CIRCLE */
+ {"circ", 0x02C6}, /* MODIFIER LETTER CIRCUMFLEX ACCENT */
+ {"cire", 0x2257}, /* RING EQUAL TO */
+ {"clubs", 0x2663}, /* BLACK CLUB SUIT */
+ {"colon", 0x003A}, /* COLON */
+ {"colone", 0x2254}, /* COLON EQUALS */
+ {"comma", 0x002C}, /* COMMA */
+ {"commat", 0x0040}, /* COMMERCIAL AT */
+ {"comp", 0x2201}, /* COMPLEMENT */
+ {"compfn", 0x2218}, /* RING OPERATOR */
+ {"cong", 0x2245}, /* APPROXIMATELY EQUAL TO */
+ {"conint", 0x222E}, /* CONTOUR INTEGRAL */
+ {"coprod", 0x2210}, /* N-ARY COPRODUCT */
+ {"copy", 0x00A9}, /* COPYRIGHT SIGN */
+ {"copysr", 0x2117}, /* SOUND RECORDING COPYRIGHT */
+ {"crarr", 0x21B5}, /* DOWNWARDS ARROW WITH CORNER LEFTWARDS */
+ {"cross", 0x2717}, /* BALLOT X */
+ {"cuepr", 0x22DE}, /* EQUAL TO OR PRECEDES */
+ {"cuesc", 0x22DF}, /* EQUAL TO OR SUCCEEDS */
+ {"cularr", 0x21B6}, /* ANTICLOCKWISE TOP SEMICIRCLE ARROW */
+ {"cup", 0x222A}, /* UNION */
+ {"cupre", 0x227C}, /* PRECEDES OR EQUAL TO */
+ {"curarr", 0x21B7}, /* CLOCKWISE TOP SEMICIRCLE ARROW */
+ {"curren", 0x00A4}, /* CURRENCY SIGN */
+ {"cuvee", 0x22CE}, /* CURLY LOGICAL OR */
+ {"cuwed", 0x22CF}, /* CURLY LOGICAL AND */
+ {"dArr", 0x21D3}, /* DOWNWARDS DOUBLE ARROW */
+ {"dagger", 0x2020}, /* DAGGER */
+ {"daleth", 0x2138}, /* DALET SYMBOL */
+ {"darr", 0x2193}, /* DOWNWARDS ARROW */
+ {"darr2", 0x21CA}, /* DOWNWARDS PAIRED ARROWS */
+ {"dash", 0x2010}, /* HYPHEN */
+ {"dashv", 0x22A3}, /* LEFT TACK */
+ {"dblac", 0x02DD}, /* DOUBLE ACUTE ACCENT */
+ {"dcaron", 0x010F}, /* LATIN SMALL LETTER D WITH CARON */
+ {"dcy", 0x0434}, /* CYRILLIC SMALL LETTER DE */
+ {"deg", 0x00B0}, /* DEGREE SIGN */
+ {"delta", 0x03B4}, /* GREEK SMALL LETTER DELTA */
+ {"dgr", 0x03B4}, /* GREEK SMALL LETTER DELTA */
+ {"dharl", 0x21C3}, /* DOWNWARDS HARPOON WITH BARB LEFTWARDS */
+ {"dharr", 0x21C2}, /* DOWNWARDS HARPOON WITH BARB RIGHTWARDS */
+ {"diam", 0x22C4}, /* DIAMOND OPERATOR */
+ {"diams", 0x2666}, /* BLACK DIAMOND SUIT */
+ {"die", 0x00A8}, /* DIAERESIS */
+ {"divide", 0x00F7}, /* DIVISION SIGN */
+ {"divonx", 0x22C7}, /* DIVISION TIMES */
+ {"djcy", 0x0452}, /* CYRILLIC SMALL LETTER DJE */
+ {"dlarr", 0x2199}, /* SOUTH WEST ARROW */
+ {"dlcorn", 0x231E}, /* BOTTOM LEFT CORNER */
+ {"dlcrop", 0x230D}, /* BOTTOM LEFT CROP */
+ {"dollar", 0x0024}, /* DOLLAR SIGN */
+ {"dot", 0x02D9}, /* DOT ABOVE */
+ {"drarr", 0x2198}, /* SOUTH EAST ARROW */
+ {"drcorn", 0x231F}, /* BOTTOM RIGHT CORNER */
+ {"drcrop", 0x230C}, /* BOTTOM RIGHT CROP */
+ {"dscy", 0x0455}, /* CYRILLIC SMALL LETTER DZE */
+ {"dstrok", 0x0111}, /* LATIN SMALL LETTER D WITH STROKE */
+ {"dtri", 0x25BF}, /* WHITE DOWN-POINTING SMALL TRIANGLE */
+ {"dtrif", 0x25BE}, /* BLACK DOWN-POINTING SMALL TRIANGLE */
+ {"dzcy", 0x045F}, /* CYRILLIC SMALL LETTER DZHE */
+ {"eDot", 0x2251}, /* GEOMETRICALLY EQUAL TO */
+ {"eacgr", 0x03AD}, /* GREEK SMALL LETTER EPSILON WITH TONOS */
+ {"eacute", 0x00E9}, /* LATIN SMALL LETTER E WITH ACUTE */
+ {"ecaron", 0x011B}, /* LATIN SMALL LETTER E WITH CARON */
+ {"ecir", 0x2256}, /* RING IN EQUAL TO */
+ {"ecirc", 0x00EA}, /* LATIN SMALL LETTER E WITH CIRCUMFLEX */
+ {"ecolon", 0x2255}, /* EQUALS COLON */
+ {"ecy", 0x044D}, /* CYRILLIC SMALL LETTER E */
+ {"edot", 0x0117}, /* LATIN SMALL LETTER E WITH DOT ABOVE */
+ {"eeacgr", 0x03AE}, /* GREEK SMALL LETTER ETA WITH TONOS */
+ {"eegr", 0x03B7}, /* GREEK SMALL LETTER ETA */
+ {"efDot", 0x2252}, /* APPROXIMATELY EQUAL TO OR THE IMAGE OF */
+ {"egr", 0x03B5}, /* GREEK SMALL LETTER EPSILON */
+ {"egrave", 0x00E8}, /* LATIN SMALL LETTER E WITH GRAVE */
+ {"egs", 0x22DD}, /* EQUAL TO OR GREATER-THAN */
+ {"ell", 0x2113}, /* SCRIPT SMALL L */
+ {"els", 0x22DC}, /* EQUAL TO OR LESS-THAN */
+ {"emacr", 0x0113}, /* LATIN SMALL LETTER E WITH MACRON */
+ {"emdash", 0x2014}, /* obsolete synonym for "mdash" 0x2014 */
+ {"empty", 0x2205}, /* EMPTY SET */
+ {"emsp", 0x2003}, /* EM SPACE */
+ {"emsp13", 0x2004}, /* THREE-PER-EM SPACE */
+ {"emsp14", 0x2005}, /* FOUR-PER-EM SPACE */
+ {"endash", 0x2013}, /* obsolete synonym for "ndash" 0x2013 */
+ {"eng", 0x014B}, /* LATIN SMALL LETTER ENG */
+ {"ensp", 0x2002}, /* EN SPACE */
+ {"eogon", 0x0119}, /* LATIN SMALL LETTER E WITH OGONEK */
+ {"epsi", 0x03B5}, /* GREEK SMALL LETTER EPSILON */
+ {"epsilon", 0x03B5}, /* GREEK SMALL LETTER EPSILON */
+ {"epsis", 0x220A}, /* SMALL ELEMENT OF */
+ {"equals", 0x003D}, /* EQUALS SIGN */
+ {"equiv", 0x2261}, /* IDENTICAL TO */
+ {"erDot", 0x2253}, /* IMAGE OF OR APPROXIMATELY EQUAL TO */
+ {"esdot", 0x2250}, /* APPROACHES THE LIMIT */
+ {"eta", 0x03B7}, /* GREEK SMALL LETTER ETA */
+ {"eth", 0x00F0}, /* LATIN SMALL LETTER ETH */
+ {"euml", 0x00EB}, /* LATIN SMALL LETTER E WITH DIAERESIS */
+ {"euro", 0x20AC}, /* EURO SIGN */
+ {"excl", 0x0021}, /* EXCLAMATION MARK */
+ {"exist", 0x2203}, /* THERE EXISTS */
+ {"fcy", 0x0444}, /* CYRILLIC SMALL LETTER EF */
+ {"female", 0x2640}, /* FEMALE SIGN */
+ {"ffilig", 0xFB03}, /* LATIN SMALL LIGATURE FFI */
+ {"fflig", 0xFB00}, /* LATIN SMALL LIGATURE FF */
+ {"ffllig", 0xFB04}, /* LATIN SMALL LIGATURE FFL */
+ {"filig", 0xFB01}, /* LATIN SMALL LIGATURE FI */
+ {"flat", 0x266D}, /* MUSIC FLAT SIGN */
+ {"fllig", 0xFB02}, /* LATIN SMALL LIGATURE FL */
+ {"fnof", 0x0192}, /* LATIN SMALL LETTER F WITH HOOK */
+ {"forall", 0x2200}, /* FOR ALL */
+ {"fork", 0x22D4}, /* PITCHFORK */
+ {"frac12", 0x00BD}, /* VULGAR FRACTION ONE HALF */
+ {"frac13", 0x2153}, /* VULGAR FRACTION ONE THIRD */
+ {"frac14", 0x00BC}, /* VULGAR FRACTION ONE QUARTER */
+ {"frac15", 0x2155}, /* VULGAR FRACTION ONE FIFTH */
+ {"frac16", 0x2159}, /* VULGAR FRACTION ONE SIXTH */
+ {"frac18", 0x215B}, /* VULGAR FRACTION ONE EIGHTH */
+ {"frac23", 0x2154}, /* VULGAR FRACTION TWO THIRDS */
+ {"frac25", 0x2156}, /* VULGAR FRACTION TWO FIFTHS */
+ {"frac34", 0x00BE}, /* VULGAR FRACTION THREE QUARTERS */
+ {"frac35", 0x2157}, /* VULGAR FRACTION THREE FIFTHS */
+ {"frac38", 0x215C}, /* VULGAR FRACTION THREE EIGHTHS */
+ {"frac45", 0x2158}, /* VULGAR FRACTION FOUR FIFTHS */
+ {"frac56", 0x215A}, /* VULGAR FRACTION FIVE SIXTHS */
+ {"frac58", 0x215D}, /* VULGAR FRACTION FIVE EIGHTHS */
+ {"frac78", 0x215E}, /* VULGAR FRACTION SEVEN EIGHTHS */
+ {"frasl", 0x2044}, /* FRACTION SLASH */
+ {"frown", 0x2322}, /* FROWN */
+ {"gE", 0x2267}, /* GREATER-THAN OVER EQUAL TO */
+ {"gacute", 0x01F5}, /* LATIN SMALL LETTER G WITH ACUTE */
+ {"gamma", 0x03B3}, /* GREEK SMALL LETTER GAMMA */
+ {"gammad", 0x03DC}, /* GREEK LETTER DIGAMMA */
+ {"gbreve", 0x011F}, /* LATIN SMALL LETTER G WITH BREVE */
+ {"gcedil", 0x0123}, /* LATIN SMALL LETTER G WITH CEDILLA */
+ {"gcirc", 0x011D}, /* LATIN SMALL LETTER G WITH CIRCUMFLEX */
+ {"gcy", 0x0433}, /* CYRILLIC SMALL LETTER GHE */
+ {"gdot", 0x0121}, /* LATIN SMALL LETTER G WITH DOT ABOVE */
+ {"ge", 0x2265}, /* GREATER-THAN OR EQUAL TO */
+ {"gel", 0x22DB}, /* GREATER-THAN EQUAL TO OR LESS-THAN */
+ {"ges", 0x2265}, /* GREATER-THAN OR EQUAL TO */
+ {"ggr", 0x03B3}, /* GREEK SMALL LETTER GAMMA */
+ {"gimel", 0x2137}, /* GIMEL SYMBOL */
+ {"gjcy", 0x0453}, /* CYRILLIC SMALL LETTER GJE */
+ {"gl", 0x2277}, /* GREATER-THAN OR LESS-THAN */
+ {"gnE", 0x2269}, /* GREATER-THAN BUT NOT EQUAL TO */
+ {"gne", 0x2269}, /* GREATER-THAN BUT NOT EQUAL TO */
+ {"gnsim", 0x22E7}, /* GREATER-THAN BUT NOT EQUIVALENT TO */
+ {"grave", 0x0060}, /* GRAVE ACCENT */
+ {"gsdot", 0x22D7}, /* GREATER-THAN WITH DOT */
+ {"gsim", 0x2273}, /* GREATER-THAN OR EQUIVALENT TO */
+ {"gt", 0x003E}, /* GREATER-THAN SIGN */
+ {"gvnE", 0x2269}, /* GREATER-THAN BUT NOT EQUAL TO */
+ {"hArr", 0x21D4}, /* LEFT RIGHT DOUBLE ARROW */
+ {"hairsp", 0x200A}, /* HAIR SPACE */
+ {"half", 0x00BD}, /* VULGAR FRACTION ONE HALF */
+ {"hamilt", 0x210B}, /* SCRIPT CAPITAL H */
+ {"hardcy", 0x044A}, /* CYRILLIC SMALL LETTER HARD SIGN */
+ {"harr", 0x2194}, /* LEFT RIGHT ARROW */
+ {"harrw", 0x21AD}, /* LEFT RIGHT WAVE ARROW */
+ {"hcirc", 0x0125}, /* LATIN SMALL LETTER H WITH CIRCUMFLEX */
+ {"hearts", 0x2665}, /* BLACK HEART SUIT */
+ {"hellip", 0x2026}, /* HORIZONTAL ELLIPSIS */
+ {"hibar", 0x00AF}, /* obsolete synonym for "macr" 0x00AF */
+ {"horbar", 0x2015}, /* HORIZONTAL BAR */
+ {"hstrok", 0x0127}, /* LATIN SMALL LETTER H WITH STROKE */
+ {"hybull", 0x2043}, /* HYPHEN BULLET */
+ {"hyphen", 0x002D}, /* HYPHEN-MINUS */
+ {"iacgr", 0x03AF}, /* GREEK SMALL LETTER IOTA WITH TONOS */
+ {"iacute", 0x00ED}, /* LATIN SMALL LETTER I WITH ACUTE */
+ {"icirc", 0x00EE}, /* LATIN SMALL LETTER I WITH CIRCUMFLEX */
+ {"icy", 0x0438}, /* CYRILLIC SMALL LETTER I */
+ {"idiagr", 0x0390}, /* GREEK SMALL LETTER IOTA WITH DIALYTIKA AND TON*/
+ {"idigr", 0x03CA}, /* GREEK SMALL LETTER IOTA WITH DIALYTIKA */
+ {"iecy", 0x0435}, /* CYRILLIC SMALL LETTER IE */
+ {"iexcl", 0x00A1}, /* INVERTED EXCLAMATION MARK */
+ {"iff", 0x21D4}, /* LEFT RIGHT DOUBLE ARROW */
+ {"igr", 0x03B9}, /* GREEK SMALL LETTER IOTA */
+ {"igrave", 0x00EC}, /* LATIN SMALL LETTER I WITH GRAVE */
+ {"ijlig", 0x0133}, /* LATIN SMALL LIGATURE IJ */
+ {"imacr", 0x012B}, /* LATIN SMALL LETTER I WITH MACRON */
+ {"image", 0x2111}, /* BLACK-LETTER CAPITAL I */
+ {"incare", 0x2105}, /* CARE OF */
+ {"infin", 0x221E}, /* INFINITY */
+ {"inodot", 0x0131}, /* LATIN SMALL LETTER DOTLESS I */
+ {"int", 0x222B}, /* INTEGRAL */
+ {"intcal", 0x22BA}, /* INTERCALATE */
+ {"iocy", 0x0451}, /* CYRILLIC SMALL LETTER IO */
+ {"iogon", 0x012F}, /* LATIN SMALL LETTER I WITH OGONEK */
+ {"iota", 0x03B9}, /* GREEK SMALL LETTER IOTA */
+ {"iquest", 0x00BF}, /* INVERTED QUESTION MARK */
+ {"isin", 0x2208}, /* ELEMENT OF */
+ {"itilde", 0x0129}, /* LATIN SMALL LETTER I WITH TILDE */
+ {"iukcy", 0x0456}, /* CYRILLIC SMALL LETTER BYELORUSSIAN-UKRAINIAN I*/
+ {"iuml", 0x00EF}, /* LATIN SMALL LETTER I WITH DIAERESIS */
+ {"jcirc", 0x0135}, /* LATIN SMALL LETTER J WITH CIRCUMFLEX */
+ {"jcy", 0x0439}, /* CYRILLIC SMALL LETTER SHORT I */
+ {"jsercy", 0x0458}, /* CYRILLIC SMALL LETTER JE */
+ {"jukcy", 0x0454}, /* CYRILLIC SMALL LETTER UKRAINIAN IE */
+ {"kappa", 0x03BA}, /* GREEK SMALL LETTER KAPPA */
+ {"kappav", 0x03F0}, /* GREEK KAPPA SYMBOL */
+ {"kcedil", 0x0137}, /* LATIN SMALL LETTER K WITH CEDILLA */
+ {"kcy", 0x043A}, /* CYRILLIC SMALL LETTER KA */
+ {"kgr", 0x03BA}, /* GREEK SMALL LETTER KAPPA */
+ {"kgreen", 0x0138}, /* LATIN SMALL LETTER KRA */
+ {"khcy", 0x0445}, /* CYRILLIC SMALL LETTER HA */
+ {"khgr", 0x03C7}, /* GREEK SMALL LETTER CHI */
+ {"kjcy", 0x045C}, /* CYRILLIC SMALL LETTER KJE */
+ {"lAarr", 0x21DA}, /* LEFTWARDS TRIPLE ARROW */
+ {"lArr", 0x21D0}, /* LEFTWARDS DOUBLE ARROW */
+ {"lE", 0x2266}, /* LESS-THAN OVER EQUAL TO */
+ {"lacute", 0x013A}, /* LATIN SMALL LETTER L WITH ACUTE */
+ {"lagran", 0x2112}, /* SCRIPT CAPITAL L */
+ {"lambda", 0x03BB}, /* GREEK SMALL LETTER LAMDA */
+ {"lang", 0x2329}, /* LEFT-POINTING ANGLE BRACKET */
+ {"laquo", 0x00AB}, /* LEFT-POINTING DOUBLE ANGLE QUOTATION MARK */
+ {"larr", 0x2190}, /* LEFTWARDS ARROW */
+ {"larr2", 0x21C7}, /* LEFTWARDS PAIRED ARROWS */
+ {"larrhk", 0x21A9}, /* LEFTWARDS ARROW WITH HOOK */
+ {"larrlp", 0x21AB}, /* LEFTWARDS ARROW WITH LOOP */
+ {"larrtl", 0x21A2}, /* LEFTWARDS ARROW WITH TAIL */
+ {"lcaron", 0x013E}, /* LATIN SMALL LETTER L WITH CARON */
+ {"lcedil", 0x013C}, /* LATIN SMALL LETTER L WITH CEDILLA */
+ {"lceil", 0x2308}, /* LEFT CEILING */
+ {"lcub", 0x007B}, /* LEFT CURLY BRACKET */
+ {"lcy", 0x043B}, /* CYRILLIC SMALL LETTER EL */
+ {"ldot", 0x22D6}, /* LESS-THAN WITH DOT */
+ {"ldquo", 0x201C}, /* LEFT DOUBLE QUOTATION MARK */
+ {"ldquor", 0x201E}, /* DOUBLE LOW-9 QUOTATION MARK */
+ {"le", 0x2264}, /* LESS-THAN OR EQUAL TO */
+ {"leg", 0x22DA}, /* LESS-THAN EQUAL TO OR GREATER-THAN */
+ {"les", 0x2264}, /* LESS-THAN OR EQUAL TO */
+ {"lfloor", 0x230A}, /* LEFT FLOOR */
+ {"lg", 0x2276}, /* LESS-THAN OR GREATER-THAN */
+ {"lgr", 0x03BB}, /* GREEK SMALL LETTER LAMDA */
+ {"lhard", 0x21BD}, /* LEFTWARDS HARPOON WITH BARB DOWNWARDS */
+ {"lharu", 0x21BC}, /* LEFTWARDS HARPOON WITH BARB UPWARDS */
+ {"lhblk", 0x2584}, /* LOWER HALF BLOCK */
+ {"ljcy", 0x0459}, /* CYRILLIC SMALL LETTER LJE */
+ {"lmidot", 0x0140}, /* LATIN SMALL LETTER L WITH MIDDLE DOT */
+ {"lnE", 0x2268}, /* LESS-THAN BUT NOT EQUAL TO */
+ {"lne", 0x2268}, /* LESS-THAN BUT NOT EQUAL TO */
+ {"lnsim", 0x22E6}, /* LESS-THAN BUT NOT EQUIVALENT TO */
+ {"lowast", 0x2217}, /* ASTERISK OPERATOR */
+ {"lowbar", 0x005F}, /* LOW LINE */
+ {"loz", 0x25CA}, /* LOZENGE */
+/*{"loz", 0x2727}, WHITE FOUR POINTED STAR */
+ /* Warning: Duplicated &loz; entry. HTML 4,0 defines it as U+25CA. */
+ {"lozf", 0x2726}, /* BLACK FOUR POINTED STAR */
+ {"lpar", 0x0028}, /* LEFT PARENTHESIS */
+ {"lrarr2", 0x21C6}, /* LEFTWARDS ARROW OVER RIGHTWARDS ARROW */
+ {"lrhar2", 0x21CB}, /* LEFTWARDS HARPOON OVER RIGHTWARDS HARPOON */
+ {"lrm", 0x200E}, /* LEFT-TO-RIGHT MARK */
+ {"lsaquo", 0x2039}, /* SINGLE LEFT-POINTING ANGLE QUOTATION MARK */
+ {"lsh", 0x21B0}, /* UPWARDS ARROW WITH TIP LEFTWARDS */
+ {"lsim", 0x2272}, /* LESS-THAN OR EQUIVALENT TO */
+ {"lsqb", 0x005B}, /* LEFT SQUARE BRACKET */
+ {"lsquo", 0x2018}, /* LEFT SINGLE QUOTATION MARK */
+ {"lsquor", 0x201A}, /* SINGLE LOW-9 QUOTATION MARK */
+ {"lstrok", 0x0142}, /* LATIN SMALL LETTER L WITH STROKE */
+ {"lt", 0x003C}, /* LESS-THAN SIGN */
+ {"lthree", 0x22CB}, /* LEFT SEMIDIRECT PRODUCT */
+ {"ltimes", 0x22C9}, /* LEFT NORMAL FACTOR SEMIDIRECT PRODUCT */
+ {"ltri", 0x25C3}, /* WHITE LEFT-POINTING SMALL TRIANGLE */
+ {"ltrie", 0x22B4}, /* NORMAL SUBGROUP OF OR EQUAL TO */
+ {"ltrif", 0x25C2}, /* BLACK LEFT-POINTING SMALL TRIANGLE */
+ {"lvnE", 0x2268}, /* LESS-THAN BUT NOT EQUAL TO */
+ {"macr", 0x00AF}, /* MACRON */
+ {"male", 0x2642}, /* MALE SIGN */
+ {"malt", 0x2720}, /* MALTESE CROSS */
+ {"map", 0x21A6}, /* RIGHTWARDS ARROW FROM BAR */
+ {"marker", 0x25AE}, /* BLACK VERTICAL RECTANGLE */
+ {"mcy", 0x043C}, /* CYRILLIC SMALL LETTER EM */
+ {"mdash", 0x2014}, /* EM DASH */
+ {"mgr", 0x03BC}, /* GREEK SMALL LETTER MU */
+ {"micro", 0x00B5}, /* MICRO SIGN */
+ {"mid", 0x2223}, /* DIVIDES */
+ {"middot", 0x00B7}, /* MIDDLE DOT */
+ {"minus", 0x2212}, /* MINUS SIGN */
+ {"minusb", 0x229F}, /* SQUARED MINUS */
+ {"mldr", 0x2026}, /* HORIZONTAL ELLIPSIS */
+ {"mnplus", 0x2213}, /* MINUS-OR-PLUS SIGN */
+ {"models", 0x22A7}, /* MODELS */
+ {"mu", 0x03BC}, /* GREEK SMALL LETTER MU */
+ {"mumap", 0x22B8}, /* MULTIMAP */
+ {"nVDash", 0x22AF}, /* NEGATED DOUBLE VERTICAL BAR DOUBLE RIGHT TURNS*/
+ {"nVdash", 0x22AE}, /* DOES NOT FORCE */
+ {"nabla", 0x2207}, /* NABLA */
+ {"nacute", 0x0144}, /* LATIN SMALL LETTER N WITH ACUTE */
+ {"nap", 0x2249}, /* NOT ALMOST EQUAL TO */
+ {"napos", 0x0149}, /* LATIN SMALL LETTER N PRECEDED BY APOSTROPHE */
+ {"natur", 0x266E}, /* MUSIC NATURAL SIGN */
+ {"nbsp", 0x00A0}, /* NO-BREAK SPACE */
+ {"ncaron", 0x0148}, /* LATIN SMALL LETTER N WITH CARON */
+ {"ncedil", 0x0146}, /* LATIN SMALL LETTER N WITH CEDILLA */
+ {"ncong", 0x2247}, /* NEITHER APPROXIMATELY NOR ACTUALLY EQUAL TO */
+ {"ncy", 0x043D}, /* CYRILLIC SMALL LETTER EN */
+ {"ndash", 0x2013}, /* EN DASH */
+ {"ne", 0x2260}, /* NOT EQUAL TO */
+ {"nearr", 0x2197}, /* NORTH EAST ARROW */
+ {"nequiv", 0x2262}, /* NOT IDENTICAL TO */
+ {"nexist", 0x2204}, /* THERE DOES NOT EXIST */
+ {"nge", 0x2271}, /* NEITHER GREATER-THAN NOR EQUAL TO */
+ {"nges", 0x2271}, /* NEITHER GREATER-THAN NOR EQUAL TO */
+ {"ngr", 0x03BD}, /* GREEK SMALL LETTER NU */
+ {"ngt", 0x226F}, /* NOT GREATER-THAN */
+ {"nhArr", 0x21CE}, /* LEFT RIGHT DOUBLE ARROW WITH STROKE */
+ {"nharr", 0x21AE}, /* LEFT RIGHT ARROW WITH STROKE */
+ {"ni", 0x220B}, /* CONTAINS AS MEMBER */
+ {"njcy", 0x045A}, /* CYRILLIC SMALL LETTER NJE */
+ {"nlArr", 0x21CD}, /* LEFTWARDS DOUBLE ARROW WITH STROKE */
+ {"nlarr", 0x219A}, /* LEFTWARDS ARROW WITH STROKE */
+ {"nldr", 0x2025}, /* TWO DOT LEADER */
+ {"nle", 0x2270}, /* NEITHER LESS-THAN NOR EQUAL TO */
+ {"nles", 0x2270}, /* NEITHER LESS-THAN NOR EQUAL TO */
+ {"nlt", 0x226E}, /* NOT LESS-THAN */
+ {"nltri", 0x22EA}, /* NOT NORMAL SUBGROUP OF */
+ {"nltrie", 0x22EC}, /* NOT NORMAL SUBGROUP OF OR EQUAL TO */
+ {"nmid", 0x2224}, /* DOES NOT DIVIDE */
+ {"not", 0x00AC}, /* NOT SIGN */
+ {"notin", 0x2209}, /* NOT AN ELEMENT OF */
+ {"npar", 0x2226}, /* NOT PARALLEL TO */
+ {"npr", 0x2280}, /* DOES NOT PRECEDE */
+ {"npre", 0x22E0}, /* DOES NOT PRECEDE OR EQUAL */
+ {"nrArr", 0x21CF}, /* RIGHTWARDS DOUBLE ARROW WITH STROKE */
+ {"nrarr", 0x219B}, /* RIGHTWARDS ARROW WITH STROKE */
+ {"nrtri", 0x22EB}, /* DOES NOT CONTAIN AS NORMAL SUBGROUP */
+ {"nrtrie", 0x22ED}, /* DOES NOT CONTAIN AS NORMAL SUBGROUP OR EQUAL */
+ {"nsc", 0x2281}, /* DOES NOT SUCCEED */
+ {"nsce", 0x22E1}, /* DOES NOT SUCCEED OR EQUAL */
+ {"nsim", 0x2241}, /* NOT TILDE */
+ {"nsime", 0x2244}, /* NOT ASYMPTOTICALLY EQUAL TO */
+ {"nspar", 0x2226}, /* NOT PARALLEL TO */
+ {"nsub", 0x2284}, /* NOT A SUBSET OF */
+ {"nsubE", 0x2288}, /* NEITHER A SUBSET OF NOR EQUAL TO */
+ {"nsube", 0x2288}, /* NEITHER A SUBSET OF NOR EQUAL TO */
+ {"nsup", 0x2285}, /* NOT A SUPERSET OF */
+ {"nsupE", 0x2289}, /* NEITHER A SUPERSET OF NOR EQUAL TO */
+ {"nsupe", 0x2289}, /* NEITHER A SUPERSET OF NOR EQUAL TO */
+ {"ntilde", 0x00F1}, /* LATIN SMALL LETTER N WITH TILDE */
+ {"nu", 0x03BD}, /* GREEK SMALL LETTER NU */
+ {"num", 0x0023}, /* NUMBER SIGN */
+ {"numero", 0x2116}, /* NUMERO SIGN */
+ {"numsp", 0x2007}, /* FIGURE SPACE */
+ {"nvDash", 0x22AD}, /* NOT TRUE */
+ {"nvdash", 0x22AC}, /* DOES NOT PROVE */
+ {"nwarr", 0x2196}, /* NORTH WEST ARROW */
+ {"oS", 0x24C8}, /* CIRCLED LATIN CAPITAL LETTER S */
+ {"oacgr", 0x03CC}, /* GREEK SMALL LETTER OMICRON WITH TONOS */
+ {"oacute", 0x00F3}, /* LATIN SMALL LETTER O WITH ACUTE */
+ {"oast", 0x229B}, /* CIRCLED ASTERISK OPERATOR */
+ {"ocir", 0x229A}, /* CIRCLED RING OPERATOR */
+ {"ocirc", 0x00F4}, /* LATIN SMALL LETTER O WITH CIRCUMFLEX */
+ {"ocy", 0x043E}, /* CYRILLIC SMALL LETTER O */
+ {"odash", 0x229D}, /* CIRCLED DASH */
+ {"odblac", 0x0151}, /* LATIN SMALL LETTER O WITH DOUBLE ACUTE */
+ {"odot", 0x2299}, /* CIRCLED DOT OPERATOR */
+ {"oelig", 0x0153}, /* LATIN SMALL LIGATURE OE */
+ {"ogon", 0x02DB}, /* OGONEK */
+ {"ogr", 0x03BF}, /* GREEK SMALL LETTER OMICRON */
+ {"ograve", 0x00F2}, /* LATIN SMALL LETTER O WITH GRAVE */
+ {"ohacgr", 0x03CE}, /* GREEK SMALL LETTER OMEGA WITH TONOS */
+ {"ohgr", 0x03C9}, /* GREEK SMALL LETTER OMEGA */
+ {"ohm", 0x2126}, /* OHM SIGN */
+ {"olarr", 0x21BA}, /* ANTICLOCKWISE OPEN CIRCLE ARROW */
+ {"oline", 0x203E}, /* OVERLINE */
+ {"omacr", 0x014D}, /* LATIN SMALL LETTER O WITH MACRON */
+ {"omega", 0x03C9}, /* GREEK SMALL LETTER OMEGA */
+ {"omicron", 0x03BF}, /* GREEK SMALL LETTER OMICRON */
+ {"ominus", 0x2296}, /* CIRCLED MINUS */
+ {"oplus", 0x2295}, /* CIRCLED PLUS */
+ {"or", 0x2228}, /* LOGICAL OR */
+ {"orarr", 0x21BB}, /* CLOCKWISE OPEN CIRCLE ARROW */
+ {"order", 0x2134}, /* SCRIPT SMALL O */
+ {"ordf", 0x00AA}, /* FEMININE ORDINAL INDICATOR */
+ {"ordm", 0x00BA}, /* MASCULINE ORDINAL INDICATOR */
+ {"oslash", 0x00F8}, /* LATIN SMALL LETTER O WITH STROKE */
+ {"osol", 0x2298}, /* CIRCLED DIVISION SLASH */
+ {"otilde", 0x00F5}, /* LATIN SMALL LETTER O WITH TILDE */
+ {"otimes", 0x2297}, /* CIRCLED TIMES */
+ {"ouml", 0x00F6}, /* LATIN SMALL LETTER O WITH DIAERESIS */
+ {"par", 0x2225}, /* PARALLEL TO */
+ {"para", 0x00B6}, /* PILCROW SIGN */
+ {"part", 0x2202}, /* PARTIAL DIFFERENTIAL */
+ {"pcy", 0x043F}, /* CYRILLIC SMALL LETTER PE */
+ {"percnt", 0x0025}, /* PERCENT SIGN */
+ {"period", 0x002E}, /* FULL STOP */
+ {"permil", 0x2030}, /* PER MILLE SIGN */
+ {"perp", 0x22A5}, /* UP TACK */
+ {"pgr", 0x03C0}, /* GREEK SMALL LETTER PI */
+ {"phgr", 0x03C6}, /* GREEK SMALL LETTER PHI */
+ {"phi", 0x03C6}, /* GREEK SMALL LETTER PHI */
+ {"phis", 0x03C6}, /* GREEK SMALL LETTER PHI */
+ {"phiv", 0x03D5}, /* GREEK PHI SYMBOL */
+ {"phmmat", 0x2133}, /* SCRIPT CAPITAL M */
+ {"phone", 0x260E}, /* BLACK TELEPHONE */
+ {"pi", 0x03C0}, /* GREEK SMALL LETTER PI */
+ {"piv", 0x03D6}, /* GREEK PI SYMBOL */
+ {"planck", 0x210F}, /* PLANCK CONSTANT OVER TWO PI */
+ {"plus", 0x002B}, /* PLUS SIGN */
+ {"plusb", 0x229E}, /* SQUARED PLUS */
+ {"plusdo", 0x2214}, /* DOT PLUS */
+ {"plusmn", 0x00B1}, /* PLUS-MINUS SIGN */
+ {"pound", 0x00A3}, /* POUND SIGN */
+ {"pr", 0x227A}, /* PRECEDES */
+ {"pre", 0x227C}, /* PRECEDES OR EQUAL TO */
+ {"prime", 0x2032}, /* PRIME */
+ {"prnsim", 0x22E8}, /* PRECEDES BUT NOT EQUIVALENT TO */
+ {"prod", 0x220F}, /* N-ARY PRODUCT */
+ {"prop", 0x221D}, /* PROPORTIONAL TO */
+ {"prsim", 0x227E}, /* PRECEDES OR EQUIVALENT TO */
+ {"psgr", 0x03C8}, /* GREEK SMALL LETTER PSI */
+ {"psi", 0x03C8}, /* GREEK SMALL LETTER PSI */
+ {"puncsp", 0x2008}, /* PUNCTUATION SPACE */
+ {"quest", 0x003F}, /* QUESTION MARK */
+ {"quot", 0x0022}, /* QUOTATION MARK */
+ {"rAarr", 0x21DB}, /* RIGHTWARDS TRIPLE ARROW */
+ {"rArr", 0x21D2}, /* RIGHTWARDS DOUBLE ARROW */
+ {"racute", 0x0155}, /* LATIN SMALL LETTER R WITH ACUTE */
+ {"radic", 0x221A}, /* SQUARE ROOT */
+ {"rang", 0x232A}, /* RIGHT-POINTING ANGLE BRACKET */
+ {"raquo", 0x00BB}, /* RIGHT-POINTING DOUBLE ANGLE QUOTATION MARK */
+ {"rarr", 0x2192}, /* RIGHTWARDS ARROW */
+ {"rarr2", 0x21C9}, /* RIGHTWARDS PAIRED ARROWS */
+ {"rarrhk", 0x21AA}, /* RIGHTWARDS ARROW WITH HOOK */
+ {"rarrlp", 0x21AC}, /* RIGHTWARDS ARROW WITH LOOP */
+ {"rarrtl", 0x21A3}, /* RIGHTWARDS ARROW WITH TAIL */
+ {"rarrw", 0x219D}, /* RIGHTWARDS WAVE ARROW */
+ {"rcaron", 0x0159}, /* LATIN SMALL LETTER R WITH CARON */
+ {"rcedil", 0x0157}, /* LATIN SMALL LETTER R WITH CEDILLA */
+ {"rceil", 0x2309}, /* RIGHT CEILING */
+ {"rcub", 0x007D}, /* RIGHT CURLY BRACKET */
+ {"rcy", 0x0440}, /* CYRILLIC SMALL LETTER ER */
+ {"rdquo", 0x201D}, /* RIGHT DOUBLE QUOTATION MARK */
+ {"rdquor", 0x201C}, /* LEFT DOUBLE QUOTATION MARK */
+ {"real", 0x211C}, /* BLACK-LETTER CAPITAL R */
+ {"rect", 0x25AD}, /* WHITE RECTANGLE */
+ {"reg", 0x00AE}, /* REGISTERED SIGN */
+ {"rfloor", 0x230B}, /* RIGHT FLOOR */
+ {"rgr", 0x03C1}, /* GREEK SMALL LETTER RHO */
+ {"rhard", 0x21C1}, /* RIGHTWARDS HARPOON WITH BARB DOWNWARDS */
+ {"rharu", 0x21C0}, /* RIGHTWARDS HARPOON WITH BARB UPWARDS */
+ {"rho", 0x03C1}, /* GREEK SMALL LETTER RHO */
+ {"rhov", 0x03F1}, /* GREEK RHO SYMBOL */
+ {"ring", 0x02DA}, /* RING ABOVE */
+ {"rlarr2", 0x21C4}, /* RIGHTWARDS ARROW OVER LEFTWARDS ARROW */
+ {"rlhar2", 0x21CC}, /* RIGHTWARDS HARPOON OVER LEFTWARDS HARPOON */
+ {"rlm", 0x200F}, /* RIGHT-TO-LEFT MARK */
+ {"rpar", 0x0029}, /* RIGHT PARENTHESIS */
+ {"rsaquo", 0x203A}, /* SINGLE RIGHT-POINTING ANGLE QUOTATION MARK */
+ {"rsh", 0x21B1}, /* UPWARDS ARROW WITH TIP RIGHTWARDS */
+ {"rsqb", 0x005D}, /* RIGHT SQUARE BRACKET */
+ {"rsquo", 0x2019}, /* RIGHT SINGLE QUOTATION MARK */
+ {"rsquor", 0x2018}, /* LEFT SINGLE QUOTATION MARK */
+ {"rthree", 0x22CC}, /* RIGHT SEMIDIRECT PRODUCT */
+ {"rtimes", 0x22CA}, /* RIGHT NORMAL FACTOR SEMIDIRECT PRODUCT */
+ {"rtri", 0x25B9}, /* WHITE RIGHT-POINTING SMALL TRIANGLE */
+ {"rtrie", 0x22B5}, /* CONTAINS AS NORMAL SUBGROUP OR EQUAL TO */
+ {"rtrif", 0x25B8}, /* BLACK RIGHT-POINTING SMALL TRIANGLE */
+ {"rx", 0x211E}, /* PRESCRIPTION TAKE */
+ {"sacute", 0x015B}, /* LATIN SMALL LETTER S WITH ACUTE */
+ {"samalg", 0x2210}, /* N-ARY COPRODUCT */
+ {"sbquo", 0x201A}, /* SINGLE LOW-9 QUOTATION MARK */
+ {"sbsol", 0x005C}, /* REVERSE SOLIDUS */
+ {"sc", 0x227B}, /* SUCCEEDS */
+ {"scaron", 0x0161}, /* LATIN SMALL LETTER S WITH CARON */
+ {"sccue", 0x227D}, /* SUCCEEDS OR EQUAL TO */
+ {"sce", 0x227D}, /* SUCCEEDS OR EQUAL TO */
+ {"scedil", 0x015F}, /* LATIN SMALL LETTER S WITH CEDILLA */
+ {"scirc", 0x015D}, /* LATIN SMALL LETTER S WITH CIRCUMFLEX */
+ {"scnsim", 0x22E9}, /* SUCCEEDS BUT NOT EQUIVALENT TO */
+ {"scsim", 0x227F}, /* SUCCEEDS OR EQUIVALENT TO */
+ {"scy", 0x0441}, /* CYRILLIC SMALL LETTER ES */
+ {"sdot", 0x22C5}, /* DOT OPERATOR */
+ {"sdotb", 0x22A1}, /* SQUARED DOT OPERATOR */
+ {"sect", 0x00A7}, /* SECTION SIGN */
+ {"semi", 0x003B}, /* SEMICOLON */
+ {"setmn", 0x2216}, /* SET MINUS */
+ {"sext", 0x2736}, /* SIX POINTED BLACK STAR */
+ {"sfgr", 0x03C2}, /* GREEK SMALL LETTER FINAL SIGMA */
+ {"sfrown", 0x2322}, /* FROWN */
+ {"sgr", 0x03C3}, /* GREEK SMALL LETTER SIGMA */
+ {"sharp", 0x266F}, /* MUSIC SHARP SIGN */
+ {"shchcy", 0x0449}, /* CYRILLIC SMALL LETTER SHCHA */
+ {"shcy", 0x0448}, /* CYRILLIC SMALL LETTER SHA */
+ {"shy", 0x00AD}, /* SOFT HYPHEN */
+ {"sigma", 0x03C3}, /* GREEK SMALL LETTER SIGMA */
+ {"sigmaf", 0x03C2}, /* GREEK SMALL LETTER FINAL SIGMA */
+ {"sigmav", 0x03C2}, /* GREEK SMALL LETTER FINAL SIGMA */
+ {"sim", 0x223C}, /* TILDE OPERATOR */
+ {"sime", 0x2243}, /* ASYMPTOTICALLY EQUAL TO */
+ {"smile", 0x2323}, /* SMILE */
+ {"softcy", 0x044C}, /* CYRILLIC SMALL LETTER SOFT SIGN */
+ {"sol", 0x002F}, /* SOLIDUS */
+ {"spades", 0x2660}, /* BLACK SPADE SUIT */
+ {"spar", 0x2225}, /* PARALLEL TO */
+ {"sqcap", 0x2293}, /* SQUARE CAP */
+ {"sqcup", 0x2294}, /* SQUARE CUP */
+ {"sqsub", 0x228F}, /* SQUARE IMAGE OF */
+ {"sqsube", 0x2291}, /* SQUARE IMAGE OF OR EQUAL TO */
+ {"sqsup", 0x2290}, /* SQUARE ORIGINAL OF */
+ {"sqsupe", 0x2292}, /* SQUARE ORIGINAL OF OR EQUAL TO */
+ {"squ", 0x25A1}, /* WHITE SQUARE */
+ {"square", 0x25A1}, /* WHITE SQUARE */
+ {"squf", 0x25AA}, /* BLACK SMALL SQUARE */
+ {"ssetmn", 0x2216}, /* SET MINUS */
+ {"ssmile", 0x2323}, /* SMILE */
+ {"sstarf", 0x22C6}, /* STAR OPERATOR */
+ {"star", 0x2606}, /* WHITE STAR */
+ {"starf", 0x2605}, /* BLACK STAR */
+ {"sub", 0x2282}, /* SUBSET OF */
+ {"subE", 0x2286}, /* SUBSET OF OR EQUAL TO */
+ {"sube", 0x2286}, /* SUBSET OF OR EQUAL TO */
+ {"subnE", 0x228A}, /* SUBSET OF WITH NOT EQUAL TO */
+ {"subne", 0x228A}, /* SUBSET OF WITH NOT EQUAL TO */
+ {"sum", 0x2211}, /* N-ARY SUMMATION */
+ {"sung", 0x266A}, /* EIGHTH NOTE */
+ {"sup", 0x2283}, /* SUPERSET OF */
+ {"sup1", 0x00B9}, /* SUPERSCRIPT ONE */
+ {"sup2", 0x00B2}, /* SUPERSCRIPT TWO */
+ {"sup3", 0x00B3}, /* SUPERSCRIPT THREE */
+ {"supE", 0x2287}, /* SUPERSET OF OR EQUAL TO */
+ {"supe", 0x2287}, /* SUPERSET OF OR EQUAL TO */
+ {"supnE", 0x228B}, /* SUPERSET OF WITH NOT EQUAL TO */
+ {"supne", 0x228B}, /* SUPERSET OF WITH NOT EQUAL TO */
+ {"szlig", 0x00DF}, /* LATIN SMALL LETTER SHARP S */
+ {"target", 0x2316}, /* POSITION INDICATOR */
+ {"tau", 0x03C4}, /* GREEK SMALL LETTER TAU */
+ {"tcaron", 0x0165}, /* LATIN SMALL LETTER T WITH CARON */
+ {"tcedil", 0x0163}, /* LATIN SMALL LETTER T WITH CEDILLA */
+ {"tcy", 0x0442}, /* CYRILLIC SMALL LETTER TE */
+ {"tdot", 0x20DB}, /* COMBINING THREE DOTS ABOVE */
+ {"telrec", 0x2315}, /* TELEPHONE RECORDER */
+ {"tgr", 0x03C4}, /* GREEK SMALL LETTER TAU */
+ {"there4", 0x2234}, /* THEREFORE */
+ {"theta", 0x03B8}, /* GREEK SMALL LETTER THETA */
+ {"thetas", 0x03B8}, /* GREEK SMALL LETTER THETA */
+ {"thetasym", 0x03D1}, /* GREEK THETA SYMBOL */
+ {"thetav", 0x03D1}, /* GREEK THETA SYMBOL */
+ {"thgr", 0x03B8}, /* GREEK SMALL LETTER THETA */
+ {"thinsp", 0x2009}, /* THIN SPACE */
+ {"thkap", 0x2248}, /* ALMOST EQUAL TO */
+ {"thksim", 0x223C}, /* TILDE OPERATOR */
+ {"thorn", 0x00FE}, /* LATIN SMALL LETTER THORN */
+ {"tilde", 0x02DC}, /* SMALL TILDE */
+ {"times", 0x00D7}, /* MULTIPLICATION SIGN */
+ {"timesb", 0x22A0}, /* SQUARED TIMES */
+ {"top", 0x22A4}, /* DOWN TACK */
+ {"tprime", 0x2034}, /* TRIPLE PRIME */
+ {"trade", 0x2122}, /* TRADE MARK SIGN */
+ {"trie", 0x225C}, /* DELTA EQUAL TO */
+ {"tscy", 0x0446}, /* CYRILLIC SMALL LETTER TSE */
+ {"tshcy", 0x045B}, /* CYRILLIC SMALL LETTER TSHE */
+ {"tstrok", 0x0167}, /* LATIN SMALL LETTER T WITH STROKE */
+ {"twixt", 0x226C}, /* BETWEEN */
+ {"uArr", 0x21D1}, /* UPWARDS DOUBLE ARROW */
+ {"uacgr", 0x03CD}, /* GREEK SMALL LETTER UPSILON WITH TONOS */
+ {"uacute", 0x00FA}, /* LATIN SMALL LETTER U WITH ACUTE */
+ {"uarr", 0x2191}, /* UPWARDS ARROW */
+ {"uarr2", 0x21C8}, /* UPWARDS PAIRED ARROWS */
+ {"ubrcy", 0x045E}, /* CYRILLIC SMALL LETTER SHORT U */
+ {"ubreve", 0x016D}, /* LATIN SMALL LETTER U WITH BREVE */
+ {"ucirc", 0x00FB}, /* LATIN SMALL LETTER U WITH CIRCUMFLEX */
+ {"ucy", 0x0443}, /* CYRILLIC SMALL LETTER U */
+ {"udblac", 0x0171}, /* LATIN SMALL LETTER U WITH DOUBLE ACUTE */
+ {"udiagr", 0x03B0}, /* GREEK SMALL LETTER UPSILON WITH DIALYTIKA AND */
+ {"udigr", 0x03CB}, /* GREEK SMALL LETTER UPSILON WITH DIALYTIKA */
+ {"ugr", 0x03C5}, /* GREEK SMALL LETTER UPSILON */
+ {"ugrave", 0x00F9}, /* LATIN SMALL LETTER U WITH GRAVE */
+ {"uharl", 0x21BF}, /* UPWARDS HARPOON WITH BARB LEFTWARDS */
+ {"uharr", 0x21BE}, /* UPWARDS HARPOON WITH BARB RIGHTWARDS */
+ {"uhblk", 0x2580}, /* UPPER HALF BLOCK */
+ {"ulcorn", 0x231C}, /* TOP LEFT CORNER */
+ {"ulcrop", 0x230F}, /* TOP LEFT CROP */
+ {"umacr", 0x016B}, /* LATIN SMALL LETTER U WITH MACRON */
+ {"uml", 0x00A8}, /* DIAERESIS */
+ {"uogon", 0x0173}, /* LATIN SMALL LETTER U WITH OGONEK */
+ {"uplus", 0x228E}, /* MULTISET UNION */
+ {"upsi", 0x03C5}, /* GREEK SMALL LETTER UPSILON */
+ {"upsih", 0x03D2}, /* GREEK UPSILON WITH HOOK SYMBOL */
+ {"upsilon", 0x03C5}, /* GREEK SMALL LETTER UPSILON */
+ {"urcorn", 0x231D}, /* TOP RIGHT CORNER */
+ {"urcrop", 0x230E}, /* TOP RIGHT CROP */
+ {"uring", 0x016F}, /* LATIN SMALL LETTER U WITH RING ABOVE */
+ {"utilde", 0x0169}, /* LATIN SMALL LETTER U WITH TILDE */
+ {"utri", 0x25B5}, /* WHITE UP-POINTING SMALL TRIANGLE */
+ {"utrif", 0x25B4}, /* BLACK UP-POINTING SMALL TRIANGLE */
+ {"uuml", 0x00FC}, /* LATIN SMALL LETTER U WITH DIAERESIS */
+ {"vArr", 0x21D5}, /* UP DOWN DOUBLE ARROW */
+ {"vDash", 0x22A8}, /* TRUE */
+ {"varr", 0x2195}, /* UP DOWN ARROW */
+ {"vcy", 0x0432}, /* CYRILLIC SMALL LETTER VE */
+ {"vdash", 0x22A2}, /* RIGHT TACK */
+ {"veebar", 0x22BB}, /* XOR */
+ {"vellip", 0x22EE}, /* VERTICAL ELLIPSIS */
+ {"verbar", 0x007C}, /* VERTICAL LINE */
+ {"vltri", 0x22B2}, /* NORMAL SUBGROUP OF */
+ {"vprime", 0x2032}, /* PRIME */
+ {"vprop", 0x221D}, /* PROPORTIONAL TO */
+ {"vrtri", 0x22B3}, /* CONTAINS AS NORMAL SUBGROUP */
+ {"vsubnE", 0x228A}, /* SUBSET OF WITH NOT EQUAL TO */
+ {"vsubne", 0x228A}, /* SUBSET OF WITH NOT EQUAL TO */
+ {"vsupnE", 0x228B}, /* SUPERSET OF WITH NOT EQUAL TO */
+ {"vsupne", 0x228B}, /* SUPERSET OF WITH NOT EQUAL TO */
+ {"wcirc", 0x0175}, /* LATIN SMALL LETTER W WITH CIRCUMFLEX */
+ {"wedgeq", 0x2259}, /* ESTIMATES */
+ {"weierp", 0x2118}, /* SCRIPT CAPITAL P */
+ {"wreath", 0x2240}, /* WREATH PRODUCT */
+ {"xcirc", 0x25CB}, /* WHITE CIRCLE */
+ {"xdtri", 0x25BD}, /* WHITE DOWN-POINTING TRIANGLE */
+ {"xgr", 0x03BE}, /* GREEK SMALL LETTER XI */
+ {"xhArr", 0x2194}, /* LEFT RIGHT ARROW */
+ {"xharr", 0x2194}, /* LEFT RIGHT ARROW */
+ {"xi", 0x03BE}, /* GREEK SMALL LETTER XI */
+ {"xlArr", 0x21D0}, /* LEFTWARDS DOUBLE ARROW */
+ {"xrArr", 0x21D2}, /* RIGHTWARDS DOUBLE ARROW */
+ {"xutri", 0x25B3}, /* WHITE UP-POINTING TRIANGLE */
+ {"yacute", 0x00FD}, /* LATIN SMALL LETTER Y WITH ACUTE */
+ {"yacy", 0x044F}, /* CYRILLIC SMALL LETTER YA */
+ {"ycirc", 0x0177}, /* LATIN SMALL LETTER Y WITH CIRCUMFLEX */
+ {"ycy", 0x044B}, /* CYRILLIC SMALL LETTER YERU */
+ {"yen", 0x00A5}, /* YEN SIGN */
+ {"yicy", 0x0457}, /* CYRILLIC SMALL LETTER YI */
+ {"yucy", 0x044E}, /* CYRILLIC SMALL LETTER YU */
+ {"yuml", 0x00FF}, /* LATIN SMALL LETTER Y WITH DIAERESIS */
+ {"zacute", 0x017A}, /* LATIN SMALL LETTER Z WITH ACUTE */
+ {"zcaron", 0x017E}, /* LATIN SMALL LETTER Z WITH CARON */
+ {"zcy", 0x0437}, /* CYRILLIC SMALL LETTER ZE */
+ {"zdot", 0x017C}, /* LATIN SMALL LETTER Z WITH DOT ABOVE */
+ {"zeta", 0x03B6}, /* GREEK SMALL LETTER ZETA */
+ {"zgr", 0x03B6}, /* GREEK SMALL LETTER ZETA */
+ {"zhcy", 0x0436}, /* CYRILLIC SMALL LETTER ZHE */
+ {"zwj", 0x200D}, /* ZERO WIDTH JOINER */
+ {"zwnj", 0x200C}, /* ZERO WIDTH NON-JOINER */
+/* {"epsiv", 0x????}, variant epsilon # ISOgrk3 */
+/* {"fjlig", 0x????}, fj ligature # ISOpub */
+/* {"gEl", 0x????}, greater-than, double equals, less-than # ISOamsr */
+/* {"gap", 0x????}, greater-than, approximately equal to # ISOamsr */
+/* {"gnap", 0x????}, greater-than, not approximately equal t# ISOamsn */
+/* {"jnodot", 0x????}, latin small letter dotless j # ISOamso */
+/* {"lEg", 0x????}, less-than, double equals, greater-than # ISOamsr */
+/* {"lap", 0x????}, less-than, approximately equal to # ISOamsr */
+/* {"lnap", 0x????}, less-than, not approximately equal to # ISOamsn */
+/* {"lpargt", 0x????}, left parenthesis, greater-than # ISOamsc */
+/* {"ngE", 0x????}, not greater-than, double equals # ISOamsn */
+/* {"nlE", 0x????}, not less-than, double equals # ISOamsn */
+/* {"nsmid", 0x????}, nshortmid # ISOamsn */
+/* {"prap", 0x????}, precedes, approximately equal to # ISOamsr */
+/* {"prnE", 0x????}, precedes, not double equal # ISOamsn */
+/* {"prnap", 0x????}, precedes, not approximately equal to # ISOamsn */
+/* {"rpargt", 0x????}, right parenthesis, greater-than # ISOamsc */
+/* {"scap", 0x????}, succeeds, approximately equal to # ISOamsr */
+/* {"scnE", 0x????}, succeeds, not double equals # ISOamsn */
+/* {"scnap", 0x????}, succeeds, not approximately equal to # ISOamsn */
+/* {"smid", 0x????}, shortmid # ISOamsr */
+};
+
+#endif /* not ENTITIES_HTML40_ONLY */
+/* *INDENT-ON* */
diff --git a/src/chrtrans/hp_uni.tbl b/src/chrtrans/hp_uni.tbl
new file mode 100644
index 0000000..5802551
--- /dev/null
+++ b/src/chrtrans/hp_uni.tbl
@@ -0,0 +1,212 @@
+# The MIME name of this charset.
+Mhp-roman8
+
+# Name as a Display Charset (used on Options screen)
+OHP Roman8
+
+# This is not the default font!
+D0
+
+#
+# Name: HP Roman8 to Unicode
+# Date: 1999-01-09
+# Authors: Christian "naddy" Weisgerber <naddy@mips.rhein-neckar.de>
+# Mapping by Roman Czyborra,
+# <URL:http://czyborra.com/charsets/codepages.html#HP-Roman8>
+#
+
+0x20-0x7E idem # ASCII
+
+#0x20 U+0020 # SPACE
+#0x21 U+0021 # EXCLAMATION MARK
+#0x22 U+0022 # QUOTATION MARK
+#0x23 U+0023 # NUMBER SIGN
+#0x24 U+0024 # DOLLAR SIGN
+#0x25 U+0025 # PERCENT SIGN
+#0x26 U+0026 # AMPERSAND
+#0x27 U+0027 # APOSTROPHE
+#0x28 U+0028 # LEFT PARENTHESIS
+#0x29 U+0029 # RIGHT PARENTHESIS
+#0x2A U+002A # ASTERISK
+#0x2B U+002B # PLUS SIGN
+#0x2C U+002C # COMMA
+#0x2D U+002D # HYPHEN-MINUS
+#0x2E U+002E # FULL STOP
+#0x2F U+002F # SOLIDUS
+#0x30 U+0030 # DIGIT ZERO
+#0x31 U+0031 # DIGIT ONE
+#0x32 U+0032 # DIGIT TWO
+#0x33 U+0033 # DIGIT THREE
+#0x34 U+0034 # DIGIT FOUR
+#0x35 U+0035 # DIGIT FIVE
+#0x36 U+0036 # DIGIT SIX
+#0x37 U+0037 # DIGIT SEVEN
+#0x38 U+0038 # DIGIT EIGHT
+#0x39 U+0039 # DIGIT NINE
+#0x3A U+003A # COLON
+#0x3B U+003B # SEMICOLON
+#0x3C U+003C # LESS-THAN SIGN
+#0x3D U+003D # EQUALS SIGN
+#0x3E U+003E # GREATER-THAN SIGN
+#0x3F U+003F # QUESTION MARK
+#0x40 U+0040 # COMMERCIAL AT
+#0x41 U+0041 # LATIN CAPITAL LETTER A
+#0x42 U+0042 # LATIN CAPITAL LETTER B
+#0x43 U+0043 # LATIN CAPITAL LETTER C
+#0x44 U+0044 # LATIN CAPITAL LETTER D
+#0x45 U+0045 # LATIN CAPITAL LETTER E
+#0x46 U+0046 # LATIN CAPITAL LETTER F
+#0x47 U+0047 # LATIN CAPITAL LETTER G
+#0x48 U+0048 # LATIN CAPITAL LETTER H
+#0x49 U+0049 # LATIN CAPITAL LETTER I
+#0x4A U+004A # LATIN CAPITAL LETTER J
+#0x4B U+004B # LATIN CAPITAL LETTER K
+#0x4C U+004C # LATIN CAPITAL LETTER L
+#0x4D U+004D # LATIN CAPITAL LETTER M
+#0x4E U+004E # LATIN CAPITAL LETTER N
+#0x4F U+004F # LATIN CAPITAL LETTER O
+#0x50 U+0050 # LATIN CAPITAL LETTER P
+#0x51 U+0051 # LATIN CAPITAL LETTER Q
+#0x52 U+0052 # LATIN CAPITAL LETTER R
+#0x53 U+0053 # LATIN CAPITAL LETTER S
+#0x54 U+0054 # LATIN CAPITAL LETTER T
+#0x55 U+0055 # LATIN CAPITAL LETTER U
+#0x56 U+0056 # LATIN CAPITAL LETTER V
+#0x57 U+0057 # LATIN CAPITAL LETTER W
+#0x58 U+0058 # LATIN CAPITAL LETTER X
+#0x59 U+0059 # LATIN CAPITAL LETTER Y
+#0x5A U+005A # LATIN CAPITAL LETTER Z
+#0x5B U+005B # LEFT SQUARE BRACKET
+#0x5C U+005C # REVERSE SOLIDUS
+#0x5D U+005D # RIGHT SQUARE BRACKET
+#0x5E U+005E # CIRCUMFLEX ACCENT
+#0x5F U+005F # LOW LINE
+#0x60 U+0060 # GRAVE ACCENT
+#0x61 U+0061 # LATIN SMALL LETTER A
+#0x62 U+0062 # LATIN SMALL LETTER B
+#0x63 U+0063 # LATIN SMALL LETTER C
+#0x64 U+0064 # LATIN SMALL LETTER D
+#0x65 U+0065 # LATIN SMALL LETTER E
+#0x66 U+0066 # LATIN SMALL LETTER F
+#0x67 U+0067 # LATIN SMALL LETTER G
+#0x68 U+0068 # LATIN SMALL LETTER H
+#0x69 U+0069 # LATIN SMALL LETTER I
+#0x6A U+006A # LATIN SMALL LETTER J
+#0x6B U+006B # LATIN SMALL LETTER K
+#0x6C U+006C # LATIN SMALL LETTER L
+#0x6D U+006D # LATIN SMALL LETTER M
+#0x6E U+006E # LATIN SMALL LETTER N
+#0x6F U+006F # LATIN SMALL LETTER O
+#0x70 U+0070 # LATIN SMALL LETTER P
+#0x71 U+0071 # LATIN SMALL LETTER Q
+#0x72 U+0072 # LATIN SMALL LETTER R
+#0x73 U+0073 # LATIN SMALL LETTER S
+#0x74 U+0074 # LATIN SMALL LETTER T
+#0x75 U+0075 # LATIN SMALL LETTER U
+#0x76 U+0076 # LATIN SMALL LETTER V
+#0x77 U+0077 # LATIN SMALL LETTER W
+#0x78 U+0078 # LATIN SMALL LETTER X
+#0x79 U+0079 # LATIN SMALL LETTER Y
+#0x7A U+007A # LATIN SMALL LETTER Z
+#0x7B U+007B # LEFT CURLY BRACKET
+#0x7C U+007C # VERTICAL LINE
+#0x7D U+007D # RIGHT CURLY BRACKET
+#0x7E U+007E # TILDE
+
+0xA0 U+00A0 # NO-BREAK SPACE
+0xA1 U+00C0 # LATIN CAPITAL LETTER A WITH GRAVE
+0xA2 U+00C2 # LATIN CAPITAL LETTER A WITH CIRCUMFLEX
+0xA3 U+00C8 # LATIN CAPITAL LETTER E WITH GRAVE
+0xA4 U+00CA # LATIN CAPITAL LETTER E WITH CIRCUMFLEX
+0xA5 U+00CB # LATIN CAPITAL LETTER E WITH DIAERESIS
+0xA6 U+00CE # LATIN CAPITAL LETTER I WITH CIRCUMFLEX
+0xA7 U+00CF # LATIN CAPITAL LETTER I WITH DIAERESIS
+0xA8 U+00B4 # ACUTE ACCENT
+0xA9 U+02CB # MODIFIER LETTER GRAVE ACCENT
+0xAA U+02C6 # MODIFIER LETTER CIRCUMFLEX ACCENT
+0xAB U+00A8 # DIAERESIS
+0xAC U+02DC # SMALL TILDE
+0xAD U+00D9 # LATIN CAPITAL LETTER U WITH GRAVE
+0xAE U+00DB # LATIN CAPITAL LETTER U WITH CIRCUMFLEX
+0xAF U+20A4 # LIRA SIGN
+0xB0 U+00AF # MACRON
+0xB1 U+00DD # LATIN CAPITAL LETTER Y WITH ACUTE
+0xB2 U+00FD # LATIN SMALL LETTER Y WITH ACUTE
+0xB3 U+00B0 # DEGREE SIGN
+0xB4 U+00C7 # LATIN CAPITAL LETTER C WITH CEDILLA
+0xB5 U+00E7 # LATIN SMALL LETTER C WITH CEDILLA
+0xB6 U+00D1 # LATIN CAPITAL LETTER N WITH TILDE
+0xB7 U+00F1 # LATIN SMALL LETTER N WITH TILDE
+0xB8 U+00A1 # INVERTED EXCLAMATION MARK
+0xB9 U+00BF # INVERTED QUESTION MARK
+0xBA U+00A4 # CURRENCY SIGN
+0xBB U+00A3 # POUND SIGN
+0xBC U+00A5 # YEN SIGN
+0xBD U+00A7 # SECTION SIGN
+0xBE U+0192 # LATIN SMALL LETTER F WITH HOOK
+0xBF U+00A2 # CENT SIGN
+0xC0 U+00E2 # LATIN SMALL LETTER A WITH CIRCUMFLEX
+0xC1 U+00EA # LATIN SMALL LETTER E WITH CIRCUMFLEX
+0xC2 U+00F4 # LATIN SMALL LETTER O WITH CIRCUMFLEX
+0xC3 U+00FB # LATIN SMALL LETTER U WITH CIRCUMFLEX
+0xC4 U+00E1 # LATIN SMALL LETTER A WITH ACUTE
+0xC5 U+00E9 # LATIN SMALL LETTER E WITH ACUTE
+0xC6 U+00F3 # LATIN SMALL LETTER O WITH ACUTE
+0xC7 U+00FA # LATIN SMALL LETTER U WITH ACUTE
+0xC8 U+00E0 # LATIN SMALL LETTER A WITH GRAVE
+0xC9 U+00E8 # LATIN SMALL LETTER E WITH GRAVE
+0xCA U+00F2 # LATIN SMALL LETTER O WITH GRAVE
+0xCB U+00F9 # LATIN SMALL LETTER U WITH GRAVE
+0xCC U+00E4 # LATIN SMALL LETTER A WITH DIAERESIS
+0xCD U+00EB # LATIN SMALL LETTER E WITH DIAERESIS
+0xCE U+00F6 # LATIN SMALL LETTER O WITH DIAERESIS
+0xCF U+00FC # LATIN SMALL LETTER U WITH DIAERESIS
+0xD0 U+00C5 # LATIN CAPITAL LETTER A WITH RING ABOVE
+0xD1 U+00EE # LATIN SMALL LETTER I WITH CIRCUMFLEX
+0xD2 U+00D8 # LATIN CAPITAL LETTER O WITH STROKE
+0xD3 U+00C6 # LATIN CAPITAL LETTER AE
+0xD4 U+00E5 # LATIN SMALL LETTER A WITH RING ABOVE
+0xD5 U+00ED # LATIN SMALL LETTER I WITH ACUTE
+0xD6 U+00F8 # LATIN SMALL LETTER O WITH STROKE
+0xD7 U+00E6 # LATIN SMALL LETTER AE
+0xD8 U+00C4 # LATIN CAPITAL LETTER A WITH DIAERESIS
+0xD9 U+00EC # LATIN SMALL LETTER I WITH GRAVE
+0xDA U+00D6 # LATIN CAPITAL LETTER O WITH DIAERESIS
+0xDB U+00DC # LATIN CAPITAL LETTER U WITH DIAERESIS
+0xDC U+00C9 # LATIN CAPITAL LETTER E WITH ACUTE
+0xDD U+00EF # LATIN SMALL LETTER I WITH DIAERESIS
+0xDE U+00DF # LATIN SMALL LETTER SHARP S
+0xDF U+00D4 # LATIN CAPITAL LETTER O WITH CIRCUMFLEX
+0xE0 U+00C1 # LATIN CAPITAL LETTER A WITH ACUTE
+0xE1 U+00C3 # LATIN CAPITAL LETTER A WITH TILDE
+0xE2 U+00E3 # LATIN SMALL LETTER A WITH TILDE
+0xE3 U+00D0 # LATIN CAPITAL LETTER ETH
+0xE4 U+00F0 # LATIN SMALL LETTER ETH
+0xE5 U+00CD # LATIN CAPITAL LETTER I WITH ACUTE
+0xE6 U+00CC # LATIN CAPITAL LETTER I WITH GRAVE
+0xE7 U+00D3 # LATIN CAPITAL LETTER O WITH ACUTE
+0xE8 U+00D2 # LATIN CAPITAL LETTER O WITH GRAVE
+0xE9 U+00D5 # LATIN CAPITAL LETTER O WITH TILDE
+0xEA U+00F5 # LATIN SMALL LETTER O WITH TILDE
+0xEB U+0160 # LATIN CAPITAL LETTER S WITH CARON
+0xEC U+0161 # LATIN SMALL LETTER S WITH CARON
+0xED U+00DA # LATIN CAPITAL LETTER U WITH ACUTE
+0xEE U+0178 # LATIN CAPITAL LETTER Y WITH DIAERESIS
+0xEF U+00FF # LATIN SMALL LETTER Y WITH DIAERESIS
+0xF0 U+00DE # LATIN CAPITAL LETTER THORN
+0xF1 U+00FE # LATIN SMALL LETTER THORN
+0xF2 U+00B7 # MIDDLE DOT
+0xF3 U+00B5 # MICRO SIGN
+0xF4 U+00B6 # PILCROW SIGN
+0xF5 U+00BE # VULGAR FRACTION THREE QUARTERS
+0xF6 U+2014 # EM DASH
+0xF7 U+00BC # VULGAR FRACTION ONE QUARTER
+0xF8 U+00BD # VULGAR FRACTION ONE HALF
+0xF9 U+00AA # FEMININE ORDINAL INDICATOR
+0xFA U+00BA # MASCULINE ORDINAL INDICATOR
+0xFB U+00AB # LEFT-POINTING DOUBLE ANGLE QUOTATION MARK
+0xFC U+25A0 # BLACK SQUARE
+0xFD U+00BB # RIGHT-POINTING DOUBLE ANGLE QUOTATION MARK
+0xFE U+00B1 # PLUS-MINUS SIGN
+
+## EOF ##
diff --git a/src/chrtrans/iso01_uni.tbl b/src/chrtrans/iso01_uni.tbl
new file mode 100644
index 0000000..95e705e
--- /dev/null
+++ b/src/chrtrans/iso01_uni.tbl
@@ -0,0 +1,334 @@
+# $LynxId: iso01_uni.tbl,v 1.11 2007/07/31 20:35:04 Tim.Larson Exp $
+# vile:tblmode:
+# This file has been modified for lynx (see README.tables)
+
+#Shall this become the "default" translation?
+#Meaning of that is currently not well defined. It is different
+#from the default input or default output charset...
+#but there has to be exactly one table marked as "default".
+D0
+#
+#The MIME name of this charset.
+Miso-8859-1
+
+#Name as a Display Charset (used on Options screen)
+OWestern (ISO-8859-1)
+
+#Codepage number
+C819
+
+#
+# Name: ISO/IEC 8859-1:1998 to Unicode
+# Unicode version: 3.0
+# Table version: 1.0
+# Table format: Format A
+# Date: 1999 July 27
+# Authors: Ken Whistler <kenw@sybase.com>
+#
+# Copyright (c) 1991-1999 Unicode, Inc. All Rights reserved.
+#
+# This file is provided as-is by Unicode, Inc. (The Unicode Consortium).
+# No claims are made as to fitness for any particular purpose. No
+# warranties of any kind are expressed or implied. The recipient
+# agrees to determine applicability of information provided. If this
+# file has been provided on optical media by Unicode, Inc., the sole
+# remedy for any claim will be exchange of defective media within 90
+# days of receipt.
+#
+# Unicode, Inc. hereby grants the right to freely use the information
+# supplied in this file in the creation of products supporting the
+# Unicode Standard, and to make copies of this file in any form for
+# internal or external distribution as long as this notice remains
+# attached.
+#
+# General notes:
+#
+# This table contains the data the Unicode Consortium has on how
+# ISO/IEC 8859-1:1998 characters map into Unicode.
+#
+# Format: Three tab-separated columns
+# Column #1 is the ISO/IEC 8859-1 code (in hex as 0xXX)
+# Column #2 is the Unicode (in hex as 0xXXXX)
+# Column #3 the Unicode name (follows a comment sign, '#')
+#
+# The entries are in ISO/IEC 8859-1 order.
+#
+# Version history
+# 1.0 version updates 0.1 version by adding mappings for all
+# control characters.
+#
+# Updated versions of this file may be found in:
+# <ftp://ftp.unicode.org/Public/MAPPINGS/>
+#
+# Any comments or problems, contact <errata@unicode.org>
+# Please note that <errata@unicode.org> is an archival address;
+# notices will be checked, but do not expect an immediate response.
+#
+# Lines with more than one Unicode (U+XXXX) value contain additional
+# replacement mappings added for lynx. - kw
+#
+0x20-0x7e idem
+0xa0-0xff idem # iso 8859-1 special: trivial mapping to Unicode
+#
+#0x20 U+0020 # SPACE
+#0x21 U+0021 # EXCLAMATION MARK
+#0x22 U+0022 # QUOTATION MARK
+#0x23 U+0023 # NUMBER SIGN
+#0x24 U+0024 # DOLLAR SIGN
+#0x25 U+0025 # PERCENT SIGN
+#0x26 U+0026 # AMPERSAND
+#0x27 U+0027 # APOSTROPHE
+#0x28 U+0028 # LEFT PARENTHESIS
+#0x29 U+0029 # RIGHT PARENTHESIS
+#0x2A U+002A # ASTERISK
+#0x2B U+002B # PLUS SIGN
+#0x2C U+002C # COMMA
+#0x2D U+002D # HYPHEN-MINUS
+#0x2E U+002E # FULL STOP
+#0x2F U+002F # SOLIDUS
+#0x30 U+0030 # DIGIT ZERO
+#0x31 U+0031 # DIGIT ONE
+#0x32 U+0032 # DIGIT TWO
+#0x33 U+0033 # DIGIT THREE
+#0x34 U+0034 # DIGIT FOUR
+#0x35 U+0035 # DIGIT FIVE
+#0x36 U+0036 # DIGIT SIX
+#0x37 U+0037 # DIGIT SEVEN
+#0x38 U+0038 # DIGIT EIGHT
+#0x39 U+0039 # DIGIT NINE
+#0x3A U+003A # COLON
+#0x3B U+003B # SEMICOLON
+#0x3C U+003C # LESS-THAN SIGN
+#0x3D U+003D # EQUALS SIGN
+#0x3E U+003E # GREATER-THAN SIGN
+#0x3F U+003F # QUESTION MARK
+#0x40 U+0040 # COMMERCIAL AT
+#0x41 U+0041 # LATIN CAPITAL LETTER A
+#0x42 U+0042 # LATIN CAPITAL LETTER B
+#0x43 U+0043 # LATIN CAPITAL LETTER C
+#0x44 U+0044 # LATIN CAPITAL LETTER D
+#0x45 U+0045 # LATIN CAPITAL LETTER E
+#0x46 U+0046 # LATIN CAPITAL LETTER F
+#0x47 U+0047 # LATIN CAPITAL LETTER G
+#0x48 U+0048 # LATIN CAPITAL LETTER H
+#0x49 U+0049 # LATIN CAPITAL LETTER I
+#0x4A U+004A # LATIN CAPITAL LETTER J
+#0x4B U+004B # LATIN CAPITAL LETTER K
+#0x4C U+004C # LATIN CAPITAL LETTER L
+#0x4D U+004D # LATIN CAPITAL LETTER M
+#0x4E U+004E # LATIN CAPITAL LETTER N
+#0x4F U+004F # LATIN CAPITAL LETTER O
+#0x50 U+0050 # LATIN CAPITAL LETTER P
+#0x51 U+0051 # LATIN CAPITAL LETTER Q
+#0x52 U+0052 # LATIN CAPITAL LETTER R
+#0x53 U+0053 # LATIN CAPITAL LETTER S
+#0x54 U+0054 # LATIN CAPITAL LETTER T
+#0x55 U+0055 # LATIN CAPITAL LETTER U
+#0x56 U+0056 # LATIN CAPITAL LETTER V
+#0x57 U+0057 # LATIN CAPITAL LETTER W
+#0x58 U+0058 # LATIN CAPITAL LETTER X
+#0x59 U+0059 # LATIN CAPITAL LETTER Y
+#0x5A U+005A # LATIN CAPITAL LETTER Z
+#0x5B U+005B # LEFT SQUARE BRACKET
+#0x5C U+005C # REVERSE SOLIDUS
+#0x5D U+005D # RIGHT SQUARE BRACKET
+#0x5E U+005E # CIRCUMFLEX ACCENT
+#0x5F U+005F # LOW LINE
+#0x60 U+0060 # GRAVE ACCENT
+#0x61 U+0061 # LATIN SMALL LETTER A
+#0x62 U+0062 # LATIN SMALL LETTER B
+#0x63 U+0063 # LATIN SMALL LETTER C
+#0x64 U+0064 # LATIN SMALL LETTER D
+#0x65 U+0065 # LATIN SMALL LETTER E
+#0x66 U+0066 # LATIN SMALL LETTER F
+#0x67 U+0067 # LATIN SMALL LETTER G
+#0x68 U+0068 # LATIN SMALL LETTER H
+#0x69 U+0069 # LATIN SMALL LETTER I
+#0x6A U+006A # LATIN SMALL LETTER J
+#0x6B U+006B # LATIN SMALL LETTER K
+#0x6C U+006C # LATIN SMALL LETTER L
+#0x6D U+006D # LATIN SMALL LETTER M
+#0x6E U+006E # LATIN SMALL LETTER N
+#0x6F U+006F # LATIN SMALL LETTER O
+#0x70 U+0070 # LATIN SMALL LETTER P
+#0x71 U+0071 # LATIN SMALL LETTER Q
+#0x72 U+0072 # LATIN SMALL LETTER R
+#0x73 U+0073 # LATIN SMALL LETTER S
+#0x74 U+0074 # LATIN SMALL LETTER T
+#0x75 U+0075 # LATIN SMALL LETTER U
+#0x76 U+0076 # LATIN SMALL LETTER V
+#0x77 U+0077 # LATIN SMALL LETTER W
+#0x78 U+0078 # LATIN SMALL LETTER X
+#0x79 U+0079 # LATIN SMALL LETTER Y
+#0x7A U+007A # LATIN SMALL LETTER Z
+#0x7B U+007B # LEFT CURLY BRACKET
+#0x7C U+007C # VERTICAL LINE
+#0x7D U+007D # RIGHT CURLY BRACKET
+#0x7E U+007E # TILDE
+0xA0 U+00A0 U+2007 # NO-BREAK SPACE
+#0xA1 U+00A1 # INVERTED EXCLAMATION MARK
+#0xA2 U+00A2 # CENT SIGN
+#0xA3 U+00A3 # POUND SIGN
+#0xA4 U+00A4 # CURRENCY SIGN
+#0xA5 U+00A5 # YEN SIGN
+#0xA6 U+00A6 # BROKEN BAR
+#0xA7 U+00A7 # SECTION SIGN
+0xA8 U+00A8 U+0308 # DIAERESIS
+#0xA9 U+00A9 # COPYRIGHT SIGN
+#0xAA U+00AA # FEMININE ORDINAL INDICATOR
+#0xAB U+00AB # LEFT-POINTING DOUBLE ANGLE QUOTATION MARK
+#0xAC U+00AC # NOT SIGN
+#0xAD U+00AD # SOFT HYPHEN
+#0xAE U+00AE # REGISTERED SIGN
+0xAF U+00AF U+0304 # MACRON
+0xB0 U+00B0 U+030a # DEGREE SIGN
+#0xB1 U+00B1 # PLUS-MINUS SIGN
+#0xB2 U+00B2 # SUPERSCRIPT TWO
+#0xB3 U+00B3 # SUPERSCRIPT THREE
+#0xB4 U+00B4 # ACUTE ACCENT
+0xB5 U+00B5 U+03bc # MICRO SIGN
+#0xB6 U+00B6 # PILCROW SIGN
+0xB7 U+00B7 U+0307 U+0387 U+2027 # MIDDLE DOT
+0xB8 U+00B8 U+0327 # CEDILLA
+#0xB9 U+00B9 # SUPERSCRIPT ONE
+#0xBA U+00BA # MASCULINE ORDINAL INDICATOR
+#0xBB U+00BB # RIGHT-POINTING DOUBLE ANGLE QUOTATION MARK
+#0xBC U+00BC # VULGAR FRACTION ONE QUARTER
+#0xBD U+00BD # VULGAR FRACTION ONE HALF
+#0xBE U+00BE # VULGAR FRACTION THREE QUARTERS
+#0xBF U+00BF # INVERTED QUESTION MARK
+#0xC0 U+00C0 # LATIN CAPITAL LETTER A WITH GRAVE
+#0xC1 U+00C1 # LATIN CAPITAL LETTER A WITH ACUTE
+#0xC2 U+00C2 # LATIN CAPITAL LETTER A WITH CIRCUMFLEX
+#0xC3 U+00C3 # LATIN CAPITAL LETTER A WITH TILDE
+#0xC4 U+00C4 # LATIN CAPITAL LETTER A WITH DIAERESIS
+#0xC5 U+00C5 # LATIN CAPITAL LETTER A WITH RING ABOVE
+#0xC6 U+00C6 # LATIN CAPITAL LETTER AE
+#0xC7 U+00C7 # LATIN CAPITAL LETTER C WITH CEDILLA
+#0xC8 U+00C8 # LATIN CAPITAL LETTER E WITH GRAVE
+#0xC9 U+00C9 # LATIN CAPITAL LETTER E WITH ACUTE
+#0xCA U+00CA # LATIN CAPITAL LETTER E WITH CIRCUMFLEX
+#0xCB U+00CB # LATIN CAPITAL LETTER E WITH DIAERESIS
+#0xCC U+00CC # LATIN CAPITAL LETTER I WITH GRAVE
+#0xCD U+00CD # LATIN CAPITAL LETTER I WITH ACUTE
+#0xCE U+00CE # LATIN CAPITAL LETTER I WITH CIRCUMFLEX
+#0xCF U+00CF # LATIN CAPITAL LETTER I WITH DIAERESIS
+#0xD0 U+00D0 # LATIN CAPITAL LETTER ETH (Icelandic)
+#0xD1 U+00D1 # LATIN CAPITAL LETTER N WITH TILDE
+#0xD2 U+00D2 # LATIN CAPITAL LETTER O WITH GRAVE
+#0xD3 U+00D3 # LATIN CAPITAL LETTER O WITH ACUTE
+#0xD4 U+00D4 # LATIN CAPITAL LETTER O WITH CIRCUMFLEX
+#0xD5 U+00D5 # LATIN CAPITAL LETTER O WITH TILDE
+#0xD6 U+00D6 # LATIN CAPITAL LETTER O WITH DIAERESIS
+#0xD7 U+00D7 # MULTIPLICATION SIGN
+#0xD8 U+00D8 # LATIN CAPITAL LETTER O WITH STROKE
+#0xD9 U+00D9 # LATIN CAPITAL LETTER U WITH GRAVE
+#0xDA U+00DA # LATIN CAPITAL LETTER U WITH ACUTE
+#0xDB U+00DB # LATIN CAPITAL LETTER U WITH CIRCUMFLEX
+#0xDC U+00DC # LATIN CAPITAL LETTER U WITH DIAERESIS
+#0xDD U+00DD # LATIN CAPITAL LETTER Y WITH ACUTE
+#0xDE U+00DE # LATIN CAPITAL LETTER THORN (Icelandic)
+#0xDF U+00DF # LATIN SMALL LETTER SHARP S (German)
+#0xE0 U+00E0 # LATIN SMALL LETTER A WITH GRAVE
+#0xE1 U+00E1 # LATIN SMALL LETTER A WITH ACUTE
+#0xE2 U+00E2 # LATIN SMALL LETTER A WITH CIRCUMFLEX
+#0xE3 U+00E3 # LATIN SMALL LETTER A WITH TILDE
+#0xE4 U+00E4 # LATIN SMALL LETTER A WITH DIAERESIS
+#0xE5 U+00E5 # LATIN SMALL LETTER A WITH RING ABOVE
+#0xE6 U+00E6 # LATIN SMALL LETTER AE
+#0xE7 U+00E7 # LATIN SMALL LETTER C WITH CEDILLA
+#0xE8 U+00E8 # LATIN SMALL LETTER E WITH GRAVE
+#0xE9 U+00E9 # LATIN SMALL LETTER E WITH ACUTE
+#0xEA U+00EA # LATIN SMALL LETTER E WITH CIRCUMFLEX
+#0xEB U+00EB # LATIN SMALL LETTER E WITH DIAERESIS
+#0xEC U+00EC # LATIN SMALL LETTER I WITH GRAVE
+#0xED U+00ED # LATIN SMALL LETTER I WITH ACUTE
+#0xEE U+00EE # LATIN SMALL LETTER I WITH CIRCUMFLEX
+#0xEF U+00EF # LATIN SMALL LETTER I WITH DIAERESIS
+#0xF0 U+00F0 # LATIN SMALL LETTER ETH (Icelandic)
+#0xF1 U+00F1 # LATIN SMALL LETTER N WITH TILDE
+#0xF2 U+00F2 # LATIN SMALL LETTER O WITH GRAVE
+#0xF3 U+00F3 # LATIN SMALL LETTER O WITH ACUTE
+#0xF4 U+00F4 # LATIN SMALL LETTER O WITH CIRCUMFLEX
+#0xF5 U+00F5 # LATIN SMALL LETTER O WITH TILDE
+#0xF6 U+00F6 # LATIN SMALL LETTER O WITH DIAERESIS
+#0xF7 U+00F7 # DIVISION SIGN
+#0xF8 U+00F8 # LATIN SMALL LETTER O WITH STROKE
+#0xF9 U+00F9 # LATIN SMALL LETTER U WITH GRAVE
+#0xFA U+00FA # LATIN SMALL LETTER U WITH ACUTE
+#0xFB U+00FB # LATIN SMALL LETTER U WITH CIRCUMFLEX
+#0xFC U+00FC # LATIN SMALL LETTER U WITH DIAERESIS
+#0xFD U+00FD # LATIN SMALL LETTER Y WITH ACUTE
+#0xFE U+00FE # LATIN SMALL LETTER THORN (Icelandic)
+#0xFF U+00FF # LATIN SMALL LETTER Y WITH DIAERESIS
+
+
+0xd0 U+0110 # Dstrok and ETH are nearly the same...
+
+U+219e "\253-"
+U+21a0 "-\273"
+U+21ab "<-\260"
+U+21ac "\260->"
+U+21b4 "\254v"
+U+21b8 "\257^\134"
+U+21c7 "\253="
+U+21c9 "=\273"
+U+21e0 "<\267\267"
+U+21e2 "\267\267>"
+U+21f1 "|\257^\134"
+U+21f6 "=-\273>"
+U+2218 " \260 " # RING OPERATOR
+U+221b " ROOT\263 "
+U+2297 "(\327)" # CIRCLED TIMES
+U+2299 "(\267)" # CIRCLED DOT OPERATOR
+U+229A "(\260)" # CIRCLED RING OPERATOR
+U+22A0 "[\327]" # SQUARED TIMES
+U+22A1 "[\267]" # SQUARED DOT OPERATOR
+U+22C5 " \267 " # DOT OPERATOR
+U+2603 "\2508\250"
+U+2609 "(\267)"
+U+2614 "\250J\250"
+U+262d "\264\134,)"
+U+266b "d\257d"
+U+2686 "(\267)"
+U+2688 "((\267))"
+U+2692 "\264X`"
+U+2694 ",\327,"
+U+2697 "\360"
+U+2698 "\316"
+U+2699 "\244"
+U+269b ":\244:"
+U+2701 "8\264"
+U+270c "mV\270"
+U+2721 "\244"
+U+273f "\244"
+U+2740 "\244"
+U+2741 "\244"
+U+2761 "\266"
+U+279f "\267->"
+U+27a0 "\267->"
+U+27a7 "\267>"
+U+2900 "-|-\273"
+U+2901 "-||-\273"
+U+2905 "|-\273"
+U+290c "<-\267"
+U+290d "\267->"
+U+290e "<-\267\267"
+U+290f "\267\267->"
+U+2910 ">\267\267-\273"
+U+2911 "\267\267>"
+U+2912 "\257^|"
+U+2916 ">-\273"
+U+2917 ">-|-\273"
+U+2918 ">-||-\273"
+U+291b "-\253"
+U+291c "\273-"
+U+2923 "^\134\270"
+U+2924 "\270/^"
+U+2942 "-><\267"
+U+2943 "<-\267>"
+U+2944 "\267><-"
+U+2954 "\257^|"
+U+2958 "\257^|"
+U+295d "\257|v"
+U+2961 "\257|v"
diff --git a/src/chrtrans/iso02_uni.tbl b/src/chrtrans/iso02_uni.tbl
new file mode 100644
index 0000000..fe3fc71
--- /dev/null
+++ b/src/chrtrans/iso02_uni.tbl
@@ -0,0 +1,265 @@
+# This file has been modified for lynx (see README.tables)
+
+#The MIME name of this charset.
+Miso-8859-2
+
+#Name as a Display Charset (used on Options screen)
+OEastern European (ISO-8859-2)
+
+#Codepage number
+C912
+
+#
+# Name: ISO 8859-2:1999 to Unicode
+# Unicode version: 3.0
+# Table version: 1.0
+# Table format: Format A
+# Date: 1999 July 27
+# Authors: Ken Whistler <kenw@sybase.com>
+#
+# Copyright (c) 1991-1999 Unicode, Inc. All Rights reserved.
+#
+# This file is provided as-is by Unicode, Inc. (The Unicode Consortium).
+# No claims are made as to fitness for any particular purpose. No
+# warranties of any kind are expressed or implied. The recipient
+# agrees to determine applicability of information provided. If this
+# file has been provided on optical media by Unicode, Inc., the sole
+# remedy for any claim will be exchange of defective media within 90
+# days of receipt.
+#
+# Unicode, Inc. hereby grants the right to freely use the information
+# supplied in this file in the creation of products supporting the
+# Unicode Standard, and to make copies of this file in any form for
+# internal or external distribution as long as this notice remains
+# attached.
+#
+# General notes:
+#
+# This table contains the data the Unicode Consortium has on how
+# ISO/IEC 8859-2:1999 characters map into Unicode.
+#
+# Format: Three tab-separated columns
+# Column #1 is the ISO/IEC 8859-2 code (in hex as 0xXX)
+# Column #2 is the Unicode (in hex as 0xXXXX)
+# Column #3 the Unicode name (follows a comment sign, '#')
+#
+# The entries are in ISO/IEC 8859-2 order.
+#
+# Version history
+# 1.0 version updates 0.1 version by adding mappings for all
+# control characters.
+#
+# Updated versions of this file may be found in:
+# <ftp://ftp.unicode.org/Public/MAPPINGS/>
+#
+# Any comments or problems, contact <errata@unicode.org>
+# Please note that <errata@unicode.org> is an archival address;
+# notices will be checked, but do not expect an immediate response.
+#
+# Lines with more than one Unicode (U+XXXX) value contain additional
+# replacement mappings added for lynx. - kw
+#
+0x20-0x7e idem
+#
+#0x20 U+0020 # SPACE
+#0x21 U+0021 # EXCLAMATION MARK
+#0x22 U+0022 # QUOTATION MARK
+#0x23 U+0023 # NUMBER SIGN
+#0x24 U+0024 # DOLLAR SIGN
+#0x25 U+0025 # PERCENT SIGN
+#0x26 U+0026 # AMPERSAND
+#0x27 U+0027 # APOSTROPHE
+#0x28 U+0028 # LEFT PARENTHESIS
+#0x29 U+0029 # RIGHT PARENTHESIS
+#0x2A U+002A # ASTERISK
+#0x2B U+002B # PLUS SIGN
+#0x2C U+002C # COMMA
+#0x2D U+002D # HYPHEN-MINUS
+#0x2E U+002E # FULL STOP
+#0x2F U+002F # SOLIDUS
+#0x30 U+0030 # DIGIT ZERO
+#0x31 U+0031 # DIGIT ONE
+#0x32 U+0032 # DIGIT TWO
+#0x33 U+0033 # DIGIT THREE
+#0x34 U+0034 # DIGIT FOUR
+#0x35 U+0035 # DIGIT FIVE
+#0x36 U+0036 # DIGIT SIX
+#0x37 U+0037 # DIGIT SEVEN
+#0x38 U+0038 # DIGIT EIGHT
+#0x39 U+0039 # DIGIT NINE
+#0x3A U+003A # COLON
+#0x3B U+003B # SEMICOLON
+#0x3C U+003C # LESS-THAN SIGN
+#0x3D U+003D # EQUALS SIGN
+#0x3E U+003E # GREATER-THAN SIGN
+#0x3F U+003F # QUESTION MARK
+#0x40 U+0040 # COMMERCIAL AT
+#0x41 U+0041 # LATIN CAPITAL LETTER A
+#0x42 U+0042 # LATIN CAPITAL LETTER B
+#0x43 U+0043 # LATIN CAPITAL LETTER C
+#0x44 U+0044 # LATIN CAPITAL LETTER D
+#0x45 U+0045 # LATIN CAPITAL LETTER E
+#0x46 U+0046 # LATIN CAPITAL LETTER F
+#0x47 U+0047 # LATIN CAPITAL LETTER G
+#0x48 U+0048 # LATIN CAPITAL LETTER H
+#0x49 U+0049 # LATIN CAPITAL LETTER I
+#0x4A U+004A # LATIN CAPITAL LETTER J
+#0x4B U+004B # LATIN CAPITAL LETTER K
+#0x4C U+004C # LATIN CAPITAL LETTER L
+#0x4D U+004D # LATIN CAPITAL LETTER M
+#0x4E U+004E # LATIN CAPITAL LETTER N
+#0x4F U+004F # LATIN CAPITAL LETTER O
+#0x50 U+0050 # LATIN CAPITAL LETTER P
+#0x51 U+0051 # LATIN CAPITAL LETTER Q
+#0x52 U+0052 # LATIN CAPITAL LETTER R
+#0x53 U+0053 # LATIN CAPITAL LETTER S
+#0x54 U+0054 # LATIN CAPITAL LETTER T
+#0x55 U+0055 # LATIN CAPITAL LETTER U
+#0x56 U+0056 # LATIN CAPITAL LETTER V
+#0x57 U+0057 # LATIN CAPITAL LETTER W
+#0x58 U+0058 # LATIN CAPITAL LETTER X
+#0x59 U+0059 # LATIN CAPITAL LETTER Y
+#0x5A U+005A # LATIN CAPITAL LETTER Z
+#0x5B U+005B # LEFT SQUARE BRACKET
+#0x5C U+005C # REVERSE SOLIDUS
+#0x5D U+005D # RIGHT SQUARE BRACKET
+#0x5E U+005E # CIRCUMFLEX ACCENT
+#0x5F U+005F # LOW LINE
+#0x60 U+0060 # GRAVE ACCENT
+#0x61 U+0061 # LATIN SMALL LETTER A
+#0x62 U+0062 # LATIN SMALL LETTER B
+#0x63 U+0063 # LATIN SMALL LETTER C
+#0x64 U+0064 # LATIN SMALL LETTER D
+#0x65 U+0065 # LATIN SMALL LETTER E
+#0x66 U+0066 # LATIN SMALL LETTER F
+#0x67 U+0067 # LATIN SMALL LETTER G
+#0x68 U+0068 # LATIN SMALL LETTER H
+#0x69 U+0069 # LATIN SMALL LETTER I
+#0x6A U+006A # LATIN SMALL LETTER J
+#0x6B U+006B # LATIN SMALL LETTER K
+#0x6C U+006C # LATIN SMALL LETTER L
+#0x6D U+006D # LATIN SMALL LETTER M
+#0x6E U+006E # LATIN SMALL LETTER N
+#0x6F U+006F # LATIN SMALL LETTER O
+#0x70 U+0070 # LATIN SMALL LETTER P
+#0x71 U+0071 # LATIN SMALL LETTER Q
+#0x72 U+0072 # LATIN SMALL LETTER R
+#0x73 U+0073 # LATIN SMALL LETTER S
+#0x74 U+0074 # LATIN SMALL LETTER T
+#0x75 U+0075 # LATIN SMALL LETTER U
+#0x76 U+0076 # LATIN SMALL LETTER V
+#0x77 U+0077 # LATIN SMALL LETTER W
+#0x78 U+0078 # LATIN SMALL LETTER X
+#0x79 U+0079 # LATIN SMALL LETTER Y
+#0x7A U+007A # LATIN SMALL LETTER Z
+#0x7B U+007B # LEFT CURLY BRACKET
+#0x7C U+007C # VERTICAL LINE
+#0x7D U+007D # RIGHT CURLY BRACKET
+#0x7E U+007E # TILDE
+0xA0 U+00A0 # NO-BREAK SPACE
+0xA1 U+0104 # LATIN CAPITAL LETTER A WITH OGONEK
+0xA2 U+02D8 U+0306 # BREVE
+0xA3 U+0141 # LATIN CAPITAL LETTER L WITH STROKE
+0xA4 U+00A4 # CURRENCY SIGN
+0xA5 U+013D # LATIN CAPITAL LETTER L WITH CARON
+0xA6 U+015A # LATIN CAPITAL LETTER S WITH ACUTE
+0xA7 U+00A7 # SECTION SIGN
+0xA8 U+00A8 U+0308 # DIAERESIS
+0xA9 U+0160 U+0428 # LATIN CAPITAL LETTER S WITH CARON
+0xAA U+015E # LATIN CAPITAL LETTER S WITH CEDILLA
+0xAB U+0164 # LATIN CAPITAL LETTER T WITH CARON
+0xAC U+0179 # LATIN CAPITAL LETTER Z WITH ACUTE
+0xAD U+00AD # SOFT HYPHEN
+0xAE U+017D U+0416 # LATIN CAPITAL LETTER Z WITH CARON
+0xAF U+017B # LATIN CAPITAL LETTER Z WITH DOT ABOVE
+0xB0 U+00B0 U+030a # DEGREE SIGN
+0xB1 U+0105 # LATIN SMALL LETTER A WITH OGONEK
+0xB2 U+02DB U+0328 # OGONEK
+0xB3 U+0142 # LATIN SMALL LETTER L WITH STROKE
+0xB4 U+00B4 # ACUTE ACCENT
+0xB5 U+013E # LATIN SMALL LETTER L WITH CARON
+0xB6 U+015B # LATIN SMALL LETTER S WITH ACUTE
+0xB7 U+02C7 U+030c # CARON
+0xB8 U+00B8 U+0327 # CEDILLA
+0xB9 U+0161 U+0448 # LATIN SMALL LETTER S WITH CARON
+0xBA U+015F # LATIN SMALL LETTER S WITH CEDILLA
+0xBB U+0165 # LATIN SMALL LETTER T WITH CARON
+0xBC U+017A # LATIN SMALL LETTER Z WITH ACUTE
+0xBD U+02DD U+030b # DOUBLE ACUTE ACCENT
+0xBE U+017E U+0436 # LATIN SMALL LETTER Z WITH CARON
+0xBF U+017C # LATIN SMALL LETTER Z WITH DOT ABOVE
+0xC0 U+0154 # LATIN CAPITAL LETTER R WITH ACUTE
+0xC1 U+00C1 # LATIN CAPITAL LETTER A WITH ACUTE
+0xC2 U+00C2 # LATIN CAPITAL LETTER A WITH CIRCUMFLEX
+0xC3 U+0102 # LATIN CAPITAL LETTER A WITH BREVE
+0xC4 U+00C4 # LATIN CAPITAL LETTER A WITH DIAERESIS
+0xC5 U+0139 # LATIN CAPITAL LETTER L WITH ACUTE
+0xC6 U+0106 # LATIN CAPITAL LETTER C WITH ACUTE
+0xC7 U+00C7 # LATIN CAPITAL LETTER C WITH CEDILLA
+0xC8 U+010C U+0427 # LATIN CAPITAL LETTER C WITH CARON
+0xC9 U+00C9 # LATIN CAPITAL LETTER E WITH ACUTE
+0xCA U+0118 # LATIN CAPITAL LETTER E WITH OGONEK
+0xCB U+00CB # LATIN CAPITAL LETTER E WITH DIAERESIS
+0xCC U+011A # LATIN CAPITAL LETTER E WITH CARON
+0xCD U+00CD # LATIN CAPITAL LETTER I WITH ACUTE
+0xCE U+00CE # LATIN CAPITAL LETTER I WITH CIRCUMFLEX
+0xCF U+010E # LATIN CAPITAL LETTER D WITH CARON
+0xD0 U+0110 # LATIN CAPITAL LETTER D WITH STROKE
+0xD1 U+0143 # LATIN CAPITAL LETTER N WITH ACUTE
+0xD2 U+0147 # LATIN CAPITAL LETTER N WITH CARON
+0xD3 U+00D3 # LATIN CAPITAL LETTER O WITH ACUTE
+0xD4 U+00D4 # LATIN CAPITAL LETTER O WITH CIRCUMFLEX
+0xD5 U+0150 # LATIN CAPITAL LETTER O WITH DOUBLE ACUTE
+0xD6 U+00D6 # LATIN CAPITAL LETTER O WITH DIAERESIS
+0xD7 U+00D7 U+00b7 # MULTIPLICATION SIGN
+0xD8 U+0158 # LATIN CAPITAL LETTER R WITH CARON
+0xD9 U+016E # LATIN CAPITAL LETTER U WITH RING ABOVE
+0xDA U+00DA # LATIN CAPITAL LETTER U WITH ACUTE
+0xDB U+0170 # LATIN CAPITAL LETTER U WITH DOUBLE ACUTE
+0xDC U+00DC # LATIN CAPITAL LETTER U WITH DIAERESIS
+0xDD U+00DD # LATIN CAPITAL LETTER Y WITH ACUTE
+0xDE U+0162 # LATIN CAPITAL LETTER T WITH CEDILLA
+0xDF U+00DF # LATIN SMALL LETTER SHARP S
+0xE0 U+0155 # LATIN SMALL LETTER R WITH ACUTE
+0xE1 U+00E1 # LATIN SMALL LETTER A WITH ACUTE
+0xE2 U+00E2 # LATIN SMALL LETTER A WITH CIRCUMFLEX
+0xE3 U+0103 # LATIN SMALL LETTER A WITH BREVE
+0xE4 U+00E4 # LATIN SMALL LETTER A WITH DIAERESIS
+0xE5 U+013A # LATIN SMALL LETTER L WITH ACUTE
+0xE6 U+0107 # LATIN SMALL LETTER C WITH ACUTE
+0xE7 U+00E7 # LATIN SMALL LETTER C WITH CEDILLA
+0xE8 U+010D U+02a7 U+0447 # LATIN SMALL LETTER C WITH CARON
+0xE9 U+00E9 # LATIN SMALL LETTER E WITH ACUTE
+0xEA U+0119 # LATIN SMALL LETTER E WITH OGONEK
+0xEB U+00EB # LATIN SMALL LETTER E WITH DIAERESIS
+0xEC U+011B # LATIN SMALL LETTER E WITH CARON
+0xED U+00ED # LATIN SMALL LETTER I WITH ACUTE
+0xEE U+00EE # LATIN SMALL LETTER I WITH CIRCUMFLEX
+0xEF U+010F # LATIN SMALL LETTER D WITH CARON
+0xF0 U+0111 # LATIN SMALL LETTER D WITH STROKE
+0xF1 U+0144 # LATIN SMALL LETTER N WITH ACUTE
+0xF2 U+0148 # LATIN SMALL LETTER N WITH CARON
+0xF3 U+00F3 # LATIN SMALL LETTER O WITH ACUTE
+0xF4 U+00F4 # LATIN SMALL LETTER O WITH CIRCUMFLEX
+0xF5 U+0151 # LATIN SMALL LETTER O WITH DOUBLE ACUTE
+0xF6 U+00F6 # LATIN SMALL LETTER O WITH DIAERESIS
+0xF7 U+00F7 # DIVISION SIGN
+0xF8 U+0159 # LATIN SMALL LETTER R WITH CARON
+0xF9 U+016F # LATIN SMALL LETTER U WITH RING ABOVE
+0xFA U+00FA # LATIN SMALL LETTER U WITH ACUTE
+0xFB U+0171 # LATIN SMALL LETTER U WITH DOUBLE ACUTE
+0xFC U+00FC # LATIN SMALL LETTER U WITH DIAERESIS
+0xFD U+00FD # LATIN SMALL LETTER Y WITH ACUTE
+0xFE U+0163 # LATIN SMALL LETTER T WITH CEDILLA
+0xFF U+02D9 U+0307 U+0387 # DOT ABOVE
+
+
+0xd0 U+00d0 # Dstrok and ETH are nearly the same...
+
+U+2218 " \260 " # RING OPERATOR
+U+2297 "(\327)" # CIRCLED TIMES
+U+2299 "(\377)" # CIRCLED DOT OPERATOR
+U+229A "(\260)" # CIRCLED RING OPERATOR
+U+22A0 "[\327]" # SQUARED TIMES
+U+22A1 "[\377]" # SQUARED DOT OPERATOR
+U+22C5 " \377 " # DOT OPERATOR
diff --git a/src/chrtrans/iso03_uni.tbl b/src/chrtrans/iso03_uni.tbl
new file mode 100644
index 0000000..aafce8d
--- /dev/null
+++ b/src/chrtrans/iso03_uni.tbl
@@ -0,0 +1,255 @@
+# This file has been modified for lynx (see README.tables)
+
+#The MIME name of this charset.
+Miso-8859-3
+
+#Name as a Display Charset (used on Options screen)
+OLatin 3 (ISO-8859-3)
+
+#Codepage number
+C913
+
+#
+# Name: ISO/IEC 8859-3:1999 to Unicode
+# Unicode version: 3.0
+# Table version: 1.0
+# Table format: Format A
+# Date: 1999 July 27
+# Authors: Ken Whistler <kenw@sybase.com>
+#
+# Copyright (c) 1991-1999 Unicode, Inc. All Rights reserved.
+#
+# This file is provided as-is by Unicode, Inc. (The Unicode Consortium).
+# No claims are made as to fitness for any particular purpose. No
+# warranties of any kind are expressed or implied. The recipient
+# agrees to determine applicability of information provided. If this
+# file has been provided on optical media by Unicode, Inc., the sole
+# remedy for any claim will be exchange of defective media within 90
+# days of receipt.
+#
+# Unicode, Inc. hereby grants the right to freely use the information
+# supplied in this file in the creation of products supporting the
+# Unicode Standard, and to make copies of this file in any form for
+# internal or external distribution as long as this notice remains
+# attached.
+#
+# General notes:
+#
+# This table contains the data the Unicode Consortium has on how
+# ISO/IEC 8859-3:1999 characters map into Unicode.
+#
+# Format: Three tab-separated columns
+# Column #1 is the ISO/IEC 8859-3 code (in hex as 0xXX)
+# Column #2 is the Unicode (in hex as 0xXXXX)
+# Column #3 the Unicode name (follows a comment sign, '#')
+#
+# The entries are in ISO/IEC 8859-3 order.
+#
+# Version history
+# 1.0 version updates 0.1 version by adding mappings for all
+# control characters.
+#
+# Updated versions of this file may be found in:
+# <ftp://ftp.unicode.org/Public/MAPPINGS/>
+#
+# Any comments or problems, contact <errata@unicode.org>
+# Please note that <errata@unicode.org> is an archival address;
+# notices will be checked, but do not expect an immediate response.
+#
+0x20-0x7e idem
+#
+#0x20 U+0020 # SPACE
+#0x21 U+0021 # EXCLAMATION MARK
+#0x22 U+0022 # QUOTATION MARK
+#0x23 U+0023 # NUMBER SIGN
+#0x24 U+0024 # DOLLAR SIGN
+#0x25 U+0025 # PERCENT SIGN
+#0x26 U+0026 # AMPERSAND
+#0x27 U+0027 # APOSTROPHE
+#0x28 U+0028 # LEFT PARENTHESIS
+#0x29 U+0029 # RIGHT PARENTHESIS
+#0x2A U+002A # ASTERISK
+#0x2B U+002B # PLUS SIGN
+#0x2C U+002C # COMMA
+#0x2D U+002D # HYPHEN-MINUS
+#0x2E U+002E # FULL STOP
+#0x2F U+002F # SOLIDUS
+#0x30 U+0030 # DIGIT ZERO
+#0x31 U+0031 # DIGIT ONE
+#0x32 U+0032 # DIGIT TWO
+#0x33 U+0033 # DIGIT THREE
+#0x34 U+0034 # DIGIT FOUR
+#0x35 U+0035 # DIGIT FIVE
+#0x36 U+0036 # DIGIT SIX
+#0x37 U+0037 # DIGIT SEVEN
+#0x38 U+0038 # DIGIT EIGHT
+#0x39 U+0039 # DIGIT NINE
+#0x3A U+003A # COLON
+#0x3B U+003B # SEMICOLON
+#0x3C U+003C # LESS-THAN SIGN
+#0x3D U+003D # EQUALS SIGN
+#0x3E U+003E # GREATER-THAN SIGN
+#0x3F U+003F # QUESTION MARK
+#0x40 U+0040 # COMMERCIAL AT
+#0x41 U+0041 # LATIN CAPITAL LETTER A
+#0x42 U+0042 # LATIN CAPITAL LETTER B
+#0x43 U+0043 # LATIN CAPITAL LETTER C
+#0x44 U+0044 # LATIN CAPITAL LETTER D
+#0x45 U+0045 # LATIN CAPITAL LETTER E
+#0x46 U+0046 # LATIN CAPITAL LETTER F
+#0x47 U+0047 # LATIN CAPITAL LETTER G
+#0x48 U+0048 # LATIN CAPITAL LETTER H
+#0x49 U+0049 # LATIN CAPITAL LETTER I
+#0x4A U+004A # LATIN CAPITAL LETTER J
+#0x4B U+004B # LATIN CAPITAL LETTER K
+#0x4C U+004C # LATIN CAPITAL LETTER L
+#0x4D U+004D # LATIN CAPITAL LETTER M
+#0x4E U+004E # LATIN CAPITAL LETTER N
+#0x4F U+004F # LATIN CAPITAL LETTER O
+#0x50 U+0050 # LATIN CAPITAL LETTER P
+#0x51 U+0051 # LATIN CAPITAL LETTER Q
+#0x52 U+0052 # LATIN CAPITAL LETTER R
+#0x53 U+0053 # LATIN CAPITAL LETTER S
+#0x54 U+0054 # LATIN CAPITAL LETTER T
+#0x55 U+0055 # LATIN CAPITAL LETTER U
+#0x56 U+0056 # LATIN CAPITAL LETTER V
+#0x57 U+0057 # LATIN CAPITAL LETTER W
+#0x58 U+0058 # LATIN CAPITAL LETTER X
+#0x59 U+0059 # LATIN CAPITAL LETTER Y
+#0x5A U+005A # LATIN CAPITAL LETTER Z
+#0x5B U+005B # LEFT SQUARE BRACKET
+#0x5C U+005C # REVERSE SOLIDUS
+#0x5D U+005D # RIGHT SQUARE BRACKET
+#0x5E U+005E # CIRCUMFLEX ACCENT
+#0x5F U+005F # LOW LINE
+#0x60 U+0060 # GRAVE ACCENT
+#0x61 U+0061 # LATIN SMALL LETTER A
+#0x62 U+0062 # LATIN SMALL LETTER B
+#0x63 U+0063 # LATIN SMALL LETTER C
+#0x64 U+0064 # LATIN SMALL LETTER D
+#0x65 U+0065 # LATIN SMALL LETTER E
+#0x66 U+0066 # LATIN SMALL LETTER F
+#0x67 U+0067 # LATIN SMALL LETTER G
+#0x68 U+0068 # LATIN SMALL LETTER H
+#0x69 U+0069 # LATIN SMALL LETTER I
+#0x6A U+006A # LATIN SMALL LETTER J
+#0x6B U+006B # LATIN SMALL LETTER K
+#0x6C U+006C # LATIN SMALL LETTER L
+#0x6D U+006D # LATIN SMALL LETTER M
+#0x6E U+006E # LATIN SMALL LETTER N
+#0x6F U+006F # LATIN SMALL LETTER O
+#0x70 U+0070 # LATIN SMALL LETTER P
+#0x71 U+0071 # LATIN SMALL LETTER Q
+#0x72 U+0072 # LATIN SMALL LETTER R
+#0x73 U+0073 # LATIN SMALL LETTER S
+#0x74 U+0074 # LATIN SMALL LETTER T
+#0x75 U+0075 # LATIN SMALL LETTER U
+#0x76 U+0076 # LATIN SMALL LETTER V
+#0x77 U+0077 # LATIN SMALL LETTER W
+#0x78 U+0078 # LATIN SMALL LETTER X
+#0x79 U+0079 # LATIN SMALL LETTER Y
+#0x7A U+007A # LATIN SMALL LETTER Z
+#0x7B U+007B # LEFT CURLY BRACKET
+#0x7C U+007C # VERTICAL LINE
+#0x7D U+007D # RIGHT CURLY BRACKET
+#0x7E U+007E # TILDE
+0xA0 U+00A0 # NO-BREAK SPACE
+0xA1 U+0126 # LATIN CAPITAL LETTER H WITH STROKE
+0xA2 U+02D8 # BREVE
+0xA3 U+00A3 # POUND SIGN
+0xA4 U+00A4 # CURRENCY SIGN
+0xA6 U+0124 # LATIN CAPITAL LETTER H WITH CIRCUMFLEX
+0xA7 U+00A7 # SECTION SIGN
+0xA8 U+00A8 # DIAERESIS
+0xA9 U+0130 # LATIN CAPITAL LETTER I WITH DOT ABOVE
+0xAA U+015E # LATIN CAPITAL LETTER S WITH CEDILLA
+0xAB U+011E # LATIN CAPITAL LETTER G WITH BREVE
+0xAC U+0134 # LATIN CAPITAL LETTER J WITH CIRCUMFLEX
+0xAD U+00AD # SOFT HYPHEN
+0xAF U+017B # LATIN CAPITAL LETTER Z WITH DOT ABOVE
+0xB0 U+00B0 # DEGREE SIGN
+0xB1 U+0127 # LATIN SMALL LETTER H WITH STROKE
+0xB2 U+00B2 # SUPERSCRIPT TWO
+0xB3 U+00B3 # SUPERSCRIPT THREE
+0xB4 U+00B4 # ACUTE ACCENT
+0xB5 U+00B5 # MICRO SIGN
+0xB6 U+0125 # LATIN SMALL LETTER H WITH CIRCUMFLEX
+0xB7 U+00B7 # MIDDLE DOT
+0xB8 U+00B8 # CEDILLA
+0xB9 U+0131 # LATIN SMALL LETTER DOTLESS I
+0xBA U+015F # LATIN SMALL LETTER S WITH CEDILLA
+0xBB U+011F # LATIN SMALL LETTER G WITH BREVE
+0xBC U+0135 # LATIN SMALL LETTER J WITH CIRCUMFLEX
+0xBD U+00BD # VULGAR FRACTION ONE HALF
+0xBF U+017C # LATIN SMALL LETTER Z WITH DOT ABOVE
+0xC0 U+00C0 # LATIN CAPITAL LETTER A WITH GRAVE
+0xC1 U+00C1 # LATIN CAPITAL LETTER A WITH ACUTE
+0xC2 U+00C2 # LATIN CAPITAL LETTER A WITH CIRCUMFLEX
+0xC4 U+00C4 # LATIN CAPITAL LETTER A WITH DIAERESIS
+0xC5 U+010A # LATIN CAPITAL LETTER C WITH DOT ABOVE
+0xC6 U+0108 # LATIN CAPITAL LETTER C WITH CIRCUMFLEX
+0xC7 U+00C7 # LATIN CAPITAL LETTER C WITH CEDILLA
+0xC8 U+00C8 # LATIN CAPITAL LETTER E WITH GRAVE
+0xC9 U+00C9 # LATIN CAPITAL LETTER E WITH ACUTE
+0xCA U+00CA # LATIN CAPITAL LETTER E WITH CIRCUMFLEX
+0xCB U+00CB # LATIN CAPITAL LETTER E WITH DIAERESIS
+0xCC U+00CC # LATIN CAPITAL LETTER I WITH GRAVE
+0xCD U+00CD # LATIN CAPITAL LETTER I WITH ACUTE
+0xCE U+00CE # LATIN CAPITAL LETTER I WITH CIRCUMFLEX
+0xCF U+00CF # LATIN CAPITAL LETTER I WITH DIAERESIS
+0xD1 U+00D1 # LATIN CAPITAL LETTER N WITH TILDE
+0xD2 U+00D2 # LATIN CAPITAL LETTER O WITH GRAVE
+0xD3 U+00D3 # LATIN CAPITAL LETTER O WITH ACUTE
+0xD4 U+00D4 # LATIN CAPITAL LETTER O WITH CIRCUMFLEX
+0xD5 U+0120 # LATIN CAPITAL LETTER G WITH DOT ABOVE
+0xD6 U+00D6 # LATIN CAPITAL LETTER O WITH DIAERESIS
+0xD7 U+00D7 # MULTIPLICATION SIGN
+0xD8 U+011C # LATIN CAPITAL LETTER G WITH CIRCUMFLEX
+0xD9 U+00D9 # LATIN CAPITAL LETTER U WITH GRAVE
+0xDA U+00DA # LATIN CAPITAL LETTER U WITH ACUTE
+0xDB U+00DB # LATIN CAPITAL LETTER U WITH CIRCUMFLEX
+0xDC U+00DC # LATIN CAPITAL LETTER U WITH DIAERESIS
+0xDD U+016C # LATIN CAPITAL LETTER U WITH BREVE
+0xDE U+015C # LATIN CAPITAL LETTER S WITH CIRCUMFLEX
+0xDF U+00DF # LATIN SMALL LETTER SHARP S
+0xE0 U+00E0 # LATIN SMALL LETTER A WITH GRAVE
+0xE1 U+00E1 # LATIN SMALL LETTER A WITH ACUTE
+0xE2 U+00E2 # LATIN SMALL LETTER A WITH CIRCUMFLEX
+0xE4 U+00E4 # LATIN SMALL LETTER A WITH DIAERESIS
+0xE5 U+010B # LATIN SMALL LETTER C WITH DOT ABOVE
+0xE6 U+0109 # LATIN SMALL LETTER C WITH CIRCUMFLEX
+0xE7 U+00E7 # LATIN SMALL LETTER C WITH CEDILLA
+0xE8 U+00E8 # LATIN SMALL LETTER E WITH GRAVE
+0xE9 U+00E9 # LATIN SMALL LETTER E WITH ACUTE
+0xEA U+00EA # LATIN SMALL LETTER E WITH CIRCUMFLEX
+0xEB U+00EB # LATIN SMALL LETTER E WITH DIAERESIS
+0xEC U+00EC # LATIN SMALL LETTER I WITH GRAVE
+0xED U+00ED # LATIN SMALL LETTER I WITH ACUTE
+0xEE U+00EE # LATIN SMALL LETTER I WITH CIRCUMFLEX
+0xEF U+00EF # LATIN SMALL LETTER I WITH DIAERESIS
+0xF1 U+00F1 # LATIN SMALL LETTER N WITH TILDE
+0xF2 U+00F2 # LATIN SMALL LETTER O WITH GRAVE
+0xF3 U+00F3 # LATIN SMALL LETTER O WITH ACUTE
+0xF4 U+00F4 # LATIN SMALL LETTER O WITH CIRCUMFLEX
+0xF5 U+0121 # LATIN SMALL LETTER G WITH DOT ABOVE
+0xF6 U+00F6 # LATIN SMALL LETTER O WITH DIAERESIS
+0xF7 U+00F7 # DIVISION SIGN
+0xF8 U+011D # LATIN SMALL LETTER G WITH CIRCUMFLEX
+0xF9 U+00F9 # LATIN SMALL LETTER U WITH GRAVE
+0xFA U+00FA # LATIN SMALL LETTER U WITH ACUTE
+0xFB U+00FB # LATIN SMALL LETTER U WITH CIRCUMFLEX
+0xFC U+00FC # LATIN SMALL LETTER U WITH DIAERESIS
+0xFD U+016D # LATIN SMALL LETTER U WITH BREVE
+0xFE U+015D # LATIN SMALL LETTER S WITH CIRCUMFLEX
+0xFF U+02D9 # DOT ABOVE
+
+
+# unassigned 8859-3 codepoints:
+# 0xa5 unused
+# 0xae unused
+# 0xbe unused
+# 0xc3 unused
+# 0xd0 unused
+# 0xe3 unused
+# 0xf0 unused
+
diff --git a/src/chrtrans/iso04_uni.tbl b/src/chrtrans/iso04_uni.tbl
new file mode 100644
index 0000000..a1c3ed0
--- /dev/null
+++ b/src/chrtrans/iso04_uni.tbl
@@ -0,0 +1,252 @@
+# This file has been modified for lynx (see README.tables)
+
+#The MIME name of this charset.
+Miso-8859-4
+
+#Name as a Display Charset (used on Options screen)
+OLatin 4 (ISO-8859-4)
+
+#Codepage number
+C914
+
+#
+# Name: ISO/IEC 8859-4:1998 to Unicode
+# Unicode version: 3.0
+# Table version: 1.0
+# Table format: Format A
+# Date: 1999 July 27
+# Authors: Ken Whistler <kenw@sybase.com>
+#
+# Copyright (c) 1991-1999 Unicode, Inc. All Rights reserved.
+#
+# This file is provided as-is by Unicode, Inc. (The Unicode Consortium).
+# No claims are made as to fitness for any particular purpose. No
+# warranties of any kind are expressed or implied. The recipient
+# agrees to determine applicability of information provided. If this
+# file has been provided on optical media by Unicode, Inc., the sole
+# remedy for any claim will be exchange of defective media within 90
+# days of receipt.
+#
+# Unicode, Inc. hereby grants the right to freely use the information
+# supplied in this file in the creation of products supporting the
+# Unicode Standard, and to make copies of this file in any form for
+# internal or external distribution as long as this notice remains
+# attached.
+#
+# General notes:
+#
+# This table contains the data the Unicode Consortium has on how
+# ISO/IEC 8859-4:1998 characters map into Unicode.
+#
+# Format: Three tab-separated columns
+# Column #1 is the ISO/IEC 8859-4 code (in hex as 0xXX)
+# Column #2 is the Unicode (in hex as 0xXXXX)
+# Column #3 the Unicode name (follows a comment sign, '#')
+#
+# The entries are in ISO/IEC 8859-4 order.
+#
+# Version history
+# 1.0 version updates 0.1 version by adding mappings for all
+# control characters.
+#
+# Updated versions of this file may be found in:
+# <ftp://ftp.unicode.org/Public/MAPPINGS/>
+#
+# Any comments or problems, contact <errata@unicode.org>
+# Please note that <errata@unicode.org> is an archival address;
+# notices will be checked, but do not expect an immediate response.
+#
+#0x20 U+0020 # SPACE
+#0x21 U+0021 # EXCLAMATION MARK
+#0x22 U+0022 # QUOTATION MARK
+#0x23 U+0023 # NUMBER SIGN
+#0x24 U+0024 # DOLLAR SIGN
+#0x25 U+0025 # PERCENT SIGN
+#0x26 U+0026 # AMPERSAND
+#0x27 U+0027 # APOSTROPHE
+#0x28 U+0028 # LEFT PARENTHESIS
+#0x29 U+0029 # RIGHT PARENTHESIS
+#0x2A U+002A # ASTERISK
+#0x2B U+002B # PLUS SIGN
+#0x2C U+002C # COMMA
+#0x2D U+002D # HYPHEN-MINUS
+#0x2E U+002E # FULL STOP
+#0x2F U+002F # SOLIDUS
+#0x30 U+0030 # DIGIT ZERO
+#0x31 U+0031 # DIGIT ONE
+#0x32 U+0032 # DIGIT TWO
+#0x33 U+0033 # DIGIT THREE
+#0x34 U+0034 # DIGIT FOUR
+#0x35 U+0035 # DIGIT FIVE
+#0x36 U+0036 # DIGIT SIX
+#0x37 U+0037 # DIGIT SEVEN
+#0x38 U+0038 # DIGIT EIGHT
+#0x39 U+0039 # DIGIT NINE
+#0x3A U+003A # COLON
+#0x3B U+003B # SEMICOLON
+#0x3C U+003C # LESS-THAN SIGN
+#0x3D U+003D # EQUALS SIGN
+#0x3E U+003E # GREATER-THAN SIGN
+#0x3F U+003F # QUESTION MARK
+#0x40 U+0040 # COMMERCIAL AT
+#0x41 U+0041 # LATIN CAPITAL LETTER A
+#0x42 U+0042 # LATIN CAPITAL LETTER B
+#0x43 U+0043 # LATIN CAPITAL LETTER C
+#0x44 U+0044 # LATIN CAPITAL LETTER D
+#0x45 U+0045 # LATIN CAPITAL LETTER E
+#0x46 U+0046 # LATIN CAPITAL LETTER F
+#0x47 U+0047 # LATIN CAPITAL LETTER G
+#0x48 U+0048 # LATIN CAPITAL LETTER H
+#0x49 U+0049 # LATIN CAPITAL LETTER I
+#0x4A U+004A # LATIN CAPITAL LETTER J
+#0x4B U+004B # LATIN CAPITAL LETTER K
+#0x4C U+004C # LATIN CAPITAL LETTER L
+#0x4D U+004D # LATIN CAPITAL LETTER M
+#0x4E U+004E # LATIN CAPITAL LETTER N
+#0x4F U+004F # LATIN CAPITAL LETTER O
+#0x50 U+0050 # LATIN CAPITAL LETTER P
+#0x51 U+0051 # LATIN CAPITAL LETTER Q
+#0x52 U+0052 # LATIN CAPITAL LETTER R
+#0x53 U+0053 # LATIN CAPITAL LETTER S
+#0x54 U+0054 # LATIN CAPITAL LETTER T
+#0x55 U+0055 # LATIN CAPITAL LETTER U
+#0x56 U+0056 # LATIN CAPITAL LETTER V
+#0x57 U+0057 # LATIN CAPITAL LETTER W
+#0x58 U+0058 # LATIN CAPITAL LETTER X
+#0x59 U+0059 # LATIN CAPITAL LETTER Y
+#0x5A U+005A # LATIN CAPITAL LETTER Z
+#0x5B U+005B # LEFT SQUARE BRACKET
+#0x5C U+005C # REVERSE SOLIDUS
+#0x5D U+005D # RIGHT SQUARE BRACKET
+#0x5E U+005E # CIRCUMFLEX ACCENT
+#0x5F U+005F # LOW LINE
+#0x60 U+0060 # GRAVE ACCENT
+#0x61 U+0061 # LATIN SMALL LETTER A
+#0x62 U+0062 # LATIN SMALL LETTER B
+#0x63 U+0063 # LATIN SMALL LETTER C
+#0x64 U+0064 # LATIN SMALL LETTER D
+#0x65 U+0065 # LATIN SMALL LETTER E
+#0x66 U+0066 # LATIN SMALL LETTER F
+#0x67 U+0067 # LATIN SMALL LETTER G
+#0x68 U+0068 # LATIN SMALL LETTER H
+#0x69 U+0069 # LATIN SMALL LETTER I
+#0x6A U+006A # LATIN SMALL LETTER J
+#0x6B U+006B # LATIN SMALL LETTER K
+#0x6C U+006C # LATIN SMALL LETTER L
+#0x6D U+006D # LATIN SMALL LETTER M
+#0x6E U+006E # LATIN SMALL LETTER N
+#0x6F U+006F # LATIN SMALL LETTER O
+#0x70 U+0070 # LATIN SMALL LETTER P
+#0x71 U+0071 # LATIN SMALL LETTER Q
+#0x72 U+0072 # LATIN SMALL LETTER R
+#0x73 U+0073 # LATIN SMALL LETTER S
+#0x74 U+0074 # LATIN SMALL LETTER T
+#0x75 U+0075 # LATIN SMALL LETTER U
+#0x76 U+0076 # LATIN SMALL LETTER V
+#0x77 U+0077 # LATIN SMALL LETTER W
+#0x78 U+0078 # LATIN SMALL LETTER X
+#0x79 U+0079 # LATIN SMALL LETTER Y
+#0x7A U+007A # LATIN SMALL LETTER Z
+#0x7B U+007B # LEFT CURLY BRACKET
+#0x7C U+007C # VERTICAL LINE
+#0x7D U+007D # RIGHT CURLY BRACKET
+#0x7E U+007E # TILDE
+0xA0 U+00A0 # NO-BREAK SPACE
+0xA1 U+0104 # LATIN CAPITAL LETTER A WITH OGONEK
+0xA2 U+0138 # LATIN SMALL LETTER KRA
+0xA3 U+0156 # LATIN CAPITAL LETTER R WITH CEDILLA
+0xA4 U+00A4 # CURRENCY SIGN
+0xA5 U+0128 # LATIN CAPITAL LETTER I WITH TILDE
+0xA6 U+013B # LATIN CAPITAL LETTER L WITH CEDILLA
+0xA7 U+00A7 # SECTION SIGN
+0xA8 U+00A8 # DIAERESIS
+0xA9 U+0160 # LATIN CAPITAL LETTER S WITH CARON
+0xAA U+0112 # LATIN CAPITAL LETTER E WITH MACRON
+0xAB U+0122 # LATIN CAPITAL LETTER G WITH CEDILLA
+0xAC U+0166 # LATIN CAPITAL LETTER T WITH STROKE
+0xAD U+00AD # SOFT HYPHEN
+0xAE U+017D # LATIN CAPITAL LETTER Z WITH CARON
+0xAF U+00AF # MACRON
+0xB0 U+00B0 # DEGREE SIGN
+0xB1 U+0105 # LATIN SMALL LETTER A WITH OGONEK
+0xB2 U+02DB # OGONEK
+0xB3 U+0157 # LATIN SMALL LETTER R WITH CEDILLA
+0xB4 U+00B4 # ACUTE ACCENT
+0xB5 U+0129 # LATIN SMALL LETTER I WITH TILDE
+0xB6 U+013C # LATIN SMALL LETTER L WITH CEDILLA
+0xB7 U+02C7 # CARON
+0xB8 U+00B8 # CEDILLA
+0xB9 U+0161 # LATIN SMALL LETTER S WITH CARON
+0xBA U+0113 # LATIN SMALL LETTER E WITH MACRON
+0xBB U+0123 # LATIN SMALL LETTER G WITH CEDILLA
+0xBC U+0167 # LATIN SMALL LETTER T WITH STROKE
+0xBD U+014A # LATIN CAPITAL LETTER ENG
+0xBE U+017E # LATIN SMALL LETTER Z WITH CARON
+0xBF U+014B # LATIN SMALL LETTER ENG
+0xC0 U+0100 # LATIN CAPITAL LETTER A WITH MACRON
+0xC1 U+00C1 # LATIN CAPITAL LETTER A WITH ACUTE
+0xC2 U+00C2 # LATIN CAPITAL LETTER A WITH CIRCUMFLEX
+0xC3 U+00C3 # LATIN CAPITAL LETTER A WITH TILDE
+0xC4 U+00C4 # LATIN CAPITAL LETTER A WITH DIAERESIS
+0xC5 U+00C5 # LATIN CAPITAL LETTER A WITH RING ABOVE
+0xC6 U+00C6 # LATIN CAPITAL LETTER AE
+0xC7 U+012E # LATIN CAPITAL LETTER I WITH OGONEK
+0xC8 U+010C # LATIN CAPITAL LETTER C WITH CARON
+0xC9 U+00C9 # LATIN CAPITAL LETTER E WITH ACUTE
+0xCA U+0118 # LATIN CAPITAL LETTER E WITH OGONEK
+0xCB U+00CB # LATIN CAPITAL LETTER E WITH DIAERESIS
+0xCC U+0116 # LATIN CAPITAL LETTER E WITH DOT ABOVE
+0xCD U+00CD # LATIN CAPITAL LETTER I WITH ACUTE
+0xCE U+00CE # LATIN CAPITAL LETTER I WITH CIRCUMFLEX
+0xCF U+012A # LATIN CAPITAL LETTER I WITH MACRON
+0xD0 U+0110 # LATIN CAPITAL LETTER D WITH STROKE
+0xD1 U+0145 # LATIN CAPITAL LETTER N WITH CEDILLA
+0xD2 U+014C # LATIN CAPITAL LETTER O WITH MACRON
+0xD3 U+0136 # LATIN CAPITAL LETTER K WITH CEDILLA
+0xD4 U+00D4 # LATIN CAPITAL LETTER O WITH CIRCUMFLEX
+0xD5 U+00D5 # LATIN CAPITAL LETTER O WITH TILDE
+0xD6 U+00D6 # LATIN CAPITAL LETTER O WITH DIAERESIS
+0xD7 U+00D7 # MULTIPLICATION SIGN
+0xD8 U+00D8 # LATIN CAPITAL LETTER O WITH STROKE
+0xD9 U+0172 # LATIN CAPITAL LETTER U WITH OGONEK
+0xDA U+00DA # LATIN CAPITAL LETTER U WITH ACUTE
+0xDB U+00DB # LATIN CAPITAL LETTER U WITH CIRCUMFLEX
+0xDC U+00DC # LATIN CAPITAL LETTER U WITH DIAERESIS
+0xDD U+0168 # LATIN CAPITAL LETTER U WITH TILDE
+0xDE U+016A # LATIN CAPITAL LETTER U WITH MACRON
+0xDF U+00DF # LATIN SMALL LETTER SHARP S
+0xE0 U+0101 # LATIN SMALL LETTER A WITH MACRON
+0xE1 U+00E1 # LATIN SMALL LETTER A WITH ACUTE
+0xE2 U+00E2 # LATIN SMALL LETTER A WITH CIRCUMFLEX
+0xE3 U+00E3 # LATIN SMALL LETTER A WITH TILDE
+0xE4 U+00E4 # LATIN SMALL LETTER A WITH DIAERESIS
+0xE5 U+00E5 # LATIN SMALL LETTER A WITH RING ABOVE
+0xE6 U+00E6 # LATIN SMALL LETTER AE
+0xE7 U+012F # LATIN SMALL LETTER I WITH OGONEK
+0xE8 U+010D # LATIN SMALL LETTER C WITH CARON
+0xE9 U+00E9 # LATIN SMALL LETTER E WITH ACUTE
+0xEA U+0119 # LATIN SMALL LETTER E WITH OGONEK
+0xEB U+00EB # LATIN SMALL LETTER E WITH DIAERESIS
+0xEC U+0117 # LATIN SMALL LETTER E WITH DOT ABOVE
+0xED U+00ED # LATIN SMALL LETTER I WITH ACUTE
+0xEE U+00EE # LATIN SMALL LETTER I WITH CIRCUMFLEX
+0xEF U+012B # LATIN SMALL LETTER I WITH MACRON
+0xF0 U+0111 # LATIN SMALL LETTER D WITH STROKE
+0xF1 U+0146 # LATIN SMALL LETTER N WITH CEDILLA
+0xF2 U+014D # LATIN SMALL LETTER O WITH MACRON
+0xF3 U+0137 # LATIN SMALL LETTER K WITH CEDILLA
+0xF4 U+00F4 # LATIN SMALL LETTER O WITH CIRCUMFLEX
+0xF5 U+00F5 # LATIN SMALL LETTER O WITH TILDE
+0xF6 U+00F6 # LATIN SMALL LETTER O WITH DIAERESIS
+0xF7 U+00F7 # DIVISION SIGN
+0xF8 U+00F8 # LATIN SMALL LETTER O WITH STROKE
+0xF9 U+0173 # LATIN SMALL LETTER U WITH OGONEK
+0xFA U+00FA # LATIN SMALL LETTER U WITH ACUTE
+0xFB U+00FB # LATIN SMALL LETTER U WITH CIRCUMFLEX
+0xFC U+00FC # LATIN SMALL LETTER U WITH DIAERESIS
+0xFD U+0169 # LATIN SMALL LETTER U WITH TILDE
+0xFE U+016B # LATIN SMALL LETTER U WITH MACRON
+0xFF U+02D9 # DOT ABOVE
+
+
+0xd0 U+00d0 # Dstrok and ETH are nearly the same...
diff --git a/src/chrtrans/iso05_uni.tbl b/src/chrtrans/iso05_uni.tbl
new file mode 100644
index 0000000..afbb213
--- /dev/null
+++ b/src/chrtrans/iso05_uni.tbl
@@ -0,0 +1,259 @@
+# This file has been modified for lynx (see README.tables)
+
+#The MIME name of this charset.
+Miso-8859-5
+
+#Name as a Display Charset (used on Options screen)
+OCyrillic (ISO-8859-5)
+
+#Codepage number
+C915
+
+#
+# Name: ISO 8859-5:1999 to Unicode
+# Unicode version: 3.0
+# Table version: 1.0
+# Table format: Format A
+# Date: 1999 July 27
+# Authors: Ken Whistler <kenw@sybase.com>
+#
+# Copyright (c) 1991-1999 Unicode, Inc. All Rights reserved.
+#
+# This file is provided as-is by Unicode, Inc. (The Unicode Consortium).
+# No claims are made as to fitness for any particular purpose. No
+# warranties of any kind are expressed or implied. The recipient
+# agrees to determine applicability of information provided. If this
+# file has been provided on optical media by Unicode, Inc., the sole
+# remedy for any claim will be exchange of defective media within 90
+# days of receipt.
+#
+# Unicode, Inc. hereby grants the right to freely use the information
+# supplied in this file in the creation of products supporting the
+# Unicode Standard, and to make copies of this file in any form for
+# internal or external distribution as long as this notice remains
+# attached.
+#
+# General notes:
+#
+# This table contains the data the Unicode Consortium has on how
+# ISO/IEC 8859-5:1999 characters map into Unicode.
+#
+# Format: Three tab-separated columns
+# Column #1 is the ISO/IEC 8859-5 code (in hex as 0xXX)
+# Column #2 is the Unicode (in hex as 0xXXXX)
+# Column #3 the Unicode name (follows a comment sign, '#')
+#
+# The entries are in ISO/IEC 8859-5 order.
+#
+# Version history
+# 1.0 version updates 0.1 version by adding mappings for all
+# control characters.
+#
+# Updated versions of this file may be found in:
+# <ftp://ftp.unicode.org/Public/MAPPINGS/>
+#
+# Any comments or problems, contact <errata@unicode.org>
+# Please note that <errata@unicode.org> is an archival address;
+# notices will be checked, but do not expect an immediate response.
+#
+# Lines with more than one Unicode (U+XXXX) value contain additional
+# replacement mappings added for lynx. - kw
+#
+0x20-0x7e idem
+#
+#0x20 U+0020 # SPACE
+#0x21 U+0021 # EXCLAMATION MARK
+#0x22 U+0022 # QUOTATION MARK
+#0x23 U+0023 # NUMBER SIGN
+#0x24 U+0024 # DOLLAR SIGN
+#0x25 U+0025 # PERCENT SIGN
+#0x26 U+0026 # AMPERSAND
+#0x27 U+0027 # APOSTROPHE
+#0x28 U+0028 # LEFT PARENTHESIS
+#0x29 U+0029 # RIGHT PARENTHESIS
+#0x2A U+002A # ASTERISK
+#0x2B U+002B # PLUS SIGN
+#0x2C U+002C # COMMA
+#0x2D U+002D # HYPHEN-MINUS
+#0x2E U+002E # FULL STOP
+#0x2F U+002F # SOLIDUS
+#0x30 U+0030 # DIGIT ZERO
+#0x31 U+0031 # DIGIT ONE
+#0x32 U+0032 # DIGIT TWO
+#0x33 U+0033 # DIGIT THREE
+#0x34 U+0034 # DIGIT FOUR
+#0x35 U+0035 # DIGIT FIVE
+#0x36 U+0036 # DIGIT SIX
+#0x37 U+0037 # DIGIT SEVEN
+#0x38 U+0038 # DIGIT EIGHT
+#0x39 U+0039 # DIGIT NINE
+#0x3A U+003A # COLON
+#0x3B U+003B # SEMICOLON
+#0x3C U+003C # LESS-THAN SIGN
+#0x3D U+003D # EQUALS SIGN
+#0x3E U+003E # GREATER-THAN SIGN
+#0x3F U+003F # QUESTION MARK
+#0x40 U+0040 # COMMERCIAL AT
+#0x41 U+0041 # LATIN CAPITAL LETTER A
+#0x42 U+0042 # LATIN CAPITAL LETTER B
+#0x43 U+0043 # LATIN CAPITAL LETTER C
+#0x44 U+0044 # LATIN CAPITAL LETTER D
+#0x45 U+0045 # LATIN CAPITAL LETTER E
+#0x46 U+0046 # LATIN CAPITAL LETTER F
+#0x47 U+0047 # LATIN CAPITAL LETTER G
+#0x48 U+0048 # LATIN CAPITAL LETTER H
+#0x49 U+0049 # LATIN CAPITAL LETTER I
+#0x4A U+004A # LATIN CAPITAL LETTER J
+#0x4B U+004B # LATIN CAPITAL LETTER K
+#0x4C U+004C # LATIN CAPITAL LETTER L
+#0x4D U+004D # LATIN CAPITAL LETTER M
+#0x4E U+004E # LATIN CAPITAL LETTER N
+#0x4F U+004F # LATIN CAPITAL LETTER O
+#0x50 U+0050 # LATIN CAPITAL LETTER P
+#0x51 U+0051 # LATIN CAPITAL LETTER Q
+#0x52 U+0052 # LATIN CAPITAL LETTER R
+#0x53 U+0053 # LATIN CAPITAL LETTER S
+#0x54 U+0054 # LATIN CAPITAL LETTER T
+#0x55 U+0055 # LATIN CAPITAL LETTER U
+#0x56 U+0056 # LATIN CAPITAL LETTER V
+#0x57 U+0057 # LATIN CAPITAL LETTER W
+#0x58 U+0058 # LATIN CAPITAL LETTER X
+#0x59 U+0059 # LATIN CAPITAL LETTER Y
+#0x5A U+005A # LATIN CAPITAL LETTER Z
+#0x5B U+005B # LEFT SQUARE BRACKET
+#0x5C U+005C # REVERSE SOLIDUS
+#0x5D U+005D # RIGHT SQUARE BRACKET
+#0x5E U+005E # CIRCUMFLEX ACCENT
+#0x5F U+005F # LOW LINE
+#0x60 U+0060 # GRAVE ACCENT
+#0x61 U+0061 # LATIN SMALL LETTER A
+#0x62 U+0062 # LATIN SMALL LETTER B
+#0x63 U+0063 # LATIN SMALL LETTER C
+#0x64 U+0064 # LATIN SMALL LETTER D
+#0x65 U+0065 # LATIN SMALL LETTER E
+#0x66 U+0066 # LATIN SMALL LETTER F
+#0x67 U+0067 # LATIN SMALL LETTER G
+#0x68 U+0068 # LATIN SMALL LETTER H
+#0x69 U+0069 # LATIN SMALL LETTER I
+#0x6A U+006A # LATIN SMALL LETTER J
+#0x6B U+006B # LATIN SMALL LETTER K
+#0x6C U+006C # LATIN SMALL LETTER L
+#0x6D U+006D # LATIN SMALL LETTER M
+#0x6E U+006E # LATIN SMALL LETTER N
+#0x6F U+006F # LATIN SMALL LETTER O
+#0x70 U+0070 # LATIN SMALL LETTER P
+#0x71 U+0071 # LATIN SMALL LETTER Q
+#0x72 U+0072 # LATIN SMALL LETTER R
+#0x73 U+0073 # LATIN SMALL LETTER S
+#0x74 U+0074 # LATIN SMALL LETTER T
+#0x75 U+0075 # LATIN SMALL LETTER U
+#0x76 U+0076 # LATIN SMALL LETTER V
+#0x77 U+0077 # LATIN SMALL LETTER W
+#0x78 U+0078 # LATIN SMALL LETTER X
+#0x79 U+0079 # LATIN SMALL LETTER Y
+#0x7A U+007A # LATIN SMALL LETTER Z
+#0x7B U+007B # LEFT CURLY BRACKET
+#0x7C U+007C # VERTICAL LINE
+#0x7D U+007D # RIGHT CURLY BRACKET
+#0x7E U+007E # TILDE
+0xA0 U+00A0 # NO-BREAK SPACE
+0xA1 U+0401 # CYRILLIC CAPITAL LETTER IO
+0xA2 U+0402 # CYRILLIC CAPITAL LETTER DJE
+0xA3 U+0403 # CYRILLIC CAPITAL LETTER GJE
+0xA4 U+0404 # CYRILLIC CAPITAL LETTER UKRAINIAN IE
+0xA5 U+0405 # CYRILLIC CAPITAL LETTER DZE
+0xA6 U+0406 U+0130 # CYRILLIC CAPITAL LETTER BYELORUSSIAN-UKRAINIAN I
+0xA7 U+0407 U+03AA # CYRILLIC CAPITAL LETTER YI
+0xA8 U+0408 # CYRILLIC CAPITAL LETTER JE
+0xA9 U+0409 # CYRILLIC CAPITAL LETTER LJE
+0xAA U+040A # CYRILLIC CAPITAL LETTER NJE
+0xAB U+040B # CYRILLIC CAPITAL LETTER TSHE
+0xAC U+040C # CYRILLIC CAPITAL LETTER KJE
+0xAD U+00AD # SOFT HYPHEN
+0xAE U+040E # CYRILLIC CAPITAL LETTER SHORT U
+0xAF U+040F # CYRILLIC CAPITAL LETTER DZHE
+0xB0 U+0410 # CYRILLIC CAPITAL LETTER A
+0xB1 U+0411 # CYRILLIC CAPITAL LETTER BE
+0xB2 U+0412 # CYRILLIC CAPITAL LETTER VE
+0xB3 U+0413 U+0393 # CYRILLIC CAPITAL LETTER GHE
+0xB4 U+0414 # CYRILLIC CAPITAL LETTER DE
+0xB5 U+0415 # CYRILLIC CAPITAL LETTER IE
+0xB6 U+0416 U+017d # CYRILLIC CAPITAL LETTER ZHE
+0xB7 U+0417 # CYRILLIC CAPITAL LETTER ZE
+0xB8 U+0418 # CYRILLIC CAPITAL LETTER I
+0xB9 U+0419 # CYRILLIC CAPITAL LETTER SHORT I
+0xBA U+041A # CYRILLIC CAPITAL LETTER KA
+0xBB U+041B U+039b # CYRILLIC CAPITAL LETTER EL
+0xBC U+041C # CYRILLIC CAPITAL LETTER EM
+0xBD U+041D # CYRILLIC CAPITAL LETTER EN
+0xBE U+041E # CYRILLIC CAPITAL LETTER O
+0xBF U+041F U+03a0 # CYRILLIC CAPITAL LETTER PE
+0xC0 U+0420 # CYRILLIC CAPITAL LETTER ER
+0xC1 U+0421 # CYRILLIC CAPITAL LETTER ES
+0xC2 U+0422 # CYRILLIC CAPITAL LETTER TE
+0xC3 U+0423 # CYRILLIC CAPITAL LETTER U
+0xC4 U+0424 U+03a6 # CYRILLIC CAPITAL LETTER EF
+0xC5 U+0425 # CYRILLIC CAPITAL LETTER HA
+0xC6 U+0426 # CYRILLIC CAPITAL LETTER TSE
+0xC7 U+0427 U+010c # CYRILLIC CAPITAL LETTER CHE
+0xC8 U+0428 U+0160 # CYRILLIC CAPITAL LETTER SHA
+0xC9 U+0429 # CYRILLIC CAPITAL LETTER SHCHA
+0xCA U+042A # CYRILLIC CAPITAL LETTER HARD SIGN
+0xCB U+042B # CYRILLIC CAPITAL LETTER YERU
+0xCC U+042C # CYRILLIC CAPITAL LETTER SOFT SIGN
+0xCD U+042D # CYRILLIC CAPITAL LETTER E
+0xCE U+042E # CYRILLIC CAPITAL LETTER YU
+0xCF U+042F # CYRILLIC CAPITAL LETTER YA
+0xD0 U+0430 # CYRILLIC SMALL LETTER A
+0xD1 U+0431 # CYRILLIC SMALL LETTER BE
+0xD2 U+0432 # CYRILLIC SMALL LETTER VE
+0xD3 U+0433 # CYRILLIC SMALL LETTER GHE
+0xD4 U+0434 # CYRILLIC SMALL LETTER DE
+0xD5 U+0435 # CYRILLIC SMALL LETTER IE
+0xD6 U+0436 U+017e # CYRILLIC SMALL LETTER ZHE
+0xD7 U+0437 # CYRILLIC SMALL LETTER ZE
+0xD8 U+0438 # CYRILLIC SMALL LETTER I
+0xD9 U+0439 # CYRILLIC SMALL LETTER SHORT I
+0xDA U+043A # CYRILLIC SMALL LETTER KA
+0xDB U+043B U+03bb # CYRILLIC SMALL LETTER EL
+0xDC U+043C # CYRILLIC SMALL LETTER EM
+0xDD U+043D # CYRILLIC SMALL LETTER EN
+0xDE U+043E # CYRILLIC SMALL LETTER O
+0xDF U+043F U+03c0 # CYRILLIC SMALL LETTER PE
+0xE0 U+0440 # CYRILLIC SMALL LETTER ER
+0xE1 U+0441 # CYRILLIC SMALL LETTER ES
+0xE2 U+0442 # CYRILLIC SMALL LETTER TE
+0xE3 U+0443 # CYRILLIC SMALL LETTER U
+0xE4 U+0444 U+03c6 # CYRILLIC SMALL LETTER EF
+0xE5 U+0445 # CYRILLIC SMALL LETTER HA
+0xE6 U+0446 # CYRILLIC SMALL LETTER TSE
+0xE7 U+0447 U+010d # CYRILLIC SMALL LETTER CHE
+0xE8 U+0448 U+0161 # CYRILLIC SMALL LETTER SHA
+0xE9 U+0449 # CYRILLIC SMALL LETTER SHCHA
+0xEA U+044A # CYRILLIC SMALL LETTER HARD SIGN
+0xEB U+044B U+0131 # CYRILLIC SMALL LETTER YERU
+0xEC U+044C # CYRILLIC SMALL LETTER SOFT SIGN
+0xED U+044D # CYRILLIC SMALL LETTER E
+0xEE U+044E # CYRILLIC SMALL LETTER YU
+0xEF U+044F # CYRILLIC SMALL LETTER YA
+0xF0 U+2116 # NUMERO SIGN
+0xF1 U+0451 # CYRILLIC SMALL LETTER IO
+0xF2 U+0452 # CYRILLIC SMALL LETTER DJE
+0xF3 U+0453 # CYRILLIC SMALL LETTER GJE
+0xF4 U+0454 # CYRILLIC SMALL LETTER UKRAINIAN IE
+0xF5 U+0455 # CYRILLIC SMALL LETTER DZE
+0xF6 U+0456 # CYRILLIC SMALL LETTER BYELORUSSIAN-UKRAINIAN I
+0xF7 U+0457 U+03CA # CYRILLIC SMALL LETTER YI
+0xF8 U+0458 # CYRILLIC SMALL LETTER JE
+0xF9 U+0459 # CYRILLIC SMALL LETTER LJE
+0xFA U+045A # CYRILLIC SMALL LETTER NJE
+0xFB U+045B # CYRILLIC SMALL LETTER TSHE
+0xFC U+045C # CYRILLIC SMALL LETTER KJE
+0xFD U+00A7 # SECTION SIGN
+0xFE U+045E # CYRILLIC SMALL LETTER SHORT U
+0xFF U+045F # CYRILLIC SMALL LETTER DZHE
+
+U+0400 "`\265"
+U+040d "`\270"
+U+0450 "`\325"
+U+045d "`\330"
diff --git a/src/chrtrans/iso06_uni.tbl b/src/chrtrans/iso06_uni.tbl
new file mode 100644
index 0000000..e4ef995
--- /dev/null
+++ b/src/chrtrans/iso06_uni.tbl
@@ -0,0 +1,208 @@
+# This file has been modified for lynx (see README.tables)
+
+#The MIME name of this charset.
+Miso-8859-6
+
+#Name as a Display Charset (used on Options screen).
+OArabic (ISO-8859-6)
+
+#Codepage number
+C1089
+
+#
+# Name: ISO 8859-6:1999 to Unicode
+# Unicode version: 3.0
+# Table version: 1.0
+# Table format: Format A
+# Date: 1999 July 27
+# Authors: Ken Whistler <kenw@sybase.com>
+#
+# Copyright (c) 1991-1999 Unicode, Inc. All Rights reserved.
+#
+# This file is provided as-is by Unicode, Inc. (The Unicode Consortium).
+# No claims are made as to fitness for any particular purpose. No
+# warranties of any kind are expressed or implied. The recipient
+# agrees to determine applicability of information provided. If this
+# file has been provided on optical media by Unicode, Inc., the sole
+# remedy for any claim will be exchange of defective media within 90
+# days of receipt.
+#
+# Unicode, Inc. hereby grants the right to freely use the information
+# supplied in this file in the creation of products supporting the
+# Unicode Standard, and to make copies of this file in any form for
+# internal or external distribution as long as this notice remains
+# attached.
+#
+# General notes:
+#
+# This table contains the data the Unicode Consortium has on how
+# ISO/IEC 8859-6:1999 characters map into Unicode.
+#
+# Format: Three tab-separated columns
+# Column #1 is the ISO/IEC 8859-6 code (in hex as 0xXX)
+# Column #2 is the Unicode (in hex as 0xXXXX)
+# Column #3 the Unicode name (follows a comment sign, '#')
+#
+# The entries are in ISO/IEC 8859-6 order.
+#
+# Version history
+# 1.0 version updates 0.1 version by adding mappings for all
+# control characters.
+# 0x30..0x39 remapped to the ASCII digits (U+0030..U+0039) instead
+# of the Arabic digits (U+0660..U+0669).
+#
+# Updated versions of this file may be found in:
+# <ftp://ftp.unicode.org/Public/MAPPINGS/>
+#
+# Any comments or problems, contact <errata@unicode.org>
+# Please note that <errata@unicode.org> is an archival address;
+# notices will be checked, but do not expect an immediate response.
+#
+0x20-0x7e idem
+#
+#0x20 U+0020 # SPACE
+#0x21 U+0021 # EXCLAMATION MARK
+#0x22 U+0022 # QUOTATION MARK
+#0x23 U+0023 # NUMBER SIGN
+#0x24 U+0024 # DOLLAR SIGN
+#0x25 U+0025 # PERCENT SIGN
+#0x26 U+0026 # AMPERSAND
+#0x27 U+0027 # APOSTROPHE
+#0x28 U+0028 # LEFT PARENTHESIS
+#0x29 U+0029 # RIGHT PARENTHESIS
+#0x2A U+002A # ASTERISK
+#0x2B U+002B # PLUS SIGN
+#0x2C U+002C # COMMA
+#0x2D U+002D # HYPHEN-MINUS
+#0x2E U+002E # FULL STOP
+#0x2F U+002F # SOLIDUS
+#0x30 U+0030 # DIGIT ZERO
+#0x31 U+0031 # DIGIT ONE
+#0x32 U+0032 # DIGIT TWO
+#0x33 U+0033 # DIGIT THREE
+#0x34 U+0034 # DIGIT FOUR
+#0x35 U+0035 # DIGIT FIVE
+#0x36 U+0036 # DIGIT SIX
+#0x37 U+0037 # DIGIT SEVEN
+#0x38 U+0038 # DIGIT EIGHT
+#0x39 U+0039 # DIGIT NINE
+#0x3A U+003A # COLON
+#0x3B U+003B # SEMICOLON
+#0x3C U+003C # LESS-THAN SIGN
+#0x3D U+003D # EQUALS SIGN
+#0x3E U+003E # GREATER-THAN SIGN
+#0x3F U+003F # QUESTION MARK
+#0x40 U+0040 # COMMERCIAL AT
+#0x41 U+0041 # LATIN CAPITAL LETTER A
+#0x42 U+0042 # LATIN CAPITAL LETTER B
+#0x43 U+0043 # LATIN CAPITAL LETTER C
+#0x44 U+0044 # LATIN CAPITAL LETTER D
+#0x45 U+0045 # LATIN CAPITAL LETTER E
+#0x46 U+0046 # LATIN CAPITAL LETTER F
+#0x47 U+0047 # LATIN CAPITAL LETTER G
+#0x48 U+0048 # LATIN CAPITAL LETTER H
+#0x49 U+0049 # LATIN CAPITAL LETTER I
+#0x4A U+004A # LATIN CAPITAL LETTER J
+#0x4B U+004B # LATIN CAPITAL LETTER K
+#0x4C U+004C # LATIN CAPITAL LETTER L
+#0x4D U+004D # LATIN CAPITAL LETTER M
+#0x4E U+004E # LATIN CAPITAL LETTER N
+#0x4F U+004F # LATIN CAPITAL LETTER O
+#0x50 U+0050 # LATIN CAPITAL LETTER P
+#0x51 U+0051 # LATIN CAPITAL LETTER Q
+#0x52 U+0052 # LATIN CAPITAL LETTER R
+#0x53 U+0053 # LATIN CAPITAL LETTER S
+#0x54 U+0054 # LATIN CAPITAL LETTER T
+#0x55 U+0055 # LATIN CAPITAL LETTER U
+#0x56 U+0056 # LATIN CAPITAL LETTER V
+#0x57 U+0057 # LATIN CAPITAL LETTER W
+#0x58 U+0058 # LATIN CAPITAL LETTER X
+#0x59 U+0059 # LATIN CAPITAL LETTER Y
+#0x5A U+005A # LATIN CAPITAL LETTER Z
+#0x5B U+005B # LEFT SQUARE BRACKET
+#0x5C U+005C # REVERSE SOLIDUS
+#0x5D U+005D # RIGHT SQUARE BRACKET
+#0x5E U+005E # CIRCUMFLEX ACCENT
+#0x5F U+005F # LOW LINE
+#0x60 U+0060 # GRAVE ACCENT
+#0x61 U+0061 # LATIN SMALL LETTER A
+#0x62 U+0062 # LATIN SMALL LETTER B
+#0x63 U+0063 # LATIN SMALL LETTER C
+#0x64 U+0064 # LATIN SMALL LETTER D
+#0x65 U+0065 # LATIN SMALL LETTER E
+#0x66 U+0066 # LATIN SMALL LETTER F
+#0x67 U+0067 # LATIN SMALL LETTER G
+#0x68 U+0068 # LATIN SMALL LETTER H
+#0x69 U+0069 # LATIN SMALL LETTER I
+#0x6A U+006A # LATIN SMALL LETTER J
+#0x6B U+006B # LATIN SMALL LETTER K
+#0x6C U+006C # LATIN SMALL LETTER L
+#0x6D U+006D # LATIN SMALL LETTER M
+#0x6E U+006E # LATIN SMALL LETTER N
+#0x6F U+006F # LATIN SMALL LETTER O
+#0x70 U+0070 # LATIN SMALL LETTER P
+#0x71 U+0071 # LATIN SMALL LETTER Q
+#0x72 U+0072 # LATIN SMALL LETTER R
+#0x73 U+0073 # LATIN SMALL LETTER S
+#0x74 U+0074 # LATIN SMALL LETTER T
+#0x75 U+0075 # LATIN SMALL LETTER U
+#0x76 U+0076 # LATIN SMALL LETTER V
+#0x77 U+0077 # LATIN SMALL LETTER W
+#0x78 U+0078 # LATIN SMALL LETTER X
+#0x79 U+0079 # LATIN SMALL LETTER Y
+#0x7A U+007A # LATIN SMALL LETTER Z
+#0x7B U+007B # LEFT CURLY BRACKET
+#0x7C U+007C # VERTICAL LINE
+#0x7D U+007D # RIGHT CURLY BRACKET
+#0x7E U+007E # TILDE
+0xA0 U+00A0 # NO-BREAK SPACE
+0xA4 U+00A4 # CURRENCY SIGN
+0xAC U+060C # ARABIC COMMA
+0xAD U+00AD # SOFT HYPHEN
+0xBB U+061B # ARABIC SEMICOLON
+0xBF U+061F # ARABIC QUESTION MARK
+0xC1 U+0621 # ARABIC LETTER HAMZA
+0xC2 U+0622 # ARABIC LETTER ALEF WITH MADDA ABOVE
+0xC3 U+0623 # ARABIC LETTER ALEF WITH HAMZA ABOVE
+0xC4 U+0624 # ARABIC LETTER WAW WITH HAMZA ABOVE
+0xC5 U+0625 # ARABIC LETTER ALEF WITH HAMZA BELOW
+0xC6 U+0626 # ARABIC LETTER YEH WITH HAMZA ABOVE
+0xC7 U+0627 # ARABIC LETTER ALEF
+0xC8 U+0628 # ARABIC LETTER BEH
+0xC9 U+0629 # ARABIC LETTER TEH MARBUTA
+0xCA U+062A # ARABIC LETTER TEH
+0xCB U+062B # ARABIC LETTER THEH
+0xCC U+062C # ARABIC LETTER JEEM
+0xCD U+062D # ARABIC LETTER HAH
+0xCE U+062E # ARABIC LETTER KHAH
+0xCF U+062F # ARABIC LETTER DAL
+0xD0 U+0630 # ARABIC LETTER THAL
+0xD1 U+0631 # ARABIC LETTER REH
+0xD2 U+0632 # ARABIC LETTER ZAIN
+0xD3 U+0633 # ARABIC LETTER SEEN
+0xD4 U+0634 # ARABIC LETTER SHEEN
+0xD5 U+0635 # ARABIC LETTER SAD
+0xD6 U+0636 # ARABIC LETTER DAD
+0xD7 U+0637 # ARABIC LETTER TAH
+0xD8 U+0638 # ARABIC LETTER ZAH
+0xD9 U+0639 # ARABIC LETTER AIN
+0xDA U+063A # ARABIC LETTER GHAIN
+0xE0 U+0640 # ARABIC TATWEEL
+0xE1 U+0641 # ARABIC LETTER FEH
+0xE2 U+0642 # ARABIC LETTER QAF
+0xE3 U+0643 # ARABIC LETTER KAF
+0xE4 U+0644 # ARABIC LETTER LAM
+0xE5 U+0645 # ARABIC LETTER MEEM
+0xE6 U+0646 # ARABIC LETTER NOON
+0xE7 U+0647 # ARABIC LETTER HEH
+0xE8 U+0648 # ARABIC LETTER WAW
+0xE9 U+0649 # ARABIC LETTER ALEF MAKSURA
+0xEA U+064A # ARABIC LETTER YEH
+0xEB U+064B # ARABIC FATHATAN
+0xEC U+064C # ARABIC DAMMATAN
+0xED U+064D # ARABIC KASRATAN
+0xEE U+064E # ARABIC FATHA
+0xEF U+064F # ARABIC DAMMA
+0xF0 U+0650 # ARABIC KASRA
+0xF1 U+0651 # ARABIC SHADDA
+0xF2 U+0652 # ARABIC SUKUN
diff --git a/src/chrtrans/iso07_uni.tbl b/src/chrtrans/iso07_uni.tbl
new file mode 100644
index 0000000..87372a9
--- /dev/null
+++ b/src/chrtrans/iso07_uni.tbl
@@ -0,0 +1,275 @@
+# This file has been modified for lynx (see README.tables)
+
+#The MIME name of this charset.
+Miso-8859-7
+
+#Name as a Display Charset (used on Options screen)
+OGreek (ISO-8859-7)
+
+#Codepage number
+C813
+
+#
+# Name: ISO 8859-7:2003 to Unicode
+# Unicode version: 4.0
+# Table version: 2.0
+# Table format: Format A
+# Date: 2003-Nov-12
+# Authors: Ken Whistler <kenw@sybase.com>
+#
+# Copyright (c) 1991-2003 Unicode, Inc. All Rights reserved.
+#
+# This file is provided as-is by Unicode, Inc. (The Unicode Consortium).
+# No claims are made as to fitness for any particular purpose. No
+# warranties of any kind are expressed or implied. The recipient
+# agrees to determine applicability of information provided. If this
+# file has been provided on optical media by Unicode, Inc., the sole
+# remedy for any claim will be exchange of defective media within 90
+# days of receipt.
+#
+# Unicode, Inc. hereby grants the right to freely use the information
+# supplied in this file in the creation of products supporting the
+# Unicode Standard, and to make copies of this file in any form for
+# internal or external distribution as long as this notice remains
+# attached.
+#
+# General notes:
+#
+# This table contains the data the Unicode Consortium has on how
+# ISO 8859-7:2003 characters map into Unicode.
+#
+# ISO 8859-7:1987 is equivalent to ISO-IR-126, ELOT 928,
+# and ECMA 118. ISO 8859-7:2003 adds two currency signs
+# and one other character not in the earlier standard.
+#
+# Format: Three tab-separated columns
+# Column #1 is the ISO 8859-7 code (in hex as 0xXX)
+# Column #2 is the Unicode (in hex as 0xXXXX)
+# Column #3 the Unicode name (follows a comment sign, '#')
+#
+# The entries are in ISO 8859-7 order.
+#
+# Version history
+# 1.0 version updates 0.1 version by adding mappings for all
+# control characters.
+# Remap 0xA1 to U+2018 (instead of 0x02BD) to match text of 8859-7
+# Remap 0xA2 to U+2019 (instead of 0x02BC) to match text of 8859-7
+#
+# 2.0 version updates 1.0 version by adding mappings for the
+# three newly added characters 0xA4, 0xA5, 0xAA.
+#
+# Updated versions of this file may be found in:
+# <http://www.unicode.org/Public/MAPPINGS/>
+#
+# Any comments or problems, contact the Unicode Consortium at:
+# <http://www.unicode.org/reporting.html>
+#
+# Lines with more than one Unicode (U+XXXX) value contain additional
+# replacement mappings added for lynx. - kw
+#
+0x20-0x7e idem
+#
+#0x20 U+0020 # SPACE
+#0x21 U+0021 # EXCLAMATION MARK
+#0x22 U+0022 # QUOTATION MARK
+#0x23 U+0023 # NUMBER SIGN
+#0x24 U+0024 # DOLLAR SIGN
+#0x25 U+0025 # PERCENT SIGN
+#0x26 U+0026 # AMPERSAND
+#0x27 U+0027 # APOSTROPHE
+#0x28 U+0028 # LEFT PARENTHESIS
+#0x29 U+0029 # RIGHT PARENTHESIS
+#0x2A U+002A # ASTERISK
+#0x2B U+002B # PLUS SIGN
+#0x2C U+002C # COMMA
+#0x2D U+002D # HYPHEN-MINUS
+#0x2E U+002E # FULL STOP
+#0x2F U+002F # SOLIDUS
+#0x30 U+0030 # DIGIT ZERO
+#0x31 U+0031 # DIGIT ONE
+#0x32 U+0032 # DIGIT TWO
+#0x33 U+0033 # DIGIT THREE
+#0x34 U+0034 # DIGIT FOUR
+#0x35 U+0035 # DIGIT FIVE
+#0x36 U+0036 # DIGIT SIX
+#0x37 U+0037 # DIGIT SEVEN
+#0x38 U+0038 # DIGIT EIGHT
+#0x39 U+0039 # DIGIT NINE
+#0x3A U+003A # COLON
+#0x3B U+003B # SEMICOLON
+#0x3C U+003C # LESS-THAN SIGN
+#0x3D U+003D # EQUALS SIGN
+#0x3E U+003E # GREATER-THAN SIGN
+#0x3F U+003F # QUESTION MARK
+#0x40 U+0040 # COMMERCIAL AT
+#0x41 U+0041 # LATIN CAPITAL LETTER A
+#0x42 U+0042 # LATIN CAPITAL LETTER B
+#0x43 U+0043 # LATIN CAPITAL LETTER C
+#0x44 U+0044 # LATIN CAPITAL LETTER D
+#0x45 U+0045 # LATIN CAPITAL LETTER E
+#0x46 U+0046 # LATIN CAPITAL LETTER F
+#0x47 U+0047 # LATIN CAPITAL LETTER G
+#0x48 U+0048 # LATIN CAPITAL LETTER H
+#0x49 U+0049 # LATIN CAPITAL LETTER I
+#0x4A U+004A # LATIN CAPITAL LETTER J
+#0x4B U+004B # LATIN CAPITAL LETTER K
+#0x4C U+004C # LATIN CAPITAL LETTER L
+#0x4D U+004D # LATIN CAPITAL LETTER M
+#0x4E U+004E # LATIN CAPITAL LETTER N
+#0x4F U+004F # LATIN CAPITAL LETTER O
+#0x50 U+0050 # LATIN CAPITAL LETTER P
+#0x51 U+0051 # LATIN CAPITAL LETTER Q
+#0x52 U+0052 # LATIN CAPITAL LETTER R
+#0x53 U+0053 # LATIN CAPITAL LETTER S
+#0x54 U+0054 # LATIN CAPITAL LETTER T
+#0x55 U+0055 # LATIN CAPITAL LETTER U
+#0x56 U+0056 # LATIN CAPITAL LETTER V
+#0x57 U+0057 # LATIN CAPITAL LETTER W
+#0x58 U+0058 # LATIN CAPITAL LETTER X
+#0x59 U+0059 # LATIN CAPITAL LETTER Y
+#0x5A U+005A # LATIN CAPITAL LETTER Z
+#0x5B U+005B # LEFT SQUARE BRACKET
+#0x5C U+005C # REVERSE SOLIDUS
+#0x5D U+005D # RIGHT SQUARE BRACKET
+#0x5E U+005E # CIRCUMFLEX ACCENT
+#0x5F U+005F # LOW LINE
+#0x60 U+0060 # GRAVE ACCENT
+#0x61 U+0061 # LATIN SMALL LETTER A
+#0x62 U+0062 # LATIN SMALL LETTER B
+#0x63 U+0063 # LATIN SMALL LETTER C
+#0x64 U+0064 # LATIN SMALL LETTER D
+#0x65 U+0065 # LATIN SMALL LETTER E
+#0x66 U+0066 # LATIN SMALL LETTER F
+#0x67 U+0067 # LATIN SMALL LETTER G
+#0x68 U+0068 # LATIN SMALL LETTER H
+#0x69 U+0069 # LATIN SMALL LETTER I
+#0x6A U+006A # LATIN SMALL LETTER J
+#0x6B U+006B # LATIN SMALL LETTER K
+#0x6C U+006C # LATIN SMALL LETTER L
+#0x6D U+006D # LATIN SMALL LETTER M
+#0x6E U+006E # LATIN SMALL LETTER N
+#0x6F U+006F # LATIN SMALL LETTER O
+#0x70 U+0070 # LATIN SMALL LETTER P
+#0x71 U+0071 # LATIN SMALL LETTER Q
+#0x72 U+0072 # LATIN SMALL LETTER R
+#0x73 U+0073 # LATIN SMALL LETTER S
+#0x74 U+0074 # LATIN SMALL LETTER T
+#0x75 U+0075 # LATIN SMALL LETTER U
+#0x76 U+0076 # LATIN SMALL LETTER V
+#0x77 U+0077 # LATIN SMALL LETTER W
+#0x78 U+0078 # LATIN SMALL LETTER X
+#0x79 U+0079 # LATIN SMALL LETTER Y
+#0x7A U+007A # LATIN SMALL LETTER Z
+#0x7B U+007B # LEFT CURLY BRACKET
+#0x7C U+007C # VERTICAL LINE
+#0x7D U+007D # RIGHT CURLY BRACKET
+#0x7E U+007E # TILDE
+0xA0 U+00A0 # NO-BREAK SPACE
+#
+# The following two changed in ISO 8859:1999
+#
+# Remap 0xA1 to U+2018 (instead of U+02BD)
+# Remap 0xA2 to U+2019 (instead of U+02BC)
+#
+# Keep the old ones as primary for now. Also added old U+037[12]
+# found in existing linux kbd files and in RFC 1345 for compatibility.
+# - kw 1999-10-29
+0xA1 U+02BD U+2018 U+0371 # MODIFIER LETTER REVERSED COMMA
+0xA2 U+02BC U+2019 U+0372 # MODIFIER LETTER APOSTROPHE
+0xA3 U+00A3 # POUND SIGN
+0xA4 U+20AC # EURO SIGN
+0xA5 U+20AF # DRACHMA SIGN
+0xA6 U+00A6 # BROKEN BAR
+0xA7 U+00A7 # SECTION SIGN
+0xA8 U+00A8 # DIAERESIS
+0xA9 U+00A9 # COPYRIGHT SIGN
+0xAA U+037A # GREEK YPOGEGRAMMENI
+0xAB U+00AB # LEFT-POINTING DOUBLE ANGLE QUOTATION MARK
+0xAC U+00AC # NOT SIGN
+0xAD U+00AD # SOFT HYPHEN
+0xAF U+2015 # HORIZONTAL BAR
+0xB0 U+00B0 # DEGREE SIGN
+0xB1 U+00B1 # PLUS-MINUS SIGN
+0xB2 U+00B2 # SUPERSCRIPT TWO
+0xB3 U+00B3 # SUPERSCRIPT THREE
+0xB4 U+0384 # GREEK TONOS
+0xB5 U+0385 # GREEK DIALYTIKA TONOS
+0xB6 U+0386 U+1fbb # GREEK CAPITAL LETTER ALPHA WITH TONOS
+0xB7 U+00B7 U+0307 U+0387 U+2027 # MIDDLE DOT
+0xB8 U+0388 U+1fc9 # GREEK CAPITAL LETTER EPSILON WITH TONOS
+0xB9 U+0389 U+1fcb # GREEK CAPITAL LETTER ETA WITH TONOS
+0xBA U+038A U+1fdb # GREEK CAPITAL LETTER IOTA WITH TONOS
+0xBB U+00BB # RIGHT-POINTING DOUBLE ANGLE QUOTATION MARK
+0xBC U+038C U+1ff9 # GREEK CAPITAL LETTER OMICRON WITH TONOS
+0xBD U+00BD # VULGAR FRACTION ONE HALF
+0xBE U+038E U+1feb # GREEK CAPITAL LETTER UPSILON WITH TONOS
+0xBF U+038F U+1ffb # GREEK CAPITAL LETTER OMEGA WITH TONOS
+0xC0 U+0390 # GREEK SMALL LETTER IOTA WITH DIALYTIKA AND TONOS
+0xC1 U+0391 # GREEK CAPITAL LETTER ALPHA
+0xC2 U+0392 # GREEK CAPITAL LETTER BETA
+0xC3 U+0393 U+0413 # GREEK CAPITAL LETTER GAMMA
+0xC4 U+0394 # GREEK CAPITAL LETTER DELTA
+0xC5 U+0395 # GREEK CAPITAL LETTER EPSILON
+0xC6 U+0396 # GREEK CAPITAL LETTER ZETA
+0xC7 U+0397 # GREEK CAPITAL LETTER ETA
+0xC8 U+0398 # GREEK CAPITAL LETTER THETA
+0xC9 U+0399 # GREEK CAPITAL LETTER IOTA
+0xCA U+039A # GREEK CAPITAL LETTER KAPPA
+0xCB U+039B U+041b # GREEK CAPITAL LETTER LAMDA
+0xCC U+039C # GREEK CAPITAL LETTER MU
+0xCD U+039D # GREEK CAPITAL LETTER NU
+0xCE U+039E # GREEK CAPITAL LETTER XI
+0xCF U+039F # GREEK CAPITAL LETTER OMICRON
+0xD0 U+03A0 U+041f # GREEK CAPITAL LETTER PI
+0xD1 U+03A1 # GREEK CAPITAL LETTER RHO
+0xD3 U+03A3 # GREEK CAPITAL LETTER SIGMA
+0xD4 U+03A4 # GREEK CAPITAL LETTER TAU
+0xD5 U+03A5 # GREEK CAPITAL LETTER UPSILON
+0xD6 U+03A6 U+0424 # GREEK CAPITAL LETTER PHI
+0xD7 U+03A7 U+0425 # GREEK CAPITAL LETTER CHI
+0xD8 U+03A8 # GREEK CAPITAL LETTER PSI
+0xD9 U+03A9 # GREEK CAPITAL LETTER OMEGA
+0xDA U+03AA # GREEK CAPITAL LETTER IOTA WITH DIALYTIKA
+0xDB U+03AB # GREEK CAPITAL LETTER UPSILON WITH DIALYTIKA
+0xDC U+03AC U+1f71 # GREEK SMALL LETTER ALPHA WITH TONOS
+0xDD U+03AD U+1f73 # GREEK SMALL LETTER EPSILON WITH TONOS
+0xDE U+03AE U+1f75 # GREEK SMALL LETTER ETA WITH TONOS
+0xDF U+03AF U+1f77 # GREEK SMALL LETTER IOTA WITH TONOS
+0xE0 U+03B0 # GREEK SMALL LETTER UPSILON WITH DIALYTIKA AND TONOS
+0xE1 U+03B1 # GREEK SMALL LETTER ALPHA
+0xE2 U+03B2 # GREEK SMALL LETTER BETA
+0xE3 U+03B3 U+0263 # GREEK SMALL LETTER GAMMA
+0xE4 U+03B4 U+00f0 # GREEK SMALL LETTER DELTA
+0xE5 U+03B5 # GREEK SMALL LETTER EPSILON
+0xE6 U+03B6 # GREEK SMALL LETTER ZETA
+0xE7 U+03B7 # GREEK SMALL LETTER ETA
+0xE8 U+03B8 # GREEK SMALL LETTER THETA
+0xE9 U+03B9 U+0131 # GREEK SMALL LETTER IOTA
+0xEA U+03BA # GREEK SMALL LETTER KAPPA
+0xEB U+03BB # GREEK SMALL LETTER LAMDA
+0xEC U+03BC U+00b5 # GREEK SMALL LETTER MU
+0xED U+03BD # GREEK SMALL LETTER NU
+0xEE U+03BE # GREEK SMALL LETTER XI
+0xEF U+03BF # GREEK SMALL LETTER OMICRON
+0xF0 U+03C0 # GREEK SMALL LETTER PI
+0xF1 U+03C1 # GREEK SMALL LETTER RHO
+0xF2 U+03C2 # GREEK SMALL LETTER FINAL SIGMA
+0xF3 U+03C3 # GREEK SMALL LETTER SIGMA
+0xF4 U+03C4 # GREEK SMALL LETTER TAU
+0xF5 U+03C5 U+028a # GREEK SMALL LETTER UPSILON
+0xF6 U+03C6 # GREEK SMALL LETTER PHI
+0xF7 U+03C7 # GREEK SMALL LETTER CHI
+0xF8 U+03C8 # GREEK SMALL LETTER PSI
+0xF9 U+03C9 # GREEK SMALL LETTER OMEGA
+0xFA U+03CA # GREEK SMALL LETTER IOTA WITH DIALYTIKA
+0xFB U+03CB U+00fc # GREEK SMALL LETTER UPSILON WITH DIALYTIKA
+0xFC U+03CC U+1f79 # GREEK SMALL LETTER OMICRON WITH TONOS
+0xFD U+03CD U+1f7b # GREEK SMALL LETTER UPSILON WITH TONOS
+0xFE U+03CE U+1f7d # GREEK SMALL LETTER OMEGA WITH TONOS
+
+U+2218 " \260 " # RING OPERATOR
+U+2209 " !\345 "
+U+221b " ROOT\263 "
+U+229A "(\260)" # CIRCLED RING OPERATOR
+U+02a4 "d\346"
+U+20af "\304\361\367"
diff --git a/src/chrtrans/iso08_uni.tbl b/src/chrtrans/iso08_uni.tbl
new file mode 100644
index 0000000..d162396
--- /dev/null
+++ b/src/chrtrans/iso08_uni.tbl
@@ -0,0 +1,229 @@
+# This file has been modified for lynx (see README.tables)
+
+#The MIME name of this charset.
+Miso-8859-8
+
+#Name as a Display Charset (used on Options screen).
+OHebrew (ISO-8859-8)
+
+#Codepage number
+C916
+
+#
+# Name: ISO/IEC 8859-8:1999 to Unicode
+# Unicode version: 3.0
+# Table version: 1.1
+# Table format: Format A
+# Date: 2000-Jan-03
+# Authors: Ken Whistler <kenw@sybase.com>
+#
+# Copyright (c) 1991-1999 Unicode, Inc. All Rights reserved.
+#
+# This file is provided as-is by Unicode, Inc. (The Unicode Consortium).
+# No claims are made as to fitness for any particular purpose. No
+# warranties of any kind are expressed or implied. The recipient
+# agrees to determine applicability of information provided. If this
+# file has been provided on optical media by Unicode, Inc., the sole
+# remedy for any claim will be exchange of defective media within 90
+# days of receipt.
+#
+# Unicode, Inc. hereby grants the right to freely use the information
+# supplied in this file in the creation of products supporting the
+# Unicode Standard, and to make copies of this file in any form for
+# internal or external distribution as long as this notice remains
+# attached.
+#
+# General notes:
+#
+# This table contains the data the Unicode Consortium has on how
+# ISO/IEC 8859-8:1999 characters map into Unicode.
+#
+# Format: Three tab-separated columns
+# Column #1 is the ISO/IEC 8859-8 code (in hex as 0xXX)
+# Column #2 is the Unicode (in hex as 0xXXXX)
+# Column #3 the Unicode name (follows a comment sign, '#')
+#
+# The entries are in ISO/IEC 8859-8 order.
+#
+# Version history
+# 1.0 version updates 0.1 version by adding mappings for all
+# control characters.
+# 1.1 version updates to the published 8859-8:1999, correcting
+# the mapping of 0xAF and adding mappings for LRM and RLM.
+#
+# Updated versions of this file may be found in:
+# <ftp://ftp.unicode.org/Public/MAPPINGS/>
+#
+# Any comments or problems, contact <errata@unicode.org>
+# Please note that <errata@unicode.org> is an archival address;
+# notices will be checked, but do not expect an immediate response.
+#
+0x20-0x7e idem
+#
+#0x20 U+0020 # SPACE
+#0x21 U+0021 # EXCLAMATION MARK
+#0x22 U+0022 # QUOTATION MARK
+#0x23 U+0023 # NUMBER SIGN
+#0x24 U+0024 # DOLLAR SIGN
+#0x25 U+0025 # PERCENT SIGN
+#0x26 U+0026 # AMPERSAND
+#0x27 U+0027 # APOSTROPHE
+#0x28 U+0028 # LEFT PARENTHESIS
+#0x29 U+0029 # RIGHT PARENTHESIS
+#0x2A U+002A # ASTERISK
+#0x2B U+002B # PLUS SIGN
+#0x2C U+002C # COMMA
+#0x2D U+002D # HYPHEN-MINUS
+#0x2E U+002E # FULL STOP
+#0x2F U+002F # SOLIDUS
+#0x30 U+0030 # DIGIT ZERO
+#0x31 U+0031 # DIGIT ONE
+#0x32 U+0032 # DIGIT TWO
+#0x33 U+0033 # DIGIT THREE
+#0x34 U+0034 # DIGIT FOUR
+#0x35 U+0035 # DIGIT FIVE
+#0x36 U+0036 # DIGIT SIX
+#0x37 U+0037 # DIGIT SEVEN
+#0x38 U+0038 # DIGIT EIGHT
+#0x39 U+0039 # DIGIT NINE
+#0x3A U+003A # COLON
+#0x3B U+003B # SEMICOLON
+#0x3C U+003C # LESS-THAN SIGN
+#0x3D U+003D # EQUALS SIGN
+#0x3E U+003E # GREATER-THAN SIGN
+#0x3F U+003F # QUESTION MARK
+#0x40 U+0040 # COMMERCIAL AT
+#0x41 U+0041 # LATIN CAPITAL LETTER A
+#0x42 U+0042 # LATIN CAPITAL LETTER B
+#0x43 U+0043 # LATIN CAPITAL LETTER C
+#0x44 U+0044 # LATIN CAPITAL LETTER D
+#0x45 U+0045 # LATIN CAPITAL LETTER E
+#0x46 U+0046 # LATIN CAPITAL LETTER F
+#0x47 U+0047 # LATIN CAPITAL LETTER G
+#0x48 U+0048 # LATIN CAPITAL LETTER H
+#0x49 U+0049 # LATIN CAPITAL LETTER I
+#0x4A U+004A # LATIN CAPITAL LETTER J
+#0x4B U+004B # LATIN CAPITAL LETTER K
+#0x4C U+004C # LATIN CAPITAL LETTER L
+#0x4D U+004D # LATIN CAPITAL LETTER M
+#0x4E U+004E # LATIN CAPITAL LETTER N
+#0x4F U+004F # LATIN CAPITAL LETTER O
+#0x50 U+0050 # LATIN CAPITAL LETTER P
+#0x51 U+0051 # LATIN CAPITAL LETTER Q
+#0x52 U+0052 # LATIN CAPITAL LETTER R
+#0x53 U+0053 # LATIN CAPITAL LETTER S
+#0x54 U+0054 # LATIN CAPITAL LETTER T
+#0x55 U+0055 # LATIN CAPITAL LETTER U
+#0x56 U+0056 # LATIN CAPITAL LETTER V
+#0x57 U+0057 # LATIN CAPITAL LETTER W
+#0x58 U+0058 # LATIN CAPITAL LETTER X
+#0x59 U+0059 # LATIN CAPITAL LETTER Y
+#0x5A U+005A # LATIN CAPITAL LETTER Z
+#0x5B U+005B # LEFT SQUARE BRACKET
+#0x5C U+005C # REVERSE SOLIDUS
+#0x5D U+005D # RIGHT SQUARE BRACKET
+#0x5E U+005E # CIRCUMFLEX ACCENT
+#0x5F U+005F # LOW LINE
+#0x60 U+0060 # GRAVE ACCENT
+#0x61 U+0061 # LATIN SMALL LETTER A
+#0x62 U+0062 # LATIN SMALL LETTER B
+#0x63 U+0063 # LATIN SMALL LETTER C
+#0x64 U+0064 # LATIN SMALL LETTER D
+#0x65 U+0065 # LATIN SMALL LETTER E
+#0x66 U+0066 # LATIN SMALL LETTER F
+#0x67 U+0067 # LATIN SMALL LETTER G
+#0x68 U+0068 # LATIN SMALL LETTER H
+#0x69 U+0069 # LATIN SMALL LETTER I
+#0x6A U+006A # LATIN SMALL LETTER J
+#0x6B U+006B # LATIN SMALL LETTER K
+#0x6C U+006C # LATIN SMALL LETTER L
+#0x6D U+006D # LATIN SMALL LETTER M
+#0x6E U+006E # LATIN SMALL LETTER N
+#0x6F U+006F # LATIN SMALL LETTER O
+#0x70 U+0070 # LATIN SMALL LETTER P
+#0x71 U+0071 # LATIN SMALL LETTER Q
+#0x72 U+0072 # LATIN SMALL LETTER R
+#0x73 U+0073 # LATIN SMALL LETTER S
+#0x74 U+0074 # LATIN SMALL LETTER T
+#0x75 U+0075 # LATIN SMALL LETTER U
+#0x76 U+0076 # LATIN SMALL LETTER V
+#0x77 U+0077 # LATIN SMALL LETTER W
+#0x78 U+0078 # LATIN SMALL LETTER X
+#0x79 U+0079 # LATIN SMALL LETTER Y
+#0x7A U+007A # LATIN SMALL LETTER Z
+#0x7B U+007B # LEFT CURLY BRACKET
+#0x7C U+007C # VERTICAL LINE
+#0x7D U+007D # RIGHT CURLY BRACKET
+#0x7E U+007E # TILDE
+0xA0 U+00A0 # NO-BREAK SPACE
+0xA2 U+00A2 # CENT SIGN
+0xA3 U+00A3 # POUND SIGN
+0xA4 U+00A4 # CURRENCY SIGN
+0xA5 U+00A5 # YEN SIGN
+0xA6 U+00A6 # BROKEN BAR
+0xA7 U+00A7 # SECTION SIGN
+0xA8 U+00A8 # DIAERESIS
+0xA9 U+00A9 # COPYRIGHT SIGN
+0xAA U+00D7 # MULTIPLICATION SIGN
+0xAB U+00AB # LEFT-POINTING DOUBLE ANGLE QUOTATION MARK
+0xAC U+00AC # NOT SIGN
+0xAD U+00AD # SOFT HYPHEN
+0xAE U+00AE # REGISTERED SIGN
+0xAF U+00AF # MACRON
+0xB0 U+00B0 # DEGREE SIGN
+0xB1 U+00B1 # PLUS-MINUS SIGN
+0xB2 U+00B2 # SUPERSCRIPT TWO
+0xB3 U+00B3 # SUPERSCRIPT THREE
+0xB4 U+00B4 # ACUTE ACCENT
+0xB5 U+00B5 # MICRO SIGN
+0xB6 U+00B6 # PILCROW SIGN
+0xB7 U+00B7 # MIDDLE DOT
+0xB8 U+00B8 # CEDILLA
+0xB9 U+00B9 # SUPERSCRIPT ONE
+0xBA U+00F7 # DIVISION SIGN
+0xBB U+00BB # RIGHT-POINTING DOUBLE ANGLE QUOTATION MARK
+0xBC U+00BC # VULGAR FRACTION ONE QUARTER
+0xBD U+00BD # VULGAR FRACTION ONE HALF
+0xBE U+00BE # VULGAR FRACTION THREE QUARTERS
+0xDF U+2017 # DOUBLE LOW LINE
+0xE0 U+05D0 # HEBREW LETTER ALEF
+0xE1 U+05D1 # HEBREW LETTER BET
+0xE2 U+05D2 # HEBREW LETTER GIMEL
+0xE3 U+05D3 # HEBREW LETTER DALET
+0xE4 U+05D4 # HEBREW LETTER HE
+0xE5 U+05D5 # HEBREW LETTER VAV
+0xE6 U+05D6 # HEBREW LETTER ZAYIN
+0xE7 U+05D7 # HEBREW LETTER HET
+0xE8 U+05D8 # HEBREW LETTER TET
+0xE9 U+05D9 # HEBREW LETTER YOD
+0xEA U+05DA # HEBREW LETTER FINAL KAF
+0xEB U+05DB # HEBREW LETTER KAF
+0xEC U+05DC # HEBREW LETTER LAMED
+0xED U+05DD # HEBREW LETTER FINAL MEM
+0xEE U+05DE # HEBREW LETTER MEM
+0xEF U+05DF # HEBREW LETTER FINAL NUN
+0xF0 U+05E0 # HEBREW LETTER NUN
+0xF1 U+05E1 # HEBREW LETTER SAMEKH
+0xF2 U+05E2 # HEBREW LETTER AYIN
+0xF3 U+05E3 # HEBREW LETTER FINAL PE
+0xF4 U+05E4 # HEBREW LETTER PE
+0xF5 U+05E5 # HEBREW LETTER FINAL TSADI
+0xF6 U+05E6 # HEBREW LETTER TSADI
+0xF7 U+05E7 # HEBREW LETTER QOF
+0xF8 U+05E8 # HEBREW LETTER RESH
+0xF9 U+05E9 # HEBREW LETTER SHIN
+0xFA U+05EA # HEBREW LETTER TAV
+0xFD U+200E # LEFT-TO-RIGHT MARK
+0xFE U+200F # RIGHT-TO-LEFT MARK
+
+
+#Hebrew points - map to empty string
+U+05B0-U+05C2:
+
+#HEBREW LETTER DOUBLE VAV #U+05F0:åå
+U+05F0 "\345\345"
+#HEBREW LETTER VAV YOD #U+05F1:éå
+U+05F1 "\351\345"
+#HEBREW LETTER DOUBLE YOD #U+05F2:éé
+U+05F2 "\351\351"
+
diff --git a/src/chrtrans/iso09_uni.tbl b/src/chrtrans/iso09_uni.tbl
new file mode 100644
index 0000000..87afe48
--- /dev/null
+++ b/src/chrtrans/iso09_uni.tbl
@@ -0,0 +1,266 @@
+# This file has been modified for lynx (see README.tables)
+
+#The MIME name of this charset.
+Miso-8859-9
+
+#Name as a Display Charset (used on Options screen)
+OTurkish (ISO-8859-9)
+
+#Codepage number
+C920
+
+#
+# Name: ISO/IEC 8859-9:1999 to Unicode
+# Unicode version: 3.0
+# Table version: 1.0
+# Table format: Format A
+# Date: 1999 July 27
+# Authors: Ken Whistler <kenw@sybase.com>
+#
+# Copyright (c) 1991-1999 Unicode, Inc. All Rights reserved.
+#
+# This file is provided as-is by Unicode, Inc. (The Unicode Consortium).
+# No claims are made as to fitness for any particular purpose. No
+# warranties of any kind are expressed or implied. The recipient
+# agrees to determine applicability of information provided. If this
+# file has been provided on magnetic media by Unicode, Inc., the sole
+# remedy for any claim will be exchange of defective media within 90
+# days of receipt.
+#
+# Unicode, Inc. hereby grants the right to freely use the information
+# supplied in this file in the creation of products supporting the
+# Unicode Standard, and to make copies of this file in any form for
+# internal or external distribution as long as this notice remains
+# attached.
+#
+# General notes:
+#
+# This table contains the data the Unicode Consortium has on how
+# ISO/IEC 8859-9:1999 characters map into Unicode.
+#
+# Format: Three tab-separated columns
+# Column #1 is the ISO/IEC 8859-9 code (in hex as 0xXX)
+# Column #2 is the Unicode (in hex as 0xXXXX)
+# Column #3 the Unicode name (follows a comment sign, '#')
+#
+# The entries are in ISO/IEC 8859-9 order.
+#
+# ISO/IEC 8859-9 is also equivalent to ISO-IR-148.
+#
+# Version history
+# 1.0 version updates 0.1 version by adding mappings for all
+# control characters.
+#
+# Updated versions of this file may be found in:
+# <ftp://ftp.unicode.org/Public/MAPPINGS/>
+#
+# Any comments or problems, contact <errata@unicode.org>
+# Please note that <errata@unicode.org> is an archival address;
+# notices will be checked, but do not expect an immediate response.
+#
+# Lines with more than one Unicode (U+XXXX) value contain additional
+# replacement mappings added for lynx. - kw
+#
+0x20-0x7e idem
+0x49 U+042b
+#
+#0x20 U+0020 # SPACE
+#0x21 U+0021 # EXCLAMATION MARK
+#0x22 U+0022 # QUOTATION MARK
+#0x23 U+0023 # NUMBER SIGN
+#0x24 U+0024 # DOLLAR SIGN
+#0x25 U+0025 # PERCENT SIGN
+#0x26 U+0026 # AMPERSAND
+#0x27 U+0027 # APOSTROPHE
+#0x28 U+0028 # LEFT PARENTHESIS
+#0x29 U+0029 # RIGHT PARENTHESIS
+#0x2A U+002A # ASTERISK
+#0x2B U+002B # PLUS SIGN
+#0x2C U+002C # COMMA
+#0x2D U+002D # HYPHEN-MINUS
+#0x2E U+002E # FULL STOP
+#0x2F U+002F # SOLIDUS
+#0x30 U+0030 # DIGIT ZERO
+#0x31 U+0031 # DIGIT ONE
+#0x32 U+0032 # DIGIT TWO
+#0x33 U+0033 # DIGIT THREE
+#0x34 U+0034 # DIGIT FOUR
+#0x35 U+0035 # DIGIT FIVE
+#0x36 U+0036 # DIGIT SIX
+#0x37 U+0037 # DIGIT SEVEN
+#0x38 U+0038 # DIGIT EIGHT
+#0x39 U+0039 # DIGIT NINE
+#0x3A U+003A # COLON
+#0x3B U+003B # SEMICOLON
+#0x3C U+003C # LESS-THAN SIGN
+#0x3D U+003D # EQUALS SIGN
+#0x3E U+003E # GREATER-THAN SIGN
+#0x3F U+003F # QUESTION MARK
+#0x40 U+0040 # COMMERCIAL AT
+#0x41 U+0041 # LATIN CAPITAL LETTER A
+#0x42 U+0042 # LATIN CAPITAL LETTER B
+#0x43 U+0043 # LATIN CAPITAL LETTER C
+#0x44 U+0044 # LATIN CAPITAL LETTER D
+#0x45 U+0045 # LATIN CAPITAL LETTER E
+#0x46 U+0046 # LATIN CAPITAL LETTER F
+#0x47 U+0047 # LATIN CAPITAL LETTER G
+#0x48 U+0048 # LATIN CAPITAL LETTER H
+#0x49 U+0049 # LATIN CAPITAL LETTER I
+#0x4A U+004A # LATIN CAPITAL LETTER J
+#0x4B U+004B # LATIN CAPITAL LETTER K
+#0x4C U+004C # LATIN CAPITAL LETTER L
+#0x4D U+004D # LATIN CAPITAL LETTER M
+#0x4E U+004E # LATIN CAPITAL LETTER N
+#0x4F U+004F # LATIN CAPITAL LETTER O
+#0x50 U+0050 # LATIN CAPITAL LETTER P
+#0x51 U+0051 # LATIN CAPITAL LETTER Q
+#0x52 U+0052 # LATIN CAPITAL LETTER R
+#0x53 U+0053 # LATIN CAPITAL LETTER S
+#0x54 U+0054 # LATIN CAPITAL LETTER T
+#0x55 U+0055 # LATIN CAPITAL LETTER U
+#0x56 U+0056 # LATIN CAPITAL LETTER V
+#0x57 U+0057 # LATIN CAPITAL LETTER W
+#0x58 U+0058 # LATIN CAPITAL LETTER X
+#0x59 U+0059 # LATIN CAPITAL LETTER Y
+#0x5A U+005A # LATIN CAPITAL LETTER Z
+#0x5B U+005B # LEFT SQUARE BRACKET
+#0x5C U+005C # REVERSE SOLIDUS
+#0x5D U+005D # RIGHT SQUARE BRACKET
+#0x5E U+005E # CIRCUMFLEX ACCENT
+#0x5F U+005F # LOW LINE
+#0x60 U+0060 # GRAVE ACCENT
+#0x61 U+0061 # LATIN SMALL LETTER A
+#0x62 U+0062 # LATIN SMALL LETTER B
+#0x63 U+0063 # LATIN SMALL LETTER C
+#0x64 U+0064 # LATIN SMALL LETTER D
+#0x65 U+0065 # LATIN SMALL LETTER E
+#0x66 U+0066 # LATIN SMALL LETTER F
+#0x67 U+0067 # LATIN SMALL LETTER G
+#0x68 U+0068 # LATIN SMALL LETTER H
+#0x69 U+0069 # LATIN SMALL LETTER I
+#0x6A U+006A # LATIN SMALL LETTER J
+#0x6B U+006B # LATIN SMALL LETTER K
+#0x6C U+006C # LATIN SMALL LETTER L
+#0x6D U+006D # LATIN SMALL LETTER M
+#0x6E U+006E # LATIN SMALL LETTER N
+#0x6F U+006F # LATIN SMALL LETTER O
+#0x70 U+0070 # LATIN SMALL LETTER P
+#0x71 U+0071 # LATIN SMALL LETTER Q
+#0x72 U+0072 # LATIN SMALL LETTER R
+#0x73 U+0073 # LATIN SMALL LETTER S
+#0x74 U+0074 # LATIN SMALL LETTER T
+#0x75 U+0075 # LATIN SMALL LETTER U
+#0x76 U+0076 # LATIN SMALL LETTER V
+#0x77 U+0077 # LATIN SMALL LETTER W
+#0x78 U+0078 # LATIN SMALL LETTER X
+#0x79 U+0079 # LATIN SMALL LETTER Y
+#0x7A U+007A # LATIN SMALL LETTER Z
+#0x7B U+007B # LEFT CURLY BRACKET
+#0x7C U+007C # VERTICAL LINE
+#0x7D U+007D # RIGHT CURLY BRACKET
+#0x7E U+007E # TILDE
+0xA0 U+00A0 # NO-BREAK SPACE
+0xA1 U+00A1 # INVERTED EXCLAMATION MARK
+0xA2 U+00A2 # CENT SIGN
+0xA3 U+00A3 # POUND SIGN
+0xA4 U+00A4 # CURRENCY SIGN
+0xA5 U+00A5 # YEN SIGN
+0xA6 U+00A6 # BROKEN BAR
+0xA7 U+00A7 # SECTION SIGN
+0xA8 U+00A8 U+0308 # DIAERESIS
+0xA9 U+00A9 # COPYRIGHT SIGN
+0xAA U+00AA # FEMININE ORDINAL INDICATOR
+0xAB U+00AB # LEFT-POINTING DOUBLE ANGLE QUOTATION MARK
+0xAC U+00AC # NOT SIGN
+0xAD U+00AD # SOFT HYPHEN
+0xAE U+00AE # REGISTERED SIGN
+0xAF U+00AF U+0304 # MACRON
+0xB0 U+00B0 U+030a # DEGREE SIGN
+0xB1 U+00B1 # PLUS-MINUS SIGN
+0xB2 U+00B2 # SUPERSCRIPT TWO
+0xB3 U+00B3 # SUPERSCRIPT THREE
+0xB4 U+00B4 # ACUTE ACCENT
+0xB5 U+00B5 U+03bc # MICRO SIGN
+0xB6 U+00B6 # PILCROW SIGN
+0xB7 U+00B7 U+0307 U+0387 # MIDDLE DOT
+0xB8 U+00B8 U+0327 # CEDILLA
+0xB9 U+00B9 # SUPERSCRIPT ONE
+0xBA U+00BA # MASCULINE ORDINAL INDICATOR
+0xBB U+00BB # RIGHT-POINTING DOUBLE ANGLE QUOTATION MARK
+0xBC U+00BC # VULGAR FRACTION ONE QUARTER
+0xBD U+00BD # VULGAR FRACTION ONE HALF
+0xBE U+00BE # VULGAR FRACTION THREE QUARTERS
+0xBF U+00BF # INVERTED QUESTION MARK
+0xC0 U+00C0 # LATIN CAPITAL LETTER A WITH GRAVE
+0xC1 U+00C1 # LATIN CAPITAL LETTER A WITH ACUTE
+0xC2 U+00C2 # LATIN CAPITAL LETTER A WITH CIRCUMFLEX
+0xC3 U+00C3 # LATIN CAPITAL LETTER A WITH TILDE
+0xC4 U+00C4 # LATIN CAPITAL LETTER A WITH DIAERESIS
+0xC5 U+00C5 # LATIN CAPITAL LETTER A WITH RING ABOVE
+0xC6 U+00C6 # LATIN CAPITAL LETTER AE
+0xC7 U+00C7 # LATIN CAPITAL LETTER C WITH CEDILLA
+0xC8 U+00C8 # LATIN CAPITAL LETTER E WITH GRAVE
+0xC9 U+00C9 # LATIN CAPITAL LETTER E WITH ACUTE
+0xCA U+00CA # LATIN CAPITAL LETTER E WITH CIRCUMFLEX
+0xCB U+00CB # LATIN CAPITAL LETTER E WITH DIAERESIS
+0xCC U+00CC # LATIN CAPITAL LETTER I WITH GRAVE
+0xCD U+00CD # LATIN CAPITAL LETTER I WITH ACUTE
+0xCE U+00CE # LATIN CAPITAL LETTER I WITH CIRCUMFLEX
+0xCF U+00CF # LATIN CAPITAL LETTER I WITH DIAERESIS
+0xD0 U+011E # LATIN CAPITAL LETTER G WITH BREVE
+0xD1 U+00D1 # LATIN CAPITAL LETTER N WITH TILDE
+0xD2 U+00D2 # LATIN CAPITAL LETTER O WITH GRAVE
+0xD3 U+00D3 # LATIN CAPITAL LETTER O WITH ACUTE
+0xD4 U+00D4 # LATIN CAPITAL LETTER O WITH CIRCUMFLEX
+0xD5 U+00D5 # LATIN CAPITAL LETTER O WITH TILDE
+0xD6 U+00D6 # LATIN CAPITAL LETTER O WITH DIAERESIS
+0xD7 U+00D7 # MULTIPLICATION SIGN
+0xD8 U+00D8 # LATIN CAPITAL LETTER O WITH STROKE
+0xD9 U+00D9 # LATIN CAPITAL LETTER U WITH GRAVE
+0xDA U+00DA # LATIN CAPITAL LETTER U WITH ACUTE
+0xDB U+00DB # LATIN CAPITAL LETTER U WITH CIRCUMFLEX
+0xDC U+00DC # LATIN CAPITAL LETTER U WITH DIAERESIS
+0xDD U+0130 U+0418 U+0406 # LATIN CAPITAL LETTER I WITH DOT ABOVE
+0xDE U+015E U+0428 # LATIN CAPITAL LETTER S WITH CEDILLA
+0xDF U+00DF # LATIN SMALL LETTER SHARP S
+0xE0 U+00E0 # LATIN SMALL LETTER A WITH GRAVE
+0xE1 U+00E1 # LATIN SMALL LETTER A WITH ACUTE
+0xE2 U+00E2 # LATIN SMALL LETTER A WITH CIRCUMFLEX
+0xE3 U+00E3 # LATIN SMALL LETTER A WITH TILDE
+0xE4 U+00E4 # LATIN SMALL LETTER A WITH DIAERESIS
+0xE5 U+00E5 # LATIN SMALL LETTER A WITH RING ABOVE
+0xE6 U+00E6 # LATIN SMALL LETTER AE
+0xE7 U+00E7 # LATIN SMALL LETTER C WITH CEDILLA
+0xE8 U+00E8 # LATIN SMALL LETTER E WITH GRAVE
+0xE9 U+00E9 # LATIN SMALL LETTER E WITH ACUTE
+0xEA U+00EA # LATIN SMALL LETTER E WITH CIRCUMFLEX
+0xEB U+00EB # LATIN SMALL LETTER E WITH DIAERESIS
+0xEC U+00EC # LATIN SMALL LETTER I WITH GRAVE
+0xED U+00ED # LATIN SMALL LETTER I WITH ACUTE
+0xEE U+00EE # LATIN SMALL LETTER I WITH CIRCUMFLEX
+0xEF U+00EF # LATIN SMALL LETTER I WITH DIAERESIS
+0xF0 U+011F # LATIN SMALL LETTER G WITH BREVE
+0xF1 U+00F1 # LATIN SMALL LETTER N WITH TILDE
+0xF2 U+00F2 # LATIN SMALL LETTER O WITH GRAVE
+0xF3 U+00F3 # LATIN SMALL LETTER O WITH ACUTE
+0xF4 U+00F4 # LATIN SMALL LETTER O WITH CIRCUMFLEX
+0xF5 U+00F5 # LATIN SMALL LETTER O WITH TILDE
+0xF6 U+00F6 # LATIN SMALL LETTER O WITH DIAERESIS
+0xF7 U+00F7 # DIVISION SIGN
+0xF8 U+00F8 # LATIN SMALL LETTER O WITH STROKE
+0xF9 U+00F9 # LATIN SMALL LETTER U WITH GRAVE
+0xFA U+00FA # LATIN SMALL LETTER U WITH ACUTE
+0xFB U+00FB # LATIN SMALL LETTER U WITH CIRCUMFLEX
+0xFC U+00FC # LATIN SMALL LETTER U WITH DIAERESIS
+0xFD U+0131 U+03b9 U+044b # LATIN SMALL LETTER DOTLESS I
+0xFE U+015F U+0448 # LATIN SMALL LETTER S WITH CEDILLA
+0xFF U+00FF # LATIN SMALL LETTER Y WITH DIAERESIS
+
+U+2218 " \260 " # RING OPERATOR
+U+221b " ROOT\263 "
+U+2297 "(\327)" # CIRCLED TIMES
+U+2299 "(\267)" # CIRCLED DOT OPERATOR
+U+229A "(\260)" # CIRCLED RING OPERATOR
+U+22A0 "[\327]" # SQUARED TIMES
+U+22A1 "[\267]" # SQUARED DOT OPERATOR
+U+22C5 " \267 " # DOT OPERATOR
diff --git a/src/chrtrans/iso10_uni.tbl b/src/chrtrans/iso10_uni.tbl
new file mode 100644
index 0000000..edd59e2
--- /dev/null
+++ b/src/chrtrans/iso10_uni.tbl
@@ -0,0 +1,153 @@
+#
+# Unicode mapping table for the fonts iso10.*
+# [use: unicode_start iso10.f16 iso10]
+#
+#This is not default font!
+D0
+
+#The MIME name of this charset.
+
+Miso-8859-10
+
+#Name as a Display Charset (used on Options screen)
+ONorth European (ISO-8859-10)
+
+# Name: ISO 8859-10 Latin 6 (1998) to Unicode
+# Date: 2005-12-15
+# Authors: Thomas E Dickey from
+# http://czyborra.com/charsets/iso8859.html
+# (ISO 8859 Alphabet Soup)
+
+0x20-0x7e idem
+
+0xA0 U+00A0 # NO-BREAK SPACE
+0xA1 U+0104 # LATIN CAPITAL LETTER A WITH OGONEK
+0xA2 U+0112 # LATIN CAPITAL LETTER E WITH MACRON
+0xA3 U+0122 # LATIN CAPITAL LETTER G WITH CEDILLA
+0xA4 U+012A # LATIN CAPITAL LETTER I WITH MACRON
+0xA5 U+0128 # LATIN CAPITAL LETTER I WITH TILDE
+0xA6 U+0136 # LATIN CAPITAL LETTER K WITH CEDILLA
+0xA7 U+00A7 # SECTION SIGN
+0xA8 U+013B # LATIN CAPITAL LETTER L WITH CEDILLA
+0xA9 U+0110 # LATIN CAPITAL LETTER D WITH STROKE
+0xAA U+0160 # LATIN CAPITAL LETTER S WITH CARON
+0xAB U+0166 # LATIN CAPITAL LETTER T WITH STROKE
+0xAC U+017D # LATIN CAPITAL LETTER Z WITH CARON
+0xAD U+00AD # SOFT HYPHEN
+0xAE U+016A # LATIN CAPITAL LETTER U WITH MACRON
+0xAF U+014A # LATIN CAPITAL LETTER ENG
+0xB0 U+00B0 # DEGREE SIGN
+0xB1 U+0105 # LATIN SMALL LETTER A WITH OGONEK
+0xB2 U+0113 # LATIN SMALL LETTER E WITH MACRON
+0xB3 U+0123 # LATIN SMALL LETTER G WITH CEDILLA
+0xB4 U+012B # LATIN SMALL LETTER I WITH MACRON
+0xB5 U+0129 # LATIN SMALL LETTER I WITH TILDE
+0xB6 U+0137 # LATIN SMALL LETTER K WITH CEDILLA
+0xB7 U+00B7 # MIDDLE DOT
+0xB8 U+013C # LATIN SMALL LETTER L WITH CEDILLA
+0xB9 U+0111 # LATIN SMALL LETTER D WITH STROKE
+0xBA U+0161 # LATIN SMALL LETTER S WITH CARON
+0xBB U+0167 # LATIN SMALL LETTER T WITH STROKE
+0xBC U+017E # LATIN SMALL LETTER Z WITH CARON
+0xBD U+2015 # HORIZONTAL BAR
+0xBE U+016B # LATIN SMALL LETTER U WITH MACRON
+0xBF U+014B # LATIN SMALL LETTER ENG
+0xC0 U+0100 # LATIN CAPITAL LETTER A WITH MACRON
+
+0xC1-0xC6 idem
+
+#0xC1 U+00C1 # LATIN CAPITAL LETTER A WITH ACUTE
+#0xC2 U+00C2 # LATIN CAPITAL LETTER A WITH CIRCUMFLEX
+#0xC3 U+00C3 # LATIN CAPITAL LETTER A WITH TILDE
+#0xC4 U+00C4 # LATIN CAPITAL LETTER A WITH DIAERESIS
+#0xC5 U+00C5 # LATIN CAPITAL LETTER A WITH RING ABOVE
+#0xC6 U+00C6 # LATIN CAPITAL LETTER AE
+
+0xC7 U+012E # LATIN CAPITAL LETTER I WITH OGONEK
+0xC8 U+010C # LATIN CAPITAL LETTER C WITH CARON
+0xC9 U+00C9 # LATIN CAPITAL LETTER E WITH ACUTE
+0xCA U+0118 # LATIN CAPITAL LETTER E WITH OGONEK
+0xCB U+00CB # LATIN CAPITAL LETTER E WITH DIAERESIS
+0xCC U+0116 # LATIN CAPITAL LETTER E WITH DOT ABOVE
+
+0xCD-0xD0 idem
+
+#0xCD U+00CD # LATIN CAPITAL LETTER I WITH ACUTE
+#0xCE U+00CE # LATIN CAPITAL LETTER I WITH CIRCUMFLEX
+#0xCF U+00CF # LATIN CAPITAL LETTER I WITH DIAERESIS
+#0xD0 U+00D0 # LATIN CAPITAL LETTER ETH
+
+0xD1 U+0145 # LATIN CAPITAL LETTER N WITH CEDILLA
+0xD2 U+014C # LATIN CAPITAL LETTER O WITH MACRON
+
+0xD3-0xD6 idem
+
+#0xD3 U+00D3 # LATIN CAPITAL LETTER O WITH ACUTE
+#0xD4 U+00D4 # LATIN CAPITAL LETTER O WITH CIRCUMFLEX
+#0xD5 U+00D5 # LATIN CAPITAL LETTER O WITH TILDE
+#0xD6 U+00D6 # LATIN CAPITAL LETTER O WITH DIAERESIS
+
+0xD7 U+0168 # LATIN CAPITAL LETTER U WITH TILDE
+0xD8 U+00D8 # LATIN CAPITAL LETTER O WITH STROKE
+0xD9 U+0172 # LATIN CAPITAL LETTER U WITH OGONEK
+
+0xDA-0xDE idem
+
+#0xDA U+00DA # LATIN CAPITAL LETTER U WITH ACUTE
+#0xDB U+00DB # LATIN CAPITAL LETTER U WITH CIRCUMFLEX
+#0xDC U+00DC # LATIN CAPITAL LETTER U WITH DIAERESIS
+#0xDD U+00DD # LATIN CAPITAL LETTER Y WITH ACUTE
+#0xDE U+00DE # LATIN CAPITAL LETTER THORN
+
+0xDf U+00DF # LATIN SMALL LETTER SHARP S
+0xE0 U+0101 # LATIN SMALL LETTER A WITH MACRON
+
+0xE1-0xE6 idem
+
+#0xE1 U+00E1 # LATIN SMALL LETTER A WITH ACUTE
+#0xE2 U+00E2 # LATIN SMALL LETTER A WITH CIRCUMFLEX
+#0xE3 U+00E3 # LATIN SMALL LETTER A WITH TILDE
+#0xE4 U+00E4 # LATIN SMALL LETTER A WITH DIAERESIS
+#0xE5 U+00E5 # LATIN SMALL LETTER A WITH RING ABOVE
+#0xE6 U+00E6 # LATIN SMALL LETTER AE
+
+0xE7 U+012F # LATIN SMALL LETTER I WITH OGONEK
+0xE8 U+010D # LATIN SMALL LETTER C WITH CARON
+0xE9 U+00E9 # LATIN SMALL LETTER E WITH ACUTE
+0xEA U+0119 # LATIN SMALL LETTER E WITH OGONEK
+0xEB U+00EB # LATIN SMALL LETTER E WITH DIAERESIS
+0xEC U+0117 # LATIN SMALL LETTER E WITH DOT ABOVE
+
+0xED-0xF0 idem
+
+#0xED U+00ED # LATIN SMALL LETTER I WITH ACUTE
+#0xEE U+00EE # LATIN SMALL LETTER I WITH CIRCUMFLEX
+#0xEF U+00EF # LATIN SMALL LETTER I WITH DIAERESIS
+#0xF0 U+00f0 # LATIN SMALL LETTER ETH
+
+0xF1 U+0146 # LATIN SMALL LETTER N WITH CEDILLA
+0xF2 U+014D # LATIN SMALL LETTER O WITH MACRON
+
+0xF3-0xF6 idem
+
+#0xF3 U+00F3 # LATIN SMALL LETTER O WITH ACUTE
+#0xF4 U+00F4 # LATIN SMALL LETTER O WITH CIRCUMFLEX
+#0xF5 U+00F5 # LATIN SMALL LETTER O WITH TILDE
+#0xF6 U+00F6 # LATIN SMALL LETTER O WITH DIAERESIS
+
+0xF7 U+0169 # LATIN SMALL LETTER U WITH TILDE
+0xF8 U+00F8 # LATIN SMALL LETTER O WITH STROKE
+0xF9 U+0173 # LATIN SMALL LETTER U WITH OGONEK
+
+0xFA-0xFE idem
+
+#0xFA U+00FA # LATIN SMALL LETTER U WITH ACUTE
+#0xFB U+00FB # LATIN SMALL LETTER U WITH CIRCUMFLEX
+#0xFC U+00FC # LATIN SMALL LETTER U WITH DIAERESIS
+#0xFD U+00FD # LATIN SMALL LETTER Y WITH ACUTE
+#0xFE U+00FE # LATIN SMALL LETTER THORN
+
+0xFF U+0138 # LATIN SMALL LETTER KRA
+
+# TRADE MARK SIGN:
+U+2122:(TM)
diff --git a/src/chrtrans/iso13_uni.tbl b/src/chrtrans/iso13_uni.tbl
new file mode 100644
index 0000000..33e3b8f
--- /dev/null
+++ b/src/chrtrans/iso13_uni.tbl
@@ -0,0 +1,114 @@
+# The MIME name of this charset.
+Miso-8859-13
+
+# Name as a Display Charset (used on Options screen)
+OBaltic Rim (ISO-8859-13)
+
+# This is not the default font!
+D0
+
+#
+# Name: ISO 8859-13 Latin 7 (1998) to Unicode
+# Date: 2005-12-15
+# Authors: Thomas E Dickey from
+# http://czyborra.com/charsets/iso8859.html
+# (ISO 8859 Alphabet Soup)
+
+0x20-0x7E idem # ASCII
+
+0xA0 U+00A0 # NO-BREAK SPACE
+0xA1 U+201D # RIGHT DOUBLE QUOTATION MARK
+0xA2 U+00A2 # CENT SIGN
+0xA3 U+00A3 # POUND SIGN
+0xA4 U+00A4 # CURRENCY SIGN
+0xA5 U+201E # DOUBLE LOW-9 QUOTATION MARK
+0xA6 U+00A6 # BROKEN BAR
+0xA7 U+00A7 # SECTION SIGN
+0xA8 U+00D8 # LATIN CAPITAL LETTER O WITH STROKE
+0xA9 U+00A9 # COPYRIGHT SIGN
+0xAA U+0156 # LATIN CAPITAL LETTER R WITH CEDILLA
+0xAB U+00AB # LEFT-POINTING DOUBLE ANGLE QUOTATION MARK
+0xAC U+00AC # NOT SIGN
+0xAD U+00AD # SOFT HYPHEN
+0xAE U+00AE # REGISTERED SIGN
+0xAF U+00C6 # LATIN CAPITAL LETTER AE
+0xB0 U+00B0 # DEGREE SIGN
+0xB1 U+00B1 # PLUS-MINUS SIGN
+0xB2 U+00B2 # SUPERSCRIPT TWO
+0xB3 U+00B3 # SUPERSCRIPT THREE
+0xB4 U+201C # LEFT DOUBLE QUOTATION MARK
+0xB5 U+00B5 # MICRO SIGN
+0xB6 U+00B6 # PILCROW SIGN
+0xB7 U+00B7 # MIDDLE DOT
+0xB8 U+00F8 # LATIN SMALL LETTER O WITH STROKE
+0xB9 U+00B9 # SUPERSCRIPT ONE
+0xBA U+0157 # LATIN SMALL LETTER R WITH CEDILLA
+0xBB U+00BB # RIGHT-POINTING DOUBLE ANGLE QUOTATION MARK
+0xBC U+00BC # VULGAR FRACTION ONE QUARTER
+0xBD U+00BD # VULGAR FRACTION ONE HALF
+0xBE U+00BE # VULGAR FRACTION THREE QUARTERS
+0xBF U+00E6 # LATIN SMALL LETTER AE
+0xC0 U+0104 # LATIN CAPITAL LETTER A WITH OGONEK
+0xC1 U+012E # LATIN CAPITAL LETTER I WITH OGONEK
+0xC2 U+0100 # LATIN CAPITAL LETTER A WITH MACRON
+0xC3 U+0106 # LATIN CAPITAL LETTER C WITH ACUTE
+0xC4 U+00C4 # LATIN CAPITAL LETTER A WITH DIAERESIS
+0xC5 U+00C5 # LATIN CAPITAL LETTER A WITH RING ABOVE
+0xC6 U+0118 # LATIN CAPITAL LETTER E WITH OGONEK
+0xC7 U+0112 # LATIN CAPITAL LETTER E WITH MACRON
+0xC8 U+010C # LATIN CAPITAL LETTER C WITH CARON
+0xC9 U+00C9 # LATIN CAPITAL LETTER E WITH ACUTE
+0xCA U+0179 # LATIN CAPITAL LETTER Z WITH ACUTE
+0xCB U+0116 # LATIN CAPITAL LETTER E WITH DOT ABOVE
+0xCC U+0122 # LATIN CAPITAL LETTER G WITH CEDILLA
+0xCD U+0136 # LATIN CAPITAL LETTER K WITH CEDILLA
+0xCE U+012A # LATIN CAPITAL LETTER I WITH MACRON
+0xCF U+013B # LATIN CAPITAL LETTER L WITH CEDILLA
+0xD0 U+0160 # LATIN CAPITAL LETTER S WITH CARON
+0xD1 U+0143 # LATIN CAPITAL LETTER N WITH ACUTE
+0xD2 U+0145 # LATIN CAPITAL LETTER N WITH CEDILLA
+0xD3 U+00D3 # LATIN CAPITAL LETTER O WITH ACUTE
+0xD4 U+014C # LATIN CAPITAL LETTER O WITH MACRON
+0xD5 U+00D5 # LATIN CAPITAL LETTER O WITH TILDE
+0xD6 U+00D6 # LATIN CAPITAL LETTER O WITH DIAERESIS
+0xD7 U+00D7 # MULTIPLICATION SIGN
+0xD8 U+0172 # LATIN CAPITAL LETTER U WITH OGONEK
+0xD9 U+0141 # LATIN CAPITAL LETTER L WITH STROKE
+0xDA U+015A # LATIN CAPITAL LETTER S WITH ACUTE
+0xDB U+016A # LATIN CAPITAL LETTER U WITH MACRON
+0xDC U+00DC # LATIN CAPITAL LETTER U WITH DIAERESIS
+0xDD U+017B # LATIN CAPITAL LETTER Z WITH DOT ABOVE
+0xDE U+017D # LATIN CAPITAL LETTER Z WITH CARON
+0xDF U+00DF # LATIN SMALL LETTER SHARP S (German)
+0xE0 U+0105 # LATIN SMALL LETTER A WITH OGONEK
+0xE1 U+012F # LATIN SMALL LETTER I WITH OGONEK
+0xE2 U+0101 # LATIN SMALL LETTER A WITH MACRON
+0xE3 U+0107 # LATIN SMALL LETTER C WITH ACUTE
+0xE4 U+00E4 # LATIN SMALL LETTER A WITH DIAERESIS
+0xE5 U+00E5 # LATIN SMALL LETTER A WITH RING ABOVE
+0xE6 U+0119 # LATIN SMALL LETTER E WITH OGONEK
+0xE7 U+0113 # LATIN SMALL LETTER E WITH MACRON
+0xE8 U+010D # LATIN SMALL LETTER C WITH CARON
+0xE9 U+00E9 # LATIN SMALL LETTER E WITH ACUTE
+0xEA U+017A # LATIN SMALL LETTER Z WITH ACUTE
+0xEB U+0117 # LATIN SMALL LETTER E WITH DOT ABOVE
+0xEC U+0123 # LATIN SMALL LETTER G WITH CEDILLA
+0xED U+0137 # LATIN SMALL LETTER K WITH CEDILLA
+0xEE U+012B # LATIN SMALL LETTER I WITH MACRON
+0xEF U+013C # LATIN SMALL LETTER L WITH CEDILLA
+0xF0 U+0161 # LATIN SMALL LETTER S WITH CARON
+0xF1 U+0144 # LATIN SMALL LETTER N WITH ACUTE
+0xF2 U+0146 # LATIN SMALL LETTER N WITH CEDILLA
+0xF3 U+00F3 # LATIN SMALL LETTER O WITH ACUTE
+0xF4 U+014D # LATIN SMALL LETTER O WITH MACRON
+0xF5 U+00F5 # LATIN SMALL LETTER O WITH TILDE
+0xF6 U+00F6 # LATIN SMALL LETTER O WITH DIAERESIS
+0xF7 U+00F7 # DIVISION SIGN
+0xF8 U+0173 # LATIN SMALL LETTER U WITH OGONEK
+0xF9 U+0142 # LATIN SMALL LETTER L WITH STROKE
+0xFA U+015B # LATIN SMALL LETTER S WITH ACUTE
+0xFB U+016B # LATIN SMALL LETTER U WITH MACRON
+0xFC U+00FC # LATIN SMALL LETTER U WITH DIAERESIS
+0xFD U+017C # LATIN SMALL LETTER Z WITH DOT ABOVE
+0xFE U+017E # LATIN SMALL LETTER Z WITH CARON
+0xFF U+2019 # RIGHT SINGLE QUOTATION MARK
diff --git a/src/chrtrans/iso14_uni.tbl b/src/chrtrans/iso14_uni.tbl
new file mode 100644
index 0000000..630a946
--- /dev/null
+++ b/src/chrtrans/iso14_uni.tbl
@@ -0,0 +1,114 @@
+# The MIME name of this charset.
+Miso-8859-14
+
+# Name as a Display Charset (used on Options screen)
+OCeltic (ISO-8859-14)
+
+# This is not the default font!
+D0
+
+#
+# Name: ISO 8859-13 Latin 8 (1998) to Unicode
+# Date: 2005-12-15
+# Authors: Thomas E Dickey from
+# http://czyborra.com/charsets/iso8859.html
+# (ISO 8859 Alphabet Soup)
+
+0x20-0x7E idem # ASCII
+
+0xA0 U+00A0 # NO-BREAK SPACE
+0xA1 U+1E02 # LATIN CAPITAL LETTER B WITH DOT ABOVE
+0xA2 U+1E03 # LATIN SMALL LETTER B WITH DOT ABOVE
+0xA3 U+00A3 # POUND SIGN
+0xA4 U+010A # LATIN CAPITAL LETTER C WITH DOT ABOVE
+0xA5 U+010B # LATIN SMALL LETTER C WITH DOT ABOVE
+0xA6 U+1E0A # LATIN CAPITAL LETTER D WITH DOT ABOVE
+0xA7 U+00A7 # SECTION SIGN
+0xA8 U+1E80 # LATIN CAPITAL LETTER W WITH GRAVE
+0xA9 U+00A9 # COPYRIGHT SIGN
+0xAA U+1E82 # LATIN CAPITAL LETTER W WITH ACUTE
+0xAB U+1E0B # LATIN SMALL LETTER D WITH DOT ABOVE
+0xAC U+1EF2 # LATIN CAPITAL LETTER Y WITH GRAVE
+0xAD U+00AD # SOFT HYPHEN
+0xAE U+00AE # REGISTERED SIGN
+0xAF U+0178 # LATIN CAPITAL LETTER Y WITH DIAERESIS
+0xB0 U+1E1E # LATIN CAPITAL LETTER F WITH DOT ABOVE
+0xB1 U+1E1F # LATIN SMALL LETTER F WITH DOT ABOVE
+0xB2 U+0120 # LATIN CAPITAL LETTER G WITH DOT ABOVE
+0xB3 U+0121 # LATIN SMALL LETTER G WITH DOT ABOVE
+0xB4 U+1E40 # LATIN CAPITAL LETTER M WITH DOT ABOVE
+0xB5 U+1E41 # LATIN SMALL LETTER M WITH DOT ABOVE
+0xB6 U+00B6 # PILCROW SIGN
+0xB7 U+1E56 # LATIN CAPITAL LETTER P WITH DOT ABOVE
+0xB8 U+1E81 # LATIN SMALL LETTER W WITH GRAVE
+0xB9 U+1E57 # LATIN SMALL LETTER P WITH DOT ABOVE
+0xBA U+1E83 # LATIN SMALL LETTER W WITH ACUTE
+0xBB U+1E60 # LATIN CAPITAL LETTER S WITH DOT ABOVE
+0xBC U+1EF3 # LATIN SMALL LETTER Y WITH GRAVE
+0xBD U+1E84 # LATIN CAPITAL LETTER W WITH DIAERESIS
+0xBE U+1E85 # LATIN SMALL LETTER W WITH DIAERESIS
+0xBF U+1E61 # LATIN SMALL LETTER S WITH DOT ABOVE
+0xC0 U+00C0 # LATIN CAPITAL LETTER A WITH GRAVE
+0xC1 U+00C1 # LATIN CAPITAL LETTER A WITH ACUTE
+0xC2 U+00C2 # LATIN CAPITAL LETTER A WITH CIRCUMFLEX
+0xC3 U+00C3 # LATIN CAPITAL LETTER A WITH TILDE
+0xC4 U+00C4 # LATIN CAPITAL LETTER A WITH DIAERESIS
+0xC5 U+00C5 # LATIN CAPITAL LETTER A WITH RING ABOVE
+0xC6 U+00C6 # LATIN CAPITAL LETTER AE
+0xC7 U+00C7 # LATIN CAPITAL LETTER C WITH CEDILLA
+0xC8 U+00C8 # LATIN CAPITAL LETTER E WITH GRAVE
+0xC9 U+00C9 # LATIN CAPITAL LETTER E WITH ACUTE
+0xCA U+00CA # LATIN CAPITAL LETTER E WITH CIRCUMFLEX
+0xCB U+00CB # LATIN CAPITAL LETTER E WITH DIAERESIS
+0xCC U+00CC # LATIN CAPITAL LETTER I WITH GRAVE
+0xCD U+00CD # LATIN CAPITAL LETTER I WITH ACUTE
+0xCE U+00CE # LATIN CAPITAL LETTER I WITH CIRCUMFLEX
+0xCF U+00CF # LATIN CAPITAL LETTER I WITH DIAERESIS
+0xD0 U+0174 # LATIN CAPITAL LETTER W WITH CIRCUMFLEX
+0xD1 U+00D1 # LATIN CAPITAL LETTER N WITH TILDE
+0xD2 U+00D2 # LATIN CAPITAL LETTER O WITH GRAVE
+0xD3 U+00D3 # LATIN CAPITAL LETTER O WITH ACUTE
+0xD4 U+00D4 # LATIN CAPITAL LETTER O WITH CIRCUMFLEX
+0xD5 U+00D5 # LATIN CAPITAL LETTER O WITH TILDE
+0xD6 U+00D6 # LATIN CAPITAL LETTER O WITH DIAERESIS
+0xD7 U+1E6A # LATIN CAPITAL LETTER T WITH DOT ABOVE
+0xD8 U+00D8 # LATIN CAPITAL LETTER O WITH STROKE
+0xD9 U+00D9 # LATIN CAPITAL LETTER U WITH GRAVE
+0xDA U+00DA # LATIN CAPITAL LETTER U WITH ACUTE
+0xDB U+00DB # LATIN CAPITAL LETTER U WITH CIRCUMFLEX
+0xDC U+00DC # LATIN CAPITAL LETTER U WITH DIAERESIS
+0xDD U+00DD # LATIN CAPITAL LETTER Y WITH ACUTE
+0xDE U+0176 # LATIN CAPITAL LETTER Y WITH CIRCUMFLEX
+0xDF U+00DF # LATIN SMALL LETTER SHARP S
+0xE0 U+00E0 # LATIN SMALL LETTER A WITH GRAVE
+0xE1 U+00E1 # LATIN SMALL LETTER A WITH ACUTE
+0xE2 U+00E2 # LATIN SMALL LETTER A WITH CIRCUMFLEX
+0xE3 U+00E3 # LATIN SMALL LETTER A WITH TILDE
+0xE4 U+00E4 # LATIN SMALL LETTER A WITH DIAERESIS
+0xE5 U+00E5 # LATIN SMALL LETTER A WITH RING ABOVE
+0xE6 U+00E6 # LATIN SMALL LETTER AE
+0xE7 U+00E7 # LATIN SMALL LETTER C WITH CEDILLA
+0xE8 U+00E8 # LATIN SMALL LETTER E WITH GRAVE
+0xE9 U+00E9 # LATIN SMALL LETTER E WITH ACUTE
+0xEA U+00EA # LATIN SMALL LETTER E WITH CIRCUMFLEX
+0xEB U+00EB # LATIN SMALL LETTER E WITH DIAERESIS
+0xEC U+00EC # LATIN SMALL LETTER I WITH GRAVE
+0xED U+00ED # LATIN SMALL LETTER I WITH ACUTE
+0xEE U+00EE # LATIN SMALL LETTER I WITH CIRCUMFLEX
+0xEF U+00EF # LATIN SMALL LETTER I WITH DIAERESIS
+0xF0 U+0175 # LATIN SMALL LETTER W WITH CIRCUMFLEX
+0xF1 U+00F1 # LATIN SMALL LETTER N WITH TILDE
+0xF2 U+00F2 # LATIN SMALL LETTER O WITH GRAVE
+0xF3 U+00F3 # LATIN SMALL LETTER O WITH ACUTE
+0xF4 U+00F4 # LATIN SMALL LETTER O WITH CIRCUMFLEX
+0xF5 U+00F5 # LATIN SMALL LETTER O WITH TILDE
+0xF6 U+00F6 # LATIN SMALL LETTER O WITH DIAERESIS
+0xF7 U+1E6B # LATIN SMALL LETTER T WITH DOT ABOVE
+0xF8 U+00F8 # LATIN SMALL LETTER O WITH STROKE
+0xF9 U+00F9 # LATIN SMALL LETTER U WITH GRAVE
+0xFA U+00FA # LATIN SMALL LETTER U WITH ACUTE
+0xFB U+00FB # LATIN SMALL LETTER U WITH CIRCUMFLEX
+0xFC U+00FC # LATIN SMALL LETTER U WITH DIAERESIS
+0xFD U+00FD # LATIN SMALL LETTER Y WITH ACUTE
+0xFE U+0177 # LATIN SMALL LETTER Y WITH CIRCUMFLEX
+0xFF U+00FF # LATIN SMALL LETTER Y WITH DIAERESIS
diff --git a/src/chrtrans/iso15_uni.tbl b/src/chrtrans/iso15_uni.tbl
new file mode 100644
index 0000000..398affd
--- /dev/null
+++ b/src/chrtrans/iso15_uni.tbl
@@ -0,0 +1,216 @@
+# The MIME name of this charset.
+Miso-8859-15
+
+# Name as a Display Charset (used on Options screen)
+OWestern (ISO-8859-15)
+
+# This is not the default font!
+D0
+
+#Codepage number
+#?
+
+#
+# Name: ISO 8859-15 Latin 9 (1998) to Unicode
+# Date: 1999-01-01
+# Authors: Christian "naddy" Weisgerber <naddy@mips.rhein-neckar.de>
+#
+# Remarks: Latin 9 is identical to Latin 1 except for
+# code positions A4, A6, A8, B4, B8, BC, BD, BE
+
+0x20-0x7E idem # ASCII
+
+#0x20 U+0020 # SPACE
+#0x21 U+0021 # EXCLAMATION MARK
+#0x22 U+0022 # QUOTATION MARK
+#0x23 U+0023 # NUMBER SIGN
+#0x24 U+0024 # DOLLAR SIGN
+#0x25 U+0025 # PERCENT SIGN
+#0x26 U+0026 # AMPERSAND
+#0x27 U+0027 # APOSTROPHE
+#0x28 U+0028 # LEFT PARENTHESIS
+#0x29 U+0029 # RIGHT PARENTHESIS
+#0x2A U+002A # ASTERISK
+#0x2B U+002B # PLUS SIGN
+#0x2C U+002C # COMMA
+#0x2D U+002D # HYPHEN-MINUS
+#0x2E U+002E # FULL STOP
+#0x2F U+002F # SOLIDUS
+#0x30 U+0030 # DIGIT ZERO
+#0x31 U+0031 # DIGIT ONE
+#0x32 U+0032 # DIGIT TWO
+#0x33 U+0033 # DIGIT THREE
+#0x34 U+0034 # DIGIT FOUR
+#0x35 U+0035 # DIGIT FIVE
+#0x36 U+0036 # DIGIT SIX
+#0x37 U+0037 # DIGIT SEVEN
+#0x38 U+0038 # DIGIT EIGHT
+#0x39 U+0039 # DIGIT NINE
+#0x3A U+003A # COLON
+#0x3B U+003B # SEMICOLON
+#0x3C U+003C # LESS-THAN SIGN
+#0x3D U+003D # EQUALS SIGN
+#0x3E U+003E # GREATER-THAN SIGN
+#0x3F U+003F # QUESTION MARK
+#0x40 U+0040 # COMMERCIAL AT
+#0x41 U+0041 # LATIN CAPITAL LETTER A
+#0x42 U+0042 # LATIN CAPITAL LETTER B
+#0x43 U+0043 # LATIN CAPITAL LETTER C
+#0x44 U+0044 # LATIN CAPITAL LETTER D
+#0x45 U+0045 # LATIN CAPITAL LETTER E
+#0x46 U+0046 # LATIN CAPITAL LETTER F
+#0x47 U+0047 # LATIN CAPITAL LETTER G
+#0x48 U+0048 # LATIN CAPITAL LETTER H
+#0x49 U+0049 # LATIN CAPITAL LETTER I
+#0x4A U+004A # LATIN CAPITAL LETTER J
+#0x4B U+004B # LATIN CAPITAL LETTER K
+#0x4C U+004C # LATIN CAPITAL LETTER L
+#0x4D U+004D # LATIN CAPITAL LETTER M
+#0x4E U+004E # LATIN CAPITAL LETTER N
+#0x4F U+004F # LATIN CAPITAL LETTER O
+#0x50 U+0050 # LATIN CAPITAL LETTER P
+#0x51 U+0051 # LATIN CAPITAL LETTER Q
+#0x52 U+0052 # LATIN CAPITAL LETTER R
+#0x53 U+0053 # LATIN CAPITAL LETTER S
+#0x54 U+0054 # LATIN CAPITAL LETTER T
+#0x55 U+0055 # LATIN CAPITAL LETTER U
+#0x56 U+0056 # LATIN CAPITAL LETTER V
+#0x57 U+0057 # LATIN CAPITAL LETTER W
+#0x58 U+0058 # LATIN CAPITAL LETTER X
+#0x59 U+0059 # LATIN CAPITAL LETTER Y
+#0x5A U+005A # LATIN CAPITAL LETTER Z
+#0x5B U+005B # LEFT SQUARE BRACKET
+#0x5C U+005C # REVERSE SOLIDUS
+#0x5D U+005D # RIGHT SQUARE BRACKET
+#0x5E U+005E # CIRCUMFLEX ACCENT
+#0x5F U+005F # LOW LINE
+#0x60 U+0060 # GRAVE ACCENT
+#0x61 U+0061 # LATIN SMALL LETTER A
+#0x62 U+0062 # LATIN SMALL LETTER B
+#0x63 U+0063 # LATIN SMALL LETTER C
+#0x64 U+0064 # LATIN SMALL LETTER D
+#0x65 U+0065 # LATIN SMALL LETTER E
+#0x66 U+0066 # LATIN SMALL LETTER F
+#0x67 U+0067 # LATIN SMALL LETTER G
+#0x68 U+0068 # LATIN SMALL LETTER H
+#0x69 U+0069 # LATIN SMALL LETTER I
+#0x6A U+006A # LATIN SMALL LETTER J
+#0x6B U+006B # LATIN SMALL LETTER K
+#0x6C U+006C # LATIN SMALL LETTER L
+#0x6D U+006D # LATIN SMALL LETTER M
+#0x6E U+006E # LATIN SMALL LETTER N
+#0x6F U+006F # LATIN SMALL LETTER O
+#0x70 U+0070 # LATIN SMALL LETTER P
+#0x71 U+0071 # LATIN SMALL LETTER Q
+#0x72 U+0072 # LATIN SMALL LETTER R
+#0x73 U+0073 # LATIN SMALL LETTER S
+#0x74 U+0074 # LATIN SMALL LETTER T
+#0x75 U+0075 # LATIN SMALL LETTER U
+#0x76 U+0076 # LATIN SMALL LETTER V
+#0x77 U+0077 # LATIN SMALL LETTER W
+#0x78 U+0078 # LATIN SMALL LETTER X
+#0x79 U+0079 # LATIN SMALL LETTER Y
+#0x7A U+007A # LATIN SMALL LETTER Z
+#0x7B U+007B # LEFT CURLY BRACKET
+#0x7C U+007C # VERTICAL LINE
+#0x7D U+007D # RIGHT CURLY BRACKET
+#0x7E U+007E # TILDE
+
+0xA0 U+00A0 # NO-BREAK SPACE
+0xA1 U+00A1 # INVERTED EXCLAMATION MARK
+0xA2 U+00A2 # CENT SIGN
+0xA3 U+00A3 # POUND SIGN
+0xA4 U+20AC # EURO SIGN
+0xA5 U+00A5 # YEN SIGN
+0xA6 U+0160 # LATIN CAPITAL LETTER S WITH CARON
+0xA7 U+00A7 # SECTION SIGN
+0xA8 U+0161 # LATIN SMALL LETTER S WITH CARON
+0xA9 U+00A9 # COPYRIGHT SIGN
+0xAA U+00AA # FEMININE ORDINAL INDICATOR
+0xAB U+00AB # LEFT-POINTING DOUBLE ANGLE QUOTATION MARK
+0xAC U+00AC # NOT SIGN
+0xAD U+00AD # SOFT HYPHEN
+0xAE U+00AE # REGISTERED SIGN
+0xAF U+00AF # MACRON
+0xB0 U+00B0 # DEGREE SIGN
+0xB1 U+00B1 # PLUS-MINUS SIGN
+0xB2 U+00B2 # SUPERSCRIPT TWO
+0xB3 U+00B3 # SUPERSCRIPT THREE
+0xB4 U+017D # LATIN CAPITAL LETTER Z WITH CARON
+0xB5 U+00B5 # MICRO SIGN
+0xB6 U+00B6 # PILCROW SIGN
+0xB7 U+00B7 # MIDDLE DOT
+0xB8 U+017E # LATIN SMALL LETTER Z WITH CARON
+0xB9 U+00B9 # SUPERSCRIPT ONE
+0xBA U+00BA # MASCULINE ORDINAL INDICATOR
+0xBB U+00BB # RIGHT-POINTING DOUBLE ANGLE QUOTATION MARK
+0xBC U+0152 # LATIN CAPITAL LIGATURE OE
+0xBD U+0153 # LATIN SMALL LIGATURE OE
+0xBE U+0178 # LATIN CAPITAL LETTER Y WITH DIAERESIS
+0xBF U+00BF # INVERTED QUESTION MARK
+0xC0 U+00C0 # LATIN CAPITAL LETTER A WITH GRAVE
+0xC1 U+00C1 # LATIN CAPITAL LETTER A WITH ACUTE
+0xC2 U+00C2 # LATIN CAPITAL LETTER A WITH CIRCUMFLEX
+0xC3 U+00C3 # LATIN CAPITAL LETTER A WITH TILDE
+0xC4 U+00C4 # LATIN CAPITAL LETTER A WITH DIAERESIS
+0xC5 U+00C5 # LATIN CAPITAL LETTER A WITH RING ABOVE
+0xC6 U+00C6 # LATIN CAPITAL LETTER AE
+0xC7 U+00C7 # LATIN CAPITAL LETTER C WITH CEDILLA
+0xC8 U+00C8 # LATIN CAPITAL LETTER E WITH GRAVE
+0xC9 U+00C9 # LATIN CAPITAL LETTER E WITH ACUTE
+0xCA U+00CA # LATIN CAPITAL LETTER E WITH CIRCUMFLEX
+0xCB U+00CB # LATIN CAPITAL LETTER E WITH DIAERESIS
+0xCC U+00CC # LATIN CAPITAL LETTER I WITH GRAVE
+0xCD U+00CD # LATIN CAPITAL LETTER I WITH ACUTE
+0xCE U+00CE # LATIN CAPITAL LETTER I WITH CIRCUMFLEX
+0xCF U+00CF # LATIN CAPITAL LETTER I WITH DIAERESIS
+0xD0 U+00D0 # LATIN CAPITAL LETTER ETH
+0xD1 U+00D1 # LATIN CAPITAL LETTER N WITH TILDE
+0xD2 U+00D2 # LATIN CAPITAL LETTER O WITH GRAVE
+0xD3 U+00D3 # LATIN CAPITAL LETTER O WITH ACUTE
+0xD4 U+00D4 # LATIN CAPITAL LETTER O WITH CIRCUMFLEX
+0xD5 U+00D5 # LATIN CAPITAL LETTER O WITH TILDE
+0xD6 U+00D6 # LATIN CAPITAL LETTER O WITH DIAERESIS
+0xD7 U+00D7 # MULTIPLICATION SIGN
+0xD8 U+00D8 # LATIN CAPITAL LETTER O WITH STROKE
+0xD9 U+00D9 # LATIN CAPITAL LETTER U WITH GRAVE
+0xDA U+00DA # LATIN CAPITAL LETTER U WITH ACUTE
+0xDB U+00DB # LATIN CAPITAL LETTER U WITH CIRCUMFLEX
+0xDC U+00DC # LATIN CAPITAL LETTER U WITH DIAERESIS
+0xDD U+00DD # LATIN CAPITAL LETTER Y WITH ACUTE
+0xDE U+00DE # LATIN CAPITAL LETTER THORN
+0xDF U+00DF # LATIN SMALL LETTER SHARP S
+0xE0 U+00E0 # LATIN SMALL LETTER A WITH GRAVE
+0xE1 U+00E1 # LATIN SMALL LETTER A WITH ACUTE
+0xE2 U+00E2 # LATIN SMALL LETTER A WITH CIRCUMFLEX
+0xE3 U+00E3 # LATIN SMALL LETTER A WITH TILDE
+0xE4 U+00E4 # LATIN SMALL LETTER A WITH DIAERESIS
+0xE5 U+00E5 # LATIN SMALL LETTER A WITH RING ABOVE
+0xE6 U+00E6 # LATIN SMALL LETTER AE
+0xE7 U+00E7 # LATIN SMALL LETTER C WITH CEDILLA
+0xE8 U+00E8 # LATIN SMALL LETTER E WITH GRAVE
+0xE9 U+00E9 # LATIN SMALL LETTER E WITH ACUTE
+0xEA U+00EA # LATIN SMALL LETTER E WITH CIRCUMFLEX
+0xEB U+00EB # LATIN SMALL LETTER E WITH DIAERESIS
+0xEC U+00EC # LATIN SMALL LETTER I WITH GRAVE
+0xED U+00ED # LATIN SMALL LETTER I WITH ACUTE
+0xEE U+00EE # LATIN SMALL LETTER I WITH CIRCUMFLEX
+0xEF U+00EF # LATIN SMALL LETTER I WITH DIAERESIS
+0xF0 U+00F0 # LATIN SMALL LETTER ETH
+0xF1 U+00F1 # LATIN SMALL LETTER N WITH TILDE
+0xF2 U+00F2 # LATIN SMALL LETTER O WITH GRAVE
+0xF3 U+00F3 # LATIN SMALL LETTER O WITH ACUTE
+0xF4 U+00F4 # LATIN SMALL LETTER O WITH CIRCUMFLEX
+0xF5 U+00F5 # LATIN SMALL LETTER O WITH TILDE
+0xF6 U+00F6 # LATIN SMALL LETTER O WITH DIAERESIS
+0xF7 U+00F7 # DIVISION SIGN
+0xF8 U+00F8 # LATIN SMALL LETTER O WITH STROKE
+0xF9 U+00F9 # LATIN SMALL LETTER U WITH GRAVE
+0xFA U+00FA # LATIN SMALL LETTER U WITH ACUTE
+0xFB U+00FB # LATIN SMALL LETTER U WITH CIRCUMFLEX
+0xFC U+00FC # LATIN SMALL LETTER U WITH DIAERESIS
+0xFD U+00FD # LATIN SMALL LETTER Y WITH ACUTE
+0xFE U+00FE # LATIN SMALL LETTER THORN
+0xFF U+00FF # LATIN SMALL LETTER Y WITH DIAERESIS
+
+## EOF ##
diff --git a/src/chrtrans/iso16_uni.tbl b/src/chrtrans/iso16_uni.tbl
new file mode 100644
index 0000000..e846b24
--- /dev/null
+++ b/src/chrtrans/iso16_uni.tbl
@@ -0,0 +1,120 @@
+#The MIME name of this charset.
+Miso-8859-16
+
+#Name as a Display Charset (used on Options screen)
+OLatin 10 (ISO-8859-16)
+
+# This is not the default font!
+D0
+
+#Codepage number
+#C28606
+
+#This table contains the data the Unicode Consortium has on how
+#ISO/IEC 8859-16:2001 characters map into Unicode.
+#
+#Format: Three tab-separated columns
+# Column #1 is the ISO/IEC 8859-16 code (in hex as 0xXX)
+# Column #2 is the Unicode (in hex as 0xXXXX)
+# Column #3 the Unicode name (follows a comment sign, '#')
+#
+#The entries are in ISO/IEC 8859-16 order.
+
+0x20-0x7E idem # ASCII
+
+0xA0 U+00A0 # NO-BREAK SPACE
+0xA1 U+0104 # LATIN CAPITAL LETTER A WITH OGONEK
+0xA2 U+0105 # LATIN SMALL LETTER A WITH OGONEK
+0xA3 U+0141 # LATIN CAPITAL LETTER L WITH STROKE
+0xA4 U+20AC # EURO SIGN
+0xA5 U+201E # DOUBLE LOW-9 QUOTATION MARK
+0xA6 U+0160 # LATIN CAPITAL LETTER S WITH CARON
+0xA7 U+00A7 # SECTION SIGN
+0xA8 U+0161 # LATIN SMALL LETTER S WITH CARON
+0xA9 U+00A9 # COPYRIGHT SIGN
+0xAA U+0218 # LATIN CAPITAL LETTER S WITH COMMA BELOW
+0xAB U+00AB # LEFT-POINTING DOUBLE ANGLE QUOTATION MARK
+0xAC U+0179 # LATIN CAPITAL LETTER Z WITH ACUTE
+0xAD U+00AD # SOFT HYPHEN
+0xAE U+017A # LATIN SMALL LETTER Z WITH ACUTE
+0xAF U+017B # LATIN CAPITAL LETTER Z WITH DOT ABOVE
+0xB0 U+00B0 # DEGREE SIGN
+0xB1 U+00B1 # PLUS-MINUS SIGN
+0xB2 U+010C # LATIN CAPITAL LETTER C WITH CARON
+0xB3 U+0142 # LATIN SMALL LETTER L WITH STROKE
+0xB4 U+017D # LATIN CAPITAL LETTER Z WITH CARON
+0xB5 U+201D # RIGHT DOUBLE QUOTATION MARK
+0xB6 U+00B6 # PILCROW SIGN
+0xB7 U+00B7 # MIDDLE DOT
+0xB8 U+017E # LATIN SMALL LETTER Z WITH CARON
+0xB9 U+010D # LATIN SMALL LETTER C WITH CARON
+0xBA U+0219 # LATIN SMALL LETTER S WITH COMMA BELOW
+0xBB U+00BB # RIGHT-POINTING DOUBLE ANGLE QUOTATION MARK
+0xBC U+0152 # LATIN CAPITAL LIGATURE OE
+0xBD U+0153 # LATIN SMALL LIGATURE OE
+0xBE U+0178 # LATIN CAPITAL LETTER Y WITH DIAERESIS
+0xBF U+017C # LATIN SMALL LETTER Z WITH DOT ABOVE
+0xC0 U+00C0 # LATIN CAPITAL LETTER A WITH GRAVE
+0xC1 U+00C1 # LATIN CAPITAL LETTER A WITH ACUTE
+0xC2 U+00C2 # LATIN CAPITAL LETTER A WITH CIRCUMFLEX
+0xC3 U+0102 # LATIN CAPITAL LETTER A WITH BREVE
+0xC4 U+00C4 # LATIN CAPITAL LETTER A WITH DIAERESIS
+0xC5 U+0106 # LATIN CAPITAL LETTER C WITH ACUTE
+0xC6 U+00C6 # LATIN CAPITAL LETTER AE
+0xC7 U+00C7 # LATIN CAPITAL LETTER C WITH CEDILLA
+0xC8 U+00C8 # LATIN CAPITAL LETTER E WITH GRAVE
+0xC9 U+00C9 # LATIN CAPITAL LETTER E WITH ACUTE
+0xCA U+00CA # LATIN CAPITAL LETTER E WITH CIRCUMFLEX
+0xCB U+00CB # LATIN CAPITAL LETTER E WITH DIAERESIS
+0xCC U+00CC # LATIN CAPITAL LETTER I WITH GRAVE
+0xCD U+00CD # LATIN CAPITAL LETTER I WITH ACUTE
+0xCE U+00CE # LATIN CAPITAL LETTER I WITH CIRCUMFLEX
+0xCF U+00CF # LATIN CAPITAL LETTER I WITH DIAERESIS
+0xD0 U+0110 # LATIN CAPITAL LETTER D WITH STROKE
+0xD1 U+0143 # LATIN CAPITAL LETTER N WITH ACUTE
+0xD2 U+00D2 # LATIN CAPITAL LETTER O WITH GRAVE
+0xD3 U+00D3 # LATIN CAPITAL LETTER O WITH ACUTE
+0xD4 U+00D4 # LATIN CAPITAL LETTER O WITH CIRCUMFLEX
+0xD5 U+0150 # LATIN CAPITAL LETTER O WITH DOUBLE ACUTE
+0xD6 U+00D6 # LATIN CAPITAL LETTER O WITH DIAERESIS
+0xD7 U+015A # LATIN CAPITAL LETTER S WITH ACUTE
+0xD8 U+0170 # LATIN CAPITAL LETTER U WITH DOUBLE ACUTE
+0xD9 U+00D9 # LATIN CAPITAL LETTER U WITH GRAVE
+0xDA U+00DA # LATIN CAPITAL LETTER U WITH ACUTE
+0xDB U+00DB # LATIN CAPITAL LETTER U WITH CIRCUMFLEX
+0xDC U+00DC # LATIN CAPITAL LETTER U WITH DIAERESIS
+0xDD U+0118 # LATIN CAPITAL LETTER E WITH OGONEK
+0xDE U+021A # LATIN CAPITAL LETTER T WITH COMMA BELOW
+0xDF U+00DF # LATIN SMALL LETTER SHARP S
+0xE0 U+00E0 # LATIN SMALL LETTER A WITH GRAVE
+0xE1 U+00E1 # LATIN SMALL LETTER A WITH ACUTE
+0xE2 U+00E2 # LATIN SMALL LETTER A WITH CIRCUMFLEX
+0xE3 U+0103 # LATIN SMALL LETTER A WITH BREVE
+0xE4 U+00E4 # LATIN SMALL LETTER A WITH DIAERESIS
+0xE5 U+0107 # LATIN SMALL LETTER C WITH ACUTE
+0xE6 U+00E6 # LATIN SMALL LETTER AE
+0xE7 U+00E7 # LATIN SMALL LETTER C WITH CEDILLA
+0xE8 U+00E8 # LATIN SMALL LETTER E WITH GRAVE
+0xE9 U+00E9 # LATIN SMALL LETTER E WITH ACUTE
+0xEA U+00EA # LATIN SMALL LETTER E WITH CIRCUMFLEX
+0xEB U+00EB # LATIN SMALL LETTER E WITH DIAERESIS
+0xEC U+00EC # LATIN SMALL LETTER I WITH GRAVE
+0xED U+00ED # LATIN SMALL LETTER I WITH ACUTE
+0xEE U+00EE # LATIN SMALL LETTER I WITH CIRCUMFLEX
+0xEF U+00EF # LATIN SMALL LETTER I WITH DIAERESIS
+0xF0 U+0111 # LATIN SMALL LETTER D WITH STROKE
+0xF1 U+0144 # LATIN SMALL LETTER N WITH ACUTE
+0xF2 U+00F2 # LATIN SMALL LETTER O WITH GRAVE
+0xF3 U+00F3 # LATIN SMALL LETTER O WITH ACUTE
+0xF4 U+00F4 # LATIN SMALL LETTER O WITH CIRCUMFLEX
+0xF5 U+0151 # LATIN SMALL LETTER O WITH DOUBLE ACUTE
+0xF6 U+00F6 # LATIN SMALL LETTER O WITH DIAERESIS
+0xF7 U+015B # LATIN SMALL LETTER S WITH ACUTE
+0xF8 U+0171 # LATIN SMALL LETTER U WITH DOUBLE ACUTE
+0xF9 U+00F9 # LATIN SMALL LETTER U WITH GRAVE
+0xFA U+00FA # LATIN SMALL LETTER U WITH ACUTE
+0xFB U+00FB # LATIN SMALL LETTER U WITH CIRCUMFLEX
+0xFC U+00FC # LATIN SMALL LETTER U WITH DIAERESIS
+0xFD U+0119 # LATIN SMALL LETTER E WITH OGONEK
+0xFE U+021B # LATIN SMALL LETTER T WITH COMMA BELOW
+0xFF U+00FF # LATIN SMALL LETTER Y WITH DIAERESIS
diff --git a/src/chrtrans/jcuken_kb.h b/src/chrtrans/jcuken_kb.h
new file mode 100644
index 0000000..5f42d26
--- /dev/null
+++ b/src/chrtrans/jcuken_kb.h
@@ -0,0 +1,22 @@
+static LYKbLayout_t kb_layout_jcuken[128] =
+{
+ 0x0000, 0x0001, 0x0002, 0x0003, 0x0004, 0x0005, 0x0006, 0x0007, /* 00..07 */
+ 0x0008, 0x0009, 0x000A, 0x000B, 0x000C, 0x000D, 0x000E, 0x000F, /* 08..0F */
+ 0x0010, 0x0011, 0x0012, 0x0013, 0x0014, 0x0015, 0x0016, 0x0017, /* 10..17 */
+ 0x0018, 0x0019, 0x001A, 0x001B, 0x001C, 0x001D, 0x001E, 0x001F, /* 18..1F */
+
+ 0x0020, 0x0021, 0x042D, 0x002F, 0x0024, 0x003A, 0x002E, 0x044D, /* 20..27 */
+ 0x003F, 0x0025, 0x003B, 0x002B, 0x0431, 0x002D, 0x044E, 0x0451, /* 28..2F */
+ 0x0030, 0x0031, 0x0032, 0x0033, 0x0034, 0x0035, 0x0036, 0x0037, /* 30..37 */
+ 0x0038, 0x0039, 0x0416, 0x0436, 0x0411, 0x003D, 0x042E, 0x0401, /* 38..3F */
+
+ 0x0022, 0x0424, 0x0418, 0x0421, 0x0412, 0x0423, 0x0410, 0x041F, /* 40..47 */
+ 0x0420, 0x0428, 0x041E, 0x041B, 0x0414, 0x042C, 0x0422, 0x0429, /* 48..4F */
+ 0x0417, 0x0419, 0x041A, 0x042B, 0x0415, 0x0413, 0x041C, 0x0426, /* 50..57 */
+ 0x0427, 0x041D, 0x042F, 0x0445, 0x005C, 0x044A, 0x002C, 0x005F, /* 58..5F */
+
+ 0x0029, 0x0444, 0x0438, 0x0441, 0x0432, 0x0443, 0x0430, 0x043F, /* 60..67 */
+ 0x0440, 0x0448, 0x043E, 0x043B, 0x0434, 0x044C, 0x0442, 0x0449, /* 68..6F */
+ 0x0437, 0x0439, 0x043A, 0x044B, 0x0435, 0x0433, 0x043C, 0x0446, /* 70..77 */
+ 0x0447, 0x043D, 0x044F, 0x0425, 0x007C, 0x042A, 0x0028, 0x007F /* 78..7F */
+};
diff --git a/src/chrtrans/koi8r_uni.tbl b/src/chrtrans/koi8r_uni.tbl
new file mode 100644
index 0000000..8bf4001
--- /dev/null
+++ b/src/chrtrans/koi8r_uni.tbl
@@ -0,0 +1,147 @@
+# Options screen name for this character set
+OCyrillic (KOI8-R)
+
+# MIME name for this charset
+Mkoi8-r
+
+#Codepage number
+C878
+
+0x20-0x7f idem
+# Based on a table received from "Glenn E. Thobe" <thobe@lafn.org>
+# (verified against RFC1489).
+#
+# Lines with more than one Unicode (U+XXXX) value contain additional
+# replacement mappings added for lynx. - kw
+#
+#hex unicode # description
+#--- U+---- # ---------------
+0x80 U+2500 # FORMS LIGHT HORIZONTAL
+0x81 U+2502 # FORMS LIGHT VERTICAL
+0x82 U+250C # FORMS LIGHT DOWN AND RIGHT
+0x83 U+2510 # FORMS LIGHT DOWN AND LEFT
+0x84 U+2514 # FORMS LIGHT UP AND RIGHT
+0x85 U+2518 # FORMS LIGHT UP AND LEFT
+0x86 U+251C # FORMS LIGHT VERTICAL AND RIGHT
+0x87 U+2524 # FORMS LIGHT VERTICAL AND LEFT
+0x88 U+252C # FORMS LIGHT DOWN AND HORIZONTAL
+0x89 U+2534 # FORMS LIGHT UP AND HORIZONTAL
+0x8A U+253C # FORMS LIGHT VERTICAL AND HORIZONTAL
+0x8B U+2580 # UPPER HALF BLOCK
+0x8C U+2584 # LOWER HALF BLOCK
+0x8D U+2588 # FULL BLOCK
+0x8E U+258C # LEFT HALF BLOCK
+0x8F U+2590 # RIGHT HALF BLOCK
+0x90 U+2591 # LIGHT SHADE
+0x91 U+2592 # MEDIUM SHADE
+0x92 U+2593 # DARK SHADE
+0x93 U+2320 # TOP HALF INTEGRAL
+0x94 U+25A0 # BLACK SMALL SQUARE
+0x95 U+2219 # BULLET OPERATOR
+0x96 U+221A # SQUARE ROOT
+0x97 U+2248 # ALMOST EQUAL TO
+0x98 U+2264 # LESS THAN OR EQUAL TO
+0x99 U+2265 # GREATER THAN OR EQUAL TO
+0x9A U+00A0 # NON-BREAKING SPACE
+0x9B U+2321 # BOTTOM HALF INTEGRAL
+0x9C U+00B0 # DEGREE SIGN
+0x9D U+00B2 # SUPERSCRIPT DIGIT TWO
+0x9E U+00B7 U+2027 # MIDDLE DOT
+0x9F U+00F7 # DIVISION SIGN
+0xA0 U+2550 # FORMS DOUBLE HORIZONTAL
+0xA1 U+2551 # FORMS DOUBLE VERTICAL
+0xA2 U+2552 # FORMS DOWN SINGLE AND RIGHT DOUBLE
+0xA3 U+0451 # SMA IO
+0xA4 U+2553 # FORMS DOWN DOUBLE AND RIGHT SINGLE
+0xA5 U+2554 # FORMS DOUBLE DOWN AND RIGHT
+0xA6 U+2555 # FORMS DOWN SINGLE AND LEFT DOUBLE
+0xA7 U+2556 # FORMS DOWN DOUBLE AND LEFT SINGLE
+0xA8 U+2557 # FORMS DOUBLE DOWN AND LEFT
+0xA9 U+2558 # FORMS UP SINGLE AND RIGHT DOUBLE
+0xAA U+2559 # FORMS UP DOUBLE AND RIGHT SINGLE
+0xAB U+255A # FORMS DOUBLE UP AND RIGHT
+0xAC U+255B # FORMS UP SINGLE AND LEFT DOUBLE
+0xAD U+255C # FORMS UP DOUBLE AND LEFT SINGLE
+0xAE U+255D # FORMS DOUBLE UP AND LEFT
+0xAF U+255E # FORMS VERTICAL SINGLE AND RIGHT DOUBLE
+0xB0 U+255F # FORMS VERTICAL DOUBLE AND RIGHT SINGLE
+0xB1 U+2560 # FORMS DOUBLE VERTICAL AND RIGHT
+0xB2 U+2561 # FORMS VERTICAL SINGLE AND LEFT DOUBLE
+0xB3 U+0401 # CAP IO
+0xB4 U+2562 # FORMS VERTICAL DOUBLE AND LEFT SINGLE
+0xB5 U+2563 # FORMS DOUBLE VERTICAL AND LEFT
+0xB6 U+2564 # FORMS DOWN SINGLE AND HORIZONTAL DOUBLE
+0xB7 U+2565 # FORMS DOWN DOUBLE AND HORIZONTAL SINGLE
+0xB8 U+2566 # FORMS DOUBLE DOWN AND HORIZONTAL
+0xB9 U+2567 # FORMS UP SINGLE AND HORIZONTAL DOUBLE
+0xBA U+2568 # FORMS UP DOUBLE AND HORIZONTAL SINGLE
+0xBB U+2569 # FORMS DOUBLE UP AND HORIZONTAL
+0xBC U+256A # FORMS VERTICAL SINGLE AND HORIZONTAL DOUBLE
+0xBD U+256B # FORMS VERTICAL DOUBLE AND HORIZONTAL SINGLE
+0xBE U+256C # FORMS DOUBLE VERTICAL AND HORIZONTAL
+0xBF U+00A9 # COPYRIGHT SIGN
+0xC0 U+044E # SMA IU
+0xC1 U+0430 # SMA A
+0xC2 U+0431 # SMA BE
+0xC3 U+0446 # SMA TSE
+0xC4 U+0434 # SMA DE
+0xC5 U+0435 # SMA IE
+0xC6 U+0444 U+03c6 # SMA EF
+0xC7 U+0433 # SMA GE
+0xC8 U+0445 # SMA KHA
+0xC9 U+0438 # SMA II
+0xCA U+0439 # SMA SHORT II
+0xCB U+043A # SMA KA
+0xCC U+043B U+03bb # SMA EL
+0xCD U+043C # SMA EM
+0xCE U+043D # SMA EN
+0xCF U+043E # SMA O
+0xD0 U+043F U+03c0 # SMA PE
+0xD1 U+044F # SMA IA
+0xD2 U+0440 # SMA ER
+0xD3 U+0441 # SMA ES
+0xD4 U+0442 # SMA TE
+0xD5 U+0443 # SMA U
+0xD6 U+0436 U+017e # SMA ZHE
+0xD7 U+0432 # SMA VE
+0xD8 U+044C # SMA SOFT SIGN
+0xD9 U+044B U+0131 # SMA YERI
+0xDA U+0437 # SMA ZE
+0xDB U+0448 U+0161 # SMA SHA
+0xDC U+044D # SMA REVERSED E
+0xDD U+0449 # SMA SHCHA
+0xDE U+0447 U+010d # SMA CHE
+0xDF U+044A # SMA HARD SIGN
+0xE0 U+042E # CAP IU
+0xE1 U+0410 # CAP A
+0xE2 U+0411 # CAP BE
+0xE3 U+0426 # CAP TSE
+0xE4 U+0414 # CAP DE
+0xE5 U+0415 # CAP IE
+0xE6 U+0424 U+03a6 # CAP EF
+0xE7 U+0413 U+0393 # CAP GE
+0xE8 U+0425 # CAP KHA
+0xE9 U+0418 # CAP II
+0xEA U+0419 # CAP SHORT II
+0xEB U+041A # CAP KA
+0xEC U+041B U+039b # CAP EL
+0xED U+041C # CAP EM
+0xEE U+041D # CAP EN
+0xEF U+041E # CAP O
+0xF0 U+041F U+03a0 # CAP PE
+0xF1 U+042F # CAP IA
+0xF2 U+0420 # CAP ER
+0xF3 U+0421 # CAP ES
+0xF4 U+0422 # CAP TE
+0xF5 U+0423 # CAP U
+0xF6 U+0416 U+017d # CAP ZHE
+0xF7 U+0412 # CAP VE
+0xF8 U+042C # CAP SOFT SIGN
+0xF9 U+042B # CAP YERI
+0xFA U+0417 # CAP ZE
+0xFB U+0428 U+0160 # CAP SHA
+0xFC U+042D # CAP REVERSED E
+0xFD U+0429 # CAP SHCHA
+0xFE U+0427 U+010c # CAP CHE
+0xFF U+042A # CAP HARD SIGN
+
diff --git a/src/chrtrans/koi8u_uni.tbl b/src/chrtrans/koi8u_uni.tbl
new file mode 100644
index 0000000..2c13845
--- /dev/null
+++ b/src/chrtrans/koi8u_uni.tbl
@@ -0,0 +1,154 @@
+# Options screen name for this character set
+OUkrainian Cyrillic (KOI8-U)
+
+# MIME name for this charset
+Mkoi8-u
+
+#Codepage number
+#?
+
+0x20-0x7f idem
+# Based on a table received from "Denis V. Dmitrienko" <denis@null.net>
+# (verified against RFC2319).
+# KOI8-U home page: <http://www.net.ua/KOI8-U>
+#
+# Quoted from RFC2319:
+# The upper part of the KOI8-U Character Set contains all Russian
+# letters defined in KOI8-R and four Ukrainian letters (#164, #180 -
+# ukr. ie, #166, #182 - ukr. i, #167, #183 - ukr. yi, #173, #189 - ukr.
+# ghe with upturn) which locations are compliant with ISO-IR-111.
+#
+# BOX DRAWINGS elements in the other positions (that are not used by
+# Ukrainian letters) are the same as in KOI8-R character set.
+#
+#
+#hex unicode # description
+#--- U+---- # ---------------
+0x80 U+2500 # BOX DRAWINGS LIGHT HORIZONTAL
+0x81 U+2502 # BOX DRAWINGS LIGHT VERTICAL
+0x82 U+250C # BOX DRAWINGS LIGHT DOWN AND RIGHT
+0x83 U+2510 # BOX DRAWINGS LIGHT DOWN AND LEFT
+0x84 U+2514 # BOX DRAWINGS LIGHT UP AND RIGHT
+0x85 U+2518 # BOX DRAWINGS LIGHT UP AND LEFT
+0x86 U+251C # BOX DRAWINGS LIGHT VERTICAL AND RIGHT
+0x87 U+2524 # BOX DRAWINGS LIGHT VERTICAL AND LEFT
+0x88 U+252C # BOX DRAWINGS LIGHT DOWN AND HORIZONTAL
+0x89 U+2534 # BOX DRAWINGS LIGHT UP AND HORIZONTAL
+0x8A U+253C # BOX DRAWINGS LIGHT VERTICAL AND HORIZONTAL
+0x8B U+2580 # UPPER HALF BLOCK
+0x8C U+2584 # LOWER HALF BLOCK
+0x8D U+2588 # FULL BLOCK
+0x8E U+258C # LEFT HALF BLOCK
+0x8F U+2590 # RIGHT HALF BLOCK
+0x90 U+2591 # LIGHT SHADE
+0x91 U+2592 # MEDIUM SHADE
+0x92 U+2593 # DARK SHADE
+0x93 U+2320 # TOP HALF INTEGRAL
+0x94 U+25A0 # BLACK SQUARE
+0x95 U+2219 # BULLET OPERATOR
+0x96 U+221A # SQUARE ROOT
+0x97 U+2248 # ALMOST EQUAL TO
+0x98 U+2264 # LESS THAN OR EQUAL TO
+0x99 U+2265 # GREATER THAN OR EQUAL TO
+0x9A U+00A0 # NO-BREAK SPACE
+0x9B U+2321 # BOTTOM HALF INTEGRAL
+0x9C U+00B0 # DEGREE SIGN
+0x9D U+00B2 # SUPERSCRIPT TWO
+0x9E U+00B7 # MIDDLE DOT
+0x9F U+00F7 # DIVISION SIGN
+0xA0 U+2550 # BOX DRAWINGS DOUBLE HORIZONTAL
+0xA1 U+2551 # BOX DRAWINGS DOUBLE VERTICAL
+0xA2 U+2552 # BOX DRAWINGS DOWN SINGLE AND RIGHT DOUBLE
+0xA3 U+0451 # CYRILLIC SMALL LETTER IO
+0xA4 U+0454 # CYRILLIC SMALL LETTER UKRAINIAN IE
+0xA5 U+2554 # BOX DRAWINGS DOUBLE DOWN AND RIGHT
+0xA6 U+0456 # CYRILLIC SMALL LETTER BYELORUSSIAN-UKRAINIAN I
+0xA7 U+0457 # CYRILLIC SMALL LETTER YI (UKRAINIAN)
+0xA8 U+2557 # BOX DRAWINGS DOUBLE DOWN AND LEFT
+0xA9 U+2558 # BOX DRAWINGS UP SINGLE AND RIGHT DOUBLE
+0xAA U+2559 # BOX DRAWINGS UP DOUBLE AND RIGHT SINGLE
+0xAB U+255A # BOX DRAWINGS DOUBLE UP AND RIGHT
+0xAC U+255B # BOX DRAWINGS UP SINGLE AND LEFT DOUBLE
+0xAD U+0491 # CYRILLIC SMALL LETTER GHE WITH UPTURN
+0xAE U+255D # BOX DRAWINGS DOUBLE UP AND LEFT
+0xAF U+255E # BOX DRAWINGS VERTICAL SINGLE AND RIGHT DOUBLE
+0xB0 U+255F # BOX DRAWINGS VERTICAL DOUBLE AND RIGHT SINGLE
+0xB1 U+2560 # BOX DRAWINGS DOUBLE VERTICAL AND RIGHT
+0xB2 U+2561 # BOX DRAWINGS VERTICAL SINGLE AND LEFT DOUBLE
+0xB3 U+0401 # CYRILLIC CAPITAL LETTER IO
+0xB4 U+0404 # CYRILLIC CAPITAL LETTER UKRAINIAN IE
+0xB5 U+2563 # BOX DRAWINGS DOUBLE VERTICAL AND LEFT
+0xB6 U+0406 # CYRILLIC CAPITAL LETTER BYELORUSSIAN-UKRAINIAN I
+0xB7 U+0407 # CYRILLIC CAPITAL LETTER YI (UKRAINIAN)
+0xB8 U+2566 # BOX DRAWINGS DOUBLE DOWN AND HORIZONTAL
+0xB9 U+2567 # BOX DRAWINGS UP SINGLE AND HORIZONTAL DOUBLE
+0xBA U+2568 # BOX DRAWINGS UP DOUBLE AND HORIZONTAL SINGLE
+0xBB U+2569 # BOX DRAWINGS DOUBLE UP AND HORIZONTAL
+0xBC U+256A # BOX DRAWINGS VERTICAL SINGLE AND HORIZONTAL DOUBLE
+0xBD U+0490 # CYRILLIC CAPITAL LETTER GHE WITH UPTURN
+0xBE U+256C # BOX DRAWINGS DOUBLE VERTICAL AND HORIZONTAL
+0xBF U+00A9 # COPYRIGHT SIGN
+0xC0 U+044E # CYRILLIC SMALL LETTER YU
+0xC1 U+0430 # CYRILLIC SMALL LETTER A
+0xC2 U+0431 # CYRILLIC SMALL LETTER BE
+0xC3 U+0446 # CYRILLIC SMALL LETTER TSE
+0xC4 U+0434 # CYRILLIC SMALL LETTER DE
+0xC5 U+0435 # CYRILLIC SMALL LETTER IE
+0xC6 U+0444 # CYRILLIC SMALL LETTER EF
+0xC7 U+0433 # CYRILLIC SMALL LETTER GHE
+0xC8 U+0445 # CYRILLIC SMALL LETTER KHA
+0xC9 U+0438 # CYRILLIC SMALL LETTER I
+0xCA U+0439 # CYRILLIC SMALL LETTER SHORT I
+0xCB U+043A # CYRILLIC SMALL LETTER KA
+0xCC U+043B # CYRILLIC SMALL LETTER EL
+0xCD U+043C # CYRILLIC SMALL LETTER EM
+0xCE U+043D # CYRILLIC SMALL LETTER EN
+0xCF U+043E # CYRILLIC SMALL LETTER O
+0xD0 U+043F # CYRILLIC SMALL LETTER PE
+0xD1 U+044F # CYRILLIC SMALL LETTER YA
+0xD2 U+0440 # CYRILLIC SMALL LETTER ER
+0xD3 U+0441 # CYRILLIC SMALL LETTER ES
+0xD4 U+0442 # CYRILLIC SMALL LETTER TE
+0xD5 U+0443 # CYRILLIC SMALL LETTER U
+0xD6 U+0436 # CYRILLIC SMALL LETTER ZHE
+0xD7 U+0432 # CYRILLIC SMALL LETTER VE
+0xD8 U+044C # CYRILLIC SMALL LETTER SOFT SIGN
+0xD9 U+044B # CYRILLIC SMALL LETTER YERU
+0xDA U+0437 # CYRILLIC SMALL LETTER ZE
+0xDB U+0448 # CYRILLIC SMALL LETTER SHA
+0xDC U+044D # CYRILLIC SMALL LETTER E
+0xDD U+0449 # CYRILLIC SMALL LETTER SHCHA
+0xDE U+0447 # CYRILLIC SMALL LETTER CHE
+0xDF U+044A # CYRILLIC SMALL LETTER HARD SIGN
+0xE0 U+042E # CYRILLIC CAPITAL LETTER YU
+0xE1 U+0410 # CYRILLIC CAPITAL LETTER A
+0xE2 U+0411 # CYRILLIC CAPITAL LETTER BE
+0xE3 U+0426 # CYRILLIC CAPITAL LETTER TSE
+0xE4 U+0414 # CYRILLIC CAPITAL LETTER DE
+0xE5 U+0415 # CYRILLIC CAPITAL LETTER IE
+0xE6 U+0424 # CYRILLIC CAPITAL LETTER EF
+0xE7 U+0413 # CYRILLIC CAPITAL LETTER GHE
+0xE8 U+0425 # CYRILLIC CAPITAL LETTER KHA
+0xE9 U+0418 # CYRILLIC CAPITAL LETTER I
+0xEA U+0419 # CYRILLIC CAPITAL LETTER SHORT I
+0xEB U+041A # CYRILLIC CAPITAL LETTER KA
+0xEC U+041B # CYRILLIC CAPITAL LETTER EL
+0xED U+041C # CYRILLIC CAPITAL LETTER EM
+0xEE U+041D # CYRILLIC CAPITAL LETTER EN
+0xEF U+041E # CYRILLIC CAPITAL LETTER O
+0xF0 U+041F # CYRILLIC CAPITAL LETTER PE
+0xF1 U+042F # CYRILLIC CAPITAL LETTER YA
+0xF2 U+0420 # CYRILLIC CAPITAL LETTER ER
+0xF3 U+0421 # CYRILLIC CAPITAL LETTER ES
+0xF4 U+0422 # CYRILLIC CAPITAL LETTER TE
+0xF5 U+0423 # CYRILLIC CAPITAL LETTER U
+0xF6 U+0416 # CYRILLIC CAPITAL LETTER ZHE
+0xF7 U+0412 # CYRILLIC CAPITAL LETTER VE
+0xF8 U+042C # CYRILLIC CAPITAL LETTER SOFT SIGN
+0xF9 U+042B # CYRILLIC CAPITAL LETTER YERU
+0xFA U+0417 # CYRILLIC CAPITAL LETTER ZE
+0xFB U+0428 # CYRILLIC CAPITAL LETTER SHA
+0xFC U+042D # CYRILLIC CAPITAL LETTER E
+0xFD U+0429 # CYRILLIC CAPITAL LETTER SHCHA
+0xFE U+0427 # CYRILLIC CAPITAL LETTER CHE
+0xFF U+042A # CYRILLIC CAPITAL LETTER HARD SIGN
diff --git a/src/chrtrans/mac_uni.tbl b/src/chrtrans/mac_uni.tbl
new file mode 100644
index 0000000..2564701
--- /dev/null
+++ b/src/chrtrans/mac_uni.tbl
@@ -0,0 +1,284 @@
+# This file has been modified for lynx (see README.tables)
+
+#The MIME name of this charset.
+Mmacintosh
+
+#Name as a Display Charset (used on Options screen)
+OMacintosh (8 bit)
+
+#
+# Name: cp10000_MacRoman to Unicode table
+# Unicode version: 2.0
+# Table version: 2.00
+# Table format: Format A
+# Date: 04/24/96
+# Authors: Lori Brownell <loribr@microsoft.com>
+# K.D. Chang <a-kchang@microsoft.com>
+# General notes: none
+#
+# Format: Three tab-separated columns
+# Column #1 is the cp10000_MacRoman code (in hex)
+# Column #2 is the Unicode (in hex as 0xXXXX)
+# Column #3 is the Unicode name (follows a comment sign, '#')
+#
+# The entries are in cp10000_MacRoman order
+#
+# Lines with more than one Unicode (U+XXXX) value contain additional
+# replacement mappings added for lynx. - kw
+#
+0x20-0x7f idem
+#
+#0x20 U+0020 # SPACE
+#0x21 U+0021 # EXCLAMATION MARK
+#0x22 U+0022 # QUOTATION MARK
+#0x23 U+0023 # NUMBER SIGN
+#0x24 U+0024 # DOLLAR SIGN
+#0x25 U+0025 # PERCENT SIGN
+#0x26 U+0026 # AMPERSAND
+#0x27 U+0027 # APOSTROPHE
+#0x28 U+0028 # LEFT PARENTHESIS
+#0x29 U+0029 # RIGHT PARENTHESIS
+#0x2A U+002A # ASTERISK
+#0x2B U+002B # PLUS SIGN
+#0x2C U+002C # COMMA
+#0x2D U+002D # HYPHEN-MINUS
+#0x2E U+002E # FULL STOP
+#0x2F U+002F # SOLIDUS
+#0x30 U+0030 # DIGIT ZERO
+#0x31 U+0031 # DIGIT ONE
+#0x32 U+0032 # DIGIT TWO
+#0x33 U+0033 # DIGIT THREE
+#0x34 U+0034 # DIGIT FOUR
+#0x35 U+0035 # DIGIT FIVE
+#0x36 U+0036 # DIGIT SIX
+#0x37 U+0037 # DIGIT SEVEN
+#0x38 U+0038 # DIGIT EIGHT
+#0x39 U+0039 # DIGIT NINE
+#0x3A U+003A # COLON
+#0x3B U+003B # SEMICOLON
+#0x3C U+003C # LESS-THAN SIGN
+#0x3D U+003D # EQUALS SIGN
+#0x3E U+003E # GREATER-THAN SIGN
+#0x3F U+003F # QUESTION MARK
+#0x40 U+0040 # COMMERCIAL AT
+#0x41 U+0041 # LATIN CAPITAL LETTER A
+#0x42 U+0042 # LATIN CAPITAL LETTER B
+#0x43 U+0043 # LATIN CAPITAL LETTER C
+#0x44 U+0044 # LATIN CAPITAL LETTER D
+#0x45 U+0045 # LATIN CAPITAL LETTER E
+#0x46 U+0046 # LATIN CAPITAL LETTER F
+#0x47 U+0047 # LATIN CAPITAL LETTER G
+#0x48 U+0048 # LATIN CAPITAL LETTER H
+#0x49 U+0049 # LATIN CAPITAL LETTER I
+#0x4A U+004A # LATIN CAPITAL LETTER J
+#0x4B U+004B # LATIN CAPITAL LETTER K
+#0x4C U+004C # LATIN CAPITAL LETTER L
+#0x4D U+004D # LATIN CAPITAL LETTER M
+#0x4E U+004E # LATIN CAPITAL LETTER N
+#0x4F U+004F # LATIN CAPITAL LETTER O
+#0x50 U+0050 # LATIN CAPITAL LETTER P
+#0x51 U+0051 # LATIN CAPITAL LETTER Q
+#0x52 U+0052 # LATIN CAPITAL LETTER R
+#0x53 U+0053 # LATIN CAPITAL LETTER S
+#0x54 U+0054 # LATIN CAPITAL LETTER T
+#0x55 U+0055 # LATIN CAPITAL LETTER U
+#0x56 U+0056 # LATIN CAPITAL LETTER V
+#0x57 U+0057 # LATIN CAPITAL LETTER W
+#0x58 U+0058 # LATIN CAPITAL LETTER X
+#0x59 U+0059 # LATIN CAPITAL LETTER Y
+#0x5A U+005A # LATIN CAPITAL LETTER Z
+#0x5B U+005B # LEFT SQUARE BRACKET
+#0x5C U+005C # REVERSE SOLIDUS
+#0x5D U+005D # RIGHT SQUARE BRACKET
+#0x5E U+005E # CIRCUMFLEX ACCENT
+#0x5F U+005F # LOW LINE
+#0x60 U+0060 # GRAVE ACCENT
+#0x61 U+0061 # LATIN SMALL LETTER A
+#0x62 U+0062 # LATIN SMALL LETTER B
+#0x63 U+0063 # LATIN SMALL LETTER C
+#0x64 U+0064 # LATIN SMALL LETTER D
+#0x65 U+0065 # LATIN SMALL LETTER E
+#0x66 U+0066 # LATIN SMALL LETTER F
+#0x67 U+0067 # LATIN SMALL LETTER G
+#0x68 U+0068 # LATIN SMALL LETTER H
+#0x69 U+0069 # LATIN SMALL LETTER I
+#0x6A U+006A # LATIN SMALL LETTER J
+#0x6B U+006B # LATIN SMALL LETTER K
+#0x6C U+006C # LATIN SMALL LETTER L
+#0x6D U+006D # LATIN SMALL LETTER M
+#0x6E U+006E # LATIN SMALL LETTER N
+#0x6F U+006F # LATIN SMALL LETTER O
+#0x70 U+0070 # LATIN SMALL LETTER P
+#0x71 U+0071 # LATIN SMALL LETTER Q
+#0x72 U+0072 # LATIN SMALL LETTER R
+#0x73 U+0073 # LATIN SMALL LETTER S
+#0x74 U+0074 # LATIN SMALL LETTER T
+#0x75 U+0075 # LATIN SMALL LETTER U
+#0x76 U+0076 # LATIN SMALL LETTER V
+#0x77 U+0077 # LATIN SMALL LETTER W
+#0x78 U+0078 # LATIN SMALL LETTER X
+#0x79 U+0079 # LATIN SMALL LETTER Y
+#0x7A U+007A # LATIN SMALL LETTER Z
+#0x7B U+007B # LEFT CURLY BRACKET
+#0x7C U+007C # VERTICAL LINE
+#0x7D U+007D # RIGHT CURLY BRACKET
+#0x7E U+007E # TILDE
+0x80 U+00C4 # LATIN CAPITAL LETTER A WITH DIAERESIS
+0x81 U+00C5 # LATIN CAPITAL LETTER A WITH RING ABOVE
+0x82 U+00C7 # LATIN CAPITAL LETTER C WITH CEDILLA
+0x83 U+00C9 # LATIN CAPITAL LETTER E WITH ACUTE
+0x84 U+00D1 # LATIN CAPITAL LETTER N WITH TILDE
+0x85 U+00D6 # LATIN CAPITAL LETTER O WITH DIAERESIS
+0x86 U+00DC # LATIN CAPITAL LETTER U WITH DIAERESIS
+0x87 U+00E1 # LATIN SMALL LETTER A WITH ACUTE
+0x88 U+00E0 # LATIN SMALL LETTER A WITH GRAVE
+0x89 U+00E2 # LATIN SMALL LETTER A WITH CIRCUMFLEX
+0x8A U+00E4 # LATIN SMALL LETTER A WITH DIAERESIS
+0x8B U+00E3 # LATIN SMALL LETTER A WITH TILDE
+0x8C U+00E5 # LATIN SMALL LETTER A WITH RING ABOVE
+0x8D U+00E7 # LATIN SMALL LETTER C WITH CEDILLA
+0x8E U+00E9 # LATIN SMALL LETTER E WITH ACUTE
+0x8F U+00E8 # LATIN SMALL LETTER E WITH GRAVE
+0x90 U+00EA # LATIN SMALL LETTER E WITH CIRCUMFLEX
+0x91 U+00EB # LATIN SMALL LETTER E WITH DIAERESIS
+0x92 U+00ED # LATIN SMALL LETTER I WITH ACUTE
+0x93 U+00EC # LATIN SMALL LETTER I WITH GRAVE
+0x94 U+00EE # LATIN SMALL LETTER I WITH CIRCUMFLEX
+0x95 U+00EF # LATIN SMALL LETTER I WITH DIAERESIS
+0x96 U+00F1 # LATIN SMALL LETTER N WITH TILDE
+0x97 U+00F3 # LATIN SMALL LETTER O WITH ACUTE
+0x98 U+00F2 # LATIN SMALL LETTER O WITH GRAVE
+0x99 U+00F4 # LATIN SMALL LETTER O WITH CIRCUMFLEX
+0x9A U+00F6 # LATIN SMALL LETTER O WITH DIAERESIS
+0x9B U+00F5 # LATIN SMALL LETTER O WITH TILDE
+0x9C U+00FA # LATIN SMALL LETTER U WITH ACUTE
+0x9D U+00F9 # LATIN SMALL LETTER U WITH GRAVE
+0x9E U+00FB # LATIN SMALL LETTER U WITH CIRCUMFLEX
+0x9F U+00FC # LATIN SMALL LETTER U WITH DIAERESIS
+0xA0 U+2020 # DAGGER
+0xA1 U+00B0 # DEGREE SIGN
+0xA2 U+00A2 # CENT SIGN
+0xA3 U+00A3 # POUND SIGN
+0xA4 U+00A7 # SECTION SIGN
+0xA5 U+2022 # BULLET
+0xA6 U+00B6 # PILCROW SIGN
+0xA7 U+00DF # LATIN SMALL LETTER SHARP S
+0xA8 U+00AE # REGISTERED SIGN
+0xA9 U+00A9 # COPYRIGHT SIGN
+0xAA U+2122 # TRADE MARK SIGN
+0xAB U+00B4 # ACUTE ACCENT
+0xAC U+00A8 # DIAERESIS
+0xAD U+2260 # NOT EQUAL TO
+0xAE U+00C6 # LATIN CAPITAL LIGATURE AE
+0xAF U+00D8 # LATIN CAPITAL LETTER O WITH STROKE
+0xB0 U+221E # INFINITY
+0xB1 U+00B1 # PLUS-MINUS SIGN
+0xB2 U+2264 # LESS-THAN OR EQUAL TO
+0xB3 U+2265 # GREATER-THAN OR EQUAL TO
+0xB4 U+00A5 # YEN SIGN
+0xB5 U+00B5 # MICRO SIGN
+0xB6 U+2202 # PARTIAL DIFFERENTIAL
+0xB7 U+2211 # N-ARY SUMMATION
+0xB8 U+220F # N-ARY PRODUCT
+0xB9 U+03C0 # GREEK SMALL LETTER PI
+0xBA U+222B # INTEGRAL
+0xBB U+00AA # FEMININE ORDINAL INDICATOR
+0xBC U+00BA # MASCULINE ORDINAL INDICATOR
+0xBD U+2126 # OHM SIGN
+0xBE U+00E6 # LATIN SMALL LIGATURE AE
+0xBF U+00F8 # LATIN SMALL LETTER O WITH STROKE
+0xC0 U+00BF # INVERTED QUESTION MARK
+0xC1 U+00A1 # INVERTED EXCLAMATION MARK
+0xC2 U+00AC # NOT SIGN
+0xC3 U+221A # SQUARE ROOT
+0xC4 U+0192 # LATIN SMALL LETTER F WITH HOOK
+0xC5 U+2248 # ALMOST EQUAL TO
+0xC6 U+2206 # INCREMENT
+0xC7 U+00AB # LEFT-POINTING DOUBLE ANGLE QUOTATION MARK
+0xC8 U+00BB # RIGHT-POINTING DOUBLE ANGLE QUOTATION MARK
+0xC9 U+2026 # HORIZONTAL ELLIPSIS
+0xCA U+00A0 # NO-BREAK SPACE
+0xCB U+00C0 # LATIN CAPITAL LETTER A WITH GRAVE
+0xCC U+00C3 # LATIN CAPITAL LETTER A WITH TILDE
+0xCD U+00D5 # LATIN CAPITAL LETTER O WITH TILDE
+0xCE U+0152 # LATIN CAPITAL LIGATURE OE
+0xCF U+0153 # LATIN SMALL LIGATURE OE
+0xD0 U+2013 # EN DASH
+0xD1 U+2014 # EM DASH
+0xD2 U+201C # LEFT DOUBLE QUOTATION MARK
+0xD3 U+201D # RIGHT DOUBLE QUOTATION MARK
+0xD4 U+2018 # LEFT SINGLE QUOTATION MARK
+0xD5 U+2019 # RIGHT SINGLE QUOTATION MARK
+0xD6 U+00F7 # DIVISION SIGN
+0xD7 U+25CA # LOZENGE
+0xD8 U+00FF # LATIN SMALL LETTER Y WITH DIAERESIS
+0xD9 U+0178 # LATIN CAPITAL LETTER Y WITH DIAERESIS
+0xDA U+2044 # FRACTION SLASH
+0xDB U+00A4 # CURRENCY SIGN
+0xDC U+2039 # SINGLE LEFT-POINTING ANGLE QUOTATION MARK
+0xDD U+203A # SINGLE RIGHT-POINTING ANGLE QUOTATION MARK
+0xDE U+FB01 # LATIN SMALL LIGATURE FI
+0xDF U+FB02 # LATIN SMALL LIGATURE FL
+0xE0 U+2021 # DOUBLE DAGGER
+0xE1 U+00B7 U+0307 U+0387 U+2027 # MIDDLE DOT
+0xE2 U+201A # SINGLE LOW-9 QUOTATION MARK
+0xE3 U+201E # DOUBLE LOW-9 QUOTATION MARK
+0xE4 U+2030 # PER MILLE SIGN
+0xE5 U+00C2 # LATIN CAPITAL LETTER A WITH CIRCUMFLEX
+0xE6 U+00CA # LATIN CAPITAL LETTER E WITH CIRCUMFLEX
+0xE7 U+00C1 # LATIN CAPITAL LETTER A WITH ACUTE
+0xE8 U+00CB # LATIN CAPITAL LETTER E WITH DIAERESIS
+0xE9 U+00C8 # LATIN CAPITAL LETTER E WITH GRAVE
+0xEA U+00CD # LATIN CAPITAL LETTER I WITH ACUTE
+0xEB U+00CE # LATIN CAPITAL LETTER I WITH CIRCUMFLEX
+0xEC U+00CF # LATIN CAPITAL LETTER I WITH DIAERESIS
+0xED U+00CC # LATIN CAPITAL LETTER I WITH GRAVE
+0xEE U+00D3 # LATIN CAPITAL LETTER O WITH ACUTE
+0xEF U+00D4 # LATIN CAPITAL LETTER O WITH CIRCUMFLEX
+0xF0 # UNDEFINED
+0xF1 U+00D2 # LATIN CAPITAL LETTER O WITH GRAVE
+0xF2 U+00DA # LATIN CAPITAL LETTER U WITH ACUTE
+0xF3 U+00DB # LATIN CAPITAL LETTER U WITH CIRCUMFLEX
+0xF4 U+00D9 # LATIN CAPITAL LETTER U WITH GRAVE
+0xF5 U+0131 # LATIN SMALL LETTER DOTLESS I
+0xF6 U+02C6 # MODIFIER LETTER CIRCUMFLEX ACCENT
+0xF7 U+02DC # SMALL TILDE
+0xF8 U+00AF # MACRON
+0xF9 U+02D8 # BREVE
+0xFA U+02D9 # DOT ABOVE
+0xFB U+02DA # RING ABOVE
+0xFC U+00B8 # CEDILLA
+0xFD U+02DD # DOUBLE ACUTE ACCENT
+0xFE U+02DB # OGONEK
+0xFF U+02C7 # CARON
+#
+# broken vertical bar (&#166;) - brvbar, brkbar
+U+00A6:|
+# superscript 3 (&#179;) - sup3
+U+00B3:^3
+# superscript 2 (&#178;) - sup2
+U+00B2:^2
+# superscript 1 (&#185;) - sup1
+U+00B9:^1
+# fraction 1/4 (&#188;) - frac14
+U+00BC: 1/4
+# fraction 1/2 (&#189;) - frac12
+U+00BD: 1/2
+# fraction 3/4 (&#190;) - frac34
+U+00BE: 3/4
+# capital Eth, Icelandic (&#208;) - ETH
+U+00D0:DH
+# Dj # capital D with stroke - Dstrok
+# capital Y, acute accent (&#221;) - Yacute
+U+00DD:Y'
+# capital THORN, Icelandic (&#222;) - THORN
+U+00DE:P
+# multiplication sign (&#215;) - times
+U+00D7:*
+# small eth, Icelandic (&#240;) - eth
+U+00F0:dh
+# small y, acute accent (&#253;) - yacute
+U+00FD:y'
+# small thorn, Icelandic (&#254;) - thorn
+U+00FE:p
+#
diff --git a/src/chrtrans/make-msc.bat b/src/chrtrans/make-msc.bat
new file mode 100644
index 0000000..81d615e
--- /dev/null
+++ b/src/chrtrans/make-msc.bat
@@ -0,0 +1,6 @@
+@rem $LynxId: make-msc.bat,v 1.6 2008/02/18 00:34:44 tom Exp $
+@echo off
+
+nmake -f makefile.msc %1 %2 %3 %4 %5 %6 %7 %8 %9
+
+if exist makeuctb.exe call makehdrs
diff --git a/src/chrtrans/makefile.bcb b/src/chrtrans/makefile.bcb
new file mode 100644
index 0000000..134a395
--- /dev/null
+++ b/src/chrtrans/makefile.bcb
@@ -0,0 +1,123 @@
+#
+# Borland C++ IDE generated makefile
+#
+# 1997/11/09 (Sun) 14:29:50
+#
+.AUTODEPEND
+
+
+#
+# Borland C++ tools
+#
+IMPLIB = Implib
+BCC32 = Bcc32 +BccW32.cfg
+TLINK32 = TLink32
+TLIB = TLib
+BRC32 = Brc32
+TASM32 = Tasm32
+#
+# macros
+#
+BCB = $(MAKEDIR)/..
+BCC_INC = $(BCB)/INCLUDE
+
+#
+# Options
+#
+
+INCLUDES = -I.;../..;../../WWW/LIBRARY/IMPLEMENTATION;$(BCC_INC)
+DEFS =-DNO_FILIO_H;NO_UNISTD_H;_WINDOWS;DOSPATH
+LNIEAT_dbmakeuctbdexe = -x
+
+#
+# Dependency List
+#
+Dep_char = .\makeuctb.exe
+
+char : BccW32.cfg $(Dep_char)
+ echo MakeNode
+
+Dep_dbmakeuctbdexe = .\makeuctb.obj
+
+.\makeuctb.exe : $(Dep_dbmakeuctbdexe)
+ $(BCC32) makeuctb.obj
+
+###
+.\makeuctb.obj : makeuctb.c
+ $(BCC32) -P- -c $(DEFS) $(INCLUDES) -o$@ makeuctb.c
+
+# Compiler configuration file
+BccW32.cfg :
+ Copy &&|
+-R
+-v
+-vi
+-H
+-H=lynx.csm
+-w-
+-A-
+-wcpt
+-wrpt
+-wrng
+-w-voi
+-w-ret
+-w-sus
+-w-dup
+-w-big
+-w-ext
+-w-zdi
+-w-bei
+-w-obi
+-w-ofp
+-w-eas
+-w-hid
+-w-ncf
+-w-ibc
+-w-dsz
+-w-nst
+-w-mpc
+-w-mpd
+-w-ntd
+-w-nvf
+-w-hch
+-w-inl
+-w-lin
+-w-lvc
+-w-pia
+-w-def
+-w-nod
+-w-pro
+-w-rvl
+-w-ccc
+-w-aus
+-w-par
+-w-rch
+-w-eff
+-w-ill
+-w-ias
+-w-msg
+-WC
+-Ot
+-d-
+-K
+-a-
+-w-stu
+-wbbf
+-w-dpu
+-wcln
+-wsig
+-wucp
+-g200
+-H-
+-v-
+| $@
+
+clean :
+ -del *_uni.h
+ -del *_suni.h
+ -del *.exe
+ -del *.map
+ -del *.obj
+ -del *.tds
+ -del BccW32.cfg
+ -del /f/s/q *.i
diff --git a/src/chrtrans/makefile.dos b/src/chrtrans/makefile.dos
new file mode 100644
index 0000000..9878637
--- /dev/null
+++ b/src/chrtrans/makefile.dos
@@ -0,0 +1,137 @@
+#
+# Makefile for the makeuctb and unicode tables
+# for use with DJGPP.
+#
+# Type make to build makeuctb and all character translation maps.
+# Type make fontmap to build makeuctb and translation map iso8859-1.
+# Type make makeuctb.exe to build makeuctb only.
+# Type make clean to remove makeuctb and character translation maps.
+# Type make distclean to remove makeuctb, character translation maps
+# and .bak files.
+#
+CFLAGS = $(MCFLAGS)
+
+CC = gcc
+MCFLAGS = -O2 -DDOSPATH \
+-I. \
+-I../../WWW/Library/Implementation \
+-I/djgpp/watt32/inc
+-I../..
+
+.SUFFIXES: .tbl
+#
+# This file contains the font map for the default (hardware) font
+#
+
+FONTMAP_INC = iso01_un.h
+
+TABLES= \
+ cp1250_uni.h \
+ cp1251_uni.h \
+ cp1252_uni.h \
+ cp1253_uni.h \
+ cp1255_uni.h \
+ cp1256_uni.h \
+ cp1257_uni.h \
+ cp437_uni.h \
+ cp737_uni.h \
+ cp775_uni.h \
+ cp850_uni.h \
+ cp852_uni.h \
+ cp857_uni.h \
+ cp862_uni.h \
+ cp864_uni.h \
+ cp866_uni.h \
+ cp866u_uni.h \
+ cp869_uni.h \
+ def7_uni.h \
+ dmcs_uni.h \
+ hp_uni.h \
+ iso01_uni.h \
+ iso02_uni.h \
+ iso03_uni.h \
+ iso04_uni.h \
+ iso05_uni.h \
+ iso06_uni.h \
+ iso07_uni.h \
+ iso08_uni.h \
+ iso09_uni.h \
+ iso10_uni.h \
+ iso13_uni.h \
+ iso14_uni.h \
+ iso15_uni.h \
+ iso16_uni.h \
+ koi8r_uni.h \
+ koi8u_uni.h \
+ mac_uni.h \
+ mnem2_suni.h \
+ mnem_suni.h \
+ next_uni.h \
+ pt154_uni.h \
+ rfc_suni.h \
+ utf8_uni.h \
+ viscii_uni.h
+
+default: $(TABLES)
+
+fontmap: $(FONTMAP_INC)
+
+makeuctb.exe: makeuctb.c UCkd.h
+ $(CC) $(CFLAGS) -o makeuctb.exe makeuctb.c
+ strip makeuctb.exe
+
+.tbl.h:
+ ./makeuctb $*.tbl
+
+cp1250_uni.h: cp1250_uni.tbl makeuctb.exe
+cp1251_uni.h: cp1251_uni.tbl makeuctb.exe
+cp1252_uni.h: cp1252_uni.tbl makeuctb.exe
+cp1253_uni.h: cp1253_uni.tbl makeuctb.exe
+cp1255_uni.h: cp1255_uni.tbl makeuctb.exe
+cp1256_uni.h: cp1256_uni.tbl makeuctb.exe
+cp1257_uni.h: cp1257_uni.tbl makeuctb.exe
+cp437_uni.h: cp437_uni.tbl makeuctb.exe
+cp737_uni.h: cp737_uni.tbl makeuctb.exe
+cp775_uni.h: cp775_uni.tbl makeuctb.exe
+cp850_uni.h: cp850_uni.tbl makeuctb.exe
+cp852_uni.h: cp852_uni.tbl makeuctb.exe
+cp857_uni.h: cp857_uni.tbl makeuctb.exe
+cp862_uni.h: cp862_uni.tbl makeuctb.exe
+cp864_uni.h: cp864_uni.tbl makeuctb.exe
+cp866_uni.h: cp866_uni.tbl makeuctb.exe
+cp866u_uni.h: cp866u_uni.tbl makeuctb.exe
+cp869_uni.h: cp869_uni.tbl makeuctb.exe
+def7_uni.h: def7_uni.tbl makeuctb.exe
+dmcs_uni.h: dmcs_uni.tbl makeuctb.exe
+hp_uni.h: hp_uni.tbl makeuctb.exe
+iso01_uni.h: iso01_uni.tbl makeuctb.exe
+iso02_uni.h: iso02_uni.tbl makeuctb.exe
+iso03_uni.h: iso03_uni.tbl makeuctb.exe
+iso04_uni.h: iso04_uni.tbl makeuctb.exe
+iso05_uni.h: iso05_uni.tbl makeuctb.exe
+iso06_uni.h: iso06_uni.tbl makeuctb.exe
+iso07_uni.h: iso07_uni.tbl makeuctb.exe
+iso08_uni.h: iso08_uni.tbl makeuctb.exe
+iso09_uni.h: iso09_uni.tbl makeuctb.exe
+iso10_uni.h: iso10_uni.tbl makeuctb.exe
+iso13_uni.h: iso13_uni.tbl makeuctb.exe
+iso14_uni.h: iso14_uni.tbl makeuctb.exe
+iso15_uni.h: iso15_uni.tbl makeuctb.exe
+iso16_uni.h: iso16_uni.tbl makeuctb.exe
+koi8r_uni.h: koi8r_uni.tbl makeuctb.exe
+koi8u_uni.h: koi8u_uni.tbl makeuctb.exe
+mac_uni.h: mac_uni.tbl makeuctb.exe
+mnem2_suni.h: mnem2_suni.tbl makeuctb.exe
+mnem_suni.h: mnem_suni.tbl makeuctb.exe
+next_uni.h: next_uni.tbl makeuctb.exe
+pt154_uni.h: pt154_uni.tbl makeuctb.exe
+rfc_suni.h: rfc_suni.tbl makeuctb.exe
+utf8_uni.h: utf8_uni.tbl makeuctb.exe
+viscii_uni.h: viscii_uni.tbl makeuctb.exe
+
+clean:
+ rm -f makeuctb.exe makeuctb *.o *un.h *u.h *c.h *i.h
+
+distclean: clean
+ -rm -f *.bak
+
diff --git a/src/chrtrans/makefile.in b/src/chrtrans/makefile.in
new file mode 100644
index 0000000..8eb4e58
--- /dev/null
+++ b/src/chrtrans/makefile.in
@@ -0,0 +1,201 @@
+# $LynxId: makefile.in,v 1.45 2021/03/14 17:14:26 emil Exp $
+#
+# Makefile for the makeuctb and unicode tables.
+#
+# This may not yet work for the general case.
+# Only some dependencies included.
+#
+SHELL = @CONFIG_SHELL@
+
+prefix = @prefix@
+exec_prefix = @exec_prefix@
+datarootdir = @datarootdir@
+top_srcdir = @top_srcdir@
+srcdir = @srcdir@
+VPATH = $(srcdir)
+
+top_builddir = ../..
+
+CC = @CC@
+CPP = @CPP@
+CFLAGS = @CFLAGS@ @EXTRA_CPPFLAGS@
+CPPFLAGS = @CPPFLAGS@
+
+WWWINC = WWW/Library/Implementation
+
+SITE_DEFS = # FIXME: set in parent makefile
+
+BUILD_CC = @BUILD_CC@
+BUILD_CPP = @BUILD_CPP@
+BUILD_CFLAGS = @BUILD_CFLAGS@
+BUILD_CPPFLAGS = @BUILD_CPPFLAGS@ @DEFS@
+
+BUILD_LIBS = @BUILD_LIBS@
+BUILD_LDFLAGS = @BUILD_LDFLAGS@
+
+x = @EXEEXT@
+o = .@BUILD_OBJEXT@
+BUILD_EXEEXT = @BUILD_EXEEXT@
+
+CPP_OPTS = \
+ -I$(top_builddir) \
+ -I$(top_srcdir)/src \
+ -I$(top_srcdir)/src/chrtrans \
+ -I$(top_srcdir)/$(WWWINC) \
+ -I$(top_srcdir)/ \
+ $(SITE_DEFS) $(BUILD_CPPFLAGS)
+CC_OPTS = $(CPP_OPTS) $(BUILD_CFLAGS)
+
+LINT = @LINT@
+LINTOPTS = @LINT_OPTS@
+
+CTAGS = @CTAGS@
+
+#
+# This file contains the font map for the default (hardware) font
+#
+
+FONTMAP_INC = iso01_uni.h# default, if not set by recursive call
+
+### fastdep: $(FONTMAP_INC)
+
+MAKEUCTB = makeuctb$(BUILD_EXEEXT)
+
+TABLES= \
+ cp1250_uni.h \
+ cp1251_uni.h \
+ cp1252_uni.h \
+ cp1253_uni.h \
+ cp1255_uni.h \
+ cp1256_uni.h \
+ cp1257_uni.h \
+ cp437_uni.h \
+ cp737_uni.h \
+ cp775_uni.h \
+ cp850_uni.h \
+ cp852_uni.h \
+ cp857_uni.h \
+ cp862_uni.h \
+ cp864_uni.h \
+ cp866_uni.h \
+ cp866u_uni.h \
+ cp869_uni.h \
+ def7_uni.h \
+ dmcs_uni.h \
+ hp_uni.h \
+ iso01_uni.h \
+ iso02_uni.h \
+ iso03_uni.h \
+ iso04_uni.h \
+ iso05_uni.h \
+ iso06_uni.h \
+ iso07_uni.h \
+ iso08_uni.h \
+ iso09_uni.h \
+ iso10_uni.h \
+ iso13_uni.h \
+ iso14_uni.h \
+ iso15_uni.h \
+ iso16_uni.h \
+ koi8r_uni.h \
+ koi8u_uni.h \
+ mac_uni.h \
+ mnem2_suni.h \
+ mnem_suni.h \
+ next_uni.h \
+ next_uni.h \
+ pt154_uni.h \
+ rfc_suni.h \
+ utf8_uni.h \
+ viscii_uni.h
+
+default: $(FONTMAP_INC)
+
+all tables: $(TABLES)
+
+OBJS = makeuctb$o
+C_SRC = $(OBJS:$o=.c)
+
+$(MAKEUCTB) : $(OBJS)
+ $(BUILD_CC) $(CC_OPTS) $(BUILD_LDFLAGS) -o $@ $(OBJS) $(BUILD_LIBS)
+
+makeuctb$o : $(srcdir)/UCkd.h $(srcdir)/makeuctb.c
+
+.SUFFIXES : $o .tbl .i .h
+
+.c$o:
+ @RULE_CC@
+ @ECHO_CC@$(BUILD_CC) $(CC_OPTS) -c $(srcdir)/$*.c
+
+.c.i:
+ @RULE_CC@
+ @ECHO_CC@$(BUILD_CPP) $(CPP_OPTS) $(srcdir)/$*.c >$@
+
+.tbl.h:
+ ./$(MAKEUCTB) $(srcdir)/$*.tbl $*.h
+
+# table files listed here once again to get the make dependencies
+# right, in case makeuctb was recompiled.
+cp1250_uni.h: $(srcdir)/cp1250_uni.tbl $(MAKEUCTB)
+cp1251_uni.h: $(srcdir)/cp1251_uni.tbl $(MAKEUCTB)
+cp1252_uni.h: $(srcdir)/cp1252_uni.tbl $(MAKEUCTB)
+cp1253_uni.h: $(srcdir)/cp1253_uni.tbl $(MAKEUCTB)
+cp1255_uni.h: $(srcdir)/cp1255_uni.tbl $(MAKEUCTB)
+cp1256_uni.h: $(srcdir)/cp1256_uni.tbl $(MAKEUCTB)
+cp1257_uni.h: $(srcdir)/cp1257_uni.tbl $(MAKEUCTB)
+cp437_uni.h: $(srcdir)/cp437_uni.tbl $(MAKEUCTB)
+cp737_uni.h: $(srcdir)/cp737_uni.tbl $(MAKEUCTB)
+cp775_uni.h: $(srcdir)/cp775_uni.tbl $(MAKEUCTB)
+cp850_uni.h: $(srcdir)/cp850_uni.tbl $(MAKEUCTB)
+cp852_uni.h: $(srcdir)/cp852_uni.tbl $(MAKEUCTB)
+cp857_uni.h: $(srcdir)/cp857_uni.tbl $(MAKEUCTB)
+cp862_uni.h: $(srcdir)/cp862_uni.tbl $(MAKEUCTB)
+cp864_uni.h: $(srcdir)/cp864_uni.tbl $(MAKEUCTB)
+cp866_uni.h: $(srcdir)/cp866_uni.tbl $(MAKEUCTB)
+cp866u_uni.h: $(srcdir)/cp866u_uni.tbl $(MAKEUCTB)
+cp869_uni.h: $(srcdir)/cp869_uni.tbl $(MAKEUCTB)
+def7_uni.h: $(srcdir)/def7_uni.tbl $(MAKEUCTB)
+dmcs_uni.h: $(srcdir)/dmcs_uni.tbl $(MAKEUCTB)
+hp_uni.h: $(srcdir)/hp_uni.tbl $(MAKEUCTB)
+iso01_uni.h: $(srcdir)/iso01_uni.tbl $(MAKEUCTB)
+iso02_uni.h: $(srcdir)/iso02_uni.tbl $(MAKEUCTB)
+iso03_uni.h: $(srcdir)/iso03_uni.tbl $(MAKEUCTB)
+iso04_uni.h: $(srcdir)/iso04_uni.tbl $(MAKEUCTB)
+iso05_uni.h: $(srcdir)/iso05_uni.tbl $(MAKEUCTB)
+iso06_uni.h: $(srcdir)/iso06_uni.tbl $(MAKEUCTB)
+iso07_uni.h: $(srcdir)/iso07_uni.tbl $(MAKEUCTB)
+iso08_uni.h: $(srcdir)/iso08_uni.tbl $(MAKEUCTB)
+iso09_uni.h: $(srcdir)/iso09_uni.tbl $(MAKEUCTB)
+iso10_uni.h: $(srcdir)/iso10_uni.tbl $(MAKEUCTB)
+iso13_uni.h: $(srcdir)/iso13_uni.tbl $(MAKEUCTB)
+iso14_uni.h: $(srcdir)/iso14_uni.tbl $(MAKEUCTB)
+iso15_uni.h: $(srcdir)/iso15_uni.tbl $(MAKEUCTB)
+iso16_uni.h: $(srcdir)/iso16_uni.tbl $(MAKEUCTB)
+koi8r_uni.h: $(srcdir)/koi8r_uni.tbl $(MAKEUCTB)
+koi8u_uni.h: $(srcdir)/koi8u_uni.tbl $(MAKEUCTB)
+mac_uni.h: $(srcdir)/mac_uni.tbl $(MAKEUCTB)
+mnem2_suni.h: $(srcdir)/mnem2_suni.tbl $(MAKEUCTB)
+mnem_suni.h: $(srcdir)/mnem_suni.tbl $(MAKEUCTB)
+next_uni.h: $(srcdir)/next_uni.tbl $(MAKEUCTB)
+pt154_uni.h: $(srcdir)/pt154_uni.tbl $(MAKEUCTB)
+rfc_suni.h: $(srcdir)/rfc_suni.tbl $(MAKEUCTB)
+utf8_uni.h: $(srcdir)/utf8_uni.tbl $(MAKEUCTB)
+viscii_uni.h: $(srcdir)/viscii_uni.tbl $(MAKEUCTB)
+
+lint:
+ $(LINT) $(LINTOPTS) $(CPP_OPTS) $(C_SRC) 2>&1 |tee $(top_builddir)/lint.chrtrans
+
+tags:
+ $(CTAGS) *.[ch]
+
+clean:
+ rm -f $(MAKEUCTB) *$o *uni.h *uni2.h *.i
+
+distclean: clean
+ -rm -rf obsolete
+ rm -f core *.bak *.sav *~ *.h_old
+
+depend :
+ makedepend -fmakefile -- $(CPP_OPTS) -- $(C_SRC)
+
+# DO NOT DELETE THIS LINE -- make depend depends on it.
diff --git a/src/chrtrans/makefile.msc b/src/chrtrans/makefile.msc
new file mode 100644
index 0000000..8dd8ace
--- /dev/null
+++ b/src/chrtrans/makefile.msc
@@ -0,0 +1,139 @@
+#
+# Makefile for Microsoft Visual C++ 4.2 or later
+#
+
+CC = cl
+LD = link
+
+INCLUDES = /I "." /I ".." /I "..\.." /I "..\..\WWW\Library\Implementation" /I "..\..\lib"
+DEFS = /D "WIN32_LEAN_AND_MEAN" /D "NDEBUG" /D "__WIN32__" /D "WIN32" /D "_CONSOLE" /D "_MBCS" /D "_WIN32" /D "NO_FILEIO_H" /D "NO_UNISTD_H" /D "_WINDOWS" /D "DOSPATH"
+CFLAGS = /nologo /MT /W3 /EHsc /O2 /c
+
+MACHINE = i386
+LDFLAGS = /nologo /subsystem:console /incremental:no /machine:$(MACHINE)
+LIBS = user32.lib wsock32.lib
+
+COMPILE = $(CC) $(CFLAGS) $(INCLUDES) $(DEFS)
+LINK = $(LD) $(LDFLAGS) /out:$@
+
+
+.SUFFIXES: .tbl
+#
+# This file contains the font map for the default (hardware) font
+#
+
+FONTMAP_INC = iso01_un.h
+
+TABLES= \
+ cp1250_uni.h \
+ cp1251_uni.h \
+ cp1252_uni.h \
+ cp1253_uni.h \
+ cp1255_uni.h \
+ cp1256_uni.h \
+ cp1257_uni.h \
+ cp437_uni.h \
+ cp737_uni.h \
+ cp775_uni.h \
+ cp850_uni.h \
+ cp852_uni.h \
+ cp857_uni.h \
+ cp862_uni.h \
+ cp864_uni.h \
+ cp866_uni.h \
+ cp866u_uni.h \
+ cp869_uni.h \
+ def7_uni.h \
+ dmcs_uni.h \
+ hp_uni.h \
+ iso01_uni.h \
+ iso02_uni.h \
+ iso03_uni.h \
+ iso04_uni.h \
+ iso05_uni.h \
+ iso06_uni.h \
+ iso07_uni.h \
+ iso08_uni.h \
+ iso09_uni.h \
+ iso10_uni.h \
+ iso13_uni.h \
+ iso14_uni.h \
+ iso15_uni.h \
+ iso16_uni.h \
+ koi8r_uni.h \
+ koi8u_uni.h \
+ mac_uni.h \
+ mnem2_suni.h \
+ mnem_suni.h \
+ next_uni.h \
+ pt154_uni.h \
+ rfc_suni.h \
+ utf8_uni.h \
+ viscii_uni.h
+
+default: $(TABLES)
+
+fontmap: $(FONTMAP_INC)
+
+makeuctb.exe : makeuctb.obj
+ $(LINK) makeuctb.obj $(LIBS)
+
+makeuctb.obj : makeuctb.c
+ $(COMPILE) makeuctb.c
+
+.tbl.h:
+ makeuctb $*.tbl
+
+cp1250_uni.h: cp1250_uni.tbl makeuctb.exe
+cp1251_uni.h: cp1251_uni.tbl makeuctb.exe
+cp1252_uni.h: cp1252_uni.tbl makeuctb.exe
+cp1253_uni.h: cp1253_uni.tbl makeuctb.exe
+cp1255_uni.h: cp1255_uni.tbl makeuctb.exe
+cp1256_uni.h: cp1256_uni.tbl makeuctb.exe
+cp1257_uni.h: cp1257_uni.tbl makeuctb.exe
+cp437_uni.h: cp437_uni.tbl makeuctb.exe
+cp737_uni.h: cp737_uni.tbl makeuctb.exe
+cp775_uni.h: cp775_uni.tbl makeuctb.exe
+cp850_uni.h: cp850_uni.tbl makeuctb.exe
+cp852_uni.h: cp852_uni.tbl makeuctb.exe
+cp857_uni.h: cp857_uni.tbl makeuctb.exe
+cp862_uni.h: cp862_uni.tbl makeuctb.exe
+cp864_uni.h: cp864_uni.tbl makeuctb.exe
+cp866_uni.h: cp866_uni.tbl makeuctb.exe
+cp866u_uni.h: cp866u_uni.tbl makeuctb.exe
+cp869_uni.h: cp869_uni.tbl makeuctb.exe
+def7_uni.h: def7_uni.tbl makeuctb.exe
+dmcs_uni.h: dmcs_uni.tbl makeuctb.exe
+hp_uni.h: hp_uni.tbl makeuctb.exe
+iso01_uni.h: iso01_uni.tbl makeuctb.exe
+iso02_uni.h: iso02_uni.tbl makeuctb.exe
+iso03_uni.h: iso03_uni.tbl makeuctb.exe
+iso04_uni.h: iso04_uni.tbl makeuctb.exe
+iso05_uni.h: iso05_uni.tbl makeuctb.exe
+iso06_uni.h: iso06_uni.tbl makeuctb.exe
+iso07_uni.h: iso07_uni.tbl makeuctb.exe
+iso08_uni.h: iso08_uni.tbl makeuctb.exe
+iso09_uni.h: iso09_uni.tbl makeuctb.exe
+iso10_uni.h: iso10_uni.tbl makeuctb.exe
+iso13_uni.h: iso13_uni.tbl makeuctb.exe
+iso14_uni.h: iso14_uni.tbl makeuctb.exe
+iso15_uni.h: iso15_uni.tbl makeuctb.exe
+iso16_uni.h: iso16_uni.tbl makeuctb.exe
+koi8r_uni.h: koi8r_uni.tbl makeuctb.exe
+koi8u_uni.h: koi8u_uni.tbl makeuctb.exe
+mac_uni.h: mac_uni.tbl makeuctb.exe
+mnem2_suni.h: mnem2_suni.tbl makeuctb.exe
+mnem_suni.h: mnem_suni.tbl makeuctb.exe
+next_uni.h: next_uni.tbl makeuctb.exe
+pt154_uni.h: pt154_uni.tbl makeuctb.exe
+rfc_suni.h: rfc_suni.tbl makeuctb.exe
+utf8_uni.h: utf8_uni.tbl makeuctb.exe
+viscii_uni.h: viscii_uni.tbl makeuctb.exe
+
+clean :
+ - erase *.obj
+ - erase *.exe
+ - for %%i in ( $(TABLES) ) do erase %%i
+
+distclean : clean
+ - erase *.bak
diff --git a/src/chrtrans/makehdrs.bat b/src/chrtrans/makehdrs.bat
new file mode 100644
index 0000000..a349897
--- /dev/null
+++ b/src/chrtrans/makehdrs.bat
@@ -0,0 +1,51 @@
+@rem $LynxId: makehdrs.bat,v 1.2 2021/03/14 17:14:26 emil Exp $
+@echo If .tbl files are added or removed you will need to hand edit
+@echo this batch file.
+@echo .
+@echo off
+
+makeuctb cp1250_uni.tbl
+makeuctb cp1251_uni.tbl
+makeuctb cp1252_uni.tbl
+makeuctb cp1253_uni.tbl
+makeuctb cp1255_uni.tbl
+makeuctb cp1256_uni.tbl
+makeuctb cp1257_uni.tbl
+makeuctb cp437_uni.tbl
+makeuctb cp737_uni.tbl
+makeuctb cp775_uni.tbl
+makeuctb cp850_uni.tbl
+makeuctb cp852_uni.tbl
+makeuctb cp857_uni.tbl
+makeuctb cp862_uni.tbl
+makeuctb cp864_uni.tbl
+makeuctb cp866_uni.tbl
+makeuctb cp866u_uni.tbl
+makeuctb cp869_uni.tbl
+makeuctb def7_uni.tbl
+makeuctb dmcs_uni.tbl
+makeuctb hp_uni.tbl
+makeuctb iso01_uni.tbl
+makeuctb iso02_uni.tbl
+makeuctb iso03_uni.tbl
+makeuctb iso04_uni.tbl
+makeuctb iso05_uni.tbl
+makeuctb iso06_uni.tbl
+makeuctb iso07_uni.tbl
+makeuctb iso08_uni.tbl
+makeuctb iso09_uni.tbl
+makeuctb iso10_uni.tbl
+makeuctb iso13_uni.tbl
+makeuctb iso14_uni.tbl
+makeuctb iso15_uni.tbl
+makeuctb iso16_uni.tbl
+makeuctb koi8r_uni.tbl
+makeuctb koi8u_uni.tbl
+makeuctb mac_uni.tbl
+makeuctb mnem2_suni.tbl
+makeuctb mnem_suni.tbl
+makeuctb next_uni.tbl
+makeuctb pt154_uni.tbl
+makeuctb rfc_suni.tbl
+makeuctb utf8_uni.tbl
+makeuctb viscii_uni.tbl
diff --git a/src/chrtrans/makeuctb.c b/src/chrtrans/makeuctb.c
new file mode 100644
index 0000000..29d43ee
--- /dev/null
+++ b/src/chrtrans/makeuctb.c
@@ -0,0 +1,914 @@
+/*
+ * $LynxId: makeuctb.c,v 1.52 2021/03/22 22:52:58 tom Exp $
+ *
+ * makeuctb.c, derived from conmakehash.c - kw
+ *
+ * Original comments from conmakehash.c:
+ *
+ * Create arrays for initializing the kernel folded tables (using a hash
+ * table turned out to be to limiting...) Unfortunately we can't simply
+ * preinitialize the tables at compile time since kfree() cannot accept
+ * memory not allocated by kmalloc(), and doing our own memory management
+ * just for this seems like massive overkill.
+ *
+ * Copyright (C) 1995 H. Peter Anvin
+ *
+ * This program is a part of the Linux kernel, and may be freely
+ * copied under the terms of the GNU General Public License (GPL),
+ * version 2, or at your option any later version.
+ */
+
+#ifndef HAVE_CONFIG_H
+/* override HTUtils.h fallbacks for cross-compiling */
+#undef HAVE_LSTAT
+#undef NO_FILIO_H
+#define HAVE_LSTAT 1
+#define NO_FILIO_H 1
+#endif
+
+#define DONT_USE_GETTEXT
+#define DONT_USE_SOCKS5
+#include <UCDefs.h>
+#include <UCkd.h>
+
+#ifdef LY_FIND_LEAKS /* CF_ARG_ENABLE(find-leaks) */
+#define FreeLeak(p) FREE(p)
+#else
+#define FreeLeak(p) /* nothing */
+#endif
+
+#define L_CURL '{'
+#define R_CURL '}'
+
+/*
+ * Don't try to use LYexit() since this is a standalone file.
+ */
+#ifdef exit
+#undef exit
+#endif /* exit */
+
+#include <stdlib.h>
+#include <string.h>
+#include <ctype.h>
+
+#define MAX_FONTLEN 256
+
+/*
+ * We don't deal with UCS4 here. - KW
+ */
+typedef u16 unicode;
+
+static FILE *chdr = 0;
+
+/*
+ * Since we may be writing the formatted file to stdout, ensure that we flush
+ * everything before leaving, since some old (and a few not-so-old) platforms
+ * do not properly implement POSIX 'exit()'.
+ */
+static GCC_NORETURN void done(int code);
+
+static void done(int code)
+{
+ if (chdr != 0) {
+ fflush(chdr);
+ fclose(chdr);
+ }
+ fflush(stderr);
+ exit(code);
+}
+
+static void usage(void)
+{
+ static const char *tbl[] =
+ {
+ "Usage: makeuctb [parameters]",
+ "",
+ "Utility to convert .tbl into .h files for Lynx compilation.",
+ "",
+ "Parameters (all are optional):",
+ " 1: the input file (normally {filename}.tbl, but \"-\" for stdin",
+ " 2: the output file (normally {filename}.tbl but \"-\" for stdout",
+ " 3: charset mime name",
+ " 4: charset display name"
+ };
+ unsigned n;
+
+ for (n = 0; n < TABLESIZE(tbl); n++) {
+ fprintf(stderr, "%s\n", tbl[n]);
+ };
+ done(EX_USAGE);
+}
+
+#ifdef USE_ASCII_CTYPES
+static int ascii_tolower(int i)
+{
+ if (91 > i && i > 64)
+ return (i + 32);
+ else
+ return i;
+}
+#endif
+
+/* copied from HTString.c, not everybody has strncasecmp */
+int strncasecomp(const char *a, const char *b, int n)
+{
+ const char *p;
+ const char *q;
+
+ for (p = a, q = b;; p++, q++) {
+ int diff;
+
+ if (p == (a + n))
+ return 0; /* Match up to n characters */
+ if (!(*p && *q))
+ return (*p - *q);
+ diff = TOLOWER(*p) - TOLOWER(*q);
+ if (diff)
+ return diff;
+ }
+ /*NOTREACHED */
+}
+
+static int getunicode(char **p0)
+{
+ char *p = *p0;
+
+ while (*p == ' ' || *p == '\t')
+ p++;
+
+ if (*p == '-') {
+ return -2;
+ } else if (*p != 'U' || p[1] != '+' ||
+ !isxdigit(UCH(p[2])) ||
+ !isxdigit(UCH(p[3])) ||
+ !isxdigit(UCH(p[4])) ||
+ !isxdigit(UCH(p[5])) ||
+ isxdigit(UCH(p[6]))) {
+ return -1;
+ }
+ *p0 = p + 6;
+ return (int) strtol((p + 2), 0, 16);
+}
+
+/*
+ * Massive overkill, but who cares?
+ */
+static unicode unitable[MAX_FONTLEN][255];
+static int unicount[MAX_FONTLEN];
+
+static struct unimapdesc_str themap_str =
+{0, NULL, 0, 0};
+
+static const char *tblname;
+static const char *hdrname;
+
+static int RawOrEnc = 0;
+static int Raw_found = 0; /* whether explicit R directive found */
+static int CodePage = 0;
+
+#define MAX_UNIPAIRS 4500
+
+static void addpair_str(char *str, int un)
+{
+ int i = 0;
+
+ if (un <= 0xfffe) {
+ if (!themap_str.entry_ct) {
+ /*
+ * Initialize the map for replacement strings.
+ */
+ themap_str.entries = (struct unipair_str *) calloc(MAX_UNIPAIRS,
+ sizeof(struct unipair_str));
+
+ if (!themap_str.entries) {
+ fprintf(stderr,
+ "%s: Out of memory\n", tblname);
+ done(EX_DATAERR);
+ }
+ } else {
+ /*
+ * Check that it isn't a duplicate.
+ */
+ for (i = 0; i < themap_str.entry_ct; i++) {
+ if (themap_str.entries[i].unicode == un) {
+ FreeLeak(themap_str.entries[i].replace_str);
+ themap_str.entries[i].replace_str = str;
+ return;
+ }
+ }
+ }
+
+ /*
+ * Add to list.
+ */
+ if (themap_str.entry_ct > MAX_UNIPAIRS - 1) {
+ fprintf(stderr,
+ "ERROR: Only %d unicode replacement strings permitted!\n",
+ MAX_UNIPAIRS);
+ done(EX_DATAERR);
+ }
+ FreeLeak(themap_str.entries[themap_str.entry_ct].replace_str);
+ themap_str.entries[themap_str.entry_ct].unicode = (u16) un;
+ themap_str.entries[themap_str.entry_ct].replace_str = str;
+ themap_str.entry_ct++;
+ }
+ /* otherwise: ignore */
+}
+
+static void addpair(int fp, int un)
+{
+ int i;
+
+ if (!Raw_found) { /* enc not (yet) explicitly given with 'R' */
+ if (fp >= 128) {
+ if (RawOrEnc != UCT_ENC_8BIT && RawOrEnc <= UCT_ENC_8859) {
+ if (fp < 160) { /* cannot be 8859 */
+ RawOrEnc = UCT_ENC_8BIT;
+ } else if (fp != 160 && fp != 173) {
+ RawOrEnc = UCT_ENC_8859; /* hmmm.. more tests needed? */
+ } else if (unicount[fp] == 0 && fp != un) {
+ /* first unicode for fp doesn't map to itself */
+ RawOrEnc = UCT_ENC_8BIT;
+ } else {
+ RawOrEnc = UCT_ENC_8859; /* hmmm.. more tests needed? */
+ }
+ }
+ }
+ }
+ if (un <= 0xfffe) {
+ /*
+ * Check that it isn't a duplicate.
+ */
+ for (i = 0; i < unicount[fp]; i++) {
+ if (unitable[fp][i] == un) {
+ return;
+ }
+ }
+
+ /*
+ * Add to list.
+ */
+ if (unicount[fp] > 254) {
+ fprintf(stderr, "ERROR: Only 255 unicodes/glyph permitted!\n");
+ done(EX_DATAERR);
+ }
+ unitable[fp][unicount[fp]] = (u16) un;
+ unicount[fp]++;
+ }
+ /* otherwise: ignore */
+}
+
+static char this_MIMEcharset[UC_MAXLEN_MIMECSNAME + 1];
+static char this_LYNXcharset[UC_MAXLEN_LYNXCSNAME + 1];
+static char id_append[UC_MAXLEN_ID_APPEND + 1] = "_";
+static int this_isDefaultMap = -1;
+static int useDefaultMap = 1;
+static int lowest_eight = 999;
+
+int main(int argc, char **argv)
+{
+ static const char *first_ifdefs[] =
+ {
+ "/*",
+ " * Compile-in this chunk of code unless we've turned it off specifically",
+ " * or in general (id=%s).",
+ " */",
+ "",
+ "#ifndef INCL_CHARSET%s",
+ "#define INCL_CHARSET%s 1",
+ "",
+ "/*ifdef NO_CHARSET*/",
+ "#ifdef NO_CHARSET",
+ "#undef NO_CHARSET",
+ "#endif",
+ "#define NO_CHARSET 0 /* force default to always be active */",
+ "",
+ "/*ifndef NO_CHARSET%s*/",
+ "#ifndef NO_CHARSET%s",
+ "",
+ "#if ALL_CHARSETS",
+ "#define NO_CHARSET%s 0",
+ "#else",
+ "#define NO_CHARSET%s 1",
+ "#endif",
+ "",
+ "#endif /* ndef(NO_CHARSET%s) */",
+ "",
+ "#if NO_CHARSET%s",
+ "#define UC_CHARSET_SETUP%s /*nothing*/",
+ "#else"
+ };
+ static const char *last_ifdefs[] =
+ {
+ "",
+ "#endif /* NO_CHARSET%s */",
+ "",
+ "#endif /* INCL_CHARSET%s */"
+ };
+
+ FILE *ctbl;
+ char buffer[65536];
+ char *outname = 0;
+ unsigned n;
+ int fontlen;
+ int i, nuni, nent;
+ int fp0 = 0, fp1 = 0, un0, un1;
+ char *p, *p1;
+ char *tbuf = NULL, ch;
+
+ if (argc < 2 || argc > 5) {
+ usage();
+ }
+
+ if (!strcmp(argv[1], "-")) {
+ ctbl = stdin;
+ tblname = "stdin";
+ } else {
+ ctbl = fopen(tblname = argv[1], "r");
+ if (!ctbl) {
+ perror(tblname);
+ done(EX_NOINPUT);
+ }
+ }
+
+ if (argc > 2) {
+ if (!strcmp(argv[2], "-")) {
+ chdr = stdout;
+ hdrname = "stdout";
+ } else {
+ hdrname = argv[2];
+ }
+ } else if (ctbl == stdin) {
+ chdr = stdout;
+ hdrname = "stdout";
+ } else if ((outname = (char *) malloc(strlen(tblname) + 3)) != 0) {
+ strcpy(outname, tblname);
+ hdrname = outname;
+ if ((p = strrchr(outname, '.')) == 0)
+ p = outname + strlen(outname);
+ strcpy(p, ".h");
+ } else {
+ perror("malloc");
+ done(EX_NOINPUT);
+ }
+
+ if (chdr == 0) {
+ chdr = fopen(hdrname, "w");
+ if (!chdr) {
+ perror(hdrname);
+ done(EX_NOINPUT);
+ }
+ }
+
+ /*
+ * For now we assume the default font is always 256 characters.
+ */
+ fontlen = 256;
+
+ /*
+ * Initialize table.
+ */
+ for (i = 0; i < fontlen; i++) {
+ unicount[i] = 0;
+ }
+
+ /*
+ * Now we comes to the tricky part. Parse the input table.
+ */
+ while (fgets(buffer, (int) sizeof(buffer), ctbl) != NULL) {
+ if ((p = StrChr(buffer, '\n')) != NULL) {
+ *p = '\0';
+ } else {
+ fprintf(stderr,
+ "%s: Warning: line too long or incomplete.\n",
+ tblname);
+ }
+
+ /*
+ * Syntax accepted:
+ * <fontpos> <unicode> <unicode> ...
+ * <fontpos> <unicode range> <unicode range> ...
+ * <fontpos> idem
+ * <range> idem
+ * <range> <unicode range>
+ * <unicode> :<replace>
+ * <unicode range> :<replace>
+ * <unicode> "<C replace>"
+ * <unicode range> "<C replace>"
+ *
+ * where <range> ::= <fontpos>-<fontpos>
+ * and <unicode> ::= U+<h><h><h><h>
+ * and <h> ::= <hexadecimal digit>
+ * and <replace> any string not containing '\n' or '\0'
+ * and <C replace> any string with C backslash escapes.
+ */
+ p = buffer;
+ while (*p == ' ' || *p == '\t') {
+ p++;
+ }
+ if (!(*p) || *p == '#') {
+ /*
+ * Skip comment or blank line.
+ */
+ continue;
+ }
+
+ switch (*p) {
+ /*
+ * Raw Unicode? I.e. needs some special
+ * processing. One digit code.
+ */
+ case 'R':
+ if (p[1] == 'a' || p[1] == 'A') {
+ buffer[sizeof(buffer) - 1] = '\0';
+ if (!strncasecomp(p, "RawOrEnc", 8)) {
+ p += 8;
+ }
+ }
+ p++;
+ while (*p == ' ' || *p == '\t') {
+ p++;
+ }
+ RawOrEnc = (int) strtol(p, 0, 10);
+ Raw_found = 1;
+ continue;
+
+ /*
+ * Is this the default table?
+ */
+ case 'D':
+ if (p[1] == 'e' || p[1] == 'E') {
+ buffer[sizeof(buffer) - 1] = '\0';
+ if (!strncasecomp(p, "Default", 7)) {
+ p += 7;
+ }
+ }
+ p++;
+ while (*p == ' ' || *p == '\t') {
+ p++;
+ }
+ this_isDefaultMap = (*p == '1' || TOLOWER(*p) == 'y');
+ continue;
+
+ /*
+ * Is this the default table?
+ */
+ case 'F':
+ if (p[1] == 'a' || p[1] == 'A') {
+ buffer[sizeof(buffer) - 1] = '\0';
+ if (!strncasecomp(p, "FallBack", 8)) {
+ p += 8;
+ }
+ }
+ p++;
+ while (*p == ' ' || *p == '\t') {
+ p++;
+ }
+ useDefaultMap = (*p == '1' || TOLOWER(*p) == 'y');
+ continue;
+
+ case 'M':
+ if (p[1] == 'i' || p[1] == 'I') {
+ buffer[sizeof(buffer) - 1] = '\0';
+ if (!strncasecomp(p, "MIMEName", 8)) {
+ p += 8;
+ }
+ }
+ p++;
+ while (*p == ' ' || *p == '\t') {
+ p++;
+ }
+ sscanf(p, "%40s", this_MIMEcharset);
+ continue;
+
+ /*
+ * Display charset name for options screen.
+ */
+ case 'O':
+ if (p[1] == 'p' || p[1] == 'P') {
+ buffer[sizeof(buffer) - 1] = '\0';
+ if (!strncasecomp(p, "OptionName", 10)) {
+ p += 10;
+ }
+ }
+ p++;
+ while (*p == ' ' || *p == '\t') {
+ p++;
+ }
+ for (i = 0; *p && i < UC_MAXLEN_LYNXCSNAME; p++, i++) {
+ this_LYNXcharset[i] = *p;
+ }
+ this_LYNXcharset[i] = '\0';
+ continue;
+
+ /*
+ * Codepage number. Three or four digit code.
+ */
+ case 'C':
+ if (p[1] == 'o' || p[1] == 'O') {
+ buffer[sizeof(buffer) - 1] = '\0';
+ if (!strncasecomp(p, "CodePage", 8)) {
+ p += 8;
+ }
+ }
+ p++;
+ while (*p == ' ' || *p == '\t') {
+ p++;
+ }
+ CodePage = (int) strtol(p, 0, 10);
+ continue;
+ }
+
+ if (*p == 'U') {
+ un0 = getunicode(&p);
+ if (un0 < 0) {
+ fprintf(stderr, "Bad input line: %s\n", buffer);
+ done(EX_DATAERR);
+ fprintf(stderr,
+ "%s: Bad Unicode range corresponding to font position range 0x%x-0x%x\n",
+ tblname, fp0, fp1);
+ done(EX_DATAERR);
+ }
+ un1 = un0;
+ while (*p == ' ' || *p == '\t') {
+ p++;
+ }
+ if (*p == '-') {
+ p++;
+ while (*p == ' ' || *p == '\t') {
+ p++;
+ }
+ un1 = getunicode(&p);
+ if (un1 < 0 || un1 < un0) {
+ fprintf(stderr,
+ "%s: Bad Unicode range U+%x-U+%x\n",
+ tblname, un0, un1);
+ fprintf(stderr, "Bad input line: %s\n", buffer);
+ done(EX_DATAERR);
+ }
+ while (*p == ' ' || *p == '\t') {
+ p++;
+ }
+ }
+
+ if (*p != ':' && *p != '"') {
+ fprintf(stderr, "No ':' or '\"' where expected: %s\n",
+ buffer);
+ continue;
+ }
+
+ /*
+ * Allocate a string large enough for the worst-case use in the
+ * loop using sprintf.
+ */
+ tbuf = (char *) malloc(5 * strlen(p));
+
+ if (!(p1 = tbuf)) {
+ fprintf(stderr, "%s: Out of memory\n", tblname);
+ done(EX_DATAERR);
+ }
+ if (*p == '"') {
+ /*
+ * Handle "<C replace>".
+ * Copy chars verbatim until first '"' not \-escaped or
+ * end of buffer.
+ */
+ int escaped = 0;
+
+ ch = 0;
+ for (++p; *p != '\0'; p++) {
+ ch = *p;
+ if (escaped) {
+ escaped = 0;
+ } else if (ch == '"') {
+ break;
+ } else if (ch == '\\') {
+ escaped = 1;
+ }
+ *p1++ = ch;
+ }
+ if (escaped || ch != '"') {
+ fprintf(stderr, "Warning: String not terminated: %s\n",
+ buffer);
+ if (escaped)
+ *p1++ = '\n';
+ }
+ } else {
+ /*
+ * We had ':'.
+ */
+ for (++p; *p != '\0'; p++, p1++) {
+ ch = *p;
+ if (UCH(ch) < 32 || ch == '\\' || ch == '\"' ||
+ UCH(ch) >= 127) {
+ sprintf(p1, "\\%.3o", UCH(ch));
+ p1 += 3;
+ } else {
+ *p1 = ch;
+ }
+ }
+ }
+ *p1 = '\0';
+ for (i = un0; i <= un1; i++) {
+ addpair_str(tbuf, i);
+ }
+ continue;
+ }
+
+ /*
+ * Input line (after skipping spaces) doesn't start with one
+ * of the specially recognized characters, so try to interpret
+ * it as starting with a fontpos.
+ */
+ fp0 = (int) strtol(p, &p1, 0);
+ if (p1 == p) {
+ fprintf(stderr, "Bad input line: %s\n", buffer);
+ done(EX_DATAERR);
+ }
+ p = p1;
+
+ while (*p == ' ' || *p == '\t') {
+ p++;
+ }
+ if (*p == '-') {
+ p++;
+ fp1 = (int) strtol(p, &p1, 0);
+ if (p1 == p) {
+ fprintf(stderr, "Bad input line: %s\n", buffer);
+ done(EX_DATAERR);
+ }
+ p = p1;
+ } else {
+ fp1 = 0;
+ }
+
+ if (fp0 < 0 || fp0 >= fontlen) {
+ fprintf(stderr,
+ "%s: Glyph number (0x%x) larger than font length\n",
+ tblname, fp0);
+ done(EX_DATAERR);
+ }
+ if (fp1 && (fp1 < fp0 || fp1 >= fontlen)) {
+ fprintf(stderr,
+ "%s: Bad end of range (0x%x)\n",
+ tblname, fp1);
+ done(EX_DATAERR);
+ }
+
+ if (fp1) {
+ /*
+ * We have a range; expect the word "idem"
+ * or a Unicode range of the same length.
+ */
+ while (*p == ' ' || *p == '\t') {
+ p++;
+ }
+ if (!StrNCmp(p, "idem", 4)) {
+ for (i = fp0; i <= fp1; i++) {
+ addpair(i, i);
+ }
+ p += 4;
+ } else {
+ un0 = getunicode(&p);
+ while (*p == ' ' || *p == '\t') {
+ p++;
+ }
+ if (*p != '-') {
+ fprintf(stderr,
+ "%s: Corresponding to a range of font positions,",
+ tblname);
+ fprintf(stderr,
+ " there should be a Unicode range.\n");
+ done(EX_DATAERR);
+ }
+ p++;
+ un1 = getunicode(&p);
+ if (un0 < 0 || un1 < 0) {
+ fprintf(stderr,
+ "%s: Bad Unicode range corresponding to font position range 0x%x-0x%x\n",
+ tblname, fp0, fp1);
+ done(EX_DATAERR);
+ }
+ if (un1 - un0 != fp1 - fp0) {
+ fprintf(stderr,
+ "%s: Unicode range U+%x-U+%x not of the same length",
+ tblname, un0, un1);
+ fprintf(stderr,
+ " as font position range 0x%x-0x%x\n",
+ fp0, fp1);
+ done(EX_DATAERR);
+ }
+ for (i = fp0; i <= fp1; i++) {
+ addpair(i, un0 - fp0 + i);
+ }
+ }
+ } else {
+ /*
+ * No range; expect a list of unicode values
+ * or unicode ranges for a single font position,
+ * or the word "idem"
+ */
+ while (*p == ' ' || *p == '\t') {
+ p++;
+ }
+ if (!StrNCmp(p, "idem", 4)) {
+ addpair(fp0, fp0);
+ p += 4;
+ }
+ while ((un0 = getunicode(&p)) >= 0) {
+ addpair(fp0, un0);
+ while (*p == ' ' || *p == '\t') {
+ p++;
+ }
+ if (*p == '-') {
+ p++;
+ un1 = getunicode(&p);
+ if (un1 < un0) {
+ fprintf(stderr,
+ "%s: Bad Unicode range 0x%x-0x%x\n",
+ tblname, un0, un1);
+ done(EX_DATAERR);
+ }
+ for (un0++; un0 <= un1; un0++) {
+ addpair(fp0, un0);
+ }
+ }
+ }
+ }
+ while (*p == ' ' || *p == '\t') {
+ p++;
+ }
+ if (*p && *p != '#') {
+ fprintf(stderr, "%s: trailing junk (%s) ignored\n", tblname, p);
+ }
+ }
+
+ /*
+ * Okay, we hit EOF, now output tables.
+ */
+ fclose(ctbl);
+
+ /*
+ * Compute total size of Unicode list.
+ */
+ nuni = 0;
+ for (i = 0; i < fontlen; i++) {
+ nuni += unicount[i];
+ }
+
+ if (argc > 3) {
+ StrNCpy(this_MIMEcharset, argv[3], UC_MAXLEN_MIMECSNAME);
+ } else if (this_MIMEcharset[0] == '\0') {
+ StrNCpy(this_MIMEcharset, tblname, UC_MAXLEN_MIMECSNAME);
+ if ((p = StrChr(this_MIMEcharset, '.')) != 0) {
+ *p = '\0';
+ }
+ }
+ for (p = this_MIMEcharset; *p; p++) {
+ *p = (char) TOLOWER(*p);
+ }
+ if (argc > 4) {
+ StrNCpy(this_LYNXcharset, argv[4], UC_MAXLEN_LYNXCSNAME);
+ } else if (this_LYNXcharset[0] == '\0') {
+ memcpy(this_LYNXcharset, this_MIMEcharset, UC_MAXLEN_LYNXCSNAME);
+ }
+
+ if (this_isDefaultMap == -1) {
+ this_isDefaultMap = !StrNCmp(this_MIMEcharset, "iso-8859-1", 10);
+ }
+ fprintf(stderr,
+ "makeuctb: %s: %stranslation map",
+ this_MIMEcharset, (this_isDefaultMap ? "default " : ""));
+ if (this_isDefaultMap == 1) {
+ *id_append = '\0';
+ } else {
+ for (i = 0, p = this_MIMEcharset;
+ *p && (i < UC_MAXLEN_ID_APPEND - 1);
+ p++, i++) {
+ id_append[i + 1] = (char) (isalnum(UCH(*p)) ? *p : '_');
+ }
+ id_append[i + 1] = '\0';
+ }
+ fprintf(stderr, " (%s).\n", id_append);
+
+ for (n = 0; n < TABLESIZE(first_ifdefs); n++) {
+ fprintf(chdr, first_ifdefs[n], id_append);
+ fprintf(chdr, "\n");
+ }
+
+ fprintf(chdr, "\n\
+/*\n\
+ * uni_hash.tbl\n\
+ *\n\
+ * Do not edit this file; it was automatically generated by\n\
+ *\n\
+ * %s %s\n\
+ *\n\
+ */\n\
+\n\
+static const u8 dfont_unicount%s[%d] = \n\
+%c\n\t", argv[0], argv[1], id_append, fontlen, L_CURL);
+
+ for (i = 0; i < fontlen; i++) {
+ if (i >= 128 && unicount[i] > 0 && i < lowest_eight) {
+ lowest_eight = i;
+ }
+ fprintf(chdr, "%3d", unicount[i]);
+ if (i == (fontlen - 1)) {
+ fprintf(chdr, "\n%c;\n", R_CURL);
+ } else if ((i % 8) == 7) {
+ fprintf(chdr, ",\n\t");
+ } else {
+ fprintf(chdr, ", ");
+ }
+ }
+
+ /*
+ * If lowest_eightbit is anything else but 999,
+ * this can't be 7-bit only.
+ */
+ if (lowest_eight != 999 && !RawOrEnc) {
+ RawOrEnc = UCT_ENC_8BIT;
+ }
+
+ if (nuni) {
+ fprintf(chdr, "\nstatic const u16 dfont_unitable%s[%d] = \n%c\n\t",
+ id_append, nuni, L_CURL);
+ } else {
+ fprintf(chdr,
+ "\nstatic const u16 dfont_unitable%s[1] = {0}; /* dummy */\n", id_append);
+ }
+
+ fp0 = 0;
+ nent = 0;
+ for (i = 0; i < nuni; i++) {
+ while (nent >= unicount[fp0]) {
+ fp0++;
+ nent = 0;
+ }
+ fprintf(chdr, "0x%04x", unitable[fp0][nent++]);
+ if (i == (nuni - 1)) {
+ fprintf(chdr, "\n%c;\n", R_CURL);
+ } else if ((i % 8) == 7) {
+ fprintf(chdr, ",\n\t");
+ } else {
+ fprintf(chdr, ", ");
+ }
+ }
+
+ if (themap_str.entry_ct) {
+ fprintf(chdr, "\n\
+static struct unipair_str repl_map%s[%d] = \n\
+%c\n\t", id_append, themap_str.entry_ct, L_CURL);
+ } else {
+ fprintf(chdr, "\n\
+/* static struct unipair_str repl_map%s[]; */\n", id_append);
+ }
+
+ for (i = 0; i < themap_str.entry_ct; i++) {
+ fprintf(chdr, "%c0x%x,\"%s\"%c",
+ L_CURL,
+ themap_str.entries[i].unicode,
+ themap_str.entries[i].replace_str,
+ R_CURL);
+ if (i == (themap_str.entry_ct - 1)) {
+ fprintf(chdr, "\n%c;\n", R_CURL);
+ } else if ((i % 4) == 3) {
+ fprintf(chdr, ",\n\t");
+ } else {
+ fprintf(chdr, ", ");
+ }
+ }
+ if (themap_str.entry_ct) {
+ fprintf(chdr, "\n\
+static const struct unimapdesc_str dfont_replacedesc%s = %c%d,repl_map%s,",
+ id_append, L_CURL, themap_str.entry_ct, id_append);
+ } else {
+ fprintf(chdr, "\n\
+static const struct unimapdesc_str dfont_replacedesc%s = %c0,NULL,", id_append, L_CURL);
+ }
+ fprintf(chdr, "%d,%d%c;\n",
+ this_isDefaultMap ? 1 : 0,
+ (useDefaultMap && !this_isDefaultMap) ? 1 : 0,
+ R_CURL);
+
+ fprintf(chdr, "#define UC_CHARSET_SETUP%s UC_Charset_Setup(\
+\"%s\",\\\n\"%s\",\\\n\
+dfont_unicount%s,dfont_unitable%s,%d,\\\n\
+dfont_replacedesc%s,%d,%d,%d)\n",
+ id_append, this_MIMEcharset, this_LYNXcharset,
+ id_append, id_append, nuni, id_append, lowest_eight, RawOrEnc, CodePage);
+
+ for (n = 0; n < TABLESIZE(last_ifdefs); n++) {
+ fprintf(chdr, last_ifdefs[n], id_append);
+ fprintf(chdr, "\n");
+ }
+
+ done(EX_OK);
+#ifdef LY_FIND_LEAKS /* CF_ARG_ENABLE(find-leaks) */
+ for (i = 0; i < themap_str.entry_ct; i++) {
+ FreeLeak(themap_str.entries[i].replace_str);
+ }
+#endif
+ return 0;
+}
diff --git a/src/chrtrans/makew32.bat b/src/chrtrans/makew32.bat
new file mode 100644
index 0000000..da47601
--- /dev/null
+++ b/src/chrtrans/makew32.bat
@@ -0,0 +1,13 @@
+@rem $LynxId: makew32.bat,v 1.6 2007/06/28 21:07:24 tom Exp $
+@echo off
+
+if "%1"=="" goto normal
+make -l -f makefile.bcb %1
+goto done
+
+:normal
+make -l -f makefile.bcb
+
+call makehdrs
+
+:done
diff --git a/src/chrtrans/mnem2_suni.tbl b/src/chrtrans/mnem2_suni.tbl
new file mode 100644
index 0000000..3fc122c
--- /dev/null
+++ b/src/chrtrans/mnem2_suni.tbl
@@ -0,0 +1,1865 @@
+#The MIME name of this charset.
+# (this file was renamed from mnemonic_suni.tbl)
+Mmnemonic
+
+#Name as a Display Charset (used on Options screen)
+O RFC 1345 Mnemonic
+
+# Don't fall back to default table for unicode -> 8bit
+Fallback NO
+
+# U+0020:&SP
+U+0021:!
+U+0022:"
+U+0023:&Nb
+U+0024:&DO
+U+0025:%
+U+0026:&&
+U+0027:'
+U+0028:(
+U+0029:)
+U+002a:*
+U+002b:+
+U+002c:,
+U+002d:-
+U+002e:.
+U+002f:/
+U+0030:0
+U+0031:1
+U+0032:2
+U+0033:3
+U+0034:4
+U+0035:5
+U+0036:6
+U+0037:7
+U+0038:8
+U+0039:9
+U+003a::
+U+003b:;
+U+003c:<
+U+003d:=
+U+003e:>
+U+003f:?
+U+0040:&At
+U+0041:A
+U+0042:B
+U+0043:C
+U+0044:D
+U+0045:E
+U+0046:F
+U+0047:G
+U+0048:H
+U+0049:I
+U+004a:J
+U+004b:K
+U+004c:L
+U+004d:M
+U+004e:N
+U+004f:O
+U+0050:P
+U+0051:Q
+U+0052:R
+U+0053:S
+U+0054:T
+U+0055:U
+U+0056:V
+U+0057:W
+U+0058:X
+U+0059:Y
+U+005a:Z
+U+005b:&<(
+U+005c:&//
+U+005d:&)>
+U+005e:&'>
+U+005f:_
+U+0060:&'!
+U+0061:a
+U+0062:b
+U+0063:c
+U+0064:d
+U+0065:e
+U+0066:f
+U+0067:g
+U+0068:h
+U+0069:i
+U+006a:j
+U+006b:k
+U+006c:l
+U+006d:m
+U+006e:n
+U+006f:o
+U+0070:p
+U+0071:q
+U+0072:r
+U+0073:s
+U+0074:t
+U+0075:u
+U+0076:v
+U+0077:w
+U+0078:x
+U+0079:y
+U+007a:z
+U+007b:&(!
+U+007c:&!!
+U+007d:&!)
+U+007e:&'?
+U+00a0:&NS
+U+00a1:&!I
+U+00a2:&Ct
+U+00a3:&Pd
+U+00a4:&Cu
+U+00a5:&Ye
+U+00a6:&BB
+U+00a7:&SE
+U+00a8:&':
+U+00a9:&Co
+U+00aa:&-a
+U+00ab:&<<
+U+00ac:&NO
+U+00ad:&--
+U+00ae:&Rg
+U+00af:&'m
+U+00b0:&DG
+U+00b1:&+-
+U+00b2:&2S
+U+00b3:&3S
+U+00b4:&''
+U+00b5:&My
+U+00b6:&PI
+U+00b7:&.M
+U+00b8:&',
+U+00b9:&1S
+U+00ba:&-o
+U+00bb:&>>
+U+00bc:&14
+U+00bd:&12
+U+00be:&34
+U+00bf:&?I
+U+00c0:&A!
+U+00c1:&A'
+U+00c2:&A>
+U+00c3:&A?
+U+00c4:&A:
+U+00c5:&AA
+U+00c6:&AE
+U+00c7:&C,
+U+00c8:&E!
+U+00c9:&E'
+U+00ca:&E>
+U+00cb:&E:
+U+00cc:&I!
+U+00cd:&I'
+U+00ce:&I>
+U+00cf:&I:
+U+00d0:&D-
+U+00d1:&N?
+U+00d2:&O!
+U+00d3:&O'
+U+00d4:&O>
+U+00d5:&O?
+U+00d6:&O:
+U+00d7:&*X
+U+00d8:&O/
+U+00d9:&U!
+U+00da:&U'
+U+00db:&U>
+U+00dc:&U:
+U+00dd:&Y'
+U+00de:&TH
+U+00df:&ss
+U+00e0:&a!
+U+00e1:&a'
+U+00e2:&a>
+U+00e3:&a?
+U+00e4:&a:
+U+00e5:&aa
+U+00e6:&ae
+U+00e7:&c,
+U+00e8:&e!
+U+00e9:&e'
+U+00ea:&e>
+U+00eb:&e:
+U+00ec:&i!
+U+00ed:&i'
+U+00ee:&i>
+U+00ef:&i:
+U+00f0:&d-
+U+00f1:&n?
+U+00f2:&o!
+U+00f3:&o'
+U+00f4:&o>
+U+00f5:&o?
+U+00f6:&o:
+U+00f7:&-:
+U+00f8:&o/
+U+00f9:&u!
+U+00fa:&u'
+U+00fb:&u>
+U+00fc:&u:
+U+00fd:&y'
+U+00fe:&th
+U+00ff:&y:
+U+0100:&A-
+U+0101:&a-
+U+0102:&A(
+U+0103:&a(
+U+0104:&A;
+U+0105:&a;
+U+0106:&C'
+U+0107:&c'
+U+0108:&C>
+U+0109:&c>
+U+010a:&C.
+U+010b:&c.
+U+010c:&C<
+U+010d:&c<
+U+010e:&D<
+U+010f:&d<
+U+0110:&D/
+U+0111:&d/
+U+0112:&E-
+U+0113:&e-
+U+0114:&E(
+U+0115:&e(
+U+0116:&E.
+U+0117:&e.
+U+0118:&E;
+U+0119:&e;
+U+011a:&E<
+U+011b:&e<
+U+011c:&G>
+U+011d:&g>
+U+011e:&G(
+U+011f:&g(
+U+0120:&G.
+U+0121:&g.
+U+0122:&G,
+U+0123:&g,
+U+0124:&H>
+U+0125:&h>
+U+0126:&H/
+U+0127:&h/
+U+0128:&I?
+U+0129:&i?
+U+012a:&I-
+U+012b:&i-
+U+012c:&I(
+U+012d:&i(
+U+012e:&I;
+U+012f:&i;
+U+0130:&I.
+U+0131:&i.
+U+0132:&IJ
+U+0133:&ij
+U+0134:&J>
+U+0135:&j>
+U+0136:&K,
+U+0137:&k,
+U+0138:&kk
+U+0139:&L'
+U+013a:&l'
+U+013b:&L,
+U+013c:&l,
+U+013d:&L<
+U+013e:&l<
+U+013f:&L.
+U+0140:&l.
+U+0141:&L/
+U+0142:&l/
+U+0143:&N'
+U+0144:&n'
+U+0145:&N,
+U+0146:&n,
+U+0147:&N<
+U+0148:&n<
+U+0149:&'n
+U+014a:&NG
+U+014b:&ng
+U+014c:&O-
+U+014d:&o-
+U+014e:&O(
+U+014f:&o(
+U+0150:&O"
+U+0151:&o"
+U+0152:&OE
+U+0153:&oe
+U+0154:&R'
+U+0155:&r'
+U+0156:&R,
+U+0157:&r,
+U+0158:&R<
+U+0159:&r<
+U+015a:&S'
+U+015b:&s'
+U+015c:&S>
+U+015d:&s>
+U+015e:&S,
+U+015f:&s,
+U+0160:&S<
+U+0161:&s<
+U+0162:&T,
+U+0163:&t,
+U+0164:&T<
+U+0165:&t<
+U+0166:&T/
+U+0167:&t/
+U+0168:&U?
+U+0169:&u?
+U+016a:&U-
+U+016b:&u-
+U+016c:&U(
+U+016d:&u(
+U+016e:&U0
+U+016f:&u0
+U+0170:&U"
+U+0171:&u"
+U+0172:&U;
+U+0173:&u;
+U+0174:&W>
+U+0175:&w>
+U+0176:&Y>
+U+0177:&y>
+U+0178:&Y:
+U+0179:&Z'
+U+017a:&z'
+U+017b:&Z.
+U+017c:&z.
+U+017d:&Z<
+U+017e:&z<
+U+01a0:&O9
+U+01a1:&o9
+U+01a2:&OI
+U+01a3:&oi
+U+01a6:&yr
+U+01af:&U9
+U+01b0:&u9
+U+01b5:&Z/
+U+01b6:&z/
+U+01b7:&ED
+U+01cd:&A<
+U+01ce:&a<
+U+01cf:&I<
+U+01d0:&i<
+U+01d1:&O<
+U+01d2:&o<
+U+01d3:&U<
+U+01d4:&u<
+U+01d5:&_U:-_
+U+01d6:&_u:-_
+U+01d7:&_U:'_
+U+01d8:&_u:'_
+U+01d9:&_U:<_
+U+01da:&_u:<_
+U+01db:&_U:!_
+U+01dc:&_u:!_
+U+01de:&A1
+U+01df:&a1
+U+01e0:&A7
+U+01e1:&a7
+U+01e2:&A3
+U+01e3:&a3
+U+01e4:&G/
+U+01e5:&g/
+U+01e6:&G<
+U+01e7:&g<
+U+01e8:&K<
+U+01e9:&k<
+U+01ea:&O;
+U+01eb:&o;
+U+01ec:&O1
+U+01ed:&o1
+U+01ee:&EZ
+U+01ef:&ez
+U+01f0:&j<
+U+01f4:&G'
+U+01f5:&g'
+U+01fa:&_AA'_
+U+01fb:&_aa'_
+U+01fc:&_AE'_
+U+01fd:&_ae'_
+U+01fe:&_O/'_
+U+01ff:&_o/'_
+U+02bf:&;S
+U+02c7:&'<
+U+02d8:&'(
+U+02d9:&'.
+U+02da:&'0
+U+02db:&';
+U+02dd:&'"
+U+0386:&A%
+U+0388:&E%
+U+0389:&Y%
+U+038a:&I%
+U+038c:&O%
+U+038e:&U%
+U+038f:&W%
+U+0390:&i3
+U+0391:&A*
+U+0392:&B*
+U+0393:&G*
+U+0394:&D*
+U+0395:&E*
+U+0396:&Z*
+U+0397:&Y*
+U+0398:&H*
+U+0399:&I*
+U+039a:&K*
+U+039b:&L*
+U+039c:&M*
+U+039d:&N*
+U+039e:&C*
+U+039f:&O*
+U+03a0:&P*
+U+03a1:&R*
+U+03a3:&S*
+U+03a4:&T*
+U+03a5:&U*
+U+03a6:&F*
+U+03a7:&X*
+U+03a8:&Q*
+U+03a9:&W*
+U+03aa:&J*
+U+03ab:&V*
+U+03ac:&a%
+U+03ad:&e%
+U+03ae:&y%
+U+03af:&i%
+U+03b0:&u3
+U+03b1:&a*
+U+03b2:&b*
+U+03b3:&g*
+U+03b4:&d*
+U+03b5:&e*
+U+03b6:&z*
+U+03b7:&y*
+U+03b8:&h*
+U+03b9:&i*
+U+03ba:&k*
+U+03bb:&l*
+U+03bc:&m*
+U+03bd:&n*
+U+03be:&c*
+U+03bf:&o*
+U+03c0:&p*
+U+03c1:&r*
+U+03c2:&*s
+U+03c3:&s*
+U+03c4:&t*
+U+03c5:&u*
+U+03c6:&f*
+U+03c7:&x*
+U+03c8:&q*
+U+03c9:&w*
+U+03ca:&j*
+U+03cb:&v*
+U+03cc:&o%
+U+03cd:&u%
+U+03ce:&w%
+U+03d8:&'G
+U+03d9:&,G
+U+03da:&T3
+U+03db:&t3
+U+03dc:&M3
+U+03dd:&m3
+U+03de:&K3
+U+03df:&k3
+U+03e0:&P3
+U+03e1:&p3
+U+03f4:&'%
+U+03f5:&j3
+U+0401:&IO
+U+0402:&D%
+U+0403:&G%
+U+0404:&IE
+U+0405:&DS
+U+0406:&II
+U+0407:&YI
+U+0408:&J%
+U+0409:&LJ
+U+040a:&NJ
+U+040b:&Ts
+U+040c:&KJ
+U+040e:&V%
+U+040f:&DZ
+U+0410:&A=
+U+0411:&B=
+U+0412:&V=
+U+0413:&G=
+U+0414:&D=
+U+0415:&E=
+U+0416:&Z%
+U+0417:&Z=
+U+0418:&I=
+U+0419:&J=
+U+041a:&K=
+U+041b:&L=
+U+041c:&M=
+U+041d:&N=
+U+041e:&O=
+U+041f:&P=
+U+0420:&R=
+U+0421:&S=
+U+0422:&T=
+U+0423:&U=
+U+0424:&F=
+U+0425:&H=
+U+0426:&C=
+U+0427:&C%
+U+0428:&S%
+U+0429:&Sc
+U+042a:&="
+U+042b:&Y=
+U+042c:&%"
+U+042d:&JE
+U+042e:&JU
+U+042f:&JA
+U+0430:&a=
+U+0431:&b=
+U+0432:&v=
+U+0433:&g=
+U+0434:&d=
+U+0435:&e=
+U+0436:&z%
+U+0437:&z=
+U+0438:&i=
+U+0439:&j=
+U+043a:&k=
+U+043b:&l=
+U+043c:&m=
+U+043d:&n=
+U+043e:&o=
+U+043f:&p=
+U+0440:&r=
+U+0441:&s=
+U+0442:&t=
+U+0443:&u=
+U+0444:&f=
+U+0445:&h=
+U+0446:&c=
+U+0447:&c%
+U+0448:&s%
+U+0449:&sc
+U+044a:&='
+U+044b:&y=
+U+044c:&%'
+U+044d:&je
+U+044e:&ju
+U+044f:&ja
+U+0451:&io
+U+0452:&d%
+U+0453:&g%
+U+0454:&ie
+U+0455:&ds
+U+0456:&ii
+U+0457:&yi
+U+0458:&j%
+U+0459:&lj
+U+045a:&nj
+U+045b:&ts
+U+045c:&kj
+U+045e:&v%
+U+045f:&dz
+U+0462:&Y3
+U+0463:&y3
+U+046a:&O3
+U+046b:&o3
+U+0472:&F3
+U+0473:&f3
+U+0474:&V3
+U+0475:&v3
+U+0480:&C3
+U+0481:&c3
+U+0490:&G3
+U+0491:&g3
+U+05d0:&A+
+U+05d1:&B+
+U+05d2:&G+
+U+05d3:&D+
+U+05d4:&H+
+U+05d5:&W+
+U+05d6:&Z+
+U+05d7:&X+
+U+05d8:&Tj
+U+05d9:&J+
+U+05da:&K%
+U+05db:&K+
+U+05dc:&L+
+U+05dd:&M%
+U+05de:&M+
+U+05df:&N%
+U+05e0:&N+
+U+05e1:&S+
+U+05e2:&E+
+U+05e3:&P%
+U+05e4:&P+
+U+05e5:&Zj
+U+05e6:&ZJ
+U+05e7:&Q+
+U+05e8:&R+
+U+05e9:&Sh
+U+05ea:&T+
+U+060c:&,+
+U+061b:&;+
+U+061f:&?+
+U+0621:&H'
+U+0622:&aM
+U+0623:&aH
+U+0624:&wH
+U+0625:&ah
+U+0626:&yH
+U+0627:&a+
+U+0628:&b+
+U+0629:&tm
+U+062a:&t+
+U+062b:&tk
+U+062c:&g+
+U+062d:&hk
+U+062e:&x+
+U+062f:&d+
+U+0630:&dk
+U+0631:&r+
+U+0632:&z+
+U+0633:&s+
+U+0634:&sn
+U+0635:&c+
+U+0636:&dd
+U+0637:&tj
+U+0638:&zH
+U+0639:&e+
+U+063a:&i+
+U+0640:&++
+U+0641:&f+
+U+0642:&q+
+U+0643:&k+
+U+0644:&l+
+U+0645:&m+
+U+0646:&n+
+U+0647:&h+
+U+0648:&w+
+U+0649:&j+
+U+064a:&y+
+U+064b:&:+
+U+064c:&"+
+U+064d:&=+
+U+064e:&/+
+U+064f:&'+
+U+0650:&1+
+U+0651:&3+
+U+0652:&0+
+U+0670:&aS
+U+067e:&p+
+U+06a4:&v+
+U+06af:&gf
+U+06f0:&0a
+U+06f1:&1a
+U+06f2:&2a
+U+06f3:&3a
+U+06f4:&4a
+U+06f5:&5a
+U+06f6:&6a
+U+06f7:&7a
+U+06f8:&8a
+U+06f9:&9a
+U+1e00:&_A-0_
+U+1e01:&_a-0_
+U+1e02:&B.
+U+1e03:&b.
+U+1e04:&_B-._
+U+1e05:&_b-._
+U+1e06:&B_
+U+1e07:&b_
+U+1e08:&_C,'_
+U+1e09:&_c,'_
+U+1e0a:&D.
+U+1e0b:&d.
+U+1e0c:&_D-._
+U+1e0d:&_d-._
+U+1e0e:&D_
+U+1e0f:&d_
+U+1e10:&D,
+U+1e11:&d,
+U+1e12:&_D->_
+U+1e13:&_d->_
+U+1e14:&_E-!_
+U+1e15:&_e-!_
+U+1e16:&_E-'_
+U+1e17:&_e-'_
+U+1e18:&_E->_
+U+1e19:&_e->_
+U+1e1a:&_E-?_
+U+1e1b:&_e-?_
+U+1e1c:&_E,(_
+U+1e1d:&_e,(_
+U+1e1e:&F.
+U+1e1f:&f.
+U+1e20:&G-
+U+1e21:&g-
+U+1e22:&H.
+U+1e23:&h.
+U+1e24:&_H-._
+U+1e25:&_h-._
+U+1e26:&H:
+U+1e27:&h:
+U+1e28:&H,
+U+1e29:&h,
+U+1e2a:&_H-(_
+U+1e2b:&_h-(_
+U+1e2c:&_I-?_
+U+1e2d:&_i-?_
+U+1e2e:&_I:'_
+U+1e2f:&_i:'_
+U+1e30:&K'
+U+1e31:&k'
+U+1e32:&_K-._
+U+1e33:&_k-._
+U+1e34:&K_
+U+1e35:&k_
+U+1e36:&_L-._
+U+1e37:&_l-._
+U+1e38:&_L--._
+U+1e39:&_l--._
+U+1e3a:&L_
+U+1e3b:&l_
+U+1e3c:&_L->_
+U+1e3d:&_l->_
+U+1e3e:&M'
+U+1e3f:&m'
+U+1e40:&M.
+U+1e41:&m.
+U+1e42:&_M-._
+U+1e43:&_m-._
+U+1e44:&N.
+U+1e45:&n.
+U+1e46:&_N-._
+U+1e47:&_n-._
+U+1e48:&N_
+U+1e49:&n_
+U+1e4a:&_N->_
+U+1e4b:&_N->_
+U+1e4c:&_O?'_
+U+1e4d:&_o?'_
+U+1e4e:&_O?:_
+U+1e4f:&_o?:_
+U+1e50:&_O-!_
+U+1e51:&_o-!_
+U+1e52:&_O-'_
+U+1e53:&_o-'_
+U+1e54:&P'
+U+1e55:&p'
+U+1e56:&P.
+U+1e57:&p.
+U+1e58:&R.
+U+1e59:&r.
+U+1e5a:&_R-._
+U+1e5b:&_r-._
+U+1e5c:&_R--._
+U+1e5d:&_r--._
+U+1e5e:&R_
+U+1e5f:&r_
+U+1e60:&S.
+U+1e61:&s.
+U+1e62:&_S-._
+U+1e63:&_s-._
+U+1e64:&_S'._
+U+1e65:&_s'._
+U+1e66:&_S<._
+U+1e67:&_s<._
+U+1e68:&_S.-._
+U+1e69:&_S.-._
+U+1e6a:&T.
+U+1e6b:&t.
+U+1e6c:&_T-._
+U+1e6d:&_t-._
+U+1e6e:&T_
+U+1e6f:&t_
+U+1e70:&_T->_
+U+1e71:&_t->_
+U+1e72:&_U--:_
+U+1e73:&_u--:_
+U+1e74:&_U-?_
+U+1e75:&_u-?_
+U+1e76:&_U->_
+U+1e77:&_u->_
+U+1e78:&_U?'_
+U+1e79:&_u?'_
+U+1e7a:&_U-:_
+U+1e7b:&_u-:_
+U+1e7c:&V?
+U+1e7d:&v?
+U+1e7e:&_V-._
+U+1e7f:&_v-._
+U+1e80:&W!
+U+1e81:&w!
+U+1e82:&W'
+U+1e83:&w'
+U+1e84:&W:
+U+1e85:&w:
+U+1e86:&W.
+U+1e87:&w.
+U+1e88:&_W-._
+U+1e89:&_w-._
+U+1e8a:&X.
+U+1e8b:&x.
+U+1e8c:&X:
+U+1e8d:&x:
+U+1e8e:&Y.
+U+1e8f:&y.
+U+1e90:&Z>
+U+1e91:&z>
+U+1e92:&_Z-._
+U+1e93:&_z-._
+U+1e94:&Z_
+U+1e95:&z_
+U+1e96:&h_
+U+1e97:&t:
+U+1e98:&w0
+U+1e99:&y0
+U+1ea0:&_A-._
+U+1ea1:&_a-._
+U+1ea2:&A2
+U+1ea3:&a2
+U+1ea4:&_A>'_
+U+1ea5:&_a>'_
+U+1ea6:&_A>!_
+U+1ea7:&_a>!_
+U+1ea8:&_A>2_
+U+1ea9:&_a>2_
+U+1eaa:&_A>?_
+U+1eab:&_a>?_
+U+1eac:&_A>-._
+U+1ead:&_a>-._
+U+1eae:&_A('_
+U+1eaf:&_a('_
+U+1eb0:&_A(!_
+U+1eb1:&_a(!_
+U+1eb2:&_A(2_
+U+1eb3:&_a(2_
+U+1eb4:&_A(?_
+U+1eb5:&_a(?_
+U+1eb6:&_A(-._
+U+1eb7:&_a(-._
+U+1eb8:&_E-._
+U+1eb9:&_e-._
+U+1eba:&E2
+U+1ebb:&e2
+U+1ebc:&E?
+U+1ebd:&e?
+U+1ebe:&_E>'_
+U+1ebf:&_e>'_
+U+1ec0:&_E>!_
+U+1ec1:&_e>!_
+U+1ec2:&_E>2_
+U+1ec3:&_e>2_
+U+1ec4:&_E>?_
+U+1ec5:&_e>?_
+U+1ec6:&_E>-._
+U+1ec7:&_e>-._
+U+1ec8:&I2
+U+1ec9:&i2
+U+1eca:&_I-._
+U+1ecb:&_i-._
+U+1ecc:&_O-._
+U+1ecd:&_o-._
+U+1ece:&O2
+U+1ecf:&o2
+U+1ed0:&_O>'_
+U+1ed1:&_o>'_
+U+1ed2:&_O>!_
+U+1ed3:&_o>!_
+U+1ed4:&_O>2_
+U+1ed5:&_o>2_
+U+1ed6:&_O>?_
+U+1ed7:&_o>?_
+U+1ed8:&_O>-._
+U+1ed9:&_o>-._
+U+1eda:&_O9'_
+U+1edb:&_o9'_
+U+1edc:&_O9!_
+U+1edd:&_o9!_
+U+1ede:&_O92_
+U+1edf:&_o92_
+U+1ee0:&_O9?_
+U+1ee1:&_o9?_
+U+1ee2:&_O9-._
+U+1ee3:&_o9-._
+U+1ee4:&_U-._
+U+1ee5:&_u-._
+U+1ee6:&U2
+U+1ee7:&u2
+U+1ee8:&_U9'_
+U+1ee9:&_u9'_
+U+1eea:&_U9!_
+U+1eeb:&_u9!_
+U+1eec:&_U92_
+U+1eed:&_u92_
+U+1eee:&_U9?_
+U+1eef:&_u9?_
+U+1ef0:&_U9-._
+U+1ef1:&_u9-._
+U+1ef2:&Y!
+U+1ef3:&y!
+U+1ef4:&_Y-._
+U+1ef5:&_y-._
+U+1ef6:&Y2
+U+1ef7:&y2
+U+1ef8:&Y?
+U+1ef9:&y?
+U+1f00:&;'
+U+1f01:&,'
+U+1f02:&;!
+U+1f03:&,!
+U+1f04:&?;
+U+1f05:&?,
+U+1f06:&!:
+U+1f07:&?:
+U+2002:&1N
+U+2003:&1M
+U+2004:&3M
+U+2005:&4M
+U+2006:&6M
+U+2009:&1T
+U+200a:&1H
+U+2010:&-1
+U+2013:&-N
+U+2014:&-M
+U+2015:&-3
+U+2016:&!2
+U+2017:&=2
+U+2018:&'6
+U+2019:&'9
+U+201a:&.9
+U+201b:&9'
+U+201c:&"6
+U+201d:&"9
+U+201e:&:9
+U+201f:&9"
+U+2020:&/-
+U+2021:&/=
+U+2025:&..
+U+2030:&%0
+U+2032:&1'
+U+2033:&2'
+U+2034:&3'
+U+2035:&1"
+U+2036:&2"
+U+2037:&3"
+U+2038:&Ca
+U+2039:&<1
+U+203a:&>1
+U+203b:&:X
+U+203c:&_!*2_
+U+203e:&'-
+U+2044:&/f
+U+2070:&0S
+U+2074:&4S
+U+2075:&5S
+U+2076:&6S
+U+2077:&7S
+U+2078:&8S
+U+2079:&9S
+U+207a:&+S
+U+207b:&-S
+U+207c:&=S
+U+207d:&(S
+U+207e:&)S
+U+207f:&nS
+U+2080:&0s
+U+2081:&1s
+U+2082:&2s
+U+2083:&3s
+U+2084:&4s
+U+2085:&5s
+U+2086:&6s
+U+2087:&7s
+U+2088:&8s
+U+2089:&9s
+U+208a:&+s
+U+208b:&-s
+U+208c:&=s
+U+208d:&(s
+U+208e:&)s
+U+20a4:&Li
+U+20a7:&Pt
+U+20a9:&W=
+U+2103:&oC
+U+2105:&co
+U+2109:&oF
+U+2116:&N0
+U+2117:&PO
+U+211e:&Rx
+U+2120:&SM
+U+2122:&TM
+U+2126:&Om
+U+212b:&AO
+U+2153:&13
+U+2154:&23
+U+2155:&15
+U+2156:&25
+U+2157:&35
+U+2158:&45
+U+2159:&16
+U+215a:&56
+U+215b:&18
+U+215c:&38
+U+215d:&58
+U+215e:&78
+U+2160:&1R
+U+2161:&2R
+U+2162:&3R
+U+2163:&4R
+U+2164:&5R
+U+2165:&6R
+U+2166:&7R
+U+2167:&8R
+U+2168:&9R
+U+2169:&aR
+U+216a:&bR
+U+216b:&cR
+U+216c:&_50R_
+U+216d:&_100R_
+U+216e:&_500R_
+U+216f:&_1000R_
+U+2170:&1r
+U+2171:&2r
+U+2172:&3r
+U+2173:&4r
+U+2174:&5r
+U+2175:&6r
+U+2176:&7r
+U+2177:&8r
+U+2178:&9r
+U+2179:&ar
+U+217a:&br
+U+217b:&cr
+U+217c:&_50r_
+U+217d:&_100r_
+U+217e:&_500r_
+U+217f:&_1000r_
+U+2180:&_1000RCD_
+U+2181:&_5000R_
+U+2182:&_10000R_
+U+2190:&<-
+U+2191:&-!
+U+2192:&->
+U+2193:&-v
+U+2194:&<>
+U+2195:&UD
+U+2196:&_<!!_
+U+2197:&_//>_
+U+2198:&_!!>_
+U+2199:&_<//_
+U+21d0:&<=
+U+21d2:&=>
+U+21d4:&==
+U+2200:&FA
+U+2202:&dP
+U+2203:&TE
+U+2205:&/0
+U+2206:&DE
+U+2207:&NB
+U+2208:&(-
+U+220b:&-)
+U+220f:&*P
+U+2211:&+Z
+U+2212:&-2
+U+2213:&-+
+U+2217:&*-
+U+2218:&Ob
+U+2219:&Sb
+U+221a:&RT
+U+221d:&0(
+U+221e:&00
+U+221f:&-L
+U+2220:&-V
+U+2225:&PP
+U+2227:&AN
+U+2228:&OR
+U+2229:&(U
+U+222a:&)U
+U+222b:&In
+U+222c:&DI
+U+222e:&Io
+U+2234:&.:
+U+2235:&:.
+U+2236:&:R
+U+2237:&::
+U+223c:&?1
+U+223e:&CG
+U+2243:&?-
+U+2245:&?=
+U+2248:&?2
+U+224c:&=?
+U+2253:&HI
+U+2260:&!=
+U+2261:&=3
+U+2264:&=<
+U+2265:&>=
+U+226a:&<*
+U+226b:&*>
+U+226e:&!<
+U+226f:&!>
+U+2282:&(C
+U+2283:&)C
+U+2286:&(_
+U+2287:&)_
+U+2299:&0.
+U+229a:&02
+U+22a5:&-T
+U+22c5:&.P
+U+22ee:&:3
+U+22ef:&.3
+U+2302:&Eh
+U+2308:&<7
+U+2309:&>7
+U+230a:&7<
+U+230b:&7>
+U+2310:&NI
+U+2312:&(A
+U+2315:&TR
+U+2320:&Iu
+U+2321:&Il
+U+2329:&</
+U+232a:&/>
+U+2423:&Vs
+U+2440:&1h
+U+2441:&3h
+U+2442:&2h
+U+2443:&4h
+U+2446:&1j
+U+2447:&2j
+U+2448:&3j
+U+2449:&4j
+U+2460:&_1-o_
+U+2461:&_2-o_
+U+2462:&_3-o_
+U+2463:&_4-o_
+U+2464:&_5-o_
+U+2465:&_6-o_
+U+2466:&_7-o_
+U+2467:&_8-o_
+U+2468:&_9-o_
+U+2469:&_10-o_
+U+246a:&_11-o_
+U+246b:&_12-o_
+U+246c:&_13-o_
+U+246d:&_14-o_
+U+246e:&_15-o_
+U+246f:&_16-o_
+U+2470:&_17-o_
+U+2471:&_18-o_
+U+2472:&_19-o_
+U+2473:&_20-o_
+U+2474:&_(1)_
+U+2475:&_(2)_
+U+2476:&_(3)_
+U+2477:&_(4)_
+U+2478:&_(5)_
+U+2479:&_(6)_
+U+247a:&_(7)_
+U+247b:&_(8)_
+U+247c:&_(9)_
+U+247d:&_(10)_
+U+247e:&_(11)_
+U+247f:&_(12)_
+U+2480:&_(13)_
+U+2481:&_(14)_
+U+2482:&_(15)_
+U+2483:&_(16)_
+U+2484:&_(17)_
+U+2485:&_(18)_
+U+2486:&_(19)_
+U+2487:&_(20)_
+U+2488:&1.
+U+2489:&2.
+U+248a:&3.
+U+248b:&4.
+U+248c:&5.
+U+248d:&6.
+U+248e:&7.
+U+248f:&8.
+U+2490:&9.
+U+2491:&_10._
+U+2492:&_11._
+U+2493:&_12._
+U+2494:&_13._
+U+2495:&_14._
+U+2496:&_15._
+U+2497:&_16._
+U+2498:&_17._
+U+2499:&_18._
+U+249a:&_19._
+U+249b:&_20._
+U+249c:&_(a)_
+U+249d:&_(b)_
+U+249e:&_(c)_
+U+249f:&_(d)_
+U+24a0:&_(e)_
+U+24a1:&_(f)_
+U+24a2:&_(g)_
+U+24a3:&_(h)_
+U+24a4:&_(i)_
+U+24a5:&_(j)_
+U+24a6:&_(k)_
+U+24a7:&_(l)_
+U+24a8:&_(m)_
+U+24a9:&_(n)_
+U+24aa:&_(o)_
+U+24ab:&_(p)_
+U+24ac:&_(q)_
+U+24ad:&_(r)_
+U+24ae:&_(s)_
+U+24af:&_(t)_
+U+24b0:&_(u)_
+U+24b1:&_(v)_
+U+24b2:&_(w)_
+U+24b3:&_(x)_
+U+24b4:&_(y)_
+U+24b5:&_(z)_
+U+24b6:&_A-o_
+U+24b7:&_B-o_
+U+24b8:&_C-o_
+U+24b9:&_D-o_
+U+24ba:&_E-o_
+U+24bb:&_F-o_
+U+24bc:&_G-o_
+U+24bd:&_H-o_
+U+24be:&_I-o_
+U+24bf:&_J-o_
+U+24c0:&_K-o_
+U+24c1:&_L-o_
+U+24c2:&_M-o_
+U+24c3:&_N-o_
+U+24c4:&_O-o_
+U+24c5:&_P-o_
+U+24c6:&_Q-o_
+U+24c7:&_R-o_
+U+24c8:&_S-o_
+U+24c9:&_T-o_
+U+24ca:&_U-o_
+U+24cb:&_V-o_
+U+24cc:&_W-o_
+U+24cd:&_X-o_
+U+24ce:&_Y-o_
+U+24cf:&_Z-o_
+U+24d0:&_a-o_
+U+24d1:&_b-o_
+U+24d2:&_c-o_
+U+24d3:&_d-o_
+U+24d4:&_e-o_
+U+24d5:&_f-o_
+U+24d6:&_g-o_
+U+24d7:&_h-o_
+U+24d8:&_i-o_
+U+24d9:&_j-o_
+U+24da:&_k-o_
+U+24db:&_l-o_
+U+24dc:&_m-o_
+U+24dd:&_n-o_
+U+24de:&_o-o_
+U+24df:&_p-o_
+U+24e0:&_q-o_
+U+24e1:&_r-o_
+U+24e2:&_s-o_
+U+24e3:&_t-o_
+U+24e4:&_u-o_
+U+24e5:&_v-o_
+U+24e6:&_w-o_
+U+24e7:&_x-o_
+U+24e8:&_y-o_
+U+24e9:&_z-o_
+U+24ea:&_0-o_
+U+2500:&hh
+U+2501:&HH
+U+2502:&vv
+U+2503:&VV
+U+2504:&3-
+U+2505:&3_
+U+2506:&3!
+U+2507:&3/
+U+2508:&4-
+U+2509:&4_
+U+250a:&4!
+U+250b:&4/
+U+250c:&dr
+U+250d:&dR
+U+250e:&Dr
+U+250f:&DR
+U+2510:&dl
+U+2511:&dL
+U+2512:&Dl
+U+2513:&LD
+U+2514:&ur
+U+2515:&uR
+U+2516:&Ur
+U+2517:&UR
+U+2518:&ul
+U+2519:&uL
+U+251a:&Ul
+U+251b:&UL
+U+251c:&vr
+U+251d:&vR
+U+251e:&_Udr_
+U+251f:&_uDr_
+U+2520:&Vr
+U+2521:&_UdR_
+U+2522:&_uDR_
+U+2523:&VR
+U+2524:&vl
+U+2525:&vL
+U+2526:&_Udl_
+U+2527:&_uDl_
+U+2528:&Vl
+U+2529:&_UdL_
+U+252a:&_uDL_
+U+252b:&VL
+U+252c:&dh
+U+252d:&_dLr_
+U+252e:&_dlR_
+U+252f:&dH
+U+2530:&Dh
+U+2531:&_DLr_
+U+2532:&_DlR_
+U+2533:&DH
+U+2534:&uh
+U+2535:&_uLr_
+U+2536:&_ulR_
+U+2537:&uH
+U+2538:&Uh
+U+2539:&_ULr_
+U+253a:&_UlR_
+U+253b:&UH
+U+253c:&vh
+U+253d:&_vLr_
+U+253e:&_vlR_
+U+253f:&vH
+U+2540:&_Udh_
+U+2541:&_uDh_
+U+2542:&Vh
+U+2543:&_UdLr_
+U+2544:&_UdlR_
+U+2545:&_uDLr_
+U+2546:&_uDlR_
+U+2547:&_UdH_
+U+2548:&_uDH_
+U+2549:&_VLr_
+U+254a:&_VlR_
+U+254b:&VH
+U+2571:&FD
+U+2572:&BD
+U+2580:&TB
+U+2584:&LB
+U+2588:&FB
+U+258c:&lB
+U+2590:&RB
+U+2591:&.S
+U+2592:&:S
+U+2593:&?S
+U+25a0:&fS
+U+25a1:&OS
+U+25a2:&RO
+U+25a3:&Rr
+U+25a4:&RF
+U+25a5:&RY
+U+25a6:&RH
+U+25a7:&RZ
+U+25a8:&RK
+U+25a9:&RX
+U+25aa:&sB
+U+25ac:&SR
+U+25ad:&Or
+U+25b2:&UT
+U+25b3:&uT
+U+25b6:&PR
+U+25b7:&Tr
+U+25bc:&Dt
+U+25bd:&dT
+U+25c0:&PL
+U+25c1:&Tl
+U+25c6:&Db
+U+25c7:&Dw
+U+25ca:&LZ
+U+25cb:&0m
+U+25ce:&0o
+U+25cf:&0M
+U+25d0:&0L
+U+25d1:&0R
+U+25d8:&Sn
+U+25d9:&Ic
+U+25e2:&Fd
+U+25e3:&Bd
+U+2605:&*2
+U+2606:&*1
+U+260e:&_TEL_
+U+260f:&_tel_
+U+261c:&<H
+U+261e:&>H
+U+263a:&0u
+U+263b:&0U
+U+263c:&SU
+U+2640:&Fm
+U+2642:&Ml
+U+2660:&cS
+U+2661:&cH
+U+2662:&cD
+U+2663:&cC
+U+2664:&_cS-_
+U+2665:&_cH-_
+U+2666:&_cD-_
+U+2667:&_cC-_
+U+2669:&Md
+U+266a:&M8
+U+266b:&M2
+U+266c:&_M16_
+U+266d:&Mb
+U+266e:&Mx
+U+266f:&MX
+U+2713:&OK
+U+2717:&XX
+U+2720:&-X
+U+3000:&IS
+U+3001:&,_
+U+3002:&._
+U+3003:&+"
+U+3004:&+_
+U+3005:&*_
+U+3006:&;_
+U+3007:&0_
+U+300a:&<+
+U+300b:&>+
+U+300c:&<'
+U+300d:&>'
+U+300e:&<"
+U+300f:&>"
+U+3010:&("
+U+3011:&)"
+U+3012:&=T
+U+3013:&=_
+U+3014:&('
+U+3015:&)'
+U+3016:&(I
+U+3017:&)I
+U+301c:&-?
+U+3020:&_=T:)_
+U+3041:&A5
+U+3042:&a5
+U+3043:&I5
+U+3044:&i5
+U+3045:&U5
+U+3046:&u5
+U+3047:&E5
+U+3048:&e5
+U+3049:&O5
+U+304a:&o5
+U+304b:&ka
+U+304c:&ga
+U+304d:&ki
+U+304e:&gi
+U+304f:&ku
+U+3050:&gu
+U+3051:&ke
+U+3052:&ge
+U+3053:&ko
+U+3054:&go
+U+3055:&sa
+U+3056:&za
+U+3057:&si
+U+3058:&zi
+U+3059:&su
+U+305a:&zu
+U+305b:&se
+U+305c:&ze
+U+305d:&so
+U+305e:&zo
+U+305f:&ta
+U+3060:&da
+U+3061:&ti
+U+3062:&di
+U+3063:&tU
+U+3064:&tu
+U+3065:&du
+U+3066:&te
+U+3067:&de
+U+3068:&to
+U+3069:&do
+U+306a:&na
+U+306b:&ni
+U+306c:&nu
+U+306d:&ne
+U+306e:&no
+U+306f:&ha
+U+3070:&ba
+U+3071:&pa
+U+3072:&hi
+U+3073:&bi
+U+3074:&pi
+U+3075:&hu
+U+3076:&bu
+U+3077:&pu
+U+3078:&he
+U+3079:&be
+U+307a:&pe
+U+307b:&ho
+U+307c:&bo
+U+307d:&po
+U+307e:&ma
+U+307f:&mi
+U+3080:&mu
+U+3081:&me
+U+3082:&mo
+U+3083:&yA
+U+3084:&ya
+U+3085:&yU
+U+3086:&yu
+U+3087:&yO
+U+3088:&yo
+U+3089:&ra
+U+308a:&ri
+U+308b:&ru
+U+308c:&re
+U+308d:&ro
+U+308e:&wA
+U+308f:&wa
+U+3090:&wi
+U+3091:&we
+U+3092:&wo
+U+3093:&n5
+U+3094:&vu
+U+309b:&"5
+U+309c:&05
+U+309d:&*5
+U+309e:&+5
+U+30a1:&a6
+U+30a2:&A6
+U+30a3:&i6
+U+30a4:&I6
+U+30a5:&u6
+U+30a6:&U6
+U+30a7:&e6
+U+30a8:&E6
+U+30a9:&o6
+U+30aa:&O6
+U+30ab:&Ka
+U+30ac:&Ga
+U+30ad:&Ki
+U+30ae:&Gi
+U+30af:&Ku
+U+30b0:&Gu
+U+30b1:&Ke
+U+30b2:&Ge
+U+30b3:&Ko
+U+30b4:&Go
+U+30b5:&Sa
+U+30b6:&Za
+U+30b7:&Si
+U+30b8:&Zi
+U+30b9:&Su
+U+30ba:&Zu
+U+30bb:&Se
+U+30bc:&Ze
+U+30bd:&So
+U+30be:&Zo
+U+30bf:&Ta
+U+30c0:&Da
+U+30c1:&Ti
+U+30c2:&Di
+U+30c3:&TU
+U+30c4:&Tu
+U+30c5:&Du
+U+30c6:&Te
+U+30c7:&De
+U+30c8:&To
+U+30c9:&Do
+U+30ca:&Na
+U+30cb:&Ni
+U+30cc:&Nu
+U+30cd:&Ne
+U+30ce:&No
+U+30cf:&Ha
+U+30d0:&Ba
+U+30d1:&Pa
+U+30d2:&Hi
+U+30d3:&Bi
+U+30d4:&Pi
+U+30d5:&Hu
+U+30d6:&Bu
+U+30d7:&Pu
+U+30d8:&He
+U+30d9:&Be
+U+30da:&Pe
+U+30db:&Ho
+U+30dc:&Bo
+U+30dd:&Po
+U+30de:&Ma
+U+30df:&Mi
+U+30e0:&Mu
+U+30e1:&Me
+U+30e2:&Mo
+U+30e3:&YA
+U+30e4:&Ya
+U+30e5:&YU
+U+30e6:&Yu
+U+30e7:&YO
+U+30e8:&Yo
+U+30e9:&Ra
+U+30ea:&Ri
+U+30eb:&Ru
+U+30ec:&Re
+U+30ed:&Ro
+U+30ee:&WA
+U+30ef:&Wa
+U+30f0:&Wi
+U+30f1:&We
+U+30f2:&Wo
+U+30f3:&N6
+U+30f4:&Vu
+U+30f5:&KA
+U+30f6:&KE
+U+30f7:&Va
+U+30f8:&Vi
+U+30f9:&Ve
+U+30fa:&Vo
+U+30fb:&.6
+U+30fc:&-6
+U+30fd:&*6
+U+30fe:&+6
+U+3105:&b4
+U+3106:&p4
+U+3107:&m4
+U+3108:&f4
+U+3109:&d4
+U+310a:&t4
+U+310b:&n4
+U+310c:&l4
+U+310d:&g4
+U+310e:&k4
+U+310f:&h4
+U+3110:&j4
+U+3111:&q4
+U+3112:&x4
+U+3113:&zh
+U+3114:&ch
+U+3115:&sh
+U+3116:&r4
+U+3117:&z4
+U+3118:&c4
+U+3119:&s4
+U+311a:&a4
+U+311b:&o4
+U+311c:&e4
+U+311d:&_eh4_
+U+311e:&ai
+U+311f:&ei
+U+3120:&au
+U+3121:&ou
+U+3122:&an
+U+3123:&en
+U+3124:&aN
+U+3125:&eN
+U+3126:&er
+U+3127:&i4
+U+3128:&u4
+U+3129:&iu
+U+312a:&v4
+U+312b:&nG
+U+312c:&gn
+U+321c:&_(JU)_
+U+3220:&1c
+U+3221:&2c
+U+3222:&3c
+U+3223:&4c
+U+3224:&5c
+U+3225:&6c
+U+3226:&7c
+U+3227:&8c
+U+3228:&9c
+U+3229:&_10c_
+U+327f:&_KSC_
+U+fb00:&ff
+U+fb01:&fi
+U+fb02:&fl
+U+fb03:&_ffi_
+U+fb04:&_ffl_
+U+fb05:&ft
+U+fb06:&st
+U+fe7d:&_3+;_
+U+fe82:&_aM._
+U+fe84:&_aH._
+U+fe8d:&_a+-_
+U+fe8e:&_a+._
+U+fe8f:&_b+-_
+U+fe90:&_b+,_
+U+fe91:&_b+;_
+U+fe92:&_b+._
+U+fe93:&_tm-_
+U+fe94:&_tm._
+U+fe95:&_t+-_
+U+fe96:&_t+,_
+U+fe97:&_t+;_
+U+fe98:&_t+._
+U+fe99:&_tk-_
+U+fe9a:&_tk,_
+U+fe9b:&_tk;_
+U+fe9c:&_tk._
+U+fe9d:&_g+-_
+U+fe9e:&_g+,_
+U+fe9f:&_g+;_
+U+fea0:&_g+._
+U+fea1:&_hk-_
+U+fea2:&_hk,_
+U+fea3:&_hk;_
+U+fea4:&_hk._
+U+fea5:&_x+-_
+U+fea6:&_x+,_
+U+fea7:&_x+;_
+U+fea8:&_x+._
+U+fea9:&_d+-_
+U+feaa:&_d+._
+U+feab:&_dk-_
+U+feac:&_dk._
+U+fead:&_r+-_
+U+feae:&_r+._
+U+feaf:&_z+-_
+U+feb0:&_z+._
+U+feb1:&_s+-_
+U+feb2:&_s+,_
+U+feb3:&_s+;_
+U+feb4:&_s+._
+U+feb5:&_sn-_
+U+feb6:&_sn,_
+U+feb7:&_sn;_
+U+feb8:&_sn._
+U+feb9:&_c+-_
+U+feba:&_c+,_
+U+febb:&_c+;_
+U+febc:&_c+._
+U+febd:&_dd-_
+U+febe:&_dd,_
+U+febf:&_dd;_
+U+fec0:&_dd._
+U+fec1:&_tj-_
+U+fec2:&_tj,_
+U+fec3:&_tj;_
+U+fec4:&_tj._
+U+fec5:&_zH-_
+U+fec6:&_zH,_
+U+fec7:&_zH;_
+U+fec8:&_zH._
+U+fec9:&_e+-_
+U+feca:&_e+,_
+U+fecb:&_e+;_
+U+fecc:&_e+._
+U+fecd:&_i+-_
+U+fece:&_i+,_
+U+fecf:&_i+;_
+U+fed0:&_i+._
+U+fed1:&_f+-_
+U+fed2:&_f+,_
+U+fed3:&_f+;_
+U+fed4:&_f+._
+U+fed5:&_q+-_
+U+fed6:&_q+,_
+U+fed7:&_q+;_
+U+fed8:&_q+._
+U+fed9:&_k+-_
+U+feda:&_k+,_
+U+fedb:&_k+;_
+U+fedc:&_k+._
+U+fedd:&_l+-_
+U+fede:&_l+,_
+U+fedf:&_l+;_
+U+fee0:&_l+._
+U+fee1:&_m+-_
+U+fee2:&_m+,_
+U+fee3:&_m+;_
+U+fee4:&_m+._
+U+fee5:&_n+-_
+U+fee6:&_n+,_
+U+fee7:&_n+;_
+U+fee8:&_n+._
+U+fee9:&_h+-_
+U+feea:&_h+,_
+U+feeb:&_h+;_
+U+feec:&_h+._
+U+feed:&_w+-_
+U+feee:&_w+._
+U+feef:&_j+-_
+U+fef0:&_j+._
+U+fef1:&_y+-_
+U+fef2:&_y+,_
+U+fef3:&_y+;_
+U+fef4:&_y+._
+U+fef5:&_lM-_
+U+fef6:&_lM._
+U+fef7:&_lH-_
+U+fef8:&_lH._
+U+fef9:&_lh-_
+U+fefa:&_lh._
+U+fefb:&_la-_
+U+fefc:&_la._
+U+0000:&NU
+U+0001:&SH
+U+0002:&SX
+U+0003:&EX
+U+0004:&ET
+U+0005:&EQ
+U+0006:&AK
+U+0007:&BL
+U+0008:&BS
+U+0009:&HT
+# U+000a:&LF
+U+000b:&VT
+U+000c:&FF
+U+000d:&CR
+U+000e:&SO
+U+000f:&SI
+U+0010:&DL
+U+0011:&D1
+U+0012:&D2
+U+0013:&D3
+U+0014:&D4
+U+0015:&NK
+U+0016:&SY
+U+0017:&EB
+U+0018:&CN
+U+0019:&EM
+U+001a:&SB
+U+001b:&EC
+U+001c:&FS
+U+001d:&GS
+U+001e:&RS
+U+001f:&US
+U+007f:&DT
+U+0080:&PA
+U+0081:&HO
+U+0082:&BH
+U+0083:&NH
+U+0084:&IN
+U+0085:&NL
+U+0086:&SA
+U+0087:&ES
+U+0088:&HS
+U+0089:&HJ
+U+008a:&VS
+U+008b:&PD
+U+008c:&PU
+U+008d:&RI
+U+008e:&S2
+U+008f:&S3
+U+0090:&DC
+U+0091:&P1
+U+0092:&P2
+U+0093:&TS
+U+0094:&CC
+U+0095:&MW
+U+0096:&SG
+U+0097:&EG
+U+0098:&SS
+U+0099:&GC
+U+009a:&SC
+U+009b:&CI
+U+009c:&ST
+U+009d:&OC
+U+009e:&PM
+U+009f:&AC
+# Characters in Private Use Area (e000-f8ff) do not have ussigned numbers
+# according Unicode 2.0
diff --git a/src/chrtrans/mnem_suni.tbl b/src/chrtrans/mnem_suni.tbl
new file mode 100644
index 0000000..02bd8ea
--- /dev/null
+++ b/src/chrtrans/mnem_suni.tbl
@@ -0,0 +1,1861 @@
+#The MIME name of this charset.
+Mmnem
+
+#Name as a Display Charset (used on Options screen)
+ORFC1345 Mnem
+
+# U+0020: SP
+U+0021:!
+U+0022:"
+U+0023: Nb
+U+0024: DO
+U+0025:%
+U+0026:&&
+U+0027:'
+U+0028:(
+U+0029:)
+U+002a:*
+U+002b:+
+U+002c:,
+U+002d:-
+U+002e:.
+U+002f:/
+U+0030:0
+U+0031:1
+U+0032:2
+U+0033:3
+U+0034:4
+U+0035:5
+U+0036:6
+U+0037:7
+U+0038:8
+U+0039:9
+U+003a::
+U+003b:;
+U+003c:<
+U+003d:=
+U+003e:>
+U+003f:?
+U+0040: At
+U+0041:A
+U+0042:B
+U+0043:C
+U+0044:D
+U+0045:E
+U+0046:F
+U+0047:G
+U+0048:H
+U+0049:I
+U+004a:J
+U+004b:K
+U+004c:L
+U+004d:M
+U+004e:N
+U+004f:O
+U+0050:P
+U+0051:Q
+U+0052:R
+U+0053:S
+U+0054:T
+U+0055:U
+U+0056:V
+U+0057:W
+U+0058:X
+U+0059:Y
+U+005a:Z
+U+005b: <(
+U+005c: //
+U+005d: )>
+U+005e: '>
+U+005f:_
+U+0060: '!
+U+0061:a
+U+0062:b
+U+0063:c
+U+0064:d
+U+0065:e
+U+0066:f
+U+0067:g
+U+0068:h
+U+0069:i
+U+006a:j
+U+006b:k
+U+006c:l
+U+006d:m
+U+006e:n
+U+006f:o
+U+0070:p
+U+0071:q
+U+0072:r
+U+0073:s
+U+0074:t
+U+0075:u
+U+0076:v
+U+0077:w
+U+0078:x
+U+0079:y
+U+007a:z
+U+007b: (!
+U+007c: !!
+U+007d: !)
+U+007e: '?
+U+00a0: NS
+U+00a1: !I
+U+00a2: Ct
+U+00a3: Pd
+U+00a4: Cu
+U+00a5: Ye
+U+00a6: BB
+U+00a7: SE
+U+00a8: ':
+U+00a9: Co
+U+00aa: -a
+U+00ab: <<
+U+00ac: NO
+U+00ad: --
+U+00ae: Rg
+U+00af: 'm
+U+00b0: DG
+U+00b1: +-
+U+00b2: 2S
+U+00b3: 3S
+U+00b4: ''
+U+00b5: My
+U+00b6: PI
+U+00b7: .M
+U+00b8: ',
+U+00b9: 1S
+U+00ba: -o
+U+00bb: >>
+U+00bc: 14
+U+00bd: 12
+U+00be: 34
+U+00bf: ?I
+U+00c0: A!
+U+00c1: A'
+U+00c2: A>
+U+00c3: A?
+U+00c4: A:
+U+00c5: AA
+U+00c6: AE
+U+00c7: C,
+U+00c8: E!
+U+00c9: E'
+U+00ca: E>
+U+00cb: E:
+U+00cc: I!
+U+00cd: I'
+U+00ce: I>
+U+00cf: I:
+U+00d0: D-
+U+00d1: N?
+U+00d2: O!
+U+00d3: O'
+U+00d4: O>
+U+00d5: O?
+U+00d6: O:
+U+00d7: *X
+U+00d8: O/
+U+00d9: U!
+U+00da: U'
+U+00db: U>
+U+00dc: U:
+U+00dd: Y'
+U+00de: TH
+U+00df: ss
+U+00e0: a!
+U+00e1: a'
+U+00e2: a>
+U+00e3: a?
+U+00e4: a:
+U+00e5: aa
+U+00e6: ae
+U+00e7: c,
+U+00e8: e!
+U+00e9: e'
+U+00ea: e>
+U+00eb: e:
+U+00ec: i!
+U+00ed: i'
+U+00ee: i>
+U+00ef: i:
+U+00f0: d-
+U+00f1: n?
+U+00f2: o!
+U+00f3: o'
+U+00f4: o>
+U+00f5: o?
+U+00f6: o:
+U+00f7: -:
+U+00f8: o/
+U+00f9: u!
+U+00fa: u'
+U+00fb: u>
+U+00fc: u:
+U+00fd: y'
+U+00fe: th
+U+00ff: y:
+U+0100: A-
+U+0101: a-
+U+0102: A(
+U+0103: a(
+U+0104: A;
+U+0105: a;
+U+0106: C'
+U+0107: c'
+U+0108: C>
+U+0109: c>
+U+010a: C.
+U+010b: c.
+U+010c: C<
+U+010d: c<
+U+010e: D<
+U+010f: d<
+U+0110: D/
+U+0111: d/
+U+0112: E-
+U+0113: e-
+U+0114: E(
+U+0115: e(
+U+0116: E.
+U+0117: e.
+U+0118: E;
+U+0119: e;
+U+011a: E<
+U+011b: e<
+U+011c: G>
+U+011d: g>
+U+011e: G(
+U+011f: g(
+U+0120: G.
+U+0121: g.
+U+0122: G,
+U+0123: g,
+U+0124: H>
+U+0125: h>
+U+0126: H/
+U+0127: h/
+U+0128: I?
+U+0129: i?
+U+012a: I-
+U+012b: i-
+U+012c: I(
+U+012d: i(
+U+012e: I;
+U+012f: i;
+U+0130: I.
+U+0131: i.
+U+0132: IJ
+U+0133: ij
+U+0134: J>
+U+0135: j>
+U+0136: K,
+U+0137: k,
+U+0138: kk
+U+0139: L'
+U+013a: l'
+U+013b: L,
+U+013c: l,
+U+013d: L<
+U+013e: l<
+U+013f: L.
+U+0140: l.
+U+0141: L/
+U+0142: l/
+U+0143: N'
+U+0144: n'
+U+0145: N,
+U+0146: n,
+U+0147: N<
+U+0148: n<
+U+0149: 'n
+U+014a: NG
+U+014b: ng
+U+014c: O-
+U+014d: o-
+U+014e: O(
+U+014f: o(
+U+0150: O"
+U+0151: o"
+U+0152: OE
+U+0153: oe
+U+0154: R'
+U+0155: r'
+U+0156: R,
+U+0157: r,
+U+0158: R<
+U+0159: r<
+U+015a: S'
+U+015b: s'
+U+015c: S>
+U+015d: s>
+U+015e: S,
+U+015f: s,
+U+0160: S<
+U+0161: s<
+U+0162: T,
+U+0163: t,
+U+0164: T<
+U+0165: t<
+U+0166: T/
+U+0167: t/
+U+0168: U?
+U+0169: u?
+U+016a: U-
+U+016b: u-
+U+016c: U(
+U+016d: u(
+U+016e: U0
+U+016f: u0
+U+0170: U"
+U+0171: u"
+U+0172: U;
+U+0173: u;
+U+0174: W>
+U+0175: w>
+U+0176: Y>
+U+0177: y>
+U+0178: Y:
+U+0179: Z'
+U+017a: z'
+U+017b: Z.
+U+017c: z.
+U+017d: Z<
+U+017e: z<
+U+01a0: O9
+U+01a1: o9
+U+01a2: OI
+U+01a3: oi
+U+01a6: yr
+U+01af: U9
+U+01b0: u9
+U+01b5: Z/
+U+01b6: z/
+U+01b7: ED
+U+01cd: A<
+U+01ce: a<
+U+01cf: I<
+U+01d0: i<
+U+01d1: O<
+U+01d2: o<
+U+01d3: U<
+U+01d4: u<
+U+01d5: _U:-_
+U+01d6: _u:-_
+U+01d7: _U:'_
+U+01d8: _u:'_
+U+01d9: _U:<_
+U+01da: _u:<_
+U+01db: _U:!_
+U+01dc: _u:!_
+U+01de: A1
+U+01df: a1
+U+01e0: A7
+U+01e1: a7
+U+01e2: A3
+U+01e3: a3
+U+01e4: G/
+U+01e5: g/
+U+01e6: G<
+U+01e7: g<
+U+01e8: K<
+U+01e9: k<
+U+01ea: O;
+U+01eb: o;
+U+01ec: O1
+U+01ed: o1
+U+01ee: EZ
+U+01ef: ez
+U+01f0: j<
+U+01f4: G'
+U+01f5: g'
+U+01fa: _AA'_
+U+01fb: _aa'_
+U+01fc: _AE'_
+U+01fd: _ae'_
+U+01fe: _O/'_
+U+01ff: _o/'_
+U+02bf: ;S
+U+02c7: '<
+U+02d8: '(
+U+02d9: '.
+U+02da: '0
+U+02db: ';
+U+02dd: '"
+U+0386: A%
+U+0388: E%
+U+0389: Y%
+U+038a: I%
+U+038c: O%
+U+038e: U%
+U+038f: W%
+U+0390: i3
+U+0391: A*
+U+0392: B*
+U+0393: G*
+U+0394: D*
+U+0395: E*
+U+0396: Z*
+U+0397: Y*
+U+0398: H*
+U+0399: I*
+U+039a: K*
+U+039b: L*
+U+039c: M*
+U+039d: N*
+U+039e: C*
+U+039f: O*
+U+03a0: P*
+U+03a1: R*
+U+03a3: S*
+U+03a4: T*
+U+03a5: U*
+U+03a6: F*
+U+03a7: X*
+U+03a8: Q*
+U+03a9: W*
+U+03aa: J*
+U+03ab: V*
+U+03ac: a%
+U+03ad: e%
+U+03ae: y%
+U+03af: i%
+U+03b0: u3
+U+03b1: a*
+U+03b2: b*
+U+03b3: g*
+U+03b4: d*
+U+03b5: e*
+U+03b6: z*
+U+03b7: y*
+U+03b8: h*
+U+03b9: i*
+U+03ba: k*
+U+03bb: l*
+U+03bc: m*
+U+03bd: n*
+U+03be: c*
+U+03bf: o*
+U+03c0: p*
+U+03c1: r*
+U+03c2: *s
+U+03c3: s*
+U+03c4: t*
+U+03c5: u*
+U+03c6: f*
+U+03c7: x*
+U+03c8: q*
+U+03c9: w*
+U+03ca: j*
+U+03cb: v*
+U+03cc: o%
+U+03cd: u%
+U+03ce: w%
+U+03d8: 'G
+U+03d9: ,G
+U+03da: T3
+U+03db: t3
+U+03dc: M3
+U+03dd: m3
+U+03de: K3
+U+03df: k3
+U+03e0: P3
+U+03e1: p3
+U+03f4: '%
+U+03f5: j3
+U+0401: IO
+U+0402: D%
+U+0403: G%
+U+0404: IE
+U+0405: DS
+U+0406: II
+U+0407: YI
+U+0408: J%
+U+0409: LJ
+U+040a: NJ
+U+040b: Ts
+U+040c: KJ
+U+040e: V%
+U+040f: DZ
+U+0410: A=
+U+0411: B=
+U+0412: V=
+U+0413: G=
+U+0414: D=
+U+0415: E=
+U+0416: Z%
+U+0417: Z=
+U+0418: I=
+U+0419: J=
+U+041a: K=
+U+041b: L=
+U+041c: M=
+U+041d: N=
+U+041e: O=
+U+041f: P=
+U+0420: R=
+U+0421: S=
+U+0422: T=
+U+0423: U=
+U+0424: F=
+U+0425: H=
+U+0426: C=
+U+0427: C%
+U+0428: S%
+U+0429: Sc
+U+042a: ="
+U+042b: Y=
+U+042c: %"
+U+042d: JE
+U+042e: JU
+U+042f: JA
+U+0430: a=
+U+0431: b=
+U+0432: v=
+U+0433: g=
+U+0434: d=
+U+0435: e=
+U+0436: z%
+U+0437: z=
+U+0438: i=
+U+0439: j=
+U+043a: k=
+U+043b: l=
+U+043c: m=
+U+043d: n=
+U+043e: o=
+U+043f: p=
+U+0440: r=
+U+0441: s=
+U+0442: t=
+U+0443: u=
+U+0444: f=
+U+0445: h=
+U+0446: c=
+U+0447: c%
+U+0448: s%
+U+0449: sc
+U+044a: ='
+U+044b: y=
+U+044c: %'
+U+044d: je
+U+044e: ju
+U+044f: ja
+U+0451: io
+U+0452: d%
+U+0453: g%
+U+0454: ie
+U+0455: ds
+U+0456: ii
+U+0457: yi
+U+0458: j%
+U+0459: lj
+U+045a: nj
+U+045b: ts
+U+045c: kj
+U+045e: v%
+U+045f: dz
+U+0462: Y3
+U+0463: y3
+U+046a: O3
+U+046b: o3
+U+0472: F3
+U+0473: f3
+U+0474: V3
+U+0475: v3
+U+0480: C3
+U+0481: c3
+U+0490: G3
+U+0491: g3
+U+05d0: A+
+U+05d1: B+
+U+05d2: G+
+U+05d3: D+
+U+05d4: H+
+U+05d5: W+
+U+05d6: Z+
+U+05d7: X+
+U+05d8: Tj
+U+05d9: J+
+U+05da: K%
+U+05db: K+
+U+05dc: L+
+U+05dd: M%
+U+05de: M+
+U+05df: N%
+U+05e0: N+
+U+05e1: S+
+U+05e2: E+
+U+05e3: P%
+U+05e4: P+
+U+05e5: Zj
+U+05e6: ZJ
+U+05e7: Q+
+U+05e8: R+
+U+05e9: Sh
+U+05ea: T+
+U+060c: ,+
+U+061b: ;+
+U+061f: ?+
+U+0621: H'
+U+0622: aM
+U+0623: aH
+U+0624: wH
+U+0625: ah
+U+0626: yH
+U+0627: a+
+U+0628: b+
+U+0629: tm
+U+062a: t+
+U+062b: tk
+U+062c: g+
+U+062d: hk
+U+062e: x+
+U+062f: d+
+U+0630: dk
+U+0631: r+
+U+0632: z+
+U+0633: s+
+U+0634: sn
+U+0635: c+
+U+0636: dd
+U+0637: tj
+U+0638: zH
+U+0639: e+
+U+063a: i+
+U+0640: ++
+U+0641: f+
+U+0642: q+
+U+0643: k+
+U+0644: l+
+U+0645: m+
+U+0646: n+
+U+0647: h+
+U+0648: w+
+U+0649: j+
+U+064a: y+
+U+064b: :+
+U+064c: "+
+U+064d: =+
+U+064e: /+
+U+064f: '+
+U+0650: 1+
+U+0651: 3+
+U+0652: 0+
+U+0670: aS
+U+067e: p+
+U+06a4: v+
+U+06af: gf
+U+06f0: 0a
+U+06f1: 1a
+U+06f2: 2a
+U+06f3: 3a
+U+06f4: 4a
+U+06f5: 5a
+U+06f6: 6a
+U+06f7: 7a
+U+06f8: 8a
+U+06f9: 9a
+U+1e00: _A-0_
+U+1e01: _a-0_
+U+1e02: B.
+U+1e03: b.
+U+1e04: _B-._
+U+1e05: _b-._
+U+1e06: B_
+U+1e07: b_
+U+1e08: _C,'_
+U+1e09: _c,'_
+U+1e0a: D.
+U+1e0b: d.
+U+1e0c: _D-._
+U+1e0d: _d-._
+U+1e0e: D_
+U+1e0f: d_
+U+1e10: D,
+U+1e11: d,
+U+1e12: _D->_
+U+1e13: _d->_
+U+1e14: _E-!_
+U+1e15: _e-!_
+U+1e16: _E-'_
+U+1e17: _e-'_
+U+1e18: _E->_
+U+1e19: _e->_
+U+1e1a: _E-?_
+U+1e1b: _e-?_
+U+1e1c: _E,(_
+U+1e1d: _e,(_
+U+1e1e: F.
+U+1e1f: f.
+U+1e20: G-
+U+1e21: g-
+U+1e22: H.
+U+1e23: h.
+U+1e24: _H-._
+U+1e25: _h-._
+U+1e26: H:
+U+1e27: h:
+U+1e28: H,
+U+1e29: h,
+U+1e2a: _H-(_
+U+1e2b: _h-(_
+U+1e2c: _I-?_
+U+1e2d: _i-?_
+U+1e2e: _I:'_
+U+1e2f: _i:'_
+U+1e30: K'
+U+1e31: k'
+U+1e32: _K-._
+U+1e33: _k-._
+U+1e34: K_
+U+1e35: k_
+U+1e36: _L-._
+U+1e37: _l-._
+U+1e38: _L--._
+U+1e39: _l--._
+U+1e3a: L_
+U+1e3b: l_
+U+1e3c: _L->_
+U+1e3d: _l->_
+U+1e3e: M'
+U+1e3f: m'
+U+1e40: M.
+U+1e41: m.
+U+1e42: _M-._
+U+1e43: _m-._
+U+1e44: N.
+U+1e45: n.
+U+1e46: _N-._
+U+1e47: _n-._
+U+1e48: N_
+U+1e49: n_
+U+1e4a: _N->_
+U+1e4b: _N->_
+U+1e4c: _O?'_
+U+1e4d: _o?'_
+U+1e4e: _O?:_
+U+1e4f: _o?:_
+U+1e50: _O-!_
+U+1e51: _o-!_
+U+1e52: _O-'_
+U+1e53: _o-'_
+U+1e54: P'
+U+1e55: p'
+U+1e56: P.
+U+1e57: p.
+U+1e58: R.
+U+1e59: r.
+U+1e5a: _R-._
+U+1e5b: _r-._
+U+1e5c: _R--._
+U+1e5d: _r--._
+U+1e5e: R_
+U+1e5f: r_
+U+1e60: S.
+U+1e61: s.
+U+1e62: _S-._
+U+1e63: _s-._
+U+1e64: _S'._
+U+1e65: _s'._
+U+1e66: _S<._
+U+1e67: _s<._
+U+1e68: _S.-._
+U+1e69: _S.-._
+U+1e6a: T.
+U+1e6b: t.
+U+1e6c: _T-._
+U+1e6d: _t-._
+U+1e6e: T_
+U+1e6f: t_
+U+1e70: _T->_
+U+1e71: _t->_
+U+1e72: _U--:_
+U+1e73: _u--:_
+U+1e74: _U-?_
+U+1e75: _u-?_
+U+1e76: _U->_
+U+1e77: _u->_
+U+1e78: _U?'_
+U+1e79: _u?'_
+U+1e7a: _U-:_
+U+1e7b: _u-:_
+U+1e7c: V?
+U+1e7d: v?
+U+1e7e: _V-._
+U+1e7f: _v-._
+U+1e80: W!
+U+1e81: w!
+U+1e82: W'
+U+1e83: w'
+U+1e84: W:
+U+1e85: w:
+U+1e86: W.
+U+1e87: w.
+U+1e88: _W-._
+U+1e89: _w-._
+U+1e8a: X.
+U+1e8b: x.
+U+1e8c: X:
+U+1e8d: x:
+U+1e8e: Y.
+U+1e8f: y.
+U+1e90: Z>
+U+1e91: z>
+U+1e92: _Z-._
+U+1e93: _z-._
+U+1e94: Z_
+U+1e95: z_
+U+1e96: h_
+U+1e97: t:
+U+1e98: w0
+U+1e99: y0
+U+1ea0: _A-._
+U+1ea1: _a-._
+U+1ea2: A2
+U+1ea3: a2
+U+1ea4: _A>'_
+U+1ea5: _a>'_
+U+1ea6: _A>!_
+U+1ea7: _a>!_
+U+1ea8: _A>2_
+U+1ea9: _a>2_
+U+1eaa: _A>?_
+U+1eab: _a>?_
+U+1eac: _A>-._
+U+1ead: _a>-._
+U+1eae: _A('_
+U+1eaf: _a('_
+U+1eb0: _A(!_
+U+1eb1: _a(!_
+U+1eb2: _A(2_
+U+1eb3: _a(2_
+U+1eb4: _A(?_
+U+1eb5: _a(?_
+U+1eb6: _A(-._
+U+1eb7: _a(-._
+U+1eb8: _E-._
+U+1eb9: _e-._
+U+1eba: E2
+U+1ebb: e2
+U+1ebc: E?
+U+1ebd: e?
+U+1ebe: _E>'_
+U+1ebf: _e>'_
+U+1ec0: _E>!_
+U+1ec1: _e>!_
+U+1ec2: _E>2_
+U+1ec3: _e>2_
+U+1ec4: _E>?_
+U+1ec5: _e>?_
+U+1ec6: _E>-._
+U+1ec7: _e>-._
+U+1ec8: I2
+U+1ec9: i2
+U+1eca: _I-._
+U+1ecb: _i-._
+U+1ecc: _O-._
+U+1ecd: _o-._
+U+1ece: O2
+U+1ecf: o2
+U+1ed0: _O>'_
+U+1ed1: _o>'_
+U+1ed2: _O>!_
+U+1ed3: _o>!_
+U+1ed4: _O>2_
+U+1ed5: _o>2_
+U+1ed6: _O>?_
+U+1ed7: _o>?_
+U+1ed8: _O>-._
+U+1ed9: _o>-._
+U+1eda: _O9'_
+U+1edb: _o9'_
+U+1edc: _O9!_
+U+1edd: _o9!_
+U+1ede: _O92_
+U+1edf: _o92_
+U+1ee0: _O9?_
+U+1ee1: _o9?_
+U+1ee2: _O9-._
+U+1ee3: _o9-._
+U+1ee4: _U-._
+U+1ee5: _u-._
+U+1ee6: U2
+U+1ee7: u2
+U+1ee8: _U9'_
+U+1ee9: _u9'_
+U+1eea: _U9!_
+U+1eeb: _u9!_
+U+1eec: _U92_
+U+1eed: _u92_
+U+1eee: _U9?_
+U+1eef: _u9?_
+U+1ef0: _U9-._
+U+1ef1: _u9-._
+U+1ef2: Y!
+U+1ef3: y!
+U+1ef4: _Y-._
+U+1ef5: _y-._
+U+1ef6: Y2
+U+1ef7: y2
+U+1ef8: Y?
+U+1ef9: y?
+U+1f00: ;'
+U+1f01: ,'
+U+1f02: ;!
+U+1f03: ,!
+U+1f04: ?;
+U+1f05: ?,
+U+1f06: !:
+U+1f07: ?:
+U+2002: 1N
+U+2003: 1M
+U+2004: 3M
+U+2005: 4M
+U+2006: 6M
+U+2009: 1T
+U+200a: 1H
+U+2010: -1
+U+2013: -N
+U+2014: -M
+U+2015: -3
+U+2016: !2
+U+2017: =2
+U+2018: '6
+U+2019: '9
+U+201a: .9
+U+201b: 9'
+U+201c: "6
+U+201d: "9
+U+201e: :9
+U+201f: 9"
+U+2020: /-
+U+2021: /=
+U+2025: ..
+U+2030: %0
+U+2032: 1'
+U+2033: 2'
+U+2034: 3'
+U+2035: 1"
+U+2036: 2"
+U+2037: 3"
+U+2038: Ca
+U+2039: <1
+U+203a: >1
+U+203b: :X
+U+203c: _!*2_
+U+203e: '-
+U+2044: /f
+U+2070: 0S
+U+2074: 4S
+U+2075: 5S
+U+2076: 6S
+U+2077: 7S
+U+2078: 8S
+U+2079: 9S
+U+207a: +S
+U+207b: -S
+U+207c: =S
+U+207d: (S
+U+207e: )S
+U+207f: nS
+U+2080: 0s
+U+2081: 1s
+U+2082: 2s
+U+2083: 3s
+U+2084: 4s
+U+2085: 5s
+U+2086: 6s
+U+2087: 7s
+U+2088: 8s
+U+2089: 9s
+U+208a: +s
+U+208b: -s
+U+208c: =s
+U+208d: (s
+U+208e: )s
+U+20a4: Li
+U+20a7: Pt
+U+20a9: W=
+U+2103: oC
+U+2105: co
+U+2109: oF
+U+2116: N0
+U+2117: PO
+U+211e: Rx
+U+2120: SM
+U+2122: TM
+U+2126: Om
+U+212b: AO
+U+2153: 13
+U+2154: 23
+U+2155: 15
+U+2156: 25
+U+2157: 35
+U+2158: 45
+U+2159: 16
+U+215a: 56
+U+215b: 18
+U+215c: 38
+U+215d: 58
+U+215e: 78
+U+2160: 1R
+U+2161: 2R
+U+2162: 3R
+U+2163: 4R
+U+2164: 5R
+U+2165: 6R
+U+2166: 7R
+U+2167: 8R
+U+2168: 9R
+U+2169: aR
+U+216a: bR
+U+216b: cR
+U+216c: _50R_
+U+216d: _100R_
+U+216e: _500R_
+U+216f: _1000R_
+U+2170: 1r
+U+2171: 2r
+U+2172: 3r
+U+2173: 4r
+U+2174: 5r
+U+2175: 6r
+U+2176: 7r
+U+2177: 8r
+U+2178: 9r
+U+2179: ar
+U+217a: br
+U+217b: cr
+U+217c: _50r_
+U+217d: _100r_
+U+217e: _500r_
+U+217f: _1000r_
+U+2180: _1000RCD_
+U+2181: _5000R_
+U+2182: _10000R_
+U+2190: <-
+U+2191: -!
+U+2192: ->
+U+2193: -v
+U+2194: <>
+U+2195: UD
+U+2196: _<!!_
+U+2197: _//>_
+U+2198: _!!>_
+U+2199: _<//_
+U+21d0: <=
+U+21d2: =>
+U+21d4: ==
+U+2200: FA
+U+2202: dP
+U+2203: TE
+U+2205: /0
+U+2206: DE
+U+2207: NB
+U+2208: (-
+U+220b: -)
+U+220f: *P
+U+2211: +Z
+U+2212: -2
+U+2213: -+
+U+2217: *-
+U+2218: Ob
+U+2219: Sb
+U+221a: RT
+U+221d: 0(
+U+221e: 00
+U+221f: -L
+U+2220: -V
+U+2225: PP
+U+2227: AN
+U+2228: OR
+U+2229: (U
+U+222a: )U
+U+222b: In
+U+222c: DI
+U+222e: Io
+U+2234: .:
+U+2235: :.
+U+2236: :R
+U+2237: ::
+U+223c: ?1
+U+223e: CG
+U+2243: ?-
+U+2245: ?=
+U+2248: ?2
+U+224c: =?
+U+2253: HI
+U+2260: !=
+U+2261: =3
+U+2264: =<
+U+2265: >=
+U+226a: <*
+U+226b: *>
+U+226e: !<
+U+226f: !>
+U+2282: (C
+U+2283: )C
+U+2286: (_
+U+2287: )_
+U+2299: 0.
+U+229a: 02
+U+22a5: -T
+U+22c5: .P
+U+22ee: :3
+U+22ef: .3
+U+2302: Eh
+U+2308: <7
+U+2309: >7
+U+230a: 7<
+U+230b: 7>
+U+2310: NI
+U+2312: (A
+U+2315: TR
+U+2320: Iu
+U+2321: Il
+U+2329: </
+U+232a: />
+U+2423: Vs
+U+2440: 1h
+U+2441: 3h
+U+2442: 2h
+U+2443: 4h
+U+2446: 1j
+U+2447: 2j
+U+2448: 3j
+U+2449: 4j
+U+2460: _1-o_
+U+2461: _2-o_
+U+2462: _3-o_
+U+2463: _4-o_
+U+2464: _5-o_
+U+2465: _6-o_
+U+2466: _7-o_
+U+2467: _8-o_
+U+2468: _9-o_
+U+2469: _10-o_
+U+246a: _11-o_
+U+246b: _12-o_
+U+246c: _13-o_
+U+246d: _14-o_
+U+246e: _15-o_
+U+246f: _16-o_
+U+2470: _17-o_
+U+2471: _18-o_
+U+2472: _19-o_
+U+2473: _20-o_
+U+2474: _(1)_
+U+2475: _(2)_
+U+2476: _(3)_
+U+2477: _(4)_
+U+2478: _(5)_
+U+2479: _(6)_
+U+247a: _(7)_
+U+247b: _(8)_
+U+247c: _(9)_
+U+247d: _(10)_
+U+247e: _(11)_
+U+247f: _(12)_
+U+2480: _(13)_
+U+2481: _(14)_
+U+2482: _(15)_
+U+2483: _(16)_
+U+2484: _(17)_
+U+2485: _(18)_
+U+2486: _(19)_
+U+2487: _(20)_
+U+2488: 1.
+U+2489: 2.
+U+248a: 3.
+U+248b: 4.
+U+248c: 5.
+U+248d: 6.
+U+248e: 7.
+U+248f: 8.
+U+2490: 9.
+U+2491: _10._
+U+2492: _11._
+U+2493: _12._
+U+2494: _13._
+U+2495: _14._
+U+2496: _15._
+U+2497: _16._
+U+2498: _17._
+U+2499: _18._
+U+249a: _19._
+U+249b: _20._
+U+249c: _(a)_
+U+249d: _(b)_
+U+249e: _(c)_
+U+249f: _(d)_
+U+24a0: _(e)_
+U+24a1: _(f)_
+U+24a2: _(g)_
+U+24a3: _(h)_
+U+24a4: _(i)_
+U+24a5: _(j)_
+U+24a6: _(k)_
+U+24a7: _(l)_
+U+24a8: _(m)_
+U+24a9: _(n)_
+U+24aa: _(o)_
+U+24ab: _(p)_
+U+24ac: _(q)_
+U+24ad: _(r)_
+U+24ae: _(s)_
+U+24af: _(t)_
+U+24b0: _(u)_
+U+24b1: _(v)_
+U+24b2: _(w)_
+U+24b3: _(x)_
+U+24b4: _(y)_
+U+24b5: _(z)_
+U+24b6: _A-o_
+U+24b7: _B-o_
+U+24b8: _C-o_
+U+24b9: _D-o_
+U+24ba: _E-o_
+U+24bb: _F-o_
+U+24bc: _G-o_
+U+24bd: _H-o_
+U+24be: _I-o_
+U+24bf: _J-o_
+U+24c0: _K-o_
+U+24c1: _L-o_
+U+24c2: _M-o_
+U+24c3: _N-o_
+U+24c4: _O-o_
+U+24c5: _P-o_
+U+24c6: _Q-o_
+U+24c7: _R-o_
+U+24c8: _S-o_
+U+24c9: _T-o_
+U+24ca: _U-o_
+U+24cb: _V-o_
+U+24cc: _W-o_
+U+24cd: _X-o_
+U+24ce: _Y-o_
+U+24cf: _Z-o_
+U+24d0: _a-o_
+U+24d1: _b-o_
+U+24d2: _c-o_
+U+24d3: _d-o_
+U+24d4: _e-o_
+U+24d5: _f-o_
+U+24d6: _g-o_
+U+24d7: _h-o_
+U+24d8: _i-o_
+U+24d9: _j-o_
+U+24da: _k-o_
+U+24db: _l-o_
+U+24dc: _m-o_
+U+24dd: _n-o_
+U+24de: _o-o_
+U+24df: _p-o_
+U+24e0: _q-o_
+U+24e1: _r-o_
+U+24e2: _s-o_
+U+24e3: _t-o_
+U+24e4: _u-o_
+U+24e5: _v-o_
+U+24e6: _w-o_
+U+24e7: _x-o_
+U+24e8: _y-o_
+U+24e9: _z-o_
+U+24ea: _0-o_
+U+2500: hh
+U+2501: HH
+U+2502: vv
+U+2503: VV
+U+2504: 3-
+U+2505: 3_
+U+2506: 3!
+U+2507: 3/
+U+2508: 4-
+U+2509: 4_
+U+250a: 4!
+U+250b: 4/
+U+250c: dr
+U+250d: dR
+U+250e: Dr
+U+250f: DR
+U+2510: dl
+U+2511: dL
+U+2512: Dl
+U+2513: LD
+U+2514: ur
+U+2515: uR
+U+2516: Ur
+U+2517: UR
+U+2518: ul
+U+2519: uL
+U+251a: Ul
+U+251b: UL
+U+251c: vr
+U+251d: vR
+U+251e: _Udr_
+U+251f: _uDr_
+U+2520: Vr
+U+2521: _UdR_
+U+2522: _uDR_
+U+2523: VR
+U+2524: vl
+U+2525: vL
+U+2526: _Udl_
+U+2527: _uDl_
+U+2528: Vl
+U+2529: _UdL_
+U+252a: _uDL_
+U+252b: VL
+U+252c: dh
+U+252d: _dLr_
+U+252e: _dlR_
+U+252f: dH
+U+2530: Dh
+U+2531: _DLr_
+U+2532: _DlR_
+U+2533: DH
+U+2534: uh
+U+2535: _uLr_
+U+2536: _ulR_
+U+2537: uH
+U+2538: Uh
+U+2539: _ULr_
+U+253a: _UlR_
+U+253b: UH
+U+253c: vh
+U+253d: _vLr_
+U+253e: _vlR_
+U+253f: vH
+U+2540: _Udh_
+U+2541: _uDh_
+U+2542: Vh
+U+2543: _UdLr_
+U+2544: _UdlR_
+U+2545: _uDLr_
+U+2546: _uDlR_
+U+2547: _UdH_
+U+2548: _uDH_
+U+2549: _VLr_
+U+254a: _VlR_
+U+254b: VH
+U+2571: FD
+U+2572: BD
+U+2580: TB
+U+2584: LB
+U+2588: FB
+U+258c: lB
+U+2590: RB
+U+2591: .S
+U+2592: :S
+U+2593: ?S
+U+25a0: fS
+U+25a1: OS
+U+25a2: RO
+U+25a3: Rr
+U+25a4: RF
+U+25a5: RY
+U+25a6: RH
+U+25a7: RZ
+U+25a8: RK
+U+25a9: RX
+U+25aa: sB
+U+25ac: SR
+U+25ad: Or
+U+25b2: UT
+U+25b3: uT
+U+25b6: PR
+U+25b7: Tr
+U+25bc: Dt
+U+25bd: dT
+U+25c0: PL
+U+25c1: Tl
+U+25c6: Db
+U+25c7: Dw
+U+25ca: LZ
+U+25cb: 0m
+U+25ce: 0o
+U+25cf: 0M
+U+25d0: 0L
+U+25d1: 0R
+U+25d8: Sn
+U+25d9: Ic
+U+25e2: Fd
+U+25e3: Bd
+U+2605: *2
+U+2606: *1
+U+260e: _TEL_
+U+260f: _tel_
+U+261c: <H
+U+261e: >H
+U+263a: 0u
+U+263b: 0U
+U+263c: SU
+U+2640: Fm
+U+2642: Ml
+U+2660: cS
+U+2661: cH
+U+2662: cD
+U+2663: cC
+U+2664: _cS-_
+U+2665: _cH-_
+U+2666: _cD-_
+U+2667: _cC-_
+U+2669: Md
+U+266a: M8
+U+266b: M2
+U+266c: _M16_
+U+266d: Mb
+U+266e: Mx
+U+266f: MX
+U+2713: OK
+U+2717: XX
+U+2720: -X
+U+3000: IS
+U+3001: ,_
+U+3002: ._
+U+3003: +"
+U+3004: +_
+U+3005: *_
+U+3006: ;_
+U+3007: 0_
+U+300a: <+
+U+300b: >+
+U+300c: <'
+U+300d: >'
+U+300e: <"
+U+300f: >"
+U+3010: ("
+U+3011: )"
+U+3012: =T
+U+3013: =_
+U+3014: ('
+U+3015: )'
+U+3016: (I
+U+3017: )I
+U+301c: -?
+U+3020: _=T:)_
+U+3041: A5
+U+3042: a5
+U+3043: I5
+U+3044: i5
+U+3045: U5
+U+3046: u5
+U+3047: E5
+U+3048: e5
+U+3049: O5
+U+304a: o5
+U+304b: ka
+U+304c: ga
+U+304d: ki
+U+304e: gi
+U+304f: ku
+U+3050: gu
+U+3051: ke
+U+3052: ge
+U+3053: ko
+U+3054: go
+U+3055: sa
+U+3056: za
+U+3057: si
+U+3058: zi
+U+3059: su
+U+305a: zu
+U+305b: se
+U+305c: ze
+U+305d: so
+U+305e: zo
+U+305f: ta
+U+3060: da
+U+3061: ti
+U+3062: di
+U+3063: tU
+U+3064: tu
+U+3065: du
+U+3066: te
+U+3067: de
+U+3068: to
+U+3069: do
+U+306a: na
+U+306b: ni
+U+306c: nu
+U+306d: ne
+U+306e: no
+U+306f: ha
+U+3070: ba
+U+3071: pa
+U+3072: hi
+U+3073: bi
+U+3074: pi
+U+3075: hu
+U+3076: bu
+U+3077: pu
+U+3078: he
+U+3079: be
+U+307a: pe
+U+307b: ho
+U+307c: bo
+U+307d: po
+U+307e: ma
+U+307f: mi
+U+3080: mu
+U+3081: me
+U+3082: mo
+U+3083: yA
+U+3084: ya
+U+3085: yU
+U+3086: yu
+U+3087: yO
+U+3088: yo
+U+3089: ra
+U+308a: ri
+U+308b: ru
+U+308c: re
+U+308d: ro
+U+308e: wA
+U+308f: wa
+U+3090: wi
+U+3091: we
+U+3092: wo
+U+3093: n5
+U+3094: vu
+U+309b: "5
+U+309c: 05
+U+309d: *5
+U+309e: +5
+U+30a1: a6
+U+30a2: A6
+U+30a3: i6
+U+30a4: I6
+U+30a5: u6
+U+30a6: U6
+U+30a7: e6
+U+30a8: E6
+U+30a9: o6
+U+30aa: O6
+U+30ab: Ka
+U+30ac: Ga
+U+30ad: Ki
+U+30ae: Gi
+U+30af: Ku
+U+30b0: Gu
+U+30b1: Ke
+U+30b2: Ge
+U+30b3: Ko
+U+30b4: Go
+U+30b5: Sa
+U+30b6: Za
+U+30b7: Si
+U+30b8: Zi
+U+30b9: Su
+U+30ba: Zu
+U+30bb: Se
+U+30bc: Ze
+U+30bd: So
+U+30be: Zo
+U+30bf: Ta
+U+30c0: Da
+U+30c1: Ti
+U+30c2: Di
+U+30c3: TU
+U+30c4: Tu
+U+30c5: Du
+U+30c6: Te
+U+30c7: De
+U+30c8: To
+U+30c9: Do
+U+30ca: Na
+U+30cb: Ni
+U+30cc: Nu
+U+30cd: Ne
+U+30ce: No
+U+30cf: Ha
+U+30d0: Ba
+U+30d1: Pa
+U+30d2: Hi
+U+30d3: Bi
+U+30d4: Pi
+U+30d5: Hu
+U+30d6: Bu
+U+30d7: Pu
+U+30d8: He
+U+30d9: Be
+U+30da: Pe
+U+30db: Ho
+U+30dc: Bo
+U+30dd: Po
+U+30de: Ma
+U+30df: Mi
+U+30e0: Mu
+U+30e1: Me
+U+30e2: Mo
+U+30e3: YA
+U+30e4: Ya
+U+30e5: YU
+U+30e6: Yu
+U+30e7: YO
+U+30e8: Yo
+U+30e9: Ra
+U+30ea: Ri
+U+30eb: Ru
+U+30ec: Re
+U+30ed: Ro
+U+30ee: WA
+U+30ef: Wa
+U+30f0: Wi
+U+30f1: We
+U+30f2: Wo
+U+30f3: N6
+U+30f4: Vu
+U+30f5: KA
+U+30f6: KE
+U+30f7: Va
+U+30f8: Vi
+U+30f9: Ve
+U+30fa: Vo
+U+30fb: .6
+U+30fc: -6
+U+30fd: *6
+U+30fe: +6
+U+3105: b4
+U+3106: p4
+U+3107: m4
+U+3108: f4
+U+3109: d4
+U+310a: t4
+U+310b: n4
+U+310c: l4
+U+310d: g4
+U+310e: k4
+U+310f: h4
+U+3110: j4
+U+3111: q4
+U+3112: x4
+U+3113: zh
+U+3114: ch
+U+3115: sh
+U+3116: r4
+U+3117: z4
+U+3118: c4
+U+3119: s4
+U+311a: a4
+U+311b: o4
+U+311c: e4
+U+311d: _eh4_
+U+311e: ai
+U+311f: ei
+U+3120: au
+U+3121: ou
+U+3122: an
+U+3123: en
+U+3124: aN
+U+3125: eN
+U+3126: er
+U+3127: i4
+U+3128: u4
+U+3129: iu
+U+312a: v4
+U+312b: nG
+U+312c: gn
+U+321c: _(JU)_
+U+3220: 1c
+U+3221: 2c
+U+3222: 3c
+U+3223: 4c
+U+3224: 5c
+U+3225: 6c
+U+3226: 7c
+U+3227: 8c
+U+3228: 9c
+U+3229: _10c_
+U+327f: _KSC_
+U+fb00: ff
+U+fb01: fi
+U+fb02: fl
+U+fb03: _ffi_
+U+fb04: _ffl_
+U+fb05: ft
+U+fb06: st
+U+fe7d: _3+;_
+U+fe82: _aM._
+U+fe84: _aH._
+U+fe8d: _a+-_
+U+fe8e: _a+._
+U+fe8f: _b+-_
+U+fe90: _b+,_
+U+fe91: _b+;_
+U+fe92: _b+._
+U+fe93: _tm-_
+U+fe94: _tm._
+U+fe95: _t+-_
+U+fe96: _t+,_
+U+fe97: _t+;_
+U+fe98: _t+._
+U+fe99: _tk-_
+U+fe9a: _tk,_
+U+fe9b: _tk;_
+U+fe9c: _tk._
+U+fe9d: _g+-_
+U+fe9e: _g+,_
+U+fe9f: _g+;_
+U+fea0: _g+._
+U+fea1: _hk-_
+U+fea2: _hk,_
+U+fea3: _hk;_
+U+fea4: _hk._
+U+fea5: _x+-_
+U+fea6: _x+,_
+U+fea7: _x+;_
+U+fea8: _x+._
+U+fea9: _d+-_
+U+feaa: _d+._
+U+feab: _dk-_
+U+feac: _dk._
+U+fead: _r+-_
+U+feae: _r+._
+U+feaf: _z+-_
+U+feb0: _z+._
+U+feb1: _s+-_
+U+feb2: _s+,_
+U+feb3: _s+;_
+U+feb4: _s+._
+U+feb5: _sn-_
+U+feb6: _sn,_
+U+feb7: _sn;_
+U+feb8: _sn._
+U+feb9: _c+-_
+U+feba: _c+,_
+U+febb: _c+;_
+U+febc: _c+._
+U+febd: _dd-_
+U+febe: _dd,_
+U+febf: _dd;_
+U+fec0: _dd._
+U+fec1: _tj-_
+U+fec2: _tj,_
+U+fec3: _tj;_
+U+fec4: _tj._
+U+fec5: _zH-_
+U+fec6: _zH,_
+U+fec7: _zH;_
+U+fec8: _zH._
+U+fec9: _e+-_
+U+feca: _e+,_
+U+fecb: _e+;_
+U+fecc: _e+._
+U+fecd: _i+-_
+U+fece: _i+,_
+U+fecf: _i+;_
+U+fed0: _i+._
+U+fed1: _f+-_
+U+fed2: _f+,_
+U+fed3: _f+;_
+U+fed4: _f+._
+U+fed5: _q+-_
+U+fed6: _q+,_
+U+fed7: _q+;_
+U+fed8: _q+._
+U+fed9: _k+-_
+U+feda: _k+,_
+U+fedb: _k+;_
+U+fedc: _k+._
+U+fedd: _l+-_
+U+fede: _l+,_
+U+fedf: _l+;_
+U+fee0: _l+._
+U+fee1: _m+-_
+U+fee2: _m+,_
+U+fee3: _m+;_
+U+fee4: _m+._
+U+fee5: _n+-_
+U+fee6: _n+,_
+U+fee7: _n+;_
+U+fee8: _n+._
+U+fee9: _h+-_
+U+feea: _h+,_
+U+feeb: _h+;_
+U+feec: _h+._
+U+feed: _w+-_
+U+feee: _w+._
+U+feef: _j+-_
+U+fef0: _j+._
+U+fef1: _y+-_
+U+fef2: _y+,_
+U+fef3: _y+;_
+U+fef4: _y+._
+U+fef5: _lM-_
+U+fef6: _lM._
+U+fef7: _lH-_
+U+fef8: _lH._
+U+fef9: _lh-_
+U+fefa: _lh._
+U+fefb: _la-_
+U+fefc: _la._
+U+0000: NU
+U+0001: SH
+U+0002: SX
+U+0003: EX
+U+0004: ET
+U+0005: EQ
+U+0006: AK
+U+0007: BL
+U+0008: BS
+U+0009: HT
+# U+000a: LF
+U+000b: VT
+U+000c: FF
+U+000d: CR
+U+000e: SO
+U+000f: SI
+U+0010: DL
+U+0011: D1
+U+0012: D2
+U+0013: D3
+U+0014: D4
+U+0015: NK
+U+0016: SY
+U+0017: EB
+U+0018: CN
+U+0019: EM
+U+001a: SB
+U+001b: EC
+U+001c: FS
+U+001d: GS
+U+001e: RS
+U+001f: US
+U+007f: DT
+U+0080: PA
+U+0081: HO
+U+0082: BH
+U+0083: NH
+U+0084: IN
+U+0085: NL
+U+0086: SA
+U+0087: ES
+U+0088: HS
+U+0089: HJ
+U+008a: VS
+U+008b: PD
+U+008c: PU
+U+008d: RI
+U+008e: S2
+U+008f: S3
+U+0090: DC
+U+0091: P1
+U+0092: P2
+U+0093: TS
+U+0094: CC
+U+0095: MW
+U+0096: SG
+U+0097: EG
+U+0098: SS
+U+0099: GC
+U+009a: SC
+U+009b: CI
+U+009c: ST
+U+009d: OC
+U+009e: PM
+U+009f: AC
+# Characters in Private Use Area (e000-f8ff) do not have ussigned numbers
+# according Unicode 2.0
diff --git a/src/chrtrans/next_uni.tbl b/src/chrtrans/next_uni.tbl
new file mode 100644
index 0000000..a76ae34
--- /dev/null
+++ b/src/chrtrans/next_uni.tbl
@@ -0,0 +1,185 @@
+# This file has been modified for lynx (see README.tables)
+
+#The MIME name of this charset.
+Mnext
+
+#Name as a Display Charset (used on Options screen)
+ONeXT character set
+
+#
+# Name: NextStep Encoding to Unicode
+# Unicode version: 1.1
+# Table version: 0.1
+# Table format: Format A
+# Date: 1999 September 23
+# Authors: Rick McGowan
+#
+# Copyright (c) 1991-1999 Unicode, Inc. All Rights reserved.
+#
+# This file is provided as-is by Unicode, Inc. (The Unicode Consortium).
+# No claims are made as to fitness for any particular purpose. No
+# warranties of any kind are expressed or implied. The recipient
+# agrees to determine applicability of information provided. If this
+# file has been provided on optical media by Unicode, Inc., the sole
+# remedy for any claim will be exchange of defective media within 90
+# days of receipt.
+#
+# Unicode, Inc. hereby grants the right to freely use the information
+# supplied in this file in the creation of products supporting the
+# Unicode Standard, and to make copies of this file in any form for
+# internal or external distribution as long as this notice remains
+# attached.
+#
+# General notes:
+#
+# This table contains the data the Unicode Consortium has on how
+# NextStep Encoding characters map into Unicode. Since the first
+# 128 characters (0x0 - 0x7f) are identical to ASCII and Unicode,
+# this table only maps the NextStep range from 0x80 - 0xFF.
+#
+# This file is provided for historical reference only and pertains
+# to NextStep and OpenStep products shipped prior to the acquisition
+# of NeXT by Apple Computer, Inc. See http://www.apple.com for
+# further information.
+#
+# Format: Three tab-separated columns
+# Column #1 is the NextStep code (in hex as 0xXX)
+# Column #2 is the Unicode (in hex as 0xXXXX)
+# Column #3 NextStep name, Unicode name (follows a comment sign, '#')
+#
+# The entries are in NextStep order
+#
+# Any comments or problems, contact info@unicode.org
+#
+#
+0x20-0x7f idem
+#
+0x80 U+00a0 # NO-BREAK SPACE
+0x81 U+00c0 # LATIN CAPITAL LETTER A WITH GRAVE
+0x82 U+00c1 # LATIN CAPITAL LETTER A WITH ACUTE
+0x83 U+00c2 # LATIN CAPITAL LETTER A WITH CIRCUMFLEX
+0x84 U+00c3 # LATIN CAPITAL LETTER A WITH TILDE
+0x85 U+00c4 # LATIN CAPITAL LETTER A WITH DIAERESIS
+0x86 U+00c5 # LATIN CAPITAL LETTER A WITH RING
+0x87 U+00c7 # LATIN CAPITAL LETTER C WITH CEDILLA
+0x88 U+00c8 # LATIN CAPITAL LETTER E WITH GRAVE
+0x89 U+00c9 # LATIN CAPITAL LETTER E WITH ACUTE
+0x8a U+00ca # LATIN CAPITAL LETTER E WITH CIRCUMFLEX
+0x8b U+00cb # LATIN CAPITAL LETTER E WITH DIAERESIS
+0x8c U+00cc # LATIN CAPITAL LETTER I WITH GRAVE
+0x8d U+00cd # LATIN CAPITAL LETTER I WITH ACUTE
+0x8e U+00ce # LATIN CAPITAL LETTER I WITH CIRCUMFLEX
+0x8f U+00cf # LATIN CAPITAL LETTER I WITH DIAERESIS
+0x90 U+00d0 # LATIN CAPITAL LETTER ETH
+0x91 U+00d1 # LATIN CAPITAL LETTER N WITH TILDE
+0x92 U+00d2 # LATIN CAPITAL LETTER O WITH GRAVE
+0x93 U+00d3 # LATIN CAPITAL LETTER O WITH ACUTE
+0x94 U+00d4 # LATIN CAPITAL LETTER O WITH CIRCUMFLEX
+0x95 U+00d5 # LATIN CAPITAL LETTER O WITH TILDE
+0x96 U+00d6 # LATIN CAPITAL LETTER O WITH DIAERESIS
+0x97 U+00d9 # LATIN CAPITAL LETTER U WITH GRAVE
+0x98 U+00da # LATIN CAPITAL LETTER U WITH ACUTE
+0x99 U+00db # LATIN CAPITAL LETTER U WITH CIRCUMFLEX
+0x9a U+00dc # LATIN CAPITAL LETTER U WITH DIAERESIS
+0x9b U+00dd # LATIN CAPITAL LETTER Y WITH ACUTE
+0x9c U+00de # LATIN CAPITAL LETTER THORN
+0x9d U+00b5 # MICRO SIGN
+0x9e U+00d7 # MULTIPLICATION SIGN
+0x9f U+00f7 # DIVISION SIGN
+0xa0 U+00a9 # COPYRIGHT SIGN
+0xa1 U+00a1 # INVERTED EXCLAMATION MARK
+0xa2 U+00a2 # CENT SIGN
+0xa3 U+00a3 # POUND SIGN
+0xa4 U+2044 # FRACTION SLASH
+0xa5 U+00a5 # YEN SIGN
+0xa6 U+0192 # LATIN SMALL LETTER F WITH HOOK
+0xa7 U+00a7 # SECTION SIGN
+0xa8 U+00a4 # CURRENCY SIGN
+0xa9 U+2019 # RIGHT SINGLE QUOTATION MARK
+0xaa U+201c # LEFT DOUBLE QUOTATION MARK
+0xab U+00ab # LEFT-POINTING DOUBLE ANGLE QUOTATION MARK
+0xac U+2039 # SINGLE LEFT-POINTING ANGLE QUOTATION MARK
+0xad U+203a # SINGLE RIGHT-POINTING ANGLE QUOTATION MARK
+0xae U+fb01 # LATIN SMALL LIGATURE FI
+0xaf U+fb02 # LATIN SMALL LIGATURE FL
+0xb0 U+00ae # REGISTERED SIGN
+0xb1 U+2013 # EN DASH
+0xb2 U+2020 # DAGGER
+0xb3 U+2021 # DOUBLE DAGGER
+0xb4 U+00b7 # MIDDLE DOT
+0xb5 U+00a6 # BROKEN BAR
+0xb6 U+00b6 # PILCROW SIGN
+0xb7 U+2022 # BULLET
+0xb8 U+201a # SINGLE LOW-9 QUOTATION MARK
+0xb9 U+201e # DOUBLE LOW-9 QUOTATION MARK
+0xba U+201d # RIGHT DOUBLE QUOTATION MARK
+0xbb U+00bb # RIGHT-POINTING DOUBLE ANGLE QUOTATION MARK
+0xbc U+2026 # HORIZONTAL ELLIPSIS
+0xbd U+2030 # PER MILLE SIGN
+0xbe U+00ac # NOT SIGN
+0xbf U+00bf # INVERTED QUESTION MARK
+0xc0 U+00b9 # SUPERSCRIPT ONE
+0xc1 U+02cb # MODIFIER LETTER GRAVE ACCENT
+0xc2 U+00b4 # ACUTE ACCENT
+0xc3 U+02c6 # MODIFIER LETTER CIRCUMFLEX ACCENT
+0xc4 U+02dc # SMALL TILDE
+0xc5 U+00af # MACRON
+0xc6 U+02d8 # BREVE
+0xc7 U+02d9 # DOT ABOVE
+0xc8 U+00a8 # DIAERESIS
+0xc9 U+00b2 # SUPERSCRIPT TWO
+0xca U+02da # RING ABOVE
+0xcb U+00b8 # CEDILLA
+0xcc U+00b3 # SUPERSCRIPT THREE
+0xcd U+02dd # DOUBLE ACUTE ACCENT
+0xce U+02db # OGONEK
+0xcf U+02c7 # CARON
+0xd0 U+2014 # EM DASH
+0xd1 U+00b1 # PLUS-MINUS SIGN
+0xd2 U+00bc # VULGAR FRACTION ONE QUARTER
+0xd3 U+00bd # VULGAR FRACTION ONE HALF
+0xd4 U+00be # VULGAR FRACTION THREE QUARTERS
+0xd5 U+00e0 # LATIN SMALL LETTER A WITH GRAVE
+0xd6 U+00e1 # LATIN SMALL LETTER A WITH ACUTE
+0xd7 U+00e2 # LATIN SMALL LETTER A WITH CIRCUMFLEX
+0xd8 U+00e3 # LATIN SMALL LETTER A WITH TILDE
+0xd9 U+00e4 # LATIN SMALL LETTER A WITH DIAERESIS
+0xda U+00e5 # LATIN SMALL LETTER A WITH RING ABOVE
+0xdb U+00e7 # LATIN SMALL LETTER C WITH CEDILLA
+0xdc U+00e8 # LATIN SMALL LETTER E WITH GRAVE
+0xdd U+00e9 # LATIN SMALL LETTER E WITH ACUTE
+0xde U+00ea # LATIN SMALL LETTER E WITH CIRCUMFLEX
+0xdf U+00eb # LATIN SMALL LETTER E WITH DIAERESIS
+0xe0 U+00ec # LATIN SMALL LETTER I WITH GRAVE
+0xe1 U+00c6 # LATIN CAPITAL LETTER AE
+0xe2 U+00ed # LATIN SMALL LETTER I WITH ACUTE
+0xe3 U+00aa # FEMININE ORDINAL INDICATOR
+0xe4 U+00ee # LATIN SMALL LETTER I WITH CIRCUMFLEX
+0xe5 U+00ef # LATIN SMALL LETTER I WITH DIAERESIS
+0xe6 U+00f0 # LATIN SMALL LETTER ETH
+0xe7 U+00f1 # LATIN SMALL LETTER N WITH TILDE
+0xe8 U+0141 # LATIN CAPITAL LETTER L WITH STROKE
+0xe9 U+00d8 # LATIN CAPITAL LETTER O WITH STROKE
+0xea U+0152 # LATIN CAPITAL LIGATURE OE
+0xeb U+00ba # MASCULINE ORDINAL INDICATOR
+0xec U+00f2 # LATIN SMALL LETTER O WITH GRAVE
+0xed U+00f3 # LATIN SMALL LETTER O WITH ACUTE
+0xee U+00f4 # LATIN SMALL LETTER O WITH CIRCUMFLEX
+0xef U+00f5 # LATIN SMALL LETTER O WITH TILDE
+0xf0 U+00f6 # LATIN SMALL LETTER O WITH DIAERESIS
+0xf1 U+00e6 # LATIN SMALL LETTER AE
+0xf2 U+00f9 # LATIN SMALL LETTER U WITH GRAVE
+0xf3 U+00fa # LATIN SMALL LETTER U WITH ACUTE
+0xf4 U+00fb # LATIN SMALL LETTER U WITH CIRCUMFLEX
+0xf5 U+0131 # LATIN SMALL LETTER DOTLESS I
+0xf6 U+00fc # LATIN SMALL LETTER U WITH DIAERESIS
+0xf7 U+00fd # LATIN SMALL LETTER Y WITH ACUTE
+0xf8 U+0142 # LATIN SMALL LETTER L WITH STROKE
+0xf9 U+00f8 # LATIN SMALL LETTER O WITH STROKE
+0xfa U+0153 # LATIN SMALL LIGATURE OE
+0xfb U+00df # LATIN SMALL LETTER SHARP S
+0xfc U+00fe # LATIN SMALL LETTER THORN
+0xfd U+00ff # LATIN SMALL LETTER Y WITH DIAERESIS
+#0xfe U+fffd # .notdef, REPLACEMENT CHARACTER
+#0xff U+fffd # .notdef, REPLACEMENT CHARACTER
+
diff --git a/src/chrtrans/pt154_uni.tbl b/src/chrtrans/pt154_uni.tbl
new file mode 100644
index 0000000..0bacb52
--- /dev/null
+++ b/src/chrtrans/pt154_uni.tbl
@@ -0,0 +1,174 @@
+Mptcp154
+#
+OCyrillic-Asian (PT154)
+#
+C1540
+
+#####
+#
+# Charset aliases:
+# csPTCP154
+# PT154
+# CP154
+# Cyrillic-Asian
+#
+# Suitability for use in MIME text:
+# Yes
+#
+# ISO 10646 equivalency table:
+# Format: Three tab-separated columns
+# Column #1 is the Paratype CP154 code (in hex)
+# Column #2 is the Unicode (in hex as 0xXXXX)
+# Column #3 is the Unicode name (follows a comment sign, '#')
+#
+# The entries are in Paratype CP154 order
+#
+#####
+
+0x20-0x7e idem
+#
+0x80 U+0496 # CYRILLIC CAPITAL LETTER ZHE WITH DESCENDER
+0x81 U+0492 # CYRILLIC CAPITAL LETTER GHE WITH STROKE
+0x82 U+04EE # CYRILLIC CAPITAL LETTER U WITH MACRON
+0x83 U+0493 # CYRILLIC SMALL LETTER GHE WITH STROKE
+0x84 U+201E # DOUBLE LOW-9 QUOTATION MARK
+0x85 U+2026 # HORIZONTAL ELLIPSIS
+0x86 U+04B6 # CYRILLIC CAPITAL LETTER CHE WITH DESCENDER
+0x87 U+04AE # CYRILLIC CAPITAL LETTER STRAIGHT U
+0x88 U+04B2 # CYRILLIC CAPITAL LETTER HA WITH DESCENDER
+0x89 U+04AF # CYRILLIC SMALL LETTER STRAIGHT U
+0x8a U+04A0 # CYRILLIC CAPITAL LETTER BASHKIR KA
+0x8b U+04E2 # CYRILLIC CAPITAL LETTER I WITH MACRON
+0x8c U+04A2 # CYRILLIC CAPITAL LETTER EN WITH DESCENDER
+0x8d U+049A # CYRILLIC CAPITAL LETTER KA WITH DESCENDER
+0x8e U+04BA # CYRILLIC CAPITAL LETTER SHHA
+0x8f U+04B8 # CYRILLIC CAPITAL LETTER CHE WITH VERTICAL STROKE
+0x90 U+0497 # CYRILLIC SMALL LETTER ZHE WITH DESCENDER
+0x91 U+2018 # LEFT SINGLE QUOTATION MARK
+0x92 U+2019 # RIGHT SINGLE QUOTATION MARK
+0x93 U+201C # LEFT DOUBLE QUOTATION MARK
+0x94 U+201D # RIGHT DOUBLE QUOTATION MARK
+0x95 U+2022 # BULLET
+0x96 U+2013 # EN DASH
+0x97 U+2014 # EM DASH
+0x98 U+04B3 # CYRILLIC SMALL LETTER HA WITH DESCENDER
+0x99 U+04B7 # CYRILLIC SMALL LETTER CHE WITH DESCENDER
+0x9a U+04A1 # CYRILLIC SMALL LETTER BASHKIR KA
+0x9b U+04E3 # CYRILLIC SMALL LETTER I WITH MACRON
+0x9c U+04A3 # CYRILLIC SMALL LETTER EN WITH DESCENDER
+0x9d U+049B # CYRILLIC SMALL LETTER KA WITH DESCENDER
+0x9e U+04BB # CYRILLIC SMALL LETTER SHHA
+0x9f U+04B9 # CYRILLIC SMALL LETTER CHE WITH VERTICAL STROKE
+0xa0 U+00A0 # NO-BREAK SPACE
+0xa1 U+040E # CYRILLIC CAPITAL LETTER SHORT U (Byelorussian)
+0xa2 U+045E # CYRILLIC SMALL LETTER SHORT U (Byelorussian)
+0xa3 U+0408 # CYRILLIC CAPITAL LETTER JE
+0xa4 U+04E8 # CYRILLIC CAPITAL LETTER BARRED O
+0xa5 U+0498 # CYRILLIC CAPITAL LETTER ZE WITH DESCENDER
+0xa6 U+04B0 # CYRILLIC CAPITAL LETTER STRAIGHT U WITH STROKE
+0xa7 U+00A7 # SECTION SIGN
+0xa8 U+0401 # CYRILLIC CAPITAL LETTER IO
+0xa9 U+00A9 # COPYRIGHT SIGN
+0xaa U+04D8 # CYRILLIC CAPITAL LETTER SCHWA
+0xab U+00AB # LEFT-POINTING DOUBLE ANGLE QUOTATION MARK
+0xac U+00AC # NOT SIGN
+0xad U+04EF # CYRILLIC SMALL LETTER U WITH MACRON
+0xae U+00AE # REGISTERED SIGN
+0xaf U+049C # CYRILLIC CAPITAL LETTER KA WITH VERTICAL STROKE
+0xb0 U+00B0 # DEGREE SIGN
+0xb1 U+04B1 # CYRILLIC SMALL LETTER STRAIGHT U WITH STROKE
+0xb2 U+0406 # CYRILLIC CAPITAL LETTER BYELORUSSIAN-UKRAINIAN I
+0xb3 U+0456 # CYRILLIC SMALL LETTER BYELORUSSIAN-UKRAINIAN I
+0xb4 U+0499 # CYRILLIC SMALL LETTER ZE WITH DESCENDER
+0xb5 U+04E9 # CYRILLIC SMALL LETTER BARRED O
+0xb6 U+00B6 # PILCROW SIGN
+0xb7 U+00B7 # MIDDLE DOT
+0xb8 U+0451 # CYRILLIC SMALL LETTER IO
+0xb9 U+2116 # NUMERO SIGN
+0xba U+04D9 # CYRILLIC SMALL LETTER SCHWA
+0xbb U+00BB # RIGHT-POINTING DOUBLE ANGLE QUOTATION MARK
+0xbc U+0458 # CYRILLIC SMALL LETTER JE
+0xbd U+04AA # CYRILLIC CAPITAL LETTER ES WITH DESCENDER
+0xbe U+04AB # CYRILLIC SMALL LETTER ES WITH DESCENDER
+0xbf U+049D # CYRILLIC SMALL LETTER KA WITH VERTICAL STROKE
+0xc0 U+0410 # CYRILLIC CAPITAL LETTER A
+0xc1 U+0411 # CYRILLIC CAPITAL LETTER BE
+0xc2 U+0412 # CYRILLIC CAPITAL LETTER VE
+0xc3 U+0413 # CYRILLIC CAPITAL LETTER GHE
+0xc4 U+0414 # CYRILLIC CAPITAL LETTER DE
+0xc5 U+0415 # CYRILLIC CAPITAL LETTER IE
+0xc6 U+0416 # CYRILLIC CAPITAL LETTER ZHE
+0xc7 U+0417 # CYRILLIC CAPITAL LETTER ZE
+0xc8 U+0418 # CYRILLIC CAPITAL LETTER I
+0xc9 U+0419 # CYRILLIC CAPITAL LETTER SHORT I
+0xca U+041A # CYRILLIC CAPITAL LETTER KA
+0xcb U+041B # CYRILLIC CAPITAL LETTER EL
+0xcc U+041C # CYRILLIC CAPITAL LETTER EM
+0xcd U+041D # CYRILLIC CAPITAL LETTER EN
+0xce U+041E # CYRILLIC CAPITAL LETTER O
+0xcf U+041F # CYRILLIC CAPITAL LETTER PE
+0xd0 U+0420 # CYRILLIC CAPITAL LETTER ER
+0xd1 U+0421 # CYRILLIC CAPITAL LETTER ES
+0xd2 U+0422 # CYRILLIC CAPITAL LETTER TE
+0xd3 U+0423 # CYRILLIC CAPITAL LETTER U
+0xd4 U+0424 # CYRILLIC CAPITAL LETTER EF
+0xd5 U+0425 # CYRILLIC CAPITAL LETTER HA
+0xd6 U+0426 # CYRILLIC CAPITAL LETTER TSE
+0xd7 U+0427 # CYRILLIC CAPITAL LETTER CHE
+0xd8 U+0428 # CYRILLIC CAPITAL LETTER SHA
+0xd9 U+0429 # CYRILLIC CAPITAL LETTER SHCHA
+0xda U+042A # CYRILLIC CAPITAL LETTER HARD SIGN
+0xdb U+042B # CYRILLIC CAPITAL LETTER YERU
+0xdc U+042C # CYRILLIC CAPITAL LETTER SOFT SIGN
+0xdd U+042D # CYRILLIC CAPITAL LETTER E
+0xde U+042E # CYRILLIC CAPITAL LETTER YU
+0xdf U+042F # CYRILLIC CAPITAL LETTER YA
+0xe0 U+0430 # CYRILLIC SMALL LETTER A
+0xe1 U+0431 # CYRILLIC SMALL LETTER BE
+0xe2 U+0432 # CYRILLIC SMALL LETTER VE
+0xe3 U+0433 # CYRILLIC SMALL LETTER GHE
+0xe4 U+0434 # CYRILLIC SMALL LETTER DE
+0xe5 U+0435 # CYRILLIC SMALL LETTER IE
+0xe6 U+0436 # CYRILLIC SMALL LETTER ZHE
+0xe7 U+0437 # CYRILLIC SMALL LETTER ZE
+0xe8 U+0438 # CYRILLIC SMALL LETTER I
+0xe9 U+0439 # CYRILLIC SMALL LETTER SHORT I
+0xea U+043A # CYRILLIC SMALL LETTER KA
+0xeb U+043B # CYRILLIC SMALL LETTER EL
+0xec U+043C # CYRILLIC SMALL LETTER EM
+0xed U+043D # CYRILLIC SMALL LETTER EN
+0xee U+043E # CYRILLIC SMALL LETTER O
+0xef U+043F # CYRILLIC SMALL LETTER PE
+0xf0 U+0440 # CYRILLIC SMALL LETTER ER
+0xf1 U+0441 # CYRILLIC SMALL LETTER ES
+0xf2 U+0442 # CYRILLIC SMALL LETTER TE
+0xf3 U+0443 # CYRILLIC SMALL LETTER U
+0xf4 U+0444 # CYRILLIC SMALL LETTER EF
+0xf5 U+0445 # CYRILLIC SMALL LETTER HA
+0xf6 U+0446 # CYRILLIC SMALL LETTER TSE
+0xf7 U+0447 # CYRILLIC SMALL LETTER CHE
+0xf8 U+0448 # CYRILLIC SMALL LETTER SHA
+0xf9 U+0449 # CYRILLIC SMALL LETTER SHCHA
+0xfa U+044A # CYRILLIC SMALL LETTER HARD SIGN
+0xfb U+044B # CYRILLIC SMALL LETTER YERU
+0xfc U+044C # CYRILLIC SMALL LETTER SOFT SIGN
+0xfd U+044D # CYRILLIC SMALL LETTER E
+0xfe U+044E # CYRILLIC SMALL LETTER YU
+0xff U+044F # CYRILLIC SMALL LETTER YA
+
+#####
+#
+# Additional information:
+# This charset based on CP1251 with added asian cyrillic symbols.
+#
+# Person & email address to contact for further information:
+# Alexander Uskov
+# InternetDataCenter of KazakhTelecom.
+# e-mail: auskov@idc.kz
+#
+# Intended usage:
+# COMMON
+#
+# (record created 2002-09-27)
+#
+#####
diff --git a/src/chrtrans/rfc_suni.tbl b/src/chrtrans/rfc_suni.tbl
new file mode 100644
index 0000000..65fa17e
--- /dev/null
+++ b/src/chrtrans/rfc_suni.tbl
@@ -0,0 +1,1958 @@
+# Generated from the mnemonic file found under ftp://dkuug.dk/i18n/
+# then hand-tweaked
+# perl -n -e \
+# 'if (s|<([^ \t]+)>\s+<U([\dA-Z]{4})>\s.*$|U+\L\2\E:\1|) {s|/?(.)|\1|g&&print}'\
+# mnemonic,ds
+
+#The MIME name of this charset.
+Mmnemonic+ascii+0
+
+#Name as a Display Charset (used on Options screen)
+ORFC 1345 w/o Intro
+
+# Don't fall back to default table for unicode -> 8bit
+Fallback NO
+
+U+0020:SP
+U+0021:!
+U+0022:"
+U+0023:Nb
+U+0024:DO
+U+0025:%
+U+0026:&
+U+0027:'
+U+0028:(
+U+0029:)
+U+002a:*
+U+002b:+
+U+002c:,
+U+002d:-
+U+002e:.
+U+002f:/
+U+0030:0
+U+0031:1
+U+0032:2
+U+0033:3
+U+0034:4
+U+0035:5
+U+0036:6
+U+0037:7
+U+0038:8
+U+0039:9
+U+003a::
+U+003b:;
+U+003c:<
+U+003d:=
+U+003e:>
+U+003f:?
+U+0040:At
+U+0041:A
+U+0042:B
+U+0043:C
+U+0044:D
+U+0045:E
+U+0046:F
+U+0047:G
+U+0048:H
+U+0049:I
+U+004a:J
+U+004b:K
+U+004c:L
+U+004d:M
+U+004e:N
+U+004f:O
+U+0050:P
+U+0051:Q
+U+0052:R
+U+0053:S
+U+0054:T
+U+0055:U
+U+0056:V
+U+0057:W
+U+0058:X
+U+0059:Y
+U+005a:Z
+U+005b:<(
+U+005c://
+U+005d:)>
+U+005e:'>
+U+005f:_
+U+0060:'!
+U+0061:a
+U+0062:b
+U+0063:c
+U+0064:d
+U+0065:e
+U+0066:f
+U+0067:g
+U+0068:h
+U+0069:i
+U+006a:j
+U+006b:k
+U+006c:l
+U+006d:m
+U+006e:n
+U+006f:o
+U+0070:p
+U+0071:q
+U+0072:r
+U+0073:s
+U+0074:t
+U+0075:u
+U+0076:v
+U+0077:w
+U+0078:x
+U+0079:y
+U+007a:z
+U+007b:(!
+U+007c:!!
+U+007d:!)
+U+007e:'?
+U+00a0:NS
+U+00a1:!I
+U+00a2:Ct
+U+00a3:Pd
+U+00a4:Cu
+U+00a5:Ye
+U+00a6:BB
+U+00a7:SE
+U+00a8:':
+U+00a9:Co
+U+00aa:-a
+U+00ab:<<
+U+00ac:NO
+U+00ad:--
+U+00ae:Rg
+U+00af:'m
+U+00b0:DG
+U+00b1:+-
+U+00b2:2S
+U+00b3:3S
+U+00b4:''
+U+00b5:My
+U+00b6:PI
+U+00b7:.M
+U+00b8:',
+U+00b9:1S
+U+00ba:-o
+U+00bb:>>
+U+00bc:14
+U+00bd:12
+U+00be:34
+U+00bf:?I
+U+00c0:A!
+U+00c1:A'
+U+00c2:A>
+U+00c3:A?
+U+00c4:A:
+U+00c5:AA
+U+00c6:AE
+U+00c7:C,
+U+00c8:E!
+U+00c9:E'
+U+00ca:E>
+U+00cb:E:
+U+00cc:I!
+U+00cd:I'
+U+00ce:I>
+U+00cf:I:
+U+00d0:D-
+U+00d1:N?
+U+00d2:O!
+U+00d3:O'
+U+00d4:O>
+U+00d5:O?
+U+00d6:O:
+U+00d7:*X
+U+00d8:O/
+U+00d9:U!
+U+00da:U'
+U+00db:U>
+U+00dc:U:
+U+00dd:Y'
+U+00de:TH
+U+00df:ss
+U+00e0:a!
+U+00e1:a'
+U+00e2:a>
+U+00e3:a?
+U+00e4:a:
+U+00e5:aa
+U+00e6:ae
+U+00e7:c,
+U+00e8:e!
+U+00e9:e'
+U+00ea:e>
+U+00eb:e:
+U+00ec:i!
+U+00ed:i'
+U+00ee:i>
+U+00ef:i:
+U+00f0:d-
+U+00f1:n?
+U+00f2:o!
+U+00f3:o'
+U+00f4:o>
+U+00f5:o?
+U+00f6:o:
+U+00f7:-:
+U+00f8:o/
+U+00f9:u!
+U+00fa:u'
+U+00fb:u>
+U+00fc:u:
+U+00fd:y'
+U+00fe:th
+U+00ff:y:
+U+0100:A-
+U+0101:a-
+U+0102:A(
+U+0103:a(
+U+0104:A;
+U+0105:a;
+U+0106:C'
+U+0107:c'
+U+0108:C>
+U+0109:c>
+U+010a:C.
+U+010b:c.
+U+010c:C<
+U+010d:c<
+U+010e:D<
+U+010f:d<
+U+0110:D/
+U+0111:d/
+U+0112:E-
+U+0113:e-
+U+0114:E(
+U+0115:e(
+U+0116:E.
+U+0117:e.
+U+0118:E;
+U+0119:e;
+U+011a:E<
+U+011b:e<
+U+011c:G>
+U+011d:g>
+U+011e:G(
+U+011f:g(
+U+0120:G.
+U+0121:g.
+U+0122:G,
+U+0123:g,
+U+0124:H>
+U+0125:h>
+U+0126:H/
+U+0127:h/
+U+0128:I?
+U+0129:i?
+U+012a:I-
+U+012b:i-
+U+012c:I(
+U+012d:i(
+U+012e:I;
+U+012f:i;
+U+0130:I.
+U+0131:i.
+U+0132:IJ
+U+0133:ij
+U+0134:J>
+U+0135:j>
+U+0136:K,
+U+0137:k,
+U+0138:kk
+U+0139:L'
+U+013a:l'
+U+013b:L,
+U+013c:l,
+U+013d:L<
+U+013e:l<
+U+013f:L.
+U+0140:l.
+U+0141:L/
+U+0142:l/
+U+0143:N'
+U+0144:n'
+U+0145:N,
+U+0146:n,
+U+0147:N<
+U+0148:n<
+U+0149:'n
+U+014a:NG
+U+014b:ng
+U+014c:O-
+U+014d:o-
+U+014e:O(
+U+014f:o(
+U+0150:O"
+U+0151:o"
+U+0152:OE
+U+0153:oe
+U+0154:R'
+U+0155:r'
+U+0156:R,
+U+0157:r,
+U+0158:R<
+U+0159:r<
+U+015a:S'
+U+015b:s'
+U+015c:S>
+U+015d:s>
+U+015e:S,
+U+015f:s,
+U+0160:S<
+U+0161:s<
+U+0162:T,
+U+0163:t,
+U+0164:T<
+U+0165:t<
+U+0166:T/
+U+0167:t/
+U+0168:U?
+U+0169:u?
+U+016a:U-
+U+016b:u-
+U+016c:U(
+U+016d:u(
+U+016e:U0
+U+016f:u0
+U+0170:U"
+U+0171:u"
+U+0172:U;
+U+0173:u;
+U+0174:W>
+U+0175:w>
+U+0176:Y>
+U+0177:y>
+U+0178:Y:
+U+0179:Z'
+U+017a:z'
+U+017b:Z.
+U+017c:z.
+U+017d:Z<
+U+017e:z<
+U+017f:s1
+U+0187:C2
+U+0188:c2
+U+0191:F2
+U+0192:f2
+U+0198:K2
+U+0199:k2
+U+01a0:O9
+U+01a1:o9
+U+01a2:OI
+U+01a3:oi
+U+01a6:yr
+U+01af:U9
+U+01b0:u9
+U+01b5:Z/
+U+01b6:z/
+U+01b7:ED
+U+01cd:A<
+U+01ce:a<
+U+01cf:I<
+U+01d0:i<
+U+01d1:O<
+U+01d2:o<
+U+01d3:U<
+U+01d4:u<
+U+01d5:U:-
+U+01d6:u:-
+U+01d7:U:'
+U+01d8:u:'
+U+01d9:U:<
+U+01da:u:<
+U+01db:U:!
+U+01dc:u:!
+U+01de:A1
+U+01df:a1
+U+01e0:A7
+U+01e1:a7
+U+01e2:A3
+U+01e3:a3
+U+01e4:G/
+U+01e5:g/
+U+01e6:G<
+U+01e7:g<
+U+01e8:K<
+U+01e9:k<
+U+01ea:O;
+U+01eb:o;
+U+01ec:O1
+U+01ed:o1
+U+01ee:EZ
+U+01ef:ez
+U+01f0:j<
+U+01f4:G'
+U+01f5:g'
+U+01fa:AA'
+U+01fb:aa'
+U+01fc:AE'
+U+01fd:ae'
+U+01fe:O/'
+U+01ff:o/'
+U+0200:A!!
+U+0201:a!!
+U+0202:A)
+U+0203:a)
+U+0204:E!!
+U+0205:e!!
+U+0206:E)
+U+0207:e)
+U+0208:I!!
+U+0209:i!!
+U+020a:I)
+U+020b:i)
+U+020c:O!!
+U+020d:o!!
+U+020e:O)
+U+020f:o)
+U+0210:R!!
+U+0211:r!!
+U+0212:R)
+U+0213:r)
+U+0214:U!!
+U+0215:u!!
+U+0216:U)
+U+0217:u)
+U+0292:ed
+U+02bb:;S
+U+02c6:1>
+U+02c7:'<
+U+02c9:1-
+U+02cb:1!
+U+02d8:'(
+U+02d9:'.
+U+02da:'0
+U+02db:';
+U+02dc:1?
+U+02dd:'"
+U+0374:'G
+U+0375:,G
+U+037a:j3
+U+037e:?%
+U+0384:'*
+U+0385:'%
+U+0386:A%
+U+0387:.*
+U+0388:E%
+U+0389:Y%
+U+038a:I%
+U+038c:O%
+U+038e:U%
+U+038f:W%
+U+0390:i3
+U+0391:A*
+U+0392:B*
+U+0393:G*
+U+0394:D*
+U+0395:E*
+U+0396:Z*
+U+0397:Y*
+U+0398:H*
+U+0399:I*
+U+039a:K*
+U+039b:L*
+U+039c:M*
+U+039d:N*
+U+039e:C*
+U+039f:O*
+U+03a0:P*
+U+03a1:R*
+U+03a3:S*
+U+03a4:T*
+U+03a5:U*
+U+03a6:F*
+U+03a7:X*
+U+03a8:Q*
+U+03a9:W*
+U+03aa:J*
+U+03ab:V*
+U+03ac:a%
+U+03ad:e%
+U+03ae:y%
+U+03af:i%
+U+03b0:u3
+U+03b1:a*
+U+03b2:b*
+U+03b3:g*
+U+03b4:d*
+U+03b5:e*
+U+03b6:z*
+U+03b7:y*
+U+03b8:h*
+U+03b9:i*
+U+03ba:k*
+U+03bb:l*
+U+03bc:m*
+U+03bd:n*
+U+03be:c*
+U+03bf:o*
+U+03c0:p*
+U+03c1:r*
+U+03c2:*s
+U+03c3:s*
+U+03c4:t*
+U+03c5:u*
+U+03c6:f*
+U+03c7:x*
+U+03c8:q*
+U+03c9:w*
+U+03ca:j*
+U+03cb:v*
+U+03cc:o%
+U+03cd:u%
+U+03ce:w%
+U+03d0:b3
+U+03da:T3
+U+03db:t3
+U+03dc:M3
+U+03dd:m3
+U+03de:K3
+U+03df:k3
+U+03e0:P3
+U+03e1:p3
+U+0401:IO
+U+0402:D%
+U+0403:G%
+U+0404:IE
+U+0405:DS
+U+0406:II
+U+0407:YI
+U+0408:J%
+U+0409:LJ
+U+040a:NJ
+U+040b:Ts
+U+040c:KJ
+U+040e:V%
+U+040f:DZ
+U+0410:A=
+U+0411:B=
+U+0412:V=
+U+0413:G=
+U+0414:D=
+U+0415:E=
+U+0416:Z%
+U+0417:Z=
+U+0418:I=
+U+0419:J=
+U+041a:K=
+U+041b:L=
+U+041c:M=
+U+041d:N=
+U+041e:O=
+U+041f:P=
+U+0420:R=
+U+0421:S=
+U+0422:T=
+U+0423:U=
+U+0424:F=
+U+0425:H=
+U+0426:C=
+U+0427:C%
+U+0428:S%
+U+0429:Sc
+U+042a:="
+U+042b:Y=
+U+042c:%"
+U+042d:JE
+U+042e:JU
+U+042f:JA
+U+0430:a=
+U+0431:b=
+U+0432:v=
+U+0433:g=
+U+0434:d=
+U+0435:e=
+U+0436:z%
+U+0437:z=
+U+0438:i=
+U+0439:j=
+U+043a:k=
+U+043b:l=
+U+043c:m=
+U+043d:n=
+U+043e:o=
+U+043f:p=
+U+0440:r=
+U+0441:s=
+U+0442:t=
+U+0443:u=
+U+0444:f=
+U+0445:h=
+U+0446:c=
+U+0447:c%
+U+0448:s%
+U+0449:sc
+U+044a:='
+U+044b:y=
+U+044c:%'
+U+044d:je
+U+044e:ju
+U+044f:ja
+U+0451:io
+U+0452:d%
+U+0453:g%
+U+0454:ie
+U+0455:ds
+U+0456:ii
+U+0457:yi
+U+0458:j%
+U+0459:lj
+U+045a:nj
+U+045b:ts
+U+045c:kj
+U+045e:v%
+U+045f:dz
+U+0462:Y3
+U+0463:y3
+U+046a:O3
+U+046b:o3
+U+0472:F3
+U+0473:f3
+U+0474:V3
+U+0475:v3
+U+0480:C3
+U+0481:c3
+U+0490:G3
+U+0491:g3
+U+05d0:A+
+U+05d1:B+
+U+05d2:G+
+U+05d3:D+
+U+05d4:H+
+U+05d5:W+
+U+05d6:Z+
+U+05d7:X+
+U+05d8:Tj
+U+05d9:J+
+U+05da:K%
+U+05db:K+
+U+05dc:L+
+U+05dd:M%
+U+05de:M+
+U+05df:N%
+U+05e0:N+
+U+05e1:S+
+U+05e2:E+
+U+05e3:P%
+U+05e4:P+
+U+05e5:Zj
+U+05e6:ZJ
+U+05e7:Q+
+U+05e8:R+
+U+05e9:Sh
+U+05ea:T+
+U+060c:,+
+U+061b:;+
+U+061f:?+
+U+0621:H'
+U+0622:aM
+U+0623:aH
+U+0624:wH
+U+0625:ah
+U+0626:yH
+U+0627:a+
+U+0628:b+
+U+0629:tm
+U+062a:t+
+U+062b:tk
+U+062c:g+
+U+062d:hk
+U+062e:x+
+U+062f:d+
+U+0630:dk
+U+0631:r+
+U+0632:z+
+U+0633:s+
+U+0634:sn
+U+0635:c+
+U+0636:dd
+U+0637:tj
+U+0638:zH
+U+0639:e+
+U+063a:i+
+U+0640:++
+U+0641:f+
+U+0642:q+
+U+0643:k+
+U+0644:l+
+U+0645:m+
+U+0646:n+
+U+0647:h+
+U+0648:w+
+U+0649:j+
+U+064a:y+
+U+064b::+
+U+064c:"+
+U+064d:=+
+U+064e:/+
+U+064f:'+
+U+0650:1+
+U+0651:3+
+U+0652:0+
+U+0660:0a
+U+0661:1a
+U+0662:2a
+U+0663:3a
+U+0664:4a
+U+0665:5a
+U+0666:6a
+U+0667:7a
+U+0668:8a
+U+0669:9a
+U+0670:aS
+U+067e:p+
+U+0681:hH
+U+0686:tc
+U+0698:zj
+U+06a4:v+
+U+06af:gf
+U+1e00:A-0
+U+1e01:a-0
+U+1e02:B.
+U+1e03:b.
+U+1e04:B-.
+U+1e05:b-.
+U+1e06:B_
+U+1e07:b_
+U+1e08:C,'
+U+1e09:c,'
+U+1e0a:D.
+U+1e0b:d.
+U+1e0c:D-.
+U+1e0d:d-.
+U+1e0e:D_
+U+1e0f:d_
+U+1e10:D,
+U+1e11:d,
+U+1e12:D->
+U+1e13:d->
+U+1e14:E-!
+U+1e15:e-!
+U+1e16:E-'
+U+1e17:e-'
+U+1e18:E->
+U+1e19:e->
+U+1e1a:E-?
+U+1e1b:e-?
+U+1e1c:E,(
+U+1e1d:e,(
+U+1e1e:F.
+U+1e1f:f.
+U+1e20:G-
+U+1e21:g-
+U+1e22:H.
+U+1e23:h.
+U+1e24:H-.
+U+1e25:h-.
+U+1e26:H:
+U+1e27:h:
+U+1e28:H,
+U+1e29:h,
+U+1e2a:H-(
+U+1e2b:h-(
+U+1e2c:I-?
+U+1e2d:i-?
+U+1e2e:I:'
+U+1e2f:i:'
+U+1e30:K'
+U+1e31:k'
+U+1e32:K-.
+U+1e33:k-.
+U+1e34:K_
+U+1e35:k_
+U+1e36:L-.
+U+1e37:l-.
+U+1e38:L--.
+U+1e39:l--.
+U+1e3a:L_
+U+1e3b:l_
+U+1e3c:L->
+U+1e3d:l->
+U+1e3e:M'
+U+1e3f:m'
+U+1e40:M.
+U+1e41:m.
+U+1e42:M-.
+U+1e43:m-.
+U+1e44:N.
+U+1e45:n.
+U+1e46:N-.
+U+1e47:n-.
+U+1e48:N_
+U+1e49:n_
+U+1e4a:N->
+U+1e4b:n->
+U+1e4c:O?'
+U+1e4d:o?'
+U+1e4e:O?:
+U+1e4f:o?:
+U+1e50:O-!
+U+1e51:o-!
+U+1e52:O-'
+U+1e53:o-'
+U+1e54:P'
+U+1e55:p'
+U+1e56:P.
+U+1e57:p.
+U+1e58:R.
+U+1e59:r.
+U+1e5a:R-.
+U+1e5b:r-.
+U+1e5c:R--.
+U+1e5d:r--.
+U+1e5e:R_
+U+1e5f:r_
+U+1e60:S.
+U+1e61:s.
+U+1e62:S-.
+U+1e63:s-.
+U+1e64:S'.
+U+1e65:s'.
+U+1e66:S<.
+U+1e67:s<.
+U+1e68:S.-.
+U+1e69:s.-.
+U+1e6a:T.
+U+1e6b:t.
+U+1e6c:T-.
+U+1e6d:t-.
+U+1e6e:T_
+U+1e6f:t_
+U+1e70:T->
+U+1e71:t->
+U+1e72:U--:
+U+1e73:u--:
+U+1e74:U-?
+U+1e75:u-?
+U+1e76:U->
+U+1e77:u->
+U+1e78:U?'
+U+1e79:u?'
+U+1e7a:U-:
+U+1e7b:u-:
+U+1e7c:V?
+U+1e7d:v?
+U+1e7e:V-.
+U+1e7f:v-.
+U+1e80:W!
+U+1e81:w!
+U+1e82:W'
+U+1e83:w'
+U+1e84:W:
+U+1e85:w:
+U+1e86:W.
+U+1e87:w.
+U+1e88:W-.
+U+1e89:w-.
+U+1e8a:X.
+U+1e8b:x.
+U+1e8c:X:
+U+1e8d:x:
+U+1e8e:Y.
+U+1e8f:y.
+U+1e90:Z>
+U+1e91:z>
+U+1e92:Z-.
+U+1e93:z-.
+U+1e94:Z_
+U+1e95:z_
+U+1e96:h_
+U+1e97:t:
+U+1e98:w0
+U+1e99:y0
+U+1ea0:A-.
+U+1ea1:a-.
+U+1ea2:A2
+U+1ea3:a2
+U+1ea4:A>'
+U+1ea5:a>'
+U+1ea6:A>!
+U+1ea7:a>!
+U+1ea8:A>2
+U+1ea9:a>2
+U+1eaa:A>?
+U+1eab:a>?
+U+1eac:A>-.
+U+1ead:a>-.
+U+1eae:A('
+U+1eaf:a('
+U+1eb0:A(!
+U+1eb1:a(!
+U+1eb2:A(2
+U+1eb3:a(2
+U+1eb4:A(?
+U+1eb5:a(?
+U+1eb6:A(-.
+U+1eb7:a(-.
+U+1eb8:E-.
+U+1eb9:e-.
+U+1eba:E2
+U+1ebb:e2
+U+1ebc:E?
+U+1ebd:e?
+U+1ebe:E>'
+U+1ebf:e>'
+U+1ec0:E>!
+U+1ec1:e>!
+U+1ec2:E>2
+U+1ec3:e>2
+U+1ec4:E>?
+U+1ec5:e>?
+U+1ec6:E>-.
+U+1ec7:e>-.
+U+1ec8:I2
+U+1ec9:i2
+U+1eca:I-.
+U+1ecb:i-.
+U+1ecc:O-.
+U+1ecd:o-.
+U+1ece:O2
+U+1ecf:o2
+U+1ed0:O>'
+U+1ed1:o>'
+U+1ed2:O>!
+U+1ed3:o>!
+U+1ed4:O>2
+U+1ed5:o>2
+U+1ed6:O>?
+U+1ed7:o>?
+U+1ed8:O>-.
+U+1ed9:o>-.
+U+1eda:O9'
+U+1edb:o9'
+U+1edc:O9!
+U+1edd:o9!
+U+1ede:O92
+U+1edf:o92
+U+1ee0:O9?
+U+1ee1:o9?
+U+1ee2:O9-.
+U+1ee3:o9-.
+U+1ee4:U-.
+U+1ee5:u-.
+U+1ee6:U2
+U+1ee7:u2
+U+1ee8:U9'
+U+1ee9:u9'
+U+1eea:U9!
+U+1eeb:u9!
+U+1eec:U92
+U+1eed:u92
+U+1eee:U9?
+U+1eef:u9?
+U+1ef0:U9-.
+U+1ef1:u9-.
+U+1ef2:Y!
+U+1ef3:y!
+U+1ef4:Y-.
+U+1ef5:y-.
+U+1ef6:Y2
+U+1ef7:y2
+U+1ef8:Y?
+U+1ef9:y?
+U+1fbf:,,
+U+1fc0:?*
+U+1fc1:?:
+U+1fcd:,!
+U+1fce:,'
+U+1fcf:?,
+U+1fdd:;!
+U+1fde:;'
+U+1fdf:?;
+U+1fed:!:
+U+1fef:!*
+U+1ffe:;;
+U+2002:1N
+U+2003:1M
+U+2004:3M
+U+2005:4M
+U+2006:6M
+U+200e:LR
+U+200f:RL
+U+2009:1T
+U+200a:1H
+U+2010:-1
+U+2013:-N
+U+2014:-M
+U+2015:-3
+U+2016:!2
+U+2017:=2
+U+2018:'6
+U+2019:'9
+U+201a:.9
+U+201b:9'
+U+201c:"6
+U+201d:"9
+U+201e::9
+U+201f:9"
+U+2020:/-
+U+2021:/=
+U+2022:Sb
+U+2025:..
+U+2026:.3
+U+2030:%0
+U+2032:1'
+U+2033:2'
+U+2034:3'
+U+2035:1"
+U+2036:2"
+U+2037:3"
+U+2038:Ca
+U+2039:<1
+U+203a:>1
+U+203b::X
+U+203c:!*2
+U+203e:'-
+U+2044:/f
+U+2070:0S
+U+2074:4S
+U+2075:5S
+U+2076:6S
+U+2077:7S
+U+2078:8S
+U+2079:9S
+U+207a:+S
+U+207b:-S
+U+207c:=S
+U+207d:(S
+U+207e:)S
+U+207f:nS
+U+2080:0s
+U+2081:1s
+U+2082:2s
+U+2083:3s
+U+2084:4s
+U+2085:5s
+U+2086:6s
+U+2087:7s
+U+2088:8s
+U+2089:9s
+U+208a:+s
+U+208b:-s
+U+208c:=s
+U+208d:(s
+U+208e:)s
+U+20a3:Ff
+U+20a4:Li
+U+20a7:Pt
+U+20a9:W=
+U+2103:oC
+U+2105:co
+U+2109:oF
+U+2116:N0
+U+2117:PO
+U+211e:Rx
+U+2120:SM
+U+2122:TM
+U+2126:Om
+U+212b:AO
+U+2153:13
+U+2154:23
+U+2155:15
+U+2156:25
+U+2157:35
+U+2158:45
+U+2159:16
+U+215a:56
+U+215b:18
+U+215c:38
+U+215d:58
+U+215e:78
+U+2160:1R
+U+2161:2R
+U+2162:3R
+U+2163:4R
+U+2164:5R
+U+2165:6R
+U+2166:7R
+U+2167:8R
+U+2168:9R
+U+2169:aR
+U+216a:bR
+U+216b:cR
+U+216c:50R
+U+216d:100R
+U+216e:500R
+U+216f:1000R
+U+2170:1r
+U+2171:2r
+U+2172:3r
+U+2173:4r
+U+2174:5r
+U+2175:6r
+U+2176:7r
+U+2177:8r
+U+2178:9r
+U+2179:ar
+U+217a:br
+U+217b:cr
+U+217c:50r
+U+217d:100r
+U+217e:500r
+U+217f:1000r
+U+2180:1000RCD
+U+2181:5000R
+U+2182:10000R
+U+2190:<-
+U+2191:-!
+U+2192:->
+U+2193:-v
+U+2194:<>
+U+2195:UD
+U+2196:<!!
+U+2197://>
+U+2198:!!>
+U+2199:<//
+U+21a8:UD-
+U+21c0:>V
+U+21d0:<=
+U+21d2:=>
+U+21d4:==
+U+2200:FA
+U+2202:dP
+U+2203:TE
+U+2205:/0
+U+2206:DE
+U+2207:NB
+U+2208:(-
+U+220b:-)
+U+220f:*P
+U+2211:+Z
+U+2212:-2
+U+2213:-+
+U+2214:.+
+U+2217:*-
+U+2218:Ob
+U+2219:sb
+U+221a:RT
+U+221d:0(
+U+221e:00
+U+221f:-L
+U+2220:-V
+U+2225:PP
+U+2227:AN
+U+2228:OR
+U+2229:(U
+U+222a:)U
+U+222b:In
+U+222c:DI
+U+222e:Io
+U+2234:.:
+U+2235::.
+U+2236::R
+U+2237:::
+U+223c:?1
+U+223e:CG
+U+2243:?-
+U+2245:?=
+U+2248:?2
+U+224c:=?
+U+2253:HI
+U+2260:!=
+U+2261:=3
+U+2264:=<
+U+2265:>=
+U+226a:<*
+U+226b:*>
+U+226e:!<
+U+226f:!>
+U+2282:(C
+U+2283:)C
+U+2286:(_
+U+2287:)_
+U+2299:0.
+U+229a:02
+U+22a5:-T
+U+22c5:.P
+U+22ee::3
+U+2302:Eh
+U+2308:<7
+U+2309:>7
+U+230a:7<
+U+230b:7>
+U+2310:NI
+U+2312:(A
+U+2315:TR
+U+2318:88
+U+2320:Iu
+U+2321:Il
+U+2329:</
+U+232a:/>
+U+2423:Vs
+U+2440:1h
+U+2441:3h
+U+2442:2h
+U+2443:4h
+U+2446:1j
+U+2447:2j
+U+2448:3j
+U+2449:4j
+U+2460:1-o
+U+2461:2-o
+U+2462:3-o
+U+2463:4-o
+U+2464:5-o
+U+2465:6-o
+U+2466:7-o
+U+2467:8-o
+U+2468:9-o
+U+2469:10-o
+U+246a:11-o
+U+246b:12-o
+U+246c:13-o
+U+246d:14-o
+U+246e:15-o
+U+246f:16-o
+U+2470:17-o
+U+2471:18-o
+U+2472:19-o
+U+2473:20-o
+U+2474:(1)
+U+2475:(2)
+U+2476:(3)
+U+2477:(4)
+U+2478:(5)
+U+2479:(6)
+U+247a:(7)
+U+247b:(8)
+U+247c:(9)
+U+247d:(10)
+U+247e:(11)
+U+247f:(12)
+U+2480:(13)
+U+2481:(14)
+U+2482:(15)
+U+2483:(16)
+U+2484:(17)
+U+2485:(18)
+U+2486:(19)
+U+2487:(20)
+U+2488:1.
+U+2489:2.
+U+248a:3.
+U+248b:4.
+U+248c:5.
+U+248d:6.
+U+248e:7.
+U+248f:8.
+U+2490:9.
+U+2491:10.
+U+2492:11.
+U+2493:12.
+U+2494:13.
+U+2495:14.
+U+2496:15.
+U+2497:16.
+U+2498:17.
+U+2499:18.
+U+249a:19.
+U+249b:20.
+U+249c:(a)
+U+249d:(b)
+U+249e:(c)
+U+249f:(d)
+U+24a0:(e)
+U+24a1:(f)
+U+24a2:(g)
+U+24a3:(h)
+U+24a4:(i)
+U+24a5:(j)
+U+24a6:(k)
+U+24a7:(l)
+U+24a8:(m)
+U+24a9:(n)
+U+24aa:(o)
+U+24ab:(p)
+U+24ac:(q)
+U+24ad:(r)
+U+24ae:(s)
+U+24af:(t)
+U+24b0:(u)
+U+24b1:(v)
+U+24b2:(w)
+U+24b3:(x)
+U+24b4:(y)
+U+24b5:(z)
+U+24b6:A-o
+U+24b7:B-o
+U+24b8:C-o
+U+24b9:D-o
+U+24ba:E-o
+U+24bb:F-o
+U+24bc:G-o
+U+24bd:H-o
+U+24be:I-o
+U+24bf:J-o
+U+24c0:K-o
+U+24c1:L-o
+U+24c2:M-o
+U+24c3:N-o
+U+24c4:O-o
+U+24c5:P-o
+U+24c6:Q-o
+U+24c7:R-o
+U+24c8:S-o
+U+24c9:T-o
+U+24ca:U-o
+U+24cb:V-o
+U+24cc:W-o
+U+24cd:X-o
+U+24ce:Y-o
+U+24cf:Z-o
+U+24d0:a-o
+U+24d1:b-o
+U+24d2:c-o
+U+24d3:d-o
+U+24d4:e-o
+U+24d5:f-o
+U+24d6:g-o
+U+24d7:h-o
+U+24d8:i-o
+U+24d9:j-o
+U+24da:k-o
+U+24db:l-o
+U+24dc:m-o
+U+24dd:n-o
+U+24de:o-o
+U+24df:p-o
+U+24e0:q-o
+U+24e1:r-o
+U+24e2:s-o
+U+24e3:t-o
+U+24e4:u-o
+U+24e5:v-o
+U+24e6:w-o
+U+24e7:x-o
+U+24e8:y-o
+U+24e9:z-o
+U+24ea:0-o
+U+2500:hh
+U+2501:HH-
+U+2502:vv
+U+2503:VV-
+U+2504:3-
+U+2505:3_
+U+2506:3!
+U+2507:3/
+U+2508:4-
+U+2509:4_
+U+250a:4!
+U+250b:4/
+U+250c:dr
+U+250d:dR-
+U+250e:Dr-
+U+250f:DR-
+U+2510:dl
+U+2511:dL-
+U+2512:Dl-
+U+2513:LD-
+U+2514:ur
+U+2515:uR-
+U+2516:Ur-
+U+2517:UR-
+U+2518:ul
+U+2519:uL-
+U+251a:Ul-
+U+251b:UL-
+U+251c:vr
+U+251d:vR-
+U+251e:Udr
+U+251f:uDr
+U+2520:Vr-
+U+2521:UdR
+U+2522:uDR
+U+2523:VR-
+U+2524:vl
+U+2525:vL-
+U+2526:Udl
+U+2527:uDl
+U+2528:Vl-
+U+2529:UdL
+U+252a:uDL
+U+252b:VL-
+U+252c:dh
+U+252d:dLr
+U+252e:dlR
+U+252f:dH-
+U+2530:Dh-
+U+2531:DLr
+U+2532:DlR
+U+2533:DH-
+U+2534:uh
+U+2535:uLr
+U+2536:ulR
+U+2537:uH-
+U+2538:Uh-
+U+2539:ULr
+U+253a:UlR
+U+253b:UH-
+U+253c:vh
+U+253d:vLr
+U+253e:vlR
+U+253f:vH-
+U+2540:Udh
+U+2541:uDh
+U+2542:Vh-
+U+2543:UdLr
+U+2544:UdlR
+U+2545:uDLr
+U+2546:uDlR
+U+2547:UdH
+U+2548:uDH
+U+2549:VLr
+U+254a:VlR
+U+254b:VH-
+U+2550:HH
+U+2551:VV
+U+2552:dR
+U+2553:Dr
+U+2554:DR
+U+2555:dL
+U+2556:Dl
+U+2557:LD
+U+2558:uR
+U+2559:Ur
+U+255a:UR
+U+255b:uL
+U+255c:Ul
+U+255d:UL
+U+255e:vR
+U+255f:Vr
+U+2560:VR
+U+2561:vL
+U+2562:Vl
+U+2563:VL
+U+2564:dH
+U+2565:Dh
+U+2566:DH
+U+2567:uH
+U+2568:Uh
+U+2569:UH
+U+256a:vH
+U+256b:Vh
+U+256c:VH
+U+2571:FD
+U+2572:BD
+U+2580:TB
+U+2584:LB
+U+2588:FB
+U+258c:lB
+U+2590:RB
+U+2591:.S
+U+2592::S
+U+2593:?S
+U+25a0:fS
+U+25a1:OS
+U+25a2:RO
+U+25a3:Rr
+U+25a4:RF
+U+25a5:RY
+U+25a6:RH
+U+25a7:RZ
+U+25a8:RK
+U+25a9:RX
+U+25aa:sB
+U+25ac:SR
+U+25ad:Or
+U+25b2:UT
+U+25b3:uT
+U+25b7:Tr
+U+25ba:PR
+U+25bc:Dt
+U+25bd:dT
+U+25c1:Tl
+U+25c4:PL
+U+25c6:Db
+U+25c7:Dw
+U+25ca:LZ
+U+25cb:0m
+U+25ce:0o
+U+25cf:0M
+U+25d0:0L
+U+25d1:0R
+U+25d8:Sn
+U+25d9:Ic
+U+25e2:Fd
+U+25e3:Bd
+U+25ef:Ci
+U+2605:*2
+U+2606:*1
+U+260e:TEL
+U+260f:tel
+U+261c:<H
+U+261e:>H
+U+263a:0u
+U+263b:0U
+U+263c:SU
+U+2640:Fm
+U+2642:Ml
+U+2660:cS
+U+2661:cH
+U+2662:cD
+U+2663:cC
+U+2664:cS-
+U+2665:cH-
+U+2666:cD-
+U+2667:cC-
+U+2669:Md
+U+266a:M8
+U+266b:M2
+U+266c:M16
+U+266d:Mb
+U+266e:Mx
+U+266f:MX
+U+2713:OK
+U+2717:XX
+U+2720:-X
+U+3000:IS
+U+3001:,_
+U+3002:._
+U+3003:+"
+U+3004:JIS
+U+3005:*_
+U+3006:;_
+U+3007:0_
+U+300a:<+
+U+300b:>+
+U+300c:<'
+U+300d:>'
+U+300e:<"
+U+300f:>"
+U+3010:("
+U+3011:)"
+U+3012:=T
+U+3013:=_
+U+3014:('
+U+3015:)'
+U+3016:(I
+U+3017:)I
+U+301c:-?
+U+3020:=T:)
+U+3041:A5
+U+3042:a5
+U+3043:I5
+U+3044:i5
+U+3045:U5
+U+3046:u5
+U+3047:E5
+U+3048:e5
+U+3049:O5
+U+304a:o5
+U+304b:ka
+U+304c:ga
+U+304d:ki
+U+304e:gi
+U+304f:ku
+U+3050:gu
+U+3051:ke
+U+3052:ge
+U+3053:ko
+U+3054:go
+U+3055:sa
+U+3056:za
+U+3057:si
+U+3058:zi
+U+3059:su
+U+305a:zu
+U+305b:se
+U+305c:ze
+U+305d:so
+U+305e:zo
+U+305f:ta
+U+3060:da
+U+3061:ti
+U+3062:di
+U+3063:tU
+U+3064:tu
+U+3065:du
+U+3066:te
+U+3067:de
+U+3068:to
+U+3069:do
+U+306a:na
+U+306b:ni
+U+306c:nu
+U+306d:ne
+U+306e:no
+U+306f:ha
+U+3070:ba
+U+3071:pa
+U+3072:hi
+U+3073:bi
+U+3074:pi
+U+3075:hu
+U+3076:bu
+U+3077:pu
+U+3078:he
+U+3079:be
+U+307a:pe
+U+307b:ho
+U+307c:bo
+U+307d:po
+U+307e:ma
+U+307f:mi
+U+3080:mu
+U+3081:me
+U+3082:mo
+U+3083:yA
+U+3084:ya
+U+3085:yU
+U+3086:yu
+U+3087:yO
+U+3088:yo
+U+3089:ra
+U+308a:ri
+U+308b:ru
+U+308c:re
+U+308d:ro
+U+308e:wA
+U+308f:wa
+U+3090:wi
+U+3091:we
+U+3092:wo
+U+3093:n5
+U+3094:vu
+U+309b:"5
+U+309c:05
+U+309d:*5
+U+309e:+5
+U+30a1:a6
+U+30a2:A6
+U+30a3:i6
+U+30a4:I6
+U+30a5:u6
+U+30a6:U6
+U+30a7:e6
+U+30a8:E6
+U+30a9:o6
+U+30aa:O6
+U+30ab:Ka
+U+30ac:Ga
+U+30ad:Ki
+U+30ae:Gi
+U+30af:Ku
+U+30b0:Gu
+U+30b1:Ke
+U+30b2:Ge
+U+30b3:Ko
+U+30b4:Go
+U+30b5:Sa
+U+30b6:Za
+U+30b7:Si
+U+30b8:Zi
+U+30b9:Su
+U+30ba:Zu
+U+30bb:Se
+U+30bc:Ze
+U+30bd:So
+U+30be:Zo
+U+30bf:Ta
+U+30c0:Da
+U+30c1:Ti
+U+30c2:Di
+U+30c3:TU
+U+30c4:Tu
+U+30c5:Du
+U+30c6:Te
+U+30c7:De
+U+30c8:To
+U+30c9:Do
+U+30ca:Na
+U+30cb:Ni
+U+30cc:Nu
+U+30cd:Ne
+U+30ce:No
+U+30cf:Ha
+U+30d0:Ba
+U+30d1:Pa
+U+30d2:Hi
+U+30d3:Bi
+U+30d4:Pi
+U+30d5:Hu
+U+30d6:Bu
+U+30d7:Pu
+U+30d8:He
+U+30d9:Be
+U+30da:Pe
+U+30db:Ho
+U+30dc:Bo
+U+30dd:Po
+U+30de:Ma
+U+30df:Mi
+U+30e0:Mu
+U+30e1:Me
+U+30e2:Mo
+U+30e3:YA
+U+30e4:Ya
+U+30e5:YU
+U+30e6:Yu
+U+30e7:YO
+U+30e8:Yo
+U+30e9:Ra
+U+30ea:Ri
+U+30eb:Ru
+U+30ec:Re
+U+30ed:Ro
+U+30ee:WA
+U+30ef:Wa
+U+30f0:Wi
+U+30f1:We
+U+30f2:Wo
+U+30f3:N6
+U+30f4:Vu
+U+30f5:KA
+U+30f6:KE
+U+30f7:Va
+U+30f8:Vi
+U+30f9:Ve
+U+30fa:Vo
+U+30fb:.6
+U+30fc:-6
+U+30fd:*6
+U+30fe:+6
+U+3105:b4
+U+3106:p4
+U+3107:m4
+U+3108:f4
+U+3109:d4
+U+310a:t4
+U+310b:n4
+U+310c:l4
+U+310d:g4
+U+310e:k4
+U+310f:h4
+U+3110:j4
+U+3111:q4
+U+3112:x4
+U+3113:zh
+U+3114:ch
+U+3115:sh
+U+3116:r4
+U+3117:z4
+U+3118:c4
+U+3119:s4
+U+311a:a4
+U+311b:o4
+U+311c:e4
+U+311d:eh4
+U+311e:ai
+U+311f:ei
+U+3120:au
+U+3121:ou
+U+3122:an
+U+3123:en
+U+3124:aN
+U+3125:eN
+U+3126:er
+U+3127:i4
+U+3128:u4
+U+3129:iu
+U+312a:v4
+U+312b:nG
+U+312c:gn
+U+321c:(JU)
+U+3220:1c
+U+3221:2c
+U+3222:3c
+U+3223:4c
+U+3224:5c
+U+3225:6c
+U+3226:7c
+U+3227:8c
+U+3228:9c
+U+3229:10c
+U+327f:KSC
+U+33c2:am
+U+33d8:pm
+U+fb00:ff
+U+fb01:fi
+U+fb02:fl
+U+fb03:ffi
+U+fb04:ffl
+U+fb05:St
+U+fb06:st
+U+fe7d:3+;
+U+fe82:aM.
+U+fe84:aH.
+U+fe88:ah.
+U+fe8d:a+-
+U+fe8e:a+.
+U+fe8f:b+-
+U+fe90:b+.
+U+fe91:b+,
+U+fe92:b+;
+U+fe93:tm-
+U+fe94:tm.
+U+fe95:t+-
+U+fe96:t+.
+U+fe97:t+,
+U+fe98:t+;
+U+fe99:tk-
+U+fe9a:tk.
+U+fe9b:tk,
+U+fe9c:tk;
+U+fe9d:g+-
+U+fe9e:g+.
+U+fe9f:g+,
+U+fea0:g+;
+U+fea1:hk-
+U+fea2:hk.
+U+fea3:hk,
+U+fea4:hk;
+U+fea5:x+-
+U+fea6:x+.
+U+fea7:x+,
+U+fea8:x+;
+U+fea9:d+-
+U+feaa:d+.
+U+feab:dk-
+U+feac:dk.
+U+fead:r+-
+U+feae:r+.
+U+feaf:z+-
+U+feb0:z+.
+U+feb1:s+-
+U+feb2:s+.
+U+feb3:s+,
+U+feb4:s+;
+U+feb5:sn-
+U+feb6:sn.
+U+feb7:sn,
+U+feb8:sn;
+U+feb9:c+-
+U+feba:c+.
+U+febb:c+,
+U+febc:c+;
+U+febd:dd-
+U+febe:dd.
+U+febf:dd,
+U+fec0:dd;
+U+fec1:tj-
+U+fec2:tj.
+U+fec3:tj,
+U+fec4:tj;
+U+fec5:zH-
+U+fec6:zH.
+U+fec7:zH,
+U+fec8:zH;
+U+fec9:e+-
+U+feca:e+.
+U+fecb:e+,
+U+fecc:e+;
+U+fecd:i+-
+U+fece:i+.
+U+fecf:i+,
+U+fed0:i+;
+U+fed1:f+-
+U+fed2:f+.
+U+fed3:f+,
+U+fed4:f+;
+U+fed5:q+-
+U+fed6:q+.
+U+fed7:q+,
+U+fed8:q+;
+U+fed9:k+-
+U+feda:k+.
+U+fedb:k+,
+U+fedc:k+;
+U+fedd:l+-
+U+fede:l+.
+U+fedf:l+,
+U+fee0:l+;
+U+fee1:m+-
+U+fee2:m+.
+U+fee3:m+,
+U+fee4:m+;
+U+fee5:n+-
+U+fee6:n+.
+U+fee7:n+,
+U+fee8:n+;
+U+fee9:h+-
+U+feea:h+.
+U+feeb:h+,
+U+feec:h+;
+U+feed:w+-
+U+feee:w+.
+U+feef:j+-
+U+fef0:j+.
+U+fef1:y+-
+U+fef2:y+.
+U+fef3:y+,
+U+fef4:y+;
+U+fef5:lM-
+U+fef6:lM.
+U+fef7:lH-
+U+fef8:lH.
+U+fef9:lh-
+U+fefa:lh.
+U+fefb:la-
+U+fefc:la.
+U+0000:NU
+U+0001:SH
+U+0002:SX
+U+0003:EX
+U+0004:ET
+U+0005:EQ
+U+0006:AK
+U+0007:BL
+U+0008:BS
+U+0009:HT
+U+000a:LF
+U+000b:VT
+U+000c:FF
+U+000d:CR
+U+000e:SO
+U+000f:SI
+U+0010:DL
+U+0011:D1
+U+0012:D2
+U+0013:D3
+U+0014:D4
+U+0015:NK
+U+0016:SY
+U+0017:EB
+U+0018:CN
+U+0019:EM
+U+001a:SB
+U+001b:EC
+U+001c:FS
+U+001d:GS
+U+001e:RS
+U+001f:US
+U+007f:DT
+U+0080:PA
+U+0081:HO
+U+0082:BH
+U+0083:NH
+U+0084:IN
+U+0085:NL
+U+0086:SA
+U+0087:ES
+U+0088:HS
+U+0089:HJ
+U+008a:VS
+U+008b:PD
+U+008c:PU
+U+008d:RI
+U+008e:S2
+U+008f:S3
+U+0090:DC
+U+0091:P1
+U+0092:P2
+U+0093:TS
+U+0094:CC
+U+0095:MW
+U+0096:SG
+U+0097:EG
+U+0098:SS
+U+0099:GC
+U+009a:SC
+U+009b:CI
+U+009c:ST
+U+009d:OC
+U+009e:PM
+U+009f:AC
+# Characters in Private Use Area (e000-f8ff) do not have ussigned numbers
+# according Unicode 2.0
diff --git a/src/chrtrans/rot13_kb.h b/src/chrtrans/rot13_kb.h
new file mode 100644
index 0000000..1df5f32
--- /dev/null
+++ b/src/chrtrans/rot13_kb.h
@@ -0,0 +1,22 @@
+static LYKbLayout_t kb_layout_rot13[128] =
+{
+ 0x0000, 0x0001, 0x0002, 0x0003, 0x0004, 0x0005, 0x0006, 0x0007, /* 00..07 */
+ 0x0008, 0x0009, 0x000A, 0x000B, 0x000C, 0x000D, 0x000E, 0x000F, /* 08..0F */
+ 0x0010, 0x0011, 0x0012, 0x0013, 0x0014, 0x0015, 0x0016, 0x0017, /* 10..17 */
+ 0x0018, 0x0019, 0x001A, 0x001B, 0x001C, 0x001D, 0x001E, 0x001F, /* 18..1F */
+
+ 0x0020, 0x0021, 0x0022, 0x0023, 0x0024, 0x0025, 0x0026, 0x0027, /* 20..27 */
+ 0x0028, 0x0029, 0x002A, 0x002B, 0x002C, 0x002D, 0x002E, 0x002F, /* 28..2F */
+ 0x0030, 0x0031, 0x0032, 0x0033, 0x0034, 0x0035, 0x0036, 0x0037, /* 30..37 */
+ 0x0038, 0x0039, 0x003A, 0x003B, 0x003C, 0x003D, 0x003E, 0x003F, /* 38..3F */
+
+ 0x0000, 0x004e, 0x004f, 0x0050, 0x0051, 0x0052, 0x0053, 0x0054, /* 40..48 */
+ 0x0055, 0x0056, 0x0057, 0x0058, 0x0059, 0x005a, 0x0041, 0x0042, /* 40..4F */
+ 0x0043, 0x0044, 0x0045, 0x0046, 0x0047, 0x0048, 0x0049, 0x004a, /* 50..58 */
+ 0x004b, 0x004c, 0x004d, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, /* 50..5F */
+
+ 0x0000, 0x006e, 0x006f, 0x0070, 0x0071, 0x0072, 0x0073, 0x0074, /* 60..68 */
+ 0x0075, 0x0076, 0x0077, 0x0078, 0x0079, 0x007a, 0x0061, 0x0062, /* 60..6F */
+ 0x0063, 0x0064, 0x0065, 0x0066, 0x0067, 0x0068, 0x0069, 0x006a, /* 70..78 */
+ 0x006b, 0x006c, 0x006d, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000 /* 70..7F */
+};
diff --git a/src/chrtrans/utf8_uni.tbl b/src/chrtrans/utf8_uni.tbl
new file mode 100644
index 0000000..88ad492
--- /dev/null
+++ b/src/chrtrans/utf8_uni.tbl
@@ -0,0 +1,35 @@
+#
+# This one is not really much of a "translation table", it mostly just
+# tells Lynx that "utf-8" is Unicode/UCS2 encoded in UTF8. Note that
+# "unicode-1-1-utf-8" and "utf8" are treated as synonyms.
+#
+#The MIME name of this charset.
+Mutf-8
+
+#Name as a Display Charset (used on Options screen)
+OUNICODE (UTF-8)
+
+# Some kind of raw Unicode?
+# Use 6 for for really "raw" 16bit UCS-2, 7 for UTF-8, ...
+
+# most of these codes currently don't make much sense in a *.tbl file,
+# but for completeness (from UCDefs.h):
+# #define UCT_ENC_7BIT 0
+# #define UCT_ENC_8BIT 1
+# #define UCT_ENC_8859 2
+# #define UCT_ENC_8BIT_C0 3
+# #define UCT_ENC_MAYBE2022 4
+# #define UCT_ENC_CJK 5
+# #define UCT_ENC_16BIT 6
+# #define UCT_ENC_UTF8 7
+
+R 7
+
+#Shall this become the "default" translation?
+#There has to be exactly one table marked as "default".
+Default NO
+
+# Don't fall back to default table for unicode -> 8bit
+Fallback NO
+
+0x20-0x7f idem
diff --git a/src/chrtrans/viscii_uni.tbl b/src/chrtrans/viscii_uni.tbl
new file mode 100644
index 0000000..617f1e1
--- /dev/null
+++ b/src/chrtrans/viscii_uni.tbl
@@ -0,0 +1,300 @@
+#
+# Unicode mapping table for VISCII 1.1 fonts and charset=viscii,
+# described in RFC 1456.
+# See also <URL:http://www.trichlor.org/vietstd/report/rep92.htm>,
+# also for testing.
+# The 6 characters encoded in the C0 control region should not
+# be passed through to the terminal but be mapped to VIQR strings.
+# THe two changed mappings of MacVISCII are recognized in documents.
+
+# [convert with makeuctb]
+#
+#The MIME name of this charset.
+Mviscii
+
+#Name as a Display Charset (used on Options screen).
+OVietnamese (VISCII)
+
+# Special 'enc' flag to signal that some C0 characters are used.
+# Tables with R3 should properly map the allowed C0 control chars!
+#
+# most of these codes currently don't make much sense in a *.tbl file,
+# but for completeness (from UCDefs.h):
+# #define UCT_ENC_7BIT 0
+# #define UCT_ENC_8BIT 1
+# #define UCT_ENC_8859 2
+# #define UCT_ENC_8BIT_C0 3
+# #define UCT_ENC_MAYBE2022 4
+# #define UCT_ENC_CJK 5
+# #define UCT_ENC_16BIT 6
+# #define UCT_ENC_UTF8 7
+
+R 3
+
+#0x00 U+0000
+#0x01 U+0001
+#0x03 U+0003
+#0x04 U+0004
+#0x07 U+0007
+#0x08 U+0008
+0x09 U+0009
+0x0a U+000a
+#0x0b U+000b
+0x0c U+000c
+U+000c " "
+0x0d U+000d
+#0x0e U+000e
+#0x0f U+000f
+#0x10 U+0010
+#0x11 U+0011
+#0x12 U+0012
+#0x13 U+0013
+#0x15 U+0015
+#0x16 U+0016
+#0x17 U+0017
+#0x18 U+0018
+0x1a U+001a
+U+001a:^Z
+#0x1b U+001b
+#0x1c U+001c
+#0x1d U+001d
+#0x1f U+001f
+#0x20 U+0020
+#0x21 U+0021
+#0x22 U+0022
+#0x23 U+0023
+#0x24 U+0024
+#0x25 U+0025
+#0x26 U+0026
+#0x27 U+0027
+#0x28 U+0028
+#0x29 U+0029
+#0x2a U+002a
+#0x2b U+002b
+#0x2c U+002c
+#0x2d U+002d
+#0x2e U+002e
+#0x2f U+002f
+#0x30 U+0030
+#0x31 U+0031
+#0x32 U+0032
+#0x33 U+0033
+#0x34 U+0034
+#0x35 U+0035
+#0x36 U+0036
+#0x37 U+0037
+#0x38 U+0038
+#0x39 U+0039
+#0x3a U+003a
+#0x3b U+003b
+#0x3c U+003c
+#0x3d U+003d
+#0x3e U+003e
+#0x3f U+003f
+#0x40 U+0040
+#0x41 U+0041
+#0x42 U+0042
+#0x43 U+0043
+#0x44 U+0044
+#0x45 U+0045
+#0x46 U+0046
+#0x47 U+0047
+#0x48 U+0048
+#0x49 U+0049
+#0x4a U+004a
+#0x4b U+004b
+#0x4c U+004c
+#0x4d U+004d
+#0x4e U+004e
+#0x4f U+004f
+#0x50 U+0050
+#0x51 U+0051
+#0x52 U+0052
+#0x53 U+0053
+#0x54 U+0054
+#0x55 U+0055
+#0x56 U+0056
+#0x57 U+0057
+#0x58 U+0058
+#0x59 U+0059
+#0x5a U+005a
+#0x5b U+005b
+#0x5c U+005c
+#0x5d U+005d
+#0x5e U+005e
+#0x5f U+005f
+#0x60 U+0060
+#0x61 U+0061
+#0x62 U+0062
+#0x63 U+0063
+#0x64 U+0064
+#0x65 U+0065
+#0x66 U+0066
+#0x67 U+0067
+#0x68 U+0068
+#0x69 U+0069
+#0x6a U+006a
+#0x6b U+006b
+#0x6c U+006c
+#0x6d U+006d
+#0x6e U+006e
+#0x6f U+006f
+#0x70 U+0070
+#0x71 U+0071
+#0x72 U+0072
+#0x73 U+0073
+#0x74 U+0074
+#0x75 U+0075
+#0x76 U+0076
+#0x77 U+0077
+#0x78 U+0078
+#0x79 U+0079
+#0x7a U+007a
+#0x7b U+007b
+#0x7c U+007c
+#0x7d U+007d
+#0x7e U+007e
+#0x7f U+007f
+0xc0 U+00c0
+0xc1 U+00c1
+0xc2 U+00c2
+0xc3 U+00c3
+0xc8 U+00c8
+0xc9 U+00c9
+0xca U+00ca
+0xcc U+00cc
+0xcd U+00cd
+0xd2 U+00d2
+0xd3 U+00d3
+0xd4 U+00d4
+0xa0 U+00d5
+0xd9 U+00d9
+0xda U+00da
+0xdd U+00dd
+0xe0 U+00e0
+0xe1 U+00e1
+0xe2 U+00e2
+0xe3 U+00e3
+0xe8 U+00e8
+0xe9 U+00e9
+0xea U+00ea
+0xec U+00ec
+0xed U+00ed
+0xf2 U+00f2
+0xf3 U+00f3
+0xf4 U+00f4
+0xf5 U+00f5
+0xf9 U+00f9
+0xfa U+00fa
+0xfd U+00fd
+0xc5 U+0102
+0xe5 U+0103
+0xd0 U+0110
+0xf0 U+0111 U+00f0 # "edh" is similar enough to map it here
+0xce U+0128
+0xee U+0129
+0x9d U+0168
+0xfb U+0169
+0xb4 U+01a0
+0xbd U+01a1
+0xbf U+01af
+0xdf U+01b0
+0x80 U+1ea0
+0xd5 U+1ea1
+0xc4 U+1ea2
+0xe4 U+1ea3
+0x84 U+1ea4
+0xa4 U+1ea5
+0x85 U+1ea6
+0xa5 U+1ea7
+0x86 U+1ea8
+0xa6 U+1ea9
+0x06 U+1eaa
+U+1eaa "\302~" # A with circumflex (same code as in iso-8859-1) and tilde
+0xe7 U+1eab
+0x87 U+1eac
+0xa7 U+1ead
+0x81 U+1eae
+0xa1 U+1eaf
+0x82 U+1eb0
+0xa2 U+1eb1
+0x02 U+1eb2
+U+1eb2:A(?
+0xc6 U+1eb3
+0x05 U+1eb4
+U+1eb4:A(~
+0xc7 U+1eb5
+0x83 U+1eb6
+0xa3 U+1eb7
+0x89 U+1eb8
+0xa9 U+1eb9
+0xcb U+1eba
+0xeb U+1ebb
+0x88 U+1ebc
+0xa8 U+1ebd
+0x8a U+1ebe
+0xaa U+1ebf
+0x8b U+1ec0
+0xab U+1ec1
+0x8c U+1ec2
+0xac U+1ec3
+0x8d U+1ec4
+0xad U+1ec5
+0x8e U+1ec6
+0xae U+1ec7
+0x9b U+1ec8
+0xef U+1ec9
+0x98 U+1eca
+0xb8 U+1ecb
+0x9a U+1ecc
+0xf7 U+1ecd
+0x99 U+1ece
+0xf6 U+1ecf
+0x8f U+1ed0
+0xaf U+1ed1
+0x90 U+1ed2
+0xb0 U+1ed3
+0x91 U+1ed4
+0xb1 U+1ed5
+0x92 U+1ed6
+0xb2 U+1ed7
+0x93 U+1ed8
+0xb5 U+1ed9
+0x95 U+1eda
+0xbe U+1edb
+0x96 U+1edc
+0xb6 U+1edd
+0x97 U+1ede
+0xb7 U+1edf
+0xb3 U+1ee0
+0xde U+1ee1
+0x94 U+1ee2
+0xfe U+1ee3
+0x9e U+1ee4
+0xf8 U+1ee5
+0x9c U+1ee6
+0xfc U+1ee7
+0xba U+1ee8
+0xd1 U+1ee9
+0xbb U+1eea
+0xd7 U+1eeb
+0xbc U+1eec
+0xd8 U+1eed
+0xff U+1eee
+0xe6 U+1eef
+0xb9 U+1ef0
+0xf1 U+1ef1
+0x9f U+1ef2
+0xcf U+1ef3
+0x18 U+1ef4 # MacVISCII
+0x1e U+1ef4
+U+1ef4:Y.
+0xdc U+1ef5
+0x17 U+1ef6 # MacVISCII
+0x14 U+1ef6
+U+1ef6:Y?
+0xd6 U+1ef7
+0x19 U+1ef8
+U+1ef8:Y~
+0xdb U+1ef9
+
diff --git a/src/chrtrans/yawerty_kb.h b/src/chrtrans/yawerty_kb.h
new file mode 100644
index 0000000..8301c81
--- /dev/null
+++ b/src/chrtrans/yawerty_kb.h
@@ -0,0 +1,22 @@
+static LYKbLayout_t kb_layout_yawerty[128] =
+{
+ 0x0000, 0x0001, 0x0002, 0x0003, 0x0004, 0x0005, 0x0006, 0x0007, /* 00..07 */
+ 0x0008, 0x0009, 0x000A, 0x000B, 0x000C, 0x000D, 0x000E, 0x000F, /* 08..0F */
+ 0x0010, 0x0011, 0x0012, 0x0013, 0x0014, 0x0015, 0x0016, 0x0017, /* 10..17 */
+ 0x0018, 0x0019, 0x001A, 0x001B, 0x001C, 0x001D, 0x001E, 0x001F, /* 18..1F */
+
+ 0x0020, 0x0021, 0x0022, 0x0023, 0x0024, 0x0025, 0x0026, 0x0027, /* 20..27 */
+ 0x0028, 0x0029, 0x002A, 0x002B, 0x002C, 0x002D, 0x002E, 0x002F, /* 28..2F */
+ 0x0030, 0x0031, 0x0032, 0x0033, 0x0034, 0x0035, 0x0036, 0x0037, /* 30..37 */
+ 0x0038, 0x0039, 0x003A, 0x003B, 0x003C, 0x003D, 0x003E, 0x003F, /* 38..3F */
+
+ 0x042e, 0x0410, 0x0411, 0x0426, 0x0414, 0x0415, 0x0424, 0x0413, /* 40..47 */
+ 0x0425, 0x0418, 0x0419, 0x041a, 0x041b, 0x041c, 0x041d, 0x041e, /* 48..4F */
+ 0x041f, 0x042f, 0x0420, 0x0421, 0x0422, 0x0423, 0x0416, 0x0412, /* 50..57 */
+ 0x042c, 0x042b, 0x0417, 0x0448, 0x044d, 0x0449, 0x0447, 0x044a, /* 58..5F */
+
+ 0x044e, 0x0430, 0x0431, 0x0446, 0x0434, 0x0435, 0x0444, 0x0433, /* 60..67 */
+ 0x0445, 0x0438, 0x0439, 0x043a, 0x043b, 0x043c, 0x043d, 0x043e, /* 68..6F */
+ 0x043f, 0x044f, 0x0440, 0x0441, 0x0442, 0x0443, 0x0436, 0x0432, /* 70..77 */
+ 0x044c, 0x044b, 0x0437, 0x0428, 0x042d, 0x0429, 0x0427, 0x0000 /* 78..7F */
+};
diff --git a/src/cmu_tcp.opt b/src/cmu_tcp.opt
new file mode 100644
index 0000000..24e43fe
--- /dev/null
+++ b/src/cmu_tcp.opt
@@ -0,0 +1 @@
+cmuip_root:[syslib]libcmu/library
diff --git a/src/decc.opt b/src/decc.opt
new file mode 100644
index 0000000..670a21b
--- /dev/null
+++ b/src/decc.opt
@@ -0,0 +1,2 @@
+sys$library:vaxcrtl/library
+sys$library:vaxccurse/library
diff --git a/src/descrip.mms b/src/descrip.mms
new file mode 100644
index 0000000..6b22b78
--- /dev/null
+++ b/src/descrip.mms
@@ -0,0 +1,172 @@
+! $LynxId: descrip.mms,v 1.12 2008/06/30 23:50:22 tom Exp $
+!
+! Make LYNX hypertext browser under VMS
+! =====================================
+!
+! NOTE: Use [.SRC.CHRTRANS]BUILD-CHRTRANS.COM to create the
+! chrtrans header files before using this descrip.mms.
+!
+! History:
+! 1/1/93 creation at KU (Lou montulli@ukanaix.cc.ukans.edu).
+! 4/12/93 (seb@lns61.tn.cornell.edu)
+! modified to support either UCX or MULTINET
+! 12/2/93 modified to support Lynx rewrite
+! 12/13/93 (macrides@sci.wfeb.edu)
+! Added conditional compilations for VAXC vs. DECC
+! (dependencies not yet specified; this is just a
+! "starter", should anyone want to do it well).
+! 10/31/94 RLD Updated for Lynx v2.3.4-VMS, supporting OpenCMU
+! and TCPWare
+! 11/11/94 RLD Updated for Lynx v2.3.5-VMS
+! 11/18/94 FM Updated for SOCKETSHR/NETLIB
+! 12/07/94 FM Updated for DECC/VAX, VAXC/VAX and DECC/AXP
+! 05/03/95 FM Include /NoMember for DECC (not the default on AXP, and
+! the code assumes byte alignment).
+! 06/14/95 FM Added LYList.
+! 07/26/95 FM Separated transport (TOPT) and compiler (COPT) option files.
+! 07/29/95 FM Added support for GNUC.
+! 02/29/96 FM Added LYMap.
+! 06/28/97 FM Added UCAuto, UCAux, and UCdomap.
+! 15 Sep 06 (TD) Cleanup...
+!
+! Instructions:
+! Use the correct command line for your TCP/IP implementation:
+!
+! $ MMS for VAXC - MultiNet
+! $ MMS /Macro = (MULTINET=1) for VAXC - MultiNet
+! $ MMS /Macro = (WIN_TCP=1) for VAXC - Wollongong TCP/IP
+! $ MMS /Macro = (UCX=1) for VAXC - UCX
+! $ MMS /Macro = (CMU_TCP=1) for VAXC - OpenCMU TCP/IP
+! $ MMS /Macro = (SOCKETSHR_TCP=1) for VAXC - SOCKETSHR/NETLIB
+! $ MMS /Macro = (TCPWARE=1) for VAXC - TCPWare TCP/IP
+! $ MMS /Macro = (DECNET=1) for VAXC - socket emulation over DECnet
+!
+! $ MMS /Macro = (MULTINET=1, DEC_C=1) for DECC - MultiNet
+! $ MMS /Macro = (WIN_TCP=1, DEC_C=1) for DECC - Wollongong TCP/IP
+! $ MMS /Macro = (UCX=1, DEC_C=1) for DECC - UCX
+! $ MMS /Macro = (CMU_TCP=1, DEC_C=1) for DECC - OpenCMU TCP/IP
+! $ MMS /Macro = (SOCKETSHR_TCP=1,DEC_C=1) for DECC - SOCKETSHR/NETLIB
+! $ MMS /Macro = (TCPWARE=1, DEC_C=1) for DECC - OpenCMU TCP/IP
+! $ MMS /Macro = (DECNET=1, DEC_C=1) for DECC - socket emulation over DECnet
+!
+! $ MMS /Macro = (MULTINET=1, GNU_C=1) for GNUC - MultiNet
+! $ MMS /Macro = (WIN_TCP=1, GNU_C=1) for GNUC - Wollongong TCP/IP
+! $ MMS /Macro = (UCX=1, GNU_C=1) for GNUC - UCX
+! $ MMS /Macro = (CMU_TCP=1, GNU_C=1) for GNUC - OpenCMU TCP/IP
+! $ MMS /Macro = (SOCKETSHR_TCP=1,GNU_C=1) for GNUC - SOCKETSHR/NETLIB
+! $ MMS /Macro = (TCPWARE=1, GNU_C=1) for GNUC - OpenCMU TCP/IP
+! $ MMS /Macro = (DECNET=1, GNU_C=1) for GNUC - socket emulation over DECnet
+
+OBJS = DefaultStyle.obj, GridText.obj, HTAlert.obj, HTFWriter.obj, -
+ HTInit.obj, HTML.obj, LYBookmark.obj, LYCgi.obj, LYCharSets.obj, -
+ LYCharUtils.obj, LYClean.obj, LYCookie.obj, LYCurses.obj, -
+ LYDownload.obj, LYEdit.obj, LYEditmap.obj, LYForms.obj, -
+ LYGetFile.obj, LYHistory.obj, LYJump.obj, LYKeymap.obj, -
+ LYLeaks.obj, LYList.obj, LYMail.obj, LYMain.obj, LYMainLoop.obj, -
+ LYMap.obj, LYNews.obj, LYOptions.obj, LYPrint.obj, LYReadCFG.obj, -
+ LYSearch.obj, LYShowInfo.obj, LYStrings.obj, LYTraversal.obj, -
+ LYUpload.obj, LYUtils.obj, LYexit.obj, LYrcFile.obj, TRSTable.obj, -
+ LYmktime.obj, UCAuto.obj, UCAux.obj, UCdomap.obj, parsdate.obj
+
+.ifdef SLANG
+SCREEN_DEF = USE_SLANG
+SCREEN_INC = , SLANG_INC
+SCREEN_LIB = , SLANG_LIB:slang.olb/lib
+.else
+.ifdef DEC_C
+SCREEN_DEF = __VMS_CURSES
+.endif
+.endif
+
+.ifdef DEC_C
+COMPILER = DECC
+MODEL_DEF = _DECC_V4_SOURCE
+.else
+MODEL_DEF =
+.ifdef GNU_C
+COMPILER = GNUC
+CC = gcc
+.else
+COMPILER = VAXC
+.endif
+.endif
+
+.ifdef WIN_TCP
+NETWORK_DEF = WIN_TCP
+NETWORK_OPT = WIN_TCP
+.else
+.ifdef CMU_TCP
+NETWORK_DEF = CMU_TCP
+NETWORK_OPT = CMU_TCP
+.else
+.ifdef SOCKETSHR_TCP
+NETWORK_DEF = SOCKETSHR_TCP
+NETWORK_OPT = SOCKETSHR_TCP
+.else
+.ifdef UCX
+NETWORK_DEF = UCX
+.ifdef DEC_C
+NETWORK_OPT = UCXSHR
+.else
+NETWORK_OPT = UCXOLB
+.endif
+.else
+.ifdef TCPWARE
+NETWORK_DEF = TCPWARE,UCX
+.ifdef DEC_C
+NETWORK_OPT = TCPWARESHR
+.else
+NETWORK_OPT = TCPWAREOLB
+.endif
+.else
+.ifdef DECnet
+NETWORK_DEF = DECNET
+NETWORK_OPT = DECNET
+.else ! Default to MultiNet
+NETWORK_DEF = MULTINET,__SOCKET_TYPEDEFS
+NETWORK_OPT = MULTINET
+.endif ! DECnet
+.endif ! TCPWARE
+.endif ! UCX
+.endif ! SOCKETSHR_TCP
+.endif ! CMU_TCP
+.endif ! WIN_TCP
+
+COMPILER_DEF = $(MODEL_DEF),$(NETWORK_DEF),$(SCREEN_DEF)
+
+.ifdef DEC_C
+MY_CFLAGS = /decc/Prefix=All/NoMember/Define=(ACCESS_AUTH,$(COMPILER_DEF))
+.else
+MY_CFLAGS = /Define = (ACCESS_AUTH, $(COMPILER_DEF))
+.endif
+
+.if "$(MMS_ARCHNAME)" .eq "IA64"
+TOPT =
+COPT =
+.else
+TOPT = ,sys$disk:[]$(NETWORK_OPT).opt/opt
+COPT = ,sys$disk:[]$(COMPILER).opt/opt
+.endif
+
+WWWLIB = [-.WWW.Library.Implementation]WWWLib.olb
+CFLAGS = $(MY_CFLAGS) $(CFLAGS)/Include=([], [-], [.chrtrans], [-.WWW.Library.Implementation]$(SCREEN_INC))
+
+
+lynx : lynx.exe
+ @ Continue
+
+HDRS = [.chrtrans]iso01_uni.h
+
+lynx.exe : $(HDRS) $(OBJS) $(WWWLIB)
+ $(LINK) /Executable = Lynx.exe $(OBJS), $(WWWLIB)/lib $(SCREEN_LIB) $(TOPT) $(COPT)
+
+$(HDRS) :
+ set default [.chrtrans]
+ @build-chrtrans
+ set default [-]
+
+clean :
+ - Set Protection = (Owner:RWED) *.*;-1
+ - Purge /NoLog /NoConfirm
+ - Delete /NoConfirm /NoLog *.obj;*
+ - Delete /NoConfirm /NoLog *.exe;*
diff --git a/src/gnuc.opt b/src/gnuc.opt
new file mode 100644
index 0000000..0fe5159
--- /dev/null
+++ b/src/gnuc.opt
@@ -0,0 +1,3 @@
+gnu_cc:[000000]gcclib/library
+sys$share:vaxcrtl/share
+sys$library:vaxccurse/library
diff --git a/src/makefile.dos b/src/makefile.dos
new file mode 100644
index 0000000..5409676
--- /dev/null
+++ b/src/makefile.dos
@@ -0,0 +1,115 @@
+# $LynxId: makefile.dos,v 1.33 2011/05/28 13:07:55 tom Exp $
+
+OBJS= UCdomap.o UCAux.o UCAuto.o \
+LYClean.o LYShowInfo.o LYEdit.o LYStrings.o \
+LYMail.o HTAlert.o GridText.o LYGetFile.o \
+LYMain.o LYMainLoop.o LYCurses.o LYBookmark.o LYmktime.o LYUtils.o \
+LYOptions.o LYReadCFG.o LYSearch.o LYHistory.o LYSession.o \
+LYForms.o LYPrint.o LYrcFile.o LYDownload.o LYNews.o LYKeymap.o \
+HTML.o HTFWriter.o HTInit.o DefaultStyle.o LYLocal.o LYUpload.o \
+LYLeaks.o LYexit.o LYJump.o LYList.o LYCgi.o LYTraversal.o \
+LYEditmap.o LYCharSets.o LYCharUtils.o LYMap.o LYCookie.o LYExtern.o \
+LYStyle.o LYHash.o LYPrettySrc.o TRSTable.o parsdate.o
+
+CFLAGS= -O2 $(MCFLAGS) $(INTLFLAGS) -I. -I..
+
+# comment this line to suppress DIRED support
+DIRED_DEFS = \
+ -DDIRED_SUPPORT \
+ -DOK_UUDECODE \
+ -DOK_TAR \
+ -DOK_GZIP \
+ -DOK_ZIP \
+ -DOK_OVERRIDE
+
+# Use this option to enable optional and *experimental* color style.
+#ENABLE_COLOR_STYLE = -DUSE_COLOR_STYLE
+
+CC = gcc
+
+MCFLAGS = \
+ $(DIRED_DEFS) \
+ $(ENABLE_COLOR_STYLE) \
+ -DACCESS_AUTH \
+ -DCOLOR_CURSES \
+ -DDISP_PARTIAL \
+ -DDOSPATH \
+ -DUSE_ADDRLIST_PAGE \
+ -DUSE_ALT_BINDINGS \
+ -DEXP_NESTED_TABLES \
+ -DUSE_PERSISTENT_COOKIES \
+ -DFANCY_CURSES \
+ -DNOUSERS \
+ -DNO_CUSERID \
+ -DNO_TTYTYPE \
+ -DNO_UTMP \
+ -DPDCURSES \
+ -DUSE_SOURCE_CACHE \
+ -DUSE_EXTERNALS \
+ -DUSE_FILE_UPLOAD \
+ -DUSE_PRETTYSRC \
+ -DUSE_ZLIB \
+ $(SSLFLAGS) \
+ $(SSLINC) \
+ -I./chrtrans \
+ -I../WWW/Library/Implementation \
+ -I/djgpp/pdcur26 \
+ -I/djgpp/watt32/inc
+
+WWWLIB = \
+ ../WWW/Library/djgpp/libwww.a \
+ /djgpp/pdcur26/lib/pdcurses.a
+
+LIBS= -L/djgpp/watt32/lib -lwatt -lz -lwmemu
+
+# Uncomment the following to enable Internationalization.
+#INTLFLAGS = -DHAVE_GETTEXT -DHAVE_LIBINTL_H
+#INTLLIBS= -lintl -liconv
+
+# Uncomment the following to enable SSL.
+#SSLFLAGS = -DUSE_SSL
+#SSLLIB = -lssl -lcrypto
+#SSLINC = -I/djgpp/include/openssl
+
+all: lynx
+
+lynx: message $(OBJS) $(WWWLIB)
+ @echo "Linking and creating Lynx executable"
+ $(CC) $(CFLAGS) -o lynx.exe $(OBJS) $(WWWLIB) $(SSLLIB) $(LIBS) $(INTLLIBS)
+ @echo "Welcome to Lynx!"
+
+message:
+ @echo "Compiling Lynx sources"
+
+dbg: $(OBJS) $(WWWLIB)
+ @echo "Making Lynx code"
+ $(CC) -g $(OBJS) $(CFLAGS) $(WWWLIB) $(LIBS)
+
+lint:
+ lint *.c > ../lint.out
+
+clean:
+ rm -f lynx.exe core *.[ob]
+
+DefaultStyle.o: ../userdefs.h
+HTFWriter.o: ../userdefs.h
+LYBookmark.o: ../userdefs.h
+LYCharSets.o: ../userdefs.h
+LYCharUtils.o: ../userdefs.h
+LYCookie.o: ../userdefs.h
+LYDownload.o: ../userdefs.h
+LYEditmap.o: ../userdefs.h
+LYExtern.o: ../userdefs.h
+LYGetFile.o: ../userdefs.h
+LYHistory.o: ../userdefs.h
+LYKeymap.o: ../userdefs.h
+LYMain.o: ../userdefs.h
+LYMainLoop.o: ../userdefs.h
+LYOptions.o: ../userdefs.h
+LYReadCFG.o: ../userdefs.h
+LYShowInfo.o: ../userdefs.h
+LYStrings.o: ../userdefs.h
+LYTraversal.o: ../userdefs.h
+LYUtils.o: ../userdefs.h
+LYmktime.o: ../userdefs.h
+parsdate.o: ../userdefs.h
diff --git a/src/makefile.dsl b/src/makefile.dsl
new file mode 100644
index 0000000..64024f7
--- /dev/null
+++ b/src/makefile.dsl
@@ -0,0 +1,105 @@
+# $LynxId: makefile.dsl,v 1.19 2008/06/30 23:53:42 tom Exp $
+
+OBJS= UCdomap.o UCAux.o UCAuto.o \
+LYClean.o LYShowInfo.o LYEdit.o LYStrings.o \
+LYMail.o HTAlert.o GridText.o LYGetFile.o \
+LYMain.o LYMainLoop.o LYCurses.o LYBookmark.o LYmktime.o LYUtils.o \
+LYOptions.o LYReadCFG.o LYSearch.o LYHistory.o LYSession.o \
+LYForms.o LYPrint.o LYrcFile.o LYDownload.o LYNews.o LYKeymap.o \
+HTML.o HTFWriter.o HTInit.o DefaultStyle.o LYLocal.o LYUpload.o \
+LYLeaks.o LYexit.o LYJump.o LYList.o LYCgi.o LYTraversal.o \
+LYEditmap.o LYCharSets.o LYCharUtils.o LYMap.o LYCookie.o LYExtern.o \
+LYStyle.o LYHash.o LYPrettySrc.o TRSTable.o parsdate.o
+
+CFLAGS= -O2 $(MCFLAGS) $(INTLFLAGS) -I. -I.. $(SLANGINC)
+
+# comment this line to suppress DIRED support
+DIRED_DEFS = \
+ -DDIRED_SUPPORT \
+ -DOK_UUDECODE \
+ -DOK_TAR \
+ -DOK_GZIP \
+ -DOK_ZIP \
+ -DOK_OVERRIDE
+
+CC = gcc
+
+MCFLAGS = \
+ $(DIRED_DEFS) \
+ -DACCESS_AUTH \
+ -DDISP_PARTIAL \
+ -DDJGPP_KEYHANDLER \
+ -DDOSPATH \
+ -DHAVE_POPEN \
+ -DNOUSERS \
+ -DNO_CUSERID \
+ -DNO_TTYTYPE \
+ -DNO_UTMP \
+ -DUSE_EXTERNALS \
+ -DUSE_PRETTYSRC \
+ -DUSE_SLANG \
+ -DUSE_SOURCE_CACHE \
+ -DUSE_ZLIB \
+ $(SSLFLAGS) \
+ $(SSLINC) \
+ -I./chrtrans \
+ -I../WWW/Library/Implementation \
+ -I/dev/env/DJDIR/watt32/inc
+
+WWWLIB = \
+ ../WWW/Library/djgpp/libwww.a \
+ /dev/env/DJDIR/watt32/lib/libwatt.a
+
+LIBS= $(SLANGLIB) -lslang $(SSLLIB) -lz $(INTLLIBS)
+
+# Uncomment the following to enable Internationalization.
+#INTLFLAGS = -DHAVE_GETTEXT -DHAVE_LIBINTL_H
+#INTLLIBS= -lintl -liconv
+
+# Uncomment the following to enable SSL.
+#SSLFLAGS = -DUSE_SSL
+#SSLLIB = -lssl -lcrypto
+#SSLINC = -I/dev/env/DJDIR/include/openssl
+
+all: lynx.exe
+
+lynx.exe: message $(OBJS) $(WWWLIB)
+ @echo "Linking and creating Lynx executable"
+ $(CC) $(CFLAGS) -o lynx.exe $(OBJS) $(WWWLIB) $(LIBS)
+ @echo "Welcome to Lynx!"
+
+message:
+ @echo "Compiling Lynx sources"
+
+dbg: $(OBJS) $(WWWLIB)
+ @echo "Making Lynx code"
+ $(CC) -g $(OBJS) $(CFLAGS) $(WWWLIB) $(LIBS)
+
+lint:
+ lint *.c > ../lint.out
+
+clean:
+ rm -f lynx.exe core *.[ob]
+
+DefaultStyle.o: ../userdefs.h
+HTFWriter.o: ../userdefs.h
+LYBookmark.o: ../userdefs.h
+LYCharSets.o: ../userdefs.h
+LYCharUtils.o: ../userdefs.h
+LYCookie.o: ../userdefs.h
+LYDownload.o: ../userdefs.h
+LYEditmap.o: ../userdefs.h
+LYExtern.o: ../userdefs.h
+LYGetFile.o: ../userdefs.h
+LYHistory.o: ../userdefs.h
+LYKeymap.o: ../userdefs.h
+LYMain.o: ../userdefs.h
+LYMainLoop.o: ../userdefs.h
+LYOptions.o: ../userdefs.h
+LYReadCFG.o: ../userdefs.h
+LYShowInfo.o: ../userdefs.h
+LYStrings.o: ../userdefs.h
+LYTraversal.o: ../userdefs.h
+LYUtils.o: ../userdefs.h
+LYmktime.o: ../userdefs.h
+parsdate.o: ../userdefs.h
diff --git a/src/makefile.in b/src/makefile.in
new file mode 100644
index 0000000..5246226
--- /dev/null
+++ b/src/makefile.in
@@ -0,0 +1,242 @@
+# $LynxId: makefile.in,v 1.77 2021/02/28 15:56:53 tom Exp $
+# template-makefile for Lynx src directory
+
+SHELL = @CONFIG_SHELL@
+CDPATH = .
+
+@SET_MAKE@
+prefix = @prefix@
+datarootdir = @datarootdir@
+exec_prefix = @exec_prefix@
+top_srcdir = @top_srcdir@
+srcdir = @srcdir@
+VPATH = .:$(srcdir)
+
+top_builddir = ..
+
+# see po/makefile
+localedir = @NLS_DATADIR@/locale
+
+# Symbols which the configure script can set in each makefile:
+CC = @CC@
+CPP = @CPP@
+CFLAGS = @CFLAGS@ @EXTRA_CPPFLAGS@
+DEFS = @DEFS@
+CHARSET_DEFS = @CHARSET_DEFS@
+CPPFLAGS = @CPPFLAGS@
+
+x = @EXEEXT@
+o = .@OBJEXT@
+
+BUILD_CC = @BUILD_CC@
+BUILD_CPP = @BUILD_CPP@
+BUILD_CFLAGS = @BUILD_CFLAGS@
+BUILD_CPPFLAGS = @BUILD_CPPFLAGS@ @DEFS@
+BUILD_EXEEXT = @BUILD_EXEEXT@
+BUILD_LDFLAGS = @BUILD_LDFLAGS@
+BUILD_LIBS = @BUILD_LIBS@
+
+MAKE_RECUR = $(MAKE) @cf_cv_makeflags@ DESTDIR="$(DESTDIR)" CC="$(CC)"
+
+YACC = @YACC@
+WINDRES = @WINDRES@
+
+LIBS = @LIBS@ $(RESOLVLIB) $(WAISLIB) $(SITE_LIBS)
+LDFLAGS = @EXTRA_LDFLAGS@ @LDFLAGS@
+
+# Symbols inherited from the top-level makefile
+RESOLVLIB = # FIXME: set in parent makefile
+SITE_DEFS = # FIXME: set in parent makefile
+SITE_LIBS = # FIXME: set in parent makefile
+WAISLIB = # FIXME: set in parent makefile
+
+WWWINC = WWW/Library/Implementation
+WWWLIB = $(top_builddir)/WWW/Library/Implementation/libwww.a
+
+INTLLIB = @INTLLIBS@
+INTLDIR_CPPFLAGS= @INTLDIR_CPPFLAGS@-I$(top_srcdir)/intl
+
+CPP_OPTS = $(DEFS) $(CHARSET_DEFS) \
+ -DLOCALEDIR=\"$(localedir)\" \
+ -I. \
+ -I$(top_builddir) \
+ -Ichrtrans \
+ -I$(srcdir)/chrtrans \
+ -I$(top_srcdir) \
+ -I$(top_srcdir)/src \
+ -I$(top_srcdir)/$(WWWINC) \
+ $(INTLDIR_CPPFLAGS) $(SITE_DEFS) $(CPPFLAGS)
+CC_OPTS = $(CFLAGS) $(CPP_OPTS)
+
+LINT = @LINT@
+LINTOPTS =
+
+CTAGS = @CTAGS@
+
+COMPRESS_PROG =@COMPRESS_PROG@
+COMPRESS_EXT =@COMPRESS_EXT@
+
+CHARTRANS_OBJS = UCdomap$o UCAux$o UCAuto$o
+OBJS = \
+ LYebcdic$o \
+ LYClean$o LYShowInfo$o LYEdit$o LYStrings$o LYMail$o \
+ HTAlert$o GridText$o LYGetFile$o LYMain$o LYMainLoop$o \
+ LYCurses$o LYBookmark$o LYmktime$o LYUtils$o LYOptions$o \
+ LYReadCFG$o LYSearch$o LYHistory$o LYForms$o LYPrint$o \
+ LYrcFile$o LYDownload$o LYNews$o LYKeymap$o HTML$o \
+ HTFWriter$o HTInit$o DefaultStyle$o LYUpload$o \
+ LYLeaks$o LYexit$o LYJump$o LYList$o LYCgi$o \
+ LYTraversal$o LYEditmap$o LYCharSets$o LYCharUtils$o \
+ LYMap$o LYCookie$o LYStyle$o LYHash$o LYPrettySrc$o \
+ TRSTable$o parsdate$o $(CHARTRANS_OBJS) @EXTRA_OBJS@ @LIBOBJS@
+
+C_SRC = $(OBJS:$o=.c)
+
+all: lynx$x
+
+.SUFFIXES : $o .i
+
+# yacc builds .c in target directory, not $(srcdir)
+.c$o:
+ @RULE_CC@
+ @ECHO_CC@$(CC) $(CC_OPTS) -c $<
+
+.c.i:
+ @RULE_CC@
+ @ECHO_CC@$(CPP) $(CPP_OPTS) $< >$@
+
+lynx$x: message $(top_builddir)/LYHelp.h $(OBJS) $(WWWLIB)
+ @echo "Linking and creating Lynx executable"
+ $(CC) $(CC_OPTS) $(LDFLAGS) -o $@ $(OBJS) $(WWWLIB) $(LIBS) $(INTLLIB)
+ @echo "Copying Lynx executable into top-level directory"
+ rm -f $(top_builddir)/$@
+ cp $@ $(top_builddir)/
+ @echo "Welcome to Lynx!"
+
+message:
+ @echo "Compiling Lynx sources"
+
+do_chartrans_stuff:
+ -( cd chrtrans && $(MAKE_RECUR) \
+ SITE_DEFS="$(SITE_DEFS)" \
+ BUILD_CFLAGS="$(BUILD_CFLAGS)" \
+ BUILD_CPPFLAGS="$(BUILD_CPPFLAGS)" \
+ BUILD_LDFLAGS="$(BUILD_LDFLAGS)" \
+ BUILD_LIBS="$(BUILD_LIBS)" \
+ BUILD_CC="$(BUILD_CC)" tables )
+
+lint:
+ $(LINT) $(LINTOPTS) $(CPP_OPTS) $(C_SRC) 2>&1 |tee $(top_builddir)/lint.lynx
+
+clean:
+ rm -f lynx$x core *.core *.leaks *.i *$o *.bak tags TAGS test_*
+ ( cd chrtrans && $(MAKE_RECUR) clean )
+
+tags:
+ $(CTAGS) *.[ch]
+
+distclean: clean
+
+CMN=$(top_srcdir)/WWW/Library/Implementation/
+
+GridText$o : $(top_srcdir)/userdefs.h
+HTFWriter$o : $(top_srcdir)/userdefs.h
+HTInit$o : $(top_srcdir)/userdefs.h
+LYCharSets$o : $(top_srcdir)/userdefs.h
+LYGetFile$o : $(top_srcdir)/userdefs.h
+LYKeymap$o : $(top_srcdir)/userdefs.h
+LYLeaks$o : $(CMN)LYLeaks.h $(CMN)HTString.h
+LYMail$o : $(top_srcdir)/userdefs.h
+LYMain$o : $(top_srcdir)/userdefs.h $(top_builddir)/lynx_cfg.h
+LYMainLoop$o : $(top_srcdir)/userdefs.h
+LYOptions$o : $(top_srcdir)/userdefs.h
+LYReadCFG$o : $(top_srcdir)/userdefs.h
+LYShowInfo$o : $(top_builddir)/cfg_defs.h
+LYTraversal$o : $(top_srcdir)/userdefs.h
+LYUtils$o : $(top_srcdir)/userdefs.h
+LYmktime$o : $(top_srcdir)/userdefs.h
+LYrcFile$o : $(top_srcdir)/userdefs.h
+
+LYIcon$o: LYIcon.rc
+ $(WINDRES) -i LYIcon.rc -o LYIcon$o -O coff
+
+CHRTR= chrtrans/
+
+TABLES= \
+ $(CHRTR)cp1250_uni.h \
+ $(CHRTR)cp1251_uni.h \
+ $(CHRTR)cp1252_uni.h \
+ $(CHRTR)cp1253_uni.h \
+ $(CHRTR)cp1255_uni.h \
+ $(CHRTR)cp1256_uni.h \
+ $(CHRTR)cp1257_uni.h \
+ $(CHRTR)cp437_uni.h \
+ $(CHRTR)cp737_uni.h \
+ $(CHRTR)cp775_uni.h \
+ $(CHRTR)cp850_uni.h \
+ $(CHRTR)cp852_uni.h \
+ $(CHRTR)cp857_uni.h \
+ $(CHRTR)cp862_uni.h \
+ $(CHRTR)cp864_uni.h \
+ $(CHRTR)cp866_uni.h \
+ $(CHRTR)cp866u_uni.h \
+ $(CHRTR)cp869_uni.h \
+ $(CHRTR)def7_uni.h \
+ $(CHRTR)dmcs_uni.h \
+ $(CHRTR)hp_uni.h \
+ $(CHRTR)iso01_uni.h \
+ $(CHRTR)iso02_uni.h \
+ $(CHRTR)iso03_uni.h \
+ $(CHRTR)iso04_uni.h \
+ $(CHRTR)iso05_uni.h \
+ $(CHRTR)iso06_uni.h \
+ $(CHRTR)iso07_uni.h \
+ $(CHRTR)iso08_uni.h \
+ $(CHRTR)iso09_uni.h \
+ $(CHRTR)iso10_uni.h \
+ $(CHRTR)iso13_uni.h \
+ $(CHRTR)iso14_uni.h \
+ $(CHRTR)iso15_uni.h \
+ $(CHRTR)koi8r_uni.h \
+ $(CHRTR)koi8u_uni.h \
+ $(CHRTR)mac_uni.h \
+ $(CHRTR)mnem2_suni.h \
+ $(CHRTR)mnem_suni.h \
+ $(CHRTR)next_uni.h \
+ $(CHRTR)pt154_uni.h \
+ $(CHRTR)rfc_suni.h \
+ $(CHRTR)utf8_uni.h \
+ $(CHRTR)viscii_uni.h
+
+$(TABLES):
+ -( cd chrtrans && $(MAKE_RECUR) tables )
+
+UCdomap$o : UCdomap.c \
+ chrtrans/UCkd.h \
+ chrtrans/makeuctb$(BUILD_EXEEXT) \
+ chrtrans/makeuctb.c \
+ UCdomap.h $(CMN)UCMap.h $(TABLES) $(top_srcdir)/userdefs.h
+
+chrtrans/makeuctb$(BUILD_EXEEXT):
+ ( cd chrtrans && $(MAKE_RECUR) makeuctb$(BUILD_EXEEXT) )
+
+UCAux$o : UCAux.c $(CMN)UCAux.h $(CMN)UCDefs.h
+LYCookie$o : $(top_srcdir)/userdefs.h
+
+test_mktime.o: $(srcdir)/LYmktime.c
+ $(CC) -o $@ $(CC_OPTS) -DTEST_DRIVER -c $(srcdir)/LYmktime.c
+
+# test-driver for LYmktime
+test_mktime: test_mktime.o parsdate.o LYebcdic.o
+ $(CC) -o $@ $(CC_OPTS) test_mktime.o parsdate.o LYebcdic.o
+
+# update generated source (may be in ".", or srcdir)
+parsdate.c : $(srcdir)/parsdate.y
+ $(YACC) $(srcdir)/parsdate.y
+ -rm -f $@
+ mv y.tab.c $@
+
+depend : $(TABLES)
+ makedepend -fmakefile -- $(CC_OPTS) -- $(C_SRC)
+
+# DO NOT DELETE THIS LINE -- make depend depends on it.
diff --git a/src/makefile.wsl b/src/makefile.wsl
new file mode 100644
index 0000000..5890ec3
--- /dev/null
+++ b/src/makefile.wsl
@@ -0,0 +1,68 @@
+# $LynxId: makefile.wsl,v 1.15 2008/06/30 23:53:42 tom Exp $
+
+OBJS= UCdomap.o UCAux.o UCAuto.o \
+LYClean.o LYShowInfo.o LYEdit.o LYStrings.o \
+LYMail.o HTAlert.o GridText.o LYGetFile.o \
+LYMain.o LYMainLoop.o LYCurses.o LYBookmark.o LYmktime.o LYUtils.o \
+LYOptions.o LYReadCFG.o LYSearch.o LYHistory.o LYSession.o \
+LYForms.o LYPrint.o LYrcFile.o LYDownload.o LYNews.o LYKeymap.o \
+HTML.o HTFWriter.o HTInit.o DefaultStyle.o LYLocal.o LYUpload.o \
+LYLeaks.o LYexit.o LYJump.o LYList.o LYCgi.o LYTraversal.o \
+LYEditmap.o LYCharSets.o LYCharUtils.o LYMap.o LYCookie.o LYExtern.o \
+LYStyle.o LYHash.o LYPrettySrc.o TRSTable.o parsdate.o
+
+CFLAGS= -O1 $(MCFLAGS) -I. -I.. $(SLANGINC)
+
+CC = gcc
+MCFLAGS = -DDISP_PARTIAL -DUSE_ZLIB -DUSE_EXTERNALS \
+-DUSE_SOURCE_CACHE -DUSE_PRETTYSRC \
+-DUSE_SLANG -DACCESS_AUTH -DNO_CUSERID \
+-DNOUSERS -DDOSPATH -DNO_TTYTYPE -DNO_UTMP -I../WWW/library/implement -I../djgpp/tcplib/include \
+-I./chrtrans -I../djgpp/tcplib/include/tcp
+WWWLIB = ../WWW/library/djgpp/libwww.a ../djgpp/tcplib/obj/libtcp.a
+LIBS= -lslang -lz
+CHRTR= ./chrtrans/
+
+all: lynx.exe
+
+lynx.exe: message $(OBJS) $(WWWLIB)
+ @echo "Linking and creating Lynx executable"
+ $(CC) $(CFLAGS) -o lynx.exe $(OBJS) $(WWWLIB) $(SLANGLIB) $(LIBS)
+ @echo "Welcome to Lynx!"
+
+message:
+ @echo "Compiling Lynx sources"
+
+dbg: $(OBJS) $(WWWLIB)
+ @echo "Making Lynx code"
+ $(CC) $(OBJS) $(CFLAGS) $(WWWLIB) $(SLANGLIB) $(LIBS)
+
+lint:
+ lint *.c > ../lint.out
+
+clean:
+ rm -f lynx.exe core *.[ob]
+
+DefaultStyle.o: ../userdefs.h
+HTFWriter.o: ../userdefs.h
+LYBookmark.o: ../userdefs.h
+LYCharSets.o: ../userdefs.h
+LYCharUtils.o: ../userdefs.h
+LYCookie.o: ../userdefs.h
+LYDownload.o: ../userdefs.h
+LYEditmap.o: ../userdefs.h
+LYExtern.o: ../userdefs.h
+LYGetFile.o: ../userdefs.h
+LYHistory.o: ../userdefs.h
+LYKeymap.o: ../userdefs.h
+LYMain.o: ../userdefs.h
+LYMainLoop.o: ../userdefs.h
+LYOptions.o: ../userdefs.h
+LYReadCFG.o: ../userdefs.h
+LYReadCFG.o: ../userdefs.h
+LYShowInfo.o: ../userdefs.h
+LYStrings.o: ../userdefs.h
+LYTraversal.o: ../userdefs.h
+LYUtils.o: ../userdefs.h
+LYmktime.o: ../userdefs.h
+parsdate.o: ../userdefs.h
diff --git a/src/mktime.c b/src/mktime.c
new file mode 100644
index 0000000..961f6b4
--- /dev/null
+++ b/src/mktime.c
@@ -0,0 +1,71 @@
+/*
+ * mktime.c -- converts a struct tm into a time_t
+ *
+ * Copyright (C) 1997 Free Software Foundation, Inc.
+ *
+ * This program is free software; you can redistribute it and/or modify it under
+ * the terms of the GNU General Public License as published by the Free
+ * Software Foundation; either version 2, or (at your option) any later
+ * version.
+ *
+ * This program is distributed in the hope that it will be useful, but WITHOUT
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+ * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
+ * more details.
+ *
+ * You should have received a copy of the GNU General Public License along with
+ * this program; if not, write to the Free Software Foundation, Inc., 59
+ * Temple Place - Suite 330, Boston, MA 02111-1307, USA.
+ */
+
+/* Written by Philippe De Muyter <phdm@macqel.be>. */
+
+#include <time.h>
+
+static time_t mkgmtime(register struct tm *t)
+{
+ register short month, year;
+ register time_t result;
+ static int m_to_d[12] =
+ {0, 31, 59, 90, 120, 151, 181, 212, 243, 273, 304, 334};
+
+ month = t->tm_mon;
+ year = t->tm_year + month / 12 + 1900;
+ month %= 12;
+ if (month < 0) {
+ year -= 1;
+ month += 12;
+ }
+ result = (year - 1970) * 365 + m_to_d[month];
+ if (month <= 1)
+ year -= 1;
+ result += (year - 1968) / 4;
+ result -= (year - 1900) / 100;
+ result += (year - 1600) / 400;
+ result += t->tm_mday;
+ result -= 1;
+ result *= 24;
+ result += t->tm_hour;
+ result *= 60;
+ result += t->tm_min;
+ result *= 60;
+ result += t->tm_sec;
+ return (result);
+}
+
+/*
+ * mktime -- convert tm struct to time_t
+ * if tm_isdst >= 0 use it, else compute it
+ */
+
+time_t mktime(struct tm * t)
+{
+ time_t result;
+
+ tzset();
+ result = mkgmtime(t) + timezone;
+ if (t->tm_isdst > 0
+ || (t->tm_isdst < 0 && localtime(&result)->tm_isdst))
+ result -= 3600;
+ return (result);
+}
diff --git a/src/multinet.opt b/src/multinet.opt
new file mode 100644
index 0000000..97b420f
--- /dev/null
+++ b/src/multinet.opt
@@ -0,0 +1 @@
+multinet:multinet_socket_library/share
diff --git a/src/multinet_ucx.opt b/src/multinet_ucx.opt
new file mode 100644
index 0000000..51bac45
--- /dev/null
+++ b/src/multinet_ucx.opt
@@ -0,0 +1 @@
+multinet_root:[multinet.library]ucx$ipc/LIBRARY
diff --git a/src/parsdate.c b/src/parsdate.c
new file mode 100644
index 0000000..07bbc9d
--- /dev/null
+++ b/src/parsdate.c
@@ -0,0 +1,2425 @@
+/* original parser id follows */
+/* yysccsid[] = "@(#)yaccpar 1.9 (Berkeley) 02/21/93" */
+/* (use YYMAJOR/YYMINOR for ifdefs dependent on parser version) */
+
+#define YYBYACC 1
+#define YYMAJOR 2
+#define YYMINOR 0
+#define YYPATCH 20240109
+
+#define YYEMPTY (-1)
+#define yyclearin (yychar = YYEMPTY)
+#define yyerrok (yyerrflag = 0)
+#define YYRECOVERING() (yyerrflag != 0)
+#define YYENOMEM (-2)
+#define YYEOF 0
+#undef YYBTYACC
+#define YYBTYACC 0
+#define YYDEBUGSTR YYPREFIX "debug"
+#define YYPREFIX "yy"
+
+#define YYPURE 0
+
+#line 2 "./parsdate.y"
+
+#include <LYLeaks.h>
+
+/*
+ * $LynxId: parsdate.y,v 1.32 2024/01/15 11:09:08 tom Exp $
+ *
+ * This module is adapted and extended from tin, to use for LYmktime().
+ *
+ * Project : tin - a Usenet reader
+ * Module : parsedate.y
+ * Author : S. Bellovin, R. $alz, J. Berets, P. Eggert
+ * Created : 1990-08-01
+ * Updated : 2019-08-28 (by Thomas Dickey, for Lynx)
+ * Notes : This grammar has 8 shift/reduce conflicts.
+ *
+ * Originally written by Steven M. Bellovin <smb@research.att.com>
+ * while at the University of North Carolina at Chapel Hill.
+ * Later tweaked by a couple of people on Usenet. Completely
+ * overhauled by Rich $alz <rsalz@osf.org> and Jim Berets
+ * <jberets@bbn.com> in August, 1990.
+ *
+ * Further revised (removed obsolete constructs and cleaned up
+ * timezone names) in August, 1991, by Rich.
+ * Paul Eggert <eggert@twinsun.com> helped in September 1992.
+ * Roland Rosenfeld added MET DST code in April 1994.
+ *
+ * Revision : 1.13
+ * Copyright : This code is in the public domain and has no copyright.
+ */
+
+/* SUPPRESS 530 */ /* Empty body for statement */
+/* SUPPRESS 593 on yyerrlab */ /* Label was not used */
+/* SUPPRESS 593 on yynewstate */ /* Label was not used */
+/* SUPPRESS 595 on yypvt */ /* Automatic variable may be used before set */
+
+#undef alloca /* conflicting def may be set by yacc */
+#include <parsdate.h>
+
+/*
+** Get the number of elements in a fixed-size array, or a pointer just
+** past the end of it.
+*/
+#define ENDOF(array) (&array[ARRAY_SIZE(array)])
+
+#ifdef EBCDIC
+#define TO_ASCII(c) TOASCII(c)
+#define TO_LOCAL(c) FROMASCII(c)
+#else
+#define TO_ASCII(c) (c)
+#define TO_LOCAL(c) (c)
+#endif
+
+#define IS7BIT(x) ((unsigned) TO_ASCII(x) < 128)
+#define CTYPE(isXXXXX, c) (IS7BIT(c) && isXXXXX(((unsigned char)c)))
+
+typedef char *PD_STRING;
+
+extern int date_parse(void);
+
+#define yyparse date_parse
+#define yylex date_lex
+#define yyerror date_error
+
+#define BAD_TIME ((time_t)-1)
+#define isBadTime(n) ((n) != 0 && (((n) == BAD_TIME) || !((n) > 0)))
+
+ /* See the LeapYears table in Convert. */
+#define EPOCH 1970
+#define END_OF_TIME 2200
+
+ /* Constants for general time calculations. */
+#define DST_OFFSET 1
+#define SECSPERDAY (24L * 60L * 60L)
+ /* Readability for TABLE stuff. */
+#define HOUR(x) (x * 60)
+
+#define LPAREN '('
+#define RPAREN ')'
+
+/*
+** Daylight-savings mode: on, off, or not yet known.
+*/
+typedef enum _DSTMODE {
+ DSTon, DSToff, DSTmaybe
+} DSTMODE;
+
+/*
+** Meridian: am, pm, or 24-hour style.
+*/
+typedef enum _MERIDIAN {
+ MERam, MERpm, MER24
+} MERIDIAN;
+
+/*
+** Global variables. We could get rid of most of them by using a yacc
+** union, but this is more efficient. (This routine predates the
+** yacc %union construct.)
+*/
+static char *yyInput;
+static DSTMODE yyDSTmode;
+static int yyHaveDate;
+static int yyHaveRel;
+static int yyHaveTime;
+static time_t yyTimezone;
+static time_t yyDay;
+static time_t yyHour;
+static time_t yyMinutes;
+static time_t yyMonth;
+static time_t yySeconds;
+static time_t yyYear;
+static MERIDIAN yyMeridian;
+static time_t yyRelMonth;
+static time_t yyRelSeconds;
+
+static time_t ToSeconds(time_t, time_t, time_t, MERIDIAN);
+static time_t Convert(time_t, time_t, time_t, time_t, time_t, time_t,
+ MERIDIAN, DSTMODE);
+static time_t DSTcorrect(time_t, time_t);
+static time_t RelativeMonth(time_t, time_t);
+static int LookupWord(char *, int);
+static int date_lex(void);
+static int GetTimeInfo(TIMEINFO * Now);
+
+/*
+ * The 'date_error()' function is declared here to work around a defect in
+ * bison 1.22, which redefines 'const' further down in this file, making it
+ * impossible to put a prototype here, and the function later. We're using
+ * 'const' on the parameter to quiet gcc's -Wwrite-strings warning.
+ */
+/*ARGSUSED*/
+static void date_error(const char GCC_UNUSED *s)
+{
+ /*NOTREACHED */
+}
+
+#ifdef YYSTYPE
+#undef YYSTYPE_IS_DECLARED
+#define YYSTYPE_IS_DECLARED 1
+#endif
+#ifndef YYSTYPE_IS_DECLARED
+#define YYSTYPE_IS_DECLARED 1
+#line 139 "./parsdate.y"
+typedef union YYSTYPE {
+ time_t Number;
+ enum _MERIDIAN Meridian;
+} YYSTYPE;
+#endif /* !YYSTYPE_IS_DECLARED */
+#line 172 "y.tab.c"
+
+/* compatibility with bison */
+#ifdef YYPARSE_PARAM
+/* compatibility with FreeBSD */
+# ifdef YYPARSE_PARAM_TYPE
+# define YYPARSE_DECL() yyparse(YYPARSE_PARAM_TYPE YYPARSE_PARAM)
+# else
+# define YYPARSE_DECL() yyparse(void *YYPARSE_PARAM)
+# endif
+#else
+# define YYPARSE_DECL() yyparse(void)
+#endif
+
+/* Parameters sent to lex. */
+#ifdef YYLEX_PARAM
+# define YYLEX_DECL() yylex(void *YYLEX_PARAM)
+# define YYLEX yylex(YYLEX_PARAM)
+#else
+# define YYLEX_DECL() yylex(void)
+# define YYLEX yylex()
+#endif
+
+#if !(defined(yylex) || defined(YYSTATE))
+int YYLEX_DECL();
+#endif
+
+/* Parameters sent to yyerror. */
+#ifndef YYERROR_DECL
+#define YYERROR_DECL() yyerror(const char *s)
+#endif
+#ifndef YYERROR_CALL
+#define YYERROR_CALL(msg) yyerror(msg)
+#endif
+
+extern int YYPARSE_DECL();
+
+#define tDAY 257
+#define tDAYZONE 258
+#define tMERIDIAN 259
+#define tMONTH 260
+#define tMONTH_UNIT 261
+#define tSEC_UNIT 262
+#define tSNUMBER 263
+#define tUNUMBER 264
+#define tZONE 265
+#define tDST 266
+#define YYERRCODE 256
+typedef int YYINT;
+static const YYINT yylhs[] = { -1,
+ 0, 0, 4, 4, 4, 4, 4, 4, 5, 5,
+ 5, 5, 5, 2, 2, 2, 2, 2, 1, 6,
+ 6, 6, 6, 6, 6, 6, 6, 6, 7, 8,
+ 8, 8, 8, 3, 3,
+};
+static const YYINT yylen[] = { 2,
+ 0, 2, 1, 2, 1, 1, 2, 1, 2, 4,
+ 4, 6, 6, 1, 1, 2, 2, 1, 1, 3,
+ 5, 2, 4, 2, 3, 5, 6, 3, 9, 2,
+ 2, 2, 2, 0, 1,
+};
+static const YYINT yydefred[] = { 1,
+ 0, 0, 0, 0, 0, 2, 0, 5, 0, 8,
+ 0, 0, 0, 32, 30, 35, 0, 33, 31, 0,
+ 0, 0, 9, 0, 19, 0, 18, 4, 7, 0,
+ 0, 0, 25, 28, 0, 0, 16, 17, 0, 0,
+ 0, 23, 0, 11, 10, 0, 0, 26, 0, 0,
+ 21, 0, 27, 13, 12, 0, 0, 29,
+};
+#if defined(YYDESTRUCT_CALL) || defined(YYSTYPE_TOSTRING)
+static const YYINT yystos[] = { 0,
+ 268, 257, 260, 263, 264, 272, 273, 274, 275, 276,
+ 260, 44, 264, 261, 262, 259, 260, 261, 262, 263,
+ 58, 47, 271, 258, 263, 265, 269, 270, 270, 264,
+ 264, 44, 264, 263, 264, 264, 266, 269, 264, 260,
+ 45, 264, 58, 269, 271, 47, 58, 264, 260, 264,
+ 264, 264, 263, 269, 271, 58, 264, 264,
+};
+#endif /* YYDESTRUCT_CALL || YYSTYPE_TOSTRING */
+static const YYINT yydgoto[] = { 1,
+ 27, 28, 23, 6, 7, 8, 9, 10,
+};
+static const YYINT yysindex[] = { 0,
+ -240, -41, -256, -227, -45, 0, -251, 0, -251, 0,
+ -254, -249, -22, 0, 0, 0, -237, 0, 0, -235,
+ -228, -226, 0, -236, 0, -224, 0, 0, 0, -223,
+ -39, -222, 0, 0, -58, -7, 0, 0, -15, -220,
+ -215, 0, -218, 0, 0, -217, -216, 0, -214, -234,
+ 0, -8, 0, 0, 0, -213, -212, 0,
+};
+static const YYINT yyrindex[] = { 0,
+ 0, 0, 0, 0, 5, 0, 26, 0, 31, 0,
+ 0, 0, 11, 0, 0, 0, 37, 0, 0, 0,
+ 0, 0, 0, 16, 0, 32, 0, 0, 0, 0,
+ 0, 0, 0, 0, 1, 21, 0, 0, 0, 0,
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 1,
+ 0, 0, 0, 0, 0, 0, 0, 0,
+};
+#if YYBTYACC
+static const YYINT yycindex[] = { 0,
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+ 0, 0, 0, 0, 0, 0, 0, 0,
+};
+#endif
+static const YYINT yygindex[] = { 0,
+ -17, 44, -31, 0, 0, 0, 0, 0,
+};
+#define YYTABLESIZE 300
+static const YYINT yytable[] = { 43,
+ 34, 22, 12, 45, 34, 41, 24, 13, 38, 30,
+ 22, 25, 21, 26, 31, 15, 2, 44, 55, 3,
+ 20, 32, 4, 5, 16, 3, 33, 34, 25, 37,
+ 6, 14, 54, 14, 15, 35, 24, 36, 25, 46,
+ 39, 42, 47, 48, 49, 50, 51, 52, 53, 56,
+ 57, 58, 29, 0, 0, 0, 0, 0, 0, 0,
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+ 16, 0, 0, 0, 25, 0, 0, 0, 0, 0,
+ 0, 0, 0, 16, 17, 18, 19, 20, 11, 0,
+ 40, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+ 0, 0, 0, 0, 0, 0, 0, 34, 34, 0,
+ 34, 34, 34, 0, 34, 34, 0, 22, 34, 34,
+ 22, 0, 15, 22, 22, 15, 0, 20, 15, 15,
+ 20, 0, 3, 20, 20, 3, 0, 6, 14, 3,
+ 6, 14, 0, 24, 6, 14, 24, 0, 0, 24,
+};
+static const YYINT yycheck[] = { 58,
+ 0, 47, 44, 35, 0, 45, 258, 264, 26, 264,
+ 0, 263, 58, 265, 264, 0, 257, 35, 50, 260,
+ 0, 44, 263, 264, 259, 0, 264, 263, 263, 266,
+ 0, 0, 50, 261, 262, 264, 0, 264, 263, 47,
+ 264, 264, 58, 264, 260, 264, 264, 264, 263, 58,
+ 264, 264, 9, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
+ 259, -1, -1, -1, 263, -1, -1, -1, -1, -1,
+ -1, -1, -1, 259, 260, 261, 262, 263, 260, -1,
+ 260, -1, -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, 257, 258, -1,
+ 260, 257, 258, -1, 264, 265, -1, 257, 264, 265,
+ 260, -1, 257, 263, 264, 260, -1, 257, 263, 264,
+ 260, -1, 257, 263, 264, 260, -1, 257, 257, 264,
+ 260, 260, -1, 257, 264, 264, 260, -1, -1, 263,
+};
+#if YYBTYACC
+static const YYINT yyctable[] = { -1,
+ -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
+ -1, -1, -1, -1, -1, -1, -1, -1, -1,
+};
+#endif
+#define YYFINAL 1
+#ifndef YYDEBUG
+#define YYDEBUG 0
+#endif
+#define YYMAXTOKEN 266
+#define YYUNDFTOKEN 277
+#define YYTRANSLATE(a) ((a) > YYMAXTOKEN ? YYUNDFTOKEN : (a))
+#if YYDEBUG
+static const char *const yyname[] = {
+
+"$end",0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
+0,0,0,0,0,0,0,"','","'-'",0,"'/'",0,0,0,0,0,0,0,0,0,0,"':'",0,0,0,0,0,0,0,0,0,0,
+0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
+0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
+0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
+0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
+0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,"error","tDAY","tDAYZONE",
+"tMERIDIAN","tMONTH","tMONTH_UNIT","tSEC_UNIT","tSNUMBER","tUNUMBER","tZONE",
+"tDST","$accept","spec","numzone","zone","o_merid","item","time","date","both",
+"rel","illegal-symbol",
+};
+static const char *const yyrule[] = {
+"$accept : spec",
+"spec :",
+"spec : spec item",
+"item : time",
+"item : time zone",
+"item : date",
+"item : both",
+"item : both zone",
+"item : rel",
+"time : tUNUMBER o_merid",
+"time : tUNUMBER ':' tUNUMBER o_merid",
+"time : tUNUMBER ':' tUNUMBER numzone",
+"time : tUNUMBER ':' tUNUMBER ':' tUNUMBER o_merid",
+"time : tUNUMBER ':' tUNUMBER ':' tUNUMBER numzone",
+"zone : tZONE",
+"zone : tDAYZONE",
+"zone : tDAYZONE tDST",
+"zone : tZONE numzone",
+"zone : numzone",
+"numzone : tSNUMBER",
+"date : tUNUMBER '/' tUNUMBER",
+"date : tUNUMBER '/' tUNUMBER '/' tUNUMBER",
+"date : tMONTH tUNUMBER",
+"date : tMONTH tUNUMBER ',' tUNUMBER",
+"date : tUNUMBER tMONTH",
+"date : tUNUMBER tMONTH tUNUMBER",
+"date : tDAY ',' tUNUMBER tMONTH tUNUMBER",
+"date : tDAY ',' tUNUMBER '-' tMONTH tSNUMBER",
+"date : tUNUMBER tSNUMBER tSNUMBER",
+"both : tDAY tMONTH tUNUMBER tUNUMBER ':' tUNUMBER ':' tUNUMBER tUNUMBER",
+"rel : tSNUMBER tSEC_UNIT",
+"rel : tUNUMBER tSEC_UNIT",
+"rel : tSNUMBER tMONTH_UNIT",
+"rel : tUNUMBER tMONTH_UNIT",
+"o_merid :",
+"o_merid : tMERIDIAN",
+
+};
+#endif
+
+#if YYDEBUG
+int yydebug;
+#endif
+
+int yyerrflag;
+int yychar;
+YYSTYPE yyval;
+YYSTYPE yylval;
+int yynerrs;
+
+#if defined(YYLTYPE) || defined(YYLTYPE_IS_DECLARED)
+YYLTYPE yyloc; /* position returned by actions */
+YYLTYPE yylloc; /* position from the lexer */
+#endif
+
+#if defined(YYLTYPE) || defined(YYLTYPE_IS_DECLARED)
+#ifndef YYLLOC_DEFAULT
+#define YYLLOC_DEFAULT(loc, rhs, n) \
+do \
+{ \
+ if (n == 0) \
+ { \
+ (loc).first_line = YYRHSLOC(rhs, 0).last_line; \
+ (loc).first_column = YYRHSLOC(rhs, 0).last_column; \
+ (loc).last_line = YYRHSLOC(rhs, 0).last_line; \
+ (loc).last_column = YYRHSLOC(rhs, 0).last_column; \
+ } \
+ else \
+ { \
+ (loc).first_line = YYRHSLOC(rhs, 1).first_line; \
+ (loc).first_column = YYRHSLOC(rhs, 1).first_column; \
+ (loc).last_line = YYRHSLOC(rhs, n).last_line; \
+ (loc).last_column = YYRHSLOC(rhs, n).last_column; \
+ } \
+} while (0)
+#endif /* YYLLOC_DEFAULT */
+#endif /* defined(YYLTYPE) || defined(YYLTYPE_IS_DECLARED) */
+#if YYBTYACC
+
+#ifndef YYLVQUEUEGROWTH
+#define YYLVQUEUEGROWTH 32
+#endif
+#endif /* YYBTYACC */
+
+/* define the initial stack-sizes */
+#ifdef YYSTACKSIZE
+#undef YYMAXDEPTH
+#define YYMAXDEPTH YYSTACKSIZE
+#else
+#ifdef YYMAXDEPTH
+#define YYSTACKSIZE YYMAXDEPTH
+#else
+#define YYSTACKSIZE 10000
+#define YYMAXDEPTH 10000
+#endif
+#endif
+
+#ifndef YYINITSTACKSIZE
+#define YYINITSTACKSIZE 200
+#endif
+
+typedef struct {
+ unsigned stacksize;
+ YYINT *s_base;
+ YYINT *s_mark;
+ YYINT *s_last;
+ YYSTYPE *l_base;
+ YYSTYPE *l_mark;
+#if defined(YYLTYPE) || defined(YYLTYPE_IS_DECLARED)
+ YYLTYPE *p_base;
+ YYLTYPE *p_mark;
+#endif
+} YYSTACKDATA;
+#if YYBTYACC
+
+struct YYParseState_s
+{
+ struct YYParseState_s *save; /* Previously saved parser state */
+ YYSTACKDATA yystack; /* saved parser stack */
+ int state; /* saved parser state */
+ int errflag; /* saved error recovery status */
+ int lexeme; /* saved index of the conflict lexeme in the lexical queue */
+ YYINT ctry; /* saved index in yyctable[] for this conflict */
+};
+typedef struct YYParseState_s YYParseState;
+#endif /* YYBTYACC */
+/* variables for the parser stack */
+static YYSTACKDATA yystack;
+#if YYBTYACC
+
+/* Current parser state */
+static YYParseState *yyps = 0;
+
+/* yypath != NULL: do the full parse, starting at *yypath parser state. */
+static YYParseState *yypath = 0;
+
+/* Base of the lexical value queue */
+static YYSTYPE *yylvals = 0;
+
+/* Current position at lexical value queue */
+static YYSTYPE *yylvp = 0;
+
+/* End position of lexical value queue */
+static YYSTYPE *yylve = 0;
+
+/* The last allocated position at the lexical value queue */
+static YYSTYPE *yylvlim = 0;
+
+#if defined(YYLTYPE) || defined(YYLTYPE_IS_DECLARED)
+/* Base of the lexical position queue */
+static YYLTYPE *yylpsns = 0;
+
+/* Current position at lexical position queue */
+static YYLTYPE *yylpp = 0;
+
+/* End position of lexical position queue */
+static YYLTYPE *yylpe = 0;
+
+/* The last allocated position at the lexical position queue */
+static YYLTYPE *yylplim = 0;
+#endif
+
+/* Current position at lexical token queue */
+static YYINT *yylexp = 0;
+
+static YYINT *yylexemes = 0;
+#endif /* YYBTYACC */
+#line 361 "./parsdate.y"
+
+
+/*
+** An entry in the lexical lookup table.
+*/
+/* *INDENT-OFF* */
+typedef struct _TABLE {
+ const char *name;
+ int type;
+ time_t value;
+} TABLE;
+
+/* Month and day table. */
+static const TABLE MonthDayTable[] = {
+ { "january", tMONTH, 1 },
+ { "february", tMONTH, 2 },
+ { "march", tMONTH, 3 },
+ { "april", tMONTH, 4 },
+ { "may", tMONTH, 5 },
+ { "june", tMONTH, 6 },
+ { "july", tMONTH, 7 },
+ { "august", tMONTH, 8 },
+ { "september", tMONTH, 9 },
+ { "october", tMONTH, 10 },
+ { "november", tMONTH, 11 },
+ { "december", tMONTH, 12 },
+ /* The value of the day isn't used... */
+ { "sunday", tDAY, 0 },
+ { "monday", tDAY, 0 },
+ { "tuesday", tDAY, 0 },
+ { "wednesday", tDAY, 0 },
+ { "thursday", tDAY, 0 },
+ { "friday", tDAY, 0 },
+ { "saturday", tDAY, 0 },
+};
+
+/* Time units table. */
+static const TABLE UnitsTable[] = {
+ { "year", tMONTH_UNIT, 12 },
+ { "month", tMONTH_UNIT, 1 },
+ { "week", tSEC_UNIT, 7 * 24 * 60 * 60 },
+ { "day", tSEC_UNIT, 1 * 24 * 60 * 60 },
+ { "hour", tSEC_UNIT, 60 * 60 },
+ { "minute", tSEC_UNIT, 60 },
+ { "min", tSEC_UNIT, 60 },
+ { "second", tSEC_UNIT, 1 },
+ { "sec", tSEC_UNIT, 1 },
+};
+
+/* Timezone table. */
+static const TABLE TimezoneTable[] = {
+ { "gmt", tZONE, HOUR( 0) }, /* Greenwich Mean */
+ { "ut", tZONE, HOUR( 0) }, /* Universal */
+ { "utc", tZONE, HOUR( 0) }, /* Universal Coordinated */
+ { "cut", tZONE, HOUR( 0) }, /* Coordinated Universal */
+ { "z", tZONE, HOUR( 0) }, /* Greenwich Mean */
+ { "wet", tZONE, HOUR( 0) }, /* Western European */
+ { "bst", tDAYZONE, HOUR( 0) }, /* British Summer */
+ { "nst", tZONE, HOUR(3)+30 }, /* Newfoundland Standard */
+ { "ndt", tDAYZONE, HOUR(3)+30 }, /* Newfoundland Daylight */
+ { "ast", tZONE, HOUR( 4) }, /* Atlantic Standard */
+ { "adt", tDAYZONE, HOUR( 4) }, /* Atlantic Daylight */
+ { "est", tZONE, HOUR( 5) }, /* Eastern Standard */
+ { "edt", tDAYZONE, HOUR( 5) }, /* Eastern Daylight */
+ { "cst", tZONE, HOUR( 6) }, /* Central Standard */
+ { "cdt", tDAYZONE, HOUR( 6) }, /* Central Daylight */
+ { "mst", tZONE, HOUR( 7) }, /* Mountain Standard */
+ { "mdt", tDAYZONE, HOUR( 7) }, /* Mountain Daylight */
+ { "pst", tZONE, HOUR( 8) }, /* Pacific Standard */
+ { "pdt", tDAYZONE, HOUR( 8) }, /* Pacific Daylight */
+ { "yst", tZONE, HOUR( 9) }, /* Yukon Standard */
+ { "ydt", tDAYZONE, HOUR( 9) }, /* Yukon Daylight */
+ { "akst", tZONE, HOUR( 9) }, /* Alaska Standard */
+ { "akdt", tDAYZONE, HOUR( 9) }, /* Alaska Daylight */
+ { "hst", tZONE, HOUR(10) }, /* Hawaii Standard */
+ { "hast", tZONE, HOUR(10) }, /* Hawaii-Aleutian Standard */
+ { "hadt", tDAYZONE, HOUR(10) }, /* Hawaii-Aleutian Daylight */
+ { "ces", tDAYZONE, -HOUR(1) }, /* Central European Summer */
+ { "cest", tDAYZONE, -HOUR(1) }, /* Central European Summer */
+ { "mez", tZONE, -HOUR(1) }, /* Middle European */
+ { "mezt", tDAYZONE, -HOUR(1) }, /* Middle European Summer */
+ { "cet", tZONE, -HOUR(1) }, /* Central European */
+ { "met", tZONE, -HOUR(1) }, /* Middle European */
+/* Additional aliases for MET / MET DST *************************************/
+ { "mez", tZONE, -HOUR(1) }, /* Middle European */
+ { "mewt", tZONE, -HOUR(1) }, /* Middle European Winter */
+ { "mest", tDAYZONE, -HOUR(1) }, /* Middle European Summer */
+ { "mes", tDAYZONE, -HOUR(1) }, /* Middle European Summer */
+ { "mesz", tDAYZONE, -HOUR(1) }, /* Middle European Summer */
+ { "msz", tDAYZONE, -HOUR(1) }, /* Middle European Summer */
+ { "metdst", tDAYZONE, -HOUR(1) }, /* Middle European Summer */
+/****************************************************************************/
+ { "eet", tZONE, -HOUR(2) }, /* Eastern Europe */
+ { "msk", tZONE, -HOUR(3) }, /* Moscow Winter */
+ { "msd", tDAYZONE, -HOUR(3) }, /* Moscow Summer */
+ { "wast", tZONE, -HOUR(8) }, /* West Australian Standard */
+ { "wadt", tDAYZONE, -HOUR(8) }, /* West Australian Daylight */
+ { "hkt", tZONE, -HOUR(8) }, /* Hong Kong */
+ { "cct", tZONE, -HOUR(8) }, /* China Coast */
+ { "jst", tZONE, -HOUR(9) }, /* Japan Standard */
+ { "kst", tZONE, -HOUR(9) }, /* Korean Standard */
+ { "kdt", tZONE, -HOUR(9) }, /* Korean Daylight */
+ { "cast", tZONE, -(HOUR(9)+30) }, /* Central Australian Standard */
+ { "cadt", tDAYZONE, -(HOUR(9)+30) }, /* Central Australian Daylight */
+ { "east", tZONE, -HOUR(10) }, /* Eastern Australian Standard */
+ { "eadt", tDAYZONE, -HOUR(10) }, /* Eastern Australian Daylight */
+ { "nzst", tZONE, -HOUR(12) }, /* New Zealand Standard */
+ { "nzdt", tDAYZONE, -HOUR(12) }, /* New Zealand Daylight */
+
+ /* For completeness we include the following entries. */
+#if 0
+
+ /* Duplicate names. Either they conflict with a zone listed above
+ * (which is either more likely to be seen or just been in circulation
+ * longer), or they conflict with another zone in this section and
+ * we could not reasonably choose one over the other. */
+ { "fst", tZONE, HOUR( 2) }, /* Fernando De Noronha Standard */
+ { "fdt", tDAYZONE, HOUR( 2) }, /* Fernando De Noronha Daylight */
+ { "bst", tZONE, HOUR( 3) }, /* Brazil Standard */
+ { "est", tZONE, HOUR( 3) }, /* Eastern Standard (Brazil) */
+ { "edt", tDAYZONE, HOUR( 3) }, /* Eastern Daylight (Brazil) */
+ { "wst", tZONE, HOUR( 4) }, /* Western Standard (Brazil) */
+ { "wdt", tDAYZONE, HOUR( 4) }, /* Western Daylight (Brazil) */
+ { "cst", tZONE, HOUR( 5) }, /* Chile Standard */
+ { "cdt", tDAYZONE, HOUR( 5) }, /* Chile Daylight */
+ { "ast", tZONE, HOUR( 5) }, /* Acre Standard */
+ { "adt", tDAYZONE, HOUR( 5) }, /* Acre Daylight */
+ { "cst", tZONE, HOUR( 5) }, /* Cuba Standard */
+ { "cdt", tDAYZONE, HOUR( 5) }, /* Cuba Daylight */
+ { "est", tZONE, HOUR( 6) }, /* Easter Island Standard */
+ { "edt", tDAYZONE, HOUR( 6) }, /* Easter Island Daylight */
+ { "sst", tZONE, HOUR(11) }, /* Samoa Standard */
+ { "ist", tZONE, -HOUR(2) }, /* Israel Standard */
+ { "idt", tDAYZONE, -HOUR(2) }, /* Israel Daylight */
+ { "idt", tDAYZONE, -(HOUR(3)+30) }, /* Iran Daylight */
+ { "ist", tZONE, -(HOUR(3)+30) }, /* Iran Standard */
+ { "cst", tZONE, -HOUR(8) }, /* China Standard */
+ { "cdt", tDAYZONE, -HOUR(8) }, /* China Daylight */
+ { "sst", tZONE, -HOUR(8) }, /* Singapore Standard */
+
+ /* Dubious (e.g., not in Olson's TIMEZONE package) or obsolete. */
+ { "gst", tZONE, HOUR( 3) }, /* Greenland Standard */
+ { "wat", tZONE, -HOUR(1) }, /* West Africa */
+ { "at", tZONE, HOUR( 2) }, /* Azores */
+ { "gst", tZONE, -HOUR(10) }, /* Guam Standard */
+ { "nft", tZONE, HOUR(3)+30 }, /* Newfoundland */
+ { "idlw", tZONE, HOUR(12) }, /* International Date Line West */
+ { "mewt", tZONE, -HOUR(1) }, /* Middle European Winter */
+ { "mest", tDAYZONE, -HOUR(1) }, /* Middle European Summer */
+ { "swt", tZONE, -HOUR(1) }, /* Swedish Winter */
+ { "sst", tDAYZONE, -HOUR(1) }, /* Swedish Summer */
+ { "fwt", tZONE, -HOUR(1) }, /* French Winter */
+ { "fst", tDAYZONE, -HOUR(1) }, /* French Summer */
+ { "bt", tZONE, -HOUR(3) }, /* Baghdad */
+ { "it", tZONE, -(HOUR(3)+30) }, /* Iran */
+ { "zp4", tZONE, -HOUR(4) }, /* USSR Zone 3 */
+ { "zp5", tZONE, -HOUR(5) }, /* USSR Zone 4 */
+ { "ist", tZONE, -(HOUR(5)+30) }, /* Indian Standard */
+ { "zp6", tZONE, -HOUR(6) }, /* USSR Zone 5 */
+ { "nst", tZONE, -HOUR(7) }, /* North Sumatra */
+ { "sst", tZONE, -HOUR(7) }, /* South Sumatra */
+ { "jt", tZONE, -(HOUR(7)+30) }, /* Java (3pm in Cronusland!) */
+ { "nzt", tZONE, -HOUR(12) }, /* New Zealand */
+ { "idle", tZONE, -HOUR(12) }, /* International Date Line East */
+ { "cat", tZONE, HOUR(10) }, /* -- expired 1967 */
+ { "nt", tZONE, HOUR(11) }, /* -- expired 1967 */
+ { "ahst", tZONE, HOUR(10) }, /* -- expired 1983 */
+ { "hdt", tDAYZONE, HOUR(10) }, /* -- expired 1986 */
+#endif /* 0 */
+};
+/* *INDENT-ON* */
+
+static time_t ToSeconds(time_t Hours, time_t Minutes, time_t Seconds, MERIDIAN Meridian)
+{
+ if (isBadTime(Minutes) || Minutes > 59 || isBadTime(Seconds) || Seconds > 61)
+ return BAD_TIME;
+ if (Meridian == MER24) {
+ if (isBadTime(Hours) || Hours > 23)
+ return BAD_TIME;
+ } else {
+ if (Hours < 1 || Hours > 12)
+ return BAD_TIME;
+ if (Hours == 12)
+ Hours = 0;
+ if (Meridian == MERpm)
+ Hours += 12;
+ }
+ return (Hours * 60L + Minutes) * 60L + Seconds;
+}
+
+static time_t Convert(time_t Month, time_t Day, time_t Year, time_t Hours,
+ time_t Minutes, time_t Seconds, MERIDIAN Meridian,
+ DSTMODE dst)
+{
+ static const int DaysNormal[13] =
+ {
+ 0, 31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31
+ };
+ static const int DaysLeap[13] =
+ {
+ 0, 31, 29, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31
+ };
+ /* *INDENT-OFF* */
+ static const int LeapYears[] =
+ {
+ 1972, 1976, 1980, 1984, 1988, 1992, 1996,
+ 2000, 2004, 2008, 2012, 2016, 2020, 2024, 2028, 2032, 2036,
+ 2040, 2044, 2048, 2052, 2056, 2060, 2064, 2068, 2072, 2076,
+ 2080, 2084, 2088, 2092, 2096, /***/ 2104, 2108, 2112, 2116,
+ 2120, 2124, 2128, 2132, 2136, 2140, 2144, 2148, 2152, 2156,
+ 2160, 2164, 2168, 2172, 2176, 2180, 2184, 2188, 2192, 2196,
+ };
+ /* *INDENT-ON* */
+
+ const int *yp;
+ const int *mp;
+ int i;
+ time_t Julian;
+ time_t tod;
+
+ if (isBadTime(Year))
+ Year = -Year;
+ if (Year < 70)
+ Year += 2000;
+ if (Year < 100)
+ Year += 1900;
+ if (Year < EPOCH)
+ Year += 100;
+ for (mp = DaysNormal, yp = LeapYears; yp < ENDOF(LeapYears); yp++)
+ if (Year == *yp) {
+ mp = DaysLeap;
+ break;
+ }
+ if (Year < EPOCH || Year > END_OF_TIME
+ || Month < 1 || Month > 12
+ /* NOSTRICT */
+ /* conversion from long may lose accuracy */
+ || Day < 1 || Day > mp[(int) Month]) {
+ return BAD_TIME;
+ }
+
+ Julian = Day - 1 + (Year - EPOCH) * 365;
+ for (yp = LeapYears; yp < ENDOF(LeapYears); yp++, Julian++) {
+ if (Year <= *yp)
+ break;
+ }
+ for (i = 1; i < Month; i++)
+ Julian += *++mp;
+ Julian *= SECSPERDAY;
+ Julian += yyTimezone * 60L;
+ tod = ToSeconds(Hours, Minutes, Seconds, Meridian);
+ if (isBadTime(tod)) {
+ return BAD_TIME;
+ }
+ Julian += tod;
+ tod = Julian;
+ if (dst == DSTon) {
+ Julian -= DST_OFFSET * 60 * 60;
+ } else if (dst == DSTmaybe) {
+ struct tm *tm = localtime(&tod);
+
+ if (tm != NULL) {
+ if (tm->tm_isdst)
+ Julian -= DST_OFFSET * 60 * 60;
+ } else {
+ Julian = BAD_TIME;
+ }
+ }
+ return Julian;
+}
+
+static time_t DSTcorrect(time_t Start, time_t Future)
+{
+ struct tm *tm;
+ time_t StartDay;
+ time_t FutureDay;
+
+ if ((tm = localtime(&Start)) == NULL)
+ return BAD_TIME;
+ StartDay = (tm->tm_hour + 1) % 24;
+
+ if ((tm = localtime(&Future)) == NULL)
+ return BAD_TIME;
+ FutureDay = (tm->tm_hour + 1) % 24;
+
+ return (Future - Start) + (StartDay - FutureDay) * DST_OFFSET * 60 * 60;
+}
+
+static time_t RelativeMonth(time_t Start, time_t RelMonth)
+{
+ struct tm *tm;
+ time_t Month;
+ time_t Year;
+
+ if ((tm = localtime(&Start)) == NULL)
+ return BAD_TIME;
+
+ Month = 12 * tm->tm_year + tm->tm_mon + RelMonth;
+ Year = Month / 12 + 1900;
+ Month = Month % 12 + 1;
+ return DSTcorrect(Start,
+ Convert(Month, (time_t) tm->tm_mday, Year,
+ (time_t) tm->tm_hour, (time_t) tm->tm_min,
+ (time_t) tm->tm_sec,
+ MER24, DSTmaybe));
+}
+
+static int LookupWord(char *buff,
+ int length)
+{
+ char *p;
+ const char *q;
+ const TABLE *tp;
+ int c;
+
+ p = buff;
+ c = p[0];
+
+ /* See if we have an abbreviation for a month. */
+ if (length == 3 || (length == 4 && p[3] == '.')) {
+ for (tp = MonthDayTable; tp < ENDOF(MonthDayTable); tp++) {
+ q = tp->name;
+ if (c == q[0] && p[1] == q[1] && p[2] == q[2]) {
+ yylval.Number = tp->value;
+ return tp->type;
+ }
+ }
+ } else {
+ for (tp = MonthDayTable; tp < ENDOF(MonthDayTable); tp++) {
+ if (c == tp->name[0] && strcmp(p, tp->name) == 0) {
+ yylval.Number = tp->value;
+ return tp->type;
+ }
+ }
+ }
+
+ /* Try for a timezone. */
+ for (tp = TimezoneTable; tp < ENDOF(TimezoneTable); tp++) {
+ if (c == tp->name[0] && p[1] == tp->name[1]
+ && strcmp(p, tp->name) == 0) {
+ yylval.Number = tp->value;
+ return tp->type;
+ }
+ }
+
+ if (strcmp(buff, "dst") == 0)
+ return tDST;
+
+ /* Try the units table. */
+ for (tp = UnitsTable; tp < ENDOF(UnitsTable); tp++) {
+ if (c == tp->name[0] && strcmp(p, tp->name) == 0) {
+ yylval.Number = tp->value;
+ return tp->type;
+ }
+ }
+
+ /* Strip off any plural and try the units table again. */
+ if (--length > 0 && p[length] == 's') {
+ p[length] = '\0';
+ for (tp = UnitsTable; tp < ENDOF(UnitsTable); tp++) {
+ if (c == tp->name[0] && strcmp(p, tp->name) == 0) {
+ p[length] = 's';
+ yylval.Number = tp->value;
+ return tp->type;
+ }
+ }
+ p[length] = 's';
+ }
+ length++;
+
+ /* Drop out any periods. */
+ for (p = buff, q = (PD_STRING) buff; *q; q++) {
+ if (*q != '.')
+ *p++ = *q;
+ }
+ *p = '\0';
+
+ /* Try the meridians. */
+ if (buff[1] == 'm' && buff[2] == '\0') {
+ if (buff[0] == 'a') {
+ yylval.Meridian = MERam;
+ return tMERIDIAN;
+ }
+ if (buff[0] == 'p') {
+ yylval.Meridian = MERpm;
+ return tMERIDIAN;
+ }
+ }
+
+ /* If we saw any periods, try the timezones again. */
+ if (p - buff != length) {
+ c = buff[0];
+ for (p = buff, tp = TimezoneTable; tp < ENDOF(TimezoneTable); tp++) {
+ if (c == tp->name[0] && p[1] == tp->name[1]
+ && strcmp(p, tp->name) == 0) {
+ yylval.Number = tp->value;
+ return tp->type;
+ }
+ }
+ }
+
+ /* Unknown word -- assume GMT timezone. */
+ yylval.Number = 0;
+ return tZONE;
+}
+
+/*
+ * This returns characters as-is (the ones that are not part of some token),
+ * and codes greater than 256 (the token values).
+ *
+ * yacc generates tables that may use the character value. In particular,
+ * byacc's yycheck[] table contains integer values for the expected codes from
+ * this function, which (unless byacc is run locally) are ASCII codes.
+ *
+ * The TO_LOCAL() function assumes its input is in ASCII, and the output is
+ * whatever native encoding is used on the machine, e.g., EBCDIC.
+ *
+ * The TO_ASCII() function is the inverse of TO_LOCAL().
+ */
+static int date_lex(void)
+{
+ int c;
+ char *p;
+ char buff[20];
+ int sign;
+ int i;
+ int nesting;
+
+ /* Get first character after the whitespace. */
+ for (;;) {
+ while (CTYPE(isspace, *yyInput))
+ yyInput++;
+ c = *yyInput;
+
+ /* Ignore RFC 822 comments, typically time zone names. */
+ if (c != LPAREN)
+ break;
+ for (nesting = 1;
+ (c = *++yyInput) != RPAREN || --nesting;
+ ) {
+ if (c == LPAREN) {
+ nesting++;
+ } else if (!IS7BIT(c) || c == '\0' || c == '\r'
+ || (c == '\\'
+ && ((c = *++yyInput) == '\0'
+ || !IS7BIT(c)))) {
+ /* Lexical error: bad comment. */
+ return '?';
+ }
+ }
+ yyInput++;
+ }
+
+ /* A number? */
+ if (CTYPE(isdigit, c) || c == '-' || c == '+') {
+ if (c == '-' || c == '+') {
+ sign = c == '-' ? -1 : 1;
+ yyInput++;
+ if (!CTYPE(isdigit, *yyInput)) {
+ /* Return the isolated plus or minus sign. */
+ --yyInput;
+ return *yyInput++;
+ }
+ } else {
+ sign = 0;
+ }
+ for (p = buff;
+ (c = *yyInput++) != '\0' && CTYPE(isdigit, c);
+ ) {
+ if (p < &buff[sizeof buff - 1])
+ *p++ = (char) c;
+ }
+ *p = '\0';
+ i = atoi(buff);
+
+ yyInput--;
+ yylval.Number = sign < 0 ? -i : i;
+ return sign ? tSNUMBER : tUNUMBER;
+ }
+
+ /* A word? */
+ if (CTYPE(isalpha, c)) {
+ for (p = buff;
+ (c = *yyInput++) == '.' || CTYPE(isalpha, c);
+ ) {
+ if (p < &buff[sizeof buff - 1])
+ *p++ = (char) (CTYPE(isupper, c) ? tolower(c) : c);
+ }
+ *p = '\0';
+ yyInput--;
+ return LookupWord(buff, (int) (p - buff));
+ }
+
+ return *yyInput++;
+}
+
+static int GetTimeInfo(TIMEINFO * Now)
+{
+ static time_t LastTime;
+ static long LastTzone;
+ struct tm *tm;
+
+#if defined(HAVE_GETTIMEOFDAY)
+ struct timeval tv;
+#endif /* defined(HAVE_GETTIMEOFDAY) */
+#if defined(DONT_HAVE_TM_GMTOFF)
+ struct tm local;
+ struct tm gmt;
+#endif /* !defined(DONT_HAVE_TM_GMTOFF) */
+
+ /* Get the basic time. */
+ memset(Now, 0, sizeof(TIMEINFO));
+#if defined(HAVE_GETTIMEOFDAY)
+ if (gettimeofday(&tv, (struct timezone *) NULL) == -1)
+ return -1;
+ Now->time = tv.tv_sec;
+ Now->usec = tv.tv_usec;
+#else
+ /* Can't check for -1 since that might be a time, I guess. */
+ (void) time(&Now->time);
+ Now->usec = 0;
+#endif /* defined(HAVE_GETTIMEOFDAY) */
+
+ /* Now get the timezone if it's been an hour since the last time. */
+ if (Now->time - LastTime > 60 * 60) {
+ LastTime = Now->time;
+ if ((tm = localtime(&Now->time)) == NULL)
+ return -1;
+#if defined(DONT_HAVE_TM_GMTOFF)
+ /* To get the timezone, compare localtime with GMT. */
+ local = *tm;
+ if ((tm = gmtime(&Now->time)) == NULL)
+ return -1;
+ gmt = *tm;
+
+ /* Assume we are never more than 24 hours away. */
+ LastTzone = gmt.tm_yday - local.tm_yday;
+ if (LastTzone > 1)
+ LastTzone = -24;
+ else if (LastTzone < -1)
+ LastTzone = 24;
+ else
+ LastTzone *= 24;
+
+ /* Scale in the hours and minutes; ignore seconds. */
+ LastTzone += gmt.tm_hour - local.tm_hour;
+ LastTzone *= 60;
+ LastTzone += gmt.tm_min - local.tm_min;
+#else
+ LastTzone = (0 - tm->tm_gmtoff) / 60;
+#endif /* defined(DONT_HAVE_TM_GMTOFF) */
+ }
+ Now->tzone = LastTzone;
+ return 0;
+}
+
+#if defined(YYBYACC) && defined(YYPURE) && defined(LY_FIND_LEAKS)
+#undef YYPURE
+#define YYPURE 1
+static void yyfreestack(YYSTACKDATA *);
+static void parsedate_leaks(void)
+{
+ yyfreestack(&yystack);
+}
+#endif
+
+time_t parsedate(char *p,
+ TIMEINFO * now)
+{
+ struct tm *tm;
+ TIMEINFO ti;
+ time_t Start;
+
+#if defined(YYBYACC) && defined(YYPURE) && defined(LY_FIND_LEAKS)
+ static int initialized;
+
+ if (!initialized) {
+ initialized = 1;
+ atexit(parsedate_leaks);
+ }
+#endif
+
+ yyInput = p;
+ if (now == NULL) {
+ now = &ti;
+ (void) GetTimeInfo(&ti);
+ }
+
+ if ((tm = localtime(&now->time)) == NULL)
+ return BAD_TIME;
+
+ /* *INDENT-EQLS* */
+ yyYear = tm->tm_year + 1900;
+ yyMonth = tm->tm_mon + 1;
+ yyDay = tm->tm_mday;
+ yyTimezone = now->tzone;
+ if (tm->tm_isdst) /* Correct timezone offset for DST */
+ yyTimezone += DST_OFFSET * 60;
+ yyDSTmode = DSTmaybe;
+ yyHour = 0;
+ yyMinutes = 0;
+ yySeconds = 0;
+ yyMeridian = MER24;
+ yyRelSeconds = 0;
+ yyRelMonth = 0;
+ yyHaveDate = 0;
+ yyHaveRel = 0;
+ yyHaveTime = 0;
+
+ if (date_parse() || yyHaveTime > 1 || yyHaveDate > 1)
+ return BAD_TIME;
+
+ if (yyHaveDate || yyHaveTime) {
+ Start = Convert(yyMonth, yyDay, yyYear, yyHour, yyMinutes, yySeconds,
+ yyMeridian, yyDSTmode);
+ if (isBadTime(Start))
+ return BAD_TIME;
+ } else {
+ Start = now->time;
+ if (!yyHaveRel)
+ Start -= (tm->tm_hour * 60L + tm->tm_min) * 60L + tm->tm_sec;
+ }
+
+ Start += yyRelSeconds;
+ if (yyRelMonth)
+ Start += RelativeMonth(Start, yyRelMonth);
+
+ /* Have to do *something* with a legitimate -1 so it's distinguishable
+ * from the error return value. (Alternately could set errno on error.) */
+ return (Start == BAD_TIME) ? 0 : Start;
+}
+#line 1203 "y.tab.c"
+
+/* For use in generated program */
+#define yydepth (int)(yystack.s_mark - yystack.s_base)
+#if YYBTYACC
+#define yytrial (yyps->save)
+#endif /* YYBTYACC */
+
+#if YYDEBUG
+#include <stdio.h> /* needed for printf */
+#endif
+
+#include <stdlib.h> /* needed for malloc, etc */
+#include <string.h> /* needed for memset */
+
+/* allocate initial stack or double stack size, up to YYMAXDEPTH */
+static int yygrowstack(YYSTACKDATA *data)
+{
+ int i;
+ unsigned newsize;
+ YYINT *newss;
+ YYSTYPE *newvs;
+#if defined(YYLTYPE) || defined(YYLTYPE_IS_DECLARED)
+ YYLTYPE *newps;
+#endif
+
+ if ((newsize = data->stacksize) == 0)
+ newsize = YYINITSTACKSIZE;
+ else if (newsize >= YYMAXDEPTH)
+ return YYENOMEM;
+ else if ((newsize *= 2) > YYMAXDEPTH)
+ newsize = YYMAXDEPTH;
+
+ i = (int) (data->s_mark - data->s_base);
+ newss = (YYINT *)realloc(data->s_base, newsize * sizeof(*newss));
+ if (newss == 0)
+ return YYENOMEM;
+
+ data->s_base = newss;
+ data->s_mark = newss + i;
+
+ newvs = (YYSTYPE *)realloc(data->l_base, newsize * sizeof(*newvs));
+ if (newvs == 0)
+ return YYENOMEM;
+
+ data->l_base = newvs;
+ data->l_mark = newvs + i;
+
+#if defined(YYLTYPE) || defined(YYLTYPE_IS_DECLARED)
+ newps = (YYLTYPE *)realloc(data->p_base, newsize * sizeof(*newps));
+ if (newps == 0)
+ return YYENOMEM;
+
+ data->p_base = newps;
+ data->p_mark = newps + i;
+#endif
+
+ data->stacksize = newsize;
+ data->s_last = data->s_base + newsize - 1;
+
+#if YYDEBUG
+ if (yydebug)
+ fprintf(stderr, "%sdebug: stack size increased to %d\n", YYPREFIX, newsize);
+#endif
+ return 0;
+}
+
+#if YYPURE || defined(YY_NO_LEAKS)
+static void yyfreestack(YYSTACKDATA *data)
+{
+ free(data->s_base);
+ free(data->l_base);
+#if defined(YYLTYPE) || defined(YYLTYPE_IS_DECLARED)
+ free(data->p_base);
+#endif
+ memset(data, 0, sizeof(*data));
+}
+#else
+#define yyfreestack(data) /* nothing */
+#endif /* YYPURE || defined(YY_NO_LEAKS) */
+#if YYBTYACC
+
+static YYParseState *
+yyNewState(unsigned size)
+{
+ YYParseState *p = (YYParseState *) malloc(sizeof(YYParseState));
+ if (p == NULL) return NULL;
+
+ p->yystack.stacksize = size;
+ if (size == 0)
+ {
+ p->yystack.s_base = NULL;
+ p->yystack.l_base = NULL;
+#if defined(YYLTYPE) || defined(YYLTYPE_IS_DECLARED)
+ p->yystack.p_base = NULL;
+#endif
+ return p;
+ }
+ p->yystack.s_base = (YYINT *) malloc(size * sizeof(YYINT));
+ if (p->yystack.s_base == NULL) return NULL;
+ p->yystack.l_base = (YYSTYPE *) malloc(size * sizeof(YYSTYPE));
+ if (p->yystack.l_base == NULL) return NULL;
+ memset(p->yystack.l_base, 0, size * sizeof(YYSTYPE));
+#if defined(YYLTYPE) || defined(YYLTYPE_IS_DECLARED)
+ p->yystack.p_base = (YYLTYPE *) malloc(size * sizeof(YYLTYPE));
+ if (p->yystack.p_base == NULL) return NULL;
+ memset(p->yystack.p_base, 0, size * sizeof(YYLTYPE));
+#endif
+
+ return p;
+}
+
+static void
+yyFreeState(YYParseState *p)
+{
+ yyfreestack(&p->yystack);
+ free(p);
+}
+#endif /* YYBTYACC */
+
+#define YYABORT goto yyabort
+#define YYREJECT goto yyabort
+#define YYACCEPT goto yyaccept
+#define YYERROR goto yyerrlab
+#if YYBTYACC
+#define YYVALID do { if (yyps->save) goto yyvalid; } while(0)
+#define YYVALID_NESTED do { if (yyps->save && \
+ yyps->save->save == 0) goto yyvalid; } while(0)
+#endif /* YYBTYACC */
+
+int
+YYPARSE_DECL()
+{
+ int yym, yyn, yystate, yyresult;
+#if YYBTYACC
+ int yynewerrflag;
+ YYParseState *yyerrctx = NULL;
+#endif /* YYBTYACC */
+#if defined(YYLTYPE) || defined(YYLTYPE_IS_DECLARED)
+ YYLTYPE yyerror_loc_range[3]; /* position of error start/end (0 unused) */
+#endif
+#if YYDEBUG
+ const char *yys;
+
+ if ((yys = getenv("YYDEBUG")) != 0)
+ {
+ yyn = *yys;
+ if (yyn >= '0' && yyn <= '9')
+ yydebug = yyn - '0';
+ }
+ if (yydebug)
+ fprintf(stderr, "%sdebug[<# of symbols on state stack>]\n", YYPREFIX);
+#endif
+#if defined(YYLTYPE) || defined(YYLTYPE_IS_DECLARED)
+ memset(yyerror_loc_range, 0, sizeof(yyerror_loc_range));
+#endif
+
+#if YYBTYACC
+ yyps = yyNewState(0); if (yyps == 0) goto yyenomem;
+ yyps->save = 0;
+#endif /* YYBTYACC */
+ yym = 0;
+ /* yyn is set below */
+ yynerrs = 0;
+ yyerrflag = 0;
+ yychar = YYEMPTY;
+ yystate = 0;
+
+#if YYPURE
+ memset(&yystack, 0, sizeof(yystack));
+#endif
+
+ if (yystack.s_base == NULL && yygrowstack(&yystack) == YYENOMEM) goto yyoverflow;
+ yystack.s_mark = yystack.s_base;
+ yystack.l_mark = yystack.l_base;
+#if defined(YYLTYPE) || defined(YYLTYPE_IS_DECLARED)
+ yystack.p_mark = yystack.p_base;
+#endif
+ yystate = 0;
+ *yystack.s_mark = 0;
+
+yyloop:
+ if ((yyn = yydefred[yystate]) != 0) goto yyreduce;
+ if (yychar < 0)
+ {
+#if YYBTYACC
+ do {
+ if (yylvp < yylve)
+ {
+ /* we're currently re-reading tokens */
+ yylval = *yylvp++;
+#if defined(YYLTYPE) || defined(YYLTYPE_IS_DECLARED)
+ yylloc = *yylpp++;
+#endif
+ yychar = *yylexp++;
+ break;
+ }
+ if (yyps->save)
+ {
+ /* in trial mode; save scanner results for future parse attempts */
+ if (yylvp == yylvlim)
+ { /* Enlarge lexical value queue */
+ size_t p = (size_t) (yylvp - yylvals);
+ size_t s = (size_t) (yylvlim - yylvals);
+
+ s += YYLVQUEUEGROWTH;
+ if ((yylexemes = (YYINT *)realloc(yylexemes, s * sizeof(YYINT))) == NULL) goto yyenomem;
+ if ((yylvals = (YYSTYPE *)realloc(yylvals, s * sizeof(YYSTYPE))) == NULL) goto yyenomem;
+#if defined(YYLTYPE) || defined(YYLTYPE_IS_DECLARED)
+ if ((yylpsns = (YYLTYPE *)realloc(yylpsns, s * sizeof(YYLTYPE))) == NULL) goto yyenomem;
+#endif
+ yylvp = yylve = yylvals + p;
+ yylvlim = yylvals + s;
+#if defined(YYLTYPE) || defined(YYLTYPE_IS_DECLARED)
+ yylpp = yylpe = yylpsns + p;
+ yylplim = yylpsns + s;
+#endif
+ yylexp = yylexemes + p;
+ }
+ *yylexp = (YYINT) YYLEX;
+ *yylvp++ = yylval;
+ yylve++;
+#if defined(YYLTYPE) || defined(YYLTYPE_IS_DECLARED)
+ *yylpp++ = yylloc;
+ yylpe++;
+#endif
+ yychar = *yylexp++;
+ break;
+ }
+ /* normal operation, no conflict encountered */
+#endif /* YYBTYACC */
+ yychar = YYLEX;
+#if YYBTYACC
+ } while (0);
+#endif /* YYBTYACC */
+ if (yychar < 0) yychar = YYEOF;
+#if YYDEBUG
+ if (yydebug)
+ {
+ if ((yys = yyname[YYTRANSLATE(yychar)]) == NULL) yys = yyname[YYUNDFTOKEN];
+ fprintf(stderr, "%s[%d]: state %d, reading token %d (%s)",
+ YYDEBUGSTR, yydepth, yystate, yychar, yys);
+#ifdef YYSTYPE_TOSTRING
+#if YYBTYACC
+ if (!yytrial)
+#endif /* YYBTYACC */
+ fprintf(stderr, " <%s>", YYSTYPE_TOSTRING(yychar, yylval));
+#endif
+ fputc('\n', stderr);
+ }
+#endif
+ }
+#if YYBTYACC
+
+ /* Do we have a conflict? */
+ if (((yyn = yycindex[yystate]) != 0) && (yyn += yychar) >= 0 &&
+ yyn <= YYTABLESIZE && yycheck[yyn] == (YYINT) yychar)
+ {
+ YYINT ctry;
+
+ if (yypath)
+ {
+ YYParseState *save;
+#if YYDEBUG
+ if (yydebug)
+ fprintf(stderr, "%s[%d]: CONFLICT in state %d: following successful trial parse\n",
+ YYDEBUGSTR, yydepth, yystate);
+#endif
+ /* Switch to the next conflict context */
+ save = yypath;
+ yypath = save->save;
+ save->save = NULL;
+ ctry = save->ctry;
+ if (save->state != yystate) YYABORT;
+ yyFreeState(save);
+
+ }
+ else
+ {
+
+ /* Unresolved conflict - start/continue trial parse */
+ YYParseState *save;
+#if YYDEBUG
+ if (yydebug)
+ {
+ fprintf(stderr, "%s[%d]: CONFLICT in state %d. ", YYDEBUGSTR, yydepth, yystate);
+ if (yyps->save)
+ fputs("ALREADY in conflict, continuing trial parse.\n", stderr);
+ else
+ fputs("Starting trial parse.\n", stderr);
+ }
+#endif
+ save = yyNewState((unsigned)(yystack.s_mark - yystack.s_base + 1));
+ if (save == NULL) goto yyenomem;
+ save->save = yyps->save;
+ save->state = yystate;
+ save->errflag = yyerrflag;
+ save->yystack.s_mark = save->yystack.s_base + (yystack.s_mark - yystack.s_base);
+ memcpy (save->yystack.s_base, yystack.s_base, (size_t) (yystack.s_mark - yystack.s_base + 1) * sizeof(YYINT));
+ save->yystack.l_mark = save->yystack.l_base + (yystack.l_mark - yystack.l_base);
+ memcpy (save->yystack.l_base, yystack.l_base, (size_t) (yystack.l_mark - yystack.l_base + 1) * sizeof(YYSTYPE));
+#if defined(YYLTYPE) || defined(YYLTYPE_IS_DECLARED)
+ save->yystack.p_mark = save->yystack.p_base + (yystack.p_mark - yystack.p_base);
+ memcpy (save->yystack.p_base, yystack.p_base, (size_t) (yystack.p_mark - yystack.p_base + 1) * sizeof(YYLTYPE));
+#endif
+ ctry = yytable[yyn];
+ if (yyctable[ctry] == -1)
+ {
+#if YYDEBUG
+ if (yydebug && yychar >= YYEOF)
+ fprintf(stderr, "%s[%d]: backtracking 1 token\n", YYDEBUGSTR, yydepth);
+#endif
+ ctry++;
+ }
+ save->ctry = ctry;
+ if (yyps->save == NULL)
+ {
+ /* If this is a first conflict in the stack, start saving lexemes */
+ if (!yylexemes)
+ {
+ yylexemes = (YYINT *) malloc((YYLVQUEUEGROWTH) * sizeof(YYINT));
+ if (yylexemes == NULL) goto yyenomem;
+ yylvals = (YYSTYPE *) malloc((YYLVQUEUEGROWTH) * sizeof(YYSTYPE));
+ if (yylvals == NULL) goto yyenomem;
+ yylvlim = yylvals + YYLVQUEUEGROWTH;
+#if defined(YYLTYPE) || defined(YYLTYPE_IS_DECLARED)
+ yylpsns = (YYLTYPE *) malloc((YYLVQUEUEGROWTH) * sizeof(YYLTYPE));
+ if (yylpsns == NULL) goto yyenomem;
+ yylplim = yylpsns + YYLVQUEUEGROWTH;
+#endif
+ }
+ if (yylvp == yylve)
+ {
+ yylvp = yylve = yylvals;
+#if defined(YYLTYPE) || defined(YYLTYPE_IS_DECLARED)
+ yylpp = yylpe = yylpsns;
+#endif
+ yylexp = yylexemes;
+ if (yychar >= YYEOF)
+ {
+ *yylve++ = yylval;
+#if defined(YYLTYPE) || defined(YYLTYPE_IS_DECLARED)
+ *yylpe++ = yylloc;
+#endif
+ *yylexp = (YYINT) yychar;
+ yychar = YYEMPTY;
+ }
+ }
+ }
+ if (yychar >= YYEOF)
+ {
+ yylvp--;
+#if defined(YYLTYPE) || defined(YYLTYPE_IS_DECLARED)
+ yylpp--;
+#endif
+ yylexp--;
+ yychar = YYEMPTY;
+ }
+ save->lexeme = (int) (yylvp - yylvals);
+ yyps->save = save;
+ }
+ if (yytable[yyn] == ctry)
+ {
+#if YYDEBUG
+ if (yydebug)
+ fprintf(stderr, "%s[%d]: state %d, shifting to state %d\n",
+ YYDEBUGSTR, yydepth, yystate, yyctable[ctry]);
+#endif
+ if (yychar < 0)
+ {
+ yylvp++;
+#if defined(YYLTYPE) || defined(YYLTYPE_IS_DECLARED)
+ yylpp++;
+#endif
+ yylexp++;
+ }
+ if (yystack.s_mark >= yystack.s_last && yygrowstack(&yystack) == YYENOMEM)
+ goto yyoverflow;
+ yystate = yyctable[ctry];
+ *++yystack.s_mark = (YYINT) yystate;
+ *++yystack.l_mark = yylval;
+#if defined(YYLTYPE) || defined(YYLTYPE_IS_DECLARED)
+ *++yystack.p_mark = yylloc;
+#endif
+ yychar = YYEMPTY;
+ if (yyerrflag > 0) --yyerrflag;
+ goto yyloop;
+ }
+ else
+ {
+ yyn = yyctable[ctry];
+ goto yyreduce;
+ }
+ } /* End of code dealing with conflicts */
+#endif /* YYBTYACC */
+ if (((yyn = yysindex[yystate]) != 0) && (yyn += yychar) >= 0 &&
+ yyn <= YYTABLESIZE && yycheck[yyn] == (YYINT) yychar)
+ {
+#if YYDEBUG
+ if (yydebug)
+ fprintf(stderr, "%s[%d]: state %d, shifting to state %d\n",
+ YYDEBUGSTR, yydepth, yystate, yytable[yyn]);
+#endif
+ if (yystack.s_mark >= yystack.s_last && yygrowstack(&yystack) == YYENOMEM) goto yyoverflow;
+ yystate = yytable[yyn];
+ *++yystack.s_mark = yytable[yyn];
+ *++yystack.l_mark = yylval;
+#if defined(YYLTYPE) || defined(YYLTYPE_IS_DECLARED)
+ *++yystack.p_mark = yylloc;
+#endif
+ yychar = YYEMPTY;
+ if (yyerrflag > 0) --yyerrflag;
+ goto yyloop;
+ }
+ if (((yyn = yyrindex[yystate]) != 0) && (yyn += yychar) >= 0 &&
+ yyn <= YYTABLESIZE && yycheck[yyn] == (YYINT) yychar)
+ {
+ yyn = yytable[yyn];
+ goto yyreduce;
+ }
+ if (yyerrflag != 0) goto yyinrecovery;
+#if YYBTYACC
+
+ yynewerrflag = 1;
+ goto yyerrhandler;
+ goto yyerrlab; /* redundant goto avoids 'unused label' warning */
+
+yyerrlab:
+ /* explicit YYERROR from an action -- pop the rhs of the rule reduced
+ * before looking for error recovery */
+ yystack.s_mark -= yym;
+ yystate = *yystack.s_mark;
+ yystack.l_mark -= yym;
+#if defined(YYLTYPE) || defined(YYLTYPE_IS_DECLARED)
+ yystack.p_mark -= yym;
+#endif
+
+ yynewerrflag = 0;
+yyerrhandler:
+ while (yyps->save)
+ {
+ int ctry;
+ YYParseState *save = yyps->save;
+#if YYDEBUG
+ if (yydebug)
+ fprintf(stderr, "%s[%d]: ERROR in state %d, CONFLICT BACKTRACKING to state %d, %d tokens\n",
+ YYDEBUGSTR, yydepth, yystate, yyps->save->state,
+ (int)(yylvp - yylvals - yyps->save->lexeme));
+#endif
+ /* Memorize most forward-looking error state in case it's really an error. */
+ if (yyerrctx == NULL || yyerrctx->lexeme < yylvp - yylvals)
+ {
+ /* Free old saved error context state */
+ if (yyerrctx) yyFreeState(yyerrctx);
+ /* Create and fill out new saved error context state */
+ yyerrctx = yyNewState((unsigned)(yystack.s_mark - yystack.s_base + 1));
+ if (yyerrctx == NULL) goto yyenomem;
+ yyerrctx->save = yyps->save;
+ yyerrctx->state = yystate;
+ yyerrctx->errflag = yyerrflag;
+ yyerrctx->yystack.s_mark = yyerrctx->yystack.s_base + (yystack.s_mark - yystack.s_base);
+ memcpy (yyerrctx->yystack.s_base, yystack.s_base, (size_t) (yystack.s_mark - yystack.s_base + 1) * sizeof(YYINT));
+ yyerrctx->yystack.l_mark = yyerrctx->yystack.l_base + (yystack.l_mark - yystack.l_base);
+ memcpy (yyerrctx->yystack.l_base, yystack.l_base, (size_t) (yystack.l_mark - yystack.l_base + 1) * sizeof(YYSTYPE));
+#if defined(YYLTYPE) || defined(YYLTYPE_IS_DECLARED)
+ yyerrctx->yystack.p_mark = yyerrctx->yystack.p_base + (yystack.p_mark - yystack.p_base);
+ memcpy (yyerrctx->yystack.p_base, yystack.p_base, (size_t) (yystack.p_mark - yystack.p_base + 1) * sizeof(YYLTYPE));
+#endif
+ yyerrctx->lexeme = (int) (yylvp - yylvals);
+ }
+ yylvp = yylvals + save->lexeme;
+#if defined(YYLTYPE) || defined(YYLTYPE_IS_DECLARED)
+ yylpp = yylpsns + save->lexeme;
+#endif
+ yylexp = yylexemes + save->lexeme;
+ yychar = YYEMPTY;
+ yystack.s_mark = yystack.s_base + (save->yystack.s_mark - save->yystack.s_base);
+ memcpy (yystack.s_base, save->yystack.s_base, (size_t) (yystack.s_mark - yystack.s_base + 1) * sizeof(YYINT));
+ yystack.l_mark = yystack.l_base + (save->yystack.l_mark - save->yystack.l_base);
+ memcpy (yystack.l_base, save->yystack.l_base, (size_t) (yystack.l_mark - yystack.l_base + 1) * sizeof(YYSTYPE));
+#if defined(YYLTYPE) || defined(YYLTYPE_IS_DECLARED)
+ yystack.p_mark = yystack.p_base + (save->yystack.p_mark - save->yystack.p_base);
+ memcpy (yystack.p_base, save->yystack.p_base, (size_t) (yystack.p_mark - yystack.p_base + 1) * sizeof(YYLTYPE));
+#endif
+ ctry = ++save->ctry;
+ yystate = save->state;
+ /* We tried shift, try reduce now */
+ if ((yyn = yyctable[ctry]) >= 0) goto yyreduce;
+ yyps->save = save->save;
+ save->save = NULL;
+ yyFreeState(save);
+
+ /* Nothing left on the stack -- error */
+ if (!yyps->save)
+ {
+#if YYDEBUG
+ if (yydebug)
+ fprintf(stderr, "%sdebug[%d,trial]: trial parse FAILED, entering ERROR mode\n",
+ YYPREFIX, yydepth);
+#endif
+ /* Restore state as it was in the most forward-advanced error */
+ yylvp = yylvals + yyerrctx->lexeme;
+#if defined(YYLTYPE) || defined(YYLTYPE_IS_DECLARED)
+ yylpp = yylpsns + yyerrctx->lexeme;
+#endif
+ yylexp = yylexemes + yyerrctx->lexeme;
+ yychar = yylexp[-1];
+ yylval = yylvp[-1];
+#if defined(YYLTYPE) || defined(YYLTYPE_IS_DECLARED)
+ yylloc = yylpp[-1];
+#endif
+ yystack.s_mark = yystack.s_base + (yyerrctx->yystack.s_mark - yyerrctx->yystack.s_base);
+ memcpy (yystack.s_base, yyerrctx->yystack.s_base, (size_t) (yystack.s_mark - yystack.s_base + 1) * sizeof(YYINT));
+ yystack.l_mark = yystack.l_base + (yyerrctx->yystack.l_mark - yyerrctx->yystack.l_base);
+ memcpy (yystack.l_base, yyerrctx->yystack.l_base, (size_t) (yystack.l_mark - yystack.l_base + 1) * sizeof(YYSTYPE));
+#if defined(YYLTYPE) || defined(YYLTYPE_IS_DECLARED)
+ yystack.p_mark = yystack.p_base + (yyerrctx->yystack.p_mark - yyerrctx->yystack.p_base);
+ memcpy (yystack.p_base, yyerrctx->yystack.p_base, (size_t) (yystack.p_mark - yystack.p_base + 1) * sizeof(YYLTYPE));
+#endif
+ yystate = yyerrctx->state;
+ yyFreeState(yyerrctx);
+ yyerrctx = NULL;
+ }
+ yynewerrflag = 1;
+ }
+ if (yynewerrflag == 0) goto yyinrecovery;
+#endif /* YYBTYACC */
+
+ YYERROR_CALL("syntax error");
+#if defined(YYLTYPE) || defined(YYLTYPE_IS_DECLARED)
+ yyerror_loc_range[1] = yylloc; /* lookahead position is error start position */
+#endif
+
+#if !YYBTYACC
+ goto yyerrlab; /* redundant goto avoids 'unused label' warning */
+yyerrlab:
+#endif
+ ++yynerrs;
+
+yyinrecovery:
+ if (yyerrflag < 3)
+ {
+ yyerrflag = 3;
+ for (;;)
+ {
+ if (((yyn = yysindex[*yystack.s_mark]) != 0) && (yyn += YYERRCODE) >= 0 &&
+ yyn <= YYTABLESIZE && yycheck[yyn] == (YYINT) YYERRCODE)
+ {
+#if YYDEBUG
+ if (yydebug)
+ fprintf(stderr, "%s[%d]: state %d, error recovery shifting to state %d\n",
+ YYDEBUGSTR, yydepth, *yystack.s_mark, yytable[yyn]);
+#endif
+ if (yystack.s_mark >= yystack.s_last && yygrowstack(&yystack) == YYENOMEM) goto yyoverflow;
+ yystate = yytable[yyn];
+ *++yystack.s_mark = yytable[yyn];
+ *++yystack.l_mark = yylval;
+#if defined(YYLTYPE) || defined(YYLTYPE_IS_DECLARED)
+ /* lookahead position is error end position */
+ yyerror_loc_range[2] = yylloc;
+ YYLLOC_DEFAULT(yyloc, yyerror_loc_range, 2); /* position of error span */
+ *++yystack.p_mark = yyloc;
+#endif
+ goto yyloop;
+ }
+ else
+ {
+#if YYDEBUG
+ if (yydebug)
+ fprintf(stderr, "%s[%d]: error recovery discarding state %d\n",
+ YYDEBUGSTR, yydepth, *yystack.s_mark);
+#endif
+ if (yystack.s_mark <= yystack.s_base) goto yyabort;
+#if defined(YYLTYPE) || defined(YYLTYPE_IS_DECLARED)
+ /* the current TOS position is the error start position */
+ yyerror_loc_range[1] = *yystack.p_mark;
+#endif
+#if defined(YYDESTRUCT_CALL)
+#if YYBTYACC
+ if (!yytrial)
+#endif /* YYBTYACC */
+#if defined(YYLTYPE) || defined(YYLTYPE_IS_DECLARED)
+ YYDESTRUCT_CALL("error: discarding state",
+ yystos[*yystack.s_mark], yystack.l_mark, yystack.p_mark);
+#else
+ YYDESTRUCT_CALL("error: discarding state",
+ yystos[*yystack.s_mark], yystack.l_mark);
+#endif /* defined(YYLTYPE) || defined(YYLTYPE_IS_DECLARED) */
+#endif /* defined(YYDESTRUCT_CALL) */
+ --yystack.s_mark;
+ --yystack.l_mark;
+#if defined(YYLTYPE) || defined(YYLTYPE_IS_DECLARED)
+ --yystack.p_mark;
+#endif
+ }
+ }
+ }
+ else
+ {
+ if (yychar == YYEOF) goto yyabort;
+#if YYDEBUG
+ if (yydebug)
+ {
+ if ((yys = yyname[YYTRANSLATE(yychar)]) == NULL) yys = yyname[YYUNDFTOKEN];
+ fprintf(stderr, "%s[%d]: state %d, error recovery discarding token %d (%s)\n",
+ YYDEBUGSTR, yydepth, yystate, yychar, yys);
+ }
+#endif
+#if defined(YYDESTRUCT_CALL)
+#if YYBTYACC
+ if (!yytrial)
+#endif /* YYBTYACC */
+#if defined(YYLTYPE) || defined(YYLTYPE_IS_DECLARED)
+ YYDESTRUCT_CALL("error: discarding token", yychar, &yylval, &yylloc);
+#else
+ YYDESTRUCT_CALL("error: discarding token", yychar, &yylval);
+#endif /* defined(YYLTYPE) || defined(YYLTYPE_IS_DECLARED) */
+#endif /* defined(YYDESTRUCT_CALL) */
+ yychar = YYEMPTY;
+ goto yyloop;
+ }
+
+yyreduce:
+ yym = yylen[yyn];
+#if YYDEBUG
+ if (yydebug)
+ {
+ fprintf(stderr, "%s[%d]: state %d, reducing by rule %d (%s)",
+ YYDEBUGSTR, yydepth, yystate, yyn, yyrule[yyn]);
+#ifdef YYSTYPE_TOSTRING
+#if YYBTYACC
+ if (!yytrial)
+#endif /* YYBTYACC */
+ if (yym > 0)
+ {
+ int i;
+ fputc('<', stderr);
+ for (i = yym; i > 0; i--)
+ {
+ if (i != yym) fputs(", ", stderr);
+ fputs(YYSTYPE_TOSTRING(yystos[yystack.s_mark[1-i]],
+ yystack.l_mark[1-i]), stderr);
+ }
+ fputc('>', stderr);
+ }
+#endif
+ fputc('\n', stderr);
+ }
+#endif
+ if (yym > 0)
+ yyval = yystack.l_mark[1-yym];
+ else
+ memset(&yyval, 0, sizeof yyval);
+#if defined(YYLTYPE) || defined(YYLTYPE_IS_DECLARED)
+
+ /* Perform position reduction */
+ memset(&yyloc, 0, sizeof(yyloc));
+#if YYBTYACC
+ if (!yytrial)
+#endif /* YYBTYACC */
+ {
+ YYLLOC_DEFAULT(yyloc, &yystack.p_mark[-yym], yym);
+ /* just in case YYERROR is invoked within the action, save
+ the start of the rhs as the error start position */
+ yyerror_loc_range[1] = yystack.p_mark[1-yym];
+ }
+#endif
+
+ switch (yyn)
+ {
+case 3:
+#line 157 "./parsdate.y"
+ {
+ yyHaveTime++;
+#if defined(lint)
+ /* I am compulsive about lint natterings... */
+ if (yyHaveTime == -1) {
+ YYERROR;
+ }
+#endif /* defined(lint) */
+ }
+#line 1884 "y.tab.c"
+break;
+case 4:
+#line 166 "./parsdate.y"
+ {
+ yyHaveTime++;
+ yyTimezone = yystack.l_mark[0].Number;
+ }
+#line 1892 "y.tab.c"
+break;
+case 5:
+#line 170 "./parsdate.y"
+ {
+ yyHaveDate++;
+ }
+#line 1899 "y.tab.c"
+break;
+case 6:
+#line 173 "./parsdate.y"
+ {
+ yyHaveDate++;
+ yyHaveTime++;
+ }
+#line 1907 "y.tab.c"
+break;
+case 7:
+#line 177 "./parsdate.y"
+ {
+ yyHaveDate++;
+ yyHaveTime++;
+ yyTimezone = yystack.l_mark[0].Number;
+ }
+#line 1916 "y.tab.c"
+break;
+case 8:
+#line 182 "./parsdate.y"
+ {
+ yyHaveRel = 1;
+ }
+#line 1923 "y.tab.c"
+break;
+case 9:
+#line 187 "./parsdate.y"
+ {
+ if (yystack.l_mark[-1].Number < 100) {
+ yyHour = yystack.l_mark[-1].Number;
+ yyMinutes = 0;
+ }
+ else {
+ yyHour = yystack.l_mark[-1].Number / 100;
+ yyMinutes = yystack.l_mark[-1].Number % 100;
+ }
+ yySeconds = 0;
+ yyMeridian = yystack.l_mark[0].Meridian;
+ }
+#line 1939 "y.tab.c"
+break;
+case 10:
+#line 199 "./parsdate.y"
+ {
+ yyHour = yystack.l_mark[-3].Number;
+ yyMinutes = yystack.l_mark[-1].Number;
+ yySeconds = 0;
+ yyMeridian = yystack.l_mark[0].Meridian;
+ }
+#line 1949 "y.tab.c"
+break;
+case 11:
+#line 205 "./parsdate.y"
+ {
+ yyHour = yystack.l_mark[-3].Number;
+ yyMinutes = yystack.l_mark[-1].Number;
+ yyTimezone = yystack.l_mark[0].Number;
+ yyMeridian = MER24;
+ yyDSTmode = DSToff;
+ }
+#line 1960 "y.tab.c"
+break;
+case 12:
+#line 212 "./parsdate.y"
+ {
+ yyHour = yystack.l_mark[-5].Number;
+ yyMinutes = yystack.l_mark[-3].Number;
+ yySeconds = yystack.l_mark[-1].Number;
+ yyMeridian = yystack.l_mark[0].Meridian;
+ }
+#line 1970 "y.tab.c"
+break;
+case 13:
+#line 218 "./parsdate.y"
+ {
+ yyHour = yystack.l_mark[-5].Number;
+ yyMinutes = yystack.l_mark[-3].Number;
+ yySeconds = yystack.l_mark[-1].Number;
+ yyTimezone = yystack.l_mark[0].Number;
+ yyMeridian = MER24;
+ yyDSTmode = DSToff;
+ }
+#line 1982 "y.tab.c"
+break;
+case 14:
+#line 228 "./parsdate.y"
+ {
+ yyval.Number = yystack.l_mark[0].Number;
+ yyDSTmode = DSToff;
+ }
+#line 1990 "y.tab.c"
+break;
+case 15:
+#line 232 "./parsdate.y"
+ {
+ yyval.Number = yystack.l_mark[0].Number;
+ yyDSTmode = DSTon;
+ }
+#line 1998 "y.tab.c"
+break;
+case 16:
+#line 236 "./parsdate.y"
+ {
+ yyTimezone = yystack.l_mark[-1].Number;
+ yyDSTmode = DSTon;
+ }
+#line 2006 "y.tab.c"
+break;
+case 17:
+#line 240 "./parsdate.y"
+ {
+ /* Only allow "GMT+300" and "GMT-0800" */
+ if (yystack.l_mark[-1].Number != 0) {
+ YYABORT;
+ }
+ yyval.Number = yystack.l_mark[0].Number;
+ yyDSTmode = DSToff;
+ }
+#line 2018 "y.tab.c"
+break;
+case 18:
+#line 248 "./parsdate.y"
+ {
+ yyval.Number = yystack.l_mark[0].Number;
+ yyDSTmode = DSToff;
+ }
+#line 2026 "y.tab.c"
+break;
+case 19:
+#line 254 "./parsdate.y"
+ {
+ int i;
+
+ /* Unix and GMT and numeric timezones -- a little confusing. */
+ if ((long)yystack.l_mark[0].Number < 0) {
+ /* Don't work with negative modulus. */
+ yystack.l_mark[0].Number = -(int)yystack.l_mark[0].Number;
+ if (yystack.l_mark[0].Number > 9999 || (i = (int) (yystack.l_mark[0].Number % 100)) >= 60) {
+ YYABORT;
+ }
+ yyval.Number = (yystack.l_mark[0].Number / 100) * 60 + i;
+ }
+ else {
+ if (yystack.l_mark[0].Number > 9999 || (i = (int) (yystack.l_mark[0].Number % 100)) >= 60) {
+ YYABORT;
+ }
+ yyval.Number = -((yystack.l_mark[0].Number / 100) * 60 + i);
+ }
+ }
+#line 2049 "y.tab.c"
+break;
+case 20:
+#line 275 "./parsdate.y"
+ {
+ yyMonth = yystack.l_mark[-2].Number;
+ yyDay = yystack.l_mark[0].Number;
+ }
+#line 2057 "y.tab.c"
+break;
+case 21:
+#line 279 "./parsdate.y"
+ {
+ if (yystack.l_mark[-4].Number > 100) {
+ yyYear = yystack.l_mark[-4].Number;
+ yyMonth = yystack.l_mark[-2].Number;
+ yyDay = yystack.l_mark[0].Number;
+ }
+ else {
+ yyMonth = yystack.l_mark[-4].Number;
+ yyDay = yystack.l_mark[-2].Number;
+ yyYear = yystack.l_mark[0].Number;
+ }
+ }
+#line 2073 "y.tab.c"
+break;
+case 22:
+#line 291 "./parsdate.y"
+ {
+ yyMonth = yystack.l_mark[-1].Number;
+ yyDay = yystack.l_mark[0].Number;
+ }
+#line 2081 "y.tab.c"
+break;
+case 23:
+#line 295 "./parsdate.y"
+ {
+ yyMonth = yystack.l_mark[-3].Number;
+ yyDay = yystack.l_mark[-2].Number;
+ yyYear = yystack.l_mark[0].Number;
+ }
+#line 2090 "y.tab.c"
+break;
+case 24:
+#line 300 "./parsdate.y"
+ {
+ yyDay = yystack.l_mark[-1].Number;
+ yyMonth = yystack.l_mark[0].Number;
+ }
+#line 2098 "y.tab.c"
+break;
+case 25:
+#line 304 "./parsdate.y"
+ {
+ yyDay = yystack.l_mark[-2].Number;
+ yyMonth = yystack.l_mark[-1].Number;
+ yyYear = yystack.l_mark[0].Number;
+ }
+#line 2107 "y.tab.c"
+break;
+case 26:
+#line 309 "./parsdate.y"
+ {
+ yyDay = yystack.l_mark[-2].Number;
+ yyMonth = yystack.l_mark[-1].Number;
+ yyYear = yystack.l_mark[0].Number;
+ }
+#line 2116 "y.tab.c"
+break;
+case 27:
+#line 314 "./parsdate.y"
+ {
+ yyDay = yystack.l_mark[-3].Number;
+ yyMonth = yystack.l_mark[-1].Number;
+ yyYear = -yystack.l_mark[0].Number;
+ }
+#line 2125 "y.tab.c"
+break;
+case 28:
+#line 319 "./parsdate.y"
+ {
+ yyDay = yystack.l_mark[-2].Number;
+ yyMonth = -yystack.l_mark[-1].Number;
+ yyYear = -yystack.l_mark[0].Number;
+ yyDSTmode = DSToff; /* assume midnight if no time given */
+ yyTimezone = 0; /* Lynx assumes GMT for this format */
+ }
+#line 2136 "y.tab.c"
+break;
+case 29:
+#line 328 "./parsdate.y"
+ {
+ yyMonth = yystack.l_mark[-7].Number;
+ yyDay = yystack.l_mark[-6].Number;
+ yyYear = yystack.l_mark[0].Number;
+ yyHour = yystack.l_mark[-5].Number;
+ yyMinutes = yystack.l_mark[-3].Number;
+ yySeconds = yystack.l_mark[-1].Number;
+ }
+#line 2148 "y.tab.c"
+break;
+case 30:
+#line 338 "./parsdate.y"
+ {
+ yyRelSeconds += yystack.l_mark[-1].Number * yystack.l_mark[0].Number;
+ }
+#line 2155 "y.tab.c"
+break;
+case 31:
+#line 341 "./parsdate.y"
+ {
+ yyRelSeconds += yystack.l_mark[-1].Number * yystack.l_mark[0].Number;
+ }
+#line 2162 "y.tab.c"
+break;
+case 32:
+#line 344 "./parsdate.y"
+ {
+ yyRelMonth += yystack.l_mark[-1].Number * yystack.l_mark[0].Number;
+ }
+#line 2169 "y.tab.c"
+break;
+case 33:
+#line 347 "./parsdate.y"
+ {
+ yyRelMonth += yystack.l_mark[-1].Number * yystack.l_mark[0].Number;
+ }
+#line 2176 "y.tab.c"
+break;
+case 34:
+#line 352 "./parsdate.y"
+ {
+ yyval.Meridian = MER24;
+ }
+#line 2183 "y.tab.c"
+break;
+case 35:
+#line 355 "./parsdate.y"
+ {
+ yyval.Meridian = yystack.l_mark[0].Meridian;
+ }
+#line 2190 "y.tab.c"
+break;
+#line 2192 "y.tab.c"
+ default:
+ break;
+ }
+ yystack.s_mark -= yym;
+ yystate = *yystack.s_mark;
+ yystack.l_mark -= yym;
+#if defined(YYLTYPE) || defined(YYLTYPE_IS_DECLARED)
+ yystack.p_mark -= yym;
+#endif
+ yym = yylhs[yyn];
+ if (yystate == 0 && yym == 0)
+ {
+#if YYDEBUG
+ if (yydebug)
+ {
+ fprintf(stderr, "%s[%d]: after reduction, ", YYDEBUGSTR, yydepth);
+#ifdef YYSTYPE_TOSTRING
+#if YYBTYACC
+ if (!yytrial)
+#endif /* YYBTYACC */
+ fprintf(stderr, "result is <%s>, ", YYSTYPE_TOSTRING(yystos[YYFINAL], yyval));
+#endif
+ fprintf(stderr, "shifting from state 0 to final state %d\n", YYFINAL);
+ }
+#endif
+ yystate = YYFINAL;
+ *++yystack.s_mark = YYFINAL;
+ *++yystack.l_mark = yyval;
+#if defined(YYLTYPE) || defined(YYLTYPE_IS_DECLARED)
+ *++yystack.p_mark = yyloc;
+#endif
+ if (yychar < 0)
+ {
+#if YYBTYACC
+ do {
+ if (yylvp < yylve)
+ {
+ /* we're currently re-reading tokens */
+ yylval = *yylvp++;
+#if defined(YYLTYPE) || defined(YYLTYPE_IS_DECLARED)
+ yylloc = *yylpp++;
+#endif
+ yychar = *yylexp++;
+ break;
+ }
+ if (yyps->save)
+ {
+ /* in trial mode; save scanner results for future parse attempts */
+ if (yylvp == yylvlim)
+ { /* Enlarge lexical value queue */
+ size_t p = (size_t) (yylvp - yylvals);
+ size_t s = (size_t) (yylvlim - yylvals);
+
+ s += YYLVQUEUEGROWTH;
+ if ((yylexemes = (YYINT *)realloc(yylexemes, s * sizeof(YYINT))) == NULL)
+ goto yyenomem;
+ if ((yylvals = (YYSTYPE *)realloc(yylvals, s * sizeof(YYSTYPE))) == NULL)
+ goto yyenomem;
+#if defined(YYLTYPE) || defined(YYLTYPE_IS_DECLARED)
+ if ((yylpsns = (YYLTYPE *)realloc(yylpsns, s * sizeof(YYLTYPE))) == NULL)
+ goto yyenomem;
+#endif
+ yylvp = yylve = yylvals + p;
+ yylvlim = yylvals + s;
+#if defined(YYLTYPE) || defined(YYLTYPE_IS_DECLARED)
+ yylpp = yylpe = yylpsns + p;
+ yylplim = yylpsns + s;
+#endif
+ yylexp = yylexemes + p;
+ }
+ *yylexp = (YYINT) YYLEX;
+ *yylvp++ = yylval;
+ yylve++;
+#if defined(YYLTYPE) || defined(YYLTYPE_IS_DECLARED)
+ *yylpp++ = yylloc;
+ yylpe++;
+#endif
+ yychar = *yylexp++;
+ break;
+ }
+ /* normal operation, no conflict encountered */
+#endif /* YYBTYACC */
+ yychar = YYLEX;
+#if YYBTYACC
+ } while (0);
+#endif /* YYBTYACC */
+ if (yychar < 0) yychar = YYEOF;
+#if YYDEBUG
+ if (yydebug)
+ {
+ if ((yys = yyname[YYTRANSLATE(yychar)]) == NULL) yys = yyname[YYUNDFTOKEN];
+ fprintf(stderr, "%s[%d]: state %d, reading token %d (%s)\n",
+ YYDEBUGSTR, yydepth, YYFINAL, yychar, yys);
+ }
+#endif
+ }
+ if (yychar == YYEOF) goto yyaccept;
+ goto yyloop;
+ }
+ if (((yyn = yygindex[yym]) != 0) && (yyn += yystate) >= 0 &&
+ yyn <= YYTABLESIZE && yycheck[yyn] == (YYINT) yystate)
+ yystate = yytable[yyn];
+ else
+ yystate = yydgoto[yym];
+#if YYDEBUG
+ if (yydebug)
+ {
+ fprintf(stderr, "%s[%d]: after reduction, ", YYDEBUGSTR, yydepth);
+#ifdef YYSTYPE_TOSTRING
+#if YYBTYACC
+ if (!yytrial)
+#endif /* YYBTYACC */
+ fprintf(stderr, "result is <%s>, ", YYSTYPE_TOSTRING(yystos[yystate], yyval));
+#endif
+ fprintf(stderr, "shifting from state %d to state %d\n", *yystack.s_mark, yystate);
+ }
+#endif
+ if (yystack.s_mark >= yystack.s_last && yygrowstack(&yystack) == YYENOMEM) goto yyoverflow;
+ *++yystack.s_mark = (YYINT) yystate;
+ *++yystack.l_mark = yyval;
+#if defined(YYLTYPE) || defined(YYLTYPE_IS_DECLARED)
+ *++yystack.p_mark = yyloc;
+#endif
+ goto yyloop;
+#if YYBTYACC
+
+ /* Reduction declares that this path is valid. Set yypath and do a full parse */
+yyvalid:
+ if (yypath) YYABORT;
+ while (yyps->save)
+ {
+ YYParseState *save = yyps->save;
+ yyps->save = save->save;
+ save->save = yypath;
+ yypath = save;
+ }
+#if YYDEBUG
+ if (yydebug)
+ fprintf(stderr, "%s[%d]: state %d, CONFLICT trial successful, backtracking to state %d, %d tokens\n",
+ YYDEBUGSTR, yydepth, yystate, yypath->state, (int)(yylvp - yylvals - yypath->lexeme));
+#endif
+ if (yyerrctx)
+ {
+ yyFreeState(yyerrctx);
+ yyerrctx = NULL;
+ }
+ yylvp = yylvals + yypath->lexeme;
+#if defined(YYLTYPE) || defined(YYLTYPE_IS_DECLARED)
+ yylpp = yylpsns + yypath->lexeme;
+#endif
+ yylexp = yylexemes + yypath->lexeme;
+ yychar = YYEMPTY;
+ yystack.s_mark = yystack.s_base + (yypath->yystack.s_mark - yypath->yystack.s_base);
+ memcpy (yystack.s_base, yypath->yystack.s_base, (size_t) (yystack.s_mark - yystack.s_base + 1) * sizeof(YYINT));
+ yystack.l_mark = yystack.l_base + (yypath->yystack.l_mark - yypath->yystack.l_base);
+ memcpy (yystack.l_base, yypath->yystack.l_base, (size_t) (yystack.l_mark - yystack.l_base + 1) * sizeof(YYSTYPE));
+#if defined(YYLTYPE) || defined(YYLTYPE_IS_DECLARED)
+ yystack.p_mark = yystack.p_base + (yypath->yystack.p_mark - yypath->yystack.p_base);
+ memcpy (yystack.p_base, yypath->yystack.p_base, (size_t) (yystack.p_mark - yystack.p_base + 1) * sizeof(YYLTYPE));
+#endif
+ yystate = yypath->state;
+ goto yyloop;
+#endif /* YYBTYACC */
+
+yyoverflow:
+ YYERROR_CALL("yacc stack overflow");
+#if YYBTYACC
+ goto yyabort_nomem;
+yyenomem:
+ YYERROR_CALL("memory exhausted");
+yyabort_nomem:
+#endif /* YYBTYACC */
+ yyresult = 2;
+ goto yyreturn;
+
+yyabort:
+ yyresult = 1;
+ goto yyreturn;
+
+yyaccept:
+#if YYBTYACC
+ if (yyps->save) goto yyvalid;
+#endif /* YYBTYACC */
+ yyresult = 0;
+
+yyreturn:
+#if defined(YYDESTRUCT_CALL)
+ if (yychar != YYEOF && yychar != YYEMPTY)
+#if defined(YYLTYPE) || defined(YYLTYPE_IS_DECLARED)
+ YYDESTRUCT_CALL("cleanup: discarding token", yychar, &yylval, &yylloc);
+#else
+ YYDESTRUCT_CALL("cleanup: discarding token", yychar, &yylval);
+#endif /* defined(YYLTYPE) || defined(YYLTYPE_IS_DECLARED) */
+
+ {
+ YYSTYPE *pv;
+#if defined(YYLTYPE) || defined(YYLTYPE_IS_DECLARED)
+ YYLTYPE *pp;
+
+ for (pv = yystack.l_base, pp = yystack.p_base; pv <= yystack.l_mark; ++pv, ++pp)
+ YYDESTRUCT_CALL("cleanup: discarding state",
+ yystos[*(yystack.s_base + (pv - yystack.l_base))], pv, pp);
+#else
+ for (pv = yystack.l_base; pv <= yystack.l_mark; ++pv)
+ YYDESTRUCT_CALL("cleanup: discarding state",
+ yystos[*(yystack.s_base + (pv - yystack.l_base))], pv);
+#endif /* defined(YYLTYPE) || defined(YYLTYPE_IS_DECLARED) */
+ }
+#endif /* defined(YYDESTRUCT_CALL) */
+
+#if YYBTYACC
+ if (yyerrctx)
+ {
+ yyFreeState(yyerrctx);
+ yyerrctx = NULL;
+ }
+ while (yyps)
+ {
+ YYParseState *save = yyps;
+ yyps = save->save;
+ save->save = NULL;
+ yyFreeState(save);
+ }
+ while (yypath)
+ {
+ YYParseState *save = yypath;
+ yypath = save->save;
+ save->save = NULL;
+ yyFreeState(save);
+ }
+#endif /* YYBTYACC */
+ yyfreestack(&yystack);
+ return (yyresult);
+}
diff --git a/src/parsdate.h b/src/parsdate.h
new file mode 100644
index 0000000..9853f50
--- /dev/null
+++ b/src/parsdate.h
@@ -0,0 +1,21 @@
+/* $LynxId: parsdate.h,v 1.2 2010/10/31 17:56:22 tom Exp $ */
+#ifndef PARSDATE_H
+#define PARSDATE_H
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+#include <LYUtils.h>
+#define ARRAY_SIZE(array) ((int) (sizeof(array) / sizeof(array[0])))
+ typedef struct _TIMEINFO {
+ time_t time;
+ long usec;
+ long tzone;
+ } TIMEINFO;
+
+ extern time_t parsedate(char *p, TIMEINFO * now);
+
+#ifdef __cplusplus
+}
+#endif
+#endif /* PARSDATE_H */
diff --git a/src/parsdate.y b/src/parsdate.y
new file mode 100644
index 0000000..faab5e5
--- /dev/null
+++ b/src/parsdate.y
@@ -0,0 +1,991 @@
+%{
+
+#include <LYLeaks.h>
+
+/*
+ * $LynxId: parsdate.y,v 1.32 2024/01/15 11:09:08 tom Exp $
+ *
+ * This module is adapted and extended from tin, to use for LYmktime().
+ *
+ * Project : tin - a Usenet reader
+ * Module : parsedate.y
+ * Author : S. Bellovin, R. $alz, J. Berets, P. Eggert
+ * Created : 1990-08-01
+ * Updated : 2019-08-28 (by Thomas Dickey, for Lynx)
+ * Notes : This grammar has 8 shift/reduce conflicts.
+ *
+ * Originally written by Steven M. Bellovin <smb@research.att.com>
+ * while at the University of North Carolina at Chapel Hill.
+ * Later tweaked by a couple of people on Usenet. Completely
+ * overhauled by Rich $alz <rsalz@osf.org> and Jim Berets
+ * <jberets@bbn.com> in August, 1990.
+ *
+ * Further revised (removed obsolete constructs and cleaned up
+ * timezone names) in August, 1991, by Rich.
+ * Paul Eggert <eggert@twinsun.com> helped in September 1992.
+ * Roland Rosenfeld added MET DST code in April 1994.
+ *
+ * Revision : 1.13
+ * Copyright : This code is in the public domain and has no copyright.
+ */
+
+/* SUPPRESS 530 */ /* Empty body for statement */
+/* SUPPRESS 593 on yyerrlab */ /* Label was not used */
+/* SUPPRESS 593 on yynewstate */ /* Label was not used */
+/* SUPPRESS 595 on yypvt */ /* Automatic variable may be used before set */
+
+#undef alloca /* conflicting def may be set by yacc */
+#include <parsdate.h>
+
+/*
+** Get the number of elements in a fixed-size array, or a pointer just
+** past the end of it.
+*/
+#define ENDOF(array) (&array[ARRAY_SIZE(array)])
+
+#ifdef EBCDIC
+#define TO_ASCII(c) TOASCII(c)
+#define TO_LOCAL(c) FROMASCII(c)
+#else
+#define TO_ASCII(c) (c)
+#define TO_LOCAL(c) (c)
+#endif
+
+#define IS7BIT(x) ((unsigned) TO_ASCII(x) < 128)
+#define CTYPE(isXXXXX, c) (IS7BIT(c) && isXXXXX(((unsigned char)c)))
+
+typedef char *PD_STRING;
+
+extern int date_parse(void);
+
+#define yyparse date_parse
+#define yylex date_lex
+#define yyerror date_error
+
+#define BAD_TIME ((time_t)-1)
+#define isBadTime(n) ((n) != 0 && (((n) == BAD_TIME) || !((n) > 0)))
+
+ /* See the LeapYears table in Convert. */
+#define EPOCH 1970
+#define END_OF_TIME 2200
+
+ /* Constants for general time calculations. */
+#define DST_OFFSET 1
+#define SECSPERDAY (24L * 60L * 60L)
+ /* Readability for TABLE stuff. */
+#define HOUR(x) (x * 60)
+
+#define LPAREN '('
+#define RPAREN ')'
+
+/*
+** Daylight-savings mode: on, off, or not yet known.
+*/
+typedef enum _DSTMODE {
+ DSTon, DSToff, DSTmaybe
+} DSTMODE;
+
+/*
+** Meridian: am, pm, or 24-hour style.
+*/
+typedef enum _MERIDIAN {
+ MERam, MERpm, MER24
+} MERIDIAN;
+
+/*
+** Global variables. We could get rid of most of them by using a yacc
+** union, but this is more efficient. (This routine predates the
+** yacc %union construct.)
+*/
+static char *yyInput;
+static DSTMODE yyDSTmode;
+static int yyHaveDate;
+static int yyHaveRel;
+static int yyHaveTime;
+static time_t yyTimezone;
+static time_t yyDay;
+static time_t yyHour;
+static time_t yyMinutes;
+static time_t yyMonth;
+static time_t yySeconds;
+static time_t yyYear;
+static MERIDIAN yyMeridian;
+static time_t yyRelMonth;
+static time_t yyRelSeconds;
+
+static time_t ToSeconds(time_t, time_t, time_t, MERIDIAN);
+static time_t Convert(time_t, time_t, time_t, time_t, time_t, time_t,
+ MERIDIAN, DSTMODE);
+static time_t DSTcorrect(time_t, time_t);
+static time_t RelativeMonth(time_t, time_t);
+static int LookupWord(char *, int);
+static int date_lex(void);
+static int GetTimeInfo(TIMEINFO * Now);
+
+/*
+ * The 'date_error()' function is declared here to work around a defect in
+ * bison 1.22, which redefines 'const' further down in this file, making it
+ * impossible to put a prototype here, and the function later. We're using
+ * 'const' on the parameter to quiet gcc's -Wwrite-strings warning.
+ */
+/*ARGSUSED*/
+static void date_error(const char GCC_UNUSED *s)
+{
+ /*NOTREACHED */
+}
+
+%}
+
+%union {
+ time_t Number;
+ enum _MERIDIAN Meridian;
+}
+
+%token tDAY tDAYZONE tMERIDIAN tMONTH tMONTH_UNIT tSEC_UNIT tSNUMBER
+%token tUNUMBER tZONE tDST
+
+%type <Number> tDAYZONE tMONTH tMONTH_UNIT tSEC_UNIT
+%type <Number> tSNUMBER tUNUMBER tZONE numzone zone
+%type <Meridian> tMERIDIAN o_merid
+
+%%
+
+spec : /* NULL */
+ | spec item
+ ;
+
+item : time {
+ yyHaveTime++;
+#if defined(lint)
+ /* I am compulsive about lint natterings... */
+ if (yyHaveTime == -1) {
+ YYERROR;
+ }
+#endif /* defined(lint) */
+ }
+ | time zone {
+ yyHaveTime++;
+ yyTimezone = $2;
+ }
+ | date {
+ yyHaveDate++;
+ }
+ | both {
+ yyHaveDate++;
+ yyHaveTime++;
+ }
+ | both zone {
+ yyHaveDate++;
+ yyHaveTime++;
+ yyTimezone = $2;
+ }
+ | rel {
+ yyHaveRel = 1;
+ }
+ ;
+
+time : tUNUMBER o_merid {
+ if ($1 < 100) {
+ yyHour = $1;
+ yyMinutes = 0;
+ }
+ else {
+ yyHour = $1 / 100;
+ yyMinutes = $1 % 100;
+ }
+ yySeconds = 0;
+ yyMeridian = $2;
+ }
+ | tUNUMBER ':' tUNUMBER o_merid {
+ yyHour = $1;
+ yyMinutes = $3;
+ yySeconds = 0;
+ yyMeridian = $4;
+ }
+ | tUNUMBER ':' tUNUMBER numzone {
+ yyHour = $1;
+ yyMinutes = $3;
+ yyTimezone = $4;
+ yyMeridian = MER24;
+ yyDSTmode = DSToff;
+ }
+ | tUNUMBER ':' tUNUMBER ':' tUNUMBER o_merid {
+ yyHour = $1;
+ yyMinutes = $3;
+ yySeconds = $5;
+ yyMeridian = $6;
+ }
+ | tUNUMBER ':' tUNUMBER ':' tUNUMBER numzone {
+ yyHour = $1;
+ yyMinutes = $3;
+ yySeconds = $5;
+ yyTimezone = $6;
+ yyMeridian = MER24;
+ yyDSTmode = DSToff;
+ }
+ ;
+
+zone : tZONE {
+ $$ = $1;
+ yyDSTmode = DSToff;
+ }
+ | tDAYZONE {
+ $$ = $1;
+ yyDSTmode = DSTon;
+ }
+ | tDAYZONE tDST {
+ yyTimezone = $1;
+ yyDSTmode = DSTon;
+ }
+ | tZONE numzone {
+ /* Only allow "GMT+300" and "GMT-0800" */
+ if ($1 != 0) {
+ YYABORT;
+ }
+ $$ = $2;
+ yyDSTmode = DSToff;
+ }
+ | numzone {
+ $$ = $1;
+ yyDSTmode = DSToff;
+ }
+ ;
+
+numzone : tSNUMBER {
+ int i;
+
+ /* Unix and GMT and numeric timezones -- a little confusing. */
+ if ((long)$1 < 0) {
+ /* Don't work with negative modulus. */
+ $1 = -(int)$1;
+ if ($1 > 9999 || (i = (int) ($1 % 100)) >= 60) {
+ YYABORT;
+ }
+ $$ = ($1 / 100) * 60 + i;
+ }
+ else {
+ if ($1 > 9999 || (i = (int) ($1 % 100)) >= 60) {
+ YYABORT;
+ }
+ $$ = -(($1 / 100) * 60 + i);
+ }
+ }
+ ;
+
+date : tUNUMBER '/' tUNUMBER {
+ yyMonth = $1;
+ yyDay = $3;
+ }
+ | tUNUMBER '/' tUNUMBER '/' tUNUMBER {
+ if ($1 > 100) {
+ yyYear = $1;
+ yyMonth = $3;
+ yyDay = $5;
+ }
+ else {
+ yyMonth = $1;
+ yyDay = $3;
+ yyYear = $5;
+ }
+ }
+ | tMONTH tUNUMBER {
+ yyMonth = $1;
+ yyDay = $2;
+ }
+ | tMONTH tUNUMBER ',' tUNUMBER {
+ yyMonth = $1;
+ yyDay = $2;
+ yyYear = $4;
+ }
+ | tUNUMBER tMONTH {
+ yyDay = $1;
+ yyMonth = $2;
+ }
+ | tUNUMBER tMONTH tUNUMBER {
+ yyDay = $1;
+ yyMonth = $2;
+ yyYear = $3;
+ }
+ | tDAY ',' tUNUMBER tMONTH tUNUMBER {
+ yyDay = $3;
+ yyMonth = $4;
+ yyYear = $5;
+ }
+ | tDAY ',' tUNUMBER '-' tMONTH tSNUMBER {
+ yyDay = $3;
+ yyMonth = $5;
+ yyYear = -$6;
+ }
+ | tUNUMBER tSNUMBER tSNUMBER {
+ yyDay = $1;
+ yyMonth = -$2;
+ yyYear = -$3;
+ yyDSTmode = DSToff; /* assume midnight if no time given */
+ yyTimezone = 0; /* Lynx assumes GMT for this format */
+ }
+ ;
+
+both : tDAY tMONTH tUNUMBER tUNUMBER ':' tUNUMBER ':' tUNUMBER tUNUMBER {
+ yyMonth = $2;
+ yyDay = $3;
+ yyYear = $9;
+ yyHour = $4;
+ yyMinutes = $6;
+ yySeconds = $8;
+ }
+ ;
+
+rel : tSNUMBER tSEC_UNIT {
+ yyRelSeconds += $1 * $2;
+ }
+ | tUNUMBER tSEC_UNIT {
+ yyRelSeconds += $1 * $2;
+ }
+ | tSNUMBER tMONTH_UNIT {
+ yyRelMonth += $1 * $2;
+ }
+ | tUNUMBER tMONTH_UNIT {
+ yyRelMonth += $1 * $2;
+ }
+ ;
+
+o_merid : /* NULL */ {
+ $$ = MER24;
+ }
+ | tMERIDIAN {
+ $$ = $1;
+ }
+ ;
+
+%%
+
+
+/*
+** An entry in the lexical lookup table.
+*/
+/* *INDENT-OFF* */
+typedef struct _TABLE {
+ const char *name;
+ int type;
+ time_t value;
+} TABLE;
+
+/* Month and day table. */
+static const TABLE MonthDayTable[] = {
+ { "january", tMONTH, 1 },
+ { "february", tMONTH, 2 },
+ { "march", tMONTH, 3 },
+ { "april", tMONTH, 4 },
+ { "may", tMONTH, 5 },
+ { "june", tMONTH, 6 },
+ { "july", tMONTH, 7 },
+ { "august", tMONTH, 8 },
+ { "september", tMONTH, 9 },
+ { "october", tMONTH, 10 },
+ { "november", tMONTH, 11 },
+ { "december", tMONTH, 12 },
+ /* The value of the day isn't used... */
+ { "sunday", tDAY, 0 },
+ { "monday", tDAY, 0 },
+ { "tuesday", tDAY, 0 },
+ { "wednesday", tDAY, 0 },
+ { "thursday", tDAY, 0 },
+ { "friday", tDAY, 0 },
+ { "saturday", tDAY, 0 },
+};
+
+/* Time units table. */
+static const TABLE UnitsTable[] = {
+ { "year", tMONTH_UNIT, 12 },
+ { "month", tMONTH_UNIT, 1 },
+ { "week", tSEC_UNIT, 7 * 24 * 60 * 60 },
+ { "day", tSEC_UNIT, 1 * 24 * 60 * 60 },
+ { "hour", tSEC_UNIT, 60 * 60 },
+ { "minute", tSEC_UNIT, 60 },
+ { "min", tSEC_UNIT, 60 },
+ { "second", tSEC_UNIT, 1 },
+ { "sec", tSEC_UNIT, 1 },
+};
+
+/* Timezone table. */
+static const TABLE TimezoneTable[] = {
+ { "gmt", tZONE, HOUR( 0) }, /* Greenwich Mean */
+ { "ut", tZONE, HOUR( 0) }, /* Universal */
+ { "utc", tZONE, HOUR( 0) }, /* Universal Coordinated */
+ { "cut", tZONE, HOUR( 0) }, /* Coordinated Universal */
+ { "z", tZONE, HOUR( 0) }, /* Greenwich Mean */
+ { "wet", tZONE, HOUR( 0) }, /* Western European */
+ { "bst", tDAYZONE, HOUR( 0) }, /* British Summer */
+ { "nst", tZONE, HOUR(3)+30 }, /* Newfoundland Standard */
+ { "ndt", tDAYZONE, HOUR(3)+30 }, /* Newfoundland Daylight */
+ { "ast", tZONE, HOUR( 4) }, /* Atlantic Standard */
+ { "adt", tDAYZONE, HOUR( 4) }, /* Atlantic Daylight */
+ { "est", tZONE, HOUR( 5) }, /* Eastern Standard */
+ { "edt", tDAYZONE, HOUR( 5) }, /* Eastern Daylight */
+ { "cst", tZONE, HOUR( 6) }, /* Central Standard */
+ { "cdt", tDAYZONE, HOUR( 6) }, /* Central Daylight */
+ { "mst", tZONE, HOUR( 7) }, /* Mountain Standard */
+ { "mdt", tDAYZONE, HOUR( 7) }, /* Mountain Daylight */
+ { "pst", tZONE, HOUR( 8) }, /* Pacific Standard */
+ { "pdt", tDAYZONE, HOUR( 8) }, /* Pacific Daylight */
+ { "yst", tZONE, HOUR( 9) }, /* Yukon Standard */
+ { "ydt", tDAYZONE, HOUR( 9) }, /* Yukon Daylight */
+ { "akst", tZONE, HOUR( 9) }, /* Alaska Standard */
+ { "akdt", tDAYZONE, HOUR( 9) }, /* Alaska Daylight */
+ { "hst", tZONE, HOUR(10) }, /* Hawaii Standard */
+ { "hast", tZONE, HOUR(10) }, /* Hawaii-Aleutian Standard */
+ { "hadt", tDAYZONE, HOUR(10) }, /* Hawaii-Aleutian Daylight */
+ { "ces", tDAYZONE, -HOUR(1) }, /* Central European Summer */
+ { "cest", tDAYZONE, -HOUR(1) }, /* Central European Summer */
+ { "mez", tZONE, -HOUR(1) }, /* Middle European */
+ { "mezt", tDAYZONE, -HOUR(1) }, /* Middle European Summer */
+ { "cet", tZONE, -HOUR(1) }, /* Central European */
+ { "met", tZONE, -HOUR(1) }, /* Middle European */
+/* Additional aliases for MET / MET DST *************************************/
+ { "mez", tZONE, -HOUR(1) }, /* Middle European */
+ { "mewt", tZONE, -HOUR(1) }, /* Middle European Winter */
+ { "mest", tDAYZONE, -HOUR(1) }, /* Middle European Summer */
+ { "mes", tDAYZONE, -HOUR(1) }, /* Middle European Summer */
+ { "mesz", tDAYZONE, -HOUR(1) }, /* Middle European Summer */
+ { "msz", tDAYZONE, -HOUR(1) }, /* Middle European Summer */
+ { "metdst", tDAYZONE, -HOUR(1) }, /* Middle European Summer */
+/****************************************************************************/
+ { "eet", tZONE, -HOUR(2) }, /* Eastern Europe */
+ { "msk", tZONE, -HOUR(3) }, /* Moscow Winter */
+ { "msd", tDAYZONE, -HOUR(3) }, /* Moscow Summer */
+ { "wast", tZONE, -HOUR(8) }, /* West Australian Standard */
+ { "wadt", tDAYZONE, -HOUR(8) }, /* West Australian Daylight */
+ { "hkt", tZONE, -HOUR(8) }, /* Hong Kong */
+ { "cct", tZONE, -HOUR(8) }, /* China Coast */
+ { "jst", tZONE, -HOUR(9) }, /* Japan Standard */
+ { "kst", tZONE, -HOUR(9) }, /* Korean Standard */
+ { "kdt", tZONE, -HOUR(9) }, /* Korean Daylight */
+ { "cast", tZONE, -(HOUR(9)+30) }, /* Central Australian Standard */
+ { "cadt", tDAYZONE, -(HOUR(9)+30) }, /* Central Australian Daylight */
+ { "east", tZONE, -HOUR(10) }, /* Eastern Australian Standard */
+ { "eadt", tDAYZONE, -HOUR(10) }, /* Eastern Australian Daylight */
+ { "nzst", tZONE, -HOUR(12) }, /* New Zealand Standard */
+ { "nzdt", tDAYZONE, -HOUR(12) }, /* New Zealand Daylight */
+
+ /* For completeness we include the following entries. */
+#if 0
+
+ /* Duplicate names. Either they conflict with a zone listed above
+ * (which is either more likely to be seen or just been in circulation
+ * longer), or they conflict with another zone in this section and
+ * we could not reasonably choose one over the other. */
+ { "fst", tZONE, HOUR( 2) }, /* Fernando De Noronha Standard */
+ { "fdt", tDAYZONE, HOUR( 2) }, /* Fernando De Noronha Daylight */
+ { "bst", tZONE, HOUR( 3) }, /* Brazil Standard */
+ { "est", tZONE, HOUR( 3) }, /* Eastern Standard (Brazil) */
+ { "edt", tDAYZONE, HOUR( 3) }, /* Eastern Daylight (Brazil) */
+ { "wst", tZONE, HOUR( 4) }, /* Western Standard (Brazil) */
+ { "wdt", tDAYZONE, HOUR( 4) }, /* Western Daylight (Brazil) */
+ { "cst", tZONE, HOUR( 5) }, /* Chile Standard */
+ { "cdt", tDAYZONE, HOUR( 5) }, /* Chile Daylight */
+ { "ast", tZONE, HOUR( 5) }, /* Acre Standard */
+ { "adt", tDAYZONE, HOUR( 5) }, /* Acre Daylight */
+ { "cst", tZONE, HOUR( 5) }, /* Cuba Standard */
+ { "cdt", tDAYZONE, HOUR( 5) }, /* Cuba Daylight */
+ { "est", tZONE, HOUR( 6) }, /* Easter Island Standard */
+ { "edt", tDAYZONE, HOUR( 6) }, /* Easter Island Daylight */
+ { "sst", tZONE, HOUR(11) }, /* Samoa Standard */
+ { "ist", tZONE, -HOUR(2) }, /* Israel Standard */
+ { "idt", tDAYZONE, -HOUR(2) }, /* Israel Daylight */
+ { "idt", tDAYZONE, -(HOUR(3)+30) }, /* Iran Daylight */
+ { "ist", tZONE, -(HOUR(3)+30) }, /* Iran Standard */
+ { "cst", tZONE, -HOUR(8) }, /* China Standard */
+ { "cdt", tDAYZONE, -HOUR(8) }, /* China Daylight */
+ { "sst", tZONE, -HOUR(8) }, /* Singapore Standard */
+
+ /* Dubious (e.g., not in Olson's TIMEZONE package) or obsolete. */
+ { "gst", tZONE, HOUR( 3) }, /* Greenland Standard */
+ { "wat", tZONE, -HOUR(1) }, /* West Africa */
+ { "at", tZONE, HOUR( 2) }, /* Azores */
+ { "gst", tZONE, -HOUR(10) }, /* Guam Standard */
+ { "nft", tZONE, HOUR(3)+30 }, /* Newfoundland */
+ { "idlw", tZONE, HOUR(12) }, /* International Date Line West */
+ { "mewt", tZONE, -HOUR(1) }, /* Middle European Winter */
+ { "mest", tDAYZONE, -HOUR(1) }, /* Middle European Summer */
+ { "swt", tZONE, -HOUR(1) }, /* Swedish Winter */
+ { "sst", tDAYZONE, -HOUR(1) }, /* Swedish Summer */
+ { "fwt", tZONE, -HOUR(1) }, /* French Winter */
+ { "fst", tDAYZONE, -HOUR(1) }, /* French Summer */
+ { "bt", tZONE, -HOUR(3) }, /* Baghdad */
+ { "it", tZONE, -(HOUR(3)+30) }, /* Iran */
+ { "zp4", tZONE, -HOUR(4) }, /* USSR Zone 3 */
+ { "zp5", tZONE, -HOUR(5) }, /* USSR Zone 4 */
+ { "ist", tZONE, -(HOUR(5)+30) }, /* Indian Standard */
+ { "zp6", tZONE, -HOUR(6) }, /* USSR Zone 5 */
+ { "nst", tZONE, -HOUR(7) }, /* North Sumatra */
+ { "sst", tZONE, -HOUR(7) }, /* South Sumatra */
+ { "jt", tZONE, -(HOUR(7)+30) }, /* Java (3pm in Cronusland!) */
+ { "nzt", tZONE, -HOUR(12) }, /* New Zealand */
+ { "idle", tZONE, -HOUR(12) }, /* International Date Line East */
+ { "cat", tZONE, HOUR(10) }, /* -- expired 1967 */
+ { "nt", tZONE, HOUR(11) }, /* -- expired 1967 */
+ { "ahst", tZONE, HOUR(10) }, /* -- expired 1983 */
+ { "hdt", tDAYZONE, HOUR(10) }, /* -- expired 1986 */
+#endif /* 0 */
+};
+/* *INDENT-ON* */
+
+static time_t ToSeconds(time_t Hours, time_t Minutes, time_t Seconds, MERIDIAN Meridian)
+{
+ if (isBadTime(Minutes) || Minutes > 59 || isBadTime(Seconds) || Seconds > 61)
+ return BAD_TIME;
+ if (Meridian == MER24) {
+ if (isBadTime(Hours) || Hours > 23)
+ return BAD_TIME;
+ } else {
+ if (Hours < 1 || Hours > 12)
+ return BAD_TIME;
+ if (Hours == 12)
+ Hours = 0;
+ if (Meridian == MERpm)
+ Hours += 12;
+ }
+ return (Hours * 60L + Minutes) * 60L + Seconds;
+}
+
+static time_t Convert(time_t Month, time_t Day, time_t Year, time_t Hours,
+ time_t Minutes, time_t Seconds, MERIDIAN Meridian,
+ DSTMODE dst)
+{
+ static const int DaysNormal[13] =
+ {
+ 0, 31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31
+ };
+ static const int DaysLeap[13] =
+ {
+ 0, 31, 29, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31
+ };
+ /* *INDENT-OFF* */
+ static const int LeapYears[] =
+ {
+ 1972, 1976, 1980, 1984, 1988, 1992, 1996,
+ 2000, 2004, 2008, 2012, 2016, 2020, 2024, 2028, 2032, 2036,
+ 2040, 2044, 2048, 2052, 2056, 2060, 2064, 2068, 2072, 2076,
+ 2080, 2084, 2088, 2092, 2096, /***/ 2104, 2108, 2112, 2116,
+ 2120, 2124, 2128, 2132, 2136, 2140, 2144, 2148, 2152, 2156,
+ 2160, 2164, 2168, 2172, 2176, 2180, 2184, 2188, 2192, 2196,
+ };
+ /* *INDENT-ON* */
+
+ const int *yp;
+ const int *mp;
+ int i;
+ time_t Julian;
+ time_t tod;
+
+ if (isBadTime(Year))
+ Year = -Year;
+ if (Year < 70)
+ Year += 2000;
+ if (Year < 100)
+ Year += 1900;
+ if (Year < EPOCH)
+ Year += 100;
+ for (mp = DaysNormal, yp = LeapYears; yp < ENDOF(LeapYears); yp++)
+ if (Year == *yp) {
+ mp = DaysLeap;
+ break;
+ }
+ if (Year < EPOCH || Year > END_OF_TIME
+ || Month < 1 || Month > 12
+ /* NOSTRICT */
+ /* conversion from long may lose accuracy */
+ || Day < 1 || Day > mp[(int) Month]) {
+ return BAD_TIME;
+ }
+
+ Julian = Day - 1 + (Year - EPOCH) * 365;
+ for (yp = LeapYears; yp < ENDOF(LeapYears); yp++, Julian++) {
+ if (Year <= *yp)
+ break;
+ }
+ for (i = 1; i < Month; i++)
+ Julian += *++mp;
+ Julian *= SECSPERDAY;
+ Julian += yyTimezone * 60L;
+ tod = ToSeconds(Hours, Minutes, Seconds, Meridian);
+ if (isBadTime(tod)) {
+ return BAD_TIME;
+ }
+ Julian += tod;
+ tod = Julian;
+ if (dst == DSTon) {
+ Julian -= DST_OFFSET * 60 * 60;
+ } else if (dst == DSTmaybe) {
+ struct tm *tm = localtime(&tod);
+
+ if (tm != NULL) {
+ if (tm->tm_isdst)
+ Julian -= DST_OFFSET * 60 * 60;
+ } else {
+ Julian = BAD_TIME;
+ }
+ }
+ return Julian;
+}
+
+static time_t DSTcorrect(time_t Start, time_t Future)
+{
+ struct tm *tm;
+ time_t StartDay;
+ time_t FutureDay;
+
+ if ((tm = localtime(&Start)) == NULL)
+ return BAD_TIME;
+ StartDay = (tm->tm_hour + 1) % 24;
+
+ if ((tm = localtime(&Future)) == NULL)
+ return BAD_TIME;
+ FutureDay = (tm->tm_hour + 1) % 24;
+
+ return (Future - Start) + (StartDay - FutureDay) * DST_OFFSET * 60 * 60;
+}
+
+static time_t RelativeMonth(time_t Start, time_t RelMonth)
+{
+ struct tm *tm;
+ time_t Month;
+ time_t Year;
+
+ if ((tm = localtime(&Start)) == NULL)
+ return BAD_TIME;
+
+ Month = 12 * tm->tm_year + tm->tm_mon + RelMonth;
+ Year = Month / 12 + 1900;
+ Month = Month % 12 + 1;
+ return DSTcorrect(Start,
+ Convert(Month, (time_t) tm->tm_mday, Year,
+ (time_t) tm->tm_hour, (time_t) tm->tm_min,
+ (time_t) tm->tm_sec,
+ MER24, DSTmaybe));
+}
+
+static int LookupWord(char *buff,
+ int length)
+{
+ char *p;
+ const char *q;
+ const TABLE *tp;
+ int c;
+
+ p = buff;
+ c = p[0];
+
+ /* See if we have an abbreviation for a month. */
+ if (length == 3 || (length == 4 && p[3] == '.')) {
+ for (tp = MonthDayTable; tp < ENDOF(MonthDayTable); tp++) {
+ q = tp->name;
+ if (c == q[0] && p[1] == q[1] && p[2] == q[2]) {
+ yylval.Number = tp->value;
+ return tp->type;
+ }
+ }
+ } else {
+ for (tp = MonthDayTable; tp < ENDOF(MonthDayTable); tp++) {
+ if (c == tp->name[0] && strcmp(p, tp->name) == 0) {
+ yylval.Number = tp->value;
+ return tp->type;
+ }
+ }
+ }
+
+ /* Try for a timezone. */
+ for (tp = TimezoneTable; tp < ENDOF(TimezoneTable); tp++) {
+ if (c == tp->name[0] && p[1] == tp->name[1]
+ && strcmp(p, tp->name) == 0) {
+ yylval.Number = tp->value;
+ return tp->type;
+ }
+ }
+
+ if (strcmp(buff, "dst") == 0)
+ return tDST;
+
+ /* Try the units table. */
+ for (tp = UnitsTable; tp < ENDOF(UnitsTable); tp++) {
+ if (c == tp->name[0] && strcmp(p, tp->name) == 0) {
+ yylval.Number = tp->value;
+ return tp->type;
+ }
+ }
+
+ /* Strip off any plural and try the units table again. */
+ if (--length > 0 && p[length] == 's') {
+ p[length] = '\0';
+ for (tp = UnitsTable; tp < ENDOF(UnitsTable); tp++) {
+ if (c == tp->name[0] && strcmp(p, tp->name) == 0) {
+ p[length] = 's';
+ yylval.Number = tp->value;
+ return tp->type;
+ }
+ }
+ p[length] = 's';
+ }
+ length++;
+
+ /* Drop out any periods. */
+ for (p = buff, q = (PD_STRING) buff; *q; q++) {
+ if (*q != '.')
+ *p++ = *q;
+ }
+ *p = '\0';
+
+ /* Try the meridians. */
+ if (buff[1] == 'm' && buff[2] == '\0') {
+ if (buff[0] == 'a') {
+ yylval.Meridian = MERam;
+ return tMERIDIAN;
+ }
+ if (buff[0] == 'p') {
+ yylval.Meridian = MERpm;
+ return tMERIDIAN;
+ }
+ }
+
+ /* If we saw any periods, try the timezones again. */
+ if (p - buff != length) {
+ c = buff[0];
+ for (p = buff, tp = TimezoneTable; tp < ENDOF(TimezoneTable); tp++) {
+ if (c == tp->name[0] && p[1] == tp->name[1]
+ && strcmp(p, tp->name) == 0) {
+ yylval.Number = tp->value;
+ return tp->type;
+ }
+ }
+ }
+
+ /* Unknown word -- assume GMT timezone. */
+ yylval.Number = 0;
+ return tZONE;
+}
+
+/*
+ * This returns characters as-is (the ones that are not part of some token),
+ * and codes greater than 256 (the token values).
+ *
+ * yacc generates tables that may use the character value. In particular,
+ * byacc's yycheck[] table contains integer values for the expected codes from
+ * this function, which (unless byacc is run locally) are ASCII codes.
+ *
+ * The TO_LOCAL() function assumes its input is in ASCII, and the output is
+ * whatever native encoding is used on the machine, e.g., EBCDIC.
+ *
+ * The TO_ASCII() function is the inverse of TO_LOCAL().
+ */
+static int date_lex(void)
+{
+ int c;
+ char *p;
+ char buff[20];
+ int sign;
+ int i;
+ int nesting;
+
+ /* Get first character after the whitespace. */
+ for (;;) {
+ while (CTYPE(isspace, *yyInput))
+ yyInput++;
+ c = *yyInput;
+
+ /* Ignore RFC 822 comments, typically time zone names. */
+ if (c != LPAREN)
+ break;
+ for (nesting = 1;
+ (c = *++yyInput) != RPAREN || --nesting;
+ ) {
+ if (c == LPAREN) {
+ nesting++;
+ } else if (!IS7BIT(c) || c == '\0' || c == '\r'
+ || (c == '\\'
+ && ((c = *++yyInput) == '\0'
+ || !IS7BIT(c)))) {
+ /* Lexical error: bad comment. */
+ return '?';
+ }
+ }
+ yyInput++;
+ }
+
+ /* A number? */
+ if (CTYPE(isdigit, c) || c == '-' || c == '+') {
+ if (c == '-' || c == '+') {
+ sign = c == '-' ? -1 : 1;
+ yyInput++;
+ if (!CTYPE(isdigit, *yyInput)) {
+ /* Return the isolated plus or minus sign. */
+ --yyInput;
+ return *yyInput++;
+ }
+ } else {
+ sign = 0;
+ }
+ for (p = buff;
+ (c = *yyInput++) != '\0' && CTYPE(isdigit, c);
+ ) {
+ if (p < &buff[sizeof buff - 1])
+ *p++ = (char) c;
+ }
+ *p = '\0';
+ i = atoi(buff);
+
+ yyInput--;
+ yylval.Number = sign < 0 ? -i : i;
+ return sign ? tSNUMBER : tUNUMBER;
+ }
+
+ /* A word? */
+ if (CTYPE(isalpha, c)) {
+ for (p = buff;
+ (c = *yyInput++) == '.' || CTYPE(isalpha, c);
+ ) {
+ if (p < &buff[sizeof buff - 1])
+ *p++ = (char) (CTYPE(isupper, c) ? tolower(c) : c);
+ }
+ *p = '\0';
+ yyInput--;
+ return LookupWord(buff, (int) (p - buff));
+ }
+
+ return *yyInput++;
+}
+
+static int GetTimeInfo(TIMEINFO * Now)
+{
+ static time_t LastTime;
+ static long LastTzone;
+ struct tm *tm;
+
+#if defined(HAVE_GETTIMEOFDAY)
+ struct timeval tv;
+#endif /* defined(HAVE_GETTIMEOFDAY) */
+#if defined(DONT_HAVE_TM_GMTOFF)
+ struct tm local;
+ struct tm gmt;
+#endif /* !defined(DONT_HAVE_TM_GMTOFF) */
+
+ /* Get the basic time. */
+ memset(Now, 0, sizeof(TIMEINFO));
+#if defined(HAVE_GETTIMEOFDAY)
+ if (gettimeofday(&tv, (struct timezone *) NULL) == -1)
+ return -1;
+ Now->time = tv.tv_sec;
+ Now->usec = tv.tv_usec;
+#else
+ /* Can't check for -1 since that might be a time, I guess. */
+ (void) time(&Now->time);
+ Now->usec = 0;
+#endif /* defined(HAVE_GETTIMEOFDAY) */
+
+ /* Now get the timezone if it's been an hour since the last time. */
+ if (Now->time - LastTime > 60 * 60) {
+ LastTime = Now->time;
+ if ((tm = localtime(&Now->time)) == NULL)
+ return -1;
+#if defined(DONT_HAVE_TM_GMTOFF)
+ /* To get the timezone, compare localtime with GMT. */
+ local = *tm;
+ if ((tm = gmtime(&Now->time)) == NULL)
+ return -1;
+ gmt = *tm;
+
+ /* Assume we are never more than 24 hours away. */
+ LastTzone = gmt.tm_yday - local.tm_yday;
+ if (LastTzone > 1)
+ LastTzone = -24;
+ else if (LastTzone < -1)
+ LastTzone = 24;
+ else
+ LastTzone *= 24;
+
+ /* Scale in the hours and minutes; ignore seconds. */
+ LastTzone += gmt.tm_hour - local.tm_hour;
+ LastTzone *= 60;
+ LastTzone += gmt.tm_min - local.tm_min;
+#else
+ LastTzone = (0 - tm->tm_gmtoff) / 60;
+#endif /* defined(DONT_HAVE_TM_GMTOFF) */
+ }
+ Now->tzone = LastTzone;
+ return 0;
+}
+
+#if defined(YYBYACC) && defined(YYPURE) && defined(LY_FIND_LEAKS)
+#undef YYPURE
+#define YYPURE 1
+static void yyfreestack(YYSTACKDATA *);
+static void parsedate_leaks(void)
+{
+ yyfreestack(&yystack);
+}
+#endif
+
+time_t parsedate(char *p,
+ TIMEINFO * now)
+{
+ struct tm *tm;
+ TIMEINFO ti;
+ time_t Start;
+
+#if defined(YYBYACC) && defined(YYPURE) && defined(LY_FIND_LEAKS)
+ static int initialized;
+
+ if (!initialized) {
+ initialized = 1;
+ atexit(parsedate_leaks);
+ }
+#endif
+
+ yyInput = p;
+ if (now == NULL) {
+ now = &ti;
+ (void) GetTimeInfo(&ti);
+ }
+
+ if ((tm = localtime(&now->time)) == NULL)
+ return BAD_TIME;
+
+ /* *INDENT-EQLS* */
+ yyYear = tm->tm_year + 1900;
+ yyMonth = tm->tm_mon + 1;
+ yyDay = tm->tm_mday;
+ yyTimezone = now->tzone;
+ if (tm->tm_isdst) /* Correct timezone offset for DST */
+ yyTimezone += DST_OFFSET * 60;
+ yyDSTmode = DSTmaybe;
+ yyHour = 0;
+ yyMinutes = 0;
+ yySeconds = 0;
+ yyMeridian = MER24;
+ yyRelSeconds = 0;
+ yyRelMonth = 0;
+ yyHaveDate = 0;
+ yyHaveRel = 0;
+ yyHaveTime = 0;
+
+ if (date_parse() || yyHaveTime > 1 || yyHaveDate > 1)
+ return BAD_TIME;
+
+ if (yyHaveDate || yyHaveTime) {
+ Start = Convert(yyMonth, yyDay, yyYear, yyHour, yyMinutes, yySeconds,
+ yyMeridian, yyDSTmode);
+ if (isBadTime(Start))
+ return BAD_TIME;
+ } else {
+ Start = now->time;
+ if (!yyHaveRel)
+ Start -= (tm->tm_hour * 60L + tm->tm_min) * 60L + tm->tm_sec;
+ }
+
+ Start += yyRelSeconds;
+ if (yyRelMonth)
+ Start += RelativeMonth(Start, yyRelMonth);
+
+ /* Have to do *something* with a legitimate -1 so it's distinguishable
+ * from the error return value. (Alternately could set errno on error.) */
+ return (Start == BAD_TIME) ? 0 : Start;
+}
diff --git a/src/socketshr_tcp.opt b/src/socketshr_tcp.opt
new file mode 100644
index 0000000..f6e3131
--- /dev/null
+++ b/src/socketshr_tcp.opt
@@ -0,0 +1 @@
+socketshr/share
diff --git a/src/strstr.c b/src/strstr.c
new file mode 100644
index 0000000..7dcd7d5
--- /dev/null
+++ b/src/strstr.c
@@ -0,0 +1,60 @@
+/*
+ * strstr.c -- return the offset of one string within another.
+ *
+ * Copyright (C) 1997 Free Software Foundation, Inc.
+ *
+ * This program is free software; you can redistribute it and/or modify it under
+ * the terms of the GNU General Public License as published by the Free
+ * Software Foundation; either version 2, or (at your option) any later
+ * version.
+ *
+ * This program is distributed in the hope that it will be useful, but WITHOUT
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+ * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
+ * more details.
+ *
+ * You should have received a copy of the GNU General Public License along with
+ * this program; if not, write to the Free Software Foundation, Inc., 59
+ * Temple Place - Suite 330, Boston, MA 02111-1307, USA.
+ */
+
+/* Written by Philippe De Muyter <phdm@macqel.be>. */
+
+/*
+ * NAME
+ *
+ * strstr -- locate first occurrence of a substring
+ *
+ * SYNOPSIS
+ *
+ * char *strstr (char *s1, char *s2)
+ *
+ * DESCRIPTION
+ *
+ * Locates the first occurrence in the string pointed to by S1 of the string
+ * pointed to by S2. Returns a pointer to the substring found, or a NULL
+ * pointer if not found. If S2 points to a string with zero length, the
+ * function returns S1.
+ *
+ * BUGS
+ *
+ */
+
+char *strstr(char *buf, char *sub)
+{
+ register char *bp;
+ register char *sp;
+
+ if (!*sub)
+ return buf;
+ while (*buf) {
+ bp = buf;
+ sp = sub;
+ do {
+ if (!*sp)
+ return buf;
+ } while (*bp++ == *sp++);
+ buf += 1;
+ }
+ return 0;
+}
diff --git a/src/structdump.h b/src/structdump.h
new file mode 100644
index 0000000..78e25db
--- /dev/null
+++ b/src/structdump.h
@@ -0,0 +1,164 @@
+/*
+ *
+ * Some macros to dump out formatted struct's via the trace file. -KED
+ *
+ */
+#ifndef STRUCTDUMP_H
+#define STRUCTDUMP_H
+
+/* usage: DUMPSTRUCT_LINK(link_ptr, "message"); */
+#define DUMPSTRUCT_LINK(L,X) \
+if ((L)) { \
+CTRACE((tfp, "\n" \
+ "KED: link_ptr=%p sizeof=%d ["X"]\n" \
+ "link struct {\n" \
+ " *lname=%p\n" \
+ " lname=|%s|\n" \
+ " *target=%p\n" \
+ " target=|%s|\n" \
+ " *hightext=%p\n" \
+ " hightext=|%s|\n" \
+ " *hightext2=%p\n" \
+ " hightext2=|%s|\n" \
+ " hightext2_offset=%d\n" \
+ " inUnderline=%1x\n" \
+ " lx=%d\n" \
+ " ly=%d\n" \
+ " type=%d\n" \
+ " anchor_number=%d\n" \
+ " anchor_line_num=%d\n" \
+ " *form=%p\n" \
+ "}\n", \
+ (L), sizeof(*((L))), \
+ (L)->lname, (L)->lname, (L)->target, (L)->target, \
+ (L)->l_hightext, (L)->l_hightext, \
+ (L)->l_hightext2, (L)->l_hightext2, \
+ (L)->l_hightext2_offset, \
+ (L)->inUnderline, (L)->lx, (L)->ly, \
+ (L)->type, (L)->anchor_number, (L)->anchor_line_num, (L)->form)); \
+}else{ \
+CTRACE((tfp, "\n" \
+ "KED: link_ptr=0x00000000 (NULL) ["X"]\n")); \
+} \
+CTRACE_FLUSH(tfp);
+
+/* usage: DUMPSTRUCT_ANCHOR(anchor_ptr, "message"); */
+#define DUMPSTRUCT_ANCHOR(A,X) \
+if ((A)) { \
+CTRACE((tfp, "\n" \
+ "KED: anchor_ptr=%p sizeof=%lu ["X"]\n" \
+ "TextAnchor struct {\n" \
+ " *next=%p\n" \
+ " *prev=%p\n" \
+ " number=%d\n" \
+ " line_pos=%d\n" \
+ " extent=%d\n" \
+ " line_num=%d\n" \
+ " link_type=%d\n" \
+ " *input_field=%p\n" \
+ " input_field=|%s|\n" \
+ " show_anchor=%1x\n" \
+ " inUnderline=%1x\n" \
+ " expansion_anch=%1x\n" \
+ " *anchor=%p\n" \
+ "}\n", \
+ (A), (unsigned long) sizeof(*((A))), \
+ (A)->next, (A)->prev, \
+ (A)->number, (A)->line_pos, \
+ (A)->extent, (A)->line_num, \
+ (A)->link_type, \
+ (A)->input_field, \
+ (A)->input_field ? NonNull((A)->input_field->name) : "", \
+ (A)->show_anchor, \
+ (A)->inUnderline, (A)->expansion_anch, (A)->anchor)); \
+}else{ \
+CTRACE((tfp, "\n" \
+ "KED: anchor_ptr=0x00000000 (NULL) ["X"]\n")); \
+} \
+CTRACE_FLUSH(tfp);
+
+/* usage: DUMPSTRUCT_FORM(forminfo_ptr, "message"); */
+#define DUMPSTRUCT_FORMINFO(F,X) \
+if ((F)) { \
+CTRACE((tfp, "\n" \
+ "KED: forminfo_ptr=%p sizeof=%lu ["X"]\n" \
+ "FormInfo struct {\n" \
+ " *name=%p\n" \
+ " name=|%s|\n" \
+ " number=%d\n" \
+ " type=%d\n" \
+ " *value=%p\n" \
+ " value=|%s|\n" \
+ " *orig_value=%p\n" \
+ " orig_value=|%s|\n" \
+ " size=%d\n" \
+ " maxlength=%lu\n" \
+ " group=%d\n" \
+ " num_value=%d\n" \
+ " hrange=%d\n" \
+ " lrange=%d\n" \
+ " *select_list=%p\n" \
+ " submit_action=|%s|\n" \
+ " submit_method=%d\n" \
+ " submit_enctype=|%s|\n" \
+ " submit_title=|%s|\n" \
+ " no_cache=%1x\n" \
+ " cp_submit_value=|%s|\n" \
+ "orig_submit_value=|%s|\n" \
+ " size_l=%d\n" \
+ " disabled=%d\n" \
+ " readonly=%d\n" \
+ " name_cs=%d\n" \
+ " value_cs=%d\n" \
+ " accept_cs=|%s|\n" \
+ "}\n", \
+ (F), (unsigned long) sizeof(*((F))), \
+ (F)->name, NonNull((F)->name), \
+ (F)->number, (F)->type, \
+ (F)->value, NonNull((F)->value), \
+ (F)->orig_value, NonNull((F)->orig_value), \
+ (F)->size, (unsigned long) (F)->maxlength, \
+ (F)->group, (F)->num_value, \
+ (F)->hrange, (F)->lrange, (F)->select_list, \
+ NonNull((F)->submit_action), \
+ (F)->submit_method, \
+ NonNull((F)->submit_enctype), \
+ NonNull((F)->submit_title), \
+ (F)->no_cache, \
+ NonNull((F)->cp_submit_value), \
+ NonNull((F)->orig_submit_value), \
+ (F)->size_l, (F)->disabled, (F)->readonly, (F)->name_cs, (F)->value_cs, \
+ NonNull((F)->accept_cs))); \
+} else { \
+CTRACE((tfp, "\n" \
+ "KED: forminfo_ptr=0x00000000 (NULL) ["X"]\n")); \
+} \
+CTRACE_FLUSH(tfp);
+
+/* usage: DUMPSTRUCT_LINE(htline_ptr, "message"); */
+#define DUMPSTRUCT_LINE(L,X) \
+if ((L)) { \
+CTRACE((tfp, "\n" \
+ "KED: htline_ptr=%p sizeof=%d ["X"]\n" \
+ "HTLine struct {\n" \
+ " *next=%p\n" \
+ " *prev=%p\n" \
+ " offset=%d\n" \
+ " size=%d\n" \
+ " split_after=%1x\n" \
+ " bullet=%1x\n" \
+ "expansion_line=%1x\n" \
+ "w/o U_C_S def\n" \
+ " data[]=%p\n" \
+ " data=|%s|\n" \
+ "}\n", \
+ (L), sizeof(*((L))), \
+ (L)->next, (L)->prev, (L)->offset, (L)->size, (L)->split_after, \
+ (L)->bullet, (L)->expansion_line, (L)->data, (L)->data)); \
+}else{ \
+CTRACE((tfp, "\n" \
+ "KED: htline_ptr=0x00000000 (NULL) ["X"]\n")); \
+} \
+CTRACE_FLUSH(tfp);
+
+#endif /* STRUCTDUMP_H */
diff --git a/src/tcpipolb.opt b/src/tcpipolb.opt
new file mode 100644
index 0000000..9c87cc8
--- /dev/null
+++ b/src/tcpipolb.opt
@@ -0,0 +1 @@
+tcpip$library:tcpip$lib/library
diff --git a/src/tcpipshr.opt b/src/tcpipshr.opt
new file mode 100644
index 0000000..729be9f
--- /dev/null
+++ b/src/tcpipshr.opt
@@ -0,0 +1 @@
+tcpip$ipc_shr/share
diff --git a/src/tcpwareolb.opt b/src/tcpwareolb.opt
new file mode 100644
index 0000000..045d770
--- /dev/null
+++ b/src/tcpwareolb.opt
@@ -0,0 +1 @@
+tcpware:ucx$ipc/library
diff --git a/src/tcpwareshr.opt b/src/tcpwareshr.opt
new file mode 100644
index 0000000..e34f846
--- /dev/null
+++ b/src/tcpwareshr.opt
@@ -0,0 +1 @@
+tcpware:ucx$ipc_shr.exe/share
diff --git a/src/tidy_tls.c b/src/tidy_tls.c
new file mode 100644
index 0000000..b0288fb
--- /dev/null
+++ b/src/tidy_tls.c
@@ -0,0 +1,707 @@
+/*
+ * $LynxId: tidy_tls.c,v 1.41 2020/03/03 11:46:07 Gisle.Vanem Exp $
+ * Copyright 2008-2019,2020 Thomas E. Dickey
+ * with fix Copyright 2008 by Thomas Viehmann
+ *
+ * Required libraries:
+ * libgnutls
+ * libcrypt
+ */
+#include <HTUtils.h>
+#include <tidy_tls.h>
+
+#include <gnutls/x509.h>
+#ifdef HAVE_GNUTLS_RND
+#include <gnutls/crypto.h>
+#else
+#include <gcrypt.h>
+#endif
+#include <libtasn1.h> /* ASN1_SUCCESS,etc */
+#include <string.h>
+
+#define typeCalloc(type) (type *) calloc(1, sizeof(type))
+
+static int last_error = 0;
+
+/* ugly, but hey, we could just use a more sane api, too */
+#define GetDnByOID(target, oid, thewhat) \
+ len = sizeof(target); \
+ if (! thewhat) \
+ gnutls_x509_crt_get_dn_by_oid(xcert, oid, 0, 0, target, &len); \
+ else \
+ gnutls_x509_crt_get_issuer_dn_by_oid(xcert, oid, 0, 0, target, &len)
+
+/* thewhat: which DN to get 0 = subject, 1 = issuer */
+static int ExtractCertificate(const gnutls_datum_t *cert, X509_NAME * result, int thewhat)
+{
+ gnutls_x509_crt_t xcert;
+ int rc;
+ size_t len;
+
+ if ((rc = gnutls_x509_crt_init(&xcert)) >= 0) {
+ if ((rc = gnutls_x509_crt_import(xcert, cert, GNUTLS_X509_FMT_DER)) >= 0) {
+ GetDnByOID(result->country,
+ GNUTLS_OID_X520_COUNTRY_NAME, thewhat);
+ GetDnByOID(result->organization,
+ GNUTLS_OID_X520_ORGANIZATION_NAME, thewhat);
+ GetDnByOID(result->organizational_unit_name,
+ GNUTLS_OID_X520_ORGANIZATIONAL_UNIT_NAME, thewhat);
+ GetDnByOID(result->common_name,
+ GNUTLS_OID_X520_COMMON_NAME, thewhat);
+ GetDnByOID(result->locality_name,
+ GNUTLS_OID_X520_LOCALITY_NAME, thewhat);
+ GetDnByOID(result->state_or_province_name,
+ GNUTLS_OID_X520_STATE_OR_PROVINCE_NAME, thewhat);
+ GetDnByOID(result->email,
+ GNUTLS_OID_PKCS9_EMAIL, thewhat);
+ rc = 0;
+ }
+ gnutls_x509_crt_deinit(xcert);
+ }
+ return rc;
+}
+
+/*
+ * (stub)
+ * ERR_error_string() generates a human-readable string representing the
+ * error code 'e', and places it at 'buffer', which must be at least 120 bytes
+ * long. If 'buffer' is NULL, the error string is placed in a static buffer.
+ */
+const char *ERR_error_string(unsigned long e, char *buffer)
+{
+ (void) buffer;
+ return gnutls_strerror((int) -e);
+}
+
+/*
+ * (stub)
+ * Return the earliest error code from the thread's error queue and remove the
+ * entry.
+ */
+unsigned long ERR_get_error(void)
+{
+ unsigned long rc;
+
+ rc = (unsigned long) (-last_error);
+ last_error = 0;
+
+ return rc;
+}
+
+/*
+ * Put 'num' cryptographically strong pseudo-random bytes into 'buffer'.
+ */
+int RAND_bytes(unsigned char *buffer, int num)
+{
+ int rc;
+
+#ifdef HAVE_GNUTLS_RND
+ rc = gnutls_rnd(GNUTLS_RND_KEY, buffer, (size_t) num);
+#else
+ gcry_randomize(buffer, num, GCRY_VERY_STRONG_RANDOM);
+ rc = 1;
+#endif
+ return rc;
+}
+
+/*
+ * (stub)
+ * Generate a default path for the random seed file. 'buffer' points to a
+ * buffer of size 'len' in which to store the filename.
+ */
+const char *RAND_file_name(char *buffer, size_t len)
+{
+ (void) buffer;
+ (void) len;
+ return "";
+}
+
+/*
+ * (stub)
+ * Read a number of bytes from file 'name' and adds them to the PRNG. If
+ * 'maxbytes' is non-negative, up to to 'maxbytes' are read; if 'maxbytes' is
+ * -1, the complete file is read.
+ */
+int RAND_load_file(const char *name, long maxbytes)
+{
+ (void) name;
+ return (int) maxbytes;
+}
+
+/*
+ * (stub)
+ * Mix the 'num' bytes at 'buffer' into the PRNG state.
+ */
+void RAND_seed(const void *buffer, int num)
+{
+ (void) buffer;
+ (void) num;
+}
+
+/*
+ * (stub)
+ * Return 1 if the PRNG has been seeded with enough data, 0 otherwise.
+ */
+int RAND_status(void)
+{
+ return 1;
+}
+
+/*
+ * (stub)
+ * Write a number of random bytes (currently 1024) to file 'name' which can be
+ * used to initialize the PRNG by calling RAND_load_file() in a later session.
+ */
+int RAND_write_file(const char *name)
+{
+ (void) name;
+ return 0;
+}
+
+/*
+ * Return the number of secret bits used for cipher. If 'bits' is not NULL, it
+ * contains the number of bits processed by the chosen algorithm. If cipher is
+ * NULL, 0 is returned.
+ */
+int SSL_CIPHER_get_bits(SSL_CIPHER * cipher, int *bits)
+{
+ int result = 0;
+
+ if (cipher) {
+ result = (8 * (int) gnutls_cipher_get_key_size(cipher->encrypts));
+ }
+
+ if (bits)
+ *bits = result;
+
+ return result;
+}
+
+/*
+ * Return a pointer to the name of 'cipher'. If 'cipher' is NULL the constant
+ * value "NONE" is returned.
+ */
+const char *SSL_CIPHER_get_name(SSL_CIPHER * cipher)
+{
+ const char *result = "NONE";
+
+ if (cipher) {
+ result = gnutls_cipher_suite_get_name(cipher->key_xchg,
+ cipher->encrypts,
+ cipher->msg_code);
+ }
+ return result;
+}
+
+/*
+ * Return the protocol version for cipher, currently "SSLv2", "SSLv3", or
+ * "TLSv1". If cipher is NULL, "(NONE)" is returned.
+ */
+const char *SSL_CIPHER_get_version(SSL_CIPHER * cipher)
+{
+ const char *result = "NONE";
+
+ if (cipher) {
+ if ((result = gnutls_protocol_get_name(cipher->protocol)) == 0)
+ result = "unknown";
+ }
+ return result;
+}
+
+/*
+ * Free an allocated SSL_CTX object.
+ */
+void SSL_CTX_free(SSL_CTX * ctx)
+{
+ free(ctx->method);
+ free(ctx);
+}
+
+/*
+ * Create a new SSL_CTX object as framework for TLS/SSL enabled functions.
+ */
+SSL_CTX *SSL_CTX_new(SSL_METHOD * method)
+{
+ SSL_CTX *ctx;
+
+ if ((ctx = typeCalloc(SSL_CTX)) != 0) {
+ ctx->method = method;
+ }
+
+ return ctx;
+}
+
+/*
+ * See SSL_CTX_load_verify_locations() - this sets the paths for that and
+ * SSL_CTX_set_verify() to their default values. GNU TLS does not have a
+ * comparable feature (stub).
+ */
+int SSL_CTX_set_default_verify_paths(SSL_CTX * ctx)
+{
+ (void) ctx;
+ return 0;
+}
+
+/*
+ * SSL_CTX_set_options() adds the options set via bitmask in options to
+ * ctx. Options already set before are not cleared.
+ */
+unsigned long SSL_CTX_set_options(SSL_CTX * ctx, unsigned long options)
+{
+ ctx->options |= options;
+ return ctx->options;
+}
+
+/*
+ * Set peer certificate verification parameters.
+ */
+void SSL_CTX_set_verify(SSL_CTX * ctx, int verify_mode,
+ int (*verify_callback) (int, X509_STORE_CTX *))
+{
+ ctx->verify_mode = verify_mode;
+ ctx->verify_callback = verify_callback;
+}
+
+#ifdef HAVE_GNUTLS_PROTOCOL_SET_PRIORITY
+static void RemoveProtocol(SSL * ssl, int protocol)
+{
+ int j, k;
+ int changed = 0;
+ int *protocols = ssl->ctx->method->priority.protocol;
+
+ for (j = k = 0; j < GNUTLS_MAX_ALGORITHM_NUM;) {
+ if (protocols[k] == protocol) {
+ if (++k >= GNUTLS_MAX_ALGORITHM_NUM)
+ break;
+ changed = 1;
+ } else {
+ protocols[j++] = protocols[k++];
+ }
+ }
+
+ if (changed) {
+ gnutls_protocol_set_priority(ssl->gnutls_state, protocols);
+ }
+}
+#endif
+
+/*
+ * Initiate the TLS/SSL handshake with an TLS/SSL server.
+ */
+int SSL_connect(SSL * ssl)
+{
+ X509_STORE_CTX *store;
+ int rc;
+ gnutls_alert_description_t alert;
+ const char *aname;
+
+ if (ssl->options & SSL_OP_NO_TLSv1) {
+#ifdef HAVE_GNUTLS_PROTOCOL_SET_PRIORITY
+ RemoveProtocol(ssl, GNUTLS_TLS1);
+#else
+ gnutls_priority_set_direct(ssl->gnutls_state, "NORMAL:-VERS-TLS1.0", NULL);
+#endif
+ }
+
+ while ((rc = gnutls_handshake(ssl->gnutls_state)) < 0 &&
+ !gnutls_error_is_fatal(rc)) {
+ if (rc == GNUTLS_E_WARNING_ALERT_RECEIVED) {
+ alert = gnutls_alert_get(ssl->gnutls_state);
+ aname = gnutls_alert_get_name(alert);
+ CTRACE((tfp, "SSL Alert: %s\n", NonNull(aname)));
+ switch (gnutls_alert_get(ssl->gnutls_state)) {
+ case GNUTLS_A_UNRECOGNIZED_NAME:
+ continue; /* ignore */
+ default:
+ break;
+ }
+ break; /* treat all other alerts as fatal */
+ }
+ }
+ ssl->last_error = rc;
+
+ if (rc < 0) {
+ last_error = rc;
+ return 0;
+ }
+
+ store = typeCalloc(X509_STORE_CTX);
+ if (store == 0)
+ outofmem(__FILE__, "SSL_connect");
+
+ store->ssl = ssl;
+ store->cert_list = SSL_get_peer_certificate(ssl);
+
+ if (ssl->verify_callback) {
+ ssl->verify_callback(1, store);
+ }
+ ssl->state = SSL_ST_OK;
+
+ free(store);
+
+ /* FIXME: deal with error from callback */
+
+ return 1;
+}
+
+/*
+ * Free an allocated SSL structure.
+ */
+void SSL_free(SSL * ssl)
+{
+ gnutls_certificate_free_credentials(ssl->gnutls_cred);
+ gnutls_deinit(ssl->gnutls_state);
+ free(ssl);
+}
+
+/*
+ * Get SSL_CIPHER data of a connection.
+ */
+SSL_CIPHER *SSL_get_current_cipher(SSL * ssl)
+{
+ SSL_CIPHER *result = 0;
+
+ if (ssl) {
+ result = &(ssl->ciphersuite);
+
+ result->protocol = gnutls_protocol_get_version(ssl->gnutls_state);
+ result->encrypts = gnutls_cipher_get(ssl->gnutls_state);
+ result->key_xchg = gnutls_kx_get(ssl->gnutls_state);
+ result->msg_code = gnutls_mac_get(ssl->gnutls_state);
+ result->cert = gnutls_certificate_type_get(ssl->gnutls_state);
+#if !defined(_GNUTLS_GCC_VERSION) || (_GNUTLS_GCC_VERSION < 30100)
+ result->compress = gnutls_compression_get(ssl->gnutls_state);
+#else
+ result->compress = GNUTLS_COMP_UNKNOWN;
+#endif
+ }
+
+ return result;
+}
+
+/*
+ * Get the X509 certificate of the peer.
+ */
+const X509 *SSL_get_peer_certificate(SSL * ssl)
+{
+ const gnutls_datum_t *result;
+ unsigned list_size = 0;
+
+ result =
+ (const gnutls_datum_t *) gnutls_certificate_get_peers(ssl->gnutls_state,
+ &list_size);
+
+ return (const X509 *) result;
+}
+
+/*
+ * Initialize SSL library by registering algorithms.
+ */
+int SSL_library_init(void)
+{
+ gnutls_global_init();
+ return 1;
+}
+
+/*
+ * (stub)
+ * OpenSSL uses this to prepare for ERR_get_error() calls.
+ */
+void SSL_load_error_strings(void)
+{
+}
+
+/*
+ * Create a new SSL structure for a connection.
+ */
+SSL *SSL_new(SSL_CTX * ctx)
+{
+ SSL *ssl;
+ int rc;
+
+ if ((ssl = typeCalloc(SSL)) != 0) {
+
+ rc = gnutls_certificate_allocate_credentials(&ssl->gnutls_cred);
+ if (rc < 0) {
+ last_error = rc;
+ free(ssl);
+ ssl = 0;
+ } else {
+ ssl->ctx = ctx;
+
+ gnutls_init(&ssl->gnutls_state, ctx->method->connend);
+ gnutls_set_default_priority(ssl->gnutls_state);
+
+ gnutls_credentials_set(ssl->gnutls_state, GNUTLS_CRD_CERTIFICATE,
+ ssl->gnutls_cred);
+ if (ctx->certfile)
+ gnutls_certificate_set_x509_trust_file(ssl->gnutls_cred,
+ ctx->certfile,
+ ctx->certfile_type);
+ if (ctx->client_keyfile)
+ gnutls_certificate_set_x509_key_file(ssl->gnutls_cred,
+ ctx->client_certfile,
+ ctx->client_keyfile,
+ ctx->client_keyfile_type);
+ ssl->verify_mode = ctx->verify_mode;
+ ssl->verify_callback = ctx->verify_callback;
+
+ ssl->options = ctx->options;
+
+ ssl->rfd = (gnutls_transport_ptr_t) (-1);
+ ssl->wfd = (gnutls_transport_ptr_t) (-1);
+ ssl->bytes_sent = 0;
+ ssl->sendbuffer = 0;
+ }
+ }
+
+ return ssl;
+}
+
+/*
+ * Read 'length' bytes into 'buffer' from the given SSL connection.
+ * Returns the number of bytes read, or zero on error.
+ */
+int SSL_read(SSL * ssl, void *buffer, int length)
+{
+ int rc;
+
+ do {
+ rc = (int) gnutls_record_recv(ssl->gnutls_state, buffer, (size_t) length);
+ }
+ while ((rc == GNUTLS_E_AGAIN) || (rc == GNUTLS_E_INTERRUPTED));
+
+ if (rc < 0 && gnutls_error_is_fatal(rc) == 0) {
+ if (rc == GNUTLS_E_REHANDSHAKE) {
+ (void) gnutls_handshake(ssl->gnutls_state);
+ do {
+ rc = (int) gnutls_record_send(ssl->gnutls_state,
+ ssl->sendbuffer,
+ (size_t) ssl->bytes_sent);
+ }
+ while ((rc == GNUTLS_E_AGAIN) || (rc == GNUTLS_E_INTERRUPTED));
+ do {
+ rc = (int) gnutls_record_recv(ssl->gnutls_state,
+ buffer,
+ (size_t) length);
+ }
+ while ((rc == GNUTLS_E_AGAIN) || (rc == GNUTLS_E_INTERRUPTED));
+ }
+ }
+
+ ssl->last_error = rc;
+
+ if (rc < 0) {
+ last_error = rc;
+ rc = 0;
+ }
+
+ return rc;
+}
+
+#ifdef _WINDOWS
+static int Lynx_gtls_push(void *s, const void *buf, size_t len)
+{
+ return NETWRITE((SOCKET) s, buf, len);
+}
+
+/* This calls 'recv()' in a thread for every GnuTls pull. Maybe too much overhead?
+ */
+static int Lynx_gtls_pull(void *s, void *buf, size_t len)
+{
+ return NETREAD((SOCKET) s, buf, len);
+}
+#endif
+
+/*
+ * Connect the SSL object with a file descriptor.
+ * This always returns 1 (success) since GNU TLS does not check for errors.
+ */
+int SSL_set_fd(SSL * ssl, int fd)
+{
+#ifdef _WINDOWS
+ /* register callback functions to send and receive data. */
+ gnutls_transport_set_push_function(ssl->gnutls_state, Lynx_gtls_push);
+ gnutls_transport_set_pull_function(ssl->gnutls_state, Lynx_gtls_pull);
+#endif
+
+ gnutls_transport_set_ptr(ssl->gnutls_state,
+ (gnutls_transport_ptr_t) (intptr_t) (fd));
+ return 1;
+}
+
+/*
+ * Write 'length' bytes from 'buffer' to the given SSL connection.
+ */
+int SSL_write(SSL * ssl, const void *buffer, int length)
+{
+ int rc;
+
+ do {
+ rc = (int) gnutls_record_send(ssl->gnutls_state, buffer, (size_t) length);
+ }
+ while ((rc == GNUTLS_E_AGAIN) || (rc == GNUTLS_E_INTERRUPTED));
+ ssl->last_error = rc;
+
+ if (rc < 0) {
+ last_error = rc;
+ rc = 0;
+ } else {
+ size_t need = (size_t) rc;
+
+ free(ssl->sendbuffer);
+ ssl->sendbuffer = malloc(need);
+ ssl->bytes_sent = need;
+ }
+
+ return rc;
+}
+
+/*
+ * Return method-data for SSL version 3, with the option of rollback to SSL
+ * version 2.
+ */
+SSL_METHOD *SSLv23_client_method(void)
+{
+ SSL_METHOD *m;
+
+ if ((m = typeCalloc(SSL_METHOD)) != 0) {
+ int n;
+
+ /*
+ * List the protocols in decreasing order of priority.
+ */
+ n = 0;
+#if GNUTLS_VERSION_NUMBER >= 0x030000
+ m->priority.protocol[n++] = GNUTLS_SSL3;
+ m->priority.protocol[n++] = GNUTLS_TLS1_2;
+#endif
+ m->priority.protocol[n++] = GNUTLS_TLS1_1;
+ m->priority.protocol[n++] = GNUTLS_TLS1_0;
+ m->priority.protocol[n] = 0;
+
+ /*
+ * List the cipher algorithms in decreasing order of priority.
+ */
+ n = 0;
+#if GNUTLS_VERSION_NUMBER >= 0x030000
+ m->priority.encrypts[n++] = GNUTLS_CIPHER_AES_256_GCM;
+ m->priority.encrypts[n++] = GNUTLS_CIPHER_AES_128_GCM;
+#endif
+ m->priority.encrypts[n++] = GNUTLS_CIPHER_AES_256_CBC;
+ m->priority.encrypts[n++] = GNUTLS_CIPHER_AES_128_CBC;
+ m->priority.encrypts[n++] = GNUTLS_CIPHER_CAMELLIA_256_CBC;
+ m->priority.encrypts[n++] = GNUTLS_CIPHER_CAMELLIA_128_CBC;
+ m->priority.encrypts[n++] = GNUTLS_CIPHER_3DES_CBC;
+ m->priority.encrypts[n] = 0;
+
+ /*
+ * List the compression algorithms in decreasing order of priority.
+ */
+ n = 0;
+ m->priority.compress[n++] = GNUTLS_COMP_NULL;
+ m->priority.compress[n] = 0;
+
+ /*
+ * List the key exchange algorithms in decreasing order of priority.
+ */
+ n = 0;
+ m->priority.key_xchg[n++] = GNUTLS_KX_DHE_RSA;
+ m->priority.key_xchg[n++] = GNUTLS_KX_RSA;
+ m->priority.key_xchg[n++] = GNUTLS_KX_DHE_DSS;
+ m->priority.key_xchg[n] = 0;
+
+ /*
+ * List message authentication code (MAC) algorithms in decreasing
+ * order of priority to specify via gnutls_mac_set_priority().
+ */
+ n = 0;
+ m->priority.msg_code[n++] = GNUTLS_MAC_SHA1;
+ m->priority.msg_code[n++] = GNUTLS_MAC_MD5;
+ m->priority.msg_code[n] = 0;
+
+ /*
+ * For gnutls_init, says we're a client.
+ */
+ m->connend = GNUTLS_CLIENT;
+ }
+
+ return m;
+}
+
+static int add_name(char *target, int len, const char *tag, const char *data)
+{
+ if (*data != '\0') {
+ int need = (int) strlen(tag) + 2;
+
+ target += strlen(target);
+ if (need < len) {
+ strcat(target, "/");
+ strcat(target, tag);
+ strcat(target, "=");
+ len -= need;
+ target += need;
+ }
+ need = (int) strlen(data);
+ if (need >= len - 1)
+ need = len - 1;
+ strncat(target, data, (size_t) need)[need] = '\0';
+ }
+ return len;
+}
+#define ADD_NAME(tag, data) len = add_name(target, len, tag, data);
+
+/*
+ * Convert the X509 name 'source' to a string in the given buffer 'target',
+ * whose length is 'len'. Return a pointer to the buffer.
+ */
+char *X509_NAME_oneline(X509_NAME * source, char *target, int len)
+{
+ if (target && (len > 0)) {
+ *target = '\0';
+ if (source) {
+ ADD_NAME("C", source->country);
+ ADD_NAME("ST", source->state_or_province_name);
+ ADD_NAME("L", source->locality_name);
+ ADD_NAME("O", source->organization);
+ ADD_NAME("OU", source->organizational_unit_name);
+ ADD_NAME("CN", source->common_name);
+ ADD_NAME("Email", source->email);
+ }
+ }
+ return target;
+}
+
+/*
+ * Extract the certificate's issuer-name data.
+ */
+X509_NAME *X509_get_issuer_name(const X509 * cert)
+{
+ static X509_NAME *result;
+
+ free(result);
+ if ((result = typeCalloc(X509_NAME)) != 0) {
+ if (ExtractCertificate(cert, result, 1) < 0) {
+ free(result);
+ result = 0;
+ }
+ }
+ return result;
+}
+
+/*
+ * Extract the certificate's subject-name data.
+ */
+X509_NAME *X509_get_subject_name(const X509 * cert)
+{
+ static X509_NAME *result;
+
+ free(result);
+ if ((result = typeCalloc(X509_NAME)) != 0) {
+ if (ExtractCertificate(cert, result, 0) < 0) {
+ free(result);
+ result = 0;
+ }
+ }
+ return result;
+}
diff --git a/src/ucxolb.opt b/src/ucxolb.opt
new file mode 100644
index 0000000..2c7cb54
--- /dev/null
+++ b/src/ucxolb.opt
@@ -0,0 +1 @@
+sys$library:ucx$ipc/library
diff --git a/src/ucxshr.opt b/src/ucxshr.opt
new file mode 100644
index 0000000..ba84be0
--- /dev/null
+++ b/src/ucxshr.opt
@@ -0,0 +1 @@
+sys$share:ucx$ipc_shr/share
diff --git a/src/vaxc.opt b/src/vaxc.opt
new file mode 100644
index 0000000..fbb523b
--- /dev/null
+++ b/src/vaxc.opt
@@ -0,0 +1,2 @@
+sys$share:vaxcrtl/share
+sys$library:vaxccurse/library
diff --git a/src/wcwidth.c b/src/wcwidth.c
new file mode 100644
index 0000000..ea46336
--- /dev/null
+++ b/src/wcwidth.c
@@ -0,0 +1,709 @@
+/* $XTermId: wcwidth.c,v 1.42 2018/12/04 10:13:14 tom Exp $ */
+
+/* $XFree86: xc/programs/xterm/wcwidth.c,v 1.9 2006/06/19 00:36:52 dickey Exp $ */
+
+/*
+ * Copyright 2002-2017,2018 by Thomas E. Dickey
+ *
+ * All Rights Reserved
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a
+ * copy of this software and associated documentation files (the
+ * "Software"), to deal in the Software without restriction, including
+ * without limitation the rights to use, copy, modify, merge, publish,
+ * distribute, sublicense, and/or sell copies of the Software, and to
+ * permit persons to whom the Software is furnished to do so, subject to
+ * the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included
+ * in all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
+ * OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+ * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
+ * IN NO EVENT SHALL THE ABOVE LISTED COPYRIGHT HOLDER(S) BE LIABLE FOR ANY
+ * CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
+ * TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
+ * SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+ *
+ * Except as contained in this notice, the name(s) of the above copyright
+ * holders shall not be used in advertising or otherwise to promote the
+ * sale, use or other dealings in this Software without prior written
+ * authorization.
+ *-----------------------------------------------------------------------------
+ * This is an updated version of Markus Kuhn's implementation of wcwidth.
+ *
+ * Originally added to xterm in 2000 (patch #141), there were a couple of
+ * updates from Kuhn until 2005 (patch #202), renaming entrypoints and applying
+ * data from Unicode.org (e.g., 3.2, 4.0, 4.1.0). The Unicode data is
+ * transformed into tables in this file by a script "uniset" written by Kuhn.
+ *
+ * While Kuhn implemented the original CJK variant, it was unused by xterm
+ * until Jungshik Shin used it in 2002 to implement the -cjk_width command-line
+ * option.
+ *
+ * Kuhn added a check for the vertical forms block (double-width) in 2007;
+ * other updates were derived from the Unicode.org data (release 5.0).
+ *
+ * Since then, additional updates have been made:
+ * + data-type fixes
+ * + new Unicode releases (6.2.0, 9.0.0),
+ * + additional special symbol blocks have been added to the special cases.
+ * + soft-hyphen behavior has been made configurable.
+ * + added table shows when a character is not part of Unicode.
+ *
+ * Kuhn's original header follows giving the design information:
+ *-----------------------------------------------------------------------------
+ * This is an implementation of wcwidth() and wcswidth() (defined in
+ * IEEE Std 1002.1-2001) for Unicode.
+ *
+ * http://www.opengroup.org/onlinepubs/007904975/functions/wcwidth.html
+ * http://www.opengroup.org/onlinepubs/007904975/functions/wcswidth.html
+ *
+ * In fixed-width output devices, Latin characters all occupy a single
+ * "cell" position of equal width, whereas ideographic CJK characters
+ * occupy two such cells. Interoperability between terminal-line
+ * applications and (teletype-style) character terminals using the
+ * UTF-8 encoding requires agreement on which character should advance
+ * the cursor by how many cell positions. No established formal
+ * standards exist at present on which Unicode character shall occupy
+ * how many cell positions on character terminals. These routines are
+ * a first attempt of defining such behavior based on simple rules
+ * applied to data provided by the Unicode Consortium.
+ *
+ * For some graphical characters, the Unicode standard explicitly
+ * defines a character-cell width via the definition of the East Asian
+ * FullWidth (F), Wide (W), Half-width (H), and Narrow (Na) classes.
+ * In all these cases, there is no ambiguity about which width a
+ * terminal shall use. For characters in the East Asian Ambiguous (A)
+ * class, the width choice depends purely on a preference of backward
+ * compatibility with either historic CJK or Western practice.
+ * Choosing single-width for these characters is easy to justify as
+ * the appropriate long-term solution, as the CJK practice of
+ * displaying these characters as double-width comes from historic
+ * implementation simplicity (8-bit encoded characters were displayed
+ * single-width and 16-bit ones double-width, even for Greek,
+ * Cyrillic, etc.) and not any typographic considerations.
+ *
+ * Much less clear is the choice of width for the Not East Asian
+ * (Neutral) class. Existing practice does not dictate a width for any
+ * of these characters. It would nevertheless make sense
+ * typographically to allocate two character cells to characters such
+ * as for instance EM SPACE or VOLUME INTEGRAL, which cannot be
+ * represented adequately with a single-width glyph. The following
+ * routines at present merely assign a single-cell width to all
+ * neutral characters, in the interest of simplicity. This is not
+ * entirely satisfactory and should be reconsidered before
+ * establishing a formal standard in this area. At the moment, the
+ * decision which Not East Asian (Neutral) characters should be
+ * represented by double-width glyphs cannot yet be answered by
+ * applying a simple rule from the Unicode database content. Setting
+ * up a proper standard for the behavior of UTF-8 character terminals
+ * will require a careful analysis not only of each Unicode character,
+ * but also of each presentation form, something the author of these
+ * routines has avoided to do so far.
+ *
+ * http://www.unicode.org/unicode/reports/tr11/
+ *
+ * Markus Kuhn -- 2007-05-25 (Unicode 5.0)
+ *
+ * Permission to use, copy, modify, and distribute this software
+ * for any purpose and without fee is hereby granted. The author
+ * disclaims all warranties with regard to this software.
+ *
+ * Latest version: http://www.cl.cam.ac.uk/~mgk25/ucs/wcwidth.c
+ */
+
+#include <wcwidth.h>
+
+struct interval {
+ unsigned long first;
+ unsigned long last;
+};
+
+static int use_latin1 = 1;
+
+/* auxiliary function for binary search in interval table */
+static int bisearch(unsigned long ucs, const struct interval *table, int max) {
+
+ if (ucs >= table[0].first && ucs <= table[max].last) {
+ int min = 0;
+
+ while (max >= min) {
+ int mid;
+
+ mid = (min + max) / 2;
+ if (ucs > table[mid].last)
+ min = mid + 1;
+ else if (ucs < table[mid].first)
+ max = mid - 1;
+ else
+ return 1;
+ }
+ }
+
+ return 0;
+}
+
+/*
+ * Provide a way to change the behavior of soft-hyphen.
+ */
+void
+mk_wcwidth_init(int mode)
+{
+ use_latin1 = (mode == 0);
+}
+
+/* The following two functions define the column width of an ISO 10646
+ * character as follows:
+ *
+ * - The null character (U+0000) has a column width of 0.
+ *
+ * - Other C0/C1 control characters and DEL will lead to a return
+ * value of -1.
+ *
+ * - Non-spacing and enclosing combining characters (general
+ * category code Mn or Me in the Unicode database) have a
+ * column width of 0.
+ *
+ * - A few spacing combining marks have a column width of 0.
+ *
+ * - SOFT HYPHEN (U+00AD) has a column width of 1 in Latin-1, 0 in Unicode.
+ * An initialization function is used to switch between the two.
+ *
+ * - Other format characters (general category code Cf in the Unicode
+ * database) and ZERO WIDTH SPACE (U+200B) have a column width of 0.
+ *
+ * - Hangul Jamo medial vowels and final consonants (U+1160-U+11FF)
+ * have a column width of 0.
+ *
+ * - Spacing characters in the East Asian Wide (W) or East Asian
+ * Full-width (F) category as defined in Unicode Technical
+ * Report #11 have a column width of 2. In that report, some codes
+ * were unassigned. Characters in these blocks use a column width of 1:
+ * 4DC0..4DFF; Yijing Hexagram Symbols
+ * A960..A97F; Hangul Jamo Extended-A
+ *
+ * - All remaining characters (including all printable
+ * ISO 8859-1 and WGL4 characters, Unicode control characters,
+ * etc.) have a column width of 1.
+ *
+ * - Codes which do not correspond to a Unicode character have a column
+ * width of -1.
+ *
+ * This implementation assumes that wchar_t characters are encoded
+ * in ISO 10646.
+ */
+
+int mk_wcwidth(wchar_t ucs)
+{
+ unsigned long cmp = (unsigned long) ucs;
+
+ /* sorted list of non-overlapping intervals of non-spacing characters */
+ /* generated by
+ * uniset +cat=Me +cat=Mn +cat=Cf -00AD +1160-11FF +200B c
+ */
+ static const struct interval combining[] = {
+ { 0x0300, 0x036F }, { 0x0483, 0x0489 }, { 0x0591, 0x05BD },
+ { 0x05BF, 0x05BF }, { 0x05C1, 0x05C2 }, { 0x05C4, 0x05C5 },
+ { 0x05C7, 0x05C7 }, { 0x0600, 0x0605 }, { 0x0610, 0x061A },
+ { 0x061C, 0x061C }, { 0x064B, 0x065F }, { 0x0670, 0x0670 },
+ { 0x06D6, 0x06DD }, { 0x06DF, 0x06E4 }, { 0x06E7, 0x06E8 },
+ { 0x06EA, 0x06ED }, { 0x070F, 0x070F }, { 0x0711, 0x0711 },
+ { 0x0730, 0x074A }, { 0x07A6, 0x07B0 }, { 0x07EB, 0x07F3 },
+ { 0x0816, 0x0819 }, { 0x081B, 0x0823 }, { 0x0825, 0x0827 },
+ { 0x0829, 0x082D }, { 0x0859, 0x085B }, { 0x08D4, 0x0902 },
+ { 0x093A, 0x093A }, { 0x093C, 0x093C }, { 0x0941, 0x0948 },
+ { 0x094D, 0x094D }, { 0x0951, 0x0957 }, { 0x0962, 0x0963 },
+ { 0x0981, 0x0981 }, { 0x09BC, 0x09BC }, { 0x09C1, 0x09C4 },
+ { 0x09CD, 0x09CD }, { 0x09E2, 0x09E3 }, { 0x0A01, 0x0A02 },
+ { 0x0A3C, 0x0A3C }, { 0x0A41, 0x0A42 }, { 0x0A47, 0x0A48 },
+ { 0x0A4B, 0x0A4D }, { 0x0A51, 0x0A51 }, { 0x0A70, 0x0A71 },
+ { 0x0A75, 0x0A75 }, { 0x0A81, 0x0A82 }, { 0x0ABC, 0x0ABC },
+ { 0x0AC1, 0x0AC5 }, { 0x0AC7, 0x0AC8 }, { 0x0ACD, 0x0ACD },
+ { 0x0AE2, 0x0AE3 }, { 0x0AFA, 0x0AFF }, { 0x0B01, 0x0B01 },
+ { 0x0B3C, 0x0B3C }, { 0x0B3F, 0x0B3F }, { 0x0B41, 0x0B44 },
+ { 0x0B4D, 0x0B4D }, { 0x0B56, 0x0B56 }, { 0x0B62, 0x0B63 },
+ { 0x0B82, 0x0B82 }, { 0x0BC0, 0x0BC0 }, { 0x0BCD, 0x0BCD },
+ { 0x0C00, 0x0C00 }, { 0x0C3E, 0x0C40 }, { 0x0C46, 0x0C48 },
+ { 0x0C4A, 0x0C4D }, { 0x0C55, 0x0C56 }, { 0x0C62, 0x0C63 },
+ { 0x0C81, 0x0C81 }, { 0x0CBC, 0x0CBC }, { 0x0CBF, 0x0CBF },
+ { 0x0CC6, 0x0CC6 }, { 0x0CCC, 0x0CCD }, { 0x0CE2, 0x0CE3 },
+ { 0x0D00, 0x0D01 }, { 0x0D3B, 0x0D3C }, { 0x0D41, 0x0D44 },
+ { 0x0D4D, 0x0D4D }, { 0x0D62, 0x0D63 }, { 0x0DCA, 0x0DCA },
+ { 0x0DD2, 0x0DD4 }, { 0x0DD6, 0x0DD6 }, { 0x0E31, 0x0E31 },
+ { 0x0E34, 0x0E3A }, { 0x0E47, 0x0E4E }, { 0x0EB1, 0x0EB1 },
+ { 0x0EB4, 0x0EB9 }, { 0x0EBB, 0x0EBC }, { 0x0EC8, 0x0ECD },
+ { 0x0F18, 0x0F19 }, { 0x0F35, 0x0F35 }, { 0x0F37, 0x0F37 },
+ { 0x0F39, 0x0F39 }, { 0x0F71, 0x0F7E }, { 0x0F80, 0x0F84 },
+ { 0x0F86, 0x0F87 }, { 0x0F8D, 0x0F97 }, { 0x0F99, 0x0FBC },
+ { 0x0FC6, 0x0FC6 }, { 0x102D, 0x1030 }, { 0x1032, 0x1037 },
+ { 0x1039, 0x103A }, { 0x103D, 0x103E }, { 0x1058, 0x1059 },
+ { 0x105E, 0x1060 }, { 0x1071, 0x1074 }, { 0x1082, 0x1082 },
+ { 0x1085, 0x1086 }, { 0x108D, 0x108D }, { 0x109D, 0x109D },
+ { 0x1160, 0x11FF }, { 0x135D, 0x135F }, { 0x1712, 0x1714 },
+ { 0x1732, 0x1734 }, { 0x1752, 0x1753 }, { 0x1772, 0x1773 },
+ { 0x17B4, 0x17B5 }, { 0x17B7, 0x17BD }, { 0x17C6, 0x17C6 },
+ { 0x17C9, 0x17D3 }, { 0x17DD, 0x17DD }, { 0x180B, 0x180E },
+ { 0x1885, 0x1886 }, { 0x18A9, 0x18A9 }, { 0x1920, 0x1922 },
+ { 0x1927, 0x192B }, { 0x1932, 0x1932 }, { 0x1939, 0x193B },
+ { 0x1A17, 0x1A18 }, { 0x1A1B, 0x1A1B }, { 0x1A56, 0x1A56 },
+ { 0x1A58, 0x1A5E }, { 0x1A60, 0x1A60 }, { 0x1A62, 0x1A62 },
+ { 0x1A65, 0x1A6C }, { 0x1A73, 0x1A7C }, { 0x1A7F, 0x1A7F },
+ { 0x1AB0, 0x1ABE }, { 0x1B00, 0x1B03 }, { 0x1B34, 0x1B34 },
+ { 0x1B36, 0x1B3A }, { 0x1B3C, 0x1B3C }, { 0x1B42, 0x1B42 },
+ { 0x1B6B, 0x1B73 }, { 0x1B80, 0x1B81 }, { 0x1BA2, 0x1BA5 },
+ { 0x1BA8, 0x1BA9 }, { 0x1BAB, 0x1BAD }, { 0x1BE6, 0x1BE6 },
+ { 0x1BE8, 0x1BE9 }, { 0x1BED, 0x1BED }, { 0x1BEF, 0x1BF1 },
+ { 0x1C2C, 0x1C33 }, { 0x1C36, 0x1C37 }, { 0x1CD0, 0x1CD2 },
+ { 0x1CD4, 0x1CE0 }, { 0x1CE2, 0x1CE8 }, { 0x1CED, 0x1CED },
+ { 0x1CF4, 0x1CF4 }, { 0x1CF8, 0x1CF9 }, { 0x1DC0, 0x1DF9 },
+ { 0x1DFB, 0x1DFF }, { 0x200B, 0x200F }, { 0x202A, 0x202E },
+ { 0x2060, 0x2064 }, { 0x2066, 0x206F }, { 0x20D0, 0x20F0 },
+ { 0x2CEF, 0x2CF1 }, { 0x2D7F, 0x2D7F }, { 0x2DE0, 0x2DFF },
+ { 0x302A, 0x302F }, { 0x3099, 0x309A }, { 0xA66F, 0xA672 },
+ { 0xA674, 0xA67D }, { 0xA69E, 0xA69F }, { 0xA6F0, 0xA6F1 },
+ { 0xA802, 0xA802 }, { 0xA806, 0xA806 }, { 0xA80B, 0xA80B },
+ { 0xA825, 0xA826 }, { 0xA8C4, 0xA8C5 }, { 0xA8E0, 0xA8F1 },
+ { 0xA926, 0xA92D }, { 0xA947, 0xA951 }, { 0xA980, 0xA982 },
+ { 0xA9B3, 0xA9B3 }, { 0xA9B6, 0xA9B9 }, { 0xA9BC, 0xA9BC },
+ { 0xA9E5, 0xA9E5 }, { 0xAA29, 0xAA2E }, { 0xAA31, 0xAA32 },
+ { 0xAA35, 0xAA36 }, { 0xAA43, 0xAA43 }, { 0xAA4C, 0xAA4C },
+ { 0xAA7C, 0xAA7C }, { 0xAAB0, 0xAAB0 }, { 0xAAB2, 0xAAB4 },
+ { 0xAAB7, 0xAAB8 }, { 0xAABE, 0xAABF }, { 0xAAC1, 0xAAC1 },
+ { 0xAAEC, 0xAAED }, { 0xAAF6, 0xAAF6 }, { 0xABE5, 0xABE5 },
+ { 0xABE8, 0xABE8 }, { 0xABED, 0xABED }, { 0xFB1E, 0xFB1E },
+ { 0xFE00, 0xFE0F }, { 0xFE20, 0xFE2F }, { 0xFEFF, 0xFEFF },
+ { 0xFFF9, 0xFFFB }, { 0x101FD, 0x101FD }, { 0x102E0, 0x102E0 },
+ { 0x10376, 0x1037A }, { 0x10A01, 0x10A03 }, { 0x10A05, 0x10A06 },
+ { 0x10A0C, 0x10A0F }, { 0x10A38, 0x10A3A }, { 0x10A3F, 0x10A3F },
+ { 0x10AE5, 0x10AE6 }, { 0x11001, 0x11001 }, { 0x11038, 0x11046 },
+ { 0x1107F, 0x11081 }, { 0x110B3, 0x110B6 }, { 0x110B9, 0x110BA },
+ { 0x110BD, 0x110BD }, { 0x11100, 0x11102 }, { 0x11127, 0x1112B },
+ { 0x1112D, 0x11134 }, { 0x11173, 0x11173 }, { 0x11180, 0x11181 },
+ { 0x111B6, 0x111BE }, { 0x111CA, 0x111CC }, { 0x1122F, 0x11231 },
+ { 0x11234, 0x11234 }, { 0x11236, 0x11237 }, { 0x1123E, 0x1123E },
+ { 0x112DF, 0x112DF }, { 0x112E3, 0x112EA }, { 0x11300, 0x11301 },
+ { 0x1133C, 0x1133C }, { 0x11340, 0x11340 }, { 0x11366, 0x1136C },
+ { 0x11370, 0x11374 }, { 0x11438, 0x1143F }, { 0x11442, 0x11444 },
+ { 0x11446, 0x11446 }, { 0x114B3, 0x114B8 }, { 0x114BA, 0x114BA },
+ { 0x114BF, 0x114C0 }, { 0x114C2, 0x114C3 }, { 0x115B2, 0x115B5 },
+ { 0x115BC, 0x115BD }, { 0x115BF, 0x115C0 }, { 0x115DC, 0x115DD },
+ { 0x11633, 0x1163A }, { 0x1163D, 0x1163D }, { 0x1163F, 0x11640 },
+ { 0x116AB, 0x116AB }, { 0x116AD, 0x116AD }, { 0x116B0, 0x116B5 },
+ { 0x116B7, 0x116B7 }, { 0x1171D, 0x1171F }, { 0x11722, 0x11725 },
+ { 0x11727, 0x1172B }, { 0x11A01, 0x11A06 }, { 0x11A09, 0x11A0A },
+ { 0x11A33, 0x11A38 }, { 0x11A3B, 0x11A3E }, { 0x11A47, 0x11A47 },
+ { 0x11A51, 0x11A56 }, { 0x11A59, 0x11A5B }, { 0x11A8A, 0x11A96 },
+ { 0x11A98, 0x11A99 }, { 0x11C30, 0x11C36 }, { 0x11C38, 0x11C3D },
+ { 0x11C3F, 0x11C3F }, { 0x11C92, 0x11CA7 }, { 0x11CAA, 0x11CB0 },
+ { 0x11CB2, 0x11CB3 }, { 0x11CB5, 0x11CB6 }, { 0x11D31, 0x11D36 },
+ { 0x11D3A, 0x11D3A }, { 0x11D3C, 0x11D3D }, { 0x11D3F, 0x11D45 },
+ { 0x11D47, 0x11D47 }, { 0x16AF0, 0x16AF4 }, { 0x16B30, 0x16B36 },
+ { 0x16F8F, 0x16F92 }, { 0x1BC9D, 0x1BC9E }, { 0x1BCA0, 0x1BCA3 },
+ { 0x1D167, 0x1D169 }, { 0x1D173, 0x1D182 }, { 0x1D185, 0x1D18B },
+ { 0x1D1AA, 0x1D1AD }, { 0x1D242, 0x1D244 }, { 0x1DA00, 0x1DA36 },
+ { 0x1DA3B, 0x1DA6C }, { 0x1DA75, 0x1DA75 }, { 0x1DA84, 0x1DA84 },
+ { 0x1DA9B, 0x1DA9F }, { 0x1DAA1, 0x1DAAF }, { 0x1E000, 0x1E006 },
+ { 0x1E008, 0x1E018 }, { 0x1E01B, 0x1E021 }, { 0x1E023, 0x1E024 },
+ { 0x1E026, 0x1E02A }, { 0x1E8D0, 0x1E8D6 }, { 0x1E944, 0x1E94A },
+ { 0xE0001, 0xE0001 }, { 0xE0020, 0xE007F }, { 0xE0100, 0xE01EF }
+ };
+
+ /* sorted list of non-overlapping intervals of non-characters */
+ /* generated by
+ * uniset +0000..DFFF -4e00..9fd5 +F900..10FFFD unknown +2028..2029 c
+ */
+ static const struct interval unknowns[] = {
+ { 0x0378, 0x0379 }, { 0x0380, 0x0383 }, { 0x038B, 0x038B },
+ { 0x038D, 0x038D }, { 0x03A2, 0x03A2 }, { 0x0530, 0x0530 },
+ { 0x0557, 0x0558 }, { 0x0560, 0x0560 }, { 0x0588, 0x0588 },
+ { 0x058B, 0x058C }, { 0x0590, 0x0590 }, { 0x05C8, 0x05CF },
+ { 0x05EB, 0x05EF }, { 0x05F5, 0x05FF }, { 0x061D, 0x061D },
+ { 0x070E, 0x070E }, { 0x074B, 0x074C }, { 0x07B2, 0x07BF },
+ { 0x07FB, 0x07FF }, { 0x082E, 0x082F }, { 0x083F, 0x083F },
+ { 0x085C, 0x085D }, { 0x085F, 0x085F }, { 0x086B, 0x089F },
+ { 0x08B5, 0x08B5 }, { 0x08BE, 0x08D3 }, { 0x0984, 0x0984 },
+ { 0x098D, 0x098E }, { 0x0991, 0x0992 }, { 0x09A9, 0x09A9 },
+ { 0x09B1, 0x09B1 }, { 0x09B3, 0x09B5 }, { 0x09BA, 0x09BB },
+ { 0x09C5, 0x09C6 }, { 0x09C9, 0x09CA }, { 0x09CF, 0x09D6 },
+ { 0x09D8, 0x09DB }, { 0x09DE, 0x09DE }, { 0x09E4, 0x09E5 },
+ { 0x09FE, 0x0A00 }, { 0x0A04, 0x0A04 }, { 0x0A0B, 0x0A0E },
+ { 0x0A11, 0x0A12 }, { 0x0A29, 0x0A29 }, { 0x0A31, 0x0A31 },
+ { 0x0A34, 0x0A34 }, { 0x0A37, 0x0A37 }, { 0x0A3A, 0x0A3B },
+ { 0x0A3D, 0x0A3D }, { 0x0A43, 0x0A46 }, { 0x0A49, 0x0A4A },
+ { 0x0A4E, 0x0A50 }, { 0x0A52, 0x0A58 }, { 0x0A5D, 0x0A5D },
+ { 0x0A5F, 0x0A65 }, { 0x0A76, 0x0A80 }, { 0x0A84, 0x0A84 },
+ { 0x0A8E, 0x0A8E }, { 0x0A92, 0x0A92 }, { 0x0AA9, 0x0AA9 },
+ { 0x0AB1, 0x0AB1 }, { 0x0AB4, 0x0AB4 }, { 0x0ABA, 0x0ABB },
+ { 0x0AC6, 0x0AC6 }, { 0x0ACA, 0x0ACA }, { 0x0ACE, 0x0ACF },
+ { 0x0AD1, 0x0ADF }, { 0x0AE4, 0x0AE5 }, { 0x0AF2, 0x0AF8 },
+ { 0x0B00, 0x0B00 }, { 0x0B04, 0x0B04 }, { 0x0B0D, 0x0B0E },
+ { 0x0B11, 0x0B12 }, { 0x0B29, 0x0B29 }, { 0x0B31, 0x0B31 },
+ { 0x0B34, 0x0B34 }, { 0x0B3A, 0x0B3B }, { 0x0B45, 0x0B46 },
+ { 0x0B49, 0x0B4A }, { 0x0B4E, 0x0B55 }, { 0x0B58, 0x0B5B },
+ { 0x0B5E, 0x0B5E }, { 0x0B64, 0x0B65 }, { 0x0B78, 0x0B81 },
+ { 0x0B84, 0x0B84 }, { 0x0B8B, 0x0B8D }, { 0x0B91, 0x0B91 },
+ { 0x0B96, 0x0B98 }, { 0x0B9B, 0x0B9B }, { 0x0B9D, 0x0B9D },
+ { 0x0BA0, 0x0BA2 }, { 0x0BA5, 0x0BA7 }, { 0x0BAB, 0x0BAD },
+ { 0x0BBA, 0x0BBD }, { 0x0BC3, 0x0BC5 }, { 0x0BC9, 0x0BC9 },
+ { 0x0BCE, 0x0BCF }, { 0x0BD1, 0x0BD6 }, { 0x0BD8, 0x0BE5 },
+ { 0x0BFB, 0x0BFF }, { 0x0C04, 0x0C04 }, { 0x0C0D, 0x0C0D },
+ { 0x0C11, 0x0C11 }, { 0x0C29, 0x0C29 }, { 0x0C3A, 0x0C3C },
+ { 0x0C45, 0x0C45 }, { 0x0C49, 0x0C49 }, { 0x0C4E, 0x0C54 },
+ { 0x0C57, 0x0C57 }, { 0x0C5B, 0x0C5F }, { 0x0C64, 0x0C65 },
+ { 0x0C70, 0x0C77 }, { 0x0C84, 0x0C84 }, { 0x0C8D, 0x0C8D },
+ { 0x0C91, 0x0C91 }, { 0x0CA9, 0x0CA9 }, { 0x0CB4, 0x0CB4 },
+ { 0x0CBA, 0x0CBB }, { 0x0CC5, 0x0CC5 }, { 0x0CC9, 0x0CC9 },
+ { 0x0CCE, 0x0CD4 }, { 0x0CD7, 0x0CDD }, { 0x0CDF, 0x0CDF },
+ { 0x0CE4, 0x0CE5 }, { 0x0CF0, 0x0CF0 }, { 0x0CF3, 0x0CFF },
+ { 0x0D04, 0x0D04 }, { 0x0D0D, 0x0D0D }, { 0x0D11, 0x0D11 },
+ { 0x0D45, 0x0D45 }, { 0x0D49, 0x0D49 }, { 0x0D50, 0x0D53 },
+ { 0x0D64, 0x0D65 }, { 0x0D80, 0x0D81 }, { 0x0D84, 0x0D84 },
+ { 0x0D97, 0x0D99 }, { 0x0DB2, 0x0DB2 }, { 0x0DBC, 0x0DBC },
+ { 0x0DBE, 0x0DBF }, { 0x0DC7, 0x0DC9 }, { 0x0DCB, 0x0DCE },
+ { 0x0DD5, 0x0DD5 }, { 0x0DD7, 0x0DD7 }, { 0x0DE0, 0x0DE5 },
+ { 0x0DF0, 0x0DF1 }, { 0x0DF5, 0x0E00 }, { 0x0E3B, 0x0E3E },
+ { 0x0E5C, 0x0E80 }, { 0x0E83, 0x0E83 }, { 0x0E85, 0x0E86 },
+ { 0x0E89, 0x0E89 }, { 0x0E8B, 0x0E8C }, { 0x0E8E, 0x0E93 },
+ { 0x0E98, 0x0E98 }, { 0x0EA0, 0x0EA0 }, { 0x0EA4, 0x0EA4 },
+ { 0x0EA6, 0x0EA6 }, { 0x0EA8, 0x0EA9 }, { 0x0EAC, 0x0EAC },
+ { 0x0EBA, 0x0EBA }, { 0x0EBE, 0x0EBF }, { 0x0EC5, 0x0EC5 },
+ { 0x0EC7, 0x0EC7 }, { 0x0ECE, 0x0ECF }, { 0x0EDA, 0x0EDB },
+ { 0x0EE0, 0x0EFF }, { 0x0F48, 0x0F48 }, { 0x0F6D, 0x0F70 },
+ { 0x0F98, 0x0F98 }, { 0x0FBD, 0x0FBD }, { 0x0FCD, 0x0FCD },
+ { 0x0FDB, 0x0FFF }, { 0x10C6, 0x10C6 }, { 0x10C8, 0x10CC },
+ { 0x10CE, 0x10CF }, { 0x1249, 0x1249 }, { 0x124E, 0x124F },
+ { 0x1257, 0x1257 }, { 0x1259, 0x1259 }, { 0x125E, 0x125F },
+ { 0x1289, 0x1289 }, { 0x128E, 0x128F }, { 0x12B1, 0x12B1 },
+ { 0x12B6, 0x12B7 }, { 0x12BF, 0x12BF }, { 0x12C1, 0x12C1 },
+ { 0x12C6, 0x12C7 }, { 0x12D7, 0x12D7 }, { 0x1311, 0x1311 },
+ { 0x1316, 0x1317 }, { 0x135B, 0x135C }, { 0x137D, 0x137F },
+ { 0x139A, 0x139F }, { 0x13F6, 0x13F7 }, { 0x13FE, 0x13FF },
+ { 0x169D, 0x169F }, { 0x16F9, 0x16FF }, { 0x170D, 0x170D },
+ { 0x1715, 0x171F }, { 0x1737, 0x173F }, { 0x1754, 0x175F },
+ { 0x176D, 0x176D }, { 0x1771, 0x1771 }, { 0x1774, 0x177F },
+ { 0x17DE, 0x17DF }, { 0x17EA, 0x17EF }, { 0x17FA, 0x17FF },
+ { 0x180F, 0x180F }, { 0x181A, 0x181F }, { 0x1878, 0x187F },
+ { 0x18AB, 0x18AF }, { 0x18F6, 0x18FF }, { 0x191F, 0x191F },
+ { 0x192C, 0x192F }, { 0x193C, 0x193F }, { 0x1941, 0x1943 },
+ { 0x196E, 0x196F }, { 0x1975, 0x197F }, { 0x19AC, 0x19AF },
+ { 0x19CA, 0x19CF }, { 0x19DB, 0x19DD }, { 0x1A1C, 0x1A1D },
+ { 0x1A5F, 0x1A5F }, { 0x1A7D, 0x1A7E }, { 0x1A8A, 0x1A8F },
+ { 0x1A9A, 0x1A9F }, { 0x1AAE, 0x1AAF }, { 0x1ABF, 0x1AFF },
+ { 0x1B4C, 0x1B4F }, { 0x1B7D, 0x1B7F }, { 0x1BF4, 0x1BFB },
+ { 0x1C38, 0x1C3A }, { 0x1C4A, 0x1C4C }, { 0x1C89, 0x1CBF },
+ { 0x1CC8, 0x1CCF }, { 0x1CFA, 0x1CFF }, { 0x1DFA, 0x1DFA },
+ { 0x1F16, 0x1F17 }, { 0x1F1E, 0x1F1F }, { 0x1F46, 0x1F47 },
+ { 0x1F4E, 0x1F4F }, { 0x1F58, 0x1F58 }, { 0x1F5A, 0x1F5A },
+ { 0x1F5C, 0x1F5C }, { 0x1F5E, 0x1F5E }, { 0x1F7E, 0x1F7F },
+ { 0x1FB5, 0x1FB5 }, { 0x1FC5, 0x1FC5 }, { 0x1FD4, 0x1FD5 },
+ { 0x1FDC, 0x1FDC }, { 0x1FF0, 0x1FF1 }, { 0x1FF5, 0x1FF5 },
+ { 0x1FFF, 0x1FFF }, { 0x2028, 0x2029 }, { 0x2065, 0x2065 },
+ { 0x2072, 0x2073 }, { 0x208F, 0x208F }, { 0x209D, 0x209F },
+ { 0x20C0, 0x20CF }, { 0x20F1, 0x20FF }, { 0x218C, 0x218F },
+ { 0x2427, 0x243F }, { 0x244B, 0x245F }, { 0x2B74, 0x2B75 },
+ { 0x2B96, 0x2B97 }, { 0x2BBA, 0x2BBC }, { 0x2BC9, 0x2BC9 },
+ { 0x2BD3, 0x2BEB }, { 0x2BF0, 0x2BFF }, { 0x2C2F, 0x2C2F },
+ { 0x2C5F, 0x2C5F }, { 0x2CF4, 0x2CF8 }, { 0x2D26, 0x2D26 },
+ { 0x2D28, 0x2D2C }, { 0x2D2E, 0x2D2F }, { 0x2D68, 0x2D6E },
+ { 0x2D71, 0x2D7E }, { 0x2D97, 0x2D9F }, { 0x2DA7, 0x2DA7 },
+ { 0x2DAF, 0x2DAF }, { 0x2DB7, 0x2DB7 }, { 0x2DBF, 0x2DBF },
+ { 0x2DC7, 0x2DC7 }, { 0x2DCF, 0x2DCF }, { 0x2DD7, 0x2DD7 },
+ { 0x2DDF, 0x2DDF }, { 0x2E4A, 0x2E7F }, { 0x2E9A, 0x2E9A },
+ { 0x2EF4, 0x2EFF }, { 0x2FD6, 0x2FEF }, { 0x2FFC, 0x2FFF },
+ { 0x3040, 0x3040 }, { 0x3097, 0x3098 }, { 0x3100, 0x3104 },
+ { 0x312F, 0x3130 }, { 0x318F, 0x318F }, { 0x31BB, 0x31BF },
+ { 0x31E4, 0x31EF }, { 0x321F, 0x321F }, { 0x32FF, 0x32FF },
+ { 0x4DB6, 0x4DBF }, { 0x9FD6, 0x9FFF }, { 0xA48D, 0xA48F },
+ { 0xA4C7, 0xA4CF }, { 0xA62C, 0xA63F }, { 0xA6F8, 0xA6FF },
+ { 0xA7AF, 0xA7AF }, { 0xA7B8, 0xA7F6 }, { 0xA82C, 0xA82F },
+ { 0xA83A, 0xA83F }, { 0xA878, 0xA87F }, { 0xA8C6, 0xA8CD },
+ { 0xA8DA, 0xA8DF }, { 0xA8FE, 0xA8FF }, { 0xA954, 0xA95E },
+ { 0xA97D, 0xA97F }, { 0xA9CE, 0xA9CE }, { 0xA9DA, 0xA9DD },
+ { 0xA9FF, 0xA9FF }, { 0xAA37, 0xAA3F }, { 0xAA4E, 0xAA4F },
+ { 0xAA5A, 0xAA5B }, { 0xAAC3, 0xAADA }, { 0xAAF7, 0xAB00 },
+ { 0xAB07, 0xAB08 }, { 0xAB0F, 0xAB10 }, { 0xAB17, 0xAB1F },
+ { 0xAB27, 0xAB27 }, { 0xAB2F, 0xAB2F }, { 0xAB66, 0xAB6F },
+ { 0xABEE, 0xABEF }, { 0xABFA, 0xABFF }, { 0xD7A4, 0xD7AF },
+ { 0xD7C7, 0xD7CA }, { 0xD7FC, 0xDFFF }, { 0xFA6E, 0xFA6F },
+ { 0xFADA, 0xFAFF }, { 0xFB07, 0xFB12 }, { 0xFB18, 0xFB1C },
+ { 0xFB37, 0xFB37 }, { 0xFB3D, 0xFB3D }, { 0xFB3F, 0xFB3F },
+ { 0xFB42, 0xFB42 }, { 0xFB45, 0xFB45 }, { 0xFBC2, 0xFBD2 },
+ { 0xFD40, 0xFD4F }, { 0xFD90, 0xFD91 }, { 0xFDC8, 0xFDEF },
+ { 0xFDFE, 0xFDFF }, { 0xFE1A, 0xFE1F }, { 0xFE53, 0xFE53 },
+ { 0xFE67, 0xFE67 }, { 0xFE6C, 0xFE6F }, { 0xFE75, 0xFE75 },
+ { 0xFEFD, 0xFEFE }, { 0xFF00, 0xFF00 }, { 0xFFBF, 0xFFC1 },
+ { 0xFFC8, 0xFFC9 }, { 0xFFD0, 0xFFD1 }, { 0xFFD8, 0xFFD9 },
+ { 0xFFDD, 0xFFDF }, { 0xFFE7, 0xFFE7 }, { 0xFFEF, 0xFFF8 },
+ { 0xFFFE, 0xFFFF }, { 0x1000C, 0x1000C }, { 0x10027, 0x10027 },
+ { 0x1003B, 0x1003B }, { 0x1003E, 0x1003E }, { 0x1004E, 0x1004F },
+ { 0x1005E, 0x1007F }, { 0x100FB, 0x100FF }, { 0x10103, 0x10106 },
+ { 0x10134, 0x10136 }, { 0x1018F, 0x1018F }, { 0x1019C, 0x1019F },
+ { 0x101A1, 0x101CF }, { 0x101FE, 0x1027F }, { 0x1029D, 0x1029F },
+ { 0x102D1, 0x102DF }, { 0x102FC, 0x102FF }, { 0x10324, 0x1032C },
+ { 0x1034B, 0x1034F }, { 0x1037B, 0x1037F }, { 0x1039E, 0x1039E },
+ { 0x103C4, 0x103C7 }, { 0x103D6, 0x103FF }, { 0x1049E, 0x1049F },
+ { 0x104AA, 0x104AF }, { 0x104D4, 0x104D7 }, { 0x104FC, 0x104FF },
+ { 0x10528, 0x1052F }, { 0x10564, 0x1056E }, { 0x10570, 0x105FF },
+ { 0x10737, 0x1073F }, { 0x10756, 0x1075F }, { 0x10768, 0x107FF },
+ { 0x10806, 0x10807 }, { 0x10809, 0x10809 }, { 0x10836, 0x10836 },
+ { 0x10839, 0x1083B }, { 0x1083D, 0x1083E }, { 0x10856, 0x10856 },
+ { 0x1089F, 0x108A6 }, { 0x108B0, 0x108DF }, { 0x108F3, 0x108F3 },
+ { 0x108F6, 0x108FA }, { 0x1091C, 0x1091E }, { 0x1093A, 0x1093E },
+ { 0x10940, 0x1097F }, { 0x109B8, 0x109BB }, { 0x109D0, 0x109D1 },
+ { 0x10A04, 0x10A04 }, { 0x10A07, 0x10A0B }, { 0x10A14, 0x10A14 },
+ { 0x10A18, 0x10A18 }, { 0x10A34, 0x10A37 }, { 0x10A3B, 0x10A3E },
+ { 0x10A48, 0x10A4F }, { 0x10A59, 0x10A5F }, { 0x10AA0, 0x10ABF },
+ { 0x10AE7, 0x10AEA }, { 0x10AF7, 0x10AFF }, { 0x10B36, 0x10B38 },
+ { 0x10B56, 0x10B57 }, { 0x10B73, 0x10B77 }, { 0x10B92, 0x10B98 },
+ { 0x10B9D, 0x10BA8 }, { 0x10BB0, 0x10BFF }, { 0x10C49, 0x10C7F },
+ { 0x10CB3, 0x10CBF }, { 0x10CF3, 0x10CF9 }, { 0x10D00, 0x10E5F },
+ { 0x10E7F, 0x10FFF }, { 0x1104E, 0x11051 }, { 0x11070, 0x1107E },
+ { 0x110C2, 0x110CF }, { 0x110E9, 0x110EF }, { 0x110FA, 0x110FF },
+ { 0x11135, 0x11135 }, { 0x11144, 0x1114F }, { 0x11177, 0x1117F },
+ { 0x111CE, 0x111CF }, { 0x111E0, 0x111E0 }, { 0x111F5, 0x111FF },
+ { 0x11212, 0x11212 }, { 0x1123F, 0x1127F }, { 0x11287, 0x11287 },
+ { 0x11289, 0x11289 }, { 0x1128E, 0x1128E }, { 0x1129E, 0x1129E },
+ { 0x112AA, 0x112AF }, { 0x112EB, 0x112EF }, { 0x112FA, 0x112FF },
+ { 0x11304, 0x11304 }, { 0x1130D, 0x1130E }, { 0x11311, 0x11312 },
+ { 0x11329, 0x11329 }, { 0x11331, 0x11331 }, { 0x11334, 0x11334 },
+ { 0x1133A, 0x1133B }, { 0x11345, 0x11346 }, { 0x11349, 0x1134A },
+ { 0x1134E, 0x1134F }, { 0x11351, 0x11356 }, { 0x11358, 0x1135C },
+ { 0x11364, 0x11365 }, { 0x1136D, 0x1136F }, { 0x11375, 0x113FF },
+ { 0x1145A, 0x1145A }, { 0x1145C, 0x1145C }, { 0x1145E, 0x1147F },
+ { 0x114C8, 0x114CF }, { 0x114DA, 0x1157F }, { 0x115B6, 0x115B7 },
+ { 0x115DE, 0x115FF }, { 0x11645, 0x1164F }, { 0x1165A, 0x1165F },
+ { 0x1166D, 0x1167F }, { 0x116B8, 0x116BF }, { 0x116CA, 0x116FF },
+ { 0x1171A, 0x1171C }, { 0x1172C, 0x1172F }, { 0x11740, 0x1189F },
+ { 0x118F3, 0x118FE }, { 0x11900, 0x119FF }, { 0x11A48, 0x11A4F },
+ { 0x11A84, 0x11A85 }, { 0x11A9D, 0x11A9D }, { 0x11AA3, 0x11ABF },
+ { 0x11AF9, 0x11BFF }, { 0x11C09, 0x11C09 }, { 0x11C37, 0x11C37 },
+ { 0x11C46, 0x11C4F }, { 0x11C6D, 0x11C6F }, { 0x11C90, 0x11C91 },
+ { 0x11CA8, 0x11CA8 }, { 0x11CB7, 0x11CFF }, { 0x11D07, 0x11D07 },
+ { 0x11D0A, 0x11D0A }, { 0x11D37, 0x11D39 }, { 0x11D3B, 0x11D3B },
+ { 0x11D3E, 0x11D3E }, { 0x11D48, 0x11D4F }, { 0x11D5A, 0x11FFF },
+ { 0x1239A, 0x123FF }, { 0x1246F, 0x1246F }, { 0x12475, 0x1247F },
+ { 0x12544, 0x12FFF }, { 0x1342F, 0x143FF }, { 0x14647, 0x167FF },
+ { 0x16A39, 0x16A3F }, { 0x16A5F, 0x16A5F }, { 0x16A6A, 0x16A6D },
+ { 0x16A70, 0x16ACF }, { 0x16AEE, 0x16AEF }, { 0x16AF6, 0x16AFF },
+ { 0x16B46, 0x16B4F }, { 0x16B5A, 0x16B5A }, { 0x16B62, 0x16B62 },
+ { 0x16B78, 0x16B7C }, { 0x16B90, 0x16EFF }, { 0x16F45, 0x16F4F },
+ { 0x16F7F, 0x16F8E }, { 0x16FA0, 0x16FDF }, { 0x16FE2, 0x187FF },
+ { 0x18AF3, 0x1AFFF }, { 0x1B11F, 0x1B16F }, { 0x1B2FC, 0x1BBFF },
+ { 0x1BC6B, 0x1BC6F }, { 0x1BC7D, 0x1BC7F }, { 0x1BC89, 0x1BC8F },
+ { 0x1BC9A, 0x1BC9B }, { 0x1BCA4, 0x1CFFF }, { 0x1D0F6, 0x1D0FF },
+ { 0x1D127, 0x1D128 }, { 0x1D1E9, 0x1D1FF }, { 0x1D246, 0x1D2FF },
+ { 0x1D357, 0x1D35F }, { 0x1D372, 0x1D3FF }, { 0x1D455, 0x1D455 },
+ { 0x1D49D, 0x1D49D }, { 0x1D4A0, 0x1D4A1 }, { 0x1D4A3, 0x1D4A4 },
+ { 0x1D4A7, 0x1D4A8 }, { 0x1D4AD, 0x1D4AD }, { 0x1D4BA, 0x1D4BA },
+ { 0x1D4BC, 0x1D4BC }, { 0x1D4C4, 0x1D4C4 }, { 0x1D506, 0x1D506 },
+ { 0x1D50B, 0x1D50C }, { 0x1D515, 0x1D515 }, { 0x1D51D, 0x1D51D },
+ { 0x1D53A, 0x1D53A }, { 0x1D53F, 0x1D53F }, { 0x1D545, 0x1D545 },
+ { 0x1D547, 0x1D549 }, { 0x1D551, 0x1D551 }, { 0x1D6A6, 0x1D6A7 },
+ { 0x1D7CC, 0x1D7CD }, { 0x1DA8C, 0x1DA9A }, { 0x1DAA0, 0x1DAA0 },
+ { 0x1DAB0, 0x1DFFF }, { 0x1E007, 0x1E007 }, { 0x1E019, 0x1E01A },
+ { 0x1E022, 0x1E022 }, { 0x1E025, 0x1E025 }, { 0x1E02B, 0x1E7FF },
+ { 0x1E8C5, 0x1E8C6 }, { 0x1E8D7, 0x1E8FF }, { 0x1E94B, 0x1E94F },
+ { 0x1E95A, 0x1E95D }, { 0x1E960, 0x1EDFF }, { 0x1EE04, 0x1EE04 },
+ { 0x1EE20, 0x1EE20 }, { 0x1EE23, 0x1EE23 }, { 0x1EE25, 0x1EE26 },
+ { 0x1EE28, 0x1EE28 }, { 0x1EE33, 0x1EE33 }, { 0x1EE38, 0x1EE38 },
+ { 0x1EE3A, 0x1EE3A }, { 0x1EE3C, 0x1EE41 }, { 0x1EE43, 0x1EE46 },
+ { 0x1EE48, 0x1EE48 }, { 0x1EE4A, 0x1EE4A }, { 0x1EE4C, 0x1EE4C },
+ { 0x1EE50, 0x1EE50 }, { 0x1EE53, 0x1EE53 }, { 0x1EE55, 0x1EE56 },
+ { 0x1EE58, 0x1EE58 }, { 0x1EE5A, 0x1EE5A }, { 0x1EE5C, 0x1EE5C },
+ { 0x1EE5E, 0x1EE5E }, { 0x1EE60, 0x1EE60 }, { 0x1EE63, 0x1EE63 },
+ { 0x1EE65, 0x1EE66 }, { 0x1EE6B, 0x1EE6B }, { 0x1EE73, 0x1EE73 },
+ { 0x1EE78, 0x1EE78 }, { 0x1EE7D, 0x1EE7D }, { 0x1EE7F, 0x1EE7F },
+ { 0x1EE8A, 0x1EE8A }, { 0x1EE9C, 0x1EEA0 }, { 0x1EEA4, 0x1EEA4 },
+ { 0x1EEAA, 0x1EEAA }, { 0x1EEBC, 0x1EEEF }, { 0x1EEF2, 0x1EFFF },
+ { 0x1F02C, 0x1F02F }, { 0x1F094, 0x1F09F }, { 0x1F0AF, 0x1F0B0 },
+ { 0x1F0C0, 0x1F0C0 }, { 0x1F0D0, 0x1F0D0 }, { 0x1F0F6, 0x1F0FF },
+ { 0x1F10D, 0x1F10F }, { 0x1F12F, 0x1F12F }, { 0x1F16C, 0x1F16F },
+ { 0x1F1AD, 0x1F1E5 }, { 0x1F203, 0x1F20F }, { 0x1F23C, 0x1F23F },
+ { 0x1F249, 0x1F24F }, { 0x1F252, 0x1F25F }, { 0x1F266, 0x1F2FF },
+ { 0x1F6D5, 0x1F6DF }, { 0x1F6ED, 0x1F6EF }, { 0x1F6F9, 0x1F6FF },
+ { 0x1F774, 0x1F77F }, { 0x1F7D5, 0x1F7FF }, { 0x1F80C, 0x1F80F },
+ { 0x1F848, 0x1F84F }, { 0x1F85A, 0x1F85F }, { 0x1F888, 0x1F88F },
+ { 0x1F8AE, 0x1F8FF }, { 0x1F90C, 0x1F90F }, { 0x1F93F, 0x1F93F },
+ { 0x1F94D, 0x1F94F }, { 0x1F96C, 0x1F97F }, { 0x1F998, 0x1F9BF },
+ { 0x1F9C1, 0x1F9CF }, { 0x1F9E7, 0x1FFFF }, { 0x2A6D7, 0x2F7FF },
+ { 0x2FA1E, 0xE0000 }, { 0xE0002, 0xE001F }, { 0xE0080, 0xE00FF },
+ { 0xE01F0, 0x10FFFD }
+ };
+
+ int result;
+
+#define Lookup(cmp, table) \
+ bisearch(cmp, table, \
+ (int) (sizeof(table) / sizeof(struct interval) - 1))
+
+ /* test for 8-bit control characters */
+ if (cmp == 0) {
+ result = 0;
+ } else if (cmp < 32 || (cmp >= 0x7f && cmp < 0xa0)) {
+ result = -1;
+ } else if (cmp == 0xad) {
+ result = use_latin1;
+ } else if (Lookup(cmp, combining)) {
+ /* binary search in table of non-spacing characters */
+ result = 0;
+ } else {
+ /* if we arrive here, cmp is not a combining or C0/C1 control character */
+ result = 1;
+
+ if (cmp >= 0x1100 &&
+ (cmp <= 0x115f || /* Hangul Jamo init. consonants */
+ cmp == 0x2329 ||
+ cmp == 0x232a ||
+ (cmp >= 0x2e80 && cmp <= 0x4dbf &&
+ cmp != 0x303f) || /* CJK ... Yi */
+ (cmp >= 0x4e00 && cmp <= 0xa4cf) || /* CJK Unified Ideographs, Yi */
+ (cmp >= 0xa960 && cmp <= 0xa97f) || /* Hangul Jamo Extended-A */
+ (cmp >= 0xac00 && cmp <= 0xd7a3) || /* Hangul Syllables */
+ (cmp >= 0xf900 && cmp <= 0xfaff) || /* CJK Compatibility Ideographs */
+ (cmp >= 0xfe10 && cmp <= 0xfe19) || /* Vertical forms */
+ (cmp >= 0xfe30 && cmp <= 0xfe6f) || /* CJK Compatibility Forms */
+ (cmp >= 0xff00 && cmp <= 0xff60) || /* Fullwidth Forms */
+ (cmp >= 0xffe0 && cmp <= 0xffe6) ||
+ (cmp >= 0x20000 && cmp <= 0x2fffd) ||
+ (cmp >= 0x30000 && cmp <= 0x3fffd))) {
+ result = 2;
+ }
+ if (cmp >= unknowns[0].first && Lookup(cmp, unknowns)) {
+ result = -1;
+ }
+ }
+ return result;
+}
+
+
+int mk_wcswidth(const wchar_t *pwcs, size_t n)
+{
+ int width = 0;
+
+ for (;*pwcs && n-- > 0; pwcs++) {
+ int w;
+
+ if ((w = mk_wcwidth(*pwcs)) < 0)
+ return -1;
+ else
+ width += w;
+ }
+
+ return width;
+}
+
+
+/*
+ * The following functions are the same as mk_wcwidth() and
+ * mk_wcwidth_cjk(), except that spacing characters in the East Asian
+ * Ambiguous (A) category as defined in Unicode Technical Report #11
+ * have a column width of 2. This variant might be useful for users of
+ * CJK legacy encodings who want to migrate to UCS without changing
+ * the traditional terminal character-width behaviour. It is not
+ * otherwise recommended for general use.
+ */
+int mk_wcwidth_cjk(wchar_t ucs)
+{
+ /* sorted list of non-overlapping intervals of East Asian Ambiguous
+ * characters, generated by
+ *
+ * uniset +WIDTH-A -cat=Me -cat=Mn -cat=Cf \
+ * +E000..F8FF \
+ * +F0000..FFFFD \
+ * +100000..10FFFD c
+ *
+ * "WIDTH-A" is a file extracted from EastAsianWidth.txt by selecting
+ * only those with width "A", and omitting:
+ *
+ * 0xAD
+ * all lines with "COMBINING"
+ *
+ * (uniset does not recognize the range expressions in WIDTH-A).
+ */
+ static const struct interval ambiguous[] = {
+ { 0x00A1, 0x00A1 }, { 0x00A4, 0x00A4 }, { 0x00A7, 0x00A8 },
+ { 0x00AA, 0x00AA }, { 0x00AE, 0x00AE }, { 0x00B0, 0x00B4 },
+ { 0x00B6, 0x00BA }, { 0x00BC, 0x00BF }, { 0x00C6, 0x00C6 },
+ { 0x00D0, 0x00D0 }, { 0x00D7, 0x00D8 }, { 0x00DE, 0x00E1 },
+ { 0x00E6, 0x00E6 }, { 0x00E8, 0x00EA }, { 0x00EC, 0x00ED },
+ { 0x00F0, 0x00F0 }, { 0x00F2, 0x00F3 }, { 0x00F7, 0x00FA },
+ { 0x00FC, 0x00FC }, { 0x00FE, 0x00FE }, { 0x0101, 0x0101 },
+ { 0x0111, 0x0111 }, { 0x0113, 0x0113 }, { 0x011B, 0x011B },
+ { 0x0126, 0x0127 }, { 0x012B, 0x012B }, { 0x0131, 0x0133 },
+ { 0x0138, 0x0138 }, { 0x013F, 0x0142 }, { 0x0144, 0x0144 },
+ { 0x0148, 0x014B }, { 0x014D, 0x014D }, { 0x0152, 0x0153 },
+ { 0x0166, 0x0167 }, { 0x016B, 0x016B }, { 0x01CE, 0x01CE },
+ { 0x01D0, 0x01D0 }, { 0x01D2, 0x01D2 }, { 0x01D4, 0x01D4 },
+ { 0x01D6, 0x01D6 }, { 0x01D8, 0x01D8 }, { 0x01DA, 0x01DA },
+ { 0x01DC, 0x01DC }, { 0x0251, 0x0251 }, { 0x0261, 0x0261 },
+ { 0x02C4, 0x02C4 }, { 0x02C7, 0x02C7 }, { 0x02C9, 0x02CB },
+ { 0x02CD, 0x02CD }, { 0x02D0, 0x02D0 }, { 0x02D8, 0x02DB },
+ { 0x02DD, 0x02DD }, { 0x02DF, 0x02DF }, { 0x0391, 0x03A1 },
+ { 0x03A3, 0x03A9 }, { 0x03B1, 0x03C1 }, { 0x03C3, 0x03C9 },
+ { 0x0401, 0x0401 }, { 0x0410, 0x044F }, { 0x0451, 0x0451 },
+ { 0x2010, 0x2010 }, { 0x2013, 0x2016 }, { 0x2018, 0x2019 },
+ { 0x201C, 0x201D }, { 0x2020, 0x2022 }, { 0x2024, 0x2027 },
+ { 0x2030, 0x2030 }, { 0x2032, 0x2033 }, { 0x2035, 0x2035 },
+ { 0x203B, 0x203B }, { 0x203E, 0x203E }, { 0x2074, 0x2074 },
+ { 0x207F, 0x207F }, { 0x2081, 0x2084 }, { 0x20AC, 0x20AC },
+ { 0x2103, 0x2103 }, { 0x2105, 0x2105 }, { 0x2109, 0x2109 },
+ { 0x2113, 0x2113 }, { 0x2116, 0x2116 }, { 0x2121, 0x2122 },
+ { 0x2126, 0x2126 }, { 0x212B, 0x212B }, { 0x2153, 0x2154 },
+ { 0x215B, 0x215E }, { 0x2160, 0x216B }, { 0x2170, 0x2179 },
+ { 0x2189, 0x2189 }, { 0x2190, 0x2199 }, { 0x21B8, 0x21B9 },
+ { 0x21D2, 0x21D2 }, { 0x21D4, 0x21D4 }, { 0x21E7, 0x21E7 },
+ { 0x2200, 0x2200 }, { 0x2202, 0x2203 }, { 0x2207, 0x2208 },
+ { 0x220B, 0x220B }, { 0x220F, 0x220F }, { 0x2211, 0x2211 },
+ { 0x2215, 0x2215 }, { 0x221A, 0x221A }, { 0x221D, 0x2220 },
+ { 0x2223, 0x2223 }, { 0x2225, 0x2225 }, { 0x2227, 0x222C },
+ { 0x222E, 0x222E }, { 0x2234, 0x2237 }, { 0x223C, 0x223D },
+ { 0x2248, 0x2248 }, { 0x224C, 0x224C }, { 0x2252, 0x2252 },
+ { 0x2260, 0x2261 }, { 0x2264, 0x2267 }, { 0x226A, 0x226B },
+ { 0x226E, 0x226F }, { 0x2282, 0x2283 }, { 0x2286, 0x2287 },
+ { 0x2295, 0x2295 }, { 0x2299, 0x2299 }, { 0x22A5, 0x22A5 },
+ { 0x22BF, 0x22BF }, { 0x2312, 0x2312 }, { 0x2460, 0x24E9 },
+ { 0x24EB, 0x254B }, { 0x2550, 0x2573 }, { 0x2580, 0x258F },
+ { 0x2592, 0x2595 }, { 0x25A0, 0x25A1 }, { 0x25A3, 0x25A9 },
+ { 0x25B2, 0x25B3 }, { 0x25B6, 0x25B7 }, { 0x25BC, 0x25BD },
+ { 0x25C0, 0x25C1 }, { 0x25C6, 0x25C8 }, { 0x25CB, 0x25CB },
+ { 0x25CE, 0x25D1 }, { 0x25E2, 0x25E5 }, { 0x25EF, 0x25EF },
+ { 0x2605, 0x2606 }, { 0x2609, 0x2609 }, { 0x260E, 0x260F },
+ { 0x261C, 0x261C }, { 0x261E, 0x261E }, { 0x2640, 0x2640 },
+ { 0x2642, 0x2642 }, { 0x2660, 0x2661 }, { 0x2663, 0x2665 },
+ { 0x2667, 0x266A }, { 0x266C, 0x266D }, { 0x266F, 0x266F },
+ { 0x269E, 0x269F }, { 0x26BF, 0x26BF }, { 0x26C6, 0x26CD },
+ { 0x26CF, 0x26D3 }, { 0x26D5, 0x26E1 }, { 0x26E3, 0x26E3 },
+ { 0x26E8, 0x26E9 }, { 0x26EB, 0x26F1 }, { 0x26F4, 0x26F4 },
+ { 0x26F6, 0x26F9 }, { 0x26FB, 0x26FC }, { 0x26FE, 0x26FF },
+ { 0x273D, 0x273D }, { 0x2776, 0x277F }, { 0x2B56, 0x2B59 },
+ { 0x3248, 0x324F }, { 0xE000, 0xF8FF }, { 0xFFFD, 0xFFFD },
+ { 0x1F100, 0x1F10A }, { 0x1F110, 0x1F12D }, { 0x1F130, 0x1F169 },
+ { 0x1F170, 0x1F18D }, { 0x1F18F, 0x1F190 }, { 0x1F19B, 0x1F1AC },
+ { 0xF0000, 0xFFFFD }, { 0x100000, 0x10FFFD }
+ };
+
+ /* binary search in table of non-spacing characters */
+ if (Lookup((unsigned long) ucs, ambiguous))
+ return 2;
+
+ return mk_wcwidth(ucs);
+}
+
+
+int mk_wcswidth_cjk(const wchar_t *pwcs, size_t n)
+{
+ int width = 0;
+
+ for (;*pwcs && n-- > 0; pwcs++) {
+ int w;
+
+ if ((w = mk_wcwidth_cjk(*pwcs)) < 0)
+ return -1;
+ else
+ width += w;
+ }
+
+ return width;
+}
diff --git a/src/wcwidth.h b/src/wcwidth.h
new file mode 100644
index 0000000..dde7c89
--- /dev/null
+++ b/src/wcwidth.h
@@ -0,0 +1,47 @@
+/* $XTermId: wcwidth.h,v 1.14 2017/06/18 17:56:35 tom Exp $ */
+
+/* $XFree86: xc/programs/xterm/wcwidth.h,v 1.5 2005/05/03 00:38:25 dickey Exp $ */
+
+/*
+ * Copyright 2000-2005,2017 by Thomas E. Dickey
+ *
+ * All Rights Reserved
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a
+ * copy of this software and associated documentation files (the
+ * "Software"), to deal in the Software without restriction, including
+ * without limitation the rights to use, copy, modify, merge, publish,
+ * distribute, sublicense, and/or sell copies of the Software, and to
+ * permit persons to whom the Software is furnished to do so, subject to
+ * the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included
+ * in all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
+ * OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+ * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
+ * IN NO EVENT SHALL THE ABOVE LISTED COPYRIGHT HOLDER(S) BE LIABLE FOR ANY
+ * CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
+ * TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
+ * SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+ *
+ * Except as contained in this notice, the name(s) of the above copyright
+ * holders shall not be used in advertising or otherwise to promote the
+ * sale, use or other dealings in this Software without prior written
+ * authorization.
+ */
+#ifndef included_wcwidth_h
+#define included_wcwidth_h 1
+
+#include <stddef.h>
+
+extern void mk_wcwidth_init(int mode);
+
+extern int mk_wcswidth(const wchar_t * pwcs, size_t n);
+extern int mk_wcswidth_cjk(const wchar_t * pwcs, size_t n);
+extern int mk_wcwidth(wchar_t ucs);
+extern int mk_wcwidth_cjk(wchar_t ucs);
+extern int wcswidth_cjk(const wchar_t * pwcs, size_t n);
+
+#endif /* included_wcwidth_h */
diff --git a/src/win_tcp.opt b/src/win_tcp.opt
new file mode 100644
index 0000000..7fcb9fd
--- /dev/null
+++ b/src/win_tcp.opt
@@ -0,0 +1 @@
+twg$tcp:[netdist.lib]twglib/library