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;
}