1 /*
2  * $LynxId: GridText.c,v 1.266 2013/06/12 21:03:36 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 <TRSTable.h>
40 #include <LYHistory.h>
41 #ifdef EXP_CHARTRANS_AUTOSWITCH
42 #include <UCAuto.h>
43 #endif /* EXP_CHARTRANS_AUTOSWITCH */
44 
45 #include <LYexit.h>
46 #include <LYLeaks.h>
47 
48 #ifdef USE_COLOR_STYLE
49 #include <AttrList.h>
50 #include <LYHash.h>
51 #include <LYStyle.h>
52 
53 #endif
54 
55 #include <LYJustify.h>
56 
57 #define is_CJK2(b) (IS_CJK_TTY && is8bits(UCH(b)))
58 
59 #ifdef USE_CURSES_PADS
60 #  define DISPLAY_COLS    (LYwideLines ? MAX_COLS : LYcols)
61 #  define WRAP_COLS(text) ((text)->stbl ?				\
62 			   (LYtableCols <= 0				\
63 			    ? DISPLAY_COLS				\
64 			    : (LYtableCols * LYcols)/12) - LYbarWidth	\
65 			   : LYcolLimit)
66 #else
67 #  define DISPLAY_COLS    LYcols
68 #  define WRAP_COLS(text) LYcolLimit
69 #endif
70 
71 #define FirstHTLine(text) ((text)->last_line->next)
72 #define LastHTLine(text)  ((text)->last_line)
73 
74 static void HText_trimHightext(HText *text, int final, int stop_before);
75 
76 #define IS_UTF_EXTRA(ch) (text->T.output_utf8 && \
77 			  (UCH((ch))&0xc0) == 0x80)
78 
79 #define IS_UTF8_EXTRA(ch) (!(text && text->T.output_utf8) || \
80 			  !is8bits(ch) || \
81 			  (UCH(line->data[i] & 0xc0) == 0xc0))
82 
83 /* a test in compact form: how many extra UTF-8 chars after initial? - kw */
84 #define UTF8_XNEGLEN(c) (c&0xC0? 0 :c&32? 1 :c&16? 2 :c&8? 3 :c&4? 4 :c&2? 5:0)
85 #define UTF_XLEN(c) UTF8_XNEGLEN(((char)~(c)))
86 
87 #ifdef KANJI_CODE_OVERRIDE
88 HTkcode last_kcode = NOKANJI;	/* 1997/11/14 (Fri) 09:09:26 */
89 #endif
90 
91 #ifdef CJK_EX
92 #define CHAR_WIDTH 6
93 #else
94 #define CHAR_WIDTH 1
95 #endif
96 
97 /*	Exports
98 */
99 HText *HTMainText = NULL;	/* Equivalent of main window */
100 HTParentAnchor *HTMainAnchor = NULL;	/* Anchor for HTMainText */
101 
102 const char *HTAppName = LYNX_NAME;	/* Application name */
103 const char *HTAppVersion = LYNX_VERSION;	/* Application version */
104 
105 static int HTFormNumber = 0;
106 static int HTFormFields = 0;
107 static char *HTCurSelectGroup = NULL;	/* Form select group name */
108 static int HTCurSelectGroupCharset = -1;	/* ... and name's charset */
109 int HTCurSelectGroupType = F_RADIO_TYPE;	/* Group type */
110 char *HTCurSelectGroupSize = NULL;	/* Length of select */
111 static char *HTCurSelectedOptionValue = NULL;	/* Select choice */
112 
113 const char *checked_box = "[X]";
114 const char *unchecked_box = "[ ]";
115 const char *checked_radio = "(*)";
116 const char *unchecked_radio = "( )";
117 
118 static BOOLEAN underline_on = FALSE;
119 static BOOLEAN bold_on = FALSE;
120 
121 #ifdef USE_SOURCE_CACHE
122 int LYCacheSource = SOURCE_CACHE_NONE;
123 int LYCacheSourceForAborted = SOURCE_CACHE_FOR_ABORTED_DROP;
124 #endif
125 
126 #ifdef USE_SCROLLBAR
127 BOOLEAN LYShowScrollbar = FALSE;
128 BOOLEAN LYsb_arrow = TRUE;
129 int LYsb_begin = -1;
130 int LYsb_end = -1;
131 #endif
132 
133 #ifndef VMS			/* VMS has a better way - right? - kw */
134 #define CHECK_FREE_MEM
135 #endif
136 
137 #ifdef CHECK_FREE_MEM
138 static void *LY_check_calloc(size_t nmemb, size_t size);
139 
140 #define LY_CALLOC LY_check_calloc
141 #else
142   /* using the regular calloc */
143 #define LY_CALLOC calloc
144 #endif
145 
146 /*
147  * The HTPool.data[] array has to align the same as malloc() would, to make the
148  * ALLOC_POOL scheme portable.  For many platforms, that is the same as the
149  * number of bytes in a pointer.  It may be larger, e.g., on machines which
150  * have more stringent requirements for floating point.  32-bits are plenty for
151  * representing styles, but we may need 64-bit or 128-bit alignment.
152  *
153  * The real issue is that performance is degraded if the alignment is not met,
154  * and some platforms such as Tru64 generate lots of warning messages.
155  */
156 #ifndef ALIGN_SIZE
157 #define ALIGN_SIZE      sizeof(double)
158 #endif
159 
160 typedef struct {
161     unsigned int sc_direction:2;	/* on or off */
162     unsigned int sc_horizpos:14;	/* horizontal position of this change */
163     unsigned int sc_style:16;	/* which style to change to */
164 } HTStyleChange;
165 
166 #if defined(USE_COLOR_STYLE)
167 #define MAX_STYLES_ON_LINE   64
168   /* buffers used when current line is being aggregated, in split_line() */
169 static HTStyleChange stylechanges_buffers[2][MAX_STYLES_ON_LINE];
170 #endif
171 
172 typedef HTStyleChange pool_data;
173 
174 enum {
175     POOL_SIZE = ((8192
176 		  - 4 * sizeof(void *)
177 		  - sizeof(struct _HTPool *)
178 		  - sizeof(int))
179 		 / sizeof(pool_data))
180 };
181 
182 typedef struct _HTPool {
183     pool_data data[POOL_SIZE];
184     struct _HTPool *prev;
185     unsigned used;
186 } HTPool;
187 
188 /************************************************************************
189 These are generic macros for any pools (provided those structures have the
190 same members as HTPool).  Pools are used for allocation of groups of
191 objects of the same type T.  Pools are represented as a list of structures of
192 type P (called pool chunks here).  Structure P has an array of N objects of
193 type T named 'data' (the number N in the array can be chosen arbitrary),
194 pointer to the previous pool chunk named 'prev', and the number of used items
195 in that pool chunk named 'used'.  Here is a definition of the structure P:
196 	struct P
197 	{
198 	    T data[N];
199 	    struct P* prev;
200 	    int used;
201 	};
202  It's recommended that sizeof(P) be memory page size minus 32 in order malloc'd
203 chunks to fit in machine page size.
204  Allocation of 'n' items in the pool is implemented by incrementing member
205 'used' by 'n' if (used+n <= N), or malloc a new pool chunk and
206 allocating 'n' items in that new chunk.  It's the task of the programmer to
207 assert that 'n' is <= N.  Only entire pool may be freed - this limitation makes
208 allocation algorithms trivial and fast - so the use of pools is limited to
209 objects that are freed in batch, that are not deallocated not in the batch, and
210 not reallocated.
211  Pools greatly reduce memory fragmentation and memory allocation/deallocation
212 speed due to the simple algorithms used.  Due to the fact that memory is
213 'allocated' in array, alignment overhead is minimal.  Allocating strings in a
214 pool provided their length will never exceed N and is much smaller than N seems
215 to be very efficient.
216  [Several types of memory-hungry objects are stored in the pool now:  styles,
217 lines, anchors, and FormInfo. Arrays of HTStyleChange are stored as is,
218 other objects are stored using a cast.]
219 
220  Pool is referenced by the pointer to the last chunk that contains free slots.
221 Functions that allocate memory in the pool update that pointer if needed.
222 There are 3 functions - POOL_NEW, POOL_FREE, and ALLOC_IN_POOL.
223 
224       - VH
225 
226 *************************************************************************/
227 
228 #define POOLallocstyles(ptr, n)     ptr = ALLOC_IN_POOL(&HTMainText->pool, (unsigned) ((n) * sizeof(pool_data)))
229 #define POOLallocHTLine(ptr, size)  ptr = (HTLine*) ALLOC_IN_POOL(&HTMainText->pool, (unsigned) LINE_SIZE(size))
230 #define POOLallocstring(ptr, len)   ptr = (char*) ALLOC_IN_POOL(&HTMainText->pool, (unsigned) ((len) + 1))
231 #define POOLtypecalloc(T, ptr)      ptr = (T*) ALLOC_IN_POOL(&HTMainText->pool, (unsigned) sizeof(T))
232 
233 /**************************************************************************/
234 /*
235  * Allocates 'n' items in the pool of type 'HTPool' pointed by 'poolptr'.
236  * Returns a pointer to the "allocated" memory if successful.
237  * Updates 'poolptr' if necessary.
238  */
ALLOC_IN_POOL(HTPool ** ppoolptr,unsigned request)239 static void *ALLOC_IN_POOL(HTPool ** ppoolptr, unsigned request)
240 {
241     HTPool *pool = *ppoolptr;
242     pool_data *ptr;
243     unsigned n;
244     unsigned j;
245 
246     if (!pool) {
247 	outofmem(__FILE__, "ALLOC_IN_POOL");
248     } else {
249 	n = request;
250 	if (n == 0)
251 	    n = 1;
252 	j = (n % ALIGN_SIZE);
253 	if (j != 0)
254 	    n += (unsigned) (ALIGN_SIZE - j);
255 	n /= sizeof(pool_data);
256 
257 	if (POOL_SIZE >= (pool->used + n)) {
258 	    ptr = pool->data + pool->used;
259 	    pool->used += n;
260 	} else {
261 	    HTPool *newpool = (HTPool *) LY_CALLOC((size_t) 1, sizeof(HTPool));
262 
263 	    if (!newpool) {
264 		outofmem(__FILE__, "ALLOC_IN_POOL");
265 	    } else {
266 		newpool->prev = pool;
267 		newpool->used = n;
268 		ptr = newpool->data;
269 		*ppoolptr = newpool;
270 	    }
271 	}
272     }
273     return ptr;
274 }
275 
276 /*
277  * Returns a pointer to initialized pool of type 'HTPool', or NULL if fails.
278  */
POOL_NEW(void)279 static HTPool *POOL_NEW(void)
280 {
281     HTPool *poolptr = (HTPool *) LY_CALLOC((size_t) 1, sizeof(HTPool));
282 
283     if (poolptr) {
284 	poolptr->prev = NULL;
285 	poolptr->used = 0;
286     }
287     return poolptr;
288 }
289 
290 /*
291  * Frees a pool of type 'HTPool' pointed by poolptr.
292  */
POOL_FREE(HTPool * poolptr)293 static void POOL_FREE(HTPool * poolptr)
294 {
295     HTPool *cur = poolptr;
296     HTPool *prev;
297 
298     while (cur) {
299 	prev = cur->prev;
300 	free(cur);
301 	cur = prev;
302     }
303 }
304 
305 /**************************************************************************/
306 /**************************************************************************/
307 
308 typedef struct _line {
309     struct _line *next;
310     struct _line *prev;
311     unsigned short offset;	/* Implicit initial spaces */
312     unsigned short size;	/* Number of characters */
313 #if defined(USE_COLOR_STYLE)
314     HTStyleChange *styles;
315     unsigned short numstyles;
316 #endif
317     char data[1];		/* Space for terminator at least! */
318 } HTLine;
319 
320     /* Allow for terminator */
321 #define LINE_SIZE(size) (sizeof(HTLine) + (size_t)(size))
322 
323 #ifndef HTLINE_NOT_IN_POOL
324 #define HTLINE_NOT_IN_POOL 0	/* debug with this set to 1 */
325 #endif
326 
327 #if HTLINE_NOT_IN_POOL
328 #define allocHTLine(ptr, size)  { ptr = (HTLine *)calloc(1, LINE_SIZE(size)); }
329 #define freeHTLine(self, ptr)   { \
330 	if (ptr && ptr != TEMP_LINE(self, 0) && ptr != TEMP_LINE(self, 1)) \
331 	    FREE(ptr); \
332     }
333 #else
334 #define allocHTLine(ptr, size)  POOLallocHTLine(ptr, size)
335 #define freeHTLine(self, ptr)   {}
336 #endif
337 
338 /*
339  * Last line buffer; the second is used in split_line(). Not in pool!
340  * We cannot wrap in middle of multibyte sequences, so allocate 2 extra
341  * for a workspace.  This is stored in the HText, to prevent confusion
342  * between different documents.  Note also that it is declared with an
343  * HTLine at the beginning so pointers will be properly aligned.
344  */
345 typedef struct {
346     HTLine base;
347     char data[MAX_LINE + 2];
348 } HTLineTemp;
349 
350 #define TEMP_LINE(p,n) ((HTLine *)&(p->temp_line[n]))
351 
352 typedef struct _TextAnchor {
353     struct _TextAnchor *next;
354     struct _TextAnchor *prev;	/* www_user_search only! */
355     int sgml_offset;		/* used for updating position after reparsing */
356     int number;			/* For user interface */
357     int show_number;		/* For user interface (unique-urls) */
358     int line_num;		/* Place in document */
359     short line_pos;		/* Bytes/chars - extent too */
360     short extent;		/* (see HText_trimHightext) */
361     BOOL show_anchor;		/* Show the anchor? */
362     BOOL inUnderline;		/* context is underlined */
363     BOOL expansion_anch;	/* TEXTAREA edit new anchor */
364     char link_type;		/* Normal, internal, or form? */
365     FormInfo *input_field;	/* Info for form links */
366     HiliteList lites;
367 
368     HTChildAnchor *anchor;
369 } TextAnchor;
370 
371 typedef struct {
372     char *name;			/* ID value of TAB */
373     int column;			/* Zero-based column value */
374 } HTTabID;
375 
376 typedef enum {
377     S_text,
378     S_esc,
379     S_dollar,
380     S_paren,
381     S_nonascii_text,
382     S_dollar_paren,
383     S_jisx0201_text
384 } eGridState;			/* Escape sequence? */
385 
386 #ifdef USE_TH_JP_AUTO_DETECT
387 typedef enum {			/* Detected Kanji code */
388     DET_SJIS,
389     DET_EUC,
390     DET_NOTYET,
391     DET_MIXED
392 } eDetectedKCode;
393 
394 typedef enum {
395     SJIS_state_neutral,
396     SJIS_state_in_kanji,
397     SJIS_state_has_bad_code
398 } eSJIS_status;
399 
400 typedef enum {
401     EUC_state_neutral,
402     EUC_state_in_kanji,
403     EUC_state_in_kana,
404     EUC_state_has_bad_code
405 } eEUC_status;
406 #endif
407 
408 /*	Notes on struct _HText:
409  *	next_line is valid if stale is false.
410  *	top_of_screen line means the line at the top of the screen
411  *			or just under the title if there is one.
412  */
413 struct _HText {
414     HTParentAnchor *node_anchor;
415 
416     HTLine *last_line;
417     HTLineTemp temp_line[2];
418     int Lines;			/* Number of them */
419     TextAnchor *first_anchor;	/* double-linked on demand */
420     TextAnchor *last_anchor;
421     TextAnchor *last_anchor_before_stbl;
422     TextAnchor *last_anchor_before_split;
423     HTList *forms;		/* also linked internally */
424     int last_anchor_number;	/* user number */
425     BOOL source;		/* Is the text source? */
426     BOOL toolbar;		/* Toolbar set? */
427     HTList *tabs;		/* TAB IDs */
428     HTList *hidden_links;	/* Content-less links ... */
429     int hiddenlinkflag;		/*  ... and how to treat them */
430     BOOL no_cache;		/* Always refresh? */
431     char LastChar;		/* For absorbing white space */
432 
433 /* For Internal use: */
434     HTStyle *style;		/* Current style */
435     int display_on_the_fly;	/* Lines left */
436     int top_of_screen;		/* Line number */
437     HTLine *top_of_screen_line;	/* Top */
438     HTLine *next_line;		/* Bottom + 1 */
439     unsigned permissible_split;	/* in last line */
440     BOOL in_line_1;		/* of paragraph */
441     BOOL stale;			/* Must refresh */
442     BOOL page_has_target;	/* has target on screen */
443     BOOL has_utf8;		/* has utf-8 on screen or line */
444     BOOL had_utf8;		/* had utf-8 when last displayed */
445     int next_number;		/* next a->number value */
446 #ifdef DISP_PARTIAL
447     int first_lineno_last_disp_partial;
448     int last_lineno_last_disp_partial;
449 #endif
450     STable_info *stbl;
451     HTList *enclosed_stbl;
452 
453     HTkcode kcode;		/* Kanji code? */
454     HTkcode specified_kcode;	/* Specified Kanji code */
455 #ifdef USE_TH_JP_AUTO_DETECT
456     eDetectedKCode detected_kcode;
457     eSJIS_status SJIS_status;
458     eEUC_status EUC_status;
459 #endif
460     eGridState state;		/* Escape sequence? */
461     int kanji_buf;		/* Lead multibyte */
462     int in_sjis;		/* SJIS flag */
463     int halted;			/* emergency halt */
464 
465     BOOL have_8bit_chars;	/* Any non-ASCII chars? */
466     LYUCcharset *UCI;		/* node_anchor UCInfo */
467     int UCLYhndl;		/* charset we are fed */
468     UCTransParams T;
469 
470     HTStream *target;		/* Output stream */
471     HTStreamClass targetClass;	/* Output routines */
472 
473     HTPool *pool;		/* this HText memory pool */
474 
475 #ifdef USE_SOURCE_CACHE
476     /*
477      * Parse settings when this HText was generated.
478      */
479     BOOL clickable_images;
480     BOOL pseudo_inline_alts;
481     BOOL verbose_img;
482     BOOL raw_mode;
483     BOOL historical_comments;
484     BOOL minimal_comments;
485     BOOL soft_dquotes;
486     short old_dtd;
487     short keypad_mode;
488     short disp_lines;		/* Screen size */
489     short disp_cols;		/* Used for reports only */
490 #endif
491 };
492 
493 /* exported */
HText_pool_calloc(HText * text,unsigned size)494 void *HText_pool_calloc(HText *text, unsigned size)
495 {
496     return (void *) ALLOC_IN_POOL(&text->pool, size);
497 }
498 
499 static void HText_AddHiddenLink(HText *text, TextAnchor *textanchor);
500 
501 #ifdef USE_JUSTIFY_ELTS
502 BOOL can_justify_here;
503 BOOL can_justify_here_saved;
504 
505 BOOL can_justify_this_line;	/* =FALSE if line contains form objects */
506 int wait_for_this_stacked_elt;	/* -1 if can justify contents of the
507 
508 				   element on the op of stack. If positive - specifies minimal stack depth
509 				   plus 1 at which we can justify element (can be MAX_LINE+2 if
510 				   ok_justify ==FALSE or in psrcview. */
511 BOOL form_in_htext;		/*to indicate that we are in form (since HTML_FORM is
512 
513 				   not stacked in the HTML.c */
514 BOOL in_DT = FALSE;
515 
516 #ifdef DEBUG_JUSTIFY
517 BOOL can_justify_stack_depth;	/* can be 0 or 1 if all code is correct */
518 #endif
519 
520 typedef struct {
521     int byte_len;		/*length in bytes */
522     int cell_len;		/*length in cells */
523 } ht_run_info;
524 
525 static int justify_start_position;	/* this is an index of char from which
526 
527 					   justification can start (eg after "* " preceeding <li> text) */
528 
529 static int ht_num_runs;		/*the number of runs filled */
530 static ht_run_info ht_runs[MAX_LINE];
531 static BOOL this_line_was_split;
532 static TextAnchor *last_anchor_of_previous_line;
533 static BOOL have_raw_nbsps = FALSE;
534 
ht_justify_cleanup(void)535 void ht_justify_cleanup(void)
536 {
537     wait_for_this_stacked_elt = !ok_justify
538 #  ifdef USE_PRETTYSRC
539 	|| psrc_view
540 #  endif
541 	? 30000 /*MAX_NESTING */  + 2	/*some unreachable value */
542 	: -1;
543     can_justify_here = TRUE;
544     can_justify_this_line = TRUE;
545     form_in_htext = FALSE;
546 
547     last_anchor_of_previous_line = NULL;
548     this_line_was_split = FALSE;
549     in_DT = FALSE;
550     have_raw_nbsps = FALSE;
551 }
552 
mark_justify_start_position(void * text)553 void mark_justify_start_position(void *text)
554 {
555     if (text && ((HText *) text)->last_line)
556 	justify_start_position = ((HText *) text)->last_line->size;
557 }
558 
559 #define REALLY_CAN_JUSTIFY(text) ( (wait_for_this_stacked_elt<0) && \
560 	( text->style->alignment == HT_LEFT     || \
561 	  text->style->alignment == HT_JUSTIFY) && \
562 	!IS_CJK_TTY && !in_DT && \
563 	can_justify_here && can_justify_this_line && !form_in_htext )
564 
565 #endif /* USE_JUSTIFY_ELTS */
566 
567 /*
568  * Boring static variable used for moving cursor across
569  */
570 #define UNDERSCORES(n) \
571  ((n) >= MAX_LINE ? underscore_string : &underscore_string[(MAX_LINE-1)] - (n))
572 
573 static char underscore_string[MAX_LINE + 1];
574 char star_string[MAX_LINE + 1];
575 
576 static int ctrl_chars_on_this_line = 0;		/* num of ctrl chars in current line */
577 static int utfxtra_on_this_line = 0;	/* num of UTF-8 extra bytes in line,
578 
579 					   they *also* count as ctrl chars. */
580 #ifdef WIDEC_CURSES
581 #define UTFXTRA_ON_THIS_LINE 0
582 #else
583 #define UTFXTRA_ON_THIS_LINE utfxtra_on_this_line
584 #endif
585 
586 static HTStyle default_style =
587 {0, NULL, "(Unstyled)", 0, NULL, "",
588  (HTFont) 0, 1, HT_BLACK, 0, 0,
589  0, 0, 0, HT_LEFT, 1, 0, 0,
590  NO, NO, 0, 0, 0};
591 
592 static HTList *loaded_texts = NULL;	/* A list of all those in memory */
593 HTList *search_queries = NULL;	/* isindex and whereis queries   */
594 
595 #ifdef LY_FIND_LEAKS
596 static void free_all_texts(void);
597 #endif
598 
599 static BOOL HText_TrueEmptyLine(HTLine *line, HText *text, int IgnoreSpaces);
600 
601 static int HText_TrueLineSize(HTLine *line, HText *text, int IgnoreSpaces);
602 
603 #ifdef CHECK_FREE_MEM
604 
605 /*
606  * text->halted = 1: have set fake 'Z' and output a message
607  *		  2: next time when HText_appendCharacter is called
608  *		     it will append *** MEMORY EXHAUSTED ***, then set
609  *		     to 3.
610  *		  3: normal text output will be suppressed (but not anchors,
611  *		     form fields etc.)
612  */
HText_halt(void)613 static void HText_halt(void)
614 {
615     if (HTFormNumber > 0)
616 	HText_DisableCurrentForm();
617     if (!HTMainText)
618 	return;
619     if (HTMainText->halted < 2)
620 	HTMainText->halted = 2;
621 }
622 
623 #define MIN_NEEDED_MEM 5000
624 
625 /*
626  * Check whether factor*min(bytes,MIN_NEEDED_MEM) is available,
627  * or bytes if factor is 0.
628  * MIN_NEEDED_MEM and factor together represent a security margin,
629  * to take account of all the memory allocations where we don't check
630  * and of buffers which may be emptied before HTCheckForInterupt()
631  * is (maybe) called and other things happening, with some chance of
632  * success.
633  * This just tries to malloc() the to-be-checked-for amount of memory,
634  * which might make the situation worse depending how allocation works.
635  * There should be a better way...  - kw
636  */
mem_is_avail(int factor,size_t bytes)637 static BOOL mem_is_avail(int factor, size_t bytes)
638 {
639     void *p;
640 
641     if (bytes < MIN_NEEDED_MEM && factor > 0)
642 	bytes = MIN_NEEDED_MEM;
643     if (factor == 0)
644 	factor = 1;
645     p = malloc((size_t) factor * bytes);
646     if (p) {
647 	FREE(p);
648 	return YES;
649     } else {
650 	return NO;
651     }
652 }
653 
654 /*
655  * Replacement for calloc which checks for "enough" free memory
656  * (with some security margins) and tries various recovery actions
657  * if deemed necessary.  - kw
658  */
LY_check_calloc(size_t nmemb,size_t size)659 static void *LY_check_calloc(size_t nmemb, size_t size)
660 {
661     int i, n;
662 
663     if (mem_is_avail(4, nmemb * size)) {
664 	return (calloc(nmemb, size));
665     }
666     n = HTList_count(loaded_texts);
667     for (i = n - 1; i > 0; i--) {
668 	HText *t = (HText *) HTList_objectAt(loaded_texts, i);
669 
670 	CTRACE((tfp,
671 		"\nBUG *** Emergency freeing document %d/%d for '%s'%s!\n",
672 		i + 1, n,
673 		((t && t->node_anchor &&
674 		  t->node_anchor->address) ?
675 		 t->node_anchor->address : "unknown anchor"),
676 		((t && t->node_anchor &&
677 		  t->node_anchor->post_data) ?
678 		 " with POST data" : "")));
679 	HTList_removeObjectAt(loaded_texts, i);
680 	HText_free(t);
681 	if (mem_is_avail(4, nmemb * size)) {
682 	    return (calloc(nmemb, size));
683 	}
684     }
685     LYFakeZap(YES);
686     if (!HTMainText || HTMainText->halted <= 1) {
687 	if (!mem_is_avail(2, nmemb * size)) {
688 	    HText_halt();
689 	    if (mem_is_avail(0, (size_t) 700)) {
690 		HTAlert(gettext("Memory exhausted, display interrupted!"));
691 	    }
692 	} else {
693 	    if ((!HTMainText || HTMainText->halted == 0) &&
694 		mem_is_avail(0, (size_t) 700)) {
695 		HTAlert(gettext("Memory exhausted, will interrupt transfer!"));
696 		if (HTMainText)
697 		    HTMainText->halted = 1;
698 	    }
699 	}
700     }
701     return (calloc(nmemb, size));
702 }
703 
704 #endif /* CHECK_FREE_MEM */
705 
706 #ifdef USE_COLOR_STYLE
707 /*
708  * Color style information is stored with the multibyte-character offset into
709  * the string at which the style would apply.  Compute the corresponding column
710  * so we can compare it with the updated column value after writing strings
711  * with curses.
712  *
713  * The offsets count multibyte characters.  Other parts of the code assume each
714  * character uses one cell, but some CJK (or UTF-8) codes use two cells.  We
715  * need to know the number of cells.
716  */
StyleToCols(HText * text,HTLine * line,int nstyle)717 static int StyleToCols(HText *text, HTLine *line, int nstyle)
718 {
719     int result = line->offset;	/* this much is spaces one byte/cell */
720     int nchars = line->styles[nstyle].sc_horizpos;
721     char *data = line->data;
722     char *last = line->size + data;
723     int utf_extra;
724 
725     while (nchars > 0 && data < last) {
726 	if (IsSpecialAttrChar(*data) && *data != LY_SOFT_NEWLINE) {
727 	    ++data;
728 	} else {
729 	    utf_extra = (int) utf8_length(text->T.output_utf8, data);
730 	    if (utf_extra++) {
731 		result += LYstrExtent(data, utf_extra, 2);
732 		data += utf_extra;
733 	    } else if (is_CJK2(*data)) {
734 		data += 2;
735 		result += 2;
736 	    } else {
737 		++data;
738 		++result;
739 	    }
740 	    --nchars;
741 	}
742     }
743 
744     return result;
745 }
746 #endif
747 
748 /*
749  * Clear highlight information for a given anchor
750  * (text was allocated in the pool).
751  */
LYClearHiText(TextAnchor * a)752 static void LYClearHiText(TextAnchor *a)
753 {
754     FREE(a->lites.hl_info);
755 
756     a->lites.hl_base.hl_text = NULL;
757     a->lites.hl_len = 0;
758 }
759 
760 #define LYFreeHiText(a)     FREE((a)->lites.hl_info)
761 
762 /*
763  * Set the initial highlight information for a given anchor.
764  */
LYSetHiText(TextAnchor * a,const char * text,unsigned len)765 static void LYSetHiText(TextAnchor *a,
766 			const char *text,
767 			unsigned len)
768 {
769     if (text != NULL) {
770 	POOLallocstring(a->lites.hl_base.hl_text, len + 1);
771 	memcpy(a->lites.hl_base.hl_text, text, (size_t) len);
772 	*(a->lites.hl_base.hl_text + len) = '\0';
773 
774 	a->lites.hl_len = 1;
775     }
776 }
777 
778 /*
779  * Add highlight information for the next line of a anchor.
780  */
LYAddHiText(TextAnchor * a,const char * text,int x)781 static void LYAddHiText(TextAnchor *a,
782 			const char *text,
783 			int x)
784 {
785     HiliteInfo *have = a->lites.hl_info;
786     size_t need = (unsigned) (a->lites.hl_len - 1);
787     size_t want;
788 
789     a->lites.hl_len = (short) (a->lites.hl_len + 1);
790     want = (size_t) (a->lites.hl_len) * sizeof(HiliteInfo);
791     if (have != NULL) {
792 	have = (HiliteInfo *) realloc(have, want);
793     } else {
794 	have = (HiliteInfo *) malloc(want);
795     }
796     a->lites.hl_info = have;
797 
798     POOLallocstring(have[need].hl_text, strlen(text) + 1);
799     strcpy(have[need].hl_text, text);
800     have[need].hl_x = (short) x;
801 }
802 
803 /*
804  * Return an offset to skip leading blanks in the highlighted link.  That is
805  * needed to avoid having the color-style paint the leading blanks.
806  */
807 #ifdef USE_COLOR_STYLE
LYAdjHiTextPos(TextAnchor * a,int count)808 static int LYAdjHiTextPos(TextAnchor *a, int count)
809 {
810     char *result;
811 
812     if (count >= a->lites.hl_len)
813 	result = NULL;
814     else if (count > 0)
815 	result = a->lites.hl_info[count - 1].hl_text;
816     else
817 	result = a->lites.hl_base.hl_text;
818 
819     return (result != 0) ? (int) (LYSkipBlanks(result) - result) : 0;
820 }
821 
822 #else
823 #define LYAdjHiTextPos(a,count) 0
824 #endif
825 
826 /*
827  * Get the highlight text, counting from zero.
828  */
LYGetHiTextStr(TextAnchor * a,int count)829 static char *LYGetHiTextStr(TextAnchor *a, int count)
830 {
831     char *result;
832 
833     if (count >= a->lites.hl_len)
834 	result = NULL;
835     else if (count > 0)
836 	result = a->lites.hl_info[count - 1].hl_text;
837     else
838 	result = a->lites.hl_base.hl_text;
839     result += LYAdjHiTextPos(a, count);
840     return result;
841 }
842 
843 /*
844  * Get the X-ordinate at which to draw the corresponding highlight-text
845  */
LYGetHiTextPos(TextAnchor * a,int count)846 static int LYGetHiTextPos(TextAnchor *a, int count)
847 {
848     int result;
849 
850     if (count >= a->lites.hl_len)
851 	result = -1;
852     else if (count > 0)
853 	result = a->lites.hl_info[count - 1].hl_x;
854     else
855 	result = a->line_pos;
856     result += LYAdjHiTextPos(a, count);
857     return result;
858 }
859 
860 /*
861  * Copy highlighting information from anchor 'b' to 'a'.
862  */
LYCopyHiText(TextAnchor * a,TextAnchor * b)863 static void LYCopyHiText(TextAnchor *a, TextAnchor *b)
864 {
865     int count;
866     char *s;
867 
868     LYClearHiText(a);
869     for (count = 0;; ++count) {
870 	if ((s = LYGetHiTextStr(b, count)) == NULL)
871 	    break;
872 	if (count == 0) {
873 	    LYSetHiText(a, s, (unsigned) strlen(s));
874 	} else {
875 	    LYAddHiText(a, s, LYGetHiTextPos(b, count));
876 	}
877     }
878 }
879 
HText_getChartransInfo(HText * me)880 static void HText_getChartransInfo(HText *me)
881 {
882     me->UCLYhndl = HTAnchor_getUCLYhndl(me->node_anchor, UCT_STAGE_HTEXT);
883     if (me->UCLYhndl < 0) {
884 	int chndl = current_char_set;
885 
886 	HTAnchor_setUCInfoStage(me->node_anchor, chndl,
887 				UCT_STAGE_HTEXT, UCT_SETBY_STRUCTURED);
888 	me->UCLYhndl = HTAnchor_getUCLYhndl(me->node_anchor,
889 					    UCT_STAGE_HTEXT);
890     }
891     me->UCI = HTAnchor_getUCInfoStage(me->node_anchor, UCT_STAGE_HTEXT);
892 }
893 
PerFormInfo_free(PerFormInfo * form)894 static void PerFormInfo_free(PerFormInfo * form)
895 {
896     if (form) {
897 	FREE(form->data.submit_action);
898 	FREE(form->data.submit_enctype);
899 	FREE(form->data.submit_title);
900 	FREE(form->accept_cs);
901 	FREE(form->thisacceptcs);
902 	FREE(form);
903     }
904 }
905 
free_form_fields(FormInfo * input_field)906 static void free_form_fields(FormInfo * input_field)
907 {
908     /*
909      * Free form fields.
910      */
911     if (input_field->type == F_OPTION_LIST_TYPE &&
912 	input_field->select_list != NULL) {
913 	/*
914 	 * Free off option lists if present.
915 	 * It should always be present for F_OPTION_LIST_TYPE
916 	 * unless we had invalid markup which prevented
917 	 * HText_setLastOptionValue from finishing its job
918 	 * and left the input field in an insane state.  - kw
919 	 */
920 	OptionType *optptr = input_field->select_list;
921 	OptionType *tmp;
922 
923 	while (optptr) {
924 	    tmp = optptr;
925 	    optptr = tmp->next;
926 	    FREE(tmp->name);
927 	    FREE(tmp->cp_submit_value);
928 	    FREE(tmp);
929 	}
930 	input_field->select_list = NULL;
931 	/*
932 	 * Don't free the value field on option
933 	 * lists since it points to a option value
934 	 * same for orig value.
935 	 */
936 	input_field->value = NULL;
937 	input_field->orig_value = NULL;
938 	input_field->cp_submit_value = NULL;
939 	input_field->orig_submit_value = NULL;
940     } else {
941 	FREE(input_field->value);
942 	FREE(input_field->orig_value);
943 	FREE(input_field->cp_submit_value);
944 	FREE(input_field->orig_submit_value);
945     }
946     FREE(input_field->name);
947     FREE(input_field->submit_action);
948     FREE(input_field->submit_enctype);
949     FREE(input_field->submit_title);
950 
951     FREE(input_field->accept_cs);
952 }
953 
FormList_delete(HTList * forms)954 static void FormList_delete(HTList *forms)
955 {
956     HTList *cur = forms;
957     PerFormInfo *form;
958 
959     while ((form = (PerFormInfo *) HTList_nextObject(cur)) != NULL)
960 	PerFormInfo_free(form);
961     HTList_delete(forms);
962 }
963 
964 #ifdef DISP_PARTIAL
ResetPartialLinenos(HText * text)965 static void ResetPartialLinenos(HText *text)
966 {
967     if (text != 0) {
968 	text->first_lineno_last_disp_partial = -1;
969 	text->last_lineno_last_disp_partial = -1;
970     }
971 }
972 #endif
973 
974 /*			Creation Method
975  *			---------------
976  */
HText_new(HTParentAnchor * anchor)977 HText *HText_new(HTParentAnchor *anchor)
978 {
979 #if defined(VMS) && defined(VAXC) && !defined(__DECC)
980 #include <lib$routines.h>
981     int status, VMType = 3, VMTotal;
982 #endif /* VMS && VAXC && !__DECC */
983     HTLine *line = NULL;
984     HText *self = typecalloc(HText);
985 
986     if (!self)
987 	outofmem(__FILE__, "HText_New");
988 
989     CTRACE((tfp, "GridText: start HText_new\n"));
990 
991 #if defined(VMS) && defined (VAXC) && !defined(__DECC)
992     status = lib$stat_vm(&VMType, &VMTotal);
993     CTRACE((tfp, "GridText: VMTotal = %d\n", VMTotal));
994 #endif /* VMS && VAXC && !__DECC */
995 
996     /*
997      * If the previously shown text had UTF-8 characters on screen,
998      * remember this in the newly created object.  Do this now, before
999      * the previous object may become invalid.  - kw
1000      */
1001     if (HTMainText) {
1002 	if (HText_hasUTF8OutputSet(HTMainText) &&
1003 	    HTLoadedDocumentEightbit() &&
1004 	    IS_UTF8_TTY) {
1005 	    self->had_utf8 = HTMainText->has_utf8;
1006 	} else {
1007 	    self->had_utf8 = HTMainText->has_utf8;
1008 	}
1009 	HTMainText->has_utf8 = NO;
1010     }
1011 
1012     if (!loaded_texts) {
1013 	loaded_texts = HTList_new();
1014 #ifdef LY_FIND_LEAKS
1015 	atexit(free_all_texts);
1016 #endif
1017     }
1018 
1019     /*
1020      * Links between anchors & documents are a 1-1 relationship.  If
1021      * an anchor is already linked to a document we didn't call
1022      * HTuncache_current_document(), so we'll check now
1023      * and free it before reloading.  - Dick Wesseling (ftu@fi.ruu.nl)
1024      */
1025     if (anchor->document) {
1026 	HTList_removeObject(loaded_texts, anchor->document);
1027 	CTRACE((tfp, "GridText: Auto-uncaching\n"));
1028 
1029 	HTAnchor_delete_links(anchor);
1030 	((HText *) anchor->document)->node_anchor = NULL;
1031 	HText_free((HText *) anchor->document);
1032 	anchor->document = NULL;
1033     }
1034 
1035     HTList_addObject(loaded_texts, self);
1036 #if defined(VMS) && defined(VAXC) && !defined(__DECC)
1037     while (HTList_count(loaded_texts) > HTCacheSize &&
1038 	   VMTotal > HTVirtualMemorySize)
1039 #else
1040     if (HTList_count(loaded_texts) > HTCacheSize)
1041 #endif /* VMS && VAXC && !__DECC */
1042     {
1043 	CTRACE((tfp, "GridText: Freeing off cached doc.\n"));
1044 	HText_free((HText *) HTList_removeFirstObject(loaded_texts));
1045 #if defined(VMS) && defined (VAXC) && !defined(__DECC)
1046 	status = lib$stat_vm(&VMType, &VMTotal);
1047 	CTRACE((tfp, "GridText: VMTotal reduced to %d\n", VMTotal));
1048 #endif /* VMS && VAXC && !__DECC */
1049     }
1050 
1051     self->pool = POOL_NEW();
1052     if (!self->pool)
1053 	outofmem(__FILE__, "HText_New");
1054 
1055     line = self->last_line = TEMP_LINE(self, 0);
1056     line->next = line->prev = line;
1057     line->offset = line->size = 0;
1058     line->data[line->size] = '\0';
1059 #ifdef USE_COLOR_STYLE
1060     line->numstyles = 0;
1061     line->styles = stylechanges_buffers[0];
1062 #endif
1063     self->Lines = 0;
1064     self->first_anchor = self->last_anchor = NULL;
1065     self->last_anchor_before_split = NULL;
1066     self->style = &default_style;
1067     self->top_of_screen = 0;
1068     self->node_anchor = anchor;
1069     self->last_anchor_number = 0;	/* Numbering of them for references */
1070     self->stale = YES;
1071     self->toolbar = NO;
1072     self->tabs = NULL;
1073     self->next_number = 1;
1074 #ifdef USE_SOURCE_CACHE
1075     /*
1076      * Remember the parse settings.
1077      */
1078     self->clickable_images = clickable_images;
1079     self->pseudo_inline_alts = pseudo_inline_alts;
1080     self->verbose_img = verbose_img;
1081     self->raw_mode = LYUseDefaultRawMode;
1082     self->historical_comments = historical_comments;
1083     self->minimal_comments = minimal_comments;
1084     self->soft_dquotes = soft_dquotes;
1085     self->old_dtd = (short) Old_DTD;
1086     self->keypad_mode = (short) keypad_mode;
1087     self->disp_lines = (short) LYlines;
1088     self->disp_cols = (short) DISPLAY_COLS;
1089 #endif
1090     /*
1091      * If we are going to render the List Page, always merge in hidden
1092      * links to get the numbering consistent if form fields are numbered
1093      * and show up as hidden links in the list of links.
1094      * If we are going to render a bookmark file, also always merge in
1095      * hidden links, to get the link numbers consistent with the counting
1096      * in remove_bookmark_link().  Normally a bookmark file shouldn't
1097      * contain any entries with empty titles, but it might happen.  - kw
1098      */
1099     if (anchor->bookmark ||
1100 	LYIsUIPage3(anchor->address, UIP_LIST_PAGE, 0) ||
1101 	LYIsUIPage3(anchor->address, UIP_ADDRLIST_PAGE, 0))
1102 	self->hiddenlinkflag = HIDDENLINKS_MERGE;
1103     else
1104 	self->hiddenlinkflag = LYHiddenLinks;
1105     self->hidden_links = NULL;
1106     self->no_cache = (BOOLEAN) ((anchor->no_cache ||
1107 				 anchor->post_data)
1108 				? YES
1109 				: NO);
1110     self->LastChar = '\0';
1111 
1112 #ifndef USE_PRETTYSRC
1113     if (HTOutputFormat == WWW_SOURCE)
1114 	self->source = YES;
1115     else
1116 	self->source = NO;
1117 #else
1118     /* mark_htext_as_source == TRUE if we are parsing html file (and psrc_view
1119      * is set temporary to false at creation time)
1120      *
1121      * psrc_view == TRUE if source of the text produced by some lynx module
1122      * (like ftp browsers) is requested).  - VH
1123      */
1124     self->source = (BOOL) (LYpsrc
1125 			   ? mark_htext_as_source || psrc_view
1126 			   : HTOutputFormat == WWW_SOURCE);
1127     mark_htext_as_source = FALSE;
1128 #endif
1129     HTAnchor_setDocument(anchor, (HyperDoc *) self);
1130     HTFormNumber = 0;		/* no forms started yet */
1131     HTMainText = self;
1132     HTMainAnchor = anchor;
1133     self->display_on_the_fly = 0;
1134     self->kcode = NOKANJI;
1135     self->specified_kcode = NOKANJI;
1136 #ifdef USE_TH_JP_AUTO_DETECT
1137     self->detected_kcode = DET_NOTYET;
1138     self->SJIS_status = SJIS_state_neutral;
1139     self->EUC_status = EUC_state_neutral;
1140 #endif
1141     self->state = S_text;
1142     self->kanji_buf = '\0';
1143     self->in_sjis = 0;
1144     self->have_8bit_chars = NO;
1145     HText_getChartransInfo(self);
1146     UCSetTransParams(&self->T,
1147 		     self->UCLYhndl, self->UCI,
1148 		     current_char_set,
1149 		     &LYCharSet_UC[current_char_set]);
1150 
1151     /*
1152      * Check the kcode setting if the anchor has a charset element.  -FM
1153      */
1154     HText_setKcode(self, anchor->charset,
1155 		   HTAnchor_getUCInfoStage(anchor, UCT_STAGE_HTEXT));
1156 
1157     /*
1158      * Check to see if our underline and star_string need initialization
1159      * if the underline is not filled with dots.
1160      */
1161     if (underscore_string[0] != '.') {
1162 	/*
1163 	 * Create an array of dots for the UNDERSCORES macro.  -FM
1164 	 */
1165 	memset(underscore_string, '.', (size_t) (MAX_LINE - 1));
1166 	underscore_string[(MAX_LINE - 1)] = '\0';
1167 	underscore_string[MAX_LINE] = '\0';
1168 	/*
1169 	 * Create an array of underscores for the STARS macro.  -FM
1170 	 */
1171 	memset(star_string, '_', (size_t) (MAX_LINE - 1));
1172 	star_string[(MAX_LINE - 1)] = '\0';
1173 	star_string[MAX_LINE] = '\0';
1174     }
1175 
1176     underline_on = FALSE;	/* reset */
1177     bold_on = FALSE;
1178 
1179 #ifdef DISP_PARTIAL
1180     /*
1181      * By this function we create HText object
1182      * so we may start displaying the document while downloading. - LP
1183      */
1184     if (display_partial_flag) {
1185 	display_partial = TRUE;	/* enable HTDisplayPartial() */
1186 	NumOfLines_partial = 0;	/* initialize */
1187     }
1188 
1189     /*
1190      * These two fields should only be set to valid line numbers
1191      * by calls of display_page during partial displaying.  This
1192      * is just so that the FIRST display_page AFTER that can avoid
1193      * repainting the same lines on the screen.  - kw
1194      */
1195     ResetPartialLinenos(self);
1196 #endif
1197 
1198 #ifdef USE_JUSTIFY_ELTS
1199     ht_justify_cleanup();
1200 #endif
1201     return self;
1202 }
1203 
1204 /*			Creation Method 2
1205  *			---------------
1206  *
1207  *      Stream is assumed open and left open.
1208  */
HText_new2(HTParentAnchor * anchor,HTStream * stream)1209 HText *HText_new2(HTParentAnchor *anchor,
1210 		  HTStream *stream)
1211 {
1212     HText *result = HText_new(anchor);
1213 
1214     if (stream) {
1215 	result->target = stream;
1216 	result->targetClass = *stream->isa;	/* copy action procedures */
1217     }
1218     return result;
1219 }
1220 
1221 /*	Free Entire Text
1222  *	----------------
1223  */
HText_free(HText * self)1224 void HText_free(HText *self)
1225 {
1226     if (!self)
1227 	return;
1228 
1229 #if HTLINE_NOT_IN_POOL
1230     {
1231 	HTLine *f = FirstHTLine(self);
1232 	HTLine *l = self->last_line;
1233 
1234 	while (l != f) {	/* Free off line array */
1235 	    self->last_line = l->prev;
1236 	    freeHTLine(self, l);
1237 	    l = self->last_line;
1238 	}
1239 	freeHTLine(self, f);
1240     }
1241 #endif
1242 
1243     while (self->first_anchor) {	/* Free off anchor array */
1244 	TextAnchor *l = self->first_anchor;
1245 
1246 	self->first_anchor = l->next;
1247 
1248 	if (l->link_type == INPUT_ANCHOR && l->input_field) {
1249 	    free_form_fields(l->input_field);
1250 	}
1251 
1252 	LYFreeHiText(l);
1253     }
1254     FormList_delete(self->forms);
1255 
1256     /*
1257      * Free the tabs list.  -FM
1258      */
1259     if (self->tabs) {
1260 	HTTabID *Tab = NULL;
1261 	HTList *cur = self->tabs;
1262 
1263 	while (NULL != (Tab = (HTTabID *) HTList_nextObject(cur))) {
1264 	    FREE(Tab->name);
1265 	    FREE(Tab);
1266 	}
1267 	HTList_delete(self->tabs);
1268 	self->tabs = NULL;
1269     }
1270 
1271     /*
1272      * Free the hidden links list.  -FM
1273      */
1274     if (self->hidden_links) {
1275 	LYFreeStringList(self->hidden_links);
1276 	self->hidden_links = NULL;
1277     }
1278 
1279     /*
1280      * Invoke HTAnchor_delete() to free the node_anchor
1281      * if it is not a destination of other links.  -FM
1282      */
1283     if (self->node_anchor) {
1284 	HTAnchor_resetUCInfoStage(self->node_anchor, -1, UCT_STAGE_STRUCTURED,
1285 				  UCT_SETBY_NONE);
1286 	HTAnchor_resetUCInfoStage(self->node_anchor, -1, UCT_STAGE_HTEXT,
1287 				  UCT_SETBY_NONE);
1288 #ifdef USE_SOURCE_CACHE
1289 	/* Remove source cache files and chunks always, even if the
1290 	 * HTAnchor_delete call does not actually remove the anchor.
1291 	 * Keeping them would just be a waste of space - they won't
1292 	 * be used any more after the anchor has been disassociated
1293 	 * from a HText structure. - kw
1294 	 */
1295 	HTAnchor_clearSourceCache(self->node_anchor);
1296 #endif
1297 
1298 	HTAnchor_delete_links(self->node_anchor);
1299 
1300 	HTAnchor_setDocument(self->node_anchor, (HyperDoc *) 0);
1301 
1302 	if (HTAnchor_delete(self->node_anchor->parent))
1303 	    /*
1304 	     * Make sure HTMainAnchor won't point
1305 	     * to an invalid structure.  - KW
1306 	     */
1307 	    HTMainAnchor = NULL;
1308     }
1309 
1310     POOL_FREE(self->pool);
1311     FREE(self);
1312 }
1313 
1314 /*		Display Methods
1315  *		---------------
1316  */
1317 
1318 /*	Output a line
1319  *	-------------
1320  */
display_line(HTLine * line,HText * text,int scrline GCC_UNUSED,const char * target GCC_UNUSED)1321 static int display_line(HTLine *line,
1322 			HText *text,
1323 			int scrline GCC_UNUSED,
1324 			const char *target GCC_UNUSED)
1325 {
1326     register int i, j;
1327     char buffer[7];
1328     char *data;
1329     size_t utf_extra = 0;
1330     char LastDisplayChar = ' ';
1331 
1332 #ifdef USE_COLOR_STYLE
1333     int current_style = 0;
1334 
1335 #define inunderline NO
1336 #define inbold NO
1337 #else
1338     BOOL inbold = NO, inunderline = NO;
1339 #endif
1340 #if defined(SHOW_WHEREIS_TARGETS) && !defined(USE_COLOR_STYLE)
1341     const char *cp_tgt;
1342     int i_start_tgt = 0, i_after_tgt;
1343     int HitOffset, LenNeeded;
1344     BOOL intarget = NO;
1345 
1346 #else
1347 #define intarget NO
1348 #endif /* SHOW_WHEREIS_TARGETS && !USE_COLOR_STYLE */
1349 
1350 #if !(defined(NCURSES_VERSION) || defined(WIDEC_CURSES))
1351     text->has_utf8 = NO;	/* use as per-line flag, except with ncurses */
1352 #endif
1353 
1354 #if defined(WIDEC_CURSES)
1355     /*
1356      * FIXME: this should not be necessary, but in some wide-character pages
1357      * the output line wraps, foiling our attempt to just use newlines to
1358      * advance to the next page.
1359      */
1360     LYmove(scrline + TITLE_LINES - 1, 0);
1361 #endif
1362 
1363     /*
1364      * Set up the multibyte character buffer,
1365      * and clear the line to which we will be
1366      * writing.
1367      */
1368     buffer[0] = buffer[1] = buffer[2] = '\0';
1369     LYclrtoeol();
1370 
1371     /*
1372      * Add offset, making sure that we do not
1373      * go over the COLS limit on the display.
1374      */
1375     j = (int) line->offset;
1376     if (j >= DISPLAY_COLS)
1377 	j = DISPLAY_COLS - 1;
1378 #ifdef USE_SLANG
1379     SLsmg_forward(j);
1380     i = j;
1381 #else
1382 #ifdef USE_COLOR_STYLE
1383     if (line->size == 0)
1384 	i = j;
1385     else
1386 #endif
1387 	for (i = 0; i < j; i++)
1388 	    LYaddch(' ');
1389 #endif /* USE_SLANG */
1390 
1391     /*
1392      * Add the data, making sure that we do not
1393      * go over the COLS limit on the display.
1394      */
1395     data = line->data;
1396     i++;
1397 
1398 #ifndef USE_COLOR_STYLE
1399 #if defined(SHOW_WHEREIS_TARGETS)
1400     /*
1401      * If the target is on this line, it will be emphasized.
1402      */
1403     i_after_tgt = i;
1404     if (target) {
1405 	cp_tgt = LYno_attr_mb_strstr(data,
1406 				     target,
1407 				     text->T.output_utf8, YES,
1408 				     &HitOffset,
1409 				     &LenNeeded);
1410 	if (cp_tgt) {
1411 	    if (((int) line->offset + LenNeeded) >= DISPLAY_COLS) {
1412 		cp_tgt = NULL;
1413 	    } else {
1414 		text->page_has_target = YES;
1415 		i_start_tgt = i + HitOffset;
1416 		i_after_tgt = i + LenNeeded;
1417 	    }
1418 	}
1419     } else {
1420 	cp_tgt = NULL;
1421     }
1422 #endif /* SHOW_WHEREIS_TARGETS */
1423 #endif /* USE_COLOR_STYLE */
1424 
1425     while ((i <= DISPLAY_COLS) && ((buffer[0] = *data) != '\0')) {
1426 
1427 #ifndef USE_COLOR_STYLE
1428 #if defined(SHOW_WHEREIS_TARGETS)
1429 	if (cp_tgt && i >= i_after_tgt) {
1430 	    if (intarget) {
1431 		cp_tgt = LYno_attr_mb_strstr(data,
1432 					     target,
1433 					     text->T.output_utf8, YES,
1434 					     &HitOffset,
1435 					     &LenNeeded);
1436 		if (cp_tgt) {
1437 		    i_start_tgt = i + HitOffset;
1438 		    i_after_tgt = i + LenNeeded;
1439 		}
1440 		if (!cp_tgt || i_start_tgt != i) {
1441 		    LYstopTargetEmphasis();
1442 		    intarget = NO;
1443 		    if (inbold)
1444 			lynx_start_bold();
1445 		    if (inunderline)
1446 			lynx_start_underline();
1447 		}
1448 	    }
1449 	}
1450 #endif /* SHOW_WHEREIS_TARGETS */
1451 #endif /* USE_COLOR_STYLE */
1452 
1453 	data++;
1454 
1455 #if defined(USE_COLOR_STYLE)
1456 #define CStyle line->styles[current_style]
1457 
1458 	while (current_style < line->numstyles &&
1459 	       i >= (int) (CStyle.sc_horizpos + line->offset + 1)) {
1460 	    LynxChangeStyle(CStyle.sc_style, CStyle.sc_direction);
1461 	    current_style++;
1462 	}
1463 #endif
1464 	switch (buffer[0]) {
1465 
1466 #ifndef USE_COLOR_STYLE
1467 	case LY_UNDERLINE_START_CHAR:
1468 	    if (dump_output_immediately && use_underscore) {
1469 		LYaddch('_');
1470 		i++;
1471 	    } else {
1472 		inunderline = YES;
1473 		if (!intarget) {
1474 #if defined(PDCURSES)
1475 		    if (LYShowColor == SHOW_COLOR_NEVER)
1476 			lynx_start_bold();
1477 		    else
1478 			lynx_start_underline();
1479 #else
1480 		    lynx_start_underline();
1481 #endif /* PDCURSES */
1482 		}
1483 	    }
1484 	    break;
1485 
1486 	case LY_UNDERLINE_END_CHAR:
1487 	    if (dump_output_immediately && use_underscore) {
1488 		LYaddch('_');
1489 		i++;
1490 	    } else {
1491 		inunderline = NO;
1492 		if (!intarget) {
1493 #if defined(PDCURSES)
1494 		    if (LYShowColor == SHOW_COLOR_NEVER)
1495 			lynx_stop_bold();
1496 		    else
1497 			lynx_stop_underline();
1498 #else
1499 		    lynx_stop_underline();
1500 #endif /* PDCURSES */
1501 		}
1502 	    }
1503 	    break;
1504 
1505 	case LY_BOLD_START_CHAR:
1506 	    inbold = YES;
1507 	    if (!intarget)
1508 		lynx_start_bold();
1509 	    break;
1510 
1511 	case LY_BOLD_END_CHAR:
1512 	    inbold = NO;
1513 	    if (!intarget)
1514 		lynx_stop_bold();
1515 	    break;
1516 
1517 #endif /* !USE_COLOR_STYLE */
1518 	case LY_SOFT_NEWLINE:
1519 	    if (!dump_output_immediately) {
1520 		LYaddch('+');
1521 		i++;
1522 #if defined(SHOW_WHEREIS_TARGETS) && !defined(USE_COLOR_STYLE)
1523 		i_after_tgt++;
1524 #endif
1525 	    }
1526 	    break;
1527 
1528 	case LY_SOFT_HYPHEN:
1529 	    if (*data != '\0' ||
1530 		isspace(UCH(LastDisplayChar)) ||
1531 		LastDisplayChar == '-') {
1532 		/*
1533 		 * Ignore the soft hyphen if it is not the last character in
1534 		 * the line.  Also ignore it if is first character following
1535 		 * the margin, or if it is preceded by a white character (we
1536 		 * loaded 'M' into LastDisplayChar if it was a multibyte
1537 		 * character) or hyphen, though it should have been excluded by
1538 		 * HText_appendCharacter() or by split_line() in those cases.
1539 		 * -FM
1540 		 */
1541 		break;
1542 	    } else {
1543 		/*
1544 		 * Make it a hard hyphen and fall through.  -FM
1545 		 */
1546 		buffer[0] = '-';
1547 	    }
1548 	    /* FALLTHRU */
1549 
1550 	default:
1551 #ifndef USE_COLOR_STYLE
1552 #if defined(SHOW_WHEREIS_TARGETS)
1553 	    if (!intarget && cp_tgt && i >= i_start_tgt) {
1554 		/*
1555 		 * Start the emphasis.
1556 		 */
1557 		if (data > cp_tgt) {
1558 		    LYstartTargetEmphasis();
1559 		    intarget = YES;
1560 		}
1561 	    }
1562 #endif /* SHOW_WHEREIS_TARGETS */
1563 #endif /* USE_COLOR_STYLE */
1564 	    if (text->T.output_utf8 && is8bits(buffer[0])) {
1565 		text->has_utf8 = YES;
1566 		utf_extra = utf8_length(text->T.output_utf8, data - 1);
1567 		LastDisplayChar = 'M';
1568 	    }
1569 	    if (utf_extra) {
1570 		LYStrNCpy(&buffer[1], data, utf_extra);
1571 		LYaddstr(buffer);
1572 		buffer[1] = '\0';
1573 		data += utf_extra;
1574 		utf_extra = 0;
1575 	    } else if (is_CJK2(buffer[0])) {
1576 		/*
1577 		 * For CJK strings, by Masanobu Kimura.
1578 		 */
1579 		if (i <= DISPLAY_COLS) {
1580 		    buffer[1] = *data;
1581 		    buffer[2] = '\0';
1582 		    data++;
1583 		    i++;
1584 		    LYaddstr(buffer);
1585 		    buffer[1] = '\0';
1586 		    /*
1587 		     * For now, load 'M' into LastDisplayChar, but we should
1588 		     * check whether it's white and if so, use ' '.  I don't
1589 		     * know if there actually are white CJK characters, and
1590 		     * we're loading ' ' for multibyte spacing characters in
1591 		     * this code set, but this will become an issue when the
1592 		     * development code set's multibyte character handling is
1593 		     * used.  -FM
1594 		     */
1595 		    LastDisplayChar = 'M';
1596 #ifndef USE_SLANG
1597 		    {
1598 			int y, x;
1599 
1600 			getyx(LYwin, y, x);
1601 			(void) y;
1602 			if (x >= DISPLAY_COLS || x == 0)
1603 			    break;
1604 		    }
1605 #endif
1606 		}
1607 	    } else {
1608 		LYaddstr(buffer);
1609 		LastDisplayChar = buffer[0];
1610 	    }
1611 	    i++;
1612 	}			/* end of switch */
1613     }				/* end of while */
1614 
1615 #if !(defined(NCURSES_VERSION) || defined(WIDEC_CURSES))
1616     if (text->has_utf8) {
1617 	LYtouchline(scrline);
1618 	text->has_utf8 = NO;	/* we had some, but have dealt with it. */
1619     }
1620 #endif
1621     /*
1622      * Add the return.
1623      */
1624     LYaddch('\n');
1625 
1626 #if defined(SHOW_WHEREIS_TARGETS) && !defined(USE_COLOR_STYLE)
1627     if (intarget)
1628 	LYstopTargetEmphasis();
1629 #else
1630 #undef intarget
1631 #endif /* SHOW_WHEREIS_TARGETS && !USE_COLOR_STYLE */
1632 #ifndef USE_COLOR_STYLE
1633     lynx_stop_underline();
1634     lynx_stop_bold();
1635 #else
1636     while (current_style < line->numstyles) {
1637 	LynxChangeStyle(CStyle.sc_style, CStyle.sc_direction);
1638 	current_style++;
1639     }
1640 #undef CStyle
1641 #endif
1642     return (0);
1643 }
1644 
1645 /*	Output the title line
1646  *	---------------------
1647  */
display_title(HText * text)1648 static void display_title(HText *text)
1649 {
1650     char *title = NULL;
1651     char percent[20];
1652     unsigned char *tmp = NULL;
1653     int i = 0, j = 0;
1654     int limit;
1655 
1656 #ifdef USE_COLOR_STYLE
1657     int toolbar = 0;
1658 #endif
1659 
1660     /*
1661      * Make sure we have a text structure.  -FM
1662      */
1663     if (!text)
1664 	return;
1665 
1666     lynx_start_title_color();
1667 #ifdef USE_COLOR_STYLE
1668 /* turn the TITLE style on */
1669     if (last_colorattr_ptr > 0) {
1670 	LynxChangeStyle(s_title, STACK_ON);
1671     } else {
1672 	LynxChangeStyle(s_title, ABS_ON);
1673     }
1674 #endif /* USE_COLOR_STYLE */
1675 
1676     /*
1677      * Load the title field.  -FM
1678      */
1679     StrAllocCopy(title,
1680 		 (HTAnchor_title(text->node_anchor) ?
1681 		  HTAnchor_title(text->node_anchor) : " "));	/* "" -> " " */
1682     LYReduceBlanks(title);
1683 
1684     /*
1685      * Generate the page indicator (percent) string.
1686      */
1687     limit = LYscreenWidth();
1688     if (limit < 10) {
1689 	percent[0] = '\0';
1690     } else if ((display_lines) <= 0 && LYlines > 0 &&
1691 	       text->top_of_screen <= 99999 && text->Lines <= 999999) {
1692 	sprintf(percent, " (l%d of %d)",
1693 		text->top_of_screen, text->Lines);
1694     } else if ((text->Lines >= display_lines) && (display_lines > 0)) {
1695 	int total_pages = ((text->Lines + display_lines)
1696 			   / display_lines);
1697 	int start_of_last_page = ((text->Lines <= display_lines)
1698 				  ? 0
1699 				  : (text->Lines - display_lines));
1700 
1701 	sprintf(percent, " (p%d of %d)",
1702 		((text->top_of_screen > start_of_last_page)
1703 		 ? total_pages
1704 		 : ((text->top_of_screen + display_lines) / (display_lines))),
1705 		total_pages);
1706     } else {
1707 	percent[0] = '\0';
1708     }
1709 
1710     /*
1711      * Generate and display the title string, with page indicator
1712      * if appropriate, preceded by the toolbar token if appropriate,
1713      * and truncated if necessary.  -FM & KW
1714      */
1715     if (IS_CJK_TTY) {
1716 	if (*title &&
1717 	    (tmp = typecallocn(unsigned char, (strlen(title) * 2 + 256)))) {
1718 	    if (kanji_code == EUC) {
1719 		TO_EUC((unsigned char *) title, tmp);
1720 	    } else if (kanji_code == SJIS) {
1721 		TO_SJIS((unsigned char *) title, tmp);
1722 	    } else {
1723 		for (i = 0, j = 0; title[i]; i++) {
1724 		    if (title[i] != CH_ESC) {	/* S/390 -- gil -- 1487 */
1725 			tmp[j++] = UCH(title[i]);
1726 		    }
1727 		}
1728 		tmp[j] = '\0';
1729 	    }
1730 	    StrAllocCopy(title, (const char *) tmp);
1731 	    FREE(tmp);
1732 	}
1733     }
1734     LYmove(0, 0);
1735     LYclrtoeol();
1736 #if defined(SH_EX) && defined(KANJI_CODE_OVERRIDE)
1737     LYaddstr(str_kcode(last_kcode));
1738 #endif
1739     if (HText_hasToolbar(text)) {
1740 	LYaddch('#');
1741 #ifdef USE_COLOR_STYLE
1742 	toolbar = 1;
1743 #endif
1744     }
1745 #ifdef USE_COLOR_STYLE
1746     if (s_forw_backw != NOSTYLE && (nhist || nhist_extra > 1)) {
1747 	chtype c = nhist ? ACS_LARROW : ' ';
1748 
1749 	/* turn the FORWBACKW.ARROW style on */
1750 	LynxChangeStyle(s_forw_backw, STACK_ON);
1751 	if (nhist) {
1752 	    LYaddch(c);
1753 	    LYaddch(c);
1754 	    LYaddch(c);
1755 	} else
1756 	    LYmove(0, 3 + toolbar);
1757 	if (nhist_extra > 1) {
1758 	    LYaddch(ACS_RARROW);
1759 	    LYaddch(ACS_RARROW);
1760 	    LYaddch(ACS_RARROW);
1761 	}
1762 	LynxChangeStyle(s_forw_backw, STACK_OFF);
1763     }
1764 #endif /* USE_COLOR_STYLE */
1765 #ifdef WIDEC_CURSES
1766     i = limit - LYbarWidth - (int) strlen(percent) - LYstrCells(title);
1767     if (i <= 0) {		/* title is truncated */
1768 	i = limit - LYbarWidth - (int) strlen(percent) - 3;
1769 	if (i <= 0) {		/* no room at all */
1770 	    title[0] = '\0';
1771 	} else {
1772 	    strcpy(title + LYstrFittable(title, i), "...");
1773 	}
1774 	i = 0;
1775     }
1776     LYmove(0, i);
1777 #else
1778     i = (limit - 1) - (int) (strlen(percent) + strlen(title));
1779     if (i >= CHAR_WIDTH) {
1780 	LYmove(0, i);
1781     } else {
1782 	/*
1783 	 * Truncation takes into account the possibility that
1784 	 * multibyte characters might be present.  -HS (H.  Senshu)
1785 	 */
1786 	int last;
1787 
1788 	last = (int) strlen(percent) + CHAR_WIDTH;
1789 	if (limit - 3 >= last) {
1790 	    title[(limit - 3) - last] = '.';
1791 	    title[(limit - 2) - last] = '.';
1792 	    title[(limit - 1) - last] = '\0';
1793 	} else {
1794 	    title[(limit - 1) - last] = '\0';
1795 	}
1796 	LYmove(0, CHAR_WIDTH);
1797     }
1798 #endif
1799     LYaddstr(title);
1800     if (percent[0] != '\0')
1801 	LYaddstr(percent);
1802     LYaddch('\n');
1803     FREE(title);
1804 
1805 #if defined(USE_COLOR_STYLE) && defined(CAN_CUT_AND_PASTE)
1806     if (s_hot_paste != NOSTYLE) {	/* Only if the user set the style */
1807 	LYmove(0, LYcolLimit);
1808 	LynxChangeStyle(s_hot_paste, STACK_ON);
1809 	LYaddch(ACS_RARROW);
1810 	LynxChangeStyle(s_hot_paste, STACK_OFF);
1811 	LYmove(1, 0);		/* As after \n */
1812     }
1813 #endif /* USE_COLOR_STYLE */
1814 
1815 #ifdef USE_COLOR_STYLE
1816 /* turn the TITLE style off */
1817     LynxChangeStyle(s_title, STACK_OFF);
1818 #endif /* USE_COLOR_STYLE */
1819     lynx_stop_title_color();
1820 
1821     return;
1822 }
1823 
1824 /*	Output the scrollbar
1825  *	---------------------
1826  */
1827 #ifdef USE_SCROLLBAR
display_scrollbar(HText * text)1828 static void display_scrollbar(HText *text)
1829 {
1830     int i;
1831     int h = display_lines - 2 * (LYsb_arrow != 0);	/* Height of the scrollbar */
1832     int off = (LYsb_arrow != 0);	/* Start of the scrollbar */
1833     int top_skip, bot_skip, sh, shown;
1834 
1835     LYsb_begin = LYsb_end = -1;
1836     if (!LYShowScrollbar || !text || h <= 2
1837 	|| text->Lines <= display_lines)
1838 	return;
1839 
1840     if (text->top_of_screen >= text->Lines - display_lines) {
1841 	/* Only part of the screen shows actual text */
1842 	shown = text->Lines - text->top_of_screen;
1843 
1844 	if (shown <= 0)
1845 	    shown = 1;
1846     } else
1847 	shown = display_lines;
1848     /* Each cell of scrollbar represents text->Lines/h lines of text. */
1849     /* Always smaller than h */
1850     sh = (shown * h + text->Lines / 2) / text->Lines;
1851     if (sh <= 0)
1852 	sh = 1;
1853     if (sh >= h - 1)
1854 	sh = h - 2;		/* Position at ends indicates BEG and END */
1855 
1856     if (text->top_of_screen == 0)
1857 	top_skip = 0;
1858     else if (text->Lines - (text->top_of_screen + display_lines - 1) <= 0)
1859 	top_skip = h - sh;
1860     else {
1861 	/* text->top_of_screen between 1 and text->Lines - display_lines
1862 	   corresponds to top_skip between 1 and h - sh - 1 */
1863 	/* Use rounding to get as many positions into top_skip==h - sh - 1
1864 	   as into top_skip == 1:
1865 	   1--->1, text->Lines - display_lines + 1--->h - sh. */
1866 	top_skip = (int) (1 +
1867 			  1. * (h - sh - 1) * text->top_of_screen
1868 			  / (text->Lines - display_lines + 1));
1869     }
1870     bot_skip = h - sh - top_skip;
1871 
1872     LYsb_begin = top_skip;
1873     LYsb_end = h - bot_skip;
1874 
1875     if (LYsb_arrow) {
1876 #ifdef USE_COLOR_STYLE
1877 	int s = top_skip ? s_sb_aa : s_sb_naa;
1878 
1879 	if (last_colorattr_ptr > 0) {
1880 	    LynxChangeStyle(s, STACK_ON);
1881 	} else {
1882 	    LynxChangeStyle(s, ABS_ON);
1883 	}
1884 #endif /* USE_COLOR_STYLE */
1885 	LYmove(1, LYcolLimit + LYshiftWin);
1886 	addch_raw(ACS_UARROW);
1887 #ifdef USE_COLOR_STYLE
1888 	LynxChangeStyle(s, STACK_OFF);
1889 #endif /* USE_COLOR_STYLE */
1890     }
1891 #ifdef USE_COLOR_STYLE
1892     if (last_colorattr_ptr > 0) {
1893 	LynxChangeStyle(s_sb_bg, STACK_ON);
1894     } else {
1895 	LynxChangeStyle(s_sb_bg, ABS_ON);
1896     }
1897 #endif /* USE_COLOR_STYLE */
1898 
1899     for (i = 1; i <= h; i++) {
1900 #ifdef USE_COLOR_STYLE
1901 	if (i - 1 <= top_skip && i > top_skip)
1902 	    LynxChangeStyle(s_sb_bar, STACK_ON);
1903 	if (i - 1 <= h - bot_skip && i > h - bot_skip)
1904 	    LynxChangeStyle(s_sb_bar, STACK_OFF);
1905 #endif /* USE_COLOR_STYLE */
1906 	LYmove(i + off, LYcolLimit + LYshiftWin);
1907 	if (i > top_skip && i <= h - bot_skip) {
1908 	    LYaddch(ACS_BLOCK);
1909 	} else {
1910 	    LYaddch(ACS_CKBOARD);
1911 	}
1912     }
1913 #ifdef USE_COLOR_STYLE
1914     LynxChangeStyle(s_sb_bg, STACK_OFF);
1915 #endif /* USE_COLOR_STYLE */
1916 
1917     if (LYsb_arrow) {
1918 #ifdef USE_COLOR_STYLE
1919 	int s = bot_skip ? s_sb_aa : s_sb_naa;
1920 
1921 	if (last_colorattr_ptr > 0) {
1922 	    LynxChangeStyle(s, STACK_ON);
1923 	} else {
1924 	    LynxChangeStyle(s, ABS_ON);
1925 	}
1926 #endif /* USE_COLOR_STYLE */
1927 	LYmove(h + 2, LYcolLimit + LYshiftWin);
1928 	addch_raw(ACS_DARROW);
1929 #ifdef USE_COLOR_STYLE
1930 	LynxChangeStyle(s, STACK_OFF);
1931 #endif /* USE_COLOR_STYLE */
1932     }
1933     return;
1934 }
1935 #else
1936 #define display_scrollbar(text)	/*nothing */
1937 #endif /* USE_SCROLLBAR */
1938 
1939 /*	Output a page
1940  *	-------------
1941  */
display_page(HText * text,int line_number,const char * target)1942 static void display_page(HText *text,
1943 			 int line_number,
1944 			 const char *target)
1945 {
1946     HTLine *line = NULL;
1947     int i;
1948     int title_lines = TITLE_LINES;
1949 
1950 #if defined(USE_COLOR_STYLE) && defined(SHOW_WHEREIS_TARGETS)
1951     const char *cp;
1952 #endif
1953     char tmp[7];
1954     TextAnchor *Anchor_ptr = NULL;
1955     int stop_before_for_anchors;
1956     FormInfo *FormInfo_ptr;
1957     BOOL display_flag = FALSE;
1958     HTAnchor *link_dest;
1959     HTAnchor *link_dest_intl = NULL;
1960     static int last_nlinks = 0;
1961     static int charset_last_displayed = -1;
1962 
1963 #ifdef DISP_PARTIAL
1964     int last_disp_partial = -1;
1965 #endif
1966 
1967     lynx_mode = NORMAL_LYNX_MODE;
1968 
1969     if (text == NULL) {
1970 	/*
1971 	 * Check whether to force a screen clear to enable scrollback,
1972 	 * or as a hack to fix a reverse clear screen problem for some
1973 	 * curses packages.  - shf@access.digex.net & seldon@eskimo.com
1974 	 */
1975 	if (enable_scrollback) {
1976 	    LYaddch('*');
1977 	    LYrefresh();
1978 	    LYclear();
1979 	}
1980 	LYaddstr("\n\nError accessing document!\nNo data available!\n");
1981 	LYrefresh();
1982 	nlinks = 0;		/* set number of links to 0 */
1983 	return;
1984     }
1985 #ifdef DISP_PARTIAL
1986     if (display_partial || recent_sizechange || text->stale) {
1987 	/*  Reset them, will be set near end if all is okay. - kw */
1988 	ResetPartialLinenos(text);
1989     }
1990 #endif /* DISP_PARTIAL */
1991 
1992     tmp[0] = tmp[1] = tmp[2] = '\0';
1993     if (target && *target == '\0')
1994 	target = NULL;
1995     text->page_has_target = NO;
1996     if (display_lines <= 0) {
1997 	/* No screen space to display anything!
1998 	 * returning here makes it more likely we will survive if
1999 	 * an xterm is temporarily made very small.  - kw */
2000 	return;
2001     }
2002 
2003     line_number = HText_getPreferredTopLine(text, line_number);
2004 
2005     for (i = 0, line = FirstHTLine(text);	/* Find line */
2006 	 i < line_number && (line != text->last_line);
2007 	 i++, line = line->next) {	/* Loop */
2008 #ifndef VMS
2009 	if (!LYNoCore) {
2010 	    assert(line->next != NULL);
2011 	} else if (line->next == NULL) {
2012 	    if (enable_scrollback) {
2013 		LYaddch('*');
2014 		LYrefresh();
2015 		LYclear();
2016 	    }
2017 	    LYaddstr("\n\nError drawing page!\nBad HText structure!\n");
2018 	    LYrefresh();
2019 	    nlinks = 0;		/* set number of links to 0 */
2020 	    return;
2021 	}
2022 #else
2023 	assert(line->next != NULL);
2024 #endif /* !VMS */
2025     }				/* Loop */
2026 
2027     if (LYlowest_eightbit[current_char_set] <= 255 &&
2028 	(current_char_set != charset_last_displayed) &&
2029     /*
2030      * current_char_set has changed since last invocation,
2031      * and it's not just 7-bit.
2032      * Also we don't want to do this for -dump and -source etc.
2033      */
2034 	LYCursesON) {
2035 #ifdef EXP_CHARTRANS_AUTOSWITCH
2036 	UCChangeTerminalCodepage(current_char_set,
2037 				 &LYCharSet_UC[current_char_set]);
2038 #endif /* EXP_CHARTRANS_AUTOSWITCH */
2039 	charset_last_displayed = current_char_set;
2040     }
2041 
2042     /*
2043      * Check whether to force a screen clear to enable scrollback,
2044      * or as a hack to fix a reverse clear screen problem for some
2045      * curses packages.  - shf@access.digex.net & seldon@eskimo.com
2046      */
2047     if (enable_scrollback) {
2048 	LYaddch('*');
2049 	LYrefresh();
2050 	LYclear();
2051     }
2052 #ifdef USE_COLOR_STYLE
2053     /*
2054      * Reset stack of color attribute changes to avoid color leaking,
2055      * except if what we last displayed from this text was the previous
2056      * screenful, in which case carrying over the state might be beneficial
2057      * (although it shouldn't generally be needed any more).  - kw
2058      */
2059     if (text->stale ||
2060 	line_number != text->top_of_screen + (display_lines)) {
2061 	last_colorattr_ptr = 0;
2062     }
2063 #endif
2064 
2065     text->top_of_screen = line_number;
2066     text->top_of_screen_line = line;
2067     if (no_title) {
2068 	LYmove(0, 0);
2069 	title_lines = 0;
2070     } else {
2071 	display_title(text);	/* will move cursor to top of screen */
2072     }
2073     display_flag = TRUE;
2074 
2075 #ifdef USE_COLOR_STYLE
2076 #ifdef DISP_PARTIAL
2077     if (display_partial ||
2078 	line_number != text->first_lineno_last_disp_partial ||
2079 	line_number > text->last_lineno_last_disp_partial)
2080 #endif /* DISP_PARTIAL */
2081 	ResetCachedStyles();
2082 #endif /* USE_COLOR_STYLE */
2083 
2084 #ifdef DISP_PARTIAL
2085     if (display_partial && text->stbl) {
2086 	stop_before_for_anchors = Stbl_getStartLineDeep(text->stbl);
2087 	if (stop_before_for_anchors > line_number + (display_lines))
2088 	    stop_before_for_anchors = line_number + (display_lines);
2089     } else
2090 #endif
2091 	stop_before_for_anchors = line_number + (display_lines);
2092 
2093     /*
2094      * Output the page.
2095      */
2096     if (line) {
2097 #if defined(USE_COLOR_STYLE) && defined(SHOW_WHEREIS_TARGETS)
2098 	char *data;
2099 	int offset, LenNeeded;
2100 #endif
2101 #ifdef DISP_PARTIAL
2102 	if (display_partial ||
2103 	    line_number != text->first_lineno_last_disp_partial)
2104 	    text->has_utf8 = NO;
2105 #else
2106 	text->has_utf8 = NO;
2107 #endif
2108 	for (i = 0; i < (display_lines); i++) {
2109 	    /*
2110 	     * Verify and display each line.
2111 	     */
2112 #ifndef VMS
2113 	    if (!LYNoCore) {
2114 		assert(line != NULL);
2115 	    } else if (line == NULL) {
2116 		if (enable_scrollback) {
2117 		    LYaddch('*');
2118 		    LYrefresh();
2119 		    LYclear();
2120 		}
2121 		LYaddstr("\n\nError drawing page!\nBad HText structure!\n");
2122 		LYrefresh();
2123 		nlinks = 0;	/* set number of links to 0 */
2124 		return;
2125 	    }
2126 #else
2127 	    assert(line != NULL);
2128 #endif /* !VMS */
2129 
2130 #ifdef DISP_PARTIAL
2131 	    if (!display_partial &&
2132 		line_number == text->first_lineno_last_disp_partial &&
2133 		i + line_number <= text->last_lineno_last_disp_partial)
2134 		LYmove((i + title_lines + 1), 0);
2135 	    else
2136 #endif
2137 		display_line(line, text, i + 1, target);
2138 
2139 #if defined(SHOW_WHEREIS_TARGETS)
2140 #ifdef USE_COLOR_STYLE		/* otherwise done in display_line - kw */
2141 	    /*
2142 	     * If the target is on this line, recursively
2143 	     * seek and emphasize it.  -FM
2144 	     */
2145 	    data = (char *) line->data;
2146 	    offset = (int) line->offset;
2147 	    while (non_empty(target) &&
2148 		   (cp = LYno_attr_mb_strstr(data,
2149 					     target,
2150 					     text->T.output_utf8, YES,
2151 					     NULL,
2152 					     &LenNeeded)) != NULL &&
2153 		   ((int) line->offset + LenNeeded) <= DISPLAY_COLS) {
2154 		size_t itmp = 0;
2155 		size_t written = 0;
2156 		int x_off = offset + (int) (cp - data);
2157 		size_t len = strlen(target);
2158 		size_t utf_extra = 0;
2159 
2160 		text->page_has_target = YES;
2161 
2162 		/*
2163 		 * Start the emphasis.
2164 		 */
2165 		LYstartTargetEmphasis();
2166 
2167 		/*
2168 		 * Output the target characters.
2169 		 */
2170 		for (;
2171 		     written < len && (tmp[0] = data[itmp]) != '\0';
2172 		     itmp++) {
2173 		    if (IsSpecialAttrChar(tmp[0]) && tmp[0] != LY_SOFT_NEWLINE) {
2174 			/*
2175 			 * Ignore special characters.
2176 			 */
2177 			x_off--;
2178 
2179 		    } else if (&data[itmp] >= cp) {
2180 			if (cp == &data[itmp]) {
2181 			    /*
2182 			     * First printable character of target.
2183 			     */
2184 			    LYmove((i + title_lines),
2185 				   line->offset + LYstrExtent2(line->data,
2186 							       x_off - line->offset));
2187 			}
2188 			/*
2189 			 * Output all the printable target chars.
2190 			 */
2191 			utf_extra = utf8_length(text->T.output_utf8, data + itmp);
2192 			if (utf_extra) {
2193 			    LYStrNCpy(&tmp[1], &line->data[itmp + 1], utf_extra);
2194 			    itmp += utf_extra;
2195 			    LYaddstr(tmp);
2196 			    tmp[1] = '\0';
2197 			    written += (utf_extra + 1);
2198 			} else if (IS_CJK_TTY && is8bits(tmp[0])) {
2199 			    /*
2200 			     * For CJK strings, by Masanobu Kimura.
2201 			     */
2202 			    tmp[1] = data[++itmp];
2203 			    LYaddstr(tmp);
2204 			    tmp[1] = '\0';
2205 			    written += 2;
2206 			} else {
2207 			    LYaddstr(tmp);
2208 			    written++;
2209 			}
2210 		    }
2211 		}
2212 
2213 		/*
2214 		 * Stop the emphasis, and reset the offset and
2215 		 * data pointer for our current position in the
2216 		 * line.  -FM
2217 		 */
2218 		LYstopTargetEmphasis();
2219 		data = (char *) &data[itmp];
2220 		offset = (int) (data - line->data + line->offset);
2221 
2222 	    }			/* end while */
2223 	    LYmove((i + title_lines + 1), 0);
2224 #endif /* USE_COLOR_STYLE */
2225 #endif /* SHOW_WHEREIS_TARGETS */
2226 
2227 	    /*
2228 	     * Stop if this is the last line.  Otherwise, make sure
2229 	     * display_flag is set and process the next line.  -FM
2230 	     */
2231 	    if (line == text->last_line) {
2232 		/*
2233 		 * Clear remaining lines of display.
2234 		 */
2235 		for (i++; i < (display_lines); i++) {
2236 		    LYmove((i + title_lines), 0);
2237 		    LYclrtoeol();
2238 		}
2239 		break;
2240 	    }
2241 #ifdef DISP_PARTIAL
2242 	    if (display_partial) {
2243 		/*
2244 		 * Remember as fully shown during last partial display,
2245 		 * if it was not the last text line.  - kw
2246 		 */
2247 		last_disp_partial = i + line_number;
2248 	    }
2249 #endif /* DISP_PARTIAL */
2250 	    display_flag = TRUE;
2251 	    line = line->next;
2252 	}			/* end of "Verify and display each line." loop */
2253     }
2254     /* end "Output the page." */
2255     text->next_line = line;	/* Line after screen */
2256     text->stale = NO;		/* Display is up-to-date */
2257 
2258     /*
2259      * Add the anchors to Lynx structures.
2260      */
2261     nlinks = 0;
2262     for (Anchor_ptr = text->first_anchor;
2263 	 Anchor_ptr != NULL && Anchor_ptr->line_num <= stop_before_for_anchors;
2264 	 Anchor_ptr = Anchor_ptr->next) {
2265 
2266 	if (Anchor_ptr->line_num >= line_number
2267 	    && Anchor_ptr->line_num < stop_before_for_anchors) {
2268 	    char *hi_string = LYGetHiTextStr(Anchor_ptr, 0);
2269 
2270 	    /*
2271 	     * Load normal hypertext anchors.
2272 	     */
2273 	    if (Anchor_ptr->show_anchor
2274 		&& non_empty(hi_string)
2275 		&& (Anchor_ptr->link_type & HYPERTEXT_ANCHOR)) {
2276 		int count;
2277 		char *s;
2278 
2279 		for (count = 0;; ++count) {
2280 		    s = LYGetHiTextStr(Anchor_ptr, count);
2281 		    if (count == 0)
2282 			LYSetHilite(nlinks, s);
2283 		    if (s == NULL)
2284 			break;
2285 		    if (count != 0) {
2286 			LYAddHilite(nlinks, s, LYGetHiTextPos(Anchor_ptr, count));
2287 		    }
2288 		}
2289 
2290 		links[nlinks].inUnderline = Anchor_ptr->inUnderline;
2291 
2292 		links[nlinks].sgml_offset = Anchor_ptr->sgml_offset;
2293 		links[nlinks].anchor_number = Anchor_ptr->number;
2294 		links[nlinks].anchor_line_num = Anchor_ptr->line_num;
2295 
2296 		link_dest = HTAnchor_followLink(Anchor_ptr->anchor);
2297 		{
2298 		    auto char *cp_AnchorAddress = NULL;
2299 
2300 		    if (traversal) {
2301 			cp_AnchorAddress = stub_HTAnchor_address(link_dest);
2302 		    } else if (track_internal_links) {
2303 			if (Anchor_ptr->link_type == INTERNAL_LINK_ANCHOR) {
2304 			    link_dest_intl = HTAnchor_followTypedLink(Anchor_ptr->anchor,
2305 								      HTInternalLink);
2306 			    if (link_dest_intl && link_dest_intl != link_dest) {
2307 
2308 				CTRACE((tfp,
2309 					"GridText: display_page: unexpected typed link to %s!\n",
2310 					link_dest_intl->parent->address));
2311 				link_dest_intl = NULL;
2312 			    }
2313 			} else {
2314 			    link_dest_intl = NULL;
2315 			}
2316 			if (link_dest_intl) {
2317 			    char *cp2 = HTAnchor_address(link_dest_intl);
2318 
2319 			    cp_AnchorAddress = cp2;
2320 			} else {
2321 			    cp_AnchorAddress = HTAnchor_address(link_dest);
2322 			}
2323 		    } else {
2324 			cp_AnchorAddress = HTAnchor_address(link_dest);
2325 		    }
2326 		    FREE(links[nlinks].lname);
2327 
2328 		    if (cp_AnchorAddress != NULL)
2329 			links[nlinks].lname = cp_AnchorAddress;
2330 		    else
2331 			StrAllocCopy(links[nlinks].lname, empty_string);
2332 		}
2333 
2334 		links[nlinks].lx = Anchor_ptr->line_pos;
2335 		links[nlinks].ly = ((Anchor_ptr->line_num + 1) - line_number);
2336 		if (link_dest_intl)
2337 		    links[nlinks].type = WWW_INTERN_LINK_TYPE;
2338 		else
2339 		    links[nlinks].type = WWW_LINK_TYPE;
2340 		links[nlinks].target = empty_string;
2341 		links[nlinks].l_form = NULL;
2342 
2343 		nlinks++;
2344 		display_flag = TRUE;
2345 
2346 	    } else if (Anchor_ptr->link_type == INPUT_ANCHOR
2347 		       && Anchor_ptr->input_field->type != F_HIDDEN_TYPE) {
2348 		/*
2349 		 * Handle form fields.
2350 		 */
2351 		lynx_mode = FORMS_LYNX_MODE;
2352 
2353 		FormInfo_ptr = Anchor_ptr->input_field;
2354 
2355 		links[nlinks].sgml_offset = Anchor_ptr->sgml_offset;
2356 		links[nlinks].anchor_number = Anchor_ptr->number;
2357 		links[nlinks].anchor_line_num = Anchor_ptr->line_num;
2358 
2359 		links[nlinks].l_form = FormInfo_ptr;
2360 		links[nlinks].lx = Anchor_ptr->line_pos;
2361 		links[nlinks].ly = ((Anchor_ptr->line_num + 1) - line_number);
2362 		links[nlinks].type = WWW_FORM_LINK_TYPE;
2363 		links[nlinks].inUnderline = Anchor_ptr->inUnderline;
2364 		links[nlinks].target = empty_string;
2365 		StrAllocCopy(links[nlinks].lname, empty_string);
2366 
2367 		if (FormInfo_ptr->type == F_RADIO_TYPE) {
2368 		    LYSetHilite(nlinks,
2369 				FormInfo_ptr->num_value
2370 				? checked_radio
2371 				: unchecked_radio);
2372 		} else if (FormInfo_ptr->type == F_CHECKBOX_TYPE) {
2373 		    LYSetHilite(nlinks,
2374 				FormInfo_ptr->num_value
2375 				? checked_box
2376 				: unchecked_box);
2377 		} else if (FormInfo_ptr->type == F_PASSWORD_TYPE) {
2378 		    LYSetHilite(nlinks,
2379 				STARS(LYstrCells(FormInfo_ptr->value)));
2380 		} else {	/* TEXT type */
2381 		    LYSetHilite(nlinks,
2382 				FormInfo_ptr->value);
2383 		}
2384 
2385 		nlinks++;
2386 		/*
2387 		 * Bold the link after incrementing nlinks.
2388 		 */
2389 		LYhighlight(FALSE, (nlinks - 1), target);
2390 
2391 		display_flag = TRUE;
2392 
2393 	    } else {
2394 		/*
2395 		 * Not showing anchor.
2396 		 */
2397 		if (non_empty(hi_string))
2398 		    CTRACE((tfp,
2399 			    "\nGridText: Not showing link, hightext=%s\n",
2400 			    hi_string));
2401 	    }
2402 	}
2403 
2404 	if (nlinks == MAXLINKS) {
2405 	    /*
2406 	     * Links array is full.  If interactive, tell user
2407 	     * to use half-page or two-line scrolling.  -FM
2408 	     */
2409 	    if (LYCursesON) {
2410 		HTAlert(MAXLINKS_REACHED);
2411 	    }
2412 	    CTRACE((tfp, "\ndisplay_page: MAXLINKS reached.\n"));
2413 	    break;
2414 	}
2415     }				/* end of loop "Add the anchors to Lynx structures." */
2416 
2417     /*
2418      * Free any un-reallocated links[] entries
2419      * from the previous page draw.  -FM
2420      */
2421     LYFreeHilites(nlinks, last_nlinks);
2422     last_nlinks = nlinks;
2423 
2424     /*
2425      * If Anchor_ptr is not NULL and is not pointing to the last
2426      * anchor, then there are anchors farther down in the document,
2427      * and we need to flag this for traversals.
2428      */
2429     more_links = FALSE;
2430     if (traversal && Anchor_ptr) {
2431 	if (Anchor_ptr->next)
2432 	    more_links = TRUE;
2433     }
2434 
2435     if (!display_flag) {
2436 	/*
2437 	 * Nothing on the page.
2438 	 */
2439 	LYaddstr("\n     Document is empty");
2440     }
2441     display_scrollbar(text);
2442 
2443 #ifdef DISP_PARTIAL
2444     if (display_partial && display_flag &&
2445 	last_disp_partial >= text->top_of_screen &&
2446 	!enable_scrollback &&
2447 	!recent_sizechange) {	/*  really remember them if ok - kw  */
2448 	text->first_lineno_last_disp_partial = text->top_of_screen;
2449 	text->last_lineno_last_disp_partial = last_disp_partial;
2450     } else {
2451 	ResetPartialLinenos(text);
2452     }
2453 #endif /* DISP_PARTIAL */
2454 
2455 #if !defined(WIDEC_CURSES)
2456     if (text->has_utf8 || text->had_utf8) {
2457 	/*
2458 	 * For other than ncurses, repainting is taken care of
2459 	 * by touching lines in display_line and highlight.  - kw 1999-10-07
2460 	 */
2461 	text->had_utf8 = text->has_utf8;
2462 	clearok(curscr, TRUE);
2463     } else if (IS_CJK_TTY) {
2464 	/*
2465 	 * For non-multibyte curses.
2466 	 *
2467 	 * Full repainting is necessary, otherwise only part of a multibyte
2468 	 * character sequence might be written because of curses output
2469 	 * optimizations.
2470 	 */
2471 	clearok(curscr, TRUE);
2472     }
2473 #endif /* WIDEC_CURSES */
2474 
2475     LYrefresh();
2476     return;
2477 }
2478 
2479 /*			Object Building methods
2480  *			-----------------------
2481  *
2482  *	These are used by a parser to build the text in an object
2483  */
HText_beginAppend(HText * text)2484 void HText_beginAppend(HText *text)
2485 {
2486     text->permissible_split = 0;
2487     text->in_line_1 = YES;
2488 
2489 }
2490 
2491 /* LYcols_cu is the notion that the display library has of the screen
2492    width.  Normally it is the same as LYcols, but there may be a
2493    difference via SLANG_MBCS_HACK.  Checks of the line length (as the
2494    non-UTF-8-aware display library would see it) against LYcols_cu are
2495    is used to try to prevent that lines with UTF-8 chars get wrapped
2496    by the library when they shouldn't.
2497    If there is no display library involved, i.e. dump_output_immediately,
2498    no such limit should be imposed.  MAX_COLS should be just as good
2499    as any other large value.  (But don't use INT_MAX or something close
2500    to it to, avoid over/underflow.) - kw */
2501 #ifdef USE_SLANG
2502 #define LYcols_cu(text) (dump_output_immediately ? MAX_COLS : SLtt_Screen_Cols)
2503 #else
2504 #ifdef WIDEC_CURSES
2505 #define LYcols_cu(text) WRAP_COLS(text)
2506 #else
2507 #define LYcols_cu(text) (dump_output_immediately ? MAX_COLS : DISPLAY_COLS)
2508 #endif
2509 #endif
2510 
2511 /*	Add a new line of text
2512  *	----------------------
2513  *
2514  * On entry,
2515  *
2516  *	split	is zero for newline function, else number of characters
2517  *		before split.
2518  *	text->display_on_the_fly
2519  *		may be set to indicate direct output of the finished line.
2520  * On exit,
2521  *		A new line has been made, justified according to the
2522  *		current style.  Text after the split (if split nonzero)
2523  *		is taken over onto the next line.
2524  *
2525  *		If display_on_the_fly is set, then it is decremented and
2526  *		the finished line is displayed.
2527  */
2528 
set_style_by_embedded_chars(char * s,char * e,unsigned start_c,unsigned end_c)2529 static int set_style_by_embedded_chars(char *s,
2530 				       char *e,
2531 				       unsigned start_c,
2532 				       unsigned end_c)
2533 {
2534     int ret = NO;
2535 
2536     while (--e >= s) {
2537 	if (UCH(*e) == UCH(end_c))
2538 	    break;
2539 	if (UCH(*e) == UCH(start_c)) {
2540 	    ret = YES;
2541 	    break;
2542 	}
2543     }
2544     return ret;
2545 }
2546 
move_anchors_in_region(HTLine * line,int line_number,TextAnchor ** prev_anchor,int * prev_head_processed,int sbyte,int ebyte,int shift)2547 static void move_anchors_in_region(HTLine *line, int line_number,
2548 				   TextAnchor **prev_anchor,	/*updates++ */
2549 				   int *prev_head_processed,
2550 				   int sbyte,
2551 				   int ebyte,
2552 				   int shift)	/* Likewise */
2553 {
2554     /*
2555      * Update anchor positions for anchors that start on this line.  Note:  we
2556      * rely on a->line_pos counting bytes, not characters.  That's one reason
2557      * why HText_trimHightext has to be prevented from acting on these anchors
2558      * in partial display mode before we get a chance to deal with them here.
2559      */
2560     TextAnchor *a;
2561     int head_processed = *prev_head_processed;
2562 
2563     /*
2564      * We need to know whether (*prev_anchor)->line_pos is "in new coordinates"
2565      * or in old ones.  If prev_anchor' head was touched on the previous
2566      * iteration, we set head_processed.  The tail may need to be treated now.
2567      */
2568     for (a = *prev_anchor;
2569 	 a && a->line_num <= line_number;
2570 	 a = a->next, head_processed = 0) {
2571 	/* extent==0 needs to be special-cased; happens if no text for
2572 	   the anchor was processed yet.  */
2573 	/* Subtract one so that the space is not inserted at the end
2574 	   of the anchor... */
2575 	int last = a->line_pos + (a->extent ? a->extent - 1 : 0);
2576 
2577 	/* Include the anchors started on the previous line */
2578 	if (a->line_num < line_number - 1)
2579 	    continue;
2580 	if (a->line_num == line_number - 1)
2581 	    last -= line->prev->size + 1;	/* Fake "\n" "between" lines counted too */
2582 	if (last < sbyte)	/* Completely before the start */
2583 	    continue;
2584 
2585 	if (!head_processed	/* a->line_pos is not edited yet */
2586 	    && a->line_num == line_number
2587 	    && a->line_pos >= ebyte)	/* Completely after the end */
2588 	    break;
2589 	/* Now we know that the anchor context intersects the chunk */
2590 
2591 	/* Fix the start */
2592 	if (!head_processed && a->line_num == line_number
2593 	    && a->line_pos >= sbyte) {
2594 	    a->line_pos = (short) (a->line_pos + shift);
2595 	    a->extent = (short) (a->extent - shift);
2596 	    head_processed = 1;
2597 	}
2598 	/* Fix the end */
2599 	if (last < ebyte) {
2600 	    a->extent = (short) (a->extent + shift);
2601 	} else {
2602 	    break;		/* Keep this `a' for the next step */
2603 	}
2604     }
2605     *prev_anchor = a;
2606     *prev_head_processed = head_processed;
2607 }
2608 
2609 /*
2610  * Given a line and two int arrays of old/now position, this function
2611  * creates a new line where spaces have been inserted/removed
2612  * in appropriate places - so that characters at/after the old
2613  * position end up at/after the new position, for each pair, if possible.
2614  * Some necessary changes for anchors starting on this line are also done
2615  * here if needed.  Updates 'prev_anchor' internally.
2616  * Returns a newly allocated HTLine* if changes were made
2617  * (caller has to free the old one).
2618  * Returns NULL if no changes needed.  (Remove-spaces code may be buggy...)
2619  * - kw
2620  */
insert_blanks_in_line(HTLine * line,int line_number,HText * text,TextAnchor ** prev_anchor,int ninserts,int * oldpos,int * newpos)2621 static HTLine *insert_blanks_in_line(HTLine *line, int line_number,
2622 				     HText *text,
2623 				     TextAnchor **prev_anchor,	/*updates++ */
2624 				     int ninserts,
2625 				     int *oldpos,	/* Measured in cells */
2626 				     int *newpos)	/* Likewise */
2627 {
2628     int ioldc = 0;		/* count visible characters */
2629     int ip;			/* count insertion pairs */
2630 
2631 #if defined(USE_COLOR_STYLE)
2632     int istyle = 0;
2633 #endif
2634     int added_chars = 0;
2635     int shift = 0;
2636     int head_processed;
2637     HTLine *mod_line;
2638     char *newdata;
2639     char *s = line->data;
2640     char *pre = s;
2641     char *copied = line->data, *t;
2642 
2643     if (!(line && line->size && ninserts))
2644 	return NULL;
2645     for (ip = 0; ip < ninserts; ip++)
2646 	if (newpos[ip] > oldpos[ip] &&
2647 	    (newpos[ip] - oldpos[ip]) > added_chars)
2648 	    added_chars = newpos[ip] - oldpos[ip];
2649     if (line->size + added_chars > MAX_LINE - 2)
2650 	return NULL;
2651     if (line == text->last_line) {
2652 	if (line == TEMP_LINE(text, 0))
2653 	    mod_line = TEMP_LINE(text, 1);
2654 	else
2655 	    mod_line = TEMP_LINE(text, 0);
2656     } else {
2657 	allocHTLine(mod_line, (unsigned) (line->size + added_chars));
2658     }
2659     if (!mod_line)
2660 	return NULL;
2661     if (!*prev_anchor)
2662 	*prev_anchor = text->first_anchor;
2663     head_processed = (*prev_anchor && (*prev_anchor)->line_num < line_number);
2664     memcpy(mod_line, line, LINE_SIZE(0));
2665     t = newdata = mod_line->data;
2666     ip = 0;
2667     while (ip <= ninserts) {
2668 	/* line->size is in bytes, so it may be larger than needed... */
2669 	int curlim = (ip < ninserts
2670 		      ? oldpos[ip]
2671 		      : ((int) line->size <= MAX_LINE
2672 			 ? MAX_LINE + 1
2673 			 : (int) line->size + 1));
2674 
2675 	pre = s;
2676 
2677 	/* Fast forward to char==curlim or EOL.  Stop *before* the
2678 	   style-change chars. */
2679 	while (*s) {
2680 	    if (text && text->T.output_utf8
2681 		&& UCH(*s) >= 0x80 && UCH(*s) < 0xC0) {
2682 		pre = s + 1;
2683 	    } else if (!IsSpecialAttrChar(*s)) {	/* At a "displayed" char */
2684 		if (ioldc >= curlim)
2685 		    break;
2686 		ioldc++;
2687 		pre = s + 1;
2688 	    }
2689 	    s++;
2690 	}
2691 
2692 	/* Now s is at the "displayed" char, pre is before the style change */
2693 	if (ip)			/* Fix anchor positions */
2694 	    move_anchors_in_region(line, line_number, prev_anchor /*updates++ */ ,
2695 				   &head_processed,
2696 				   (int) (copied - line->data), (int) (pre - line->data),
2697 				   shift);
2698 #if defined(USE_COLOR_STYLE)	/* Move styles too */
2699 #define NStyle mod_line->styles[istyle]
2700 	for (;
2701 	     istyle < line->numstyles && (int) NStyle.sc_horizpos < curlim;
2702 	     istyle++)
2703 	    /* Should not we include OFF-styles at curlim? */
2704 	    NStyle.sc_horizpos += shift;
2705 #endif
2706 	while (copied < pre)	/* Copy verbatim to byte == pre */
2707 	    *t++ = *copied++;
2708 	if (ip < ninserts) {	/* Insert spaces */
2709 	    int delta = newpos[ip] - oldpos[ip] - shift;
2710 
2711 	    if (delta < 0) {	/* Not used yet? */
2712 		while (delta++ < 0 && t > newdata && t[-1] == ' ')
2713 		    t--, shift--;
2714 	    } else
2715 		shift = newpos[ip] - oldpos[ip];
2716 	    while (delta-- > 0)
2717 		*t++ = ' ';
2718 	}
2719 	ip++;
2720     }
2721     while (pre < s)		/* Copy remaining style-codes */
2722 	*t++ = *pre++;
2723     /* Check whether the last anchor continues on the next line */
2724     if (head_processed
2725 	&& *prev_anchor
2726 	&& (*prev_anchor)->line_num == line_number) {
2727 	(*prev_anchor)->extent = (short) ((*prev_anchor)->extent + shift);
2728     }
2729     *t = '\0';
2730     mod_line->size = (unsigned short) (t - newdata);
2731     return mod_line;
2732 }
2733 
2734 #if defined(USE_COLOR_STYLE)
2735 #define direction2s(d) ((d) == STACK_OFF \
2736 			? "OFF" \
2737 			: ((d) == STACK_ON \
2738 			   ? "ON" \
2739 			   : "*ON"))
2740 
2741 /*
2742  * Found an OFF change not part of an adjacent matched pair.
2743  *
2744  * Walk backward looking for the corresponding ON change.
2745  * Move everything after split_pos to be at split_pos.
2746  *
2747  * This can only work correctly if all changes are correctly nested!  If this
2748  * fails, assume it is safer to leave whatever comes before the OFF on the
2749  * previous line alone.
2750  */
skip_matched_and_correct_offsets(HTStyleChange * end,HTStyleChange * start,unsigned split_pos)2751 static HTStyleChange *skip_matched_and_correct_offsets(HTStyleChange *end,
2752 						       HTStyleChange *start,
2753 						       unsigned split_pos)
2754 {
2755     HTStyleChange *result = 0;
2756     int level = 0;
2757     HTStyleChange *tmp = end;
2758 
2759     CTRACE_STYLE((tfp, "SKIP Style %d %d (%s), split %u\n",
2760 		  tmp->sc_horizpos,
2761 		  tmp->sc_style,
2762 		  direction2s(tmp->sc_direction),
2763 		  split_pos));
2764     for (; tmp >= start; tmp--) {
2765 	CTRACE_STYLE((tfp, "... %d %d (%s)\n",
2766 		      tmp->sc_horizpos,
2767 		      tmp->sc_style,
2768 		      direction2s(tmp->sc_direction)));
2769 	if (tmp->sc_style == end->sc_style) {
2770 	    if (tmp->sc_direction == STACK_OFF) {
2771 		level--;
2772 	    } else if (tmp->sc_direction == STACK_ON) {
2773 		if (++level == 0) {
2774 		    result = tmp;
2775 		    break;
2776 		}
2777 	    } else {
2778 		break;
2779 	    }
2780 	}
2781 	if (tmp->sc_horizpos > split_pos) {
2782 	    tmp->sc_horizpos = split_pos;
2783 	}
2784     }
2785     return result;
2786 }
2787 #endif /* USE_COLOR_STYLE */
2788 
2789 #define reset_horizpos(value) value = 0, value = ~value
2790 
split_line(HText * text,unsigned split)2791 static void split_line(HText *text, unsigned split)
2792 {
2793     HTStyle *style = text->style;
2794     int spare;
2795     int indent = (text->in_line_1
2796 		  ? text->style->indent1st
2797 		  : text->style->leftIndent);
2798     int new_offset;
2799     short alignment;
2800     TextAnchor *a;
2801     int CurLine = text->Lines;
2802     int HeadTrim = 0;
2803     int SpecialAttrChars = 0;
2804     int TailTrim = 0;
2805     int s, s_post, s_pre, t_underline = underline_on, t_bold = bold_on;
2806     char *p;
2807     char *cp;
2808     int ctrl_chars_on_previous_line = 0;
2809 
2810 #ifndef WIDEC_CURSES
2811     int utfxtra_on_previous_line = UTFXTRA_ON_THIS_LINE;
2812 #endif
2813 
2814     HTLine *previous = text->last_line;
2815     HTLine *line;
2816 
2817     /*
2818      * Set new line.
2819      */
2820     if (previous == TEMP_LINE(text, 0))
2821 	line = TEMP_LINE(text, 1);
2822     else
2823 	line = TEMP_LINE(text, 0);
2824     if (line == NULL)
2825 	return;
2826     memset(line, 0, (size_t) LINE_SIZE(0));
2827 
2828     ctrl_chars_on_this_line = 0;	/*reset since we are going to a new line */
2829     utfxtra_on_this_line = 0;	/*reset too, we'll count them */
2830     text->LastChar = ' ';
2831 
2832 #ifdef DEBUG_APPCH
2833     CTRACE((tfp, "GridText: split_line(%p,%d) called\n", text, split));
2834     CTRACE((tfp, "   previous=%s\n", previous->data));
2835     CTRACE((tfp, "   bold_on=%d, underline_on=%d\n", bold_on, underline_on));
2836 #endif
2837 
2838     cp = previous->data;
2839 
2840     /* Float LY_SOFT_NEWLINE to the start */
2841     if (cp[0] == LY_BOLD_START_CHAR
2842 	|| cp[0] == LY_UNDERLINE_START_CHAR) {
2843 	switch (cp[1]) {
2844 	case LY_SOFT_NEWLINE:
2845 	    cp[1] = cp[0];
2846 	    cp[0] = LY_SOFT_NEWLINE;
2847 	    break;
2848 	case LY_BOLD_START_CHAR:
2849 	case LY_UNDERLINE_START_CHAR:
2850 	    if (cp[2] == LY_SOFT_NEWLINE) {
2851 		cp[2] = cp[1];
2852 		cp[1] = cp[0];
2853 		cp[0] = LY_SOFT_NEWLINE;
2854 	    }
2855 	    break;
2856 	}
2857     }
2858     if (split > previous->size) {
2859 	CTRACE((tfp,
2860 		"*** split_line: split==%u greater than last_line->size==%d !\n",
2861 		split, previous->size));
2862 	if (split > MAX_LINE) {
2863 	    split = previous->size;
2864 	    if ((cp = strrchr(previous->data, ' ')) &&
2865 		cp - previous->data > 1)
2866 		split = (unsigned) (cp - previous->data);
2867 	    CTRACE((tfp, "                split adjusted to %u.\n", split));
2868 	}
2869     }
2870 
2871     text->Lines++;
2872 
2873     previous->next->prev = line;
2874     line->prev = previous;
2875     line->next = previous->next;
2876     previous->next = line;
2877     text->last_line = line;
2878     line->size = 0;
2879     line->offset = 0;
2880     text->permissible_split = 0;	/* 12/13/93 */
2881     line->data[0] = '\0';
2882 
2883     alignment = style->alignment;
2884 
2885     if (split > 0) {		/* Restore flags to the value at the splitting point */
2886 	if (!(dump_output_immediately && use_underscore))
2887 	    t_underline = set_style_by_embedded_chars(previous->data,
2888 						      previous->data + split,
2889 						      LY_UNDERLINE_START_CHAR, LY_UNDERLINE_END_CHAR);
2890 
2891 	t_bold = set_style_by_embedded_chars(previous->data,
2892 					     previous->data + split,
2893 					     LY_BOLD_START_CHAR, LY_BOLD_END_CHAR);
2894 
2895     }
2896 
2897     if (!(dump_output_immediately && use_underscore) && t_underline) {
2898 	line->data[line->size++] = LY_UNDERLINE_START_CHAR;
2899 	line->data[line->size] = '\0';
2900 	ctrl_chars_on_this_line++;
2901 	SpecialAttrChars++;
2902     }
2903     if (t_bold) {
2904 	line->data[line->size++] = LY_BOLD_START_CHAR;
2905 	line->data[line->size] = '\0';
2906 	ctrl_chars_on_this_line++;
2907 	SpecialAttrChars++;
2908     }
2909 
2910     /*
2911      * Split at required point
2912      */
2913     if (split > 0) {		/* Delete space at "split" splitting line */
2914 	char *prevdata = previous->data, *linedata = line->data;
2915 	unsigned plen;
2916 	int i;
2917 
2918 	/* Split the line. -FM */
2919 	prevdata[previous->size] = '\0';
2920 	previous->size = (unsigned short) split;
2921 
2922 	/*
2923 	 * Trim any spaces or soft hyphens from the beginning
2924 	 * of our new line.  -FM
2925 	 */
2926 	p = prevdata + split;
2927 	while (((*p == ' '
2928 #ifdef USE_JUSTIFY_ELTS
2929 	/* if justification is allowed for prev line, then raw
2930 	 * HT_NON_BREAK_SPACE are still present in data[] (they'll be
2931 	 * substituted at the end of this function with ' ') - VH
2932 	 */
2933 		 || *p == HT_NON_BREAK_SPACE
2934 #endif
2935 		)
2936 		&& (HeadTrim || text->first_anchor ||
2937 		    underline_on || bold_on ||
2938 		    alignment != HT_LEFT ||
2939 		    style->wordWrap || style->freeFormat ||
2940 		    style->spaceBefore || style->spaceAfter)) ||
2941 	       *p == LY_SOFT_HYPHEN) {
2942 	    p++;
2943 	    HeadTrim++;
2944 	}
2945 
2946 	plen = (unsigned) strlen(p);
2947 	if (plen) {		/* Count funny characters */
2948 	    for (i = (int) (plen - 1); i >= 0; i--) {
2949 		if (p[i] == LY_UNDERLINE_START_CHAR ||
2950 		    p[i] == LY_UNDERLINE_END_CHAR ||
2951 		    p[i] == LY_BOLD_START_CHAR ||
2952 		    p[i] == LY_BOLD_END_CHAR ||
2953 		    p[i] == LY_SOFT_HYPHEN) {
2954 		    ctrl_chars_on_this_line++;
2955 		} else if (IS_UTF_EXTRA(p[i])) {
2956 		    utfxtra_on_this_line++;
2957 		}
2958 		if (p[i] == LY_SOFT_HYPHEN &&
2959 		    (int) text->permissible_split < i)
2960 		    text->permissible_split = (unsigned) (i + 1);
2961 	    }
2962 	    ctrl_chars_on_this_line += utfxtra_on_this_line;
2963 
2964 	    /* Add the data to the new line. -FM */
2965 	    strcat(linedata, p);
2966 	    line->size = (unsigned short) (line->size + plen);
2967 	}
2968     }
2969 
2970     /*
2971      * Economize on space.
2972      */
2973     p = previous->data + previous->size - 1;
2974     while (p >= previous->data
2975 	   && (*p == ' '
2976 #ifdef USE_JUSTIFY_ELTS
2977     /* if justification is allowed for prev line, then raw
2978      * HT_NON_BREAK_SPACE are still present in data[] (they'll be
2979      * substituted at the end of this function with ' ') - VH
2980      */
2981 	       || *p == HT_NON_BREAK_SPACE
2982 #endif
2983 	   )
2984 #ifdef USE_PRETTYSRC
2985 	   && !psrc_view	/*don't strip trailing whites - since next line can
2986 				   start with LY_SOFT_NEWLINE - so we don't lose spaces when
2987 				   'p'rinting this text to file -VH */
2988 #endif
2989 	   && (ctrl_chars_on_this_line || HeadTrim || text->first_anchor ||
2990 	       underline_on || bold_on ||
2991 	       alignment != HT_LEFT ||
2992 	       style->wordWrap || style->freeFormat ||
2993 	       style->spaceBefore || style->spaceAfter)) {
2994 	p--;			/*  Strip trailers. */
2995     }
2996     /*  Strip trailers. */
2997     TailTrim = (int) (previous->data + previous->size - 1 - p);
2998     previous->size = (unsigned short) (previous->size - TailTrim);
2999     p[1] = '\0';
3000 
3001     /*
3002      * s is the effective split position, given by either a non-zero
3003      * value of split or by the size of the previous line before
3004      * trimming.  - kw
3005      */
3006     if (split == 0) {
3007 	s = previous->size + TailTrim;	/* the original size */
3008     } else {
3009 	s = (int) split;
3010     }
3011     s_post = s + HeadTrim;
3012     s_pre = s - TailTrim;
3013 
3014 #ifdef DEBUG_SPLITLINE
3015 #ifdef DEBUG_APPCH
3016     if (s != (int) split)
3017 #endif
3018 	CTRACE((tfp, "GridText: split_line(%u [now:%d]) called\n", split, s));
3019 #endif
3020 
3021 #if defined(USE_COLOR_STYLE)
3022     if (previous->styles == stylechanges_buffers[0])
3023 	line->styles = stylechanges_buffers[1];
3024     else
3025 	line->styles = stylechanges_buffers[0];
3026     line->numstyles = 0;
3027     {
3028 	HTStyleChange *from = previous->styles + previous->numstyles - 1;
3029 	HTStyleChange *to = line->styles + MAX_STYLES_ON_LINE - 1;
3030 	HTStyleChange *scan, *at_end;
3031 
3032 	/* Color style changes after the split position
3033 	 * are transferred to the new line.  Ditto for changes
3034 	 * in the trimming region, but we stop when we reach an OFF change.
3035 	 * The second loop below may then handle remaining changes.  - kw */
3036 	while (from >= previous->styles && to >= line->styles) {
3037 	    *to = *from;
3038 	    if ((int) to->sc_horizpos > s_post) {
3039 		to->sc_horizpos += -s_post + SpecialAttrChars;
3040 	    } else if ((int) to->sc_horizpos > s_pre &&
3041 		       (to->sc_direction == STACK_ON ||
3042 			to->sc_direction == ABS_ON)) {
3043 		to->sc_horizpos = ((int) to->sc_horizpos < s) ? 0 : SpecialAttrChars;
3044 	    } else {
3045 		break;
3046 	    }
3047 	    to--;
3048 	    from--;
3049 	}
3050 	/* FROM may be invalid, otherwise it is either an ON change at or
3051 	   before s_pre, or is an OFF change at or before s_post.  */
3052 
3053 	scan = from;
3054 	at_end = from;
3055 	/* Now on the previous line we have a correctly nested but
3056 	   possibly non-terminated sequence of style changes.
3057 	   Terminate it, and duplicate unterminated changes at the
3058 	   beginning of the new line. */
3059 	while (scan >= previous->styles && at_end >= previous->styles) {
3060 	    /* The algorithm: scan back though the styles on the previous line.
3061 	       a) If OFF, skip the matched group.
3062 	       Report a bug on failure.
3063 	       b) If ON, (try to) cancel the corresponding ON at at_end,
3064 	       and the corresponding OFF at to;
3065 	       If not, put the corresponding OFF at at_end, and copy to to;
3066 	     */
3067 	    if (scan->sc_direction == STACK_OFF) {
3068 		scan = skip_matched_and_correct_offsets(scan, previous->styles,
3069 							(unsigned) s_pre);
3070 		if (!scan) {
3071 		    CTRACE((tfp, "BUG: styles improperly nested.\n"));
3072 		    break;
3073 		}
3074 	    } else if (scan->sc_direction == STACK_ON) {
3075 		if (at_end->sc_direction == STACK_ON
3076 		    && at_end->sc_style == scan->sc_style
3077 		    && (int) at_end->sc_horizpos >= s_pre)
3078 		    at_end--;
3079 		else if (at_end >= previous->styles + MAX_STYLES_ON_LINE - 1) {
3080 		    CTRACE((tfp, "BUG: style overflow before split_line.\n"));
3081 		    break;
3082 		} else {
3083 		    at_end++;
3084 		    at_end->sc_direction = STACK_OFF;
3085 		    at_end->sc_style = scan->sc_style;
3086 		    at_end->sc_horizpos = s_pre;
3087 		    CTRACE_STYLE((tfp,
3088 				  "split_line, %d:style[%d] %d (dir=%d)\n",
3089 				  s_pre,
3090 				  (int) (at_end - from),
3091 				  scan->sc_style,
3092 				  at_end->sc_direction));
3093 		}
3094 		if (to < line->styles + MAX_STYLES_ON_LINE - 1
3095 		    && to[1].sc_direction == STACK_OFF
3096 		    && to[1].sc_horizpos <= (unsigned) SpecialAttrChars
3097 		    && to[1].sc_style == scan->sc_style)
3098 		    to++;
3099 		else if (to >= line->styles) {
3100 		    *to = *scan;
3101 		    to->sc_horizpos = SpecialAttrChars;
3102 		    to--;
3103 		} else {
3104 		    CTRACE((tfp, "BUG: style overflow after split_line.\n"));
3105 		    break;
3106 		}
3107 	    }
3108 	    if ((int) scan->sc_horizpos > s_pre) {
3109 		scan->sc_horizpos = s_pre;
3110 	    }
3111 	    scan--;
3112 	}
3113 	line->numstyles = (unsigned short) (line->styles
3114 					    + MAX_STYLES_ON_LINE
3115 					    - 1 - to);
3116 	if (line->numstyles > 0 && line->numstyles < MAX_STYLES_ON_LINE) {
3117 	    int n;
3118 
3119 	    for (n = 0; n < line->numstyles; n++)
3120 		line->styles[n] = to[n + 1];
3121 	} else if (line->numstyles == 0) {
3122 	    reset_horizpos(line->styles[0].sc_horizpos);
3123 	}
3124 	previous->numstyles = (unsigned short) (at_end - previous->styles + 1);
3125 	if (previous->numstyles == 0) {
3126 	    reset_horizpos(previous->styles[0].sc_horizpos);
3127 	}
3128     }
3129 #endif /*USE_COLOR_STYLE */
3130 
3131     {
3132 	HTLine *temp;
3133 
3134 	allocHTLine(temp, previous->size);
3135 	if (!temp)
3136 	    outofmem(__FILE__, "split_line_2");
3137 
3138 	assert(temp != NULL);
3139 
3140 	memcpy(temp, previous, LINE_SIZE(previous->size));
3141 #if defined(USE_COLOR_STYLE)
3142 	POOLallocstyles(temp->styles, previous->numstyles);
3143 	if (!temp->styles)
3144 	    outofmem(__FILE__, "split_line_2");
3145 	memcpy(temp->styles, previous->styles, sizeof(HTStyleChange) * previous->numstyles);
3146 #endif
3147 	previous = temp;
3148     }
3149 
3150     previous->prev->next = previous;	/* Link in new line */
3151     previous->next->prev = previous;	/* Could be same node of course */
3152 
3153     /*
3154      * Terminate finished line for printing.
3155      */
3156     previous->data[previous->size] = '\0';
3157 
3158     /*
3159      * Align left, right or center.
3160      */
3161     spare = 0;
3162     if (
3163 #ifdef USE_JUSTIFY_ELTS
3164 	   this_line_was_split ||
3165 #endif
3166 	   (alignment == HT_CENTER ||
3167 	    alignment == HT_RIGHT) || text->stbl) {
3168 	/* Calculate spare character positions if needed */
3169 	for (cp = previous->data; *cp; cp++) {
3170 	    if (*cp == LY_UNDERLINE_START_CHAR ||
3171 		*cp == LY_UNDERLINE_END_CHAR ||
3172 		*cp == LY_BOLD_START_CHAR ||
3173 		*cp == LY_BOLD_END_CHAR ||
3174 #ifndef WIDEC_CURSES
3175 		IS_UTF_EXTRA(*cp) ||
3176 #endif
3177 		*cp == LY_SOFT_HYPHEN) {
3178 		ctrl_chars_on_previous_line++;
3179 	    }
3180 	}
3181 	if ((previous->size > 0) &&
3182 	    (int) (previous->data[previous->size - 1] == LY_SOFT_HYPHEN))
3183 	    ctrl_chars_on_previous_line--;
3184 
3185 	/* @@ first line indent */
3186 #ifdef WIDEC_CURSES
3187 	spare = WRAP_COLS(text)
3188 	    - (int) style->rightIndent
3189 	    - indent
3190 	    + ctrl_chars_on_previous_line
3191 	    - LYstrExtent2(previous->data, previous->size);
3192 	if (spare < 0 && LYwideLines)	/* Can be wider than screen */
3193 	    spare = 0;
3194 #else
3195 	spare = WRAP_COLS(text)
3196 	    - (int) style->rightIndent
3197 	    - indent
3198 	    + ctrl_chars_on_previous_line
3199 	    - previous->size;
3200 	if (spare < 0 && LYwideLines)	/* Can be wider than screen */
3201 	    spare = 0;
3202 
3203 	if (spare > 0 && !dump_output_immediately &&
3204 	    text->T.output_utf8 && ctrl_chars_on_previous_line) {
3205 	    utfxtra_on_previous_line -= UTFXTRA_ON_THIS_LINE;
3206 	    if (utfxtra_on_previous_line) {
3207 		int spare_cu = (LYcols_cu(text) -
3208 				utfxtra_on_previous_line - indent +
3209 				ctrl_chars_on_previous_line - previous->size);
3210 
3211 		/*
3212 		 * Shift non-leftaligned UTF-8 lines that would be
3213 		 * mishandled by the display library towards the left
3214 		 * if this would make them fit.  The resulting display
3215 		 * will not be as intended, but this is better than
3216 		 * having them split by curses.  (Curses cursor movement
3217 		 * optimization may still cause wrong positioning within
3218 		 * the line, in particular after a sequence of spaces).
3219 		 * - kw
3220 		 */
3221 		if (spare_cu < spare) {
3222 		    if (spare_cu >= 0) {
3223 			if (alignment == HT_CENTER &&
3224 			    (int) (previous->offset + indent + spare / 2 +
3225 				   previous->size)
3226 			    - ctrl_chars_on_previous_line
3227 			    + utfxtra_on_previous_line <= LYcols_cu(text))
3228 			    /* do nothing - it still fits - kw */ ;
3229 			else {
3230 			    spare = spare_cu;
3231 			}
3232 		    } else if (indent + (int) previous->offset + spare_cu >= 0) {	/* subtract overdraft from effective indentation */
3233 			indent += (int) previous->offset + spare_cu;
3234 			previous->offset = 0;
3235 			spare = 0;
3236 		    }
3237 		}
3238 	    }
3239 	}
3240 #endif
3241     }
3242 
3243     new_offset = previous->offset;
3244     switch (style->alignment) {
3245     case HT_CENTER:
3246 	new_offset += indent + spare / 2;
3247 	break;
3248     case HT_RIGHT:
3249 	new_offset += indent + spare;
3250 	break;
3251     case HT_LEFT:
3252     case HT_JUSTIFY:		/* Not implemented */
3253     default:
3254 	new_offset += indent;
3255 	break;
3256     }				/* switch */
3257     previous->offset = (unsigned short) ((new_offset < 0) ? 0 : new_offset);
3258 
3259     if (text->stbl)
3260 	/*
3261 	 * Notify simple table stuff of line split, so that it can
3262 	 * set the last cell's length.  The last cell should and
3263 	 * its row should really end here, or on one of the following
3264 	 * lines with no more characters added after the break.
3265 	 * We don't know whether a cell has been started, so ignore
3266 	 * errors here.
3267 	 * This call is down here because we need the
3268 	 * ctrl_chars_on_previous_line, which have just been re-
3269 	 * counted above.  - kw
3270 	 */
3271 	Stbl_lineBreak(text->stbl,
3272 		       text->Lines - 1,
3273 		       previous->offset,
3274 		       previous->size - ctrl_chars_on_previous_line);
3275 
3276     text->in_line_1 = NO;	/* unless caller sets it otherwise */
3277 
3278     /*
3279      * If we split the line, adjust the anchor
3280      * structure values for the new line.  -FM
3281      */
3282 
3283     if (s > 0) {		/* if not completely empty */
3284 	int moved = 0;
3285 
3286 	/* In the algorithm below we move or not move anchors between
3287 	   lines using some heuristic criteria.  However, it is
3288 	   desirable not to have two consequent anchors on different
3289 	   lines *in a wrong order*!  (How can this happen?)
3290 	   So when the "reasonable choice" is not unique, we use the
3291 	   MOVED flag to choose one.
3292 	 */
3293 	/* Our operations can make a non-empty all-whitespace link
3294 	   empty.  So what? */
3295 	if ((a = text->last_anchor_before_split) == 0)
3296 	    a = text->first_anchor;
3297 
3298 	for (; a; a = a->next) {
3299 	    if (a->line_num == CurLine) {
3300 		int len = a->extent, n = a->number, start = a->line_pos;
3301 		int end = start + len;
3302 
3303 		text->last_anchor_before_split = a;
3304 
3305 		/* Which anchors do we leave on the previous line?
3306 		   a) empty finished (We need a cut-off value.
3307 		   "Just because": those before s;
3308 		   this is the only case when we use s, not s_pre/s_post);
3309 		   b) Those which start before s_pre;
3310 		 */
3311 		if (start < s_pre) {
3312 		    if (end <= s_pre)
3313 			continue;	/* No problem */
3314 
3315 		    CTRACE_SPLITLINE((tfp, "anchor %d: no relocation", n));
3316 		    if (end > s_post) {
3317 			CTRACE_SPLITLINE((tfp, " of the start.\n"));
3318 			a->extent = (short) (a->extent
3319 					     - (TailTrim + HeadTrim)
3320 					     + SpecialAttrChars);
3321 		    } else {
3322 			CTRACE_SPLITLINE((tfp, ", cut the end.\n"));
3323 			a->extent = (short) (s_pre - start);
3324 		    }
3325 		    continue;
3326 		} else if (start < s && !len
3327 			   && (!n || (a->show_anchor && !moved))) {
3328 		    CTRACE_SPLITLINE((tfp,
3329 				      "anchor %d: no relocation, empty-finished",
3330 				      n));
3331 		    a->line_pos = (short) s_pre;	/* Leave at the end of line */
3332 		    continue;
3333 		}
3334 
3335 		/* The rest we relocate */
3336 		moved = 1;
3337 		a->line_num++;
3338 		CTRACE_SPLITLINE((tfp,
3339 				  "anchor %d: (T,H,S)=(%d,%d,%d); (line,pos,ext):(%d,%d,%d), ",
3340 				  n, TailTrim, HeadTrim, SpecialAttrChars,
3341 				  a->line_num, a->line_pos, a->extent));
3342 		if (end < s_post) {	/* Move the end to s_post */
3343 		    CTRACE_SPLITLINE((tfp, "Move end +%d, ", s_post - end));
3344 		    len += s_post - end;
3345 		}
3346 		if (start < s_post) {	/* Move the start to s_post */
3347 		    CTRACE_SPLITLINE((tfp, "Move start +%d, ", s_post - start));
3348 		    len -= s_post - start;
3349 		    start = s_post;
3350 		}
3351 		a->line_pos = (short) (start - s_post + SpecialAttrChars);
3352 		a->extent = (short) len;
3353 
3354 		CTRACE_SPLITLINE((tfp, "->(%d,%d,%d)\n",
3355 				  a->line_num, a->line_pos, a->extent));
3356 	    } else if (a->line_num > CurLine)
3357 		break;
3358 	}
3359     }
3360 #ifdef USE_JUSTIFY_ELTS
3361     /* now perform justification - by VH */
3362 
3363     if (this_line_was_split
3364 	&& spare > 0
3365 	&& !text->stbl		/* We don't inform TRST on the cell width change yet */
3366 	&& justify_max_void_percent > 0
3367 	&& justify_max_void_percent <= 100
3368 	&& justify_max_void_percent >= ((100 * spare)
3369 					/ (WRAP_COLS(text)
3370 					   - (int) style->rightIndent
3371 					   - indent
3372 					   + ctrl_chars_on_previous_line))) {
3373 	/* this is the only case when we need justification */
3374 	char *jp = previous->data + justify_start_position;
3375 	ht_run_info *r = ht_runs;
3376 	char c;
3377 	int d_, r_;
3378 	HTLine *jline;
3379 
3380 	ht_num_runs = 0;
3381 	r->byte_len = r->cell_len = 0;
3382 
3383 	for (; (c = *jp) != 0; ++jp) {
3384 	    if (c == ' ') {
3385 		++r;
3386 		++ht_num_runs;
3387 		r->byte_len = r->cell_len = 0;
3388 		continue;
3389 	    }
3390 	    ++r->byte_len;
3391 	    if (IsSpecialAttrChar(c))
3392 		continue;
3393 
3394 	    ++r->cell_len;
3395 	    if (c == HT_NON_BREAK_SPACE) {
3396 		*jp = ' ';	/* substitute it */
3397 		continue;
3398 	    }
3399 	    if (text->T.output_utf8 && is8bits(c)) {
3400 		int utf_extra = (int) utf8_length(text->T.output_utf8, jp);
3401 
3402 		r->byte_len += utf_extra;
3403 		jp += utf_extra;
3404 	    }
3405 	}
3406 	++ht_num_runs;
3407 
3408 	if (ht_num_runs != 1) {
3409 	    int *oldpos = (int *) malloc(sizeof(int)
3410 					 * 2 * (size_t) (ht_num_runs - 1));
3411 	    int *newpos = oldpos + ht_num_runs - 1;
3412 	    int i = 1;
3413 
3414 	    if (oldpos == NULL)
3415 		outofmem(__FILE__, "split_line_3");
3416 
3417 	    d_ = spare / (ht_num_runs - 1);
3418 	    r_ = spare % (ht_num_runs - 1);
3419 
3420 	    /* The first run is not moved, proceed to the second one */
3421 	    oldpos[0] = justify_start_position + ht_runs[0].cell_len + 1;
3422 	    newpos[0] = oldpos[0] + (d_ + (r_-- > 0));
3423 	    while (i < ht_num_runs - 1) {
3424 		int delta = ht_runs[i].cell_len + 1;
3425 
3426 		oldpos[i] = oldpos[i - 1] + delta;
3427 		newpos[i] = newpos[i - 1] + delta + (d_ + (r_-- > 0));
3428 		i++;
3429 	    }
3430 	    jline = insert_blanks_in_line(previous, CurLine, text,
3431 					  &last_anchor_of_previous_line /*updates++ */ ,
3432 					  ht_num_runs - 1, oldpos, newpos);
3433 	    free(oldpos);
3434 	    if (jline == NULL)
3435 		outofmem(__FILE__, "split_line_4");
3436 	    previous->next->prev = jline;
3437 	    previous->prev->next = jline;
3438 
3439 	    freeHTLine(text, previous);
3440 
3441 	    previous = jline;
3442 	}
3443 	if (justify_start_position) {
3444 	    char *p2 = previous->data;
3445 
3446 	    for (; p2 < previous->data + justify_start_position; ++p2)
3447 		*p2 = (char) (*p2 == HT_NON_BREAK_SPACE ? ' ' : *p2);
3448 	}
3449     } else {
3450 	if (REALLY_CAN_JUSTIFY(text)) {
3451 	    char *p2;
3452 
3453 	    /* it was permitted to justify line, but this function was called
3454 	     * to end paragraph - we must substitute HT_NON_BREAK_SPACEs with
3455 	     * spaces in previous line
3456 	     */
3457 	    if (line->size && !text->stbl) {
3458 		CTRACE((tfp,
3459 			"BUG: justification: shouldn't happen - new line is not empty!\n\t'%.*s'\n",
3460 			line->size, line->data));
3461 	    }
3462 
3463 	    for (p2 = previous->data; *p2; ++p2)
3464 		if (*p2 == HT_NON_BREAK_SPACE)
3465 		    *p2 = ' ';
3466 	} else if (have_raw_nbsps) {
3467 	    /* this is very rare case, that can happen in forms placed in
3468 	       table cells */
3469 	    unsigned i;
3470 
3471 	    for (i = 0; i < previous->size; ++i)
3472 		if (previous->data[i] == HT_NON_BREAK_SPACE)
3473 		    previous->data[i] = ' ';
3474 
3475 	    /*next line won't be justified, so substitute nbsps in it too */
3476 	    for (i = 0; i < line->size; ++i)
3477 		if (line->data[i] == HT_NON_BREAK_SPACE)
3478 		    line->data[i] = ' ';
3479 	}
3480 
3481 	/* else HT_NON_BREAK_SPACEs were substituted with spaces in
3482 	   HText_appendCharacter */
3483     }
3484     /* cleanup */
3485     can_justify_this_line = TRUE;
3486     justify_start_position = 0;
3487     this_line_was_split = FALSE;
3488     have_raw_nbsps = FALSE;
3489 #endif /* USE_JUSTIFY_ELTS */
3490     return;
3491 }				/* split_line */
3492 
3493 #ifdef DEBUG_SPLITLINE
do_new_line(HText * text,const char * fn,int ln)3494 static void do_new_line(HText *text, const char *fn, int ln)
3495 {
3496     CTRACE_SPLITLINE((tfp, "new_line %s@%d\n", fn, ln));
3497     split_line(text, 0);
3498 }
3499 
3500 #define new_line(text) do_new_line(text, __FILE__, __LINE__)
3501 #else
3502 #define new_line(text) split_line(text, 0)
3503 #endif
3504 
3505 /*	Allow vertical blank space
3506  *	--------------------------
3507  */
blank_lines(HText * text,int newlines)3508 static void blank_lines(HText *text, int newlines)
3509 {
3510     if (HText_TrueEmptyLine(text->last_line, text, FALSE)) {	/* No text on current line */
3511 	HTLine *line = text->last_line->prev;
3512 	BOOL first = (BOOL) (line == text->last_line);
3513 
3514 	if (no_title && first)
3515 	    return;
3516 
3517 #ifdef USE_COLOR_STYLE
3518 	/* Style-change petty requests at the start of the document: */
3519 	if (first && newlines == 1)
3520 	    return;		/* Do not add a blank line at start */
3521 #endif
3522 
3523 	while (line != NULL &&
3524 	       line != text->last_line &&
3525 	       HText_TrueEmptyLine(line, text, FALSE)) {
3526 	    if (newlines == 0)
3527 		break;
3528 	    newlines--;		/* Don't bother: already blank */
3529 	    line = line->prev;
3530 	}
3531     } else {
3532 	newlines++;		/* Need also to finish this line */
3533     }
3534 
3535     for (; newlines; newlines--) {
3536 	new_line(text);
3537     }
3538     text->in_line_1 = YES;
3539 }
3540 
3541 /*	New paragraph in current style
3542  *	------------------------------
3543  * See also: setStyle.
3544  */
HText_appendParagraph(HText * text)3545 void HText_appendParagraph(HText *text)
3546 {
3547     int after = text->style->spaceAfter;
3548     int before = text->style->spaceBefore;
3549 
3550     blank_lines(text, ((after > before) ? after : before));
3551 }
3552 
3553 /*	Set Style
3554  *	---------
3555  *
3556  *	Does not filter unnecessary style changes.
3557  */
HText_setStyle(HText * text,HTStyle * style)3558 void HText_setStyle(HText *text, HTStyle *style)
3559 {
3560     int after, before;
3561 
3562     if (!style)
3563 	return;			/* Safety */
3564     after = text->style->spaceAfter;
3565     before = style->spaceBefore;
3566 
3567     CTRACE((tfp, "GridText: Change to style %s\n", GetHTStyleName(style)));
3568 
3569     blank_lines(text, ((after > before) ? after : before));
3570 
3571     text->style = style;
3572 }
3573 
3574 /*	Append a character to the text object
3575  *	-------------------------------------
3576  */
HText_appendCharacter(HText * text,int ch)3577 void HText_appendCharacter(HText *text, int ch)
3578 {
3579     HTLine *line;
3580     HTStyle *style;
3581     int indent;
3582     int actual;
3583 
3584 #ifdef DEBUG_APPCH
3585 #ifdef CJK_EX
3586     static unsigned char save_ch = 0;
3587 #endif
3588 
3589     if (TRACE) {
3590 	char *special = NULL;	/* make trace a little more readable */
3591 
3592 	switch (ch) {
3593 	case HT_NON_BREAK_SPACE:
3594 	    special = "HT_NON_BREAK_SPACE";
3595 	    break;
3596 	case HT_EN_SPACE:
3597 	    special = "HT_EN_SPACE";
3598 	    break;
3599 	case LY_UNDERLINE_START_CHAR:
3600 	    special = "LY_UNDERLINE_START_CHAR";
3601 	    break;
3602 	case LY_UNDERLINE_END_CHAR:
3603 	    special = "LY_UNDERLINE_END_CHAR";
3604 	    break;
3605 	case LY_BOLD_START_CHAR:
3606 	    special = "LY_BOLD_START_CHAR";
3607 	    break;
3608 	case LY_BOLD_END_CHAR:
3609 	    special = "LY_BOLD_END_CHAR";
3610 	    break;
3611 	case LY_SOFT_HYPHEN:
3612 	    special = "LY_SOFT_HYPHEN";
3613 	    break;
3614 	case LY_SOFT_NEWLINE:
3615 	    special = "LY_SOFT_NEWLINE";
3616 	    break;
3617 	default:
3618 	    special = NULL;
3619 	    break;
3620 	}
3621 
3622 	if (special != NULL) {
3623 	    CTRACE((tfp, "add(%s %d special char) %d/%d\n", special, ch,
3624 		    HTisDocumentSource(), HTOutputFormat != WWW_SOURCE));
3625 	} else {
3626 #ifdef CJK_EX			/* 1998/08/30 (Sun) 13:26:23 */
3627 	    if (save_ch == 0) {
3628 		if (IS_SJIS_HI1(ch) || IS_SJIS_HI2(ch)) {
3629 		    save_ch = ch;
3630 		} else {
3631 		    CTRACE((tfp, "add(%c) %d/%d\n", ch,
3632 			    HTisDocumentSource(), HTOutputFormat != WWW_SOURCE));
3633 		}
3634 	    } else {
3635 		CTRACE((tfp, "add(%c%c) %d/%d\n", save_ch, ch,
3636 			HTisDocumentSource(), HTOutputFormat != WWW_SOURCE));
3637 		save_ch = 0;
3638 	    }
3639 #else
3640 	    if (UCH(ch) < 0x80) {
3641 		CTRACE((tfp, "add(%c) %d/%d\n", UCH(ch),
3642 			HTisDocumentSource(), HTOutputFormat != WWW_SOURCE));
3643 	    } else {
3644 		CTRACE((tfp, "add(%02x) %d/%d\n", UCH(ch),
3645 			HTisDocumentSource(), HTOutputFormat != WWW_SOURCE));
3646 	    }
3647 #endif /* CJK_EX */
3648 	}
3649     }				/* trace only */
3650 #endif /* DEBUG_APPCH */
3651 
3652     /*
3653      * Make sure we don't crash on NULLs.
3654      */
3655     if (!text)
3656 	return;
3657 
3658     if (text->halted > 1) {
3659 	/*
3660 	 * We should stop outputting more text, because low memory was
3661 	 * detected.  - kw
3662 	 */
3663 	if (text->halted == 2) {
3664 	    /*
3665 	     * But if we haven't done so yet, first append a warning.
3666 	     * We should still have a few bytes left for that :).
3667 	     * We temporarily reset test->halted to 0 for this, since
3668 	     * this function will get called recursively.  - kw
3669 	     */
3670 	    text->halted = 0;
3671 	    text->kanji_buf = '\0';
3672 	    HText_appendText(text, gettext(" *** MEMORY EXHAUSTED ***"));
3673 	}
3674 	text->halted = 3;
3675 	return;
3676     }
3677 #ifdef USE_TH_JP_AUTO_DETECT
3678     if ((HTCJK == JAPANESE) && (text->detected_kcode != DET_MIXED) &&
3679 	(text->specified_kcode != SJIS) && (text->specified_kcode != EUC)) {
3680 	unsigned char c;
3681 	eDetectedKCode save_d_kcode;
3682 
3683 	c = UCH(ch);
3684 	save_d_kcode = text->detected_kcode;
3685 	switch (text->SJIS_status) {
3686 	case SJIS_state_has_bad_code:
3687 	    break;
3688 	case SJIS_state_neutral:
3689 	    if (IS_SJIS_HI1(c) || IS_SJIS_HI2(c)) {
3690 		text->SJIS_status = SJIS_state_in_kanji;
3691 	    } else if ((c & 0x80) && !IS_SJIS_X0201KANA(c)) {
3692 		text->SJIS_status = SJIS_state_has_bad_code;
3693 		if (text->EUC_status == EUC_state_has_bad_code)
3694 		    text->detected_kcode = DET_MIXED;
3695 		else
3696 		    text->detected_kcode = DET_EUC;
3697 	    }
3698 	    break;
3699 	case SJIS_state_in_kanji:
3700 	    if (IS_SJIS_LO(c)) {
3701 		text->SJIS_status = SJIS_state_neutral;
3702 	    } else {
3703 		text->SJIS_status = SJIS_state_has_bad_code;
3704 		if (text->EUC_status == EUC_state_has_bad_code)
3705 		    text->detected_kcode = DET_MIXED;
3706 		else
3707 		    text->detected_kcode = DET_EUC;
3708 	    }
3709 	    break;
3710 	}
3711 	switch (text->EUC_status) {
3712 	case EUC_state_has_bad_code:
3713 	    break;
3714 	case EUC_state_neutral:
3715 	    if (IS_EUC_HI(c)) {
3716 		text->EUC_status = EUC_state_in_kanji;
3717 	    } else if (c == 0x8e) {
3718 		text->EUC_status = EUC_state_in_kana;
3719 	    } else if (c & 0x80) {
3720 		text->EUC_status = EUC_state_has_bad_code;
3721 		if (text->SJIS_status == SJIS_state_has_bad_code)
3722 		    text->detected_kcode = DET_MIXED;
3723 		else
3724 		    text->detected_kcode = DET_SJIS;
3725 	    }
3726 	    break;
3727 	case EUC_state_in_kanji:
3728 	    if (IS_EUC_LOX(c)) {
3729 		text->EUC_status = EUC_state_neutral;
3730 	    } else {
3731 		text->EUC_status = EUC_state_has_bad_code;
3732 		if (text->SJIS_status == SJIS_state_has_bad_code)
3733 		    text->detected_kcode = DET_MIXED;
3734 		else
3735 		    text->detected_kcode = DET_SJIS;
3736 	    }
3737 	    break;
3738 	case EUC_state_in_kana:
3739 	    if ((0xA1 <= c) && (c <= 0xDF)) {
3740 		text->EUC_status = EUC_state_neutral;
3741 	    } else {
3742 		text->EUC_status = EUC_state_has_bad_code;
3743 		if (text->SJIS_status == SJIS_state_has_bad_code)
3744 		    text->detected_kcode = DET_MIXED;
3745 		else
3746 		    text->detected_kcode = DET_SJIS;
3747 	    }
3748 	    break;
3749 	}
3750 	if (save_d_kcode != text->detected_kcode) {
3751 	    switch (text->detected_kcode) {
3752 	    case DET_SJIS:
3753 		CTRACE((tfp,
3754 			"TH_JP_AUTO_DETECT: This document's kcode seems SJIS.\n"));
3755 		break;
3756 	    case DET_EUC:
3757 		CTRACE((tfp,
3758 			"TH_JP_AUTO_DETECT: This document's kcode seems EUC.\n"));
3759 		break;
3760 	    case DET_MIXED:
3761 		CTRACE((tfp,
3762 			"TH_JP_AUTO_DETECT: This document's kcode seems mixed!\n"));
3763 		break;
3764 	    default:
3765 		CTRACE((tfp,
3766 			"TH_JP_AUTO_DETECT: This document's kcode is unexpected!\n"));
3767 		break;
3768 	    }
3769 	}
3770     }
3771 #endif /* USE_TH_JP_AUTO_DETECT */
3772     /*
3773      * Make sure we don't hang on escape sequences.
3774      */
3775     if (ch == CH_ESC && !IS_CJK_TTY) {	/* decimal 27  S/390 -- gil -- 1504 */
3776 	return;
3777     }
3778 #ifndef USE_SLANG
3779     /*
3780      * Block 8-bit chars not allowed by the current display character
3781      * set if they are below what LYlowest_eightbit indicates.
3782      * Slang used its own replacements, so for USE_SLANG blocking here
3783      * is not necessary to protect terminals from those characters.
3784      * They should have been filtered out or translated by an earlier
3785      * processing stage anyway.  - kw
3786      */
3787 #ifndef   EBCDIC		/* S/390 -- gil -- 1514 */
3788     if (is8bits(ch) && !IS_CJK_TTY &&
3789 	!text->T.transp && !text->T.output_utf8 &&
3790 	UCH(ch) < LYlowest_eightbit[current_char_set]) {
3791 	return;
3792     }
3793 #endif /* EBCDIC */
3794 #endif /* !USE_SLANG */
3795     if (UCH(ch) == 155 && !IS_CJK_TTY) {	/* octal 233 */
3796 	if (!HTPassHighCtrlRaw &&
3797 	    !text->T.transp && !text->T.output_utf8 &&
3798 	    (155 < LYlowest_eightbit[current_char_set])) {
3799 	    return;
3800 	}
3801     }
3802 
3803     line = text->last_line;
3804     style = text->style;
3805 
3806     indent = text->in_line_1 ? (int) style->indent1st : (int) style->leftIndent;
3807 
3808     if (IS_CJK_TTY) {
3809 	switch (text->state) {
3810 	case S_text:
3811 	    if (ch == CH_ESC) {	/* S/390 -- gil -- 1536 */
3812 		/*
3813 		 * Setting up for CJK escape sequence handling (based on
3814 		 * Takuya ASADA's (asada@three-a.co.jp) CJK Lynx).  -FM
3815 		 */
3816 		text->state = S_esc;
3817 		text->kanji_buf = '\0';
3818 		return;
3819 	    }
3820 	    break;
3821 
3822 	case S_esc:
3823 	    /*
3824 	     * Expecting '$'or '(' following CJK ESC.
3825 	     */
3826 	    if (ch == '$') {
3827 		text->state = S_dollar;
3828 		return;
3829 	    } else if (ch == '(') {
3830 		text->state = S_paren;
3831 		return;
3832 	    } else {
3833 		text->state = S_text;
3834 	    }
3835 	    /* FALLTHRU */
3836 
3837 	case S_dollar:
3838 	    /*
3839 	     * Expecting '@', 'B', 'A' or '(' after CJK "ESC$".
3840 	     */
3841 	    if (ch == '@' || ch == 'B' || ch == 'A') {
3842 		text->state = S_nonascii_text;
3843 		if (ch == '@' || ch == 'B')
3844 		    text->kcode = JIS;
3845 		return;
3846 	    } else if (ch == '(') {
3847 		text->state = S_dollar_paren;
3848 		return;
3849 	    } else {
3850 		text->state = S_text;
3851 	    }
3852 	    break;
3853 
3854 	case S_dollar_paren:
3855 	    /*
3856 	     * Expecting 'C' after CJK "ESC$(".
3857 	     */
3858 	    if (ch == 'C') {
3859 		text->state = S_nonascii_text;
3860 		return;
3861 	    } else {
3862 		text->state = S_text;
3863 	    }
3864 	    break;
3865 
3866 	case S_paren:
3867 	    /*
3868 	     * Expecting 'B', 'J', 'T' or 'I' after CJK "ESC(".
3869 	     */
3870 	    if (ch == 'B' || ch == 'J' || ch == 'T') {
3871 		/*
3872 		 * Can split here.  -FM
3873 		 */
3874 		text->permissible_split = text->last_line->size;
3875 		text->state = S_text;
3876 		return;
3877 	    } else if (ch == 'I') {
3878 		text->state = S_jisx0201_text;
3879 		/*
3880 		 * Can split here.  -FM
3881 		 */
3882 		text->permissible_split = text->last_line->size;
3883 		text->kcode = JIS;
3884 		return;
3885 	    } else {
3886 		text->state = S_text;
3887 	    }
3888 	    break;
3889 
3890 	case S_nonascii_text:
3891 	    /*
3892 	     * Expecting CJK ESC after non-ASCII text.
3893 	     */
3894 	    if (ch == CH_ESC) {	/* S/390 -- gil -- 1553 */
3895 		text->state = S_esc;
3896 		text->kanji_buf = '\0';
3897 		if (HTCJK == JAPANESE) {
3898 		    text->kcode = NOKANJI;
3899 		}
3900 		return;
3901 	    } else if (UCH(ch) < 32) {
3902 		text->state = S_text;
3903 		text->kanji_buf = '\0';
3904 		if (HTCJK == JAPANESE) {
3905 		    text->kcode = NOKANJI;
3906 		}
3907 	    } else {
3908 		ch |= 0200;
3909 	    }
3910 	    break;
3911 
3912 	    /*
3913 	     * JIS X0201 Kana in JIS support.  - by ASATAKU
3914 	     */
3915 	case S_jisx0201_text:
3916 	    if (ch == CH_ESC) {	/* S/390 -- gil -- 1570 */
3917 		text->state = S_esc;
3918 		text->kanji_buf = '\0';
3919 		text->kcode = NOKANJI;
3920 		return;
3921 	    } else {
3922 		text->kanji_buf = '\216';
3923 		ch |= 0200;
3924 	    }
3925 	    break;
3926 	}			/* end switch */
3927 
3928 	if (!text->kanji_buf) {
3929 	    if ((ch & 0200) != 0) {
3930 		/*
3931 		 * JIS X0201 Kana in SJIS support.  - by ASATAKU
3932 		 */
3933 		if ((text->kcode != JIS)
3934 #ifdef USE_TH_JP_AUTO_DETECT
3935 		    && (text->specified_kcode != EUC)
3936 		    && (text->detected_kcode != DET_EUC)
3937 #endif
3938 		    && (
3939 #ifdef KANJI_CODE_OVERRIDE
3940 			   (last_kcode == SJIS) ||
3941 			   ((last_kcode == NOKANJI) &&
3942 #endif
3943 			    ((text->kcode == SJIS) ||
3944 #ifdef USE_TH_JP_AUTO_DETECT
3945 			     ((text->detected_kcode == DET_SJIS) &&
3946 			      (text->specified_kcode == NOKANJI)) ||
3947 #endif
3948 			     ((text->kcode == NOKANJI) &&
3949 			      (text->specified_kcode == SJIS)))
3950 #ifdef KANJI_CODE_OVERRIDE
3951 			   )
3952 #endif
3953 		    ) &&
3954 		    (UCH(ch) >= 0xA1) &&
3955 		    (UCH(ch) <= 0xDF)) {
3956 		    if (conv_jisx0201kana) {
3957 			unsigned char c = UCH(ch);
3958 			unsigned char kb = UCH(text->kanji_buf);
3959 
3960 			JISx0201TO0208_SJIS(c,
3961 					    (unsigned char *) &kb,
3962 					    (unsigned char *) &c);
3963 			ch = (char) c;
3964 			text->kanji_buf = kb;
3965 		    }
3966 		    /* 1998/01/19 (Mon) 09:06:15 */
3967 		    text->permissible_split = (int) text->last_line->size;
3968 		} else {
3969 		    text->kanji_buf = ch;
3970 		    /*
3971 		     * Can split here.  -FM
3972 		     */
3973 		    text->permissible_split = text->last_line->size;
3974 		    return;
3975 		}
3976 	    }
3977 	} else {
3978 	    goto check_WrapSource;
3979 	}
3980     } else if (ch == CH_ESC) {	/* S/390 -- gil -- 1587 */
3981 	return;
3982     }
3983 #ifdef CJK_EX			/* MOJI-BAKE Fix! 1997/10/12 -- 10/31 (Fri) 00:22:57 - JH7AYN */
3984     if (IS_CJK_TTY &&		/* added condition - kw */
3985 	(ch == LY_BOLD_START_CHAR || ch == LY_BOLD_END_CHAR)) {
3986 	text->permissible_split = (int) line->size;	/* Can split here */
3987 	if (HTCJK == JAPANESE)
3988 	    text->kcode = NOKANJI;
3989     }
3990 #endif
3991 
3992     if (IsSpecialAttrChar(ch) && ch != LY_SOFT_NEWLINE) {
3993 #if !defined(USE_COLOR_STYLE) || !defined(NO_DUMP_WITH_BACKSPACES)
3994 	if (line->size >= (MAX_LINE - 1)) {
3995 	    return;
3996 	}
3997 #if defined(USE_COLOR_STYLE) && !defined(NO_DUMP_WITH_BACKSPACES)
3998 	if (with_backspaces && !IS_CJK_TTY && !text->T.output_utf8) {
3999 #endif
4000 	    if (ch == LY_UNDERLINE_START_CHAR) {
4001 		line->data[line->size++] = LY_UNDERLINE_START_CHAR;
4002 		line->data[line->size] = '\0';
4003 		underline_on = TRUE;
4004 		if (!(dump_output_immediately && use_underscore))
4005 		    ctrl_chars_on_this_line++;
4006 		return;
4007 	    } else if (ch == LY_UNDERLINE_END_CHAR) {
4008 		line->data[line->size++] = LY_UNDERLINE_END_CHAR;
4009 		line->data[line->size] = '\0';
4010 		underline_on = FALSE;
4011 		if (!(dump_output_immediately && use_underscore))
4012 		    ctrl_chars_on_this_line++;
4013 		return;
4014 	    } else if (ch == LY_BOLD_START_CHAR) {
4015 		line->data[line->size++] = LY_BOLD_START_CHAR;
4016 		line->data[line->size] = '\0';
4017 		bold_on = TRUE;
4018 		ctrl_chars_on_this_line++;
4019 		return;
4020 	    } else if (ch == LY_BOLD_END_CHAR) {
4021 		line->data[line->size++] = LY_BOLD_END_CHAR;
4022 		line->data[line->size] = '\0';
4023 		bold_on = FALSE;
4024 		ctrl_chars_on_this_line++;
4025 		return;
4026 	    } else if (ch == LY_SOFT_HYPHEN) {
4027 		int i;
4028 
4029 		/*
4030 		 * Ignore the soft hyphen if it is the first character
4031 		 * on the line, or if it is preceded by a space or
4032 		 * hyphen.  -FM
4033 		 */
4034 		if (line->size < 1 || text->permissible_split >= line->size) {
4035 		    return;
4036 		}
4037 
4038 		for (i = (int) (text->permissible_split + 1);
4039 		     line->data[i];
4040 		     i++) {
4041 		    if (!IsSpecialAttrChar(UCH(line->data[i])) &&
4042 			!isspace(UCH(line->data[i])) &&
4043 			UCH(line->data[i]) != '-' &&
4044 			UCH(line->data[i]) != HT_NON_BREAK_SPACE &&
4045 			UCH(line->data[i]) != HT_EN_SPACE) {
4046 			break;
4047 		    }
4048 		}
4049 		if (line->data[i] == '\0') {
4050 		    return;
4051 		}
4052 	    }
4053 #if defined(USE_COLOR_STYLE) && !defined(NO_DUMP_WITH_BACKSPACES)
4054 	} else {
4055 	    /* if (with_backspaces && HTCJK==HTNOCJK && !text->T.output_utf8) */
4056 	    return;
4057 	}
4058 #endif
4059 
4060 #else
4061 	return;
4062 #endif
4063     } else if (ch == LY_SOFT_NEWLINE) {
4064 	line->data[line->size++] = LY_SOFT_NEWLINE;
4065 	line->data[line->size] = '\0';
4066 	return;
4067     }
4068 
4069     if (text->T.output_utf8) {
4070 	/*
4071 	 * Some extra checks for UTF-8 output here to make sure
4072 	 * memory is not overrun.  For a non-first char, append
4073 	 * to the line here and return.  - kw
4074 	 */
4075 	if (IS_UTF_EXTRA(ch)) {
4076 	    if ((line->size > (MAX_LINE - 1))
4077 		|| (indent + (int) (line->offset + line->size)
4078 		    + UTFXTRA_ON_THIS_LINE
4079 		    - ctrl_chars_on_this_line
4080 		    + ((line->size > 0) &&
4081 		       (int) (line->data[line->size - 1] ==
4082 			      LY_SOFT_HYPHEN ?
4083 			      1 : 0)) >= LYcols_cu(text))
4084 		) {
4085 		if (!text->permissible_split || text->source) {
4086 		    text->permissible_split = line->size;
4087 		    while (text->permissible_split > 0 &&
4088 			   IS_UTF_EXTRA(line->data[text->permissible_split - 1]))
4089 			text->permissible_split--;
4090 		    if (text->permissible_split &&
4091 			(line->data[text->permissible_split - 1] & 0x80))
4092 			text->permissible_split--;
4093 		    if (text->permissible_split == line->size)
4094 			text->permissible_split = 0;
4095 		}
4096 		split_line(text, text->permissible_split);
4097 		line = text->last_line;
4098 		if (text->source && line->size - ctrl_chars_on_this_line
4099 		    + UTFXTRA_ON_THIS_LINE == 0)
4100 		    HText_appendCharacter(text, LY_SOFT_NEWLINE);
4101 	    }
4102 	    line->data[line->size++] = (char) ch;
4103 	    line->data[line->size] = '\0';
4104 	    utfxtra_on_this_line++;
4105 	    ctrl_chars_on_this_line++;
4106 	    return;
4107 	} else if (ch & 0x80) {	/* a first char of UTF-8 sequence - kw */
4108 	    if ((line->size > (MAX_LINE - 7))) {
4109 		if (!text->permissible_split || text->source) {
4110 		    text->permissible_split = line->size;
4111 		    while (text->permissible_split > 0 &&
4112 			   (line->data[text->permissible_split - 1] & 0xc0)
4113 			   == 0x80) {
4114 			text->permissible_split--;
4115 		    }
4116 		    if (text->permissible_split == line->size)
4117 			text->permissible_split = 0;
4118 		}
4119 		split_line(text, text->permissible_split);
4120 		line = text->last_line;
4121 		if (text->source && line->size - ctrl_chars_on_this_line
4122 		    + UTFXTRA_ON_THIS_LINE == 0)
4123 		    HText_appendCharacter(text, LY_SOFT_NEWLINE);
4124 	    }
4125 	}
4126     }
4127 
4128     /*
4129      * New Line.
4130      */
4131     if (ch == '\n') {
4132 	new_line(text);
4133 	text->in_line_1 = YES;	/* First line of new paragraph */
4134 	/*
4135 	 * There are some pages written in
4136 	 * different kanji codes.  - TA & kw
4137 	 */
4138 	if (HTCJK == JAPANESE)
4139 	    text->kcode = NOKANJI;
4140 	return;
4141     }
4142 
4143     /*
4144      * Convert EN_SPACE to a space here so that it doesn't get collapsed.
4145      */
4146     if (ch == HT_EN_SPACE)
4147 	ch = ' ';
4148 
4149 #ifdef SH_EX			/* 1997/11/01 (Sat) 12:08:54 */
4150     if (ch == 0x0b) {		/* ^K ??? */
4151 	ch = '\r';
4152     }
4153     if (ch == 0x1a) {		/* ^Z ??? */
4154 	ch = '\r';
4155     }
4156 #endif
4157 
4158     /*
4159      * I'm going to cheat here in a BIG way.  Since I know that all
4160      * \rs will be trapped by HTML_put_character I'm going to use
4161      * \r to mean go down a line but don't start a new paragraph.
4162      * i.e., use the second line indenting.
4163      */
4164     if (ch == '\r') {
4165 	new_line(text);
4166 	text->in_line_1 = NO;
4167 	/*
4168 	 * There are some pages written in
4169 	 * different kanji codes.  - TA & kw
4170 	 */
4171 	if (HTCJK == JAPANESE)
4172 	    text->kcode = NOKANJI;
4173 	return;
4174     }
4175 
4176     /*
4177      * Tabs.
4178      */
4179     if (ch == '\t') {
4180 	const HTTabStop *Tab;
4181 	int target, target_cu;	/* Where to tab to */
4182 	int here, here_cu;	/* in _cu we try to guess what curses thinks */
4183 
4184 	if (line->size > 0 && line->data[line->size - 1] == LY_SOFT_HYPHEN) {
4185 	    /*
4186 	     * A tab shouldn't follow a soft hyphen, so
4187 	     * if one does, we'll dump the soft hyphen.  -FM
4188 	     */
4189 	    line->data[--line->size] = '\0';
4190 	    ctrl_chars_on_this_line--;
4191 	}
4192 	here = ((int) (line->size + line->offset) + indent)
4193 	    - ctrl_chars_on_this_line;	/* Consider special chars GAB */
4194 	here_cu = here + UTFXTRA_ON_THIS_LINE;
4195 	if (style->tabs) {	/* Use tab table */
4196 	    for (Tab = style->tabs;
4197 		 Tab->position <= here;
4198 		 Tab++) {
4199 		if (!Tab->position) {
4200 		    new_line(text);
4201 		    return;
4202 		}
4203 	    }
4204 	    target = Tab->position;
4205 	} else if (text->in_line_1) {	/* Use 2nd indent */
4206 	    if (here >= (int) style->leftIndent) {
4207 		new_line(text);	/* wrap */
4208 		return;
4209 	    } else {
4210 		target = (int) style->leftIndent;
4211 	    }
4212 	} else {		/* Default tabs align with left indent mod 8 */
4213 #ifdef DEFAULT_TABS_8
4214 	    target = (((int) line->offset + (int) line->size + 8) & (-8))
4215 		+ (int) style->leftIndent;
4216 #else
4217 	    new_line(text);
4218 	    return;
4219 #endif
4220 	}
4221 
4222 	if (target >= here)
4223 	    target_cu = target;
4224 	else
4225 	    target_cu = target + (here_cu - here);
4226 
4227 	if (target > WRAP_COLS(text) - (int) style->rightIndent &&
4228 	    HTOutputFormat != WWW_SOURCE) {
4229 	    new_line(text);
4230 	} else {
4231 	    /*
4232 	     * Can split here.  -FM
4233 	     */
4234 	    text->permissible_split = line->size;
4235 	    if (target_cu > WRAP_COLS(text))
4236 		target -= target_cu - WRAP_COLS(text);
4237 	    if (line->size == 0) {
4238 		line->offset = (unsigned short) (line->offset + (target - here));
4239 	    } else {
4240 		for (; here < target; here++) {
4241 		    /* Put character into line */
4242 		    line->data[line->size++] = ' ';
4243 		    line->data[line->size] = '\0';
4244 		}
4245 	    }
4246 	}
4247 	return;
4248     }
4249     /* if tab */
4250   check_WrapSource:
4251     if ((text->source || dont_wrap_pre) && text == HTMainText) {
4252 	/*
4253 	 * If we're displaying document source, wrap long lines to keep all of
4254 	 * the source visible.
4255 	 */
4256 	int target = (int) (line->offset + line->size) - ctrl_chars_on_this_line;
4257 	int target_cu = target + UTFXTRA_ON_THIS_LINE;
4258 
4259 	if (target >= WRAP_COLS(text) - style->rightIndent -
4260 	    ((IS_CJK_TTY && text->kanji_buf) ? 1 : 0) ||
4261 	    (text->T.output_utf8 &&
4262 	     target_cu + UTF_XLEN(ch) >= LYcols_cu(text))) {
4263 	    int saved_kanji_buf;
4264 	    eGridState saved_state;
4265 	    BOOL add_blank = (dont_wrap_pre
4266 			      && line->size
4267 			      && (line->data[line->size - 1] == ' '));
4268 
4269 	    new_line(text);
4270 	    line = text->last_line;
4271 
4272 	    saved_kanji_buf = text->kanji_buf;
4273 	    saved_state = text->state;
4274 	    text->kanji_buf = '\0';
4275 	    text->state = S_text;
4276 	    HText_appendCharacter(text, LY_SOFT_NEWLINE);
4277 	    if (add_blank)
4278 		HText_appendCharacter(text, ' ');
4279 	    text->kanji_buf = saved_kanji_buf;
4280 	    text->state = saved_state;
4281 	}
4282     }
4283 
4284     if (ch == ' ') {
4285 	/*
4286 	 * Can split here.  -FM
4287 	 */
4288 	text->permissible_split = text->last_line->size;
4289 	/*
4290 	 * There are some pages written in
4291 	 * different kanji codes.  - TA
4292 	 */
4293 	if (HTCJK == JAPANESE)
4294 	    text->kcode = NOKANJI;
4295     }
4296 
4297     /*
4298      * Check for end of line.
4299      *
4300      * Notes:
4301      * 1) text->permissible_split is nonzero if we found a place to split the
4302      *    line.  If there is no such place, we still will wrap at the display
4303      *    limits (the comparison against LYcols_cu).  Furthermore, if the
4304      *    curses-pads feature is active, we will ignore the first comparison
4305      *    (against WRAP_COLS) to allow wide preformatted text to be displayed
4306      *    without wrapping.
4307      * 2) ctrl_chars_on_this_line are nonprintable bytes used for formatting.
4308      */
4309     actual = ((indent + (int) line->offset + (int) line->size) +
4310 	      ((line->size > 0) &&
4311 	       (int) (line->data[line->size - 1] == LY_SOFT_HYPHEN ? 1 : 0))
4312 	      - ctrl_chars_on_this_line);
4313 
4314     if (((text->permissible_split
4315 #ifdef USE_CURSES_PADS
4316 	  || !LYwideLines
4317 #endif
4318 	 ) && (actual
4319 	       + (int) style->rightIndent
4320 	       + ((IS_CJK_TTY && text->kanji_buf) ? 1 : 0)
4321 	 ) >= WRAP_COLS(text))
4322 	|| (text->T.output_utf8
4323 	    && ((actual
4324 		 + UTFXTRA_ON_THIS_LINE
4325 		 + UTF_XLEN(ch)
4326 		) > (LYcols_cu(text) - 1)))) {
4327 
4328 	if (style->wordWrap && HTOutputFormat != WWW_SOURCE) {
4329 #ifdef USE_JUSTIFY_ELTS
4330 	    if (REALLY_CAN_JUSTIFY(text))
4331 		this_line_was_split = TRUE;
4332 #endif
4333 	    split_line(text, text->permissible_split);
4334 	    if (ch == ' ') {
4335 		return;		/* Ignore space causing split */
4336 	    }
4337 
4338 	} else if (HTOutputFormat == WWW_SOURCE) {
4339 	    /*
4340 	     * For source output we don't want to wrap this stuff
4341 	     * unless absolutely necessary.  - LJM
4342 	     * !
4343 	     * If we don't wrap here we might get a segmentation fault.
4344 	     * but let's see what happens
4345 	     */
4346 	    if ((int) line->size >= (int) (MAX_LINE - 1)) {
4347 		new_line(text);	/* try not to linewrap */
4348 	    }
4349 	} else {
4350 	    /*
4351 	     * For normal stuff like pre let's go ahead and
4352 	     * wrap so the user can see all of the text.
4353 	     */
4354 	    if ((dump_output_immediately || (crawl && traversal))
4355 		&& dont_wrap_pre) {
4356 		if ((int) line->size >= (int) (MAX_LINE - 1)) {
4357 		    new_line(text);
4358 		}
4359 	    } else {
4360 		new_line(text);
4361 	    }
4362 	}
4363     } else if ((int) line->size >= (int) (MAX_LINE - 1)) {
4364 	/*
4365 	 * Never overrun memory if DISPLAY_COLS is set to a large value - KW
4366 	 */
4367 	new_line(text);
4368     }
4369 
4370     /*
4371      * Insert normal characters.
4372      */
4373     if (ch == HT_NON_BREAK_SPACE
4374 #ifdef USE_JUSTIFY_ELTS
4375 	&& !REALLY_CAN_JUSTIFY(text)
4376 #endif
4377 	)
4378 	ch = ' ';
4379 #ifdef USE_JUSTIFY_ELTS
4380     else
4381 	have_raw_nbsps = TRUE;
4382 #endif
4383 
4384     /* we leave raw HT_NON_BREAK_SPACE otherwise (we'll substitute it later) */
4385 
4386     if (ch & 0x80)
4387 	text->have_8bit_chars = YES;
4388 
4389     /*
4390      * Kanji charactor handling.
4391      */
4392     {
4393 	HTFont font = style->font;
4394 	unsigned char hi, lo, tmp[2];
4395 
4396 	line = text->last_line;	/* May have changed */
4397 
4398 	if (IS_CJK_TTY && text->kanji_buf) {
4399 	    hi = UCH(text->kanji_buf);
4400 	    lo = UCH(ch);
4401 
4402 	    if (HTCJK == JAPANESE) {
4403 		if (text->kcode != JIS) {
4404 		    if (IS_SJIS_2BYTE(hi, lo)) {
4405 			if (IS_EUC(hi, lo)) {
4406 #ifdef KANJI_CODE_OVERRIDE
4407 			    if (last_kcode != NOKANJI)
4408 				text->kcode = last_kcode;
4409 			    else
4410 #endif
4411 			    if (text->specified_kcode != NOKANJI)
4412 				text->kcode = text->specified_kcode;
4413 #ifdef USE_TH_JP_AUTO_DETECT
4414 			    else if (text->detected_kcode == DET_EUC)
4415 				text->kcode = EUC;
4416 			    else if (text->detected_kcode == DET_SJIS)
4417 				text->kcode = SJIS;
4418 #endif
4419 			    else if (IS_EUC_X0201KANA(hi, lo) &&
4420 				     (text->kcode != EUC))
4421 				text->kcode = SJIS;
4422 			} else
4423 			    text->kcode = SJIS;
4424 		    } else if (IS_EUC(hi, lo))
4425 			text->kcode = EUC;
4426 		    else
4427 			text->kcode = NOKANJI;
4428 		}
4429 
4430 		switch (kanji_code) {
4431 		case EUC:
4432 		    if (text->kcode == SJIS) {
4433 			SJIS_TO_EUC1(hi, lo, tmp);
4434 			line->data[line->size++] = (char) tmp[0];
4435 			line->data[line->size++] = (char) tmp[1];
4436 		    } else if (IS_EUC(hi, lo)) {
4437 			if (conv_jisx0201kana) {
4438 			    JISx0201TO0208_EUC(hi, lo, &hi, &lo);
4439 			}
4440 			line->data[line->size++] = (char) hi;
4441 			line->data[line->size++] = (char) lo;
4442 		    } else {
4443 			CTRACE((tfp,
4444 				"This character (%X:%X) doesn't seem Japanese\n",
4445 				hi, lo));
4446 			line->data[line->size++] = '=';
4447 			line->data[line->size++] = '=';
4448 		    }
4449 		    break;
4450 
4451 		case SJIS:
4452 		    if ((text->kcode == EUC) || (text->kcode == JIS)) {
4453 			if (!conv_jisx0201kana && IS_EUC_X0201KANA(hi, lo)) {
4454 			    if (IS_EUC_X0201KANA(hi, lo)) {
4455 				line->data[line->size++] = (char) lo;
4456 			    } else {
4457 				EUC_TO_SJIS1(hi, lo, tmp);
4458 				line->data[line->size++] = (char) tmp[0];
4459 				line->data[line->size++] = (char) tmp[1];
4460 			    }
4461 			}
4462 		    } else if (IS_SJIS_2BYTE(hi, lo)) {
4463 			line->data[line->size++] = (char) hi;
4464 			line->data[line->size++] = (char) lo;
4465 		    } else {
4466 			line->data[line->size++] = '=';
4467 			line->data[line->size++] = '=';
4468 			CTRACE((tfp,
4469 				"This character (%X:%X) doesn't seem Japanese\n",
4470 				hi, lo));
4471 		    }
4472 		    break;
4473 
4474 		default:
4475 		    break;
4476 		}
4477 	    } else {
4478 		line->data[line->size++] = (char) hi;
4479 		line->data[line->size++] = (char) lo;
4480 	    }
4481 	    text->kanji_buf = 0;
4482 	} else if (!conv_jisx0201kana
4483 		   && (HTCJK == JAPANESE)
4484 		   && IS_SJIS_X0201KANA(UCH((ch))) &&
4485 		   (kanji_code == EUC)) {
4486 	    line->data[line->size++] = (char) UCH(0x8e);
4487 	    line->data[line->size++] = (char) ch;
4488 	} else if (IS_CJK_TTY) {
4489 	    line->data[line->size++] = (char) ((kanji_code != NOKANJI) ?
4490 					       ch :
4491 					       (font & HT_CAPITALS) ?
4492 					       TOUPPER(ch) : ch);
4493 	} else {
4494 	    line->data[line->size++] =	/* Put character into line */
4495 		(char) (font & HT_CAPITALS ? TOUPPER(ch) : ch);
4496 	}
4497 	line->data[line->size] = '\0';
4498 	if (font & HT_DOUBLE)	/* Do again if doubled */
4499 	    HText_appendCharacter(text, HT_NON_BREAK_SPACE);
4500 	/* NOT a permissible split */
4501 
4502 	if (ch == LY_SOFT_HYPHEN) {
4503 	    ctrl_chars_on_this_line++;
4504 	    /*
4505 	     * Can split here.  -FM
4506 	     */
4507 	    text->permissible_split = text->last_line->size;
4508 	}
4509 	if (ch == LY_SOFT_NEWLINE) {
4510 	    ctrl_chars_on_this_line++;
4511 	}
4512     }
4513     return;
4514 }
4515 
4516 #ifdef USE_COLOR_STYLE
4517 /*  Insert a style change into the current line
4518  *  -------------------------------------------
4519  */
_internal_HTC(HText * text,int style,int dir)4520 void _internal_HTC(HText *text, int style, int dir)
4521 {
4522     HTLine *line;
4523 
4524     /* can't change style if we have no text to change style with */
4525     if (text != 0) {
4526 
4527 	line = text->last_line;
4528 
4529 	if (line->numstyles > 0 && dir == 0 &&
4530 	    line->styles[line->numstyles - 1].sc_direction &&
4531 	    line->styles[line->numstyles - 1].sc_style == (unsigned) style &&
4532 	    (int) line->styles[line->numstyles - 1].sc_horizpos
4533 	    == (int) line->size - ctrl_chars_on_this_line) {
4534 	    /*
4535 	     * If this is an OFF change directly preceded by an
4536 	     * ON for the same style, just remove the previous one.  - kw
4537 	     */
4538 	    line->numstyles--;
4539 	} else if (line->numstyles < MAX_STYLES_ON_LINE) {
4540 	    line->styles[line->numstyles].sc_horizpos = line->size;
4541 	    /*
4542 	     * Special chars for bold and underlining usually don't
4543 	     * occur with color style, but soft hyphen can.
4544 	     * And in UTF-8 display mode all non-initial bytes are
4545 	     * counted as ctrl_chars.  - kw
4546 	     */
4547 	    if ((int) line->styles[line->numstyles].sc_horizpos >= ctrl_chars_on_this_line) {
4548 		line->styles[line->numstyles].sc_horizpos -= ctrl_chars_on_this_line;
4549 	    }
4550 	    line->styles[line->numstyles].sc_style = style;
4551 	    line->styles[line->numstyles].sc_direction = dir;
4552 	    CTRACE_STYLE((tfp, "internal_HTC %d:style[%d] %d (dir=%d)\n",
4553 			  line->size,
4554 			  line->numstyles,
4555 			  style,
4556 			  dir));
4557 	    line->numstyles++;
4558 	}
4559     }
4560 }
4561 #endif
4562 
4563 /*	Set LastChar element in the text object.
4564  *	----------------------------------------
4565  */
HText_setLastChar(HText * text,int ch)4566 void HText_setLastChar(HText *text, int ch)
4567 {
4568     if (!text)
4569 	return;
4570 
4571     text->LastChar = (char) ch;
4572 }
4573 
4574 /*	Get LastChar element in the text object.
4575  *	----------------------------------------
4576  */
HText_getLastChar(HText * text)4577 char HText_getLastChar(HText *text)
4578 {
4579     if (!text)
4580 	return ('\0');
4581 
4582     return ((char) text->LastChar);
4583 }
4584 
4585 /*		Simple table handling - private
4586  *		-------------------------------
4587  */
4588 
4589 /*
4590  * HText_insertBlanksInStblLines fixes up table lines when simple table
4591  * processing is closed, by calling insert_blanks_in_line for lines
4592  * that need fixup.  Also recalculates alignment for those lines,
4593  * does additional updating of anchor positions, and makes sure the
4594  * display of the lines on screen will be updated after partial display
4595  * upon return to mainloop.  - kw
4596  */
HText_insertBlanksInStblLines(HText * me,int ncols)4597 static int HText_insertBlanksInStblLines(HText *me, int ncols)
4598 {
4599     HTLine *line;
4600     HTLine *mod_line, *first_line = NULL;
4601     int *oldpos;
4602     int *newpos;
4603     int ninserts, lineno;
4604     int last_lineno, first_lineno_pass2;
4605 
4606 #ifdef EXP_NESTED_TABLES
4607     int last_nonempty = -1;
4608 #endif
4609     int added_chars_before = 0;
4610     int lines_changed = 0;
4611     int max_width = 0, indent, spare, table_offset;
4612     HTStyle *style;
4613     short alignment;
4614     int i = 0;
4615 
4616     lineno = Stbl_getStartLine(me->stbl);
4617     if (lineno < 0 || lineno > me->Lines)
4618 	return -1;
4619     /*
4620      * oldpos, newpos:  allocate space for two int arrays.
4621      */
4622     oldpos = typecallocn(int, 2 * (size_t)ncols);
4623     if (!oldpos)
4624 	return -1;
4625     else
4626 	newpos = oldpos + ncols;
4627     for (line = FirstHTLine(me); i < lineno; line = line->next, i++) {
4628 	if (!line) {
4629 	    free(oldpos);
4630 	    return -1;
4631 	}
4632     }
4633     first_lineno_pass2 = last_lineno = me->Lines;
4634     for (; line && lineno <= last_lineno; line = line->next, lineno++) {
4635 	ninserts = Stbl_getFixupPositions(me->stbl, lineno, oldpos, newpos);
4636 	if (ninserts < 0)
4637 	    continue;
4638 	if (!first_line) {
4639 	    first_line = line;
4640 	    first_lineno_pass2 = lineno;
4641 	    if (TRACE) {
4642 		int ip;
4643 
4644 		CTRACE((tfp, "line %d first to adjust  --  newpos:", lineno));
4645 		for (ip = 0; ip < ncols; ip++)
4646 		    CTRACE((tfp, " %d", newpos[ip]));
4647 		CTRACE((tfp, "\n"));
4648 	    }
4649 	}
4650 	if (line == me->last_line) {
4651 	    if (line->size == 0 || HText_TrueEmptyLine(line, me, FALSE))
4652 		continue;
4653 	    /*
4654 	     * Last ditch effort to end the table with a line break,
4655 	     * if HTML_end_element didn't do it.  - kw
4656 	     */
4657 	    if (first_line == line)	/* obscure: all table on last line... */
4658 		first_line = NULL;
4659 	    new_line(me);
4660 	    line = me->last_line->prev;
4661 	    if (first_line == NULL)
4662 		first_line = line;
4663 	}
4664 	if (ninserts == 0) {
4665 	    /*  Do it also for no positions (but not error) */
4666 	    int width = HText_TrueLineSize(line, me, FALSE);
4667 
4668 	    if (width > max_width)
4669 		max_width = width;
4670 #ifdef EXP_NESTED_TABLES
4671 	    if (nested_tables) {
4672 		if (width && last_nonempty < lineno)
4673 		    last_nonempty = lineno;
4674 	    }
4675 #endif
4676 	    CTRACE((tfp, "line %d true/max width:%d/%d oldpos: NONE\n",
4677 		    lineno, width, max_width));
4678 	    continue;
4679 	}
4680 	mod_line = insert_blanks_in_line(line, lineno, me,
4681 					 &me->last_anchor_before_stbl /*updates++ */ ,
4682 					 ninserts, oldpos, newpos);
4683 	if (mod_line) {
4684 	    if (line == me->last_line) {
4685 		me->last_line = mod_line;
4686 	    } else {
4687 		added_chars_before += (mod_line->size - line->size);
4688 	    }
4689 	    line->prev->next = mod_line;
4690 	    line->next->prev = mod_line;
4691 	    lines_changed++;
4692 	    if (line == first_line)
4693 		first_line = mod_line;
4694 	    freeHTLine(me, line);
4695 	    line = mod_line;
4696 #ifdef DISP_PARTIAL
4697 	    /*
4698 	     * Make sure modified lines get fully re-displayed after
4699 	     * loading with partial display is done.
4700 	     */
4701 	    if (me->first_lineno_last_disp_partial >= 0) {
4702 		if (me->first_lineno_last_disp_partial >= lineno) {
4703 		    ResetPartialLinenos(me);
4704 		} else if (me->last_lineno_last_disp_partial >= lineno) {
4705 		    me->last_lineno_last_disp_partial = lineno - 1;
4706 		}
4707 	    }
4708 #endif
4709 	} {
4710 	    int width = HText_TrueLineSize(line, me, FALSE);
4711 
4712 	    if (width > max_width)
4713 		max_width = width;
4714 #ifdef EXP_NESTED_TABLES
4715 	    if (nested_tables) {
4716 		if (width && last_nonempty < lineno)
4717 		    last_nonempty = lineno;
4718 	    }
4719 #endif
4720 	    if (TRACE) {
4721 		int ip;
4722 
4723 		CTRACE((tfp, "line %d true/max width:%d/%d oldpos:",
4724 			lineno, width, max_width));
4725 		for (ip = 0; ip < ninserts; ip++)
4726 		    CTRACE((tfp, " %d", oldpos[ip]));
4727 		CTRACE((tfp, "\n"));
4728 	    }
4729 	}
4730     }
4731     /*
4732      * Line offsets have been set based on the paragraph style, and
4733      * have already been updated for centering or right-alignment
4734      * for each line in split_line.  Here we want to undo all that, and
4735      * align the table as a whole (i.e.  all lines for which
4736      * Stbl_getFixupPositions returned >= 0).  All those lines have to
4737      * get the same offset, for the simple table formatting mechanism
4738      * to make sense, and that may not actually be the case at this point.
4739      *
4740      * What indentation and alignment do we want for the table as
4741      * a whole?  Let's take most style properties from me->style.
4742      * With some luck, it is the appropriate style for the element
4743      * enclosing the TABLE.  But let's take alignment from the attribute
4744      * of the TABLE itself instead, if it was specified.
4745      *
4746      * Note that this logic assumes that all lines have been finished
4747      * by split_line.  The order of calls made by HTML_end_element for
4748      * HTML_TABLE should take care of this.
4749      */
4750     style = me->style;
4751     alignment = Stbl_getAlignment(me->stbl);
4752     if (alignment == HT_ALIGN_NONE)
4753 	alignment = style->alignment;
4754     indent = style->leftIndent;
4755     /* Calculate spare character positions */
4756     spare = WRAP_COLS(me) -
4757 	(int) style->rightIndent - indent - max_width;
4758     if (spare < 0 && (int) style->rightIndent + spare >= 0) {
4759 	/*
4760 	 * Not enough room!  But we can fit if we ignore right indentation,
4761 	 * so let's do that.
4762 	 */
4763 	spare = 0;
4764     } else if (spare < 0) {
4765 	spare += style->rightIndent;	/* ignore right indent, but need more */
4766     }
4767     if (spare < 0 && indent + spare >= 0) {
4768 	/*
4769 	 * Still not enough room.  But we can move to the left.
4770 	 */
4771 	indent += spare;
4772 	spare = 0;
4773     } else if (spare < 0) {
4774 	/*
4775 	 * Still not enough.  Something went wrong.  Try the best we
4776 	 * can do.
4777 	 */
4778 	CTRACE((tfp,
4779 		"BUG: insertBlanks: resulting table too wide by %d positions!\n",
4780 		-spare));
4781 	indent = spare = 0;
4782     }
4783     /*
4784      * Align left, right or center.
4785      */
4786     switch (alignment) {
4787     case HT_CENTER:
4788 	table_offset = indent + spare / 2;
4789 	break;
4790     case HT_RIGHT:
4791 	table_offset = indent + spare;
4792 	break;
4793     case HT_LEFT:
4794     case HT_JUSTIFY:
4795     default:
4796 	table_offset = indent;
4797 	break;
4798     }				/* switch */
4799 
4800     CTRACE((tfp, "changing offsets"));
4801     for (line = first_line, lineno = first_lineno_pass2;
4802 	 line && lineno <= last_lineno && line != me->last_line;
4803 	 line = line->next, lineno++) {
4804 	ninserts = Stbl_getFixupPositions(me->stbl, lineno, oldpos, newpos);
4805 	if (ninserts >= 0 && (int) line->offset != table_offset) {
4806 #ifdef DISP_PARTIAL
4807 	    /*  As above make sure modified lines get fully re-displayed */
4808 	    if (me->first_lineno_last_disp_partial >= 0) {
4809 		if (me->first_lineno_last_disp_partial >= lineno) {
4810 		    ResetPartialLinenos(me);
4811 		} else if (me->last_lineno_last_disp_partial >= lineno) {
4812 		    me->last_lineno_last_disp_partial = lineno - 1;
4813 		}
4814 	    }
4815 #endif
4816 	    CTRACE((tfp, " %d:%d", lineno, table_offset - line->offset));
4817 	    line->offset = (unsigned short) (table_offset > 0
4818 					     ? table_offset
4819 					     : 0);
4820 	}
4821     }
4822 #ifdef EXP_NESTED_TABLES
4823     if (nested_tables) {
4824 	if (max_width)
4825 	    Stbl_update_enclosing(me->stbl, max_width, last_nonempty);
4826     }
4827 #endif
4828     CTRACE((tfp, " %d:done\n", lineno));
4829     free(oldpos);
4830     return lines_changed;
4831 }
4832 
4833 /*		Simple table handling - public functions
4834  *		----------------------------------------
4835  */
4836 
4837 /*	Cancel simple table handling
4838 */
HText_cancelStbl(HText * me)4839 void HText_cancelStbl(HText *me)
4840 {
4841     if (!me || !me->stbl) {
4842 	CTRACE((tfp, "cancelStbl: ignored.\n"));
4843 	return;
4844     }
4845     CTRACE((tfp, "cancelStbl: ok, will do.\n"));
4846 #ifdef EXP_NESTED_TABLES
4847     if (nested_tables) {
4848 	STable_info *stbl = me->stbl;
4849 
4850 	while (stbl) {
4851 	    STable_info *enclosing = Stbl_get_enclosing(stbl);
4852 
4853 	    Stbl_free(stbl);
4854 	    stbl = enclosing;
4855 	}
4856     } else
4857 #endif
4858 	Stbl_free(me->stbl);
4859     me->stbl = NULL;
4860 }
4861 
4862 /*	Start simple table handling
4863 */
HText_startStblTABLE(HText * me,int alignment)4864 void HText_startStblTABLE(HText *me, int alignment)
4865 {
4866     if (me) {
4867 #ifdef EXP_NESTED_TABLES
4868 	STable_info *current = me->stbl;
4869 #endif
4870 
4871 #ifdef EXP_NESTED_TABLES
4872 	if (nested_tables) {
4873 	    if (current)
4874 		new_line(me);
4875 	} else
4876 #endif
4877 	{
4878 	    if (me->stbl)
4879 		HText_cancelStbl(me);	/* auto cancel previously open table */
4880 	}
4881 
4882 	me->stbl = Stbl_startTABLE(alignment);
4883 	if (me->stbl) {
4884 	    CTRACE((tfp, "startStblTABLE: started.\n"));
4885 #ifdef EXP_NESTED_TABLES
4886 	    if (nested_tables) {
4887 		Stbl_set_enclosing(me->stbl, current, me->last_anchor_before_stbl);
4888 	    }
4889 #endif
4890 	    me->last_anchor_before_stbl = me->last_anchor;
4891 	} else {
4892 	    CTRACE((tfp, "startStblTABLE: failed.\n"));
4893 	}
4894     }
4895 }
4896 
4897 #ifdef EXP_NESTED_TABLES
free_enclosed_stbl(HText * me)4898 static void free_enclosed_stbl(HText *me)
4899 {
4900     if (me != NULL && me->enclosed_stbl != NULL) {
4901 	HTList *list = me->enclosed_stbl;
4902 	STable_info *stbl;
4903 
4904 	while (NULL != (stbl = (STable_info *) HTList_nextObject(list))) {
4905 	    CTRACE((tfp, "endStblTABLE: finally free %p\n", (void *) me->stbl));
4906 	    Stbl_free(stbl);
4907 	}
4908 	HTList_delete(me->enclosed_stbl);
4909 	me->enclosed_stbl = NULL;
4910     }
4911 }
4912 
4913 #else
4914 #define free_enclosed_stbl(me)	/* nothing */
4915 #endif
4916 
4917 /*	Finish simple table handling
4918  *	Return TRUE if the table is nested inside another table.
4919  */
HText_endStblTABLE(HText * me)4920 BOOLEAN HText_endStblTABLE(HText *me)
4921 {
4922     int ncols, lines_changed = 0;
4923     STable_info *enclosing = NULL;
4924 
4925     if (!me || !me->stbl) {
4926 	CTRACE((tfp, "endStblTABLE: ignored.\n"));
4927 	free_enclosed_stbl(me);
4928 	return FALSE;
4929     }
4930     CTRACE((tfp, "endStblTABLE: ok, will try.\n"));
4931 
4932     ncols = Stbl_finishTABLE(me->stbl);
4933     CTRACE((tfp, "endStblTABLE: ncols = %d.\n", ncols));
4934 
4935     if (ncols > 0) {
4936 	lines_changed = HText_insertBlanksInStblLines(me, ncols);
4937 	CTRACE((tfp, "endStblTABLE: changed %d lines, done.\n", lines_changed));
4938 #ifdef DISP_PARTIAL
4939 	/* allow HTDisplayPartial() to redisplay the changed lines.
4940 	 * There is no harm if we got several stbl in the document, hope so.
4941 	 */
4942 	NumOfLines_partial -= lines_changed;	/* fake */
4943 #endif /* DISP_PARTIAL */
4944     }
4945 #ifdef EXP_NESTED_TABLES
4946     if (nested_tables) {
4947 	enclosing = Stbl_get_enclosing(me->stbl);
4948 	me->last_anchor_before_stbl = Stbl_get_last_anchor_before(me->stbl);
4949 	if (enclosing == NULL) {
4950 	    Stbl_free(me->stbl);
4951 	    free_enclosed_stbl(me);
4952 	} else {
4953 	    if (me->enclosed_stbl == NULL)
4954 		me->enclosed_stbl = HTList_new();
4955 	    HTList_addObject(me->enclosed_stbl, me->stbl);
4956 	    CTRACE((tfp, "endStblTABLE: postpone free %p\n", (void *) me->stbl));
4957 	}
4958 	me->stbl = enclosing;
4959     } else {
4960 	Stbl_free(me->stbl);
4961 	me->stbl = NULL;
4962     }
4963 #else
4964     Stbl_free(me->stbl);
4965     me->stbl = NULL;
4966 #endif
4967 
4968     CTRACE((tfp, "endStblTABLE: have%s enclosing table (%p)\n",
4969 	    enclosing == 0 ? " NO" : "", (void *) enclosing));
4970 
4971     return (BOOLEAN) (enclosing != 0);
4972 }
4973 
4974 /*	Start simple table row
4975 */
HText_startStblTR(HText * me,int alignment)4976 void HText_startStblTR(HText *me, int alignment)
4977 {
4978     if (!me || !me->stbl)
4979 	return;
4980     if (Stbl_addRowToTable(me->stbl, alignment, me->Lines) < 0)
4981 	HText_cancelStbl(me);	/* give up */
4982 }
4983 
4984 /*	Finish simple table row
4985 */
HText_endStblTR(HText * me)4986 void HText_endStblTR(HText *me)
4987 {
4988     if (!me || !me->stbl)
4989 	return;
4990     /* should this do something?? */
4991 }
4992 
4993 /*	Start simple table cell
4994 */
HText_startStblTD(HText * me,int colspan,int rowspan,int alignment,int isheader)4995 void HText_startStblTD(HText *me, int colspan,
4996 		       int rowspan,
4997 		       int alignment,
4998 		       int isheader)
4999 {
5000     if (!me || !me->stbl)
5001 	return;
5002     if (colspan < 0)
5003 	colspan = 1;
5004     if (colspan > TRST_MAXCOLSPAN) {
5005 	CTRACE((tfp, "*** COLSPAN=%d is too large, ignored!\n", colspan));
5006 	colspan = 1;
5007     }
5008     if (rowspan > TRST_MAXROWSPAN) {
5009 	CTRACE((tfp, "*** ROWSPAN=%d is too large, ignored!\n", rowspan));
5010 	rowspan = 1;
5011     }
5012     if (Stbl_addCellToTable(me->stbl, colspan, rowspan, alignment, isheader,
5013 			    me->Lines,
5014 			    HText_LastLineOffset(me),
5015 			    HText_LastLineSize(me, FALSE)) < 0)
5016 	HText_cancelStbl(me);	/* give up */
5017 }
5018 
5019 /*	Finish simple table cell
5020 */
HText_endStblTD(HText * me)5021 void HText_endStblTD(HText *me)
5022 {
5023     if (!me || !me->stbl)
5024 	return;
5025     if (Stbl_finishCellInTable(me->stbl, TRST_ENDCELL_ENDTD,
5026 			       me->Lines,
5027 			       HText_LastLineOffset(me),
5028 			       HText_LastLineSize(me, FALSE)) < 0)
5029 	HText_cancelStbl(me);	/* give up */
5030 }
5031 
5032 /*	Remember COL info / Start a COLGROUP and remember info
5033 */
HText_startStblCOL(HText * me,int span,int alignment,int isgroup)5034 void HText_startStblCOL(HText *me, int span,
5035 			int alignment,
5036 			int isgroup)
5037 {
5038     if (!me || !me->stbl)
5039 	return;
5040     if (span <= 0)
5041 	span = 1;
5042     if (span > TRST_MAXCOLSPAN) {
5043 	CTRACE((tfp, "*** SPAN=%d is too large, ignored!\n", span));
5044 	span = 1;
5045     }
5046     if (Stbl_addColInfo(me->stbl, span, alignment, isgroup) < 0)
5047 	HText_cancelStbl(me);	/* give up */
5048 }
5049 
5050 /*	Finish a COLGROUP
5051 */
HText_endStblCOLGROUP(HText * me)5052 void HText_endStblCOLGROUP(HText *me)
5053 {
5054     if (!me || !me->stbl)
5055 	return;
5056     if (Stbl_finishColGroup(me->stbl) < 0)
5057 	HText_cancelStbl(me);	/* give up */
5058 }
5059 
5060 /*	Start a THEAD / TFOOT / TBODY - remember its alignment info
5061 */
HText_startStblRowGroup(HText * me,int alignment)5062 void HText_startStblRowGroup(HText *me, int alignment)
5063 {
5064     if (!me || !me->stbl)
5065 	return;
5066     if (Stbl_addRowGroup(me->stbl, alignment) < 0)
5067 	HText_cancelStbl(me);	/* give up */
5068 }
5069 
compute_show_number(TextAnchor * a)5070 static void compute_show_number(TextAnchor *a)
5071 {
5072     HTAnchor *cur, *tst;
5073     TextAnchor *b;
5074     int match;
5075 
5076     a->show_number = a->number;
5077     if (unique_urls
5078 	&& HTMainText != 0
5079 	&& HTMainText->first_anchor != 0
5080 	&& a->anchor != 0
5081 	&& (cur = a->anchor->dest) != 0
5082 	&& cur->parent != 0
5083 	&& cur->parent->address != 0) {
5084 
5085 	match = 0;
5086 	for (b = HTMainText->first_anchor; b != a; b = b->next) {
5087 	    if (b->anchor != 0
5088 		&& (tst = b->anchor->dest) != 0
5089 		&& tst->parent != 0
5090 		&& tst->parent->address != 0
5091 		&& !strcmp(cur->parent->address,
5092 			   tst->parent->address)
5093 		&& !strcmp(NonNull(a->anchor->tag), NonNull(b->anchor->tag))) {
5094 		match = b->show_number;
5095 		break;
5096 	    }
5097 	}
5098 	if (match)
5099 	    a->show_number = match;
5100 	else
5101 	    a->show_number = HTMainText->next_number++;
5102     }
5103 }
5104 
5105 /*		Anchor handling
5106  *		---------------
5107  */
add_link_number(HText * text,TextAnchor * a,int save_position)5108 static void add_link_number(HText *text, TextAnchor *a, int save_position)
5109 {
5110     char marker[32];
5111 
5112     /*
5113      * If we are doing link_numbering add the link number.
5114      */
5115     if ((a->number > 0)
5116 #ifdef USE_PRETTYSRC
5117 	&& (text->source ? !psrcview_no_anchor_numbering : 1)
5118 #endif
5119 	&& links_are_numbered()) {
5120 	char saved_lastchar = text->LastChar;
5121 	int saved_linenum = text->Lines;
5122 
5123 	compute_show_number(a);
5124 
5125 	sprintf(marker, "[%d]", a->show_number);
5126 	HText_appendText(text, marker);
5127 	if (saved_linenum && text->Lines && saved_lastchar != ' ')
5128 	    text->LastChar = ']';	/* if marker not after space caused split */
5129 	if (save_position) {
5130 	    a->line_num = text->Lines;
5131 	    a->line_pos = (short) text->last_line->size;
5132 	}
5133     }
5134 }
5135 
5136 /*	Start an anchor field
5137 */
HText_beginAnchor(HText * text,int underline,HTChildAnchor * anc)5138 int HText_beginAnchor(HText *text, int underline,
5139 		      HTChildAnchor *anc)
5140 {
5141     TextAnchor *a;
5142 
5143     POOLtypecalloc(TextAnchor, a);
5144 
5145     if (a == NULL)
5146 	outofmem(__FILE__, "HText_beginAnchor");
5147 
5148     assert(a != NULL);
5149 
5150     a->inUnderline = (BOOLEAN) underline;
5151 
5152     a->sgml_offset = SGML_offset();
5153     a->line_num = text->Lines;
5154     a->line_pos = (short) text->last_line->size;
5155     if (text->last_anchor) {
5156 	text->last_anchor->next = a;
5157     } else {
5158 	text->first_anchor = a;
5159     }
5160     a->next = 0;
5161     a->anchor = anc;
5162     a->extent = 0;
5163     a->link_type = HYPERTEXT_ANCHOR;
5164     text->last_anchor = a;
5165 
5166     if (track_internal_links
5167 	&& HTAnchor_followTypedLink(anc, HTInternalLink)) {
5168 	a->number = ++(text->last_anchor_number);
5169 	a->link_type = INTERNAL_LINK_ANCHOR;
5170     } else if (HTAnchor_followLink(anc)) {
5171 	a->number = ++(text->last_anchor_number);
5172     } else {
5173 	a->number = 0;
5174     }
5175     a->show_number = 0;
5176 
5177     if (number_links_on_left)
5178 	add_link_number(text, a, TRUE);
5179     return (a->number);
5180 }
5181 
5182 /* If !really, report whether the anchor is empty. */
HText_endAnchor0(HText * text,int number,int really)5183 static BOOL HText_endAnchor0(HText *text, int number,
5184 			     int really)
5185 {
5186     TextAnchor *a;
5187 
5188     /*
5189      * The number argument is set to 0 in HTML.c and
5190      * LYCharUtils.c when we want to end the anchor
5191      * for the immediately preceding HText_beginAnchor()
5192      * call.  If it's greater than 0, we want to handle
5193      * a particular anchor.  This allows us to set links
5194      * for positions indicated by NAME or ID attributes,
5195      * without needing to close any anchor with an HREF
5196      * within which that link might be embedded.  -FM
5197      */
5198     if (number <= 0 || number == text->last_anchor->number) {
5199 	a = text->last_anchor;
5200     } else {
5201 	for (a = text->first_anchor; a; a = a->next) {
5202 	    if (a->number == number) {
5203 		break;
5204 	    }
5205 	}
5206 	if (a == NULL) {
5207 	    /*
5208 	     * There's no anchor with that number,
5209 	     * so we'll default to the last anchor,
5210 	     * and cross our fingers.  -FM
5211 	     */
5212 	    a = text->last_anchor;
5213 	}
5214     }
5215 
5216     CTRACE((tfp, "GridText:HText_endAnchor0: number:%d link_type:%d\n",
5217 	    a->number, a->link_type));
5218     if (a->link_type == INPUT_ANCHOR) {
5219 	/*
5220 	 * Shouldn't happen, but put test here anyway to be safe.  - LE
5221 	 */
5222 
5223 	CTRACE((tfp,
5224 		"BUG: HText_endAnchor0: internal error: last anchor was input field!\n"));
5225 	return FALSE;
5226     }
5227 
5228     if (a->number) {
5229 	/*
5230 	 * If it goes somewhere...
5231 	 */
5232 	int i, j, k, l;
5233 	BOOL remove_numbers_on_empty = (BOOL) ((links_are_numbered() &&
5234 						((text->hiddenlinkflag != HIDDENLINKS_MERGE)
5235 						 || (LYNoISMAPifUSEMAP &&
5236 						     !(text->node_anchor && text->node_anchor->bookmark)
5237 						     && HTAnchor_isISMAPScript
5238 						     (HTAnchor_followLink(a->anchor))))));
5239 	HTLine *last = text->last_line;
5240 	HTLine *prev = text->last_line->prev;
5241 	HTLine *start = last;
5242 	int CurBlankExtent = 0;
5243 	int BlankExtent = 0;
5244 	int extent_adjust = 0;
5245 
5246 	/* Find the length taken by the anchor */
5247 	l = text->Lines;	/* lineno of last */
5248 
5249 	/* the last line of an anchor may contain a trailing blank,
5250 	 * which will be trimmed later.  Discount it from the extent.
5251 	 */
5252 	if (l > a->line_num) {
5253 	    for (i = start->size; i > 0; --i) {
5254 		if (isspace(UCH(start->data[i - 1]))) {
5255 		    --extent_adjust;
5256 		} else {
5257 		    break;
5258 		}
5259 	    }
5260 	}
5261 
5262 	while (l > a->line_num) {
5263 	    extent_adjust += start->size;
5264 	    start = start->prev;
5265 	    l--;
5266 	}
5267 	/* Now start is the start line of the anchor */
5268 	extent_adjust += start->size - a->line_pos;
5269 	start = last;		/* Used later */
5270 
5271 	/*
5272 	 * Check if the anchor content has only
5273 	 * white and special characters, starting
5274 	 * with the content on the last line.  -FM
5275 	 */
5276 	a->extent = (short) (a->extent + extent_adjust);
5277 	if (a->extent > (int) last->size) {
5278 	    /*
5279 	     * The anchor extends over more than one line,
5280 	     * so set up to check the entire last line.  -FM
5281 	     */
5282 	    i = last->size;
5283 	} else {
5284 	    /*
5285 	     * The anchor is restricted to the last line,
5286 	     * so check from the start of the anchor.  -FM
5287 	     */
5288 	    i = a->extent;
5289 	}
5290 	k = j = (last->size - i);
5291 	while (j < (int) last->size) {
5292 	    if (!IsSpecialAttrChar(last->data[j]) &&
5293 		!isspace(UCH(last->data[j])) &&
5294 		last->data[j] != HT_NON_BREAK_SPACE &&
5295 		last->data[j] != HT_EN_SPACE)
5296 		break;
5297 	    i--;
5298 	    j++;
5299 	}
5300 	if (i == 0) {
5301 	    if (a->extent > (int) last->size) {
5302 		/*
5303 		 * The anchor starts on a preceding line, and
5304 		 * the last line has only white and special
5305 		 * characters, so declare the entire extent
5306 		 * of the last line as blank.  -FM
5307 		 */
5308 		CurBlankExtent = BlankExtent = last->size;
5309 	    } else {
5310 		/*
5311 		 * The anchor starts on the last line, and
5312 		 * has only white or special characters, so
5313 		 * declare the anchor's extent as blank.  -FM
5314 		 */
5315 		CurBlankExtent = BlankExtent = a->extent;
5316 	    }
5317 	}
5318 	/*
5319 	 * While the anchor starts on a line preceding
5320 	 * the one we just checked, and the one we just
5321 	 * checked has only white and special characters,
5322 	 * check whether the anchor's content on the
5323 	 * immediately preceding line also has only
5324 	 * white and special characters.  -FM
5325 	 */
5326 	while (i == 0 &&
5327 	       (a->extent > CurBlankExtent ||
5328 		(a->extent == CurBlankExtent &&
5329 		 k == 0 &&
5330 		 prev != text->last_line &&
5331 		 (prev->size == 0 ||
5332 		  prev->data[prev->size - 1] == ']')))) {
5333 	    start = prev;
5334 	    k = j = prev->size - a->extent + CurBlankExtent;
5335 	    if (j < 0) {
5336 		/*
5337 		 * The anchor starts on a preceding line,
5338 		 * so check all of this line.  -FM
5339 		 */
5340 		j = 0;
5341 		i = prev->size;
5342 	    } else {
5343 		/*
5344 		 * The anchor starts on this line.  -FM
5345 		 */
5346 		i = a->extent - CurBlankExtent;
5347 	    }
5348 	    while (j < (int) prev->size) {
5349 		if (!IsSpecialAttrChar(prev->data[j]) &&
5350 		    !isspace(UCH(prev->data[j])) &&
5351 		    prev->data[j] != HT_NON_BREAK_SPACE &&
5352 		    prev->data[j] != HT_EN_SPACE)
5353 		    break;
5354 		i--;
5355 		j++;
5356 	    }
5357 	    if (i == 0) {
5358 		if (a->extent > (CurBlankExtent + (int) prev->size) ||
5359 		    (a->extent == CurBlankExtent + (int) prev->size &&
5360 		     k == 0 &&
5361 		     prev->prev != text->last_line &&
5362 		     (prev->prev->size == 0 ||
5363 		      prev->prev->data[prev->prev->size - 1] == ']'))) {
5364 		    /*
5365 		     * This line has only white and special
5366 		     * characters, so treat its entire extent
5367 		     * as blank, and decrement the pointer for
5368 		     * the line to be analyzed.  -FM
5369 		     */
5370 		    CurBlankExtent += prev->size;
5371 		    BlankExtent = CurBlankExtent;
5372 		    prev = prev->prev;
5373 		} else {
5374 		    /*
5375 		     * The anchor starts on this line, and it
5376 		     * has only white or special characters, so
5377 		     * declare the anchor's extent as blank.  -FM
5378 		     */
5379 		    BlankExtent = a->extent;
5380 		    break;
5381 		}
5382 	    }
5383 	}
5384 	if (!really) {		/* Just report whether it is empty */
5385 	    a->extent = (short) (a->extent - extent_adjust);
5386 	    return (BOOL) (i == 0);
5387 	}
5388 	if (i == 0) {
5389 	    /*
5390 	     * It's an invisible anchor probably from an ALT=""
5391 	     * or an ignored ISMAP attribute due to a companion
5392 	     * USEMAP.  -FM
5393 	     */
5394 	    a->show_anchor = NO;
5395 
5396 	    CTRACE((tfp,
5397 		    "HText_endAnchor0: hidden (line,pos,ext,BlankExtent):(%d,%d,%d,%d)",
5398 		    a->line_num, a->line_pos, a->extent,
5399 		    BlankExtent));
5400 
5401 	    /*
5402 	     * If links are numbered, then try to get rid of the
5403 	     * numbered bracket and adjust the anchor count.  -FM
5404 	     *
5405 	     * Well, let's do this only if -hiddenlinks=merged is not in
5406 	     * effect, or if we can be reasonably sure that
5407 	     * this is the result of an intentional non-generation of
5408 	     * anchor text via NO_ISMAP_IF_USEMAP.  In other cases it can
5409 	     * actually be a feature that numbered links alert the viewer
5410 	     * to the presence of a link which is otherwise not selectable -
5411 	     * possibly caused by HTML errors. - kw
5412 	     */
5413 	    if (remove_numbers_on_empty) {
5414 		int NumSize = 0;
5415 		TextAnchor *anc;
5416 
5417 		/*
5418 		 * Set start->data[j] to the close-square-bracket,
5419 		 * or to the beginning of the line on which the
5420 		 * anchor start.  -FM
5421 		 */
5422 		if (start == last) {
5423 		    /*
5424 		     * The anchor starts on the last line.  -FM
5425 		     */
5426 		    j = (last->size - a->extent - 1);
5427 		} else {
5428 		    /*
5429 		     * The anchor starts on a previous line.  -FM
5430 		     */
5431 		    prev = start->prev;
5432 		    j = (start->size - a->extent + CurBlankExtent - 1);
5433 		}
5434 		if (j < 0)
5435 		    j = 0;
5436 		i = j;
5437 
5438 		/*
5439 		 * If start->data[j] is a close-square-bracket, verify
5440 		 * that it's the end of the numbered bracket, and if so,
5441 		 * strip the numbered bracket.  If start->data[j] is not
5442 		 * a close-square-bracket, check whether we had a wrap
5443 		 * and the close-square-bracket is at the end of the
5444 		 * previous line.  If so, strip the numbered bracket
5445 		 * from that line.  -FM
5446 		 */
5447 		if (start->data[j] == ']') {
5448 		    j--;
5449 		    NumSize++;
5450 		    while (j >= 0 && isdigit(UCH(start->data[j]))) {
5451 			j--;
5452 			NumSize++;
5453 		    }
5454 		    while (j < 0) {
5455 			j++;
5456 			NumSize--;
5457 		    }
5458 		    if (start->data[j] == '[') {
5459 			/*
5460 			 * The numbered bracket is entirely
5461 			 * on this line.  -FM
5462 			 */
5463 			NumSize++;
5464 			if (start == last && (int) text->permissible_split > j) {
5465 			    if ((int) text->permissible_split - NumSize < j)
5466 				text->permissible_split = (unsigned) j;
5467 			    else
5468 				text->permissible_split -= (unsigned) NumSize;
5469 			}
5470 			k = j + NumSize;
5471 			while (k < (int) start->size)
5472 			    start->data[j++] = start->data[k++];
5473 			for (anc = a; anc; anc = anc->next) {
5474 			    if (anc->line_num == a->line_num &&
5475 				anc->line_pos >= NumSize) {
5476 				anc->line_pos = (short) (anc->line_pos - NumSize);
5477 			    }
5478 			}
5479 			start->size = (unsigned short) j;
5480 			start->data[j++] = '\0';
5481 			while (j < k)
5482 			    start->data[j++] = '\0';
5483 		    } else if (prev && prev->size > 1) {
5484 			k = (i + 1);
5485 			j = (prev->size - 1);
5486 			while ((j >= 0) && IsSpecialAttrChar(prev->data[j]))
5487 			    j--;
5488 			i = (j + 1);
5489 			while (j >= 0 &&
5490 			       isdigit(UCH(prev->data[j]))) {
5491 			    j--;
5492 			    NumSize++;
5493 			}
5494 			while (j < 0) {
5495 			    j++;
5496 			    NumSize--;
5497 			}
5498 			if (prev->data[j] == '[') {
5499 			    /*
5500 			     * The numbered bracket started on the
5501 			     * previous line, and part of it was
5502 			     * wrapped to this line.  -FM
5503 			     */
5504 			    while (i < (int) prev->size)
5505 				prev->data[j++] = prev->data[i++];
5506 			    prev->size = (unsigned short) j;
5507 			    prev->data[j] = '\0';
5508 			    while (j < i)
5509 				prev->data[j++] = '\0';
5510 			    if (start == last && text->permissible_split > 0) {
5511 				if ((int) text->permissible_split < k)
5512 				    text->permissible_split = 0;
5513 				else
5514 				    text->permissible_split -= (unsigned) k;
5515 			    }
5516 			    j = 0;
5517 			    i = k;
5518 			    while (k < (int) start->size)
5519 				start->data[j++] = start->data[k++];
5520 			    for (anc = a; anc; anc = anc->next) {
5521 				if (anc->line_num == a->line_num &&
5522 				    anc->line_pos >= i) {
5523 				    anc->line_pos = (short) (anc->line_pos - i);
5524 				}
5525 			    }
5526 			    start->size = (unsigned short) j;
5527 			    start->data[j++] = '\0';
5528 			    while (j < k)
5529 				start->data[j++] = '\0';
5530 			} else {
5531 			    /*
5532 			     * Shucks!  We didn't find the
5533 			     * numbered bracket.  -FM
5534 			     */
5535 			    a->show_anchor = YES;
5536 			}
5537 		    } else {
5538 			/*
5539 			 * Shucks!  We didn't find the
5540 			 * numbered bracket.  -FM
5541 			 */
5542 			a->show_anchor = YES;
5543 		    }
5544 		} else if (prev && prev->size > 2) {
5545 		    j = (prev->size - 1);
5546 		    while ((j >= 0) && IsSpecialAttrChar(prev->data[j]))
5547 			j--;
5548 		    if (j < 0)
5549 			j = 0;
5550 		    if ((j >= 2) &&
5551 			(prev->data[j] == ']' &&
5552 			 isdigit(UCH(prev->data[j - 1])))) {
5553 			j--;
5554 			NumSize++;
5555 			while (j >= 0 &&
5556 			       isdigit(UCH(prev->data[j]))) {
5557 			    j--;
5558 			    NumSize++;
5559 			}
5560 			while (j < 0) {
5561 			    j++;
5562 			    NumSize--;
5563 			}
5564 			if (prev->data[j] == '[') {
5565 			    /*
5566 			     * The numbered bracket is all on the
5567 			     * previous line, and the anchor content
5568 			     * was wrapped to the last line.  -FM
5569 			     */
5570 			    NumSize++;
5571 			    k = j + NumSize;
5572 			    while (k < (int) prev->size)
5573 				prev->data[j++] = prev->data[k++];
5574 			    prev->size = (unsigned short) j;
5575 			    prev->data[j++] = '\0';
5576 			    while (j < k)
5577 				prev->data[j++] = '\0';
5578 			} else {
5579 			    /*
5580 			     * Shucks!  We didn't find the
5581 			     * numbered bracket.  -FM
5582 			     */
5583 			    a->show_anchor = YES;
5584 			}
5585 		    } else {
5586 			/*
5587 			 * Shucks!  We didn't find the
5588 			 * numbered bracket.  -FM
5589 			 */
5590 			a->show_anchor = YES;
5591 		    }
5592 		} else {
5593 		    /*
5594 		     * Shucks!  We didn't find the
5595 		     * numbered bracket.  -FM
5596 		     */
5597 		    a->show_anchor = YES;
5598 		}
5599 	    }
5600 	} else {
5601 	    if (!number_links_on_left)
5602 		add_link_number(text, a, FALSE);
5603 	    /*
5604 	     * The anchor's content is not restricted to only
5605 	     * white and special characters, so we'll show it
5606 	     * as a link.  -FM
5607 	     */
5608 	    a->show_anchor = YES;
5609 	    if (BlankExtent) {
5610 		CTRACE((tfp,
5611 			"HText_endAnchor0: blanks (line,pos,ext,BlankExtent):(%d,%d,%d,%d)",
5612 			a->line_num, a->line_pos, a->extent,
5613 			BlankExtent));
5614 	    }
5615 	}
5616 	if (a->show_anchor == NO) {
5617 	    /*
5618 	     * The anchor's content is restricted to white
5619 	     * and special characters, so set its number
5620 	     * and extent to zero, decrement the visible
5621 	     * anchor number counter, and add this anchor
5622 	     * to the hidden links list.  -FM
5623 	     */
5624 	    a->extent = 0;
5625 	    if (text->hiddenlinkflag != HIDDENLINKS_MERGE) {
5626 		a->number = 0;
5627 		text->last_anchor_number--;
5628 		HText_AddHiddenLink(text, a);
5629 	    }
5630 	} else {
5631 	    /*
5632 	     * The anchor's content is not restricted to white
5633 	     * and special characters, so we'll display the
5634 	     * content, but shorten its extent by any trailing
5635 	     * blank lines we've detected.  -FM
5636 	     */
5637 	    a->extent = (short) (a->extent - ((BlankExtent < a->extent)
5638 					      ? BlankExtent
5639 					      : 0));
5640 	}
5641 	if (BlankExtent || a->extent <= 0 || a->number <= 0) {
5642 	    CTRACE((tfp,
5643 		    "->[%d](%d,%d,%d,%d)\n",
5644 		    a->number,
5645 		    a->line_num, a->line_pos, a->extent,
5646 		    BlankExtent));
5647 	}
5648     } else {
5649 	if (!really)		/* Just report whether it is empty */
5650 	    return FALSE;
5651 	/*
5652 	 * It's a named anchor without an HREF, so it
5653 	 * should be registered but not shown as a
5654 	 * link.  -FM
5655 	 */
5656 	a->show_anchor = NO;
5657 	a->extent = 0;
5658     }
5659     return FALSE;
5660 }
5661 
HText_endAnchor(HText * text,int number)5662 void HText_endAnchor(HText *text, int number)
5663 {
5664     HText_endAnchor0(text, number, 1);
5665 }
5666 
5667 /*
5668     This returns whether the given anchor has blank content. Shamelessly copied
5669     from HText_endAnchor. The values returned are meaningful only for "normal"
5670     links - like ones produced by <a href=".">foo</a>, no inputs, etc. - VH
5671 */
5672 #ifdef MARK_HIDDEN_LINKS
HText_isAnchorBlank(HText * text,int number)5673 BOOL HText_isAnchorBlank(HText *text, int number)
5674 {
5675     return HText_endAnchor0(text, number, 0);
5676 }
5677 #endif /* MARK_HIDDEN_LINKS */
5678 
HText_appendText(HText * text,const char * str)5679 void HText_appendText(HText *text, const char *str)
5680 {
5681     const char *p;
5682 
5683     if (str != NULL &&
5684 	text != NULL &&
5685 	text->halted != 3) {
5686 	for (p = str; *p; p++) {
5687 	    HText_appendCharacter(text, *p);
5688 	}
5689     }
5690 }
5691 
remove_special_attr_chars(char * buf)5692 static int remove_special_attr_chars(char *buf)
5693 {
5694     register char *cp;
5695     register int soft_newline_count = 0;
5696 
5697     for (cp = buf; *cp != '\0'; cp++) {
5698 	/*
5699 	 * Don't print underline chars.
5700 	 */
5701 	soft_newline_count += (*cp == LY_SOFT_NEWLINE);
5702 	if (!IsSpecialAttrChar(*cp)) {
5703 	    *buf++ = *cp;
5704 	}
5705     }
5706     *buf = '\0';
5707     return soft_newline_count;
5708 }
5709 
5710 /*
5711  *  This function trims blank lines from the end of the document, and
5712  *  then gets the hightext from the text by finding the char position,
5713  *  and brings the anchors in line with the text by adding the text
5714  *  offset to each of the anchors.
5715  */
HText_endAppend(HText * text)5716 void HText_endAppend(HText *text)
5717 {
5718     HTLine *line_ptr;
5719 
5720     if (!text)
5721 	return;
5722 
5723     CTRACE((tfp, "GridText: Entering HText_endAppend\n"));
5724 
5725     /*
5726      * Create a blank line at the bottom.
5727      */
5728     new_line(text);
5729 
5730     if (text->halted) {
5731 	if (text->stbl)
5732 	    HText_cancelStbl(text);
5733 	/*
5734 	 * If output was stopped because memory was low, and we made
5735 	 * it to the end of the document, reset those flags and hope
5736 	 * things are better now.  - kw
5737 	 */
5738 	LYFakeZap(NO);
5739 	text->halted = 0;
5740     } else if (text->stbl) {
5741 	/*
5742 	 * Could happen if TABLE end tag was missing.
5743 	 * Alternatively we could cancel in this case.  - kw
5744 	 */
5745 	HText_endStblTABLE(text);
5746     }
5747 
5748     /*
5749      * Get the first line.
5750      */
5751     if ((line_ptr = FirstHTLine(text)) != 0) {
5752 	/*
5753 	 * Remove the blank lines at the end of document.
5754 	 */
5755 	while (text->last_line->data[0] == '\0' && text->Lines > 2) {
5756 	    HTLine *next_to_the_last_line = text->last_line->prev;
5757 
5758 	    CTRACE((tfp, "GridText: Removing bottom blank line: `%s'\n",
5759 		    text->last_line->data));
5760 	    /*
5761 	     * line_ptr points to the first line.
5762 	     */
5763 	    next_to_the_last_line->next = line_ptr;
5764 	    line_ptr->prev = next_to_the_last_line;
5765 	    freeHTLine(text, text->last_line);
5766 	    text->last_line = next_to_the_last_line;
5767 	    text->Lines--;
5768 	    CTRACE((tfp, "GridText: New bottom line: `%s'\n",
5769 		    text->last_line->data));
5770 	}
5771     }
5772 
5773     /*
5774      * Fix up the anchor structure values and
5775      * create the hightext strings.  -FM
5776      */
5777     HText_trimHightext(text, TRUE, -1);
5778 }
5779 
5780 /*
5781  *  This function gets the hightext from the text by finding the char
5782  *  position, and brings the anchors in line with the text by adding the text
5783  *  offset to each of the anchors.
5784  *
5785  *  `Forms input' fields cannot be displayed properly without this function
5786  *  to be invoked (detected in display_partial mode).
5787  *
5788  *  If final is set, this is the final fixup; if not set, we don't have
5789  *  to do everything because there should be another call later.
5790  *
5791  *  BEFORE this function has treated a TextAnchor, its line_pos and
5792  *  extent fields are counting bytes in the HTLine data, including
5793  *  invisible special attribute chars and counting UTF-8 multibyte
5794  *  characters as multiple bytes.
5795  *
5796  *  AFTER the adjustment, the anchor line_pos (and hightext offset if
5797  *  applicable) fields indicate x positions in terms of displayed character
5798  *  cells, and the extent field apparently is unimportant; the anchor text has
5799  *  been copied to the hightext fields (which should have been NULL up to that
5800  *  point), with special attribute chars removed.
5801  *
5802  *  This needs to be done so that display_page finds the anchors in the
5803  *  form it expects when it sets the links[] elements.
5804  */
HText_trimHightext(HText * text,int final,int stop_before)5805 static void HText_trimHightext(HText *text,
5806 			       int final,
5807 			       int stop_before)
5808 {
5809     int cur_line, cur_shift;
5810     TextAnchor *anchor_ptr;
5811     TextAnchor *prev_a = NULL;
5812     HTLine *line_ptr;
5813     HTLine *line_ptr2;
5814     unsigned char ch;
5815     char *hilite_str;
5816     int hilite_len;
5817     int actual_len;
5818     int count_line;
5819 
5820     if (!text)
5821 	return;
5822 
5823     if (final) {
5824 	CTRACE((tfp, "GridText: Entering HText_trimHightext (final)\n"));
5825     } else {
5826 	if (stop_before < 0 || stop_before > text->Lines)
5827 	    stop_before = text->Lines;
5828 	CTRACE((tfp,
5829 		"GridText: Entering HText_trimHightext (partial: 0..%d/%d)\n",
5830 		stop_before, text->Lines));
5831     }
5832 
5833     /*
5834      * Get the first line.
5835      */
5836     line_ptr = FirstHTLine(text);
5837     cur_line = 0;
5838 
5839     /*
5840      * Fix up the anchor structure values and
5841      * create the hightext strings.  -FM
5842      */
5843     for (anchor_ptr = text->first_anchor;
5844 	 anchor_ptr != NULL;
5845 	 prev_a = anchor_ptr, anchor_ptr = anchor_ptr->next) {
5846 	int anchor_col;
5847 
5848       re_parse:
5849 	/*
5850 	 * Find the right line.
5851 	 */
5852 	for (; anchor_ptr->line_num > cur_line;
5853 	     line_ptr = line_ptr->next, cur_line++) {
5854 	    ;			/* null body */
5855 	}
5856 
5857 	if (!final) {
5858 	    /*
5859 	     * If this is not the final call, stop when we have reached
5860 	     * the last line, or the very end of preceding line.
5861 	     * The last line is probably still not finished.  - kw
5862 	     */
5863 	    if (cur_line >= stop_before)
5864 		break;
5865 	    if (anchor_ptr->line_num >= text->Lines - 1
5866 		&& anchor_ptr->line_pos >= (int) text->last_line->prev->size)
5867 		break;
5868 	    /*
5869 	     * Also skip this anchor if it looks like HText_endAnchor
5870 	     * is not yet done with it.  - kw
5871 	     */
5872 	    if (!anchor_ptr->extent && anchor_ptr->number &&
5873 		(anchor_ptr->link_type & HYPERTEXT_ANCHOR) &&
5874 		!anchor_ptr->show_anchor &&
5875 		anchor_ptr->number == text->last_anchor_number)
5876 		continue;
5877 	}
5878 
5879 	/*
5880 	 * If hightext has already been set, then we must have already
5881 	 * done the trimming & adjusting for this anchor, so avoid
5882 	 * doing it a second time.  - kw
5883 	 */
5884 	if (LYGetHiTextStr(anchor_ptr, 0) != NULL)
5885 	    continue;
5886 
5887 	if (anchor_ptr->line_pos > (int) line_ptr->size) {
5888 	    anchor_ptr->line_pos = (short) line_ptr->size;
5889 	}
5890 	if (anchor_ptr->line_pos < 0) {
5891 	    anchor_ptr->line_pos = 0;
5892 	    anchor_ptr->line_num = cur_line;
5893 	}
5894 	CTRACE((tfp,
5895 		"GridText: Anchor found on line:%d col:%d [%05d:%d] ext:%d\n",
5896 		cur_line,
5897 		anchor_ptr->line_pos,
5898 		anchor_ptr->sgml_offset,
5899 		anchor_ptr->number,
5900 		anchor_ptr->extent));
5901 
5902 	cur_shift = 0;
5903 	/*
5904 	 * Strip off any spaces or SpecialAttrChars at the beginning,
5905 	 * if they exist, but only on HYPERTEXT_ANCHORS.
5906 	 */
5907 	if (anchor_ptr->link_type & HYPERTEXT_ANCHOR) {
5908 	    ch = UCH(line_ptr->data[anchor_ptr->line_pos]);
5909 	    while (isspace(ch) ||
5910 		   IsSpecialAttrChar(ch)) {
5911 		anchor_ptr->line_pos++;
5912 		anchor_ptr->extent--;
5913 		cur_shift++;
5914 		ch = UCH(line_ptr->data[anchor_ptr->line_pos]);
5915 	    }
5916 	}
5917 	if (anchor_ptr->extent < 0) {
5918 	    anchor_ptr->extent = 0;
5919 	}
5920 
5921 	CTRACE((tfp, "anchor text: '%s'\n", line_ptr->data));
5922 	/*
5923 	 * If the link begins with an end of line and we have more lines, then
5924 	 * start the highlighting on the next line.  -FM.
5925 	 *
5926 	 * But if an empty anchor is at the end of line and empty, keep it
5927 	 * where it is, unless the previous anchor in the list (if any) already
5928 	 * starts later.  - kw
5929 	 */
5930 	if ((unsigned) anchor_ptr->line_pos >= strlen(line_ptr->data)) {
5931 	    if (cur_line < text->Lines &&
5932 		(anchor_ptr->extent ||
5933 		 anchor_ptr->line_pos != (int) line_ptr->size ||
5934 		 (prev_a &&	/* How could this happen? */
5935 		  (prev_a->line_num > anchor_ptr->line_num)))) {
5936 		anchor_ptr->line_num++;
5937 		anchor_ptr->line_pos = 0;
5938 		CTRACE((tfp, "found anchor at end of line\n"));
5939 		goto re_parse;
5940 	    } else {
5941 		CTRACE((tfp, "found anchor at end of line, leaving it there\n"));
5942 	    }
5943 	}
5944 
5945 	/*
5946 	 * Copy the link name into the data structure.
5947 	 */
5948 	if (anchor_ptr->extent > 0
5949 	    && anchor_ptr->line_pos >= 0) {
5950 	    int size = (int) line_ptr->size - anchor_ptr->line_pos;
5951 
5952 	    if (size > anchor_ptr->extent)
5953 		size = anchor_ptr->extent;
5954 	    LYClearHiText(anchor_ptr);
5955 	    LYSetHiText(anchor_ptr,
5956 			&line_ptr->data[anchor_ptr->line_pos],
5957 			(unsigned) size);
5958 	} else {
5959 	    LYClearHiText(anchor_ptr);
5960 	    LYSetHiText(anchor_ptr, "", 0);
5961 	}
5962 
5963 	/*
5964 	 * If the anchor extends over more than one line, copy that into the
5965 	 * data structure.
5966 	 */
5967 	hilite_str = LYGetHiTextStr(anchor_ptr, 0);
5968 	hilite_len = (int) strlen(hilite_str);
5969 	actual_len = anchor_ptr->extent;
5970 
5971 	line_ptr2 = line_ptr;
5972 	assert(line_ptr2 != 0);
5973 
5974 	count_line = cur_line;
5975 	while (actual_len > hilite_len) {
5976 	    HTLine *old_line_ptr2 = line_ptr2;
5977 
5978 	    count_line++;
5979 	    if ((line_ptr2 = line_ptr2->next) == NULL)
5980 		break;
5981 
5982 	    if (!final
5983 		&& count_line >= stop_before) {
5984 		LYClearHiText(anchor_ptr);
5985 		break;
5986 	    } else if (old_line_ptr2 == text->last_line) {
5987 		break;
5988 	    }
5989 
5990 	    /*
5991 	     * Double check that we have a line pointer, and if so, copy into
5992 	     * highlight text.
5993 	     */
5994 	    if (line_ptr2) {
5995 		char *hi_string = NULL;
5996 		int hi_offset = line_ptr2->offset;
5997 
5998 		StrnAllocCopy(hi_string,
5999 			      line_ptr2->data,
6000 			      (actual_len - hilite_len));
6001 		actual_len -= (int) strlen(hi_string);
6002 		/*handle LY_SOFT_NEWLINEs -VH */
6003 		hi_offset += remove_special_attr_chars(hi_string);
6004 
6005 		if (anchor_ptr->link_type & HYPERTEXT_ANCHOR) {
6006 		    LYTrimTrailing(hi_string);
6007 		}
6008 		if (non_empty(hi_string)) {
6009 		    LYAddHiText(anchor_ptr, hi_string, hi_offset);
6010 		} else if (actual_len > hilite_len) {
6011 		    LYAddHiText(anchor_ptr, "", hi_offset);
6012 		}
6013 		FREE(hi_string);
6014 	    }
6015 	}
6016 
6017 	if (!final
6018 	    && count_line >= stop_before) {
6019 	    break;
6020 	}
6021 
6022 	hilite_str = LYGetHiTextStr(anchor_ptr, 0);
6023 	remove_special_attr_chars(hilite_str);
6024 	if (anchor_ptr->link_type & HYPERTEXT_ANCHOR) {
6025 	    LYTrimTrailing(hilite_str);
6026 	}
6027 
6028 	/*
6029 	 * Save the offset (bytes) of the anchor in the line's data.
6030 	 */
6031 	anchor_col = anchor_ptr->line_pos;
6032 
6033 	/*
6034 	 * Subtract any formatting characters from the x position of the link.
6035 	 */
6036 #ifdef WIDEC_CURSES
6037 	if (anchor_ptr->line_pos > 0) {
6038 	    /*
6039 	     * LYstrExtent filters out the formatting characters, so we do not
6040 	     * have to count them here, except for soft newlines.
6041 	     */
6042 	    anchor_ptr->line_pos = (short) LYstrExtent2(line_ptr->data, anchor_col);
6043 	    if (line_ptr->data[0] == LY_SOFT_NEWLINE)
6044 		anchor_ptr->line_pos = (short) (anchor_ptr->line_pos + 1);
6045 	}
6046 #else /* 8-bit curses, etc.  */
6047 	if (anchor_ptr->line_pos > 0) {
6048 	    register int offset = 0, i = 0;
6049 	    int have_soft_newline_in_1st_line = 0;
6050 
6051 	    for (; i < anchor_col; i++) {
6052 		if (IS_UTF_EXTRA(line_ptr->data[i]) ||
6053 		    IsSpecialAttrChar(line_ptr->data[i])) {
6054 		    offset++;
6055 		    have_soft_newline_in_1st_line += (line_ptr->data[i] == LY_SOFT_NEWLINE);
6056 		}
6057 	    }
6058 	    anchor_ptr->line_pos = (short) (anchor_ptr->line_pos - offset);
6059 	    /*handle LY_SOFT_NEWLINEs -VH */
6060 	    anchor_ptr->line_pos = (short) (anchor_ptr->line_pos + have_soft_newline_in_1st_line);
6061 	}
6062 #endif /* WIDEC_CURSES */
6063 
6064 	/*
6065 	 * Set the line number.
6066 	 */
6067 	anchor_ptr->line_pos = (short) (anchor_ptr->line_pos + line_ptr->offset);
6068 	anchor_ptr->line_num = cur_line;
6069 
6070 	CTRACE((tfp, "GridText:     add link on line %d col %d [%d] %s\n",
6071 		cur_line, anchor_ptr->line_pos,
6072 		anchor_ptr->number, "in HText_trimHightext"));
6073     }
6074 }
6075 
6076 /*	Return the anchor associated with this node
6077 */
HText_nodeAnchor(HText * text)6078 HTParentAnchor *HText_nodeAnchor(HText *text)
6079 {
6080     return text->node_anchor;
6081 }
6082 
6083 /*				GridText specials
6084  *				=================
6085  */
6086 
6087 /*
6088  * HText_childNextNumber() returns the anchor with index [number],
6089  * using a pointer from the previous number (=optimization) or NULL.
6090  */
HText_childNextNumber(int number,void ** prev)6091 HTChildAnchor *HText_childNextNumber(int number, void **prev)
6092 {
6093     /* Sorry, TextAnchor is not declared outside this file, use a cast. */
6094     TextAnchor *a = (TextAnchor *) *prev;
6095 
6096     if (!HTMainText || number <= 0)
6097 	return (HTChildAnchor *) 0;	/* Fail */
6098     if (number == 1 || !a)
6099 	a = HTMainText->first_anchor;
6100 
6101     /* a strange thing:  positive a->numbers are sorted,
6102      * and between them several a->numbers may be 0 -- skip them
6103      */
6104     for (; a && a->number != number; a = a->next) ;
6105 
6106     if (!a)
6107 	return (HTChildAnchor *) 0;	/* Fail */
6108     *prev = (void *) a;
6109     return a->anchor;
6110 }
6111 
6112 /*
6113  * For the -unique-urls option, find the anchor-number of the first occurrence
6114  * of a given address.
6115  */
HText_findAnchorNumber(void * avoid)6116 int HText_findAnchorNumber(void *avoid)
6117 {
6118     TextAnchor *a = (TextAnchor *) avoid;
6119 
6120     if (a->number > 0 && a->show_number == 0)
6121 	compute_show_number(a);
6122 
6123     return a->show_number;
6124 }
6125 
inputFieldDesc(FormInfo * input)6126 static const char *inputFieldDesc(FormInfo * input)
6127 {
6128     const char *result = 0;
6129 
6130     switch (input->type) {
6131     case F_TEXT_TYPE:
6132 	result = gettext("text entry field");
6133 	break;
6134     case F_PASSWORD_TYPE:
6135 	result = gettext("password entry field");
6136 	break;
6137     case F_CHECKBOX_TYPE:
6138 	result = gettext("checkbox");
6139 	break;
6140     case F_RADIO_TYPE:
6141 	result = gettext("radio button");
6142 	break;
6143     case F_SUBMIT_TYPE:
6144 	result = gettext("submit button");
6145 	break;
6146     case F_RESET_TYPE:
6147 	result = gettext("reset button");
6148 	break;
6149     case F_BUTTON_TYPE:
6150 	result = gettext("script button");
6151 	break;
6152     case F_OPTION_LIST_TYPE:
6153 	result = gettext("popup menu");
6154 	break;
6155     case F_HIDDEN_TYPE:
6156 	result = gettext("hidden form field");
6157 	break;
6158     case F_TEXTAREA_TYPE:
6159 	result = gettext("text entry area");
6160 	break;
6161     case F_RANGE_TYPE:
6162 	result = gettext("range entry field");
6163 	break;
6164     case F_FILE_TYPE:
6165 	result = gettext("file entry field");
6166 	break;
6167     case F_TEXT_SUBMIT_TYPE:
6168 	result = gettext("text-submit field");
6169 	break;
6170     case F_IMAGE_SUBMIT_TYPE:
6171 	result = gettext("image-submit button");
6172 	break;
6173     case F_KEYGEN_TYPE:
6174 	result = gettext("keygen field");
6175 	break;
6176     default:
6177 	result = gettext("unknown form field");
6178 	break;
6179     }
6180     return result;
6181 }
6182 
6183 /*
6184  * HText_FormDescNumber() returns a description of the form field
6185  * with index N.  The index corresponds to the [number] we print
6186  * for the field.  -FM & LE
6187  */
HText_FormDescNumber(int number,const char ** desc)6188 void HText_FormDescNumber(int number,
6189 			  const char **desc)
6190 {
6191     TextAnchor *a;
6192 
6193     if (!desc)
6194 	return;
6195 
6196     if (!(HTMainText && HTMainText->first_anchor) || number <= 0) {
6197 	*desc = gettext("unknown field or link");
6198 	return;
6199     }
6200 
6201     for (a = HTMainText->first_anchor; a; a = a->next) {
6202 	if (a->number == number) {
6203 	    if (!(a->input_field && a->input_field->type)) {
6204 		*desc = gettext("unknown field or link");
6205 		return;
6206 	    }
6207 	    break;
6208 	}
6209     }
6210 
6211     if (a != NULL)
6212 	*desc = inputFieldDesc(a->input_field);
6213 }
6214 
6215 /* HTGetRelLinkNum returns the anchor number to which follow_link_number()
6216  * is to jump (input was 123+ or 123- or 123+g or 123-g or 123 or 123g)
6217  * num is the number specified
6218  * rel is 0 or '+' or '-'
6219  * cur is the current link
6220  */
HTGetRelLinkNum(int num,int rel,int cur)6221 int HTGetRelLinkNum(int num,
6222 		    int rel,
6223 		    int cur)
6224 {
6225     TextAnchor *a, *l = 0;
6226     int scrtop = HText_getTopOfScreen();	/*XXX +1? */
6227     int curline = links[cur].anchor_line_num;
6228     int curpos = links[cur].lx;
6229     int on_screen = (curline >= scrtop && curline < (scrtop + display_lines));
6230 
6231     /* curanchor may or may not be the "current link", depending whether it's
6232      * on the current screen
6233      */
6234     int curanchor = links[cur].anchor_number;
6235 
6236     CTRACE((tfp, "HTGetRelLinkNum(%d,%d,%d) -- HTMainText=%p\n",
6237 	    num, rel, cur, (void *) HTMainText));
6238     CTRACE((tfp,
6239 	    "  scrtop=%d, curline=%d, curanchor=%d, display_lines=%d, %s\n",
6240 	    scrtop, curline, curanchor, display_lines,
6241 	    on_screen ? "on_screen" : "0"));
6242     if (!HTMainText)
6243 	return 0;
6244     if (rel == 0)
6245 	return num;
6246 
6247     /* if cur numbered link is on current page, use it */
6248     if (on_screen && curanchor) {
6249 	CTRACE((tfp, "curanchor=%d at line %d on screen\n", curanchor, curline));
6250 	if (rel == '+')
6251 	    return curanchor + num;
6252 	else if (rel == '-')
6253 	    return curanchor - num;
6254 	else
6255 	    return num;		/* shouldn't happen */
6256     }
6257 
6258     /* no current link on screen, or current link is not numbered
6259      * -- find previous closest numbered link
6260      */
6261     for (a = HTMainText->first_anchor; a; a = a->next) {
6262 	CTRACE((tfp, "  a->line_num=%d, a->number=%d\n", a->line_num, a->number));
6263 	if (a->line_num >= scrtop)
6264 	    break;
6265 	if (a->number == 0)
6266 	    continue;
6267 	l = a;
6268 	curanchor = l->number;
6269     }
6270     CTRACE((tfp, "  a=%p, l=%p, curanchor=%d\n", (void *) a, (void *) l, curanchor));
6271     if (on_screen) {		/* on screen but not a numbered link */
6272 	for (; a; a = a->next) {
6273 	    if (a->number) {
6274 		l = a;
6275 		curanchor = l->number;
6276 	    }
6277 	    if (curline == a->line_num && curpos == a->line_pos)
6278 		break;
6279 	}
6280     }
6281     if (rel == '+') {
6282 	return curanchor + num;
6283     } else if (rel == '-') {
6284 	if (l)
6285 	    return curanchor + 1 - num;
6286 	else {
6287 	    for (; a && a->number == 0; a = a->next) ;
6288 	    return a ? a->number - num : 0;
6289 	}
6290     } else
6291 	return num;		/* shouldn't happen */
6292 }
6293 
6294 /*
6295  * HTGetLinkInfo returns some link info based on the number.
6296  *
6297  * If want_go is not 0, caller requests to know a line number for
6298  * the link indicated by number.  It will be returned in *go_line, and
6299  * *linknum will be set to an index into the links[] array, to use after
6300  * the line in *go_line has been made the new top screen line.
6301  * *hightext and *lname are unchanged.  - KW
6302  *
6303  * If want_go is 0 and the number doesn't represent an input field, info
6304  * on the link indicated by number is deposited in *hightext and *lname.
6305  */
HTGetLinkInfo(int number,int want_go,int * go_line,int * linknum,char ** hightext,char ** lname)6306 int HTGetLinkInfo(int number,
6307 		  int want_go,
6308 		  int *go_line,
6309 		  int *linknum,
6310 		  char **hightext,
6311 		  char **lname)
6312 {
6313     TextAnchor *a;
6314     HTAnchor *link_dest;
6315 
6316     HTAnchor *link_dest_intl = NULL;
6317     int anchors_this_line = 0, anchors_this_screen = 0;
6318     int prev_anchor_line = -1, prev_prev_anchor_line = -1;
6319 
6320     if (!HTMainText)
6321 	return (NO);
6322 
6323     for (a = HTMainText->first_anchor; a; a = a->next) {
6324 	/*
6325 	 * Count anchors, first on current line if there is more
6326 	 * than one.  We have to count all links, including form
6327 	 * field anchors and others with a->number == 0, because
6328 	 * they are or will be included in the links[] array.
6329 	 * The exceptions are hidden form fields and anchors with
6330 	 * show_anchor not set, because they won't appear in links[]
6331 	 * and don't count towards nlinks.  - KW
6332 	 */
6333 	if ((a->show_anchor) &&
6334 	    !(a->link_type == INPUT_ANCHOR
6335 	      && a->input_field->type == F_HIDDEN_TYPE)) {
6336 	    if (a->line_num == prev_anchor_line) {
6337 		anchors_this_line++;
6338 	    } else {
6339 		/*
6340 		 * This anchor is on a different line than the previous one.
6341 		 * Remember which was the line number of the previous anchor,
6342 		 * for use in screen positioning later.  - KW
6343 		 */
6344 		anchors_this_line = 1;
6345 		prev_prev_anchor_line = prev_anchor_line;
6346 		prev_anchor_line = a->line_num;
6347 	    }
6348 	    if (a->line_num >= HTMainText->top_of_screen) {
6349 		/*
6350 		 * Count all anchors starting with the top line of the
6351 		 * currently displayed screen.  Just keep on counting
6352 		 * beyond this screen's bottom line - we'll know whether
6353 		 * a found anchor is below the current screen by a check
6354 		 * against nlinks later.  - KW
6355 		 */
6356 		anchors_this_screen++;
6357 	    }
6358 	}
6359 
6360 	if (a->number == number) {
6361 	    /*
6362 	     * We found it.  Now process it, depending
6363 	     * on what kind of info is requested.  - KW
6364 	     */
6365 	    if (want_go || a->link_type == INPUT_ANCHOR) {
6366 		if (a->show_anchor == NO) {
6367 		    /*
6368 		     * The number requested has been assigned to an anchor
6369 		     * without any selectable text, so we cannot position
6370 		     * on it.  The code for suppressing such anchors in
6371 		     * HText_endAnchor() may not have applied, or it may
6372 		     * have failed.  Return a failure indication so that
6373 		     * the user will notice that something is wrong,
6374 		     * instead of positioning on some other anchor which
6375 		     * might result in inadvertent activation.  - KW
6376 		     */
6377 		    return (NO);
6378 		}
6379 		if (anchors_this_screen > 0 &&
6380 		    anchors_this_screen <= nlinks &&
6381 		    a->line_num >= HTMainText->top_of_screen &&
6382 		    a->line_num < HTMainText->top_of_screen + (display_lines)) {
6383 		    /*
6384 		     * If the requested anchor is within the current screen,
6385 		     * just set *go_line so that the screen window won't move
6386 		     * (keep it as it is), and set *linknum to the index of
6387 		     * this link in the current links[] array.  - KW
6388 		     */
6389 		    *go_line = HTMainText->top_of_screen;
6390 		    if (linknum)
6391 			*linknum = anchors_this_screen - 1;
6392 		} else {
6393 		    /*
6394 		     * if the requested anchor is not within the currently
6395 		     * displayed screen, set *go_line such that the top line
6396 		     * will be either
6397 		     *  (1) the line immediately below the previous
6398 		     *      anchor, or
6399 		     *  (2) about one third of a screenful above the line
6400 		     *      with the target, or
6401 		     *  (3) the first line of the document -
6402 		     * whichever comes last.  In all cases the line with our
6403 		     * target will end up being the first line with any links
6404 		     * on the new screen, so that we can use the
6405 		     * anchors_this_line counter to point to the anchor in
6406 		     * the new links[] array.  - kw
6407 		     */
6408 		    int max_offset = SEARCH_GOAL_LINE - 1;
6409 
6410 		    if (max_offset < 0)
6411 			max_offset = 0;
6412 		    else if (max_offset >= display_lines)
6413 			max_offset = display_lines - 1;
6414 		    *go_line = prev_anchor_line - max_offset;
6415 		    if (*go_line <= prev_prev_anchor_line)
6416 			*go_line = prev_prev_anchor_line + 1;
6417 		    if (*go_line < 0)
6418 			*go_line = 0;
6419 		    if (linknum)
6420 			*linknum = anchors_this_line - 1;
6421 		}
6422 		return (LINK_LINE_FOUND);
6423 	    } else {
6424 		*hightext = LYGetHiTextStr(a, 0);
6425 		link_dest = HTAnchor_followLink(a->anchor);
6426 		{
6427 		    char *cp_freeme = NULL;
6428 
6429 		    if (traversal) {
6430 			cp_freeme = stub_HTAnchor_address(link_dest);
6431 		    } else if (track_internal_links) {
6432 			if (a->link_type == INTERNAL_LINK_ANCHOR) {
6433 			    link_dest_intl =
6434 				HTAnchor_followTypedLink(a->anchor, HTInternalLink);
6435 			    if (link_dest_intl && link_dest_intl != link_dest) {
6436 
6437 				CTRACE((tfp,
6438 					"HTGetLinkInfo: unexpected typed link to %s!\n",
6439 					link_dest_intl->parent->address));
6440 				link_dest_intl = NULL;
6441 			    }
6442 			}
6443 			if (link_dest_intl) {
6444 			    char *cp2 = HTAnchor_address(link_dest_intl);
6445 
6446 			    FREE(*lname);
6447 			    *lname = cp2;
6448 			    return (WWW_INTERN_LINK_TYPE);
6449 			} else {
6450 			    cp_freeme = HTAnchor_address(link_dest);
6451 			}
6452 		    } else {
6453 			cp_freeme = HTAnchor_address(link_dest);
6454 		    }
6455 		    StrAllocCopy(*lname, cp_freeme);
6456 		    FREE(cp_freeme);
6457 		}
6458 		return (WWW_LINK_TYPE);
6459 	    }
6460 	}
6461     }
6462     return (NO);
6463 }
6464 
same_anchor_or_field(int numberA,FormInfo * formA,int numberB,FormInfo * formB,int ta_same)6465 static BOOLEAN same_anchor_or_field(int numberA,
6466 				    FormInfo * formA,
6467 				    int numberB,
6468 				    FormInfo * formB,
6469 				    int ta_same)
6470 {
6471     if (numberA > 0 || numberB > 0) {
6472 	if (numberA == numberB)
6473 	    return (YES);
6474 	else if (!ta_same)
6475 	    return (NO);
6476     }
6477     if (formA || formB) {
6478 	if (formA == formB) {
6479 	    return (YES);
6480 	} else if (!ta_same) {
6481 	    return (NO);
6482 	} else if (!(formA && formB)) {
6483 	    return (NO);
6484 	}
6485     } else {
6486 	return (NO);
6487     }
6488     if (formA->type != formB->type ||
6489 	formA->type != F_TEXTAREA_TYPE ||
6490 	formB->type != F_TEXTAREA_TYPE) {
6491 	return (NO);
6492     }
6493     if (formA->number != formB->number)
6494 	return (NO);
6495     if (!formA->name || !formB->name)
6496 	return (YES);
6497     return (BOOL) (strcmp(formA->name, formB->name) == 0);
6498 }
6499 
6500 #define same_anchor_as_link(i,a,ta_same) (BOOL) (i >= 0 && a && \
6501 		same_anchor_or_field(links[i].anchor_number,\
6502 		(links[i].type == WWW_FORM_LINK_TYPE) ? links[i].l_form : NULL,\
6503 		a->number,\
6504 		(a->link_type == INPUT_ANCHOR) ? a->input_field : NULL,\
6505 		ta_same))
6506 #define same_anchors(a1,a2,ta_same) (BOOL) (a1 && a2 && \
6507 		same_anchor_or_field(a1->number,\
6508 		(a1->link_type == INPUT_ANCHOR) ? a1->input_field : NULL,\
6509 		a2->number,\
6510 		(a2->link_type == INPUT_ANCHOR) ? a2->input_field : NULL,\
6511 		ta_same))
6512 
6513 /*
6514  * Are there more textarea lines belonging to the same textarea before
6515  * (direction < 0) or after (direction > 0) the current one?
6516  * On entry, curlink must be the index in links[] of a textarea field.  - kw
6517  */
HText_TAHasMoreLines(int curlink,int direction)6518 BOOL HText_TAHasMoreLines(int curlink,
6519 			  int direction)
6520 {
6521     TextAnchor *a;
6522     TextAnchor *prev_a = NULL;
6523 
6524     if (!HTMainText)
6525 	return (NO);
6526     if (direction < 0) {
6527 	for (a = HTMainText->first_anchor; a; prev_a = a, a = a->next) {
6528 	    if (a->link_type == INPUT_ANCHOR &&
6529 		links[curlink].l_form == a->input_field) {
6530 		return same_anchors(a, prev_a, TRUE);
6531 	    }
6532 	    if (links[curlink].anchor_number &&
6533 		a->number >= links[curlink].anchor_number)
6534 		break;
6535 	}
6536 	return NO;
6537     } else {
6538 	for (a = HTMainText->first_anchor; a; a = a->next) {
6539 	    if (a->link_type == INPUT_ANCHOR &&
6540 		links[curlink].l_form == a->input_field) {
6541 		return same_anchors(a, a->next, TRUE);
6542 	    }
6543 	    if (links[curlink].anchor_number &&
6544 		a->number >= links[curlink].anchor_number)
6545 		break;
6546 	}
6547 	return NO;
6548     }
6549 }
6550 
6551 /*
6552  * HTGetLinkOrFieldStart - moving to previous or next link or form field.
6553  *
6554  * On input,
6555  *	curlink: current link, as index in links[] array (-1 if none)
6556  *	direction: whether to move up or down (or stay where we are)
6557  *	ta_skip: if FALSE, input fields belonging to the same textarea are
6558  *		 are treated as different fields, as usual;
6559  *		 if TRUE, fields of the same textarea are treated as a
6560  *		 group for skipping.
6561  * The caller wants information for positioning on the new link to be
6562  * deposited in *go_line and (if linknum is not NULL) *linknum.
6563  *
6564  * On failure (no more links in the requested direction) returns NO
6565  * and doesn't change *go_line or *linknum.  Otherwise, LINK_DO_ARROWUP
6566  * may be returned, and *go_line and *linknum not changed, to indicate that
6567  * the caller should use a normal PREV_LINK or PREV_PAGE mechanism.
6568  * Otherwise:
6569  * The number (0-based counting) for the new top screen line will be returned
6570  * in *go_line, and *linknum will be set to an index into the links[] array,
6571  * to use after the line in *go_line has been made the new top screen
6572  * line.  - kw
6573  */
HTGetLinkOrFieldStart(int curlink,int * go_line,int * linknum,int direction,int ta_skip)6574 int HTGetLinkOrFieldStart(int curlink,
6575 			  int *go_line,
6576 			  int *linknum,
6577 			  int direction,
6578 			  int ta_skip)
6579 {
6580     TextAnchor *a;
6581     int anchors_this_line = 0;
6582     int prev_anchor_line = -1, prev_prev_anchor_line = -1;
6583 
6584     struct agroup {
6585 	TextAnchor *anc;
6586 	int prev_anchor_line;
6587 	int anchors_this_line;
6588 	int anchors_this_group;
6589     } previous, current;
6590     struct agroup *group_to_go = NULL;
6591 
6592     if (!HTMainText)
6593 	return (NO);
6594 
6595     previous.anc = current.anc = NULL;
6596     previous.prev_anchor_line = current.prev_anchor_line = -1;
6597     previous.anchors_this_line = current.anchors_this_line = 0;
6598     previous.anchors_this_group = current.anchors_this_group = 0;
6599 
6600     for (a = HTMainText->first_anchor; a; a = a->next) {
6601 	/*
6602 	 * Count anchors, first on current line if there is more
6603 	 * than one.  We have to count all links, including form
6604 	 * field anchors and others with a->number == 0, because
6605 	 * they are or will be included in the links[] array.
6606 	 * The exceptions are hidden form fields and anchors with
6607 	 * show_anchor not set, because they won't appear in links[]
6608 	 * and don't count towards nlinks.  - KW
6609 	 */
6610 	if ((a->show_anchor) &&
6611 	    !(a->link_type == INPUT_ANCHOR
6612 	      && a->input_field->type == F_HIDDEN_TYPE)) {
6613 	    if (a->line_num == prev_anchor_line) {
6614 		anchors_this_line++;
6615 	    } else {
6616 		/*
6617 		 * This anchor is on a different line than the previous one.
6618 		 * Remember which was the line number of the previous anchor,
6619 		 * for use in screen positioning later.  - KW
6620 		 */
6621 		anchors_this_line = 1;
6622 		prev_prev_anchor_line = prev_anchor_line;
6623 		prev_anchor_line = a->line_num;
6624 	    }
6625 
6626 	    if (!same_anchors(current.anc, a, ta_skip)) {
6627 		previous.anc = current.anc;
6628 		previous.prev_anchor_line = current.prev_anchor_line;
6629 		previous.anchors_this_line = current.anchors_this_line;
6630 		previous.anchors_this_group = current.anchors_this_group;
6631 		current.anc = a;
6632 		current.prev_anchor_line = prev_prev_anchor_line;
6633 		current.anchors_this_line = anchors_this_line;
6634 		current.anchors_this_group = 1;
6635 	    } else {
6636 		current.anchors_this_group++;
6637 	    }
6638 	    if (curlink >= 0) {
6639 		if (same_anchor_as_link(curlink, a, ta_skip)) {
6640 		    if (direction == -1) {
6641 			group_to_go = &previous;
6642 			break;
6643 		    } else if (direction == 0) {
6644 			group_to_go = &current;
6645 			break;
6646 		    }
6647 		} else if (direction > 0 &&
6648 			   same_anchor_as_link(curlink, previous.anc, ta_skip)) {
6649 		    group_to_go = &current;
6650 		    break;
6651 		}
6652 	    } else {
6653 		if (a->line_num >= HTMainText->top_of_screen) {
6654 		    if (direction < 0) {
6655 			group_to_go = &previous;
6656 			break;
6657 		    } else if (direction == 0) {
6658 			if (previous.anc) {
6659 			    group_to_go = &previous;
6660 			    break;
6661 			} else {
6662 			    group_to_go = &current;
6663 			    break;
6664 			}
6665 		    } else {
6666 			group_to_go = &current;
6667 			break;
6668 		    }
6669 		}
6670 	    }
6671 	}
6672     }
6673     if (!group_to_go && curlink < 0 && direction <= 0) {
6674 	group_to_go = &current;
6675     }
6676     if (group_to_go) {
6677 	a = group_to_go->anc;
6678 	if (a) {
6679 	    int max_offset;
6680 
6681 	    /*
6682 	     * We know where to go; most of the stuff below is just
6683 	     * tweaks to try to position the new screen in a specific
6684 	     * way.
6685 	     *
6686 	     * In some cases going to a previous link can be done
6687 	     * via the normal LYK_PREV_LINK action, which may give
6688 	     * better positioning of the new screen.  - kw
6689 	     */
6690 	    if (a->line_num < HTMainText->top_of_screen &&
6691 		a->line_num >= HTMainText->top_of_screen - (display_lines)) {
6692 		if ((curlink < 0 &&
6693 		     group_to_go->anchors_this_group == 1) ||
6694 		    (direction < 0 &&
6695 		     group_to_go != &current &&
6696 		     current.anc &&
6697 		     current.anc->line_num >= HTMainText->top_of_screen &&
6698 		     group_to_go->anchors_this_group == 1) ||
6699 		    (a->next &&
6700 		     a->next->line_num >= HTMainText->top_of_screen)) {
6701 		    return (LINK_DO_ARROWUP);
6702 		}
6703 	    }
6704 	    /*
6705 	     * The fundamental limitation of the current anchors_this_line
6706 	     * counter method is that we only can set *linknum to the right
6707 	     * index into the future links[] array if the line with our link
6708 	     * ends up being the first line with any links (that count) on
6709 	     * the new screen.  Subject to that restriction we still have
6710 	     * some vertical liberty (sometimes), and try to make the best
6711 	     * of it.  It may be a question of taste though.  - kw
6712 	     */
6713 	    if (a->line_num <= (display_lines)) {
6714 		max_offset = 0;
6715 	    } else if (a->line_num < HTMainText->top_of_screen) {
6716 		int screensback =
6717 		(HTMainText->top_of_screen - a->line_num + (display_lines) - 1)
6718 		/ (display_lines);
6719 
6720 		max_offset = a->line_num - (HTMainText->top_of_screen -
6721 					    screensback * (display_lines));
6722 	    } else if (HTMainText->Lines - a->line_num <= (display_lines)) {
6723 		max_offset = a->line_num - (HTMainText->Lines + 1
6724 					    - (display_lines));
6725 	    } else if (a->line_num >=
6726 		       HTMainText->top_of_screen + (display_lines)) {
6727 		int screensahead =
6728 		(a->line_num - HTMainText->top_of_screen) / (display_lines);
6729 
6730 		max_offset = a->line_num - HTMainText->top_of_screen -
6731 		    screensahead * (display_lines);
6732 	    } else {
6733 		max_offset = SEARCH_GOAL_LINE - 1;
6734 	    }
6735 
6736 	    /* Stuff below should remain unchanged if line positioning
6737 	       is tweaked. - kw */
6738 	    if (max_offset < 0)
6739 		max_offset = 0;
6740 	    else if (max_offset >= display_lines)
6741 		max_offset = display_lines - 1;
6742 	    *go_line = a->line_num - max_offset;
6743 	    if (*go_line <= group_to_go->prev_anchor_line)
6744 		*go_line = group_to_go->prev_anchor_line + 1;
6745 
6746 	    if (*go_line < 0)
6747 		*go_line = 0;
6748 	    if (linknum)
6749 		*linknum = group_to_go->anchors_this_line - 1;
6750 	    return (LINK_LINE_FOUND);
6751 	}
6752     }
6753     return (NO);
6754 }
6755 
6756 /*
6757  * This function finds the line indicated by line_num in the
6758  * HText structure indicated by text, and searches that line
6759  * for the first hit with the string indicated by target.  If
6760  * there is no hit, FALSE is returned.  If there is a hit, then
6761  * a copy of the line starting at that first hit is loaded into
6762  * *data with all IsSpecial characters stripped, its offset and
6763  * the printable target length (without IsSpecial, or extra CJK
6764  * or utf8 characters) are loaded into *offset and *tLen, and
6765  * TRUE is returned.  -FM
6766  */
HText_getFirstTargetInLine(HText * text,int line_num,int utf_flag,int * offset,int * tLen,char ** data,const char * target)6767 BOOL HText_getFirstTargetInLine(HText *text, int line_num,
6768 				int utf_flag,
6769 				int *offset,
6770 				int *tLen,
6771 				char **data,
6772 				const char *target)
6773 {
6774     HTLine *line;
6775     char *LineData;
6776     int LineOffset, HitOffset, LenNeeded, i;
6777     const char *cp;
6778 
6779     /*
6780      * Make sure we have an HText structure, that line_num is
6781      * in its range, and that we have a target string.  -FM
6782      */
6783     if (!(text &&
6784 	  line_num >= 0 &&
6785 	  line_num <= text->Lines &&
6786 	  non_empty(target))) {
6787 	return (FALSE);
6788     }
6789 
6790     /*
6791      * Find the line and set up its data and offset -FM
6792      */
6793     for (i = 0, line = FirstHTLine(text);
6794 	 i < line_num && (line != text->last_line);
6795 	 i++, line = line->next) {
6796 	if (line->next == NULL) {
6797 	    return (FALSE);
6798 	}
6799     }
6800     if (!(line && line->data[0]))
6801 	return (FALSE);
6802     LineData = (char *) line->data;
6803     LineOffset = (int) line->offset;
6804 
6805     /*
6806      * If the target is on the line, load the offset of
6807      * its first character and the subsequent line data,
6808      * strip any special characters from the loaded line
6809      * data, and return TRUE.  -FM
6810      */
6811     if (((cp = LYno_attr_mb_strstr(LineData,
6812 				   target,
6813 				   utf_flag, YES,
6814 				   &HitOffset,
6815 				   &LenNeeded)) != NULL) &&
6816 	(LineOffset + LenNeeded) <= DISPLAY_COLS) {
6817 	/*
6818 	 * We had a hit so load the results,
6819 	 * remove IsSpecial characters from
6820 	 * the allocated data string, and
6821 	 * return TRUE.  -FM
6822 	 */
6823 	*offset = (LineOffset + HitOffset);
6824 	*tLen = (LenNeeded - HitOffset);
6825 	StrAllocCopy(*data, cp);
6826 	remove_special_attr_chars(*data);
6827 	return (TRUE);
6828     }
6829 
6830     /*
6831      * The line does not contain the target.  -FM
6832      */
6833     return (FALSE);
6834 }
6835 
6836 /*
6837  * HText_getNumOfLines returns the number of lines in the
6838  * current document.
6839  */
HText_getNumOfLines(void)6840 int HText_getNumOfLines(void)
6841 {
6842     return (HTMainText ? HTMainText->Lines : 0);
6843 }
6844 
6845 /*
6846  * HText_getNumOfBytes returns the size of the document, as rendered.  This
6847  * may be different from the original filesize.
6848  */
HText_getNumOfBytes(void)6849 int HText_getNumOfBytes(void)
6850 {
6851     int result = -1;
6852     HTLine *line = NULL;
6853 
6854     if (HTMainText != 0) {
6855 	for (line = FirstHTLine(HTMainText);
6856 	     line != HTMainText->last_line;
6857 	     line = line->next) {
6858 	    result += 1 + (int) strlen(line->data);
6859 	}
6860     }
6861     return result;
6862 }
6863 
6864 /*
6865  * HText_getTitle returns the title of the
6866  * current document.
6867  */
HText_getTitle(void)6868 const char *HText_getTitle(void)
6869 {
6870     return (HTMainText ?
6871 	    HTAnchor_title(HTMainText->node_anchor) : 0);
6872 }
6873 
6874 #ifdef USE_COLOR_STYLE
HText_getStyle(void)6875 const char *HText_getStyle(void)
6876 {
6877     return (HTMainText ?
6878 	    HTAnchor_style(HTMainText->node_anchor) : 0);
6879 }
6880 #endif
6881 
6882 /*
6883  * HText_getSugFname returns the suggested filename of the current
6884  * document (normally derived from a Content-Disposition header with
6885  * attachment; filename=name.suffix).  -FM
6886  */
HText_getSugFname(void)6887 const char *HText_getSugFname(void)
6888 {
6889     return (HTMainText ?
6890 	    HTAnchor_SugFname(HTMainText->node_anchor) : 0);
6891 }
6892 
6893 /*
6894  * HTCheckFnameForCompression receives the address of an allocated
6895  * string containing a filename, and an anchor pointer, and expands
6896  * or truncates the string's suffix if appropriate, based on whether
6897  * the anchor indicates that the file is compressed.  We assume
6898  * that the file was not uncompressed (as when downloading), and
6899  * believe the headers about whether it's compressed or not.  -FM
6900  *
6901  * Added third arg - if strip_ok is FALSE, we don't trust the anchor
6902  * info enough to remove a compression suffix if the anchor object
6903  * does not indicate compression.  - kw
6904  */
HTCheckFnameForCompression(char ** fname,HTParentAnchor * anchor,int strip_ok)6905 void HTCheckFnameForCompression(char **fname,
6906 				HTParentAnchor *anchor,
6907 				int strip_ok)
6908 {
6909     char *fn = *fname;
6910     char *dot = NULL;
6911     char *cp = NULL;
6912     const char *suffix = "";
6913     CompressFileType method;
6914     CompressFileType second;
6915 
6916     /*
6917      * Make sure we have a string and anchor.  -FM
6918      */
6919     if (!(fn && anchor))
6920 	return;
6921 
6922     /*
6923      * Make sure we have a file, not directory, name.  -FM
6924      */
6925     if (*(fn = LYPathLeaf(fn)) == '\0')
6926 	return;
6927 
6928     method = HTContentToCompressType(anchor);
6929 
6930     /*
6931      * If no Content-Encoding has been detected via the anchor
6932      * pointer, but strip_ok is not set, there is nothing left
6933      * to do.  - kw
6934      */
6935     if ((method == cftNone) && !strip_ok)
6936 	return;
6937 
6938     /*
6939      * Treat .tgz specially
6940      */
6941     if ((dot = strrchr(fn, '.')) != NULL
6942 	&& !strcasecomp(dot, ".tgz")) {
6943 	if (method == cftNone) {
6944 	    strcpy(dot, ".tar");
6945 	}
6946 	return;
6947     }
6948 
6949     /*
6950      * Seek the last dot, and check whether
6951      * we have a gzip or compress suffix.  -FM
6952      */
6953     if ((dot = strrchr(fn, '.')) != NULL) {
6954 	int rootlen = 0;
6955 
6956 	if (HTCompressFileType(fn, ".", &rootlen) != cftNone) {
6957 	    if (method == cftNone) {
6958 		/*
6959 		 * It has a suffix which signifies a gzipped
6960 		 * or compressed file for us, but the anchor
6961 		 * claims otherwise, so tweak the suffix.  -FM
6962 		 */
6963 		*dot = '\0';
6964 	    }
6965 	    return;
6966 	}
6967 	if ((second = HTCompressFileType(fn, "-_", &rootlen)) != cftNone) {
6968 	    cp = fn + rootlen;
6969 	    if (method == cftNone) {
6970 		/*
6971 		 * It has a tail which signifies a gzipped
6972 		 * file for us, but the anchor claims otherwise,
6973 		 * so tweak the suffix.  -FM
6974 		 */
6975 		if (cp == dot + 1)
6976 		    cp--;
6977 		*cp = '\0';
6978 	    } else {
6979 		/*
6980 		 * The anchor claims it's gzipped, and we
6981 		 * believe it, so force this tail to the
6982 		 * conventional suffix.  -FM
6983 		 */
6984 #ifdef VMS
6985 		*cp = '-';
6986 #else
6987 		*cp = '.';
6988 #endif /* VMS */
6989 		if (second == cftCompress)
6990 		    LYUpperCase(cp);
6991 		else
6992 		    LYLowerCase(cp);
6993 	    }
6994 	    return;
6995 	}
6996     }
6997 
6998     suffix = HTCompressTypeToSuffix(method);
6999 
7000     /*
7001      * Add the appropriate suffix.  -FM
7002      */
7003     if (*suffix) {
7004 	if (!dot) {
7005 	    StrAllocCat(*fname, suffix);
7006 	} else if (*++dot == '\0') {
7007 	    StrAllocCat(*fname, suffix + 1);
7008 	} else {
7009 	    StrAllocCat(*fname, suffix);
7010 #ifdef VMS
7011 	    (*fname)[strlen(*fname) - strlen(suffix)] = '-';
7012 #endif /* !VMS */
7013 	}
7014     }
7015 }
7016 
7017 /*
7018  * HText_getLastModified returns the Last-Modified header
7019  * if available, for the current document.  -FM
7020  */
HText_getLastModified(void)7021 const char *HText_getLastModified(void)
7022 {
7023     return (HTMainText ?
7024 	    HTAnchor_last_modified(HTMainText->node_anchor) : 0);
7025 }
7026 
7027 /*
7028  * HText_getDate returns the Date header
7029  * if available, for the current document.  -FM
7030  */
HText_getDate(void)7031 const char *HText_getDate(void)
7032 {
7033     return (HTMainText ?
7034 	    HTAnchor_date(HTMainText->node_anchor) : 0);
7035 }
7036 
7037 /*
7038  * HText_getServer returns the Server header
7039  * if available, for the current document.  -FM
7040  */
HText_getServer(void)7041 const char *HText_getServer(void)
7042 {
7043     return (HTMainText ?
7044 	    HTAnchor_server(HTMainText->node_anchor) : 0);
7045 }
7046 
7047 #ifdef EXP_HTTP_HEADERS
7048 /*
7049  * Returns the full text of HTTP headers, if available, for the current
7050  * document.
7051  */
HText_getHttpHeaders(void)7052 const char *HText_getHttpHeaders(void)
7053 {
7054     return (HTMainText ?
7055 	    HTAnchor_http_headers(HTMainText->node_anchor) : 0);
7056 }
7057 #endif
7058 
7059 /*
7060  * HText_pageDisplay displays a screen of text
7061  * starting from the line 'line_num'-1.
7062  * This is the primary call for lynx.
7063  */
HText_pageDisplay(int line_num,char * target)7064 void HText_pageDisplay(int line_num,
7065 		       char *target)
7066 {
7067 #ifdef DISP_PARTIAL
7068     if (debug_display_partial || (LYTraceLogFP != NULL)) {
7069 	CTRACE((tfp, "GridText: HText_pageDisplay at line %d started\n", line_num));
7070     }
7071 
7072     if (display_partial) {
7073 	int stop_before = -1;
7074 
7075 	/*
7076 	 * Garbage is reported from forms input fields in incremental mode.
7077 	 * So we start HText_trimHightext() to forget this side effect.
7078 	 * This function was split-out from HText_endAppend().
7079 	 * It may not be the best solution but it works.  - LP
7080 	 *
7081 	 * (FALSE = indicate that we are in partial mode)
7082 	 * Multiple calls of HText_trimHightext works without problem now.
7083 	 */
7084 	if (HTMainText && HTMainText->stbl)
7085 	    stop_before = Stbl_getStartLineDeep(HTMainText->stbl);
7086 	HText_trimHightext(HTMainText, FALSE, stop_before);
7087     }
7088 #endif
7089     display_page(HTMainText, line_num - 1, target);
7090 
7091 #ifdef DISP_PARTIAL
7092     if (display_partial && debug_display_partial)
7093 	LYSleepMsg();
7094 #endif
7095 
7096     is_www_index = HTAnchor_isIndex(HTMainAnchor);
7097 
7098 #ifdef DISP_PARTIAL
7099     if (debug_display_partial || (LYTraceLogFP != NULL)) {
7100 	CTRACE((tfp, "GridText: HText_pageDisplay finished\n"));
7101     }
7102 #endif
7103 }
7104 
7105 /*
7106  * Return YES if we have a whereis search target on the displayed
7107  * page.  - kw
7108  */
HText_pageHasPrevTarget(void)7109 BOOL HText_pageHasPrevTarget(void)
7110 {
7111     if (!HTMainText)
7112 	return NO;
7113     else
7114 	return HTMainText->page_has_target;
7115 }
7116 
7117 /*
7118  * Find the number of the closest anchor to the given document offset.  Used
7119  * in reparsing, this will usually find an exact match, as a link shifts around
7120  * on the display.  It will not find a match when (for example) the source view
7121  * shows images that are not links in the html.
7122  */
HText_closestAnchor(HText * text,int offset)7123 int HText_closestAnchor(HText *text, int offset)
7124 {
7125     int result = -1;
7126     int absdiff = 0;
7127     int newdiff;
7128     TextAnchor *Anchor_ptr = NULL;
7129     TextAnchor *closest = NULL;
7130 
7131     for (Anchor_ptr = text->first_anchor;
7132 	 Anchor_ptr != NULL;
7133 	 Anchor_ptr = Anchor_ptr->next) {
7134 	if (Anchor_ptr->sgml_offset == offset) {
7135 	    result = Anchor_ptr->number;
7136 	    break;
7137 	} else {
7138 	    newdiff = abs(Anchor_ptr->sgml_offset - offset);
7139 	    if (absdiff == 0 || absdiff > newdiff) {
7140 		absdiff = newdiff;
7141 		closest = Anchor_ptr;
7142 	    }
7143 	}
7144     }
7145     if (result < 0 && closest != 0) {
7146 	result = closest->number;
7147     }
7148 
7149     return result;
7150 }
7151 
7152 /*
7153  * Find the offset for the given anchor, e.g., the inverse of
7154  * HText_closestAnchor().
7155  */
HText_locateAnchor(HText * text,int anchor_number)7156 int HText_locateAnchor(HText *text, int anchor_number)
7157 {
7158     int result = -1;
7159     TextAnchor *Anchor_ptr = NULL;
7160 
7161     for (Anchor_ptr = text->first_anchor;
7162 	 Anchor_ptr != NULL;
7163 	 Anchor_ptr = Anchor_ptr->next) {
7164 	if (Anchor_ptr->number == anchor_number) {
7165 	    result = Anchor_ptr->sgml_offset;
7166 	    break;
7167 	}
7168     }
7169 
7170     return result;
7171 }
7172 
7173 /*
7174  * This is supposed to give the same result as the inline checks in
7175  * display_page(), so we can decide which anchors will be visible.
7176  */
anchor_is_numbered(TextAnchor * Anchor_ptr)7177 static BOOL anchor_is_numbered(TextAnchor *Anchor_ptr)
7178 {
7179     BOOL result = FALSE;
7180 
7181     if (Anchor_ptr->show_anchor
7182 	&& (Anchor_ptr->link_type & HYPERTEXT_ANCHOR)) {
7183 	result = TRUE;
7184     } else if (Anchor_ptr->link_type == INPUT_ANCHOR
7185 	       && Anchor_ptr->input_field->type != F_HIDDEN_TYPE) {
7186 	result = TRUE;
7187     }
7188     return result;
7189 }
7190 
7191 /*
7192  * Return the absolute line number (counting from the beginning of the
7193  * document) for the given absolute anchor number.  Normally line numbers are
7194  * computed within the screen, and for that we use the links[] array.  A few
7195  * uses require the absolute anchor number.  For example, reparsing a document,
7196  * e.g., switching between normal and source views will alter the line numbers
7197  * of each link, and may require adjusting the top line number used for the
7198  * display, before we recompute the links[] array.
7199  */
HText_getAbsLineNumber(HText * text,int anchor_number)7200 int HText_getAbsLineNumber(HText *text,
7201 			   int anchor_number)
7202 {
7203     int result = -1;
7204 
7205     if (anchor_number >= 0 && text != 0) {
7206 	TextAnchor *Anchor_ptr = NULL;
7207 
7208 	for (Anchor_ptr = text->first_anchor;
7209 	     Anchor_ptr != NULL;
7210 	     Anchor_ptr = Anchor_ptr->next) {
7211 	    if (anchor_is_numbered(Anchor_ptr)
7212 		&& Anchor_ptr->number == anchor_number) {
7213 		result = Anchor_ptr->line_num;
7214 		break;
7215 	    }
7216 	}
7217     }
7218     return result;
7219 }
7220 
7221 /*
7222  * Compute the link-number in a page, given the top line number of the page and
7223  * the absolute anchor number.
7224  */
HText_anchorRelativeTo(HText * text,int top_lineno,int anchor_number)7225 int HText_anchorRelativeTo(HText *text, int top_lineno, int anchor_number)
7226 {
7227     int result = 0;
7228     int from_top = 0;
7229     TextAnchor *Anchor_ptr = NULL;
7230 
7231     for (Anchor_ptr = text->first_anchor;
7232 	 Anchor_ptr != NULL;
7233 	 Anchor_ptr = Anchor_ptr->next) {
7234 	if (Anchor_ptr->number == anchor_number) {
7235 	    result = from_top;
7236 	    break;
7237 	}
7238 	if (!anchor_is_numbered(Anchor_ptr))
7239 	    continue;
7240 	if (Anchor_ptr->line_num >= top_lineno) {
7241 	    ++from_top;
7242 	}
7243     }
7244     return result;
7245 }
7246 
7247 /*
7248  * HText_LinksInLines returns the number of links in the
7249  * 'Lines' number of lines beginning with 'line_num'-1.  -FM
7250  */
HText_LinksInLines(HText * text,int line_num,int Lines)7251 int HText_LinksInLines(HText *text,
7252 		       int line_num,
7253 		       int Lines)
7254 {
7255     int total = 0;
7256     int start = (line_num - 1);
7257     int end = (start + Lines);
7258     TextAnchor *Anchor_ptr = NULL;
7259 
7260     if (!text)
7261 	return total;
7262 
7263     for (Anchor_ptr = text->first_anchor;
7264 	 Anchor_ptr != NULL && Anchor_ptr->line_num <= end;
7265 	 Anchor_ptr = Anchor_ptr->next) {
7266 	if (Anchor_ptr->line_num >= start &&
7267 	    Anchor_ptr->line_num < end &&
7268 	    Anchor_ptr->show_anchor &&
7269 	    !(Anchor_ptr->link_type == INPUT_ANCHOR
7270 	      && Anchor_ptr->input_field->type == F_HIDDEN_TYPE))
7271 	    ++total;
7272     }
7273 
7274     return total;
7275 }
7276 
HText_setStale(HText * text)7277 void HText_setStale(HText *text)
7278 {
7279     text->stale = YES;
7280 }
7281 
HText_refresh(HText * text)7282 void HText_refresh(HText *text)
7283 {
7284     if (text->stale)
7285 	display_page(text, text->top_of_screen, "");
7286 }
7287 
HText_sourceAnchors(HText * text)7288 int HText_sourceAnchors(HText *text)
7289 {
7290     return (text ? text->last_anchor_number : -1);
7291 }
7292 
HText_canScrollUp(HText * text)7293 BOOL HText_canScrollUp(HText *text)
7294 {
7295     return (BOOL) (text->top_of_screen != 0);
7296 }
7297 
7298 /*
7299  * Check if there is more info below this page.
7300  */
HText_canScrollDown(void)7301 BOOL HText_canScrollDown(void)
7302 {
7303     HText *text = HTMainText;
7304 
7305     return (BOOL) ((text != 0)
7306 		   && ((text->top_of_screen + display_lines) <= text->Lines));
7307 }
7308 
7309 /*		Scroll actions
7310 */
HText_scrollTop(HText * text)7311 void HText_scrollTop(HText *text)
7312 {
7313     display_page(text, 0, "");
7314 }
7315 
HText_scrollDown(HText * text)7316 void HText_scrollDown(HText *text)
7317 {
7318     display_page(text, text->top_of_screen + display_lines, "");
7319 }
7320 
HText_scrollUp(HText * text)7321 void HText_scrollUp(HText *text)
7322 {
7323     display_page(text, text->top_of_screen - display_lines, "");
7324 }
7325 
HText_scrollBottom(HText * text)7326 void HText_scrollBottom(HText *text)
7327 {
7328     display_page(text, text->Lines - display_lines, "");
7329 }
7330 
7331 /*		Browsing functions
7332  *		==================
7333  */
7334 
7335 /* Bring to front and highlight it
7336 */
HText_select(HText * text)7337 BOOL HText_select(HText *text)
7338 {
7339     if (text != HTMainText) {
7340 	/*
7341 	 * Reset flag for whereis search string - cannot be true here
7342 	 * since text is not our HTMainText.  - kw
7343 	 */
7344 	if (text)
7345 	    text->page_has_target = NO;
7346 
7347 #ifdef DISP_PARTIAL
7348 	/* Reset these for the previous and current text. - kw */
7349 	ResetPartialLinenos(text);
7350 	ResetPartialLinenos(HTMainText);
7351 #endif /* DISP_PARTIAL */
7352 
7353 #ifdef CAN_SWITCH_DISPLAY_CHARSET
7354 	/* text->UCLYhndl is not reset by META, so use a more circumvent way */
7355 	if (text->node_anchor->UCStages->s[UCT_STAGE_HTEXT].LYhndl
7356 	    != current_char_set)
7357 	    Switch_Display_Charset(text->node_anchor->UCStages->s[UCT_STAGE_HTEXT].LYhndl, SWITCH_DISPLAY_CHARSET_MAYBE);
7358 #endif
7359 	assert(text != NULL);
7360 	if (HTMainText) {
7361 	    if (HText_hasUTF8OutputSet(HTMainText) &&
7362 		HTLoadedDocumentEightbit() &&
7363 		IS_UTF8_TTY) {
7364 		text->had_utf8 = HTMainText->has_utf8;
7365 	    } else {
7366 		text->had_utf8 = NO;
7367 	    }
7368 	    HTMainText->has_utf8 = NO;
7369 	    text->has_utf8 = NO;
7370 	}
7371 
7372 	HTMainText = text;
7373 	HTMainAnchor = text->node_anchor;
7374 
7375 	/*
7376 	 * Make this text the most current in the loaded texts list.  -FM
7377 	 */
7378 	if (loaded_texts && HTList_removeObject(loaded_texts, text))
7379 	    HTList_addObject(loaded_texts, text);
7380     }
7381     return YES;
7382 }
7383 
7384 /*
7385  * This function returns TRUE if doc's post_data, address
7386  * and isHEAD elements are identical to those of a loaded
7387  * (memory cached) text.  -FM
7388  */
HText_POSTReplyLoaded(DocInfo * doc)7389 BOOL HText_POSTReplyLoaded(DocInfo *doc)
7390 {
7391     HText *text = NULL;
7392     HTList *cur = loaded_texts;
7393     bstring *post_data;
7394     char *address;
7395     BOOL is_head;
7396 
7397     /*
7398      * Make sure we have the structures.  -FM
7399      */
7400     if (!cur || !doc)
7401 	return (FALSE);
7402 
7403     /*
7404      * Make sure doc is for a POST reply.  -FM
7405      */
7406     if ((post_data = doc->post_data) == NULL ||
7407 	(address = doc->address) == NULL)
7408 	return (FALSE);
7409     is_head = doc->isHEAD;
7410 
7411     /*
7412      * Loop through the loaded texts looking for a
7413      * POST reply match.  -FM
7414      */
7415     while (NULL != (text = (HText *) HTList_nextObject(cur))) {
7416 	if (text->node_anchor &&
7417 	    text->node_anchor->post_data &&
7418 	    BINEQ(post_data, text->node_anchor->post_data) &&
7419 	    text->node_anchor->address &&
7420 	    !strcmp(address, text->node_anchor->address) &&
7421 	    is_head == text->node_anchor->isHEAD) {
7422 	    return (TRUE);
7423 	}
7424     }
7425 
7426     return (FALSE);
7427 }
7428 
HTFindPoundSelector(const char * selector)7429 BOOL HTFindPoundSelector(const char *selector)
7430 {
7431     TextAnchor *a;
7432 
7433     CTRACE((tfp, "FindPound: searching for \"%s\"\n", selector));
7434     for (a = HTMainText->first_anchor; a != 0; a = a->next) {
7435 
7436 	if (a->anchor && a->anchor->tag) {
7437 	    if (!strcmp(a->anchor->tag, selector)) {
7438 
7439 		www_search_result = a->line_num + 1;
7440 
7441 		CTRACE((tfp, "FindPound: Selecting anchor [%d] at line %d\n",
7442 			a->number, www_search_result));
7443 		if (!strcmp(selector, LYToolbarName)) {
7444 		    --www_search_result;
7445 		}
7446 		return (YES);
7447 	    }
7448 	}
7449     }
7450     return (NO);
7451 }
7452 
HText_selectAnchor(HText * text,HTChildAnchor * anchor)7453 BOOL HText_selectAnchor(HText *text, HTChildAnchor *anchor)
7454 {
7455     TextAnchor *a;
7456     int l;
7457 
7458     for (a = text->first_anchor; a; a = a->next) {
7459 	if (a->anchor == anchor)
7460 	    break;
7461     }
7462     if (!a) {
7463 	CTRACE((tfp, "HText: No such anchor in this text!\n"));
7464 	return NO;
7465     }
7466 
7467     if (text != HTMainText) {	/* Comment out by ??? */
7468 	HTMainText = text;	/* Put back in by tbl 921208 */
7469 	HTMainAnchor = text->node_anchor;
7470     }
7471     l = a->line_num;
7472 
7473     CTRACE((tfp, "HText: Selecting anchor [%d] at line %d\n",
7474 	    a->number, l));
7475 
7476     if (!text->stale &&
7477 	(l >= text->top_of_screen) &&
7478 	(l < text->top_of_screen + display_lines + 1))
7479 	return YES;
7480 
7481     www_search_result = l - (display_lines / 3);	/* put in global variable */
7482 
7483     return YES;
7484 }
7485 
7486 /*		Editing functions		- NOT IMPLEMENTED
7487  *		=================
7488  *
7489  *	These are called from the application.  There are many more functions
7490  *	not included here from the original text object.
7491  */
7492 
7493 /*	Style handling:
7494 */
7495 /*	Apply this style to the selection
7496 */
HText_applyStyle(HText * me GCC_UNUSED,HTStyle * style GCC_UNUSED)7497 void HText_applyStyle(HText *me GCC_UNUSED, HTStyle *style GCC_UNUSED)
7498 {
7499 
7500 }
7501 
7502 /*	Update all text with changed style.
7503 */
HText_updateStyle(HText * me GCC_UNUSED,HTStyle * style GCC_UNUSED)7504 void HText_updateStyle(HText *me GCC_UNUSED, HTStyle *style GCC_UNUSED)
7505 {
7506 
7507 }
7508 
7509 /*	Return style of  selection
7510 */
HText_selectionStyle(HText * me GCC_UNUSED,HTStyleSheet * sheet GCC_UNUSED)7511 HTStyle *HText_selectionStyle(HText *me GCC_UNUSED, HTStyleSheet *sheet GCC_UNUSED)
7512 {
7513     return 0;
7514 }
7515 
7516 /*	Paste in styled text
7517 */
HText_replaceSel(HText * me GCC_UNUSED,const char * aString GCC_UNUSED,HTStyle * aStyle GCC_UNUSED)7518 void HText_replaceSel(HText *me GCC_UNUSED, const char *aString GCC_UNUSED,
7519 		      HTStyle *aStyle GCC_UNUSED)
7520 {
7521 }
7522 
7523 /*	Apply this style to the selection and all similarly formatted text
7524  *	(style recovery only)
7525  */
HTextApplyToSimilar(HText * me GCC_UNUSED,HTStyle * style GCC_UNUSED)7526 void HTextApplyToSimilar(HText *me GCC_UNUSED, HTStyle *style GCC_UNUSED)
7527 {
7528 
7529 }
7530 
7531 /*	Select the first unstyled run.
7532  *	(style recovery only)
7533  */
HTextSelectUnstyled(HText * me GCC_UNUSED,HTStyleSheet * sheet GCC_UNUSED)7534 void HTextSelectUnstyled(HText *me GCC_UNUSED, HTStyleSheet *sheet GCC_UNUSED)
7535 {
7536 
7537 }
7538 
7539 /*	Anchor handling:
7540 */
HText_unlinkSelection(HText * me GCC_UNUSED)7541 void HText_unlinkSelection(HText *me GCC_UNUSED)
7542 {
7543 
7544 }
7545 
HText_referenceSelected(HText * me GCC_UNUSED)7546 HTAnchor *HText_referenceSelected(HText *me GCC_UNUSED)
7547 {
7548     return 0;
7549 }
7550 
HText_getTopOfScreen(void)7551 int HText_getTopOfScreen(void)
7552 {
7553     HText *text = HTMainText;
7554 
7555     return text != 0 ? text->top_of_screen : 0;
7556 }
7557 
HText_getLines(HText * text)7558 int HText_getLines(HText *text)
7559 {
7560     return text->Lines;
7561 }
7562 
7563 /*
7564  * Constrain the line number to be within the document.  The line number is
7565  * zero-based.
7566  */
HText_getPreferredTopLine(HText * text,int line_number)7567 int HText_getPreferredTopLine(HText *text, int line_number)
7568 {
7569     int last_screen = text->Lines - (display_lines - 2);
7570 
7571     if (text->Lines < display_lines) {
7572 	line_number = 0;
7573     } else if (line_number > text->Lines) {
7574 	line_number = last_screen;
7575     } else if (line_number < 0) {
7576 	line_number = 0;
7577     }
7578     return line_number;
7579 }
7580 
HText_linkSelTo(HText * me GCC_UNUSED,HTAnchor * anchor GCC_UNUSED)7581 HTAnchor *HText_linkSelTo(HText *me GCC_UNUSED,
7582 			  HTAnchor * anchor GCC_UNUSED)
7583 {
7584     return 0;
7585 }
7586 
7587 /*
7588  * Utility for freeing the list of previous isindex and whereis queries.  -FM
7589  */
HTSearchQueries_free(void)7590 void HTSearchQueries_free(void)
7591 {
7592     LYFreeStringList(search_queries);
7593     search_queries = NULL;
7594 }
7595 
7596 /*
7597  * Utility for listing isindex and whereis queries, making
7598  * any repeated queries the most current in the list.  -FM
7599  */
HTAddSearchQuery(char * query)7600 void HTAddSearchQuery(char *query)
7601 {
7602     char *new_query = NULL;
7603     char *old;
7604     HTList *cur;
7605 
7606     if (!non_empty(query))
7607 	return;
7608 
7609     StrAllocCopy(new_query, query);
7610 
7611     if (!search_queries) {
7612 	search_queries = HTList_new();
7613 #ifdef LY_FIND_LEAKS
7614 	atexit(HTSearchQueries_free);
7615 #endif
7616 	HTList_addObject(search_queries, new_query);
7617 	return;
7618     }
7619 
7620     cur = search_queries;
7621     while (NULL != (old = (char *) HTList_nextObject(cur))) {
7622 	if (!strcmp(old, new_query)) {
7623 	    HTList_removeObject(search_queries, old);
7624 	    FREE(old);
7625 	    break;
7626 	}
7627     }
7628     HTList_addObject(search_queries, new_query);
7629 
7630     return;
7631 }
7632 
do_www_search(DocInfo * doc)7633 int do_www_search(DocInfo *doc)
7634 {
7635     bstring *searchstring = NULL;
7636     bstring *temp = NULL;
7637     char *cp;
7638     char *tmpaddress = NULL;
7639     int ch;
7640     RecallType recall;
7641     int QueryTotal;
7642     int QueryNum;
7643     BOOLEAN PreviousSearch = FALSE;
7644     int code;
7645 
7646     /*
7647      * Load the default query buffer
7648      */
7649     if ((cp = strchr(doc->address, '?')) != NULL) {
7650 	/*
7651 	 * This is an index from a previous search.
7652 	 * Use its query as the default.
7653 	 */
7654 	PreviousSearch = TRUE;
7655 	BStrCopy0(searchstring, ++cp);
7656 	for (cp = searchstring->str; *cp; cp++)
7657 	    if (*cp == '+')
7658 		*cp = ' ';
7659 	HTUnEscape(searchstring->str);
7660 	BStrCopy(temp, searchstring);
7661 	/*
7662 	 * Make sure it's treated as the most recent query.  -FM
7663 	 */
7664 	HTAddSearchQuery(searchstring->str);
7665     } else {
7666 	/*
7667 	 * New search; no default.
7668 	 */
7669 	BStrCopy0(searchstring, "");
7670 	BStrCopy0(temp, "");
7671     }
7672 
7673     /*
7674      * Prompt for a query string.
7675      */
7676     if (isBEmpty(searchstring)) {
7677 	if (HTMainAnchor->isIndexPrompt)
7678 	    _statusline(HTMainAnchor->isIndexPrompt);
7679 	else
7680 	    _statusline(ENTER_DATABASE_QUERY);
7681     } else
7682 	_statusline(EDIT_CURRENT_QUERY);
7683     QueryTotal = (search_queries ? HTList_count(search_queries) : 0);
7684     recall = (((PreviousSearch && QueryTotal >= 2) ||
7685 	       (!PreviousSearch && QueryTotal >= 1)) ? RECALL_URL : NORECALL);
7686     QueryNum = QueryTotal;
7687 
7688   get_query:
7689     if ((ch = LYgetBString(&searchstring, VISIBLE, 0, recall)) < 0 ||
7690 	isBEmpty(searchstring) ||
7691 	ch == UPARROW ||
7692 	ch == DNARROW) {
7693 
7694 	if (recall && ch == UPARROW) {
7695 	    if (PreviousSearch) {
7696 		/*
7697 		 * Use the second to last query in the list.  -FM
7698 		 */
7699 		QueryNum = 1;
7700 		PreviousSearch = FALSE;
7701 	    } else {
7702 		/*
7703 		 * Go back to the previous query in the list.  -FM
7704 		 */
7705 		QueryNum++;
7706 	    }
7707 	    if (QueryNum >= QueryTotal)
7708 		/*
7709 		 * Roll around to the last query in the list.  -FM
7710 		 */
7711 		QueryNum = 0;
7712 	    if ((cp = (char *) HTList_objectAt(search_queries,
7713 					       QueryNum)) != NULL) {
7714 		BStrCopy0(searchstring, cp);
7715 		if (!isBEmpty(temp) &&
7716 		    !strcmp(temp->str, searchstring->str)) {
7717 		    _statusline(EDIT_CURRENT_QUERY);
7718 		} else if ((!isBEmpty(temp) && QueryTotal == 2) ||
7719 			   (isBEmpty(temp) && QueryTotal == 1)) {
7720 		    _statusline(EDIT_THE_PREV_QUERY);
7721 		} else {
7722 		    _statusline(EDIT_A_PREV_QUERY);
7723 		}
7724 		goto get_query;
7725 	    }
7726 	} else if (recall && ch == DNARROW) {
7727 	    if (PreviousSearch) {
7728 		/*
7729 		 * Use the first query in the list.  -FM
7730 		 */
7731 		QueryNum = QueryTotal - 1;
7732 		PreviousSearch = FALSE;
7733 	    } else {
7734 		/*
7735 		 * Advance to the next query in the list.  -FM
7736 		 */
7737 		QueryNum--;
7738 	    }
7739 	    if (QueryNum < 0)
7740 		/*
7741 		 * Roll around to the first query in the list.  -FM
7742 		 */
7743 		QueryNum = QueryTotal - 1;
7744 	    if ((cp = (char *) HTList_objectAt(search_queries,
7745 					       QueryNum)) != NULL) {
7746 		BStrCopy0(searchstring, cp);
7747 		if (!isBEmpty(temp) &&
7748 		    !strcmp(temp->str, searchstring->str)) {
7749 		    _statusline(EDIT_CURRENT_QUERY);
7750 		} else if ((!isBEmpty(temp) && QueryTotal == 2) ||
7751 			   (isBEmpty(temp) && QueryTotal == 1)) {
7752 		    _statusline(EDIT_THE_PREV_QUERY);
7753 		} else {
7754 		    _statusline(EDIT_A_PREV_QUERY);
7755 		}
7756 		goto get_query;
7757 	    }
7758 	}
7759 
7760 	/*
7761 	 * Search cancelled.
7762 	 */
7763 	HTInfoMsg(CANCELLED);
7764 	code = NULLFILE;
7765     } else {
7766 
7767 	LYTrimLeading(searchstring->str);
7768 	LYTrimTrailing(searchstring->str);
7769 	if (isBEmpty(searchstring)) {
7770 	    HTInfoMsg(CANCELLED);
7771 	    code = NULLFILE;
7772 	} else if (!LYforce_no_cache &&
7773 		   !isBEmpty(temp) &&
7774 		   !strcmp(temp->str, searchstring->str)) {
7775 	    /*
7776 	     * Don't resubmit the same query unintentionally.
7777 	     */
7778 	    HTUserMsg(USE_C_R_TO_RESUB_CUR_QUERY);
7779 	    code = NULLFILE;
7780 	} else {
7781 
7782 	    /*
7783 	     * Add searchstring to the query list,
7784 	     * or make it the most current.  -FM
7785 	     */
7786 	    HTAddSearchQuery(searchstring->str);
7787 
7788 	    /*
7789 	     * Show the URL with the new query.
7790 	     */
7791 	    if ((cp = strchr(doc->address, '?')) != NULL)
7792 		*cp = '\0';
7793 	    StrAllocCopy(tmpaddress, doc->address);
7794 	    StrAllocCat(tmpaddress, "?");
7795 	    StrAllocCat(tmpaddress, searchstring->str);
7796 	    user_message(WWW_WAIT_MESSAGE, tmpaddress);
7797 #ifdef SYSLOG_REQUESTED_URLS
7798 	    LYSyslog(tmpaddress);
7799 #endif
7800 	    FREE(tmpaddress);
7801 	    if (cp)
7802 		*cp = '?';
7803 
7804 	    /*
7805 	     * OK, now we do the search.
7806 	     */
7807 	    if (HTSearch(searchstring->str, HTMainAnchor)) {
7808 		auto char *cp_freeme = NULL;
7809 
7810 		if (traversal)
7811 		    cp_freeme = stub_HTAnchor_address((HTAnchor *) HTMainAnchor);
7812 		else
7813 		    cp_freeme = HTAnchor_address((HTAnchor *) HTMainAnchor);
7814 		StrAllocCopy(doc->address, cp_freeme);
7815 		FREE(cp_freeme);
7816 
7817 		CTRACE((tfp, "\ndo_www_search: newfile: %s\n", doc->address));
7818 
7819 		/*
7820 		 * Yah, the search succeeded.
7821 		 */
7822 		code = NORMAL;
7823 	    } else {
7824 
7825 		/*
7826 		 * Either the search failed (Yuk), or we got redirection.
7827 		 * If it's redirection, use_this_url_instead is set, and
7828 		 * mainloop() will deal with it such that security features
7829 		 * and restrictions are checked before acting on the URL, or
7830 		 * rejecting it.  -FM
7831 		 */
7832 		code = NOT_FOUND;
7833 	    }
7834 	}
7835     }
7836     BStrFree(searchstring);
7837     BStrFree(temp);
7838     return code;
7839 }
7840 
write_offset(FILE * fp,HTLine * line)7841 static void write_offset(FILE *fp, HTLine *line)
7842 {
7843     int i;
7844 
7845     if (line->data[0]) {
7846 	for (i = 0; i < (int) line->offset; i++) {
7847 	    fputc(' ', fp);
7848 	}
7849     }
7850 }
7851 
write_hyphen(FILE * fp)7852 static void write_hyphen(FILE *fp)
7853 {
7854     if (dump_output_immediately &&
7855 	LYRawMode &&
7856 	LYlowest_eightbit[current_char_set] <= 173 &&
7857 	(LYCharSet_UC[current_char_set].enc == UCT_ENC_8859 ||
7858 	 (LYCharSet_UC[current_char_set].like8859 & UCT_R_8859SPECL)) != 0) {
7859 	fputc(0xad, fp);	/* the iso8859 byte for SHY */
7860     } else {
7861 	fputc('-', fp);
7862     }
7863 }
7864 
7865 /*
7866  * Returns the length after trimming trailing blanks.  Modify the string as
7867  * needed so that any special character which follows a trailing blank is moved
7868  * before the (trimmed) blank, so the result which will be dumped has no
7869  * trailing blanks.
7870  */
TrimmedLength(char * string)7871 static int TrimmedLength(char *string)
7872 {
7873     int result = (int) strlen(string);
7874 
7875     if (!HTisDocumentSource()) {
7876 	int adjust = result;
7877 	unsigned ch;
7878 
7879 	while (adjust > 0) {
7880 	    ch = UCH(string[adjust - 1]);
7881 	    if (isspace(ch) || IsSpecialAttrChar(ch)) {
7882 		--adjust;
7883 	    } else {
7884 		break;
7885 	    }
7886 	}
7887 	if (result != adjust) {
7888 	    char *dst = string + adjust;
7889 	    char *src = dst;
7890 
7891 	    for (;;) {
7892 		src = LYSkipBlanks(src);
7893 		if ((*dst++ = *src++) == '\0')
7894 		    break;
7895 	    }
7896 	    result = (int) (dst - string - 1);
7897 	}
7898     }
7899     return result;
7900 }
7901 
7902 typedef struct _AnchorIndex {
7903     struct _AnchorIndex *next;
7904     int type;			/* field type */
7905     int size;			/* character-width of field */
7906     int length;			/* byte-count for field's data */
7907     int offset;			/* byte-offset in line's data */
7908     int filler;			/* character to use for filler */
7909     const char *value;		/* field's value */
7910 } AnchorIndex;
7911 
countHTLines(void)7912 static unsigned countHTLines(void)
7913 {
7914     unsigned result = 0;
7915     HTLine *line = FirstHTLine(HTMainText);
7916 
7917     while (line != 0) {
7918 	++result;
7919 	if (line == HTMainText->last_line)
7920 	    break;
7921 	line = line->next;
7922     }
7923     CTRACE((tfp, "countHTLines %u\n", result));
7924     return result;
7925 }
7926 
7927 /*
7928  * The TextAnchor list is not organized to allow efficient dumping of a page.
7929  * Make an array with one item per line of the page, and store (by byte-offset)
7930  * pointers to the TextAnchors we want to use.
7931  */
allocAnchorIndex(unsigned * size)7932 static AnchorIndex **allocAnchorIndex(unsigned *size)
7933 {
7934     AnchorIndex **result = NULL;
7935     AnchorIndex *p, *q;
7936     TextAnchor *anchor = NULL;
7937     FormInfo *input = NULL;
7938 
7939     *size = countHTLines();
7940     if (*size != 0) {
7941 	result = typecallocn(AnchorIndex *, *size + 1);
7942 	if (result == NULL)
7943 	    outofmem(__FILE__, "allocAnchorIndex");
7944 
7945 	for (anchor = HTMainText->first_anchor;
7946 	     anchor != NULL;
7947 	     anchor = anchor->next) {
7948 
7949 	    if (anchor->link_type == INPUT_ANCHOR
7950 		&& anchor->show_anchor
7951 		&& anchor->line_num < (int) *size
7952 		&& (input = anchor->input_field) != NULL) {
7953 		CTRACE2(TRACE_GRIDTEXT,
7954 			(tfp, "line %d.%d %d %s->%s(%s)\n",
7955 			 anchor->line_num,
7956 			 anchor->line_pos,
7957 			 input->size,
7958 			 inputFieldDesc(input),
7959 			 input->value,
7960 			 input->orig_value));
7961 		switch (input->type) {
7962 		case F_SUBMIT_TYPE:
7963 		case F_RESET_TYPE:
7964 		case F_TEXT_SUBMIT_TYPE:
7965 		case F_IMAGE_SUBMIT_TYPE:
7966 		    CTRACE2(TRACE_GRIDTEXT, (tfp, "skipping\n"));
7967 		    continue;
7968 		case F_TEXT_TYPE:
7969 		case F_PASSWORD_TYPE:
7970 		case F_CHECKBOX_TYPE:
7971 		case F_RADIO_TYPE:
7972 		case F_OPTION_LIST_TYPE:
7973 		case F_TEXTAREA_TYPE:
7974 		case F_RANGE_TYPE:
7975 		case F_FILE_TYPE:
7976 		    p = typecalloc(AnchorIndex);
7977 		    if (p == NULL)
7978 			outofmem(__FILE__, "allocAnchorIndex");
7979 
7980 		    assert(p != NULL);
7981 		    p->type = input->type;
7982 		    p->size = input->size;
7983 		    p->offset = anchor->line_pos;
7984 		    p->value = input->value;
7985 
7986 		    switch (input->type) {
7987 		    case F_TEXTAREA_TYPE:
7988 		    case F_TEXT_TYPE:
7989 		    case F_PASSWORD_TYPE:
7990 			p->filler = '_';
7991 			break;
7992 		    case F_OPTION_LIST_TYPE:
7993 			p->filler = '_';
7994 			break;
7995 		    case F_CHECKBOX_TYPE:
7996 			p->value = (input->num_value
7997 				    ? checked_box
7998 				    : unchecked_box);
7999 			break;
8000 		    case F_RADIO_TYPE:
8001 			p->value = (input->num_value
8002 				    ? checked_radio
8003 				    : unchecked_radio);
8004 			break;
8005 		    default:
8006 			p->filler = ' ';
8007 			break;
8008 		    }
8009 		    p->length = (int) strlen(p->value);
8010 
8011 		    if ((q = result[anchor->line_num]) != NULL) {
8012 			/* insert, ordering by offset */
8013 			if (q->offset < p->offset) {
8014 			    while (q->next != NULL
8015 				   && q->next->offset < p->offset) {
8016 				q = q->next;
8017 			    }
8018 			    p->next = q->next;
8019 			    q->next = p;
8020 			} else {
8021 			    p->next = q;
8022 			    result[anchor->line_num] = p;
8023 			}
8024 		    } else {
8025 			result[anchor->line_num] = p;
8026 		    }
8027 		    break;
8028 		}
8029 	    }
8030 	}
8031     }
8032     return result;
8033 }
8034 
8035 /*
8036  * Free the data allocated in allocAnchorIndex().
8037  */
freeAnchorIndex(AnchorIndex ** inx,unsigned inx_size)8038 static void freeAnchorIndex(AnchorIndex ** inx, unsigned inx_size)
8039 {
8040     AnchorIndex *cur;
8041     unsigned num;
8042 
8043     if (inx != 0) {
8044 	if (inx_size != 0) {
8045 	    for (num = 0; num < inx_size; ++num) {
8046 		while ((cur = inx[num]) != NULL) {
8047 		    inx[num] = cur->next;
8048 		    free(cur);
8049 		}
8050 	    }
8051 	}
8052 	free(inx);
8053     }
8054 }
8055 
8056 /*
8057  * Print the contents of the file in HTMainText to
8058  * the file descriptor fp.
8059  * If is_email is TRUE add ">" before each "From " line.
8060  * If is_reply is TRUE add ">" to the beginning of each
8061  * line to specify the file is a reply to message.
8062  */
8063 #define FieldFirst(p) (this_wrap ? 0 : (p)->offset)
8064 #define FieldLast(p)  (FieldFirst(p) + (p)->size - this_wrap)
print_wwwfile_to_fd(FILE * fp,int is_email,int is_reply)8065 void print_wwwfile_to_fd(FILE *fp,
8066 			 int is_email,
8067 			 int is_reply)
8068 {
8069     int line_num, byte_num, byte_count;
8070     int first = TRUE;
8071     HTLine *line;
8072     AnchorIndex **inx;		/* sorted index of input-fields */
8073     AnchorIndex *cur = 0;	/* current input-field */
8074     unsigned inx_size;		/* number of entries in inx[] */
8075     int in_field = -1;		/* if positive, is index in cur->value[] */
8076     int this_wrap = 0;		/* current wrapping point of cur->value[] */
8077     int next_wrap = 0;		/* next wrapping point of cur->value[] */
8078 
8079 #ifndef NO_DUMP_WITH_BACKSPACES
8080     HText *text = HTMainText;
8081     BOOL in_b = FALSE;
8082     BOOL in_u = FALSE;
8083     BOOL bs = (BOOL) (!is_email && !is_reply
8084 		      && text != 0
8085 		      && with_backspaces
8086 		      && !IS_CJK_TTY
8087 		      && !text->T.output_utf8);
8088 #endif
8089 
8090     if (!HTMainText)
8091 	return;
8092 
8093     /*
8094      * Build an index of anchors for each line, so we can override the
8095      * static text which is stored in the list of HTLines.
8096      */
8097     inx = allocAnchorIndex(&inx_size);
8098 
8099     line = FirstHTLine(HTMainText);
8100     for (line_num = 0;; ++line_num, line = line->next) {
8101 	if (in_field >= 0) {
8102 	    this_wrap = next_wrap;
8103 	    next_wrap = 0;	/* FIXME - allow for multiple continuations */
8104 	    CTRACE2(TRACE_GRIDTEXT,
8105 		    (tfp, "wrap %d:%d, offset %d\n",
8106 		     in_field, cur ? cur->length : -1, this_wrap));
8107 	} else {
8108 	    cur = inx[line_num];
8109 	}
8110 
8111 	CTRACE2(TRACE_GRIDTEXT, (tfp, "dump %d:%s\n", line_num, line->data));
8112 
8113 	if (first) {
8114 	    first = FALSE;
8115 	    if (is_reply) {
8116 		fputc('>', fp);
8117 	    } else if (is_email && !StrNCmp(line->data, "From ", 5)) {
8118 		fputc('>', fp);
8119 	    }
8120 	} else if (line->data[0] != LY_SOFT_NEWLINE) {
8121 	    fputc('\n', fp);
8122 	    /*
8123 	     * Add news-style quotation if requested.  -FM
8124 	     */
8125 	    if (is_reply) {
8126 		fputc('>', fp);
8127 	    } else if (is_email && !StrNCmp(line->data, "From ", 5)) {
8128 		fputc('>', fp);
8129 	    }
8130 	}
8131 
8132 	write_offset(fp, line);
8133 
8134 	/*
8135 	 * Add data.
8136 	 */
8137 	byte_count = TrimmedLength(line->data);
8138 	for (byte_num = 0; byte_num < byte_count; byte_num++) {
8139 	    int byte_offset = byte_num + line->offset;
8140 	    int ch = UCH(line->data[byte_num]);
8141 	    int c2;
8142 
8143 	    while (cur != 0 && FieldLast(cur) < byte_offset) {
8144 		CTRACE2(TRACE_GRIDTEXT,
8145 			(tfp, "skip field since last %d < %d\n",
8146 			 FieldLast(cur), byte_offset));
8147 		cur = cur->next;
8148 		in_field = -1;
8149 	    }
8150 	    if (cur != 0 && in_field >= 0) {
8151 		CTRACE2(TRACE_GRIDTEXT,
8152 			(tfp, "compare %d to [%d..%d]\n",
8153 			 byte_offset,
8154 			 FieldFirst(cur),
8155 			 FieldLast(cur) - 1));
8156 	    }
8157 	    if (cur != 0
8158 		&& FieldFirst(cur) <= byte_offset
8159 		&& FieldLast(cur) > byte_offset) {
8160 		int off2 = ((in_field > 0)
8161 			    ? in_field
8162 			    : (byte_offset - FieldFirst(cur)));
8163 
8164 		/*
8165 		 * On the first time (for each line that the field appears on),
8166 		 * check if this field wraps.  If it does, save the offset into
8167 		 * the field which will be used to adjust the beginning of the
8168 		 * continuation line.
8169 		 */
8170 		if (byte_offset == FieldFirst(cur)) {
8171 		    next_wrap = 0;
8172 		    if (cur->size - this_wrap + byte_num > byte_count) {
8173 			CTRACE((tfp, "size %d, offset %d, length %d\n",
8174 				cur->size,
8175 				cur->offset,
8176 				cur->length));
8177 			CTRACE((tfp, "byte_count %d, byte_num %d\n",
8178 				byte_count, byte_num));
8179 			next_wrap = byte_count - byte_num;
8180 			CTRACE2(TRACE_GRIDTEXT,
8181 				(tfp, "field will wrap: %d\n", next_wrap));
8182 		    }
8183 		}
8184 
8185 		c2 = ((off2 < cur->length)
8186 		      ? cur->value[off2]
8187 		      : cur->filler);
8188 
8189 		if (ch != c2) {
8190 		    CTRACE2(TRACE_GRIDTEXT,
8191 			    (tfp, "line %d %d/%d [%d..%d] map %d %c->%c\n",
8192 			     line_num,
8193 			     off2, cur->length,
8194 			     FieldFirst(cur), FieldLast(cur) - 1,
8195 			     byte_offset, ch, c2));
8196 		    ch = c2;
8197 		}
8198 		++off2;
8199 		if ((off2 >= cur->size) &&
8200 		    (off2 >= cur->length || F_TEXTLIKE(cur->type))) {
8201 		    in_field = -1;
8202 		    this_wrap = 0;
8203 		    next_wrap = 0;
8204 		} else {
8205 		    in_field = off2;
8206 		}
8207 	    }
8208 
8209 	    if (!IsSpecialAttrChar(ch)) {
8210 #ifndef NO_DUMP_WITH_BACKSPACES
8211 		if (in_b) {
8212 		    fputc(ch, fp);
8213 		    fputc('\b', fp);
8214 		    fputc(ch, fp);
8215 		} else if (in_u) {
8216 		    fputc('_', fp);
8217 		    fputc('\b', fp);
8218 		    fputc(ch, fp);
8219 		} else
8220 #endif
8221 		    fputc(ch, fp);
8222 	    } else if (ch == LY_SOFT_HYPHEN &&
8223 		       (byte_num + 1) >= byte_count) {
8224 		write_hyphen(fp);
8225 	    } else if (dump_output_immediately && use_underscore) {
8226 		switch (ch) {
8227 		case LY_UNDERLINE_START_CHAR:
8228 		case LY_UNDERLINE_END_CHAR:
8229 		    fputc('_', fp);
8230 		    break;
8231 		case LY_BOLD_START_CHAR:
8232 		case LY_BOLD_END_CHAR:
8233 		    break;
8234 		}
8235 	    }
8236 #ifndef NO_DUMP_WITH_BACKSPACES
8237 	    else if (bs) {
8238 		switch (ch) {
8239 		case LY_UNDERLINE_START_CHAR:
8240 		    if (!in_b)
8241 			in_u = TRUE;	/*favor bold over underline */
8242 		    break;
8243 		case LY_UNDERLINE_END_CHAR:
8244 		    in_u = FALSE;
8245 		    break;
8246 		case LY_BOLD_START_CHAR:
8247 		    if (in_u)
8248 			in_u = FALSE;	/* turn it off */
8249 		    in_b = TRUE;
8250 		    break;
8251 		case LY_BOLD_END_CHAR:
8252 		    in_b = FALSE;
8253 		    break;
8254 		}
8255 	    }
8256 #endif
8257 	}
8258 
8259 	if (line == HTMainText->last_line)
8260 	    break;
8261 
8262 #ifdef VMS
8263 	if (HadVMSInterrupt)
8264 	    break;
8265 #endif /* VMS */
8266     }
8267     fputc('\n', fp);
8268 
8269     freeAnchorIndex(inx, inx_size);
8270 }
8271 
8272 /*
8273  * Print the contents of the file in HTMainText to
8274  * the file descriptor fp.
8275  * First output line is "thelink", ie, the URL for this file.
8276  */
print_crawl_to_fd(FILE * fp,char * thelink,char * thetitle)8277 void print_crawl_to_fd(FILE *fp, char *thelink,
8278 		       char *thetitle)
8279 {
8280     register int i;
8281     int first = TRUE;
8282     int limit;
8283     HTLine *line;
8284 
8285     if (!HTMainText)
8286 	return;
8287 
8288     line = FirstHTLine(HTMainText);
8289     fprintf(fp, "THE_URL:%s\n", thelink);
8290     if (thetitle != NULL) {
8291 	fprintf(fp, "THE_TITLE:%s\n", thetitle);
8292     }
8293 
8294     for (;; line = line->next) {
8295 	if (!first && line->data[0] != LY_SOFT_NEWLINE)
8296 	    fputc('\n', fp);
8297 	first = FALSE;
8298 	write_offset(fp, line);
8299 
8300 	/*
8301 	 * Add data.
8302 	 */
8303 	limit = TrimmedLength(line->data);
8304 	for (i = 0; i < limit; i++) {
8305 	    int ch = UCH(line->data[i]);
8306 
8307 	    if (!IsSpecialAttrChar(ch)) {
8308 		fputc(ch, fp);
8309 	    } else if (ch == LY_SOFT_HYPHEN &&
8310 		       (i + 1) >= limit) {	/* last char on line */
8311 		write_hyphen(fp);
8312 	    }
8313 	}
8314 
8315 	if (!HTMainText || (line == HTMainText->last_line)) {
8316 	    break;
8317 	}
8318     }
8319     fputc('\n', fp);
8320 
8321     /*
8322      * Add the References list if appropriate
8323      */
8324     if ((no_list == FALSE) &&
8325 	links_are_numbered()) {
8326 	printlist(fp, FALSE);
8327     }
8328 #ifdef VMS
8329     HadVMSInterrupt = FALSE;
8330 #endif /* VMS */
8331 }
8332 
adjust_search_result(DocInfo * doc,int tentative_result,int start_line)8333 static void adjust_search_result(DocInfo *doc, int tentative_result,
8334 				 int start_line)
8335 {
8336     if (tentative_result > 0) {
8337 	int anch_line = -1;
8338 	TextAnchor *a;
8339 	int nl_closest = -1;
8340 	int goal = SEARCH_GOAL_LINE;
8341 	int max_offset;
8342 	BOOL on_screen = (BOOL) (tentative_result > HTMainText->top_of_screen &&
8343 				 tentative_result <= HTMainText->top_of_screen +
8344 				 display_lines);
8345 
8346 	if (goal < 1)
8347 	    goal = 1;
8348 	else if (goal > display_lines)
8349 	    goal = display_lines;
8350 	max_offset = goal - 1;
8351 
8352 	if (on_screen && nlinks > 0) {
8353 	    int i;
8354 
8355 	    for (i = 0; i < nlinks; i++) {
8356 		if (doc->line + links[i].ly - 1 <= tentative_result)
8357 		    nl_closest = i;
8358 		if (doc->line + links[i].ly - 1 >= tentative_result)
8359 		    break;
8360 	    }
8361 	    if (nl_closest >= 0 &&
8362 		doc->line + links[nl_closest].ly - 1 == tentative_result) {
8363 		www_search_result = doc->line;
8364 		doc->link = nl_closest;
8365 		return;
8366 	    }
8367 	}
8368 
8369 	/* find last anchor before or on target line */
8370 	for (a = HTMainText->first_anchor;
8371 	     a && a->line_num <= tentative_result - 1; a = a->next) {
8372 	    anch_line = a->line_num + 1;
8373 	}
8374 	/* position such that the anchor found is on first screen line,
8375 	   if it is not too far above the target line; but also try to
8376 	   make sure we move forward. */
8377 	if (anch_line >= 0 &&
8378 	    anch_line >= tentative_result - max_offset &&
8379 	    (anch_line > start_line ||
8380 	     tentative_result <= HTMainText->top_of_screen)) {
8381 	    www_search_result = anch_line;
8382 	} else if (tentative_result - start_line > 0 &&
8383 		   tentative_result - (start_line + 1) <= max_offset) {
8384 	    www_search_result = start_line + 1;
8385 	} else if (tentative_result > HTMainText->top_of_screen &&
8386 		   tentative_result <= start_line &&	/* have wrapped */
8387 		   tentative_result <= HTMainText->top_of_screen + goal) {
8388 	    www_search_result = HTMainText->top_of_screen + 1;
8389 	} else if (tentative_result <= goal)
8390 	    www_search_result = 1;
8391 	else
8392 	    www_search_result = tentative_result - max_offset;
8393 	if (www_search_result == doc->line) {
8394 	    if (nl_closest >= 0) {
8395 		doc->link = nl_closest;
8396 		return;
8397 	    }
8398 	}
8399     }
8400 }
8401 
anchor_has_target(TextAnchor * a,char * target)8402 static BOOL anchor_has_target(TextAnchor *a, char *target)
8403 {
8404     OptionType *option;
8405     char *stars = NULL, *sp;
8406     const char *cp;
8407     int count;
8408 
8409     /*
8410      * Search the hightext strings, taking the LYcase_sensitive setting into
8411      * account.  -FM
8412      */
8413     for (count = 0;; ++count) {
8414 	if ((cp = LYGetHiTextStr(a, count)) == NULL)
8415 	    break;
8416 	if (LYno_attr_strstr(cp, target))
8417 	    return TRUE;
8418     }
8419 
8420     /*
8421      * Search the relevant form fields, taking the
8422      * LYcase_sensitive setting into account.  -FM
8423      */
8424     if ((a->input_field != NULL && a->input_field->value != NULL) &&
8425 	a->input_field->type != F_HIDDEN_TYPE) {
8426 	if (a->input_field->type == F_PASSWORD_TYPE) {
8427 	    /*
8428 	     * Check the actual, hidden password, and then
8429 	     * the displayed string.  -FM
8430 	     */
8431 	    if (LYno_attr_strstr(a->input_field->value, target)) {
8432 		return TRUE;
8433 	    }
8434 	    StrAllocCopy(stars, a->input_field->value);
8435 	    for (sp = stars; *sp != '\0'; sp++)
8436 		*sp = '*';
8437 	    if (LYno_attr_strstr(stars, target)) {
8438 		FREE(stars);
8439 		return TRUE;
8440 	    }
8441 	    FREE(stars);
8442 	} else if (a->input_field->type == F_OPTION_LIST_TYPE) {
8443 	    /*
8444 	     * Search the option strings that are displayed
8445 	     * when the popup is invoked.  -FM
8446 	     */
8447 	    option = a->input_field->select_list;
8448 	    while (option != NULL) {
8449 		if (LYno_attr_strstr(option->name, target)) {
8450 		    return TRUE;
8451 		}
8452 		option = option->next;
8453 	    }
8454 	} else if (a->input_field->type == F_RADIO_TYPE) {
8455 	    /*
8456 	     * Search for checked or unchecked parens.  -FM
8457 	     */
8458 	    if (a->input_field->num_value) {
8459 		cp = checked_radio;
8460 	    } else {
8461 		cp = unchecked_radio;
8462 	    }
8463 	    if (LYno_attr_strstr(cp, target)) {
8464 		return TRUE;
8465 	    }
8466 	} else if (a->input_field->type == F_CHECKBOX_TYPE) {
8467 	    /*
8468 	     * Search for checked or unchecked square brackets.  -FM
8469 	     */
8470 	    if (a->input_field->num_value) {
8471 		cp = checked_box;
8472 	    } else {
8473 		cp = unchecked_box;
8474 	    }
8475 	    if (LYno_attr_strstr(cp, target)) {
8476 		return TRUE;
8477 	    }
8478 	} else {
8479 	    /*
8480 	     * Check the values intended for display.  May have been found
8481 	     * already via the hightext search, but make sure here that the
8482 	     * entire value is searched.  -FM
8483 	     */
8484 	    if (LYno_attr_strstr(a->input_field->value, target)) {
8485 		return TRUE;
8486 	    }
8487 	}
8488     }
8489     return FALSE;
8490 }
8491 
line_num_to_anchor(int line_num)8492 static TextAnchor *line_num_to_anchor(int line_num)
8493 {
8494     TextAnchor *a;
8495 
8496     if (HTMainText != 0) {
8497 	a = HTMainText->first_anchor;
8498 	while (a != 0 && a->line_num < line_num) {
8499 	    a = a->next;
8500 	}
8501     } else {
8502 	a = 0;
8503     }
8504     return a;
8505 }
8506 
line_num_in_text(HText * text,HTLine * line)8507 static int line_num_in_text(HText *text, HTLine *line)
8508 {
8509     int result = 1;
8510     HTLine *temp = FirstHTLine(text);
8511 
8512     while (temp != line) {
8513 	temp = temp->next;
8514 	++result;
8515     }
8516     return result;
8517 }
8518 
8519 /* Computes the 'prev' pointers on demand, and returns the one for the given
8520  * anchor.
8521  */
get_prev_anchor(TextAnchor * a)8522 static TextAnchor *get_prev_anchor(TextAnchor *a)
8523 {
8524     TextAnchor *p, *q;
8525 
8526     if (a->prev == 0) {
8527 	if ((p = HTMainText->first_anchor) != 0) {
8528 	    while ((q = p->next) != 0) {
8529 		q->prev = p;
8530 		p = q;
8531 	    }
8532 	}
8533     }
8534     return a->prev;
8535 }
8536 
www_search_forward(int start_line,DocInfo * doc,char * target,HTLine * line,int count)8537 static int www_search_forward(int start_line,
8538 			      DocInfo *doc,
8539 			      char *target,
8540 			      HTLine *line,
8541 			      int count)
8542 {
8543     int wrapped = 0;
8544     TextAnchor *a = line_num_to_anchor(count - 1);
8545     int tentative_result = -1;
8546 
8547     for (;;) {
8548 	while ((a != NULL) && a->line_num == (count - 1)) {
8549 	    if (a->show_anchor &&
8550 		!(a->link_type == INPUT_ANCHOR
8551 		  && a->input_field->type == F_HIDDEN_TYPE)) {
8552 		if (anchor_has_target(a, target)) {
8553 		    adjust_search_result(doc, count, start_line);
8554 		    return 1;
8555 		}
8556 	    }
8557 	    a = a->next;
8558 	}
8559 
8560 	if (LYno_attr_strstr(line->data, target)) {
8561 	    tentative_result = count;
8562 	    break;
8563 	} else if ((count == start_line && wrapped) || wrapped > 1) {
8564 	    HTUserMsg2(STRING_NOT_FOUND, target);
8565 	    return -1;
8566 	} else if (line == HTMainText->last_line) {
8567 	    count = 0;
8568 	    wrapped++;
8569 	}
8570 	line = line->next;
8571 	count++;
8572     }
8573     if (tentative_result > 0) {
8574 	adjust_search_result(doc, tentative_result, start_line);
8575     }
8576     return 0;
8577 }
8578 
www_search_backward(int start_line,DocInfo * doc,char * target,HTLine * line,int count)8579 static int www_search_backward(int start_line,
8580 			       DocInfo *doc,
8581 			       char *target,
8582 			       HTLine *line,
8583 			       int count)
8584 {
8585     int wrapped = 0;
8586     TextAnchor *a = line_num_to_anchor(count - 1);
8587     int tentative_result = -1;
8588 
8589     for (;;) {
8590 	while ((a != NULL) && a->line_num == (count - 1)) {
8591 	    if (a->show_anchor &&
8592 		!(a->link_type == INPUT_ANCHOR
8593 		  && a->input_field->type == F_HIDDEN_TYPE)) {
8594 		if (anchor_has_target(a, target)) {
8595 		    adjust_search_result(doc, count, start_line);
8596 		    return 1;
8597 		}
8598 	    }
8599 	    a = get_prev_anchor(a);
8600 	}
8601 
8602 	if (LYno_attr_strstr(line->data, target)) {
8603 	    tentative_result = count;
8604 	    break;
8605 	} else if ((count == start_line && wrapped) || wrapped > 1) {
8606 	    HTUserMsg2(STRING_NOT_FOUND, target);
8607 	    return -1;
8608 	} else if (line == FirstHTLine(HTMainText)) {
8609 	    count = line_num_in_text(HTMainText, LastHTLine(HTMainText)) + 1;
8610 	    wrapped++;
8611 	}
8612 	line = line->prev;
8613 	count--;
8614     }
8615     if (tentative_result > 0) {
8616 	adjust_search_result(doc, tentative_result, start_line);
8617     }
8618     return 0;
8619 }
8620 
www_user_search(int start_line,DocInfo * doc,char * target,int direction)8621 void www_user_search(int start_line,
8622 		     DocInfo *doc,
8623 		     char *target,
8624 		     int direction)
8625 {
8626     HTLine *line;
8627     int count;
8628 
8629     if (!HTMainText) {
8630 	return;
8631     }
8632 
8633     /*
8634      * Advance to the start line.
8635      */
8636     line = FirstHTLine(HTMainText);
8637     if (start_line + direction > 0) {
8638 	for (count = 1;
8639 	     count < start_line + direction;
8640 	     line = line->next, count++) {
8641 	    if (line == HTMainText->last_line) {
8642 		line = FirstHTLine(HTMainText);
8643 		count = 1;
8644 		break;
8645 	    }
8646 	}
8647     } else {
8648 	line = HTMainText->last_line;
8649 	count = line_num_in_text(HTMainText, line);
8650     }
8651 
8652     if (direction >= 0)
8653 	www_search_forward(start_line, doc, target, line, count);
8654     else
8655 	www_search_backward(start_line, doc, target, line, count);
8656 }
8657 
user_message(const char * message,const char * argument)8658 void user_message(const char *message,
8659 		  const char *argument)
8660 {
8661     if (message == NULL) {
8662 	mustshow = FALSE;
8663     } else {
8664 	char *temp = NULL;
8665 
8666 	HTSprintf0(&temp, message, NonNull(argument));
8667 	statusline(temp);
8668 	FREE(temp);
8669     }
8670 }
8671 
8672 /*
8673  * HText_getOwner returns the owner of the
8674  * current document.
8675  */
HText_getOwner(void)8676 const char *HText_getOwner(void)
8677 {
8678     return (HTMainText ?
8679 	    HTAnchor_owner(HTMainText->node_anchor) : 0);
8680 }
8681 
8682 /*
8683  * HText_setMainTextOwner sets the owner for the
8684  * current document.
8685  */
HText_setMainTextOwner(const char * owner)8686 void HText_setMainTextOwner(const char *owner)
8687 {
8688     if (!HTMainText)
8689 	return;
8690 
8691     HTAnchor_setOwner(HTMainText->node_anchor, owner);
8692 }
8693 
8694 /*
8695  * HText_getRevTitle returns the RevTitle element of the
8696  * current document, used as the subject for mailto comments
8697  * to the owner.
8698  */
HText_getRevTitle(void)8699 const char *HText_getRevTitle(void)
8700 {
8701     return (HTMainText ?
8702 	    HTAnchor_RevTitle(HTMainText->node_anchor) : 0);
8703 }
8704 
8705 /*
8706  * HText_getContentBase returns the Content-Base header
8707  * of the current document.
8708  */
HText_getContentBase(void)8709 const char *HText_getContentBase(void)
8710 {
8711     return (HTMainText ?
8712 	    HTAnchor_content_base(HTMainText->node_anchor) : 0);
8713 }
8714 
8715 /*
8716  * HText_getContentLocation returns the Content-Location header
8717  * of the current document.
8718  */
HText_getContentLocation(void)8719 const char *HText_getContentLocation(void)
8720 {
8721     return (HTMainText ?
8722 	    HTAnchor_content_location(HTMainText->node_anchor) : 0);
8723 }
8724 
8725 /*
8726  * HText_getMessageID returns the Message-ID of the
8727  * current document.
8728  */
HText_getMessageID(void)8729 const char *HText_getMessageID(void)
8730 {
8731     return (HTMainText ?
8732 	    HTAnchor_messageID(HTMainText->node_anchor) : NULL);
8733 }
8734 
HTuncache_current_document(void)8735 void HTuncache_current_document(void)
8736 {
8737     /*
8738      * Should remove current document from memory.
8739      */
8740     if (HTMainText) {
8741 	HTParentAnchor *htmain_anchor = HTMainText->node_anchor;
8742 
8743 	if (htmain_anchor) {
8744 	    if (!(HTOutputFormat && HTOutputFormat == WWW_SOURCE)) {
8745 		FREE(htmain_anchor->UCStages);
8746 	    }
8747 	}
8748 	CTRACE((tfp, "\nHTuncache.. freeing document for '%s'%s\n",
8749 		((htmain_anchor &&
8750 		  htmain_anchor->address) ?
8751 		 htmain_anchor->address : "unknown anchor"),
8752 		((htmain_anchor &&
8753 		  htmain_anchor->post_data)
8754 		 ? " with POST data"
8755 		 : "")));
8756 	HTList_removeObject(loaded_texts, HTMainText);
8757 	HText_free(HTMainText);
8758 	HTMainText = NULL;
8759     } else {
8760 	CTRACE((tfp, "HTuncache.. HTMainText already is NULL!\n"));
8761     }
8762 }
8763 
8764 /*
8765  * This magic FREE(anchor->UCStages) call
8766  * stolen from HTuncache_current_document() above.
8767  */
magicUncache(void)8768 static void magicUncache(void)
8769 {
8770     if (!(HTOutputFormat && HTOutputFormat == WWW_SOURCE)) {
8771 	FREE(HTMainAnchor->UCStages);
8772     }
8773     /* avoid null-reference later */
8774     if (!HTOutputFormat)
8775 	HTOutputFormat = WWW_SOURCE;
8776 }
8777 
8778 #ifdef USE_SOURCE_CACHE
8779 
8780 /* dummy - kw */
8781 static HTProtocol scm =
8782 {
8783     "source-cache-mem", 0, 0
8784 };
8785 
useSourceCache(void)8786 static BOOLEAN useSourceCache(void)
8787 {
8788     BOOLEAN result = FALSE;
8789 
8790     if (LYCacheSource == SOURCE_CACHE_FILE) {
8791 	result = (BOOLEAN) (HTMainAnchor->source_cache_file != 0);
8792 	CTRACE((tfp, "SourceCache: file-cache%s found\n",
8793 		result ? "" : " not"));
8794     }
8795     return result;
8796 }
8797 
useMemoryCache(void)8798 static BOOLEAN useMemoryCache(void)
8799 {
8800     BOOLEAN result = FALSE;
8801 
8802     if (LYCacheSource == SOURCE_CACHE_MEMORY) {
8803 	result = (BOOLEAN) (HTMainAnchor->source_cache_chunk != 0);
8804 	CTRACE((tfp, "SourceCache: memory-cache%s found\n",
8805 		result ? "" : " not"));
8806     }
8807     return result;
8808 }
8809 
HTreparse_document(void)8810 BOOLEAN HTreparse_document(void)
8811 {
8812     BOOLEAN ok = FALSE;
8813 
8814     if (!HTMainAnchor || LYCacheSource == SOURCE_CACHE_NONE) {
8815 	CTRACE((tfp, "HTreparse_document returns FALSE\n"));
8816     } else if (useSourceCache()) {
8817 	FILE *fp;
8818 	HTFormat format;
8819 	int ret;
8820 
8821 	CTRACE((tfp, "SourceCache: Reparsing file %s\n",
8822 		HTMainAnchor->source_cache_file));
8823 
8824 	magicUncache();
8825 
8826 	/*
8827 	 * This is more or less copied out of HTLoadFile(), except we don't
8828 	 * get a content encoding.  This may be overkill.  -dsb
8829 	 */
8830 	if (HTMainAnchor->content_type) {
8831 	    format = HTAtom_for(HTMainAnchor->content_type);
8832 	} else {
8833 	    format = HTFileFormat(HTMainAnchor->source_cache_file, NULL, NULL);
8834 	    format = HTCharsetFormat(format, HTMainAnchor,
8835 				     UCLYhndl_for_unspec);
8836 	    /* not UCLYhndl_HTFile_for_unspec - we are talking about remote
8837 	     * documents...
8838 	     */
8839 	}
8840 	CTRACE((tfp, "  Content type is \"%s\"\n", format->name));
8841 
8842 	fp = fopen(HTMainAnchor->source_cache_file, "r");
8843 	if (!fp) {
8844 	    CTRACE((tfp, "  Cannot read file %s\n", HTMainAnchor->source_cache_file));
8845 	    (void) LYRemoveTemp(HTMainAnchor->source_cache_file);
8846 	    FREE(HTMainAnchor->source_cache_file);
8847 	} else {
8848 
8849 	    if (HText_HaveUserChangedForms(HTMainText)) {
8850 		/*
8851 		 * Issue a warning.  Will not restore changed forms, currently.
8852 		 */
8853 		HTAlert(RELOADING_FORM);
8854 	    }
8855 	    /* Set HTMainAnchor->protocol or HTMainAnchor->physical to convince
8856 	     * the SourceCacheWriter to not regenerate the cache file (which
8857 	     * would be an unnecessary "loop"). - kw
8858 	     */
8859 	    HTAnchor_setProtocol(HTMainAnchor, &HTFile);
8860 	    ret = HTParseFile(format, HTOutputFormat, HTMainAnchor, fp, NULL);
8861 	    LYCloseInput(fp);
8862 	    if (ret == HT_PARTIAL_CONTENT) {
8863 		HTInfoMsg(gettext("Loading incomplete."));
8864 		CTRACE((tfp,
8865 			"SourceCache: `%s' has been accessed, partial content.\n",
8866 			HTLoadedDocumentURL()));
8867 	    }
8868 	    ok = (BOOL) (ret == HT_LOADED || ret == HT_PARTIAL_CONTENT);
8869 
8870 	    CTRACE((tfp, "Reparse file %s\n", (ok ? "succeeded" : "failed")));
8871 	}
8872     } else if (useMemoryCache()) {
8873 	HTFormat format = WWW_HTML;
8874 	int ret;
8875 
8876 	CTRACE((tfp, "SourceCache: Reparsing from memory chunk %p\n",
8877 		(void *) HTMainAnchor->source_cache_chunk));
8878 
8879 	magicUncache();
8880 
8881 	if (HTMainAnchor->content_type) {
8882 	    format = HTAtom_for(HTMainAnchor->content_type);
8883 	} else {
8884 	    /*
8885 	     * This is only done to make things aligned with SOURCE_CACHE_NONE
8886 	     * and SOURCE_CACHE_FILE when switching to source mode since the
8887 	     * original document's charset will be LYPushAssumed() and then
8888 	     * LYPopAssumed().  See LYK_SOURCE in mainloop if you change
8889 	     * something here.  No user-visible benefits, seems just '=' Info
8890 	     * Page will show source's effective charset as "(assumed)".
8891 	     */
8892 	    format = HTCharsetFormat(format, HTMainAnchor,
8893 				     UCLYhndl_for_unspec);
8894 	}
8895 	/* not UCLYhndl_HTFile_for_unspec - we are talking about remote documents... */
8896 
8897 	if (HText_HaveUserChangedForms(HTMainText)) {
8898 	    /*
8899 	     * Issue a warning.  Will not restore changed forms, currently.
8900 	     */
8901 	    HTAlert(RELOADING_FORM);
8902 	}
8903 	/* Set HTMainAnchor->protocol or HTMainAnchor->physical to convince
8904 	 * the SourceCacheWriter to not regenerate the cache chunk (which
8905 	 * would be an unnecessary "loop"). - kw
8906 	 */
8907 	HTAnchor_setProtocol(HTMainAnchor, &scm);	/* cheating -
8908 							   anything != &HTTP or &HTTPS would do - kw */
8909 	ret = HTParseMem(format, HTOutputFormat, HTMainAnchor,
8910 			 HTMainAnchor->source_cache_chunk, NULL);
8911 	ok = (BOOL) (ret == HT_LOADED);
8912 
8913 	CTRACE((tfp, "Reparse memory %s\n", (ok ? "succeeded" : "failed")));
8914     }
8915 
8916     return ok;
8917 }
8918 
HTcan_reparse_document(void)8919 BOOLEAN HTcan_reparse_document(void)
8920 {
8921     BOOLEAN result = FALSE;
8922 
8923     if (!HTMainAnchor || LYCacheSource == SOURCE_CACHE_NONE) {
8924 	result = FALSE;
8925     } else if (useSourceCache()) {
8926 	result = LYCanReadFile(HTMainAnchor->source_cache_file);
8927     } else if (useMemoryCache()) {
8928 	result = TRUE;
8929     }
8930 
8931     CTRACE((tfp, "HTcan_reparse_document -> %d\n", result));
8932     return result;
8933 }
8934 
trace_setting_change(const char * name,int prev_setting,int new_setting)8935 static void trace_setting_change(const char *name,
8936 				 int prev_setting,
8937 				 int new_setting)
8938 {
8939     if (prev_setting != new_setting)
8940 	CTRACE((tfp,
8941 		"HTdocument_settings_changed: %s setting has changed (was %d, now %d)\n",
8942 		name, prev_setting, new_setting));
8943 }
8944 
HTdocument_settings_changed(void)8945 BOOLEAN HTdocument_settings_changed(void)
8946 {
8947     /*
8948      * Annoying Hack(TM):  If we don't have a source cache, we can't
8949      * reparse anyway, so pretend the settings haven't changed.
8950      */
8951     if (!HTMainText || !HTcan_reparse_document())
8952 	return FALSE;
8953 
8954     if (TRACE) {
8955 	/*
8956 	 * If we're tracing, note everying that has changed.
8957 	 */
8958 	trace_setting_change("CLICKABLE_IMAGES",
8959 			     HTMainText->clickable_images, clickable_images);
8960 	trace_setting_change("PSEUDO_INLINE_ALTS",
8961 			     HTMainText->pseudo_inline_alts,
8962 			     pseudo_inline_alts);
8963 	trace_setting_change("VERBOSE_IMG",
8964 			     HTMainText->verbose_img,
8965 			     verbose_img);
8966 	trace_setting_change("RAW_MODE", HTMainText->raw_mode,
8967 			     LYUseDefaultRawMode);
8968 	trace_setting_change("HISTORICAL_COMMENTS",
8969 			     HTMainText->historical_comments,
8970 			     historical_comments);
8971 	trace_setting_change("MINIMAL_COMMENTS",
8972 			     HTMainText->minimal_comments, minimal_comments);
8973 	trace_setting_change("SOFT_DQUOTES",
8974 			     HTMainText->soft_dquotes, soft_dquotes);
8975 	trace_setting_change("OLD_DTD", HTMainText->old_dtd, Old_DTD);
8976 	trace_setting_change("KEYPAD_MODE",
8977 			     HTMainText->keypad_mode, keypad_mode);
8978 	if (HTMainText->disp_lines != LYlines || HTMainText->disp_cols != DISPLAY_COLS)
8979 	    CTRACE((tfp,
8980 		    "HTdocument_settings_changed: Screen size has changed (was %dx%d, now %dx%d)\n",
8981 		    HTMainText->disp_cols,
8982 		    HTMainText->disp_lines,
8983 		    DISPLAY_COLS,
8984 		    LYlines));
8985     }
8986 
8987     return (BOOLEAN) (HTMainText->clickable_images != clickable_images ||
8988 		      HTMainText->pseudo_inline_alts != pseudo_inline_alts ||
8989 		      HTMainText->verbose_img != verbose_img ||
8990 		      HTMainText->raw_mode != LYUseDefaultRawMode ||
8991 		      HTMainText->historical_comments != historical_comments ||
8992 		      (HTMainText->minimal_comments != minimal_comments &&
8993 		       !historical_comments) ||
8994 		      HTMainText->soft_dquotes != soft_dquotes ||
8995 		      HTMainText->old_dtd != Old_DTD ||
8996 		      HTMainText->keypad_mode != keypad_mode ||
8997 		      HTMainText->disp_cols != DISPLAY_COLS);
8998 }
8999 #endif
9000 
HTisDocumentSource(void)9001 int HTisDocumentSource(void)
9002 {
9003     return (HTMainText != 0) ? HTMainText->source : FALSE;
9004 }
9005 
HTLoadedDocumentURL(void)9006 const char *HTLoadedDocumentURL(void)
9007 {
9008     if (!HTMainText)
9009 	return ("");
9010 
9011     if (HTMainText->node_anchor && HTMainText->node_anchor->address)
9012 	return (HTMainText->node_anchor->address);
9013     else
9014 	return ("");
9015 }
9016 
HTLoadedDocumentPost_data(void)9017 bstring *HTLoadedDocumentPost_data(void)
9018 {
9019     if (HTMainText
9020 	&& HTMainText->node_anchor
9021 	&& HTMainText->node_anchor->post_data)
9022 	return (HTMainText->node_anchor->post_data);
9023     else
9024 	return (0);
9025 }
9026 
HTLoadedDocumentTitle(void)9027 const char *HTLoadedDocumentTitle(void)
9028 {
9029     if (!HTMainText)
9030 	return ("");
9031 
9032     if (HTMainText->node_anchor && HTMainText->node_anchor->title)
9033 	return (HTMainText->node_anchor->title);
9034     else
9035 	return ("");
9036 }
9037 
HTLoadedDocumentIsHEAD(void)9038 BOOLEAN HTLoadedDocumentIsHEAD(void)
9039 {
9040     if (!HTMainText)
9041 	return (FALSE);
9042 
9043     if (HTMainText->node_anchor && HTMainText->node_anchor->isHEAD)
9044 	return (HTMainText->node_anchor->isHEAD);
9045     else
9046 	return (FALSE);
9047 }
9048 
HTLoadedDocumentIsSafe(void)9049 BOOLEAN HTLoadedDocumentIsSafe(void)
9050 {
9051     if (!HTMainText)
9052 	return (FALSE);
9053 
9054     if (HTMainText->node_anchor && HTMainText->node_anchor->safe)
9055 	return (HTMainText->node_anchor->safe);
9056     else
9057 	return (FALSE);
9058 }
9059 
HTLoadedDocumentCharset(void)9060 const char *HTLoadedDocumentCharset(void)
9061 {
9062     const char *result = NULL;
9063 
9064     if (HTMainText &&
9065 	HTMainText->node_anchor) {
9066 	result = HTMainText->node_anchor->charset;
9067     }
9068 
9069     return result;
9070 }
9071 
HTLoadedDocumentEightbit(void)9072 BOOL HTLoadedDocumentEightbit(void)
9073 {
9074     if (!HTMainText)
9075 	return (NO);
9076     else
9077 	return (HTMainText->have_8bit_chars);
9078 }
9079 
HText_setNodeAnchorBookmark(const char * bookmark)9080 void HText_setNodeAnchorBookmark(const char *bookmark)
9081 {
9082     if (!HTMainText)
9083 	return;
9084 
9085     if (HTMainText->node_anchor)
9086 	HTAnchor_setBookmark(HTMainText->node_anchor, bookmark);
9087 }
9088 
HTLoadedDocumentBookmark(void)9089 const char *HTLoadedDocumentBookmark(void)
9090 {
9091     if (!HTMainText)
9092 	return (NULL);
9093 
9094     if (HTMainText->node_anchor && HTMainText->node_anchor->bookmark)
9095 	return (HTMainText->node_anchor->bookmark);
9096     else
9097 	return (NULL);
9098 }
9099 
HText_LastLineSize(HText * text,int IgnoreSpaces)9100 int HText_LastLineSize(HText *text, int IgnoreSpaces)
9101 {
9102     if (!text || !text->last_line || !text->last_line->size)
9103 	return 0;
9104     return HText_TrueLineSize(text->last_line, text, IgnoreSpaces);
9105 }
9106 
HText_LastLineEmpty(HText * text,int IgnoreSpaces)9107 BOOL HText_LastLineEmpty(HText *text, int IgnoreSpaces)
9108 {
9109     if (!text || !text->last_line || !text->last_line->size)
9110 	return TRUE;
9111     return HText_TrueEmptyLine(text->last_line, text, IgnoreSpaces);
9112 }
9113 
HText_LastLineOffset(HText * text)9114 int HText_LastLineOffset(HText *text)
9115 {
9116     if (!text || !text->last_line)
9117 	return 0;
9118     return text->last_line->offset;
9119 }
9120 
HText_PreviousLineSize(HText * text,int IgnoreSpaces)9121 int HText_PreviousLineSize(HText *text, int IgnoreSpaces)
9122 {
9123     HTLine *line;
9124 
9125     if (!text || !text->last_line)
9126 	return 0;
9127     if (!(line = text->last_line->prev))
9128 	return 0;
9129     return HText_TrueLineSize(line, text, IgnoreSpaces);
9130 }
9131 
HText_PreviousLineEmpty(HText * text,int IgnoreSpaces)9132 BOOL HText_PreviousLineEmpty(HText *text, int IgnoreSpaces)
9133 {
9134     HTLine *line;
9135 
9136     if (!text || !text->last_line)
9137 	return TRUE;
9138     if (!(line = text->last_line->prev))
9139 	return TRUE;
9140     return HText_TrueEmptyLine(line, text, IgnoreSpaces);
9141 }
9142 
9143 /*
9144  * Compute the "true" line size.
9145  */
HText_TrueLineSize(HTLine * line,HText * text,int IgnoreSpaces)9146 static int HText_TrueLineSize(HTLine *line, HText *text, int IgnoreSpaces)
9147 {
9148     size_t i;
9149     int true_size = 0;
9150 
9151     if (!(line && line->size))
9152 	return 0;
9153 
9154     if (IgnoreSpaces) {
9155 	for (i = 0; i < line->size; i++) {
9156 	    if (!IsSpecialAttrChar(UCH(line->data[i])) &&
9157 		IS_UTF8_EXTRA(line->data[i]) &&
9158 		!isspace(UCH(line->data[i])) &&
9159 		UCH(line->data[i]) != HT_NON_BREAK_SPACE &&
9160 		UCH(line->data[i]) != HT_EN_SPACE) {
9161 		true_size++;
9162 	    }
9163 	}
9164     } else {
9165 	for (i = 0; i < line->size; i++) {
9166 	    if (!IsSpecialAttrChar(line->data[i]) &&
9167 		IS_UTF8_EXTRA(line->data[i])) {
9168 		true_size++;
9169 	    }
9170 	}
9171     }
9172     return true_size;
9173 }
9174 
9175 /*
9176  * Tell if the line is really empty.  This is invoked much more often than
9177  * HText_TrueLineSize(), and most lines are not empty.  So it is faster to
9178  * do this check than to check if the line size happens to be zero.
9179  */
HText_TrueEmptyLine(HTLine * line,HText * text,int IgnoreSpaces)9180 static BOOL HText_TrueEmptyLine(HTLine *line, HText *text, int IgnoreSpaces)
9181 {
9182     size_t i;
9183 
9184     if (!(line && line->size))
9185 	return TRUE;
9186 
9187     if (IgnoreSpaces) {
9188 	for (i = 0; i < line->size; i++) {
9189 	    if (!IsSpecialAttrChar(UCH(line->data[i])) &&
9190 		IS_UTF8_EXTRA(line->data[i]) &&
9191 		!isspace(UCH(line->data[i])) &&
9192 		UCH(line->data[i]) != HT_NON_BREAK_SPACE &&
9193 		UCH(line->data[i]) != HT_EN_SPACE) {
9194 		return FALSE;
9195 	    }
9196 	}
9197     } else {
9198 	for (i = 0; i < line->size; i++) {
9199 	    if (!IsSpecialAttrChar(line->data[i]) &&
9200 		IS_UTF8_EXTRA(line->data[i])) {
9201 		return FALSE;
9202 	    }
9203 	}
9204     }
9205     return TRUE;
9206 }
9207 
HText_NegateLineOne(HText * text)9208 void HText_NegateLineOne(HText *text)
9209 {
9210     if (text) {
9211 	text->in_line_1 = NO;
9212     }
9213     return;
9214 }
9215 
HText_inLineOne(HText * text)9216 BOOL HText_inLineOne(HText *text)
9217 {
9218     if (text) {
9219 	return text->in_line_1;
9220     }
9221     return YES;
9222 }
9223 
9224 /*
9225  * This function is for removing the first of two
9226  * successive blank lines.  It should be called after
9227  * checking the situation with HText_LastLineSize()
9228  * and HText_PreviousLineSize().  Any characters in
9229  * the removed line (i.e., control characters, or it
9230  * wouldn't have tested blank) should have been
9231  * reiterated by split_line() in the retained blank
9232  * line.  -FM
9233  */
HText_RemovePreviousLine(HText * text)9234 void HText_RemovePreviousLine(HText *text)
9235 {
9236     HTLine *line, *previous;
9237 
9238     if (!(text && text->Lines > 1))
9239 	return;
9240 
9241     line = text->last_line->prev;
9242     previous = line->prev;
9243     previous->next = text->last_line;
9244     text->last_line->prev = previous;
9245     text->Lines--;
9246     freeHTLine(text, line);
9247 }
9248 
9249 /*
9250  * NOTE:  This function presently is correct only if the
9251  *	  alignment is HT_LEFT.  The offset is still zero,
9252  *	  because that's not determined for HT_CENTER or
9253  *	  HT_RIGHT until subsequent characters are received
9254  *	  and split_line() is called. -FM
9255  */
HText_getCurrentColumn(HText * text)9256 int HText_getCurrentColumn(HText *text)
9257 {
9258     int column = 0;
9259     BOOL IgnoreSpaces = FALSE;
9260 
9261     if (text) {
9262 	column = ((text->in_line_1
9263 		   ? (int) text->style->indent1st
9264 		   : (int) text->style->leftIndent)
9265 		  + (int) text->last_line->offset
9266 		  + HText_LastLineSize(text, IgnoreSpaces));
9267     }
9268     return column;
9269 }
9270 
HText_getMaximumColumn(HText * text)9271 int HText_getMaximumColumn(HText *text)
9272 {
9273     int column = DISPLAY_COLS;
9274 
9275     if (text) {
9276 	column -= (int) text->style->rightIndent;
9277     }
9278     return column;
9279 }
9280 
9281 /*
9282  * NOTE:  This function uses HText_getCurrentColumn() which
9283  *	  presently is correct only if the alignment is
9284  *	  HT_LEFT. -FM
9285  */
HText_setTabID(HText * text,const char * name)9286 void HText_setTabID(HText *text, const char *name)
9287 {
9288     HTTabID *Tab = NULL;
9289     HTList *cur = text->tabs;
9290     HTList *last = NULL;
9291 
9292     if (!text || isEmpty(name))
9293 	return;
9294 
9295     if (!cur) {
9296 	cur = text->tabs = HTList_new();
9297     } else {
9298 	while (NULL != (Tab = (HTTabID *) HTList_nextObject(cur))) {
9299 	    if (Tab->name && !strcmp(Tab->name, name))
9300 		return;		/* Already set.  Keep the first value. */
9301 	    last = cur;
9302 	}
9303 	if (last)
9304 	    cur = last;
9305     }
9306     if (!Tab) {			/* New name.  Create a new node */
9307 	Tab = typecalloc(HTTabID);
9308 	if (Tab == NULL)
9309 	    outofmem(__FILE__, "HText_setTabID");
9310 	HTList_addObject(cur, Tab);
9311 	StrAllocCopy(Tab->name, name);
9312     }
9313 
9314     assert(Tab != NULL);
9315     Tab->column = HText_getCurrentColumn(text);
9316     return;
9317 }
9318 
HText_getTabIDColumn(HText * text,const char * name)9319 int HText_getTabIDColumn(HText *text, const char *name)
9320 {
9321     int column = 0;
9322     HTTabID *Tab;
9323     HTList *cur = text->tabs;
9324 
9325     if (text && non_empty(name) && cur) {
9326 	while (NULL != (Tab = (HTTabID *) HTList_nextObject(cur))) {
9327 	    if (Tab->name && !strcmp(Tab->name, name))
9328 		break;
9329 	}
9330 	if (Tab)
9331 	    column = Tab->column;
9332     }
9333     return column;
9334 }
9335 
9336 /*
9337  * This function is for saving the address of a link
9338  * which had an attribute in the markup that resolved
9339  * to a URL (i.e., not just a NAME or ID attribute),
9340  * but was found in HText_endAnchor() to have no visible
9341  * content for use as a link name.  It loads the address
9342  * into text->hidden_links, whose count can be determined
9343  * via HText_HiddenLinks(), below.  The addresses can be
9344  * retrieved via HText_HiddenLinkAt(), below, based on
9345  * count.  -FM
9346  */
HText_AddHiddenLink(HText * text,TextAnchor * textanchor)9347 static void HText_AddHiddenLink(HText *text, TextAnchor *textanchor)
9348 {
9349     HTAnchor *dest;
9350 
9351     /*
9352      * Make sure we have an HText structure and anchor.  -FM
9353      */
9354     if (!(text && textanchor && textanchor->anchor))
9355 	return;
9356 
9357     /*
9358      * Create the hidden links list
9359      * if it hasn't been already.  -FM
9360      */
9361     if (text->hidden_links == NULL)
9362 	text->hidden_links = HTList_new();
9363 
9364     /*
9365      * Store the address, in reverse list order
9366      * so that first in will be first out on
9367      * retrievals.  -FM
9368      */
9369     if ((dest = HTAnchor_followLink(textanchor->anchor)) &&
9370 	(text->hiddenlinkflag != HIDDENLINKS_IGNORE ||
9371 	 HTList_isEmpty(text->hidden_links))) {
9372 	char *value = HTAnchor_address(dest);
9373 	BOOL ignore = FALSE;
9374 
9375 	if (unique_urls) {
9376 	    int cnt;
9377 	    char *check;
9378 
9379 	    for (cnt = 0;; ++cnt) {
9380 
9381 		check = (char *) HTList_objectAt(text->hidden_links, cnt);
9382 		if (check == 0)
9383 		    break;
9384 		if (!strcmp(check, value)) {
9385 		    ignore = TRUE;
9386 		    break;
9387 		}
9388 	    }
9389 	}
9390 	if (ignore) {
9391 	    FREE(value);
9392 	} else {
9393 	    HTList_appendObject(text->hidden_links, value);
9394 	}
9395     }
9396 
9397     return;
9398 }
9399 
9400 /*
9401  * This function returns the number of addresses
9402  * that are loaded in text->hidden_links.  -FM
9403  */
HText_HiddenLinkCount(HText * text)9404 int HText_HiddenLinkCount(HText *text)
9405 {
9406     int count = 0;
9407 
9408     if (text && text->hidden_links)
9409 	count = HTList_count((HTList *) text->hidden_links);
9410 
9411     return (count);
9412 }
9413 
9414 /*
9415  * This function returns the address, corresponding to
9416  * a hidden link, at the position (zero-based) in the
9417  * text->hidden_links list of the number argument.  -FM
9418  */
HText_HiddenLinkAt(HText * text,int number)9419 const char *HText_HiddenLinkAt(HText *text, int number)
9420 {
9421     char *href = NULL;
9422 
9423     if (text && text->hidden_links && number >= 0)
9424 	href = (char *) HTList_objectAt((HTList *) text->hidden_links, number);
9425 
9426     return (href);
9427 }
9428 
9429 /*
9430  * Form methods
9431  * These routines are used to build forms consisting
9432  * of input fields
9433  */
9434 static BOOLEAN HTFormDisabled = FALSE;
9435 static PerFormInfo *HTCurrentForm;
9436 
addFormAction(FormInfo * f)9437 static BOOLEAN addFormAction(FormInfo * f)
9438 {
9439     BOOLEAN result = FALSE;
9440 
9441     if (HTCurrentForm != NULL) {
9442 	result = TRUE;
9443 	f->submit_action = NULL;
9444 	StrAllocCopy(f->submit_action, HTCurrentForm->data.submit_action);
9445 	if (HTCurrentForm->data.submit_enctype != NULL)
9446 	    StrAllocCopy(f->submit_enctype, HTCurrentForm->data.submit_enctype);
9447 	if (HTCurrentForm->data.submit_title != NULL)
9448 	    StrAllocCopy(f->submit_title, HTCurrentForm->data.submit_title);
9449 	f->submit_method = HTCurrentForm->data.submit_method;
9450     }
9451     return result;
9452 }
9453 
HText_beginForm(char * action,char * method,char * enctype,char * title,const char * accept_cs)9454 void HText_beginForm(char *action,
9455 		     char *method,
9456 		     char *enctype,
9457 		     char *title,
9458 		     const char *accept_cs)
9459 {
9460     PerFormInfo *newform;
9461     int HTFormMethod = URL_GET_METHOD;
9462     char *HTFormAction = NULL;
9463     char *HTFormEnctype = NULL;
9464     char *HTFormTitle = NULL;
9465     char *HTFormAcceptCharset = NULL;
9466 
9467     HTFormNumber++;
9468 
9469     HTFormFields = 0;
9470     HTFormDisabled = FALSE;
9471 
9472     /*
9473      * Check the ACTION.  -FM
9474      */
9475     if (action != NULL) {
9476 	if (isMAILTO_URL(action)) {
9477 	    HTFormMethod = URL_MAIL_METHOD;
9478 	}
9479 	StrAllocCopy(HTFormAction, action);
9480     } else
9481 	StrAllocCopy(HTFormAction, HTLoadedDocumentURL());
9482 
9483     /*
9484      * Check the METHOD.  -FM
9485      */
9486     if (method != NULL && HTFormMethod != URL_MAIL_METHOD)
9487 	if (!strcasecomp(method, "post") || !strcasecomp(method, "pget"))
9488 	    HTFormMethod = URL_POST_METHOD;
9489 
9490     /*
9491      * Check the ENCTYPE.  -FM
9492      */
9493     if (non_empty(enctype)) {
9494 	StrAllocCopy(HTFormEnctype, enctype);
9495 	if (HTFormMethod != URL_MAIL_METHOD &&
9496 	    !strncasecomp(enctype, "multipart/form-data", 19))
9497 	    HTFormMethod = URL_POST_METHOD;
9498     } else {
9499 	FREE(HTFormEnctype);
9500     }
9501 
9502     /*
9503      * Check the TITLE.  -FM
9504      */
9505     if (non_empty(title))
9506 	StrAllocCopy(HTFormTitle, title);
9507     else
9508 	FREE(HTFormTitle);
9509 
9510     /*
9511      * Check for an ACCEPT_CHARSET.  If present, store it and
9512      * convert to lowercase and collapse spaces.  - kw
9513      */
9514     if (accept_cs != NULL) {
9515 	StrAllocCopy(HTFormAcceptCharset, accept_cs);
9516 	LYRemoveBlanks(HTFormAcceptCharset);
9517 	LYLowerCase(HTFormAcceptCharset);
9518     }
9519 
9520     /*
9521      * Create a new "PerFormInfo" structure to hold info on the current form.
9522      * This will be appended to the forms list kept by the HText object if and
9523      * when we reach a HText_endForm.
9524      */
9525     newform = typecalloc(PerFormInfo);
9526     if (newform == NULL)
9527 	outofmem(__FILE__, "HText_beginForm");
9528 
9529     assert(newform != NULL);
9530 
9531     PerFormInfo_free(HTCurrentForm);	/* shouldn't happen here - kw */
9532     HTCurrentForm = newform;
9533 
9534     newform->number = HTFormNumber;
9535     newform->data.submit_action = HTFormAction;
9536     newform->data.submit_enctype = HTFormEnctype;
9537     newform->data.submit_method = HTFormMethod;
9538     newform->data.submit_title = HTFormTitle;
9539     newform->accept_cs = HTFormAcceptCharset;
9540 
9541     CTRACE((tfp, "BeginForm: action:%s Method:%d%s%s%s%s%s%s\n",
9542 	    HTFormAction, HTFormMethod,
9543 	    (HTFormTitle ? " Title:" : ""),
9544 	    NonNull(HTFormTitle),
9545 	    (HTFormEnctype ? " Enctype:" : ""),
9546 	    NonNull(HTFormEnctype),
9547 	    (HTFormAcceptCharset ? " Accept-charset:" : ""),
9548 	    NonNull(HTFormAcceptCharset)));
9549 }
9550 
HText_endForm(HText * text)9551 void HText_endForm(HText *text)
9552 {
9553     if (text != NULL) {
9554 	if (HTFormFields == 1 && text->first_anchor) {
9555 	    /*
9556 	     * Support submission of a single text input field in
9557 	     * the form via <return> instead of a submit button.  -FM
9558 	     */
9559 	    TextAnchor *a;
9560 
9561 	    /*
9562 	     * Go through list of anchors and get our input field.  -FM
9563 	     */
9564 	    for (a = text->first_anchor; a != NULL; a = a->next) {
9565 		if (a->link_type == INPUT_ANCHOR &&
9566 		    a->input_field->number == HTFormNumber &&
9567 		    a->input_field->type != F_TEXTAREA_TYPE &&
9568 		    F_TEXTLIKE(a->input_field->type)) {
9569 		    /*
9570 		     * Got it.  Make it submitting.  -FM
9571 		     */
9572 		    if (addFormAction(a->input_field)) {
9573 			a->input_field->type = F_TEXT_SUBMIT_TYPE;
9574 			if (HTFormDisabled)
9575 			    a->input_field->disabled = TRUE;
9576 		    }
9577 		    break;
9578 		}
9579 	    }
9580 	}
9581 
9582 	/*
9583 	 * Append info on the current form to the HText object's list of forms.
9584 	 * HText_beginInput call will have set some of the data in the
9585 	 * PerFormInfo structure (if there were any form fields at all).
9586 	 */
9587 	if (HTCurrentForm) {
9588 	    if (HTFormDisabled)
9589 		HTCurrentForm->disabled = TRUE;
9590 	    if (!text->forms)
9591 		text->forms = HTList_new();
9592 	    HTList_appendObject(text->forms, HTCurrentForm);
9593 	    HTCurrentForm = NULL;
9594 	} else {
9595 	    CTRACE((tfp, "endForm:    HTCurrentForm is missing!\n"));
9596 	}
9597     } else {
9598 	CTRACE((tfp, "endForm:    HText is missing!\n"));
9599     }
9600 
9601     FREE(HTCurSelectGroup);
9602     FREE(HTCurSelectGroupSize);
9603     FREE(HTCurSelectedOptionValue);
9604     HTFormFields = 0;
9605     HTFormDisabled = FALSE;
9606 }
9607 
HText_beginSelect(char * name,int name_cs,int multiple,char * size)9608 void HText_beginSelect(char *name,
9609 		       int name_cs,
9610 		       int multiple,
9611 		       char *size)
9612 {
9613     /*
9614      * Save the group name.
9615      */
9616     StrAllocCopy(HTCurSelectGroup, name);
9617     HTCurSelectGroupCharset = name_cs;
9618 
9619     /*
9620      * If multiple then all options are actually checkboxes.
9621      */
9622     if (multiple)
9623 	HTCurSelectGroupType = F_CHECKBOX_TYPE;
9624     /*
9625      * If not multiple then all options are radio buttons.
9626      */
9627     else
9628 	HTCurSelectGroupType = F_RADIO_TYPE;
9629 
9630     /*
9631      * Length of an option list.
9632      */
9633     StrAllocCopy(HTCurSelectGroupSize, size);
9634 
9635     CTRACE((tfp, "HText_beginSelect: name=%s type=%d size=%s\n",
9636 	    ((HTCurSelectGroup == NULL) ?
9637 	     "<NULL>" : HTCurSelectGroup),
9638 	    HTCurSelectGroupType,
9639 	    ((HTCurSelectGroupSize == NULL) ?
9640 	     "<NULL>" : HTCurSelectGroupSize)));
9641     CTRACE((tfp, "HText_beginSelect: name_cs=%d \"%s\"\n",
9642 	    HTCurSelectGroupCharset,
9643 	    (HTCurSelectGroupCharset >= 0 ?
9644 	     LYCharSet_UC[HTCurSelectGroupCharset].MIMEname : "<UNKNOWN>")));
9645 }
9646 
9647 /*
9648  *  This function returns the number of the option whose
9649  *  value currently is being accumulated for a select
9650  *  block. - LE && FM
9651  */
HText_getOptionNum(HText * text)9652 int HText_getOptionNum(HText *text)
9653 {
9654     TextAnchor *a;
9655     OptionType *op;
9656     int n = 1;			/* start count at 1 */
9657 
9658     if (!(text && text->last_anchor))
9659 	return (0);
9660 
9661     a = text->last_anchor;
9662     if (!(a->link_type == INPUT_ANCHOR && a->input_field &&
9663 	  a->input_field->type == F_OPTION_LIST_TYPE))
9664 	return (0);
9665 
9666     for (op = a->input_field->select_list; op; op = op->next)
9667 	n++;
9668     CTRACE((tfp, "HText_getOptionNum: Got number '%d'.\n", n));
9669     return (n);
9670 }
9671 
9672 /*
9673  *  This function checks for a numbered option pattern
9674  *  as the prefix for an option value.  If present, and
9675  *  we are in the correct keypad mode, it returns a
9676  *  pointer to the actual value, following that prefix.
9677  *  Otherwise, it returns the original pointer.
9678  */
HText_skipOptionNumPrefix(char * opname)9679 static char *HText_skipOptionNumPrefix(char *opname)
9680 {
9681     /*
9682      * Check if we are in the correct keypad mode.
9683      */
9684     if (fields_are_numbered()) {
9685 	/*
9686 	 * Skip the option number embedded in the option name so the
9687 	 * extra chars won't mess up cgi scripts processing the value.
9688 	 * The format is (nnn)__ where nnn is a number and there is a
9689 	 * minimum of 5 chars (no underscores if (nnn) exceeds 5 chars).
9690 	 * See HTML.c.  If the chars don't exactly match this format,
9691 	 * just use all of opname.  - LE
9692 	 */
9693 	char *cp = opname;
9694 
9695 	if ((non_empty(cp) && *cp++ == '(') &&
9696 	    *cp && isdigit(UCH(*cp++))) {
9697 	    while (*cp && isdigit(UCH(*cp)))
9698 		++cp;
9699 	    if (*cp && *cp++ == ')') {
9700 		int i = (int) (cp - opname);
9701 
9702 		while (i < 5) {
9703 		    if (*cp != '_')
9704 			break;
9705 		    i++;
9706 		    cp++;
9707 		}
9708 		if (i < 5) {
9709 		    cp = opname;
9710 		}
9711 	    } else {
9712 		cp = opname;
9713 	    }
9714 	} else {
9715 	    cp = opname;
9716 	}
9717 	return (cp);
9718     }
9719 
9720     return (opname);
9721 }
9722 
9723 /*
9724  * We couldn't set the value field for the previous option tag so we have to do
9725  * it now.  Assume that the last anchor was the previous options' tag.
9726  */
HText_setLastOptionValue(HText * text,char * value,char * submit_value,int order,int checked,int val_cs,int submit_val_cs)9727 char *HText_setLastOptionValue(HText *text, char *value,
9728 			       char *submit_value,
9729 			       int order,
9730 			       int checked,
9731 			       int val_cs,
9732 			       int submit_val_cs)
9733 {
9734     char *cp, *cp1;
9735     char *ret_Value = NULL;
9736     unsigned char *tmp = NULL;
9737     int number = 0, i, j;
9738 
9739     if (!(value
9740 	  && text
9741 	  && text->last_anchor
9742 	  && text->last_anchor->input_field
9743 	  && text->last_anchor->link_type == INPUT_ANCHOR)) {
9744 	CTRACE((tfp, "HText_setLastOptionValue: invalid call!  value:%s!\n",
9745 		(value ? value : "<NULL>")));
9746 	return NULL;
9747     }
9748 
9749     CTRACE((tfp,
9750 	    "Entering HText_setLastOptionValue: value:\"%s\", checked:%s\n",
9751 	    value, (checked ? "on" : "off")));
9752 
9753     /*
9754      * Strip end spaces, newline is also whitespace.
9755      */
9756     if (*value) {
9757 	cp = &value[strlen(value) - 1];
9758 	while ((cp >= value) && (isspace(UCH(*cp)) ||
9759 				 IsSpecialAttrChar(UCH(*cp))))
9760 	    cp--;
9761 	*(cp + 1) = '\0';
9762     }
9763 
9764     /*
9765      * Find first non space
9766      */
9767     cp = value;
9768     while (isspace(UCH(*cp)) ||
9769 	   IsSpecialAttrChar(UCH(*cp)))
9770 	cp++;
9771     if (HTCurSelectGroupType == F_RADIO_TYPE &&
9772 	LYSelectPopups &&
9773 	fields_are_numbered()) {
9774 	/*
9775 	 * Collapse any space between the popup option
9776 	 * prefix and actual value.  -FM
9777 	 */
9778 	if ((cp1 = HText_skipOptionNumPrefix(cp)) > cp) {
9779 	    i = 0, j = (int) (cp1 - cp);
9780 	    while (isspace(UCH(cp1[i])) ||
9781 		   IsSpecialAttrChar(UCH(cp1[i]))) {
9782 		i++;
9783 	    }
9784 	    if (i > 0) {
9785 		while (cp1[i] != '\0')
9786 		    cp[j++] = cp1[i++];
9787 		cp[j] = '\0';
9788 	    }
9789 	}
9790     }
9791 
9792     if (HTCurSelectGroupType == F_CHECKBOX_TYPE) {
9793 	StrAllocCopy(text->last_anchor->input_field->value, cp);
9794 	text->last_anchor->input_field->value_cs = val_cs;
9795 	/*
9796 	 * Put the text on the screen as well.
9797 	 */
9798 	HText_appendText(text, cp);
9799 
9800     } else if (LYSelectPopups == FALSE) {
9801 	StrAllocCopy(text->last_anchor->input_field->value,
9802 		     (submit_value ? submit_value : cp));
9803 	text->last_anchor->input_field->value_cs = (submit_value ?
9804 						    submit_val_cs : val_cs);
9805 	/*
9806 	 * Put the text on the screen as well.
9807 	 */
9808 	HText_appendText(text, cp);
9809 
9810     } else {
9811 	/*
9812 	 * Create a linked list of option values.
9813 	 */
9814 	OptionType *op_ptr = text->last_anchor->input_field->select_list;
9815 	OptionType *new_ptr = NULL;
9816 	BOOLEAN first_option = FALSE;
9817 
9818 	/*
9819 	 * Deal with newlines or tabs.
9820 	 */
9821 	LYReduceBlanks(value);
9822 
9823 	if (!op_ptr) {
9824 	    /*
9825 	     * No option items yet.
9826 	     */
9827 	    if (text->last_anchor->input_field->type != F_OPTION_LIST_TYPE) {
9828 		CTRACE((tfp,
9829 			"HText_setLastOptionValue: last input_field not F_OPTION_LIST_TYPE (%d)\n",
9830 			F_OPTION_LIST_TYPE));
9831 		CTRACE((tfp, "                          but %d, ignoring!\n",
9832 			text->last_anchor->input_field->type));
9833 		return NULL;
9834 	    }
9835 
9836 	    new_ptr = typecalloc(OptionType);
9837 	    if (new_ptr == NULL)
9838 		outofmem(__FILE__, "HText_setLastOptionValue");
9839 
9840 	    text->last_anchor->input_field->select_list = new_ptr;
9841 	    first_option = TRUE;
9842 	} else {
9843 	    while (op_ptr->next) {
9844 		number++;
9845 		op_ptr = op_ptr->next;
9846 	    }
9847 	    number++;		/* add one more */
9848 
9849 	    op_ptr->next = new_ptr = typecalloc(OptionType);
9850 	    if (new_ptr == NULL)
9851 		outofmem(__FILE__, "HText_setLastOptionValue");
9852 	}
9853 
9854 	assert(new_ptr != NULL);
9855 	new_ptr->name = NULL;
9856 	new_ptr->cp_submit_value = NULL;
9857 	new_ptr->next = NULL;
9858 	/*
9859 	 * Find first non-space again, convert_to_spaces above may have
9860 	 * changed the string.  - kw
9861 	 */
9862 	cp = value;
9863 	while (isspace(UCH(*cp)) ||
9864 	       IsSpecialAttrChar(UCH(*cp)))
9865 	    cp++;
9866 	for (i = 0, j = 0; cp[i]; i++) {
9867 	    if (cp[i] == HT_NON_BREAK_SPACE ||
9868 		cp[i] == HT_EN_SPACE) {
9869 		cp[j++] = ' ';
9870 	    } else if (cp[i] != LY_SOFT_HYPHEN &&
9871 		       !IsSpecialAttrChar(UCH(cp[i]))) {
9872 		cp[j++] = cp[i];
9873 	    }
9874 	}
9875 	cp[j] = '\0';
9876 	if (IS_CJK_TTY) {
9877 	    if ((tmp = typecallocn(unsigned char, strlen(cp) * 2 + 1)) != 0) {
9878 		if (kanji_code == EUC) {
9879 		    TO_EUC((unsigned char *) cp, tmp);
9880 		    val_cs = current_char_set;
9881 		} else if (kanji_code == SJIS) {
9882 		    TO_SJIS((unsigned char *) cp, tmp);
9883 		    val_cs = current_char_set;
9884 		} else {
9885 		    for (i = 0, j = 0; cp[i]; i++) {
9886 			if (cp[i] != CH_ESC) {	/* S/390 -- gil -- 1604 */
9887 			    tmp[j++] = UCH(cp[i]);
9888 			}
9889 		    }
9890 		}
9891 		StrAllocCopy(new_ptr->name, (const char *) tmp);
9892 		FREE(tmp);
9893 	    } else {
9894 		outofmem(__FILE__, "HText_setLastOptionValue");
9895 	    }
9896 	} else {
9897 	    StrAllocCopy(new_ptr->name, cp);
9898 	}
9899 	StrAllocCopy(new_ptr->cp_submit_value,
9900 		     (submit_value ? submit_value :
9901 		      HText_skipOptionNumPrefix(new_ptr->name)));
9902 	new_ptr->value_cs = (submit_value ? submit_val_cs : val_cs);
9903 
9904 	if (first_option) {
9905 	    FormInfo *last_input = text->last_anchor->input_field;
9906 
9907 	    StrAllocCopy(HTCurSelectedOptionValue, new_ptr->name);
9908 	    last_input->num_value = 0;
9909 	    /*
9910 	     * If this is the first option in a popup select list,
9911 	     * HText_beginInput may have allocated the value and
9912 	     * cp_submit_value fields, so free them now to avoid
9913 	     * a memory leak.  - kw
9914 	     */
9915 	    FREE(last_input->value);
9916 	    FREE(last_input->cp_submit_value);
9917 
9918 	    last_input->value = last_input->select_list->name;
9919 	    last_input->orig_value = last_input->select_list->name;
9920 	    last_input->cp_submit_value = last_input->select_list->cp_submit_value;
9921 	    last_input->orig_submit_value = last_input->select_list->cp_submit_value;
9922 	    last_input->value_cs = new_ptr->value_cs;
9923 	} else {
9924 	    int newlen = (int) strlen(new_ptr->name);
9925 	    int curlen = (int) (HTCurSelectedOptionValue
9926 				? strlen(HTCurSelectedOptionValue)
9927 				: 0);
9928 
9929 	    /*
9930 	     * Make the selected Option Value as long as
9931 	     * the longest option.
9932 	     */
9933 	    if (newlen > curlen)
9934 		StrAllocCat(HTCurSelectedOptionValue,
9935 			    UNDERSCORES(newlen - curlen));
9936 	}
9937 
9938 	if (checked) {
9939 	    int curlen = (int) strlen(new_ptr->name);
9940 	    int newlen = (HTCurSelectedOptionValue
9941 			  ? (int) strlen(HTCurSelectedOptionValue)
9942 			  : 0);
9943 	    FormInfo *last_input = text->last_anchor->input_field;
9944 
9945 	    /*
9946 	     * Set the default option as this one.
9947 	     */
9948 	    last_input->num_value = number;
9949 	    last_input->value = new_ptr->name;
9950 	    last_input->orig_value = new_ptr->name;
9951 	    last_input->cp_submit_value = new_ptr->cp_submit_value;
9952 	    last_input->orig_submit_value = new_ptr->cp_submit_value;
9953 	    last_input->value_cs = new_ptr->value_cs;
9954 	    StrAllocCopy(HTCurSelectedOptionValue, new_ptr->name);
9955 	    if (newlen > curlen)
9956 		StrAllocCat(HTCurSelectedOptionValue,
9957 			    UNDERSCORES(newlen - curlen));
9958 	}
9959 
9960 	/*
9961 	 * Return the selected Option value to be sent to the screen.
9962 	 */
9963 	if (order == LAST_ORDER) {
9964 	    /*
9965 	     * Change the value.
9966 	     */
9967 	    if (HTCurSelectedOptionValue == 0)
9968 		StrAllocCopy(HTCurSelectedOptionValue, "");
9969 	    text->last_anchor->input_field->size =
9970 		(int) strlen(HTCurSelectedOptionValue);
9971 	    ret_Value = HTCurSelectedOptionValue;
9972 	}
9973     }
9974 
9975     if (TRACE) {
9976 	CTRACE((tfp, "HText_setLastOptionValue:%s value=\"%s\"\n",
9977 		(order == LAST_ORDER) ? " LAST_ORDER" : "",
9978 		value));
9979 	CTRACE((tfp, "            val_cs=%d \"%s\"",
9980 		val_cs,
9981 		(val_cs >= 0 ?
9982 		 LYCharSet_UC[val_cs].MIMEname : "<UNKNOWN>")));
9983 	if (submit_value) {
9984 	    CTRACE((tfp, " (submit_val_cs %d \"%s\") submit_value%s=\"%s\"\n",
9985 		    submit_val_cs,
9986 		    (submit_val_cs >= 0 ?
9987 		     LYCharSet_UC[submit_val_cs].MIMEname : "<UNKNOWN>"),
9988 		    (HTCurSelectGroupType == F_CHECKBOX_TYPE) ?
9989 		    "(ignored)" : "",
9990 		    submit_value));
9991 	} else {
9992 	    CTRACE((tfp, "\n"));
9993 	}
9994     }
9995     return (ret_Value);
9996 }
9997 
9998 /*
9999  * Assign a form input anchor.
10000  * Returns the number of characters to leave
10001  * blank so that the input field can fit.
10002  */
HText_beginInput(HText * text,int underline,InputFieldData * I)10003 int HText_beginInput(HText *text,
10004 		     int underline,
10005 		     InputFieldData * I)
10006 {
10007     TextAnchor *a;
10008     FormInfo *f;
10009     const char *cp_option = NULL;
10010     char *IValue = NULL;
10011     unsigned char *tmp = NULL;
10012     int i, j;
10013     int adjust_marker = 0;
10014     int MaximumSize;
10015     char marker[16];
10016 
10017     CTRACE((tfp, "GridText: Entering HText_beginInput type=%s\n", NonNull(I->type)));
10018 
10019     POOLtypecalloc(TextAnchor, a);
10020 
10021     POOLtypecalloc(FormInfo, f);
10022     if (a == NULL || f == NULL)
10023 	outofmem(__FILE__, "HText_beginInput");
10024 
10025     assert(a != NULL);
10026     assert(f != NULL);
10027 
10028     a->sgml_offset = SGML_offset();
10029     a->inUnderline = (BOOLEAN) underline;
10030     a->line_num = text->Lines;
10031     a->line_pos = (short) text->last_line->size;
10032 
10033     /*
10034      * If this is a radio button, or an OPTION we're converting
10035      * to a radio button, and it's the first with this name, make
10036      * sure it's checked by default.  Otherwise, if it's checked,
10037      * uncheck the default or any preceding radio button with this
10038      * name that was checked.  -FM
10039      */
10040     if (I->type != NULL && !strcmp(I->type, "OPTION") &&
10041 	HTCurSelectGroupType == F_RADIO_TYPE && LYSelectPopups == FALSE) {
10042 	I->type = "RADIO";
10043 	I->name = HTCurSelectGroup;
10044 	I->name_cs = HTCurSelectGroupCharset;
10045     }
10046     if (I->name && I->type && !strcasecomp(I->type, "radio")) {
10047 	if (!text->last_anchor) {
10048 	    I->checked = TRUE;
10049 	} else {
10050 	    TextAnchor *b;
10051 	    int i2 = 0;
10052 
10053 	    for (b = text->first_anchor; b != NULL; b = b->next) {
10054 		if (b->link_type == INPUT_ANCHOR &&
10055 		    b->input_field->type == F_RADIO_TYPE &&
10056 		    b->input_field->number == HTFormNumber) {
10057 		    if (!strcmp(b->input_field->name, I->name)) {
10058 			if (I->checked && b->input_field->num_value) {
10059 			    b->input_field->num_value = 0;
10060 			    StrAllocCopy(b->input_field->orig_value, "0");
10061 			    break;
10062 			}
10063 			i2++;
10064 		    }
10065 		}
10066 	    }
10067 	    if (i2 == 0)
10068 		I->checked = TRUE;
10069 	}
10070     }
10071 
10072     a->next = 0;
10073     a->anchor = NULL;
10074     a->link_type = INPUT_ANCHOR;
10075     a->show_anchor = YES;
10076 
10077     LYClearHiText(a);
10078     a->extent = 2;
10079 
10080     a->input_field = f;
10081 
10082     f->select_list = 0;
10083     f->number = HTFormNumber;
10084     f->disabled = HTFormDisabled || I->disabled;
10085     f->readonly = I->readonly;
10086     f->no_cache = NO;
10087 
10088     HTFormFields++;
10089 
10090     /*
10091      * Set up VALUE.
10092      */
10093     if (I->value)
10094 	StrAllocCopy(IValue, I->value);
10095     if (IValue &&
10096 	IS_CJK_TTY &&
10097 	((I->type == NULL) || strcasecomp(I->type, "hidden"))) {
10098 	if ((tmp = typecallocn(unsigned char, strlen(IValue) * 2 + 1)) != 0) {
10099 	    if (kanji_code == EUC) {
10100 		TO_EUC((unsigned char *) IValue, tmp);
10101 		I->value_cs = current_char_set;
10102 	    } else if (kanji_code == SJIS) {
10103 		TO_SJIS((unsigned char *) IValue, tmp);
10104 		I->value_cs = current_char_set;
10105 	    } else {
10106 		for (i = 0, j = 0; IValue[i]; i++) {
10107 		    if (IValue[i] != CH_ESC) {	/* S/390 -- gil -- 1621 */
10108 			tmp[j++] = UCH(IValue[i]);
10109 		    }
10110 		}
10111 	    }
10112 	    StrAllocCopy(IValue, (const char *) tmp);
10113 	    FREE(tmp);
10114 	}
10115     }
10116 
10117     /*
10118      * Special case of OPTION.
10119      * Is handled above if radio type and LYSelectPopups is FALSE.
10120      */
10121     /* set the values and let the parsing below do the work */
10122     if (I->type != NULL && !strcmp(I->type, "OPTION")) {
10123 	cp_option = I->type;
10124 	if (HTCurSelectGroupType == F_RADIO_TYPE)
10125 	    I->type = "OPTION_LIST";
10126 	else
10127 	    I->type = "CHECKBOX";
10128 	I->name = HTCurSelectGroup;
10129 	I->name_cs = HTCurSelectGroupCharset;
10130 
10131 	/*
10132 	 * The option's size parameter actually gives the length and not
10133 	 * the width of the list.  Perform the conversion here
10134 	 * and get rid of the allocated HTCurSelect....
10135 	 * 0 is ok as it means any length (arbitrary decision).
10136 	 */
10137 	if (HTCurSelectGroupSize != NULL) {
10138 	    f->size_l = atoi(HTCurSelectGroupSize);
10139 	    FREE(HTCurSelectGroupSize);
10140 	}
10141     }
10142 
10143     /*
10144      * Set SIZE.
10145      */
10146     if (I->size != 0) {
10147 	f->size = I->size;
10148 	/*
10149 	 * Leave at zero for option lists.
10150 	 */
10151 	if (f->size == 0 && cp_option == NULL) {
10152 	    f->size = 20;	/* default */
10153 	}
10154     } else {
10155 	f->size = 20;		/* default */
10156     }
10157 
10158     /*
10159      * Set MAXLENGTH.
10160      */
10161     if (I->maxlength != NULL) {
10162 	f->maxlength = (unsigned) atoi(I->maxlength);
10163     } else {
10164 	f->maxlength = 0;	/* 0 means infinite */
10165     }
10166 
10167     /*
10168      * Set CHECKED
10169      * (num_value is only relevant to check and radio types).
10170      */
10171     if (I->checked == TRUE)
10172 	f->num_value = 1;
10173     else
10174 	f->num_value = 0;
10175 
10176     /*
10177      * Set TYPE.
10178      */
10179     if (I->type != NULL) {
10180 	if (!strcasecomp(I->type, "password")) {
10181 	    f->type = F_PASSWORD_TYPE;
10182 	} else if (!strcasecomp(I->type, "checkbox")) {
10183 	    f->type = F_CHECKBOX_TYPE;
10184 	} else if (!strcasecomp(I->type, "radio")) {
10185 	    f->type = F_RADIO_TYPE;
10186 	} else if (!strcasecomp(I->type, "submit")) {
10187 	    f->type = F_SUBMIT_TYPE;
10188 	} else if (!strcasecomp(I->type, "image")) {
10189 	    f->type = F_IMAGE_SUBMIT_TYPE;
10190 	} else if (!strcasecomp(I->type, "reset")) {
10191 	    f->type = F_RESET_TYPE;
10192 	} else if (!strcasecomp(I->type, "OPTION_LIST")) {
10193 	    f->type = F_OPTION_LIST_TYPE;
10194 	} else if (!strcasecomp(I->type, "hidden")) {
10195 	    f->type = F_HIDDEN_TYPE;
10196 	    HTFormFields--;
10197 	    f->size = 0;
10198 	} else if (!strcasecomp(I->type, "textarea")) {
10199 	    f->type = F_TEXTAREA_TYPE;
10200 	} else if (!strcasecomp(I->type, "range")) {
10201 	    f->type = F_RANGE_TYPE;
10202 	} else if (!strcasecomp(I->type, "file")) {
10203 	    f->type = F_FILE_TYPE;
10204 	    CTRACE((tfp, "ok, got a file uploader\n"));
10205 	} else if (!strcasecomp(I->type, "keygen")) {
10206 	    f->type = F_KEYGEN_TYPE;
10207 	} else if (!strcasecomp(I->type, "button")) {
10208 	    f->type = F_BUTTON_TYPE;
10209 	} else {
10210 	    /*
10211 	     * Note that TYPE="scribble" defaults to TYPE="text".  -FM
10212 	     */
10213 	    f->type = F_TEXT_TYPE;	/* default */
10214 	}
10215     } else {
10216 	f->type = F_TEXT_TYPE;
10217     }
10218 
10219     /*
10220      * Set NAME.
10221      */
10222     if (I->name != NULL) {
10223 	StrAllocCopy(f->name, I->name);
10224 	f->name_cs = I->name_cs;
10225     } else {
10226 	if (f->type == F_RESET_TYPE ||
10227 	    f->type == F_SUBMIT_TYPE ||
10228 	    f->type == F_IMAGE_SUBMIT_TYPE) {
10229 	    /*
10230 	     * Set name to empty string.
10231 	     */
10232 	    StrAllocCopy(f->name, "");
10233 	} else {
10234 	    /*
10235 	     * Error!  NAME must be present.
10236 	     */
10237 	    CTRACE((tfp,
10238 		    "GridText: No name present in input field; not displaying\n"));
10239 	    FREE(IValue);
10240 	    return (0);
10241 	}
10242     }
10243 
10244     /*
10245      * Add this anchor to the anchor list
10246      */
10247     if (text->last_anchor) {
10248 	text->last_anchor->next = a;
10249     } else {
10250 	text->first_anchor = a;
10251     }
10252 
10253     /*
10254      * Set VALUE, if it exists.  Otherwise, if it's not
10255      * an option list make it a zero-length string.  -FM
10256      */
10257     if (IValue != NULL) {
10258 	/*
10259 	 * OPTION VALUE is not actually the value to be seen but is to
10260 	 * be sent....
10261 	 */
10262 	if (f->type == F_OPTION_LIST_TYPE ||
10263 	    f->type == F_CHECKBOX_TYPE) {
10264 	    /*
10265 	     * Fill both with the value.  The f->value may be
10266 	     * overwritten in HText_setLastOptionValue....
10267 	     */
10268 	    StrAllocCopy(f->value, IValue);
10269 	    StrAllocCopy(f->cp_submit_value, IValue);
10270 	} else {
10271 	    StrAllocCopy(f->value, IValue);
10272 	}
10273 	f->value_cs = I->value_cs;
10274     } else if (f->type != F_OPTION_LIST_TYPE) {
10275 	StrAllocCopy(f->value, "");
10276 	/*
10277 	 * May be an empty INPUT field.  The text entered will then
10278 	 * probably be in the current display character set.  - kw
10279 	 */
10280 	f->value_cs = current_char_set;
10281     }
10282 
10283     /*
10284      * Run checks and fill in necessary values.
10285      */
10286     if (f->type == F_RESET_TYPE) {
10287 	if (non_empty(f->value)) {
10288 	    f->size = (int) strlen(f->value);
10289 	} else {
10290 	    StrAllocCopy(f->value, "Reset");
10291 	    f->size = 5;
10292 	}
10293     } else if (f->type == F_BUTTON_TYPE) {
10294 	if (non_empty(f->value)) {
10295 	    f->size = (int) strlen(f->value);
10296 	} else {
10297 	    StrAllocCopy(f->value, "BUTTON");
10298 	    f->size = 5;
10299 	}
10300     } else if (f->type == F_IMAGE_SUBMIT_TYPE ||
10301 	       f->type == F_SUBMIT_TYPE) {
10302 	if (non_empty(f->value)) {
10303 	    f->size = (int) strlen(f->value);
10304 	} else if (f->type == F_IMAGE_SUBMIT_TYPE) {
10305 	    StrAllocCopy(f->value, "[IMAGE]-Submit");
10306 	    f->size = 14;
10307 	} else {
10308 	    StrAllocCopy(f->value, "Submit");
10309 	    f->size = 6;
10310 	}
10311 	addFormAction(f);
10312     } else if (f->type == F_RADIO_TYPE || f->type == F_CHECKBOX_TYPE) {
10313 	f->size = 3;
10314 	if (IValue == NULL)
10315 	    StrAllocCopy(f->value, (f->type == F_CHECKBOX_TYPE ? "on" : ""));
10316 
10317     }
10318     FREE(IValue);
10319 
10320     /*
10321      * Set original values.
10322      */
10323     if (f->type == F_RADIO_TYPE || f->type == F_CHECKBOX_TYPE) {
10324 	if (f->num_value)
10325 	    StrAllocCopy(f->orig_value, "1");
10326 	else
10327 	    StrAllocCopy(f->orig_value, "0");
10328     } else if (f->type == F_OPTION_LIST_TYPE) {
10329 	f->orig_value = NULL;
10330     } else {
10331 	StrAllocCopy(f->orig_value, f->value);
10332     }
10333 
10334     /*
10335      * Store accept-charset if present, converting to lowercase
10336      * and collapsing spaces.  - kw
10337      */
10338     if (I->accept_cs) {
10339 	StrAllocCopy(f->accept_cs, I->accept_cs);
10340 	LYRemoveBlanks(f->accept_cs);
10341 	LYLowerCase(f->accept_cs);
10342     }
10343 
10344     /*
10345      * Add numbers to form fields if needed.  - LE & FM
10346      */
10347     switch (f->type) {
10348 	/*
10349 	 * Do not supply number for hidden fields, nor
10350 	 * for types that are not yet implemented.
10351 	 */
10352     case F_HIDDEN_TYPE:
10353 #ifndef USE_FILE_UPLOAD
10354     case F_FILE_TYPE:
10355 #endif
10356     case F_RANGE_TYPE:
10357     case F_KEYGEN_TYPE:
10358     case F_BUTTON_TYPE:
10359 	a->number = 0;
10360 	break;
10361 
10362     default:
10363 	if (fields_are_numbered())
10364 	    a->number = ++(text->last_anchor_number);
10365 	else
10366 	    a->number = 0;
10367 	break;
10368     }
10369     if (fields_are_numbered() && (a->number > 0)) {
10370 	if (HTMainText != 0) {
10371 	    HText_findAnchorNumber(a);
10372 	} else {
10373 	    a->show_number = a->number;
10374 	}
10375 	sprintf(marker, "[%d]", a->show_number);
10376 	adjust_marker = (int) strlen(marker);
10377 	if (number_fields_on_left) {
10378 	    BOOL had_bracket = (BOOL) (f->type == F_OPTION_LIST_TYPE);
10379 
10380 	    HText_appendText(text, had_bracket ? (marker + 1) : marker);
10381 	    if (had_bracket)
10382 		HText_appendCharacter(text, '[');
10383 	}
10384 	a->line_num = text->Lines;
10385 	a->line_pos = (short) text->last_line->size;
10386     } else {
10387 	*marker = '\0';
10388     }
10389 
10390     /*
10391      * Restrict SIZE to maximum allowable size.
10392      */
10393     MaximumSize = WRAP_COLS(text) + 1 - adjust_marker;
10394     switch (f->type) {
10395 
10396     case F_SUBMIT_TYPE:
10397     case F_IMAGE_SUBMIT_TYPE:
10398     case F_RESET_TYPE:
10399     case F_TEXT_TYPE:
10400     case F_TEXTAREA_TYPE:
10401 	/*
10402 	 * For submit and reset buttons, and for text entry
10403 	 * fields and areas, we limit the size element to that
10404 	 * of one line for the current style because that's
10405 	 * the most we could highlight on overwrites, and/or
10406 	 * handle in the line editor.  The actual values for
10407 	 * text entry lines can be long, and will be scrolled
10408 	 * horizontally within the editing window.  -FM
10409 	 */
10410 	MaximumSize -= (1 +
10411 			(int) text->style->leftIndent +
10412 			(int) text->style->rightIndent);
10413 
10414 	/*  If we are numbering form links, place is taken by [nn]  */
10415 	if (fields_are_numbered()) {
10416 	    if (!number_fields_on_left
10417 		&& f->type == F_TEXT_TYPE
10418 		&& MaximumSize > a->line_pos + 10)
10419 		MaximumSize -= a->line_pos;
10420 	    else
10421 		MaximumSize -= (int) strlen(marker);
10422 	}
10423 
10424 	/*
10425 	 * Save value for submit/reset buttons so they
10426 	 * will be visible when printing the page.  - LE
10427 	 */
10428 	I->value = f->value;
10429 	break;
10430 
10431     default:
10432 	/*
10433 	 * For all other fields we limit the size element to
10434 	 * 10 less than the screen width, because either they
10435 	 * are types with small placeholders, and/or are a
10436 	 * type which is handled via a popup window.  -FM
10437 	 */
10438 	MaximumSize -= 10;
10439 	break;
10440     }
10441 
10442     if (MaximumSize < 1)
10443 	MaximumSize = 1;
10444 
10445     if (f->size > MaximumSize)
10446 	f->size = MaximumSize;
10447 
10448     /*
10449      * Add this anchor to the anchor list
10450      */
10451     text->last_anchor = a;
10452 
10453     if (HTCurrentForm) {	/* should always apply! - kw */
10454 	if (!HTCurrentForm->first_field) {
10455 	    HTCurrentForm->first_field = f;
10456 	}
10457 	HTCurrentForm->last_field = f;
10458 	HTCurrentForm->nfields++;	/* will count hidden fields - kw */
10459 	/*
10460 	 * Set the no_cache flag if the METHOD is POST.  -FM
10461 	 */
10462 	if (HTCurrentForm->data.submit_method == URL_POST_METHOD)
10463 	    f->no_cache = TRUE;
10464 	/*
10465 	 * Propagate form field's accept-charset attribute to enclosing
10466 	 * form if the form itself didn't have an accept-charset - kw
10467 	 */
10468 	if (f->accept_cs && !HTCurrentForm->accept_cs) {
10469 	    StrAllocCopy(HTCurrentForm->accept_cs, f->accept_cs);
10470 	}
10471 	if (!text->forms) {
10472 	    text->forms = HTList_new();
10473 	}
10474     } else {
10475 	CTRACE((tfp, "beginInput: HTCurrentForm is missing!\n"));
10476     }
10477 
10478     CTRACE((tfp, "Input link: name=%s\nvalue=%s\nsize=%d\n",
10479 	    f->name,
10480 	    NonNull(f->value),
10481 	    f->size));
10482     CTRACE((tfp, "Input link: name_cs=%d \"%s\" (from %d \"%s\")\n",
10483 	    f->name_cs,
10484 	    (f->name_cs >= 0 ?
10485 	     LYCharSet_UC[f->name_cs].MIMEname : "<UNKNOWN>"),
10486 	    I->name_cs,
10487 	    (I->name_cs >= 0 ?
10488 	     LYCharSet_UC[I->name_cs].MIMEname : "<UNKNOWN>")));
10489     CTRACE((tfp, "            value_cs=%d \"%s\" (from %d \"%s\")\n",
10490 	    f->value_cs,
10491 	    (f->value_cs >= 0 ?
10492 	     LYCharSet_UC[f->value_cs].MIMEname : "<UNKNOWN>"),
10493 	    I->value_cs,
10494 	    (I->value_cs >= 0 ?
10495 	     LYCharSet_UC[I->value_cs].MIMEname : "<UNKNOWN>")));
10496 
10497     /*
10498      * Return the SIZE of the input field.
10499      */
10500     if (I->size && f->size > adjust_marker) {
10501 	f->size -= adjust_marker;
10502     }
10503     return (f->size);
10504 }
10505 
10506 /*
10507  * If we're numbering fields on the right, do it.  Note that some fields may
10508  * be too long for the line - we'll lose the marker in that case rather than
10509  * truncate the field.
10510  */
HText_endInput(HText * text)10511 void HText_endInput(HText *text)
10512 {
10513     if (fields_are_numbered()
10514 	&& !number_fields_on_left
10515 	&& text != NULL
10516 	&& text->last_anchor != NULL
10517 	&& text->last_anchor->number > 0) {
10518 	char marker[20];
10519 
10520 	sprintf(marker, "[%d]", text->last_anchor->show_number);
10521 	HText_appendText(text, marker);
10522     }
10523 }
10524 
10525 /*
10526  * Get a translation (properly:  transcoding) quality, factoring in
10527  * our ability to translate (an UCTQ_t) and a possible q parameter
10528  * on the given charset string, for cs_from -> givenmime.
10529  * The parsed input string will be mutilated on exit(!).
10530  * Note that results are not normalised to 1.0, but results from
10531  * different calls of this function can be compared.  - kw
10532  *
10533  * Obsolete, it was planned to use here a quality parametr UCTQ_t,
10534  * which is boolean now.
10535  */
get_trans_q(int cs_from,char * givenmime)10536 static double get_trans_q(int cs_from,
10537 			  char *givenmime)
10538 {
10539     double df = 1.0;
10540     BOOL tq;
10541     char *p;
10542 
10543     if (!givenmime || !(*givenmime))
10544 	return 0.0;
10545     if ((p = strchr(givenmime, ';')) != NULL) {
10546 	*p++ = '\0';
10547     }
10548     if (!strcmp(givenmime, "*"))
10549 	tq = UCCanTranslateFromTo(cs_from,
10550 				  UCGetLYhndl_byMIME("utf-8"));
10551     else
10552 	tq = UCCanTranslateFromTo(cs_from,
10553 				  UCGetLYhndl_byMIME(givenmime));
10554     if (!tq)
10555 	return 0.0;
10556     if (non_empty(p)) {
10557 	char *pair, *field = p, *pval, *ptok;
10558 
10559 	/* Get all the parameters to the Charset */
10560 	while ((pair = HTNextTok(&field, ";", "\"", NULL)) != NULL) {
10561 	    if ((ptok = HTNextTok(&pair, "= ", NULL, NULL)) != NULL &&
10562 		(pval = HTNextField(&pair)) != NULL) {
10563 		if (0 == strcasecomp(ptok, "q")) {
10564 		    df = strtod(pval, NULL);
10565 		    break;
10566 		}
10567 	    }
10568 	}
10569 	return (df * tq);
10570     } else {
10571 	return tq;
10572     }
10573 }
10574 
10575 /*
10576  * Find the best charset for submission, if we have an ACCEPT_CHARSET
10577  * list.  It factors in how well we can translate (just as guess, and
10578  * not a very good one..) and possible ";q=" factors.  Yes this is
10579  * more general than it needs to be here.
10580  *
10581  * Input is cs_in and acceptstring.
10582  *
10583  * Will return charset handle as int.
10584  * best_csname will point to a newly allocated MIME string for the
10585  * charset corresponding to the return value if return value >= 0.
10586  * - kw
10587  */
find_best_target_cs(char ** best_csname,int cs_from,const char * acceptstring)10588 static int find_best_target_cs(char **best_csname,
10589 			       int cs_from,
10590 			       const char *acceptstring)
10591 {
10592     char *paccept = NULL;
10593     double bestq = -1.0;
10594     char *bestmime = NULL;
10595     char *field, *nextfield;
10596 
10597     StrAllocCopy(paccept, acceptstring);
10598     nextfield = paccept;
10599     while ((field = HTNextTok(&nextfield, ",", "\"", NULL)) != NULL) {
10600 	double q;
10601 
10602 	if (*field != '\0') {
10603 	    /* Get the Charset */
10604 	    q = get_trans_q(cs_from, field);
10605 	    if (q > bestq) {
10606 		bestq = q;
10607 		bestmime = field;
10608 	    }
10609 	}
10610     }
10611     if (bestmime) {
10612 	if (!strcmp(bestmime, "*"))	/* non-standard for HTML attribute.. */
10613 	    StrAllocCopy(*best_csname, "utf-8");
10614 	else
10615 	    StrAllocCopy(*best_csname, bestmime);
10616 	FREE(paccept);
10617 	if (bestq > 0)
10618 	    return (UCGetLYhndl_byMIME(*best_csname));
10619 	else
10620 	    return (-1);
10621     }
10622     FREE(paccept);
10623     return (-1);
10624 }
10625 
10626 #ifdef USE_FILE_UPLOAD
load_a_file(const char * val_used,bstring ** result)10627 static void load_a_file(const char *val_used,
10628 			bstring **result)
10629 {
10630     FILE *fd;
10631     size_t bytes;
10632     char buffer[BUFSIZ + 1];
10633 
10634     CTRACE((tfp, "Ok, about to convert \"%s\" to mime/thingy\n", val_used));
10635 
10636     if (*val_used) {		/* ignore empty form field */
10637 	if ((fd = fopen(val_used, BIN_R)) == 0) {
10638 	    HTAlert(gettext("Can't open file for uploading"));
10639 	} else {
10640 	    while ((bytes = fread(buffer, sizeof(char), BUFSIZ, fd)) != 0) {
10641 		HTSABCat(result, buffer, (int) bytes);
10642 	    }
10643 	    LYCloseInput(fd);
10644 	}
10645     }
10646 }
10647 
guess_content_type(const char * filename)10648 static const char *guess_content_type(const char *filename)
10649 {
10650     HTAtom *encoding;
10651     const char *desc;
10652     HTFormat format = HTFileFormat(filename, &encoding, &desc);
10653 
10654     return (format != 0 && non_empty(format->name))
10655 	? format->name
10656 	: "text/plain";
10657 }
10658 #endif /* USE_FILE_UPLOAD */
10659 
cannot_transcode(BOOL * had_warning,const char * target_csname)10660 static void cannot_transcode(BOOL *had_warning,
10661 			     const char *target_csname)
10662 {
10663     if (*had_warning == NO) {
10664 	*had_warning = YES;
10665 	_user_message(CANNOT_TRANSCODE_FORM,
10666 		      target_csname ? target_csname : "UNKNOWN");
10667 	LYSleepAlert();
10668     }
10669 }
10670 
10671 #define SPECIAL_8BIT 1
10672 #define SPECIAL_FORM 2
10673 
check_form_specialchars(const char * value)10674 static unsigned check_form_specialchars(const char *value)
10675 {
10676     unsigned result = 0;
10677     const char *p;
10678 
10679     for (p = value;
10680 	 non_empty(p) && (result != (SPECIAL_8BIT | SPECIAL_FORM));
10681 	 p++) {
10682 	if ((*p == HT_NON_BREAK_SPACE) ||
10683 	    (*p == HT_EN_SPACE) ||
10684 	    (*p == LY_SOFT_HYPHEN)) {
10685 	    result |= SPECIAL_FORM;
10686 	} else if ((*p & 0x80) != 0) {
10687 	    result |= SPECIAL_8BIT;
10688 	}
10689     }
10690     return result;
10691 }
10692 
10693 /*
10694  * Scan the given data, adding characters to the MIME-boundary to keep it from
10695  * matching any part of the data.
10696  */
UpdateBoundary(char ** Boundary,bstring * data)10697 static void UpdateBoundary(char **Boundary,
10698 			   bstring *data)
10699 {
10700     size_t j;
10701     size_t have = strlen(*Boundary);
10702     size_t last = (size_t) BStrLen(data);
10703     char *text = BStrData(data);
10704     char *want = *Boundary;
10705 
10706     assert(want != NULL);
10707     assert(text != NULL);
10708 
10709     for (j = 0; (long) j <= (long) (last - have); ++j) {
10710 	if (want[0] == text[j]
10711 	    && !memcmp(want, text + j, have)) {
10712 	    char temp[2];
10713 
10714 	    temp[0] = (char) (isdigit(UCH(text[have + j])) ? 'a' : '0');
10715 	    temp[1] = '\0';
10716 	    StrAllocCat(want, temp);
10717 	    ++have;
10718 	}
10719     }
10720     *Boundary = want;
10721 }
10722 
10723 /*
10724  * Convert a string to base64
10725  */
convert_to_base64(const char * src,size_t len)10726 static char *convert_to_base64(const char *src,
10727 			       size_t len)
10728 {
10729 #define B64_LINE       76
10730 
10731     static const char basis_64[] =
10732     "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/";
10733 
10734     char *dest;
10735     size_t rlen;		/* length of result string */
10736     unsigned char c1, c2, c3;
10737     const char *eol;
10738     char *r;
10739     const char *str;
10740     size_t eollen;
10741     int chunk;
10742 
10743     str = src;
10744     eol = "\n";
10745     eollen = 1;
10746 
10747     /* calculate the length of the result */
10748     rlen = (len + 2) / 3 * 4;	/* encoded bytes */
10749     if (rlen) {
10750 	/* add space for EOL */
10751 	rlen += ((rlen - 1) / B64_LINE + 1) * eollen;
10752     }
10753 
10754     /* allocate a result buffer */
10755     if ((dest = (char *) malloc(rlen + 1)) == NULL) {
10756 	outofmem(__FILE__, "convert_to_base64");
10757     }
10758     assert(dest != NULL);
10759     r = dest;
10760 
10761     /* encode */
10762     for (chunk = 0; len > 0; len -= 3, chunk++) {
10763 	if (chunk == (B64_LINE / 4)) {
10764 	    const char *c = eol;
10765 	    const char *e = eol + eollen;
10766 
10767 	    while (c < e)
10768 		*r++ = *c++;
10769 	    chunk = 0;
10770 	}
10771 	c1 = UCH(*str++);
10772 	c2 = UCH(*str++);
10773 	*r++ = basis_64[c1 >> 2];
10774 	*r++ = basis_64[((c1 & 0x3) << 4) | ((c2 & 0xF0) >> 4)];
10775 	if (len > 2) {
10776 	    c3 = UCH(*str++);
10777 	    *r++ = basis_64[((c2 & 0xF) << 2) | ((c3 & 0xC0) >> 6)];
10778 	    *r++ = basis_64[c3 & 0x3F];
10779 	} else if (len == 2) {
10780 	    *r++ = basis_64[(c2 & 0xF) << 2];
10781 	    *r++ = '=';
10782 	} else {		/* len == 1 */
10783 	    *r++ = '=';
10784 	    *r++ = '=';
10785 	}
10786     }
10787     if (rlen) {
10788 	/* append eol to the result string */
10789 	const char *c = eol;
10790 	const char *e = eol + eollen;
10791 
10792 	while (c < e)
10793 	    *r++ = *c++;
10794     }
10795     *r = '\0';
10796 
10797     return dest;
10798 }
10799 
10800 typedef enum {
10801     NO_QUOTE			/* no quoting needed */
10802     ,QUOTE_MULTI		/* multipart */
10803     ,QUOTE_BASE64		/* encode as base64 */
10804     ,QUOTE_SPECIAL		/* escape special characters only */
10805 } QuoteData;
10806 
10807 typedef struct {
10808     int type;			/* the type of this field */
10809     BOOL first;			/* true if this begins a submission part */
10810     char *name;			/* the name of this field */
10811     char *value;		/* the nominal value of this field */
10812     bstring *data;		/* its data, which is usually the same as the value */
10813     QuoteData quote;		/* how to quote/translate the data */
10814 } PostData;
10815 
escape_or_quote_name(const char * name,QuoteData quoting,const char * MultipartContentType)10816 static char *escape_or_quote_name(const char *name,
10817 				  QuoteData quoting,
10818 				  const char *MultipartContentType)
10819 {
10820     char *escaped1 = NULL;
10821 
10822     switch (quoting) {
10823     case NO_QUOTE:
10824 	StrAllocCopy(escaped1, name);
10825 	break;
10826     case QUOTE_MULTI:
10827     case QUOTE_BASE64:
10828 	StrAllocCopy(escaped1, "Content-Disposition: form-data");
10829 	HTSprintf(&escaped1, "; name=\"%s\"", name);
10830 	if (MultipartContentType)
10831 	    HTSprintf(&escaped1, MultipartContentType, "text/plain");
10832 	if (quoting == QUOTE_BASE64)
10833 	    StrAllocCat(escaped1, "\r\nContent-Transfer-Encoding: base64");
10834 	StrAllocCat(escaped1, "\r\n\r\n");
10835 	break;
10836     case QUOTE_SPECIAL:
10837 	escaped1 = HTEscapeSP(name, URL_XALPHAS);
10838 	break;
10839     }
10840     return escaped1;
10841 }
10842 
escape_or_quote_value(const char * value,QuoteData quoting)10843 static char *escape_or_quote_value(const char *value,
10844 				   QuoteData quoting)
10845 {
10846     char *escaped2 = NULL;
10847 
10848     switch (quoting) {
10849     case NO_QUOTE:
10850     case QUOTE_MULTI:
10851 	StrAllocCopy(escaped2, NonNull(value));
10852 	break;
10853     case QUOTE_BASE64:
10854 	/* FIXME: this is redundant */
10855 	escaped2 = convert_to_base64(value, strlen(value));
10856 	break;
10857     case QUOTE_SPECIAL:
10858 	escaped2 = HTEscapeSP(value, URL_XALPHAS);
10859 	break;
10860     }
10861     return escaped2;
10862 }
10863 
10864 /*
10865  * Check if we should encode the data in base64.  We can, only if we're using
10866  * a multipart content type.  We should, if we're sending mail and the data
10867  * contains long lines or nonprinting characters.
10868  */
check_if_base64_needed(int submit_method,bstring * data)10869 static int check_if_base64_needed(int submit_method,
10870 				  bstring *data)
10871 {
10872     int width = 0;
10873     BOOL printable = TRUE;
10874     char *text = BStrData(data);
10875 
10876     if (text != 0) {
10877 	int col = 0;
10878 	int n;
10879 	int length = BStrLen(data);
10880 
10881 	for (n = 0; n < length; ++n) {
10882 	    int ch = UCH(text[n]);
10883 
10884 	    if (is8bits(ch) || ((ch < 32 && ch != '\n'))) {
10885 		CTRACE((tfp, "nonprintable %d:%#x\n", n, ch));
10886 		printable = FALSE;
10887 	    }
10888 	    if (ch == '\n' || ch == '\r') {
10889 		if (width < col)
10890 		    width = col;
10891 		col = 0;
10892 	    } else {
10893 		++col;
10894 	    }
10895 	}
10896 	if (width < col)
10897 	    width = col;
10898     }
10899     return !printable && ((submit_method == URL_MAIL_METHOD) && (width > 72));
10900 }
10901 
HText_PerFormInfo(int number)10902 PerFormInfo *HText_PerFormInfo(int number)
10903 {
10904     return (PerFormInfo *) HTList_objectAt(HTMainText->forms, number - 1);
10905 }
10906 
10907 /*
10908  * HText_SubmitForm - generate submit data from form fields.
10909  * For mailto forms, send the data.
10910  * For other methods, set fields in structure pointed to by doc
10911  * appropriately for next request.
10912  * Returns 1 if *doc set appropriately for next request,
10913  * 0 otherwise.  - kw
10914  */
HText_SubmitForm(FormInfo * submit_item,DocInfo * doc,const char * link_name,const char * link_value)10915 int HText_SubmitForm(FormInfo * submit_item, DocInfo *doc,
10916 		     const char *link_name,
10917 		     const char *link_value)
10918 {
10919     BOOL had_chartrans_warning = NO;
10920     BOOL have_accept_cs = NO;
10921     BOOL success;
10922     BOOLEAN PlainText = FALSE;
10923     BOOLEAN SemiColon = FALSE;
10924     BOOL skip_field = FALSE;
10925     const char *out_csname;
10926     const char *target_csname = NULL;
10927     PerFormInfo *thisform;
10928     PostData *my_data = NULL;
10929     TextAnchor *anchor_ptr;
10930     bstring *my_query = NULL;
10931     char *Boundary = NULL;
10932     char *MultipartContentType = NULL;
10933     char *content_type_out = NULL;
10934     char *copied_name_used = NULL;
10935     char *copied_val_used = NULL;
10936     char *escaped1 = NULL;
10937     char *escaped2 = NULL;
10938     char *last_textarea_name = NULL;
10939     const char *name_used = "";
10940     char *previous_blanks = NULL;
10941     const char *val_used = "";
10942     int anchor_count = 0;
10943     int anchor_limit = 0;
10944     int form_number = submit_item->number;
10945     int result = 0;
10946     int target_cs = -1;
10947     int textarea_lineno = 0;
10948     unsigned form_is_special = 0;
10949 
10950     CTRACE((tfp, "SubmitForm\n  link_name=%s\n  link_value=%s\n", link_name, link_value));
10951     if (!HTMainText)
10952 	return 0;
10953 
10954     thisform = HText_PerFormInfo(form_number);
10955     /*  Sanity check */
10956     if (!thisform) {
10957 	CTRACE((tfp, "SubmitForm: form %d not in HTMainText's list!\n",
10958 		form_number));
10959     } else if (thisform->number != form_number) {
10960 	CTRACE((tfp, "SubmitForm: failed sanity check, %d!=%d !\n",
10961 		thisform->number, form_number));
10962 	thisform = NULL;
10963     }
10964 
10965     if (isEmpty(submit_item->submit_action)) {
10966 	CTRACE((tfp, "SubmitForm: no action given\n"));
10967 	return 0;
10968     }
10969 
10970     /*
10971      * If we're mailing, make sure it's a mailto ACTION.  -FM
10972      */
10973     if ((submit_item->submit_method == URL_MAIL_METHOD) &&
10974 	!isMAILTO_URL(submit_item->submit_action)) {
10975 	HTAlert(BAD_FORM_MAILTO);
10976 	return 0;
10977     }
10978 
10979     /*
10980      * Check the ENCTYPE and set up the appropriate variables.  -FM
10981      */
10982     if (submit_item->submit_enctype &&
10983 	!strncasecomp(submit_item->submit_enctype, "text/plain", 10)) {
10984 	/*
10985 	 * Do not hex escape, and use physical newlines
10986 	 * to separate name=value pairs.  -FM
10987 	 */
10988 	PlainText = TRUE;
10989     } else if (submit_item->submit_enctype &&
10990 	       !strncasecomp(submit_item->submit_enctype,
10991 			     "application/sgml-form-urlencoded", 32)) {
10992 	/*
10993 	 * Use semicolons instead of ampersands as the
10994 	 * separators for name=value pairs.  -FM
10995 	 */
10996 	SemiColon = TRUE;
10997     } else if (submit_item->submit_enctype &&
10998 	       !strncasecomp(submit_item->submit_enctype,
10999 			     "multipart/form-data", 19)) {
11000 	/*
11001 	 * Use the multipart MIME format.  Later we will ensure it does not
11002 	 * occur within the content.
11003 	 */
11004 	StrAllocCopy(Boundary, "xnyLAaB03X");
11005     }
11006 
11007     /*
11008      * Determine in what character encoding (aka.  charset) we should
11009      * submit.  We call this target_cs and the MIME name for it
11010      * target_csname.
11011      * TODO:   - actually use ACCEPT-CHARSET stuff from FORM
11012      * TODO:   - deal with list in ACCEPT-CHARSET, find a "best"
11013      *           charset to submit
11014      */
11015 
11016     /* Look at ACCEPT-CHARSET on the submitting field if we have one. */
11017     if (thisform && submit_item->accept_cs &&
11018 	strcasecomp(submit_item->accept_cs, "UNKNOWN")) {
11019 	have_accept_cs = YES;
11020 	target_cs = find_best_target_cs(&thisform->thisacceptcs,
11021 					current_char_set,
11022 					submit_item->accept_cs);
11023     }
11024     /* Look at ACCEPT-CHARSET on form as a whole if submitting field
11025      * didn't have one. */
11026     if (thisform && !have_accept_cs && thisform->accept_cs &&
11027 	strcasecomp(thisform->accept_cs, "UNKNOWN")) {
11028 	have_accept_cs = YES;
11029 	target_cs = find_best_target_cs(&thisform->thisacceptcs,
11030 					current_char_set,
11031 					thisform->accept_cs);
11032     }
11033     if (have_accept_cs && (target_cs >= 0) && thisform->thisacceptcs) {
11034 	target_csname = thisform->thisacceptcs;
11035     }
11036 
11037     if (target_cs < 0 &&
11038 	non_empty(HTMainText->node_anchor->charset)) {
11039 	target_cs = UCGetLYhndl_byMIME(HTMainText->node_anchor->charset);
11040 	if (target_cs >= 0) {
11041 	    target_csname = HTMainText->node_anchor->charset;
11042 	} else {
11043 	    target_cs = UCLYhndl_for_unspec;	/* always >= 0 */
11044 	    target_csname = LYCharSet_UC[target_cs].MIMEname;
11045 	}
11046     }
11047     if (target_cs < 0) {
11048 	target_cs = UCLYhndl_for_unspec;	/* always >= 0 */
11049     }
11050 
11051     /*
11052      * Go through list of anchors and get a "max." charset parameter - kw
11053      */
11054     for (anchor_ptr = HTMainText->first_anchor;
11055 	 anchor_ptr != NULL;
11056 	 anchor_ptr = anchor_ptr->next) {
11057 
11058 	if (anchor_ptr->link_type != INPUT_ANCHOR)
11059 	    continue;
11060 
11061 	if (anchor_ptr->input_field->number == form_number &&
11062 	    !anchor_ptr->input_field->disabled) {
11063 
11064 	    FormInfo *form_ptr = anchor_ptr->input_field;
11065 	    char *val = (form_ptr->cp_submit_value != NULL
11066 			 ? form_ptr->cp_submit_value
11067 			 : form_ptr->value);
11068 
11069 	    unsigned field_is_special = check_form_specialchars(val);
11070 	    unsigned name_is_special = check_form_specialchars(form_ptr->name);
11071 
11072 	    form_is_special = (field_is_special | name_is_special);
11073 
11074 	    if (field_is_special == 0) {
11075 		/* already ok */
11076 	    } else if (target_cs < 0) {
11077 		/* already confused */
11078 	    } else if ((field_is_special & SPECIAL_8BIT) == 0
11079 		       && (LYCharSet_UC[target_cs].enc == UCT_ENC_8859
11080 			   || (LYCharSet_UC[target_cs].like8859 & UCT_R_8859SPECL))) {
11081 		/* those specials will be trivial */
11082 	    } else if (UCNeedNotTranslate(form_ptr->value_cs, target_cs)) {
11083 		/* already ok */
11084 	    } else if (UCCanTranslateFromTo(form_ptr->value_cs, target_cs)) {
11085 		/* also ok */
11086 	    } else if (UCCanTranslateFromTo(target_cs, form_ptr->value_cs)) {
11087 		target_cs = form_ptr->value_cs;		/* try this */
11088 		target_csname = NULL;	/* will be set after loop */
11089 	    } else {
11090 		target_cs = -1;	/* don't know what to do */
11091 	    }
11092 
11093 	    /*  Same for name */
11094 	    if (name_is_special == 0) {
11095 		/* already ok */
11096 	    } else if (target_cs < 0) {
11097 		/* already confused */
11098 	    } else if ((name_is_special & SPECIAL_8BIT) == 0
11099 		       && (LYCharSet_UC[target_cs].enc == UCT_ENC_8859
11100 			   || (LYCharSet_UC[target_cs].like8859 & UCT_R_8859SPECL))) {
11101 		/* those specials will be trivial */
11102 	    } else if (UCNeedNotTranslate(form_ptr->name_cs, target_cs)) {
11103 		/* already ok */
11104 	    } else if (UCCanTranslateFromTo(form_ptr->name_cs, target_cs)) {
11105 		/* also ok */
11106 	    } else if (UCCanTranslateFromTo(target_cs, form_ptr->name_cs)) {
11107 		target_cs = form_ptr->value_cs;		/* try this */
11108 		target_csname = NULL;	/* will be set after loop */
11109 	    } else {
11110 		target_cs = -1;	/* don't know what to do */
11111 	    }
11112 
11113 	    ++anchor_limit;
11114 	} else if (anchor_ptr->input_field->number > form_number) {
11115 	    break;
11116 	}
11117     }
11118 
11119     /*
11120      * If we have input fields (we expect this), make an array of them so we
11121      * can organize the data.
11122      */
11123     if (anchor_limit != 0) {
11124 	my_data = typecallocn(PostData, (size_t) anchor_limit);
11125 	if (my_data == 0)
11126 	    outofmem(__FILE__, "HText_SubmitForm");
11127 	assert(my_data != NULL);
11128     }
11129 
11130     if (target_csname == NULL) {
11131 	if (target_cs >= 0) {
11132 	    if ((form_is_special & SPECIAL_8BIT) != 0) {
11133 		target_csname = LYCharSet_UC[target_cs].MIMEname;
11134 	    } else if ((form_is_special & SPECIAL_FORM) != 0) {
11135 		target_csname = LYCharSet_UC[target_cs].MIMEname;
11136 	    } else {
11137 		target_csname = "us-ascii";
11138 	    }
11139 	} else {
11140 	    target_csname = "us-ascii";
11141 	    target_cs = UCLYhndl_for_unspec;	/* always >= 0 */
11142 	}
11143     } else if (target_cs < 0) {
11144 	target_cs = UCLYhndl_for_unspec;	/* always >= 0 */
11145     }
11146 
11147     if (submit_item->submit_method == URL_GET_METHOD && Boundary == NULL) {
11148 	char *temp = NULL;
11149 
11150 	StrAllocCopy(temp, submit_item->submit_action);
11151 	/*
11152 	 * Method is GET.  Clip out any anchor in the current URL.
11153 	 */
11154 	strtok(temp, "#");
11155 	/*
11156 	 * Clip out any old query in the current URL.
11157 	 */
11158 	strtok(temp, "?");
11159 	/*
11160 	 * Add the lead question mark for the new URL.
11161 	 */
11162 	StrAllocCat(temp, "?");
11163 	BStrCat0(my_query, temp);
11164     } else {
11165 	/*
11166 	 * We are submitting POST content to a server,
11167 	 * so load content_type_out.  This will be put in
11168 	 * the post_content_type element if all goes well.  -FM, kw
11169 	 */
11170 	if (SemiColon == TRUE) {
11171 	    StrAllocCopy(content_type_out,
11172 			 "application/sgml-form-urlencoded");
11173 	} else if (PlainText == TRUE) {
11174 	    StrAllocCopy(content_type_out,
11175 			 "text/plain");
11176 	} else if (Boundary != NULL) {
11177 	    StrAllocCopy(content_type_out,
11178 			 "multipart/form-data");
11179 	} else {
11180 	    StrAllocCopy(content_type_out,
11181 			 "application/x-www-form-urlencoded");
11182 	}
11183 
11184 	/*
11185 	 * If the ENCTYPE is not multipart/form-data, append the
11186 	 * charset we'll be sending to the post_content_type, IF
11187 	 *  (1) there was an explicit accept-charset attribute, OR
11188 	 *  (2) we have 8-bit or special chars, AND the document had
11189 	 *      an explicit (recognized and accepted) charset parameter,
11190 	 *      AND it or target_csname is different from iso-8859-1,
11191 	 *      OR
11192 	 *  (3) we have 8-bit or special chars, AND the document had
11193 	 *      no explicit (recognized and accepted) charset parameter,
11194 	 *      AND target_cs is different from the currently effective
11195 	 *      assumed charset (which should have been set by the user
11196 	 *      so that it reflects what the server is sending, if the
11197 	 *      document is rendered correctly).
11198 	 * For multipart/form-data the equivalent will be done later,
11199 	 * separately for each form field.  - kw
11200 	 */
11201 	if (have_accept_cs
11202 	    || ((form_is_special & SPECIAL_8BIT) != 0
11203 		|| (form_is_special & SPECIAL_FORM) != 0)) {
11204 	    if (target_cs >= 0 && target_csname) {
11205 		if (Boundary == NULL) {
11206 		    if ((HTMainText->node_anchor->charset &&
11207 			 (strcmp(HTMainText->node_anchor->charset,
11208 				 "iso-8859-1") ||
11209 			  strcmp(target_csname, "iso-8859-1"))) ||
11210 			(!HTMainText->node_anchor->charset &&
11211 			 target_cs != UCLYhndl_for_unspec)) {
11212 			HTSprintf(&content_type_out, "; charset=%s", target_csname);
11213 		    }
11214 		}
11215 	    } else {
11216 		cannot_transcode(&had_chartrans_warning, target_csname);
11217 	    }
11218 	}
11219     }
11220 
11221     out_csname = target_csname;
11222 
11223     /*
11224      * Build up a list of the input fields and their associated values.
11225      */
11226     for (anchor_ptr = HTMainText->first_anchor;
11227 	 anchor_ptr != NULL;
11228 	 anchor_ptr = anchor_ptr->next) {
11229 
11230 	if (anchor_ptr->link_type != INPUT_ANCHOR)
11231 	    continue;
11232 
11233 	if (anchor_ptr->input_field->number == form_number &&
11234 	    !anchor_ptr->input_field->disabled) {
11235 
11236 	    FormInfo *form_ptr = anchor_ptr->input_field;
11237 	    int out_cs;
11238 	    QuoteData quoting = (PlainText
11239 				 ? NO_QUOTE
11240 				 : (Boundary
11241 				    ? QUOTE_MULTI
11242 				    : QUOTE_SPECIAL));
11243 
11244 	    assert(my_data != NULL);
11245 
11246 	    if (form_ptr->type != F_TEXTAREA_TYPE)
11247 		textarea_lineno = 0;
11248 
11249 	    CTRACE((tfp, "SubmitForm[%d/%d]: ",
11250 		    anchor_count + 1, anchor_limit));
11251 
11252 	    name_used = NonNull(form_ptr->name);
11253 
11254 	    switch (form_ptr->type) {
11255 	    case F_RESET_TYPE:
11256 		CTRACE((tfp, "reset\n"));
11257 		break;
11258 #ifdef USE_FILE_UPLOAD
11259 	    case F_FILE_TYPE:
11260 		val_used = NonNull(form_ptr->value);
11261 		CTRACE((tfp, "I will submit \"%s\" (from %s)\n",
11262 			val_used, name_used));
11263 		break;
11264 #endif
11265 	    case F_SUBMIT_TYPE:
11266 	    case F_TEXT_SUBMIT_TYPE:
11267 	    case F_IMAGE_SUBMIT_TYPE:
11268 		if (!(non_empty(form_ptr->name) &&
11269 		      !strcmp(form_ptr->name, link_name))) {
11270 		    CTRACE((tfp, "skipping submit field with "));
11271 		    CTRACE((tfp, "name \"%s\" for link_name \"%s\", %s.\n",
11272 			    form_ptr->name ? form_ptr->name : "???",
11273 			    link_name ? link_name : "???",
11274 			    non_empty(form_ptr->name) ?
11275 			    "not current link" : "no field name"));
11276 		    break;
11277 		}
11278 		if (!(form_ptr->type == F_TEXT_SUBMIT_TYPE ||
11279 		      (non_empty(form_ptr->value) &&
11280 		       !strcmp(form_ptr->value, link_value)))) {
11281 		    CTRACE((tfp, "skipping submit field with "));
11282 		    CTRACE((tfp, "name \"%s\" for link_name \"%s\", %s!\n",
11283 			    form_ptr->name ? form_ptr->name : "???",
11284 			    link_name ? link_name : "???",
11285 			    "values are different"));
11286 		    break;
11287 		}
11288 		/* FALLTHRU */
11289 	    case F_RADIO_TYPE:
11290 	    case F_CHECKBOX_TYPE:
11291 	    case F_TEXTAREA_TYPE:
11292 	    case F_PASSWORD_TYPE:
11293 	    case F_TEXT_TYPE:
11294 	    case F_OPTION_LIST_TYPE:
11295 	    case F_HIDDEN_TYPE:
11296 		/*
11297 		 * Be sure to actually look at the option submit value.
11298 		 */
11299 		if (form_ptr->cp_submit_value != NULL) {
11300 		    val_used = form_ptr->cp_submit_value;
11301 		} else {
11302 		    val_used = form_ptr->value;
11303 		}
11304 
11305 		/*
11306 		 * Charset-translate value now, because we need to know the
11307 		 * charset parameter for multipart bodyparts.  - kw
11308 		 */
11309 		if (check_form_specialchars(val_used) != 0) {
11310 		    /*  We should translate back. */
11311 		    StrAllocCopy(copied_val_used, val_used);
11312 		    success = FALSE;
11313 		    if (HTCJK == JAPANESE) {
11314 			if ((0 <= target_cs) &&
11315 			    !strcmp(LYCharSet_UC[target_cs].MIMEname, "euc-jp")) {
11316 			    TO_EUC((const unsigned char *) val_used,
11317 				   (unsigned char *) copied_val_used);
11318 			    success = YES;
11319 			} else if ((0 <= target_cs) &&
11320 				   !strcmp(LYCharSet_UC[target_cs].MIMEname,
11321 					   "shift_jis")) {
11322 			    TO_SJIS((const unsigned char *) val_used,
11323 				    (unsigned char *) copied_val_used);
11324 			    success = YES;
11325 			}
11326 		    }
11327 		    if (!success) {
11328 			success = LYUCTranslateBackFormData(&copied_val_used,
11329 							    form_ptr->value_cs,
11330 							    target_cs, PlainText);
11331 		    }
11332 		    CTRACE((tfp, "field \"%s\" %d %s -> %d %s %s\n",
11333 			    NonNull(form_ptr->name),
11334 			    form_ptr->value_cs,
11335 			    ((form_ptr->value_cs >= 0)
11336 			     ? LYCharSet_UC[form_ptr->value_cs].MIMEname
11337 			     : "???"),
11338 			    target_cs,
11339 			    target_csname ? target_csname : "???",
11340 			    success ? "OK" : "FAILED"));
11341 		    if (success) {
11342 			val_used = copied_val_used;
11343 		    }
11344 		} else {	/* We can use the value directly. */
11345 		    CTRACE((tfp, "field \"%s\" %d %s OK\n",
11346 			    NonNull(form_ptr->name),
11347 			    target_cs,
11348 			    target_csname ? target_csname : "???"));
11349 		    success = YES;
11350 		}
11351 		if (!success) {
11352 		    cannot_transcode(&had_chartrans_warning, target_csname);
11353 		    out_cs = form_ptr->value_cs;
11354 		} else {
11355 		    out_cs = target_cs;
11356 		}
11357 		if (out_cs >= 0)
11358 		    out_csname = LYCharSet_UC[out_cs].MIMEname;
11359 		if (Boundary) {
11360 		    StrAllocCopy(MultipartContentType,
11361 				 "\r\nContent-Type: %s");
11362 		    if (!success && form_ptr->value_cs < 0) {
11363 			/*  This is weird. */
11364 			out_csname = "UNKNOWN-8BIT";
11365 		    } else if (!success) {
11366 			target_csname = NULL;
11367 		    } else {
11368 			if (!target_csname) {
11369 			    target_csname = LYCharSet_UC[target_cs].MIMEname;
11370 			}
11371 		    }
11372 		    if (strcmp(out_csname, "iso-8859-1"))
11373 			HTSprintf(&MultipartContentType, "; charset=%s", out_csname);
11374 		}
11375 
11376 		/*
11377 		 * Charset-translate name now, because we need to know the
11378 		 * charset parameter for multipart bodyparts.  - kw
11379 		 */
11380 		if (form_ptr->type == F_TEXTAREA_TYPE) {
11381 		    textarea_lineno++;
11382 		    if (textarea_lineno > 1 &&
11383 			last_textarea_name && form_ptr->name &&
11384 			!strcmp(last_textarea_name, form_ptr->name)) {
11385 			break;
11386 		    }
11387 		}
11388 
11389 		if (check_form_specialchars(name_used) != 0) {
11390 		    /*  We should translate back. */
11391 		    StrAllocCopy(copied_name_used, name_used);
11392 		    success = LYUCTranslateBackFormData(&copied_name_used,
11393 							form_ptr->name_cs,
11394 							target_cs, PlainText);
11395 		    CTRACE((tfp, "name \"%s\" %d %s -> %d %s %s\n",
11396 			    NonNull(form_ptr->name),
11397 			    form_ptr->name_cs,
11398 			    ((form_ptr->name_cs >= 0)
11399 			     ? LYCharSet_UC[form_ptr->name_cs].MIMEname
11400 			     : "???"),
11401 			    target_cs,
11402 			    target_csname ? target_csname : "???",
11403 			    success ? "OK" : "FAILED"));
11404 		    if (success) {
11405 			name_used = copied_name_used;
11406 		    }
11407 		    if (Boundary) {
11408 			if (!success) {
11409 			    StrAllocCopy(MultipartContentType, "");
11410 			    target_csname = NULL;
11411 			} else {
11412 			    if (!target_csname)
11413 				target_csname = LYCharSet_UC[target_cs].MIMEname;
11414 			}
11415 		    }
11416 		} else {	/* We can use the name directly. */
11417 		    CTRACE((tfp, "name \"%s\" %d %s OK\n",
11418 			    NonNull(form_ptr->name),
11419 			    target_cs,
11420 			    target_csname ? target_csname : "???"));
11421 		    success = YES;
11422 		    if (Boundary) {
11423 			StrAllocCopy(copied_name_used, name_used);
11424 		    }
11425 		}
11426 		if (!success) {
11427 		    cannot_transcode(&had_chartrans_warning, target_csname);
11428 		}
11429 		if (Boundary) {
11430 		    /*
11431 		     * According to RFC 1867, Non-ASCII field names
11432 		     * "should be encoded according to the prescriptions
11433 		     * of RFC 1522 [...].  I don't think RFC 1522 actually
11434 		     * is meant to apply to parameters like this, and it
11435 		     * is unknown whether any server would make sense of
11436 		     * it, so for now just use some quoting/escaping and
11437 		     * otherwise leave 8-bit values as they are.
11438 		     * Non-ASCII characters in form field names submitted
11439 		     * as multipart/form-data can only occur if the form
11440 		     * provider specifically asked for it anyway.  - kw
11441 		     */
11442 		    HTMake822Word(&copied_name_used, FALSE);
11443 		    name_used = copied_name_used;
11444 		}
11445 
11446 		break;
11447 	    default:
11448 		CTRACE((tfp, "What type is %d?\n", form_ptr->type));
11449 		break;
11450 	    }
11451 
11452 	    skip_field = FALSE;
11453 	    my_data[anchor_count].first = TRUE;
11454 	    my_data[anchor_count].type = form_ptr->type;
11455 
11456 	    /*
11457 	     * Using the values of 'name_used' and 'val_used' computed in the
11458 	     * previous case-statement, compute the 'first' and 'data' values
11459 	     * for the current input field.
11460 	     */
11461 	    switch (form_ptr->type) {
11462 
11463 	    default:
11464 		skip_field = TRUE;
11465 		break;
11466 
11467 #ifdef USE_FILE_UPLOAD
11468 	    case F_FILE_TYPE:
11469 		load_a_file(val_used, &(my_data[anchor_count].data));
11470 		break;
11471 #endif /* USE_FILE_UPLOAD */
11472 
11473 	    case F_SUBMIT_TYPE:
11474 	    case F_TEXT_SUBMIT_TYPE:
11475 	    case F_IMAGE_SUBMIT_TYPE:
11476 		if ((non_empty(form_ptr->name) &&
11477 		     !strcmp(form_ptr->name, link_name)) &&
11478 		    (form_ptr->type == F_TEXT_SUBMIT_TYPE ||
11479 		     (non_empty(form_ptr->value) &&
11480 		      !strcmp(form_ptr->value, link_value)))) {
11481 		    ;
11482 		} else {
11483 		    skip_field = TRUE;
11484 		}
11485 		break;
11486 
11487 	    case F_RADIO_TYPE:
11488 	    case F_CHECKBOX_TYPE:
11489 		/*
11490 		 * Only add if selected.
11491 		 */
11492 		if (form_ptr->num_value) {
11493 		    ;
11494 		} else {
11495 		    skip_field = TRUE;
11496 		}
11497 		break;
11498 
11499 	    case F_TEXTAREA_TYPE:
11500 		if (!last_textarea_name ||
11501 		    strcmp(last_textarea_name, form_ptr->name)) {
11502 		    textarea_lineno = 1;
11503 		    last_textarea_name = form_ptr->name;
11504 		} else {
11505 		    my_data[anchor_count].first = FALSE;
11506 		}
11507 		break;
11508 
11509 	    case F_PASSWORD_TYPE:
11510 	    case F_TEXT_TYPE:
11511 	    case F_OPTION_LIST_TYPE:
11512 	    case F_HIDDEN_TYPE:
11513 		break;
11514 	    }
11515 
11516 	    /*
11517 	     * If we did not decide to skip the current field, populate the
11518 	     * values in the array for it.
11519 	     */
11520 	    if (!skip_field) {
11521 		StrAllocCopy(my_data[anchor_count].name, name_used);
11522 		StrAllocCopy(my_data[anchor_count].value, val_used);
11523 		if (my_data[anchor_count].data == 0)
11524 		    BStrCat0(my_data[anchor_count].data, val_used);
11525 		my_data[anchor_count].quote = quoting;
11526 		if (quoting == QUOTE_MULTI
11527 		    && check_if_base64_needed(submit_item->submit_method,
11528 					      my_data[anchor_count].data)) {
11529 		    CTRACE((tfp, "will encode as base64\n"));
11530 		    my_data[anchor_count].quote = QUOTE_BASE64;
11531 		    escaped2 =
11532 			convert_to_base64(BStrData(my_data[anchor_count].data),
11533 					  (size_t)
11534 					  BStrLen(my_data[anchor_count].data));
11535 		    BStrCopy0(my_data[anchor_count].data, escaped2);
11536 		    FREE(escaped2);
11537 		}
11538 	    }
11539 	    ++anchor_count;
11540 
11541 	    FREE(copied_name_used);
11542 	    FREE(copied_val_used);
11543 
11544 	} else if (anchor_ptr->input_field->number > form_number) {
11545 	    break;
11546 	}
11547     }
11548 
11549     FREE(copied_name_used);
11550 
11551     if (my_data != 0) {
11552 	BOOL first_one = TRUE;
11553 
11554 	/*
11555 	 * If we're using a MIME-boundary, make it unique.
11556 	 */
11557 	if (content_type_out != 0 && Boundary != 0) {
11558 	    Boundary = 0;
11559 	    StrAllocCopy(Boundary, "LYNX");
11560 	    for (anchor_count = 0; anchor_count < anchor_limit; ++anchor_count) {
11561 		if (my_data[anchor_count].data != 0) {
11562 		    UpdateBoundary(&Boundary, my_data[anchor_count].data);
11563 		}
11564 	    }
11565 	    HTSprintf(&content_type_out, "; boundary=%s", Boundary);
11566 	}
11567 
11568 	for (anchor_count = 0; anchor_count < anchor_limit; ++anchor_count) {
11569 
11570 	    if (my_data[anchor_count].name != 0
11571 		&& my_data[anchor_count].value != 0) {
11572 
11573 		CTRACE((tfp,
11574 			"processing [%d:%d] name=%s(first:%d, value=%s, data=%p)\n",
11575 			anchor_count + 1,
11576 			anchor_limit,
11577 			NonNull(my_data[anchor_count].name),
11578 			my_data[anchor_count].first,
11579 			NonNull(my_data[anchor_count].value),
11580 			(void *) my_data[anchor_count].data));
11581 
11582 		if (my_data[anchor_count].first) {
11583 		    if (first_one) {
11584 			if (Boundary) {
11585 			    HTBprintf(&my_query, "--%s\r\n", Boundary);
11586 			}
11587 			first_one = FALSE;
11588 		    } else {
11589 			if (PlainText) {
11590 			    BStrCat0(my_query, "\n");
11591 			} else if (SemiColon) {
11592 			    BStrCat0(my_query, ";");
11593 			} else if (Boundary) {
11594 			    HTBprintf(&my_query, "\r\n--%s\r\n", Boundary);
11595 			} else {
11596 			    BStrCat0(my_query, "&");
11597 			}
11598 		    }
11599 		}
11600 
11601 		/* append a null to the string */
11602 		HTSABCat(&(my_data[anchor_count].data), "", 1);
11603 		name_used = my_data[anchor_count].name;
11604 		val_used = my_data[anchor_count].value;
11605 
11606 	    } else {
11607 		/* there is no data to send */
11608 		continue;
11609 	    }
11610 
11611 	    switch (my_data[anchor_count].type) {
11612 	    case F_TEXT_TYPE:
11613 	    case F_PASSWORD_TYPE:
11614 	    case F_OPTION_LIST_TYPE:
11615 	    case F_HIDDEN_TYPE:
11616 		escaped1 = escape_or_quote_name(my_data[anchor_count].name,
11617 						my_data[anchor_count].quote,
11618 						MultipartContentType);
11619 
11620 		escaped2 = escape_or_quote_value(val_used,
11621 						 my_data[anchor_count].quote);
11622 
11623 		HTBprintf(&my_query,
11624 			  "%s%s%s%s%s",
11625 			  escaped1,
11626 			  (Boundary ? "" : "="),
11627 			  (PlainText ? "\n" : ""),
11628 			  escaped2,
11629 			  ((PlainText && *escaped2) ? "\n" : ""));
11630 		break;
11631 	    case F_CHECKBOX_TYPE:
11632 	    case F_RADIO_TYPE:
11633 		escaped1 = escape_or_quote_name(my_data[anchor_count].name,
11634 						my_data[anchor_count].quote,
11635 						MultipartContentType);
11636 
11637 		escaped2 = escape_or_quote_value(val_used,
11638 						 my_data[anchor_count].quote);
11639 
11640 		HTBprintf(&my_query,
11641 			  "%s%s%s%s%s",
11642 			  escaped1,
11643 			  (Boundary ? "" : "="),
11644 			  (PlainText ? "\n" : ""),
11645 			  escaped2,
11646 			  ((PlainText && *escaped2) ? "\n" : ""));
11647 		break;
11648 	    case F_SUBMIT_TYPE:
11649 	    case F_TEXT_SUBMIT_TYPE:
11650 	    case F_IMAGE_SUBMIT_TYPE:
11651 		/*
11652 		 * If it has a non-zero length name (e.g., because
11653 		 * its IMAGE_SUBMIT_TYPE is to be handled homologously
11654 		 * to an image map, or a SUBMIT_TYPE in a set of
11655 		 * multiple submit buttons, or a single type="text"
11656 		 * that's been converted to a TEXT_SUBMIT_TYPE),
11657 		 * include the name=value pair, or fake name.x=0 and
11658 		 * name.y=0 pairs for IMAGE_SUBMIT_TYPE.  -FM
11659 		 */
11660 		escaped1 = escape_or_quote_name(my_data[anchor_count].name,
11661 						my_data[anchor_count].quote,
11662 						MultipartContentType);
11663 
11664 		escaped2 = escape_or_quote_value(val_used,
11665 						 my_data[anchor_count].quote);
11666 
11667 		if (my_data[anchor_count].type == F_IMAGE_SUBMIT_TYPE) {
11668 		    /*
11669 		     * It's a clickable image submit button.  Fake a 0,0
11670 		     * coordinate pair, which typically returns the image's
11671 		     * default.  -FM
11672 		     */
11673 		    if (Boundary) {
11674 			*(strchr(escaped1, '=') + 1) = '\0';
11675 			HTBprintf(&my_query,
11676 				  "%s\"%s.x\"\r\n\r\n0\r\n--%s\r\n%s\"%s.y\"\r\n\r\n0",
11677 				  escaped1,
11678 				  my_data[anchor_count].name,
11679 				  Boundary,
11680 				  escaped1,
11681 				  my_data[anchor_count].name);
11682 		    } else {
11683 			HTBprintf(&my_query,
11684 				  "%s.x=0%s%s.y=0%s",
11685 				  escaped1,
11686 				  (PlainText ?
11687 				   "\n" : (SemiColon ?
11688 					   ";" : "&")),
11689 				  escaped1,
11690 				  ((PlainText && *escaped1) ?
11691 				   "\n" : ""));
11692 		    }
11693 		} else {
11694 		    /*
11695 		     * It's a standard submit button.  Use the name=value
11696 		     * pair.  = FM
11697 		     */
11698 		    HTBprintf(&my_query,
11699 			      "%s%s%s%s%s",
11700 			      escaped1,
11701 			      (Boundary ? "" : "="),
11702 			      (PlainText ? "\n" : ""),
11703 			      escaped2,
11704 			      ((PlainText && *escaped2) ? "\n" : ""));
11705 		}
11706 		break;
11707 	    case F_RESET_TYPE:
11708 		/* ignore */
11709 		break;
11710 	    case F_TEXTAREA_TYPE:
11711 		escaped2 = escape_or_quote_value(val_used,
11712 						 my_data[anchor_count].quote);
11713 
11714 		if (my_data[anchor_count].first) {
11715 		    /*
11716 		     * Names are different so this is the first textarea or a
11717 		     * different one from any before it.
11718 		     */
11719 		    if (PlainText) {
11720 			FREE(previous_blanks);
11721 		    } else if (Boundary) {
11722 			StrAllocCopy(previous_blanks, "\r\n");
11723 		    } else {
11724 			StrAllocCopy(previous_blanks, "%0d%0a");
11725 		    }
11726 		    escaped1 = escape_or_quote_name(name_used,
11727 						    my_data[anchor_count].quote,
11728 						    MultipartContentType);
11729 
11730 		    HTBprintf(&my_query,
11731 			      "%s%s%s%s%s",
11732 			      escaped1,
11733 			      (Boundary ? "" : "="),
11734 			      (PlainText ? "\n" : ""),
11735 			      escaped2,
11736 			      ((PlainText && *escaped2) ? "\n" : ""));
11737 		} else {
11738 		    const char *marker = (PlainText
11739 					  ? "\n"
11740 					  : (Boundary
11741 					     ? "\r\n"
11742 					     : "%0d%0a"));
11743 
11744 		    /*
11745 		     * This is a continuation of a previous textarea.
11746 		     */
11747 		    if (escaped2[0] != '\0') {
11748 			if (previous_blanks) {
11749 			    BStrCat0(my_query, previous_blanks);
11750 			    FREE(previous_blanks);
11751 			}
11752 			BStrCat0(my_query, escaped2);
11753 			if (PlainText || Boundary)
11754 			    BStrCat0(my_query, marker);
11755 			else
11756 			    StrAllocCopy(previous_blanks, marker);
11757 		    } else {
11758 			StrAllocCat(previous_blanks, marker);
11759 		    }
11760 		}
11761 		break;
11762 	    case F_RANGE_TYPE:
11763 		/* not implemented */
11764 		break;
11765 #ifdef USE_FILE_UPLOAD
11766 	    case F_FILE_TYPE:
11767 		if (PlainText) {
11768 		    StrAllocCopy(escaped1, my_data[anchor_count].name);
11769 		} else if (Boundary) {
11770 		    const char *t = guess_content_type(val_used);
11771 		    char *copied_fname = NULL;
11772 
11773 		    StrAllocCopy(escaped1, "Content-Disposition: form-data");
11774 		    HTSprintf(&escaped1, "; name=\"%s\"",
11775 			      my_data[anchor_count].name);
11776 
11777 		    StrAllocCopy(copied_fname, val_used);
11778 		    HTMake822Word(&copied_fname, FALSE);
11779 		    HTSprintf(&escaped1, "; filename=\"%s\"", copied_fname);
11780 		    FREE(copied_fname);
11781 
11782 		    /* Should we take into account the encoding? */
11783 		    HTSprintf(&escaped1, "\r\nContent-Type: %s", t);
11784 		    if (my_data[anchor_count].quote == QUOTE_BASE64)
11785 			StrAllocCat(escaped1,
11786 				    "\r\nContent-Transfer-Encoding: base64");
11787 		    StrAllocCat(escaped1, "\r\n\r\n");
11788 		} else {
11789 		    escaped1 = HTEscapeSP(my_data[anchor_count].name, URL_XALPHAS);
11790 		}
11791 
11792 		HTBprintf(&my_query,
11793 			  "%s%s%s",
11794 			  escaped1,
11795 			  (Boundary ? "" : "="),
11796 			  (PlainText ? "\n" : ""));
11797 		/*
11798 		 * If we have anything more than the trailing null we added,
11799 		 * append the file-data to the query.
11800 		 */
11801 		if (BStrLen(my_data[anchor_count].data) > 1) {
11802 		    HTSABCat(&my_query,
11803 			     BStrData(my_data[anchor_count].data),
11804 			     BStrLen(my_data[anchor_count].data) - 1);
11805 		    if (PlainText)
11806 			HTBprintf(&my_query, "\n");
11807 		}
11808 		break;
11809 #endif /* USE_FILE_UPLOAD */
11810 	    case F_KEYGEN_TYPE:
11811 	    case F_BUTTON_TYPE:
11812 		/* not implemented */
11813 		break;
11814 	    }
11815 
11816 	    FREE(escaped1);
11817 	    FREE(escaped2);
11818 	}
11819 	if (Boundary) {
11820 	    HTBprintf(&my_query, "\r\n--%s--\r\n", Boundary);
11821 	}
11822 	/*
11823 	 * The data may contain a null - so we use fwrite().
11824 	 */
11825 	if (TRACE) {
11826 	    CTRACE((tfp, "Query %d{", BStrLen(my_query)));
11827 	    trace_bstring(my_query);
11828 	    CTRACE((tfp, "}\n"));
11829 	}
11830     }
11831 
11832     if (submit_item->submit_method == URL_MAIL_METHOD) {
11833 	HTUserMsg2(gettext("Submitting %s"), submit_item->submit_action);
11834 	HTSABCat(&my_query, "", 1);	/* append null */
11835 	mailform((submit_item->submit_action + 7),
11836 		 (isEmpty(submit_item->submit_title)
11837 		  ? NonNull(HText_getTitle())
11838 		  : submit_item->submit_title),
11839 		 BStrData(my_query),
11840 		 content_type_out);
11841 	result = 0;
11842 	BStrFree(my_query);
11843 	FREE(content_type_out);
11844     } else {
11845 	_statusline(SUBMITTING_FORM);
11846 
11847 	if (submit_item->submit_method == URL_POST_METHOD || Boundary) {
11848 	    LYFreePostData(doc);
11849 	    doc->post_data = my_query;
11850 	    doc->post_content_type = content_type_out;	/* don't free c_t_out */
11851 	    CTRACE((tfp, "GridText - post_data set:\n%s\n", content_type_out));
11852 	    StrAllocCopy(doc->address, submit_item->submit_action);
11853 	} else {		/* GET_METHOD */
11854 	    HTSABCat(&my_query, "", 1);		/* append null */
11855 	    StrAllocCopy(doc->address, BStrData(my_query));	/* FIXME? */
11856 	    LYFreePostData(doc);
11857 	    FREE(content_type_out);
11858 	}
11859 	result = 1;
11860     }
11861 
11862     FREE(MultipartContentType);
11863     FREE(previous_blanks);
11864     FREE(Boundary);
11865     if (my_data != 0) {
11866 	for (anchor_count = 0; anchor_count < anchor_limit; ++anchor_count) {
11867 	    FREE(my_data[anchor_count].name);
11868 	    FREE(my_data[anchor_count].value);
11869 	    BStrFree(my_data[anchor_count].data);
11870 	}
11871 	FREE(my_data);
11872     }
11873 
11874     return (result);
11875 }
11876 
HText_DisableCurrentForm(void)11877 void HText_DisableCurrentForm(void)
11878 {
11879     TextAnchor *anchor_ptr;
11880 
11881     HTFormDisabled = TRUE;
11882     if (HTMainText != NULL) {
11883 	/*
11884 	 * Go through list of anchors and set the disabled flag.
11885 	 */
11886 	for (anchor_ptr = HTMainText->first_anchor;
11887 	     anchor_ptr != NULL;
11888 	     anchor_ptr = anchor_ptr->next) {
11889 
11890 	    if (anchor_ptr->link_type == INPUT_ANCHOR &&
11891 		anchor_ptr->input_field->number == HTFormNumber) {
11892 
11893 		anchor_ptr->input_field->disabled = TRUE;
11894 	    }
11895 	}
11896     }
11897     return;
11898 }
11899 
HText_ResetForm(FormInfo * form)11900 void HText_ResetForm(FormInfo * form)
11901 {
11902     TextAnchor *anchor_ptr;
11903 
11904     _statusline(RESETTING_FORM);
11905     if (HTMainText == 0)
11906 	return;
11907 
11908     /*
11909      * Go through list of anchors and reset values.
11910      */
11911     for (anchor_ptr = HTMainText->first_anchor;
11912 	 anchor_ptr != NULL;
11913 	 anchor_ptr = anchor_ptr->next) {
11914 	if (anchor_ptr->link_type == INPUT_ANCHOR) {
11915 	    if (anchor_ptr->input_field->number == form->number) {
11916 
11917 		if (anchor_ptr->input_field->type == F_RADIO_TYPE ||
11918 		    anchor_ptr->input_field->type == F_CHECKBOX_TYPE) {
11919 
11920 		    if (anchor_ptr->input_field->orig_value[0] == '0')
11921 			anchor_ptr->input_field->num_value = 0;
11922 		    else
11923 			anchor_ptr->input_field->num_value = 1;
11924 
11925 		} else if (anchor_ptr->input_field->type ==
11926 			   F_OPTION_LIST_TYPE) {
11927 		    anchor_ptr->input_field->value =
11928 			anchor_ptr->input_field->orig_value;
11929 
11930 		    anchor_ptr->input_field->cp_submit_value =
11931 			anchor_ptr->input_field->orig_submit_value;
11932 
11933 		} else {
11934 		    StrAllocCopy(anchor_ptr->input_field->value,
11935 				 anchor_ptr->input_field->orig_value);
11936 		}
11937 	    } else if (anchor_ptr->input_field->number > form->number) {
11938 		break;
11939 	    }
11940 	}
11941     }
11942 }
11943 
11944 /*
11945  * This function is called before reloading/reparsing current document to find
11946  * whether any forms content was changed by user so any information will be
11947  * lost.
11948  */
HText_HaveUserChangedForms(HText * text)11949 BOOLEAN HText_HaveUserChangedForms(HText *text)
11950 {
11951     TextAnchor *anchor_ptr;
11952 
11953     if (text == 0)
11954 	return FALSE;
11955 
11956     /*
11957      * Go through list of anchors to check if any value was changed.
11958      * This code based on HText_ResetForm()
11959      */
11960     for (anchor_ptr = text->first_anchor;
11961 	 anchor_ptr != NULL;
11962 	 anchor_ptr = anchor_ptr->next) {
11963 	if (anchor_ptr->link_type == INPUT_ANCHOR) {
11964 
11965 	    if (anchor_ptr->input_field->type == F_RADIO_TYPE ||
11966 		anchor_ptr->input_field->type == F_CHECKBOX_TYPE) {
11967 
11968 		if ((anchor_ptr->input_field->orig_value[0] == '0' &&
11969 		     anchor_ptr->input_field->num_value == 1) ||
11970 		    (anchor_ptr->input_field->orig_value[0] != '0' &&
11971 		     anchor_ptr->input_field->num_value == 0))
11972 		    return TRUE;
11973 
11974 	    } else if (anchor_ptr->input_field->type == F_OPTION_LIST_TYPE) {
11975 		if (strcmp(anchor_ptr->input_field->value,
11976 			   anchor_ptr->input_field->orig_value))
11977 		    return TRUE;
11978 
11979 		if (strcmp(anchor_ptr->input_field->cp_submit_value,
11980 			   anchor_ptr->input_field->orig_submit_value))
11981 		    return TRUE;
11982 
11983 	    } else {
11984 		if (strcmp(anchor_ptr->input_field->value,
11985 			   anchor_ptr->input_field->orig_value))
11986 		    return TRUE;
11987 	    }
11988 	}
11989     }
11990     return FALSE;
11991 }
11992 
HText_activateRadioButton(FormInfo * form)11993 void HText_activateRadioButton(FormInfo * form)
11994 {
11995     TextAnchor *anchor_ptr;
11996     int form_number = form->number;
11997 
11998     if (!HTMainText)
11999 	return;
12000     for (anchor_ptr = HTMainText->first_anchor;
12001 	 anchor_ptr != NULL;
12002 	 anchor_ptr = anchor_ptr->next) {
12003 	if (anchor_ptr->link_type == INPUT_ANCHOR &&
12004 	    anchor_ptr->input_field->type == F_RADIO_TYPE) {
12005 
12006 	    if (anchor_ptr->input_field->number == form_number) {
12007 
12008 		/* if it has the same name and its on */
12009 		if (!strcmp(anchor_ptr->input_field->name, form->name) &&
12010 		    anchor_ptr->input_field->num_value) {
12011 		    anchor_ptr->input_field->num_value = 0;
12012 		    break;
12013 		}
12014 	    } else if (anchor_ptr->input_field->number > form_number) {
12015 		break;
12016 	    }
12017 
12018 	}
12019     }
12020 
12021     form->num_value = 1;
12022 }
12023 
12024 #ifdef LY_FIND_LEAKS
12025 /*
12026  *	Purpose:	Free all currently loaded HText objects in memory.
12027  *	Arguments:	void
12028  *	Return Value:	void
12029  *	Remarks/Portability/Dependencies/Restrictions:
12030  *		Usage of this function should really be limited to program
12031  *			termination.
12032  *	Revision History:
12033  *		05-27-94	created Lynx 2-3-1 Garrett Arch Blythe
12034  */
free_all_texts(void)12035 static void free_all_texts(void)
12036 {
12037     HText *cur = NULL;
12038 
12039     if (!loaded_texts)
12040 	return;
12041 
12042     /*
12043      * Simply loop through the loaded texts list killing them off.
12044      */
12045     while (loaded_texts && !HTList_isEmpty(loaded_texts)) {
12046 	if ((cur = (HText *) HTList_removeLastObject(loaded_texts)) != NULL) {
12047 	    HText_free(cur);
12048 	}
12049     }
12050 
12051     /*
12052      * Get rid of the text list.
12053      */
12054     if (loaded_texts) {
12055 	HTList_delete(loaded_texts);
12056     }
12057 
12058     /*
12059      * Insurance for bad HTML.
12060      */
12061     FREE(HTCurSelectGroup);
12062     FREE(HTCurSelectGroupSize);
12063     FREE(HTCurSelectedOptionValue);
12064     PerFormInfo_free(HTCurrentForm);
12065 
12066     return;
12067 }
12068 #endif /* LY_FIND_LEAKS */
12069 
12070 /*
12071  *  stub_HTAnchor_address is like HTAnchor_address, but it returns the
12072  *  parent address for child links.  This is only useful for traversals
12073  *  where one does not want to index a text file N times, once for each
12074  *  of N internal links.  Since the parent link has already been taken,
12075  *  it won't go again, hence the (incorrect) links won't cause problems.
12076  */
stub_HTAnchor_address(HTAnchor * me)12077 char *stub_HTAnchor_address(HTAnchor * me)
12078 {
12079     char *addr = NULL;
12080 
12081     if (me)
12082 	StrAllocCopy(addr, me->parent->address);
12083     return addr;
12084 }
12085 
HText_setToolbar(HText * text)12086 void HText_setToolbar(HText *text)
12087 {
12088     if (text)
12089 	text->toolbar = TRUE;
12090     return;
12091 }
12092 
HText_hasToolbar(HText * text)12093 BOOL HText_hasToolbar(HText *text)
12094 {
12095     return (BOOL) ((text && text->toolbar) ? TRUE : FALSE);
12096 }
12097 
HText_setNoCache(HText * text)12098 void HText_setNoCache(HText *text)
12099 {
12100     if (text)
12101 	text->no_cache = TRUE;
12102     return;
12103 }
12104 
HText_hasNoCacheSet(HText * text)12105 BOOL HText_hasNoCacheSet(HText *text)
12106 {
12107     return (BOOL) ((text && text->no_cache) ? TRUE : FALSE);
12108 }
12109 
HText_hasUTF8OutputSet(HText * text)12110 BOOL HText_hasUTF8OutputSet(HText *text)
12111 {
12112     return (BOOL) ((text && text->T.output_utf8) ? TRUE : FALSE);
12113 }
12114 
12115 /*
12116  *  Check charset and set the kcode element. -FM
12117  *  Info on the input charset may be passed in in two forms,
12118  *  as a string (if given explicitly) and as a pointer to
12119  *  a LYUCcharset (from chartrans mechanism); either can be NULL.
12120  *  For Japanese the kcode will be reset at a space or explicit
12121  *  line or paragraph break, so what we set here may not last for
12122  *  long.  It's potentially more important not to set HTCJK to
12123  *  NOCJK unless we are sure. - kw
12124  */
HText_setKcode(HText * text,const char * charset,LYUCcharset * p_in)12125 void HText_setKcode(HText *text, const char *charset,
12126 		    LYUCcharset *p_in)
12127 {
12128     BOOL charset_explicit;
12129 
12130     if (!text)
12131 	return;
12132 
12133     /*
12134      * Check whether we have some kind of info.  - kw
12135      */
12136     if (!charset && !p_in) {
12137 	return;
12138     }
12139     charset_explicit = (BOOLEAN) (charset ? TRUE : FALSE);
12140     /*
12141      * If no explicit charset string, use the implied one.  - kw
12142      */
12143     if (isEmpty(charset)) {
12144 	charset = p_in->MIMEname;
12145     }
12146     /*
12147      * Check whether we have a specified charset.  -FM
12148      */
12149     if (isEmpty(charset)) {
12150 	return;
12151     }
12152 
12153     /*
12154      * We've included the charset, and not forced a download offer,
12155      * only if the currently selected character set can handle it,
12156      * so check the charset value and set the text->kcode element
12157      * appropriately.  -FM
12158      */
12159     /*  If charset isn't specified explicitely nor assumed,
12160      * p_in->MIMEname would be set as display charset.
12161      * So text->kcode sholud be set as SJIS or EUC here only if charset
12162      * is specified explicitely, otherwise text->kcode would cause
12163      * mishandling Japanese strings.  -- TH
12164      */
12165     if (charset_explicit && (!strcmp(charset, "shift_jis") ||
12166 			     !strcmp(charset, "x-sjis") ||	/* 1997/11/28 (Fri) 18:11:33 */
12167 			     !strcmp(charset, "x-shift-jis"))) {
12168 	text->kcode = SJIS;
12169     } else if (charset_explicit
12170 #ifdef EXP_JAPANESEUTF8_SUPPORT
12171 	       && strcmp(charset, "utf-8")
12172 #endif
12173 	       && ((p_in && (p_in->enc == UCT_ENC_CJK)) ||
12174 		   !strcmp(charset, "x-euc") ||		/* 1997/11/28 (Fri) 18:11:24 */
12175 		   !strcmp(charset, "euc-jp") ||
12176 		   !StrNCmp(charset, "x-euc-", 6) ||
12177 		   !strcmp(charset, "euc-kr") ||
12178 		   !strcmp(charset, "iso-2022-kr") ||
12179 		   !strcmp(charset, "big5") ||
12180 		   !strcmp(charset, "cn-big5") ||
12181 		   !strcmp(charset, "euc-cn") ||
12182 		   !strcmp(charset, "gb2312") ||
12183 		   !StrNCmp(charset, "cn-gb", 5) ||
12184 		   !strcmp(charset, "iso-2022-cn"))) {
12185 	text->kcode = EUC;
12186     } else {
12187 	/*
12188 	 * If we get to here, it's not CJK, so disable that if
12189 	 * it is enabled.  But only if we are quite sure.  -FM & kw
12190 	 */
12191 	text->kcode = NOKANJI;
12192 	if (IS_CJK_TTY) {
12193 	    if (!p_in || ((p_in->enc != UCT_ENC_CJK)
12194 #ifdef EXP_JAPANESEUTF8_SUPPORT
12195 			  && (p_in->enc != UCT_ENC_UTF8)
12196 #endif
12197 		)) {
12198 		HTCJK = NOCJK;
12199 	    }
12200 	}
12201     }
12202 
12203     if (charset_explicit
12204 #ifdef EXP_JAPANESEUTF8_SUPPORT
12205 	&& strcmp(charset, "utf-8")
12206 #endif
12207 	) {
12208 	text->specified_kcode = text->kcode;
12209     } else {
12210 	if (UCAssume_MIMEcharset) {
12211 	    if (!strcmp(UCAssume_MIMEcharset, "euc-jp"))
12212 		text->kcode = text->specified_kcode = EUC;
12213 	    else if (!strcmp(UCAssume_MIMEcharset, "shift_jis"))
12214 		text->kcode = text->specified_kcode = SJIS;
12215 	}
12216     }
12217 
12218     return;
12219 }
12220 
12221 /*
12222  *  Set a permissible split at the current end of the last line. -FM
12223  */
HText_setBreakPoint(HText * text)12224 void HText_setBreakPoint(HText *text)
12225 {
12226     if (!text)
12227 	return;
12228 
12229     /*
12230      * Can split here.  -FM
12231      */
12232     text->permissible_split = text->last_line->size;
12233 
12234     return;
12235 }
12236 
12237 /*
12238  *  This function determines whether a document which
12239  *  would be sought via the a URL that has a fragment
12240  *  directive appended is otherwise identical to the
12241  *  currently loaded document, and if so, returns
12242  *  FALSE, so that any no_cache directives can be
12243  *  overridden "safely", on the grounds that we are
12244  *  simply acting on the equivalent of a paging
12245  *  command.  Otherwise, it returns TRUE, i.e, that
12246  *  the target document might differ from the current,
12247  *  based on any caching directives or analyses which
12248  *  claimed or suggested this. -FM
12249  */
HText_AreDifferent(HTParentAnchor * anchor,const char * full_address)12250 BOOL HText_AreDifferent(HTParentAnchor *anchor,
12251 			const char *full_address)
12252 {
12253     HTParentAnchor *MTanc;
12254     char *MTaddress;
12255     char *MTpound;
12256 
12257     /*
12258      * Do we have a loaded document and both
12259      * arguments for this function?
12260      */
12261     if (!(HTMainText && anchor && full_address))
12262 	return TRUE;
12263 
12264     /*
12265      * Do we have both URLs?
12266      */
12267     MTanc = HTMainText->node_anchor;
12268     if (!(MTanc->address && anchor->address))
12269 	return (TRUE);
12270 
12271     /*
12272      * Do we have a fragment associated with the target?
12273      */
12274     if (findPoundSelector(full_address) == NULL)
12275 	return (TRUE);
12276 
12277     /*
12278      * Always treat client-side image map menus
12279      * as potentially stale, so we'll create a
12280      * fresh menu from the LynxMaps HTList.
12281      */
12282     if (isLYNXIMGMAP(anchor->address))
12283 	return (TRUE);
12284 
12285     /*
12286      * Do the docs differ in the type of request?
12287      */
12288     if (MTanc->isHEAD != anchor->isHEAD)
12289 	return (TRUE);
12290 
12291     /*
12292      * Are the actual URLs different, after factoring
12293      * out a "LYNXIMGMAP:" leader in the MainText URL
12294      * and its fragment, if present?
12295      */
12296     MTaddress = (isLYNXIMGMAP(MTanc->address)
12297 		 ? MTanc->address + LEN_LYNXIMGMAP
12298 		 : MTanc->address);
12299     MTpound = trimPoundSelector(MTaddress);
12300     if (strcmp(MTaddress, anchor->address)) {
12301 	restorePoundSelector(MTpound);
12302 	return (TRUE);
12303     }
12304     restorePoundSelector(MTpound);
12305 
12306     /*
12307      * If the MainText is not an image map menu,
12308      * do the docs have different POST contents?
12309      */
12310     if (MTaddress == MTanc->address) {
12311 	if (MTanc->post_data) {
12312 	    if (anchor->post_data) {
12313 		if (!BINEQ(MTanc->post_data, anchor->post_data)) {
12314 		    /*
12315 		     * Both have contents, and they differ.
12316 		     */
12317 		    return (TRUE);
12318 		}
12319 	    } else {
12320 		/*
12321 		 * The loaded document has content, but the
12322 		 * target doesn't, so they're different.
12323 		 */
12324 		return (TRUE);
12325 	    }
12326 	} else if (anchor->post_data) {
12327 	    /*
12328 	     * The loaded document does not have content, but
12329 	     * the target does, so they're different.
12330 	     */
12331 	    return (TRUE);
12332 	}
12333     }
12334 
12335     /*
12336      * We'll assume the target is a position in the currently
12337      * displayed document, and thus can ignore any header, META,
12338      * or other directives not to use a cached rendition.  -FM
12339      */
12340     return (FALSE);
12341 }
12342 
12343 #define CanTrimTextArea(c) \
12344     (LYtrimInputFields ? isspace(c) : ((c) == '\r' || (c) == '\n'))
12345 
12346 /*
12347  * Re-render the text of a tagged ("[123]") HTLine (arg1), with the tag
12348  * number incremented by some value (arg5).  The re-rendered string may
12349  * be allowed to expand in the event of a tag width change (eg, 99 -> 100)
12350  * as controlled by arg6 (CHOP or NOCHOP).  Arg4 is either (the address
12351  * of) a value which must match, in order for the tag to be incremented,
12352  * or (the address of) a 0-value, which will match any value, and cause
12353  * any valid tag to be incremented.  Arg2 is a pointer to the first/only
12354  * anchor that exists on the line; we may need to adjust their position(s)
12355  * on the line.  Arg3 when non-0 indicates the number of new digits that
12356  * were added to the 2nd line in a line crossing pair.
12357  *
12358  * All tags fields in a line which individually match an expected new value,
12359  * are incremented.  Line crossing [tags] are handled (PITA).
12360  *
12361  * Untagged or improperly tagged lines are not altered.
12362  *
12363  * Returns the number of chars added to the original string's length, if
12364  * any.
12365  *
12366  * --KED 02/03/99
12367  */
increment_tagged_htline(HTLine * ht,TextAnchor * a,int * lx_val,int * old_val,int incr,int mode)12368 static int increment_tagged_htline(HTLine *ht, TextAnchor *a, int *lx_val,
12369 				   int *old_val,
12370 				   int incr,
12371 				   int mode)
12372 {
12373     char buf[MAX_LINE];
12374     char lxbuf[MAX_LINE * 2];
12375 
12376     TextAnchor *st_anchor = a;
12377     TextAnchor *nxt_anchor;
12378 
12379     char *p = ht->data;
12380     char *s = buf;
12381     char *lx = lxbuf;
12382     char *t;
12383 
12384     BOOLEAN plx = FALSE;
12385     BOOLEAN valid;
12386 
12387     int val;
12388     int n;
12389     int new_n;
12390     int pre_n;
12391     int post_n;
12392     int fixup = 0;
12393 
12394     /*
12395      * Cleanup for the 2nd half of a line crosser, whose number of tag
12396      * digits grew by some number of places (usually 1 when it does
12397      * happen, though it *could* be more).  The tag chars were already
12398      * rendered into the 2nd line of the pair, but the positioning and
12399      * other effects haven't been rippled through any other anchors on
12400      * the (2nd) line.  So we do that here, as a special case, since
12401      * the part of the tag that's in the 2nd line of the pair, will not
12402      * be found by the tag string parsing code.  Double PITA.
12403      *
12404      * [see comments below on line crosser caused problems]
12405      */
12406     if (*lx_val != 0) {
12407 	nxt_anchor = st_anchor;
12408 	while ((nxt_anchor) && (nxt_anchor->line_num == a->line_num)) {
12409 	    nxt_anchor->line_pos = (short) (nxt_anchor->line_pos + *lx_val);
12410 	    nxt_anchor = nxt_anchor->next;
12411 	}
12412 	fixup = *lx_val;
12413 	*lx_val = 0;
12414 	if (st_anchor)
12415 	    st_anchor = st_anchor->next;
12416     }
12417 
12418     /*
12419      * Walk thru the line looking for tags (ie, "[nnn]" strings).
12420      */
12421     while (*p != '\0') {
12422 	if (*p != '[') {
12423 	    *s++ = *p++;
12424 	    continue;
12425 
12426 	} else {
12427 	    *s++ = *p++;
12428 	    t = p;
12429 	    n = 0;
12430 	    valid = TRUE;	/* p = t = byte after '[' */
12431 
12432 	    /*
12433 	     * Make sure there are only digits between "[" and "]".
12434 	     */
12435 	    while (*t != ']') {
12436 		if (*t == '\0') {	/* uhoh - we have a potential line crosser */
12437 		    valid = FALSE;
12438 		    plx = TRUE;
12439 		    break;
12440 		}
12441 		if (isdigit(UCH(*t++))) {
12442 		    n++;
12443 		    continue;
12444 		} else {
12445 		    valid = FALSE;
12446 		    break;
12447 		}
12448 	    }
12449 
12450 	    /*
12451 	     * If the format is OK, we check to see if the value is what
12452 	     * we expect.  If not, we have a random [nn] string in the text,
12453 	     * and leave it alone.
12454 	     *
12455 	     * [It is *possible* to have a false match here, *if* there are
12456 	     * two identical [nn] strings (including the numeric value of
12457 	     * nn), one of which is the [tag], and the other being part of
12458 	     * a document.  In such a case, the 1st [nn] string will get
12459 	     * incremented; the 2nd one won't, which makes it a 50-50 chance
12460 	     * of being correct, if and when such an unlikely juxtaposition
12461 	     * of text ever occurs.  Further validation tests of the [nnn]
12462 	     * string are probably not possible, since little of the actual
12463 	     * anchor-associated-text is retained in the TextAnchor or the
12464 	     * FormInfo structs.  Fortunately, I think the current method is
12465 	     * more than adequate to weed out 99.999% of any possible false
12466 	     * matches, just as it stands.  Caveat emptor.]
12467 	     */
12468 	    if ((valid) && (n > 0)) {
12469 		val = atoi(p);
12470 		if ((val == *old_val) || (*old_val == 0)) {	/* 0 matches all */
12471 		    if (*old_val != 0)
12472 			(*old_val)++;
12473 		    val += incr;
12474 		    sprintf(s, "%d", val);
12475 		    new_n = (int) strlen(s);
12476 		    s += new_n;
12477 		    p += n;
12478 
12479 		    /*
12480 		     * If the number of digits in an existing [tag] increased
12481 		     * (eg, [99] --> [100], etc), we need to "adjust" its
12482 		     * horizontal position, and that of all subsequent tags
12483 		     * that may be on the same line.  PITA.
12484 		     *
12485 		     * [This seems to work as long as a tag isn't a line
12486 		     * crosser; when it is, the position of anchors on either
12487 		     * side of the split tag, seem to "float" and try to be
12488 		     * as "centered" as possible.  Which means that simply
12489 		     * incrementing the line_pos by the fixed value of the
12490 		     * number of digits that got added to some tag in either
12491 		     * line doesn't work quite right, and the text for (say)
12492 		     * a button may get stomped on by another copy of itself,
12493 		     * but offset by a few chars, when it is selected (eg,
12494 		     * "Box Office" may end up looking like "BoBox Office" or
12495 		     * "Box Officece", etc.
12496 		     *
12497 		     * Dunno how to fix that behavior ATT, but at least the
12498 		     * tag numbers themselves are correct.  -KED /\oo/\ ]
12499 		     */
12500 		    if ((new_n -= n) != 0) {
12501 			nxt_anchor = st_anchor;
12502 			while ((nxt_anchor) &&
12503 			       (nxt_anchor->line_num == a->line_num)) {
12504 			    nxt_anchor->line_pos = (short) (nxt_anchor->line_pos
12505 							    + new_n);
12506 			    nxt_anchor = nxt_anchor->next;
12507 			}
12508 			if (st_anchor)
12509 			    st_anchor = st_anchor->next;
12510 		    }
12511 		}
12512 	    }
12513 
12514 	    /*
12515 	     * Unfortunately, valid [tag] strings *can* be split across two
12516 	     * lines.  Perhaps it would be best to just prevent that from
12517 	     * happening, but a look into that code, makes me wonder.  Anyway,
12518 	     * we can handle such tags without *too* much trouble in here [I
12519 	     * think], though since such animals are rather rare, it makes it
12520 	     * a bit difficult to test thoroughly (ie, Beyond here, there be
12521 	     * Dragons).
12522 	     *
12523 	     * We use lxbuf[] to deal with the two lines involved.
12524 	     */
12525 	    pre_n = (int) strlen(p);	/* count of 1st part chars in this line */
12526 	    post_n = (int) strlen(ht->next->data);
12527 	    if (plx
12528 		&& (pre_n + post_n + 2 < (int) sizeof(lxbuf))) {
12529 		strcpy(lx, p);	/* <- 1st part of a possible lx'ing tag */
12530 		strcat(lx, ht->next->data);	/* tack on NEXT line          */
12531 
12532 		t = lx;
12533 		n = 0;
12534 		valid = TRUE;
12535 
12536 		/*
12537 		 * Go hunting again for just digits, followed by tag end ']'.
12538 		 */
12539 		while (*t != ']') {
12540 		    if (isdigit(UCH(*t++))) {
12541 			n++;
12542 			continue;
12543 		    } else {
12544 			valid = FALSE;
12545 			break;
12546 		    }
12547 		}
12548 
12549 		/*
12550 		 * It *looks* like a line crosser; now we value test it to
12551 		 * find out for sure [but see the "false match" warning,
12552 		 * above], and if it matches, increment it into the buffer,
12553 		 * along with the 2nd line's text.
12554 		 */
12555 		if ((valid)
12556 		    && (n > 0)
12557 		    && (n + post_n + 2) < MAX_LINE) {
12558 		    val = atoi(lx);
12559 		    if ((val == *old_val) || (*old_val == 0)) {
12560 			const char *r;
12561 
12562 			if (*old_val != 0)
12563 			    (*old_val)++;
12564 			val += incr;
12565 			sprintf(lx, "%d", val);
12566 			new_n = (int) strlen(lx);
12567 			if ((r = strchr(ht->next->data, ']')) == 0) {
12568 			    r = "";
12569 			}
12570 			strcat(lx, r);
12571 
12572 			/*
12573 			 * We keep the the same number of chars from the
12574 			 * adjusted tag number in the current line; any
12575 			 * extra chars due to a digits increase, will be
12576 			 * stuffed into the next line.
12577 			 *
12578 			 * Keep track of any digits added, for the next
12579 			 * pass through.
12580 			 */
12581 			s = StrNCpy(s, lx, pre_n) + pre_n;
12582 			lx += pre_n;
12583 			strcpy(ht->next->data, lx);
12584 
12585 			*lx_val = new_n - n;
12586 		    }
12587 		}
12588 		break;		/* had an lx'er, so we're done with this line */
12589 	    }
12590 	}
12591     }
12592 
12593     *s = '\0';
12594 
12595     n = (int) strlen(ht->data);
12596     if (mode == CHOP) {
12597 	*(buf + n) = '\0';
12598     } else if (strlen(buf) > ht->size) {
12599 	/* we didn't allocate enough space originally - increase it */
12600 	HTLine *temp;
12601 
12602 	allocHTLine(temp, strlen(buf));
12603 	if (!temp)
12604 	    outofmem(__FILE__, "increment_tagged_htline");
12605 	assert(temp != NULL);
12606 
12607 	memcpy(temp, ht, LINE_SIZE(0));
12608 #if defined(USE_COLOR_STYLE)
12609 	POOLallocstyles(temp->styles, ht->numstyles);
12610 	if (!temp->styles)
12611 	    outofmem(__FILE__, "increment_tagged_htline");
12612 	assert(temp->styles != NULL);
12613 	memcpy(temp->styles, ht->styles, sizeof(HTStyleChange) * ht->numstyles);
12614 #endif
12615 	ht = temp;
12616 	ht->prev->next = ht;	/* Link in new line */
12617 	ht->next->prev = ht;	/* Could be same node of course */
12618     }
12619     strcpy(ht->data, buf);
12620 
12621     return ((int) strlen(buf) - n + fixup);
12622 }
12623 
12624 /*
12625  * Creates a new anchor and associated structs appropriate for a form
12626  * TEXTAREA, and links them into the lists following the current anchor
12627  * position (as specified by arg1).
12628  *
12629  * Exits with arg1 now pointing at the new TextAnchor, and arg2 pointing
12630  * at the new, associated HTLine.
12631  *
12632  * --KED 02/13/99
12633  */
insert_new_textarea_anchor(TextAnchor ** curr_anchor,HTLine ** exit_htline)12634 static void insert_new_textarea_anchor(TextAnchor **curr_anchor, HTLine **exit_htline)
12635 {
12636     TextAnchor *anchor = *curr_anchor;
12637     HTLine *htline;
12638 
12639     TextAnchor *a = 0;
12640     FormInfo *f = 0;
12641     HTLine *l = 0;
12642 
12643     int curr_tag = 0;		/* 0 ==> match any [tag] number */
12644     int lx = 0;			/* 0 ==> no line crossing [tag]; it's a new line */
12645     int i;
12646 
12647     /*
12648      * Find line in the text that matches ending anchorline of
12649      * the TEXTAREA.
12650      *
12651      * [Yes, Virginia ...  we *do* have to go thru this for each
12652      * anchor being added, since there is NOT a 1-to-1 mapping
12653      * between anchors and htlines.  I suppose we could create
12654      * YAS (Yet Another Struct), but there are too many structs{}
12655      * floating around in here, as it is.  IMNSHO.]
12656      */
12657     for (htline = FirstHTLine(HTMainText), i = 0; anchor->line_num != i; i++) {
12658 	htline = htline->next;
12659 	if (htline == HTMainText->last_line)
12660 	    break;
12661     }
12662 
12663     /*
12664      * Clone and initialize the structs needed to add a new TEXTAREA
12665      * anchor.
12666      */
12667     allocHTLine(l, MAX_LINE);
12668     POOLtypecalloc(TextAnchor, a);
12669 
12670     POOLtypecalloc(FormInfo, f);
12671     if (a == NULL || l == NULL || f == NULL)
12672 	outofmem(__FILE__, "insert_new_textarea_anchor");
12673 
12674     assert(a != NULL);
12675     assert(f != NULL);
12676     assert(l != NULL);
12677 
12678     /*  Init all the fields in the new TextAnchor.                 */
12679     /*  [anything "special" needed based on ->show_anchor value ?] */
12680     a->next = anchor->next;
12681     a->number = anchor->number;
12682     a->line_pos = anchor->line_pos;
12683     a->extent = anchor->extent;
12684     a->sgml_offset = SGML_offset();
12685     a->line_num = anchor->line_num + 1;
12686     LYCopyHiText(a, anchor);
12687     a->link_type = anchor->link_type;
12688     a->input_field = f;
12689     a->show_anchor = anchor->show_anchor;
12690     a->inUnderline = anchor->inUnderline;
12691     a->expansion_anch = TRUE;
12692     a->anchor = NULL;
12693 
12694     /*  Just the (seemingly) relevant fields in the new FormInfo.  */
12695     /*  [do we need to do anything "special" based on ->disabled]  */
12696     StrAllocCopy(f->name, anchor->input_field->name);
12697     f->number = anchor->input_field->number;
12698     f->type = anchor->input_field->type;
12699     StrAllocCopy(f->orig_value, "");
12700     f->size = anchor->input_field->size;
12701     f->maxlength = anchor->input_field->maxlength;
12702     f->no_cache = anchor->input_field->no_cache;
12703     f->disabled = anchor->input_field->disabled;
12704     f->readonly = anchor->input_field->readonly;
12705     f->value_cs = current_char_set;	/* use current setting - kw */
12706 
12707     /*  Init all the fields in the new HTLine (but see the #if).   */
12708     l->next = htline->next;
12709     l->prev = htline;
12710     l->offset = htline->offset;
12711     l->size = htline->size;
12712 #if defined(USE_COLOR_STYLE)
12713     /* dup styles[] if needed [no need in TEXTAREA (?); leave 0s] */
12714     l->numstyles = htline->numstyles;
12715     /*we fork the pointers! */
12716     l->styles = htline->styles;
12717 #endif
12718     strcpy(l->data, htline->data);
12719 
12720     /*
12721      * Link in the new HTLine.
12722      */
12723     htline->next->prev = l;
12724     htline->next = l;
12725 
12726     if (fields_are_numbered()) {
12727 	a->number++;
12728 	increment_tagged_htline(l, a, &lx, &curr_tag, 1, CHOP);
12729     }
12730 
12731     /*
12732      * If we're at the tail end of the TextAnchor or HTLine list(s),
12733      * the new node becomes the last node.
12734      */
12735     if (anchor == HTMainText->last_anchor)
12736 	HTMainText->last_anchor = a;
12737     if (htline == HTMainText->last_line)
12738 	HTMainText->last_line = l;
12739 
12740     /*
12741      * Link in the new TextAnchor and point the entry anchor arg at it;
12742      * point the entry HTLine arg at it, too.
12743      */
12744     anchor->next = a;
12745     *curr_anchor = a;
12746 
12747     *exit_htline = l->next;
12748 
12749     return;
12750 }
12751 
12752 /*
12753  * If new anchors were added to expand a TEXTAREA, we need to ripple the
12754  * new line numbers [and char counts ?] thru the subsequent anchors.
12755  *
12756  * If form lines are getting [nnn] tagged, we need to update the displayed
12757  * tag values to match (which means rerendering them ...  sigh).
12758  *
12759  * Finally, we need to update various HTMainText and other counts, etc.
12760  *
12761  * [dunno if the char counts really *need* to be done, or if we're using
12762  * the exactly proper values/algorithms ...  seems to be OK though ...]
12763  *
12764  * --KED 02/13/99
12765  */
update_subsequent_anchors(int newlines,TextAnchor * start_anchor,HTLine * start_htline,int start_tag)12766 static void update_subsequent_anchors(int newlines,
12767 				      TextAnchor *start_anchor,
12768 				      HTLine *start_htline,
12769 				      int start_tag)
12770 {
12771     TextAnchor *anchor;
12772     HTLine *htline = start_htline;
12773 
12774     int line_adj = 0;
12775     int tag_adj = 0;
12776     int lx = 0;
12777     int hang = 0;		/* for HANG detection of a nasty intermittent */
12778     int hang_detect = 100000;	/* ditto */
12779 
12780     CTRACE((tfp, "GridText: adjusting structs to add %d new line(s)\n", newlines));
12781 
12782     /*
12783      * Update numeric fields of the rest of the anchors.
12784      *
12785      * [We bypass bumping ->number if it has a value of 0, which takes care
12786      * of the ->input_field->type == F_HIDDEN_TYPE (as well as any other
12787      * "hidden" anchors, if such things exist).  Seems like the "right
12788      * thing" to do.  I think.]
12789      */
12790     anchor = start_anchor->next;	/* begin updating with the NEXT anchor */
12791     while (anchor) {
12792 	if (fields_are_numbered() &&
12793 	    (anchor->number != 0))
12794 	    anchor->number += newlines;
12795 	anchor->line_num += newlines;
12796 	anchor = anchor->next;
12797     }
12798 
12799     /*
12800      * Update/rerender anchor [tags], if they are being numbered.
12801      *
12802      * [If a number tag (eg, "[177]") is itself broken across a line
12803      * boundary, this fixup only partially works.  While the tag
12804      * numbering is done properly across the pair of lines, the
12805      * horizontal positioning on *either* side of the split, can get
12806      * out of sync by a char or two when it gets selected.  See the
12807      * [comments] in increment_tagged_htline() for some more detail.
12808      *
12809      * I suppose THE fix is to prevent such tag-breaking in the first
12810      * place (dunno where yet, though).  Ah well ...  at least the tag
12811      * numbers themselves are correct from top to bottom now.
12812      *
12813      * All that said, about the only time this will be a problem in
12814      * *practice*, is when a page has near 1000 links or more (possibly
12815      * after a TEXTAREA expansion), and has line crossing tag(s), and
12816      * the tag numbers in a line crosser go from initially all 3 digit
12817      * numbers, to some mix of 3 and 4 digits (or all 4 digits) as a
12818      * result of the expansion process.  Oh, you also need a "clump" of
12819      * anchors all on the same lines.
12820      *
12821      * Yes, it *can* happen, but in real life, it probably won't be
12822      * seen very much ...]
12823      *
12824      * [This may also be an artifact of bumping into the right hand
12825      * screen edge (or RHS margin), since we don't even *think* about
12826      * relocating an anchor to the following line, when [tag] digits
12827      * expansion pushes things too far in that direction.]
12828      */
12829     if (fields_are_numbered()) {
12830 	anchor = start_anchor->next;
12831 	while (htline != FirstHTLine(HTMainText)) {
12832 
12833 	    while (anchor) {
12834 		if ((anchor->number - newlines) == start_tag)
12835 		    break;
12836 
12837 		/*** A HANG (infinite loop) *has* occurred here, with */
12838 		/*** the values of anchor and anchor->next being the  */
12839 		/*** the same, OR with anchor->number "magically" and */
12840 		/*** suddenly taking on an anchor-pointer-like value. */
12841 		/***                                                  */
12842 		/*** The same code and same doc have both passed and  */
12843 		/*** failed at different times, which indicates some  */
12844 		/*** sort of content/html dependency, or some kind of */
12845 		/*** a "race" condition, but I'll be damned if I can  */
12846 		/*** find it after tons of CTRACEs, printf()s, gdb    */
12847 		/*** breakpoints and watchpoints, etc.                */
12848 		/***                                                  */
12849 		/*** I have added a hang detector (with error msg and */
12850 		/*** beep) here, to break the loop and warn the user, */
12851 		/*** until it can be isolated and fixed.              */
12852 		/***                                                  */
12853 		/*** [One UGLY intermittent .. gak ..!  02/22/99 KED] */
12854 
12855 		hang++;
12856 		if ((anchor == anchor->next) || (hang >= hang_detect))
12857 		    goto hang_detected;
12858 
12859 		anchor = anchor->next;
12860 	    }
12861 
12862 	    if (anchor) {
12863 		line_adj = increment_tagged_htline(htline, anchor, &lx,
12864 						   &start_tag, newlines,
12865 						   NOCHOP);
12866 		htline->size = (unsigned short) (htline->size + line_adj);
12867 		tag_adj += line_adj;
12868 
12869 	    } else {
12870 
12871 		break;		/* out of anchors ... we're done */
12872 	    }
12873 
12874 	    htline = htline->next;
12875 	}
12876     }
12877 
12878   finish:
12879     /*
12880      * Fixup various global variables.
12881      */
12882     nlinks += newlines;
12883     HTMainText->Lines += newlines;
12884     HTMainText->last_anchor_number += newlines;
12885 
12886     more_text = HText_canScrollDown();
12887 
12888     CTRACE((tfp, "GridText: TextAnchor and HTLine structs adjusted\n"));
12889 
12890     return;
12891 
12892   hang_detected:		/* ugliness has happened; inform user and do the best we can */
12893 
12894     HTAlert(gettext("Hang Detect: TextAnchor struct corrupted - suggest aborting!"));
12895     goto finish;
12896 }
12897 
12898 /*
12899  * Check if the given anchor is a TEXTAREA belonging to the given form.
12900  *
12901  * KED's note -
12902  * [Finding the TEXTAREA we're actually *in* with these attributes isn't
12903  * foolproof.  The form number isn't unique to a given TEXTAREA, and there
12904  * *could* be TEXTAREAs with the same "name".  If that should ever be true,
12905  * we'll actually get the data from the *1st* TEXTAREA in the page that
12906  * matches.  We should probably assign a unique id to each TEXTAREA in a page,
12907  * and match on that, to avoid this (potential) problem.
12908  *
12909  * Since the odds of "false matches" *actually* happening in real life seem
12910  * rather small though, we'll hold off doing this, for a rainy day ...]
12911  */
IsFormsTextarea(FormInfo * form,TextAnchor * anchor_ptr)12912 static BOOLEAN IsFormsTextarea(FormInfo * form, TextAnchor *anchor_ptr)
12913 {
12914     return (BOOLEAN) ((anchor_ptr->link_type == INPUT_ANCHOR) &&
12915 		      (anchor_ptr->input_field->type == F_TEXTAREA_TYPE) &&
12916 		      (anchor_ptr->input_field->number == form->number) &&
12917 		      !strcmp(anchor_ptr->input_field->name, form->name));
12918 }
12919 
readEditedFile(char * ed_temp)12920 static char *readEditedFile(char *ed_temp)
12921 {
12922     struct stat stat_info;
12923     size_t size;
12924 
12925     FILE *fp;
12926 
12927     char *ebuf;
12928 
12929     CTRACE((tfp, "GridText: entered HText_EditTextArea()\n"));
12930 
12931     /*
12932      * Read back the edited temp file into our buffer.
12933      */
12934     if ((stat(ed_temp, &stat_info) < 0) ||
12935 	!S_ISREG(stat_info.st_mode) ||
12936 	((size = (size_t) stat_info.st_size) == 0)) {
12937 	size = 0;
12938 	ebuf = typecalloc(char);
12939 
12940 	if (!ebuf)
12941 	    outofmem(__FILE__, "HText_EditTextArea");
12942 
12943 	assert(ebuf != NULL);
12944     } else {
12945 	ebuf = typecallocn(char, size + 1);
12946 
12947 	if (!ebuf) {
12948 	    /*
12949 	     * This could be huge - don't exit if we don't have enough
12950 	     * memory for it.  With some luck, the user may be even able
12951 	     * to recover the file manually from the temp space while
12952 	     * the lynx session is not over.  - kw
12953 	     */
12954 	    HTAlwaysAlert(NULL, MEMORY_EXHAUSTED_FILE);
12955 	    return 0;
12956 	}
12957 	assert(ebuf != NULL);
12958 
12959 	if ((fp = fopen(ed_temp, "r")) != 0) {
12960 	    size = fread(ebuf, (size_t) 1, size, fp);
12961 	    LYCloseInput(fp);
12962 	    ebuf[size] = '\0';	/* Terminate! - kw */
12963 	} else {
12964 	    size = 0;
12965 	}
12966     }
12967 
12968     /*
12969      * Nuke any blank lines from the end of the edited data.
12970      */
12971     while ((size != 0)
12972 	   && (CanTrimTextArea(UCH(ebuf[size - 1])) || (ebuf[size - 1] == '\0')))
12973 	ebuf[--size] = '\0';
12974 
12975     return ebuf;
12976 }
12977 
finish_ExtEditForm(LinkInfo * form_link,TextAnchor * start_anchor,char * ed_temp,int orig_cnt)12978 static int finish_ExtEditForm(LinkInfo * form_link, TextAnchor *start_anchor,
12979 			      char *ed_temp,
12980 			      int orig_cnt)
12981 {
12982     TextAnchor *anchor_ptr;
12983     TextAnchor *end_anchor = NULL;
12984     BOOLEAN wrapalert = FALSE;
12985 
12986     int entry_line = form_link->anchor_line_num;
12987     int exit_line = 0;
12988     int line_cnt = 1;
12989 
12990     HTLine *htline = NULL;
12991 
12992     char *ebuf;
12993     char *line;
12994     char *lp;
12995     char *cp;
12996     int match_tag = 0;
12997     int newlines = 0;
12998     int len, len0;
12999     int display_size;
13000     int wanted_fieldlen_wrap = -1;	/* not yet asked; 0 means don't. */
13001     char *skip_at = NULL;
13002     int skip_num = 0, i;
13003     size_t line_used = MAX_LINE;
13004 
13005     CTRACE((tfp, "GridText: entered HText_EditTextArea()\n"));
13006 
13007     if ((ebuf = readEditedFile(ed_temp)) == 0) {
13008 	return 0;
13009     }
13010 
13011     /*
13012      * Copy each line from the temp file into the corresponding anchor
13013      * struct.  Add new lines to the TEXTAREA if needed.  (Always leave
13014      * the user with a blank line at the end of the TEXTAREA.)
13015      */
13016     if ((line = typeMallocn(char, line_used)) == 0)
13017 	  outofmem(__FILE__, "HText_EditTextArea");
13018 
13019     assert(line != NULL);
13020 
13021     anchor_ptr = start_anchor;
13022     display_size = anchor_ptr->input_field->size;
13023     if (display_size <= 4 ||
13024 	display_size >= MAX_LINE)
13025 	wanted_fieldlen_wrap = 0;
13026 
13027     len = 0;
13028     lp = ebuf;
13029 
13030     while ((line_cnt <= orig_cnt) || (*lp) || ((len != 0) && (*lp == '\0'))) {
13031 
13032 	if (skip_at) {
13033 	    len0 = (int) (skip_at - lp);
13034 	    LYStrNCpy(line, lp, len0);
13035 	    lp = skip_at + skip_num;
13036 	    skip_at = NULL;
13037 	    skip_num = 0;
13038 
13039 	    assert(lp != NULL);
13040 	} else {
13041 	    len0 = 0;
13042 	}
13043 	line[len0] = '\0';
13044 
13045 	if ((cp = strchr(lp, '\n')) != 0)
13046 	    len = (int) (cp - lp);
13047 	else
13048 	    len = (int) strlen(lp);
13049 
13050 	if (wanted_fieldlen_wrap < 0 &&
13051 	    !wrapalert &&
13052 	    len0 + len >= display_size &&
13053 	    (cp = strchr(lp, ' ')) != NULL &&
13054 	    (cp - lp) < display_size - 1) {
13055 
13056 	    LYFixCursesOn("ask for confirmation:");
13057 	    LYerase();		/* don't show previous state */
13058 	    if (HTConfirmDefault(gettext("Wrap lines to fit displayed area?"),
13059 				 NO)) {
13060 		wanted_fieldlen_wrap = display_size - 1;
13061 	    } else {
13062 		wanted_fieldlen_wrap = 0;
13063 	    }
13064 	}
13065 
13066 	if (wanted_fieldlen_wrap > 0 &&
13067 	    len0 + len > wanted_fieldlen_wrap) {
13068 
13069 	    for (i = wanted_fieldlen_wrap - len0;
13070 		 i + len0 >= wanted_fieldlen_wrap / 4; i--) {
13071 
13072 		if (isspace(UCH(lp[i]))) {
13073 		    len = i + 1;
13074 		    cp = lp + i;
13075 		    if (cp[1] != '\n' &&
13076 			isspace(UCH(cp[1])) &&
13077 			!isspace(UCH(cp[2]))) {
13078 			len++;
13079 			cp++;
13080 		    }
13081 		    if (!isspace(UCH(cp[1]))) {
13082 			while (*cp && *cp != '\r' && *cp != '\n' &&
13083 			       (cp - lp) <= len + (3 * wanted_fieldlen_wrap / 4))
13084 			    cp++;	/* search for next line break */
13085 			if (*cp == '\r' && cp[1] == '\n')
13086 			    cp++;
13087 			if (*cp == '\n' &&
13088 			    (cp[1] == '\r' || cp[1] == '\n' ||
13089 			     !isspace(UCH(cp[1])))) {
13090 			    *cp = ' ';
13091 			    while (isspace(UCH(*(cp - 1)))) {
13092 				skip_num++;
13093 				cp--;
13094 			    }
13095 			    skip_at = cp;
13096 			}
13097 		    }
13098 		    break;
13099 		}
13100 	    }
13101 	}
13102 
13103 	if (wanted_fieldlen_wrap > 0 &&
13104 	    (len0 + len) > wanted_fieldlen_wrap) {
13105 
13106 	    i = len - 1;
13107 	    while (len0 + i + 1 > wanted_fieldlen_wrap &&
13108 		   isspace(UCH(lp[i])))
13109 		i--;
13110 	    if (len0 + i + 1 > wanted_fieldlen_wrap)
13111 		len = wanted_fieldlen_wrap - len0;
13112 	}
13113 
13114 	/*
13115 	 * Check if the new text will fit in the buffer.  HTML does not define
13116 	 * a "maxlength" attribute for TEXTAREA; its data can grow as needed.
13117 	 * Lynx will not adjust the display to reflect larger amounts of text;
13118 	 * it relies on the rows/cols attributes as well as the initial content
13119 	 * of the text area for the layout.
13120 	 */
13121 	if ((size_t) (len0 + len) >= line_used) {
13122 	    line_used = (size_t) (3 * (len0 + len)) / 2;
13123 	    if ((line = typeRealloc(char, line, line_used)) == 0)
13124 		  outofmem(__FILE__, "HText_EditTextArea");
13125 	}
13126 
13127 	strncat(line, lp, (size_t) len);
13128 	*(line + len0 + len) = '\0';
13129 
13130 	/*
13131 	 * If there are more lines in the edit buffer than were in the
13132 	 * original TEXTAREA, we need to add a new line/anchor, continuing
13133 	 * on until the edit buffer is empty.
13134 	 */
13135 	if (line_cnt > orig_cnt) {
13136 	    insert_new_textarea_anchor(&end_anchor, &htline);
13137 
13138 	    assert(end_anchor != NULL);
13139 	    assert(end_anchor->input_field != NULL);
13140 
13141 	    anchor_ptr = end_anchor;	/* make the new anchor current */
13142 	    newlines++;
13143 	}
13144 
13145 	assert(anchor_ptr != NULL);
13146 
13147 	/*
13148 	 * Finally copy the new line from the edit buffer into the anchor.
13149 	 */
13150 	StrAllocCopy(anchor_ptr->input_field->value, line);
13151 
13152 	/*
13153 	 * Keep track of 1st blank line in any trailing blank lines, for
13154 	 * later cursor repositioning.
13155 	 */
13156 	if (len0 + len > 0)
13157 	    exit_line = 0;
13158 	else if (exit_line == 0)
13159 	    exit_line = anchor_ptr->line_num;
13160 
13161 	/*
13162 	 * And do the next line of edited text, for the next anchor ...
13163 	 */
13164 	lp += len;
13165 	if (*lp && isspace(UCH(*lp)))
13166 	    lp++;
13167 
13168 	end_anchor = anchor_ptr;
13169 	anchor_ptr = anchor_ptr->next;
13170 
13171 	if (anchor_ptr)
13172 	    match_tag = anchor_ptr->number;
13173 
13174 	line_cnt++;
13175     }
13176 
13177     CTRACE((tfp, "GridText: edited text inserted into lynx structs\n"));
13178 
13179     /*
13180      * If we've added any new lines/anchors, we need to adjust various
13181      * things in all anchor-bearing lines following the last newly added
13182      * line/anchor.  The fun stuff starts here ...
13183      */
13184     if (newlines > 0)
13185 	update_subsequent_anchors(newlines, end_anchor, htline, match_tag);
13186 
13187     /*
13188      * Cleanup time.
13189      */
13190     FREE(line);
13191     FREE(ebuf);
13192 
13193     /*
13194      * Return the offset needed to move the cursor from its current
13195      * (on entry) line number, to the 1st blank line of the trailing
13196      * (group of) blank line(s), which is where we want to be.  Let
13197      * the caller deal with moving us there, however ...  :-) ...
13198      */
13199     return (exit_line - entry_line);
13200 }
13201 
13202 /*
13203  * Transfer the initial contents of a TEXTAREA to a temp file, invoke the
13204  * user's editor on that file, then transfer the contents of the resultant
13205  * edited file back into the TEXTAREA (expanding the size of the area, if
13206  * required).
13207  *
13208  * Returns the number of lines that the cursor should be moved so that it
13209  * will end up on the 1st blank line of whatever number of trailing blank
13210  * lines there are in the TEXTAREA (there will *always* be at least one).
13211  *
13212  * --KED 02/01/99
13213  */
HText_EditTextArea(LinkInfo * form_link)13214 int HText_EditTextArea(LinkInfo * form_link)
13215 {
13216     char *ed_temp;
13217     FILE *fp;
13218 
13219     TextAnchor *anchor_ptr;
13220     TextAnchor *start_anchor = NULL;
13221     BOOLEAN firstanchor = TRUE;
13222 
13223     char ed_offset[10];
13224     int start_line = 0;
13225     int entry_line = form_link->anchor_line_num;
13226     int orig_cnt = 0;
13227     int offset = 0;
13228 
13229     FormInfo *form = form_link->l_form;
13230 
13231     CTRACE((tfp, "GridText: entered HText_EditTextArea()\n"));
13232 
13233     if ((ed_temp = typeMallocn(char, LY_MAXPATH)) == 0) {
13234 	outofmem(__FILE__, "HText_EditTextArea");
13235     } else if ((fp = LYOpenTemp(ed_temp, "", "w")) != 0) {
13236 
13237 	/*
13238 	 * Begin at the beginning, to find 1st anchor in the TEXTAREA, then
13239 	 * write all of its lines (anchors) out to the edit temp file.
13240 	 */
13241 	anchor_ptr = HTMainText->first_anchor;
13242 
13243 	while (anchor_ptr) {
13244 
13245 	    if (IsFormsTextarea(form, anchor_ptr)) {
13246 
13247 		if (firstanchor) {
13248 		    firstanchor = FALSE;
13249 		    start_anchor = anchor_ptr;
13250 		    start_line = anchor_ptr->line_num;
13251 		}
13252 		orig_cnt++;
13253 
13254 		/*
13255 		 * Write the anchors' text to the temp edit file.
13256 		 */
13257 		fputs(anchor_ptr->input_field->value, fp);
13258 		fputc('\n', fp);
13259 
13260 	    } else {
13261 
13262 		if (!firstanchor)
13263 		    break;
13264 	    }
13265 	    anchor_ptr = anchor_ptr->next;
13266 	}
13267 	LYCloseTempFP(fp);
13268 
13269 	if (start_anchor != 0) {
13270 	    CTRACE((tfp, "GridText: TEXTAREA name=|%s| dumped to tempfile\n", form->name));
13271 	    CTRACE((tfp, "GridText: invoking editor (%s) on tempfile\n", editor));
13272 
13273 	    /*
13274 	     * Go edit the TEXTAREA temp file, with the initial editor line
13275 	     * corresponding to the TEXTAREA line the cursor is on (if such
13276 	     * positioning is supported by the editor [as lynx knows it]).
13277 	     */
13278 	    ed_offset[0] = 0;	/* pre-ANSI compilers don't initialize aggregates - TD */
13279 	    if (((entry_line - start_line) > 0) && editor_can_position())
13280 		sprintf(ed_offset, "%d", ((entry_line - start_line) + 1));
13281 
13282 	    edit_temporary_file(ed_temp, ed_offset, NULL);
13283 
13284 	    CTRACE((tfp, "GridText: returned from editor (%s)\n", editor));
13285 
13286 	    if (!form->disabled)
13287 		offset = finish_ExtEditForm(form_link, start_anchor, ed_temp, orig_cnt);
13288 
13289 	    CTRACE((tfp, "GridText: exiting HText_EditTextArea()\n"));
13290 	}
13291 	(void) LYRemoveTemp(ed_temp);
13292 	FREE(ed_temp);
13293     }
13294 
13295     /*
13296      * Return the offset needed to move the cursor from its current
13297      * (on entry) line number, to the 1st blank line of the trailing
13298      * (group of) blank line(s), which is where we want to be.  Let
13299      * the caller deal with moving us there, however ...  :-) ...
13300      */
13301     return offset;
13302 }
13303 
13304 /*
13305  * Similar to HText_EditTextArea, but assume a single-line text field -TD
13306  */
HText_EditTextField(LinkInfo * form_link)13307 void HText_EditTextField(LinkInfo * form_link)
13308 {
13309     char *ed_temp;
13310     FILE *fp;
13311 
13312     FormInfo *form = form_link->l_form;
13313 
13314     CTRACE((tfp, "GridText: entered HText_EditTextField()\n"));
13315 
13316     ed_temp = typeMallocn(char, LY_MAXPATH);
13317 
13318     if ((fp = LYOpenTemp(ed_temp, "", "w")) == 0) {
13319 	FREE(ed_temp);
13320 	return;
13321     }
13322 
13323     /*
13324      * Write the anchors' text to the temp edit file.
13325      */
13326     fputs(form->value, fp);
13327     fputc('\n', fp);
13328 
13329     LYCloseTempFP(fp);
13330 
13331     CTRACE((tfp, "GridText: text field |%s| dumped to tempfile\n", form_link->lname));
13332     CTRACE((tfp, "GridText: invoking editor (%s) on tempfile\n", editor));
13333 
13334     edit_temporary_file(ed_temp, "", NULL);
13335 
13336     CTRACE((tfp, "GridText: returned from editor (%s)\n", editor));
13337 
13338     if (!form->disabled) {
13339 	char *ebuf;
13340 	char *p;
13341 
13342 	if ((ebuf = readEditedFile(ed_temp)) != 0) {
13343 	    /*
13344 	     * Only use the first line of the result.
13345 	     */
13346 	    for (p = ebuf; *p != '\0'; ++p) {
13347 		if (*p == '\n' || *p == '\r') {
13348 		    *p = '\0';
13349 		    break;
13350 		}
13351 	    }
13352 	    StrAllocCopy(form->value, ebuf);
13353 	    FREE(ebuf);
13354 	}
13355     }
13356 
13357     (void) LYRemoveTemp(ed_temp);
13358     FREE(ed_temp);
13359 
13360     CTRACE((tfp, "GridText: exiting HText_EditTextField()\n"));
13361 }
13362 
13363 /*
13364  * Expand the size of a TEXTAREA by a fixed number of lines (as specified
13365  * by arg2).
13366  *
13367  * --KED 02/14/99
13368  */
HText_ExpandTextarea(LinkInfo * form_link,int newlines)13369 void HText_ExpandTextarea(LinkInfo * form_link, int newlines)
13370 {
13371     TextAnchor *anchor_ptr;
13372     TextAnchor *end_anchor = NULL;
13373     BOOLEAN firstanchor = TRUE;
13374 
13375     FormInfo *form = form_link->l_form;
13376 
13377     HTLine *htline = NULL;
13378 
13379     int match_tag = 0;
13380     int i;
13381 
13382     CTRACE((tfp, "GridText: entered HText_ExpandTextarea()\n"));
13383 
13384     if (newlines < 1)
13385 	return;
13386 
13387     /*
13388      * Begin at the beginning, to find the TEXTAREA, then on to find
13389      * the last line (anchor) in it.
13390      */
13391     anchor_ptr = HTMainText->first_anchor;
13392 
13393     while (anchor_ptr) {
13394 
13395 	if (IsFormsTextarea(form, anchor_ptr)) {
13396 
13397 	    if (firstanchor)
13398 		firstanchor = FALSE;
13399 
13400 	    end_anchor = anchor_ptr;
13401 
13402 	} else {
13403 
13404 	    if (!firstanchor)
13405 		break;
13406 	}
13407 	anchor_ptr = anchor_ptr->next;
13408     }
13409 
13410     for (i = 1; i <= newlines; i++) {
13411 	insert_new_textarea_anchor(&end_anchor, &htline);
13412 
13413 	/*
13414 	 * Make the new line blank.
13415 	 */
13416 	StrAllocCopy(end_anchor->input_field->value, "");
13417 
13418 	/*
13419 	 * And go add another line ...
13420 	 */
13421 	if (end_anchor->next)
13422 	    match_tag = end_anchor->next->number;
13423     }
13424 
13425     CTRACE((tfp, "GridText: %d blank line(s) added to TEXTAREA name=|%s|\n",
13426 	    newlines, form->name));
13427 
13428     /*
13429      * We need to adjust various things in all anchor bearing lines
13430      * following the last newly added line/anchor.  Fun stuff.
13431      */
13432     update_subsequent_anchors(newlines, end_anchor, htline, match_tag);
13433 
13434     CTRACE((tfp, "GridText: exiting HText_ExpandTextarea()\n"));
13435 
13436     return;
13437 }
13438 
13439 /*
13440  * Insert the contents of a file into a TEXTAREA between the cursor line,
13441  * and the line preceding it.
13442  *
13443  * Returns the number of lines that the cursor should be moved so that it
13444  * will end up on the 1st line in the TEXTAREA following the inserted file
13445  * (if we decide to do that).
13446  *
13447  * --KED 02/21/99
13448  */
HText_InsertFile(LinkInfo * form_link)13449 int HText_InsertFile(LinkInfo * form_link)
13450 {
13451     struct stat stat_info;
13452     size_t size;
13453 
13454     FILE *fp;
13455     char *fn;
13456 
13457     TextAnchor *anchor_ptr;
13458     TextAnchor *prev_anchor = NULL;
13459     TextAnchor *end_anchor = NULL;
13460     BOOLEAN firstanchor = TRUE;
13461     BOOLEAN truncalert = FALSE;
13462 
13463     FormInfo *form = form_link->l_form;
13464 
13465     HTLine *htline = NULL;
13466 
13467     TextAnchor *a = 0;
13468     FormInfo *f = 0;
13469     HTLine *l = 0;
13470 
13471     char *fbuf = 0;
13472     char *line = 0;
13473     char *lp;
13474     char *cp;
13475     int entry_line = form_link->anchor_line_num;
13476     int file_cs;
13477     int match_tag = 0;
13478     int newlines = 0;
13479     int len;
13480     int i;
13481 
13482     CTRACE((tfp, "GridText: entered HText_InsertFile()\n"));
13483 
13484     /*
13485      * Get the filename of the insert file.
13486      */
13487     if (!(fn = GetFileName())) {
13488 	HTInfoMsg(FILE_INSERT_CANCELLED);
13489 	CTRACE((tfp,
13490 		"GridText: file insert cancelled - no filename provided\n"));
13491 	return (0);
13492     }
13493     if (no_dotfiles || !show_dotfiles) {
13494 	if (*LYPathLeaf(fn) == '.') {
13495 	    HTUserMsg(FILENAME_CANNOT_BE_DOT);
13496 	    return (0);
13497 	}
13498     }
13499 
13500     /*
13501      * Read it into our buffer (abort on 0-length file).
13502      */
13503     if ((stat(fn, &stat_info) < 0) ||
13504 	((size = (size_t) stat_info.st_size) == 0)) {
13505 	HTInfoMsg(FILE_INSERT_0_LENGTH);
13506 	CTRACE((tfp,
13507 		"GridText: file insert aborted - file=|%s|- was 0-length\n",
13508 		fn));
13509 	FREE(fn);
13510 	return (0);
13511 
13512     } else {
13513 
13514 	if ((fbuf = typecallocn(char, size + 1)) == NULL) {
13515 	    /*
13516 	     * This could be huge - don't exit if we don't have enough
13517 	     * memory for it.  - kw
13518 	     */
13519 	    free(fn);
13520 	    HTAlert(MEMORY_EXHAUSTED_FILE);
13521 	    return 0;
13522 	}
13523 
13524 	/* Try to make the same assumption for the charset of the inserted
13525 	 * file as we would for normal loading of that file, i.e. taking
13526 	 * assume_local_charset and suffix mappings into account.
13527 	 * If there is a mismatch with the display character set, characters
13528 	 * may be displayed wrong, too bad; but the user has a chance to
13529 	 * correct this by editing the lines, which will update f->value_cs
13530 	 * again. - kw
13531 	 */
13532 	LYGetFileInfo(fn, 0, 0, 0, 0, 0, &file_cs);
13533 
13534 	fp = fopen(fn, "r");
13535 	if (!fp) {
13536 	    free(fbuf);
13537 	    free(fn);
13538 	    HTAlert(FILE_CANNOT_OPEN_R);
13539 	    return 0;
13540 	}
13541 	size = fread(fbuf, (size_t) 1, size, fp);
13542 	LYCloseInput(fp);
13543 	FREE(fn);
13544 	fbuf[size] = '\0';	/* Terminate! - kw */
13545     }
13546 
13547     /*
13548      * Begin at the beginning, to find the TEXTAREA we're in, then
13549      * the current cursorline.
13550      */
13551     anchor_ptr = HTMainText->first_anchor;
13552 
13553     while (anchor_ptr) {
13554 
13555 	if (IsFormsTextarea(form, anchor_ptr)) {
13556 	    if (anchor_ptr->line_num == entry_line)
13557 		break;
13558 	}
13559 	prev_anchor = anchor_ptr;
13560 	anchor_ptr = anchor_ptr->next;
13561     }
13562 
13563     if (anchor_ptr == NULL) {
13564 	CTRACE((tfp, "BUG: could not find anchor for TEXTAREA.\n"));
13565 	FREE(line);
13566 	FREE(fbuf);
13567 	return 0;
13568     }
13569 
13570     /*
13571      * Clone a new TEXTAREA line/anchor using the cursorline anchor as
13572      * a template, but link it in BEFORE the cursorline anchor/htline.
13573      *
13574      * [We can probably combine this with insert_new_textarea_anchor()
13575      * along with a flag to indicate "insert before" as we do here,
13576      * or the "normal" mode of operation (add after "current" anchor/
13577      * line).  Beware of the differences ...  some are a bit subtle to
13578      * notice.]
13579      */
13580     for (htline = FirstHTLine(HTMainText), i = 0;
13581 	 anchor_ptr->line_num != i; i++) {
13582 	htline = htline->next;
13583 	if (htline == HTMainText->last_line)
13584 	    break;
13585     }
13586 
13587     allocHTLine(l, MAX_LINE);
13588     POOLtypecalloc(TextAnchor, a);
13589 
13590     POOLtypecalloc(FormInfo, f);
13591     if (a == NULL || l == NULL || f == NULL)
13592 	outofmem(__FILE__, "HText_InsertFile");
13593 
13594     assert(a != NULL);
13595     assert(f != NULL);
13596     assert(l != NULL);
13597 
13598     /*  Init all the fields in the new TextAnchor.                 */
13599     /*  [anything "special" needed based on ->show_anchor value ?] */
13600     /* *INDENT-EQLS* */
13601     a->next        = anchor_ptr;
13602     a->number      = anchor_ptr->number;
13603     a->show_number = anchor_ptr->show_number;
13604     a->line_pos    = anchor_ptr->line_pos;
13605     a->extent      = anchor_ptr->extent;
13606     a->sgml_offset = SGML_offset();
13607     a->line_num    = anchor_ptr->line_num;
13608     LYCopyHiText(a, anchor_ptr);
13609     a->link_type   = anchor_ptr->link_type;
13610     a->input_field = f;
13611     a->show_anchor = anchor_ptr->show_anchor;
13612     a->inUnderline = anchor_ptr->inUnderline;
13613     a->expansion_anch = TRUE;
13614     a->anchor      = NULL;
13615 
13616     /*  Just the (seemingly) relevant fields in the new FormInfo.  */
13617     /*  [do we need to do anything "special" based on ->disabled]  */
13618     StrAllocCopy(f->name, anchor_ptr->input_field->name);
13619     f->number = anchor_ptr->input_field->number;
13620     f->type = anchor_ptr->input_field->type;
13621     StrAllocCopy(f->orig_value, "");
13622     f->size = anchor_ptr->input_field->size;
13623     f->maxlength = anchor_ptr->input_field->maxlength;
13624     f->no_cache = anchor_ptr->input_field->no_cache;
13625     f->disabled = anchor_ptr->input_field->disabled;
13626     f->readonly = anchor_ptr->input_field->readonly;
13627     f->value_cs = (file_cs >= 0) ? file_cs : current_char_set;
13628 
13629     /*  Init all the fields in the new HTLine (but see the #if).   */
13630     l->offset = htline->offset;
13631     l->size = htline->size;
13632 #if defined(USE_COLOR_STYLE)
13633     /* dup styles[] if needed [no need in TEXTAREA (?); leave 0s] */
13634     l->numstyles = htline->numstyles;
13635     /*we fork the pointers! */
13636     l->styles = htline->styles;
13637 #endif
13638     strcpy(l->data, htline->data);
13639 
13640     /*
13641      * If we're at the head of the TextAnchor list, the new node becomes
13642      * the first node.
13643      */
13644     if (anchor_ptr == HTMainText->first_anchor)
13645 	HTMainText->first_anchor = a;
13646 
13647     /*
13648      * Link in the new TextAnchor, and corresponding HTLine.
13649      */
13650     if (prev_anchor)
13651 	prev_anchor->next = a;
13652 
13653     htline = htline->prev;
13654     l->next = htline->next;
13655     l->prev = htline;
13656     htline->next->prev = l;
13657     htline->next = l;
13658 
13659     /*
13660      * update_subsequent_anchors() expects htline to point to 1st potential
13661      * line needing fixup; we need to do this just in case the inserted file
13662      * was only a single line (yes, it's pathological ...  ).
13663      */
13664     htline = htline->next;	/* ->new (current) htline, for 1st inserted line  */
13665     htline = htline->next;	/* ->1st potential (following) [tag] fixup htline */
13666 
13667     anchor_ptr = a;
13668     newlines++;
13669 
13670     /*
13671      * Copy each line from the insert file into the corresponding anchor
13672      * struct.
13673      *
13674      * Begin with the new line/anchor we just added (above the cursorline).
13675      */
13676     if ((line = typeMallocn(char, MAX_LINE)) == 0)
13677 	  outofmem(__FILE__, "HText_InsertFile");
13678 
13679     assert(line != NULL);
13680 
13681     match_tag = anchor_ptr->number;
13682 
13683     lp = fbuf;
13684 
13685     while (*lp) {
13686 
13687 	if ((cp = strchr(lp, '\n')) != 0)
13688 	    len = (int) (cp - lp);
13689 	else
13690 	    len = (int) strlen(lp);
13691 
13692 	if (len >= MAX_LINE) {
13693 	    if (!truncalert) {
13694 		HTAlert(gettext("Very long lines have been truncated!"));
13695 		truncalert = TRUE;
13696 	    }
13697 	    len = MAX_LINE - 1;
13698 	    if (lp[len])
13699 		lp[len + 1] = '\0';	/* prevent next iteration */
13700 	}
13701 	LYStrNCpy(line, lp, len);
13702 
13703 	/*
13704 	 * If not the first line from the insert file, we need to add
13705 	 * a new line/anchor, continuing on until the buffer is empty.
13706 	 */
13707 	if (!firstanchor) {
13708 	    insert_new_textarea_anchor(&end_anchor, &htline);
13709 	    anchor_ptr = end_anchor;	/* make the new anchor current */
13710 	    newlines++;
13711 	}
13712 
13713 	/*
13714 	 * Copy the new line from the buffer into the anchor.
13715 	 */
13716 	StrAllocCopy(anchor_ptr->input_field->value, line);
13717 
13718 	/*
13719 	 * insert_new_textarea_anchor always uses current_char_set,
13720 	 * we may want something else, so fix it up.  - kw
13721 	 */
13722 	if (file_cs >= 0)
13723 	    anchor_ptr->input_field->value_cs = file_cs;
13724 
13725 	/*
13726 	 * And do the next line of insert text, for the next anchor ...
13727 	 */
13728 	lp += len;
13729 	if (*lp)
13730 	    lp++;
13731 
13732 	firstanchor = FALSE;
13733 	end_anchor = anchor_ptr;
13734 	anchor_ptr = anchor_ptr->next;
13735     }
13736 
13737     CTRACE((tfp, "GridText: file inserted into lynx structs\n"));
13738 
13739     /*
13740      * Now adjust various things in all anchor-bearing lines following the
13741      * last newly added line/anchor.  Some say this is the fun part ...
13742      */
13743     update_subsequent_anchors(newlines, end_anchor, htline, match_tag);
13744 
13745     /*
13746      * Cleanup time.
13747      */
13748     FREE(line);
13749     FREE(fbuf);
13750 
13751     CTRACE((tfp, "GridText: exiting HText_InsertFile()\n"));
13752 
13753     return (newlines);
13754 }
13755 
13756 #ifdef USE_COLOR_STYLE
GetColumn(void)13757 static int GetColumn(void)
13758 {
13759     int result;
13760 
13761 #ifdef USE_SLANG
13762     result = SLsmg_get_column();
13763 #else
13764     int y, x;
13765 
13766     LYGetYX(y, x);
13767     result = x;
13768     (void) y;
13769 #endif
13770     return result;
13771 }
13772 
DidWrap(int y0,int x0)13773 static BOOL DidWrap(int y0, int x0)
13774 {
13775     BOOL result = NO;
13776 
13777 #ifndef USE_SLANG
13778     int y, x;
13779 
13780     LYGetYX(y, x);
13781     (void) x0;
13782     if (x >= DISPLAY_COLS || ((x == 0) && (y != y0)))
13783 	result = YES;
13784 #endif
13785     return result;
13786 }
13787 #endif /* USE_COLOR_STYLE */
13788 
13789 /*
13790  * This function draws the part of line 'line', pointed by 'str' (which can be
13791  * non terminated with null - i.e., is line->data+N) drawing 'len' bytes (not
13792  * characters) of it.  It doesn't check whether the 'len' bytes crosses a
13793  * character boundary (if multibyte chars are in string).  Assumes that the
13794  * cursor is positioned in the place where the 1st char of string should be
13795  * drawn.
13796  *
13797  * This code is based on display_line.  This code was tested with ncurses only
13798  * (since no support for lss is availble for Slang) -HV.
13799  */
13800 #ifdef USE_COLOR_STYLE
redraw_part_of_line(HTLine * line,const char * str,int len,HText * text)13801 static void redraw_part_of_line(HTLine *line, const char *str,
13802 				int len,
13803 				HText *text)
13804 {
13805     register int i;
13806     char buffer[7];
13807     const char *data, *end_of_data;
13808     size_t utf_extra = 0;
13809 
13810 #ifdef USE_COLOR_STYLE
13811     int current_style = 0;
13812     int tcols, scols;
13813 #endif
13814     char LastDisplayChar = ' ';
13815     int YP, XP;
13816 
13817     LYGetYX(YP, XP);
13818 
13819     i = XP;
13820 
13821     /* Set up the multibyte character buffer  */
13822     buffer[0] = buffer[1] = buffer[2] = '\0';
13823 
13824     data = str;
13825     end_of_data = data + len;
13826     i++;
13827 
13828     /* this assumes that the part of line to be drawn fits in the screen */
13829     while (data < end_of_data) {
13830 	buffer[0] = *data;
13831 	data++;
13832 
13833 #if defined(USE_COLOR_STYLE)
13834 #define CStyle line->styles[current_style]
13835 
13836 	tcols = GetColumn();
13837 	scols = StyleToCols(text, line, current_style);
13838 
13839 	while (current_style < line->numstyles &&
13840 	       tcols >= scols) {
13841 	    LynxChangeStyle(CStyle.sc_style, CStyle.sc_direction);
13842 	    current_style++;
13843 	    scols = StyleToCols(text, line, current_style);
13844 	}
13845 #endif
13846 	switch (buffer[0]) {
13847 
13848 #ifndef USE_COLOR_STYLE
13849 	case LY_UNDERLINE_START_CHAR:
13850 	    if (dump_output_immediately && use_underscore) {
13851 		LYaddch('_');
13852 		i++;
13853 	    } else {
13854 		lynx_start_underline();
13855 	    }
13856 	    break;
13857 
13858 	case LY_UNDERLINE_END_CHAR:
13859 	    if (dump_output_immediately && use_underscore) {
13860 		LYaddch('_');
13861 		i++;
13862 	    } else {
13863 		lynx_stop_underline();
13864 	    }
13865 	    break;
13866 
13867 	case LY_BOLD_START_CHAR:
13868 	    lynx_start_bold();
13869 	    break;
13870 
13871 	case LY_BOLD_END_CHAR:
13872 	    lynx_stop_bold();
13873 	    break;
13874 
13875 #endif
13876 	case LY_SOFT_NEWLINE:
13877 	    if (!dump_output_immediately) {
13878 		LYaddch('+');
13879 		i++;
13880 	    }
13881 	    break;
13882 
13883 	case LY_SOFT_HYPHEN:
13884 	    if (*data != '\0' ||
13885 		isspace(UCH(LastDisplayChar)) ||
13886 		LastDisplayChar == '-') {
13887 		/*
13888 		 * Ignore the soft hyphen if it is not the last character in
13889 		 * the line.  Also ignore it if it first character following
13890 		 * the margin, or if it is preceded by a white character (we
13891 		 * loaded 'M' into LastDisplayChar if it was a multibyte
13892 		 * character) or hyphen, though it should have been excluded by
13893 		 * HText_appendCharacter() or by split_line() in those cases.
13894 		 * -FM
13895 		 */
13896 		break;
13897 	    } else {
13898 		/*
13899 		 * Make it a hard hyphen and fall through.  -FM
13900 		 */
13901 		buffer[0] = '-';
13902 	    }
13903 	    /* FALLTHRU */
13904 
13905 	default:
13906 	    if (text->T.output_utf8 && is8bits(buffer[0])) {
13907 		utf_extra = utf8_length(text->T.output_utf8, data - 1);
13908 		LastDisplayChar = 'M';
13909 	    }
13910 	    if (utf_extra) {
13911 		LYStrNCpy(&buffer[1], data, utf_extra);
13912 		LYaddstr(buffer);
13913 		buffer[1] = '\0';
13914 		data += utf_extra;
13915 		utf_extra = 0;
13916 	    } else if (is_CJK2(buffer[0])) {
13917 		/*
13918 		 * For CJK strings, by Masanobu Kimura.
13919 		 */
13920 		if (i <= DISPLAY_COLS) {
13921 		    buffer[1] = *data;
13922 		    buffer[2] = '\0';
13923 		    data++;
13924 		    i++;
13925 		    LYaddstr(buffer);
13926 		    buffer[1] = '\0';
13927 		    /*
13928 		     * For now, load 'M' into LastDisplayChar, but we should
13929 		     * check whether it's white and if so, use ' '.  I don't
13930 		     * know if there actually are white CJK characters, and
13931 		     * we're loading ' ' for multibyte spacing characters in
13932 		     * this code set, but this will become an issue when the
13933 		     * development code set's multibyte character handling is
13934 		     * used.  -FM
13935 		     */
13936 		    LastDisplayChar = 'M';
13937 		}
13938 	    } else {
13939 		LYaddstr(buffer);
13940 		LastDisplayChar = buffer[0];
13941 	    }
13942 	    if (DidWrap(YP, XP))
13943 		break;
13944 	    i++;
13945 	}			/* end of switch */
13946     }				/* end of while */
13947 
13948 #ifndef USE_COLOR_STYLE
13949     lynx_stop_underline();
13950     lynx_stop_bold();
13951 #else
13952 
13953     while (current_style < line->numstyles) {
13954 	LynxChangeStyle(CStyle.sc_style, CStyle.sc_direction);
13955 	current_style++;
13956     }
13957 
13958 #undef CStyle
13959 #endif
13960     return;
13961 }
13962 #endif /* USE_COLOR_STYLE */
13963 
13964 #ifndef USE_COLOR_STYLE
13965 /*
13966  * Function move_to_glyph is called from LYMoveToLink and does all
13967  * the real work for it.
13968  * The pair LYMoveToLink()/move_to_glyph() is similar to the pair
13969  * redraw_lines_of_link()/redraw_part_of_line(), some key differences:
13970  * LYMoveToLink/move_to_glyph redraw_*
13971  * -----------------------------------------------------------------
13972  * - used without color style           - used with color style
13973  * - handles showing WHEREIS target     - WHEREIS handled elsewhere
13974  * - handles only one line              - handles first two lines for
13975  *                                        hypertext anchors
13976  * - right columns position for UTF-8
13977  *   by redrawing as necessary
13978  * - currently used for highlight       - currently used for highlight
13979  *   ON and OFF                         OFF
13980  *
13981  * Eventually the two sets of function should be unified, and should handle
13982  * UTF-8 positioning, both lines of hypertext anchors, and WHEREIS in all
13983  * cases.  If possible.  The complex WHEREIS target logic in LYhighlight()
13984  * could then be completely removed.  - kw
13985  */
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)13986 static void move_to_glyph(int YP,
13987 			  int XP,
13988 			  int XP_draw_min,
13989 			  const char *data,
13990 			  int datasize,
13991 			  unsigned offset,
13992 			  const char *target,
13993 			  const char *hightext,
13994 			  int flags,
13995 			  int utf_flag)
13996 {
13997     char buffer[7];
13998     const char *end_of_data;
13999     size_t utf_extra = 0;
14000 
14001 #if defined(SHOW_WHEREIS_TARGETS)
14002     const char *cp_tgt;
14003     int i_start_tgt = 0, i_after_tgt;
14004     int HitOffset, LenNeeded;
14005 #endif /* SHOW_WHEREIS_TARGETS */
14006     BOOL intarget = NO;
14007     BOOL inunderline = NO;
14008     BOOL inbold = NO;
14009     BOOL drawing = NO;
14010     BOOL inU = NO;
14011     BOOL hadutf8 = NO;
14012     BOOL incurlink = NO;
14013     BOOL drawingtarget = NO;
14014     BOOL flag = NO;
14015     const char *sdata = data;
14016     char LastDisplayChar = ' ';
14017 
14018     int i = (int) offset;	/* FIXME: should be columns, not offset? */
14019     int last_i = DISPLAY_COLS;
14020     int XP_link = XP;		/* column of link */
14021     int XP_next = XP;		/* column to move to when done drawing */
14022     int linkvlen;
14023 
14024     int len;
14025 
14026     if (no_title)
14027 	YP -= TITLE_LINES;
14028 
14029     if (flags & 1)
14030 	flag = YES;
14031     if (flags & 2)
14032 	inU = YES;
14033     /* Set up the multibyte character buffer  */
14034     buffer[0] = buffer[1] = buffer[2] = '\0';
14035     /*
14036      * Add offset, making sure that we do not
14037      * go over the COLS limit on the display.
14038      */
14039     if (hightext != 0) {
14040 #ifdef WIDEC_CURSES
14041 	last_i = i + LYstrExtent2(data, datasize);
14042 #endif
14043 	linkvlen = LYmbcsstrlen(hightext, utf_flag, YES);
14044     } else {
14045 	linkvlen = 0;
14046     }
14047     if (i >= last_i)
14048 	i = last_i - 1;
14049 
14050     /*
14051      * Scan through the data, making sure that we do not
14052      * go over the COLS limit on the display etc.
14053      */
14054     len = datasize;
14055     end_of_data = data + len;
14056 
14057 #if defined(SHOW_WHEREIS_TARGETS)
14058     /*
14059      * If the target overlaps with the part of this line that
14060      * we are drawing, it will be emphasized.
14061      */
14062     i_after_tgt = i;
14063     if (target) {
14064 	cp_tgt = LYno_attr_mb_strstr(sdata,
14065 				     target,
14066 				     utf_flag, YES,
14067 				     &HitOffset,
14068 				     &LenNeeded);
14069 	if (cp_tgt) {
14070 	    if ((int) offset + LenNeeded > last_i ||
14071 		((int) offset + HitOffset >= XP + linkvlen)) {
14072 		cp_tgt = NULL;
14073 	    } else {
14074 		i_start_tgt = i + HitOffset;
14075 		i_after_tgt = i + LenNeeded;
14076 	    }
14077 	}
14078     } else {
14079 	cp_tgt = NULL;
14080     }
14081 #endif /* SHOW_WHEREIS_TARGETS */
14082 
14083     /*
14084      * Iterate through the line data from the start, keeping track of
14085      * the display ("glyph") position in i.  Drawing will be turned
14086      * on when either the first UTF-8 sequence (that occurs after
14087      * XP_draw_min) is found, or when we reach the link itself (if
14088      * highlight is non-NULL).  - kw
14089      */
14090     while ((i <= last_i) && data < end_of_data && (*data != '\0')) {
14091 
14092 	if (hightext && i >= XP && !incurlink) {
14093 
14094 	    /*
14095 	     * We reached the position of link itself, and hightext is
14096 	     * non-NULL.  We switch data from being a pointer into the HTLine
14097 	     * to being a pointer into hightext.  Normally (as long as this
14098 	     * routine is applied to normal hyperlink anchors) the text in
14099 	     * hightext will be identical to that part of the HTLine that
14100 	     * data was already pointing to, except that special attribute
14101 	     * chars LY_BOLD_START_CHAR etc., have been stripped out (see
14102 	     * HText_trimHightext).  So the switching should not result in
14103 	     * any different display, but it ensures that it doesn't go
14104 	     * unnoticed if somehow hightext got messed up somewhere else.
14105 	     * This is also useful in preparation for using this function
14106 	     * for something else than normal hyperlink anchors, i.e., form
14107 	     * fields.
14108 	     * Turn on drawing here or make sure it gets turned on before the
14109 	     * next actual normal character is handled.  - kw
14110 	     */
14111 	    data = hightext;
14112 	    len = (int) strlen(hightext);
14113 	    end_of_data = hightext + len;
14114 	    last_i = i + len;
14115 	    XP_next += linkvlen;
14116 	    incurlink = YES;
14117 #ifdef SHOW_WHEREIS_TARGETS
14118 	    if (cp_tgt) {
14119 		if (flag && i_after_tgt >= XP)
14120 		    i_after_tgt = XP - 1;
14121 	    }
14122 #endif
14123 	    /*
14124 	     * The logic of where to set in-target drawing target etc.
14125 	     * and when to react to it should be cleaned up (here and
14126 	     * further below).  For now this seems to work but isn't
14127 	     * very clear.  The complications arise from reproducing
14128 	     * the behavior (previously done in LYhighlight()) for target
14129 	     * strings that fall into or overlap a link:  use target
14130 	     * emphasis for the target string, except for the first
14131 	     * and last character of the anchor text if the anchor is
14132 	     * highlighted as "current link".  - kw
14133 	     */
14134 	    if (!drawing) {
14135 #ifdef SHOW_WHEREIS_TARGETS
14136 		if (intarget) {
14137 		    if (i_after_tgt > i) {
14138 			LYmove(YP, i);
14139 			if (flag) {
14140 			    drawing = YES;
14141 			    drawingtarget = NO;
14142 			    if (inunderline)
14143 				inU = YES;
14144 			    lynx_start_link_color(flag, inU);
14145 			} else {
14146 			    drawing = YES;
14147 			    drawingtarget = YES;
14148 			    LYstartTargetEmphasis();
14149 			}
14150 		    }
14151 		}
14152 #endif /* SHOW_WHEREIS_TARGETS */
14153 	    } else {
14154 #ifdef SHOW_WHEREIS_TARGETS
14155 		if (intarget && i_after_tgt > i) {
14156 		    if (flag && (data == hightext)) {
14157 			drawingtarget = NO;
14158 			LYstopTargetEmphasis();
14159 		    }
14160 		} else if (!intarget)
14161 #endif /* SHOW_WHEREIS_TARGETS */
14162 		{
14163 		    if (inunderline)
14164 			inU = YES;
14165 		    if (inunderline)
14166 			lynx_stop_underline();
14167 		    if (inbold)
14168 			lynx_stop_bold();
14169 		    lynx_start_link_color(flag, inU);
14170 		}
14171 
14172 	    }
14173 	}
14174 	if (i >= last_i || data >= end_of_data)
14175 	    break;
14176 	if ((buffer[0] = *data) == '\0')
14177 	    break;
14178 #if defined(SHOW_WHEREIS_TARGETS)
14179 	/*
14180 	 * Look for a subsequent occurrence of the target string,
14181 	 * if we had a previous one and have now stepped past it.  - kw
14182 	 */
14183 	if (cp_tgt && i >= i_after_tgt) {
14184 	    if (intarget) {
14185 
14186 		if (incurlink && flag && i == last_i - 1)
14187 		    cp_tgt = NULL;
14188 		else
14189 		    cp_tgt = LYno_attr_mb_strstr(sdata,
14190 						 target,
14191 						 utf_flag, YES,
14192 						 &HitOffset,
14193 						 &LenNeeded);
14194 		if (cp_tgt) {
14195 		    i_start_tgt = i + HitOffset;
14196 		    i_after_tgt = i + LenNeeded;
14197 		    if (incurlink) {
14198 			if (flag && i_start_tgt == XP_link)
14199 			    i_start_tgt++;
14200 			if (flag && i_start_tgt == last_i - 1)
14201 			    i_start_tgt++;
14202 			if (flag && i_after_tgt >= last_i)
14203 			    i_after_tgt = last_i - 1;
14204 			if (flag && i_start_tgt >= last_i)
14205 			    cp_tgt = NULL;
14206 		    } else if (i_start_tgt == last_i) {
14207 			if (flag)
14208 			    i_start_tgt++;
14209 		    }
14210 		}
14211 		if (!cp_tgt || i_start_tgt != i) {
14212 		    intarget = NO;
14213 		    if (drawing) {
14214 			if (drawingtarget) {
14215 			    drawingtarget = NO;
14216 			    LYstopTargetEmphasis();
14217 			    if (incurlink) {
14218 				lynx_start_link_color(flag, inU);
14219 			    }
14220 			}
14221 			if (!incurlink) {
14222 			    if (inbold)
14223 				lynx_start_bold();
14224 			    if (inunderline)
14225 				lynx_start_underline();
14226 			}
14227 		    }
14228 		}
14229 	    }
14230 	}
14231 #endif /* SHOW_WHEREIS_TARGETS */
14232 
14233 	/*
14234 	 * Advance data to point to the next input char (for the
14235 	 * next round).  Advance sdata, used for searching for a
14236 	 * target string, so that they stay in synch.  As long
14237 	 * as we are not within the highlight text, data and sdata
14238 	 * have identical values.  After we have switched data to
14239 	 * point into hightext, sdata remains a pointer into the
14240 	 * HTLine (so that we don't miss a partial target match at
14241 	 * the end of the anchor text).  So sdata has to sometimes
14242 	 * skip additional special attribute characters that are
14243 	 * not present in highlight in order to stay in synch.  - kw
14244 	 */
14245 	data++;
14246 	if (incurlink) {
14247 	    while (IsNormalChar(*sdata)) {
14248 		++sdata;
14249 	    }
14250 	}
14251 
14252 	switch (buffer[0]) {
14253 
14254 	case LY_UNDERLINE_START_CHAR:
14255 	    if (!drawing || !incurlink)
14256 		inunderline = YES;
14257 	    if (drawing && !intarget && !incurlink)
14258 		lynx_start_underline();
14259 	    break;
14260 
14261 	case LY_UNDERLINE_END_CHAR:
14262 	    inunderline = NO;
14263 	    if (drawing && !intarget && !incurlink)
14264 		lynx_stop_underline();
14265 	    break;
14266 
14267 	case LY_BOLD_START_CHAR:
14268 	    if (!drawing || !incurlink)
14269 		inbold = YES;
14270 	    if (drawing && !intarget && !incurlink)
14271 		lynx_start_bold();
14272 	    break;
14273 
14274 	case LY_BOLD_END_CHAR:
14275 	    inbold = NO;
14276 	    if (drawing && !intarget && !incurlink)
14277 		lynx_stop_bold();
14278 	    break;
14279 
14280 	case LY_SOFT_NEWLINE:
14281 	    if (drawing) {
14282 		LYaddch('+');
14283 	    }
14284 	    i++;
14285 	    break;
14286 
14287 	case LY_SOFT_HYPHEN:
14288 	    if (*data != '\0' ||
14289 		isspace(UCH(LastDisplayChar)) ||
14290 		LastDisplayChar == '-') {
14291 		/*
14292 		 * Ignore the soft hyphen if it is not the last
14293 		 * character in the line.  Also ignore it if it
14294 		 * first character following the margin, or if it
14295 		 * is preceded by a white character (we loaded 'M'
14296 		 * into LastDisplayChar if it was a multibyte
14297 		 * character) or hyphen, though it should have
14298 		 * been excluded by HText_appendCharacter() or by
14299 		 * split_line() in those cases.  -FM
14300 		 */
14301 		break;
14302 	    } else {
14303 		/*
14304 		 * Make it a hard hyphen and fall through.  -FM
14305 		 */
14306 		buffer[0] = '-';
14307 	    }
14308 	    /* FALLTHRU */
14309 
14310 	default:
14311 	    /*
14312 	     * We have got an actual normal displayable character, or
14313 	     * the start of one.  Before proceeding check whether
14314 	     * drawing needs to be turned on now.  - kw
14315 	     */
14316 #if defined(SHOW_WHEREIS_TARGETS)
14317 	    if (incurlink && intarget && flag && i_after_tgt > i) {
14318 		if (i == last_i - 1) {
14319 		    i_after_tgt = i;
14320 		} else if (i == last_i - 2 && IS_CJK_TTY &&
14321 			   is8bits(buffer[0])) {
14322 		    i_after_tgt = i;
14323 		    cp_tgt = NULL;
14324 		    if (drawing) {
14325 			if (drawingtarget) {
14326 			    LYstopTargetEmphasis();
14327 			    drawingtarget = NO;
14328 			    lynx_start_link_color(flag, inU);
14329 			}
14330 		    }
14331 		}
14332 	    }
14333 	    if (cp_tgt && i >= i_start_tgt && sdata > cp_tgt) {
14334 		if (!intarget ||
14335 		    (intarget && incurlink && !drawingtarget)) {
14336 
14337 		    if (incurlink && drawing &&
14338 			!(flag &&
14339 			  (i == XP_link || i == last_i - 1))) {
14340 			lynx_stop_link_color(flag, inU);
14341 		    }
14342 		    if (incurlink && !drawing) {
14343 			LYmove(YP, i);
14344 			if (inunderline)
14345 			    inU = YES;
14346 			if (flag && (i == XP_link || i == last_i - 1)) {
14347 			    lynx_start_link_color(flag, inU);
14348 			    drawingtarget = NO;
14349 			} else {
14350 			    LYstartTargetEmphasis();
14351 			    drawingtarget = YES;
14352 			}
14353 			drawing = YES;
14354 		    } else if (incurlink && drawing &&
14355 			       intarget && !drawingtarget &&
14356 			       (flag &&
14357 				(i == XP_link))) {
14358 			if (inunderline)
14359 			    inU = YES;
14360 			lynx_start_link_color(flag, inU);
14361 		    } else if (drawing &&
14362 			       !(flag &&
14363 				 (i == XP_link || (incurlink && i == last_i - 1)))) {
14364 			LYstartTargetEmphasis();
14365 			drawingtarget = YES;
14366 		    }
14367 		    intarget = YES;
14368 		}
14369 	    } else
14370 #endif /* SHOW_WHEREIS_TARGETS */
14371 	    if (incurlink) {
14372 		if (!drawing) {
14373 		    LYmove(YP, i);
14374 		    if (inunderline)
14375 			inU = YES;
14376 		    lynx_start_link_color(flag, inU);
14377 		    drawing = YES;
14378 		}
14379 	    }
14380 
14381 	    i++;
14382 #ifndef WIDEC_CURSES
14383 	    if (utf_flag && is8bits(buffer[0])) {
14384 		hadutf8 = YES;
14385 		utf_extra = utf8_length(utf_flag, data - 1);
14386 		LastDisplayChar = 'M';
14387 	    }
14388 #endif
14389 	    if (utf_extra) {
14390 		LYStrNCpy(&buffer[1], data, utf_extra);
14391 		if (!drawing && i >= XP_draw_min) {
14392 		    LYmove(YP, i - 1);
14393 		    drawing = YES;
14394 #if defined(SHOW_WHEREIS_TARGETS)
14395 		    if (intarget) {
14396 			drawingtarget = YES;
14397 			LYstartTargetEmphasis();
14398 		    } else
14399 #endif /* SHOW_WHEREIS_TARGETS */
14400 		    {
14401 			if (inbold)
14402 			    lynx_start_bold();
14403 			if (inunderline)
14404 			    lynx_start_underline();
14405 		    }
14406 		}
14407 		LYaddstr(buffer);
14408 		buffer[1] = '\0';
14409 		sdata += utf_extra;
14410 		data += utf_extra;
14411 		utf_extra = 0;
14412 	    } else if (IS_CJK_TTY && is8bits(buffer[0])
14413 		       && (!conv_jisx0201kana && (kanji_code != SJIS))) {
14414 		/*
14415 		 * For CJK strings, by Masanobu Kimura.
14416 		 */
14417 		if (drawing && (i <= last_i)) {
14418 		    buffer[1] = *data;
14419 		    LYaddstr(buffer);
14420 		    buffer[1] = '\0';
14421 		}
14422 		i++;
14423 		sdata++;
14424 		data++;
14425 		/*
14426 		 * For now, load 'M' into LastDisplayChar, but we should
14427 		 * check whether it's white and if so, use ' '.  I don't
14428 		 * know if there actually are white CJK characters, and
14429 		 * we're loading ' ' for multibyte spacing characters in
14430 		 * this code set, but this will become an issue when the
14431 		 * development code set's multibyte character handling is
14432 		 * used.  -FM
14433 		 */
14434 		LastDisplayChar = 'M';
14435 	    } else {
14436 		if (drawing) {
14437 		    LYaddstr(buffer);
14438 		}
14439 		LastDisplayChar = buffer[0];
14440 	    }
14441 	}			/* end of switch */
14442     }				/* end of while */
14443 
14444     if (!drawing) {
14445 	LYmove(YP, XP_next);
14446 	lynx_start_link_color(flag, inU);
14447     } else {
14448 #if defined(SHOW_WHEREIS_TARGETS)
14449 	if (drawingtarget) {
14450 	    LYstopTargetEmphasis();
14451 	    lynx_start_link_color(flag, inU);
14452 	}
14453 #endif /* SHOW_WHEREIS_TARGETS */
14454 	if (hadutf8) {
14455 	    LYtouchline(YP);
14456 	}
14457     }
14458     return;
14459 }
14460 #endif /* !USE_COLOR_STYLE */
14461 
14462 #ifndef USE_COLOR_STYLE
14463 /*
14464  * Move cursor position to a link's place in the display.
14465  * The "moving to" is done by scanning through the line's
14466  * character data in the corresponding HTLine of HTMainText,
14467  * and starting to draw when a UTF-8 encoded non-ASCII character
14468  * is encountered before the link (with some protection against
14469  * overwriting form fields).  This refreshing of preceding data is
14470  * necessary for preventing curses' or slang's display logic from
14471  * getting too clever; their logic counts character positions wrong
14472  * since they don't know about multi-byte characters that take up
14473  * only one screen position.  So we have to make them forget their
14474  * idea of what's in a screen line drawn previously.
14475  * If hightext is non-NULL, it should be the anchor text for a normal
14476  * link as stored in a links[] element, and the anchor text will be
14477  * drawn too, with appropriate attributes.  - kw
14478  */
LYMoveToLink(int cur,const char * target,const char * hightext,int flag,int inU,int utf_flag)14479 void LYMoveToLink(int cur,
14480 		  const char *target,
14481 		  const char *hightext,
14482 		  int flag,
14483 		  int inU,
14484 		  int utf_flag)
14485 {
14486 #define pvtTITLE_HEIGHT 1
14487     HTLine *todr;
14488     int i, n = 0;
14489     int XP_draw_min = 0;
14490     int flags = ((flag == TRUE) ? 1 : 0) | (inU ? 2 : 0);
14491 
14492     /*
14493      * We need to protect changed form text fields preceding this
14494      * link on the same line against overwriting.  - kw
14495      */
14496     for (i = cur - 1; i >= 0; i++) {
14497 	if (links[i].ly < links[cur].ly)
14498 	    break;
14499 	if (links[i].type == WWW_FORM_LINK_TYPE) {
14500 	    XP_draw_min = links[i].ly + links[i].l_form->size;
14501 	    break;
14502 	}
14503     }
14504 
14505     /*  Find the right HTLine. */
14506     if (!HTMainText) {
14507 	todr = NULL;
14508     } else if (HTMainText->stale) {
14509 	todr = FirstHTLine(HTMainText);
14510 	n = links[cur].ly - pvtTITLE_HEIGHT + HTMainText->top_of_screen;
14511     } else {
14512 	todr = HTMainText->top_of_screen_line;
14513 	n = links[cur].ly - pvtTITLE_HEIGHT;
14514     }
14515     for (i = 0; i < n && todr; i++) {
14516 	todr = (todr == HTMainText->last_line) ? NULL : todr->next;
14517     }
14518     if (todr) {
14519 	if (target && *target == '\0')
14520 	    target = NULL;
14521 	move_to_glyph(links[cur].ly, links[cur].lx, XP_draw_min,
14522 		      todr->data, todr->size, todr->offset,
14523 		      target, hightext, flags, utf_flag);
14524     } else {
14525 	/*  This should not happen. */
14526 	move_to_glyph(links[cur].ly, links[cur].lx, XP_draw_min,
14527 		      "", 0, (unsigned) links[cur].lx,
14528 		      target, hightext, flags, utf_flag);
14529     }
14530 }
14531 #endif /* !USE_COLOR_STYLE */
14532 
14533 /*
14534  * This is used only if compiled with lss support.  It's called to redraw a
14535  * regular link when it's being unhighlighted in LYhighlight().
14536  */
14537 #ifdef USE_COLOR_STYLE
redraw_lines_of_link(int cur)14538 void redraw_lines_of_link(int cur)
14539 {
14540 #define pvtTITLE_HEIGHT 1
14541     HTLine *todr1;
14542     int lines_back;
14543     int row, col, count;
14544     const char *text;
14545 
14546     if (HTMainText->next_line == HTMainText->last_line) {
14547 	/* we are at the last page - that is partially filled */
14548 	lines_back = HTMainText->Lines - (links[cur].ly - pvtTITLE_HEIGHT +
14549 					  HTMainText->top_of_screen);
14550     } else {
14551 	lines_back = display_lines - (links[cur].ly - pvtTITLE_HEIGHT);
14552     }
14553     todr1 = HTMainText->next_line;
14554     while (lines_back-- > 0)
14555 	todr1 = todr1->prev;
14556 
14557     row = links[cur].ly;
14558     if (no_title)
14559 	row -= TITLE_LINES;
14560 
14561     for (count = 0;
14562 	 row <= display_lines && (text = LYGetHiliteStr(cur, count)) != NULL;
14563 	 ++count) {
14564 	col = LYGetHilitePos(cur, count);
14565 	if (col >= 0) {
14566 	    LYmove(row, col);
14567 	    redraw_part_of_line(todr1, text, (int) strlen(text), HTMainText);
14568 	}
14569 	todr1 = todr1->next;
14570 	row++;
14571     }
14572 #undef pvtTITLE_HEIGHT
14573     return;
14574 }
14575 #endif
14576 
14577 #ifdef USE_PRETTYSRC
HTMark_asSource(void)14578 void HTMark_asSource(void)
14579 {
14580     if (HTMainText)
14581 	HTMainText->source = TRUE;
14582 }
14583 #endif
14584 
HText_getKcode(HText * text)14585 HTkcode HText_getKcode(HText *text)
14586 {
14587     return text->kcode;
14588 }
14589 
HText_updateKcode(HText * text,HTkcode kcode)14590 void HText_updateKcode(HText *text, HTkcode kcode)
14591 {
14592     text->kcode = kcode;
14593 }
14594 
HText_getSpecifiedKcode(HText * text)14595 HTkcode HText_getSpecifiedKcode(HText *text)
14596 {
14597     return text->specified_kcode;
14598 }
14599 
HText_updateSpecifiedKcode(HText * text,HTkcode kcode)14600 void HText_updateSpecifiedKcode(HText *text, HTkcode kcode)
14601 {
14602     text->specified_kcode = kcode;
14603 }
14604 
HTMainText_Get_UCLYhndl(void)14605 int HTMainText_Get_UCLYhndl(void)
14606 {
14607     return (HTMainText ?
14608 	    HTAnchor_getUCLYhndl(HTMainText->node_anchor, UCT_STAGE_MIME)
14609 	    : -1);
14610 }
14611 
14612 #ifdef USE_CACHEJAR
LYHandleCache(const char * arg,HTParentAnchor * anAnchor,HTFormat format_out,HTStream * sink)14613 static int LYHandleCache(const char *arg,
14614 			 HTParentAnchor *anAnchor,
14615 			 HTFormat format_out,
14616 			 HTStream *sink)
14617 {
14618     HTFormat format_in = WWW_HTML;
14619     HTStream *target = NULL;
14620     char c;
14621     char *buf = NULL;
14622     char *title = NULL;
14623     char *address = NULL;
14624     char *content_type = NULL;
14625     char *content_language = NULL;
14626     char *content_encoding = NULL;
14627     char *content_location = NULL;
14628     char *content_disposition = NULL;
14629     char *content_md5 = NULL;
14630     char *message_id = NULL;
14631     char *date = NULL;
14632     char *owner = NULL;
14633     char *subject = NULL;
14634     char *expires = NULL;
14635     char *ETag = NULL;
14636     char *server = NULL;
14637     char *FileCache = NULL;
14638     char *last_modified = NULL;
14639     char *cache_control = NULL;
14640 
14641 #ifdef USE_SOURCE_CACHE
14642     char *source_cache_file = NULL;
14643 #endif
14644     off_t Size = 0;
14645     int x = -1;
14646 
14647     /*
14648      * Check if there is something to do.
14649      */
14650     if (HTList_count(loaded_texts) == 0) {
14651 	HTProgress(CACHE_JAR_IS_EMPTY);
14652 	LYSleepMsg();
14653 	HTNoDataOK = 1;
14654 	return (HT_NO_DATA);
14655     }
14656 
14657     /*
14658      * If # of LYNXCACHE:/# is number ask user if he/she want to delete it.
14659      */
14660     if (sscanf(arg, STR_LYNXCACHE "/%d", &x) == 1 && x > 0) {
14661 	CTRACE((tfp, "LYNXCACHE number is %d\n", x));
14662 	_statusline(CACHE_D_OR_CANCEL);
14663 	c = (char) LYgetch_single();
14664 	if (c == 'D') {
14665 	    HText *t = (HText *) HTList_objectAt(loaded_texts, x - 1);
14666 
14667 	    HTList_removeObjectAt(loaded_texts, x - 1);
14668 	    HText_free(t);
14669 	}
14670 	return (HT_NO_DATA);
14671     }
14672 
14673     /*
14674      * If we get to here, it was a LYNXCACHE:/ URL for creating and displaying
14675      * the Cache Jar Page.
14676      * Set up an HTML stream and return an updated Cache Jar Page.
14677      */
14678     target = HTStreamStack(format_in,
14679 			   format_out,
14680 			   sink, anAnchor);
14681     if (!target || target == NULL) {
14682 	HTSprintf0(&buf, CANNOT_CONVERT_I_TO_O,
14683 		   HTAtom_name(format_in), HTAtom_name(format_out));
14684 	HTAlert(buf);
14685 	FREE(buf);
14686 	return (HT_NOT_LOADED);
14687     }
14688 
14689     /*
14690      * Load HTML strings into buf and pass buf to the target for parsing and
14691      * rendering.
14692      */
14693 #define PUTS(buf)    (*target->isa->put_block)(target, buf, (int) strlen(buf))
14694 
14695     HTSprintf0(&buf,
14696 	       "<html>\n<head>\n<title>%s</title>\n</head>\n<body>\n",
14697 	       CACHE_JAR_TITLE);
14698     PUTS(buf);
14699     HTSprintf0(&buf, "<h1>%s (%s)%s<a href=\"%s%s\">%s</a></h1>\n",
14700 	       LYNX_NAME, LYNX_VERSION,
14701 	       HELP_ON_SEGMENT,
14702 	       helpfilepath, CACHE_JAR_HELP, CACHE_JAR_TITLE);
14703     PUTS(buf);
14704 
14705     /*
14706      * Max number of cached documents is always same as HTCacheSize.
14707      * We count them from oldest to newest. Currently cached document
14708      * is *never* listed, resulting in maximal entries of Cache Jar
14709      * to be HTCacheSize - 1
14710      */
14711     for (x = HTList_count(loaded_texts) - 1; x > 0; x--) {
14712 	/*
14713 	 * The number of the document in the cache list, its title in a link,
14714 	 * and its address and memory allocated for each cached document.
14715 	 */
14716 	HText *cachedoc = (HText *) HTList_objectAt(loaded_texts, x);
14717 
14718 	if (cachedoc != 0) {
14719 	    HTParentAnchor *docanchor = cachedoc->node_anchor;
14720 
14721 	    if (docanchor != 0) {
14722 #ifdef USE_SOURCE_CACHE
14723 		source_cache_file = docanchor->source_cache_file;
14724 #endif
14725 		Size = docanchor->content_length;
14726 		StrAllocCopy(title, docanchor->title);
14727 		StrAllocCopy(address, docanchor->address);
14728 		content_type = docanchor->content_type;
14729 		content_language = docanchor->content_language;
14730 		content_encoding = docanchor->content_encoding;
14731 		content_location = docanchor->content_location;
14732 		content_disposition = docanchor->content_disposition;
14733 		content_md5 = docanchor->content_md5;
14734 		message_id = docanchor->message_id;
14735 		owner = docanchor->owner;
14736 		StrAllocCopy(subject, docanchor->subject);
14737 		date = docanchor->date;
14738 		expires = docanchor->expires;
14739 		ETag = docanchor->ETag;
14740 		StrAllocCopy(server, docanchor->server);
14741 		FileCache = docanchor->FileCache;
14742 		last_modified = docanchor->last_modified;
14743 		cache_control = docanchor->cache_control;
14744 	    }
14745 	}
14746 
14747 	LYEntify(&address, TRUE);
14748 	if (isEmpty(title))
14749 	    StrAllocCopy(title, NO_TITLE);
14750 	else
14751 	    LYEntify(&title, TRUE);
14752 
14753 	HTSprintf0(&buf,
14754 		   "<p><em>%d.</em> Title: <a href=\"%s%d\">%s</a><br />URL: <a href=\"%s\">%s</a><br />",
14755 		   x, STR_LYNXCACHE, x, title, address, address);
14756 	PUTS(buf);
14757 	if (Size > 0) {
14758 	    HTSprintf0(&buf, "Size: %" PRI_off_t "  ", Size);
14759 
14760 	    PUTS(buf);
14761 	}
14762 	if (cachedoc != NULL && cachedoc->Lines > 0) {
14763 	    HTSprintf0(&buf, "Lines: %d  ", cachedoc->Lines);
14764 	    PUTS(buf);
14765 	}
14766 	if (FileCache != NULL) {
14767 	    HTSprintf0(&buf, "File-Cache: <a href=\"file://%s\">%s</a>  ",
14768 		       FileCache, FileCache);
14769 	    PUTS(buf);
14770 	}
14771 	if (cache_control != NULL) {
14772 	    HTSprintf0(&buf, "Cache-Control: %s  ", cache_control);
14773 	    PUTS(buf);
14774 	}
14775 	if (content_type != NULL) {
14776 	    HTSprintf0(&buf, "Content-Type: %s  ", content_type);
14777 	    PUTS(buf);
14778 	}
14779 	if (content_language != NULL) {
14780 	    HTSprintf0(&buf, "Content-Language: %s  ", content_language);
14781 	    PUTS(buf);
14782 	}
14783 	if (content_encoding != NULL) {
14784 	    HTSprintf0(&buf, "Content-Encoding: %s  ", content_encoding);
14785 	    PUTS(buf);
14786 	}
14787 	if (content_location != NULL) {
14788 	    HTSprintf0(&buf, "Content-Location: %s  ", content_location);
14789 	    PUTS(buf);
14790 	}
14791 	if (content_disposition != NULL) {
14792 	    HTSprintf0(&buf, "Content-Disposition: %s  ", content_disposition);
14793 	    PUTS(buf);
14794 	}
14795 	if (content_md5 != NULL) {
14796 	    HTSprintf0(&buf, "Content-MD5: %s  ", content_md5);
14797 	    PUTS(buf);
14798 	}
14799 	if (message_id != NULL) {
14800 	    HTSprintf0(&buf, "Message-ID: %s  ", message_id);
14801 	    PUTS(buf);
14802 	}
14803 	if (subject != NULL) {
14804 	    LYEntify(&subject, TRUE);
14805 	    HTSprintf0(&buf, "Subject: %s  ", subject);
14806 	    PUTS(buf);
14807 	}
14808 	if (owner != NULL) {
14809 	    HTSprintf0(&buf, "Owner: <a href=%s>%s</a>  ", owner, owner);
14810 	    PUTS(buf);
14811 	}
14812 	if (date != NULL) {
14813 	    HTSprintf0(&buf, "Date: %s  ", date);
14814 	    PUTS(buf);
14815 	}
14816 	if (expires != NULL) {
14817 	    HTSprintf0(&buf, "Expires: %s  ", expires);
14818 	    PUTS(buf);
14819 	}
14820 	if (last_modified != NULL) {
14821 	    HTSprintf0(&buf, "Last-Modified: %s  ", last_modified);
14822 	    PUTS(buf);
14823 	}
14824 	if (ETag != NULL) {
14825 	    HTSprintf0(&buf, "ETag: %s  ", ETag);
14826 	    PUTS(buf);
14827 	}
14828 	if (server != NULL) {
14829 	    LYEntify(&server, TRUE);
14830 	    HTSprintf0(&buf, "Server: <em>%s</em>  ", server);
14831 	    PUTS(buf);
14832 	}
14833 #ifdef USE_SOURCE_CACHE
14834 	if (source_cache_file != NULL) {
14835 	    HTSprintf0(&buf,
14836 		       "Source-Cache-File: <a href=\"file://%s\">%s</a>",
14837 		       source_cache_file, source_cache_file);
14838 	    PUTS(buf);
14839 	}
14840 #endif
14841 	HTSprintf0(&buf, "<br />");
14842 	PUTS(buf);
14843     }
14844     HTSprintf0(&buf, "</body></html>");
14845     PUTS(buf);
14846     FREE(subject);
14847     FREE(title);
14848     FREE(address);
14849     FREE(server);
14850 
14851     /*
14852      * Free the target to complete loading of the Cache Jar Page, and report a
14853      * successful load.
14854      */
14855     (*target->isa->_free) (target);
14856     FREE(buf);
14857     return (HT_LOADED);
14858 }
14859 
14860 #ifdef GLOBALDEF_IS_MACRO
14861 #define _LYCACHE_C_GLOBALDEF_1_INIT { "LYNXCACHE",LYHandleCache,0}
14862 GLOBALDEF(HTProtocol, LYLynxCache, _LYCACHE_C_GLOBALDEF_1_INIT);
14863 #else
14864 GLOBALDEF HTProtocol LYLynxCache =
14865 {"LYNXCACHE", LYHandleCache, 0};
14866 #endif /* GLOBALDEF_IS_MACRO */
14867 #endif /* USE_CACHEJAR */
14868