Version:
~ [ 1.0 ] ~
1 /* access.c -- carry out accessibility checks
2
3 Copyright University of Toronto
4 Portions (c) 1998-2005 (W3C) MIT, ERCIM, Keio University
5 See tidy.h for the copyright notice.
6
7 CVS Info :
8
9 $Author: arnaud02 $
10 $Date: 2005/04/12 12:43:40 $
11 $Revision: 1.28 $
12
13 */
14
15 /*********************************************************************
16 * AccessibilityChecks
17 *
18 * Carries out processes for all accessibility checks. Traverses
19 * through all the content within the tree and evaluates the tags for
20 * accessibility.
21 *
22 * To perform the following checks, 'AccessibilityChecks' must be
23 * called AFTER the tree structure has been formed.
24 *
25 * If, in the command prompt, there is no specification of which
26 * accessibility priorities to check, no accessibility checks will be
27 * performed. (ie. '1' for priority 1, '2' for priorities 1 and 2,
28 * and '3') for priorities 1, 2 and 3.)
29 *
30 * Copyright University of Toronto
31 * Programmed by: Mike Lam and Chris Ridpath
32 * Modifications by : Terry Teague (TRT)
33 *
34 *********************************************************************/
35
36 #include "tidy-int.h"
37
38 #if SUPPORT_ACCESSIBILITY_CHECKS
39
40 #include "access.h"
41 #include "message.h"
42 #include "tags.h"
43 #include "attrs.h"
44 #include "tmbstr.h"
45
46
47 /*
48 The accessibility checks to perform depending on user's desire.
49
50 1. priority 1
51 2. priority 1 & 2
52 3. priority 1, 2, & 3
53 */
54
55 /* List of possible image types */
56 static const ctmbstr imageExtensions[] =
57 {".jpg", ".gif", ".tif", ".pct", ".pic", ".iff", ".dib",
58 ".tga", ".pcx", ".png", ".jpeg", ".tiff", ".bmp"};
59
60 #define N_IMAGE_EXTS (sizeof(imageExtensions)/sizeof(ctmbstr))
61
62 /* List of possible sound file types */
63 static const ctmbstr soundExtensions[] =
64 {".wav", ".au", ".aiff", ".snd", ".ra", ".rm"};
65
66 static const int soundExtErrCodes[] =
67 {
68 AUDIO_MISSING_TEXT_WAV,
69 AUDIO_MISSING_TEXT_AU,
70 AUDIO_MISSING_TEXT_AIFF,
71 AUDIO_MISSING_TEXT_SND,
72 AUDIO_MISSING_TEXT_RA,
73 AUDIO_MISSING_TEXT_RM
74 };
75
76 #define N_AUDIO_EXTS (sizeof(soundExtensions)/sizeof(ctmbstr))
77
78 /* List of possible media extensions */
79 static const ctmbstr mediaExtensions[] =
80 {".mpg", ".mov", ".asx", ".avi", ".ivf", ".m1v", ".mmm", ".mp2v",
81 ".mpa", ".mpe", ".mpeg", ".ram", ".smi", ".smil", ".swf",
82 ".wm", ".wma", ".wmv"};
83
84 #define N_MEDIA_EXTS (sizeof(mediaExtensions)/sizeof(ctmbstr))
85
86 /* List of possible frame sources */
87 static const ctmbstr frameExtensions[] =
88 {".htm", ".html", ".shtm", ".shtml", ".cfm", ".cfml",
89 ".asp", ".cgi", ".pl", ".smil"};
90
91 #define N_FRAME_EXTS (sizeof(frameExtensions)/sizeof(ctmbstr))
92
93 /* List of possible colour values */
94 static const int colorValues[][3] =
95 {
96 { 0, 0, 0},
97 {128,128,128},
98 {192,192,192},
99 {255,255,255},
100 {192, 0, 0},
101 {255, 0, 0},
102 {128, 0,128},
103 {255, 0,255},
104 { 0,128, 0},
105 { 0,255, 0},
106 {128,128, 0},
107 {255,255, 0},
108 { 0, 0,128},
109 { 0, 0,255},
110 { 0,128,128},
111 { 0,255,255}
112 };
113
114 #define N_COLOR_VALS (sizeof(colorValues)/(sizeof(int[3]))
115
116 /* These arrays are used to convert color names to their RGB values */
117 static const ctmbstr colorNames[] =
118 {
119 "black",
120 "silver",
121 "grey",
122 "white",
123 "maroon",
124 "red",
125 "purple",
126 "fuchsia",
127 "green",
128 "lime",
129 "olive",
130 "yellow",
131 "navy",
132 "blue",
133 "teal",
134 "aqua"
135 };
136
137 #define N_COLOR_NAMES (sizeof(colorNames)/sizeof(ctmbstr))
138 #define N_COLORS N_COLOR_NAMES
139
140
141 /* function prototypes */
142 static void InitAccessibilityChecks( TidyDocImpl* doc, int level123 );
143 static void FreeAccessibilityChecks( TidyDocImpl* doc );
144
145 static Bool GetRgb( ctmbstr color, int rgb[3] );
146 static Bool CompareColors( const int rgbBG[3], const int rgbFG[3] );
147 static int ctox( tmbchar ch );
148
149 /*
150 static void CheckMapAccess( TidyDocImpl* doc, Node* node, Node* front);
151 static void GetMapLinks( TidyDocImpl* doc, Node* node, Node* front);
152 static void CompareAnchorLinks( TidyDocImpl* doc, Node* front, int counter);
153 static void FindMissingLinks( TidyDocImpl* doc, Node* node, int counter);
154 */
155 static void CheckFormControls( TidyDocImpl* doc, Node* node );
156 static void MetaDataPresent( TidyDocImpl* doc, Node* node );
157 static void CheckEmbed( TidyDocImpl* doc, Node* node );
158 static void CheckListUsage( TidyDocImpl* doc, Node* node );
159
160 /*
161 GetFileExtension takes a path and returns the extension
162 portion of the path (if any).
163 */
164
165 static void GetFileExtension( ctmbstr path, tmbchar *ext, uint maxExt )
166 {
167 int i = tmbstrlen(path) - 1;
168
169 ext[0] = '\0';
170
171 do {
172 if ( path[i] == '/' || path[i] == '\\' )
173 break;
174 else if ( path[i] == '.' )
175 {
176 tmbstrncpy( ext, path+i, maxExt );
177 break;
178 }
179 } while ( --i > 0 );
180 }
181
182 /************************************************************************
183 * IsImage
184 *
185 * Checks if the given filename is an image file.
186 * Returns 'yes' if it is, 'no' if it's not.
187 ************************************************************************/
188
189 static Bool IsImage( ctmbstr iType )
190 {
191 uint i;
192
193 /* Get the file extension */
194 tmbchar ext[20];
195 GetFileExtension( iType, ext, sizeof(ext) );
196
197 /* Compare it to the array of known image file extensions */
198 for (i = 0; i < N_IMAGE_EXTS; i++)
199 {
200 if ( tmbstrcasecmp(ext, imageExtensions[i]) == 0 )
201 return yes;
202 }
203
204 return no;
205 }
206
207
208 /***********************************************************************
209 * IsSoundFile
210 *
211 * Checks if the given filename is a sound file.
212 * Returns 'yes' if it is, 'no' if it's not.
213 ***********************************************************************/
214
215 static int IsSoundFile( ctmbstr sType )
216 {
217 uint i;
218 tmbchar ext[ 20 ];
219 GetFileExtension( sType, ext, sizeof(ext) );
220
221 for (i = 0; i < N_AUDIO_EXTS; i++)
222 {
223 if ( tmbstrcasecmp(ext, soundExtensions[i]) == 0 )
224 return soundExtErrCodes[i];
225 }
226 return 0;
227 }
228
229
230 /***********************************************************************
231 * IsValidSrcExtension
232 *
233 * Checks if the 'SRC' value within the FRAME element is valid
234 * The 'SRC' extension must end in ".htm", ".html", ".shtm", ".shtml",
235 * ".cfm", ".cfml", ".asp", ".cgi", ".pl", or ".smil"
236 *
237 * Returns yes if it is, returns no otherwise.
238 ***********************************************************************/
239
240 static Bool IsValidSrcExtension( ctmbstr sType )
241 {
242 uint i;
243 tmbchar ext[20];
244 GetFileExtension( sType, ext, sizeof(ext) );
245
246 for (i = 0; i < N_FRAME_EXTS; i++)
247 {
248 if ( tmbstrcasecmp(ext, frameExtensions[i]) == 0 )
249 return yes;
250 }
251 return no;
252 }
253
254
255 /*********************************************************************
256 * IsValidMediaExtension
257 *
258 * Checks to warn the user that syncronized text equivalents are
259 * required if multimedia is used.
260 *********************************************************************/
261
262 static Bool IsValidMediaExtension( ctmbstr sType )
263 {
264 uint i;
265 tmbchar ext[20];
266 GetFileExtension( sType, ext, sizeof(ext) );
267
268 for (i = 0; i < N_MEDIA_EXTS; i++)
269 {
270 if ( tmbstrcasecmp(ext, mediaExtensions[i]) == 0 )
271 return yes;
272 }
273 return no;
274 }
275
276
277 /************************************************************************
278 * IsWhitespace
279 *
280 * Checks if the given string is all whitespace.
281 * Returns 'yes' if it is, 'no' if it's not.
282 ************************************************************************/
283
284 static Bool IsWhitespace( ctmbstr pString )
285 {
286 Bool isWht = yes;
287 ctmbstr cp;
288
289 for ( cp = pString; isWht && cp && *cp; ++cp )
290 {
291 isWht = IsWhite( *cp );
292 }
293 return isWht;
294 }
295
296 static Bool hasValue( AttVal* av )
297 {
298 return ( av && ! IsWhitespace(av->value) );
299 }
300
301 /***********************************************************************
302 * IsPlaceholderAlt
303 *
304 * Checks to see if there is an image and photo place holder contained
305 * in the ALT text.
306 *
307 * Returns 'yes' if there is, 'no' if not.
308 ***********************************************************************/
309
310 static Bool IsPlaceholderAlt( ctmbstr txt )
311 {
312 return ( strstr(txt, "image") != NULL ||
313 strstr(txt, "photo") != NULL );
314 }
315
316
317 /***********************************************************************
318 * IsPlaceholderTitle
319 *
320 * Checks to see if there is an TITLE place holder contained
321 * in the 'ALT' text.
322 *
323 * Returns 'yes' if there is, 'no' if not.
324
325 static Bool IsPlaceHolderTitle( ctmbstr txt )
326 {
327 return ( strstr(txt, "title") != NULL );
328 }
329 ***********************************************************************/
330
331
332 /***********************************************************************
333 * IsPlaceHolderObject
334 *
335 * Checks to see if there is an OBJECT place holder contained
336 * in the 'ALT' text.
337 *
338 * Returns 'yes' if there is, 'no' if not.
339 ***********************************************************************/
340
341 static Bool IsPlaceHolderObject( ctmbstr txt )
342 {
343 return ( strstr(txt, "object") != NULL );
344 }
345
346
347 /**********************************************************
348 * EndsWithBytes
349 *
350 * Checks to see if the ALT text ends with 'bytes'
351 * Returns 'yes', if true, 'no' otherwise.
352 **********************************************************/
353
354 static Bool EndsWithBytes( ctmbstr txt )
355 {
356 uint len = tmbstrlen( txt );
357 return ( len >= 5 && strcmp(txt+len-5, "bytes") == 0 );
358 }
359
360
361 /*******************************************************
362 * textFromOneNode
363 *
364 * Returns a list of characters contained within one
365 * text node.
366 *******************************************************/
367
368 static ctmbstr textFromOneNode( TidyDocImpl* doc, Node* node )
369 {
370 uint i;
371 uint x = 0;
372 tmbstr txt = doc->access.text;
373
374 if ( node )
375 {
376 /* Copy contents of a text node */
377 for (i = node->start; i < node->end; ++i, ++x )
378 {
379 txt[x] = doc->lexer->lexbuf[i];
380
381 /* Check buffer overflow */
382 if ( x >= sizeof(doc->access.text)-1 )
383 break;
384 }
385 }
386
387 txt[x] = '\0';
388 return txt;
389 }
390
391
392 /*********************************************************
393 * getTextNode
394 *
395 * Locates text nodes within a container element.
396 * Retrieves text that are found contained within
397 * text nodes, and concatenates the text.
398 *********************************************************/
399
400 static void getTextNode( TidyDocImpl* doc, Node* node )
401 {
402 tmbstr txtnod = doc->access.textNode;
403
404 /*
405 Continues to traverse through container element until it no
406 longer contains any more contents
407 */
408
409 /* If the tag of the node is NULL, then grab the text within the node */
410 if ( nodeIsText(node) )
411 {
412 uint i;
413
414 /* Retrieves each character found within the text node */
415 for (i = node->start; i < node->end; i++)
416 {
417 /* The text must not exceed buffer */
418 if ( doc->access.counter >= TEXTBUF_SIZE-1 )
419 return;
420
421 txtnod[ doc->access.counter++ ] = doc->lexer->lexbuf[i];
422 }
423
424 /* Traverses through the contents within a container element */
425 for ( node = node->content; node != NULL; node = node->next )
426 getTextNode( doc, node );
427 }
428 }
429
430
431 /**********************************************************
432 * getTextNodeClear
433 *
434 * Clears the current 'textNode' and reloads it with new
435 * text. The textNode must be cleared before use.
436 **********************************************************/
437
438 static tmbstr getTextNodeClear( TidyDocImpl* doc, Node* node )
439 {
440 /* Clears list */
441 ClearMemory( doc->access.textNode, TEXTBUF_SIZE );
442 doc->access.counter = 0;
443
444 getTextNode( doc, node->content );
445 return doc->access.textNode;
446 }
447
448 /**********************************************************
449 * LevelX_Enabled
450 *
451 * Tell whether access "X" is enabled.
452 **********************************************************/
453
454 static Bool Level1_Enabled( TidyDocImpl* doc )
455 {
456 return doc->access.PRIORITYCHK == 1 ||
457 doc->access.PRIORITYCHK == 2 ||
458 doc->access.PRIORITYCHK == 3;
459 }
460 static Bool Level2_Enabled( TidyDocImpl* doc )
461 {
462 return doc->access.PRIORITYCHK == 2 ||
463 doc->access.PRIORITYCHK == 3;
464 }
465 static Bool Level3_Enabled( TidyDocImpl* doc )
466 {
467 return doc->access.PRIORITYCHK == 3;
468 }
469
470 /********************************************************
471 * CheckColorAvailable
472 *
473 * Verify that information conveyed with color is
474 * available without color.
475 ********************************************************/
476
477 static void CheckColorAvailable( TidyDocImpl* doc, Node* node )
478 {
479 if (Level1_Enabled( doc ))
480 {
481 if ( nodeIsIMG(node) )
482 ReportAccessWarning( doc, node, INFORMATION_NOT_CONVEYED_IMAGE );
483
484 else if ( nodeIsAPPLET(node) )
485 ReportAccessWarning( doc, node, INFORMATION_NOT_CONVEYED_APPLET );
486
487 else if ( nodeIsOBJECT(node) )
488 ReportAccessWarning( doc, node, INFORMATION_NOT_CONVEYED_OBJECT );
489
490 else if ( nodeIsSCRIPT(node) )
491 ReportAccessWarning( doc, node, INFORMATION_NOT_CONVEYED_SCRIPT );
492
493 else if ( nodeIsINPUT(node) )
494 ReportAccessWarning( doc, node, INFORMATION_NOT_CONVEYED_INPUT );
495 }
496 }
497
498 /*********************************************************************
499 * CheckColorContrast
500 *
501 * Checks elements for color contrast. Must have valid contrast for
502 * valid visibility.
503 *
504 * This logic is extremely fragile as it does not recognize
505 * the fact that color is inherited by many components and
506 * that BG and FG colors are often set separately. E.g. the
507 * background color may be set by for the body or a table
508 * or a cell. The foreground color may be set by any text
509 * element (p, h1, h2, input, textarea), either explicitly
510 * or by style. Ergo, this test will not handle most real
511 * world cases. It's a start, however.
512 *********************************************************************/
513
514 static void CheckColorContrast( TidyDocImpl* doc, Node* node )
515 {
516 int rgbBG[3] = {255,255,255}; /* Black text on white BG */
517
518 if (Level3_Enabled( doc ))
519 {
520 Bool gotBG = yes;
521 AttVal* av;
522
523 /* Check for 'BGCOLOR' first to compare with other color attributes */
524 for ( av = node->attributes; av; av = av->next )
525 {
526 if ( attrIsBGCOLOR(av) )
527 {
528 if ( hasValue(av) )
529 gotBG = GetRgb( av->value, rgbBG );
530 }
531 }
532
533 /*
534 Search for COLOR attributes to compare with background color
535 Must have valid colour contrast
536 */
537 for ( av = node->attributes; gotBG && av != NULL; av = av->next )
538 {
539 uint errcode = 0;
540 if ( attrIsTEXT(av) )
541 errcode = COLOR_CONTRAST_TEXT;
542 else if ( attrIsLINK(av) )
543 errcode = COLOR_CONTRAST_LINK;
544 else if ( attrIsALINK(av) )
545 errcode = COLOR_CONTRAST_ACTIVE_LINK;
546 else if ( attrIsVLINK(av) )
547 errcode = COLOR_CONTRAST_VISITED_LINK;
548
549 if ( errcode && hasValue(av) )
550 {
551 int rgbFG[3] = {0, 0, 0}; /* Black text */
552
553 if ( GetRgb(av->value, rgbFG) &&
554 !CompareColors(rgbBG, rgbFG) )
555 {
556 ReportAccessWarning( doc, node, errcode );
557 }
558 }
559 }
560 }
561 }
562
563
564 /**************************************************************
565 * CompareColors
566 *
567 * Compares two RGB colors for good contrast.
568 **************************************************************/
569 static int minmax( int i1, int i2 )
570 {
571 return MAX(i1, i2) - MIN(i1,i2);
572 }
573 static int brightness( const int rgb[3] )
574 {
575 return ((rgb[0]*299) + (rgb[1]*587) + (rgb[2]*114)) / 1000;
576 }
577
578 static Bool CompareColors( const int rgbBG[3], const int rgbFG[3] )
579 {
580 int brightBG = brightness( rgbBG );
581 int brightFG = brightness( rgbFG );
582
583 int diffBright = minmax( brightBG, brightFG );
584
585 int diffColor = minmax( rgbBG[0], rgbFG[0] )
586 + minmax( rgbBG[1], rgbFG[1] )
587 + minmax( rgbBG[2], rgbFG[2] );
588
589 return ( diffBright > 180 &&
590 diffColor > 500 );
591 }
592
593
594 /*********************************************************************
595 * GetRgb
596 *
597 * Gets the red, green and blue values for this attribute for the
598 * background.
599 *
600 * Example: If attribute is BGCOLOR="#121005" then red = 18, green = 16,
601 * blue = 5.
602 *********************************************************************/
603
604 static Bool GetRgb( ctmbstr color, int rgb[] )
605 {
606 uint x;
607
608 /* Check if we have a color name */
609 for (x = 0; x < N_COLORS; x++)
610 {
611 if ( strstr(colorNames[x], color) != NULL )
612 {
613 rgb[0] = colorValues[x][0];
614 rgb[1] = colorValues[x][1];
615 rgb[2] = colorValues[x][2];
616 return yes;
617 }
618 }
619
620 /*
621 No color name so must be hex values
622 Is this a number in hexadecimal format?
623 */
624
625 /* Must be 7 characters in the RGB value (including '#') */
626 if ( tmbstrlen(color) == 7 && color[0] == '#' )
627 {
628 rgb[0] = (ctox(color[1]) * 16) + ctox(color[2]);
629 rgb[1] = (ctox(color[3]) * 16) + ctox(color[4]);
630 rgb[2] = (ctox(color[5]) * 16) + ctox(color[6]);
631 return yes;
632 }
633 return no;
634 }
635
636
637
638 /*******************************************************************
639 * ctox
640 *
641 * Converts a character to a number.
642 * Example: if given character is 'A' then returns 10.
643 *
644 * Returns the number that the character represents. Returns -1 if not a
645 * valid number.
646 *******************************************************************/
647
648 static int ctox( tmbchar ch )
649 {
650 if ( ch >= '' && ch <= '9' )
651 {
652 return ch - '';
653 }
654 else if ( ch >= 'a' && ch <= 'f' )
655 {
656 return ch - 'a' + 10;
657 }
658 else if ( ch >= 'A' && ch <= 'F' )
659 {
660 return ch - 'A' + 10;
661 }
662 return -1;
663 }
664
665
666 /***********************************************************
667 * CheckImage
668 *
669 * Checks all image attributes for specific elements to
670 * check for validity of the values contained within
671 * the attributes. An appropriate warning message is displayed
672 * to indicate the error.
673 ***********************************************************/
674
675 static void CheckImage( TidyDocImpl* doc, Node* node )
676 {
677 Bool HasAlt = no;
678 Bool HasIsMap = no;
679 Bool HasLongDesc = no;
680 Bool HasDLINK = no;
681 Bool HasValidHeight = no;
682 Bool HasValidWidthBullet = no;
683 Bool HasValidWidthHR = no;
684 Bool HasTriggeredMissingLongDesc = no;
685
686 AttVal* av;
687
688 if (Level1_Enabled( doc ))
689 {
690 /* Checks all image attributes for invalid values within attributes */
691 for (av = node->attributes; av != NULL; av = av->next)
692 {
693 /*
694 Checks for valid ALT attribute.
695 The length of the alt text must be less than 150 characters
696 long.
697 */
698 if ( attrIsALT(av) )
699 {
700 if (av->value != NULL)
701 {
702 if ((tmbstrlen(av->value) < 150) &&
703 (IsPlaceholderAlt (av->value) == no) &&
704 (IsPlaceHolderObject (av->value) == no) &&
705 (EndsWithBytes (av->value) == no) &&
706 (IsImage (av->value) == no))
707 {
708 HasAlt = yes;
709 }
710
711 else if (tmbstrlen (av->value) > 150)
712 {
713 HasAlt = yes;
714 ReportAccessWarning( doc, node, IMG_ALT_SUSPICIOUS_TOO_LONG );
715 }
716
717 else if (IsImage (av->value) == yes)
718 {
719 HasAlt = yes;
720 ReportAccessWarning( doc, node, IMG_ALT_SUSPICIOUS_FILENAME);
721 }
722
723 else if (IsPlaceholderAlt (av->value) == yes)
724 {
725 HasAlt = yes;
726 ReportAccessWarning( doc, node, IMG_ALT_SUSPICIOUS_PLACEHOLDER);
727 }
728
729 else if (EndsWithBytes (av->value) == yes)
730 {
731 HasAlt = yes;
732 ReportAccessWarning( doc, node, IMG_ALT_SUSPICIOUS_FILE_SIZE);
733 }
734 }
735 }
736
737 /*
738 Checks for width values of 'bullets' and 'horizontal
739 rules' for validity.
740
741 Valid pixel width for 'bullets' must be < 30, and > 150 for
742 horizontal rules.
743 */
744 else if ( attrIsWIDTH(av) )
745 {
746 /* Longdesc attribute needed if width attribute is not present. */
747 if ( hasValue(av) )
748 {
749 int width = atoi( av->value );
750 if ( width < 30 )
751 HasValidWidthBullet = yes;
752
753 if ( width > 150 )
754 HasValidWidthHR = yes;
755 }
756 }
757
758 /*
759 Checks for height values of 'bullets' and horizontal
760 rules for validity.
761
762 Valid pixel height for 'bullets' and horizontal rules
763 mustt be < 30.
764 */
765 else if ( attrIsHEIGHT(av) )
766 {
767 /* Longdesc attribute needed if height attribute not present. */
768 if ( hasValue(av) && atoi(av->value) < 30 )
769 HasValidHeight = yes;
770 }
771
772 /*
773 Checks for longdesc and determines validity.
774 The length of the 'longdesc' must be > 1
775 */
776 else if ( attrIsLONGDESC(av) )
777 {
778 if ( hasValue(av) && tmbstrlen(av->value) > 1 )
779 HasLongDesc = yes;
780 }
781
782 /*
783 Checks for 'USEMAP' attribute. Ensures that
784 text links are provided for client-side image maps
785 */
786 else if ( attrIsUSEMAP(av) )
787 {
788 if ( hasValue(av) )
789 doc->access.HasUseMap = yes;
790 }
791
792 else if ( attrIsISMAP(av) )
793 {
794 HasIsMap = yes;
795 }
796 }
797
798
799 /*
800 Check to see if a dLINK is present. The ANCHOR element must
801 be present following the IMG element. The text found between
802 the ANCHOR tags must be < 6 characters long, and must contain
803 the letter 'd'.
804 */
805 if ( nodeIsA(node->next) )
806 {
807 node = node->next;
808
809 /*
810 Node following the anchor must be a text node
811 for dLINK to exist
812 */
813
814 if(node->content != NULL && (node->content)->tag == NULL)
815 {
816 /* Number of characters found within the text node */
817 ctmbstr word = textFromOneNode( doc, node->content);
818
819 if ((strcmp(word,"d") == 0)||
820 (strcmp(word,"D") == 0))
821 {
822 HasDLINK = yes;
823 }
824 }
825 }
826
827 /*
828 Special case check for dLINK. This will occur if there is
829 whitespace between the <img> and <a> elements. Ignores
830 whitespace and continues check for dLINK.
831 */
832
833 if ( node->next && !node->next->tag )
834 {
835 node = node->next;
836
837 if ( nodeIsA(node->next) )
838 {
839 node = node->next;
840
841 /*
842 Node following the ANCHOR must be a text node
843 for dLINK to exist
844 */
845 if(node->content != NULL && node->content->tag == NULL)
846 {
847 /* Number of characters found within the text node */
848 ctmbstr word = textFromOneNode( doc, node->content );
849
850 if ((strcmp(word, "d") == 0)||
851 (strcmp(word, "D") == 0))
852 {
853 HasDLINK = yes;
854 }
855 }
856 }
857 }
858
859 if ((HasAlt == no)&&
860 (HasValidWidthBullet == yes)&&
861 (HasValidHeight == yes))
862 {
863 }
864
865 if ((HasAlt == no)&&
866 (HasValidWidthHR == yes)&&
867 (HasValidHeight == yes))
868 {
869 }
870
871 if (HasAlt == no)
872 {
873 ReportAccessError( doc, node, IMG_MISSING_ALT);
874 }
875
876 if ((HasLongDesc == no)&&
877 (HasValidHeight ==yes)&&
878 ((HasValidWidthHR == yes)||
879 (HasValidWidthBullet == yes)))
880 {
881 HasTriggeredMissingLongDesc = yes;
882 }
883
884 if (HasTriggeredMissingLongDesc == no)
885 {
886 if ((HasDLINK == yes)&&
887 (HasLongDesc == no))
888 {
889 ReportAccessWarning( doc, node, IMG_MISSING_LONGDESC);
890 }
891
892 if ((HasLongDesc == yes)&&
893 (HasDLINK == no))
894 {
895 ReportAccessWarning( doc, node, IMG_MISSING_DLINK);
896 }
897
898 if ((HasLongDesc == no)&&
899 (HasDLINK == no))
900 {
901 ReportAccessWarning( doc, node, IMG_MISSING_LONGDESC_DLINK);
902 }
903 }
904
905 if (HasIsMap == yes)
906 {
907 ReportAccessError( doc, node, IMAGE_MAP_SERVER_SIDE_REQUIRES_CONVERSION);
908
909 ReportAccessWarning( doc, node, IMG_MAP_SERVER_REQUIRES_TEXT_LINKS);
910 }
911 }
912 }
913
914
915 /***********************************************************
916 * CheckApplet
917 *
918 * Checks APPLET element to check for validity pertaining
919 * the 'ALT' attribute. An appropriate warning message is
920 * displayed to indicate the error. An appropriate warning
921 * message is displayed to indicate the error. If no 'ALT'
922 * text is present, then there must be alternate content
923 * within the APPLET element.
924 ***********************************************************/
925
926 static void CheckApplet( TidyDocImpl* doc, Node* node )
927 {
928 Bool HasAlt = no;
929 Bool HasDescription = no;
930
931 AttVal* av;
932
933 if (Level1_Enabled( doc ))
934 {
935 /* Checks for attributes within the APPLET element */
936 for (av = node->attributes; av != NULL; av = av->next)
937 {
938 /*
939 Checks for valid ALT attribute.
940 The length of the alt text must be > 4 characters in length
941 but must be < 150 characters long.
942 */
943
944 if ( attrIsALT(av) )
945 {
946 if (av->value != NULL)
947 {
948 HasAlt = yes;
949 }
950 }
951 }
952
953 if (HasAlt == no)
954 {
955 /* Must have alternate text representation for that element */
956 if (node->content != NULL)
957 {
958 ctmbstr word = NULL;
959
960 if ( node->content->tag == NULL )
961 word = textFromOneNode( doc, node->content);
962
963 if ( node->content->content != NULL &&
964 node->content->content->tag == NULL )
965 {
966 word = textFromOneNode( doc, node->content->content);
967 }
968
969 if ( word != NULL && !IsWhitespace(word) )
970 HasDescription = yes;
971 }
972 }
973
974 if ( !HasDescription && !HasAlt )
975 {
976 ReportAccessError( doc, node, APPLET_MISSING_ALT );
977 }
978 }
979 }
980
981
982 /*******************************************************************
983 * CheckObject
984 *
985 * Checks to verify whether the OBJECT element contains
986 * 'ALT' text, and to see that the sound file selected is
987 * of a valid sound file type. OBJECT must have an alternate text
988 * representation.
989 *******************************************************************/
990
991 static void CheckObject( TidyDocImpl* doc, Node* node )
992 {
993 Bool HasAlt = no;
994 Bool HasDescription = no;
995
996 if (Level1_Enabled( doc ))
997 {
998 if ( node->content != NULL)
999 {
1000 if ( node->content->type != TextNode )
1001 {
1002 Node* tnode = node->content;
1003 AttVal* av;
1004
1005 for ( av=tnode->attributes; av; av = av->next )
1006 {
1007 if ( attrIsALT(av) )
1008 {
1009 HasAlt = yes;
1010 break;
1011 }
1012 }
1013 }
1014
1015 /* Must have alternate text representation for that element */
1016 if ( !HasAlt )
1017 {
1018 ctmbstr word = NULL;
1019
1020 if ( nodeIsText(node->content) )
1021 word = textFromOneNode( doc, node->content );
1022
1023 if ( word == NULL &&
1024 nodeIsText(node->content->content) )
1025 {
1026 word = textFromOneNode( doc, node->content->content );
1027 }
1028
1029 if ( word != NULL && !IsWhitespace(word) )
1030 HasDescription = yes;
1031 }
1032 }
1033
1034 if ( !HasAlt && !HasDescription )
1035 {
1036 ReportAccessError( doc, node, OBJECT_MISSING_ALT );
1037 }
1038 }
1039 }
1040
1041
1042 /***************************************************************
1043 * CheckMissingStyleSheets
1044 *
1045 * Ensures that stylesheets are used to control the presentation.
1046 ***************************************************************/
1047
1048 static Bool CheckMissingStyleSheets( TidyDocImpl* doc, Node* node )
1049 {
1050 AttVal* av;
1051 Node* content;
1052 Bool sspresent = no;
1053
1054 for ( content = node->content;
1055 !sspresent && content != NULL;
1056 content = content->next )
1057 {
1058 sspresent = ( nodeIsLINK(content) ||
1059 nodeIsSTYLE(content) ||
1060 nodeIsFONT(content) ||
1061 nodeIsBASEFONT(content) );
1062
1063 for ( av = content->attributes;
1064 !sspresent && av != NULL;
1065 av = av->next )
1066 {
1067 sspresent = ( attrIsSTYLE(av) || attrIsTEXT(av) ||
1068 attrIsVLINK(av) || attrIsALINK(av) ||
1069 attrIsLINK(av) );
1070
1071 if ( !sspresent && attrIsREL(av) )
1072 {
1073 sspresent = AttrValueIs(av, "stylesheet");
1074 }
1075 }
1076
1077 if ( ! sspresent )
1078 sspresent = CheckMissingStyleSheets( doc, content );
1079 }
1080 return sspresent;
1081 }
1082
1083
1084 /*******************************************************************
1085 * CheckFrame
1086 *
1087 * Checks if the URL is valid and to check if a 'LONGDESC' is needed
1088 * within the FRAME element. If a 'LONGDESC' is needed, the value must
1089 * be valid. The URL must end with the file extension, htm, or html.
1090 * Also, checks to ensure that the 'SRC' and 'TITLE' values are valid.
1091 *******************************************************************/
1092
1093 static void CheckFrame( TidyDocImpl* doc, Node* node )
1094 {
1095 Bool HasTitle = no;
1096 AttVal* av;
1097
1098 doc->access.numFrames++;
1099
1100 if (Level1_Enabled( doc ))
1101 {
1102 /* Checks for attributes within the FRAME element */
1103 for (av = node->attributes; av != NULL; av = av->next)
1104 {
1105 /* Checks if 'LONGDESC' value is valid only if present */
1106 if ( attrIsLONGDESC(av) )
1107 {
1108 if ( hasValue(av) && tmbstrlen(av->value) > 1 )
1109 {
1110 doc->access.HasCheckedLongDesc++;
1111 }
1112 }
1113
1114 /* Checks for valid 'SRC' value within the frame element */
1115 else if ( attrIsSRC(av) )
1116 {
1117 if ( hasValue(av) && !IsValidSrcExtension(av->value) )
1118 {
1119 ReportAccessError( doc, node, FRAME_SRC_INVALID );
1120 }
1121 }
1122
1123 /* Checks for valid 'TITLE' value within frame element */
1124 else if ( attrIsTITLE(av) )
1125 {
1126 if ( hasValue(av) )
1127 HasTitle = yes;
1128
1129 if ( !HasTitle )
1130 {
1131 if ( av->value == NULL || tmbstrlen(av->value) == 0 )
1132 {
1133 HasTitle = yes;
1134 ReportAccessError( doc, node, FRAME_TITLE_INVALID_NULL);
1135 }
1136 else
1137 {
1138 if ( IsWhitespace(av->value) && tmbstrlen(av->value) > 0 )
1139 {
1140 HasTitle = yes;
1141 ReportAccessError( doc, node, FRAME_TITLE_INVALID_SPACES );
1142 }
1143 }
1144 }
1145 }
1146 }
1147
1148 if ( !HasTitle )
1149 {
1150 ReportAccessError( doc, node, FRAME_MISSING_TITLE);
1151 }
1152
1153 if ( doc->access.numFrames==3 && doc->access.HasCheckedLongDesc<3 )
1154 {
1155 doc->access.numFrames = 0;
1156 ReportAccessWarning( doc, node, FRAME_MISSING_LONGDESC );
1157 }
1158 }
1159 }
1160
1161
1162 /****************************************************************
1163 * CheckIFrame
1164 *
1165 * Checks if 'SRC' value is valid. Must end in appropriate
1166 * file extension.
1167 ****************************************************************/
1168
1169 static void CheckIFrame( TidyDocImpl* doc, Node* node )
1170 {
1171 if (Level1_Enabled( doc ))
1172 {
1173 /* Checks for valid 'SRC' value within the IFRAME element */
1174 AttVal* av = attrGetSRC( node );
1175 if ( hasValue(av) )
1176 {
1177 if ( !IsValidSrcExtension(av->value) )
1178 ReportAccessError( doc, node, FRAME_SRC_INVALID );
1179 }
1180 }
1181 }
1182
1183
1184 /**********************************************************************
1185 * CheckAnchorAccess
1186 *
1187 * Checks that the sound file is valid, and to ensure that
1188 * text transcript is present describing the 'HREF' within the
1189 * ANCHOR element. Also checks to see ensure that the 'TARGET' attribute
1190 * (if it exists) is not NULL and does not contain '_new' or '_blank'.
1191 **********************************************************************/
1192
1193 static void CheckAnchorAccess( TidyDocImpl* doc, Node* node )
1194 {
1195 AttVal* av;
1196 Bool HasDescription = no;
1197 Bool HasTriggeredLink = no;
1198
1199 /* Checks for attributes within the ANCHOR element */
1200 for ( av = node->attributes; av != NULL; av = av->next )
1201 {
1202 if (Level1_Enabled( doc ))
1203 {
1204 /* Must be of valid sound file type */
1205 if ( attrIsHREF(av) )
1206 {
1207 if ( hasValue(av) )
1208 {
1209 tmbchar ext[ 20 ];
1210 GetFileExtension (av->value, ext, sizeof(ext) );
1211
1212 /* Checks to see if multimedia is used */
1213 if ( IsValidMediaExtension(av->value) )
1214 {
1215 ReportAccessError( doc, node, MULTIMEDIA_REQUIRES_TEXT );
1216 }
1217
1218 /*
1219 Checks for validity of sound file, and checks to see if
1220 the file is described within the document, or by a link
1221 that is present which gives the description.
1222 */
1223 if ( tmbstrlen(ext) < 6 && tmbstrlen(ext) > 0 )
1224 {
1225 int errcode = IsSoundFile( av->value );
1226 if ( errcode )
1227 {
1228 if (node->next != NULL)
1229 {
1230 if (node->next->tag == NULL)
1231 {
1232 ctmbstr word = textFromOneNode( doc, node->next);
1233
1234 /* Must contain at least one letter in the text */
1235 if (IsWhitespace (word) == no)
1236 {
1237 HasDescription = yes;
1238 }
1239 }
1240 }
1241
1242 /* Must contain text description of sound file */
1243 if ( !HasDescription )
1244 {
1245 ReportAccessError( doc, node, errcode );
1246 }
1247 }
1248 }
1249 }
1250 }
1251 }
1252
1253 if (Level2_Enabled( doc ))
1254 {
1255 /* Checks 'TARGET' attribute for validity if it exists */
1256 if ( attrIsTARGET(av) )
1257 {
1258 if (AttrValueIs(av, "_new"))
1259 {
1260 ReportAccessWarning( doc, node, NEW_WINDOWS_REQUIRE_WARNING_NEW);
1261 }
1262 else if (AttrValueIs(av, "_blank"))
1263 {
1264 ReportAccessWarning( doc, node, NEW_WINDOWS_REQUIRE_WARNING_BLANK);
1265 }
1266 }
1267 }
1268 }
1269
1270 if (Level2_Enabled( doc ))
1271 {
1272 if ((node->content != NULL)&&
1273 (node->content->tag == NULL))
1274 {
1275 ctmbstr word = textFromOneNode( doc, node->content);
1276
1277 if ((word != NULL)&&
1278 (IsWhitespace (word) == no))
1279 {
1280 if (strcmp (word, "more") == 0)
1281 {
1282 HasTriggeredLink = yes;
1283 }
1284
1285 if (strcmp (word, "click here") == 0)
1286 {
1287 ReportAccessWarning( doc, node, LINK_TEXT_NOT_MEANINGFUL_CLICK_HERE);
1288 }
1289
1290 if (HasTriggeredLink == no)
1291 {
1292 if (tmbstrlen (word) < 6)
1293 {
1294 ReportAccessWarning( doc, node, LINK_TEXT_NOT_MEANINGFUL);
1295 }
1296 }
1297
1298 if (tmbstrlen (word) > 60)
1299 {
1300 ReportAccessWarning( doc, node, LINK_TEXT_TOO_LONG);
1301 }
1302
1303 }
1304 }
1305
1306 if (node->content == NULL)
1307 {
1308 ReportAccessWarning( doc, node, LINK_TEXT_MISSING);
1309 }
1310 }
1311 }
1312
1313
1314 /************************************************************
1315 * CheckArea
1316 *
1317 * Checks attributes within the AREA element to
1318 * determine if the 'ALT' text and 'HREF' values are valid.
1319 * Also checks to see ensure that the 'TARGET' attribute
1320 * (if it exists) is not NULL and does not contain '_new'
1321 * or '_blank'.
1322 ************************************************************/
1323
1324 static void CheckArea( TidyDocImpl* doc, Node* node )
1325 {
1326 Bool HasAlt = no;
1327 AttVal* av;
1328
1329 /* Checks all attributes within the AREA element */
1330 for (av = node->attributes; av != NULL; av = av->next)
1331 {
1332 if (Level1_Enabled( doc ))
1333 {
1334 /*
1335 Checks for valid ALT attribute.
1336 The length of the alt text must be > 4 characters long
1337 but must be less than 150 characters long.
1338 */
1339
1340 if ( attrIsALT(av) )
1341 {
1342 /* The check for validity */
1343 if (av->value != NULL)
1344 {
1345 HasAlt = yes;
1346 }
1347 }
1348 }
1349
1350 if (Level2_Enabled( doc ))
1351 {
1352 if ( attrIsTARGET(av) )
1353 {
1354 if (AttrValueIs(av, "_new"))