Skip to content

Loading…

Fixed bug #64874 (json_decode handles whitespace and case-sensitivity incorrectly) #436

Closed
wants to merge 1 commit into from

2 participants

@TazeTSchnitzel

This is a pull request for the PHP-5.4 branch but it should also be applied to 5.5 and master.

https://bugs.php.net/bug.php?id=64874

There is a small possible backwards-compatibility issue here: Previously, json_decode('tRUe'), and other capitalisation variants of true, false or null were accepted. However, the RFC states only the lowercase variants should be supported, and this pull request fixes that (as well as fixing the whitespace issue). Hence, once this bug is fixed, code that relied on incorrect capitalisations of these constants would break. That being said, I seriously doubt there is any that relies on it at all. json_encode does not output bad JSON like that, nor do any serialisers that I know of) and this issue will only occur when you have a top-level constant, i.e. "[tRue]" didn't work anyway, the only thing you could get away with was "tRue". Furthermore, the page on wiki.php.net states, albeit incorrectly, that it implements JSON (strictly speaking, it's a superse). Anyone who read the spec would not expect to rely on such incorrect capitalised constants.

@remicollet

Perhaps if would have be a better idea to split this request.

I think skipping space is a good idea and won't affect much current behavior (just more RFC compliant and less strict). So could be acceptable for stable version (5.4 or 5.5)

Forcing literal to lowercase only (even if RFC compliant) is a bit more problematic and should probably go to a new major version (5.6 ?).

P.S. Notice jsonc extension already ignores not significant spaces, and have the same problem with uppercase literal.

@TazeTSchnitzel

Well, I'd be highly surprised if anyone is reliant on deserialising wrongly-capitalised JSON texts consisting only of a constant. "true", "false" and "null" but wrongly capitalised are the only json_decode inputs which could trip up. "[true]", for example, would not. There are no JSON serialisers which output that. In the case that, somehow, they are reliant on them, it would be trivial to do a case-insensitive comparison theirself. So I don't think this bug fix is likely to cause anyone anywhere a problem.

@TazeTSchnitzel

Splitting request.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Commits on Sep 10, 2013
  1. @TazeTSchnitzel
This page is out of date. Refresh to see the latest.
Showing with 99 additions and 8 deletions.
  1. +22 −8 ext/json/json.c
  2. +77 −0 ext/json/tests/bug64874.phpt
View
30 ext/json/json.c
@@ -684,21 +684,35 @@ PHP_JSON_API void php_json_decode_ex(zval *return_value, char *str, int str_len,
double d;
int type, overflow_info;
long p;
+ char *trim = str;
+ int trim_len = str_len;
+
+ /* Increment trimmed string pointer to strip leading whitespace */
+ /* JSON RFC says to consider as whitespace: space, tab, LF or CR */
+ while (trim_len && (*trim == ' ' || *trim == '\t' || *trim == '\n' || *trim == '\r')) {
+ trim++;
+ trim_len--;
+ }
+
+ /* Decrement trimmed string length to strip trailing whitespace */
+ while (trim_len && (trim[trim_len - 1] == ' ' || trim[trim_len - 1] == '\t' || trim[trim_len - 1] == '\n' || trim[trim_len - 1] == '\r')) {
+ trim_len--;
+ }
RETVAL_NULL();
- if (str_len == 4) {
- if (!strcasecmp(str, "null")) {
+ if (trim_len == 4) {
+ if (!strncmp(trim, "null", trim_len)) {
/* We need to explicitly clear the error because its an actual NULL and not an error */
jp->error_code = PHP_JSON_ERROR_NONE;
RETVAL_NULL();
- } else if (!strcasecmp(str, "true")) {
+ } else if (!strncmp(trim, "true", trim_len)) {
RETVAL_BOOL(1);
}
- } else if (str_len == 5 && !strcasecmp(str, "false")) {
+ } else if (trim_len == 5 && !strncmp(trim, "false", trim_len)) {
RETVAL_BOOL(0);
}
- if ((type = is_numeric_string_ex(str, str_len, &p, &d, 0, &overflow_info)) != 0) {
+ if ((type = is_numeric_string_ex(trim, trim_len, &p, &d, 0, &overflow_info)) != 0) {
if (type == IS_LONG) {
RETVAL_LONG(p);
} else if (type == IS_DOUBLE) {
@@ -711,10 +725,10 @@ PHP_JSON_API void php_json_decode_ex(zval *return_value, char *str, int str_len,
int i;
zend_bool is_float = 0;
- for (i = (str[0] == '-' ? 1 : 0); i < str_len; i++) {
+ for (i = (trim[0] == '-' ? 1 : 0); i < trim_len; i++) {
/* Not using isdigit() because it's locale specific,
* but we expect JSON input to always be UTF-8. */
- if (str[i] < '0' || str[i] > '9') {
+ if (trim[i] < '0' || trim[i] > '9') {
is_float = 1;
break;
}
@@ -723,7 +737,7 @@ PHP_JSON_API void php_json_decode_ex(zval *return_value, char *str, int str_len,
if (is_float) {
RETVAL_DOUBLE(d);
} else {
- RETVAL_STRINGL(str, str_len, 1);
+ RETVAL_STRINGL(trim, trim_len, 1);
}
} else {
RETVAL_DOUBLE(d);
View
77 ext/json/tests/bug64874.phpt
@@ -0,0 +1,77 @@
+--TEST--
+Bug #64874 (json_decode does not properly decode with options parameter)
+--SKIPIF--
+<?php if (!extension_loaded("json")) print "skip"; ?>
+--FILE--
+<?php
+function decode($json) {
+ var_dump(json_decode($json));
+ var_dump(json_last_error() !== 0);
+ echo "\n";
+}
+
+// Only lowercase should work
+decode('true');
+decode('True');
+decode('false');
+decode('False');
+decode('null');
+decode('Null');
+
+// Leading whitespace should be ignored
+decode(" true");
+decode("\ttrue");
+decode("\ntrue");
+decode("\rtrue");
+
+// So should trailing whitespace
+decode("true ");
+decode("true\t");
+decode("true\n");
+decode("true\r");
+
+echo "Done\n";
+--EXPECT--
+bool(true)
+bool(false)
+
+NULL
+bool(true)
+
+bool(false)
+bool(false)
+
+NULL
+bool(true)
+
+NULL
+bool(false)
+
+NULL
+bool(true)
+
+bool(true)
+bool(false)
+
+bool(true)
+bool(false)
+
+bool(true)
+bool(false)
+
+bool(true)
+bool(false)
+
+bool(true)
+bool(false)
+
+bool(true)
+bool(false)
+
+bool(true)
+bool(false)
+
+bool(true)
+bool(false)
+
+Done
Something went wrong with that request. Please try again.