-
Notifications
You must be signed in to change notification settings - Fork 684
/
licenses.go
309 lines (269 loc) · 9.55 KB
/
licenses.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
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
package detectlicense
import (
"bytes"
"fmt"
"path/filepath"
"regexp"
"strings"
"github.com/pkg/errors"
)
type License struct {
Name string
NoticeFile bool // are NOTICE files "a thing" for this license?
WeakCopyleft bool // requires that library to be open-source
StrongCopyleft bool // requires the resulting program to be open-source
}
var (
Proprietary = License{Name: "proprietary"}
PublicDomain = License{Name: "public domain"}
Apache2 = License{Name: "Apache License 2.0", NoticeFile: true}
BSD1 = License{Name: "1-clause BSD license"}
BSD2 = License{Name: "2-clause BSD license"}
BSD3 = License{Name: "3-clause BSD license"}
ISC = License{Name: "ISC license"}
MIT = License{Name: "MIT license"}
MPL2 = License{Name: "Mozilla Public License 2.0", NoticeFile: true, WeakCopyleft: true}
CcBySa40 = License{Name: "Creative Commons Attribution Share Alike 4.0 International", StrongCopyleft: true}
)
// https://spdx.org/licenses/
var (
// split with "+" to avoid a false-positive on itself
spdxTag = []byte("SPDX-License" + "-Identifier:")
spdxIdentifiers = map[string]License{
"Apache-2.0": Apache2,
"BSD-1-Clause": BSD1,
"BSD-2-Clause": BSD2,
"BSD-3-Clause": BSD3,
"ISC": ISC,
"MIT": MIT,
"MPL-2.0": MPL2,
"CC-BY-SA-4.0": CcBySa40,
}
)
func expectsNotice(licenses map[License]struct{}) bool {
for license := range licenses {
if license.NoticeFile == true {
return true
}
}
return false
}
func DetectLicenses(files map[string][]byte) (map[License]struct{}, error) {
licenses := make(map[License]struct{})
hasNotice := false
hasLicenseFile := false
hasNonSPDXSource := false
hasPatents := false
for filename, filebody := range files {
if filename == "github.com/miekg/dns/COPYRIGHT" {
// This file identifies copyright holders, but
// the license info is in the LICENSE file.
continue
}
name := filepath.Base(filename)
// See ./vendor.go:metaPrefixes
switch {
case strings.HasPrefix(name, "AUTHORS") ||
strings.HasPrefix(name, "CONTRIBUTORS"):
// Ignore this file; it does not identify a license.
case strings.HasPrefix(name, "COPYLEFT") ||
strings.HasPrefix(name, "COPYING") ||
strings.HasPrefix(name, "COPYRIGHT") ||
strings.HasPrefix(name, "LEGAL") ||
strings.HasPrefix(name, "LICENSE"):
ls := IdentifyLicenses(filebody)
if ls == nil || len(ls) == 0 {
return nil, errors.Errorf("could not identify license in file %q", filename)
}
if name == "LICENSE.docs" && len(ls) == 1 {
if _, isCc := ls[CcBySa40]; isCc {
// This file describes the license of the
// docs, which are licensed separately from
// the code. We don't care about the docs.
continue
}
}
for l := range ls {
licenses[l] = struct{}{}
}
hasLicenseFile = true
case strings.HasPrefix(name, "NOTICE"):
hasNotice = true
case strings.HasPrefix(name, "PATENTS"):
// ignore this file, for now
hasPatents = true
default:
// This is a source file; look for an SPDX
// identifier.
ls, err := IdentifySPDXLicenses(filebody)
if err != nil {
return nil, err
}
if ls == nil || len(ls) == 0 {
hasNonSPDXSource = true
}
for l := range ls {
licenses[l] = struct{}{}
}
}
}
if !expectsNotice(licenses) && hasNotice {
return nil, errors.New("the NOTICE file is really only for the Apache 2.0 and MPL 2.0 licenses; something hokey is going on")
}
if _, hasApache := licenses[Apache2]; hasApache && hasPatents {
// TODO: Check if the MPL has a patent grant. A quick skimming says "seems to explicitly say no", but I'm too tired to actually read the thing.
return nil, errors.New("the Apache license contains a patent-grant, but there's a separate PATENTS file; something hokey is going on")
}
if !hasLicenseFile && hasNonSPDXSource {
return nil, errors.New("could not identify a license for all sources (had no global LICENSE file)")
}
if len(licenses) == 0 {
panic(errors.New("should not happen"))
}
return licenses, nil
}
// IdentifySPDX takes the contents of a source-file and looks for SPDX
// license identifiers.
func IdentifySPDXLicenses(body []byte) (map[License]struct{}, error) {
licenses := make(map[License]struct{})
for bytes.Contains(body, spdxTag) {
tagPos := bytes.Index(body, spdxTag)
body = body[tagPos+len(spdxTag):]
idEnd := bytes.IndexByte(body, '\n')
if idEnd < 0 {
idEnd = len(body)
}
id := string(body[:idEnd])
body = body[idEnd:]
id = strings.TrimSpace(strings.TrimSuffix(strings.TrimSpace(id), "*/"))
license, licenseOK := spdxIdentifiers[id]
if !licenseOK {
return nil, errors.Errorf("unknown SPDX identifier %q", id)
}
licenses[license] = struct{}{}
}
return licenses, nil
}
var (
bsd3funnyAttributionLines = []string{
`(?:Copyright [^\n]*(?:\s+All rights reserved\.)? *\n)`,
reQuote(`As this is fork of the official Go code the same license applies:`),
reQuote(`Extensions of the original work are copyright (c) 2011 Miek Gieben`),
reQuote(`Go support for Protocol Buffers - Google's data interchange format`),
reQuote(`Protocol Buffers for Go with Gadgets`),
reQuote(`http://github.com/gogo/protobuf`),
reQuote(`https://github.com/golang/protobuf`),
reQuote(`Copyright (c) 2012 Péter Surányi. Portions Copyright (c) 2009 The Go
Authors. All rights reserved.`),
}
)
const (
rackspaceHeader = `Copyright 2012-2013 Rackspace, Inc.
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. ` + /* trailing ws matters */ `
------`
xzPublicDomain = `Licensing of github.com/xi2/xz
==============================
This Go package is a modified version of
XZ Embedded <http://tukaani.org/xz/embedded.html>
The contents of the testdata directory are modified versions of
the test files from
XZ Utils <http://tukaani.org/xz/>
All the files in this package have been written by Michael Cross,
Lasse Collin and/or Igor PavLov. All these files have been put
into the public domain. You can do whatever you want with these
files.
This software is provided "as is", without any warranty.
`
)
var (
yamlHeader = reWrap(`The following files were ported to Go from C files of libyaml, and thus
are still covered by their original (copyright and license|MIT license, with the additional
copyright start?ing in 2011 when the project was ported over):
apic\.go
emitterc\.go
parserc\.go
readerc\.go
scannerc\.go
writerc\.go
yamlh\.go
yamlprivateh\.go
`)
reYamlV2 = reCompile(yamlHeader + `\s*` + reMIT.String())
reYamlV3 = reCompile(`\s*` +
reQuote(`This project is covered by two different licenses: MIT and Apache.`) + `\s*` +
`#+ MIT License #+\s*` +
yamlHeader + `\s*` +
reMIT.String() + `\s*` +
`#+ Apache License #+\s*` +
reQuote(`All the remaining project files are covered by the Apache license:`) + `\s*` +
reApacheStatement.String())
)
// IdentifyLicense takes the contents of a license-file and attempts
// to identify the license(s) in it. If it is even a little unsure,
// it returns nil.
func IdentifyLicenses(body []byte) map[License]struct{} {
licenses := make(map[License]struct{})
switch {
case reMatch(reApacheLicense, body) || reMatch(reApacheStatement, body):
licenses[Apache2] = struct{}{}
case reMatch(reBSD2, body):
licenses[BSD2] = struct{}{}
case reMatch(reBSD3, body):
licenses[BSD3] = struct{}{}
case reMatch(reISC, body):
licenses[ISC] = struct{}{}
case reMatch(reMIT, body):
licenses[MIT] = struct{}{}
case reMatch(reMPL, body):
licenses[MPL2] = struct{}{}
case reMatch(reCcBySa40, body):
licenses[CcBySa40] = struct{}{}
// special-purpose hacks
case reMatch(reCompile(fmt.Sprintf(`%s\n-+\n+AVL Tree:\n+%s`, reBSD2, reISC)), body):
// github.com/emirpasic/gods/LICENSE
licenses[BSD2] = struct{}{}
licenses[ISC] = struct{}{}
case reMatch(reCompile(
`(?:`+strings.Join(bsd3funnyAttributionLines, `\s*|`)+`\s*)+`+reWrap(``+
bsdPrefix+
bsdClause1+
bsdClause2+
bsdClause3+
bsdSuffix)),
body):
// github.com/gogo/protobuf/LICENSE
// github.com/src-d/gcfg/LICENSE
licenses[BSD3] = struct{}{}
case reMatch(reCompile(reQuote(rackspaceHeader)+reApacheLicense.String()), body):
// github.com/gophercloud/gophercloud/LICENSE
licenses[Apache2] = struct{}{}
case reMatch(reCompile(fmt.Sprintf(`%s=*\s*The lexer and parser[^\n]*\n[^\n]*below\.%s`, reMIT, reMIT)), body):
// github.com/kevinburke/ssh_config/LICENSE
licenses[MIT] = struct{}{}
case reMatch(reCompile(`Blackfriday is distributed under the Simplified BSD License:\s*`+reBSD2.String()), regexp.MustCompile(`>\s*`).ReplaceAllLiteral(body, []byte{})):
// gopkg.in/russross/blackfriday.v2/LICENSE.txt
licenses[BSD2] = struct{}{}
case reMatch(reYamlV2, body):
licenses[MIT] = struct{}{}
case reMatch(reYamlV3, body):
licenses[MIT] = struct{}{}
licenses[Apache2] = struct{}{}
case reMatch(reCompile(reMIT.String()+`\s*`+reBSD3.String()), body):
// sigs.k8s.io/yaml/LICENSE
licenses[MIT] = struct{}{}
licenses[BSD3] = struct{}{}
case string(body) == xzPublicDomain:
// github.com/xi2/xz/LICENSE
licenses[PublicDomain] = struct{}{}
default:
return nil
}
return licenses
}