Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Added import of links and text content to ipfs #8422

Merged
merged 2 commits into from
Apr 16, 2021
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 7 additions & 1 deletion app/brave_command_ids.h
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,13 @@
#define IDC_SIDEBAR_SHOW_OPTION_MOUSEOVER 56013
#define IDC_SIDEBAR_SHOW_OPTION_ONCLICK 56014
#define IDC_SIDEBAR_SHOW_OPTION_NEVER 56015

#define IDC_CONTENT_CONTEXT_IMPORT_IPFS 56016
#define IDC_CONTENT_CONTEXT_IMPORT_LINK_IPFS 56017
#define IDC_CONTENT_CONTEXT_IMPORT_IPFS_PAGE 56018
#define IDC_CONTENT_CONTEXT_IMPORT_IMAGE_IPFS 56019
#define IDC_CONTENT_CONTEXT_IMPORT_AUDIO_IPFS 56020
#define IDC_CONTENT_CONTEXT_IMPORT_VIDEO_IPFS 56021
#define IDC_CONTENT_CONTEXT_IMPORT_SELECTED_TEXT_IPFS 56022
#define IDC_BRAVE_COMMANDS_LAST 57000

#endif // BRAVE_APP_BRAVE_COMMAND_IDS_H_
21 changes: 21 additions & 0 deletions app/brave_generated_resources.grd
Original file line number Diff line number Diff line change
Expand Up @@ -352,6 +352,27 @@ By installing this extension, you are agreeing to the Google Widevine Terms of U
<message name="IDS_LOCATION_BAR_ONION_AVAILABLE" desc="Button in location bar to indicate onion available site to open a new tab in tor window.">
Onion Available
</message>
<message name="IDS_CONTENT_CONTEXT_IMPORT_IPFS" desc="The name of the IPFS context menu item for renderer">
Import to IPFS
</message>
<message name="IDS_CONTENT_CONTEXT_IMPORT_IPFS_PAGE" desc="The name of the IPFS context menu item to import current page using IPFS">
This page
</message>
<message name="IDS_CONTENT_CONTEXT_IMPORT_IPFS_IMAGE" desc="The name of the IPFS context menu item to import selected image using IPFS">
Selected image
</message>
<message name="IDS_CONTENT_CONTEXT_IMPORT_IPFS_VIDEO" desc="The name of the IPFS context menu item to import selected video using IPFS">
Selected video
</message>
<message name="IDS_CONTENT_CONTEXT_IMPORT_IPFS_AUDIO" desc="The name of the IPFS context menu item to import selected audio using IPFS">
Selected audio
</message>
<message name="IDS_CONTENT_CONTEXT_IMPORT_IPFS_LINK" desc="The name of the IPFS context menu item to import linked content using IPFS">
Linked content
</message>
<message name="IDS_CONTENT_CONTEXT_IMPORT_IPFS_SELECTED_TEXT" desc="The name of the IPFS context menu item to import selected text using IPFS">
Import Selected text to IPFS
</message>
<if expr="use_titlecase">
<message name="IDS_NEW_OFFTHERECORD_WINDOW_TOR" desc="Title case: The text label of a menu item to open a new off-the-record window with Tor.">
New Private Window with Tor
Expand Down
144 changes: 144 additions & 0 deletions browser/ipfs/ipfs_tab_helper.cc
Original file line number Diff line number Diff line change
Expand Up @@ -12,19 +12,27 @@
#include "base/bind.h"
#include "base/callback_helpers.h"
#include "base/containers/contains.h"
#include "base/guid.h"
#include "base/location.h"
#include "base/single_thread_task_runner.h"
#include "base/strings/string_split.h"
#include "base/strings/utf_string_conversions.h"
#include "base/threading/thread_task_runner_handle.h"
#include "brave/browser/ipfs/ipfs_host_resolver.h"
#include "brave/browser/ipfs/ipfs_service_factory.h"
#include "brave/common/webui_url_constants.h"
#include "brave/components/ipfs/imported_data.h"
#include "brave/components/ipfs/ipfs_constants.h"
#include "brave/components/ipfs/ipfs_service.h"
#include "brave/components/ipfs/ipfs_utils.h"
#include "brave/components/ipfs/pref_names.h"
#include "chrome/browser/browser_process.h"
#include "chrome/browser/net/system_network_context_manager.h"
#include "chrome/browser/notifications/notification_display_service.h"
#include "chrome/browser/profiles/profile.h"
#include "chrome/browser/shell_integration.h"
#include "chrome/common/channel_info.h"
#include "components/grit/brave_components_strings.h"
#include "components/prefs/pref_service.h"
#include "components/user_prefs/user_prefs.h"
#include "content/public/browser/browser_context.h"
Expand All @@ -33,7 +41,13 @@
#include "content/public/browser/navigation_handle.h"
#include "content/public/browser/storage_partition.h"
#include "content/public/browser/web_contents_delegate.h"
#include "net/base/url_util.h"
#include "net/http/http_status_code.h"
#include "ui/base/clipboard/scoped_clipboard_writer.h"
#include "ui/base/l10n/l10n_util.h"
#include "ui/message_center/public/cpp/notification.h"
#include "ui/message_center/public/cpp/notification_types.h"
#include "ui/message_center/public/cpp/notifier_id.h"

namespace {

Expand All @@ -45,6 +59,12 @@ const char kDnsDomainPrefix[] = "_dnslink.";
// The value of the header is the IPFS path of the returned payload.
const char kIfpsPathHeader[] = "x-ipfs-path";

// Message center notifier id for user notifications
const char kNotifierId[] = "service.ipfs";

// Imported shareable link should have filename parameter.
const char kImportFileNameParam[] = "filename";

// /ipfs/{cid}/path → ipfs://{cid}/path
// query and fragment are taken from source page url
GURL ParseURLFromHeader(const std::string& value) {
Expand Down Expand Up @@ -100,6 +120,67 @@ void SetupIPFSProtocolHandler(const std::string& protocol) {
->StartCheckIsDefault(base::BindOnce(isDefaultCallback, protocol));
}

base::string16 GetImportNotificationTitle(ipfs::ImportState state) {
switch (state) {
case ipfs::IPFS_IMPORT_SUCCESS:
return l10n_util::GetStringUTF16(IDS_IPFS_IMPORT_NOTIFICATION_TITLE);
case ipfs::IPFS_IMPORT_ERROR_REQUEST_EMPTY:
case ipfs::IPFS_IMPORT_ERROR_ADD_FAILED:
return l10n_util::GetStringUTF16(
IDS_IPFS_IMPORT_ERROR_NOTIFICATION_TITLE);
case ipfs::IPFS_IMPORT_ERROR_MKDIR_FAILED:
case ipfs::IPFS_IMPORT_ERROR_MOVE_FAILED:
return l10n_util::GetStringUTF16(
IDS_IPFS_IMPORT_PARTLY_COMPLETED_NOTIFICATION_TITLE);
default:
NOTREACHED();
break;
}
return base::string16();
}

base::string16 GetImportNotificationBody(ipfs::ImportState state,
const GURL& shareable_link) {
switch (state) {
case ipfs::IPFS_IMPORT_SUCCESS:
return l10n_util::GetStringFUTF16(
IDS_IPFS_IMPORT_NOTIFICATION_BODY,
base::UTF8ToUTF16(shareable_link.spec()));
case ipfs::IPFS_IMPORT_ERROR_REQUEST_EMPTY:
return l10n_util::GetStringUTF16(IDS_IPFS_IMPORT_ERROR_NO_REQUEST_BODY);
case ipfs::IPFS_IMPORT_ERROR_ADD_FAILED:
return l10n_util::GetStringUTF16(IDS_IPFS_IMPORT_ERROR_ADD_FAILED_BODY);
case ipfs::IPFS_IMPORT_ERROR_MKDIR_FAILED:
case ipfs::IPFS_IMPORT_ERROR_MOVE_FAILED:
return l10n_util::GetStringUTF16(
IDS_IPFS_IMPORT_PARTLY_COMPLETED_NOTIFICATION_BODY);
default:
NOTREACHED();
break;
}
return base::string16();
}

std::unique_ptr<message_center::Notification> CreateMessageCenterNotification(
const base::string16& title,
const base::string16& body,
const std::string& uuid,
const GURL& link) {
message_center::RichNotificationData notification_data;

// hack to prevent origin from showing in the notification
// since we're using that to get the notification_id to OpenSettings
notification_data.context_message = base::ASCIIToUTF16(" ");
auto notification = std::make_unique<message_center::Notification>(
message_center::NOTIFICATION_TYPE_SIMPLE, uuid, title, body, gfx::Image(),
base::string16(), link,
message_center::NotifierId(message_center::NotifierType::SYSTEM_COMPONENT,
kNotifierId),
notification_data, nullptr);

return notification;
}

} // namespace

namespace ipfs {
Expand All @@ -119,6 +200,8 @@ IPFSTabHelper::IPFSTabHelper(content::WebContents* web_contents)
kIPFSResolveMethod,
base::BindRepeating(&IPFSTabHelper::UpdateDnsLinkButtonState,
base::Unretained(this)));
ipfs_service_ = ipfs::IpfsServiceFactory::GetForContext(
web_contents->GetBrowserContext());
}

// static
Expand Down Expand Up @@ -267,6 +350,67 @@ void IPFSTabHelper::DidFinishNavigation(content::NavigationHandle* handle) {
MaybeShowDNSLinkButton(handle);
}

void IPFSTabHelper::ImportLinkToIpfs(const GURL& url) {
DCHECK(ipfs_service_);
ipfs_service_->ImportLinkToIpfs(
url, base::BindOnce(&IPFSTabHelper::OnImportCompleted,
weak_ptr_factory_.GetWeakPtr()));
}

void IPFSTabHelper::ImportTextToIpfs(const std::string& text) {
DCHECK(ipfs_service_);
ipfs_service_->ImportTextToIpfs(
text, web_contents()->GetURL().host(),
base::BindOnce(&IPFSTabHelper::OnImportCompleted,
weak_ptr_factory_.GetWeakPtr()));
}

GURL IPFSTabHelper::CreateAndCopyShareableLink(const ipfs::ImportedData& data) {
if (data.hash.empty())
return GURL();
std::string ipfs = ipfs::kIPFSScheme + std::string("://") + data.hash;
auto shareable_link =
ipfs::ToPublicGatewayURL(GURL(ipfs), web_contents()->GetBrowserContext());
if (!shareable_link.is_valid())
return GURL();
if (!data.filename.empty())
shareable_link = net::AppendQueryParameter(
shareable_link, kImportFileNameParam, data.filename);
ui::ScopedClipboardWriter(ui::ClipboardBuffer::kCopyPaste)
.WriteText(base::UTF8ToUTF16(shareable_link.spec()));
ipfs_service_->PreWarmShareableLink(shareable_link);
return shareable_link;
}

void IPFSTabHelper::OnImportCompleted(const ipfs::ImportedData& data) {
auto link = CreateAndCopyShareableLink(data);
if (!link.is_valid()) {
// Open node diagnostic page if import failed
link = GURL(kIPFSWebUIURL);
}
PushNotification(GetImportNotificationTitle(data.state),
GetImportNotificationBody(data.state, link), link);
if (data.state == ipfs::IPFS_IMPORT_SUCCESS) {
GURL url = ResolveWebUIFilesLocation(data.directory, chrome::GetChannel());
content::OpenURLParams params(url, content::Referrer(),
WindowOpenDisposition::NEW_FOREGROUND_TAB,
ui::PAGE_TRANSITION_LINK, false);
web_contents()->OpenURL(params);
}
}

void IPFSTabHelper::PushNotification(const base::string16& title,
const base::string16& body,
const GURL& link) {
auto notification =
CreateMessageCenterNotification(title, body, base::GenerateGUID(), link);
Profile* profile =
Profile::FromBrowserContext(web_contents()->GetBrowserContext());
auto* display_service = NotificationDisplayService::GetForProfile(profile);
display_service->Display(NotificationHandler::Type::SEND_TAB_TO_SELF,
*notification, /*metadata=*/nullptr);
}

WEB_CONTENTS_USER_DATA_KEY_IMPL(IPFSTabHelper)

} // namespace ipfs
17 changes: 15 additions & 2 deletions browser/ipfs/ipfs_tab_helper.h
Original file line number Diff line number Diff line change
Expand Up @@ -24,8 +24,9 @@ class WebContents;
class PrefService;

namespace ipfs {

struct ImportedData;
class IPFSHostResolver;
class IpfsService;

// Determines if IPFS should be active for a given top-level navigation.
class IPFSTabHelper : public content::WebContentsObserver,
Expand All @@ -43,17 +44,28 @@ class IPFSTabHelper : public content::WebContentsObserver,
resolver_ = std::move(resolver);
}

void SetIpfsServiceForTesting(ipfs::IpfsService* service) {
ipfs_service_ = service;
}

void ImportLinkToIpfs(const GURL& url);
void ImportTextToIpfs(const std::string& text);

private:
friend class content::WebContentsUserData<IPFSTabHelper>;
explicit IPFSTabHelper(content::WebContents* web_contents);

void PushNotification(const base::string16& title,
const base::string16& body,
const GURL& link);
GURL CreateAndCopyShareableLink(const ipfs::ImportedData& data);
bool IsDNSLinkCheckEnabled() const;
void IPFSLinkResolved(const GURL& ipfs);
void MaybeShowDNSLinkButton(content::NavigationHandle* handle);
void UpdateDnsLinkButtonState();

void MaybeSetupIpfsProtocolHandlers(const GURL& url);

void OnImportCompleted(const ipfs::ImportedData& data);
// content::WebContentsObserver
void DidFinishNavigation(
content::NavigationHandle* navigation_handle) override;
Expand All @@ -65,6 +77,7 @@ class IPFSTabHelper : public content::WebContentsObserver,

PrefService* pref_service_ = nullptr;
PrefChangeRegistrar pref_change_registrar_;
ipfs::IpfsService* ipfs_service_ = nullptr;
GURL ipfs_resolved_url_;
std::unique_ptr<IPFSHostResolver> resolver_;
base::WeakPtrFactory<IPFSTabHelper> weak_ptr_factory_{this};
Expand Down
86 changes: 86 additions & 0 deletions browser/ipfs/ipfs_tab_helper_browsertest.cc
Original file line number Diff line number Diff line change
Expand Up @@ -9,9 +9,12 @@
#include "brave/browser/ipfs/ipfs_host_resolver.h"
#include "brave/browser/ipfs/ipfs_service_factory.h"
#include "brave/components/ipfs/ipfs_constants.h"
#include "brave/components/ipfs/ipfs_service.h"
#include "brave/components/ipfs/ipfs_utils.h"
#include "brave/components/ipfs/pref_names.h"
#include "chrome/browser/profiles/profile.h"
#include "chrome/browser/ui/browser.h"
#include "chrome/common/channel_info.h"
#include "chrome/common/chrome_paths.h"
#include "chrome/test/base/in_process_browser_test.h"
#include "chrome/test/base/ui_test_utils.h"
Expand Down Expand Up @@ -79,6 +82,31 @@ class IpfsTabHelperBrowserTest : public InProcessBrowserTest {
net::EmbeddedTestServer https_server_;
};

class FakeIpfsService : public ipfs::IpfsService {
public:
FakeIpfsService(content::BrowserContext* context,
ipfs::BraveIpfsClientUpdater* updater,
const base::FilePath& user_dir,
version_info::Channel channel)
: ipfs::IpfsService(context, updater, user_dir, channel) {}
~FakeIpfsService() override {}
void ImportTextToIpfs(const std::string& text,
const std::string& host,
ipfs::ImportCompletedCallback callback) override {
if (callback)
std::move(callback).Run(data_);
}
void ImportLinkToIpfs(const GURL& url,
ipfs::ImportCompletedCallback callback) override {
if (callback)
std::move(callback).Run(data_);
}
void SetImportData(const ipfs::ImportedData& data) { data_ = data; }

private:
ipfs::ImportedData data_;
};

class FakeIPFSHostResolver : public ipfs::IPFSHostResolver {
public:
explicit FakeIPFSHostResolver(network::mojom::NetworkContext* context)
Expand Down Expand Up @@ -300,3 +328,61 @@ IN_PROC_BROWSER_TEST_F(IpfsTabHelperBrowserTest, ResolvedIPFSLinkBad) {
std::string result = "";
EXPECT_EQ(helper->GetIPFSResolvedURL().spec(), result);
}

IN_PROC_BROWSER_TEST_F(IpfsTabHelperBrowserTest, ImportTextToIpfs) {
ASSERT_TRUE(
ipfs::IPFSTabHelper::MaybeCreateForWebContents(active_contents()));
ipfs::IPFSTabHelper* helper =
ipfs::IPFSTabHelper::FromWebContents(active_contents());
if (!helper)
return;
base::FilePath user_dir = base::FilePath(FILE_PATH_LITERAL("test"));
auto* context = active_contents()->GetBrowserContext();
std::unique_ptr<FakeIpfsService> ipfs_service(
new FakeIpfsService(context, nullptr, user_dir, chrome::GetChannel()));
ipfs::ImportedData data;
data.hash = "QmYbK4SLaSvTKKAKvNZMwyzYPy4P3GqBPN6CZzbS73FxxU";
data.filename = "google.com";
data.size = 111;
data.directory = "/brave/imports/";
data.state = ipfs::IPFS_IMPORT_SUCCESS;
ipfs_service->SetImportData(data);
helper->SetIpfsServiceForTesting(ipfs_service.get());
EXPECT_EQ(browser()->tab_strip_model()->GetTabCount(), 1);
helper->ImportTextToIpfs("test");
EXPECT_EQ(browser()->tab_strip_model()->GetTabCount(), 2);
auto* web_content = browser()->tab_strip_model()->GetWebContentsAt(1);
ASSERT_TRUE(web_content);
GURL url =
ipfs::ResolveWebUIFilesLocation(data.directory, chrome::GetChannel());
EXPECT_EQ(web_content->GetURL().spec(), url.spec());
}

IN_PROC_BROWSER_TEST_F(IpfsTabHelperBrowserTest, ImportLinkToIpfs) {
ASSERT_TRUE(
ipfs::IPFSTabHelper::MaybeCreateForWebContents(active_contents()));
ipfs::IPFSTabHelper* helper =
ipfs::IPFSTabHelper::FromWebContents(active_contents());
if (!helper)
return;
base::FilePath user_dir = base::FilePath(FILE_PATH_LITERAL("test"));
auto* context = active_contents()->GetBrowserContext();
std::unique_ptr<FakeIpfsService> ipfs_service(
new FakeIpfsService(context, nullptr, user_dir, chrome::GetChannel()));
ipfs::ImportedData data;
data.hash = "QmYbK4SLaSvTKKAKvNZMwyzYPy4P3GqBPN6CZzbS73FxxU";
data.filename = "google.com";
data.size = 111;
data.directory = "/brave/imports/";
data.state = ipfs::IPFS_IMPORT_SUCCESS;
ipfs_service->SetImportData(data);
helper->SetIpfsServiceForTesting(ipfs_service.get());
EXPECT_EQ(browser()->tab_strip_model()->GetTabCount(), 1);
helper->ImportLinkToIpfs(GURL("test.com"));
EXPECT_EQ(browser()->tab_strip_model()->GetTabCount(), 2);
auto* web_content = browser()->tab_strip_model()->GetWebContentsAt(1);
ASSERT_TRUE(web_content);
GURL url =
ipfs::ResolveWebUIFilesLocation(data.directory, chrome::GetChannel());
EXPECT_EQ(web_content->GetURL().spec(), url.spec());
}
Loading