1 | /* |
2 | * Copyright 2011-present Facebook, Inc. |
3 | * |
4 | * Licensed under the Apache License, Version 2.0 (the "License"); |
5 | * you may not use this file except in compliance with the License. |
6 | * You may obtain a copy of the License at |
7 | * |
8 | * http://www.apache.org/licenses/LICENSE-2.0 |
9 | * |
10 | * Unless required by applicable law or agreed to in writing, software |
11 | * distributed under the License is distributed on an "AS IS" BASIS, |
12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
13 | * See the License for the specific language governing permissions and |
14 | * limitations under the License. |
15 | */ |
16 | |
17 | #include <folly/json_pointer.h> |
18 | |
19 | #include <folly/String.h> |
20 | |
21 | namespace folly { |
22 | |
23 | // static, public |
24 | Expected<json_pointer, json_pointer::parse_error> json_pointer::try_parse( |
25 | StringPiece const str) { |
26 | // pointer describes complete document |
27 | if (str.empty()) { |
28 | return json_pointer{}; |
29 | } |
30 | |
31 | if (str.at(0) != '/') { |
32 | return makeUnexpected(parse_error::invalid_first_character); |
33 | } |
34 | |
35 | std::vector<std::string> tokens; |
36 | splitTo<std::string>("/" , str, std::inserter(tokens, tokens.begin())); |
37 | tokens.erase(tokens.begin()); |
38 | |
39 | for (auto& token : tokens) { |
40 | if (!unescape(token)) { |
41 | return makeUnexpected(parse_error::invalid_escape_sequence); |
42 | } |
43 | } |
44 | |
45 | return json_pointer(std::move(tokens)); |
46 | } |
47 | |
48 | // static, public |
49 | json_pointer json_pointer::parse(StringPiece const str) { |
50 | auto res = try_parse(str); |
51 | if (res.hasValue()) { |
52 | return std::move(res.value()); |
53 | } |
54 | switch (res.error()) { |
55 | case parse_error::invalid_first_character: |
56 | throw json_pointer::parse_exception( |
57 | "non-empty JSON pointer string does not start with '/'" ); |
58 | case parse_error::invalid_escape_sequence: |
59 | throw json_pointer::parse_exception( |
60 | "Invalid escape sequence in JSON pointer string" ); |
61 | default: |
62 | assume_unreachable(); |
63 | } |
64 | } |
65 | |
66 | bool json_pointer::is_prefix_of(json_pointer const& other) const noexcept { |
67 | auto const& other_tokens = other.tokens(); |
68 | if (tokens_.size() > other_tokens.size()) { |
69 | return false; |
70 | } |
71 | auto const other_begin = other_tokens.cbegin(); |
72 | auto const other_end = other_tokens.cbegin() + tokens_.size(); |
73 | return std::equal(tokens_.cbegin(), tokens_.cend(), other_begin, other_end); |
74 | } |
75 | |
76 | std::vector<std::string> const& json_pointer::tokens() const { |
77 | return tokens_; |
78 | } |
79 | |
80 | // private |
81 | json_pointer::json_pointer(std::vector<std::string> tokens) noexcept |
82 | : tokens_{std::move(tokens)} {} |
83 | |
84 | // private, static |
85 | bool json_pointer::unescape(std::string& str) { |
86 | char const* end = &str[str.size()]; |
87 | char* out = &str.front(); |
88 | char const* decode = out; |
89 | while (decode < end) { |
90 | if (*decode != '~') { |
91 | *out++ = *decode++; |
92 | continue; |
93 | } |
94 | if (decode + 1 == end) { |
95 | return false; |
96 | } |
97 | switch (decode[1]) { |
98 | case '1': |
99 | *out++ = '/'; |
100 | break; |
101 | case '0': |
102 | *out++ = '~'; |
103 | break; |
104 | default: |
105 | return false; |
106 | } |
107 | decode += 2; |
108 | } |
109 | str.resize(out - &str.front()); |
110 | return true; |
111 | } |
112 | |
113 | } // namespace folly |
114 | |