WFN 转换
本示例演示如何在 CPE 格式和 Well-Formed Name(WFN)格式之间进行转换,以及如何使用 WFN 进行高效的处理和匹配操作。
概述
Well-Formed Name (WFN) 是 CPE 的内部标准表示格式,提供了一种规范化的方式来表示 CPE 组件,使匹配和比较操作更加高效和可靠。
下图以 WFN 为中心展示了三种格式的互转,绑定(bind)与解绑(unbind)操作在 WFN 与各外部表示之间转换:
完整示例
go
package main
import (
"fmt"
"log"
"github.com/scagogogo/cpe-skills"
)
func main() {
fmt.Println("=== WFN 转换示例 ===")
// 示例 1:CPE 到 WFN 转换
fmt.Println("\n1. CPE 到 WFN 转换:")
cpeStrings := []string{
"cpe:2.3:a:microsoft:windows:10:*:*:*:*:*:*:*",
"cpe:2.3:a:apache:tomcat:9.0.0:*:*:*:*:*:*:*",
"cpe:/a:oracle:java:1.8.0_291",
"cpe:2.3:o:linux:kernel:5.4.0:*:*:*:*:*:*:*",
}
for i, cpeStr := range cpeStrings {
fmt.Printf("\n示例 %d: %s\n", i+1, cpeStr)
// 解析 CPE 字符串(自动识别 2.2 / 2.3 格式)
cpeObj, err := cpeskills.Parse(cpeStr)
if err != nil {
log.Printf("解析 CPE 失败: %v", err)
continue
}
// 将 CPE 转换为 WFN
wfn := cpeskills.FromCPE(cpeObj)
fmt.Printf(" 原始 CPE: %s\n", cpeStr)
fmt.Printf(" WFN 格式: %s\n", wfn.WFNString())
fmt.Printf(" 部件: %s\n", wfn.Get(cpeskills.AttrPart))
fmt.Printf(" 供应商: %s\n", wfn.Get(cpeskills.AttrVendor))
fmt.Printf(" 产品: %s\n", wfn.Get(cpeskills.AttrProduct))
fmt.Printf(" 版本: %s\n", wfn.Get(cpeskills.AttrVersion))
}
// 示例 2:WFN 到 CPE 转换
fmt.Println("\n2. WFN 到 CPE 转换:")
// 手动创建 WFN(未设置的字符串字段通过 Get 读回为 ANY)
wfn := &cpeskills.WFN{
Part: "a",
Vendor: "adobe",
Product: "reader",
Version: "2021.001.20150",
}
fmt.Printf("WFN: %s\n", wfn.WFNString())
// 将 WFN 绑定为 CPE 2.3 格式化字符串
fmt.Printf("CPE 2.3: %s\n", wfn.ToCPE23String())
// 将 WFN 绑定为 CPE 2.2 URI
fmt.Printf("CPE 2.2: %s\n", wfn.ToCPE22String())
// 示例 3:WFN 属性值与 Get/Set
fmt.Println("\n3. WFN 属性值:")
w := cpeskills.NewWFN() // 所有属性默认为 ANY
fmt.Printf(" 空 WFN Get(product): %s(ANY 匹配任意值)\n", w.Get(cpeskills.AttrProduct))
w.Set(cpeskills.AttrPart, cpeskills.PartApplicationShort)
w.Set(cpeskills.AttrVendor, "microsoft")
w.Set(cpeskills.AttrProduct, "windows")
w.Set(cpeskills.AttrVersion, "10")
w.Set(cpeskills.AttrUpdate, cpeskills.ValueNA) // NA = 不适用
fmt.Printf(" Set 后: %s\n", w.WFNString())
fmt.Printf(" ANY 常量: %q\n", cpeskills.ValueANY)
fmt.Printf(" NA 常量: %q\n", cpeskills.ValueNA)
// 示例 4:WFN 匹配
fmt.Println("\n4. WFN 匹配:")
// 源属性为 ANY (*) 时可匹配任意目标值
sourceWFN := &cpeskills.WFN{
Part: "a",
Vendor: "microsoft",
Product: cpeskills.ValueANY, // 任意产品
Version: cpeskills.ValueANY, // 任意版本
}
targetWFNs := []*cpeskills.WFN{
{Part: "a", Vendor: "microsoft", Product: "windows", Version: "10"},
{Part: "a", Vendor: "microsoft", Product: "office", Version: "2019"},
{Part: "a", Vendor: "oracle", Product: "java", Version: "11"},
{Part: "o", Vendor: "microsoft", Product: "windows", Version: "10"},
}
fmt.Printf("源 WFN: %s\n", sourceWFN.WFNString())
fmt.Println("匹配目标:")
for i, targetWFN := range targetWFNs {
match := sourceWFN.Match(targetWFN)
status := "否"
if match {
status = "是"
}
fmt.Printf(" [%s] 目标 %d: %s\n", status, i+1, targetWFN.WFNString())
}
// 示例 5:WFN 校验(标识符名称检查)
fmt.Println("\n5. WFN 校验:")
validationTests := []struct {
wfn *cpeskills.WFN
desc string
want bool
}{
{
&cpeskills.WFN{Part: "a", Vendor: "microsoft", Product: "windows"},
"含部件、供应商、产品 => 是标识符",
true,
},
{
&cpeskills.WFN{Part: "x", Vendor: "microsoft", Product: "windows"},
"仍是标识符(此处不校验部件取值)",
true,
},
{
&cpeskills.WFN{Part: "a", Vendor: "", Product: "windows"},
"空供应商(ANY)不是标识符",
false,
},
{
&cpeskills.WFN{Part: "a", Vendor: "microsoft", Product: ""},
"空产品(ANY)不是标识符",
false,
},
}
for i, test := range validationTests {
isValid := test.wfn.IsIdentifierName()
status := "通过"
if isValid != test.want {
status = "失败"
}
fmt.Printf(" [%s] 测试 %d: %s\n", status, i+1, test.desc)
fmt.Printf(" WFN: %s\n", test.wfn.WFNString())
}
// 示例 6:绑定到外部格式
fmt.Println("\n6. 绑定到外部格式:")
bindWFN := &cpeskills.WFN{
Part: "a",
Vendor: "apache",
Product: "tomcat",
Version: "9.0.0",
}
fmt.Printf(" WFN: %s\n", bindWFN.WFNString())
fmt.Printf(" BindToFS: %s\n", cpeskills.BindToFS(bindWFN))
fmt.Printf(" BindToURI: %s\n", cpeskills.BindToURI(bindWFN))
// 反向解绑回 WFN
roundTrip, err := cpeskills.UnbindFS(cpeskills.BindToFS(bindWFN))
if err != nil {
log.Printf(" UnbindFS 失败: %v", err)
} else {
fmt.Printf(" UnbindFS: %s\n", roundTrip.WFNString())
}
// 示例 7:属性级比较
fmt.Println("\n7. 属性级比较:")
wfn1 := &cpeskills.WFN{Part: "a", Vendor: "apache", Product: "tomcat", Version: "9.0.0"}
wfn2 := &cpeskills.WFN{Part: "a", Vendor: "apache", Product: "tomcat", Version: "9.0.1"}
wfn3 := &cpeskills.WFN{Part: "a", Vendor: "apache", Product: "tomcat", Version: "9.0.0"}
fmt.Printf("WFN1: %s\n", wfn1.WFNString())
fmt.Printf("WFN2: %s\n", wfn2.WFNString())
fmt.Printf("WFN3: %s\n", wfn3.WFNString())
// CompareWFNs 返回每个属性的关系映射。
// 取值含义:1=超集,0=相等,-1=子集,-2=不相交。
cmp12 := cpeskills.CompareWFNs(wfn1, wfn2)
cmp13 := cpeskills.CompareWFNs(wfn1, wfn3)
fmt.Printf("WFN1 与 WFN2 版本关系: %d(0=相等,-2=不相交)\n", cmp12[cpeskills.AttrVersion])
fmt.Printf("WFN1 与 WFN3 版本关系: %d(0=相等)\n", cmp13[cpeskills.AttrVersion])
fmt.Printf("WFN1 与 WFN3 所有属性相等: %t\n", cpeskills.CompareWFNRelation(cmp13) == cpeskills.RelationEqual)
// 示例 8:批量转换
fmt.Println("\n8. 批量转换:")
batchCPEs := []string{
"cpe:2.3:a:microsoft:windows:10:*:*:*:*:*:*:*",
"cpe:2.3:a:apache:tomcat:9.0.0:*:*:*:*:*:*:*",
"cpe:2.3:a:oracle:java:11.0.12:*:*:*:*:*:*:*",
"cpe:2.3:o:canonical:ubuntu:20.04:*:*:*:*:*:*:*",
}
fmt.Printf("批量转换 %d 个 CPE:\n", len(batchCPEs))
wfnList := make([]*cpeskills.WFN, 0, len(batchCPEs))
for i, cpeStr := range batchCPEs {
cpeObj, err := cpeskills.Parse(cpeStr)
if err != nil {
fmt.Printf(" [否] %d. 解析失败: %s\n", i+1, cpeStr)
continue
}
w := cpeskills.FromCPE(cpeObj)
wfnList = append(wfnList, w)
fmt.Printf(" [是] %d. %s %s %s\n", i+1, w.Get(cpeskills.AttrVendor), w.Get(cpeskills.AttrProduct), w.Get(cpeskills.AttrVersion))
}
fmt.Println("\n转换回 CPE 2.3:")
for i, w := range wfnList {
fmt.Printf(" [是] %d. %s\n", i+1, w.ToCPE23String())
}
}预期输出
text
=== WFN 转换示例 ===
1. CPE 到 WFN 转换:
示例 1: cpe:2.3:a:microsoft:windows:10:*:*:*:*:*:*:*
原始 CPE: cpe:2.3:a:microsoft:windows:10:*:*:*:*:*:*:*
WFN 格式: wfn:[part="a",vendor="microsoft",product="windows",version="10"]
部件: a
供应商: microsoft
产品: windows
版本: 10
示例 2: cpe:2.3:a:apache:tomcat:9.0.0:*:*:*:*:*:*:*
原始 CPE: cpe:2.3:a:apache:tomcat:9.0.0:*:*:*:*:*:*:*
WFN 格式: wfn:[part="a",vendor="apache",product="tomcat",version="9.0.0"]
部件: a
供应商: apache
产品: tomcat
版本: 9.0.0
示例 3: cpe:/a:oracle:java:1.8.0_291
原始 CPE: cpe:/a:oracle:java:1.8.0_291
WFN 格式: wfn:[part="a",vendor="oracle",product="java",version="1.8.0_291"]
部件: a
供应商: oracle
产品: java
版本: 1.8.0_291
示例 4: cpe:2.3:o:linux:kernel:5.4.0:*:*:*:*:*:*:*
原始 CPE: cpe:2.3:o:linux:kernel:5.4.0:*:*:*:*:*:*:*
WFN 格式: wfn:[part="o",vendor="linux",product="kernel",version="5.4.0"]
部件: o
供应商: linux
产品: kernel
版本: 5.4.0
2. WFN 到 CPE 转换:
WFN: wfn:[part="a",vendor="adobe",product="reader",version="2021.001.20150"]
CPE 2.3: cpe:2.3:a:adobe:reader:2021\.001\.20150:::::::
CPE 2.2: cpe:/a:adobe:reader:2021%2e001%2e20150:
3. WFN 属性值:
空 WFN Get(product): *(ANY 匹配任意值)
Set 后: wfn:[part="a",vendor="microsoft",product="windows",version="10",update="-"]
ANY 常量: "*"
NA 常量: "-"
4. WFN 匹配:
源 WFN: wfn:[part="a",vendor="microsoft"]
匹配目标:
[是] 目标 1: wfn:[part="a",vendor="microsoft",product="windows",version="10"]
[是] 目标 2: wfn:[part="a",vendor="microsoft",product="office",version="2019"]
[否] 目标 3: wfn:[part="a",vendor="oracle",product="java",version="11"]
[否] 目标 4: wfn:[part="o",vendor="microsoft",product="windows",version="10"]
5. WFN 校验:
[通过] 测试 1: 含部件、供应商、产品 => 是标识符
WFN: wfn:[part="a",vendor="microsoft",product="windows"]
[通过] 测试 2: 仍是标识符(此处不校验部件取值)
WFN: wfn:[part="x",vendor="microsoft",product="windows"]
[通过] 测试 3: 空供应商(ANY)不是标识符
WFN: wfn:[part="a",product="windows"]
[通过] 测试 4: 空产品(ANY)不是标识符
WFN: wfn:[part="a",vendor="microsoft"]
6. 绑定到外部格式:
WFN: wfn:[part="a",vendor="apache",product="tomcat",version="9.0.0"]
BindToFS: cpe:2.3:a:apache:tomcat:9\.0\.0:*:*:*:*:*:*:*
BindToURI: cpe:/a:apache:tomcat:9%2e0%2e0:*
UnbindFS: wfn:[part="a",vendor="apache",product="tomcat",version="9.0.0"]
7. 属性级比较:
WFN1: wfn:[part="a",vendor="apache",product="tomcat",version="9.0.0"]
WFN2: wfn:[part="a",vendor="apache",product="tomcat",version="9.0.1"]
WFN3: wfn:[part="a",vendor="apache",product="tomcat",version="9.0.0"]
WFN1 与 WFN2 版本关系: -2(0=相等,-2=不相交)
WFN1 与 WFN3 版本关系: 0(0=相等)
WFN1 与 WFN3 所有属性相等: true
8. 批量转换:
批量转换 4 个 CPE:
[是] 1. microsoft windows 10
[是] 2. apache tomcat 9.0.0
[是] 3. oracle java 11.0.12
[是] 4. canonical ubuntu 20.04
转换回 CPE 2.3:
[是] 1. cpe:2.3:a:microsoft:windows:10:*:*:*:*:*:*:*
[是] 2. cpe:2.3:a:apache:tomcat:9\.0\.0:*:*:*:*:*:*:*
[是] 3. cpe:2.3:a:oracle:java:11\.0\.12:*:*:*:*:*:*:*
[是] 4. cpe:2.3:o:canonical:ubuntu:20\.04:*:*:*:*:*:*:*关键概念
1. WFN 结构
WFN 包含 11 个属性:
- part: 组件类型 (a, h, o)
- vendor: 供应商名称
- product: 产品名称
- version: 版本字符串
- update: 更新标识符
- edition: 版本信息
- language: 语言代码
- sw_edition: 软件版本
- target_sw: 目标软件
- target_hw: 目标硬件
- other: 其他信息
2. 特殊值
- ANY (*): 匹配任意值
- NA (-): 不适用/未定义
- 字面值: 精确字符串匹配
3. WFN 优势
- 规范形式: 标准化表示
- 高效匹配: 优化的比较操作
- 验证: 内置验证规则
- 规范化: 一致的格式化
最佳实践
- 内部处理使用 WFN: 将 CPE 字符串转换为 WFN 进行操作
- 验证 WFN: 使用前始终验证 WFN 对象
- 规范化输入: 规范化 WFN 以确保一致的比较
- 处理特殊值: 正确处理 ANY 和 NA 值
- 转换回去: 将 WFN 转换回 CPE 格式进行输出