/src/aspell/lib/new_filter.cpp
Line | Count | Source (jump to first uncovered line) |
1 | | // This file is part of The New Aspell |
2 | | // Copyright (C) 2002 by Kevin Atkinson and Christoph Hintermüller |
3 | | // under the GNU LGPL license version 2.0 or 2.1. You should have |
4 | | // received a copy of the LGPL license along with this library if you |
5 | | // did not you can find it at http://www.gnu.org/. |
6 | | // |
7 | | // Expansion for loading filter libraries and collections upon startup |
8 | | // was added by Christoph Hintermüller |
9 | | |
10 | | #include "settings.h" |
11 | | |
12 | | #include "cache-t.hpp" |
13 | | #include "asc_ctype.hpp" |
14 | | #include "config.hpp" |
15 | | #include "enumeration.hpp" |
16 | | #include "errors.hpp" |
17 | | #include "filter.hpp" |
18 | | #include "filter_entry.hpp" |
19 | | #include "fstream.hpp" |
20 | | #include "getdata.hpp" |
21 | | #include "indiv_filter.hpp" |
22 | | #include "iostream.hpp" |
23 | | #include "itemize.hpp" |
24 | | #include "key_info.hpp" |
25 | | #include "parm_string.hpp" |
26 | | #include "posib_err.hpp" |
27 | | #include "stack_ptr.hpp" |
28 | | #include "string_enumeration.hpp" |
29 | | #include "string_list.hpp" |
30 | | #include "string_map.hpp" |
31 | | #include "strtonum.hpp" |
32 | | #include "file_util.hpp" |
33 | | |
34 | | #include <stdio.h> |
35 | | |
36 | | #ifdef HAVE_LIBDL |
37 | | # include <dlfcn.h> |
38 | | #endif |
39 | | |
40 | | namespace acommon |
41 | | { |
42 | | #include "static_filters.src.cpp" |
43 | | |
44 | | ////////////////////////////////////////////////////////////////////////// |
45 | | // |
46 | | // setup static filters |
47 | | // |
48 | | |
49 | | PosibErr<const ConfigModule *> get_dynamic_filter(Config * config, ParmStr value); |
50 | | extern void activate_filter_modes(Config *config); |
51 | | |
52 | | void setup_static_filters(Config * config) |
53 | 1.58k | { |
54 | 1.58k | config->set_filter_modules(filter_modules_begin, filter_modules_end); |
55 | 1.58k | activate_filter_modes(config); |
56 | 1.58k | #ifdef HAVE_LIBDL |
57 | 1.58k | config->load_filter_hook = get_dynamic_filter; |
58 | 1.58k | #endif |
59 | 1.58k | } |
60 | | |
61 | | ////////////////////////////////////////////////////////////////////////// |
62 | | // |
63 | | // |
64 | | // |
65 | | |
66 | | #ifdef HAVE_LIBDL |
67 | | |
68 | | struct ConfigFilterModule : public Cacheable { |
69 | | String name; |
70 | | String file; // path of shared object or dll |
71 | | String desc; // description of module |
72 | | Vector<KeyInfo> options; |
73 | | typedef Config CacheConfig; |
74 | | typedef String CacheKey; |
75 | | static PosibErr<ConfigFilterModule *> get_new(const String & key, const Config *); |
76 | 0 | bool cache_key_eq(const String & okey) const { |
77 | 0 | return name == okey; |
78 | 0 | } |
79 | 3.42k | ConfigFilterModule() : in_option(0) {} |
80 | | ~ConfigFilterModule(); |
81 | | bool in_option; |
82 | 0 | KeyInfo * new_option() { |
83 | 0 | options.push_back(KeyInfo()); |
84 | 0 | in_option = true; |
85 | 0 | return &options.back();} |
86 | | PosibErr<void> end_option(); |
87 | | }; |
88 | | |
89 | | static GlobalCache<ConfigFilterModule> filter_module_cache("filters"); |
90 | | |
91 | | ConfigFilterModule::~ConfigFilterModule() |
92 | 3.42k | { |
93 | 3.42k | for (Vector<KeyInfo>::iterator i = options.begin(); |
94 | 3.42k | i != options.end(); |
95 | 3.42k | ++i) |
96 | 0 | { |
97 | 0 | free(const_cast<char *>(i->name)); |
98 | 0 | free(const_cast<char *>(i->def)); |
99 | 0 | free(const_cast<char *>(i->desc)); |
100 | 0 | } |
101 | 3.42k | } |
102 | | |
103 | | #endif |
104 | | |
105 | | class IndividualFilter; |
106 | | |
107 | | // |
108 | | // actual code |
109 | | // |
110 | | |
111 | | FilterEntry * get_standard_filter(ParmStr); |
112 | | |
113 | | ////////////////////////////////////////////////////////////////////////// |
114 | | // |
115 | | // setup filter |
116 | | // |
117 | | |
118 | | PosibErr<void> setup_filter(Filter & filter, |
119 | | Config * config, |
120 | | bool use_decoder, bool use_filter, bool use_encoder) |
121 | 2.09k | { |
122 | 2.09k | StringList sl; |
123 | 2.09k | config->retrieve_list("filter", &sl); |
124 | 2.09k | StringListEnumeration els = sl.elements_obj(); |
125 | 2.09k | const char * filter_name; |
126 | 2.09k | String fun; |
127 | | |
128 | 2.09k | StackPtr<IndividualFilter> ifilter; |
129 | | |
130 | 2.09k | filter.clear(); |
131 | | |
132 | 4.20k | while ((filter_name = els.next()) != 0) { |
133 | | //fprintf(stderr, "Loading %s ... \n", filter_name); |
134 | 2.12k | FilterEntry * f = get_standard_filter(filter_name); |
135 | | // In case libdl is not available a filter is only available if made |
136 | | // one of the standard filters. This is done by statically linking |
137 | | // the filter sources. |
138 | | // On systems providing libdl or in case libtool mimics libdl |
139 | | // The following code parts assure that all filters needed and requested |
140 | | // by user are loaded properly or be reported to be missing. |
141 | | // |
142 | 2.12k | FilterHandle decoder_handle, filter_handle, encoder_handle; |
143 | 2.12k | #ifdef HAVE_LIBDL |
144 | 2.12k | FilterEntry dynamic_filter; |
145 | 2.12k | if (!f) { |
146 | | |
147 | 4 | RET_ON_ERR_SET(get_dynamic_filter(config, filter_name), |
148 | 4 | const ConfigModule *, module); |
149 | | |
150 | 0 | if (!(decoder_handle = dlopen(module->file,RTLD_NOW)) || |
151 | 0 | !(encoder_handle = dlopen(module->file,RTLD_NOW)) || |
152 | 0 | !(filter_handle = dlopen(module->file,RTLD_NOW))) |
153 | 0 | return make_err(cant_dlopen_file,dlerror()).with_file(filter_name); |
154 | | |
155 | 0 | fun = "new_aspell_"; |
156 | 0 | fun += filter_name; |
157 | 0 | fun += "_decoder"; |
158 | 0 | dynamic_filter.decoder = (FilterFun *)dlsym(decoder_handle.get(), fun.str()); |
159 | |
|
160 | 0 | fun = "new_aspell_"; |
161 | 0 | fun += filter_name; |
162 | 0 | fun += "_encoder"; |
163 | 0 | dynamic_filter.encoder = (FilterFun *)dlsym(encoder_handle.get(), fun.str()); |
164 | |
|
165 | 0 | fun = "new_aspell_"; |
166 | 0 | fun += filter_name; |
167 | 0 | fun += "_filter"; |
168 | 0 | dynamic_filter.filter = (FilterFun *)dlsym(filter_handle.get(), fun.str()); |
169 | |
|
170 | 0 | if (!dynamic_filter.decoder && |
171 | 0 | !dynamic_filter.encoder && |
172 | 0 | !dynamic_filter.filter) |
173 | 0 | return make_err(empty_filter,filter_name); |
174 | 0 | dynamic_filter.name = filter_name; |
175 | 0 | f = &dynamic_filter; |
176 | 0 | } |
177 | | #else |
178 | | if (!f) |
179 | | return make_err(no_such_filter, filter_name); |
180 | | #endif |
181 | 2.11k | if (use_decoder && f->decoder && (ifilter = f->decoder())) { |
182 | 159 | RET_ON_ERR_SET(ifilter->setup(config), bool, keep); |
183 | 159 | ifilter->handle = decoder_handle.release(); |
184 | 159 | if (!keep) { |
185 | 0 | ifilter.del(); |
186 | 159 | } else { |
187 | 159 | filter.add_filter(ifilter.release()); |
188 | 159 | } |
189 | 159 | } |
190 | 2.11k | if (use_filter && f->filter && (ifilter = f->filter())) { |
191 | 705 | RET_ON_ERR_SET(ifilter->setup(config), bool, keep); |
192 | 703 | ifilter->handle = filter_handle.release(); |
193 | 703 | if (!keep) { |
194 | 0 | ifilter.del(); |
195 | 703 | } else { |
196 | 703 | filter.add_filter(ifilter.release()); |
197 | 703 | } |
198 | 703 | } |
199 | 2.11k | if (use_encoder && f->encoder && (ifilter = f->encoder())) { |
200 | 0 | RET_ON_ERR_SET(ifilter->setup(config), bool, keep); |
201 | 0 | ifilter->handle = encoder_handle.release(); |
202 | 0 | if (!keep) { |
203 | 0 | ifilter.del(); |
204 | 0 | } else { |
205 | 0 | filter.add_filter(ifilter.release()); |
206 | 0 | } |
207 | 0 | } |
208 | 2.11k | } |
209 | 2.08k | return no_err; |
210 | 2.09k | } |
211 | | |
212 | | ////////////////////////////////////////////////////////////////////////// |
213 | | // |
214 | | // get filter |
215 | | // |
216 | | |
217 | 2.12k | FilterEntry * get_standard_filter(ParmStr filter_name) { |
218 | 2.12k | unsigned int i = 0; |
219 | 9.99k | while (i != standard_filters_size) { |
220 | 9.99k | if (standard_filters[i].name == filter_name) { |
221 | 2.11k | return (FilterEntry *) standard_filters + i; |
222 | 2.11k | } |
223 | 7.87k | ++i; |
224 | 7.87k | } |
225 | 4 | return 0; |
226 | 2.12k | } |
227 | | |
228 | | #ifdef HAVE_LIBDL |
229 | | |
230 | | PosibErr<const ConfigModule *> get_dynamic_filter(Config * config, ParmStr filter_name) |
231 | 3.42k | { |
232 | 3.42k | for (const ConfigModule * cur = config->filter_modules.pbegin(); |
233 | 34.2k | cur != config->filter_modules.pend(); |
234 | 30.7k | ++cur) |
235 | 30.7k | { |
236 | 30.7k | if (strcmp(cur->name,filter_name) == 0) return cur; |
237 | 30.7k | } |
238 | | |
239 | 3.42k | RET_ON_ERR_SET(get_cache_data(&filter_module_cache, config, filter_name), |
240 | 3.42k | ConfigFilterModule *, module); |
241 | | |
242 | 0 | ConfigModule m = { |
243 | 0 | module->name.str(), module->file.str(), module->desc.str(), |
244 | 0 | module->options.pbegin(), module->options.pend() |
245 | 0 | }; |
246 | |
|
247 | 0 | config->filter_modules_ptrs.push_back(module); |
248 | 0 | config->filter_modules.push_back(m); |
249 | |
|
250 | 0 | return &config->filter_modules.back(); |
251 | 3.42k | } |
252 | | |
253 | | PosibErr<ConfigFilterModule *> ConfigFilterModule::get_new(const String & filter_name, |
254 | | const Config * config) |
255 | 3.42k | { |
256 | 3.42k | StackPtr<ConfigFilterModule> module(new ConfigFilterModule); |
257 | 3.42k | module->name = filter_name; |
258 | 3.42k | KeyInfo * cur_opt = NULL; |
259 | | |
260 | 3.42k | String option_file = filter_name; |
261 | 3.42k | option_file += "-filter.info"; |
262 | 3.42k | if (!find_file(config, "filter-path", option_file)) |
263 | 3.42k | return make_err(no_such_filter, filter_name); |
264 | | |
265 | 0 | FStream options; |
266 | 0 | RET_ON_ERR(options.open(option_file,"r")); |
267 | | |
268 | 0 | String buf; DataPair d; |
269 | 0 | while (getdata_pair(options,d,buf)) |
270 | 0 | { |
271 | 0 | to_lower(d.key); |
272 | | |
273 | | // |
274 | | // key == aspell |
275 | | // |
276 | 0 | if (d.key == "aspell") |
277 | 0 | { |
278 | 0 | if ( d.value == NULL || *d.value == '\0' ) |
279 | 0 | return make_err(confusing_version).with_file(option_file,d.line_num); |
280 | | #ifdef FILTER_VERSION_CONTROL |
281 | | PosibErr<void> peb = check_version(d.value.str); |
282 | | if (peb.has_err()) return peb.with_file(option_file,d.line_num); |
283 | | #endif |
284 | 0 | continue; |
285 | 0 | } |
286 | | |
287 | | // |
288 | | // key == option |
289 | | // |
290 | 0 | if (d.key == "option" ) { |
291 | |
|
292 | 0 | RET_ON_ERR(module->end_option()); |
293 | | |
294 | 0 | to_lower(d.value.str); |
295 | |
|
296 | 0 | cur_opt = module->new_option(); |
297 | | |
298 | 0 | char * s = (char *)malloc(2 + filter_name.size() + 1 + d.value.size + 1); |
299 | 0 | cur_opt->name = s; |
300 | 0 | memcpy(s, "f-", 2); |
301 | 0 | s+= 2; |
302 | 0 | memcpy(s, filter_name.str(), filter_name.size()); |
303 | 0 | s += filter_name.size(); |
304 | 0 | *s++ = '-'; |
305 | 0 | memcpy(s, d.value.str, d.value.size); |
306 | 0 | s += d.value.size; |
307 | 0 | *s = '\0'; |
308 | 0 | for (Vector<KeyInfo>::iterator cur = module->options.begin(); |
309 | 0 | cur != module->options.end() - 1; // avoid checking the one just inserted |
310 | 0 | ++cur) { |
311 | 0 | if (strcmp(cur_opt->name,cur->name) == 0) |
312 | 0 | return make_err(identical_option).with_file(option_file,d.line_num); |
313 | 0 | } |
314 | | |
315 | 0 | continue; |
316 | 0 | } |
317 | | |
318 | | // |
319 | | // key == static |
320 | | // |
321 | 0 | if (d.key == "static") { |
322 | 0 | RET_ON_ERR(module->end_option()); |
323 | 0 | continue; |
324 | 0 | } |
325 | | |
326 | | // |
327 | | // key == description |
328 | | // |
329 | 0 | if ((d.key == "desc") || |
330 | 0 | (d.key == "description")) { |
331 | |
|
332 | 0 | unescape(d.value); |
333 | | |
334 | | // |
335 | | // filter description |
336 | | // |
337 | 0 | if (!module->in_option) { |
338 | 0 | module->desc = d.value; |
339 | 0 | } |
340 | | // |
341 | | //option description |
342 | | // |
343 | 0 | else { |
344 | | //avoid memory leak; |
345 | 0 | if (cur_opt->desc) free((char *)cur_opt->desc); |
346 | 0 | cur_opt->desc = strdup(d.value.str); |
347 | 0 | } |
348 | 0 | continue; |
349 | 0 | } |
350 | | |
351 | | // |
352 | | // key == lib-file |
353 | | // |
354 | 0 | if (d.key == "lib-file") |
355 | 0 | { |
356 | 0 | module->file = d.value; |
357 | 0 | continue; |
358 | 0 | } |
359 | | |
360 | | // |
361 | | // !active_option |
362 | | // |
363 | 0 | if (!module->in_option) { |
364 | 0 | return make_err(options_only).with_file(option_file,d.line_num); |
365 | 0 | } |
366 | | |
367 | | // |
368 | | // key == type |
369 | | // |
370 | 0 | if (d.key == "type") { |
371 | 0 | to_lower(d.value); // This is safe since normally option_value is used |
372 | 0 | if (d.value == "list") |
373 | 0 | cur_opt->type = KeyInfoList; |
374 | 0 | else if (d.value == "int" || d.value == "integer") |
375 | 0 | cur_opt->type = KeyInfoInt; |
376 | 0 | else if (d.value == "string") |
377 | 0 | cur_opt->type = KeyInfoString; |
378 | | //FIXME why not force user to omit type specifier or explicitly say bool ??? |
379 | 0 | else |
380 | 0 | cur_opt->type = KeyInfoBool; |
381 | 0 | continue; |
382 | 0 | } |
383 | | |
384 | | // |
385 | | // key == default |
386 | | // |
387 | 0 | if (d.key == "def" || d.key == "default") { |
388 | | |
389 | 0 | if (cur_opt->type == KeyInfoList) { |
390 | |
|
391 | 0 | int new_len = 0; |
392 | 0 | int orig_len = 0; |
393 | 0 | if (cur_opt->def) { |
394 | 0 | orig_len = strlen(cur_opt->def); |
395 | 0 | new_len += orig_len + 1; |
396 | 0 | } |
397 | 0 | for (const char * s = d.value.str; *s; ++s) { |
398 | 0 | if (*s == ':') ++new_len; |
399 | 0 | ++new_len; |
400 | 0 | } |
401 | 0 | new_len += 1; |
402 | 0 | char * x = (char *)realloc((char *)cur_opt->def, new_len); |
403 | 0 | cur_opt->def = x; |
404 | 0 | if (orig_len > 0) { |
405 | 0 | x += orig_len; |
406 | 0 | *x++ = ':'; |
407 | 0 | } |
408 | 0 | for (const char * s = d.value.str; *s; ++s) { |
409 | 0 | if (*s == ':') *x++ = ':'; |
410 | 0 | *x++ = *s; |
411 | 0 | } |
412 | 0 | *x = '\0'; |
413 | |
|
414 | 0 | } else { |
415 | | |
416 | | // FIXME |
417 | | //may try some syntax checking |
418 | | //if ( cur_opt->type == KeyInfoBool ) { |
419 | | // check for valid bool values true false 0 1 on off ... |
420 | | // and issue error if wrong or assume false ?? |
421 | | //} |
422 | | //if ( cur_opt->type == KeyInfoInt ) { |
423 | | // check for valid integer or double and issue error if not |
424 | | //} |
425 | 0 | unescape(d.value); |
426 | 0 | cur_opt->def = strdup(d.value.str); |
427 | 0 | } |
428 | 0 | continue; |
429 | 0 | } |
430 | | |
431 | | // |
432 | | // key == flags |
433 | | // |
434 | 0 | if (d.key == "flags") { |
435 | 0 | if (d.value == "utf-8" || d.value == "UTF-8") |
436 | 0 | cur_opt->flags = KEYINFO_UTF8; |
437 | 0 | continue; |
438 | 0 | } |
439 | | |
440 | | // |
441 | | // key == endoption |
442 | | // |
443 | 0 | if (d.key=="endoption") { |
444 | 0 | RET_ON_ERR(module->end_option()); |
445 | 0 | continue; |
446 | 0 | } |
447 | | |
448 | | // |
449 | | // error |
450 | | // |
451 | 0 | return make_err(invalid_option_modifier).with_file(option_file,d.line_num); |
452 | | |
453 | 0 | } // end while getdata_pair_c |
454 | 0 | RET_ON_ERR(module->end_option()); |
455 | 0 | const char * slash = strrchr(option_file.str(), '/'); |
456 | 0 | assert(slash); |
457 | 0 | if (module->file.empty()) { |
458 | 0 | module->file.assign(option_file.str(), slash + 1 - option_file.str()); |
459 | | //module->file += "lib"; |
460 | 0 | module->file += filter_name; |
461 | 0 | module->file += "-filter.so"; |
462 | 0 | } else { |
463 | 0 | if (module->file[0] != '/') |
464 | 0 | module->file.insert(0, option_file.str(), slash + 1 - option_file.str()); |
465 | 0 | module->file += ".so"; |
466 | 0 | } |
467 | |
|
468 | 0 | return module.release(); |
469 | 0 | } |
470 | | |
471 | | PosibErr<void> ConfigFilterModule::end_option() |
472 | 0 | { |
473 | 0 | if (in_option) |
474 | 0 | { |
475 | | // FIXME: Check to make sure there is a name and desc. |
476 | 0 | KeyInfo * cur_opt = &options.back(); |
477 | 0 | if (!cur_opt->def) cur_opt->def = strdup(""); |
478 | 0 | } |
479 | 0 | in_option = false; |
480 | 0 | return no_err; |
481 | 0 | } |
482 | | |
483 | | #endif |
484 | | |
485 | 0 | void load_all_filters(Config * config) { |
486 | 0 | #ifdef HAVE_LIBDL |
487 | 0 | StringList filter_path; |
488 | 0 | String toload; |
489 | | |
490 | 0 | config->retrieve_list("filter-path", &filter_path); |
491 | 0 | PathBrowser els(filter_path, "-filter.info"); |
492 | | |
493 | 0 | const char * file; |
494 | 0 | while ((file = els.next()) != NULL) { |
495 | | |
496 | 0 | const char * name = strrchr(file, '/'); |
497 | 0 | if (!name) name = file; |
498 | 0 | else name++; |
499 | 0 | unsigned len = strlen(name) - 12; |
500 | | |
501 | 0 | toload.assign(name, len); |
502 | | |
503 | 0 | get_dynamic_filter(config, toload); |
504 | 0 | } |
505 | 0 | #endif |
506 | 0 | } |
507 | | |
508 | | |
509 | | class FiltersEnumeration : public StringPairEnumeration |
510 | | { |
511 | | public: |
512 | | typedef Vector<ConfigModule>::const_iterator Itr; |
513 | | private: |
514 | | Itr it; |
515 | | Itr end; |
516 | | public: |
517 | 0 | FiltersEnumeration(Itr i, Itr e) : it(i), end(e) {} |
518 | 0 | bool at_end() const {return it == end;} |
519 | | StringPair next() |
520 | 0 | { |
521 | 0 | if (it == end) return StringPair(); |
522 | 0 | StringPair res = StringPair(it->name, it->desc); |
523 | 0 | ++it; |
524 | 0 | return res; |
525 | 0 | } |
526 | 0 | StringPairEnumeration * clone() const {return new FiltersEnumeration(*this);} |
527 | | void assign(const StringPairEnumeration * other0) |
528 | 0 | { |
529 | 0 | const FiltersEnumeration * other = (const FiltersEnumeration *)other0; |
530 | 0 | *this = *other; |
531 | 0 | } |
532 | | }; |
533 | | |
534 | | PosibErr<StringPairEnumeration *> available_filters(Config * config) |
535 | 0 | { |
536 | 0 | return new FiltersEnumeration(config->filter_modules.begin(), |
537 | 0 | config->filter_modules.end()); |
538 | 0 | } |
539 | | |
540 | | } |