1 /*
2  * $LynxId: GridText.c,v 1.313 2018/05/15 20:43:41 tom Exp $
3  *
4  *		Character grid hypertext object
5  *		===============================
6  */
7 
8 #include <HTUtils.h>
9 #include <HTString.h>
10 #include <HTAccess.h>
11 #include <HTAnchor.h>
12 #include <HTParse.h>
13 #include <HTTP.h>
14 #include <HTAlert.h>
15 #include <HTCJK.h>
16 #include <HTFile.h>
17 #include <UCDefs.h>
18 #include <UCAux.h>
19 #include <HText.h>
20 
21 #include <assert.h>
22 
23 #include <GridText.h>
24 #include <LYCurses.h>
25 #include <LYUtils.h>
26 #include <LYStrings.h>
27 #include <LYStructs.h>
28 #include <LYGlobalDefs.h>
29 #include <LYGetFile.h>
30 #include <LYClean.h>
31 #include <LYMail.h>
32 #include <LYList.h>
33 #include <LYCharSets.h>
34 #include <LYCharUtils.h>	/* LYUCTranslateBack... */
35 #include <UCMap.h>
36 #include <LYEdit.h>
37 #include <LYPrint.h>
38 #include <LYPrettySrc.h>
39 #include <LYSearch.h>
40 #include <TRSTable.h>
41 #include <LYHistory.h>
42 #ifdef EXP_CHARTRANS_AUTOSWITCH
43 #include <UCAuto.h>
44 #endif /* EXP_CHARTRANS_AUTOSWITCH */
45 
46 #include <LYexit.h>
47 #include <LYLeaks.h>
48 
49 #ifdef USE_COLOR_STYLE
50 #include <AttrList.h>
51 #include <LYHash.h>
52 #include <LYStyle.h>
53 
54 #endif
55 
56 #include <LYJustify.h>
57 
58 #define is_CJK2(b) (IS_CJK_TTY && is8bits(UCH(b)))
59 
60 #ifdef USE_CURSES_PADS
61 #  define DISPLAY_COLS    (LYwideLines ? MAX_COLS : LYcols)
62 #  define WRAP_COLS(text) ((text)->stbl ?				\
63 			   (LYtableCols <= 0				\
64 			    ? DISPLAY_COLS				\
65 			    : (LYtableCols * LYcols)/12) - LYbarWidth	\
66 			   : LYcolLimit)
67 #else
68 #  define DISPLAY_COLS    LYcols
69 #  define WRAP_COLS(text) LYcolLimit
70 #endif
71 
72 #define FirstHTLine(text) ((text)->last_line->next)
73 #define LastHTLine(text)  ((text)->last_line)
74 
75 static void HText_trimHightext(HText *text, int final, int stop_before);
76 
77 #define IS_UTF_EXTRA(ch) (text->T.output_utf8 && \
78 			  (UCH((ch))&0xc0) == 0x80)
79 
80 #define IS_UTF8_EXTRA(ch) (!(text && text->T.output_utf8) || \
81 			  !is8bits(ch) || \
82 			  (UCH(line->data[i] & 0xc0) == 0xc0))
83 
84 /* a test in compact form: how many extra UTF-8 chars after initial? - kw */
85 #define UTF8_XNEGLEN(c) (c&0xC0? 0 :c&32? 1 :c&16? 2 :c&8? 3 :c&4? 4 :c&2? 5:0)
86 #define UTF_XLEN(c) UTF8_XNEGLEN(((char)~(c)))
87 
88 #ifdef KANJI_CODE_OVERRIDE
89 HTkcode last_kcode = NOKANJI;	/* 1997/11/14 (Fri) 09:09:26 */
90 #endif
91 
92 #undef  CHAR_WIDTH
93 
94 #ifdef CJK_EX
95 #define CHAR_WIDTH 6
96 #else
97 #define CHAR_WIDTH 1
98 #endif
99 
100 /*	Exports
101 */
102 HText *HTMainText = NULL;	/* Equivalent of main window */
103 HTParentAnchor *HTMainAnchor = NULL;	/* Anchor for HTMainText */
104 
105 const char *HTAppName = LYNX_NAME;	/* Application name */
106 const char *HTAppVersion = LYNX_VERSION;	/* Application version */
107 
108 static int HTFormNumber = 0;
109 static int HTFormFields = 0;
110 static char *HTCurSelectGroup = NULL;	/* Form select group name */
111 static int HTCurSelectGroupCharset = -1;	/* ... and name's charset */
112 int HTCurSelectGroupType = F_RADIO_TYPE;	/* Group type */
113 char *HTCurSelectGroupSize = NULL;	/* Length of select */
114 static char *HTCurSelectedOptionValue = NULL;	/* Select choice */
115 
116 const char *checked_box = "[X]";
117 const char *unchecked_box = "[ ]";
118 const char *checked_radio = "(*)";
119 const char *unchecked_radio = "( )";
120 
121 static BOOLEAN underline_on = FALSE;
122 static BOOLEAN bold_on = FALSE;
123 
124 #ifdef USE_SOURCE_CACHE
125 int LYCacheSource = SOURCE_CACHE_NONE;
126 int LYCacheSourceForAborted = SOURCE_CACHE_FOR_ABORTED_DROP;
127 #endif
128 
129 #ifdef USE_SCROLLBAR
130 BOOLEAN LYShowScrollbar = FALSE;
131 BOOLEAN LYsb_arrow = TRUE;
132 int LYsb_begin = -1;
133 int LYsb_end = -1;
134 #endif
135 
136 #ifndef VMS			/* VMS has a better way - right? - kw */
137 #define CHECK_FREE_MEM
138 #endif
139 
140 #ifdef CHECK_FREE_MEM
141 static void *LY_check_calloc(size_t nmemb, size_t size);
142 
143 #define LY_CALLOC LY_check_calloc
144 #else
145   /* using the regular calloc */
146 #define LY_CALLOC calloc
147 #endif
148 
149 /*
150  * The HTPool.data[] array has to align the same as malloc() would, to make the
151  * ALLOC_POOL scheme portable.  For many platforms, that is the same as the
152  * number of bytes in a pointer.  It may be larger, e.g., on machines which
153  * have more stringent requirements for floating point.  32-bits are plenty for
154  * representing styles, but we may need 64-bit or 128-bit alignment.
155  *
156  * The real issue is that performance is degraded if the alignment is not met,
157  * and some platforms such as Tru64 generate lots of warning messages.
158  */
159 #ifndef ALIGN_SIZE
160 #define ALIGN_SIZE      sizeof(double)
161 #endif
162 
163 #define BITS_DIR	2
164 #define BITS_POS	14
165 
166 #define MASK_DIR	((1U << BITS_DIR) - 1)
167 #define CAST_DIR(n)	((MASK_DIR) & (unsigned)(n))
168 
169 #define MASK_POS	((1U << BITS_POS) - 1)
170 #define CAST_POS(n)	((MASK_POS) & (unsigned)(n))
171 
172 typedef struct {
173     unsigned sc_direction:BITS_DIR;	/* on or off */
174     unsigned sc_horizpos:BITS_POS;	/* horizontal position of this change */
175     unsigned sc_style:16;	/* which style to change to */
176 } HTStyleChange;
177 
178 #if defined(USE_COLOR_STYLE)
179 #define MAX_STYLES_ON_LINE   64
180   /* buffers used when current line is being aggregated, in split_line() */
181 static HTStyleChange stylechanges_buffers[2][MAX_STYLES_ON_LINE];
182 #endif
183 
184 typedef HTStyleChange pool_data;
185 
186 enum {
187     POOL_SIZE = ((8192
188 		  - 4 * sizeof(void *)
189 		  - sizeof(struct _HTPool *)
190 		  - sizeof(int))
191 		 / sizeof(pool_data))
192 };
193 
194 typedef struct _HTPool {
195     pool_data data[POOL_SIZE];
196     struct _HTPool *prev;
197     unsigned used;
198 } HTPool;
199 
200 /************************************************************************
201 These are generic macros for any pools (provided those structures have the
202 same members as HTPool).  Pools are used for allocation of groups of
203 objects of the same type T.  Pools are represented as a list of structures of
204 type P (called pool chunks here).  Structure P has an array of N objects of
205 type T named 'data' (the number N in the array can be chosen arbitrary),
206 pointer to the previous pool chunk named 'prev', and the number of used items
207 in that pool chunk named 'used'.  Here is a definition of the structure P:
208 	struct P
209 	{
210 	    T data[N];
211 	    struct P* prev;
212 	    int used;
213 	};
214  It's recommended that sizeof(P) be memory page size minus 32 in order malloc'd
215 chunks to fit in machine page size.
216  Allocation of 'n' items in the pool is implemented by incrementing member
217 'used' by 'n' if (used+n <= N), or malloc a new pool chunk and
218 allocating 'n' items in that new chunk.  It's the task of the programmer to
219 assert that 'n' is <= N.  Only entire pool may be freed - this limitation makes
220 allocation algorithms trivial and fast - so the use of pools is limited to
221 objects that are freed in batch, that are not deallocated not in the batch, and
222 not reallocated.
223  Pools greatly reduce memory fragmentation and memory allocation/deallocation
224 speed due to the simple algorithms used.  Due to the fact that memory is
225 'allocated' in array, alignment overhead is minimal.  Allocating strings in a
226 pool provided their length will never exceed N and is much smaller than N seems
227 to be very efficient.
228  [Several types of memory-hungry objects are stored in the pool now:  styles,
229 lines, anchors, and FormInfo. Arrays of HTStyleChange are stored as is,
230 other objects are stored using a cast.]
231 
232  Pool is referenced by the pointer to the last chunk that contains free slots.
233 Functions that allocate memory in the pool update that pointer if needed.
234 There are 3 functions - POOL_NEW, POOL_FREE, and ALLOC_IN_POOL.
235 
236       - VH
237 
238 *************************************************************************/
239 
240 #define POOLallocstyles(ptr, n)     ptr = ALLOC_IN_POOL(&HTMainText->pool, (unsigned) ((n) * sizeof(pool_data)))
241 #define POOLallocHTLine(ptr, size)  ptr = (HTLine*) ALLOC_IN_POOL(&HTMainText->pool, (unsigned) LINE_SIZE(size))
242 #define POOLallocstring(ptr, len)   ptr = (char*) ALLOC_IN_POOL(&HTMainText->pool, (unsigned) ((len) + 1))
243 #define POOLtypecalloc(T, ptr)      ptr = (T*) ALLOC_IN_POOL(&HTMainText->pool, (unsigned) sizeof(T))
244 
245 /**************************************************************************/
246 /*
247  * Allocates 'n' items in the pool of type 'HTPool' pointed by 'poolptr'.
248  * Returns a pointer to the "allocated" memory if successful.
249  * Updates 'poolptr' if necessary.
250  */
ALLOC_IN_POOL(HTPool ** ppoolptr,unsigned request)251 static void *ALLOC_IN_POOL(HTPool ** ppoolptr, unsigned request)
252 {
253     HTPool *pool = *ppoolptr;
254     pool_data *ptr;
255     unsigned n;
256     unsigned j;
257 
258     if (!pool) {
259 	outofmem(__FILE__, "ALLOC_IN_POOL");
260     } else {
261 	n = request;
262 	if (n == 0)
263 	    n = 1;
264 	j = (n % ALIGN_SIZE);
265 	if (j != 0)
266 	    n += (unsigned) (ALIGN_SIZE - j);
267 	n /= sizeof(pool_data);
268 
269 	if (POOL_SIZE >= (pool->used + n)) {
270 	    ptr = pool->data + pool->used;
271 	    pool->used += n;
272 	} else {
273 	    HTPool *newpool = (HTPool *) LY_CALLOC((size_t) 1, sizeof(HTPool));
274 
275 	    if (!newpool) {
276 		outofmem(__FILE__, "ALLOC_IN_POOL");
277 	    } else {
278 		newpool->prev = pool;
279 		newpool->used = n;
280 		ptr = newpool->data;
281 		*ppoolptr = newpool;
282 	    }
283 	}
284     }
285     return ptr;
286 }
287 
288 /*
289  * Returns a pointer to initialized pool of type 'HTPool', or NULL if fails.
290  */
POOL_NEW(void)291 static HTPool *POOL_NEW(void)
292 {
293     HTPool *poolptr = (HTPool *) LY_CALLOC((size_t) 1, sizeof(HTPool));
294 
295     if (poolptr) {
296 	poolptr->prev = NULL;
297 	poolptr->used = 0;
298     }
299     return poolptr;
300 }
301 
302 /*
303  * Frees a pool of type 'HTPool' pointed by poolptr.
304  */
POOL_FREE(HTPool * poolptr)305 static void POOL_FREE(HTPool * poolptr)
306 {
307     HTPool *cur = poolptr;
308     HTPool *prev;
309 
310     while (cur) {
311 	prev = cur->prev;
312 	free(cur);
313 	cur = prev;
314     }
315 }
316 
317 /**************************************************************************/
318 /**************************************************************************/
319 
320 typedef struct _line {
321     struct _line *next;
322     struct _line *prev;
323     unsigned short offset;	/* Implicit initial spaces */
324     unsigned short size;	/* Number of characters */
325 #if defined(USE_COLOR_STYLE)
326     HTStyleChange *styles;
327     unsigned short numstyles;
328 #endif
329     char data[1];		/* Space for terminator at least! */
330 } HTLine;
331 
332     /* Allow for terminator */
333 #define LINE_SIZE(size) (sizeof(HTLine) + (size_t)(size))
334 
335 #ifndef HTLINE_NOT_IN_POOL
336 #define HTLINE_NOT_IN_POOL 0	/* debug with this set to 1 */
337 #endif
338 
339 #if HTLINE_NOT_IN_POOL
340 #define allocHTLine(ptr, size)  { ptr = (HTLine *)calloc(1, LINE_SIZE(size)); }
341 #define freeHTLine(self, ptr)   { \
342 	if (ptr && ptr != TEMP_LINE(self, 0) && ptr != TEMP_LINE(self, 1)) \
343 	    FREE(ptr); \
344     }
345 #else
346 #define allocHTLine(ptr, size)  POOLallocHTLine(ptr, size)
347 #define freeHTLine(self, ptr)   {}
348 #endif
349 
350 /*
351  * Last line buffer; the second is used in split_line(). Not in pool!
352  * We cannot wrap in middle of multibyte sequences, so allocate 2 extra
353  * for a workspace.  This is stored in the HText, to prevent confusion
354  * between different documents.  Note also that it is declared with an
355  * HTLine at the beginning so pointers will be properly aligned.
356  */
357 typedef struct {
358     HTLine base;
359     char data[MAX_LINE + 2];
360 } HTLineTemp;
361 
362 #define TEMP_LINE(p,n) ((HTLine *)&(p->temp_line[n]))
363 
364 typedef struct _TextAnchor {
365     struct _TextAnchor *next;
366     struct _TextAnchor *prev;	/* www_user_search only! */
367     int sgml_offset;		/* used for updating position after reparsing */
368     int number;			/* For user interface */
369     int show_number;		/* For user interface (unique-urls) */
370     int line_num;		/* Place in document */
371     short line_pos;		/* Bytes/chars - extent too */
372     short extent;		/* (see HText_trimHightext) */
373     BOOL show_anchor;		/* Show the anchor? */
374     BOOL inUnderline;		/* context is underlined */
375     BOOL expansion_anch;	/* TEXTAREA edit new anchor */
376     char link_type;		/* Normal, internal, or form? */
377     FormInfo *input_field;	/* Info for form links */
378     HiliteList lites;
379 
380     HTChildAnchor *anchor;
381 } TextAnchor;
382 
383 typedef struct {
384     char *name;			/* ID value of TAB */
385     int column;			/* Zero-based column value */
386 } HTTabID;
387 
388 typedef enum {
389     S_text,
390     S_esc,
391     S_dollar,
392     S_paren,
393     S_nonascii_text,
394     S_dollar_paren,
395     S_jisx0201_text
396 } eGridState;			/* Escape sequence? */
397 
398 #ifdef USE_TH_JP_AUTO_DETECT
399 typedef enum {			/* Detected Kanji code */
400     DET_SJIS,
401     DET_EUC,
402     DET_NOTYET,
403     DET_MIXED
404 } eDetectedKCode;
405 
406 typedef enum {
407     SJIS_state_neutral,
408     SJIS_state_in_kanji,
409     SJIS_state_has_bad_code
410 } eSJIS_status;
411 
412 typedef enum {
413     EUC_state_neutral,
414     EUC_state_in_kanji,
415     EUC_state_in_kana,
416     EUC_state_has_bad_code
417 } eEUC_status;
418 #endif
419 
420 /*	Notes on struct _HText:
421  *	next_line is valid if stale is false.
422  *	top_of_screen line means the line at the top of the screen
423  *			or just under the title if there is one.
424  */
425 struct _HText {
426     HTParentAnchor *node_anchor;
427 
428     HTLine *last_line;
429     HTLineTemp temp_line[2];
430     int Lines;			/* Number of them */
431     TextAnchor *first_anchor;	/* double-linked on demand */
432     TextAnchor *last_anchor;
433     TextAnchor *last_anchor_before_stbl;
434     TextAnchor *last_anchor_before_split;
435     HTList *forms;		/* also linked internally */
436     int last_anchor_number;	/* user number */
437     BOOL source;		/* Is the text source? */
438     BOOL toolbar;		/* Toolbar set? */
439     HTList *tabs;		/* TAB IDs */
440     HTList *hidden_links;	/* Content-less links ... */
441     int hiddenlinkflag;		/*  ... and how to treat them */
442     BOOL no_cache;		/* Always refresh? */
443     char LastChar;		/* For absorbing white space */
444 
445 /* For Internal use: */
446     HTStyle *style;		/* Current style */
447     int display_on_the_fly;	/* Lines left */
448     int top_of_screen;		/* Line number */
449     HTLine *top_of_screen_line;	/* Top */
450     HTLine *next_line;		/* Bottom + 1 */
451     unsigned permissible_split;	/* in last line */
452     BOOL in_line_1;		/* of paragraph */
453     BOOL stale;			/* Must refresh */
454     BOOL page_has_target;	/* has target on screen */
455     BOOL has_utf8;		/* has utf-8 on screen or line */
456     BOOL had_utf8;		/* had utf-8 when last displayed */
457     int next_number;		/* next a->number value */
458 #ifdef DISP_PARTIAL
459     int first_lineno_last_disp_partial;
460     int last_lineno_last_disp_partial;
461 #endif
462     STable_info *stbl;
463     HTList *enclosed_stbl;
464 
465     HTkcode kcode;		/* Kanji code? */
466     HTkcode specified_kcode;	/* Specified Kanji code */
467 #ifdef USE_TH_JP_AUTO_DETECT
468     eDetectedKCode detected_kcode;
469     eSJIS_status SJIS_status;
470     eEUC_status EUC_status;
471 #endif
472     eGridState state;		/* Escape sequence? */
473     int kanji_buf;		/* Lead multibyte */
474     int in_sjis;		/* SJIS flag */
475     int halted;			/* emergency halt */
476 
477     BOOL have_8bit_chars;	/* Any non-ASCII chars? */
478     LYUCcharset *UCI;		/* node_anchor UCInfo */
479     int UCLYhndl;		/* charset we are fed */
480     UCTransParams T;
481 
482     HTStream *target;		/* Output stream */
483     HTStreamClass targetClass;	/* Output routines */
484 
485     HTPool *pool;		/* this HText memory pool */
486 
487 #ifdef USE_SOURCE_CACHE
488     /*
489      * Parse settings when this HText was generated.
490      */
491     BOOL clickable_images;
492     BOOL pseudo_inline_alts;
493     BOOL verbose_img;
494     BOOL raw_mode;
495     BOOL historical_comments;
496     BOOL minimal_comments;
497     BOOL soft_dquotes;
498     short old_dtd;
499     short keypad_mode;
500     short disp_lines;		/* Screen size */
501     short disp_cols;		/* Used for reports only */
502 #endif
503 };
504 
505 /* exported */
HText_pool_calloc(HText * text,unsigned size)506 void *HText_pool_calloc(HText *text, unsigned size)
507 {
508     return (void *) ALLOC_IN_POOL(&text->pool, size);
509 }
510 
511 static void HText_AddHiddenLink(HText *text, TextAnchor *textanchor);
512 
513 #ifdef USE_JUSTIFY_ELTS
514 BOOL can_justify_here;
515 BOOL can_justify_here_saved;
516 
517 BOOL can_justify_this_line;	/* =FALSE if line contains form objects */
518 int wait_for_this_stacked_elt;	/* -1 if can justify contents of the
519 
520 				   element on the op of stack. If positive - specifies minimal stack depth
521 				   plus 1 at which we can justify element (can be MAX_LINE+2 if
522 				   ok_justify ==FALSE or in psrcview. */
523 BOOL form_in_htext;		/*to indicate that we are in form (since HTML_FORM is
524 
525 				   not stacked in the HTML.c */
526 BOOL in_DT = FALSE;
527 
528 #ifdef DEBUG_JUSTIFY
529 BOOL can_justify_stack_depth;	/* can be 0 or 1 if all code is correct */
530 #endif
531 
532 typedef struct {
533     int byte_len;		/*length in bytes */
534     int cell_len;		/*length in cells */
535 } ht_run_info;
536 
537 static int justify_start_position;	/* this is an index of char from which
538 
539 					   justification can start (eg after "* " preceeding <li> text) */
540 
541 static int ht_num_runs;		/*the number of runs filled */
542 static ht_run_info ht_runs[MAX_LINE];
543 static BOOL this_line_was_split;
544 static TextAnchor *last_anchor_of_previous_line;
545 static BOOL have_raw_nbsps = FALSE;
546 
ht_justify_cleanup(void)547 void ht_justify_cleanup(void)
548 {
549     wait_for_this_stacked_elt = !ok_justify
550 #  ifdef USE_PRETTYSRC
551 	|| psrc_view
552 #  endif
553 	? 30000 /*MAX_NESTING */  + 2	/*some unreachable value */
554 	: -1;
555     can_justify_here = TRUE;
556     can_justify_this_line = TRUE;
557     form_in_htext = FALSE;
558 
559     last_anchor_of_previous_line = NULL;
560     this_line_was_split = FALSE;
561     in_DT = FALSE;
562     have_raw_nbsps = FALSE;
563 }
564 
mark_justify_start_position(void * text)565 void mark_justify_start_position(void *text)
566 {
567     if (text && ((HText *) text)->last_line)
568 	justify_start_position = ((HText *) text)->last_line->size;
569 }
570 
571 #define REALLY_CAN_JUSTIFY(text) ( (wait_for_this_stacked_elt<0) && \
572 	( text->style->alignment == HT_LEFT     || \
573 	  text->style->alignment == HT_JUSTIFY) && \
574 	!IS_CJK_TTY && !in_DT && \
575 	can_justify_here && can_justify_this_line && !form_in_htext )
576 
577 #endif /* USE_JUSTIFY_ELTS */
578 
579 /*
580  * Boring static variable used for moving cursor across
581  */
582 #define UNDERSCORES(n) \
583  ((n) >= MAX_LINE ? underscore_string : &underscore_string[(MAX_LINE-1)] - (n))
584 
585 static char underscore_string[MAX_LINE + 1];
586 char star_string[MAX_LINE + 1];
587 
588 static int ctrl_chars_on_this_line = 0;		/* num of ctrl chars in current line */
589 static int utfxtra_on_this_line = 0;	/* num of UTF-8 extra bytes in line,
590 
591 					   they *also* count as ctrl chars. */
592 #ifdef WIDEC_CURSES
593 #define UTFXTRA_ON_THIS_LINE 0
594 #else
595 #define UTFXTRA_ON_THIS_LINE utfxtra_on_this_line
596 #endif
597 
598 static HTStyle default_style =
599 {0, NULL, "(Unstyled)", 0, NULL, "",
600  (HTFont) 0, 1, HT_BLACK, 0, 0,
601  0, 0, 0, HT_LEFT, 1, 0, 0,
602  NO, NO, 0, 0, 0};
603 
604 static HTList *loaded_texts = NULL;	/* A list of all those in memory */
605 HTList *search_queries = NULL;	/* isindex and whereis queries   */
606 
607 #ifdef LY_FIND_LEAKS
608 static void free_all_texts(void);
609 #endif
610 
611 static BOOL HText_TrueEmptyLine(HTLine *line, HText *text, int IgnoreSpaces);
612 
613 static int HText_TrueLineSize(HTLine *line, HText *text, int IgnoreSpaces);
614 
615 #ifdef CHECK_FREE_MEM
616 
617 /*
618  * text->halted = 1: have set fake 'Z' and output a message
619  *		  2: next time when HText_appendCharacter is called
620  *		     it will append *** MEMORY EXHAUSTED ***, then set
621  *		     to 3.
622  *		  3: normal text output will be suppressed (but not anchors,
623  *		     form fields etc.)
624  */
HText_halt(void)625 static void HText_halt(void)
626 {
627     if (HTFormNumber > 0)
628 	HText_DisableCurrentForm();
629     if (!HTMainText)
630 	return;
631     if (HTMainText->halted < 2)
632 	HTMainText->halted = 2;
633 }
634 
635 #define MIN_NEEDED_MEM 5000
636 
637 /*
638  * Check whether factor*min(bytes,MIN_NEEDED_MEM) is available,
639  * or bytes if factor is 0.
640  * MIN_NEEDED_MEM and factor together represent a security margin,
641  * to take account of all the memory allocations where we don't check
642  * and of buffers which may be emptied before HTCheckForInterupt()
643  * is (maybe) called and other things happening, with some chance of
644  * success.
645  * This just tries to malloc() the to-be-checked-for amount of memory,
646  * which might make the situation worse depending how allocation works.
647  * There should be a better way...  - kw
648  */
mem_is_avail(int factor,size_t bytes)649 static BOOL mem_is_avail(int factor, size_t bytes)
650 {
651     void *p;
652 
653     if (bytes < MIN_NEEDED_MEM && factor > 0)
654 	bytes = MIN_NEEDED_MEM;
655     if (factor == 0)
656 	factor = 1;
657     p = malloc((size_t) factor * bytes);
658     if (p) {
659 	FREE(p);
660 	return YES;
661     } else {
662 	return NO;
663     }
664 }
665 
666 /*
667  * Replacement for calloc which checks for "enough" free memory
668  * (with some security margins) and tries various recovery actions
669  * if deemed necessary.  - kw
670  */
LY_check_calloc(size_t nmemb,size_t size)671 static void *LY_check_calloc(size_t nmemb, size_t size)
672 {
673     int i, n;
674 
675     if (mem_is_avail(4, nmemb * size)) {
676 	return (calloc(nmemb, size));
677     }
678     n = HTList_count(loaded_texts);
679     for (i = n - 1; i > 0; i--) {
680 	HText *t = (HText *) HTList_objectAt(loaded_texts, i);
681 
682 	CTRACE((tfp,
683 		"\nBUG *** Emergency freeing document %d/%d for '%s'%s!\n",
684 		i + 1, n,
685 		((t && t->node_anchor &&
686 		  t->node_anchor->address) ?
687 		 t->node_anchor->address : "unknown anchor"),
688 		((t && t->node_anchor &&
689 		  t->node_anchor->post_data) ?
690 		 " with POST data" : "")));
691 	HTList_removeObjectAt(loaded_texts, i);
692 	HText_free(t);
693 	if (mem_is_avail(4, nmemb * size)) {
694 	    return (calloc(nmemb, size));
695 	}
696     }
697     LYFakeZap(YES);
698     if (!HTMainText || HTMainText->halted <= 1) {
699 	if (!mem_is_avail(2, nmemb * size)) {
700 	    HText_halt();
701 	    if (mem_is_avail(0, (size_t) 700)) {
702 		HTAlert(gettext("Memory exhausted, display interrupted!"));
703 	    }
704 	} else {
705 	    if ((!HTMainText || HTMainText->halted == 0) &&
706 		mem_is_avail(0, (size_t) 700)) {
707 		HTAlert(gettext("Memory exhausted, will interrupt transfer!"));
708 		if (HTMainText)
709 		    HTMainText->halted = 1;
710 	    }
711 	}
712     }
713     return (calloc(nmemb, size));
714 }
715 
716 #endif /* CHECK_FREE_MEM */
717 
718 #ifdef USE_COLOR_STYLE
719 /*
720  * Color style information is stored with the multibyte-character offset into
721  * the string at which the style would apply.  Compute the corresponding column
722  * so we can compare it with the updated column value after writing strings
723  * with curses.
724  *
725  * The offsets count multibyte characters.  Other parts of the code assume each
726  * character uses one cell, but some CJK (or UTF-8) codes use two cells.  We
727  * need to know the number of cells.
728  */
StyleToCols(HText * text,HTLine * line,int nstyle)729 static int StyleToCols(HText *text, HTLine *line, int nstyle)
730 {
731     int result = line->offset;	/* this much is spaces one byte/cell */
732     int nchars = line->styles[nstyle].sc_horizpos;
733     char *data = line->data;
734     char *last = line->size + data;
735     int utf_extra;
736 
737     while (nchars > 0 && data < last) {
738 	if (IsSpecialAttrChar(*data) && *data != LY_SOFT_NEWLINE) {
739 	    ++data;
740 	} else {
741 	    utf_extra = (int) utf8_length(text->T.output_utf8, data);
742 	    if (utf_extra++) {
743 		result += LYstrExtent(data, utf_extra, 2);
744 		data += utf_extra;
745 	    } else if (is_CJK2(*data)) {
746 		data += 2;
747 		result += 2;
748 	    } else {
749 		++data;
750 		++result;
751 	    }
752 	    --nchars;
753 	}
754     }
755 
756     return result;
757 }
758 #endif
759 
760 /*
761  * Clear highlight information for a given anchor
762  * (text was allocated in the pool).
763  */
LYClearHiText(TextAnchor * a)764 static void LYClearHiText(TextAnchor *a)
765 {
766     FREE(a->lites.hl_info);
767 
768     a->lites.hl_base.hl_text = NULL;
769     a->lites.hl_len = 0;
770 }
771 
772 #define LYFreeHiText(a)     FREE((a)->lites.hl_info)
773 
774 /*
775  * Set the initial highlight information for a given anchor.
776  */
LYSetHiText(TextAnchor * a,const char * text,unsigned len)777 static void LYSetHiText(TextAnchor *a,
778 			const char *text,
779 			unsigned len)
780 {
781     if (text != NULL) {
782 	POOLallocstring(a->lites.hl_base.hl_text, len + 1);
783 	memcpy(a->lites.hl_base.hl_text, text, (size_t) len);
784 	*(a->lites.hl_base.hl_text + len) = '\0';
785 
786 	a->lites.hl_len = 1;
787     }
788 }
789 
790 /*
791  * Add highlight information for the next line of a anchor.
792  */
LYAddHiText(TextAnchor * a,const char * text,int x)793 static void LYAddHiText(TextAnchor *a,
794 			const char *text,
795 			int x)
796 {
797     HiliteInfo *have = a->lites.hl_info;
798     size_t need = (unsigned) (a->lites.hl_len - 1);
799     size_t want;
800 
801     a->lites.hl_len = (short) (a->lites.hl_len + 1);
802     want = (size_t) (a->lites.hl_len) * sizeof(HiliteInfo);
803     if (have != NULL) {
804 	have = (HiliteInfo *) realloc(have, want);
805     } else {
806 	have = (HiliteInfo *) malloc(want);
807     }
808     a->lites.hl_info = have;
809 
810     POOLallocstring(have[need].hl_text, strlen(text) + 1);
811     strcpy(have[need].hl_text, text);
812     have[need].hl_x = (short) x;
813 }
814 
815 /*
816  * Return an offset to skip leading blanks in the highlighted link.  That is
817  * needed to avoid having the color-style paint the leading blanks.
818  */
819 #ifdef USE_COLOR_STYLE
LYAdjHiTextPos(TextAnchor * a,int count)820 static int LYAdjHiTextPos(TextAnchor *a, int count)
821 {
822     char *result;
823 
824     if (count >= a->lites.hl_len)
825 	result = NULL;
826     else if (count > 0)
827 	result = a->lites.hl_info[count - 1].hl_text;
828     else
829 	result = a->lites.hl_base.hl_text;
830 
831     return (result != 0) ? (int) (LYSkipBlanks(result) - result) : 0;
832 }
833 
834 #else
835 #define LYAdjHiTextPos(a,count) 0
836 #endif
837 
838 /*
839  * Get the highlight text, counting from zero.
840  */
LYGetHiTextStr(TextAnchor * a,int count)841 static char *LYGetHiTextStr(TextAnchor *a, int count)
842 {
843     char *result;
844 
845     if (count >= a->lites.hl_len)
846 	result = NULL;
847     else if (count > 0)
848 	result = a->lites.hl_info[count - 1].hl_text;
849     else
850 	result = a->lites.hl_base.hl_text;
851     result += LYAdjHiTextPos(a, count);
852     return result;
853 }
854 
855 /*
856  * Get the X-ordinate at which to draw the corresponding highlight-text
857  */
LYGetHiTextPos(TextAnchor * a,int count)858 static int LYGetHiTextPos(TextAnchor *a, int count)
859 {
860     int result;
861 
862     if (count >= a->lites.hl_len)
863 	result = -1;
864     else if (count > 0)
865 	result = a->lites.hl_info[count - 1].hl_x;
866     else
867 	result = a->line_pos;
868     result += LYAdjHiTextPos(a, count);
869     return result;
870 }
871 
872 /*
873  * Copy highlighting information from anchor 'b' to 'a'.
874  */
LYCopyHiText(TextAnchor * a,TextAnchor * b)875 static void LYCopyHiText(TextAnchor *a, TextAnchor *b)
876 {
877     int count;
878     char *s;
879 
880     LYClearHiText(a);
881     for (count = 0;; ++count) {
882 	if ((s = LYGetHiTextStr(b, count)) == NULL)
883 	    break;
884 	if (count == 0) {
885 	    LYSetHiText(a, s, (unsigned) strlen(s));
886 	} else {
887 	    LYAddHiText(a, s, LYGetHiTextPos(b, count));
888 	}
889     }
890 }
891 
HText_getChartransInfo(HText * me)892 static void HText_getChartransInfo(HText *me)
893 {
894     me->UCLYhndl = HTAnchor_getUCLYhndl(me->node_anchor, UCT_STAGE_HTEXT);
895     if (me->UCLYhndl < 0) {
896 	int chndl = current_char_set;
897 
898 	HTAnchor_setUCInfoStage(me->node_anchor, chndl,
899 				UCT_STAGE_HTEXT, UCT_SETBY_STRUCTURED);
900 	me->UCLYhndl = HTAnchor_getUCLYhndl(me->node_anchor,
901 					    UCT_STAGE_HTEXT);
902     }
903     me->UCI = HTAnchor_getUCInfoStage(me->node_anchor, UCT_STAGE_HTEXT);
904 }
905 
PerFormInfo_free(PerFormInfo * form)906 static void PerFormInfo_free(PerFormInfo * form)
907 {
908     if (form) {
909 	FREE(form->data.submit_action);
910 	FREE(form->data.submit_enctype);
911 	FREE(form->data.submit_title);
912 	FREE(form->accept_cs);
913 	FREE(form->thisacceptcs);
914 	FREE(form);
915     }
916 }
917 
free_form_fields(FormInfo * input_field)918 static void free_form_fields(FormInfo * input_field)
919 {
920     /*
921      * Free form fields.
922      */
923     if (input_field->type == F_OPTION_LIST_TYPE &&
924 	input_field->select_list != NULL) {
925 	/*
926 	 * Free off option lists if present.
927 	 * It should always be present for F_OPTION_LIST_TYPE
928 	 * unless we had invalid markup which prevented
929 	 * HText_setLastOptionValue from finishing its job
930 	 * and left the input field in an insane state.  - kw
931 	 */
932 	OptionType *optptr = input_field->select_list;
933 	OptionType *tmp;
934 
935 	while (optptr) {
936 	    tmp = optptr;
937 	    optptr = tmp->next;
938 	    FREE(tmp->name);
939 	    FREE(tmp->cp_submit_value);
940 	    FREE(tmp);
941 	}
942 	input_field->select_list = NULL;
943 	/*
944 	 * Don't free the value field on option
945 	 * lists since it points to a option value
946 	 * same for orig value.
947 	 */
948 	input_field->value = NULL;
949 	input_field->orig_value = NULL;
950 	input_field->cp_submit_value = NULL;
951 	input_field->orig_submit_value = NULL;
952     } else {
953 	FREE(input_field->value);
954 	FREE(input_field->orig_value);
955 	FREE(input_field->cp_submit_value);
956 	FREE(input_field->orig_submit_value);
957     }
958     FREE(input_field->name);
959     FREE(input_field->submit_action);
960     FREE(input_field->submit_enctype);
961     FREE(input_field->submit_title);
962 
963     FREE(input_field->accept_cs);
964 }
965 
FormList_delete(HTList * forms)966 static void FormList_delete(HTList *forms)
967 {
968     HTList *cur = forms;
969     PerFormInfo *form;
970 
971     while ((form = (PerFormInfo *) HTList_nextObject(cur)) != NULL)
972 	PerFormInfo_free(form);
973     HTList_delete(forms);
974 }
975 
976 #ifdef DISP_PARTIAL
ResetPartialLinenos(HText * text)977 static void ResetPartialLinenos(HText *text)
978 {
979     if (text != 0) {
980 	text->first_lineno_last_disp_partial = -1;
981 	text->last_lineno_last_disp_partial = -1;
982     }
983 }
984 #endif
985 
986 /*			Creation Method
987  *			---------------
988  */
HText_new(HTParentAnchor * anchor)989 HText *HText_new(HTParentAnchor *anchor)
990 {
991 #if defined(VMS) && defined(VAXC) && !defined(__DECC)
992 #include <lib$routines.h>
993     int status, VMType = 3, VMTotal;
994 #endif /* VMS && VAXC && !__DECC */
995     HTLine *line = NULL;
996     HText *self = typecalloc(HText);
997 
998     if (!self)
999 	outofmem(__FILE__, "HText_New");
1000 
1001     CTRACE((tfp, "GridText: start HText_new\n"));
1002 
1003 #if defined(VMS) && defined (VAXC) && !defined(__DECC)
1004     status = lib$stat_vm(&VMType, &VMTotal);
1005     CTRACE((tfp, "GridText: VMTotal = %d\n", VMTotal));
1006 #endif /* VMS && VAXC && !__DECC */
1007 
1008     /*
1009      * If the previously shown text had UTF-8 characters on screen,
1010      * remember this in the newly created object.  Do this now, before
1011      * the previous object may become invalid.  - kw
1012      */
1013     if (HTMainText) {
1014 	self->had_utf8 = HTMainText->has_utf8;
1015 	HTMainText->has_utf8 = NO;
1016     }
1017 
1018     if (!loaded_texts) {
1019 	loaded_texts = HTList_new();
1020 #ifdef LY_FIND_LEAKS
1021 	atexit(free_all_texts);
1022 #endif
1023     }
1024 
1025     /*
1026      * Links between anchors & documents are a 1-1 relationship.  If
1027      * an anchor is already linked to a document we didn't call
1028      * HTuncache_current_document(), so we'll check now
1029      * and free it before reloading.  - Dick Wesseling (ftu@fi.ruu.nl)
1030      */
1031     if (anchor->document) {
1032 	HTList_removeObject(loaded_texts, anchor->document);
1033 	CTRACE((tfp, "GridText: Auto-uncaching\n"));
1034 
1035 	HTAnchor_delete_links(anchor);
1036 	((HText *) anchor->document)->node_anchor = NULL;
1037 	HText_free((HText *) anchor->document);
1038 	anchor->document = NULL;
1039     }
1040 
1041     HTList_addObject(loaded_texts, self);
1042 #if defined(VMS) && defined(VAXC) && !defined(__DECC)
1043     while (HTList_count(loaded_texts) > HTCacheSize &&
1044 	   VMTotal > HTVirtualMemorySize)
1045 #else
1046     if (HTList_count(loaded_texts) > HTCacheSize)
1047 #endif /* VMS && VAXC && !__DECC */
1048     {
1049 	CTRACE((tfp, "GridText: Freeing off cached doc.\n"));
1050 	HText_free((HText *) HTList_removeFirstObject(loaded_texts));
1051 #if defined(VMS) && defined (VAXC) && !defined(__DECC)
1052 	status = lib$stat_vm(&VMType, &VMTotal);
1053 	CTRACE((tfp, "GridText: VMTotal reduced to %d\n", VMTotal));
1054 #endif /* VMS && VAXC && !__DECC */
1055     }
1056 
1057     self->pool = POOL_NEW();
1058     if (!self->pool)
1059 	outofmem(__FILE__, "HText_New");
1060 
1061     line = self->last_line = TEMP_LINE(self, 0);
1062     line->next = line->prev = line;
1063     line->offset = line->size = 0;
1064     line->data[line->size] = '\0';
1065 #ifdef USE_COLOR_STYLE
1066     line->numstyles = 0;
1067     line->styles = stylechanges_buffers[0];
1068 #endif
1069     self->Lines = 0;
1070     self->first_anchor = self->last_anchor = NULL;
1071     self->last_anchor_before_split = NULL;
1072     self->style = &default_style;
1073     self->top_of_screen = 0;
1074     self->node_anchor = anchor;
1075     self->last_anchor_number = 0;	/* Numbering of them for references */
1076     self->stale = YES;
1077     self->toolbar = NO;
1078     self->tabs = NULL;
1079     self->next_number = 1;
1080 #ifdef USE_SOURCE_CACHE
1081     /*
1082      * Remember the parse settings.
1083      */
1084     self->clickable_images = clickable_images;
1085     self->pseudo_inline_alts = pseudo_inline_alts;
1086     self->verbose_img = verbose_img;
1087     self->raw_mode = LYUseDefaultRawMode;
1088     self->historical_comments = historical_comments;
1089     self->minimal_comments = minimal_comments;
1090     self->soft_dquotes = soft_dquotes;
1091     self->old_dtd = (short) Old_DTD;
1092     self->keypad_mode = (short) keypad_mode;
1093     self->disp_lines = (short) LYlines;
1094     self->disp_cols = (short) DISPLAY_COLS;
1095 #endif
1096     /*
1097      * If we are going to render the List Page, always merge in hidden
1098      * links to get the numbering consistent if form fields are numbered
1099      * and show up as hidden links in the list of links.
1100      * If we are going to render a bookmark file, also always merge in
1101      * hidden links, to get the link numbers consistent with the counting
1102      * in remove_bookmark_link().  Normally a bookmark file shouldn't
1103      * contain any entries with empty titles, but it might happen.  - kw
1104      */
1105     if (anchor->bookmark ||
1106 	LYIsUIPage3(anchor->address, UIP_LIST_PAGE, 0) ||
1107 	LYIsUIPage3(anchor->address, UIP_ADDRLIST_PAGE, 0))
1108 	self->hiddenlinkflag = HIDDENLINKS_MERGE;
1109     else
1110 	self->hiddenlinkflag = LYHiddenLinks;
1111     self->hidden_links = NULL;
1112     self->no_cache = (BOOLEAN) ((anchor->no_cache ||
1113 				 anchor->post_data)
1114 				? YES
1115 				: NO);
1116     self->LastChar = '\0';
1117 
1118 #ifndef USE_PRETTYSRC
1119     if (HTOutputFormat == WWW_SOURCE)
1120 	self->source = YES;
1121     else
1122 	self->source = NO;
1123 #else
1124     /* mark_htext_as_source == TRUE if we are parsing html file (and psrc_view
1125      * is set temporary to false at creation time)
1126      *
1127      * psrc_view == TRUE if source of the text produced by some lynx module
1128      * (like ftp browsers) is requested).  - VH
1129      */
1130     self->source = (BOOL) (LYpsrc
1131 			   ? mark_htext_as_source || psrc_view
1132 			   : HTOutputFormat == WWW_SOURCE);
1133     mark_htext_as_source = FALSE;
1134 #endif
1135     HTAnchor_setDocument(anchor, (HyperDoc *) self);
1136     HTFormNumber = 0;		/* no forms started yet */
1137     HTMainText = self;
1138     HTMainAnchor = anchor;
1139     self->display_on_the_fly = 0;
1140     self->kcode = NOKANJI;
1141     self->specified_kcode = NOKANJI;
1142 #ifdef USE_TH_JP_AUTO_DETECT
1143     self->detected_kcode = DET_NOTYET;
1144     self->SJIS_status = SJIS_state_neutral;
1145     self->EUC_status = EUC_state_neutral;
1146 #endif
1147     self->state = S_text;
1148     self->kanji_buf = '\0';
1149     self->in_sjis = 0;
1150     self->have_8bit_chars = NO;
1151     HText_getChartransInfo(self);
1152     UCSetTransParams(&self->T,
1153 		     self->UCLYhndl, self->UCI,
1154 		     current_char_set,
1155 		     &LYCharSet_UC[current_char_set]);
1156 
1157     /*
1158      * Check the kcode setting if the anchor has a charset element.  -FM
1159      */
1160     HText_setKcode(self, anchor->charset,
1161 		   HTAnchor_getUCInfoStage(anchor, UCT_STAGE_HTEXT));
1162 
1163     /*
1164      * Check to see if our underline and star_string need initialization
1165      * if the underline is not filled with dots.
1166      */
1167     if (underscore_string[0] != '.') {
1168 	/*
1169 	 * Create an array of dots for the UNDERSCORES macro.  -FM
1170 	 */
1171 	memset(underscore_string, '.', (size_t) (MAX_LINE - 1));
1172 	underscore_string[(MAX_LINE - 1)] = '\0';
1173 	underscore_string[MAX_LINE] = '\0';
1174 	/*
1175 	 * Create an array of underscores for the STARS macro.  -FM
1176 	 */
1177 	memset(star_string, '_', (size_t) (MAX_LINE - 1));
1178 	star_string[(MAX_LINE - 1)] = '\0';
1179 	star_string[MAX_LINE] = '\0';
1180     }
1181 
1182     underline_on = FALSE;	/* reset */
1183     bold_on = FALSE;
1184 
1185 #ifdef DISP_PARTIAL
1186     /*
1187      * By this function we create HText object
1188      * so we may start displaying the document while downloading. - LP
1189      */
1190     if (display_partial_flag) {
1191 	display_partial = TRUE;	/* enable HTDisplayPartial() */
1192 	NumOfLines_partial = 0;	/* initialize */
1193     }
1194 
1195     /*
1196      * These two fields should only be set to valid line numbers
1197      * by calls of display_page during partial displaying.  This
1198      * is just so that the FIRST display_page AFTER that can avoid
1199      * repainting the same lines on the screen.  - kw
1200      */
1201     ResetPartialLinenos(self);
1202 #endif
1203 
1204 #ifdef USE_JUSTIFY_ELTS
1205     ht_justify_cleanup();
1206 #endif
1207     return self;
1208 }
1209 
1210 /*			Creation Method 2
1211  *			---------------
1212  *
1213  *      Stream is assumed open and left open.
1214  */
HText_new2(HTParentAnchor * anchor,HTStream * stream)1215 HText *HText_new2(HTParentAnchor *anchor,
1216 		  HTStream *stream)
1217 {
1218     HText *result = HText_new(anchor);
1219 
1220     if (stream) {
1221 	result->target = stream;
1222 	result->targetClass = *stream->isa;	/* copy action procedures */
1223     }
1224     return result;
1225 }
1226 
1227 /*	Free Entire Text
1228  *	----------------
1229  */
HText_free(HText * self)1230 void HText_free(HText *self)
1231 {
1232     if (!self)
1233 	return;
1234 
1235 #if HTLINE_NOT_IN_POOL
1236     {
1237 	HTLine *f = FirstHTLine(self);
1238 	HTLine *l = self->last_line;
1239 
1240 	while (l != f) {	/* Free off line array */
1241 	    self->last_line = l->prev;
1242 	    freeHTLine(self, l);
1243 	    l = self->last_line;
1244 	}
1245 	freeHTLine(self, f);
1246     }
1247 #endif
1248 
1249     while (self->first_anchor) {	/* Free off anchor array */
1250 	TextAnchor *l = self->first_anchor;
1251 
1252 	self->first_anchor = l->next;
1253 
1254 	if (l->link_type == INPUT_ANCHOR && l->input_field) {
1255 	    free_form_fields(l->input_field);
1256 	}
1257 
1258 	LYFreeHiText(l);
1259     }
1260     FormList_delete(self->forms);
1261 
1262     /*
1263      * Free the tabs list.  -FM
1264      */
1265     if (self->tabs) {
1266 	HTTabID *Tab = NULL;
1267 	HTList *cur = self->tabs;
1268 
1269 	while (NULL != (Tab = (HTTabID *) HTList_nextObject(cur))) {
1270 	    FREE(Tab->name);
1271 	    FREE(Tab);
1272 	}
1273 	HTList_delete(self->tabs);
1274 	self->tabs = NULL;
1275     }
1276 
1277     /*
1278      * Free the hidden links list.  -FM
1279      */
1280     if (self->hidden_links) {
1281 	LYFreeStringList(self->hidden_links);
1282 	self->hidden_links = NULL;
1283     }
1284 
1285     /*
1286      * Invoke HTAnchor_delete() to free the node_anchor
1287      * if it is not a destination of other links.  -FM
1288      */
1289     if (self->node_anchor) {
1290 	HTAnchor_resetUCInfoStage(self->node_anchor, -1, UCT_STAGE_STRUCTURED,
1291 				  UCT_SETBY_NONE);
1292 	HTAnchor_resetUCInfoStage(self->node_anchor, -1, UCT_STAGE_HTEXT,
1293 				  UCT_SETBY_NONE);
1294 #ifdef USE_SOURCE_CACHE
1295 	/* Remove source cache files and chunks always, even if the
1296 	 * HTAnchor_delete call does not actually remove the anchor.
1297 	 * Keeping them would just be a waste of space - they won't
1298 	 * be used any more after the anchor has been disassociated
1299 	 * from a HText structure. - kw
1300 	 */
1301 	HTAnchor_clearSourceCache(self->node_anchor);
1302 #endif
1303 
1304 	HTAnchor_delete_links(self->node_anchor);
1305 
1306 	HTAnchor_setDocument(self->node_anchor, (HyperDoc *) 0);
1307 
1308 	if (HTAnchor_delete(self->node_anchor->parent))
1309 	    /*
1310 	     * Make sure HTMainAnchor won't point
1311 	     * to an invalid structure.  - KW
1312 	     */
1313 	    HTMainAnchor = NULL;
1314     }
1315 
1316     POOL_FREE(self->pool);
1317     FREE(self);
1318 }
1319 
1320 /*		Display Methods
1321  *		---------------
1322  */
1323 
1324 /*	Output a line
1325  *	-------------
1326  */
display_line(HTLine * line,HText * text,int scrline GCC_UNUSED,const char * target GCC_UNUSED)1327 static int display_line(HTLine *line,
1328 			HText *text,
1329 			int scrline GCC_UNUSED,
1330 			const char *target GCC_UNUSED)
1331 {
1332     register int i, j;
1333     char buffer[7];
1334     char *data;
1335     size_t utf_extra = 0;
1336     char LastDisplayChar = ' ';
1337 
1338 #ifdef USE_COLOR_STYLE
1339     int current_style = 0;
1340 
1341 #define inunderline NO
1342 #define inbold NO
1343 #else
1344     BOOL inbold = NO, inunderline = NO;
1345 #endif
1346 #if defined(SHOW_WHEREIS_TARGETS) && !defined(USE_COLOR_STYLE)
1347     const char *cp_tgt;
1348     int i_start_tgt = 0, i_after_tgt;
1349     int HitOffset, LenNeeded;
1350     BOOL intarget = NO;
1351 
1352 #else
1353 #define intarget NO
1354 #endif /* SHOW_WHEREIS_TARGETS && !USE_COLOR_STYLE */
1355 
1356 #if !(defined(NCURSES_VERSION) || defined(WIDEC_CURSES))
1357     text->has_utf8 = NO;	/* use as per-line flag, except with ncurses */
1358 #endif
1359 
1360 #if defined(WIDEC_CURSES)
1361     /*
1362      * FIXME: this should not be necessary, but in some wide-character pages
1363      * the output line wraps, foiling our attempt to just use newlines to
1364      * advance to the next page.
1365      */
1366     LYmove(scrline + TITLE_LINES - 1, 0);
1367 #endif
1368 
1369     /*
1370      * Set up the multibyte character buffer,
1371      * and clear the line to which we will be
1372      * writing.
1373      */
1374     buffer[0] = buffer[1] = buffer[2] = '\0';
1375     LYclrtoeol();
1376 
1377     /*
1378      * Add offset, making sure that we do not
1379      * go over the COLS limit on the display.
1380      */
1381     j = (int) line->offset;
1382     if (j >= DISPLAY_COLS)
1383 	j = DISPLAY_COLS - 1;
1384 #ifdef USE_SLANG
1385     SLsmg_forward(j);
1386     i = j;
1387 #else
1388 #ifdef USE_COLOR_STYLE
1389     if (line->size == 0)
1390 	i = j;
1391     else
1392 #endif
1393 	for (i = 0; i < j; i++)
1394 	    LYaddch(' ');
1395 #endif /* USE_SLANG */
1396 
1397     /*
1398      * Add the data, making sure that we do not
1399      * go over the COLS limit on the display.
1400      */
1401     data = line->data;
1402     i++;
1403 
1404 #ifndef USE_COLOR_STYLE
1405 #if defined(SHOW_WHEREIS_TARGETS)
1406     /*
1407      * If the target is on this line, it will be emphasized.
1408      */
1409     i_after_tgt = i;
1410     if (target) {
1411 	cp_tgt = LYno_attr_mb_strstr(data,
1412 				     target,
1413 				     text->T.output_utf8, YES,
1414 				     &HitOffset,
1415 				     &LenNeeded);
1416 	if (cp_tgt) {
1417 	    if (((int) line->offset + LenNeeded) >= DISPLAY_COLS) {
1418 		cp_tgt = NULL;
1419 	    } else {
1420 		text->page_has_target = YES;
1421 		i_start_tgt = i + HitOffset;
1422 		i_after_tgt = i + LenNeeded;
1423 	    }
1424 	}
1425     } else {
1426 	cp_tgt = NULL;
1427     }
1428 #endif /* SHOW_WHEREIS_TARGETS */
1429 #endif /* USE_COLOR_STYLE */
1430 
1431     while ((i <= DISPLAY_COLS) && ((buffer[0] = *data) != '\0')) {
1432 
1433 #ifndef USE_COLOR_STYLE
1434 #if defined(SHOW_WHEREIS_TARGETS)
1435 	if (cp_tgt && i >= i_after_tgt) {
1436 	    if (intarget) {
1437 		cp_tgt = LYno_attr_mb_strstr(data,
1438 					     target,
1439 					     text->T.output_utf8, YES,
1440 					     &HitOffset,
1441 					     &LenNeeded);
1442 		if (cp_tgt) {
1443 		    i_start_tgt = i + HitOffset;
1444 		    i_after_tgt = i + LenNeeded;
1445 		}
1446 		if (!cp_tgt || i_start_tgt != i) {
1447 		    LYstopTargetEmphasis();
1448 		    intarget = NO;
1449 		    if (inbold)
1450 			lynx_start_bold();
1451 		    if (inunderline)
1452 			lynx_start_underline();
1453 		}
1454 	    }
1455 	}
1456 #endif /* SHOW_WHEREIS_TARGETS */
1457 #endif /* USE_COLOR_STYLE */
1458 
1459 	data++;
1460 
1461 #if defined(USE_COLOR_STYLE)
1462 #define CStyle line->styles[current_style]
1463 
1464 	while (current_style < line->numstyles &&
1465 	       i >= (int) (CStyle.sc_horizpos + line->offset + 1)) {
1466 	    LynxChangeStyle(CStyle.sc_style, CStyle.sc_direction);
1467 	    current_style++;
1468 	}
1469 #endif
1470 	switch (buffer[0]) {
1471 
1472 #ifndef USE_COLOR_STYLE
1473 	case LY_UNDERLINE_START_CHAR:
1474 	    if (dump_output_immediately && use_underscore) {
1475 		LYaddch('_');
1476 		i++;
1477 	    } else {
1478 		inunderline = YES;
1479 		if (!intarget) {
1480 #if defined(PDCURSES)
1481 		    if (LYShowColor == SHOW_COLOR_NEVER)
1482 			lynx_start_bold();
1483 		    else
1484 			lynx_start_underline();
1485 #else
1486 		    lynx_start_underline();
1487 #endif /* PDCURSES */
1488 		}
1489 	    }
1490 	    break;
1491 
1492 	case LY_UNDERLINE_END_CHAR:
1493 	    if (dump_output_immediately && use_underscore) {
1494 		LYaddch('_');
1495 		i++;
1496 	    } else {
1497 		inunderline = NO;
1498 		if (!intarget) {
1499 #if defined(PDCURSES)
1500 		    if (LYShowColor == SHOW_COLOR_NEVER)
1501 			lynx_stop_bold();
1502 		    else
1503 			lynx_stop_underline();
1504 #else
1505 		    lynx_stop_underline();
1506 #endif /* PDCURSES */
1507 		}
1508 	    }
1509 	    break;
1510 
1511 	case LY_BOLD_START_CHAR:
1512 	    inbold = YES;
1513 	    if (!intarget)
1514 		lynx_start_bold();
1515 	    break;
1516 
1517 	case LY_BOLD_END_CHAR:
1518 	    inbold = NO;
1519 	    if (!intarget)
1520 		lynx_stop_bold();
1521 	    break;
1522 
1523 #endif /* !USE_COLOR_STYLE */
1524 	case LY_SOFT_NEWLINE:
1525 	    if (!dump_output_immediately) {
1526 		LYaddch('+');
1527 		i++;
1528 #if defined(SHOW_WHEREIS_TARGETS) && !defined(USE_COLOR_STYLE)
1529 		i_after_tgt++;
1530 #endif
1531 	    }
1532 	    break;
1533 
1534 	case LY_SOFT_HYPHEN:
1535 	    if (*data != '\0' ||
1536 		isspace(UCH(LastDisplayChar)) ||
1537 		LastDisplayChar == '-') {
1538 		/*
1539 		 * Ignore the soft hyphen if it is not the last character in
1540 		 * the line.  Also ignore it if is first character following
1541 		 * the margin, or if it is preceded by a white character (we
1542 		 * loaded 'M' into LastDisplayChar if it was a multibyte
1543 		 * character) or hyphen, though it should have been excluded by
1544 		 * HText_appendCharacter() or by split_line() in those cases.
1545 		 * -FM
1546 		 */
1547 		break;
1548 	    } else {
1549 		/*
1550 		 * Make it a hard hyphen and fall through.  -FM
1551 		 */
1552 		buffer[0] = '-';
1553 	    }
1554 	    /* FALLTHRU */
1555 
1556 	default:
1557 #ifndef USE_COLOR_STYLE
1558 #if defined(SHOW_WHEREIS_TARGETS)
1559 	    if (!intarget && cp_tgt && i >= i_start_tgt) {
1560 		/*
1561 		 * Start the emphasis.
1562 		 */
1563 		if (data > cp_tgt) {
1564 		    LYstartTargetEmphasis();
1565 		    intarget = YES;
1566 		}
1567 	    }
1568 #endif /* SHOW_WHEREIS_TARGETS */
1569 #endif /* USE_COLOR_STYLE */
1570 	    if (text->T.output_utf8 && is8bits(buffer[0])) {
1571 		text->has_utf8 = YES;
1572 		utf_extra = utf8_length(text->T.output_utf8, data - 1);
1573 		LastDisplayChar = 'M';
1574 	    }
1575 	    if (utf_extra) {
1576 		LYStrNCpy(&buffer[1], data, utf_extra);
1577 		LYaddstr(buffer);
1578 		buffer[1] = '\0';
1579 		data += utf_extra;
1580 		utf_extra = 0;
1581 	    } else if (is_CJK2(buffer[0])) {
1582 		/*
1583 		 * For CJK strings, by Masanobu Kimura.
1584 		 */
1585 		if (i <= DISPLAY_COLS) {
1586 		    buffer[1] = *data;
1587 		    buffer[2] = '\0';
1588 		    data++;
1589 		    i++;
1590 		    LYaddstr(buffer);
1591 		    buffer[1] = '\0';
1592 		    /*
1593 		     * For now, load 'M' into LastDisplayChar, but we should
1594 		     * check whether it's white and if so, use ' '.  I don't
1595 		     * know if there actually are white CJK characters, and
1596 		     * we're loading ' ' for multibyte spacing characters in
1597 		     * this code set, but this will become an issue when the
1598 		     * development code set's multibyte character handling is
1599 		     * used.  -FM
1600 		     */
1601 		    LastDisplayChar = 'M';
1602 #ifndef USE_SLANG
1603 		    {
1604 			int y, x;
1605 
1606 			getyx(LYwin, y, x);
1607 			(void) y;
1608 			if (x >= DISPLAY_COLS || x == 0)
1609 			    break;
1610 		    }
1611 #endif
1612 		}
1613 	    } else {
1614 		LYaddstr(buffer);
1615 		LastDisplayChar = buffer[0];
1616 	    }
1617 	    i++;
1618 	}			/* end of switch */
1619     }				/* end of while */
1620 
1621 #if !(defined(NCURSES_VERSION) || defined(WIDEC_CURSES))
1622     if (text->has_utf8) {
1623 	LYtouchline(scrline);
1624 	text->has_utf8 = NO;	/* we had some, but have dealt with it. */
1625     }
1626 #endif
1627     /*
1628      * Add the return.
1629      */
1630     LYaddch('\n');
1631 
1632 #if defined(SHOW_WHEREIS_TARGETS) && !defined(USE_COLOR_STYLE)
1633     if (intarget)
1634 	LYstopTargetEmphasis();
1635 #else
1636 #undef intarget
1637 #endif /* SHOW_WHEREIS_TARGETS && !USE_COLOR_STYLE */
1638 #ifndef USE_COLOR_STYLE
1639     lynx_stop_underline();
1640     lynx_stop_bold();
1641 #else
1642     while (current_style < line->numstyles) {
1643 	LynxChangeStyle(CStyle.sc_style, CStyle.sc_direction);
1644 	current_style++;
1645     }
1646 #undef CStyle
1647 #endif
1648     return (0);
1649 }
1650 
1651 /*	Output the title line
1652  *	---------------------
1653  */
display_title(HText * text)1654 static void display_title(HText *text)
1655 {
1656     char *title = NULL;
1657     char percent[40];
1658     unsigned char *tmp = NULL;
1659     int i = 0, j = 0;
1660     int limit;
1661 
1662 #ifdef USE_COLOR_STYLE
1663     int toolbar = 0;
1664 #endif
1665 
1666     /*
1667      * Make sure we have a text structure.  -FM
1668      */
1669     if (!text)
1670 	return;
1671 
1672     lynx_start_title_color();
1673 #ifdef USE_COLOR_STYLE
1674 /* turn the TITLE style on */
1675     if (last_colorattr_ptr > 0) {
1676 	LynxChangeStyle(s_title, STACK_ON);
1677     } else {
1678 	LynxChangeStyle(s_title, ABS_ON);
1679     }
1680 #endif /* USE_COLOR_STYLE */
1681 
1682     /*
1683      * Load the title field.  -FM
1684      */
1685     StrAllocCopy(title,
1686 		 (HTAnchor_title(text->node_anchor) ?
1687 		  HTAnchor_title(text->node_anchor) : " "));	/* "" -> " " */
1688     LYReduceBlanks(title);
1689 
1690     /*
1691      * Generate the page indicator (percent) string.
1692      */
1693     limit = LYscreenWidth();
1694     if (limit < 10) {
1695 	percent[0] = '\0';
1696     } else if ((display_lines) <= 0 && LYlines > 0 &&
1697 	       text->top_of_screen <= 99999 && text->Lines <= 999999) {
1698 	sprintf(percent, " (l%d of %d)",
1699 		text->top_of_screen, text->Lines);
1700     } else if ((text->Lines >= display_lines) && (display_lines > 0)) {
1701 	int total_pages = ((text->Lines + display_lines)
1702 			   / display_lines);
1703 	int start_of_last_page = ((text->Lines <= display_lines)
1704 				  ? 0
1705 				  : (text->Lines - display_lines));
1706 
1707 	sprintf(percent, " (p%d of %d)",
1708 		((text->top_of_screen > start_of_last_page)
1709 		 ? total_pages
1710 		 : ((text->top_of_screen + display_lines) / (display_lines))),
1711 		total_pages);
1712     } else {
1713 	percent[0] = '\0';
1714     }
1715 
1716     /*
1717      * Generate and display the title string, with page indicator
1718      * if appropriate, preceded by the toolbar token if appropriate,
1719      * and truncated if necessary.  -FM & KW
1720      */
1721     if (IS_CJK_TTY) {
1722 	if (*title &&
1723 	    (tmp = typecallocn(unsigned char, (strlen(title) * 2 + 256)))) {
1724 	    if (kanji_code == EUC) {
1725 		TO_EUC((unsigned char *) title, tmp);
1726 	    } else if (kanji_code == SJIS) {
1727 		TO_SJIS((unsigned char *) title, tmp);
1728 	    } else {
1729 		for (i = 0, j = 0; title[i]; i++) {
1730 		    if (title[i] != CH_ESC) {	/* S/390 -- gil -- 1487 */
1731 			tmp[j++] = UCH(title[i]);
1732 		    }
1733 		}
1734 		tmp[j] = '\0';
1735 	    }
1736 	    StrAllocCopy(title, (const char *) tmp);
1737 	    FREE(tmp);
1738 	}
1739     }
1740     LYmove(0, 0);
1741     LYclrtoeol();
1742 #if defined(SH_EX) && defined(KANJI_CODE_OVERRIDE)
1743     LYaddstr(str_kcode(last_kcode));
1744 #endif
1745     if (HText_hasToolbar(text)) {
1746 	LYaddch('#');
1747 #ifdef USE_COLOR_STYLE
1748 	toolbar = 1;
1749 #endif
1750     }
1751 #ifdef USE_COLOR_STYLE
1752     if (s_forw_backw != NOSTYLE && (nhist || nhist_extra > 1)) {
1753 	chtype c = nhist ? ACS_LARROW : ' ';
1754 
1755 	/* turn the FORWBACKW.ARROW style on */
1756 	LynxChangeStyle(s_forw_backw, STACK_ON);
1757 	if (nhist) {
1758 	    LYaddch(c);
1759 	    LYaddch(c);
1760 	    LYaddch(c);
1761 	} else
1762 	    LYmove(0, 3 + toolbar);
1763 	if (nhist_extra > 1) {
1764 	    LYaddch(ACS_RARROW);
1765 	    LYaddch(ACS_RARROW);
1766 	    LYaddch(ACS_RARROW);
1767 	}
1768 	LynxChangeStyle(s_forw_backw, STACK_OFF);
1769     }
1770 #endif /* USE_COLOR_STYLE */
1771 #ifdef WIDEC_CURSES
1772     i = limit - LYbarWidth - (int) strlen(percent) - LYstrCells(title);
1773     if (i <= 0) {		/* title is truncated */
1774 	i = limit - LYbarWidth - (int) strlen(percent) - 3;
1775 	if (i <= 0) {		/* no room at all */
1776 	    title[0] = '\0';
1777 	} else {
1778 	    strcpy(title + LYstrFittable(title, i), "...");
1779 	}
1780 	i = 0;
1781     }
1782     LYmove(0, i);
1783 #else
1784     i = (limit - 1) - (int) (strlen(percent) + strlen(title));
1785     if (i >= CHAR_WIDTH) {
1786 	LYmove(0, i);
1787     } else {
1788 	/*
1789 	 * Truncation takes into account the possibility that
1790 	 * multibyte characters might be present.  -HS (H.  Senshu)
1791 	 */
1792 	int last;
1793 
1794 	last = (int) strlen(percent) + CHAR_WIDTH;
1795 	if (limit - 3 >= last) {
1796 	    title[(limit - 3) - last] = '.';
1797 	    title[(limit - 2) - last] = '.';
1798 	    title[(limit - 1) - last] = '\0';
1799 	} else {
1800 	    title[(limit - 1) - last] = '\0';
1801 	}
1802 	LYmove(0, CHAR_WIDTH);
1803     }
1804 #endif
1805     LYaddstr(title);
1806     if (percent[0] != '\0')
1807 	LYaddstr(percent);
1808     LYaddch('\n');
1809     FREE(title);
1810 
1811 #if defined(USE_COLOR_STYLE) && defined(CAN_CUT_AND_PASTE)
1812     if (s_hot_paste != NOSTYLE) {	/* Only if the user set the style */
1813 	LYmove(0, LYcolLimit);
1814 	LynxChangeStyle(s_hot_paste, STACK_ON);
1815 	LYaddch(ACS_RARROW);
1816 	LynxChangeStyle(s_hot_paste, STACK_OFF);
1817 	LYmove(1, 0);		/* As after \n */
1818     }
1819 #endif /* USE_COLOR_STYLE */
1820 
1821 #ifdef USE_COLOR_STYLE
1822 /* turn the TITLE style off */
1823     LynxChangeStyle(s_title, STACK_OFF);
1824 #endif /* USE_COLOR_STYLE */
1825     lynx_stop_title_color();
1826 
1827     return;
1828 }
1829 
1830 /*	Output the scrollbar
1831  *	---------------------
1832  */
1833 #ifdef USE_SCROLLBAR
display_scrollbar(HText * text)1834 static void display_scrollbar(HText *text)
1835 {
1836     int i;
1837     int h = display_lines - 2 * (LYsb_arrow != 0);	/* Height of the scrollbar */
1838     int off = (LYsb_arrow != 0);	/* Start of the scrollbar */
1839     int top_skip, bot_skip, sh, shown;
1840 
1841     LYsb_begin = LYsb_end = -1;
1842     if (!LYShowScrollbar || !text || h <= 2
1843 	|| text->Lines <= display_lines)
1844 	return;
1845 
1846     if (text->top_of_screen >= text->Lines - display_lines) {
1847 	/* Only part of the screen shows actual text */
1848 	shown = text->Lines - text->top_of_screen;
1849 
1850 	if (shown <= 0)
1851 	    shown = 1;
1852     } else
1853 	shown = display_lines;
1854     /* Each cell of scrollbar represents text->Lines/h lines of text. */
1855     /* Always smaller than h */
1856     sh = (shown * h + text->Lines / 2) / text->Lines;
1857     if (sh <= 0)
1858 	sh = 1;
1859     if (sh >= h - 1)
1860 	sh = h - 2;		/* Position at ends indicates BEG and END */
1861 
1862     if (text->top_of_screen == 0)
1863 	top_skip = 0;
1864     else if (text->Lines - (text->top_of_screen + display_lines - 1) <= 0)
1865 	top_skip = h - sh;
1866     else {
1867 	/* text->top_of_screen between 1 and text->Lines - display_lines
1868 	   corresponds to top_skip between 1 and h - sh - 1 */
1869 	/* Use rounding to get as many positions into top_skip==h - sh - 1
1870 	   as into top_skip == 1:
1871 	   1--->1, text->Lines - display_lines + 1--->h - sh. */
1872 	top_skip = (int) (1 +
1873 			  1. * (h - sh - 1) * text->top_of_screen
1874 			  / (text->Lines - display_lines + 1));
1875     }
1876     bot_skip = h - sh - top_skip;
1877 
1878     LYsb_begin = top_skip;
1879     LYsb_end = h - bot_skip;
1880 
1881     if (LYsb_arrow) {
1882 #ifdef USE_COLOR_STYLE
1883 	int s = top_skip ? s_sb_aa : s_sb_naa;
1884 
1885 	if (last_colorattr_ptr > 0) {
1886 	    LynxChangeStyle(s, STACK_ON);
1887 	} else {
1888 	    LynxChangeStyle(s, ABS_ON);
1889 	}
1890 #endif /* USE_COLOR_STYLE */
1891 	LYmove(1, LYcolLimit + LYshiftWin);
1892 	addch_raw(ACS_UARROW);
1893 #ifdef USE_COLOR_STYLE
1894 	LynxChangeStyle(s, STACK_OFF);
1895 #endif /* USE_COLOR_STYLE */
1896     }
1897 #ifdef USE_COLOR_STYLE
1898     if (last_colorattr_ptr > 0) {
1899 	LynxChangeStyle(s_sb_bg, STACK_ON);
1900     } else {
1901 	LynxChangeStyle(s_sb_bg, ABS_ON);
1902     }
1903 #endif /* USE_COLOR_STYLE */
1904 
1905     for (i = 1; i <= h; i++) {
1906 #ifdef USE_COLOR_STYLE
1907 	if (i - 1 <= top_skip && i > top_skip)
1908 	    LynxChangeStyle(s_sb_bar, STACK_ON);
1909 	if (i - 1 <= h - bot_skip && i > h - bot_skip)
1910 	    LynxChangeStyle(s_sb_bar, STACK_OFF);
1911 #endif /* USE_COLOR_STYLE */
1912 	LYmove(i + off, LYcolLimit + LYshiftWin);
1913 	if (i > top_skip && i <= h - bot_skip) {
1914 	    LYaddch(ACS_BLOCK);
1915 	} else {
1916 	    LYaddch(ACS_CKBOARD);
1917 	}
1918     }
1919 #ifdef USE_COLOR_STYLE
1920     LynxChangeStyle(s_sb_bg, STACK_OFF);
1921 #endif /* USE_COLOR_STYLE */
1922 
1923     if (LYsb_arrow) {
1924 #ifdef USE_COLOR_STYLE
1925 	int s = bot_skip ? s_sb_aa : s_sb_naa;
1926 
1927 	if (last_colorattr_ptr > 0) {
1928 	    LynxChangeStyle(s, STACK_ON);
1929 	} else {
1930 	    LynxChangeStyle(s, ABS_ON);
1931 	}
1932 #endif /* USE_COLOR_STYLE */
1933 	LYmove(h + 2, LYcolLimit + LYshiftWin);
1934 	addch_raw(ACS_DARROW);
1935 #ifdef USE_COLOR_STYLE
1936 	LynxChangeStyle(s, STACK_OFF);
1937 #endif /* USE_COLOR_STYLE */
1938     }
1939     return;
1940 }
1941 #else
1942 #define display_scrollbar(text)	/*nothing */
1943 #endif /* USE_SCROLLBAR */
1944 
1945 /*	Output a page
1946  *	-------------
1947  */
display_page(HText * text,int line_number,const char * target)1948 static void display_page(HText *text,
1949 			 int line_number,
1950 			 const char *target)
1951 {
1952     HTLine *line = NULL;
1953     int i;
1954     int title_lines = TITLE_LINES;
1955 
1956 #if defined(USE_COLOR_STYLE) && defined(SHOW_WHEREIS_TARGETS)
1957     const char *cp;
1958 #endif
1959     char tmp[7];
1960     TextAnchor *Anchor_ptr = NULL;
1961     int stop_before_for_anchors;
1962     FormInfo *FormInfo_ptr;
1963     BOOL display_flag = FALSE;
1964     HTAnchor *link_dest;
1965     HTAnchor *link_dest_intl = NULL;
1966     static int last_nlinks = 0;
1967     static int charset_last_displayed = -1;
1968 
1969 #ifdef DISP_PARTIAL
1970     int last_disp_partial = -1;
1971 #endif
1972 
1973     lynx_mode = NORMAL_LYNX_MODE;
1974 
1975     if (text == NULL) {
1976 	/*
1977 	 * Check whether to force a screen clear to enable scrollback,
1978 	 * or as a hack to fix a reverse clear screen problem for some
1979 	 * curses packages.  - shf@access.digex.net & seldon@eskimo.com
1980 	 */
1981 	if (enable_scrollback) {
1982 	    LYaddch('*');
1983 	    LYrefresh();
1984 	    LYclear();
1985 	}
1986 	LYaddstr("\n\nError accessing document!\nNo data available!\n");
1987 	LYrefresh();
1988 	nlinks = 0;		/* set number of links to 0 */
1989 	return;
1990     }
1991 #ifdef DISP_PARTIAL
1992     if (display_partial || recent_sizechange || text->stale) {
1993 	/*  Reset them, will be set near end if all is okay. - kw */
1994 	ResetPartialLinenos(text);
1995     }
1996 #endif /* DISP_PARTIAL */
1997 
1998     tmp[0] = tmp[1] = tmp[2] = '\0';
1999     if (target && *target == '\0')
2000 	target = NULL;
2001     text->page_has_target = NO;
2002     if (display_lines <= 0) {
2003 	/* No screen space to display anything!
2004 	 * returning here makes it more likely we will survive if
2005 	 * an xterm is temporarily made very small.  - kw */
2006 	return;
2007     }
2008 
2009     line_number = HText_getPreferredTopLine(text, line_number);
2010 
2011     for (i = 0, line = FirstHTLine(text);	/* Find line */
2012 	 i < line_number && (line != text->last_line);
2013 	 i++, line = line->next) {	/* Loop */
2014 #ifndef VMS
2015 	if (!LYNoCore) {
2016 	    assert(line->next != NULL);
2017 	} else if (line->next == NULL) {
2018 	    if (enable_scrollback) {
2019 		LYaddch('*');
2020 		LYrefresh();
2021 		LYclear();
2022 	    }
2023 	    LYaddstr("\n\nError drawing page!\nBad HText structure!\n");
2024 	    LYrefresh();
2025 	    nlinks = 0;		/* set number of links to 0 */
2026 	    return;
2027 	}
2028 #else
2029 	assert(line->next != NULL);
2030 #endif /* !VMS */
2031     }				/* Loop */
2032 
2033     if (LYlowest_eightbit[current_char_set] <= 255 &&
2034 	(current_char_set != charset_last_displayed) &&
2035     /*
2036      * current_char_set has changed since last invocation,
2037      * and it's not just 7-bit.
2038      * Also we don't want to do this for -dump and -source etc.
2039      */
2040 	LYCursesON) {
2041 #ifdef EXP_CHARTRANS_AUTOSWITCH
2042 	UCChangeTerminalCodepage(current_char_set,
2043 				 &LYCharSet_UC[current_char_set]);
2044 #endif /* EXP_CHARTRANS_AUTOSWITCH */
2045 	charset_last_displayed = current_char_set;
2046     }
2047 
2048     /*
2049      * Check whether to force a screen clear to enable scrollback,
2050      * or as a hack to fix a reverse clear screen problem for some
2051      * curses packages.  - shf@access.digex.net & seldon@eskimo.com
2052      */
2053     if (enable_scrollback) {
2054 	LYaddch('*');
2055 	LYrefresh();
2056 	LYclear();
2057     }
2058 #ifdef USE_COLOR_STYLE
2059     /*
2060      * Reset stack of color attribute changes to avoid color leaking,
2061      * except if what we last displayed from this text was the previous
2062      * screenful, in which case carrying over the state might be beneficial
2063      * (although it shouldn't generally be needed any more).  - kw
2064      */
2065     if (text->stale ||
2066 	line_number != text->top_of_screen + (display_lines)) {
2067 	last_colorattr_ptr = 0;
2068     }
2069 #endif
2070 
2071     text->top_of_screen = line_number;
2072     text->top_of_screen_line = line;
2073     if (no_title) {
2074 	LYmove(0, 0);
2075 	title_lines = 0;
2076     } else {
2077 	display_title(text);	/* will move cursor to top of screen */
2078     }
2079     display_flag = TRUE;
2080 
2081 #ifdef USE_COLOR_STYLE
2082 #ifdef DISP_PARTIAL
2083     if (display_partial ||
2084 	line_number != text->first_lineno_last_disp_partial ||
2085 	line_number > text->last_lineno_last_disp_partial)
2086 #endif /* DISP_PARTIAL */
2087 	ResetCachedStyles();
2088 #endif /* USE_COLOR_STYLE */
2089 
2090 #ifdef DISP_PARTIAL
2091     if (display_partial && text->stbl) {
2092 	stop_before_for_anchors = Stbl_getStartLineDeep(text->stbl);
2093 	if (stop_before_for_anchors > line_number + (display_lines))
2094 	    stop_before_for_anchors = line_number + (display_lines);
2095     } else
2096 #endif
2097 	stop_before_for_anchors = line_number + (display_lines);
2098 
2099     /*
2100      * Output the page.
2101      */
2102     if (line) {
2103 #if defined(USE_COLOR_STYLE) && defined(SHOW_WHEREIS_TARGETS)
2104 	char *data;
2105 	int offset, LenNeeded;
2106 #endif
2107 #ifdef DISP_PARTIAL
2108 	if (display_partial ||
2109 	    line_number != text->first_lineno_last_disp_partial)
2110 	    text->has_utf8 = NO;
2111 #else
2112 	text->has_utf8 = NO;
2113 #endif
2114 	for (i = 0; i < (display_lines); i++) {
2115 	    /*
2116 	     * Verify and display each line.
2117 	     */
2118 #ifndef VMS
2119 	    if (!LYNoCore) {
2120 		assert(line != NULL);
2121 	    } else if (line == NULL) {
2122 		if (enable_scrollback) {
2123 		    LYaddch('*');
2124 		    LYrefresh();
2125 		    LYclear();
2126 		}
2127 		LYaddstr("\n\nError drawing page!\nBad HText structure!\n");
2128 		LYrefresh();
2129 		nlinks = 0;	/* set number of links to 0 */
2130 		return;
2131 	    }
2132 #else
2133 	    assert(line != NULL);
2134 #endif /* !VMS */
2135 
2136 #ifdef DISP_PARTIAL
2137 	    if (!display_partial &&
2138 		line_number == text->first_lineno_last_disp_partial &&
2139 		i + line_number <= text->last_lineno_last_disp_partial)
2140 		LYmove((i + title_lines + 1), 0);
2141 	    else
2142 #endif
2143 		display_line(line, text, i + 1, target);
2144 
2145 #if defined(SHOW_WHEREIS_TARGETS)
2146 #ifdef USE_COLOR_STYLE		/* otherwise done in display_line - kw */
2147 	    /*
2148 	     * If the target is on this line, recursively
2149 	     * seek and emphasize it.  -FM
2150 	     */
2151 	    data = (char *) line->data;
2152 	    offset = (int) line->offset;
2153 	    while (non_empty(target) &&
2154 		   (cp = LYno_attr_mb_strstr(data,
2155 					     target,
2156 					     text->T.output_utf8, YES,
2157 					     NULL,
2158 					     &LenNeeded)) != NULL &&
2159 		   ((int) line->offset + LenNeeded) <= DISPLAY_COLS) {
2160 		size_t itmp = 0;
2161 		size_t written = 0;
2162 		int x_off = offset + (int) (cp - data);
2163 		size_t len = strlen(target);
2164 		size_t utf_extra = 0;
2165 
2166 		text->page_has_target = YES;
2167 
2168 		/*
2169 		 * Start the emphasis.
2170 		 */
2171 		LYstartTargetEmphasis();
2172 
2173 		/*
2174 		 * Output the target characters.
2175 		 */
2176 		for (;
2177 		     written < len && (tmp[0] = data[itmp]) != '\0';
2178 		     itmp++) {
2179 		    if (IsSpecialAttrChar(tmp[0]) && tmp[0] != LY_SOFT_NEWLINE) {
2180 			/*
2181 			 * Ignore special characters.
2182 			 */
2183 			x_off--;
2184 
2185 		    } else if (&data[itmp] >= cp) {
2186 			if (cp == &data[itmp]) {
2187 			    /*
2188 			     * First printable character of target.
2189 			     */
2190 			    LYmove((i + title_lines),
2191 				   line->offset + LYstrExtent2(line->data,
2192 							       x_off - line->offset));
2193 			}
2194 			/*
2195 			 * Output all the printable target chars.
2196 			 */
2197 			utf_extra = utf8_length(text->T.output_utf8, data + itmp);
2198 			if (utf_extra) {
2199 			    LYStrNCpy(&tmp[1], &line->data[itmp + 1], utf_extra);
2200 			    itmp += utf_extra;
2201 			    LYaddstr(tmp);
2202 			    tmp[1] = '\0';
2203 			    written += (utf_extra + 1);
2204 			} else if (IS_CJK_TTY && is8bits(tmp[0])) {
2205 			    /*
2206 			     * For CJK strings, by Masanobu Kimura.
2207 			     */
2208 			    tmp[1] = data[++itmp];
2209 			    LYaddstr(tmp);
2210 			    tmp[1] = '\0';
2211 			    written += 2;
2212 			} else {
2213 			    LYaddstr(tmp);
2214 			    written++;
2215 			}
2216 		    }
2217 		}
2218 
2219 		/*
2220 		 * Stop the emphasis, and reset the offset and
2221 		 * data pointer for our current position in the
2222 		 * line.  -FM
2223 		 */
2224 		LYstopTargetEmphasis();
2225 		data = (char *) &data[itmp];
2226 		offset = (int) (data - line->data + line->offset);
2227 
2228 	    }			/* end while */
2229 	    LYmove((i + title_lines + 1), 0);
2230 #endif /* USE_COLOR_STYLE */
2231 #endif /* SHOW_WHEREIS_TARGETS */
2232 
2233 	    /*
2234 	     * Stop if this is the last line.  Otherwise, make sure
2235 	     * display_flag is set and process the next line.  -FM
2236 	     */
2237 	    if (line == text->last_line) {
2238 		/*
2239 		 * Clear remaining lines of display.
2240 		 */
2241 		for (i++; i < (display_lines); i++) {
2242 		    LYmove((i + title_lines), 0);
2243 		    LYclrtoeol();
2244 		}
2245 		break;
2246 	    }
2247 #ifdef DISP_PARTIAL
2248 	    if (display_partial) {
2249 		/*
2250 		 * Remember as fully shown during last partial display,
2251 		 * if it was not the last text line.  - kw
2252 		 */
2253 		last_disp_partial = i + line_number;
2254 	    }
2255 #endif /* DISP_PARTIAL */
2256 	    display_flag = TRUE;
2257 	    line = line->next;
2258 	}			/* end of "Verify and display each line." loop */
2259     }
2260     /* end "Output the page." */
2261     text->next_line = line;	/* Line after screen */
2262     text->stale = NO;		/* Display is up-to-date */
2263 
2264     /*
2265      * Add the anchors to Lynx structures.
2266      */
2267     nlinks = 0;
2268     for (Anchor_ptr = text->first_anchor;
2269 	 Anchor_ptr != NULL && Anchor_ptr->line_num <= stop_before_for_anchors;
2270 	 Anchor_ptr = Anchor_ptr->next) {
2271 
2272 	if (Anchor_ptr->line_num >= line_number
2273 	    && Anchor_ptr->line_num < stop_before_for_anchors) {
2274 	    char *hi_string = LYGetHiTextStr(Anchor_ptr, 0);
2275 
2276 	    /*
2277 	     * Load normal hypertext anchors.
2278 	     */
2279 	    if (Anchor_ptr->show_anchor
2280 		&& non_empty(hi_string)
2281 		&& (Anchor_ptr->link_type & HYPERTEXT_ANCHOR)) {
2282 		int count;
2283 		char *s;
2284 
2285 		for (count = 0;; ++count) {
2286 		    s = LYGetHiTextStr(Anchor_ptr, count);
2287 		    if (count == 0)
2288 			LYSetHilite(nlinks, s);
2289 		    if (s == NULL)
2290 			break;
2291 		    if (count != 0) {
2292 			LYAddHilite(nlinks, s, LYGetHiTextPos(Anchor_ptr, count));
2293 		    }
2294 		}
2295 
2296 		links[nlinks].inUnderline = Anchor_ptr->inUnderline;
2297 
2298 		links[nlinks].sgml_offset = Anchor_ptr->sgml_offset;
2299 		links[nlinks].anchor_number = Anchor_ptr->number;
2300 		links[nlinks].anchor_line_num = Anchor_ptr->line_num;
2301 
2302 		link_dest = HTAnchor_followLink(Anchor_ptr->anchor);
2303 		{
2304 		    auto char *cp_AnchorAddress = NULL;
2305 
2306 		    if (traversal) {
2307 			cp_AnchorAddress = stub_HTAnchor_address(link_dest);
2308 		    } else if (track_internal_links) {
2309 			if (Anchor_ptr->link_type == INTERNAL_LINK_ANCHOR) {
2310 			    link_dest_intl = HTAnchor_followTypedLink(Anchor_ptr->anchor,
2311 								      HTInternalLink);
2312 			    if (link_dest_intl && link_dest_intl != link_dest) {
2313 
2314 				CTRACE((tfp,
2315 					"GridText: display_page: unexpected typed link to %s!\n",
2316 					link_dest_intl->parent->address));
2317 				link_dest_intl = NULL;
2318 			    }
2319 			} else {
2320 			    link_dest_intl = NULL;
2321 			}
2322 			if (link_dest_intl) {
2323 			    char *cp2 = HTAnchor_address(link_dest_intl);
2324 
2325 			    cp_AnchorAddress = cp2;
2326 			} else {
2327 			    cp_AnchorAddress = HTAnchor_address(link_dest);
2328 			}
2329 		    } else {
2330 			cp_AnchorAddress = HTAnchor_address(link_dest);
2331 		    }
2332 		    FREE(links[nlinks].lname);
2333 
2334 		    if (cp_AnchorAddress != NULL)
2335 			links[nlinks].lname = cp_AnchorAddress;
2336 		    else
2337 			StrAllocCopy(links[nlinks].lname, empty_string);
2338 		}
2339 
2340 		links[nlinks].lx = Anchor_ptr->line_pos;
2341 		links[nlinks].ly = ((Anchor_ptr->line_num + 1) - line_number);
2342 		if (link_dest_intl)
2343 		    links[nlinks].type = WWW_INTERN_LINK_TYPE;
2344 		else
2345 		    links[nlinks].type = WWW_LINK_TYPE;
2346 		links[nlinks].target = empty_string;
2347 		links[nlinks].l_form = NULL;
2348 
2349 		nlinks++;
2350 		display_flag = TRUE;
2351 
2352 	    } else if (Anchor_ptr->link_type == INPUT_ANCHOR
2353 		       && Anchor_ptr->input_field->type != F_HIDDEN_TYPE) {
2354 		/*
2355 		 * Handle form fields.
2356 		 */
2357 		lynx_mode = FORMS_LYNX_MODE;
2358 
2359 		FormInfo_ptr = Anchor_ptr->input_field;
2360 
2361 		links[nlinks].sgml_offset = Anchor_ptr->sgml_offset;
2362 		links[nlinks].anchor_number = Anchor_ptr->number;
2363 		links[nlinks].anchor_line_num = Anchor_ptr->line_num;
2364 
2365 		links[nlinks].l_form = FormInfo_ptr;
2366 		links[nlinks].lx = Anchor_ptr->line_pos;
2367 		links[nlinks].ly = ((Anchor_ptr->line_num + 1) - line_number);
2368 		links[nlinks].type = WWW_FORM_LINK_TYPE;
2369 		links[nlinks].inUnderline = Anchor_ptr->inUnderline;
2370 		links[nlinks].target = empty_string;
2371 		StrAllocCopy(links[nlinks].lname, empty_string);
2372 
2373 		if (FormInfo_ptr->type == F_RADIO_TYPE) {
2374 		    LYSetHilite(nlinks,
2375 				FormInfo_ptr->num_value
2376 				? checked_radio
2377 				: unchecked_radio);
2378 		} else if (FormInfo_ptr->type == F_CHECKBOX_TYPE) {
2379 		    LYSetHilite(nlinks,
2380 				FormInfo_ptr->num_value
2381 				? checked_box
2382 				: unchecked_box);
2383 		} else if (FormInfo_ptr->type == F_PASSWORD_TYPE) {
2384 		    LYSetHilite(nlinks,
2385 				STARS(LYstrCells(FormInfo_ptr->value)));
2386 		} else {	/* TEXT type */
2387 		    LYSetHilite(nlinks,
2388 				FormInfo_ptr->value);
2389 		}
2390 
2391 		nlinks++;
2392 		/*
2393 		 * Bold the link after incrementing nlinks.
2394 		 */
2395 		LYhighlight(FALSE, (nlinks - 1), target);
2396 
2397 		display_flag = TRUE;
2398 
2399 	    } else {
2400 		/*
2401 		 * Not showing anchor.
2402 		 */
2403 		if (non_empty(hi_string))
2404 		    CTRACE((tfp,
2405 			    "\nGridText: Not showing link, hightext=%s\n",
2406 			    hi_string));
2407 	    }
2408 	}
2409 
2410 	if (nlinks == MAXLINKS) {
2411 	    /*
2412 	     * Links array is full.  If interactive, tell user
2413 	     * to use half-page or two-line scrolling.  -FM
2414 	     */
2415 	    if (LYCursesON) {
2416 		HTAlert(MAXLINKS_REACHED);
2417 	    }
2418 	    CTRACE((tfp, "\ndisplay_page: MAXLINKS reached.\n"));
2419 	    break;
2420 	}
2421     }				/* end of loop "Add the anchors to Lynx structures." */
2422 
2423     /*
2424      * Free any un-reallocated links[] entries
2425      * from the previous page draw.  -FM
2426      */
2427     LYFreeHilites(nlinks, last_nlinks);
2428     last_nlinks = nlinks;
2429 
2430     /*
2431      * If Anchor_ptr is not NULL and is not pointing to the last
2432      * anchor, then there are anchors farther down in the document,
2433      * and we need to flag this for traversals.
2434      */
2435     more_links = FALSE;
2436     if (traversal && Anchor_ptr) {
2437 	if (Anchor_ptr->next)
2438 	    more_links = TRUE;
2439     }
2440 
2441     if (!display_flag) {
2442 	/*
2443 	 * Nothing on the page.
2444 	 */
2445 	LYaddstr("\n     Document is empty");
2446     }
2447     display_scrollbar(text);
2448 
2449 #ifdef DISP_PARTIAL
2450     if (display_partial && display_flag &&
2451 	last_disp_partial >= text->top_of_screen &&
2452 	!enable_scrollback &&
2453 	!recent_sizechange) {	/*  really remember them if ok - kw  */
2454 	text->first_lineno_last_disp_partial = text->top_of_screen;
2455 	text->last_lineno_last_disp_partial = last_disp_partial;
2456     } else {
2457 	ResetPartialLinenos(text);
2458     }
2459 #endif /* DISP_PARTIAL */
2460 
2461 #if !defined(WIDEC_CURSES)
2462     if (text->has_utf8 || text->had_utf8) {
2463 	/*
2464 	 * For other than ncurses, repainting is taken care of
2465 	 * by touching lines in display_line and highlight.  - kw 1999-10-07
2466 	 */
2467 	text->had_utf8 = text->has_utf8;
2468 	clearok(curscr, TRUE);
2469     } else if (IS_CJK_TTY) {
2470 	/*
2471 	 * For non-multibyte curses.
2472 	 *
2473 	 * Full repainting is necessary, otherwise only part of a multibyte
2474 	 * character sequence might be written because of curses output
2475 	 * optimizations.
2476 	 */
2477 	clearok(curscr, TRUE);
2478     }
2479 #endif /* WIDEC_CURSES */
2480 
2481     LYrefresh();
2482     return;
2483 }
2484 
2485 /*			Object Building methods
2486  *			-----------------------
2487  *
2488  *	These are used by a parser to build the text in an object
2489  */
HText_beginAppend(HText * text)2490 void HText_beginAppend(HText *text)
2491 {
2492     text->permissible_split = 0;
2493     text->in_line_1 = YES;
2494 
2495 }
2496 
2497 /*
2498  * LYcols_cu is the notion that the display library has of the screen width.
2499  * Checks of the line length (as the non-UTF-8-aware display library would see
2500  * it) against LYcols_cu are used to try to prevent lines with UTF-8 chars from
2501  * being wrapped by the library when they shouldn't.  If there is no display
2502  * library involved, i.e., dump_output_immediately, no such limit should be
2503  * imposed.  MAX_COLS should be just as good as any other large value.  (But
2504  * don't use INT_MAX or something close to it to, avoid over/underflow.) - kw
2505  */
2506 #ifdef USE_SLANG
2507 #define LYcols_cu(text) (dump_output_immediately ? MAX_COLS : SLtt_Screen_Cols)
2508 #else
2509 #ifdef WIDEC_CURSES
2510 #define LYcols_cu(text) WRAP_COLS(text)
2511 #else
2512 #define LYcols_cu(text) (dump_output_immediately ? MAX_COLS : DISPLAY_COLS)
2513 #endif
2514 #endif
2515 
2516 /*	Add a new line of text
2517  *	----------------------
2518  *
2519  * On entry,
2520  *
2521  *	split	is zero for newline function, else number of characters
2522  *		before split.
2523  *	text->display_on_the_fly
2524  *		may be set to indicate direct output of the finished line.
2525  * On exit,
2526  *		A new line has been made, justified according to the
2527  *		current style.  Text after the split (if split nonzero)
2528  *		is taken over onto the next line.
2529  *
2530  *		If display_on_the_fly is set, then it is decremented and
2531  *		the finished line is displayed.
2532  */
2533 
set_style_by_embedded_chars(char * s,char * e,unsigned start_c,unsigned end_c)2534 static int set_style_by_embedded_chars(char *s,
2535 				       char *e,
2536 				       unsigned start_c,
2537 				       unsigned end_c)
2538 {
2539     int ret = NO;
2540 
2541     while (--e >= s) {
2542 	if (UCH(*e) == UCH(end_c))
2543 	    break;
2544 	if (UCH(*e) == UCH(start_c)) {
2545 	    ret = YES;
2546 	    break;
2547 	}
2548     }
2549     return ret;
2550 }
2551 
move_anchors_in_region(HTLine * line,int line_number,TextAnchor ** prev_anchor,int * prev_head_processed,int sbyte,int ebyte,int shift)2552 static void move_anchors_in_region(HTLine *line, int line_number,
2553 				   TextAnchor **prev_anchor,	/*updates++ */
2554 				   int *prev_head_processed,
2555 				   int sbyte,
2556 				   int ebyte,
2557 				   int shift)	/* Likewise */
2558 {
2559     /*
2560      * Update anchor positions for anchors that start on this line.  Note:  we
2561      * rely on a->line_pos counting bytes, not characters.  That's one reason
2562      * why HText_trimHightext has to be prevented from acting on these anchors
2563      * in partial display mode before we get a chance to deal with them here.
2564      */
2565     TextAnchor *a;
2566     int head_processed = *prev_head_processed;
2567 
2568     /*
2569      * We need to know whether (*prev_anchor)->line_pos is "in new coordinates"
2570      * or in old ones.  If prev_anchor' head was touched on the previous
2571      * iteration, we set head_processed.  The tail may need to be treated now.
2572      */
2573     for (a = *prev_anchor;
2574 	 a && a->line_num <= line_number;
2575 	 a = a->next, head_processed = 0) {
2576 	/* extent==0 needs to be special-cased; happens if no text for
2577 	   the anchor was processed yet.  */
2578 	/* Subtract one so that the space is not inserted at the end
2579 	   of the anchor... */
2580 	int last = a->line_pos + (a->extent ? a->extent - 1 : 0);
2581 
2582 	/* Include the anchors started on the previous line */
2583 	if (a->line_num < line_number - 1)
2584 	    continue;
2585 	if (a->line_num == line_number - 1)
2586 	    last -= line->prev->size + 1;	/* Fake "\n" "between" lines counted too */
2587 	if (last < sbyte)	/* Completely before the start */
2588 	    continue;
2589 
2590 	if (!head_processed	/* a->line_pos is not edited yet */
2591 	    && a->line_num == line_number
2592 	    && a->line_pos >= ebyte)	/* Completely after the end */
2593 	    break;
2594 	/* Now we know that the anchor context intersects the chunk */
2595 
2596 	/* Fix the start */
2597 	if (!head_processed && a->line_num == line_number
2598 	    && a->line_pos >= sbyte) {
2599 	    a->line_pos = (short) (a->line_pos + shift);
2600 	    a->extent = (short) (a->extent - shift);
2601 	    head_processed = 1;
2602 	}
2603 	/* Fix the end */
2604 	if (last < ebyte) {
2605 	    a->extent = (short) (a->extent + shift);
2606 	} else {
2607 	    break;		/* Keep this `a' for the next step */
2608 	}
2609     }
2610     *prev_anchor = a;
2611     *prev_head_processed = head_processed;
2612 }
2613 
2614 /*
2615  * Given a line and two int arrays of old/now position, this function
2616  * creates a new line where spaces have been inserted/removed
2617  * in appropriate places - so that characters at/after the old
2618  * position end up at/after the new position, for each pair, if possible.
2619  * Some necessary changes for anchors starting on this line are also done
2620  * here if needed.  Updates 'prev_anchor' internally.
2621  * Returns a newly allocated HTLine* if changes were made
2622  * (caller has to free the old one).
2623  * Returns NULL if no changes needed.  (Remove-spaces code may be buggy...)
2624  * - kw
2625  */
insert_blanks_in_line(HTLine * line,int line_number,HText * text,TextAnchor ** prev_anchor,int ninserts,int * oldpos,int * newpos)2626 static HTLine *insert_blanks_in_line(HTLine *line, int line_number,
2627 				     HText *text,
2628 				     TextAnchor **prev_anchor,	/*updates++ */
2629 				     int ninserts,
2630 				     int *oldpos,	/* Measured in cells */
2631 				     int *newpos)	/* Likewise */
2632 {
2633     int ioldc = 0;		/* count visible characters */
2634     int ip;			/* count insertion pairs */
2635 
2636 #if defined(USE_COLOR_STYLE)
2637     int istyle = 0;
2638 #endif
2639     int added_chars = 0;
2640     int shift = 0;
2641     int head_processed;
2642     HTLine *mod_line;
2643     char *newdata;
2644     char *s = line->data;
2645     char *pre = s;
2646     char *copied = line->data, *t;
2647 
2648     if (!(line && line->size && ninserts))
2649 	return NULL;
2650     for (ip = 0; ip < ninserts; ip++)
2651 	if (newpos[ip] > oldpos[ip] &&
2652 	    (newpos[ip] - oldpos[ip]) > added_chars)
2653 	    added_chars = newpos[ip] - oldpos[ip];
2654     if (line->size + added_chars > MAX_LINE - 2)
2655 	return NULL;
2656     if (line == text->last_line) {
2657 	if (line == TEMP_LINE(text, 0))
2658 	    mod_line = TEMP_LINE(text, 1);
2659 	else
2660 	    mod_line = TEMP_LINE(text, 0);
2661     } else {
2662 	allocHTLine(mod_line, (unsigned) (line->size + added_chars));
2663     }
2664     if (!mod_line)
2665 	return NULL;
2666     if (!*prev_anchor)
2667 	*prev_anchor = text->first_anchor;
2668     head_processed = (*prev_anchor && (*prev_anchor)->line_num < line_number);
2669     memcpy(mod_line, line, LINE_SIZE(0));
2670     t = newdata = mod_line->data;
2671     ip = 0;
2672     while (ip <= ninserts) {
2673 	/* line->size is in bytes, so it may be larger than needed... */
2674 	int curlim = (ip < ninserts
2675 		      ? oldpos[ip]
2676 		      : ((int) line->size <= MAX_LINE
2677 			 ? MAX_LINE + 1
2678 			 : (int) line->size + 1));
2679 
2680 	pre = s;
2681 
2682 	/* Fast forward to char==curlim or EOL.  Stop *before* the
2683 	   style-change chars. */
2684 	while (*s) {
2685 	    if (text && text->T.output_utf8
2686 		&& UCH(*s) >= 0x80 && UCH(*s) < 0xC0) {
2687 		pre = s + 1;
2688 	    } else if (!IsSpecialAttrChar(*s)) {	/* At a "displayed" char */
2689 		if (ioldc >= curlim)
2690 		    break;
2691 		ioldc++;
2692 		pre = s + 1;
2693 	    }
2694 	    s++;
2695 	}
2696 
2697 	/* Now s is at the "displayed" char, pre is before the style change */
2698 	if (ip)			/* Fix anchor positions */
2699 	    move_anchors_in_region(line, line_number, prev_anchor /*updates++ */ ,
2700 				   &head_processed,
2701 				   (int) (copied - line->data), (int) (pre - line->data),
2702 				   shift);
2703 #if defined(USE_COLOR_STYLE)	/* Move styles too */
2704 #define NStyle mod_line->styles[istyle]
2705 	for (;
2706 	     istyle < line->numstyles && (int) NStyle.sc_horizpos < curlim;
2707 	     istyle++)
2708 	    /* Should not we include OFF-styles at curlim? */
2709 	    NStyle.sc_horizpos = CAST_POS(NStyle.sc_horizpos + shift);
2710 #endif
2711 	while (copied < pre)	/* Copy verbatim to byte == pre */
2712 	    *t++ = *copied++;
2713 	if (ip < ninserts) {	/* Insert spaces */
2714 	    int delta = newpos[ip] - oldpos[ip] - shift;
2715 
2716 	    if (delta < 0) {	/* Not used yet? */
2717 		while (delta++ < 0 && t > newdata && t[-1] == ' ')
2718 		    t--, shift--;
2719 	    } else
2720 		shift = newpos[ip] - oldpos[ip];
2721 	    while (delta-- > 0)
2722 		*t++ = ' ';
2723 	}
2724 	ip++;
2725     }
2726     while (pre < s)		/* Copy remaining style-codes */
2727 	*t++ = *pre++;
2728     /* Check whether the last anchor continues on the next line */
2729     if (head_processed
2730 	&& *prev_anchor
2731 	&& (*prev_anchor)->line_num == line_number) {
2732 	(*prev_anchor)->extent = (short) ((*prev_anchor)->extent + shift);
2733     }
2734     *t = '\0';
2735     mod_line->size = (unsigned short) (t - newdata);
2736     return mod_line;
2737 }
2738 
2739 #if defined(USE_COLOR_STYLE)
2740 #define direction2s(d) ((d) == STACK_OFF \
2741 			? "OFF" \
2742 			: ((d) == STACK_ON \
2743 			   ? "ON" \
2744 			   : "*ON"))
2745 
2746 /*
2747  * Found an OFF change not part of an adjacent matched pair.
2748  *
2749  * Walk backward looking for the corresponding ON change.
2750  * Move everything after split_pos to be at split_pos.
2751  *
2752  * This can only work correctly if all changes are correctly nested!  If this
2753  * fails, assume it is safer to leave whatever comes before the OFF on the
2754  * previous line alone.
2755  */
skip_matched_and_correct_offsets(HTStyleChange * end,HTStyleChange * start,unsigned split_pos)2756 static HTStyleChange *skip_matched_and_correct_offsets(HTStyleChange *end,
2757 						       HTStyleChange *start,
2758 						       unsigned split_pos)
2759 {
2760     HTStyleChange *result = 0;
2761     int level = 0;
2762     HTStyleChange *tmp = end;
2763 
2764     CTRACE_STYLE((tfp, "SKIP Style %d %d (%s), split %u\n",
2765 		  tmp->sc_horizpos,
2766 		  tmp->sc_style,
2767 		  direction2s(tmp->sc_direction),
2768 		  split_pos));
2769     for (; tmp >= start; tmp--) {
2770 	CTRACE_STYLE((tfp, "... %d %d (%s)\n",
2771 		      tmp->sc_horizpos,
2772 		      tmp->sc_style,
2773 		      direction2s(tmp->sc_direction)));
2774 	if (tmp->sc_style == end->sc_style) {
2775 	    if (tmp->sc_direction == STACK_OFF) {
2776 		level--;
2777 	    } else if (tmp->sc_direction == STACK_ON) {
2778 		if (++level == 0) {
2779 		    result = tmp;
2780 		    break;
2781 		}
2782 	    } else {
2783 		break;
2784 	    }
2785 	}
2786 	if (tmp->sc_horizpos > split_pos) {
2787 	    tmp->sc_horizpos = CAST_POS(split_pos);
2788 	}
2789     }
2790     return result;
2791 }
2792 #endif /* USE_COLOR_STYLE */
2793 
2794 #define reset_horizpos(value) value = 0, value ^= MASK_POS
2795 
split_line(HText * text,unsigned split)2796 static void split_line(HText *text, unsigned split)
2797 {
2798     HTStyle *style = text->style;
2799     int spare;
2800     int indent = (text->in_line_1
2801 		  ? text->style->indent1st
2802 		  : text->style->leftIndent);
2803     int new_offset;
2804     short alignment;
2805     TextAnchor *a;
2806     int CurLine = text->Lines;
2807     int HeadTrim = 0;
2808     int SpecialAttrChars = 0;
2809     int TailTrim = 0;
2810     int s, s_post, s_pre, t_underline = underline_on, t_bold = bold_on;
2811     char *p;
2812     char *cp;
2813     int ctrl_chars_on_previous_line = 0;
2814 
2815 #ifndef WIDEC_CURSES
2816     int utfxtra_on_previous_line = UTFXTRA_ON_THIS_LINE;
2817 #endif
2818 
2819     HTLine *previous = text->last_line;
2820     HTLine *line;
2821 
2822     /*
2823      * Set new line.
2824      */
2825     if (previous == TEMP_LINE(text, 0))
2826 	line = TEMP_LINE(text, 1);
2827     else
2828 	line = TEMP_LINE(text, 0);
2829     if (line == NULL)
2830 	return;
2831     memset(line, 0, (size_t) LINE_SIZE(0));
2832 
2833     ctrl_chars_on_this_line = 0;	/*reset since we are going to a new line */
2834     utfxtra_on_this_line = 0;	/*reset too, we'll count them */
2835     text->LastChar = ' ';
2836 
2837 #ifdef DEBUG_APPCH
2838     CTRACE((tfp, "GridText: split_line(%p,%d) called\n", text, split));
2839     CTRACE((tfp, "   previous=%s\n", previous->data));
2840     CTRACE((tfp, "   bold_on=%d, underline_on=%d\n", bold_on, underline_on));
2841 #endif
2842 
2843     cp = previous->data;
2844 
2845     /* Float LY_SOFT_NEWLINE to the start */
2846     if (cp[0] == LY_BOLD_START_CHAR
2847 	|| cp[0] == LY_UNDERLINE_START_CHAR) {
2848 	switch (cp[1]) {
2849 	case LY_SOFT_NEWLINE:
2850 	    cp[1] = cp[0];
2851 	    cp[0] = LY_SOFT_NEWLINE;
2852 	    break;
2853 	case LY_BOLD_START_CHAR:
2854 	case LY_UNDERLINE_START_CHAR:
2855 	    if (cp[2] == LY_SOFT_NEWLINE) {
2856 		cp[2] = cp[1];
2857 		cp[1] = cp[0];
2858 		cp[0] = LY_SOFT_NEWLINE;
2859 	    }
2860 	    break;
2861 	}
2862     }
2863     if (split > previous->size) {
2864 	CTRACE((tfp,
2865 		"*** split_line: split==%u greater than last_line->size==%d !\n",
2866 		split, previous->size));
2867 	if (split > MAX_LINE) {
2868 	    split = previous->size;
2869 	    if ((cp = strrchr(previous->data, ' ')) &&
2870 		cp - previous->data > 1)
2871 		split = (unsigned) (cp - previous->data);
2872 	    CTRACE((tfp, "                split adjusted to %u.\n", split));
2873 	}
2874     }
2875 
2876     text->Lines++;
2877 
2878     previous->next->prev = line;
2879     line->prev = previous;
2880     line->next = previous->next;
2881     previous->next = line;
2882     text->last_line = line;
2883     line->size = 0;
2884     line->offset = 0;
2885     text->permissible_split = 0;	/* 12/13/93 */
2886     line->data[0] = '\0';
2887 
2888     alignment = style->alignment;
2889 
2890     if (split > 0) {		/* Restore flags to the value at the splitting point */
2891 	if (!(dump_output_immediately && use_underscore))
2892 	    t_underline = set_style_by_embedded_chars(previous->data,
2893 						      previous->data + split,
2894 						      LY_UNDERLINE_START_CHAR, LY_UNDERLINE_END_CHAR);
2895 
2896 	t_bold = set_style_by_embedded_chars(previous->data,
2897 					     previous->data + split,
2898 					     LY_BOLD_START_CHAR, LY_BOLD_END_CHAR);
2899 
2900     }
2901 
2902     if (!(dump_output_immediately && use_underscore) && t_underline) {
2903 	line->data[line->size++] = LY_UNDERLINE_START_CHAR;
2904 	line->data[line->size] = '\0';
2905 	ctrl_chars_on_this_line++;
2906 	SpecialAttrChars++;
2907     }
2908     if (t_bold) {
2909 	line->data[line->size++] = LY_BOLD_START_CHAR;
2910 	line->data[line->size] = '\0';
2911 	ctrl_chars_on_this_line++;
2912 	SpecialAttrChars++;
2913     }
2914 
2915     /*
2916      * Split at required point
2917      */
2918     if (split > 0) {		/* Delete space at "split" splitting line */
2919 	char *prevdata = previous->data, *linedata = line->data;
2920 	unsigned plen;
2921 	int i;
2922 
2923 	/* Split the line. -FM */
2924 	prevdata[previous->size] = '\0';
2925 	previous->size = (unsigned short) split;
2926 
2927 	/*
2928 	 * Trim any spaces or soft hyphens from the beginning
2929 	 * of our new line.  -FM
2930 	 */
2931 	p = prevdata + split;
2932 	while (((*p == ' '
2933 #ifdef USE_JUSTIFY_ELTS
2934 	/* if justification is allowed for prev line, then raw
2935 	 * HT_NON_BREAK_SPACE are still present in data[] (they'll be
2936 	 * substituted at the end of this function with ' ') - VH
2937 	 */
2938 		 || *p == HT_NON_BREAK_SPACE
2939 #endif
2940 		)
2941 		&& (HeadTrim || text->first_anchor ||
2942 		    underline_on || bold_on ||
2943 		    alignment != HT_LEFT ||
2944 		    style->wordWrap || style->freeFormat ||
2945 		    style->spaceBefore || style->spaceAfter)) ||
2946 	       *p == LY_SOFT_HYPHEN) {
2947 	    p++;
2948 	    HeadTrim++;
2949 	}
2950 
2951 	plen = (unsigned) strlen(p);
2952 	if (plen) {		/* Count funny characters */
2953 	    for (i = (int) (plen - 1); i >= 0; i--) {
2954 		if (p[i] == LY_UNDERLINE_START_CHAR ||
2955 		    p[i] == LY_UNDERLINE_END_CHAR ||
2956 		    p[i] == LY_BOLD_START_CHAR ||
2957 		    p[i] == LY_BOLD_END_CHAR ||
2958 		    p[i] == LY_SOFT_HYPHEN) {
2959 		    ctrl_chars_on_this_line++;
2960 		} else if (IS_UTF_EXTRA(p[i])) {
2961 		    utfxtra_on_this_line++;
2962 		}
2963 		if (p[i] == LY_SOFT_HYPHEN &&
2964 		    (int) text->permissible_split < i)
2965 		    text->permissible_split = (unsigned) (i + 1);
2966 	    }
2967 	    ctrl_chars_on_this_line += utfxtra_on_this_line;
2968 
2969 	    /* Add the data to the new line. -FM */
2970 	    strcat(linedata, p);
2971 	    line->size = (unsigned short) (line->size + plen);
2972 	}
2973     }
2974 
2975     /*
2976      * Economize on space.
2977      */
2978     p = previous->data + previous->size - 1;
2979     while (p >= previous->data
2980 	   && (*p == ' '
2981 #ifdef USE_JUSTIFY_ELTS
2982     /* if justification is allowed for prev line, then raw
2983      * HT_NON_BREAK_SPACE are still present in data[] (they'll be
2984      * substituted at the end of this function with ' ') - VH
2985      */
2986 	       || *p == HT_NON_BREAK_SPACE
2987 #endif
2988 	   )
2989 #ifdef USE_PRETTYSRC
2990 	   && !psrc_view	/*don't strip trailing whites - since next line can
2991 				   start with LY_SOFT_NEWLINE - so we don't lose spaces when
2992 				   'p'rinting this text to file -VH */
2993 #endif
2994 	   && (ctrl_chars_on_this_line || HeadTrim || text->first_anchor ||
2995 	       underline_on || bold_on ||
2996 	       alignment != HT_LEFT ||
2997 	       style->wordWrap || style->freeFormat ||
2998 	       style->spaceBefore || style->spaceAfter)) {
2999 	p--;			/*  Strip trailers. */
3000     }
3001     /*  Strip trailers. */
3002     TailTrim = (int) (previous->data + previous->size - 1 - p);
3003     previous->size = (unsigned short) (previous->size - TailTrim);
3004     p[1] = '\0';
3005 
3006     /*
3007      * s is the effective split position, given by either a non-zero
3008      * value of split or by the size of the previous line before
3009      * trimming.  - kw
3010      */
3011     if (split == 0) {
3012 	s = previous->size + TailTrim;	/* the original size */
3013     } else {
3014 	s = (int) split;
3015     }
3016     s_post = s + HeadTrim;
3017     s_pre = s - TailTrim;
3018 
3019 #ifdef DEBUG_SPLITLINE
3020 #ifdef DEBUG_APPCH
3021     if (s != (int) split)
3022 #endif
3023 	CTRACE((tfp, "GridText: split_line(%u [now:%d]) called\n", split, s));
3024 #endif
3025 
3026 #if defined(USE_COLOR_STYLE)
3027     if (previous->styles == stylechanges_buffers[0])
3028 	line->styles = stylechanges_buffers[1];
3029     else
3030 	line->styles = stylechanges_buffers[0];
3031     line->numstyles = 0;
3032     {
3033 	HTStyleChange *from = previous->styles + previous->numstyles - 1;
3034 	HTStyleChange *to = line->styles + MAX_STYLES_ON_LINE - 1;
3035 	HTStyleChange *scan, *at_end;
3036 
3037 	/* Color style changes after the split position
3038 	 * are transferred to the new line.  Ditto for changes
3039 	 * in the trimming region, but we stop when we reach an OFF change.
3040 	 * The second loop below may then handle remaining changes.  - kw */
3041 	while (from >= previous->styles && to >= line->styles) {
3042 	    *to = *from;
3043 	    if ((int) to->sc_horizpos > s_post) {
3044 		to->sc_horizpos = CAST_POS(to->sc_horizpos
3045 					   + SpecialAttrChars
3046 					   - s_post);
3047 	    } else if ((int) to->sc_horizpos > s_pre &&
3048 		       (to->sc_direction == STACK_ON ||
3049 			to->sc_direction == ABS_ON)) {
3050 		if ((int) to->sc_horizpos < s)
3051 		    to->sc_horizpos = 0;
3052 		else
3053 		    to->sc_horizpos = CAST_POS(SpecialAttrChars);
3054 	    } else {
3055 		break;
3056 	    }
3057 	    to--;
3058 	    from--;
3059 	}
3060 	/* FROM may be invalid, otherwise it is either an ON change at or
3061 	   before s_pre, or is an OFF change at or before s_post.  */
3062 
3063 	scan = from;
3064 	at_end = from;
3065 	/* Now on the previous line we have a correctly nested but
3066 	   possibly non-terminated sequence of style changes.
3067 	   Terminate it, and duplicate unterminated changes at the
3068 	   beginning of the new line. */
3069 	while (scan >= previous->styles && at_end >= previous->styles) {
3070 	    /* The algorithm: scan back though the styles on the previous line.
3071 	       a) If OFF, skip the matched group.
3072 	       Report a bug on failure.
3073 	       b) If ON, (try to) cancel the corresponding ON at at_end,
3074 	       and the corresponding OFF at to;
3075 	       If not, put the corresponding OFF at at_end, and copy to to;
3076 	     */
3077 	    if (scan->sc_direction == STACK_OFF) {
3078 		scan = skip_matched_and_correct_offsets(scan, previous->styles,
3079 							(unsigned) s_pre);
3080 		if (!scan) {
3081 		    CTRACE((tfp, "BUG: styles improperly nested.\n"));
3082 		    break;
3083 		}
3084 	    } else if (scan->sc_direction == STACK_ON) {
3085 		if (at_end->sc_direction == STACK_ON
3086 		    && at_end->sc_style == scan->sc_style
3087 		    && (int) at_end->sc_horizpos >= s_pre)
3088 		    at_end--;
3089 		else if (at_end >= previous->styles + MAX_STYLES_ON_LINE - 1) {
3090 		    CTRACE((tfp, "BUG: style overflow before split_line.\n"));
3091 		    break;
3092 		} else {
3093 		    at_end++;
3094 		    at_end->sc_direction = STACK_OFF;
3095 		    at_end->sc_style = scan->sc_style;
3096 		    at_end->sc_horizpos = CAST_POS(s_pre);
3097 		    CTRACE_STYLE((tfp,
3098 				  "split_line, %d:style[%d] %d (dir=%d)\n",
3099 				  s_pre,
3100 				  (int) (at_end - from),
3101 				  scan->sc_style,
3102 				  at_end->sc_direction));
3103 		}
3104 		if (to < line->styles + MAX_STYLES_ON_LINE - 1
3105 		    && to[1].sc_direction == STACK_OFF
3106 		    && to[1].sc_horizpos <= (unsigned) SpecialAttrChars
3107 		    && to[1].sc_style == scan->sc_style)
3108 		    to++;
3109 		else if (to >= line->styles) {
3110 		    *to = *scan;
3111 		    to->sc_horizpos = CAST_POS(SpecialAttrChars);
3112 		    to--;
3113 		} else {
3114 		    CTRACE((tfp, "BUG: style overflow after split_line.\n"));
3115 		    break;
3116 		}
3117 	    }
3118 	    if ((int) scan->sc_horizpos > s_pre) {
3119 		scan->sc_horizpos = CAST_POS(s_pre);
3120 	    }
3121 	    scan--;
3122 	}
3123 	line->numstyles = (unsigned short) (line->styles
3124 					    + MAX_STYLES_ON_LINE
3125 					    - 1 - to);
3126 	if (line->numstyles > 0 && line->numstyles < MAX_STYLES_ON_LINE) {
3127 	    int n;
3128 
3129 	    for (n = 0; n < line->numstyles; n++)
3130 		line->styles[n] = to[n + 1];
3131 	} else if (line->numstyles == 0) {
3132 	    reset_horizpos(line->styles[0].sc_horizpos);
3133 	}
3134 	previous->numstyles = (unsigned short) (at_end - previous->styles + 1);
3135 	if (previous->numstyles == 0) {
3136 	    reset_horizpos(previous->styles[0].sc_horizpos);
3137 	}
3138     }
3139 #endif /*USE_COLOR_STYLE */
3140 
3141     {
3142 	HTLine *temp;
3143 
3144 	allocHTLine(temp, previous->size);
3145 	if (!temp)
3146 	    outofmem(__FILE__, "split_line_2");
3147 
3148 	memcpy(temp, previous, LINE_SIZE(previous->size));
3149 #if defined(USE_COLOR_STYLE)
3150 	POOLallocstyles(temp->styles, previous->numstyles);
3151 	if (!temp->styles)
3152 	    outofmem(__FILE__, "split_line_2");
3153 	memcpy(temp->styles, previous->styles, sizeof(HTStyleChange) * previous->numstyles);
3154 #endif
3155 	previous = temp;
3156     }
3157 
3158     previous->prev->next = previous;	/* Link in new line */
3159     previous->next->prev = previous;	/* Could be same node of course */
3160 
3161     /*
3162      * Terminate finished line for printing.
3163      */
3164     previous->data[previous->size] = '\0';
3165 
3166     /*
3167      * Align left, right or center.
3168      */
3169     spare = 0;
3170     if (
3171 #ifdef USE_JUSTIFY_ELTS
3172 	   this_line_was_split ||
3173 #endif
3174 	   (alignment == HT_CENTER ||
3175 	    alignment == HT_RIGHT) || text->stbl) {
3176 	/* Calculate spare character positions if needed */
3177 	for (cp = previous->data; *cp; cp++) {
3178 	    if (*cp == LY_UNDERLINE_START_CHAR ||
3179 		*cp == LY_UNDERLINE_END_CHAR ||
3180 		*cp == LY_BOLD_START_CHAR ||
3181 		*cp == LY_BOLD_END_CHAR ||
3182 #ifndef WIDEC_CURSES
3183 		IS_UTF_EXTRA(*cp) ||
3184 #endif
3185 		*cp == LY_SOFT_HYPHEN) {
3186 		ctrl_chars_on_previous_line++;
3187 	    }
3188 	}
3189 	if ((previous->size > 0) &&
3190 	    (int) (previous->data[previous->size - 1] == LY_SOFT_HYPHEN))
3191 	    ctrl_chars_on_previous_line--;
3192 
3193 	/* @@ first line indent */
3194 #ifdef WIDEC_CURSES
3195 	spare = WRAP_COLS(text)
3196 	    - (int) style->rightIndent
3197 	    - indent
3198 	    + ctrl_chars_on_previous_line
3199 	    - LYstrExtent2(previous->data, previous->size);
3200 	if (spare < 0 && LYwideLines)	/* Can be wider than screen */
3201 	    spare = 0;
3202 #else
3203 	spare = WRAP_COLS(text)
3204 	    - (int) style->rightIndent
3205 	    - indent
3206 	    + ctrl_chars_on_previous_line
3207 	    - previous->size;
3208 	if (spare < 0 && LYwideLines)	/* Can be wider than screen */
3209 	    spare = 0;
3210 
3211 	if (spare > 0 && !dump_output_immediately &&
3212 	    text->T.output_utf8 && ctrl_chars_on_previous_line) {
3213 	    utfxtra_on_previous_line -= UTFXTRA_ON_THIS_LINE;
3214 	    if (utfxtra_on_previous_line) {
3215 		int spare_cu = (LYcols_cu(text) -
3216 				utfxtra_on_previous_line - indent +
3217 				ctrl_chars_on_previous_line - previous->size);
3218 
3219 		/*
3220 		 * Shift non-leftaligned UTF-8 lines that would be
3221 		 * mishandled by the display library towards the left
3222 		 * if this would make them fit.  The resulting display
3223 		 * will not be as intended, but this is better than
3224 		 * having them split by curses.  (Curses cursor movement
3225 		 * optimization may still cause wrong positioning within
3226 		 * the line, in particular after a sequence of spaces).
3227 		 * - kw
3228 		 */
3229 		if (spare_cu < spare) {
3230 		    if (spare_cu >= 0) {
3231 			if (alignment == HT_CENTER &&
3232 			    (int) (previous->offset + indent + spare / 2 +
3233 				   previous->size)
3234 			    - ctrl_chars_on_previous_line
3235 			    + utfxtra_on_previous_line <= LYcols_cu(text))
3236 			    /* do nothing - it still fits - kw */ ;
3237 			else {
3238 			    spare = spare_cu;
3239 			}
3240 		    } else if (indent + (int) previous->offset + spare_cu >= 0) {	/* subtract overdraft from effective indentation */
3241 			indent += (int) previous->offset + spare_cu;
3242 			previous->offset = 0;
3243 			spare = 0;
3244 		    }
3245 		}
3246 	    }
3247 	}
3248 #endif
3249     }
3250 
3251     new_offset = previous->offset;
3252     switch (style->alignment) {
3253     case HT_CENTER:
3254 	new_offset += indent + spare / 2;
3255 	break;
3256     case HT_RIGHT:
3257 	new_offset += indent + spare;
3258 	break;
3259     case HT_LEFT:
3260     case HT_JUSTIFY:		/* Not implemented */
3261     default:
3262 	new_offset += indent;
3263 	break;
3264     }				/* switch */
3265     previous->offset = (unsigned short) ((new_offset < 0) ? 0 : new_offset);
3266 
3267     if (text->stbl) {
3268 	/*
3269 	 * Notify simple table stuff of line split, so that it can
3270 	 * set the last cell's length.  The last cell should and
3271 	 * its row should really end here, or on one of the following
3272 	 * lines with no more characters added after the break.
3273 	 * We don't know whether a cell has been started, so ignore
3274 	 * errors here.
3275 	 * This call is down here because we need the
3276 	 * ctrl_chars_on_previous_line, which have just been re-
3277 	 * counted above.  - kw
3278 	 */
3279 	Stbl_lineBreak(text->stbl,
3280 		       text->Lines - 1,
3281 		       previous->offset,
3282 		       previous->size - ctrl_chars_on_previous_line);
3283     }
3284 
3285     text->in_line_1 = NO;	/* unless caller sets it otherwise */
3286 
3287     /*
3288      * If we split the line, adjust the anchor
3289      * structure values for the new line.  -FM
3290      */
3291 
3292     if (s > 0) {		/* if not completely empty */
3293 	int moved = 0;
3294 
3295 	/* In the algorithm below we move or not move anchors between
3296 	   lines using some heuristic criteria.  However, it is
3297 	   desirable not to have two consequent anchors on different
3298 	   lines *in a wrong order*!  (How can this happen?)
3299 	   So when the "reasonable choice" is not unique, we use the
3300 	   MOVED flag to choose one.
3301 	 */
3302 	/* Our operations can make a non-empty all-whitespace link
3303 	   empty.  So what? */
3304 	if ((a = text->last_anchor_before_split) == 0)
3305 	    a = text->first_anchor;
3306 
3307 	for (; a; a = a->next) {
3308 	    if (a->line_num == CurLine) {
3309 		int len = a->extent, n = a->number, start = a->line_pos;
3310 		int end = start + len;
3311 
3312 		text->last_anchor_before_split = a;
3313 
3314 		/* Which anchors do we leave on the previous line?
3315 		   a) empty finished (We need a cut-off value.
3316 		   "Just because": those before s;
3317 		   this is the only case when we use s, not s_pre/s_post);
3318 		   b) Those which start before s_pre;
3319 		 */
3320 		if (start < s_pre) {
3321 		    if (end <= s_pre)
3322 			continue;	/* No problem */
3323 
3324 		    CTRACE_SPLITLINE((tfp, "anchor %d: no relocation", n));
3325 		    if (end > s_post) {
3326 			CTRACE_SPLITLINE((tfp, " of the start.\n"));
3327 			a->extent = (short) (a->extent
3328 					     - (TailTrim + HeadTrim)
3329 					     + SpecialAttrChars);
3330 		    } else {
3331 			CTRACE_SPLITLINE((tfp, ", cut the end.\n"));
3332 			a->extent = (short) (s_pre - start);
3333 		    }
3334 		    continue;
3335 		} else if (start < s && !len
3336 			   && (!n || (a->show_anchor && !moved))) {
3337 		    CTRACE_SPLITLINE((tfp,
3338 				      "anchor %d: no relocation, empty-finished",
3339 				      n));
3340 		    a->line_pos = (short) s_pre;	/* Leave at the end of line */
3341 		    continue;
3342 		}
3343 
3344 		/* The rest we relocate */
3345 		moved = 1;
3346 		a->line_num++;
3347 		CTRACE_SPLITLINE((tfp,
3348 				  "anchor %d: (T,H,S)=(%d,%d,%d); (line,pos,ext):(%d,%d,%d), ",
3349 				  n, TailTrim, HeadTrim, SpecialAttrChars,
3350 				  a->line_num, a->line_pos, a->extent));
3351 		if (end < s_post) {	/* Move the end to s_post */
3352 		    CTRACE_SPLITLINE((tfp, "Move end +%d, ", s_post - end));
3353 		    len += s_post - end;
3354 		}
3355 		if (start < s_post) {	/* Move the start to s_post */
3356 		    CTRACE_SPLITLINE((tfp, "Move start +%d, ", s_post - start));
3357 		    len -= s_post - start;
3358 		    start = s_post;
3359 		}
3360 		a->line_pos = (short) (start - s_post + SpecialAttrChars);
3361 		a->extent = (short) len;
3362 
3363 		CTRACE_SPLITLINE((tfp, "->(%d,%d,%d)\n",
3364 				  a->line_num, a->line_pos, a->extent));
3365 	    } else if (a->line_num > CurLine)
3366 		break;
3367 	}
3368     }
3369 #ifdef USE_JUSTIFY_ELTS
3370     /* now perform justification - by VH */
3371 
3372     if (this_line_was_split
3373 	&& spare > 0
3374 	&& !text->stbl		/* We don't inform TRST on the cell width change yet */
3375 	&& justify_max_void_percent > 0
3376 	&& justify_max_void_percent <= 100
3377 	&& justify_max_void_percent >= ((100 * spare)
3378 					/ (WRAP_COLS(text)
3379 					   - (int) style->rightIndent
3380 					   - indent
3381 					   + ctrl_chars_on_previous_line))) {
3382 	/* this is the only case when we need justification */
3383 	char *jp = previous->data + justify_start_position;
3384 	ht_run_info *r = ht_runs;
3385 	char c;
3386 	int d_, r_;
3387 	HTLine *jline;
3388 
3389 	ht_num_runs = 0;
3390 	r->byte_len = r->cell_len = 0;
3391 
3392 	for (; (c = *jp) != 0; ++jp) {
3393 	    if (c == ' ') {
3394 		++r;
3395 		++ht_num_runs;
3396 		r->byte_len = r->cell_len = 0;
3397 		continue;
3398 	    }
3399 	    ++r->byte_len;
3400 	    if (IsSpecialAttrChar(c))
3401 		continue;
3402 
3403 	    ++r->cell_len;
3404 	    if (c == HT_NON_BREAK_SPACE) {
3405 		*jp = ' ';	/* substitute it */
3406 		continue;
3407 	    }
3408 	    if (text->T.output_utf8 && is8bits(c)) {
3409 		int utf_extra = (int) utf8_length(text->T.output_utf8, jp);
3410 
3411 		r->byte_len += utf_extra;
3412 		jp += utf_extra;
3413 	    }
3414 	}
3415 	++ht_num_runs;
3416 
3417 	if (ht_num_runs != 1) {
3418 	    int *oldpos = (int *) malloc(sizeof(int)
3419 					 * 2 * (size_t) (ht_num_runs - 1));
3420 	    int *newpos = oldpos + ht_num_runs - 1;
3421 	    int i = 1;
3422 
3423 	    if (oldpos == NULL)
3424 		outofmem(__FILE__, "split_line_3");
3425 
3426 	    d_ = spare / (ht_num_runs - 1);
3427 	    r_ = spare % (ht_num_runs - 1);
3428 
3429 	    /* The first run is not moved, proceed to the second one */
3430 	    oldpos[0] = justify_start_position + ht_runs[0].cell_len + 1;
3431 	    newpos[0] = oldpos[0] + (d_ + (r_-- > 0));
3432 	    while (i < ht_num_runs - 1) {
3433 		int delta = ht_runs[i].cell_len + 1;
3434 
3435 		oldpos[i] = oldpos[i - 1] + delta;
3436 		newpos[i] = newpos[i - 1] + delta + (d_ + (r_-- > 0));
3437 		i++;
3438 	    }
3439 	    jline = insert_blanks_in_line(previous, CurLine, text,
3440 					  &last_anchor_of_previous_line /*updates++ */ ,
3441 					  ht_num_runs - 1, oldpos, newpos);
3442 	    free(oldpos);
3443 	    if (jline == NULL)
3444 		outofmem(__FILE__, "split_line_4");
3445 	    previous->next->prev = jline;
3446 	    previous->prev->next = jline;
3447 
3448 	    freeHTLine(text, previous);
3449 
3450 	    previous = jline;
3451 	}
3452 	if (justify_start_position) {
3453 	    char *p2 = previous->data;
3454 
3455 	    for (; p2 < previous->data + justify_start_position; ++p2)
3456 		*p2 = (char) (*p2 == HT_NON_BREAK_SPACE ? ' ' : *p2);
3457 	}
3458     } else {
3459 	if (REALLY_CAN_JUSTIFY(text)) {
3460 	    char *p2;
3461 
3462 	    /* it was permitted to justify line, but this function was called
3463 	     * to end paragraph - we must substitute HT_NON_BREAK_SPACEs with
3464 	     * spaces in previous line
3465 	     */
3466 	    if (line->size && !text->stbl) {
3467 		CTRACE((tfp,
3468 			"BUG: justification: shouldn't happen - new line is not empty!\n\t'%.*s'\n",
3469 			line->size, line->data));
3470 	    }
3471 
3472 	    for (p2 = previous->data; *p2; ++p2)
3473 		if (*p2 == HT_NON_BREAK_SPACE)
3474 		    *p2 = ' ';
3475 	} else if (have_raw_nbsps) {
3476 	    /* this is very rare case, that can happen in forms placed in
3477 	       table cells */
3478 	    unsigned i;
3479 
3480 	    for (i = 0; i < previous->size; ++i)
3481 		if (previous->data[i] == HT_NON_BREAK_SPACE)
3482 		    previous->data[i] = ' ';
3483 
3484 	    /*next line won't be justified, so substitute nbsps in it too */
3485 	    for (i = 0; i < line->size; ++i)
3486 		if (line->data[i] == HT_NON_BREAK_SPACE)
3487 		    line->data[i] = ' ';
3488 	}
3489 
3490 	/* else HT_NON_BREAK_SPACEs were substituted with spaces in
3491 	   HText_appendCharacter */
3492     }
3493     /* cleanup */
3494     can_justify_this_line = TRUE;
3495     justify_start_position = 0;
3496     this_line_was_split = FALSE;
3497     have_raw_nbsps = FALSE;
3498 #endif /* USE_JUSTIFY_ELTS */
3499     return;
3500 }				/* split_line */
3501 
3502 #ifdef DEBUG_SPLITLINE
do_new_line(HText * text,const char * fn,int ln)3503 static void do_new_line(HText *text, const char *fn, int ln)
3504 {
3505     CTRACE_SPLITLINE((tfp, "new_line %s@%d\n", fn, ln));
3506     split_line(text, 0);
3507 }
3508 
3509 #define new_line(text) do_new_line(text, __FILE__, __LINE__)
3510 #else
3511 #define new_line(text) split_line(text, 0)
3512 #endif
3513 
3514 /*	Allow vertical blank space
3515  *	--------------------------
3516  */
blank_lines(HText * text,int newlines)3517 static void blank_lines(HText *text, int newlines)
3518 {
3519     if (HText_TrueEmptyLine(text->last_line, text, FALSE)) {	/* No text on current line */
3520 	HTLine *line = text->last_line->prev;
3521 	BOOL first = (BOOL) (line == text->last_line);
3522 
3523 	if (no_title && first)
3524 	    return;
3525 
3526 #ifdef USE_COLOR_STYLE
3527 	/* Style-change petty requests at the start of the document: */
3528 	if (first && newlines == 1)
3529 	    return;		/* Do not add a blank line at start */
3530 #endif
3531 
3532 	while (line != NULL &&
3533 	       line != text->last_line &&
3534 	       HText_TrueEmptyLine(line, text, FALSE)) {
3535 	    if (newlines == 0)
3536 		break;
3537 	    newlines--;		/* Don't bother: already blank */
3538 	    line = line->prev;
3539 	}
3540     } else {
3541 	newlines++;		/* Need also to finish this line */
3542     }
3543 
3544     for (; newlines; newlines--) {
3545 	new_line(text);
3546     }
3547     text->in_line_1 = YES;
3548 }
3549 
3550 /*	New paragraph in current style
3551  *	------------------------------
3552  * See also: setStyle.
3553  */
HText_appendParagraph(HText * text)3554 void HText_appendParagraph(HText *text)
3555 {
3556     int after = text->style->spaceAfter;
3557     int before = text->style->spaceBefore;
3558 
3559     blank_lines(text, ((after > before) ? after : before));
3560 }
3561 
3562 /*	Set Style
3563  *	---------
3564  *
3565  *	Does not filter unnecessary style changes.
3566  */
HText_setStyle(HText * text,HTStyle * style)3567 void HText_setStyle(HText *text, HTStyle *style)
3568 {
3569     int after, before;
3570 
3571     if (!style)
3572 	return;			/* Safety */
3573     after = text->style->spaceAfter;
3574     before = style->spaceBefore;
3575 
3576     CTRACE((tfp, "GridText: Change to style %s\n", GetHTStyleName(style)));
3577 
3578     blank_lines(text, ((after > before) ? after : before));
3579 
3580     text->style = style;
3581 }
3582 
3583 /*	Append a character to the text object
3584  *	-------------------------------------
3585  */
HText_appendCharacter(HText * text,int ch)3586 void HText_appendCharacter(HText *text, int ch)
3587 {
3588     HTLine *line;
3589     HTStyle *style;
3590     int indent;
3591     int actual;
3592 
3593 #ifdef DEBUG_APPCH
3594 #ifdef CJK_EX
3595     static unsigned char save_ch = 0;
3596 #endif
3597 
3598     if (TRACE) {
3599 	char *special = NULL;	/* make trace a little more readable */
3600 
3601 	switch (ch) {
3602 	case HT_NON_BREAK_SPACE:
3603 	    special = "HT_NON_BREAK_SPACE";
3604 	    break;
3605 	case HT_EN_SPACE:
3606 	    special = "HT_EN_SPACE";
3607 	    break;
3608 	case LY_UNDERLINE_START_CHAR:
3609 	    special = "LY_UNDERLINE_START_CHAR";
3610 	    break;
3611 	case LY_UNDERLINE_END_CHAR:
3612 	    special = "LY_UNDERLINE_END_CHAR";
3613 	    break;
3614 	case LY_BOLD_START_CHAR:
3615 	    special = "LY_BOLD_START_CHAR";
3616 	    break;
3617 	case LY_BOLD_END_CHAR:
3618 	    special = "LY_BOLD_END_CHAR";
3619 	    break;
3620 	case LY_SOFT_HYPHEN:
3621 	    special = "LY_SOFT_HYPHEN";
3622 	    break;
3623 	case LY_SOFT_NEWLINE:
3624 	    special = "LY_SOFT_NEWLINE";
3625 	    break;
3626 	default:
3627 	    special = NULL;
3628 	    break;
3629 	}
3630 
3631 	if (special != NULL) {
3632 	    CTRACE((tfp, "add(%s %d special char) %d/%d\n", special, ch,
3633 		    HTisDocumentSource(), HTOutputFormat != WWW_SOURCE));
3634 	} else {
3635 #ifdef CJK_EX			/* 1998/08/30 (Sun) 13:26:23 */
3636 	    if (save_ch == 0) {
3637 		if (IS_SJIS_HI1(ch) || IS_SJIS_HI2(ch)) {
3638 		    save_ch = ch;
3639 		} else {
3640 		    CTRACE((tfp, "add(%c) %d/%d\n", ch,
3641 			    HTisDocumentSource(), HTOutputFormat != WWW_SOURCE));
3642 		}
3643 	    } else {
3644 		CTRACE((tfp, "add(%c%c) %d/%d\n", save_ch, ch,
3645 			HTisDocumentSource(), HTOutputFormat != WWW_SOURCE));
3646 		save_ch = 0;
3647 	    }
3648 #else
3649 	    if (UCH(ch) < 0x80) {
3650 		CTRACE((tfp, "add(%c) %d/%d\n", UCH(ch),
3651 			HTisDocumentSource(), HTOutputFormat != WWW_SOURCE));
3652 	    } else {
3653 		CTRACE((tfp, "add(%02x) %d/%d\n", UCH(ch),
3654 			HTisDocumentSource(), HTOutputFormat != WWW_SOURCE));
3655 	    }
3656 #endif /* CJK_EX */
3657 	}
3658     }				/* trace only */
3659 #endif /* DEBUG_APPCH */
3660 
3661     /*
3662      * Make sure we don't crash on NULLs.
3663      */
3664     if (!text)
3665 	return;
3666 
3667     if (text->halted > 1) {
3668 	/*
3669 	 * We should stop outputting more text, because low memory was
3670 	 * detected.  - kw
3671 	 */
3672 	if (text->halted == 2) {
3673 	    /*
3674 	     * But if we haven't done so yet, first append a warning.
3675 	     * We should still have a few bytes left for that :).
3676 	     * We temporarily reset test->halted to 0 for this, since
3677 	     * this function will get called recursively.  - kw
3678 	     */
3679 	    text->halted = 0;
3680 	    text->kanji_buf = '\0';
3681 	    HText_appendText(text, gettext(" *** MEMORY EXHAUSTED ***"));
3682 	}
3683 	text->halted = 3;
3684 	return;
3685     }
3686 #ifdef USE_TH_JP_AUTO_DETECT
3687     if ((HTCJK == JAPANESE) && (text->detected_kcode != DET_MIXED) &&
3688 	(text->specified_kcode != SJIS) && (text->specified_kcode != EUC)) {
3689 	unsigned char c;
3690 	eDetectedKCode save_d_kcode;
3691 
3692 	c = UCH(ch);
3693 	save_d_kcode = text->detected_kcode;
3694 	switch (text->SJIS_status) {
3695 	case SJIS_state_has_bad_code:
3696 	    break;
3697 	case SJIS_state_neutral:
3698 	    if (IS_SJIS_HI1(c) || IS_SJIS_HI2(c)) {
3699 		text->SJIS_status = SJIS_state_in_kanji;
3700 	    } else if ((c & 0x80) && !IS_SJIS_X0201KANA(c)) {
3701 		text->SJIS_status = SJIS_state_has_bad_code;
3702 		if (text->EUC_status == EUC_state_has_bad_code)
3703 		    text->detected_kcode = DET_MIXED;
3704 		else
3705 		    text->detected_kcode = DET_EUC;
3706 	    }
3707 	    break;
3708 	case SJIS_state_in_kanji:
3709 	    if (IS_SJIS_LO(c)) {
3710 		text->SJIS_status = SJIS_state_neutral;
3711 	    } else {
3712 		text->SJIS_status = SJIS_state_has_bad_code;
3713 		if (text->EUC_status == EUC_state_has_bad_code)
3714 		    text->detected_kcode = DET_MIXED;
3715 		else
3716 		    text->detected_kcode = DET_EUC;
3717 	    }
3718 	    break;
3719 	}
3720 	switch (text->EUC_status) {
3721 	case EUC_state_has_bad_code:
3722 	    break;
3723 	case EUC_state_neutral:
3724 	    if (IS_EUC_HI(c)) {
3725 		text->EUC_status = EUC_state_in_kanji;
3726 	    } else if (c == 0x8e) {
3727 		text->EUC_status = EUC_state_in_kana;
3728 	    } else if (c & 0x80) {
3729 		text->EUC_status = EUC_state_has_bad_code;
3730 		if (text->SJIS_status == SJIS_state_has_bad_code)
3731 		    text->detected_kcode = DET_MIXED;
3732 		else
3733 		    text->detected_kcode = DET_SJIS;
3734 	    }
3735 	    break;
3736 	case EUC_state_in_kanji:
3737 	    if (IS_EUC_LOX(c)) {
3738 		text->EUC_status = EUC_state_neutral;
3739 	    } else {
3740 		text->EUC_status = EUC_state_has_bad_code;
3741 		if (text->SJIS_status == SJIS_state_has_bad_code)
3742 		    text->detected_kcode = DET_MIXED;
3743 		else
3744 		    text->detected_kcode = DET_SJIS;
3745 	    }
3746 	    break;
3747 	case EUC_state_in_kana:
3748 	    if ((0xA1 <= c) && (c <= 0xDF)) {
3749 		text->EUC_status = EUC_state_neutral;
3750 	    } else {
3751 		text->EUC_status = EUC_state_has_bad_code;
3752 		if (text->SJIS_status == SJIS_state_has_bad_code)
3753 		    text->detected_kcode = DET_MIXED;
3754 		else
3755 		    text->detected_kcode = DET_SJIS;
3756 	    }
3757 	    break;
3758 	}
3759 	if (save_d_kcode != text->detected_kcode) {
3760 	    switch (text->detected_kcode) {
3761 	    case DET_SJIS:
3762 		CTRACE((tfp,
3763 			"TH_JP_AUTO_DETECT: This document's kcode seems SJIS.\n"));
3764 		break;
3765 	    case DET_EUC:
3766 		CTRACE((tfp,
3767 			"TH_JP_AUTO_DETECT: This document's kcode seems EUC.\n"));
3768 		break;
3769 	    case DET_MIXED:
3770 		CTRACE((tfp,
3771 			"TH_JP_AUTO_DETECT: This document's kcode seems mixed!\n"));
3772 		break;
3773 	    default:
3774 		CTRACE((tfp,
3775 			"TH_JP_AUTO_DETECT: This document's kcode is unexpected!\n"));
3776 		break;
3777 	    }
3778 	}
3779     }
3780 #endif /* USE_TH_JP_AUTO_DETECT */
3781     /*
3782      * Make sure we don't hang on escape sequences.
3783      */
3784     if (ch == CH_ESC && !IS_CJK_TTY) {	/* decimal 27  S/390 -- gil -- 1504 */
3785 	return;
3786     }
3787 #ifndef USE_SLANG
3788     /*
3789      * Block 8-bit chars not allowed by the current display character
3790      * set if they are below what LYlowest_eightbit indicates.
3791      * Slang used its own replacements, so for USE_SLANG blocking here
3792      * is not necessary to protect terminals from those characters.
3793      * They should have been filtered out or translated by an earlier
3794      * processing stage anyway.  - kw
3795      */
3796 #ifndef   EBCDIC		/* S/390 -- gil -- 1514 */
3797     if (is8bits(ch) && !IS_CJK_TTY &&
3798 	!text->T.transp && !text->T.output_utf8 &&
3799 	UCH(ch) < LYlowest_eightbit[current_char_set]) {
3800 	return;
3801     }
3802 #endif /* EBCDIC */
3803 #endif /* !USE_SLANG */
3804     if (UCH(ch) == 155 && !IS_CJK_TTY) {	/* octal 233 */
3805 	if (!HTPassHighCtrlRaw &&
3806 	    !text->T.transp && !text->T.output_utf8 &&
3807 	    (155 < LYlowest_eightbit[current_char_set])) {
3808 	    return;
3809 	}
3810     }
3811 
3812     line = text->last_line;
3813     style = text->style;
3814 
3815     indent = text->in_line_1 ? (int) style->indent1st : (int) style->leftIndent;
3816 
3817     if (IS_CJK_TTY) {
3818 	switch (text->state) {
3819 	case S_text:
3820 	    if (ch == CH_ESC) {	/* S/390 -- gil -- 1536 */
3821 		/*
3822 		 * Setting up for CJK escape sequence handling (based on
3823 		 * Takuya ASADA's (asada@three-a.co.jp) CJK Lynx).  -FM
3824 		 */
3825 		text->state = S_esc;
3826 		text->kanji_buf = '\0';
3827 		return;
3828 	    }
3829 	    break;
3830 
3831 	case S_esc:
3832 	    /*
3833 	     * Expecting '$'or '(' following CJK ESC.
3834 	     */
3835 	    if (ch == '$') {
3836 		text->state = S_dollar;
3837 		return;
3838 	    } else if (ch == '(') {
3839 		text->state = S_paren;
3840 		return;
3841 	    } else {
3842 		text->state = S_text;
3843 	    }
3844 	    /* FALLTHRU */
3845 
3846 	case S_dollar:
3847 	    /*
3848 	     * Expecting '@', 'B', 'A' or '(' after CJK "ESC$".
3849 	     */
3850 	    if (ch == '@' || ch == 'B' || ch == 'A') {
3851 		text->state = S_nonascii_text;
3852 		if (ch == '@' || ch == 'B')
3853 		    text->kcode = JIS;
3854 		return;
3855 	    } else if (ch == '(') {
3856 		text->state = S_dollar_paren;
3857 		return;
3858 	    } else {
3859 		text->state = S_text;
3860 	    }
3861 	    break;
3862 
3863 	case S_dollar_paren:
3864 	    /*
3865 	     * Expecting 'C' after CJK "ESC$(".
3866 	     */
3867 	    if (ch == 'C') {
3868 		text->state = S_nonascii_text;
3869 		return;
3870 	    } else {
3871 		text->state = S_text;
3872 	    }
3873 	    break;
3874 
3875 	case S_paren:
3876 	    /*
3877 	     * Expecting 'B', 'J', 'T' or 'I' after CJK "ESC(".
3878 	     */
3879 	    if (ch == 'B' || ch == 'J' || ch == 'T') {
3880 		/*
3881 		 * Can split here.  -FM
3882 		 */
3883 		text->permissible_split = text->last_line->size;
3884 		text->state = S_text;
3885 		return;
3886 	    } else if (ch == 'I') {
3887 		text->state = S_jisx0201_text;
3888 		/*
3889 		 * Can split here.  -FM
3890 		 */
3891 		text->permissible_split = text->last_line->size;
3892 		text->kcode = JIS;
3893 		return;
3894 	    } else {
3895 		text->state = S_text;
3896 	    }
3897 	    break;
3898 
3899 	case S_nonascii_text:
3900 	    /*
3901 	     * Expecting CJK ESC after non-ASCII text.
3902 	     */
3903 	    if (ch == CH_ESC) {	/* S/390 -- gil -- 1553 */
3904 		text->state = S_esc;
3905 		text->kanji_buf = '\0';
3906 		if (HTCJK == JAPANESE) {
3907 		    text->kcode = NOKANJI;
3908 		}
3909 		return;
3910 	    } else if (UCH(ch) < 32) {
3911 		text->state = S_text;
3912 		text->kanji_buf = '\0';
3913 		if (HTCJK == JAPANESE) {
3914 		    text->kcode = NOKANJI;
3915 		}
3916 	    } else {
3917 		ch |= 0200;
3918 	    }
3919 	    break;
3920 
3921 	    /*
3922 	     * JIS X0201 Kana in JIS support.  - by ASATAKU
3923 	     */
3924 	case S_jisx0201_text:
3925 	    if (ch == CH_ESC) {	/* S/390 -- gil -- 1570 */
3926 		text->state = S_esc;
3927 		text->kanji_buf = '\0';
3928 		text->kcode = NOKANJI;
3929 		return;
3930 	    } else {
3931 		text->kanji_buf = '\216';
3932 		ch |= 0200;
3933 	    }
3934 	    break;
3935 	}			/* end switch */
3936 
3937 	if (!text->kanji_buf) {
3938 	    if ((ch & 0200) != 0) {
3939 		/*
3940 		 * JIS X0201 Kana in SJIS support.  - by ASATAKU
3941 		 */
3942 		if ((text->kcode != JIS)
3943 #ifdef USE_TH_JP_AUTO_DETECT
3944 		    && (text->specified_kcode != EUC)
3945 		    && (text->detected_kcode != DET_EUC)
3946 #endif
3947 		    && (
3948 #ifdef KANJI_CODE_OVERRIDE
3949 			   (last_kcode == SJIS) ||
3950 			   ((last_kcode == NOKANJI) &&
3951 #endif
3952 			    ((text->kcode == SJIS) ||
3953 #ifdef USE_TH_JP_AUTO_DETECT
3954 			     ((text->detected_kcode == DET_SJIS) &&
3955 			      (text->specified_kcode == NOKANJI)) ||
3956 #endif
3957 			     ((text->kcode == NOKANJI) &&
3958 			      (text->specified_kcode == SJIS)))
3959 #ifdef KANJI_CODE_OVERRIDE
3960 			   )
3961 #endif
3962 		    ) &&
3963 		    (UCH(ch) >= 0xA1) &&
3964 		    (UCH(ch) <= 0xDF)) {
3965 		    if (conv_jisx0201kana) {
3966 			unsigned char c = UCH(ch);
3967 			unsigned char kb = UCH(text->kanji_buf);
3968 
3969 			JISx0201TO0208_SJIS(c,
3970 					    (unsigned char *) &kb,
3971 					    (unsigned char *) &c);
3972 			ch = (char) c;
3973 			text->kanji_buf = kb;
3974 		    }
3975 		    /* 1998/01/19 (Mon) 09:06:15 */
3976 		    text->permissible_split = (int) text->last_line->size;
3977 		} else {
3978 		    text->kanji_buf = ch;
3979 		    /*
3980 		     * Can split here.  -FM
3981 		     */
3982 		    text->permissible_split = text->last_line->size;
3983 		    return;
3984 		}
3985 	    }
3986 	} else {
3987 	    goto check_WrapSource;
3988 	}
3989     } else if (ch == CH_ESC) {	/* S/390 -- gil -- 1587 */
3990 	return;
3991     }
3992 #ifdef CJK_EX			/* MOJI-BAKE Fix! 1997/10/12 -- 10/31 (Fri) 00:22:57 - JH7AYN */
3993     if (IS_CJK_TTY &&		/* added condition - kw */
3994 	(ch == LY_BOLD_START_CHAR || ch == LY_BOLD_END_CHAR)) {
3995 	text->permissible_split = (int) line->size;	/* Can split here */
3996 	if (HTCJK == JAPANESE)
3997 	    text->kcode = NOKANJI;
3998     }
3999 #endif
4000 
4001     if (IsSpecialAttrChar(ch) && ch != LY_SOFT_NEWLINE) {
4002 #if !defined(USE_COLOR_STYLE) || !defined(NO_DUMP_WITH_BACKSPACES)
4003 	if (line->size >= (MAX_LINE - 1)) {
4004 	    return;
4005 	}
4006 #if defined(USE_COLOR_STYLE) && !defined(NO_DUMP_WITH_BACKSPACES)
4007 	if (with_backspaces && !IS_CJK_TTY && !text->T.output_utf8) {
4008 #endif
4009 	    if (ch == LY_UNDERLINE_START_CHAR) {
4010 		line->data[line->size++] = LY_UNDERLINE_START_CHAR;
4011 		line->data[line->size] = '\0';
4012 		underline_on = TRUE;
4013 		if (!(dump_output_immediately && use_underscore))
4014 		    ctrl_chars_on_this_line++;
4015 		return;
4016 	    } else if (ch == LY_UNDERLINE_END_CHAR) {
4017 		line->data[line->size++] = LY_UNDERLINE_END_CHAR;
4018 		line->data[line->size] = '\0';
4019 		underline_on = FALSE;
4020 		if (!(dump_output_immediately && use_underscore))
4021 		    ctrl_chars_on_this_line++;
4022 		return;
4023 	    } else if (ch == LY_BOLD_START_CHAR) {
4024 		line->data[line->size++] = LY_BOLD_START_CHAR;
4025 		line->data[line->size] = '\0';
4026 		bold_on = TRUE;
4027 		ctrl_chars_on_this_line++;
4028 		return;
4029 	    } else if (ch == LY_BOLD_END_CHAR) {
4030 		line->data[line->size++] = LY_BOLD_END_CHAR;
4031 		line->data[line->size] = '\0';
4032 		bold_on = FALSE;
4033 		ctrl_chars_on_this_line++;
4034 		return;
4035 	    } else if (ch == LY_SOFT_HYPHEN) {
4036 		int i;
4037 
4038 		/*
4039 		 * Ignore the soft hyphen if it is the first character
4040 		 * on the line, or if it is preceded by a space or
4041 		 * hyphen.  -FM
4042 		 */
4043 		if (line->size < 1 || text->permissible_split >= line->size) {
4044 		    return;
4045 		}
4046 
4047 		for (i = (int) (text->permissible_split + 1);
4048 		     line->data[i];
4049 		     i++) {
4050 		    if (!IsSpecialAttrChar(UCH(line->data[i])) &&
4051 			!isspace(UCH(line->data[i])) &&
4052 			UCH(line->data[i]) != '-' &&
4053 			UCH(line->data[i]) != HT_NON_BREAK_SPACE &&
4054 			UCH(line->data[i]) != HT_EN_SPACE) {
4055 			break;
4056 		    }
4057 		}
4058 		if (line->data[i] == '\0') {
4059 		    return;
4060 		}
4061 	    }
4062 #if defined(USE_COLOR_STYLE) && !defined(NO_DUMP_WITH_BACKSPACES)
4063 	} else {
4064 	    /* if (with_backspaces && HTCJK==HTNOCJK && !text->T.output_utf8) */
4065 	    return;
4066 	}
4067 #endif
4068 
4069 #else
4070 	return;
4071 #endif
4072     } else if (ch == LY_SOFT_NEWLINE) {
4073 	line->data[line->size++] = LY_SOFT_NEWLINE;
4074 	line->data[line->size] = '\0';
4075 	return;
4076     }
4077 
4078     if (text->T.output_utf8) {
4079 	/*
4080 	 * Some extra checks for UTF-8 output here to make sure
4081 	 * memory is not overrun.  For a non-first char, append
4082 	 * to the line here and return.  - kw
4083 	 */
4084 	if (IS_UTF_EXTRA(ch)) {
4085 	    if ((line->size > (MAX_LINE - 1))
4086 		|| (indent + (int) (line->offset + line->size)
4087 		    + UTFXTRA_ON_THIS_LINE
4088 		    - ctrl_chars_on_this_line
4089 		    + ((line->size > 0) &&
4090 		       (int) (line->data[line->size - 1] ==
4091 			      LY_SOFT_HYPHEN ?
4092 			      1 : 0)) >= LYcols_cu(text))
4093 		) {
4094 		if (!text->permissible_split || text->source) {
4095 		    text->permissible_split = line->size;
4096 		    while (text->permissible_split > 0 &&
4097 			   IS_UTF_EXTRA(line->data[text->permissible_split - 1]))
4098 			text->permissible_split--;
4099 		    if (text->permissible_split &&
4100 			(line->data[text->permissible_split - 1] & 0x80))
4101 			text->permissible_split--;
4102 		    if (text->permissible_split == line->size)
4103 			text->permissible_split = 0;
4104 		}
4105 		split_line(text, text->permissible_split);
4106 		line = text->last_line;
4107 		if (text->source && line->size - ctrl_chars_on_this_line
4108 		    + UTFXTRA_ON_THIS_LINE == 0)
4109 		    HText_appendCharacter(text, LY_SOFT_NEWLINE);
4110 	    }
4111 	    line->data[line->size++] = (char) ch;
4112 	    line->data[line->size] = '\0';
4113 	    utfxtra_on_this_line++;
4114 	    ctrl_chars_on_this_line++;
4115 	    return;
4116 	} else if (ch & 0x80) {	/* a first char of UTF-8 sequence - kw */
4117 	    if ((line->size > (MAX_LINE - 7))) {
4118 		if (!text->permissible_split || text->source) {
4119 		    text->permissible_split = line->size;
4120 		    while (text->permissible_split > 0 &&
4121 			   (line->data[text->permissible_split - 1] & 0xc0)
4122 			   == 0x80) {
4123 			text->permissible_split--;
4124 		    }
4125 		    if (text->permissible_split == line->size)
4126 			text->permissible_split = 0;
4127 		}
4128 		split_line(text, text->permissible_split);
4129 		line = text->last_line;
4130 		if (text->source && line->size - ctrl_chars_on_this_line
4131 		    + UTFXTRA_ON_THIS_LINE == 0)
4132 		    HText_appendCharacter(text, LY_SOFT_NEWLINE);
4133 	    }
4134 	}
4135     }
4136 
4137     /*
4138      * New Line.
4139      */
4140     if (ch == '\n') {
4141 	new_line(text);
4142 	text->in_line_1 = YES;	/* First line of new paragraph */
4143 	/*
4144 	 * There are some pages written in
4145 	 * different kanji codes.  - TA & kw
4146 	 */
4147 	if (HTCJK == JAPANESE)
4148 	    text->kcode = NOKANJI;
4149 	return;
4150     }
4151 
4152     /*
4153      * Convert EN_SPACE to a space here so that it doesn't get collapsed.
4154      */
4155     if (ch == HT_EN_SPACE)
4156 	ch = ' ';
4157 
4158 #ifdef SH_EX			/* 1997/11/01 (Sat) 12:08:54 */
4159     if (ch == 0x0b) {		/* ^K ??? */
4160 	ch = '\r';
4161     }
4162     if (ch == 0x1a) {		/* ^Z ??? */
4163 	ch = '\r';
4164     }
4165 #endif
4166 
4167     /*
4168      * I'm going to cheat here in a BIG way.  Since I know that all
4169      * \r's will be trapped by HTML_put_character I'm going to use
4170      * \r to mean go down a line but don't start a new paragraph.
4171      * i.e., use the second line indenting.
4172      */
4173     if (ch == '\r') {
4174 	new_line(text);
4175 	text->in_line_1 = NO;
4176 	/*
4177 	 * There are some pages written in
4178 	 * different kanji codes.  - TA & kw
4179 	 */
4180 	if (HTCJK == JAPANESE)
4181 	    text->kcode = NOKANJI;
4182 	return;
4183     }
4184 
4185     /*
4186      * Tabs.
4187      */
4188     if (ch == '\t') {
4189 	const HTTabStop *Tab;
4190 	int target, target_cu;	/* Where to tab to */
4191 	int here, here_cu;	/* in _cu we try to guess what curses thinks */
4192 
4193 	if (line->size > 0 && line->data[line->size - 1] == LY_SOFT_HYPHEN) {
4194 	    /*
4195 	     * A tab shouldn't follow a soft hyphen, so
4196 	     * if one does, we'll dump the soft hyphen.  -FM
4197 	     */
4198 	    line->data[--line->size] = '\0';
4199 	    ctrl_chars_on_this_line--;
4200 	}
4201 	here = ((int) (line->size + line->offset) + indent)
4202 	    - ctrl_chars_on_this_line;	/* Consider special chars GAB */
4203 	here_cu = here + UTFXTRA_ON_THIS_LINE;
4204 	if (style->tabs) {	/* Use tab table */
4205 	    for (Tab = style->tabs;
4206 		 Tab->position <= here;
4207 		 Tab++) {
4208 		if (!Tab->position) {
4209 		    new_line(text);
4210 		    return;
4211 		}
4212 	    }
4213 	    target = Tab->position;
4214 	} else if (text->in_line_1) {	/* Use 2nd indent */
4215 	    if (here >= (int) style->leftIndent) {
4216 		new_line(text);	/* wrap */
4217 		return;
4218 	    } else {
4219 		target = (int) style->leftIndent;
4220 	    }
4221 	} else {		/* Default tabs align with left indent mod 8 */
4222 #ifdef DEFAULT_TABS_8
4223 	    target = (((int) line->offset + (int) line->size + 8) & (-8))
4224 		+ (int) style->leftIndent;
4225 #else
4226 	    new_line(text);
4227 	    return;
4228 #endif
4229 	}
4230 
4231 	if (target >= here)
4232 	    target_cu = target;
4233 	else
4234 	    target_cu = target + (here_cu - here);
4235 
4236 	if (target > WRAP_COLS(text) - (int) style->rightIndent &&
4237 	    HTOutputFormat != WWW_SOURCE) {
4238 	    new_line(text);
4239 	} else {
4240 	    /*
4241 	     * Can split here.  -FM
4242 	     */
4243 	    text->permissible_split = line->size;
4244 	    if (target_cu > WRAP_COLS(text))
4245 		target -= target_cu - WRAP_COLS(text);
4246 	    if (line->size == 0) {
4247 		line->offset = (unsigned short) (line->offset + (target - here));
4248 	    } else {
4249 		for (; here < target; here++) {
4250 		    /* Put character into line */
4251 		    line->data[line->size++] = ' ';
4252 		    line->data[line->size] = '\0';
4253 		}
4254 	    }
4255 	}
4256 	return;
4257     }
4258     /* if tab */
4259   check_WrapSource:
4260     if ((text->source || dont_wrap_pre) && text == HTMainText) {
4261 	/*
4262 	 * If we're displaying document source, wrap long lines to keep all of
4263 	 * the source visible.
4264 	 */
4265 	int target = (int) (line->offset + line->size) - ctrl_chars_on_this_line;
4266 	int target_cu = target + UTFXTRA_ON_THIS_LINE;
4267 
4268 	if (target >= WRAP_COLS(text) - style->rightIndent -
4269 	    ((IS_CJK_TTY && text->kanji_buf) ? 1 : 0) ||
4270 	    (text->T.output_utf8 &&
4271 	     target_cu + UTF_XLEN(ch) >= LYcols_cu(text))) {
4272 	    int saved_kanji_buf;
4273 	    eGridState saved_state;
4274 	    BOOL add_blank = (dont_wrap_pre
4275 			      && line->size
4276 			      && (line->data[line->size - 1] == ' '));
4277 
4278 	    new_line(text);
4279 	    line = text->last_line;
4280 
4281 	    saved_kanji_buf = text->kanji_buf;
4282 	    saved_state = text->state;
4283 	    text->kanji_buf = '\0';
4284 	    text->state = S_text;
4285 	    HText_appendCharacter(text, LY_SOFT_NEWLINE);
4286 	    if (add_blank)
4287 		HText_appendCharacter(text, ' ');
4288 	    text->kanji_buf = saved_kanji_buf;
4289 	    text->state = saved_state;
4290 	}
4291     }
4292 
4293     if (ch == ' ') {
4294 	/*
4295 	 * Can split here.  -FM
4296 	 */
4297 	text->permissible_split = text->last_line->size;
4298 	/*
4299 	 * There are some pages written in
4300 	 * different kanji codes.  - TA
4301 	 */
4302 	if (HTCJK == JAPANESE)
4303 	    text->kcode = NOKANJI;
4304     }
4305 
4306     /*
4307      * Check for end of line.
4308      *
4309      * Notes:
4310      * 1) text->permissible_split is nonzero if we found a place to split the
4311      *    line.  If there is no such place, we still will wrap at the display
4312      *    limits (the comparison against LYcols_cu).  Furthermore, if the
4313      *    curses-pads feature is active, we will ignore the first comparison
4314      *    (against WRAP_COLS) to allow wide preformatted text to be displayed
4315      *    without wrapping.
4316      * 2) ctrl_chars_on_this_line are nonprintable bytes used for formatting.
4317      */
4318     actual = ((indent + (int) line->offset + (int) line->size) +
4319 	      ((line->size > 0) &&
4320 	       (int) (line->data[line->size - 1] == LY_SOFT_HYPHEN ? 1 : 0))
4321 	      - ctrl_chars_on_this_line);
4322 
4323     if ((
4324 #if !defined(USE_SLANG) && !defined(PDCURSES)
4325 	    (text->permissible_split
4326 #ifdef USE_CURSES_PADS
4327 	     || !LYwideLines
4328 #endif
4329 	    ) &&
4330 #endif
4331 	    (actual
4332 	     + (int) style->rightIndent
4333 	     + ((IS_CJK_TTY && text->kanji_buf) ? 1 : 0)
4334 	    ) >= WRAP_COLS(text))
4335 	|| (text->T.output_utf8
4336 	    && ((actual
4337 		 + UTFXTRA_ON_THIS_LINE
4338 		 + UTF_XLEN(ch)
4339 		) > (LYcols_cu(text) - 1)))) {
4340 
4341 	if (style->wordWrap && HTOutputFormat != WWW_SOURCE) {
4342 #ifdef USE_JUSTIFY_ELTS
4343 	    if (REALLY_CAN_JUSTIFY(text))
4344 		this_line_was_split = TRUE;
4345 #endif
4346 	    split_line(text, text->permissible_split);
4347 	    if (ch == ' ') {
4348 		return;		/* Ignore space causing split */
4349 	    }
4350 
4351 	} else if (HTOutputFormat == WWW_SOURCE) {
4352 	    /*
4353 	     * For source output we don't want to wrap this stuff
4354 	     * unless absolutely necessary.  - LJM
4355 	     * !
4356 	     * If we don't wrap here we might get a segmentation fault.
4357 	     * but let's see what happens
4358 	     */
4359 	    if ((int) line->size >= (int) (MAX_LINE - 1)) {
4360 		new_line(text);	/* try not to linewrap */
4361 	    }
4362 	} else {
4363 	    /*
4364 	     * For normal stuff like pre let's go ahead and
4365 	     * wrap so the user can see all of the text.
4366 	     */
4367 	    if ((dump_output_immediately || (crawl && traversal))
4368 		&& dont_wrap_pre) {
4369 		if ((int) line->size >= (int) (MAX_LINE - 1)) {
4370 		    new_line(text);
4371 		}
4372 	    } else {
4373 		new_line(text);
4374 	    }
4375 	}
4376     } else if ((int) line->size >= (int) (MAX_LINE - 1)) {
4377 	/*
4378 	 * Never overrun memory if DISPLAY_COLS is set to a large value - KW
4379 	 */
4380 	new_line(text);
4381     }
4382 
4383     /*
4384      * Insert normal characters.
4385      */
4386     if (ch == HT_NON_BREAK_SPACE
4387 #ifdef USE_JUSTIFY_ELTS
4388 	&& !REALLY_CAN_JUSTIFY(text)
4389 #endif
4390 	)
4391 	ch = ' ';
4392 #ifdef USE_JUSTIFY_ELTS
4393     else
4394 	have_raw_nbsps = TRUE;
4395 #endif
4396 
4397     /* we leave raw HT_NON_BREAK_SPACE otherwise (we'll substitute it later) */
4398 
4399     if (ch & 0x80)
4400 	text->have_8bit_chars = YES;
4401 
4402     /*
4403      * Kanji charactor handling.
4404      */
4405     {
4406 	HTFont font = style->font;
4407 	unsigned char hi, lo, tmp[2];
4408 
4409 	line = text->last_line;	/* May have changed */
4410 
4411 	if (IS_CJK_TTY && text->kanji_buf) {
4412 	    hi = UCH(text->kanji_buf);
4413 	    lo = UCH(ch);
4414 
4415 	    if (HTCJK == JAPANESE) {
4416 		if (text->kcode != JIS) {
4417 		    if (IS_SJIS_2BYTE(hi, lo)) {
4418 			if (IS_EUC(hi, lo)) {
4419 #ifdef KANJI_CODE_OVERRIDE
4420 			    if (last_kcode != NOKANJI)
4421 				text->kcode = last_kcode;
4422 			    else
4423 #endif
4424 			    if (text->specified_kcode != NOKANJI)
4425 				text->kcode = text->specified_kcode;
4426 #ifdef USE_TH_JP_AUTO_DETECT
4427 			    else if (text->detected_kcode == DET_EUC)
4428 				text->kcode = EUC;
4429 			    else if (text->detected_kcode == DET_SJIS)
4430 				text->kcode = SJIS;
4431 #endif
4432 			    else if (IS_EUC_X0201KANA(hi, lo) &&
4433 				     (text->kcode != EUC))
4434 				text->kcode = SJIS;
4435 			} else
4436 			    text->kcode = SJIS;
4437 		    } else if (IS_EUC(hi, lo))
4438 			text->kcode = EUC;
4439 		    else
4440 			text->kcode = NOKANJI;
4441 		}
4442 
4443 		switch (kanji_code) {
4444 		case EUC:
4445 		    if (text->kcode == SJIS) {
4446 			SJIS_TO_EUC1(hi, lo, tmp);
4447 			line->data[line->size++] = (char) tmp[0];
4448 			line->data[line->size++] = (char) tmp[1];
4449 		    } else if (IS_EUC(hi, lo)) {
4450 			if (conv_jisx0201kana) {
4451 			    JISx0201TO0208_EUC(hi, lo, &hi, &lo);
4452 			}
4453 			line->data[line->size++] = (char) hi;
4454 			line->data[line->size++] = (char) lo;
4455 		    } else {
4456 			CTRACE((tfp,
4457 				"This character (%X:%X) doesn't seem Japanese\n",
4458 				hi, lo));
4459 			line->data[line->size++] = '=';
4460 			line->data[line->size++] = '=';
4461 		    }
4462 		    break;
4463 
4464 		case SJIS:
4465 		    if ((text->kcode == EUC) || (text->kcode == JIS)) {
4466 			if (!conv_jisx0201kana && IS_EUC_X0201KANA(hi, lo))
4467 			    line->data[line->size++] = (char) lo;
4468 			else {
4469 			    EUC_TO_SJIS1(hi, lo, tmp);
4470 			    line->data[line->size++] = (char) tmp[0];
4471 			    line->data[line->size++] = (char) tmp[1];
4472 			}
4473 		    } else if (IS_SJIS_2BYTE(hi, lo)) {
4474 			line->data[line->size++] = (char) hi;
4475 			line->data[line->size++] = (char) lo;
4476 		    } else {
4477 			line->data[line->size++] = '=';
4478 			line->data[line->size++] = '=';
4479 			CTRACE((tfp,
4480 				"This character (%X:%X) doesn't seem Japanese\n",
4481 				hi, lo));
4482 		    }
4483 		    break;
4484 
4485 		default:
4486 		    break;
4487 		}
4488 	    } else {
4489 		line->data[line->size++] = (char) hi;
4490 		line->data[line->size++] = (char) lo;
4491 	    }
4492 	    text->kanji_buf = 0;
4493 	} else if (!conv_jisx0201kana
4494 		   && (HTCJK == JAPANESE)
4495 		   && IS_SJIS_X0201KANA(UCH((ch))) &&
4496 		   (kanji_code == EUC)) {
4497 	    line->data[line->size++] = (char) UCH(0x8e);
4498 	    line->data[line->size++] = (char) ch;
4499 	} else if (IS_CJK_TTY) {
4500 	    line->data[line->size++] = (char) ((kanji_code != NOKANJI) ?
4501 					       ch :
4502 					       (font & HT_CAPITALS) ?
4503 					       TOUPPER(ch) : ch);
4504 	} else {
4505 	    line->data[line->size++] =	/* Put character into line */
4506 		(char) (font & HT_CAPITALS ? TOUPPER(ch) : ch);
4507 	}
4508 	line->data[line->size] = '\0';
4509 	if (font & HT_DOUBLE)	/* Do again if doubled */
4510 	    HText_appendCharacter(text, HT_NON_BREAK_SPACE);
4511 	/* NOT a permissible split */
4512 
4513 	if (ch == LY_SOFT_HYPHEN) {
4514 	    ctrl_chars_on_this_line++;
4515 	    /*
4516 	     * Can split here.  -FM
4517 	     */
4518 	    text->permissible_split = text->last_line->size;
4519 	}
4520 	if (ch == LY_SOFT_NEWLINE) {
4521 	    ctrl_chars_on_this_line++;
4522 	}
4523     }
4524     return;
4525 }
4526 
4527 #ifdef USE_COLOR_STYLE
4528 /*  Insert a style change into the current line
4529  *  -------------------------------------------
4530  */
_internal_HTC(HText * text,int style,int dir)4531 void _internal_HTC(HText *text, int style, int dir)
4532 {
4533     HTLine *line;
4534 
4535     /* can't change style if we have no text to change style with */
4536     if (text != 0) {
4537 
4538 	line = text->last_line;
4539 
4540 	if (line->numstyles > 0 && dir == 0 &&
4541 	    line->styles[line->numstyles - 1].sc_direction &&
4542 	    line->styles[line->numstyles - 1].sc_style == (unsigned) style &&
4543 	    (int) line->styles[line->numstyles - 1].sc_horizpos
4544 	    == (int) line->size - ctrl_chars_on_this_line) {
4545 	    /*
4546 	     * If this is an OFF change directly preceded by an
4547 	     * ON for the same style, just remove the previous one.  - kw
4548 	     */
4549 	    line->numstyles--;
4550 	} else if (line->numstyles < MAX_STYLES_ON_LINE) {
4551 	    line->styles[line->numstyles].sc_horizpos = CAST_POS(line->size);
4552 	    /*
4553 	     * Special chars for bold and underlining usually don't
4554 	     * occur with color style, but soft hyphen can.
4555 	     * And in UTF-8 display mode all non-initial bytes are
4556 	     * counted as ctrl_chars.  - kw
4557 	     */
4558 	    if ((int) line->styles[line->numstyles].sc_horizpos >= ctrl_chars_on_this_line) {
4559 		line->styles[line->numstyles].sc_horizpos =
4560 		    CAST_POS(line->styles[line->numstyles].sc_horizpos
4561 			     - ctrl_chars_on_this_line);
4562 	    }
4563 	    line->styles[line->numstyles].sc_style = (unsigned short) style;
4564 	    line->styles[line->numstyles].sc_direction = CAST_DIR(dir);
4565 	    CTRACE_STYLE((tfp, "internal_HTC %d:style[%d] %d (dir=%d)\n",
4566 			  line->size,
4567 			  line->numstyles,
4568 			  style,
4569 			  dir));
4570 	    line->numstyles++;
4571 	}
4572     }
4573 }
4574 #endif
4575 
4576 /*	Set LastChar element in the text object.
4577  *	----------------------------------------
4578  */
HText_setLastChar(HText * text,int ch)4579 void HText_setLastChar(HText *text, int ch)
4580 {
4581     if (!text)
4582 	return;
4583 
4584     text->LastChar = (char) ch;
4585 }
4586 
4587 /*	Get LastChar element in the text object.
4588  *	----------------------------------------
4589  */
HText_getLastChar(HText * text)4590 char HText_getLastChar(HText *text)
4591 {
4592     if (!text)
4593 	return ('\0');
4594 
4595     return ((char) text->LastChar);
4596 }
4597 
4598 /*		Simple table handling - private
4599  *		-------------------------------
4600  */
4601 
4602 /*
4603  * HText_insertBlanksInStblLines fixes up table lines when simple table
4604  * processing is closed, by calling insert_blanks_in_line for lines
4605  * that need fixup.  Also recalculates alignment for those lines,
4606  * does additional updating of anchor positions, and makes sure the
4607  * display of the lines on screen will be updated after partial display
4608  * upon return to mainloop.  - kw
4609  */
HText_insertBlanksInStblLines(HText * me,int ncols)4610 static int HText_insertBlanksInStblLines(HText *me, int ncols)
4611 {
4612     HTLine *line;
4613     HTLine *mod_line, *first_line = NULL;
4614     int *oldpos;
4615     int *newpos;
4616     int ninserts, lineno;
4617     int last_lineno, first_lineno_pass2;
4618 
4619 #ifdef EXP_NESTED_TABLES
4620     int last_nonempty = -1;
4621 #endif
4622     int added_chars_before = 0;
4623     int lines_changed = 0;
4624     int max_width = 0, indent, spare, table_offset;
4625     HTStyle *style;
4626     short alignment;
4627     int i = 0;
4628 
4629     lineno = Stbl_getStartLine(me->stbl);
4630     if (lineno < 0 || lineno > me->Lines)
4631 	return -1;
4632     /*
4633      * oldpos, newpos:  allocate space for two int arrays.
4634      */
4635     oldpos = typecallocn(int, 2 * (size_t)ncols);
4636     if (!oldpos)
4637 	return -1;
4638     else
4639 	newpos = oldpos + ncols;
4640     for (line = FirstHTLine(me); i < lineno; line = line->next, i++) {
4641 	if (!line) {
4642 	    free(oldpos);
4643 	    return -1;
4644 	}
4645     }
4646     first_lineno_pass2 = last_lineno = me->Lines;
4647     for (; line && lineno <= last_lineno; line = line->next, lineno++) {
4648 	ninserts = Stbl_getFixupPositions(me->stbl, lineno, oldpos, newpos);
4649 	if (ninserts < 0)
4650 	    continue;
4651 	if (!first_line) {
4652 	    first_line = line;
4653 	    first_lineno_pass2 = lineno;
4654 	    if (TRACE) {
4655 		int ip;
4656 
4657 		CTRACE((tfp, "line %d first to adjust  --  newpos:", lineno));
4658 		for (ip = 0; ip < ncols; ip++)
4659 		    CTRACE((tfp, " %d", newpos[ip]));
4660 		CTRACE((tfp, "\n"));
4661 	    }
4662 	}
4663 	if (line == me->last_line) {
4664 	    if (line->size == 0 || HText_TrueEmptyLine(line, me, FALSE))
4665 		continue;
4666 	    /*
4667 	     * Last ditch effort to end the table with a line break,
4668 	     * if HTML_end_element didn't do it.  - kw
4669 	     */
4670 	    if (first_line == line)	/* obscure: all table on last line... */
4671 		first_line = NULL;
4672 	    new_line(me);
4673 	    line = me->last_line->prev;
4674 	    if (first_line == NULL)
4675 		first_line = line;
4676 	}
4677 	if (ninserts == 0) {
4678 	    /*  Do it also for no positions (but not error) */
4679 	    int width = HText_TrueLineSize(line, me, FALSE);
4680 
4681 	    if (width > max_width)
4682 		max_width = width;
4683 #ifdef EXP_NESTED_TABLES
4684 	    if (nested_tables) {
4685 		if (width && last_nonempty < lineno)
4686 		    last_nonempty = lineno;
4687 	    }
4688 #endif
4689 	    CTRACE((tfp, "line %d true/max width:%d/%d oldpos: NONE\n",
4690 		    lineno, width, max_width));
4691 	    continue;
4692 	}
4693 	mod_line = insert_blanks_in_line(line, lineno, me,
4694 					 &me->last_anchor_before_stbl /*updates++ */ ,
4695 					 ninserts, oldpos, newpos);
4696 	if (mod_line) {
4697 	    if (line == me->last_line) {
4698 		me->last_line = mod_line;
4699 	    } else {
4700 		added_chars_before += (mod_line->size - line->size);
4701 	    }
4702 	    line->prev->next = mod_line;
4703 	    line->next->prev = mod_line;
4704 	    lines_changed++;
4705 	    if (line == first_line)
4706 		first_line = mod_line;
4707 	    freeHTLine(me, line);
4708 	    line = mod_line;
4709 #ifdef DISP_PARTIAL
4710 	    /*
4711 	     * Make sure modified lines get fully re-displayed after
4712 	     * loading with partial display is done.
4713 	     */
4714 	    if (me->first_lineno_last_disp_partial >= 0) {
4715 		if (me->first_lineno_last_disp_partial >= lineno) {
4716 		    ResetPartialLinenos(me);
4717 		} else if (me->last_lineno_last_disp_partial >= lineno) {
4718 		    me->last_lineno_last_disp_partial = lineno - 1;
4719 		}
4720 	    }
4721 #endif
4722 	} {
4723 	    int width = HText_TrueLineSize(line, me, FALSE);
4724 
4725 	    if (width > max_width)
4726 		max_width = width;
4727 #ifdef EXP_NESTED_TABLES
4728 	    if (nested_tables) {
4729 		if (width && last_nonempty < lineno)
4730 		    last_nonempty = lineno;
4731 	    }
4732 #endif
4733 	    if (TRACE) {
4734 		int ip;
4735 
4736 		CTRACE((tfp, "line %d true/max width:%d/%d oldpos:",
4737 			lineno, width, max_width));
4738 		for (ip = 0; ip < ninserts; ip++)
4739 		    CTRACE((tfp, " %d", oldpos[ip]));
4740 		CTRACE((tfp, "\n"));
4741 	    }
4742 	}
4743     }
4744     /*
4745      * Line offsets have been set based on the paragraph style, and
4746      * have already been updated for centering or right-alignment
4747      * for each line in split_line.  Here we want to undo all that, and
4748      * align the table as a whole (i.e.  all lines for which
4749      * Stbl_getFixupPositions returned >= 0).  All those lines have to
4750      * get the same offset, for the simple table formatting mechanism
4751      * to make sense, and that may not actually be the case at this point.
4752      *
4753      * What indentation and alignment do we want for the table as
4754      * a whole?  Let's take most style properties from me->style.
4755      * With some luck, it is the appropriate style for the element
4756      * enclosing the TABLE.  But let's take alignment from the attribute
4757      * of the TABLE itself instead, if it was specified.
4758      *
4759      * Note that this logic assumes that all lines have been finished
4760      * by split_line.  The order of calls made by HTML_end_element for
4761      * HTML_TABLE should take care of this.
4762      */
4763     style = me->style;
4764     alignment = Stbl_getAlignment(me->stbl);
4765     if (alignment == HT_ALIGN_NONE)
4766 	alignment = style->alignment;
4767     indent = style->leftIndent;
4768     /* Calculate spare character positions */
4769     spare = WRAP_COLS(me) -
4770 	(int) style->rightIndent - indent - max_width;
4771     if (spare < 0 && (int) style->rightIndent + spare >= 0) {
4772 	/*
4773 	 * Not enough room!  But we can fit if we ignore right indentation,
4774 	 * so let's do that.
4775 	 */
4776 	spare = 0;
4777     } else if (spare < 0) {
4778 	spare += style->rightIndent;	/* ignore right indent, but need more */
4779     }
4780     if (spare < 0 && indent + spare >= 0) {
4781 	/*
4782 	 * Still not enough room.  But we can move to the left.
4783 	 */
4784 	indent += spare;
4785 	spare = 0;
4786     } else if (spare < 0) {
4787 	/*
4788 	 * Still not enough.  Something went wrong.  Try the best we
4789 	 * can do.
4790 	 */
4791 	CTRACE((tfp,
4792 		"BUG: insertBlanks: resulting table too wide by %d positions!\n",
4793 		-spare));
4794 	indent = spare = 0;
4795     }
4796     /*
4797      * Align left, right or center.
4798      */
4799     switch (alignment) {
4800     case HT_CENTER:
4801 	table_offset = indent + spare / 2;
4802 	break;
4803     case HT_RIGHT:
4804 	table_offset = indent + spare;
4805 	break;
4806     case HT_LEFT:
4807     case HT_JUSTIFY:
4808     default:
4809 	table_offset = indent;
4810 	break;
4811     }				/* switch */
4812 
4813     CTRACE((tfp, "changing offsets"));
4814     for (line = first_line, lineno = first_lineno_pass2;
4815 	 line && lineno <= last_lineno && line != me->last_line;
4816 	 line = line->next, lineno++) {
4817 	ninserts = Stbl_getFixupPositions(me->stbl, lineno, oldpos, newpos);
4818 	if (ninserts >= 0 && (int) line->offset != table_offset) {
4819 #ifdef DISP_PARTIAL
4820 	    /*  As above make sure modified lines get fully re-displayed */
4821 	    if (me->first_lineno_last_disp_partial >= 0) {
4822 		if (me->first_lineno_last_disp_partial >= lineno) {
4823 		    ResetPartialLinenos(me);
4824 		} else if (me->last_lineno_last_disp_partial >= lineno) {
4825 		    me->last_lineno_last_disp_partial = lineno - 1;
4826 		}
4827 	    }
4828 #endif
4829 	    CTRACE((tfp, " %d:%d", lineno, table_offset - line->offset));
4830 	    line->offset = (unsigned short) (table_offset > 0
4831 					     ? table_offset
4832 					     : 0);
4833 	}
4834     }
4835 #ifdef EXP_NESTED_TABLES
4836     if (nested_tables) {
4837 	if (max_width)
4838 	    Stbl_update_enclosing(me->stbl, max_width, last_nonempty);
4839     }
4840 #endif
4841     CTRACE((tfp, " %d:done\n", lineno));
4842     free(oldpos);
4843     return lines_changed;
4844 }
4845 
4846 /*		Simple table handling - public functions
4847  *		----------------------------------------
4848  */
4849 
4850 /*	Cancel simple table handling
4851 */
HText_cancelStbl(HText * me)4852 void HText_cancelStbl(HText *me)
4853 {
4854     if (!me || !me->stbl) {
4855 	CTRACE((tfp, "cancelStbl: ignored.\n"));
4856 	return;
4857     }
4858     CTRACE((tfp, "cancelStbl: ok, will do.\n"));
4859 #ifdef EXP_NESTED_TABLES
4860     if (nested_tables) {
4861 	STable_info *stbl = me->stbl;
4862 
4863 	while (stbl) {
4864 	    STable_info *enclosing = Stbl_get_enclosing(stbl);
4865 
4866 	    Stbl_free(stbl);
4867 	    stbl = enclosing;
4868 	}
4869     } else
4870 #endif
4871 	Stbl_free(me->stbl);
4872     me->stbl = NULL;
4873 }
4874 
4875 /*	Start simple table handling
4876 */
HText_startStblTABLE(HText * me,int alignment)4877 void HText_startStblTABLE(HText *me, int alignment)
4878 {
4879     if (me) {
4880 #ifdef EXP_NESTED_TABLES
4881 	STable_info *current = me->stbl;
4882 #endif
4883 
4884 #ifdef EXP_NESTED_TABLES
4885 	if (nested_tables) {
4886 	    if (current)
4887 		new_line(me);
4888 	} else
4889 #endif
4890 	{
4891 	    if (me->stbl) {
4892 		HText_cancelStbl(me);	/* auto cancel previously open table */
4893 	    }
4894 	}
4895 
4896 	me->stbl = Stbl_startTABLE(alignment);
4897 	if (me->stbl) {
4898 	    CTRACE((tfp, "startStblTABLE: started.\n"));
4899 #ifdef EXP_NESTED_TABLES
4900 	    if (nested_tables) {
4901 		Stbl_set_enclosing(me->stbl, current, me->last_anchor_before_stbl);
4902 	    }
4903 #endif
4904 	    me->last_anchor_before_stbl = me->last_anchor;
4905 	} else {
4906 	    CTRACE((tfp, "startStblTABLE: failed.\n"));
4907 	}
4908     }
4909 }
4910 
4911 #ifdef EXP_NESTED_TABLES
free_enclosed_stbl(HText * me)4912 static void free_enclosed_stbl(HText *me)
4913 {
4914     if (me != NULL && me->enclosed_stbl != NULL) {
4915 	HTList *list = me->enclosed_stbl;
4916 	STable_info *stbl;
4917 
4918 	while (NULL != (stbl = (STable_info *) HTList_nextObject(list))) {
4919 	    CTRACE((tfp, "endStblTABLE: finally free %p\n", (void *) me->stbl));
4920 	    Stbl_free(stbl);
4921 	}
4922 	HTList_delete(me->enclosed_stbl);
4923 	me->enclosed_stbl = NULL;
4924     }
4925 }
4926 
4927 #else
4928 #define free_enclosed_stbl(me)	/* nothing */
4929 #endif
4930 
4931 /*	Finish simple table handling
4932  *	Return TRUE if the table is nested inside another table.
4933  */
HText_endStblTABLE(HText * me)4934 BOOLEAN HText_endStblTABLE(HText *me)
4935 {
4936     int ncols, lines_changed = 0;
4937     STable_info *enclosing = NULL;
4938 
4939     if (!me || !me->stbl) {
4940 	CTRACE((tfp, "endStblTABLE: ignored.\n"));
4941 	free_enclosed_stbl(me);
4942 	return FALSE;
4943     }
4944     CTRACE((tfp, "endStblTABLE: ok, will try.\n"));
4945 
4946     ncols = Stbl_finishTABLE(me->stbl);
4947     CTRACE((tfp, "endStblTABLE: ncols = %d.\n", ncols));
4948 
4949     if (ncols > 0) {
4950 	lines_changed = HText_insertBlanksInStblLines(me, ncols);
4951 	CTRACE((tfp, "endStblTABLE: changed %d lines, done.\n", lines_changed));
4952 #ifdef DISP_PARTIAL
4953 	/* allow HTDisplayPartial() to redisplay the changed lines.
4954 	 * There is no harm if we got several stbl in the document, hope so.
4955 	 */
4956 	NumOfLines_partial -= lines_changed;	/* fake */
4957 #endif /* DISP_PARTIAL */
4958     }
4959 #ifdef EXP_NESTED_TABLES
4960     if (nested_tables) {
4961 	enclosing = Stbl_get_enclosing(me->stbl);
4962 	me->last_anchor_before_stbl = Stbl_get_last_anchor_before(me->stbl);
4963 	if (enclosing == NULL) {
4964 	    Stbl_free(me->stbl);
4965 	    free_enclosed_stbl(me);
4966 	} else {
4967 	    if (me->enclosed_stbl == NULL)
4968 		me->enclosed_stbl = HTList_new();
4969 	    HTList_addObject(me->enclosed_stbl, me->stbl);
4970 	    CTRACE((tfp, "endStblTABLE: postpone free %p\n", (void *) me->stbl));
4971 	}
4972 	me->stbl = enclosing;
4973     } else {
4974 	Stbl_free(me->stbl);
4975 	me->stbl = NULL;
4976     }
4977 #else
4978     Stbl_free(me->stbl);
4979     me->stbl = NULL;
4980 #endif
4981 
4982     CTRACE((tfp, "endStblTABLE: have%s enclosing table (%p)\n",
4983 	    enclosing == 0 ? " NO" : "", (void *) enclosing));
4984 
4985     return (BOOLEAN) (enclosing != 0);
4986 }
4987 
4988 /*	Start simple table row
4989 */
HText_startStblTR(HText * me,int alignment)4990 void HText_startStblTR(HText *me, int alignment)
4991 {
4992     if (!me || !me->stbl)
4993 	return;
4994     if (Stbl_addRowToTable(me->stbl, alignment, me->Lines) < 0) {
4995 	HText_cancelStbl(me);	/* give up */
4996     }
4997 }
4998 
4999 /*	Finish simple table row
5000 */
HText_endStblTR(HText * me)5001 void HText_endStblTR(HText *me)
5002 {
5003     if (!me || !me->stbl)
5004 	return;
5005     /* should this do something?? */
5006 }
5007 
5008 /*	Start simple table cell
5009 */
HText_startStblTD(HText * me,int colspan,int rowspan,int alignment,int isheader)5010 void HText_startStblTD(HText *me, int colspan,
5011 		       int rowspan,
5012 		       int alignment,
5013 		       int isheader)
5014 {
5015     if (!me || !me->stbl)
5016 	return;
5017     if (colspan < 0)
5018 	colspan = 1;
5019     if (colspan > TRST_MAXCOLSPAN) {
5020 	CTRACE((tfp, "*** COLSPAN=%d is too large, ignored!\n", colspan));
5021 	colspan = 1;
5022     }
5023     if (rowspan > TRST_MAXROWSPAN) {
5024 	CTRACE((tfp, "*** ROWSPAN=%d is too large, ignored!\n", rowspan));
5025 	rowspan = 1;
5026     }
5027     if (Stbl_addCellToTable(me->stbl, colspan, rowspan, alignment, isheader,
5028 			    me->Lines,
5029 			    HText_LastLineOffset(me),
5030 			    HText_LastLineSize(me, FALSE)) < 0) {
5031 	HText_cancelStbl(me);	/* give up */
5032     }
5033 }
5034 
5035 /*	Finish simple table cell
5036 */
HText_endStblTD(HText * me)5037 void HText_endStblTD(HText *me)
5038 {
5039     if (!me || !me->stbl)
5040 	return;
5041     if (Stbl_finishCellInTable(me->stbl, TRST_ENDCELL_ENDTD,
5042 			       me->Lines,
5043 			       HText_LastLineOffset(me),
5044 			       HText_LastLineSize(me, FALSE)) < 0) {
5045 	HText_cancelStbl(me);	/* give up */
5046     }
5047 }
5048 
5049 /*	Remember COL info / Start a COLGROUP and remember info
5050 */
HText_startStblCOL(HText * me,int span,int alignment,int isgroup)5051 void HText_startStblCOL(HText *me, int span,
5052 			int alignment,
5053 			int isgroup)
5054 {
5055     if (!me || !me->stbl)
5056 	return;
5057     if (span <= 0)
5058 	span = 1;
5059     if (span > TRST_MAXCOLSPAN) {
5060 	CTRACE((tfp, "*** SPAN=%d is too large, ignored!\n", span));
5061 	span = 1;
5062     }
5063     if (Stbl_addColInfo(me->stbl, span, alignment, isgroup) < 0) {
5064 	HText_cancelStbl(me);	/* give up */
5065     }
5066 }
5067 
5068 /*	Finish a COLGROUP
5069 */
HText_endStblCOLGROUP(HText * me)5070 void HText_endStblCOLGROUP(HText *me)
5071 {
5072     if (!me || !me->stbl)
5073 	return;
5074     if (Stbl_finishColGroup(me->stbl) < 0) {
5075 	HText_cancelStbl(me);	/* give up */
5076     }
5077 }
5078 
5079 /*	Start a THEAD / TFOOT / TBODY - remember its alignment info
5080 */
HText_startStblRowGroup(HText * me,int alignment)5081 void HText_startStblRowGroup(HText *me, int alignment)
5082 {
5083     if (!me || !me->stbl)
5084 	return;
5085     if (Stbl_addRowGroup(me->stbl, alignment) < 0) {
5086 	HText_cancelStbl(me);	/* give up */
5087     }
5088 }
5089 
compute_show_number(TextAnchor * a)5090 static void compute_show_number(TextAnchor *a)
5091 {
5092     HTAnchor *cur, *tst;
5093     TextAnchor *b;
5094     int match;
5095 
5096     a->show_number = a->number;
5097     if (unique_urls
5098 	&& HTMainText != 0
5099 	&& HTMainText->first_anchor != 0
5100 	&& a->anchor != 0
5101 	&& (cur = a->anchor->dest) != 0
5102 	&& cur->parent != 0
5103 	&& cur->parent->address != 0) {
5104 
5105 	match = 0;
5106 	for (b = HTMainText->first_anchor; b != a; b = b->next) {
5107 	    if (b->anchor != 0
5108 		&& (tst = b->anchor->dest) != 0
5109 		&& tst->parent != 0
5110 		&& tst->parent->address != 0
5111 		&& !strcmp(cur->parent->address,
5112 			   tst->parent->address)
5113 		&& !strcmp(NonNull(a->anchor->tag), NonNull(b->anchor->tag))) {
5114 		match = b->show_number;
5115 		break;
5116 	    }
5117 	}
5118 	if (match)
5119 	    a->show_number = match;
5120 	else
5121 	    a->show_number = HTMainText->next_number++;
5122     }
5123 }
5124 
5125 /*		Anchor handling
5126  *		---------------
5127  */
add_link_number(HText * text,TextAnchor * a,int save_position)5128 static void add_link_number(HText *text, TextAnchor *a, int save_position)
5129 {
5130     char marker[32];
5131 
5132     /*
5133      * If we are doing link_numbering add the link number.
5134      */
5135     if ((a->number > 0)
5136 #ifdef USE_PRETTYSRC
5137 	&& (text->source ? !psrcview_no_anchor_numbering : 1)
5138 #endif
5139 	&& links_are_numbered()) {
5140 	char saved_lastchar = text->LastChar;
5141 	int saved_linenum = text->Lines;
5142 	HTAnchor *link_dest;
5143 	char *link_text;
5144 
5145 	compute_show_number(a);
5146 
5147 	if (dump_links_inline
5148 	    && (link_dest = HTAnchor_followLink(a->anchor)) != 0
5149 	    && (link_text = HTAnchor_address(link_dest)) != 0) {
5150 	    HText_appendText(text, "[");
5151 	    HText_appendText(text, link_text);
5152 	    HText_appendText(text, "]");
5153 	} else {
5154 	    sprintf(marker, "[%d]", a->show_number);
5155 	    HText_appendText(text, marker);
5156 	}
5157 	if (saved_linenum && text->Lines && saved_lastchar != ' ')
5158 	    text->LastChar = ']';	/* if marker not after space caused split */
5159 	if (save_position) {
5160 	    a->line_num = text->Lines;
5161 	    a->line_pos = (short) text->last_line->size;
5162 	}
5163     }
5164 }
5165 
5166 /*	Start an anchor field
5167 */
HText_beginAnchor(HText * text,int underline,HTChildAnchor * anc)5168 int HText_beginAnchor(HText *text, int underline,
5169 		      HTChildAnchor *anc)
5170 {
5171     TextAnchor *a;
5172 
5173     POOLtypecalloc(TextAnchor, a);
5174 
5175     if (a == NULL)
5176 	outofmem(__FILE__, "HText_beginAnchor");
5177 
5178     a->inUnderline = (BOOLEAN) underline;
5179 
5180     a->sgml_offset = SGML_offset();
5181     a->line_num = text->Lines;
5182     a->line_pos = (short) text->last_line->size;
5183     if (text->last_anchor) {
5184 	text->last_anchor->next = a;
5185     } else {
5186 	text->first_anchor = a;
5187     }
5188     a->next = 0;
5189     a->anchor = anc;
5190     a->extent = 0;
5191     a->link_type = HYPERTEXT_ANCHOR;
5192     text->last_anchor = a;
5193 
5194     if (track_internal_links
5195 	&& HTAnchor_followTypedLink(anc, HTInternalLink)) {
5196 	a->number = ++(text->last_anchor_number);
5197 	a->link_type = INTERNAL_LINK_ANCHOR;
5198     } else if (HTAnchor_followLink(anc)) {
5199 	a->number = ++(text->last_anchor_number);
5200     } else {
5201 	a->number = 0;
5202     }
5203     a->show_number = 0;
5204 
5205     if (number_links_on_left)
5206 	add_link_number(text, a, TRUE);
5207     return (a->number);
5208 }
5209 
5210 /* If !really, report whether the anchor is empty. */
HText_endAnchor0(HText * text,int number,int really)5211 static BOOL HText_endAnchor0(HText *text, int number,
5212 			     int really)
5213 {
5214     TextAnchor *a;
5215 
5216     /*
5217      * The number argument is set to 0 in HTML.c and
5218      * LYCharUtils.c when we want to end the anchor
5219      * for the immediately preceding HText_beginAnchor()
5220      * call.  If it's greater than 0, we want to handle
5221      * a particular anchor.  This allows us to set links
5222      * for positions indicated by NAME or ID attributes,
5223      * without needing to close any anchor with an HREF
5224      * within which that link might be embedded.  -FM
5225      */
5226     if (number <= 0 || number == text->last_anchor->number) {
5227 	a = text->last_anchor;
5228     } else {
5229 	for (a = text->first_anchor; a; a = a->next) {
5230 	    if (a->number == number) {
5231 		break;
5232 	    }
5233 	}
5234 	if (a == NULL) {
5235 	    /*
5236 	     * There's no anchor with that number,
5237 	     * so we'll default to the last anchor,
5238 	     * and cross our fingers.  -FM
5239 	     */
5240 	    a = text->last_anchor;
5241 	}
5242     }
5243 
5244     CTRACE((tfp, "GridText:HText_endAnchor0: number:%d link_type:%d\n",
5245 	    a->number, a->link_type));
5246     if (a->link_type == INPUT_ANCHOR) {
5247 	/*
5248 	 * Shouldn't happen, but put test here anyway to be safe.  - LE
5249 	 */
5250 
5251 	CTRACE((tfp,
5252 		"BUG: HText_endAnchor0: internal error: last anchor was input field!\n"));
5253 	return FALSE;
5254     }
5255 
5256     if (a->number) {
5257 	/*
5258 	 * If it goes somewhere...
5259 	 */
5260 	int i, j, k, l;
5261 	BOOL remove_numbers_on_empty = (BOOL) ((links_are_numbered() &&
5262 						((text->hiddenlinkflag != HIDDENLINKS_MERGE)
5263 						 || (LYNoISMAPifUSEMAP &&
5264 						     !(text->node_anchor && text->node_anchor->bookmark)
5265 						     && HTAnchor_isISMAPScript
5266 						     (HTAnchor_followLink(a->anchor))))));
5267 	HTLine *last = text->last_line;
5268 	HTLine *prev = text->last_line->prev;
5269 	HTLine *start = last;
5270 	int CurBlankExtent = 0;
5271 	int BlankExtent = 0;
5272 	int extent_adjust = 0;
5273 
5274 	/* Find the length taken by the anchor */
5275 	l = text->Lines;	/* lineno of last */
5276 
5277 	/* the last line of an anchor may contain a trailing blank,
5278 	 * which will be trimmed later.  Discount it from the extent.
5279 	 */
5280 	if (l > a->line_num) {
5281 	    for (i = start->size; i > 0; --i) {
5282 		if (isspace(UCH(start->data[i - 1]))) {
5283 		    --extent_adjust;
5284 		} else {
5285 		    break;
5286 		}
5287 	    }
5288 	}
5289 
5290 	while (l > a->line_num) {
5291 	    extent_adjust += start->size;
5292 	    start = start->prev;
5293 	    l--;
5294 	}
5295 	/* Now start is the start line of the anchor */
5296 	extent_adjust += start->size - a->line_pos;
5297 	start = last;		/* Used later */
5298 
5299 	/*
5300 	 * Check if the anchor content has only
5301 	 * white and special characters, starting
5302 	 * with the content on the last line.  -FM
5303 	 */
5304 	a->extent = (short) (a->extent + extent_adjust);
5305 	if (a->extent > (int) last->size) {
5306 	    /*
5307 	     * The anchor extends over more than one line,
5308 	     * so set up to check the entire last line.  -FM
5309 	     */
5310 	    i = last->size;
5311 	} else {
5312 	    /*
5313 	     * The anchor is restricted to the last line,
5314 	     * so check from the start of the anchor.  -FM
5315 	     */
5316 	    i = a->extent;
5317 	}
5318 	k = j = (last->size - i);
5319 	while (j < (int) last->size) {
5320 	    if (!IsSpecialAttrChar(last->data[j]) &&
5321 		!isspace(UCH(last->data[j])) &&
5322 		last->data[j] != HT_NON_BREAK_SPACE &&
5323 		last->data[j] != HT_EN_SPACE)
5324 		break;
5325 	    i--;
5326 	    j++;
5327 	}
5328 	if (i == 0) {
5329 	    if (a->extent > (int) last->size) {
5330 		/*
5331 		 * The anchor starts on a preceding line, and
5332 		 * the last line has only white and special
5333 		 * characters, so declare the entire extent
5334 		 * of the last line as blank.  -FM
5335 		 */
5336 		CurBlankExtent = BlankExtent = last->size;
5337 	    } else {
5338 		/*
5339 		 * The anchor starts on the last line, and
5340 		 * has only white or special characters, so
5341 		 * declare the anchor's extent as blank.  -FM
5342 		 */
5343 		CurBlankExtent = BlankExtent = a->extent;
5344 	    }
5345 	}
5346 	/*
5347 	 * While the anchor starts on a line preceding
5348 	 * the one we just checked, and the one we just
5349 	 * checked has only white and special characters,
5350 	 * check whether the anchor's content on the
5351 	 * immediately preceding line also has only
5352 	 * white and special characters.  -FM
5353 	 */
5354 	while (i == 0 &&
5355 	       (a->extent > CurBlankExtent ||
5356 		(a->extent == CurBlankExtent &&
5357 		 k == 0 &&
5358 		 prev != text->last_line &&
5359 		 (prev->size == 0 ||
5360 		  prev->data[prev->size - 1] == ']')))) {
5361 	    start = prev;
5362 	    k = j = prev->size - a->extent + CurBlankExtent;
5363 	    if (j < 0) {
5364 		/*
5365 		 * The anchor starts on a preceding line,
5366 		 * so check all of this line.  -FM
5367 		 */
5368 		j = 0;
5369 		i = prev->size;
5370 	    } else {
5371 		/*
5372 		 * The anchor starts on this line.  -FM
5373 		 */
5374 		i = a->extent - CurBlankExtent;
5375 	    }
5376 	    while (j < (int) prev->size) {
5377 		if (!IsSpecialAttrChar(prev->data[j]) &&
5378 		    !isspace(UCH(prev->data[j])) &&
5379 		    prev->data[j] != HT_NON_BREAK_SPACE &&
5380 		    prev->data[j] != HT_EN_SPACE)
5381 		    break;
5382 		i--;
5383 		j++;
5384 	    }
5385 	    if (i == 0) {
5386 		if (a->extent > (CurBlankExtent + (int) prev->size) ||
5387 		    (a->extent == CurBlankExtent + (int) prev->size &&
5388 		     k == 0 &&
5389 		     prev->prev != text->last_line &&
5390 		     (prev->prev->size == 0 ||
5391 		      prev->prev->data[prev->prev->size - 1] == ']'))) {
5392 		    /*
5393 		     * This line has only white and special
5394 		     * characters, so treat its entire extent
5395 		     * as blank, and decrement the pointer for
5396 		     * the line to be analyzed.  -FM
5397 		     */
5398 		    CurBlankExtent += prev->size;
5399 		    BlankExtent = CurBlankExtent;
5400 		    prev = prev->prev;
5401 		} else {
5402 		    /*
5403 		     * The anchor starts on this line, and it
5404 		     * has only white or special characters, so
5405 		     * declare the anchor's extent as blank.  -FM
5406 		     */
5407 		    BlankExtent = a->extent;
5408 		    break;
5409 		}
5410 	    }
5411 	}
5412 	if (!really) {		/* Just report whether it is empty */
5413 	    a->extent = (short) (a->extent - extent_adjust);
5414 	    return (BOOL) (i == 0);
5415 	}
5416 	if (i == 0) {
5417 	    /*
5418 	     * It's an invisible anchor probably from an ALT=""
5419 	     * or an ignored ISMAP attribute due to a companion
5420 	     * USEMAP.  -FM
5421 	     */
5422 	    a->show_anchor = NO;
5423 
5424 	    CTRACE((tfp,
5425 		    "HText_endAnchor0: hidden (line,pos,ext,BlankExtent):(%d,%d,%d,%d)",
5426 		    a->line_num, a->line_pos, a->extent,
5427 		    BlankExtent));
5428 
5429 	    /*
5430 	     * If links are numbered, then try to get rid of the
5431 	     * numbered bracket and adjust the anchor count.  -FM
5432 	     *
5433 	     * Well, let's do this only if -hiddenlinks=merged is not in
5434 	     * effect, or if we can be reasonably sure that
5435 	     * this is the result of an intentional non-generation of
5436 	     * anchor text via NO_ISMAP_IF_USEMAP.  In other cases it can
5437 	     * actually be a feature that numbered links alert the viewer
5438 	     * to the presence of a link which is otherwise not selectable -
5439 	     * possibly caused by HTML errors. - kw
5440 	     */
5441 	    if (remove_numbers_on_empty) {
5442 		int NumSize = 0;
5443 		TextAnchor *anc;
5444 
5445 		/*
5446 		 * Set start->data[j] to the close-square-bracket,
5447 		 * or to the beginning of the line on which the
5448 		 * anchor start.  -FM
5449 		 */
5450 		if (start == last) {
5451 		    /*
5452 		     * The anchor starts on the last line.  -FM
5453 		     */
5454 		    j = (last->size - a->extent - 1);
5455 		} else {
5456 		    /*
5457 		     * The anchor starts on a previous line.  -FM
5458 		     */
5459 		    prev = start->prev;
5460 		    j = (start->size - a->extent + CurBlankExtent - 1);
5461 		}
5462 		if (j < 0)
5463 		    j = 0;
5464 		i = j;
5465 
5466 		/*
5467 		 * If start->data[j] is a close-square-bracket, verify
5468 		 * that it's the end of the numbered bracket, and if so,
5469 		 * strip the numbered bracket.  If start->data[j] is not
5470 		 * a close-square-bracket, check whether we had a wrap
5471 		 * and the close-square-bracket is at the end of the
5472 		 * previous line.  If so, strip the numbered bracket
5473 		 * from that line.  -FM
5474 		 */
5475 		if (start->data[j] == ']') {
5476 		    j--;
5477 		    NumSize++;
5478 		    while (j >= 0 && isdigit(UCH(start->data[j]))) {
5479 			j--;
5480 			NumSize++;
5481 		    }
5482 		    while (j < 0) {
5483 			j++;
5484 			NumSize--;
5485 		    }
5486 		    if (start->data[j] == '[') {
5487 			/*
5488 			 * The numbered bracket is entirely
5489 			 * on this line.  -FM
5490 			 */
5491 			NumSize++;
5492 			if (start == last && (int) text->permissible_split > j) {
5493 			    if ((int) text->permissible_split - NumSize < j)
5494 				text->permissible_split = (unsigned) j;
5495 			    else
5496 				text->permissible_split -= (unsigned) NumSize;
5497 			}
5498 			k = j + NumSize;
5499 			while (k < (int) start->size)
5500 			    start->data[j++] = start->data[k++];
5501 			for (anc = a; anc; anc = anc->next) {
5502 			    if (anc->line_num == a->line_num &&
5503 				anc->line_pos >= NumSize) {
5504 				anc->line_pos = (short) (anc->line_pos - NumSize);
5505 			    }
5506 			}
5507 			start->size = (unsigned short) j;
5508 			start->data[j++] = '\0';
5509 			while (j < k)
5510 			    start->data[j++] = '\0';
5511 		    } else if (prev && prev->size > 1) {
5512 			k = (i + 1);
5513 			j = (prev->size - 1);
5514 			while ((j >= 0) && IsSpecialAttrChar(prev->data[j]))
5515 			    j--;
5516 			i = (j + 1);
5517 			while (j >= 0 &&
5518 			       isdigit(UCH(prev->data[j]))) {
5519 			    j--;
5520 			    NumSize++;
5521 			}
5522 			while (j < 0) {
5523 			    j++;
5524 			    NumSize--;
5525 			}
5526 			if (prev->data[j] == '[') {
5527 			    /*
5528 			     * The numbered bracket started on the
5529 			     * previous line, and part of it was
5530 			     * wrapped to this line.  -FM
5531 			     */
5532 			    while (i < (int) prev->size)
5533 				prev->data[j++] = prev->data[i++];
5534 			    prev->size = (unsigned short) j;
5535 			    prev->data[j] = '\0';
5536 			    while (j < i)
5537 				prev->data[j++] = '\0';
5538 			    if (start == last && text->permissible_split > 0) {
5539 				if ((int) text->permissible_split < k)
5540 				    text->permissible_split = 0;
5541 				else
5542 				    text->permissible_split -= (unsigned) k;
5543 			    }
5544 			    j = 0;
5545 			    i = k;
5546 			    while (k < (int) start->size)
5547 				start->data[j++] = start->data[k++];
5548 			    for (anc = a; anc; anc = anc->next) {
5549 				if (anc->line_num == a->line_num &&
5550 				    anc->line_pos >= i) {
5551 				    anc->line_pos = (short) (anc->line_pos - i);
5552 				}
5553 			    }
5554 			    start->size = (unsigned short) j;
5555 			    start->data[j++] = '\0';
5556 			    while (j < k)
5557 				start->data[j++] = '\0';
5558 			} else {
5559 			    /*
5560 			     * Shucks!  We didn't find the
5561 			     * numbered bracket.  -FM
5562 			     */
5563 			    a->show_anchor = YES;
5564 			}
5565 		    } else {
5566 			/*
5567 			 * Shucks!  We didn't find the
5568 			 * numbered bracket.  -FM
5569 			 */
5570 			a->show_anchor = YES;
5571 		    }
5572 		} else if (prev && prev->size > 2) {
5573 		    j = (prev->size - 1);
5574 		    while ((j >= 0) && IsSpecialAttrChar(prev->data[j]))
5575 			j--;
5576 		    if (j < 0)
5577 			j = 0;
5578 		    if ((j >= 2) &&
5579 			(prev->data[j] == ']' &&
5580 			 isdigit(UCH(prev->data[j - 1])))) {
5581 			j--;
5582 			NumSize++;
5583 			while (j >= 0 &&
5584 			       isdigit(UCH(prev->data[j]))) {
5585 			    j--;
5586 			    NumSize++;
5587 			}
5588 			while (j < 0) {
5589 			    j++;
5590 			    NumSize--;
5591 			}
5592 			if (prev->data[j] == '[') {
5593 			    /*
5594 			     * The numbered bracket is all on the
5595 			     * previous line, and the anchor content
5596 			     * was wrapped to the last line.  -FM
5597 			     */
5598 			    NumSize++;
5599 			    k = j + NumSize;
5600 			    while (k < (int) prev->size)
5601 				prev->data[j++] = prev->data[k++];
5602 			    prev->size = (unsigned short) j;
5603 			    prev->data[j++] = '\0';
5604 			    while (j < k)
5605 				prev->data[j++] = '\0';
5606 			} else {
5607 			    /*
5608 			     * Shucks!  We didn't find the
5609 			     * numbered bracket.  -FM
5610 			     */
5611 			    a->show_anchor = YES;
5612 			}
5613 		    } else {
5614 			/*
5615 			 * Shucks!  We didn't find the
5616 			 * numbered bracket.  -FM
5617 			 */
5618 			a->show_anchor = YES;
5619 		    }
5620 		} else {
5621 		    /*
5622 		     * Shucks!  We didn't find the
5623 		     * numbered bracket.  -FM
5624 		     */
5625 		    a->show_anchor = YES;
5626 		}
5627 	    }
5628 	} else {
5629 	    if (!number_links_on_left)
5630 		add_link_number(text, a, FALSE);
5631 	    /*
5632 	     * The anchor's content is not restricted to only
5633 	     * white and special characters, so we'll show it
5634 	     * as a link.  -FM
5635 	     */
5636 	    a->show_anchor = YES;
5637 	    if (BlankExtent) {
5638 		CTRACE((tfp,
5639 			"HText_endAnchor0: blanks (line,pos,ext,BlankExtent):(%d,%d,%d,%d)",
5640 			a->line_num, a->line_pos, a->extent,
5641 			BlankExtent));
5642 	    }
5643 	}
5644 	if (a->show_anchor == NO) {
5645 	    /*
5646 	     * The anchor's content is restricted to white
5647 	     * and special characters, so set its number
5648 	     * and extent to zero, decrement the visible
5649 	     * anchor number counter, and add this anchor
5650 	     * to the hidden links list.  -FM
5651 	     */
5652 	    a->extent = 0;
5653 	    if (text->hiddenlinkflag != HIDDENLINKS_MERGE) {
5654 		a->number = 0;
5655 		text->last_anchor_number--;
5656 		HText_AddHiddenLink(text, a);
5657 	    }
5658 	} else {
5659 	    /*
5660 	     * The anchor's content is not restricted to white
5661 	     * and special characters, so we'll display the
5662 	     * content, but shorten its extent by any trailing
5663 	     * blank lines we've detected.  -FM
5664 	     */
5665 	    a->extent = (short) (a->extent - ((BlankExtent < a->extent)
5666 					      ? BlankExtent
5667 					      : 0));
5668 	}
5669 	if (BlankExtent || a->extent <= 0 || a->number <= 0) {
5670 	    CTRACE((tfp,
5671 		    "->[%d](%d,%d,%d,%d)\n",
5672 		    a->number,
5673 		    a->line_num, a->line_pos, a->extent,
5674 		    BlankExtent));
5675 	}
5676     } else {
5677 	if (!really)		/* Just report whether it is empty */
5678 	    return FALSE;
5679 	/*
5680 	 * It's a named anchor without an HREF, so it
5681 	 * should be registered but not shown as a
5682 	 * link.  -FM
5683 	 */
5684 	a->show_anchor = NO;
5685 	a->extent = 0;
5686     }
5687     return FALSE;
5688 }
5689 
HText_endAnchor(HText * text,int number)5690 void HText_endAnchor(HText *text, int number)
5691 {
5692     HText_endAnchor0(text, number, 1);
5693 }
5694 
5695 /*
5696     This returns whether the given anchor has blank content. Shamelessly copied
5697     from HText_endAnchor. The values returned are meaningful only for "normal"
5698     links - like ones produced by <a href=".">foo</a>, no inputs, etc. - VH
5699 */
5700 #ifdef MARK_HIDDEN_LINKS
HText_isAnchorBlank(HText * text,int number)5701 BOOL HText_isAnchorBlank(HText *text, int number)
5702 {
5703     return HText_endAnchor0(text, number, 0);
5704 }
5705 #endif /* MARK_HIDDEN_LINKS */
5706 
HText_appendText(HText * text,const char * str)5707 void HText_appendText(HText *text, const char *str)
5708 {
5709     const char *p;
5710 
5711     if (str != NULL &&
5712 	text != NULL &&
5713 	text->halted != 3) {
5714 	for (p = str; *p; p++) {
5715 	    HText_appendCharacter(text, *p);
5716 	}
5717     }
5718 }
5719 
remove_special_attr_chars(char * buf)5720 static int remove_special_attr_chars(char *buf)
5721 {
5722     register char *cp;
5723     register int soft_newline_count = 0;
5724 
5725     for (cp = buf; *cp != '\0'; cp++) {
5726 	/*
5727 	 * Don't print underline chars.
5728 	 */
5729 	soft_newline_count += (*cp == LY_SOFT_NEWLINE);
5730 	if (!IsSpecialAttrChar(*cp)) {
5731 	    *buf++ = *cp;
5732 	}
5733     }
5734     *buf = '\0';
5735     return soft_newline_count;
5736 }
5737 
5738 /*
5739  *  This function trims blank lines from the end of the document, and
5740  *  then gets the hightext from the text by finding the char position,
5741  *  and brings the anchors in line with the text by adding the text
5742  *  offset to each of the anchors.
5743  */
HText_endAppend(HText * text)5744 void HText_endAppend(HText *text)
5745 {
5746     HTLine *line_ptr;
5747 
5748     if (!text)
5749 	return;
5750 
5751     CTRACE((tfp, "GridText: Entering HText_endAppend\n"));
5752 
5753     /*
5754      * Create a blank line at the bottom.
5755      */
5756     new_line(text);
5757 
5758     if (text->halted) {
5759 	if (text->stbl) {
5760 	    HText_cancelStbl(text);
5761 	}
5762 	/*
5763 	 * If output was stopped because memory was low, and we made
5764 	 * it to the end of the document, reset those flags and hope
5765 	 * things are better now.  - kw
5766 	 */
5767 	LYFakeZap(NO);
5768 	text->halted = 0;
5769     } else if (text->stbl) {
5770 	/*
5771 	 * Could happen if TABLE end tag was missing.
5772 	 * Alternatively we could cancel in this case.  - kw
5773 	 */
5774 	HText_endStblTABLE(text);
5775     }
5776 
5777     /*
5778      * Get the first line.
5779      */
5780     if (LYtrimBlankLines && (line_ptr = FirstHTLine(text)) != 0) {
5781 	/*
5782 	 * Remove blank lines at the end of the document.
5783 	 */
5784 	while (text->last_line->data[0] == '\0' && text->Lines > 0) {
5785 	    HTLine *next_to_the_last_line = text->last_line->prev;
5786 
5787 	    CTRACE((tfp, "GridText: Removing bottom blank line: `%s'\n",
5788 		    text->last_line->data));
5789 	    /*
5790 	     * line_ptr points to the first line.
5791 	     */
5792 	    next_to_the_last_line->next = line_ptr;
5793 	    line_ptr->prev = next_to_the_last_line;
5794 	    freeHTLine(text, text->last_line);
5795 	    text->last_line = next_to_the_last_line;
5796 	    text->Lines--;
5797 	    CTRACE((tfp, "GridText: New bottom line: `%s'\n",
5798 		    text->last_line->data));
5799 	}
5800     }
5801 
5802     /*
5803      * Fix up the anchor structure values and
5804      * create the hightext strings.  -FM
5805      */
5806     HText_trimHightext(text, TRUE, -1);
5807 }
5808 
5809 /*
5810  *  This function gets the hightext from the text by finding the char
5811  *  position, and brings the anchors in line with the text by adding the text
5812  *  offset to each of the anchors.
5813  *
5814  *  `Forms input' fields cannot be displayed properly without this function
5815  *  to be invoked (detected in display_partial mode).
5816  *
5817  *  If final is set, this is the final fixup; if not set, we don't have
5818  *  to do everything because there should be another call later.
5819  *
5820  *  BEFORE this function has treated a TextAnchor, its line_pos and
5821  *  extent fields are counting bytes in the HTLine data, including
5822  *  invisible special attribute chars and counting UTF-8 multibyte
5823  *  characters as multiple bytes.
5824  *
5825  *  AFTER the adjustment, the anchor line_pos (and hightext offset if
5826  *  applicable) fields indicate x positions in terms of displayed character
5827  *  cells, and the extent field apparently is unimportant; the anchor text has
5828  *  been copied to the hightext fields (which should have been NULL up to that
5829  *  point), with special attribute chars removed.
5830  *
5831  *  This needs to be done so that display_page finds the anchors in the
5832  *  form it expects when it sets the links[] elements.
5833  */
HText_trimHightext(HText * text,int final,int stop_before)5834 static void HText_trimHightext(HText *text,
5835 			       int final,
5836 			       int stop_before)
5837 {
5838     int cur_line, cur_shift;
5839     TextAnchor *anchor_ptr;
5840     TextAnchor *prev_a = NULL;
5841     HTLine *line_ptr;
5842     HTLine *line_ptr2;
5843     unsigned char ch;
5844     char *hilite_str;
5845     int hilite_len;
5846     int actual_len;
5847     int count_line;
5848 
5849     if (!text)
5850 	return;
5851 
5852     if (final) {
5853 	CTRACE((tfp, "GridText: Entering HText_trimHightext (final)\n"));
5854     } else {
5855 	if (stop_before < 0 || stop_before > text->Lines)
5856 	    stop_before = text->Lines;
5857 	CTRACE((tfp,
5858 		"GridText: Entering HText_trimHightext (partial: 0..%d/%d)\n",
5859 		stop_before, text->Lines));
5860     }
5861 
5862     /*
5863      * Get the first line.
5864      */
5865     line_ptr = FirstHTLine(text);
5866     cur_line = 0;
5867 
5868     /*
5869      * Fix up the anchor structure values and
5870      * create the hightext strings.  -FM
5871      */
5872     for (anchor_ptr = text->first_anchor;
5873 	 anchor_ptr != NULL;
5874 	 prev_a = anchor_ptr, anchor_ptr = anchor_ptr->next) {
5875 	int anchor_col;
5876 
5877       re_parse:
5878 	/*
5879 	 * Find the right line.
5880 	 */
5881 	for (; anchor_ptr->line_num > cur_line;
5882 	     line_ptr = line_ptr->next, cur_line++) {
5883 	    ;			/* null body */
5884 	}
5885 
5886 	if (!final) {
5887 	    /*
5888 	     * If this is not the final call, stop when we have reached
5889 	     * the last line, or the very end of preceding line.
5890 	     * The last line is probably still not finished.  - kw
5891 	     */
5892 	    if (cur_line >= stop_before)
5893 		break;
5894 	    if (anchor_ptr->line_num >= text->Lines - 1
5895 		&& anchor_ptr->line_pos >= (int) text->last_line->prev->size)
5896 		break;
5897 	    /*
5898 	     * Also skip this anchor if it looks like HText_endAnchor
5899 	     * is not yet done with it.  - kw
5900 	     */
5901 	    if (!anchor_ptr->extent && anchor_ptr->number &&
5902 		(anchor_ptr->link_type & HYPERTEXT_ANCHOR) &&
5903 		!anchor_ptr->show_anchor &&
5904 		anchor_ptr->number == text->last_anchor_number)
5905 		continue;
5906 	}
5907 
5908 	/*
5909 	 * If hightext has already been set, then we must have already
5910 	 * done the trimming & adjusting for this anchor, so avoid
5911 	 * doing it a second time.  - kw
5912 	 */
5913 	if (LYGetHiTextStr(anchor_ptr, 0) != NULL)
5914 	    continue;
5915 
5916 	if (anchor_ptr->line_pos > (int) line_ptr->size) {
5917 	    anchor_ptr->line_pos = (short) line_ptr->size;
5918 	}
5919 	if (anchor_ptr->line_pos < 0) {
5920 	    anchor_ptr->line_pos = 0;
5921 	    anchor_ptr->line_num = cur_line;
5922 	}
5923 	CTRACE((tfp,
5924 		"GridText: Anchor found on line:%d col:%d [%05d:%d] ext:%d\n",
5925 		cur_line,
5926 		anchor_ptr->line_pos,
5927 		anchor_ptr->sgml_offset,
5928 		anchor_ptr->number,
5929 		anchor_ptr->extent));
5930 
5931 	cur_shift = 0;
5932 	/*
5933 	 * Strip off any spaces or SpecialAttrChars at the beginning,
5934 	 * if they exist, but only on HYPERTEXT_ANCHORS.
5935 	 */
5936 	if (anchor_ptr->link_type & HYPERTEXT_ANCHOR) {
5937 	    ch = UCH(line_ptr->data[anchor_ptr->line_pos]);
5938 	    while (isspace(ch) ||
5939 		   IsSpecialAttrChar(ch)) {
5940 		anchor_ptr->line_pos++;
5941 		anchor_ptr->extent--;
5942 		cur_shift++;
5943 		ch = UCH(line_ptr->data[anchor_ptr->line_pos]);
5944 	    }
5945 	}
5946 	if (anchor_ptr->extent < 0) {
5947 	    anchor_ptr->extent = 0;
5948 	}
5949 
5950 	CTRACE((tfp, "anchor text: '%s'\n", line_ptr->data));
5951 	/*
5952 	 * If the link begins with an end of line and we have more lines, then
5953 	 * start the highlighting on the next line.  -FM.
5954 	 *
5955 	 * But if an empty anchor is at the end of line and empty, keep it
5956 	 * where it is, unless the previous anchor in the list (if any) already
5957 	 * starts later.  - kw
5958 	 */
5959 	if ((unsigned) anchor_ptr->line_pos >= strlen(line_ptr->data)) {
5960 	    if (cur_line < text->Lines &&
5961 		(anchor_ptr->extent ||
5962 		 anchor_ptr->line_pos != (int) line_ptr->size ||
5963 		 (prev_a &&	/* How could this happen? */
5964 		  (prev_a->line_num > anchor_ptr->line_num)))) {
5965 		anchor_ptr->line_num++;
5966 		anchor_ptr->line_pos = 0;
5967 		CTRACE((tfp, "found anchor at end of line\n"));
5968 		goto re_parse;
5969 	    } else {
5970 		CTRACE((tfp, "found anchor at end of line, leaving it there\n"));
5971 	    }
5972 	}
5973 
5974 	/*
5975 	 * Copy the link name into the data structure.
5976 	 */
5977 	if (anchor_ptr->extent > 0
5978 	    && anchor_ptr->line_pos >= 0) {
5979 	    int size = (int) line_ptr->size - anchor_ptr->line_pos;
5980 
5981 	    if (size > anchor_ptr->extent)
5982 		size = anchor_ptr->extent;
5983 	    LYClearHiText(anchor_ptr);
5984 	    LYSetHiText(anchor_ptr,
5985 			&line_ptr->data[anchor_ptr->line_pos],
5986 			(unsigned) size);
5987 	} else {
5988 	    LYClearHiText(anchor_ptr);
5989 	    LYSetHiText(anchor_ptr, "", 0);
5990 	}
5991 
5992 	/*
5993 	 * If the anchor extends over more than one line, copy that into the
5994 	 * data structure.
5995 	 */
5996 	hilite_str = LYGetHiTextStr(anchor_ptr, 0);
5997 	hilite_len = (int) strlen(hilite_str);
5998 	actual_len = anchor_ptr->extent;
5999 
6000 	line_ptr2 = line_ptr;
6001 	assert(line_ptr2 != 0);
6002 
6003 	count_line = cur_line;
6004 	while (actual_len > hilite_len) {
6005 	    HTLine *old_line_ptr2 = line_ptr2;
6006 
6007 	    count_line++;
6008 	    if ((line_ptr2 = line_ptr2->next) == NULL)
6009 		break;
6010 
6011 	    if (!final
6012 		&& count_line >= stop_before) {
6013 		LYClearHiText(anchor_ptr);
6014 		break;
6015 	    } else if (old_line_ptr2 == text->last_line) {
6016 		break;
6017 	    }
6018 
6019 	    /*
6020 	     * Double check that we have a line pointer, and if so, copy into
6021 	     * highlight text.
6022 	     */
6023 	    if (line_ptr2) {
6024 		char *hi_string = NULL;
6025 		int hi_offset = line_ptr2->offset;
6026 
6027 		StrnAllocCopy(hi_string,
6028 			      line_ptr2->data,
6029 			      (size_t) (actual_len - hilite_len));
6030 		actual_len -= (int) strlen(hi_string);
6031 		/*handle LY_SOFT_NEWLINEs -VH */
6032 		hi_offset += remove_special_attr_chars(hi_string);
6033 
6034 		if (anchor_ptr->link_type & HYPERTEXT_ANCHOR) {
6035 		    LYTrimTrailing(hi_string);
6036 		}
6037 		if (non_empty(hi_string)) {
6038 		    LYAddHiText(anchor_ptr, hi_string, hi_offset);
6039 		} else if (actual_len > hilite_len) {
6040 		    LYAddHiText(anchor_ptr, "", hi_offset);
6041 		}
6042 		FREE(hi_string);
6043 	    }
6044 	}
6045 
6046 	if (!final
6047 	    && count_line >= stop_before) {
6048 	    break;
6049 	}
6050 
6051 	hilite_str = LYGetHiTextStr(anchor_ptr, 0);
6052 	remove_special_attr_chars(hilite_str);
6053 	if (anchor_ptr->link_type & HYPERTEXT_ANCHOR) {
6054 	    LYTrimTrailing(hilite_str);
6055 	}
6056 
6057 	/*
6058 	 * Save the offset (bytes) of the anchor in the line's data.
6059 	 */
6060 	anchor_col = anchor_ptr->line_pos;
6061 
6062 	/*
6063 	 * Subtract any formatting characters from the x position of the link.
6064 	 */
6065 #ifdef WIDEC_CURSES
6066 	if (anchor_ptr->line_pos > 0) {
6067 	    /*
6068 	     * LYstrExtent filters out the formatting characters, so we do not
6069 	     * have to count them here, except for soft newlines.
6070 	     */
6071 	    anchor_ptr->line_pos = (short) LYstrExtent2(line_ptr->data, anchor_col);
6072 	    if (line_ptr->data[0] == LY_SOFT_NEWLINE)
6073 		anchor_ptr->line_pos = (short) (anchor_ptr->line_pos + 1);
6074 	}
6075 #else /* 8-bit curses, etc.  */
6076 	if (anchor_ptr->line_pos > 0) {
6077 	    register int offset = 0, i = 0;
6078 	    int have_soft_newline_in_1st_line = 0;
6079 
6080 	    for (; i < anchor_col; i++) {
6081 		if (IS_UTF_EXTRA(line_ptr->data[i]) ||
6082 		    IsSpecialAttrChar(line_ptr->data[i])) {
6083 		    offset++;
6084 		    have_soft_newline_in_1st_line += (line_ptr->data[i] == LY_SOFT_NEWLINE);
6085 		}
6086 	    }
6087 	    anchor_ptr->line_pos = (short) (anchor_ptr->line_pos - offset);
6088 	    /*handle LY_SOFT_NEWLINEs -VH */
6089 	    anchor_ptr->line_pos = (short) (anchor_ptr->line_pos + have_soft_newline_in_1st_line);
6090 	}
6091 #endif /* WIDEC_CURSES */
6092 
6093 	/*
6094 	 * Set the line number.
6095 	 */
6096 	anchor_ptr->line_pos = (short) (anchor_ptr->line_pos + line_ptr->offset);
6097 	anchor_ptr->line_num = cur_line;
6098 
6099 	CTRACE((tfp, "GridText:     add link on line %d col %d [%d] %s\n",
6100 		cur_line, anchor_ptr->line_pos,
6101 		anchor_ptr->number, "in HText_trimHightext"));
6102     }
6103 }
6104 
6105 /*	Return the anchor associated with this node
6106 */
HText_nodeAnchor(HText * text)6107 HTParentAnchor *HText_nodeAnchor(HText *text)
6108 {
6109     return text->node_anchor;
6110 }
6111 
6112 /*				GridText specials
6113  *				=================
6114  */
6115 
6116 /*
6117  * HText_childNextNumber() returns the anchor with index [number],
6118  * using a pointer from the previous number (=optimization) or NULL.
6119  */
HText_childNextNumber(int number,void ** prev)6120 HTChildAnchor *HText_childNextNumber(int number, void **prev)
6121 {
6122     /* Sorry, TextAnchor is not declared outside this file, use a cast. */
6123     TextAnchor *a = (TextAnchor *) *prev;
6124 
6125     if (!HTMainText || number <= 0)
6126 	return (HTChildAnchor *) 0;	/* Fail */
6127     if (number == 1 || !a)
6128 	a = HTMainText->first_anchor;
6129 
6130     /* a strange thing:  positive a->number's are sorted,
6131      * and between them several a->number's may be 0 -- skip them
6132      */
6133     for (; a && a->number != number; a = a->next) ;
6134 
6135     if (!a)
6136 	return (HTChildAnchor *) 0;	/* Fail */
6137     *prev = (void *) a;
6138     return a->anchor;
6139 }
6140 
6141 /*
6142  * For the -unique-urls option, find the anchor-number of the first occurrence
6143  * of a given address.
6144  */
HText_findAnchorNumber(void * avoid)6145 int HText_findAnchorNumber(void *avoid)
6146 {
6147     TextAnchor *a = (TextAnchor *) avoid;
6148 
6149     if (a->number > 0 && a->show_number == 0)
6150 	compute_show_number(a);
6151 
6152     return a->show_number;
6153 }
6154 
inputFieldDesc(FormInfo * input)6155 static const char *inputFieldDesc(FormInfo * input)
6156 {
6157     const char *result = 0;
6158 
6159     switch (input->type) {
6160     case F_TEXT_TYPE:
6161 	result = gettext("text entry field");
6162 	break;
6163     case F_PASSWORD_TYPE:
6164 	result = gettext("password entry field");
6165 	break;
6166     case F_CHECKBOX_TYPE:
6167 	result = gettext("checkbox");
6168 	break;
6169     case F_RADIO_TYPE:
6170 	result = gettext("radio button");
6171 	break;
6172     case F_SUBMIT_TYPE:
6173 	result = gettext("submit button");
6174 	break;
6175     case F_RESET_TYPE:
6176 	result = gettext("reset button");
6177 	break;
6178     case F_BUTTON_TYPE:
6179 	result = gettext("script button");
6180 	break;
6181     case F_OPTION_LIST_TYPE:
6182 	result = gettext("popup menu");
6183 	break;
6184     case F_HIDDEN_TYPE:
6185 	result = gettext("hidden form field");
6186 	break;
6187     case F_TEXTAREA_TYPE:
6188 	result = gettext("text entry area");
6189 	break;
6190     case F_RANGE_TYPE:
6191 	result = gettext("range entry field");
6192 	break;
6193     case F_FILE_TYPE:
6194 	result = gettext("file entry field");
6195 	break;
6196     case F_TEXT_SUBMIT_TYPE:
6197 	result = gettext("text-submit field");
6198 	break;
6199     case F_IMAGE_SUBMIT_TYPE:
6200 	result = gettext("image-submit button");
6201 	break;
6202     case F_KEYGEN_TYPE:
6203 	result = gettext("keygen field");
6204 	break;
6205     default:
6206 	result = gettext("unknown form field");
6207 	break;
6208     }
6209     return result;
6210 }
6211 
6212 /*
6213  * HText_FormDescNumber() returns a description of the form field
6214  * with index N.  The index corresponds to the [number] we print
6215  * for the field.  -FM & LE
6216  */
HText_FormDescNumber(int number,const char ** desc)6217 void HText_FormDescNumber(int number,
6218 			  const char **desc)
6219 {
6220     TextAnchor *a;
6221 
6222     if (!desc)
6223 	return;
6224 
6225     if (!(HTMainText && HTMainText->first_anchor) || number <= 0) {
6226 	*desc = gettext("unknown field or link");
6227 	return;
6228     }
6229 
6230     for (a = HTMainText->first_anchor; a; a = a->next) {
6231 	if (a->number == number) {
6232 	    if (!(a->input_field && a->input_field->type)) {
6233 		*desc = gettext("unknown field or link");
6234 		return;
6235 	    }
6236 	    break;
6237 	}
6238     }
6239 
6240     if (a != NULL)
6241 	*desc = inputFieldDesc(a->input_field);
6242 }
6243 
6244 /* HTGetRelLinkNum returns the anchor number to which follow_link_number()
6245  * is to jump (input was 123+ or 123- or 123+g or 123-g or 123 or 123g)
6246  * num is the number specified
6247  * rel is 0 or '+' or '-'
6248  * cur is the current link
6249  */
HTGetRelLinkNum(int num,int rel,int cur)6250 int HTGetRelLinkNum(int num,
6251 		    int rel,
6252 		    int cur)
6253 {
6254     TextAnchor *a, *l = 0;
6255     int scrtop = HText_getTopOfScreen();	/*XXX +1? */
6256     int curline = links[cur].anchor_line_num;
6257     int curpos = links[cur].lx;
6258     int on_screen = (curline >= scrtop && curline < (scrtop + display_lines));
6259 
6260     /* curanchor may or may not be the "current link", depending whether it's
6261      * on the current screen
6262      */
6263     int curanchor = links[cur].anchor_number;
6264 
6265     CTRACE((tfp, "HTGetRelLinkNum(%d,%d,%d) -- HTMainText=%p\n",
6266 	    num, rel, cur, (void *) HTMainText));
6267     CTRACE((tfp,
6268 	    "  scrtop=%d, curline=%d, curanchor=%d, display_lines=%d, %s\n",
6269 	    scrtop, curline, curanchor, display_lines,
6270 	    on_screen ? "on_screen" : "0"));
6271     if (!HTMainText)
6272 	return 0;
6273     if (rel == 0)
6274 	return num;
6275 
6276     /* if cur numbered link is on current page, use it */
6277     if (on_screen && curanchor) {
6278 	CTRACE((tfp, "curanchor=%d at line %d on screen\n", curanchor, curline));
6279 	if (rel == '+')
6280 	    return curanchor + num;
6281 	else if (rel == '-')
6282 	    return curanchor - num;
6283 	else
6284 	    return num;		/* shouldn't happen */
6285     }
6286 
6287     /* no current link on screen, or current link is not numbered
6288      * -- find previous closest numbered link
6289      */
6290     for (a = HTMainText->first_anchor; a; a = a->next) {
6291 	CTRACE((tfp, "  a->line_num=%d, a->number=%d\n", a->line_num, a->number));
6292 	if (a->line_num >= scrtop)
6293 	    break;
6294 	if (a->number == 0)
6295 	    continue;
6296 	l = a;
6297 	curanchor = l->number;
6298     }
6299     CTRACE((tfp, "  a=%p, l=%p, curanchor=%d\n", (void *) a, (void *) l, curanchor));
6300     if (on_screen) {		/* on screen but not a numbered link */
6301 	for (; a; a = a->next) {
6302 	    if (a->number) {
6303 		l = a;
6304 		curanchor = l->number;
6305 	    }
6306 	    if (curline == a->line_num && curpos == a->line_pos)
6307 		break;
6308 	}
6309     }
6310     if (rel == '+') {
6311 	return curanchor + num;
6312     } else if (rel == '-') {
6313 	if (l)
6314 	    return curanchor + 1 - num;
6315 	else {
6316 	    for (; a && a->number == 0; a = a->next) ;
6317 	    return a ? a->number - num : 0;
6318 	}
6319     } else
6320 	return num;		/* shouldn't happen */
6321 }
6322 
6323 /*
6324  * HTGetLinkInfo returns some link info based on the number.
6325  *
6326  * If want_go is not 0, caller requests to know a line number for
6327  * the link indicated by number.  It will be returned in *go_line, and
6328  * *linknum will be set to an index into the links[] array, to use after
6329  * the line in *go_line has been made the new top screen line.
6330  * *hightext and *lname are unchanged.  - KW
6331  *
6332  * If want_go is 0 and the number doesn't represent an input field, info
6333  * on the link indicated by number is deposited in *hightext and *lname.
6334  */
HTGetLinkInfo(int number,int want_go,int * go_line,int * linknum,char ** hightext,char ** lname)6335 int HTGetLinkInfo(int number,
6336 		  int want_go,
6337 		  int *go_line,
6338 		  int *linknum,
6339 		  char **hightext,
6340 		  char **lname)
6341 {
6342     TextAnchor *a;
6343     HTAnchor *link_dest;
6344 
6345     HTAnchor *link_dest_intl = NULL;
6346     int anchors_this_line = 0, anchors_this_screen = 0;
6347     int prev_anchor_line = -1, prev_prev_anchor_line = -1;
6348 
6349     if (!HTMainText)
6350 	return (NO);
6351 
6352     for (a = HTMainText->first_anchor; a; a = a->next) {
6353 	/*
6354 	 * Count anchors, first on current line if there is more
6355 	 * than one.  We have to count all links, including form
6356 	 * field anchors and others with a->number == 0, because
6357 	 * they are or will be included in the links[] array.
6358 	 * The exceptions are hidden form fields and anchors with
6359 	 * show_anchor not set, because they won't appear in links[]
6360 	 * and don't count towards nlinks.  - KW
6361 	 */
6362 	if ((a->show_anchor) &&
6363 	    !(a->link_type == INPUT_ANCHOR
6364 	      && a->input_field->type == F_HIDDEN_TYPE)) {
6365 	    if (a->line_num == prev_anchor_line) {
6366 		anchors_this_line++;
6367 	    } else {
6368 		/*
6369 		 * This anchor is on a different line than the previous one.
6370 		 * Remember which was the line number of the previous anchor,
6371 		 * for use in screen positioning later.  - KW
6372 		 */
6373 		anchors_this_line = 1;
6374 		prev_prev_anchor_line = prev_anchor_line;
6375 		prev_anchor_line = a->line_num;
6376 	    }
6377 	    if (a->line_num >= HTMainText->top_of_screen) {
6378 		/*
6379 		 * Count all anchors starting with the top line of the
6380 		 * currently displayed screen.  Just keep on counting
6381 		 * beyond this screen's bottom line - we'll know whether
6382 		 * a found anchor is below the current screen by a check
6383 		 * against nlinks later.  - KW
6384 		 */
6385 		anchors_this_screen++;
6386 	    }
6387 	}
6388 
6389 	if (a->number == number) {
6390 	    /*
6391 	     * We found it.  Now process it, depending
6392 	     * on what kind of info is requested.  - KW
6393 	     */
6394 	    if (want_go || a->link_type == INPUT_ANCHOR) {
6395 		if (a->show_anchor == NO) {
6396 		    /*
6397 		     * The number requested has been assigned to an anchor
6398 		     * without any selectable text, so we cannot position
6399 		     * on it.  The code for suppressing such anchors in
6400 		     * HText_endAnchor() may not have applied, or it may
6401 		     * have failed.  Return a failure indication so that
6402 		     * the user will notice that something is wrong,
6403 		     * instead of positioning on some other anchor which
6404 		     * might result in inadvertent activation.  - KW
6405 		     */
6406 		    return (NO);
6407 		}
6408 		if (anchors_this_screen > 0 &&
6409 		    anchors_this_screen <= nlinks &&
6410 		    a->line_num >= HTMainText->top_of_screen &&
6411 		    a->line_num < HTMainText->top_of_screen + (display_lines)) {
6412 		    /*
6413 		     * If the requested anchor is within the current screen,
6414 		     * just set *go_line so that the screen window won't move
6415 		     * (keep it as it is), and set *linknum to the index of
6416 		     * this link in the current links[] array.  - KW
6417 		     */
6418 		    *go_line = HTMainText->top_of_screen;
6419 		    if (linknum)
6420 			*linknum = anchors_this_screen - 1;
6421 		} else {
6422 		    /*
6423 		     * if the requested anchor is not within the currently
6424 		     * displayed screen, set *go_line such that the top line
6425 		     * will be either
6426 		     *  (1) the line immediately below the previous
6427 		     *      anchor, or
6428 		     *  (2) about one third of a screenful above the line
6429 		     *      with the target, or
6430 		     *  (3) the first line of the document -
6431 		     * whichever comes last.  In all cases the line with our
6432 		     * target will end up being the first line with any links
6433 		     * on the new screen, so that we can use the
6434 		     * anchors_this_line counter to point to the anchor in
6435 		     * the new links[] array.  - kw
6436 		     */
6437 		    int max_offset = SEARCH_GOAL_LINE - 1;
6438 
6439 		    if (max_offset < 0)
6440 			max_offset = 0;
6441 		    else if (max_offset >= display_lines)
6442 			max_offset = display_lines - 1;
6443 		    *go_line = prev_anchor_line - max_offset;
6444 		    if (*go_line <= prev_prev_anchor_line)
6445 			*go_line = prev_prev_anchor_line + 1;
6446 		    if (*go_line < 0)
6447 			*go_line = 0;
6448 		    if (linknum)
6449 			*linknum = anchors_this_line - 1;
6450 		}
6451 		return (LINK_LINE_FOUND);
6452 	    } else {
6453 		*hightext = LYGetHiTextStr(a, 0);
6454 		link_dest = HTAnchor_followLink(a->anchor);
6455 		{
6456 		    char *cp_freeme = NULL;
6457 
6458 		    if (traversal) {
6459 			cp_freeme = stub_HTAnchor_address(link_dest);
6460 		    } else if (track_internal_links) {
6461 			if (a->link_type == INTERNAL_LINK_ANCHOR) {
6462 			    link_dest_intl =
6463 				HTAnchor_followTypedLink(a->anchor, HTInternalLink);
6464 			    if (link_dest_intl && link_dest_intl != link_dest) {
6465 
6466 				CTRACE((tfp,
6467 					"HTGetLinkInfo: unexpected typed link to %s!\n",
6468 					link_dest_intl->parent->address));
6469 				link_dest_intl = NULL;
6470 			    }
6471 			}
6472 			if (link_dest_intl) {
6473 			    char *cp2 = HTAnchor_address(link_dest_intl);
6474 
6475 			    FREE(*lname);
6476 			    *lname = cp2;
6477 			    return (WWW_INTERN_LINK_TYPE);
6478 			} else {
6479 			    cp_freeme = HTAnchor_address(link_dest);
6480 			}
6481 		    } else {
6482 			cp_freeme = HTAnchor_address(link_dest);
6483 		    }
6484 		    StrAllocCopy(*lname, cp_freeme);
6485 		    FREE(cp_freeme);
6486 		}
6487 		return (WWW_LINK_TYPE);
6488 	    }
6489 	}
6490     }
6491     return (NO);
6492 }
6493 
same_anchor_or_field(int numberA,FormInfo * formA,int numberB,FormInfo * formB,int ta_same)6494 static BOOLEAN same_anchor_or_field(int numberA,
6495 				    FormInfo * formA,
6496 				    int numberB,
6497 				    FormInfo * formB,
6498 				    int ta_same)
6499 {
6500     if (numberA > 0 || numberB > 0) {
6501 	if (numberA == numberB)
6502 	    return (YES);
6503 	else if (!ta_same)
6504 	    return (NO);
6505     }
6506     if (formA || formB) {
6507 	if (formA == formB) {
6508 	    return (YES);
6509 	} else if (!ta_same) {
6510 	    return (NO);
6511 	} else if (!(formA && formB)) {
6512 	    return (NO);
6513 	}
6514     } else {
6515 	return (NO);
6516     }
6517     if (formA->type != formB->type ||
6518 	formA->type != F_TEXTAREA_TYPE ||
6519 	formB->type != F_TEXTAREA_TYPE) {
6520 	return (NO);
6521     }
6522     if (formA->number != formB->number)
6523 	return (NO);
6524     if (!formA->name || !formB->name)
6525 	return (YES);
6526     return (BOOL) (strcmp(formA->name, formB->name) == 0);
6527 }
6528 
6529 #define same_anchor_as_link(i,a,ta_same) (BOOL) (i >= 0 && a && \
6530 		same_anchor_or_field(links[i].anchor_number,\
6531 		(links[i].type == WWW_FORM_LINK_TYPE) ? links[i].l_form : NULL,\
6532 		a->number,\
6533 		(a->link_type == INPUT_ANCHOR) ? a->input_field : NULL,\
6534 		ta_same))
6535 #define same_anchors(a1,a2,ta_same) (BOOL) (a1 && a2 && \
6536 		same_anchor_or_field(a1->number,\
6537 		(a1->link_type == INPUT_ANCHOR) ? a1->input_field : NULL,\
6538 		a2->number,\
6539 		(a2->link_type == INPUT_ANCHOR) ? a2->input_field : NULL,\
6540 		ta_same))
6541 
6542 /*
6543  * Are there more textarea lines belonging to the same textarea before
6544  * (direction < 0) or after (direction > 0) the current one?
6545  * On entry, curlink must be the index in links[] of a textarea field.  - kw
6546  */
HText_TAHasMoreLines(int curlink,int direction)6547 BOOL HText_TAHasMoreLines(int curlink,
6548 			  int direction)
6549 {
6550     TextAnchor *a;
6551     TextAnchor *prev_a = NULL;
6552 
6553     if (!HTMainText)
6554 	return (NO);
6555     if (direction < 0) {
6556 	for (a = HTMainText->first_anchor; a; prev_a = a, a = a->next) {
6557 	    if (a->link_type == INPUT_ANCHOR &&
6558 		links[curlink].l_form == a->input_field) {
6559 		return same_anchors(a, prev_a, TRUE);
6560 	    }
6561 	    if (links[curlink].anchor_number &&
6562 		a->number >= links[curlink].anchor_number)
6563 		break;
6564 	}
6565 	return NO;
6566     } else {
6567 	for (a = HTMainText->first_anchor; a; a = a->next) {
6568 	    if (a->link_type == INPUT_ANCHOR &&
6569 		links[curlink].l_form == a->input_field) {
6570 		return same_anchors(a, a->next, TRUE);
6571 	    }
6572 	    if (links[curlink].anchor_number &&
6573 		a->number >= links[curlink].anchor_number)
6574 		break;
6575 	}
6576 	return NO;
6577     }
6578 }
6579 
6580 /*
6581  * HTGetLinkOrFieldStart - moving to previous or next link or form field.
6582  *
6583  * On input,
6584  *	curlink: current link, as index in links[] array (-1 if none)
6585  *	direction: whether to move up or down (or stay where we are)
6586  *	ta_skip: if FALSE, input fields belonging to the same textarea are
6587  *		 are treated as different fields, as usual;
6588  *		 if TRUE, fields of the same textarea are treated as a
6589  *		 group for skipping.
6590  * The caller wants information for positioning on the new link to be
6591  * deposited in *go_line and (if linknum is not NULL) *linknum.
6592  *
6593  * On failure (no more links in the requested direction) returns NO
6594  * and doesn't change *go_line or *linknum.  Otherwise, LINK_DO_ARROWUP
6595  * may be returned, and *go_line and *linknum not changed, to indicate that
6596  * the caller should use a normal PREV_LINK or PREV_PAGE mechanism.
6597  * Otherwise:
6598  * The number (0-based counting) for the new top screen line will be returned
6599  * in *go_line, and *linknum will be set to an index into the links[] array,
6600  * to use after the line in *go_line has been made the new top screen
6601  * line.  - kw
6602  */
HTGetLinkOrFieldStart(int curlink,int * go_line,int * linknum,int direction,int ta_skip)6603 int HTGetLinkOrFieldStart(int curlink,
6604 			  int *go_line,
6605 			  int *linknum,
6606 			  int direction,
6607 			  int ta_skip)
6608 {
6609     TextAnchor *a;
6610     int anchors_this_line = 0;
6611     int prev_anchor_line = -1, prev_prev_anchor_line = -1;
6612 
6613     struct agroup {
6614 	TextAnchor *anc;
6615 	int prev_anchor_line;
6616 	int anchors_this_line;
6617 	int anchors_this_group;
6618     } previous, current;
6619     struct agroup *group_to_go = NULL;
6620 
6621     if (!HTMainText)
6622 	return (NO);
6623 
6624     previous.anc = current.anc = NULL;
6625     previous.prev_anchor_line = current.prev_anchor_line = -1;
6626     previous.anchors_this_line = current.anchors_this_line = 0;
6627     previous.anchors_this_group = current.anchors_this_group = 0;
6628 
6629     for (a = HTMainText->first_anchor; a; a = a->next) {
6630 	/*
6631 	 * Count anchors, first on current line if there is more
6632 	 * than one.  We have to count all links, including form
6633 	 * field anchors and others with a->number == 0, because
6634 	 * they are or will be included in the links[] array.
6635 	 * The exceptions are hidden form fields and anchors with
6636 	 * show_anchor not set, because they won't appear in links[]
6637 	 * and don't count towards nlinks.  - KW
6638 	 */
6639 	if ((a->show_anchor) &&
6640 	    !(a->link_type == INPUT_ANCHOR
6641 	      && a->input_field->type == F_HIDDEN_TYPE)) {
6642 	    if (a->line_num == prev_anchor_line) {
6643 		anchors_this_line++;
6644 	    } else {
6645 		/*
6646 		 * This anchor is on a different line than the previous one.
6647 		 * Remember which was the line number of the previous anchor,
6648 		 * for use in screen positioning later.  - KW
6649 		 */
6650 		anchors_this_line = 1;
6651 		prev_prev_anchor_line = prev_anchor_line;
6652 		prev_anchor_line = a->line_num;
6653 	    }
6654 
6655 	    if (!same_anchors(current.anc, a, ta_skip)) {
6656 		previous.anc = current.anc;
6657 		previous.prev_anchor_line = current.prev_anchor_line;
6658 		previous.anchors_this_line = current.anchors_this_line;
6659 		previous.anchors_this_group = current.anchors_this_group;
6660 		current.anc = a;
6661 		current.prev_anchor_line = prev_prev_anchor_line;
6662 		current.anchors_this_line = anchors_this_line;
6663 		current.anchors_this_group = 1;
6664 	    } else {
6665 		current.anchors_this_group++;
6666 	    }
6667 	    if (curlink >= 0) {
6668 		if (same_anchor_as_link(curlink, a, ta_skip)) {
6669 		    if (direction == -1) {
6670 			group_to_go = &previous;
6671 			break;
6672 		    } else if (direction == 0) {
6673 			group_to_go = &current;
6674 			break;
6675 		    }
6676 		} else if (direction > 0 &&
6677 			   same_anchor_as_link(curlink, previous.anc, ta_skip)) {
6678 		    group_to_go = &current;
6679 		    break;
6680 		}
6681 	    } else {
6682 		if (a->line_num >= HTMainText->top_of_screen) {
6683 		    if (direction < 0) {
6684 			group_to_go = &previous;
6685 			break;
6686 		    } else if (direction == 0) {
6687 			if (previous.anc) {
6688 			    group_to_go = &previous;
6689 			    break;
6690 			} else {
6691 			    group_to_go = &current;
6692 			    break;
6693 			}
6694 		    } else {
6695 			group_to_go = &current;
6696 			break;
6697 		    }
6698 		}
6699 	    }
6700 	}
6701     }
6702     if (!group_to_go && curlink < 0 && direction <= 0) {
6703 	group_to_go = &current;
6704     }
6705     if (group_to_go) {
6706 	a = group_to_go->anc;
6707 	if (a) {
6708 	    int max_offset;
6709 
6710 	    /*
6711 	     * We know where to go; most of the stuff below is just
6712 	     * tweaks to try to position the new screen in a specific
6713 	     * way.
6714 	     *
6715 	     * In some cases going to a previous link can be done
6716 	     * via the normal LYK_PREV_LINK action, which may give
6717 	     * better positioning of the new screen.  - kw
6718 	     */
6719 	    if (a->line_num < HTMainText->top_of_screen &&
6720 		a->line_num >= HTMainText->top_of_screen - (display_lines)) {
6721 		if ((curlink < 0 &&
6722 		     group_to_go->anchors_this_group == 1) ||
6723 		    (direction < 0 &&
6724 		     group_to_go != &current &&
6725 		     current.anc &&
6726 		     current.anc->line_num >= HTMainText->top_of_screen &&
6727 		     group_to_go->anchors_this_group == 1) ||
6728 		    (a->next &&
6729 		     a->next->line_num >= HTMainText->top_of_screen)) {
6730 		    return (LINK_DO_ARROWUP);
6731 		}
6732 	    }
6733 	    /*
6734 	     * The fundamental limitation of the current anchors_this_line
6735 	     * counter method is that we only can set *linknum to the right
6736 	     * index into the future links[] array if the line with our link
6737 	     * ends up being the first line with any links (that count) on
6738 	     * the new screen.  Subject to that restriction we still have
6739 	     * some vertical liberty (sometimes), and try to make the best
6740 	     * of it.  It may be a question of taste though.  - kw
6741 	     */
6742 	    if (a->line_num <= (display_lines)) {
6743 		max_offset = 0;
6744 	    } else if (a->line_num < HTMainText->top_of_screen) {
6745 		int screensback =
6746 		(HTMainText->top_of_screen - a->line_num + (display_lines) - 1)
6747 		/ (display_lines);
6748 
6749 		max_offset = a->line_num - (HTMainText->top_of_screen -
6750 					    screensback * (display_lines));
6751 	    } else if (HTMainText->Lines - a->line_num <= (display_lines)) {
6752 		max_offset = a->line_num - (HTMainText->Lines + 1
6753 					    - (display_lines));
6754 	    } else if (a->line_num >=
6755 		       HTMainText->top_of_screen + (display_lines)) {
6756 		int screensahead =
6757 		(a->line_num - HTMainText->top_of_screen) / (display_lines);
6758 
6759 		max_offset = a->line_num - HTMainText->top_of_screen -
6760 		    screensahead * (display_lines);
6761 	    } else {
6762 		max_offset = SEARCH_GOAL_LINE - 1;
6763 	    }
6764 
6765 	    /* Stuff below should remain unchanged if line positioning
6766 	       is tweaked. - kw */
6767 	    if (max_offset < 0)
6768 		max_offset = 0;
6769 	    else if (max_offset >= display_lines)
6770 		max_offset = display_lines - 1;
6771 	    *go_line = a->line_num - max_offset;
6772 	    if (*go_line <= group_to_go->prev_anchor_line)
6773 		*go_line = group_to_go->prev_anchor_line + 1;
6774 
6775 	    if (*go_line < 0)
6776 		*go_line = 0;
6777 	    if (linknum)
6778 		*linknum = group_to_go->anchors_this_line - 1;
6779 	    return (LINK_LINE_FOUND);
6780 	}
6781     }
6782     return (NO);
6783 }
6784 
6785 /*
6786  * This function finds the line indicated by line_num in the
6787  * HText structure indicated by text, and searches that line
6788  * for the first hit with the string indicated by target.  If
6789  * there is no hit, FALSE is returned.  If there is a hit, then
6790  * a copy of the line starting at that first hit is loaded into
6791  * *data with all IsSpecial characters stripped, its offset and
6792  * the printable target length (without IsSpecial, or extra CJK
6793  * or utf8 characters) are loaded into *offset and *tLen, and
6794  * TRUE is returned.  -FM
6795  */
HText_getFirstTargetInLine(HText * text,int line_num,int utf_flag,int * offset,int * tLen,char ** data,const char * target)6796 BOOL HText_getFirstTargetInLine(HText *text, int line_num,
6797 				int utf_flag,
6798 				int *offset,
6799 				int *tLen,
6800 				char **data,
6801 				const char *target)
6802 {
6803     HTLine *line;
6804     char *LineData;
6805     int LineOffset, HitOffset, LenNeeded, i;
6806     const char *cp;
6807 
6808     /*
6809      * Make sure we have an HText structure, that line_num is
6810      * in its range, and that we have a target string.  -FM
6811      */
6812     if (!(text &&
6813 	  line_num >= 0 &&
6814 	  line_num <= text->Lines &&
6815 	  non_empty(target))) {
6816 	return (FALSE);
6817     }
6818 
6819     /*
6820      * Find the line and set up its data and offset -FM
6821      */
6822     for (i = 0, line = FirstHTLine(text);
6823 	 i < line_num && (line != text->last_line);
6824 	 i++, line = line->next) {
6825 	if (line->next == NULL) {
6826 	    return (FALSE);
6827 	}
6828     }
6829     if (!(line && line->data[0]))
6830 	return (FALSE);
6831     LineData = (char *) line->data;
6832     LineOffset = (int) line->offset;
6833 
6834     /*
6835      * If the target is on the line, load the offset of
6836      * its first character and the subsequent line data,
6837      * strip any special characters from the loaded line
6838      * data, and return TRUE.  -FM
6839      */
6840     if (((cp = LYno_attr_mb_strstr(LineData,
6841 				   target,
6842 				   utf_flag, YES,
6843 				   &HitOffset,
6844 				   &LenNeeded)) != NULL) &&
6845 	(LineOffset + LenNeeded) <= DISPLAY_COLS) {
6846 	/*
6847 	 * We had a hit so load the results,
6848 	 * remove IsSpecial characters from
6849 	 * the allocated data string, and
6850 	 * return TRUE.  -FM
6851 	 */
6852 	*offset = (LineOffset + HitOffset);
6853 	*tLen = (LenNeeded - HitOffset);
6854 	StrAllocCopy(*data, cp);
6855 	remove_special_attr_chars(*data);
6856 	return (TRUE);
6857     }
6858 
6859     /*
6860      * The line does not contain the target.  -FM
6861      */
6862     return (FALSE);
6863 }
6864 
6865 /*
6866  * HText_getNumOfLines returns the number of lines in the
6867  * current document.
6868  */
HText_getNumOfLines(void)6869 int HText_getNumOfLines(void)
6870 {
6871     return (HTMainText ? HTMainText->Lines : 0);
6872 }
6873 
6874 /*
6875  * HText_getNumOfBytes returns the size of the document, as rendered.  This
6876  * may be different from the original filesize.
6877  */
HText_getNumOfBytes(void)6878 int HText_getNumOfBytes(void)
6879 {
6880     int result = -1;
6881     HTLine *line = NULL;
6882 
6883     if (HTMainText != 0) {
6884 	for (line = FirstHTLine(HTMainText);
6885 	     line != HTMainText->last_line;
6886 	     line = line->next) {
6887 	    result += 1 + (int) strlen(line->data);
6888 	}
6889     }
6890     return result;
6891 }
6892 
6893 /*
6894  * HText_getTitle returns the title of the
6895  * current document.
6896  */
HText_getTitle(void)6897 const char *HText_getTitle(void)
6898 {
6899     return (HTMainText ?
6900 	    HTAnchor_title(HTMainText->node_anchor) : 0);
6901 }
6902 
6903 #ifdef USE_COLOR_STYLE
HText_getStyle(void)6904 const char *HText_getStyle(void)
6905 {
6906     return (HTMainText ?
6907 	    HTAnchor_style(HTMainText->node_anchor) : 0);
6908 }
6909 #endif
6910 
6911 /*
6912  * HText_getSugFname returns the suggested filename of the current
6913  * document (normally derived from a Content-Disposition header with
6914  * attachment; filename=name.suffix).  -FM
6915  */
HText_getSugFname(void)6916 const char *HText_getSugFname(void)
6917 {
6918     return (HTMainText ?
6919 	    HTAnchor_SugFname(HTMainText->node_anchor) : 0);
6920 }
6921 
6922 /*
6923  * HTCheckFnameForCompression receives the address of an allocated
6924  * string containing a filename, and an anchor pointer, and expands
6925  * or truncates the string's suffix if appropriate, based on whether
6926  * the anchor indicates that the file is compressed.  We assume
6927  * that the file was not uncompressed (as when downloading), and
6928  * believe the headers about whether it's compressed or not.  -FM
6929  *
6930  * Added third arg - if strip_ok is FALSE, we don't trust the anchor
6931  * info enough to remove a compression suffix if the anchor object
6932  * does not indicate compression.  - kw
6933  */
HTCheckFnameForCompression(char ** fname,HTParentAnchor * anchor,int strip_ok)6934 void HTCheckFnameForCompression(char **fname,
6935 				HTParentAnchor *anchor,
6936 				int strip_ok)
6937 {
6938     char *fn = *fname;
6939     char *dot = NULL;
6940     char *cp = NULL;
6941     const char *suffix = "";
6942     CompressFileType method;
6943     CompressFileType second;
6944 
6945     /*
6946      * Make sure we have a string and anchor.  -FM
6947      */
6948     if (!(fn && anchor))
6949 	return;
6950 
6951     /*
6952      * Make sure we have a file, not directory, name.  -FM
6953      */
6954     if (*(fn = LYPathLeaf(fn)) == '\0')
6955 	return;
6956 
6957     method = HTContentToCompressType(anchor);
6958 
6959     /*
6960      * If no Content-Encoding has been detected via the anchor
6961      * pointer, but strip_ok is not set, there is nothing left
6962      * to do.  - kw
6963      */
6964     if ((method == cftNone) && !strip_ok)
6965 	return;
6966 
6967     /*
6968      * Treat .tgz specially
6969      */
6970     if ((dot = strrchr(fn, '.')) != NULL
6971 	&& !strcasecomp(dot, ".tgz")) {
6972 	if (method == cftNone) {
6973 	    strcpy(dot, ".tar");
6974 	}
6975 	return;
6976     }
6977 
6978     /*
6979      * Seek the last dot, and check whether
6980      * we have a gzip or compress suffix.  -FM
6981      */
6982     if ((dot = strrchr(fn, '.')) != NULL) {
6983 	int rootlen = 0;
6984 
6985 	if (HTCompressFileType(fn, ".", &rootlen) != cftNone) {
6986 	    if (method == cftNone) {
6987 		/*
6988 		 * It has a suffix which signifies a gzipped
6989 		 * or compressed file for us, but the anchor
6990 		 * claims otherwise, so tweak the suffix.  -FM
6991 		 */
6992 		*dot = '\0';
6993 	    }
6994 	    return;
6995 	}
6996 	if ((second = HTCompressFileType(fn, "-_", &rootlen)) != cftNone) {
6997 	    cp = fn + rootlen;
6998 	    if (method == cftNone) {
6999 		/*
7000 		 * It has a tail which signifies a gzipped
7001 		 * file for us, but the anchor claims otherwise,
7002 		 * so tweak the suffix.  -FM
7003 		 */
7004 		if (cp == dot + 1)
7005 		    cp--;
7006 		*cp = '\0';
7007 	    } else {
7008 		/*
7009 		 * The anchor claims it's gzipped, and we
7010 		 * believe it, so force this tail to the
7011 		 * conventional suffix.  -FM
7012 		 */
7013 #ifdef VMS
7014 		*cp = '-';
7015 #else
7016 		*cp = '.';
7017 #endif /* VMS */
7018 		if (second == cftCompress)
7019 		    LYUpperCase(cp);
7020 		else
7021 		    LYLowerCase(cp);
7022 	    }
7023 	    return;
7024 	}
7025     }
7026 
7027     suffix = HTCompressTypeToSuffix(method);
7028 
7029     /*
7030      * Add the appropriate suffix.  -FM
7031      */
7032     if (*suffix) {
7033 	if (!dot) {
7034 	    StrAllocCat(*fname, suffix);
7035 	} else if (*++dot == '\0') {
7036 	    StrAllocCat(*fname, suffix + 1);
7037 	} else {
7038 	    StrAllocCat(*fname, suffix);
7039 #ifdef VMS
7040 	    (*fname)[strlen(*fname) - strlen(suffix)] = '-';
7041 #endif /* !VMS */
7042 	}
7043     }
7044 }
7045 
7046 /*
7047  * HText_getLastModified returns the Last-Modified header
7048  * if available, for the current document.  -FM
7049  */
HText_getLastModified(void)7050 const char *HText_getLastModified(void)
7051 {
7052     return (HTMainText ?
7053 	    HTAnchor_last_modified(HTMainText->node_anchor) : 0);
7054 }
7055 
7056 /*
7057  * HText_getDate returns the Date header
7058  * if available, for the current document.  -FM
7059  */
HText_getDate(void)7060 const char *HText_getDate(void)
7061 {
7062     return (HTMainText ?
7063 	    HTAnchor_date(HTMainText->node_anchor) : 0);
7064 }
7065 
7066 /*
7067  * HText_getServer returns the Server header
7068  * if available, for the current document.  -FM
7069  */
HText_getServer(void)7070 const char *HText_getServer(void)
7071 {
7072     return (HTMainText ?
7073 	    HTAnchor_server(HTMainText->node_anchor) : 0);
7074 }
7075 
7076 /*
7077  * Returns the full text of HTTP headers, if available, for the current
7078  * document.
7079  */
HText_getHttpHeaders(void)7080 const char *HText_getHttpHeaders(void)
7081 {
7082     return (HTMainText ?
7083 	    HTAnchor_http_headers(HTMainText->node_anchor) : 0);
7084 }
7085 
7086 /*
7087  * HText_pageDisplay displays a screen of text
7088  * starting from the line 'line_num'-1.
7089  * This is the primary call for lynx.
7090  */
HText_pageDisplay(int line_num,char * target)7091 void HText_pageDisplay(int line_num,
7092 		       char *target)
7093 {
7094 #ifdef DISP_PARTIAL
7095     if (debug_display_partial || (LYTraceLogFP != NULL)) {
7096 	CTRACE((tfp, "GridText: HText_pageDisplay at line %d started\n", line_num));
7097     }
7098 
7099     if (display_partial) {
7100 	int stop_before = -1;
7101 
7102 	/*
7103 	 * Garbage is reported from forms input fields in incremental mode.
7104 	 * So we start HText_trimHightext() to forget this side effect.
7105 	 * This function was split-out from HText_endAppend().
7106 	 * It may not be the best solution but it works.  - LP
7107 	 *
7108 	 * (FALSE = indicate that we are in partial mode)
7109 	 * Multiple calls of HText_trimHightext works without problem now.
7110 	 */
7111 	if (HTMainText && HTMainText->stbl)
7112 	    stop_before = Stbl_getStartLineDeep(HTMainText->stbl);
7113 	HText_trimHightext(HTMainText, FALSE, stop_before);
7114     }
7115 #endif
7116     display_page(HTMainText, line_num - 1, target);
7117 
7118 #ifdef DISP_PARTIAL
7119     if (display_partial && debug_display_partial)
7120 	LYSleepMsg();
7121 #endif
7122 
7123     is_www_index = HTAnchor_isIndex(HTMainAnchor);
7124 
7125 #ifdef DISP_PARTIAL
7126     if (debug_display_partial || (LYTraceLogFP != NULL)) {
7127 	CTRACE((tfp, "GridText: HText_pageDisplay finished\n"));
7128     }
7129 #endif
7130 }
7131 
7132 /*
7133  * Return YES if we have a whereis search target on the displayed
7134  * page.  - kw
7135  */
HText_pageHasPrevTarget(void)7136 BOOL HText_pageHasPrevTarget(void)
7137 {
7138     if (!HTMainText)
7139 	return NO;
7140     else
7141 	return HTMainText->page_has_target;
7142 }
7143 
7144 /*
7145  * Find the number of the closest anchor to the given document offset.  Used
7146  * in reparsing, this will usually find an exact match, as a link shifts around
7147  * on the display.  It will not find a match when (for example) the source view
7148  * shows images that are not links in the html.
7149  */
HText_closestAnchor(HText * text,int offset)7150 int HText_closestAnchor(HText *text, int offset)
7151 {
7152     int result = -1;
7153     int absdiff = 0;
7154     int newdiff;
7155     TextAnchor *Anchor_ptr = NULL;
7156     TextAnchor *closest = NULL;
7157 
7158     for (Anchor_ptr = text->first_anchor;
7159 	 Anchor_ptr != NULL;
7160 	 Anchor_ptr = Anchor_ptr->next) {
7161 	if (Anchor_ptr->sgml_offset == offset) {
7162 	    result = Anchor_ptr->number;
7163 	    break;
7164 	} else {
7165 	    newdiff = abs(Anchor_ptr->sgml_offset - offset);
7166 	    if (absdiff == 0 || absdiff > newdiff) {
7167 		absdiff = newdiff;
7168 		closest = Anchor_ptr;
7169 	    }
7170 	}
7171     }
7172     if (result < 0 && closest != 0) {
7173 	result = closest->number;
7174     }
7175 
7176     return result;
7177 }
7178 
7179 /*
7180  * Find the offset for the given anchor, e.g., the inverse of
7181  * HText_closestAnchor().
7182  */
HText_locateAnchor(HText * text,int anchor_number)7183 int HText_locateAnchor(HText *text, int anchor_number)
7184 {
7185     int result = -1;
7186     TextAnchor *Anchor_ptr = NULL;
7187 
7188     for (Anchor_ptr = text->first_anchor;
7189 	 Anchor_ptr != NULL;
7190 	 Anchor_ptr = Anchor_ptr->next) {
7191 	if (Anchor_ptr->number == anchor_number) {
7192 	    result = Anchor_ptr->sgml_offset;
7193 	    break;
7194 	}
7195     }
7196 
7197     return result;
7198 }
7199 
7200 /*
7201  * This is supposed to give the same result as the inline checks in
7202  * display_page(), so we can decide which anchors will be visible.
7203  */
anchor_is_numbered(TextAnchor * Anchor_ptr)7204 static BOOL anchor_is_numbered(TextAnchor *Anchor_ptr)
7205 {
7206     BOOL result = FALSE;
7207 
7208     if (Anchor_ptr->show_anchor
7209 	&& (Anchor_ptr->link_type & HYPERTEXT_ANCHOR)) {
7210 	result = TRUE;
7211     } else if (Anchor_ptr->link_type == INPUT_ANCHOR
7212 	       && Anchor_ptr->input_field->type != F_HIDDEN_TYPE) {
7213 	result = TRUE;
7214     }
7215     return result;
7216 }
7217 
7218 /*
7219  * Return the absolute line number (counting from the beginning of the
7220  * document) for the given absolute anchor number.  Normally line numbers are
7221  * computed within the screen, and for that we use the links[] array.  A few
7222  * uses require the absolute anchor number.  For example, reparsing a document,
7223  * e.g., switching between normal and source views will alter the line numbers
7224  * of each link, and may require adjusting the top line number used for the
7225  * display, before we recompute the links[] array.
7226  */
HText_getAbsLineNumber(HText * text,int anchor_number)7227 int HText_getAbsLineNumber(HText *text,
7228 			   int anchor_number)
7229 {
7230     int result = -1;
7231 
7232     if (anchor_number >= 0 && text != 0) {
7233 	TextAnchor *Anchor_ptr = NULL;
7234 
7235 	for (Anchor_ptr = text->first_anchor;
7236 	     Anchor_ptr != NULL;
7237 	     Anchor_ptr = Anchor_ptr->next) {
7238 	    if (anchor_is_numbered(Anchor_ptr)
7239 		&& Anchor_ptr->number == anchor_number) {
7240 		result = Anchor_ptr->line_num;
7241 		break;
7242 	    }
7243 	}
7244     }
7245     return result;
7246 }
7247 
7248 /*
7249  * Compute the link-number in a page, given the top line number of the page and
7250  * the absolute anchor number.
7251  */
HText_anchorRelativeTo(HText * text,int top_lineno,int anchor_number)7252 int HText_anchorRelativeTo(HText *text, int top_lineno, int anchor_number)
7253 {
7254     int result = 0;
7255     int from_top = 0;
7256     TextAnchor *Anchor_ptr = NULL;
7257 
7258     for (Anchor_ptr = text->first_anchor;
7259 	 Anchor_ptr != NULL;
7260 	 Anchor_ptr = Anchor_ptr->next) {
7261 	if (Anchor_ptr->number == anchor_number) {
7262 	    result = from_top;
7263 	    break;
7264 	}
7265 	if (!anchor_is_numbered(Anchor_ptr))
7266 	    continue;
7267 	if (Anchor_ptr->line_num >= top_lineno) {
7268 	    ++from_top;
7269 	}
7270     }
7271     return result;
7272 }
7273 
7274 /*
7275  * HText_LinksInLines returns the number of links in the
7276  * 'Lines' number of lines beginning with 'line_num'-1.  -FM
7277  */
HText_LinksInLines(HText * text,int line_num,int Lines)7278 int HText_LinksInLines(HText *text,
7279 		       int line_num,
7280 		       int Lines)
7281 {
7282     int total = 0;
7283     int start = (line_num - 1);
7284     int end = (start + Lines);
7285     TextAnchor *Anchor_ptr = NULL;
7286 
7287     if (!text)
7288 	return total;
7289 
7290     for (Anchor_ptr = text->first_anchor;
7291 	 Anchor_ptr != NULL && Anchor_ptr->line_num <= end;
7292 	 Anchor_ptr = Anchor_ptr->next) {
7293 	if (Anchor_ptr->line_num >= start &&
7294 	    Anchor_ptr->line_num < end &&
7295 	    Anchor_ptr->show_anchor &&
7296 	    !(Anchor_ptr->link_type == INPUT_ANCHOR
7297 	      && Anchor_ptr->input_field->type == F_HIDDEN_TYPE))
7298 	    ++total;
7299     }
7300 
7301     return total;
7302 }
7303 
HText_setStale(HText * text)7304 void HText_setStale(HText *text)
7305 {
7306     text->stale = YES;
7307 }
7308 
HText_refresh(HText * text)7309 void HText_refresh(HText *text)
7310 {
7311     if (text->stale)
7312 	display_page(text, text->top_of_screen, "");
7313 }
7314 
HText_sourceAnchors(HText * text)7315 int HText_sourceAnchors(HText *text)
7316 {
7317     return (text ? text->last_anchor_number : -1);
7318 }
7319 
HText_canScrollUp(HText * text)7320 BOOL HText_canScrollUp(HText *text)
7321 {
7322     return (BOOL) (text->top_of_screen != 0);
7323 }
7324 
7325 /*
7326  * Check if there is more info below this page.
7327  */
HText_canScrollDown(void)7328 BOOL HText_canScrollDown(void)
7329 {
7330     HText *text = HTMainText;
7331 
7332     return (BOOL) ((text != 0)
7333 		   && ((text->top_of_screen + display_lines) <= text->Lines));
7334 }
7335 
7336 /*		Scroll actions
7337 */
HText_scrollTop(HText * text)7338 void HText_scrollTop(HText *text)
7339 {
7340     display_page(text, 0, "");
7341 }
7342 
HText_scrollDown(HText * text)7343 void HText_scrollDown(HText *text)
7344 {
7345     display_page(text, text->top_of_screen + display_lines, "");
7346 }
7347 
HText_scrollUp(HText * text)7348 void HText_scrollUp(HText *text)
7349 {
7350     display_page(text, text->top_of_screen - display_lines, "");
7351 }
7352 
HText_scrollBottom(HText * text)7353 void HText_scrollBottom(HText *text)
7354 {
7355     display_page(text, text->Lines - display_lines, "");
7356 }
7357 
7358 /*		Browsing functions
7359  *		==================
7360  */
7361 
7362 /* Bring to front and highlight it
7363 */
HText_select(HText * text)7364 BOOL HText_select(HText *text)
7365 {
7366     if (text != HTMainText) {
7367 	/*
7368 	 * Reset flag for whereis search string - cannot be true here
7369 	 * since text is not our HTMainText.  - kw
7370 	 */
7371 	if (text)
7372 	    text->page_has_target = NO;
7373 
7374 #ifdef DISP_PARTIAL
7375 	/* Reset these for the previous and current text. - kw */
7376 	ResetPartialLinenos(text);
7377 	ResetPartialLinenos(HTMainText);
7378 #endif /* DISP_PARTIAL */
7379 
7380 #ifdef CAN_SWITCH_DISPLAY_CHARSET
7381 	/* text->UCLYhndl is not reset by META, so use a more circumvent way */
7382 	if (text->node_anchor->UCStages->s[UCT_STAGE_HTEXT].LYhndl
7383 	    != current_char_set)
7384 	    Switch_Display_Charset(text->node_anchor->UCStages->s[UCT_STAGE_HTEXT].LYhndl, SWITCH_DISPLAY_CHARSET_MAYBE);
7385 #endif
7386 	assert(text != NULL);
7387 	if (HTMainText) {
7388 	    if (HText_hasUTF8OutputSet(HTMainText) &&
7389 		HTLoadedDocumentEightbit() &&
7390 		IS_UTF8_TTY) {
7391 		text->had_utf8 = HTMainText->has_utf8;
7392 	    } else {
7393 		text->had_utf8 = NO;
7394 	    }
7395 	    HTMainText->has_utf8 = NO;
7396 	    text->has_utf8 = NO;
7397 	}
7398 
7399 	HTMainText = text;
7400 	HTMainAnchor = text->node_anchor;
7401 
7402 	/*
7403 	 * Make this text the most current in the loaded texts list.  -FM
7404 	 */
7405 	if (loaded_texts && HTList_removeObject(loaded_texts, text))
7406 	    HTList_addObject(loaded_texts, text);
7407     }
7408     return YES;
7409 }
7410 
7411 /*
7412  * This function returns TRUE if doc's post_data, address
7413  * and isHEAD elements are identical to those of a loaded
7414  * (memory cached) text.  -FM
7415  */
HText_POSTReplyLoaded(DocInfo * doc)7416 BOOL HText_POSTReplyLoaded(DocInfo *doc)
7417 {
7418     HText *text = NULL;
7419     HTList *cur = loaded_texts;
7420     bstring *post_data;
7421     char *address;
7422     BOOL is_head;
7423 
7424     /*
7425      * Make sure we have the structures.  -FM
7426      */
7427     if (!cur || !doc)
7428 	return (FALSE);
7429 
7430     /*
7431      * Make sure doc is for a POST reply.  -FM
7432      */
7433     if ((post_data = doc->post_data) == NULL ||
7434 	(address = doc->address) == NULL)
7435 	return (FALSE);
7436     is_head = doc->isHEAD;
7437 
7438     /*
7439      * Loop through the loaded texts looking for a
7440      * POST reply match.  -FM
7441      */
7442     while (NULL != (text = (HText *) HTList_nextObject(cur))) {
7443 	if (text->node_anchor &&
7444 	    text->node_anchor->post_data &&
7445 	    BINEQ(post_data, text->node_anchor->post_data) &&
7446 	    text->node_anchor->address &&
7447 	    !strcmp(address, text->node_anchor->address) &&
7448 	    is_head == text->node_anchor->isHEAD) {
7449 	    return (TRUE);
7450 	}
7451     }
7452 
7453     return (FALSE);
7454 }
7455 
HTFindPoundSelector(const char * selector)7456 BOOL HTFindPoundSelector(const char *selector)
7457 {
7458     TextAnchor *a;
7459 
7460     CTRACE((tfp, "FindPound: searching for \"%s\"\n", selector));
7461     for (a = HTMainText->first_anchor; a != 0; a = a->next) {
7462 
7463 	if (a->anchor && a->anchor->tag) {
7464 	    if (!strcmp(a->anchor->tag, selector)) {
7465 
7466 		www_search_result = a->line_num + 1;
7467 
7468 		CTRACE((tfp, "FindPound: Selecting anchor [%d] at line %d\n",
7469 			a->number, www_search_result));
7470 		if (!strcmp(selector, LYToolbarName)) {
7471 		    --www_search_result;
7472 		}
7473 		return (YES);
7474 	    }
7475 	}
7476     }
7477     return (NO);
7478 }
7479 
HText_selectAnchor(HText * text,HTChildAnchor * anchor)7480 BOOL HText_selectAnchor(HText *text, HTChildAnchor *anchor)
7481 {
7482     TextAnchor *a;
7483     int l;
7484 
7485     for (a = text->first_anchor; a; a = a->next) {
7486 	if (a->anchor == anchor)
7487 	    break;
7488     }
7489     if (!a) {
7490 	CTRACE((tfp, "HText: No such anchor in this text!\n"));
7491 	return NO;
7492     }
7493 
7494     if (text != HTMainText) {	/* Comment out by ??? */
7495 	HTMainText = text;	/* Put back in by tbl 921208 */
7496 	HTMainAnchor = text->node_anchor;
7497     }
7498     l = a->line_num;
7499 
7500     CTRACE((tfp, "HText: Selecting anchor [%d] at line %d\n",
7501 	    a->number, l));
7502 
7503     if (!text->stale &&
7504 	(l >= text->top_of_screen) &&
7505 	(l < text->top_of_screen + display_lines + 1))
7506 	return YES;
7507 
7508     www_search_result = l - (display_lines / 3);	/* put in global variable */
7509 
7510     return YES;
7511 }
7512 
7513 /*		Editing functions		- NOT IMPLEMENTED
7514  *		=================
7515  *
7516  *	These are called from the application.  There are many more functions
7517  *	not included here from the original text object.
7518  */
7519 
7520 /*	Style handling:
7521 */
7522 /*	Apply this style to the selection
7523 */
HText_applyStyle(HText * me GCC_UNUSED,HTStyle * style GCC_UNUSED)7524 void HText_applyStyle(HText *me GCC_UNUSED, HTStyle *style GCC_UNUSED)
7525 {
7526 
7527 }
7528 
7529 /*	Update all text with changed style.
7530 */
HText_updateStyle(HText * me GCC_UNUSED,HTStyle * style GCC_UNUSED)7531 void HText_updateStyle(HText *me GCC_UNUSED, HTStyle *style GCC_UNUSED)
7532 {
7533 
7534 }
7535 
7536 /*	Return style of  selection
7537 */
HText_selectionStyle(HText * me GCC_UNUSED,HTStyleSheet * sheet GCC_UNUSED)7538 HTStyle *HText_selectionStyle(HText *me GCC_UNUSED, HTStyleSheet *sheet GCC_UNUSED)
7539 {
7540     return 0;
7541 }
7542 
7543 /*	Paste in styled text
7544 */
HText_replaceSel(HText * me GCC_UNUSED,const char * aString GCC_UNUSED,HTStyle * aStyle GCC_UNUSED)7545 void HText_replaceSel(HText *me GCC_UNUSED, const char *aString GCC_UNUSED,
7546 		      HTStyle *aStyle GCC_UNUSED)
7547 {
7548 }
7549 
7550 /*	Apply this style to the selection and all similarly formatted text
7551  *	(style recovery only)
7552  */
HTextApplyToSimilar(HText * me GCC_UNUSED,HTStyle * style GCC_UNUSED)7553 void HTextApplyToSimilar(HText *me GCC_UNUSED, HTStyle *style GCC_UNUSED)
7554 {
7555 
7556 }
7557 
7558 /*	Select the first unstyled run.
7559  *	(style recovery only)
7560  */
HTextSelectUnstyled(HText * me GCC_UNUSED,HTStyleSheet * sheet GCC_UNUSED)7561 void HTextSelectUnstyled(HText *me GCC_UNUSED, HTStyleSheet *sheet GCC_UNUSED)
7562 {
7563 
7564 }
7565 
7566 /*	Anchor handling:
7567 */
HText_unlinkSelection(HText * me GCC_UNUSED)7568 void HText_unlinkSelection(HText *me GCC_UNUSED)
7569 {
7570 
7571 }
7572 
HText_referenceSelected(HText * me GCC_UNUSED)7573 HTAnchor *HText_referenceSelected(HText *me GCC_UNUSED)
7574 {
7575     return 0;
7576 }
7577 
HText_getTopOfScreen(void)7578 int HText_getTopOfScreen(void)
7579 {
7580     HText *text = HTMainText;
7581 
7582     return text != 0 ? text->top_of_screen : 0;
7583 }
7584 
HText_getLines(HText * text)7585 int HText_getLines(HText *text)
7586 {
7587     return text->Lines;
7588 }
7589 
7590 /*
7591  * Constrain the line number to be within the document.  The line number is
7592  * zero-based.
7593  */
HText_getPreferredTopLine(HText * text,int line_number)7594 int HText_getPreferredTopLine(HText *text, int line_number)
7595 {
7596     int last_screen = text->Lines - (display_lines - 2);
7597 
7598     if (text->Lines < display_lines) {
7599 	line_number = 0;
7600     } else if (line_number > text->Lines) {
7601 	line_number = last_screen;
7602     } else if (line_number < 0) {
7603 	line_number = 0;
7604     }
7605     return line_number;
7606 }
7607 
HText_linkSelTo(HText * me GCC_UNUSED,HTAnchor * anchor GCC_UNUSED)7608 HTAnchor *HText_linkSelTo(HText *me GCC_UNUSED,
7609 			  HTAnchor * anchor GCC_UNUSED)
7610 {
7611     return 0;
7612 }
7613 
7614 /*
7615  * Utility for freeing the list of previous isindex and whereis queries.  -FM
7616  */
HTSearchQueries_free(void)7617 void HTSearchQueries_free(void)
7618 {
7619     LYFreeStringList(search_queries);
7620     search_queries = NULL;
7621 }
7622 
7623 /*
7624  * Utility for listing isindex and whereis queries, making
7625  * any repeated queries the most current in the list.  -FM
7626  */
HTAddSearchQuery(char * query)7627 void HTAddSearchQuery(char *query)
7628 {
7629     char *new_query = NULL;
7630     char *old;
7631     HTList *cur;
7632 
7633     if (!non_empty(query))
7634 	return;
7635 
7636     StrAllocCopy(new_query, query);
7637 
7638     if (!search_queries) {
7639 	search_queries = HTList_new();
7640 #ifdef LY_FIND_LEAKS
7641 	atexit(HTSearchQueries_free);
7642 #endif
7643 	HTList_addObject(search_queries, new_query);
7644 	return;
7645     }
7646 
7647     cur = search_queries;
7648     while (NULL != (old = (char *) HTList_nextObject(cur))) {
7649 	if (!strcmp(old, new_query)) {
7650 	    HTList_removeObject(search_queries, old);
7651 	    FREE(old);
7652 	    break;
7653 	}
7654     }
7655     HTList_addObject(search_queries, new_query);
7656 
7657     return;
7658 }
7659 
do_www_search(DocInfo * doc)7660 int do_www_search(DocInfo *doc)
7661 {
7662     bstring *searchstring = NULL;
7663     bstring *temp = NULL;
7664     char *cp;
7665     char *tmpaddress = NULL;
7666     int ch;
7667     RecallType recall;
7668     int QueryTotal;
7669     int QueryNum;
7670     BOOLEAN PreviousSearch = FALSE;
7671     int code;
7672 
7673     /*
7674      * Load the default query buffer
7675      */
7676     if ((cp = StrChr(doc->address, '?')) != NULL) {
7677 	/*
7678 	 * This is an index from a previous search.
7679 	 * Use its query as the default.
7680 	 */
7681 	PreviousSearch = TRUE;
7682 	BStrCopy0(searchstring, ++cp);
7683 	for (cp = searchstring->str; *cp; cp++)
7684 	    if (*cp == '+')
7685 		*cp = ' ';
7686 	HTUnEscape(searchstring->str);
7687 	BStrCopy(temp, searchstring);
7688 	/*
7689 	 * Make sure it's treated as the most recent query.  -FM
7690 	 */
7691 	HTAddSearchQuery(searchstring->str);
7692     } else {
7693 	/*
7694 	 * New search; no default.
7695 	 */
7696 	BStrCopy0(searchstring, "");
7697 	BStrCopy0(temp, "");
7698     }
7699 
7700     /*
7701      * Prompt for a query string.
7702      */
7703     if (isBEmpty(searchstring)) {
7704 	if (HTMainAnchor->isIndexPrompt)
7705 	    _statusline(HTMainAnchor->isIndexPrompt);
7706 	else
7707 	    _statusline(ENTER_DATABASE_QUERY);
7708     } else
7709 	_statusline(EDIT_CURRENT_QUERY);
7710     QueryTotal = (search_queries ? HTList_count(search_queries) : 0);
7711     recall = (((PreviousSearch && QueryTotal >= 2) ||
7712 	       (!PreviousSearch && QueryTotal >= 1)) ? RECALL_URL : NORECALL);
7713     QueryNum = QueryTotal;
7714 
7715   get_query:
7716     if ((ch = LYgetBString(&searchstring, FALSE, 0, recall)) < 0 ||
7717 	isBEmpty(searchstring) ||
7718 	ch == UPARROW_KEY ||
7719 	ch == DNARROW_KEY) {
7720 
7721 	if (recall && ch == UPARROW_KEY) {
7722 	    if (PreviousSearch) {
7723 		/*
7724 		 * Use the second to last query in the list.  -FM
7725 		 */
7726 		QueryNum = 1;
7727 		PreviousSearch = FALSE;
7728 	    } else {
7729 		/*
7730 		 * Go back to the previous query in the list.  -FM
7731 		 */
7732 		QueryNum++;
7733 	    }
7734 	    if (QueryNum >= QueryTotal)
7735 		/*
7736 		 * Roll around to the last query in the list.  -FM
7737 		 */
7738 		QueryNum = 0;
7739 	    if ((cp = (char *) HTList_objectAt(search_queries,
7740 					       QueryNum)) != NULL) {
7741 		BStrCopy0(searchstring, cp);
7742 		if (!isBEmpty(temp) &&
7743 		    !strcmp(temp->str, searchstring->str)) {
7744 		    _statusline(EDIT_CURRENT_QUERY);
7745 		} else if ((!isBEmpty(temp) && QueryTotal == 2) ||
7746 			   (isBEmpty(temp) && QueryTotal == 1)) {
7747 		    _statusline(EDIT_THE_PREV_QUERY);
7748 		} else {
7749 		    _statusline(EDIT_A_PREV_QUERY);
7750 		}
7751 		goto get_query;
7752 	    }
7753 	} else if (recall && ch == DNARROW_KEY) {
7754 	    if (PreviousSearch) {
7755 		/*
7756 		 * Use the first query in the list.  -FM
7757 		 */
7758 		QueryNum = QueryTotal - 1;
7759 		PreviousSearch = FALSE;
7760 	    } else {
7761 		/*
7762 		 * Advance to the next query in the list.  -FM
7763 		 */
7764 		QueryNum--;
7765 	    }
7766 	    if (QueryNum < 0)
7767 		/*
7768 		 * Roll around to the first query in the list.  -FM
7769 		 */
7770 		QueryNum = QueryTotal - 1;
7771 	    if ((cp = (char *) HTList_objectAt(search_queries,
7772 					       QueryNum)) != NULL) {
7773 		BStrCopy0(searchstring, cp);
7774 		if (!isBEmpty(temp) &&
7775 		    !strcmp(temp->str, searchstring->str)) {
7776 		    _statusline(EDIT_CURRENT_QUERY);
7777 		} else if ((!isBEmpty(temp) && QueryTotal == 2) ||
7778 			   (isBEmpty(temp) && QueryTotal == 1)) {
7779 		    _statusline(EDIT_THE_PREV_QUERY);
7780 		} else {
7781 		    _statusline(EDIT_A_PREV_QUERY);
7782 		}
7783 		goto get_query;
7784 	    }
7785 	}
7786 
7787 	/*
7788 	 * Search cancelled.
7789 	 */
7790 	HTInfoMsg(CANCELLED);
7791 	code = NULLFILE;
7792     } else {
7793 
7794 	LYTrimLeading(searchstring->str);
7795 	LYTrimTrailing(searchstring->str);
7796 	if (isBEmpty(searchstring)) {
7797 	    HTInfoMsg(CANCELLED);
7798 	    code = NULLFILE;
7799 	} else if (!LYforce_no_cache &&
7800 		   !isBEmpty(temp) &&
7801 		   !strcmp(temp->str, searchstring->str)) {
7802 	    /*
7803 	     * Don't resubmit the same query unintentionally.
7804 	     */
7805 	    HTUserMsg(USE_C_R_TO_RESUB_CUR_QUERY);
7806 	    code = NULLFILE;
7807 	} else {
7808 
7809 	    /*
7810 	     * Add searchstring to the query list,
7811 	     * or make it the most current.  -FM
7812 	     */
7813 	    HTAddSearchQuery(searchstring->str);
7814 
7815 	    /*
7816 	     * Show the URL with the new query.
7817 	     */
7818 	    if ((cp = StrChr(doc->address, '?')) != NULL)
7819 		*cp = '\0';
7820 	    StrAllocCopy(tmpaddress, doc->address);
7821 	    StrAllocCat(tmpaddress, "?");
7822 	    StrAllocCat(tmpaddress, searchstring->str);
7823 	    user_message(WWW_WAIT_MESSAGE, tmpaddress);
7824 #ifdef SYSLOG_REQUESTED_URLS
7825 	    LYSyslog(tmpaddress);
7826 #endif
7827 	    FREE(tmpaddress);
7828 	    if (cp)
7829 		*cp = '?';
7830 
7831 	    /*
7832 	     * OK, now we do the search.
7833 	     */
7834 	    if (HTSearch(searchstring->str, HTMainAnchor)) {
7835 		auto char *cp_freeme = NULL;
7836 
7837 		if (traversal)
7838 		    cp_freeme = stub_HTAnchor_address((HTAnchor *) HTMainAnchor);
7839 		else
7840 		    cp_freeme = HTAnchor_address((HTAnchor *) HTMainAnchor);
7841 		StrAllocCopy(doc->address, cp_freeme);
7842 		FREE(cp_freeme);
7843 
7844 		CTRACE((tfp, "\ndo_www_search: newfile: %s\n", doc->address));
7845 
7846 		/*
7847 		 * Yah, the search succeeded.
7848 		 */
7849 		code = NORMAL;
7850 	    } else {
7851 
7852 		/*
7853 		 * Either the search failed (Yuk), or we got redirection.
7854 		 * If it's redirection, use_this_url_instead is set, and
7855 		 * mainloop() will deal with it such that security features
7856 		 * and restrictions are checked before acting on the URL, or
7857 		 * rejecting it.  -FM
7858 		 */
7859 		code = NOT_FOUND;
7860 	    }
7861 	}
7862     }
7863     BStrFree(searchstring);
7864     BStrFree(temp);
7865     return code;
7866 }
7867 
write_offset(FILE * fp,HTLine * line)7868 static void write_offset(FILE *fp, HTLine *line)
7869 {
7870     int i;
7871 
7872     if (line->data[0]) {
7873 	for (i = 0; i < (int) line->offset; i++) {
7874 	    fputc(' ', fp);
7875 	}
7876     }
7877 }
7878 
write_hyphen(FILE * fp)7879 static void write_hyphen(FILE *fp)
7880 {
7881     if (dump_output_immediately &&
7882 	LYRawMode &&
7883 	LYlowest_eightbit[current_char_set] <= 173 &&
7884 	(LYCharSet_UC[current_char_set].enc == UCT_ENC_8859 ||
7885 	 (LYCharSet_UC[current_char_set].like8859 & UCT_R_8859SPECL)) != 0) {
7886 	fputc(0xad, fp);	/* the iso8859 byte for SHY */
7887     } else {
7888 	fputc('-', fp);
7889     }
7890 }
7891 
7892 /*
7893  * Returns the length after trimming trailing blanks.  Modify the string as
7894  * needed so that any special character which follows a trailing blank is moved
7895  * before the (trimmed) blank, so the result which will be dumped has no
7896  * trailing blanks.
7897  */
TrimmedLength(char * string)7898 static int TrimmedLength(char *string)
7899 {
7900     int result = (int) strlen(string);
7901 
7902     if (!HTisDocumentSource()) {
7903 	int adjust = result;
7904 	int ch;
7905 
7906 	while (adjust > 0) {
7907 	    ch = UCH(string[adjust - 1]);
7908 	    if (isspace(ch) || IsSpecialAttrChar(ch)) {
7909 		--adjust;
7910 	    } else {
7911 		break;
7912 	    }
7913 	}
7914 	if (result != adjust) {
7915 	    char *dst = string + adjust;
7916 	    char *src = dst;
7917 
7918 	    for (;;) {
7919 		src = LYSkipBlanks(src);
7920 		if ((*dst++ = *src++) == '\0')
7921 		    break;
7922 	    }
7923 	    result = (int) (dst - string - 1);
7924 	}
7925     }
7926     return result;
7927 }
7928 
7929 typedef struct _AnchorIndex {
7930     struct _AnchorIndex *next;
7931     int type;			/* field type */
7932     int size;			/* character-width of field */
7933     int length;			/* byte-count for field's data */
7934     int offset;			/* byte-offset in line's data */
7935     char filler;		/* character to use for filler */
7936     const char *value;		/* field's value */
7937 } AnchorIndex;
7938 
countHTLines(void)7939 static unsigned countHTLines(void)
7940 {
7941     unsigned result = 0;
7942     HTLine *line = FirstHTLine(HTMainText);
7943 
7944     while (line != 0) {
7945 	++result;
7946 	if (line == HTMainText->last_line)
7947 	    break;
7948 	line = line->next;
7949     }
7950     CTRACE((tfp, "countHTLines %u\n", result));
7951     return result;
7952 }
7953 
7954 /*
7955  * The TextAnchor list is not organized to allow efficient dumping of a page.
7956  * Make an array with one item per line of the page, and store (by byte-offset)
7957  * pointers to the TextAnchor's we want to use.
7958  */
allocAnchorIndex(unsigned * size)7959 static AnchorIndex **allocAnchorIndex(unsigned *size)
7960 {
7961     AnchorIndex **result = NULL;
7962     AnchorIndex *p, *q;
7963     TextAnchor *anchor = NULL;
7964     FormInfo *input = NULL;
7965 
7966     *size = countHTLines();
7967     if (*size != 0) {
7968 	result = typecallocn(AnchorIndex *, *size + 1);
7969 	if (result == NULL)
7970 	    outofmem(__FILE__, "allocAnchorIndex");
7971 
7972 	for (anchor = HTMainText->first_anchor;
7973 	     anchor != NULL;
7974 	     anchor = anchor->next) {
7975 
7976 	    if (anchor->link_type == INPUT_ANCHOR
7977 		&& anchor->show_anchor
7978 		&& anchor->line_num < (int) *size
7979 		&& (input = anchor->input_field) != NULL) {
7980 		CTRACE2(TRACE_GRIDTEXT,
7981 			(tfp, "line %d.%d %d %s->%s(%s)\n",
7982 			 anchor->line_num,
7983 			 anchor->line_pos,
7984 			 input->size,
7985 			 inputFieldDesc(input),
7986 			 input->value,
7987 			 input->orig_value));
7988 		switch (input->type) {
7989 		case F_SUBMIT_TYPE:
7990 		case F_RESET_TYPE:
7991 		case F_TEXT_SUBMIT_TYPE:
7992 		case F_IMAGE_SUBMIT_TYPE:
7993 		    CTRACE2(TRACE_GRIDTEXT, (tfp, "skipping\n"));
7994 		    continue;
7995 		case F_TEXT_TYPE:
7996 		case F_PASSWORD_TYPE:
7997 		case F_CHECKBOX_TYPE:
7998 		case F_RADIO_TYPE:
7999 		case F_OPTION_LIST_TYPE:
8000 		case F_TEXTAREA_TYPE:
8001 		case F_RANGE_TYPE:
8002 		case F_FILE_TYPE:
8003 		    p = typecalloc(AnchorIndex);
8004 		    if (p == NULL)
8005 			outofmem(__FILE__, "allocAnchorIndex");
8006 
8007 		    p->type = input->type;
8008 		    p->size = input->size;
8009 		    p->offset = anchor->line_pos;
8010 		    p->value = input->value;
8011 
8012 		    switch (input->type) {
8013 		    case F_TEXTAREA_TYPE:
8014 		    case F_TEXT_TYPE:
8015 		    case F_PASSWORD_TYPE:
8016 			p->filler = '_';
8017 			break;
8018 		    case F_OPTION_LIST_TYPE:
8019 			p->filler = '_';
8020 			break;
8021 		    case F_CHECKBOX_TYPE:
8022 			p->value = (input->num_value
8023 				    ? checked_box
8024 				    : unchecked_box);
8025 			break;
8026 		    case F_RADIO_TYPE:
8027 			p->value = (input->num_value
8028 				    ? checked_radio
8029 				    : unchecked_radio);
8030 			break;
8031 		    default:
8032 			p->filler = ' ';
8033 			break;
8034 		    }
8035 		    p->length = (int) strlen(p->value);
8036 
8037 		    if ((q = result[anchor->line_num]) != NULL) {
8038 			/* insert, ordering by offset */
8039 			if (q->offset < p->offset) {
8040 			    while (q->next != NULL
8041 				   && q->next->offset < p->offset) {
8042 				q = q->next;
8043 			    }
8044 			    p->next = q->next;
8045 			    q->next = p;
8046 			} else {
8047 			    p->next = q;
8048 			    result[anchor->line_num] = p;
8049 			}
8050 		    } else {
8051 			result[anchor->line_num] = p;
8052 		    }
8053 		    break;
8054 		}
8055 	    }
8056 	}
8057     }
8058     return result;
8059 }
8060 
8061 /*
8062  * Free the data allocated in allocAnchorIndex().
8063  */
freeAnchorIndex(AnchorIndex ** inx,unsigned inx_size)8064 static void freeAnchorIndex(AnchorIndex ** inx, unsigned inx_size)
8065 {
8066     AnchorIndex *cur;
8067     unsigned num;
8068 
8069     if (inx != 0) {
8070 	if (inx_size != 0) {
8071 	    for (num = 0; num < inx_size; ++num) {
8072 		while ((cur = inx[num]) != NULL) {
8073 		    inx[num] = cur->next;
8074 		    free(cur);
8075 		}
8076 	    }
8077 	}
8078 	free(inx);
8079     }
8080 }
8081 
8082 /*
8083  * Return the column (counting from zero) at which a field should be overlaid
8084  * on the form.
8085  */
FieldFirst(AnchorIndex * p,int wrap)8086 static int FieldFirst(AnchorIndex * p, int wrap)
8087 {
8088     return (wrap ? 0 : (p)->offset);
8089 }
8090 
8091 /*
8092  * Return the column (counting from zero) just past the field in a form.
8093  */
FieldLast(AnchorIndex * p,int wrap)8094 static int FieldLast(AnchorIndex * p, int wrap)
8095 {
8096     return ((p)->size - wrap) + FieldFirst(p, wrap);
8097 }
8098 
8099 /*
8100  * Print the contents of the file in HTMainText to
8101  * the file descriptor fp.
8102  * If is_email is TRUE add ">" before each "From " line.
8103  * If is_reply is TRUE add ">" to the beginning of each
8104  * line to specify the file is a reply to message.
8105  */
print_wwwfile_to_fd(FILE * fp,int is_email,int is_reply)8106 void print_wwwfile_to_fd(FILE *fp,
8107 			 int is_email,
8108 			 int is_reply)
8109 {
8110     int line_num, byte_num, byte_count, byte_next, byte_offset;
8111     int first = TRUE;
8112     HTLine *line;
8113     AnchorIndex **inx;		/* sorted index of input-fields */
8114     AnchorIndex *cur = 0;	/* current input-field */
8115     unsigned inx_size;		/* number of entries in inx[] */
8116     int in_field = -1;		/* if positive, is index in cur->value[] */
8117     int this_wrap = 0;		/* current wrapping point of cur->value[] */
8118     int next_wrap = 0;		/* next wrapping point of cur->value[] */
8119 
8120 #ifndef NO_DUMP_WITH_BACKSPACES
8121     HText *text = HTMainText;
8122     BOOL in_b = FALSE;
8123     BOOL in_u = FALSE;
8124     BOOL bs = (BOOL) (!is_email && !is_reply
8125 		      && text != 0
8126 		      && with_backspaces
8127 		      && !IS_CJK_TTY
8128 		      && !text->T.output_utf8);
8129 #endif
8130 
8131     if (!HTMainText)
8132 	return;
8133 
8134     /*
8135      * Build an index of anchors for each line, so we can override the
8136      * static text which is stored in the list of HTLine's.
8137      */
8138     inx = allocAnchorIndex(&inx_size);
8139 
8140     line = FirstHTLine(HTMainText);
8141     for (line_num = 0;; ++line_num, line = line->next) {
8142 	if (in_field >= 0) {
8143 	    this_wrap = next_wrap;
8144 	    next_wrap = 0;	/* FIXME - allow for multiple continuations */
8145 	    CTRACE2(TRACE_GRIDTEXT,
8146 		    (tfp, "wrap %d:%d, offset %d\n",
8147 		     in_field, cur ? cur->length : -1, this_wrap));
8148 	} else {
8149 	    cur = inx[line_num];
8150 	}
8151 
8152 	CTRACE2(TRACE_GRIDTEXT, (tfp, "dump %d:%s\n", line_num, line->data));
8153 
8154 	if (first) {
8155 	    first = FALSE;
8156 	    if (is_reply) {
8157 		fputc('>', fp);
8158 	    } else if (is_email && !StrNCmp(line->data, "From ", 5)) {
8159 		fputc('>', fp);
8160 	    }
8161 	} else if (line->data[0] != LY_SOFT_NEWLINE) {
8162 	    fputc('\n', fp);
8163 	    /*
8164 	     * Add news-style quotation if requested.  -FM
8165 	     */
8166 	    if (is_reply) {
8167 		fputc('>', fp);
8168 	    } else if (is_email && !StrNCmp(line->data, "From ", 5)) {
8169 		fputc('>', fp);
8170 	    }
8171 	}
8172 
8173 	write_offset(fp, line);
8174 
8175 	/*
8176 	 * Add data.
8177 	 */
8178 	byte_offset = line->offset;
8179 	byte_count = TrimmedLength(line->data);
8180 	byte_next = 1;
8181 	for (byte_num = 0; byte_num < byte_count; byte_num += byte_next) {
8182 	    int cell_chr, temp_chr;
8183 	    size_t cell_len, temp_len;
8184 	    const char *cell_ptr, *temp_ptr, *try_utf8;
8185 
8186 	    cell_ptr = &line->data[byte_num];
8187 	    cell_len = 1;
8188 	    cell_chr = UCH(*cell_ptr);
8189 	    byte_next = 1;
8190 
8191 	    while (cur != 0 && FieldLast(cur, this_wrap) < byte_offset) {
8192 		CTRACE2(TRACE_GRIDTEXT,
8193 			(tfp, "skip field since last %d < %d\n",
8194 			 FieldLast(cur, this_wrap), byte_offset));
8195 		cur = cur->next;
8196 		in_field = -1;
8197 	    }
8198 	    if (cur != 0 && in_field >= 0) {
8199 		CTRACE2(TRACE_GRIDTEXT,
8200 			(tfp, "compare %d to [%d..%d]\n",
8201 			 byte_offset,
8202 			 FieldFirst(cur, this_wrap),
8203 			 FieldLast(cur, this_wrap) - 1));
8204 	    }
8205 	    if (cur != 0
8206 		&& FieldFirst(cur, this_wrap) <= byte_offset
8207 		&& FieldLast(cur, this_wrap) > byte_offset) {
8208 		int off2 = ((in_field > 0)
8209 			    ? in_field
8210 			    : (byte_offset - FieldFirst(cur, this_wrap)));
8211 
8212 		/*
8213 		 * On the first time (for each line that the field appears on),
8214 		 * check if this field wraps.  If it does, save the offset into
8215 		 * the field which will be used to adjust the beginning of the
8216 		 * continuation line.
8217 		 */
8218 		if (byte_offset == FieldFirst(cur, this_wrap)) {
8219 		    next_wrap = 0;
8220 		    if (cur->size - this_wrap + byte_num > byte_count) {
8221 			CTRACE((tfp, "size %d, offset %d, length %d\n",
8222 				cur->size,
8223 				cur->offset,
8224 				cur->length));
8225 			CTRACE((tfp, "byte_count %d, byte_num %d\n",
8226 				byte_count, byte_num));
8227 			next_wrap = byte_count - byte_num;
8228 			CTRACE2(TRACE_GRIDTEXT,
8229 				(tfp, "field will wrap: %d\n", next_wrap));
8230 		    }
8231 		}
8232 
8233 		if (off2 >= 0 && off2 < cur->length) {
8234 		    temp_ptr = &(cur->value[off2]);
8235 		    try_utf8 = temp_ptr;
8236 		    temp_chr = (int) UCGetUniFromUtf8String(&try_utf8);
8237 		    if (temp_chr > 127) {
8238 			temp_len = (size_t) (try_utf8 - temp_ptr) + 1;
8239 		    } else {
8240 			temp_chr = UCH(*temp_ptr);
8241 			temp_len = 1;
8242 		    }
8243 		} else {
8244 		    temp_ptr = &(cur->filler);
8245 		    temp_len = 1;
8246 		    temp_chr = UCH(*temp_ptr);
8247 		}
8248 
8249 		if (cell_chr != temp_chr) {
8250 		    CTRACE2(TRACE_GRIDTEXT,
8251 			    (tfp, "line %d %d/%d [%d..%d] map %d %04X->%04X\n",
8252 			     line_num,
8253 			     off2, cur->length,
8254 			     FieldFirst(cur, this_wrap),
8255 			     FieldLast(cur, this_wrap) - 1,
8256 			     byte_offset, cell_chr, temp_chr));
8257 		    cell_chr = temp_chr;
8258 		    cell_ptr = temp_ptr;
8259 		    cell_len = temp_len;
8260 		}
8261 		off2 += (int) temp_len;
8262 		byte_offset += (int) temp_len;
8263 		if ((off2 >= cur->size) &&
8264 		    (off2 >= cur->length || F_TEXTLIKE(cur->type))) {
8265 		    in_field = -1;
8266 		    this_wrap = 0;
8267 		    next_wrap = 0;
8268 		} else {
8269 		    in_field = off2;
8270 		}
8271 	    } else {
8272 		byte_offset++;
8273 	    }
8274 
8275 	    if (!IsSpecialAttrChar(cell_chr)) {
8276 #ifndef NO_DUMP_WITH_BACKSPACES
8277 		size_t n;
8278 
8279 		if (in_b) {
8280 		    IGNORE_RC(fwrite(cell_ptr, sizeof(char), cell_len, fp));
8281 
8282 		    for (n = 0; n < cell_len; ++n) {
8283 			fputc('\b', fp);
8284 		    }
8285 		    IGNORE_RC(fwrite(cell_ptr, sizeof(char), cell_len, fp));
8286 		} else if (in_u) {
8287 		    for (n = 0; n < cell_len; ++n) {
8288 			fputc('_', fp);
8289 		    }
8290 		    for (n = 0; n < cell_len; ++n) {
8291 			fputc('\b', fp);
8292 		    }
8293 		    IGNORE_RC(fwrite(cell_ptr, sizeof(char), cell_len, fp));
8294 		} else
8295 #endif
8296 		    IGNORE_RC(fwrite(cell_ptr, sizeof(char), cell_len, fp));
8297 	    } else if (cell_chr == LY_SOFT_HYPHEN &&
8298 		       (byte_num + 1) >= byte_count) {
8299 		write_hyphen(fp);
8300 	    } else if (dump_output_immediately && use_underscore) {
8301 		switch (cell_chr) {
8302 		case LY_UNDERLINE_START_CHAR:
8303 		case LY_UNDERLINE_END_CHAR:
8304 		    fputc('_', fp);
8305 		    break;
8306 		case LY_BOLD_START_CHAR:
8307 		case LY_BOLD_END_CHAR:
8308 		    break;
8309 		}
8310 	    }
8311 #ifndef NO_DUMP_WITH_BACKSPACES
8312 	    else if (bs) {
8313 		switch (cell_chr) {
8314 		case LY_UNDERLINE_START_CHAR:
8315 		    if (!in_b)
8316 			in_u = TRUE;	/*favor bold over underline */
8317 		    break;
8318 		case LY_UNDERLINE_END_CHAR:
8319 		    in_u = FALSE;
8320 		    break;
8321 		case LY_BOLD_START_CHAR:
8322 		    if (in_u)
8323 			in_u = FALSE;	/* turn it off */
8324 		    in_b = TRUE;
8325 		    break;
8326 		case LY_BOLD_END_CHAR:
8327 		    in_b = FALSE;
8328 		    break;
8329 		}
8330 	    }
8331 #endif
8332 	}
8333 
8334 	if (line == HTMainText->last_line)
8335 	    break;
8336 
8337 #ifdef VMS
8338 	if (HadVMSInterrupt)
8339 	    break;
8340 #endif /* VMS */
8341     }
8342     fputc('\n', fp);
8343 
8344     freeAnchorIndex(inx, inx_size);
8345 }
8346 
8347 /*
8348  * Print the contents of the file in HTMainText to
8349  * the file descriptor fp.
8350  * First output line is "thelink", ie, the URL for this file.
8351  */
print_crawl_to_fd(FILE * fp,char * thelink,char * thetitle)8352 void print_crawl_to_fd(FILE *fp, char *thelink,
8353 		       char *thetitle)
8354 {
8355     register int i;
8356     int first = TRUE;
8357     int limit;
8358     HTLine *line;
8359 
8360     if (!HTMainText)
8361 	return;
8362 
8363     line = FirstHTLine(HTMainText);
8364     fprintf(fp, "THE_URL:%s\n", thelink);
8365     if (thetitle != NULL) {
8366 	fprintf(fp, "THE_TITLE:%s\n", thetitle);
8367     }
8368 
8369     for (;; line = line->next) {
8370 	if (!first && line->data[0] != LY_SOFT_NEWLINE)
8371 	    fputc('\n', fp);
8372 	first = FALSE;
8373 	write_offset(fp, line);
8374 
8375 	/*
8376 	 * Add data.
8377 	 */
8378 	limit = TrimmedLength(line->data);
8379 	for (i = 0; i < limit; i++) {
8380 	    int ch = UCH(line->data[i]);
8381 
8382 	    if (!IsSpecialAttrChar(ch)) {
8383 		fputc(ch, fp);
8384 	    } else if (ch == LY_SOFT_HYPHEN &&
8385 		       (i + 1) >= limit) {	/* last char on line */
8386 		write_hyphen(fp);
8387 	    }
8388 	}
8389 
8390 	if (!HTMainText || (line == HTMainText->last_line)) {
8391 	    break;
8392 	}
8393     }
8394     fputc('\n', fp);
8395 
8396     /*
8397      * Add the References list if appropriate
8398      */
8399     if ((no_list == FALSE) &&
8400 	(dump_links_inline == FALSE) &&
8401 	links_are_numbered()) {
8402 	printlist(fp, FALSE);
8403     }
8404 #ifdef VMS
8405     HadVMSInterrupt = FALSE;
8406 #endif /* VMS */
8407 }
8408 
adjust_search_result(DocInfo * doc,int tentative_result,int start_line)8409 static void adjust_search_result(DocInfo *doc, int tentative_result,
8410 				 int start_line)
8411 {
8412     if (tentative_result > 0) {
8413 	int anch_line = -1;
8414 	TextAnchor *a;
8415 	int nl_closest = -1;
8416 	int goal = SEARCH_GOAL_LINE;
8417 	int max_offset;
8418 	BOOL on_screen = (BOOL) (tentative_result > HTMainText->top_of_screen &&
8419 				 tentative_result <= HTMainText->top_of_screen +
8420 				 display_lines);
8421 
8422 	if (goal < 1)
8423 	    goal = 1;
8424 	else if (goal > display_lines)
8425 	    goal = display_lines;
8426 	max_offset = goal - 1;
8427 
8428 	if (on_screen && nlinks > 0) {
8429 	    int i;
8430 
8431 	    for (i = 0; i < nlinks; i++) {
8432 		if (doc->line + links[i].ly - 1 <= tentative_result)
8433 		    nl_closest = i;
8434 		if (doc->line + links[i].ly - 1 >= tentative_result)
8435 		    break;
8436 	    }
8437 	    if (nl_closest >= 0 &&
8438 		doc->line + links[nl_closest].ly - 1 == tentative_result) {
8439 		www_search_result = doc->line;
8440 		doc->link = nl_closest;
8441 		return;
8442 	    }
8443 	}
8444 
8445 	/* find last anchor before or on target line */
8446 	for (a = HTMainText->first_anchor;
8447 	     a && a->line_num <= tentative_result - 1; a = a->next) {
8448 	    anch_line = a->line_num + 1;
8449 	}
8450 	/* position such that the anchor found is on first screen line,
8451 	   if it is not too far above the target line; but also try to
8452 	   make sure we move forward. */
8453 	if (anch_line >= 0 &&
8454 	    anch_line >= tentative_result - max_offset &&
8455 	    (anch_line > start_line ||
8456 	     tentative_result <= HTMainText->top_of_screen)) {
8457 	    www_search_result = anch_line;
8458 	} else if (tentative_result - start_line > 0 &&
8459 		   tentative_result - (start_line + 1) <= max_offset) {
8460 	    www_search_result = start_line + 1;
8461 	} else if (tentative_result > HTMainText->top_of_screen &&
8462 		   tentative_result <= start_line &&	/* have wrapped */
8463 		   tentative_result <= HTMainText->top_of_screen + goal) {
8464 	    www_search_result = HTMainText->top_of_screen + 1;
8465 	} else if (tentative_result <= goal)
8466 	    www_search_result = 1;
8467 	else
8468 	    www_search_result = tentative_result - max_offset;
8469 	if (www_search_result == doc->line) {
8470 	    if (nl_closest >= 0) {
8471 		doc->link = nl_closest;
8472 		return;
8473 	    }
8474 	}
8475     }
8476 }
8477 
8478 /*
8479  * see also link_has_target
8480  */
anchor_has_target(TextAnchor * a,char * target)8481 static BOOL anchor_has_target(TextAnchor *a, char *target)
8482 {
8483     char *text = NULL;
8484     const char *last = "?";
8485     int count;
8486 
8487     /*
8488      * Combine the parts of the link's text using the highlighting information,
8489      * and compare the target against that.
8490      */
8491     for (count = 0; count < 10; ++count) {
8492 	const char *part = LYGetHiTextStr(a, count);
8493 
8494 	if (part == NULL || part == last) {
8495 	    if (text != NULL && LYno_attr_strstr(text, target)) {
8496 		return TRUE;
8497 	    }
8498 	    break;
8499 	}
8500 	StrAllocCat(text, part);
8501 	last = part;
8502     }
8503 
8504     return field_has_target(a->input_field, target);
8505 }
8506 
line_num_to_anchor(int line_num)8507 static TextAnchor *line_num_to_anchor(int line_num)
8508 {
8509     TextAnchor *a;
8510 
8511     if (HTMainText != 0) {
8512 	a = HTMainText->first_anchor;
8513 	while (a != 0 && a->line_num < line_num) {
8514 	    a = a->next;
8515 	}
8516     } else {
8517 	a = 0;
8518     }
8519     return a;
8520 }
8521 
line_num_in_text(HText * text,HTLine * line)8522 static int line_num_in_text(HText *text, HTLine *line)
8523 {
8524     int result = 1;
8525     HTLine *temp = FirstHTLine(text);
8526 
8527     while (temp != line) {
8528 	temp = temp->next;
8529 	++result;
8530     }
8531     return result;
8532 }
8533 
8534 /* Computes the 'prev' pointers on demand, and returns the one for the given
8535  * anchor.
8536  */
get_prev_anchor(TextAnchor * a)8537 static TextAnchor *get_prev_anchor(TextAnchor *a)
8538 {
8539     TextAnchor *p, *q;
8540 
8541     if (a->prev == 0) {
8542 	if ((p = HTMainText->first_anchor) != 0) {
8543 	    while ((q = p->next) != 0) {
8544 		q->prev = p;
8545 		p = q;
8546 	    }
8547 	}
8548     }
8549     return a->prev;
8550 }
8551 
www_search_forward(int start_line,DocInfo * doc,char * target,HTLine * line,int count)8552 static int www_search_forward(int start_line,
8553 			      DocInfo *doc,
8554 			      char *target,
8555 			      HTLine *line,
8556 			      int count)
8557 {
8558     int wrapped = 0;
8559     TextAnchor *a = line_num_to_anchor(count - 1);
8560     int tentative_result = -1;
8561 
8562     for (;;) {
8563 	while ((a != NULL) && a->line_num == (count - 1)) {
8564 	    if (a->show_anchor &&
8565 		!(a->link_type == INPUT_ANCHOR
8566 		  && a->input_field->type == F_HIDDEN_TYPE)) {
8567 		if (anchor_has_target(a, target)) {
8568 		    adjust_search_result(doc, count, start_line);
8569 		    return 1;
8570 		}
8571 	    }
8572 	    a = a->next;
8573 	}
8574 
8575 	if (LYno_attr_strstr(line->data, target)) {
8576 	    tentative_result = count;
8577 	    break;
8578 	} else if ((count == start_line && wrapped) || wrapped > 1) {
8579 	    HTUserMsg2(STRING_NOT_FOUND, target);
8580 	    return -1;
8581 	} else if (line == HTMainText->last_line) {
8582 	    count = 0;
8583 	    wrapped++;
8584 	    a = HTMainText->first_anchor;
8585 	}
8586 	line = line->next;
8587 	count++;
8588     }
8589     if (tentative_result > 0) {
8590 	adjust_search_result(doc, tentative_result, start_line);
8591     }
8592     return 0;
8593 }
8594 
www_search_backward(int start_line,DocInfo * doc,char * target,HTLine * line,int count)8595 static int www_search_backward(int start_line,
8596 			       DocInfo *doc,
8597 			       char *target,
8598 			       HTLine *line,
8599 			       int count)
8600 {
8601     int wrapped = 0;
8602     TextAnchor *a = line_num_to_anchor(count - 1);
8603     int tentative_result = -1;
8604 
8605     for (;;) {
8606 	while ((a != NULL) && a->line_num == (count - 1)) {
8607 	    if (a->show_anchor &&
8608 		!(a->link_type == INPUT_ANCHOR
8609 		  && a->input_field->type == F_HIDDEN_TYPE)) {
8610 		if (anchor_has_target(a, target)) {
8611 		    adjust_search_result(doc, count, start_line);
8612 		    return 1;
8613 		}
8614 	    }
8615 	    a = get_prev_anchor(a);
8616 	}
8617 
8618 	if (LYno_attr_strstr(line->data, target)) {
8619 	    tentative_result = count;
8620 	    break;
8621 	} else if ((count == start_line && wrapped) || wrapped > 1) {
8622 	    HTUserMsg2(STRING_NOT_FOUND, target);
8623 	    return -1;
8624 	} else if (line == FirstHTLine(HTMainText)) {
8625 	    count = line_num_in_text(HTMainText, LastHTLine(HTMainText)) + 1;
8626 	    wrapped++;
8627 	    a = HTMainText->last_anchor;
8628 	}
8629 	line = line->prev;
8630 	count--;
8631     }
8632     if (tentative_result > 0) {
8633 	adjust_search_result(doc, tentative_result, start_line);
8634     }
8635     return 0;
8636 }
8637 
www_user_search(int start_line,DocInfo * doc,char * target,int direction)8638 void www_user_search(int start_line,
8639 		     DocInfo *doc,
8640 		     char *target,
8641 		     int direction)
8642 {
8643     HTLine *line;
8644     int count;
8645 
8646     if (!HTMainText) {
8647 	return;
8648     }
8649 
8650     /*
8651      * Advance to the start line.
8652      */
8653     line = FirstHTLine(HTMainText);
8654     if (start_line + direction > 0) {
8655 	for (count = 1;
8656 	     count < start_line + direction;
8657 	     line = line->next, count++) {
8658 	    if (line == HTMainText->last_line) {
8659 		line = FirstHTLine(HTMainText);
8660 		count = 1;
8661 		break;
8662 	    }
8663 	}
8664     } else {
8665 	line = HTMainText->last_line;
8666 	count = line_num_in_text(HTMainText, line);
8667     }
8668 
8669     if (direction >= 0)
8670 	www_search_forward(start_line, doc, target, line, count);
8671     else
8672 	www_search_backward(start_line, doc, target, line, count);
8673 }
8674 
user_message(const char * message,const char * argument)8675 void user_message(const char *message,
8676 		  const char *argument)
8677 {
8678     if (message == NULL) {
8679 	mustshow = FALSE;
8680     } else {
8681 	char *temp = NULL;
8682 
8683 	HTSprintf0(&temp, message, NonNull(argument));
8684 	statusline(temp);
8685 	FREE(temp);
8686     }
8687 }
8688 
8689 /*
8690  * HText_getOwner returns the owner of the
8691  * current document.
8692  */
HText_getOwner(void)8693 const char *HText_getOwner(void)
8694 {
8695     return (HTMainText ?
8696 	    HTAnchor_owner(HTMainText->node_anchor) : 0);
8697 }
8698 
8699 /*
8700  * HText_setMainTextOwner sets the owner for the
8701  * current document.
8702  */
HText_setMainTextOwner(const char * owner)8703 void HText_setMainTextOwner(const char *owner)
8704 {
8705     if (!HTMainText)
8706 	return;
8707 
8708     HTAnchor_setOwner(HTMainText->node_anchor, owner);
8709 }
8710 
8711 /*
8712  * HText_getRevTitle returns the RevTitle element of the
8713  * current document, used as the subject for mailto comments
8714  * to the owner.
8715  */
HText_getRevTitle(void)8716 const char *HText_getRevTitle(void)
8717 {
8718     return (HTMainText ?
8719 	    HTAnchor_RevTitle(HTMainText->node_anchor) : 0);
8720 }
8721 
8722 /*
8723  * HText_getContentBase returns the Content-Base header
8724  * of the current document.
8725  */
HText_getContentBase(void)8726 const char *HText_getContentBase(void)
8727 {
8728     return (HTMainText ?
8729 	    HTAnchor_content_base(HTMainText->node_anchor) : 0);
8730 }
8731 
8732 /*
8733  * HText_getContentLocation returns the Content-Location header
8734  * of the current document.
8735  */
HText_getContentLocation(void)8736 const char *HText_getContentLocation(void)
8737 {
8738     return (HTMainText ?
8739 	    HTAnchor_content_location(HTMainText->node_anchor) : 0);
8740 }
8741 
8742 /*
8743  * HText_getMessageID returns the Message-ID of the
8744  * current document.
8745  */
HText_getMessageID(void)8746 const char *HText_getMessageID(void)
8747 {
8748     return (HTMainText ?
8749 	    HTAnchor_messageID(HTMainText->node_anchor) : NULL);
8750 }
8751 
HTuncache_current_document(void)8752 void HTuncache_current_document(void)
8753 {
8754     /*
8755      * Should remove current document from memory.
8756      */
8757     if (HTMainText) {
8758 	HTParentAnchor *htmain_anchor = HTMainText->node_anchor;
8759 
8760 	if (htmain_anchor) {
8761 	    if (!(HTOutputFormat && HTOutputFormat == WWW_SOURCE)) {
8762 		FREE(htmain_anchor->UCStages);
8763 	    }
8764 	}
8765 	CTRACE((tfp, "\nHTuncache.. freeing document for '%s'%s\n",
8766 		((htmain_anchor &&
8767 		  htmain_anchor->address) ?
8768 		 htmain_anchor->address : "unknown anchor"),
8769 		((htmain_anchor &&
8770 		  htmain_anchor->post_data)
8771 		 ? " with POST data"
8772 		 : "")));
8773 	HTList_removeObject(loaded_texts, HTMainText);
8774 	HText_free(HTMainText);
8775 	HTMainText = NULL;
8776     } else {
8777 	CTRACE((tfp, "HTuncache.. HTMainText already is NULL!\n"));
8778     }
8779 }
8780 
8781 /*
8782  * This magic FREE(anchor->UCStages) call
8783  * stolen from HTuncache_current_document() above.
8784  */
magicUncache(void)8785 static void magicUncache(void)
8786 {
8787     if (!(HTOutputFormat && HTOutputFormat == WWW_SOURCE)) {
8788 	FREE(HTMainAnchor->UCStages);
8789     }
8790     /* avoid null-reference later */
8791     if (!HTOutputFormat)
8792 	HTOutputFormat = WWW_SOURCE;
8793 }
8794 
8795 #ifdef USE_SOURCE_CACHE
8796 
8797 /* dummy - kw */
8798 static HTProtocol scm =
8799 {
8800     "source-cache-mem", 0, 0
8801 };
8802 
useSourceCache(void)8803 static BOOLEAN useSourceCache(void)
8804 {
8805     BOOLEAN result = FALSE;
8806 
8807     if (LYCacheSource == SOURCE_CACHE_FILE) {
8808 	result = (BOOLEAN) (HTMainAnchor->source_cache_file != 0);
8809 	CTRACE((tfp, "SourceCache: file-cache%s found\n",
8810 		result ? "" : " not"));
8811     }
8812     return result;
8813 }
8814 
useMemoryCache(void)8815 static BOOLEAN useMemoryCache(void)
8816 {
8817     BOOLEAN result = FALSE;
8818 
8819     if (LYCacheSource == SOURCE_CACHE_MEMORY) {
8820 	result = (BOOLEAN) (HTMainAnchor->source_cache_chunk != 0);
8821 	CTRACE((tfp, "SourceCache: memory-cache%s found\n",
8822 		result ? "" : " not"));
8823     }
8824     return result;
8825 }
8826 
HTreparse_document(void)8827 BOOLEAN HTreparse_document(void)
8828 {
8829     BOOLEAN ok = FALSE;
8830 
8831     if (!HTMainAnchor || LYCacheSource == SOURCE_CACHE_NONE) {
8832 	CTRACE((tfp, "HTreparse_document returns FALSE\n"));
8833     } else if (useSourceCache()) {
8834 	FILE *fp;
8835 	HTFormat format;
8836 	int ret;
8837 
8838 	CTRACE((tfp, "SourceCache: Reparsing file %s\n",
8839 		HTMainAnchor->source_cache_file));
8840 
8841 	magicUncache();
8842 
8843 	/*
8844 	 * This is more or less copied out of HTLoadFile(), except we don't
8845 	 * get a content encoding.  This may be overkill.  -dsb
8846 	 */
8847 	if (HTMainAnchor->content_type) {
8848 	    format = HTAtom_for(HTMainAnchor->content_type);
8849 	} else {
8850 	    format = HTFileFormat(HTMainAnchor->source_cache_file, NULL, NULL);
8851 	    format = HTCharsetFormat(format, HTMainAnchor,
8852 				     UCLYhndl_for_unspec);
8853 	    /* not UCLYhndl_HTFile_for_unspec - we are talking about remote
8854 	     * documents...
8855 	     */
8856 	}
8857 	CTRACE((tfp, "  Content type is \"%s\"\n", format->name));
8858 
8859 	fp = fopen(HTMainAnchor->source_cache_file, "r");
8860 	if (!fp) {
8861 	    CTRACE((tfp, "  Cannot read file %s\n", HTMainAnchor->source_cache_file));
8862 	    (void) LYRemoveTemp(HTMainAnchor->source_cache_file);
8863 	    FREE(HTMainAnchor->source_cache_file);
8864 	} else {
8865 
8866 	    if (HText_HaveUserChangedForms(HTMainText)) {
8867 		/*
8868 		 * Issue a warning.  Will not restore changed forms, currently.
8869 		 */
8870 		HTAlert(RELOADING_FORM);
8871 	    }
8872 	    /* Set HTMainAnchor->protocol or HTMainAnchor->physical to convince
8873 	     * the SourceCacheWriter to not regenerate the cache file (which
8874 	     * would be an unnecessary "loop"). - kw
8875 	     */
8876 	    HTAnchor_setProtocol(HTMainAnchor, &HTFile);
8877 	    ret = HTParseFile(format, HTOutputFormat, HTMainAnchor, fp, NULL);
8878 	    LYCloseInput(fp);
8879 	    if (ret == HT_PARTIAL_CONTENT) {
8880 		HTInfoMsg(gettext("Loading incomplete."));
8881 		CTRACE((tfp,
8882 			"SourceCache: `%s' has been accessed, partial content.\n",
8883 			HTLoadedDocumentURL()));
8884 	    }
8885 	    ok = (BOOL) (ret == HT_LOADED || ret == HT_PARTIAL_CONTENT);
8886 
8887 	    CTRACE((tfp, "Reparse file %s\n", (ok ? "succeeded" : "failed")));
8888 	}
8889     } else if (useMemoryCache()) {
8890 	HTFormat format = WWW_HTML;
8891 	int ret;
8892 
8893 	CTRACE((tfp, "SourceCache: Reparsing from memory chunk %p\n",
8894 		(void *) HTMainAnchor->source_cache_chunk));
8895 
8896 	magicUncache();
8897 
8898 	if (HTMainAnchor->content_type) {
8899 	    format = HTAtom_for(HTMainAnchor->content_type);
8900 	} else {
8901 	    /*
8902 	     * This is only done to make things aligned with SOURCE_CACHE_NONE
8903 	     * and SOURCE_CACHE_FILE when switching to source mode since the
8904 	     * original document's charset will be LYPushAssumed() and then
8905 	     * LYPopAssumed().  See LYK_SOURCE in mainloop if you change
8906 	     * something here.  No user-visible benefits, seems just '=' Info
8907 	     * Page will show source's effective charset as "(assumed)".
8908 	     */
8909 	    format = HTCharsetFormat(format, HTMainAnchor,
8910 				     UCLYhndl_for_unspec);
8911 	}
8912 	/* not UCLYhndl_HTFile_for_unspec - we are talking about remote documents... */
8913 
8914 	if (HText_HaveUserChangedForms(HTMainText)) {
8915 	    /*
8916 	     * Issue a warning.  Will not restore changed forms, currently.
8917 	     */
8918 	    HTAlert(RELOADING_FORM);
8919 	}
8920 	/* Set HTMainAnchor->protocol or HTMainAnchor->physical to convince
8921 	 * the SourceCacheWriter to not regenerate the cache chunk (which
8922 	 * would be an unnecessary "loop"). - kw
8923 	 */
8924 	HTAnchor_setProtocol(HTMainAnchor, &scm);	/* cheating -
8925 							   anything != &HTTP or &HTTPS would do - kw */
8926 	ret = HTParseMem(format, HTOutputFormat, HTMainAnchor,
8927 			 HTMainAnchor->source_cache_chunk, NULL);
8928 	ok = (BOOL) (ret == HT_LOADED);
8929 
8930 	CTRACE((tfp, "Reparse memory %s\n", (ok ? "succeeded" : "failed")));
8931     }
8932 
8933     return ok;
8934 }
8935 
HTcan_reparse_document(void)8936 BOOLEAN HTcan_reparse_document(void)
8937 {
8938     BOOLEAN result = FALSE;
8939 
8940     if (!HTMainAnchor || LYCacheSource == SOURCE_CACHE_NONE) {
8941 	result = FALSE;
8942     } else if (useSourceCache()) {
8943 	result = LYCanReadFile(HTMainAnchor->source_cache_file);
8944     } else if (useMemoryCache()) {
8945 	result = TRUE;
8946     }
8947 
8948     CTRACE((tfp, "HTcan_reparse_document -> %d\n", result));
8949     return result;
8950 }
8951 
trace_setting_change(const char * name,int prev_setting,int new_setting)8952 static void trace_setting_change(const char *name,
8953 				 int prev_setting,
8954 				 int new_setting)
8955 {
8956     if (prev_setting != new_setting)
8957 	CTRACE((tfp,
8958 		"HTdocument_settings_changed: %s setting has changed (was %d, now %d)\n",
8959 		name, prev_setting, new_setting));
8960 }
8961 
HTdocument_settings_changed(void)8962 BOOLEAN HTdocument_settings_changed(void)
8963 {
8964     /*
8965      * Annoying Hack(TM):  If we don't have a source cache, we can't
8966      * reparse anyway, so pretend the settings haven't changed.
8967      */
8968     if (!HTMainText || !HTcan_reparse_document())
8969 	return FALSE;
8970 
8971     if (TRACE) {
8972 	/*
8973 	 * If we're tracing, note everying that has changed.
8974 	 */
8975 	trace_setting_change("CLICKABLE_IMAGES",
8976 			     HTMainText->clickable_images, clickable_images);
8977 	trace_setting_change("PSEUDO_INLINE_ALTS",
8978 			     HTMainText->pseudo_inline_alts,
8979 			     pseudo_inline_alts);
8980 	trace_setting_change("VERBOSE_IMG",
8981 			     HTMainText->verbose_img,
8982 			     verbose_img);
8983 	trace_setting_change("RAW_MODE", HTMainText->raw_mode,
8984 			     LYUseDefaultRawMode);
8985 	trace_setting_change("HISTORICAL_COMMENTS",
8986 			     HTMainText->historical_comments,
8987 			     historical_comments);
8988 	trace_setting_change("MINIMAL_COMMENTS",
8989 			     HTMainText->minimal_comments, minimal_comments);
8990 	trace_setting_change("SOFT_DQUOTES",
8991 			     HTMainText->soft_dquotes, soft_dquotes);
8992 	trace_setting_change("OLD_DTD", HTMainText->old_dtd, Old_DTD);
8993 	trace_setting_change("KEYPAD_MODE",
8994 			     HTMainText->keypad_mode, keypad_mode);
8995 	if (HTMainText->disp_lines != LYlines || HTMainText->disp_cols != DISPLAY_COLS)
8996 	    CTRACE((tfp,
8997 		    "HTdocument_settings_changed: Screen size has changed (was %dx%d, now %dx%d)\n",
8998 		    HTMainText->disp_cols,
8999 		    HTMainText->disp_lines,
9000 		    DISPLAY_COLS,
9001 		    LYlines));
9002     }
9003 
9004     return (BOOLEAN) (HTMainText->clickable_images != clickable_images ||
9005 		      HTMainText->pseudo_inline_alts != pseudo_inline_alts ||
9006 		      HTMainText->verbose_img != verbose_img ||
9007 		      HTMainText->raw_mode != LYUseDefaultRawMode ||
9008 		      HTMainText->historical_comments != historical_comments ||
9009 		      (HTMainText->minimal_comments != minimal_comments &&
9010 		       !historical_comments) ||
9011 		      HTMainText->soft_dquotes != soft_dquotes ||
9012 		      HTMainText->old_dtd != Old_DTD ||
9013 		      HTMainText->keypad_mode != keypad_mode ||
9014 		      HTMainText->disp_cols != DISPLAY_COLS);
9015 }
9016 #endif
9017 
HTisDocumentSource(void)9018 int HTisDocumentSource(void)
9019 {
9020     return (HTMainText != 0) ? HTMainText->source : FALSE;
9021 }
9022 
HTLoadedDocumentURL(void)9023 const char *HTLoadedDocumentURL(void)
9024 {
9025     if (!HTMainText)
9026 	return ("");
9027 
9028     if (HTMainText->node_anchor && HTMainText->node_anchor->address)
9029 	return (HTMainText->node_anchor->address);
9030     else
9031 	return ("");
9032 }
9033 
HTLoadedDocumentPost_data(void)9034 bstring *HTLoadedDocumentPost_data(void)
9035 {
9036     if (HTMainText
9037 	&& HTMainText->node_anchor
9038 	&& HTMainText->node_anchor->post_data)
9039 	return (HTMainText->node_anchor->post_data);
9040     else
9041 	return (0);
9042 }
9043 
HTLoadedDocumentTitle(void)9044 const char *HTLoadedDocumentTitle(void)
9045 {
9046     if (!HTMainText)
9047 	return ("");
9048 
9049     if (HTMainText->node_anchor && HTMainText->node_anchor->title)
9050 	return (HTMainText->node_anchor->title);
9051     else
9052 	return ("");
9053 }
9054 
HTLoadedDocumentIsHEAD(void)9055 BOOLEAN HTLoadedDocumentIsHEAD(void)
9056 {
9057     if (!HTMainText)
9058 	return (FALSE);
9059 
9060     if (HTMainText->node_anchor && HTMainText->node_anchor->isHEAD)
9061 	return (HTMainText->node_anchor->isHEAD);
9062     else
9063 	return (FALSE);
9064 }
9065 
HTLoadedDocumentIsSafe(void)9066 BOOLEAN HTLoadedDocumentIsSafe(void)
9067 {
9068     if (!HTMainText)
9069 	return (FALSE);
9070 
9071     if (HTMainText->node_anchor && HTMainText->node_anchor->safe)
9072 	return (HTMainText->node_anchor->safe);
9073     else
9074 	return (FALSE);
9075 }
9076 
HTLoadedDocumentCharset(void)9077 const char *HTLoadedDocumentCharset(void)
9078 {
9079     const char *result = NULL;
9080 
9081     if (HTMainText &&
9082 	HTMainText->node_anchor) {
9083 	result = HTMainText->node_anchor->charset;
9084     }
9085 
9086     return result;
9087 }
9088 
HTLoadedDocumentEightbit(void)9089 BOOL HTLoadedDocumentEightbit(void)
9090 {
9091     if (!HTMainText)
9092 	return (NO);
9093     else
9094 	return (HTMainText->have_8bit_chars);
9095 }
9096 
HText_setNodeAnchorBookmark(const char * bookmark)9097 void HText_setNodeAnchorBookmark(const char *bookmark)
9098 {
9099     if (!HTMainText)
9100 	return;
9101 
9102     if (HTMainText->node_anchor)
9103 	HTAnchor_setBookmark(HTMainText->node_anchor, bookmark);
9104 }
9105 
HTLoadedDocumentBookmark(void)9106 const char *HTLoadedDocumentBookmark(void)
9107 {
9108     if (!HTMainText)
9109 	return (NULL);
9110 
9111     if (HTMainText->node_anchor && HTMainText->node_anchor->bookmark)
9112 	return (HTMainText->node_anchor->bookmark);
9113     else
9114 	return (NULL);
9115 }
9116 
HText_LastLineSize(HText * text,int IgnoreSpaces)9117 int HText_LastLineSize(HText *text, int IgnoreSpaces)
9118 {
9119     if (!text || !text->last_line || !text->last_line->size)
9120 	return 0;
9121     return HText_TrueLineSize(text->last_line, text, IgnoreSpaces);
9122 }
9123 
HText_LastLineEmpty(HText * text,int IgnoreSpaces)9124 BOOL HText_LastLineEmpty(HText *text, int IgnoreSpaces)
9125 {
9126     if (!text || !text->last_line || !text->last_line->size)
9127 	return TRUE;
9128     return HText_TrueEmptyLine(text->last_line, text, IgnoreSpaces);
9129 }
9130 
HText_LastLineOffset(HText * text)9131 int HText_LastLineOffset(HText *text)
9132 {
9133     if (!text || !text->last_line)
9134 	return 0;
9135     return text->last_line->offset;
9136 }
9137 
HText_PreviousLineSize(HText * text,int IgnoreSpaces)9138 int HText_PreviousLineSize(HText *text, int IgnoreSpaces)
9139 {
9140     HTLine *line;
9141 
9142     if (!text || !text->last_line)
9143 	return 0;
9144     if (!(line = text->last_line->prev))
9145 	return 0;
9146     return HText_TrueLineSize(line, text, IgnoreSpaces);
9147 }
9148 
HText_PreviousLineEmpty(HText * text,int IgnoreSpaces)9149 BOOL HText_PreviousLineEmpty(HText *text, int IgnoreSpaces)
9150 {
9151     HTLine *line;
9152 
9153     if (!text || !text->last_line)
9154 	return TRUE;
9155     if (!(line = text->last_line->prev))
9156 	return TRUE;
9157     return HText_TrueEmptyLine(line, text, IgnoreSpaces);
9158 }
9159 
9160 /*
9161  * Compute the "true" line size.
9162  */
HText_TrueLineSize(HTLine * line,HText * text,int IgnoreSpaces)9163 static int HText_TrueLineSize(HTLine *line, HText *text, int IgnoreSpaces)
9164 {
9165     size_t i;
9166     int true_size = 0;
9167 
9168     if (!(line && line->size))
9169 	return 0;
9170 
9171     if (IgnoreSpaces) {
9172 	for (i = 0; i < line->size; i++) {
9173 	    if (!IsSpecialAttrChar(UCH(line->data[i])) &&
9174 		IS_UTF8_EXTRA(line->data[i]) &&
9175 		!isspace(UCH(line->data[i])) &&
9176 		UCH(line->data[i]) != HT_NON_BREAK_SPACE &&
9177 		UCH(line->data[i]) != HT_EN_SPACE) {
9178 		true_size++;
9179 	    }
9180 	}
9181     } else {
9182 	for (i = 0; i < line->size; i++) {
9183 	    if (!IsSpecialAttrChar(line->data[i]) &&
9184 		IS_UTF8_EXTRA(line->data[i])) {
9185 		true_size++;
9186 	    }
9187 	}
9188     }
9189     return true_size;
9190 }
9191 
9192 /*
9193  * Tell if the line is really empty.  This is invoked much more often than
9194  * HText_TrueLineSize(), and most lines are not empty.  So it is faster to
9195  * do this check than to check if the line size happens to be zero.
9196  */
HText_TrueEmptyLine(HTLine * line,HText * text,int IgnoreSpaces)9197 static BOOL HText_TrueEmptyLine(HTLine *line, HText *text, int IgnoreSpaces)
9198 {
9199     size_t i;
9200 
9201     if (!(line && line->size))
9202 	return TRUE;
9203 
9204     if (IgnoreSpaces) {
9205 	for (i = 0; i < line->size; i++) {
9206 	    if (!IsSpecialAttrChar(UCH(line->data[i])) &&
9207 		IS_UTF8_EXTRA(line->data[i]) &&
9208 		!isspace(UCH(line->data[i])) &&
9209 		UCH(line->data[i]) != HT_NON_BREAK_SPACE &&
9210 		UCH(line->data[i]) != HT_EN_SPACE) {
9211 		return FALSE;
9212 	    }
9213 	}
9214     } else {
9215 	for (i = 0; i < line->size; i++) {
9216 	    if (!IsSpecialAttrChar(line->data[i]) &&
9217 		IS_UTF8_EXTRA(line->data[i])) {
9218 		return FALSE;
9219 	    }
9220 	}
9221     }
9222     return TRUE;
9223 }
9224 
HText_NegateLineOne(HText * text)9225 void HText_NegateLineOne(HText *text)
9226 {
9227     if (text) {
9228 	text->in_line_1 = NO;
9229     }
9230     return;
9231 }
9232 
HText_inLineOne(HText * text)9233 BOOL HText_inLineOne(HText *text)
9234 {
9235     if (text) {
9236 	return text->in_line_1;
9237     }
9238     return YES;
9239 }
9240 
9241 /*
9242  * This function is for removing the first of two
9243  * successive blank lines.  It should be called after
9244  * checking the situation with HText_LastLineSize()
9245  * and HText_PreviousLineSize().  Any characters in
9246  * the removed line (i.e., control characters, or it
9247  * wouldn't have tested blank) should have been
9248  * reiterated by split_line() in the retained blank
9249  * line.  -FM
9250  */
HText_RemovePreviousLine(HText * text)9251 void HText_RemovePreviousLine(HText *text)
9252 {
9253     HTLine *line, *previous;
9254 
9255     if (!(text && text->Lines > 1))
9256 	return;
9257 
9258     line = text->last_line->prev;
9259     previous = line->prev;
9260     previous->next = text->last_line;
9261     text->last_line->prev = previous;
9262     text->Lines--;
9263     freeHTLine(text, line);
9264 }
9265 
9266 /*
9267  * NOTE:  This function presently is correct only if the
9268  *	  alignment is HT_LEFT.  The offset is still zero,
9269  *	  because that's not determined for HT_CENTER or
9270  *	  HT_RIGHT until subsequent characters are received
9271  *	  and split_line() is called. -FM
9272  */
HText_getCurrentColumn(HText * text)9273 int HText_getCurrentColumn(HText *text)
9274 {
9275     int column = 0;
9276     BOOL IgnoreSpaces = FALSE;
9277 
9278     if (text) {
9279 	column = ((text->in_line_1
9280 		   ? (int) text->style->indent1st
9281 		   : (int) text->style->leftIndent)
9282 		  + (int) text->last_line->offset
9283 		  + HText_LastLineSize(text, IgnoreSpaces));
9284     }
9285     return column;
9286 }
9287 
HText_getMaximumColumn(HText * text)9288 int HText_getMaximumColumn(HText *text)
9289 {
9290     int column = DISPLAY_COLS;
9291 
9292     if (text) {
9293 	column -= (int) text->style->rightIndent;
9294     }
9295     return column;
9296 }
9297 
9298 /*
9299  * NOTE:  This function uses HText_getCurrentColumn() which
9300  *	  presently is correct only if the alignment is
9301  *	  HT_LEFT. -FM
9302  */
HText_setTabID(HText * text,const char * name)9303 void HText_setTabID(HText *text, const char *name)
9304 {
9305     HTTabID *Tab = NULL;
9306     HTList *cur = text->tabs;
9307     HTList *last = NULL;
9308 
9309     if (!text || isEmpty(name))
9310 	return;
9311 
9312     if (!cur) {
9313 	cur = text->tabs = HTList_new();
9314     } else {
9315 	while (NULL != (Tab = (HTTabID *) HTList_nextObject(cur))) {
9316 	    if (Tab->name && !strcmp(Tab->name, name))
9317 		return;		/* Already set.  Keep the first value. */
9318 	    last = cur;
9319 	}
9320 	if (last)
9321 	    cur = last;
9322     }
9323     if (!Tab) {			/* New name.  Create a new node */
9324 	Tab = typecalloc(HTTabID);
9325 	if (Tab == NULL)
9326 	    outofmem(__FILE__, "HText_setTabID");
9327 	HTList_addObject(cur, Tab);
9328 	StrAllocCopy(Tab->name, name);
9329     }
9330 
9331     Tab->column = HText_getCurrentColumn(text);
9332     return;
9333 }
9334 
HText_getTabIDColumn(HText * text,const char * name)9335 int HText_getTabIDColumn(HText *text, const char *name)
9336 {
9337     int column = 0;
9338     HTTabID *Tab;
9339     HTList *cur = text->tabs;
9340 
9341     if (text && non_empty(name) && cur) {
9342 	while (NULL != (Tab = (HTTabID *) HTList_nextObject(cur))) {
9343 	    if (Tab->name && !strcmp(Tab->name, name))
9344 		break;
9345 	}
9346 	if (Tab)
9347 	    column = Tab->column;
9348     }
9349     return column;
9350 }
9351 
9352 /*
9353  * This function is for saving the address of a link
9354  * which had an attribute in the markup that resolved
9355  * to a URL (i.e., not just a NAME or ID attribute),
9356  * but was found in HText_endAnchor() to have no visible
9357  * content for use as a link name.  It loads the address
9358  * into text->hidden_links, whose count can be determined
9359  * via HText_HiddenLinks(), below.  The addresses can be
9360  * retrieved via HText_HiddenLinkAt(), below, based on
9361  * count.  -FM
9362  */
HText_AddHiddenLink(HText * text,TextAnchor * textanchor)9363 static void HText_AddHiddenLink(HText *text, TextAnchor *textanchor)
9364 {
9365     HTAnchor *dest;
9366 
9367     /*
9368      * Make sure we have an HText structure and anchor.  -FM
9369      */
9370     if (!(text && textanchor && textanchor->anchor))
9371 	return;
9372 
9373     /*
9374      * Create the hidden links list
9375      * if it hasn't been already.  -FM
9376      */
9377     if (text->hidden_links == NULL)
9378 	text->hidden_links = HTList_new();
9379 
9380     /*
9381      * Store the address, in reverse list order
9382      * so that first in will be first out on
9383      * retrievals.  -FM
9384      */
9385     if ((dest = HTAnchor_followLink(textanchor->anchor)) &&
9386 	(text->hiddenlinkflag != HIDDENLINKS_IGNORE ||
9387 	 HTList_isEmpty(text->hidden_links))) {
9388 	char *value = HTAnchor_address(dest);
9389 	BOOL ignore = FALSE;
9390 
9391 	if (unique_urls) {
9392 	    int cnt;
9393 	    char *check;
9394 
9395 	    for (cnt = 0;; ++cnt) {
9396 
9397 		check = (char *) HTList_objectAt(text->hidden_links, cnt);
9398 		if (check == 0)
9399 		    break;
9400 		if (!strcmp(check, value)) {
9401 		    ignore = TRUE;
9402 		    break;
9403 		}
9404 	    }
9405 	}
9406 	if (ignore) {
9407 	    FREE(value);
9408 	} else {
9409 	    HTList_appendObject(text->hidden_links, value);
9410 	}
9411     }
9412 
9413     return;
9414 }
9415 
9416 /*
9417  * This function returns the number of addresses
9418  * that are loaded in text->hidden_links.  -FM
9419  */
HText_HiddenLinkCount(HText * text)9420 int HText_HiddenLinkCount(HText *text)
9421 {
9422     int count = 0;
9423 
9424     if (text && text->hidden_links)
9425 	count = HTList_count((HTList *) text->hidden_links);
9426 
9427     return (count);
9428 }
9429 
9430 /*
9431  * This function returns the address, corresponding to
9432  * a hidden link, at the position (zero-based) in the
9433  * text->hidden_links list of the number argument.  -FM
9434  */
HText_HiddenLinkAt(HText * text,int number)9435 const char *HText_HiddenLinkAt(HText *text, int number)
9436 {
9437     char *href = NULL;
9438 
9439     if (text && text->hidden_links && number >= 0)
9440 	href = (char *) HTList_objectAt((HTList *) text->hidden_links, number);
9441 
9442     return (href);
9443 }
9444 
9445 /*
9446  * Form methods
9447  * These routines are used to build forms consisting
9448  * of input fields
9449  */
9450 static BOOLEAN HTFormDisabled = FALSE;
9451 static PerFormInfo *HTCurrentForm;
9452 
addFormAction(FormInfo * f)9453 static BOOLEAN addFormAction(FormInfo * f)
9454 {
9455     BOOLEAN result = FALSE;
9456 
9457     if (HTCurrentForm != NULL) {
9458 	result = TRUE;
9459 	f->submit_action = NULL;
9460 	StrAllocCopy(f->submit_action, HTCurrentForm->data.submit_action);
9461 	if (HTCurrentForm->data.submit_enctype != NULL)
9462 	    StrAllocCopy(f->submit_enctype, HTCurrentForm->data.submit_enctype);
9463 	if (HTCurrentForm->data.submit_title != NULL)
9464 	    StrAllocCopy(f->submit_title, HTCurrentForm->data.submit_title);
9465 	f->submit_method = HTCurrentForm->data.submit_method;
9466     }
9467     return result;
9468 }
9469 
HText_beginForm(char * action,char * method,char * enctype,char * title,const char * accept_cs)9470 void HText_beginForm(char *action,
9471 		     char *method,
9472 		     char *enctype,
9473 		     char *title,
9474 		     const char *accept_cs)
9475 {
9476     PerFormInfo *newform;
9477     int HTFormMethod = URL_GET_METHOD;
9478     char *HTFormAction = NULL;
9479     char *HTFormEnctype = NULL;
9480     char *HTFormTitle = NULL;
9481     char *HTFormAcceptCharset = NULL;
9482 
9483     HTFormNumber++;
9484 
9485     HTFormFields = 0;
9486     HTFormDisabled = FALSE;
9487 
9488     /*
9489      * Check the ACTION.  -FM
9490      */
9491     if (action != NULL) {
9492 	if (isMAILTO_URL(action)) {
9493 	    HTFormMethod = URL_MAIL_METHOD;
9494 	}
9495 	StrAllocCopy(HTFormAction, action);
9496     } else
9497 	StrAllocCopy(HTFormAction, HTLoadedDocumentURL());
9498 
9499     /*
9500      * Check the METHOD.  -FM
9501      */
9502     if (method != NULL && HTFormMethod != URL_MAIL_METHOD)
9503 	if (!strcasecomp(method, "post") || !strcasecomp(method, "pget"))
9504 	    HTFormMethod = URL_POST_METHOD;
9505 
9506     /*
9507      * Check the ENCTYPE.  -FM
9508      */
9509     if (non_empty(enctype)) {
9510 	StrAllocCopy(HTFormEnctype, enctype);
9511 	if (HTFormMethod != URL_MAIL_METHOD &&
9512 	    !strncasecomp(enctype, "multipart/form-data", 19))
9513 	    HTFormMethod = URL_POST_METHOD;
9514     } else {
9515 	FREE(HTFormEnctype);
9516     }
9517 
9518     /*
9519      * Check the TITLE.  -FM
9520      */
9521     if (non_empty(title))
9522 	StrAllocCopy(HTFormTitle, title);
9523     else
9524 	FREE(HTFormTitle);
9525 
9526     /*
9527      * Check for an ACCEPT_CHARSET.  If present, store it and
9528      * convert to lowercase and collapse spaces.  - kw
9529      */
9530     if (accept_cs != NULL) {
9531 	StrAllocCopy(HTFormAcceptCharset, accept_cs);
9532 	LYRemoveBlanks(HTFormAcceptCharset);
9533 	LYLowerCase(HTFormAcceptCharset);
9534     }
9535 
9536     /*
9537      * Create a new "PerFormInfo" structure to hold info on the current form.
9538      * This will be appended to the forms list kept by the HText object if and
9539      * when we reach a HText_endForm.
9540      */
9541     newform = typecalloc(PerFormInfo);
9542     if (newform == NULL)
9543 	outofmem(__FILE__, "HText_beginForm");
9544 
9545     PerFormInfo_free(HTCurrentForm);	/* shouldn't happen here - kw */
9546     HTCurrentForm = newform;
9547 
9548     newform->number = HTFormNumber;
9549     newform->data.submit_action = HTFormAction;
9550     newform->data.submit_enctype = HTFormEnctype;
9551     newform->data.submit_method = HTFormMethod;
9552     newform->data.submit_title = HTFormTitle;
9553     newform->accept_cs = HTFormAcceptCharset;
9554 
9555     CTRACE((tfp, "BeginForm: action:%s Method:%d%s%s%s%s%s%s\n",
9556 	    HTFormAction, HTFormMethod,
9557 	    (HTFormTitle ? " Title:" : ""),
9558 	    NonNull(HTFormTitle),
9559 	    (HTFormEnctype ? " Enctype:" : ""),
9560 	    NonNull(HTFormEnctype),
9561 	    (HTFormAcceptCharset ? " Accept-charset:" : ""),
9562 	    NonNull(HTFormAcceptCharset)));
9563 }
9564 
HText_endForm(HText * text)9565 void HText_endForm(HText *text)
9566 {
9567     if (text != NULL) {
9568 	if (HTFormFields == 1 && text->first_anchor) {
9569 	    /*
9570 	     * Support submission of a single text input field in
9571 	     * the form via <return> instead of a submit button.  -FM
9572 	     */
9573 	    TextAnchor *a;
9574 
9575 	    /*
9576 	     * Go through list of anchors and get our input field.  -FM
9577 	     */
9578 	    for (a = text->first_anchor; a != NULL; a = a->next) {
9579 		if (a->link_type == INPUT_ANCHOR &&
9580 		    a->input_field->number == HTFormNumber &&
9581 		    a->input_field->type != F_TEXTAREA_TYPE &&
9582 		    F_TEXTLIKE(a->input_field->type)) {
9583 		    /*
9584 		     * Got it.  Make it submitting.  -FM
9585 		     */
9586 		    if (addFormAction(a->input_field)) {
9587 			a->input_field->type = F_TEXT_SUBMIT_TYPE;
9588 			if (HTFormDisabled)
9589 			    a->input_field->disabled = TRUE;
9590 		    }
9591 		    break;
9592 		}
9593 	    }
9594 	}
9595 
9596 	/*
9597 	 * Append info on the current form to the HText object's list of forms.
9598 	 * HText_beginInput call will have set some of the data in the
9599 	 * PerFormInfo structure (if there were any form fields at all).
9600 	 */
9601 	if (HTCurrentForm) {
9602 	    if (HTFormDisabled)
9603 		HTCurrentForm->disabled = TRUE;
9604 	    if (!text->forms)
9605 		text->forms = HTList_new();
9606 	    HTList_appendObject(text->forms, HTCurrentForm);
9607 	    HTCurrentForm = NULL;
9608 	} else {
9609 	    CTRACE((tfp, "endForm:    HTCurrentForm is missing!\n"));
9610 	}
9611     } else {
9612 	CTRACE((tfp, "endForm:    HText is missing!\n"));
9613     }
9614 
9615     FREE(HTCurSelectGroup);
9616     FREE(HTCurSelectGroupSize);
9617     FREE(HTCurSelectedOptionValue);
9618     HTFormFields = 0;
9619     HTFormDisabled = FALSE;
9620 }
9621 
HText_beginSelect(char * name,int name_cs,int multiple,char * size)9622 void HText_beginSelect(char *name,
9623 		       int name_cs,
9624 		       int multiple,
9625 		       char *size)
9626 {
9627     /*
9628      * Save the group name.
9629      */
9630     StrAllocCopy(HTCurSelectGroup, name);
9631     HTCurSelectGroupCharset = name_cs;
9632 
9633     /*
9634      * If multiple then all options are actually checkboxes.
9635      */
9636     if (multiple)
9637 	HTCurSelectGroupType = F_CHECKBOX_TYPE;
9638     /*
9639      * If not multiple then all options are radio buttons.
9640      */
9641     else
9642 	HTCurSelectGroupType = F_RADIO_TYPE;
9643 
9644     /*
9645      * Length of an option list.
9646      */
9647     StrAllocCopy(HTCurSelectGroupSize, size);
9648 
9649     CTRACE((tfp, "HText_beginSelect: name=%s type=%d size=%s\n",
9650 	    ((HTCurSelectGroup == NULL) ?
9651 	     "<NULL>" : HTCurSelectGroup),
9652 	    HTCurSelectGroupType,
9653 	    ((HTCurSelectGroupSize == NULL) ?
9654 	     "<NULL>" : HTCurSelectGroupSize)));
9655     CTRACE((tfp, "HText_beginSelect: name_cs=%d \"%s\"\n",
9656 	    HTCurSelectGroupCharset,
9657 	    (HTCurSelectGroupCharset >= 0 ?
9658 	     LYCharSet_UC[HTCurSelectGroupCharset].MIMEname : "<UNKNOWN>")));
9659 }
9660 
9661 /*
9662  *  This function returns the number of the option whose
9663  *  value currently is being accumulated for a select
9664  *  block. - LE && FM
9665  */
HText_getOptionNum(HText * text)9666 int HText_getOptionNum(HText *text)
9667 {
9668     TextAnchor *a;
9669     OptionType *op;
9670     int n = 1;			/* start count at 1 */
9671 
9672     if (!(text && text->last_anchor))
9673 	return (0);
9674 
9675     a = text->last_anchor;
9676     if (!(a->link_type == INPUT_ANCHOR && a->input_field &&
9677 	  a->input_field->type == F_OPTION_LIST_TYPE))
9678 	return (0);
9679 
9680     for (op = a->input_field->select_list; op; op = op->next)
9681 	n++;
9682     CTRACE((tfp, "HText_getOptionNum: Got number '%d'.\n", n));
9683     return (n);
9684 }
9685 
9686 /*
9687  *  This function checks for a numbered option pattern
9688  *  as the prefix for an option value.  If present, and
9689  *  we are in the correct keypad mode, it returns a
9690  *  pointer to the actual value, following that prefix.
9691  *  Otherwise, it returns the original pointer.
9692  */
HText_skipOptionNumPrefix(char * opname)9693 static char *HText_skipOptionNumPrefix(char *opname)
9694 {
9695     /*
9696      * Check if we are in the correct keypad mode.
9697      */
9698     if (fields_are_numbered()) {
9699 	/*
9700 	 * Skip the option number embedded in the option name so the
9701 	 * extra chars won't mess up cgi scripts processing the value.
9702 	 * The format is (nnn)__ where nnn is a number and there is a
9703 	 * minimum of 5 chars (no underscores if (nnn) exceeds 5 chars).
9704 	 * See HTML.c.  If the chars don't exactly match this format,
9705 	 * just use all of opname.  - LE
9706 	 */
9707 	char *cp = opname;
9708 
9709 	if ((non_empty(cp) && *cp++ == '(') &&
9710 	    *cp && isdigit(UCH(*cp++))) {
9711 	    while (*cp && isdigit(UCH(*cp)))
9712 		++cp;
9713 	    if (*cp && *cp++ == ')') {
9714 		int i = (int) (cp - opname);
9715 
9716 		while (i < 5) {
9717 		    if (*cp != '_')
9718 			break;
9719 		    i++;
9720 		    cp++;
9721 		}
9722 		if (i < 5) {
9723 		    cp = opname;
9724 		}
9725 	    } else {
9726 		cp = opname;
9727 	    }
9728 	} else {
9729 	    cp = opname;
9730 	}
9731 	return (cp);
9732     }
9733 
9734     return (opname);
9735 }
9736 
9737 /*
9738  * We couldn't set the value field for the previous option tag so we have to do
9739  * it now.  Assume that the last anchor was the previous options' tag.
9740  */
HText_setLastOptionValue(HText * text,char * value,char * submit_value,int order,int checked,int val_cs,int submit_val_cs)9741 char *HText_setLastOptionValue(HText *text, char *value,
9742 			       char *submit_value,
9743 			       int order,
9744 			       int checked,
9745 			       int val_cs,
9746 			       int submit_val_cs)
9747 {
9748     char *cp, *cp1;
9749     char *ret_Value = NULL;
9750     unsigned char *tmp = NULL;
9751     int number = 0, i, j;
9752 
9753     if (!(value
9754 	  && text
9755 	  && text->last_anchor
9756 	  && text->last_anchor->input_field
9757 	  && text->last_anchor->link_type == INPUT_ANCHOR)) {
9758 	CTRACE((tfp, "HText_setLastOptionValue: invalid call!  value:%s!\n",
9759 		(value ? value : "<NULL>")));
9760 	return NULL;
9761     }
9762 
9763     CTRACE((tfp,
9764 	    "Entering HText_setLastOptionValue: value:\"%s\", checked:%s\n",
9765 	    value, (checked ? "on" : "off")));
9766 
9767     /*
9768      * Strip end spaces, newline is also whitespace.
9769      */
9770     if (*value) {
9771 	cp = &value[strlen(value) - 1];
9772 	while ((cp >= value) && (isspace(UCH(*cp)) ||
9773 				 IsSpecialAttrChar(UCH(*cp))))
9774 	    cp--;
9775 	*(cp + 1) = '\0';
9776     }
9777 
9778     /*
9779      * Find first non space
9780      */
9781     cp = value;
9782     while (isspace(UCH(*cp)) ||
9783 	   IsSpecialAttrChar(UCH(*cp)))
9784 	cp++;
9785     if (HTCurSelectGroupType == F_RADIO_TYPE &&
9786 	LYSelectPopups &&
9787 	fields_are_numbered()) {
9788 	/*
9789 	 * Collapse any space between the popup option
9790 	 * prefix and actual value.  -FM
9791 	 */
9792 	if ((cp1 = HText_skipOptionNumPrefix(cp)) > cp) {
9793 	    i = 0, j = (int) (cp1 - cp);
9794 	    while (isspace(UCH(cp1[i])) ||
9795 		   IsSpecialAttrChar(UCH(cp1[i]))) {
9796 		i++;
9797 	    }
9798 	    if (i > 0) {
9799 		while (cp1[i] != '\0')
9800 		    cp[j++] = cp1[i++];
9801 		cp[j] = '\0';
9802 	    }
9803 	}
9804     }
9805 
9806     if (HTCurSelectGroupType == F_CHECKBOX_TYPE) {
9807 	StrAllocCopy(text->last_anchor->input_field->value, cp);
9808 	text->last_anchor->input_field->value_cs = val_cs;
9809 	/*
9810 	 * Put the text on the screen as well.
9811 	 */
9812 	HText_appendText(text, cp);
9813 
9814     } else if (LYSelectPopups == FALSE) {
9815 	StrAllocCopy(text->last_anchor->input_field->value,
9816 		     (submit_value ? submit_value : cp));
9817 	text->last_anchor->input_field->value_cs = (submit_value ?
9818 						    submit_val_cs : val_cs);
9819 	/*
9820 	 * Put the text on the screen as well.
9821 	 */
9822 	HText_appendText(text, cp);
9823 
9824     } else {
9825 	/*
9826 	 * Create a linked list of option values.
9827 	 */
9828 	OptionType *op_ptr = text->last_anchor->input_field->select_list;
9829 	OptionType *new_ptr = NULL;
9830 	BOOLEAN first_option = FALSE;
9831 
9832 	/*
9833 	 * Deal with newlines or tabs.
9834 	 */
9835 	LYReduceBlanks(value);
9836 
9837 	if (!op_ptr) {
9838 	    /*
9839 	     * No option items yet.
9840 	     */
9841 	    if (text->last_anchor->input_field->type != F_OPTION_LIST_TYPE) {
9842 		CTRACE((tfp,
9843 			"HText_setLastOptionValue: last input_field not F_OPTION_LIST_TYPE (%d)\n",
9844 			F_OPTION_LIST_TYPE));
9845 		CTRACE((tfp, "                          but %d, ignoring!\n",
9846 			text->last_anchor->input_field->type));
9847 		return NULL;
9848 	    }
9849 
9850 	    new_ptr = typecalloc(OptionType);
9851 	    if (new_ptr == NULL)
9852 		outofmem(__FILE__, "HText_setLastOptionValue");
9853 
9854 	    text->last_anchor->input_field->select_list = new_ptr;
9855 	    first_option = TRUE;
9856 	} else {
9857 	    while (op_ptr->next) {
9858 		number++;
9859 		op_ptr = op_ptr->next;
9860 	    }
9861 	    number++;		/* add one more */
9862 
9863 	    op_ptr->next = new_ptr = typecalloc(OptionType);
9864 	    if (new_ptr == NULL)
9865 		outofmem(__FILE__, "HText_setLastOptionValue");
9866 	}
9867 
9868 	new_ptr->name = NULL;
9869 	new_ptr->cp_submit_value = NULL;
9870 	new_ptr->next = NULL;
9871 	/*
9872 	 * Find first non-space again, convert_to_spaces above may have
9873 	 * changed the string.  - kw
9874 	 */
9875 	cp = value;
9876 	while (isspace(UCH(*cp)) ||
9877 	       IsSpecialAttrChar(UCH(*cp)))
9878 	    cp++;
9879 	for (i = 0, j = 0; cp[i]; i++) {
9880 	    if (cp[i] == HT_NON_BREAK_SPACE ||
9881 		cp[i] == HT_EN_SPACE) {
9882 		cp[j++] = ' ';
9883 	    } else if (cp[i] != LY_SOFT_HYPHEN &&
9884 		       !IsSpecialAttrChar(UCH(cp[i]))) {
9885 		cp[j++] = cp[i];
9886 	    }
9887 	}
9888 	cp[j] = '\0';
9889 	if (IS_CJK_TTY) {
9890 	    if ((tmp = typecallocn(unsigned char, strlen(cp) * 2 + 1)) != 0) {
9891 		if (kanji_code == EUC) {
9892 		    TO_EUC((unsigned char *) cp, tmp);
9893 		    val_cs = current_char_set;
9894 		} else if (kanji_code == SJIS) {
9895 		    TO_SJIS((unsigned char *) cp, tmp);
9896 		    val_cs = current_char_set;
9897 		} else {
9898 		    for (i = 0, j = 0; cp[i]; i++) {
9899 			if (cp[i] != CH_ESC) {	/* S/390 -- gil -- 1604 */
9900 			    tmp[j++] = UCH(cp[i]);
9901 			}
9902 		    }
9903 		}
9904 		StrAllocCopy(new_ptr->name, (const char *) tmp);
9905 		FREE(tmp);
9906 	    } else {
9907 		outofmem(__FILE__, "HText_setLastOptionValue");
9908 	    }
9909 	} else {
9910 	    StrAllocCopy(new_ptr->name, cp);
9911 	}
9912 	StrAllocCopy(new_ptr->cp_submit_value,
9913 		     (submit_value ? submit_value :
9914 		      HText_skipOptionNumPrefix(new_ptr->name)));
9915 	new_ptr->value_cs = (submit_value ? submit_val_cs : val_cs);
9916 
9917 	if (first_option) {
9918 	    FormInfo *last_input = text->last_anchor->input_field;
9919 
9920 	    StrAllocCopy(HTCurSelectedOptionValue, new_ptr->name);
9921 	    last_input->num_value = 0;
9922 	    /*
9923 	     * If this is the first option in a popup select list,
9924 	     * HText_beginInput may have allocated the value and
9925 	     * cp_submit_value fields, so free them now to avoid
9926 	     * a memory leak.  - kw
9927 	     */
9928 	    FREE(last_input->value);
9929 	    FREE(last_input->cp_submit_value);
9930 
9931 	    last_input->value = last_input->select_list->name;
9932 	    last_input->orig_value = last_input->select_list->name;
9933 	    last_input->cp_submit_value = last_input->select_list->cp_submit_value;
9934 	    last_input->orig_submit_value = last_input->select_list->cp_submit_value;
9935 	    last_input->value_cs = new_ptr->value_cs;
9936 	} else {
9937 	    int newlen = (int) strlen(new_ptr->name);
9938 	    int curlen = (int) (HTCurSelectedOptionValue
9939 				? strlen(HTCurSelectedOptionValue)
9940 				: 0);
9941 
9942 	    /*
9943 	     * Make the selected Option Value as long as
9944 	     * the longest option.
9945 	     */
9946 	    if (newlen > curlen)
9947 		StrAllocCat(HTCurSelectedOptionValue,
9948 			    UNDERSCORES(newlen - curlen));
9949 	}
9950 
9951 	if (checked) {
9952 	    int curlen = (int) strlen(new_ptr->name);
9953 	    int newlen = (HTCurSelectedOptionValue
9954 			  ? (int) strlen(HTCurSelectedOptionValue)
9955 			  : 0);
9956 	    FormInfo *last_input = text->last_anchor->input_field;
9957 
9958 	    /*
9959 	     * Set the default option as this one.
9960 	     */
9961 	    last_input->num_value = number;
9962 	    last_input->value = new_ptr->name;
9963 	    last_input->orig_value = new_ptr->name;
9964 	    last_input->cp_submit_value = new_ptr->cp_submit_value;
9965 	    last_input->orig_submit_value = new_ptr->cp_submit_value;
9966 	    last_input->value_cs = new_ptr->value_cs;
9967 	    StrAllocCopy(HTCurSelectedOptionValue, new_ptr->name);
9968 	    if (newlen > curlen)
9969 		StrAllocCat(HTCurSelectedOptionValue,
9970 			    UNDERSCORES(newlen - curlen));
9971 	}
9972 
9973 	/*
9974 	 * Return the selected Option value to be sent to the screen.
9975 	 */
9976 	if (order == LAST_ORDER) {
9977 	    /*
9978 	     * Change the value.
9979 	     */
9980 	    if (HTCurSelectedOptionValue == 0)
9981 		StrAllocCopy(HTCurSelectedOptionValue, "");
9982 	    text->last_anchor->input_field->size =
9983 		(int) strlen(HTCurSelectedOptionValue);
9984 	    ret_Value = HTCurSelectedOptionValue;
9985 	}
9986     }
9987 
9988     if (TRACE) {
9989 	CTRACE((tfp, "HText_setLastOptionValue:%s value=\"%s\"\n",
9990 		(order == LAST_ORDER) ? " LAST_ORDER" : "",
9991 		value));
9992 	CTRACE((tfp, "            val_cs=%d \"%s\"",
9993 		val_cs,
9994 		(val_cs >= 0 ?
9995 		 LYCharSet_UC[val_cs].MIMEname : "<UNKNOWN>")));
9996 	if (submit_value) {
9997 	    CTRACE((tfp, " (submit_val_cs %d \"%s\") submit_value%s=\"%s\"\n",
9998 		    submit_val_cs,
9999 		    (submit_val_cs >= 0 ?
10000 		     LYCharSet_UC[submit_val_cs].MIMEname : "<UNKNOWN>"),
10001 		    (HTCurSelectGroupType == F_CHECKBOX_TYPE) ?
10002 		    "(ignored)" : "",
10003 		    submit_value));
10004 	} else {
10005 	    CTRACE((tfp, "\n"));
10006 	}
10007     }
10008     return (ret_Value);
10009 }
10010 
10011 /*
10012  * Assign a form input anchor.
10013  * Returns the number of characters to leave
10014  * blank so that the input field can fit.
10015  */
HText_beginInput(HText * text,int underline,InputFieldData * I)10016 int HText_beginInput(HText *text,
10017 		     int underline,
10018 		     InputFieldData * I)
10019 {
10020     TextAnchor *a;
10021     FormInfo *f;
10022     const char *cp_option = NULL;
10023     char *IValue = NULL;
10024     unsigned char *tmp = NULL;
10025     int i, j;
10026     int adjust_marker = 0;
10027     int MaximumSize;
10028     char marker[16];
10029 
10030     CTRACE((tfp, "GridText: Entering HText_beginInput type=%s\n", NonNull(I->type)));
10031 
10032     POOLtypecalloc(TextAnchor, a);
10033 
10034     POOLtypecalloc(FormInfo, f);
10035     if (a == NULL || f == NULL)
10036 	outofmem(__FILE__, "HText_beginInput");
10037 
10038     a->sgml_offset = SGML_offset();
10039     a->inUnderline = (BOOLEAN) underline;
10040     a->line_num = text->Lines;
10041     a->line_pos = (short) text->last_line->size;
10042 
10043     /*
10044      * If this is a radio button, or an OPTION we're converting
10045      * to a radio button, and it's the first with this name, make
10046      * sure it's checked by default.  Otherwise, if it's checked,
10047      * uncheck the default or any preceding radio button with this
10048      * name that was checked.  -FM
10049      */
10050     if (I->type != NULL && !strcmp(I->type, "OPTION") &&
10051 	HTCurSelectGroupType == F_RADIO_TYPE && LYSelectPopups == FALSE) {
10052 	I->type = "RADIO";
10053 	I->name = HTCurSelectGroup;
10054 	I->name_cs = HTCurSelectGroupCharset;
10055     }
10056     if (I->name && I->type && !strcasecomp(I->type, "radio")) {
10057 	if (!text->last_anchor) {
10058 	    I->checked = TRUE;
10059 	} else {
10060 	    TextAnchor *b;
10061 	    int i2 = 0;
10062 
10063 	    for (b = text->first_anchor; b != NULL; b = b->next) {
10064 		if (b->link_type == INPUT_ANCHOR &&
10065 		    b->input_field->type == F_RADIO_TYPE &&
10066 		    b->input_field->number == HTFormNumber) {
10067 		    if (!strcmp(b->input_field->name, I->name)) {
10068 			if (I->checked && b->input_field->num_value) {
10069 			    b->input_field->num_value = 0;
10070 			    StrAllocCopy(b->input_field->orig_value, "0");
10071 			    break;
10072 			}
10073 			i2++;
10074 		    }
10075 		}
10076 	    }
10077 	    if (i2 == 0)
10078 		I->checked = TRUE;
10079 	}
10080     }
10081 
10082     a->next = 0;
10083     a->anchor = NULL;
10084     a->link_type = INPUT_ANCHOR;
10085     a->show_anchor = YES;
10086 
10087     LYClearHiText(a);
10088     a->extent = 2;
10089 
10090     a->input_field = f;
10091 
10092     f->select_list = 0;
10093     f->number = HTFormNumber;
10094     f->disabled = HTFormDisabled || I->disabled;
10095     f->readonly = I->readonly;
10096     f->no_cache = NO;
10097 
10098     HTFormFields++;
10099 
10100     /*
10101      * Set up VALUE.
10102      */
10103     if (I->value) {
10104 	StrAllocCopy(IValue, I->value);
10105     }
10106     if (IValue &&
10107 	IS_CJK_TTY &&
10108 	((I->type == NULL) || strcasecomp(I->type, "hidden"))) {
10109 	if ((tmp = typecallocn(unsigned char, strlen(IValue) * 2 + 1)) != 0) {
10110 	    if (kanji_code == EUC) {
10111 		TO_EUC((unsigned char *) IValue, tmp);
10112 		I->value_cs = current_char_set;
10113 	    } else if (kanji_code == SJIS) {
10114 		TO_SJIS((unsigned char *) IValue, tmp);
10115 		I->value_cs = current_char_set;
10116 	    } else {
10117 		for (i = 0, j = 0; IValue[i]; i++) {
10118 		    if (IValue[i] != CH_ESC) {	/* S/390 -- gil -- 1621 */
10119 			tmp[j++] = UCH(IValue[i]);
10120 		    }
10121 		}
10122 	    }
10123 	    StrAllocCopy(IValue, (const char *) tmp);
10124 	    FREE(tmp);
10125 	}
10126     }
10127 
10128     /*
10129      * Special case of OPTION.
10130      * Is handled above if radio type and LYSelectPopups is FALSE.
10131      */
10132     /* set the values and let the parsing below do the work */
10133     if (I->type != NULL && !strcmp(I->type, "OPTION")) {
10134 	cp_option = I->type;
10135 	if (HTCurSelectGroupType == F_RADIO_TYPE)
10136 	    I->type = "OPTION_LIST";
10137 	else
10138 	    I->type = "CHECKBOX";
10139 	I->name = HTCurSelectGroup;
10140 	I->name_cs = HTCurSelectGroupCharset;
10141 
10142 	/*
10143 	 * The option's size parameter actually gives the length and not
10144 	 * the width of the list.  Perform the conversion here
10145 	 * and get rid of the allocated HTCurSelect....
10146 	 * 0 is ok as it means any length (arbitrary decision).
10147 	 */
10148 	if (HTCurSelectGroupSize != NULL) {
10149 	    f->size_l = atoi(HTCurSelectGroupSize);
10150 	    FREE(HTCurSelectGroupSize);
10151 	}
10152     }
10153 
10154     /*
10155      * Set SIZE.
10156      */
10157     if (I->size != 0) {
10158 	f->size = I->size;
10159 	/*
10160 	 * Leave at zero for option lists.
10161 	 */
10162 	if (f->size == 0 && cp_option == NULL) {
10163 	    f->size = 20;	/* default */
10164 	}
10165     } else {
10166 	f->size = 20;		/* default */
10167     }
10168 
10169     /*
10170      * Set MAXLENGTH.
10171      */
10172     if (I->maxlength != NULL) {
10173 	f->maxlength = (unsigned) atoi(I->maxlength);
10174     } else {
10175 	f->maxlength = 0;	/* 0 means infinite */
10176     }
10177 
10178     /*
10179      * Set CHECKED
10180      * (num_value is only relevant to check and radio types).
10181      */
10182     if (I->checked == TRUE)
10183 	f->num_value = 1;
10184     else
10185 	f->num_value = 0;
10186 
10187     /*
10188      * Set TYPE.
10189      */
10190     if (I->type != NULL) {
10191 	if (!strcasecomp(I->type, "password")) {
10192 	    f->type = F_PASSWORD_TYPE;
10193 	} else if (!strcasecomp(I->type, "checkbox")) {
10194 	    f->type = F_CHECKBOX_TYPE;
10195 	} else if (!strcasecomp(I->type, "radio")) {
10196 	    f->type = F_RADIO_TYPE;
10197 	} else if (!strcasecomp(I->type, "submit")) {
10198 	    f->type = F_SUBMIT_TYPE;
10199 	} else if (!strcasecomp(I->type, "image")) {
10200 	    f->type = F_IMAGE_SUBMIT_TYPE;
10201 	} else if (!strcasecomp(I->type, "reset")) {
10202 	    f->type = F_RESET_TYPE;
10203 	} else if (!strcasecomp(I->type, "OPTION_LIST")) {
10204 	    f->type = F_OPTION_LIST_TYPE;
10205 	} else if (!strcasecomp(I->type, "hidden")) {
10206 	    f->type = F_HIDDEN_TYPE;
10207 	    HTFormFields--;
10208 	    f->size = 0;
10209 	} else if (!strcasecomp(I->type, "textarea")) {
10210 	    f->type = F_TEXTAREA_TYPE;
10211 	} else if (!strcasecomp(I->type, "range")) {
10212 	    f->type = F_RANGE_TYPE;
10213 	} else if (!strcasecomp(I->type, "file")) {
10214 	    f->type = F_FILE_TYPE;
10215 	    CTRACE((tfp, "ok, got a file uploader\n"));
10216 	} else if (!strcasecomp(I->type, "keygen")) {
10217 	    f->type = F_KEYGEN_TYPE;
10218 	} else if (!strcasecomp(I->type, "button")) {
10219 	    f->type = F_BUTTON_TYPE;
10220 	} else {
10221 	    /*
10222 	     * Note that TYPE="scribble" defaults to TYPE="text".  -FM
10223 	     */
10224 	    f->type = F_TEXT_TYPE;	/* default */
10225 	}
10226     } else {
10227 	f->type = F_TEXT_TYPE;
10228     }
10229 
10230     /*
10231      * Set NAME.
10232      */
10233     if (I->name != NULL) {
10234 	StrAllocCopy(f->name, I->name);
10235 	f->name_cs = I->name_cs;
10236     } else {
10237 	if (f->type == F_RESET_TYPE ||
10238 	    f->type == F_SUBMIT_TYPE ||
10239 	    f->type == F_IMAGE_SUBMIT_TYPE) {
10240 	    /*
10241 	     * Set name to empty string.
10242 	     */
10243 	    StrAllocCopy(f->name, "");
10244 	} else {
10245 	    /*
10246 	     * Error!  NAME must be present.
10247 	     */
10248 	    CTRACE((tfp,
10249 		    "GridText: No name present in input field; not displaying\n"));
10250 	    FREE(IValue);
10251 	    return (0);
10252 	}
10253     }
10254 
10255     /*
10256      * Add this anchor to the anchor list
10257      */
10258     if (text->last_anchor) {
10259 	text->last_anchor->next = a;
10260     } else {
10261 	text->first_anchor = a;
10262     }
10263 
10264     /*
10265      * Set VALUE, if it exists.  Otherwise, if it's not
10266      * an option list make it a zero-length string.  -FM
10267      */
10268     if (IValue != NULL) {
10269 	/*
10270 	 * OPTION VALUE is not actually the value to be seen but is to
10271 	 * be sent....
10272 	 */
10273 	if (f->type == F_OPTION_LIST_TYPE ||
10274 	    f->type == F_CHECKBOX_TYPE) {
10275 	    /*
10276 	     * Fill both with the value.  The f->value may be
10277 	     * overwritten in HText_setLastOptionValue....
10278 	     */
10279 	    StrAllocCopy(f->value, IValue);
10280 	    StrAllocCopy(f->cp_submit_value, IValue);
10281 	} else {
10282 	    StrAllocCopy(f->value, IValue);
10283 	}
10284 	f->value_cs = I->value_cs;
10285     } else if (f->type != F_OPTION_LIST_TYPE) {
10286 	StrAllocCopy(f->value, "");
10287 	/*
10288 	 * May be an empty INPUT field.  The text entered will then
10289 	 * probably be in the current display character set.  - kw
10290 	 */
10291 	f->value_cs = current_char_set;
10292     }
10293 
10294     /*
10295      * Run checks and fill in necessary values.
10296      */
10297     if (f->type == F_RESET_TYPE) {
10298 	if (non_empty(f->value)) {
10299 	    f->size = (int) strlen(f->value);
10300 	} else {
10301 	    StrAllocCopy(f->value, "Reset");
10302 	    f->size = 5;
10303 	}
10304     } else if (f->type == F_BUTTON_TYPE) {
10305 	if (non_empty(f->value)) {
10306 	    f->size = (int) strlen(f->value);
10307 	} else {
10308 	    StrAllocCopy(f->value, "BUTTON");
10309 	    f->size = 5;
10310 	}
10311     } else if (f->type == F_IMAGE_SUBMIT_TYPE ||
10312 	       f->type == F_SUBMIT_TYPE) {
10313 	if (non_empty(f->value)) {
10314 	    f->size = (int) strlen(f->value);
10315 	} else if (f->type == F_IMAGE_SUBMIT_TYPE) {
10316 	    StrAllocCopy(f->value, "[IMAGE]-Submit");
10317 	    f->size = 14;
10318 	} else {
10319 	    StrAllocCopy(f->value, "Submit");
10320 	    f->size = 6;
10321 	}
10322 	addFormAction(f);
10323     } else if (f->type == F_RADIO_TYPE || f->type == F_CHECKBOX_TYPE) {
10324 	f->size = 3;
10325 	if (IValue == NULL)
10326 	    StrAllocCopy(f->value, (f->type == F_CHECKBOX_TYPE ? "on" : ""));
10327 
10328     }
10329     FREE(IValue);
10330 
10331     /*
10332      * Set original values.
10333      */
10334     if (f->type == F_RADIO_TYPE || f->type == F_CHECKBOX_TYPE) {
10335 	if (f->num_value)
10336 	    StrAllocCopy(f->orig_value, "1");
10337 	else
10338 	    StrAllocCopy(f->orig_value, "0");
10339     } else if (f->type == F_OPTION_LIST_TYPE) {
10340 	f->orig_value = NULL;
10341     } else {
10342 	StrAllocCopy(f->orig_value, f->value);
10343     }
10344 
10345     /*
10346      * Store accept-charset if present, converting to lowercase
10347      * and collapsing spaces.  - kw
10348      */
10349     if (I->accept_cs) {
10350 	StrAllocCopy(f->accept_cs, I->accept_cs);
10351 	LYRemoveBlanks(f->accept_cs);
10352 	LYLowerCase(f->accept_cs);
10353     }
10354 
10355     /*
10356      * Add numbers to form fields if needed.  - LE & FM
10357      */
10358     switch (f->type) {
10359 	/*
10360 	 * Do not supply number for hidden fields, nor
10361 	 * for types that are not yet implemented.
10362 	 */
10363     case F_HIDDEN_TYPE:
10364 #ifndef USE_FILE_UPLOAD
10365     case F_FILE_TYPE:
10366 #endif
10367     case F_RANGE_TYPE:
10368     case F_KEYGEN_TYPE:
10369     case F_BUTTON_TYPE:
10370 	a->number = 0;
10371 	break;
10372 
10373     default:
10374 	if (fields_are_numbered())
10375 	    a->number = ++(text->last_anchor_number);
10376 	else
10377 	    a->number = 0;
10378 	break;
10379     }
10380     if (fields_are_numbered() && (a->number > 0)) {
10381 	if (HTMainText != 0) {
10382 	    HText_findAnchorNumber(a);
10383 	} else {
10384 	    a->show_number = a->number;
10385 	}
10386 	sprintf(marker, "[%d]", a->show_number);
10387 	adjust_marker = (int) strlen(marker);
10388 	if (number_fields_on_left) {
10389 	    BOOL had_bracket = (BOOL) (f->type == F_OPTION_LIST_TYPE);
10390 
10391 	    HText_appendText(text, had_bracket ? (marker + 1) : marker);
10392 	    if (had_bracket)
10393 		HText_appendCharacter(text, '[');
10394 	}
10395 	a->line_num = text->Lines;
10396 	a->line_pos = (short) text->last_line->size;
10397     } else {
10398 	*marker = '\0';
10399     }
10400 
10401     /*
10402      * Restrict SIZE to maximum allowable size.
10403      */
10404     MaximumSize = WRAP_COLS(text) + 1 - adjust_marker;
10405     switch (f->type) {
10406 
10407     case F_SUBMIT_TYPE:
10408     case F_IMAGE_SUBMIT_TYPE:
10409     case F_RESET_TYPE:
10410     case F_TEXT_TYPE:
10411     case F_TEXTAREA_TYPE:
10412 	/*
10413 	 * For submit and reset buttons, and for text entry
10414 	 * fields and areas, we limit the size element to that
10415 	 * of one line for the current style because that's
10416 	 * the most we could highlight on overwrites, and/or
10417 	 * handle in the line editor.  The actual values for
10418 	 * text entry lines can be long, and will be scrolled
10419 	 * horizontally within the editing window.  -FM
10420 	 */
10421 	MaximumSize -= (1 +
10422 			(int) text->style->leftIndent +
10423 			(int) text->style->rightIndent);
10424 
10425 	/*  If we are numbering form links, place is taken by [nn]  */
10426 	if (fields_are_numbered()) {
10427 	    if (!number_fields_on_left
10428 		&& f->type == F_TEXT_TYPE
10429 		&& MaximumSize > a->line_pos + 10)
10430 		MaximumSize -= a->line_pos;
10431 	    else
10432 		MaximumSize -= (int) strlen(marker);
10433 	}
10434 
10435 	/*
10436 	 * Save value for submit/reset buttons so they
10437 	 * will be visible when printing the page.  - LE
10438 	 */
10439 	if (f->type == F_SUBMIT_TYPE)
10440 	    FREE(I->value);
10441 	I->value = f->value;
10442 	break;
10443 
10444     default:
10445 	/*
10446 	 * For all other fields we limit the size element to
10447 	 * 10 less than the screen width, because either they
10448 	 * are types with small placeholders, and/or are a
10449 	 * type which is handled via a popup window.  -FM
10450 	 */
10451 	MaximumSize -= 10;
10452 	break;
10453     }
10454 
10455     if (MaximumSize < 1)
10456 	MaximumSize = 1;
10457 
10458     if (f->size > MaximumSize)
10459 	f->size = MaximumSize;
10460 
10461     /*
10462      * Add this anchor to the anchor list
10463      */
10464     text->last_anchor = a;
10465 
10466     if (HTCurrentForm) {	/* should always apply! - kw */
10467 	if (!HTCurrentForm->first_field) {
10468 	    HTCurrentForm->first_field = f;
10469 	}
10470 	HTCurrentForm->last_field = f;
10471 	HTCurrentForm->nfields++;	/* will count hidden fields - kw */
10472 	/*
10473 	 * Set the no_cache flag if the METHOD is POST.  -FM
10474 	 */
10475 	if (HTCurrentForm->data.submit_method == URL_POST_METHOD)
10476 	    f->no_cache = TRUE;
10477 	/*
10478 	 * Propagate form field's accept-charset attribute to enclosing
10479 	 * form if the form itself didn't have an accept-charset - kw
10480 	 */
10481 	if (f->accept_cs && !HTCurrentForm->accept_cs) {
10482 	    StrAllocCopy(HTCurrentForm->accept_cs, f->accept_cs);
10483 	}
10484 	if (!text->forms) {
10485 	    text->forms = HTList_new();
10486 	}
10487     } else {
10488 	CTRACE((tfp, "beginInput: HTCurrentForm is missing!\n"));
10489     }
10490 
10491     CTRACE((tfp, "Input link: name=%s\nvalue=%s\nsize=%d\n",
10492 	    f->name,
10493 	    NonNull(f->value),
10494 	    f->size));
10495     CTRACE((tfp, "Input link: name_cs=%d \"%s\" (from %d \"%s\")\n",
10496 	    f->name_cs,
10497 	    (f->name_cs >= 0 ?
10498 	     LYCharSet_UC[f->name_cs].MIMEname : "<UNKNOWN>"),
10499 	    I->name_cs,
10500 	    (I->name_cs >= 0 ?
10501 	     LYCharSet_UC[I->name_cs].MIMEname : "<UNKNOWN>")));
10502     CTRACE((tfp, "            value_cs=%d \"%s\" (from %d \"%s\")\n",
10503 	    f->value_cs,
10504 	    (f->value_cs >= 0 ?
10505 	     LYCharSet_UC[f->value_cs].MIMEname : "<UNKNOWN>"),
10506 	    I->value_cs,
10507 	    (I->value_cs >= 0 ?
10508 	     LYCharSet_UC[I->value_cs].MIMEname : "<UNKNOWN>")));
10509 
10510     /*
10511      * Return the SIZE of the input field.
10512      */
10513     if (I->size && f->size > adjust_marker) {
10514 	f->size -= adjust_marker;
10515     }
10516     return (f->size);
10517 }
10518 
10519 /*
10520  * If we're numbering fields on the right, do it.  Note that some fields may
10521  * be too long for the line - we'll lose the marker in that case rather than
10522  * truncate the field.
10523  */
HText_endInput(HText * text)10524 void HText_endInput(HText *text)
10525 {
10526     if (fields_are_numbered()
10527 	&& !number_fields_on_left
10528 	&& text != NULL
10529 	&& text->last_anchor != NULL
10530 	&& text->last_anchor->number > 0) {
10531 	char marker[20];
10532 
10533 	sprintf(marker, "[%d]", text->last_anchor->show_number);
10534 	HText_appendText(text, marker);
10535     }
10536 }
10537 
10538 /*
10539  * Get a translation (properly:  transcoding) quality, factoring in
10540  * our ability to translate (an UCTQ_t) and a possible q parameter
10541  * on the given charset string, for cs_from -> givenmime.
10542  * The parsed input string will be mutilated on exit(!).
10543  * Note that results are not normalised to 1.0, but results from
10544  * different calls of this function can be compared.  - kw
10545  *
10546  * Obsolete, it was planned to use here a quality parametr UCTQ_t,
10547  * which is boolean now.
10548  */
get_trans_q(int cs_from,char * givenmime)10549 static double get_trans_q(int cs_from,
10550 			  char *givenmime)
10551 {
10552     double df = 1.0;
10553     BOOL tq;
10554     char *p;
10555 
10556     if (!givenmime || !(*givenmime))
10557 	return 0.0;
10558     if ((p = StrChr(givenmime, ';')) != NULL) {
10559 	*p++ = '\0';
10560     }
10561     if (!strcmp(givenmime, "*"))
10562 	tq = UCCanTranslateFromTo(cs_from,
10563 				  UCGetLYhndl_byMIME("utf-8"));
10564     else
10565 	tq = UCCanTranslateFromTo(cs_from,
10566 				  UCGetLYhndl_byMIME(givenmime));
10567     if (!tq)
10568 	return 0.0;
10569     if (non_empty(p)) {
10570 	char *pair, *field = p, *pval, *ptok;
10571 
10572 	/* Get all the parameters to the Charset */
10573 	while ((pair = HTNextTok(&field, ";", "\"", NULL)) != NULL) {
10574 	    if ((ptok = HTNextTok(&pair, "= ", NULL, NULL)) != NULL &&
10575 		(pval = HTNextField(&pair)) != NULL) {
10576 		if (0 == strcasecomp(ptok, "q")) {
10577 		    df = strtod(pval, NULL);
10578 		    break;
10579 		}
10580 	    }
10581 	}
10582 	return (df * tq);
10583     } else {
10584 	return tq;
10585     }
10586 }
10587 
10588 /*
10589  * Find the best charset for submission, if we have an ACCEPT_CHARSET
10590  * list.  It factors in how well we can translate (just as guess, and
10591  * not a very good one..) and possible ";q=" factors.  Yes this is
10592  * more general than it needs to be here.
10593  *
10594  * Input is cs_in and acceptstring.
10595  *
10596  * Will return charset handle as int.
10597  * best_csname will point to a newly allocated MIME string for the
10598  * charset corresponding to the return value if return value >= 0.
10599  * - kw
10600  */
find_best_target_cs(char ** best_csname,int cs_from,const char * acceptstring)10601 static int find_best_target_cs(char **best_csname,
10602 			       int cs_from,
10603 			       const char *acceptstring)
10604 {
10605     char *paccept = NULL;
10606     double bestq = -1.0;
10607     char *bestmime = NULL;
10608     char *field, *nextfield;
10609 
10610     StrAllocCopy(paccept, acceptstring);
10611     nextfield = paccept;
10612     while ((field = HTNextTok(&nextfield, ",", "\"", NULL)) != NULL) {
10613 	double q;
10614 
10615 	if (*field != '\0') {
10616 	    /* Get the Charset */
10617 	    q = get_trans_q(cs_from, field);
10618 	    if (q > bestq) {
10619 		bestq = q;
10620 		bestmime = field;
10621 	    }
10622 	}
10623     }
10624     if (bestmime) {
10625 	if (!strcmp(bestmime, "*"))	/* non-standard for HTML attribute.. */
10626 	    StrAllocCopy(*best_csname, "utf-8");
10627 	else
10628 	    StrAllocCopy(*best_csname, bestmime);
10629 	FREE(paccept);
10630 	if (bestq > 0)
10631 	    return (UCGetLYhndl_byMIME(*best_csname));
10632 	else
10633 	    return (-1);
10634     }
10635     FREE(paccept);
10636     return (-1);
10637 }
10638 
10639 #ifdef USE_FILE_UPLOAD
load_a_file(const char * val_used,bstring ** result)10640 static void load_a_file(const char *val_used,
10641 			bstring **result)
10642 {
10643     FILE *fd;
10644     size_t bytes;
10645     char bfr[BUFSIZ + 1];
10646 
10647     CTRACE((tfp, "Ok, about to convert \"%s\" to mime/thingy\n", val_used));
10648 
10649     if (*val_used) {		/* ignore empty form field */
10650 	if ((fd = fopen(val_used, BIN_R)) == 0) {
10651 	    HTAlert(gettext("Can't open file for uploading"));
10652 	} else {
10653 	    while ((bytes = fread(bfr, sizeof(char), sizeof(bfr) - 1, fd)) != 0) {
10654 		HTSABCat(result, bfr, (int) bytes);
10655 	    }
10656 	    LYCloseInput(fd);
10657 	}
10658     }
10659 }
10660 
guess_content_type(const char * filename)10661 static const char *guess_content_type(const char *filename)
10662 {
10663     HTAtom *encoding;
10664     const char *desc;
10665     HTFormat format = HTFileFormat(filename, &encoding, &desc);
10666 
10667     return (format != 0 && non_empty(format->name))
10668 	? format->name
10669 	: STR_PLAINTEXT;
10670 }
10671 #endif /* USE_FILE_UPLOAD */
10672 
cannot_transcode(BOOL * had_warning,const char * target_csname)10673 static void cannot_transcode(BOOL *had_warning,
10674 			     const char *target_csname)
10675 {
10676     if (*had_warning == NO) {
10677 	*had_warning = YES;
10678 	_user_message(CANNOT_TRANSCODE_FORM,
10679 		      target_csname ? target_csname : "UNKNOWN");
10680 	LYSleepAlert();
10681     }
10682 }
10683 
10684 #define SPECIAL_8BIT 1
10685 #define SPECIAL_FORM 2
10686 
check_form_specialchars(const char * value)10687 static unsigned check_form_specialchars(const char *value)
10688 {
10689     unsigned result = 0;
10690     const char *p;
10691 
10692     for (p = value;
10693 	 non_empty(p) && (result != (SPECIAL_8BIT | SPECIAL_FORM));
10694 	 p++) {
10695 	if ((*p == HT_NON_BREAK_SPACE) ||
10696 	    (*p == HT_EN_SPACE) ||
10697 	    (*p == LY_SOFT_HYPHEN)) {
10698 	    result |= SPECIAL_FORM;
10699 	} else if ((*p & 0x80) != 0) {
10700 	    result |= SPECIAL_8BIT;
10701 	}
10702     }
10703     return result;
10704 }
10705 
10706 /*
10707  * Scan the given data, adding characters to the MIME-boundary to keep it from
10708  * matching any part of the data.
10709  */
UpdateBoundary(char ** Boundary,bstring * data)10710 static void UpdateBoundary(char **Boundary,
10711 			   bstring *data)
10712 {
10713     size_t j;
10714     size_t have = strlen(*Boundary);
10715     size_t last = (size_t) BStrLen(data);
10716     char *text = BStrData(data);
10717     char *want = *Boundary;
10718 
10719     for (j = 0; (long) j <= (long) (last - have); ++j) {
10720 	if (want[0] == text[j]
10721 	    && !memcmp(want, text + j, have)) {
10722 	    char temp[2];
10723 
10724 	    temp[0] = (char) (isdigit(UCH(text[have + j])) ? 'a' : '0');
10725 	    temp[1] = '\0';
10726 	    StrAllocCat(want, temp);
10727 	    ++have;
10728 	}
10729     }
10730     *Boundary = want;
10731 }
10732 
10733 /*
10734  * Convert a string to base64
10735  */
convert_to_base64(const char * src,size_t len)10736 static char *convert_to_base64(const char *src,
10737 			       size_t len)
10738 {
10739 #define B64_LINE       76
10740 
10741     static const char basis_64[] =
10742     "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/";
10743 
10744     char *dest;
10745     size_t rlen;		/* length of result string */
10746     unsigned char c1, c2, c3;
10747     const char *eol;
10748     char *r;
10749     const char *str;
10750     size_t eollen;
10751     int chunk;
10752 
10753     str = src;
10754     eol = "\n";
10755     eollen = 1;
10756 
10757     /* calculate the length of the result */
10758     rlen = (len + 2) / 3 * 4;	/* encoded bytes */
10759     if (rlen) {
10760 	/* add space for EOL */
10761 	rlen += ((rlen - 1) / B64_LINE + 1) * eollen;
10762     }
10763 
10764     /* allocate a result buffer */
10765     if ((dest = (char *) malloc(rlen + 1)) == NULL) {
10766 	outofmem(__FILE__, "convert_to_base64");
10767     }
10768     r = dest;
10769 
10770     /* encode */
10771     for (chunk = 0; len > 0; len -= 3, chunk++) {
10772 	if (chunk == (B64_LINE / 4)) {
10773 	    const char *c = eol;
10774 	    const char *e = eol + eollen;
10775 
10776 	    while (c < e)
10777 		*r++ = *c++;
10778 	    chunk = 0;
10779 	}
10780 	c1 = UCH(*str++);
10781 	c2 = UCH(*str++);
10782 	*r++ = basis_64[c1 >> 2];
10783 	*r++ = basis_64[((c1 & 0x3) << 4) | ((c2 & 0xF0) >> 4)];
10784 	if (len > 2) {
10785 	    c3 = UCH(*str++);
10786 	    *r++ = basis_64[((c2 & 0xF) << 2) | ((c3 & 0xC0) >> 6)];
10787 	    *r++ = basis_64[c3 & 0x3F];
10788 	} else if (len == 2) {
10789 	    *r++ = basis_64[(c2 & 0xF) << 2];
10790 	    *r++ = '=';
10791 	} else {		/* len == 1 */
10792 	    *r++ = '=';
10793 	    *r++ = '=';
10794 	}
10795     }
10796     if (rlen) {
10797 	/* append eol to the result string */
10798 	const char *c = eol;
10799 	const char *e = eol + eollen;
10800 
10801 	while (c < e)
10802 	    *r++ = *c++;
10803     }
10804     *r = '\0';
10805 
10806     return dest;
10807 }
10808 
10809 typedef enum {
10810     NO_QUOTE			/* no quoting needed */
10811     ,QUOTE_MULTI		/* multipart */
10812     ,QUOTE_BASE64		/* encode as base64 */
10813     ,QUOTE_SPECIAL		/* escape special characters only */
10814 } QuoteData;
10815 
10816 typedef struct {
10817     int type;			/* the type of this field */
10818     BOOL first;			/* true if this begins a submission part */
10819     char *name;			/* the name of this field */
10820     char *value;		/* the nominal value of this field */
10821     bstring *data;		/* its data, which is usually the same as the value */
10822     QuoteData quote;		/* how to quote/translate the data */
10823 } PostData;
10824 
escape_or_quote_name(const char * name,QuoteData quoting,const char * MultipartContentType)10825 static char *escape_or_quote_name(const char *name,
10826 				  QuoteData quoting,
10827 				  const char *MultipartContentType)
10828 {
10829     char *escaped1 = NULL;
10830 
10831     switch (quoting) {
10832     case NO_QUOTE:
10833 	StrAllocCopy(escaped1, name);
10834 	break;
10835     case QUOTE_MULTI:
10836     case QUOTE_BASE64:
10837 	StrAllocCopy(escaped1, "Content-Disposition: form-data");
10838 	HTSprintf(&escaped1, "; name=\"%s\"", name);
10839 	if (MultipartContentType)
10840 	    HTSprintf(&escaped1, MultipartContentType, STR_PLAINTEXT);
10841 	if (quoting == QUOTE_BASE64)
10842 	    StrAllocCat(escaped1, "\r\nContent-Transfer-Encoding: base64");
10843 	StrAllocCat(escaped1, "\r\n\r\n");
10844 	break;
10845     case QUOTE_SPECIAL:
10846 	escaped1 = HTEscapeSP(name, URL_XALPHAS);
10847 	break;
10848     }
10849     return escaped1;
10850 }
10851 
escape_or_quote_value(const char * value,QuoteData quoting)10852 static char *escape_or_quote_value(const char *value,
10853 				   QuoteData quoting)
10854 {
10855     char *escaped2 = NULL;
10856 
10857     switch (quoting) {
10858     case NO_QUOTE:
10859     case QUOTE_MULTI:
10860 	StrAllocCopy(escaped2, NonNull(value));
10861 	break;
10862     case QUOTE_BASE64:
10863 	/* FIXME: this is redundant */
10864 	escaped2 = convert_to_base64(value, strlen(value));
10865 	break;
10866     case QUOTE_SPECIAL:
10867 	escaped2 = HTEscapeSP(value, URL_XALPHAS);
10868 	break;
10869     }
10870     return escaped2;
10871 }
10872 
10873 /*
10874  * Check if we should encode the data in base64.  We can, only if we're using
10875  * a multipart content type.  We should, if we're sending mail and the data
10876  * contains long lines or nonprinting characters.
10877  */
check_if_base64_needed(int submit_method,bstring * data)10878 static int check_if_base64_needed(int submit_method,
10879 				  bstring *data)
10880 {
10881     int width = 0;
10882     BOOL printable = TRUE;
10883     char *text = BStrData(data);
10884 
10885     if (text != 0) {
10886 	int col = 0;
10887 	int n;
10888 	int length = BStrLen(data);
10889 
10890 	for (n = 0; n < length; ++n) {
10891 	    int ch = UCH(text[n]);
10892 
10893 	    if (is8bits(ch) || ((ch < 32 && ch != '\n'))) {
10894 		CTRACE((tfp, "nonprintable %d:%#x\n", n, ch));
10895 		printable = FALSE;
10896 	    }
10897 	    if (ch == '\n' || ch == '\r') {
10898 		if (width < col)
10899 		    width = col;
10900 		col = 0;
10901 	    } else {
10902 		++col;
10903 	    }
10904 	}
10905 	if (width < col)
10906 	    width = col;
10907     }
10908     return !printable && ((submit_method == URL_MAIL_METHOD) && (width > 72));
10909 }
10910 
HText_PerFormInfo(int number)10911 PerFormInfo *HText_PerFormInfo(int number)
10912 {
10913     return (PerFormInfo *) HTList_objectAt(HTMainText->forms, number - 1);
10914 }
10915 
10916 /*
10917  * HText_SubmitForm - generate submit data from form fields.
10918  * For mailto forms, send the data.
10919  * For other methods, set fields in structure pointed to by doc
10920  * appropriately for next request.
10921  * Returns 1 if *doc set appropriately for next request,
10922  * 0 otherwise.  - kw
10923  */
HText_SubmitForm(FormInfo * submit_item,DocInfo * doc,const char * link_name,const char * link_value)10924 int HText_SubmitForm(FormInfo * submit_item, DocInfo *doc,
10925 		     const char *link_name,
10926 		     const char *link_value)
10927 {
10928     BOOL had_chartrans_warning = NO;
10929     BOOL have_accept_cs = NO;
10930     BOOL success;
10931     BOOLEAN PlainText = FALSE;
10932     BOOLEAN SemiColon = FALSE;
10933     BOOL skip_field = FALSE;
10934     const char *out_csname;
10935     const char *target_csname = NULL;
10936     PerFormInfo *thisform;
10937     PostData *my_data = NULL;
10938     TextAnchor *anchor_ptr;
10939     bstring *my_query = NULL;
10940     char *Boundary = NULL;
10941     char *MultipartContentType = NULL;
10942     char *content_type_out = NULL;
10943     char *copied_name_used = NULL;
10944     char *copied_val_used = NULL;
10945     char *escaped1 = NULL;
10946     char *escaped2 = NULL;
10947     char *last_textarea_name = NULL;
10948     const char *name_used = "";
10949     char *previous_blanks = NULL;
10950     const char *val_used = "";
10951     int anchor_count = 0;
10952     int anchor_limit = 0;
10953     int form_number = submit_item->number;
10954     int result = 0;
10955     int target_cs = -1;
10956     int textarea_lineno = 0;
10957     unsigned form_is_special = 0;
10958 
10959     CTRACE((tfp, "SubmitForm\n  link_name=%s\n  link_value=%s\n", link_name, link_value));
10960     if (!HTMainText)
10961 	return 0;
10962 
10963     thisform = HText_PerFormInfo(form_number);
10964     /*  Sanity check */
10965     if (!thisform) {
10966 	CTRACE((tfp, "SubmitForm: form %d not in HTMainText's list!\n",
10967 		form_number));
10968     } else if (thisform->number != form_number) {
10969 	CTRACE((tfp, "SubmitForm: failed sanity check, %d!=%d !\n",
10970 		thisform->number, form_number));
10971 	thisform = NULL;
10972     }
10973 
10974     if (isEmpty(submit_item->submit_action)) {
10975 	CTRACE((tfp, "SubmitForm: no action given\n"));
10976 	return 0;
10977     }
10978 
10979     /*
10980      * If we're mailing, make sure it's a mailto ACTION.  -FM
10981      */
10982     if ((submit_item->submit_method == URL_MAIL_METHOD) &&
10983 	!isMAILTO_URL(submit_item->submit_action)) {
10984 	HTAlert(BAD_FORM_MAILTO);
10985 	return 0;
10986     }
10987 
10988     /*
10989      * Check the ENCTYPE and set up the appropriate variables.  -FM
10990      */
10991     if (submit_item->submit_enctype &&
10992 	!strncasecomp(submit_item->submit_enctype, STR_PLAINTEXT, 10)) {
10993 	/*
10994 	 * Do not hex escape, and use physical newlines
10995 	 * to separate name=value pairs.  -FM
10996 	 */
10997 	PlainText = TRUE;
10998     } else if (submit_item->submit_enctype &&
10999 	       !strncasecomp(submit_item->submit_enctype,
11000 			     "application/sgml-form-urlencoded", 32)) {
11001 	/*
11002 	 * Use semicolons instead of ampersands as the
11003 	 * separators for name=value pairs.  -FM
11004 	 */
11005 	SemiColon = TRUE;
11006     } else if (submit_item->submit_enctype &&
11007 	       !strncasecomp(submit_item->submit_enctype,
11008 			     "multipart/form-data", 19)) {
11009 	/*
11010 	 * Use the multipart MIME format.  Later we will ensure it does not
11011 	 * occur within the content.
11012 	 */
11013 	StrAllocCopy(Boundary, "xnyLAaB03X");
11014     }
11015 
11016     /*
11017      * Determine in what character encoding (aka.  charset) we should
11018      * submit.  We call this target_cs and the MIME name for it
11019      * target_csname.
11020      * TODO:   - actually use ACCEPT-CHARSET stuff from FORM
11021      * TODO:   - deal with list in ACCEPT-CHARSET, find a "best"
11022      *           charset to submit
11023      */
11024 
11025     /* Look at ACCEPT-CHARSET on the submitting field if we have one. */
11026     if (thisform && submit_item->accept_cs &&
11027 	strcasecomp(submit_item->accept_cs, "UNKNOWN")) {
11028 	have_accept_cs = YES;
11029 	target_cs = find_best_target_cs(&thisform->thisacceptcs,
11030 					current_char_set,
11031 					submit_item->accept_cs);
11032     }
11033     /* Look at ACCEPT-CHARSET on form as a whole if submitting field
11034      * didn't have one. */
11035     if (thisform && !have_accept_cs && thisform->accept_cs &&
11036 	strcasecomp(thisform->accept_cs, "UNKNOWN")) {
11037 	have_accept_cs = YES;
11038 	target_cs = find_best_target_cs(&thisform->thisacceptcs,
11039 					current_char_set,
11040 					thisform->accept_cs);
11041     }
11042     if (have_accept_cs && (target_cs >= 0) && thisform->thisacceptcs) {
11043 	target_csname = thisform->thisacceptcs;
11044     }
11045 
11046     if (target_cs < 0 &&
11047 	non_empty(HTMainText->node_anchor->charset)) {
11048 	target_cs = UCGetLYhndl_byMIME(HTMainText->node_anchor->charset);
11049 	if (target_cs >= 0) {
11050 	    target_csname = HTMainText->node_anchor->charset;
11051 	} else {
11052 	    target_cs = UCLYhndl_for_unspec;	/* always >= 0 */
11053 	    target_csname = LYCharSet_UC[target_cs].MIMEname;
11054 	}
11055     }
11056     if (target_cs < 0) {
11057 	target_cs = UCLYhndl_for_unspec;	/* always >= 0 */
11058     }
11059 
11060     /*
11061      * Go through list of anchors and get a "max." charset parameter - kw
11062      */
11063     for (anchor_ptr = HTMainText->first_anchor;
11064 	 anchor_ptr != NULL;
11065 	 anchor_ptr = anchor_ptr->next) {
11066 
11067 	if (anchor_ptr->link_type != INPUT_ANCHOR)
11068 	    continue;
11069 
11070 	if (anchor_ptr->input_field->number == form_number &&
11071 	    !anchor_ptr->input_field->disabled) {
11072 
11073 	    FormInfo *form_ptr = anchor_ptr->input_field;
11074 	    char *val = (form_ptr->cp_submit_value != NULL
11075 			 ? form_ptr->cp_submit_value
11076 			 : form_ptr->value);
11077 
11078 	    unsigned field_is_special = check_form_specialchars(val);
11079 	    unsigned name_is_special = check_form_specialchars(form_ptr->name);
11080 
11081 	    form_is_special = (field_is_special | name_is_special);
11082 
11083 	    if (field_is_special == 0) {
11084 		/* already ok */
11085 	    } else if (target_cs < 0) {
11086 		/* already confused */
11087 	    } else if ((field_is_special & SPECIAL_8BIT) == 0
11088 		       && (LYCharSet_UC[target_cs].enc == UCT_ENC_8859
11089 			   || (LYCharSet_UC[target_cs].like8859 & UCT_R_8859SPECL))) {
11090 		/* those specials will be trivial */
11091 	    } else if (UCNeedNotTranslate(form_ptr->value_cs, target_cs)) {
11092 		/* already ok */
11093 	    } else if (UCCanTranslateFromTo(form_ptr->value_cs, target_cs)) {
11094 		/* also ok */
11095 	    } else if (UCCanTranslateFromTo(target_cs, form_ptr->value_cs)) {
11096 		target_cs = form_ptr->value_cs;		/* try this */
11097 		target_csname = NULL;	/* will be set after loop */
11098 	    } else {
11099 		target_cs = -1;	/* don't know what to do */
11100 	    }
11101 
11102 	    /*  Same for name */
11103 	    if (name_is_special == 0) {
11104 		/* already ok */
11105 	    } else if (target_cs < 0) {
11106 		/* already confused */
11107 	    } else if ((name_is_special & SPECIAL_8BIT) == 0
11108 		       && (LYCharSet_UC[target_cs].enc == UCT_ENC_8859
11109 			   || (LYCharSet_UC[target_cs].like8859 & UCT_R_8859SPECL))) {
11110 		/* those specials will be trivial */
11111 	    } else if (UCNeedNotTranslate(form_ptr->name_cs, target_cs)) {
11112 		/* already ok */
11113 	    } else if (UCCanTranslateFromTo(form_ptr->name_cs, target_cs)) {
11114 		/* also ok */
11115 	    } else if (UCCanTranslateFromTo(target_cs, form_ptr->name_cs)) {
11116 		target_cs = form_ptr->value_cs;		/* try this */
11117 		target_csname = NULL;	/* will be set after loop */
11118 	    } else {
11119 		target_cs = -1;	/* don't know what to do */
11120 	    }
11121 
11122 	    ++anchor_limit;
11123 	} else if (anchor_ptr->input_field->number > form_number) {
11124 	    break;
11125 	}
11126     }
11127 
11128     /*
11129      * If we have input fields (we expect this), make an array of them so we
11130      * can organize the data.
11131      */
11132     if (anchor_limit != 0) {
11133 	my_data = typecallocn(PostData, (size_t) anchor_limit);
11134 	if (my_data == 0)
11135 	    outofmem(__FILE__, "HText_SubmitForm");
11136     }
11137 
11138     if (target_csname == NULL) {
11139 	if (target_cs >= 0) {
11140 	    if ((form_is_special & SPECIAL_8BIT) != 0) {
11141 		target_csname = LYCharSet_UC[target_cs].MIMEname;
11142 	    } else if ((form_is_special & SPECIAL_FORM) != 0) {
11143 		target_csname = LYCharSet_UC[target_cs].MIMEname;
11144 	    } else {
11145 		target_csname = "us-ascii";
11146 	    }
11147 	} else {
11148 	    target_csname = "us-ascii";
11149 	    target_cs = UCLYhndl_for_unspec;	/* always >= 0 */
11150 	}
11151     } else if (target_cs < 0) {
11152 	target_cs = UCLYhndl_for_unspec;	/* always >= 0 */
11153     }
11154 
11155     if (submit_item->submit_method == URL_GET_METHOD && Boundary == NULL) {
11156 	char *temp = NULL;
11157 
11158 	StrAllocCopy(temp, submit_item->submit_action);
11159 	/*
11160 	 * Method is GET.  Clip out any anchor in the current URL.
11161 	 */
11162 	strtok(temp, "#");
11163 	/*
11164 	 * Clip out any old query in the current URL.
11165 	 */
11166 	strtok(temp, "?");
11167 	/*
11168 	 * Add the lead question mark for the new URL.
11169 	 */
11170 	StrAllocCat(temp, "?");
11171 	BStrCat0(my_query, temp);
11172 	free(temp);
11173     } else {
11174 	/*
11175 	 * We are submitting POST content to a server,
11176 	 * so load content_type_out.  This will be put in
11177 	 * the post_content_type element if all goes well.  -FM, kw
11178 	 */
11179 	if (SemiColon == TRUE) {
11180 	    StrAllocCopy(content_type_out,
11181 			 "application/sgml-form-urlencoded");
11182 	} else if (PlainText == TRUE) {
11183 	    StrAllocCopy(content_type_out,
11184 			 STR_PLAINTEXT);
11185 	} else if (Boundary != NULL) {
11186 	    StrAllocCopy(content_type_out,
11187 			 "multipart/form-data");
11188 	} else {
11189 	    StrAllocCopy(content_type_out,
11190 			 "application/x-www-form-urlencoded");
11191 	}
11192 
11193 	/*
11194 	 * If the ENCTYPE is not multipart/form-data, append the
11195 	 * charset we'll be sending to the post_content_type, IF
11196 	 *  (1) there was an explicit accept-charset attribute, OR
11197 	 *  (2) we have 8-bit or special chars, AND the document had
11198 	 *      an explicit (recognized and accepted) charset parameter,
11199 	 *      AND it or target_csname is different from iso-8859-1,
11200 	 *      OR
11201 	 *  (3) we have 8-bit or special chars, AND the document had
11202 	 *      no explicit (recognized and accepted) charset parameter,
11203 	 *      AND target_cs is different from the currently effective
11204 	 *      assumed charset (which should have been set by the user
11205 	 *      so that it reflects what the server is sending, if the
11206 	 *      document is rendered correctly).
11207 	 * For multipart/form-data the equivalent will be done later,
11208 	 * separately for each form field.  - kw
11209 	 */
11210 	if (have_accept_cs
11211 	    || ((form_is_special & SPECIAL_8BIT) != 0
11212 		|| (form_is_special & SPECIAL_FORM) != 0)) {
11213 	    if (target_cs >= 0 && target_csname) {
11214 		if (Boundary == NULL) {
11215 		    if ((HTMainText->node_anchor->charset &&
11216 			 (strcmp(HTMainText->node_anchor->charset,
11217 				 "iso-8859-1") ||
11218 			  strcmp(target_csname, "iso-8859-1"))) ||
11219 			(!HTMainText->node_anchor->charset &&
11220 			 target_cs != UCLYhndl_for_unspec)) {
11221 			HTSprintf(&content_type_out, "; charset=%s", target_csname);
11222 		    }
11223 		}
11224 	    } else {
11225 		cannot_transcode(&had_chartrans_warning, target_csname);
11226 	    }
11227 	}
11228     }
11229 
11230     out_csname = target_csname;
11231 
11232     /*
11233      * Build up a list of the input fields and their associated values.
11234      */
11235     for (anchor_ptr = HTMainText->first_anchor;
11236 	 anchor_ptr != NULL;
11237 	 anchor_ptr = anchor_ptr->next) {
11238 
11239 	if (anchor_ptr->link_type != INPUT_ANCHOR)
11240 	    continue;
11241 
11242 	if (anchor_ptr->input_field->number == form_number &&
11243 	    !anchor_ptr->input_field->disabled) {
11244 
11245 	    FormInfo *form_ptr = anchor_ptr->input_field;
11246 	    int out_cs;
11247 	    QuoteData quoting = (PlainText
11248 				 ? NO_QUOTE
11249 				 : (Boundary
11250 				    ? QUOTE_MULTI
11251 				    : QUOTE_SPECIAL));
11252 
11253 	    assert(my_data != NULL);
11254 
11255 	    if (form_ptr->type != F_TEXTAREA_TYPE)
11256 		textarea_lineno = 0;
11257 
11258 	    CTRACE((tfp, "SubmitForm[%d/%d]: ",
11259 		    anchor_count + 1, anchor_limit));
11260 
11261 	    name_used = NonNull(form_ptr->name);
11262 
11263 	    switch (form_ptr->type) {
11264 	    case F_RESET_TYPE:
11265 		CTRACE((tfp, "reset\n"));
11266 		break;
11267 #ifdef USE_FILE_UPLOAD
11268 	    case F_FILE_TYPE:
11269 		val_used = NonNull(form_ptr->value);
11270 		CTRACE((tfp, "I will submit \"%s\" (from %s)\n",
11271 			val_used, name_used));
11272 		break;
11273 #endif
11274 	    case F_SUBMIT_TYPE:
11275 	    case F_TEXT_SUBMIT_TYPE:
11276 	    case F_IMAGE_SUBMIT_TYPE:
11277 		if (!(non_empty(form_ptr->name) &&
11278 		      !strcmp(form_ptr->name, link_name))) {
11279 		    CTRACE((tfp, "skipping submit field with "));
11280 		    CTRACE((tfp, "name \"%s\" for link_name \"%s\", %s.\n",
11281 			    form_ptr->name ? form_ptr->name : "???",
11282 			    link_name ? link_name : "???",
11283 			    non_empty(form_ptr->name) ?
11284 			    "not current link" : "no field name"));
11285 		    break;
11286 		}
11287 		if (!(form_ptr->type == F_TEXT_SUBMIT_TYPE ||
11288 		      (non_empty(form_ptr->value) &&
11289 		       !strcmp(form_ptr->value, link_value)))) {
11290 		    CTRACE((tfp, "skipping submit field with "));
11291 		    CTRACE((tfp, "name \"%s\" for link_name \"%s\", %s!\n",
11292 			    form_ptr->name ? form_ptr->name : "???",
11293 			    link_name ? link_name : "???",
11294 			    "values are different"));
11295 		    break;
11296 		}
11297 		/* FALLTHRU */
11298 	    case F_RADIO_TYPE:
11299 	    case F_CHECKBOX_TYPE:
11300 	    case F_TEXTAREA_TYPE:
11301 	    case F_PASSWORD_TYPE:
11302 	    case F_TEXT_TYPE:
11303 	    case F_OPTION_LIST_TYPE:
11304 	    case F_HIDDEN_TYPE:
11305 		/*
11306 		 * Be sure to actually look at the option submit value.
11307 		 */
11308 		if (form_ptr->cp_submit_value != NULL) {
11309 		    val_used = form_ptr->cp_submit_value;
11310 		} else {
11311 		    val_used = form_ptr->value;
11312 		}
11313 
11314 		/*
11315 		 * Charset-translate value now, because we need to know the
11316 		 * charset parameter for multipart bodyparts.  - kw
11317 		 */
11318 		if (check_form_specialchars(val_used) != 0) {
11319 		    /*  We should translate back. */
11320 		    StrAllocCopy(copied_val_used, val_used);
11321 		    success = FALSE;
11322 		    if (HTCJK == JAPANESE) {
11323 			if ((0 <= target_cs) &&
11324 			    !strcmp(LYCharSet_UC[target_cs].MIMEname, "euc-jp")) {
11325 			    TO_EUC((const unsigned char *) val_used,
11326 				   (unsigned char *) copied_val_used);
11327 			    success = YES;
11328 			} else if ((0 <= target_cs) &&
11329 				   !strcmp(LYCharSet_UC[target_cs].MIMEname,
11330 					   "shift_jis")) {
11331 			    TO_SJIS((const unsigned char *) val_used,
11332 				    (unsigned char *) copied_val_used);
11333 			    success = YES;
11334 			}
11335 		    }
11336 		    if (!success) {
11337 			success = LYUCTranslateBackFormData(&copied_val_used,
11338 							    form_ptr->value_cs,
11339 							    target_cs, PlainText);
11340 		    }
11341 		    CTRACE((tfp, "field \"%s\" %d %s -> %d %s %s\n",
11342 			    NonNull(form_ptr->name),
11343 			    form_ptr->value_cs,
11344 			    ((form_ptr->value_cs >= 0)
11345 			     ? LYCharSet_UC[form_ptr->value_cs].MIMEname
11346 			     : "???"),
11347 			    target_cs,
11348 			    target_csname ? target_csname : "???",
11349 			    success ? "OK" : "FAILED"));
11350 		    if (success) {
11351 			val_used = copied_val_used;
11352 		    }
11353 		} else {	/* We can use the value directly. */
11354 		    CTRACE((tfp, "field \"%s\" %d %s OK\n",
11355 			    NonNull(form_ptr->name),
11356 			    target_cs,
11357 			    target_csname ? target_csname : "???"));
11358 		    success = YES;
11359 		}
11360 		if (!success) {
11361 		    cannot_transcode(&had_chartrans_warning, target_csname);
11362 		    out_cs = form_ptr->value_cs;
11363 		} else {
11364 		    out_cs = target_cs;
11365 		}
11366 		if (out_cs >= 0)
11367 		    out_csname = LYCharSet_UC[out_cs].MIMEname;
11368 		if (Boundary) {
11369 		    StrAllocCopy(MultipartContentType,
11370 				 "\r\nContent-Type: %s");
11371 		    if (!success && form_ptr->value_cs < 0) {
11372 			/*  This is weird. */
11373 			out_csname = "UNKNOWN-8BIT";
11374 		    } else if (!success) {
11375 			target_csname = NULL;
11376 		    } else {
11377 			if (!target_csname) {
11378 			    target_csname = LYCharSet_UC[target_cs].MIMEname;
11379 			}
11380 		    }
11381 		    if (strcmp(out_csname, "iso-8859-1"))
11382 			HTSprintf(&MultipartContentType, "; charset=%s", out_csname);
11383 		}
11384 
11385 		/*
11386 		 * Charset-translate name now, because we need to know the
11387 		 * charset parameter for multipart bodyparts.  - kw
11388 		 */
11389 		if (form_ptr->type == F_TEXTAREA_TYPE) {
11390 		    textarea_lineno++;
11391 		    if (textarea_lineno > 1 &&
11392 			last_textarea_name && form_ptr->name &&
11393 			!strcmp(last_textarea_name, form_ptr->name)) {
11394 			break;
11395 		    }
11396 		}
11397 
11398 		if (check_form_specialchars(name_used) != 0) {
11399 		    /*  We should translate back. */
11400 		    StrAllocCopy(copied_name_used, name_used);
11401 		    success = LYUCTranslateBackFormData(&copied_name_used,
11402 							form_ptr->name_cs,
11403 							target_cs, PlainText);
11404 		    CTRACE((tfp, "name \"%s\" %d %s -> %d %s %s\n",
11405 			    NonNull(form_ptr->name),
11406 			    form_ptr->name_cs,
11407 			    ((form_ptr->name_cs >= 0)
11408 			     ? LYCharSet_UC[form_ptr->name_cs].MIMEname
11409 			     : "???"),
11410 			    target_cs,
11411 			    target_csname ? target_csname : "???",
11412 			    success ? "OK" : "FAILED"));
11413 		    if (success) {
11414 			name_used = copied_name_used;
11415 		    }
11416 		    if (Boundary) {
11417 			if (!success) {
11418 			    StrAllocCopy(MultipartContentType, "");
11419 			    target_csname = NULL;
11420 			} else {
11421 			    if (!target_csname)
11422 				target_csname = LYCharSet_UC[target_cs].MIMEname;
11423 			}
11424 		    }
11425 		} else {	/* We can use the name directly. */
11426 		    CTRACE((tfp, "name \"%s\" %d %s OK\n",
11427 			    NonNull(form_ptr->name),
11428 			    target_cs,
11429 			    target_csname ? target_csname : "???"));
11430 		    success = YES;
11431 		    if (Boundary) {
11432 			StrAllocCopy(copied_name_used, name_used);
11433 		    }
11434 		}
11435 		if (!success) {
11436 		    cannot_transcode(&had_chartrans_warning, target_csname);
11437 		}
11438 		if (Boundary) {
11439 		    /*
11440 		     * According to RFC 1867, Non-ASCII field names
11441 		     * "should be encoded according to the prescriptions
11442 		     * of RFC 1522 [...].  I don't think RFC 1522 actually
11443 		     * is meant to apply to parameters like this, and it
11444 		     * is unknown whether any server would make sense of
11445 		     * it, so for now just use some quoting/escaping and
11446 		     * otherwise leave 8-bit values as they are.
11447 		     * Non-ASCII characters in form field names submitted
11448 		     * as multipart/form-data can only occur if the form
11449 		     * provider specifically asked for it anyway.  - kw
11450 		     */
11451 		    HTMake822Word(&copied_name_used, FALSE);
11452 		    name_used = copied_name_used;
11453 		}
11454 
11455 		break;
11456 	    default:
11457 		CTRACE((tfp, "What type is %d?\n", form_ptr->type));
11458 		break;
11459 	    }
11460 
11461 	    skip_field = FALSE;
11462 	    my_data[anchor_count].first = TRUE;
11463 	    my_data[anchor_count].type = form_ptr->type;
11464 
11465 	    /*
11466 	     * Using the values of 'name_used' and 'val_used' computed in the
11467 	     * previous case-statement, compute the 'first' and 'data' values
11468 	     * for the current input field.
11469 	     */
11470 	    switch (form_ptr->type) {
11471 
11472 	    default:
11473 		skip_field = TRUE;
11474 		break;
11475 
11476 #ifdef USE_FILE_UPLOAD
11477 	    case F_FILE_TYPE:
11478 		load_a_file(val_used, &(my_data[anchor_count].data));
11479 		break;
11480 #endif /* USE_FILE_UPLOAD */
11481 
11482 	    case F_SUBMIT_TYPE:
11483 	    case F_TEXT_SUBMIT_TYPE:
11484 	    case F_IMAGE_SUBMIT_TYPE:
11485 		if ((non_empty(form_ptr->name) &&
11486 		     !strcmp(form_ptr->name, link_name)) &&
11487 		    (form_ptr->type == F_TEXT_SUBMIT_TYPE ||
11488 		     (non_empty(form_ptr->value) &&
11489 		      !strcmp(form_ptr->value, link_value)))) {
11490 		    ;
11491 		} else {
11492 		    skip_field = TRUE;
11493 		}
11494 		break;
11495 
11496 	    case F_RADIO_TYPE:
11497 	    case F_CHECKBOX_TYPE:
11498 		/*
11499 		 * Only add if selected.
11500 		 */
11501 		if (form_ptr->num_value) {
11502 		    ;
11503 		} else {
11504 		    skip_field = TRUE;
11505 		}
11506 		break;
11507 
11508 	    case F_TEXTAREA_TYPE:
11509 		if (!last_textarea_name ||
11510 		    strcmp(last_textarea_name, form_ptr->name)) {
11511 		    textarea_lineno = 1;
11512 		    last_textarea_name = form_ptr->name;
11513 		} else {
11514 		    my_data[anchor_count].first = FALSE;
11515 		}
11516 		break;
11517 
11518 	    case F_PASSWORD_TYPE:
11519 	    case F_TEXT_TYPE:
11520 	    case F_OPTION_LIST_TYPE:
11521 	    case F_HIDDEN_TYPE:
11522 		break;
11523 	    }
11524 
11525 	    /*
11526 	     * If we did not decide to skip the current field, populate the
11527 	     * values in the array for it.
11528 	     */
11529 	    if (!skip_field) {
11530 		StrAllocCopy(my_data[anchor_count].name, name_used);
11531 		StrAllocCopy(my_data[anchor_count].value, val_used);
11532 		if (my_data[anchor_count].data == 0)
11533 		    BStrCat0(my_data[anchor_count].data, val_used);
11534 		my_data[anchor_count].quote = quoting;
11535 		if (quoting == QUOTE_MULTI
11536 		    && check_if_base64_needed(submit_item->submit_method,
11537 					      my_data[anchor_count].data)) {
11538 		    CTRACE((tfp, "will encode as base64\n"));
11539 		    my_data[anchor_count].quote = QUOTE_BASE64;
11540 		    escaped2 =
11541 			convert_to_base64(BStrData(my_data[anchor_count].data),
11542 					  (size_t)
11543 					  BStrLen(my_data[anchor_count].data));
11544 		    BStrCopy0(my_data[anchor_count].data, escaped2);
11545 		    FREE(escaped2);
11546 		}
11547 	    }
11548 	    ++anchor_count;
11549 
11550 	    FREE(copied_name_used);
11551 	    FREE(copied_val_used);
11552 
11553 	} else if (anchor_ptr->input_field->number > form_number) {
11554 	    break;
11555 	}
11556     }
11557 
11558     FREE(copied_name_used);
11559 
11560     if (my_data != 0) {
11561 	BOOL first_one = TRUE;
11562 
11563 	/*
11564 	 * If we're using a MIME-boundary, make it unique.
11565 	 */
11566 	if (content_type_out != 0 && Boundary != 0) {
11567 	    Boundary = 0;
11568 	    StrAllocCopy(Boundary, "LYNX");
11569 	    for (anchor_count = 0; anchor_count < anchor_limit; ++anchor_count) {
11570 		if (my_data[anchor_count].data != 0) {
11571 		    UpdateBoundary(&Boundary, my_data[anchor_count].data);
11572 		}
11573 	    }
11574 	    HTSprintf(&content_type_out, "; boundary=%s", Boundary);
11575 	}
11576 
11577 	for (anchor_count = 0; anchor_count < anchor_limit; ++anchor_count) {
11578 
11579 	    if (my_data[anchor_count].name != 0
11580 		&& my_data[anchor_count].value != 0) {
11581 
11582 		CTRACE((tfp,
11583 			"processing [%d:%d] name=%s(first:%d, value=%s, data=%p)\n",
11584 			anchor_count + 1,
11585 			anchor_limit,
11586 			NonNull(my_data[anchor_count].name),
11587 			my_data[anchor_count].first,
11588 			NonNull(my_data[anchor_count].value),
11589 			(void *) my_data[anchor_count].data));
11590 
11591 		if (my_data[anchor_count].first) {
11592 		    if (first_one) {
11593 			if (Boundary) {
11594 			    HTBprintf(&my_query, "--%s\r\n", Boundary);
11595 			}
11596 			first_one = FALSE;
11597 		    } else {
11598 			if (PlainText) {
11599 			    BStrCat0(my_query, "\n");
11600 			} else if (SemiColon) {
11601 			    BStrCat0(my_query, ";");
11602 			} else if (Boundary) {
11603 			    HTBprintf(&my_query, "\r\n--%s\r\n", Boundary);
11604 			} else {
11605 			    BStrCat0(my_query, "&");
11606 			}
11607 		    }
11608 		}
11609 
11610 		/* append a null to the string */
11611 		HTSABCat(&(my_data[anchor_count].data), "", 1);
11612 		name_used = my_data[anchor_count].name;
11613 		val_used = my_data[anchor_count].value;
11614 
11615 	    } else {
11616 		/* there is no data to send */
11617 		continue;
11618 	    }
11619 
11620 	    switch (my_data[anchor_count].type) {
11621 	    case F_TEXT_TYPE:
11622 	    case F_PASSWORD_TYPE:
11623 	    case F_OPTION_LIST_TYPE:
11624 	    case F_HIDDEN_TYPE:
11625 		escaped1 = escape_or_quote_name(my_data[anchor_count].name,
11626 						my_data[anchor_count].quote,
11627 						MultipartContentType);
11628 
11629 		escaped2 = escape_or_quote_value(val_used,
11630 						 my_data[anchor_count].quote);
11631 
11632 		HTBprintf(&my_query,
11633 			  "%s%s%s%s%s",
11634 			  escaped1,
11635 			  (Boundary ? "" : "="),
11636 			  (PlainText ? "\n" : ""),
11637 			  escaped2,
11638 			  ((PlainText && *escaped2) ? "\n" : ""));
11639 		break;
11640 	    case F_CHECKBOX_TYPE:
11641 	    case F_RADIO_TYPE:
11642 		escaped1 = escape_or_quote_name(my_data[anchor_count].name,
11643 						my_data[anchor_count].quote,
11644 						MultipartContentType);
11645 
11646 		escaped2 = escape_or_quote_value(val_used,
11647 						 my_data[anchor_count].quote);
11648 
11649 		HTBprintf(&my_query,
11650 			  "%s%s%s%s%s",
11651 			  escaped1,
11652 			  (Boundary ? "" : "="),
11653 			  (PlainText ? "\n" : ""),
11654 			  escaped2,
11655 			  ((PlainText && *escaped2) ? "\n" : ""));
11656 		break;
11657 	    case F_SUBMIT_TYPE:
11658 	    case F_TEXT_SUBMIT_TYPE:
11659 	    case F_IMAGE_SUBMIT_TYPE:
11660 		/*
11661 		 * If it has a non-zero length name (e.g., because
11662 		 * its IMAGE_SUBMIT_TYPE is to be handled homologously
11663 		 * to an image map, or a SUBMIT_TYPE in a set of
11664 		 * multiple submit buttons, or a single type="text"
11665 		 * that's been converted to a TEXT_SUBMIT_TYPE),
11666 		 * include the name=value pair, or fake name.x=0 and
11667 		 * name.y=0 pairs for IMAGE_SUBMIT_TYPE.  -FM
11668 		 */
11669 		escaped1 = escape_or_quote_name(my_data[anchor_count].name,
11670 						my_data[anchor_count].quote,
11671 						MultipartContentType);
11672 
11673 		escaped2 = escape_or_quote_value(val_used,
11674 						 my_data[anchor_count].quote);
11675 
11676 		if (my_data[anchor_count].type == F_IMAGE_SUBMIT_TYPE) {
11677 		    /*
11678 		     * It's a clickable image submit button.  Fake a 0,0
11679 		     * coordinate pair, which typically returns the image's
11680 		     * default.  -FM
11681 		     */
11682 		    if (Boundary) {
11683 			*(StrChr(escaped1, '=') + 1) = '\0';
11684 			HTBprintf(&my_query,
11685 				  "%s\"%s.x\"\r\n\r\n0\r\n--%s\r\n%s\"%s.y\"\r\n\r\n0",
11686 				  escaped1,
11687 				  my_data[anchor_count].name,
11688 				  Boundary,
11689 				  escaped1,
11690 				  my_data[anchor_count].name);
11691 		    } else {
11692 			HTBprintf(&my_query,
11693 				  "%s.x=0%s%s.y=0%s",
11694 				  escaped1,
11695 				  (PlainText ?
11696 				   "\n" : (SemiColon ?
11697 					   ";" : "&")),
11698 				  escaped1,
11699 				  ((PlainText && *escaped1) ?
11700 				   "\n" : ""));
11701 		    }
11702 		} else {
11703 		    /*
11704 		     * It's a standard submit button.  Use the name=value
11705 		     * pair.  = FM
11706 		     */
11707 		    HTBprintf(&my_query,
11708 			      "%s%s%s%s%s",
11709 			      escaped1,
11710 			      (Boundary ? "" : "="),
11711 			      (PlainText ? "\n" : ""),
11712 			      escaped2,
11713 			      ((PlainText && *escaped2) ? "\n" : ""));
11714 		}
11715 		break;
11716 	    case F_RESET_TYPE:
11717 		/* ignore */
11718 		break;
11719 	    case F_TEXTAREA_TYPE:
11720 		escaped2 = escape_or_quote_value(val_used,
11721 						 my_data[anchor_count].quote);
11722 
11723 		if (my_data[anchor_count].first) {
11724 		    /*
11725 		     * Names are different so this is the first textarea or a
11726 		     * different one from any before it.
11727 		     */
11728 		    if (PlainText) {
11729 			FREE(previous_blanks);
11730 		    } else if (Boundary) {
11731 			StrAllocCopy(previous_blanks, "\r\n");
11732 		    } else {
11733 			StrAllocCopy(previous_blanks, "%0d%0a");
11734 		    }
11735 		    escaped1 = escape_or_quote_name(name_used,
11736 						    my_data[anchor_count].quote,
11737 						    MultipartContentType);
11738 
11739 		    HTBprintf(&my_query,
11740 			      "%s%s%s%s%s",
11741 			      escaped1,
11742 			      (Boundary ? "" : "="),
11743 			      (PlainText ? "\n" : ""),
11744 			      escaped2,
11745 			      ((PlainText && *escaped2) ? "\n" : ""));
11746 		} else {
11747 		    const char *marker = (PlainText
11748 					  ? "\n"
11749 					  : (Boundary
11750 					     ? "\r\n"
11751 					     : "%0d%0a"));
11752 
11753 		    /*
11754 		     * This is a continuation of a previous textarea.
11755 		     */
11756 		    if (escaped2[0] != '\0') {
11757 			if (previous_blanks) {
11758 			    BStrCat0(my_query, previous_blanks);
11759 			    FREE(previous_blanks);
11760 			}
11761 			BStrCat0(my_query, escaped2);
11762 			if (PlainText || Boundary)
11763 			    BStrCat0(my_query, marker);
11764 			else
11765 			    StrAllocCopy(previous_blanks, marker);
11766 		    } else {
11767 			StrAllocCat(previous_blanks, marker);
11768 		    }
11769 		}
11770 		break;
11771 	    case F_RANGE_TYPE:
11772 		/* not implemented */
11773 		break;
11774 #ifdef USE_FILE_UPLOAD
11775 	    case F_FILE_TYPE:
11776 		if (PlainText) {
11777 		    StrAllocCopy(escaped1, my_data[anchor_count].name);
11778 		} else if (Boundary) {
11779 		    const char *t = guess_content_type(val_used);
11780 		    char *copied_fname = NULL;
11781 
11782 		    StrAllocCopy(escaped1, "Content-Disposition: form-data");
11783 		    HTSprintf(&escaped1, "; name=\"%s\"",
11784 			      my_data[anchor_count].name);
11785 
11786 		    StrAllocCopy(copied_fname, val_used);
11787 		    HTMake822Word(&copied_fname, FALSE);
11788 		    HTSprintf(&escaped1, "; filename=\"%s\"", copied_fname);
11789 		    FREE(copied_fname);
11790 
11791 		    /* Should we take into account the encoding? */
11792 		    HTSprintf(&escaped1, "\r\nContent-Type: %s", t);
11793 		    if (my_data[anchor_count].quote == QUOTE_BASE64)
11794 			StrAllocCat(escaped1,
11795 				    "\r\nContent-Transfer-Encoding: base64");
11796 		    StrAllocCat(escaped1, "\r\n\r\n");
11797 		} else {
11798 		    escaped1 = HTEscapeSP(my_data[anchor_count].name, URL_XALPHAS);
11799 		}
11800 
11801 		HTBprintf(&my_query,
11802 			  "%s%s%s",
11803 			  escaped1,
11804 			  (Boundary ? "" : "="),
11805 			  (PlainText ? "\n" : ""));
11806 		/*
11807 		 * If we have anything more than the trailing null we added,
11808 		 * append the file-data to the query.
11809 		 */
11810 		if (BStrLen(my_data[anchor_count].data) > 1) {
11811 		    HTSABCat(&my_query,
11812 			     BStrData(my_data[anchor_count].data),
11813 			     BStrLen(my_data[anchor_count].data) - 1);
11814 		    if (PlainText)
11815 			HTBprintf(&my_query, "\n");
11816 		}
11817 		break;
11818 #endif /* USE_FILE_UPLOAD */
11819 	    case F_KEYGEN_TYPE:
11820 	    case F_BUTTON_TYPE:
11821 		/* not implemented */
11822 		break;
11823 	    }
11824 
11825 	    FREE(escaped1);
11826 	    FREE(escaped2);
11827 	}
11828 	if (Boundary) {
11829 	    HTBprintf(&my_query, "\r\n--%s--\r\n", Boundary);
11830 	}
11831 	if (TRACE) {
11832 	    CTRACE((tfp, "Query %d{", BStrLen(my_query)));
11833 	    trace_bstring(my_query);
11834 	    CTRACE((tfp, "}\n"));
11835 	}
11836     }
11837 
11838     if (submit_item->submit_method == URL_MAIL_METHOD) {
11839 	HTUserMsg2(gettext("Submitting %s"), submit_item->submit_action);
11840 	HTSABCat(&my_query, "", 1);	/* append null */
11841 	mailform((submit_item->submit_action + 7),
11842 		 (isEmpty(submit_item->submit_title)
11843 		  ? NonNull(HText_getTitle())
11844 		  : submit_item->submit_title),
11845 		 BStrData(my_query),
11846 		 content_type_out);
11847 	result = 0;
11848 	BStrFree(my_query);
11849 	FREE(content_type_out);
11850     } else {
11851 	_statusline(SUBMITTING_FORM);
11852 
11853 	/*
11854 	 * File-URLs (whether via GET or POST) cannot provide search queries.
11855 	 * The relevant RFCs 1630, 1738 are silent on what to do with
11856 	 * unexpected query parameters in a file-URL.
11857 	 *
11858 	 * Internet Explorer trims the query string here (after all, a "?" is
11859 	 * not a legal part of a Windows filename), and other browsers copy the
11860 	 * behavior.  We do this for compatibility, in case someone cares.
11861 	 */
11862 	if (my_query != 0 &&
11863 	    my_query->len > 5 &&
11864 	    !strncmp(my_query->str, "file:", (size_t) 5)) {
11865 	    strtok(my_query->str, "?");
11866 	}
11867 	if (submit_item->submit_method == URL_POST_METHOD || Boundary) {
11868 	    LYFreePostData(doc);
11869 	    doc->post_data = my_query;
11870 	    doc->post_content_type = content_type_out;	/* don't free c_t_out */
11871 	    CTRACE((tfp, "GridText - post_data set:\n%s\n", content_type_out));
11872 	    StrAllocCopy(doc->address, submit_item->submit_action);
11873 	} else {		/* GET_METHOD */
11874 	    HTSABCat(&my_query, "", 1);		/* append null */
11875 	    StrAllocCopy(doc->address, BStrData(my_query));
11876 	    LYFreePostData(doc);
11877 	    FREE(content_type_out);
11878 	    HTSABFree(&my_query);
11879 	}
11880 	result = 1;
11881     }
11882 
11883     FREE(MultipartContentType);
11884     FREE(previous_blanks);
11885     FREE(Boundary);
11886     if (my_data != 0) {
11887 	for (anchor_count = 0; anchor_count < anchor_limit; ++anchor_count) {
11888 	    FREE(my_data[anchor_count].name);
11889 	    FREE(my_data[anchor_count].value);
11890 	    BStrFree(my_data[anchor_count].data);
11891 	}
11892 	FREE(my_data);
11893     }
11894 
11895     return (result);
11896 }
11897 
HText_DisableCurrentForm(void)11898 void HText_DisableCurrentForm(void)
11899 {
11900     TextAnchor *anchor_ptr;
11901 
11902     HTFormDisabled = TRUE;
11903     if (HTMainText != NULL) {
11904 	/*
11905 	 * Go through list of anchors and set the disabled flag.
11906 	 */
11907 	for (anchor_ptr = HTMainText->first_anchor;
11908 	     anchor_ptr != NULL;
11909 	     anchor_ptr = anchor_ptr->next) {
11910 
11911 	    if (anchor_ptr->link_type == INPUT_ANCHOR &&
11912 		anchor_ptr->input_field->number == HTFormNumber) {
11913 
11914 		anchor_ptr->input_field->disabled = TRUE;
11915 	    }
11916 	}
11917     }
11918     return;
11919 }
11920 
HText_ResetForm(FormInfo * form)11921 void HText_ResetForm(FormInfo * form)
11922 {
11923     TextAnchor *anchor_ptr;
11924 
11925     _statusline(RESETTING_FORM);
11926     if (HTMainText == 0)
11927 	return;
11928 
11929     /*
11930      * Go through list of anchors and reset values.
11931      */
11932     for (anchor_ptr = HTMainText->first_anchor;
11933 	 anchor_ptr != NULL;
11934 	 anchor_ptr = anchor_ptr->next) {
11935 	if (anchor_ptr->link_type == INPUT_ANCHOR) {
11936 	    if (anchor_ptr->input_field->number == form->number) {
11937 
11938 		if (anchor_ptr->input_field->type == F_RADIO_TYPE ||
11939 		    anchor_ptr->input_field->type == F_CHECKBOX_TYPE) {
11940 
11941 		    if (anchor_ptr->input_field->orig_value[0] == '0')
11942 			anchor_ptr->input_field->num_value = 0;
11943 		    else
11944 			anchor_ptr->input_field->num_value = 1;
11945 
11946 		} else if (anchor_ptr->input_field->type ==
11947 			   F_OPTION_LIST_TYPE) {
11948 		    anchor_ptr->input_field->value =
11949 			anchor_ptr->input_field->orig_value;
11950 
11951 		    anchor_ptr->input_field->cp_submit_value =
11952 			anchor_ptr->input_field->orig_submit_value;
11953 
11954 		} else {
11955 		    StrAllocCopy(anchor_ptr->input_field->value,
11956 				 anchor_ptr->input_field->orig_value);
11957 		}
11958 	    } else if (anchor_ptr->input_field->number > form->number) {
11959 		break;
11960 	    }
11961 	}
11962     }
11963 }
11964 
11965 /*
11966  * This function is called before reloading/reparsing current document to find
11967  * whether any forms content was changed by user so any information will be
11968  * lost.
11969  */
HText_HaveUserChangedForms(HText * text)11970 BOOLEAN HText_HaveUserChangedForms(HText *text)
11971 {
11972     TextAnchor *anchor_ptr;
11973 
11974     if (text == 0)
11975 	return FALSE;
11976 
11977     /*
11978      * Go through list of anchors to check if any value was changed.
11979      * This code based on HText_ResetForm()
11980      */
11981     for (anchor_ptr = text->first_anchor;
11982 	 anchor_ptr != NULL;
11983 	 anchor_ptr = anchor_ptr->next) {
11984 	if (anchor_ptr->link_type == INPUT_ANCHOR) {
11985 
11986 	    if (anchor_ptr->input_field->type == F_RADIO_TYPE ||
11987 		anchor_ptr->input_field->type == F_CHECKBOX_TYPE) {
11988 
11989 		if ((anchor_ptr->input_field->orig_value[0] == '0' &&
11990 		     anchor_ptr->input_field->num_value == 1) ||
11991 		    (anchor_ptr->input_field->orig_value[0] != '0' &&
11992 		     anchor_ptr->input_field->num_value == 0))
11993 		    return TRUE;
11994 
11995 	    } else if (anchor_ptr->input_field->type == F_OPTION_LIST_TYPE) {
11996 		if (strcmp(anchor_ptr->input_field->value,
11997 			   anchor_ptr->input_field->orig_value))
11998 		    return TRUE;
11999 
12000 		if (strcmp(anchor_ptr->input_field->cp_submit_value,
12001 			   anchor_ptr->input_field->orig_submit_value))
12002 		    return TRUE;
12003 
12004 	    } else {
12005 		if (strcmp(anchor_ptr->input_field->value,
12006 			   anchor_ptr->input_field->orig_value))
12007 		    return TRUE;
12008 	    }
12009 	}
12010     }
12011     return FALSE;
12012 }
12013 
HText_activateRadioButton(FormInfo * form)12014 void HText_activateRadioButton(FormInfo * form)
12015 {
12016     TextAnchor *anchor_ptr;
12017     int form_number = form->number;
12018 
12019     if (!HTMainText)
12020 	return;
12021     for (anchor_ptr = HTMainText->first_anchor;
12022 	 anchor_ptr != NULL;
12023 	 anchor_ptr = anchor_ptr->next) {
12024 	if (anchor_ptr->link_type == INPUT_ANCHOR &&
12025 	    anchor_ptr->input_field->type == F_RADIO_TYPE) {
12026 
12027 	    if (anchor_ptr->input_field->number == form_number) {
12028 
12029 		/* if it has the same name and its on */
12030 		if (!strcmp(anchor_ptr->input_field->name, form->name) &&
12031 		    anchor_ptr->input_field->num_value) {
12032 		    anchor_ptr->input_field->num_value = 0;
12033 		    break;
12034 		}
12035 	    } else if (anchor_ptr->input_field->number > form_number) {
12036 		break;
12037 	    }
12038 
12039 	}
12040     }
12041 
12042     form->num_value = 1;
12043 }
12044 
12045 #ifdef LY_FIND_LEAKS
12046 /*
12047  *	Purpose:	Free all currently loaded HText objects in memory.
12048  *	Arguments:	void
12049  *	Return Value:	void
12050  *	Remarks/Portability/Dependencies/Restrictions:
12051  *		Usage of this function should really be limited to program
12052  *			termination.
12053  *	Revision History:
12054  *		05-27-94	created Lynx 2-3-1 Garrett Arch Blythe
12055  */
free_all_texts(void)12056 static void free_all_texts(void)
12057 {
12058     HText *cur = NULL;
12059 
12060     if (!loaded_texts)
12061 	return;
12062 
12063     /*
12064      * Simply loop through the loaded texts list killing them off.
12065      */
12066     while (loaded_texts && !HTList_isEmpty(loaded_texts)) {
12067 	if ((cur = (HText *) HTList_removeLastObject(loaded_texts)) != NULL) {
12068 	    HText_free(cur);
12069 	}
12070     }
12071 
12072     /*
12073      * Get rid of the text list.
12074      */
12075     if (loaded_texts) {
12076 	HTList_delete(loaded_texts);
12077     }
12078 
12079     /*
12080      * Insurance for bad HTML.
12081      */
12082     FREE(HTCurSelectGroup);
12083     FREE(HTCurSelectGroupSize);
12084     FREE(HTCurSelectedOptionValue);
12085     PerFormInfo_free(HTCurrentForm);
12086 
12087     return;
12088 }
12089 #endif /* LY_FIND_LEAKS */
12090 
12091 /*
12092  *  stub_HTAnchor_address is like HTAnchor_address, but it returns the
12093  *  parent address for child links.  This is only useful for traversal's
12094  *  where one does not want to index a text file N times, once for each
12095  *  of N internal links.  Since the parent link has already been taken,
12096  *  it won't go again, hence the (incorrect) links won't cause problems.
12097  */
stub_HTAnchor_address(HTAnchor * me)12098 char *stub_HTAnchor_address(HTAnchor * me)
12099 {
12100     char *addr = NULL;
12101 
12102     if (me)
12103 	StrAllocCopy(addr, me->parent->address);
12104     return addr;
12105 }
12106 
HText_setToolbar(HText * text)12107 void HText_setToolbar(HText *text)
12108 {
12109     if (text)
12110 	text->toolbar = TRUE;
12111     return;
12112 }
12113 
HText_hasToolbar(HText * text)12114 BOOL HText_hasToolbar(HText *text)
12115 {
12116     return (BOOL) ((text && text->toolbar) ? TRUE : FALSE);
12117 }
12118 
HText_setNoCache(HText * text)12119 void HText_setNoCache(HText *text)
12120 {
12121     if (text)
12122 	text->no_cache = TRUE;
12123     return;
12124 }
12125 
HText_hasNoCacheSet(HText * text)12126 BOOL HText_hasNoCacheSet(HText *text)
12127 {
12128     return (BOOL) ((text && text->no_cache) ? TRUE : FALSE);
12129 }
12130 
HText_hasUTF8OutputSet(HText * text)12131 BOOL HText_hasUTF8OutputSet(HText *text)
12132 {
12133     return (BOOL) ((text && text->T.output_utf8) ? TRUE : FALSE);
12134 }
12135 
12136 /*
12137  *  Check charset and set the kcode element. -FM
12138  *  Info on the input charset may be passed in in two forms,
12139  *  as a string (if given explicitly) and as a pointer to
12140  *  a LYUCcharset (from chartrans mechanism); either can be NULL.
12141  *  For Japanese the kcode will be reset at a space or explicit
12142  *  line or paragraph break, so what we set here may not last for
12143  *  long.  It's potentially more important not to set HTCJK to
12144  *  NOCJK unless we are sure. - kw
12145  */
HText_setKcode(HText * text,const char * charset,LYUCcharset * p_in)12146 void HText_setKcode(HText *text, const char *charset,
12147 		    LYUCcharset *p_in)
12148 {
12149     BOOL charset_explicit;
12150 
12151     if (!text)
12152 	return;
12153 
12154     /*
12155      * Check whether we have some kind of info.  - kw
12156      */
12157     if (!charset && !p_in) {
12158 	return;
12159     }
12160     charset_explicit = (BOOLEAN) (charset ? TRUE : FALSE);
12161     /*
12162      * If no explicit charset string, use the implied one.  - kw
12163      */
12164     if (isEmpty(charset)) {
12165 	charset = p_in->MIMEname;
12166     }
12167     /*
12168      * Check whether we have a specified charset.  -FM
12169      */
12170     if (isEmpty(charset)) {
12171 	return;
12172     }
12173 
12174     /*
12175      * We've included the charset, and not forced a download offer,
12176      * only if the currently selected character set can handle it,
12177      * so check the charset value and set the text->kcode element
12178      * appropriately.  -FM
12179      */
12180     /*  If charset isn't specified explicitely nor assumed,
12181      * p_in->MIMEname would be set as display charset.
12182      * So text->kcode sholud be set as SJIS or EUC here only if charset
12183      * is specified explicitely, otherwise text->kcode would cause
12184      * mishandling Japanese strings.  -- TH
12185      */
12186     if (charset_explicit && (!strcmp(charset, "shift_jis") ||
12187 			     !strcmp(charset, "x-sjis") ||	/* 1997/11/28 (Fri) 18:11:33 */
12188 			     !strcmp(charset, "x-shift-jis"))) {
12189 	text->kcode = SJIS;
12190     } else if (charset_explicit
12191 #ifdef EXP_JAPANESEUTF8_SUPPORT
12192 	       && strcmp(charset, "utf-8")
12193 #endif
12194 	       && ((p_in && (p_in->enc == UCT_ENC_CJK)) ||
12195 		   !strcmp(charset, "x-euc") ||		/* 1997/11/28 (Fri) 18:11:24 */
12196 		   !strcmp(charset, "euc-jp") ||
12197 		   !StrNCmp(charset, "x-euc-", 6) ||
12198 		   !strcmp(charset, "euc-kr") ||
12199 		   !strcmp(charset, "iso-2022-kr") ||
12200 		   !strcmp(charset, "big5") ||
12201 		   !strcmp(charset, "cn-big5") ||
12202 		   !strcmp(charset, "euc-cn") ||
12203 		   !strcmp(charset, "gb2312") ||
12204 		   !StrNCmp(charset, "cn-gb", 5) ||
12205 		   !strcmp(charset, "iso-2022-cn"))) {
12206 	text->kcode = EUC;
12207     } else {
12208 	/*
12209 	 * If we get to here, it's not CJK, so disable that if
12210 	 * it is enabled.  But only if we are quite sure.  -FM & kw
12211 	 */
12212 	text->kcode = NOKANJI;
12213 	if (IS_CJK_TTY) {
12214 	    if (!p_in || ((p_in->enc != UCT_ENC_CJK)
12215 #ifdef EXP_JAPANESEUTF8_SUPPORT
12216 			  && (p_in->enc != UCT_ENC_UTF8)
12217 #endif
12218 		)) {
12219 		HTCJK = NOCJK;
12220 	    }
12221 	}
12222     }
12223 
12224     if (charset_explicit
12225 #ifdef EXP_JAPANESEUTF8_SUPPORT
12226 	&& strcmp(charset, "utf-8")
12227 #endif
12228 	) {
12229 	text->specified_kcode = text->kcode;
12230     } else {
12231 	if (UCAssume_MIMEcharset) {
12232 	    if (!strcmp(UCAssume_MIMEcharset, "euc-jp"))
12233 		text->kcode = text->specified_kcode = EUC;
12234 	    else if (!strcmp(UCAssume_MIMEcharset, "shift_jis"))
12235 		text->kcode = text->specified_kcode = SJIS;
12236 	}
12237     }
12238 
12239     return;
12240 }
12241 
12242 /*
12243  *  Set a permissible split at the current end of the last line. -FM
12244  */
HText_setBreakPoint(HText * text)12245 void HText_setBreakPoint(HText *text)
12246 {
12247     if (!text)
12248 	return;
12249 
12250     /*
12251      * Can split here.  -FM
12252      */
12253     text->permissible_split = text->last_line->size;
12254 
12255     return;
12256 }
12257 
12258 /*
12259  *  This function determines whether a document which
12260  *  would be sought via the a URL that has a fragment
12261  *  directive appended is otherwise identical to the
12262  *  currently loaded document, and if so, returns
12263  *  FALSE, so that any no_cache directives can be
12264  *  overridden "safely", on the grounds that we are
12265  *  simply acting on the equivalent of a paging
12266  *  command.  Otherwise, it returns TRUE, i.e, that
12267  *  the target document might differ from the current,
12268  *  based on any caching directives or analyses which
12269  *  claimed or suggested this. -FM
12270  */
HText_AreDifferent(HTParentAnchor * anchor,const char * full_address)12271 BOOL HText_AreDifferent(HTParentAnchor *anchor,
12272 			const char *full_address)
12273 {
12274     HTParentAnchor *MTanc;
12275     char *MTaddress;
12276     char *MTpound;
12277 
12278     /*
12279      * Do we have a loaded document and both
12280      * arguments for this function?
12281      */
12282     if (!(HTMainText && anchor && full_address))
12283 	return TRUE;
12284 
12285     /*
12286      * Do we have both URLs?
12287      */
12288     MTanc = HTMainText->node_anchor;
12289     if (!(MTanc->address && anchor->address))
12290 	return (TRUE);
12291 
12292     /*
12293      * Do we have a fragment associated with the target?
12294      */
12295     if (findPoundSelector(full_address) == NULL)
12296 	return (TRUE);
12297 
12298     /*
12299      * Always treat client-side image map menus
12300      * as potentially stale, so we'll create a
12301      * fresh menu from the LynxMaps HTList.
12302      */
12303     if (isLYNXIMGMAP(anchor->address))
12304 	return (TRUE);
12305 
12306     /*
12307      * Do the docs differ in the type of request?
12308      */
12309     if (MTanc->isHEAD != anchor->isHEAD)
12310 	return (TRUE);
12311 
12312     /*
12313      * Are the actual URLs different, after factoring
12314      * out a "LYNXIMGMAP:" leader in the MainText URL
12315      * and its fragment, if present?
12316      */
12317     MTaddress = (isLYNXIMGMAP(MTanc->address)
12318 		 ? MTanc->address + LEN_LYNXIMGMAP
12319 		 : MTanc->address);
12320     MTpound = trimPoundSelector(MTaddress);
12321     if (strcmp(MTaddress, anchor->address)) {
12322 	restorePoundSelector(MTpound);
12323 	return (TRUE);
12324     }
12325     restorePoundSelector(MTpound);
12326 
12327     /*
12328      * If the MainText is not an image map menu,
12329      * do the docs have different POST contents?
12330      */
12331     if (MTaddress == MTanc->address) {
12332 	if (MTanc->post_data) {
12333 	    if (anchor->post_data) {
12334 		if (!BINEQ(MTanc->post_data, anchor->post_data)) {
12335 		    /*
12336 		     * Both have contents, and they differ.
12337 		     */
12338 		    return (TRUE);
12339 		}
12340 	    } else {
12341 		/*
12342 		 * The loaded document has content, but the
12343 		 * target doesn't, so they're different.
12344 		 */
12345 		return (TRUE);
12346 	    }
12347 	} else if (anchor->post_data) {
12348 	    /*
12349 	     * The loaded document does not have content, but
12350 	     * the target does, so they're different.
12351 	     */
12352 	    return (TRUE);
12353 	}
12354     }
12355 
12356     /*
12357      * We'll assume the target is a position in the currently
12358      * displayed document, and thus can ignore any header, META,
12359      * or other directives not to use a cached rendition.  -FM
12360      */
12361     return (FALSE);
12362 }
12363 
12364 #define CanTrimTextArea(c) \
12365     (LYtrimInputFields ? isspace(c) : ((c) == '\r' || (c) == '\n'))
12366 
12367 /*
12368  * Re-render the text of a tagged ("[123]") HTLine (arg1), with the tag
12369  * number incremented by some value (arg5).  The re-rendered string may
12370  * be allowed to expand in the event of a tag width change (eg, 99 -> 100)
12371  * as controlled by arg6 (CHOP or NOCHOP).  Arg4 is either (the address
12372  * of) a value which must match, in order for the tag to be incremented,
12373  * or (the address of) a 0-value, which will match any value, and cause
12374  * any valid tag to be incremented.  Arg2 is a pointer to the first/only
12375  * anchor that exists on the line; we may need to adjust their position(s)
12376  * on the line.  Arg3 when non-0 indicates the number of new digits that
12377  * were added to the 2nd line in a line crossing pair.
12378  *
12379  * All tags fields in a line which individually match an expected new value,
12380  * are incremented.  Line crossing [tags] are handled (PITA).
12381  *
12382  * Untagged or improperly tagged lines are not altered.
12383  *
12384  * Returns the number of chars added to the original string's length, if
12385  * any.
12386  *
12387  * --KED 02/03/99
12388  */
increment_tagged_htline(HTLine * ht,TextAnchor * a,int * lx_val,int * old_val,int incr,int mode)12389 static int increment_tagged_htline(HTLine *ht, TextAnchor *a, int *lx_val,
12390 				   int *old_val,
12391 				   int incr,
12392 				   int mode)
12393 {
12394     char buf[MAX_LINE];
12395     char lxbuf[MAX_LINE * 2];
12396 
12397     TextAnchor *st_anchor = a;
12398     TextAnchor *nxt_anchor;
12399 
12400     char *p = ht->data;
12401     char *s = buf;
12402     char *lx = lxbuf;
12403     char *t;
12404 
12405     BOOLEAN plx = FALSE;
12406     BOOLEAN valid;
12407 
12408     int val;
12409     int n;
12410     int new_n;
12411     int pre_n;
12412     int post_n;
12413     int fixup = 0;
12414 
12415     /*
12416      * Cleanup for the 2nd half of a line crosser, whose number of tag
12417      * digits grew by some number of places (usually 1 when it does
12418      * happen, though it *could* be more).  The tag chars were already
12419      * rendered into the 2nd line of the pair, but the positioning and
12420      * other effects haven't been rippled through any other anchors on
12421      * the (2nd) line.  So we do that here, as a special case, since
12422      * the part of the tag that's in the 2nd line of the pair, will not
12423      * be found by the tag string parsing code.  Double PITA.
12424      *
12425      * [see comments below on line crosser caused problems]
12426      */
12427     if (*lx_val != 0) {
12428 	nxt_anchor = st_anchor;
12429 	while ((nxt_anchor) && (nxt_anchor->line_num == a->line_num)) {
12430 	    nxt_anchor->line_pos = (short) (nxt_anchor->line_pos + *lx_val);
12431 	    nxt_anchor = nxt_anchor->next;
12432 	}
12433 	fixup = *lx_val;
12434 	*lx_val = 0;
12435 	if (st_anchor)
12436 	    st_anchor = st_anchor->next;
12437     }
12438 
12439     /*
12440      * Walk thru the line looking for tags (ie, "[nnn]" strings).
12441      */
12442     while (*p != '\0') {
12443 	if (*p != '[') {
12444 	    *s++ = *p++;
12445 	    continue;
12446 
12447 	} else {
12448 	    *s++ = *p++;
12449 	    t = p;
12450 	    n = 0;
12451 	    valid = TRUE;	/* p = t = byte after '[' */
12452 
12453 	    /*
12454 	     * Make sure there are only digits between "[" and "]".
12455 	     */
12456 	    while (*t != ']') {
12457 		if (*t == '\0') {	/* uhoh - we have a potential line crosser */
12458 		    valid = FALSE;
12459 		    plx = TRUE;
12460 		    break;
12461 		}
12462 		if (isdigit(UCH(*t++))) {
12463 		    n++;
12464 		    continue;
12465 		} else {
12466 		    valid = FALSE;
12467 		    break;
12468 		}
12469 	    }
12470 
12471 	    /*
12472 	     * If the format is OK, we check to see if the value is what
12473 	     * we expect.  If not, we have a random [nn] string in the text,
12474 	     * and leave it alone.
12475 	     *
12476 	     * [It is *possible* to have a false match here, *if* there are
12477 	     * two identical [nn] strings (including the numeric value of
12478 	     * nn), one of which is the [tag], and the other being part of
12479 	     * a document.  In such a case, the 1st [nn] string will get
12480 	     * incremented; the 2nd one won't, which makes it a 50-50 chance
12481 	     * of being correct, if and when such an unlikely juxtaposition
12482 	     * of text ever occurs.  Further validation tests of the [nnn]
12483 	     * string are probably not possible, since little of the actual
12484 	     * anchor-associated-text is retained in the TextAnchor or the
12485 	     * FormInfo structs.  Fortunately, I think the current method is
12486 	     * more than adequate to weed out 99.999% of any possible false
12487 	     * matches, just as it stands.  Caveat emptor.]
12488 	     */
12489 	    if ((valid) && (n > 0)) {
12490 		val = atoi(p);
12491 		if ((val == *old_val) || (*old_val == 0)) {	/* 0 matches all */
12492 		    if (*old_val != 0)
12493 			(*old_val)++;
12494 		    val += incr;
12495 		    sprintf(s, "%d", val);
12496 		    new_n = (int) strlen(s);
12497 		    s += new_n;
12498 		    p += n;
12499 
12500 		    /*
12501 		     * If the number of digits in an existing [tag] increased
12502 		     * (eg, [99] --> [100], etc), we need to "adjust" its
12503 		     * horizontal position, and that of all subsequent tags
12504 		     * that may be on the same line.  PITA.
12505 		     *
12506 		     * [This seems to work as long as a tag isn't a line
12507 		     * crosser; when it is, the position of anchors on either
12508 		     * side of the split tag, seem to "float" and try to be
12509 		     * as "centered" as possible.  Which means that simply
12510 		     * incrementing the line_pos by the fixed value of the
12511 		     * number of digits that got added to some tag in either
12512 		     * line doesn't work quite right, and the text for (say)
12513 		     * a button may get stomped on by another copy of itself,
12514 		     * but offset by a few chars, when it is selected (eg,
12515 		     * "Box Office" may end up looking like "BoBox Office" or
12516 		     * "Box Officece", etc.
12517 		     *
12518 		     * Dunno how to fix that behavior ATT, but at least the
12519 		     * tag numbers themselves are correct.  -KED /\oo/\ ]
12520 		     */
12521 		    if ((new_n -= n) != 0) {
12522 			nxt_anchor = st_anchor;
12523 			while ((nxt_anchor) &&
12524 			       (nxt_anchor->line_num == a->line_num)) {
12525 			    nxt_anchor->line_pos = (short) (nxt_anchor->line_pos
12526 							    + new_n);
12527 			    nxt_anchor = nxt_anchor->next;
12528 			}
12529 			if (st_anchor)
12530 			    st_anchor = st_anchor->next;
12531 		    }
12532 		}
12533 	    }
12534 
12535 	    /*
12536 	     * Unfortunately, valid [tag] strings *can* be split across two
12537 	     * lines.  Perhaps it would be best to just prevent that from
12538 	     * happening, but a look into that code, makes me wonder.  Anyway,
12539 	     * we can handle such tags without *too* much trouble in here [I
12540 	     * think], though since such animals are rather rare, it makes it
12541 	     * a bit difficult to test thoroughly (ie, Beyond here, there be
12542 	     * Dragons).
12543 	     *
12544 	     * We use lxbuf[] to deal with the two lines involved.
12545 	     */
12546 	    pre_n = (int) strlen(p);	/* count of 1st part chars in this line */
12547 	    post_n = (int) strlen(ht->next->data);
12548 	    if (plx
12549 		&& (pre_n + post_n + 2 < (int) sizeof(lxbuf))) {
12550 		strcpy(lx, p);	/* <- 1st part of a possible lx'ing tag */
12551 		strcat(lx, ht->next->data);	/* tack on NEXT line          */
12552 
12553 		t = lx;
12554 		n = 0;
12555 		valid = TRUE;
12556 
12557 		/*
12558 		 * Go hunting again for just digits, followed by tag end ']'.
12559 		 */
12560 		while (*t != ']') {
12561 		    if (isdigit(UCH(*t++))) {
12562 			n++;
12563 			continue;
12564 		    } else {
12565 			valid = FALSE;
12566 			break;
12567 		    }
12568 		}
12569 
12570 		/*
12571 		 * It *looks* like a line crosser; now we value test it to
12572 		 * find out for sure [but see the "false match" warning,
12573 		 * above], and if it matches, increment it into the buffer,
12574 		 * along with the 2nd line's text.
12575 		 */
12576 		if ((valid)
12577 		    && (n > 0)
12578 		    && (n + post_n + 2) < MAX_LINE) {
12579 		    val = atoi(lx);
12580 		    if ((val == *old_val) || (*old_val == 0)) {
12581 			const char *r;
12582 
12583 			if (*old_val != 0)
12584 			    (*old_val)++;
12585 			val += incr;
12586 			sprintf(lx, "%d", val);
12587 			new_n = (int) strlen(lx);
12588 			if ((r = StrChr(ht->next->data, ']')) == 0) {
12589 			    r = "";
12590 			}
12591 			strcat(lx, r);
12592 
12593 			/*
12594 			 * We keep the the same number of chars from the
12595 			 * adjusted tag number in the current line; any
12596 			 * extra chars due to a digits increase, will be
12597 			 * stuffed into the next line.
12598 			 *
12599 			 * Keep track of any digits added, for the next
12600 			 * pass through.
12601 			 */
12602 			s = StrNCpy(s, lx, pre_n) + pre_n;
12603 			lx += pre_n;
12604 			strcpy(ht->next->data, lx);
12605 
12606 			*lx_val = new_n - n;
12607 		    }
12608 		}
12609 		break;		/* had an lx'er, so we're done with this line */
12610 	    }
12611 	}
12612     }
12613 
12614     *s = '\0';
12615 
12616     n = (int) strlen(ht->data);
12617     if (mode == CHOP) {
12618 	*(buf + n) = '\0';
12619     } else if (strlen(buf) > ht->size) {
12620 	/* we didn't allocate enough space originally - increase it */
12621 	HTLine *temp;
12622 
12623 	allocHTLine(temp, strlen(buf));
12624 	if (!temp)
12625 	    outofmem(__FILE__, "increment_tagged_htline");
12626 
12627 	memcpy(temp, ht, LINE_SIZE(0));
12628 #if defined(USE_COLOR_STYLE)
12629 	POOLallocstyles(temp->styles, ht->numstyles);
12630 	if (!temp->styles)
12631 	    outofmem(__FILE__, "increment_tagged_htline");
12632 	memcpy(temp->styles, ht->styles, sizeof(HTStyleChange) * ht->numstyles);
12633 #endif
12634 	ht = temp;
12635 	ht->prev->next = ht;	/* Link in new line */
12636 	ht->next->prev = ht;	/* Could be same node of course */
12637     }
12638     strcpy(ht->data, buf);
12639 
12640     return ((int) strlen(buf) - n + fixup);
12641 }
12642 
12643 /*
12644  * Creates a new anchor and associated struct's appropriate for a form
12645  * TEXTAREA, and links them into the lists following the current anchor
12646  * position (as specified by arg1).
12647  *
12648  * Exits with arg1 now pointing at the new TextAnchor, and arg2 pointing
12649  * at the new, associated HTLine.
12650  *
12651  * --KED 02/13/99
12652  */
insert_new_textarea_anchor(TextAnchor ** curr_anchor,HTLine ** exit_htline)12653 static void insert_new_textarea_anchor(TextAnchor **curr_anchor, HTLine **exit_htline)
12654 {
12655     TextAnchor *anchor = *curr_anchor;
12656     HTLine *htline;
12657 
12658     TextAnchor *a = 0;
12659     FormInfo *f = 0;
12660     HTLine *l = 0;
12661 
12662     int curr_tag = 0;		/* 0 ==> match any [tag] number */
12663     int lx = 0;			/* 0 ==> no line crossing [tag]; it's a new line */
12664     int i;
12665 
12666     /*
12667      * Find line in the text that matches ending anchorline of
12668      * the TEXTAREA.
12669      *
12670      * [Yes, Virginia ...  we *do* have to go thru this for each
12671      * anchor being added, since there is NOT a 1-to-1 mapping
12672      * between anchors and htlines.  I suppose we could create
12673      * YAS (Yet Another Struct), but there are too many structs{}
12674      * floating around in here, as it is.  IMNSHO.]
12675      */
12676     for (htline = FirstHTLine(HTMainText), i = 0; anchor->line_num != i; i++) {
12677 	htline = htline->next;
12678 	if (htline == HTMainText->last_line)
12679 	    break;
12680     }
12681 
12682     /*
12683      * Clone and initialize the struct's needed to add a new TEXTAREA
12684      * anchor.
12685      */
12686     allocHTLine(l, MAX_LINE);
12687     POOLtypecalloc(TextAnchor, a);
12688 
12689     POOLtypecalloc(FormInfo, f);
12690     if (a == NULL || l == NULL || f == NULL)
12691 	outofmem(__FILE__, "insert_new_textarea_anchor");
12692 
12693     /*  Init all the fields in the new TextAnchor.                 */
12694     /*  [anything "special" needed based on ->show_anchor value ?] */
12695     a->next = anchor->next;
12696     a->number = anchor->number;
12697     a->line_pos = anchor->line_pos;
12698     a->extent = anchor->extent;
12699     a->sgml_offset = SGML_offset();
12700     a->line_num = anchor->line_num + 1;
12701     LYCopyHiText(a, anchor);
12702     a->link_type = anchor->link_type;
12703     a->input_field = f;
12704     a->show_anchor = anchor->show_anchor;
12705     a->inUnderline = anchor->inUnderline;
12706     a->expansion_anch = TRUE;
12707     a->anchor = NULL;
12708 
12709     /*  Just the (seemingly) relevant fields in the new FormInfo.  */
12710     /*  [do we need to do anything "special" based on ->disabled]  */
12711     StrAllocCopy(f->name, anchor->input_field->name);
12712     f->number = anchor->input_field->number;
12713     f->type = anchor->input_field->type;
12714     StrAllocCopy(f->orig_value, "");
12715     f->size = anchor->input_field->size;
12716     f->maxlength = anchor->input_field->maxlength;
12717     f->no_cache = anchor->input_field->no_cache;
12718     f->disabled = anchor->input_field->disabled;
12719     f->readonly = anchor->input_field->readonly;
12720     f->value_cs = current_char_set;	/* use current setting - kw */
12721 
12722     /*  Init all the fields in the new HTLine (but see the #if).   */
12723     l->next = htline->next;
12724     l->prev = htline;
12725     l->offset = htline->offset;
12726     l->size = htline->size;
12727 #if defined(USE_COLOR_STYLE)
12728     /* dup styles[] if needed [no need in TEXTAREA (?); leave 0's] */
12729     l->numstyles = htline->numstyles;
12730     /*we fork the pointers! */
12731     l->styles = htline->styles;
12732 #endif
12733     strcpy(l->data, htline->data);
12734 
12735     /*
12736      * Link in the new HTLine.
12737      */
12738     htline->next->prev = l;
12739     htline->next = l;
12740 
12741     if (fields_are_numbered()) {
12742 	a->number++;
12743 	increment_tagged_htline(l, a, &lx, &curr_tag, 1, CHOP);
12744     }
12745 
12746     /*
12747      * If we're at the tail end of the TextAnchor or HTLine list(s),
12748      * the new node becomes the last node.
12749      */
12750     if (anchor == HTMainText->last_anchor)
12751 	HTMainText->last_anchor = a;
12752     if (htline == HTMainText->last_line)
12753 	HTMainText->last_line = l;
12754 
12755     /*
12756      * Link in the new TextAnchor and point the entry anchor arg at it;
12757      * point the entry HTLine arg at it, too.
12758      */
12759     anchor->next = a;
12760     *curr_anchor = a;
12761 
12762     *exit_htline = l->next;
12763 
12764     return;
12765 }
12766 
12767 /*
12768  * If new anchors were added to expand a TEXTAREA, we need to ripple the
12769  * new line numbers [and char counts ?] thru the subsequent anchors.
12770  *
12771  * If form lines are getting [nnn] tagged, we need to update the displayed
12772  * tag values to match (which means rerendering them ...  sigh).
12773  *
12774  * Finally, we need to update various HTMainText and other counts, etc.
12775  *
12776  * [dunno if the char counts really *need* to be done, or if we're using
12777  * the exactly proper values/algorithms ...  seems to be OK though ...]
12778  *
12779  * --KED 02/13/99
12780  */
update_subsequent_anchors(int newlines,TextAnchor * start_anchor,HTLine * start_htline,int start_tag)12781 static void update_subsequent_anchors(int newlines,
12782 				      TextAnchor *start_anchor,
12783 				      HTLine *start_htline,
12784 				      int start_tag)
12785 {
12786     TextAnchor *anchor;
12787     HTLine *htline = start_htline;
12788 
12789     int line_adj = 0;
12790     int tag_adj = 0;
12791     int lx = 0;
12792     int hang = 0;		/* for HANG detection of a nasty intermittent */
12793     int hang_detect = 100000;	/* ditto */
12794 
12795     CTRACE((tfp, "GridText: adjusting struct's to add %d new line(s)\n", newlines));
12796 
12797     /*
12798      * Update numeric fields of the rest of the anchors.
12799      *
12800      * [We bypass bumping ->number if it has a value of 0, which takes care
12801      * of the ->input_field->type == F_HIDDEN_TYPE (as well as any other
12802      * "hidden" anchors, if such things exist).  Seems like the "right
12803      * thing" to do.  I think.]
12804      */
12805     anchor = start_anchor->next;	/* begin updating with the NEXT anchor */
12806     while (anchor) {
12807 	if (fields_are_numbered() &&
12808 	    (anchor->number != 0))
12809 	    anchor->number += newlines;
12810 	anchor->line_num += newlines;
12811 	anchor = anchor->next;
12812     }
12813 
12814     /*
12815      * Update/rerender anchor [tags], if they are being numbered.
12816      *
12817      * [If a number tag (eg, "[177]") is itself broken across a line
12818      * boundary, this fixup only partially works.  While the tag
12819      * numbering is done properly across the pair of lines, the
12820      * horizontal positioning on *either* side of the split, can get
12821      * out of sync by a char or two when it gets selected.  See the
12822      * [comments] in increment_tagged_htline() for some more detail.
12823      *
12824      * I suppose THE fix is to prevent such tag-breaking in the first
12825      * place (dunno where yet, though).  Ah well ...  at least the tag
12826      * numbers themselves are correct from top to bottom now.
12827      *
12828      * All that said, about the only time this will be a problem in
12829      * *practice*, is when a page has near 1000 links or more (possibly
12830      * after a TEXTAREA expansion), and has line crossing tag(s), and
12831      * the tag numbers in a line crosser go from initially all 3 digit
12832      * numbers, to some mix of 3 and 4 digits (or all 4 digits) as a
12833      * result of the expansion process.  Oh, you also need a "clump" of
12834      * anchors all on the same lines.
12835      *
12836      * Yes, it *can* happen, but in real life, it probably won't be
12837      * seen very much ...]
12838      *
12839      * [This may also be an artifact of bumping into the right hand
12840      * screen edge (or RHS margin), since we don't even *think* about
12841      * relocating an anchor to the following line, when [tag] digits
12842      * expansion pushes things too far in that direction.]
12843      */
12844     if (fields_are_numbered()) {
12845 	anchor = start_anchor->next;
12846 	while (htline != FirstHTLine(HTMainText)) {
12847 
12848 	    while (anchor) {
12849 		if ((anchor->number - newlines) == start_tag)
12850 		    break;
12851 
12852 		/*** A HANG (infinite loop) *has* occurred here, with */
12853 		/*** the values of anchor and anchor->next being the  */
12854 		/*** the same, OR with anchor->number "magically" and */
12855 		/*** suddenly taking on an anchor-pointer-like value. */
12856 		/***                                                  */
12857 		/*** The same code and same doc have both passed and  */
12858 		/*** failed at different times, which indicates some  */
12859 		/*** sort of content/html dependency, or some kind of */
12860 		/*** a "race" condition, but I'll be damned if I can  */
12861 		/*** find it after tons of CTRACE's, printf()'s, gdb  */
12862 		/*** breakpoints and watchpoints, etc.                */
12863 		/***                                                  */
12864 		/*** I have added a hang detector (with error msg and */
12865 		/*** beep) here, to break the loop and warn the user, */
12866 		/*** until it can be isolated and fixed.              */
12867 		/***                                                  */
12868 		/*** [One UGLY intermittent .. gak ..!  02/22/99 KED] */
12869 
12870 		hang++;
12871 		if ((anchor == anchor->next) || (hang >= hang_detect))
12872 		    goto hang_detected;
12873 
12874 		anchor = anchor->next;
12875 	    }
12876 
12877 	    if (anchor) {
12878 		line_adj = increment_tagged_htline(htline, anchor, &lx,
12879 						   &start_tag, newlines,
12880 						   NOCHOP);
12881 		htline->size = (unsigned short) (htline->size + line_adj);
12882 		tag_adj += line_adj;
12883 
12884 	    } else {
12885 
12886 		break;		/* out of anchors ... we're done */
12887 	    }
12888 
12889 	    htline = htline->next;
12890 	}
12891     }
12892 
12893   finish:
12894     /*
12895      * Fixup various global variables.
12896      */
12897     nlinks += newlines;
12898     HTMainText->Lines += newlines;
12899     HTMainText->last_anchor_number += newlines;
12900 
12901     more_text = HText_canScrollDown();
12902 
12903     CTRACE((tfp, "GridText: TextAnchor and HTLine struct's adjusted\n"));
12904 
12905     return;
12906 
12907   hang_detected:		/* ugliness has happened; inform user and do the best we can */
12908 
12909     HTAlert(gettext("Hang Detect: TextAnchor struct corrupted - suggest aborting!"));
12910     goto finish;
12911 }
12912 
12913 /*
12914  * Check if the given anchor is a TEXTAREA belonging to the given form.
12915  *
12916  * KED's note -
12917  * [Finding the TEXTAREA we're actually *in* with these attributes isn't
12918  * foolproof.  The form number isn't unique to a given TEXTAREA, and there
12919  * *could* be TEXTAREA's with the same "name".  If that should ever be true,
12920  * we'll actually get the data from the *1st* TEXTAREA in the page that
12921  * matches.  We should probably assign a unique id to each TEXTAREA in a page,
12922  * and match on that, to avoid this (potential) problem.
12923  *
12924  * Since the odds of "false matches" *actually* happening in real life seem
12925  * rather small though, we'll hold off doing this, for a rainy day ...]
12926  */
IsFormsTextarea(FormInfo * form,TextAnchor * anchor_ptr)12927 static BOOLEAN IsFormsTextarea(FormInfo * form, TextAnchor *anchor_ptr)
12928 {
12929     return (BOOLEAN) ((anchor_ptr->link_type == INPUT_ANCHOR) &&
12930 		      (anchor_ptr->input_field->type == F_TEXTAREA_TYPE) &&
12931 		      (anchor_ptr->input_field->number == form->number) &&
12932 		      !strcmp(anchor_ptr->input_field->name, form->name));
12933 }
12934 
readEditedFile(char * ed_temp)12935 static char *readEditedFile(char *ed_temp)
12936 {
12937     struct stat stat_info;
12938     size_t size;
12939 
12940     FILE *fp;
12941 
12942     char *ebuf;
12943 
12944     CTRACE((tfp, "GridText: entered HText_EditTextArea()\n"));
12945 
12946     /*
12947      * Read back the edited temp file into our buffer.
12948      */
12949     if ((stat(ed_temp, &stat_info) < 0) ||
12950 	!S_ISREG(stat_info.st_mode) ||
12951 	((size = (size_t) stat_info.st_size) == 0)) {
12952 	size = 0;
12953 	ebuf = typecalloc(char);
12954 
12955 	if (!ebuf)
12956 	    outofmem(__FILE__, "HText_EditTextArea");
12957     } else {
12958 	ebuf = typecallocn(char, size + 1);
12959 
12960 	if (!ebuf) {
12961 	    /*
12962 	     * This could be huge - don't exit if we don't have enough
12963 	     * memory for it.  With some luck, the user may be even able
12964 	     * to recover the file manually from the temp space while
12965 	     * the lynx session is not over.  - kw
12966 	     */
12967 	    HTAlwaysAlert(NULL, MEMORY_EXHAUSTED_FILE);
12968 	    return 0;
12969 	}
12970 
12971 	if ((fp = fopen(ed_temp, "r")) != 0) {
12972 	    size = fread(ebuf, (size_t) 1, size, fp);
12973 	    LYCloseInput(fp);
12974 	    ebuf[size] = '\0';	/* Terminate! - kw */
12975 	} else {
12976 	    size = 0;
12977 	}
12978     }
12979 
12980     /*
12981      * Nuke any blank lines from the end of the edited data.
12982      */
12983     while ((size != 0)
12984 	   && (CanTrimTextArea(UCH(ebuf[size - 1])) || (ebuf[size - 1] == '\0')))
12985 	ebuf[--size] = '\0';
12986 
12987     return ebuf;
12988 }
12989 
finish_ExtEditForm(LinkInfo * form_link,TextAnchor * start_anchor,char * ed_temp,int orig_cnt)12990 static int finish_ExtEditForm(LinkInfo * form_link, TextAnchor *start_anchor,
12991 			      char *ed_temp,
12992 			      int orig_cnt)
12993 {
12994     TextAnchor *anchor_ptr;
12995     TextAnchor *end_anchor = NULL;
12996     BOOLEAN wrapalert = FALSE;
12997 
12998     int entry_line = form_link->anchor_line_num;
12999     int exit_line = 0;
13000     int line_cnt = 1;
13001 
13002     HTLine *htline = NULL;
13003 
13004     char *ebuf;
13005     char *line;
13006     char *lp;
13007     char *cp;
13008     int match_tag = 0;
13009     int newlines = 0;
13010     int len, len0;
13011     int display_size;
13012     int wanted_fieldlen_wrap = -1;	/* not yet asked; 0 means don't. */
13013     char *skip_at = NULL;
13014     int skip_num = 0, i;
13015     size_t line_used = MAX_LINE;
13016 
13017     CTRACE((tfp, "GridText: entered HText_EditTextArea()\n"));
13018 
13019     if ((ebuf = readEditedFile(ed_temp)) == 0) {
13020 	return 0;
13021     }
13022 
13023     /*
13024      * Copy each line from the temp file into the corresponding anchor
13025      * struct.  Add new lines to the TEXTAREA if needed.  (Always leave
13026      * the user with a blank line at the end of the TEXTAREA.)
13027      */
13028     if ((line = typeMallocn(char, line_used)) == 0)
13029 	  outofmem(__FILE__, "HText_EditTextArea");
13030 
13031     anchor_ptr = start_anchor;
13032     display_size = anchor_ptr->input_field->size;
13033     if (display_size <= 4 ||
13034 	display_size >= MAX_LINE)
13035 	wanted_fieldlen_wrap = 0;
13036 
13037     len = 0;
13038     lp = ebuf;
13039 
13040     while ((line_cnt <= orig_cnt) || (*lp) || ((len != 0) && (*lp == '\0'))) {
13041 
13042 	if (skip_at) {
13043 	    len0 = (int) (skip_at - lp);
13044 	    LYStrNCpy(line, lp, len0);
13045 	    lp = skip_at + skip_num;
13046 	    skip_at = NULL;
13047 	    skip_num = 0;
13048 
13049 	    assert(lp != NULL);
13050 	} else {
13051 	    len0 = 0;
13052 	}
13053 	line[len0] = '\0';
13054 
13055 	if ((cp = StrChr(lp, '\n')) != 0)
13056 	    len = (int) (cp - lp);
13057 	else
13058 	    len = (int) strlen(lp);
13059 
13060 	if (wanted_fieldlen_wrap < 0 &&
13061 	    !wrapalert &&
13062 	    len0 + len >= display_size &&
13063 	    (cp = StrChr(lp, ' ')) != NULL &&
13064 	    (cp - lp) < display_size - 1) {
13065 
13066 	    LYFixCursesOn("ask for confirmation:");
13067 	    LYerase();		/* don't show previous state */
13068 	    if (HTConfirmDefault(gettext("Wrap lines to fit displayed area?"),
13069 				 NO)) {
13070 		wanted_fieldlen_wrap = display_size - 1;
13071 	    } else {
13072 		wanted_fieldlen_wrap = 0;
13073 	    }
13074 	}
13075 
13076 	if (wanted_fieldlen_wrap > 0 &&
13077 	    len0 + len > wanted_fieldlen_wrap) {
13078 
13079 	    for (i = wanted_fieldlen_wrap - len0;
13080 		 i + len0 >= wanted_fieldlen_wrap / 4; i--) {
13081 
13082 		if (isspace(UCH(lp[i]))) {
13083 		    len = i + 1;
13084 		    cp = lp + i;
13085 		    if (cp[1] != '\n' &&
13086 			isspace(UCH(cp[1])) &&
13087 			!isspace(UCH(cp[2]))) {
13088 			len++;
13089 			cp++;
13090 		    }
13091 		    if (!isspace(UCH(cp[1]))) {
13092 			while (*cp && *cp != '\r' && *cp != '\n' &&
13093 			       (cp - lp) <= len + (3 * wanted_fieldlen_wrap / 4))
13094 			    cp++;	/* search for next line break */
13095 			if (*cp == '\r' && cp[1] == '\n')
13096 			    cp++;
13097 			if (*cp == '\n' &&
13098 			    (cp[1] == '\r' || cp[1] == '\n' ||
13099 			     !isspace(UCH(cp[1])))) {
13100 			    *cp = ' ';
13101 			    while (isspace(UCH(*(cp - 1)))) {
13102 				skip_num++;
13103 				cp--;
13104 			    }
13105 			    skip_at = cp;
13106 			}
13107 		    }
13108 		    break;
13109 		}
13110 	    }
13111 	}
13112 
13113 	if (wanted_fieldlen_wrap > 0 &&
13114 	    (len0 + len) > wanted_fieldlen_wrap) {
13115 
13116 	    i = len - 1;
13117 	    while (len0 + i + 1 > wanted_fieldlen_wrap &&
13118 		   isspace(UCH(lp[i])))
13119 		i--;
13120 	    if (len0 + i + 1 > wanted_fieldlen_wrap)
13121 		len = wanted_fieldlen_wrap - len0;
13122 	}
13123 
13124 	/*
13125 	 * Check if the new text will fit in the buffer.  HTML does not define
13126 	 * a "maxlength" attribute for TEXTAREA; its data can grow as needed.
13127 	 * Lynx will not adjust the display to reflect larger amounts of text;
13128 	 * it relies on the rows/cols attributes as well as the initial content
13129 	 * of the text area for the layout.
13130 	 */
13131 	if ((size_t) (len0 + len) >= line_used) {
13132 	    line_used = (size_t) (3 * (len0 + len)) / 2;
13133 	    if ((line = typeRealloc(char, line, line_used)) == 0)
13134 		  outofmem(__FILE__, "HText_EditTextArea");
13135 	}
13136 
13137 	strncat(line, lp, (size_t) len);
13138 	*(line + len0 + len) = '\0';
13139 
13140 	/*
13141 	 * If there are more lines in the edit buffer than were in the
13142 	 * original TEXTAREA, we need to add a new line/anchor, continuing
13143 	 * on until the edit buffer is empty.
13144 	 */
13145 	if (line_cnt > orig_cnt) {
13146 	    insert_new_textarea_anchor(&end_anchor, &htline);
13147 
13148 	    assert(end_anchor != NULL);
13149 	    assert(end_anchor->input_field != NULL);
13150 
13151 	    anchor_ptr = end_anchor;	/* make the new anchor current */
13152 	    newlines++;
13153 	}
13154 
13155 	assert(anchor_ptr != NULL);
13156 
13157 	/*
13158 	 * Finally copy the new line from the edit buffer into the anchor.
13159 	 */
13160 	StrAllocCopy(anchor_ptr->input_field->value, line);
13161 
13162 	/*
13163 	 * Keep track of 1st blank line in any trailing blank lines, for
13164 	 * later cursor repositioning.
13165 	 */
13166 	if (len0 + len > 0)
13167 	    exit_line = 0;
13168 	else if (exit_line == 0)
13169 	    exit_line = anchor_ptr->line_num;
13170 
13171 	/*
13172 	 * And do the next line of edited text, for the next anchor ...
13173 	 */
13174 	lp += len;
13175 	if (*lp && isspace(UCH(*lp)))
13176 	    lp++;
13177 
13178 	end_anchor = anchor_ptr;
13179 	anchor_ptr = anchor_ptr->next;
13180 
13181 	if (anchor_ptr)
13182 	    match_tag = anchor_ptr->number;
13183 
13184 	line_cnt++;
13185     }
13186 
13187     CTRACE((tfp, "GridText: edited text inserted into lynx struct's\n"));
13188 
13189     /*
13190      * If we've added any new lines/anchors, we need to adjust various
13191      * things in all anchor-bearing lines following the last newly added
13192      * line/anchor.  The fun stuff starts here ...
13193      */
13194     if (newlines > 0)
13195 	update_subsequent_anchors(newlines, end_anchor, htline, match_tag);
13196 
13197     /*
13198      * Cleanup time.
13199      */
13200     FREE(line);
13201     FREE(ebuf);
13202 
13203     /*
13204      * Return the offset needed to move the cursor from its current
13205      * (on entry) line number, to the 1st blank line of the trailing
13206      * (group of) blank line(s), which is where we want to be.  Let
13207      * the caller deal with moving us there, however ...  :-) ...
13208      */
13209     return (exit_line - entry_line);
13210 }
13211 
13212 /*
13213  * Transfer the initial contents of a TEXTAREA to a temp file, invoke the
13214  * user's editor on that file, then transfer the contents of the resultant
13215  * edited file back into the TEXTAREA (expanding the size of the area, if
13216  * required).
13217  *
13218  * Returns the number of lines that the cursor should be moved so that it
13219  * will end up on the 1st blank line of whatever number of trailing blank
13220  * lines there are in the TEXTAREA (there will *always* be at least one).
13221  *
13222  * --KED 02/01/99
13223  */
HText_EditTextArea(LinkInfo * form_link)13224 int HText_EditTextArea(LinkInfo * form_link)
13225 {
13226     char *ed_temp;
13227     FILE *fp;
13228 
13229     TextAnchor *anchor_ptr;
13230     TextAnchor *start_anchor = NULL;
13231     BOOLEAN firstanchor = TRUE;
13232 
13233     char ed_offset[DigitsOf(int) + 3];
13234     int start_line = 0;
13235     int entry_line = form_link->anchor_line_num;
13236     int orig_cnt = 0;
13237     int offset = 0;
13238 
13239     FormInfo *form = form_link->l_form;
13240 
13241     CTRACE((tfp, "GridText: entered HText_EditTextArea()\n"));
13242 
13243     if ((ed_temp = typeMallocn(char, LY_MAXPATH)) == 0) {
13244 	outofmem(__FILE__, "HText_EditTextArea");
13245     } else if ((fp = LYOpenTemp(ed_temp, "", "w")) != 0) {
13246 
13247 	/*
13248 	 * Begin at the beginning, to find 1st anchor in the TEXTAREA, then
13249 	 * write all of its lines (anchors) out to the edit temp file.
13250 	 */
13251 	anchor_ptr = HTMainText->first_anchor;
13252 
13253 	while (anchor_ptr) {
13254 
13255 	    if (IsFormsTextarea(form, anchor_ptr)) {
13256 
13257 		if (firstanchor) {
13258 		    firstanchor = FALSE;
13259 		    start_anchor = anchor_ptr;
13260 		    start_line = anchor_ptr->line_num;
13261 		}
13262 		orig_cnt++;
13263 
13264 		/*
13265 		 * Write the anchors' text to the temp edit file.
13266 		 */
13267 		fputs(anchor_ptr->input_field->value, fp);
13268 		fputc('\n', fp);
13269 
13270 	    } else {
13271 
13272 		if (!firstanchor)
13273 		    break;
13274 	    }
13275 	    anchor_ptr = anchor_ptr->next;
13276 	}
13277 	LYCloseTempFP(fp);
13278 
13279 	if (start_anchor != 0) {
13280 	    CTRACE((tfp, "GridText: TEXTAREA name=|%s| dumped to tempfile\n", form->name));
13281 	    CTRACE((tfp, "GridText: invoking editor (%s) on tempfile\n", editor));
13282 
13283 	    /*
13284 	     * Go edit the TEXTAREA temp file, with the initial editor line
13285 	     * corresponding to the TEXTAREA line the cursor is on (if such
13286 	     * positioning is supported by the editor [as lynx knows it]).
13287 	     */
13288 	    ed_offset[0] = 0;	/* pre-ANSI compilers don't initialize aggregates - TD */
13289 	    if (((entry_line - start_line) > 0) && editor_can_position())
13290 		sprintf(ed_offset, "%d", ((entry_line - start_line) + 1));
13291 
13292 	    edit_temporary_file(ed_temp, ed_offset, NULL);
13293 
13294 	    CTRACE((tfp, "GridText: returned from editor (%s)\n", editor));
13295 
13296 	    if (!form->disabled)
13297 		offset = finish_ExtEditForm(form_link, start_anchor, ed_temp, orig_cnt);
13298 
13299 	    CTRACE((tfp, "GridText: exiting HText_EditTextArea()\n"));
13300 	}
13301 	(void) LYRemoveTemp(ed_temp);
13302 	FREE(ed_temp);
13303     }
13304 
13305     /*
13306      * Return the offset needed to move the cursor from its current
13307      * (on entry) line number, to the 1st blank line of the trailing
13308      * (group of) blank line(s), which is where we want to be.  Let
13309      * the caller deal with moving us there, however ...  :-) ...
13310      */
13311     return offset;
13312 }
13313 
13314 /*
13315  * Similar to HText_EditTextArea, but assume a single-line text field -TD
13316  */
HText_EditTextField(LinkInfo * form_link)13317 void HText_EditTextField(LinkInfo * form_link)
13318 {
13319     char *ed_temp;
13320     FILE *fp;
13321 
13322     FormInfo *form = form_link->l_form;
13323 
13324     CTRACE((tfp, "GridText: entered HText_EditTextField()\n"));
13325 
13326     ed_temp = typeMallocn(char, LY_MAXPATH);
13327 
13328     if ((fp = LYOpenTemp(ed_temp, "", "w")) == 0) {
13329 	FREE(ed_temp);
13330 	return;
13331     }
13332 
13333     /*
13334      * Write the anchors' text to the temp edit file.
13335      */
13336     fputs(form->value, fp);
13337     fputc('\n', fp);
13338 
13339     LYCloseTempFP(fp);
13340 
13341     CTRACE((tfp, "GridText: text field |%s| dumped to tempfile\n", form_link->lname));
13342     CTRACE((tfp, "GridText: invoking editor (%s) on tempfile\n", editor));
13343 
13344     edit_temporary_file(ed_temp, "", NULL);
13345 
13346     CTRACE((tfp, "GridText: returned from editor (%s)\n", editor));
13347 
13348     if (!form->disabled) {
13349 	char *ebuf;
13350 	char *p;
13351 
13352 	if ((ebuf = readEditedFile(ed_temp)) != 0) {
13353 	    /*
13354 	     * Only use the first line of the result.
13355 	     */
13356 	    for (p = ebuf; *p != '\0'; ++p) {
13357 		if (*p == '\n' || *p == '\r') {
13358 		    *p = '\0';
13359 		    break;
13360 		}
13361 	    }
13362 	    StrAllocCopy(form->value, ebuf);
13363 	    FREE(ebuf);
13364 	}
13365     }
13366 
13367     (void) LYRemoveTemp(ed_temp);
13368     FREE(ed_temp);
13369 
13370     CTRACE((tfp, "GridText: exiting HText_EditTextField()\n"));
13371 }
13372 
13373 /*
13374  * Expand the size of a TEXTAREA by a fixed number of lines (as specified
13375  * by arg2).
13376  *
13377  * --KED 02/14/99
13378  */
HText_ExpandTextarea(LinkInfo * form_link,int newlines)13379 void HText_ExpandTextarea(LinkInfo * form_link, int newlines)
13380 {
13381     TextAnchor *anchor_ptr;
13382     TextAnchor *end_anchor = NULL;
13383     BOOLEAN firstanchor = TRUE;
13384 
13385     FormInfo *form = form_link->l_form;
13386 
13387     HTLine *htline = NULL;
13388 
13389     int match_tag = 0;
13390     int i;
13391 
13392     CTRACE((tfp, "GridText: entered HText_ExpandTextarea()\n"));
13393 
13394     if (newlines < 1)
13395 	return;
13396 
13397     /*
13398      * Begin at the beginning, to find the TEXTAREA, then on to find
13399      * the last line (anchor) in it.
13400      */
13401     anchor_ptr = HTMainText->first_anchor;
13402 
13403     while (anchor_ptr) {
13404 
13405 	if (IsFormsTextarea(form, anchor_ptr)) {
13406 
13407 	    if (firstanchor)
13408 		firstanchor = FALSE;
13409 
13410 	    end_anchor = anchor_ptr;
13411 
13412 	} else {
13413 
13414 	    if (!firstanchor)
13415 		break;
13416 	}
13417 	anchor_ptr = anchor_ptr->next;
13418     }
13419 
13420     for (i = 1; i <= newlines; i++) {
13421 	insert_new_textarea_anchor(&end_anchor, &htline);
13422 
13423 	/*
13424 	 * Make the new line blank.
13425 	 */
13426 	StrAllocCopy(end_anchor->input_field->value, "");
13427 
13428 	/*
13429 	 * And go add another line ...
13430 	 */
13431 	if (end_anchor->next)
13432 	    match_tag = end_anchor->next->number;
13433     }
13434 
13435     CTRACE((tfp, "GridText: %d blank line(s) added to TEXTAREA name=|%s|\n",
13436 	    newlines, form->name));
13437 
13438     /*
13439      * We need to adjust various things in all anchor bearing lines
13440      * following the last newly added line/anchor.  Fun stuff.
13441      */
13442     update_subsequent_anchors(newlines, end_anchor, htline, match_tag);
13443 
13444     CTRACE((tfp, "GridText: exiting HText_ExpandTextarea()\n"));
13445 
13446     return;
13447 }
13448 
13449 /*
13450  * Insert the contents of a file into a TEXTAREA between the cursor line,
13451  * and the line preceding it.
13452  *
13453  * Returns the number of lines that the cursor should be moved so that it
13454  * will end up on the 1st line in the TEXTAREA following the inserted file
13455  * (if we decide to do that).
13456  *
13457  * --KED 02/21/99
13458  */
HText_InsertFile(LinkInfo * form_link)13459 int HText_InsertFile(LinkInfo * form_link)
13460 {
13461     struct stat stat_info;
13462     size_t size;
13463 
13464     FILE *fp;
13465     char *fn;
13466 
13467     TextAnchor *anchor_ptr;
13468     TextAnchor *prev_anchor = NULL;
13469     TextAnchor *end_anchor = NULL;
13470     BOOLEAN firstanchor = TRUE;
13471     BOOLEAN truncalert = FALSE;
13472 
13473     FormInfo *form = form_link->l_form;
13474 
13475     HTLine *htline = NULL;
13476 
13477     TextAnchor *a = 0;
13478     FormInfo *f = 0;
13479     HTLine *l = 0;
13480 
13481     char *fbuf = 0;
13482     char *line = 0;
13483     char *lp;
13484     char *cp;
13485     int entry_line = form_link->anchor_line_num;
13486     int file_cs;
13487     int match_tag = 0;
13488     int newlines = 0;
13489     int len;
13490     int i;
13491 
13492     CTRACE((tfp, "GridText: entered HText_InsertFile()\n"));
13493 
13494     /*
13495      * Get the filename of the insert file.
13496      */
13497     if (!(fn = GetFileName())) {
13498 	HTInfoMsg(FILE_INSERT_CANCELLED);
13499 	CTRACE((tfp,
13500 		"GridText: file insert cancelled - no filename provided\n"));
13501 	return (0);
13502     }
13503     if (no_dotfiles || !show_dotfiles) {
13504 	if (*LYPathLeaf(fn) == '.') {
13505 	    HTUserMsg(FILENAME_CANNOT_BE_DOT);
13506 	    return (0);
13507 	}
13508     }
13509 
13510     /*
13511      * Read it into our buffer (abort on 0-length file).
13512      */
13513     if ((stat(fn, &stat_info) < 0) ||
13514 	((size = (size_t) stat_info.st_size) == 0)) {
13515 	HTInfoMsg(FILE_INSERT_0_LENGTH);
13516 	CTRACE((tfp,
13517 		"GridText: file insert aborted - file=|%s|- was 0-length\n",
13518 		fn));
13519 	FREE(fn);
13520 	return (0);
13521 
13522     } else {
13523 
13524 	if ((fbuf = typecallocn(char, size + 1)) == NULL) {
13525 	    /*
13526 	     * This could be huge - don't exit if we don't have enough
13527 	     * memory for it.  - kw
13528 	     */
13529 	    free(fn);
13530 	    HTAlert(MEMORY_EXHAUSTED_FILE);
13531 	    return 0;
13532 	}
13533 
13534 	/* Try to make the same assumption for the charset of the inserted
13535 	 * file as we would for normal loading of that file, i.e. taking
13536 	 * assume_local_charset and suffix mappings into account.
13537 	 * If there is a mismatch with the display character set, characters
13538 	 * may be displayed wrong, too bad; but the user has a chance to
13539 	 * correct this by editing the lines, which will update f->value_cs
13540 	 * again. - kw
13541 	 */
13542 	LYGetFileInfo(fn, 0, 0, 0, 0, 0, &file_cs);
13543 
13544 	fp = fopen(fn, "r");
13545 	if (!fp) {
13546 	    free(fbuf);
13547 	    free(fn);
13548 	    HTAlert(FILE_CANNOT_OPEN_R);
13549 	    return 0;
13550 	}
13551 	size = fread(fbuf, (size_t) 1, size, fp);
13552 	LYCloseInput(fp);
13553 	FREE(fn);
13554 	fbuf[size] = '\0';	/* Terminate! - kw */
13555     }
13556 
13557     /*
13558      * Begin at the beginning, to find the TEXTAREA we're in, then
13559      * the current cursorline.
13560      */
13561     anchor_ptr = HTMainText->first_anchor;
13562 
13563     while (anchor_ptr) {
13564 
13565 	if (IsFormsTextarea(form, anchor_ptr)) {
13566 	    if (anchor_ptr->line_num == entry_line)
13567 		break;
13568 	}
13569 	prev_anchor = anchor_ptr;
13570 	anchor_ptr = anchor_ptr->next;
13571     }
13572 
13573     if (anchor_ptr == NULL) {
13574 	CTRACE((tfp, "BUG: could not find anchor for TEXTAREA.\n"));
13575 	FREE(line);
13576 	FREE(fbuf);
13577 	return 0;
13578     }
13579 
13580     /*
13581      * Clone a new TEXTAREA line/anchor using the cursorline anchor as
13582      * a template, but link it in BEFORE the cursorline anchor/htline.
13583      *
13584      * [We can probably combine this with insert_new_textarea_anchor()
13585      * along with a flag to indicate "insert before" as we do here,
13586      * or the "normal" mode of operation (add after "current" anchor/
13587      * line).  Beware of the differences ...  some are a bit subtle to
13588      * notice.]
13589      */
13590     for (htline = FirstHTLine(HTMainText), i = 0;
13591 	 anchor_ptr->line_num != i; i++) {
13592 	htline = htline->next;
13593 	if (htline == HTMainText->last_line)
13594 	    break;
13595     }
13596 
13597     allocHTLine(l, MAX_LINE);
13598     POOLtypecalloc(TextAnchor, a);
13599 
13600     POOLtypecalloc(FormInfo, f);
13601     if (a == NULL || l == NULL || f == NULL)
13602 	outofmem(__FILE__, "HText_InsertFile");
13603 
13604     /*  Init all the fields in the new TextAnchor.                 */
13605     /*  [anything "special" needed based on ->show_anchor value ?] */
13606     /* *INDENT-EQLS* */
13607     a->next        = anchor_ptr;
13608     a->number      = anchor_ptr->number;
13609     a->show_number = anchor_ptr->show_number;
13610     a->line_pos    = anchor_ptr->line_pos;
13611     a->extent      = anchor_ptr->extent;
13612     a->sgml_offset = SGML_offset();
13613     a->line_num    = anchor_ptr->line_num;
13614     LYCopyHiText(a, anchor_ptr);
13615     a->link_type   = anchor_ptr->link_type;
13616     a->input_field = f;
13617     a->show_anchor = anchor_ptr->show_anchor;
13618     a->inUnderline = anchor_ptr->inUnderline;
13619     a->expansion_anch = TRUE;
13620     a->anchor      = NULL;
13621 
13622     /*  Just the (seemingly) relevant fields in the new FormInfo.  */
13623     /*  [do we need to do anything "special" based on ->disabled]  */
13624     StrAllocCopy(f->name, anchor_ptr->input_field->name);
13625     f->number = anchor_ptr->input_field->number;
13626     f->type = anchor_ptr->input_field->type;
13627     StrAllocCopy(f->orig_value, "");
13628     f->size = anchor_ptr->input_field->size;
13629     f->maxlength = anchor_ptr->input_field->maxlength;
13630     f->no_cache = anchor_ptr->input_field->no_cache;
13631     f->disabled = anchor_ptr->input_field->disabled;
13632     f->readonly = anchor_ptr->input_field->readonly;
13633     f->value_cs = (file_cs >= 0) ? file_cs : current_char_set;
13634 
13635     /*  Init all the fields in the new HTLine (but see the #if).   */
13636     l->offset = htline->offset;
13637     l->size = htline->size;
13638 #if defined(USE_COLOR_STYLE)
13639     /* dup styles[] if needed [no need in TEXTAREA (?); leave 0's] */
13640     l->numstyles = htline->numstyles;
13641     /*we fork the pointers! */
13642     l->styles = htline->styles;
13643 #endif
13644     strcpy(l->data, htline->data);
13645 
13646     /*
13647      * If we're at the head of the TextAnchor list, the new node becomes
13648      * the first node.
13649      */
13650     if (anchor_ptr == HTMainText->first_anchor)
13651 	HTMainText->first_anchor = a;
13652 
13653     /*
13654      * Link in the new TextAnchor, and corresponding HTLine.
13655      */
13656     if (prev_anchor)
13657 	prev_anchor->next = a;
13658 
13659     htline = htline->prev;
13660     l->next = htline->next;
13661     l->prev = htline;
13662     htline->next->prev = l;
13663     htline->next = l;
13664 
13665     /*
13666      * update_subsequent_anchors() expects htline to point to 1st potential
13667      * line needing fixup; we need to do this just in case the inserted file
13668      * was only a single line (yes, it's pathological ...  ).
13669      */
13670     htline = htline->next;	/* ->new (current) htline, for 1st inserted line  */
13671     htline = htline->next;	/* ->1st potential (following) [tag] fixup htline */
13672 
13673     anchor_ptr = a;
13674     newlines++;
13675 
13676     /*
13677      * Copy each line from the insert file into the corresponding anchor
13678      * struct.
13679      *
13680      * Begin with the new line/anchor we just added (above the cursorline).
13681      */
13682     if ((line = typeMallocn(char, MAX_LINE)) == 0)
13683 	  outofmem(__FILE__, "HText_InsertFile");
13684 
13685     match_tag = anchor_ptr->number;
13686 
13687     lp = fbuf;
13688 
13689     while (*lp) {
13690 
13691 	if ((cp = StrChr(lp, '\n')) != 0)
13692 	    len = (int) (cp - lp);
13693 	else
13694 	    len = (int) strlen(lp);
13695 
13696 	if (len >= MAX_LINE) {
13697 	    if (!truncalert) {
13698 		HTAlert(gettext("Very long lines have been truncated!"));
13699 		truncalert = TRUE;
13700 	    }
13701 	    len = MAX_LINE - 1;
13702 	    if (lp[len])
13703 		lp[len + 1] = '\0';	/* prevent next iteration */
13704 	}
13705 	LYStrNCpy(line, lp, len);
13706 
13707 	/*
13708 	 * If not the first line from the insert file, we need to add
13709 	 * a new line/anchor, continuing on until the buffer is empty.
13710 	 */
13711 	if (!firstanchor) {
13712 	    insert_new_textarea_anchor(&end_anchor, &htline);
13713 	    anchor_ptr = end_anchor;	/* make the new anchor current */
13714 	    newlines++;
13715 	}
13716 
13717 	/*
13718 	 * Copy the new line from the buffer into the anchor.
13719 	 */
13720 	StrAllocCopy(anchor_ptr->input_field->value, line);
13721 
13722 	/*
13723 	 * insert_new_textarea_anchor always uses current_char_set,
13724 	 * we may want something else, so fix it up.  - kw
13725 	 */
13726 	if (file_cs >= 0)
13727 	    anchor_ptr->input_field->value_cs = file_cs;
13728 
13729 	/*
13730 	 * And do the next line of insert text, for the next anchor ...
13731 	 */
13732 	lp += len;
13733 	if (*lp)
13734 	    lp++;
13735 
13736 	firstanchor = FALSE;
13737 	end_anchor = anchor_ptr;
13738 	anchor_ptr = anchor_ptr->next;
13739     }
13740 
13741     CTRACE((tfp, "GridText: file inserted into lynx struct's\n"));
13742 
13743     /*
13744      * Now adjust various things in all anchor-bearing lines following the
13745      * last newly added line/anchor.  Some say this is the fun part ...
13746      */
13747     update_subsequent_anchors(newlines, end_anchor, htline, match_tag);
13748 
13749     /*
13750      * Cleanup time.
13751      */
13752     FREE(line);
13753     FREE(fbuf);
13754 
13755     CTRACE((tfp, "GridText: exiting HText_InsertFile()\n"));
13756 
13757     return (newlines);
13758 }
13759 
13760 #ifdef USE_COLOR_STYLE
GetColumn(void)13761 static int GetColumn(void)
13762 {
13763     int result;
13764 
13765 #ifdef USE_SLANG
13766     result = SLsmg_get_column();
13767 #else
13768     int y, x;
13769 
13770     LYGetYX(y, x);
13771     result = x;
13772     (void) y;
13773 #endif
13774     return result;
13775 }
13776 
DidWrap(int y0,int x0)13777 static BOOL DidWrap(int y0, int x0)
13778 {
13779     BOOL result = NO;
13780 
13781 #ifndef USE_SLANG
13782     int y, x;
13783 
13784     LYGetYX(y, x);
13785     (void) x0;
13786     if (x >= DISPLAY_COLS || ((x == 0) && (y != y0)))
13787 	result = YES;
13788 #endif
13789     return result;
13790 }
13791 #endif /* USE_COLOR_STYLE */
13792 
13793 /*
13794  * This function draws the part of line 'line', pointed by 'str' (which can be
13795  * non terminated with null - i.e., is line->data+N) drawing 'len' bytes (not
13796  * characters) of it.  It doesn't check whether the 'len' bytes crosses a
13797  * character boundary (if multibyte chars are in string).  Assumes that the
13798  * cursor is positioned in the place where the 1st char of string should be
13799  * drawn.
13800  *
13801  * This code is based on display_line.  This code was tested with ncurses only
13802  * (since no support for lss is availble for Slang) -HV.
13803  */
13804 #ifdef USE_COLOR_STYLE
redraw_part_of_line(HTLine * line,const char * str,int len,HText * text)13805 static void redraw_part_of_line(HTLine *line, const char *str,
13806 				int len,
13807 				HText *text)
13808 {
13809     register int i;
13810     char buffer[7];
13811     const char *data, *end_of_data;
13812     size_t utf_extra = 0;
13813 
13814 #ifdef USE_COLOR_STYLE
13815     int current_style = 0;
13816     int tcols, scols;
13817 #endif
13818     char LastDisplayChar = ' ';
13819     int YP, XP;
13820 
13821     LYGetYX(YP, XP);
13822 
13823     i = XP;
13824 
13825     /* Set up the multibyte character buffer  */
13826     buffer[0] = buffer[1] = buffer[2] = '\0';
13827 
13828     data = str;
13829     end_of_data = data + len;
13830     i++;
13831 
13832     /* this assumes that the part of line to be drawn fits in the screen */
13833     while (data < end_of_data) {
13834 	buffer[0] = *data;
13835 	data++;
13836 
13837 #if defined(USE_COLOR_STYLE)
13838 #define CStyle line->styles[current_style]
13839 
13840 	tcols = GetColumn();
13841 	scols = StyleToCols(text, line, current_style);
13842 
13843 	while (current_style < line->numstyles &&
13844 	       tcols >= scols) {
13845 	    LynxChangeStyle(CStyle.sc_style, CStyle.sc_direction);
13846 	    current_style++;
13847 	    scols = StyleToCols(text, line, current_style);
13848 	}
13849 #endif
13850 	switch (buffer[0]) {
13851 
13852 #ifndef USE_COLOR_STYLE
13853 	case LY_UNDERLINE_START_CHAR:
13854 	    if (dump_output_immediately && use_underscore) {
13855 		LYaddch('_');
13856 		i++;
13857 	    } else {
13858 		lynx_start_underline();
13859 	    }
13860 	    break;
13861 
13862 	case LY_UNDERLINE_END_CHAR:
13863 	    if (dump_output_immediately && use_underscore) {
13864 		LYaddch('_');
13865 		i++;
13866 	    } else {
13867 		lynx_stop_underline();
13868 	    }
13869 	    break;
13870 
13871 	case LY_BOLD_START_CHAR:
13872 	    lynx_start_bold();
13873 	    break;
13874 
13875 	case LY_BOLD_END_CHAR:
13876 	    lynx_stop_bold();
13877 	    break;
13878 
13879 #endif
13880 	case LY_SOFT_NEWLINE:
13881 	    if (!dump_output_immediately) {
13882 		LYaddch('+');
13883 		i++;
13884 	    }
13885 	    break;
13886 
13887 	case LY_SOFT_HYPHEN:
13888 	    if (*data != '\0' ||
13889 		isspace(UCH(LastDisplayChar)) ||
13890 		LastDisplayChar == '-') {
13891 		/*
13892 		 * Ignore the soft hyphen if it is not the last character in
13893 		 * the line.  Also ignore it if it first character following
13894 		 * the margin, or if it is preceded by a white character (we
13895 		 * loaded 'M' into LastDisplayChar if it was a multibyte
13896 		 * character) or hyphen, though it should have been excluded by
13897 		 * HText_appendCharacter() or by split_line() in those cases.
13898 		 * -FM
13899 		 */
13900 		break;
13901 	    } else {
13902 		/*
13903 		 * Make it a hard hyphen and fall through.  -FM
13904 		 */
13905 		buffer[0] = '-';
13906 	    }
13907 	    /* FALLTHRU */
13908 
13909 	default:
13910 	    if (text->T.output_utf8 && is8bits(buffer[0])) {
13911 		utf_extra = utf8_length(text->T.output_utf8, data - 1);
13912 		LastDisplayChar = 'M';
13913 	    }
13914 	    if (utf_extra) {
13915 		LYStrNCpy(&buffer[1], data, utf_extra);
13916 		LYaddstr(buffer);
13917 		buffer[1] = '\0';
13918 		data += utf_extra;
13919 		utf_extra = 0;
13920 	    } else if (is_CJK2(buffer[0])) {
13921 		/*
13922 		 * For CJK strings, by Masanobu Kimura.
13923 		 */
13924 		if (i <= DISPLAY_COLS) {
13925 		    buffer[1] = *data;
13926 		    buffer[2] = '\0';
13927 		    data++;
13928 		    i++;
13929 		    LYaddstr(buffer);
13930 		    buffer[1] = '\0';
13931 		    /*
13932 		     * For now, load 'M' into LastDisplayChar, but we should
13933 		     * check whether it's white and if so, use ' '.  I don't
13934 		     * know if there actually are white CJK characters, and
13935 		     * we're loading ' ' for multibyte spacing characters in
13936 		     * this code set, but this will become an issue when the
13937 		     * development code set's multibyte character handling is
13938 		     * used.  -FM
13939 		     */
13940 		    LastDisplayChar = 'M';
13941 		}
13942 	    } else {
13943 		LYaddstr(buffer);
13944 		LastDisplayChar = buffer[0];
13945 	    }
13946 	    if (DidWrap(YP, XP))
13947 		break;
13948 	    i++;
13949 	}			/* end of switch */
13950     }				/* end of while */
13951 
13952 #ifndef USE_COLOR_STYLE
13953     lynx_stop_underline();
13954     lynx_stop_bold();
13955 #else
13956 
13957     while (current_style < line->numstyles) {
13958 	LynxChangeStyle(CStyle.sc_style, CStyle.sc_direction);
13959 	current_style++;
13960     }
13961 
13962 #undef CStyle
13963 #endif
13964     return;
13965 }
13966 #endif /* USE_COLOR_STYLE */
13967 
13968 #ifndef USE_COLOR_STYLE
13969 /*
13970  * Function move_to_glyph is called from LYMoveToLink and does all
13971  * the real work for it.
13972  * The pair LYMoveToLink()/move_to_glyph() is similar to the pair
13973  * redraw_lines_of_link()/redraw_part_of_line(), some key differences:
13974  * LYMoveToLink/move_to_glyph redraw_*
13975  * -----------------------------------------------------------------
13976  * - used without color style           - used with color style
13977  * - handles showing WHEREIS target     - WHEREIS handled elsewhere
13978  * - handles only one line              - handles first two lines for
13979  *                                        hypertext anchors
13980  * - right columns position for UTF-8
13981  *   by redrawing as necessary
13982  * - currently used for highlight       - currently used for highlight
13983  *   ON and OFF                         OFF
13984  *
13985  * Eventually the two sets of function should be unified, and should handle
13986  * UTF-8 positioning, both lines of hypertext anchors, and WHEREIS in all
13987  * cases.  If possible.  The complex WHEREIS target logic in LYhighlight()
13988  * could then be completely removed.  - kw
13989  */
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)13990 static void move_to_glyph(int YP,
13991 			  int XP,
13992 			  int XP_draw_min,
13993 			  const char *data,
13994 			  int datasize,
13995 			  unsigned offset,
13996 			  const char *target,
13997 			  const char *hightext,
13998 			  int flags,
13999 			  int utf_flag)
14000 {
14001     char buffer[7];
14002     const char *end_of_data;
14003     size_t utf_extra = 0;
14004 
14005 #if defined(SHOW_WHEREIS_TARGETS)
14006     const char *cp_tgt;
14007     int i_start_tgt = 0, i_after_tgt;
14008     int HitOffset, LenNeeded;
14009 #endif /* SHOW_WHEREIS_TARGETS */
14010     BOOL intarget = NO;
14011     BOOL inunderline = NO;
14012     BOOL inbold = NO;
14013     BOOL drawing = NO;
14014     BOOL inU = NO;
14015     BOOL hadutf8 = NO;
14016     BOOL incurlink = NO;
14017     BOOL drawingtarget = NO;
14018     BOOL flag = NO;
14019     const char *sdata = data;
14020     char LastDisplayChar = ' ';
14021 
14022     int i = (int) offset;	/* FIXME: should be columns, not offset? */
14023     int last_i = DISPLAY_COLS;
14024     int XP_link = XP;		/* column of link */
14025     int XP_next = XP;		/* column to move to when done drawing */
14026     int linkvlen;
14027 
14028     int len;
14029 
14030     if (no_title)
14031 	YP -= TITLE_LINES;
14032 
14033     if (flags & 1)
14034 	flag = YES;
14035     if (flags & 2)
14036 	inU = YES;
14037     /* Set up the multibyte character buffer  */
14038     buffer[0] = buffer[1] = buffer[2] = '\0';
14039     /*
14040      * Add offset, making sure that we do not
14041      * go over the COLS limit on the display.
14042      */
14043     if (hightext != 0) {
14044 #ifdef WIDEC_CURSES
14045 	last_i = i + LYstrExtent2(data, datasize);
14046 #endif
14047 	linkvlen = LYmbcsstrlen(hightext, utf_flag, YES);
14048     } else {
14049 	linkvlen = 0;
14050     }
14051     if (i >= last_i)
14052 	i = last_i - 1;
14053 
14054     /*
14055      * Scan through the data, making sure that we do not
14056      * go over the COLS limit on the display etc.
14057      */
14058     len = datasize;
14059     end_of_data = data + len;
14060 
14061 #if defined(SHOW_WHEREIS_TARGETS)
14062     /*
14063      * If the target overlaps with the part of this line that
14064      * we are drawing, it will be emphasized.
14065      */
14066     i_after_tgt = i;
14067     if (target) {
14068 	cp_tgt = LYno_attr_mb_strstr(sdata,
14069 				     target,
14070 				     utf_flag, YES,
14071 				     &HitOffset,
14072 				     &LenNeeded);
14073 	if (cp_tgt) {
14074 	    if ((int) offset + LenNeeded > last_i ||
14075 		((int) offset + HitOffset >= XP + linkvlen)) {
14076 		cp_tgt = NULL;
14077 	    } else {
14078 		i_start_tgt = i + HitOffset;
14079 		i_after_tgt = i + LenNeeded;
14080 	    }
14081 	}
14082     } else {
14083 	cp_tgt = NULL;
14084     }
14085 #endif /* SHOW_WHEREIS_TARGETS */
14086 
14087     /*
14088      * Iterate through the line data from the start, keeping track of
14089      * the display ("glyph") position in i.  Drawing will be turned
14090      * on when either the first UTF-8 sequence (that occurs after
14091      * XP_draw_min) is found, or when we reach the link itself (if
14092      * highlight is non-NULL).  - kw
14093      */
14094     while ((i <= last_i) && data < end_of_data && (*data != '\0')) {
14095 
14096 	if (hightext && i >= XP && !incurlink) {
14097 
14098 	    /*
14099 	     * We reached the position of link itself, and hightext is
14100 	     * non-NULL.  We switch data from being a pointer into the HTLine
14101 	     * to being a pointer into hightext.  Normally (as long as this
14102 	     * routine is applied to normal hyperlink anchors) the text in
14103 	     * hightext will be identical to that part of the HTLine that
14104 	     * data was already pointing to, except that special attribute
14105 	     * chars LY_BOLD_START_CHAR etc., have been stripped out (see
14106 	     * HText_trimHightext).  So the switching should not result in
14107 	     * any different display, but it ensures that it doesn't go
14108 	     * unnoticed if somehow hightext got messed up somewhere else.
14109 	     * This is also useful in preparation for using this function
14110 	     * for something else than normal hyperlink anchors, i.e., form
14111 	     * fields.
14112 	     * Turn on drawing here or make sure it gets turned on before the
14113 	     * next actual normal character is handled.  - kw
14114 	     */
14115 	    data = hightext;
14116 	    len = (int) strlen(hightext);
14117 	    end_of_data = hightext + len;
14118 	    last_i = i + len;
14119 	    XP_next += linkvlen;
14120 	    incurlink = YES;
14121 #ifdef SHOW_WHEREIS_TARGETS
14122 	    if (cp_tgt) {
14123 		if (flag && i_after_tgt >= XP)
14124 		    i_after_tgt = XP - 1;
14125 	    }
14126 #endif
14127 	    /*
14128 	     * The logic of where to set in-target drawing target etc.
14129 	     * and when to react to it should be cleaned up (here and
14130 	     * further below).  For now this seems to work but isn't
14131 	     * very clear.  The complications arise from reproducing
14132 	     * the behavior (previously done in LYhighlight()) for target
14133 	     * strings that fall into or overlap a link:  use target
14134 	     * emphasis for the target string, except for the first
14135 	     * and last character of the anchor text if the anchor is
14136 	     * highlighted as "current link".  - kw
14137 	     */
14138 	    if (!drawing) {
14139 #ifdef SHOW_WHEREIS_TARGETS
14140 		if (intarget) {
14141 		    if (i_after_tgt > i) {
14142 			LYmove(YP, i);
14143 			if (flag) {
14144 			    drawing = YES;
14145 			    drawingtarget = NO;
14146 			    if (inunderline)
14147 				inU = YES;
14148 			    lynx_start_link_color(flag, inU);
14149 			} else {
14150 			    drawing = YES;
14151 			    drawingtarget = YES;
14152 			    LYstartTargetEmphasis();
14153 			}
14154 		    }
14155 		}
14156 #endif /* SHOW_WHEREIS_TARGETS */
14157 	    } else {
14158 #ifdef SHOW_WHEREIS_TARGETS
14159 		if (intarget && i_after_tgt > i) {
14160 		    if (flag && (data == hightext)) {
14161 			drawingtarget = NO;
14162 			LYstopTargetEmphasis();
14163 		    }
14164 		} else if (!intarget)
14165 #endif /* SHOW_WHEREIS_TARGETS */
14166 		{
14167 		    if (inunderline)
14168 			inU = YES;
14169 		    if (inunderline)
14170 			lynx_stop_underline();
14171 		    if (inbold)
14172 			lynx_stop_bold();
14173 		    lynx_start_link_color(flag, inU);
14174 		}
14175 
14176 	    }
14177 	}
14178 	if (i >= last_i || data >= end_of_data)
14179 	    break;
14180 	if ((buffer[0] = *data) == '\0')
14181 	    break;
14182 #if defined(SHOW_WHEREIS_TARGETS)
14183 	/*
14184 	 * Look for a subsequent occurrence of the target string,
14185 	 * if we had a previous one and have now stepped past it.  - kw
14186 	 */
14187 	if (cp_tgt && i >= i_after_tgt) {
14188 	    if (intarget) {
14189 
14190 		if (incurlink && flag && i == last_i - 1)
14191 		    cp_tgt = NULL;
14192 		else
14193 		    cp_tgt = LYno_attr_mb_strstr(sdata,
14194 						 target,
14195 						 utf_flag, YES,
14196 						 &HitOffset,
14197 						 &LenNeeded);
14198 		if (cp_tgt) {
14199 		    i_start_tgt = i + HitOffset;
14200 		    i_after_tgt = i + LenNeeded;
14201 		    if (incurlink) {
14202 			if (flag && i_start_tgt == XP_link)
14203 			    i_start_tgt++;
14204 			if (flag && i_start_tgt == last_i - 1)
14205 			    i_start_tgt++;
14206 			if (flag && i_after_tgt >= last_i)
14207 			    i_after_tgt = last_i - 1;
14208 			if (flag && i_start_tgt >= last_i)
14209 			    cp_tgt = NULL;
14210 		    } else if (i_start_tgt == last_i) {
14211 			if (flag)
14212 			    i_start_tgt++;
14213 		    }
14214 		}
14215 		if (!cp_tgt || i_start_tgt != i) {
14216 		    intarget = NO;
14217 		    if (drawing) {
14218 			if (drawingtarget) {
14219 			    drawingtarget = NO;
14220 			    LYstopTargetEmphasis();
14221 			    if (incurlink) {
14222 				lynx_start_link_color(flag, inU);
14223 			    }
14224 			}
14225 			if (!incurlink) {
14226 			    if (inbold)
14227 				lynx_start_bold();
14228 			    if (inunderline)
14229 				lynx_start_underline();
14230 			}
14231 		    }
14232 		}
14233 	    }
14234 	}
14235 #endif /* SHOW_WHEREIS_TARGETS */
14236 
14237 	/*
14238 	 * Advance data to point to the next input char (for the
14239 	 * next round).  Advance sdata, used for searching for a
14240 	 * target string, so that they stay in synch.  As long
14241 	 * as we are not within the highlight text, data and sdata
14242 	 * have identical values.  After we have switched data to
14243 	 * point into hightext, sdata remains a pointer into the
14244 	 * HTLine (so that we don't miss a partial target match at
14245 	 * the end of the anchor text).  So sdata has to sometimes
14246 	 * skip additional special attribute characters that are
14247 	 * not present in highlight in order to stay in synch.  - kw
14248 	 */
14249 	data++;
14250 	if (incurlink) {
14251 	    while (IsNormalChar(*sdata)) {
14252 		++sdata;
14253 	    }
14254 	}
14255 
14256 	switch (buffer[0]) {
14257 
14258 	case LY_UNDERLINE_START_CHAR:
14259 	    if (!drawing || !incurlink)
14260 		inunderline = YES;
14261 	    if (drawing && !intarget && !incurlink)
14262 		lynx_start_underline();
14263 	    break;
14264 
14265 	case LY_UNDERLINE_END_CHAR:
14266 	    inunderline = NO;
14267 	    if (drawing && !intarget && !incurlink)
14268 		lynx_stop_underline();
14269 	    break;
14270 
14271 	case LY_BOLD_START_CHAR:
14272 	    if (!drawing || !incurlink)
14273 		inbold = YES;
14274 	    if (drawing && !intarget && !incurlink)
14275 		lynx_start_bold();
14276 	    break;
14277 
14278 	case LY_BOLD_END_CHAR:
14279 	    inbold = NO;
14280 	    if (drawing && !intarget && !incurlink)
14281 		lynx_stop_bold();
14282 	    break;
14283 
14284 	case LY_SOFT_NEWLINE:
14285 	    if (drawing) {
14286 		LYaddch('+');
14287 	    }
14288 	    i++;
14289 	    break;
14290 
14291 	case LY_SOFT_HYPHEN:
14292 	    if (*data != '\0' ||
14293 		isspace(UCH(LastDisplayChar)) ||
14294 		LastDisplayChar == '-') {
14295 		/*
14296 		 * Ignore the soft hyphen if it is not the last
14297 		 * character in the line.  Also ignore it if it
14298 		 * first character following the margin, or if it
14299 		 * is preceded by a white character (we loaded 'M'
14300 		 * into LastDisplayChar if it was a multibyte
14301 		 * character) or hyphen, though it should have
14302 		 * been excluded by HText_appendCharacter() or by
14303 		 * split_line() in those cases.  -FM
14304 		 */
14305 		break;
14306 	    } else {
14307 		/*
14308 		 * Make it a hard hyphen and fall through.  -FM
14309 		 */
14310 		buffer[0] = '-';
14311 	    }
14312 	    /* FALLTHRU */
14313 
14314 	default:
14315 	    /*
14316 	     * We have got an actual normal displayable character, or
14317 	     * the start of one.  Before proceeding check whether
14318 	     * drawing needs to be turned on now.  - kw
14319 	     */
14320 #if defined(SHOW_WHEREIS_TARGETS)
14321 	    if (incurlink && intarget && flag && i_after_tgt > i) {
14322 		if (i == last_i - 1) {
14323 		    i_after_tgt = i;
14324 		} else if (i == last_i - 2 && IS_CJK_TTY &&
14325 			   is8bits(buffer[0])) {
14326 		    i_after_tgt = i;
14327 		    cp_tgt = NULL;
14328 		    if (drawing) {
14329 			if (drawingtarget) {
14330 			    LYstopTargetEmphasis();
14331 			    drawingtarget = NO;
14332 			    lynx_start_link_color(flag, inU);
14333 			}
14334 		    }
14335 		}
14336 	    }
14337 	    if (cp_tgt && i >= i_start_tgt && sdata > cp_tgt) {
14338 		if (!intarget ||
14339 		    (intarget && incurlink && !drawingtarget)) {
14340 
14341 		    if (incurlink && drawing &&
14342 			!(flag &&
14343 			  (i == XP_link || i == last_i - 1))) {
14344 			lynx_stop_link_color(flag, inU);
14345 		    }
14346 		    if (incurlink && !drawing) {
14347 			LYmove(YP, i);
14348 			if (inunderline)
14349 			    inU = YES;
14350 			if (flag && (i == XP_link || i == last_i - 1)) {
14351 			    lynx_start_link_color(flag, inU);
14352 			    drawingtarget = NO;
14353 			} else {
14354 			    LYstartTargetEmphasis();
14355 			    drawingtarget = YES;
14356 			}
14357 			drawing = YES;
14358 		    } else if (incurlink && drawing &&
14359 			       intarget && !drawingtarget &&
14360 			       (flag &&
14361 				(i == XP_link))) {
14362 			if (inunderline)
14363 			    inU = YES;
14364 			lynx_start_link_color(flag, inU);
14365 		    } else if (drawing &&
14366 			       !(flag &&
14367 				 (i == XP_link || (incurlink && i == last_i - 1)))) {
14368 			LYstartTargetEmphasis();
14369 			drawingtarget = YES;
14370 		    }
14371 		    intarget = YES;
14372 		}
14373 	    } else
14374 #endif /* SHOW_WHEREIS_TARGETS */
14375 	    if (incurlink) {
14376 		if (!drawing) {
14377 		    LYmove(YP, i);
14378 		    if (inunderline)
14379 			inU = YES;
14380 		    lynx_start_link_color(flag, inU);
14381 		    drawing = YES;
14382 		}
14383 	    }
14384 
14385 	    i++;
14386 #ifndef WIDEC_CURSES
14387 	    if (utf_flag && is8bits(buffer[0])) {
14388 		hadutf8 = YES;
14389 		utf_extra = utf8_length(utf_flag, data - 1);
14390 		LastDisplayChar = 'M';
14391 	    }
14392 #endif
14393 	    if (utf_extra) {
14394 		LYStrNCpy(&buffer[1], data, utf_extra);
14395 		if (!drawing && i >= XP_draw_min) {
14396 		    LYmove(YP, i - 1);
14397 		    drawing = YES;
14398 #if defined(SHOW_WHEREIS_TARGETS)
14399 		    if (intarget) {
14400 			drawingtarget = YES;
14401 			LYstartTargetEmphasis();
14402 		    } else
14403 #endif /* SHOW_WHEREIS_TARGETS */
14404 		    {
14405 			if (inbold)
14406 			    lynx_start_bold();
14407 			if (inunderline)
14408 			    lynx_start_underline();
14409 		    }
14410 		}
14411 		LYaddstr(buffer);
14412 		buffer[1] = '\0';
14413 		sdata += utf_extra;
14414 		data += utf_extra;
14415 		utf_extra = 0;
14416 	    } else if (IS_CJK_TTY && is8bits(buffer[0])
14417 		       && (!conv_jisx0201kana && (kanji_code != SJIS))) {
14418 		/*
14419 		 * For CJK strings, by Masanobu Kimura.
14420 		 */
14421 		if (drawing && (i <= last_i)) {
14422 		    buffer[1] = *data;
14423 		    LYaddstr(buffer);
14424 		    buffer[1] = '\0';
14425 		}
14426 		i++;
14427 		sdata++;
14428 		data++;
14429 		/*
14430 		 * For now, load 'M' into LastDisplayChar, but we should
14431 		 * check whether it's white and if so, use ' '.  I don't
14432 		 * know if there actually are white CJK characters, and
14433 		 * we're loading ' ' for multibyte spacing characters in
14434 		 * this code set, but this will become an issue when the
14435 		 * development code set's multibyte character handling is
14436 		 * used.  -FM
14437 		 */
14438 		LastDisplayChar = 'M';
14439 	    } else {
14440 		if (drawing) {
14441 		    LYaddstr(buffer);
14442 		}
14443 		LastDisplayChar = buffer[0];
14444 	    }
14445 	}			/* end of switch */
14446     }				/* end of while */
14447 
14448     if (!drawing) {
14449 	LYmove(YP, XP_next);
14450 	lynx_start_link_color(flag, inU);
14451     } else {
14452 #if defined(SHOW_WHEREIS_TARGETS)
14453 	if (drawingtarget) {
14454 	    LYstopTargetEmphasis();
14455 	    lynx_start_link_color(flag, inU);
14456 	}
14457 #endif /* SHOW_WHEREIS_TARGETS */
14458 	if (hadutf8) {
14459 	    LYtouchline(YP);
14460 	}
14461     }
14462     return;
14463 }
14464 #endif /* !USE_COLOR_STYLE */
14465 
14466 #ifndef USE_COLOR_STYLE
14467 /*
14468  * Move cursor position to a link's place in the display.
14469  * The "moving to" is done by scanning through the line's
14470  * character data in the corresponding HTLine of HTMainText,
14471  * and starting to draw when a UTF-8 encoded non-ASCII character
14472  * is encountered before the link (with some protection against
14473  * overwriting form fields).  This refreshing of preceding data is
14474  * necessary for preventing curses's or slang's display logic from
14475  * getting too clever; their logic counts character positions wrong
14476  * since they don't know about multi-byte characters that take up
14477  * only one screen position.  So we have to make them forget their
14478  * idea of what's in a screen line drawn previously.
14479  * If hightext is non-NULL, it should be the anchor text for a normal
14480  * link as stored in a links[] element, and the anchor text will be
14481  * drawn too, with appropriate attributes.  - kw
14482  */
LYMoveToLink(int cur,const char * target,const char * hightext,int flag,int inU,int utf_flag)14483 void LYMoveToLink(int cur,
14484 		  const char *target,
14485 		  const char *hightext,
14486 		  int flag,
14487 		  int inU,
14488 		  int utf_flag)
14489 {
14490 #define pvtTITLE_HEIGHT 1
14491     HTLine *todr;
14492     int i, n = 0;
14493     int XP_draw_min = 0;
14494     int flags = ((flag == TRUE) ? 1 : 0) | (inU ? 2 : 0);
14495 
14496     /*
14497      * We need to protect changed form text fields preceding this
14498      * link on the same line against overwriting.  - kw
14499      */
14500     for (i = cur - 1; i >= 0; i++) {
14501 	if (links[i].ly < links[cur].ly)
14502 	    break;
14503 	if (links[i].type == WWW_FORM_LINK_TYPE) {
14504 	    XP_draw_min = links[i].ly + links[i].l_form->size;
14505 	    break;
14506 	}
14507     }
14508 
14509     /*  Find the right HTLine. */
14510     if (!HTMainText) {
14511 	todr = NULL;
14512     } else if (HTMainText->stale) {
14513 	todr = FirstHTLine(HTMainText);
14514 	n = links[cur].ly - pvtTITLE_HEIGHT + HTMainText->top_of_screen;
14515     } else {
14516 	todr = HTMainText->top_of_screen_line;
14517 	n = links[cur].ly - pvtTITLE_HEIGHT;
14518     }
14519     for (i = 0; i < n && todr; i++) {
14520 	todr = (todr == HTMainText->last_line) ? NULL : todr->next;
14521     }
14522     if (todr) {
14523 	if (target && *target == '\0')
14524 	    target = NULL;
14525 	move_to_glyph(links[cur].ly, links[cur].lx, XP_draw_min,
14526 		      todr->data, todr->size, todr->offset,
14527 		      target, hightext, flags, utf_flag);
14528     } else {
14529 	/*  This should not happen. */
14530 	move_to_glyph(links[cur].ly, links[cur].lx, XP_draw_min,
14531 		      "", 0, (unsigned) links[cur].lx,
14532 		      target, hightext, flags, utf_flag);
14533     }
14534 }
14535 #endif /* !USE_COLOR_STYLE */
14536 
14537 /*
14538  * This is used only if compiled with lss support.  It's called to redraw a
14539  * regular link when it's being unhighlighted in LYhighlight().
14540  */
14541 #ifdef USE_COLOR_STYLE
redraw_lines_of_link(int cur)14542 void redraw_lines_of_link(int cur)
14543 {
14544 #define pvtTITLE_HEIGHT 1
14545     HTLine *todr1;
14546     int lines_back;
14547     int row, col, count;
14548     const char *text;
14549 
14550     if (HTMainText->next_line == HTMainText->last_line) {
14551 	/* we are at the last page - that is partially filled */
14552 	lines_back = HTMainText->Lines - (links[cur].ly - pvtTITLE_HEIGHT +
14553 					  HTMainText->top_of_screen);
14554     } else {
14555 	lines_back = display_lines - (links[cur].ly - pvtTITLE_HEIGHT);
14556     }
14557     todr1 = HTMainText->next_line;
14558     while (lines_back-- > 0)
14559 	todr1 = todr1->prev;
14560 
14561     row = links[cur].ly;
14562     if (no_title)
14563 	row -= TITLE_LINES;
14564 
14565     for (count = 0;
14566 	 row <= display_lines && (text = LYGetHiliteStr(cur, count)) != NULL;
14567 	 ++count) {
14568 	col = LYGetHilitePos(cur, count);
14569 	if (col >= 0) {
14570 	    LYmove(row, col);
14571 	    redraw_part_of_line(todr1, text, (int) strlen(text), HTMainText);
14572 	}
14573 	todr1 = todr1->next;
14574 	row++;
14575     }
14576 #undef pvtTITLE_HEIGHT
14577     return;
14578 }
14579 #endif
14580 
14581 #ifdef USE_PRETTYSRC
HTMark_asSource(void)14582 void HTMark_asSource(void)
14583 {
14584     if (HTMainText)
14585 	HTMainText->source = TRUE;
14586 }
14587 #endif
14588 
HText_getKcode(HText * text)14589 HTkcode HText_getKcode(HText *text)
14590 {
14591     return text->kcode;
14592 }
14593 
HText_updateKcode(HText * text,HTkcode kcode)14594 void HText_updateKcode(HText *text, HTkcode kcode)
14595 {
14596     text->kcode = kcode;
14597 }
14598 
HText_getSpecifiedKcode(HText * text)14599 HTkcode HText_getSpecifiedKcode(HText *text)
14600 {
14601     return text->specified_kcode;
14602 }
14603 
HText_updateSpecifiedKcode(HText * text,HTkcode kcode)14604 void HText_updateSpecifiedKcode(HText *text, HTkcode kcode)
14605 {
14606     text->specified_kcode = kcode;
14607 }
14608 
HTMainText_Get_UCLYhndl(void)14609 int HTMainText_Get_UCLYhndl(void)
14610 {
14611     return (HTMainText ?
14612 	    HTAnchor_getUCLYhndl(HTMainText->node_anchor, UCT_STAGE_MIME)
14613 	    : -1);
14614 }
14615 
14616 #ifdef USE_CACHEJAR
LYHandleCache(const char * arg,HTParentAnchor * anAnchor,HTFormat format_out,HTStream * sink)14617 static int LYHandleCache(const char *arg,
14618 			 HTParentAnchor *anAnchor,
14619 			 HTFormat format_out,
14620 			 HTStream *sink)
14621 {
14622     HTFormat format_in = WWW_HTML;
14623     HTStream *target = NULL;
14624     char c;
14625     char *buf = NULL;
14626     char *title = NULL;
14627     char *address = NULL;
14628     char *content_type = NULL;
14629     char *content_language = NULL;
14630     char *content_encoding = NULL;
14631     char *content_location = NULL;
14632     char *content_disposition = NULL;
14633     char *content_md5 = NULL;
14634     char *message_id = NULL;
14635     char *date = NULL;
14636     char *owner = NULL;
14637     char *subject = NULL;
14638     char *expires = NULL;
14639     char *ETag = NULL;
14640     char *server = NULL;
14641     char *FileCache = NULL;
14642     char *last_modified = NULL;
14643     char *cache_control = NULL;
14644 
14645 #ifdef USE_SOURCE_CACHE
14646     char *source_cache_file = NULL;
14647 #endif
14648     off_t Size = 0;
14649     int x = -1;
14650 
14651     /*
14652      * Check if there is something to do.
14653      */
14654     if (HTList_count(loaded_texts) == 0) {
14655 	HTProgress(CACHE_JAR_IS_EMPTY);
14656 	LYSleepMsg();
14657 	HTNoDataOK = 1;
14658 	return (HT_NO_DATA);
14659     }
14660 
14661     /*
14662      * If # of LYNXCACHE:/# is number ask user if he/she want to delete it.
14663      */
14664     if (sscanf(arg, STR_LYNXCACHE "/%d", &x) == 1 && x > 0) {
14665 	CTRACE((tfp, "LYNXCACHE number is %d\n", x));
14666 	_statusline(CACHE_D_OR_CANCEL);
14667 	c = (char) LYgetch_single();
14668 	if (c == 'D') {
14669 	    HText *t = (HText *) HTList_objectAt(loaded_texts, x - 1);
14670 
14671 	    HTList_removeObjectAt(loaded_texts, x - 1);
14672 	    HText_free(t);
14673 	}
14674 	return (HT_NO_DATA);
14675     }
14676 
14677     /*
14678      * If we get to here, it was a LYNXCACHE:/ URL for creating and displaying
14679      * the Cache Jar Page.
14680      * Set up an HTML stream and return an updated Cache Jar Page.
14681      */
14682     target = HTStreamStack(format_in,
14683 			   format_out,
14684 			   sink, anAnchor);
14685     if (target == NULL) {
14686 	HTSprintf0(&buf, CANNOT_CONVERT_I_TO_O,
14687 		   HTAtom_name(format_in), HTAtom_name(format_out));
14688 	HTAlert(buf);
14689 	FREE(buf);
14690 	return (HT_NOT_LOADED);
14691     }
14692 
14693     /*
14694      * Load HTML strings into buf and pass buf to the target for parsing and
14695      * rendering.
14696      */
14697 #define PUTS(buf)    (*target->isa->put_block)(target, buf, (int) strlen(buf))
14698 
14699     HTSprintf0(&buf,
14700 	       "<html>\n<head>\n<title>%s</title>\n</head>\n<body>\n",
14701 	       CACHE_JAR_TITLE);
14702     PUTS(buf);
14703     HTSprintf0(&buf, "<h1>%s (%s)%s<a href=\"%s%s\">%s</a></h1>\n",
14704 	       LYNX_NAME, LYNX_VERSION,
14705 	       HELP_ON_SEGMENT,
14706 	       helpfilepath, CACHE_JAR_HELP, CACHE_JAR_TITLE);
14707     PUTS(buf);
14708 
14709     /*
14710      * Max number of cached documents is always same as HTCacheSize.
14711      * We count them from oldest to newest. Currently cached document
14712      * is *never* listed, resulting in maximal entries of Cache Jar
14713      * to be HTCacheSize - 1
14714      */
14715     for (x = HTList_count(loaded_texts) - 1; x > 0; x--) {
14716 	/*
14717 	 * The number of the document in the cache list, its title in a link,
14718 	 * and its address and memory allocated for each cached document.
14719 	 */
14720 	HText *cachedoc = (HText *) HTList_objectAt(loaded_texts, x);
14721 
14722 	if (cachedoc != 0) {
14723 	    HTParentAnchor *docanchor = cachedoc->node_anchor;
14724 
14725 	    if (docanchor != 0) {
14726 #ifdef USE_SOURCE_CACHE
14727 		source_cache_file = docanchor->source_cache_file;
14728 #endif
14729 		Size = docanchor->content_length;
14730 		StrAllocCopy(title, docanchor->title);
14731 		StrAllocCopy(address, docanchor->address);
14732 		content_type = docanchor->content_type;
14733 		content_language = docanchor->content_language;
14734 		content_encoding = docanchor->content_encoding;
14735 		content_location = docanchor->content_location;
14736 		content_disposition = docanchor->content_disposition;
14737 		content_md5 = docanchor->content_md5;
14738 		message_id = docanchor->message_id;
14739 		owner = docanchor->owner;
14740 		StrAllocCopy(subject, docanchor->subject);
14741 		date = docanchor->date;
14742 		expires = docanchor->expires;
14743 		ETag = docanchor->ETag;
14744 		StrAllocCopy(server, docanchor->server);
14745 		FileCache = docanchor->FileCache;
14746 		last_modified = docanchor->last_modified;
14747 		cache_control = docanchor->cache_control;
14748 	    }
14749 	}
14750 
14751 	LYEntify(&address, TRUE);
14752 	if (isEmpty(title))
14753 	    StrAllocCopy(title, NO_TITLE);
14754 	else
14755 	    LYEntify(&title, TRUE);
14756 
14757 	HTSprintf0(&buf,
14758 		   "<p><em>%d.</em> Title: <a href=\"%s%d\">%s</a><br />URL: <a href=\"%s\">%s</a><br />",
14759 		   x, STR_LYNXCACHE, x, title, address, address);
14760 	PUTS(buf);
14761 	if (Size > 0) {
14762 	    HTSprintf0(&buf, "Size: %" PRI_off_t "  ", CAST_off_t (Size));
14763 
14764 	    PUTS(buf);
14765 	}
14766 	if (cachedoc != NULL && cachedoc->Lines > 0) {
14767 	    HTSprintf0(&buf, "Lines: %d  ", cachedoc->Lines);
14768 	    PUTS(buf);
14769 	}
14770 	if (FileCache != NULL) {
14771 	    HTSprintf0(&buf, "File-Cache: <a href=\"file://%s\">%s</a>  ",
14772 		       FileCache, FileCache);
14773 	    PUTS(buf);
14774 	}
14775 	if (cache_control != NULL) {
14776 	    HTSprintf0(&buf, "Cache-Control: %s  ", cache_control);
14777 	    PUTS(buf);
14778 	}
14779 	if (content_type != NULL) {
14780 	    HTSprintf0(&buf, "Content-Type: %s  ", content_type);
14781 	    PUTS(buf);
14782 	}
14783 	if (content_language != NULL) {
14784 	    HTSprintf0(&buf, "Content-Language: %s  ", content_language);
14785 	    PUTS(buf);
14786 	}
14787 	if (content_encoding != NULL) {
14788 	    HTSprintf0(&buf, "Content-Encoding: %s  ", content_encoding);
14789 	    PUTS(buf);
14790 	}
14791 	if (content_location != NULL) {
14792 	    HTSprintf0(&buf, "Content-Location: %s  ", content_location);
14793 	    PUTS(buf);
14794 	}
14795 	if (content_disposition != NULL) {
14796 	    HTSprintf0(&buf, "Content-Disposition: %s  ", content_disposition);
14797 	    PUTS(buf);
14798 	}
14799 	if (content_md5 != NULL) {
14800 	    HTSprintf0(&buf, "Content-MD5: %s  ", content_md5);
14801 	    PUTS(buf);
14802 	}
14803 	if (message_id != NULL) {
14804 	    HTSprintf0(&buf, "Message-ID: %s  ", message_id);
14805 	    PUTS(buf);
14806 	}
14807 	if (subject != NULL) {
14808 	    LYEntify(&subject, TRUE);
14809 	    HTSprintf0(&buf, "Subject: %s  ", subject);
14810 	    PUTS(buf);
14811 	}
14812 	if (owner != NULL) {
14813 	    HTSprintf0(&buf, "Owner: <a href=%s>%s</a>  ", owner, owner);
14814 	    PUTS(buf);
14815 	}
14816 	if (date != NULL) {
14817 	    HTSprintf0(&buf, "Date: %s  ", date);
14818 	    PUTS(buf);
14819 	}
14820 	if (expires != NULL) {
14821 	    HTSprintf0(&buf, "Expires: %s  ", expires);
14822 	    PUTS(buf);
14823 	}
14824 	if (last_modified != NULL) {
14825 	    HTSprintf0(&buf, "Last-Modified: %s  ", last_modified);
14826 	    PUTS(buf);
14827 	}
14828 	if (ETag != NULL) {
14829 	    HTSprintf0(&buf, "ETag: %s  ", ETag);
14830 	    PUTS(buf);
14831 	}
14832 	if (server != NULL) {
14833 	    LYEntify(&server, TRUE);
14834 	    HTSprintf0(&buf, "Server: <em>%s</em>  ", server);
14835 	    PUTS(buf);
14836 	}
14837 #ifdef USE_SOURCE_CACHE
14838 	if (source_cache_file != NULL) {
14839 	    HTSprintf0(&buf,
14840 		       "Source-Cache-File: <a href=\"file://%s\">%s</a>",
14841 		       source_cache_file, source_cache_file);
14842 	    PUTS(buf);
14843 	}
14844 #endif
14845 	HTSprintf0(&buf, "<br />");
14846 	PUTS(buf);
14847     }
14848     HTSprintf0(&buf, "</body></html>");
14849     PUTS(buf);
14850     FREE(subject);
14851     FREE(title);
14852     FREE(address);
14853     FREE(server);
14854 
14855     /*
14856      * Free the target to complete loading of the Cache Jar Page, and report a
14857      * successful load.
14858      */
14859     (*target->isa->_free) (target);
14860     FREE(buf);
14861     return (HT_LOADED);
14862 }
14863 
14864 #ifdef GLOBALDEF_IS_MACRO
14865 #define _LYCACHE_C_GLOBALDEF_1_INIT { "LYNXCACHE",LYHandleCache,0}
14866 GLOBALDEF(HTProtocol, LYLynxCache, _LYCACHE_C_GLOBALDEF_1_INIT);
14867 #else
14868 GLOBALDEF HTProtocol LYLynxCache =
14869 {"LYNXCACHE", LYHandleCache, 0};
14870 #endif /* GLOBALDEF_IS_MACRO */
14871 #endif /* USE_CACHEJAR */
14872