handle client ip without forwarding headers
All checks were successful
Generic zig build / build (push) Successful in 1m17s
Generic zig build / deploy (push) Successful in 13s

This commit is contained in:
Emil Lerch 2026-01-05 16:13:59 -08:00
parent bf4ea51424
commit 6e829eea30
Signed by: lobo
GPG key ID: A7B62D657EF764F8
2 changed files with 27 additions and 19 deletions

View file

@ -21,6 +21,17 @@ pub fn handleWeather(
req: *httpz.Request,
res: *httpz.Response,
) !void {
var client_ip_buf: [47]u8 = undefined;
// We need IP possibly for location, and possibly to determine implicit Imperial/Metric
// Let's get it once here
const client_ip = blk: {
const client_ip = getClientIpFromHeaders(req);
if (client_ip.len == 0) {
const full_location = try std.fmt.bufPrint(&client_ip_buf, "{f}", .{req.conn.address});
break :blk full_location[0..std.mem.lastIndexOf(u8, full_location, ":").?];
}
break :blk client_ip;
};
// Get location from path parameter or query string
const location = req.param("location") orelse blk: {
// Check query string for location parameter
@ -33,14 +44,14 @@ pub fn handleWeather(
if (params.location) |loc| {
break :blk loc;
} else {
// Fall back to IP-based detection
const client_ip = getClientIP(req);
std.log.debug("No location requested, using IP address '{s}'", .{client_ip});
break :blk client_ip;
}
} else break :blk client_ip; // no location, just use client ip instead
};
if (location.len == 0) {
res.content_type = .TEXT;
res.body = "Sorry, we are unable to determine your location at this time. Try with /<location> or /?location=<location>\n";
return;
}
// Handle special endpoints
if (location[0] == ':') {
if (std.mem.eql(u8, location, ":help")) {
@ -54,10 +65,10 @@ pub fn handleWeather(
}
}
try handleWeatherInternal(opts, req, res, location);
try handleWeatherInternal(opts, req, res, location, client_ip);
}
fn getClientIP(req: *httpz.Request) []const u8 {
fn getClientIpFromHeaders(req: *httpz.Request) []const u8 {
// Check X-Forwarded-For header first (for proxies)
if (req.header("x-forwarded-for")) |xff| {
return parseXForwardedFor(xff);
@ -68,7 +79,7 @@ fn getClientIP(req: *httpz.Request) []const u8 {
return real_ip;
}
// Fall back to empty (no IP available)
// Fall back to client connection
return "";
}
@ -86,6 +97,7 @@ fn handleWeatherInternal(
req: *httpz.Request,
res: *httpz.Response,
location_query: []const u8,
client_ip: []const u8,
) !void {
const req_alloc = req.arena;
@ -151,12 +163,8 @@ fn handleWeatherInternal(
break :blk true;
}
}
const client_ip = getClientIP(req);
if (client_ip.len > 0) {
if (opts.geoip.isUSIP(client_ip)) {
break :blk true;
}
}
if (client_ip.len > 0 and opts.geoip.isUSIp(client_ip))
break :blk true;
break :blk false;
};

View file

@ -89,7 +89,7 @@ fn lookupInternal(mmdb: *c.MMDB_s, ip: []const u8) !c.MMDB_lookup_result_s {
return result;
}
pub fn isUSIP(self: *GeoIP, ip: []const u8) bool {
pub fn isUSIp(self: *GeoIP, ip: []const u8) bool {
const result = lookupInternal(&self.mmdb, ip) catch return false;
if (!result.found_entry) return false;
@ -156,10 +156,10 @@ test "isUSIP detects US IPs" {
defer geoip.deinit();
// Test that the function doesn't crash with various IPs
_ = geoip.isUSIP("8.8.8.8");
_ = geoip.isUSIP("1.1.1.1");
_ = geoip.isUSIp("8.8.8.8");
_ = geoip.isUSIp("1.1.1.1");
// Test invalid IP returns false
const invalid = geoip.isUSIP("invalid");
const invalid = geoip.isUSIp("invalid");
try std.testing.expect(!invalid);
}