diff --git a/parsers/framework.go b/parsers/framework.go index 0c182ff..cda9468 100644 --- a/parsers/framework.go +++ b/parsers/framework.go @@ -110,10 +110,29 @@ type Framework struct { Froms map[From]bool `json:"froms,omitempty"` Tags []string `json:"tags,omitempty"` IsFocus bool `json:"is_focus,omitempty"` + Judge *Judgement `json:"judge,omitempty"` // 判定层(fingers/judge)的结论, nil 表示未经判定 MatchDetail *MatchDetail `json:"matcher,omitempty"` *Attributes `json:"attributes,omitempty"` } +// Judgement is a judgement layer's ruling on the claim that a framework +// produced the response (see github.com/chainreactors/fingers/judge). +// Rejected and duplicate entries are kept so callers can explain them; +// Frameworks.Accepted drops them. +type Judgement struct { + // Option is the claim option the ruling picked, such as "declared" + // (the response names the product in a header or cookie, decided without + // a model), "running", "mentioned" or "unrelated". + Option string `json:"option,omitempty"` + // Outcome is what Option means for the claim: "holds", "refuted" or + // "insufficient" (the evidence decides neither way). + Outcome string `json:"outcome,omitempty"` + Confidence float64 `json:"confidence,omitempty"` // the provider's confidence in Option; 1 for code's rulings + Evidence []string `json:"evidence,omitempty"` // the response excerpts the claim was ruled on + Rejected bool `json:"rejected,omitempty"` // the action taken: not reported (refuted, or insufficient where so configured) + Duplicate bool `json:"duplicate,omitempty"` // another engine's spelling of a product kept under another name +} + // MatchDetail describes which rule and matcher produced a hit. type MatchDetail struct { RuleIndex int `json:"rule_index,omitempty"` @@ -227,6 +246,9 @@ func (fs Frameworks) Add(other *Framework) bool { } frame.Tags = iutils.StringsUnique(append(frame.Tags, other.Tags...)) frame.UpdateAttributes(other.Attributes) + if frame.Judge == nil { + frame.Judge = other.Judge + } return false } else { fs[other.Name] = other @@ -337,3 +359,15 @@ func (fs Frameworks) HasFrom(from string) bool { } return false } + +// Accepted returns the frameworks without judged false positives and +// duplicate spellings. Frameworks that were not judged are kept. +func (fs Frameworks) Accepted() Frameworks { + out := make(Frameworks, len(fs)) + for k, f := range fs { + if f != nil && (f.Judge == nil || !f.Judge.Rejected && !f.Judge.Duplicate) { + out[k] = f + } + } + return out +} diff --git a/parsers/framework_test.go b/parsers/framework_test.go new file mode 100644 index 0000000..21b3883 --- /dev/null +++ b/parsers/framework_test.go @@ -0,0 +1,43 @@ +package parsers + +import ( + "encoding/json" + "testing" +) + +func TestFrameworksJudgement(t *testing.T) { + fs := Frameworks{} + fs.Add(&Framework{Name: "nginx", Judge: &Judgement{Option: "running", Outcome: "holds", Evidence: []string{"Server: nginx"}, Confidence: 0.9}}) + fs.Add(&Framework{Name: "jenkins", Judge: &Judgement{Option: "insufficient", Outcome: "insufficient"}}) + fs.Add(&Framework{Name: "tomcat", Judge: &Judgement{Rejected: true}}) + fs.Add(&Framework{Name: "apache-tomcat", Judge: &Judgement{Duplicate: true}}) + fs.Add(&Framework{Name: "unjudged"}) + + accepted := fs.Accepted() + if len(accepted) != 3 || accepted["tomcat"] != nil || accepted["apache-tomcat"] != nil || accepted["unjudged"] == nil { + t.Fatalf("accepted %v", accepted.GetNames()) + } + + data, err := json.Marshal(fs["nginx"]) + if err != nil { + t.Fatal(err) + } + var back Framework + if err := json.Unmarshal(data, &back); err != nil || back.Judge == nil || back.Judge.Outcome != "holds" || back.Judge.Option != "running" || len(back.Judge.Evidence) != 1 || back.Judge.Confidence != 0.9 { + t.Fatalf("round trip %s -> %+v", data, back.Judge) + } + if data, _ := json.Marshal(fs["unjudged"]); string(data) != `{"name":"unjudged"}` { + t.Fatalf("unjudged serializes as %s", data) + } +} + +func TestFrameworksAddKeepsJudgement(t *testing.T) { + fs := Frameworks{} + fs.Add(NewFramework("nginx", FrameFromDefault)) + judged := NewFramework("nginx", FrameFromDefault) + judged.Judge = &Judgement{Option: "running", Outcome: "holds"} + fs.Add(judged) + if fs["nginx"].Judge == nil { + t.Fatal("judgement lost on merge") + } +}