1 /*
2  * Copyright (c) Facebook, Inc. and its affiliates.
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* out = &str[0];
87   char const* begin = out;
88   char const* end = begin + str.size();
89   char const* decode = begin;
90   while (decode < end) {
91     if (*decode != '~') {
92       *out++ = *decode++;
93       continue;
94     }
95     if (decode + 1 == end) {
96       return false;
97     }
98     switch (decode[1]) {
99       case '1':
100         *out++ = '/';
101         break;
102       case '0':
103         *out++ = '~';
104         break;
105       default:
106         return false;
107     }
108     decode += 2;
109   }
110   str.resize(out - begin);
111   return true;
112 }
113 
114 } // namespace folly
115