1// Copyright (c) 2012 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/android/download_controller_android_impl.h"
6
7#include "base/android/jni_android.h"
8#include "base/android/jni_string.h"
9#include "base/bind.h"
10#include "base/logging.h"
11#include "base/memory/scoped_ptr.h"
12#include "base/time/time.h"
13#include "content/browser/android/content_view_core_impl.h"
14#include "content/browser/download/download_item_impl.h"
15#include "content/browser/download/download_manager_impl.h"
16#include "content/browser/loader/resource_dispatcher_host_impl.h"
17#include "content/browser/renderer_host/render_process_host_impl.h"
18#include "content/browser/renderer_host/render_view_host_delegate.h"
19#include "content/browser/renderer_host/render_view_host_impl.h"
20#include "content/browser/web_contents/web_contents_impl.h"
21#include "content/public/browser/browser_context.h"
22#include "content/public/browser/browser_thread.h"
23#include "content/public/browser/download_url_parameters.h"
24#include "content/public/browser/global_request_id.h"
25#include "content/public/browser/resource_request_info.h"
26#include "content/public/common/referrer.h"
27#include "jni/DownloadController_jni.h"
28#include "net/cookies/cookie_options.h"
29#include "net/cookies/cookie_store.h"
30#include "net/http/http_content_disposition.h"
31#include "net/http/http_request_headers.h"
32#include "net/http/http_response_headers.h"
33#include "net/url_request/url_request.h"
34#include "net/url_request/url_request_context.h"
35
36using base::android::ConvertUTF8ToJavaString;
37using base::android::ScopedJavaLocalRef;
38
39namespace content {
40
41// JNI methods
42static void Init(JNIEnv* env, jobject obj) {
43  DownloadControllerAndroidImpl::GetInstance()->Init(env, obj);
44}
45
46struct DownloadControllerAndroidImpl::JavaObject {
47  ScopedJavaLocalRef<jobject> Controller(JNIEnv* env) {
48    return GetRealObject(env, obj);
49  }
50  jweak obj;
51};
52
53// static
54bool DownloadControllerAndroidImpl::RegisterDownloadController(JNIEnv* env) {
55  return RegisterNativesImpl(env);
56}
57
58// static
59DownloadControllerAndroid* DownloadControllerAndroid::Get() {
60  return DownloadControllerAndroidImpl::GetInstance();
61}
62
63// static
64DownloadControllerAndroidImpl* DownloadControllerAndroidImpl::GetInstance() {
65  return Singleton<DownloadControllerAndroidImpl>::get();
66}
67
68DownloadControllerAndroidImpl::DownloadControllerAndroidImpl()
69    : java_object_(NULL) {
70}
71
72DownloadControllerAndroidImpl::~DownloadControllerAndroidImpl() {
73  if (java_object_) {
74    JNIEnv* env = base::android::AttachCurrentThread();
75    env->DeleteWeakGlobalRef(java_object_->obj);
76    delete java_object_;
77    base::android::CheckException(env);
78  }
79}
80
81// Initialize references to Java object.
82void DownloadControllerAndroidImpl::Init(JNIEnv* env, jobject obj) {
83  java_object_ = new JavaObject;
84  java_object_->obj = env->NewWeakGlobalRef(obj);
85}
86
87void DownloadControllerAndroidImpl::CreateGETDownload(
88    int render_process_id, int render_view_id, int request_id) {
89  DCHECK(BrowserThread::CurrentlyOn(BrowserThread::IO));
90  GlobalRequestID global_id(render_process_id, request_id);
91
92  // We are yielding the UI thread and render_view_host may go away by
93  // the time we come back. Pass along render_process_id and render_view_id
94  // to retrieve it later (if it still exists).
95  GetDownloadInfoCB cb = base::Bind(
96        &DownloadControllerAndroidImpl::StartAndroidDownload,
97        base::Unretained(this), render_process_id,
98        render_view_id);
99
100  PrepareDownloadInfo(
101      global_id,
102      base::Bind(&DownloadControllerAndroidImpl::StartDownloadOnUIThread,
103                 base::Unretained(this), cb));
104}
105
106void DownloadControllerAndroidImpl::PrepareDownloadInfo(
107    const GlobalRequestID& global_id,
108    const GetDownloadInfoCB& callback) {
109  DCHECK(BrowserThread::CurrentlyOn(BrowserThread::IO));
110
111  net::URLRequest* request =
112      ResourceDispatcherHostImpl::Get()->GetURLRequest(global_id);
113  if (!request) {
114    LOG(ERROR) << "Request to download not found.";
115    return;
116  }
117
118  DownloadInfoAndroid info_android(request);
119
120  net::CookieStore* cookie_store = request->context()->cookie_store();
121  if (cookie_store) {
122    net::CookieMonster* cookie_monster = cookie_store->GetCookieMonster();
123    if (cookie_monster) {
124      cookie_monster->GetAllCookiesForURLAsync(
125          request->url(),
126          base::Bind(&DownloadControllerAndroidImpl::CheckPolicyAndLoadCookies,
127                     base::Unretained(this), info_android, callback,
128                     global_id));
129    } else {
130      DoLoadCookies(info_android, callback, global_id);
131    }
132  } else {
133    // Can't get any cookies, start android download.
134    callback.Run(info_android);
135  }
136}
137
138void DownloadControllerAndroidImpl::CheckPolicyAndLoadCookies(
139    const DownloadInfoAndroid& info, const GetDownloadInfoCB& callback,
140    const GlobalRequestID& global_id, const net::CookieList& cookie_list) {
141  DCHECK(BrowserThread::CurrentlyOn(BrowserThread::IO));
142
143  net::URLRequest* request =
144      ResourceDispatcherHostImpl::Get()->GetURLRequest(global_id);
145  if (!request) {
146    LOG(ERROR) << "Request to download not found.";
147    return;
148  }
149
150  if (request->context()->network_delegate()->CanGetCookies(
151      *request, cookie_list)) {
152    DoLoadCookies(info, callback, global_id);
153  } else {
154    callback.Run(info);
155  }
156}
157
158void DownloadControllerAndroidImpl::DoLoadCookies(
159    const DownloadInfoAndroid& info, const GetDownloadInfoCB& callback,
160    const GlobalRequestID& global_id) {
161  DCHECK(BrowserThread::CurrentlyOn(BrowserThread::IO));
162
163  net::CookieOptions options;
164  options.set_include_httponly();
165
166  net::URLRequest* request =
167      ResourceDispatcherHostImpl::Get()->GetURLRequest(global_id);
168  if (!request) {
169    LOG(ERROR) << "Request to download not found.";
170    return;
171  }
172
173  request->context()->cookie_store()->GetCookiesWithOptionsAsync(
174      info.url, options,
175      base::Bind(&DownloadControllerAndroidImpl::OnCookieResponse,
176                 base::Unretained(this), info, callback));
177}
178
179void DownloadControllerAndroidImpl::OnCookieResponse(
180    DownloadInfoAndroid download_info,
181    const GetDownloadInfoCB& callback,
182    const std::string& cookie) {
183  download_info.cookie = cookie;
184
185  // We have everything we need, start Android download.
186  callback.Run(download_info);
187}
188
189void DownloadControllerAndroidImpl::StartDownloadOnUIThread(
190    const GetDownloadInfoCB& callback,
191    const DownloadInfoAndroid& info) {
192  DCHECK(BrowserThread::CurrentlyOn(BrowserThread::IO));
193  BrowserThread::PostTask(
194      BrowserThread::UI, FROM_HERE, base::Bind(callback, info));
195}
196
197void DownloadControllerAndroidImpl::StartAndroidDownload(
198    int render_process_id, int render_view_id,
199    const DownloadInfoAndroid& info) {
200  DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
201  JNIEnv* env = base::android::AttachCurrentThread();
202
203  // Call newHttpGetDownload
204  ScopedJavaLocalRef<jobject> view = GetContentView(render_process_id,
205                                                    render_view_id);
206  if (view.is_null()) {
207    // The view went away. Can't proceed.
208    LOG(ERROR) << "Download failed on URL:" << info.url.spec();
209    return;
210  }
211
212  ScopedJavaLocalRef<jstring> jurl =
213      ConvertUTF8ToJavaString(env, info.url.spec());
214  ScopedJavaLocalRef<jstring> juser_agent =
215      ConvertUTF8ToJavaString(env, info.user_agent);
216  ScopedJavaLocalRef<jstring> jcontent_disposition =
217      ConvertUTF8ToJavaString(env, info.content_disposition);
218  ScopedJavaLocalRef<jstring> jmime_type =
219      ConvertUTF8ToJavaString(env, info.original_mime_type);
220  ScopedJavaLocalRef<jstring> jcookie =
221      ConvertUTF8ToJavaString(env, info.cookie);
222  ScopedJavaLocalRef<jstring> jreferer =
223      ConvertUTF8ToJavaString(env, info.referer);
224
225  // Try parsing the content disposition header to get a
226  // explicitly specified filename if available.
227  net::HttpContentDisposition header(info.content_disposition, "");
228  ScopedJavaLocalRef<jstring> jfilename =
229      ConvertUTF8ToJavaString(env, header.filename());
230
231  Java_DownloadController_newHttpGetDownload(
232      env, GetJavaObject()->Controller(env).obj(), view.obj(), jurl.obj(),
233      juser_agent.obj(), jcontent_disposition.obj(), jmime_type.obj(),
234      jcookie.obj(), jreferer.obj(), info.has_user_gesture, jfilename.obj(),
235      info.total_bytes);
236}
237
238void DownloadControllerAndroidImpl::OnDownloadStarted(
239    DownloadItem* download_item) {
240  DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
241  if (!download_item->GetWebContents())
242    return;
243
244  JNIEnv* env = base::android::AttachCurrentThread();
245
246  // Register for updates to the DownloadItem.
247  download_item->AddObserver(this);
248
249  ScopedJavaLocalRef<jobject> view =
250      GetContentViewCoreFromWebContents(download_item->GetWebContents());
251  // The view went away. Can't proceed.
252  if (view.is_null())
253    return;
254
255  ScopedJavaLocalRef<jstring> jmime_type =
256      ConvertUTF8ToJavaString(env, download_item->GetMimeType());
257  ScopedJavaLocalRef<jstring> jfilename = ConvertUTF8ToJavaString(
258      env, download_item->GetTargetFilePath().BaseName().value());
259  Java_DownloadController_onDownloadStarted(
260      env, GetJavaObject()->Controller(env).obj(), view.obj(), jfilename.obj(),
261      jmime_type.obj());
262}
263
264void DownloadControllerAndroidImpl::OnDownloadUpdated(DownloadItem* item) {
265  DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
266  if (item->IsDangerous() && (item->GetState() != DownloadItem::CANCELLED))
267    OnDangerousDownload(item);
268
269  JNIEnv* env = base::android::AttachCurrentThread();
270  ScopedJavaLocalRef<jstring> jurl =
271      ConvertUTF8ToJavaString(env, item->GetURL().spec());
272  ScopedJavaLocalRef<jstring> jmime_type =
273      ConvertUTF8ToJavaString(env, item->GetMimeType());
274  ScopedJavaLocalRef<jstring> jpath =
275      ConvertUTF8ToJavaString(env, item->GetTargetFilePath().value());
276  ScopedJavaLocalRef<jstring> jfilename = ConvertUTF8ToJavaString(
277      env, item->GetTargetFilePath().BaseName().value());
278
279  switch (item->GetState()) {
280    case DownloadItem::IN_PROGRESS: {
281      base::TimeDelta time_delta;
282      item->TimeRemaining(&time_delta);
283      Java_DownloadController_onDownloadUpdated(
284          env, GetJavaObject()->Controller(env).obj(),
285          base::android::GetApplicationContext(), jurl.obj(), jmime_type.obj(),
286          jfilename.obj(), jpath.obj(), item->GetReceivedBytes(), true,
287          item->GetId(), item->PercentComplete(), time_delta.InMilliseconds());
288      break;
289    }
290    case DownloadItem::COMPLETE:
291      // Multiple OnDownloadUpdated() notifications may be issued while the
292      // download is in the COMPLETE state. Only handle one.
293      item->RemoveObserver(this);
294
295      // Call onDownloadCompleted
296      Java_DownloadController_onDownloadCompleted(
297          env, GetJavaObject()->Controller(env).obj(),
298          base::android::GetApplicationContext(), jurl.obj(), jmime_type.obj(),
299          jfilename.obj(), jpath.obj(), item->GetReceivedBytes(), true,
300          item->GetId());
301      break;
302    case DownloadItem::CANCELLED:
303    // TODO(shashishekhar): An interrupted download can be resumed. Android
304    // currently does not support resumable downloads. Add handling for
305    // interrupted case based on item->CanResume().
306    case DownloadItem::INTERRUPTED:
307      // Call onDownloadCompleted with success = false.
308      Java_DownloadController_onDownloadCompleted(
309          env, GetJavaObject()->Controller(env).obj(),
310          base::android::GetApplicationContext(), jurl.obj(), jmime_type.obj(),
311          jfilename.obj(), jpath.obj(), item->GetReceivedBytes(), false,
312          item->GetId());
313      break;
314    case DownloadItem::MAX_DOWNLOAD_STATE:
315      NOTREACHED();
316  }
317}
318
319void DownloadControllerAndroidImpl::OnDangerousDownload(DownloadItem* item) {
320  JNIEnv* env = base::android::AttachCurrentThread();
321  ScopedJavaLocalRef<jstring> jfilename = ConvertUTF8ToJavaString(
322      env, item->GetTargetFilePath().BaseName().value());
323  ScopedJavaLocalRef<jobject> view_core = GetContentViewCoreFromWebContents(
324      item->GetWebContents());
325  if (!view_core.is_null()) {
326    Java_DownloadController_onDangerousDownload(
327        env, GetJavaObject()->Controller(env).obj(), view_core.obj(),
328        jfilename.obj(), item->GetId());
329  }
330}
331
332ScopedJavaLocalRef<jobject> DownloadControllerAndroidImpl::GetContentView(
333    int render_process_id, int render_view_id) {
334  RenderViewHost* render_view_host =
335      RenderViewHost::FromID(render_process_id, render_view_id);
336
337  if (!render_view_host)
338    return ScopedJavaLocalRef<jobject>();
339
340  WebContents* web_contents =
341      render_view_host->GetDelegate()->GetAsWebContents();
342
343  return GetContentViewCoreFromWebContents(web_contents);
344}
345
346ScopedJavaLocalRef<jobject>
347    DownloadControllerAndroidImpl::GetContentViewCoreFromWebContents(
348    WebContents* web_contents) {
349  if (!web_contents)
350    return ScopedJavaLocalRef<jobject>();
351
352  ContentViewCore* view_core = ContentViewCore::FromWebContents(web_contents);
353  return view_core ? view_core->GetJavaObject() :
354      ScopedJavaLocalRef<jobject>();
355}
356
357DownloadControllerAndroidImpl::JavaObject*
358    DownloadControllerAndroidImpl::GetJavaObject() {
359  if (!java_object_) {
360    // Initialize Java DownloadController by calling
361    // DownloadController.getInstance(), which will call Init()
362    // if Java DownloadController is not instantiated already.
363    JNIEnv* env = base::android::AttachCurrentThread();
364    Java_DownloadController_getInstance(env);
365  }
366
367  DCHECK(java_object_);
368  return java_object_;
369}
370
371void DownloadControllerAndroidImpl::StartContextMenuDownload(
372    const ContextMenuParams& params, WebContents* web_contents, bool is_link) {
373  const GURL& url = is_link ? params.link_url : params.src_url;
374  const GURL& referrer = params.frame_url.is_empty() ?
375      params.page_url : params.frame_url;
376  DownloadManagerImpl* dlm = static_cast<DownloadManagerImpl*>(
377      BrowserContext::GetDownloadManager(web_contents->GetBrowserContext()));
378  scoped_ptr<DownloadUrlParameters> dl_params(
379      DownloadUrlParameters::FromWebContents(web_contents, url));
380  dl_params->set_referrer(
381      Referrer(referrer, params.referrer_policy));
382  if (is_link)
383    dl_params->set_referrer_encoding(params.frame_charset);
384  else
385    dl_params->set_prefer_cache(true);
386  dl_params->set_prompt(false);
387  dlm->DownloadUrl(dl_params.Pass());
388}
389
390void DownloadControllerAndroidImpl::DangerousDownloadValidated(
391    WebContents* web_contents, int download_id, bool accept) {
392  if (!web_contents)
393    return;
394  DownloadManagerImpl* dlm = static_cast<DownloadManagerImpl*>(
395      BrowserContext::GetDownloadManager(web_contents->GetBrowserContext()));
396  DownloadItem* item = dlm->GetDownload(download_id);
397  if (!item)
398    return;
399  if (accept)
400    item->ValidateDangerousDownload();
401  else
402    item->Remove();
403}
404
405DownloadControllerAndroidImpl::DownloadInfoAndroid::DownloadInfoAndroid(
406    net::URLRequest* request)
407    : has_user_gesture(false) {
408  request->GetResponseHeaderByName("content-disposition", &content_disposition);
409
410  if (request->response_headers())
411    request->response_headers()->GetMimeType(&original_mime_type);
412
413  request->extra_request_headers().GetHeader(
414      net::HttpRequestHeaders::kUserAgent, &user_agent);
415  GURL referer_url(request->referrer());
416  if (referer_url.is_valid())
417    referer = referer_url.spec();
418  if (!request->url_chain().empty()) {
419    original_url = request->url_chain().front();
420    url = request->url_chain().back();
421  }
422
423  const content::ResourceRequestInfo* info =
424      content::ResourceRequestInfo::ForRequest(request);
425  if (info)
426    has_user_gesture = info->HasUserGesture();
427}
428
429DownloadControllerAndroidImpl::DownloadInfoAndroid::~DownloadInfoAndroid() {}
430
431}  // namespace content
432