~phroa/jmap

b8744684bc6577150f5ea4bbfe6f0179e0fb1041 — Jack Stratton 5 months ago eed1c2a
Add enough code to log in
2 files changed, 112 insertions(+), 0 deletions(-)

M .gitignore
M src/main.zig
M .gitignore => .gitignore +1 -0
@@ 3,3 3,4 @@ zig-out
gyro.lock
.gyro
deps.zig
.env

M src/main.zig => src/main.zig +111 -0
@@ 1,4 1,9 @@
const std = @import("std");
const base64 = std.base64;
const fmt = std.fmt;
const json = std.json;
const mem = std.mem;
const os = std.os;
const zfetch = @import("zfetch");
const testing = std.testing;



@@ 6,6 11,112 @@ export fn add(a: i32, b: i32) i32 {
    return a + b;
}

const Capability = json.ObjectMap;

const Account = struct {
    const name: []const u8;
    const isPersonal: bool;
    const isReadOnly: bool;
    const accountCapabilities: []*Capability;
};

const Session = struct {
    const capabilities: []*Capability;
    const accounts: []*Account;
    const primaryAccounts: []*Account;
    const username: []const u8;
    const apiUrl: []const u8;
    const downloadUrl: []const u8;
    const uploadUrl: []const u8;
    const eventSourceUrl: []const u8;
    const state: []const u8;
};

/// TODO server autodiscovery
fn getLoginUrl(allocator: *mem.Allocator, baseUri: []const u8) ![]u8 {
    if (baseUri[baseUri.len - 1] == '/') {
        return fmt.allocPrint(allocator, "{s}.well-known/jmap", .{baseUri});
    } else {
        return fmt.allocPrint(allocator, "{s}/.well-known/jmap", .{baseUri});
    }
}

test "getLoginUrl" {
    const a = try getLoginUrl(testing.allocator, "https://example.com");
    defer testing.allocator.free(a);
    const b = try getLoginUrl(testing.allocator, "https://example.com/");
    defer testing.allocator.free(b);

    try testing.expectEqualStrings("https://example.com/.well-known/jmap", a);
    try testing.expectEqualStrings("https://example.com/.well-known/jmap", b);
}

fn basicAuthString(allocator: *mem.Allocator, username: []const u8, password: []const u8) ![]u8 {
    const colon = mem.indexOf(u8, username, ":");
    if (colon != null) {
        return error.ColonInUsername;
    }

    // Add 1 for colon
    const inLen = username.len + 1 + password.len;
    const b64Len = base64.standard.Encoder.calcSize(inLen);
    var inBuf = try fmt.allocPrint(allocator, "{s}:{s}", .{ username, password });
    defer allocator.free(inBuf);
    var b64Buf = try allocator.alloc(u8, b64Len);
    defer allocator.free(b64Buf);

    const b64Res = base64.standard.Encoder.encode(b64Buf, inBuf);
    return fmt.allocPrint(allocator, "Basic {s}", .{b64Res});
}

fn login(allocator: *mem.Allocator, server: []const u8, username: []const u8, password: []const u8) !Session {
    const loginUrl = try getLoginUrl(allocator, server);
    defer allocator.free(loginUrl);

    var headers = zfetch.Headers.init(allocator);
    defer headers.deinit();

    const auth = try basicAuthString(allocator, username, password);
    defer allocator.free(auth);

    try headers.appendValue("Authorization", auth);
    try headers.appendValue("Accept", "application/json");

    var request = try zfetch.Request.init(allocator, loginUrl, null);
    defer request.deinit();

    try request.do(.GET, headers, null);

    const stdout = std.io.getStdOut().writer();

    try stdout.print("status: {d} {s}\n", .{ request.status.code, request.status.reason });
    try stdout.print("headers:\n", .{});
    for (request.headers.list.items) |header| {
        try stdout.print("  {s}: {s}\n", .{ header.name, header.value });
    }
    try stdout.print("body:\n", .{});

    const reader = request.reader();

    var buf: [1024]u8 = undefined;
    while (true) {
        const read = try reader.read(&buf);
        if (read == 0) break;

        try stdout.writeAll(buf[0..read]);
    }

    return Session{};
}

test "login" {
    const host = os.getenv("JMAP_HOST") orelse return error.MissingConfig;
    const user = os.getenv("JMAP_USER") orelse return error.MissingConfig;
    const pass = os.getenv("JMAP_PASS") orelse return error.MissingConfig;

    const session = try login(testing.allocator, host, user, pass);
}

test "basic add functionality" {
    try testing.expect(add(3, 7) == 10);
}