Chromium Code Reviews
chromiumcodereview-hr@appspot.gserviceaccount.com (chromiumcodereview-hr) | Please choose your nickname with Settings | Help | Chromium Project | Gerrit Changes | Sign out
(296)

Side by Side Diff: chrome/browser/enumerate_modules_model_win.cc

Issue 4524002: First cut of the about:conflicts page, listing all DLLs loaded in the Chrome ... (Closed) Base URL: svn://svn.chromium.org/chrome/trunk/src/
Patch Set: '' Created 10 years, 1 month ago
Use n/p to move between diff chunks; N/P to move between comments. Draft comments are only viewable by you.
Jump to:
View unified diff | Download patch | Annotate | Revision Log
Property Changes:
Added: svn:eol-style
+ LF
OLDNEW
(Empty)
1 // Copyright (c) 2010 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/enumerate_modules_model_win.h"
6
7 #include <Tlhelp32.h>
8 #include <wintrust.h>
9
10 #include "app/l10n_util.h"
11 #include "app/win_util.h"
12 #include "base/command_line.h"
13 #include "base/environment.h"
14 #include "base/file_path.h"
15 #include "base/file_version_info_win.h"
16 #include "base/scoped_handle.h"
17 #include "base/sha2.h"
18 #include "base/string_number_conversions.h"
19 #include "base/string_util.h"
20 #include "base/utf_string_conversions.h"
21 #include "base/values.h"
22 #include "base/version.h"
23 #include "chrome/browser/net/service_providers_win.h"
24 #include "chrome/common/chrome_constants.h"
25 #include "chrome/common/chrome_switches.h"
26 #include "chrome/common/notification_service.h"
27 #include "grit/generated_resources.h"
28
29 // The period of time (in milliseconds) to wait until checking to see if any
30 // incompatible modules exist.
31 static const int kModuleCheckDelayMs = 60 * 1000;
32
33 // A sort method that sorts by ModuleType ordinal (loaded module at the top),
34 // then by full name (including path).
35 static bool ModuleSort(const ModuleEnumerator::Module& a,
36 const ModuleEnumerator::Module& b) {
37 if (a.type != b.type)
38 return a.type < b.type;
39 if (a.location == b.location)
40 return a.name < b.name;
41
42 return a.location < b.location;
43 }
44
45 namespace {
46
47 // Used to protect the LoadedModuleVector which is accessed
48 // from both the UI thread and the FILE thread.
49 Lock* lock = NULL;
50
51 }
52
53 // The browser process module blacklist. This lists all modules that are known
54 // to cause compatibility issues within the browser process. When adding to this
55 // list, make sure that all paths are lower-case, in long pathname form, end
56 // with a slash and use environments variables (or just look at one of the
57 // comments below and keep it consistent with that). When adding an entry with
58 // an environment variable not currently used in the list below, make sure to
59 // update the list in PreparePathMappings. Filename, Description/Signer, and
60 // Location must be entered as hashes (see GenerateHash). Filename is mandatory.
61 // Entries without any Description, Signer info, or Location will never be
62 // marked as confirmed bad (only as suspicious).
63 const ModuleEnumerator::BlacklistEntry ModuleEnumerator::kModuleBlacklist[] = {
64 // Test DLLs, to demonstrate the feature. Will be removed soon.
65 { // apphelp.dll, "%systemroot%\\system32\\"
66 "f5fda581", "23d01d5b", "", "", "", NONE
67 }, { // rsaenh.dll, "%systemroot%\\system32\\", "Microsoft Windows"
68 "6af212cb", "23d01d5b", "7b47bf79", "", "",
69 static_cast<RecommendedAction>(UPDATE | DISABLE | SEE_LINK)
70 },
71
72 // NOTE: Please keep this list sorted by dll name, then location.
73
74 // foldersizecolumn.dll.
75 {"5ec91bd7", "", "", "", "", NONE},
76
77 // idmmbc.dll, "%programfiles%\\internet download manager\\", "Tonec Inc.".
78 // See: http://crbug.com/26892/.
79 {"b8dce5c3", "94541bf5", "d33ad640", "", "", NONE},
80
81 // imon.dll. See: http://crbug.com/21715.
82 {"8f42f22e", "", "", "", "", NONE},
83
84 // is3lsp.dll. See: http://crbug.com/26892.
85 {"7ffbdce9", "", "", "", "", NONE},
86
87 // nvlsp.dll. See: http://crbug.com/22083.
88 {"37f907e2", "", "", "", "", NONE},
89
90 // nvshell.dll. See: http://crbug.com/3269.
91 {"9290318f", "", "", "", "", NONE},
92
93 // securenet.dll. See: http://crbug.com/5165.
94 {"9b266e1c", "", "", "", "", NONE},
95
96 // sgprxy.dll.
97 {"005965ea", "", "", "", "", NONE},
98
99 // vaproxyd.dll. See: http://crbug.com/42445.
100 {"0a1c7f81", "", "", "", "", NONE},
101
102 // vlsp.dll. See: http://crbug.com/22826.
103 {"2e4eb93d", "", "", "", "", NONE},
104 };
105
106 // Generates an 8 digit hash from the input given.
107 static void GenerateHash(const std::string& input, std::string* output) {
108 if (input.empty()) {
109 *output = "";
110 return;
111 }
112
113 uint8 hash[4];
114 base::SHA256HashString(input, hash, sizeof(hash));
115 *output = StringToLowerASCII(base::HexEncode(hash, sizeof(hash)));
116 }
117
118 // -----------------------------------------------------------------------------
119
120 // static
121 void ModuleEnumerator::NormalizeModule(Module* module) {
122 string16 path = module->location;
123 if (!win_util::ConvertToLongPath(path, &module->location))
124 module->location = path;
125
126 module->location = l10n_util::ToLower(module->location);
127
128 // Location contains the filename, so the last slash is where the path
129 // ends.
130 size_t last_slash = module->location.find_last_of(L"\\");
131 if (last_slash != string16::npos) {
132 module->name = module->location.substr(last_slash + 1);
133 module->location = module->location.substr(0, last_slash + 1);
134 } else {
135 module->name = module->location;
136 module->location.clear();
137 }
138
139 // Some version strings have things like (win7_rtm.090713-1255) appended
140 // to them. Remove that.
141 size_t first_space = module->version.find_first_of(L" ");
142 if (first_space != string16::npos)
143 module->version = module->version.substr(0, first_space);
144
145 module->normalized = true;
146 }
147
148 // static
149 ModuleEnumerator::ModuleStatus ModuleEnumerator::Match(
150 const ModuleEnumerator::Module& module,
151 const ModuleEnumerator::BlacklistEntry& blacklisted) {
152 // All modules must be normalized before matching against blacklist.
153 DCHECK(module.normalized);
154 // Filename is mandatory and version should not contain spaces.
155 DCHECK(strlen(blacklisted.filename) > 0);
156 DCHECK(!strstr(blacklisted.version_from, " "));
157 DCHECK(!strstr(blacklisted.version_to, " "));
158
159 std::string filename_hash, location_hash;
160 GenerateHash(WideToUTF8(module.name), &filename_hash);
161 GenerateHash(WideToUTF8(module.location), &location_hash);
162
163 // Filenames are mandatory. Location is mandatory if given.
164 if (filename_hash == blacklisted.filename &&
165 (std::string(blacklisted.location).empty() ||
166 location_hash == blacklisted.location)) {
167 // We have a name match against the blacklist (and possibly location match
168 // also), so check version.
169 scoped_ptr<Version> module_version(
170 Version::GetVersionFromString(module.version));
171 scoped_ptr<Version> version_min(
172 Version::GetVersionFromString(blacklisted.version_from));
173 scoped_ptr<Version> version_max(
174 Version::GetVersionFromString(blacklisted.version_to));
175 bool version_ok = !version_min.get() && !version_max.get();
176 if (!version_ok) {
177 bool too_low = version_min.get() &&
178 (!module_version.get() ||
179 module_version->CompareTo(*version_min.get()) < 0);
180 bool too_high = version_max.get() &&
181 (!module_version.get() ||
182 module_version->CompareTo(*version_max.get()) > 0);
183 version_ok = !too_low && !too_high;
184 }
185
186 if (version_ok) {
187 // At this point, the names match and there is no version specified
188 // or the versions also match.
189
190 std::string desc_or_signer(blacklisted.desc_or_signer);
191 std::string signer_hash, description_hash;
192 GenerateHash(WideToUTF8(module.digital_signer), &signer_hash);
193 GenerateHash(WideToUTF8(module.description), &description_hash);
194
195 // If signatures match, we have a winner.
196 if (!desc_or_signer.empty() && signer_hash == desc_or_signer)
197 return CONFIRMED_BAD;
198
199 // If description matches and location, then we also have a match.
200 if (!desc_or_signer.empty() && description_hash == desc_or_signer &&
201 !location_hash.empty() && location_hash == blacklisted.location) {
202 return CONFIRMED_BAD;
203 }
204
205 // We are not sure, but it is likely bad.
206 return SUSPECTED_BAD;
207 }
208 }
209
210 return NOT_MATCHED;
211 }
212
213 ModuleEnumerator::ModuleEnumerator(EnumerateModulesModel* observer)
214 : observer_(observer) {
tfarina 2010/11/08 14:57:47 nit: indent 4 spaces.
215 CHECK(BrowserThread::GetCurrentThreadIdentifier(&callback_thread_id_));
216 DCHECK(!BrowserThread::CurrentlyOn(BrowserThread::FILE));
217 }
218
219 ModuleEnumerator::~ModuleEnumerator() {
220 }
221
222 void ModuleEnumerator::ScanNow(ModulesVector* list) {
223 DCHECK(!BrowserThread::CurrentlyOn(BrowserThread::FILE));
224 enumerated_modules_ = list;
225 BrowserThread::PostTask(
226 BrowserThread::FILE, FROM_HERE,
227 NewRunnableMethod(this, &ModuleEnumerator::ScanOnFileThread));
228 }
229
230 void ModuleEnumerator::ScanOnFileThread() {
231 enumerated_modules_->clear();
232
233 // Make sure the path mapping vector is setup so we can collapse paths.
234 PreparePathMappings();
235
236 // Get all modules in the current process.
237 ScopedHandle snap(::CreateToolhelp32Snapshot(TH32CS_SNAPMODULE,
238 ::GetCurrentProcessId()));
239 if (!snap.Get())
240 return;
241
242 // Walk the module list.
243 MODULEENTRY32 module = { sizeof(module) };
244 if (!::Module32First(snap.Get(), &module))
245 return;
246
247 do {
248 // It would be weird to present chrome.exe as a loaded module.
249 if (_wcsicmp(chrome::kBrowserProcessExecutableName, module.szModule) == 0)
250 continue;
251
252 Module entry;
253 entry.type = LOADED_MODULE;
254 entry.status = NOT_MATCHED;
255 entry.normalized = false;
256 entry.location = module.szExePath;
257 entry.digital_signer =
258 GetSubjectNameFromDigitalSignature(FilePath(entry.location));
259 entry.recommended_action = NONE;
260 scoped_ptr<FileVersionInfo> version_info(
261 FileVersionInfo::CreateFileVersionInfo(FilePath(entry.location)));
262 if (version_info.get()) {
263 FileVersionInfoWin* version_info_win =
264 static_cast<FileVersionInfoWin*>(version_info.get());
265
266 VS_FIXEDFILEINFO* fixed_file_info = version_info_win->fixed_file_info();
267 if (fixed_file_info) {
268 entry.description = version_info_win->file_description();
269 entry.version = version_info_win->file_version();
270 entry.product_name = version_info_win->product_name();
271 }
272 }
273
274 NormalizeModule(&entry);
275 CollapsePath(&entry);
276 enumerated_modules_->push_back(entry);
277 } while (::Module32Next(snap.Get(), &module));
278
279 // Add to this list the Winsock LSP DLLs.
280 WinsockLayeredServiceProviderList layered_providers;
281 GetWinsockLayeredServiceProviders(&layered_providers);
282 for (size_t i = 0; i < layered_providers.size(); ++i) {
283 Module entry;
284 entry.type = WINSOCK_MODULE_REGISTRATION;
285 entry.status = NOT_MATCHED;
286 entry.normalized = false;
287 entry.location = layered_providers[i].path;
288 entry.description = layered_providers[i].name;
289 entry.recommended_action = NONE;
290
291 wchar_t expanded[MAX_PATH];
292 DWORD size = ExpandEnvironmentStrings(
293 entry.location.c_str(), expanded, MAX_PATH);
294 if (size != 0 && size <= MAX_PATH) {
295 entry.digital_signer =
296 GetSubjectNameFromDigitalSignature(FilePath(expanded));
297 }
298 entry.version = base::IntToString16(layered_providers[i].version);
299
300 // Paths have already been collapsed.
301 NormalizeModule(&entry);
302 enumerated_modules_->push_back(entry);
303 }
304
305 MatchAgainstBlacklist();
306
307 std::sort(enumerated_modules_->begin(),
308 enumerated_modules_->end(), ModuleSort);
309
310 // Send a reply back on the UI thread.
311 BrowserThread::PostTask(
312 callback_thread_id_, FROM_HERE,
313 NewRunnableMethod(this, &ModuleEnumerator::ReportBack));
314 }
315
316 void ModuleEnumerator::PreparePathMappings() {
317 path_mapping_.clear();
318
319 scoped_ptr<base::Environment> environment(base::Environment::Create());
320 std::vector<string16> env_vars;
321 env_vars.push_back(L"LOCALAPPDATA");
322 env_vars.push_back(L"ProgramFiles");
323 env_vars.push_back(L"USERPROFILE");
324 env_vars.push_back(L"SystemRoot");
325 env_vars.push_back(L"TEMP");
326 for (std::vector<string16>::const_iterator variable = env_vars.begin();
327 variable != env_vars.end(); ++variable) {
328 std::string path;
329 if (environment->GetVar(WideToASCII(*variable).c_str(), &path)) {
330 path_mapping_.push_back(
331 std::make_pair(l10n_util::ToLower(UTF8ToWide(path)) + L"\\",
332 L"%" + l10n_util::ToLower(*variable) + L"%"));
333 }
334 }
335 }
336
337 void ModuleEnumerator::CollapsePath(Module* entry) {
338 // Take the path and see if we can use any of the substitution values
339 // from the vector constructed above to replace c:\windows with, for
340 // example, %systemroot%.
341 for (PathMapping::const_iterator mapping = path_mapping_.begin();
342 mapping != path_mapping_.end(); ++mapping) {
343 string16 prefix = mapping->first;
344 if (StartsWith(entry->location, prefix, false)) {
345 entry->location = mapping->second +
346 entry->location.substr(prefix.length() - 1);
347 return;
348 }
349 }
350 }
351
352 void ModuleEnumerator::MatchAgainstBlacklist() {
353 for (size_t m = 0; m < enumerated_modules_->size(); ++m) {
354 // Match this module against the blacklist.
355 Module* module = &(*enumerated_modules_)[m];
356 module->status = GOOD; // We change this below potentially.
357 for (size_t i = 0; i < arraysize(kModuleBlacklist); ++i) {
358 #if !defined(NDEBUG)
359 // This saves time when constructing the blacklist.
360 std::string hashes(kModuleBlacklist[i].filename);
361 std::string hash1, hash2, hash3;
362 GenerateHash(kModuleBlacklist[i].filename, &hash1);
363 hashes += " - " + hash1;
364 GenerateHash(kModuleBlacklist[i].location, &hash2);
365 hashes += " - " + hash2;
366 GenerateHash(kModuleBlacklist[i].desc_or_signer, &hash3);
367 hashes += " - " + hash3;
368 #endif
369
370 ModuleStatus status = Match(*module, kModuleBlacklist[i]);
371 if (status != NOT_MATCHED) {
372 // We have a match against the blacklist. Mark it as such.
373 module->status = status;
374 module->recommended_action = kModuleBlacklist[i].help_tip;
375 break;
376 }
377 }
378 }
379 }
380
381 void ModuleEnumerator::ReportBack() {
382 DCHECK(BrowserThread::CurrentlyOn(callback_thread_id_));
383 observer_->DoneScanning();
384 }
385
386 string16 ModuleEnumerator::GetSubjectNameFromDigitalSignature(
387 const FilePath& filename) {
388 HCERTSTORE store = NULL;
389 HCRYPTMSG message = NULL;
390
391 // Find the crypto message for this filename.
392 bool result = !!CryptQueryObject(CERT_QUERY_OBJECT_FILE,
393 filename.value().c_str(),
394 CERT_QUERY_CONTENT_FLAG_PKCS7_SIGNED_EMBED,
395 CERT_QUERY_FORMAT_FLAG_BINARY,
396 0,
397 NULL,
398 NULL,
399 NULL,
400 &store,
401 &message,
402 NULL);
403 if (!result)
404 return string16();
405
406 // Determine the size of the signer info data.
407 DWORD signer_info_size = 0;
408 result = !!CryptMsgGetParam(message,
409 CMSG_SIGNER_INFO_PARAM,
410 0,
411 NULL,
412 &signer_info_size);
413 if (!result)
414 return string16();
415
416 // Allocate enough space to hold the signer info.
417 scoped_array<BYTE> signer_info_buffer(new BYTE[signer_info_size]);
418 CMSG_SIGNER_INFO* signer_info =
419 reinterpret_cast<CMSG_SIGNER_INFO*>(signer_info_buffer.get());
420
421 // Obtain the signer info.
422 result = !!CryptMsgGetParam(message,
423 CMSG_SIGNER_INFO_PARAM,
424 0,
425 signer_info,
426 &signer_info_size);
427 if (!result)
428 return string16();
429
430 // Search for the signer certificate.
431 CERT_INFO CertInfo = {0};
432 PCCERT_CONTEXT cert_context = NULL;
433 CertInfo.Issuer = signer_info->Issuer;
434 CertInfo.SerialNumber = signer_info->SerialNumber;
435
436 cert_context = CertFindCertificateInStore(
437 store,
438 X509_ASN_ENCODING | PKCS_7_ASN_ENCODING,
439 0,
440 CERT_FIND_SUBJECT_CERT,
441 &CertInfo,
442 NULL);
443 if (!cert_context)
444 return string16();
445
446 // Determine the size of the Subject name.
447 DWORD subject_name_size = 0;
448 if (!(subject_name_size = CertGetNameString(cert_context,
449 CERT_NAME_SIMPLE_DISPLAY_TYPE,
450 0,
451 NULL,
452 NULL,
453 0))) {
454 return string16();
455 }
456
457 string16 subject_name;
458 subject_name.resize(subject_name_size);
459
460 // Get subject name.
461 if (!(CertGetNameString(cert_context,
462 CERT_NAME_SIMPLE_DISPLAY_TYPE,
463 0,
464 NULL,
465 const_cast<LPWSTR>(subject_name.c_str()),
466 subject_name_size))) {
467 return string16();
468 }
469
470 return subject_name;
471 }
472
473 // ----------------------------------------------------------------------------
474
475 void EnumerateModulesModel::ScanNow() {
476 if (scanning_)
477 return; // A scan is already in progress.
478
479 lock->Acquire(); // Balanced in DoneScanning();
480
481 scanning_ = true;
482
483 // Instruct the ModuleEnumerator class to load this on the File thread.
484 // ScanNow does not block.
485 if (!module_enumerator_)
486 module_enumerator_ = new ModuleEnumerator(this);
487 module_enumerator_->ScanNow(&enumerated_modules_);
488 }
489
490 ListValue* EnumerateModulesModel::GetModuleList() {
491 if (scanning_)
492 return NULL;
493
494 lock->Acquire();
495
496 if (enumerated_modules_.empty()) {
497 lock->Release();
498 return NULL;
499 }
500
501 ListValue* list = new ListValue();
502
503 for (ModuleEnumerator::ModulesVector::const_iterator module =
504 enumerated_modules_.begin();
505 module != enumerated_modules_.end(); ++module) {
506 DictionaryValue* data = new DictionaryValue();
507 data->SetInteger("type", module->type);
508 data->SetString("type_description",
509 (module->type == ModuleEnumerator::WINSOCK_MODULE_REGISTRATION) ?
510 ASCIIToWide("Winsock") : ASCIIToWide(""));
511 data->SetInteger("status", module->status);
512 data->SetString("location", module->location);
513 data->SetString("name", module->name);
514 data->SetString("product_name", module->product_name);
515 data->SetString("description", module->description);
516 data->SetString("version", module->version.empty() ? ASCIIToWide("") :
517 l10n_util::GetStringF(IDS_CONFLICTS_CHECK_VERSION_STRING,
518 module->version));
519 data->SetString("digital_signer", module->digital_signer);
520
521 // Figure out the possible resolution help string.
522 string16 actions;
523 string16 separator = ASCIIToWide(" ") + l10n_util::GetStringUTF16(
524 IDS_CONFLICTS_CHECK_POSSIBLE_ACTION_SEPERATOR) +
525 ASCIIToWide(" ");
526
527 if (module->recommended_action & ModuleEnumerator::NONE) {
528 actions = l10n_util::GetStringUTF16(
529 IDS_CONFLICTS_CHECK_INVESTIGATING);
530 }
531 if (module->recommended_action & ModuleEnumerator::UNINSTALL) {
532 if (!actions.empty())
533 actions += separator;
534 actions = l10n_util::GetStringUTF16(
535 IDS_CONFLICTS_CHECK_POSSIBLE_ACTION_UNINSTALL);
536 }
537 if (module->recommended_action & ModuleEnumerator::UPDATE) {
538 if (!actions.empty())
539 actions += separator;
540 actions += l10n_util::GetStringUTF16(
541 IDS_CONFLICTS_CHECK_POSSIBLE_ACTION_UPDATE);
542 }
543 if (module->recommended_action & ModuleEnumerator::DISABLE) {
544 if (!actions.empty())
545 actions += separator;
546 actions += l10n_util::GetStringUTF16(
547 IDS_CONFLICTS_CHECK_POSSIBLE_ACTION_DISABLE);
548 }
549 string16 possible_resolution = actions.empty() ? ASCIIToWide("") :
550 l10n_util::GetStringUTF16(IDS_CONFLICTS_CHECK_POSSIBLE_ACTIONS) +
551 ASCIIToWide(" ") +
552 actions;
553 data->SetString("possibleResolution", possible_resolution);
554 data->SetString("help_url", ConstructHelpCenterUrl(*module).spec().c_str());
555
556 list->Append(data);
557 }
558
559 lock->Release();
560 return list;
561 }
562
563 EnumerateModulesModel::EnumerateModulesModel()
564 : scanning_(false),
tfarina 2010/11/08 14:57:47 nit: indent 4 spaces.
565 confirmed_bad_modules_detected_(0),
tfarina 2010/11/08 14:57:47 mit: align with |scanning_|.
566 suspected_bad_modules_detected_(0) {
567 const CommandLine& cmd_line = *CommandLine::ForCurrentProcess();
568 if (cmd_line.HasSwitch(switches::kConflictingModulesCheck)) {
569 check_modules_timer_.Start(
570 base::TimeDelta::FromMilliseconds(kModuleCheckDelayMs),
tfarina 2010/11/08 14:57:47 nit: indent 4 spaces.
571 this, &EnumerateModulesModel::ScanNow);
572 }
573
574 lock = new Lock();
575 }
576
577 EnumerateModulesModel::~EnumerateModulesModel() {
578 delete lock;
579 }
580
581 void EnumerateModulesModel::DoneScanning() {
582 confirmed_bad_modules_detected_ = 0;
583 suspected_bad_modules_detected_ = 0;
584 for (ModuleEnumerator::ModulesVector::const_iterator module =
585 enumerated_modules_.begin();
586 module != enumerated_modules_.end(); ++module) {
587 if (module->status == ModuleEnumerator::CONFIRMED_BAD)
588 ++confirmed_bad_modules_detected_;
589 if (module->status == ModuleEnumerator::SUSPECTED_BAD)
590 ++suspected_bad_modules_detected_;
591 }
592
593 scanning_ = false;
594 lock->Release();
595
596 NotificationService::current()->Notify(
597 NotificationType::MODULE_LIST_ENUMERATED,
598 Source<EnumerateModulesModel>(this),
599 NotificationService::NoDetails());
600
601 if (suspected_bad_modules_detected_ || confirmed_bad_modules_detected_) {
602 bool found_confirmed_bad_modules = confirmed_bad_modules_detected_ > 0;
603 NotificationService::current()->Notify(
604 NotificationType::MODULE_INCOMPATIBILITY_DETECTED,
605 Source<EnumerateModulesModel>(this),
606 Details<bool>(&found_confirmed_bad_modules));
607 }
608 }
609
610 GURL EnumerateModulesModel::ConstructHelpCenterUrl(
611 const ModuleEnumerator::Module& module) {
612 if (!(module.recommended_action & ModuleEnumerator::SEE_LINK))
613 return GURL();
614
615 // Construct the needed hashes.
616 std::string filename, location, description, signer;
617 GenerateHash(WideToUTF8(module.name), &filename);
618 GenerateHash(WideToUTF8(module.location), &location);
619 GenerateHash(WideToUTF8(module.description), &description);
620 GenerateHash(WideToUTF8(module.digital_signer), &signer);
621
622 string16 url = l10n_util::GetStringF(IDS_HELP_CENTER_VIEW_CONFLICTS,
623 ASCIIToWide(filename), ASCIIToWide(location),
624 ASCIIToWide(description), ASCIIToWide(signer));
625 return GURL(WideToUTF8(url));
626 }
OLDNEW

Powered by Google App Engine
This is Rietveld 408576698