OLD | NEW |
1 // Copyright 2016 The Chromium Authors. All rights reserved. | 1 // Copyright 2016 The Chromium Authors. All rights reserved. |
2 // Use of this source code is governed by a BSD-style license that can be | 2 // Use of this source code is governed by a BSD-style license that can be |
3 // found in the LICENSE file. | 3 // found in the LICENSE file. |
4 | 4 |
5 #include <iostream> | 5 #include <iostream> |
6 | 6 |
7 #include <map> | 7 #include <map> |
8 #include <set> | 8 #include <set> |
9 #include <string> | 9 #include <string> |
10 #include <vector> | 10 #include <vector> |
11 | 11 |
12 #include "base/command_line.h" | 12 #include "base/command_line.h" |
13 #include "base/files/file_util.h" | 13 #include "base/files/file_util.h" |
14 #include "base/json/json_reader.h" | |
15 #include "base/path_service.h" | 14 #include "base/path_service.h" |
16 #include "base/strings/string_piece.h" | 15 #include "base/strings/string_piece.h" |
17 #include "base/strings/string_split.h" | |
18 #include "base/strings/string_util.h" | |
19 #include "base/strings/utf_string_conversions.h" | 16 #include "base/strings/utf_string_conversions.h" |
20 #include "base/values.h" | |
21 #include "crypto/openssl_util.h" | 17 #include "crypto/openssl_util.h" |
22 #include "net/tools/transport_security_state_generator/cert_util.h" | 18 #include "net/tools/transport_security_state_generator/input_file_parsers.h" |
23 #include "net/tools/transport_security_state_generator/pinset.h" | |
24 #include "net/tools/transport_security_state_generator/pinsets.h" | 19 #include "net/tools/transport_security_state_generator/pinsets.h" |
25 #include "net/tools/transport_security_state_generator/preloaded_state_generator
.h" | 20 #include "net/tools/transport_security_state_generator/preloaded_state_generator
.h" |
26 #include "net/tools/transport_security_state_generator/spki_hash.h" | |
27 #include "net/tools/transport_security_state_generator/transport_security_state_
entry.h" | 21 #include "net/tools/transport_security_state_generator/transport_security_state_
entry.h" |
28 #include "third_party/boringssl/src/include/openssl/x509v3.h" | |
29 | 22 |
30 using net::transport_security_state::TransportSecurityStateEntry; | |
31 using net::transport_security_state::TransportSecurityStateEntries; | 23 using net::transport_security_state::TransportSecurityStateEntries; |
32 using net::transport_security_state::Pinset; | |
33 using net::transport_security_state::Pinsets; | 24 using net::transport_security_state::Pinsets; |
34 using net::transport_security_state::PreloadedStateGenerator; | 25 using net::transport_security_state::PreloadedStateGenerator; |
35 using net::transport_security_state::DomainIDList; | 26 using net::transport_security_state::DomainIDList; |
36 using net::transport_security_state::SPKIHash; | |
37 | 27 |
38 namespace { | 28 namespace { |
39 | 29 |
40 // Print the command line help. | 30 // Print the command line help. |
41 void PrintHelp() { | 31 void PrintHelp() { |
42 std::cout << "transport_security_state_generator <json-file> <pins-file>" | 32 std::cout << "transport_security_state_generator <json-file> <pins-file>" |
43 << " <template-file> <output-file> [-v]" << std::endl; | 33 << " <template-file> <output-file> [-v]" << std::endl; |
44 } | 34 } |
45 | 35 |
46 // Parses the |json| string and copies the items under the "entries" key to | |
47 // |entries|, the pinsets under the "pinsets" key to |pinsets|, and the domain | |
48 // IDs under the "domain_ids" key to |domain_ids|. | |
49 // | |
50 // More info on the format can be found in | |
51 // net/http/transport_security_state_static.json | |
52 bool ParseJSON(const std::string& json, | |
53 TransportSecurityStateEntries* entries, | |
54 Pinsets* pinsets, | |
55 DomainIDList* domain_ids) { | |
56 std::unique_ptr<base::Value> value = base::JSONReader::Read(json); | |
57 base::DictionaryValue* dict_value = nullptr; | |
58 if (!value.get() || !value->GetAsDictionary(&dict_value)) { | |
59 std::cerr << "Could not parse the input JSON" << std::endl; | |
60 return false; | |
61 } | |
62 | |
63 const base::ListValue* preload_entries = nullptr; | |
64 if (!dict_value->GetList("entries", &preload_entries)) { | |
65 std::cerr << "Could not parse the entries in the input JSON" << std::endl; | |
66 return false; | |
67 } | |
68 | |
69 for (size_t i = 0; i < preload_entries->GetSize(); ++i) { | |
70 const base::DictionaryValue* parsed = nullptr; | |
71 if (!preload_entries->GetDictionary(i, &parsed)) { | |
72 std::cerr << "Could not parse entry " << i << std::endl; | |
73 return false; | |
74 } | |
75 | |
76 std::unique_ptr<TransportSecurityStateEntry> entry( | |
77 new TransportSecurityStateEntry()); | |
78 | |
79 if (!parsed->GetString("name", &entry->hostname)) { | |
80 std::cerr << "Could not extract the name for entry " << i << std::endl; | |
81 return false; | |
82 } | |
83 | |
84 parsed->GetBoolean("include_subdomains", &entry->include_subdomains); | |
85 std::string mode; | |
86 parsed->GetString("mode", &mode); | |
87 entry->force_https = (mode == "force-https"); | |
88 parsed->GetBoolean("include_subdomains_for_pinning", | |
89 &entry->hpkp_include_subdomains); | |
90 parsed->GetString("pins", &entry->pinset); | |
91 parsed->GetBoolean("expect_ct", &entry->expect_ct); | |
92 parsed->GetString("expect_ct_report_uri", &entry->expect_ct_report_uri); | |
93 parsed->GetBoolean("expect_staple", &entry->expect_staple); | |
94 parsed->GetBoolean("include_subdomains_for_expect_staple", | |
95 &entry->expect_staple_include_subdomains); | |
96 parsed->GetString("expect_staple_report_uri", | |
97 &entry->expect_staple_report_uri); | |
98 | |
99 entries->push_back(std::move(entry)); | |
100 } | |
101 | |
102 const base::ListValue* pinsets_list = nullptr; | |
103 if (!dict_value->GetList("pinsets", &pinsets_list)) { | |
104 std::cerr << "Could not parse the pinsets in the input JSON" << std::endl; | |
105 return false; | |
106 } | |
107 | |
108 for (size_t i = 0; i < pinsets_list->GetSize(); ++i) { | |
109 const base::DictionaryValue* parsed = nullptr; | |
110 if (!pinsets_list->GetDictionary(i, &parsed)) { | |
111 std::cerr << "Could not parse pinset " << i << std::endl; | |
112 return false; | |
113 } | |
114 | |
115 std::string name; | |
116 if (!parsed->GetString("name", &name)) { | |
117 std::cerr << "Could not extract the name for pinset " << i << std::endl; | |
118 return false; | |
119 } | |
120 | |
121 std::string report_uri; | |
122 parsed->GetString("report_uri", &report_uri); | |
123 | |
124 std::unique_ptr<Pinset> pinset(new Pinset(name, report_uri)); | |
125 | |
126 const base::ListValue* pinset_static_hashes_list = nullptr; | |
127 if (parsed->GetList("static_spki_hashes", &pinset_static_hashes_list)) { | |
128 for (size_t i = 0; i < pinset_static_hashes_list->GetSize(); ++i) { | |
129 std::string hash; | |
130 pinset_static_hashes_list->GetString(i, &hash); | |
131 pinset->AddStaticSPKIHash(hash); | |
132 } | |
133 } | |
134 | |
135 const base::ListValue* pinset_bad_static_hashes_list = nullptr; | |
136 if (parsed->GetList("bad_static_spki_hashes", | |
137 &pinset_bad_static_hashes_list)) { | |
138 for (size_t i = 0; i < pinset_bad_static_hashes_list->GetSize(); ++i) { | |
139 std::string hash; | |
140 pinset_bad_static_hashes_list->GetString(i, &hash); | |
141 pinset->AddBadStaticSPKIHash(hash); | |
142 } | |
143 } | |
144 | |
145 pinsets->RegisterPinset(std::move(pinset)); | |
146 } | |
147 | |
148 // TODO(Martijnc): Remove the domain IDs from the preload format. | |
149 // https://crbug.com/661206. | |
150 const base::ListValue* domain_ids_list = nullptr; | |
151 if (!dict_value->GetList("domain_ids", &domain_ids_list)) { | |
152 std::cerr << "Failed parsing JSON (domain_ids)" << std::endl; | |
153 return false; | |
154 } | |
155 | |
156 for (size_t i = 0; i < domain_ids_list->GetSize(); ++i) { | |
157 std::string domain; | |
158 domain_ids_list->GetString(i, &domain); | |
159 domain_ids->push_back(domain); | |
160 } | |
161 | |
162 return true; | |
163 } | |
164 | |
165 bool IsImportantWordInCertificateName(base::StringPiece name) { | |
166 const char* const important_words[] = {"Universal", "Global", "EV", "G1", | |
167 "G2", "G3", "G4", "G5"}; | |
168 for (auto* important_word : important_words) { | |
169 if (name == important_word) { | |
170 return true; | |
171 } | |
172 } | |
173 return false; | |
174 } | |
175 | |
176 // Strips all characters not matched by the RegEx [A-Za-z0-9_] from |name| and | |
177 // returns the result. | |
178 std::string FilterName(base::StringPiece name) { | |
179 std::string filtered; | |
180 for (const char& character : name) { | |
181 if ((character >= '0' && character <= '9') || | |
182 (character >= 'a' && character <= 'z') || | |
183 (character >= 'A' && character <= 'Z') || character == '_') { | |
184 filtered += character; | |
185 } | |
186 } | |
187 return base::ToLowerASCII(filtered); | |
188 } | |
189 | |
190 // Returns true if |pin_name| is a reasonable match for the certificate name | |
191 // |name|. | |
192 bool MatchCertificateName(base::StringPiece name, base::StringPiece pin_name) { | |
193 std::vector<base::StringPiece> words = base::SplitStringPiece( | |
194 name, " ", base::TRIM_WHITESPACE, base::SPLIT_WANT_ALL); | |
195 if (words.empty()) { | |
196 std::cerr << "no words in certificate name" << std::endl; | |
197 return false; | |
198 } | |
199 base::StringPiece first_word = words[0]; | |
200 | |
201 if (first_word.ends_with(",")) { | |
202 first_word = first_word.substr(0, first_word.size() - 1); | |
203 } | |
204 | |
205 if (first_word.starts_with("*.")) { | |
206 first_word = first_word.substr(2, first_word.size() - 2); | |
207 } | |
208 | |
209 size_t pos = first_word.find('.'); | |
210 if (pos != std::string::npos) { | |
211 first_word = first_word.substr(0, first_word.size() - pos); | |
212 } | |
213 | |
214 pos = first_word.find('-'); | |
215 if (pos != std::string::npos) { | |
216 first_word = first_word.substr(0, first_word.size() - pos); | |
217 } | |
218 | |
219 if (first_word.empty()) { | |
220 std::cerr << "first word of certificate name is empty" << std::endl; | |
221 return false; | |
222 } | |
223 | |
224 std::string filtered_word = FilterName(first_word); | |
225 first_word = filtered_word; | |
226 if (!base::EqualsCaseInsensitiveASCII(pin_name.substr(0, first_word.size()), | |
227 first_word)) { | |
228 std::cerr << "the first word of the certificate name (" | |
229 << first_word.as_string() | |
230 << ") isn't a prefix of the variable name (" | |
231 << pin_name.as_string() << ")" << std::endl; | |
232 return false; | |
233 } | |
234 | |
235 for (size_t i = 0; i < words.size(); ++i) { | |
236 const base::StringPiece& word = words[i]; | |
237 if (word == "Class" && (i + 1) < words.size()) { | |
238 std::string class_name = word.as_string(); | |
239 words[i + 1].AppendToString(&class_name); | |
240 | |
241 size_t pos = pin_name.find(class_name); | |
242 if (pos == std::string::npos) { | |
243 std::cerr << "class specification doesn't appear in the variable name" | |
244 << std::endl; | |
245 return false; | |
246 } | |
247 } else if (word.size() == 1 && word[0] >= '0' && word[0] <= '9') { | |
248 size_t pos = pin_name.find(word); | |
249 if (pos == std::string::npos) { | |
250 std::cerr << "number doesn't appear in the variable name" << std::endl; | |
251 return false; | |
252 } | |
253 } else if (IsImportantWordInCertificateName(word)) { | |
254 size_t pos = pin_name.find(word); | |
255 if (pos == std::string::npos) { | |
256 std::cerr << word.as_string() << " doesn't appear in the variable name" | |
257 << std::endl; | |
258 return false; | |
259 } | |
260 } | |
261 } | |
262 | |
263 return true; | |
264 } | |
265 | |
266 // Returns true iff |candidate| is not empty, the first character is in the | |
267 // range A-Z, and the remaining characters are in the ranges a-Z, 0-9, or '_'. | |
268 bool IsValidName(const std::string& candidate) { | |
269 if (candidate.empty() || candidate[0] < 'A' || candidate[0] > 'Z') { | |
270 return false; | |
271 } | |
272 | |
273 bool isValid = true; | |
274 for (const char& character : candidate) { | |
275 isValid = (character >= '0' && character <= '9') || | |
276 (character >= 'a' && character <= 'z') || | |
277 (character >= 'A' && character <= 'Z') || character == '_'; | |
278 if (!isValid) { | |
279 return false; | |
280 } | |
281 } | |
282 return true; | |
283 } | |
284 | |
285 static const char kStartOfCert[] = "-----BEGIN CERTIFICATE"; | |
286 static const char kStartOfPublicKey[] = "-----BEGIN PUBLIC KEY"; | |
287 static const char kEndOfCert[] = "-----END CERTIFICATE"; | |
288 static const char kEndOfPublicKey[] = "-----END PUBLIC KEY"; | |
289 static const char kStartOfSHA256[] = "sha256/"; | |
290 | |
291 enum class CertificateParserState { | |
292 PRE_NAME, | |
293 POST_NAME, | |
294 IN_CERTIFICATE, | |
295 IN_PUBLIC_KEY | |
296 }; | |
297 | |
298 // Extracts SPKI information from the preloaded pins file. The SPKI's can be | |
299 // in the form of a PEM certificate, a PEM public key, or a BASE64 string. | |
300 // | |
301 // More info on the format can be found in | |
302 // net/http/transport_security_state_static.pins | |
303 bool ParseCertificatesFile(const std::string& certs_input, Pinsets* pinsets) { | |
304 std::istringstream input_stream(certs_input); | |
305 std::string line; | |
306 CertificateParserState current_state = CertificateParserState::PRE_NAME; | |
307 | |
308 const base::CompareCase& compare_mode = base::CompareCase::INSENSITIVE_ASCII; | |
309 std::string name; | |
310 std::string buffer; | |
311 std::string subject_name; | |
312 bssl::UniquePtr<X509> certificate; | |
313 SPKIHash hash; | |
314 | |
315 for (std::string line; std::getline(input_stream, line);) { | |
316 if (line[0] == '#') { | |
317 continue; | |
318 } | |
319 | |
320 if (line.empty() && current_state == CertificateParserState::PRE_NAME) { | |
321 continue; | |
322 } | |
323 | |
324 switch (current_state) { | |
325 case CertificateParserState::PRE_NAME: | |
326 if (!IsValidName(line)) { | |
327 std::cerr << "Invalid name in certificates file: " << line; | |
328 return false; | |
329 } | |
330 name = line; | |
331 current_state = CertificateParserState::POST_NAME; | |
332 break; | |
333 case CertificateParserState::POST_NAME: | |
334 if (base::StartsWith(line, kStartOfSHA256, compare_mode)) { | |
335 if (!hash.FromString(line)) { | |
336 std::cerr << "Invalid hash value in certificate file for " << name | |
337 << std::endl; | |
338 return false; | |
339 } | |
340 | |
341 pinsets->RegisterSPKIHash(name, hash); | |
342 current_state = CertificateParserState::PRE_NAME; | |
343 } else if (base::StartsWith(line, kStartOfCert, compare_mode)) { | |
344 buffer = line + '\n'; | |
345 current_state = CertificateParserState::IN_CERTIFICATE; | |
346 } else if (base::StartsWith(line, kStartOfPublicKey, compare_mode)) { | |
347 buffer = line + '\n'; | |
348 current_state = CertificateParserState::IN_PUBLIC_KEY; | |
349 } else { | |
350 std::cerr << "Invalid value in certificates file for " << name | |
351 << std::endl; | |
352 return false; | |
353 } | |
354 break; | |
355 case CertificateParserState::IN_CERTIFICATE: | |
356 buffer += line + '\n'; | |
357 if (!base::StartsWith(line, kEndOfCert, compare_mode)) { | |
358 continue; | |
359 } | |
360 | |
361 certificate = GetX509CertificateFromPEM(buffer); | |
362 if (!certificate) { | |
363 std::cerr << "Could not parse certificate " << name << std::endl; | |
364 return false; | |
365 } | |
366 | |
367 if (!CalculateSPKIHashFromCertificate(certificate.get(), &hash)) { | |
368 std::cerr << "Could not extract SPKI from certificate " << name | |
369 << std::endl; | |
370 return false; | |
371 } | |
372 | |
373 if (!ExtractSubjectNameFromCertificate(certificate.get(), | |
374 &subject_name)) { | |
375 std::cerr << "Could not extract name from certificate " << name | |
376 << std::endl; | |
377 return false; | |
378 } | |
379 | |
380 if (!MatchCertificateName(subject_name, name)) { | |
381 std::cerr << name << " is not a reasonable name for " << subject_name | |
382 << std::endl; | |
383 return false; | |
384 } | |
385 | |
386 pinsets->RegisterSPKIHash(name, hash); | |
387 current_state = CertificateParserState::PRE_NAME; | |
388 break; | |
389 case CertificateParserState::IN_PUBLIC_KEY: | |
390 buffer += line + '\n'; | |
391 if (!base::StartsWith(line, kEndOfPublicKey, compare_mode)) { | |
392 continue; | |
393 } | |
394 | |
395 if (!CalculateSPKIHashFromKey(buffer, &hash)) { | |
396 std::cerr << "Parsing of the public key " << name << " failed" | |
397 << std::endl; | |
398 return false; | |
399 } | |
400 | |
401 pinsets->RegisterSPKIHash(name, hash); | |
402 current_state = CertificateParserState::PRE_NAME; | |
403 break; | |
404 default: | |
405 DCHECK(false) << "Unknown parser state"; | |
406 } | |
407 } | |
408 | |
409 return true; | |
410 } | |
411 | |
412 // Checks if there are pins with the same name or the same hash. | 36 // Checks if there are pins with the same name or the same hash. |
413 bool CheckForDuplicatePins(const Pinsets& pinsets) { | 37 bool CheckForDuplicatePins(const Pinsets& pinsets) { |
414 std::set<std::string> seen_names; | 38 std::set<std::string> seen_names; |
415 std::map<std::string, std::string> seen_hashes; | 39 std::map<std::string, std::string> seen_hashes; |
416 | 40 |
417 for (const auto& pin : pinsets.spki_hashes()) { | 41 for (const auto& pin : pinsets.spki_hashes()) { |
418 if (seen_names.find(pin.first) != seen_names.cend()) { | 42 if (seen_names.find(pin.first) != seen_names.cend()) { |
419 std::cerr << "Duplicate pin name " << pin.first << std::endl; | 43 std::cerr << "Duplicate pin name " << pin.first << std::endl; |
420 return false; | 44 return false; |
421 } | 45 } |
(...skipping 125 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
547 args.push_back(base::WideToUTF8(arg)); | 171 args.push_back(base::WideToUTF8(arg)); |
548 } | 172 } |
549 #else | 173 #else |
550 base::CommandLine::StringVector args = command_line.GetArgs(); | 174 base::CommandLine::StringVector args = command_line.GetArgs(); |
551 #endif | 175 #endif |
552 if (args.size() < 4U) { | 176 if (args.size() < 4U) { |
553 PrintHelp(); | 177 PrintHelp(); |
554 return 1; | 178 return 1; |
555 } | 179 } |
556 | 180 |
557 bool verbose = command_line.HasSwitch("v"); | |
558 | |
559 base::FilePath json_filepath = base::FilePath::FromUTF8Unsafe(argv[1]); | 181 base::FilePath json_filepath = base::FilePath::FromUTF8Unsafe(argv[1]); |
560 if (!base::PathExists(json_filepath)) { | 182 if (!base::PathExists(json_filepath)) { |
561 std::cerr << "Input JSON file doesn't exist." << std::endl; | 183 std::cerr << "Input JSON file doesn't exist." << std::endl; |
562 return 1; | 184 return 1; |
563 } | 185 } |
564 json_filepath = base::MakeAbsoluteFilePath(json_filepath); | 186 json_filepath = base::MakeAbsoluteFilePath(json_filepath); |
565 | 187 |
566 std::string json_input; | 188 std::string json_input; |
567 if (!base::ReadFileToString(json_filepath, &json_input)) { | 189 if (!base::ReadFileToString(json_filepath, &json_input)) { |
568 std::cerr << "Could not read input JSON file." << std::endl; | 190 std::cerr << "Could not read input JSON file." << std::endl; |
(...skipping 10 matching lines...) Expand all Loading... |
579 std::string certs_input; | 201 std::string certs_input; |
580 if (!base::ReadFileToString(pins_filepath, &certs_input)) { | 202 if (!base::ReadFileToString(pins_filepath, &certs_input)) { |
581 std::cerr << "Could not read input pins file." << std::endl; | 203 std::cerr << "Could not read input pins file." << std::endl; |
582 return 1; | 204 return 1; |
583 } | 205 } |
584 | 206 |
585 TransportSecurityStateEntries entries; | 207 TransportSecurityStateEntries entries; |
586 Pinsets pinsets; | 208 Pinsets pinsets; |
587 DomainIDList domain_ids; | 209 DomainIDList domain_ids; |
588 | 210 |
589 if (!ParseCertificatesFile(certs_input, &pinsets)) { | 211 std::vector<std::string> errors; |
590 std::cerr << "Error while parsing the pins file." << std::endl; | 212 if (!ParseCertificatesFile(certs_input, &pinsets, &errors) || |
591 return 1; | 213 !ParseJSON(json_input, &entries, &pinsets, &domain_ids, &errors)) { |
592 } | 214 std::cerr << "Error while parsing the iput files." << std::endl; |
593 if (!ParseJSON(json_input, &entries, &pinsets, &domain_ids)) { | 215 |
594 std::cerr << "Error while parsing the JSON file." << std::endl; | 216 for (const auto& error : errors) { |
| 217 std::cerr << error << std::endl; |
| 218 } |
| 219 |
595 return 1; | 220 return 1; |
596 } | 221 } |
597 | 222 |
598 if (!CheckDuplicateEntries(entries) || !CheckNoopEntries(entries) || | 223 if (!CheckDuplicateEntries(entries) || !CheckNoopEntries(entries) || |
599 !CheckSubdomainsFlags(entries) || !CheckForDuplicatePins(pinsets) || | 224 !CheckSubdomainsFlags(entries) || !CheckForDuplicatePins(pinsets) || |
600 !CheckCertificatesInPinsets(pinsets)) { | 225 !CheckCertificatesInPinsets(pinsets)) { |
601 std::cerr << "Checks failed. Aborting." << std::endl; | 226 std::cerr << "Checks failed. Aborting." << std::endl; |
602 return 1; | 227 return 1; |
603 } | 228 } |
604 | 229 |
605 base::FilePath template_path = base::FilePath::FromUTF8Unsafe(argv[3]); | 230 base::FilePath template_path = base::FilePath::FromUTF8Unsafe(argv[3]); |
606 if (!base::PathExists(template_path)) { | 231 if (!base::PathExists(template_path)) { |
607 std::cerr << "Template file doesn't exist." << std::endl; | 232 std::cerr << "Template file doesn't exist." << std::endl; |
608 return 1; | 233 return 1; |
609 } | 234 } |
610 template_path = base::MakeAbsoluteFilePath(template_path); | 235 template_path = base::MakeAbsoluteFilePath(template_path); |
611 | 236 |
612 std::string preload_template; | 237 std::string preload_template; |
613 if (!base::ReadFileToString(template_path, &preload_template)) { | 238 if (!base::ReadFileToString(template_path, &preload_template)) { |
614 std::cerr << "Could not read template file." << std::endl; | 239 std::cerr << "Could not read template file." << std::endl; |
615 return 1; | 240 return 1; |
616 } | 241 } |
617 | 242 |
618 std::string result; | 243 std::string result; |
619 PreloadedStateGenerator generator; | 244 PreloadedStateGenerator generator; |
620 result = generator.Generate(preload_template, entries, domain_ids, pinsets, | 245 if (!generator.Generate(preload_template, entries, domain_ids, pinsets, |
621 verbose); | 246 &result)) { |
| 247 std::cerr << "Trie generation failed." << std::endl; |
| 248 return 1; |
| 249 } |
622 | 250 |
623 base::FilePath output_path; | 251 base::FilePath output_path; |
624 output_path = base::FilePath::FromUTF8Unsafe(argv[4]); | 252 output_path = base::FilePath::FromUTF8Unsafe(argv[4]); |
625 | 253 |
626 if (base::WriteFile(output_path, result.c_str(), | 254 if (base::WriteFile(output_path, result.c_str(), |
627 static_cast<uint32_t>(result.size())) <= 0) { | 255 static_cast<uint32_t>(result.size())) <= 0) { |
628 std::cerr << "Failed to write output." << std::endl; | 256 std::cerr << "Failed to write output." << std::endl; |
629 return 1; | 257 return 1; |
630 } | 258 } |
631 | 259 |
| 260 if (command_line.HasSwitch("v")) { |
| 261 std::cout << "Wrote trie containing " << entries.size() |
| 262 << " entries, referencing " << pinsets.size() << " pinsets and " |
| 263 << domain_ids.size() << " domain IDs to " |
| 264 << output_path.AsUTF8Unsafe() << std::endl; |
| 265 } |
| 266 |
632 return 0; | 267 return 0; |
633 } | 268 } |
OLD | NEW |