Skip to content

Commit 5b9e435

Browse files
author
drh
committed
When a JSON input is a blob, but it looks like valid JSON when cast to text,
then accept it as valid JSON. This replicates a long-standing bug in the behavior of JSON routines, and thus avoids breaking legacy apps. FossilOrigin-Name: 4c2c1b97dce46a279846380c937ac6de5c367927c6843516641eead7ea6db472
1 parent f05bd8b commit 5b9e435

4 files changed

Lines changed: 154 additions & 41 deletions

File tree

manifest

Lines changed: 9 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
1-
C Ensure\sthat\sthe\sxIntegrity\smethods\sof\sfts3\sand\sfts5\swork\son\sread-only\sdatabases.
2-
D 2024-01-23T10:47:04.969
1+
C When\sa\sJSON\sinput\sis\sa\sblob,\sbut\sit\slooks\slike\svalid\sJSON\swhen\scast\sto\stext,\nthen\saccept\sit\sas\svalid\sJSON.\s\sThis\sreplicates\sa\slong-standing\sbug\sin\sthe\nbehavior\sof\sJSON\sroutines,\sand\sthus\savoids\sbreaking\slegacy\sapps.
2+
D 2024-01-23T13:53:45.407
33
F .fossil-settings/empty-dirs dbb81e8fc0401ac46a1491ab34a7f2c7c0452f2f06b54ebb845d024ca8283ef1
44
F .fossil-settings/ignore-glob 35175cdfcf539b2318cb04a9901442804be81cd677d8b889fcc9149c21f239ea
55
F LICENSE.md df5091916dbb40e6e9686186587125e1b2ff51f022cc334e886c19a0e9982724
@@ -697,7 +697,7 @@ F src/hash.h 3340ab6e1d13e725571d7cee6d3e3135f0779a7d8e76a9ce0a85971fa3953c51
697697
F src/hwtime.h f9c2dfb84dce7acf95ce6d289e46f5f9d3d1afd328e53da8f8e9008e3b3caae6
698698
F src/in-operator.md 10cd8f4bcd225a32518407c2fb2484089112fd71
699699
F src/insert.c 3f0a94082d978bbdd33c38fefea15346c6c6bffb70bc645a71dc0f1f87dd3276
700-
F src/json.c fdb6b417e997d9b45ffd817c8c9d955dba11b99fa1199f8d03cb8fc5a9ee0941
700+
F src/json.c 19d96d7cae66e9b78b4ef98203e9fd916e35d20f5c8c85f079b66bd883fc9533
701701
F src/legacy.c d7874bc885906868cd51e6c2156698f2754f02d9eee1bae2d687323c3ca8e5aa
702702
F src/loadext.c 7432c944ff197046d67a1207790a1b13eec4548c85a9457eb0896bb3641dfb36
703703
F src/main.c 438b95162acfa17b7d218f586f5bde11d6ae82bcf030c9611fc537556870ad6b
@@ -1341,6 +1341,7 @@ F test/json103.test 53df87f83a4e5fa0c0a56eb29ff6c94055c6eb919f33316d62161a888011
13411341
F test/json104.test 1b844a70cddcfa2e4cd81a5db0657b2e61e7f00868310f24f56a9ba0114348c1
13421342
F test/json105.test 043838b56e68f3252a0dcf5be1689016f6f3f05056f8dcfcdc9d074f4d932988
13431343
F test/json106.test 1d46a9294e2ced35c7f87cebbcb9626d01abab04f1969d7ded7b6f6a1d9be0f2
1344+
F test/json107.test 59054e815c8f6b67d634d44ace421cf975828fb5651c4460aa66015c8e19d562
13441345
F test/json501.test ab168a12eb6eb14d479f8c1cdae3ac062fd5a4679f17f976e96f1af518408330
13451346
F test/json502.test 84634d3dbb521d2814e43624025b760c6198456c8197bbec6c977c0236648f5b
13461347
F test/jsonb01.test cace70765b36a36aec9a85a41ea65667d3bbf647d4400ddc3ac76f8fe7d94f90
@@ -2157,9 +2158,9 @@ F vsixtest/vsixtest.tcl 6a9a6ab600c25a91a7acc6293828957a386a8a93
21572158
F vsixtest/vsixtest.vcxproj.data 2ed517e100c66dc455b492e1a33350c1b20fbcdc
21582159
F vsixtest/vsixtest.vcxproj.filters 37e51ffedcdb064aad6ff33b6148725226cd608e
21592160
F vsixtest/vsixtest_TemporaryKey.pfx e5b1b036facdb453873e7084e1cae9102ccc67a0
2160-
P 950bf9fe7829864e0abe6d71ca0495f346feb5d7943d76c95e55a6b86ea855da
2161-
Q +b855886c4ccce0745af6957943e77be18949722f09821688725d546d3d79b4fb
2162-
R 542e8b3387a0404a78fe04600e4c511f
2163-
U dan
2164-
Z f4b03554379000a3d3fd150f7e515fb8
2161+
P e79b97369fa740f62f695057d4a2cf8dae48a683982ec879f04a19039c9cb418
2162+
Q +e5dc81d5c7ee97866feb688dfa9b6fc225dabff2b020b9b96b49a8fea5640aec
2163+
R b629d4d7fbfecc274ba1dea1a4bad552
2164+
U drh
2165+
Z 2e50b8df024d35696c0f9db8b6090112
21652166
# Remove this line to create a well-formed Fossil manifest.

manifest.uuid

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1 +1 @@
1-
e79b97369fa740f62f695057d4a2cf8dae48a683982ec879f04a19039c9cb418
1+
4c2c1b97dce46a279846380c937ac6de5c367927c6843516641eead7ea6db472

src/json.c

Lines changed: 58 additions & 32 deletions
Original file line numberDiff line numberDiff line change
@@ -3216,6 +3216,38 @@ static void jsonInsertIntoBlob(
32163216
return;
32173217
}
32183218

3219+
/*
3220+
** If pArg is a blob that seems like a JSONB blob, then initialize
3221+
** p to point to that JSONB and return TRUE. If pArg does not seem like
3222+
** a JSONB blob, then return FALSE;
3223+
**
3224+
** This routine is only called if it is already known that pArg is a
3225+
** blob. The only open question is whether or not the blob appears
3226+
** to be a JSONB blob.
3227+
*/
3228+
static int jsonArgIsJsonb(sqlite3_value *pArg, JsonParse *p){
3229+
u32 n, sz = 0;
3230+
p->aBlob = (u8*)sqlite3_value_blob(pArg);
3231+
p->nBlob = (u32)sqlite3_value_bytes(pArg);
3232+
if( p->nBlob==0 ){
3233+
p->aBlob = 0;
3234+
return 0;
3235+
}
3236+
if( NEVER(p->aBlob==0) ){
3237+
return 0;
3238+
}
3239+
if( (p->aBlob[0] & 0x0f)<=JSONB_OBJECT
3240+
&& (n = jsonbPayloadSize(p, 0, &sz))>0
3241+
&& sz+n==p->nBlob
3242+
&& ((p->aBlob[0] & 0x0f)>JSONB_FALSE || sz==0)
3243+
){
3244+
return 1;
3245+
}
3246+
p->aBlob = 0;
3247+
p->nBlob = 0;
3248+
return 0;
3249+
}
3250+
32193251
/*
32203252
** Generate a JsonParse object, containing valid JSONB in aBlob and nBlob,
32213253
** from the SQL function argument pArg. Return a pointer to the new
@@ -3272,29 +3304,24 @@ static JsonParse *jsonParseFuncArg(
32723304
return p;
32733305
}
32743306
if( eType==SQLITE_BLOB ){
3275-
u32 n, sz = 0;
3276-
p->aBlob = (u8*)sqlite3_value_blob(pArg);
3277-
p->nBlob = (u32)sqlite3_value_bytes(pArg);
3278-
if( p->nBlob==0 ){
3279-
goto json_pfa_malformed;
3280-
}
3281-
if( NEVER(p->aBlob==0) ){
3282-
goto json_pfa_oom;
3283-
}
3284-
if( (p->aBlob[0] & 0x0f)>JSONB_OBJECT ){
3285-
goto json_pfa_malformed;
3286-
}
3287-
n = jsonbPayloadSize(p, 0, &sz);
3288-
if( n==0
3289-
|| sz+n!=p->nBlob
3290-
|| ((p->aBlob[0] & 0x0f)<=JSONB_FALSE && sz>0)
3291-
){
3292-
goto json_pfa_malformed;
3293-
}
3294-
if( (flgs & JSON_EDITABLE)!=0 && jsonBlobMakeEditable(p, 0)==0 ){
3295-
goto json_pfa_oom;
3307+
if( jsonArgIsJsonb(pArg,p) ){
3308+
if( (flgs & JSON_EDITABLE)!=0 && jsonBlobMakeEditable(p, 0)==0 ){
3309+
goto json_pfa_oom;
3310+
}
3311+
return p;
32963312
}
3297-
return p;
3313+
/* If the blob is not valid JSONB, fall through into trying to cast
3314+
** the blob into text which is then interpreted as JSON. (tag-20240123-a)
3315+
**
3316+
** This goes against all historical documentation about how the SQLite
3317+
** JSON functions were suppose to work. From the beginning, blob was
3318+
** reserved for expansion and a blob value should have raised an error.
3319+
** But it did not, due to a bug. And many applications came to depend
3320+
** upon this buggy behavior, espeically when using the CLI and reading
3321+
** JSON text using readfile(), which returns a blob. For this reason
3322+
** we will continue to support the bug moving forward.
3323+
** See for example https://sqlite.org/forum/forumpost/012136abd5292b8d
3324+
*/
32983325
}
32993326
p->zJson = (char*)sqlite3_value_text(pArg);
33003327
p->nJson = sqlite3_value_bytes(pArg);
@@ -4270,12 +4297,12 @@ static void jsonValidFunc(
42704297
return;
42714298
}
42724299
case SQLITE_BLOB: {
4273-
if( (flags & 0x0c)!=0 && jsonFuncArgMightBeBinary(argv[0]) ){
4300+
if( jsonFuncArgMightBeBinary(argv[0]) ){
42744301
if( flags & 0x04 ){
42754302
/* Superficial checking only - accomplished by the
42764303
** jsonFuncArgMightBeBinary() call above. */
42774304
res = 1;
4278-
}else{
4305+
}else if( flags & 0x08 ){
42794306
/* Strict checking. Check by translating BLOB->TEXT->BLOB. If
42804307
** no errors occur, call that a "strict check". */
42814308
JsonParse px;
@@ -4286,8 +4313,11 @@ static void jsonValidFunc(
42864313
iErr = jsonbValidityCheck(&px, 0, px.nBlob, 1);
42874314
res = iErr==0;
42884315
}
4316+
break;
42894317
}
4290-
break;
4318+
/* Fall through into interpreting the input as text. See note
4319+
** above at tag-20240123-a. */
4320+
/* no break */ deliberate_fall_through
42914321
}
42924322
default: {
42934323
JsonParse px;
@@ -5023,13 +5053,9 @@ static int jsonEachFilter(
50235053
memset(&p->sParse, 0, sizeof(p->sParse));
50245054
p->sParse.nJPRef = 1;
50255055
p->sParse.db = p->db;
5026-
if( sqlite3_value_type(argv[0])==SQLITE_BLOB ){
5027-
if( jsonFuncArgMightBeBinary(argv[0]) ){
5028-
p->sParse.nBlob = sqlite3_value_bytes(argv[0]);
5029-
p->sParse.aBlob = (u8*)sqlite3_value_blob(argv[0]);
5030-
}else{
5031-
goto json_each_malformed_input;
5032-
}
5056+
if( jsonFuncArgMightBeBinary(argv[0]) ){
5057+
p->sParse.nBlob = sqlite3_value_bytes(argv[0]);
5058+
p->sParse.aBlob = (u8*)sqlite3_value_blob(argv[0]);
50335059
}else{
50345060
p->sParse.zJson = (char*)sqlite3_value_text(argv[0]);
50355061
p->sParse.nJson = sqlite3_value_bytes(argv[0]);

test/json107.test

Lines changed: 86 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,86 @@
1+
# 2024-01-23
2+
#
3+
# The author disclaims copyright to this source code. In place of
4+
# a legal notice, here is a blessing:
5+
#
6+
# May you do good and not evil.
7+
# May you find forgiveness for yourself and forgive others.
8+
# May you share freely, never taking more than you give.
9+
#
10+
#***********************************************************************
11+
#
12+
# Legacy JSON bug: If the input is a BLOB that when cast into TEXT looks
13+
# like valid JSON, then treat it as valid JSON.
14+
#
15+
# The original intent of the JSON functions was to raise an error on any
16+
# BLOB input. That intent was clearly documented, but the code failed to
17+
# to implement it. Subsequently, many applications began to depend on the
18+
# incorrect behavior, especially apps that used readfile() to read JSON
19+
# content, since readfile() returns a BLOB. So we need to support the
20+
# bug moving forward.
21+
#
22+
# The tests in this fail verify that the original buggy behavior is
23+
# preserved.
24+
#
25+
26+
set testdir [file dirname $argv0]
27+
source $testdir/tester.tcl
28+
set testprefix json107
29+
30+
if {[db one {PRAGMA encoding}]!="UTF-8"} {
31+
# These tests only work for a UTF-8 encoding.
32+
finish_test
33+
return
34+
}
35+
36+
do_execsql_test 1.1 {
37+
SELECT json_valid( CAST('{"a":1}' AS BLOB) );
38+
} 1
39+
do_execsql_test 1.1.1 {
40+
SELECT json_valid( CAST('{"a":1}' AS BLOB), 1);
41+
} 1
42+
do_execsql_test 1.1.2 {
43+
SELECT json_valid( CAST('{"a":1}' AS BLOB), 2);
44+
} 1
45+
do_execsql_test 1.1.4 {
46+
SELECT json_valid( CAST('{"a":1}' AS BLOB), 4);
47+
} 0
48+
do_execsql_test 1.1.8 {
49+
SELECT json_valid( CAST('{"a":1}' AS BLOB), 8);
50+
} 0
51+
52+
do_execsql_test 1.2.1 {
53+
SELECT CAST('{"a":123}' AS blob) -> 'a';
54+
} 123
55+
do_execsql_test 1.2.2 {
56+
SELECT CAST('{"a":123}' AS blob) ->> 'a';
57+
} 123
58+
do_execsql_test 1.2.3 {
59+
SELECT json_extract(CAST('{"a":123}' AS blob), '$.a');
60+
} 123
61+
do_execsql_test 1.3 {
62+
SELECT json_insert(CAST('{"a":123}' AS blob),'$.b',456);
63+
} {{{"a":123,"b":456}}}
64+
do_execsql_test 1.4 {
65+
SELECT json_remove(CAST('{"a":123,"b":456}' AS blob),'$.a');
66+
} {{{"b":456}}}
67+
do_execsql_test 1.5 {
68+
SELECT json_set(CAST('{"a":123,"b":456}' AS blob),'$.a',789);
69+
} {{{"a":789,"b":456}}}
70+
do_execsql_test 1.6 {
71+
SELECT json_replace(CAST('{"a":123,"b":456}' AS blob),'$.a',789);
72+
} {{{"a":789,"b":456}}}
73+
do_execsql_test 1.7 {
74+
SELECT json_type(CAST('{"a":123,"b":456}' AS blob));
75+
} object
76+
do_execsql_test 1.8 {
77+
SELECT json(CAST('{"a":123,"b":456}' AS blob));
78+
} {{{"a":123,"b":456}}}
79+
80+
ifcapable vtab {
81+
do_execsql_test 2.1 {
82+
SELECT key, value FROM json_tree( CAST('{"a":123,"b":456}' AS blob) )
83+
WHERE atom;
84+
} {a 123 b 456}
85+
}
86+
finish_test

0 commit comments

Comments
 (0)