| // Copyright 2019 The ChromiumOS Authors | |
| // Use of this source code is governed by a BSD-style license that can be | |
| // found in the LICENSE file. | |
| #include "libipp/ipp_parser.h" | |
| #include <set> | |
| #include "libipp/frame.h" | |
| #include "libipp/ipp_encoding.h" | |
| namespace ipp { | |
| namespace { | |
| // This parameter defines how deep can be a package with recursive collections. | |
| // A collection placed directly in attributes group has level 1, each | |
| // sub-collection belonging directly to it has level 2 etc.. | |
| constexpr int kMaxCollectionLevel = 16; | |
| // Decodes 1-, 2- or 4-bytes integers (two's-complement binary encoding). | |
| // Returns false if (data.size() != BytesCount) or (out == nullptr). | |
| template <size_t BytesCount> | |
| bool LoadInteger(const std::vector<uint8_t>& data, int32_t* out) { | |
| if ((data.size() != BytesCount) || (out == nullptr)) | |
| return false; | |
| const uint8_t* ptr = data.data(); | |
| ParseSignedInteger<BytesCount>(&ptr, out); | |
| return true; | |
| } | |
| // Reads simple string from buf. | |
| std::string LoadString(const std::vector<uint8_t>& buf) { | |
| return std::string(buf.data(), buf.data() + buf.size()); | |
| } | |
| // Reads textWithLanguage/nameWithLanguage (see [rfc8010], section 3.9) from | |
| // buf. Returns false if given content is invalid or (out == nullptr). | |
| bool LoadStringWithLanguage(const std::vector<uint8_t>& buf, | |
| ipp::StringWithLanguage* out) { | |
| // The shortest possible value has 4 bytes: 2 times 2-bytes zero. | |
| if ((buf.size() < 4) || (out == nullptr)) | |
| return false; | |
| const uint8_t* ptr = buf.data(); | |
| size_t length; | |
| if (!ParseUnsignedInteger<2>(&ptr, &length)) | |
| return false; | |
| if (buf.size() < 4 + length) | |
| return false; | |
| out->language.assign(ptr, ptr + length); | |
| ptr += length; | |
| if (!ParseUnsignedInteger<2>(&ptr, &length)) | |
| return false; | |
| if (buf.size() != 4 + out->language.size() + length) | |
| return false; | |
| out->value.assign(ptr, ptr + length); | |
| return true; | |
| } | |
| // Reads dateTime (see [rfc8010]) from buf. | |
| // Fails when binary representation has invalid size or (out == nullptr). | |
| bool LoadDateTime(const std::vector<uint8_t>& buf, ipp::DateTime* out) { | |
| if ((buf.size() != 11) || (out == nullptr)) | |
| return false; | |
| const uint8_t* ptr = buf.data(); | |
| return (ParseUnsignedInteger<2>(&ptr, &out->year) && | |
| ParseUnsignedInteger<1>(&ptr, &out->month) && | |
| ParseUnsignedInteger<1>(&ptr, &out->day) && | |
| ParseUnsignedInteger<1>(&ptr, &out->hour) && | |
| ParseUnsignedInteger<1>(&ptr, &out->minutes) && | |
| ParseUnsignedInteger<1>(&ptr, &out->seconds) && | |
| ParseUnsignedInteger<1>(&ptr, &out->deci_seconds) && | |
| ParseUnsignedInteger<1>(&ptr, &out->UTC_direction) && | |
| ParseUnsignedInteger<1>(&ptr, &out->UTC_hours) && | |
| ParseUnsignedInteger<1>(&ptr, &out->UTC_minutes)); | |
| } | |
| // Reads resolution (see [rfc8010]) from buf. | |
| // Fails when binary representation has invalid size or (out == nullptr). | |
| bool LoadResolution(const std::vector<uint8_t>& buf, ipp::Resolution* out) { | |
| if ((buf.size() != 9) || (out == nullptr)) | |
| return false; | |
| const uint8_t* ptr = buf.data(); | |
| ParseSignedInteger<4>(&ptr, &out->xres); | |
| ParseSignedInteger<4>(&ptr, &out->yres); | |
| int8_t units; | |
| ParseSignedInteger<1>(&ptr, &units); | |
| out->units = static_cast<Resolution::Units>(units); | |
| return true; | |
| } | |
| // Reads rangeOfInteger (see [rfc8010]) from buf. | |
| // Fails when binary representation has invalid size or (out == nullptr). | |
| bool LoadRangeOfInteger(const std::vector<uint8_t>& buf, | |
| ipp::RangeOfInteger* out) { | |
| if ((buf.size() != 8) || (out == nullptr)) | |
| return false; | |
| const uint8_t* ptr = buf.data(); | |
| ParseSignedInteger<4>(&ptr, &out->min_value); | |
| ParseSignedInteger<4>(&ptr, &out->max_value); | |
| return true; | |
| } | |
| // Scope guard to control the context path. Constructor adds new a element to | |
| // the path while destructor removes it from the path. | |
| class ContextPathGuard { | |
| public: | |
| ContextPathGuard(AttrPath* path, uint16_t index) : path_(path) { | |
| path_->PushBack(index, ""); | |
| } | |
| ~ContextPathGuard() { path_->PopBack(); } | |
| private: | |
| ContextPathGuard(const ContextPathGuard&) = delete; | |
| ContextPathGuard(ContextPathGuard&&) = delete; | |
| ContextPathGuard& operator=(const ContextPathGuard&) = delete; | |
| ContextPathGuard& operator=(ContextPathGuard&&) = delete; | |
| AttrPath* path_; | |
| }; | |
| // Return true if source type can be used in attribute of target type. | |
| bool IsConvertibleTo(const ipp::ValueTag source, const ipp::ValueTag target) { | |
| if (source == target) | |
| return true; | |
| if (source == ipp::ValueTag::integer && | |
| target == ipp::ValueTag::rangeOfInteger) | |
| return true; | |
| if (source == ipp::ValueTag::integer && target == ipp::ValueTag::enum_) | |
| return true; | |
| if (source == ipp::ValueTag::nameWithoutLanguage && | |
| target == ipp::ValueTag::nameWithLanguage) | |
| return true; | |
| if (source == ipp::ValueTag::textWithoutLanguage && | |
| target == ipp::ValueTag::textWithLanguage) | |
| return true; | |
| return false; | |
| } | |
| // Parses two bytes from `ptr` and move it forward by 2 bytes. | |
| uint16_t ParseUInt16(const uint8_t*& ptr) { | |
| uint16_t val = *ptr; | |
| val <<= 8; | |
| val += *++ptr; | |
| ++ptr; | |
| return val; | |
| } | |
| } // namespace | |
| void Parser::LogParserError(ParserCode error_code, const uint8_t* ptr) { | |
| ssize_t buf_offset = -1; | |
| if (ptr != nullptr && ptr >= buffer_begin_ && ptr <= buffer_end_) { | |
| // Current position in the buffer. | |
| buf_offset = ptr - buffer_begin_; | |
| } | |
| log_->AddParserError({parser_context_, error_code, buf_offset}); | |
| } | |
| void Parser::LogParserErrors(const std::vector<ParserCode>& error_codes) { | |
| for (ParserCode error_code : error_codes) { | |
| LogParserError(error_code); | |
| } | |
| } | |
| // Temporary representation of an attribute's value parsed from TNVs. | |
| struct RawValue { | |
| // original tag (IsValid(tag)) | |
| ValueTag tag; | |
| // original data, empty when (tag == collection), content not verified | |
| std::vector<uint8_t> data; | |
| // (not nullptr) <=> (tag == collection) | |
| std::unique_ptr<RawCollection> collection; | |
| // create as standard value | |
| RawValue(ValueTag tag, const std::vector<uint8_t>& data) | |
| : tag(tag), data(data) {} | |
| // create as collection | |
| explicit RawValue(RawCollection* coll) | |
| : tag(ValueTag::collection), collection(coll) {} | |
| }; | |
| struct RawCollection; | |
| // Temporary representation of an attribute parsed from TNVs. | |
| struct RawAttribute { | |
| // verified (non-empty) | |
| std::string name; | |
| // parsed values (see RawValue) | |
| std::vector<RawValue> values; | |
| explicit RawAttribute(const std::string& name) : name(name) {} | |
| }; | |
| // Temporary representation of a collection parsed from TNVs. | |
| struct RawCollection { | |
| // parsed attributes (may have duplicate names) | |
| std::vector<RawAttribute> attributes; | |
| }; | |
| // Parse a value of type `attr_type` from `raw_value` to `output` when possible. | |
| // Returns true <=> parsing was successful. All spotted errors are added to | |
| // `errors`. | |
| template <typename ApiType> | |
| bool LoadAttrValue(ValueTag attr_type, | |
| const RawValue& raw_value, | |
| ApiType& output, | |
| std::vector<ParserCode>& errors); | |
| template <> | |
| bool LoadAttrValue<std::string>(ValueTag attr_type, | |
| const RawValue& raw_value, | |
| std::string& output, | |
| std::vector<ParserCode>& errors) { | |
| if (!IsString(raw_value.tag) && raw_value.tag != ValueTag::octetString) { | |
| errors.push_back(ParserCode::kValueMismatchTagOmitted); | |
| return false; | |
| } | |
| output = LoadString(raw_value.data); | |
| if (attr_type != raw_value.tag) | |
| errors.push_back(ParserCode::kValueMismatchTagConverted); | |
| return true; | |
| } | |
| template <> | |
| bool LoadAttrValue<int32_t>(ValueTag attr_type, | |
| const RawValue& raw_value, | |
| int32_t& output, | |
| std::vector<ParserCode>& errors) { | |
| switch (raw_value.tag) { | |
| case ValueTag::boolean: { | |
| if (!LoadInteger<1>(raw_value.data, &output)) { | |
| errors.push_back(ParserCode::kValueInvalidSize); | |
| return false; | |
| } | |
| if (attr_type != ValueTag::boolean) { | |
| errors.push_back(ParserCode::kValueMismatchTagConverted); | |
| } | |
| if (output < 0 || output > 1) { | |
| output = 1; | |
| errors.push_back(ParserCode::kBooleanValueOutOfRange); | |
| } | |
| return true; | |
| } | |
| case ValueTag::integer: | |
| case ValueTag::enum_: { | |
| if (!LoadInteger<4>(raw_value.data, &output)) { | |
| errors.push_back(ParserCode::kValueInvalidSize); | |
| return false; | |
| } | |
| if (attr_type != raw_value.tag) { | |
| errors.push_back(ParserCode::kValueMismatchTagConverted); | |
| } | |
| return true; | |
| } | |
| default: | |
| errors.push_back(ParserCode::kValueMismatchTagOmitted); | |
| return false; | |
| } | |
| } | |
| template <> | |
| bool LoadAttrValue<DateTime>(ValueTag attr_type, | |
| const RawValue& raw_value, | |
| DateTime& output, | |
| std::vector<ParserCode>& errors) { | |
| if (raw_value.tag != ValueTag::dateTime) { | |
| errors.push_back(ParserCode::kValueMismatchTagOmitted); | |
| return false; | |
| } | |
| if (!LoadDateTime(raw_value.data, &output)) { | |
| errors.push_back(ParserCode::kValueInvalidSize); | |
| return false; | |
| } | |
| return true; | |
| } | |
| template <> | |
| bool LoadAttrValue<Resolution>(ValueTag attr_type, | |
| const RawValue& raw_value, | |
| Resolution& output, | |
| std::vector<ParserCode>& errors) { | |
| if (raw_value.tag != ValueTag::resolution) { | |
| errors.push_back(ParserCode::kValueMismatchTagOmitted); | |
| return false; | |
| } | |
| if (!LoadResolution(raw_value.data, &output)) { | |
| errors.push_back(ParserCode::kValueInvalidSize); | |
| return false; | |
| } | |
| return true; | |
| } | |
| template <> | |
| bool LoadAttrValue<RangeOfInteger>(ValueTag attr_type, | |
| const RawValue& raw_value, | |
| RangeOfInteger& output, | |
| std::vector<ParserCode>& errors) { | |
| if (raw_value.tag == ValueTag::integer) { | |
| if (!LoadInteger<4>(raw_value.data, &output.min_value)) { | |
| errors.push_back(ParserCode::kValueInvalidSize); | |
| return false; | |
| } | |
| output.max_value = output.min_value; | |
| return true; | |
| } | |
| if (raw_value.tag != ValueTag::rangeOfInteger) { | |
| errors.push_back(ParserCode::kValueMismatchTagOmitted); | |
| return false; | |
| } | |
| if (!LoadRangeOfInteger(raw_value.data, &output)) { | |
| errors.push_back(ParserCode::kValueInvalidSize); | |
| return false; | |
| } | |
| return true; | |
| } | |
| template <> | |
| bool LoadAttrValue<StringWithLanguage>(ValueTag attr_type, | |
| const RawValue& raw_value, | |
| StringWithLanguage& output, | |
| std::vector<ParserCode>& errors) { | |
| if (raw_value.tag == ValueTag::nameWithLanguage || | |
| raw_value.tag == ValueTag::textWithLanguage) { | |
| if (!LoadStringWithLanguage(raw_value.data, &output)) { | |
| errors.push_back(ParserCode::kValueInvalidSize); | |
| return false; | |
| } | |
| if (raw_value.tag != attr_type) { | |
| errors.push_back(ParserCode::kValueMismatchTagConverted); | |
| } | |
| return true; | |
| } | |
| if (IsString(raw_value.tag)) { | |
| output.language.clear(); | |
| output.value = LoadString(raw_value.data); | |
| if (raw_value.tag == ValueTag::nameWithoutLanguage && | |
| attr_type != ValueTag::nameWithLanguage) { | |
| errors.push_back(ParserCode::kValueMismatchTagConverted); | |
| } | |
| if (raw_value.tag == ValueTag::textWithoutLanguage && | |
| attr_type != ValueTag::textWithLanguage) { | |
| errors.push_back(ParserCode::kValueMismatchTagConverted); | |
| } | |
| return true; | |
| } | |
| errors.push_back(ParserCode::kValueMismatchTagOmitted); | |
| return false; | |
| } | |
| // Parse an attribute of type `attr_type` from `raw_attr` and add it to `coll` | |
| // when possible. Return a list of parser errors. `coll` must not be nullptr. | |
| template <typename ApiType> | |
| std::vector<ParserCode> LoadAttrValues(Collection* coll, | |
| ValueTag attr_type, | |
| const RawAttribute& raw_attr) { | |
| std::vector<ParserCode> errors; | |
| std::vector<ApiType> vals; | |
| vals.reserve(raw_attr.values.size()); | |
| for (const RawValue& raw_value : raw_attr.values) { | |
| ApiType val; | |
| if (LoadAttrValue<ApiType>(attr_type, raw_value, val, errors)) { | |
| vals.push_back(std::move(val)); | |
| } | |
| } | |
| if (vals.empty()) { | |
| errors.push_back(ParserCode::kAttributeNoValues); | |
| } else { | |
| const Code err = coll->AddAttr(raw_attr.name, attr_type, vals); | |
| if (err != Code::kOK) { | |
| errors.push_back(ParserCode::kErrorWhenAddingAttribute); | |
| } | |
| } | |
| return errors; | |
| } | |
| bool Parser::SaveFrameToPackage(Frame* package) { | |
| for (size_t i = 0; i < frame_->groups_tags_.size(); ++i) { | |
| GroupTag gn = frame_->groups_tags_[i]; | |
| parser_context_ = AttrPath(gn); | |
| parser_context_.PushBack(package->Groups(gn).size(), ""); | |
| CollsView::iterator coll; | |
| Code err = package->AddGroup(gn, coll); | |
| if (err != Code::kOK) { | |
| LogParserError(ParserCode::kErrorWhenAddingGroup); | |
| continue; | |
| } | |
| RawCollection raw_coll; | |
| if (!ParseRawGroup(&(frame_->groups_content_[i]), &raw_coll)) { | |
| return false; | |
| } | |
| DecodeCollection(&raw_coll, &*coll); | |
| } | |
| package->SetData(std::move(frame_->data_)); | |
| return true; | |
| } | |
| bool Parser::ReadFrameFromBuffer(const uint8_t* ptr, | |
| const uint8_t* const buf_end) { | |
| parser_context_ = AttrPath(AttrPath::kHeader); | |
| buffer_begin_ = ptr; | |
| buffer_end_ = buf_end; | |
| if (buf_end - ptr < 9) { | |
| LogParserError(ParserCode::kUnexpectedEndOfFrame, ptr); | |
| return false; | |
| } | |
| frame_->version_ = ParseUInt16(ptr); | |
| ParseSignedInteger<2>(&ptr, &frame_->operation_id_or_status_code_); | |
| ParseSignedInteger<4>(&ptr, &frame_->request_id_); | |
| while (*ptr != end_of_attributes_tag) { | |
| GroupTag group_tag = static_cast<GroupTag>(*ptr); | |
| parser_context_ = AttrPath(group_tag); | |
| if (!IsValid(group_tag)) { | |
| // begin-attribute-group-tag was expected. | |
| LogParserError(ParserCode::kGroupTagWasExpected, ptr); | |
| return false; | |
| } | |
| if (frame_->groups_tags_.size() >= kMaxCountOfAttributeGroups) { | |
| LogParserError(ParserCode::kLimitOnGroupsCountExceeded, ptr); | |
| return false; | |
| } | |
| frame_->groups_tags_.push_back(group_tag); | |
| frame_->groups_content_.resize(frame_->groups_tags_.size()); | |
| ++ptr; | |
| if (!ReadTNVsFromBuffer(&ptr, buf_end, &(frame_->groups_content_.back()))) | |
| return false; | |
| if (ptr >= buf_end) { | |
| // begin-attribute-group-tag or end-of-attributes-tag was expected. | |
| LogParserError(ParserCode::kUnexpectedEndOfFrame); | |
| return false; | |
| } | |
| } | |
| ++ptr; | |
| frame_->data_.assign(ptr, buf_end); | |
| ptr = buf_end; | |
| return true; | |
| } | |
| // Parses TNVs from given buffer until the end of the buffer is reached or next | |
| // begin-attribute-group-tag is spotted. The pointer ptr is shifted accordingly. | |
| // returns true <=> (ptr == buf_end) or (*ptr is begin-attribute-group-tag) | |
| // returns false <=> parsing error occurs, ptr points to incorrect field | |
| // Parsed TNVs are added to the end of |tnvs|. If |tnvs| is nullptr then no | |
| // output is saved but parsing occurs as usual. | |
| bool Parser::ReadTNVsFromBuffer(const uint8_t** ptr2, | |
| const uint8_t* const buf_end, | |
| std::list<TagNameValue>* tnvs) { | |
| const uint8_t*& ptr = *ptr2; | |
| while ((ptr < buf_end) && (*ptr > max_begin_attribute_group_tag)) { | |
| TagNameValue tnv; | |
| if (buf_end - ptr < 5) { | |
| // Expected at least 1-byte tag, 2-bytes name-length and 2-bytes | |
| // value-length. | |
| LogParserError(ParserCode::kUnexpectedEndOfFrame, ptr); | |
| return false; | |
| } | |
| // *ptr is a ValueTag because it was already checked in while (...) | |
| tnv.tag = *ptr; | |
| ++ptr; | |
| int length = 0; | |
| if (!ParseUnsignedInteger<2>(&ptr, &length)) { | |
| LogParserError(ParserCode::kNegativeNameLengthInTNV, ptr); | |
| return false; | |
| } | |
| if (buf_end - ptr < length + 2) { | |
| // Expected at least `length`-bytes name and 2-bytes value-length. | |
| LogParserError(ParserCode::kUnexpectedEndOfFrame, ptr); | |
| return false; | |
| } | |
| tnv.name.assign(ptr, ptr + length); | |
| ptr += length; | |
| if (!ParseUnsignedInteger<2>(&ptr, &length)) { | |
| LogParserError(ParserCode::kNegativeValueLengthInTNV, ptr); | |
| return false; | |
| } | |
| if (buf_end - ptr < length) { | |
| LogParserError(ParserCode::kUnexpectedEndOfFrame, ptr); | |
| return false; | |
| } | |
| tnv.value.assign(ptr, ptr + length); | |
| ptr += length; | |
| if (tnvs != nullptr) | |
| tnvs->push_back(std::move(tnv)); | |
| } | |
| return true; | |
| } | |
| void Parser::ResetContent() { | |
| buffer_begin_ = nullptr; | |
| buffer_end_ = nullptr; | |
| parser_context_ = AttrPath(AttrPath::kHeader); | |
| } | |
| // Parses single attribute's value and add it to |attr|. |tnv| is the first TNV | |
| // with the value, |tnvs| contains all following TNVs. Both |tnvs| and |attr| | |
| // cannot be nullptr. |coll_level| denotes how "deep" is the collection that | |
| // contains the attribute; attributes defined directly in the attributes group | |
| // have level 0. Returns false <=> critical parsing error was spotted. | |
| // See section 3.5.2 from rfc8010 for details. | |
| bool Parser::ParseRawValue(int coll_level, | |
| const TagNameValue& tnv, | |
| std::list<TagNameValue>* tnvs, | |
| RawAttribute* attr) { | |
| // Is it correct attribute's value tag? If not then fail. | |
| if (tnv.tag == endCollection_value_tag || | |
| tnv.tag == memberAttrName_value_tag) { | |
| LogParserError(ParserCode::kTNVWithUnexpectedValueTag); | |
| return false; | |
| } | |
| // Is it a collection ? | |
| if (tnv.tag == begCollection_value_tag) { | |
| ContextPathGuard path_update(&parser_context_, attr->values.size()); | |
| if (!tnv.value.empty()) { | |
| LogParserError(ParserCode::kEmptyValueExpectedInTNV); | |
| return false; | |
| } | |
| std::unique_ptr<RawCollection> coll(new RawCollection); | |
| if (!ParseRawCollection(coll_level + 1, tnvs, coll.get())) | |
| return false; | |
| attr->values.emplace_back(coll.release()); | |
| return true; | |
| } | |
| ValueTag type = static_cast<ValueTag>(tnv.tag); | |
| if (!IsValid(type)) { | |
| // unknown attribute's syntax | |
| LogParserError(ParserCode::kUnsupportedValueTag); | |
| return true; | |
| } | |
| attr->values.emplace_back(type, tnv.value); | |
| return true; | |
| } | |
| // Parses single collections from given TNVs. |coll_level| denotes how "deep" | |
| // the collection is; collections defined directly in the attributes group | |
| // have level 1. Both |tnvs| and |coll| cannot be nullptr. | |
| // Returns false <=> critical parsing error was spotted. | |
| bool Parser::ParseRawCollection(int coll_level, | |
| std::list<TagNameValue>* tnvs, | |
| RawCollection* coll) { | |
| if (coll_level > kMaxCollectionLevel) { | |
| LogParserError(ParserCode::kLimitOnCollectionsLevelExceeded); | |
| return false; | |
| } | |
| while (true) { | |
| if (tnvs->empty()) { | |
| LogParserError(ParserCode::kUnexpectedEndOfGroup); | |
| return false; | |
| } | |
| TagNameValue tnv = tnvs->front(); | |
| tnvs->pop_front(); | |
| // exit if the end of the collection was reached | |
| if (tnv.tag == endCollection_value_tag) { | |
| if (!tnv.name.empty()) { | |
| LogParserError(ParserCode::kEmptyNameExpectedInTNV); | |
| return false; | |
| } | |
| if (!tnv.value.empty()) { | |
| LogParserError(ParserCode::kEmptyValueExpectedInTNV); | |
| return false; | |
| } | |
| return true; | |
| } | |
| // still here, so we parse an attribute (collection's member) | |
| if (tnv.tag != memberAttrName_value_tag) { | |
| LogParserError(ParserCode::kTNVWithUnexpectedValueTag); | |
| return false; | |
| } | |
| // parse name & create attribute | |
| const std::string name = LoadString(tnv.value); | |
| parser_context_.Back().attribute_name = name; | |
| if (!tnv.name.empty()) { | |
| LogParserError(ParserCode::kEmptyNameExpectedInTNV); | |
| return false; | |
| } | |
| if (name.empty()) { | |
| LogParserErrors({ParserCode::kAttributeNameIsEmpty}); | |
| return false; | |
| } | |
| coll->attributes.emplace_back(name); | |
| RawAttribute* attr = &coll->attributes.back(); | |
| // parse tag | |
| if (tnvs->empty()) { | |
| LogParserError(ParserCode::kUnexpectedEndOfGroup); | |
| return false; | |
| } | |
| // parse all values | |
| while (!tnvs->empty() && tnvs->front().tag != endCollection_value_tag && | |
| tnvs->front().tag != memberAttrName_value_tag) { | |
| tnv = tnvs->front(); | |
| tnvs->pop_front(); | |
| if (!tnv.name.empty()) { | |
| LogParserError(ParserCode::kEmptyNameExpectedInTNV); | |
| return false; | |
| } | |
| if (!ParseRawValue(coll_level, tnv, tnvs, attr)) | |
| return false; | |
| } | |
| } | |
| } | |
| // Parses attributes group from given TNVs and saves it to |coll|. Both |tnvs| | |
| // and |coll| cannot be nullptr. Returns false <=> critical parsing error was | |
| // spotted. | |
| bool Parser::ParseRawGroup(std::list<TagNameValue>* tnvs, RawCollection* coll) { | |
| while (!tnvs->empty()) { | |
| TagNameValue tnv = tnvs->front(); | |
| tnvs->pop_front(); | |
| // parse name & create attribute | |
| const std::string name = LoadString(tnv.name); | |
| parser_context_.Back().attribute_name = name; | |
| if (name.empty()) { | |
| LogParserErrors({ParserCode::kAttributeNameIsEmpty}); | |
| return false; | |
| } | |
| coll->attributes.emplace_back(name); | |
| RawAttribute* attr = &coll->attributes.back(); | |
| // parse all values | |
| while (true) { | |
| // parse value | |
| if (!ParseRawValue(0 /*collection level*/, tnv, tnvs, attr)) | |
| return false; | |
| // go to the next value or attribute | |
| if (tnvs->empty() || !tnvs->front().name.empty()) | |
| break; // end of the attribute | |
| // next value | |
| tnv = tnvs->front(); | |
| tnvs->pop_front(); | |
| } | |
| } | |
| return true; | |
| } | |
| // Converts a collection/group saved in |raw_coll| to |coll|. Both |raw_coll| | |
| // and |coll| must not be nullptr. | |
| void Parser::DecodeCollection(RawCollection* raw_coll, Collection* coll) { | |
| for (RawAttribute& raw_attr : raw_coll->attributes) { | |
| // Tries to match the attribute to existing one by name. | |
| parser_context_.Back().attribute_name = raw_attr.name; | |
| if (coll->GetAttr(raw_attr.name) != coll->end()) { | |
| // The attribute exists. | |
| LogParserErrors({ParserCode::kAttributeNameConflict}); | |
| continue; | |
| } | |
| if (raw_attr.values.empty()) { | |
| LogParserErrors({ParserCode::kAttributeNoValues}); | |
| continue; | |
| } | |
| // Tries to detect an attribute's type. | |
| ValueTag detected_type = raw_attr.values.front().tag; | |
| for (auto& raw_val : raw_attr.values) | |
| if (IsConvertibleTo(detected_type, raw_val.tag)) | |
| detected_type = raw_val.tag; | |
| // Is it an attribute with Ouf-Of-Band value? Then set it and finish. | |
| if (IsOutOfBand(detected_type)) { | |
| if (raw_attr.values.size() > 1) { | |
| LogParserError(ParserCode::kOutOfBandAttributeWithManyValues); | |
| } | |
| if (!raw_attr.values.front().data.empty()) { | |
| LogParserError(ParserCode::kOutOfBandValueWithNonEmptyData); | |
| } | |
| const Code err = coll->AddAttr(raw_attr.name, detected_type); | |
| if (err != Code::kOK) { | |
| LogParserErrors({ParserCode::kErrorWhenAddingAttribute}); | |
| } | |
| continue; | |
| } | |
| // It is a collection? | |
| if (detected_type == ValueTag::collection) { | |
| std::vector<ParserCode> errors; | |
| std::vector<RawCollection*> raw_colls; | |
| raw_colls.reserve(raw_attr.values.size()); | |
| for (const RawValue& raw_value : raw_attr.values) { | |
| if (raw_value.collection) { | |
| raw_colls.push_back(raw_value.collection.get()); | |
| } else { | |
| errors.push_back(ParserCode::kValueMismatchTagOmitted); | |
| } | |
| } | |
| CollsView colls; | |
| Code err = coll->AddAttr(raw_attr.name, raw_colls.size(), colls); | |
| if (err == Code::kOK) { | |
| for (size_t i = 0; i < colls.size(); ++i) { | |
| ContextPathGuard path_update(&parser_context_, i); | |
| DecodeCollection(raw_colls[i], &colls[i]); | |
| } | |
| } else { | |
| errors.push_back(ParserCode::kErrorWhenAddingAttribute); | |
| } | |
| LogParserErrors(errors); | |
| continue; | |
| } | |
| // It is an attribute with standard values. Parse the values and create | |
| // a new attribute. | |
| if (IsInteger(detected_type)) { | |
| LogParserErrors(LoadAttrValues<int32_t>(coll, detected_type, raw_attr)); | |
| continue; | |
| } | |
| if (IsString(detected_type) || detected_type == ValueTag::octetString) { | |
| LogParserErrors( | |
| LoadAttrValues<std::string>(coll, detected_type, raw_attr)); | |
| continue; | |
| } | |
| switch (detected_type) { | |
| case ValueTag::dateTime: | |
| LogParserErrors( | |
| LoadAttrValues<DateTime>(coll, detected_type, raw_attr)); | |
| break; | |
| case ValueTag::resolution: | |
| LogParserErrors( | |
| LoadAttrValues<Resolution>(coll, detected_type, raw_attr)); | |
| break; | |
| case ValueTag::rangeOfInteger: | |
| LogParserErrors( | |
| LoadAttrValues<RangeOfInteger>(coll, detected_type, raw_attr)); | |
| break; | |
| case ValueTag::nameWithLanguage: | |
| case ValueTag::textWithLanguage: | |
| LogParserErrors( | |
| LoadAttrValues<StringWithLanguage>(coll, detected_type, raw_attr)); | |
| break; | |
| default: | |
| LogParserErrors({ParserCode::kErrorWhenAddingAttribute}); | |
| break; | |
| } | |
| } | |
| } | |
| } // namespace ipp |