diff --git a/luci-app-passwall2/Makefile b/luci-app-passwall2/Makefile index 63f0c792e..ebaffbc62 100644 --- a/luci-app-passwall2/Makefile +++ b/luci-app-passwall2/Makefile @@ -7,7 +7,7 @@ include $(TOPDIR)/rules.mk PKG_NAME:=luci-app-passwall2 PKG_VERSION:=26.10.20 -PKG_RELEASE:=150 +PKG_RELEASE:=151 PKG_PO_VERSION:=$(PKG_VERSION) PKG_CONFIG_DEPENDS:= \ diff --git a/luci-app-passwall2/po/fa/passwall2.po b/luci-app-passwall2/po/fa/passwall2.po index 10762bb58..06bb1f226 100644 --- a/luci-app-passwall2/po/fa/passwall2.po +++ b/luci-app-passwall2/po/fa/passwall2.po @@ -2291,6 +2291,9 @@ msgstr "خطای تأیید اندازه فایل دانلود شده. اندا msgid "%s SHA256 verification information is invalid. Update failed." msgstr "اطلاعات تأیید SHA256 برای %s نامعتبر است. به‌روزرسانی ناموفق بود." +msgid "%s file format verification failed. Update failed." +msgstr "قالب فایل %s نامعتبر است. به‌روزرسانی ناموفق بود." + msgid "Downloaded file is empty or an error occurred while reading it." msgstr "فایل دانلود شده خالی است یا خطایی هنگام خواندن آن رخ داده است." diff --git a/luci-app-passwall2/po/ru/passwall2.po b/luci-app-passwall2/po/ru/passwall2.po index fa79867c2..e5d8985b4 100644 --- a/luci-app-passwall2/po/ru/passwall2.po +++ b/luci-app-passwall2/po/ru/passwall2.po @@ -2292,6 +2292,9 @@ msgstr "Ошибка проверки размера файла. Исходны msgid "%s SHA256 verification information is invalid. Update failed." msgstr "Недопустимые данные проверки SHA256 для %s. Обновление не выполнено." +msgid "%s file format verification failed. Update failed." +msgstr "Ошибка проверки формата файла %s. Обновление не выполнено." + msgid "Downloaded file is empty or an error occurred while reading it." msgstr "Загруженный файл пуст или произошла ошибка при чтении." diff --git a/luci-app-passwall2/po/zh-cn/passwall2.po b/luci-app-passwall2/po/zh-cn/passwall2.po index 09d0955e4..da2002425 100644 --- a/luci-app-passwall2/po/zh-cn/passwall2.po +++ b/luci-app-passwall2/po/zh-cn/passwall2.po @@ -2277,6 +2277,9 @@ msgstr "下载文件大小校验出错,原始文件大小 %sB,下载文件 msgid "%s SHA256 verification information is invalid. Update failed." msgstr "%s SHA256 校验信息无效,更新失败。" +msgid "%s file format verification failed. Update failed." +msgstr "%s 文件格式校验错误,更新失败。" + msgid "Downloaded file is empty or an error occurred while reading it." msgstr "下载的文件为空或读取文件时发生错误。" diff --git a/luci-app-passwall2/po/zh-tw/passwall2.po b/luci-app-passwall2/po/zh-tw/passwall2.po index dfdbbf4d6..9ec22971e 100644 --- a/luci-app-passwall2/po/zh-tw/passwall2.po +++ b/luci-app-passwall2/po/zh-tw/passwall2.po @@ -2283,6 +2283,9 @@ msgstr "下載檔案大小校驗出錯,原始檔案大小 %sB,下載檔案 msgid "%s SHA256 verification information is invalid. Update failed." msgstr "%s SHA256 校驗資訊無效,更新失敗。" +msgid "%s file format verification failed. Update failed." +msgstr "%s 檔案格式校驗錯誤,更新失敗。" + msgid "Downloaded file is empty or an error occurred while reading it." msgstr "下載的文件為空或讀取文件時發生錯誤。" diff --git a/luci-app-passwall2/root/usr/share/passwall2/rule_update.lua b/luci-app-passwall2/root/usr/share/passwall2/rule_update.lua index d854c95cb..d5e98bb93 100755 --- a/luci-app-passwall2/root/usr/share/passwall2/rule_update.lua +++ b/luci-app-passwall2/root/usr/share/passwall2/rule_update.lua @@ -109,6 +109,98 @@ local function non_file_check(file_path, header_content) return false end +-- Check every Geo category boundary and sample its first rule to identify the format. +local function validate_geo_file(file_path, geo_type) + if geo_type ~= "geoip" and geo_type ~= "geosite" then return false end + local file = io.open(file_path, "rb") + if not file then return false end + local size = file:seek("end") + if not size or size == 0 or not file:seek("set", 0) then file:close(); return false end + local pos, rules = 0, 0 + -- Use the file's read buffer and seek past skipped content without an extra buffer. + local function skip(length, limit) + assert(length <= limit - pos) + pos = pos + length + assert(file:seek("set", pos)) + end + -- Without a numeric limit, only validate and skip varints to avoid precision loss for 64-bit attributes. + local function varint(limit, maximum) + local value, factor = 0, 1 + for i = 1, 10 do + assert(pos < limit) + local byte = assert(file:read(1)):byte() + pos = pos + 1 + assert(i < 10 or byte <= 1) + if maximum then + local digit = byte % 128 + assert(digit <= math.floor((maximum - value) / factor)) + value = value + digit * factor + end + if byte < 128 then return value end + factor = factor * 128 + end + error(false) + end + -- Field definitions: wire type, optional child message type, optional numeric limit. + local schema = { + list = {{2, geo_type}}, + geoip = {{2}, {2, "cidr"}, {0}}, + geosite = {{2}, {2, "domain"}}, + cidr = {{2}, {0, nil, 4294967295}}, + domain = {{0, nil, 3}, {2}, {2, "attribute"}}, + attribute = {{2}, {0}, {0}} + } + local parse + parse = function(kind, limit) + local values, entries = {}, 0 + local is_entry = kind == "geoip" or kind == "geosite" + while pos < limit do + if is_entry and values[1] and values[1] > 0 and entries > 0 then + skip(limit - pos, limit) + return + end + local tag = varint(limit, 4294967295) + local field, wire = math.floor(tag / 8), tag % 8 + local spec = schema[kind][field] + assert(field > 0 and (not spec or wire == spec[1])) + local value + if wire == 0 then + value = varint(limit, spec and spec[3]) + elseif wire == 2 then + value = varint(limit, limit - pos) + assert(value <= limit - pos) + if spec and spec[2] and not (is_entry and entries > 0) then + parse(spec[2], pos + value) + else + skip(value, limit) + end + if spec and spec[2] then entries = entries + 1 end + elseif wire == 1 or wire == 5 then + skip(wire == 1 and 8 or 4, limit) + else + -- Geo files do not use Protobuf group fields. + error(false) + end + if spec then values[field] = value end + end + if kind == "list" then + assert(entries > 0 and rules > 0) + elseif kind == "geoip" or kind == "geosite" or kind == "attribute" then + assert(values[1] and values[1] > 0) + elseif kind == "cidr" then + assert(values[1] == 4 or values[1] == 16) + assert((values[2] or 0) <= (values[1] == 4 and 32 or 128)) + rules = rules + 1 + elseif kind == "domain" then + assert(values[2] and values[2] > 0) + rules = rules + 1 + end + end + local valid = pcall(parse, "list", size) + file:close() + return valid +end + local function fetch_geofile(geo_name, geo_type, url) local tmp_path = "/tmp/" .. geo_name local asset_path = asset_location .. geo_name @@ -160,6 +252,10 @@ local function fetch_geofile(geo_name, geo_type, url) return 1 end else + if not validate_geo_file(tmp_path, geo_type) then + log(1, api.i18n.translatef("%s file format verification failed. Update failed.", geo_type)) + return 1 + end if fs.access(asset_path) and sys.call(string.format("cmp -s %s %s", tmp_path, asset_path)) == 0 then log(1, api.i18n.translatef("%s version is the same and does not need to be updated.", geo_type)) return 0