luci/libs/web/src/template_parser.c

464 lines
11 KiB
C
Raw Normal View History

/*
* LuCI Template - Parser implementation
*
* Copyright (C) 2009 Jo-Philipp Wich <xm@subsignal.org>
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
#include "template_parser.h"
/* leading and trailing code for different types */
const char * gen_code[6][2] = {
{ "write(\"", "\")" },
{ NULL, NULL },
{ "write(tostring(", "))" },
{ "include(\"", "\")" },
{ "write(translate(\"", "\"))" },
{ NULL, " " }
};
/* Simple strstr() like function that takes len arguments for both haystack and needle. */
static char *strfind(char *haystack, int hslen, const char *needle, int ndlen)
{
int match = 0;
int i, j;
for( i = 0; i < hslen; i++ )
{
if( haystack[i] == needle[0] )
{
match = ((ndlen == 1) || ((i + ndlen) <= hslen));
for( j = 1; (j < ndlen) && ((i + j) < hslen); j++ )
{
if( haystack[i+j] != needle[j] )
{
match = 0;
break;
}
}
if( match )
return &haystack[i];
}
}
return NULL;
}
/*
* Inspect current read buffer and find the number of "vague" characters at the end
* which could indicate an opening token. Returns the number of "vague" chars.
* The last continuous sequence of whitespace, optionally followed by a "<" is
* treated as "vague" because whitespace may be discarded if the upcoming opening
* token indicates pre-whitespace-removal ("<%-"). A single remaining "<" char
* can't be differentiated from an opening token ("<%"), so it's kept to be processed
* in the next cycle.
*/
static int stokscan(struct template_parser *data, int off, int no_whitespace)
{
int i;
int skip = 0;
int tokoff = data->bufsize - 1;
for( i = tokoff; i >= off; i-- )
{
if( data->buf[i] == T_TOK_START[0] )
{
skip = tokoff - i + 1;
tokoff = i - 1;
break;
}
}
if( !no_whitespace )
{
for( i = tokoff; i >= off; i-- )
{
if( isspace(data->buf[i]) )
skip++;
else
break;
}
}
return skip;
}
/*
* Similar to stokscan() but looking for closing token indicators.
* Matches "-", optionally followed by a "%" char.
*/
static int etokscan(struct template_parser *data)
{
int skip = 0;
if( (data->bufsize > 0) && (data->buf[data->bufsize-1] == T_TOK_END[0]) )
skip++;
if( (data->bufsize > skip) && (data->buf[data->bufsize-skip-1] == T_TOK_SKIPWS[0]) )
skip++;
return skip;
}
/*
* Generate Lua expressions from the given raw code, write it into the
* output buffer and set the lua_Reader specific size pointer.
* Takes parser-state, lua_Reader's size pointer and generator flags
* as parameter. The given flags indicate whether leading or trailing
* code should be added. Returns a pointer to the output buffer.
*/
static const char * generate_expression(struct template_parser *data, size_t *sz, int what)
{
char tmp[T_OUTBUFSZ];
int i;
int size = 0;
int start = 0;
int i18n_hasdef = 0;
memset(tmp, 0, T_OUTBUFSZ);
/* Inject leading expression code (if any) */
if( (what & T_GEN_START) && (gen_code[data->type][0] != NULL) )
{
memcpy(tmp, gen_code[data->type][0], strlen(gen_code[data->type][0]));
size += strlen(gen_code[data->type][0]);
}
/* Parse source buffer */
for( i = 0; i < data->outsize; i++ )
{
/* Skip leading whitespace for non-raw and non-expr chunks */
if( !start && isspace(data->out[i]) && (data->type == T_TYPE_I18N || data->type == T_TYPE_INCLUDE) )
continue;
else if( !start )
start = 1;
/* Found whitespace after i18n key */
if( (data->type == T_TYPE_I18N) && (i18n_hasdef == 1) )
{
/* At non-whitespace char, inject seperator token */
if( !isspace(data->out[i]) )
{
memcpy(&tmp[size], T_TOK_I18NSEP, strlen(T_TOK_I18NSEP));
size += strlen(T_TOK_I18NSEP);
i18n_hasdef = 2;
}
/* At further whitespace, skip */
else
{
continue;
}
}
/* Escape quotes, backslashes and newlines for plain, i18n and include expressions */
if( (data->type == T_TYPE_TEXT || data->type == T_TYPE_I18N || data->type == T_TYPE_INCLUDE) &&
(data->out[i] == '\\' || data->out[i] == '"' || data->out[i] == '\n' || data->out[i] == '\t') )
{
tmp[size++] = '\\';
switch(data->out[i])
{
case '\n':
tmp[size++] = 'n';
break;
case '\t':
tmp[size++] = 't';
break;
default:
tmp[size++] = data->out[i];
}
}
/* Found first whitespace in i18n expression, raise flag */
else if( isspace(data->out[i]) && (data->type == T_TYPE_I18N) && (i18n_hasdef == 0) )
{
i18n_hasdef = 1;
}
/* Normal char */
else
{
tmp[size++] = data->out[i];
}
}
/* Processed i18n expression without default text, inject separator */
if( (data->type == T_TYPE_I18N) && (i18n_hasdef < 2) )
{
memcpy(&tmp[size], T_TOK_I18NSEP, strlen(T_TOK_I18NSEP));
size += strlen(T_TOK_I18NSEP);
}
/* Inject trailing expression code (if any) */
if( (what & T_GEN_END) && (gen_code[data->type][1] != NULL) )
{
memcpy(&tmp[size], gen_code[data->type][1], strlen(gen_code[data->type][1]));
size += strlen(gen_code[data->type][1]);
}
*sz = data->outsize = size;
memset(data->out, 0, T_OUTBUFSZ);
memcpy(data->out, tmp, size);
//printf("<<<%i|%i|%i|%s>>>\n", what, data->type, *sz, data->out);
return data->out;
}
/*
* Move the number of bytes specified in data->bufsize from the
* given source pointer to the beginning of the read buffer.
*/
static void bufmove(struct template_parser *data, const char *src)
{
if( data->bufsize > 0 )
memmove(data->buf, src, data->bufsize);
else if( data->bufsize < 0 )
data->bufsize = 0;
data->buf[data->bufsize] = 0;
}
/*
* Move the given amount of bytes from the given source pointer
* to the output buffer and set data->outputsize.
*/
static void bufout(struct template_parser *data, const char *src, int len)
{
if( len >= 0 )
{
memset(data->out, 0, T_OUTBUFSZ);
memcpy(data->out, src, len);
data->outsize = len;
}
else
{
data->outsize = 0;
}
}
/*
* lua_Reader compatible function that parses template code on demand from
* the given file handle.
*/
const char *template_reader(lua_State *L, void *ud, size_t *sz)
{
struct template_parser *data = ud;
char *match = NULL;
int off = 0;
int ignore = 0;
int genflags = 0;
int readlen = 0;
int vague = 0;
while( !(data->flags & T_FLAG_EOF) || (data->bufsize > 0) )
{
/* Fill buffer */
if( !(data->flags & T_FLAG_EOF) && (data->bufsize < T_READBUFSZ) )
{
if( (readlen = read(data->fd, &data->buf[data->bufsize], T_READBUFSZ - data->bufsize)) > 0 )
data->bufsize += readlen;
else if( readlen == 0 )
data->flags |= T_FLAG_EOF;
else
return NULL;
}
/* Evaluate state */
switch(data->state)
{
/* Plain text chunk (before "<%") */
case T_STATE_TEXT_INIT:
case T_STATE_TEXT_NEXT:
off = 0; ignore = 0; *sz = 0;
data->type = T_TYPE_TEXT;
/* Skip leading whitespace if requested */
if( data->flags & T_FLAG_SKIPWS )
{
data->flags &= ~T_FLAG_SKIPWS;
while( (off < data->bufsize) && isspace(data->buf[off]) )
off++;
}
/* Found "<%" */
if( (match = strfind(&data->buf[off], data->bufsize - off - 1, T_TOK_START, strlen(T_TOK_START))) != NULL )
{
readlen = (int)(match - &data->buf[off]);
data->bufsize -= (readlen + strlen(T_TOK_START) + off);
match += strlen(T_TOK_START);
/* Check for leading '-' */
if( match[0] == T_TOK_SKIPWS[0] )
{
data->bufsize--;
match++;
while( (readlen > 1) && isspace(data->buf[off+readlen-1]) )
{
readlen--;
}
}
bufout(data, &data->buf[off], readlen);
bufmove(data, match);
data->state = T_STATE_CODE_INIT;
}
/* Maybe plain chunk */
else
{
/* Preserve trailing "<" or white space, maybe a start token */
vague = stokscan(data, off, 0);
/* We can process some bytes ... */
if( vague < data->bufsize )
{
readlen = data->bufsize - vague - off;
}
/* No bytes to process, so try to remove at least whitespace ... */
else
{
/* ... but try to preserve trailing "<" ... */
vague = stokscan(data, off, 1);
if( vague < data->bufsize )
{
readlen = data->bufsize - vague - off;
}
/* ... no chance, push out buffer */
else
{
readlen = vague - off;
vague = 0;
}
}
bufout(data, &data->buf[off], readlen);
data->state = T_STATE_TEXT_NEXT;
data->bufsize = vague;
bufmove(data, &data->buf[off+readlen]);
}
if( ignore || data->outsize == 0 )
continue;
else
return generate_expression(data, sz, T_GEN_START | T_GEN_END);
break;
/* Ignored chunk (inside "<%# ... %>") */
case T_STATE_SKIP:
ignore = 1;
/* Initial code chunk ("<% ...") */
case T_STATE_CODE_INIT:
off = 0;
/* Check for leading '-' */
if( data->buf[off] == T_TOK_SKIPWS[0] )
off++;
/* Determine code type */
switch(data->buf[off])
{
case '#':
ignore = 1;
off++;
data->type = T_TYPE_COMMENT;
break;
case '=':
off++;
data->type = T_TYPE_EXPR;
break;
case '+':
off++;
data->type = T_TYPE_INCLUDE;
break;
case ':':
off++;
data->type = T_TYPE_I18N;
break;
default:
data->type = T_TYPE_CODE;
break;
}
/* Subsequent code chunk ("..." or "... %>") */
case T_STATE_CODE_NEXT:
/* Found "%>" */
if( (match = strfind(&data->buf[off], data->bufsize - off, T_TOK_END, strlen(T_TOK_END))) != NULL )
{
genflags = ( data->state == T_STATE_CODE_INIT )
? (T_GEN_START | T_GEN_END) : T_GEN_END;
readlen = (int)(match - &data->buf[off]);
/* Check for trailing '-' */
if( (match > data->buf) && (*(match-1) == T_TOK_SKIPWS[0]) )
{
readlen--;
data->flags |= T_FLAG_SKIPWS;
}
bufout(data, &data->buf[off], readlen);
data->state = T_STATE_TEXT_INIT;
data->bufsize -= ((int)(match - &data->buf[off]) + strlen(T_TOK_END) + off);
bufmove(data, &match[strlen(T_TOK_END)]);
}
/* Code chunk */
else
{
genflags = ( data->state == T_STATE_CODE_INIT ) ? T_GEN_START : 0;
/* Preserve trailing "%" and "-", maybe an end token */
vague = etokscan(data);
readlen = data->bufsize - off - vague;
bufout(data, &data->buf[off], readlen);
data->state = T_STATE_CODE_NEXT;
data->bufsize = vague;
bufmove(data, &data->buf[readlen+off]);
}
if( ignore || (data->outsize == 0 && !genflags) )
continue;
else
return generate_expression(data, sz, genflags);
break;
}
}
*sz = 0;
return NULL;
}