24 #include <gutil/strings/substitute.h>
25 #include <rapidjson/document.h>
26 #include <rapidjson/filestream.h>
27 #include <rapidjson/rapidjson.h>
28 #include <rapidjson/reader.h>
30 #include <re2/stringpiece.h>
36 using rapidjson::Document;
37 using rapidjson::Value;
41 using std::ostringstream;
44 using strings::Substitute;
57 Regex::Options options;
58 options.set_case_sensitive(case_sensitive);
59 Regex re(search_regex, options);
60 return Rule(trigger, re, replacement);
86 search_pattern(search_pattern.pattern(), search_pattern.options()),
87 replacement(replacement) {}
96 switch (value.GetType()) {
97 case rapidjson::kNullType:
99 case rapidjson::kFalseType:
100 case rapidjson::kTrueType:
102 case rapidjson::kObjectType:
104 case rapidjson::kArrayType:
106 case rapidjson::kStringType:
108 case rapidjson::kNumberType:
109 if (value.IsInt())
return "Integer";
110 if (value.IsDouble())
return "Float";
122 string Parse(
const Document& rules_doc) {
124 bool found_rules =
false;
125 for (Value::ConstMemberIterator member = rules_doc.MemberBegin();
126 member != rules_doc.MemberEnd(); ++member) {
127 if (strcmp(
"rules", member->name.GetString()) == 0) {
130 }
else if (strcmp(
"version", member->name.GetString()) == 0) {
134 <<
"unexpected property '" << member->name.GetString() <<
"' must be removed";
153 if (!rules.IsArray()) {
160 if (!rule.IsObject()) {
171 bool found_replace =
false;
172 bool case_sensitive =
true;
173 string search_text, replace, trigger;
174 for (Value::ConstMemberIterator member = json_rule.MemberBegin();
175 member != json_rule.MemberEnd(); ++member) {
176 if (strcmp(
"search", member->name.GetString()) == 0) {
178 if (search_text.empty()) {
182 }
else if (strcmp(
"replace", member->name.GetString()) == 0) {
183 found_replace =
true;
185 }
else if (strcmp(
"trigger", member->name.GetString()) == 0) {
187 }
else if (strcmp(
"caseSensitive", member->name.GetString()) == 0) {
188 if (!
ReadRuleProperty(
"caseSensitive", json_rule, &case_sensitive,
false))
return;
189 }
else if (strcmp(
"description", member->name.GetString()) == 0) {
195 <<
"' must be removed";
199 if (search_text.empty()) {
202 }
else if (!found_replace) {
206 const Rule& rule =
Rule::Create(trigger, search_text, replace, case_sensitive);
211 (*g_rules).push_back(rule);
218 bool required =
true) {
219 const Value& json_value = rule[name.c_str()];
220 if (json_value.IsNull()) {
227 return ValidateTypeAndExtractValue(name, json_value, value);
233 #define EXTRACT_VALUE(json_type, cpp_type) \
234 bool ValidateTypeAndExtractValue(const string& name, const Value& json_value, \
236 if (!json_value.Is ## json_type()) { \
237 AddRuleParseError() << name << " property must be of type " #json_type \
238 << " but is a " << NameOfTypeOfJsonValue(json_value); \
241 *value = json_value.Get ## json_type(); \
265 FILE* rules_file = fopen(rules_file_path.c_str(),
"r");
266 if (rules_file == NULL) {
267 return Substitute(
"Could not open redaction rules file '$0'; $1",
268 rules_file_path, strerror(errno));
273 struct stat rules_file_stats;
274 if (fstat(fileno(rules_file), &rules_file_stats)) {
276 return Substitute(
"Error reading redaction rules file; $0", strerror(errno));
278 if (rules_file_stats.st_size == 0) {
283 rapidjson::FileStream stream(rules_file);
285 rules_doc.ParseStream<rapidjson::kParseDefaultFlags>(stream);
287 if (rules_doc.HasParseError()) {
288 return Substitute(
"Error parsing redaction rules; $0", rules_doc.GetParseError());
290 if (!rules_doc.IsObject()) {
291 return "Error parsing redaction rules; root element must be a JSON Object.";
293 const Value& version = rules_doc[
"version"];
294 if (version.IsNull()) {
295 return "Error parsing redaction rules; a document version is required.";
297 if (!version.IsInt()) {
298 return Substitute(
"Error parsing redaction rules; version must be an Integer but "
301 if (version.GetInt() != 1) {
302 return "Error parsing redaction rules; only version 1 is supported.";
306 return rules_parser.
Parse(rules_doc);
309 void Redact(
string* value,
bool* changed) {
310 DCHECK(value != NULL);
312 for (Rules::const_iterator rule =
g_rules->begin(); rule !=
g_rules->end(); ++rule) {
313 if (rule->case_sensitive()) {
314 if (value->find(rule->trigger) == string::npos)
continue;
316 if (strcasestr(value->c_str(), rule->trigger.c_str()) == NULL)
continue;
318 int replacement_count = re2::RE2::GlobalReplace(
319 value, rule->search_pattern, rule->replacement);
320 if (changed != NULL && !*changed) *changed = replacement_count;
bool ReadRuleProperty(const string &name, const Value &rule, T *value, bool required=true)
Rule(const string &trigger, const Regex &search_pattern, const string &replacement)
void Redact(string *value, bool *changed)
string Parse(const Document &rules_doc)
rapidjson::SizeType rule_idx_
const Rule & operator=(const Rule &other)
ostringstream error_message_
string SetRedactionRulesFromFile(const string &rules_file_path)
ostream & AddDocParseError()
static Rule Create(const string &trigger, const string &search_regex, const string &replacement, bool case_sensitive)
#define EXTRACT_VALUE(json_type, cpp_type)
const Regex search_pattern
void ParseRules(const Value &rules)
string NameOfTypeOfJsonValue(const Value &value)
bool case_sensitive() const
void ParseRule(const Value &json_rule)
ostream & AddRuleParseError()