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