• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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