Hi guys,
I am using tinyMCE editor. In my textarea, I want to let user type some html like <table></table> and store that in db just like that, <table></table>.
However, what I am getting is:
&lt;table&gt;&lt;/table&gt;
Now, if I go to the edit form for the item that contains that html piece and I update it, what I get in my database is <table></table>.
Can someone please explain how I can store <table> as <table> in one single step?

Dani AI

Generated

Good summary of the symptom and the cause: TinyMCE (and many editors) will sometimes serialize or entity‑encode angle brackets when the editor is initialized from a textarea or when content passes through an intermediate HTML context; that explains the “encoded on first submit, normal on second” behavior showed. ’s suggestion to decode before server‑side processing is on the right track, and ’s goal — a server‑side whitelist — is the safest way to go.

Suggested, practical workflow to avoid double‑encoding and keep security tight:

  • Treat the editor as the UI only. On submit send the editor’s HTML (the editor API or a hidden input synced on submit), not a pre‑escaped textarea value. Provide a “Source/Code” view for users who must type raw tags.
  • On the server, first decode any HTML entities (so the sanitizer sees real angle brackets), then run a trusted sanitizer configured with your whitelist (allow the specific table/tbody/tr/td/th attributes you need, but block inline event handlers and javascript: URIs). Store the sanitized HTML (raw, not entity‑escaped) in the DB.
  • When you render the item into an edit form, escape the stored HTML for the textarea/view context so the browser shows tags (or load it into the editor’s code view).

Quick troubleshooting tips: log the raw POST body to see whether encoding happens client‑ or server‑side; check your template/ORM for automatic escaping; test with a minimal TinyMCE config and a simple POST to isolate where the conversion occurs. Finally, keep a small test suite of inputs (tables, attributes, attempted XSS) so you can validate the sanitizer’s whitelist reliably.

Recommended Answers

All 7 Replies

Hey cereal,
thanks for your reply. I looked in that link, but I am not sure that solves my issue.
What I am having difficulties with is preventin tinyMCE from encoding < and >. However, if you look here http://www.tinymce.com/wiki.php/Configuration:entities you'll see that < and > will ALWAYS be encoded... even if I use the entity_encoding : "raw" option http://www.tinymce.com/wiki.php/Configuration:entity_encoding

perhaps I will have to encode &gt; to > in some filter or something for my code to work. OR, use tinyMCE plugins/toolbars to insert html tags instead of typing them in.
what do you think?

Hmm, I'm not sure about this. By testing I see what you mean, but I also see that if I resubmit the same form, the allowed tags will be converted again:

<!DOCTYPE html>
<html>
    <head>
        <title>TinyMCE</title>
        <script src="//tinymce.cachefly.net/4.0/tinymce.min.js"></script>
        <script type="text/javascript">
            tinymce.init({
                selector: "textarea",
                valid_elements : "a[href|target=_blank],strong,em,div[align],br"
            });
        </script>
    </head>
    <body>

        <h2>Test</h2>
        <form method="post">
            <textarea name="contents"><?php echo array_key_exists('contents', $_POST) ? $_POST['contents']:''; ?></textarea>
            <input type="submit" name="submit" value="send" />
        </form>

        <?php

        if($_POST)
        {
            echo "<h3>POST:</h3><pre>";
            highlight_string($_POST['contents']);
            echo "</pre>";
        }

        ?>

    </body>
</html>

So the first time outputs:

<p>Hello &lt;strong&gt;World&lt;/strong&gt;</p>

The second time:

<p>Hello <strong>World</strong></p>

As expected. If instead you load the code plugin:

tinymce.init({
            selector: "textarea",
            plugins: "code",
            valid_elements : "a[href|target=_blank],strong,em,div[align],br"
        });

You can submit the code throught the Tools > Code tab and it won't be converted to entities. BUT if for example you submit quote or another not whitelisted tag, it will be removed only after the resubmit, not during the first instance.

For now I stop here, hope it helps a bit to understand the problem. I think the best would be to find a method to disable completely this feature of TinyMCE and use HTMLPurifier or another similar library to clean the input.

hi cereal, thanks for your reply.
I am trying to take control of html purification in server side as I am building a whitelist of tags that I want to allow in. As is right now, the encoding of < and > actually prevents AntiSamy to do its job to remove html xss attacks. Any JS attacks are being thwarted allthough the first submition of data allows javascript tags in, but it wrapps their content in [[CDATA... upon the second resubmition, they are removed completely.

Now, letting code in via code tool of tinymce makes things work fine, but using the textarea that first appears to input html tags causes the issue you documented aboe.

I am trying to see what classes the code tool has different than the first textarea and fix the issue... or maybe, I could add an onSubmit listener to my form, to read the content from my first textarea instead of submitting the encoded content of the textarea (sounds confusing because it is).

what do you think?

What about using htmlspecialchars_decode() in your textarea

Docs

Example:

<textarea rows="" cols="" name=""><?php echo htmlspecialchars_decode($data['content']); ?></textarea>
commented: good suggestion! +13

good suggestion, it could be done when the script receives the POST request, decode the input, filter and send back clean data, at that point there's no need to perform it in the textarea. For example:

<?php

    if($_POST)
    {
        $filter['contents'] = htmlspecialchars_decode($_POST['contents']);

        # clean the input
        require './library/HTMLPurifier.includes.php';
        require './library/HTMLPurifier.autoload.php';
        $config = HTMLPurifier_Config::createDefault();
        $config->set('Core.Encoding', 'UTF-8');
        $config->set('HTML.Allowed', 'p,a[href|target],em,strong,div[align],br');
        $config->set('Attr.AllowedFrameTargets', array('_blank'));
        $purifier = new HTMLPurifier($config);

        $data['contents'] = $purifier->purify($filter['contents']);
    }

?>

<!DOCTYPE html>
<html>
    <head>
        <meta http-equiv="Content-Type" content="text/html; charset=UTF-8" />
        <title>TinyMCE</title>
        <script src="//tinymce.cachefly.net/4.0/tinymce.min.js"></script>
        <script type="text/javascript">
            tinymce.init({
                selector: "textarea",
                plugins: "code",
                valid_elements : "a[href|target=_blank],strong,em,div[align],br"
            });
        </script>
    </head>
    <body>

        <h2>Test</h2>
        <form method="post">
            <textarea name="contents"><?php echo array_key_exists('contents', $_POST) ? $data['contents']:''; ?></textarea>
            <input type="submit" name="submit" value="send" />
        </form>

        <?php

        if($_POST)
        {
            echo "<h3>POST:</h3><pre>";
            highlight_string($data['contents']);
            echo "</pre>";
        }

        ?>

    </body>
</html>

hi guys,
gabrielcastillo's idea sounds good for consistency... this way, on resubmition, my filter will receive the same input as in the first submition, so my app won't behave differently.
so, consistency issues in app behavior are out of the way now. thanks... I did not think of that :)

Be a part of the DaniWeb community

We're a friendly, industry-focused community of developers, IT pros, digital marketers, and technology enthusiasts meeting, networking, learning, and sharing knowledge.