1 // Copyright 2019 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 "content/browser/native_file_system/native_file_system_handle_base.h"
6 
7 #include "base/task/post_task.h"
8 #include "content/browser/native_file_system/native_file_system_error.h"
9 #include "content/browser/web_contents/web_contents_impl.h"
10 #include "content/public/browser/back_forward_cache.h"
11 #include "content/public/browser/browser_task_traits.h"
12 
13 namespace content {
14 
NativeFileSystemHandleBase(NativeFileSystemManagerImpl * manager,const BindingContext & context,const storage::FileSystemURL & url,const SharedHandleState & handle_state,bool is_directory)15 NativeFileSystemHandleBase::NativeFileSystemHandleBase(
16     NativeFileSystemManagerImpl* manager,
17     const BindingContext& context,
18     const storage::FileSystemURL& url,
19     const SharedHandleState& handle_state,
20     bool is_directory)
21     : manager_(manager),
22       context_(context),
23       url_(url),
24       handle_state_(handle_state) {
25   DCHECK(manager_);
26   DCHECK_EQ(url_.mount_type() == storage::kFileSystemTypeIsolated,
27             handle_state_.file_system.is_valid())
28       << url_.mount_type();
29   // For now only support sandboxed file system and native file system.
30   DCHECK(url_.type() == storage::kFileSystemTypeNativeLocal ||
31          url_.type() == storage::kFileSystemTypePersistent ||
32          url_.type() == storage::kFileSystemTypeTemporary ||
33          url_.type() == storage::kFileSystemTypeTest)
34       << url_.type();
35 
36   if (ShouldTrackUsage()) {
37     DCHECK_EQ(url_.type(), storage::kFileSystemTypeNativeLocal);
38     DCHECK_EQ(url_.mount_type(), storage::kFileSystemTypeIsolated);
39 
40     handle_state_.read_grant->AddObserver(this);
41     // In some cases we use the same grant for read and write access. In that
42     // case only add an observer once.
43     if (handle_state_.read_grant != handle_state_.write_grant)
44       handle_state_.write_grant->AddObserver(this);
45 
46     Observe(WebContentsImpl::FromRenderFrameHostID(context_.process_id,
47                                                    context_.frame_id));
48 
49     // Disable back-forward cache as native file system's usage of
50     // RenderFrameHost::IsCurrent at the moment is not compatible with bfcache.
51     BackForwardCache::DisableForRenderFrameHost(
52         GlobalFrameRoutingId(context_.process_id, context_.frame_id),
53         "NativeFileSystem");
54 
55     if (is_directory) {
56       // For usage reporting purposes try to get the root path of the isolated
57       // file system, i.e. the path the user picked in a directory picker.
58       auto* isolated_context = storage::IsolatedContext::GetInstance();
59       if (!isolated_context->GetRegisteredPath(
60               handle_state_.file_system.id(), &directory_for_usage_tracking_)) {
61         // If for some reason the isolated file system no longer exists, fall
62         // back to the path of the handle itself, which could be a child of
63         // the originally picked path.
64         directory_for_usage_tracking_ = url.path();
65       }
66     }
67 
68     if (web_contents())
69       web_contents()->IncrementNativeFileSystemHandleCount();
70     UpdateUsage();
71   }
72 }
73 
~NativeFileSystemHandleBase()74 NativeFileSystemHandleBase::~NativeFileSystemHandleBase() {
75   DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
76   // It is fine to remove an observer that never was added, so no need to check
77   // for URL type and/or the same grant being used for read and write access.
78   handle_state_.read_grant->RemoveObserver(this);
79   handle_state_.write_grant->RemoveObserver(this);
80 
81   if (ShouldTrackUsage() && web_contents()) {
82     web_contents()->DecrementNativeFileSystemHandleCount();
83     if (!directory_for_usage_tracking_.empty() && was_readable_at_last_check_) {
84       web_contents()->RemoveNativeFileSystemDirectoryHandle(
85           directory_for_usage_tracking_);
86     }
87     if (was_writable_at_last_check_)
88       web_contents()->DecrementWritableNativeFileSystemHandleCount();
89   }
90 }
91 
92 NativeFileSystemHandleBase::PermissionStatus
GetReadPermissionStatus()93 NativeFileSystemHandleBase::GetReadPermissionStatus() {
94   DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
95   return handle_state_.read_grant->GetStatus();
96 }
97 
98 NativeFileSystemHandleBase::PermissionStatus
GetWritePermissionStatus()99 NativeFileSystemHandleBase::GetWritePermissionStatus() {
100   DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
101   UpdateUsage();
102   // It is not currently possible to have write only handles, so first check the
103   // read permission status. See also:
104   // http://wicg.github.io/native-file-system/#api-filesystemhandle-querypermission
105   PermissionStatus read_status = GetReadPermissionStatus();
106   if (read_status != PermissionStatus::GRANTED)
107     return read_status;
108 
109   return handle_state_.write_grant->GetStatus();
110 }
111 
DoGetPermissionStatus(bool writable,base::OnceCallback<void (PermissionStatus)> callback)112 void NativeFileSystemHandleBase::DoGetPermissionStatus(
113     bool writable,
114     base::OnceCallback<void(PermissionStatus)> callback) {
115   DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
116   std::move(callback).Run(writable ? GetWritePermissionStatus()
117                                    : GetReadPermissionStatus());
118 }
119 
DoRequestPermission(bool writable,base::OnceCallback<void (blink::mojom::NativeFileSystemErrorPtr,PermissionStatus)> callback)120 void NativeFileSystemHandleBase::DoRequestPermission(
121     bool writable,
122     base::OnceCallback<void(blink::mojom::NativeFileSystemErrorPtr,
123                             PermissionStatus)> callback) {
124   DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
125   PermissionStatus current_status =
126       writable ? GetWritePermissionStatus() : GetReadPermissionStatus();
127   // If we already have a valid permission status, just return that. Also just
128   // return the current permission status if this is called from a worker, as we
129   // don't support prompting for increased permissions from workers.
130   //
131   // Currently the worker check here is redundant because there is no way for
132   // workers to get native file system handles. While workers will never be able
133   // to call chooseEntries(), they will be able to receive existing handles from
134   // windows via postMessage() and IndexedDB.
135   if (current_status != PermissionStatus::ASK || context_.is_worker()) {
136     std::move(callback).Run(native_file_system_error::Ok(), current_status);
137     return;
138   }
139   if (!writable) {
140     handle_state_.read_grant->RequestPermission(
141         context().process_id, context().frame_id,
142         base::BindOnce(&NativeFileSystemHandleBase::DidRequestPermission,
143                        AsWeakPtr(), writable, std::move(callback)));
144     return;
145   }
146 
147   // Ask for both read and write permission at the same time, the permission
148   // context should coalesce these into one prompt.
149   if (GetReadPermissionStatus() == PermissionStatus::ASK) {
150     // Ignore callback for the read permission request; if the request fails,
151     // the write permission request probably fails the same way. And we check
152     // the final permission status after the permission request completes
153     // anyway.
154     handle_state_.read_grant->RequestPermission(
155         context().process_id, context().frame_id, base::DoNothing());
156   }
157 
158   handle_state_.write_grant->RequestPermission(
159       context().process_id, context().frame_id,
160       base::BindOnce(&NativeFileSystemHandleBase::DidRequestPermission,
161                      AsWeakPtr(), writable, std::move(callback)));
162 }
163 
DidRequestPermission(bool writable,base::OnceCallback<void (blink::mojom::NativeFileSystemErrorPtr,PermissionStatus)> callback,NativeFileSystemPermissionGrant::PermissionRequestOutcome outcome)164 void NativeFileSystemHandleBase::DidRequestPermission(
165     bool writable,
166     base::OnceCallback<void(blink::mojom::NativeFileSystemErrorPtr,
167                             PermissionStatus)> callback,
168     NativeFileSystemPermissionGrant::PermissionRequestOutcome outcome) {
169   DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
170   using Outcome = NativeFileSystemPermissionGrant::PermissionRequestOutcome;
171   switch (outcome) {
172     case Outcome::kInvalidFrame:
173     case Outcome::kThirdPartyContext:
174       std::move(callback).Run(
175           native_file_system_error::FromStatus(
176               blink::mojom::NativeFileSystemStatus::kPermissionDenied,
177               "Not allowed to request permissions in this context."),
178           writable ? GetWritePermissionStatus() : GetReadPermissionStatus());
179       return;
180     case Outcome::kNoUserActivation:
181       std::move(callback).Run(
182           native_file_system_error::FromStatus(
183               blink::mojom::NativeFileSystemStatus::kPermissionDenied,
184               "User activation is required to request permissions."),
185           writable ? GetWritePermissionStatus() : GetReadPermissionStatus());
186       return;
187     case Outcome::kBlockedByContentSetting:
188     case Outcome::kUserGranted:
189     case Outcome::kUserDenied:
190     case Outcome::kUserDismissed:
191     case Outcome::kRequestAborted:
192     case Outcome::kGrantedByContentSetting:
193       std::move(callback).Run(
194           native_file_system_error::Ok(),
195           writable ? GetWritePermissionStatus() : GetReadPermissionStatus());
196       return;
197   }
198   NOTREACHED();
199 }
200 
UpdateUsage()201 void NativeFileSystemHandleBase::UpdateUsage() {
202   DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
203   if (!ShouldTrackUsage() || !web_contents())
204     return;
205 
206   bool is_readable =
207       handle_state_.read_grant->GetStatus() == PermissionStatus::GRANTED;
208   if (is_readable != was_readable_at_last_check_) {
209     was_readable_at_last_check_ = is_readable;
210     if (!directory_for_usage_tracking_.empty()) {
211       if (is_readable) {
212         web_contents()->AddNativeFileSystemDirectoryHandle(
213             directory_for_usage_tracking_);
214       } else {
215         web_contents()->RemoveNativeFileSystemDirectoryHandle(
216             directory_for_usage_tracking_);
217       }
218     }
219   }
220 
221   bool is_writable = is_readable && handle_state_.write_grant->GetStatus() ==
222                                         PermissionStatus::GRANTED;
223   if (is_writable != was_writable_at_last_check_) {
224     was_writable_at_last_check_ = is_writable;
225     if (is_writable)
226       web_contents()->IncrementWritableNativeFileSystemHandleCount();
227     else
228       web_contents()->DecrementWritableNativeFileSystemHandleCount();
229   }
230 }
231 
OnPermissionStatusChanged()232 void NativeFileSystemHandleBase::OnPermissionStatusChanged() {
233   DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
234   UpdateUsage();
235 }
236 
237 }  // namespace content
238