• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 // Copyright (c) 2011 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/web_applications/web_app.h"
6 
7 #if defined(OS_WIN)
8 #include <shlobj.h>
9 #endif  // defined(OS_WIN)
10 
11 #include "base/command_line.h"
12 #include "base/file_util.h"
13 #include "base/i18n/file_util_icu.h"
14 #include "base/md5.h"
15 #include "base/path_service.h"
16 #include "base/string_util.h"
17 #include "base/threading/thread.h"
18 #include "base/utf_string_conversions.h"
19 #include "base/win/windows_version.h"
20 #include "chrome/browser/download/download_util.h"
21 #include "chrome/common/chrome_constants.h"
22 #include "chrome/common/chrome_paths.h"
23 #include "chrome/common/url_constants.h"
24 #include "content/browser/browser_thread.h"
25 
26 #if defined(OS_LINUX)
27 #include "base/environment.h"
28 #endif  // defined(OS_LINUX)
29 
30 #if defined(OS_WIN)
31 #include "ui/gfx/icon_util.h"
32 #endif  // defined(OS_WIN)
33 
34 namespace {
35 
36 #if defined(OS_WIN)
37 const FilePath::CharType kIconChecksumFileExt[] = FILE_PATH_LITERAL(".ico.md5");
38 
39 // Returns true if |ch| is in visible ASCII range and not one of
40 // "/ \ : * ? " < > | ; ,".
IsValidFilePathChar(char16 c)41 bool IsValidFilePathChar(char16 c) {
42   if (c < 32)
43     return false;
44 
45   switch (c) {
46     case '/':
47     case '\\':
48     case ':':
49     case '*':
50     case '?':
51     case '"':
52     case '<':
53     case '>':
54     case '|':
55     case ';':
56     case ',':
57       return false;
58   };
59 
60   return true;
61 }
62 
63 #endif  // defined(OS_WIN)
64 
65 // Returns relative directory of given web app url.
GetWebAppDir(const ShellIntegration::ShortcutInfo & info)66 FilePath GetWebAppDir(const ShellIntegration::ShortcutInfo& info) {
67   if (!info.extension_id.empty()) {
68     std::string app_name =
69         web_app::GenerateApplicationNameFromExtensionId(info.extension_id);
70 #if defined(OS_WIN)
71     return FilePath(UTF8ToWide(app_name));
72 #elif defined(OS_POSIX)
73     return FilePath(app_name);
74 #endif
75   } else {
76     FilePath::StringType host;
77     FilePath::StringType scheme_port;
78 
79 #if defined(OS_WIN)
80     host = UTF8ToWide(info.url.host());
81     scheme_port = (info.url.has_scheme() ? UTF8ToWide(info.url.scheme())
82         : L"http") + FILE_PATH_LITERAL("_") +
83         (info.url.has_port() ? UTF8ToWide(info.url.port()) : L"80");
84 #elif defined(OS_POSIX)
85     host = info.url.host();
86     scheme_port = info.url.scheme() + FILE_PATH_LITERAL("_") + info.url.port();
87 #endif
88 
89     return FilePath(host).Append(scheme_port);
90   }
91 }
92 
93 #if defined(TOOLKIT_VIEWS)
94 // Predicator for sorting images from largest to smallest.
IconPrecedes(const WebApplicationInfo::IconInfo & left,const WebApplicationInfo::IconInfo & right)95 bool IconPrecedes(const WebApplicationInfo::IconInfo& left,
96                   const WebApplicationInfo::IconInfo& right) {
97   return left.width < right.width;
98 }
99 #endif
100 
101 #if defined(OS_WIN)
102 // Calculates image checksum using MD5.
GetImageCheckSum(const SkBitmap & image,MD5Digest * digest)103 void GetImageCheckSum(const SkBitmap& image, MD5Digest* digest) {
104   DCHECK(digest);
105 
106   SkAutoLockPixels image_lock(image);
107   MD5Sum(image.getPixels(), image.getSize(), digest);
108 }
109 
110 // Saves |image| as an |icon_file| with the checksum.
SaveIconWithCheckSum(const FilePath & icon_file,const SkBitmap & image)111 bool SaveIconWithCheckSum(const FilePath& icon_file, const SkBitmap& image) {
112   if (!IconUtil::CreateIconFileFromSkBitmap(image, icon_file))
113     return false;
114 
115   MD5Digest digest;
116   GetImageCheckSum(image, &digest);
117 
118   FilePath cheksum_file(icon_file.ReplaceExtension(kIconChecksumFileExt));
119   return file_util::WriteFile(cheksum_file,
120                               reinterpret_cast<const char*>(&digest),
121                               sizeof(digest)) == sizeof(digest);
122 }
123 
124 // Returns true if |icon_file| is missing or different from |image|.
ShouldUpdateIcon(const FilePath & icon_file,const SkBitmap & image)125 bool ShouldUpdateIcon(const FilePath& icon_file, const SkBitmap& image) {
126   FilePath checksum_file(icon_file.ReplaceExtension(kIconChecksumFileExt));
127 
128   // Returns true if icon_file or checksum file is missing.
129   if (!file_util::PathExists(icon_file) ||
130       !file_util::PathExists(checksum_file))
131     return true;
132 
133   MD5Digest persisted_image_checksum;
134   if (sizeof(persisted_image_checksum) != file_util::ReadFile(checksum_file,
135                       reinterpret_cast<char*>(&persisted_image_checksum),
136                       sizeof(persisted_image_checksum)))
137     return true;
138 
139   MD5Digest downloaded_image_checksum;
140   GetImageCheckSum(image, &downloaded_image_checksum);
141 
142   // Update icon if checksums are not equal.
143   return memcmp(&persisted_image_checksum, &downloaded_image_checksum,
144                 sizeof(MD5Digest)) != 0;
145 }
146 
147 #endif  // defined(OS_WIN)
148 
149 // Represents a task that creates web application shortcut. This runs on
150 // file thread and schedules the callback (if any) on the calling thread
151 // when finished (either success or failure).
152 class CreateShortcutTask : public Task {
153  public:
154   CreateShortcutTask(const FilePath& profile_path,
155                      const ShellIntegration::ShortcutInfo& shortcut_info,
156                      web_app::CreateShortcutCallback* callback);
157 
158  private:
159   class CreateShortcutCallbackTask : public Task {
160    public:
CreateShortcutCallbackTask(web_app::CreateShortcutCallback * callback,bool success)161     CreateShortcutCallbackTask(web_app::CreateShortcutCallback* callback,
162         bool success)
163         : callback_(callback),
164           success_(success) {
165     }
166 
167     // Overridden from Task:
Run()168     virtual void Run() {
169       callback_->Run(success_);
170     }
171 
172    private:
173     web_app::CreateShortcutCallback* callback_;
174     bool success_;
175   };
176 
177   // Overridden from Task:
178   virtual void Run();
179 
180   // Returns true if shortcut is created successfully.
181   bool CreateShortcut();
182 
183   // Path to store persisted data for web app.
184   FilePath web_app_path_;
185 
186   // Out copy of profile path.
187   FilePath profile_path_;
188 
189   // Our copy of short cut data.
190   ShellIntegration::ShortcutInfo shortcut_info_;
191 
192   // Callback when task is finished.
193   web_app::CreateShortcutCallback* callback_;
194   MessageLoop* message_loop_;
195 
196   DISALLOW_COPY_AND_ASSIGN(CreateShortcutTask);
197 };
198 
CreateShortcutTask(const FilePath & profile_path,const ShellIntegration::ShortcutInfo & shortcut_info,web_app::CreateShortcutCallback * callback)199 CreateShortcutTask::CreateShortcutTask(
200     const FilePath& profile_path,
201     const ShellIntegration::ShortcutInfo& shortcut_info,
202     web_app::CreateShortcutCallback* callback)
203     : web_app_path_(web_app::internals::GetWebAppDataDirectory(
204         web_app::GetDataDir(profile_path),
205         shortcut_info)),
206       profile_path_(profile_path),
207       shortcut_info_(shortcut_info),
208       callback_(callback),
209       message_loop_(MessageLoop::current()) {
210   DCHECK(message_loop_ != NULL);
211 }
212 
Run()213 void CreateShortcutTask::Run() {
214   bool success = CreateShortcut();
215 
216   if (callback_ != NULL)
217     message_loop_->PostTask(FROM_HERE,
218       new CreateShortcutCallbackTask(callback_, success));
219 }
220 
CreateShortcut()221 bool CreateShortcutTask::CreateShortcut() {
222   DCHECK(BrowserThread::CurrentlyOn(BrowserThread::FILE));
223 
224 #if defined(OS_LINUX)
225   scoped_ptr<base::Environment> env(base::Environment::Create());
226 
227   std::string shortcut_template;
228   if (!ShellIntegration::GetDesktopShortcutTemplate(env.get(),
229                                                     &shortcut_template)) {
230     return false;
231   }
232   ShellIntegration::CreateDesktopShortcut(shortcut_info_, shortcut_template);
233   return true;  // assuming always success.
234 #elif defined(OS_WIN)
235   // Shortcut paths under which to create shortcuts.
236   std::vector<FilePath> shortcut_paths;
237 
238   // Locations to add to shortcut_paths.
239   struct {
240     const bool& use_this_location;
241     int location_id;
242     const wchar_t* sub_dir;
243   } locations[] = {
244     {
245       shortcut_info_.create_on_desktop,
246       chrome::DIR_USER_DESKTOP,
247       NULL
248     }, {
249       shortcut_info_.create_in_applications_menu,
250       base::DIR_START_MENU,
251       NULL
252     }, {
253       shortcut_info_.create_in_quick_launch_bar,
254       // For Win7, create_in_quick_launch_bar means pinning to taskbar. Use
255       // base::PATH_START as a flag for this case.
256       (base::win::GetVersion() >= base::win::VERSION_WIN7) ?
257           base::PATH_START : base::DIR_APP_DATA,
258       (base::win::GetVersion() >= base::win::VERSION_WIN7) ?
259           NULL : L"Microsoft\\Internet Explorer\\Quick Launch"
260     }
261   };
262 
263   // Populate shortcut_paths.
264   for (int i = 0; i < arraysize(locations); ++i) {
265     if (locations[i].use_this_location) {
266       FilePath path;
267 
268       // Skip the Win7 case.
269       if (locations[i].location_id == base::PATH_START)
270         continue;
271 
272       if (!PathService::Get(locations[i].location_id, &path)) {
273         NOTREACHED();
274         return false;
275       }
276 
277       if (locations[i].sub_dir != NULL)
278         path = path.Append(locations[i].sub_dir);
279 
280       shortcut_paths.push_back(path);
281     }
282   }
283 
284   bool pin_to_taskbar =
285       shortcut_info_.create_in_quick_launch_bar &&
286       (base::win::GetVersion() >= base::win::VERSION_WIN7);
287 
288   // For Win7's pinning support, any shortcut could be used. So we only create
289   // the shortcut file when there is no shortcut file will be created. That is,
290   // user only selects "Pin to taskbar".
291   if (pin_to_taskbar && shortcut_paths.empty()) {
292     // Creates the shortcut in web_app_path_ in this case.
293     shortcut_paths.push_back(web_app_path_);
294   }
295 
296   if (shortcut_paths.empty()) {
297     NOTREACHED();
298     return false;
299   }
300 
301   // Ensure web_app_path_ exists.
302   if (!file_util::PathExists(web_app_path_) &&
303       !file_util::CreateDirectory(web_app_path_)) {
304     NOTREACHED();
305     return false;
306   }
307 
308   // Generates file name to use with persisted ico and shortcut file.
309   FilePath file_name =
310       web_app::internals::GetSanitizedFileName(shortcut_info_.title);
311 
312   // Creates an ico file to use with shortcut.
313   FilePath icon_file = web_app_path_.Append(file_name).ReplaceExtension(
314       FILE_PATH_LITERAL(".ico"));
315   if (!web_app::internals::CheckAndSaveIcon(icon_file,
316                                             shortcut_info_.favicon)) {
317     NOTREACHED();
318     return false;
319   }
320 
321   FilePath chrome_exe;
322   if (!PathService::Get(base::FILE_EXE, &chrome_exe)) {
323     NOTREACHED();
324     return false;
325   }
326 
327   // Working directory.
328   FilePath chrome_folder = chrome_exe.DirName();
329 
330   CommandLine cmd_line =
331      ShellIntegration::CommandLineArgsForLauncher(shortcut_info_.url,
332                                                   shortcut_info_.extension_id);
333   // TODO(evan): we rely on the fact that command_line_string() is
334   // properly quoted for a Windows command line.  The method on
335   // CommandLine should probably be renamed to better reflect that
336   // fact.
337   std::wstring wide_switches(cmd_line.command_line_string());
338 
339   // Sanitize description
340   if (shortcut_info_.description.length() >= MAX_PATH)
341     shortcut_info_.description.resize(MAX_PATH - 1);
342 
343   // Generates app id from web app url and profile path.
344   std::string app_name =
345       web_app::GenerateApplicationNameFromInfo(shortcut_info_);
346   std::wstring app_id = ShellIntegration::GetAppId(
347       UTF8ToWide(app_name), profile_path_);
348 
349   FilePath shortcut_to_pin;
350 
351   bool success = true;
352   for (size_t i = 0; i < shortcut_paths.size(); ++i) {
353     FilePath shortcut_file = shortcut_paths[i].Append(file_name).
354         ReplaceExtension(FILE_PATH_LITERAL(".lnk"));
355 
356     int unique_number = download_util::GetUniquePathNumber(shortcut_file);
357     if (unique_number == -1) {
358       success = false;
359       continue;
360     } else if (unique_number > 0) {
361       download_util::AppendNumberToPath(&shortcut_file, unique_number);
362     }
363 
364     success &= file_util::CreateShortcutLink(chrome_exe.value().c_str(),
365         shortcut_file.value().c_str(),
366         chrome_folder.value().c_str(),
367         wide_switches.c_str(),
368         shortcut_info_.description.c_str(),
369         icon_file.value().c_str(),
370         0,
371         app_id.c_str());
372 
373     // Any shortcut would work for the pinning. We use the first one.
374     if (success && pin_to_taskbar && shortcut_to_pin.empty())
375       shortcut_to_pin = shortcut_file;
376   }
377 
378   if (success && pin_to_taskbar) {
379     if (!shortcut_to_pin.empty()) {
380       success &= file_util::TaskbarPinShortcutLink(
381           shortcut_to_pin.value().c_str());
382     } else {
383       NOTREACHED();
384       success = false;
385     }
386   }
387 
388   return success;
389 #else
390   NOTIMPLEMENTED();
391   return false;
392 #endif
393 }
394 
395 }  // namespace
396 
397 namespace web_app {
398 
399 // The following string is used to build the directory name for
400 // shortcuts to chrome applications (the kind which are installed
401 // from a CRX).  Application shortcuts to URLs use the {host}_{path}
402 // for the name of this directory.  Hosts can't include an underscore.
403 // By starting this string with an underscore, we ensure that there
404 // are no naming conflicts.
405 static const char* kCrxAppPrefix = "_crx_";
406 
407 namespace internals {
408 
409 #if defined(OS_WIN)
410 // Returns sanitized name that could be used as a file name
GetSanitizedFileName(const string16 & name)411 FilePath GetSanitizedFileName(const string16& name) {
412   string16 file_name;
413 
414   for (size_t i = 0; i < name.length(); ++i) {
415     char16 c = name[i];
416     if (!IsValidFilePathChar(c))
417       c = '_';
418 
419     file_name += c;
420   }
421 
422   return FilePath(file_name);
423 }
424 
425 // Saves |image| to |icon_file| if the file is outdated and refresh shell's
426 // icon cache to ensure correct icon is displayed. Returns true if icon_file
427 // is up to date or successfully updated.
CheckAndSaveIcon(const FilePath & icon_file,const SkBitmap & image)428 bool CheckAndSaveIcon(const FilePath& icon_file, const SkBitmap& image) {
429   if (ShouldUpdateIcon(icon_file, image)) {
430     if (SaveIconWithCheckSum(icon_file, image)) {
431       // Refresh shell's icon cache. This call is quite disruptive as user would
432       // see explorer rebuilding the icon cache. It would be great that we find
433       // a better way to achieve this.
434       SHChangeNotify(SHCNE_ASSOCCHANGED, SHCNF_IDLIST | SHCNF_FLUSHNOWAIT,
435                      NULL, NULL);
436     } else {
437       return false;
438     }
439   }
440 
441   return true;
442 }
443 #endif  // OS_WIN
444 
445 // Returns data directory for given web app url
GetWebAppDataDirectory(const FilePath & root_dir,const ShellIntegration::ShortcutInfo & info)446 FilePath GetWebAppDataDirectory(const FilePath& root_dir,
447                                 const ShellIntegration::ShortcutInfo& info) {
448   return root_dir.Append(GetWebAppDir(info));
449 }
450 
451 }  // namespace internals
452 
GenerateApplicationNameFromInfo(const ShellIntegration::ShortcutInfo & shortcut_info)453 std::string GenerateApplicationNameFromInfo(
454     const ShellIntegration::ShortcutInfo& shortcut_info) {
455   if (!shortcut_info.extension_id.empty()) {
456     return web_app::GenerateApplicationNameFromExtensionId(
457         shortcut_info.extension_id);
458   } else {
459     return web_app::GenerateApplicationNameFromURL(
460         shortcut_info.url);
461   }
462 }
463 
GenerateApplicationNameFromURL(const GURL & url)464 std::string GenerateApplicationNameFromURL(const GURL& url) {
465   std::string t;
466   t.append(url.host());
467   t.append("_");
468   t.append(url.path());
469   return t;
470 }
471 
GenerateApplicationNameFromExtensionId(const std::string & id)472 std::string GenerateApplicationNameFromExtensionId(const std::string& id) {
473   std::string t(web_app::kCrxAppPrefix);
474   t.append(id);
475   return t;
476 }
477 
CreateShortcut(const FilePath & data_dir,const ShellIntegration::ShortcutInfo & shortcut_info,CreateShortcutCallback * callback)478 void CreateShortcut(
479     const FilePath& data_dir,
480     const ShellIntegration::ShortcutInfo& shortcut_info,
481     CreateShortcutCallback* callback) {
482   BrowserThread::PostTask(BrowserThread::FILE, FROM_HERE,
483       new CreateShortcutTask(data_dir, shortcut_info, callback));
484 }
485 
IsValidUrl(const GURL & url)486 bool IsValidUrl(const GURL& url) {
487   static const char* const kValidUrlSchemes[] = {
488       chrome::kFileScheme,
489       chrome::kFtpScheme,
490       chrome::kHttpScheme,
491       chrome::kHttpsScheme,
492       chrome::kExtensionScheme,
493   };
494 
495   for (size_t i = 0; i < arraysize(kValidUrlSchemes); ++i) {
496     if (url.SchemeIs(kValidUrlSchemes[i]))
497       return true;
498   }
499 
500   return false;
501 }
502 
GetDataDir(const FilePath & profile_path)503 FilePath GetDataDir(const FilePath& profile_path) {
504   return profile_path.Append(chrome::kWebAppDirname);
505 }
506 
507 #if defined(TOOLKIT_VIEWS)
GetIconsInfo(const WebApplicationInfo & app_info,IconInfoList * icons)508 void GetIconsInfo(const WebApplicationInfo& app_info,
509                   IconInfoList* icons) {
510   DCHECK(icons);
511 
512   icons->clear();
513   for (size_t i = 0; i < app_info.icons.size(); ++i) {
514     // We only take square shaped icons (i.e. width == height).
515     if (app_info.icons[i].width == app_info.icons[i].height) {
516       icons->push_back(app_info.icons[i]);
517     }
518   }
519 
520   std::sort(icons->begin(), icons->end(), &IconPrecedes);
521 }
522 #endif
523 
524 #if defined(TOOLKIT_USES_GTK)
GetWMClassFromAppName(std::string app_name)525 std::string GetWMClassFromAppName(std::string app_name) {
526   file_util::ReplaceIllegalCharactersInPath(&app_name, '_');
527   TrimString(app_name, "_", &app_name);
528   return app_name;
529 }
530 #endif
531 
532 }  // namespace web_app
533