Skip to content

Commit 073056e

Browse files
committed
feat(ls): allow configuring rules for validating metadata entries
The language server now can be configured for validating metadata entries in YARA rules. The user specifies the identifier of a metadata entry alongside its type and whether it is required or not. A warning will be raised if some of the rules don't have required metadata entries, or if the type of some metadata entry is not the expected one.
1 parent f9b7798 commit 073056e

File tree

3 files changed

+138
-8
lines changed

3 files changed

+138
-8
lines changed

ls/editors/code/package.json

Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -69,6 +69,36 @@
6969
"description": "Puts the opening curly brace on a new line."
7070
}
7171
}
72+
},
73+
"YARA.metadataValidation": {
74+
"type": "array",
75+
"default": [
76+
{
77+
"identifier": "author",
78+
"required": false,
79+
"type": "string"
80+
}
81+
],
82+
"description": "Rules for validating metadata in YARA rules.",
83+
"items": {
84+
"type": "object",
85+
"properties": {
86+
"identifier": {
87+
"type": "string",
88+
"description": "The metadata identifier (e.g., `author`, `version`)."
89+
},
90+
"required": {
91+
"type": "boolean",
92+
"default": false,
93+
"description": "Specifies if the metadata is required."
94+
},
95+
"type": {
96+
"type": "string",
97+
"enum": ["string", "integer", "float", "bool"],
98+
"description": "The expected type of the metadata value."
99+
}
100+
}
101+
}
72102
}
73103
}
74104
},

ls/src/features/diagnostics.rs

Lines changed: 49 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -3,13 +3,16 @@ use std::sync::Arc;
33
use async_lsp::lsp_types::{
44
Diagnostic, DiagnosticRelatedInformation, Location, Range, Url,
55
};
6+
#[cfg(feature = "full-compiler")]
7+
use async_lsp::lsp_types::{DiagnosticSeverity, NumberOrString};
68
use dashmap::mapref::one::Ref;
79
use serde::{Deserialize, Serialize};
810

911
use crate::documents::{document::Document, storage::DocumentStorage};
12+
use crate::server::MetadataValidationRule;
1013

1114
#[cfg(feature = "full-compiler")]
12-
use async_lsp::lsp_types::{DiagnosticSeverity, NumberOrString};
15+
use yara_x::linters;
1316
#[cfg(feature = "full-compiler")]
1417
use yara_x::{Compiler, SourceCode};
1518

@@ -29,6 +32,7 @@ pub struct Patch {
2932
pub fn diagnostics(
3033
documents: Arc<DocumentStorage>,
3134
uri: Url,
35+
meta_validation_rules: Vec<MetadataValidationRule>,
3236
) -> Vec<Diagnostic> {
3337
#[allow(unused_mut)]
3438
let mut diagnostics: Vec<Diagnostic> = Vec::new();
@@ -37,7 +41,7 @@ pub fn diagnostics(
3741

3842
if let Some(doc) = doc {
3943
#[cfg(feature = "full-compiler")]
40-
diagnostics.extend(compiler_diagnostics(doc));
44+
diagnostics.extend(compiler_diagnostics(doc, meta_validation_rules));
4145
}
4246

4347
diagnostics
@@ -52,11 +56,54 @@ pub fn diagnostics(
5256
#[cfg(feature = "full-compiler")]
5357
pub fn compiler_diagnostics(
5458
document: Ref<'_, Url, Document>,
59+
metadata_validation_rules: Vec<MetadataValidationRule>,
5560
) -> Vec<Diagnostic> {
5661
let source_code = SourceCode::from(document.text.as_str())
5762
.with_origin(document.uri.clone());
5863

5964
let mut compiler = Compiler::new();
65+
66+
for validation_rule in metadata_validation_rules {
67+
let mut linter = linters::metadata(&validation_rule.identifier)
68+
.required(validation_rule.required);
69+
70+
if let Some(ty) = validation_rule.ty {
71+
let predicate = match ty.as_str() {
72+
"string" => |meta: &yara_x_parser::ast::Meta| {
73+
matches!(
74+
meta.value,
75+
yara_x_parser::ast::MetaValue::String(_)
76+
)
77+
},
78+
"integer" => |meta: &yara_x_parser::ast::Meta| {
79+
matches!(
80+
meta.value,
81+
yara_x_parser::ast::MetaValue::Integer(_)
82+
)
83+
},
84+
"float" => |meta: &yara_x_parser::ast::Meta| {
85+
matches!(
86+
meta.value,
87+
yara_x_parser::ast::MetaValue::Float(_)
88+
)
89+
},
90+
"bool" => |meta: &yara_x_parser::ast::Meta| {
91+
matches!(
92+
meta.value,
93+
yara_x_parser::ast::MetaValue::Bool(_)
94+
)
95+
},
96+
_ => continue,
97+
};
98+
linter = linter.validator(
99+
predicate,
100+
format!("`{}` must be a `{}`", validation_rule.identifier, ty),
101+
);
102+
}
103+
104+
compiler.add_linter(linter);
105+
}
106+
60107
// VSCode don't handle well error messages with too many columns.
61108
compiler.errors_max_width(110);
62109
// Attempt to compile the source. We don't care about the result

ls/src/server.rs

Lines changed: 59 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -36,6 +36,7 @@ use async_lsp::lsp_types::{
3636
use async_lsp::router::Router;
3737
use async_lsp::{ClientSocket, LanguageClient, LanguageServer, ResponseError};
3838
use futures::future::BoxFuture;
39+
use serde::Deserialize;
3940

4041
use crate::documents::storage::DocumentStorage;
4142
use crate::features::code_action::code_actions;
@@ -54,6 +55,20 @@ use crate::features::semantic_tokens::{
5455
semantic_tokens, SEMANTIC_TOKEN_MODIFIERS, SEMANTIC_TOKEN_TYPES,
5556
};
5657

58+
/// Rule that describes a how to validate a metadata entry in a rule.
59+
#[derive(Deserialize, Debug, Clone, Default)]
60+
#[serde(rename_all = "camelCase")]
61+
pub struct MetadataValidationRule {
62+
/// Metadata identifier
63+
pub identifier: String,
64+
/// Whether the metadata entry is required or not.
65+
#[serde(default)]
66+
pub required: bool,
67+
/// Type of the metadata entry.
68+
#[serde(rename = "type")]
69+
pub ty: Option<String>,
70+
}
71+
5772
/// Represents a YARA language server.
5873
pub struct YARALanguageServer {
5974
/// Client socket for communication with the Development Tool.
@@ -408,15 +423,20 @@ impl LanguageServer for YARALanguageServer {
408423
{
409424
let uri = params.text_document.uri;
410425
let documents = Arc::clone(&self.documents);
426+
let client = self.client.clone();
411427

412428
Box::pin(async move {
429+
let meta_specs =
430+
Self::get_meta_validation_rules(client.clone(), uri.clone())
431+
.await;
432+
413433
Ok(DocumentDiagnosticReportResult::Report(
414434
async_lsp::lsp_types::DocumentDiagnosticReport::Full(
415435
RelatedFullDocumentDiagnosticReport {
416436
full_document_diagnostic_report:
417437
FullDocumentDiagnosticReport {
418438
result_id: None,
419-
items: diagnostics(documents, uri),
439+
items: diagnostics(documents, uri, meta_specs),
420440
},
421441
related_documents: None,
422442
},
@@ -543,18 +563,51 @@ impl YARALanguageServer {
543563
})
544564
}
545565

566+
async fn get_meta_validation_rules(
567+
mut client: ClientSocket,
568+
scope_uri: Url,
569+
) -> Vec<MetadataValidationRule> {
570+
client
571+
.configuration(ConfigurationParams {
572+
items: vec![ConfigurationItem {
573+
scope_uri: Some(scope_uri),
574+
section: Some("YARA.metadataValidation".to_string()),
575+
}],
576+
})
577+
.await
578+
.ok()
579+
.and_then(|mut v| v.pop())
580+
.and_then(|value| {
581+
serde_json::from_value::<Vec<MetadataValidationRule>>(value)
582+
.ok()
583+
})
584+
.unwrap_or_default()
585+
}
586+
546587
/// Sends diagnostics for specific document if publish model is used.
547588
fn publish_diagnostics(&mut self, uri: &Url) {
548589
if self.should_send_diagnostics {
549-
let _ =
550-
self.client.publish_diagnostics(PublishDiagnosticsParams {
590+
let documents = Arc::clone(&self.documents);
591+
let mut client = self.client.clone();
592+
let uri = uri.clone();
593+
594+
tokio::spawn(async move {
595+
let meta_validation_rules = Self::get_meta_validation_rules(
596+
client.clone(),
597+
uri.clone(),
598+
)
599+
.await;
600+
601+
client.publish_diagnostics(PublishDiagnosticsParams {
551602
uri: uri.clone(),
552603
diagnostics: diagnostics(
553-
Arc::clone(&self.documents),
554-
uri.clone(),
604+
documents,
605+
uri,
606+
meta_validation_rules,
555607
),
556608
version: None,
557-
});
609+
})
610+
});
558611
}
559612
}
560613
}

0 commit comments

Comments
 (0)