- add sources.
[platform/framework/web/crosswalk.git] / src / net / tools / tld_cleanup / tld_cleanup_util.cc
1 // Copyright 2013 The Chromium Authors. All rights reserved.
2 // Use of this source code is governed by a BSD-style license that can be
3 // found in the LICENSE file.
4
5 #include "net/tools/tld_cleanup/tld_cleanup_util.h"
6
7 #include "base/file_util.h"
8 #include "base/logging.h"
9 #include "base/strings/string_number_conversions.h"
10 #include "base/strings/string_util.h"
11 #include "url/gurl.h"
12 #include "url/url_parse.h"
13
14 namespace {
15
16 const char kBeginPrivateDomainsComment[] = "// ===BEGIN PRIVATE DOMAINS===";
17 const char kEndPrivateDomainsComment[] = "// ===END PRIVATE DOMAINS===";
18
19 const int kExceptionRule = 1;
20 const int kWildcardRule = 2;
21 const int kPrivateRule = 4;
22 }
23
24 namespace net {
25 namespace tld_cleanup {
26
27 // Writes the list of domain rules contained in the 'rules' set to the
28 // 'outfile', with each rule terminated by a LF.  The file must already have
29 // been created with write access.
30 bool WriteRules(const RuleMap& rules, const base::FilePath& outfile) {
31   std::string data;
32   data.append("%{\n"
33               "// Copyright 2012 The Chromium Authors. All rights reserved.\n"
34               "// Use of this source code is governed by a BSD-style license "
35               "that can be\n"
36               "// found in the LICENSE file.\n\n"
37               "// This file is generated by net/tools/tld_cleanup/.\n"
38               "// DO NOT MANUALLY EDIT!\n"
39               "%}\n"
40               "struct DomainRule {\n"
41               "  int name_offset;\n"
42               "  int type;  // flags: 1: exception, 2: wildcard, 4: private\n"
43               "};\n"
44               "%%\n");
45
46   for (RuleMap::const_iterator i = rules.begin(); i != rules.end(); ++i) {
47     data.append(i->first);
48     data.append(", ");
49     int type = 0;
50     if (i->second.exception) {
51       type = kExceptionRule;
52     } else if (i->second.wildcard) {
53       type = kWildcardRule;
54     }
55     if (i->second.is_private) {
56       type += kPrivateRule;
57     }
58     data.append(base::IntToString(type));
59     data.append("\n");
60   }
61
62   data.append("%%\n");
63
64   int written = file_util::WriteFile(outfile,
65                                      data.data(),
66                                      static_cast<int>(data.size()));
67
68   return written == static_cast<int>(data.size());
69 }
70
71 // Adjusts the rule to a standard form: removes single extraneous dots and
72 // canonicalizes it using GURL. Returns kSuccess if the rule is interpreted as
73 // valid; logs a warning and returns kWarning if it is probably invalid; and
74 // logs an error and returns kError if the rule is (almost) certainly invalid.
75 NormalizeResult NormalizeRule(std::string* domain, Rule* rule) {
76   NormalizeResult result = kSuccess;
77
78   // Strip single leading and trailing dots.
79   if (domain->at(0) == '.')
80     domain->erase(0, 1);
81   if (domain->empty()) {
82     LOG(WARNING) << "Ignoring empty rule";
83     return kWarning;
84   }
85   if (domain->at(domain->size() - 1) == '.')
86     domain->erase(domain->size() - 1, 1);
87   if (domain->empty()) {
88     LOG(WARNING) << "Ignoring empty rule";
89     return kWarning;
90   }
91
92   // Allow single leading '*.' or '!', saved here so it's not canonicalized.
93   size_t start_offset = 0;
94   if (domain->at(0) == '!') {
95     domain->erase(0, 1);
96     rule->exception = true;
97   } else if (domain->find("*.") == 0) {
98     domain->erase(0, 2);
99     rule->wildcard = true;
100   }
101   if (domain->empty()) {
102     LOG(WARNING) << "Ignoring empty rule";
103     return kWarning;
104   }
105
106   // Warn about additional '*.' or '!'.
107   if (domain->find("*.", start_offset) != std::string::npos ||
108       domain->find('!', start_offset) != std::string::npos) {
109     LOG(WARNING) << "Keeping probably invalid rule: " << *domain;
110     result = kWarning;
111   }
112
113   // Make a GURL and normalize it, then get the host back out.
114   std::string url = "http://";
115   url.append(*domain);
116   GURL gurl(url);
117   const std::string& spec = gurl.possibly_invalid_spec();
118   url_parse::Component host = gurl.parsed_for_possibly_invalid_spec().host;
119   if (host.len < 0) {
120     LOG(ERROR) << "Ignoring rule that couldn't be normalized: " << *domain;
121     return kError;
122   }
123   if (!gurl.is_valid()) {
124     LOG(WARNING) << "Keeping rule that GURL says is invalid: " << *domain;
125     result = kWarning;
126   }
127   domain->assign(spec.substr(host.begin, host.len));
128
129   return result;
130 }
131
132 NormalizeResult NormalizeDataToRuleMap(const std::string data,
133                                        RuleMap* rules) {
134   CHECK(rules);
135   // We do a lot of string assignment during parsing, but simplicity is more
136   // important than performance here.
137   std::string domain;
138   NormalizeResult result = kSuccess;
139   size_t line_start = 0;
140   size_t line_end = 0;
141   bool is_private = false;
142   RuleMap extra_rules;
143   int begin_private_length = arraysize(kBeginPrivateDomainsComment) - 1;
144   int end_private_length = arraysize(kEndPrivateDomainsComment) - 1;
145   while (line_start < data.size()) {
146     if (line_start + begin_private_length < data.size() &&
147         !data.compare(line_start, begin_private_length,
148                       kBeginPrivateDomainsComment)) {
149       is_private = true;
150       line_end = line_start + begin_private_length;
151     } else if (line_start + end_private_length < data.size() &&
152         !data.compare(line_start, end_private_length,
153                       kEndPrivateDomainsComment)) {
154       is_private = false;
155       line_end = line_start + end_private_length;
156     } else if (line_start + 1 < data.size() &&
157         data[line_start] == '/' &&
158         data[line_start + 1] == '/') {
159       // Skip comments.
160       line_end = data.find_first_of("\r\n", line_start);
161       if (line_end == std::string::npos)
162         line_end = data.size();
163     } else {
164       // Truncate at first whitespace.
165       line_end = data.find_first_of("\r\n \t", line_start);
166       if (line_end == std::string::npos)
167         line_end = data.size();
168       domain.assign(data.data(), line_start, line_end - line_start);
169
170       Rule rule;
171       rule.wildcard = false;
172       rule.exception = false;
173       rule.is_private = is_private;
174       NormalizeResult new_result = NormalizeRule(&domain, &rule);
175       if (new_result != kError) {
176         // Check the existing rules to make sure we don't have an exception and
177         // wildcard for the same rule, or that the same domain is listed as both
178         // private and not private. If we did, we'd have to update our
179         // parsing code to handle this case.
180         CHECK(rules->find(domain) == rules->end());
181
182         (*rules)[domain] = rule;
183         // Add true TLD for multi-level rules.  We don't add them right now, in
184         // case there's an exception or wild card that either exists or might be
185         // added in a later iteration.  In those cases, there's no need to add
186         // it and it would just slow down parsing the data.
187         size_t tld_start = domain.find_last_of('.');
188         if (tld_start != std::string::npos && tld_start + 1 < domain.size()) {
189           std::string extra_rule_domain = domain.substr(tld_start + 1);
190           RuleMap::const_iterator iter = extra_rules.find(extra_rule_domain);
191           Rule extra_rule;
192           extra_rule.exception = false;
193           extra_rule.wildcard = false;
194           if (iter == extra_rules.end()) {
195             extra_rule.is_private = is_private;
196           } else {
197             // A rule already exists, so we ensure that if any of the entries is
198             // not private the result should be that the entry is not private.
199             // An example is .au which is not listed as a real TLD, but only
200             // lists second-level domains such as com.au. Subdomains of .au
201             // (eg. blogspot.com.au) are also listed in the private section,
202             // which is processed later, so this ensures that the real TLD
203             // (eg. .au) is listed as public.
204             extra_rule.is_private = is_private && iter->second.is_private;
205           }
206           extra_rules[extra_rule_domain] = extra_rule;
207         }
208       }
209       result = std::max(result, new_result);
210     }
211
212     // Find beginning of next non-empty line.
213     line_start = data.find_first_of("\r\n", line_end);
214     if (line_start == std::string::npos)
215       line_start = data.size();
216     line_start = data.find_first_not_of("\r\n", line_start);
217     if (line_start == std::string::npos)
218       line_start = data.size();
219   }
220
221   for (RuleMap::const_iterator iter = extra_rules.begin();
222        iter != extra_rules.end();
223        ++iter) {
224     if (rules->find(iter->first) == rules->end()) {
225       (*rules)[iter->first] = iter->second;
226     }
227   }
228
229   return result;
230 }
231
232 NormalizeResult NormalizeFile(const base::FilePath& in_filename,
233                               const base::FilePath& out_filename) {
234   RuleMap rules;
235   std::string data;
236   if (!base::ReadFileToString(in_filename, &data)) {
237     LOG(ERROR) << "Unable to read file";
238     // We return success since we've already reported the error.
239     return kSuccess;
240   }
241
242   NormalizeResult result = NormalizeDataToRuleMap(data, &rules);
243
244   if (!WriteRules(rules, out_filename)) {
245     LOG(ERROR) << "Error(s) writing output file";
246     result = kError;
247   }
248
249   return result;
250 }
251
252
253 }  // namespace tld_cleanup
254 }  // namespace net