Skip to main content

ferripfs_config/
gateway.rs

1// Ported from: kubo/config/gateway.go
2// Kubo version: v0.39.0
3// Original: https://github.com/ipfs/kubo/blob/v0.39.0/config/gateway.go
4//
5// Original work: Copyright (c) Protocol Labs, Inc.
6// Port: Copyright (c) 2026 ferripfs contributors
7// SPDX-License-Identifier: MIT OR Apache-2.0
8
9//! Gateway configuration for HTTP gateway.
10
11use crate::{Flag, OptionalDuration, OptionalInteger, OptionalString};
12use serde::{Deserialize, Serialize};
13use std::collections::HashMap;
14
15/// Gateway configuration section
16#[derive(Debug, Clone, Default, Serialize, Deserialize)]
17#[serde(rename_all = "PascalCase")]
18pub struct Gateway {
19    /// HTTP headers to include in responses
20    #[serde(default, rename = "HTTPHeaders")]
21    pub http_headers: HashMap<String, Vec<String>>,
22
23    /// Root redirect URL
24    #[serde(default)]
25    pub root_redirect: String,
26
27    /// Disable fetching content from network
28    #[serde(default)]
29    pub no_fetch: bool,
30
31    /// Disable DNSLink resolution
32    #[serde(default, rename = "NoDNSLink")]
33    pub no_dns_link: bool,
34
35    /// Enable deserialized responses
36    #[serde(default, skip_serializing_if = "Option::is_none")]
37    pub deserialized_responses: Option<Flag>,
38
39    /// Disable HTML error pages
40    #[serde(
41        default,
42        skip_serializing_if = "Option::is_none",
43        rename = "DisableHTMLErrors"
44    )]
45    pub disable_html_errors: Option<Flag>,
46
47    /// Public gateway configurations by hostname
48    #[serde(default)]
49    pub public_gateways: HashMap<String, GatewaySpec>,
50
51    /// Expose routing API
52    #[serde(
53        default,
54        skip_serializing_if = "Option::is_none",
55        rename = "ExposeRoutingAPI"
56    )]
57    pub expose_routing_api: Option<Flag>,
58
59    /// Content retrieval timeout
60    #[serde(default, skip_serializing_if = "Option::is_none")]
61    pub retrieval_timeout: Option<OptionalDuration>,
62
63    /// Maximum concurrent requests
64    #[serde(default, skip_serializing_if = "Option::is_none")]
65    pub max_concurrent_requests: Option<OptionalInteger>,
66
67    /// Maximum file size for range requests
68    #[serde(default, skip_serializing_if = "Option::is_none")]
69    pub max_range_request_file_size: Option<OptionalString>,
70
71    /// Diagnostic service URL
72    #[serde(
73        default,
74        skip_serializing_if = "Option::is_none",
75        rename = "DiagnosticServiceURL"
76    )]
77    pub diagnostic_service_url: Option<OptionalString>,
78}
79
80/// Gateway specification for public gateways
81#[derive(Debug, Clone, Default, Serialize, Deserialize)]
82#[serde(rename_all = "PascalCase")]
83pub struct GatewaySpec {
84    /// Paths to serve (e.g., "/ipfs", "/ipns")
85    #[serde(default)]
86    pub paths: Vec<String>,
87
88    /// Use subdomain-based gateway
89    #[serde(default)]
90    pub use_subdomains: bool,
91
92    /// Disable DNSLink for this gateway
93    #[serde(default, rename = "NoDNSLink")]
94    pub no_dns_link: bool,
95
96    /// Inline DNSLink resolution
97    #[serde(
98        default,
99        skip_serializing_if = "Option::is_none",
100        rename = "InlineDNSLink"
101    )]
102    pub inline_dns_link: Option<Flag>,
103
104    /// Enable deserialized responses for this gateway
105    #[serde(default, skip_serializing_if = "Option::is_none")]
106    pub deserialized_responses: Option<Flag>,
107}
108
109impl Gateway {
110    /// Default paths for public gateways
111    pub fn default_paths() -> Vec<String> {
112        vec!["/ipfs".to_string(), "/ipns".to_string()]
113    }
114
115    /// Get default HTTP headers
116    pub fn default_http_headers() -> HashMap<String, Vec<String>> {
117        let mut headers = HashMap::new();
118        headers.insert(
119            "Access-Control-Allow-Origin".to_string(),
120            vec!["*".to_string()],
121        );
122        headers.insert(
123            "Access-Control-Allow-Methods".to_string(),
124            vec!["GET".to_string()],
125        );
126        headers.insert(
127            "Access-Control-Allow-Headers".to_string(),
128            vec![
129                "X-Requested-With".to_string(),
130                "Range".to_string(),
131                "User-Agent".to_string(),
132            ],
133        );
134        headers.insert(
135            "Access-Control-Expose-Headers".to_string(),
136            vec![
137                "Content-Range".to_string(),
138                "X-Chunked-Output".to_string(),
139                "X-Stream-Output".to_string(),
140            ],
141        );
142        headers
143    }
144}
145
146#[cfg(test)]
147mod tests {
148    use super::*;
149
150    #[test]
151    fn test_gateway_default() {
152        let gw = Gateway::default();
153        assert!(gw.http_headers.is_empty());
154        assert!(!gw.no_fetch);
155    }
156
157    #[test]
158    fn test_default_paths() {
159        let paths = Gateway::default_paths();
160        assert!(paths.contains(&"/ipfs".to_string()));
161        assert!(paths.contains(&"/ipns".to_string()));
162    }
163
164    #[test]
165    fn test_default_headers() {
166        let headers = Gateway::default_http_headers();
167        assert!(headers.contains_key("Access-Control-Allow-Origin"));
168    }
169}