#include "common/printing.h" #include "common/jsonconfig.h" #include "modules/command/command.h" #include "detection/command/command.h" #if defined(_WIN32) // `ffStrbufGetline()` splits on `\n` only, so a CRLF line keeps its trailing `\r`. That is what every // `cmd.exe` builtin and most native Windows tools produce, and the leftover carriage return makes the // terminal jump back to the start of the line. The `\r` is overwritten with a NUL, so `line` stays a // usable C string; the length without the `\r` is returned for callers that need to copy the line. // // `len` itself must stay the distance from the line start to the delimiter: `ffStrbufGetdelim()` // advances with `*lineptr += *n`, so shortening it would make the next call start inside this line. static size_t trimCarriageReturn(char* line, size_t len) { if (len > 0 && line[len - 1] == '\r') { line[len - 1] = '\0'; return len - 1; } return len; } #endif bool ffPrintCommand(FFCommandOptions* options) { FF_STRBUF_AUTO_DESTROY result = ffStrbufCreate(); const char* error = ffDetectCommand(options, &result); if (error) { ffPrintError(FF_MODULE_GET_DISPLAY_NAME(Command), 0, &options->moduleArgs, FF_PRINT_TYPE_DEFAULT, "%s", error); return false; } if (!result.length) { ffPrintError(FF_MODULE_GET_DISPLAY_NAME(Command), 0, &options->moduleArgs, FF_PRINT_TYPE_DEFAULT, "No result generated"); return false; } if (options->splitLines) { uint32_t index = 0; char* line = nullptr; size_t len = 0; while (ffStrbufGetline(&line, &len, &result)) { #if defined(_WIN32) trimCarriageReturn(line, len); #endif if (options->moduleArgs.outputFormat.length == 0) { ffPrintLogoAndKey(FF_MODULE_GET_DISPLAY_NAME(Command), ++index, &options->moduleArgs, FF_PRINT_TYPE_DEFAULT); puts(line); } else { FF_PRINT_FORMAT_CHECKED(FF_MODULE_GET_DISPLAY_NAME(Command), ++index, &options->moduleArgs, FF_PRINT_TYPE_DEFAULT, ((FFformatarg[]) { FF_ARG(line, "result") })); } } } else { if (options->moduleArgs.outputFormat.length == 0) { ffPrintLogoAndKey(FF_MODULE_GET_DISPLAY_NAME(Command), 0, &options->moduleArgs, FF_PRINT_TYPE_DEFAULT); ffStrbufPutTo(&result, stdout); } else { FF_PRINT_FORMAT_CHECKED(FF_MODULE_GET_DISPLAY_NAME(Command), 0, &options->moduleArgs, FF_PRINT_TYPE_DEFAULT, ((FFformatarg[]) { FF_ARG(result, "result") })); } } return true; } void ffParseCommandJsonObject(FFCommandOptions* options, yyjson_val* module) { yyjson_val *key, *val; size_t idx, max; yyjson_obj_foreach (module, idx, max, key, val) { if (ffJsonConfigParseModuleArgs(key, val, &options->moduleArgs)) { continue; } if (unsafe_yyjson_equals_str(key, "shell")) { ffStrbufSetJsonVal(&options->shell, val); continue; } if (unsafe_yyjson_equals_str(key, "param")) { ffStrbufSetJsonVal(&options->param, val); continue; } if (unsafe_yyjson_equals_str(key, "text")) { ffStrbufSetJsonVal(&options->text, val); continue; } if (unsafe_yyjson_equals_str(key, "useStdErr")) { options->useStdErr = yyjson_get_bool(val); continue; } if (unsafe_yyjson_equals_str(key, "parallel")) { options->parallel = yyjson_get_bool(val); continue; } if (unsafe_yyjson_equals_str(key, "splitLines")) { options->splitLines = yyjson_get_bool(val); continue; } ffPrintError(FF_MODULE_GET_DISPLAY_NAME(Command), 0, &options->moduleArgs, FF_PRINT_TYPE_DEFAULT, "Unknown JSON key %s", unsafe_yyjson_get_str(key)); } } [[gnu::cold]] void ffGenerateCommandJsonConfig(FFCommandOptions* options, yyjson_mut_doc* doc, yyjson_mut_val* module) { ffJsonConfigGenerateModuleArgsConfig(doc, module, &options->moduleArgs); yyjson_mut_obj_add_strbuf(doc, module, "shell", &options->shell); yyjson_mut_obj_add_strbuf(doc, module, "param", &options->param); yyjson_mut_obj_add_strbuf(doc, module, "text", &options->text); yyjson_mut_obj_add_bool(doc, module, "useStdErr", options->useStdErr); yyjson_mut_obj_add_bool(doc, module, "parallel", options->parallel); yyjson_mut_obj_add_bool(doc, module, "splitLines", options->splitLines); } bool ffGenerateCommandJsonResult([[maybe_unused]] FFCommandOptions* options, yyjson_mut_doc* doc, yyjson_mut_val* module) { FF_STRBUF_AUTO_DESTROY result = ffStrbufCreate(); const char* error = ffDetectCommand(options, &result); if (error) { yyjson_mut_obj_add_str(doc, module, "error", error); return false; } if (!result.length) { yyjson_mut_obj_add_str(doc, module, "error", "No result generated"); return false; } if (options->splitLines) { yyjson_mut_val* jsonArray = yyjson_mut_obj_add_arr(doc, module, "result"); char* line = nullptr; size_t len = 0; while (ffStrbufGetline(&line, &len, &result)) { // `len` has to stay the distance to the delimiter (see `trimCarriageReturn`), so the // possibly shorter length of the line itself is tracked separately. #if defined(_WIN32) size_t contentLen = trimCarriageReturn(line, len); #else size_t contentLen = len; #endif yyjson_mut_arr_add_strncpy(doc, jsonArray, line, contentLen); } } else { yyjson_mut_obj_add_strbuf(doc, module, "result", &result); } return true; } void ffInitCommandOptions(FFCommandOptions* options) { ffOptionInitModuleArg(&options->moduleArgs, ""); ffStrbufInitStatic(&options->shell, #ifdef _WIN32 "cmd.exe" #else "/bin/sh" #endif ); ffStrbufInitStatic(&options->param, #ifdef _WIN32 "/c" #else "-c" #endif ); ffStrbufInit(&options->text); options->useStdErr = false; options->parallel = true; options->splitLines = false; } void ffDestroyCommandOptions(FFCommandOptions* options) { ffOptionDestroyModuleArg(&options->moduleArgs); ffStrbufDestroy(&options->shell); ffStrbufDestroy(&options->param); ffStrbufDestroy(&options->text); } FFModuleBaseInfo ffCommandModuleInfo = { .name = "Command", .description = "Run custom shell scripts", .displayName = { .en = "Command", .ar = "الأمر", .cs = "Příkaz", .de = "Befehl", .es = "Comando", .fi = "Komento", .fr = "Commande", .gl = "Comando", .he = "פקודה", .id = "Perintah", .it = "Comando", .ja = "コマンド", .ko = "명령어", .pl = "Polecenie", .pt = "Comando", .ru = "Команда", .tr = "Komut", .uk = "Команда", .vi = "Lệnh", .zh_CN = "命令", .zh_TW = "命令", }, .initOptions = (void*) ffInitCommandOptions, .destroyOptions = (void*) ffDestroyCommandOptions, .parseJsonObject = (void*) ffParseCommandJsonObject, .printModule = (void*) ffPrintCommand, .generateJsonResult = (void*) ffGenerateCommandJsonResult, .generateJsonConfig = (void*) ffGenerateCommandJsonConfig, .formatArgs = FF_FORMAT_ARG_LIST(((FFModuleFormatArg[]) { { "Command result", "result" }, })), };