/* * 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 = lws_container_of(ps->list.owner, lhp_ctx_t, stack); const lws_display_font_t *f = ps->font; lws_fx_t t1, t2, t3; int ref; if (!a) return NULL; 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 0–9, a–z, and A–Z */ 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 † * but not supported yet. */ ctx->state = LHPS_OUTER; break; case LHPS_AMPHASH: /* * This is either decimal or hex unicode like * Ӓ or ﰖ */ 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; } }