Question

I'm seeing an issue with some javascript string literals, when encoding this value:

Unencoded

<!-- Start ValueClick Media 300x250 Code for Test Tag -->
<script language="javascript" src="http://media.fastclick.net/w/get.media?sid=38901&m=6&tp=8&d=j&t=n"></script>
<noscript><a href="http://media.fastclick.net/w/click.here?sid=38901&m=6&c=1" target="_blank">
<img src="http://media.fastclick.net/w/get.media?sid=38901&m=6&tp=8&d=s&c=1"width=300 height=250 border=1></a></noscript>
<!-- End ValueClick Media 300x250 Code for Test Tag -->

I end up with this value:

Decoded

"<!-- Start ValueClick Media 300x250 Code for Test Tag -->\r\n<script language=\"javascript\" src=\"http://media.fastclick.net/w/get.media?sid=38901&m=6&tp=8&d=j&t=n\"></script>\r\n<noscript><a href=\"http://media.fastclick.net/w/click.here?sid=38901&m=6&c=1\" target=\"_blank\">\r\n<img src=\"http://media.fastclick.net/w/get.media?sid=38901&m=6&tp=8&d=s&c=1\"width=300 height=250 border=1></a></noscript>\r\n<!-- End ValueClick Media 300x250 Code for Test Tag -->"

which when used as a javascript literal in some javascript code, Firefox complains that it's unterminated - but I can't see why myself.

Oddly enough if I remove the "</script>" closing tag from the above html, the encoded version works correctly, as below:

Unecoded

<!-- Start ValueClick Media 300x250 Code for Test Tag -->
<script language="javascript" src="http://media.fastclick.net/w/get.media?sid=38901&m=6&tp=8&d=j&t=n">
<noscript><a href="http://media.fastclick.net/w/click.here?sid=38901&m=6&c=1" target="_blank">
<img src="http://media.fastclick.net/w/get.media?sid=38901&m=6&tp=8&d=s&c=1"width=300 height=250 border=1></a></noscript>
<!-- End ValueClick Media 300x250 Code for Test Tag -->

Encoded

"<!-- Start ValueClick Media 300x250 Code for Test Tag -->\r\n<script language=\"javascript\" src=\"http://media.fastclick.net/w/get.media?sid=38901&m=6&tp=8&d=j&t=n\">\r\n<noscript><a href=\"http://media.fastclick.net/w/click.here?sid=38901&m=6&c=1\" target=\"_blank\">\r\n<img src=\"http://media.fastclick.net/w/get.media?sid=38901&m=6&tp=8&d=s&c=1\"width=300 height=250 border=1></a></noscript>\r\n<!-- End ValueClick Media 300x250 Code for Test Tag -->"

This encoded value works...

Anyone know what I'm missing?

Update

Seems rather obvious now, I blame lack of sleep, in this case the application was relying on an older release of JSON.Net for encoding the javascript - so I worked around the issue by introducing a new JsonConverter for strings, that dealt with escaping closing tags on a second pass after the JavaScript escaping had been applied.

public class EscapeTagsStringConverter : JsonConverter
{
    public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer)
    {
        if (value == null)
        {
            writer.WriteNull();
            return;
        }

        string escapedValue = ToEscapedJavaScriptString(value.ToString(), '"').Replace("</", "<\\/");

        writer.WriteRawValue("\"" + escapedValue + "\"");
    }

    public override object ReadJson(JsonReader reader, Type objectType, JsonSerializer serializer)
    {
        return reader.Value.ToString();
    }

    public override bool CanConvert(Type objectType)
    {
        return (objectType == typeof (string));
    }

    public static char IntToHex(int n)
    {
        if (n <= 9)
        {
            return (char)(n + 48);
        }
        return (char)((n - 10) + 97);
    }

    public static void WriteCharAsUnicode(TextWriter writer, char c)
    {
        char h1 = IntToHex((c >> 12) & '\x000f');
        char h2 = IntToHex((c >> 8) & '\x000f');
        char h3 = IntToHex((c >> 4) & '\x000f');
        char h4 = IntToHex(c & '\x000f');

        writer.Write('\\');
        writer.Write('u');
        writer.Write(h1);
        writer.Write(h2);
        writer.Write(h3);
        writer.Write(h4);
    }

    public static void WriteEscapedJavaScriptChar(TextWriter writer, char c, char delimiter)
    {
        switch (c)
        {
            case '\t':
                writer.Write(@"\t");
                break;
            case '\n':
                writer.Write(@"\n");
                break;
            case '\r':
                writer.Write(@"\r");
                break;
            case '\f':
                writer.Write(@"\f");
                break;
            case '\b':
                writer.Write(@"\b");
                break;
            case '\\':
                writer.Write(@"\\");
                break;
            case '\'':
                writer.Write((delimiter == '\'') ? @"\'" : @"'");
                break;
            case '"':
                writer.Write((delimiter == '"') ? "\\\"" : @"""");
                break;
            default:
                if (c > '\u001f')
                    writer.Write(c);
                else
                    WriteCharAsUnicode(writer, c);
                break;
        }
    }

    public void WriteEscapedJavaScriptString(TextWriter writer, string value, char delimiter)
    {
        if (value != null)
        {
            for (int i = 0; i < value.Length; i++)
            {
                WriteEscapedJavaScriptChar(writer, value[i], delimiter);
            }
        }
    }

    public string ToEscapedJavaScriptString(string value)
    {
        return ToEscapedJavaScriptString(value, '"');
    }

    public string ToEscapedJavaScriptString(string value, char delimiter)
    {
        using (StringWriter w = CreateStringWriter(GetLength(value) ?? 16))
        {
            WriteEscapedJavaScriptString(w, value, delimiter);
            return w.ToString();
        }
    }

    public static StringWriter CreateStringWriter(int capacity)
    {
        StringBuilder sb = new StringBuilder(capacity);
        StringWriter sw = new StringWriter(sb, CultureInfo.InvariantCulture);

        return sw;
    }

    public static int? GetLength(string value)
    {
        if (value == null)
            return null;
        return value.Length;
    }
}
Was it helpful?

Solution

Well, yeah, if you have:

<script>
    var s= '</script>';
</script>

How is the browser supposed to know that the first </script> isn't a real end of the script element? Every browser, not just Firefox, will read that as:

<script>
    var s= '   // uh-oh! string literal left open!
</script>';    // script element closed. Then some trailing text content
</script>      // close-tag for a script that isn't open, ignore

To avoid a premature end to a string literal containing the </ (ETAGO) sequence, you must escape it in some way. You could say '<\/script>', or '\x3C/script>' or even '<'+'/script>' (that one is popular, though I find it quite inelegant).

OTHER TIPS

the decoded value doesn't throw an error in chrome or ff 3.6.10 What ff version are you using?

Licensed under: CC-BY-SA with attribution
Not affiliated with StackOverflow
scroll top