module Hadolint.Rule.DL3045 (rule) where

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
_ 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
":\\" Text -> Text -> Bool
`Text.isPrefixOf` Linenumber -> Text -> Text
Text.drop Linenumber
1 ((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