Skip to content

Commit

Permalink
Added import of links and text content to ipfs
Browse files Browse the repository at this point in the history
  • Loading branch information
spylogsster committed Apr 13, 2021
1 parent 3a78d66 commit 1e8418b
Show file tree
Hide file tree
Showing 30 changed files with 1,915 additions and 73 deletions.
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
137 changes: 137 additions & 0 deletions browser/ipfs/ipfs_tab_helper.cc
Original file line number Diff line number Diff line change
Expand Up @@ -12,19 +12,26 @@
#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/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 +40,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 +58,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 +119,66 @@ 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) {
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(), GURL(),
message_center::NotifierId(message_center::NotifierType::SYSTEM_COMPONENT,
kNotifierId),
notification_data, nullptr);

return notification;
}

} // namespace

namespace ipfs {
Expand All @@ -119,6 +198,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 +348,62 @@ 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) {
PushNotification(
GetImportNotificationTitle(data.state),
GetImportNotificationBody(data.state, CreateAndCopyShareableLink(data)));
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) {
auto notification =
CreateMessageCenterNotification(title, body, base::GenerateGUID());
Profile* profile =
Profile::FromBrowserContext(web_contents()->GetBrowserContext());
auto* display_service = NotificationDisplayService::GetForProfile(profile);
display_service->Display(NotificationHandler::Type::ANNOUNCEMENT,
*notification, /*metadata=*/nullptr);
}

WEB_CONTENTS_USER_DATA_KEY_IMPL(IPFSTabHelper)

} // namespace ipfs
16 changes: 14 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,27 @@ 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);
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 +76,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

0 comments on commit 1e8418b

Please sign in to comment.