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
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
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
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 :: forall args. Rule args
rule = forall a args.
(Linenumber -> State a -> Instruction args -> State a)
-> State a -> Rule args
customRule forall {args}.
Linenumber -> State Acc -> Instruction args -> State Acc
check (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 forall a b. a -> (a -> b) -> b
|> 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 forall a b. a -> (a -> b) -> b
|> 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)) CopyFlags
_)
      | Acc Stage
s Map Stage Bool
m <- forall a. State a -> a
state State Acc
st, Just Bool
True <- 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 forall a b. a -> (a -> b) -> b
|> forall a. CheckFailure -> State a -> State a
addFail 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
    { current :: Stage
current = Stage {stage :: Text
stage = ImageAlias -> Text
unImageAlias ImageAlias
als},
      workdirSet :: Map Stage Bool
workdirSet = 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
    { current :: Stage
current = Stage {stage :: Text
stage = Image -> Text
imageName Image
image},
      workdirSet :: Map Stage Bool
workdirSet = 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
    { current :: Stage
current = Stage {stage :: Text
stage = ImageAlias -> Text
unImageAlias ImageAlias
als},
      workdirSet :: Map Stage Bool
workdirSet =
        let parentValue :: Bool
parentValue =
              forall k a. Ord k => k -> Map k a -> Maybe a
Map.lookup (Stage {stage :: Text
stage = Image -> Text
imageName Image
image}) Map Stage Bool
workdirSet
                forall a b. a -> (a -> b) -> b
|> forall a. a -> Maybe a -> a
fromMaybe Bool
False
         in Map Stage Bool
workdirSet
              forall a b. a -> (a -> b) -> b
|> forall k a. Ord k => k -> a -> Map k a -> Map k a
Map.insert (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
    { current :: Stage
current = Stage {stage :: Text
stage = Image -> Text
imageName Image
image},
      workdirSet :: Map Stage Bool
workdirSet =
        let parentValue :: Bool
parentValue =
              forall k a. Ord k => k -> Map k a -> Maybe a
Map.lookup (Stage {stage :: Text
stage = Image -> Text
imageName Image
image}) Map Stage Bool
workdirSet
                forall a b. a -> (a -> b) -> b
|> forall a. a -> Maybe a -> a
fromMaybe Bool
False
         in Map Stage Bool
workdirSet
              forall a b. a -> (a -> b) -> b
|> forall k a. Ord k => k -> a -> Map k a -> Map k a
Map.insert (Stage {stage :: Text
stage = Image -> Text
imageName Image
image}) Bool
parentValue
    }

rememberWorkdir :: Acc -> Acc
rememberWorkdir :: Acc -> Acc
rememberWorkdir Acc
Empty = Acc {current :: Stage
current = Stage
None, workdirSet :: Map Stage Bool
workdirSet = forall k a. Ord k => k -> a -> Map k a -> Map k a
Map.insert Stage
None Bool
True 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
current :: Stage
current :: Stage
current, workdirSet :: Map Stage Bool
workdirSet = 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 forall a. Eq a => a -> a -> Bool
== Char
'"' = Bool
True
  | Char
c 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
':' forall a. Eq a => a -> a -> Bool
== Char
c = Bool
True
  | Bool
otherwise = Bool
False