Skip to content

Commit 1bd1cdb

Browse files
committed
url encoded query parameters
1 parent 3cac6b4 commit 1bd1cdb

8 files changed

Lines changed: 91 additions & 17 deletions

File tree

CMakeLists.txt

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -128,6 +128,7 @@ set(SOURCES
128128
src/internal/pool_manager.cpp
129129
src/internal/logger.cpp
130130
src/internal/http_client.cpp
131+
src/internal/url_utils.cpp
131132
)
132133

133134
set(HEADERS
@@ -152,6 +153,7 @@ set(INTERNAL_HEADERS
152153
src/internal/pool_manager.h
153154
src/internal/logger.h
154155
src/internal/http_client.h
156+
src/internal/url_utils.h
155157
)
156158

157159
# Create library target

src/compute/compute.cpp

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@
55
#include "../internal/http_client.h"
66
#include "../internal/http_client_interface.h"
77
#include "../internal/logger.h"
8+
#include "../internal/url_utils.h"
89

910
#include <nlohmann/json.hpp>
1011

@@ -73,8 +74,11 @@ bool Compute::create_compute(const Cluster& cluster_config) {
7374
Cluster Compute::get_compute(const std::string& cluster_id) {
7475
internal::get_logger()->info("Getting compute cluster details for cluster_id=" + cluster_id);
7576

77+
// URL-encode the cluster_id to prevent injection
78+
std::string encoded_id = internal::url_encode(cluster_id);
79+
7680
// Make API request with cluster_id as query parameter
77-
auto response = pimpl_->http_client_->get("/clusters/get?cluster_id=" + cluster_id);
81+
auto response = pimpl_->http_client_->get("/clusters/get?cluster_id=" + encoded_id);
7882
pimpl_->http_client_->check_response(response, "getCompute");
7983

8084
internal::get_logger()->debug("Compute cluster details response: " + response.body);

src/internal/url_utils.cpp

Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,32 @@
1+
// Copyright (c) 2025 Calvin Min
2+
// SPDX-License-Identifier: MIT
3+
#include "url_utils.h"
4+
5+
#include <stdexcept>
6+
7+
#include <curl/curl.h>
8+
9+
namespace databricks {
10+
namespace internal {
11+
12+
std::string url_encode(const std::string& str) {
13+
CURL* curl = curl_easy_init();
14+
if (!curl) {
15+
throw std::runtime_error("Failed to initialize CURL for URL encoding");
16+
}
17+
18+
char* encoded = curl_easy_escape(curl, str.c_str(), static_cast<int>(str.length()));
19+
if (!encoded) {
20+
curl_easy_cleanup(curl);
21+
throw std::runtime_error("Failed to URL encode string");
22+
}
23+
24+
std::string result(encoded);
25+
curl_free(encoded);
26+
curl_easy_cleanup(curl);
27+
28+
return result;
29+
}
30+
31+
} // namespace internal
32+
} // namespace databricks

src/internal/url_utils.h

Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,31 @@
1+
// Copyright (c) 2025 Calvin Min
2+
// SPDX-License-Identifier: MIT
3+
#pragma once
4+
5+
#include <string>
6+
7+
namespace databricks {
8+
namespace internal {
9+
10+
/**
11+
* @brief URL-encode a string to safely include in query parameters
12+
*
13+
* Encodes special characters in a string to make it safe for use in URLs.
14+
* Uses libcurl's curl_easy_escape function for RFC 3986 compliant encoding.
15+
*
16+
* @param str String to encode
17+
* @return std::string URL-encoded string
18+
*
19+
* @throws std::runtime_error if CURL initialization or encoding fails
20+
*
21+
* @example
22+
* std::string encoded = url_encode("hello world");
23+
* // Returns: "hello%20world"
24+
*
25+
* std::string safe = url_encode("cluster&id=123");
26+
* // Returns: "cluster%26id%3D123"
27+
*/
28+
std::string url_encode(const std::string& str);
29+
30+
} // namespace internal
31+
} // namespace databricks

src/jobs/jobs.cpp

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@
55
#include "../internal/http_client.h"
66
#include "../internal/http_client_interface.h"
77
#include "../internal/logger.h"
8+
#include "../internal/url_utils.h"
89

910
#include <sstream>
1011
#include <stdexcept>
@@ -32,7 +33,7 @@ class Jobs::Impl {
3233
// ============================================================================
3334

3435
namespace {
35-
// Build query string from parameters
36+
// Build query string from parameters with URL encoding
3637
std::string build_query_string(const std::map<std::string, std::string>& params) {
3738
if (params.empty()) {
3839
return "";
@@ -45,7 +46,7 @@ std::string build_query_string(const std::map<std::string, std::string>& params)
4546
if (!first) {
4647
oss << "&";
4748
}
48-
oss << key << "=" << value;
49+
oss << key << "=" << internal::url_encode(value);
4950
first = false;
5051
}
5152
return oss.str();

src/secrets/secrets.cpp

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@
55
#include "../internal/http_client.h"
66
#include "../internal/http_client_interface.h"
77
#include "../internal/logger.h"
8+
#include "../internal/url_utils.h"
89

910
#include <unordered_map>
1011

@@ -144,8 +145,8 @@ void Secrets::delete_secret(const std::string& scope, const std::string& key) {
144145
std::vector<Secret> Secrets::list_secrets(const std::string& scope) {
145146
internal::get_logger()->info("Listing secrets in scope: " + scope);
146147

147-
// Make GET request with scope as query parameter
148-
auto response = pimpl_->http_client_->get("/secrets/list?scope=" + scope);
148+
// Make GET request with scope as query parameter (URL-encoded)
149+
auto response = pimpl_->http_client_->get("/secrets/list?scope=" + internal::url_encode(scope));
149150
pimpl_->http_client_->check_response(response, "listSecrets");
150151

151152
internal::get_logger()->debug("Successfully retrieved secrets list");

src/unity_catalog/unity_catalog.cpp

Lines changed: 8 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@
55
#include "../internal/http_client.h"
66
#include "../internal/http_client_interface.h"
77
#include "../internal/logger.h"
8+
#include "../internal/url_utils.h"
89

910
#include <nlohmann/json.hpp>
1011

@@ -51,7 +52,7 @@ std::vector<CatalogInfo> UnityCatalog::list_catalogs() {
5152
CatalogInfo UnityCatalog::get_catalog(const std::string& catalog_name) {
5253
internal::get_logger()->info("Getting catalog details for catalog=" + catalog_name);
5354

54-
auto response = pimpl_->http_client_->get("/unity-catalog/catalogs/" + catalog_name);
55+
auto response = pimpl_->http_client_->get("/unity-catalog/catalogs/" + internal::url_encode(catalog_name));
5556
pimpl_->http_client_->check_response(response, "getCatalog");
5657

5758
internal::get_logger()->debug("Catalog details response: " + response.body);
@@ -109,7 +110,7 @@ bool UnityCatalog::delete_catalog(const std::string& catalog_name, bool force) {
109110
std::vector<SchemaInfo> UnityCatalog::list_schemas(const std::string& catalog_name) {
110111
internal::get_logger()->info("Listing schemas in catalog: " + catalog_name);
111112

112-
auto response = pimpl_->http_client_->get("/unity-catalog/schemas?catalog_name=" + catalog_name);
113+
auto response = pimpl_->http_client_->get("/unity-catalog/schemas?catalog_name=" + internal::url_encode(catalog_name));
113114
pimpl_->http_client_->check_response(response, "listSchemas");
114115

115116
internal::get_logger()->debug("Schemas list response: " + response.body);
@@ -119,7 +120,7 @@ std::vector<SchemaInfo> UnityCatalog::list_schemas(const std::string& catalog_na
119120
SchemaInfo UnityCatalog::get_schema(const std::string& full_name) {
120121
internal::get_logger()->info("Getting schema details for: " + full_name);
121122

122-
auto response = pimpl_->http_client_->get("/unity-catalog/schemas/" + full_name);
123+
auto response = pimpl_->http_client_->get("/unity-catalog/schemas/" + internal::url_encode(full_name));
123124
pimpl_->http_client_->check_response(response, "getSchema");
124125

125126
internal::get_logger()->debug("Schema details response: " + response.body);
@@ -169,8 +170,9 @@ bool UnityCatalog::delete_schema(const std::string& full_name) {
169170
std::vector<TableInfo> UnityCatalog::list_tables(const std::string& catalog_name, const std::string& schema_name) {
170171
internal::get_logger()->info("Listing tables in " + catalog_name + "." + schema_name);
171172

172-
// Create Endpoint with Catalog and Schema name
173-
std::string endpoint = "/unity-catalog/tables?catalog_name=" + catalog_name + "&schema_name=" + schema_name;
173+
// Create Endpoint with Catalog and Schema name (URL-encoded)
174+
std::string endpoint = "/unity-catalog/tables?catalog_name=" + internal::url_encode(catalog_name) +
175+
"&schema_name=" + internal::url_encode(schema_name);
174176
auto response = pimpl_->http_client_->get(endpoint);
175177
pimpl_->http_client_->check_response(response, "listTables");
176178

@@ -181,7 +183,7 @@ std::vector<TableInfo> UnityCatalog::list_tables(const std::string& catalog_name
181183
TableInfo UnityCatalog::get_table(const std::string& full_name) {
182184
internal::get_logger()->info("Getting table details for: " + full_name);
183185

184-
auto response = pimpl_->http_client_->get("/unity-catalog/tables/" + full_name);
186+
auto response = pimpl_->http_client_->get("/unity-catalog/tables/" + internal::url_encode(full_name));
185187
pimpl_->http_client_->check_response(response, "getTable");
186188

187189
internal::get_logger()->debug("Table details response: " + response.body);

src/workspace/workspace.cpp

Lines changed: 7 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@
55
#include "../internal/http_client.h"
66
#include "../internal/http_client_interface.h"
77
#include "../internal/logger.h"
8+
#include "../internal/url_utils.h"
89

910
#include <sstream>
1011
#include <stdexcept>
@@ -52,8 +53,8 @@ std::vector<ObjectInfo> Workspace::list(const std::string& path,
5253
throw std::invalid_argument("Path cannot be empty");
5354
}
5455

55-
// Build query parameters
56-
std::string query_params = "?path=" + path;
56+
// Build query parameters with URL encoding
57+
std::string query_params = "?path=" + internal::url_encode(path);
5758
if (notebooks_modified_after.has_value()) {
5859
query_params += "&notebooks_modified_after=" + std::to_string(notebooks_modified_after.value());
5960
}
@@ -94,8 +95,8 @@ ObjectInfo Workspace::get_status(const std::string& path) {
9495
throw std::invalid_argument("Path cannot be empty");
9596
}
9697

97-
// Build query parameters
98-
std::string query_params = "?path=" + path;
98+
// Build query parameters with URL encoding
99+
std::string query_params = "?path=" + internal::url_encode(path);
99100

100101
auto response = pimpl_->http_client_->get("/workspace/get-status" + query_params);
101102
pimpl_->http_client_->check_response(response, "getStatus");
@@ -112,8 +113,8 @@ ExportResponse Workspace::export_file(const std::string& path, ExportFormat form
112113
throw std::invalid_argument("Path cannot be empty");
113114
}
114115

115-
// Build query parameters
116-
std::string query_params = "?path=" + path;
116+
// Build query parameters with URL encoding
117+
std::string query_params = "?path=" + internal::url_encode(path);
117118
query_params += "&format=" + export_format_to_string(format);
118119

119120
internal::get_logger()->debug("Export request for path=" + path + ", format=" + export_format_to_string(format));

0 commit comments

Comments
 (0)