Skip to content
Open
Show file tree
Hide file tree
Changes from 4 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
6 changes: 6 additions & 0 deletions release_build_files/readme.md
Original file line number Diff line number Diff line change
Expand Up @@ -613,6 +613,12 @@ workflow use only during the development of your app, not for publicly shipping
code.

## Release Notes
### 13.3.0
- Changes
- Storage: Add support for Firebase Storage emulator via `UseEmulator`.
The `UseEmulator` method should be called before invoking any other
methods on a new instance of Storage. Default port is 9199.

### 13.2.0
- Changes
- General (Android): Update to Firebase Android BoM version 34.4.0.
Expand Down
21 changes: 20 additions & 1 deletion storage/src/android/storage_android.cc
Original file line number Diff line number Diff line change
Expand Up @@ -62,7 +62,9 @@ namespace internal {
"(Ljava/lang/String;)" \
"Lcom/google/firebase/storage/StorageReference;"), \
X(GetApp, "getApp", \
"()Lcom/google/firebase/FirebaseApp;")
"()Lcom/google/firebase/FirebaseApp;"), \
X(UseEmulator, "useEmulator", \
"(Ljava/lang/String;I)V")
// clang-format on

METHOD_LOOKUP_DECLARATION(firebase_storage, FIREBASE_STORAGE_METHODS)
Expand Down Expand Up @@ -471,6 +473,23 @@ void StorageInternal::set_max_operation_retry_time(
millis);
}

void StorageInternal::UseEmulator(const char* host, int port) {
JNIEnv* env = app_->GetJNIEnv();
FIREBASE_ASSERT_MESSAGE_RETURN_VOID((host != nullptr && host[0] != '\0'),
"Emulator host cannot be null or empty.")
FIREBASE_ASSERT_MESSAGE_RETURN_VOID(
(port > 0), "Emulator port must be a positive number.")

jobject host_string = env->NewStringUTF(host);
jint port_num = static_cast<jint>(port);

env->CallVoidMethod(
obj_, firebase_storage::GetMethodId(firebase_storage::kUseEmulator),
host_string, port_num);
env->DeleteLocalRef(host_string);
util::CheckAndClearJniExceptions(env);
}

} // namespace internal
} // namespace storage
} // namespace firebase
4 changes: 4 additions & 0 deletions storage/src/android/storage_android.h
Original file line number Diff line number Diff line change
Expand Up @@ -92,6 +92,10 @@ class StorageInternal {
// if a failure occurs.
void set_max_operation_retry_time(double max_transfer_retry_seconds);

// Configures the Storage SDK to use an emulated backend instead of call the
// default remote backend
void UseEmulator(const char* host, int port);

// Convert an error code obtained from a Java StorageException into a C++
// Error enum.
Error ErrorFromJavaErrorCode(jint java_error_code) const;
Expand Down
4 changes: 4 additions & 0 deletions storage/src/common/storage.cc
Original file line number Diff line number Diff line change
Expand Up @@ -226,5 +226,9 @@ void Storage::set_max_operation_retry_time(double max_transfer_retry_seconds) {
return internal_->set_max_operation_retry_time(max_transfer_retry_seconds);
}

void Storage::UseEmulator(const char* host, int port) {
if (internal_) internal_->UseEmulator(host, port);
}

} // namespace storage
} // namespace firebase
3 changes: 2 additions & 1 deletion storage/src/desktop/metadata_desktop.cc
Original file line number Diff line number Diff line change
Expand Up @@ -133,7 +133,8 @@ const char* MetadataInternal::download_url() const {

std::string MetadataInternal::GetPathFromToken(const std::string& token) const {
std::string http_url =
StoragePath("gs://" + bucket_ + "/" + path_).AsHttpUrl();
StoragePath(storage_internal_, "gs://" + bucket_ + "/" + path_)
.AsHttpUrl();
if (!token.empty()) http_url += "&token=" + token;
return http_url;
}
Expand Down
35 changes: 29 additions & 6 deletions storage/src/desktop/storage_desktop.cc
Original file line number Diff line number Diff line change
Expand Up @@ -35,10 +35,10 @@ StorageInternal::StorageInternal(App* app, const char* url) {

if (url) {
url_ = url;
root_ = StoragePath(url_);
root_ = StoragePath(this, url_);
} else {
const char* bucket = app->options().storage_bucket();
root_ = StoragePath(bucket ? std::string(kGsScheme) + bucket : "");
root_ = StoragePath(this, bucket ? std::string(kGsScheme) + bucket : "");
}

// LINT.IfChange
Expand Down Expand Up @@ -76,20 +76,22 @@ StorageInternal::~StorageInternal() {
}

// Get a StorageReference to the root of the database.
StorageReferenceInternal* StorageInternal::GetReference() const {
StorageReferenceInternal* StorageInternal::GetReference() {
configured_ = true;
return new StorageReferenceInternal(url_, const_cast<StorageInternal*>(this));
}

// Get a StorageReference for the specified path.
StorageReferenceInternal* StorageInternal::GetReference(
const char* path) const {
StorageReferenceInternal* StorageInternal::GetReference(const char* path) {
configured_ = true;
return new StorageReferenceInternal(root_.GetChild(path),
const_cast<StorageInternal*>(this));
}

// Get a StorageReference for the provided URL.
StorageReferenceInternal* StorageInternal::GetReferenceFromUrl(
const char* url) const {
const char* url) {
configured_ = true;
return new StorageReferenceInternal(url, const_cast<StorageInternal*>(this));
}

Expand Down Expand Up @@ -134,6 +136,27 @@ void StorageInternal::CleanupCompletedOperations() {
}
}

void StorageInternal::UseEmulator(const char* host, int port) {
if (host == nullptr || host[0] == '\0') {
throw std::invalid_argument("Emulator host cannot be null or empty.");
}
host_ = host;

if (port <= 0) {
throw std::invalid_argument("Emulator port must be a positive number.");
}
port_ = port;

if (configured_) {
throw std::logic_error(
"Cannot connect to emulator after Storage SDK initialization. "
"Call use_emulator(host, port) before creating a Storage "
"reference or trying to load data.");
}

scheme_ = "http";
}

} // namespace internal
} // namespace storage
} // namespace firebase
23 changes: 20 additions & 3 deletions storage/src/desktop/storage_desktop.h
Original file line number Diff line number Diff line change
Expand Up @@ -43,13 +43,13 @@ class StorageInternal {
std::string url() { return url_; }

// Get a StorageReference to the root of the database.
StorageReferenceInternal* GetReference() const;
StorageReferenceInternal* GetReference();

// Get a StorageReference for the specified path.
StorageReferenceInternal* GetReference(const char* path) const;
StorageReferenceInternal* GetReference(const char* path);

// Get a StorageReference for the provided URL.
StorageReferenceInternal* GetReferenceFromUrl(const char* url) const;
StorageReferenceInternal* GetReferenceFromUrl(const char* url);

// Returns the maximum time (in seconds) to retry a download if a failure
// occurs.
Expand Down Expand Up @@ -99,6 +99,19 @@ class StorageInternal {
// Remove an operation from the list of outstanding operations.
void RemoveOperation(RestOperation* operation);

// Configures the Storage SDK to use an emulated backend instead of call the
// default remote backend
void UseEmulator(const char* host, int port);

// Returns the Host for the storage backend
std::string get_host() { return host_; }

// Returns the Port for the storage backend
int get_port() { return port_; }

// Returns the url scheme currenly in use for the storage backend
std::string get_scheme() { return scheme_; }

private:
// Clean up completed operations.
void CleanupCompletedOperations();
Expand All @@ -119,6 +132,10 @@ class StorageInternal {
std::string user_agent_;
Mutex operations_mutex_;
std::vector<RestOperation*> operations_;
std::string host_ = "firebasestorage.googleapis.com";
std::string scheme_ = "https";
int port_ = 443;
bool configured_ = false;
};

} // namespace internal
Expand Down
24 changes: 17 additions & 7 deletions storage/src/desktop/storage_path.cc
Original file line number Diff line number Diff line change
Expand Up @@ -16,10 +16,12 @@

#include <string.h>

#include <iostream>
#include <string>

#include "app/rest/util.h"
#include "app/src/include/firebase/internal/common.h"
#include "storage/src/desktop/storage_desktop.h"

namespace firebase {
namespace storage {
Expand All @@ -38,8 +40,10 @@ const char kBucketStartString[] = "firebasestorage.googleapis.com/v0/b/";
const size_t kBucketStartStringLength = FIREBASE_STRLEN(kBucketStartString);
const char kBucketEndString[] = "/o/";
const size_t kBucketEndStringLength = FIREBASE_STRLEN(kBucketEndString);
const char kBucketIdentifierString[] = "/v0/b/";

StoragePath::StoragePath(const std::string& path) {
StoragePath::StoragePath(StorageInternal* storage, const std::string& path) {
storage_internal_ = storage;
bucket_ = "";
path_ = Path("");
if (path.compare(0, kGsSchemeLength, kGsScheme) == 0) {
Expand All @@ -56,8 +60,9 @@ StoragePath::StoragePath(const std::string& path) {

// Constructs a storage path, based on raw strings for the bucket, path, and
// object.
StoragePath::StoragePath(const std::string& bucket, const std::string& path,
const std::string& object) {
StoragePath::StoragePath(StorageInternal* storage, const std::string& bucket,
const std::string& path, const std::string& object) {
storage_internal_ = storage;
bucket_ = bucket;
path_ = Path(path).GetChild(object);
}
Expand Down Expand Up @@ -97,15 +102,20 @@ void StoragePath::ConstructFromHttpUrl(const std::string& url, int path_start) {
std::string StoragePath::AsHttpUrl() const {
static const char* kUrlEnd = "?alt=media";
// Construct the URL. Final format is:
// https://[projectname].googleapis.com/v0/b/[bucket]/o/[path and/or object]
// http[s]://[host]:[port]/v0/b/[bucket]/o/[path and/or object]
return AsHttpMetadataUrl() + kUrlEnd;
}

std::string StoragePath::AsHttpMetadataUrl() const {
// Construct the URL. Final format is:
// https://[projectname].googleapis.com/v0/b/[bucket]/o/[path and/or object]
std::string result = kHttpsScheme;
result += kBucketStartString;
// [scheme]://[host]:[port]/v0/b/[bucket]/o/[path and/or object]

std::string result = storage_internal_->get_scheme();
result += "://";
result += storage_internal_->get_host();
result += ":";
result += std::to_string(storage_internal_->get_port());
result += kBucketIdentifierString;
result += bucket_;
result += kBucketEndString;
result += rest::util::EncodeUrl(path_.str());
Expand Down
18 changes: 11 additions & 7 deletions storage/src/desktop/storage_path.h
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,8 @@ namespace internal {

extern const char kGsScheme[];

class StorageInternal;

// Class for managing paths for firebase storage.
// Storage paths are made up of a bucket, a path,
// and (optionally) an object, located at that path.
Expand All @@ -35,12 +37,12 @@ class StoragePath {

// Constructs a storage path, based on an input URL. The URL can either be
// an HTTP[s] link, or a gs URI.
explicit StoragePath(const std::string& path);
explicit StoragePath(StorageInternal* storage, const std::string& path);

// Constructs a storage path, based on raw strings for the bucket, path, and
// object.
StoragePath(const std::string& bucket, const std::string& path,
const std::string& object = "");
StoragePath(StorageInternal* storage, const std::string& bucket,
const std::string& path, const std::string& object = "");

// The bucket portion of this path.
// In the path: MyBucket/folder/object, it would return "MyBucket".
Expand All @@ -60,14 +62,14 @@ class StoragePath {
// in a path where bucket is "bucket", local_path is "path/otherchild/" and
// object is an empty string.
StoragePath GetChild(const std::string& path) const {
return StoragePath(bucket_, path_.GetChild(path));
return StoragePath(storage_internal_, bucket_, path_.GetChild(path));
}

// Returns the location one folder up from the current location. If the
// path is at already at the root level, this returns the path unchanged.
// The Object in the result is always set to empty.
StoragePath GetParent() const {
return StoragePath(bucket_, path_.GetParent());
return StoragePath(storage_internal_, bucket_, path_.GetParent());
}

// Returns the path as a HTTP URL to the asset.
Expand All @@ -82,14 +84,16 @@ class StoragePath {
private:
static const char* const kSeparator;

StoragePath(const std::string& bucket, const Path& path)
: bucket_(bucket), path_(path) {}
StoragePath(StorageInternal* storage, const std::string& bucket,
const Path& path)
: storage_internal_(storage), bucket_(bucket), path_(path) {}

void ConstructFromGsUri(const std::string& uri, int path_start);
void ConstructFromHttpUrl(const std::string& url, int path_start);

std::string bucket_;
Path path_;
StorageInternal* storage_internal_;
};

} // namespace internal
Expand Down
2 changes: 1 addition & 1 deletion storage/src/desktop/storage_reference_desktop.cc
Original file line number Diff line number Diff line change
Expand Up @@ -48,7 +48,7 @@ namespace internal {

StorageReferenceInternal::StorageReferenceInternal(
const std::string& storageUri, StorageInternal* storage)
: storage_(storage), storageUri_(storageUri) {
: storage_(storage), storageUri_(storage, storageUri) {
storage_->future_manager().AllocFutureApi(this, kStorageReferenceFnCount);
}

Expand Down
11 changes: 11 additions & 0 deletions storage/src/include/firebase/storage.h
Original file line number Diff line number Diff line change
Expand Up @@ -139,6 +139,17 @@ class Storage {
/// download if a failure occurs. Defaults to 120 seconds (2 minutes).
void set_max_operation_retry_time(double max_transfer_retry_seconds);

/// @brief Configures the Storage SDK to use an emulated backend instead of
/// the default remote backend. This method should be called before invoking
/// any other methods on a new instance of Storage
void UseEmulator(const std::string& host, int port) {
UseEmulator(host.c_str(), port);
}
/// @brief Configures the Storage SDK to use an emulated backend instead of
/// the default remote backend. This method should be called before invoking
/// any other methods on a new instance of Storage
void UseEmulator(const char* host, int port);

private:
/// @cond FIREBASE_APP_INTERNAL
friend class Metadata;
Expand Down
4 changes: 4 additions & 0 deletions storage/src/ios/storage_ios.h
Original file line number Diff line number Diff line change
Expand Up @@ -92,6 +92,10 @@ class StorageInternal {
// if a failure occurs.
void set_max_operation_retry_time(double max_transfer_retry_seconds);

// Configures the Storage SDK to use an emulated backend instead of call the
// default remote backend
void UseEmulator(const char* _Nullable host, int port);

FutureManager& future_manager() { return future_manager_; }

// Whether this object was successfully initialized by the constructor.
Expand Down
12 changes: 12 additions & 0 deletions storage/src/ios/storage_ios.mm
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@
#include "storage/src/ios/storage_reference_ios.h"

#import "FirebaseStorage-Swift.h"
#include <Foundation/Foundation.h>

namespace firebase {
namespace storage {
Expand Down Expand Up @@ -97,6 +98,17 @@
impl().maxOperationRetryTime = max_transfer_retry_seconds;
}

void StorageInternal::UseEmulator(const char* host, int port) {

NSCAssert(host && host[0] != '\0', @"Emulator host cannot be null or empty.");

NSCAssert(port > 0, @"Emulator port must be a positive number.");
Comment on lines +103 to +105

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

high

NSCAssert is a debug-only assertion and is stripped from release builds. This leaves the host and port parameters unvalidated in release builds, which could lead to crashes or other undefined behavior if invalid values are passed. It's better to use runtime checks that are present in all build configurations to ensure robustness.

  if (!(host && host[0] != '\0')) {
    LogError("Emulator host cannot be null or empty.");
    return;
  }

  if (!(port > 0)) {
    LogError("Emulator port must be a positive number.");
    return;
  }


NSString *hostString = [NSString stringWithUTF8String:host];

[impl() useEmulatorWithHost:hostString port:port];
}

// Whether this object was successfully initialized by the constructor.
bool StorageInternal::initialized() const { return impl() != nil; }

Expand Down
Loading