1 /*
2  * lftp - file transfer program
3  *
4  * Copyright (c) 1996-2017 by Alexander V. Lukyanov (lav@yars.free.net)
5  *
6  * This program is free software; you can redistribute it and/or modify
7  * it under the terms of the GNU General Public License as published by
8  * the Free Software Foundation; either version 3 of the License, or
9  * (at your option) any later version.
10  *
11  * This program is distributed in the hope that it will be useful,
12  * but WITHOUT ANY WARRANTY; without even the implied warranty of
13  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
14  * GNU General Public License for more details.
15  *
16  * You should have received a copy of the GNU General Public License
17  * along with this program.  If not, see <http://www.gnu.org/licenses/>.
18  */
19 
20 #include <config.h>
21 
22 #include <fnmatch.h>
23 #include <ctype.h>
24 #include <unistd.h>
25 #include <stdlib.h>
26 #include <cmath>
27 #include <sys/types.h>
28 #include <sys/stat.h>
29 #include <errno.h>
30 #include <limits.h>
31 CDECL_BEGIN
32 #include "regex.h"
33 CDECL_END
34 #include "ResMgr.h"
35 #include "SMTask.h"
36 #include "misc.h"
37 #include "StringSet.h"
38 #include "log.h"
39 
40 xlist_head<Resource> Resource::all_list;
41 xmap<ResType*> *ResType::types_by_name;
42 
VarNameCmp(const char * good_name,const char * name)43 int ResType::VarNameCmp(const char *good_name,const char *name)
44 {
45    int res=EXACT_PREFIX+EXACT_NAME;
46    const char *colon=strchr(good_name,':');
47    if(colon && !strchr(name,':'))
48    {
49       good_name=colon+1;
50       res|=SUBSTR_PREFIX;
51    }
52    while(*good_name || *name)
53    {
54       if(*good_name==*name
55       || (*good_name && *name && strchr("-_",*good_name) && strchr("-_",*name)))
56       {
57 	 good_name++;
58 	 name++;
59 	 continue;
60       }
61       if(*name && !*good_name)
62 	 return DIFFERENT;
63       if((!*name && *good_name)
64       || (strchr("-_:",*name) && !strchr("-_:",*good_name)))
65       {
66 	 good_name++;
67 	 if(strchr(name,':'))
68 	    res|=SUBSTR_PREFIX;
69 	 else
70 	    res|=SUBSTR_NAME;
71 	 continue;
72       }
73       return DIFFERENT;
74    }
75    return res;
76 }
77 
IsAlias() const78 bool ResType::IsAlias() const
79 {
80    return closure_valid==ResMgr::AliasValidate;
81 }
82 
FindVar(const char * name,const ResType ** type,const char ** re_closure)83 const char *ResType::FindVar(const char *name,const ResType **type,const char **re_closure)
84 {
85    const ResType *exact_proto=0;
86    const ResType *exact_name=0;
87    int sub=0;
88 
89    *type=types_by_name->lookup(name);
90    if(*type)
91       goto found; // exact match
92 
93    for(const ResType *type_scan=types_by_name->each_begin(); type_scan; type_scan=types_by_name->each_next())
94    {
95       switch(VarNameCmp(type_scan->name,name))
96       {
97       case EXACT_PREFIX+EXACT_NAME:
98 	 *type=type_scan;
99 	 return 0;
100       case EXACT_PREFIX+SUBSTR_NAME:
101 	 if(!exact_proto && !exact_name)
102 	    sub=0;
103 	 exact_proto=*type=type_scan;
104 	 sub++;
105 	 break;
106       case SUBSTR_PREFIX+EXACT_NAME:
107 	 if(!exact_proto && !exact_name)
108 	    sub=0;
109 	 exact_name=*type=type_scan;
110 	 sub++;
111 	 break;
112       case SUBSTR_PREFIX+SUBSTR_NAME:
113 	 if(exact_proto || exact_name)
114 	    break;
115 	 sub++;
116 	 *type=type_scan;
117 	 break;
118       default:
119 	 break;
120       }
121    }
122    if(!*type && sub==0)
123       return _("no such variable");
124    if(sub==1)
125       goto found;
126    *type=0;
127    return _("ambiguous variable name");
128 
129 found:
130    if((*type)->IsAlias()) {
131       const char *alias_c=(*type)->GetAliasTarget();
132       char *alias=alloca_strdup(alias_c);
133       char *slash=strchr(alias,'/');
134       if(slash) {
135 	 *slash=0;
136 	 if(re_closure)
137 	    *re_closure=alias_c+(slash+1-alias);
138       }
139       *type=types_by_name->lookup(alias);
140       if(!*type)
141 	 return "invalid compatibility alias";
142    }
143    return 0;
144 }
145 
FindRes(const char * name)146 const ResType *ResType::FindRes(const char *name)
147 {
148    const ResType *type;
149    const char *msg=FindVar(name,&type);
150    if(msg)
151       return 0;
152    return type;
153 }
154 
Set(const char * name,const char * cclosure,const char * cvalue,bool def)155 const char *ResType::Set(const char *name,const char *cclosure,const char *cvalue,bool def)
156 {
157    ResType *type;
158    // find type of given variable
159    const char *msg=FindVar(name,&type,&cclosure);
160    if(msg)
161       return msg;
162 
163    return type->Set(cclosure,cvalue,def);
164 }
165 
Set(const char * cclosure,const char * cvalue,bool def)166 const char *ResType::Set(const char *cclosure,const char *cvalue,bool def)
167 {
168    const char *msg=0;
169 
170    xstring_c value(cvalue);
171    if(value && val_valid && (msg=val_valid(&value))!=0)
172       return msg;
173 
174    xstring_c closure(cclosure);
175    if((closure || closure_valid==ResMgr::HasClosure)
176    && closure_valid && (msg=closure_valid(&closure))!=0)
177       return msg;
178 
179    bool need_reconfig=false;
180 
181    xlist_for_each(Resource,*(type_value_list),node,scan)
182    {
183       // find the old value
184       if(closure==scan->closure || !xstrcmp(scan->closure,closure))
185       {
186 	 if(def)
187 	    return 0;
188 	 need_reconfig=true;
189 	 delete scan;
190 	 break;
191       }
192    }
193    if(value)
194    {
195       (void)new Resource(this,closure,value,def);
196       need_reconfig=true;
197    }
198    if(need_reconfig)
199       ResClient::ReconfigAll(name);
200    return 0;
201 }
202 
ResourceCompare(const Resource * ar,const Resource * br)203 int ResMgr::ResourceCompare(const Resource *ar,const Resource *br)
204 {
205    int diff=strcmp(ar->type->name,br->type->name);
206    if(diff)
207       return diff;
208    if(ar->closure==br->closure)
209       return 0;
210    if(ar->closure==0)
211       return -1;
212    if(br->closure==0)
213       return 1;
214    return strcmp(ar->closure,br->closure);
215 }
216 
Format(xstring & buf) const217 void Resource::Format(xstring& buf) const
218 {
219    buf.appendf("set %s",type->name);
220    const char *s=closure;
221    if(s)
222    {
223       buf.append('/');
224       bool par=false;
225       if(strcspn(s," \t>|;&")!=strlen(s))
226 	 par=true;
227       if(par)
228 	 buf.append('"');
229       while(*s)
230       {
231 	 if(strchr("\"\\",*s))
232 	    buf.append('\\');
233 	 buf.append(*s++);
234       }
235       if(par)
236 	 buf.append('"');
237    }
238    buf.append(' ');
239    s=value;
240 
241    bool par=false;
242    if(*s==0 || strcspn(s," \t>|;&")!=strlen(s))
243       par=true;
244    if(par)
245       buf.append('"');
246    while(*s)
247    {
248       if(strchr("\"\\",*s))
249 	 buf.append('\\');
250       buf.append(*s++);
251    }
252    if(par)
253       buf.append('"');
254    buf.append('\n');
255 }
256 
PResourceCompare(const Resource * const * a,const Resource * const * b)257 static int PResourceCompare(const Resource *const*a,const Resource *const*b)
258 {
259    return ResMgr::ResourceCompare(*a,*b);
260 }
RefResourceCompare(const Ref<Resource> * a,const Ref<Resource> * b)261 static int RefResourceCompare(const Ref<Resource> *a,const Ref<Resource> *b)
262 {
263    return ResMgr::ResourceCompare(*a,*b);
264 }
265 
Format(bool with_defaults,bool only_defaults)266 char *ResType::Format(bool with_defaults,bool only_defaults)
267 {
268    RefArray<Resource> created;
269    if(with_defaults || only_defaults)
270    {
271       for(ResType *dscan=types_by_name->each_begin(); dscan; dscan=types_by_name->each_next())
272 	 if((only_defaults || dscan->SimpleQuery(0)==0) && !dscan->IsAlias())
273 	    created.append(new Resource(dscan,
274 	       0,xstrdup(dscan->defvalue?dscan->defvalue:"(nil)")));
275    }
276 
277    xstring buf("");
278 
279    if(!only_defaults)
280    {
281       // just created Resources are also in all_list.
282       xarray<const Resource*> arr;
283       xlist_for_each(Resource,Resource::all_list,node,scan) {
284 	 if(!scan->def || with_defaults)
285 	    arr.append(scan);
286       }
287       arr.qsort(PResourceCompare);
288       for(int i=0; i<arr.count(); i++)
289 	 arr[i]->Format(buf);
290    }
291    else
292    {
293       created.qsort(RefResourceCompare);
294       for(int i=0; i<created.count(); i++)
295 	 created[i]->Format(buf);
296    }
297 
298    return buf.borrow();
299 }
300 
Generator(void)301 char **ResType::Generator(void)
302 {
303    StringSet res;
304 
305    for(ResType *dscan=types_by_name->each_begin(); dscan; dscan=types_by_name->each_next())
306       if(!dscan->IsAlias())
307 	 res.Append(dscan->name);
308 
309    res.qsort();
310    return res.borrow();
311 }
312 
BoolValidate(xstring_c * value)313 const char *ResMgr::BoolValidate(xstring_c *value)
314 {
315    const char *v=*value;
316    const char *newval=0;
317 
318    switch(v[0])
319    {
320    case 't':   newval="true";	 break;
321    case 'T':   newval="True";	 break;
322    case 'f':   newval="false";	 break;
323    case 'F':   newval="False";	 break;
324    case 'y':   newval="yes";	 break;
325    case 'Y':   newval="Yes";	 break;
326    case 'n':   newval="no";	 break;
327    case 'N':   newval="No";	 break;
328    case '1':   newval="1";	 break;
329    case '0':   newval="0";	 break;
330    case '+':   newval="+";	 break;
331    case '-':   newval="-";	 break;
332    case 'o':   newval=(v[1]=='f' || v[1]=='F')?"off":"on";  break;
333    case 'O':   newval=(v[1]=='f' || v[1]=='F')?"Off":"On";  break;
334    default:
335       return _("invalid boolean value");
336    }
337    if(strcmp(v,newval))
338       value->set(newval);
339 
340    return 0;
341 }
342 
TriBoolValidate(xstring_c * value)343 const char *ResMgr::TriBoolValidate(xstring_c *value)
344 {
345    if(!BoolValidate(value))
346       return 0;
347 
348    /* not bool */
349    const char *v=*value;
350    const char *newval=0;
351 
352    switch(v[0])
353    {
354    case 'a':   newval="auto";	 break;
355    case 'A':   newval="Auto";	 break;
356    default:
357       return _("invalid boolean/auto value");
358    }
359 
360    if(strcmp(v,newval))
361       value->set(newval);
362 
363    return 0;
364 }
365 
366 static const char power_letter[] =
367 {
368   0,	/* not used */
369   'K',	/* kibi ('k' for kilo is a special case) */
370   'M',	/* mega or mebi */
371   'G',	/* giga or gibi */
372   'T',	/* tera or tebi */
373   'P',	/* peta or pebi */
374   'E',	/* exa or exbi */
375   'Z',	/* zetta or 2**70 */
376   'Y'	/* yotta or 2**80 */
377 };
get_power_multiplier(char p)378 static unsigned long long get_power_multiplier(char p)
379 {
380    const char *scan=power_letter;
381    const int scale=1024;
382    unsigned long long mul=1;
383    p=toupper(p);
384    while(scan<power_letter+sizeof(power_letter)) {
385       if(p==*scan)
386 	 return mul;
387       mul*=scale;
388       scan++;
389    }
390    return 0;
391 }
392 
NumberValidate(xstring_c * value)393 const char *ResMgr::NumberValidate(xstring_c *value)
394 {
395    const char *v=*value;
396    const char *end=v;
397 
398    (void)strtoll(v,const_cast<char**>(&end),0);
399    unsigned long long m=get_power_multiplier(*end);
400 
401    if(v==end || m==0 || end[m>1])
402       return _("invalid number");
403 
404    return 0;
405 }
FloatValidate(xstring_c * value)406 const char *ResMgr::FloatValidate(xstring_c *value)
407 {
408    const char *v=*value;
409    const char *end=v;
410 
411    (void)strtod(v,const_cast<char**>(&end));
412    unsigned long long m=get_power_multiplier(*end);
413 
414    if(v==end || m==0 || end[m>1])
415       return _("invalid floating point number");
416 
417    return 0;
418 }
UNumberValidate(xstring_c * value)419 const char *ResMgr::UNumberValidate(xstring_c *value)
420 {
421    const char *v=*value;
422    const char *end=v;
423 
424    (void)strtoull(v,const_cast<char**>(&end),0);
425    unsigned long long m=get_power_multiplier(*end);
426 
427    if(!isdigit((unsigned char)v[0])
428    || v==end || m==0 || end[m>1])
429       return _("invalid unsigned number");
430 
431    return 0;
432 }
AliasValidate(xstring_c *)433 const char *ResMgr::AliasValidate(xstring_c *)
434 {
435    return "";
436 }
437 
to_unumber(unsigned long long max) const438 unsigned long long ResValue::to_unumber(unsigned long long max) const
439 {
440    if (is_nil())
441       return 0;
442    const char *end=s;
443    unsigned long long v=strtoull(s,const_cast<char**>(&end),0);
444    unsigned long long m=get_power_multiplier(*end);
445    unsigned long long vm=v*m;
446    if(vm/m!=v || vm>max)
447       return max;
448    return vm;
449 }
to_number(long long min,long long max) const450 long long ResValue::to_number(long long min,long long max) const
451 {
452    if (is_nil())
453       return 0;
454    const char *end=s;
455    long long v=strtoll(s,const_cast<char**>(&end),0);
456    long long m=get_power_multiplier(*end);
457    long long vm=v*m;
458    if(vm/m!=v)
459       return v>0?max:min;
460    if(vm>max)
461       return max;
462    if(vm<min)
463       return min;
464    return vm;
465 }
operator int() const466 ResValue::operator int() const
467 {
468    return to_number(INT_MIN,INT_MAX);
469 }
operator long() const470 ResValue::operator long() const
471 {
472    return to_number(LONG_MIN,LONG_MAX);
473 }
operator unsigned() const474 ResValue::operator unsigned() const
475 {
476    return to_unumber(UINT_MAX);
477 }
operator unsigned long() const478 ResValue::operator unsigned long() const
479 {
480    return to_unumber(ULONG_MAX);
481 }
to_tri_bool(bool a) const482 bool ResValue::to_tri_bool(bool a) const
483 {
484    if(*s=='a' || *s=='A')
485       return a;
486    return to_bool();
487 }
488 
Resource(ResType * type,const char * closure,const char * value,bool def)489 Resource::Resource(ResType *type,const char *closure,const char *value,bool def)
490    : type(type), value(value), closure(closure), def(def), all_node(this), type_value_node(this)
491 {
492    all_list.add_tail(all_node);
493    type->type_value_list->add_tail(type_value_node);
494 }
~Resource()495 Resource::~Resource()
496 {
497    all_node.remove();
498    type_value_node.remove();
499 }
500 
ClosureMatch(const char * cl_data)501 bool Resource::ClosureMatch(const char *cl_data)
502 {
503    if(!closure && !cl_data)
504       return true;
505    if(!(closure && cl_data))
506       return false;
507    // a special case for domain name match (i.e. example.org matches *.example.org)
508    if(closure[0]=='*' && closure[1]=='.' && !strcmp(closure+2,cl_data))
509       return true;
510    if(0==fnmatch(closure,cl_data,FNM_PATHNAME))
511       return true;
512    // try to match basename; helps matching torrent metadata url to *.torrent
513    const char *bn=basename_ptr(cl_data);
514    if(bn!=cl_data && 0==fnmatch(closure,bn,FNM_PATHNAME))
515       return true;
516    return false;
517 }
518 
QueryNext(const char * name,const char ** closure,Resource ** ptr)519 const char *ResMgr::QueryNext(const char *name,const char **closure,Resource **ptr)
520 {
521    xlist<Resource> *node=0;
522    if(*ptr==0)
523    {
524       const ResType *type=FindRes(name);
525       if(!type) {
526 	 *ptr=0;
527 	 *closure=0;
528 	 return 0;
529       }
530       node=type->type_value_list->get_next();
531    }
532    else
533    {
534       node=(*ptr)->type_value_node.get_next();
535    }
536    *ptr=node->get_obj();
537    if(*ptr) {
538       *closure=(*ptr)->closure;
539       return (*ptr)->value;
540    }
541    *closure=0;
542    return 0;
543 }
544 
SimpleQuery(const char * closure) const545 const char *ResType::SimpleQuery(const char *closure) const
546 {
547    // find the value
548    xlist_for_each(Resource,*(type_value_list),node,scan)
549       if(scan->ClosureMatch(closure))
550 	 return scan->value;
551    return 0;
552 }
553 
Query(const char * name,const char * closure)554 ResValue ResMgr::Query(const char *name,const char *closure)
555 {
556    const char *msg;
557 
558    const ResType *type;
559    // find type of given variable
560    msg=FindVar(name,&type);
561    if(msg)
562    {
563       // debug only
564       // fprintf(stderr,_("Query of variable `%s' failed: %s\n"),name,msg);
565       return 0;
566    }
567 
568    return type->Query(closure);
569 }
570 
Query(const char * closure) const571 ResValue ResType::Query(const char *closure) const
572 {
573    const char *v=0;
574 
575    if(closure)
576       v=SimpleQuery(closure);
577    if(!v)
578       v=SimpleQuery(0);
579    if(!v)
580       v=defvalue;
581 
582    return v;
583 }
584 
str2bool(const char * s)585 bool ResMgr::str2bool(const char *s)
586 {
587    return(strchr("TtYy1+",s[0])!=0 || !strcasecmp(s,"on"));
588 }
589 
ResDecl(const char * a_name,const char * a_defvalue,ResValValid * a_val_valid,ResClValid * a_closure_valid)590 ResDecl::ResDecl(const char *a_name,const char *a_defvalue,ResValValid *a_val_valid,ResClValid *a_closure_valid)
591 {
592    name=a_name;
593    defvalue=a_defvalue;
594    val_valid=a_val_valid;
595    closure_valid=a_closure_valid;
596    Register();
597 }
ResDecls(ResType * array)598 ResDecls::ResDecls(ResType *array)
599 {
600    while(array->name)
601       array++->Register();
602 }
ResDecls(ResType * r1,ResType * r2,...)603 ResDecls::ResDecls(ResType *r1,ResType *r2,...)
604 {
605    r.append(r1);
606    r1->Register();
607    if(!r2)
608       return;
609    r.append(r2);
610    r2->Register();
611    va_list v;
612    va_start(v,r2);
613    while((r1=va_arg(v,ResType *))!=0)
614    {
615       r1->Register();
616       r.append(r1);
617    }
618    va_end(v);
619 }
~ResDecls()620 ResDecls::~ResDecls()
621 {
622    for(int i=0; i<r.count(); i++)
623       r[i]->Unregister();
624 }
625 
Register()626 void ResType::Register()
627 {
628    if(!types_by_name)
629       types_by_name=new xmap<ResType*>;
630    types_by_name->add(name,this);
631    if(!type_value_list)
632       type_value_list=new xlist_head<Resource>();
633 }
Unregister()634 void ResType::Unregister()
635 {
636    if(types_by_name)
637       types_by_name->remove(name);
638    if(type_value_list) {
639       // remove all resources of this type
640       xlist_for_each_safe(Resource,*type_value_list,node,scan,next)
641 	 delete scan;
642       delete type_value_list;
643       type_value_list=0;
644    }
645 }
646 
init(const char * s)647 void TimeIntervalR::init(const char *s)
648 {
649    double interval=0;
650    infty=false;
651    error_text=0;
652 
653    if(!strncasecmp(s,"inf",3)
654    || !strcasecmp(s,"forever")
655    || !strcasecmp(s,"never"))
656    {
657       infty=true;
658       return;
659    }
660    int pos=0;
661    for(;;)
662    {
663       double prec;
664       char ch='s';
665       int pos1=strlen(s+pos);
666       int n=sscanf(s+pos,"%lf%c%n",&prec,&ch,&pos1);
667       if(n<1)
668 	 break;
669       ch=tolower((unsigned char)ch);
670       if(ch=='m')
671 	 prec*=MINUTE;
672       else if(ch=='h')
673 	 prec*=HOUR;
674       else if(ch=='d')
675 	 prec*=DAY;
676       else if(ch!='s')
677       {
678 	 error_text=_("Invalid time unit letter, only [smhd] are allowed.");
679 	 return;
680       }
681       interval+=prec;
682       pos+=pos1;
683    }
684    if(pos==0)
685    {
686       error_text=_("Invalid time format. Format is <time><unit>, e.g. 2h30m.");
687       return;
688    }
689    TimeDiff::Set(interval);
690 }
691 
TimeIntervalValidate(xstring_c * s)692 const char *ResMgr::TimeIntervalValidate(xstring_c *s)
693 {
694    TimeIntervalR t(*s);
695    if(t.Error())
696       return t.ErrorText();
697    return 0;
698 }
699 
init(char sep1,const char * s)700 void NumberPair::init(char sep1,const char *s)
701 {
702    sep=sep1;
703    Set(s);
704 }
parse1(const char * s)705 long long NumberPair::parse1(const char *s)
706 {
707    if(!s || !*s)
708       return 0;
709    const char *end=s;
710    long long v=strtoll(s,const_cast<char**>(&end),0);
711    long long m=get_power_multiplier(*end);
712    if(s==end || m==0 || end[m>1]) {
713       error_text=_("invalid number");
714       return 0;
715    }
716    long long vm=v*m;
717    if(vm/m!=v) {
718       error_text=_("integer overflow");
719       return 0;
720    }
721    return vm;
722 }
Set(const char * s0)723 void NumberPair::Set(const char *s0)
724 {
725    n1=n2=0;
726    no_n1=no_n2=true;
727    error_text=0;
728 
729    if(!s0)
730       return;
731 
732    char *s1=alloca_strdup(s0);
733    char *s2=s1;
734    while(*s2 && *s2!=sep && *s2!=':')
735       s2++;
736    if(*s2)
737       *s2++=0;
738    else
739       s2=0;
740 
741    n1=parse1(s1);
742    no_n1=!*s1;
743    n2=(s2?parse1(s2):n1);
744    no_n2=(s2 && !*s2);
745 
746    if(!error_text && Log::global) {
747       Log::global->Format(10,"%s translated to pair %lld%c%lld (%d,%d)\n",
748 	 s0,n1,sep,n2,no_n1,no_n2);
749    }
750 }
751 
Range(const char * s)752 Range::Range(const char *s) : NumberPair('-')
753 {
754    if(!strcasecmp(s,"full") || !strcasecmp(s,"any"))
755       return;
756    Set(s);
757 }
758 
Random()759 long long Range::Random()
760 {
761    random_init();
762 
763    if(no_n1 && no_n2)
764       return random();
765    if(no_n2)
766       return n1+random();
767 
768    return n1 + (long long)((n2-n1+1)*random01());
769 }
770 
RangeValidate(xstring_c * s)771 const char *ResMgr::RangeValidate(xstring_c *s)
772 {
773    Range r(*s);
774    if(r.Error())
775       return r.ErrorText();
776    char *colon=strchr(s->get_non_const(),':');
777    if(colon)
778       *colon='-';
779    return 0;
780 }
781 
ERegExpValidate(xstring_c * s)782 const char *ResMgr::ERegExpValidate(xstring_c *s)
783 {
784    if(**s==0)
785       return 0;
786    regex_t re;
787    int err=regcomp(&re,*s,REG_EXTENDED|REG_NOSUB);
788    if(err)
789    {
790       const int max_err_len=128;
791       char *err_msg=xstring::tmp_buf(max_err_len);
792       regerror(err,0,err_msg,max_err_len);
793       return err_msg;
794    }
795    regfree(&re);
796    return 0;
797 }
798 
IPv4AddrValidate(xstring_c * value)799 const char *ResMgr::IPv4AddrValidate(xstring_c *value)
800 {
801    if(!**value)
802       return 0;
803    if(!is_ipv4_address(*value))
804       return _("Invalid IPv4 numeric address");
805    return 0;
806 }
807 
808 #if INET6
IPv6AddrValidate(xstring_c * value)809 const char *ResMgr::IPv6AddrValidate(xstring_c *value)
810 {
811    if(!**value)
812       return 0;
813    if(!is_ipv6_address(*value))
814       return _("Invalid IPv6 numeric address");
815    return 0;
816 }
817 #endif
818 
FileAccessible(xstring_c * value,int mode,bool want_dir)819 const char *ResMgr::FileAccessible(xstring_c *value,int mode,bool want_dir)
820 {
821    if(!**value)
822       return 0;
823    const char *f=expand_home_relative(*value);
824    xstring_c cwd;
825    const char *error=0;
826    if(f[0]!='/')
827    {
828       xgetcwd_to(cwd);
829       if(cwd)
830 	 f=dir_file(cwd,f);
831    }
832    struct stat st;
833    if(stat(f,&st)<0)
834       error=strerror(errno);
835    else if(want_dir ^ S_ISDIR(st.st_mode))
836       error=strerror(errno=want_dir?ENOTDIR:EISDIR);
837    else if(access(f,mode)<0)
838       error=strerror(errno);
839    else
840       value->set(f);
841    return error;
842 }
FileReadable(xstring_c * value)843 const char *ResMgr::FileReadable(xstring_c *value)
844 {
845    return FileAccessible(value,R_OK);
846 }
FileExecutable(xstring_c * value)847 const char *ResMgr::FileExecutable(xstring_c *value)
848 {
849    return FileAccessible(value,X_OK);
850 }
DirReadable(xstring_c * value)851 const char *ResMgr::DirReadable(xstring_c *value)
852 {
853    return FileAccessible(value,R_OK|X_OK,true);
854 }
FileCreatable(xstring_c * value)855 const char *ResMgr::FileCreatable(xstring_c *value)
856 {
857    if(!**value)
858       return 0;
859    const char *error=FileAccessible(value,W_OK,false);
860    if(!error || (error && errno!=ENOENT))
861       return error;
862    const char *bn=basename_ptr(*value);
863    xstring_c dir(dirname(*value));
864    if(!*dir)
865       xgetcwd_to(dir);
866    error=FileAccessible(&dir,X_OK|W_OK,true);
867    if(!error)  // dir may be expanded, combine it with base file name.
868       value->set(dir_file(dir,bn));
869    return error;
870 }
871 
872 #ifdef HAVE_ICONV
873 CDECL_BEGIN
874 # include <iconv.h>
875 CDECL_END
876 #endif
CharsetValidate(xstring_c * value)877 const char *ResMgr::CharsetValidate(xstring_c *value)
878 {
879    if(!**value)
880       return 0;
881 #ifdef HAVE_ICONV
882    iconv_t ic=iconv_open(*value,*value);
883    if(ic==(iconv_t)-1)
884       return _("this encoding is not supported");
885    iconv_close(ic);
886    return 0;
887 #else
888    return _("this encoding is not supported");
889 #endif
890 }
891 
NoClosure(xstring_c *)892 const char *ResMgr::NoClosure(xstring_c *)
893 {
894    return _("no closure defined for this setting");
895 }
896 
HasClosure(xstring_c * c)897 const char *ResMgr::HasClosure(xstring_c *c)
898 {
899    if(!*c || !**c)
900       return _("a closure is required for this setting");
901    return 0;
902 }
903 
UNumberPairValidate(xstring_c * value)904 const char *ResMgr::UNumberPairValidate(xstring_c *value)
905 {
906    NumberPair pair(':',*value);
907    if(pair.Error())
908       return pair.ErrorText();
909    return 0;
910 }
ToNumberPair(int & a,int & b) const911 void ResValue::ToNumberPair(int &a,int &b) const
912 {
913    NumberPair pair(':',s);
914    if(pair.Error()) {
915       a=b=0;
916    } else {
917       a=pair.N1();
918       b=pair.HasN2()?pair.N2():a;
919    }
920 }
921 
922 xlist_head<ResClient> ResClient::list;
Query(const char * name,const char * closure) const923 ResValue ResClient::Query(const char *name,const char *closure) const
924 {
925    if(!strchr(name,':'))
926    {
927       const char *prefix=ResPrefix();
928       name=xstring::cat(prefix,":",name,NULL);
929       name=alloca_strdup(name);
930    }
931    if(!closure)
932       closure=ResClosure();
933    return ResMgr::Query(name,closure);
934 }
ResClient()935 ResClient::ResClient()
936    : node(this)
937 {
938    list.add(node);
939 }
~ResClient()940 ResClient::~ResClient()
941 {
942    node.remove();
943 }
ReconfigAll(const char * r)944 void ResClient::ReconfigAll(const char *r)
945 {
946    xlist_for_each(ResClient,list,node,scan)
947       scan->Reconfig(r);
948 }
949 
QueryBool(const char * closure) const950 bool ResType::QueryBool(const char *closure) const
951 {
952    return Query(closure).to_bool();
953 }
QueryBool(const char * name,const char * closure)954 bool ResMgr::QueryBool(const char *name,const char *closure)
955 {
956    return Query(name,closure).to_bool();
957 }
QueryBool(const char * name,const char * closure) const958 bool ResClient::QueryBool(const char *name,const char *closure) const
959 {
960    return Query(name,closure).to_bool();
961 }
QueryTriBool(const char * closure,bool a) const962 bool ResType::QueryTriBool(const char *closure,bool a) const
963 {
964    return Query(closure).to_tri_bool(a);
965 }
QueryTriBool(const char * name,const char * closure,bool a)966 bool ResMgr::QueryTriBool(const char *name,const char *closure,bool a)
967 {
968    return Query(name,closure).to_tri_bool(a);
969 }
QueryTriBool(const char * name,const char * closure,bool a) const970 bool ResClient::QueryTriBool(const char *name,const char *closure,bool a) const
971 {
972    return Query(name,closure).to_tri_bool(a);
973 }
974 
ClassCleanup()975 void ResType::ClassCleanup()
976 {
977    xlist_for_each_safe(Resource,Resource::all_list,node,scan,next)
978       delete scan;
979    if(types_by_name) {
980       for(ResType *t=types_by_name->each_begin(); t; t=types_by_name->each_next())
981 	 t->Unregister();
982       delete types_by_name; types_by_name=0;
983    }
984 }
985