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/util/internal/utility.h>
34 #include <google/protobuf/stubs/strutil.h>
35 #include <google/protobuf/stubs/status_macros.h>
36
37 // Must be included last.
38 #include <google/protobuf/port_def.inc>
39
40 namespace google {
41 namespace protobuf {
42 namespace util {
43 namespace converter {
44
45 namespace {
46
47 // Appends a FieldMask path segment to a prefix.
AppendPathSegmentToPrefix(StringPiece prefix,StringPiece segment)48 std::string AppendPathSegmentToPrefix(StringPiece prefix,
49 StringPiece segment) {
50 if (prefix.empty()) {
51 return std::string(segment);
52 }
53 if (segment.empty()) {
54 return std::string(prefix);
55 }
56 // If the segment is a map key, appends it to the prefix without the ".".
57 if (HasPrefixString(segment, "[\"")) {
58 return StrCat(prefix, segment);
59 }
60 return StrCat(prefix, ".", segment);
61 }
62
63 } // namespace
64
ConvertFieldMaskPath(const StringPiece path,ConverterCallback converter)65 std::string ConvertFieldMaskPath(const StringPiece path,
66 ConverterCallback converter) {
67 std::string result;
68 result.reserve(path.size() << 1);
69
70 bool is_quoted = false;
71 bool is_escaping = false;
72 int current_segment_start = 0;
73
74 // Loops until 1 passed the end of the input to make handling the last
75 // segment easier.
76 for (size_t i = 0; i <= path.size(); ++i) {
77 // Outputs quoted string as-is.
78 if (is_quoted) {
79 if (i == path.size()) {
80 break;
81 }
82 result.push_back(path[i]);
83 if (is_escaping) {
84 is_escaping = false;
85 } else if (path[i] == '\\') {
86 is_escaping = true;
87 } else if (path[i] == '\"') {
88 current_segment_start = i + 1;
89 is_quoted = false;
90 }
91 continue;
92 }
93 if (i == path.size() || path[i] == '.' || path[i] == '(' ||
94 path[i] == ')' || path[i] == '\"') {
95 result += converter(
96 path.substr(current_segment_start, i - current_segment_start));
97 if (i < path.size()) {
98 result.push_back(path[i]);
99 }
100 current_segment_start = i + 1;
101 }
102 if (i < path.size() && path[i] == '\"') {
103 is_quoted = true;
104 }
105 }
106 return result;
107 }
108
DecodeCompactFieldMaskPaths(StringPiece paths,PathSinkCallback path_sink)109 util::Status DecodeCompactFieldMaskPaths(StringPiece paths,
110 PathSinkCallback path_sink) {
111 std::stack<std::string> prefix;
112 int length = paths.length();
113 int previous_position = 0;
114 bool in_map_key = false;
115 bool is_escaping = false;
116 // Loops until 1 passed the end of the input to make the handle of the last
117 // segment easier.
118 for (int i = 0; i <= length; ++i) {
119 if (i != length) {
120 // Skips everything in a map key until we hit the end of it, which is
121 // marked by an un-escaped '"' immediately followed by a ']'.
122 if (in_map_key) {
123 if (is_escaping) {
124 is_escaping = false;
125 continue;
126 }
127 if (paths[i] == '\\') {
128 is_escaping = true;
129 continue;
130 }
131 if (paths[i] != '\"') {
132 continue;
133 }
134 // Un-escaped '"' must be followed with a ']'.
135 if (i >= length - 1 || paths[i + 1] != ']') {
136 return util::Status(
137 util::error::INVALID_ARGUMENT,
138 StrCat(
139 "Invalid FieldMask '", paths,
140 "'. Map keys should be represented as [\"some_key\"]."));
141 }
142 // The end of the map key ("\"]") has been found.
143 in_map_key = false;
144 // Skips ']'.
145 i++;
146 // Checks whether the key ends at the end of a path segment.
147 if (i < length - 1 && paths[i + 1] != '.' && paths[i + 1] != ',' &&
148 paths[i + 1] != ')' && paths[i + 1] != '(') {
149 return util::Status(
150 util::error::INVALID_ARGUMENT,
151 StrCat(
152 "Invalid FieldMask '", paths,
153 "'. Map keys should be at the end of a path segment."));
154 }
155 is_escaping = false;
156 continue;
157 }
158
159 // We are not in a map key, look for the start of one.
160 if (paths[i] == '[') {
161 if (i >= length - 1 || paths[i + 1] != '\"') {
162 return util::Status(
163 util::error::INVALID_ARGUMENT,
164 StrCat(
165 "Invalid FieldMask '", paths,
166 "'. Map keys should be represented as [\"some_key\"]."));
167 }
168 // "[\"" starts a map key.
169 in_map_key = true;
170 i++; // Skips the '\"'.
171 continue;
172 }
173 // If the current character is not a special character (',', '(' or ')'),
174 // continue to the next.
175 if (paths[i] != ',' && paths[i] != ')' && paths[i] != '(') {
176 continue;
177 }
178 }
179 // Gets the current segment - sub-string between previous position (after
180 // '(', ')', ',', or the beginning of the input) and the current position.
181 StringPiece segment =
182 paths.substr(previous_position, i - previous_position);
183 std::string current_prefix = prefix.empty() ? "" : prefix.top();
184
185 if (i < length && paths[i] == '(') {
186 // Builds a prefix and save it into the stack.
187 prefix.push(AppendPathSegmentToPrefix(current_prefix, segment));
188 } else if (!segment.empty()) {
189 // When the current character is ')', ',' or the current position has
190 // passed the end of the input, builds and outputs a new paths by
191 // concatenating the last prefix with the current segment.
192 RETURN_IF_ERROR(
193 path_sink(AppendPathSegmentToPrefix(current_prefix, segment)));
194 }
195
196 // Removes the last prefix after seeing a ')'.
197 if (i < length && paths[i] == ')') {
198 if (prefix.empty()) {
199 return util::Status(
200 util::error::INVALID_ARGUMENT,
201 StrCat("Invalid FieldMask '", paths,
202 "'. Cannot find matching '(' for all ')'."));
203 }
204 prefix.pop();
205 }
206 previous_position = i + 1;
207 }
208 if (in_map_key) {
209 return util::Status(
210 util::error::INVALID_ARGUMENT,
211 StrCat("Invalid FieldMask '", paths,
212 "'. Cannot find matching ']' for all '['."));
213 }
214 if (!prefix.empty()) {
215 return util::Status(
216 util::error::INVALID_ARGUMENT,
217 StrCat("Invalid FieldMask '", paths,
218 "'. Cannot find matching ')' for all '('."));
219 }
220 return util::Status();
221 }
222
223 } // namespace converter
224 } // namespace util
225 } // namespace protobuf
226 } // namespace google
227