Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
30 changes: 30 additions & 0 deletions ls/editors/code/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -69,6 +69,36 @@
"description": "Puts the opening curly brace on a new line."
}
}
},
"YARA.metadataValidation": {
"type": "array",
"default": [
{
"identifier": "author",
"required": false,
"type": "string"
}
],
"description": "Rules for validating metadata in YARA rules.",
"items": {
"type": "object",
"properties": {
"identifier": {
"type": "string",
"description": "The metadata identifier (e.g., `author`, `version`)."
},
"required": {
"type": "boolean",
"default": false,
"description": "Specifies if the metadata is required."
},
"type": {
"type": "string",
"enum": ["string", "integer", "float", "bool"],
"description": "The expected type of the metadata value."
}
}
}
}
}
},
Expand Down
51 changes: 49 additions & 2 deletions ls/src/features/diagnostics.rs
Original file line number Diff line number Diff line change
Expand Up @@ -3,13 +3,16 @@ use std::sync::Arc;
use async_lsp::lsp_types::{
Diagnostic, DiagnosticRelatedInformation, Location, Range, Url,
};
#[cfg(feature = "full-compiler")]
use async_lsp::lsp_types::{DiagnosticSeverity, NumberOrString};
use dashmap::mapref::one::Ref;
use serde::{Deserialize, Serialize};

use crate::documents::{document::Document, storage::DocumentStorage};
use crate::server::MetadataValidationRule;

#[cfg(feature = "full-compiler")]
use async_lsp::lsp_types::{DiagnosticSeverity, NumberOrString};
use yara_x::linters;
#[cfg(feature = "full-compiler")]
use yara_x::{Compiler, SourceCode};

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

if let Some(doc) = doc {
#[cfg(feature = "full-compiler")]
diagnostics.extend(compiler_diagnostics(doc));
diagnostics.extend(compiler_diagnostics(doc, meta_validation_rules));
}

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

let mut compiler = Compiler::new();

for validation_rule in metadata_validation_rules {
let mut linter = linters::metadata(&validation_rule.identifier)
.required(validation_rule.required);

if let Some(ty) = validation_rule.ty {
let predicate = match ty.as_str() {
"string" => |meta: &yara_x_parser::ast::Meta| {
matches!(
meta.value,
yara_x_parser::ast::MetaValue::String(_)
)
},
"integer" => |meta: &yara_x_parser::ast::Meta| {
matches!(
meta.value,
yara_x_parser::ast::MetaValue::Integer(_)
)
},
"float" => |meta: &yara_x_parser::ast::Meta| {
matches!(
meta.value,
yara_x_parser::ast::MetaValue::Float(_)
)
},
"bool" => |meta: &yara_x_parser::ast::Meta| {
matches!(
meta.value,
yara_x_parser::ast::MetaValue::Bool(_)
)
},
_ => continue,
};
linter = linter.validator(
predicate,
format!("`{}` must be a `{}`", validation_rule.identifier, ty),
);
}

compiler.add_linter(linter);
}

// VSCode don't handle well error messages with too many columns.
compiler.errors_max_width(110);
// Attempt to compile the source. We don't care about the result
Expand Down
65 changes: 59 additions & 6 deletions ls/src/server.rs
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,7 @@ use async_lsp::lsp_types::{
use async_lsp::router::Router;
use async_lsp::{ClientSocket, LanguageClient, LanguageServer, ResponseError};
use futures::future::BoxFuture;
use serde::Deserialize;

use crate::documents::storage::DocumentStorage;
use crate::features::code_action::code_actions;
Expand All @@ -54,6 +55,20 @@ use crate::features::semantic_tokens::{
semantic_tokens, SEMANTIC_TOKEN_MODIFIERS, SEMANTIC_TOKEN_TYPES,
};

/// Rule that describes a how to validate a metadata entry in a rule.
#[derive(Deserialize, Debug, Clone, Default)]
#[serde(rename_all = "camelCase")]
pub struct MetadataValidationRule {
/// Metadata identifier
pub identifier: String,
/// Whether the metadata entry is required or not.
#[serde(default)]
pub required: bool,
/// Type of the metadata entry.
#[serde(rename = "type")]
pub ty: Option<String>,
}

/// Represents a YARA language server.
pub struct YARALanguageServer {
/// Client socket for communication with the Development Tool.
Expand Down Expand Up @@ -408,15 +423,20 @@ impl LanguageServer for YARALanguageServer {
{
let uri = params.text_document.uri;
let documents = Arc::clone(&self.documents);
let client = self.client.clone();

Box::pin(async move {
let meta_specs =
Self::get_meta_validation_rules(client.clone(), uri.clone())
.await;

Ok(DocumentDiagnosticReportResult::Report(
async_lsp::lsp_types::DocumentDiagnosticReport::Full(
RelatedFullDocumentDiagnosticReport {
full_document_diagnostic_report:
FullDocumentDiagnosticReport {
result_id: None,
items: diagnostics(documents, uri),
items: diagnostics(documents, uri, meta_specs),
},
related_documents: None,
},
Expand Down Expand Up @@ -543,18 +563,51 @@ impl YARALanguageServer {
})
}

async fn get_meta_validation_rules(
mut client: ClientSocket,
scope_uri: Url,
) -> Vec<MetadataValidationRule> {
client
.configuration(ConfigurationParams {
items: vec![ConfigurationItem {
scope_uri: Some(scope_uri),
section: Some("YARA.metadataValidation".to_string()),
}],
})
.await
.ok()
.and_then(|mut v| v.pop())
.and_then(|value| {
serde_json::from_value::<Vec<MetadataValidationRule>>(value)
.ok()
})
.unwrap_or_default()
}

/// Sends diagnostics for specific document if publish model is used.
fn publish_diagnostics(&mut self, uri: &Url) {
if self.should_send_diagnostics {
let _ =
self.client.publish_diagnostics(PublishDiagnosticsParams {
let documents = Arc::clone(&self.documents);
let mut client = self.client.clone();
let uri = uri.clone();

tokio::spawn(async move {
let meta_validation_rules = Self::get_meta_validation_rules(
client.clone(),
uri.clone(),
)
.await;

client.publish_diagnostics(PublishDiagnosticsParams {
uri: uri.clone(),
diagnostics: diagnostics(
Arc::clone(&self.documents),
uri.clone(),
documents,
uri,
meta_validation_rules,
),
version: None,
});
})
});
}
}
}
Loading