blob: a87988768a5d0a8751b269ad3ef15f1dbf2f78cb [file]
// Copyright 2022 The ChromiumOS Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#include "validator.h"
#include <set>
#include <string>
#include <string_view>
#include <utility>
#include <vector>
#include "attribute.h"
#include "errors.h"
#include "frame.h"
namespace ipp {
namespace {
constexpr std::string_view kAllowedCharsInKeyword = "-_.";
constexpr std::string_view kAllowedCharsInUri = ":/?#[]@!$&'()*+,;=-._~%";
constexpr std::string_view kAllowedCharsInUriScheme = "+-.";
constexpr std::string_view kAllowedCharsInNaturalLanguage = "-";
bool IsLowercaseLetter(char c) {
return c >= 'a' && c <= 'z';
}
bool IsUppercaseLetter(char c) {
return c >= 'A' && c <= 'Z';
}
bool IsDigit(char c) {
return c >= '1' && c <= '9';
}
// Helper struct for string validation.
struct StringValidator {
// The input string to validate.
std::string_view value;
// Output set of error codes.
std::set<ValidationCode> codes;
// Validates the string length.
void CheckLength(size_t max_length, bool empty_string_allowed = false) {
if (value.empty()) {
if (!empty_string_allowed) {
codes.insert(ValidationCode::kStringEmpty);
}
return;
}
if (value.size() > max_length) {
codes.insert(ValidationCode::kStringTooLong);
}
}
// Checks if the string starts from lowercase letter. It does nothing if
// the input string is empty.
void CheckFirstLetterIsLowercase() {
if (value.empty() || IsLowercaseLetter(value.front()))
return;
codes.insert(ValidationCode::kStringMustStartLowercaseLetter);
}
// Checks if the input string consists only of letters, digits and
// characters from `allowed_chars`.
void CheckLettersDigits(std::string_view allowed_chars,
bool uppercase_letters_allowed = false) {
for (char c : value) {
if (IsLowercaseLetter(c))
continue;
if (uppercase_letters_allowed && IsUppercaseLetter(c))
continue;
if (IsDigit(c))
continue;
if (allowed_chars.find(c) == std::string_view::npos) {
codes.insert(ValidationCode::kStringInvalidCharacter);
break;
}
}
}
// Checks if the input string consists only of printable characters.
void CheckPrintable(bool uppercase_letters_allowed = false) {
for (char c : value) {
if (c < 0x20 || c > 0x7e) {
codes.insert(ValidationCode::kStringInvalidCharacter);
break;
}
if (!uppercase_letters_allowed && IsUppercaseLetter(c)) {
codes.insert(ValidationCode::kStringInvalidCharacter);
break;
}
}
}
};
// `year` must be > 0.
bool IsLeapYear(uint16_t year) {
if (year % 4)
return false;
// Is divisible by 4.
if (year % 100)
return true;
// Is divisible by 4 and 100.
return (year % 400 == 0);
}
// Validate 'text' value based on:
// * rfc8011, section 5.1.2.
std::set<ValidationCode> validateTextWithoutLanguage(std::string_view value) {
StringValidator validator = {value};
validator.CheckLength(kMaxLengthOfText, /*empty_string_allowed=*/true);
return validator.codes;
}
// Validate 'name' value based on:
// * rfc8011, section 5.1.3.
std::set<ValidationCode> validateNameWithoutLanguage(std::string_view value) {
StringValidator validator = {value};
validator.CheckLength(kMaxLengthOfName, /*empty_string_allowed=*/true);
return validator.codes;
}
// Validate 'keyword' value based on:
// * rfc8011, section 5.1.4.
// * rfc8011 errata
std::set<ValidationCode> validateKeyword(std::string_view value) {
StringValidator validator = {value};
validator.CheckLength(kMaxLengthOfKeyword);
validator.CheckLettersDigits(kAllowedCharsInKeyword,
/*uppercase_letters_allowed=*/true);
return validator.codes;
}
// Validate 'uri' value based on:
// * rfc8011, section 5.1.6;
// * rfc3986, section 2.
std::set<ValidationCode> validateUri(std::string_view value) {
StringValidator validator = {value};
validator.CheckLength(kMaxLengthOfUri);
validator.CheckLettersDigits(kAllowedCharsInUri,
/*uppercase_letters_allowed=*/true);
return validator.codes;
}
// Validate 'uriScheme' value based on:
// * rfc8011, section 5.1.7;
// * rfc3986, section 3.1.
std::set<ValidationCode> validateUriScheme(std::string_view value) {
StringValidator validator = {value};
validator.CheckLength(kMaxLengthOfUriScheme);
validator.CheckFirstLetterIsLowercase();
validator.CheckLettersDigits(kAllowedCharsInUriScheme);
return validator.codes;
}
// Validate 'charset' value based on:
// * rfc8011, section 5.1.8;
// * https://www.iana.org/assignments/character-sets/character-sets.xhtml.
std::set<ValidationCode> validateCharset(std::string_view value) {
StringValidator validator = {value};
validator.CheckLength(kMaxLengthOfCharset);
validator.CheckPrintable();
return validator.codes;
}
// Validate 'naturalLanguage' value based on:
// * rfc8011, section 5.1.9;
// * rfc5646, section 2.1.
std::set<ValidationCode> validateNaturalLanguage(std::string_view value) {
StringValidator validator = {value};
validator.CheckLength(kMaxLengthOfNaturalLanguage);
validator.CheckLettersDigits(kAllowedCharsInNaturalLanguage);
return validator.codes;
}
// Validate 'mimeMediaType' value based on:
// * rfc8011, section 5.1.10;
// * https://www.iana.org/assignments/media-types/media-types.xhtml.
std::set<ValidationCode> validateMimeMediaType(std::string_view value) {
StringValidator validator = {value};
validator.CheckLength(kMaxLengthOfMimeMediaType);
validator.CheckPrintable(/*uppercase_letters_allowed=*/true);
return validator.codes;
}
// Validate 'octetString' value based on:
// * rfc8011, section 5.1.11.
std::set<ValidationCode> validateOctetString(std::string_view value) {
std::set<ValidationCode> codes;
if (value.size() > kMaxLengthOfOctetString)
codes.insert(ValidationCode::kStringTooLong);
return codes;
}
// Validate 'dateTime' value based on:
// * rfc8011, section 5.1.15;
// * DateAndTime defined in rfc2579, section 2;
// * also enforces 1970 <= year <= 2100.
std::set<ValidationCode> validateDateTime(const DateTime& value) {
std::set<ValidationCode> codes;
// Verify the date.
if (value.year < 1970 || value.year > 2100 || value.month < 1 ||
value.month > 12 || value.day < 1) {
codes.insert(ValidationCode::kDateTimeInvalidDate);
} else {
uint8_t max_day = 31;
switch (value.month) {
case 2:
if (IsLeapYear(value.year)) {
max_day = 29;
} else {
max_day = 28;
}
break;
case 4: // FALLTHROUGH
case 6: // FALLTHROUGH
case 9: // FALLTHROUGH
case 11:
max_day = 30;
break;
}
if (value.day > max_day) {
codes.insert(ValidationCode::kDateTimeInvalidDate);
}
}
// Verify the time of day (seconds == 60 means leap second).
if (value.hour > 23 || value.minutes > 59 || value.seconds > 60 ||
value.deci_seconds > 9) {
codes.insert(ValidationCode::kDateTimeInvalidTimeOfDay);
}
// Verify the timezone (daylight saving time in New Zealand is +13).
if ((value.UTC_direction != '-' && value.UTC_direction != '+') ||
value.UTC_hours > 13 || value.UTC_minutes > 59) {
codes.insert(ValidationCode::kDateTimeInvalidZone);
}
return codes;
}
// Validate 'resolution' value based on:
// * rfc8011, section 5.1.16.
std::set<ValidationCode> validateResolution(Resolution value) {
std::set<ValidationCode> codes;
if (value.units != Resolution::Units::kDotsPerCentimeter &&
value.units != Resolution::Units::kDotsPerInch)
codes.insert(ValidationCode::kResolutionInvalidUnit);
if (value.xres < 1 || value.yres < 1)
codes.insert(ValidationCode::kResolutionInvalidDimension);
return codes;
}
// Validate 'rangeOfInteger' value based on:
// * rfc8011, section 5.1.14.
std::set<ValidationCode> validateRangeOfInteger(RangeOfInteger value) {
std::set<ValidationCode> codes;
if (value.min_value > value.max_value)
codes.insert(ValidationCode::kRangeOfIntegerMaxLessMin);
return codes;
}
// Validate 'textWithLanguage' value based on:
// * rfc8011, section 5.1.2.2.
std::set<ValidationCode> validateTextWithLanguage(
const StringWithLanguage& value) {
std::set<ValidationCode> codes = validateTextWithoutLanguage(value.value);
if (!value.language.empty()) {
if (!validateNaturalLanguage(value.language).empty())
codes.insert(ValidationCode::kStringWithLangInvalidLanguage);
}
return codes;
}
// Validate 'nameWithLanguage' value based on:
// * rfc8011, section 5.1.3.2.
std::set<ValidationCode> validateNameWithLanguage(
const StringWithLanguage& value) {
std::set<ValidationCode> codes = validateNameWithoutLanguage(value.value);
if (!value.language.empty()) {
if (!validateNaturalLanguage(value.language).empty())
codes.insert(ValidationCode::kStringWithLangInvalidLanguage);
}
return codes;
}
// Validate a single value in `attribute`. `attribute` must not be nullptr and
// `value_index` must be a valid index.
std::set<ValidationCode> ValidateValue(const Attribute* attribute,
size_t value_index) {
if (IsString(attribute->Tag())) {
std::string values_str;
attribute->GetValue(&values_str, value_index);
switch (attribute->Tag()) {
case ValueTag::textWithoutLanguage:
return validateTextWithoutLanguage(values_str);
case ValueTag::nameWithoutLanguage:
return validateNameWithoutLanguage(values_str);
case ValueTag::keyword:
return validateKeyword(values_str);
case ValueTag::uri:
return validateUri(values_str);
case ValueTag::uriScheme:
return validateUriScheme(values_str);
case ValueTag::charset:
return validateCharset(values_str);
case ValueTag::naturalLanguage:
return validateNaturalLanguage(values_str);
case ValueTag::mimeMediaType:
return validateMimeMediaType(values_str);
default:
// There are no validation rules for other strings.
return {};
}
}
switch (attribute->Tag()) {
case ValueTag::octetString: {
std::string value;
attribute->GetValue(&value, value_index);
return validateOctetString(value);
}
case ValueTag::dateTime: {
DateTime value;
attribute->GetValue(&value, value_index);
return validateDateTime(value);
}
case ValueTag::resolution: {
Resolution value;
attribute->GetValue(&value, value_index);
return validateResolution(value);
}
case ValueTag::rangeOfInteger: {
RangeOfInteger value;
attribute->GetValue(&value, value_index);
return validateRangeOfInteger(value);
}
case ValueTag::textWithLanguage: {
StringWithLanguage value;
attribute->GetValue(&value, value_index);
return validateTextWithLanguage(value);
}
case ValueTag::nameWithLanguage: {
StringWithLanguage value;
attribute->GetValue(&value, value_index);
return validateNameWithLanguage(value);
}
default:
// Other types does not need validation.
return {};
}
}
struct ValidationResult {
bool no_errors = true;
bool keep_going = true;
};
ValidationResult operator&&(ValidationResult vr1, ValidationResult vr2) {
ValidationResult result;
result.keep_going = vr1.keep_going && vr2.keep_going;
result.no_errors = vr1.no_errors && vr2.no_errors;
return result;
}
ValidationResult ValidateCollections(
const std::vector<const Collection*>& colls,
ErrorsLog& log,
AttrPath& path);
ValidationResult ValidateAttribute(const Attribute* attr,
ErrorsLog& log,
AttrPath& path) {
ValidationResult result;
std::set<ValidationCode> name_errors = validateKeyword(attr->Name());
if (!name_errors.empty()) {
result.no_errors = false;
result.keep_going =
log.AddValidationError(path, AttrError(std::move(name_errors)));
if (!result.keep_going)
return result;
}
const size_t values_count = attr->Size();
if (attr->Tag() == ValueTag::collection) {
std::vector<const Collection*> colls(values_count);
for (size_t i = 0; i < values_count; ++i) {
colls[i] = attr->GetCollection(i);
}
result = result && ValidateCollections(colls, log, path);
} else {
for (size_t i = 0; i < values_count; ++i) {
std::set<ValidationCode> value_errors = ValidateValue(attr, i);
if (!value_errors.empty()) {
result.no_errors = false;
result.keep_going =
log.AddValidationError(path, AttrError(i, std::move(value_errors)));
if (!result.keep_going)
return result;
}
}
}
return result;
}
ValidationResult ValidateCollections(
const std::vector<const Collection*>& colls,
ErrorsLog& log,
AttrPath& path) {
ValidationResult result;
for (size_t icoll = 0; icoll < colls.size(); ++icoll) {
const Collection* coll = colls[icoll];
std::vector<const Attribute*> attrs = coll->GetAllAttributes();
for (const Attribute* attr : attrs) {
path.PushBack(icoll, attr->Name());
result = result && ValidateAttribute(attr, log, path);
path.PopBack();
if (!result.keep_going)
return result;
}
}
return result;
}
} // namespace
bool Validate(const Frame& frame, ErrorsLog& log) {
ValidationResult result;
for (GroupTag group_tag : kGroupTags) {
std::vector<const Collection*> groups = frame.GetGroups(group_tag);
for (size_t index = 0; index < groups.size(); ++index) {
AttrPath path(group_tag);
std::vector<const Attribute*> attrs = groups[index]->GetAllAttributes();
for (const Attribute* attr : attrs) {
path.PushBack(index, attr->Name());
result = result && ValidateAttribute(attr, log, path);
path.PopBack();
if (!result.keep_going)
return result.no_errors;
}
}
}
return result.no_errors;
}
} // namespace ipp