module Hadolint.Rule.DL3045 (rule) where

import qualified Data.Char as Char
import Data.Map.Strict (Map)
import qualified Data.Map.Strict as Map
import Data.Maybe (fromMaybe)
import Data.Text (Text)
import qualified Data.Text as Text
import Hadolint.Rule
import Language.Docker.Syntax

-- This data encapsulates the name of a build stage. It may be None withing an
-- `ONBUILD` context.
data Stage
  = Stage {Stage -> Text
stage :: Text}
  | None
  deriving (Stage -> Stage -> Bool
(Stage -> Stage -> Bool) -> (Stage -> Stage -> Bool) -> Eq Stage
forall a. (a -> a -> Bool) -> (a -> a -> Bool) -> Eq a
/= :: Stage -> Stage -> Bool
$c/= :: Stage -> Stage -> Bool
== :: Stage -> Stage -> Bool
$c== :: Stage -> Stage -> Bool
Eq, Eq Stage
Eq Stage
-> (Stage -> Stage -> Ordering)
-> (Stage -> Stage -> Bool)
-> (Stage -> Stage -> Bool)
-> (Stage -> Stage -> Bool)
-> (Stage -> Stage -> Bool)
-> (Stage -> Stage -> Stage)
-> (Stage -> Stage -> Stage)
-> Ord Stage
Stage -> Stage -> Bool
Stage -> Stage -> Ordering
Stage -> Stage -> Stage
forall a.
Eq a
-> (a -> a -> Ordering)
-> (a -> a -> Bool)
-> (a -> a -> Bool)
-> (a -> a -> Bool)
-> (a -> a -> Bool)
-> (a -> a -> a)
-> (a -> a -> a)
-> Ord a
min :: Stage -> Stage -> Stage
$cmin :: Stage -> Stage -> Stage
max :: Stage -> Stage -> Stage
$cmax :: Stage -> Stage -> Stage
>= :: Stage -> Stage -> Bool
$c>= :: Stage -> Stage -> Bool
> :: Stage -> Stage -> Bool
$c> :: Stage -> Stage -> Bool
<= :: Stage -> Stage -> Bool
$c<= :: Stage -> Stage -> Bool
< :: Stage -> Stage -> Bool
$c< :: Stage -> Stage -> Bool
compare :: Stage -> Stage -> Ordering
$ccompare :: Stage -> Stage -> Ordering
$cp1Ord :: Eq Stage
Ord)

-- | The state here keeps the image name/alias of the current build stage
-- and a map from image names/aliases to a Bool, saving whether or
-- not a `WORKDIR` has been set in a build stage.
data Acc
  = Acc {Acc -> Stage
current :: Stage, Acc -> Map Stage Bool
workdirSet :: Map Stage Bool}
  | Empty

rule :: Rule args
rule :: Rule args
rule = (Linenumber -> State Acc -> Instruction args -> State Acc)
-> State Acc -> Rule args
forall a args.
(Linenumber -> State a -> Instruction args -> State a)
-> State a -> Rule args
customRule Linenumber -> State Acc -> Instruction args -> State Acc
forall args.
Linenumber -> State Acc -> Instruction args -> State Acc
check (Acc -> State Acc
forall a. a -> State a
emptyState Acc
Empty)
  where
    code :: RuleCode
code = RuleCode
"DL3045"
    severity :: DLSeverity
severity = DLSeverity
DLWarningC
    message :: Text
message = Text
"`COPY` to a relative destination without `WORKDIR` set."

    check :: Linenumber -> State Acc -> Instruction args -> State Acc
check Linenumber
_ State Acc
st (From BaseImage
from) = State Acc
st State Acc -> (State Acc -> State Acc) -> State Acc
forall a b. a -> (a -> b) -> b
|> (Acc -> Acc) -> State Acc -> State Acc
forall a. (a -> a) -> State a -> State a
modify (BaseImage -> Acc -> Acc
rememberStage BaseImage
from)
    check Linenumber
_ State Acc
st (Workdir Text
_) = State Acc
st State Acc -> (State Acc -> State Acc) -> State Acc
forall a b. a -> (a -> b) -> b
|> (Acc -> Acc) -> State Acc -> State Acc
forall a. (a -> a) -> State a -> State a
modify Acc -> Acc
rememberWorkdir
    check Linenumber
line State Acc
st (Copy (CopyArgs NonEmpty SourcePath
_ (TargetPath Text
dest) Chown
_ Chmod
_ CopySource
_))
      | Acc Stage
s Map Stage Bool
m <- State Acc -> Acc
forall a. State a -> a
state State Acc
st, Just Bool
True <- Stage -> Map Stage Bool -> Maybe Bool
forall k a. Ord k => k -> Map k a -> Maybe a
Map.lookup Stage
s Map Stage Bool
m = State Acc
st -- workdir has been set
      | Text
"/" Text -> Text -> Bool
`Text.isPrefixOf` (Char -> Bool) -> Text -> Text
Text.dropAround Char -> Bool
quotePredicate Text
dest = State Acc
st -- absolute dest. normal
      | Text -> Bool
isWindowsAbsolute ((Char -> Bool) -> Text -> Text
Text.dropAround Char -> Bool
quotePredicate Text
dest) = State Acc
st -- absolute dest. windows
      | Text
"$" Text -> Text -> Bool
`Text.isPrefixOf` (Char -> Bool) -> Text -> Text
Text.dropAround Char -> Bool
quotePredicate Text
dest = State Acc
st -- dest is a variable
      | Bool
otherwise = State Acc
st State Acc -> (State Acc -> State Acc) -> State Acc
forall a b. a -> (a -> b) -> b
|> CheckFailure -> State Acc -> State Acc
forall a. CheckFailure -> State a -> State a
addFail CheckFailure :: RuleCode -> DLSeverity -> Text -> Linenumber -> CheckFailure
CheckFailure {Linenumber
Text
RuleCode
DLSeverity
line :: Linenumber
message :: Text
severity :: DLSeverity
code :: RuleCode
line :: Linenumber
message :: Text
severity :: DLSeverity
code :: RuleCode
..}
    check Linenumber
_ State Acc
st Instruction args
_ = State Acc
st
{-# INLINEABLE rule #-}

rememberStage :: BaseImage -> Acc -> Acc
rememberStage :: BaseImage -> Acc -> Acc
rememberStage BaseImage {$sel:alias:BaseImage :: BaseImage -> Maybe ImageAlias
alias = Just ImageAlias
als} Acc
Empty =
  Acc :: Stage -> Map Stage Bool -> Acc
Acc
    { current :: Stage
current = Stage :: Text -> Stage
Stage {stage :: Text
stage = ImageAlias -> Text
unImageAlias ImageAlias
als},
      workdirSet :: Map Stage Bool
workdirSet = Map Stage Bool
forall a. Monoid a => a
mempty
    }
rememberStage BaseImage {$sel:alias:BaseImage :: BaseImage -> Maybe ImageAlias
alias = Maybe ImageAlias
Nothing, Image
$sel:image:BaseImage :: BaseImage -> Image
image :: Image
image} Acc
Empty =
  Acc :: Stage -> Map Stage Bool -> Acc
Acc
    { current :: Stage
current = Stage :: Text -> Stage
Stage {stage :: Text
stage = Image -> Text
imageName Image
image},
      workdirSet :: Map Stage Bool
workdirSet = Map Stage Bool
forall a. Monoid a => a
mempty
    }
rememberStage BaseImage {$sel:alias:BaseImage :: BaseImage -> Maybe ImageAlias
alias = Just ImageAlias
als, Image
image :: Image
$sel:image:BaseImage :: BaseImage -> Image
image} Acc {Map Stage Bool
Stage
workdirSet :: Map Stage Bool
current :: Stage
workdirSet :: Acc -> Map Stage Bool
current :: Acc -> Stage
..} =
  Acc :: Stage -> Map Stage Bool -> Acc
Acc
    { current :: Stage
current = Stage :: Text -> Stage
Stage {stage :: Text
stage = ImageAlias -> Text
unImageAlias ImageAlias
als},
      workdirSet :: Map Stage Bool
workdirSet =
        let parentValue :: Bool
parentValue =
              Stage -> Map Stage Bool -> Maybe Bool
forall k a. Ord k => k -> Map k a -> Maybe a
Map.lookup (Stage :: Text -> Stage
Stage {stage :: Text
stage = Image -> Text
imageName Image
image}) Map Stage Bool
workdirSet
                Maybe Bool -> (Maybe Bool -> Bool) -> Bool
forall a b. a -> (a -> b) -> b
|> Bool -> Maybe Bool -> Bool
forall a. a -> Maybe a -> a
fromMaybe Bool
False
         in Map Stage Bool
workdirSet
              Map Stage Bool
-> (Map Stage Bool -> Map Stage Bool) -> Map Stage Bool
forall a b. a -> (a -> b) -> b
|> Stage -> Bool -> Map Stage Bool -> Map Stage Bool
forall k a. Ord k => k -> a -> Map k a -> Map k a
Map.insert (Stage :: Text -> Stage
Stage {stage :: Text
stage = ImageAlias -> Text
unImageAlias ImageAlias
als}) Bool
parentValue
    }
rememberStage BaseImage {$sel:alias:BaseImage :: BaseImage -> Maybe ImageAlias
alias = Maybe ImageAlias
Nothing, Image
image :: Image
$sel:image:BaseImage :: BaseImage -> Image
image} Acc {Map Stage Bool
Stage
workdirSet :: Map Stage Bool
current :: Stage
workdirSet :: Acc -> Map Stage Bool
current :: Acc -> Stage
..} =
  Acc :: Stage -> Map Stage Bool -> Acc
Acc
    { current :: Stage
current = Stage :: Text -> Stage
Stage {stage :: Text
stage = Image -> Text
imageName Image
image},
      workdirSet :: Map Stage Bool
workdirSet =
        let parentValue :: Bool
parentValue =
              Stage -> Map Stage Bool -> Maybe Bool
forall k a. Ord k => k -> Map k a -> Maybe a
Map.lookup (Stage :: Text -> Stage
Stage {stage :: Text
stage = Image -> Text
imageName Image
image}) Map Stage Bool
workdirSet
                Maybe Bool -> (Maybe Bool -> Bool) -> Bool
forall a b. a -> (a -> b) -> b
|> Bool -> Maybe Bool -> Bool
forall a. a -> Maybe a -> a
fromMaybe Bool
False
         in Map Stage Bool
workdirSet
              Map Stage Bool
-> (Map Stage Bool -> Map Stage Bool) -> Map Stage Bool
forall a b. a -> (a -> b) -> b
|> Stage -> Bool -> Map Stage Bool -> Map Stage Bool
forall k a. Ord k => k -> a -> Map k a -> Map k a
Map.insert (Stage :: Text -> Stage
Stage {stage :: Text
stage = Image -> Text
imageName Image
image}) Bool
parentValue
    }

rememberWorkdir :: Acc -> Acc
rememberWorkdir :: Acc -> Acc
rememberWorkdir Acc
Empty = Acc :: Stage -> Map Stage Bool -> Acc
Acc {current :: Stage
current = Stage
None, workdirSet :: Map Stage Bool
workdirSet = Stage -> Bool -> Map Stage Bool -> Map Stage Bool
forall k a. Ord k => k -> a -> Map k a -> Map k a
Map.insert Stage
None Bool
True Map Stage Bool
forall k a. Map k a
Map.empty}
rememberWorkdir Acc {Map Stage Bool
Stage
workdirSet :: Map Stage Bool
current :: Stage
workdirSet :: Acc -> Map Stage Bool
current :: Acc -> Stage
..} = Acc :: Stage -> Map Stage Bool -> Acc
Acc {Stage
current :: Stage
current :: Stage
current, workdirSet :: Map Stage Bool
workdirSet = Stage -> Bool -> Map Stage Bool -> Map Stage Bool
forall k a. Ord k => k -> a -> Map k a -> Map k a
Map.insert Stage
current Bool
True Map Stage Bool
workdirSet}

quotePredicate :: Char -> Bool
quotePredicate :: Char -> Bool
quotePredicate Char
c
  | Char
c Char -> Char -> Bool
forall a. Eq a => a -> a -> Bool
== Char
'"' = Bool
True
  | Char
c Char -> Char -> Bool
forall a. Eq a => a -> a -> Bool
== Char
'\'' = Bool
True
  | Bool
otherwise = Bool
False

isWindowsAbsolute :: Text.Text -> Bool
isWindowsAbsolute :: Text -> Bool
isWindowsAbsolute Text
path
  | Just (Char
d, Text
r1) <- Text -> Maybe (Char, Text)
Text.uncons Text
path,
    Just (Char
c, Text
_) <- Text -> Maybe (Char, Text)
Text.uncons Text
r1,
    Char -> Bool
Char.isLetter Char
d Bool -> Bool -> Bool
&& Char
':' Char -> Char -> Bool
forall a. Eq a => a -> a -> Bool
== Char
c = Bool
True
  | Bool
otherwise = Bool
False