Commit 3d85b87e authored by Jan Köster's avatar Jan Köster Committed by Jan Koester
Browse files

tesst

parent 98122c40
Loading
Loading
Loading
Loading
+90 −0
Changes for editor/src/htmlimport.cpp: 90 added lines, 0 removed lines.
Original line number Diff line number Diff line
@@ -2093,6 +2093,89 @@ void extractGlobalCssFromStylesheet(const libhtmlpp::CSSStyleSheet &sheet,
    }
}

// True for any Unicode codepoint XML 1.0 allows in character data/attribute
// values -- #x9 | #xA | #xD | [#x20-#xD7FF] | [#xE000-#xFFFD] | [#x10000-#x10FFFF].
// Notably excludes the C0 control characters (other than tab/LF/CR) and the
// noncharacters U+FFFE/U+FFFF, which are illegal in XML even as a numeric
// character reference -- there is no way to escape them into a well-formed
// document, only to remove them.
bool isXmlLegalCodepoint(unsigned int cp)
{
    return cp == 0x9 || cp == 0xA || cp == 0xD ||
           (cp >= 0x20 && cp <= 0xD7FF) ||
           (cp >= 0xE000 && cp <= 0xFFFD) ||
           (cp >= 0x10000 && cp <= 0x10FFFF);
}

// Strips codepoints isXmlLegalCodepoint rejects from @p s, decoding as UTF-8
// so a stripped character can't split a multi-byte sequence and corrupt the
// surrounding text; a malformed/truncated UTF-8 byte is dropped on its own
// rather than resyncing further. Needed because real-world fetched content --
// a minified external JS bundle was found doing exactly this -- occasionally
// embeds a raw noncharacter codepoint (e.g. U+FFFE, used as a compact literal
// for a reversed-BOM check) directly in a string literal: valid JS, valid
// UTF-8, but once it lands in an XML attribute (GlobalJs, GlobalCss, ...) it
// makes the whole generated WebEditor document fail to parse back on the
// very next load/publish -- tinyxml2's printer escapes the standard special
// characters but has no reason to know about XML's separate, stricter
// "legal character" rule.
std::string sanitizeXmlText(const std::string &s)
{
    std::string out;
    out.reserve(s.size());
    size_t i = 0, n = s.size();
    while (i < n) {
        unsigned char c = static_cast<unsigned char>(s[i]);
        unsigned int cp;
        size_t len;
        if (c < 0x80) { cp = c; len = 1; }
        else if ((c & 0xE0) == 0xC0 && i + 1 < n) { cp = c & 0x1F; len = 2; }
        else if ((c & 0xF0) == 0xE0 && i + 2 < n) { cp = c & 0x0F; len = 3; }
        else if ((c & 0xF8) == 0xF0 && i + 3 < n) { cp = c & 0x07; len = 4; }
        else { ++i; continue; }

        bool validCont = true;
        for (size_t k = 1; k < len; ++k) {
            unsigned char cc = static_cast<unsigned char>(s[i + k]);
            if ((cc & 0xC0) != 0x80) { validCont = false; break; }
            cp = (cp << 6) | (cc & 0x3F);
        }
        if (!validCont) { ++i; continue; }

        if (isXmlLegalCodepoint(cp)) out.append(s, i, len);
        i += len;
    }
    return out;
}

// Recursively strips XML-illegal codepoints (see sanitizeXmlText) from every
// attribute value and text node under @p el, in place. Run once, on the
// fully-built tree right before printing, rather than at each individual
// SetAttribute/SetText call site: cheap relative to the rest of an import of
// this size, and one guaranteed choke point beats keeping dozens of call
// sites throughout this file each individually safe.
void sanitizeXmlTree(tinyxml2::XMLElement *el)
{
    for (const tinyxml2::XMLAttribute *attr = el->FirstAttribute(); attr; ) {
        const tinyxml2::XMLAttribute *next = attr->Next();
        std::string val = attr->Value();
        std::string clean = sanitizeXmlText(val);
        if (clean != val) el->SetAttribute(attr->Name(), clean.c_str());
        attr = next;
    }
    for (tinyxml2::XMLNode *child = el->FirstChild(); child; ) {
        tinyxml2::XMLNode *next = child->NextSibling();
        if (tinyxml2::XMLElement *childEl = child->ToElement()) {
            sanitizeXmlTree(childEl);
        } else if (tinyxml2::XMLText *textNode = child->ToText()) {
            std::string val = textNode->Value();
            std::string clean = sanitizeXmlText(val);
            if (clean != val) textNode->SetValue(clean.c_str());
        }
        child = next;
    }
}

} // namespace

std::vector<blogi::htmlimport::ImportNode>
@@ -2442,6 +2525,13 @@ bool blogi::htmlimport::convertHtmlToWidgetXml(const std::string &html,
        xmlRoot->SetAttribute("GlobalJs", globalJs.c_str());
    }

    // Last step before printing: strip any XML-illegal codepoints that made
    // it into an attribute or text node (see sanitizeXmlTree's doc comment).
    // Every widget attribute/text value in this tree ultimately traces back
    // to fetched/scraped content, so this is checked once, tree-wide, rather
    // than at each individual SetAttribute/SetText call site above.
    sanitizeXmlTree(xmlRoot);

    tinyxml2::XMLPrinter printer;
    xmlDoc.Print(&printer);
    outXml = printer.CStr();
+20 −0
Changes for tests/htmlimport_smoke.cpp: 20 added lines, 0 removed lines.
Original line number Diff line number Diff line
@@ -390,6 +390,26 @@ int main(){
              "display:none on an Image maps to hidden=\"true\" (previously silently dropped)");
    }

    // --- XML-illegal codepoints in fetched/inline JS don't break the
    // generated document ---
    // A real minified external JS bundle was found embedding a raw U+FFFE
    // (a noncharacter, used as a compact literal for a reversed-BOM check)
    // directly in a string literal -- valid JS, valid UTF-8, but XML 1.0
    // forbids that codepoint even via a numeric character reference, so it
    // made the entire generated WebEditor document fail to parse back on
    // the next load/publish.
    std::cout << "=== XML-illegal codepoint in inline script ===" << std::endl;
    {
        std::string html = "<script>var x = \"\xef\xbf\xbe\";</script><p>hi</p>";
        std::string xml = importXml(html);
        check(xml.find("\xef\xbf\xbe") == std::string::npos,
              "raw U+FFFE from an inline script is stripped from GlobalJs");

        tinyxml2::XMLDocument doc;
        check(doc.Parse(xml.c_str()) == tinyxml2::XML_SUCCESS,
              "output XML is well-formed even when source JS contains an XML-illegal codepoint");
    }

    // --- Summary ---
    std::cout << "\n=== " << passCount << "/" << testCount << " tests passed ===" << std::endl;