diff options
Diffstat (limited to 'src/cmd_sie.c')
| -rw-r--r-- | src/cmd_sie.c | 171 |
1 files changed, 171 insertions, 0 deletions
diff --git a/src/cmd_sie.c b/src/cmd_sie.c new file mode 100644 index 0000000..d5b2e8e --- /dev/null +++ b/src/cmd_sie.c @@ -0,0 +1,171 @@ +#include "commands.h" +#include "cmd_util.h" + +#include <stdio.h> +#include <stdlib.h> +#include <string.h> + +#include "audit.h" +#include "config.h" +#include "db.h" +#include "sha256.h" +#include "sie.h" +#include "util.h" + +static int hash_file(const char *path, char hex[65], int64_t *size) +{ + FILE *f = fopen(path, "rb"); + if (!f) + return -1; + SHA256_CTX ctx; + sha256_init(&ctx); + unsigned char chunk[65536]; + size_t rn; + int64_t total = 0; + while ((rn = fread(chunk, 1, sizeof chunk, f)) > 0) { + sha256_update(&ctx, (const BYTE *)chunk, rn); + total += (int64_t)rn; + } + int bad = ferror(f); + fclose(f); + if (bad) + return -1; + unsigned char digest[32]; + sha256_final(&ctx, digest); + util_hex(digest, 32, hex); + if (size) + *size = total; + return 0; +} +static yyjson_mut_val *h_sie_export(struct req *r) +{ + int64_t fy = 0; + if (req_fy(r, &fy) != 0) + return NULL; + int inline_out = 0; + arg_bool(r->args, "inline", &inline_out); + + if (mkdir_p(g_cfg.export_dir, 0700) != 0) + return fail(r, "INTERNAL", "cannot create export directory"); + char label[64] = ""; + sqlite3_stmt *st = NULL; + if (sqlite3_prepare_v2( + r->db, + "SELECT label FROM fiscal_years WHERE org_id=?1 AND id=?2", -1, + &st, NULL) != SQLITE_OK) + return fail(r, "INTERNAL", "database error"); + sqlite3_bind_int64(st, 1, r->org_id); + sqlite3_bind_int64(st, 2, fy); + if (sqlite3_step(st) != SQLITE_ROW) { + sqlite3_finalize(st); + return fail(r, "NOT_FOUND", "fiscal year not found"); + } + snprintf(label, sizeof label, "%s", sqlite3_column_text(st, 0)); + sqlite3_finalize(st); + for (char *p = label; *p; p++) + if (*p == '/') + *p = '-'; + char path[4096]; + snprintf(path, sizeof path, "%s/%lld_%s.se", g_cfg.export_dir, + (long long)r->org_id, label); + char *err = NULL; + if (sie_export_file(r->db, r->org_id, fy, path, &err) != 0) { + yyjson_mut_val *e = fail(r, "INTERNAL", err ? err : "export failed"); + free(err); + return e; + } + char hex[65]; + int64_t size = 0; + if (hash_file(path, hex, &size) != 0) + return fail(r, "INTERNAL", "could not read exported file"); + + audit_append(r->db, r->org_id, r->sess->user_id, r->sess->token_id, + "sie.export", "{}", "OK", NULL); + yyjson_mut_val *o = yyjson_mut_obj(r->rdoc); + yyjson_mut_obj_add_int(r->rdoc, o, "fiscal_year_id", fy); + yyjson_mut_obj_add_strcpy(r->rdoc, o, "path", path); + yyjson_mut_obj_add_strcpy(r->rdoc, o, "sha256", hex); + yyjson_mut_obj_add_int(r->rdoc, o, "size", size); + if (inline_out) { + size_t flen = 0; + unsigned char *content = read_file(path, &flen); + if (!content) + return fail(r, "INTERNAL", "could not read exported file"); + char *b64 = util_b64(content, flen); + yyjson_mut_obj_add_strcpy(r->rdoc, o, "content_base64", b64); + free(b64); + free(content); + } + return o; +} + +static yyjson_mut_val *h_sie_import(struct req *r) +{ + const char *b64 = arg_str(r->args, "content_base64"); + const char *path = arg_str(r->args, "path"); + unsigned char *content = NULL; + size_t len = 0; + if (b64) { + if (util_b64_decode(b64, strlen(b64), &content, &len) != 0) + return fail(r, "INVALID_ARGS", + "content_base64 is not valid base64"); + } else if (path) { + content = read_file(path, &len); + if (!content) + return fail(r, "NOT_FOUND", "could not read file"); + } else { + return fail(r, "INVALID_ARGS", "content_base64 or path is required"); + } + if (len > 64u * 1024u * 1024u) { + free(content); + return fail(r, "TOO_LARGE", "SIE file is too large"); + } + int64_t fy = 0, vouchers = 0, accounts = 0; + char *err = NULL; + int rc = sie_import_content(r->db, r->org_id, r->sess->user_id, content, + len, r->dry_run, &fy, &vouchers, &accounts, + &err); + free(content); + if (rc != 0) { + yyjson_mut_val *e = fail(r, "INVALID_ARGS", err ? err : "import failed"); + free(err); + return e; + } + if (!r->dry_run) { + char *reqjson = audit_args_json(r->args); + audit_append(r->db, r->org_id, r->sess->user_id, r->sess->token_id, + "sie.import", reqjson, "OK", NULL); + free(reqjson); + } + yyjson_mut_val *o = yyjson_mut_obj(r->rdoc); + yyjson_mut_obj_add_int(r->rdoc, o, "fiscal_year_id", fy); + yyjson_mut_obj_add_int(r->rdoc, o, "vouchers", vouchers); + yyjson_mut_obj_add_int(r->rdoc, o, "accounts_created", accounts); + if (r->dry_run) + yyjson_mut_obj_add_bool(r->rdoc, o, "dry_run", true); + return o; +} + + +static const struct cmd_arg args_sie_export[] = { + { "fiscal_year", ARG_INT, 0, NULL, NULL, + "Fiscal year id; defaults to the latest" }, + { "inline", ARG_BOOL, 0, "false", NULL, "Also return content_base64" }, +}; + +static const struct cmd_arg args_sie_import[] = { + { "content_base64", ARG_STR, 0, NULL, NULL, "SIE file content, base64" }, + { "path", ARG_STR, 0, NULL, NULL, + "Server-side path; alternative to content_base64" }, +}; + +const struct command g_cmd_sie[] = { + { "sie.export", "Export SIE 4 (PC8)", PERM_READ, 1, 0, 0, h_sie_export, + CMD_ARGS(args_sie_export) }, + { "sie.import", "Import SIE 4 into an empty fiscal year", PERM_WRITE, 1, 1, + 1, h_sie_import, CMD_ARGS(args_sie_import) }, +}; + +const struct cmd_table g_cmd_table_sie = { + g_cmd_sie, sizeof g_cmd_sie / sizeof g_cmd_sie[0] +}; |
