blob: 96179b6c2f730123a93c59ecdb28ba7332054212 [file] [log] [blame]
// 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 <limits>
#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<ValidatorCode> 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(ValidatorCode::kStringEmpty);
}
return;
}
if (value.size() > max_length) {
codes.insert(ValidatorCode::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(ValidatorCode::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(ValidatorCode::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(ValidatorCode::kStringInvalidCharacter);
break;
}
if (!uppercase_letters_allowed && IsUppercaseLetter(c)) {
codes.insert(ValidatorCode::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<ValidatorCode> 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<ValidatorCode> 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<ValidatorCode> 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<ValidatorCode> 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<ValidatorCode> 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<ValidatorCode> 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<ValidatorCode> 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<ValidatorCode> 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<ValidatorCode> validateOctetString(std::string_view value) {
std::set<ValidatorCode> codes;
if (value.size() > kMaxLengthOfOctetString)
codes.insert(ValidatorCode::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<ValidatorCode> validateDateTime(const DateTime& value) {
std::set<ValidatorCode> codes;
// Verify the date.
if (value.year < 1970 || value.year > 2100 || value.month < 1 ||
value.month > 12 || value.day < 1) {
codes.insert(ValidatorCode::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(ValidatorCode::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(ValidatorCode::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(ValidatorCode::kDateTimeInvalidZone);
}
return codes;
}
// Validate 'resolution' value based on:
// * rfc8011, section 5.1.16.
std::set<ValidatorCode> validateResolution(Resolution value) {
std::set<ValidatorCode> codes;
if (value.units != Resolution::Units::kDotsPerCentimeter &&
value.units != Resolution::Units::kDotsPerInch)
codes.insert(ValidatorCode::kResolutionInvalidUnit);
if (value.xres < 1 || value.yres < 1)
codes.insert(ValidatorCode::kResolutionInvalidDimension);
return codes;
}
// Validate 'rangeOfInteger' value based on:
// * rfc8011, section 5.1.14.
std::set<ValidatorCode> validateRangeOfInteger(RangeOfInteger value) {
std::set<ValidatorCode> codes;
if (value.min_value > value.max_value)
codes.insert(ValidatorCode::kRangeOfIntegerMaxLessMin);
return codes;
}
// Validate 'textWithLanguage' value based on:
// * rfc8011, section 5.1.2.2.
std::set<ValidatorCode> validateTextWithLanguage(
const StringWithLanguage& value) {
std::set<ValidatorCode> codes = validateTextWithoutLanguage(value.value);
if (!value.language.empty()) {
if (!validateNaturalLanguage(value.language).empty())
codes.insert(ValidatorCode::kStringWithLangInvalidLanguage);
}
return codes;
}
// Validate 'nameWithLanguage' value based on:
// * rfc8011, section 5.1.3.2.
std::set<ValidatorCode> validateNameWithLanguage(
const StringWithLanguage& value) {
std::set<ValidatorCode> codes = validateNameWithoutLanguage(value.value);
if (!value.language.empty()) {
if (!validateNaturalLanguage(value.language).empty())
codes.insert(ValidatorCode::kStringWithLangInvalidLanguage);
}
return codes;
}
// Validate a single value in `attribute`. `attribute` must not be nullptr and
// `value_index` must be a valid index.
std::set<ValidatorCode> ValidateValue(const Attribute* attribute,
size_t value_index) {
if (IsString(attribute->Tag())) {
std::string values_str;
attribute->GetValue(value_index, values_str);
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_index, value);
return validateOctetString(value);
}
case ValueTag::dateTime: {
DateTime value;
attribute->GetValue(value_index, value);
return validateDateTime(value);
}
case ValueTag::resolution: {
Resolution value;
attribute->GetValue(value_index, value);
return validateResolution(value);
}
case ValueTag::rangeOfInteger: {
RangeOfInteger value;
attribute->GetValue(value_index, value);
return validateRangeOfInteger(value);
}
case ValueTag::textWithLanguage: {
StringWithLanguage value;
attribute->GetValue(value_index, value);
return validateTextWithLanguage(value);
}
case ValueTag::nameWithLanguage: {
StringWithLanguage value;
attribute->GetValue(value_index, value);
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(ConstCollsView colls,
ValidatorLog& log,
AttrPath& path);
ValidationResult ValidateAttribute(const Attribute* attr,
ValidatorLog& log,
AttrPath& path) {
ValidationResult result;
std::set<ValidatorCode> name_errors = validateKeyword(attr->Name());
if (!name_errors.empty()) {
result.no_errors = false;
result.keep_going =
log.AddValidatorError({path, AttrError(std::move(name_errors))});
if (!result.keep_going)
return result;
}
const size_t values_count = attr->Size();
if (attr->Tag() == ValueTag::collection) {
result = result && ValidateCollections(attr->Colls(), log, path);
} else {
for (size_t i = 0; i < values_count; ++i) {
std::set<ValidatorCode> value_errors = ValidateValue(attr, i);
if (!value_errors.empty()) {
result.no_errors = false;
result.keep_going = log.AddValidatorError(
{path, AttrError(i, std::move(value_errors))});
if (!result.keep_going)
return result;
}
}
}
return result;
}
ValidationResult ValidateCollections(ConstCollsView colls,
ValidatorLog& log,
AttrPath& path) {
ValidationResult result;
uint16_t icoll = 0;
for (const Collection& coll : colls) {
for (const Attribute& attr : coll) {
path.PushBack(icoll, attr.Name());
result = result && ValidateAttribute(&attr, log, path);
path.PopBack();
if (!result.keep_going)
return result;
}
++icoll;
}
return result;
}
// Checks the values saved in the header of `frame`.
ValidationResult ValidateHeader(const Frame& frame, ValidatorLog& log) {
std::vector<std::string> invalid_fields;
invalid_fields.reserve(4);
uint8_t ver_major = static_cast<uint16_t>(frame.VersionNumber()) >> 8;
uint8_t ver_minor = static_cast<uint16_t>(frame.VersionNumber()) & 0xffu;
if (ver_major < 1 || ver_major > 9) {
invalid_fields.push_back("major-version-number");
}
if (ver_minor > 9) {
invalid_fields.push_back("minor-version-number");
}
if (frame.OperationIdOrStatusCode() < 0) {
invalid_fields.push_back("operation-id or status-code");
}
if (frame.RequestId() < 1) {
invalid_fields.push_back("request-id");
}
ValidationResult result;
result.no_errors = invalid_fields.empty();
AttrError error = AttrError(0, {ValidatorCode::kIntegerOutOfRange});
AttrPath path = AttrPath(AttrPath::kHeader);
for (const std::string& name : invalid_fields) {
path.PushBack(0, name);
result.keep_going = log.AddValidatorError({path, error});
if (!result.keep_going) {
break;
}
path.PopBack();
}
return result;
}
} // namespace
std::string_view ToStrView(ValidatorCode code) {
switch (code) {
case ValidatorCode::kStringEmpty:
return "StringEmpty";
case ValidatorCode::kStringTooLong:
return "StringTooLong";
case ValidatorCode::kStringMustStartLowercaseLetter:
return "StringMustStartLowercaseLetter";
case ValidatorCode::kStringInvalidCharacter:
return "StringInvalidCharacter";
case ValidatorCode::kStringWithLangInvalidLanguage:
return "StringWithLangInvalidLanguage";
case ValidatorCode::kDateTimeInvalidDate:
return "DateTimeInvalidDate";
case ValidatorCode::kDateTimeInvalidTimeOfDay:
return "DateTimeInvalidTimeOfDay";
case ValidatorCode::kDateTimeInvalidZone:
return "DateTimeInvalidZone";
case ValidatorCode::kResolutionInvalidUnit:
return "ResolutionInvalidUnit";
case ValidatorCode::kResolutionInvalidDimension:
return "ResolutionInvalidDimension";
case ValidatorCode::kRangeOfIntegerMaxLessMin:
return "RangeOfIntegerMaxLessMin";
case ValidatorCode::kIntegerOutOfRange:
return "IntegerOutOfRange";
}
}
std::vector<ValidatorCode> AttrError::ErrorsAsVector() const {
return std::vector<ValidatorCode>(errors_.begin(), errors_.end());
}
std::string ToString(const ValidatorError& error) {
std::string msg = error.path.AsString();
if (error.error.IsInTheName()) {
msg += "; name; ";
} else {
msg += "; ";
msg += std::to_string(error.error.Index());
msg += "; ";
}
const std::vector<ValidatorCode> codes = error.error.ErrorsAsVector();
for (ValidatorCode code : codes) {
if (msg.back() != '\t') {
msg += ",";
}
msg += ToStrView(code);
}
return msg;
}
bool SimpleValidatorLog::AddValidatorError(const ValidatorError& error) {
if (entries_.size() < max_entries_count_) {
entries_.emplace_back(error);
}
return (entries_.size() < max_entries_count_);
}
bool Validate(const Frame& frame, ValidatorLog& log) {
ValidationResult result = ValidateHeader(frame, log);
if (!result.keep_going)
return result.no_errors;
for (GroupTag group_tag : kGroupTags) {
size_t coll_index = 0;
for (const Collection& coll : frame.Groups(group_tag)) {
AttrPath path(group_tag);
for (const Attribute& attr : coll) {
path.PushBack(coll_index, attr.Name());
result = result && ValidateAttribute(&attr, log, path);
path.PopBack();
if (!result.keep_going)
return result.no_errors;
}
++coll_index;
}
}
return result.no_errors;
}
} // namespace ipp