Skip to content

Commit cfdf0d5

Browse files
committed
JSON: implement proper string escaping according to JSON spec (+ WIP)
Add a DecoderResult::addExtra convenience helper. Also experiment with `BarcodeExtra::...` string constants for JSON keys.
1 parent ab1bd81 commit cfdf0d5

File tree

10 files changed

+171
-39
lines changed

10 files changed

+171
-39
lines changed

core/src/Barcode.h

Lines changed: 13 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -20,7 +20,17 @@
2020
#include <memory>
2121
namespace ZXing {
2222
class BitMatrix;
23-
}
23+
24+
namespace BarcodeExtra {
25+
#define ZX_EXTRA(NAME) static constexpr auto NAME = #NAME
26+
ZX_EXTRA(DataMask); // QRCodes
27+
ZX_EXTRA(Version);
28+
ZX_EXTRA(EanAddOn); // EAN/UPC
29+
ZX_EXTRA(UPCE);
30+
#undef ZX_EXTRA
31+
} // namespace BarcodeExtra
32+
33+
} // namespace ZXing
2434

2535
extern "C" struct zint_symbol;
2636
struct zint_symbol_deleter
@@ -180,6 +190,8 @@ class Result
180190
void zint(unique_zint_symbol&& z);
181191
zint_symbol* zint() const { return _zint.get(); }
182192
Result&& addExtra(std::string&& json) { _json += std::move(json); return std::move(*this); }
193+
// template<typename T>
194+
// Result&& addExtra(std::string_view key, T val, T ignore = {}) { _json += JsonProp(key, val, ignore); return std::move(*this); }
183195
std::string extra(std::string_view key = "") const;
184196
#endif
185197

core/src/DecoderResult.h

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@
88

99
#include "Content.h"
1010
#include "Error.h"
11+
#include "JSON.h"
1112
#include "StructuredAppend.h"
1213

1314
#include <memory>
@@ -81,8 +82,11 @@ class DecoderResult
8182
ZX_PROPERTY(bool, readerInit, setReaderInit)
8283
ZX_PROPERTY(std::string, json, setJson)
8384
ZX_PROPERTY(std::shared_ptr<CustomData>, customData, setCustomData)
84-
8585
#undef ZX_PROPERTY
86+
87+
template<typename T>
88+
DecoderResult&& addExtra(std::string_view key, T val, T ignore = {}) { _json += JsonProp(key, val, ignore); return std::move(*this); }
89+
8690
};
8791

8892
} // ZXing

core/src/JSON.cpp

Lines changed: 81 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -22,7 +22,8 @@
2222
#include "ctre.hpp"
2323
#pragma GCC diagnostic error "-Wundef"
2424

25-
static constexpr auto PATTERN = ctll::fixed_string{R"(["']?([[:alpha:]][[:alnum:]]*)["']?\s*(?:[:]\s*["']?([[:alnum:]]+)["']?)?(?:,|\}|$))"};
25+
static constexpr auto PATTERN =
26+
ctll::fixed_string{R"(["']?([[:alpha:]][[:alnum:]]*)["']?\s*(?:[:]\s*["']?([[:alnum:]]+)["']?)?(?:,|\}|$))"};
2627
#else
2728
#include "ZXAlgorithms.h"
2829
#endif
@@ -76,4 +77,83 @@ std::string_view JsonGetStr(std::string_view json, std::string_view key)
7677
#endif
7778
}
7879

80+
std::string JsonEscapeStr(std::string_view str)
81+
{
82+
std::string res;
83+
res.reserve(str.size() + 10);
84+
85+
for (unsigned char c : str) {
86+
switch (c) {
87+
case '\"': res += "\\\""; break;
88+
case '\\': res += "\\\\"; break;
89+
case '\b': res += "\\b"; break;
90+
case '\f': res += "\\f"; break;
91+
case '\n': res += "\\n"; break;
92+
case '\r': res += "\\r"; break;
93+
case '\t': res += "\\t"; break;
94+
default:
95+
if (c <= 0x1F) {
96+
char buf[7];
97+
std::snprintf(buf, sizeof(buf), "\\u%04X", c);
98+
res += buf;
99+
} else {
100+
res += c;
101+
}
102+
break;
103+
}
104+
}
105+
106+
return res;
107+
}
108+
109+
std::string JsonUnEscapeStr(std::string_view str)
110+
{
111+
std::string res;
112+
res.reserve(str.size());
113+
114+
for (size_t i = 0; i < str.size(); ++i) {
115+
char c = str[i];
116+
if (c == '\\') {
117+
if (++i >= str.size())
118+
throw std::runtime_error("Invalid escape sequence");
119+
120+
char esc = str[i];
121+
switch (esc) {
122+
case '"': res += '\"'; break;
123+
case '\\': res += '\\'; break;
124+
case '/': res += '/'; break;
125+
case 'b': res += '\b'; break;
126+
case 'f': res += '\f'; break;
127+
case 'n': res += '\n'; break;
128+
case 'r': res += '\r'; break;
129+
case 't': res += '\t'; break;
130+
case 'u': {
131+
if (i + 4 >= str.size())
132+
throw std::runtime_error("Incomplete \\u escape");
133+
134+
uint32_t code = 0;
135+
auto first = str.data() + i + 1;
136+
auto last = first + 4;
137+
138+
auto [ptr, ec] = std::from_chars(first, last, code, 16);
139+
if (ec != std::errc() || ptr != last)
140+
throw std::runtime_error("Failed to parse hex code");
141+
142+
if (code > 0x1F)
143+
throw std::runtime_error("Unexpected code point in \\u escape");
144+
145+
res += static_cast<char>(code);
146+
i += 4;
147+
break;
148+
}
149+
default: throw std::runtime_error("Unknown escape sequence");
150+
}
151+
} else {
152+
res += c;
153+
}
154+
}
155+
156+
return res;
157+
}
158+
79159
} // ZXing

core/src/JSON.h

Lines changed: 13 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -14,22 +14,25 @@
1414

1515
namespace ZXing {
1616

17-
inline std::string JsonKeyValue(std::string_view key, std::string_view val)
18-
{
19-
return val.empty() ? std::string() : StrCat("\"", key, "\":", val, ',');
20-
}
17+
std::string JsonEscapeStr(std::string_view str);
18+
std::string JsonUnEscapeStr(std::string_view str);
2119

2220
template<typename T>
23-
inline std::string JsonValue(std::string_view key, T val)
21+
inline std::string JsonProp(std::string_view key, T val, T ignore = {})
2422
{
23+
if (val == ignore)
24+
return {};
25+
26+
#define ZX_JSON_KEY_VAL(...) StrCat("\"", key, "\":", __VA_ARGS__, ',')
2527
if constexpr (std::is_same_v<T, bool>)
26-
return val ? JsonKeyValue(key, "true") : "";
28+
return val ? ZX_JSON_KEY_VAL("true") : "";
2729
else if constexpr (std::is_arithmetic_v<T>)
28-
return JsonKeyValue(key, std::to_string(val));
30+
return ZX_JSON_KEY_VAL(std::to_string(val));
2931
else if constexpr (std::is_convertible_v<T, std::string_view>)
30-
return JsonKeyValue(key, StrCat("\"" , val, "\""));
32+
return ZX_JSON_KEY_VAL("\"" , JsonEscapeStr(val), "\"");
3133
else
3234
static_assert("unsupported JSON value type");
35+
#undef ZX_JSON_KEY_VAL
3336
}
3437

3538
std::string_view JsonGetStr(std::string_view json, std::string_view key);
@@ -45,8 +48,8 @@ inline std::optional<T> JsonGet(std::string_view json, std::string_view key)
4548
return str.empty() || Contains("1tT", str.front()) ? std::optional(true) : std::nullopt;
4649
else if constexpr (std::is_arithmetic_v<T>)
4750
return str.empty() ? std::nullopt : std::optional(FromString<T>(str));
48-
else if constexpr (std::is_same_v<std::string_view, T>)
49-
return str;
51+
else if constexpr (std::is_same_v<std::string, T>)
52+
return JsonUnEscapeStr(str);
5053
else
5154
static_assert("unsupported JSON value type");
5255
}

core/src/datamatrix/DMDecoder.cpp

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -437,7 +437,11 @@ static DecoderResult DoDecode(const BitMatrix& bits)
437437

438438
// Decode the contents of that stream of bytes
439439
return DecodedBitStreamParser::Decode(std::move(resultBytes), version->isDMRE())
440-
.setVersionNumber(version->versionNumber);
440+
.setVersionNumber(version->versionNumber)
441+
#ifdef ZXING_EXPERIMENTAL_API
442+
.addExtra(BarcodeExtra::Version, std::to_string(version->symbolWidth) + 'x' + std::to_string(version->symbolWidth))
443+
#endif
444+
;
441445
}
442446

443447
static BitMatrix FlippedL(const BitMatrix& bits)

core/src/oned/ODMultiUPCEANReader.cpp

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -115,7 +115,6 @@ static bool DecodeDigits(int digitCount, PatternView& next, std::string& txt, in
115115
struct PartialResult
116116
{
117117
std::string txt;
118-
std::string json;
119118
PatternView end;
120119
BarcodeFormat format = BarcodeFormat::None;
121120

@@ -282,8 +281,9 @@ Barcode MultiUPCEANReader::decodePattern(int rowNumber, PatternView& next, std::
282281

283282
// ISO/IEC 15420:2009 (& GS1 General Specifications 5.1.3) states that the content for "]E0" should be 13 digits,
284283
// i.e. converted to EAN-13 if UPC-A/E
284+
std::string upceTxt;
285285
if (res.format == BarcodeFormat::UPCE) {
286-
res.json = JsonValue("UPC-E", res.txt);
286+
upceTxt = res.txt;
287287
res.txt = "0" + UPCEANCommon::ConvertUPCEtoUPCA(res.txt);
288288
}
289289

@@ -319,7 +319,8 @@ Barcode MultiUPCEANReader::decodePattern(int rowNumber, PatternView& next, std::
319319

320320
return Barcode(res.txt, rowNumber, begin.pixelsInFront(), next.pixelsTillEnd(), res.format, symbologyIdentifier, error)
321321
#ifdef ZXING_EXPERIMENTAL_API
322-
.addExtra(std::move(res.json))
322+
.addExtra(JsonProp(BarcodeExtra::UPCE, upceTxt))
323+
.addExtra(JsonProp(BarcodeExtra::EanAddOn, addOnRes.txt))
323324
#endif
324325
;
325326
}

core/src/pdf417/PDFReader.cpp

Lines changed: 11 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,6 @@
1313
#include "BitMatrixCursor.h"
1414
#include "DecoderResult.h"
1515
#include "DetectorResult.h"
16-
#include "JSON.h"
1716
#include "PDFCodewordDecoder.h"
1817
#include "PDFCustomData.h"
1918
#include "PDFDetector.h"
@@ -98,19 +97,19 @@ static Barcodes DoDecode(const BinaryBitmap& image, bool multiple, bool tryRotat
9897
return p;
9998
}
10099
};
101-
auto barcode =
102-
Barcode(std::move(decoderResult), DetectorResult{{}, {point(0), point(2), point(3), point(1)}}, BarcodeFormat::PDF417)
103100
#ifdef ZXING_EXPERIMENTAL_API
104-
.addExtra(JsonValue("Sender", customData->sender))
105-
.addExtra(JsonValue("Addresse", customData->addressee))
106-
.addExtra(JsonValue("FileId", customData->fileId))
107-
.addExtra(JsonValue("FileName", customData->fileName))
108-
.addExtra(customData->fileSize != -1 ? JsonValue("FileSize", customData->fileSize) : "")
109-
.addExtra(customData->timestamp != -1 ? JsonValue("Timestamp", customData->timestamp) : "")
110-
.addExtra(customData->checksum != -1 ? JsonValue("Checksum", customData->checksum) : "")
101+
decoderResult
102+
.addExtra("Sender", customData->sender)
103+
.addExtra("Addressee", customData->addressee)
104+
.addExtra("FileId", customData->fileId)
105+
.addExtra("FileName", customData->fileName)
106+
.addExtra("FileSize", customData->fileSize, -1L)
107+
.addExtra("Timestamp", customData->timestamp, -1L)
108+
.addExtra("Checksum", customData->checksum, -1)
109+
;
111110
#endif
112-
;
113-
res.push_back(std::move(barcode));
111+
res.emplace_back(std::move(decoderResult), DetectorResult{{}, {point(0), point(2), point(3), point(1)}},
112+
BarcodeFormat::PDF417);
114113
if (!multiple)
115114
return res;
116115
}

core/src/qrcode/QRDecoder.cpp

Lines changed: 8 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -6,12 +6,14 @@
66

77
#include "QRDecoder.h"
88

9+
#ifdef ZXING_EXPERIMENTAL_API
10+
#include "Barcode.h"
11+
#endif
912
#include "BitMatrix.h"
1013
#include "BitSource.h"
1114
#include "CharacterSet.h"
1215
#include "DecoderResult.h"
1316
#include "GenericGF.h"
14-
#include "JSON.h"
1517
#include "QRBitMatrixParser.h"
1618
#include "QRCodecMode.h"
1719
#include "QRDataBlock.h"
@@ -367,7 +369,11 @@ DecoderResult Decode(const BitMatrix& bits)
367369
// Decode the contents of that stream of bytes
368370
auto ret = DecodeBitStream(std::move(resultBytes), version, formatInfo.ecLevel)
369371
.setIsMirrored(formatInfo.isMirrored)
370-
.setJson(JsonValue("DataMask", formatInfo.dataMask) + JsonValue("Version", versionStr));
372+
#ifdef ZXING_EXPERIMENTAL_API
373+
.addExtra(BarcodeExtra::DataMask, formatInfo.dataMask, uint8_t(255))
374+
.addExtra(BarcodeExtra::Version, versionStr)
375+
#endif
376+
;
371377
if (error)
372378
ret.setError(error);
373379
return ret;

core/src/qrcode/QRFormatInformation.h

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -28,7 +28,7 @@ class FormatInformation
2828
uint8_t bitsIndex = 255;
2929

3030
bool isMirrored = false;
31-
uint8_t dataMask = 0;
31+
uint8_t dataMask = 255;
3232
uint8_t microVersion = 0;
3333
ErrorCorrectionLevel ecLevel = ErrorCorrectionLevel::Invalid;
3434

test/unit/JSONTest.cpp

Lines changed: 30 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -9,11 +9,14 @@
99

1010
using namespace ZXing;
1111

12-
TEST(JSONTest, Value)
12+
TEST(JSONTest, Prop)
1313
{
14-
EXPECT_EQ(JsonValue("key", "val"), R"("key":"val",)");
15-
EXPECT_EQ(JsonValue("key", true), R"("key":true,)");
16-
EXPECT_EQ(JsonValue("key", 1), R"("key":1,)");
14+
EXPECT_EQ(JsonProp("key", "val"), R"("key":"val",)");
15+
EXPECT_EQ(JsonProp("key", true), R"("key":true,)");
16+
EXPECT_EQ(JsonProp("key", 1), R"("key":1,)");
17+
18+
EXPECT_EQ(JsonProp("key", R"(C:\)"), R"("key":"C:\\",)");
19+
EXPECT_EQ(JsonProp("key", R"("quotes")"), R"("key":"\"quotes\"",)");
1720
}
1821

1922
TEST(JSONTest, GetStr)
@@ -49,8 +52,8 @@ TEST(JSONTest, GetBool)
4952
EXPECT_FALSE(JsonGet<bool>("keys", "key"));
5053
EXPECT_FALSE(JsonGet<bool>("thekey", "key"));
5154

52-
EXPECT_TRUE(JsonGet<bool>("key , other", "key"));
53-
EXPECT_TRUE(JsonGet<bool>("\"key\": \"true\"", "key"));
55+
EXPECT_TRUE(JsonGet<bool>("key , other", "key").value());
56+
EXPECT_TRUE(JsonGet<bool>("\"key\": \"true\"", "key").value());
5457
EXPECT_TRUE(JsonGet<bool>("{\"key\": true}", "key")); // JSON
5558
EXPECT_TRUE(JsonGet<bool>("{'key': True'}", "key")); // Python
5659
}
@@ -62,5 +65,25 @@ TEST(JSONTest, GetInt)
6265

6366
EXPECT_EQ(JsonGet<int>("key:1", "key").value(), 1);
6467
EXPECT_EQ(JsonGet<int>("{\"key\": 2}", "key").value(), 2); // JSON
65-
EXPECT_EQ(JsonGet<int>("{'key': 1}", "key").value(), 1); // Python
68+
EXPECT_EQ(JsonGet<int>("{'key': 1}", "key").value(), 1); // Python
69+
}
70+
71+
TEST(JSONTest, GetString)
72+
{
73+
EXPECT_FALSE(JsonGet<std::string>("key:", "key"));
74+
75+
EXPECT_EQ(JsonGet<std::string>("key:abc", "key").value(), "abc");
76+
EXPECT_EQ(JsonGet<std::string>("{\"key\":\"abc\\n\"}", "key").value(), "abc\n"); // JSON
77+
EXPECT_EQ(JsonGet<std::string>("{'key': 'abc'}", "key").value(), "abc"); // Python
78+
}
79+
80+
TEST(JSONTest, Escaping)
81+
{
82+
EXPECT_EQ("\\u0001", JsonEscapeStr("\x01"));
83+
EXPECT_EQ("\x80", JsonEscapeStr("\x80"));
84+
85+
for (int c = 0; c <= 0xFF; ++c) {
86+
auto str = std::string(1, static_cast<char>(c));
87+
EXPECT_EQ(str, JsonUnEscapeStr(JsonEscapeStr(str)));
88+
}
6689
}

0 commit comments

Comments
 (0)
pFad - Phonifier reborn

Pfad - The Proxy pFad of © 2024 Garber Painting. All rights reserved.

Note: This service is not intended for secure transactions such as banking, social media, email, or purchasing. Use at your own risk. We assume no liability whatsoever for broken pages.


Alternative Proxies:

Alternative Proxy

pFad Proxy

pFad v3 Proxy

pFad v4 Proxy