implement Milestone 9: Network HTTP with SSRF prevention
This commit is contained in:
249
src/main/cpp/sandbox/network_manager.cpp
Normal file
249
src/main/cpp/sandbox/network_manager.cpp
Normal file
@@ -0,0 +1,249 @@
|
||||
#include "network_manager.h"
|
||||
#include <lua.hpp>
|
||||
#include <algorithm>
|
||||
|
||||
namespace mosis {
|
||||
|
||||
NetworkManager::NetworkManager(const std::string& app_id, const NetworkLimits& limits)
|
||||
: m_app_id(app_id)
|
||||
, m_limits(limits)
|
||||
, m_mock_mode(true)
|
||||
{
|
||||
}
|
||||
|
||||
NetworkManager::~NetworkManager() {
|
||||
}
|
||||
|
||||
void NetworkManager::SetAllowedDomains(const std::vector<std::string>& domains) {
|
||||
m_validator.SetAllowedDomains(domains);
|
||||
}
|
||||
|
||||
void NetworkManager::ClearDomainRestrictions() {
|
||||
m_validator.ClearDomainRestrictions();
|
||||
}
|
||||
|
||||
bool NetworkManager::ValidateRequest(const HttpRequest& request, std::string& error) {
|
||||
// Validate URL
|
||||
auto parsed = m_validator.Validate(request.url, error);
|
||||
if (!parsed) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// Validate method
|
||||
std::string method = request.method;
|
||||
std::transform(method.begin(), method.end(), method.begin(),
|
||||
[](unsigned char c) { return std::toupper(c); });
|
||||
|
||||
static const std::vector<std::string> allowed_methods = {
|
||||
"GET", "POST", "PUT", "DELETE", "PATCH", "HEAD", "OPTIONS"
|
||||
};
|
||||
|
||||
bool method_valid = false;
|
||||
for (const auto& m : allowed_methods) {
|
||||
if (method == m) {
|
||||
method_valid = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (!method_valid) {
|
||||
error = "Invalid HTTP method: " + request.method;
|
||||
return false;
|
||||
}
|
||||
|
||||
// Validate request body size
|
||||
if (request.body.size() > m_limits.max_request_body) {
|
||||
error = "Request body too large: " + std::to_string(request.body.size()) +
|
||||
" bytes (max " + std::to_string(m_limits.max_request_body) + ")";
|
||||
return false;
|
||||
}
|
||||
|
||||
// Validate timeout
|
||||
if (request.timeout_ms > m_limits.max_timeout_ms) {
|
||||
error = "Timeout too large: " + std::to_string(request.timeout_ms) +
|
||||
"ms (max " + std::to_string(m_limits.max_timeout_ms) + "ms)";
|
||||
return false;
|
||||
}
|
||||
|
||||
// Check concurrent request limit
|
||||
if (m_active_requests.load() >= m_limits.max_concurrent_requests) {
|
||||
error = "Too many concurrent requests (max " +
|
||||
std::to_string(m_limits.max_concurrent_requests) + ")";
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
HttpResponse NetworkManager::Request(const HttpRequest& request, std::string& error) {
|
||||
HttpResponse response;
|
||||
|
||||
// Validate the request
|
||||
if (!ValidateRequest(request, error)) {
|
||||
response.error = error;
|
||||
return response;
|
||||
}
|
||||
|
||||
// In mock mode, we don't actually make network calls
|
||||
// This is for testing the validation logic
|
||||
if (m_mock_mode) {
|
||||
error = "Network requests disabled in mock mode";
|
||||
response.error = error;
|
||||
return response;
|
||||
}
|
||||
|
||||
// Track active requests
|
||||
m_active_requests++;
|
||||
|
||||
// In a real implementation, we would make the HTTP request here
|
||||
// For now, just return an error indicating no network implementation
|
||||
error = "Network requests not implemented on this platform";
|
||||
response.error = error;
|
||||
|
||||
m_active_requests--;
|
||||
return response;
|
||||
}
|
||||
|
||||
int NetworkManager::GetActiveRequestCount() const {
|
||||
return m_active_requests.load();
|
||||
}
|
||||
|
||||
// Lua API implementation
|
||||
|
||||
// Get NetworkManager from upvalue
|
||||
static NetworkManager* GetManager(lua_State* L) {
|
||||
return static_cast<NetworkManager*>(lua_touserdata(L, lua_upvalueindex(1)));
|
||||
}
|
||||
|
||||
// network.request(options) -> response, error
|
||||
static int L_network_request(lua_State* L) {
|
||||
NetworkManager* manager = GetManager(L);
|
||||
if (!manager) {
|
||||
lua_pushnil(L);
|
||||
lua_pushstring(L, "NetworkManager not available");
|
||||
return 2;
|
||||
}
|
||||
|
||||
// Expect table argument
|
||||
luaL_checktype(L, 1, LUA_TTABLE);
|
||||
|
||||
HttpRequest request;
|
||||
|
||||
// Get URL (required)
|
||||
lua_getfield(L, 1, "url");
|
||||
if (!lua_isstring(L, -1)) {
|
||||
lua_pushnil(L);
|
||||
lua_pushstring(L, "url is required and must be a string");
|
||||
return 2;
|
||||
}
|
||||
request.url = lua_tostring(L, -1);
|
||||
lua_pop(L, 1);
|
||||
|
||||
// Get method (optional, default GET)
|
||||
lua_getfield(L, 1, "method");
|
||||
if (lua_isstring(L, -1)) {
|
||||
request.method = lua_tostring(L, -1);
|
||||
}
|
||||
lua_pop(L, 1);
|
||||
|
||||
// Get headers (optional)
|
||||
lua_getfield(L, 1, "headers");
|
||||
if (lua_istable(L, -1)) {
|
||||
lua_pushnil(L);
|
||||
while (lua_next(L, -2) != 0) {
|
||||
if (lua_isstring(L, -2) && lua_isstring(L, -1)) {
|
||||
request.headers[lua_tostring(L, -2)] = lua_tostring(L, -1);
|
||||
}
|
||||
lua_pop(L, 1);
|
||||
}
|
||||
}
|
||||
lua_pop(L, 1);
|
||||
|
||||
// Get body (optional)
|
||||
lua_getfield(L, 1, "body");
|
||||
if (lua_isstring(L, -1)) {
|
||||
size_t len;
|
||||
const char* body = lua_tolstring(L, -1, &len);
|
||||
request.body = std::string(body, len);
|
||||
}
|
||||
lua_pop(L, 1);
|
||||
|
||||
// Get timeout (optional)
|
||||
lua_getfield(L, 1, "timeout");
|
||||
if (lua_isnumber(L, -1)) {
|
||||
request.timeout_ms = static_cast<int>(lua_tointeger(L, -1));
|
||||
}
|
||||
lua_pop(L, 1);
|
||||
|
||||
// Make request
|
||||
std::string error;
|
||||
HttpResponse response = manager->Request(request, error);
|
||||
|
||||
if (!error.empty()) {
|
||||
lua_pushnil(L);
|
||||
lua_pushstring(L, error.c_str());
|
||||
return 2;
|
||||
}
|
||||
|
||||
// Return response as table
|
||||
lua_newtable(L);
|
||||
|
||||
lua_pushinteger(L, response.status_code);
|
||||
lua_setfield(L, -2, "status");
|
||||
|
||||
lua_pushstring(L, response.body.c_str());
|
||||
lua_setfield(L, -2, "body");
|
||||
|
||||
// Headers table
|
||||
lua_newtable(L);
|
||||
for (const auto& [key, value] : response.headers) {
|
||||
lua_pushstring(L, value.c_str());
|
||||
lua_setfield(L, -2, key.c_str());
|
||||
}
|
||||
lua_setfield(L, -2, "headers");
|
||||
|
||||
if (!response.error.empty()) {
|
||||
lua_pushstring(L, response.error.c_str());
|
||||
lua_setfield(L, -2, "error");
|
||||
}
|
||||
|
||||
return 1; // Return response table
|
||||
}
|
||||
|
||||
// Helper to set a global in the real _G (bypassing any proxy)
|
||||
static void SetGlobalInRealG(lua_State* L, const char* name) {
|
||||
// Stack: value to set as global
|
||||
lua_rawgeti(L, LUA_REGISTRYINDEX, LUA_RIDX_GLOBALS);
|
||||
|
||||
// Check if it's a proxy with __index pointing to real _G
|
||||
if (lua_getmetatable(L, -1)) {
|
||||
lua_getfield(L, -1, "__index");
|
||||
if (lua_istable(L, -1)) {
|
||||
// This is the real _G, set our value there
|
||||
lua_pushvalue(L, -4); // Push the value
|
||||
lua_setfield(L, -2, name);
|
||||
lua_pop(L, 4); // Pop __index, metatable, proxy, (value already consumed)
|
||||
return;
|
||||
}
|
||||
lua_pop(L, 2); // Pop __index and metatable
|
||||
}
|
||||
|
||||
// No proxy, set directly
|
||||
lua_pushvalue(L, -2); // Push the value
|
||||
lua_setfield(L, -2, name);
|
||||
lua_pop(L, 2); // Pop globals table and original value
|
||||
}
|
||||
|
||||
void RegisterNetworkAPI(lua_State* L, NetworkManager* manager) {
|
||||
// Create network table
|
||||
lua_newtable(L);
|
||||
|
||||
// Add request function with manager as upvalue
|
||||
lua_pushlightuserdata(L, manager);
|
||||
lua_pushcclosure(L, L_network_request, 1);
|
||||
lua_setfield(L, -2, "request");
|
||||
|
||||
// Set as global
|
||||
SetGlobalInRealG(L, "network");
|
||||
}
|
||||
|
||||
} // namespace mosis
|
||||
Reference in New Issue
Block a user