# Milestone 9: Network - HTTP

**Status**: Complete
**Goal**: Secure HTTP requests with domain filtering and SSRF prevention.

---

## Overview

This milestone implements secure HTTP networking for Lua apps:
- HTTPS required (no plain HTTP)
- SSRF prevention (block private IPs, localhost, metadata endpoints)
- Domain whitelist from app manifest
- Request/response size limits
- Concurrent request limits

### Key Deliverables

1. **HttpValidator class** - URL parsing, domain validation, IP blocking
2. **NetworkManager class** - HTTP client with limits
3. **Lua network API** - `network.request()` function
4. **Desktop mock server** - For testing without network

---

## File Structure

```
src/main/cpp/sandbox/
├── http_validator.h        # NEW - URL validation
├── http_validator.cpp      # NEW - SSRF prevention
├── network_manager.h       # NEW - HTTP client wrapper
└── network_manager.cpp     # NEW - Request execution
```

---

## Implementation Details

### 1. URL Validation Rules

```
Allowed:
  https://api.example.com/path
  https://192.0.2.1/api  (public IP)

Blocked:
  http://example.com/...           # No plain HTTP
  https://127.0.0.1/...            # Localhost
  https://localhost/...            # Localhost name
  https://10.0.0.1/...             # Private IP (10.x.x.x)
  https://192.168.1.1/...          # Private IP (192.168.x.x)
  https://172.16.0.1/...           # Private IP (172.16-31.x.x)
  https://169.254.169.254/...      # AWS metadata
  https://[::1]/...                # IPv6 localhost
  https://[fc00::1]/...            # IPv6 private
  https://0.0.0.0/...              # All interfaces
  file:///etc/passwd              # File scheme
  ftp://ftp.example.com/...       # Non-HTTP schemes
```

### 2. HttpValidator Class

```cpp
// http_validator.h
#pragma once

#include <string>
#include <vector>
#include <optional>

namespace mosis {

struct ParsedUrl {
    std::string scheme;      // "https"
    std::string host;        // "api.example.com" or "192.0.2.1"
    uint16_t port;           // 443
    std::string path;        // "/api/data"
    std::string query;       // "?key=value"
    bool is_ip_address;      // true if host is IP
};

class HttpValidator {
public:
    HttpValidator();

    // Set allowed domains (from app manifest)
    void SetAllowedDomains(const std::vector<std::string>& domains);

    // Clear domain restrictions (for testing)
    void ClearDomainRestrictions();

    // Validate URL
    // Returns parsed URL on success, sets error on failure
    std::optional<ParsedUrl> Validate(const std::string& url, std::string& error);

private:
    std::vector<std::string> m_allowed_domains;
    bool m_domain_restrictions_enabled;

    bool IsPrivateIP(const std::string& ip);
    bool IsLocalhostIP(const std::string& ip);
    bool IsMetadataIP(const std::string& ip);
    bool IsDomainAllowed(const std::string& host);
    std::optional<ParsedUrl> ParseUrl(const std::string& url);
};

} // namespace mosis
```

### 3. NetworkManager Class

```cpp
// network_manager.h
#pragma once

#include <string>
#include <vector>
#include <map>
#include <functional>
#include <mutex>
#include <atomic>
#include "http_validator.h"

struct lua_State;

namespace mosis {

struct HttpRequest {
    std::string url;
    std::string method = "GET";
    std::map<std::string, std::string> headers;
    std::string body;
    int timeout_ms = 30000;
};

struct HttpResponse {
    int status_code = 0;
    std::map<std::string, std::string> headers;
    std::string body;
    std::string error;
};

struct NetworkLimits {
    size_t max_request_body = 10 * 1024 * 1024;     // 10 MB
    size_t max_response_body = 50 * 1024 * 1024;    // 50 MB
    int max_timeout_ms = 60000;                      // 60 seconds
    int max_concurrent_requests = 6;
    int default_timeout_ms = 30000;
};

class NetworkManager {
public:
    NetworkManager(const std::string& app_id, const NetworkLimits& limits = NetworkLimits{});
    ~NetworkManager();

    // Configure domain restrictions
    void SetAllowedDomains(const std::vector<std::string>& domains);
    void ClearDomainRestrictions();

    // Synchronous request (for testing)
    HttpResponse Request(const HttpRequest& request, std::string& error);

    // Stats
    int GetActiveRequestCount() const;

    // Get validator for testing
    HttpValidator& GetValidator() { return m_validator; }

private:
    std::string m_app_id;
    NetworkLimits m_limits;
    HttpValidator m_validator;
    std::atomic<int> m_active_requests{0};
    std::mutex m_mutex;

    HttpResponse ExecuteRequest(const ParsedUrl& parsed, const HttpRequest& request);
};

// Register network.* APIs as globals
void RegisterNetworkAPI(lua_State* L, NetworkManager* manager);

} // namespace mosis
```

### 4. Private IP Ranges (SSRF Prevention)

| Range | Description |
|-------|-------------|
| `127.0.0.0/8` | Loopback |
| `10.0.0.0/8` | Private Class A |
| `172.16.0.0/12` | Private Class B |
| `192.168.0.0/16` | Private Class C |
| `169.254.0.0/16` | Link-local |
| `169.254.169.254` | Cloud metadata |
| `0.0.0.0` | All interfaces |
| `::1` | IPv6 loopback |
| `fc00::/7` | IPv6 private |
| `fe80::/10` | IPv6 link-local |

### 5. Lua API

```lua
-- Simple GET request
local response, err = network.request({
    url = "https://api.example.com/data"
})
if not response then
    print("Error: " .. err)
    return
end
print("Status:", response.status)
print("Body:", response.body)

-- POST with headers and body
local response = network.request({
    url = "https://api.example.com/submit",
    method = "POST",
    headers = {
        ["Content-Type"] = "application/json",
        ["Authorization"] = "Bearer token123"
    },
    body = '{"key": "value"}',
    timeout = 30000  -- 30 seconds
})

-- Response structure
-- response.status   - HTTP status code (200, 404, etc.)
-- response.headers  - Response headers table
-- response.body     - Response body string
-- response.error    - Error message (if request failed)
```

### 6. Desktop Implementation

For desktop testing, we use cpp-httplib (header-only HTTP client).

On Android, we would use HttpURLConnection through JNI, but for the sandbox tests,
we implement a mock HTTP server that returns predefined responses.

### 7. Mock Server for Testing

```cpp
// Test helper - mock HTTP responses
class MockHttpServer {
public:
    void AddResponse(const std::string& url, const HttpResponse& response);
    std::optional<HttpResponse> GetResponse(const std::string& url);
    void Clear();
private:
    std::map<std::string, HttpResponse> m_responses;
};
```

---

## Test Cases

### Test 1: Block Private IPs

```cpp
bool Test_NetworkBlocksPrivateIP(std::string& error_msg) {
    mosis::NetworkManager manager("test.app");
    manager.ClearDomainRestrictions();

    std::string err;

    // All these should be blocked
    std::vector<std::string> private_urls = {
        "https://127.0.0.1/api",
        "https://10.0.0.1/api",
        "https://192.168.1.1/api",
        "https://172.16.0.1/api",
        "https://169.254.169.254/latest/meta-data/",
        "https://localhost/api",
        "https://0.0.0.0/api"
    };

    for (const auto& url : private_urls) {
        mosis::HttpRequest req;
        req.url = url;
        auto response = manager.Request(req, err);
        EXPECT_TRUE(response.status_code == 0);  // Request should fail
        EXPECT_TRUE(err.find("blocked") != std::string::npos ||
                    err.find("private") != std::string::npos ||
                    err.find("localhost") != std::string::npos);
    }

    return true;
}
```

### Test 2: Block Plain HTTP

```cpp
bool Test_NetworkBlocksPlainHttp(std::string& error_msg) {
    mosis::NetworkManager manager("test.app");
    manager.ClearDomainRestrictions();

    std::string err;
    mosis::HttpRequest req;
    req.url = "http://example.com/api";  // No HTTPS

    auto response = manager.Request(req, err);
    EXPECT_TRUE(response.status_code == 0);
    EXPECT_TRUE(err.find("HTTPS") != std::string::npos);

    return true;
}
```

### Test 3: Require HTTPS

```cpp
bool Test_NetworkRequiresHttps(std::string& error_msg) {
    mosis::NetworkManager manager("test.app");
    manager.ClearDomainRestrictions();

    // HTTPS URL with mock validator should parse successfully
    std::string err;
    auto parsed = manager.GetValidator().Validate("https://example.com/api", err);
    EXPECT_TRUE(parsed.has_value());
    EXPECT_TRUE(parsed->scheme == "https");

    // HTTP should fail validation
    parsed = manager.GetValidator().Validate("http://example.com/api", err);
    EXPECT_FALSE(parsed.has_value());

    return true;
}
```

### Test 4: Domain Whitelist

```cpp
bool Test_NetworkEnforcesDomainWhitelist(std::string& error_msg) {
    mosis::NetworkManager manager("test.app");

    // Set allowed domains
    manager.SetAllowedDomains({"api.example.com", "cdn.example.com"});

    std::string err;

    // Allowed domain should validate
    auto parsed = manager.GetValidator().Validate("https://api.example.com/data", err);
    EXPECT_TRUE(parsed.has_value());

    // Disallowed domain should fail
    parsed = manager.GetValidator().Validate("https://evil.com/steal", err);
    EXPECT_FALSE(parsed.has_value());
    EXPECT_TRUE(err.find("allowed") != std::string::npos ||
                err.find("whitelist") != std::string::npos);

    return true;
}
```

### Test 5: URL Parsing

```cpp
bool Test_NetworkUrlParsing(std::string& error_msg) {
    mosis::HttpValidator validator;
    std::string err;

    // Full URL
    auto parsed = validator.Validate("https://api.example.com:8443/path/to/resource?key=value", err);
    EXPECT_TRUE(parsed.has_value());
    EXPECT_TRUE(parsed->scheme == "https");
    EXPECT_TRUE(parsed->host == "api.example.com");
    EXPECT_TRUE(parsed->port == 8443);
    EXPECT_TRUE(parsed->path == "/path/to/resource");
    EXPECT_TRUE(parsed->query == "?key=value");

    // Default port
    parsed = validator.Validate("https://example.com/api", err);
    EXPECT_TRUE(parsed.has_value());
    EXPECT_TRUE(parsed->port == 443);

    return true;
}
```

### Test 6: Block Metadata Endpoints

```cpp
bool Test_NetworkBlocksMetadata(std::string& error_msg) {
    mosis::NetworkManager manager("test.app");
    manager.ClearDomainRestrictions();

    std::string err;

    // AWS metadata
    mosis::HttpRequest req;
    req.url = "https://169.254.169.254/latest/meta-data/";
    auto response = manager.Request(req, err);
    EXPECT_TRUE(response.status_code == 0);

    // GCP metadata
    req.url = "https://metadata.google.internal/computeMetadata/v1/";
    response = manager.Request(req, err);
    EXPECT_TRUE(response.status_code == 0);

    return true;
}
```

### Test 7: Request Limits

```cpp
bool Test_NetworkRequestLimits(std::string& error_msg) {
    mosis::NetworkLimits limits;
    limits.max_request_body = 1024;  // 1 KB for testing

    mosis::NetworkManager manager("test.app", limits);
    manager.ClearDomainRestrictions();

    std::string err;
    mosis::HttpRequest req;
    req.url = "https://example.com/api";
    req.method = "POST";
    req.body = std::string(2048, 'X');  // 2 KB - exceeds limit

    auto response = manager.Request(req, err);
    EXPECT_TRUE(response.status_code == 0);
    EXPECT_TRUE(err.find("size") != std::string::npos ||
                err.find("limit") != std::string::npos ||
                err.find("large") != std::string::npos);

    return true;
}
```

### Test 8: Lua Integration

```cpp
bool Test_NetworkLuaIntegration(std::string& error_msg) {
    SandboxContext ctx = TestContext();
    LuaSandbox sandbox(ctx);

    mosis::NetworkManager manager("test.app");
    manager.ClearDomainRestrictions();
    mosis::RegisterNetworkAPI(sandbox.GetState(), &manager);

    std::string script = R"lua(
        -- Test that network global exists
        if not network then
            error("network global not found")
        end
        if not network.request then
            error("network.request not found")
        end

        -- Test validation rejection (private IP)
        local response, err = network.request({
            url = "https://127.0.0.1/api"
        })
        if response then
            error("expected private IP to be blocked")
        end
    )lua";

    bool ok = sandbox.LoadString(script, "network_test");
    if (!ok) {
        error_msg = "Lua test failed: " + sandbox.GetLastError();
        return false;
    }
    return true;
}
```

---

## Acceptance Criteria

All tests must pass:

- [x] `Test_NetworkBlocksPrivateIP` - Private IPs blocked
- [x] `Test_NetworkBlocksPlainHttp` - Plain HTTP rejected
- [x] `Test_NetworkRequiresHttps` - HTTPS required
- [x] `Test_NetworkEnforcesDomainWhitelist` - Domain restrictions work
- [x] `Test_NetworkUrlParsing` - URL parsing correct
- [x] `Test_NetworkBlocksMetadata` - Cloud metadata blocked
- [x] `Test_NetworkRequestLimits` - Size limits enforced
- [x] `Test_NetworkLuaIntegration` - Lua API works

---

## Dependencies

- Milestone 1 (LuaSandbox)
- Milestone 2 (PermissionGate)
- Milestone 3 (RateLimiter, AuditLog)

---

## Notes

### Desktop vs Android Implementation

For the sandbox-test project (desktop), we focus on URL validation and request
structure validation. Actual network requests would go through cpp-httplib or
a mock server.

On Android, the real implementation would:
1. Call through JNI to Java HttpURLConnection
2. Use Android's network security config
3. Integrate with the permission system

### Security Considerations

1. **DNS Rebinding**: In production, re-validate IP after DNS resolution
2. **Redirects**: Follow redirects only to allowed domains, max 5 redirects
3. **Content-Type**: Validate response content-type matches expected
4. **Timeouts**: Both connection and read timeouts

---

## Next Steps

After Milestone 9 passes:
1. Milestone 10: Network - WebSocket
