blob: ba90efed00951e1ade59c09f05e8f5f0081a5f58 [file] [log] [blame]
// Copyright (c) 2006-2008 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#include "chrome/browser/ssl_blocking_page.h"
#include "base/string_piece.h"
#include "chrome/browser/browser.h"
#include "chrome/browser/browser_resources.h"
#include "chrome/browser/cert_store.h"
#include "chrome/browser/dom_operation_notification_details.h"
#include "chrome/browser/navigation_controller.h"
#include "chrome/browser/navigation_entry.h"
#include "chrome/browser/profile.h"
#include "chrome/browser/render_view_host.h"
#include "chrome/browser/ssl_error_info.h"
#include "chrome/browser/tab_contents.h"
#include "chrome/browser/web_contents.h"
#include "chrome/common/jstemplate_builder.h"
#include "chrome/common/l10n_util.h"
#include "chrome/common/pref_names.h"
#include "chrome/common/pref_service.h"
#include "chrome/common/resource_bundle.h"
#include "generated_resources.h"
// static
SSLBlockingPage::SSLBlockingPageMap*
SSLBlockingPage::tab_to_blocking_page_ = NULL;
SSLBlockingPage::SSLBlockingPage(SSLManager::CertError* error,
Delegate* delegate)
: error_(error),
delegate_(delegate),
delegate_has_been_notified_(false),
remove_last_entry_(true),
created_nav_entry_(false) {
InitSSLBlockingPageMap();
// Remember the tab, because we might not be able to get to it later
// via the error.
tab_ = error->GetTabContents();
DCHECK(tab_);
// If there's already an interstitial in this tab, then we're about to
// replace it. We should be ok with just deleting the previous
// SSLBlockingPage (not hiding it first), since we're about to be shown.
SSLBlockingPageMap::const_iterator iter = tab_to_blocking_page_->find(tab_);
if (iter != tab_to_blocking_page_->end()) {
// Deleting the SSLBlockingPage will also remove it from the map.
delete iter->second;
// Since WebContents::InterstitialPageGone won't be called, we need
// to clear the last NavigationEntry manually.
tab_->controller()->RemoveLastEntryForInterstitial();
}
(*tab_to_blocking_page_)[tab_] = this;
// Register notifications so we can delete ourself if the tab is closed.
NotificationService::current()->AddObserver(this,
NOTIFY_TAB_CLOSING,
Source<NavigationController>(tab_->controller()));
NotificationService::current()->AddObserver(this,
NOTIFY_INTERSTITIAL_PAGE_CLOSED,
Source<NavigationController>(tab_->controller()));
// Register for DOM operations, this is how the blocking page notifies us of
// what the user chooses.
NotificationService::current()->AddObserver(this,
NOTIFY_DOM_OPERATION_RESPONSE,
Source<TabContents>(tab_));
}
SSLBlockingPage::~SSLBlockingPage() {
NotificationService::current()->RemoveObserver(this,
NOTIFY_TAB_CLOSING,
Source<NavigationController>(tab_->controller()));
NotificationService::current()->RemoveObserver(this,
NOTIFY_INTERSTITIAL_PAGE_CLOSED,
Source<NavigationController>(tab_->controller()));
NotificationService::current()->RemoveObserver(this,
NOTIFY_DOM_OPERATION_RESPONSE,
Source<TabContents>(tab_));
SSLBlockingPageMap::iterator iter =
tab_to_blocking_page_->find(tab_);
DCHECK(iter != tab_to_blocking_page_->end());
tab_to_blocking_page_->erase(iter);
if (!delegate_has_been_notified_) {
// The page is closed without the user having chosen what to do, default to
// deny.
NotifyDenyCertificate();
}
}
void SSLBlockingPage::Show() {
// Let's build the html error page.
DictionaryValue strings;
SSLErrorInfo error_info = delegate_->GetSSLErrorInfo(error_);
strings.SetString(L"title",
l10n_util::GetString(IDS_SSL_BLOCKING_PAGE_TITLE));
strings.SetString(L"headLine", error_info.title());
strings.SetString(L"description", error_info.details());
strings.SetString(L"moreInfoTitle",
l10n_util::GetString(IDS_CERT_ERROR_EXTRA_INFO_TITLE));
SetExtraInfo(&strings, error_info.extra_information());
strings.SetString(L"proceed",
l10n_util::GetString(IDS_SSL_BLOCKING_PAGE_PROCEED));
strings.SetString(L"exit",
l10n_util::GetString(IDS_SSL_BLOCKING_PAGE_EXIT));
strings.SetString(L"textdirection",
(l10n_util::GetTextDirection() == l10n_util::RIGHT_TO_LEFT) ?
L"rtl" : L"ltr");
static const StringPiece html(
ResourceBundle::GetSharedInstance().GetRawDataResource(
IDR_SSL_ROAD_BLOCK_HTML));
std::string html_text(jstemplate_builder::GetTemplateHtml(html,
&strings,
"template_root"));
DCHECK(tab_->type() == TAB_CONTENTS_WEB);
WebContents* tab = tab_->AsWebContents();
const net::SSLInfo& ssl_info = error_->ssl_info();
int cert_id = CertStore::GetSharedInstance()->StoreCert(
ssl_info.cert, tab->render_view_host()->process()->host_id());
if (tab_->controller()->GetPendingEntryIndex() == -1) {
// For new navigations, we just create a new navigation entry.
NavigationEntry new_entry(TAB_CONTENTS_WEB);
new_entry.set_url(error_->request_url());
tab_->controller()->AddDummyEntryForInterstitial(new_entry);
created_nav_entry_ = true;
} else {
// When there is a pending entry index, that means we're doing a
// back/forward navigation. Clone that entry instead.
tab_->controller()->AddDummyEntryForInterstitial(
*tab_->controller()->GetPendingEntry());
}
NavigationEntry* entry = tab_->controller()->GetActiveEntry();
entry->set_page_type(NavigationEntry::INTERSTITIAL_PAGE);
entry->ssl().set_security_style(SECURITY_STYLE_AUTHENTICATION_BROKEN);
entry->ssl().set_cert_id(cert_id);
entry->ssl().set_cert_status(ssl_info.cert_status);
entry->ssl().set_security_bits(ssl_info.security_bits);
NotificationService::current()->Notify(
NOTIFY_SSL_STATE_CHANGED,
Source<NavigationController>(tab_->controller()),
NotificationService::NoDetails());
tab->ShowInterstitialPage(html_text, NULL);
}
void SSLBlockingPage::Observe(NotificationType type,
const NotificationSource& source,
const NotificationDetails& details) {
switch (type) {
case NOTIFY_TAB_CLOSING:
case NOTIFY_INTERSTITIAL_PAGE_CLOSED: {
// We created a navigation entry for the interstitial, remove it.
// Note that we don't remove the entry if we are closing all tabs so that
// the last entry is kept for the restoring on next start-up.
Browser* browser = Browser::GetBrowserForController(tab_->controller(),
NULL);
// We may not have a browser (this is the case for constrained popups), in
// which case it does not matter if we do not remove the temporary entry
// as their navigation history is not saved.
if (remove_last_entry_ && browser &&
!browser->tabstrip_model()->closing_all()) {
tab_->controller()->RemoveLastEntryForInterstitial();
}
delete this;
break;
}
case NOTIFY_DOM_OPERATION_RESPONSE: {
std::string json =
Details<DomOperationNotificationDetails>(details)->json();
if (json == "1") {
Proceed();
} else {
DontProceed();
}
break;
}
default:
NOTREACHED();
}
}
void SSLBlockingPage::Proceed() {
// We hide the interstitial page first as allowing the certificate will
// resume the request and we want the WebContents back to showing the
// non interstitial page (otherwise the request completion messages may
// confuse the WebContents if it is still showing the interstitial
// page).
DCHECK(tab_->type() == TAB_CONTENTS_WEB);
tab_->AsWebContents()->HideInterstitialPage(true, true);
// Accepting the certificate resumes the loading of the page.
NotifyAllowCertificate();
// Do not remove the navigation entry if we have not created it explicitly
// as in such cases (session restore) the controller would not create a new
// entry on navigation since the page id is less than max page id.
if (!created_nav_entry_)
remove_last_entry_ = false;
}
void SSLBlockingPage::DontProceed() {
NotifyDenyCertificate();
// We are navigating, remove the current entry before we mess with it.
remove_last_entry_ = false;
tab_->controller()->RemoveLastEntryForInterstitial();
NavigationEntry* entry = tab_->controller()->GetActiveEntry();
if (!entry) {
// Nothing to go to, default to about:blank. Navigating will cause the
// interstitial to hide which will trigger "this" to be deleted.
tab_->controller()->LoadURL(GURL("about:blank"),
PageTransition::AUTO_BOOKMARK);
} else if (entry->tab_type() != TAB_CONTENTS_WEB) {
// Not a WebContent, reload it so to recreate the TabContents for it.
tab_->controller()->Reload();
} else {
DCHECK(tab_->type() == TAB_CONTENTS_WEB);
if (entry->restored()) {
// If this page was restored, it is not available, we have to navigate to
// it.
tab_->controller()->GoToOffset(0);
} else {
tab_->AsWebContents()->HideInterstitialPage(false, false);
}
}
// WARNING: we are now deleted!
}
// static
void SSLBlockingPage::InitSSLBlockingPageMap() {
if (!tab_to_blocking_page_)
tab_to_blocking_page_ = new SSLBlockingPageMap;
}
void SSLBlockingPage::NotifyDenyCertificate() {
DCHECK(!delegate_has_been_notified_);
delegate_->OnDenyCertificate(error_);
delegate_has_been_notified_ = true;
}
void SSLBlockingPage::NotifyAllowCertificate() {
DCHECK(!delegate_has_been_notified_);
delegate_->OnAllowCertificate(error_);
delegate_has_been_notified_ = true;
}
// static
SSLBlockingPage* SSLBlockingPage::GetSSLBlockingPage(
TabContents* tab_contents) {
InitSSLBlockingPageMap();
SSLBlockingPageMap::const_iterator iter =
tab_to_blocking_page_->find(tab_contents);
if (iter == tab_to_blocking_page_->end())
return NULL;
return iter->second;
}
// static
void SSLBlockingPage::SetExtraInfo(
DictionaryValue* strings,
const std::vector<std::wstring>& extra_info) {
DCHECK(extra_info.size() < 5); // We allow 5 paragraphs max.
const std::wstring keys[5] = {
L"moreInfo1", L"moreInfo2", L"moreInfo3", L"moreInfo4", L"moreInfo5"
};
int i;
for (i = 0; i < static_cast<int>(extra_info.size()); i++) {
strings->SetString(keys[i], extra_info[i]);
}
for (;i < 5; i++) {
strings->SetString(keys[i], L"");
}
}