const std = @import("std"); const clap = @import("clap"); const curl = @cImport({ @cInclude("curl/curl.h"); }); const uri = @import("uri"); const json = @import("json"); const log = std.log.scoped(.b2dlkeygen); const params = clap.parseParamsComptime( \\-h, --help Display this help and exit. \\-c, --config <FILE> Read application config from FILE. ); fn printFullUsage(w: anytype) !void { _ = try w.print("{s} ", .{std.os.argv[0]}); try clap.usage(w, clap.Help, ¶ms); _ = try w.writeByte('\n'); try clap.help(w, clap.Help, ¶ms, .{}); return; } var curlerr = [_:0]u8{0} ** (curl.CURL_ERROR_SIZE); fn curlErrorReport(str: []const u8, code: curl.CURLcode) void { log.err("{s}: {s} {s}", .{ str, curl.curl_easy_strerror(code), curlerr[0.. :0] }); } const strList = std.ArrayList([]const u8); const str0List = std.ArrayList([:0]const u8); fn Deoptional(comptime t: type) type { const ti = @typeInfo(t); return switch (ti) { .Optional => |o| o.child, else => t, }; } pub fn unwrap( un: anytype, comptime tag: std.meta.Tag(Deoptional(@TypeOf(un))), ) ?std.meta.TagPayload(Deoptional(@TypeOf(un)), tag) { if (@typeInfo(@TypeOf(un)) == .Optional) { if (un == null) return null; if (un.? != tag) return null; return @field(un.?, @tagName(tag)); } else { if (un != tag) return null; return @field(un, @tagName(tag)); } } const Config = struct { key: [*:0]const u8, pre: []const u8, post: []const u8, output: []const u8, pub fn newFromFile(path: []const u8, alloc: std.mem.Allocator) !Config { const file = std.fs.cwd().openFile(path, .{ .mode = .read_only }) catch |e| { log.err("Error opening file: {s}\n", .{e}); std.os.exit(2); }; const buf = try file.readToEndAlloc(alloc, 100000); defer alloc.free(buf); var config: Config = undefined; var obj = try json.parse(alloc, buf); defer { if (unwrap(obj, .Object)) |o| alloc.free(o); } if (unwrap(obj.get(.{"key"}), .String)) |key| { config.key = try alloc.dupeZ(u8, key); } else { log.err("No key in config, or not a string.", .{}); return error.ConfigError; } if (unwrap(obj.get(.{"pre"}), .String)) |pre| { config.pre = try alloc.dupe(u8, pre); } else { log.err("No pre in config, or not a string.", .{}); return error.ConfigError; } if (unwrap(obj.get(.{"post"}), .String)) |post| { config.post = try alloc.dupe(u8, post); } else { log.err("No post in config, or not a string.", .{}); return error.ConfigError; } if (unwrap(obj.get(.{"output"}), .String)) |output| { config.output = try alloc.dupe(u8, output); } else { log.err("No output in config, or not a string.", .{}); return error.ConfigError; } return config; } }; pub fn main() anyerror!void { var gpa = std.heap.GeneralPurposeAllocator(.{}){}; const alloc = gpa.allocator(); std.log.info("All your codebase are belong to us.", .{}); const parsers = comptime .{ .PATH = clap.parsers.string, .FILE = clap.parsers.string, .NAME = clap.parsers.string, .REGEX = clap.parsers.string, .ID = clap.parsers.int(i64, 10), }; var diag = clap.Diagnostic{}; var clap_res = clap.parse(clap.Help, ¶ms, parsers, .{ .diagnostic = &diag, .allocator = alloc, }) catch |err| { diag.report(std.io.getStdErr().writer(), err) catch {}; return err; }; defer clap_res.deinit(); var args = clap_res.args; if (args.help) { var w = std.io.getStdOut().writer(); try printFullUsage(w); return; } if (args.config == null) { log.err("Please specify a config file with --config.", .{}); std.os.exit(3); } const cfile = args.config.?; var ret = curl.curl_global_init(curl.CURL_GLOBAL_ALL); if (ret != curl.CURLE_OK) { log.err("cURL global init failure: {s}", .{curl.curl_easy_strerror(ret)}); return; } defer curl.curl_global_cleanup(); const handle = curl.curl_easy_init() orelse return error.CURLHandleInitFailed; defer curl.curl_easy_cleanup(handle); var response_buffer = std.ArrayList(u8).init(alloc); defer response_buffer.deinit(); _ = curl.curl_easy_setopt(handle, curl.CURLOPT_ERRORBUFFER, &curlerr); // try easyFetch(handle, "http://cloud/foo.jpeg", &response_buffer); // var dir = try std.fs.cwd().makeOpenPath(".", .{ // .access_sub_paths = true, // }); // var file = try dir.createFile( // "test.foo", // .{ // .read = false, // .truncate = true, // }, // ); // defer file.close(); // try file.writeAll(response_buffer.items); const conf = try Config.newFromFile(cfile, alloc); var api = try getAuthToken(handle, conf.key, alloc); try getDlToken(handle, &api, alloc); var file: std.fs.File = try std.fs.cwd().createFile(conf.output, .{ .read = false, .truncate = true }); defer file.close(); var w = file.writer(); try w.print("{s}{s}{s}\n", .{ conf.pre, api.dl_auth.?, conf.post }); } const B2API = struct { auth: [*:0]const u8, url: [*:0]const u8, dl_url: [*:0]const u8, dl_auth: ?[]const u8, }; fn getAuthToken(handle: *curl.CURL, key: [*:0]const u8, alloc: std.mem.Allocator) !B2API { _ = curl.curl_easy_setopt(handle, curl.CURLOPT_URL, "https://api.backblazeb2.com/b2api/v2/b2_authorize_account"); _ = curl.curl_easy_setopt(handle, curl.CURLOPT_USERPWD, key); _ = curl.curl_easy_setopt(handle, curl.CURLOPT_WRITEFUNCTION, writeToArrayListCallback); var resp = std.ArrayList(u8).init(alloc); defer resp.deinit(); _ = curl.curl_easy_setopt(handle, curl.CURLOPT_WRITEDATA, resp); _ = curl.curl_easy_setopt(handle, curl.CURLOPT_USERAGENT, "b2keygen 0.1 (linux)"); _ = curl.curl_easy_perform(handle); var val = try json.parse(alloc, resp.items); var autho = val.get(.{"authorizationToken"}); var urlo = val.get(.{"apiUrl"}); var urlo2 = val.get(.{"downloadUrl"}); if (autho == null or urlo == null or urlo2 == null) { return error.ShitsFucked; } var foo: B2API = undefined; foo.auth = try std.fmt.allocPrintZ(alloc, "Authorization: {s}", .{autho.?.String}); foo.url = try alloc.dupeZ(u8, urlo.?.String); foo.dl_url = try alloc.dupeZ(u8, urlo2.?.String); foo.dl_auth = null; return foo; } fn getDlToken(handle: *curl.CURL, api: *B2API, alloc: std.mem.Allocator) !void { const url = try std.fmt.allocPrintZ(alloc, "{s}/b2api/v2/b2_get_download_authorization", .{api.url}); //const url: [:0]const u8 = "http://localhost:6666"; defer alloc.free(url); var resp = std.ArrayList(u8).init(alloc); defer resp.deinit(); var bar = curl.curl_slist_append(null, api.auth); bar = curl.curl_slist_append(bar, "Content-Type: application/json"); defer curl.curl_slist_free_all(bar); _ = curl.curl_easy_setopt(handle, curl.CURLOPT_URL, url.ptr); _ = curl.curl_easy_setopt(handle, curl.CURLOPT_WRITEFUNCTION, writeToArrayListCallback); _ = curl.curl_easy_setopt(handle, curl.CURLOPT_WRITEDATA, resp); _ = curl.curl_easy_setopt(handle, curl.CURLOPT_HTTPHEADER, bar); _ = curl.curl_easy_setopt(handle, curl.CURLOPT_USERAGENT, "b2keygen 0.1 (linux)"); _ = curl.curl_easy_setopt(handle, curl.CURLOPT_POST, @as(u32, 1)); var buffer: [1024]u8 = undefined; var aaa = std.io.fixedBufferStream(buffer[0..]).writer(); var asd = [_]json.Member{ .{ .key = "bucketId", .value = json.Value{ .String = "1b191ebe58f4fa1876bc031d" } }, .{ .key = "fileNamePrefix", .value = json.Value{ .String = "" } }, .{ .key = "validDurationInSeconds", .value = json.Value{ .Int = 259200 } }, }; const data = json.Value{ .Object = asd[0..], }; try data.format("", .{}, aaa); buffer[aaa.context.pos] = 0; const jason = @ptrCast([*:0]const u8, buffer[0..aaa.context.pos].ptr); _ = curl.curl_easy_setopt(handle, curl.CURLOPT_POSTFIELDS, jason); _ = curl.curl_easy_setopt(handle, curl.CURLOPT_POSTFIELDSIZE, aaa.context.pos); _ = curl.curl_easy_perform(handle); var val = try json.parse(alloc, resp.items); var autho = val.get(.{"authorizationToken"}); if (autho == null) { return error.ShitsFucked2; } api.dl_auth = try alloc.dupe(u8, autho.?.String); } fn easyFetch(handle: *curl.CURL, url: [*:0]const u8, resp: *std.ArrayList(u8)) !void { // if (fetch_wait > 0) { // if (fetch_timer) |*timer| { // const cur = timer.read() / (1000 * 1000); // if (cur < fetch_wait) { // std.time.sleep((fetch_wait - cur) * 1000 * 1000); // } // timer.reset(); // } else { // fetch_timer = try std.time.Timer.start(); // } // } var ret = curl.curl_easy_setopt(handle, curl.CURLOPT_URL, url); if (ret != curl.CURLE_OK) { curlErrorReport("cURL set url:", ret); return error.CurlError; } ret = curl.curl_easy_setopt(handle, curl.CURLOPT_WRITEFUNCTION, writeToArrayListCallback); if (ret != curl.CURLE_OK) { curlErrorReport("cURL set writefunction:", ret); return error.CurlError; } ret = curl.curl_easy_setopt(handle, curl.CURLOPT_WRITEDATA, resp); if (ret != curl.CURLE_OK) { curlErrorReport("cURL set writedata:", ret); return error.CurlError; } ret = curl.curl_easy_setopt(handle, curl.CURLOPT_USERAGENT, "b2backup 0.1 (linux)"); if (ret != curl.CURLE_OK) { curlErrorReport("cURL set user agent:", ret); return error.CurlError; } ret = curl.curl_easy_perform(handle); if (ret != curl.CURLE_OK) { curlErrorReport("cURL perform:", ret); return error.CurlError; } log.info("Got {d} bytes", .{resp.items.len}); } fn writeToArrayListCallback( data: *anyopaque, size: c_uint, nmemb: c_uint, user_data: *anyopaque, ) callconv(.C) c_uint { var buffer = @intToPtr(*std.ArrayList(u8), @ptrToInt(user_data)); var typed_data = @ptrCast([*]u8, data); buffer.appendSlice(typed_data[0 .. nmemb * size]) catch return 0; return nmemb * size; }