@bniladridas/cursor 0.1.7
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.clang-tidy +28 -0
- package/.dockerignore +56 -0
- package/.env.example +29 -0
- package/.github/CODEOWNERS +2 -0
- package/.github/ISSUE_TEMPLATE/blank.md +27 -0
- package/.github/ISSUE_TEMPLATE/bug_report.md +33 -0
- package/.github/ISSUE_TEMPLATE/feature_request.md +24 -0
- package/.github/SECURITY.md +24 -0
- package/.github/codeql/codeql-config.yml +8 -0
- package/.github/dependabot.yml +14 -0
- package/.github/labeler.yml +50 -0
- package/.github/packaging/brand-cursor.png +0 -0
- package/.github/packaging/database/init.sql +48 -0
- package/.github/packaging/docker/Dockerfile +111 -0
- package/.github/packaging/docker/docker-compose.yml +56 -0
- package/.github/packaging/scripts/preflight.sh +413 -0
- package/.github/packaging/scripts/prepare-release.sh +141 -0
- package/.github/packaging/scripts/release.sh +22 -0
- package/.github/packaging/scripts/setup-git-hooks.sh +73 -0
- package/.github/pull_request_template.md +31 -0
- package/.github/signed.json +9 -0
- package/.github/workflows/README.md +23 -0
- package/.github/workflows/ci.yml +181 -0
- package/.github/workflows/cla.yml +33 -0
- package/.github/workflows/formula-sha.yml +63 -0
- package/.github/workflows/issue-response.yml +44 -0
- package/.github/workflows/labeler.yml +42 -0
- package/.github/workflows/pr-body.yml +49 -0
- package/.github/workflows/release.yml +176 -0
- package/.github/workflows/security.yml +94 -0
- package/.github/workflows/stale.yml +38 -0
- package/AGENTS.md +49 -0
- package/CHANGELOG.md +3 -0
- package/CMakeLists.txt +646 -0
- package/Formula/cursor.rb +46 -0
- package/LICENSE +201 -0
- package/Makefile +28 -0
- package/README.md +46 -0
- package/cli.js +16 -0
- package/include/agent.h +86 -0
- package/include/agent_mode.h +17 -0
- package/include/memory_manager.h +102 -0
- package/include/services/ai_service.h +31 -0
- package/include/services/auth_service.h +87 -0
- package/include/services/checkpoint_service.h +69 -0
- package/include/services/codebase_service.h +38 -0
- package/include/services/command_service.h +23 -0
- package/include/services/context_service.h +74 -0
- package/include/services/database_service.h +56 -0
- package/include/services/error_service.h +106 -0
- package/include/services/file_service.h +51 -0
- package/include/services/git_service.h +29 -0
- package/include/services/github_service.h +85 -0
- package/include/services/mcp_service.h +85 -0
- package/include/services/multi_file_service.h +93 -0
- package/include/services/sandbox_service.h +96 -0
- package/include/services/theme_service.h +67 -0
- package/include/services/web_service.h +52 -0
- package/include/utils/config.h +68 -0
- package/include/utils/memory_utils.h +79 -0
- package/include/utils/platform.h +56 -0
- package/include/utils/ui.h +43 -0
- package/include/utils/validation.h +63 -0
- package/include/utils/version.h.in +17 -0
- package/install.js +49 -0
- package/package.json +16 -0
- package/release/checksums.txt +3 -0
- package/release/cursor-linux/cursor_v0.1.7_linux_amd64.tar.gz +0 -0
- package/release/cursor-macos/cursor_v0.1.7_darwin_arm64.tar.gz +0 -0
- package/release/cursor-windows/cursor__windows_amd64.zip +0 -0
- package/src/agent.cpp +2026 -0
- package/src/main.cpp +97 -0
- package/src/memory_manager.cpp +814 -0
- package/src/services/ai_service.cpp +366 -0
- package/src/services/auth_service.cpp +779 -0
- package/src/services/checkpoint_service.cpp +465 -0
- package/src/services/codebase_service.cpp +233 -0
- package/src/services/command_service.cpp +82 -0
- package/src/services/context_service.cpp +348 -0
- package/src/services/database_service.cpp +148 -0
- package/src/services/error_service.cpp +438 -0
- package/src/services/file_service.cpp +349 -0
- package/src/services/git_service.cpp +148 -0
- package/src/services/github_service.cpp +435 -0
- package/src/services/mcp_service.cpp +481 -0
- package/src/services/multi_file_service.cpp +591 -0
- package/src/services/sandbox_service.cpp +678 -0
- package/src/services/theme_service.cpp +429 -0
- package/src/services/web_service.cpp +532 -0
- package/src/utils/config.cpp +77 -0
- package/src/utils/memory_utils.cpp +93 -0
- package/src/utils/ui.cpp +307 -0
- package/src/utils/validation.cpp +306 -0
- package/src/utils/version.cpp +175 -0
- package/tests/e2e/docker-compose.yml +195 -0
- package/tests/e2e/run_e2e_tests.sh +70 -0
- package/tests/e2e/run_tests_in_docker.sh +115 -0
- package/tests/main_test.cpp +16 -0
- package/tests/mocks/mock_ollama.py +98 -0
- package/tests/mocks/start_nginx.sh +64 -0
|
@@ -0,0 +1,532 @@
|
|
|
1
|
+
#include "services/web_service.h"
|
|
2
|
+
#include "utils/config.h"
|
|
3
|
+
#include <algorithm>
|
|
4
|
+
#include <cpr/cpr.h>
|
|
5
|
+
#include <curl/curl.h>
|
|
6
|
+
#include <iostream>
|
|
7
|
+
#include <nlohmann/json.hpp>
|
|
8
|
+
#include <sstream>
|
|
9
|
+
#include <utility>
|
|
10
|
+
#include <vector>
|
|
11
|
+
|
|
12
|
+
struct CurlHandle {
|
|
13
|
+
CURL *handle;
|
|
14
|
+
CurlHandle() : handle(curl_easy_init()) {}
|
|
15
|
+
~CurlHandle() {
|
|
16
|
+
if (handle)
|
|
17
|
+
curl_easy_cleanup(handle);
|
|
18
|
+
}
|
|
19
|
+
CurlHandle(const CurlHandle &) = delete;
|
|
20
|
+
CurlHandle &operator=(const CurlHandle &) = delete;
|
|
21
|
+
CURL *get() const { return handle; }
|
|
22
|
+
explicit operator bool() const { return handle != nullptr; }
|
|
23
|
+
};
|
|
24
|
+
|
|
25
|
+
const long kDefaultTimeoutSeconds = 30;
|
|
26
|
+
const size_t kMaxContentLength = 8000;
|
|
27
|
+
|
|
28
|
+
// Callback function for writing response data
|
|
29
|
+
static size_t WriteCallback(void *contents, size_t size, size_t nmemb,
|
|
30
|
+
void *userp) {
|
|
31
|
+
size_t real_size = size * nmemb;
|
|
32
|
+
std::string *response = static_cast<std::string *>(userp);
|
|
33
|
+
// Reserve space to avoid frequent reallocations
|
|
34
|
+
if (response->capacity() < response->size() + real_size) {
|
|
35
|
+
response->reserve(response->size() + real_size + 4096);
|
|
36
|
+
}
|
|
37
|
+
response->append(static_cast<char *>(contents), real_size);
|
|
38
|
+
return real_size;
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
// Callback function for writing header data
|
|
42
|
+
static size_t HeaderCallback(char *buffer, size_t size, size_t nitems,
|
|
43
|
+
void *userdata) {
|
|
44
|
+
size_t real_size = size * nitems;
|
|
45
|
+
std::string *headers = static_cast<std::string *>(userdata);
|
|
46
|
+
headers->append(buffer, real_size);
|
|
47
|
+
return real_size;
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
namespace Services {
|
|
51
|
+
std::string WebService::get_api_key() {
|
|
52
|
+
return Utils::Config::get_env_var("SERPAPI_KEY");
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
bool WebService::is_available() { return !get_api_key().empty(); }
|
|
56
|
+
|
|
57
|
+
std::string WebService::search(const std::string &query) {
|
|
58
|
+
|
|
59
|
+
try {
|
|
60
|
+
std::string api_key = get_api_key();
|
|
61
|
+
if (api_key.empty()) {
|
|
62
|
+
return "Error: SERPAPI_KEY is missing.";
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
// URL encode the query
|
|
66
|
+
std::string encoded_query;
|
|
67
|
+
{
|
|
68
|
+
CurlHandle curl;
|
|
69
|
+
if (curl) {
|
|
70
|
+
char *output = curl_easy_escape(curl.get(), query.c_str(),
|
|
71
|
+
static_cast<int>(query.length()));
|
|
72
|
+
if (output) {
|
|
73
|
+
encoded_query = output;
|
|
74
|
+
curl_free(output);
|
|
75
|
+
}
|
|
76
|
+
} else {
|
|
77
|
+
encoded_query = query;
|
|
78
|
+
}
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
std::string search_url = "https://api.duckduckgo.com/?q=" + encoded_query +
|
|
82
|
+
"&api_key=" + api_key;
|
|
83
|
+
|
|
84
|
+
std::string response_str;
|
|
85
|
+
std::string response_headers;
|
|
86
|
+
|
|
87
|
+
{
|
|
88
|
+
CurlHandle search_curl;
|
|
89
|
+
if (search_curl) {
|
|
90
|
+
curl_easy_setopt(search_curl.get(), CURLOPT_URL, search_url.c_str());
|
|
91
|
+
curl_easy_setopt(search_curl.get(), CURLOPT_WRITEFUNCTION, WriteCallback);
|
|
92
|
+
curl_easy_setopt(search_curl.get(), CURLOPT_WRITEDATA, &response_str);
|
|
93
|
+
curl_easy_setopt(search_curl.get(), CURLOPT_HEADERFUNCTION, HeaderCallback);
|
|
94
|
+
curl_easy_setopt(search_curl.get(), CURLOPT_HEADERDATA, &response_headers);
|
|
95
|
+
curl_easy_setopt(search_curl.get(), CURLOPT_TIMEOUT, 5L);
|
|
96
|
+
curl_easy_setopt(search_curl.get(), CURLOPT_USERAGENT, "Cursor-Agent/1.0");
|
|
97
|
+
|
|
98
|
+
CURLcode res = curl_easy_perform(search_curl.get());
|
|
99
|
+
if (res != CURLE_OK) {
|
|
100
|
+
std::cerr << "curl_easy_perform() failed: " << curl_easy_strerror(res)
|
|
101
|
+
<< std::endl;
|
|
102
|
+
return "Error: Failed to perform web search";
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
long http_code = 0;
|
|
106
|
+
curl_easy_getinfo(search_curl.get(), CURLINFO_RESPONSE_CODE, &http_code);
|
|
107
|
+
if (http_code != 200) {
|
|
108
|
+
std::cerr << "HTTP request failed with code: " << http_code
|
|
109
|
+
<< std::endl;
|
|
110
|
+
return "Error: Failed to perform web search (HTTP " +
|
|
111
|
+
std::to_string(http_code) + ")";
|
|
112
|
+
}
|
|
113
|
+
} else {
|
|
114
|
+
return "Error: Failed to initialize cURL";
|
|
115
|
+
}
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
// Parse JSON response
|
|
119
|
+
try {
|
|
120
|
+
auto json = nlohmann::json::parse(response_str);
|
|
121
|
+
std::string result;
|
|
122
|
+
|
|
123
|
+
// Extract and format the search results
|
|
124
|
+
if (json.contains("AbstractText") && !json["AbstractText"].is_null()) {
|
|
125
|
+
result = json["AbstractText"].get<std::string>();
|
|
126
|
+
if (!result.empty()) {
|
|
127
|
+
return "Abstract: " + result;
|
|
128
|
+
}
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
if (json.contains("RelatedTopics") && json["RelatedTopics"].is_array()) {
|
|
132
|
+
for (const auto &topic : json["RelatedTopics"]) {
|
|
133
|
+
if (topic.contains("Text")) {
|
|
134
|
+
result += "• " + topic["Text"].get<std::string>() + "\n";
|
|
135
|
+
}
|
|
136
|
+
}
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
return result.empty() ? "No results found." : result;
|
|
140
|
+
} catch (const std::exception &e) {
|
|
141
|
+
return "Error parsing search results: " + std::string(e.what());
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
} catch (const std::exception &e) {
|
|
145
|
+
return std::string("Error performing search: ") + e.what();
|
|
146
|
+
}
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
bool WebService::is_valid_url(const std::string &url) {
|
|
150
|
+
// Basic URL validation
|
|
151
|
+
if (url.empty())
|
|
152
|
+
return false;
|
|
153
|
+
|
|
154
|
+
// Check for common protocols
|
|
155
|
+
if (url.find("http://") == 0 || url.find("https://") == 0) {
|
|
156
|
+
// Must have at least a domain after protocol
|
|
157
|
+
size_t domain_start = url.find("://") + 3;
|
|
158
|
+
if (domain_start < url.length() &&
|
|
159
|
+
url.find('.', domain_start) != std::string::npos) {
|
|
160
|
+
return true;
|
|
161
|
+
}
|
|
162
|
+
}
|
|
163
|
+
return false;
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
std::string WebService::extract_text_content(const std::string &html) {
|
|
167
|
+
std::string text = html;
|
|
168
|
+
|
|
169
|
+
// Remove script and style tags and their content
|
|
170
|
+
size_t pos = 0;
|
|
171
|
+
while ((pos = text.find("<script", pos)) != std::string::npos) {
|
|
172
|
+
size_t end = text.find("</script>", pos);
|
|
173
|
+
if (end != std::string::npos) {
|
|
174
|
+
text.erase(pos, end - pos + 9);
|
|
175
|
+
} else {
|
|
176
|
+
break;
|
|
177
|
+
}
|
|
178
|
+
}
|
|
179
|
+
|
|
180
|
+
pos = 0;
|
|
181
|
+
while ((pos = text.find("<style", pos)) != std::string::npos) {
|
|
182
|
+
size_t end = text.find("</style>", pos);
|
|
183
|
+
if (end != std::string::npos) {
|
|
184
|
+
text.erase(pos, end - pos + 8);
|
|
185
|
+
} else {
|
|
186
|
+
break;
|
|
187
|
+
}
|
|
188
|
+
}
|
|
189
|
+
|
|
190
|
+
// Remove HTML tags
|
|
191
|
+
pos = 0;
|
|
192
|
+
while ((pos = text.find('<', pos)) != std::string::npos) {
|
|
193
|
+
size_t end = text.find('>', pos);
|
|
194
|
+
if (end != std::string::npos) {
|
|
195
|
+
text.erase(pos, end - pos + 1);
|
|
196
|
+
} else {
|
|
197
|
+
break;
|
|
198
|
+
}
|
|
199
|
+
}
|
|
200
|
+
|
|
201
|
+
return sanitize_content(text);
|
|
202
|
+
}
|
|
203
|
+
|
|
204
|
+
std::string WebService::sanitize_content(const std::string &content) {
|
|
205
|
+
std::string sanitized = content;
|
|
206
|
+
|
|
207
|
+
// Replace HTML entities
|
|
208
|
+
size_t pos = 0;
|
|
209
|
+
while ((pos = sanitized.find("&", pos)) != std::string::npos) {
|
|
210
|
+
sanitized.replace(pos, 5, "&");
|
|
211
|
+
pos += 1;
|
|
212
|
+
}
|
|
213
|
+
while ((pos = sanitized.find("<", pos)) != std::string::npos) {
|
|
214
|
+
sanitized.replace(pos, 4, "<");
|
|
215
|
+
pos += 1;
|
|
216
|
+
}
|
|
217
|
+
while ((pos = sanitized.find(">", pos)) != std::string::npos) {
|
|
218
|
+
sanitized.replace(pos, 4, ">");
|
|
219
|
+
pos += 1;
|
|
220
|
+
}
|
|
221
|
+
while ((pos = sanitized.find(""", pos)) != std::string::npos) {
|
|
222
|
+
sanitized.replace(pos, 6, "\"");
|
|
223
|
+
pos += 1;
|
|
224
|
+
}
|
|
225
|
+
while ((pos = sanitized.find("'", pos)) != std::string::npos) {
|
|
226
|
+
sanitized.replace(pos, 5, "'");
|
|
227
|
+
pos += 1;
|
|
228
|
+
}
|
|
229
|
+
|
|
230
|
+
// Clean up whitespace
|
|
231
|
+
pos = 0;
|
|
232
|
+
while ((pos = sanitized.find("\n\n\n", pos)) != std::string::npos) {
|
|
233
|
+
sanitized.replace(pos, 3, "\n\n");
|
|
234
|
+
}
|
|
235
|
+
|
|
236
|
+
// Trim leading/trailing whitespace
|
|
237
|
+
size_t start = sanitized.find_first_not_of(" \t\n\r");
|
|
238
|
+
if (start == std::string::npos)
|
|
239
|
+
return "";
|
|
240
|
+
|
|
241
|
+
size_t end = sanitized.find_last_not_of(" \t\n\r");
|
|
242
|
+
return sanitized.substr(start, end - start + 1);
|
|
243
|
+
}
|
|
244
|
+
|
|
245
|
+
WebResponse WebService::fetch_url(const std::string &url) {
|
|
246
|
+
WebResponse response;
|
|
247
|
+
response.success = false;
|
|
248
|
+
|
|
249
|
+
if (!is_valid_url(url)) {
|
|
250
|
+
response.error_message = "Invalid URL format";
|
|
251
|
+
response.status_code = 0;
|
|
252
|
+
return response;
|
|
253
|
+
}
|
|
254
|
+
|
|
255
|
+
CurlHandle curl;
|
|
256
|
+
if (!curl) {
|
|
257
|
+
response.error_message = "Failed to initialize cURL";
|
|
258
|
+
return response;
|
|
259
|
+
}
|
|
260
|
+
|
|
261
|
+
try {
|
|
262
|
+
std::string response_body;
|
|
263
|
+
std::string response_headers;
|
|
264
|
+
long http_code = 0;
|
|
265
|
+
|
|
266
|
+
// Set up curl options
|
|
267
|
+
curl_easy_setopt(curl.get(), CURLOPT_URL, url.c_str());
|
|
268
|
+
curl_easy_setopt(curl.get(), CURLOPT_USERAGENT, "Cursor-Agent/1.0");
|
|
269
|
+
curl_easy_setopt(curl.get(), CURLOPT_TIMEOUT, 10L);
|
|
270
|
+
curl_easy_setopt(curl.get(), CURLOPT_FOLLOWLOCATION, 1L);
|
|
271
|
+
curl_easy_setopt(curl.get(), CURLOPT_WRITEFUNCTION, WriteCallback);
|
|
272
|
+
curl_easy_setopt(curl.get(), CURLOPT_WRITEDATA, &response_body);
|
|
273
|
+
curl_easy_setopt(curl.get(), CURLOPT_HEADERFUNCTION, HeaderCallback);
|
|
274
|
+
curl_easy_setopt(curl.get(), CURLOPT_HEADERDATA, &response_headers);
|
|
275
|
+
|
|
276
|
+
// Perform the request
|
|
277
|
+
CURLcode res = curl_easy_perform(curl.get());
|
|
278
|
+
|
|
279
|
+
if (res != CURLE_OK) {
|
|
280
|
+
response.error_message =
|
|
281
|
+
"cURL error: " + std::string(curl_easy_strerror(res));
|
|
282
|
+
return response;
|
|
283
|
+
}
|
|
284
|
+
|
|
285
|
+
// Get HTTP status code
|
|
286
|
+
curl_easy_getinfo(curl.get(), CURLINFO_RESPONSE_CODE, &http_code);
|
|
287
|
+
|
|
288
|
+
// Process response
|
|
289
|
+
response.status_code = static_cast<int>(http_code);
|
|
290
|
+
response.content = response_body;
|
|
291
|
+
response.success = (http_code >= 200 && http_code < 300);
|
|
292
|
+
|
|
293
|
+
// Parse headers
|
|
294
|
+
std::istringstream header_stream(response_headers);
|
|
295
|
+
std::string header_line;
|
|
296
|
+
while (std::getline(header_stream, header_line, '\n')) {
|
|
297
|
+
size_t colon_pos = header_line.find(':');
|
|
298
|
+
if (colon_pos != std::string::npos) {
|
|
299
|
+
std::string key = header_line.substr(0, colon_pos);
|
|
300
|
+
std::string value = header_line.substr(colon_pos + 1);
|
|
301
|
+
|
|
302
|
+
// Trim whitespace
|
|
303
|
+
key.erase(key.begin(), std::find_if(key.begin(), key.end(), [](int ch) {
|
|
304
|
+
return !std::isspace(ch);
|
|
305
|
+
}));
|
|
306
|
+
key.erase(std::find_if(key.rbegin(), key.rend(),
|
|
307
|
+
[](int ch) { return !std::isspace(ch); })
|
|
308
|
+
.base(),
|
|
309
|
+
key.end());
|
|
310
|
+
|
|
311
|
+
value.erase(value.begin(),
|
|
312
|
+
std::find_if(value.begin(), value.end(),
|
|
313
|
+
[](int ch) { return !std::isspace(ch); }));
|
|
314
|
+
value.erase(std::find_if(value.rbegin(), value.rend(),
|
|
315
|
+
[](int ch) { return !std::isspace(ch); })
|
|
316
|
+
.base(),
|
|
317
|
+
value.end());
|
|
318
|
+
|
|
319
|
+
if (!key.empty()) {
|
|
320
|
+
response.headers[key] = value;
|
|
321
|
+
}
|
|
322
|
+
}
|
|
323
|
+
}
|
|
324
|
+
|
|
325
|
+
// Set content type
|
|
326
|
+
auto content_type_it = response.headers.find("content-type");
|
|
327
|
+
if (content_type_it != response.headers.end()) {
|
|
328
|
+
response.content_type = content_type_it->second;
|
|
329
|
+
}
|
|
330
|
+
|
|
331
|
+
if (!response.success) {
|
|
332
|
+
response.error_message = "HTTP " + std::to_string(response.status_code);
|
|
333
|
+
}
|
|
334
|
+
|
|
335
|
+
} catch (const std::exception &e) {
|
|
336
|
+
response.error_message = "Exception: " + std::string(e.what());
|
|
337
|
+
response.success = false;
|
|
338
|
+
|
|
339
|
+
} catch (...) {
|
|
340
|
+
response.error_message = "Unknown exception";
|
|
341
|
+
response.success = false;
|
|
342
|
+
}
|
|
343
|
+
|
|
344
|
+
return response;
|
|
345
|
+
}
|
|
346
|
+
|
|
347
|
+
std::string WebService::fetch_text(const std::string &url) {
|
|
348
|
+
WebResponse response = fetch_url(url);
|
|
349
|
+
|
|
350
|
+
if (!response.success) {
|
|
351
|
+
return "Error fetching URL: " + response.error_message;
|
|
352
|
+
}
|
|
353
|
+
|
|
354
|
+
// Check if content is HTML and extract text
|
|
355
|
+
if (response.content_type.find("text/html") != std::string::npos) {
|
|
356
|
+
std::string extracted = extract_text_content(response.content);
|
|
357
|
+
if (extracted.length() > kMaxContentLength) {
|
|
358
|
+
extracted = extracted.substr(0, kMaxContentLength) +
|
|
359
|
+
"\n\n[Content truncated - showing first " +
|
|
360
|
+
std::to_string(kMaxContentLength) + " characters]";
|
|
361
|
+
}
|
|
362
|
+
return extracted;
|
|
363
|
+
}
|
|
364
|
+
|
|
365
|
+
// For plain text or other content types
|
|
366
|
+
if (response.content.length() > kMaxContentLength) {
|
|
367
|
+
return response.content.substr(0, kMaxContentLength) +
|
|
368
|
+
"\n\n[Content truncated - showing first " +
|
|
369
|
+
std::to_string(kMaxContentLength) + " characters]";
|
|
370
|
+
}
|
|
371
|
+
|
|
372
|
+
return response.content;
|
|
373
|
+
}
|
|
374
|
+
|
|
375
|
+
std::string WebService::fetch_json(const std::string &url) {
|
|
376
|
+
try {
|
|
377
|
+
auto response = fetch_url(url);
|
|
378
|
+
if (response.status_code == 200) {
|
|
379
|
+
// Validate JSON
|
|
380
|
+
auto json = nlohmann::json::parse(response.content);
|
|
381
|
+
// Pretty print JSON with truncation if needed
|
|
382
|
+
std::string formatted = json.dump(2);
|
|
383
|
+
if (formatted.length() > kMaxContentLength) {
|
|
384
|
+
formatted = formatted.substr(0, kMaxContentLength) +
|
|
385
|
+
"\n\n[JSON truncated - showing first " +
|
|
386
|
+
std::to_string(kMaxContentLength) + " characters]";
|
|
387
|
+
}
|
|
388
|
+
return formatted;
|
|
389
|
+
} else {
|
|
390
|
+
return "Error: " + std::to_string(response.status_code) + " - " +
|
|
391
|
+
response.error_message;
|
|
392
|
+
}
|
|
393
|
+
} catch (const std::exception &e) {
|
|
394
|
+
return "Error: " + std::string(e.what());
|
|
395
|
+
}
|
|
396
|
+
}
|
|
397
|
+
|
|
398
|
+
WebResponse WebService::fetch_with_headers(const std::string &url,
|
|
399
|
+
const HeaderMap &headers) {
|
|
400
|
+
CurlHandle curl;
|
|
401
|
+
WebResponse response;
|
|
402
|
+
|
|
403
|
+
if (!curl) {
|
|
404
|
+
response.status_code = 0;
|
|
405
|
+
response.error_message = "Failed to initialize CURL";
|
|
406
|
+
response.success = false;
|
|
407
|
+
return response;
|
|
408
|
+
}
|
|
409
|
+
|
|
410
|
+
std::string response_body;
|
|
411
|
+
std::string response_headers;
|
|
412
|
+
|
|
413
|
+
// Add custom headers to the request
|
|
414
|
+
struct curl_slist *header_list = nullptr;
|
|
415
|
+
for (const auto &[key, value] : headers) {
|
|
416
|
+
std::string header = key + ": " + value;
|
|
417
|
+
header_list = curl_slist_append(header_list, header.c_str());
|
|
418
|
+
}
|
|
419
|
+
|
|
420
|
+
// Set CURL options
|
|
421
|
+
curl_easy_setopt(curl.get(), CURLOPT_URL, url.c_str());
|
|
422
|
+
curl_easy_setopt(curl.get(), CURLOPT_HTTPHEADER, header_list);
|
|
423
|
+
curl_easy_setopt(curl.get(), CURLOPT_WRITEFUNCTION, WriteCallback);
|
|
424
|
+
curl_easy_setopt(curl.get(), CURLOPT_WRITEDATA, &response_body);
|
|
425
|
+
curl_easy_setopt(curl.get(), CURLOPT_HEADERFUNCTION, HeaderCallback);
|
|
426
|
+
curl_easy_setopt(curl.get(), CURLOPT_HEADERDATA, &response_headers);
|
|
427
|
+
curl_easy_setopt(curl.get(), CURLOPT_FOLLOWLOCATION, 1L);
|
|
428
|
+
curl_easy_setopt(curl.get(), CURLOPT_TIMEOUT, kDefaultTimeoutSeconds);
|
|
429
|
+
|
|
430
|
+
// Perform the request
|
|
431
|
+
CURLcode res = curl_easy_perform(curl.get());
|
|
432
|
+
|
|
433
|
+
// Get the HTTP status code
|
|
434
|
+
long http_code = 0;
|
|
435
|
+
curl_easy_getinfo(curl.get(), CURLINFO_RESPONSE_CODE, &http_code);
|
|
436
|
+
|
|
437
|
+
// Get content type if available
|
|
438
|
+
char *content_type = nullptr;
|
|
439
|
+
curl_easy_getinfo(curl.get(), CURLINFO_CONTENT_TYPE, &content_type);
|
|
440
|
+
|
|
441
|
+
// Clean up
|
|
442
|
+
if (header_list) {
|
|
443
|
+
curl_slist_free_all(header_list);
|
|
444
|
+
}
|
|
445
|
+
|
|
446
|
+
// Set response properties
|
|
447
|
+
response.status_code = static_cast<int>(http_code);
|
|
448
|
+
response.content = response_body;
|
|
449
|
+
response.content_type = content_type ? content_type : "";
|
|
450
|
+
response.success = (res == CURLE_OK);
|
|
451
|
+
|
|
452
|
+
if (res != CURLE_OK) {
|
|
453
|
+
response.error_message = curl_easy_strerror(res);
|
|
454
|
+
} else if (http_code >= 400) {
|
|
455
|
+
response.error_message = "HTTP error " + std::to_string(http_code);
|
|
456
|
+
}
|
|
457
|
+
|
|
458
|
+
// Parse response headers
|
|
459
|
+
std::istringstream header_stream(response_headers);
|
|
460
|
+
std::string header_line;
|
|
461
|
+
while (std::getline(header_stream, header_line)) {
|
|
462
|
+
size_t colon_pos = header_line.find(':');
|
|
463
|
+
if (colon_pos != std::string::npos) {
|
|
464
|
+
std::string key = header_line.substr(0, colon_pos);
|
|
465
|
+
std::string value = header_line.substr(colon_pos + 1);
|
|
466
|
+
// Trim whitespace
|
|
467
|
+
key.erase(0, key.find_first_not_of(" \t"));
|
|
468
|
+
key.erase(key.find_last_not_of(" \t") + 1);
|
|
469
|
+
value.erase(0, value.find_first_not_of(" \t"));
|
|
470
|
+
value.erase(value.find_last_not_of(" \t") + 1);
|
|
471
|
+
response.headers[key] = value;
|
|
472
|
+
}
|
|
473
|
+
}
|
|
474
|
+
|
|
475
|
+
return response;
|
|
476
|
+
}
|
|
477
|
+
|
|
478
|
+
WebResponse WebService::post_json(const std::string &url,
|
|
479
|
+
const std::string &json_body,
|
|
480
|
+
const HeaderMap &headers) {
|
|
481
|
+
WebResponse response;
|
|
482
|
+
response.success = false;
|
|
483
|
+
|
|
484
|
+
try {
|
|
485
|
+
// Prepare headers for CPR
|
|
486
|
+
cpr::Header cpr_headers;
|
|
487
|
+
for (const auto &[key, value] : headers) {
|
|
488
|
+
cpr_headers[key] = value;
|
|
489
|
+
}
|
|
490
|
+
|
|
491
|
+
// Set Content-Type if not provided
|
|
492
|
+
if (headers.find("Content-Type") == headers.end()) {
|
|
493
|
+
cpr_headers["Content-Type"] = "application/json";
|
|
494
|
+
}
|
|
495
|
+
|
|
496
|
+
// Make POST request
|
|
497
|
+
cpr::Response cpr_response =
|
|
498
|
+
cpr::Post(cpr::Url{url}, cpr_headers, cpr::Body{json_body},
|
|
499
|
+
cpr::Timeout{std::chrono::seconds{kDefaultTimeoutSeconds}});
|
|
500
|
+
|
|
501
|
+
// Fill response
|
|
502
|
+
response.status_code = static_cast<int>(cpr_response.status_code);
|
|
503
|
+
response.content = cpr_response.text;
|
|
504
|
+
response.success =
|
|
505
|
+
(cpr_response.status_code >= 200 && cpr_response.status_code < 300);
|
|
506
|
+
|
|
507
|
+
if (!response.success) {
|
|
508
|
+
response.error_message = "HTTP " +
|
|
509
|
+
std::to_string(cpr_response.status_code) +
|
|
510
|
+
" | Error: " + cpr_response.error.message;
|
|
511
|
+
}
|
|
512
|
+
|
|
513
|
+
// Parse headers
|
|
514
|
+
for (const auto &[key, value] : cpr_response.header) {
|
|
515
|
+
response.headers[key] = value;
|
|
516
|
+
}
|
|
517
|
+
|
|
518
|
+
// Set content type
|
|
519
|
+
auto content_type_it = response.headers.find("content-type");
|
|
520
|
+
if (content_type_it != response.headers.end()) {
|
|
521
|
+
response.content_type = content_type_it->second;
|
|
522
|
+
}
|
|
523
|
+
|
|
524
|
+
} catch (const std::exception &e) {
|
|
525
|
+
response.error_message = "Exception: " + std::string(e.what());
|
|
526
|
+
} catch (...) {
|
|
527
|
+
response.error_message = "Unknown exception";
|
|
528
|
+
}
|
|
529
|
+
|
|
530
|
+
return response;
|
|
531
|
+
}
|
|
532
|
+
} // namespace Services
|
|
@@ -0,0 +1,77 @@
|
|
|
1
|
+
#include "utils/config.h"
|
|
2
|
+
|
|
3
|
+
// Define the CURSOR_LIBRARY macro to ensure proper symbol export
|
|
4
|
+
#ifndef CURSOR_LIBRARY
|
|
5
|
+
#define CURSOR_LIBRARY
|
|
6
|
+
#endif
|
|
7
|
+
#include <cstdlib>
|
|
8
|
+
#include <fstream>
|
|
9
|
+
#include <iostream>
|
|
10
|
+
#include <sstream>
|
|
11
|
+
|
|
12
|
+
namespace Utils {
|
|
13
|
+
namespace Config {
|
|
14
|
+
|
|
15
|
+
static void set_env_var(const std::string &key, const std::string &value) {
|
|
16
|
+
#if defined(_WIN32)
|
|
17
|
+
_putenv_s(key.c_str(), value.c_str());
|
|
18
|
+
#else
|
|
19
|
+
setenv(key.c_str(), value.c_str(), 1);
|
|
20
|
+
#endif
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
void load_environment(const std::string &filename) {
|
|
24
|
+
std::ifstream file(filename);
|
|
25
|
+
if (!file.is_open()) {
|
|
26
|
+
return; // Optional
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
std::string line;
|
|
30
|
+
while (std::getline(file, line)) {
|
|
31
|
+
// Trim leading/trailing spaces
|
|
32
|
+
auto trim = [](std::string &s) {
|
|
33
|
+
s.erase(0, s.find_first_not_of(" \t\r\n"));
|
|
34
|
+
s.erase(s.find_last_not_of(" \t\r\n") + 1);
|
|
35
|
+
};
|
|
36
|
+
|
|
37
|
+
trim(line);
|
|
38
|
+
if (line.empty() || line[0] == '#')
|
|
39
|
+
continue;
|
|
40
|
+
|
|
41
|
+
// Remove inline comment if not inside quotes
|
|
42
|
+
size_t hash_pos = line.find('#');
|
|
43
|
+
if (hash_pos != std::string::npos && (line.find('"') > hash_pos)) {
|
|
44
|
+
line = line.substr(0, hash_pos);
|
|
45
|
+
trim(line);
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
std::istringstream is_line(line);
|
|
49
|
+
std::string key, value;
|
|
50
|
+
|
|
51
|
+
if (std::getline(is_line, key, '=') && std::getline(is_line, value)) {
|
|
52
|
+
trim(key);
|
|
53
|
+
trim(value);
|
|
54
|
+
|
|
55
|
+
// Remove quotes
|
|
56
|
+
if (value.size() >= 2 && value.front() == '"' && value.back() == '"') {
|
|
57
|
+
value = value.substr(1, value.size() - 2);
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
if (!key.empty()) {
|
|
61
|
+
set_env_var(key, value);
|
|
62
|
+
}
|
|
63
|
+
}
|
|
64
|
+
}
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
std::string get_env_var(const std::string &key,
|
|
68
|
+
const std::string &default_value) {
|
|
69
|
+
const char *value = std::getenv(key.c_str());
|
|
70
|
+
return value ? std::string(value) : default_value;
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
bool has_env_var(const std::string &key) {
|
|
74
|
+
return std::getenv(key.c_str()) != nullptr;
|
|
75
|
+
}
|
|
76
|
+
} // namespace Config
|
|
77
|
+
} // namespace Utils
|
|
@@ -0,0 +1,93 @@
|
|
|
1
|
+
#include "utils/memory_utils.h"
|
|
2
|
+
#include <algorithm>
|
|
3
|
+
#include <array>
|
|
4
|
+
#include <iomanip>
|
|
5
|
+
#include <sstream>
|
|
6
|
+
#include <string>
|
|
7
|
+
|
|
8
|
+
namespace Utils {
|
|
9
|
+
namespace Memory {
|
|
10
|
+
|
|
11
|
+
size_t MemoryTracker::current_usage_ = 0;
|
|
12
|
+
size_t MemoryTracker::peak_usage_ = 0;
|
|
13
|
+
|
|
14
|
+
void MemoryTracker::add_allocation(size_t size) {
|
|
15
|
+
current_usage_ += size;
|
|
16
|
+
peak_usage_ = std::max(peak_usage_, current_usage_);
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
void MemoryTracker::remove_allocation(size_t size) {
|
|
20
|
+
if (current_usage_ >= size) {
|
|
21
|
+
current_usage_ -= size;
|
|
22
|
+
}
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
std::string MemoryTracker::format_bytes(size_t bytes) {
|
|
26
|
+
constexpr std::array<std::string_view, 4> units = {"B", "KB", "MB", "GB"};
|
|
27
|
+
constexpr double KB = 1024.0;
|
|
28
|
+
double size = static_cast<double>(bytes);
|
|
29
|
+
size_t unit_index = 0;
|
|
30
|
+
|
|
31
|
+
while (size >= KB && unit_index < 3) {
|
|
32
|
+
size /= KB;
|
|
33
|
+
unit_index++;
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
std::ostringstream oss;
|
|
37
|
+
oss << std::fixed << std::setprecision(2) << size << " " << units[unit_index];
|
|
38
|
+
return oss.str();
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
std::vector<std::string_view> split_view(std::string_view input,
|
|
42
|
+
char delimiter) {
|
|
43
|
+
std::vector<std::string_view> result;
|
|
44
|
+
result.reserve(8); // Reserve for common case
|
|
45
|
+
|
|
46
|
+
size_t start = 0;
|
|
47
|
+
size_t pos = 0;
|
|
48
|
+
|
|
49
|
+
while ((pos = input.find(delimiter, start)) != std::string_view::npos) {
|
|
50
|
+
if (pos > start) { // Skip empty tokens
|
|
51
|
+
result.emplace_back(input.substr(start, pos - start));
|
|
52
|
+
}
|
|
53
|
+
start = pos + 1;
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
// Add the last token if it exists
|
|
57
|
+
if (start < input.length()) {
|
|
58
|
+
result.emplace_back(input.substr(start));
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
return result;
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
std::vector<std::string_view> split_view(std::string_view input,
|
|
65
|
+
std::string_view delimiter) {
|
|
66
|
+
std::vector<std::string_view> result;
|
|
67
|
+
result.reserve(8); // Reserve for common case
|
|
68
|
+
|
|
69
|
+
if (delimiter.empty()) {
|
|
70
|
+
result.emplace_back(input);
|
|
71
|
+
return result;
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
size_t start = 0;
|
|
75
|
+
size_t pos = 0;
|
|
76
|
+
|
|
77
|
+
while ((pos = input.find(delimiter, start)) != std::string_view::npos) {
|
|
78
|
+
if (pos > start) { // Skip empty tokens
|
|
79
|
+
result.emplace_back(input.substr(start, pos - start));
|
|
80
|
+
}
|
|
81
|
+
start = pos + delimiter.length();
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
// Add the last token if it exists
|
|
85
|
+
if (start < input.length()) {
|
|
86
|
+
result.emplace_back(input.substr(start));
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
return result;
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
} // namespace Memory
|
|
93
|
+
} // namespace Utils
|