/* * lftp - file transfer program * * Copyright (c) 1996-2017 by Alexander V. Lukyanov (lav@yars.free.net) * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 3 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program. If not, see . */ #include #include #include #include #include #include #include #include #include #include CDECL_BEGIN #include "regex.h" CDECL_END #include "ResMgr.h" #include "SMTask.h" #include "misc.h" #include "StringSet.h" #include "log.h" xlist_head Resource::all_list; xmap *ResType::types_by_name; int ResType::VarNameCmp(const char *good_name,const char *name) { int res=EXACT_PREFIX+EXACT_NAME; const char *colon=strchr(good_name,':'); if(colon && !strchr(name,':')) { good_name=colon+1; res|=SUBSTR_PREFIX; } while(*good_name || *name) { if(*good_name==*name || (*good_name && *name && strchr("-_",*good_name) && strchr("-_",*name))) { good_name++; name++; continue; } if(*name && !*good_name) return DIFFERENT; if((!*name && *good_name) || (strchr("-_:",*name) && !strchr("-_:",*good_name))) { good_name++; if(strchr(name,':')) res|=SUBSTR_PREFIX; else res|=SUBSTR_NAME; continue; } return DIFFERENT; } return res; } bool ResType::IsAlias() const { return closure_valid==ResMgr::AliasValidate; } const char *ResType::FindVar(const char *name,const ResType **type,const char **re_closure) { const ResType *exact_proto=0; const ResType *exact_name=0; int sub=0; *type=types_by_name->lookup(name); if(*type) goto found; // exact match for(const ResType *type_scan=types_by_name->each_begin(); type_scan; type_scan=types_by_name->each_next()) { switch(VarNameCmp(type_scan->name,name)) { case EXACT_PREFIX+EXACT_NAME: *type=type_scan; return 0; case EXACT_PREFIX+SUBSTR_NAME: if(!exact_proto && !exact_name) sub=0; exact_proto=*type=type_scan; sub++; break; case SUBSTR_PREFIX+EXACT_NAME: if(!exact_proto && !exact_name) sub=0; exact_name=*type=type_scan; sub++; break; case SUBSTR_PREFIX+SUBSTR_NAME: if(exact_proto || exact_name) break; sub++; *type=type_scan; break; default: break; } } if(!*type && sub==0) return _("no such variable"); if(sub==1) goto found; *type=0; return _("ambiguous variable name"); found: if((*type)->IsAlias()) { const char *alias_c=(*type)->GetAliasTarget(); char *alias=alloca_strdup(alias_c); char *slash=strchr(alias,'/'); if(slash) { *slash=0; if(re_closure) *re_closure=alias_c+(slash+1-alias); } *type=types_by_name->lookup(alias); if(!*type) return "invalid compatibility alias"; } return 0; } const ResType *ResType::FindRes(const char *name) { const ResType *type; const char *msg=FindVar(name,&type); if(msg) return 0; return type; } const char *ResType::Set(const char *name,const char *cclosure,const char *cvalue,bool def) { ResType *type; // find type of given variable const char *msg=FindVar(name,&type,&cclosure); if(msg) return msg; return type->Set(cclosure,cvalue,def); } const char *ResType::Set(const char *cclosure,const char *cvalue,bool def) { const char *msg=0; xstring_c value(cvalue); if(value && val_valid && (msg=val_valid(&value))!=0) return msg; xstring_c closure(cclosure); if((closure || closure_valid==ResMgr::HasClosure) && closure_valid && (msg=closure_valid(&closure))!=0) return msg; bool need_reconfig=false; xlist_for_each(Resource,*(type_value_list),node,scan) { // find the old value if(closure==scan->closure || !xstrcmp(scan->closure,closure)) { if(def) return 0; need_reconfig=true; delete scan; break; } } if(value) { (void)new Resource(this,closure,value,def); need_reconfig=true; } if(need_reconfig) ResClient::ReconfigAll(name); return 0; } int ResMgr::ResourceCompare(const Resource *ar,const Resource *br) { int diff=strcmp(ar->type->name,br->type->name); if(diff) return diff; if(ar->closure==br->closure) return 0; if(ar->closure==0) return -1; if(br->closure==0) return 1; return strcmp(ar->closure,br->closure); } void Resource::Format(xstring& buf) const { buf.appendf("set %s",type->name); const char *s=closure; if(s) { buf.append('/'); bool par=false; if(strcspn(s," \t>|;&")!=strlen(s)) par=true; if(par) buf.append('"'); while(*s) { if(strchr("\"\\",*s)) buf.append('\\'); buf.append(*s++); } if(par) buf.append('"'); } buf.append(' '); s=value; bool par=false; if(*s==0 || strcspn(s," \t>|;&")!=strlen(s)) par=true; if(par) buf.append('"'); while(*s) { if(strchr("\"\\",*s)) buf.append('\\'); buf.append(*s++); } if(par) buf.append('"'); buf.append('\n'); } static int PResourceCompare(const Resource *const*a,const Resource *const*b) { return ResMgr::ResourceCompare(*a,*b); } static int RefResourceCompare(const Ref *a,const Ref *b) { return ResMgr::ResourceCompare(*a,*b); } char *ResType::Format(bool with_defaults,bool only_defaults) { RefArray created; if(with_defaults || only_defaults) { for(ResType *dscan=types_by_name->each_begin(); dscan; dscan=types_by_name->each_next()) if((only_defaults || dscan->SimpleQuery(0)==0) && !dscan->IsAlias()) created.append(new Resource(dscan, 0,xstrdup(dscan->defvalue?dscan->defvalue:"(nil)"))); } xstring buf(""); if(!only_defaults) { // just created Resources are also in all_list. xarray arr; xlist_for_each(Resource,Resource::all_list,node,scan) { if(!scan->def || with_defaults) arr.append(scan); } arr.qsort(PResourceCompare); for(int i=0; iFormat(buf); } else { created.qsort(RefResourceCompare); for(int i=0; iFormat(buf); } return buf.borrow(); } char **ResType::Generator(void) { StringSet res; for(ResType *dscan=types_by_name->each_begin(); dscan; dscan=types_by_name->each_next()) if(!dscan->IsAlias()) res.Append(dscan->name); res.qsort(); return res.borrow(); } const char *ResMgr::BoolValidate(xstring_c *value) { const char *v=*value; const char *newval=0; switch(v[0]) { case 't': newval="true"; break; case 'T': newval="True"; break; case 'f': newval="false"; break; case 'F': newval="False"; break; case 'y': newval="yes"; break; case 'Y': newval="Yes"; break; case 'n': newval="no"; break; case 'N': newval="No"; break; case '1': newval="1"; break; case '0': newval="0"; break; case '+': newval="+"; break; case '-': newval="-"; break; case 'o': newval=(v[1]=='f' || v[1]=='F')?"off":"on"; break; case 'O': newval=(v[1]=='f' || v[1]=='F')?"Off":"On"; break; default: return _("invalid boolean value"); } if(strcmp(v,newval)) value->set(newval); return 0; } const char *ResMgr::TriBoolValidate(xstring_c *value) { if(!BoolValidate(value)) return 0; /* not bool */ const char *v=*value; const char *newval=0; switch(v[0]) { case 'a': newval="auto"; break; case 'A': newval="Auto"; break; default: return _("invalid boolean/auto value"); } if(strcmp(v,newval)) value->set(newval); return 0; } static const char power_letter[] = { 0, /* not used */ 'K', /* kibi ('k' for kilo is a special case) */ 'M', /* mega or mebi */ 'G', /* giga or gibi */ 'T', /* tera or tebi */ 'P', /* peta or pebi */ 'E', /* exa or exbi */ 'Z', /* zetta or 2**70 */ 'Y' /* yotta or 2**80 */ }; static unsigned long long get_power_multiplier(char p) { const char *scan=power_letter; const int scale=1024; unsigned long long mul=1; p=toupper(p); while(scan(&end),0); unsigned long long m=get_power_multiplier(*end); if(v==end || m==0 || end[m>1]) return _("invalid number"); return 0; } const char *ResMgr::FloatValidate(xstring_c *value) { const char *v=*value; const char *end=v; (void)strtod(v,const_cast(&end)); unsigned long long m=get_power_multiplier(*end); if(v==end || m==0 || end[m>1]) return _("invalid floating point number"); return 0; } const char *ResMgr::UNumberValidate(xstring_c *value) { const char *v=*value; const char *end=v; (void)strtoull(v,const_cast(&end),0); unsigned long long m=get_power_multiplier(*end); if(!isdigit((unsigned char)v[0]) || v==end || m==0 || end[m>1]) return _("invalid unsigned number"); return 0; } const char *ResMgr::AliasValidate(xstring_c *) { return ""; } unsigned long long ResValue::to_unumber(unsigned long long max) const { if (is_nil()) return 0; const char *end=s; unsigned long long v=strtoull(s,const_cast(&end),0); unsigned long long m=get_power_multiplier(*end); unsigned long long vm=v*m; if(vm/m!=v || vm>max) return max; return vm; } long long ResValue::to_number(long long min,long long max) const { if (is_nil()) return 0; const char *end=s; long long v=strtoll(s,const_cast(&end),0); long long m=get_power_multiplier(*end); long long vm=v*m; if(vm/m!=v) return v>0?max:min; if(vm>max) return max; if(vmtype_value_list->add_tail(type_value_node); } Resource::~Resource() { all_node.remove(); type_value_node.remove(); } bool Resource::ClosureMatch(const char *cl_data) { if(!closure && !cl_data) return true; if(!(closure && cl_data)) return false; // a special case for domain name match (i.e. example.org matches *.example.org) if(closure[0]=='*' && closure[1]=='.' && !strcmp(closure+2,cl_data)) return true; if(0==fnmatch(closure,cl_data,FNM_PATHNAME)) return true; // try to match basename; helps matching torrent metadata url to *.torrent const char *bn=basename_ptr(cl_data); if(bn!=cl_data && 0==fnmatch(closure,bn,FNM_PATHNAME)) return true; return false; } const char *ResMgr::QueryNext(const char *name,const char **closure,Resource **ptr) { xlist *node=0; if(*ptr==0) { const ResType *type=FindRes(name); if(!type) { *ptr=0; *closure=0; return 0; } node=type->type_value_list->get_next(); } else { node=(*ptr)->type_value_node.get_next(); } *ptr=node->get_obj(); if(*ptr) { *closure=(*ptr)->closure; return (*ptr)->value; } *closure=0; return 0; } const char *ResType::SimpleQuery(const char *closure) const { // find the value xlist_for_each(Resource,*(type_value_list),node,scan) if(scan->ClosureMatch(closure)) return scan->value; return 0; } ResValue ResMgr::Query(const char *name,const char *closure) { const char *msg; const ResType *type; // find type of given variable msg=FindVar(name,&type); if(msg) { // debug only // fprintf(stderr,_("Query of variable `%s' failed: %s\n"),name,msg); return 0; } return type->Query(closure); } ResValue ResType::Query(const char *closure) const { const char *v=0; if(closure) v=SimpleQuery(closure); if(!v) v=SimpleQuery(0); if(!v) v=defvalue; return v; } bool ResMgr::str2bool(const char *s) { return(strchr("TtYy1+",s[0])!=0 || !strcasecmp(s,"on")); } ResDecl::ResDecl(const char *a_name,const char *a_defvalue,ResValValid *a_val_valid,ResClValid *a_closure_valid) { name=a_name; defvalue=a_defvalue; val_valid=a_val_valid; closure_valid=a_closure_valid; Register(); } ResDecls::ResDecls(ResType *array) { while(array->name) array++->Register(); } ResDecls::ResDecls(ResType *r1,ResType *r2,...) { r.append(r1); r1->Register(); if(!r2) return; r.append(r2); r2->Register(); va_list v; va_start(v,r2); while((r1=va_arg(v,ResType *))!=0) { r1->Register(); r.append(r1); } va_end(v); } ResDecls::~ResDecls() { for(int i=0; iUnregister(); } void ResType::Register() { if(!types_by_name) types_by_name=new xmap; types_by_name->add(name,this); if(!type_value_list) type_value_list=new xlist_head(); } void ResType::Unregister() { if(types_by_name) types_by_name->remove(name); if(type_value_list) { // remove all resources of this type xlist_for_each_safe(Resource,*type_value_list,node,scan,next) delete scan; delete type_value_list; type_value_list=0; } } void TimeIntervalR::init(const char *s) { double interval=0; infty=false; error_text=0; if(!strncasecmp(s,"inf",3) || !strcasecmp(s,"forever") || !strcasecmp(s,"never")) { infty=true; return; } int pos=0; for(;;) { double prec; char ch='s'; int pos1=strlen(s+pos); int n=sscanf(s+pos,"%lf%c%n",&prec,&ch,&pos1); if(n<1) break; ch=tolower((unsigned char)ch); if(ch=='m') prec*=MINUTE; else if(ch=='h') prec*=HOUR; else if(ch=='d') prec*=DAY; else if(ch!='s') { error_text=_("Invalid time unit letter, only [smhd] are allowed."); return; } interval+=prec; pos+=pos1; } if(pos==0) { error_text=_("Invalid time format. Format is