Coverage Report

Created: 2023-12-08 06:59

/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
}