Home | History | Annotate | Download | only in web_applications
      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 // "/ \ : * ? " < > | ; ,".
     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.
     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.
     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.
    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.
    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|.
    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:
    161     CreateShortcutCallbackTask(web_app::CreateShortcutCallback* callback,
    162         bool success)
    163         : callback_(callback),
    164           success_(success) {
    165     }
    166 
    167     // Overridden from Task:
    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 
    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 
    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 
    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
    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.
    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
    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 
    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 
    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 
    472 std::string GenerateApplicationNameFromExtensionId(const std::string& id) {
    473   std::string t(web_app::kCrxAppPrefix);
    474   t.append(id);
    475   return t;
    476 }
    477 
    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 
    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 
    503 FilePath GetDataDir(const FilePath& profile_path) {
    504   return profile_path.Append(chrome::kWebAppDirname);
    505 }
    506 
    507 #if defined(TOOLKIT_VIEWS)
    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)
    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