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
try_parse(StringPiece const str)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
parse(StringPiece const str)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 
is_prefix_of(json_pointer const & other) const66 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 
tokens() const76 std::vector<std::string> const& json_pointer::tokens() const {
77   return tokens_;
78 }
79 
80 // private
json_pointer(std::vector<std::string> tokens)81 json_pointer::json_pointer(std::vector<std::string> tokens) noexcept
82     : tokens_{std::move(tokens)} {}
83 
84 // private, static
unescape(std::string & str)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