Skip to content

Commit 3af2d88

Browse files
committed
Change license acceptance criteria
- allow license exceptions by default – specific ones can be added to a reject list (fixes #1440) - allow `+` licenses - report the portion of the license that is unacceptable in the error message (improves #710, but could be more explicit about why licenses are rejected) - extract the constant allow/reject lists from the evaluation function
1 parent 799f412 commit 3af2d88

File tree

1 file changed

+76
-30
lines changed

1 file changed

+76
-30
lines changed

src/Distribution/Server/Packages/Unpack.hs

Lines changed: 76 additions & 30 deletions
Original file line numberDiff line numberDiff line change
@@ -51,6 +51,8 @@ import Distribution.Server.Util.ParseSpecVer
5151
import qualified Distribution.SPDX as SPDX
5252
import qualified Distribution.SPDX.LicenseId as SPDX.LId
5353
import qualified Distribution.License as License
54+
import Distribution.Pretty
55+
( prettyShow )
5456

5557
import Control.Monad.Except
5658
( ExceptT, runExceptT, MonadError, throwError )
@@ -60,9 +62,13 @@ import Control.Monad.Writer
6062
( WriterT(..), MonadWriter, tell )
6163
import Data.Bits
6264
( (.&.) )
65+
import Data.Bitraversable
66+
( bitraverse )
6367
import Data.ByteString.Lazy
6468
( ByteString )
6569
import qualified Data.ByteString.Lazy as LBS
70+
import Data.Foldable
71+
( traverse_ )
6672
import Data.List
6773
( nub, partition, isPrefixOf )
6874
import qualified Data.Map.Strict as Map
@@ -217,7 +223,7 @@ specVersionChecks specVerOk specVer = do
217223
throwError "'cabal-version' must be at least 1.2"
218224

219225
-- To keep people from uploading packages most users cannot use. Disabled for now.
220-
{-
226+
{-
221227
unless (specVer <= CabalSpecV3_6) $
222228
throwError "'cabal-version' must be at most 3.6"
223229
-}
@@ -317,11 +323,18 @@ extraChecks genPkgDesc pkgId tarIndex = do
317323
mapM_ (warn . ppPackageCheck) warnings
318324

319325
-- Proprietary License check (only active in central-server branch)
320-
unless (allowAllRightsReserved || isAcceptableLicense pkgDesc) $
321-
throwError $ "This server does not accept packages with 'license' "
322-
++ "field set to e.g. AllRightsReserved. See "
323-
++ "https://hackage.haskell.org/upload for more information "
324-
++ "about accepted licenses."
326+
unless allowAllRightsReserved $
327+
traverse_
328+
( \badLicense ->
329+
throwError $ "This server does not accept packages with 'license' "
330+
++ "field containing "
331+
++ either prettyShow prettyShow badLicense
332+
++ ". See https://hackage.haskell.org/upload for more "
333+
++ "information about accepted licenses. (if the license "
334+
++ "shown above contains “OR”, only one of the alternatives "
335+
++ "needs be be acceptable.)"
336+
)
337+
$ extractUnacceptableLicense pkgDesc
325338

326339
-- Check for an existing x-revision
327340
when (isJust (lookup "x-revision" (customFieldsPD pkgDesc))) $
@@ -502,37 +515,70 @@ quote s = "'" ++ s ++ "'"
502515
startsWithBOM :: ByteString -> Bool
503516
startsWithBOM bs = LBS.take 3 bs == LBS.pack [0xEF, 0xBB, 0xBF]
504517

505-
-- | Licence acceptance predicate (only used on central-server)
518+
-- | This is a list of licences that are accepted, even though they aren’t OSI-
519+
-- or FSF-approved.
520+
allowedLicenses :: [SPDX.LicenseId]
521+
allowedLicenses =
522+
[ SPDX.CC0_1_0, -- CC0 isn't OSI approved, but we allow it as "PublicDomain", this is eg. PublicDomain in http://hackage.haskell.org/package/string-qq-0.0.2/src/LICENSE
523+
SPDX.Bzip2_1_0_5, -- not OSI approved, but make an exception: https://github.com/haskell/hackage-server/issues/1294
524+
SPDX.Bzip2_1_0_6 -- same as above
525+
]
526+
527+
rejectedLicenseExceptions :: [SPDX.LicenseExceptionId]
528+
rejectedLicenseExceptions =
529+
[
530+
]
531+
532+
-- | Licence acceptance predicate – `Nothing` represents an acceptable license.
533+
-- (only used on central-server)
506534
--
507535
-- * NONE is rejected
508536
--
509-
-- * "or later" syntax (+ postfix) is rejected
537+
-- * license refs are rejected
510538
--
511-
-- * "WITH exc" exceptions are rejected
539+
-- * specific SPDX license ids (other than those that are OSI- or FSF-approved)
540+
-- can be added to `allowedLicenses` above
512541
--
513-
-- * There should be a way to interpert license as (conjunction of)
514-
-- OSI-accepted licenses or CC0
515-
--
516-
isAcceptableLicense :: PackageDescription -> Bool
517-
isAcceptableLicense = either goSpdx goLegacy . licenseRaw
542+
-- * specific SPDX license exception ids can be added to
543+
-- `rejectedLicenseExceptions` above
544+
extractUnacceptableLicense ::
545+
PackageDescription -> Maybe (Either SPDX.License License.License)
546+
extractUnacceptableLicense = bitraverse goSpdx goLegacy . licenseRaw
518547
where
519548
-- `cabal-version: 2.2` and later
520-
goSpdx :: SPDX.License -> Bool
521-
goSpdx SPDX.NONE = False
522-
goSpdx (SPDX.License expr) = goExpr expr
549+
goSpdx :: SPDX.License -> Maybe SPDX.License
550+
goSpdx SPDX.NONE = pure SPDX.NONE
551+
goSpdx (SPDX.License expr) = SPDX.License <$> goExpr expr
523552
where
524-
goExpr (SPDX.EAnd a b) = goExpr a && goExpr b
525-
goExpr (SPDX.EOr a b) = goExpr a || goExpr b
526-
goExpr (SPDX.ELicense _ (Just _)) = False -- Don't allow exceptions
527-
goExpr (SPDX.ELicense s Nothing) = goSimple s
528-
529-
goSimple (SPDX.ELicenseRef _) = False -- don't allow referenced licenses
530-
goSimple (SPDX.ELicenseIdPlus _) = False -- don't allow + licenses (use GPL-3.0-or-later e.g.)
531-
goSimple (SPDX.ELicenseId SPDX.CC0_1_0) = True -- CC0 isn't OSI approved, but we allow it as "PublicDomain", this is eg. PublicDomain in http://hackage.haskell.org/package/string-qq-0.0.2/src/LICENSE
532-
goSimple (SPDX.ELicenseId SPDX.Bzip2_1_0_5) = True -- not OSI approved, but make an exception: https://github.com/haskell/hackage-server/issues/1294
533-
goSimple (SPDX.ELicenseId SPDX.Bzip2_1_0_6) = True -- same as above
534-
goSimple (SPDX.ELicenseId lid) = SPDX.licenseIsOsiApproved lid || SPDX.LId.licenseIsFsfLibre lid -- allow only OSI or FSF approved licenses.
553+
goExpr (SPDX.EAnd a b) = case (goExpr a, goExpr b) of
554+
(Nothing, Nothing) -> Nothing
555+
(Just l, Nothing) -> pure l
556+
(Nothing, Just l) -> pure l
557+
(Just l, Just l') -> pure $ SPDX.EAnd l l'
558+
goExpr (SPDX.EOr a b) = case (goExpr a, goExpr b) of
559+
(Just l, Just l') -> pure $ SPDX.EOr l l'
560+
(_, _) -> Nothing
561+
goExpr l@(SPDX.ELicense s e) = case (goSimple s, goException <$> e) of
562+
(False, Just False) -> pure l
563+
-- TODO: This case should _only_ return the exception, but it includes both
564+
(True, Just False) -> pure $ SPDX.ELicense s e
565+
(False, _) -> pure $ SPDX.ELicense s Nothing
566+
(True, _) -> Nothing
567+
568+
goException eid =
569+
-- most exceptions grant additional rights – reject specific ones
570+
not $ eid `elem` rejectedLicenseExceptions
571+
goSimple (SPDX.ELicenseRef _) = False -- don't allow referenced licenses
572+
-- TODO: Reject GNU license ids with a `+`, because they should use
573+
-- `-only` or `-or-later` instead.
574+
goSimple (SPDX.ELicenseIdPlus lid) = goId lid
575+
goSimple (SPDX.ELicenseId lid) = goId lid
576+
goId lid =
577+
-- allow only OSI or FSF approved licenses (plus some specific execeptions).
578+
lid `elem` allowedLicenses
579+
|| SPDX.licenseIsOsiApproved lid
580+
|| SPDX.LId.licenseIsFsfLibre lid
535581

536582
-- pre `cabal-version: 2.2`
537-
goLegacy License.AllRightsReserved = False
538-
goLegacy _ = True
583+
goLegacy License.AllRightsReserved = pure License.AllRightsReserved
584+
goLegacy _ = Nothing

0 commit comments

Comments
 (0)