Skip to content

Commit 50e85e5

Browse files
committed
csscolorparser:
- set isValid state flag on success - no heap allocation for NamedColor parsing - check the actual length of the parsed string for hex format
1 parent f0f6c4b commit 50e85e5

File tree

2 files changed

+129
-54
lines changed

2 files changed

+129
-54
lines changed

csscolorparser.cpp

Lines changed: 127 additions & 54 deletions
Original file line numberDiff line numberDiff line change
@@ -203,27 +203,34 @@ float clamp_css_float(T f) { // Clamp to float 0.0 .. 1.0.
203203
return f < 0 ? 0 : f > 1 ? 1 : f;
204204
}
205205

206-
float parseFloat(const char* str) {
207-
return strtof(str, nullptr);
206+
float parse_float(const std::string& str) {
207+
return strtof(str.c_str(), nullptr);
208208
}
209209

210-
int64_t parseInt(const char* str, uint8_t base = 10) {
211-
return strtoll(str, nullptr, base);
210+
int64_t parse_int(const char* str, int& read, uint8_t base) {
211+
char *pos = nullptr;
212+
int64_t val = strtoll(str, &pos, base);
213+
read = pos - str;
214+
return val;
215+
}
216+
217+
int64_t parse_int(const std::string& str) {
218+
return strtoll(str.c_str(), nullptr, 10);
212219
}
213220

214221
uint8_t parse_css_int(const std::string& str) { // int or percentage.
215222
if (str.length() && str.back() == '%') {
216-
return clamp_css_byte(parseFloat(str.c_str()) / 100.0f * 255.0f);
223+
return clamp_css_byte(parse_float(str) / 100.0f * 255.0f);
217224
} else {
218-
return clamp_css_byte(parseInt(str.c_str()));
225+
return clamp_css_byte(parse_int(str));
219226
}
220227
}
221228

222229
float parse_css_float(const std::string& str) { // float or percentage.
223230
if (str.length() && str.back() == '%') {
224-
return clamp_css_float(parseFloat(str.c_str()) / 100.0f);
231+
return clamp_css_float(parse_float(str) / 100.0f);
225232
} else {
226-
return clamp_css_float(parseFloat(str.c_str()));
233+
return clamp_css_float(parse_float(str));
227234
}
228235
}
229236

@@ -246,11 +253,41 @@ float css_hue_to_rgb(float m1, float m2, float h) {
246253
return m1;
247254
}
248255

256+
bool match(char c, const std::string& text, size_t& pos, size_t end) {
257+
if (end < pos + 1)
258+
return false;
259+
260+
if (c != tolower(text[pos]))
261+
return false;
262+
263+
pos += 1;
264+
return true;
265+
}
249266

267+
bool match_prefix(const std::string& prefix, const std::string& text, size_t& pos, size_t end) {
268+
size_t length = prefix.size();
269+
if (end < length + pos)
270+
return false;
250271

251-
std::vector<std::string> split(const std::string& s, char delim) {
272+
for (size_t i = 0; i < length; ++i)
273+
if (prefix[i] != tolower(text[pos+i]))
274+
return false;
275+
276+
pos += length;
277+
return true;
278+
}
279+
280+
void skip_whitespace(const std::string& text, size_t& pos, size_t end){
281+
while((pos < end) && (text[pos] == ' ')) {
282+
pos++;
283+
}
284+
}
285+
286+
std::vector<std::string> split(const std::string& s, char delim, size_t position) {
252287
std::vector<std::string> elems;
253288
std::stringstream ss(s);
289+
ss.seekg(position);
290+
254291
std::string item;
255292
while (std::getline(ss, item, delim)) {
256293
elems.push_back(item);
@@ -259,57 +296,72 @@ std::vector<std::string> split(const std::string& s, char delim) {
259296
}
260297

261298
Color CSSColorParser::parse(const std::string& css_str) {
262-
int length = css_str.length();
299+
bool valid;
300+
return parse(css_str, valid);
301+
}
263302

264-
if (length == 0) {
303+
Color CSSColorParser::parse(const std::string& css_str, bool& valid) {
304+
valid = false;
305+
306+
size_t pos = 0;
307+
size_t end = css_str.length();
308+
309+
skip_whitespace(css_str, pos, end);
310+
if (pos == end) {
265311
return {};
266312
}
267313

268314
// #abc and #abc123 syntax.
269-
if (css_str.front() == '#') {
270-
const char* str = css_str.c_str()+1;
315+
if (match('#', css_str, pos, end)) {
316+
int read = 0;
317+
318+
const char* str = css_str.c_str() + pos;
319+
int64_t iv = parse_int(str, read, 16);
320+
if (iv < 0) {
321+
// Invalid: out of range.
322+
return {};
323+
}
271324

272-
if (length == 4) { // rgb
273-
int64_t iv = parseInt(str, 16); // TODO(deanm): Stricter parsing.
274-
if (!(iv >= 0 && iv <= 0xfff)) {
275-
return {};
276-
} else {
325+
pos += read;
326+
skip_whitespace(css_str, pos, end);
327+
if (pos != end) {
328+
// Invalid: contains trailing chars.
329+
return {};
330+
}
331+
332+
if (read == 3) { // rgb
333+
if (iv <= 0xfff) {
334+
valid = true;
277335
return {
278336
static_cast<uint8_t>(((iv & 0xf00) >> 4) | ((iv & 0xf00) >> 8)),
279337
static_cast<uint8_t>((iv & 0xf0) | ((iv & 0xf0) >> 4)),
280338
static_cast<uint8_t>((iv & 0xf) | ((iv & 0xf) << 4)),
281339
1
282340
};
283341
}
284-
} else if (length == 7) { // rrggbb
285-
int64_t iv = parseInt(str, 16);
286-
if (!(iv >= 0 && iv <= 0xffffff)) {
287-
return {}; // Covers NaN.
288-
} else {
342+
} else if (read == 6) { // rrggbb
343+
if (iv <= 0xffffff) {
344+
valid = true;
289345
return {
290346
static_cast<uint8_t>((iv & 0xff0000) >> 16),
291347
static_cast<uint8_t>((iv & 0xff00) >> 8),
292348
static_cast<uint8_t>(iv & 0xff),
293349
1
294350
};
295351
}
296-
} else if (length == 5) { // argb
297-
int64_t iv = parseInt(str, 16);
298-
if (!(iv >= 0 && iv <= 0xffff)) {
299-
return {};
300-
} else {
352+
} else if (read == 4) { // argb
353+
if (iv <= 0xffff) {
354+
valid = true;
301355
return {
302356
static_cast<uint8_t>(((iv & 0xf00) >> 4) | ((iv & 0xf00) >> 8)),
303357
static_cast<uint8_t>((iv & 0xf0) | ((iv & 0xf0) >> 4)),
304358
static_cast<uint8_t>((iv & 0xf) | ((iv & 0xf) << 4)),
305359
static_cast<uint8_t>((iv & 0xf000) >> 12) / 255.0f,
306360
};
307361
}
308-
} else if (length == 9) { // aarrggbb
309-
int64_t iv = parseInt(str, 16);
310-
if (!(iv >= 0 && iv <= 0xffffffff)) {
311-
return {}; // Covers NaN.
312-
} else {
362+
} else if (read == 8) { // aarrggbb
363+
if (iv <= 0xffffffff) {
364+
valid = true;
313365
return {
314366
static_cast<uint8_t>((iv & 0xff0000) >> 16),
315367
static_cast<uint8_t>((iv & 0xff00) >> 8),
@@ -318,27 +370,30 @@ Color CSSColorParser::parse(const std::string& css_str) {
318370
};
319371
}
320372
}
373+
321374
return {};
322375
}
323376

324-
// TODO avoid copy
325-
std::string str = css_str;
377+
bool rgb, hsl = false;
378+
rgb = match_prefix("rgb", css_str, pos, end);
379+
if (!rgb) {
380+
hsl = match_prefix("hsl", css_str, pos, end);
381+
}
382+
if (rgb || hsl) {
326383

327-
// Remove all whitespace, not compliant, but should just be more accepting.
328-
str.erase(std::remove(str.begin(), str.end(), ' '), str.end());
384+
bool hasAlpha = match('a', css_str, pos, end);
329385

330-
// Convert to lowercase.
331-
std::transform(str.begin(), str.end(), str.begin(), ::tolower);
386+
if (!match('(', css_str, pos, end)) {
387+
return {};
388+
}
332389

333-
size_t op = str.find_first_of('('), ep = str.find_first_of(')');
334-
if (op != std::string::npos && ep + 1 == str.length()) {
335-
const std::string fname = str.substr(0, op);
336-
const std::vector<std::string> params = split(str.substr(op + 1, ep - (op + 1)), ',');
390+
// TODO: validation
391+
const std::vector<std::string> params = split(css_str, ',', pos);
337392

338393
float alpha = 1.0f;
339394

340-
if (fname == "rgba" || fname == "rgb") {
341-
if (fname == "rgba") {
395+
if (rgb) {
396+
if (hasAlpha) {
342397
if (params.size() != 4) {
343398
return {};
344399
}
@@ -349,15 +404,16 @@ Color CSSColorParser::parse(const std::string& css_str) {
349404
}
350405
}
351406

407+
valid = true;
352408
return {
353409
parse_css_int(params[0]),
354410
parse_css_int(params[1]),
355411
parse_css_int(params[2]),
356412
alpha
357413
};
358414

359-
} else if (fname == "hsla" || fname == "hsl") {
360-
if (fname == "hsla") {
415+
} else if (hsl) {
416+
if (hasAlpha) {
361417
if (params.size() != 4) {
362418
return {};
363419
}
@@ -368,7 +424,7 @@ Color CSSColorParser::parse(const std::string& css_str) {
368424
}
369425
}
370426

371-
float h = parseFloat(params[0].c_str()) / 360.0f;
427+
float h = parse_float(params[0].c_str()) / 360.0f;
372428
while (h < 0.0f) h++;
373429
while (h > 1.0f) h--;
374430

@@ -380,6 +436,7 @@ Color CSSColorParser::parse(const std::string& css_str) {
380436
float m2 = l <= 0.5f ? l * (s + 1.0f) : l + s - l * s;
381437
float m1 = l * 2.0f - m2;
382438

439+
valid = true;
383440
return {
384441
clamp_css_byte(css_hue_to_rgb(m1, m2, h + 1.0f / 3.0f) * 255.0f),
385442
clamp_css_byte(css_hue_to_rgb(m1, m2, h) * 255.0f),
@@ -389,15 +446,31 @@ Color CSSColorParser::parse(const std::string& css_str) {
389446
}
390447
}
391448

392-
NamedColor v{str.c_str(), {}};
449+
size_t length = end - pos;
450+
451+
// Skip if longer than longest named color
452+
if (length > 20)
453+
return {};
454+
455+
// Convert to lowercase.
456+
char cstr[32];
457+
cstr[length] = '\0';
458+
459+
for (size_t i = 0; i < length; ++i) {
460+
cstr[i] = std::tolower(css_str[pos++]);
461+
}
393462

394-
auto end = (namedColors + namedColorCount);
395-
auto it = std::lower_bound(namedColors, end, v,
396-
[](const NamedColor& a, const NamedColor& b){
397-
return std::strcmp(a.name, b.name) < 0; });
463+
// Binary search
464+
auto itEnd = (namedColors + namedColorCount);
465+
auto it = std::lower_bound(namedColors, itEnd, cstr,
466+
[](const NamedColor& a, const char* b) {
467+
return std::strcmp(a.name, b) < 0; });
398468

399-
if (it != end && std::strcmp(it->name, v.name) == 0)
469+
if (it != itEnd && std::strcmp(it->name, cstr) == 0) {
470+
valid = true;
400471
return it->color;
472+
}
401473

474+
// No named color found
402475
return {};
403476
}

csscolorparser.hpp

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -37,6 +37,8 @@ struct Color {
3737
float a = 1.0f;
3838
};
3939

40+
41+
Color parse(const std::string& css_str, bool& isValid);
4042
Color parse(const std::string& css_str);
4143

4244
}

0 commit comments

Comments
 (0)