2152 lines
53 KiB
C
Raw Permalink Normal View History

2025-01-22 17:22:38 +01:00
/*
* libwebsockets - small server side websockets and web server implementation
*
* Copyright (C) 2010 - 2022 Andy Green <andy@warmcat.com>
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to
* deal in the Software without restriction, including without limitation the
* rights to use, copy, modify, merge, publish, distribute, sublicense, and/or
* sell copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in
* all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
* FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
* IN THE SOFTWARE.
*
* Stream parser for HTML 5
* https://w3c.github.io/html-reference/syntax.html
*
*/
#include <private-lib-core.h>
#define FAIL_CHAR 0x08
static uint8_t css_lextable[] = { /* the css property names */
#include "css-lextable.h"
};
static uint8_t css_propconst_lextable[] = { /* the css property values */
#include "css-propconst-lextable.h"
};
#define LHP_AC_GRANULE 512
enum {
/* html */
LHPS_INIT, /* default css injection */
LHPS_OUTER,
LHPS_TAG,
LHPS_BAD_TAG,
LHPS_DO_START_ELEM,
LHPS_ATTRIB,
LHPS_ATTRIB_VAL,
LHPS_AMP,
LHPS_AMPHASH,
LHPS_AMPHASH_HEX,
LHPS_SCOMMENT1,
LHPS_SCOMMENT2,
LHPS_COMMENT,
LHPS_ECOMMENT1,
LHPS_ECOMMENT2,
/* css */
LCSPS_CSS_OUTER,
LCSPS_CCOM_S1,
LCSPS_CCOM_E1,
LCSPS_CCOM,
LCSPS_CSS_OUTER_TAG1,
LCSPS_CSS_NAMES,
LCSPS_CSS_DEF_NAME,
LCSPS_CSS_DEF_VALUE,
LCSPS_SCOMMENT1,
LCSPS_SCOMMENT2,
LCSPS_COMMENT,
LCSPS_ECOMMENT1,
LCSPS_ECOMMENT2,
LCSPS_CSS_STANZA,
};
/*
* 17 well-known colours specified by CSS 2.1
* https://www.w3.org/TR/CSS21/syndata.html#value-def-color
*/
#if 0
static struct cols {
const char * const name;
uint32_t rgba;
} cols[] = {
{ "maroon", LWSDC_RGBA(0x80, 0x00, 0x00, 255) },
{ "red", LWSDC_RGBA(0xff, 0x00, 0x00, 255) },
{ "orange", LWSDC_RGBA(0xff, 0xa5, 0x00, 255) },
{ "yellow", LWSDC_RGBA(0xff, 0xff, 0x00, 255) },
{ "olive", LWSDC_RGBA(0x80, 0x80, 0x00, 255) },
{ "purple", LWSDC_RGBA(0x80, 0x00, 0x80, 255) },
{ "fuchsia", LWSDC_RGBA(0xff, 0x00, 0xff, 255) },
{ "white", LWSDC_RGBA(0xff, 0xff, 0xff, 255) },
{ "lime", LWSDC_RGBA(0x00, 0xff, 0x00, 255) },
{ "green", LWSDC_RGBA(0x00, 0x80, 0x00, 255) },
{ "navy", LWSDC_RGBA(0x00, 0x00, 0x80, 255) },
{ "blue", LWSDC_RGBA(0x00, 0x00, 0xff, 255) },
{ "aqua", LWSDC_RGBA(0x00, 0xff, 0xff, 255) },
{ "teal", LWSDC_RGBA(0x00, 0x80, 0x80, 255) },
{ "black", LWSDC_RGBA(0x00, 0x00, 0x00, 255) },
{ "silver", LWSDC_RGBA(0xc0, 0xc0, 0xc0, 255) },
{ "gray", LWSDC_RGBA(0x80, 0x80, 0x80, 255) },
};
#endif
/*
* "void elements" are html elements that don't have a scope, and so don't
* have a scope closure
*/
static const char * const void_elems[] = {
"area", "base", "br", "col", "command", "embed", "hr", "img",
"input", "keygen", "link", "meta", "param", "source", "track", "wbr"
};
static const uint8_t void_elems_lens[] = /* lengths for the table above */
{ 4, 4, 2, 3, 7, 5, 2, 3, 5, 6, 4, 4, 5, 6, 5, 3 };
static const char *const default_css =
"/* lws_lhp default css */"
"html, address,blockquote, dd, div,dl, dt, fieldset, form, frame, "
"frameset, h1, h2, h3, h4, h5, h6, noframes, ol, p, ul, center, "
"dir, hr, menu, pre { top: 0px; right: 0px; bottom: 0px; left: 0px;"
" unicode-bidi: embed; color: #000;"
"padding-top: 2px; padding-left: 2px; padding-bottom: 2px; padding-right: 2px;"
"margin-top: 2px; margin-left: 2px; margin-bottom: 2px; margin-right: 2px;"
"position: static; width: auto; height: auto;"
"}\n"
"div { display: block; width: auto; }\n"
"body { display: block}\n"
"li { display: list-item }\n"
"head { display: none }\n"
"table { display: table; }\n"
"tr { display: table-row }\n"
"thead { display: table-header-group }\n"
"tbody { display: table-row-group }\n"
"tfoot { display: table-footer-group }\n"
"col { display: table-column }\n"
"colgroup { display: table-column-group }\n"
"td, th { display: table-cell }\n"
"caption { display: table-caption }\n"
"th { font-weight: bolder; text-align: center }\n"
"caption { text-align: center }\n"
"body { margin: 8px }\n"
"h1 { font-size: 2em; margin: .67em 0 }\n"
"h2 { font-size: 1.5em; margin: .75em 0 }\n"
"h3 { font-size: 1.17em; margin: .83em 0 }\n"
"h4, p, blockquote, ul, fieldset, form, ol, dl, dir, menu "
"{ margin: 1.12em 0 }\n"
"h5 { font-size: .83em; margin: 1.5em 0 }\n"
"h6 { font-size: .75em; margin: 1.67em 0 }\n"
"h1, h2, h3, h4, h5, h6, b, strong { font-weight: bolder }\n"
"blockquote { margin-left: 40px; margin-right: 40px }\n"
"i, cite, em, var, address { font-style: italic }\n"
" pre, tt, code, kbd, samp { font-family: monospace }\n"
"pre { white-space: pre }\n"
"button, textarea, input, select { display: inline-block }\n"
"big { font-size: 1.17em }\n"
"small, sub, sup { font-size: .83em }\n"
"sub { vertical-align: sub }\n"
"sup { vertical-align: super }\n"
"table { border-spacing: 2px; padding-top: 2px; padding-left: 2px; padding-bottom: 2px; padding-right: 2px; margin-top: 2px; margin-bottom: 2px; margin-left: 2px; margin-right: 2px }\n"
"thead, tbody, tfoot { vertical-align: middle }\n"
"td, th, tr { vertical-align: inherit; width: auto; padding-top: 2px; padding-left: 2px; padding-bottom: 2px; padding-right: 2px; margin-top: 2px; margin-bottom: 2px; margin-left: 2px; margin-right: 2px }\n"
"s, strike, del { text-decoration: line-through }\n"
"hr { border: 1px inset }\n"
"ol, ul, dir, menu, dd { margin-left: 40px }\n"
"ol { list-style-type: decimal }\n"
"ol ul, ul ol, ul ul, ol ol { margin-top: 0; margin-bottom: 0 }\n"
"u, ins { text-decoration: underline }\n"
"br:before { content: \"A\"; white-space: pre-line }\n"
"center { text-align: center }\n"
":link, :visited { text-decoration: underline }\n"
":focus { outline: thin dotted invert }\n"
"BDO[DIR=\"ltr\"] { direction: ltr; unicode-bidi: bidi-override }"
"BDO[DIR=\"rtl\"] { direction: rtl; unicode-bidi: bidi-override }"
"*[DIR=\"ltr\"] { direction: ltr; unicode-bidi: embed }"
"*[DIR=\"rtl\"] { direction: rtl; unicode-bidi: embed }"
"@media print {"
" h1 { page-break-before: always }\n"
" h1, h2, h3, h4, h5, h6 { page-break-after: avoid }\n"
" ul, ol, dl { page-break-before: avoid }\n"
"}\n"
;
static int
lhp_clean_atr(lws_dll2_t *d, void *user)
{
lhp_atr_t *atr = lws_container_of(d, lhp_atr_t, list);
lws_dll2_remove(d);
lws_free(atr);
return 0;
}
static void
lhp_clean_level(lhp_pstack_t *ps)
{
lws_dll2_foreach_safe(&ps->atr, NULL, lhp_clean_atr);
lws_dll2_remove(&ps->list);
lws_free(ps);
}
int
lws_lhp_construct(lhp_ctx_t *ctx, lhp_callback cb, void *user,
const lws_surface_info_t *ic)
{
lhp_pstack_t *ps = lws_zalloc(sizeof(*ps), __func__);
if (!ps)
return 1;
memset(ctx, 0, sizeof(*ctx) - sizeof(ctx->buf));
ctx->user = user;
ctx->ic = *ic;
/*
* these are done implicitly by the memset above
* ctx->state = LHPS_INIT;
* ctx->sp = 0;
*/
ps->cb = cb;
lws_dll2_add_tail(&ps->list, &ctx->stack);
return 0;
}
static int
lhp_clean_stack(lws_dll2_t *d, void *user)
{
lhp_pstack_t *ps = lws_container_of(d, lhp_pstack_t, list);
lhp_clean_level(ps);
return 0;
}
static const lws_fx_t c_254= { 2,54000000 }, c_10 = { 10,0 },
c_72 = { 72,0 }, c_6 = { 6,0 }, c_100 = { 100,0 };
/*
* We need to go backward until we reach an absolute length for the reference
* axis, then base off that and go forward applying relative operations (like %)
* on it in order.
*/
static int
lws_css_compute_cascaded_length(lhp_ctx_t *ctx, int ref, lhp_pstack_t *ps,
lws_fx_t *t1)
{
lhp_pstack_t *psb = ps, *psmap[20];
const struct lcsp_atr *atrmap[20];
lws_fx_t t2;
int amp = 0;
do {
const struct lcsp_atr *a;
psb = lws_css_get_parent_block(ctx, psb);
if (!psb)
break;
a = (ref == LWS_LHPREF_WIDTH) ? psb->css_width : psb->css_height;
if (!a)
/* skip levels that don't change it */
continue;
if (amp + 1 == LWS_ARRAY_SIZE(atrmap))
/* uhh... */
break;
psmap[amp] = psb;
atrmap[amp++] = a;
if (a->unit == LCSP_UNIT_LENGTH_PERCENT ||
a->unit == LCSP_UNIT_ANGLE_REL_DEG ||
a->unit == LCSP_UNIT_NONE)
/* need earlier info to compute... keep going back */
continue;
break;
} while (1);
/*
* We have the path back through the elements to the first
* absolute one
*/
while (amp-- > 0) {
if (atrmap[amp]->unit != LCSP_UNIT_LENGTH_PERCENT) {
*t1 = *lws_csp_px(atrmap[amp], psmap[amp]);
} else
if (amp)
lws_fx_div(t1,
lws_fx_mul(&t2, &atrmap[amp]->u.i, t1),
&c_100);
}
return 0;
}
const lws_fx_t *
lws_csp_px(const lcsp_atr_t *a, lhp_pstack_t *ps)
{
lhp_ctx_t *ctx;
const lws_display_font_t *f;
lws_fx_t t1, t2, t3;
int ref;
assert(ps);
if (!a)
return NULL;
ctx = lws_container_of(ps->list.owner, lhp_ctx_t, stack);
f = ps->font;
ref = lhp_prop_axis(a);
switch (a->unit) {
case LCSP_UNIT_LENGTH_EM:
return lws_fx_mul((lws_fx_t *)&a->r, &a->u.i, &f->em);
case LCSP_UNIT_LENGTH_EX:
return lws_fx_mul((lws_fx_t *)&a->r, &a->u.i, &f->ex);
case LCSP_UNIT_LENGTH_IN: /* (inches * 2.54 * hwmm) / hwpx */
if (ref == LWS_LHPREF_NONE)
break;
return lws_fx_div((lws_fx_t *)&a->r, lws_fx_mul(&t2,
lws_fx_mul(&t3, &a->u.i, &c_254),
&ctx->ic.wh_mm[ref]), &ctx->ic.wh_px[ref]);
case LCSP_UNIT_LENGTH_CM: /* (cm * 10 * hwmm) / hwpx */
if (ref == LWS_LHPREF_NONE)
break;
return lws_fx_div((lws_fx_t *)&a->r,
lws_fx_mul(&t2,
lws_fx_mul(&t3, &a->u.i, &c_10),
&ctx->ic.wh_mm[ref]), &ctx->ic.wh_px[ref]);
case LCSP_UNIT_LENGTH_MM: /* (mm * hwmm) / hwpx */
if (ref == LWS_LHPREF_NONE)
break;
return lws_fx_div((lws_fx_t *)&a->r, lws_fx_mul(&t2,
&a->u.i, &ctx->ic.wh_mm[ref]), &ctx->ic.wh_px[ref]);
case LCSP_UNIT_LENGTH_PT: /* ((pt * 2.54 * hwmm) / hwpx ) / 72 */
if (ref == LWS_LHPREF_NONE)
break;
return lws_fx_div((lws_fx_t *)&a->r, lws_fx_div(&t1,
lws_fx_mul(&t2, lws_fx_mul(&t3,
&a->u.i, &c_254),
&ctx->ic.wh_mm[ref]),
&ctx->ic.wh_px[ref]), &c_72);
case LCSP_UNIT_LENGTH_PC: /* ((pc * 2.54 * hwmm) / hwpx ) / 6 */
if (ref == LWS_LHPREF_NONE)
break;
return lws_fx_div((lws_fx_t *)&a->r, lws_fx_div(&t1,
lws_fx_mul(&t2, lws_fx_mul(&t3,
&a->u.i, &c_254), &ctx->ic.wh_mm[ref]),
&ctx->ic.wh_px[ref]), &c_6);
case LCSP_UNIT_LENGTH_PX: /* px */
return &a->u.i;
case LCSP_UNIT_LENGTH_PERCENT: /* (percent * psb->w) / 100 */
if (ref == LWS_LHPREF_NONE)
break;
t1.whole = 0;
t1.frac = 0;
lws_css_compute_cascaded_length(ctx, ref, ps, &t1);
return lws_fx_div((lws_fx_t *)&a->r,
lws_fx_mul(&t2, &a->u.i, &t1), &c_100);
default:
break;
}
return &a->u.i;
}
static lhp_atr_t *
lhp_atr_new(lhp_ctx_t *ctx, size_t name_len, size_t value_len)
{
lhp_pstack_t *ps = lws_container_of(ctx->stack.tail, lhp_pstack_t, list);
/* create the element name attribute */
lhp_atr_t *a = lws_malloc(sizeof(*a) + name_len + 1 + value_len + 1,
"html_elem_atr");
size_t n;
if (!a)
return NULL;
if (!ps->atr.count) {
/* only check the tag string, not the attributes */
ctx->u.f.void_element = 0;
/*
* mark ps that are elements that contain others for layout as
* being the parent block
*/
if ((name_len == 4 && !strncmp(ctx->buf, "body", 4)) ||
(name_len == 3 && !strncmp(ctx->buf, "div", 3)))
ps->is_block = 1;
for (n = 0; n < LWS_ARRAY_SIZE(void_elems); n++)
if (ctx->npos == void_elems_lens[n] &&
!strncmp(void_elems[n], ctx->buf, (size_t)ctx->npos))
ctx->u.f.void_element = 1;
}
lws_dll2_clear(&a->list);
a->name_len = name_len;
a->value_len = value_len;
ctx->buf[ctx->npos] = '\0';
memcpy(&a[1], ctx->buf, (unsigned int)ctx->npos + 1u);
*(((uint8_t *)&a[1]) + name_len) = '\0';
lws_dll2_add_tail(&a->list, &ps->atr);
ctx->npos = 0;
return a;
}
static int
hspace(uint8_t c)
{
return c == ' ' || c == 9 || c == 10 || c == 12 || c == 13;
}
void
lhp_uni_emit(lhp_ctx_t *ctx)
{
/* emit */
if (ctx->temp <= 0x7f) {
ctx->buf[ctx->npos++] = (char)(ctx->temp & 0x7f);
return;
}
if (ctx->temp <= 0x7ff) {
ctx->buf[ctx->npos++] = (char)(0xc0 | ((uint8_t)(ctx->temp >> 6) & 0x1f));
goto a;
}
if (ctx->temp <= 0xffff) {
ctx->buf[ctx->npos++] = (char)(0xe0 | ((uint8_t)(ctx->temp >> 12) & 0xf));
goto b;
}
if (ctx->temp <= 0x10ffff) {
ctx->buf[ctx->npos++] = (char)(0xf0 | ((uint8_t)(ctx->temp >> 18) & 7));
ctx->buf[ctx->npos++] = (char)(0x80 | ((uint8_t)(ctx->temp >> 12) & 0x3f));
}
b:
ctx->buf[ctx->npos++] = (char)(0x80 | ((uint8_t)(ctx->temp >> 6) & 0x3f));
a:
ctx->buf[ctx->npos++] = (char)(0x80 | ((uint8_t)(ctx->temp) & 0x3f));
}
static int
lcsp_append_cssval_int(lhp_ctx_t *ctx)
{
lcsp_atr_t *atr = lwsac_use_zero(&ctx->cssac, sizeof(*atr), LHP_AC_GRANULE);
if (!atr)
return 1;
/* add this prop value atr to the def */
//lwsl_err("%s: tf %d.%u\n", __func__, ctx->tf.whole, ctx->tf.frac);
atr->u.i = ctx->tf;
atr->unit = ctx->unit;
lws_dll2_add_tail(&atr->list, &ctx->def->atrs);
return 0;
}
static int
lcsp_append_cssval_color(lhp_ctx_t *ctx)
{
lcsp_atr_t *atr = lwsac_use_zero(&ctx->cssac, sizeof(*atr), LHP_AC_GRANULE);
unsigned int r, g, b, a = 0xff;
if (!atr)
return 1;
/* add this prop value atr to the def */
switch (ctx->temp_count) {
case 3:
r = (ctx->temp >> 8) & 0xf;
g = (ctx->temp >> 4) & 0xf;
b = ctx->temp & 0xf;
atr->u.rgba = (a << 24) | (b << 20) | (b << 16) |
(g << 12) | (g << 8) | (r << 4) | r;
break;
case 4:
r = (ctx->temp >> 12) & 0xf;
g = (ctx->temp >> 8) & 0xf;
b = (ctx->temp >> 4) & 0xf;
a = ctx->temp & 0xf;
atr->u.rgba = (a << 28) | (a << 24) | (b << 20) | (b << 16) |
(g << 12) | (g << 8) | (r << 4) | r;
break;
case 6:
r = (ctx->temp >> 16) & 0xff;
g = (ctx->temp >> 8) & 0xff;
b = (ctx->temp) & 0xff;
atr->u.rgba = (a << 24) | (b << 16) | (g << 8) | r;
break;
case 8:
r = (ctx->temp >> 24) & 0xff;
g = (ctx->temp >> 16) & 0xff;
b = (ctx->temp >> 8) & 0xff;
a = (ctx->temp) & 0xff;
atr->u.rgba = (a << 24) | (b << 16) | (g << 8) | r;
break;
}
// lwsl_err("%s: %d, 0x%08x, 0x%08x\n", __func__, ctx->temp_count, ctx->temp, atr->u.rgba);
atr->unit = LCSP_UNIT_RGBA;
lws_dll2_add_tail(&atr->list, &ctx->def->atrs);
ctx->u.f.color = 0;
ctx->temp = 0;
ctx->temp_count = 0;
return 0;
}
static int
lcsp_append_cssval_string(lhp_ctx_t *ctx)
{
lcsp_atr_t *atr;
char *v, *c = &ctx->buf[0];
if (c[0] == '\"' || c[0] == '\'') {
c++;
ctx->npos--;
}
if (ctx->npos && (c[ctx->npos - 1] == '\"' || c[ctx->npos - 1] == '\''))
ctx->npos--;
atr = lwsac_use_zero(&ctx->cssac, sizeof(*atr) + (size_t)ctx->npos + 1u,
LHP_AC_GRANULE);
if (!atr)
return 1;
v = (char *)&atr[1];
atr->value_len = (size_t)ctx->npos;
memcpy(v, c, (size_t)ctx->npos);
v[ctx->npos] = '\0';
//lwsl_notice("%s: %s\n", __func__, v);
lws_dll2_add_tail(&atr->list, &ctx->def->atrs);
return 0;
}
static int
lws_css_cascade_atr_match(lhp_ctx_t *ctx, const char *tag, size_t tag_len)
{
lws_start_foreach_dll(struct lws_dll2 *, q, ctx->css.head) {
lcsp_stanza_t *stz = lws_container_of(q, lcsp_stanza_t, list);
/* ... does this stanza mention our name? */
lws_start_foreach_dll(struct lws_dll2 *, z, stz->names.head) {
lcsp_names_t *nm = lws_container_of(z, lcsp_names_t,
list);
const char *p = (const char *)&nm[1];
size_t nl = nm->name_len;
if (nl && *p == '.') { /* match .mycss as mycss */
p++;
nl--;
}
if (nl == tag_len && !memcmp(p, tag, tag_len)) {
lcsp_stanza_ptr_t *sp = lwsac_use_zero(
&ctx->cascadeac,
sizeof(*sp), LHP_AC_GRANULE);
if (!sp)
return 1;
sp->stz = stz;
lws_dll2_add_tail(&sp->list,
&ctx->active_stanzas);
break;
}
} lws_end_foreach_dll(z);
} lws_end_foreach_dll(q);
return 0;
}
const char *
lws_html_get_atr(lhp_pstack_t *ps, const char *aname, size_t aname_len)
{
/* look for src= attribute */
lws_start_foreach_dll(struct lws_dll2 *, p,
lws_dll2_get_head(&ps->atr)) {
const lhp_atr_t *at = lws_container_of(p,
lhp_atr_t, list);
const char *ats = (const char *)&at[1];
if (at->name_len == aname_len && !strcmp(ats, aname))
return ats + aname_len + 1;
} lws_end_foreach_dll(p);
return NULL;
}
/*
* Produce an ordered list of css stanzas that apply to the current html
* parsing context, accounting for class="xxx" at each level
*/
static int
lws_css_cascade(lhp_ctx_t *ctx)
{
lws_dll2_owner_clear(&ctx->active_stanzas);
lwsac_free(&ctx->cascadeac);
lws_dll2_owner_clear(&ctx->active_atr);
lwsac_free(&ctx->propatrac);
ctx->in_body = 0;
/* let's proceed through the html element stack that applies */
lws_start_foreach_dll(struct lws_dll2 *, p, ctx->stack.head) {
lhp_pstack_t *ps = lws_container_of(p, lhp_pstack_t, list);
/*
* if there is a css definition for the html entity at this
* stack level, add its stanza to the results
*/
lws_start_foreach_dll(struct lws_dll2 *, ha, ps->atr.head) {
lhp_atr_t *a = lws_container_of(ha, lhp_atr_t, list);
struct lws_tokenize ts;
memset(&ts, 0, sizeof(ts));
if (ha == ps->atr.head) {
ts.start = (const char *)&a[1];
ts.len = a->name_len;
}
if (a->name_len == 5 &&
!strcmp((const char *)&a[1], "class")) {
ts.start = ((const char *)&a[1]) + 5 + 1;
ts.len = a->value_len;
}
do {
ts.e = (int8_t)lws_tokenize(&ts);
if (ts.e == LWS_TOKZE_TOKEN) {
if (ha == ps->atr.head &&
ts.token_len == 4 &&
!memcmp(ts.token, "body", 4))
ctx->in_body = 1;
/*
* let's look through the css stanzas
* for a tag match
*/
if (lws_css_cascade_atr_match(ctx,
ts.token, ts.token_len))
return 1;
}
} while (ts.e > 0);
} lws_end_foreach_dll(ha);
/*
* ... fill layout-related CSS lookups into the element
* stack item... these are all pointers to the attribute
* not necessarily computed scalars. Eg lws_csp_px() can be
* used later to resolve atr like 50% to pixel values.
*/
ps->css_position = lws_css_cascade_get_prop_atr(ctx, LCSP_PROP_POSITION);
ps->css_width = lws_css_cascade_get_prop_atr(ctx, LCSP_PROP_WIDTH);
ps->css_height = lws_css_cascade_get_prop_atr(ctx, LCSP_PROP_HEIGHT);
ps->css_display = lws_css_cascade_get_prop_atr(ctx, LCSP_PROP_DISPLAY);
ps->css_border_radius[0] = lws_css_cascade_get_prop_atr(ctx, LCSP_PROP_BORDER_TOP_LEFT_RADIUS);
ps->css_border_radius[1] = lws_css_cascade_get_prop_atr(ctx, LCSP_PROP_BORDER_TOP_RIGHT_RADIUS);
ps->css_border_radius[2] = lws_css_cascade_get_prop_atr(ctx, LCSP_PROP_BORDER_BOTTOM_LEFT_RADIUS);
ps->css_border_radius[3] = lws_css_cascade_get_prop_atr(ctx, LCSP_PROP_BORDER_BOTTOM_RIGHT_RADIUS);
ps->css_background_color = lws_css_cascade_get_prop_atr(ctx, LCSP_PROP_BACKGROUND_COLOR);
ps->css_color = lws_css_cascade_get_prop_atr(ctx, LCSP_PROP_COLOR);
ps->css_pos[CCPAS_TOP] = lws_css_cascade_get_prop_atr(ctx, LCSP_PROP_TOP);
ps->css_pos[CCPAS_RIGHT] = lws_css_cascade_get_prop_atr(ctx, LCSP_PROP_RIGHT);
ps->css_pos[CCPAS_BOTTOM] = lws_css_cascade_get_prop_atr(ctx, LCSP_PROP_BOTTOM);
ps->css_pos[CCPAS_LEFT] = lws_css_cascade_get_prop_atr(ctx, LCSP_PROP_LEFT);
ps->css_margin[CCPAS_TOP] = lws_css_cascade_get_prop_atr(ctx, LCSP_PROP_MARGIN_TOP);
ps->css_margin[CCPAS_RIGHT] = lws_css_cascade_get_prop_atr(ctx, LCSP_PROP_MARGIN_RIGHT);
ps->css_margin[CCPAS_BOTTOM] = lws_css_cascade_get_prop_atr(ctx, LCSP_PROP_MARGIN_BOTTOM);
ps->css_margin[CCPAS_LEFT] = lws_css_cascade_get_prop_atr(ctx, LCSP_PROP_MARGIN_LEFT);
ps->css_padding[CCPAS_TOP] = lws_css_cascade_get_prop_atr(ctx, LCSP_PROP_PADDING_TOP);
ps->css_padding[CCPAS_RIGHT] = lws_css_cascade_get_prop_atr(ctx, LCSP_PROP_PADDING_RIGHT);
ps->css_padding[CCPAS_BOTTOM] = lws_css_cascade_get_prop_atr(ctx, LCSP_PROP_PADDING_BOTTOM);
ps->css_padding[CCPAS_LEFT] = lws_css_cascade_get_prop_atr(ctx, LCSP_PROP_PADDING_LEFT);
} lws_end_foreach_dll(p);
return 0;
}
void
lws_lhp_destruct(lhp_ctx_t *ctx)
{
if (ctx->base_url) {
free((void *)ctx->base_url);
ctx->base_url = NULL;
}
lws_dll2_foreach_safe(&ctx->stack, NULL, lhp_clean_stack);
lws_dll2_owner_clear(&ctx->active_stanzas);
lws_dll2_owner_clear(&ctx->active_atr);
lwsac_free(&ctx->propatrac);
lwsac_free(&ctx->cascadeac);
lwsac_free(&ctx->cssac);
}
void
lws_lhp_tag_dlo_id(lhp_ctx_t *ctx, lhp_pstack_t *ps, lws_dlo_t *dlo)
{
const char *pname;
/* Deal with ID matching */
pname = lws_html_get_atr(ps, "id", 2);
if (!pname)
return;
lws_start_foreach_dll(struct lws_dll2 *, d, lws_dll2_get_head(ctx->ids)) {
lws_display_id_t *id = lws_container_of(d, lws_display_id_t, list);
if (!strcmp(pname, id->id)) {
dlo->id = id;
id->exists = 1;
lwsl_debug("%s: %s tagged\n", __func__, pname);
return;
}
} lws_end_foreach_dll(d);
}
lws_stateful_ret_t
lws_lhp_parse(lhp_ctx_t *ctx, const uint8_t **buf, size_t *len)
{
lhp_pstack_t *ps1, *ps = lws_container_of(ctx->stack.tail,
lhp_pstack_t, list);
struct lws_context *cx = (struct lws_context *)ctx->user1;
lws_dl_rend_t *drt = (lws_dl_rend_t *)ctx->user;
lws_stateful_ret_t r;
const uint8_t *rbuf;
size_t rsize;
lhp_atr_t *a;
if (ctx->await_css_done && !ctx->is_css)
return LWS_SRET_AWAIT_RETRY;
assert(drt);
if (!*len && ctx->is_css && ctx->await_css_done && ctx->finish_css)
goto finish_css;
while (*len) {
uint8_t c = *(*buf)++;
(*len)--;
if (ctx->state == LHPS_DO_START_ELEM) {
/* we are retrying the inner callback */
(*len)++;
(*buf)--;
}
// lwsl_notice("%s: %d, '%c', %02X\n", __func__, ctx->state, c, c);
switch (ctx->state) {
case LHPS_INIT:
/* default css injection first, then... */
ctx->state = LCSPS_CSS_OUTER;
ctx->u.f.default_css = 1;
/*
* recurse (there's no stack usage to speak of) to
* do the default css parse first, CSS doesn't have a
* way to recurse further.
*/
rbuf = (const uint8_t *)default_css;
rsize = strlen(default_css);
r = lws_lhp_parse(ctx, &rbuf, &rsize);
if (r >= LWS_SRET_FATAL) {
lwsl_err("%s: css parse fail\n", __func__);
return r;
}
ctx->u.f.default_css = 0;
ctx->npos = 0;
ctx->state = LHPS_OUTER;
/* fallthru */
case LHPS_OUTER:
switch (c) {
case '<':
ctx->u.s = 0;
ctx->u.f.first = 1;
ctx->tag = NULL;
ctx->tag_len = 0;
ctx->state = LHPS_TAG;
if (ctx->stack.count == LHP_MAX_ELEMS_NEST /* sanity */) {
lwsl_err("%s: MAX_ELEMS_NEST\n", __func__);
ps->cb(ctx, LHPCB_FAILED);
return LWS_SRET_FATAL;
}
ps1 = lws_zalloc(sizeof(*ps1), __func__);
if (!ps1)
goto oom;
/* inherit user and cb to start with */
ps1->user = ps->user;
ps1->cb = ps->cb;
lws_dll2_owner_clear(&ps1->atr);
lws_dll2_add_tail(&ps1->list, &ctx->stack);
ps = ps1;
break;
case '&':
ctx->state = LHPS_AMP;
ctx->temp_count = 0;
continue;
case '\t':
case '\n':
c = ' ';
/* fallthru */
default:
if (c != ' ' || !ctx->npos ||
ctx->buf[ctx->npos - 1] != ' ')
ctx->buf[ctx->npos++] = (char)c;
break;
}
if (ctx->npos &&
(ctx->state != LHPS_OUTER ||
ctx->npos >= LHP_STRING_CHUNK - 4)) {
if (ctx->in_body && (ctx->npos != 1 || ctx->buf[0] != ' ')) {
lws_css_cascade(ctx);
ps->cb(ctx, LHPCB_CONTENT);
}
ctx->npos = 0;
}
break;
case LHPS_TAG:
if (c == '!' && ctx->u.f.first) {
ctx->state = LHPS_SCOMMENT1;
ctx->u.f.first = 0;
break;
}
if (c == '/' && ctx->u.f.first) {
/* remove the level we just prepared for this */
lhp_clean_level(ps);
ps = lws_container_of(ctx->stack.tail,
lhp_pstack_t, list);
ctx->u.f.closing = 1;
ctx->u.f.first = 0;
break;
}
ctx->u.f.first = 0;
/* it implies the end of the tag name */
if (hspace(c) || c == '/' || c == '>') {
if (!ctx->u.f.tag_used && ctx->npos && !ctx->u.f.closing) {
a = lhp_atr_new(ctx, (size_t)ctx->npos, 0);
if (!a)
goto oom;
ctx->tag = (const char *)&a[1];
ctx->tag_len = a->name_len;
ctx->u.f.tag_used = 1;
if (ctx->tag_len == 8 &&
!strncasecmp(ctx->buf, "!doctype", 8))
ctx->u.f.doctype = 1;
}
if (c != '/' && c != '>') {
/* after that, there may be attributes */
ctx->state = LHPS_ATTRIB;
break;
}
/* <style> trapdoor into inline css parsing */
if (ctx->u.f.tag_used && c == '>' &&
ctx->tag_len == 5 &&
!strncasecmp(ctx->buf, "style", 5)) {
ctx->npos = 5;
ps->cb(ctx, LHPCB_ELEMENT_START);
ctx->npos = 0;
// lwsl_warn("leaving html for css\n");
ctx->state = LCSPS_CSS_OUTER;
break;
}
}
if (ctx->u.f.void_element && c == '/') {
/* we had something like <br and then we see a
* closing / */
ctx->u.f.closing = 1;
break;
}
if (c == '>') {
ctx->state = LHPS_DO_START_ELEM;
goto elem_start;
}
/* tag names may only contain 09, az, and AZ */
if ( //ctx->closing ||
c < '0' ||
(c > '9' && c < 'A') ||
(c > 'Z' && c < 'a') ||
c > 'z') {
ctx->state = LHPS_BAD_TAG;
break;
}
/* collect the tag name */
if (!hspace(c))
ctx->buf[ctx->npos++] = (char)c;
if (ctx->npos == 32) { /* sanity */
ctx->npos = 0;
ctx->state = LHPS_BAD_TAG;
break;
}
break;
case LHPS_BAD_TAG:
/* just sit it out until the element end */
if (c != '>')
break;
ctx->state = LHPS_DO_START_ELEM;
/* fallthru */
case LHPS_DO_START_ELEM:
elem_start:
/* present the tag in buf, if any */
if (ctx->tag_len)
memcpy(ctx->buf, ctx->tag, ctx->tag_len);
ctx->buf[ctx->tag_len] = '\0';
ctx->npos = (int)ctx->tag_len;
if (!ctx->u.f.closing || ctx->u.f.void_element) {
const char *pname = NULL, *rel = NULL;
const struct lcsp_atr *aa = NULL;
lws_dlo_ss_create_info_t i;
lws_dlo_image_t u;
lhp_pstack_t *psb;
lws_dlo_t *dlo;
lws_box_t box;
char url[128];
memset(&i, 0, sizeof(i));
lws_css_cascade(ctx);
if (ctx->npos == 4 && !strncmp(ctx->buf, "body", 4)) {
lws_display_colour_t col =
LWSDC_RGBA(255, 255, 255, 255);
if (ps->css_background_color &&
ps->css_background_color->unit == LCSP_UNIT_RGBA)
col = ps->css_background_color->u.rgba;
ps->drt.w = ctx->ic.wh_px[LWS_LHPREF_WIDTH];
if (ps->css_width &&
ps->css_width->propval != LCSP_PROPVAL_AUTO// &&
//lws_fx_comp(lws_csp_px(ps->css_width, ps), &box.w) < 0
)
ps->drt.w = *lws_csp_px(ps->css_width, ps);
ps->drt.h = ctx->ic.wh_px[LWS_LHPREF_HEIGHT];
if (ps->css_height &&
ps->css_height->propval != LCSP_PROPVAL_AUTO) //&&
//lws_fx_comp(lws_csp_px(ps->css_height, ps),
// &ps->drt.h) < 0)
ps->drt.h = *lws_csp_px(ps->css_height, ps);
/* put a default white body background behind everything */
lws_fx_set(box.x, 0, 0);
lws_fx_set(box.y, 0, 0);
box.w = ps->drt.w;
box.h = ps->drt.h;
ps->dlo = (lws_dlo_t *)lws_display_dlo_rect_new(
drt->dl, NULL, &box, 0,
col);
ps->dlo->flag_toplevel = 1;
lhp_set_dlo_padding_margin(ps, ps->dlo);
}
/* it's a link? */
if (ctx->npos == 4 && !strncmp(ctx->buf, "link", 4)) {
pname = lws_html_get_atr(ps, "href", 4);
rel = lws_html_get_atr(ps, "rel", 3);
if (!rel || strncmp(rel, "stylesheet", 10))
goto issue_elem_start;
}
/* it's an img? */
if (ctx->npos == 3 && !strncmp(ctx->buf, "img", 3))
pname = lws_html_get_atr(ps, "src", 3);
else {
aa = lws_css_cascade_get_prop_atr(ctx,
LCSP_PROP_BACKGROUND_IMAGE);
if (ctx->npos == 4 &&
!strncmp(ctx->buf, "body", 4) && aa)
pname = (const char *)(aa + 1);
}
assert(ctx->base_url);
if (!pname)
goto issue_elem_start;
/* we should be in an <img tag or
* something with a background image */
assert(ctx->base_url);
if (lws_http_rel_to_url(url, sizeof(url),
ctx->base_url, pname))
goto skip_image;
psb = lws_css_get_parent_block(ctx, ps);
//if (!psb)
// lwsl_err("%s: NULL psb\n", __func__);
if (ctx->npos == 3 && !strncmp(ctx->buf, "img", 3)) {
lws_fx_set(box.x, 0, 0);
lws_fx_set(box.y, 0, 0);
if (ps->css_position->propval == LCSP_PROPVAL_ABSOLUTE) {
// box.x = *lws_csp_px(ps->css_pos[CCPAS_LEFT], ps);
/// box.y = *lws_csp_px(ps->css_pos[CCPAS_TOP], ps);
// abs = 1;
} else {
if (psb) {
box.x = psb->curx;
box.y = psb->cury;
}
}
if (psb) {
lws_fx_add(&box.x, &box.x,
lws_csp_px(psb->css_margin[CCPAS_LEFT], psb));
lws_fx_add(&box.y, &box.y,
lws_csp_px(psb->css_margin[CCPAS_TOP], psb));
}
box.h = ctx->ic.wh_px[LWS_LHPREF_HEIGHT]; /* placeholder */
lws_fx_sub(&box.w, &ctx->ic.wh_px[0], &box.x);
if (ps->css_width &&
lws_fx_comp(lws_csp_px(ps->css_width, ps), &box.w) > 0)
box.w = *lws_csp_px(ps->css_width, ps);
}
memset(&u, 0, sizeof(u));
if (lws_dlo_ss_find(cx, url, &u)) {
i.cx = cx;
i.dl = drt->dl;
if (psb)
i.dlo_parent = psb->dlo;
i.box = &box;
i.on_rx = ctx->ssevcb;
i.on_rx_sul = ctx->ssevsul;
i.url = url;
i.lhp = ctx;
i.u = &u;
i.window = ctx->window;
lwsl_cx_warn(cx, "not already in progress: %s", url);
if (lws_dlo_ss_create(&i, &dlo)) {
/* we can't get it */
lwsl_cx_warn(cx, "Can't get %s", url);
goto issue_elem_start;
} else {
lwsl_cx_warn(cx, "Created SS for %s\n", url);
if (psb)
psb->dlo = dlo;
// else
ps->dlo = dlo;
}
} else {
// lwsl_cx_warn(cx, "Found in-progress %s\n", url);
if (psb)
psb->dlo = &u.u.dlo_png->dlo;
//else
ps->dlo = &u.u.dlo_png->dlo;
}
if (ctx->npos == 4 && !strncmp(ctx->buf, "link", 4)) {
ps->cb(ctx, LHPCB_ELEMENT_START);
ctx->npos = 0;
ctx->state = LCSPS_CSS_OUTER;
ctx->await_css_done = 1;
return LWS_SRET_AWAIT_RETRY;
}
/*
* It's on its way to some extent and *u set...
*
* If he has given explicit width and height
* for the image, no need to wait for them
*/
if (lws_csp_px(lws_css_cascade_get_prop_atr(ctx,
LCSP_PROP_HEIGHT), ps)->whole &&
lws_csp_px(lws_css_cascade_get_prop_atr(ctx,
LCSP_PROP_WIDTH), ps)->whole) {
lwsl_cx_warn(cx, "Have width and height %d x %d",
(int)lws_csp_px(lws_css_cascade_get_prop_atr(ctx,
LCSP_PROP_WIDTH), ps)->whole,
(int)lws_csp_px(lws_css_cascade_get_prop_atr(ctx,
LCSP_PROP_HEIGHT), ps)->whole);
u.u.dlo_png->dlo.box.w.whole = lws_csp_px(lws_css_cascade_get_prop_atr(ctx,
LCSP_PROP_WIDTH), ps)->whole;
u.u.dlo_png->dlo.box.h.whole = lws_csp_px(lws_css_cascade_get_prop_atr(ctx,
LCSP_PROP_HEIGHT), ps)->whole;
goto issue_elem_start;
}
/*
* Do we have the dimensions? If not, bail
* from here and await a retry (maybe caused by
* data coming for the image)
*/
if (!lws_dlo_image_width(&u) ||
!lws_dlo_image_height(&u)) {
// lwsl_warn("%s: exiting with AWAIT_RETRY\n", __func__);
return LWS_SRET_AWAIT_RETRY;
}
u.u.dlo_png->dlo.box.w.whole = (int32_t)lws_dlo_image_width(&u);
u.u.dlo_png->dlo.box.h.whole = (int32_t)lws_dlo_image_height(&u);
/* did it fail to retreive it? */
if (u.u.dlo_png->dlo.box.w.whole < 0) {
lwsl_notice("%s: understanding image failed\n", __func__);
goto skip_image;
}
/*
* ... we needed it, we have it... we set it...
* ... let's go
*/
issue_elem_start:
r = ps->cb(ctx, LHPCB_ELEMENT_START);
ctx->npos = 0;
if (r) {
lwsl_notice("%s: inner cb returned %d\n", __func__, r);
return r;
}
}
if (ctx->u.f.closing || ctx->u.f.void_element){
if (ctx->stack.count == 1) {
lwsl_err("%s: element close mismatch\n", __func__);
ps->cb(ctx, LHPCB_FAILED);
return LWS_SRET_FATAL;
}
if (ps->atr.head) {
lhp_atr_t *a = lws_container_of(ps->atr.head, lhp_atr_t, list);
memcpy(ctx->buf, &a[1], a->name_len);
ctx->npos = (int)a->name_len;
}
ps->cb(ctx, LHPCB_ELEMENT_END);
ctx->npos = 0;
/* remove the start level */
lhp_clean_level(ps);
lws_css_cascade(ctx);
ps = lws_container_of(ctx->stack.tail,
lhp_pstack_t, list);
}
skip_image:
ctx->npos = 0;
ctx->state = LHPS_OUTER;
break;
case LHPS_ATTRIB:
if (ctx->u.f.doctype && c == '\"') {
ctx->u.f.inq = ctx->u.f.inq ^ 1u;
if (ctx->u.f.inq)
break;
}
if ((ctx->u.f.inq || !hspace(c)) &&
(c != '/' || ctx->u.f.inq) && c != '>') {
/* collect the attrib name */
ctx->buf[ctx->npos++] = (char)c;
/* sanity */
if (ctx->npos == LHP_STRING_CHUNK) {
lwsl_err("%s: string chunk\n", __func__);
ps->cb(ctx, LHPCB_FAILED);
return LWS_SRET_FATAL;
}
if (c == '=') {
ctx->nl_temp = ctx->npos - 1;
ctx->state = LHPS_ATTRIB_VAL;
}
break;
}
if (c == '/') {
ctx->u.f.closing = 1;
break;
}
if (ctx->npos &&
!lhp_atr_new(ctx, (size_t)ctx->npos, 0))
goto oom;
if (c == '>') {
ctx->state = LHPS_DO_START_ELEM;
goto elem_start;
}
break;
case LHPS_ATTRIB_VAL:
if (/*ctx->u.f.doctype && */c == '\"') {
ctx->u.f.inq = ctx->u.f.inq ^ 1u;
if (ctx->u.f.inq)
break;
}
if ((ctx->u.f.inq || !hspace(c)) &&
c != '>' && c != '\'' && c != '\"') {
/* collect the attrib value */
ctx->buf[ctx->npos++] = (char)c;
/* sanity */
if (ctx->npos == LHP_STRING_CHUNK) {
lwsl_err("%s: string chunk 2\n", __func__);
ps->cb(ctx, LHPCB_FAILED);
return LWS_SRET_FATAL;
}
if (c == '=') {
// !!! lwsl_err("%s: equal\n", __func__);
//ps->cb(ctx, LHPCB_FAILED);
//return LWS_SRET_FATAL;
}
break;
}
if (c == '/') {
ctx->u.f.closing = 1;
break;
}
if (c == '\'' || c == '\"')
break;
if (ctx->u.f.inq)
break;
if (ctx->npos) {
ctx->buf[ctx->npos] = '\0';
if (!lhp_atr_new(ctx, (size_t)ctx->nl_temp,
(size_t)ctx->npos - (size_t)ctx->nl_temp - 1u))
goto oom;
ctx->state = LHPS_ATTRIB;
ctx->npos = 0;
if (c != '>')
break;
}
if (c == '>') {
ctx->state = LHPS_DO_START_ELEM;
goto elem_start;
}
break;
case LHPS_AMP:
/* the character after the & */
if (c == '#') {
ctx->state = LHPS_AMPHASH;
ctx->temp = 0;
break;
}
/*
* These are supposed to be named chars, like &dagger;
* but not supported yet.
*/
ctx->state = LHPS_OUTER;
break;
case LHPS_AMPHASH:
/*
* This is either decimal or hex unicode like
* &#1234; or &#xfc16;
*/
if (c == 'x' || c == 'X') {
ctx->state = LHPS_AMPHASH_HEX;
break;
}
if (ctx->temp_count++ > 32 /* sanity */) {
ctx->state = LHPS_OUTER;
break;
}
if (c == ';') {
if (ctx->npos >= LHP_STRING_CHUNK - 5) {
if (ctx->in_body)
ps->cb(ctx, LHPCB_CONTENT);
ctx->npos = 0;
}
ctx->state = LHPS_OUTER;
lhp_uni_emit(ctx);
break;
}
if (c >= '0' && c <= '9')
ctx->temp = (uint32_t)(((int)ctx->temp * 10) + ((int)c - '0'));
else
ctx->state = LHPS_OUTER;
break;
case LHPS_AMPHASH_HEX:
if (c == ';') {
if (ctx->npos >= LHP_STRING_CHUNK - 5) {
if (ctx->in_body)
ps->cb(ctx, LHPCB_CONTENT);
ctx->npos = 0;
}
ctx->state = LHPS_OUTER;
lhp_uni_emit(ctx);
break;
}
if (ctx->temp_count++ > 8 /* sanity */) {
ctx->state = LHPS_OUTER;
break;
}
if (c >= '0' && c <= '9') {
ctx->temp = (uint32_t)(((int)ctx->temp << 4) + ((int)c - '0'));
break;
}
if (c >= 'A' && c <= 'F') {
ctx->temp = (uint32_t)(((int)ctx->temp << 4) + ((int)c - 'A') + 10);
break;
}
if (c >= 'a' && c <= 'f') {
ctx->temp = (uint32_t)(((int)ctx->temp << 4) + ((int)c - 'a') + 10);
break;
}
ctx->state = LHPS_OUTER;
break;
case LHPS_SCOMMENT1: /* we have <! */
if (c == '-') {
ctx->state = LHPS_SCOMMENT2;
break;
}
/* !doctype is an element tag */
ctx->buf[ctx->npos++] = '!';
ctx->buf[ctx->npos++] = (char)c;
ctx->state = LHPS_TAG;
break;
case LHPS_SCOMMENT2: /* we have <!- */
if (c == '-') {
ctx->state = LHPS_COMMENT;
break;
}
/* it can't be an element tag with - in it */
ctx->state = LHPS_BAD_TAG;
break;
case LHPS_COMMENT:
/* sanity */
if (ctx->npos >= LHP_STRING_CHUNK - 4) {
ps->cb(ctx, LHPCB_COMMENT);
ctx->npos = 0;
}
if (c == '-') {
ctx->state = LHPS_ECOMMENT1;
break;
}
/* collect the comment */
ctx->buf[ctx->npos++] = (char)c;
/* sanity */
if (ctx->npos >= LHP_STRING_CHUNK - 4) {
ps->cb(ctx, LHPCB_COMMENT);
ctx->npos = 0;
}
break;
case LHPS_ECOMMENT1:
if (c == '-') {
ctx->state = LHPS_ECOMMENT2;
break;
}
ctx->buf[ctx->npos++] = '-';
ctx->buf[ctx->npos++] = (char)c;
ctx->state = LHPS_COMMENT;
break;
case LHPS_ECOMMENT2:
if (c == '>') {
if (ctx->npos) {
ps->cb(ctx, LHPCB_COMMENT);
ctx->npos = 0;
}
ctx->state = LHPS_OUTER;
break;
}
ctx->buf[ctx->npos++] = '-';
ctx->buf[ctx->npos++] = '-';
ctx->buf[ctx->npos++] = (char)c;
ctx->state = LHPS_COMMENT;
break;
/*
* CSS parser
*/
case LCSPS_CSS_OUTER:
/* comments... */
ctx->state_css_comm = LCSPS_CSS_OUTER;
if (c == '<') {
ctx->state = LCSPS_CSS_OUTER_TAG1;
ctx->u.f.first = 1;
break;
}
if (c == '/') {
ctx->state = LCSPS_CCOM_S1;
break;
}
if (c == '{') { /* open stanza */
struct lws_tokenize ts;
/* create the stanza object */
ctx->stz = lwsac_use_zero(&ctx->cssac,
sizeof(*ctx->stz),
LHP_AC_GRANULE);
if (!ctx->stz)
goto oom;
/* attach names to it */
memset(&ts, 0, sizeof(ts));
ts.start = ctx->buf;
ts.len = (size_t)ctx->npos;
ts.flags = LWS_TOKENIZE_F_COMMA_SEP_LIST |
LWS_TOKENIZE_F_DOT_NONTERM;
do {
ts.e = (int8_t)lws_tokenize(&ts);
if (ts.e == LWS_TOKZE_TOKEN) {
lcsp_names_t *na = lwsac_use_zero(
&ctx->cssac,
sizeof(*na) +
ts.token_len + 1,
LHP_AC_GRANULE);
if (!na)
goto oom;
//lwsl_notice("%s: CSS name %.*s\n",
// __func__,
// (int)ts.token_len, ts.token);
na->name_len = ts.token_len;
memcpy(&na[1], ts.token, ts.token_len);
((char *)(&na[1]))[ts.token_len] = '\0';
lws_dll2_add_tail(&na->list, &ctx->stz->names);
}
} while (ts.e > 0);
/* list this stanza in our lhp context CSS */
lws_dll2_add_tail(&ctx->stz->list, &ctx->css);
ctx->buf[ctx->npos] = '\0';
ctx->state = LCSPS_CSS_STANZA;
ctx->cssval_state = 0;
ctx->css_state = 0;
ctx->u.f.arg = 0;
ctx->u.f.integer = 0;
ctx->u.f.color = 0;
break;
}
/* otherwise let's collect the name pieces */
if (ctx->npos >= LHP_STRING_CHUNK) {
lwsl_err("%s: css lhs too long\n", __func__);
return LWS_SRET_FATAL;
}
if (!hspace(c))
ctx->buf[ctx->npos++] = (char)c;
break;
case LCSPS_CSS_STANZA:
ctx->state_css_comm = LCSPS_CSS_STANZA;
if (c == '}') {
ctx->state = LCSPS_CSS_OUTER;
ctx->u.f.arg = 0;
if (ctx->u.f.color) {
lcsp_append_cssval_color(ctx);
ctx->npos = 0;
break;
}
if (ctx->u.f.integer) {/* x: 123} */
if (lcsp_append_cssval_int(ctx))
goto oom;
ctx->u.f.integer = 0;
ctx->npos = 0;
break;
}
//lwsl_notice("close curly cssval_state %d\n", ctx->cssval_state);
if (ctx->cssval_state || ctx->npos)
goto for_term;
ctx->npos = 0;
break;
}
if (c == '/') {
ctx->state = LCSPS_CCOM_S1;
break;
}
if (ctx->u.f.arg) {
/* we're on the value side of prop: value */
if (c == ';') {
/* resync after unknown prop: restart with
* whatever is after the ';' */
ctx->css_state = 0;
ctx->u.f.arg = 0;
if (ctx->u.f.color) {
lcsp_append_cssval_color(ctx);
ctx->npos = 0;
break;
}
if (ctx->u.f.integer) { /* x: 123; */
if (lcsp_append_cssval_int(ctx))
goto oom;
ctx->u.f.integer = 0;
ctx->npos = 0;
}
if (ctx->cssval_state)
goto for_term;
ctx->npos = 0;
break;
}
if (ctx->cssval_state == (int16_t)-1 &&
hspace(c)) {
/* resync after unknown prop: restart
* with whatever is after the ';' */
ctx->cssval_state = 0;
break;
}
if (ctx->u.f.color &&
((c >= '0' && c <= '9') ||
(c >= 'a' && c <= 'f') ||
(c >= 'A' && c <= 'F'))) {
ctx->temp = (uint32_t)(((int)ctx->temp << 4) |
((c >= '0' && c <= '9') ? c - '0' :
(c >= 'a' && c <= 'f') ? 10 + (c - 'a') :
10 + (c - 'A')));
ctx->temp_count++;
break;
}
if (!ctx->u.f.integer && hspace(c))
break;
if (!ctx->cssval_state && !ctx->u.f.integer &&
((c >= '0' && c <= '9') || c == '.')) {
// lwsl_notice("integer...\n");
lws_fx_set(ctx->tf, 0, 0);
ctx->u.f.integer = LHP_CSS_PROPVAL_INT_WHOLE;
ctx->temp = LWS_FX_FRACTION_MSD / 10;
ctx->unit = LCSP_UNIT_NONE;
}
if (ctx->u.f.integer) {
if (c == '.' &&
ctx->u.f.integer <= LHP_CSS_PROPVAL_INT_FRAC) {
ctx->u.f.integer = LHP_CSS_PROPVAL_INT_FRAC;
break;
}
if (ctx->u.f.integer < LHP_CSS_PROPVAL_INT_UNIT &&
c >= '0' && c <= '9') {
if (ctx->u.f.integer == LHP_CSS_PROPVAL_INT_WHOLE)
ctx->tf.whole =
(ctx->tf.whole * 10) +
(c - '0');
else {
if (ctx->temp) {
ctx->tf.frac += (int32_t)ctx->temp * (c - '0');
ctx->temp /= 10;
}
}
break;
}
if (hspace(c)) {
ctx->u.f.integer = 0;
break;
}
if (ctx->u.f.integer != LHP_CSS_PROPVAL_INT_UNIT) {
ctx->u.f.integer = LHP_CSS_PROPVAL_INT_UNIT;
ctx->npos = 0;
}
if (c == '%') {
ctx->unit = LCSP_UNIT_LENGTH_PERCENT;
goto issue_post;
}
if (ctx->npos < 4 && !ctx->unit) {
ctx->buf[ctx->npos++] = (char)c;
ctx->buf[ctx->npos] = '\0';
if (ctx->npos == 2) {
if (!strcmp(ctx->buf, "em"))
ctx->unit = LCSP_UNIT_LENGTH_EM;
if (!strcmp(ctx->buf, "ex"))
ctx->unit = LCSP_UNIT_LENGTH_EX;
if (!strcmp(ctx->buf, "in"))
ctx->unit = LCSP_UNIT_LENGTH_IN;
if (!strcmp(ctx->buf, "cm"))
ctx->unit = LCSP_UNIT_LENGTH_CM;
if (!strcmp(ctx->buf, "mm"))
ctx->unit = LCSP_UNIT_LENGTH_MM;
if (!strcmp(ctx->buf, "pt"))
ctx->unit = LCSP_UNIT_LENGTH_PT;
if (!strcmp(ctx->buf, "pc"))
ctx->unit = LCSP_UNIT_LENGTH_PC;
if (!strcmp(ctx->buf, "px"))
ctx->unit = LCSP_UNIT_LENGTH_PX;
}
if (ctx->npos == 3) {
if (!strcmp(ctx->buf, "deg"))
ctx->unit = LCSP_UNIT_ANGLE_ABS_DEG;
if (!strcmp(ctx->buf, "rad"))
ctx->unit = LCSP_UNIT_ANGLE_ABS_DEG;
}
if (ctx->npos == 4) {
if (!strcmp(ctx->buf, "grad"))
ctx->unit = LCSP_UNIT_ANGLE_ABS_DEG;
}
issue_post:
if (ctx->unit) {
if (lcsp_append_cssval_int(ctx))
goto oom;
ctx->u.f.integer = 0;
ctx->npos = 0;
}
break;
}
}
if (c == '#') {
ctx->temp = 0;
ctx->temp_count = 0;
ctx->u.f.color = 1;
break;
}
if (ctx->npos >= LHP_STRING_CHUNK) {
lwsl_err("%s: prop value string too long\n", __func__);
goto oom;
}
ctx->buf[ctx->npos++] = (char)c;
/* well-known property value strings */
for_term:
switch(lws_minilex_parse(css_propconst_lextable,
&ctx->cssval_state,
c, &ctx->propval)) {
case LWS_MINILEX_FAIL:
/*
* We don't know this property value, keep
* eating until we can resync at next
* ';', or we hit the '}'.
*/
//lwsl_notice("minilex val fail %c\n", c);
/* fallthru */
case LWS_MINILEX_CONTINUE:
if (!ctx->u.f.arg) { /* term */
if (ctx->npos)
lcsp_append_cssval_string(ctx);
ctx->npos = 0;
}
break;
case LWS_MINILEX_MATCH:
/* we have an unambiguous well-known
* property value match */
//lwsl_notice("propval %d\n", ctx->propval);
{
lcsp_atr_t *atr = lwsac_use_zero(
&ctx->cssac,
sizeof(*atr),
LHP_AC_GRANULE);
if (!atr)
goto oom;
/* add this prop value atr to the def */
atr->propval = ctx->propval;
lws_dll2_add_tail(&atr->list,
&ctx->def->atrs);
ctx->npos = 0;
}
ctx->cssval_state = 0;
break;
}
break;
}
/* we're trying to figure out the well-known prop name
* The matches all have the : attached, so they will
* match unambiguously */
if (ctx->css_state == (int16_t)-1 && c == ';') {
/* resync after unknown prop: restart with
* whatever is after the ';' */
ctx->css_state = 0;
break;
}
if (hspace(c)) {
ctx->u.f.color = 0;
if (ctx->css_state) /* space after the start
* means no match */
ctx->css_state = (int16_t)-1;
break;
}
switch(lws_minilex_parse(css_lextable, &ctx->css_state,
c, &ctx->prop)) {
case LWS_MINILEX_FAIL:
/*
* We don't know this property, keep eating
* until we can resync at next ';', or we hit
* the '}'.
*/
break;
case LWS_MINILEX_CONTINUE:
break;
case LWS_MINILEX_MATCH:
/* we have an unambiguous match, now we are
* doing the property args */
ctx->def = lwsac_use_zero(&ctx->cssac,
sizeof(*ctx->def),
LHP_AC_GRANULE);
if (!ctx->def)
goto oom;
ctx->def->prop = (lcsp_props_t)ctx->prop;
/* add this prop def to the stanza */
lws_dll2_add_tail(&ctx->def->list, &ctx->stz->defs);
ctx->u.f.arg = 1;
ctx->npos = 0;
ctx->cssval_state = 0;
//lwsl_notice("%s: minilex prop match %d\n", __func__, ctx->prop);
break;
}
break;
case LCSPS_CCOM_S1:
if (c == '*') {
ctx->state = LCSPS_CCOM;
break;
}
ctx->state = ctx->state_css_comm;
break;
case LCSPS_CSS_OUTER_TAG1:
/*
* We could see <!-- or perhaps </script> if we are
* inside a <script> section
*/
if (c == '!' && ctx->u.f.first) {
ctx->state = LCSPS_SCOMMENT1;
ctx->u.f.first = 0;
break;
}
if (ctx->state_css_comm == LCSPS_CSS_OUTER &&
c == '/' && ctx->u.f.first) {
finish_css:
r = ctx->await_css_done;
// lwsl_warn("leaving css for tag");
ctx->u.s = 0;
ctx->tag = NULL;
ctx->tag_len = 0;
ctx->npos = 0;
ctx->state = LHPS_TAG;
ctx->await_css_done = 0;
ctx->finish_css = 0;
if (r)
return LWS_SRET_AWAIT_RETRY;
ctx->u.f.closing = 1;
break;
}
if (hspace(c))
break;
break;
case LCSPS_CSS_NAMES:
break;
case LCSPS_CSS_DEF_NAME:
break;
case LCSPS_CSS_DEF_VALUE:
break;
case LCSPS_SCOMMENT1:
if (c == '-') {
ctx->state = LCSPS_SCOMMENT2;
break;
}
/* we saw <! and then not - */
ctx->state = ctx->state_css_comm;
break;
case LCSPS_SCOMMENT2:
if (c == '-') {
ctx->state = LCSPS_COMMENT;
break;
}
/* we saw <!- and then not - */
ctx->state = ctx->state_css_comm;
break;
case LCSPS_CCOM:
/* fallthru */
case LCSPS_COMMENT:
/* sanity */
if (ctx->npos >= LHP_STRING_CHUNK - 4) {
ps->cb(ctx, LHPCB_COMMENT);
ctx->npos = 0;
}
if (ctx->state == LCSPS_COMMENT && c == '-') {
ctx->state = LCSPS_ECOMMENT1;
break;
}
if (ctx->state == LCSPS_CCOM && c == '*') {
ctx->state = LCSPS_CCOM_E1;
break;
}
/* collect the comment */
ctx->buf[ctx->npos++] = (char)c;
/* sanity */
if (ctx->npos >= LHP_STRING_CHUNK - 4) {
ps->cb(ctx, LHPCB_COMMENT);
ctx->npos = 0;
}
break;
case LCSPS_CCOM_E1:
if (c == '/') {
if (ctx->npos) {
ps->cb(ctx, LHPCB_COMMENT);
ctx->npos = 0;
}
ctx->state = ctx->state_css_comm;
break;
}
ctx->state = LCSPS_CCOM;
break;
case LCSPS_ECOMMENT1:
if (c == '-') {
ctx->state = LCSPS_ECOMMENT2;
break;
}
ctx->buf[ctx->npos++] = '-';
ctx->buf[ctx->npos++] = (char)c;
ctx->state = LCSPS_COMMENT;
break;
case LCSPS_ECOMMENT2:
if (c == '>') {
if (ctx->npos) {
ps->cb(ctx, LHPCB_COMMENT);
ctx->npos = 0;
}
ctx->state = ctx->state_css_comm;
break;
}
ctx->buf[ctx->npos++] = '-';
ctx->buf[ctx->npos++] = '-';
ctx->buf[ctx->npos++] = (char)c;
ctx->state = LCSPS_COMMENT;
break;
}
if (!*len && ctx->is_css && ctx->await_css_done && ctx->finish_css)
goto finish_css;
}
if (!ctx->u.f.default_css && ctx->flags & LHP_FLAG_DOCUMENT_END) {
/*
* if we're holding on to anything in case more comes, no more
* is coming and we should flush it.
*/
if (ctx->state == LHPS_OUTER && ctx->npos) {
if (ctx->in_body && (ctx->npos != 1 || ctx->buf[0] != ' '))
ps->cb(ctx, LHPCB_CONTENT);
ctx->npos = 0;
}
ps->cb(ctx, LHPCB_COMPLETE);
return LWS_SRET_NO_FURTHER_OUT;
}
return LWS_SRET_WANT_INPUT;
oom:
lwsl_err("%s: OOM\n", __func__);
ps->cb(ctx, LHPCB_FAILED);
return LWS_SRET_FATAL;
}
/*
* Query the css cascade active at this html parsing point for a list of active
* css attributes belonging to a particular property, accounting for cascading
* overriding inside the list.
*/
const lcsp_atr_t *
lws_css_cascade_get_prop_atr(lhp_ctx_t *ctx, lcsp_props_t prop)
{
lcsp_atr_ptr_t *ap;
lws_dll2_owner_clear(&ctx->active_atr);
lwsac_free(&ctx->propatrac);
/*
* Let's go through the active stanzas looking for defs that relate to
* the property we care about
*/
lws_start_foreach_dll(struct lws_dll2 *, q, ctx->active_stanzas.head) {
lcsp_stanza_ptr_t *pstz = lws_container_of(q, lcsp_stanza_ptr_t,
list);
/* each def entry in the stanza in turn */
lws_start_foreach_dll(struct lws_dll2 *, p, pstz->stz->defs.head) {
lcsp_defs_t *def = lws_container_of(p, lcsp_defs_t, list);
if (def->prop == prop) {
lws_start_foreach_dll(struct lws_dll2 *, z,
def->atrs.head) {
lcsp_atr_ptr_t *patr = lwsac_use_zero(
&ctx->propatrac,
sizeof(*patr),
LHP_AC_GRANULE);
if (!patr)
return NULL;
patr->atr = lws_container_of(z,
lcsp_atr_t, list);
lws_dll2_add_tail(&patr->list,
&ctx->active_atr);
} lws_end_foreach_dll(z);
}
} lws_end_foreach_dll(p);
} lws_end_foreach_dll(q);
if (!ctx->active_atr.count)
return NULL;
ap = lws_container_of(ctx->active_atr.tail, lcsp_atr_ptr_t, list);
return ap->atr;
}
lhp_pstack_t *
lws_css_get_parent_block(lhp_ctx_t *ctx, lhp_pstack_t *ps)
{
do {
if (!ps->list.prev)
return NULL;
ps = lws_container_of(ps->list.prev, lhp_pstack_t, list);
if (ps->dlo)
return ps;
} while(1);
}
const char *
lws_css_pstack_name(lhp_pstack_t *ps)
{
lhp_atr_t *a;
if (!ps)
return "(null ps)";
if (!ps->atr.head)
return "no-name";
a = lws_container_of(ps->atr.head, lhp_atr_t, list);
return (const char *)&a[1];
}
/*
* Some properties have an impied affinity for an axis, eg, left: references
* the parent width if it has a % expression
*/
int
lhp_prop_axis(const lcsp_atr_t *a)
{
const lcsp_defs_t *d = lws_container_of(a->list.owner, lcsp_defs_t, atrs);
switch (d->prop) {
/* referenced to height */
case LCSP_PROP_BORDER_TOP_WIDTH:
case LCSP_PROP_BORDER_BOTTOM_WIDTH:
case LCSP_PROP_HEIGHT:
case LCSP_PROP_TOP:
case LCSP_PROP_BOTTOM:
case LCSP_PROP_MARGIN_TOP:
case LCSP_PROP_MARGIN_BOTTOM:
case LCSP_PROP_PADDING_TOP:
case LCSP_PROP_PADDING_BOTTOM:
case LCSP_PROP_MAX_HEIGHT:
case LCSP_PROP_MIN_HEIGHT:
//lwsl_notice("%s: %d: LWS_LHPREF_HEIGHT\n", __func__, d->prop);
return LWS_LHPREF_HEIGHT;
/* referenced to width */
case LCSP_PROP_BORDER_LEFT_WIDTH:
case LCSP_PROP_BORDER_RIGHT_WIDTH:
case LCSP_PROP_WHITE_SPACE:
case LCSP_PROP_WIDTH:
case LCSP_PROP_LEFT:
case LCSP_PROP_RIGHT:
case LCSP_PROP_MARGIN_LEFT:
case LCSP_PROP_MARGIN_RIGHT:
case LCSP_PROP_PADDING_LEFT:
case LCSP_PROP_PADDING_RIGHT:
case LCSP_PROP_MAX_WIDTH:
case LCSP_PROP_MIN_WIDTH:
//lwsl_notice("%s: %d: LWS_LHPREF_WIDTH\n", __func__, d->prop);
return LWS_LHPREF_WIDTH;
default:
//lwsl_notice("%s: %d: LWS_LHPREF_NONE\n", __func__, d->prop);
return LWS_LHPREF_NONE;
}
}