fabrica

hanna/fabrica

This repository is licensed under the

GNU Affero General Public License v3.0 (AGPL-3.0)

Like GPLv3, but network use counts as distribution — users interacting over a network must get the source.

Permissions

  • Commercial use
  • Modification
  • Distribution
  • Patent use
  • Private use

Conditions

  • Disclose source
  • License and copyright notice
  • Network use is distribution
  • Same license
  • State changes

Limitations

  • Liability
  • Warranty

This is a summary, not legal advice.

9690 bytes
Raw
1// This Source Code Form is subject to the terms of the Mozilla Public
2// License, v. 2.0. If a copy of the MPL was not distributed with this
3// file, You can obtain one at https://mozilla.org/MPL/2.0/.
4
5//! Lightweight license detection for the file view.
6//!
7//! This is not a full SPDX matcher (à la GitHub's licensee): it recognizes a
8//! curated set of common licenses by distinctive marker phrases and maps each to
9//! a small table of permissions / conditions / limitations sourced from
10//! [choosealicense.com](https://choosealicense.com). It is deliberately
11//! conservative — an unrecognized or heavily modified license simply shows no
12//! banner. The rights summary is informational, never legal advice.
13
14/// A recognized license and its rights summary.
15#[derive(Debug, Clone, Copy)]
16pub(crate) struct LicenseInfo {
17 /// Human-readable name, e.g. "MIT License".
18 pub name: &'static str,
19 /// SPDX identifier, e.g. "MIT".
20 pub spdx: &'static str,
21 /// One-line plain description.
22 pub description: &'static str,
23 /// What the license permits.
24 pub permissions: &'static [&'static str],
25 /// What it requires.
26 pub conditions: &'static [&'static str],
27 /// What it limits.
28 pub limitations: &'static [&'static str],
29}
30
31/// Whether `path`'s filename looks like a license file (`LICENSE`, `LICENCE`,
32/// `COPYING`, optionally with an extension, case-insensitive).
33pub(crate) fn is_license_path(path: &str) -> bool {
34 let name = path.rsplit('/').next().unwrap_or(path).to_ascii_lowercase();
35 let stem = name.split('.').next().unwrap_or(&name);
36 matches!(
37 stem,
38 "license" | "licence" | "copying" | "copyright" | "unlicense"
39 )
40}
41
42/// Detect the license from a file's text, or `None` if it is not one of the
43/// recognized licenses. Matching is order-sensitive (most specific first).
44pub(crate) fn detect(content: &str) -> Option<&'static LicenseInfo> {
45 // Collapse whitespace and lowercase so wrapping/indentation doesn't matter.
46 let normalized: String = content
47 .to_ascii_lowercase()
48 .split_whitespace()
49 .collect::<Vec<_>>()
50 .join(" ");
51 let has = |needle: &str| normalized.contains(needle);
52
53 // Order matters: check more specific markers before generic ones.
54 if has("gnu affero general public license") {
55 return Some(&AGPL_3_0);
56 }
57 if has("gnu lesser general public license") {
58 return Some(&LGPL_3_0);
59 }
60 if has("gnu general public license") {
61 if has("version 2") {
62 return Some(&GPL_2_0);
63 }
64 return Some(&GPL_3_0);
65 }
66 if has("mozilla public license version 2.0") {
67 return Some(&MPL_2_0);
68 }
69 if has("apache license") && has("version 2.0") {
70 return Some(&APACHE_2_0);
71 }
72 if has("this is free and unencumbered software released into the public domain") {
73 return Some(&UNLICENSE);
74 }
75 if has("permission to use, copy, modify, and/or distribute this software") {
76 // 0BSD drops ISC's "provided that the above copyright" retention clause.
77 if has("provided that the above copyright") {
78 return Some(&ISC);
79 }
80 return Some(&BSD_0);
81 }
82 if has("redistribution and use in source and binary forms") {
83 if has("neither the name") {
84 return Some(&BSD_3_CLAUSE);
85 }
86 return Some(&BSD_2_CLAUSE);
87 }
88 if has("permission is hereby granted, free of charge") {
89 return Some(&MIT);
90 }
91 None
92}
93
94// ---- Rights vocabulary (choosealicense.com labels) ----
95const COMMERCIAL: &str = "Commercial use";
96const MODIFY: &str = "Modification";
97const DISTRIBUTE: &str = "Distribution";
98const PRIVATE: &str = "Private use";
99const PATENT: &str = "Patent use";
100const LIABILITY: &str = "Liability";
101const WARRANTY: &str = "Warranty";
102const TRADEMARK: &str = "Trademark use";
103const KEEP_NOTICE: &str = "License and copyright notice";
104const STATE_CHANGES: &str = "State changes";
105const DISCLOSE_SOURCE: &str = "Disclose source";
106const SAME_LICENSE: &str = "Same license";
107const NETWORK_DISCLOSE: &str = "Network use is distribution";
108
109const PERMISSIVE: &[&str] = &[COMMERCIAL, MODIFY, DISTRIBUTE, PRIVATE];
110const PERMISSIVE_PATENT: &[&str] = &[COMMERCIAL, MODIFY, DISTRIBUTE, PATENT, PRIVATE];
111const NO_WARRANTY: &[&str] = &[LIABILITY, WARRANTY];
112
113const MIT: LicenseInfo = LicenseInfo {
114 name: "MIT License",
115 spdx: "MIT",
116 description: "A short, permissive license: do almost anything, provided the copyright and license notice are preserved.",
117 permissions: PERMISSIVE,
118 conditions: &[KEEP_NOTICE],
119 limitations: NO_WARRANTY,
120};
121
122const APACHE_2_0: LicenseInfo = LicenseInfo {
123 name: "Apache License 2.0",
124 spdx: "Apache-2.0",
125 description: "A permissive license with an express grant of patent rights and requirements to note changes.",
126 permissions: PERMISSIVE_PATENT,
127 conditions: &[KEEP_NOTICE, STATE_CHANGES],
128 limitations: &[TRADEMARK, LIABILITY, WARRANTY],
129};
130
131const BSD_2_CLAUSE: LicenseInfo = LicenseInfo {
132 name: "BSD 2-Clause License",
133 spdx: "BSD-2-Clause",
134 description: "A permissive license that requires preservation of the copyright and license notices.",
135 permissions: PERMISSIVE,
136 conditions: &[KEEP_NOTICE],
137 limitations: NO_WARRANTY,
138};
139
140const BSD_3_CLAUSE: LicenseInfo = LicenseInfo {
141 name: "BSD 3-Clause License",
142 spdx: "BSD-3-Clause",
143 description: "A permissive license like BSD 2-Clause, also forbidding use of contributors' names to endorse derivatives.",
144 permissions: PERMISSIVE,
145 conditions: &[KEEP_NOTICE],
146 limitations: NO_WARRANTY,
147};
148
149const BSD_0: LicenseInfo = LicenseInfo {
150 name: "BSD Zero Clause License",
151 spdx: "0BSD",
152 description: "A public-domain-equivalent license with no attribution requirement whatsoever.",
153 permissions: PERMISSIVE,
154 conditions: &[],
155 limitations: NO_WARRANTY,
156};
157
158const ISC: LicenseInfo = LicenseInfo {
159 name: "ISC License",
160 spdx: "ISC",
161 description: "A permissive license functionally equivalent to MIT/BSD 2-Clause, with simpler language.",
162 permissions: PERMISSIVE,
163 conditions: &[KEEP_NOTICE],
164 limitations: NO_WARRANTY,
165};
166
167const MPL_2_0: LicenseInfo = LicenseInfo {
168 name: "Mozilla Public License 2.0",
169 spdx: "MPL-2.0",
170 description: "A file-level copyleft license: modified files must stay open, but they can be combined with proprietary code.",
171 permissions: PERMISSIVE_PATENT,
172 conditions: &[DISCLOSE_SOURCE, KEEP_NOTICE, SAME_LICENSE],
173 limitations: &[TRADEMARK, LIABILITY, WARRANTY],
174};
175
176const GPL_2_0: LicenseInfo = LicenseInfo {
177 name: "GNU General Public License v2.0",
178 spdx: "GPL-2.0",
179 description: "A strong copyleft license: derivatives must be released under the same terms with source available.",
180 permissions: PERMISSIVE,
181 conditions: &[DISCLOSE_SOURCE, KEEP_NOTICE, SAME_LICENSE, STATE_CHANGES],
182 limitations: NO_WARRANTY,
183};
184
185const GPL_3_0: LicenseInfo = LicenseInfo {
186 name: "GNU General Public License v3.0",
187 spdx: "GPL-3.0",
188 description: "A strong copyleft license with a patent grant: derivatives must stay open under the same terms.",
189 permissions: PERMISSIVE_PATENT,
190 conditions: &[DISCLOSE_SOURCE, KEEP_NOTICE, SAME_LICENSE, STATE_CHANGES],
191 limitations: NO_WARRANTY,
192};
193
194const LGPL_3_0: LicenseInfo = LicenseInfo {
195 name: "GNU Lesser General Public License v3.0",
196 spdx: "LGPL-3.0",
197 description: "A weaker copyleft: the library stays open under the same terms, but may be linked from other software.",
198 permissions: PERMISSIVE_PATENT,
199 conditions: &[DISCLOSE_SOURCE, KEEP_NOTICE, SAME_LICENSE, STATE_CHANGES],
200 limitations: NO_WARRANTY,
201};
202
203const AGPL_3_0: LicenseInfo = LicenseInfo {
204 name: "GNU Affero General Public License v3.0",
205 spdx: "AGPL-3.0",
206 description: "Like GPLv3, but network use counts as distribution — users interacting over a network must get the source.",
207 permissions: PERMISSIVE_PATENT,
208 conditions: &[
209 DISCLOSE_SOURCE,
210 KEEP_NOTICE,
211 NETWORK_DISCLOSE,
212 SAME_LICENSE,
213 STATE_CHANGES,
214 ],
215 limitations: NO_WARRANTY,
216};
217
218const UNLICENSE: LicenseInfo = LicenseInfo {
219 name: "The Unlicense",
220 spdx: "Unlicense",
221 description: "A public-domain dedication: do anything, with no conditions at all.",
222 permissions: PERMISSIVE,
223 conditions: &[],
224 limitations: NO_WARRANTY,
225};
226
227#[cfg(test)]
228mod tests {
229 use super::*;
230
231 #[test]
232 fn recognizes_license_filenames() {
233 assert!(is_license_path("LICENSE"));
234 assert!(is_license_path("license.md"));
235 assert!(is_license_path("path/to/COPYING"));
236 assert!(is_license_path("LICENCE.txt"));
237 assert!(!is_license_path("src/main.rs"));
238 assert!(!is_license_path("readme.md"));
239 }
240
241 #[test]
242 fn detects_common_licenses() {
243 assert_eq!(
244 detect("Permission is hereby granted, free of charge, to any person").map(|l| l.spdx),
245 Some("MIT")
246 );
247 assert_eq!(
248 detect("Apache License\nVersion 2.0, January 2004").map(|l| l.spdx),
249 Some("Apache-2.0")
250 );
251 assert_eq!(
252 detect("GNU GENERAL PUBLIC LICENSE\nVersion 3, 29 June 2007").map(|l| l.spdx),
253 Some("GPL-3.0")
254 );
255 let zero_bsd = "BSD Zero Clause License\n\nPermission to use, copy, modify, \
256 and/or distribute this software for any purpose with or without fee is \
257 hereby granted.";
258 assert_eq!(detect(zero_bsd).map(|l| l.spdx), Some("0BSD"));
259 assert!(detect("just some random text file").is_none());
260 }
261}