-
-
Notifications
You must be signed in to change notification settings - Fork 287
/
index.go
218 lines (172 loc) Β· 5.5 KB
/
index.go
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
package filterlists
import (
"fmt"
"io/ioutil"
"sync"
"github.com/safing/portbase/database"
"github.com/safing/portbase/database/record"
"github.com/safing/portbase/formats/dsd"
"github.com/safing/portbase/log"
"github.com/safing/portmaster/updates"
)
// the following definitions are copied from the intelhub repository
// and stripped down to only include data required by portmaster.
// Category is used to group different list sources by the type
// of entity they are blocking. Categories may be nested using
// the Parent field.
type Category struct {
// ID is a unique ID for the category. For sub-categories
// this ID must be used in the Parent field of any directly
// nesteded categories.
ID string `json:"id"`
// Parent may hold the ID of another category. If set, this
// category is made a sub-category of it's parent.
Parent string `json:"parent,omitempty"`
// Name is a human readable name for the category and can
// be used in user interfaces.
Name string `json:"name"`
// Description is a human readable description that may be
// displayed in user interfaces.
Description string `json:"description,omitempty"`
}
// Source defines an external filterlists source.
type Source struct {
// ID is a unique ID for the source. Entities always reference the
// sources they have been observed in using this ID. Refer to the
// Entry struct for more information.
ID string `json:"id"`
// Name is a human readable name for the source and can be used
// in user interfaces.
Name string `json:"name"`
// Description may hold a human readable description for the source.
// It may be used in user interfaces.
Description string `json:"description"`
// Type describes the type of entities the source provides. Refer
// to the Type definition for more information and well-known types.
Type string `json:"type"`
// URL points to the filterlists file.
URL string `json:"url"`
// Category holds the unique ID of a category the source belongs to. Since
// categories can be nested the source is automatically part of all categories
// in the hierarchy. Refer to the Category struct for more information.
Category string `json:"category"`
// Website may holds the URL of the source maintainers website.
Website string `json:"website,omitempty"`
// License holds the license that is used for the source.
License string `json:"license"`
// Contribute may hold an opaque string that informs a user on how to
// contribute to the source. This may be a URL or mail address.
Contribute string `json:"contribute"`
}
// ListIndexFile describes the structure of the released list
// index file.
type ListIndexFile struct {
record.Base
sync.RWMutex
Version string `json:"version"`
SchemaVersion string `json:"schemaVersion"`
Categories []Category `json:"categories"`
Sources []Source `json:"sources"`
}
func (index *ListIndexFile) getCategorySources(id string) []string {
ids := make(map[string]struct{})
// find all sources that match against cat
for _, s := range index.Sources {
if s.Category == id {
ids[s.ID] = struct{}{}
}
}
// find all child-categories recursing into getCategorySources.
for _, c := range index.Categories {
if c.Parent == id {
for _, sid := range index.getCategorySources(c.ID) {
ids[sid] = struct{}{}
}
}
}
return mapKeys(ids)
}
func (index *ListIndexFile) getSourcesMatching(id string) []string {
// if id is already a source ID we just return it
for _, s := range index.Sources {
if s.ID == id {
return []string{s.ID}
}
}
// otherwise we need to check the category tree
return index.getCategorySources(id)
}
func (index *ListIndexFile) getDistictSourceIDs(ids ...string) []string {
index.RLock()
defer index.RUnlock()
distinctIDs := make(map[string]struct{})
for _, id := range ids {
for _, sid := range index.getSourcesMatching(id) {
distinctIDs[sid] = struct{}{}
}
}
return mapKeys(distinctIDs)
}
func getListIndexFromCache() (*ListIndexFile, error) {
r, err := cache.Get(filterListIndexKey)
if err != nil {
return nil, err
}
var index *ListIndexFile
if r.IsWrapped() {
index = new(ListIndexFile)
if err := record.Unwrap(r, index); err != nil {
return nil, err
}
} else {
var ok bool
index, ok = r.(*ListIndexFile)
if !ok {
return nil, fmt.Errorf("invalid type, expected ListIndexFile but got %T", r)
}
}
return index, nil
}
func updateListIndex() error {
index, err := updates.GetFile(listIndexFilePath)
if err != nil {
return err
}
blob, err := ioutil.ReadFile(index.Path())
if err != nil {
return err
}
res, err := dsd.Load(blob, &ListIndexFile{})
if err != nil {
return err
}
content, ok := res.(*ListIndexFile)
if !ok {
return fmt.Errorf("unexpected format in list index")
}
content.SetKey(filterListIndexKey)
if err := cache.Put(content); err != nil {
return err
}
log.Debugf("intel/filterlists: updated cache record for list index with version %s", content.Version)
return nil
}
// ResolveListIDs resolves a slice of source or category IDs into
// a slice of distinct source IDs.
func ResolveListIDs(ids []string) ([]string, error) {
index, err := getListIndexFromCache()
if err != nil {
if err == database.ErrNotFound {
if err := updateListIndex(); err != nil {
return nil, err
}
// retry resolving IDs
return ResolveListIDs(ids)
}
log.Errorf("failed to resolved ids %v: %s", ids, err)
return nil, err
}
resolved := index.getDistictSourceIDs(ids...)
log.Debugf("intel/filterlists: resolved ids %v to %v", ids, resolved)
return resolved, nil
}