Skip to content

Commit 2a8eb4d

Browse files
chore: wip
1 parent e5af541 commit 2a8eb4d

2 files changed

Lines changed: 48 additions & 0 deletions

File tree

packages/zig/src/api/api.zig

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -147,6 +147,12 @@ pub const APIServer = struct {
147147
} else if (std.mem.startsWith(u8, path, "/email.mobileconfig")) {
148148
// Apple mail autoconfiguration
149149
try self.handleAutoconfig(stream, path, "GET", null);
150+
} else if (std.mem.startsWith(u8, path, "/.well-known/caldav")) {
151+
// CalDAV discovery (RFC 6764) - Apple Calendar, etc.
152+
try self.sendWellKnownRedirect(stream, "/calendars/");
153+
} else if (std.mem.startsWith(u8, path, "/.well-known/carddav")) {
154+
// CardDAV discovery (RFC 6764) - Apple Contacts, etc.
155+
try self.sendWellKnownRedirect(stream, "/addressbooks/");
150156
} else {
151157
try self.send404(stream);
152158
}
@@ -1520,6 +1526,17 @@ pub const APIServer = struct {
15201526
_ = try stream.write(response);
15211527
}
15221528

1529+
/// Send a 301 redirect for .well-known CalDAV/CardDAV discovery (RFC 6764)
1530+
fn sendWellKnownRedirect(self: *APIServer, stream: std.net.Stream, location: []const u8) !void {
1531+
const response = try std.fmt.allocPrint(
1532+
self.allocator,
1533+
"HTTP/1.1 301 Moved Permanently\r\nLocation: {s}\r\nContent-Length: 0\r\n\r\n",
1534+
.{location},
1535+
);
1536+
defer self.allocator.free(response);
1537+
_ = try stream.write(response);
1538+
}
1539+
15231540
/// Handle autoconfig requests (Thunderbird, Outlook, Apple)
15241541
fn handleAutoconfig(self: *APIServer, stream: std.net.Stream, path: []const u8, method: []const u8, request: ?[]const u8) !void {
15251542
_ = method;

packages/zig/src/api/autoconfig.zig

Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -79,23 +79,34 @@ pub const HttpResponse = struct {
7979
status_code: u16,
8080
content_type: []const u8,
8181
body: []const u8,
82+
location: ?[]const u8 = null,
8283
allocator: std.mem.Allocator,
8384

8485
pub fn deinit(self: *HttpResponse) void {
8586
self.allocator.free(self.body);
87+
if (self.location) |loc| self.allocator.free(loc);
8688
}
8789

8890
/// Format the response as a raw HTTP response string.
8991
pub fn toHttp(self: *const HttpResponse, allocator: std.mem.Allocator) ![]u8 {
9092
const status_text = switch (self.status_code) {
9193
200 => "OK",
94+
301 => "Moved Permanently",
9295
400 => "Bad Request",
9396
404 => "Not Found",
9497
405 => "Method Not Allowed",
9598
500 => "Internal Server Error",
9699
else => "Unknown",
97100
};
98101

102+
if (self.location) |loc| {
103+
return try std.fmt.allocPrint(
104+
allocator,
105+
"HTTP/1.1 {d} {s}\r\nLocation: {s}\r\nContent-Length: 0\r\nConnection: close\r\n\r\n",
106+
.{ self.status_code, status_text, loc },
107+
);
108+
}
109+
99110
return try std.fmt.allocPrint(
100111
allocator,
101112
"HTTP/1.1 {d} {s}\r\nContent-Type: {s}\r\nContent-Length: {d}\r\nCache-Control: no-cache, no-store\r\nConnection: close\r\n\r\n{s}",
@@ -239,6 +250,26 @@ pub const AutoconfigServer = struct {
239250
}
240251
}
241252

253+
// CalDAV/CardDAV well-known discovery (RFC 6764) - any method
254+
if (std.mem.startsWith(u8, path, "/.well-known/caldav")) {
255+
return HttpResponse{
256+
.status_code = 301,
257+
.content_type = "text/plain",
258+
.body = try self.allocator.dupe(u8, ""),
259+
.location = try self.allocator.dupe(u8, "/calendars/"),
260+
.allocator = self.allocator,
261+
};
262+
}
263+
if (std.mem.startsWith(u8, path, "/.well-known/carddav")) {
264+
return HttpResponse{
265+
.status_code = 301,
266+
.content_type = "text/plain",
267+
.body = try self.allocator.dupe(u8, ""),
268+
.location = try self.allocator.dupe(u8, "/addressbooks/"),
269+
.allocator = self.allocator,
270+
};
271+
}
272+
242273
// Method not allowed for known paths
243274
if (std.ascii.eqlIgnoreCase(path, "/autodiscover/autodiscover.xml")) {
244275
return HttpResponse{

0 commit comments

Comments
 (0)