diff --git a/DEPENDENCIES b/DEPENDENCIES index 227116dd..0c98014b 100644 --- a/DEPENDENCIES +++ b/DEPENDENCIES @@ -1,4 +1,4 @@ vendorpull https://github.com/sourcemeta/vendorpull 1dcbac42809cf87cb5b045106b863e17ad84ba02 -core https://github.com/sourcemeta/core 3e1d11893f8a7144c4ec2267f5d3f54da551f083 -blaze https://github.com/sourcemeta/blaze 554ac837d7a088602276df5fff6fac21434fbeda +core https://github.com/sourcemeta/core 9ba55aa07360c270d289f5c74a9e9fef4e0cf33d +blaze https://github.com/sourcemeta/blaze 714bea1948f551918682236c75c1d386f8051adb bootstrap https://github.com/twbs/bootstrap 1a6fdfae6be09b09eaced8f0e442ca6f7680a61e diff --git a/cmake/FindCore.cmake b/cmake/FindCore.cmake index 3924264c..6edb5b87 100644 --- a/cmake/FindCore.cmake +++ b/cmake/FindCore.cmake @@ -11,10 +11,8 @@ if(NOT Core_FOUND) set(SOURCEMETA_CORE_USE_SYSTEM_ALLOCATOR OFF CACHE BOOL "disable the system allocator") endif() - set(SOURCEMETA_CORE_LANG_PROCESS OFF CACHE BOOL "disable") set(SOURCEMETA_CORE_LANG_PARALLEL OFF CACHE BOOL "disable") set(SOURCEMETA_CORE_LANG_ERROR OFF CACHE BOOL "disable") - set(SOURCEMETA_CORE_GZIP OFF CACHE BOOL "disable") set(SOURCEMETA_CORE_JSONL OFF CACHE BOOL "disable JSONL support") set(SOURCEMETA_CORE_JSONRPC OFF CACHE BOOL "disable") set(SOURCEMETA_CORE_MCP OFF CACHE BOOL "disable") diff --git a/vendor/blaze/DEPENDENCIES b/vendor/blaze/DEPENDENCIES index f44968c2..bce3320e 100644 --- a/vendor/blaze/DEPENDENCIES +++ b/vendor/blaze/DEPENDENCIES @@ -1,6 +1,6 @@ vendorpull https://github.com/sourcemeta/vendorpull 1dcbac42809cf87cb5b045106b863e17ad84ba02 -core https://github.com/sourcemeta/core 3e1d11893f8a7144c4ec2267f5d3f54da551f083 -jsonschema-test-suite https://github.com/json-schema-org/JSON-Schema-Test-Suite 1acd90e53554fa24d2529b49fd7d50bab18f8b7e +core https://github.com/sourcemeta/core b0308c787d9122292888c37070a65f2cb911da85 +jsonschema-test-suite https://github.com/json-schema-org/JSON-Schema-Test-Suite 6648e8194c69697b2e1a15fe76a06a480b183a51 jsonschema-2020-12 https://github.com/json-schema-org/json-schema-spec 769daad75a9553562333a8937a187741cb708c72 jsonschema-2019-09 https://github.com/json-schema-org/json-schema-spec 41014ea723120ce70b314d72f863c6929d9f3cfd jsonschema-draft7 https://github.com/json-schema-org/json-schema-spec 567f768506aaa33a38e552c85bf0586029ef1b32 diff --git a/vendor/blaze/src/bundle/bundle.cc b/vendor/blaze/src/bundle/bundle.cc index c5ccee7b..2cbdaf04 100644 --- a/vendor/blaze/src/bundle/bundle.cc +++ b/vendor/blaze/src/bundle/bundle.cc @@ -115,7 +115,7 @@ auto dependencies_internal( for (const auto &entry : found) { dependencies_internal(std::get<0>(entry), walker, resolver, callback, default_dialect, std::get<1>(entry), - {sourcemeta::core::empty_weak_pointer}, visited); + {sourcemeta::core::EMPTY_WEAK_POINTER}, visited); } } diff --git a/vendor/blaze/src/bundle/include/sourcemeta/blaze/bundle.h b/vendor/blaze/src/bundle/include/sourcemeta/blaze/bundle.h index e7d90c8f..9a167170 100644 --- a/vendor/blaze/src/bundle/include/sourcemeta/blaze/bundle.h +++ b/vendor/blaze/src/bundle/include/sourcemeta/blaze/bundle.h @@ -96,7 +96,7 @@ auto dependencies(const sourcemeta::core::JSON &schema, std::string_view default_dialect = "", std::string_view default_id = "", const SchemaFrame::Paths &paths = { - sourcemeta::core::empty_weak_pointer}) -> void; + sourcemeta::core::EMPTY_WEAK_POINTER}) -> void; /// @ingroup bundle /// @@ -157,7 +157,7 @@ auto bundle(sourcemeta::core::JSON &schema, const SchemaWalker &walker, const std::optional &default_container = std::nullopt, const SchemaFrame::Paths &paths = { - sourcemeta::core::empty_weak_pointer}) -> void; + sourcemeta::core::EMPTY_WEAK_POINTER}) -> void; /// @ingroup bundle /// @@ -219,7 +219,7 @@ auto bundle( std::string_view default_dialect = "", std::string_view default_id = "", const std::optional &default_container = std::nullopt, - const SchemaFrame::Paths &paths = {sourcemeta::core::empty_weak_pointer}) + const SchemaFrame::Paths &paths = {sourcemeta::core::EMPTY_WEAK_POINTER}) -> sourcemeta::core::JSON; } // namespace sourcemeta::blaze diff --git a/vendor/blaze/src/compiler/compile.cc b/vendor/blaze/src/compiler/compile.cc index 38993048..671c5cec 100644 --- a/vendor/blaze/src/compiler/compile.cc +++ b/vendor/blaze/src/compiler/compile.cc @@ -457,8 +457,8 @@ auto compile(const sourcemeta::core::JSON &schema, compiled_targets[index] = compile(context, schema_context, relative_dynamic_context(), - sourcemeta::core::empty_weak_pointer, - sourcemeta::core::empty_weak_pointer, destination_uri); + sourcemeta::core::EMPTY_WEAK_POINTER, + sourcemeta::core::EMPTY_WEAK_POINTER, destination_uri); } /////////////////////////////////////////////////////////////////// diff --git a/vendor/blaze/src/compiler/compile_helpers.h b/vendor/blaze/src/compiler/compile_helpers.h index 2b0aaf98..e54080ce 100644 --- a/vendor/blaze/src/compiler/compile_helpers.h +++ b/vendor/blaze/src/compiler/compile_helpers.h @@ -54,8 +54,8 @@ inline auto make_weak_pointer(const std::string &property1, inline auto relative_dynamic_context() -> DynamicContext { return {.keyword = KEYWORD_EMPTY, - .base_schema_location = sourcemeta::core::empty_weak_pointer, - .base_instance_location = sourcemeta::core::empty_weak_pointer}; + .base_schema_location = sourcemeta::core::EMPTY_WEAK_POINTER, + .base_instance_location = sourcemeta::core::EMPTY_WEAK_POINTER}; } inline auto schema_resource_id(const std::vector &resources, @@ -135,7 +135,7 @@ inline auto make(const InstructionIndex type, const Context &context, inline auto unroll(const Context &context, const Instruction &step, const sourcemeta::core::WeakPointer &base_instance_location = - sourcemeta::core::empty_weak_pointer) -> Instruction { + sourcemeta::core::EMPTY_WEAK_POINTER) -> Instruction { auto source_extra{context.extra[step.extra_index]}; const auto extra_index{context.extra.size()}; context.extra.push_back(std::move(source_extra)); diff --git a/vendor/blaze/src/compiler/default_compiler_2019_09.h b/vendor/blaze/src/compiler/default_compiler_2019_09.h index 81fd43bc..2c1bc266 100644 --- a/vendor/blaze/src/compiler/default_compiler_2019_09.h +++ b/vendor/blaze/src/compiler/default_compiler_2019_09.h @@ -155,8 +155,8 @@ auto compiler_2019_09_applicator_contains_with_options( Instructions children{compile(context, schema_context, relative_dynamic_context(), - sourcemeta::core::empty_weak_pointer, - sourcemeta::core::empty_weak_pointer)}; + sourcemeta::core::EMPTY_WEAK_POINTER, + sourcemeta::core::EMPTY_WEAK_POINTER)}; if (annotate && annotations_enabled(context, dynamic_context.keyword)) { children.push_back( @@ -281,8 +281,8 @@ auto compiler_2019_09_applicator_unevaluateditems( Instructions children{compile(context, schema_context, relative_dynamic_context(), - sourcemeta::core::empty_weak_pointer, - sourcemeta::core::empty_weak_pointer)}; + sourcemeta::core::EMPTY_WEAK_POINTER, + sourcemeta::core::EMPTY_WEAK_POINTER)}; if (annotations_enabled(context, dynamic_context.keyword)) { children.push_back( @@ -325,8 +325,8 @@ auto compiler_2019_09_applicator_unevaluatedproperties( Instructions children{compile(context, schema_context, relative_dynamic_context(), - sourcemeta::core::empty_weak_pointer, - sourcemeta::core::empty_weak_pointer)}; + sourcemeta::core::EMPTY_WEAK_POINTER, + sourcemeta::core::EMPTY_WEAK_POINTER)}; if (annotations_enabled(context, dynamic_context.keyword)) { children.push_back( diff --git a/vendor/blaze/src/compiler/default_compiler_draft3.h b/vendor/blaze/src/compiler/default_compiler_draft3.h index b65c0620..61868ff8 100644 --- a/vendor/blaze/src/compiler/default_compiler_draft3.h +++ b/vendor/blaze/src/compiler/default_compiler_draft3.h @@ -218,8 +218,8 @@ auto compile_required_assertions(const Context &context, .is_property_name = schema_context.is_property_name}; const DynamicContext new_dynamic_context{ .keyword = KEYWORD_PROPERTIES, - .base_schema_location = sourcemeta::core::empty_weak_pointer, - .base_instance_location = sourcemeta::core::empty_weak_pointer}; + .base_schema_location = sourcemeta::core::EMPTY_WEAK_POINTER, + .base_instance_location = sourcemeta::core::EMPTY_WEAK_POINTER}; auto properties{compile_properties(context, new_schema_context, new_dynamic_context, current)}; if (std::ranges::all_of(properties, [](const auto &property) -> auto { @@ -1181,8 +1181,8 @@ properties_enforce_closed_object(const Context &context, .is_property_name = schema_context.is_property_name}; const DynamicContext new_dynamic_context{ .keyword = KEYWORD_PROPERTIES, - .base_schema_location = sourcemeta::core::empty_weak_pointer, - .base_instance_location = sourcemeta::core::empty_weak_pointer}; + .base_schema_location = sourcemeta::core::EMPTY_WEAK_POINTER, + .base_instance_location = sourcemeta::core::EMPTY_WEAK_POINTER}; const auto properties{ compile_properties(context, new_schema_context, new_dynamic_context, {})}; if (!std::ranges::all_of(properties, [](const auto &property) -> auto { @@ -1213,8 +1213,8 @@ auto compiler_draft3_applicator_additionalproperties_with_options( Instructions children{compile(context, schema_context, relative_dynamic_context(), - sourcemeta::core::empty_weak_pointer, - sourcemeta::core::empty_weak_pointer)}; + sourcemeta::core::EMPTY_WEAK_POINTER, + sourcemeta::core::EMPTY_WEAK_POINTER)}; if (annotate && annotations_enabled(context, dynamic_context.keyword)) { children.push_back( @@ -1537,8 +1537,8 @@ auto compiler_draft3_applicator_items_with_options( if (emit_annotation || track_evaluation) { Instructions subchildren{compile(context, schema_context, relative_dynamic_context(), - sourcemeta::core::empty_weak_pointer, - sourcemeta::core::empty_weak_pointer)}; + sourcemeta::core::EMPTY_WEAK_POINTER, + sourcemeta::core::EMPTY_WEAK_POINTER)}; Instructions children; @@ -1576,8 +1576,8 @@ auto compiler_draft3_applicator_items_with_options( Instructions children{compile(context, schema_context, relative_dynamic_context(), - sourcemeta::core::empty_weak_pointer, - sourcemeta::core::empty_weak_pointer)}; + sourcemeta::core::EMPTY_WEAK_POINTER, + sourcemeta::core::EMPTY_WEAK_POINTER)}; if (track_evaluation) { children.push_back( make(sourcemeta::blaze::InstructionIndex::ControlEvaluate, context, @@ -1667,8 +1667,8 @@ auto compiler_draft3_applicator_additionalitems_from_cursor( Instructions subchildren{compile(context, schema_context, relative_dynamic_context(), - sourcemeta::core::empty_weak_pointer, - sourcemeta::core::empty_weak_pointer)}; + sourcemeta::core::EMPTY_WEAK_POINTER, + sourcemeta::core::EMPTY_WEAK_POINTER)}; Instructions children; @@ -2454,8 +2454,8 @@ auto compiler_draft3_validation_disallow(const Context &context, auto inner_instructions{ compile(context, schema_context, relative_dynamic_context(), - sourcemeta::core::empty_weak_pointer, - sourcemeta::core::empty_weak_pointer, element_uri)}; + sourcemeta::core::EMPTY_WEAK_POINTER, + sourcemeta::core::EMPTY_WEAK_POINTER, element_uri)}; const auto element_relative_pointer{ schema_context.relative_pointer.concat(index_suffix)}; @@ -2504,13 +2504,13 @@ auto compiler_draft3_applicator_extends(const Context &context, // TODO: Make this work with `$dynamicRef` if (context.mode == Mode::FastValidation && !context.uses_dynamic_scopes) { return compile(context, schema_context, dynamic_context, - sourcemeta::core::empty_weak_pointer, - sourcemeta::core::empty_weak_pointer); + sourcemeta::core::EMPTY_WEAK_POINTER, + sourcemeta::core::EMPTY_WEAK_POINTER); } auto inner{compile(context, schema_context, relative_dynamic_context(), - sourcemeta::core::empty_weak_pointer, - sourcemeta::core::empty_weak_pointer)}; + sourcemeta::core::EMPTY_WEAK_POINTER, + sourcemeta::core::EMPTY_WEAK_POINTER)}; if (inner.empty()) { return {}; } diff --git a/vendor/blaze/src/compiler/default_compiler_draft4.h b/vendor/blaze/src/compiler/default_compiler_draft4.h index 8960ab6b..4afa0b0f 100644 --- a/vendor/blaze/src/compiler/default_compiler_draft4.h +++ b/vendor/blaze/src/compiler/default_compiler_draft4.h @@ -188,8 +188,8 @@ auto compiler_draft4_applicator_not(const Context &context, Instructions children{compile(context, schema_context, relative_dynamic_context(), - sourcemeta::core::empty_weak_pointer, - sourcemeta::core::empty_weak_pointer)}; + sourcemeta::core::EMPTY_WEAK_POINTER, + sourcemeta::core::EMPTY_WEAK_POINTER)}; // TODO: Be smarter about how we treat `unevaluatedItems` like how we do for // `unevaluatedProperties` diff --git a/vendor/blaze/src/compiler/default_compiler_draft6.h b/vendor/blaze/src/compiler/default_compiler_draft6.h index f4ee5032..87287688 100644 --- a/vendor/blaze/src/compiler/default_compiler_draft6.h +++ b/vendor/blaze/src/compiler/default_compiler_draft6.h @@ -445,8 +445,8 @@ auto compiler_draft6_applicator_contains(const Context &context, Instructions children{compile(context, schema_context, relative_dynamic_context(), - sourcemeta::core::empty_weak_pointer, - sourcemeta::core::empty_weak_pointer)}; + sourcemeta::core::EMPTY_WEAK_POINTER, + sourcemeta::core::EMPTY_WEAK_POINTER)}; if (children.empty()) { // We still need to check the instance is not empty @@ -475,8 +475,8 @@ auto compiler_draft6_validation_propertynames( nested_schema_context.is_property_name = true; Instructions children{compile(context, nested_schema_context, relative_dynamic_context(), - sourcemeta::core::empty_weak_pointer, - sourcemeta::core::empty_weak_pointer)}; + sourcemeta::core::EMPTY_WEAK_POINTER, + sourcemeta::core::EMPTY_WEAK_POINTER)}; if (children.empty()) { return {}; diff --git a/vendor/blaze/src/compiler/default_compiler_draft7.h b/vendor/blaze/src/compiler/default_compiler_draft7.h index 715eb66f..de03677a 100644 --- a/vendor/blaze/src/compiler/default_compiler_draft7.h +++ b/vendor/blaze/src/compiler/default_compiler_draft7.h @@ -16,8 +16,8 @@ auto compiler_draft7_applicator_if(const Context &context, // `if` Instructions children{compile(context, schema_context, relative_dynamic_context(), - sourcemeta::core::empty_weak_pointer, - sourcemeta::core::empty_weak_pointer)}; + sourcemeta::core::EMPTY_WEAK_POINTER, + sourcemeta::core::EMPTY_WEAK_POINTER)}; // `then` std::size_t then_cursor{children.size()}; @@ -39,8 +39,8 @@ auto compiler_draft7_applicator_if(const Context &context, if (then_cursor == 0 && context.uses_dynamic_scopes) { Instructions substeps{ compile(context, schema_context, relative_dynamic_context(), - sourcemeta::core::empty_weak_pointer, - sourcemeta::core::empty_weak_pointer, destination)}; + sourcemeta::core::EMPTY_WEAK_POINTER, + sourcemeta::core::EMPTY_WEAK_POINTER, destination)}; if (substeps.empty()) { return children; } @@ -69,11 +69,11 @@ auto compiler_draft7_applicator_if(const Context &context, .base_schema_location = dynamic_context.base_schema_location, .base_instance_location = then_cursor == 0 ? dynamic_context.base_instance_location - : sourcemeta::core::empty_weak_pointer}; + : sourcemeta::core::EMPTY_WEAK_POINTER}; for (auto &&step : compile(context, schema_context, new_dynamic_context, - sourcemeta::core::empty_weak_pointer, - sourcemeta::core::empty_weak_pointer, destination)) { + sourcemeta::core::EMPTY_WEAK_POINTER, + sourcemeta::core::EMPTY_WEAK_POINTER, destination)) { children.push_back(std::move(step)); } @@ -105,11 +105,11 @@ auto compiler_draft7_applicator_if(const Context &context, DynamicContext new_dynamic_context{ .keyword = KEYWORD_ELSE, .base_schema_location = dynamic_context.base_schema_location, - .base_instance_location = sourcemeta::core::empty_weak_pointer}; + .base_instance_location = sourcemeta::core::EMPTY_WEAK_POINTER}; for (auto &&step : compile(context, schema_context, new_dynamic_context, - sourcemeta::core::empty_weak_pointer, - sourcemeta::core::empty_weak_pointer, destination)) { + sourcemeta::core::EMPTY_WEAK_POINTER, + sourcemeta::core::EMPTY_WEAK_POINTER, destination)) { children.push_back(std::move(step)); } } diff --git a/vendor/blaze/src/compiler/include/sourcemeta/blaze/compiler.h b/vendor/blaze/src/compiler/include/sourcemeta/blaze/compiler.h index bebc4f25..ed3dd9f9 100644 --- a/vendor/blaze/src/compiler/include/sourcemeta/blaze/compiler.h +++ b/vendor/blaze/src/compiler/include/sourcemeta/blaze/compiler.h @@ -215,7 +215,7 @@ compile(const Context &context, const SchemaContext &schema_context, const DynamicContext &dynamic_context, const sourcemeta::core::WeakPointer &schema_suffix, const sourcemeta::core::WeakPointer &instance_suffix = - sourcemeta::core::empty_weak_pointer, + sourcemeta::core::EMPTY_WEAK_POINTER, std::optional uri = std::nullopt) -> Instructions; /// @ingroup compiler diff --git a/vendor/blaze/src/evaluator/include/sourcemeta/blaze/evaluator_dispatch.h b/vendor/blaze/src/evaluator/include/sourcemeta/blaze/evaluator_dispatch.h index 14a3837e..22e2f10b 100644 --- a/vendor/blaze/src/evaluator/include/sourcemeta/blaze/evaluator_dispatch.h +++ b/vendor/blaze/src/evaluator/include/sourcemeta/blaze/evaluator_dispatch.h @@ -913,7 +913,7 @@ INSTRUCTION_HANDLER(AssertionStringType) { result = is_email(target); break; case ValueStringType::IDNEmail: - result = is_idn_email(target); + result = is_idn_email_uts46(target); break; case ValueStringType::IPv4: result = is_ipv4(target); diff --git a/vendor/blaze/src/frame/frame.cc b/vendor/blaze/src/frame/frame.cc index 38d02b1e..4e351b0b 100644 --- a/vendor/blaze/src/frame/frame.cc +++ b/vendor/blaze/src/frame/frame.cc @@ -254,10 +254,10 @@ auto find_nearest_bases(const MapType &bases, if (default_base.has_value()) { return {{StringType{default_base.value()}}, - sourcemeta::core::empty_weak_pointer}; + sourcemeta::core::EMPTY_WEAK_POINTER}; } - return {{}, sourcemeta::core::empty_weak_pointer}; + return {{}, sourcemeta::core::EMPTY_WEAK_POINTER}; } struct DialectAtPointer { @@ -305,9 +305,9 @@ auto find_dialect_and_all_bases(const DialectMapType &base_dialects, } if (result.every_base.empty() || - result.every_base.back().second != sourcemeta::core::empty_weak_pointer) { + result.every_base.back().second != sourcemeta::core::EMPTY_WEAK_POINTER) { result.every_base.emplace_back(std::string_view{}, - sourcemeta::core::empty_weak_pointer); + sourcemeta::core::EMPTY_WEAK_POINTER); } return result; @@ -1057,7 +1057,7 @@ auto SchemaFrame::analyse(const sourcemeta::core::JSON &root, const auto &parent_pointer{ combined.dialect_match.has_value() ? combined.dialect_match->second - : sourcemeta::core::empty_weak_pointer}; + : sourcemeta::core::EMPTY_WEAK_POINTER}; const auto parent_subschema_it{subschemas.find(parent_pointer)}; const bool parent_property_name{ parent_subschema_it != subschemas.cend() && diff --git a/vendor/blaze/src/frame/include/sourcemeta/blaze/frame.h b/vendor/blaze/src/frame/include/sourcemeta/blaze/frame.h index d78a02e2..2d344a52 100644 --- a/vendor/blaze/src/frame/include/sourcemeta/blaze/frame.h +++ b/vendor/blaze/src/frame/include/sourcemeta/blaze/frame.h @@ -166,7 +166,7 @@ class SOURCEMETA_BLAZE_FRAME_EXPORT SchemaFrame { const SchemaResolver &resolver, std::string_view default_dialect = "", std::string_view default_id = "", - const Paths &paths = {sourcemeta::core::empty_weak_pointer}) + const Paths &paths = {sourcemeta::core::EMPTY_WEAK_POINTER}) -> void; /// Access the analysed schema locations @@ -197,7 +197,7 @@ class SOURCEMETA_BLAZE_FRAME_EXPORT SchemaFrame { [[nodiscard]] auto uri(const Location &location, const sourcemeta::core::WeakPointer &relative_schema_location = - sourcemeta::core::empty_weak_pointer) const + sourcemeta::core::EMPTY_WEAK_POINTER) const -> sourcemeta::core::JSON::String; /// Get the location associated by traversing a pointer from another location @@ -229,7 +229,7 @@ class SOURCEMETA_BLAZE_FRAME_EXPORT SchemaFrame { [[nodiscard]] auto dereference(const Location &location, const sourcemeta::core::WeakPointer &relative_schema_location = - sourcemeta::core::empty_weak_pointer) const + sourcemeta::core::EMPTY_WEAK_POINTER) const -> std::pair>>; diff --git a/vendor/blaze/src/output/include/sourcemeta/blaze/output_simple.h b/vendor/blaze/src/output/include/sourcemeta/blaze/output_simple.h index d441aa7e..344ec98e 100644 --- a/vendor/blaze/src/output/include/sourcemeta/blaze/output_simple.h +++ b/vendor/blaze/src/output/include/sourcemeta/blaze/output_simple.h @@ -69,7 +69,7 @@ class SOURCEMETA_BLAZE_OUTPUT_EXPORT SimpleOutput { public: SimpleOutput(const sourcemeta::core::JSON &instance, sourcemeta::core::WeakPointer base = - sourcemeta::core::empty_weak_pointer); + sourcemeta::core::EMPTY_WEAK_POINTER); // Prevent accidental copies SimpleOutput(const SimpleOutput &) = delete; diff --git a/vendor/blaze/src/output/include/sourcemeta/blaze/output_trace.h b/vendor/blaze/src/output/include/sourcemeta/blaze/output_trace.h index 8e8c0523..72342d89 100644 --- a/vendor/blaze/src/output/include/sourcemeta/blaze/output_trace.h +++ b/vendor/blaze/src/output/include/sourcemeta/blaze/output_trace.h @@ -83,7 +83,7 @@ class SOURCEMETA_BLAZE_OUTPUT_EXPORT TraceOutput { TraceOutput( sourcemeta::blaze::SchemaWalker walker, sourcemeta::blaze::SchemaResolver resolver, Callback callback, - sourcemeta::core::WeakPointer base = sourcemeta::core::empty_weak_pointer, + sourcemeta::core::WeakPointer base = sourcemeta::core::EMPTY_WEAK_POINTER, const std::optional< std::reference_wrapper> &frame = std::nullopt); diff --git a/vendor/blaze/src/test/test_parser.cc b/vendor/blaze/src/test/test_parser.cc index 89da54a0..c66c541b 100644 --- a/vendor/blaze/src/test/test_parser.cc +++ b/vendor/blaze/src/test/test_parser.cc @@ -145,10 +145,10 @@ auto TestSuite::parse(const sourcemeta::core::JSON &document, const std::string_view default_id, const std::optional &tweaks) -> TestSuite { assert(std::filesystem::is_directory(base_path)); - TEST_ERROR_IF(!document.is_object(), tracker, sourcemeta::core::empty_pointer, + TEST_ERROR_IF(!document.is_object(), tracker, sourcemeta::core::EMPTY_POINTER, "The test document must be an object"); TEST_ERROR_IF(!document.defines("target"), tracker, - sourcemeta::core::empty_pointer, + sourcemeta::core::EMPTY_POINTER, "The test document must contain a `target` property"); TEST_ERROR_IF(!document.at("target").is_string() && !document.at("target").is_array(), @@ -156,7 +156,7 @@ auto TestSuite::parse(const sourcemeta::core::JSON &document, "The test document `target` property must be a URI or an " "array of URIs"); TEST_ERROR_IF(!document.defines("tests"), tracker, - sourcemeta::core::empty_pointer, + sourcemeta::core::EMPTY_POINTER, "The test document must contain a `tests` property"); TEST_ERROR_IF(!document.at("tests").is_array(), tracker, sourcemeta::core::Pointer{"tests"}, diff --git a/vendor/core/config.cmake.in b/vendor/core/config.cmake.in index 71c227ac..81cf401d 100644 --- a/vendor/core/config.cmake.in +++ b/vendor/core/config.cmake.in @@ -321,6 +321,7 @@ foreach(component ${SOURCEMETA_CORE_COMPONENTS}) include("${CMAKE_CURRENT_LIST_DIR}/sourcemeta_core_diff.cmake") include("${CMAKE_CURRENT_LIST_DIR}/sourcemeta_core_test.cmake") include("${CMAKE_CURRENT_LIST_DIR}/sourcemeta_core_test_main.cmake") + include("${CMAKE_CURRENT_LIST_DIR}/sourcemeta_core_clitest.cmake") else() message(FATAL_ERROR "Unknown Sourcemeta Core component: ${component}") endif() diff --git a/vendor/core/src/core/crypto/CMakeLists.txt b/vendor/core/src/core/crypto/CMakeLists.txt index 693765c5..d5170a05 100644 --- a/vendor/core/src/core/crypto/CMakeLists.txt +++ b/vendor/core/src/core/crypto/CMakeLists.txt @@ -3,16 +3,16 @@ sourcemeta_library(NAMESPACE sourcemeta PROJECT core NAME crypto sha512.h sha1.h fnv128.h uuid.h crc32.h base64.h secure.h verify.h sign.h aes_gcm.h aes_cbc_hmac.h - aes_kw.h rsa_oaep.h ecdh.h + aes_kw.h rsa_oaep.h ecdh.h hkdf.h SOURCES crypto_sha256.cc crypto_hmac_sha256.cc crypto_hmac_sha384.cc crypto_hmac_sha512.cc crypto_sha384.cc crypto_sha512.cc crypto_sha1.cc crypto_uuid.cc crypto_fnv128.cc crypto_crc32.cc crypto_base64.cc crypto_random.cc crypto_sign_rsa.cc crypto_aes_gcm.cc crypto_aes_cbc_hmac.cc - crypto_aes_kw.cc crypto_ecdh.cc + crypto_aes_kw.cc crypto_ecdh.cc crypto_hkdf.cc crypto_sha2_64.h crypto_sha256_other.h crypto_random.h crypto_helpers.h - crypto_der.h crypto_aes.h) + crypto_der.h crypto_aes.h crypto_kdf.h crypto_hkdf_loop.h) target_link_libraries(sourcemeta_core_crypto PRIVATE sourcemeta::core::text sourcemeta::core::numeric) @@ -27,6 +27,7 @@ if(SOURCEMETA_CORE_CRYPTO_USE_SYSTEM_OPENSSL) crypto_verify_openssl.cc crypto_secure_equals_openssl.cc crypto_sign_openssl.cc crypto_aes_gcm_openssl.cc crypto_aes_cbc_openssl.cc crypto_aes_kw_openssl.cc crypto_rsa_oaep_openssl.cc crypto_ecdh_openssl.cc + crypto_hkdf_openssl.cc crypto_openssl.h crypto_pkcs8.h) target_link_libraries(sourcemeta_core_crypto PRIVATE OpenSSL::Crypto) elseif(APPLE) @@ -118,8 +119,10 @@ elseif(APPLE) crypto_verify_apple.cc crypto_secure_equals_apple.cc crypto_sign_apple.cc crypto_aes_gcm_apple.cc crypto_aes_cbc_apple.cc crypto_aes_kw_apple.cc crypto_rsa_oaep_apple.cc crypto_ecdh_apple.cc + crypto_hkdf_apple.cc crypto_eddsa_cryptokit.mm "${CRYPTOKIT_OBJECT}" crypto_eddsa.h crypto_eddsa_apple.h crypto_apple.h crypto_aes_apple.h + crypto_kdf_apple.h crypto_bignum.h crypto_shake256.h crypto_pkcs8.h) # The generated Objective-C interface header lives in the build tree @@ -149,6 +152,7 @@ elseif(WIN32) crypto_verify_windows.cc crypto_secure_equals_generic.cc crypto_sign_windows.cc crypto_aes_gcm_windows.cc crypto_aes_cbc_windows.cc crypto_aes_kw_windows.cc crypto_rsa_oaep_windows.cc crypto_ecdh_windows.cc + crypto_hkdf_windows.cc crypto_eddsa.h crypto_windows.h crypto_aes_kw_loop.h crypto_bignum.h crypto_ecc.h crypto_shake256.h crypto_pkcs8.h) @@ -163,7 +167,7 @@ else() crypto_sha512_other.cc crypto_random_other.cc crypto_verify_other.cc crypto_secure_equals_generic.cc crypto_sign_other.cc crypto_aes_gcm_other.cc crypto_aes_cbc_other.cc crypto_aes_kw_other.cc - crypto_rsa_oaep_other.cc crypto_ecdh_other.cc + crypto_rsa_oaep_other.cc crypto_ecdh_other.cc crypto_hkdf_other.cc crypto_aes_block.h crypto_aes_kw_loop.h crypto_sha1_other.h crypto_bignum.h crypto_ecc.h crypto_eddsa.h crypto_shake256.h crypto_pkcs8.h crypto_other.h) diff --git a/vendor/core/src/core/crypto/crypto_aes_cbc_hmac.cc b/vendor/core/src/core/crypto/crypto_aes_cbc_hmac.cc index 8821cdfe..2dfd0514 100644 --- a/vendor/core/src/core/crypto/crypto_aes_cbc_hmac.cc +++ b/vendor/core/src/core/crypto/crypto_aes_cbc_hmac.cc @@ -35,7 +35,7 @@ auto associated_data_length_block(const std::size_t associated_data_size) std::string block(8, '\x00'); const auto bits{static_cast(associated_data_size) * 8}; for (std::size_t index = 0; index < 8; ++index) { - block[7 - index] = static_cast((bits >> (8 * index)) & 0xffu); + block[7 - index] = static_cast((bits >> (8 * index)) & 0xffU); } return block; diff --git a/vendor/core/src/core/crypto/crypto_apple.h b/vendor/core/src/core/crypto/crypto_apple.h index 16a8ed08..a4fb8696 100644 --- a/vendor/core/src/core/crypto/crypto_apple.h +++ b/vendor/core/src/core/crypto/crypto_apple.h @@ -43,7 +43,7 @@ struct PrivateKey::Internal { inline auto copy_external_representation(SecKeyRef key) -> std::optional { CFErrorRef error{nullptr}; - auto data{SecKeyCopyExternalRepresentation(key, &error)}; + const auto *data{SecKeyCopyExternalRepresentation(key, &error)}; if (data == nullptr) { if (error != nullptr) { CFRelease(error); diff --git a/vendor/core/src/core/crypto/crypto_base64.cc b/vendor/core/src/core/crypto/crypto_base64.cc index 7ebff8d6..7a314e1e 100644 --- a/vendor/core/src/core/crypto/crypto_base64.cc +++ b/vendor/core/src/core/crypto/crypto_base64.cc @@ -45,18 +45,18 @@ auto encode(const std::string_view input, const std::string_view alphabet, const std::uint32_t first{static_cast(input[index])}; const std::uint32_t second{static_cast(input[index + 1])}; const std::uint32_t third{static_cast(input[index + 2])}; - output.push_back(alphabet[first >> 2u]); - output.push_back(alphabet[((first & 0x03u) << 4u) | (second >> 4u)]); - output.push_back(alphabet[((second & 0x0Fu) << 2u) | (third >> 6u)]); - output.push_back(alphabet[third & 0x3Fu]); + output.push_back(alphabet[first >> 2U]); + output.push_back(alphabet[((first & 0x03U) << 4U) | (second >> 4U)]); + output.push_back(alphabet[((second & 0x0FU) << 2U) | (third >> 6U)]); + output.push_back(alphabet[third & 0x3FU]); index += 3; } const auto remaining{input.size() - index}; if (remaining == 1) { const std::uint32_t first{static_cast(input[index])}; - output.push_back(alphabet[first >> 2u]); - output.push_back(alphabet[(first & 0x03u) << 4u]); + output.push_back(alphabet[first >> 2U]); + output.push_back(alphabet[(first & 0x03U) << 4U]); if (padding) { output.push_back('='); output.push_back('='); @@ -64,9 +64,9 @@ auto encode(const std::string_view input, const std::string_view alphabet, } else if (remaining == 2) { const std::uint32_t first{static_cast(input[index])}; const std::uint32_t second{static_cast(input[index + 1])}; - output.push_back(alphabet[first >> 2u]); - output.push_back(alphabet[((first & 0x03u) << 4u) | (second >> 4u)]); - output.push_back(alphabet[(second & 0x0Fu) << 2u]); + output.push_back(alphabet[first >> 2U]); + output.push_back(alphabet[((first & 0x03U) << 4U) | (second >> 4U)]); + output.push_back(alphabet[(second & 0x0FU) << 2U]); if (padding) { output.push_back('='); } @@ -121,11 +121,11 @@ auto decode_into(const std::string_view input, return false; } - const std::uint32_t group{(first << 18u) | (second << 12u) | (third << 6u) | + const std::uint32_t group{(first << 18U) | (second << 12U) | (third << 6U) | fourth}; - output.push_back(static_cast((group >> 16u) & 0xFFu)); - output.push_back(static_cast((group >> 8u) & 0xFFu)); - output.push_back(static_cast(group & 0xFFu)); + output.push_back(static_cast((group >> 16U) & 0xFFU)); + output.push_back(static_cast((group >> 8U) & 0xFFU)); + output.push_back(static_cast(group & 0xFFU)); index += 4; } @@ -138,12 +138,12 @@ auto decode_into(const std::string_view input, const std::uint32_t second{ table[static_cast(data[index + 1])]}; if (first == INVALID_SEXTET || second == INVALID_SEXTET || - (second & 0x0Fu) != 0) { + (second & 0x0FU) != 0) { output.resize(base, '\0'); return false; } - output.push_back(static_cast((first << 2u) | (second >> 4u))); + output.push_back(static_cast((first << 2U) | (second >> 4U))); } else if (remaining == 3) { const std::uint32_t first{table[static_cast(data[index])]}; const std::uint32_t second{ @@ -151,14 +151,14 @@ auto decode_into(const std::string_view input, const std::uint32_t third{ table[static_cast(data[index + 2])]}; if (first == INVALID_SEXTET || second == INVALID_SEXTET || - third == INVALID_SEXTET || (third & 0x03u) != 0) { + third == INVALID_SEXTET || (third & 0x03U) != 0) { output.resize(base, '\0'); return false; } - output.push_back(static_cast((first << 2u) | (second >> 4u))); + output.push_back(static_cast((first << 2U) | (second >> 4U))); output.push_back( - static_cast(((second & 0x0Fu) << 4u) | (third >> 2u))); + static_cast(((second & 0x0FU) << 4U) | (third >> 2U))); } return true; @@ -183,7 +183,7 @@ auto base64_encode(const std::string_view input, SecureString &output) -> void { // The input is copied first so that growing the output cannot invalidate it // when the two alias the same storage const SecureString input_copy{input}; - output.reserve(output.size() + ((input_copy.size() + 2) / 3) * 4); + output.reserve(output.size() + (((input_copy.size() + 2) / 3) * 4)); encode(std::string_view{input_copy}, BASE64_ALPHABET, true, output); } @@ -233,7 +233,7 @@ auto base64url_encode(const std::string_view input, SecureString &output) // The input is copied first so that growing the output cannot invalidate it // when the two alias the same storage const SecureString input_copy{input}; - output.reserve(output.size() + ((input_copy.size() + 2) / 3) * 4); + output.reserve(output.size() + (((input_copy.size() + 2) / 3) * 4)); encode(std::string_view{input_copy}, BASE64URL_ALPHABET, false, output); } diff --git a/vendor/core/src/core/crypto/crypto_bignum.h b/vendor/core/src/core/crypto/crypto_bignum.h index 932e779c..949d9343 100644 --- a/vendor/core/src/core/crypto/crypto_bignum.h +++ b/vendor/core/src/core/crypto/crypto_bignum.h @@ -12,6 +12,7 @@ #include #include +#include // std::max, std::min #include // std::array #include // std::size_t #include // std::uint8_t, std::uint64_t @@ -25,8 +26,8 @@ using BignumDoubleWord = uint128_t; struct Bignum { // Enough words for an 8192-bit product plus shifting headroom - static constexpr std::size_t capacity{130}; - std::array words{}; + static constexpr std::size_t CAPACITY{130}; + std::array words{}; std::size_t size{0}; }; @@ -68,7 +69,7 @@ inline auto bignum_from_bytes(const std::string_view input) noexcept -> Bignum { for (std::size_t index = input.size(); index > 0; --index) { const auto byte{static_cast(input[index - 1])}; const auto word_index{bytes_consumed / 8}; - if (word_index >= Bignum::capacity) { + if (word_index >= Bignum::CAPACITY) { break; } @@ -129,7 +130,7 @@ inline auto bignum_bit_length(const Bignum &value) noexcept -> std::size_t { auto top_word{value.words[value.size - 1]}; std::size_t top_bits{0}; while (top_word > 0) { - top_word >>= 1u; + top_word >>= 1U; top_bits += 1; } @@ -143,7 +144,7 @@ inline auto bignum_get_bit(const Bignum &value, const std::size_t bit) noexcept return false; } - return ((value.words[word] >> (bit % 64)) & 1u) != 0; + return ((value.words[word] >> (bit % 64)) & 1U) != 0; } // The same read without the size-dependent early return, so the signing ladders @@ -152,8 +153,8 @@ inline auto bignum_get_bit(const Bignum &value, const std::size_t bit) noexcept inline auto bignum_get_bit_fixed(const Bignum &value, const std::size_t bit) noexcept -> bool { const auto word{bit / 64}; - return word < Bignum::capacity && - ((value.words[word] >> (bit % 64)) & 1u) != 0; + return word < Bignum::CAPACITY && + ((value.words[word] >> (bit % 64)) & 1U) != 0; } // Assumes the result fits in the capacity @@ -163,19 +164,17 @@ inline auto bignum_shift_left(const Bignum &value, const auto word_shift{bits / 64}; const auto bit_shift{bits % 64}; result.size = value.size + word_shift + 1; - if (result.size > Bignum::capacity) { - result.size = Bignum::capacity; - } + result.size = std::min(result.size, Bignum::CAPACITY); for (std::size_t index = 0; index < value.size; ++index) { const auto destination{index + word_shift}; - if (destination >= Bignum::capacity) { + if (destination >= Bignum::CAPACITY) { break; } result.words[destination] |= value.words[index] << bit_shift; - if (bit_shift > 0 && destination + 1 < Bignum::capacity) { - result.words[destination + 1] |= value.words[index] >> (64u - bit_shift); + if (bit_shift > 0 && destination + 1 < Bignum::CAPACITY) { + result.words[destination + 1] |= value.words[index] >> (64U - bit_shift); } } @@ -219,7 +218,7 @@ inline auto bignum_reduce(Bignum &value, const Bignum &modulus) noexcept const auto divisor{modulus.words[0]}; BignumDoubleWord remainder{0}; for (std::size_t index = value.size; index > 0; --index) { - remainder = (remainder << 64u) | value.words[index - 1]; + remainder = (remainder << 64U) | value.words[index - 1]; remainder %= divisor; } @@ -230,14 +229,14 @@ inline auto bignum_reduce(Bignum &value, const Bignum &modulus) noexcept // Normalize so the divisor's top word has its high bit set, which bounds the // error of each quotient word estimate to at most two const auto shift{static_cast( - (64u - (bignum_bit_length(modulus) % 64u)) % 64u)}; + (64U - (bignum_bit_length(modulus) % 64U)) % 64U)}; const auto divisor{shift > 0 ? bignum_shift_left(modulus, shift) : modulus}; auto dividend{shift > 0 ? bignum_shift_left(value, shift) : value}; const auto dividend_words{dividend.size}; const auto quotient_words{dividend_words - divisor_words}; const auto top{divisor.words[divisor_words - 1]}; const auto next{divisor.words[divisor_words - 2]}; - const BignumDoubleWord base{static_cast(1) << 64u}; + const BignumDoubleWord base{static_cast(1) << 64U}; for (std::size_t step = quotient_words + 1; step > 0; --step) { const auto offset{step - 1}; @@ -245,12 +244,12 @@ inline auto bignum_reduce(Bignum &value, const Bignum &modulus) noexcept // Estimate the quotient word from the top two words of the running value const auto numerator{ (static_cast(dividend.words[offset + divisor_words]) - << 64u) | + << 64U) | dividend.words[offset + divisor_words - 1]}; auto estimate{numerator / top}; auto estimate_remainder{numerator % top}; while (estimate >= base || - estimate * next > (estimate_remainder << 64u) + + estimate * next > (estimate_remainder << 64U) + dividend.words[offset + divisor_words - 2]) { estimate -= 1; estimate_remainder += top; @@ -264,17 +263,17 @@ inline auto bignum_reduce(Bignum &value, const Bignum &modulus) noexcept BignumDoubleWord carry{0}; std::uint64_t borrow{0}; for (std::size_t index = 0; index < divisor_words; ++index) { - const auto product{static_cast(quotient_word) * - divisor.words[index] + + const auto product{(static_cast(quotient_word) * + divisor.words[index]) + carry}; - carry = product >> 64u; + carry = product >> 64U; const auto subtrahend{static_cast(product)}; const auto current{dividend.words[offset + index]}; const auto without_subtrahend{current - subtrahend}; - auto next_borrow{current < subtrahend ? 1u : 0u}; + auto next_borrow{current < subtrahend ? 1U : 0U}; const auto result_word{without_subtrahend - borrow}; if (without_subtrahend < borrow) { - next_borrow += 1u; + next_borrow += 1U; } dividend.words[offset + index] = result_word; @@ -284,10 +283,10 @@ inline auto bignum_reduce(Bignum &value, const Bignum &modulus) noexcept const auto current{dividend.words[offset + divisor_words]}; const auto subtrahend{static_cast(carry)}; const auto without_subtrahend{current - subtrahend}; - auto next_borrow{current < subtrahend ? 1u : 0u}; + auto next_borrow{current < subtrahend ? 1U : 0U}; dividend.words[offset + divisor_words] = without_subtrahend - borrow; if (without_subtrahend < borrow) { - next_borrow += 1u; + next_borrow += 1U; } // The estimate was at most one too large, so add the divisor back when the @@ -299,7 +298,7 @@ inline auto bignum_reduce(Bignum &value, const Bignum &modulus) noexcept static_cast(dividend.words[offset + index]) + divisor.words[index] + add_carry}; dividend.words[offset + index] = static_cast(sum); - add_carry = sum >> 64u; + add_carry = sum >> 64U; } dividend.words[offset + divisor_words] += @@ -318,27 +317,26 @@ inline auto bignum_multiply(const Bignum &left, const Bignum &right) noexcept -> Bignum { Bignum result; result.size = left.size + right.size; - if (result.size > Bignum::capacity) { - result.size = Bignum::capacity; - } + result.size = std::min(result.size, Bignum::CAPACITY); for (std::size_t left_index = 0; left_index < left.size; ++left_index) { std::uint64_t carry{0}; for (std::size_t right_index = 0; right_index < right.size; ++right_index) { const auto destination{left_index + right_index}; - if (destination >= Bignum::capacity) { + if (destination >= Bignum::CAPACITY) { break; } - const auto product{static_cast(left.words[left_index]) * - right.words[right_index] + - result.words[destination] + carry}; + const auto product{ + (static_cast(left.words[left_index]) * + right.words[right_index]) + + result.words[destination] + carry}; result.words[destination] = static_cast(product); - carry = static_cast(product >> 64u); + carry = static_cast(product >> 64U); } const auto carry_destination{left_index + right.size}; - if (carry_destination < Bignum::capacity) { + if (carry_destination < Bignum::CAPACITY) { result.words[carry_destination] += carry; } } @@ -379,11 +377,11 @@ inline auto bignum_add(const Bignum &left, const Bignum &right) noexcept const auto second{index < right.size ? right.words[index] : 0}; const auto sum{static_cast(first) + second + carry}; result.words[index] = static_cast(sum); - carry = static_cast(sum >> 64u); + carry = static_cast(sum >> 64U); } result.size = larger; - if (carry > 0 && larger < Bignum::capacity) { + if (carry > 0 && larger < Bignum::CAPACITY) { result.words[larger] = carry; result.size = larger + 1; } @@ -405,7 +403,7 @@ inline auto bignum_shift_right(const Bignum &value, for (std::size_t index = 0; index < result.size; ++index) { auto word{value.words[index + word_shift] >> bit_shift}; if (bit_shift > 0 && index + word_shift + 1 < value.size) { - word |= value.words[index + word_shift + 1] << (64u - bit_shift); + word |= value.words[index + word_shift + 1] << (64U - bit_shift); } result.words[index] = word; @@ -452,7 +450,7 @@ inline auto bignum_mod_multiply(const Bignum &left, const Bignum &right, // becomes even by adding the modulus first, so the result stays an integer inline auto bignum_mod_halve(const Bignum &value, const Bignum &modulus) noexcept -> Bignum { - if ((value.words[0] & 1u) == 0) { + if ((value.words[0] & 1U) == 0) { return bignum_shift_right(value, 1); } @@ -481,12 +479,12 @@ inline auto bignum_mod_inverse(const Bignum &value, return {}; } - while ((first.words[0] & 1u) == 0) { + while ((first.words[0] & 1U) == 0) { first = bignum_shift_right(first, 1); first_coefficient = bignum_mod_halve(first_coefficient, modulus); } - while ((second.words[0] & 1u) == 0) { + while ((second.words[0] & 1U) == 0) { second = bignum_shift_right(second, 1); second_coefficient = bignum_mod_halve(second_coefficient, modulus); } @@ -515,7 +513,7 @@ inline auto bignum_conditional_select(const bool condition, const std::uint64_t mask{std::uint64_t{0} - static_cast(condition)}; Bignum result; - for (std::size_t index = 0; index < Bignum::capacity; ++index) { + for (std::size_t index = 0; index < Bignum::CAPACITY; ++index) { result.words[index] = (when_true.words[index] & mask) | (when_false.words[index] & ~mask); } @@ -538,9 +536,9 @@ inline auto bignum_subtract_fixed(const Bignum &left, const Bignum &right, const auto left_word{left.words[index]}; const auto right_word{right.words[index]}; const auto without_right{left_word - right_word}; - const std::uint64_t borrow_from_right{left_word < right_word ? 1u : 0u}; + const std::uint64_t borrow_from_right{left_word < right_word ? 1U : 0U}; const auto result_word{without_right - borrow}; - const std::uint64_t borrow_from_previous{without_right < borrow ? 1u : 0u}; + const std::uint64_t borrow_from_previous{without_right < borrow ? 1U : 0U}; out.words[index] = result_word; borrow = borrow_from_right | borrow_from_previous; } @@ -561,11 +559,12 @@ inline auto bignum_multiply_fixed(const Bignum &left, const Bignum &right, for (std::size_t right_index = 0; right_index < right_words; ++right_index) { const auto destination{left_index + right_index}; - const auto product{static_cast(left.words[left_index]) * - right.words[right_index] + - result.words[destination] + carry}; + const auto product{ + (static_cast(left.words[left_index]) * + right.words[right_index]) + + result.words[destination] + carry}; result.words[destination] = static_cast(product); - carry = static_cast(product >> 64u); + carry = static_cast(product >> 64U); } result.words[left_index + right_words] = carry; @@ -580,7 +579,7 @@ inline auto bignum_multiply_fixed(const Bignum &left, const Bignum &right, inline auto bignum_drop_low_words(const Bignum &value, const std::size_t words) noexcept -> Bignum { Bignum result; - for (std::size_t index = 0; index + words < Bignum::capacity; ++index) { + for (std::size_t index = 0; index + words < Bignum::CAPACITY; ++index) { result.words[index] = value.words[index + words]; } @@ -609,7 +608,7 @@ inline auto bignum_divide(const Bignum &numerator, for (std::size_t index = bits; index > 0; --index) { remainder = bignum_shift_left(remainder, 1); if (bignum_get_bit(numerator, index - 1)) { - remainder.words[0] |= 1u; + remainder.words[0] |= 1U; if (remainder.size == 0) { remainder.size = 1; } @@ -619,9 +618,7 @@ inline auto bignum_divide(const Bignum &numerator, bignum_subtract_in_place(remainder, denominator); quotient.words[(index - 1) / 64] |= std::uint64_t{1} << ((index - 1) % 64); - if (quotient.size < ((index - 1) / 64) + 1) { - quotient.size = ((index - 1) / 64) + 1; - } + quotient.size = std::max(quotient.size, ((index - 1) / 64) + 1); } } @@ -700,7 +697,7 @@ inline auto field_add_ct(const Bignum &left, const Bignum &right, const auto total{static_cast(left.words[index]) + right.words[index] + carry}; sum.words[index] = static_cast(total); - carry = static_cast(total >> 64u); + carry = static_cast(total >> 64U); } sum.words[width] = carry; @@ -722,7 +719,7 @@ inline auto field_subtract_ct(const Bignum &left, const Bignum &right, const auto total{static_cast(difference.words[index]) + context.modulus.words[index] + carry}; wrapped.words[index] = static_cast(total); - carry = static_cast(total >> 64u); + carry = static_cast(total >> 64U); } wrapped.size = width; @@ -787,7 +784,7 @@ inline auto bignum_to_bytes(const Bignum &value, const std::size_t length) } const auto byte{static_cast( - (value.words[word_index] >> (8 * (index % 8))) & 0xffu)}; + (value.words[word_index] >> (8 * (index % 8))) & 0xffU)}; result[length - 1 - index] = static_cast(byte); } diff --git a/vendor/core/src/core/crypto/crypto_crc32.cc b/vendor/core/src/core/crypto/crypto_crc32.cc index d6c57040..80a32840 100644 --- a/vendor/core/src/core/crypto/crypto_crc32.cc +++ b/vendor/core/src/core/crypto/crypto_crc32.cc @@ -82,12 +82,12 @@ constexpr std::array, 8> CRC32_TABLES{ namespace sourcemeta::core { auto crc32(const std::string_view input) -> std::uint32_t { - return crc32_update(0u, input); + return crc32_update(0U, input); } auto crc32_update(const std::uint32_t previous, const std::string_view input) -> std::uint32_t { - auto checksum{previous ^ 0xFFFFFFFFu}; + auto checksum{previous ^ 0xFFFFFFFFU}; const auto *data{reinterpret_cast(input.data())}; auto remaining{input.size()}; @@ -104,7 +104,7 @@ auto crc32_update(const std::uint32_t previous, const std::string_view input) checksum = crc32_hardware_byte(checksum, *data++); --remaining; } - return checksum ^ 0xFFFFFFFFu; + return checksum ^ 0xFFFFFFFFU; #else // Slice-by-8 software fallback: consume 8 bytes per iteration while (remaining >= 8) { diff --git a/vendor/core/src/core/crypto/crypto_der.h b/vendor/core/src/core/crypto/crypto_der.h index da79f612..2f2c240d 100644 --- a/vendor/core/src/core/crypto/crypto_der.h +++ b/vendor/core/src/core/crypto/crypto_der.h @@ -36,7 +36,7 @@ inline auto der_read(const std::string_view input) if (first < 0x80) { length = first; } else { - const std::size_t count{first & 0x7fu}; + const std::size_t count{first & 0x7fU}; if (count == 0 || count > 4 || position + count > input.size()) { return std::nullopt; } @@ -49,7 +49,7 @@ inline auto der_read(const std::string_view input) } for (std::size_t index{0}; index < count; ++index) { - length = (length << 8u) | static_cast(input[position]); + length = (length << 8U) | static_cast(input[position]); position += 1; } @@ -80,11 +80,11 @@ inline auto der_append_length(std::string &output, const std::size_t length) } std::string octets; - for (std::size_t remaining{length}; remaining > 0; remaining >>= 8u) { - octets.insert(octets.begin(), static_cast(remaining & 0xffu)); + for (std::size_t remaining{length}; remaining > 0; remaining >>= 8U) { + octets.insert(octets.begin(), static_cast(remaining & 0xffU)); } - output.push_back(static_cast(0x80u | octets.size())); + output.push_back(static_cast(0x80U | octets.size())); output.append(octets); } @@ -107,7 +107,7 @@ inline auto der_append_unsigned_integer(std::string &output, // and represents the value zero when nothing remains const auto needs_zero_prefix{ value.empty() || - (static_cast(value.front()) & 0x80u) != 0}; + (static_cast(value.front()) & 0x80U) != 0}; output.push_back('\x02'); der_append_length(output, value.size() + (needs_zero_prefix ? 1 : 0)); if (needs_zero_prefix) { diff --git a/vendor/core/src/core/crypto/crypto_ecdh.cc b/vendor/core/src/core/crypto/crypto_ecdh.cc index 8f0e9162..1b099d5a 100644 --- a/vendor/core/src/core/crypto/crypto_ecdh.cc +++ b/vendor/core/src/core/crypto/crypto_ecdh.cc @@ -31,12 +31,12 @@ auto kdf_concat(const std::string_view shared_secret, -> std::optional { // The construction encodes each length and the key length in bits as a 32-bit // field, so an input that would not fit cannot be represented - constexpr std::size_t maximum_field{ + constexpr std::size_t MAXIMUM_FIELD{ std::numeric_limits::max()}; - if (algorithm_id.size() > maximum_field || - party_u_info.size() > maximum_field || - party_v_info.size() > maximum_field || - derived_key_bytes > maximum_field / 8u) { + if (algorithm_id.size() > MAXIMUM_FIELD || + party_u_info.size() > MAXIMUM_FIELD || + party_v_info.size() > MAXIMUM_FIELD || + derived_key_bytes > MAXIMUM_FIELD / 8U) { return std::nullopt; } @@ -49,7 +49,7 @@ auto kdf_concat(const std::string_view shared_secret, append_length_prefixed(other_info, party_u_info); append_length_prefixed(other_info, party_v_info); append_big_endian_uint32(other_info, - static_cast(derived_key_bytes * 8u)); + static_cast(derived_key_bytes * 8U)); // Each round hashes the big-endian round counter, the shared secret, and // OtherInfo, taking blocks until the requested length is reached diff --git a/vendor/core/src/core/crypto/crypto_ecdh_apple.cc b/vendor/core/src/core/crypto/crypto_ecdh_apple.cc index 484dbbc9..0f6be18d 100644 --- a/vendor/core/src/core/crypto/crypto_ecdh_apple.cc +++ b/vendor/core/src/core/crypto/crypto_ecdh_apple.cc @@ -22,16 +22,16 @@ auto ecdh_derive(const PrivateKey &private_key, const PublicKey &public_key) return std::nullopt; } - auto parameters{CFDictionaryCreate(kCFAllocatorDefault, nullptr, nullptr, 0, - &kCFTypeDictionaryKeyCallBacks, - &kCFTypeDictionaryValueCallBacks)}; + const auto *parameters{CFDictionaryCreate( + kCFAllocatorDefault, nullptr, nullptr, 0, &kCFTypeDictionaryKeyCallBacks, + &kCFTypeDictionaryValueCallBacks)}; if (parameters == nullptr) { return std::nullopt; } // The standard exchange returns the agreed point x coordinate, and the peer // key was validated on the curve when it was parsed - auto shared{SecKeyCopyKeyExchangeResult( + const auto *shared{SecKeyCopyKeyExchangeResult( private_internal->key, kSecKeyAlgorithmECDHKeyExchangeStandard, public_internal->key, parameters, nullptr)}; CFRelease(parameters); diff --git a/vendor/core/src/core/crypto/crypto_eddsa.h b/vendor/core/src/core/crypto/crypto_eddsa.h index a74b8e22..1640a4de 100644 --- a/vendor/core/src/core/crypto/crypto_eddsa.h +++ b/vendor/core/src/core/crypto/crypto_eddsa.h @@ -183,7 +183,7 @@ inline auto edwards_point_encode(const EdwardsPoint &point, const Bignum &prime, std::string encoding{big_endian.rbegin(), big_endian.rend()}; if (bignum_get_bit(x, 0)) { encoding.back() = - static_cast(static_cast(encoding.back()) | 0x80u); + static_cast(static_cast(encoding.back()) | 0x80U); } return encoding; @@ -215,9 +215,9 @@ inline auto edwards25519_decode_point(const std::string_view encoding, // The final bit holds the sign of x, the remaining bits the little-endian y std::string bytes{encoding}; const auto sign_bit{ - static_cast(static_cast(bytes.back()) >> 7) & 1u}; + static_cast(static_cast(bytes.back()) >> 7) & 1U}; bytes.back() = - static_cast(static_cast(bytes.back()) & 0x7fu); + static_cast(static_cast(bytes.back()) & 0x7fU); const auto y{bignum_from_bytes_little_endian(bytes)}; // A y coordinate at or beyond the field prime is not a canonical encoding @@ -388,9 +388,9 @@ inline auto edwards25519_verify(const std::string_view public_key, inline auto edwards25519_prune_scalar(std::string &scalar_bytes) noexcept -> void { scalar_bytes.front() = static_cast( - static_cast(scalar_bytes.front()) & 0xf8u); + static_cast(scalar_bytes.front()) & 0xf8U); scalar_bytes.back() = static_cast( - (static_cast(scalar_bytes.back()) & 0x7fu) | 0x40u); + (static_cast(scalar_bytes.back()) & 0x7fU) | 0x40U); } // The Ed25519 public key derived from the 32-byte private seed, the encoded @@ -501,9 +501,9 @@ inline auto edwards448_decode_point(const std::string_view encoding, // The final bit holds the sign of x, the remaining bits the little-endian y std::string bytes{encoding}; const auto sign_bit{ - static_cast(static_cast(bytes.back()) >> 7) & 1u}; + static_cast(static_cast(bytes.back()) >> 7) & 1U}; bytes.back() = - static_cast(static_cast(bytes.back()) & 0x7fu); + static_cast(static_cast(bytes.back()) & 0x7fU); const auto y{bignum_from_bytes_little_endian(bytes)}; // A y coordinate at or beyond the field prime is not a canonical encoding @@ -651,9 +651,9 @@ inline auto edwards448_verify(const std::string_view public_key, inline auto edwards448_prune_scalar(std::string &scalar_bytes) noexcept -> void { scalar_bytes.front() = static_cast( - static_cast(scalar_bytes.front()) & 0xfcu); + static_cast(scalar_bytes.front()) & 0xfcU); scalar_bytes[55] = - static_cast(static_cast(scalar_bytes[55]) | 0x80u); + static_cast(static_cast(scalar_bytes[55]) | 0x80U); scalar_bytes[56] = '\x00'; } diff --git a/vendor/core/src/core/crypto/crypto_eddsa_cryptokit.mm b/vendor/core/src/core/crypto/crypto_eddsa_cryptokit.mm index 065258d0..35f262b3 100644 --- a/vendor/core/src/core/crypto/crypto_eddsa_cryptokit.mm +++ b/vendor/core/src/core/crypto/crypto_eddsa_cryptokit.mm @@ -1,5 +1,6 @@ #include "crypto_aes_apple.h" #include "crypto_eddsa_apple.h" +#include "crypto_kdf_apple.h" #include // std::memcpy @@ -117,3 +118,71 @@ return true; } } + +extern "C" auto sourcemeta_core_hkdf_extract_cryptokit( + int hash, const unsigned char *input_key_material, + std::size_t input_key_material_size, const unsigned char *salt, + std::size_t salt_size, unsigned char *output, std::size_t output_size) + -> bool { + @autoreleasepool { + NSData *const key{[NSData dataWithBytes:input_key_material + length:input_key_material_size]}; + NSData *const salt_data{[NSData dataWithBytes:salt length:salt_size]}; + NSData *const result{[SourcemetaCoreHKDF extractKeyWithHash:hash + inputKeyMaterial:key + salt:salt_data]}; + if (result == nil || result.length != output_size || output == nullptr) { + return false; + } + + std::memcpy(output, result.bytes, output_size); + return true; + } +} + +extern "C" auto sourcemeta_core_hkdf_expand_cryptokit( + int hash, const unsigned char *pseudorandom_key, + std::size_t pseudorandom_key_size, const unsigned char *info, + std::size_t info_size, unsigned char *output, std::size_t length) -> bool { + @autoreleasepool { + NSData *const key{[NSData dataWithBytes:pseudorandom_key + length:pseudorandom_key_size]}; + NSData *const info_data{[NSData dataWithBytes:info length:info_size]}; + NSData *const result{[SourcemetaCoreHKDF + expandKeyWithHash:hash + pseudoRandomKey:key + info:info_data + outputByteCount:static_cast(length)]}; + if (result == nil || result.length != length || output == nullptr) { + return false; + } + + std::memcpy(output, result.bytes, length); + return true; + } +} + +extern "C" auto sourcemeta_core_hkdf_derive_cryptokit( + int hash, const unsigned char *input_key_material, + std::size_t input_key_material_size, const unsigned char *salt, + std::size_t salt_size, const unsigned char *info, std::size_t info_size, + unsigned char *output, std::size_t length) -> bool { + @autoreleasepool { + NSData *const key{[NSData dataWithBytes:input_key_material + length:input_key_material_size]}; + NSData *const salt_data{[NSData dataWithBytes:salt length:salt_size]}; + NSData *const info_data{[NSData dataWithBytes:info length:info_size]}; + NSData *const result{[SourcemetaCoreHKDF + deriveKeyWithHash:hash + inputKeyMaterial:key + salt:salt_data + info:info_data + outputByteCount:static_cast(length)]}; + if (result == nil || result.length != length || output == nullptr) { + return false; + } + + std::memcpy(output, result.bytes, length); + return true; + } +} diff --git a/vendor/core/src/core/crypto/crypto_eddsa_cryptokit.swift b/vendor/core/src/core/crypto/crypto_eddsa_cryptokit.swift index d2e82fc6..0491a646 100644 --- a/vendor/core/src/core/crypto/crypto_eddsa_cryptokit.swift +++ b/vendor/core/src/core/crypto/crypto_eddsa_cryptokit.swift @@ -66,3 +66,104 @@ public final class SourcemetaCoreAESGCM: NSObject { box, using: SymmetricKey(data: key), authenticating: associatedData) } } + +// HMAC-based key derivation (RFC 5869), which the Apple Security framework +// does not expose through its C API and CommonCrypto does not implement at +// all. CryptoKit provides it since macOS 11, so every entry point reports an +// absent result on an older system and the caller composes the derivation from +// HMAC instead +@objc(SourcemetaCoreHKDF) +public final class SourcemetaCoreHKDF: NSObject { + @available(macOS 11.0, *) + private static func derive( + _ hash: H.Type, inputKeyMaterial: Data, salt: Data, info: Data, + outputByteCount: Int) -> Data { + let key = HKDF.deriveKey( + inputKeyMaterial: SymmetricKey(data: inputKeyMaterial), salt: salt, + info: info, outputByteCount: outputByteCount) + return key.withUnsafeBytes { Data($0) } + } + + @available(macOS 11.0, *) + private static func extract( + _ hash: H.Type, inputKeyMaterial: Data, salt: Data) -> Data { + let code = HKDF.extract( + inputKeyMaterial: SymmetricKey(data: inputKeyMaterial), salt: salt) + return code.withUnsafeBytes { Data($0) } + } + + @available(macOS 11.0, *) + private static func expand( + _ hash: H.Type, pseudoRandomKey: Data, info: Data, + outputByteCount: Int) -> Data { + let key = HKDF.expand( + pseudoRandomKey: pseudoRandomKey, info: info, + outputByteCount: outputByteCount) + return key.withUnsafeBytes { Data($0) } + } + + @objc public static func deriveKey(hash: Int, inputKeyMaterial: Data, + salt: Data, info: Data, + outputByteCount: Int) -> Data? { + guard #available(macOS 11.0, *) else { + return nil + } + + switch hash { + case 0: + return derive(SHA256.self, inputKeyMaterial: inputKeyMaterial, + salt: salt, info: info, outputByteCount: outputByteCount) + case 1: + return derive(SHA384.self, inputKeyMaterial: inputKeyMaterial, + salt: salt, info: info, outputByteCount: outputByteCount) + case 2: + return derive(SHA512.self, inputKeyMaterial: inputKeyMaterial, + salt: salt, info: info, outputByteCount: outputByteCount) + default: + return nil + } + } + + @objc public static func extractKey(hash: Int, inputKeyMaterial: Data, + salt: Data) -> Data? { + guard #available(macOS 11.0, *) else { + return nil + } + + switch hash { + case 0: + return extract(SHA256.self, inputKeyMaterial: inputKeyMaterial, + salt: salt) + case 1: + return extract(SHA384.self, inputKeyMaterial: inputKeyMaterial, + salt: salt) + case 2: + return extract(SHA512.self, inputKeyMaterial: inputKeyMaterial, + salt: salt) + default: + return nil + } + } + + @objc public static func expandKey(hash: Int, pseudoRandomKey: Data, + info: Data, + outputByteCount: Int) -> Data? { + guard #available(macOS 11.0, *) else { + return nil + } + + switch hash { + case 0: + return expand(SHA256.self, pseudoRandomKey: pseudoRandomKey, info: info, + outputByteCount: outputByteCount) + case 1: + return expand(SHA384.self, pseudoRandomKey: pseudoRandomKey, info: info, + outputByteCount: outputByteCount) + case 2: + return expand(SHA512.self, pseudoRandomKey: pseudoRandomKey, info: info, + outputByteCount: outputByteCount) + default: + return nil + } + } +} diff --git a/vendor/core/src/core/crypto/crypto_fnv128.cc b/vendor/core/src/core/crypto/crypto_fnv128.cc index 8ce5a30e..e9c616f9 100644 --- a/vendor/core/src/core/crypto/crypto_fnv128.cc +++ b/vendor/core/src/core/crypto/crypto_fnv128.cc @@ -14,22 +14,22 @@ constexpr std::uint64_t OFFSET_BASIS_LOW{0x62b821756295c58dULL}; // The 128-bit FNV prime is 2^88 + 2^8 + 0x3b (draft-eastlake-fnv Section 4), // so multiplying by it reduces to (value << 88) + (value << 8) + value * 0x3b // modulo 2^128, computed here over two 64-bit limbs -inline constexpr auto multiply_by_prime(std::uint64_t &high, - std::uint64_t &low) noexcept -> void { +constexpr auto multiply_by_prime(std::uint64_t &high, + std::uint64_t &low) noexcept -> void { // value << 88, whose low limb is always zero - const auto shift_88_high = low << 24u; + const auto shift_88_high = low << 24U; // value << 8 - const auto shift_8_high = (high << 8u) | (low >> 56u); - const auto shift_8_low = low << 8u; + const auto shift_8_high = (high << 8U) | (low >> 56U); + const auto shift_8_low = low << 8U; // value * 0x3b, multiplying the low limb in 32-bit halves to portably // capture the carry into the high limb const auto low_half_product = (low & 0xffffffffULL) * 0x3bULL; - const auto high_half_product = (low >> 32u) * 0x3bULL; - const auto small_low = low_half_product + (high_half_product << 32u); + const auto high_half_product = (low >> 32U) * 0x3bULL; + const auto small_low = low_half_product + (high_half_product << 32U); const auto small_carry = - (high_half_product + (low_half_product >> 32u)) >> 32u; + (high_half_product + (low_half_product >> 32U)) >> 32U; const auto small_high = (high * 0x3bULL) + small_carry; const auto result_low = shift_8_low + small_low; @@ -54,10 +54,10 @@ auto fnv128_digest(const std::string_view input) } std::array result{}; - for (std::uint64_t index = 0u; index < 8u; ++index) { - const auto shift = 8u * (7u - index); - result[index] = static_cast((high >> shift) & 0xffu); - result[8u + index] = static_cast((low >> shift) & 0xffu); + for (std::uint64_t index = 0U; index < 8U; ++index) { + const auto shift = 8U * (7U - index); + result[index] = static_cast((high >> shift) & 0xffU); + result[8U + index] = static_cast((low >> shift) & 0xffU); } return result; diff --git a/vendor/core/src/core/crypto/crypto_helpers.h b/vendor/core/src/core/crypto/crypto_helpers.h index 366dee45..ee5816c3 100644 --- a/vendor/core/src/core/crypto/crypto_helpers.h +++ b/vendor/core/src/core/crypto/crypto_helpers.h @@ -26,10 +26,10 @@ inline constexpr std::size_t MAXIMUM_KEY_BYTES{512}; // their counters and length fields inline auto append_big_endian_uint32(std::string &target, const std::uint32_t value) -> void { - target.push_back(static_cast((value >> 24u) & 0xffu)); - target.push_back(static_cast((value >> 16u) & 0xffu)); - target.push_back(static_cast((value >> 8u) & 0xffu)); - target.push_back(static_cast(value & 0xffu)); + target.push_back(static_cast((value >> 24U) & 0xffU)); + target.push_back(static_cast((value >> 16U) & 0xffU)); + target.push_back(static_cast((value >> 8U) & 0xffU)); + target.push_back(static_cast(value & 0xffU)); } // The same guard for a raw buffer, so a secret that a fixed-size digest array diff --git a/vendor/core/src/core/crypto/crypto_hkdf.cc b/vendor/core/src/core/crypto/crypto_hkdf.cc new file mode 100644 index 00000000..4f50a183 --- /dev/null +++ b/vendor/core/src/core/crypto/crypto_hkdf.cc @@ -0,0 +1,148 @@ +#include +#include + +#include "crypto_kdf.h" + +#include // std::array +#include // std::size_t +#include // std::uint8_t +#include // std::optional, std::nullopt +#include // std::runtime_error +#include // std::string +#include // std::string_view + +namespace { + +// RFC 5869 Section 2.3: L is at most 255*HashLen octets +auto is_valid_length(const sourcemeta::core::KDFHash hash, + const std::size_t length) noexcept -> bool { + return length <= 255 * sourcemeta::core::kdf_digest_bytes(hash); +} + +auto as_bytes(std::string &value) noexcept -> unsigned char * { + return reinterpret_cast(value.data()); +} + +template +auto extract(const sourcemeta::core::KDFHash hash, const std::string_view salt, + const std::string_view input_key_material) + -> std::array { + std::array output{}; + // A refused extraction would otherwise leave the zero-filled buffer standing + // in for a pseudorandom key, which is both wrong and predictable, so it + // throws as the HMAC underneath does rather than reporting a derivation that + // never happened + if (!sourcemeta::core::hkdf_extract(hash, salt, input_key_material, + output.data())) { + throw std::runtime_error("Could not extract an HKDF pseudorandom key"); + } + + return output; +} + +auto expand(const sourcemeta::core::KDFHash hash, + const std::string_view pseudorandom_key, + const std::string_view info, const std::size_t length) + -> std::optional { + // RFC 5869 Section 2.3: the pseudorandom key is at least HashLen octets, + // which the output of the extract step always is + if (pseudorandom_key.size() < sourcemeta::core::kdf_digest_bytes(hash) || + !is_valid_length(hash, length)) { + return std::nullopt; + } + + // An empty request needs no block at all, and asking a backend for one + // invites disagreement over whether that is an error + if (length == 0) { + return std::string{}; + } + + std::string output(length, '\0'); + if (!sourcemeta::core::hkdf_expand(hash, pseudorandom_key, info, + as_bytes(output), length)) { + return std::nullopt; + } + + return output; +} + +auto derive(const sourcemeta::core::KDFHash hash, + const std::string_view input_key_material, + const std::string_view salt, const std::string_view info, + const std::size_t length) -> std::optional { + if (!is_valid_length(hash, length)) { + return std::nullopt; + } + + if (length == 0) { + return std::string{}; + } + + std::string output(length, '\0'); + if (!sourcemeta::core::hkdf_derive(hash, input_key_material, salt, info, + as_bytes(output), length)) { + return std::nullopt; + } + + return output; +} + +} // namespace + +namespace sourcemeta::core { + +auto hkdf_sha256_extract(const std::string_view salt, + const std::string_view input_key_material) + -> std::array { + return extract<32>(KDFHash::SHA256, salt, input_key_material); +} + +auto hkdf_sha256_expand(const std::string_view pseudorandom_key, + const std::string_view info, const std::size_t length) + -> std::optional { + return expand(KDFHash::SHA256, pseudorandom_key, info, length); +} + +auto hkdf_sha256(const std::string_view input_key_material, + const std::string_view salt, const std::string_view info, + const std::size_t length) -> std::optional { + return derive(KDFHash::SHA256, input_key_material, salt, info, length); +} + +auto hkdf_sha384_extract(const std::string_view salt, + const std::string_view input_key_material) + -> std::array { + return extract<48>(KDFHash::SHA384, salt, input_key_material); +} + +auto hkdf_sha384_expand(const std::string_view pseudorandom_key, + const std::string_view info, const std::size_t length) + -> std::optional { + return expand(KDFHash::SHA384, pseudorandom_key, info, length); +} + +auto hkdf_sha384(const std::string_view input_key_material, + const std::string_view salt, const std::string_view info, + const std::size_t length) -> std::optional { + return derive(KDFHash::SHA384, input_key_material, salt, info, length); +} + +auto hkdf_sha512_extract(const std::string_view salt, + const std::string_view input_key_material) + -> std::array { + return extract<64>(KDFHash::SHA512, salt, input_key_material); +} + +auto hkdf_sha512_expand(const std::string_view pseudorandom_key, + const std::string_view info, const std::size_t length) + -> std::optional { + return expand(KDFHash::SHA512, pseudorandom_key, info, length); +} + +auto hkdf_sha512(const std::string_view input_key_material, + const std::string_view salt, const std::string_view info, + const std::size_t length) -> std::optional { + return derive(KDFHash::SHA512, input_key_material, salt, info, length); +} + +} // namespace sourcemeta::core diff --git a/vendor/core/src/core/crypto/crypto_hkdf_apple.cc b/vendor/core/src/core/crypto/crypto_hkdf_apple.cc new file mode 100644 index 00000000..e1f16890 --- /dev/null +++ b/vendor/core/src/core/crypto/crypto_hkdf_apple.cc @@ -0,0 +1,73 @@ +#include "crypto_hkdf_loop.h" +#include "crypto_kdf.h" +#include "crypto_kdf_apple.h" + +#include // std::size_t +#include // std::string_view +#include // std::unreachable + +namespace { + +// The selector the Swift shim switches on, kept in step with the shared +// enumeration rather than relying on its underlying values +auto to_selector(const sourcemeta::core::KDFHash hash) noexcept -> int { + switch (hash) { + case sourcemeta::core::KDFHash::SHA256: + return 0; + case sourcemeta::core::KDFHash::SHA384: + return 1; + case sourcemeta::core::KDFHash::SHA512: + return 2; + } + + std::unreachable(); +} + +auto octets(const std::string_view value) noexcept -> const unsigned char * { + return reinterpret_cast(value.data()); +} + +} // namespace + +namespace sourcemeta::core { + +auto hkdf_extract(const KDFHash hash, const std::string_view salt, + const std::string_view input_key_material, + unsigned char *const output) -> bool { + if (sourcemeta_core_hkdf_extract_cryptokit( + to_selector(hash), octets(input_key_material), + input_key_material.size(), octets(salt), salt.size(), output, + kdf_digest_bytes(hash))) { + return true; + } + + return hkdf_extract_loop(hash, salt, input_key_material, output); +} + +auto hkdf_expand(const KDFHash hash, const std::string_view pseudorandom_key, + const std::string_view info, unsigned char *const output, + const std::size_t length) -> bool { + if (sourcemeta_core_hkdf_expand_cryptokit( + to_selector(hash), octets(pseudorandom_key), pseudorandom_key.size(), + octets(info), info.size(), output, length)) { + return true; + } + + return hkdf_expand_loop(hash, pseudorandom_key, info, output, length); +} + +auto hkdf_derive(const KDFHash hash, const std::string_view input_key_material, + const std::string_view salt, const std::string_view info, + unsigned char *const output, const std::size_t length) + -> bool { + if (sourcemeta_core_hkdf_derive_cryptokit( + to_selector(hash), octets(input_key_material), + input_key_material.size(), octets(salt), salt.size(), octets(info), + info.size(), output, length)) { + return true; + } + + return hkdf_derive_loop(hash, input_key_material, salt, info, output, length); +} + +} // namespace sourcemeta::core diff --git a/vendor/core/src/core/crypto/crypto_hkdf_loop.h b/vendor/core/src/core/crypto/crypto_hkdf_loop.h new file mode 100644 index 00000000..c41676e2 --- /dev/null +++ b/vendor/core/src/core/crypto/crypto_hkdf_loop.h @@ -0,0 +1,138 @@ +#ifndef SOURCEMETA_CORE_CRYPTO_HKDF_LOOP_H_ +#define SOURCEMETA_CORE_CRYPTO_HKDF_LOOP_H_ + +#include +#include +#include +#include + +#include "crypto_kdf.h" + +#include // std::copy_n, std::min +#include // std::array +#include // std::size_t +#include // std::uint8_t +#include // std::string +#include // std::string_view +#include // std::unreachable + +namespace sourcemeta::core { + +// RFC 5869 builds entirely on HMAC, so a backend without a dedicated key +// derivation primitive composes one from the HMAC it already provides + +// The counterpart of the string scope for a fixed digest buffer, since the +// HMAC underneath throws on two of the backends and an unwind would otherwise +// leave a block of derived key material behind +struct SecureDigestScope { + explicit SecureDigestScope( + std::array &value) noexcept + : target{value} {} + SecureDigestScope(const SecureDigestScope &) = delete; + auto operator=(const SecureDigestScope &) -> SecureDigestScope & = delete; + SecureDigestScope(SecureDigestScope &&) = delete; + auto operator=(SecureDigestScope &&) -> SecureDigestScope & = delete; + ~SecureDigestScope() { + secure_zero(this->target.data(), this->target.size()); + } + std::array ⌖ +}; + +inline auto +hkdf_loop_hmac(const KDFHash hash, const std::string_view key, + const std::string_view message, + std::array &output) + -> std::size_t { + switch (hash) { + case KDFHash::SHA256: { + auto digest{hmac_sha256_digest(key, message)}; + std::copy_n(digest.begin(), digest.size(), output.begin()); + secure_zero(digest.data(), digest.size()); + return digest.size(); + } + case KDFHash::SHA384: { + auto digest{hmac_sha384_digest(key, message)}; + std::copy_n(digest.begin(), digest.size(), output.begin()); + secure_zero(digest.data(), digest.size()); + return digest.size(); + } + case KDFHash::SHA512: { + auto digest{hmac_sha512_digest(key, message)}; + std::copy_n(digest.begin(), digest.size(), output.begin()); + secure_zero(digest.data(), digest.size()); + return digest.size(); + } + } + + std::unreachable(); +} + +// RFC 5869 Section 2.2: PRK = HMAC-Hash(salt, IKM), noting that the salt keys +// the HMAC while the input keying material is the message +inline auto hkdf_extract_loop(const KDFHash hash, const std::string_view salt, + const std::string_view input_key_material, + unsigned char *const output) -> bool { + std::array digest{}; + const SecureDigestScope scope{digest}; + const auto size{hkdf_loop_hmac(hash, salt, input_key_material, digest)}; + std::copy_n(digest.begin(), size, output); + return true; +} + +// RFC 5869 Section 2.3: T(i) = HMAC-Hash(PRK, T(i-1) | info | i), with the +// output being the first L octets of the concatenated blocks +inline auto hkdf_expand_loop(const KDFHash hash, + const std::string_view pseudorandom_key, + const std::string_view info, + unsigned char *const output, + const std::size_t length) -> bool { + std::array block{}; + const SecureDigestScope block_scope{block}; + std::size_t block_size{0}; + std::size_t produced{0}; + std::uint8_t counter{0}; + + std::string message; + const SecureStringScope message_scope{message}; + message.reserve(kdf_digest_bytes(hash) + info.size() + 1); + + while (produced < length) { + // The shared entry point caps the length at 255 digests, so the single + // octet the block index occupies cannot wrap + counter = static_cast(counter + 1); + message.assign(reinterpret_cast(block.data()), block_size); + message.append(info); + message.push_back(static_cast(counter)); + block_size = hkdf_loop_hmac(hash, pseudorandom_key, message, block); + const auto usable{std::min(block_size, length - produced)}; + std::copy_n(block.begin(), usable, output + produced); + produced += usable; + } + + return true; +} + +// RFC 5869 Section 2: the extract-then-expand paradigm, with the intermediate +// pseudorandom key wiped once it has served the expansion +inline auto +hkdf_derive_loop(const KDFHash hash, const std::string_view input_key_material, + const std::string_view salt, const std::string_view info, + unsigned char *const output, const std::size_t length) + -> bool { + std::array pseudorandom_key{}; + const SecureDigestScope scope{pseudorandom_key}; + if (!hkdf_extract_loop(hash, salt, input_key_material, + pseudorandom_key.data())) { + return false; + } + + return hkdf_expand_loop( + hash, + std::string_view{reinterpret_cast(pseudorandom_key.data()), + kdf_digest_bytes(hash)}, + info, output, length); +} + +} // namespace sourcemeta::core + +#endif diff --git a/vendor/core/src/core/crypto/crypto_hkdf_openssl.cc b/vendor/core/src/core/crypto/crypto_hkdf_openssl.cc new file mode 100644 index 00000000..5a72bad7 --- /dev/null +++ b/vendor/core/src/core/crypto/crypto_hkdf_openssl.cc @@ -0,0 +1,117 @@ +#include "crypto_kdf.h" + +#include // OSSL_KDF_PARAM_* +#include // EVP_KDF_*, EVP_KDF_HKDF_MODE_* +#include // OSSL_PARAM, OSSL_PARAM_construct_* + +#include // std::array +#include // std::size_t +#include // std::runtime_error +#include // std::string_view +#include // std::unreachable + +namespace { + +// Performing the derivation through the key derivation interface, rather than +// composing it from separate HMAC calls, is what keeps the operation inside a +// validated provider such as the FIPS one +struct HkdfAlgorithm { + HkdfAlgorithm() : handle{EVP_KDF_fetch(nullptr, "HKDF", nullptr)} { + if (this->handle == nullptr) { + throw std::runtime_error("Could not fetch the OpenSSL HKDF algorithm"); + } + } + ~HkdfAlgorithm() { EVP_KDF_free(this->handle); } + HkdfAlgorithm(const HkdfAlgorithm &) = delete; + HkdfAlgorithm(HkdfAlgorithm &&) = delete; + auto operator=(const HkdfAlgorithm &) -> HkdfAlgorithm & = delete; + auto operator=(HkdfAlgorithm &&) -> HkdfAlgorithm & = delete; + EVP_KDF *handle; +}; + +auto digest_name(const sourcemeta::core::KDFHash hash) noexcept -> const + char * { + switch (hash) { + case sourcemeta::core::KDFHash::SHA256: + return "SHA256"; + case sourcemeta::core::KDFHash::SHA384: + return "SHA384"; + case sourcemeta::core::KDFHash::SHA512: + return "SHA512"; + } + + std::unreachable(); +} + +// An empty view may carry a null pointer, which the parameter interface would +// read as an absent parameter rather than as a zero-length one +constexpr unsigned char EMPTY{0x00}; + +auto octets(const std::string_view value) noexcept -> unsigned char * { + return value.empty() + ? const_cast(&EMPTY) + : const_cast( + reinterpret_cast(value.data())); +} + +// The parameter interface is not const-qualified but never writes through the +// pointers it is handed +auto derive(const sourcemeta::core::KDFHash hash, const int mode, + const std::string_view key, const std::string_view salt, + const std::string_view info, unsigned char *const output, + const std::size_t length) -> bool { + static const HkdfAlgorithm algorithm; + auto *context{EVP_KDF_CTX_new(algorithm.handle)}; + if (context == nullptr) { + return false; + } + + int kdf_mode{mode}; + std::array parameters{ + {OSSL_PARAM_construct_utf8_string( + OSSL_KDF_PARAM_DIGEST, const_cast(digest_name(hash)), 0), + OSSL_PARAM_construct_int(OSSL_KDF_PARAM_MODE, &kdf_mode), + OSSL_PARAM_construct_octet_string(OSSL_KDF_PARAM_KEY, octets(key), + key.size()), + OSSL_PARAM_construct_octet_string(OSSL_KDF_PARAM_SALT, octets(salt), + salt.size()), + OSSL_PARAM_construct_octet_string(OSSL_KDF_PARAM_INFO, octets(info), + info.size()), + OSSL_PARAM_construct_end()}}; + + const auto success{ + EVP_KDF_derive(context, output, length, parameters.data()) == 1}; + EVP_KDF_CTX_free(context); + return success; +} + +} // namespace + +namespace sourcemeta::core { + +auto hkdf_extract(const KDFHash hash, const std::string_view salt, + const std::string_view input_key_material, + unsigned char *const output) -> bool { + // The extract step yields exactly one digest, which is the only length the + // provider accepts in this mode + return derive(hash, EVP_KDF_HKDF_MODE_EXTRACT_ONLY, input_key_material, salt, + {}, output, kdf_digest_bytes(hash)); +} + +auto hkdf_expand(const KDFHash hash, const std::string_view pseudorandom_key, + const std::string_view info, unsigned char *const output, + const std::size_t length) -> bool { + return derive(hash, EVP_KDF_HKDF_MODE_EXPAND_ONLY, pseudorandom_key, {}, info, + output, length); +} + +auto hkdf_derive(const KDFHash hash, const std::string_view input_key_material, + const std::string_view salt, const std::string_view info, + unsigned char *const output, const std::size_t length) + -> bool { + // Both steps in one call, so the pseudorandom key never leaves the provider + return derive(hash, EVP_KDF_HKDF_MODE_EXTRACT_AND_EXPAND, input_key_material, + salt, info, output, length); +} + +} // namespace sourcemeta::core diff --git a/vendor/core/src/core/crypto/crypto_hkdf_other.cc b/vendor/core/src/core/crypto/crypto_hkdf_other.cc new file mode 100644 index 00000000..737dc8b1 --- /dev/null +++ b/vendor/core/src/core/crypto/crypto_hkdf_other.cc @@ -0,0 +1,28 @@ +#include "crypto_hkdf_loop.h" +#include "crypto_kdf.h" + +#include // std::size_t +#include // std::string_view + +namespace sourcemeta::core { + +auto hkdf_extract(const KDFHash hash, const std::string_view salt, + const std::string_view input_key_material, + unsigned char *const output) -> bool { + return hkdf_extract_loop(hash, salt, input_key_material, output); +} + +auto hkdf_expand(const KDFHash hash, const std::string_view pseudorandom_key, + const std::string_view info, unsigned char *const output, + const std::size_t length) -> bool { + return hkdf_expand_loop(hash, pseudorandom_key, info, output, length); +} + +auto hkdf_derive(const KDFHash hash, const std::string_view input_key_material, + const std::string_view salt, const std::string_view info, + unsigned char *const output, const std::size_t length) + -> bool { + return hkdf_derive_loop(hash, input_key_material, salt, info, output, length); +} + +} // namespace sourcemeta::core diff --git a/vendor/core/src/core/crypto/crypto_hkdf_windows.cc b/vendor/core/src/core/crypto/crypto_hkdf_windows.cc new file mode 100644 index 00000000..8fa0f4c5 --- /dev/null +++ b/vendor/core/src/core/crypto/crypto_hkdf_windows.cc @@ -0,0 +1,153 @@ +#include "crypto_hkdf_loop.h" +#include "crypto_kdf.h" + +#define WIN32_LEAN_AND_MEAN +#define NOMINMAX +#include // ULONG, PUCHAR + +#include // BCrypt*, BCRYPT_* + +#include // std::size_t +#include // std::numeric_limits +#include // std::string_view +#include // std::unreachable + +namespace { + +auto hash_name(const sourcemeta::core::KDFHash hash) noexcept -> LPCWSTR { + switch (hash) { + case sourcemeta::core::KDFHash::SHA256: + return BCRYPT_SHA256_ALGORITHM; + case sourcemeta::core::KDFHash::SHA384: + return BCRYPT_SHA384_ALGORITHM; + case sourcemeta::core::KDFHash::SHA512: + return BCRYPT_SHA512_ALGORITHM; + } + + std::unreachable(); +} + +auto hash_name_bytes(const sourcemeta::core::KDFHash hash) noexcept -> ULONG { + switch (hash) { + case sourcemeta::core::KDFHash::SHA256: + return sizeof(BCRYPT_SHA256_ALGORITHM); + case sourcemeta::core::KDFHash::SHA384: + return sizeof(BCRYPT_SHA384_ALGORITHM); + case sourcemeta::core::KDFHash::SHA512: + return sizeof(BCRYPT_SHA512_ALGORITHM); + } + + std::unreachable(); +} + +auto as_native(const std::string_view value) noexcept -> PUCHAR { + return reinterpret_cast(const_cast(value.data())); +} + +// The provider carries the derivation from the input keying material through +// to the output. Whether the secret is finalized as input keying material to +// be salted, or as an already extracted pseudorandom key, is what separates +// the two RFC 5869 steps here +auto derive(const sourcemeta::core::KDFHash hash, const std::string_view secret, + const std::string_view salt, const bool secret_is_pseudorandom_key, + const std::string_view info, unsigned char *const output, + const std::size_t length) -> bool { + // The provider takes its lengths as a 32-bit count + constexpr auto maximum{ + static_cast(std::numeric_limits::max())}; + if (secret.size() > maximum || salt.size() > maximum || + info.size() > maximum || length > maximum) { + return false; + } + + BCRYPT_ALG_HANDLE algorithm{nullptr}; + // The key derivation provider is absent before Windows 10 version 1803, and + // a caller falls back to composing the derivation from HMAC + if (!BCRYPT_SUCCESS(BCryptOpenAlgorithmProvider( + &algorithm, BCRYPT_HKDF_ALGORITHM, nullptr, 0))) { + return false; + } + + BCRYPT_KEY_HANDLE key{nullptr}; + auto success{BCRYPT_SUCCESS( + BCryptGenerateSymmetricKey(algorithm, &key, nullptr, 0, as_native(secret), + static_cast(secret.size()), 0))}; + + success = + success && + BCRYPT_SUCCESS(BCryptSetProperty( + key, BCRYPT_HKDF_HASH_ALGORITHM, + reinterpret_cast(const_cast(hash_name(hash))), + hash_name_bytes(hash), 0)); + + if (secret_is_pseudorandom_key) { + // RFC 5869 Section 2.3 alone, where the secret is already the + // pseudorandom key and no extract step runs + success = success && BCRYPT_SUCCESS(BCryptSetProperty( + key, BCRYPT_HKDF_PRK_AND_FINALIZE, nullptr, 0, 0)); + } else { + // RFC 5869 Section 2.2 followed by Section 2.3 + success = + success && BCRYPT_SUCCESS(BCryptSetProperty( + key, BCRYPT_HKDF_SALT_AND_FINALIZE, as_native(salt), + static_cast(salt.size()), 0)); + } + + BCryptBuffer buffers[1]{}; + buffers[0].BufferType = KDF_HKDF_INFO; + buffers[0].cbBuffer = static_cast(info.size()); + buffers[0].pvBuffer = as_native(info); + BCryptBufferDesc parameters{}; + parameters.ulVersion = BCRYPTBUFFER_VERSION; + parameters.cBuffers = 1; + parameters.pBuffers = buffers; + + ULONG produced{0}; + success = success && BCRYPT_SUCCESS(BCryptKeyDerivation( + key, ¶meters, output, static_cast(length), + &produced, 0)); + success = success && produced == static_cast(length); + + if (key != nullptr) { + BCryptDestroyKey(key); + } + + BCryptCloseAlgorithmProvider(algorithm, 0); + return success; +} + +} // namespace + +namespace sourcemeta::core { + +auto hkdf_extract(const KDFHash hash, const std::string_view salt, + const std::string_view input_key_material, + unsigned char *const output) -> bool { + // The provider finalizes the pseudorandom key inside the key object and + // never surfaces it, so the extract step alone is composed from HMAC, which + // is itself a native primitive here + return hkdf_extract_loop(hash, salt, input_key_material, output); +} + +auto hkdf_expand(const KDFHash hash, const std::string_view pseudorandom_key, + const std::string_view info, unsigned char *const output, + const std::size_t length) -> bool { + if (derive(hash, pseudorandom_key, {}, true, info, output, length)) { + return true; + } + + return hkdf_expand_loop(hash, pseudorandom_key, info, output, length); +} + +auto hkdf_derive(const KDFHash hash, const std::string_view input_key_material, + const std::string_view salt, const std::string_view info, + unsigned char *const output, const std::size_t length) + -> bool { + if (derive(hash, input_key_material, salt, false, info, output, length)) { + return true; + } + + return hkdf_derive_loop(hash, input_key_material, salt, info, output, length); +} + +} // namespace sourcemeta::core diff --git a/vendor/core/src/core/crypto/crypto_kdf.h b/vendor/core/src/core/crypto/crypto_kdf.h new file mode 100644 index 00000000..54ea7474 --- /dev/null +++ b/vendor/core/src/core/crypto/crypto_kdf.h @@ -0,0 +1,57 @@ +#ifndef SOURCEMETA_CORE_CRYPTO_KDF_H_ +#define SOURCEMETA_CORE_CRYPTO_KDF_H_ + +#include // std::size_t +#include // std::uint8_t +#include // std::string_view +#include // std::unreachable + +namespace sourcemeta::core { + +// The hash a key derivation instantiates its HMAC with (RFC 5869 Section 2.1) +enum class KDFHash : std::uint8_t { SHA256, SHA384, SHA512 }; + +// The digest length in octets, which RFC 5869 calls HashLen +inline auto kdf_digest_bytes(const KDFHash hash) noexcept -> std::size_t { + switch (hash) { + case KDFHash::SHA256: + return 32; + case KDFHash::SHA384: + return 48; + case KDFHash::SHA512: + return 64; + } + + std::unreachable(); +} + +// The widest digest any of the above produces, so a caller can hold one on the +// stack without knowing which hash it asked for +inline constexpr std::size_t KDF_MAXIMUM_DIGEST_BYTES{64}; + +// The raw RFC 5869 steps, defined once per backend. Every length check lives +// in the shared functions, so these assume an output buffer of the digest +// length for the extract step, and of the requested length, itself no more +// than 255 digests, for the expand step + +// RFC 5869 Section 2.2, where an empty salt stands for the digest-length run +// of zeros that section substitutes when none is provided, the two being +// identical once HMAC pads the key out to its block size +auto hkdf_extract(const KDFHash hash, const std::string_view salt, + const std::string_view input_key_material, + unsigned char *const output) -> bool; + +// RFC 5869 Section 2.3 +auto hkdf_expand(const KDFHash hash, const std::string_view pseudorandom_key, + const std::string_view info, unsigned char *const output, + const std::size_t length) -> bool; + +// Both steps at once, which a backend with a dedicated key derivation +// primitive performs without surfacing the intermediate pseudorandom key +auto hkdf_derive(const KDFHash hash, const std::string_view input_key_material, + const std::string_view salt, const std::string_view info, + unsigned char *const output, const std::size_t length) -> bool; + +} // namespace sourcemeta::core + +#endif diff --git a/vendor/core/src/core/crypto/crypto_kdf_apple.h b/vendor/core/src/core/crypto/crypto_kdf_apple.h new file mode 100644 index 00000000..491da261 --- /dev/null +++ b/vendor/core/src/core/crypto/crypto_kdf_apple.h @@ -0,0 +1,29 @@ +#ifndef SOURCEMETA_CORE_CRYPTO_KDF_APPLE_H_ +#define SOURCEMETA_CORE_CRYPTO_KDF_APPLE_H_ + +#include // std::size_t + +// The RFC 5869 steps through CryptoKit, defined in the Objective-C++ bridge +// that consumes the Swift shim. The hash selector matches the order of the +// shared enumeration. Each returns false when the running system predates the +// CryptoKit key derivation interface, which is macOS 11, so the caller +// composes the derivation from HMAC instead + +extern "C" auto sourcemeta_core_hkdf_extract_cryptokit( + int hash, const unsigned char *input_key_material, + std::size_t input_key_material_size, const unsigned char *salt, + std::size_t salt_size, unsigned char *output, std::size_t output_size) + -> bool; + +extern "C" auto sourcemeta_core_hkdf_expand_cryptokit( + int hash, const unsigned char *pseudorandom_key, + std::size_t pseudorandom_key_size, const unsigned char *info, + std::size_t info_size, unsigned char *output, std::size_t length) -> bool; + +extern "C" auto sourcemeta_core_hkdf_derive_cryptokit( + int hash, const unsigned char *input_key_material, + std::size_t input_key_material_size, const unsigned char *salt, + std::size_t salt_size, const unsigned char *info, std::size_t info_size, + unsigned char *output, std::size_t length) -> bool; + +#endif diff --git a/vendor/core/src/core/crypto/crypto_pkcs8.h b/vendor/core/src/core/crypto/crypto_pkcs8.h index e0d6d98f..dba3939f 100644 --- a/vendor/core/src/core/crypto/crypto_pkcs8.h +++ b/vendor/core/src/core/crypto/crypto_pkcs8.h @@ -152,14 +152,14 @@ inline auto parse_pkcs8(const std::string_view der) -> std::optional { // The object identifiers are raw DER content bytes, kept as escaped literals // NOLINTBEGIN(modernize-raw-string-literal) - constexpr std::string_view rsa{"\x2a\x86\x48\x86\xf7\x0d\x01\x01\x01", 9}; - constexpr std::string_view rsa_pss{"\x2a\x86\x48\x86\xf7\x0d\x01\x01\x0a", 9}; - constexpr std::string_view elliptic_curve{"\x2a\x86\x48\xce\x3d\x02\x01", 7}; - constexpr std::string_view ed25519{"\x2b\x65\x70", 3}; - constexpr std::string_view ed448{"\x2b\x65\x71", 3}; + constexpr std::string_view RSA{"\x2a\x86\x48\x86\xf7\x0d\x01\x01\x01", 9}; + constexpr std::string_view RSA_PSS{"\x2a\x86\x48\x86\xf7\x0d\x01\x01\x0a", 9}; + constexpr std::string_view ELLIPTIC_CURVE{"\x2a\x86\x48\xce\x3d\x02\x01", 7}; + constexpr std::string_view ED25519{"\x2b\x65\x70", 3}; + constexpr std::string_view ED448{"\x2b\x65\x71", 3}; // NOLINTEND(modernize-raw-string-literal) - if (oid->content == rsa || oid->content == rsa_pss) { + if (oid->content == RSA || oid->content == RSA_PSS) { if (!rsa_private_key_acceptable(private_key->content)) { return std::nullopt; } @@ -168,35 +168,35 @@ inline auto parse_pkcs8(const std::string_view der) -> std::optional { .curve = {}, .edwards_curve = {}, .key = private_key->content, - .rsa_pss_restricted = oid->content == rsa_pss}; + .rsa_pss_restricted = oid->content == RSA_PSS}; } - if (oid->content == ed25519 || oid->content == ed448) { + if (oid->content == ED25519 || oid->content == ED448) { return PKCS8Key{.kind = PKCS8KeyKind::Edwards, .curve = {}, - .edwards_curve = oid->content == ed25519 + .edwards_curve = oid->content == ED25519 ? EdwardsCurve::Ed25519 : EdwardsCurve::Ed448, .key = private_key->content}; } - if (oid->content == elliptic_curve) { + if (oid->content == ELLIPTIC_CURVE) { const auto curve_oid{der_read(oid->rest)}; if (!curve_oid.has_value() || curve_oid->tag != 0x06) { return std::nullopt; } // NOLINTBEGIN(modernize-raw-string-literal) - constexpr std::string_view p256{"\x2a\x86\x48\xce\x3d\x03\x01\x07", 8}; - constexpr std::string_view p384{"\x2b\x81\x04\x00\x22", 5}; - constexpr std::string_view p521{"\x2b\x81\x04\x00\x23", 5}; + constexpr std::string_view P256{"\x2a\x86\x48\xce\x3d\x03\x01\x07", 8}; + constexpr std::string_view P384{"\x2b\x81\x04\x00\x22", 5}; + constexpr std::string_view P521{"\x2b\x81\x04\x00\x23", 5}; // NOLINTEND(modernize-raw-string-literal) EllipticCurve curve{}; - if (curve_oid->content == p256) { + if (curve_oid->content == P256) { curve = EllipticCurve::P256; - } else if (curve_oid->content == p384) { + } else if (curve_oid->content == P384) { curve = EllipticCurve::P384; - } else if (curve_oid->content == p521) { + } else if (curve_oid->content == P521) { curve = EllipticCurve::P521; } else { return std::nullopt; diff --git a/vendor/core/src/core/crypto/crypto_rsa_oaep_apple.cc b/vendor/core/src/core/crypto/crypto_rsa_oaep_apple.cc index 973e782a..e708ab32 100644 --- a/vendor/core/src/core/crypto/crypto_rsa_oaep_apple.cc +++ b/vendor/core/src/core/crypto/crypto_rsa_oaep_apple.cc @@ -41,12 +41,12 @@ auto rsa_oaep_encrypt(const PublicKey &key, const RSAOAEPHash hash, return std::nullopt; } - auto plaintext_data{make_data(plaintext)}; + const auto *plaintext_data{make_data(plaintext)}; if (plaintext_data == nullptr) { return std::nullopt; } - auto ciphertext{SecKeyCreateEncryptedData( + const auto *ciphertext{SecKeyCreateEncryptedData( internal->key, to_oaep_algorithm(hash), plaintext_data, nullptr)}; CFRelease(plaintext_data); if (ciphertext == nullptr) { @@ -77,12 +77,12 @@ auto rsa_oaep_decrypt(const PrivateKey &key, const RSAOAEPHash hash, return std::nullopt; } - auto ciphertext_data{make_data(ciphertext)}; + const auto *ciphertext_data{make_data(ciphertext)}; if (ciphertext_data == nullptr) { return std::nullopt; } - auto plaintext{SecKeyCreateDecryptedData( + const auto *plaintext{SecKeyCreateDecryptedData( internal->key, to_oaep_algorithm(hash), ciphertext_data, nullptr)}; CFRelease(ciphertext_data); if (plaintext == nullptr) { diff --git a/vendor/core/src/core/crypto/crypto_sha1_apple.cc b/vendor/core/src/core/crypto/crypto_sha1_apple.cc index 7a47a2c6..2232f2ec 100644 --- a/vendor/core/src/core/crypto/crypto_sha1_apple.cc +++ b/vendor/core/src/core/crypto/crypto_sha1_apple.cc @@ -22,9 +22,9 @@ auto sha1(const std::string_view input) -> std::string { // inputs must be fed in chunks const auto *remaining_data{input.data()}; auto remaining_size{input.size()}; - constexpr std::size_t maximum_chunk{std::numeric_limits::max()}; + constexpr std::size_t MAXIMUM_CHUNK{std::numeric_limits::max()}; while (remaining_size > 0) { - const auto chunk_size{remaining_size > maximum_chunk ? maximum_chunk + const auto chunk_size{remaining_size > MAXIMUM_CHUNK ? MAXIMUM_CHUNK : remaining_size}; CC_SHA1_Update(&context, remaining_data, static_cast(chunk_size)); remaining_data += chunk_size; diff --git a/vendor/core/src/core/crypto/crypto_sha256_apple.cc b/vendor/core/src/core/crypto/crypto_sha256_apple.cc index 5a8f5506..9690d1bc 100644 --- a/vendor/core/src/core/crypto/crypto_sha256_apple.cc +++ b/vendor/core/src/core/crypto/crypto_sha256_apple.cc @@ -17,9 +17,9 @@ auto sha256_update(CC_SHA256_CTX &context, const std::string_view input) // inputs must be fed in chunks const auto *remaining_data{input.data()}; auto remaining_size{input.size()}; - constexpr std::size_t maximum_chunk{std::numeric_limits::max()}; + constexpr std::size_t MAXIMUM_CHUNK{std::numeric_limits::max()}; while (remaining_size > 0) { - const auto chunk_size{remaining_size > maximum_chunk ? maximum_chunk + const auto chunk_size{remaining_size > MAXIMUM_CHUNK ? MAXIMUM_CHUNK : remaining_size}; CC_SHA256_Update(&context, remaining_data, static_cast(chunk_size)); diff --git a/vendor/core/src/core/crypto/crypto_sha384_apple.cc b/vendor/core/src/core/crypto/crypto_sha384_apple.cc index 3ca2a0a3..c2db6d37 100644 --- a/vendor/core/src/core/crypto/crypto_sha384_apple.cc +++ b/vendor/core/src/core/crypto/crypto_sha384_apple.cc @@ -18,9 +18,9 @@ auto sha384_digest(const std::string_view input) // inputs must be fed in chunks const auto *remaining_data{input.data()}; auto remaining_size{input.size()}; - constexpr std::size_t maximum_chunk{std::numeric_limits::max()}; + constexpr std::size_t MAXIMUM_CHUNK{std::numeric_limits::max()}; while (remaining_size > 0) { - const auto chunk_size{remaining_size > maximum_chunk ? maximum_chunk + const auto chunk_size{remaining_size > MAXIMUM_CHUNK ? MAXIMUM_CHUNK : remaining_size}; CC_SHA384_Update(&context, remaining_data, static_cast(chunk_size)); diff --git a/vendor/core/src/core/crypto/crypto_sha512_apple.cc b/vendor/core/src/core/crypto/crypto_sha512_apple.cc index bd455604..ab087504 100644 --- a/vendor/core/src/core/crypto/crypto_sha512_apple.cc +++ b/vendor/core/src/core/crypto/crypto_sha512_apple.cc @@ -18,9 +18,9 @@ auto sha512_digest(const std::string_view input) // inputs must be fed in chunks const auto *remaining_data{input.data()}; auto remaining_size{input.size()}; - constexpr std::size_t maximum_chunk{std::numeric_limits::max()}; + constexpr std::size_t MAXIMUM_CHUNK{std::numeric_limits::max()}; while (remaining_size > 0) { - const auto chunk_size{remaining_size > maximum_chunk ? maximum_chunk + const auto chunk_size{remaining_size > MAXIMUM_CHUNK ? MAXIMUM_CHUNK : remaining_size}; CC_SHA512_Update(&context, remaining_data, static_cast(chunk_size)); diff --git a/vendor/core/src/core/crypto/crypto_shake256.h b/vendor/core/src/core/crypto/crypto_shake256.h index dd942542..09b9e3d5 100644 --- a/vendor/core/src/core/crypto/crypto_shake256.h +++ b/vendor/core/src/core/crypto/crypto_shake256.h @@ -12,7 +12,7 @@ namespace sourcemeta::core { -inline constexpr std::array keccak_round_constants{ +inline constexpr std::array KECCAK_ROUND_CONSTANTS{ {0x0000000000000001ULL, 0x0000000000008082ULL, 0x800000000000808aULL, 0x8000000080008000ULL, 0x000000000000808bULL, 0x0000000080000001ULL, 0x8000000080008081ULL, 0x8000000000008009ULL, 0x000000000000008aULL, @@ -24,18 +24,18 @@ inline constexpr std::array keccak_round_constants{ // The rotation offsets and destination lanes of the combined rho and pi steps, // walking the lanes starting from lane 1 -inline constexpr std::array keccak_rho_offsets{ +inline constexpr std::array KECCAK_RHO_OFFSETS{ {1, 3, 6, 10, 15, 21, 28, 36, 45, 55, 2, 14, 27, 41, 56, 8, 25, 43, 62, 18, 39, 61, 20, 44}}; -inline constexpr std::array keccak_pi_lanes{ +inline constexpr std::array KECCAK_PI_LANES{ {10, 7, 11, 17, 18, 3, 5, 16, 8, 21, 24, 4, 15, 23, 19, 13, 12, 2, 20, 14, 22, 9, 6, 1}}; -inline constexpr auto keccak_rotate_left(const std::uint64_t value, - const unsigned offset) noexcept +constexpr auto keccak_rotate_left(const std::uint64_t value, + const unsigned offset) noexcept -> std::uint64_t { - return (value << offset) | (value >> (64u - offset)); + return (value << offset) | (value >> (64U - offset)); } inline auto keccak_permute(std::array &state) noexcept @@ -60,9 +60,9 @@ inline auto keccak_permute(std::array &state) noexcept // Rho and pi auto current{state[1]}; for (std::size_t index = 0; index < 24; ++index) { - const auto lane{keccak_pi_lanes[index]}; + const auto lane{KECCAK_PI_LANES[index]}; const auto moved{state[lane]}; - state[lane] = keccak_rotate_left(current, keccak_rho_offsets[index]); + state[lane] = keccak_rotate_left(current, KECCAK_RHO_OFFSETS[index]); current = moved; } @@ -80,7 +80,7 @@ inline auto keccak_permute(std::array &state) noexcept } // Iota - state[0] ^= keccak_round_constants[round]; + state[0] ^= KECCAK_ROUND_CONSTANTS[round]; } } @@ -88,7 +88,7 @@ inline auto keccak_permute(std::array &state) noexcept inline auto shake256(const std::string_view input, const std::size_t output_length) -> std::string { // The bitrate is 1600 - 2 * 256 = 1088 bits, that is 136 octets - constexpr std::size_t rate{136}; + constexpr std::size_t RATE{136}; std::array state{}; std::size_t pointer{0}; @@ -97,7 +97,7 @@ inline auto shake256(const std::string_view input, static_cast(static_cast(character)) << (8 * (pointer % 8)); pointer += 1; - if (pointer == rate) { + if (pointer == RATE) { keccak_permute(state); pointer = 0; } @@ -106,8 +106,8 @@ inline auto shake256(const std::string_view input, // The SHAKE domain separation suffix is the bits 1111, padded to the rate // with the pad10*1 rule state[pointer / 8] ^= static_cast(0x1f) << (8 * (pointer % 8)); - state[(rate - 1) / 8] ^= static_cast(0x80) - << (8 * ((rate - 1) % 8)); + state[(RATE - 1) / 8] ^= static_cast(0x80) + << (8 * ((RATE - 1) % 8)); keccak_permute(state); std::string output; @@ -115,9 +115,9 @@ inline auto shake256(const std::string_view input, std::size_t squeeze_pointer{0}; while (output.size() < output_length) { output.push_back(static_cast( - (state[squeeze_pointer / 8] >> (8 * (squeeze_pointer % 8))) & 0xffu)); + (state[squeeze_pointer / 8] >> (8 * (squeeze_pointer % 8))) & 0xffU)); squeeze_pointer += 1; - if (squeeze_pointer == rate) { + if (squeeze_pointer == RATE) { keccak_permute(state); squeeze_pointer = 0; } diff --git a/vendor/core/src/core/crypto/crypto_sign_apple.cc b/vendor/core/src/core/crypto/crypto_sign_apple.cc index 09de7538..67e26b7f 100644 --- a/vendor/core/src/core/crypto/crypto_sign_apple.cc +++ b/vendor/core/src/core/crypto/crypto_sign_apple.cc @@ -76,7 +76,7 @@ auto make_data(const std::string_view value) -> CFDataRef { auto native_private_key(const void *type, const std::string_view key) -> SecKeyRef { - auto key_data{make_data(key)}; + const auto *key_data{make_data(key)}; if (key_data == nullptr) { return nullptr; } @@ -84,7 +84,7 @@ auto native_private_key(const void *type, const std::string_view key) std::array attribute_keys{ {kSecAttrKeyType, kSecAttrKeyClass}}; std::array attribute_values{{type, kSecAttrKeyClassPrivate}}; - auto attributes{CFDictionaryCreate( + const auto *attributes{CFDictionaryCreate( kCFAllocatorDefault, attribute_keys.data(), attribute_values.data(), static_cast(attribute_keys.size()), &kCFTypeDictionaryKeyCallBacks, &kCFTypeDictionaryValueCallBacks)}; @@ -93,7 +93,7 @@ auto native_private_key(const void *type, const std::string_view key) return nullptr; } - auto key_reference{SecKeyCreateWithData(key_data, attributes, nullptr)}; + auto *key_reference{SecKeyCreateWithData(key_data, attributes, nullptr)}; CFRelease(attributes); CFRelease(key_data); return key_reference; @@ -179,12 +179,13 @@ auto native_ec_private_key_components(const std::size_t field_bytes, auto sign_with_algorithm(SecKeyRef key, const SecKeyAlgorithm algorithm, const std::string_view message) -> std::optional { - auto message_data{make_data(message)}; + const auto *message_data{make_data(message)}; if (message_data == nullptr) { return std::nullopt; } - auto signature{SecKeyCreateSignature(key, algorithm, message_data, nullptr)}; + const auto *signature{ + SecKeyCreateSignature(key, algorithm, message_data, nullptr)}; CFRelease(message_data); if (signature == nullptr) { return std::nullopt; @@ -360,7 +361,8 @@ auto make_ec_private_key(const EllipticCurve curve, auto generate_ec_private_key(const EllipticCurve curve) -> std::optional { const int bits{static_cast(curve_bit_length(curve))}; - auto size{CFNumberCreate(kCFAllocatorDefault, kCFNumberIntType, &bits)}; + const auto *size{ + CFNumberCreate(kCFAllocatorDefault, kCFNumberIntType, &bits)}; if (size == nullptr) { return std::nullopt; } @@ -369,7 +371,7 @@ auto generate_ec_private_key(const EllipticCurve curve) {kSecAttrKeyType, kSecAttrKeySizeInBits}}; std::array attribute_values{ {kSecAttrKeyTypeECSECPrimeRandom, size}}; - auto attributes{CFDictionaryCreate( + const auto *attributes{CFDictionaryCreate( kCFAllocatorDefault, attribute_keys.data(), attribute_values.data(), static_cast(attribute_keys.size()), &kCFTypeDictionaryKeyCallBacks, &kCFTypeDictionaryValueCallBacks)}; diff --git a/vendor/core/src/core/crypto/crypto_uuid.cc b/vendor/core/src/core/crypto/crypto_uuid.cc index adeda4c8..ede6380b 100644 --- a/vendor/core/src/core/crypto/crypto_uuid.cc +++ b/vendor/core/src/core/crypto/crypto_uuid.cc @@ -15,9 +15,9 @@ namespace sourcemeta::core { // Format: xxxxxxxx-xxxx-4xxx-Nxxx-xxxxxxxxxxxx // where 4 is the version and N is the variant (8, 9, a, or b) auto uuidv4() -> std::string { - static constexpr std::string_view digits = "0123456789abcdef"; - static constexpr std::string_view variant_digits = "89ab"; - static constexpr std::array dash = { + static constexpr std::string_view DIGITS = "0123456789abcdef"; + static constexpr std::string_view VARIANT_DIGITS = "89ab"; + static constexpr std::array DASH = { {false, false, false, false, true, false, true, false, true, false, true, false, false, false, false, false}}; @@ -26,25 +26,25 @@ auto uuidv4() -> std::string { std::string result; result.reserve(36); - for (std::size_t index = 0; index < dash.size(); ++index) { - if (dash[index]) { + for (std::size_t index = 0; index < DASH.size(); ++index) { + if (DASH[index]) { result += '-'; } - const auto high_nibble = (bytes[index] >> 4u) & 0x0fu; - const auto low_nibble = bytes[index] & 0x0fu; + const auto high_nibble = (bytes[index] >> 4U) & 0x0fU; + const auto low_nibble = bytes[index] & 0x0fU; // RFC 9562 Section 5.4: version bits (48-51) must be 0b0100 if (index == 6) { result += '4'; // RFC 9562 Section 5.4: variant bits (64-65) must be 0b10 } else if (index == 8) { - result += variant_digits[high_nibble & 0x03u]; + result += VARIANT_DIGITS[high_nibble & 0x03U]; } else { - result += digits[high_nibble]; + result += DIGITS[high_nibble]; } - result += digits[low_nibble]; + result += DIGITS[low_nibble]; } return result; diff --git a/vendor/core/src/core/crypto/crypto_verify_apple.cc b/vendor/core/src/core/crypto/crypto_verify_apple.cc index 5959ee8a..4320a39c 100644 --- a/vendor/core/src/core/crypto/crypto_verify_apple.cc +++ b/vendor/core/src/core/crypto/crypto_verify_apple.cc @@ -85,7 +85,7 @@ auto native_rsa_key(const std::string_view modulus, sourcemeta::core::der_append_length(der, body.size()); der.append(body); - auto key_data{make_data(der)}; + const auto *key_data{make_data(der)}; if (key_data == nullptr) { return nullptr; } @@ -94,7 +94,7 @@ auto native_rsa_key(const std::string_view modulus, {kSecAttrKeyType, kSecAttrKeyClass}}; std::array attribute_values{ {kSecAttrKeyTypeRSA, kSecAttrKeyClassPublic}}; - auto attributes{CFDictionaryCreate( + const auto *attributes{CFDictionaryCreate( kCFAllocatorDefault, attribute_keys.data(), attribute_values.data(), static_cast(attribute_keys.size()), &kCFTypeDictionaryKeyCallBacks, &kCFTypeDictionaryValueCallBacks)}; @@ -103,7 +103,7 @@ auto native_rsa_key(const std::string_view modulus, return nullptr; } - auto key{SecKeyCreateWithData(key_data, attributes, nullptr)}; + auto *key{SecKeyCreateWithData(key_data, attributes, nullptr)}; CFRelease(attributes); CFRelease(key_data); return key; @@ -126,7 +126,7 @@ auto native_ec_key(const sourcemeta::core::EllipticCurve curve, point.append(sourcemeta::core::pad_left(stripped_x, width, '\x00')); point.append(sourcemeta::core::pad_left(stripped_y, width, '\x00')); - auto key_data{make_data(point)}; + const auto *key_data{make_data(point)}; if (key_data == nullptr) { return nullptr; } @@ -135,7 +135,7 @@ auto native_ec_key(const sourcemeta::core::EllipticCurve curve, {kSecAttrKeyType, kSecAttrKeyClass}}; std::array attribute_values{ {kSecAttrKeyTypeECSECPrimeRandom, kSecAttrKeyClassPublic}}; - auto attributes{CFDictionaryCreate( + const auto *attributes{CFDictionaryCreate( kCFAllocatorDefault, attribute_keys.data(), attribute_values.data(), static_cast(attribute_keys.size()), &kCFTypeDictionaryKeyCallBacks, &kCFTypeDictionaryValueCallBacks)}; @@ -144,7 +144,7 @@ auto native_ec_key(const sourcemeta::core::EllipticCurve curve, return nullptr; } - auto key{SecKeyCreateWithData(key_data, attributes, nullptr)}; + auto *key{SecKeyCreateWithData(key_data, attributes, nullptr)}; CFRelease(attributes); CFRelease(key_data); return key; @@ -170,12 +170,12 @@ auto encode_ecdsa_signature(const std::string_view raw_signature) auto verify_with_algorithm(SecKeyRef key, const SecKeyAlgorithm algorithm, const std::string_view message, const std::string_view signature) -> bool { - auto message_data{make_data(message)}; - auto signature_data{make_data(signature)}; + const auto *message_data{make_data(message)}; + const auto *signature_data{make_data(signature)}; auto result{false}; if (message_data != nullptr && signature_data != nullptr) { result = SecKeyVerifySignature(key, algorithm, message_data, signature_data, - nullptr) == true; + nullptr) == 1; } if (signature_data != nullptr) { diff --git a/vendor/core/src/core/crypto/crypto_verify_other.cc b/vendor/core/src/core/crypto/crypto_verify_other.cc index df77ff86..b9a8f020 100644 --- a/vendor/core/src/core/crypto/crypto_verify_other.cc +++ b/vendor/core/src/core/crypto/crypto_verify_other.cc @@ -21,7 +21,7 @@ namespace sourcemeta::core { // The big integer capacity must hold the product of two maximum-size operands // plus a normalisation limb and Knuth's implicit leading-zero digit, otherwise // the modular reduction would index past the fixed-capacity word array -static_assert(Bignum::capacity >= 2 * ((MAXIMUM_KEY_BYTES + 7) / 8) + 2); +static_assert(Bignum::CAPACITY >= 2 * ((MAXIMUM_KEY_BYTES + 7) / 8) + 2); namespace { diff --git a/vendor/core/src/core/crypto/include/sourcemeta/core/crypto.h b/vendor/core/src/core/crypto/include/sourcemeta/core/crypto.h index 932f9f17..73dfd7e6 100644 --- a/vendor/core/src/core/crypto/include/sourcemeta/core/crypto.h +++ b/vendor/core/src/core/crypto/include/sourcemeta/core/crypto.h @@ -23,6 +23,7 @@ #include #include #include +#include #include #include #include diff --git a/vendor/core/src/core/crypto/include/sourcemeta/core/crypto_hkdf.h b/vendor/core/src/core/crypto/include/sourcemeta/core/crypto_hkdf.h new file mode 100644 index 00000000..556bee2b --- /dev/null +++ b/vendor/core/src/core/crypto/include/sourcemeta/core/crypto_hkdf.h @@ -0,0 +1,161 @@ +#ifndef SOURCEMETA_CORE_CRYPTO_HKDF_H_ +#define SOURCEMETA_CORE_CRYPTO_HKDF_H_ + +#ifndef SOURCEMETA_CORE_CRYPTO_EXPORT +#include +#endif + +#include // std::array +#include // std::size_t +#include // std::uint8_t +#include // std::optional +#include // std::string +#include // std::string_view + +namespace sourcemeta::core { + +/// @ingroup crypto +/// Extract a pseudorandom key from input keying material under HMAC-SHA256, +/// the first step of RFC 5869 Section 2.2. The salt is optional and need not +/// be secret, and an empty one stands for the string of digest-length zeros +/// that section substitutes when none is provided. RFC 5869 Section 3.1 +/// recommends supplying one. Throws when the underlying provider refuses the +/// extraction, since there is no pseudorandom key to report. For example: +/// +/// ```cpp +/// #include +/// #include +/// +/// const auto key{sourcemeta::core::hkdf_sha256_extract("salt", "secret")}; +/// assert(key.size() == 32); +/// ``` +auto SOURCEMETA_CORE_CRYPTO_EXPORT hkdf_sha256_extract( + const std::string_view salt, const std::string_view input_key_material) + -> std::array; + +/// @ingroup crypto +/// Expand a pseudorandom key into output keying material of the requested +/// length under HMAC-SHA256, the second step of RFC 5869 Section 2.3. The info +/// input binds the result to a purpose, so one secret yields several keys that +/// cannot be substituted for one another. There is no result when the key is +/// shorter than the digest or when the length exceeds the 255 digests that +/// section permits. For example: +/// +/// ```cpp +/// #include +/// #include +/// +/// const auto key{sourcemeta::core::hkdf_sha256_extract("salt", "secret")}; +/// const std::string_view pseudorandom_key{ +/// reinterpret_cast(key.data()), key.size()}; +/// const auto material{ +/// sourcemeta::core::hkdf_sha256_expand(pseudorandom_key, "session", 32)}; +/// assert(material.has_value()); +/// ``` +auto SOURCEMETA_CORE_CRYPTO_EXPORT hkdf_sha256_expand( + const std::string_view pseudorandom_key, const std::string_view info, + const std::size_t length) -> std::optional; + +/// @ingroup crypto +/// Derive output keying material from input keying material under HMAC-SHA256, +/// extracting and then expanding as RFC 5869 Section 2 prescribes. Prefer this +/// over the two steps alone, since Section 3.3 warns that skipping the extract +/// step is only sound when the input is already a cryptographically strong key, +/// and never for a Diffie-Hellman value. There is no result when the length +/// exceeds the 255 digests Section 2.3 permits. For example: +/// +/// ```cpp +/// #include +/// #include +/// +/// const auto material{ +/// sourcemeta::core::hkdf_sha256("secret", "salt", "session", 32)}; +/// assert(material.has_value()); +/// assert(material.value().size() == 32); +/// ``` +auto SOURCEMETA_CORE_CRYPTO_EXPORT +hkdf_sha256(const std::string_view input_key_material, + const std::string_view salt, const std::string_view info, + const std::size_t length) -> std::optional; + +/// @ingroup crypto +/// Extract a pseudorandom key from input keying material under HMAC-SHA384, +/// the first step of RFC 5869 Section 2.2. For example: +/// +/// ```cpp +/// #include +/// #include +/// +/// const auto key{sourcemeta::core::hkdf_sha384_extract("salt", "secret")}; +/// assert(key.size() == 48); +/// ``` +auto SOURCEMETA_CORE_CRYPTO_EXPORT hkdf_sha384_extract( + const std::string_view salt, const std::string_view input_key_material) + -> std::array; + +/// @ingroup crypto +/// Expand a pseudorandom key into output keying material of the requested +/// length under HMAC-SHA384, the second step of RFC 5869 Section 2.3. +auto SOURCEMETA_CORE_CRYPTO_EXPORT hkdf_sha384_expand( + const std::string_view pseudorandom_key, const std::string_view info, + const std::size_t length) -> std::optional; + +/// @ingroup crypto +/// Derive output keying material from input keying material under HMAC-SHA384, +/// extracting and then expanding as RFC 5869 Section 2 prescribes. For example: +/// +/// ```cpp +/// #include +/// #include +/// +/// const auto material{ +/// sourcemeta::core::hkdf_sha384("secret", "salt", "session", 32)}; +/// assert(material.has_value()); +/// ``` +auto SOURCEMETA_CORE_CRYPTO_EXPORT +hkdf_sha384(const std::string_view input_key_material, + const std::string_view salt, const std::string_view info, + const std::size_t length) -> std::optional; + +/// @ingroup crypto +/// Extract a pseudorandom key from input keying material under HMAC-SHA512, +/// the first step of RFC 5869 Section 2.2. For example: +/// +/// ```cpp +/// #include +/// #include +/// +/// const auto key{sourcemeta::core::hkdf_sha512_extract("salt", "secret")}; +/// assert(key.size() == 64); +/// ``` +auto SOURCEMETA_CORE_CRYPTO_EXPORT hkdf_sha512_extract( + const std::string_view salt, const std::string_view input_key_material) + -> std::array; + +/// @ingroup crypto +/// Expand a pseudorandom key into output keying material of the requested +/// length under HMAC-SHA512, the second step of RFC 5869 Section 2.3. +auto SOURCEMETA_CORE_CRYPTO_EXPORT hkdf_sha512_expand( + const std::string_view pseudorandom_key, const std::string_view info, + const std::size_t length) -> std::optional; + +/// @ingroup crypto +/// Derive output keying material from input keying material under HMAC-SHA512, +/// extracting and then expanding as RFC 5869 Section 2 prescribes. For example: +/// +/// ```cpp +/// #include +/// #include +/// +/// const auto material{ +/// sourcemeta::core::hkdf_sha512("secret", "salt", "session", 32)}; +/// assert(material.has_value()); +/// ``` +auto SOURCEMETA_CORE_CRYPTO_EXPORT +hkdf_sha512(const std::string_view input_key_material, + const std::string_view salt, const std::string_view info, + const std::size_t length) -> std::optional; + +} // namespace sourcemeta::core + +#endif diff --git a/vendor/core/src/core/crypto/include/sourcemeta/core/crypto_secure.h b/vendor/core/src/core/crypto/include/sourcemeta/core/crypto_secure.h index 99e3841c..1314c905 100644 --- a/vendor/core/src/core/crypto/include/sourcemeta/core/crypto_secure.h +++ b/vendor/core/src/core/crypto/include/sourcemeta/core/crypto_secure.h @@ -114,7 +114,8 @@ template struct SecureAllocator { /// Construct from an allocator for another type, as the containers require. template - constexpr SecureAllocator(const SecureAllocator &) noexcept {} + constexpr SecureAllocator( + [[maybe_unused]] const SecureAllocator &other) noexcept {} /// Allocate storage for the given number of objects. [[nodiscard]] auto allocate(const std::size_t count) -> T * { @@ -134,13 +135,15 @@ template struct SecureAllocator { /// Every instance is interchangeable, since the allocator holds no state. template - auto operator==(const SecureAllocator &) const noexcept -> bool { + auto operator==([[maybe_unused]] const SecureAllocator &other) + const noexcept -> bool { return true; } /// Every instance is interchangeable, since the allocator holds no state. template - auto operator!=(const SecureAllocator &) const noexcept -> bool { + auto operator!=([[maybe_unused]] const SecureAllocator &other) + const noexcept -> bool { return false; } }; diff --git a/vendor/core/src/core/email/email.cc b/vendor/core/src/core/email/email.cc index 3c7bfb98..639acfd0 100644 --- a/vendor/core/src/core/email/email.cc +++ b/vendor/core/src/core/email/email.cc @@ -163,6 +163,15 @@ auto is_idn_email_uts46(const std::string_view value) -> bool { return mailbox_separator(value).has_value(); } +auto email_domain(const std::string_view value) -> std::string_view { + const auto separator{mailbox_separator(value)}; + if (!separator.has_value()) { + return {}; + } + + return value.substr(separator.value() + 1); +} + auto mailto_iri(const std::string_view value) -> std::optional { const auto separator{mailbox_separator(value)}; if (!separator.has_value()) { @@ -170,7 +179,7 @@ auto mailto_iri(const std::string_view value) -> std::optional { } std::string result; - result.reserve(value.size() * 3 + 7); + result.reserve((value.size() * 3) + 7); result.append("mailto:"); for (const auto character : value.substr(0, separator.value())) { if (is_mailto_verbatim(character)) { @@ -222,7 +231,7 @@ auto acct_iri(const std::string_view value) -> std::optional { } std::string result; - result.reserve(value.size() * 3 + 5); + result.reserve((value.size() * 3) + 5); result.append("acct:"); for (const auto character : parts->first) { if (is_acct_userpart_verbatim(character)) { diff --git a/vendor/core/src/core/email/helpers.h b/vendor/core/src/core/email/helpers.h index 5495a067..e69d1237 100644 --- a/vendor/core/src/core/email/helpers.h +++ b/vendor/core/src/core/email/helpers.h @@ -13,7 +13,7 @@ namespace { // RFC 5321 §4.1.2: atext = ALPHA / DIGIT / "!" / "#" / "$" / "%" / // "&" / "'" / "*" / "+" / "-" / "/" / "=" / "?" / "^" / "_" / "`" / // "{" / "|" / "}" / "~" -inline constexpr auto is_atext(const char character) -> bool { +constexpr auto is_atext(const char character) -> bool { switch (character) { case '!': case '#': @@ -41,21 +41,21 @@ inline constexpr auto is_atext(const char character) -> bool { } // RFC 5321 §4.1.2: qtextSMTP = %d32-33 / %d35-91 / %d93-126 -inline constexpr auto is_qtext_smtp(const unsigned char character) -> bool { +constexpr auto is_qtext_smtp(const unsigned char character) -> bool { return (character >= 32 && character <= 33) || (character >= 35 && character <= 91) || (character >= 93 && character <= 126); } // RFC 5321 §4.1.3: dcontent = %d33-90 / %d94-126 -inline constexpr auto is_dcontent(const unsigned char character) -> bool { +constexpr auto is_dcontent(const unsigned char character) -> bool { return (character >= 33 && character <= 90) || (character >= 94 && character <= 126); } // RFC 5321 §4.1.2: Ldh-str = *( ALPHA / DIGIT / "-" ) Let-dig // RFC 5321 §4.1.3: Standardized-tag = Ldh-str -inline constexpr auto is_ldh_str(const std::string_view value) -> bool { +constexpr auto is_ldh_str(const std::string_view value) -> bool { if (value.empty() || !sourcemeta::core::is_alphanum(value.back())) { return false; } @@ -72,7 +72,7 @@ inline constexpr auto is_ldh_str(const std::string_view value) -> bool { // RFC 5321 §4.1.3: Snum = 1*3DIGIT ; representing a decimal integer // value in the range 0 through 255. Leading zeros are permitted, unlike // the RFC 3986 dec-octet that backs is_ipv4 -inline constexpr auto is_snum(const std::string_view value) -> bool { +constexpr auto is_snum(const std::string_view value) -> bool { if (value.empty() || value.size() > 3) { return false; } @@ -82,14 +82,13 @@ inline constexpr auto is_snum(const std::string_view value) -> bool { return false; } result = static_cast( - result * 10 + static_cast(character - '0')); + (result * 10) + static_cast(character - '0')); } return result <= 255; } // RFC 5321 §4.1.3: IPv4-address-literal = Snum 3("." Snum) -inline constexpr auto is_ipv4_address_literal(const std::string_view value) - -> bool { +constexpr auto is_ipv4_address_literal(const std::string_view value) -> bool { std::string_view::size_type start{0}; std::uint8_t octets{0}; while (true) { @@ -116,7 +115,7 @@ inline constexpr auto is_ipv4_address_literal(const std::string_view value) // RFC 5234 §2.3: ABNF literal strings are case-insensitive by default // RFC 5321 §4.1.3: IPv6-address-literal prefix is the literal "IPv6:" -inline constexpr auto matches_ipv6_tag(const std::string_view value) -> bool { +constexpr auto matches_ipv6_tag(const std::string_view value) -> bool { return value.size() >= 5 && (value[0] == 'I' || value[0] == 'i') && (value[1] == 'P' || value[1] == 'p') && (value[2] == 'v' || value[2] == 'V') && value[3] == '6' && @@ -124,7 +123,7 @@ inline constexpr auto matches_ipv6_tag(const std::string_view value) -> bool { } // RFC 5321 §4.1.3: General-address-literal = Standardized-tag ":" 1*dcontent -inline constexpr auto is_general_address_literal(const std::string_view value) +constexpr auto is_general_address_literal(const std::string_view value) -> bool { const auto colon_position{value.find(':')}; if (colon_position == std::string_view::npos) { @@ -164,7 +163,7 @@ inline auto is_address_literal(const std::string_view domain) -> bool { // falls through to IPv4 or General-address-literal. // RFC 5321 §4.1.3: IPv4-address-literal has no ":"; // General-address-literal requires ":" - if (inner.find(':') == std::string_view::npos) { + if (!inner.contains(':')) { return is_ipv4_address_literal(inner); } return is_general_address_literal(inner); @@ -174,10 +173,10 @@ inline auto is_address_literal(const std::string_view domain) -> bool { // uppercase hexadecimal digits for all percent-encodings" inline auto percent_encode(const unsigned char byte, std::string &output) -> void { - constexpr std::string_view hexadecimal{"0123456789ABCDEF"}; + constexpr std::string_view HEXADECIMAL{"0123456789ABCDEF"}; output.push_back('%'); - output.push_back(hexadecimal[byte >> 4U]); - output.push_back(hexadecimal[byte & 0x0FU]); + output.push_back(HEXADECIMAL[byte >> 4U]); + output.push_back(HEXADECIMAL[byte & 0x0FU]); } // RFC 6068 §2: within addr-spec, the characters that cannot appear in a URI, @@ -188,7 +187,7 @@ inline auto percent_encode(const unsigned char byte, std::string &output) // encoded as well because the "to" production takes it as the address list // separator, and "@" inside quoted content is encoded following the §6.2 // example "%22not%40me%22" -inline constexpr auto is_mailto_verbatim(const char character) -> bool { +constexpr auto is_mailto_verbatim(const char character) -> bool { switch (character) { case '!': case '$': @@ -211,7 +210,7 @@ inline constexpr auto is_mailto_verbatim(const char character) -> bool { // RFC 7565 §7: userpart consists of unreserved, sub-delims, and pct-encoded, // so those two literal sets pass through and every other octet is // percent-encoded, as the §4 example does for "juliet@capulet.example" -inline constexpr auto is_acct_userpart_verbatim(const char character) -> bool { +constexpr auto is_acct_userpart_verbatim(const char character) -> bool { switch (character) { case '!': case '$': diff --git a/vendor/core/src/core/email/include/sourcemeta/core/email.h b/vendor/core/src/core/email/include/sourcemeta/core/email.h index 0f17746e..eeadfaed 100644 --- a/vendor/core/src/core/email/include/sourcemeta/core/email.h +++ b/vendor/core/src/core/email/include/sourcemeta/core/email.h @@ -104,6 +104,30 @@ auto is_idn_email(const std::string_view value) -> bool; SOURCEMETA_CORE_EMAIL_EXPORT auto is_idn_email_uts46(const std::string_view value) -> bool; +/// @ingroup email +/// Produce the domain half of a valid RFC 5321 `Mailbox`, returning an empty +/// view when the input is not one, which no valid mailbox can otherwise yield +/// since a domain is never empty. The separator is located by parsing the +/// address, since neither the first nor the last at sign reliably marks it: +/// RFC 5321 Section 4.1.2 admits one inside a quoted local part, and Section +/// 4.1.3 admits one inside the content of a General-address-literal. The +/// result borrows from the input and keeps its spelling, so a caller comparing +/// domains applies the RFC 5321 Section 2.4 case insensitivity itself. Only +/// the ASCII grammar is accepted, so an internationalized address per RFC 6531 +/// yields nothing. For example: +/// +/// ```cpp +/// #include +/// +/// #include +/// +/// assert(sourcemeta::core::email_domain("\"not@me\"@example.org") == +/// "example.org"); +/// assert(sourcemeta::core::email_domain("plain").empty()); +/// ``` +SOURCEMETA_CORE_EMAIL_EXPORT +auto email_domain(const std::string_view value) -> std::string_view; + /// @ingroup email /// Produce the canonical RFC 6068 `mailto` IRI that identifies the given /// RFC 5321 `Mailbox`, with no result when the input is not one. The domain diff --git a/vendor/core/src/core/gzip/huffman.h b/vendor/core/src/core/gzip/huffman.h index e3d243ef..79a6cf07 100644 --- a/vendor/core/src/core/gzip/huffman.h +++ b/vendor/core/src/core/gzip/huffman.h @@ -25,7 +25,7 @@ class HuffmanDecoder { // Primary lookup table covering all codes of length <= LUT_BITS. Misses // fall back to the canonical-puff traversal for codes of length 10..15 static constexpr unsigned int LUT_BITS{9}; - static constexpr std::size_t LUT_SIZE{1u << LUT_BITS}; + static constexpr std::size_t LUT_SIZE{1U << LUT_BITS}; HuffmanDecoder() = default; @@ -88,7 +88,7 @@ class HuffmanDecoder { std::array next_code{}; next_code[1] = 0; for (unsigned int bits = 2; bits <= MAX_HUFFMAN_BITS; ++bits) { - next_code[bits] = (next_code[bits - 1] + this->count_[bits - 1]) << 1u; + next_code[bits] = (next_code[bits - 1] + this->count_[bits - 1]) << 1U; } std::size_t symbol_index{0}; @@ -101,8 +101,8 @@ class HuffmanDecoder { const auto lsb_key{reverse_bits(code, code_length)}; const auto entry{static_cast( static_cast(code_length) | - static_cast(symbol << 4u))}; - const std::uint32_t stride{1u << code_length}; + static_cast(symbol << 4U))}; + const std::uint32_t stride{1U << code_length}; for (std::uint32_t slot = lsb_key; slot < LUT_SIZE; slot += stride) { this->lut_[slot] = entry; } @@ -117,9 +117,9 @@ class HuffmanDecoder { const auto key{reader.peek_bits(LUT_BITS)}; const auto entry{this->lut_[key]}; if (entry != 0) { - const unsigned int length{entry & 0xfu}; + const unsigned int length{entry & 0xfU}; reader.consume_bits(length); - return static_cast(entry >> 4u); + return static_cast(entry >> 4U); } return this->decode_long(reader); } @@ -131,8 +131,8 @@ class HuffmanDecoder { int first{0}; int index{0}; for (unsigned int length = 1; length <= MAX_HUFFMAN_BITS; ++length) { - code |= static_cast(bits & 1u); - bits >>= 1u; + code |= static_cast(bits & 1U); + bits >>= 1U; const auto entries{static_cast(this->count_[length])}; if (code - entries < first) { const auto position{static_cast(index) + @@ -151,8 +151,8 @@ class HuffmanDecoder { -> std::uint32_t { std::uint32_t result{0}; for (unsigned int index = 0; index < length; ++index) { - if ((value & (1u << (length - 1 - index))) != 0u) { - result |= (1u << index); + if ((value & (1U << (length - 1 - index))) != 0U) { + result |= (1U << index); } } return result; diff --git a/vendor/core/src/core/gzip/include/sourcemeta/core/gzip_streambuf.h b/vendor/core/src/core/gzip/include/sourcemeta/core/gzip_streambuf.h index 484872b6..a104374d 100644 --- a/vendor/core/src/core/gzip/include/sourcemeta/core/gzip_streambuf.h +++ b/vendor/core/src/core/gzip/include/sourcemeta/core/gzip_streambuf.h @@ -37,7 +37,7 @@ class SOURCEMETA_CORE_GZIP_EXPORT GZIPStreamBuffer : public std::streambuf { #pragma warning(disable : 4251) #endif struct Internal; - std::unique_ptr internal; + std::unique_ptr internal_; #if defined(_MSC_VER) #pragma warning(default : 4251) #endif diff --git a/vendor/core/src/core/gzip/streambuf.cc b/vendor/core/src/core/gzip/streambuf.cc index 4ba4801b..15c318e3 100644 --- a/vendor/core/src/core/gzip/streambuf.cc +++ b/vendor/core/src/core/gzip/streambuf.cc @@ -48,7 +48,7 @@ class HeaderChecksum { } [[nodiscard]] auto low16() const -> std::uint16_t { - return static_cast(this->checksum_ & 0xffffu); + return static_cast(this->checksum_ & 0xffffU); } private: @@ -148,76 +148,76 @@ auto try_parse_member_header(BitReader &reader, const std::uint8_t first_byte) } // namespace GZIPStreamBuffer::GZIPStreamBuffer(std::istream &compressed_stream) - : internal{new Internal{compressed_stream}} {} + : internal_{new Internal{compressed_stream}} {} GZIPStreamBuffer::~GZIPStreamBuffer() = default; auto GZIPStreamBuffer::underflow() -> int_type { - if (this->gptr() && this->gptr() < this->egptr()) { + if ((this->gptr() != nullptr) && this->gptr() < this->egptr()) { return traits_type::to_int_type(*this->gptr()); } - if (this->internal->stream_ended) { + if (this->internal_->stream_ended) { return traits_type::eof(); } while (true) { - if (!this->internal->member_started) { + if (!this->internal_->member_started) { std::uint8_t first_byte{0}; - if (!this->internal->reader.try_read_byte(first_byte)) { - if (!this->internal->any_member_completed) { + if (!this->internal_->reader.try_read_byte(first_byte)) { + if (!this->internal_->any_member_completed) { throw GZIPError{"Empty source stream"}; } - this->internal->stream_ended = true; + this->internal_->stream_ended = true; return traits_type::eof(); } - if (this->internal->any_member_completed) { + if (this->internal_->any_member_completed) { // gzip(1) silently ignores any trailing data after a complete member // rather than treating it as the start of a new member. Bytes that do // not form a valid member header end the stream without error, // independent of the first byte value if (first_byte != 0x1f || - !try_parse_member_header(this->internal->reader, first_byte)) { - this->internal->stream_ended = true; + !try_parse_member_header(this->internal_->reader, first_byte)) { + this->internal_->stream_ended = true; return traits_type::eof(); } } else { if (first_byte != 0x1f) { throw GZIPError{"Invalid gzip magic bytes"}; } - parse_member_header(this->internal->reader, first_byte); + parse_member_header(this->internal_->reader, first_byte); } - this->internal->deflate.reset(); - this->internal->member_started = true; - this->internal->member_crc32 = 0; - this->internal->member_isize = 0; + this->internal_->deflate.reset(); + this->internal_->member_started = true; + this->internal_->member_crc32 = 0; + this->internal_->member_isize = 0; } - const auto produced{this->internal->deflate.decompress( - this->internal->decompressed_buffer.data(), - this->internal->decompressed_buffer.size())}; + const auto produced{this->internal_->deflate.decompress( + this->internal_->decompressed_buffer.data(), + this->internal_->decompressed_buffer.size())}; if (produced > 0) { - this->internal->member_crc32 = crc32_update( - this->internal->member_crc32, + this->internal_->member_crc32 = crc32_update( + this->internal_->member_crc32, std::string_view{reinterpret_cast( - this->internal->decompressed_buffer.data()), + this->internal_->decompressed_buffer.data()), produced}); - this->internal->member_isize += static_cast(produced); + this->internal_->member_isize += static_cast(produced); - auto *buffer_start{ - reinterpret_cast(this->internal->decompressed_buffer.data())}; + auto *buffer_start{reinterpret_cast( + this->internal_->decompressed_buffer.data())}; this->setg(buffer_start, buffer_start, buffer_start + static_cast(produced)); return traits_type::to_int_type(*this->gptr()); } - if (!this->internal->deflate.stream_ended()) { + if (!this->internal_->deflate.stream_ended()) { throw GZIPError{"Deflate stream ended unexpectedly"}; } std::array trailer{}; - this->internal->reader.read_bytes(trailer.data(), trailer.size()); + this->internal_->reader.read_bytes(trailer.data(), trailer.size()); const auto stored_crc32{static_cast(trailer[0]) | (static_cast(trailer[1]) << 8) | (static_cast(trailer[2]) << 16) | @@ -226,15 +226,15 @@ auto GZIPStreamBuffer::underflow() -> int_type { (static_cast(trailer[5]) << 8) | (static_cast(trailer[6]) << 16) | (static_cast(trailer[7]) << 24)}; - if (stored_crc32 != this->internal->member_crc32) { + if (stored_crc32 != this->internal_->member_crc32) { throw GZIPError{"Gzip member CRC32 mismatch"}; } - if (stored_isize != this->internal->member_isize) { + if (stored_isize != this->internal_->member_isize) { throw GZIPError{"Gzip member ISIZE mismatch"}; } - this->internal->any_member_completed = true; - this->internal->member_started = false; + this->internal_->any_member_completed = true; + this->internal_->member_started = false; } } diff --git a/vendor/core/src/core/html/include/sourcemeta/core/html_buffer.h b/vendor/core/src/core/html/include/sourcemeta/core/html_buffer.h index e89595fd..42e3fd86 100644 --- a/vendor/core/src/core/html/include/sourcemeta/core/html_buffer.h +++ b/vendor/core/src/core/html/include/sourcemeta/core/html_buffer.h @@ -25,15 +25,16 @@ class SOURCEMETA_CORE_HTML_EXPORT HTMLBuffer { auto operator=(HTMLBuffer &&) -> HTMLBuffer & = delete; /// Reserve a number of bytes of capacity up front - SOURCEMETA_FORCEINLINE inline auto reserve(const std::size_t bytes) -> void { + SOURCEMETA_FORCEINLINE auto reserve(const std::size_t bytes) -> void { this->buffer_.resize(bytes); this->cursor_ = this->buffer_.data(); this->end_ = this->cursor_ + bytes; } /// Append a single character to the buffer - SOURCEMETA_FORCEINLINE inline auto append(const char character) -> void { - if (!this->cursor_ || this->cursor_ >= this->end_) [[unlikely]] { + SOURCEMETA_FORCEINLINE auto append(const char character) -> void { + if ((this->cursor_ == nullptr) || this->cursor_ >= this->end_) + [[unlikely]] { this->grow(1); } @@ -42,16 +43,16 @@ class SOURCEMETA_CORE_HTML_EXPORT HTMLBuffer { } /// Append a sequence of characters to the buffer - SOURCEMETA_FORCEINLINE inline auto append(const std::string_view data) - -> void { + SOURCEMETA_FORCEINLINE auto append(const std::string_view data) -> void { const auto length{data.size()}; if (length == 0) { return; } const auto remaining{ - this->cursor_ ? static_cast(this->end_ - this->cursor_) - : 0uz}; + (this->cursor_ != nullptr) + ? static_cast(this->end_ - this->cursor_) + : 0UZ}; if (remaining < length) [[unlikely]] { this->grow(length); } @@ -61,9 +62,8 @@ class SOURCEMETA_CORE_HTML_EXPORT HTMLBuffer { } /// Get the accumulated contents of the buffer - [[nodiscard]] SOURCEMETA_FORCEINLINE inline auto str() - -> const std::string & { - if (this->cursor_) { + [[nodiscard]] SOURCEMETA_FORCEINLINE auto str() -> const std::string & { + if (this->cursor_ != nullptr) { this->buffer_.resize( static_cast(this->cursor_ - this->buffer_.data())); this->cursor_ = nullptr; diff --git a/vendor/core/src/core/html/include/sourcemeta/core/html_writer.h b/vendor/core/src/core/html/include/sourcemeta/core/html_writer.h index 28f2ad57..829d5817 100644 --- a/vendor/core/src/core/html/include/sourcemeta/core/html_writer.h +++ b/vendor/core/src/core/html/include/sourcemeta/core/html_writer.h @@ -33,13 +33,13 @@ namespace sourcemeta::core { class SOURCEMETA_CORE_HTML_EXPORT HTMLWriter { public: /// Pre-allocate the output buffer - SOURCEMETA_FORCEINLINE inline auto reserve(std::size_t bytes) -> void { + SOURCEMETA_FORCEINLINE auto reserve(std::size_t bytes) -> void { this->buffer_.reserve(bytes); } /// Close the most recently opened element. Closing when no element is open /// has no effect. - SOURCEMETA_FORCEINLINE inline auto close() -> HTMLWriter & { + SOURCEMETA_FORCEINLINE auto close() -> HTMLWriter & { this->flush_open_tag(); assert(!this->tag_stack_.empty()); if (this->tag_stack_.empty()) [[unlikely]] { @@ -54,8 +54,8 @@ class SOURCEMETA_CORE_HTML_EXPORT HTMLWriter { /// Add an attribute to the currently open tag. Must be called /// immediately after an element method and before any content. - SOURCEMETA_FORCEINLINE inline auto attribute(std::string_view name, - std::string_view value) + SOURCEMETA_FORCEINLINE auto attribute(std::string_view name, + std::string_view value) -> HTMLWriter & { assert(this->tag_open_); this->buffer_.append(" "); @@ -72,8 +72,7 @@ class SOURCEMETA_CORE_HTML_EXPORT HTMLWriter { /// would corrupt it. This writer does not special-case content by element, so /// the content of a raw-text element must be written unescaped rather than as /// escaped text, and it must not contain that element's closing-tag sequence. - SOURCEMETA_FORCEINLINE inline auto text(std::string_view content) - -> HTMLWriter & { + SOURCEMETA_FORCEINLINE auto text(std::string_view content) -> HTMLWriter & { this->flush_open_tag(); html_escape_append(this->buffer_, content); return *this; @@ -81,16 +80,14 @@ class SOURCEMETA_CORE_HTML_EXPORT HTMLWriter { /// Write content without HTML-escaping. This is how the content of a raw-text /// element is emitted, since escaped text would corrupt it. - SOURCEMETA_FORCEINLINE inline auto raw(std::string_view content) - -> HTMLWriter & { + SOURCEMETA_FORCEINLINE auto raw(std::string_view content) -> HTMLWriter & { this->flush_open_tag(); this->buffer_.append(content); return *this; } /// Get the rendered HTML string - [[nodiscard]] SOURCEMETA_FORCEINLINE inline auto str() - -> const std::string & { + [[nodiscard]] SOURCEMETA_FORCEINLINE auto str() -> const std::string & { this->flush_open_tag(); return this->buffer_.str(); } @@ -111,43 +108,46 @@ class SOURCEMETA_CORE_HTML_EXPORT HTMLWriter { // Overloads: // .tag() open with no attributes // .tag(text) open, write escaped text, close (shorthand) +// NOLINTBEGIN(bugprone-macro-parentheses) #define HTML_WRITER_CONTAINER(name) \ - SOURCEMETA_FORCEINLINE inline auto name() -> HTMLWriter & { \ + SOURCEMETA_FORCEINLINE auto name() -> HTMLWriter & { \ this->open_tag(#name); \ return *this; \ } \ - /* NOLINTNEXTLINE(bugprone-macro-parentheses) */ \ - SOURCEMETA_FORCEINLINE inline auto name(std::string_view text_content) \ + SOURCEMETA_FORCEINLINE auto name(std::string_view text_content) \ -> HTMLWriter & { \ this->open_tag(#name); \ this->text(text_content); \ this->close(); \ return *this; \ } +// NOLINTEND(bugprone-macro-parentheses) // Same as above but with a different C++ method name than the HTML tag +// NOLINTBEGIN(bugprone-macro-parentheses) #define HTML_WRITER_CONTAINER_NAMED(name, tag) \ - SOURCEMETA_FORCEINLINE inline auto name() -> HTMLWriter & { \ + SOURCEMETA_FORCEINLINE auto name() -> HTMLWriter & { \ this->open_tag(#tag); \ return *this; \ } \ - /* NOLINTNEXTLINE(bugprone-macro-parentheses) */ \ - SOURCEMETA_FORCEINLINE inline auto name(std::string_view text_content) \ + SOURCEMETA_FORCEINLINE auto name(std::string_view text_content) \ -> HTMLWriter & { \ this->open_tag(#tag); \ this->text(text_content); \ this->close(); \ return *this; \ } +// NOLINTEND(bugprone-macro-parentheses) // Macro to generate void element methods. // Void elements are self-closing: or +// NOLINTBEGIN(bugprone-macro-parentheses) #define HTML_WRITER_VOID(name) \ - /* NOLINTNEXTLINE(bugprone-macro-parentheses) */ \ - SOURCEMETA_FORCEINLINE inline auto name() -> HTMLWriter & { \ + SOURCEMETA_FORCEINLINE auto name() -> HTMLWriter & { \ this->void_tag(#name); \ return *this; \ } +// NOLINTEND(bugprone-macro-parentheses) #endif // ========================================================================= @@ -434,6 +434,9 @@ class SOURCEMETA_CORE_HTML_EXPORT HTMLWriter { /// @ingroup html HTML_WRITER_CONTAINER(slot) /// @ingroup html + // The trailing underscore keeps this element name from colliding with the + // template keyword + // NOLINTNEXTLINE(readability-identifier-naming) HTML_WRITER_CONTAINER_NAMED(template_, template) #ifndef DOXYGEN @@ -443,7 +446,7 @@ class SOURCEMETA_CORE_HTML_EXPORT HTMLWriter { #endif private: - SOURCEMETA_FORCEINLINE inline auto open_tag(std::string_view tag) -> void { + SOURCEMETA_FORCEINLINE auto open_tag(std::string_view tag) -> void { this->flush_open_tag(); this->buffer_.append("<"); this->buffer_.append(tag); @@ -452,7 +455,7 @@ class SOURCEMETA_CORE_HTML_EXPORT HTMLWriter { this->tag_open_is_void_ = false; } - SOURCEMETA_FORCEINLINE inline auto void_tag(std::string_view tag) -> void { + SOURCEMETA_FORCEINLINE auto void_tag(std::string_view tag) -> void { this->flush_open_tag(); this->buffer_.append("<"); this->buffer_.append(tag); diff --git a/vendor/core/src/core/html/writer.cc b/vendor/core/src/core/html/writer.cc index ee6775a4..4bd1d373 100644 --- a/vendor/core/src/core/html/writer.cc +++ b/vendor/core/src/core/html/writer.cc @@ -6,10 +6,10 @@ namespace sourcemeta::core { auto HTMLBuffer::grow(const std::size_t needed) -> void { const auto current_size{ - this->cursor_ + (this->cursor_ != nullptr) ? static_cast(this->cursor_ - this->buffer_.data()) : 0}; - auto new_capacity{this->buffer_.empty() ? 1024uz : this->buffer_.size() * 2}; + auto new_capacity{this->buffer_.empty() ? 1024UZ : this->buffer_.size() * 2}; while (new_capacity < current_size + needed) { new_capacity *= 2; } @@ -20,7 +20,7 @@ auto HTMLBuffer::grow(const std::size_t needed) -> void { } auto HTMLBuffer::write(std::ostream &stream) -> void { - if (this->cursor_) { + if (this->cursor_ != nullptr) { const auto size{ static_cast(this->cursor_ - this->buffer_.data())}; stream.write(this->buffer_.data(), static_cast(size)); diff --git a/vendor/core/src/core/http/CMakeLists.txt b/vendor/core/src/core/http/CMakeLists.txt index 513233b5..b1008acd 100644 --- a/vendor/core/src/core/http/CMakeLists.txt +++ b/vendor/core/src/core/http/CMakeLists.txt @@ -12,9 +12,12 @@ sourcemeta_library(NAMESPACE sourcemeta PROJECT core NAME http PRIVATE_HEADERS problem.h status.h method.h message.h error.h system.h aws_sigv4.h syntax.h SOURCES helpers.h problem.cc match_accept.cc match_accept_language.cc - negotiate_encoding.cc from_date.cc format_link.cc field_list.cc + negotiate_encoding.cc from_date.cc format_link.cc format_vary.cc + field_list.cc accept_includes_all.cc content_type_matches.cc parse_bearer.cc cache_control_max_age.cc serialize_cookie.cc aws_sigv4.cc + serialize_challenge.cc parse_challenges.cc + serialize_cache_control.cc scan_quoted_string.cc ${SOURCEMETA_CORE_HTTP_CLIENT_SOURCE}) diff --git a/vendor/core/src/core/http/accept_includes_all.cc b/vendor/core/src/core/http/accept_includes_all.cc index 3808b6d4..f887c39e 100644 --- a/vendor/core/src/core/http/accept_includes_all.cc +++ b/vendor/core/src/core/http/accept_includes_all.cc @@ -29,7 +29,7 @@ auto http_accept_includes_all( assert(slash > 0); assert(slash < media_type_bare.size() - 1); assert(media_type_bare.find_first_of(" \t,;*") == std::string_view::npos); - float best_quality{0.0f}; + float best_quality{0.0F}; std::uint8_t best_specificity{0}; http_for_each_media_range( accept_header, @@ -46,7 +46,7 @@ auto http_accept_includes_all( best_specificity = specificity; } }); - if (best_specificity == 0 || best_quality == 0.0f) { + if (best_specificity == 0 || best_quality == 0.0F) { return false; } } diff --git a/vendor/core/src/core/http/cache_control_max_age.cc b/vendor/core/src/core/http/cache_control_max_age.cc index d00aebf8..bdf10fa6 100644 --- a/vendor/core/src/core/http/cache_control_max_age.cc +++ b/vendor/core/src/core/http/cache_control_max_age.cc @@ -2,6 +2,7 @@ #include "helpers.h" +#include // std::min #include // std::chrono::seconds #include // std::size_t #include // std::optional, std::nullopt @@ -13,7 +14,7 @@ auto http_cache_control_max_age(const std::string_view cache_control) noexcept -> std::optional { // RFC 9111 §1.2.2: a delta-seconds value larger than the cache can represent // is treated as 2^31 - constexpr std::chrono::seconds::rep overflow_seconds{2147483648}; + constexpr std::chrono::seconds::rep OVERFLOW_SECONDS{2147483648}; std::optional result; bool found{false}; http_for_each_list_entry( @@ -65,17 +66,15 @@ auto http_cache_control_max_age(const std::string_view cache_control) noexcept // Clamp before multiplying so the running total cannot itself // overflow on a hostile number of digits - if (seconds > overflow_seconds / 10) { - seconds = overflow_seconds; + if (seconds > OVERFLOW_SECONDS / 10) { + seconds = OVERFLOW_SECONDS; continue; } seconds = (seconds * 10) + (character - '0'); } - if (seconds > overflow_seconds) { - seconds = overflow_seconds; - } + seconds = std::min(seconds, OVERFLOW_SECONDS); result = std::chrono::seconds{seconds}; }); diff --git a/vendor/core/src/core/http/client_darwin.mm b/vendor/core/src/core/http/client_darwin.mm index 872c23ac..9d01800d 100644 --- a/vendor/core/src/core/http/client_darwin.mm +++ b/vendor/core/src/core/http/client_darwin.mm @@ -44,11 +44,11 @@ - (void)URLSession:(NSURLSession *)session willPerformHTTPRedirection:(NSHTTPURLResponse *)response newRequest:(NSURLRequest *)request completionHandler: - (void (^)(NSURLRequest *))completionHandler { + (void (^)(NSURLRequest *))completion_handler { // Passing a nil request stops the redirection and delivers the redirect // response itself as the final response if (!self.followRedirects) { - completionHandler(nil); + completion_handler(nil); return; } @@ -56,15 +56,15 @@ - (void)URLSession:(NSURLSession *)session if (self.redirectCount > self.maximumRedirects) { self.failure->assign("The maximum number of redirects was exceeded"); [task cancel]; - completionHandler(nil); + completion_handler(nil); return; } - completionHandler(request); + completion_handler(request); } - (void)URLSession:(NSURLSession *)session - dataTask:(NSURLSessionDataTask *)dataTask + dataTask:(NSURLSessionDataTask *)data_task didReceiveData:(NSData *)data { auto *body{&self.response->body}; if (self.hasMaximumResponseSize && @@ -72,7 +72,7 @@ - (void)URLSession:(NSURLSession *)session static_cast(data.length) > self.maximumResponseSize - body->size())) { self.failure->assign(HTTP_RESPONSE_TOO_LARGE_MESSAGE); - [dataTask cancel]; + [data_task cancel]; return; } diff --git a/vendor/core/src/core/http/format_vary.cc b/vendor/core/src/core/http/format_vary.cc new file mode 100644 index 00000000..9d44a795 --- /dev/null +++ b/vendor/core/src/core/http/format_vary.cc @@ -0,0 +1,55 @@ +#include + +#include // std::size_t +#include // std::optional, std::nullopt +#include // std::span +#include // std::string +#include // std::string_view + +namespace { + +constexpr std::string_view VARY_ENTRY_SEPARATOR{", "}; + +} // namespace + +namespace sourcemeta::core { + +auto http_format_vary(std::span field_names, + std::string &out) -> bool { + // RFC 9110 §5.6.1.1: "a sender MUST NOT generate empty list elements", which + // leaves nothing to send for an empty list, and §5.1 makes every member a + // token, the wildcard among them + if (field_names.empty()) { + return false; + } + + std::size_t total{VARY_ENTRY_SEPARATOR.size() * (field_names.size() - 1)}; + for (const auto field_name : field_names) { + if (!http_is_token(field_name)) { + return false; + } + + total += field_name.size(); + } + + out.reserve(out.size() + total); + out.append(field_names.front()); + for (const auto field_name : field_names.subspan(1)) { + out.append(VARY_ENTRY_SEPARATOR); + out.append(field_name); + } + + return true; +} + +auto http_format_vary(std::span field_names) + -> std::optional { + std::string out; + if (!http_format_vary(field_names, out)) { + return std::nullopt; + } + + return out; +} + +} // namespace sourcemeta::core diff --git a/vendor/core/src/core/http/helpers.h b/vendor/core/src/core/http/helpers.h index 146531e4..293a8582 100644 --- a/vendor/core/src/core/http/helpers.h +++ b/vendor/core/src/core/http/helpers.h @@ -265,40 +265,41 @@ inline auto http_media_range_specificity( // routed here and keeps its 1.0 default at the call site. inline auto http_parse_qvalue(const std::string_view value) noexcept -> float { if (value.empty()) { - return 0.0f; + return 0.0F; } if (value[0] != '0' && value[0] != '1') { - return 0.0f; + return 0.0F; } const float integer_part{static_cast(value[0] - '0')}; if (value.size() == 1) { return integer_part; } if (value[1] != '.' || value.size() > 5) { - return 0.0f; + return 0.0F; } std::uint16_t numerator{0}; std::uint16_t denominator{1}; for (std::size_t index{2}; index < value.size(); ++index) { const char character{value[index]}; if (character < '0' || character > '9') { - return 0.0f; + return 0.0F; } - numerator = static_cast(numerator * 10 + (character - '0')); + numerator = + static_cast((numerator * 10) + (character - '0')); denominator = static_cast(denominator * 10); } const float fraction{static_cast(numerator) / static_cast(denominator)}; const float result{integer_part + fraction}; - if (result > 1.0f) { - return 0.0f; + if (result > 1.0F) { + return 0.0F; } return result; } inline auto http_extract_quality(const std::string_view parameters) noexcept -> float { - float quality{1.0f}; + float quality{1.0F}; http_for_each_parameter( parameters, [&quality](const std::string_view name, @@ -321,7 +322,7 @@ inline auto http_split_media_range(const std::string_view parameters) noexcept -> std::pair { std::size_t position{0}; std::size_t media_parameters_end{parameters.size()}; - float quality{1.0f}; + float quality{1.0F}; while (position < parameters.size()) { const std::size_t separator{position}; if (parameters[position] == ';') { diff --git a/vendor/core/src/core/http/include/sourcemeta/core/http.h b/vendor/core/src/core/http/include/sourcemeta/core/http.h index 9a8073e1..a99bfcf2 100644 --- a/vendor/core/src/core/http/include/sourcemeta/core/http.h +++ b/vendor/core/src/core/http/include/sourcemeta/core/http.h @@ -17,6 +17,7 @@ // NOLINTEND(misc-include-cleaner) #include // std::chrono::system_clock +#include // std::size_t #include // std::uint8_t #include // std::initializer_list #include // std::optional @@ -24,6 +25,7 @@ #include // std::string #include // std::string_view #include // std::pair +#include // std::vector /// @defgroup http HTTP /// @brief An implementation of HTTP-protocol parsing, formatting, and @@ -262,6 +264,273 @@ auto http_format_links(std::span links, std::string &out) SOURCEMETA_CORE_HTTP_EXPORT auto http_format_links(std::span links) -> std::string; +/// @ingroup http +/// Whether a cached response may be stored by a shared cache or only by a +/// private one (RFC 9111 §5.2.2.7 and §5.2.2.9). The two are alternatives +/// rather than flags, so a response cannot be marked both. +enum class HTTPCacheVisibility : std::uint8_t { + /// A cache may store the response even where it would otherwise be + /// prohibited, which includes a shared cache reusing a response to an + /// authorized request. + Public, + /// A shared cache must not store the response, the response being intended + /// for a single user. + Private +}; + +/// @ingroup http +/// The response directives to serialise into an RFC 9111 §5.2 `Cache-Control` +/// header value. The caller owns the backing storage for every field. +/// +/// Whether a response may be stored by a shared cache is an access decision as +/// much as a performance one, so it is carried as one alternative rather than +/// as two independent flags that could both be set. +struct HTTPCacheControl { + /// Which caches may store the response + std::optional visibility{}; + /// How long the response stays fresh (RFC 9111 §5.2.2.1) + std::optional max_age{}; + /// How long the response stays fresh for a shared cache, overriding the + /// above for one (RFC 9111 §5.2.2.10) + std::optional shared_max_age{}; + /// Whether no cache may store any part of the exchange (RFC 9111 §5.2.2.5) + bool no_store{false}; + /// Whether the response must be validated before every reuse (RFC 9111 + /// §5.2.2.4), ignored when a field list qualifies it below + bool no_cache{false}; + /// Whether a stale response must be validated before reuse (RFC 9111 + /// §5.2.2.2) + bool must_revalidate{false}; + /// The same for a shared cache alone (RFC 9111 §5.2.2.8) + bool proxy_revalidate{false}; + /// Whether storing is limited to a cache that implements the status code's + /// caching requirements (RFC 9111 §5.2.2.3) + bool must_understand{false}; + /// Whether the payload must not be transformed (RFC 9111 §5.2.2.6) + bool no_transform{false}; + /// Whether the response will not be updated while fresh (RFC 8246) + bool immutable{false}; + /// The fields a shared cache must not store, which qualifies the private + /// directive and so requires it (RFC 9111 §5.2.2.7) + std::span private_fields{}; + /// The fields that must be revalidated before reuse, which qualifies the + /// no-cache directive (RFC 9111 §5.2.2.4) + std::span no_cache_fields{}; +}; + +/// @ingroup http +/// Test whether a set of directives can be serialised into a valid RFC 9111 +/// §5.2 `Cache-Control` header value: every duration is the non-negative +/// integer §1.2.2 defines, every qualifying field name is an RFC 9110 §5.6.2 +/// token, a private field list is accompanied by the directive it qualifies, +/// and at least one directive is named, since §5.2 lists them and RFC 9110 +/// §5.6.1.1 forbids a sender from generating an empty list element. +SOURCEMETA_CORE_HTTP_EXPORT +auto http_cache_control_valid(const HTTPCacheControl &directives) -> bool; + +/// @ingroup http +/// Append an RFC 9111 §5.2 `Cache-Control` header value to `out`, returning +/// `true` on success. When the directives are not `http_cache_control_valid`, +/// `out` is left unchanged and this returns `false`. +/// +/// A duration is written in the token form §5.2.2.1 requires, never quoted, +/// while a field list is written in the quoted-string form §5.2.2.4 and +/// §5.2.2.7 ask a sender to use even for a single entry. A qualifying field +/// list replaces the bare directive rather than joining it. For example: +/// +/// ```cpp +/// #include +/// #include +/// #include +/// #include +/// +/// std::string buffer; +/// const auto ok{sourcemeta::core::http_serialize_cache_control( +/// {.visibility = sourcemeta::core::HTTPCacheVisibility::Private, +/// .max_age = std::chrono::seconds{60}}, buffer)}; +/// assert(ok); +/// assert(buffer == "private, max-age=60"); +/// ``` +SOURCEMETA_CORE_HTTP_EXPORT +auto http_serialize_cache_control(const HTTPCacheControl &directives, + std::string &out) -> bool; + +/// @ingroup http +/// Serialise an RFC 9111 §5.2 `Cache-Control` header value, returning no value +/// when the directives are not `http_cache_control_valid`. For example: +/// +/// ```cpp +/// #include +/// #include +/// +/// const auto value{sourcemeta::core::http_serialize_cache_control( +/// {.no_store = true})}; +/// assert(value == "no-store"); +/// ``` +SOURCEMETA_CORE_HTTP_EXPORT +auto http_serialize_cache_control(const HTTPCacheControl &directives) + -> std::optional; + +/// @ingroup http +/// A challenge to serialise into an RFC 9110 §11.6.1 `WWW-Authenticate` +/// response header value. The caller owns the backing storage for every field. +/// +/// RFC 9110 §11.3 gives `challenge = auth-scheme [ 1*SP ( token68 / +/// #auth-param ) ]`, so a challenge carries either a credential or a parameter +/// list and never both. +struct HTTPChallenge { + /// The authentication scheme + std::string_view scheme{}; + /// The credential the challenge carries in place of parameters + std::optional token68{}; + /// The authentication parameters + std::span> parameters{}; +}; + +/// @ingroup http +/// Test whether a challenge can be serialised into a valid RFC 9110 §11.6.1 +/// `WWW-Authenticate` header value: the scheme is a non-empty RFC 9110 §5.6.2 +/// token, a present credential is a §11.2 token68 and stands alone, every +/// parameter name is a token, no parameter name repeats under the +/// case-insensitive matching §11.2 mandates, and every value is encodable as a +/// §5.6.4 quoted-string. +/// +/// A challenge naming the Bearer scheme is held to RFC 6750 §3 as well, which +/// requires at least one parameter and bounds the octets `scope`, `error`, +/// `error_description` and `error_uri` may carry. For example: +/// +/// ```cpp +/// #include +/// #include +/// #include +/// #include +/// #include +/// +/// const std::array, 1> +/// parameters{{{"realm", "example"}}}; +/// assert(sourcemeta::core::http_challenge_valid( +/// {.scheme = "Bearer", .parameters = parameters})); +/// ``` +SOURCEMETA_CORE_HTTP_EXPORT +auto http_challenge_valid(const HTTPChallenge &challenge) -> bool; + +/// @ingroup http +/// Append an RFC 9110 §11.6.1 `WWW-Authenticate` challenge to `out`, returning +/// `true` on success. When the challenge is not `http_challenge_valid`, `out` +/// is left unchanged and this returns `false`. +/// +/// Every parameter value is spelled as a §5.6.4 quoted-string, since §11.5 +/// leaves a sender no other choice for a realm, and the encoding escapes a +/// quote or a backslash rather than letting either close the value early. For +/// example: +/// +/// ```cpp +/// #include +/// #include +/// #include +/// #include +/// #include +/// #include +/// +/// const std::array, 1> +/// parameters{{{"realm", "example"}}}; +/// std::string buffer; +/// const auto ok{sourcemeta::core::http_serialize_challenge( +/// {.scheme = "Bearer", .parameters = parameters}, buffer)}; +/// assert(ok); +/// assert(buffer == "Bearer realm=\"example\""); +/// ``` +SOURCEMETA_CORE_HTTP_EXPORT +auto http_serialize_challenge(const HTTPChallenge &challenge, std::string &out) + -> bool; + +/// @ingroup http +/// Serialise an RFC 9110 §11.6.1 `WWW-Authenticate` challenge, returning no +/// value when the challenge is not `http_challenge_valid`. +SOURCEMETA_CORE_HTTP_EXPORT +auto http_serialize_challenge(const HTTPChallenge &challenge) + -> std::optional; + +/// @ingroup http +/// Append a whole RFC 9110 §11.6.1 `WWW-Authenticate` header value to `out`, +/// which is a list of challenges. Returns `false` without touching `out` when +/// the list is empty, since §11.6.1 requires a 401 to carry at least one +/// challenge, or when any challenge is not `http_challenge_valid`. +SOURCEMETA_CORE_HTTP_EXPORT +auto http_serialize_challenges(std::span challenges, + std::string &out) -> bool; + +/// @ingroup http +/// Serialise a whole RFC 9110 §11.6.1 `WWW-Authenticate` header value, +/// returning no value when the list is empty or carries an invalid challenge. +/// For example: +/// +/// ```cpp +/// #include +/// #include +/// #include +/// #include +/// #include +/// +/// const std::array, 1> +/// parameters{{{"realm", "api"}}}; +/// const std::array challenges{ +/// {{.scheme = "Bearer", .parameters = parameters}}}; +/// const auto value{sourcemeta::core::http_serialize_challenges(challenges)}; +/// assert(value == "Bearer realm=\"api\""); +/// ``` +SOURCEMETA_CORE_HTTP_EXPORT +auto http_serialize_challenges(std::span challenges) + -> std::optional; + +/// @ingroup http +/// A challenge read out of an RFC 9110 §11.6.1 `WWW-Authenticate` header +/// value. Unlike the challenge a caller hands to the serialiser, this one owns +/// its strings, since a quoted-pair means a value does not always appear +/// verbatim in the field it was read from. +struct HTTPParsedChallenge { + /// The authentication scheme, as spelled in the field + std::string scheme; + /// The credential the challenge carried in place of parameters + std::optional token68; + /// The authentication parameters, in the order they appeared + std::vector> parameters; +}; + +/// @ingroup http +/// Parse an RFC 9110 §11.6.1 `WWW-Authenticate` header value, given without +/// the field name, into its challenges, returning `false` and leaving the +/// container empty when the value is malformed or carries no challenge at all. +/// +/// The field is a list of challenges whose parameters are themselves +/// comma-separated, so §11.6.1 warns recipients to take special care. A token +/// followed by an equals sign continues the challenge being read, while one +/// that is not opens the next, and a run of token characters closed by equals +/// padding and nothing else is a §11.2 token68 rather than a parameter without +/// a value. Empty list elements are ignored as §5.6.1.2 requires. +/// +/// This applies the RFC 9110 grammar alone. A recipient judges nothing beyond +/// it, so a challenge that `http_challenge_valid` would refuse a sender, such +/// as a bare Bearer, still parses. For example: +/// +/// ```cpp +/// #include +/// #include +/// #include +/// +/// std::vector challenges; +/// const auto ok{sourcemeta::core::http_parse_challenges( +/// "Bearer realm=\"example\"", challenges)}; +/// assert(ok); +/// assert(challenges.size() == 1); +/// assert(challenges.at(0).scheme == "Bearer"); +/// assert(challenges.at(0).parameters.at(0).second == "example"); +/// ``` +SOURCEMETA_CORE_HTTP_EXPORT +auto http_parse_challenges(const std::string_view input, + std::vector &challenges) + -> bool; + /// @ingroup http /// The `SameSite` attribute of a cookie per RFC 6265bis §5.2. enum class HTTPCookieSameSite : std::uint8_t { @@ -298,6 +567,20 @@ struct HTTPCookie { std::optional same_site{}; }; +/// @ingroup http +/// The RFC 6265bis §5.7 ceiling on the sum of the lengths of a cookie name and +/// its value, past which a user agent ignores the cookie entirely. Note that +/// the RFC 6265 §6.1 minimum capability names the same number but measures the +/// attributes into it as well, so a cookie sized against this one is not +/// necessarily within that older bound. +inline constexpr std::size_t HTTP_COOKIE_MAXIMUM_NAME_VALUE_LENGTH{4096}; + +/// @ingroup http +/// The RFC 6265bis §5.6 ceiling on the length of a single cookie attribute +/// value, past which a user agent ignores that attribute while keeping the +/// cookie, silently widening or narrowing the scope the server asked for. +inline constexpr std::size_t HTTP_COOKIE_MAXIMUM_ATTRIBUTE_VALUE_LENGTH{1024}; + /// @ingroup http /// Test whether a cookie can be serialised into a valid RFC 6265 §4.1 /// `Set-Cookie` header value: the name is a non-empty RFC 9110 §5.6.2 token, @@ -306,7 +589,9 @@ struct HTTPCookie { /// ignorable leading dot, a present `max_age` is not negative, per RFC 6265bis /// §5.7 a `HTTPCookieSameSite::None` cookie is also `secure`, and the RFC /// 6265bis §4.1.3 `__Secure-` and `__Host-` name prefixes carry their required -/// attributes. For example: +/// attributes. The RFC 6265bis §5.6 and §5.7 length ceilings are enforced too, +/// since a cookie past either is ignored in whole or in part by the user +/// agent. For example: /// /// ```cpp /// #include @@ -353,6 +638,25 @@ SOURCEMETA_CORE_HTTP_EXPORT auto http_serialize_cookie(const HTTPCookie &cookie) -> std::optional; +/// @ingroup http +/// Derive the cookie that removes a cookie, carrying its name and every +/// attribute across so that the RFC 6265 §3.1 requirement to match the path +/// and the domain of the original is met by construction. The value is +/// dropped, and the lifetime is set to zero, which RFC 6265bis §5.6.2 maps to +/// "the earliest representable date and time". For example: +/// +/// ```cpp +/// #include +/// #include +/// +/// const auto value{sourcemeta::core::http_serialize_cookie( +/// sourcemeta::core::http_expire_cookie( +/// {.name = "session", .value = "abc", .http_only = true}))}; +/// assert(value == "session=; Max-Age=0; HttpOnly"); +/// ``` +SOURCEMETA_CORE_HTTP_EXPORT +auto http_expire_cookie(const HTTPCookie &cookie) -> HTTPCookie; + /// @ingroup http /// Test whether a comma-separated header value per RFC 9110 §5.6.1 lists any /// of the given tokens. For example: @@ -371,6 +675,55 @@ auto http_field_list_contains_any( const std::string_view header_value, std::initializer_list tokens) noexcept -> bool; +/// @ingroup http +/// Append an RFC 9110 §12.5.5 `Vary` header value to `out`, returning `true` +/// on success. Every member must be a non-empty RFC 9110 §5.6.2 token, which +/// the wildcard also is, since §5.1 defines a field name as one and §5.6.1.1 +/// forbids a sender from generating an empty list element. An empty list has +/// nothing to send, so the header is left to be omitted rather than emitted +/// blank. The spelling of each name is kept as given, as §5.1 makes field +/// names case-insensitive. Nothing is appended and this returns `false` when +/// any member is refused, which keeps a name from injecting a further field. +/// For example: +/// +/// ```cpp +/// #include +/// #include +/// #include +/// #include +/// #include +/// +/// const std::array names{{"Accept", "Accept-Encoding"}}; +/// std::string buffer; +/// const auto ok{sourcemeta::core::http_format_vary(names, buffer)}; +/// assert(ok); +/// assert(buffer == "Accept, Accept-Encoding"); +/// ``` +SOURCEMETA_CORE_HTTP_EXPORT +auto http_format_vary(std::span field_names, + std::string &out) -> bool; + +/// @ingroup http +/// Compose an RFC 9110 §12.5.5 `Vary` header value, returning no value when +/// any member is not an RFC 9110 §5.6.2 token or when there are none. +/// +/// Note that RFC 9110 §12.5.5 forbids a proxy from generating the wildcard +/// member, so only an origin server may compose one. For example: +/// +/// ```cpp +/// #include +/// #include +/// #include +/// #include +/// +/// const std::array names{{"Accept", "Accept-Encoding"}}; +/// const auto value{sourcemeta::core::http_format_vary(names)}; +/// assert(value == "Accept, Accept-Encoding"); +/// ``` +SOURCEMETA_CORE_HTTP_EXPORT +auto http_format_vary(std::span field_names) + -> std::optional; + /// @ingroup http /// Extract the credential from an `Authorization` header that uses the Bearer /// scheme per RFC 6750 §2.1, matching the scheme case-insensitively per RFC diff --git a/vendor/core/src/core/http/include/sourcemeta/core/http_message.h b/vendor/core/src/core/http/include/sourcemeta/core/http_message.h index 8f83ec9c..4168e3ba 100644 --- a/vendor/core/src/core/http/include/sourcemeta/core/http_message.h +++ b/vendor/core/src/core/http/include/sourcemeta/core/http_message.h @@ -24,7 +24,7 @@ namespace sourcemeta::core { /// assert(sourcemeta::core::http_is_status_line("HTTP/1.1 200 OK")); /// assert(!sourcemeta::core::http_is_status_line("Content-Type: text/html")); /// ``` -inline constexpr auto http_is_status_line(const std::string_view line) noexcept +constexpr auto http_is_status_line(const std::string_view line) noexcept -> bool { // The prefix cannot open a field line, as RFC 9110 §5.6.2 defines tchar // as "any VCHAR, except delimiters" where delimiters include the slash, diff --git a/vendor/core/src/core/http/include/sourcemeta/core/http_method.h b/vendor/core/src/core/http/include/sourcemeta/core/http_method.h index 9e89ccfd..f9d5465f 100644 --- a/vendor/core/src/core/http/include/sourcemeta/core/http_method.h +++ b/vendor/core/src/core/http/include/sourcemeta/core/http_method.h @@ -41,7 +41,7 @@ enum class HTTPMethod : std::uint8_t { /// assert(sourcemeta::core::http_method_string( /// sourcemeta::core::HTTPMethod::GET) == "GET"); /// ``` -inline constexpr auto http_method_string(const HTTPMethod method) noexcept +constexpr auto http_method_string(const HTTPMethod method) noexcept -> std::string_view { switch (method) { case HTTPMethod::GET: diff --git a/vendor/core/src/core/http/include/sourcemeta/core/http_status.h b/vendor/core/src/core/http/include/sourcemeta/core/http_status.h index 53a1aae1..b46bfa7c 100644 --- a/vendor/core/src/core/http/include/sourcemeta/core/http_status.h +++ b/vendor/core/src/core/http/include/sourcemeta/core/http_status.h @@ -401,7 +401,7 @@ inline constexpr HTTPStatus HTTP_STATUS_NETWORK_AUTHENTICATION_REQUIRED{ /// sourcemeta::core::HTTP_STATUS_OK); /// assert(sourcemeta::core::http_status_from_code(599).phrase.empty()); /// ``` -inline constexpr auto http_status_from_code(const std::uint16_t code) noexcept +constexpr auto http_status_from_code(const std::uint16_t code) noexcept -> HTTPStatus { switch (code) { case 100: diff --git a/vendor/core/src/core/http/match_accept.cc b/vendor/core/src/core/http/match_accept.cc index 5231f81e..e3e4d136 100644 --- a/vendor/core/src/core/http/match_accept.cc +++ b/vendor/core/src/core/http/match_accept.cc @@ -20,7 +20,7 @@ auto http_match_accept(const std::string_view accept_header, } std::string_view best{}; - float best_quality{0.0f}; + float best_quality{0.0F}; std::uint8_t best_specificity{0}; std::size_t best_order{candidates.size()}; @@ -37,7 +37,7 @@ auto http_match_accept(const std::string_view accept_header, assert(candidate_slash > 0); assert(candidate_slash < candidate_type.size() - 1); assert(candidate_type.find_first_of(" \t,;*") == std::string_view::npos); - float candidate_quality{0.0f}; + float candidate_quality{0.0F}; std::uint8_t candidate_specificity{0}; http_for_each_media_range( accept_header, @@ -55,7 +55,7 @@ auto http_match_accept(const std::string_view accept_header, candidate_specificity = specificity; } }); - if (candidate_quality > 0.0f && + if (candidate_quality > 0.0F && (candidate_quality > best_quality || (candidate_quality == best_quality && candidate_specificity > best_specificity) || diff --git a/vendor/core/src/core/http/match_accept_language.cc b/vendor/core/src/core/http/match_accept_language.cc index 422b97fb..d3050f7e 100644 --- a/vendor/core/src/core/http/match_accept_language.cc +++ b/vendor/core/src/core/http/match_accept_language.cc @@ -43,7 +43,7 @@ auto http_match_accept_language( } std::string_view best{}; - float best_quality{0.0f}; + float best_quality{0.0F}; std::size_t best_specificity{0}; std::size_t best_order{candidates.size()}; @@ -53,7 +53,7 @@ auto http_match_accept_language( assert(candidate.find_first_of(" \t,;*/") == std::string_view::npos); assert(candidate[0] != '-'); assert(candidate[candidate.size() - 1] != '-'); - float candidate_quality{0.0f}; + float candidate_quality{0.0F}; std::size_t candidate_specificity{0}; http_for_each_accept_entry( accept_language_header, @@ -73,7 +73,7 @@ auto http_match_accept_language( candidate_specificity = specificity; } }); - if (candidate_quality > 0.0f && + if (candidate_quality > 0.0F && (candidate_quality > best_quality || (candidate_quality == best_quality && candidate_specificity > best_specificity) || diff --git a/vendor/core/src/core/http/negotiate_encoding.cc b/vendor/core/src/core/http/negotiate_encoding.cc index a0df7252..fa8cfad7 100644 --- a/vendor/core/src/core/http/negotiate_encoding.cc +++ b/vendor/core/src/core/http/negotiate_encoding.cc @@ -2,6 +2,7 @@ #include "helpers.h" +#include // std::max #include // std::optional, std::nullopt #include // std::string_view @@ -16,9 +17,9 @@ auto http_negotiate_encoding( return HTTPContentEncoding::Identity; } - float gzip_quality{0.0f}; - float identity_quality{0.0f}; - float wildcard_quality{0.0f}; + float gzip_quality{0.0F}; + float identity_quality{0.0F}; + float wildcard_quality{0.0F}; bool gzip_listed{false}; bool identity_listed{false}; bool wildcard_listed{false}; @@ -29,19 +30,13 @@ auto http_negotiate_encoding( if (equals_ignore_case(token, "gzip") || equals_ignore_case(token, "x-gzip")) { gzip_listed = true; - if (quality > gzip_quality) { - gzip_quality = quality; - } + gzip_quality = std::max(quality, gzip_quality); } else if (equals_ignore_case(token, "identity")) { identity_listed = true; - if (quality > identity_quality) { - identity_quality = quality; - } + identity_quality = std::max(quality, identity_quality); } else if (token == "*") { wildcard_listed = true; - if (quality > wildcard_quality) { - wildcard_quality = quality; - } + wildcard_quality = std::max(quality, wildcard_quality); } }); @@ -52,12 +47,12 @@ auto http_negotiate_encoding( if (wildcard_listed) { identity_quality = wildcard_quality; } else { - identity_quality = 1.0f; + identity_quality = 1.0F; } } - const bool gzip_acceptable{gzip_quality > 0.0f}; - const bool identity_acceptable{identity_quality > 0.0f}; + const bool gzip_acceptable{gzip_quality > 0.0F}; + const bool identity_acceptable{identity_quality > 0.0F}; if (!gzip_acceptable && !identity_acceptable) { return std::nullopt; diff --git a/vendor/core/src/core/http/parse_bearer.cc b/vendor/core/src/core/http/parse_bearer.cc index 3c676112..11168463 100644 --- a/vendor/core/src/core/http/parse_bearer.cc +++ b/vendor/core/src/core/http/parse_bearer.cc @@ -9,20 +9,20 @@ namespace sourcemeta::core { auto http_parse_bearer(const std::string_view authorization) noexcept -> std::string_view { - constexpr std::string_view scheme{"bearer"}; - if (authorization.size() <= scheme.size() || - authorization[scheme.size()] != ' ') { + constexpr std::string_view SCHEME{"bearer"}; + if (authorization.size() <= SCHEME.size() || + authorization[SCHEME.size()] != ' ') { return {}; } - if (!equals_ignore_case(http_subview(authorization, 0, scheme.size()), - scheme)) { + if (!equals_ignore_case(http_subview(authorization, 0, SCHEME.size()), + SCHEME)) { return {}; } const auto token{http_trim_trailing_ows(http_trim_leading_ows( - http_subview(authorization, scheme.size() + 1, - authorization.size() - scheme.size() - 1)))}; + http_subview(authorization, SCHEME.size() + 1, + authorization.size() - SCHEME.size() - 1)))}; if (!http_is_b64token(token)) { return {}; } diff --git a/vendor/core/src/core/http/parse_challenges.cc b/vendor/core/src/core/http/parse_challenges.cc new file mode 100644 index 00000000..d27cf9ba --- /dev/null +++ b/vendor/core/src/core/http/parse_challenges.cc @@ -0,0 +1,214 @@ +#include + +#include "helpers.h" + +#include // std::size_t +#include // std::optional, std::nullopt +#include // std::string +#include // std::string_view +#include // std::move +#include // std::vector + +namespace { + +auto skip_whitespace(const std::string_view input, std::size_t position) + -> std::size_t { + while (position < input.size() && + sourcemeta::core::http_is_ows(input[position])) { + position += 1; + } + + return position; +} + +auto scan_token(const std::string_view input, std::size_t position) + -> std::size_t { + while (position < input.size() && + sourcemeta::core::http_is_tchar(input[position])) { + position += 1; + } + + return position; +} + +// RFC 9110 §11.2: token68 = 1*( ALPHA / DIGIT / "-" / "." / "_" / "~" / "+" / +// "/" ) *"=". The trailing padding is what makes a credential hard to tell +// from a parameter, since both can put an equals sign after a run of token +// characters, so the padding is taken greedily and the decision rests on what +// follows it +auto scan_token68(const std::string_view input, std::size_t position) + -> std::size_t { + const auto start{position}; + while (position < input.size() && + sourcemeta::core::http_is_b64token_char(input[position])) { + position += 1; + } + + if (position == start) { + return start; + } + + while (position < input.size() && input[position] == '=') { + position += 1; + } + + return position; +} + +// A credential stands alone, so nothing but the end of the field or the +// separator before the next challenge may follow it. A parameter instead has +// its value there, which is what tells the two apart +auto ends_an_element(const std::string_view input, std::size_t position) + -> bool { + const auto next{skip_whitespace(input, position)}; + return next >= input.size() || input[next] == ','; +} + +} // namespace + +namespace sourcemeta::core { + +auto http_parse_challenges(const std::string_view input, + std::vector &challenges) + -> bool { + challenges.clear(); + std::size_t position{0}; + + while (true) { + // RFC 9110 §5.6.1.2: "A recipient MUST parse and ignore a reasonable + // number of empty list elements" + position = skip_whitespace(input, position); + while (position < input.size() && input[position] == ',') { + position = skip_whitespace(input, position + 1); + } + + if (position >= input.size()) { + break; + } + + // RFC 9110 §11.1: auth-scheme = token + const auto scheme_start{position}; + const auto scheme_end{scan_token(input, scheme_start)}; + if (scheme_end == scheme_start) { + challenges.clear(); + return false; + } + + HTTPParsedChallenge challenge; + challenge.scheme = + std::string{input.substr(scheme_start, scheme_end - scheme_start)}; + position = scheme_end; + + // RFC 9110 §11.3: the scheme is followed by at least one space before + // either alternative, so without one the challenge carries neither + const auto after_scheme{skip_whitespace(input, position)}; + if (after_scheme == position || after_scheme >= input.size() || + input[after_scheme] == ',') { + challenges.push_back(std::move(challenge)); + position = after_scheme; + continue; + } + + position = after_scheme; + const auto credential_end{scan_token68(input, position)}; + if (credential_end != position && ends_an_element(input, credential_end)) { + challenge.token68 = + std::string{input.substr(position, credential_end - position)}; + challenges.push_back(std::move(challenge)); + position = credential_end; + continue; + } + + // RFC 9110 §11.2: auth-param = token BWS "=" BWS ( token / quoted-string ) + bool first_parameter{true}; + while (true) { + const auto name_start{position}; + const auto name_end{scan_token(input, name_start)}; + if (name_end == name_start) { + challenges.clear(); + return false; + } + + const auto after_name{skip_whitespace(input, name_end)}; + if (after_name >= input.size() || input[after_name] != '=') { + // A token that no equals sign follows opens the next challenge rather + // than continuing this one, so it is left for the outer loop to read. + // RFC 9110 §11.6.1 delimits that list with a comma, so one may only + // begin where a separator has just been passed + if (first_parameter) { + challenges.clear(); + return false; + } + + position = name_start; + break; + } + + first_parameter = false; + + const auto value_start{skip_whitespace(input, after_name + 1)}; + if (value_start >= input.size()) { + challenges.clear(); + return false; + } + + // The unescaping, and the refusal of any control character a + // quoted-string does not admit, is the module's own + std::string storage; + std::string_view scanned; + std::string value; + if (input[value_start] == '"') { + const auto value_end{ + http_scan_quoted_string(input, value_start, storage, scanned)}; + if (!value_end.has_value()) { + challenges.clear(); + return false; + } + + value = std::string{scanned}; + position = value_end.value(); + } else { + const auto value_end{scan_token(input, value_start)}; + if (value_end == value_start) { + challenges.clear(); + return false; + } + + value = std::string{input.substr(value_start, value_end - value_start)}; + position = value_end; + } + + challenge.parameters.emplace_back( + std::string{input.substr(name_start, name_end - name_start)}, + std::move(value)); + + position = skip_whitespace(input, position); + if (position >= input.size()) { + break; + } + + // RFC 9110 §11.6.1: the field is a comma-delimited list, so nothing but + // a separator may follow a parameter + if (input[position] != ',') { + challenges.clear(); + return false; + } + + // The separator between two parameters is the same one that separates + // two challenges, so §5.6.1.2 has empty elements ignored here too + while (position < input.size() && input[position] == ',') { + position = skip_whitespace(input, position + 1); + } + + if (position >= input.size()) { + break; + } + } + + challenges.push_back(std::move(challenge)); + } + + return !challenges.empty(); +} + +} // namespace sourcemeta::core diff --git a/vendor/core/src/core/http/serialize_cache_control.cc b/vendor/core/src/core/http/serialize_cache_control.cc new file mode 100644 index 00000000..65687ff3 --- /dev/null +++ b/vendor/core/src/core/http/serialize_cache_control.cc @@ -0,0 +1,194 @@ +#include +#include + +#include // std::ranges::all_of +#include // std::chrono::seconds +#include // std::size_t +#include // std::optional, std::nullopt +#include // std::span +#include // std::string +#include // std::string_view +#include // std::unreachable + +namespace { + +constexpr std::string_view DIRECTIVE_SEPARATOR{", "}; + +auto append_directive(std::string &out, const std::string_view name, + bool &first) -> void { + if (!first) { + out.append(DIRECTIVE_SEPARATOR); + } + + out.append(name); + first = false; +} + +// RFC 9111 §5.2.2.1: "This directive uses the token form of the argument +// syntax: e.g., 'max-age=5' not 'max-age="5"'. A sender MUST NOT generate the +// quoted-string form" +auto append_seconds(std::string &out, const std::string_view name, + const std::chrono::seconds value, bool &first) -> void { + append_directive(out, name, first); + out.push_back('='); + sourcemeta::core::DigitsBuffer buffer; + out.append(sourcemeta::core::digits_view(value.count(), buffer)); +} + +// RFC 9111 §5.2.2.4 and §5.2.2.7: a qualified directive "uses the quoted-string +// form of the argument syntax. A sender SHOULD NOT generate the token form +// (even if quoting appears not to be needed for single-entry lists)" +auto append_fields(std::string &out, const std::string_view name, + const std::span fields, bool &first) + -> void { + append_directive(out, name, first); + out.append("=\""); + bool leading{true}; + for (const auto field : fields) { + if (!leading) { + out.append(DIRECTIVE_SEPARATOR); + } + + out.append(field); + leading = false; + } + + out.push_back('"'); +} + +// RFC 9111 §5.2.2.7 and §5.2.2.9 name the two, which the shared entry point +// has already established the value to be one of +auto visibility_directive( + const sourcemeta::core::HTTPCacheVisibility value) noexcept + -> std::string_view { + switch (value) { + case sourcemeta::core::HTTPCacheVisibility::Public: + return "public"; + case sourcemeta::core::HTTPCacheVisibility::Private: + return "private"; + } + + std::unreachable(); +} + +// RFC 9110 §5.1: field-name = token, so a name outside that set could not be +// carried even by the quoting +auto fields_valid(const std::span fields) -> bool { + return std::ranges::all_of(fields, [](const std::string_view field) -> bool { + return sourcemeta::core::http_is_token(field); + }); +} + +} // namespace + +namespace sourcemeta::core { + +auto http_cache_control_valid(const HTTPCacheControl &directives) -> bool { + // A visibility naming neither of the two directives names no directive at + // all, so it cannot stand in for one below + if (directives.visibility.has_value() && + directives.visibility != HTTPCacheVisibility::Public && + directives.visibility != HTTPCacheVisibility::Private) { + return false; + } + + // RFC 9111 §1.2.2: "The delta-seconds rule specifies a non-negative integer" + if ((directives.max_age.has_value() && directives.max_age->count() < 0) || + (directives.shared_max_age.has_value() && + directives.shared_max_age->count() < 0)) { + return false; + } + + // RFC 9111 §5.2.2.7 qualifies the private directive, so a field list without + // it limits nothing and names no directive to qualify + if (!directives.private_fields.empty() && + directives.visibility != HTTPCacheVisibility::Private) { + return false; + } + + if (!fields_valid(directives.private_fields) || + !fields_valid(directives.no_cache_fields)) { + return false; + } + + // RFC 9111 §5.2: Cache-Control = #cache-directive, and RFC 9110 §5.6.1.1 + // forbids a sender from generating an empty list element, so a header naming + // no directive at all is left unsent rather than emitted blank + return directives.visibility.has_value() || directives.max_age.has_value() || + directives.shared_max_age.has_value() || directives.no_store || + directives.no_cache || !directives.no_cache_fields.empty() || + directives.must_revalidate || directives.proxy_revalidate || + directives.must_understand || directives.no_transform || + directives.immutable; +} + +auto http_serialize_cache_control(const HTTPCacheControl &directives, + std::string &out) -> bool { + if (!http_cache_control_valid(directives)) { + return false; + } + + bool first{true}; + if (directives.visibility.has_value()) { + const auto directive{visibility_directive(directives.visibility.value())}; + if (directives.private_fields.empty()) { + append_directive(out, directive, first); + } else { + append_fields(out, directive, directives.private_fields, first); + } + } + + if (directives.no_store) { + append_directive(out, "no-store", first); + } + + if (directives.max_age.has_value()) { + append_seconds(out, "max-age", directives.max_age.value(), first); + } + + if (directives.shared_max_age.has_value()) { + append_seconds(out, "s-maxage", directives.shared_max_age.value(), first); + } + + // A field list is the qualified form of the very same directive, so only one + // of the two spellings is ever emitted + if (!directives.no_cache_fields.empty()) { + append_fields(out, "no-cache", directives.no_cache_fields, first); + } else if (directives.no_cache) { + append_directive(out, "no-cache", first); + } + + if (directives.must_revalidate) { + append_directive(out, "must-revalidate", first); + } + + if (directives.proxy_revalidate) { + append_directive(out, "proxy-revalidate", first); + } + + if (directives.must_understand) { + append_directive(out, "must-understand", first); + } + + if (directives.no_transform) { + append_directive(out, "no-transform", first); + } + + if (directives.immutable) { + append_directive(out, "immutable", first); + } + + return true; +} + +auto http_serialize_cache_control(const HTTPCacheControl &directives) + -> std::optional { + std::string out; + if (!http_serialize_cache_control(directives, out)) { + return std::nullopt; + } + + return out; +} + +} // namespace sourcemeta::core diff --git a/vendor/core/src/core/http/serialize_challenge.cc b/vendor/core/src/core/http/serialize_challenge.cc new file mode 100644 index 00000000..6f26c39f --- /dev/null +++ b/vendor/core/src/core/http/serialize_challenge.cc @@ -0,0 +1,241 @@ +#include +#include +#include + +#include // std::ranges::all_of +#include // std::size_t +#include // std::optional, std::nullopt +#include // std::span +#include // std::string +#include // std::string_view + +namespace { + +constexpr std::string_view CHALLENGE_SCHEME_SEPARATOR{" "}; +constexpr std::string_view CHALLENGE_PARAMETER_SEPARATOR{", "}; +constexpr std::string_view CHALLENGE_PARAMETER_EQUALS{"="}; + +// RFC 6749 Appendix A: NQCHAR = %x21 / %x23-5B / %x5D-7E +auto is_nqchar(const char character) noexcept -> bool { + const auto value{static_cast(character)}; + return value == 0x21 || (value >= 0x23 && value <= 0x5B) || + (value >= 0x5D && value <= 0x7E); +} + +// RFC 6749 Appendix A: NQSCHAR = %x20-21 / %x23-5B / %x5D-7E +auto is_nqschar(const char character) noexcept -> bool { + const auto value{static_cast(character)}; + return (value >= 0x20 && value <= 0x21) || (value >= 0x23 && value <= 0x5B) || + (value >= 0x5D && value <= 0x7E); +} + +// RFC 6749 Appendix A.4: scope = scope-token *( SP scope-token ), where +// scope-token = 1*NQCHAR. The space delimits two values rather than being +// content, so it can neither open nor close the list nor stand doubled, which +// is what RFC 6750 §3 means by admitting it only "for delimiters between +// scope values" +auto is_scope(const std::string_view value) noexcept -> bool { + if (value.empty() || value.front() == ' ' || value.back() == ' ') { + return false; + } + + bool previous_was_space{false}; + for (const auto character : value) { + if (character == ' ') { + if (previous_was_space) { + return false; + } + + previous_was_space = true; + continue; + } + + if (!is_nqchar(character)) { + return false; + } + + previous_was_space = false; + } + + return true; +} + +// The parameters RFC 6750 §3 constrains beyond the RFC 9110 grammar, which +// only bind when the challenge carries the scheme that specification defines +auto bearer_parameter_valid(const std::string_view name, + const std::string_view value) -> bool { + if (sourcemeta::core::equals_ignore_case(name, "scope")) { + return is_scope(value); + } + + // RFC 6749 Appendix A.7 and A.8: error = 1*NQSCHAR and + // error-description = 1*NQSCHAR, so neither may be empty + if (sourcemeta::core::equals_ignore_case(name, "error") || + sourcemeta::core::equals_ignore_case(name, "error_description")) { + return !value.empty() && std::ranges::all_of(value, is_nqschar); + } + + // RFC 6749 Appendix A.9: error-uri = URI-reference, which RFC 6750 §3 notes + // "thus MUST NOT include characters outside the set %x21 / %x23-5B / + // %x5D-7E", so the grammar is checked rather than only its consequence. + // Alone among the four it carries no repetition bound, and RFC 3986 §4.1 + // admits an empty reference through a relative one whose path is empty, so + // this value may be empty where the others may not + if (sourcemeta::core::equals_ignore_case(name, "error_uri")) { + return std::ranges::all_of(value, is_nqchar) && + sourcemeta::core::URI::is_uri_reference(value); + } + + return true; +} + +auto required_size(const sourcemeta::core::HTTPChallenge &challenge) noexcept + -> std::size_t { + std::size_t size{challenge.scheme.size()}; + if (challenge.token68.has_value()) { + return size + CHALLENGE_SCHEME_SEPARATOR.size() + challenge.token68->size(); + } + + for (const auto &[name, value] : challenge.parameters) { + // Two quotes, and room for escaping every octet of the value + size += CHALLENGE_PARAMETER_SEPARATOR.size() + name.size() + + CHALLENGE_PARAMETER_EQUALS.size() + (value.size() * 2) + 2; + } + + return size; +} + +} // namespace + +namespace sourcemeta::core { + +auto http_challenge_valid(const HTTPChallenge &challenge) -> bool { + // RFC 9110 §11.1: auth-scheme = token + if (!http_is_token(challenge.scheme)) { + return false; + } + + // RFC 9110 §11.3: challenge = auth-scheme [ 1*SP ( token68 / #auth-param ) ], + // so the two alternatives are exclusive + if (challenge.token68.has_value()) { + if (!challenge.parameters.empty() || + !http_is_b64token(challenge.token68.value())) { + return false; + } + } + + // RFC 6750 §3: "All challenges defined by this specification MUST use the + // auth-scheme value "Bearer". This scheme MUST be followed by one or more + // auth-param values", which rules out both a bare scheme and a credential + const auto is_bearer{equals_ignore_case(challenge.scheme, "Bearer")}; + if (is_bearer && challenge.parameters.empty()) { + return false; + } + + for (std::size_t index{0}; index < challenge.parameters.size(); index += 1) { + const auto &[name, value] = challenge.parameters[index]; + // RFC 9110 §11.2: auth-param = token BWS "=" BWS ( token / quoted-string ) + if (!http_is_token(name)) { + return false; + } + + // RFC 9110 §11.2: "each parameter name MUST only occur once per + // challenge", where the name "is matched case-insensitively" + for (std::size_t other{0}; other < index; other += 1) { + if (equals_ignore_case(challenge.parameters[other].first, name)) { + return false; + } + } + + if (is_bearer && !bearer_parameter_valid(name, value)) { + return false; + } + + // RFC 9110 §11.5 requires a sender to spell a realm as a quoted-string, + // and every value is spelled that way, so each one has to be encodable + std::string scratch; + if (!http_encode_quoted_string(value, scratch)) { + return false; + } + } + + return true; +} + +auto http_serialize_challenge(const HTTPChallenge &challenge, std::string &out) + -> bool { + if (!http_challenge_valid(challenge)) { + return false; + } + + out.reserve(out.size() + required_size(challenge)); + out.append(challenge.scheme); + + if (challenge.token68.has_value()) { + out.append(CHALLENGE_SCHEME_SEPARATOR); + out.append(challenge.token68.value()); + return true; + } + + // RFC 9110 §11.2 admits a token as well, but the quoted-string is the only + // spelling §11.5 lets a sender use for a realm, so every value takes it and + // no caller has to weigh which one a given value needs + bool first{true}; + for (const auto &[name, value] : challenge.parameters) { + out.append(first ? CHALLENGE_SCHEME_SEPARATOR + : CHALLENGE_PARAMETER_SEPARATOR); + out.append(name); + // RFC 9110 §5.6.3: a sender must not generate the bad whitespace the + // grammar tolerates around the equals sign + out.append(CHALLENGE_PARAMETER_EQUALS); + http_encode_quoted_string(value, out); + first = false; + } + + return true; +} + +auto http_serialize_challenge(const HTTPChallenge &challenge) + -> std::optional { + std::string out; + if (!http_serialize_challenge(challenge, out)) { + return std::nullopt; + } + + return out; +} + +auto http_serialize_challenges(std::span challenges, + std::string &out) -> bool { + // RFC 9110 §11.6.1: a server answering with a 401 "MUST send a + // WWW-Authenticate header field containing at least one challenge" + if (challenges.empty()) { + return false; + } + + for (const auto &challenge : challenges) { + if (!http_challenge_valid(challenge)) { + return false; + } + } + + http_serialize_challenge(challenges.front(), out); + for (const auto &challenge : challenges.subspan(1)) { + out.append(CHALLENGE_PARAMETER_SEPARATOR); + http_serialize_challenge(challenge, out); + } + + return true; +} + +auto http_serialize_challenges(std::span challenges) + -> std::optional { + std::string out; + if (!http_serialize_challenges(challenges, out)) { + return std::nullopt; + } + + return out; +} + +} // namespace sourcemeta::core diff --git a/vendor/core/src/core/http/serialize_cookie.cc b/vendor/core/src/core/http/serialize_cookie.cc index e2d5947f..e0d3e06c 100644 --- a/vendor/core/src/core/http/serialize_cookie.cc +++ b/vendor/core/src/core/http/serialize_cookie.cc @@ -117,13 +117,37 @@ auto required_size(const sourcemeta::core::HTTPCookie &cookie, namespace sourcemeta::core { +auto http_expire_cookie(const HTTPCookie &cookie) -> HTTPCookie { + HTTPCookie result{cookie}; + result.value = {}; + result.max_age = std::chrono::seconds{0}; + return result; +} + auto http_cookie_valid(const HTTPCookie &cookie) -> bool { + // Every length ceiling is weighed before the scans below, so an oversized + // input is refused without a pass over it + + // RFC 6265bis §5.7 ignores a cookie whose name and value are together longer + // than the ceiling, so one that large can never be stored + if (cookie.name.size() + cookie.value.size() > + HTTP_COOKIE_MAXIMUM_NAME_VALUE_LENGTH) { + return false; + } + if (!sourcemeta::core::http_is_token(cookie.name) || !is_cookie_value(cookie.value)) { return false; } - if (cookie.path.has_value() && !is_attribute_value(*cookie.path)) { + // RFC 6265bis §5.6 ignores an over-long attribute value while keeping the + // rest of the cookie, which would silently change the scope the server asked + // for. The path is the only attribute a caller can make that long, since a + // host name is already bounded by RFC 1123 §2.1 and a lifetime by the digits + // of a second count + if (cookie.path.has_value() && + (cookie.path->size() > HTTP_COOKIE_MAXIMUM_ATTRIBUTE_VALUE_LENGTH || + !is_attribute_value(*cookie.path))) { return false; } diff --git a/vendor/core/src/core/idna/codegen.cc b/vendor/core/src/core/idna/codegen.cc index 4b35aa8f..108310f3 100644 --- a/vendor/core/src/core/idna/codegen.cc +++ b/vendor/core/src/core/idna/codegen.cc @@ -76,7 +76,8 @@ auto parse_entry(const std::string_view payload) -> PropertyEntry { const auto last{range_split.has_value() ? parse_hex_codepoint(range_split->second) : first}; - return {first, last, property_from_token(value_part)}; + return { + .first = first, .last = last, .value = property_from_token(value_part)}; } auto parse_idna_file(const std::filesystem::path &input_path) @@ -84,7 +85,7 @@ auto parse_idna_file(const std::filesystem::path &input_path) auto stream{sourcemeta::core::read_file(input_path)}; std::vector missing; std::vector data; - constexpr std::string_view missing_prefix{"@missing:"}; + constexpr std::string_view MISSING_PREFIX{"@missing:"}; sourcemeta::core::for_each_line(stream, [&](const std::string_view raw_line) { const auto line{sourcemeta::core::trim(raw_line)}; if (line.empty()) { @@ -92,12 +93,12 @@ auto parse_idna_file(const std::filesystem::path &input_path) } if (line.front() == '#') { const auto comment_body{sourcemeta::core::trim(line.substr(1))}; - if (comment_body.size() < missing_prefix.size() || - comment_body.substr(0, missing_prefix.size()) != missing_prefix) { + if (comment_body.size() < MISSING_PREFIX.size() || + !comment_body.starts_with(MISSING_PREFIX)) { return; } missing.push_back( - parse_entry(comment_body.substr(missing_prefix.size()))); + parse_entry(comment_body.substr(MISSING_PREFIX.size()))); return; } data.push_back(parse_entry(line)); @@ -154,10 +155,10 @@ auto build_pages(const std::vector &entries) -> TwoStageTable { template auto emit_row(std::ostream &stream, const std::span items) -> void { - constexpr std::size_t row_width{16}; - for (std::size_t offset{0}; offset < items.size(); offset += row_width) { + constexpr std::size_t ROW_WIDTH{16}; + for (std::size_t offset{0}; offset < items.size(); offset += ROW_WIDTH) { stream << " "; - const auto upper{offset + row_width < items.size() ? offset + row_width + const auto upper{offset + ROW_WIDTH < items.size() ? offset + ROW_WIDTH : items.size()}; const auto row{items.subspan(offset, upper - offset)}; const auto widened{row | std::views::transform([](const T value) { @@ -256,7 +257,10 @@ auto parse_mapping_line(const std::string_view payload) -> MappingEntry { mapping = parse_mapping_sequence(mapping_part); } - return {first, last, status, std::move(mapping)}; + return {.first = first, + .last = last, + .status = status, + .mapping = std::move(mapping)}; } auto parse_mapping_file(const std::filesystem::path &input_path) @@ -298,16 +302,17 @@ auto build_mapping(const std::vector &entries) -> MappingTable { status_values[codepoint] = static_cast(entry.status); if (entry.status == sourcemeta::core::IDNAMappingStatus::Mapped) { table.index.push_back( - {codepoint, offset, - static_cast(entry.mapping.size())}); + {.codepoint = codepoint, + .offset = offset, + .length = static_cast(entry.mapping.size())}); } } } - std::sort(table.index.begin(), table.index.end(), - [](const MappingIndexEntry &left, const MappingIndexEntry &right) { - return left.codepoint < right.codepoint; - }); + std::ranges::sort(table.index, [](const MappingIndexEntry &left, + const MappingIndexEntry &right) { + return left.codepoint < right.codepoint; + }); table.status = compress_pages(status_values); return table; } diff --git a/vendor/core/src/core/idna/idna.cc b/vendor/core/src/core/idna/idna.cc index 9a3989dc..5bd579ed 100644 --- a/vendor/core/src/core/idna/idna.cc +++ b/vendor/core/src/core/idna/idna.cc @@ -418,8 +418,8 @@ auto idna_passes_bidi_rule(const std::u32string_view label) noexcept -> bool { } auto idna_is_valid_a_label(const std::string_view label) -> bool { - constexpr std::string_view prefix{"xn--"}; - if (!label.starts_with(prefix)) { + constexpr std::string_view PREFIX{"xn--"}; + if (!label.starts_with(PREFIX)) { return false; } @@ -437,8 +437,8 @@ auto idna_is_valid_a_label(const std::string_view label) -> bool { // The substring after the prefix. Constructing the view via (data, size) // avoids `std::string_view::substr`, which is not noexcept. - const std::string_view encoded{label.data() + prefix.size(), - label.size() - prefix.size()}; + const std::string_view encoded{label.data() + PREFIX.size(), + label.size() - PREFIX.size()}; std::u32string decoded; return validate_a_label_body(encoded, decoded); diff --git a/vendor/core/src/core/ip/ipv4.cc b/vendor/core/src/core/ip/ipv4.cc index c0e9cf2c..22b773b5 100644 --- a/vendor/core/src/core/ip/ipv4.cc +++ b/vendor/core/src/core/ip/ipv4.cc @@ -27,7 +27,7 @@ auto is_ipv4(const std::string_view address) -> bool { const auto octet_start{position}; unsigned int value{0}; while (position < address.size() && is_digit(address[position])) { - value = value * 10 + static_cast(address[position] - '0'); + value = (value * 10) + static_cast(address[position] - '0'); position += 1; } diff --git a/vendor/core/src/core/ip/ipv6.cc b/vendor/core/src/core/ip/ipv6.cc index 880cf5cf..d1606480 100644 --- a/vendor/core/src/core/ip/ipv6.cc +++ b/vendor/core/src/core/ip/ipv6.cc @@ -118,7 +118,7 @@ auto fill_ipv6_run(const std::string_view run, const auto piece{run.substr(position, colon == std::string_view::npos ? std::string_view::npos : colon - position)}; - if (piece.find('.') != std::string_view::npos) { + if (piece.contains('.')) { for (const auto octet : ipv4_octets(piece)) { output[length] = octet; length += 1; diff --git a/vendor/core/src/core/jose/CMakeLists.txt b/vendor/core/src/core/jose/CMakeLists.txt index db73e168..cd36c872 100644 --- a/vendor/core/src/core/jose/CMakeLists.txt +++ b/vendor/core/src/core/jose/CMakeLists.txt @@ -1,6 +1,6 @@ sourcemeta_library(NAMESPACE sourcemeta PROJECT core NAME jose PRIVATE_HEADERS algorithm.h error.h jwk.h jwk_private.h jwks.h jwks_provider.h - jwt.h sign.h verify.h jwe.h encrypt.h decrypt.h + jwt.h sign.h verify.h jwe.h encrypt.h decrypt.h compact.h SOURCES jose_algorithm.cc jose_jwk.cc jose_jwk_private.cc jose_jwks.cc jose_jwks_provider.cc jose_jwk_thumbprint.cc jose_jwt.cc jose_jwt_check_claims.cc jose_jws_verify_signature.cc diff --git a/vendor/core/src/core/jose/include/sourcemeta/core/jose.h b/vendor/core/src/core/jose/include/sourcemeta/core/jose.h index 3240e62c..319ce16b 100644 --- a/vendor/core/src/core/jose/include/sourcemeta/core/jose.h +++ b/vendor/core/src/core/jose/include/sourcemeta/core/jose.h @@ -7,6 +7,7 @@ // NOLINTBEGIN(misc-include-cleaner) #include +#include #include #include #include diff --git a/vendor/core/src/core/jose/include/sourcemeta/core/jose_compact.h b/vendor/core/src/core/jose/include/sourcemeta/core/jose_compact.h new file mode 100644 index 00000000..6b7a0d35 --- /dev/null +++ b/vendor/core/src/core/jose/include/sourcemeta/core/jose_compact.h @@ -0,0 +1,64 @@ +#ifndef SOURCEMETA_CORE_JOSE_COMPACT_H_ +#define SOURCEMETA_CORE_JOSE_COMPACT_H_ + +#include // std::array +#include // std::size_t +#include // std::optional, std::nullopt +#include // std::string_view + +namespace sourcemeta::core { + +/// @ingroup jose +/// Split a compact serialization into its dot-separated segments, with no +/// result unless it carries exactly as many as were asked for. Both the JWS +/// (RFC 7515 Section 7.1) and the JWE (RFC 7516 Section 7.1) compact +/// serializations are a fixed count of base64url encoded parts joined by dots, +/// and base64url never spells a dot, so the split is unambiguous and a +/// trailing separator is a malformed input rather than an extra empty part. +/// +/// A segment is allowed to be empty, since detached content leaves the payload +/// blank (RFC 7515 Appendix F) and direct encryption leaves the encrypted key +/// blank (RFC 7516 Section 5.1). The segments are returned as written, so +/// decoding and validating them is left to the caller, and they borrow from +/// the input, which must outlive them. For example: +/// +/// ```cpp +/// #include +/// +/// #include +/// +/// const auto segments{sourcemeta::core::jose_compact_segments<3>( +/// "eyJhbGciOiJSUzI1NiJ9.eyJpc3MiOiJhY21lIn0.c2ln")}; +/// assert(segments.has_value()); +/// assert(segments.value().at(0) == "eyJhbGciOiJSUzI1NiJ9"); +/// ``` +template +auto jose_compact_segments(const std::string_view input) noexcept + -> std::optional> { + static_assert(Count > 0, "A compact serialization carries at least one part"); + std::array segments{}; + + std::string_view rest{input}; + for (std::size_t index{0}; index + 1 < Count; index += 1) { + const auto separator{rest.find('.')}; + if (separator == std::string_view::npos) { + return std::nullopt; + } + + std::string_view segment{rest}; + segment.remove_suffix(rest.size() - separator); + segments[index] = segment; + rest.remove_prefix(separator + 1); + } + + if (rest.find('.') != std::string_view::npos) { + return std::nullopt; + } + + segments[Count - 1] = rest; + return segments; +} + +} // namespace sourcemeta::core + +#endif diff --git a/vendor/core/src/core/jose/include/sourcemeta/core/jose_verify.h b/vendor/core/src/core/jose/include/sourcemeta/core/jose_verify.h index 132b8738..41856d66 100644 --- a/vendor/core/src/core/jose/include/sourcemeta/core/jose_verify.h +++ b/vendor/core/src/core/jose/include/sourcemeta/core/jose_verify.h @@ -35,7 +35,10 @@ enum class JWTClaimError : std::uint8_t { /// The not-before time claim is malformed or lies in the future. NotBefore, /// The issued-at time claim is malformed or lies in the future. - IssuedAt + IssuedAt, + /// The token claims to live longer than the caller admits, names no + /// interval at all, or leaves out the issuance time a bound needs. + Lifetime }; /// @ingroup jose @@ -99,9 +102,9 @@ inline auto jwt_bounded_clock_skew(const std::chrono::seconds skew) noexcept -> std::chrono::seconds { // A mean Gregorian year, the widest grace period any deployment plausibly // needs, so an extreme value cannot widen the acceptance window without bound - constexpr std::chrono::seconds maximum{31556952}; + constexpr std::chrono::seconds MAXIMUM{31556952}; return skew < std::chrono::seconds::zero() ? std::chrono::seconds::zero() - : skew > maximum ? maximum + : skew > MAXIMUM ? MAXIMUM : skew; } @@ -110,7 +113,16 @@ inline auto jwt_bounded_clock_skew(const std::chrono::seconds skew) noexcept /// issuer and audience at a given time, returning the first failing check or no /// value when every check passes. The expiration claim is required (RFC 9068 /// Section 2.2), and the subject is checked only when an expected value is -/// supplied. For example: +/// supplied. +/// +/// Supplying a maximum lifetime additionally bounds the interval the token +/// claims between its issuance and its expiration, so one claiming to outlive +/// anything the caller mints is refused whatever signature it carries. That +/// bound makes the issuance time required, since a token leaving it out could +/// otherwise escape the bound by omission, and it relates two claims to each +/// other rather than to the server clock, so no tolerance applies to it. A +/// negative bound refuses every token, and a bound of zero admits only one +/// whose expiration is the very instant it was issued. For example: /// /// ```cpp /// #include @@ -129,12 +141,14 @@ inline auto jwt_bounded_clock_skew(const std::chrono::seconds skew) noexcept /// assert(!error.has_value()); /// ``` SOURCEMETA_CORE_JOSE_EXPORT -auto jwt_check_claims(const JWT &token, const std::string_view expected_issuer, - const std::string_view expected_audience, - const std::chrono::system_clock::time_point now, - const JWTClockSkew clock_skew = {}, - const std::optional expected_subject = - std::nullopt) -> std::optional; +auto jwt_check_claims( + const JWT &token, const std::string_view expected_issuer, + const std::string_view expected_audience, + const std::chrono::system_clock::time_point now, + const JWTClockSkew clock_skew = {}, + const std::optional expected_subject = std::nullopt, + const std::optional maximum_lifetime = std::nullopt) + -> std::optional; /// @ingroup jose /// Verify a JSON Web Signature given its algorithm, its signing input, and its @@ -211,7 +225,10 @@ enum class JWTVerificationError : std::uint8_t { /// The not-before time claim is malformed or lies in the future. NotBefore, /// The issued-at time claim is malformed or lies in the future. - IssuedAt + IssuedAt, + /// The token claims to live longer than the caller admits, names no + /// interval at all, or leaves out the issuance time a bound needs. + Lifetime }; /// @ingroup jose @@ -253,8 +270,9 @@ auto jwt_verify(const JWT &token, const JWKS &keys, const std::chrono::system_clock::time_point now, const JWTClockSkew clock_skew, const std::optional expected_subject, - const std::optional expected_type) - -> std::optional; + const std::optional expected_type, + const std::optional maximum_lifetime = + std::nullopt) -> std::optional; } // namespace sourcemeta::core diff --git a/vendor/core/src/core/jose/jose_algorithm.cc b/vendor/core/src/core/jose/jose_algorithm.cc index 4f64dc45..79e42b0e 100644 --- a/vendor/core/src/core/jose/jose_algorithm.cc +++ b/vendor/core/src/core/jose/jose_algorithm.cc @@ -12,33 +12,44 @@ auto to_jws_algorithm(const std::string_view value) noexcept -> std::optional { if (value == "RS256") { return JWSAlgorithm::RS256; - } else if (value == "RS384") { + } + if (value == "RS384") { return JWSAlgorithm::RS384; - } else if (value == "RS512") { + } + if (value == "RS512") { return JWSAlgorithm::RS512; - } else if (value == "PS256") { + } + if (value == "PS256") { return JWSAlgorithm::PS256; - } else if (value == "PS384") { + } + if (value == "PS384") { return JWSAlgorithm::PS384; - } else if (value == "PS512") { + } + if (value == "PS512") { return JWSAlgorithm::PS512; - } else if (value == "ES256") { + } + if (value == "ES256") { return JWSAlgorithm::ES256; - } else if (value == "ES384") { + } + if (value == "ES384") { return JWSAlgorithm::ES384; - } else if (value == "ES512") { + } + if (value == "ES512") { return JWSAlgorithm::ES512; - } else if (value == "EdDSA") { + } + if (value == "EdDSA") { return JWSAlgorithm::EdDSA; - } else if (value == "HS256") { + } + if (value == "HS256") { return JWSAlgorithm::HS256; - } else if (value == "HS384") { + } + if (value == "HS384") { return JWSAlgorithm::HS384; - } else if (value == "HS512") { + } + if (value == "HS512") { return JWSAlgorithm::HS512; - } else { - return std::nullopt; } + return std::nullopt; } auto jws_algorithm_name(const JWSAlgorithm algorithm) noexcept @@ -126,27 +137,35 @@ auto to_jwe_algorithm(const std::string_view value) noexcept -> std::optional { if (value == "RSA-OAEP") { return JWEAlgorithm::RSA_OAEP; - } else if (value == "RSA-OAEP-256") { + } + if (value == "RSA-OAEP-256") { return JWEAlgorithm::RSA_OAEP_256; - } else if (value == "ECDH-ES") { + } + if (value == "ECDH-ES") { return JWEAlgorithm::ECDH_ES; - } else if (value == "ECDH-ES+A128KW") { + } + if (value == "ECDH-ES+A128KW") { return JWEAlgorithm::ECDH_ES_A128KW; - } else if (value == "ECDH-ES+A192KW") { + } + if (value == "ECDH-ES+A192KW") { return JWEAlgorithm::ECDH_ES_A192KW; - } else if (value == "ECDH-ES+A256KW") { + } + if (value == "ECDH-ES+A256KW") { return JWEAlgorithm::ECDH_ES_A256KW; - } else if (value == "A128KW") { + } + if (value == "A128KW") { return JWEAlgorithm::A128KW; - } else if (value == "A192KW") { + } + if (value == "A192KW") { return JWEAlgorithm::A192KW; - } else if (value == "A256KW") { + } + if (value == "A256KW") { return JWEAlgorithm::A256KW; - } else if (value == "dir") { + } + if (value == "dir") { return JWEAlgorithm::DIR; - } else { - return std::nullopt; } + return std::nullopt; } auto jwe_algorithm_name(const JWEAlgorithm algorithm) noexcept @@ -181,19 +200,23 @@ auto to_jwe_encryption(const std::string_view value) noexcept -> std::optional { if (value == "A128GCM") { return JWEEncryption::A128GCM; - } else if (value == "A192GCM") { + } + if (value == "A192GCM") { return JWEEncryption::A192GCM; - } else if (value == "A256GCM") { + } + if (value == "A256GCM") { return JWEEncryption::A256GCM; - } else if (value == "A128CBC-HS256") { + } + if (value == "A128CBC-HS256") { return JWEEncryption::A128CBC_HS256; - } else if (value == "A192CBC-HS384") { + } + if (value == "A192CBC-HS384") { return JWEEncryption::A192CBC_HS384; - } else if (value == "A256CBC-HS512") { + } + if (value == "A256CBC-HS512") { return JWEEncryption::A256CBC_HS512; - } else { - return std::nullopt; } + return std::nullopt; } auto jwe_encryption_name(const JWEEncryption encryption) noexcept diff --git a/vendor/core/src/core/jose/jose_jwe.cc b/vendor/core/src/core/jose/jose_jwe.cc index eac85b8d..5c23d323 100644 --- a/vendor/core/src/core/jose/jose_jwe.cc +++ b/vendor/core/src/core/jose/jose_jwe.cc @@ -1,7 +1,7 @@ +#include #include #include -#include #include // std::optional, std::nullopt #include // std::string_view @@ -23,34 +23,16 @@ namespace sourcemeta::core { auto JWE::parse(const std::string_view input, JWE &result) -> bool { // The compact serialization is exactly five base64url segments joined by dots // (RFC 7516 Section 7.1) - const auto first{split_once(input, '.')}; - if (!first.has_value()) { + const auto segments{jose_compact_segments<5>(input)}; + if (!segments.has_value()) { return false; } - const auto second{split_once(first->second, '.')}; - if (!second.has_value()) { - return false; - } - - const auto third{split_once(second->second, '.')}; - if (!third.has_value()) { - return false; - } - - const auto fourth{split_once(third->second, '.')}; - if (!fourth.has_value()) { - return false; - } - - const auto header_segment{first->first}; - const auto encrypted_key_segment{second->first}; - const auto iv_segment{third->first}; - const auto ciphertext_segment{fourth->first}; - const auto tag_segment{fourth->second}; - if (tag_segment.find('.') != std::string_view::npos) { - return false; - } + const auto header_segment{segments->at(0)}; + const auto encrypted_key_segment{segments->at(1)}; + const auto iv_segment{segments->at(2)}; + const auto ciphertext_segment{segments->at(3)}; + const auto tag_segment{segments->at(4)}; auto header_bytes{base64url_decode(header_segment)}; auto encrypted_key_bytes{base64url_decode(encrypted_key_segment)}; diff --git a/vendor/core/src/core/jose/jose_jwt.cc b/vendor/core/src/core/jose/jose_jwt.cc index 31d50519..390e958f 100644 --- a/vendor/core/src/core/jose/jose_jwt.cc +++ b/vendor/core/src/core/jose/jose_jwt.cc @@ -1,3 +1,4 @@ +#include #include #include @@ -42,12 +43,12 @@ auto string_claim(const sourcemeta::core::JSON &object, // equal, and RFC 7515 Section 4.1.9 makes the media type case-insensitive auto strip_application_prefix(const std::string_view value) -> std::string_view { - constexpr std::string_view prefix{"application/"}; - if (value.size() > prefix.size() && - sourcemeta::core::equals_ignore_case(value.substr(0, prefix.size()), - prefix) && - value.find('/', prefix.size()) == std::string_view::npos) { - return value.substr(prefix.size()); + constexpr std::string_view PREFIX{"application/"}; + if (value.size() > PREFIX.size() && + sourcemeta::core::equals_ignore_case(value.substr(0, PREFIX.size()), + PREFIX) && + value.find('/', PREFIX.size()) == std::string_view::npos) { + return value.substr(PREFIX.size()); } return value; @@ -84,22 +85,14 @@ namespace sourcemeta::core { auto JWT::parse(const std::string_view input, JWT &result) -> bool { // The compact serialization is exactly three base64url segments joined by // dots (RFC 7515 Section 7.1) - const auto first{split_once(input, '.')}; - if (!first.has_value()) { + const auto segments{jose_compact_segments<3>(input)}; + if (!segments.has_value()) { return false; } - const auto second{split_once(first->second, '.')}; - if (!second.has_value()) { - return false; - } - - const auto header_segment{first->first}; - const auto payload_segment{second->first}; - const auto signature_segment{second->second}; - if (signature_segment.find('.') != std::string_view::npos) { - return false; - } + const auto header_segment{segments->at(0)}; + const auto payload_segment{segments->at(1)}; + const auto signature_segment{segments->at(2)}; auto header_bytes{base64url_decode(header_segment)}; auto payload_bytes{base64url_decode(payload_segment)}; diff --git a/vendor/core/src/core/jose/jose_jwt_check_claims.cc b/vendor/core/src/core/jose/jose_jwt_check_claims.cc index e44cf90f..af8127cb 100644 --- a/vendor/core/src/core/jose/jose_jwt_check_claims.cc +++ b/vendor/core/src/core/jose/jose_jwt_check_claims.cc @@ -31,11 +31,13 @@ auto shift_forward(const Clock::time_point now, const std::chrono::seconds skew) namespace sourcemeta::core { -auto jwt_check_claims(const JWT &token, const std::string_view expected_issuer, - const std::string_view expected_audience, - const std::chrono::system_clock::time_point now, - const JWTClockSkew clock_skew, - const std::optional expected_subject) +auto jwt_check_claims( + const JWT &token, const std::string_view expected_issuer, + const std::string_view expected_audience, + const std::chrono::system_clock::time_point now, + const JWTClockSkew clock_skew, + const std::optional expected_subject, + const std::optional maximum_lifetime) -> std::optional { // The issuer must be present and match the expected value (RFC 7519 Section // 4.1.1) @@ -91,6 +93,47 @@ auto jwt_check_claims(const JWT &token, const std::string_view expected_issuer, } } + // The interval a token claims for itself, which a caller bounds so that one + // claiming to outlive anything it mints is refused whatever signature it + // carries. Both instants come from the token, so the tolerance for a + // disagreeing server clock has no part in the comparison + if (maximum_lifetime.has_value()) { + const auto issued_at{token.issued_at()}; + // RFC 9068 Section 2.2 requires the issuance time of an access token in + // any case, and without it the bound could be escaped by omission + if (!issued_at.has_value() || expires_at.value() < issued_at.value()) { + return JWTClaimError::Lifetime; + } + + // A negative bound admits no interval at all, the empty one included. The + // shift below cannot express that, since it reads a span running backwards + // as none at all and so would admit a token expiring as it was issued + if (maximum_lifetime.value() < std::chrono::seconds::zero()) { + return JWTClaimError::Lifetime; + } + + // The widest interval two representable instants can span, in seconds so + // that the tick count which cannot carry it plays no part. A bound at + // least this wide admits every interval there is, and the shift below + // would otherwise refuse one measured from an instant near the oldest, + // where moving forward by the whole span still lands short of the newest + constexpr auto WIDEST{std::chrono::duration_cast( + Clock::duration::max()) - + std::chrono::duration_cast( + Clock::duration::min())}; + + // The bound is applied by shifting the claimed issuance forward, which + // saturates, rather than by subtracting one attacker-controlled date from + // another. Both are only held within the clock's representable window, so + // the difference between the two extremes does not fit the tick count that + // would carry it, and the wrapped result would read as within any bound + if (maximum_lifetime.value() < WIDEST && + expires_at.value() > + clock_shift_forward(issued_at.value(), maximum_lifetime.value())) { + return JWTClaimError::Lifetime; + } + } + return std::nullopt; } diff --git a/vendor/core/src/core/jose/jose_jwt_verify.cc b/vendor/core/src/core/jose/jose_jwt_verify.cc index b1dc8d4c..2e20be73 100644 --- a/vendor/core/src/core/jose/jose_jwt_verify.cc +++ b/vendor/core/src/core/jose/jose_jwt_verify.cc @@ -26,6 +26,8 @@ auto to_verification_error(const sourcemeta::core::JWTClaimError error) return JWTVerificationError::NotBefore; case JWTClaimError::IssuedAt: return JWTVerificationError::IssuedAt; + case JWTClaimError::Lifetime: + return JWTVerificationError::Lifetime; } std::unreachable(); @@ -42,7 +44,8 @@ auto jwt_verify(const JWT &token, const JWKS &keys, const std::chrono::system_clock::time_point now, const JWTClockSkew clock_skew, const std::optional expected_subject, - const std::optional expected_type) + const std::optional expected_type, + const std::optional maximum_lifetime) -> std::optional { // RFC 8725 Section 3.1: "Libraries MUST enable the caller to specify a // supported set of algorithms and MUST NOT use any other algorithms when @@ -92,7 +95,7 @@ auto jwt_verify(const JWT &token, const JWKS &keys, const auto claim_error{jwt_check_claims(token, expected_issuer, expected_audience, now, clock_skew, - expected_subject)}; + expected_subject, maximum_lifetime)}; if (claim_error.has_value()) { return to_verification_error(claim_error.value()); } diff --git a/vendor/core/src/core/jose/jose_key.h b/vendor/core/src/core/jose/jose_key.h index 74033d20..6b3ab1f1 100644 --- a/vendor/core/src/core/jose/jose_key.h +++ b/vendor/core/src/core/jose/jose_key.h @@ -37,7 +37,7 @@ inline auto jwk_rsa_modulus_is_allowed(const std::string_view modulus) noexcept } const auto leading{static_cast(modulus[offset])}; - const auto bits{(modulus.size() - offset - 1) * 8 + + const auto bits{((modulus.size() - offset - 1) * 8) + (8 - static_cast(std::countl_zero(leading)))}; return bits >= MINIMUM_RSA_MODULUS_BITS; } @@ -47,13 +47,14 @@ inline auto jwk_ec_coordinate_bytes(const std::string_view curve) -> std::optional { if (curve == "P-256") { return 32; - } else if (curve == "P-384") { + } + if (curve == "P-384") { return 48; - } else if (curve == "P-521") { + } + if (curve == "P-521") { return 66; - } else { - return std::nullopt; } + return std::nullopt; } // The key octet length is fixed per Edwards curve (RFC 8032 Sections 5.1.5 and @@ -62,11 +63,11 @@ inline auto jwk_okp_key_bytes(const std::string_view curve) -> std::optional { if (curve == "Ed25519") { return 32; - } else if (curve == "Ed448") { + } + if (curve == "Ed448") { return 57; - } else { - return std::nullopt; } + return std::nullopt; } // Both mappings are only reached after the curve has been validated above @@ -74,20 +75,19 @@ inline auto jwk_to_elliptic_curve(const std::string_view curve) noexcept -> EllipticCurve { if (curve == "P-256") { return EllipticCurve::P256; - } else if (curve == "P-384") { + } + if (curve == "P-384") { return EllipticCurve::P384; - } else { - return EllipticCurve::P521; } + return EllipticCurve::P521; } inline auto jwk_to_edwards_curve(const std::string_view curve) noexcept -> EdwardsCurve { if (curve == "Ed25519") { return EdwardsCurve::Ed25519; - } else { - return EdwardsCurve::Ed448; } + return EdwardsCurve::Ed448; } // The JWK curve name each elliptic curve carries (RFC 7518 Section 6.2.1.1), diff --git a/vendor/core/src/core/json/construct.h b/vendor/core/src/core/json/construct.h index f837136f..731d3485 100644 --- a/vendor/core/src/core/json/construct.h +++ b/vendor/core/src/core/json/construct.h @@ -24,8 +24,8 @@ namespace sourcemeta::core { namespace internal { inline auto unescape_string(const char *data, const std::uint32_t length, - const bool has_escape) -> typename JSON::String { - typename JSON::String result; + const bool has_escape) -> JSON::String { + JSON::String result; const char *cursor{data}; const char *string_end{data + length}; @@ -113,7 +113,7 @@ inline auto unescape_string(const char *data, const std::uint32_t length, inline auto construct_number(const char *data, const std::uint32_t length, const std::uint8_t flags, const std::uint32_t significant_digits) -> JSON { - if (flags & TAPE_FLAG_NUMBER_EXPONENT) { + if ((flags & TAPE_FLAG_NUMBER_EXPONENT) != 0) { try { return JSON{Decimal{std::string_view{data, length}}}; } catch (const DecimalParseError &) { @@ -123,7 +123,7 @@ inline auto construct_number(const char *data, const std::uint32_t length, } } - if (flags & TAPE_FLAG_NUMBER_DOT) { + if ((flags & TAPE_FLAG_NUMBER_DOT) != 0) { constexpr std::uint32_t MAX_SAFE_SIGNIFICANT_DIGITS{15}; if (significant_digits > MAX_SAFE_SIGNIFICANT_DIGITS) { try { @@ -222,8 +222,8 @@ inline auto construct_json(const char *buffer, std::vector> frames; levels.reserve(32); frames.reserve(32); - typename Result::String key; - typename Result::Object::hash_type key_hash; + Result::String key; + Result::Object::hash_type key_hash; std::uint64_t key_line{0}; std::uint64_t key_column{0}; std::size_t tape_index{0}; @@ -473,7 +473,7 @@ do_construct_object_key: { assert(key_entry.type == TapeType::Key); const char *key_data{buffer + key_entry.offset}; const auto key_length{key_entry.length}; - if (key_entry.flags & TAPE_FLAG_STRING_ESCAPE) { + if ((key_entry.flags & TAPE_FLAG_STRING_ESCAPE) != 0) { key = internal::unescape_string(key_data, key_length, true); key_hash = Result::Object::hash(key); } else { diff --git a/vendor/core/src/core/json/grammar.h b/vendor/core/src/core/json/grammar.h index 6b9338af..123ea009 100644 --- a/vendor/core/src/core/json/grammar.h +++ b/vendor/core/src/core/json/grammar.h @@ -21,9 +21,9 @@ namespace sourcemeta::core::internal { // solidus (U+005C), and the control characters U+0000 to U+001F. // See // https://www.ecma-international.org/wp-content/uploads/ECMA-404_2nd_edition_december_2017.pdf -template static constexpr CharT token_string_quote{'\u0022'}; -template static constexpr CharT token_string_escape{'\u005C'}; -template static constexpr CharT token_string_solidus{'\u002F'}; +template static constexpr CharT TOKEN_STRING_QUOTE{'\u0022'}; +template static constexpr CharT TOKEN_STRING_ESCAPE{'\u005C'}; +template static constexpr CharT TOKEN_STRING_SOLIDUS{'\u002F'}; // There are two-character escape sequence representations of some characters. // @@ -38,31 +38,31 @@ template static constexpr CharT token_string_solidus{'\u002F'}; // See // https://www.ecma-international.org/wp-content/uploads/ECMA-404_2nd_edition_december_2017.pdf template -static constexpr CharT token_string_escape_backspace{'\u0062'}; +static constexpr CharT TOKEN_STRING_ESCAPE_BACKSPACE{'\u0062'}; template -static constexpr CharT token_string_escape_form_feed{'\u0066'}; +static constexpr CharT TOKEN_STRING_ESCAPE_FORM_FEED{'\u0066'}; template -static constexpr CharT token_string_escape_line_feed{'\u006E'}; +static constexpr CharT TOKEN_STRING_ESCAPE_LINE_FEED{'\u006E'}; template -static constexpr CharT token_string_escape_carriage_return{'\u0072'}; +static constexpr CharT TOKEN_STRING_ESCAPE_CARRIAGE_RETURN{'\u0072'}; template -static constexpr CharT token_string_escape_tabulation{'\u0074'}; +static constexpr CharT TOKEN_STRING_ESCAPE_TABULATION{'\u0074'}; template -static constexpr CharT token_string_escape_unicode{'\u0075'}; +static constexpr CharT TOKEN_STRING_ESCAPE_UNICODE{'\u0075'}; // Array -template static constexpr CharT token_array_begin{'\u005B'}; -template static constexpr CharT token_array_end{'\u005D'}; +template static constexpr CharT TOKEN_ARRAY_BEGIN{'\u005B'}; +template static constexpr CharT TOKEN_ARRAY_END{'\u005D'}; template -static constexpr CharT token_array_delimiter{'\u002C'}; +static constexpr CharT TOKEN_ARRAY_DELIMITER{'\u002C'}; // Object -template static constexpr CharT token_object_begin{'\u007B'}; -template static constexpr CharT token_object_end{'\u007D'}; +template static constexpr CharT TOKEN_OBJECT_BEGIN{'\u007B'}; +template static constexpr CharT TOKEN_OBJECT_END{'\u007D'}; template -static constexpr CharT token_object_key_delimiter{'\u003A'}; +static constexpr CharT TOKEN_OBJECT_KEY_DELIMITER{'\u003A'}; template -static constexpr CharT token_object_delimiter{'\u002C'}; +static constexpr CharT TOKEN_OBJECT_DELIMITER{'\u002C'}; // These are the three literal name tokens: // true U+0074 U+0072 U+0075 U+0065 @@ -72,19 +72,19 @@ static constexpr CharT token_object_delimiter{'\u002C'}; // https://www.ecma-international.org/wp-content/uploads/ECMA-404_2nd_edition_december_2017.pdf // Boolean -template static constexpr CharT token_true{'\u0074'}; +template static constexpr CharT TOKEN_TRUE{'\u0074'}; template -static constexpr std::basic_string_view constant_true{ +static constexpr std::basic_string_view CONSTANT_TRUE{ "\u0074\u0072\u0075\u0065"}; -template static constexpr CharT token_false{'\u0066'}; +template static constexpr CharT TOKEN_FALSE{'\u0066'}; template -static constexpr std::basic_string_view constant_false{ +static constexpr std::basic_string_view CONSTANT_FALSE{ "\u0066\u0061\u006C\u0073\u0065"}; // Null -template static constexpr CharT token_null{'\u006E'}; +template static constexpr CharT TOKEN_NULL{'\u006E'}; template -static constexpr std::basic_string_view constant_null{ +static constexpr std::basic_string_view CONSTANT_NULL{ "\u006E\u0075\u006C\u006C"}; // A number is a sequence of decimal digits with no superfluous leading zero. @@ -95,23 +95,23 @@ static constexpr std::basic_string_view constant_null{ // See // https://www.ecma-international.org/wp-content/uploads/ECMA-404_2nd_edition_december_2017.pdf template -static constexpr CharT token_number_decimal_point{'\u002E'}; -template -static constexpr CharT token_number_exponent_uppercase{'\u0045'}; -template -static constexpr CharT token_number_exponent_lowercase{'\u0065'}; -template static constexpr CharT token_number_plus{'\u002B'}; -template static constexpr CharT token_number_minus{'\u002D'}; -template static constexpr CharT token_number_zero{'\u0030'}; -template static constexpr CharT token_number_one{'\u0031'}; -template static constexpr CharT token_number_two{'\u0032'}; -template static constexpr CharT token_number_three{'\u0033'}; -template static constexpr CharT token_number_four{'\u0034'}; -template static constexpr CharT token_number_five{'\u0035'}; -template static constexpr CharT token_number_six{'\u0036'}; -template static constexpr CharT token_number_seven{'\u0037'}; -template static constexpr CharT token_number_eight{'\u0038'}; -template static constexpr CharT token_number_nine{'\u0039'}; +static constexpr CharT TOKEN_NUMBER_DECIMAL_POINT{'\u002E'}; +template +static constexpr CharT TOKEN_NUMBER_EXPONENT_UPPERCASE{'\u0045'}; +template +static constexpr CharT TOKEN_NUMBER_EXPONENT_LOWERCASE{'\u0065'}; +template static constexpr CharT TOKEN_NUMBER_PLUS{'\u002B'}; +template static constexpr CharT TOKEN_NUMBER_MINUS{'\u002D'}; +template static constexpr CharT TOKEN_NUMBER_ZERO{'\u0030'}; +template static constexpr CharT TOKEN_NUMBER_ONE{'\u0031'}; +template static constexpr CharT TOKEN_NUMBER_TWO{'\u0032'}; +template static constexpr CharT TOKEN_NUMBER_THREE{'\u0033'}; +template static constexpr CharT TOKEN_NUMBER_FOUR{'\u0034'}; +template static constexpr CharT TOKEN_NUMBER_FIVE{'\u0035'}; +template static constexpr CharT TOKEN_NUMBER_SIX{'\u0036'}; +template static constexpr CharT TOKEN_NUMBER_SEVEN{'\u0037'}; +template static constexpr CharT TOKEN_NUMBER_EIGHT{'\u0038'}; +template static constexpr CharT TOKEN_NUMBER_NINE{'\u0039'}; // Whitespace is any sequence of one or more of the following code points: // character tabulation (U+0009), line feed (U+000A), carriage return (U+000D), @@ -119,13 +119,13 @@ template static constexpr CharT token_number_nine{'\u0039'}; // See // https://www.ecma-international.org/wp-content/uploads/ECMA-404_2nd_edition_december_2017.pdf template -static constexpr CharT token_whitespace_tabulation{'\u0009'}; +static constexpr CharT TOKEN_WHITESPACE_TABULATION{'\u0009'}; template -static constexpr CharT token_whitespace_line_feed{'\u000A'}; +static constexpr CharT TOKEN_WHITESPACE_LINE_FEED{'\u000A'}; template -static constexpr CharT token_whitespace_carriage_return{'\u000D'}; +static constexpr CharT TOKEN_WHITESPACE_CARRIAGE_RETURN{'\u000D'}; template -static constexpr CharT token_whitespace_space{'\u0020'}; +static constexpr CharT TOKEN_WHITESPACE_SPACE{'\u0020'}; } // namespace sourcemeta::core::internal diff --git a/vendor/core/src/core/json/include/sourcemeta/core/json_array.h b/vendor/core/src/core/json/include/sourcemeta/core/json_array.h index 97a2fb57..651f063d 100644 --- a/vendor/core/src/core/json/include/sourcemeta/core/json_array.h +++ b/vendor/core/src/core/json/include/sourcemeta/core/json_array.h @@ -14,95 +14,95 @@ template class JSONArray { /// The underlying container type that holds the array elements using Container = std::vector>; - JSONArray() : data{} {} + JSONArray() : data_{} {} /// Construct an array from a list of values - JSONArray(std::initializer_list values) : data{values} {} + JSONArray(std::initializer_list values) : data_{values} {} // Operators // We cannot default given that this class references // a JSON "value" as an incomplete type auto operator<(const JSONArray &other) const noexcept -> bool { - return this->data < other.data; + return this->data_ < other.data_; } auto operator<=(const JSONArray &other) const noexcept -> bool { - return this->data <= other.data; + return this->data_ <= other.data_; } auto operator>(const JSONArray &other) const noexcept -> bool { - return this->data > other.data; + return this->data_ > other.data_; } auto operator>=(const JSONArray &other) const noexcept -> bool { - return this->data >= other.data; + return this->data_ >= other.data_; } auto operator==(const JSONArray &other) const noexcept -> bool { - return this->data == other.data; + return this->data_ == other.data_; } auto operator!=(const JSONArray &other) const noexcept -> bool { - return this->data != other.data; + return this->data_ != other.data_; } // Member types - using value_type = typename Container::value_type; - using allocator_type = typename Container::allocator_type; - using size_type = typename Container::size_type; - using difference_type = typename Container::difference_type; - using reference = typename Container::reference; - using const_reference = typename Container::const_reference; - using pointer = typename Container::pointer; - using const_pointer = typename Container::const_pointer; - using iterator = typename Container::iterator; - using const_iterator = typename Container::const_iterator; - using reverse_iterator = typename Container::reverse_iterator; - using const_reverse_iterator = typename Container::const_reverse_iterator; + using value_type = Container::value_type; + using allocator_type = Container::allocator_type; + using size_type = Container::size_type; + using difference_type = Container::difference_type; + using reference = Container::reference; + using const_reference = Container::const_reference; + using pointer = Container::pointer; + using const_pointer = Container::const_pointer; + using iterator = Container::iterator; + using const_iterator = Container::const_iterator; + using reverse_iterator = Container::reverse_iterator; + using const_reverse_iterator = Container::const_reverse_iterator; /// Get a mutable begin iterator on the array - auto begin() noexcept -> iterator { return this->data.begin(); } + auto begin() noexcept -> iterator { return this->data_.begin(); } /// Get a mutable end iterator on the array - auto end() noexcept -> iterator { return this->data.end(); } + auto end() noexcept -> iterator { return this->data_.end(); } /// Get a constant begin iterator on the array [[nodiscard]] auto begin() const noexcept -> const_iterator { - return this->data.begin(); + return this->data_.begin(); } /// Get a constant end iterator on the array [[nodiscard]] auto end() const noexcept -> const_iterator { - return this->data.end(); + return this->data_.end(); } /// Get a constant begin iterator on the array [[nodiscard]] auto cbegin() const noexcept -> const_iterator { - return this->data.cbegin(); + return this->data_.cbegin(); } /// Get a constant end iterator on the array [[nodiscard]] auto cend() const noexcept -> const_iterator { - return this->data.cend(); + return this->data_.cend(); } /// Get a mutable reverse begin iterator on the array - auto rbegin() noexcept -> reverse_iterator { return this->data.rbegin(); } + auto rbegin() noexcept -> reverse_iterator { return this->data_.rbegin(); } /// Get a mutable reverse end iterator on the array - auto rend() noexcept -> reverse_iterator { return this->data.rend(); } + auto rend() noexcept -> reverse_iterator { return this->data_.rend(); } /// Get a constant reverse begin iterator on the array [[nodiscard]] auto rbegin() const noexcept -> const_reverse_iterator { - return this->data.rbegin(); + return this->data_.rbegin(); } /// Get a constant reverse end iterator on the array [[nodiscard]] auto rend() const noexcept -> const_reverse_iterator { - return this->data.rend(); + return this->data_.rend(); } /// Get a constant reverse begin iterator on the array [[nodiscard]] auto crbegin() const noexcept -> const_reverse_iterator { - return this->data.crbegin(); + return this->data_.crbegin(); } /// Get a constant reverse end iterator on the array [[nodiscard]] auto crend() const noexcept -> const_reverse_iterator { - return this->data.crend(); + return this->data_.crend(); } /// Get array size [[nodiscard]] auto size() const noexcept -> size_type { - return this->data.size(); + return this->data_.size(); } /// Reserve capacity for a given number of elements auto reserve(const size_type capacity) -> void { - this->data.reserve(capacity); + this->data_.reserve(capacity); } private: @@ -113,7 +113,7 @@ template class JSONArray { #if defined(_MSC_VER) #pragma warning(disable : 4251) #endif - Container data; + Container data_; #if defined(_MSC_VER) #pragma warning(default : 4251) #endif diff --git a/vendor/core/src/core/json/include/sourcemeta/core/json_auto.h b/vendor/core/src/core/json/include/sourcemeta/core/json_auto.h index 03c1522d..64e28bf6 100644 --- a/vendor/core/src/core/json/include/sourcemeta/core/json_auto.h +++ b/vendor/core/src/core/json/include/sourcemeta/core/json_auto.h @@ -23,22 +23,21 @@ template concept json_auto_has_mapped_type = requires { typename T::mapped_type; }; /// @ingroup json -template struct json_auto_is_basic_string : std::false_type {}; +template struct JSONAutoIsBasicString : std::false_type {}; template -struct json_auto_is_basic_string> +struct JSONAutoIsBasicString> : std::true_type {}; /// @ingroup json -template -struct json_auto_is_basic_string_view : std::false_type {}; +template struct JSONAutoIsBasicStringView : std::false_type {}; template -struct json_auto_is_basic_string_view> +struct JSONAutoIsBasicStringView> : std::true_type {}; /// @ingroup json -template struct json_auto_is_bitset : std::false_type {}; +template struct JSONAutoIsBitset : std::false_type {}; template -struct json_auto_is_bitset> : std::true_type {}; +struct JSONAutoIsBitset> : std::true_type {}; /// @ingroup json template struct json_auto_bitset_size; @@ -76,8 +75,7 @@ concept json_auto_list_like = { type.cend() } -> std::same_as; } && json_auto_supports_auto && !json_auto_has_mapped_type && !json_auto_has_method_from && !json_auto_has_method_to && - !json_auto_is_basic_string::value && - !json_auto_is_basic_string_view::value; + !JSONAutoIsBasicString::value && !JSONAutoIsBasicStringView::value; /// @ingroup json template @@ -98,18 +96,18 @@ concept json_auto_has_reverse_iterator = requires { typename T::reverse_iterator; }; /// @ingroup json -template struct json_auto_is_pair : std::false_type {}; +template struct JSONAutoIsPair : std::false_type {}; /// @ingroup json template -struct json_auto_is_pair> : std::true_type {}; +struct JSONAutoIsPair> : std::true_type {}; /// @ingroup json -template struct json_auto_is_variant : std::false_type {}; +template struct JSONAutoIsVariant : std::false_type {}; /// @ingroup json template -struct json_auto_is_variant> : std::true_type {}; +struct JSONAutoIsVariant> : std::true_type {}; /// @ingroup json template @@ -138,7 +136,7 @@ auto to_json(const std::pair &value) -> JSON; template auto to_json(const T &value) -> JSON; template auto to_json(const T &value) -> JSON; template - requires json_auto_is_variant::value + requires JSONAutoIsVariant::value auto to_json(const T &value) -> JSON; #endif @@ -164,9 +162,8 @@ template auto from_json(const JSON &value) -> std::optional { if (value.is_boolean()) { return value.to_boolean(); - } else { - return std::nullopt; } + return std::nullopt; } /// @ingroup json @@ -177,9 +174,8 @@ auto from_json(const JSON &value) -> std::optional { // silently narrowed one if (value.is_integer() && std::in_range(value.to_integer())) { return static_cast(value.to_integer()); - } else { - return std::nullopt; } + return std::nullopt; } /// @ingroup json @@ -188,9 +184,8 @@ template auto from_json(const JSON &value) -> std::optional { if (value.is_decimal()) { return value.to_decimal(); - } else { - return std::nullopt; } + return std::nullopt; } // TODO: How can we keep this in the hash header that does not yet know about @@ -257,9 +252,8 @@ auto from_json(const JSON &value) -> std::optional { using file_time_type = std::filesystem::file_time_type; return file_time_type{file_time_type::duration{ static_cast(value.to_integer())}}; - } else { - return std::nullopt; } + return std::nullopt; } /// @ingroup json @@ -278,17 +272,16 @@ template auto from_json(const JSON &value) -> std::optional { if (value.is_string()) { return value.to_string(); - } else { - return std::nullopt; } + return std::nullopt; } /// @ingroup json template - requires json_auto_is_bitset::value + requires JSONAutoIsBitset::value auto to_json(const T &bitset) -> JSON { - constexpr std::size_t N{json_auto_bitset_size::value}; - if constexpr (N <= 64) { + constexpr std::size_t BIT_COUNT{json_auto_bitset_size::value}; + if constexpr (BIT_COUNT <= 64) { return JSON{static_cast(bitset.to_ullong())}; } else { return JSON{bitset.to_string()}; @@ -297,21 +290,20 @@ auto to_json(const T &bitset) -> JSON { /// @ingroup json template - requires json_auto_is_bitset::value + requires JSONAutoIsBitset::value auto from_json(const JSON &value) -> std::optional { - constexpr std::size_t N{json_auto_bitset_size::value}; - if constexpr (N <= 64) { + constexpr std::size_t BIT_COUNT{json_auto_bitset_size::value}; + if constexpr (BIT_COUNT <= 64) { if (value.is_integer()) { return T{static_cast(value.to_integer())}; - } else { - return std::nullopt; } + return std::nullopt; + } else { if (value.is_string()) { return T{value.to_string()}; - } else { - return std::nullopt; } + return std::nullopt; } } @@ -331,13 +323,12 @@ auto from_json(const JSON &value) -> std::optional { /// @ingroup json template - requires json_auto_is_basic_string::value + requires JSONAutoIsBasicString::value auto from_json(const JSON &value) -> std::optional { if (value.is_string()) { return value.to_string(); - } else { - return std::nullopt; } + return std::nullopt; } /// @ingroup json @@ -353,9 +344,8 @@ template auto from_json(const JSON &value) -> std::optional { if (value.is_integer()) { return static_cast(value.to_integer()); - } else { - return std::nullopt; } + return std::nullopt; } /// @ingroup json @@ -372,14 +362,13 @@ auto from_json(const JSON &value) -> std::optional { if (value.is_null()) { return std::optional{ std::optional{std::nullopt}}; - } else { - auto result{from_json(value)}; - if (!result.has_value()) { - return std::nullopt; - } - - return result; } + auto result{from_json(value)}; + if (!result.has_value()) { + return std::nullopt; + } + + return result; } /// @ingroup json @@ -590,7 +579,7 @@ auto to_json(const std::pair &value) -> JSON { /// @ingroup json template - requires json_auto_is_pair::value + requires JSONAutoIsPair::value auto from_json(const JSON &value) -> std::optional { if (!value.is_array() || value.size() != 2) { return std::nullopt; @@ -644,7 +633,8 @@ template auto to_json(const T &value) -> JSON { #ifndef DOXYGEN template -auto from_json_tuple_poly(const JSON &value, std::index_sequence) +auto from_json_tuple_poly( + const JSON &value, [[maybe_unused]] std::index_sequence indices) -> T { return {from_json>(value.at(Indices)) .value()...}; @@ -669,7 +659,7 @@ auto from_json(const JSON &value) -> std::optional { /// @ingroup json template - requires json_auto_is_variant::value + requires JSONAutoIsVariant::value auto to_json(const T &value) -> JSON { auto result{JSON::make_array()}; result.push_back(JSON{static_cast(value.index())}); @@ -695,16 +685,15 @@ auto from_json_variant_impl(const JSON &data, std::size_t index) } return std::nullopt; - } else { - return from_json_variant_impl(data, index); } + return from_json_variant_impl(data, index); } } #endif /// @ingroup json template - requires json_auto_is_variant::value + requires JSONAutoIsVariant::value auto from_json(const JSON &value) -> std::optional { if (!value.is_array() || value.size() != 2 || !value.at(0).is_integer()) { return std::nullopt; @@ -713,9 +702,8 @@ auto from_json(const JSON &value) -> std::optional { const auto index{static_cast(value.at(0).to_integer())}; if (index >= std::variant_size_v) { return std::nullopt; - } else { - return from_json_variant_impl(value.at(1), index); } + return from_json_variant_impl(value.at(1), index); } } // namespace sourcemeta::core diff --git a/vendor/core/src/core/json/include/sourcemeta/core/json_hash.h b/vendor/core/src/core/json/include/sourcemeta/core/json_hash.h index b3e116ed..aaf27efd 100644 --- a/vendor/core/src/core/json/include/sourcemeta/core/json_hash.h +++ b/vendor/core/src/core/json/include/sourcemeta/core/json_hash.h @@ -16,7 +16,7 @@ namespace sourcemeta::core { template struct HashJSON { using hash_type = std::uint64_t; - inline auto operator()(const T &value) const noexcept -> hash_type { + auto operator()(const T &value) const noexcept -> hash_type { if constexpr (requires { value.get().fast_hash(); }) { return value.get().fast_hash(); } else { @@ -26,7 +26,8 @@ template struct HashJSON { /// Check whether the given hash is a perfect hash [[nodiscard]] - inline auto is_perfect(const hash_type) const noexcept -> bool { + auto is_perfect([[maybe_unused]] const hash_type hash) const noexcept + -> bool { return false; } }; @@ -34,19 +35,23 @@ template struct HashJSON { /// @ingroup json /// A hash function object for JSON object property keys template struct PropertyHashJSON { - struct hash_type { + /// The two halves that make up a property key hash + struct HashType { + /// The unsigned integer type each half is stored as using type = sourcemeta::core::uint128_t; + /// The first half of the hash type a{0}; + /// The second half of the hash type b{0}; - auto operator==(const hash_type &) const noexcept -> bool = default; + auto operator==(const HashType &) const noexcept -> bool = default; }; /// Compute a perfect hash from raw data [[nodiscard]] constexpr auto perfect(const char *data, - const std::size_t size) const noexcept -> hash_type { - hash_type result; + const std::size_t size) const noexcept -> HashType { + HashType result; assert(size > 0); if consteval { // A constant evaluation cannot memcpy through a reinterpret_cast, so pack @@ -56,7 +61,7 @@ template struct PropertyHashJSON { // silently diverge static_assert(std::endian::native == std::endian::little); for (std::size_t index = 0; index < size; index += 1) { - const auto byte{static_cast( + const auto byte{static_cast( static_cast(data[index]))}; const auto position{index + 1}; if (position < 16) { @@ -76,7 +81,7 @@ template struct PropertyHashJSON { // std::string to std::string_view, so we provide separate overloads with // duplicated logic instead of unifying on a single parameter type - constexpr auto operator()(const T &value) const noexcept -> hash_type { + constexpr auto operator()(const T &value) const noexcept -> HashType { const auto size{value.size()}; switch (size) { case 0: @@ -149,8 +154,8 @@ template struct PropertyHashJSON { // have a lot of entries, so hash collision is not as common auto hash = this->perfect(value.data(), 31); hash.a |= 1 + (static_cast(size) + - static_cast(value.front()) + - static_cast(value.back())) % + static_cast(value.front()) + + static_cast(value.back())) % // Make sure the property hash can never exceed 8 bits 255; return hash; @@ -158,8 +163,7 @@ template struct PropertyHashJSON { } constexpr auto operator()(const char *data, - const std::size_t size) const noexcept - -> hash_type { + const std::size_t size) const noexcept -> HashType { switch (size) { case 0: return {}; @@ -231,8 +235,8 @@ template struct PropertyHashJSON { // have a lot of entries, so hash collision is not as common auto hash = this->perfect(data, 31); hash.a |= 1 + (static_cast(size) + - static_cast(data[0]) + - static_cast(data[size - 1])) % + static_cast(data[0]) + + static_cast(data[size - 1])) % // Make sure the property hash can never exceed 8 bits 255; return hash; @@ -241,7 +245,7 @@ template struct PropertyHashJSON { /// Check whether the given hash is a perfect hash [[nodiscard]] - constexpr auto is_perfect(const hash_type &hash) const noexcept -> bool { + constexpr auto is_perfect(const HashType &hash) const noexcept -> bool { // If there is anything written past the first byte, // then it is a perfect hash return (hash.a & 255) == 0; @@ -254,7 +258,7 @@ template struct PropertyHashJSON { /// See /// https://en.cppreference.com/w/cpp/utility/functional/reference_wrapper/operator_cmp.html template struct EqualJSON { - inline auto operator()(const T &left, const T &right) const -> bool { + auto operator()(const T &left, const T &right) const -> bool { if constexpr (requires { left.get() == right.get(); }) { return left.get() == right.get(); } else { diff --git a/vendor/core/src/core/json/include/sourcemeta/core/json_object.h b/vendor/core/src/core/json/include/sourcemeta/core/json_object.h index 559536e3..b55fe70f 100644 --- a/vendor/core/src/core/json/include/sourcemeta/core/json_object.h +++ b/vendor/core/src/core/json/include/sourcemeta/core/json_object.h @@ -22,15 +22,15 @@ template class JSONObject { using key_type = Key; using mapped_type = Value; - using hash_type = typename Hash::hash_type; + using hash_type = Hash::HashType; using pair_value_type = std::pair; /// The string view type used to look up object keys using KeyView = std::basic_string_view; /// Construct an object from a list of key and value pairs - JSONObject(std::initializer_list entries) : data{} { - this->data.reserve(entries.size()); + JSONObject(std::initializer_list entries) : data_{} { + this->data_.reserve(entries.size()); for (auto &&entry : entries) { this->emplace(std::move(entry.first), std::move(entry.second)); } @@ -59,29 +59,28 @@ template class JSONObject { /// assert(entry.key_equals( /// "foo", sourcemeta::core::JSON::Object::hash("foo"))); /// ``` - [[nodiscard]] inline auto key_equals(const KeyView key, - const hash_type key_hash) const - -> bool { + [[nodiscard]] auto key_equals(const KeyView key, + const hash_type key_hash) const -> bool { assert(JSONObject::hash(key) == key_hash); // A perfect hash captures the key bytes but not its length, so two keys // that differ only by trailing NUL bytes hash equal. Comparing sizes // disambiguates them without the cost of a full string comparison return this->hash == key_hash && - (hasher.is_perfect(key_hash) ? this->first.size() == key.size() + (HASHER.is_perfect(key_hash) ? this->first.size() == key.size() : this->first == key); } }; using underlying_type = std::vector; - using value_type = typename underlying_type::value_type; - using size_type = typename underlying_type::size_type; - using difference_type = typename underlying_type::difference_type; - using allocator_type = typename underlying_type::allocator_type; - using reference = typename underlying_type::reference; - using const_reference = typename underlying_type::const_reference; - using pointer = typename underlying_type::pointer; - using const_pointer = typename underlying_type::const_pointer; - using const_iterator = typename underlying_type::const_iterator; + using value_type = underlying_type::value_type; + using size_type = underlying_type::size_type; + using difference_type = underlying_type::difference_type; + using allocator_type = underlying_type::allocator_type; + using reference = underlying_type::reference; + using const_reference = underlying_type::const_reference; + using pointer = underlying_type::pointer; + using const_pointer = underlying_type::const_pointer; + using const_iterator = underlying_type::const_iterator; // Operators // We cannot default given that this class references @@ -95,13 +94,13 @@ template class JSONObject { // ordered as their entries would compare in key order. That outcome is // decided entirely by the smallest key at which the two objects differ, // which is found by scanning the entries in place to avoid allocating - if (this->data.size() != other.data.size()) { - return this->data.size() < other.data.size(); + if (this->data_.size() != other.data_.size()) { + return this->data_.size() < other.data_.size(); } const Key *decisive_key{nullptr}; bool decision{false}; - for (const auto &entry : this->data) { + for (const auto &entry : this->data_) { const auto match{other.find(entry.first)}; const bool differs{match == other.cend() || !(entry.second == match->second)}; @@ -111,7 +110,7 @@ template class JSONObject { } } - for (const auto &entry : other.data) { + for (const auto &entry : other.data_) { if (this->find(entry.first) == this->cend() && (decisive_key == nullptr || entry.first < *decisive_key)) { decisive_key = &entry.first; @@ -141,9 +140,9 @@ template class JSONObject { return false; } - for (const auto &entry : this->data) { + for (const auto &entry : this->data_) { const auto *result{other.try_at(entry.first, entry.hash)}; - if (!result || *result != entry.second) { + if ((result == nullptr) || *result != entry.second) { return false; } } @@ -154,20 +153,20 @@ template class JSONObject { auto operator!=(const JSONObject &other) const noexcept -> bool = default; - [[nodiscard]] inline auto begin() const noexcept -> const_iterator { - return this->data.begin(); + [[nodiscard]] auto begin() const noexcept -> const_iterator { + return this->data_.begin(); } /// Get a constant end iterator on the object - [[nodiscard]] inline auto end() const noexcept -> const_iterator { - return this->data.end(); + [[nodiscard]] auto end() const noexcept -> const_iterator { + return this->data_.end(); } /// Get a constant begin iterator on the object - [[nodiscard]] inline auto cbegin() const noexcept -> const_iterator { - return this->data.cbegin(); + [[nodiscard]] auto cbegin() const noexcept -> const_iterator { + return this->data_.cbegin(); } /// Get a constant end iterator on the object - [[nodiscard]] inline auto cend() const noexcept -> const_iterator { - return this->data.cend(); + [[nodiscard]] auto cend() const noexcept -> const_iterator { + return this->data_.cend(); } // GCC does not optimise well across implicit type conversions such as @@ -180,32 +179,32 @@ template class JSONObject { /// Compute a hash for a key [[nodiscard]] static constexpr auto hash(const Key &key) noexcept -> hash_type { - return hasher(key); + return HASHER(key); } /// Compute a hash for a key template requires std::same_as, KeyView> [[nodiscard]] static constexpr auto hash(T key) noexcept -> hash_type { - return hasher(key.data(), key.size()); + return HASHER(key.data(), key.size()); } /// Compute a hash from raw data [[nodiscard]] static constexpr auto hash(const char *raw_data, const std::size_t raw_size) noexcept -> hash_type { - return hasher(raw_data, raw_size); + return HASHER(raw_data, raw_size); } /// Attempt to find an entry by key - [[nodiscard]] inline auto find(const Key &key) const -> const_iterator { + [[nodiscard]] auto find(const Key &key) const -> const_iterator { const auto key_hash{this->hash(key)}; // Move the perfect hash condition out of the loop for extra performance - if (this->hasher.is_perfect(key_hash)) { + if (this->HASHER.is_perfect(key_hash)) { for (size_type index = 0; index < this->size(); index++) { - if (this->data[index].hash == key_hash && - this->data[index].first.size() == key.size()) { + if (this->data_[index].hash == key_hash && + this->data_[index].first.size() == key.size()) { auto iterator{this->cbegin()}; std::advance(iterator, index); return iterator; @@ -213,8 +212,8 @@ template class JSONObject { } } else { for (size_type index = 0; index < this->size(); index++) { - if (this->data[index].hash == key_hash && - this->data[index].first == key) { + if (this->data_[index].hash == key_hash && + this->data_[index].first == key) { auto iterator{this->cbegin()}; std::advance(iterator, index); return iterator; @@ -228,14 +227,14 @@ template class JSONObject { /// Attempt to find an entry by key template requires std::same_as, KeyView> - [[nodiscard]] inline auto find(T key) const -> const_iterator { + [[nodiscard]] auto find(T key) const -> const_iterator { const auto key_hash{this->hash(key)}; // Move the perfect hash condition out of the loop for extra performance - if (this->hasher.is_perfect(key_hash)) { + if (this->HASHER.is_perfect(key_hash)) { for (size_type index = 0; index < this->size(); index++) { - if (this->data[index].hash == key_hash && - this->data[index].first.size() == key.size()) { + if (this->data_[index].hash == key_hash && + this->data_[index].first.size() == key.size()) { auto iterator{this->cbegin()}; std::advance(iterator, index); return iterator; @@ -243,8 +242,8 @@ template class JSONObject { } } else { for (size_type index = 0; index < this->size(); index++) { - if (this->data[index].hash == key_hash && - this->data[index].first == key) { + if (this->data_[index].hash == key_hash && + this->data_[index].first == key) { auto iterator{this->cbegin()}; std::advance(iterator, index); return iterator; @@ -256,12 +255,12 @@ template class JSONObject { } /// Check if an entry with the given key exists - [[nodiscard]] inline auto defines(const Key &key, const hash_type hash) const + [[nodiscard]] auto defines(const Key &key, const hash_type hash) const -> bool { assert(this->hash(key) == hash); // Move the perfect hash condition out of the loop for extra performance - if (this->hasher.is_perfect(hash)) { + if (this->HASHER.is_perfect(hash)) { for (const auto &entry : *this) { if (entry.hash == hash && entry.first.size() == key.size()) { return true; @@ -281,11 +280,11 @@ template class JSONObject { /// Check if an entry with the given key exists template requires std::same_as, KeyView> - [[nodiscard]] inline auto defines(T key, const hash_type hash) const -> bool { + [[nodiscard]] auto defines(T key, const hash_type hash) const -> bool { assert(this->hash(key) == hash); // Move the perfect hash condition out of the loop for extra performance - if (this->hasher.is_perfect(hash)) { + if (this->HASHER.is_perfect(hash)) { for (const auto &entry : *this) { if (entry.hash == hash && entry.first.size() == key.size()) { return true; @@ -303,30 +302,28 @@ template class JSONObject { } /// Check the size of the object - [[nodiscard]] inline auto size() const -> std::size_t { - return this->data.size(); - } + [[nodiscard]] auto size() const -> std::size_t { return this->data_.size(); } /// Check if the object is empty - [[nodiscard]] inline auto empty() const -> bool { return this->data.empty(); } + [[nodiscard]] auto empty() const -> bool { return this->data_.empty(); } /// Reserve capacity for a given number of entries - inline auto reserve(const size_type capacity) -> void { - this->data.reserve(capacity); + auto reserve(const size_type capacity) -> void { + this->data_.reserve(capacity); } /// Access an object entry by its underlying positional index - [[nodiscard]] inline auto at(const size_type index) const -> const Entry & { - return this->data.at(index); + [[nodiscard]] auto at(const size_type index) const -> const Entry & { + return this->data_.at(index); } /// Access an object entry by its key name - [[nodiscard]] inline auto at(const Key &key, const hash_type key_hash) const + [[nodiscard]] auto at(const Key &key, const hash_type key_hash) const -> const mapped_type & { assert(this->hash(key) == key_hash); // Move the perfect hash condition out of the loop for extra performance - if (this->hasher.is_perfect(key_hash)) { + if (this->HASHER.is_perfect(key_hash)) { for (const auto &entry : *this) { if (entry.hash == key_hash && entry.first.size() == key.size()) { return entry.second; @@ -346,12 +343,12 @@ template class JSONObject { /// Access an object entry by its key name template requires std::same_as, KeyView> - [[nodiscard]] inline auto at(T key, const hash_type key_hash) const + [[nodiscard]] auto at(T key, const hash_type key_hash) const -> const mapped_type & { assert(this->hash(key) == key_hash); // Move the perfect hash condition out of the loop for extra performance - if (this->hasher.is_perfect(key_hash)) { + if (this->HASHER.is_perfect(key_hash)) { for (const auto &entry : *this) { if (entry.hash == key_hash && entry.first.size() == key.size()) { return entry.second; @@ -369,18 +366,18 @@ template class JSONObject { } /// Access an object entry by its key name - inline auto at(const Key &key, const hash_type key_hash) -> mapped_type & { + auto at(const Key &key, const hash_type key_hash) -> mapped_type & { assert(this->hash(key) == key_hash); // Move the perfect hash condition out of the loop for extra performance - if (this->hasher.is_perfect(key_hash)) { - for (auto &entry : this->data) { + if (this->HASHER.is_perfect(key_hash)) { + for (auto &entry : this->data_) { if (entry.hash == key_hash && entry.first.size() == key.size()) { return entry.second; } } } else { - for (auto &entry : this->data) { + for (auto &entry : this->data_) { if (entry.hash == key_hash && entry.first == key) { return entry.second; } @@ -393,18 +390,18 @@ template class JSONObject { /// Access an object entry by its key name template requires std::same_as, KeyView> - inline auto at(T key, const hash_type key_hash) -> mapped_type & { + auto at(T key, const hash_type key_hash) -> mapped_type & { assert(this->hash(key) == key_hash); // Move the perfect hash condition out of the loop for extra performance - if (this->hasher.is_perfect(key_hash)) { - for (auto &entry : this->data) { + if (this->HASHER.is_perfect(key_hash)) { + for (auto &entry : this->data_) { if (entry.hash == key_hash && entry.first.size() == key.size()) { return entry.second; } } } else { - for (auto &entry : this->data) { + for (auto &entry : this->data_) { if (entry.hash == key_hash && entry.first == key) { return entry.second; } @@ -415,19 +412,19 @@ template class JSONObject { } /// Try to access an object entry by its key name - [[nodiscard]] inline auto try_at(const Key &key, const hash_type key_hash) + [[nodiscard]] auto try_at(const Key &key, const hash_type key_hash) -> mapped_type * { assert(this->hash(key) == key_hash); // Move the perfect hash condition out of the loop for extra performance - if (this->hasher.is_perfect(key_hash)) { - for (auto &entry : this->data) { + if (this->HASHER.is_perfect(key_hash)) { + for (auto &entry : this->data_) { if (entry.hash == key_hash && entry.first.size() == key.size()) { return &entry.second; } } } else { - for (auto &entry : this->data) { + for (auto &entry : this->data_) { if (entry.hash == key_hash && entry.first == key) { return &entry.second; } @@ -440,19 +437,18 @@ template class JSONObject { /// Try to access an object entry by its key name template requires std::same_as, KeyView> - [[nodiscard]] inline auto try_at(T key, const hash_type key_hash) - -> mapped_type * { + [[nodiscard]] auto try_at(T key, const hash_type key_hash) -> mapped_type * { assert(this->hash(key) == key_hash); // Move the perfect hash condition out of the loop for extra performance - if (this->hasher.is_perfect(key_hash)) { - for (auto &entry : this->data) { + if (this->HASHER.is_perfect(key_hash)) { + for (auto &entry : this->data_) { if (entry.hash == key_hash && entry.first.size() == key.size()) { return &entry.second; } } } else { - for (auto &entry : this->data) { + for (auto &entry : this->data_) { if (entry.hash == key_hash && entry.first == key) { return &entry.second; } @@ -463,24 +459,23 @@ template class JSONObject { } /// Try to access an object entry by its underlying positional index - [[nodiscard]] inline auto try_at(const Key &key, - const hash_type key_hash) const + [[nodiscard]] auto try_at(const Key &key, const hash_type key_hash) const -> const mapped_type * { assert(this->hash(key) == key_hash); // Move the perfect hash condition out of the loop for extra performance - if (this->hasher.is_perfect(key_hash)) { + if (this->HASHER.is_perfect(key_hash)) { for (size_type index = 0; index < this->size(); index++) { - if (this->data[index].hash == key_hash && - this->data[index].first.size() == key.size()) { - return &this->data[index].second; + if (this->data_[index].hash == key_hash && + this->data_[index].first.size() == key.size()) { + return &this->data_[index].second; } } } else { for (size_type index = 0; index < this->size(); index++) { - if (this->data[index].hash == key_hash && - this->data[index].first == key) { - return &this->data[index].second; + if (this->data_[index].hash == key_hash && + this->data_[index].first == key) { + return &this->data_[index].second; } } } @@ -491,23 +486,23 @@ template class JSONObject { /// Try to access an object entry by its underlying positional index template requires std::same_as, KeyView> - [[nodiscard]] inline auto try_at(T key, const hash_type key_hash) const + [[nodiscard]] auto try_at(T key, const hash_type key_hash) const -> const mapped_type * { assert(this->hash(key) == key_hash); // Move the perfect hash condition out of the loop for extra performance - if (this->hasher.is_perfect(key_hash)) { + if (this->HASHER.is_perfect(key_hash)) { for (size_type index = 0; index < this->size(); index++) { - if (this->data[index].hash == key_hash && - this->data[index].first.size() == key.size()) { - return &this->data[index].second; + if (this->data_[index].hash == key_hash && + this->data_[index].first.size() == key.size()) { + return &this->data_[index].second; } } } else { for (size_type index = 0; index < this->size(); index++) { - if (this->data[index].hash == key_hash && - this->data[index].first == key) { - return &this->data[index].second; + if (this->data_[index].hash == key_hash && + this->data_[index].first == key) { + return &this->data_[index].second; } } } @@ -517,28 +512,27 @@ template class JSONObject { /// Try to access an object entry, scanning from a caller-provided start /// offset. On hit, advances `start` past the found index - [[nodiscard]] inline auto try_at(const Key &key, const hash_type key_hash, - size_type &start) const - -> const mapped_type * { + [[nodiscard]] auto try_at(const Key &key, const hash_type key_hash, + size_type &start) const -> const mapped_type * { assert(this->hash(key) == key_hash); const auto object_size{this->size()}; assert(start <= object_size); - if (this->hasher.is_perfect(key_hash)) { + if (this->HASHER.is_perfect(key_hash)) { for (size_type count = 0; count < object_size; count++) { const auto index{(start + count) % object_size}; - if (this->data[index].hash == key_hash && - this->data[index].first.size() == key.size()) { + if (this->data_[index].hash == key_hash && + this->data_[index].first.size() == key.size()) { start = index + 1; - return &this->data[index].second; + return &this->data_[index].second; } } } else { for (size_type count = 0; count < object_size; count++) { const auto index{(start + count) % object_size}; - if (this->data[index].hash == key_hash && - this->data[index].first == key) { + if (this->data_[index].hash == key_hash && + this->data_[index].first == key) { start = index + 1; - return &this->data[index].second; + return &this->data_[index].second; } } } @@ -550,28 +544,27 @@ template class JSONObject { /// offset. On hit, advances `start` past the found index template requires std::same_as, KeyView> - [[nodiscard]] inline auto try_at(T key, const hash_type key_hash, - size_type &start) const - -> const mapped_type * { + [[nodiscard]] auto try_at(T key, const hash_type key_hash, + size_type &start) const -> const mapped_type * { assert(this->hash(key) == key_hash); const auto object_size{this->size()}; assert(start <= object_size); - if (this->hasher.is_perfect(key_hash)) { + if (this->HASHER.is_perfect(key_hash)) { for (size_type count = 0; count < object_size; count++) { const auto index{(start + count) % object_size}; - if (this->data[index].hash == key_hash && - this->data[index].first.size() == key.size()) { + if (this->data_[index].hash == key_hash && + this->data_[index].first.size() == key.size()) { start = index + 1; - return &this->data[index].second; + return &this->data_[index].second; } } } else { for (size_type count = 0; count < object_size; count++) { const auto index{(start + count) % object_size}; - if (this->data[index].hash == key_hash && - this->data[index].first == key) { + if (this->data_[index].hash == key_hash && + this->data_[index].first == key) { start = index + 1; - return &this->data[index].second; + return &this->data_[index].second; } } } @@ -585,48 +578,50 @@ template class JSONObject { const auto key_hash{this->hash(key)}; const auto suffix_hash{this->hash(suffix)}; - if (this->hasher.is_perfect(key_hash)) { - for (auto iterator = this->data.begin(); iterator != this->data.end(); + if (this->HASHER.is_perfect(key_hash)) { + for (auto iterator = this->data_.begin(); iterator != this->data_.end(); ++iterator) { if (iterator->hash == key_hash && iterator->first.size() == key.size()) { iterator->second = value; return key_hash; - } else if (iterator->hash == suffix_hash && iterator->first == suffix) { - this->data.insert(iterator, {key, value, key_hash}); + } + if (iterator->hash == suffix_hash && iterator->first == suffix) { + this->data_.insert(iterator, {key, value, key_hash}); return key_hash; } } } else { - for (auto iterator = this->data.begin(); iterator != this->data.end(); + for (auto iterator = this->data_.begin(); iterator != this->data_.end(); ++iterator) { if (iterator->hash == key_hash && iterator->first == key) { iterator->second = value; return key_hash; - } else if (iterator->hash == suffix_hash && iterator->first == suffix) { - this->data.insert(iterator, {key, value, key_hash}); + } + if (iterator->hash == suffix_hash && iterator->first == suffix) { + this->data_.insert(iterator, {key, value, key_hash}); return key_hash; } } } - this->data.push_back({key, value, key_hash}); + this->data_.push_back({key, value, key_hash}); return key_hash; } /// Emplace an object property - inline auto emplace(Key &&key, mapped_type &&value) -> hash_type { + auto emplace(Key &&key, mapped_type &&value) -> hash_type { const auto key_hash{this->hash(key)}; - if (this->hasher.is_perfect(key_hash)) { - for (auto &entry : this->data) { + if (this->HASHER.is_perfect(key_hash)) { + for (auto &entry : this->data_) { if (entry.hash == key_hash && entry.first.size() == key.size()) { entry.second = std::move(value); return key_hash; } } } else { - for (auto &entry : this->data) { + for (auto &entry : this->data_) { if (entry.hash == key_hash && entry.first == key) { entry.second = std::move(value); return key_hash; @@ -634,23 +629,23 @@ template class JSONObject { } } - this->data.push_back({std::move(key), std::move(value), key_hash}); + this->data_.push_back({std::move(key), std::move(value), key_hash}); return key_hash; } /// Emplace an object property - inline auto emplace(const Key &key, mapped_type &&value) -> hash_type { + auto emplace(const Key &key, mapped_type &&value) -> hash_type { const auto key_hash{this->hash(key)}; - if (this->hasher.is_perfect(key_hash)) { - for (auto &entry : this->data) { + if (this->HASHER.is_perfect(key_hash)) { + for (auto &entry : this->data_) { if (entry.hash == key_hash && entry.first.size() == key.size()) { entry.second = std::move(value); return key_hash; } } } else { - for (auto &entry : this->data) { + for (auto &entry : this->data_) { if (entry.hash == key_hash && entry.first == key) { entry.second = std::move(value); return key_hash; @@ -658,23 +653,23 @@ template class JSONObject { } } - this->data.push_back({key, std::move(value), key_hash}); + this->data_.push_back({key, std::move(value), key_hash}); return key_hash; } /// Emplace an object property - inline auto emplace(const Key &key, const mapped_type &value) -> hash_type { + auto emplace(const Key &key, const mapped_type &value) -> hash_type { const auto key_hash{this->hash(key)}; - if (this->hasher.is_perfect(key_hash)) { - for (auto &entry : this->data) { + if (this->HASHER.is_perfect(key_hash)) { + for (auto &entry : this->data_) { if (entry.hash == key_hash && entry.first.size() == key.size()) { entry.second = value; return key_hash; } } } else { - for (auto &entry : this->data) { + for (auto &entry : this->data_) { if (entry.hash == key_hash && entry.first == key) { entry.second = value; return key_hash; @@ -682,66 +677,65 @@ template class JSONObject { } } - this->data.push_back({key, value, key_hash}); + this->data_.push_back({key, value, key_hash}); return key_hash; } /// Emplace an object property assuming the key does not already exist - inline auto emplace_assume_new(Key &&key, mapped_type &&value) -> hash_type { + auto emplace_assume_new(Key &&key, mapped_type &&value) -> hash_type { const auto key_hash{this->hash(key)}; - this->data.push_back({std::move(key), std::move(value), key_hash}); + this->data_.push_back({std::move(key), std::move(value), key_hash}); return key_hash; } /// Emplace an object property assuming the key does not already exist - inline auto emplace_assume_new(const Key &key, mapped_type &&value) - -> hash_type { + auto emplace_assume_new(const Key &key, mapped_type &&value) -> hash_type { const auto key_hash{this->hash(key)}; - this->data.push_back({key, std::move(value), key_hash}); + this->data_.push_back({key, std::move(value), key_hash}); return key_hash; } /// Emplace an object property with a pre-computed hash, returning the /// inserted value - inline auto emplace_assume_new(Key &&key, mapped_type &&value, - const hash_type key_hash) -> mapped_type & { - this->data.push_back({std::move(key), std::move(value), key_hash}); - return this->data.back().second; + auto emplace_assume_new(Key &&key, mapped_type &&value, + const hash_type key_hash) -> mapped_type & { + this->data_.push_back({std::move(key), std::move(value), key_hash}); + return this->data_.back().second; } /// Emplace an object property with a pre-computed hash - inline auto emplace_assume_new(const Key &key, mapped_type &&value, - const hash_type key_hash) -> void { - this->data.push_back({key, std::move(value), key_hash}); + auto emplace_assume_new(const Key &key, mapped_type &&value, + const hash_type key_hash) -> void { + this->data_.push_back({key, std::move(value), key_hash}); } /// Get the key of the last-inserted property - [[nodiscard]] inline auto back_key() const noexcept -> const Key & { - assert(!this->data.empty()); - return this->data.back().first; + [[nodiscard]] auto back_key() const noexcept -> const Key & { + assert(!this->data_.empty()); + return this->data_.back().first; } /// Remove every property in the object - inline auto clear() noexcept -> void { this->data.clear(); } + auto clear() noexcept -> void { this->data_.clear(); } /// Rename an object property in place - auto rename(const Key &key, const hash_type key_hash, Key &&to, - const hash_type to_hash) -> void { - this->erase(to, to_hash); + auto rename(const Key &key, const hash_type key_hash, Key &&target, + const hash_type target_hash) -> void { + this->erase(target, target_hash); - if (this->hasher.is_perfect(key_hash)) { - for (auto &entry : this->data) { + if (this->HASHER.is_perfect(key_hash)) { + for (auto &entry : this->data_) { if (entry.hash == key_hash && entry.first.size() == key.size()) { - entry.first = std::move(to); - entry.hash = to_hash; + entry.first = std::move(target); + entry.hash = target_hash; break; } } } else { - for (auto &entry : this->data) { + for (auto &entry : this->data_) { if (entry.hash == key_hash && entry.first == key) { - entry.first = std::move(to); - entry.hash = to_hash; + entry.first = std::move(target); + entry.hash = target_hash; break; } } @@ -752,20 +746,20 @@ template class JSONObject { auto erase(const Key &key, const hash_type key_hash) -> size_type { const auto current_size{this->size()}; - if (this->hasher.is_perfect(key_hash)) { - for (auto iterator = this->data.begin(); iterator != this->data.end(); + if (this->HASHER.is_perfect(key_hash)) { + for (auto iterator = this->data_.begin(); iterator != this->data_.end(); ++iterator) { if (iterator->hash == key_hash && iterator->first.size() == key.size()) { - this->data.erase(iterator); + this->data_.erase(iterator); return current_size - 1; } } } else { - for (auto iterator = this->data.begin(); iterator != this->data.end(); + for (auto iterator = this->data_.begin(); iterator != this->data_.end(); ++iterator) { if (iterator->hash == key_hash && iterator->first == key) { - this->data.erase(iterator); + this->data_.erase(iterator); return current_size - 1; } } @@ -780,20 +774,20 @@ template class JSONObject { auto erase(T key, const hash_type key_hash) -> size_type { const auto current_size{this->size()}; - if (this->hasher.is_perfect(key_hash)) { - for (auto iterator = this->data.begin(); iterator != this->data.end(); + if (this->HASHER.is_perfect(key_hash)) { + for (auto iterator = this->data_.begin(); iterator != this->data_.end(); ++iterator) { if (iterator->hash == key_hash && iterator->first.size() == key.size()) { - this->data.erase(iterator); + this->data_.erase(iterator); return current_size - 1; } } } else { - for (auto iterator = this->data.begin(); iterator != this->data.end(); + for (auto iterator = this->data_.begin(); iterator != this->data_.end(); ++iterator) { if (iterator->hash == key_hash && iterator->first == key) { - this->data.erase(iterator); + this->data_.erase(iterator); return current_size - 1; } } @@ -803,20 +797,20 @@ template class JSONObject { } /// Erase an object property - inline auto erase(const Key &key) -> size_type { + auto erase(const Key &key) -> size_type { return this->erase(key, this->hash(key)); } /// Erase an object property template requires std::same_as, KeyView> - inline auto erase(T key) -> size_type { + auto erase(T key) -> size_type { return this->erase(key, this->hash(key)); } /// Reorder object properties by keys according to a comparator function template auto reorder(const Compare &compare) -> void { - std::sort(this->data.begin(), this->data.end(), + std::sort(this->data_.begin(), this->data_.end(), [&compare](const auto &left, const auto &right) -> auto { return compare(left.first, right.first); }); @@ -830,8 +824,8 @@ template class JSONObject { #if defined(_MSC_VER) #pragma warning(disable : 4251) #endif - static constexpr Hash hasher{}; - underlying_type data; + static constexpr Hash HASHER{}; + underlying_type data_; #if defined(_MSC_VER) #pragma warning(default : 4251) #endif diff --git a/vendor/core/src/core/json/include/sourcemeta/core/json_value.h b/vendor/core/src/core/json/include/sourcemeta/core/json_value.h index f11afb98..bb90649f 100644 --- a/vendor/core/src/core/json/include/sourcemeta/core/json_value.h +++ b/vendor/core/src/core/json/include/sourcemeta/core/json_value.h @@ -148,7 +148,7 @@ class SOURCEMETA_CORE_JSON_EXPORT JSON { template explicit JSON(const long value) requires(!std::is_same_v) - : current_type{Type::Integer} { + : current_type_{Type::Integer} { this->data_integer = value; } @@ -244,7 +244,7 @@ class SOURCEMETA_CORE_JSON_EXPORT JSON { /// /// assert(my_object.is_object()); /// ``` - explicit JSON(std::initializer_list values); + explicit JSON(std::initializer_list values); /// A copy constructor for the object type. explicit JSON(const Object &value); @@ -256,11 +256,11 @@ class SOURCEMETA_CORE_JSON_EXPORT JSON { explicit JSON(Decimal &&value); /// Misc constructors - JSON(const JSON &); + JSON(const JSON &other); /// A move constructor. - JSON(JSON &&) noexcept; - auto operator=(const JSON &) -> JSON &; - auto operator=(JSON &&) noexcept -> JSON &; + JSON(JSON &&other) noexcept; + auto operator=(const JSON &other) -> JSON &; + auto operator=(JSON &&other) noexcept -> JSON &; /// Destructor ~JSON(); @@ -430,9 +430,9 @@ class SOURCEMETA_CORE_JSON_EXPORT JSON { /// const sourcemeta::core::JSON document{true}; /// assert(document.is_boolean()); /// ``` - [[nodiscard]] SOURCEMETA_FORCEINLINE inline auto is_boolean() const noexcept + [[nodiscard]] SOURCEMETA_FORCEINLINE auto is_boolean() const noexcept -> bool { - return this->current_type == Type::Boolean; + return this->current_type_ == Type::Boolean; } /// Check if the input JSON document is null. For example: @@ -444,9 +444,8 @@ class SOURCEMETA_CORE_JSON_EXPORT JSON { /// const sourcemeta::core::JSON document{nullptr}; /// assert(document.is_null()); /// ``` - [[nodiscard]] SOURCEMETA_FORCEINLINE inline auto is_null() const noexcept - -> bool { - return this->current_type == Type::Null; + [[nodiscard]] SOURCEMETA_FORCEINLINE auto is_null() const noexcept -> bool { + return this->current_type_ == Type::Null; } /// Check if the input JSON document is an integer. For example: @@ -458,9 +457,9 @@ class SOURCEMETA_CORE_JSON_EXPORT JSON { /// const sourcemeta::core::JSON document{5}; /// assert(document.is_integer()); /// ``` - [[nodiscard]] SOURCEMETA_FORCEINLINE inline auto is_integer() const noexcept + [[nodiscard]] SOURCEMETA_FORCEINLINE auto is_integer() const noexcept -> bool { - return this->current_type == Type::Integer; + return this->current_type_ == Type::Integer; } /// Check if the input JSON document is a real type. For example: @@ -472,9 +471,8 @@ class SOURCEMETA_CORE_JSON_EXPORT JSON { /// const sourcemeta::core::JSON document{3.14}; /// assert(document.is_real()); /// ``` - [[nodiscard]] SOURCEMETA_FORCEINLINE inline auto is_real() const noexcept - -> bool { - return this->current_type == Type::Real; + [[nodiscard]] SOURCEMETA_FORCEINLINE auto is_real() const noexcept -> bool { + return this->current_type_ == Type::Real; } /// Check if the input JSON document is an integer, a real number that @@ -487,7 +485,7 @@ class SOURCEMETA_CORE_JSON_EXPORT JSON { /// const sourcemeta::core::JSON document{5.0}; /// assert(document.is_integral()); /// ``` - [[nodiscard]] SOURCEMETA_FORCEINLINE inline auto is_integral() const noexcept + [[nodiscard]] SOURCEMETA_FORCEINLINE auto is_integral() const noexcept -> bool { switch (this->type()) { case Type::Integer: @@ -515,8 +513,7 @@ class SOURCEMETA_CORE_JSON_EXPORT JSON { /// assert(real.is_number()); /// assert(integer.is_number()); /// ``` - [[nodiscard]] SOURCEMETA_FORCEINLINE inline auto is_number() const noexcept - -> bool { + [[nodiscard]] SOURCEMETA_FORCEINLINE auto is_number() const noexcept -> bool { return this->is_integer() || this->is_real() || this->is_decimal(); } @@ -543,9 +540,8 @@ class SOURCEMETA_CORE_JSON_EXPORT JSON { /// const sourcemeta::core::JSON document{"foo"}; /// assert(document.is_string()); /// ``` - [[nodiscard]] SOURCEMETA_FORCEINLINE inline auto is_string() const noexcept - -> bool { - return this->current_type == Type::String; + [[nodiscard]] SOURCEMETA_FORCEINLINE auto is_string() const noexcept -> bool { + return this->current_type_ == Type::String; } /// Check if the input JSON document is an array. For example: @@ -558,9 +554,8 @@ class SOURCEMETA_CORE_JSON_EXPORT JSON { /// document=sourcemeta::core::parse_json("[ 1, 2, 3 ]"); /// assert(document.is_array()); /// ``` - [[nodiscard]] SOURCEMETA_FORCEINLINE inline auto is_array() const noexcept - -> bool { - return this->current_type == Type::Array; + [[nodiscard]] SOURCEMETA_FORCEINLINE auto is_array() const noexcept -> bool { + return this->current_type_ == Type::Array; } /// Check if the input JSON document is an object. For example: @@ -573,9 +568,8 @@ class SOURCEMETA_CORE_JSON_EXPORT JSON { /// document=sourcemeta::core::parse_json("{ \"foo\": 1 }"); /// assert(document.is_object()); /// ``` - [[nodiscard]] SOURCEMETA_FORCEINLINE inline auto is_object() const noexcept - -> bool { - return this->current_type == Type::Object; + [[nodiscard]] SOURCEMETA_FORCEINLINE auto is_object() const noexcept -> bool { + return this->current_type_ == Type::Object; } /// Check if the input JSON document is an arbitrary precision decimal value. @@ -589,9 +583,9 @@ class SOURCEMETA_CORE_JSON_EXPORT JSON { /// const sourcemeta::core::JSON document{value}; /// assert(document.is_decimal()); /// ``` - [[nodiscard]] SOURCEMETA_FORCEINLINE inline auto is_decimal() const noexcept + [[nodiscard]] SOURCEMETA_FORCEINLINE auto is_decimal() const noexcept -> bool { - return this->current_type == Type::Decimal; + return this->current_type_ == Type::Decimal; } /// Get the type of the JSON document. For example: @@ -603,9 +597,8 @@ class SOURCEMETA_CORE_JSON_EXPORT JSON { /// const sourcemeta::core::JSON document{true}; /// assert(document.type() == sourcemeta::core::JSON::Type::Boolean); /// ``` - [[nodiscard]] SOURCEMETA_FORCEINLINE inline auto type() const noexcept - -> Type { - return this->current_type; + [[nodiscard]] SOURCEMETA_FORCEINLINE auto type() const noexcept -> Type { + return this->current_type_; } /* @@ -623,7 +616,7 @@ class SOURCEMETA_CORE_JSON_EXPORT JSON { /// assert(document.is_boolean()); /// assert(document.to_boolean()); /// ``` - [[nodiscard]] SOURCEMETA_FORCEINLINE inline auto to_boolean() const noexcept + [[nodiscard]] SOURCEMETA_FORCEINLINE auto to_boolean() const noexcept -> bool { assert(this->is_boolean()); return this->data_boolean; @@ -641,7 +634,7 @@ class SOURCEMETA_CORE_JSON_EXPORT JSON { /// assert(document.is_integer()); /// assert(document.to_integer() == 5); /// ``` - [[nodiscard]] SOURCEMETA_FORCEINLINE inline auto to_integer() const noexcept + [[nodiscard]] SOURCEMETA_FORCEINLINE auto to_integer() const noexcept -> Integer { assert(this->is_integer()); return this->data_integer; @@ -659,8 +652,7 @@ class SOURCEMETA_CORE_JSON_EXPORT JSON { /// assert(document.is_real()); /// assert(document.to_real() == 3.14); /// ``` - [[nodiscard]] SOURCEMETA_FORCEINLINE inline auto to_real() const noexcept - -> Real { + [[nodiscard]] SOURCEMETA_FORCEINLINE auto to_real() const noexcept -> Real { assert(this->is_real()); assert(!std::isinf(this->data_real)); assert(!std::isnan(this->data_real)); @@ -679,7 +671,7 @@ class SOURCEMETA_CORE_JSON_EXPORT JSON { /// assert(document.is_decimal()); /// assert(document.to_decimal().to_int64() == 1234567890); /// ``` - [[nodiscard]] SOURCEMETA_FORCEINLINE inline auto to_decimal() const noexcept + [[nodiscard]] SOURCEMETA_FORCEINLINE auto to_decimal() const noexcept -> const Decimal & { assert(this->is_decimal()); assert(this->data_decimal.is_finite()); @@ -699,7 +691,7 @@ class SOURCEMETA_CORE_JSON_EXPORT JSON { /// assert(document.is_string()); /// assert(document.to_string() == "foo"); /// ``` - [[nodiscard]] SOURCEMETA_FORCEINLINE inline auto to_string() const noexcept + [[nodiscard]] SOURCEMETA_FORCEINLINE auto to_string() const noexcept -> const String & { assert(this->is_string()); return this->data_string; @@ -742,7 +734,7 @@ class SOURCEMETA_CORE_JSON_EXPORT JSON { // TODO: Merge const/non-const overloads of as_array, as_object, at, front, // back using deducing this once Apple Clang supports it // (__cpp_explicit_this_parameter) - [[nodiscard]] SOURCEMETA_FORCEINLINE inline auto as_array() const noexcept + [[nodiscard]] SOURCEMETA_FORCEINLINE auto as_array() const noexcept -> const Array & { assert(this->is_array()); return this->data_array; @@ -760,8 +752,7 @@ class SOURCEMETA_CORE_JSON_EXPORT JSON { /// sourcemeta::core::parse_json("[ 1, 2, 3 ]"); /// std::sort(document.as_array().begin(), document.as_array().end()); /// ``` - [[nodiscard]] SOURCEMETA_FORCEINLINE inline auto as_array() noexcept - -> Array & { + [[nodiscard]] SOURCEMETA_FORCEINLINE auto as_array() noexcept -> Array & { assert(this->is_array()); return this->data_array; } @@ -788,8 +779,7 @@ class SOURCEMETA_CORE_JSON_EXPORT JSON { /// << "\n"; /// }); /// ``` - [[nodiscard]] SOURCEMETA_FORCEINLINE inline auto as_object() noexcept - -> Object & { + [[nodiscard]] SOURCEMETA_FORCEINLINE auto as_object() noexcept -> Object & { assert(this->is_object()); return this->data_object; } @@ -812,7 +802,7 @@ class SOURCEMETA_CORE_JSON_EXPORT JSON { /// value += sourcemeta::core::JSON{1}; /// } /// ``` - [[nodiscard]] SOURCEMETA_FORCEINLINE inline auto as_object() const noexcept + [[nodiscard]] SOURCEMETA_FORCEINLINE auto as_object() const noexcept -> const Object & { assert(this->is_object()); return this->data_object; @@ -830,15 +820,15 @@ class SOURCEMETA_CORE_JSON_EXPORT JSON { /// const sourcemeta::core::JSON document{5}; /// assert(document.as_real() == 5.0); /// ``` - [[nodiscard]] SOURCEMETA_FORCEINLINE inline auto as_real() const -> Real { + [[nodiscard]] SOURCEMETA_FORCEINLINE auto as_real() const -> Real { assert(this->is_number()); if (this->is_real()) { return this->to_real(); - } else if (this->is_integer()) { + } + if (this->is_integer()) { return static_cast(this->to_integer()); - } else { - return this->to_decimal().to_double(); } + return this->to_decimal().to_double(); } /// Get the JSON numeric document as an integer number if it is not one @@ -853,12 +843,12 @@ class SOURCEMETA_CORE_JSON_EXPORT JSON { /// const sourcemeta::core::JSON document{5.3}; /// assert(document.as_integer() == 5); /// ``` - [[nodiscard]] SOURCEMETA_FORCEINLINE inline auto as_integer() const - -> Integer { + [[nodiscard]] SOURCEMETA_FORCEINLINE auto as_integer() const -> Integer { assert(this->is_number()); if (this->is_integer()) { return this->to_integer(); - } else if (this->is_real()) { + } + if (this->is_real()) { const auto truncated{std::trunc(this->to_real())}; if (truncated < static_cast(std::numeric_limits::min()) || truncated >= static_cast(std::numeric_limits::max())) { @@ -867,15 +857,14 @@ class SOURCEMETA_CORE_JSON_EXPORT JSON { } return static_cast(truncated); - } else { - const auto integral{this->to_decimal().to_integral()}; - if (!integral.is_int64()) { - throw std::out_of_range{ - "The decimal number does not fit in a 64-bit integer"}; - } - - return integral.to_int64(); } + const auto integral{this->to_decimal().to_integral()}; + if (!integral.is_int64()) { + throw std::out_of_range{ + "The decimal number does not fit in a 64-bit integer"}; + } + + return integral.to_int64(); } /* @@ -900,11 +889,11 @@ class SOURCEMETA_CORE_JSON_EXPORT JSON { /// sourcemeta::core::parse_json("{ \"1\": "foo" }"); /// assert(my_array.at(1).to_string() == "foo"); /// ``` - [[nodiscard]] SOURCEMETA_FORCEINLINE inline auto - at(const typename Array::size_type index) const -> const JSON & { + [[nodiscard]] SOURCEMETA_FORCEINLINE auto + at(const Array::size_type index) const -> const JSON & { assert(this->is_array()); assert(index < this->size()); - return this->data_array.data.at(index); + return this->data_array.data_.at(index); } /// This method retrieves a element by its index. If the input JSON instance @@ -925,11 +914,11 @@ class SOURCEMETA_CORE_JSON_EXPORT JSON { /// sourcemeta::core::parse_json("{ \"1\": "foo" }"); /// assert(my_array.at(1).to_string() == "foo"); /// ``` - [[nodiscard]] SOURCEMETA_FORCEINLINE inline auto - at(const typename Array::size_type index) -> JSON & { + [[nodiscard]] SOURCEMETA_FORCEINLINE auto at(const Array::size_type index) + -> JSON & { assert(this->is_array()); assert(index < this->size()); - return this->data_array.data.at(index); + return this->data_array.data_.at(index); } /// This method retrieves an object element. @@ -944,7 +933,7 @@ class SOURCEMETA_CORE_JSON_EXPORT JSON { /// sourcemeta::core::parse_json("{ \"foo\": 1, \"bar\": 2 }"); /// assert(my_object.at("bar").to_integer() == 2); /// ``` - [[nodiscard]] SOURCEMETA_FORCEINLINE inline auto at(const String &key) const + [[nodiscard]] SOURCEMETA_FORCEINLINE auto at(const String &key) const -> const JSON & { assert(this->is_object()); assert(this->defines(key)); @@ -955,8 +944,7 @@ class SOURCEMETA_CORE_JSON_EXPORT JSON { /// This method retrieves an object element by string view key template requires std::same_as, StringView> - [[nodiscard]] SOURCEMETA_FORCEINLINE inline auto at(T key) const - -> const JSON & { + [[nodiscard]] SOURCEMETA_FORCEINLINE auto at(T key) const -> const JSON & { assert(this->is_object()); assert(this->defines(key)); const auto &object{this->data_object}; @@ -977,9 +965,8 @@ class SOURCEMETA_CORE_JSON_EXPORT JSON { /// assert(my_object.at("bar", /// my_object.as_object().hash("bar")).to_integer() == 2); /// ``` - [[nodiscard]] SOURCEMETA_FORCEINLINE inline auto - at(const String &key, const typename Object::hash_type hash) const - -> const JSON & { + [[nodiscard]] SOURCEMETA_FORCEINLINE auto + at(const String &key, const Object::hash_type hash) const -> const JSON & { assert(this->is_object()); assert(this->defines(key)); return this->data_object.at(key, hash); @@ -989,8 +976,8 @@ class SOURCEMETA_CORE_JSON_EXPORT JSON { /// pre-calculated property hash template requires std::same_as, StringView> - [[nodiscard]] SOURCEMETA_FORCEINLINE inline auto - at(T key, const typename Object::hash_type hash) const -> const JSON & { + [[nodiscard]] SOURCEMETA_FORCEINLINE auto + at(T key, const Object::hash_type hash) const -> const JSON & { assert(this->is_object()); assert(this->defines(key)); return this->data_object.at(key, hash); @@ -1008,8 +995,7 @@ class SOURCEMETA_CORE_JSON_EXPORT JSON { /// sourcemeta::core::parse_json("{ \"foo\": 1, \"bar\": 2 }"); /// assert(my_object.at("bar").to_integer() == 2); /// ``` - [[nodiscard]] SOURCEMETA_FORCEINLINE inline auto at(const String &key) - -> JSON & { + [[nodiscard]] SOURCEMETA_FORCEINLINE auto at(const String &key) -> JSON & { assert(this->is_object()); assert(this->defines(key)); auto &object{this->data_object}; @@ -1019,7 +1005,7 @@ class SOURCEMETA_CORE_JSON_EXPORT JSON { /// This method retrieves an object element by string view key template requires std::same_as, StringView> - [[nodiscard]] SOURCEMETA_FORCEINLINE inline auto at(T key) -> JSON & { + [[nodiscard]] SOURCEMETA_FORCEINLINE auto at(T key) -> JSON & { assert(this->is_object()); assert(this->defines(key)); auto &object{this->data_object}; @@ -1040,8 +1026,9 @@ class SOURCEMETA_CORE_JSON_EXPORT JSON { /// assert(my_object.at("bar", /// my_object.as_object().hash("bar")).to_integer() == 2); /// ``` - [[nodiscard]] SOURCEMETA_FORCEINLINE inline auto - at(const String &key, const typename Object::hash_type hash) -> JSON & { + [[nodiscard]] SOURCEMETA_FORCEINLINE auto at(const String &key, + const Object::hash_type hash) + -> JSON & { assert(this->is_object()); assert(this->defines(key)); return this->data_object.at(key, hash); @@ -1051,8 +1038,9 @@ class SOURCEMETA_CORE_JSON_EXPORT JSON { /// pre-calculated property hash template requires std::same_as, StringView> - [[nodiscard]] SOURCEMETA_FORCEINLINE inline auto - at(T key, const typename Object::hash_type hash) -> JSON & { + [[nodiscard]] SOURCEMETA_FORCEINLINE auto at(T key, + const Object::hash_type hash) + -> JSON & { assert(this->is_object()); assert(this->defines(key)); return this->data_object.at(key, hash); @@ -1096,14 +1084,12 @@ class SOURCEMETA_CORE_JSON_EXPORT JSON { /// my_object.as_object().hash("foo"), /// default_value).to_integer() == 1); /// ``` - [[nodiscard]] auto at_or(const String &key, - const typename Object::hash_type hash, + [[nodiscard]] auto at_or(const String &key, const Object::hash_type hash, const JSON &otherwise) const -> const JSON &; /// This overload avoids misuses of returning a const reference parameter as a /// constant reference. - [[nodiscard]] auto at_or(const String &key, - const typename Object::hash_type hash, + [[nodiscard]] auto at_or(const String &key, const Object::hash_type hash, JSON &&otherwise) const -> const JSON & = delete; /// This method retrieves a reference to the first element of a JSON array. @@ -1118,10 +1104,10 @@ class SOURCEMETA_CORE_JSON_EXPORT JSON { /// sourcemeta::core::parse_json("[ 1, 2, 3 ]"); /// assert(document.front().to_integer() == 1); /// ``` - [[nodiscard]] SOURCEMETA_FORCEINLINE inline auto front() -> JSON & { + [[nodiscard]] SOURCEMETA_FORCEINLINE auto front() -> JSON & { assert(this->is_array()); assert(!this->empty()); - return this->data_array.data.front(); + return this->data_array.data_.front(); } /// This method retrieves a reference to the first element of a JSON array. @@ -1136,11 +1122,10 @@ class SOURCEMETA_CORE_JSON_EXPORT JSON { /// sourcemeta::core::parse_json("[ 1, 2, 3 ]"); /// assert(document.front().to_integer() == 1); /// ``` - [[nodiscard]] SOURCEMETA_FORCEINLINE inline auto front() const - -> const JSON & { + [[nodiscard]] SOURCEMETA_FORCEINLINE auto front() const -> const JSON & { assert(this->is_array()); assert(!this->empty()); - return this->data_array.data.front(); + return this->data_array.data_.front(); } /// This method retrieves a reference to the last element of a JSON array. @@ -1155,10 +1140,10 @@ class SOURCEMETA_CORE_JSON_EXPORT JSON { /// sourcemeta::core::parse_json("[ 1, 2, 3 ]"); /// assert(document.back().to_integer() == 3); /// ``` - [[nodiscard]] SOURCEMETA_FORCEINLINE inline auto back() -> JSON & { + [[nodiscard]] SOURCEMETA_FORCEINLINE auto back() -> JSON & { assert(this->is_array()); assert(!this->empty()); - return this->data_array.data.back(); + return this->data_array.data_.back(); } /// This method retrieves a reference to the last element of a JSON array. @@ -1173,11 +1158,10 @@ class SOURCEMETA_CORE_JSON_EXPORT JSON { /// sourcemeta::core::parse_json("[ 1, 2, 3 ]"); /// assert(document.back().to_integer() == 3); /// ``` - [[nodiscard]] SOURCEMETA_FORCEINLINE inline auto back() const - -> const JSON & { + [[nodiscard]] SOURCEMETA_FORCEINLINE auto back() const -> const JSON & { assert(this->is_array()); assert(!this->empty()); - return this->data_array.data.back(); + return this->data_array.data_.back(); } /* @@ -1204,14 +1188,14 @@ class SOURCEMETA_CORE_JSON_EXPORT JSON { /// assert(my_array.size() == 2); /// assert(my_string.size() == 3); /// ``` - [[nodiscard]] SOURCEMETA_FORCEINLINE inline auto size() const -> std::size_t { + [[nodiscard]] SOURCEMETA_FORCEINLINE auto size() const -> std::size_t { if (this->is_object()) { return this->object_size(); - } else if (this->is_array()) { + } + if (this->is_array()) { return this->array_size(); - } else { - return this->string_size(); } + return this->string_size(); } /// If the input JSON instance is a string, return its logical length. @@ -1225,8 +1209,7 @@ class SOURCEMETA_CORE_JSON_EXPORT JSON { /// const sourcemeta::core::JSON my_string{"foo"}; /// assert(my_string.string_size() == 3); /// ``` - [[nodiscard]] SOURCEMETA_FORCEINLINE inline auto string_size() const - -> std::size_t { + [[nodiscard]] SOURCEMETA_FORCEINLINE auto string_size() const -> std::size_t { assert(this->is_string()); return JSON::size(this->data_string); } @@ -1243,10 +1226,9 @@ class SOURCEMETA_CORE_JSON_EXPORT JSON { /// sourcemeta::core::parse_json("[ 1, 2 ]"); /// assert(my_array.array_size() == 2); /// ``` - [[nodiscard]] SOURCEMETA_FORCEINLINE inline auto array_size() const - -> std::size_t { + [[nodiscard]] SOURCEMETA_FORCEINLINE auto array_size() const -> std::size_t { assert(this->is_array()); - return this->data_array.data.size(); + return this->data_array.data_.size(); } /// If the input JSON instance is an object, return its number of pairs. @@ -1261,8 +1243,7 @@ class SOURCEMETA_CORE_JSON_EXPORT JSON { /// sourcemeta::core::parse_json("{ \"foo\": 1 }"); /// assert(my_object.object_size() == 1); /// ``` - [[nodiscard]] SOURCEMETA_FORCEINLINE inline auto object_size() const - -> std::size_t { + [[nodiscard]] SOURCEMETA_FORCEINLINE auto object_size() const -> std::size_t { assert(this->is_object()); return this->data_object.size(); } @@ -1279,8 +1260,7 @@ class SOURCEMETA_CORE_JSON_EXPORT JSON { /// assert(my_string.size() == 1); /// assert(my_string.byte_size() == 4); /// ``` - [[nodiscard]] SOURCEMETA_FORCEINLINE inline auto byte_size() const - -> std::size_t { + [[nodiscard]] SOURCEMETA_FORCEINLINE auto byte_size() const -> std::size_t { assert(this->is_string()); return this->data_string.size(); } @@ -1351,14 +1331,14 @@ class SOURCEMETA_CORE_JSON_EXPORT JSON { /// assert(my_array.empty()); /// assert(my_string.empty()); /// ``` - [[nodiscard]] SOURCEMETA_FORCEINLINE inline auto empty() const -> bool { + [[nodiscard]] SOURCEMETA_FORCEINLINE auto empty() const -> bool { if (this->is_object()) { return this->data_object.empty(); - } else if (this->is_array()) { - return this->data_array.data.empty(); - } else { - return this->data_string.empty(); } + if (this->is_array()) { + return this->data_array.data_.empty(); + } + return this->data_string.empty(); } /// This method checks whether an input JSON object defines a specific key @@ -1375,8 +1355,8 @@ class SOURCEMETA_CORE_JSON_EXPORT JSON { /// EXPECT_TRUE(result); /// EXPECT_EQ(result->to_integer(), 1); /// ``` - [[nodiscard]] SOURCEMETA_FORCEINLINE inline auto - try_at(const String &key) const -> const JSON * { + [[nodiscard]] SOURCEMETA_FORCEINLINE auto try_at(const String &key) const + -> const JSON * { assert(this->is_object()); const auto &object{this->data_object}; return object.try_at(key, object.hash(key)); @@ -1385,7 +1365,7 @@ class SOURCEMETA_CORE_JSON_EXPORT JSON { /// This method tries to retrieve an object element by string view key template requires std::same_as, StringView> - [[nodiscard]] SOURCEMETA_FORCEINLINE inline auto try_at(T key) const + [[nodiscard]] SOURCEMETA_FORCEINLINE auto try_at(T key) const -> const JSON * { assert(this->is_object()); const auto &object{this->data_object}; @@ -1408,8 +1388,8 @@ class SOURCEMETA_CORE_JSON_EXPORT JSON { /// EXPECT_TRUE(result); /// EXPECT_EQ(result->to_integer(), 1); /// ``` - [[nodiscard]] SOURCEMETA_FORCEINLINE inline auto - try_at(const String &key, const typename Object::hash_type hash) const + [[nodiscard]] SOURCEMETA_FORCEINLINE auto + try_at(const String &key, const Object::hash_type hash) const -> const JSON * { assert(this->is_object()); const auto &object{this->data_object}; @@ -1420,8 +1400,8 @@ class SOURCEMETA_CORE_JSON_EXPORT JSON { /// pre-calculated property hash template requires std::same_as, StringView> - [[nodiscard]] SOURCEMETA_FORCEINLINE inline auto - try_at(T key, const typename Object::hash_type hash) const -> const JSON * { + [[nodiscard]] SOURCEMETA_FORCEINLINE auto + try_at(T key, const Object::hash_type hash) const -> const JSON * { assert(this->is_object()); const auto &object{this->data_object}; return object.try_at(key, hash); @@ -1441,7 +1421,7 @@ class SOURCEMETA_CORE_JSON_EXPORT JSON { /// result->into(sourcemeta::core::JSON{2}); /// assert(document.at("foo").to_integer() == 2); /// ``` - [[nodiscard]] SOURCEMETA_FORCEINLINE inline auto try_at(const String &key) + [[nodiscard]] SOURCEMETA_FORCEINLINE auto try_at(const String &key) -> JSON * { assert(this->is_object()); auto &object{this->data_object}; @@ -1451,7 +1431,7 @@ class SOURCEMETA_CORE_JSON_EXPORT JSON { /// This method tries to retrieve a mutable object element by string view key template requires std::same_as, StringView> - [[nodiscard]] SOURCEMETA_FORCEINLINE inline auto try_at(T key) -> JSON * { + [[nodiscard]] SOURCEMETA_FORCEINLINE auto try_at(T key) -> JSON * { assert(this->is_object()); auto &object{this->data_object}; return object.try_at(key, object.hash(key)); @@ -1472,8 +1452,9 @@ class SOURCEMETA_CORE_JSON_EXPORT JSON { /// result->into(sourcemeta::core::JSON{2}); /// assert(document.at("foo").to_integer() == 2); /// ``` - [[nodiscard]] SOURCEMETA_FORCEINLINE inline auto - try_at(const String &key, const typename Object::hash_type hash) -> JSON * { + [[nodiscard]] SOURCEMETA_FORCEINLINE auto try_at(const String &key, + const Object::hash_type hash) + -> JSON * { assert(this->is_object()); return this->data_object.try_at(key, hash); } @@ -1482,8 +1463,9 @@ class SOURCEMETA_CORE_JSON_EXPORT JSON { /// given a pre-calculated property hash template requires std::same_as, StringView> - [[nodiscard]] SOURCEMETA_FORCEINLINE inline auto - try_at(T key, const typename Object::hash_type hash) -> JSON * { + [[nodiscard]] SOURCEMETA_FORCEINLINE auto try_at(T key, + const Object::hash_type hash) + -> JSON * { assert(this->is_object()); return this->data_object.try_at(key, hash); } @@ -1512,9 +1494,9 @@ class SOURCEMETA_CORE_JSON_EXPORT JSON { /// assert(bar); /// assert(bar->to_integer() == 2); /// ``` - [[nodiscard]] SOURCEMETA_FORCEINLINE inline auto - try_at(const String &key, const typename Object::hash_type hash, - typename Object::size_type &start) const -> const JSON * { + [[nodiscard]] SOURCEMETA_FORCEINLINE auto + try_at(const String &key, const Object::hash_type hash, + Object::size_type &start) const -> const JSON * { assert(this->is_object()); const auto &object{this->data_object}; return object.try_at(key, hash, start); @@ -1524,9 +1506,9 @@ class SOURCEMETA_CORE_JSON_EXPORT JSON { /// scanning from a caller-provided start offset template requires std::same_as, StringView> - [[nodiscard]] SOURCEMETA_FORCEINLINE inline auto - try_at(T key, const typename Object::hash_type hash, - typename Object::size_type &start) const -> const JSON * { + [[nodiscard]] SOURCEMETA_FORCEINLINE auto + try_at(T key, const Object::hash_type hash, Object::size_type &start) const + -> const JSON * { assert(this->is_object()); const auto &object{this->data_object}; return object.try_at(key, hash, start); @@ -1544,8 +1526,8 @@ class SOURCEMETA_CORE_JSON_EXPORT JSON { /// assert(document.defines("foo")); /// assert(!document.defines("bar")); /// ``` - [[nodiscard]] SOURCEMETA_FORCEINLINE inline auto - defines(const String &key) const -> bool { + [[nodiscard]] SOURCEMETA_FORCEINLINE auto defines(const String &key) const + -> bool { assert(this->is_object()); const auto &object{this->data_object}; return object.defines(key, object.hash(key)); @@ -1555,8 +1537,7 @@ class SOURCEMETA_CORE_JSON_EXPORT JSON { /// string view key template requires std::same_as, StringView> - [[nodiscard]] SOURCEMETA_FORCEINLINE inline auto defines(T key) const - -> bool { + [[nodiscard]] SOURCEMETA_FORCEINLINE auto defines(T key) const -> bool { assert(this->is_object()); const auto &object{this->data_object}; return object.defines(key, object.hash(key)); @@ -1576,9 +1557,8 @@ class SOURCEMETA_CORE_JSON_EXPORT JSON { /// assert(document.defines("bar", /// document.as_object().hash("bar"))); /// ``` - [[nodiscard]] SOURCEMETA_FORCEINLINE inline auto - defines(const String &key, const typename Object::hash_type hash) const - -> bool { + [[nodiscard]] SOURCEMETA_FORCEINLINE auto + defines(const String &key, const Object::hash_type hash) const -> bool { assert(this->is_object()); return this->data_object.defines(key, hash); } @@ -1587,8 +1567,8 @@ class SOURCEMETA_CORE_JSON_EXPORT JSON { /// string view key given a pre-calculated property hash template requires std::same_as, StringView> - [[nodiscard]] SOURCEMETA_FORCEINLINE inline auto - defines(T key, const typename Object::hash_type hash) const -> bool { + [[nodiscard]] SOURCEMETA_FORCEINLINE auto + defines(T key, const Object::hash_type hash) const -> bool { assert(this->is_object()); return this->data_object.defines(key, hash); } @@ -1605,8 +1585,8 @@ class SOURCEMETA_CORE_JSON_EXPORT JSON { /// assert(document.defines(0)); /// assert(!document.defines(1)); /// ``` - [[nodiscard]] SOURCEMETA_FORCEINLINE inline auto - defines(const typename Array::size_type index) const -> bool { + [[nodiscard]] SOURCEMETA_FORCEINLINE auto + defines(const Array::size_type index) const -> bool { return this->defines(std::to_string(index)); } @@ -1689,9 +1669,8 @@ class SOURCEMETA_CORE_JSON_EXPORT JSON { /// assert(document.array_member_contains("tags", /// document.as_object().hash("tags"), "b")); /// ``` - [[nodiscard]] SOURCEMETA_FORCEINLINE inline auto - array_member_contains(const StringView key, - const typename Object::hash_type hash, + [[nodiscard]] SOURCEMETA_FORCEINLINE auto + array_member_contains(const StringView key, const Object::hash_type hash, const StringView value) const -> bool { assert(this->is_object()); const auto *member{this->try_at(key, hash)}; @@ -1709,7 +1688,7 @@ class SOURCEMETA_CORE_JSON_EXPORT JSON { /// sourcemeta::core::parse_json(R"JSON({ "tags": [ "a", "b" ] })JSON"); /// assert(document.array_member_contains("tags", "b")); /// ``` - [[nodiscard]] SOURCEMETA_FORCEINLINE inline auto + [[nodiscard]] SOURCEMETA_FORCEINLINE auto array_member_contains(const StringView key, const StringView value) const -> bool { return this->array_member_contains(key, Object::hash(key), value); @@ -2045,10 +2024,10 @@ class SOURCEMETA_CORE_JSON_EXPORT JSON { /// "bar"); /// assert(document.at("foo").to_string() == "bar"); /// ``` - SOURCEMETA_FORCEINLINE inline auto - assign_if_nonempty(const StringView key, - const typename Object::hash_type hash, - const StringView value) -> void { + SOURCEMETA_FORCEINLINE auto assign_if_nonempty(const StringView key, + const Object::hash_type hash, + const StringView value) + -> void { if (!value.empty()) { this->assign_assume_new(String{key}, JSON{value}, hash); } @@ -2066,8 +2045,8 @@ class SOURCEMETA_CORE_JSON_EXPORT JSON { /// document.assign_if_nonempty("foo", "bar"); /// assert(document.at("foo").to_string() == "bar"); /// ``` - SOURCEMETA_FORCEINLINE inline auto assign_if_nonempty(const StringView key, - const StringView value) + SOURCEMETA_FORCEINLINE auto assign_if_nonempty(const StringView key, + const StringView value) -> void { this->assign_if_nonempty(key, Object::hash(key), value); } @@ -2087,8 +2066,7 @@ class SOURCEMETA_CORE_JSON_EXPORT JSON { /// values); /// assert(document.at("foo").size() == 2); /// ``` - auto assign_if_nonempty(const StringView key, - const typename Object::hash_type hash, + auto assign_if_nonempty(const StringView key, const Object::hash_type hash, const std::span values) -> void { if (values.empty()) { return; @@ -2132,12 +2110,12 @@ class SOURCEMETA_CORE_JSON_EXPORT JSON { /// document.erase("foo"); /// assert(!document.defines("foo")); /// ``` - auto erase(const String &key) -> typename Object::size_type; + auto erase(const String &key) -> Object::size_type; /// This method deletes an object key by string view. template requires std::same_as, StringView> - auto erase(T key) -> typename Object::size_type { + auto erase(T key) -> Object::size_type { assert(this->is_object()); return this->data_object.erase(key); } @@ -2205,8 +2183,7 @@ class SOURCEMETA_CORE_JSON_EXPORT JSON { /// assert(array.at(0), 1); /// assert(array.at(1), 3); /// ``` - auto erase(typename Array::const_iterator position) -> - typename Array::iterator; + auto erase(Array::const_iterator position) -> Array::iterator; /// This method deletes a set of array elements using iterators. For example: /// @@ -2221,8 +2198,8 @@ class SOURCEMETA_CORE_JSON_EXPORT JSON { /// assert(array.size(), 1); /// assert(array.at(0), 1); /// ``` - auto erase(typename Array::const_iterator first, - typename Array::const_iterator last) -> typename Array::iterator; + auto erase(Array::const_iterator first, Array::const_iterator last) + -> Array::iterator; /// This method deletes a set of array elements given a predicate. For /// example: @@ -2432,7 +2409,7 @@ class SOURCEMETA_CORE_JSON_EXPORT JSON { /// assert(document.at("bar").is_boolean()); /// assert(document.at("bar").to_boolean()); /// ``` - auto rename(const JSON::String &key, JSON::String &&to) -> void; + auto rename(const JSON::String &key, JSON::String &&target) -> void; /// This method sets a value to another JSON value by copying it. For example, /// the member of a JSON document can be transformed from a boolean to an @@ -2496,7 +2473,7 @@ class SOURCEMETA_CORE_JSON_EXPORT JSON { auto into_object() -> void; private: - Type current_type = Type::Null; + Type current_type_ = Type::Null; // Exporting symbols that depends on the standard C++ library is considered // safe. diff --git a/vendor/core/src/core/json/json.cc b/vendor/core/src/core/json/json.cc index a07ceb8b..bbb9e2ad 100644 --- a/vendor/core/src/core/json/json.cc +++ b/vendor/core/src/core/json/json.cc @@ -21,18 +21,18 @@ namespace sourcemeta::core { -template +template static auto internal_parse_json(const char *&cursor, const char *end, std::uint64_t &line, std::uint64_t &column, const JSON::ParseCallback &callback, const bool track_positions, JSON &output) - -> std::conditional_t { + -> std::conditional_t { const char *buffer_start{cursor}; // Tape entries address the input with 32-bit offsets and lengths, so a larger // input cannot be represented without truncation if (std::cmp_greater(end - cursor, std::numeric_limits::max())) { - if constexpr (should_throw) { + if constexpr (ShouldThrow) { throw JSONParseError(line, column); } else { return false; @@ -42,7 +42,7 @@ static auto internal_parse_json(const char *&cursor, const char *end, std::vector tape; tape.reserve(static_cast(end - cursor) / 8); - if constexpr (should_throw) { + if constexpr (ShouldThrow) { if (callback || track_positions) { scan_json(cursor, end, buffer_start, line, column, tape); } else { @@ -92,10 +92,10 @@ static auto skip_trailing_whitespace(const char *cursor, const char *end) -> const char * { using namespace internal; while (cursor != end && - (*cursor == token_whitespace_tabulation || - *cursor == token_whitespace_line_feed || - *cursor == token_whitespace_carriage_return || - *cursor == token_whitespace_space)) { + (*cursor == TOKEN_WHITESPACE_TABULATION || + *cursor == TOKEN_WHITESPACE_LINE_FEED || + *cursor == TOKEN_WHITESPACE_CARRIAGE_RETURN || + *cursor == TOKEN_WHITESPACE_SPACE)) { ++cursor; } @@ -111,7 +111,7 @@ static auto position_of(const char *begin, const char *target, line = 1; column = 1; for (const char *iterator{begin}; iterator != target; ++iterator) { - if (*iterator == token_whitespace_line_feed) { + if (*iterator == TOKEN_WHITESPACE_LINE_FEED) { line += 1; column = 1; } else { diff --git a/vendor/core/src/core/json/json_value.cc b/vendor/core/src/core/json/json_value.cc index 8e8d11cb..e0e25aa6 100644 --- a/vendor/core/src/core/json/json_value.cc +++ b/vendor/core/src/core/json/json_value.cc @@ -44,14 +44,14 @@ auto reverse_ordering(const std::strong_ordering ordering) auto integer_real_ordering(const std::int64_t left, const double right) -> std::strong_ordering { // Real values in a JSON document are always finite - constexpr double lowest{-9223372036854775808.0}; - constexpr double past_highest{9223372036854775808.0}; + constexpr double LOWEST{-9223372036854775808.0}; + constexpr double PAST_HIGHEST{9223372036854775808.0}; const double floor_of_right{std::floor(right)}; - if (floor_of_right >= past_highest) { + if (floor_of_right >= PAST_HIGHEST) { return std::strong_ordering::less; } - if (floor_of_right < lowest) { + if (floor_of_right < LOWEST) { return std::strong_ordering::greater; } @@ -112,19 +112,19 @@ auto cross_numeric_ordering(const JSON &left, const JSON &right) } // namespace -JSON::JSON(const std::int64_t value) : current_type{Type::Integer} { +JSON::JSON(const std::int64_t value) : current_type_{Type::Integer} { this->data_integer = value; } -JSON::JSON(const std::size_t value) : current_type{Type::Integer} { +JSON::JSON(const std::size_t value) : current_type_{Type::Integer} { this->data_integer = static_cast(value); } -JSON::JSON(const int value) : current_type{Type::Integer} { +JSON::JSON(const int value) : current_type_{Type::Integer} { this->data_integer = value; } -JSON::JSON(const double value) : current_type{Type::Real} { +JSON::JSON(const double value) : current_type_{Type::Real} { // Numeric values that cannot be represented as sequences of digits (such as // Infinity and NaN) are not permitted. See // https://www.ecma-international.org/wp-content/uploads/ECMA-404_2nd_edition_december_2017.pdf @@ -137,43 +137,43 @@ JSON::JSON(const double value) : current_type{Type::Real} { JSON::JSON(const float value) : JSON(static_cast(value)) {} -JSON::JSON(const bool value) : current_type{Type::Boolean} { +JSON::JSON(const bool value) : current_type_{Type::Boolean} { this->data_boolean = value; } JSON::JSON(const std::nullptr_t) {} -JSON::JSON(const String &value) : current_type{Type::String} { +JSON::JSON(const String &value) : current_type_{Type::String} { std::construct_at(&this->data_string, value); } -JSON::JSON(String &&value) : current_type{Type::String} { +JSON::JSON(String &&value) : current_type_{Type::String} { std::construct_at(&this->data_string, std::move(value)); } JSON::JSON(const std::basic_string_view &value) - : current_type{Type::String} { + : current_type_{Type::String} { std::construct_at(&this->data_string, value); } -JSON::JSON(const Char *const value) : current_type{Type::String} { +JSON::JSON(const Char *const value) : current_type_{Type::String} { std::construct_at(&this->data_string, value); } -JSON::JSON(const Array &value) : current_type{Type::Array} { +JSON::JSON(const Array &value) : current_type_{Type::Array} { std::construct_at(&this->data_array, value); } -JSON::JSON(std::initializer_list values) - : current_type{Type::Object} { +JSON::JSON(std::initializer_list values) + : current_type_{Type::Object} { std::construct_at(&this->data_object, values); } -JSON::JSON(const Object &value) : current_type{Type::Object} { +JSON::JSON(const Object &value) : current_type_{Type::Object} { std::construct_at(&this->data_object, value); } -JSON::JSON(const Decimal &value) : current_type{Type::Decimal} { +JSON::JSON(const Decimal &value) : current_type_{Type::Decimal} { if (value.is_nan() || value.is_infinite()) { throw std::invalid_argument("JSON does not support Infinity or NaN"); } @@ -181,7 +181,7 @@ JSON::JSON(const Decimal &value) : current_type{Type::Decimal} { std::construct_at(&this->data_decimal, value); } -JSON::JSON(Decimal &&value) : current_type{Type::Decimal} { +JSON::JSON(Decimal &&value) : current_type_{Type::Decimal} { if (value.is_nan() || value.is_infinite()) { throw std::invalid_argument("JSON does not support Infinity or NaN"); } @@ -192,28 +192,28 @@ JSON::JSON(Decimal &&value) : current_type{Type::Decimal} { JSON::JSON(const JSON &other) { // Fast path for non-container sources avoids the work-list allocation that // would otherwise dominate the cost of copying a scalar value - switch (other.current_type) { + switch (other.current_type_) { case Type::Null: return; case Type::Boolean: this->data_boolean = other.data_boolean; - this->current_type = Type::Boolean; + this->current_type_ = Type::Boolean; return; case Type::Integer: this->data_integer = other.data_integer; - this->current_type = Type::Integer; + this->current_type_ = Type::Integer; return; case Type::Real: this->data_real = other.data_real; - this->current_type = Type::Real; + this->current_type_ = Type::Real; return; case Type::String: std::construct_at(&this->data_string, other.data_string); - this->current_type = Type::String; + this->current_type_ = Type::String; return; case Type::Decimal: std::construct_at(&this->data_decimal, other.data_decimal); - this->current_type = Type::Decimal; + this->current_type_ = Type::Decimal; return; case Type::Array: case Type::Object: @@ -242,34 +242,34 @@ JSON::JSON(const JSON &other) { tasks.pop_back(); const JSON &source{*task.source}; JSON &destination{*task.destination}; - switch (source.current_type) { + switch (source.current_type_) { case Type::Null: break; case Type::Boolean: destination.data_boolean = source.data_boolean; - destination.current_type = Type::Boolean; + destination.current_type_ = Type::Boolean; break; case Type::Integer: destination.data_integer = source.data_integer; - destination.current_type = Type::Integer; + destination.current_type_ = Type::Integer; break; case Type::Real: destination.data_real = source.data_real; - destination.current_type = Type::Real; + destination.current_type_ = Type::Real; break; case Type::String: std::construct_at(&destination.data_string, source.data_string); - destination.current_type = Type::String; + destination.current_type_ = Type::String; break; case Type::Decimal: std::construct_at(&destination.data_decimal, source.data_decimal); - destination.current_type = Type::Decimal; + destination.current_type_ = Type::Decimal; break; case Type::Array: { std::construct_at(&destination.data_array, Array{}); - destination.current_type = Type::Array; - const auto &source_data{source.data_array.data}; - auto &destination_data{destination.data_array.data}; + destination.current_type_ = Type::Array; + const auto &source_data{source.data_array.data_}; + auto &destination_data{destination.data_array.data_}; destination_data.reserve(source_data.size()); for (std::size_t index = 0; index < source_data.size(); ++index) { destination_data.emplace_back(nullptr); @@ -282,9 +282,9 @@ JSON::JSON(const JSON &other) { } case Type::Object: { std::construct_at(&destination.data_object, Object{}); - destination.current_type = Type::Object; - const auto &source_data{source.data_object.data}; - auto &destination_data{destination.data_object.data}; + destination.current_type_ = Type::Object; + const auto &source_data{source.data_object.data_}; + auto &destination_data{destination.data_object.data_}; destination_data.reserve(source_data.size()); for (const auto &entry : source_data) { destination_data.emplace_back(entry.first, JSON{nullptr}, @@ -307,8 +307,8 @@ JSON::JSON(const JSON &other) { } } -JSON::JSON(JSON &&other) noexcept : current_type{other.current_type} { - switch (other.current_type) { +JSON::JSON(JSON &&other) noexcept : current_type_{other.current_type_} { + switch (other.current_type_) { case Type::Boolean: this->data_boolean = other.data_boolean; break; @@ -320,15 +320,15 @@ JSON::JSON(JSON &&other) noexcept : current_type{other.current_type} { break; case Type::String: std::construct_at(&this->data_string, std::move(other.data_string)); - other.current_type = Type::Null; + other.current_type_ = Type::Null; break; case Type::Array: std::construct_at(&this->data_array, std::move(other.data_array)); - other.current_type = Type::Null; + other.current_type_ = Type::Null; break; case Type::Object: std::construct_at(&this->data_object, std::move(other.data_object)); - other.current_type = Type::Null; + other.current_type_ = Type::Null; break; case Type::Decimal: std::construct_at(&this->data_decimal, std::move(other.data_decimal)); @@ -337,7 +337,7 @@ JSON::JSON(JSON &&other) noexcept : current_type{other.current_type} { // moved-from state owns no heap coefficient, making this a no-op // branch rather than a deallocation other.data_decimal.~Decimal(); - other.current_type = Type::Null; + other.current_type_ = Type::Null; break; default: break; @@ -354,44 +354,44 @@ auto JSON::operator=(const JSON &other) -> JSON & { // buffered into a local first, because the source may be nested inside this // value, and tearing this value down would otherwise free the storage still // being read from - switch (other.current_type) { + switch (other.current_type_) { case Type::Null: this->~JSON(); - this->current_type = Type::Null; + this->current_type_ = Type::Null; return *this; case Type::Boolean: { const auto value{other.data_boolean}; this->~JSON(); this->data_boolean = value; - this->current_type = Type::Boolean; + this->current_type_ = Type::Boolean; return *this; } case Type::Integer: { const auto value{other.data_integer}; this->~JSON(); this->data_integer = value; - this->current_type = Type::Integer; + this->current_type_ = Type::Integer; return *this; } case Type::Real: { const auto value{other.data_real}; this->~JSON(); this->data_real = value; - this->current_type = Type::Real; + this->current_type_ = Type::Real; return *this; } case Type::String: { String value{other.data_string}; this->~JSON(); std::construct_at(&this->data_string, std::move(value)); - this->current_type = Type::String; + this->current_type_ = Type::String; return *this; } case Type::Decimal: { Decimal value{other.data_decimal}; this->~JSON(); std::construct_at(&this->data_decimal, std::move(value)); - this->current_type = Type::Decimal; + this->current_type_ = Type::Decimal; return *this; } case Type::Array: @@ -431,21 +431,22 @@ JSON::~JSON() { // own container children, so containers full of scalars cost no heap // allocation. Allocation failures during destruction have no sensible // recovery, so terminate - if (this->current_type == Type::Array || this->current_type == Type::Object) { + if (this->current_type_ == Type::Array || + this->current_type_ == Type::Object) { try { std::vector pending; - if (this->current_type == Type::Array) { - for (auto &child : this->data_array.data) { - if (child.current_type == Type::Array || - child.current_type == Type::Object) { + if (this->current_type_ == Type::Array) { + for (auto &child : this->data_array.data_) { + if (child.current_type_ == Type::Array || + child.current_type_ == Type::Object) { pending.push_back(std::move(child)); } } } else { - for (auto &entry : this->data_object.data) { - if (entry.second.current_type == Type::Array || - entry.second.current_type == Type::Object) { + for (auto &entry : this->data_object.data_) { + if (entry.second.current_type_ == Type::Array || + entry.second.current_type_ == Type::Object) { pending.push_back(std::move(entry.second)); } } @@ -454,24 +455,24 @@ JSON::~JSON() { while (!pending.empty()) { JSON node = std::move(pending.back()); pending.pop_back(); - if (node.current_type == Type::Array) { - for (auto &child : node.data_array.data) { - if (child.current_type == Type::Array || - child.current_type == Type::Object) { + if (node.current_type_ == Type::Array) { + for (auto &child : node.data_array.data_) { + if (child.current_type_ == Type::Array || + child.current_type_ == Type::Object) { pending.push_back(std::move(child)); } } node.data_array.~JSONArray(); - node.current_type = Type::Null; + node.current_type_ = Type::Null; } else { - for (auto &entry : node.data_object.data) { - if (entry.second.current_type == Type::Array || - entry.second.current_type == Type::Object) { + for (auto &entry : node.data_object.data_) { + if (entry.second.current_type_ == Type::Array || + entry.second.current_type_ == Type::Object) { pending.push_back(std::move(entry.second)); } } node.data_object.~JSONObject(); - node.current_type = Type::Null; + node.current_type_ = Type::Null; } } } catch (...) { @@ -487,7 +488,7 @@ auto JSON::make_array() -> JSON { return JSON{Array{}}; } auto JSON::make_array(std::initializer_list values) -> JSON { JSON result{nullptr}; std::construct_at(&result.data_array, values); - result.current_type = Type::Array; + result.current_type_ = Type::Array; return result; } @@ -515,19 +516,20 @@ auto JSON::size(const String &value) noexcept -> std::size_t { // arbitrary precision expansion, so this comparison is not noexcept auto JSON::operator<(const JSON &other) const -> bool { if (this->is_number() && other.is_number() && - this->current_type != other.current_type) { + this->current_type_ != other.current_type_) { return std::is_lt(cross_numeric_ordering(*this, other)); } if (this->type() != other.type()) { - return this->current_type < other.current_type; + return this->current_type_ < other.current_type_; } switch (this->type()) { case Type::Null: return false; case Type::Boolean: - return this->to_boolean() < other.to_boolean(); + return static_cast(this->to_boolean()) < + static_cast(other.to_boolean()); case Type::Integer: return this->to_integer() < other.to_integer(); case Type::Real: @@ -561,15 +563,15 @@ auto JSON::operator>=(const JSON &other) const -> bool { // arbitrary precision expansion, so this comparison is not noexcept auto JSON::operator==(const JSON &other) const -> bool { if (this->is_number() && other.is_number() && - this->current_type != other.current_type) { + this->current_type_ != other.current_type_) { return std::is_eq(cross_numeric_ordering(*this, other)); } - if (this->current_type != other.current_type) { + if (this->current_type_ != other.current_type_) { return false; } - switch (this->current_type) { + switch (this->current_type_) { case Type::Boolean: return this->data_boolean == other.data_boolean; case Type::Integer: @@ -601,7 +603,8 @@ auto JSON::operator+(const JSON &other) const -> JSON { : other.is_integer() ? Decimal{other.to_integer()} : Decimal{other.to_real()}; return JSON{left + right}; - } else if (this->is_integer() && other.is_integer()) { + } + if (this->is_integer() && other.is_integer()) { const auto left{this->to_integer()}; const auto right{other.to_integer()}; // Promote to arbitrary precision when the sum would not fit, since signed @@ -612,13 +615,14 @@ auto JSON::operator+(const JSON &other) const -> JSON { } return JSON{left + right}; - } else if (this->is_integer() && other.is_real()) { + } + if (this->is_integer() && other.is_real()) { return JSON{this->as_real() + other.to_real()}; - } else if (this->is_real() && other.is_integer()) { + } + if (this->is_real() && other.is_integer()) { return JSON{this->to_real() + other.as_real()}; - } else { - return JSON{this->to_real() + other.to_real()}; } + return JSON{this->to_real() + other.to_real()}; } auto JSON::operator-(const JSON &other) const -> JSON { @@ -633,7 +637,8 @@ auto JSON::operator-(const JSON &other) const -> JSON { : other.is_integer() ? Decimal{other.to_integer()} : Decimal{other.to_real()}; return JSON{left - right}; - } else if (this->is_integer() && other.is_integer()) { + } + if (this->is_integer() && other.is_integer()) { const auto left{this->to_integer()}; const auto right{other.to_integer()}; // Promote to arbitrary precision when the difference would not fit, since @@ -644,13 +649,14 @@ auto JSON::operator-(const JSON &other) const -> JSON { } return JSON{left - right}; - } else if (this->is_integer() && other.is_real()) { + } + if (this->is_integer() && other.is_real()) { return JSON{this->as_real() - other.to_real()}; - } else if (this->is_real() && other.is_integer()) { + } + if (this->is_real() && other.is_integer()) { return JSON{this->to_real() - other.as_real()}; - } else { - return JSON{this->to_real() - other.to_real()}; } + return JSON{this->to_real() - other.to_real()}; } auto JSON::operator+=(const JSON &additive) -> JSON & { @@ -680,12 +686,11 @@ auto JSON::operator-=(const JSON &substractive) -> JSON & { this->data_string}; } -[[nodiscard]] auto JSON::at_or(const String &key, - const typename Object::hash_type hash, +[[nodiscard]] auto JSON::at_or(const String &key, const Object::hash_type hash, const JSON &otherwise) const -> const JSON & { assert(this->is_object()); - const auto result{this->try_at(key, hash)}; - return result ? *result : otherwise; + const auto *const result{this->try_at(key, hash)}; + return (result != nullptr) ? *result : otherwise; } [[nodiscard]] auto JSON::at_or(const String &key, const JSON &otherwise) const @@ -702,34 +707,37 @@ auto JSON::operator-=(const JSON &substractive) -> JSON & { return std::ranges::fold_left( this->as_object(), static_cast(0), [](const std::uint64_t accumulator, - const typename Object::value_type &pair) -> std::uint64_t { + const Object::value_type &pair) -> std::uint64_t { return accumulator + (pair.first.size() * sizeof(Char)) + pair.second.estimated_byte_size(); }); - } else if (this->is_array()) { + } + if (this->is_array()) { return std::ranges::fold_left( this->as_array(), static_cast(0), [](const std::uint64_t accumulator, const JSON &item) -> std::uint64_t { return accumulator + item.estimated_byte_size(); }); - } else if (this->is_string()) { + } + if (this->is_string()) { // Keep in mind that standard strings might reserve more // space than what it is actually used by the string return this->byte_size() * sizeof(Char); - } else if (this->is_integer()) { + } + if (this->is_integer()) { return sizeof(Integer); - } else if (this->is_real()) { + } + if (this->is_real()) { return sizeof(Real); - } else if (this->is_boolean()) { - return sizeof(bool); - } else { - // The size of the union - return 8; } + if (this->is_boolean()) { + return sizeof(bool); + } // The size of the union + return 8; } [[nodiscard]] auto JSON::fast_hash() const -> std::uint64_t { - switch (this->current_type) { + switch (this->current_type_) { case Type::Null: return 2; case Type::Boolean: @@ -764,7 +772,7 @@ auto JSON::operator-=(const JSON &substractive) -> JSON & { return std::ranges::fold_left( this->as_object(), static_cast(7), [](const std::uint64_t accumulator, - const typename Object::value_type &pair) -> std::uint64_t { + const Object::value_type &pair) -> std::uint64_t { return accumulator + 1 + pair.first.size() + pair.second.fast_hash(); }); @@ -878,7 +886,7 @@ JSON::defines_any(std::initializer_list keys) const -> bool { [[nodiscard]] auto JSON::unique() const -> bool { assert(this->is_array()); - const auto &items{this->data_array.data}; + const auto &items{this->data_array.data_}; const auto size{items.size()}; // Arrays of 0 or 1 item are unique by definition @@ -908,7 +916,7 @@ JSON::defines_any(std::initializer_list keys) const -> bool { [[nodiscard]] auto JSON::unique_keys() const -> bool { assert(this->is_object()); - const auto &entries{this->data_object.data}; + const auto &entries{this->data_object.data_}; const auto size{entries.size()}; // Objects of 0 or 1 member have unique keys by definition @@ -930,36 +938,34 @@ JSON::defines_any(std::initializer_list keys) const -> bool { auto JSON::push_back(const JSON &value) -> void { assert(this->is_array()); - return this->data_array.data.push_back(value); + this->data_array.data_.push_back(value); } auto JSON::push_back(JSON &&value) -> void { assert(this->is_array()); - return this->data_array.data.push_back(std::move(value)); + this->data_array.data_.push_back(std::move(value)); } auto JSON::push_back_if_unique(const JSON &value) -> std::pair, bool> { assert(this->is_array()); - auto &array_data{this->as_array().data}; + auto &array_data{this->as_array().data_}; if (!std::ranges::contains(array_data, value)) { array_data.push_back(value); return {array_data.back(), true}; - } else { - return {*std::ranges::find(array_data, value), false}; } + return {*std::ranges::find(array_data, value), false}; } auto JSON::push_back_if_unique(JSON &&value) -> std::pair, bool> { assert(this->is_array()); - auto &array_data{this->as_array().data}; + auto &array_data{this->as_array().data_}; if (!std::ranges::contains(array_data, value)) { array_data.push_back(std::move(value)); return {array_data.back(), true}; - } else { - return {*std::ranges::find(array_data, value), false}; } + return {*std::ranges::find(array_data, value), false}; } auto JSON::assign(const JSON::String &key, const JSON &value) -> void { @@ -1010,7 +1016,7 @@ auto JSON::assign_assume_new(JSON::String &&key, JSON &&value, hash); } -auto JSON::erase(const JSON::String &key) -> typename Object::size_type { +auto JSON::erase(const JSON::String &key) -> Object::size_type { assert(this->is_object()); return this->data_object.erase(key); } @@ -1019,30 +1025,29 @@ auto JSON::erase_keys(std::initializer_list keys) -> void { this->erase_keys(keys.begin(), keys.end()); } -auto JSON::erase(typename JSON::Array::const_iterator position) -> - typename JSON::Array::iterator { +auto JSON::erase(JSON::Array::const_iterator position) + -> JSON::Array::iterator { assert(this->is_array()); - return this->data_array.data.erase(position); + return this->data_array.data_.erase(position); } -auto JSON::erase(typename JSON::Array::const_iterator first, - typename JSON::Array::const_iterator last) -> - typename JSON::Array::iterator { +auto JSON::erase(JSON::Array::const_iterator first, + JSON::Array::const_iterator last) -> JSON::Array::iterator { assert(this->is_array()); - return this->data_array.data.erase(first, last); + return this->data_array.data_.erase(first, last); } auto JSON::erase_if(const std::function &predicate) -> void { assert(this->is_array()); - std::erase_if(this->data_array.data, predicate); + std::erase_if(this->data_array.data_, predicate); } auto JSON::clear() -> void { if (this->is_object()) { this->data_object.clear(); } else { - this->data_array.data.clear(); + this->data_array.data_.clear(); } } @@ -1062,8 +1067,9 @@ auto JSON::merge(const JSON::Object &other) -> void { } for (const auto &pair : other) { - const auto maybe_key{this->try_at(pair.first, pair.hash)}; - if (maybe_key && maybe_key->is_object() && pair.second.is_object()) { + auto *const maybe_key{this->try_at(pair.first, pair.hash)}; + if ((maybe_key != nullptr) && maybe_key->is_object() && + pair.second.is_object()) { this->at(pair.first, pair.hash).merge(pair.second.as_object()); } else { this->assign(pair.first, pair.second); @@ -1100,10 +1106,10 @@ auto JSON::reorder(const KeyComparison &compare) -> void { this->data_object.reorder(compare); } -auto JSON::rename(const JSON::String &key, JSON::String &&to) -> void { +auto JSON::rename(const JSON::String &key, JSON::String &&target) -> void { assert(this->is_object()); auto &object{this->data_object}; - object.rename(key, object.hash(key), std::move(to), object.hash(to)); + object.rename(key, object.hash(key), std::move(target), object.hash(target)); } auto JSON::into(const JSON &other) -> void { this->operator=(other); } @@ -1117,7 +1123,7 @@ auto JSON::into_array() -> void { this->into(JSON::make_array()); } auto JSON::into_object() -> void { this->into(JSON::make_object()); } auto JSON::maybe_destruct_union() -> void { - switch (this->current_type) { + switch (this->current_type_) { case Type::String: this->data_string.~basic_string(); break; @@ -1134,7 +1140,7 @@ auto JSON::maybe_destruct_union() -> void { break; } - this->current_type = Type::Null; + this->current_type_ = Type::Null; } } // namespace sourcemeta::core diff --git a/vendor/core/src/core/json/parser.h b/vendor/core/src/core/json/parser.h index bf8301ee..26a3910b 100644 --- a/vendor/core/src/core/json/parser.h +++ b/vendor/core/src/core/json/parser.h @@ -53,8 +53,8 @@ constexpr std::uint64_t WORD_LOW_BITS{0x0101010101010101ULL}; constexpr std::uint64_t WORD_HIGH_BITS{0x8080808080808080ULL}; inline auto is_plain_string_byte(const char character) -> bool { - return character != internal::token_string_quote && - character != internal::token_string_escape && + return character != internal::TOKEN_STRING_QUOTE && + character != internal::TOKEN_STRING_ESCAPE && static_cast(character) >= 0x20; } @@ -65,14 +65,14 @@ inline auto is_plain_string_byte(const char character) -> bool { // first flagged byte, and the first flag is always genuine inline auto match_string_special_bytes(const std::uint64_t word) -> std::uint64_t { - constexpr auto quote_pattern{ + constexpr auto QUOTE_PATTERN{ WORD_LOW_BITS * - static_cast(internal::token_string_quote)}; - constexpr auto escape_pattern{ + static_cast(internal::TOKEN_STRING_QUOTE)}; + constexpr auto ESCAPE_PATTERN{ WORD_LOW_BITS * - static_cast(internal::token_string_escape)}; - const auto quote_difference{word ^ quote_pattern}; - const auto escape_difference{word ^ escape_pattern}; + static_cast(internal::TOKEN_STRING_ESCAPE)}; + const auto quote_difference{word ^ QUOTE_PATTERN}; + const auto escape_difference{word ^ ESCAPE_PATTERN}; const auto quote_matches{(quote_difference - WORD_LOW_BITS) & ~quote_difference & WORD_HIGH_BITS}; const auto escape_matches{(escape_difference - WORD_LOW_BITS) & @@ -88,15 +88,15 @@ inline auto skip_whitespace(const char *&cursor, const char *end, -> void { while (cursor < end) { switch (*cursor) { - case internal::token_whitespace_space: - case internal::token_whitespace_tabulation: - case internal::token_whitespace_carriage_return: + case internal::TOKEN_WHITESPACE_SPACE: + case internal::TOKEN_WHITESPACE_TABULATION: + case internal::TOKEN_WHITESPACE_CARRIAGE_RETURN: if constexpr (TrackPositions) { column += 1; } cursor++; continue; - case internal::token_whitespace_line_feed: + case internal::TOKEN_WHITESPACE_LINE_FEED: if constexpr (TrackPositions) { line += 1; column = 0; @@ -112,10 +112,8 @@ inline auto skip_whitespace(const char *&cursor, const char *end, template inline auto scan_null(const std::uint64_t line, std::uint64_t &column, const char *&cursor, const char *end) -> void { - for ( - const auto character : - internal::constant_null.substr( - 1)) { + for (const auto character : + internal::CONSTANT_NULL.substr(1)) { if constexpr (TrackPositions) { column += 1; } @@ -132,10 +130,8 @@ inline auto scan_null(const std::uint64_t line, std::uint64_t &column, template inline auto scan_true(const std::uint64_t line, std::uint64_t &column, const char *&cursor, const char *end) -> void { - for ( - const auto character : - internal::constant_true.substr( - 1)) { + for (const auto character : + internal::CONSTANT_TRUE.substr(1)) { if constexpr (TrackPositions) { column += 1; } @@ -152,10 +148,8 @@ inline auto scan_true(const std::uint64_t line, std::uint64_t &column, template inline auto scan_false(const std::uint64_t line, std::uint64_t &column, const char *&cursor, const char *end) -> void { - for ( - const auto character : - internal::constant_false.substr( - 1)) { + for (const auto character : + internal::CONSTANT_FALSE.substr(1)) { if constexpr (TrackPositions) { column += 1; } @@ -198,7 +192,7 @@ inline auto scan_string_unicode(const std::uint64_t line, std::uint64_t &column, const char *&cursor, const char *end) -> void { auto code_point{scan_string_unicode_code_point(line, column, cursor, end)}; - using CharT = typename JSON::Char; + using CharT = JSON::Char; if (code_point >= 0xDC00 && code_point <= 0xDFFF) [[unlikely]] { throw JSONParseError(line, column); @@ -211,7 +205,7 @@ inline auto scan_string_unicode(const std::uint64_t line, std::uint64_t &column, if (cursor >= end) [[unlikely]] { throw JSONParseError(line, column); } - if (*cursor != internal::token_string_escape) [[unlikely]] { + if (*cursor != internal::TOKEN_STRING_ESCAPE) [[unlikely]] { throw JSONParseError(line, column); } cursor++; @@ -222,7 +216,7 @@ inline auto scan_string_unicode(const std::uint64_t line, std::uint64_t &column, if (cursor >= end) [[unlikely]] { throw JSONParseError(line, column); } - if (*cursor != internal::token_string_escape_unicode) [[unlikely]] { + if (*cursor != internal::TOKEN_STRING_ESCAPE_UNICODE) [[unlikely]] { throw JSONParseError(line, column); } cursor++; @@ -248,16 +242,16 @@ inline auto scan_string_escape(const std::uint64_t line, std::uint64_t &column, throw JSONParseError(line, column); } switch (*cursor++) { - case internal::token_string_quote: - case internal::token_string_escape: - case internal::token_string_solidus: - case internal::token_string_escape_backspace: - case internal::token_string_escape_form_feed: - case internal::token_string_escape_line_feed: - case internal::token_string_escape_carriage_return: - case internal::token_string_escape_tabulation: + case internal::TOKEN_STRING_QUOTE: + case internal::TOKEN_STRING_ESCAPE: + case internal::TOKEN_STRING_SOLIDUS: + case internal::TOKEN_STRING_ESCAPE_BACKSPACE: + case internal::TOKEN_STRING_ESCAPE_FORM_FEED: + case internal::TOKEN_STRING_ESCAPE_LINE_FEED: + case internal::TOKEN_STRING_ESCAPE_CARRIAGE_RETURN: + case internal::TOKEN_STRING_ESCAPE_TABULATION: return; - case internal::token_string_escape_unicode: + case internal::TOKEN_STRING_ESCAPE_UNICODE: scan_string_unicode(line, column, cursor, end); return; default: @@ -310,9 +304,9 @@ inline auto scan_string(const std::uint64_t line, std::uint64_t &column, const char character{*cursor++}; switch (character) { - case internal::token_string_quote: + case internal::TOKEN_STRING_QUOTE: return has_escape; - case internal::token_string_escape: + case internal::TOKEN_STRING_ESCAPE: has_escape = true; scan_string_escape(line, column, cursor, end); break; @@ -331,10 +325,10 @@ template inline auto scan_digits(const std::uint64_t line, std::uint64_t &column, const char *&cursor, const char *end, const bool at_least_one) -> void { - using CharT = typename JSON::Char; + using CharT = JSON::Char; bool found{false}; - while (cursor < end && *cursor >= internal::token_number_zero && - *cursor <= internal::token_number_nine) { + while (cursor < end && *cursor >= internal::TOKEN_NUMBER_ZERO && + *cursor <= internal::TOKEN_NUMBER_NINE) { found = true; if constexpr (TrackPositions) { column += 1; @@ -358,12 +352,12 @@ template inline auto scan_number(const std::uint64_t line, std::uint64_t &column, const char *&cursor, const char *end, const char first) -> NumberFacts { - using CharT = typename JSON::Char; + using CharT = JSON::Char; NumberFacts facts; const char *literal_start{cursor - 1}; - if (first == internal::token_number_minus) { - if (cursor >= end || *cursor < internal::token_number_zero || - *cursor > internal::token_number_nine) [[unlikely]] { + if (first == internal::TOKEN_NUMBER_MINUS) { + if (cursor >= end || *cursor < internal::TOKEN_NUMBER_ZERO || + *cursor > internal::TOKEN_NUMBER_NINE) [[unlikely]] { if constexpr (TrackPositions) { column += 1; } @@ -371,20 +365,20 @@ inline auto scan_number(const std::uint64_t line, std::uint64_t &column, } } - const char int_start{first == internal::token_number_minus ? *cursor + const char int_start{first == internal::TOKEN_NUMBER_MINUS ? *cursor : first}; const char *integer_begin{ - first == internal::token_number_minus ? cursor : cursor - 1}; - if (first == internal::token_number_minus) { + first == internal::TOKEN_NUMBER_MINUS ? cursor : cursor - 1}; + if (first == internal::TOKEN_NUMBER_MINUS) { if constexpr (TrackPositions) { column += 1; } cursor++; } - if (int_start == internal::token_number_zero) { - if (cursor < end && *cursor >= internal::token_number_zero && - *cursor <= internal::token_number_nine) [[unlikely]] { + if (int_start == internal::TOKEN_NUMBER_ZERO) { + if (cursor < end && *cursor >= internal::TOKEN_NUMBER_ZERO && + *cursor <= internal::TOKEN_NUMBER_NINE) [[unlikely]] { if constexpr (TrackPositions) { column += 1; } @@ -395,7 +389,7 @@ inline auto scan_number(const std::uint64_t line, std::uint64_t &column, } const char *dot_position{nullptr}; - if (cursor < end && *cursor == internal::token_number_decimal_point) { + if (cursor < end && *cursor == internal::TOKEN_NUMBER_DECIMAL_POINT) { dot_position = cursor; facts.flags |= TAPE_FLAG_NUMBER_DOT; if constexpr (TrackPositions) { @@ -406,15 +400,15 @@ inline auto scan_number(const std::uint64_t line, std::uint64_t &column, } if (cursor < end && - (*cursor == internal::token_number_exponent_lowercase || - *cursor == internal::token_number_exponent_uppercase)) { + (*cursor == internal::TOKEN_NUMBER_EXPONENT_LOWERCASE || + *cursor == internal::TOKEN_NUMBER_EXPONENT_UPPERCASE)) { facts.flags |= TAPE_FLAG_NUMBER_EXPONENT; if constexpr (TrackPositions) { column += 1; } cursor++; - if (cursor < end && (*cursor == internal::token_number_plus || - *cursor == internal::token_number_minus)) { + if (cursor < end && (*cursor == internal::TOKEN_NUMBER_PLUS || + *cursor == internal::TOKEN_NUMBER_MINUS)) { if constexpr (TrackPositions) { column += 1; } @@ -428,12 +422,12 @@ inline auto scan_number(const std::uint64_t line, std::uint64_t &column, // on their own if (dot_position && !(facts.flags & TAPE_FLAG_NUMBER_EXPONENT)) { const char *first_significant{nullptr}; - if (int_start != internal::token_number_zero) { + if (int_start != internal::TOKEN_NUMBER_ZERO) { first_significant = integer_begin; } else { for (const char *pointer = dot_position + 1; pointer < cursor; pointer++) { - if (*pointer != internal::token_number_zero) { + if (*pointer != internal::TOKEN_NUMBER_ZERO) { first_significant = pointer; break; } @@ -467,7 +461,7 @@ inline auto scan_json(const char *&cursor, const char *end, std::uint32_t child_count; }; - using CharT = typename JSON::Char; + using CharT = JSON::Char; char character = 0; std::vector container_stack; container_stack.reserve(32); @@ -488,7 +482,7 @@ inline auto scan_json(const char *&cursor, const char *end, const auto value_line{line}; const auto value_column{column}; switch (character) { - case internal::token_true: + case internal::TOKEN_TRUE: internal::scan_true(line, column, cursor, end); tape.push_back({.type = TapeType::True, .flags = 0, @@ -498,7 +492,7 @@ inline auto scan_json(const char *&cursor, const char *end, .line = value_line, .column = value_column}); return; - case internal::token_false: + case internal::TOKEN_FALSE: internal::scan_false(line, column, cursor, end); tape.push_back({.type = TapeType::False, .flags = 0, @@ -508,7 +502,7 @@ inline auto scan_json(const char *&cursor, const char *end, .line = value_line, .column = value_column}); return; - case internal::token_null: + case internal::TOKEN_NULL: internal::scan_null(line, column, cursor, end); tape.push_back({.type = TapeType::Null, .flags = 0, @@ -518,7 +512,7 @@ inline auto scan_json(const char *&cursor, const char *end, .line = value_line, .column = value_column}); return; - case internal::token_string_quote: { + case internal::TOKEN_STRING_QUOTE: { const auto string_start{ static_cast(cursor - buffer_start)}; const auto string_has_escape{ @@ -531,21 +525,21 @@ inline auto scan_json(const char *&cursor, const char *end, string_start, string_length, 0, value_line, value_column}); return; } - case internal::token_array_begin: + case internal::TOKEN_ARRAY_BEGIN: goto do_scan_array; - case internal::token_object_begin: + case internal::TOKEN_OBJECT_BEGIN: goto do_scan_object; - case internal::token_number_minus: - case internal::token_number_zero: - case internal::token_number_one: - case internal::token_number_two: - case internal::token_number_three: - case internal::token_number_four: - case internal::token_number_five: - case internal::token_number_six: - case internal::token_number_seven: - case internal::token_number_eight: - case internal::token_number_nine: { + case internal::TOKEN_NUMBER_MINUS: + case internal::TOKEN_NUMBER_ZERO: + case internal::TOKEN_NUMBER_ONE: + case internal::TOKEN_NUMBER_TWO: + case internal::TOKEN_NUMBER_THREE: + case internal::TOKEN_NUMBER_FOUR: + case internal::TOKEN_NUMBER_FIVE: + case internal::TOKEN_NUMBER_SIX: + case internal::TOKEN_NUMBER_SEVEN: + case internal::TOKEN_NUMBER_EIGHT: + case internal::TOKEN_NUMBER_NINE: { const auto number_start{ static_cast(cursor - buffer_start - 1)}; const auto number_facts{internal::scan_number( @@ -585,7 +579,7 @@ do_scan_array: { throw JSONParseError(line, column); } - if (*cursor == internal::token_array_end) { + if (*cursor == internal::TOKEN_ARRAY_END) { if constexpr (TrackPositions) { column += 1; } @@ -625,11 +619,11 @@ do_scan_array: { const auto value_line{line}; const auto value_column{column}; switch (character) { - case internal::token_array_begin: + case internal::TOKEN_ARRAY_BEGIN: goto do_scan_array; - case internal::token_object_begin: + case internal::TOKEN_OBJECT_BEGIN: goto do_scan_object; - case internal::token_true: + case internal::TOKEN_TRUE: internal::scan_true(line, column, cursor, end); tape.push_back({.type = TapeType::True, .flags = 0, @@ -639,7 +633,7 @@ do_scan_array: { .line = value_line, .column = value_column}); goto do_scan_array_item_separator; - case internal::token_false: + case internal::TOKEN_FALSE: internal::scan_false(line, column, cursor, end); tape.push_back({.type = TapeType::False, .flags = 0, @@ -649,7 +643,7 @@ do_scan_array: { .line = value_line, .column = value_column}); goto do_scan_array_item_separator; - case internal::token_null: + case internal::TOKEN_NULL: internal::scan_null(line, column, cursor, end); tape.push_back({.type = TapeType::Null, .flags = 0, @@ -659,7 +653,7 @@ do_scan_array: { .line = value_line, .column = value_column}); goto do_scan_array_item_separator; - case internal::token_string_quote: { + case internal::TOKEN_STRING_QUOTE: { const auto string_start{ static_cast(cursor - buffer_start)}; const auto string_has_escape{ @@ -672,17 +666,17 @@ do_scan_array: { string_start, string_length, 0, value_line, value_column}); goto do_scan_array_item_separator; } - case internal::token_number_minus: - case internal::token_number_zero: - case internal::token_number_one: - case internal::token_number_two: - case internal::token_number_three: - case internal::token_number_four: - case internal::token_number_five: - case internal::token_number_six: - case internal::token_number_seven: - case internal::token_number_eight: - case internal::token_number_nine: { + case internal::TOKEN_NUMBER_MINUS: + case internal::TOKEN_NUMBER_ZERO: + case internal::TOKEN_NUMBER_ONE: + case internal::TOKEN_NUMBER_TWO: + case internal::TOKEN_NUMBER_THREE: + case internal::TOKEN_NUMBER_FOUR: + case internal::TOKEN_NUMBER_FIVE: + case internal::TOKEN_NUMBER_SIX: + case internal::TOKEN_NUMBER_SEVEN: + case internal::TOKEN_NUMBER_EIGHT: + case internal::TOKEN_NUMBER_NINE: { const auto number_start{ static_cast(cursor - buffer_start - 1)}; const auto number_facts{internal::scan_number( @@ -712,9 +706,9 @@ do_scan_array: { } character = *cursor++; switch (character) { - case internal::token_array_delimiter: + case internal::TOKEN_ARRAY_DELIMITER: goto do_scan_array_item; - case internal::token_array_end: { + case internal::TOKEN_ARRAY_END: { assert(!container_stack.empty()); auto &frame{container_stack.back()}; tape[frame.tape_index].count = frame.child_count; @@ -755,7 +749,7 @@ do_scan_object: { throw JSONParseError(line, column); } - if (*cursor == internal::token_object_end) { + if (*cursor == internal::TOKEN_OBJECT_END) { if constexpr (TrackPositions) { column += 1; } @@ -791,7 +785,7 @@ do_scan_object: { } character = *cursor++; switch (character) { - case internal::token_string_quote: { + case internal::TOKEN_STRING_QUOTE: { const auto key_start{static_cast(cursor - buffer_start)}; const auto key_line{line}; const auto key_column{column}; @@ -822,7 +816,7 @@ do_scan_object: { } character = *cursor++; switch (character) { - case internal::token_object_key_delimiter: + case internal::TOKEN_OBJECT_KEY_DELIMITER: goto do_scan_object_value; default: [[unlikely]] throw JSONParseError(line, column); @@ -845,11 +839,11 @@ do_scan_object: { const auto value_line{line}; const auto value_column{column}; switch (character) { - case internal::token_array_begin: + case internal::TOKEN_ARRAY_BEGIN: goto do_scan_array; - case internal::token_object_begin: + case internal::TOKEN_OBJECT_BEGIN: goto do_scan_object; - case internal::token_true: + case internal::TOKEN_TRUE: internal::scan_true(line, column, cursor, end); tape.push_back({.type = TapeType::True, .flags = 0, @@ -859,7 +853,7 @@ do_scan_object: { .line = value_line, .column = value_column}); goto do_scan_object_property_end; - case internal::token_false: + case internal::TOKEN_FALSE: internal::scan_false(line, column, cursor, end); tape.push_back({.type = TapeType::False, .flags = 0, @@ -869,7 +863,7 @@ do_scan_object: { .line = value_line, .column = value_column}); goto do_scan_object_property_end; - case internal::token_null: + case internal::TOKEN_NULL: internal::scan_null(line, column, cursor, end); tape.push_back({.type = TapeType::Null, .flags = 0, @@ -879,7 +873,7 @@ do_scan_object: { .line = value_line, .column = value_column}); goto do_scan_object_property_end; - case internal::token_string_quote: { + case internal::TOKEN_STRING_QUOTE: { const auto string_start{ static_cast(cursor - buffer_start)}; const auto string_has_escape{ @@ -892,17 +886,17 @@ do_scan_object: { string_start, string_length, 0, value_line, value_column}); goto do_scan_object_property_end; } - case internal::token_number_minus: - case internal::token_number_zero: - case internal::token_number_one: - case internal::token_number_two: - case internal::token_number_three: - case internal::token_number_four: - case internal::token_number_five: - case internal::token_number_six: - case internal::token_number_seven: - case internal::token_number_eight: - case internal::token_number_nine: { + case internal::TOKEN_NUMBER_MINUS: + case internal::TOKEN_NUMBER_ZERO: + case internal::TOKEN_NUMBER_ONE: + case internal::TOKEN_NUMBER_TWO: + case internal::TOKEN_NUMBER_THREE: + case internal::TOKEN_NUMBER_FOUR: + case internal::TOKEN_NUMBER_FIVE: + case internal::TOKEN_NUMBER_SIX: + case internal::TOKEN_NUMBER_SEVEN: + case internal::TOKEN_NUMBER_EIGHT: + case internal::TOKEN_NUMBER_NINE: { const auto number_start{ static_cast(cursor - buffer_start - 1)}; const auto number_facts{internal::scan_number( @@ -932,9 +926,9 @@ do_scan_object: { } character = *cursor++; switch (character) { - case internal::token_object_delimiter: + case internal::TOKEN_OBJECT_DELIMITER: goto do_scan_object_key; - case internal::token_object_end: { + case internal::TOKEN_OBJECT_END: { assert(!container_stack.empty()); auto &frame{container_stack.back()}; tape[frame.tape_index].count = frame.child_count; diff --git a/vendor/core/src/core/json/stringify.h b/vendor/core/src/core/json/stringify.h index ab5b9795..e3dd5889 100644 --- a/vendor/core/src/core/json/stringify.h +++ b/vendor/core/src/core/json/stringify.h @@ -25,7 +25,7 @@ auto indent(std::basic_ostream &stream, const std::size_t indentation, const std::size_t indent_by) -> void { for (std::size_t index{0}; index < indentation * indent_by; index++) { - stream.put(internal::token_whitespace_space); + stream.put(internal::TOKEN_WHITESPACE_SPACE); } } } // namespace sourcemeta::core::internal @@ -33,48 +33,41 @@ auto indent(std::basic_ostream &stream, namespace sourcemeta::core { template