/*
* File: builtin_findfile.c
* Author: Igor Vlasenko <vlasenko@imath.kiev.ua>
* Created: Tue Jul 14 22:47:11 2009
*/
#include <ctype.h> /* for isalpha */
#include <stdlib.h> /* for getenv */
/* TODO: support CYGWIN
* see
*/
#if defined __CYGWIN32__ && !defined __CYGWIN__
/* For backwards compatibility with Cygwin b19 and
earlier, we define __CYGWIN__ here, so that
we can rely on checking just for that macro. */
# define __CYGWIN__ __CYGWIN32__
#endif
#if defined _WIN32 && !defined __CYGWIN__
/* Use Windows separators on all _WIN32 defining
environments, except Cygwin. */
# define DIR_SEPARATOR_CHAR '\\'
#endif
#if defined (DIR_SEPARATOR_CHAR)
# define IS_FILE_SEP(X) ((X=='/') || (X==DIR_SEPARATOR_CHAR))
#else
# define IS_FILE_SEP(X) (X=='/')
#endif
static int _ff_exists(const char* path) {
FILE *file_p = fopen(path, "r");
if (file_p) {
fclose(file_p);
if (debuglevel>=TMPL_LOG_DEBUG2) tmpl_log(TMPL_LOG_DEBUG2,"_ff_exists: found [%s]\n",path);
return 1;
}
if (debuglevel>=TMPL_LOG_DEBUG2) tmpl_log(TMPL_LOG_ERROR,"_ff_exists: not found [%s]\n",path);
return 0;
}
/* lame dirname implementation */
static PSTRING _ff_dirname(const char* path) {
PSTRING retval={(char*)path,(char*)path};
char c=0;
if (path!=NULL) retval.endnext += strlen(path);
else return retval;
while (retval.endnext > retval.begin && (c=*(--retval.endnext)) && ! IS_FILE_SEP(c));
/*fprintf(stderr,"built-in _ff_dirname: dir = %.*s\n",(int)(retval.endnext-retval.begin),retval.begin);*/
return retval;
}
/*
Windows Relative Paths
For functions that manipulate files, the file names can be relative to the current directory. A file name is relative to the current directory if it does not begin with one of the following:
* A UNC name of any format.
* A disk designator with a backslash, for example "C:\".
* A backslash, for example, "\directory").
*/
/* remember about \\?\ and \\?\UNC\ prefixes on WIN platform.
* see "File Names, Paths, and Namespaces"
*/
int _ff_is_absolute(const char * filename) {
unsigned char c0 = *filename;
#if defined _WIN32 || defined __CYGWIN__
unsigned char c1;
unsigned char c2;
#endif
if ('\0' == c0) return 0;
/* \\?\ and \\?\UNC\ prefixes are included too */
if (IS_FILE_SEP(c0)) return 1;
#if defined _WIN32 || defined __CYGWIN__
c1 = *(++filename);
if ('\0' == c1) return 0;
c2 = *(++filename);
if (isalpha(c0) && ':'==c1 && IS_FILE_SEP(c2)) return 1;
#endif
return 0;
}
#if defined _WIN32 || defined __CYGWIN__
int _ff_is_win_fully_qualified_path(const char * filename) {
unsigned char c0 = *filename;
unsigned char c1;
unsigned char c2;
if ('\0' == c0) return 0;
c1 = *(++filename);
if ('\0' == c1) return 0;
c2 = *(++filename);
/* \\?\ and \\?\UNC\ prefixes are included too */
if (isalpha(c0) && ':'==c1 && IS_FILE_SEP(c2)) return 1;
if ('\\'==c0 && '\\'==c1 && '?'==c2 && '\\'==*filename) return 1;
return 0;
}
#endif
#define _ff_canonical_path(X) (X)
static MPSTRING _shift_back_pstring_at(MPSTRING buf, char* pos, long shift) {
if (pos >= buf.begin && (pos+shift) <=buf.endnext) {
buf.endnext -= shift;
while (pos<buf.endnext) {
*pos=*(pos+shift);
pos++;
}
}
*buf.endnext='\0';
return buf;
}
static MPSTRING _filepath_remove_multiple_slashes(MPSTRING buf) {
char* pos = buf.begin;
#if defined _WIN32
/* due to \\?\ and \\?\UNC\ prefixes on WIN platform.
* see "File Names, Paths, and Namespaces"
* we skip first 2 bytes of path
*/
if (((buf.endnext-pos)>1) && ('\\'==*pos && '\\'==*(pos+1))) pos += 2;
#endif
while (pos<buf.endnext-1) {
if (IS_FILE_SEP(*pos) && IS_FILE_SEP(*(pos+1))) buf=_shift_back_pstring_at(buf, pos, 1);
else pos++;
}
return buf;
}
static const char* _ff_canonical_path_from_buf(MPSTRING buf) {
char* pos;
char* prev_slash_next;
char* slash_begin;
/* /./ <-- shift -2 */
pos = buf.begin;
while (pos<buf.endnext-2) {
if (IS_FILE_SEP(*pos) && ('.'==*(pos+1)) && IS_FILE_SEP(*(pos+2))) buf=_shift_back_pstring_at(buf, pos, 2);
pos++;
}
/* // <-- shift -1 */
buf=_filepath_remove_multiple_slashes(buf);
/* /.* /../ shift (scan from prevslash to slash back) */
pos = buf.begin;
slash_begin = buf.begin;
#if defined _WIN32
/* check for C: */
if (((buf.endnext-pos)>1) && isalpha((unsigned char) *pos) && ':'==*(pos+1)) {
pos += 2;
slash_begin += 2;
}
#endif
prev_slash_next = slash_begin;
while (pos<buf.endnext-3) {
/*printf("debug: %s pos=%c[%ld] fsn=%c[%ld]\n",buf.begin,*pos, pos-buf.begin, *prev_slash_next,prev_slash_next-buf.begin);*/
if (IS_FILE_SEP(*pos)) {
if (('.'==*(pos+1)) && ('.'==*(pos+2)) && IS_FILE_SEP(*(pos+3))) {
/*printf("debug: do shift pos=%ld fsn=%ld shift=%ld\n", pos-buf.begin, prev_slash_next-buf.begin, pos-prev_slash_next+4);*/
if (pos == prev_slash_next && prev_slash_next==slash_begin) {
/* begining of the string ("/../") -> leave one slash */
buf=_shift_back_pstring_at(buf, prev_slash_next, pos-prev_slash_next+3);
pos=prev_slash_next-1;/* 1 to compensate pos++ */
} else {
buf=_shift_back_pstring_at(buf, prev_slash_next, pos-prev_slash_next+4);
pos=prev_slash_next-2;/* 2 to compensate / and pos++ */
/* 2 to step back slashnext char and 'slash' char, if any */
if (prev_slash_next>slash_begin) prev_slash_next--;
if (prev_slash_next>slash_begin) prev_slash_next--;
}
/* old prev_slash_next now current, so we need to recalculate it */
/* first find a 'slash' char */
while (prev_slash_next>=slash_begin && !IS_FILE_SEP(*prev_slash_next)) prev_slash_next--;
if (prev_slash_next>slash_begin) prev_slash_next++;/* step next to slash */
} else {
prev_slash_next=pos+1;
}
}
pos++;
}
/* // <-- shift -1 */
buf=_filepath_remove_multiple_slashes(buf);
/* offset 0: if ./ shift -2 */
if ((buf.endnext-buf.begin)<2) return buf.begin;
pos = buf.begin;
if (('.'==*pos) && IS_FILE_SEP(*(pos+1))) buf=_shift_back_pstring_at(buf, pos, 2);
return buf.begin;
}
static MPSTRING _ff_add_pstr_to_buffer(MPSTRING buf, PSTRING pstr) {
MPSTRING ret = buf;
const char* s;
//tmpl_log(TMPL_LOG_ERROR,"_ff_add_pstr_to_buffer: called as [%p,%p]+[%p,%p]\n",buf.begin,buf.endnext, pstr.begin,pstr.endnext);
for (s=pstr.begin;s<pstr.endnext;s++) {*(ret.endnext++)=*s;}
//tmpl_log(TMPL_LOG_ERROR,"_ff_add_pstr_to_buffer: ret = [%p,%p]\n",ret.begin,ret.endnext);
return ret;
}
static MPSTRING _ff_add_str_to_buffer(MPSTRING buf, const char* str) {
MPSTRING ret = buf;
const char* s=str;
//tmpl_log(TMPL_LOG_ERROR,"_ff_add_str_to_buffer: called as [%p,%p]+[%s]\n",buf.begin,buf.endnext, str);
while ('\0'!=*s) {*(ret.endnext++)=*s++;}
//tmpl_log(TMPL_LOG_ERROR,"_ff_add_str_to_buffer: ret = [%p,%p]\n",ret.begin,ret.endnext);
return ret;
}
static MPSTRING _ff_add_sep_to_buffer(MPSTRING buf) {
MPSTRING ret = buf;
if (ret.endnext>ret.begin && IS_FILE_SEP(*(ret.endnext-1))) return ret;
#ifdef DIR_SEPARATOR_CHAR
*(ret.endnext++)=DIR_SEPARATOR_CHAR;
#else
*(ret.endnext++)='/';
#endif
return ret;
}
static MPSTRING _ff_add_0_to_buffer(MPSTRING buf) {
*(buf.endnext++)='\0';
return buf;
}
static const char* get_template_root(struct tmplpro_param* param) {
const char* retval = param->template_root;
if (NULL==retval) retval = getenv("HTML_TEMPLATE_ROOT");
return retval;
}
static const char* _find_file (struct tmplpro_param* param, const char* filename, PSTRING extra_dir) {
// TODO: finish it
const char* HTML_TEMPLATE_ROOT = get_template_root(param);
size_t HTML_TEMPLATE_ROOT_length=0;
size_t buffsize=0;
char** pathlist=param->path;
MPSTRING pbuf_begin, filepath;
if (param->debug >= TMPL_LOG_DEBUG2) {
tmpl_log(TMPL_LOG_DEBUG2,"built-in _find_file: looking for %s extra dir = %.*s\n",filename, (int)(extra_dir.endnext-extra_dir.begin),extra_dir.begin);
if (HTML_TEMPLATE_ROOT) tmpl_log(TMPL_LOG_DEBUG2,"built-in _find_file: HTML_TEMPLATE_ROOT = %s\n",HTML_TEMPLATE_ROOT);
}
/* first check for a full path */
if (_ff_is_absolute(filename) && _ff_exists(filename)) return _ff_canonical_path(filename);
#if defined _WIN32 || defined __CYGWIN__
/* no sense of prefixing C:\ or \\?\ */
if (_ff_is_win_fully_qualified_path(filename)) {
tmpl_log(TMPL_LOG_DEBUG2,"built-in _ff_is_win_fully_qualified_path: rejected %s: is_absolute=%d exists=%d\n",filename,_ff_is_absolute(filename),_ff_exists(filename));
return NULL;
}
#endif
if (HTML_TEMPLATE_ROOT!=NULL) HTML_TEMPLATE_ROOT_length=strlen(HTML_TEMPLATE_ROOT);
if (pathlist!=NULL) {
while (NULL!=*pathlist) {
size_t pathentrylen=strlen(*pathlist);
if (buffsize<pathentrylen) buffsize=pathentrylen;
pathlist++;
}
}
/* bufsize is max possible length path of path considered
* min is max_len(foreach pathlist)+HTML_TEMPLATE_ROOT_length+strlen(filename)+len(extra_dir)+1)
* but we malloc an extra space to avoid frequent reallocing
*/
buffsize+=HTML_TEMPLATE_ROOT_length+strlen(filename)+(extra_dir.endnext-extra_dir.begin)+4+1; /* 4 - for slashes */
pbuffer_resize(¶m->builtin_findfile_buffer, buffsize);
pbuf_begin.begin=pbuffer_string(¶m->builtin_findfile_buffer);
pbuf_begin.endnext=pbuf_begin.begin;
/* try the extra_path if one was specified */
if (extra_dir.begin!=NULL) {
filepath=_ff_add_pstr_to_buffer(pbuf_begin,extra_dir);
if (extra_dir.endnext-extra_dir.begin >0)
filepath=_ff_add_sep_to_buffer(filepath);
filepath=_ff_add_str_to_buffer(filepath,filename);
filepath=_ff_add_0_to_buffer(filepath);
if (_ff_exists(filepath.begin)) return _ff_canonical_path_from_buf(filepath);
}
/* try pre-prending HTML_Template_Root */
if (HTML_TEMPLATE_ROOT!=NULL) {
filepath=_ff_add_str_to_buffer(pbuf_begin,HTML_TEMPLATE_ROOT);
if (HTML_TEMPLATE_ROOT_length >0)
filepath=_ff_add_sep_to_buffer(filepath);
filepath=_ff_add_str_to_buffer(filepath,filename);
filepath=_ff_add_0_to_buffer(filepath);
if (_ff_exists(filepath.begin)) return _ff_canonical_path_from_buf(filepath);
}
/* try "path" option list.. */
pathlist=param->path;
if (pathlist!=NULL) {
while (NULL!=*pathlist) {
//tmpl_log(TMPL_LOG_ERROR,"try 'path' option list..: looking in [%s]\n",*pathlist);
filepath=_ff_add_str_to_buffer(pbuf_begin,*pathlist);
/* add separator only if *pathlist non-empty */
if (0!=**pathlist) filepath=_ff_add_sep_to_buffer(filepath);
filepath=_ff_add_str_to_buffer(filepath,filename);
filepath=_ff_add_0_to_buffer(filepath);
if (_ff_exists(filepath.begin)) return _ff_canonical_path_from_buf(filepath);
pathlist++;
}
}
/* try even a relative path from the current directory...*/
if (_ff_exists(filename)) return _ff_canonical_path(filename);
/* try "path" option list with HTML_TEMPLATE_ROOT prepended... */
if (HTML_TEMPLATE_ROOT!=NULL) {
pathlist=param->path;
if (pathlist!=NULL) {
while (NULL!=*pathlist) {
filepath=_ff_add_str_to_buffer(pbuf_begin,HTML_TEMPLATE_ROOT);
if (HTML_TEMPLATE_ROOT_length >0)
filepath=_ff_add_sep_to_buffer(filepath);
filepath=_ff_add_str_to_buffer(filepath,*pathlist);
/* add separator only if *pathlist non-empty */
if (0!=**pathlist) filepath=_ff_add_sep_to_buffer(filepath);
filepath=_ff_add_str_to_buffer(filepath,filename);
filepath=_ff_add_0_to_buffer(filepath);
if (_ff_exists(filepath.begin)) return _ff_canonical_path_from_buf(filepath);
pathlist++;
}
}
}
return NULL;
}
static const char* BACKCALL stub_find_file_func(ABSTRACT_FINDFILE* param,const char* filename, const char* last_visited_file) {
const char* filepath;
PSTRING extra_path ={NULL,NULL};
if (filename == last_visited_file) tmpl_log(TMPL_LOG_ERROR,"built-in find_file: internal error: buffer clash for %s\n",filename);
if (((struct tmplpro_param*)param)->debug>= TMPL_LOG_DEBUG)
tmpl_log(TMPL_LOG_DEBUG,"built-in find_file: looking for %s last_visited_file = %s\n",filename, last_visited_file);
// look for the included file...
if (last_visited_file!=NULL && ! ((struct tmplpro_param*) param)->search_path_on_include) {
extra_path = _ff_dirname(last_visited_file);
}
filepath = _find_file((struct tmplpro_param*)param,filename,extra_path);
if (filepath==NULL) {
char** path=((struct tmplpro_param*)param)->path;
const char* HTML_TEMPLATE_ROOT = get_template_root((struct tmplpro_param*)param);
tmpl_log(TMPL_LOG_ERROR,"built-in find_file: can't find file %s", filename);
if (NULL!=last_visited_file) tmpl_log(TMPL_LOG_ERROR," (included from %s)", last_visited_file);
if(HTML_TEMPLATE_ROOT!=NULL) {
tmpl_log(TMPL_LOG_ERROR," with HTML_TEMPLATE_ROOT = '%s'",HTML_TEMPLATE_ROOT);
}
if (NULL!=path) {
tmpl_log(TMPL_LOG_ERROR," with path = [");
while (NULL!=*path) {
tmpl_log(TMPL_LOG_ERROR," '%s'",*path);
path++;
}
tmpl_log(TMPL_LOG_ERROR," ]");
} else {
tmpl_log(TMPL_LOG_ERROR," with empty path list");
}
tmpl_log(TMPL_LOG_ERROR,"\n");
return NULL;
} else {
return filepath;
}
}
/*
* Local Variables:
* mode: c
* End:
*/