Skip to content

Commit 891edff

Browse files
committed
Add "trust proxy" setting and req.path/host/ip/etc
Useful.
1 parent c0cfc8c commit 891edff

File tree

2 files changed

+99
-16
lines changed

2 files changed

+99
-16
lines changed

Sources/express/IncomingMessage.swift

Lines changed: 81 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -3,26 +3,98 @@
33
// Noze.io / Macro
44
//
55
// Created by Helge Heß on 6/2/16.
6-
// Copyright © 2016-2024 ZeeZide GmbH. All rights reserved.
6+
// Copyright © 2016-2026 ZeeZide GmbH. All rights reserved.
77
//
88

99
#if canImport(Foundation)
1010
import Foundation
1111
#endif
12-
import class http.IncomingMessage
12+
import http
1313
import NIOHTTP1
14-
14+
import NIOCore
1515

1616
public extension IncomingMessage {
1717

1818
typealias Params = ExpressWrappedDictionary<String>
1919
typealias Query = ExpressWrappedDictionary<Any>
2020

21-
// TODO: baseUrl, originalUrl, path
22-
// TODO: hostname, ip, ips, protocol
21+
/// The hostname from the `Host` header, or
22+
/// `X-Forwarded-Host` when trust proxy is enabled.
23+
@inlinable
24+
var hostname: String? {
25+
let host: String
26+
if app?.settings.trustProxy ?? false,
27+
let fh = headers["X-Forwarded-Host"].first
28+
{
29+
if let i = fh.firstIndex(of: ",") { host = String(fh[..<i]) }
30+
else { host = fh }
31+
}
32+
else if let h = headers["Host"].first { host = h }
33+
else { return nil }
34+
35+
// IPv6 bracket notation: strip port after `]`
36+
if host.hasPrefix("[") {
37+
guard let b = host.firstIndex(of: "]") else { return host }
38+
let a = host.index(after: b)
39+
if a < host.endIndex, host[a] == ":" { return String(host[..<a]) }
40+
return String(host[...b])
41+
}
42+
if let i = host.firstIndex(of: ":") {
43+
return String(host[..<i])
44+
}
45+
return host
46+
}
2347

24-
// TODO: originalUrl, path
25-
// TODO: hostname, ip, ips, protocol
48+
/// The client IP. Uses `X-Forwarded-For` when trust
49+
/// proxy is enabled.
50+
@inlinable
51+
var ip: String? {
52+
if app?.settings.trustProxy ?? false,
53+
let xff = headers["X-Forwarded-For"].first
54+
{
55+
if let i = xff.firstIndex(of: ",") { return String(xff[..<i]) }
56+
return xff
57+
}
58+
return socket?.remoteAddress?.ipAddress
59+
}
60+
61+
/// All IPs from `X-Forwarded-For` when trust proxy
62+
/// is enabled, empty otherwise.
63+
@inlinable
64+
var ips: [ String ] {
65+
guard app?.settings.trustProxy ?? false,
66+
let xff = headers["X-Forwarded-For"].first else { return [] }
67+
return xff.split(separator: ",").map { part in
68+
String(part.drop(while: { $0.isWhitespace }))
69+
}
70+
}
71+
72+
/**
73+
* `"https"` when behind a trusted proxy setting
74+
* `X-Forwarded-Proto`, `"http"` otherwise.
75+
*/
76+
@inlinable
77+
var `protocol`: String {
78+
if app?.settings.trustProxy ?? false,
79+
let proto = headers["X-Forwarded-Proto"].first
80+
{
81+
if let i = proto.firstIndex(of: ",") {
82+
return String(proto[..<i])
83+
}
84+
return proto
85+
}
86+
return "http"
87+
}
88+
89+
/// The URL path without the query string.
90+
@inlinable
91+
var path: String {
92+
guard let qIdx = url.firstIndex(of: "?") else {
93+
return url.isEmpty ? "/" : url
94+
}
95+
let p = String(url[..<qIdx])
96+
return p.isEmpty ? "/" : p
97+
}
2698

2799
/// A reference to the active application. Updated when subapps are triggered.
28100
var app : Express? { return environment[ExpressExtKey.App.self] }
@@ -62,12 +134,12 @@ public extension IncomingMessage {
62134
// FIXME: improve parser (fragments?!)
63135
// TBD: just use Foundation?!
64136
guard let idx = url.firstIndex(of: "?") else {
65-
environment[ExpressExtKey.Query.self] = .init([:])
137+
environment[ExpressExtKey.Query.self] = Query([:])
66138
return Query([:])
67139
}
68140
let q = url[url.index(after: idx)...]
69141
let qp = qs.parse(String(q))
70-
environment[ExpressExtKey.Query.self] = .init(qp)
142+
environment[ExpressExtKey.Query.self] = Query(qp)
71143
return Query(qp)
72144
}
73145

Sources/express/Settings.swift

Lines changed: 18 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@
33
// Noze.io / Macro
44
//
55
// Created by Helge Heß on 02/06/16.
6-
// Copyright © 2016-2025 ZeeZide GmbH. All rights reserved.
6+
// Copyright © 2016-2026 ZeeZide GmbH. All rights reserved.
77
//
88

99
/**
@@ -181,7 +181,8 @@ public extension ExpressSettings {
181181
*
182182
* This first checks for an explicit `env` setting in the SettingsHolder
183183
* (i.e. Express application object).
184-
* If that's missing, it checks the `MACRO_ENV` environment variable.
184+
* If that's missing, it checks the `EXPRESS_ENV` and `MACRO_ENV` environment
185+
* variable.
185186
*/
186187
@inlinable
187188
var env : String {
@@ -202,22 +203,32 @@ public extension ExpressSettings {
202203
*/
203204
@inlinable
204205
var xPoweredBy : Bool {
205-
guard let v = holder.get("x-powered-by") else { return true }
206-
return boolValue(v)
206+
set { holder.set("x-powered-by", true) }
207+
get { return boolValue(holder.get("x-powered-by")) }
208+
}
209+
210+
/// Whether to trust `X-Forwarded-*` proxy headers.
211+
@inlinable
212+
var trustProxy : Bool {
213+
set { holder.set("trust proxy", true) }
214+
get { return boolValue(holder.get("trust proxy")) }
207215
}
208216
}
209217

210218

211219
// MARK: - Helpers
212220

213221
@usableFromInline
214-
func boolValue(_ v : Any) -> Bool {
222+
func boolValue(_ v : Any?) -> Bool {
215223
// TODO: this should be some Foundation like thing
216-
if let b = v as? Bool { return b }
217-
if let b = v as? Int { return b != 0 }
224+
guard let v = v else { return false }
225+
if let b = v as? Bool { return b }
226+
if let b = v as? Int { return b != 0 }
227+
218228
#if swift(>=5.10)
219229
if let i = (v as? any BinaryInteger) { return Int(i) != 0 }
220230
#endif
231+
221232
if let s = v as? String {
222233
switch s.lowercased() {
223234
case "no", "false", "0", "disable": return false

0 commit comments

Comments
 (0)