From f1b1d919dcdeafbb64f8647a4a61880b3015e1f4 Mon Sep 17 00:00:00 2001 From: DastInDark <2350416+hitenkoku@users.noreply.github.com> Date: Sun, 21 Aug 2022 00:59:12 +0900 Subject: [PATCH] Added output JSON format feature #654 --- src/afterfact.rs | 138 +++++++++++++++++++++----------------- src/detections/configs.rs | 4 +- 2 files changed, 79 insertions(+), 63 deletions(-) diff --git a/src/afterfact.rs b/src/afterfact.rs index fa339668..afd57b4f 100644 --- a/src/afterfact.rs +++ b/src/afterfact.rs @@ -154,22 +154,19 @@ pub fn after_fact(all_record_cnt: usize) { process::exit(1); } } - } else if let Some(json_path) = &configs::CONFIG.read().unwrap().args.json_timeline { - // output to file - match File::create(json_path) { - Ok(file) => Box::new(BufWriter::new(file)), - Err(err) => { - AlertMessage::alert(&format!("Failed to open file. {}", err)).ok(); - process::exit(1); - } - } } else { displayflag = true; // stdoutput (termcolor crate color output is not csv writer) Box::new(BufWriter::new(io::stdout())) }; let color_map = set_output_color(); - if let Err(err) = emit_csv(&mut target, displayflag, color_map, all_record_cnt as u128, PROFILES.clone().unwrap_or_default()) { + if let Err(err) = emit_csv( + &mut target, + displayflag, + color_map, + all_record_cnt as u128, + PROFILES.clone().unwrap_or_default(), + ) { fn_emit_csv_err(Box::new(err)); } } @@ -179,11 +176,11 @@ fn emit_csv( displayflag: bool, color_map: HashMap, all_record_cnt: u128, - profile: LinkedHashMap + profile: LinkedHashMap, ) -> io::Result<()> { let disp_wtr = BufferWriter::stdout(ColorChoice::Always); let mut disp_wtr_buf = disp_wtr.buffer(); - let json_output_flag = configs::CONFIG.read().unwrap().args.json_timeline.is_some(); + let json_output_flag = configs::CONFIG.read().unwrap().args.json_timeline; let mut wtr = if json_output_flag { WriterBuilder::new() @@ -651,23 +648,28 @@ fn _get_json_vec(target_alias_context: &str, target_data: &String) -> Vec String { +/// JSONの出力フォーマットに合わせた文字列を出力する関数 +fn _create_json_output_format(key: &String, value: &str, key_quote_exclude_flag: bool, concat_flag: bool) -> String { + let head = if key_quote_exclude_flag { + key.to_string() + } else { + format!("\"{}\"", key) + }; // 4 space is json indent. if let Ok(i) = i64::from_str(value) { - format!(" \"{}\": {}", key, i) + format!(" {}: {}", head, i) } else if let Ok(b) = bool::from_str(value) { - format!(" \"{}\": {}", key, b) + format!(" {}: {}", head, b) } else if concat_flag { - format!(" \"{}\": {}", key, value) + format!(" {}: {}", head, value) } else { - format!(" \"{}\": \"{}\"", key, value) + format!(" {}: \"{}\"", head, value) } } /// JSONの値に対して文字列の出力形式をJSON出力でエラーにならないようにするための変換を行う関数 fn _convert_valid_json_str(input: &[&str]) -> String { - let tmp = if input.is_empty() { + let tmp = if input.len() == 1 { input[0].to_string() } else { input[1..].join(": ") @@ -700,57 +702,71 @@ fn _convert_valid_json_str(input: &[&str]) -> String { } } - /// JSONに出力する1検知分のオブジェクトの文字列を出力する関数 -fn output_json_str(ext_field: &LinkedHashMap, profile: &LinkedHashMap) -> String { +fn output_json_str( + ext_field: &LinkedHashMap, + profile: &LinkedHashMap, +) -> String { let mut target: Vec = vec![]; for (k, v) in ext_field.iter() { let output_value_fmt = profile.get(k).unwrap(); let vec_data = _get_json_vec(output_value_fmt, v); if vec_data.is_empty() { - target.push(_create_json_output_format(k, v, v.starts_with('\"'))); + let tmp_val: Vec<&str> = v.split(": ").collect(); + target.push(_create_json_output_format( + k, + &_convert_valid_json_str(&tmp_val), + k.starts_with('\"'), + v.starts_with('\"'), + )); } else if output_value_fmt.contains("%Details%") { - let mut tmp = String::default(); - for (idx, detail_contents) in vec_data.iter().enumerate() { - let val: Vec<&str> = detail_contents.split(": ").collect(); - - // ': 'の記載が値の中にある場合があるため、追加予定の文面が存在するかを確認する - if !tmp.is_empty() { - // 次の要素が確認できるかを確認する - if idx != vec_data.len() - 1 { - let space_split: Vec<&str> = vec_data[idx + 1].split(' ').collect(); - if !space_split[0].ends_with(':') { - // 次の要素を確認してJSONのキー名がなかった場合は連結する - let end_pos = if tmp.is_empty() { 0 } else { tmp.len() - 1 }; - tmp = tmp[..end_pos].to_string(); - tmp = [&tmp, val.join(": ").trim()].join(" | "); - } else { - // 次の要素がJSONのキー名に合致する箇所が存在した場合は現在の文字列を展開してJSONの要素として追加する - // TODO: functionalize - let tmp_val: Vec<&str> = tmp.split(": ").collect(); - let fmted_json_val = _convert_valid_json_str(&tmp_val); - target.push(_create_json_output_format( - &tmp_val[0].to_string(), - &fmted_json_val, - fmted_json_val.starts_with('\"'), - )); - tmp = "".to_string(); - } + let mut stocked_value = vec![]; + let mut key_index_stock = vec![]; + for detail_contents in vec_data.iter() { + // 分解してキーとなりえる箇所を抽出する + let space_split: Vec<&str> = detail_contents.split(' ').collect(); + let mut tmp_stock = vec![]; + for sp in space_split.iter() { + if sp.ends_with(':') && sp != &":" && sp.len() != 2 { + stocked_value.push(tmp_stock); + tmp_stock = vec![]; + key_index_stock.push(sp.replace(':', "").to_owned()); } else { - // 配列の一番最後の要素の場合、JSONのオブジェクトとして出力する - // TODO: functionalize - let tmp_val: Vec<&str> = tmp.split(": ").collect(); - let fmted_json_val = _convert_valid_json_str(&tmp_val); - target.push(_create_json_output_format( - &tmp_val[0].to_string(), - &fmted_json_val, - fmted_json_val.starts_with('\"'), - )); - tmp = String::default(); + tmp_stock.push(sp.to_owned()); } - } else { - // JSONの出力町文字列がない場合は値を追加する - tmp = format!("{}: {}", val[0], val[1..].join(": ").trim()); + } + if !tmp_stock.is_empty() { + stocked_value.push(tmp_stock); + } + } + let mut tmp = String::default(); + let mut key_idx = 0; + let mut output_value_stock = String::default(); + for (value_idx, value) in stocked_value.iter().enumerate() { + if value.is_empty() { + tmp = key_index_stock[key_idx].to_string(); + key_idx += 1; + continue; + } else if value_idx == 0 { + tmp = k.to_string(); + } + if !output_value_stock.is_empty() { + output_value_stock.push_str(" | "); + } + output_value_stock.push_str(&value.join(" ")); + if value_idx < stocked_value.len() -1 && stocked_value[value_idx + 1].is_empty() { + // 次の要素を確認して、存在しないもしくは、キーが入っているとなった場合現在ストックしている内容が出力していいことが確定するので主力処理を行う + let output_tmp = format!("{}: {}", tmp, output_value_stock); + let output:Vec<&str> = output_tmp.split(": ").collect(); + let key = _convert_valid_json_str(&[output[0]]); + let fmted_val = _convert_valid_json_str(&output); + target.push( + _create_json_output_format( + &key, &fmted_val, key.starts_with('\"'),fmted_val.starts_with('\"') + ) + ); + output_value_stock.clear(); + tmp = String::default(); } } } diff --git a/src/detections/configs.rs b/src/detections/configs.rs index 02c1dab8..43929d2e 100644 --- a/src/detections/configs.rs +++ b/src/detections/configs.rs @@ -225,8 +225,8 @@ pub struct Config { pub set_default_profile: Option, /// Output result in JSON format - #[clap(help_heading = Some("OTHER-ACTIONS"), short = 'j', long = "json-timeline", value_name = "FILE")] - pub json_timeline: Option, + #[clap(help_heading = Some("OTHER-ACTIONS"), short = 'j', long = "json-timeline")] + pub json_timeline: bool, } impl ConfigReader<'_> {