1 | // Protocol Buffers - Google's data interchange format |
2 | // Copyright 2008 Google Inc. All rights reserved. |
3 | // https://developers.google.com/protocol-buffers/ |
4 | // |
5 | // Redistribution and use in source and binary forms, with or without |
6 | // modification, are permitted provided that the following conditions are |
7 | // met: |
8 | // |
9 | // * Redistributions of source code must retain the above copyright |
10 | // notice, this list of conditions and the following disclaimer. |
11 | // * Redistributions in binary form must reproduce the above |
12 | // copyright notice, this list of conditions and the following disclaimer |
13 | // in the documentation and/or other materials provided with the |
14 | // distribution. |
15 | // * Neither the name of Google Inc. nor the names of its |
16 | // contributors may be used to endorse or promote products derived from |
17 | // this software without specific prior written permission. |
18 | // |
19 | // THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS |
20 | // "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT |
21 | // LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR |
22 | // A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT |
23 | // OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, |
24 | // SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT |
25 | // LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, |
26 | // DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY |
27 | // THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT |
28 | // (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE |
29 | // OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. |
30 | |
31 | #include <google/protobuf/util/internal/field_mask_utility.h> |
32 | |
33 | #include <google/protobuf/stubs/status.h> |
34 | #include <google/protobuf/stubs/strutil.h> |
35 | #include <google/protobuf/util/internal/utility.h> |
36 | #include <google/protobuf/stubs/status_macros.h> |
37 | |
38 | // Must be included last. |
39 | #include <google/protobuf/port_def.inc> |
40 | |
41 | namespace google { |
42 | namespace protobuf { |
43 | namespace util { |
44 | namespace converter { |
45 | |
46 | namespace { |
47 | |
48 | // Appends a FieldMask path segment to a prefix. |
49 | std::string AppendPathSegmentToPrefix(StringPiece prefix, |
50 | StringPiece segment) { |
51 | if (prefix.empty()) { |
52 | return std::string(segment); |
53 | } |
54 | if (segment.empty()) { |
55 | return std::string(prefix); |
56 | } |
57 | // If the segment is a map key, appends it to the prefix without the ".". |
58 | if (HasPrefixString(str: segment, prefix: "[\"" )) { |
59 | return StrCat(a: prefix, b: segment); |
60 | } |
61 | return StrCat(a: prefix, b: "." , c: segment); |
62 | } |
63 | |
64 | } // namespace |
65 | |
66 | std::string ConvertFieldMaskPath(const StringPiece path, |
67 | ConverterCallback converter) { |
68 | std::string result; |
69 | result.reserve(res_arg: path.size() << 1); |
70 | |
71 | bool is_quoted = false; |
72 | bool is_escaping = false; |
73 | int current_segment_start = 0; |
74 | |
75 | // Loops until 1 passed the end of the input to make handling the last |
76 | // segment easier. |
77 | for (size_t i = 0; i <= path.size(); ++i) { |
78 | // Outputs quoted string as-is. |
79 | if (is_quoted) { |
80 | if (i == path.size()) { |
81 | break; |
82 | } |
83 | result.push_back(c: path[i]); |
84 | if (is_escaping) { |
85 | is_escaping = false; |
86 | } else if (path[i] == '\\') { |
87 | is_escaping = true; |
88 | } else if (path[i] == '\"') { |
89 | current_segment_start = i + 1; |
90 | is_quoted = false; |
91 | } |
92 | continue; |
93 | } |
94 | if (i == path.size() || path[i] == '.' || path[i] == '(' || |
95 | path[i] == ')' || path[i] == '\"') { |
96 | result += converter( |
97 | path.substr(pos: current_segment_start, n: i - current_segment_start)); |
98 | if (i < path.size()) { |
99 | result.push_back(c: path[i]); |
100 | } |
101 | current_segment_start = i + 1; |
102 | } |
103 | if (i < path.size() && path[i] == '\"') { |
104 | is_quoted = true; |
105 | } |
106 | } |
107 | return result; |
108 | } |
109 | |
110 | util::Status DecodeCompactFieldMaskPaths(StringPiece paths, |
111 | PathSinkCallback path_sink) { |
112 | std::stack<std::string> prefix; |
113 | int length = paths.length(); |
114 | int previous_position = 0; |
115 | bool in_map_key = false; |
116 | bool is_escaping = false; |
117 | // Loops until 1 passed the end of the input to make the handle of the last |
118 | // segment easier. |
119 | for (int i = 0; i <= length; ++i) { |
120 | if (i != length) { |
121 | // Skips everything in a map key until we hit the end of it, which is |
122 | // marked by an un-escaped '"' immediately followed by a ']'. |
123 | if (in_map_key) { |
124 | if (is_escaping) { |
125 | is_escaping = false; |
126 | continue; |
127 | } |
128 | if (paths[i] == '\\') { |
129 | is_escaping = true; |
130 | continue; |
131 | } |
132 | if (paths[i] != '\"') { |
133 | continue; |
134 | } |
135 | // Un-escaped '"' must be followed with a ']'. |
136 | if (i >= length - 1 || paths[i + 1] != ']') { |
137 | return util::InvalidArgumentError(message: StrCat( |
138 | a: "Invalid FieldMask '" , b: paths, |
139 | c: "'. Map keys should be represented as [\"some_key\"]." )); |
140 | } |
141 | // The end of the map key ("\"]") has been found. |
142 | in_map_key = false; |
143 | // Skips ']'. |
144 | i++; |
145 | // Checks whether the key ends at the end of a path segment. |
146 | if (i < length - 1 && paths[i + 1] != '.' && paths[i + 1] != ',' && |
147 | paths[i + 1] != ')' && paths[i + 1] != '(') { |
148 | return util::InvalidArgumentError(message: StrCat( |
149 | a: "Invalid FieldMask '" , b: paths, |
150 | c: "'. Map keys should be at the end of a path segment." )); |
151 | } |
152 | is_escaping = false; |
153 | continue; |
154 | } |
155 | |
156 | // We are not in a map key, look for the start of one. |
157 | if (paths[i] == '[') { |
158 | if (i >= length - 1 || paths[i + 1] != '\"') { |
159 | return util::InvalidArgumentError(message: StrCat( |
160 | a: "Invalid FieldMask '" , b: paths, |
161 | c: "'. Map keys should be represented as [\"some_key\"]." )); |
162 | } |
163 | // "[\"" starts a map key. |
164 | in_map_key = true; |
165 | i++; // Skips the '\"'. |
166 | continue; |
167 | } |
168 | // If the current character is not a special character (',', '(' or ')'), |
169 | // continue to the next. |
170 | if (paths[i] != ',' && paths[i] != ')' && paths[i] != '(') { |
171 | continue; |
172 | } |
173 | } |
174 | // Gets the current segment - sub-string between previous position (after |
175 | // '(', ')', ',', or the beginning of the input) and the current position. |
176 | StringPiece segment = |
177 | paths.substr(pos: previous_position, n: i - previous_position); |
178 | std::string current_prefix = prefix.empty() ? "" : prefix.top(); |
179 | |
180 | if (i < length && paths[i] == '(') { |
181 | // Builds a prefix and save it into the stack. |
182 | prefix.push(x: AppendPathSegmentToPrefix(prefix: current_prefix, segment)); |
183 | } else if (!segment.empty()) { |
184 | // When the current character is ')', ',' or the current position has |
185 | // passed the end of the input, builds and outputs a new paths by |
186 | // concatenating the last prefix with the current segment. |
187 | RETURN_IF_ERROR( |
188 | path_sink(AppendPathSegmentToPrefix(current_prefix, segment))); |
189 | } |
190 | |
191 | // Removes the last prefix after seeing a ')'. |
192 | if (i < length && paths[i] == ')') { |
193 | if (prefix.empty()) { |
194 | return util::InvalidArgumentError( |
195 | message: StrCat(a: "Invalid FieldMask '" , b: paths, |
196 | c: "'. Cannot find matching '(' for all ')'." )); |
197 | } |
198 | prefix.pop(); |
199 | } |
200 | previous_position = i + 1; |
201 | } |
202 | if (in_map_key) { |
203 | return util::InvalidArgumentError( |
204 | message: StrCat(a: "Invalid FieldMask '" , b: paths, |
205 | c: "'. Cannot find matching ']' for all '['." )); |
206 | } |
207 | if (!prefix.empty()) { |
208 | return util::InvalidArgumentError( |
209 | message: StrCat(a: "Invalid FieldMask '" , b: paths, |
210 | c: "'. Cannot find matching ')' for all '('." )); |
211 | } |
212 | return util::Status(); |
213 | } |
214 | |
215 | } // namespace converter |
216 | } // namespace util |
217 | } // namespace protobuf |
218 | } // namespace google |
219 | |