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 = ¤t;
6645 break;
6646 }
6647 } else if (direction > 0 &&
6648 same_anchor_as_link(curlink, previous.anc, ta_skip)) {
6649 group_to_go = ¤t;
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 = ¤t;
6663 break;
6664 }
6665 } else {
6666 group_to_go = ¤t;
6667 break;
6668 }
6669 }
6670 }
6671 }
6672 }
6673 if (!group_to_go && curlink < 0 && direction <= 0) {
6674 group_to_go = ¤t;
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 != ¤t &&
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