aboutsummaryrefslogtreecommitdiffstats
path: root/test
diff options
context:
space:
mode:
Diffstat (limited to 'test')
-rw-r--r--test/functional/cases/001_merged/350_magic.robot1
-rw-r--r--test/functional/cases/270_regexp_maps.robot48
-rw-r--r--test/functional/cases/400_known_senders.robot22
-rw-r--r--test/functional/cases/410_replies.robot37
-rw-r--r--test/functional/cases/411_logging/000_console/000_systemd_logger.robot (renamed from test/functional/cases/410_logging/000_console/000_systemd_logger.robot)0
-rw-r--r--test/functional/cases/411_logging/000_console/001_timestamps.robot (renamed from test/functional/cases/410_logging/000_console/001_timestamps.robot)0
-rw-r--r--test/functional/cases/411_logging/001_file/000_json.robot (renamed from test/functional/cases/410_logging/001_file/000_json.robot)0
-rw-r--r--test/functional/cases/550_milter_headers.robot11
-rw-r--r--test/functional/configs/maps/advance_fee_rules.map43
-rw-r--r--test/functional/configs/milter_headers.conf12
-rw-r--r--test/functional/configs/regexp_maps.conf51
-rw-r--r--test/functional/lib/rspamd.robot9
-rw-r--r--test/functional/messages/advance_fee_fraud.eml37
-rw-r--r--test/functional/messages/gargantua.eml54
-rw-r--r--test/lua/unit/html.lua414
-rw-r--r--test/lua/unit/logger.lua95
-rw-r--r--test/lua/unit/rspamd_resolver.lua62
-rw-r--r--test/rspamd_cryptobox_test.c6
-rw-r--r--test/rspamd_cxx_unit_cryptobox.hxx221
-rw-r--r--test/rspamd_dns_test.c19
-rw-r--r--test/rspamd_shingles_test.c80
21 files changed, 1121 insertions, 101 deletions
diff --git a/test/functional/cases/001_merged/350_magic.robot b/test/functional/cases/001_merged/350_magic.robot
index 66a18f2af..b2746ce3c 100644
--- a/test/functional/cases/001_merged/350_magic.robot
+++ b/test/functional/cases/001_merged/350_magic.robot
@@ -65,3 +65,4 @@ Magic detections bundle 1
... MAGIC_SYM_ICS_55
... MAGIC_SYM_VCF_56
... MAGIC_SYM_CSV_57
+ ... MAGIC_SYM_HEIC_58
diff --git a/test/functional/cases/270_regexp_maps.robot b/test/functional/cases/270_regexp_maps.robot
new file mode 100644
index 000000000..d89143bc8
--- /dev/null
+++ b/test/functional/cases/270_regexp_maps.robot
@@ -0,0 +1,48 @@
+*** Settings ***
+Test Setup Rspamd Setup
+Test Teardown Rspamd Teardown
+Library ${RSPAMD_TESTDIR}/lib/rspamd.py
+Resource ${RSPAMD_TESTDIR}/lib/rspamd.robot
+Variables ${RSPAMD_TESTDIR}/lib/vars.py
+
+*** Variables ***
+${CONFIG} ${RSPAMD_TESTDIR}/configs/regexp_maps.conf
+${MESSAGE1} ${RSPAMD_TESTDIR}/messages/advance_fee_fraud.eml
+${MESSAGE2} ${RSPAMD_TESTDIR}/messages/spam_message.eml
+${RSPAMD_SCOPE} Test
+${RSPAMD_URL_TLD} ${RSPAMD_TESTDIR}/../lua/unit/test_tld.dat
+
+*** Test Cases ***
+Advance Fee Fraud Detection
+ [Documentation] Test that advance fee fraud rules match correctly
+ Scan File ${MESSAGE1}
+ Expect Symbol ADVANCE_FEE_2
+ Expect Symbol ADVANCE_FEE_3
+ # Verify filtered options (no __ atoms, max 5 options)
+ ${symbols} = Get From Dictionary ${SCAN_RESULT}[symbols] ADVANCE_FEE_2
+ ${options} = Get From Dictionary ${symbols} options
+ ${options_count} = Get Length ${options}
+ Should Be True ${options_count} <= 5 msg=Too many options: ${options_count}
+ FOR ${option} IN @{options}
+ Should Not Match Regexp ${option} ^__ msg=Option should not start with __: ${option}
+ END
+
+Meta Rule Combination
+ [Documentation] Test that meta rules correctly combine atom results
+ Scan File ${MESSAGE1}
+ Expect Symbol With Score ADVANCE_FEE_2 4.0
+ Expect Symbol With Score ADVANCE_FEE_3 5.0
+
+No False Positives on Ham
+ [Documentation] Test that regexp rules don't trigger on legitimate messages
+ Scan File ${MESSAGE2}
+ Do Not Expect Symbol ADVANCE_FEE_2
+ Do Not Expect Symbol ADVANCE_FEE_3
+
+Atom Rules Availability
+ [Documentation] Test that individual atom rules are available for combination
+ Scan File ${MESSAGE1}
+ # These should be available internally but not shown as main results
+ # We test by ensuring the meta rules work correctly
+ Expect Symbol ADVANCE_FEE_2
+ Expect Symbol ADVANCE_FEE_3
diff --git a/test/functional/cases/400_known_senders.robot b/test/functional/cases/400_known_senders.robot
index d827acc0e..a7cde59cb 100644
--- a/test/functional/cases/400_known_senders.robot
+++ b/test/functional/cases/400_known_senders.robot
@@ -43,33 +43,37 @@ INCOMING MAIL SENDER IS UNKNOWN
... Settings={symbols_enabled [${SYMBOL_GLOBAL}, ${SYMBOL_LOCAL}]}
Do Not Expect Symbol ${SYMBOL_GLOBAL}
Do Not Expect Symbol ${SYMBOL_LOCAL}
-
+
INCOMING MAIL SENDER IS KNOWN RECIPIENTS ARE UNKNOWN
Scan File ${RSPAMD_TESTDIR}/messages/set_replyto_1_1.eml
- ... IP=8.8.8.8 User=user@emailbl.com
+ ... IP=8.8.8.8
+ ... User=xxx@abrakadabra.com
+ ... From=xxx@abrakadabra.com
... Settings=${SETTINGS_REPLIES}
Scan File ${RSPAMD_TESTDIR}/messages/replyto_1_1.eml
- ... IP=8.8.8.8 User=user@emailbl.com
+ ... IP=8.8.8.8
+ ... Settings=${SETTINGS_REPLIES}
+ ... Rcpt=xxx@abrakadabra.com
... Settings=${SETTINGS_REPLIES}
+ ... From=user@emailbl.com
Scan File ${RSPAMD_TESTDIR}/messages/inc_mail_known_sender.eml
- ... IP=8.8.8.8 User=user@emailbl.com
+ ... IP=8.8.8.8
... Settings={symbols_enabled [${SYMBOL_GLOBAL}, ${SYMBOL_LOCAL}]}
Expect Symbol ${SYMBOL_GLOBAL}
Do Not Expect Symbol ${SYMBOL_LOCAL}
INCOMING MAIL SENDER IS KNOWN RECIPIENTS ARE KNOWN
Scan File ${RSPAMD_TESTDIR}/messages/set_replyto_1_1.eml
- ... IP=8.8.8.8 User=user@emailbl.com
+ ... IP=8.8.8.8 User=user@emailbl.com From=user@emailbl.com
... Settings=${SETTINGS_REPLIES}
Scan File ${RSPAMD_TESTDIR}/messages/replyto_1_1.eml
- ... IP=8.8.8.8 User=user@emailbl.com
+ ... IP=8.8.8.8 User=user@emailbl.com Rcpt=user@emailbl.com
... Settings=${SETTINGS_REPLIES}
Scan File ${RSPAMD_TESTDIR}/messages/inc_mail_known_sender.eml
- ... IP=8.8.8.8 User=user@emailbl.com
+ ... IP=8.8.8.8 User=user@emailbl.com Rcpt=user@emailbl.com
... Settings=${SETTINGS_REPLIES}
Scan File ${RSPAMD_TESTDIR}/messages/inc_mail_known_sender.eml
- ... IP=8.8.8.8 User=user@emailbl.com
+ ... IP=8.8.8.8 User=user@emailbl.com Rcpt=user@emailbl.com
... Settings={symbols_enabled [${SYMBOL_GLOBAL}, ${SYMBOL_LOCAL}]}
Expect Symbol ${SYMBOL_GLOBAL}
Expect Symbol ${SYMBOL_LOCAL}
-
diff --git a/test/functional/cases/410_replies.robot b/test/functional/cases/410_replies.robot
index 23ad9df35..b6710149c 100644
--- a/test/functional/cases/410_replies.robot
+++ b/test/functional/cases/410_replies.robot
@@ -15,33 +15,36 @@ ${RSPAMD_SCOPE} Suite
*** Test Cases ***
Reply to 1 sender 1 recipients
Scan File ${RSPAMD_TESTDIR}/messages/set_replyto_1_1.eml
- ... IP=8.8.8.8 User=user@emailbl.com
+ ... IP=8.8.8.8
+ ... User=xxx@abrakadabra.com
+ ... From=xxx@abrakadabra.com
... Settings=${SETTINGS_REPLIES}
+ ... Rcpt=user@emailbl.com
Scan File ${RSPAMD_TESTDIR}/messages/replyto_1_1.eml
- ... IP=8.8.8.8 User=user@emailbl.com
+ ... IP=8.8.8.8
+ ... Rcpt=xxx@abrakadabra.com
... Settings=${SETTINGS_REPLIES}
+ ... From=user@emailbl.com
Expect Symbol ${SYMBOL}
-Reply to 1 sender 2 recipients first is set second is not
+Reply to 1 sender 2 recipients but SMTP recipient matches
Scan File ${RSPAMD_TESTDIR}/messages/set_replyto_1_2_first.eml
- ... IP=8.8.8.8 User=user@emailbl.com
+ ... IP=8.8.8.8
+ ... User=xxxx@emailbl.com
... Settings=${SETTINGS_REPLIES}
Scan File ${RSPAMD_TESTDIR}/messages/replyto_1_2.eml
- ... IP=8.8.8.8 User=user@emailbl.com
+ ... IP=8.8.8.8
+ ... Rcpt=xxxx@emailbl.com
... Settings=${SETTINGS_REPLIES}
Expect Symbol ${SYMBOL}
-Reply to 1 sender 2 recipients 1 rcpt is same
- Scan File ${RSPAMD_TESTDIR}/messages/replyto_1_2_s.eml
- ... IP=8.8.8.8 User=user@emailbl.com
+Reply to 1 sender 2 recipients but SMTP recipient NOT matches
+ Scan File ${RSPAMD_TESTDIR}/messages/set_replyto_1_2_first.eml
+ ... IP=8.8.8.8
+ ... User=user@emailbl.com
... Settings=${SETTINGS_REPLIES}
- Expect Symbol ${SYMBOL}
-
-Reply to another sender 2 recipients
- Scan File ${RSPAMD_TESTDIR}/messages/set_replyto_2_2.eml
- ... IP=8.8.8.8 User=another@emailbl.com
+ Scan File ${RSPAMD_TESTDIR}/messages/replyto_1_2.eml
+ ... IP=8.8.8.8 User=user@emailbl.com
+ ... Rcpt=another@emailbl.com
... Settings=${SETTINGS_REPLIES}
- Scan File ${RSPAMD_TESTDIR}/messages/replyto_2_2.eml
- ... IP=8.8.8.8 User=another@emailbl.com
- ... Settings=${SETTINGS_REPLIES}
- Expect Symbol ${SYMBOL}
+ Do Not Expect Symbol ${SYMBOL}
diff --git a/test/functional/cases/410_logging/000_console/000_systemd_logger.robot b/test/functional/cases/411_logging/000_console/000_systemd_logger.robot
index 88178461b..88178461b 100644
--- a/test/functional/cases/410_logging/000_console/000_systemd_logger.robot
+++ b/test/functional/cases/411_logging/000_console/000_systemd_logger.robot
diff --git a/test/functional/cases/410_logging/000_console/001_timestamps.robot b/test/functional/cases/411_logging/000_console/001_timestamps.robot
index bd8e2c349..bd8e2c349 100644
--- a/test/functional/cases/410_logging/000_console/001_timestamps.robot
+++ b/test/functional/cases/411_logging/000_console/001_timestamps.robot
diff --git a/test/functional/cases/410_logging/001_file/000_json.robot b/test/functional/cases/411_logging/001_file/000_json.robot
index a2f04e85c..a2f04e85c 100644
--- a/test/functional/cases/410_logging/001_file/000_json.robot
+++ b/test/functional/cases/411_logging/001_file/000_json.robot
diff --git a/test/functional/cases/550_milter_headers.robot b/test/functional/cases/550_milter_headers.robot
index 80471b83c..c09659714 100644
--- a/test/functional/cases/550_milter_headers.robot
+++ b/test/functional/cases/550_milter_headers.robot
@@ -37,3 +37,14 @@ CHECK HEADERS WITHOUT TEST SYMBOL
# Check X-Spam-Level header
Do Not Expect Added Header X-Spam-Level
Expect Removed Header X-Spam-Level
+
+CHECK HEADERS WITH OVERRIDE SETTINGS
+ # id_milter_headers_override setting enables only authentication-results and x-spam-level routines
+ Scan File ${MESSAGE} Settings-Id=id_milter_headers_override
+ # Test the milter_headers override behavior
+ # Check that Authentication-Results and X-Spam-Level headers are present (exact values are not important)
+ Expect Header Is Present Authentication-Results
+ Expect Header Is Present X-Spam-Level
+ # Verify other headers are not added since only authentication-results and x-spam-level routines run
+ Do Not Expect Added Header X-Virus
+ Do Not Expect Added Header My-Spamd-Bar
diff --git a/test/functional/configs/maps/advance_fee_rules.map b/test/functional/configs/maps/advance_fee_rules.map
new file mode 100644
index 000000000..d07b25c71
--- /dev/null
+++ b/test/functional/configs/maps/advance_fee_rules.map
@@ -0,0 +1,43 @@
+# SpamAssassin-style rules for advance fee fraud detection (simplified for testing)
+
+# Individual atom rules (these detect specific patterns)
+header __FRAUD_VQE Subject =~ /^(?:Re:|\[.{1,10}\])?\s*(?:very )?urgent\s+(?:(?:and|&)\s+)?(?:confidential|assistance|business|attention|reply|response|help)\b/i
+
+body __FRAUD_DBI /(?:\bdollars?\b|\busd(?:ollars)?(?:[0-9]|\b)|\bus\$|\$[0-9,.]{6,}|\$[0-9].{0,8}[mb]illion|\$[0-9.,]{2,10} ?m|\beuros?\b|u[.]?s[.]? [0-9.]+ m)/i
+body __FRAUD_KJV /(?:claim|concerning) (?:the|this) money/i
+body __FRAUD_NEB /(?:government|bank) of nigeria/i
+body __FRAUD_XJR /(?:who was a|as a|an? honest|you being a|to any) foreigner/i
+body __FRAUD_JBU /\bforeign account\b/i
+body __FRAUD_XVW /\bhonest cooperation\b/i
+body __FRAUD_LTX /\bmilli?on (?:.{1,25} thousand\s*)?(?:(?:united states|u\.?s\.?) dollars|(?i:U\.?S\.?D?))\b/i
+body __FRAUD_PVN /as the beneficiary/i
+body __FRAUD_MQO /foreign (?:business partner|customer)/i
+body __FRAUD_TCC /foreign (?:offshore )?(?:bank|account)/i
+body __FRAUD_GBW /god gives .{1,10}second chance/i
+body __FRAUD_NRG /i am contacting you/i
+body __FRAUD_YPO /the total sum/i
+body __FRAUD_UOQ /vital documents/i
+body __FRAUD_BEP /\b(?:bank of nigeria|central bank of|trust bank|apex bank|amalgamated bank)\b/i
+body __FRAUD_DPR /\b(?:(?:respond|reply) (?:urgently|immediately)|(?:urgent|immediate|earliest) (?:reply|response))\b/i
+body __FRAUD_QXX /\b(?:my name is|i am) (?:mrs?|engr|barrister|dr|prince(?:ss)?)[. ]/i
+body __FRAUD_PTS /\b(?:ass?ass?inat(?:ed|ion)|murder(?:e?d)?|kill(?:ed|ing)\b[^.]{0,99}\b(?:war veterans|rebels?))\b/i
+body __FRAUD_TDP /\b(?:business partner(?:s|ship)?|silent partner(?:s|ship)?)\b/i
+body __FRAUD_AON /\b(?:confidential|private|alternate|alternative) (?:(?:e-? *)?mail)\b/i
+body __FRAUD_YWW /\bfurnish you with\b/i
+body __FRAUD_ULK /\baffidavits?\b/i
+body __FRAUD_IOU /\b(?:no risks?|risk-? *free|free of risks?|100% safe)\b/i
+body __FRAUD_IRT /\b(?:compliments? of the|dear friend|dear sir|yours faithfully|season'?s greetings)\b/i
+body __FRAUD_ETX /\byour\b[^.]{0,99}\b(?:contact (?:details|information)|private (?:e?[- ]?mail|telephone|tel|phone|fax))\b/i
+body __FRAUD_WDR /\bprivate lawyer\b/i
+
+# Meta rules that combine multiple atoms
+meta ADVANCE_FEE_2 (__FRAUD_KJV + __FRAUD_NEB + __FRAUD_XJR + __FRAUD_JBU + __FRAUD_XVW + __FRAUD_LTX + __FRAUD_PVN + __FRAUD_MQO + __FRAUD_TCC + __FRAUD_GBW + __FRAUD_NRG + __FRAUD_YPO + __FRAUD_UOQ + __FRAUD_DBI + __FRAUD_BEP + __FRAUD_DPR + __FRAUD_QXX + __FRAUD_PTS + __FRAUD_TDP + __FRAUD_AON + __FRAUD_YWW + __FRAUD_ULK + __FRAUD_IOU + __FRAUD_IRT + __FRAUD_ETX + __FRAUD_WDR > 2)
+
+meta ADVANCE_FEE_3 (__FRAUD_KJV + __FRAUD_NEB + __FRAUD_XJR + __FRAUD_JBU + __FRAUD_XVW + __FRAUD_LTX + __FRAUD_PVN + __FRAUD_MQO + __FRAUD_TCC + __FRAUD_GBW + __FRAUD_NRG + __FRAUD_YPO + __FRAUD_UOQ + __FRAUD_DBI + __FRAUD_BEP + __FRAUD_DPR + __FRAUD_QXX + __FRAUD_PTS + __FRAUD_TDP + __FRAUD_AON + __FRAUD_YWW + __FRAUD_ULK + __FRAUD_IOU + __FRAUD_IRT + __FRAUD_ETX + __FRAUD_WDR > 3)
+
+# Descriptions for the rules
+describe ADVANCE_FEE_2 Appears to be advance fee fraud (Nigerian 419) - moderate confidence
+describe ADVANCE_FEE_3 Appears to be advance fee fraud (Nigerian 419) - high confidence
+
+score ADVANCE_FEE_2 4.0
+score ADVANCE_FEE_3 5.0 \ No newline at end of file
diff --git a/test/functional/configs/milter_headers.conf b/test/functional/configs/milter_headers.conf
index 947bc28dd..502747605 100644
--- a/test/functional/configs/milter_headers.conf
+++ b/test/functional/configs/milter_headers.conf
@@ -22,3 +22,15 @@ milter_headers {
}
}
+
+settings {
+ id_milter_headers_override {
+ apply {
+ plugins {
+ milter_headers {
+ routines = [ authentication-results, x-spam-level ];
+ }
+ }
+ }
+ }
+}
diff --git a/test/functional/configs/regexp_maps.conf b/test/functional/configs/regexp_maps.conf
new file mode 100644
index 000000000..be4cd047b
--- /dev/null
+++ b/test/functional/configs/regexp_maps.conf
@@ -0,0 +1,51 @@
+.include(duplicate=append,priority=0) "{= env.TESTDIR =}/configs/plugins.conf"
+
+# Configure multimap for regexp rules testing
+multimap {
+ ADVANCE_FEE_SA_RULES {
+ type = "regexp_rules";
+ map = "{= env.TESTDIR =}/configs/maps/advance_fee_rules.map";
+ scope = "advance_fee_scope";
+ description = "Advance fee fraud detection rules";
+ }
+}
+
+# Override symbol scores for testing
+symbols {
+ ADVANCE_FEE_2 {
+ score = 4.0;
+ description = "Advance fee fraud pattern (medium confidence)";
+ group = "scam";
+ }
+ ADVANCE_FEE_3 {
+ score = 5.0;
+ description = "Advance fee fraud pattern (high confidence)";
+ group = "scam";
+ }
+}
+
+# Set required score for testing
+actions {
+ reject = 10;
+ add_header = 6;
+ greylist = 4;
+}
+
+# Enable necessary modules
+dmarc { }
+spf { }
+dkim { }
+
+# Module path for multimap
+modules {
+ path = "{= env.TESTDIR =}/../../src/plugins/lua/"
+}
+
+# Disable some modules that might interfere with testing
+rbl {
+ enabled = false;
+}
+
+fuzzy_check {
+ enabled = false;
+}
diff --git a/test/functional/lib/rspamd.robot b/test/functional/lib/rspamd.robot
index 9c30a97db..5d23e3ceb 100644
--- a/test/functional/lib/rspamd.robot
+++ b/test/functional/lib/rspamd.robot
@@ -120,6 +120,15 @@ Expect Added Header
Should Be Equal ${SCAN_RESULT}[milter][add_headers][${header_name}][value] ${header_value}
Should Be Equal as Numbers ${SCAN_RESULT}[milter][add_headers][${header_name}][order] ${pos}
+Expect Header Is Present
+ [Arguments] ${header_name}
+ Dictionary Should Contain Key ${SCAN_RESULT} milter
+ ... msg=milter block was not present in protocol response
+ Dictionary Should Contain Key ${SCAN_RESULT}[milter] add_headers
+ ... msg=add_headers block was not present in protocol response
+ Dictionary Should Contain Key ${SCAN_RESULT}[milter][add_headers] ${header_name}
+ ... msg=${header_name} was not added
+
Expect Email
[Arguments] ${email}
List Should Contain Value ${SCAN_RESULT}[emails] ${email}
diff --git a/test/functional/messages/advance_fee_fraud.eml b/test/functional/messages/advance_fee_fraud.eml
new file mode 100644
index 000000000..d9b468b6a
--- /dev/null
+++ b/test/functional/messages/advance_fee_fraud.eml
@@ -0,0 +1,37 @@
+From: Prince John Doe <prince@nigeria-bank.com>
+To: recipient@example.com
+Subject: URGENT BUSINESS ASSISTANCE NEEDED
+Date: Thu, 26 Oct 2023 10:30:00 +0000
+Message-ID: <advance-fee-test@example.com>
+MIME-Version: 1.0
+Content-Type: text/plain; charset=utf-8
+
+Dear Friend,
+
+Compliments of the season. I am contacting you with utmost confidence
+regarding this money transfer opportunity that will be of mutual benefit
+to both of us.
+
+My name is Prince John Doe, director of the Central Bank of Nigeria.
+I am writing to you concerning the total sum of Twenty-Five Million
+United States Dollars ($25,000,000 USD) that belongs to a foreigner
+who died in a plane crash.
+
+As the beneficiary of this vast fortune, I need your honest cooperation
+to claim this money. The Government of Nigeria has approved this transfer
+to a foreign account, and you have been selected as a business partner
+to assist in this confidential transaction.
+
+This is a risk-free business proposal, and I can furnish you with
+all vital documents to prove the legitimacy of this transaction.
+Your private email and contact details are needed to proceed immediately.
+
+Please respond urgently through my private lawyer, as this requires
+your immediate attention. God gives everyone a second chance, and this
+is yours.
+
+Yours faithfully,
+Prince John Doe
+Director, Bank of Nigeria
+
+Note: This is 100% safe and no risk is involved.
diff --git a/test/functional/messages/gargantua.eml b/test/functional/messages/gargantua.eml
index c6edc50f3..acb3d367f 100644
--- a/test/functional/messages/gargantua.eml
+++ b/test/functional/messages/gargantua.eml
@@ -23502,4 +23502,58 @@ X-Real-Type: csv
ImZvbyIsImJhciIsImJheiIKIjEiLCIyIiwiMyIK
+--XXX
+Content-Type: image/heic
+Content-Transfer-Encoding: base64
+X-Real-Type: heic
+
+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
+0YCJBxFcC867msO9wip2vP786vlLeC/fqKwSng==
+
--XXX--
diff --git a/test/lua/unit/html.lua b/test/lua/unit/html.lua
index 81c52ec1b..1802dc984 100644
--- a/test/lua/unit/html.lua
+++ b/test/lua/unit/html.lua
@@ -1,11 +1,10 @@
context("HTML processing", function()
local rspamd_util = require("rspamd_util")
- local logger = require("rspamd_logger")
local cases = {
- -- Entities
- {[[<html><body>.&#102;&#105;&#114;&#101;&#98;&#97;&#115;&#101;&#97;&#112;&#112;.&#99;&#111;&#109;</body></html>]],
- [[.firebaseapp.com]]},
- {[[
+ -- Entities
+ { [[<html><body>.&#102;&#105;&#114;&#101;&#98;&#97;&#115;&#101;&#97;&#112;&#112;.&#99;&#111;&#109;</body></html>]],
+ [[.firebaseapp.com]] },
+ { [[
<?xml version="1.0" encoding="iso-8859-1"?>
<!DOCTYPE html
PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN"
@@ -22,8 +21,8 @@ context("HTML processing", function()
</p>
</body>
- </html>]], 'Hello, world!\n'},
- {[[
+ </html>]], 'Hello, world!\n' },
+ { [[
<!DOCTYPE html>
<html lang="en">
<head>
@@ -39,8 +38,8 @@ context("HTML processing", function()
Hello, world!
</body>
</html>
- ]], 'Hello, world!'},
- {[[
+ ]], 'Hello, world!' },
+ { [[
<html lang="en">
<head>
<meta charset="utf-8">
@@ -56,8 +55,8 @@ context("HTML processing", function()
</div>
</body>
</html>
- ]], 'Hello, world!\ntest\ncontentmore content\ncontent inside div\n'},
- {[[
+ ]], 'Hello, world!\ntest\ncontentmore content\ncontent inside div\n' },
+ { [[
<html lang="en">
<head>
<meta charset="utf-8">
@@ -83,8 +82,8 @@ context("HTML processing", function()
</body>
</html>
- ]], 'content\nheada headb\ndata1 data2\n'},
- {[[
+ ]], 'content\nheada headb\ndata1 data2\n' },
+ { [[
<html lang="en">
<head>
<meta charset="utf-8">
@@ -97,17 +96,398 @@ context("HTML processing", function()
a&nbsp;b a &gt; b a &lt; b a &amp; b &apos;a &quot;a&quot;
</body>
</html>
- ]], 'a b a > b a < b a & b \'a "a"'},
+ ]], 'a b a > b a < b a & b \'a "a"' },
}
- for i,c in ipairs(cases) do
+ for i, c in ipairs(cases) do
test("Extract text from HTML " .. tostring(i), function()
local t = rspamd_util.parse_html(c[1])
assert_not_nil(t)
assert_equal(c[2], tostring(t), string.format("'%s' doesn't match with '%s'",
- c[2], t))
-
+ c[2], t))
end)
end
+
+ -- Test cases for new HTML tag API methods
+ local function parse_html_and_extract_tags(html_content, pool)
+ local rspamd_parsers = require("rspamd_parsers")
+
+ local parsed = rspamd_parsers.parse_html_content(html_content, pool)
+ local tags = {}
+
+ if parsed then
+ parsed:foreach_tag("any", function(tag, content_length, is_leaf)
+ table.insert(tags, tag)
+ return false
+ end)
+ end
+
+ return parsed, tags
+ end
+
+ test("HTML tag get_all_attributes basic test", function()
+ local rspamd_mempool = require("rspamd_mempool")
+ local pool = rspamd_mempool.create()
+
+ local html = [[<div class="test-class" id="test-id" style="color: red;" width="100">content</div>]]
+ local parsed, tags = parse_html_and_extract_tags(html, pool)
+
+ assert_not_nil(parsed)
+ assert_true(#tags > 0)
+
+ -- Find the div tag
+ local div_tag = nil
+ for _, tag in ipairs(tags) do
+ if tag:get_type() == "div" then
+ div_tag = tag
+ break
+ end
+ end
+
+ assert_not_nil(div_tag)
+
+ local attrs = div_tag:get_all_attributes()
+ assert_not_nil(attrs)
+
+ -- Check that we have the expected attributes
+ assert_equal("test-class", attrs["class"])
+ assert_equal("test-id", attrs["id"])
+ assert_equal("color: red;", attrs["style"])
+ assert_equal("100", attrs["width"])
+
+ pool:destroy()
+ end)
+
+ test("HTML tag has_attribute test", function()
+ local rspamd_mempool = require("rspamd_mempool")
+ local pool = rspamd_mempool.create()
+
+ local html = [[<img src="test.jpg" width="100" height="50" alt="Test image" hidden />]]
+ local parsed, tags = parse_html_and_extract_tags(html, pool)
+
+ assert_not_nil(parsed)
+
+ local img_tag = nil
+ for _, tag in ipairs(tags) do
+ if tag:get_type() == "img" then
+ img_tag = tag
+ break
+ end
+ end
+
+ assert_not_nil(img_tag)
+
+ -- Test existing attributes
+ assert_true(img_tag:has_attribute("src"))
+ assert_true(img_tag:has_attribute("width"))
+ assert_true(img_tag:has_attribute("height"))
+ assert_true(img_tag:has_attribute("alt"))
+ assert_true(img_tag:has_attribute("hidden"))
+
+ -- Test non-existing attributes
+ assert_false(img_tag:has_attribute("nonexistent"))
+ assert_false(img_tag:has_attribute("class"))
+ assert_false(img_tag:has_attribute(""))
+
+ pool:destroy()
+ end)
+
+ test("HTML tag get_numeric_attribute test", function()
+ local rspamd_mempool = require("rspamd_mempool")
+ local pool = rspamd_mempool.create()
+
+ local html = [[<div width="200" height="150" font-size="14" opacity="0.8" tabindex="5">content</div>]]
+ local parsed, tags = parse_html_and_extract_tags(html, pool)
+
+ assert_not_nil(parsed)
+
+ local div_tag = nil
+ for _, tag in ipairs(tags) do
+ if tag:get_type() == "div" then
+ div_tag = tag
+ break
+ end
+ end
+
+ assert_not_nil(div_tag)
+
+ -- Test numeric attributes
+ assert_equal(200, div_tag:get_numeric_attribute("width"))
+ assert_equal(150, div_tag:get_numeric_attribute("height"))
+ assert_equal(14, div_tag:get_numeric_attribute("font-size"))
+
+ -- Test opacity with floating-point tolerance
+ local opacity = div_tag:get_numeric_attribute("opacity")
+ assert_not_nil(opacity)
+ assert_true(math.abs(opacity - 0.8) < 0.01, string.format("Expected opacity ~0.8, got %f", opacity))
+
+ assert_equal(5, div_tag:get_numeric_attribute("tabindex"))
+
+ -- Test non-numeric attributes
+ assert_nil(div_tag:get_numeric_attribute("nonexistent"))
+
+ pool:destroy()
+ end)
+
+ test("HTML tag get_unknown_attributes test", function()
+ local rspamd_mempool = require("rspamd_mempool")
+ local pool = rspamd_mempool.create()
+
+ local html = [[<div class="known" data-track="analytics" unknown-attr="test-value" custom-id="12345">content</div>]]
+ local parsed, tags = parse_html_and_extract_tags(html, pool)
+
+ assert_not_nil(parsed)
+
+ local div_tag = nil
+ for _, tag in ipairs(tags) do
+ if tag:get_type() == "div" then
+ div_tag = tag
+ break
+ end
+ end
+
+ assert_not_nil(div_tag)
+
+ local unknown_attrs = div_tag:get_unknown_attributes()
+ assert_not_nil(unknown_attrs)
+
+ -- Should include unknown attributes but not known ones like "class"
+ assert_not_nil(unknown_attrs["unknown-attr"])
+ assert_equal("test-value", unknown_attrs["unknown-attr"])
+ assert_not_nil(unknown_attrs["custom-id"])
+ assert_equal("12345", unknown_attrs["custom-id"])
+
+ -- data-track should be recognized as a known attribute now
+ -- but if not, it would appear in unknown attributes
+
+ pool:destroy()
+ end)
+
+ test("HTML tag get_children test", function()
+ local rspamd_mempool = require("rspamd_mempool")
+ local pool = rspamd_mempool.create()
+
+ local html = [[
+ <div id="parent">
+ <p>First child</p>
+ <span>Second child</span>
+ <img src="test.jpg" />
+ </div>
+ ]]
+ local parsed, tags = parse_html_and_extract_tags(html, pool)
+
+ assert_not_nil(parsed)
+
+ local parent_div = nil
+ for _, tag in ipairs(tags) do
+ if tag:get_type() == "div" and tag:has_attribute("id") and tag:get_attribute("id") == "parent" then
+ parent_div = tag
+ break
+ end
+ end
+
+ assert_not_nil(parent_div)
+
+ local children = parent_div:get_children()
+ assert_not_nil(children)
+ assert_equal(3, #children)
+
+ -- Check child types
+ local child_types = {}
+ for _, child in ipairs(children) do
+ table.insert(child_types, child:get_type())
+ end
+
+ -- Should contain p, span, and img
+ local child_types_str = table.concat(child_types, ",")
+ assert_true(child_types_str:find("p") ~= nil)
+ assert_true(child_types_str:find("span") ~= nil)
+ assert_true(child_types_str:find("img") ~= nil)
+
+ pool:destroy()
+ end)
+
+ test("HTML tag get_attribute vs get_all_attributes consistency", function()
+ local rspamd_mempool = require("rspamd_mempool")
+ local pool = rspamd_mempool.create()
+
+ local html = [[<a href="https://example.com" class="link" target="_blank" title="Example Link">Link</a>]]
+ local parsed, tags = parse_html_and_extract_tags(html, pool)
+
+ assert_not_nil(parsed)
+
+ local a_tag = nil
+ for _, tag in ipairs(tags) do
+ if tag:get_type() == "a" then
+ a_tag = tag
+ break
+ end
+ end
+
+ assert_not_nil(a_tag)
+
+ local all_attrs = a_tag:get_all_attributes()
+
+ -- Test that individual get_attribute calls match get_all_attributes
+ for attr_name, attr_value in pairs(all_attrs) do
+ assert_equal(attr_value, a_tag:get_attribute(attr_name),
+ string.format("Attribute '%s' mismatch: get_attribute='%s', get_all_attributes='%s'",
+ attr_name, a_tag:get_attribute(attr_name) or "nil", attr_value))
+ end
+
+ -- Test specific expected attributes
+ assert_equal("https://example.com", a_tag:get_attribute("href"))
+ assert_equal("link", a_tag:get_attribute("class"))
+ assert_equal("_blank", a_tag:get_attribute("target"))
+ assert_equal("Example Link", a_tag:get_attribute("title"))
+
+ pool:destroy()
+ end)
+
+
+
+ test("HTML tag attribute edge cases", function()
+ local rspamd_mempool = require("rspamd_mempool")
+ local pool = rspamd_mempool.create()
+
+ local html = [[<div class="" hidden style=" " width="0" height="abc">content</div>]]
+ local parsed, tags = parse_html_and_extract_tags(html, pool)
+
+ assert_not_nil(parsed)
+
+ local div_tag = nil
+ for _, tag in ipairs(tags) do
+ if tag:get_type() == "div" then
+ div_tag = tag
+ break
+ end
+ end
+
+ assert_not_nil(div_tag)
+
+ -- Test empty attribute value
+ assert_true(div_tag:has_attribute("class"))
+ assert_equal("", div_tag:get_attribute("class"))
+
+ -- Test boolean attribute (hidden)
+ assert_true(div_tag:has_attribute("hidden"))
+
+ -- Test whitespace-only attribute
+ assert_true(div_tag:has_attribute("style"))
+ assert_equal(" ", div_tag:get_attribute("style"))
+
+ -- Test numeric attributes with edge cases
+ assert_equal(0, div_tag:get_numeric_attribute("width"))
+ assert_nil(div_tag:get_numeric_attribute("height")) -- "abc" is not numeric
+
+ -- Test non-existent attribute
+ assert_false(div_tag:has_attribute("nonexistent"))
+ assert_nil(div_tag:get_attribute("nonexistent"))
+ assert_nil(div_tag:get_numeric_attribute("nonexistent"))
+
+ pool:destroy()
+ end)
+
+ test("HTML tag complex nested structure", function()
+ local rspamd_mempool = require("rspamd_mempool")
+ local pool = rspamd_mempool.create()
+
+ local html = [[
+ <table cellpadding="5" cellspacing="2" border="1">
+ <tr>
+ <td align="center" valign="top" width="100">
+ <img src="image1.jpg" width="80" height="60" alt="Image 1" />
+ </td>
+ <td align="left" valign="middle">
+ <p font-size="12">Text content</p>
+ </td>
+ </tr>
+ </table>
+ ]]
+ local parsed, tags = parse_html_and_extract_tags(html, pool)
+
+ assert_not_nil(parsed)
+
+ -- Find table tag
+ local table_tag = nil
+ for _, tag in ipairs(tags) do
+ if tag:get_type() == "table" then
+ table_tag = tag
+ break
+ end
+ end
+
+ assert_not_nil(table_tag)
+
+ -- Test table attributes
+ assert_equal(5, table_tag:get_numeric_attribute("cellpadding"))
+ assert_equal(2, table_tag:get_numeric_attribute("cellspacing"))
+ assert_equal("1", table_tag:get_attribute("border"))
+
+ -- Test that table has children
+ local children = table_tag:get_children()
+ assert_not_nil(children)
+ assert_true(#children > 0)
+
+ -- Find img tag
+ local img_tag = nil
+ for _, tag in ipairs(tags) do
+ if tag:get_type() == "img" then
+ img_tag = tag
+ break
+ end
+ end
+
+ assert_not_nil(img_tag)
+
+ -- Test img attributes
+ assert_equal("image1.jpg", img_tag:get_attribute("src"))
+ assert_equal(80, img_tag:get_numeric_attribute("width"))
+ assert_equal(60, img_tag:get_numeric_attribute("height"))
+ assert_equal("Image 1", img_tag:get_attribute("alt"))
+
+ pool:destroy()
+ end)
+
+ test("HTML tag with mixed known and unknown attributes", function()
+ local rspamd_mempool = require("rspamd_mempool")
+ local pool = rspamd_mempool.create()
+
+ local html =
+ [[<div class="container" data-analytics="track" custom-attr="value" style="color: blue;" unknown123="test">content</div>]]
+ local parsed, tags = parse_html_and_extract_tags(html, pool)
+
+ assert_not_nil(parsed)
+
+ local div_tag = nil
+ for _, tag in ipairs(tags) do
+ if tag:get_type() == "div" then
+ div_tag = tag
+ break
+ end
+ end
+
+ assert_not_nil(div_tag)
+
+ local all_attrs = div_tag:get_all_attributes()
+ local unknown_attrs = div_tag:get_unknown_attributes()
+
+ -- All attributes should include both known and unknown
+ assert_not_nil(all_attrs["class"]) -- known
+ assert_not_nil(all_attrs["style"]) -- known
+ assert_not_nil(all_attrs["custom-attr"]) -- unknown
+ assert_not_nil(all_attrs["unknown123"]) -- unknown
+
+ -- Unknown attributes should only include unrecognized ones
+ assert_nil(unknown_attrs["class"]) -- known, shouldn't be here
+ assert_nil(unknown_attrs["style"]) -- known, shouldn't be here
+ assert_not_nil(unknown_attrs["custom-attr"]) -- unknown, should be here
+ assert_not_nil(unknown_attrs["unknown123"]) -- unknown, should be here
+
+ assert_equal("value", unknown_attrs["custom-attr"])
+ assert_equal("test", unknown_attrs["unknown123"])
+
+ pool:destroy()
+ end)
end)
diff --git a/test/lua/unit/logger.lua b/test/lua/unit/logger.lua
index dc0120709..c28d8bb09 100644
--- a/test/lua/unit/logger.lua
+++ b/test/lua/unit/logger.lua
@@ -3,17 +3,17 @@ context("Logger unit tests", function()
local log = require "rspamd_logger"
local cases = {
- {'string', 'string'},
- {'%1', 'string', 'string'},
- {'%1', '1.1', 1.1},
- {'%1', '1', 1},
- {'%1', 'true', true},
- {'%1', '{[1] = 1, [2] = test}', {1, 'test'}},
- {'%1', '{[1] = 1, [2] = 2.1, [k2] = test}', {1, 2.1, k2='test'}},
- {'%s', 'true', true},
+ { 'string', 'string' },
+ { '%1', 'string', 'string' },
+ { '%1', '1.1', 1.1 },
+ { '%1', '1', 1 },
+ { '%1', 'true', true },
+ { '%1', '{[1] = 1, [2] = test}', { 1, 'test' } },
+ { '%1', '{[1] = 1, [2] = 2.1, [k2] = test}', { 1, 2.1, k2 = 'test' } },
+ { '%s', 'true', true },
}
- for _,c in ipairs(cases) do
+ for _, c in ipairs(cases) do
local s
if c[3] then
s = log.slog(c[1], c[3])
@@ -21,7 +21,82 @@ context("Logger unit tests", function()
s = log.slog(c[1])
end
assert_equal(s, c[2], string.format("'%s' doesn't match with '%s'",
- c[2], s))
+ c[2], s))
+ end
+ end)
+
+ test("Logger graceful error handling", function()
+ local log = require "rspamd_logger"
+
+ -- Test missing arguments
+ local missing_arg_cases = {
+ { '%1', '<MISSING ARGUMENT>' },
+ { '%0', '<MISSING ARGUMENT>' }, -- %0 is invalid since Lua args are 1-indexed
+ { '%2', '<MISSING ARGUMENT>', 'arg1' },
+ { '%1 %2', 'arg1 <MISSING ARGUMENT>', 'arg1' },
+ { 'prefix %1 %3 suffix', 'prefix arg1 <MISSING ARGUMENT> suffix', 'arg1' },
+ }
+
+ for _, c in ipairs(missing_arg_cases) do
+ local s
+ if c[3] then
+ s = log.slog(c[1], c[3])
+ else
+ s = log.slog(c[1])
+ end
+ assert_equal(s, c[2], string.format("Missing arg test: '%s' doesn't match with '%s'",
+ c[2], s))
+ end
+
+ -- Test extra arguments
+ local extra_arg_cases = {
+ { '%1', 'arg1 <EXTRA 1 ARGUMENTS>', 'arg1', 'extra1' },
+ { '%1', 'arg1 <EXTRA 2 ARGUMENTS>', 'arg1', 'extra1', 'extra2' },
+ { '%s', 'arg1 <EXTRA 1 ARGUMENTS>', 'arg1', 'extra1' },
+ { 'prefix %1 suffix', 'prefix arg1 suffix <EXTRA 1 ARGUMENTS>', 'arg1', 'extra1' },
+ }
+
+ for _, c in ipairs(extra_arg_cases) do
+ local s
+ if c[4] and c[5] then
+ s = log.slog(c[1], c[3], c[4], c[5])
+ elseif c[4] then
+ s = log.slog(c[1], c[3], c[4])
+ else
+ s = log.slog(c[1], c[3])
+ end
+ assert_equal(s, c[2], string.format("Extra arg test: '%s' doesn't match with '%s'",
+ c[2], s))
+ end
+
+ -- Test literal percent sequences (should pass through as-is)
+ local literal_cases = {
+ { '%-1', '%-1' },
+ { '%abc', '%abc' }, -- Should pass through as literal since it's not a valid number
+ { '%', '%' }, -- Single percent should pass through
+ }
+
+ for _, c in ipairs(literal_cases) do
+ local s = log.slog(c[1])
+ assert_equal(s, c[2], string.format("Literal test: '%s' doesn't match with '%s'",
+ c[2], s))
+ end
+
+ -- Test mixed scenarios
+ local mixed_cases = {
+ { '%1 %3', 'arg1 <MISSING ARGUMENT> <EXTRA 1 ARGUMENTS>', 'arg1', 'extra1' },
+ { '%2 %4', 'extra1 <MISSING ARGUMENT> <EXTRA 1 ARGUMENTS>', 'arg1', 'extra1' },
+ }
+
+ for _, c in ipairs(mixed_cases) do
+ local s
+ if c[4] then
+ s = log.slog(c[1], c[3], c[4])
+ else
+ s = log.slog(c[1], c[3])
+ end
+ assert_equal(s, c[2], string.format("Mixed test: '%s' doesn't match with '%s'",
+ c[2], s))
end
end)
end) \ No newline at end of file
diff --git a/test/lua/unit/rspamd_resolver.lua b/test/lua/unit/rspamd_resolver.lua
index e987ff00b..2fdec2c4b 100644
--- a/test/lua/unit/rspamd_resolver.lua
+++ b/test/lua/unit/rspamd_resolver.lua
@@ -6,24 +6,58 @@ context("Check punycoding UTF-8 URL", function()
local resolver = rspamd_resolver.init(rspamd_util.create_event_base(), rspamd_config)
- local cases = {
- -- https://unicode.org/reports/tr46/#Deviations
- ['faß.de'] = 'fass.de', -- IDNA2008 result: xn--fa-hia.de
- ['βόλος.com'] = 'xn--nxasmq6b.com', -- IDNA2008 result: xn--nxasmm1c.com
- ['نامه‌ای.com'] = 'xn--mgba3gch31f.com', -- IDNA2008 result: xn--mgba3gch31f060k.com
- ['ශ්‍රී.com'] = 'xn--10cl1a0b.com', -- IDNA2008 result: xn--10cl1a0b660p.com
-
- -- https://unicode.org/reports/tr46/#Table_Example_Processing
- ['日本語。JP'] = 'xn--wgv71a119e.jp', -- Fullwidth characters are remapped, including 。
- --['u¨.com'] = 'xn--tda.com', -- Normalize changes u + umlaut to ü
- ['☕.us'] = 'xn--53h.us', -- Post-Unicode 3.2 characters are allowed
-
- -- Other
+ -- Helper function to detect IDNA behavior by testing a known conversion
+ local function detect_idna_behavior()
+ -- Use faß.de as a test case - different results in IDNA2003 vs IDNA2008
+ local test_result = resolver:idna_convert_utf8('faß.de')
+ if test_result == 'fass.de' then
+ return 'transitional' -- IDNA2003/transitional behavior
+ elseif test_result == 'xn--fa-hia.de' then
+ return 'nontransitional' -- IDNA2008/nontransitional behavior
+ else
+ return 'unknown'
+ end
+ end
+
+ local idna_behavior = detect_idna_behavior()
+
+ -- Define test cases with both expected results
+ local cases_transitional = {
+ -- IDNA2003/transitional results (ICU < 76 default)
+ ['faß.de'] = 'fass.de',
+ ['βόλος.com'] = 'xn--nxasmq6b.com',
+ ['نامه‌ای.com'] = 'xn--mgba3gch31f.com',
+ ['ශ්‍රී.com'] = 'xn--10cl1a0b.com',
+ ['日本語。JP'] = 'xn--wgv71a119e.jp',
+ ['☕.us'] = 'xn--53h.us',
+ ['example.рф'] = 'example.xn--p1ai',
+ }
+
+ local cases_nontransitional = {
+ -- IDNA2008/nontransitional results (ICU >= 76 default)
+ ['faß.de'] = 'xn--fa-hia.de',
+ ['βόλος.com'] = 'xn--nxasmm1c.com',
+ ['نامه‌ای.com'] = 'xn--mgba3gch31f060k.com',
+ ['ශ්‍රී.com'] = 'xn--10cl1a0b660p.com',
+ ['日本語。JP'] = 'xn--wgv71a119e.jp',
+ ['☕.us'] = 'xn--53h.us',
['example.рф'] = 'example.xn--p1ai',
}
+ -- Choose appropriate test cases based on detected behavior
+ local cases
+ if idna_behavior == 'transitional' then
+ cases = cases_transitional
+ print("Detected IDNA transitional behavior (ICU < 76 or configured for IDNA2003)")
+ elseif idna_behavior == 'nontransitional' then
+ cases = cases_nontransitional
+ print("Detected IDNA nontransitional behavior (ICU >= 76 default)")
+ else
+ error("Could not detect IDNA behavior - unexpected result for test case")
+ end
+
for k, v in pairs(cases) do
- test(string.format("punycode %s -> %s", k, v), function()
+ test(string.format("punycode %s -> %s (%s)", k, v, idna_behavior), function()
local res = resolver:idna_convert_utf8(k)
assert_equal(res, v)
end)
diff --git a/test/rspamd_cryptobox_test.c b/test/rspamd_cryptobox_test.c
index 03b833404..82225d071 100644
--- a/test/rspamd_cryptobox_test.c
+++ b/test/rspamd_cryptobox_test.c
@@ -1,5 +1,5 @@
/*
- * Copyright 2024 Vsevolod Stakhov
+ * Copyright 2025 Vsevolod Stakhov
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -153,7 +153,6 @@ create_constrained_split(struct rspamd_cryptobox_segment *seg, int mseg,
void rspamd_cryptobox_test_func(void)
{
- void *map;
unsigned char *begin, *end;
rspamd_nm_t key;
rspamd_nonce_t nonce;
@@ -161,9 +160,8 @@ void rspamd_cryptobox_test_func(void)
struct rspamd_cryptobox_segment *seg;
double t1, t2;
int i, cnt, ms;
- gboolean checked_openssl = FALSE;
- map = create_mapping(mapping_size, &begin, &end);
+ create_mapping(mapping_size, &begin, &end);
ottery_rand_bytes(key, sizeof(key));
ottery_rand_bytes(nonce, sizeof(nonce));
diff --git a/test/rspamd_cxx_unit_cryptobox.hxx b/test/rspamd_cxx_unit_cryptobox.hxx
index 7d9c76b4e..4624e2f93 100644
--- a/test/rspamd_cxx_unit_cryptobox.hxx
+++ b/test/rspamd_cxx_unit_cryptobox.hxx
@@ -1,5 +1,5 @@
/*
- * Copyright 2024 Vsevolod Stakhov
+ * Copyright 2025 Vsevolod Stakhov
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -245,6 +245,225 @@ TEST_SUITE("rspamd_cryptobox")
auto out_arr = std::vector(std::begin(out), std::end(out));
CHECK(out_arr == expected_arr);
}
+
+ // Test vectors for XChaCha20-Poly1305 compatibility with Go implementation
+ // These test cases use the same inputs as the Go version to verify compatibility
+
+ TEST_CASE("rspamd xchacha20poly1305 compatibility all_zeros_64_bytes")
+ {
+ // Test case: all_zeros_64_bytes
+ // Key: 32 zero bytes
+ // Nonce: 24 zero bytes
+ // Plaintext: 64 zero bytes
+
+ rspamd_nm_t key;
+ memset(key, 0, sizeof(key));
+
+ rspamd_nonce_t nonce;
+ memset(nonce, 0, sizeof(nonce));
+
+ unsigned char plaintext[64];
+ memset(plaintext, 0, sizeof(plaintext));
+
+ // Expected values from C implementation
+ unsigned char expected_cipher[64] = {
+ 0x78, 0x9e, 0x96, 0x89, 0xe5, 0x20, 0x8d, 0x7f, 0xd9, 0xe1, 0xf3, 0xc5, 0xb5, 0x34, 0x1f, 0x48,
+ 0xef, 0x18, 0xa1, 0x3e, 0x41, 0x89, 0x98, 0xad, 0xda, 0xdd, 0x97, 0xa3, 0x69, 0x3a, 0x98, 0x7f,
+ 0x8e, 0x82, 0xec, 0xd5, 0xc1, 0x43, 0x3b, 0xfe, 0xd1, 0xaf, 0x49, 0x75, 0x0c, 0x0f, 0x1f, 0xf2,
+ 0x9c, 0x41, 0x74, 0xa0, 0x5b, 0x11, 0x9a, 0xa3, 0xa9, 0xe8, 0x33, 0x38, 0x12, 0xe0, 0xc0, 0xfe};
+
+ rspamd_mac_t expected_mac = {
+ 0x9c, 0x22, 0xbd, 0x8b, 0x7d, 0x68, 0x00, 0xca, 0x3f, 0x9d, 0xf1, 0xc0, 0x3e, 0x31, 0x3e, 0x68};
+
+ // Test encryption using Rspamd's nm (shared key) encryption
+ unsigned char ciphertext[64];
+ memcpy(ciphertext, plaintext, sizeof(plaintext));
+
+ rspamd_mac_t mac;
+
+ rspamd_cryptobox_encrypt_nm_inplace(ciphertext, sizeof(ciphertext), nonce, key, mac);
+
+ CHECK(memcmp(ciphertext, expected_cipher, sizeof(expected_cipher)) == 0);
+ CHECK(memcmp(mac, expected_mac, sizeof(expected_mac)) == 0);
+
+ // Test decryption
+ gboolean decrypt_ok = rspamd_cryptobox_decrypt_nm_inplace(ciphertext, sizeof(ciphertext), nonce, key, mac);
+ CHECK(decrypt_ok == TRUE);
+ CHECK(memcmp(ciphertext, plaintext, sizeof(plaintext)) == 0);
+ }
+
+ TEST_CASE("rspamd xchacha20poly1305 compatibility all_zeros_128_bytes")
+ {
+ // Test case: all_zeros_128_bytes
+ // Key: 32 zero bytes
+ // Nonce: 24 zero bytes
+ // Plaintext: 128 zero bytes
+
+ rspamd_nm_t key;
+ memset(key, 0, sizeof(key));
+
+ rspamd_nonce_t nonce;
+ memset(nonce, 0, sizeof(nonce));
+
+ unsigned char plaintext[128];
+ memset(plaintext, 0, sizeof(plaintext));
+
+ unsigned char expected_cipher[128] = {
+ 0x78, 0x9e, 0x96, 0x89, 0xe5, 0x20, 0x8d, 0x7f, 0xd9, 0xe1, 0xf3, 0xc5, 0xb5, 0x34, 0x1f, 0x48,
+ 0xef, 0x18, 0xa1, 0x3e, 0x41, 0x89, 0x98, 0xad, 0xda, 0xdd, 0x97, 0xa3, 0x69, 0x3a, 0x98, 0x7f,
+ 0x8e, 0x82, 0xec, 0xd5, 0xc1, 0x43, 0x3b, 0xfe, 0xd1, 0xaf, 0x49, 0x75, 0x0c, 0x0f, 0x1f, 0xf2,
+ 0x9c, 0x41, 0x74, 0xa0, 0x5b, 0x11, 0x9a, 0xa3, 0xa9, 0xe8, 0x33, 0x38, 0x12, 0xe0, 0xc0, 0xfe,
+ 0xa4, 0x9e, 0x1e, 0xe0, 0x13, 0x4a, 0x70, 0xa9, 0xd4, 0x9c, 0x24, 0xe0, 0xcb, 0xd8, 0xfc, 0x3b,
+ 0xa2, 0x7e, 0x97, 0xc3, 0x32, 0x2a, 0xd4, 0x87, 0xf7, 0x78, 0xf8, 0xdc, 0x6a, 0x12, 0x2f, 0xa5,
+ 0x9c, 0xbe, 0x33, 0xe7, 0x78, 0xea, 0x2e, 0x50, 0xbb, 0x59, 0x09, 0xc9, 0x97, 0x1c, 0x4f, 0xec,
+ 0x2f, 0x93, 0x52, 0x3f, 0x77, 0x89, 0x2d, 0x17, 0xca, 0xa5, 0x81, 0x67, 0xde, 0xc4, 0xd6, 0xc7};
+
+ rspamd_mac_t expected_mac = {
+ 0xcf, 0xe1, 0x4a, 0xc3, 0x39, 0x35, 0xd3, 0x63, 0x1a, 0x06, 0xbf, 0x55, 0x88, 0xf4, 0x12, 0xfa};
+
+ unsigned char ciphertext[128];
+ memcpy(ciphertext, plaintext, sizeof(plaintext));
+
+ rspamd_mac_t mac;
+ rspamd_cryptobox_encrypt_nm_inplace(ciphertext, sizeof(ciphertext), nonce, key, mac);
+
+ CHECK(memcmp(ciphertext, expected_cipher, sizeof(expected_cipher)) == 0);
+ CHECK(memcmp(mac, expected_mac, sizeof(expected_mac)) == 0);
+
+ // Test decryption
+ gboolean decrypt_ok = rspamd_cryptobox_decrypt_nm_inplace(ciphertext, sizeof(ciphertext), nonce, key, mac);
+ CHECK(decrypt_ok == TRUE);
+ CHECK(memcmp(ciphertext, plaintext, sizeof(plaintext)) == 0);
+ }
+
+ TEST_CASE("rspamd xchacha20poly1305 compatibility test_pattern_64_bytes")
+ {
+ // Test case: test_pattern_64_bytes
+ // Key: 0x01 repeated 32 times
+ // Nonce: 0x01, 0x02, 0x03, ... 0x18 (24 bytes)
+ // Plaintext: 0x00, 0x01, 0x02, ... 0x41 (66 bytes)
+
+ rspamd_nm_t key;
+ memset(key, 0x01, sizeof(key));
+
+ rspamd_nonce_t nonce = {
+ 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09, 0x10,
+ 0x11, 0x12, 0x13, 0x14, 0x15, 0x16, 0x17, 0x18, 0x19, 0x20,
+ 0x21, 0x22, 0x23, 0x24};
+
+ unsigned char plaintext[66];
+ for (int i = 0; i < 66; i++) {
+ plaintext[i] = i;
+ }
+
+ // Expected values from C implementation
+ unsigned char expected_cipher[66] = {
+ 0xe6, 0x0e, 0xf7, 0x6d, 0x7f, 0x04, 0x37, 0x81, 0x9f, 0x60, 0x03, 0x28, 0x60, 0xb1, 0x2b, 0xaa,
+ 0xae, 0x2b, 0x13, 0xef, 0x6d, 0xd3, 0x18, 0xf1, 0x3b, 0xc6, 0x06, 0xfb, 0x65, 0x9a, 0x53, 0x3b,
+ 0x23, 0xe6, 0x99, 0x0c, 0x65, 0x2f, 0xbf, 0x56, 0xcb, 0x7c, 0x18, 0x53, 0xa8, 0xbc, 0x11, 0xc4,
+ 0x0b, 0x35, 0xc9, 0x40, 0x9a, 0xc2, 0xe1, 0x7f, 0x1a, 0x72, 0xaa, 0xb3, 0x8b, 0x4e, 0x21, 0x32,
+ 0x87, 0xf7};
+
+ rspamd_mac_t expected_mac = {
+ 0xf2, 0xa7, 0xbd, 0xae, 0x53, 0x68, 0xfe, 0xd8, 0x4c, 0x92, 0xe8, 0x52, 0x35, 0x4d, 0x78, 0x7c};
+
+ unsigned char ciphertext[66];
+ memcpy(ciphertext, plaintext, sizeof(plaintext));
+
+ rspamd_mac_t mac;
+
+ rspamd_cryptobox_encrypt_nm_inplace(ciphertext, sizeof(ciphertext), nonce, key, mac);
+
+ CHECK(memcmp(ciphertext, expected_cipher, sizeof(expected_cipher)) == 0);
+ CHECK(memcmp(mac, expected_mac, sizeof(expected_mac)) == 0);
+
+ // Test decryption
+ gboolean decrypt_ok = rspamd_cryptobox_decrypt_nm_inplace(ciphertext, sizeof(ciphertext), nonce, key, mac);
+ CHECK(decrypt_ok == TRUE);
+ CHECK(memcmp(ciphertext, plaintext, sizeof(plaintext)) == 0);
+ }
+
+ TEST_CASE("rspamd mac key derivation compatibility all_zeros")
+ {
+ // Test MAC key derivation process
+ // Key: 32 zero bytes
+ // Nonce: 24 zero bytes
+
+ rspamd_nm_t key;
+ memset(key, 0, sizeof(key));
+
+ rspamd_nonce_t nonce;
+ memset(nonce, 0, sizeof(nonce));
+
+ // Expected values from C implementation
+ unsigned char expected_subkey[64] = {
+ 0xbc, 0xd0, 0x2a, 0x18, 0xbf, 0x3f, 0x01, 0xd1, 0x92, 0x92, 0xde, 0x30, 0xa7, 0xa8, 0xfd, 0xac,
+ 0xa4, 0xb6, 0x5e, 0x50, 0xa6, 0x00, 0x2c, 0xc7, 0x2c, 0xd6, 0xd2, 0xf7, 0xc9, 0x1a, 0xc3, 0xd5,
+ 0x72, 0x8f, 0x83, 0xe0, 0xaa, 0xd2, 0xbf, 0xcf, 0x9a, 0xbd, 0x2d, 0x2d, 0xb5, 0x8f, 0xae, 0xdd,
+ 0x65, 0x01, 0x5d, 0xd8, 0x3f, 0xc0, 0x9b, 0x13, 0x1e, 0x27, 0x10, 0x43, 0x01, 0x9e, 0x8e, 0x0f};
+
+ unsigned char expected_mac_key[32] = {
+ 0xbc, 0xd0, 0x2a, 0x18, 0xbf, 0x3f, 0x01, 0xd1, 0x92, 0x92, 0xde, 0x30, 0xa7, 0xa8, 0xfd, 0xac,
+ 0xa4, 0xb6, 0x5e, 0x50, 0xa6, 0x00, 0x2c, 0xc7, 0x2c, 0xd6, 0xd2, 0xf7, 0xc9, 0x1a, 0xc3, 0xd5};
+
+ // Generate subkey using XChaCha20 (first 64 bytes of keystream)
+ // This simulates the MAC key derivation process used in secretbox
+ unsigned char subkey[64];
+ memset(subkey, 0, sizeof(subkey));
+
+ // Use libsodium's ChaCha20 directly to generate the subkey
+ // This matches what happens inside the secretbox implementation
+ crypto_stream_xchacha20(subkey, sizeof(subkey), nonce, key);
+
+ // MAC key is first 32 bytes of subkey
+ unsigned char mac_key[32];
+ memcpy(mac_key, subkey, 32);
+
+ CHECK(memcmp(subkey, expected_subkey, sizeof(expected_subkey)) == 0);
+ CHECK(memcmp(mac_key, expected_mac_key, sizeof(expected_mac_key)) == 0);
+ }
+
+ TEST_CASE("rspamd mac key derivation compatibility test_pattern")
+ {
+ // Test MAC key derivation process
+ // Key: 0x01 repeated 32 times
+ // Nonce: 0x01, 0x02, 0x03, ... 0x18 (24 bytes)
+
+ rspamd_nm_t key;
+ memset(key, 0x01, sizeof(key));
+
+ rspamd_nonce_t nonce = {
+ 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09, 0x10,
+ 0x11, 0x12, 0x13, 0x14, 0x15, 0x16, 0x17, 0x18, 0x19, 0x20,
+ 0x21, 0x22, 0x23, 0x24};
+
+ // Expected values from C implementation
+ unsigned char expected_subkey[64] = {
+ 0x47, 0xa6, 0xe3, 0xb5, 0x0f, 0xd4, 0x7f, 0x08, 0xb5, 0x35, 0x80, 0xfc, 0x93, 0x66, 0x1a, 0x7f,
+ 0x9c, 0xf5, 0x8c, 0x93, 0xae, 0x4e, 0x3f, 0xcf, 0x86, 0xb7, 0xdf, 0x34, 0x48, 0x73, 0x33, 0xdb,
+ 0x71, 0x31, 0x0f, 0xe1, 0xcc, 0xd9, 0x0c, 0x0a, 0x1a, 0x19, 0x54, 0x30, 0xdf, 0xe3, 0xda, 0xee,
+ 0x70, 0x29, 0xd9, 0xae, 0xf6, 0x4d, 0x78, 0xe3, 0xe8, 0x43, 0x98, 0xea, 0xaa, 0xd8, 0x85, 0x79};
+
+ unsigned char expected_mac_key[32] = {
+ 0x47, 0xa6, 0xe3, 0xb5, 0x0f, 0xd4, 0x7f, 0x08, 0xb5, 0x35, 0x80, 0xfc, 0x93, 0x66, 0x1a, 0x7f,
+ 0x9c, 0xf5, 0x8c, 0x93, 0xae, 0x4e, 0x3f, 0xcf, 0x86, 0xb7, 0xdf, 0x34, 0x48, 0x73, 0x33, 0xdb};
+
+ // Generate subkey using XChaCha20 (first 64 bytes of keystream)
+ // This simulates the MAC key derivation process used in secretbox
+ unsigned char subkey[64];
+ memset(subkey, 0, sizeof(subkey));
+
+ // Use libsodium's ChaCha20 directly to generate the subkey
+ // This matches what happens inside the secretbox implementation
+ crypto_stream_xchacha20(subkey, sizeof(subkey), nonce, key);
+
+ // MAC key is first 32 bytes of subkey
+ unsigned char mac_key[32];
+ memcpy(mac_key, subkey, 32);
+
+ CHECK(memcmp(subkey, expected_subkey, sizeof(expected_subkey)) == 0);
+ CHECK(memcmp(mac_key, expected_mac_key, sizeof(expected_mac_key)) == 0);
+ }
}
#endif
diff --git a/test/rspamd_dns_test.c b/test/rspamd_dns_test.c
index d041351df..ccdc47820 100644
--- a/test/rspamd_dns_test.c
+++ b/test/rspamd_dns_test.c
@@ -1,4 +1,20 @@
+/*
+ * Copyright 2025 Vsevolod Stakhov
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
#include "config.h"
#include "tests.h"
#include "dns.h"
@@ -51,6 +67,9 @@ test_dns_cb(struct rdns_reply *reply, gpointer arg)
case RDNS_REQUEST_MX:
msg_debug("got mx %s:%d", cur->content.mx.name, cur->content.mx.priority);
break;
+ default:
+ msg_debug("got unknown type %d", cur->type);
+ break;
}
cur = cur->next;
}
diff --git a/test/rspamd_shingles_test.c b/test/rspamd_shingles_test.c
index d1a10de84..5b88f4b2d 100644
--- a/test/rspamd_shingles_test.c
+++ b/test/rspamd_shingles_test.c
@@ -17,6 +17,7 @@
#include "rspamd.h"
#include "shingles.h"
#include "ottery.h"
+#include "libserver/word.h"
#include <math.h>
static const char *
@@ -52,63 +53,76 @@ generate_random_string(char *begin, size_t len)
}
}
-static GArray *
+static rspamd_words_t *
generate_fuzzy_words(gsize cnt, gsize max_len)
{
- GArray *res;
+ rspamd_words_t *res;
gsize i, wlen;
- rspamd_ftok_t w;
+ rspamd_word_t word;
char *t;
- res = g_array_sized_new(FALSE, FALSE, sizeof(rspamd_ftok_t), cnt);
+ res = g_malloc(sizeof(*res));
+ kv_init(*res);
for (i = 0; i < cnt; i++) {
wlen = ottery_rand_range(max_len) + 1;
/* wlen = max_len; */
- w.len = wlen;
t = g_malloc(wlen);
generate_random_string(t, wlen);
- w.begin = t;
- g_array_append_val(res, w);
+
+ memset(&word, 0, sizeof(word));
+ word.stemmed.begin = t;
+ word.stemmed.len = wlen;
+ word.original.begin = t;
+ word.original.len = wlen;
+ word.flags = 0; /* No flags set, so it won't be skipped */
+
+ kv_push(rspamd_word_t, *res, word);
}
return res;
}
static void
-permute_vector(GArray *in, double prob)
+permute_vector(rspamd_words_t *in, double prob)
{
gsize i, total = 0;
- rspamd_ftok_t *w;
+ rspamd_word_t *w;
- for (i = 0; i < in->len; i++) {
+ for (i = 0; i < kv_size(*in); i++) {
if (ottery_rand_unsigned() <= G_MAXUINT * prob) {
- w = &g_array_index(in, rspamd_ftok_t, i);
- generate_random_string((char *) w->begin, w->len);
+ w = &kv_A(*in, i);
+ generate_random_string((char *) w->stemmed.begin, w->stemmed.len);
+ /* Also update original since they point to same memory */
+ w->original.begin = w->stemmed.begin;
+ w->original.len = w->stemmed.len;
total++;
}
}
- msg_debug("generated %z permutations of %ud words", total, in->len);
+ msg_debug("generated %z permutations of %ud words", total, (unsigned int) kv_size(*in));
}
static void
-free_fuzzy_words(GArray *ar)
+free_fuzzy_words(rspamd_words_t *ar)
{
gsize i;
- rspamd_ftok_t *w;
+ rspamd_word_t *w;
- for (i = 0; i < ar->len; i++) {
- w = &g_array_index(ar, rspamd_ftok_t, i);
- g_free((gpointer) w->begin);
+ for (i = 0; i < kv_size(*ar); i++) {
+ w = &kv_A(*ar, i);
+ g_free((gpointer) w->stemmed.begin);
}
+
+ kv_destroy(*ar);
+ g_free(ar);
}
static void
test_case(gsize cnt, gsize max_len, double perm_factor,
enum rspamd_shingle_alg alg)
{
- GArray *input;
+ rspamd_words_t *input;
struct rspamd_shingle *sgl, *sgl_permuted;
double res;
unsigned char key[16];
@@ -281,51 +295,59 @@ void rspamd_shingles_test_func(void)
enum rspamd_shingle_alg alg = RSPAMD_SHINGLES_OLD;
struct rspamd_shingle *sgl;
unsigned char key[16];
- GArray *input;
- rspamd_ftok_t tok;
+ rspamd_words_t input;
+ rspamd_word_t word;
int i;
memset(key, 0, sizeof(key));
- input = g_array_sized_new(FALSE, FALSE, sizeof(rspamd_ftok_t), 5);
+ kv_init(input);
for (i = 0; i < 5; i++) {
char *b = g_alloca(8);
memset(b, 0, 8);
memcpy(b + 1, "test", 4);
b[0] = 'a' + i;
- tok.begin = b;
- tok.len = 5 + ((i + 1) % 4);
- g_array_append_val(input, tok);
+
+ memset(&word, 0, sizeof(word));
+ word.stemmed.begin = b;
+ word.stemmed.len = 5 + ((i + 1) % 4);
+ word.original.begin = b;
+ word.original.len = word.stemmed.len;
+ word.flags = 0; /* No flags set, so it won't be skipped */
+
+ kv_push(rspamd_word_t, input, word);
}
- sgl = rspamd_shingles_from_text(input, key, NULL,
+ sgl = rspamd_shingles_from_text(&input, key, NULL,
rspamd_shingles_default_filter, NULL, RSPAMD_SHINGLES_OLD);
for (i = 0; i < RSPAMD_SHINGLE_SIZE; i++) {
g_assert(sgl->hashes[i] == expected_old[i]);
}
g_free(sgl);
- sgl = rspamd_shingles_from_text(input, key, NULL,
+ sgl = rspamd_shingles_from_text(&input, key, NULL,
rspamd_shingles_default_filter, NULL, RSPAMD_SHINGLES_XXHASH);
for (i = 0; i < RSPAMD_SHINGLE_SIZE; i++) {
g_assert(sgl->hashes[i] == expected_xxhash[i]);
}
g_free(sgl);
- sgl = rspamd_shingles_from_text(input, key, NULL,
+ sgl = rspamd_shingles_from_text(&input, key, NULL,
rspamd_shingles_default_filter, NULL, RSPAMD_SHINGLES_MUMHASH);
for (i = 0; i < RSPAMD_SHINGLE_SIZE; i++) {
g_assert(sgl->hashes[i] == expected_mumhash[i]);
}
g_free(sgl);
- sgl = rspamd_shingles_from_text(input, key, NULL,
+ sgl = rspamd_shingles_from_text(&input, key, NULL,
rspamd_shingles_default_filter, NULL, RSPAMD_SHINGLES_FAST);
for (i = 0; i < RSPAMD_SHINGLE_SIZE; i++) {
g_assert(sgl->hashes[i] == expected_fasthash[i]);
}
g_free(sgl);
+ kv_destroy(input);
+
for (alg = RSPAMD_SHINGLES_OLD; alg <= RSPAMD_SHINGLES_FAST; alg++) {
test_case(200, 10, 0.1, alg);
test_case(500, 20, 0.01, alg);