]>
git.ipfire.org Git - thirdparty/cups.git/blob - cgi-bin/help-index.c
2 * Online help index routines for CUPS.
4 * Copyright 2007-2015 by Apple Inc.
5 * Copyright 1997-2007 by Easy Software Products.
7 * These coded instructions, statements, and computer programs are the
8 * property of Apple Inc. and are protected by Federal copyright
9 * law. Distribution and use rights are outlined in the file "LICENSE.txt"
10 * which should have been included with this file. If this file is
11 * file is missing or damaged, see the license at "http://www.cups.org/".
15 * Include necessary headers...
18 #include "cgi-private.h"
23 * List of common English words that should not be indexed...
26 static char help_common_words
[][6] =
131 static help_word_t
*help_add_word(help_node_t
*n
, const char *text
);
132 static void help_delete_node(help_node_t
*n
);
133 static void help_delete_word(help_word_t
*w
);
134 static int help_load_directory(help_index_t
*hi
,
135 const char *directory
,
136 const char *relative
);
137 static int help_load_file(help_index_t
*hi
,
138 const char *filename
,
139 const char *relative
,
141 static help_node_t
*help_new_node(const char *filename
, const char *anchor
,
142 const char *section
, const char *text
,
143 time_t mtime
, off_t offset
,
145 __attribute__((nonnull(1,3,4)));
146 static int help_sort_by_name(help_node_t
*p1
, help_node_t
*p2
);
147 static int help_sort_by_score(help_node_t
*p1
, help_node_t
*p2
);
148 static int help_sort_words(help_word_t
*w1
, help_word_t
*w2
);
152 * 'helpDeleteIndex()' - Delete an index, freeing all memory used.
156 helpDeleteIndex(help_index_t
*hi
) /* I - Help index */
158 help_node_t
*node
; /* Current node */
161 DEBUG_printf(("helpDeleteIndex(hi=%p)", hi
));
166 for (node
= (help_node_t
*)cupsArrayFirst(hi
->nodes
);
168 node
= (help_node_t
*)cupsArrayNext(hi
->nodes
))
171 help_delete_node(node
);
174 cupsArrayDelete(hi
->nodes
);
175 cupsArrayDelete(hi
->sorted
);
182 * 'helpFindNode()' - Find a node in an index.
185 help_node_t
* /* O - Node pointer or NULL */
186 helpFindNode(help_index_t
*hi
, /* I - Index */
187 const char *filename
, /* I - Filename */
188 const char *anchor
) /* I - Anchor */
190 help_node_t key
; /* Search key */
193 DEBUG_printf(("helpFindNode(hi=%p, filename=\"%s\", anchor=\"%s\")",
194 hi
, filename
, anchor
));
197 * Range check input...
200 if (!hi
|| !filename
)
204 * Initialize the search key...
207 key
.filename
= (char *)filename
;
208 key
.anchor
= (char *)anchor
;
211 * Return any match...
214 return ((help_node_t
*)cupsArrayFind(hi
->nodes
, &key
));
219 * 'helpLoadIndex()' - Load a help index from disk.
222 help_index_t
* /* O - Index pointer or NULL */
223 helpLoadIndex(const char *hifile
, /* I - Index filename */
224 const char *directory
) /* I - Directory that is indexed */
226 help_index_t
*hi
; /* Help index */
227 cups_file_t
*fp
; /* Current file */
228 char line
[2048], /* Line from file */
229 *ptr
, /* Pointer into line */
230 *filename
, /* Filename in line */
231 *anchor
, /* Anchor in line */
232 *sectptr
, /* Section pointer in line */
233 section
[1024], /* Section name */
234 *text
; /* Text in line */
235 time_t mtime
; /* Modification time */
236 off_t offset
; /* Offset into file */
237 size_t length
; /* Length in bytes */
238 int update
; /* Update? */
239 help_node_t
*node
; /* Current node */
240 help_word_t
*word
; /* Current word */
243 DEBUG_printf(("helpLoadIndex(hifile=\"%s\", directory=\"%s\")",
247 * Create a new, empty index.
250 if ((hi
= (help_index_t
*)calloc(1, sizeof(help_index_t
))) == NULL
)
253 hi
->nodes
= cupsArrayNew((cups_array_func_t
)help_sort_by_name
, NULL
);
254 hi
->sorted
= cupsArrayNew((cups_array_func_t
)help_sort_by_score
, NULL
);
256 if (!hi
->nodes
|| !hi
->sorted
)
258 cupsArrayDelete(hi
->nodes
);
259 cupsArrayDelete(hi
->sorted
);
265 * Try loading the existing index file...
268 if ((fp
= cupsFileOpen(hifile
, "r")) != NULL
)
271 * Lock the file and then read the first line...
276 if (cupsFileGets(fp
, line
, sizeof(line
)) && !strcmp(line
, "HELPV2"))
279 * Got a valid header line, now read the data lines...
284 while (cupsFileGets(fp
, line
, sizeof(line
)))
287 * Each line looks like one of the following:
289 * filename mtime offset length "section" "text"
290 * filename#anchor offset length "text"
297 * Read a word in the current node...
300 if (!node
|| (ptr
= strrchr(line
, ' ')) == NULL
)
303 if ((word
= help_add_word(node
, ptr
+ 1)) != NULL
)
304 word
->count
= atoi(line
+ 1);
314 if ((ptr
= strchr(line
, ' ')) == NULL
)
317 while (isspace(*ptr
& 255))
320 if ((anchor
= strrchr(filename
, '#')) != NULL
)
326 mtime
= strtol(ptr
, &ptr
, 10);
328 offset
= strtoll(ptr
, &ptr
, 10);
329 length
= (size_t)strtoll(ptr
, &ptr
, 10);
331 while (isspace(*ptr
& 255))
346 while (*ptr
&& *ptr
!= '\"')
354 strlcpy(section
, sectptr
, sizeof(section
));
356 while (isspace(*ptr
& 255))
366 while (*ptr
&& *ptr
!= '\"')
374 if ((node
= help_new_node(filename
, anchor
, section
, text
,
375 mtime
, offset
, length
)) == NULL
)
380 cupsArrayAdd(hi
->nodes
, node
);
389 * Scan for new/updated files...
392 update
= help_load_directory(hi
, directory
, NULL
);
395 * Remove any files that are no longer installed...
398 for (node
= (help_node_t
*)cupsArrayFirst(hi
->nodes
);
400 node
= (help_node_t
*)cupsArrayNext(hi
->nodes
))
404 * Delete this node...
407 cupsArrayRemove(hi
->nodes
, node
);
408 help_delete_node(node
);
412 * Add nodes to the sorted array...
415 for (node
= (help_node_t
*)cupsArrayFirst(hi
->nodes
);
417 node
= (help_node_t
*)cupsArrayNext(hi
->nodes
))
418 cupsArrayAdd(hi
->sorted
, node
);
421 * Save the index if we updated it...
425 helpSaveIndex(hi
, hifile
);
428 * Return the index...
436 * 'helpSaveIndex()' - Save a help index to disk.
439 int /* O - 0 on success, -1 on error */
440 helpSaveIndex(help_index_t
*hi
, /* I - Index */
441 const char *hifile
) /* I - Index filename */
443 cups_file_t
*fp
; /* Index file */
444 help_node_t
*node
; /* Current node */
445 help_word_t
*word
; /* Current word */
448 DEBUG_printf(("helpSaveIndex(hi=%p, hifile=\"%s\")", hi
, hifile
));
451 * Try creating a new index file...
454 if ((fp
= cupsFileOpen(hifile
, "w9")) == NULL
)
458 * Lock the file while we write it...
463 cupsFilePuts(fp
, "HELPV2\n");
465 for (node
= (help_node_t
*)cupsArrayFirst(hi
->nodes
);
467 node
= (help_node_t
*)cupsArrayNext(hi
->nodes
))
470 * Write the current node with/without the anchor...
475 if (cupsFilePrintf(fp
, "%s#%s " CUPS_LLFMT
" " CUPS_LLFMT
" \"%s\"\n",
476 node
->filename
, node
->anchor
,
477 CUPS_LLCAST node
->offset
, CUPS_LLCAST node
->length
,
483 if (cupsFilePrintf(fp
, "%s %d " CUPS_LLFMT
" " CUPS_LLFMT
" \"%s\" \"%s\"\n",
484 node
->filename
, (int)node
->mtime
,
485 CUPS_LLCAST node
->offset
, CUPS_LLCAST node
->length
,
486 node
->section
? node
->section
: "", node
->text
) < 0)
491 * Then write the words associated with the node...
494 for (word
= (help_word_t
*)cupsArrayFirst(node
->words
);
496 word
= (help_word_t
*)cupsArrayNext(node
->words
))
497 if (cupsFilePrintf(fp
, " %d %s\n", word
->count
, word
->text
) < 0)
503 if (cupsFileClose(fp
) < 0)
513 * 'helpSearchIndex()' - Search an index.
516 help_index_t
* /* O - Search index */
517 helpSearchIndex(help_index_t
*hi
, /* I - Index */
518 const char *query
, /* I - Query string */
519 const char *section
, /* I - Limit search to this section */
520 const char *filename
) /* I - Limit search to this file */
522 help_index_t
*search
; /* Search index */
523 help_node_t
*node
; /* Current node */
524 help_word_t
*word
; /* Current word */
525 void *sc
; /* Search context */
526 int matches
; /* Number of matches */
529 DEBUG_printf(("helpSearchIndex(hi=%p, query=\"%s\", filename=\"%s\")",
530 hi
, query
, filename
));
540 * Reset the scores of all nodes to 0...
543 for (node
= (help_node_t
*)cupsArrayFirst(hi
->nodes
);
545 node
= (help_node_t
*)cupsArrayNext(hi
->nodes
))
549 * Find the first node to search in...
554 node
= helpFindNode(hi
, filename
, NULL
);
559 node
= (help_node_t
*)cupsArrayFirst(hi
->nodes
);
562 * Convert the query into a regular expression...
565 sc
= cgiCompileSearch(query
);
570 * Allocate a search index...
573 search
= calloc(1, sizeof(help_index_t
));
580 search
->nodes
= cupsArrayNew((cups_array_func_t
)help_sort_by_name
, NULL
);
581 search
->sorted
= cupsArrayNew((cups_array_func_t
)help_sort_by_score
, NULL
);
583 if (!search
->nodes
|| !search
->sorted
)
585 cupsArrayDelete(search
->nodes
);
586 cupsArrayDelete(search
->sorted
);
595 * Check each node in the index, adding matching nodes to the
599 for (; node
; node
= (help_node_t
*)cupsArrayNext(hi
->nodes
))
600 if (section
&& strcmp(node
->section
, section
))
602 else if (filename
&& strcmp(node
->filename
, filename
))
606 matches
= cgiDoSearch(sc
, node
->text
);
608 for (word
= (help_word_t
*)cupsArrayFirst(node
->words
);
610 word
= (help_word_t
*)cupsArrayNext(node
->words
))
611 if (cgiDoSearch(sc
, word
->text
) > 0)
612 matches
+= word
->count
;
617 * Found a match, add the node to the search index...
620 node
->score
= matches
;
622 cupsArrayAdd(search
->nodes
, node
);
623 cupsArrayAdd(search
->sorted
, node
);
628 * Free the search context...
634 * Return the results...
642 * 'help_add_word()' - Add a word to a node.
645 static help_word_t
* /* O - New word */
646 help_add_word(help_node_t
*n
, /* I - Node */
647 const char *text
) /* I - Word text */
649 help_word_t
*w
, /* New word */
650 key
; /* Search key */
653 DEBUG_printf(("2help_add_word(n=%p, text=\"%s\")", n
, text
));
656 * Create the words array as needed...
660 n
->words
= cupsArrayNew((cups_array_func_t
)help_sort_words
, NULL
);
663 * See if the word is already added...
666 key
.text
= (char *)text
;
668 if ((w
= (help_word_t
*)cupsArrayFind(n
->words
, &key
)) == NULL
)
671 * Create a new word...
674 if ((w
= calloc(1, sizeof(help_word_t
))) == NULL
)
677 if ((w
->text
= strdup(text
)) == NULL
)
683 cupsArrayAdd(n
->words
, w
);
687 * Bump the counter for this word and return it...
697 * 'help_delete_node()' - Free all memory used by a node.
701 help_delete_node(help_node_t
*n
) /* I - Node */
703 help_word_t
*w
; /* Current word */
706 DEBUG_printf(("2help_delete_node(n=%p)", n
));
723 for (w
= (help_word_t
*)cupsArrayFirst(n
->words
);
725 w
= (help_word_t
*)cupsArrayNext(n
->words
))
728 cupsArrayDelete(n
->words
);
735 * 'help_delete_word()' - Free all memory used by a word.
739 help_delete_word(help_word_t
*w
) /* I - Word */
741 DEBUG_printf(("2help_delete_word(w=%p)", w
));
754 * 'help_load_directory()' - Load a directory of files into an index.
757 static int /* O - 0 = success, -1 = error, 1 = updated */
759 help_index_t
*hi
, /* I - Index */
760 const char *directory
, /* I - Directory */
761 const char *relative
) /* I - Relative path */
763 cups_dir_t
*dir
; /* Directory file */
764 cups_dentry_t
*dent
; /* Directory entry */
765 char *ext
, /* Pointer to extension */
766 filename
[1024], /* Full filename */
767 relname
[1024]; /* Relative filename */
768 int update
; /* Updated? */
769 help_node_t
*node
; /* Current node */
772 DEBUG_printf(("2help_load_directory(hi=%p, directory=\"%s\", relative=\"%s\")",
773 hi
, directory
, relative
));
776 * Open the directory and scan it...
779 if ((dir
= cupsDirOpen(directory
)) == NULL
)
784 while ((dent
= cupsDirRead(dir
)) != NULL
)
790 if (dent
->filename
[0] == '.')
794 * Get absolute and relative filenames...
797 snprintf(filename
, sizeof(filename
), "%s/%s", directory
, dent
->filename
);
799 snprintf(relname
, sizeof(relname
), "%s/%s", relative
, dent
->filename
);
801 strlcpy(relname
, dent
->filename
, sizeof(relname
));
804 * Check if we have a HTML file...
807 if ((ext
= strstr(dent
->filename
, ".html")) != NULL
&&
808 (!ext
[5] || !strcmp(ext
+ 5, ".gz")))
811 * HTML file, see if we have already indexed the file...
814 if ((node
= helpFindNode(hi
, relname
, NULL
)) != NULL
)
817 * File already indexed - check dates to confirm that the
818 * index is up-to-date...
821 if (node
->mtime
== dent
->fileinfo
.st_mtime
)
824 * Same modification time, so mark all of the nodes
825 * for this file as up-to-date...
828 for (; node
; node
= (help_node_t
*)cupsArrayNext(hi
->nodes
))
829 if (!strcmp(node
->filename
, relname
))
840 help_load_file(hi
, filename
, relname
, dent
->fileinfo
.st_mtime
);
842 else if (S_ISDIR(dent
->fileinfo
.st_mode
))
845 * Process sub-directory...
848 if (help_load_directory(hi
, filename
, relname
) == 1)
860 * 'help_load_file()' - Load a HTML files into an index.
863 static int /* O - 0 = success, -1 = error */
865 help_index_t
*hi
, /* I - Index */
866 const char *filename
, /* I - Filename */
867 const char *relative
, /* I - Relative path */
868 time_t mtime
) /* I - Modification time */
870 cups_file_t
*fp
; /* HTML file */
871 help_node_t
*node
; /* Current node */
872 char line
[1024], /* Line from file */
873 temp
[1024], /* Temporary word */
874 section
[1024], /* Section */
875 *ptr
, /* Pointer into line */
876 *anchor
, /* Anchor name */
877 *text
; /* Text for anchor */
878 off_t offset
; /* File offset */
879 char quote
; /* Quote character */
880 help_word_t
*word
; /* Current word */
881 int wordlen
; /* Length of word */
884 DEBUG_printf(("2help_load_file(hi=%p, filename=\"%s\", relative=\"%s\", "
885 "mtime=%ld)", hi
, filename
, relative
, (long)mtime
));
887 if ((fp
= cupsFileOpen(filename
, "r")) == NULL
)
893 strlcpy(section
, "Other", sizeof(section
));
895 while (cupsFileGets(fp
, line
, sizeof(line
)))
898 * Look for "<TITLE>", "<A NAME", or "<!-- SECTION:" prefix...
901 if (!_cups_strncasecmp(line
, "<!-- SECTION:", 13))
904 * Got section line, copy it!
907 for (ptr
= line
+ 13; isspace(*ptr
& 255); ptr
++);
909 strlcpy(section
, ptr
, sizeof(section
));
910 if ((ptr
= strstr(section
, "-->")) != NULL
)
913 * Strip comment stuff from end of line...
916 for (*ptr
-- = '\0'; ptr
> line
&& isspace(*ptr
& 255); *ptr
-- = '\0');
918 if (isspace(*ptr
& 255))
924 for (ptr
= line
; (ptr
= strchr(ptr
, '<')) != NULL
;)
928 if (!_cups_strncasecmp(ptr
, "TITLE>", 6))
937 else if (!_cups_strncasecmp(ptr
, "A NAME=", 7))
945 if (*ptr
== '\"' || *ptr
== '\'')
948 * Get quoted anchor...
953 if ((ptr
= strchr(anchor
, quote
)) != NULL
)
961 * Get unquoted anchor...
966 for (ptr
= anchor
; *ptr
&& *ptr
!= '>' && !isspace(*ptr
& 255); ptr
++);
975 * Got the anchor, now lets find the end...
978 while (*ptr
&& *ptr
!= '>')
990 * Now collect text for the link...
994 while ((ptr
= strchr(text
, '<')) == NULL
)
996 ptr
= text
+ strlen(text
);
997 if (ptr
>= (line
+ sizeof(line
) - 2))
1002 if (!cupsFileGets(fp
, ptr
, sizeof(line
) - (size_t)(ptr
- line
) - 1))
1009 node
->length
= (size_t)(offset
- node
->offset
);
1017 if ((node
= helpFindNode(hi
, relative
, anchor
)) != NULL
)
1020 * Node already in the index, so replace the text and other
1024 cupsArrayRemove(hi
->nodes
, node
);
1027 free(node
->section
);
1034 for (word
= (help_word_t
*)cupsArrayFirst(node
->words
);
1036 word
= (help_word_t
*)cupsArrayNext(node
->words
))
1037 help_delete_word(word
);
1039 cupsArrayDelete(node
->words
);
1043 node
->section
= section
[0] ? strdup(section
) : NULL
;
1044 node
->text
= strdup(text
);
1045 node
->mtime
= mtime
;
1046 node
->offset
= offset
;
1055 node
= help_new_node(relative
, anchor
, section
, text
, mtime
, offset
, 0);
1059 * Go through the text value and replace tabs and newlines with
1060 * whitespace and eliminate extra whitespace...
1063 for (ptr
= node
->text
, text
= node
->text
; *ptr
;)
1064 if (isspace(*ptr
& 255))
1066 while (isspace(*ptr
& 255))
1071 else if (text
!= ptr
)
1082 * (Re)add the node to the array...
1085 cupsArrayAdd(hi
->nodes
, node
);
1095 * Scan this line for words...
1098 for (ptr
= line
; *ptr
; ptr
++)
1101 * Skip HTML stuff...
1106 if (!strncmp(ptr
, "<!--", 4))
1109 * Skip HTML comment...
1112 if ((text
= strstr(ptr
+ 4, "-->")) == NULL
)
1113 ptr
+= strlen(ptr
) - 1;
1120 * Skip HTML element...
1123 for (ptr
++; *ptr
&& *ptr
!= '>'; ptr
++)
1125 if (*ptr
== '\"' || *ptr
== '\'')
1127 for (quote
= *ptr
++; *ptr
&& *ptr
!= quote
; ptr
++);
1140 else if (*ptr
== '&')
1143 * Skip HTML entity...
1146 for (ptr
++; *ptr
&& *ptr
!= ';'; ptr
++);
1153 else if (!isalnum(*ptr
& 255))
1157 * Found the start of a word, search until we find the end...
1160 for (text
= ptr
, ptr
++; *ptr
&& isalnum(*ptr
& 255); ptr
++);
1162 wordlen
= (int)(ptr
- text
);
1164 memcpy(temp
, text
, (size_t)wordlen
);
1165 temp
[wordlen
] = '\0';
1169 if (wordlen
> 1 && !bsearch(temp
, help_common_words
,
1170 (sizeof(help_common_words
) /
1171 sizeof(help_common_words
[0])),
1172 sizeof(help_common_words
[0]),
1173 (int (*)(const void *, const void *))
1175 help_add_word(node
, temp
);
1180 * Get the offset of the next line...
1183 offset
= cupsFileTell(fp
);
1189 node
->length
= (size_t)(offset
- node
->offset
);
1196 * 'help_new_node()' - Create a new node and add it to an index.
1199 static help_node_t
* /* O - Node pointer or NULL on error */
1200 help_new_node(const char *filename
, /* I - Filename */
1201 const char *anchor
, /* I - Anchor */
1202 const char *section
, /* I - Section */
1203 const char *text
, /* I - Text */
1204 time_t mtime
, /* I - Modification time */
1205 off_t offset
, /* I - Offset in file */
1206 size_t length
) /* I - Length in bytes */
1208 help_node_t
*n
; /* Node */
1211 DEBUG_printf(("2help_new_node(filename=\"%s\", anchor=\"%s\", text=\"%s\", "
1212 "mtime=%ld, offset=%ld, length=%ld)", filename
, anchor
, text
,
1213 (long)mtime
, (long)offset
, (long)length
));
1215 n
= (help_node_t
*)calloc(1, sizeof(help_node_t
));
1219 n
->filename
= strdup(filename
);
1220 n
->anchor
= anchor
? strdup(anchor
) : NULL
;
1221 n
->section
= *section
? strdup(section
) : NULL
;
1222 n
->text
= strdup(text
);
1232 * 'help_sort_nodes_by_name()' - Sort nodes by section, filename, and anchor.
1235 static int /* O - Difference */
1236 help_sort_by_name(help_node_t
*n1
, /* I - First node */
1237 help_node_t
*n2
) /* I - Second node */
1239 int diff
; /* Difference */
1242 DEBUG_printf(("2help_sort_by_name(n1=%p(%s#%s), n2=%p(%s#%s)",
1243 n1
, n1
->filename
, n1
->anchor
,
1244 n2
, n2
->filename
, n2
->anchor
));
1246 if ((diff
= strcmp(n1
->filename
, n2
->filename
)) != 0)
1249 if (!n1
->anchor
&& !n2
->anchor
)
1251 else if (!n1
->anchor
)
1253 else if (!n2
->anchor
)
1256 return (strcmp(n1
->anchor
, n2
->anchor
));
1261 * 'help_sort_nodes_by_score()' - Sort nodes by score and text.
1264 static int /* O - Difference */
1265 help_sort_by_score(help_node_t
*n1
, /* I - First node */
1266 help_node_t
*n2
) /* I - Second node */
1268 int diff
; /* Difference */
1271 DEBUG_printf(("2help_sort_by_score(n1=%p(%d \"%s\" \"%s\"), "
1272 "n2=%p(%d \"%s\" \"%s\")",
1273 n1
, n1
->score
, n1
->section
, n1
->text
,
1274 n2
, n2
->score
, n2
->section
, n2
->text
));
1276 if (n1
->score
!= n2
->score
)
1277 return (n2
->score
- n1
->score
);
1279 if (n1
->section
&& !n2
->section
)
1281 else if (!n1
->section
&& n2
->section
)
1283 else if (n1
->section
&& n2
->section
&&
1284 (diff
= strcmp(n1
->section
, n2
->section
)) != 0)
1287 return (_cups_strcasecmp(n1
->text
, n2
->text
));
1292 * 'help_sort_words()' - Sort words alphabetically.
1295 static int /* O - Difference */
1296 help_sort_words(help_word_t
*w1
, /* I - Second word */
1297 help_word_t
*w2
) /* I - Second word */
1299 DEBUG_printf(("2help_sort_words(w1=%p(\"%s\"), w2=%p(\"%s\"))",
1300 w1
, w1
->text
, w2
, w2
->text
));
1302 return (_cups_strcasecmp(w1
->text
, w2
->text
));