1 // Copyright 2013 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/chromeos/extensions/wallpaper_api.h"
6 
7 #include <string>
8 #include <utility>
9 #include <vector>
10 
11 #include "ash/public/cpp/wallpaper_types.h"
12 #include "base/bind.h"
13 #include "base/check_op.h"
14 #include "base/files/file_util.h"
15 #include "base/lazy_instance.h"
16 #include "base/sequenced_task_runner.h"
17 #include "base/strings/stringprintf.h"
18 #include "base/values.h"
19 #include "chrome/browser/browser_process.h"
20 #include "chrome/browser/chromeos/extensions/wallpaper_private_api.h"
21 #include "chrome/browser/chromeos/file_manager/app_id.h"
22 #include "chrome/browser/chromeos/profiles/profile_helper.h"
23 #include "chrome/browser/net/system_network_context_manager.h"
24 #include "chrome/browser/profiles/profile.h"
25 #include "chrome/browser/ui/ash/wallpaper_controller_client.h"
26 #include "chrome/common/chrome_paths.h"
27 #include "chrome/common/extensions/extension_constants.h"
28 #include "components/user_manager/user.h"
29 #include "components/user_manager/user_manager.h"
30 #include "content/public/browser/browser_thread.h"
31 #include "extensions/browser/event_router.h"
32 #include "net/base/load_flags.h"
33 #include "net/http/http_response_headers.h"
34 #include "services/network/public/cpp/resource_request.h"
35 #include "services/network/public/cpp/simple_url_loader.h"
36 #include "services/network/public/mojom/url_loader_factory.mojom.h"
37 #include "url/gurl.h"
38 
39 using base::Value;
40 using content::BrowserThread;
41 
42 typedef base::Callback<void(bool success, const std::string&)> FetchCallback;
43 
44 namespace set_wallpaper = extensions::api::wallpaper::SetWallpaper;
45 
46 namespace {
47 
48 class WallpaperFetcher {
49  public:
WallpaperFetcher()50   WallpaperFetcher() {}
51 
FetchWallpaper(const GURL & url,FetchCallback callback)52   void FetchWallpaper(const GURL& url, FetchCallback callback) {
53     CancelPreviousFetch();
54     original_url_ = url;
55     callback_ = callback;
56 
57     net::NetworkTrafficAnnotationTag traffic_annotation =
58         net::DefineNetworkTrafficAnnotation("wallpaper_fetcher", R"(
59           semantics {
60             sender: "Wallpaper Fetcher"
61             description:
62               "Chrome OS downloads wallpaper upon user request."
63             trigger:
64               "When an app or extension requests to download "
65               "a wallpaper from a remote URL."
66             data:
67               "User-selected image."
68             destination: WEBSITE
69           }
70           policy {
71             cookies_allowed: YES
72             cookies_store: "user"
73             setting:
74               "This feature cannot be disabled by settings, but it is only "
75               "triggered by user request."
76             policy_exception_justification: "Not implemented."
77           })");
78     auto resource_request = std::make_unique<network::ResourceRequest>();
79     resource_request->url = original_url_;
80     resource_request->load_flags = net::LOAD_DISABLE_CACHE;
81     simple_loader_ = network::SimpleURLLoader::Create(
82         std::move(resource_request), traffic_annotation);
83     network::mojom::URLLoaderFactory* loader_factory =
84         g_browser_process->system_network_context_manager()
85             ->GetURLLoaderFactory();
86     simple_loader_->DownloadToStringOfUnboundedSizeUntilCrashAndDie(
87         loader_factory,
88         base::BindOnce(&WallpaperFetcher::OnSimpleLoaderComplete,
89                        base::Unretained(this)));
90   }
91 
92  private:
OnSimpleLoaderComplete(std::unique_ptr<std::string> response_body)93   void OnSimpleLoaderComplete(std::unique_ptr<std::string> response_body) {
94     std::string response;
95     bool success = false;
96     if (response_body) {
97       response = std::move(*response_body);
98       success = true;
99     } else if (simple_loader_->ResponseInfo() &&
100                simple_loader_->ResponseInfo()->headers) {
101       int response_code =
102           simple_loader_->ResponseInfo()->headers->response_code();
103       response = base::StringPrintf(
104           "Downloading wallpaper %s failed. The response code is %d.",
105           original_url_.ExtractFileName().c_str(), response_code);
106     }
107 
108     simple_loader_.reset();
109     callback_.Run(success, response);
110     callback_.Reset();
111   }
112 
CancelPreviousFetch()113   void CancelPreviousFetch() {
114     if (simple_loader_.get()) {
115       callback_.Run(false, wallpaper_api_util::kCancelWallpaperMessage);
116       callback_.Reset();
117       simple_loader_.reset();
118     }
119   }
120 
121   GURL original_url_;
122   std::unique_ptr<network::SimpleURLLoader> simple_loader_;
123   FetchCallback callback_;
124 };
125 
126 base::LazyInstance<WallpaperFetcher>::DestructorAtExit g_wallpaper_fetcher =
127     LAZY_INSTANCE_INITIALIZER;
128 
129 // Gets the |User| for a given |BrowserContext|. The function will only return
130 // valid objects.
GetUserFromBrowserContext(content::BrowserContext * context)131 const user_manager::User* GetUserFromBrowserContext(
132     content::BrowserContext* context) {
133   Profile* profile = Profile::FromBrowserContext(context);
134   DCHECK(profile);
135   const user_manager::User* user =
136       chromeos::ProfileHelper::Get()->GetUserByProfile(profile);
137   DCHECK(user);
138   return user;
139 }
140 
141 }  // namespace
142 
WallpaperSetWallpaperFunction()143 WallpaperSetWallpaperFunction::WallpaperSetWallpaperFunction() {
144 }
145 
~WallpaperSetWallpaperFunction()146 WallpaperSetWallpaperFunction::~WallpaperSetWallpaperFunction() {
147 }
148 
Run()149 ExtensionFunction::ResponseAction WallpaperSetWallpaperFunction::Run() {
150   DCHECK_CURRENTLY_ON(BrowserThread::UI);
151   params_ = set_wallpaper::Params::Create(*args_);
152   EXTENSION_FUNCTION_VALIDATE(params_);
153 
154   // Gets account id from the caller, ensuring multiprofile compatibility.
155   const user_manager::User* user = GetUserFromBrowserContext(browser_context());
156   account_id_ = user->GetAccountId();
157   wallpaper_files_id_ =
158       WallpaperControllerClient::Get()->GetFilesId(account_id_);
159 
160   if (params_->details.data) {
161     StartDecode(*params_->details.data);
162     // StartDecode() responds asynchronously.
163     return RespondLater();
164   }
165 
166   if (!params_->details.url)
167     return RespondNow(Error("Either url or data field is required."));
168 
169   GURL wallpaper_url(*params_->details.url);
170   if (!wallpaper_url.is_valid())
171     return RespondNow(Error("URL is invalid."));
172 
173   g_wallpaper_fetcher.Get().FetchWallpaper(
174       wallpaper_url,
175       base::Bind(&WallpaperSetWallpaperFunction::OnWallpaperFetched, this));
176   // FetchWallpaper() repsonds asynchronously.
177   return RespondLater();
178 }
179 
OnWallpaperDecoded(const gfx::ImageSkia & image)180 void WallpaperSetWallpaperFunction::OnWallpaperDecoded(
181     const gfx::ImageSkia& image) {
182   ash::WallpaperLayout layout = wallpaper_api_util::GetLayoutEnum(
183       extensions::api::wallpaper::ToString(params_->details.layout));
184   wallpaper_api_util::RecordCustomWallpaperLayout(layout);
185 
186   const std::string file_name =
187       base::FilePath(params_->details.filename).BaseName().value();
188   WallpaperControllerClient::Get()->SetCustomWallpaper(
189       account_id_, wallpaper_files_id_, file_name, layout, image,
190       /*preview_mode=*/false);
191   unsafe_wallpaper_decoder_ = nullptr;
192 
193   // We need to generate thumbnail image anyway to make the current third party
194   // wallpaper syncable through different devices.
195   image.EnsureRepsForSupportedScales();
196   std::vector<uint8_t> thumbnail_data = GenerateThumbnail(
197       image, gfx::Size(kWallpaperThumbnailWidth, kWallpaperThumbnailHeight));
198 
199   // Inform the native Wallpaper Picker Application that the current wallpaper
200   // has been modified by a third party application.
201   if (extension()->id() != extension_misc::kWallpaperManagerId) {
202     Profile* profile = Profile::FromBrowserContext(browser_context());
203     extensions::EventRouter* event_router =
204         extensions::EventRouter::Get(profile);
205 
206     base::Value event_args(Value::Type::LIST);
207     event_args.Append(Value(GenerateThumbnail(image, image.size())));
208     event_args.Append(Value(thumbnail_data));
209     event_args.Append(
210         extensions::api::wallpaper::ToString(params_->details.layout));
211     // Setting wallpaper from right click menu in 'Files' app is a feature that
212     // was implemented in crbug.com/578935. Since 'Files' app is a built-in v1
213     // app in ChromeOS, we should treat it slightly differently with other third
214     // party apps: the wallpaper set by the 'Files' app should still be syncable
215     // and it should not appear in the wallpaper grid in the Wallpaper Picker.
216     // But we should not display the 'wallpaper-set-by-mesage' since it might
217     // introduce confusion as shown in crbug.com/599407.
218     event_args.Append((extension()->id() == file_manager::kFileManagerAppId)
219                           ? base::StringPiece()
220                           : extension()->name());
221     std::unique_ptr<extensions::Event> event(new extensions::Event(
222         extensions::events::WALLPAPER_PRIVATE_ON_WALLPAPER_CHANGED_BY_3RD_PARTY,
223         extensions::api::wallpaper_private::OnWallpaperChangedBy3rdParty::
224             kEventName,
225         base::ListValue::From(std::make_unique<Value>(std::move(event_args)))));
226     event_router->DispatchEventToExtension(extension_misc::kWallpaperManagerId,
227                                            std::move(event));
228   }
229 
230   Respond(params_->details.thumbnail
231               ? OneArgument(Value(std::move(thumbnail_data)))
232               : NoArguments());
233 }
234 
OnWallpaperFetched(bool success,const std::string & response)235 void WallpaperSetWallpaperFunction::OnWallpaperFetched(
236     bool success,
237     const std::string& response) {
238   if (success) {
239     params_->details.data.reset(
240         new std::vector<uint8_t>(response.begin(), response.end()));
241     StartDecode(*params_->details.data);
242     // StartDecode() will Respond later through OnWallpaperDecoded()
243   } else {
244     Respond(Error(response));
245   }
246 }
247