diff --git a/dom/bindings/BindingUtils.cpp b/dom/bindings/BindingUtils.cpp index e79ee820816..5fd80cd5cec 100644 --- a/dom/bindings/BindingUtils.cpp +++ b/dom/bindings/BindingUtils.cpp @@ -2124,30 +2124,51 @@ ConvertJSValueToByteString(JSContext* cx, JS::Handle v, pval.set(JS::StringValue(s)); // Root the new string. } - size_t length; - const jschar *chars = JS_GetStringCharsZAndLength(cx, s, &length); - if (!chars) { - return false; - } - // Conversion from Javascript string to ByteString is only valid if all - // characters < 256. - for (size_t i = 0; i < length; i++) { - if (chars[i] > 255) { + // characters < 256. This is always the case for Latin1 strings. + size_t length; + if (!JS_StringHasLatin1Chars(s)) { + // ThrowErrorMessage can GC, so we first scan the string for bad chars + // and report the error outside the AutoCheckCannotGC scope. + bool foundBadChar = false; + size_t badCharIndex; + jschar badChar; + { + JS::AutoCheckCannotGC nogc; + const jschar* chars = JS_GetTwoByteStringCharsAndLength(cx, nogc, s, &length); + if (!chars) { + return false; + } + + for (size_t i = 0; i < length; i++) { + if (chars[i] > 255) { + badCharIndex = i; + badChar = chars[i]; + foundBadChar = true; + break; + } + } + } + + if (foundBadChar) { + MOZ_ASSERT(badCharIndex < length); + MOZ_ASSERT(badChar > 255); // The largest unsigned 64 bit number (18,446,744,073,709,551,615) has // 20 digits, plus one more for the null terminator. char index[21]; static_assert(sizeof(size_t) <= 8, "index array too small"); - PR_snprintf(index, sizeof(index), "%d", i); + PR_snprintf(index, sizeof(index), "%d", badCharIndex); // A jschar is 16 bits long. The biggest unsigned 16 bit // number (65,535) has 5 digits, plus one more for the null // terminator. - char badChar[6]; - static_assert(sizeof(jschar) <= 2, "badChar array too small"); - PR_snprintf(badChar, sizeof(badChar), "%d", chars[i]); - ThrowErrorMessage(cx, MSG_INVALID_BYTESTRING, index, badChar); + char badCharArray[6]; + static_assert(sizeof(jschar) <= 2, "badCharArray too small"); + PR_snprintf(badCharArray, sizeof(badCharArray), "%d", badChar); + ThrowErrorMessage(cx, MSG_INVALID_BYTESTRING, index, badCharArray); return false; } + } else { + length = JS_GetStringLength(s); } if (length >= UINT32_MAX) { diff --git a/js/src/jsapi.cpp b/js/src/jsapi.cpp index c95903f45c1..0651eba419e 100644 --- a/js/src/jsapi.cpp +++ b/js/src/jsapi.cpp @@ -5321,6 +5321,12 @@ JS_GetStringLength(JSString *str) return str->length(); } +JS_PUBLIC_API(bool) +JS_StringHasLatin1Chars(JSString *str) +{ + return str->hasLatin1Chars(); +} + JS_PUBLIC_API(const jschar *) JS_GetStringCharsZ(JSContext *cx, JSString *str) { @@ -5359,6 +5365,21 @@ JS_GetStringCharsAndLength(JSContext *cx, JSString *str, size_t *plength) return linear->chars(); } +JS_PUBLIC_API(const jschar *) +JS_GetTwoByteStringCharsAndLength(JSContext *cx, const JS::AutoCheckCannotGC &nogc, JSString *str, + size_t *plength) +{ + JS_ASSERT(plength); + AssertHeapIsIdleOrStringIsFlat(cx, str); + CHECK_REQUEST(cx); + assertSameCompartment(cx, str); + JSLinearString *linear = str->ensureLinear(cx); + if (!linear) + return nullptr; + *plength = linear->length(); + return linear->twoByteChars(nogc); +} + JS_PUBLIC_API(const jschar *) JS_GetInternedStringChars(JSString *str) { diff --git a/js/src/jsapi.h b/js/src/jsapi.h index 4cfc9939e1d..ed213a3803c 100644 --- a/js/src/jsapi.h +++ b/js/src/jsapi.h @@ -4166,9 +4166,17 @@ JS_FileEscapedString(FILE *fp, JSString *str, char quote); extern JS_PUBLIC_API(size_t) JS_GetStringLength(JSString *str); +/* Returns true iff the string's characters are stored as Latin1. */ +extern JS_PUBLIC_API(bool) +JS_StringHasLatin1Chars(JSString *str); + extern JS_PUBLIC_API(const jschar *) JS_GetStringCharsAndLength(JSContext *cx, JSString *str, size_t *length); +extern JS_PUBLIC_API(const jschar *) +JS_GetTwoByteStringCharsAndLength(JSContext *cx, const JS::AutoCheckCannotGC &nogc, JSString *str, + size_t *length); + extern JS_PUBLIC_API(const jschar *) JS_GetInternedStringChars(JSString *str);