- add sources.
[platform/framework/web/crosswalk.git] / src / chrome / browser / safe_browsing / safe_browsing_util.cc
1 // Copyright (c) 2012 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 "chrome/browser/safe_browsing/safe_browsing_util.h"
6
7 #include "base/logging.h"
8 #include "base/strings/string_util.h"
9 #include "base/strings/stringprintf.h"
10 #include "chrome/browser/google/google_util.h"
11 #include "crypto/sha2.h"
12 #include "net/base/escape.h"
13 #include "url/gurl.h"
14 #include "url/url_util.h"
15
16 #if defined(OS_WIN)
17 #include "chrome/installer/util/browser_distribution.h"
18 #endif
19
20 static const char kReportParams[] = "?tpl=%s&url=%s";
21
22 // SBChunk ---------------------------------------------------------------------
23
24 SBChunk::SBChunk()
25     : chunk_number(0),
26       list_id(0),
27       is_add(false) {
28 }
29
30 SBChunk::~SBChunk() {}
31
32 // SBChunkList -----------------------------------------------------------------
33
34 SBChunkList::SBChunkList() {}
35
36 SBChunkList::~SBChunkList() {
37   clear();
38 }
39
40 void SBChunkList::clear() {
41   for (std::vector<SBChunk>::iterator citer = chunks_.begin();
42        citer != chunks_.end(); ++citer) {
43     for (std::deque<SBChunkHost>::iterator hiter = citer->hosts.begin();
44          hiter != citer->hosts.end(); ++hiter) {
45       if (hiter->entry) {
46         hiter->entry->Destroy();
47         hiter->entry = NULL;
48       }
49     }
50   }
51   chunks_.clear();
52 }
53
54 // SBListChunkRanges -----------------------------------------------------------
55
56 SBListChunkRanges::SBListChunkRanges(const std::string& n) : name(n) {}
57
58 // SBChunkDelete ---------------------------------------------------------------
59
60 SBChunkDelete::SBChunkDelete() : is_sub_del(false) {}
61
62 SBChunkDelete::~SBChunkDelete() {}
63
64 // SBEntry ---------------------------------------------------------------------
65
66 // static
67 SBEntry* SBEntry::Create(Type type, int prefix_count) {
68   int size = Size(type, prefix_count);
69   SBEntry *rv = static_cast<SBEntry*>(malloc(size));
70   memset(rv, 0, size);
71   rv->set_type(type);
72   rv->set_prefix_count(prefix_count);
73   return rv;
74 }
75
76 void SBEntry::Destroy() {
77   free(this);
78 }
79
80 // static
81 int SBEntry::PrefixSize(Type type) {
82   switch (type) {
83     case ADD_PREFIX:
84       return sizeof(SBPrefix);
85     case ADD_FULL_HASH:
86       return sizeof(SBFullHash);
87     case SUB_PREFIX:
88       return sizeof(SBSubPrefix);
89     case SUB_FULL_HASH:
90       return sizeof(SBSubFullHash);
91     default:
92       NOTREACHED();
93       return 0;
94   }
95 }
96
97 int SBEntry::Size() const {
98   return Size(type(), prefix_count());
99 }
100
101 // static
102 int SBEntry::Size(Type type, int prefix_count) {
103   return sizeof(Data) + prefix_count * PrefixSize(type);
104 }
105
106 int SBEntry::ChunkIdAtPrefix(int index) const {
107   if (type() == SUB_PREFIX)
108     return sub_prefixes_[index].add_chunk;
109   return (type() == SUB_FULL_HASH) ?
110       sub_full_hashes_[index].add_chunk : chunk_id();
111 }
112
113 void SBEntry::SetChunkIdAtPrefix(int index, int chunk_id) {
114   DCHECK(IsSub());
115
116   if (type() == SUB_PREFIX)
117     sub_prefixes_[index].add_chunk = chunk_id;
118   else
119     sub_full_hashes_[index].add_chunk = chunk_id;
120 }
121
122 const SBPrefix& SBEntry::PrefixAt(int index) const {
123   DCHECK(IsPrefix());
124
125   return IsAdd() ? add_prefixes_[index] : sub_prefixes_[index].prefix;
126 }
127
128 const SBFullHash& SBEntry::FullHashAt(int index) const {
129   DCHECK(!IsPrefix());
130
131   return IsAdd() ? add_full_hashes_[index] : sub_full_hashes_[index].prefix;
132 }
133
134 void SBEntry::SetPrefixAt(int index, const SBPrefix& prefix) {
135   DCHECK(IsPrefix());
136
137   if (IsAdd())
138     add_prefixes_[index] = prefix;
139   else
140     sub_prefixes_[index].prefix = prefix;
141 }
142
143 void SBEntry::SetFullHashAt(int index, const SBFullHash& full_hash) {
144   DCHECK(!IsPrefix());
145
146   if (IsAdd())
147     add_full_hashes_[index] = full_hash;
148   else
149     sub_full_hashes_[index].prefix = full_hash;
150 }
151
152
153 // Utility functions -----------------------------------------------------------
154
155 namespace safe_browsing_util {
156
157 // Listnames that browser can process.
158 const char kMalwareList[] = "goog-malware-shavar";
159 const char kPhishingList[] = "goog-phish-shavar";
160 const char kBinUrlList[] = "goog-badbinurl-shavar";
161 // We don't use the bad binary digest list anymore.  Use a fake listname to be
162 // sure we don't request it accidentally.
163 const char kBinHashList[] = "goog-badbin-digestvar-disabled";
164 const char kCsdWhiteList[] = "goog-csdwhite-sha256";
165 const char kDownloadWhiteList[] = "goog-downloadwhite-digest256";
166 const char kExtensionBlacklist[] = "goog-badcrxids-digestvar";
167 const char kSideEffectFreeWhitelist[] = "goog-sideeffectfree-shavar";
168 const char kIPBlacklist[] = "goog-badip-digest256";
169
170 ListType GetListId(const std::string& name) {
171   ListType id;
172   if (name == safe_browsing_util::kMalwareList) {
173     id = MALWARE;
174   } else if (name == safe_browsing_util::kPhishingList) {
175     id = PHISH;
176   } else if (name == safe_browsing_util::kBinUrlList) {
177     id = BINURL;
178   } else if (name == safe_browsing_util::kBinHashList) {
179     id = BINHASH;
180   } else if (name == safe_browsing_util::kCsdWhiteList) {
181     id = CSDWHITELIST;
182   } else if (name == safe_browsing_util::kDownloadWhiteList) {
183     id = DOWNLOADWHITELIST;
184   } else if (name == safe_browsing_util::kExtensionBlacklist) {
185     id = EXTENSIONBLACKLIST;
186   } else if (name == safe_browsing_util::kSideEffectFreeWhitelist) {
187     id = SIDEEFFECTFREEWHITELIST;
188   } else if (name == safe_browsing_util::kIPBlacklist) {
189     id = IPBLACKLIST;
190   } else {
191     id = INVALID;
192   }
193   return id;
194 }
195
196 bool GetListName(ListType list_id, std::string* list) {
197   switch (list_id) {
198     case MALWARE:
199       *list = safe_browsing_util::kMalwareList;
200       break;
201     case PHISH:
202       *list = safe_browsing_util::kPhishingList;
203       break;
204     case BINURL:
205       *list = safe_browsing_util::kBinUrlList;
206       break;
207     case BINHASH:
208       *list = safe_browsing_util::kBinHashList;
209       break;
210     case CSDWHITELIST:
211       *list = safe_browsing_util::kCsdWhiteList;
212       break;
213     case DOWNLOADWHITELIST:
214       *list = safe_browsing_util::kDownloadWhiteList;
215       break;
216     case EXTENSIONBLACKLIST:
217       *list = safe_browsing_util::kExtensionBlacklist;
218       break;
219     case SIDEEFFECTFREEWHITELIST:
220       *list = safe_browsing_util::kSideEffectFreeWhitelist;
221       break;
222     case IPBLACKLIST:
223       *list = safe_browsing_util::kIPBlacklist;
224       break;
225     default:
226       return false;
227   }
228   return true;
229 }
230
231 std::string Unescape(const std::string& url) {
232   std::string unescaped_str(url);
233   std::string old_unescaped_str;
234   const int kMaxLoopIterations = 1024;
235   int loop_var = 0;
236   do {
237     old_unescaped_str = unescaped_str;
238     unescaped_str = net::UnescapeURLComponent(old_unescaped_str,
239         net::UnescapeRule::CONTROL_CHARS | net::UnescapeRule::SPACES |
240         net::UnescapeRule::URL_SPECIAL_CHARS);
241   } while (unescaped_str != old_unescaped_str && ++loop_var <=
242            kMaxLoopIterations);
243
244   return unescaped_str;
245 }
246
247 std::string Escape(const std::string& url) {
248   std::string escaped_str;
249   const char* kHexString = "0123456789ABCDEF";
250   for (size_t i = 0; i < url.length(); i++) {
251     unsigned char c = static_cast<unsigned char>(url[i]);
252     if (c <= ' ' || c > '~' || c == '#' || c == '%') {
253       escaped_str.push_back('%');
254       escaped_str.push_back(kHexString[c >> 4]);
255       escaped_str.push_back(kHexString[c & 0xf]);
256     } else {
257       escaped_str.push_back(c);
258     }
259   }
260
261   return escaped_str;
262 }
263
264 std::string RemoveConsecutiveChars(const std::string& str, const char c) {
265   std::string output(str);
266   std::string string_to_find;
267   std::string::size_type loc = 0;
268   string_to_find.append(2, c);
269   while ((loc = output.find(string_to_find, loc)) != std::string::npos) {
270     output.erase(loc, 1);
271   }
272
273   return output;
274 }
275
276 // Canonicalizes url as per Google Safe Browsing Specification.
277 // See section 6.1 in
278 // http://code.google.com/p/google-safe-browsing/wiki/Protocolv2Spec.
279 void CanonicalizeUrl(const GURL& url,
280                      std::string* canonicalized_hostname,
281                      std::string* canonicalized_path,
282                      std::string* canonicalized_query) {
283   DCHECK(url.is_valid());
284
285   // We only canonicalize "normal" URLs.
286   if (!url.IsStandard())
287     return;
288
289   // Following canonicalization steps are excluded since url parsing takes care
290   // of those :-
291   // 1. Remove any tab (0x09), CR (0x0d), and LF (0x0a) chars from url.
292   //    (Exclude escaped version of these chars).
293   // 2. Normalize hostname to 4 dot-seperated decimal values.
294   // 3. Lowercase hostname.
295   // 4. Resolve path sequences "/../" and "/./".
296
297   // That leaves us with the following :-
298   // 1. Remove fragment in URL.
299   GURL url_without_fragment;
300   GURL::Replacements f_replacements;
301   f_replacements.ClearRef();
302   f_replacements.ClearUsername();
303   f_replacements.ClearPassword();
304   url_without_fragment = url.ReplaceComponents(f_replacements);
305
306   // 2. Do URL unescaping until no more hex encoded characters exist.
307   std::string url_unescaped_str(Unescape(url_without_fragment.spec()));
308   url_parse::Parsed parsed;
309   url_parse::ParseStandardURL(url_unescaped_str.data(),
310       url_unescaped_str.length(), &parsed);
311
312   // 3. In hostname, remove all leading and trailing dots.
313   const std::string host =
314       (parsed.host.len > 0)
315           ? url_unescaped_str.substr(parsed.host.begin, parsed.host.len)
316           : std::string();
317   const char kCharsToTrim[] = ".";
318   std::string host_without_end_dots;
319   TrimString(host, kCharsToTrim, &host_without_end_dots);
320
321   // 4. In hostname, replace consecutive dots with a single dot.
322   std::string host_without_consecutive_dots(RemoveConsecutiveChars(
323       host_without_end_dots, '.'));
324
325   // 5. In path, replace runs of consecutive slashes with a single slash.
326   std::string path =
327       (parsed.path.len > 0)
328           ? url_unescaped_str.substr(parsed.path.begin, parsed.path.len)
329           : std::string();
330   std::string path_without_consecutive_slash(RemoveConsecutiveChars(path, '/'));
331
332   url_canon::Replacements<char> hp_replacements;
333   hp_replacements.SetHost(host_without_consecutive_dots.data(),
334   url_parse::Component(0, host_without_consecutive_dots.length()));
335   hp_replacements.SetPath(path_without_consecutive_slash.data(),
336   url_parse::Component(0, path_without_consecutive_slash.length()));
337
338   std::string url_unescaped_with_can_hostpath;
339   url_canon::StdStringCanonOutput output(&url_unescaped_with_can_hostpath);
340   url_parse::Parsed temp_parsed;
341   url_util::ReplaceComponents(url_unescaped_str.data(),
342                               url_unescaped_str.length(), parsed,
343                               hp_replacements, NULL, &output, &temp_parsed);
344   output.Complete();
345
346   // 6. Step needed to revert escaping done in url_util::ReplaceComponents.
347   url_unescaped_with_can_hostpath = Unescape(url_unescaped_with_can_hostpath);
348
349   // 7. After performing all above steps, percent-escape all chars in url which
350   // are <= ASCII 32, >= 127, #, %. Escapes must be uppercase hex characters.
351   std::string escaped_canon_url_str(Escape(url_unescaped_with_can_hostpath));
352   url_parse::Parsed final_parsed;
353   url_parse::ParseStandardURL(escaped_canon_url_str.data(),
354                               escaped_canon_url_str.length(), &final_parsed);
355
356   if (canonicalized_hostname && final_parsed.host.len > 0) {
357     *canonicalized_hostname =
358         escaped_canon_url_str.substr(final_parsed.host.begin,
359                                      final_parsed.host.len);
360   }
361   if (canonicalized_path && final_parsed.path.len > 0) {
362     *canonicalized_path = escaped_canon_url_str.substr(final_parsed.path.begin,
363                                                        final_parsed.path.len);
364   }
365   if (canonicalized_query && final_parsed.query.len > 0) {
366     *canonicalized_query = escaped_canon_url_str.substr(
367         final_parsed.query.begin, final_parsed.query.len);
368   }
369 }
370
371 void GenerateHostsToCheck(const GURL& url, std::vector<std::string>* hosts) {
372   hosts->clear();
373
374   std::string canon_host;
375   CanonicalizeUrl(url, &canon_host, NULL, NULL);
376
377   const std::string host = canon_host;  // const sidesteps GCC bugs below!
378   if (host.empty())
379     return;
380
381   // Per the Safe Browsing Protocol v2 spec, we try the host, and also up to 4
382   // hostnames formed by starting with the last 5 components and successively
383   // removing the leading component.  The last component isn't examined alone,
384   // since it's the TLD or a subcomponent thereof.
385   //
386   // Note that we don't need to be clever about stopping at the "real" eTLD --
387   // the data on the server side has been filtered to ensure it will not
388   // blacklist a whole TLD, and it's not significantly slower on our side to
389   // just check too much.
390   //
391   // Also note that because we have a simple blacklist, not some sort of complex
392   // whitelist-in-blacklist or vice versa, it doesn't matter what order we check
393   // these in.
394   const size_t kMaxHostsToCheck = 4;
395   bool skipped_last_component = false;
396   for (std::string::const_reverse_iterator i(host.rbegin());
397        i != host.rend() && hosts->size() < kMaxHostsToCheck; ++i) {
398     if (*i == '.') {
399       if (skipped_last_component)
400         hosts->push_back(std::string(i.base(), host.end()));
401       else
402         skipped_last_component = true;
403     }
404   }
405   hosts->push_back(host);
406 }
407
408 void GeneratePathsToCheck(const GURL& url, std::vector<std::string>* paths) {
409   paths->clear();
410
411   std::string canon_path;
412   std::string canon_query;
413   CanonicalizeUrl(url, NULL, &canon_path, &canon_query);
414
415   const std::string path = canon_path;   // const sidesteps GCC bugs below!
416   const std::string query = canon_query;
417   if (path.empty())
418     return;
419
420   // Per the Safe Browsing Protocol v2 spec, we try the exact path with/without
421   // the query parameters, and also up to 4 paths formed by starting at the root
422   // and adding more path components.
423   //
424   // As with the hosts above, it doesn't matter what order we check these in.
425   const size_t kMaxPathsToCheck = 4;
426   for (std::string::const_iterator i(path.begin());
427        i != path.end() && paths->size() < kMaxPathsToCheck; ++i) {
428     if (*i == '/')
429       paths->push_back(std::string(path.begin(), i + 1));
430   }
431
432   if (!paths->empty() && paths->back() != path)
433     paths->push_back(path);
434
435   if (!query.empty())
436     paths->push_back(path + "?" + query);
437 }
438
439 void GeneratePatternsToCheck(const GURL& url, std::vector<std::string>* urls) {
440   std::vector<std::string> hosts, paths;
441   GenerateHostsToCheck(url, &hosts);
442   GeneratePathsToCheck(url, &paths);
443   for (size_t h = 0; h < hosts.size(); ++h) {
444     for (size_t p = 0; p < paths.size(); ++p) {
445       urls->push_back(hosts[h] + paths[p]);
446     }
447   }
448 }
449
450 int GetHashIndex(const SBFullHash& hash,
451                  const std::vector<SBFullHashResult>& full_hashes) {
452   for (size_t i = 0; i < full_hashes.size(); ++i) {
453     if (hash == full_hashes[i].hash)
454       return static_cast<int>(i);
455   }
456   return -1;
457 }
458
459 int GetUrlHashIndex(const GURL& url,
460                     const std::vector<SBFullHashResult>& full_hashes) {
461   if (full_hashes.empty())
462     return -1;
463
464   std::vector<std::string> patterns;
465   GeneratePatternsToCheck(url, &patterns);
466
467   for (size_t i = 0; i < patterns.size(); ++i) {
468     SBFullHash key;
469     crypto::SHA256HashString(patterns[i], key.full_hash, sizeof(SBFullHash));
470     int index = GetHashIndex(key, full_hashes);
471     if (index != -1)
472       return index;
473   }
474   return -1;
475 }
476
477 bool IsPhishingList(const std::string& list_name) {
478   return list_name.compare(kPhishingList) == 0;
479 }
480
481 bool IsMalwareList(const std::string& list_name) {
482   return list_name.compare(kMalwareList) == 0;
483 }
484
485 bool IsBadbinurlList(const std::string& list_name) {
486   return list_name.compare(kBinUrlList) == 0;
487 }
488
489 bool IsBadbinhashList(const std::string& list_name) {
490   return list_name.compare(kBinHashList) == 0;
491 }
492
493 bool IsExtensionList(const std::string& list_name) {
494   return list_name.compare(kExtensionBlacklist) == 0;
495 }
496
497 GURL GeneratePhishingReportUrl(const std::string& report_page,
498                                const std::string& url_to_report,
499                                bool is_client_side_detection) {
500   const std::string current_esc = net::EscapeQueryParamValue(url_to_report,
501                                                              true);
502
503 #if defined(OS_WIN)
504   BrowserDistribution* dist = BrowserDistribution::GetDistribution();
505   std::string client_name(dist->GetSafeBrowsingName());
506 #else
507   std::string client_name("googlechrome");
508 #endif
509   if (is_client_side_detection)
510     client_name.append("_csd");
511
512   GURL report_url(report_page + base::StringPrintf(kReportParams,
513                                                    client_name.c_str(),
514                                                    current_esc.c_str()));
515   return google_util::AppendGoogleLocaleParam(report_url);
516 }
517
518 SBFullHash StringToSBFullHash(const std::string& hash_in) {
519   DCHECK_EQ(crypto::kSHA256Length, hash_in.size());
520   SBFullHash hash_out;
521   memcpy(hash_out.full_hash, hash_in.data(), crypto::kSHA256Length);
522   return hash_out;
523 }
524
525 std::string SBFullHashToString(const SBFullHash& hash) {
526   DCHECK_EQ(crypto::kSHA256Length, sizeof(hash.full_hash));
527   return std::string(hash.full_hash, sizeof(hash.full_hash));
528 }
529
530 }  // namespace safe_browsing_util