@@ -34,7 +34,6 @@ import Control.DeepSeq
34
34
( force )
35
35
import Control.Monad
36
36
( guard )
37
- import qualified Data.ByteString.Lazy.Char8 as BS
38
37
import qualified Paths_cabal_install (version )
39
38
import Distribution.Verbosity (Verbosity )
40
39
import Distribution.Pretty (prettyShow )
@@ -57,6 +56,7 @@ import System.IO
57
56
( withFile , IOMode (ReadMode ), hGetContents , hClose )
58
57
import System.IO.Error
59
58
( isDoesNotExistError )
59
+ import Distribution.Parsec (explicitEitherParsec )
60
60
import Distribution.Simple.Program
61
61
( Program , simpleProgram , ConfiguredProgram , programPath
62
62
, ProgramInvocation (.. ), programInvocation
@@ -74,6 +74,13 @@ import System.Random (randomRIO)
74
74
import System.Exit (ExitCode (.. ))
75
75
import Data.Version (showVersion )
76
76
77
+ import qualified Crypto.Hash.SHA256 as SHA256
78
+ import qualified Data.ByteString.Base16 as Base16
79
+ import qualified Distribution.Compat.CharParsing as P
80
+ import qualified Data.ByteString as BS
81
+ import qualified Data.ByteString.Char8 as BS8
82
+ import qualified Data.ByteString.Lazy as LBS
83
+ import qualified Data.ByteString.Lazy.Char8 as LBS8
77
84
78
85
------------------------------------------------------------------------------
79
86
-- Downloading a URI, given an HttpTransport
@@ -83,6 +90,12 @@ data DownloadResult = FileAlreadyInCache
83
90
| FileDownloaded FilePath
84
91
deriving (Eq )
85
92
93
+ data DownloadCheck
94
+ = Downloaded -- ^ already downloaded and sha256 matches
95
+ | CheckETag String -- ^ already downloaded and we have etag
96
+ | NeedsDownload (Maybe BS. ByteString ) -- ^ needs download with optional hash check
97
+ deriving Eq
98
+
86
99
downloadURI :: HttpTransport
87
100
-> Verbosity
88
101
-> URI -- ^ What to download
@@ -96,13 +109,34 @@ downloadURI _transport verbosity uri path | uriScheme uri == "file:" = do
96
109
97
110
downloadURI transport verbosity uri path = do
98
111
99
- let etagPath = path <.> " etag"
100
- targetExists <- doesFileExist path
101
- etagPathExists <- doesFileExist etagPath
102
- -- In rare cases the target file doesn't exist, but the etag does.
103
- etag <- if targetExists && etagPathExists
104
- then Just <$> readFile etagPath
105
- else return Nothing
112
+ targetExists <- doesFileExist path
113
+
114
+ downloadCheck <-
115
+ -- if we have uriFrag, then we expect there to be #sha256=...
116
+ if not (null uriFrag)
117
+ then case sha256parsed of
118
+ -- we know the hash, and target exists
119
+ Right expected | targetExists -> do
120
+ contents <- LBS. readFile path
121
+ let actual = SHA256. hashlazy contents
122
+ if expected == actual
123
+ then return Downloaded
124
+ else return (NeedsDownload (Just expected))
125
+
126
+ -- we known the hash, target doesn't exist
127
+ Right expected -> return (NeedsDownload (Just expected))
128
+
129
+ -- we failed to parse uriFragment
130
+ Left err -> die' verbosity $
131
+ " Cannot parse URI fragment " ++ uriFrag ++ " " ++ err
132
+
133
+ -- if there are no uri fragment, use ETag
134
+ else do
135
+ etagPathExists <- doesFileExist etagPath
136
+ -- In rare cases the target file doesn't exist, but the etag does.
137
+ if targetExists && etagPathExists
138
+ then return (CheckETag etagPath)
139
+ else return (NeedsDownload Nothing )
106
140
107
141
-- Only use the external http transports if we actually have to
108
142
-- (or have been told to do so)
@@ -114,12 +148,29 @@ downloadURI transport verbosity uri path = do
114
148
| otherwise
115
149
= transport
116
150
117
- withTempFileName (takeDirectory path) (takeFileName path) $ \ tmpFile -> do
151
+ case downloadCheck of
152
+ Downloaded -> return FileAlreadyInCache
153
+ CheckETag etag -> makeDownload transport' Nothing (Just etag)
154
+ NeedsDownload hash -> makeDownload transport' hash Nothing
155
+
156
+ where
157
+ makeDownload transport' sha256 etag = withTempFileName (takeDirectory path) (takeFileName path) $ \ tmpFile -> do
118
158
result <- getHttp transport' verbosity uri etag tmpFile []
119
159
120
160
-- Only write the etag if we get a 200 response code.
121
161
-- A 304 still sends us an etag header.
122
162
case result of
163
+ -- if we have hash, we don't care about etag.
164
+ (200 , _) | Just expected <- sha256 -> do
165
+ contents <- LBS. readFile tmpFile
166
+ let actual = SHA256. hashlazy contents
167
+ unless (actual == expected) $
168
+ die' verbosity $ unwords
169
+ [ " Failed to download" , show uri
170
+ , " : SHA256 don't match; expected:" , BS8. unpack (Base16. encode expected)
171
+ , " actual:" , BS8. unpack (Base16. encode actual)
172
+ ]
173
+
123
174
(200 , Just newEtag) -> writeFile etagPath newEtag
124
175
_ -> return ()
125
176
@@ -131,9 +182,20 @@ downloadURI transport verbosity uri path = do
131
182
304 -> do
132
183
notice verbosity " Skipping download: local and remote files match."
133
184
return FileAlreadyInCache
134
- errCode -> die' verbosity $ " Failed to download " ++ show uri
185
+ errCode -> die' verbosity $ " failed to download " ++ show uri
135
186
++ " : HTTP code " ++ show errCode
136
187
188
+ etagPath = path <.> " etag"
189
+ uriFrag = uriFragment uri
190
+
191
+ sha256parsed :: Either String BS. ByteString
192
+ sha256parsed = explicitEitherParsec fragmentParser uriFrag
193
+
194
+ fragmentParser = do
195
+ _ <- P. string " #sha256="
196
+ str <- some P. hexDigit
197
+ return (fst (Base16. decode (BS8. pack str)))
198
+
137
199
------------------------------------------------------------------------------
138
200
-- Utilities for repo url management
139
201
--
@@ -463,7 +525,7 @@ wgetTransport prog =
463
525
\ responseFile responseHandle -> do
464
526
hClose responseHandle
465
527
(body, boundary) <- generateMultipartBody path
466
- BS . hPut tmpHandle body
528
+ LBS . hPut tmpHandle body
467
529
hClose tmpHandle
468
530
let args = [ " --post-file=" ++ tmpFile
469
531
, " --user-agent=" ++ userAgent
@@ -586,7 +648,7 @@ powershellTransport prog =
586
648
withTempFile (takeDirectory path)
587
649
(takeFileName path) $ \ tmpFile tmpHandle -> do
588
650
(body, boundary) <- generateMultipartBody path
589
- BS . hPut tmpHandle body
651
+ LBS . hPut tmpHandle body
590
652
hClose tmpHandle
591
653
fullPath <- canonicalizePath tmpFile
592
654
@@ -736,7 +798,7 @@ plainHttpTransport =
736
798
rqHeaders = [ Header HdrIfNoneMatch t
737
799
| t <- maybeToList etag ]
738
800
++ reqHeaders,
739
- rqBody = BS . empty
801
+ rqBody = LBS . empty
740
802
}
741
803
(_, resp) <- cabalBrowse verbosity Nothing (request req)
742
804
let code = convertRspCode (rspCode resp)
@@ -752,7 +814,7 @@ plainHttpTransport =
752
814
(body, boundary) <- generateMultipartBody path
753
815
let headers = [ Header HdrContentType
754
816
(" multipart/form-data; boundary=" ++ boundary)
755
- , Header HdrContentLength (show (BS .length body))
817
+ , Header HdrContentLength (show (LBS8 .length body))
756
818
, Header HdrAccept (" text/plain" )
757
819
]
758
820
req = Request {
@@ -765,11 +827,11 @@ plainHttpTransport =
765
827
return (convertRspCode (rspCode resp), rspErrorString resp)
766
828
767
829
puthttpfile verbosity uri path auth headers = do
768
- body <- BS .readFile path
830
+ body <- LBS8 .readFile path
769
831
let req = Request {
770
832
rqURI = uri,
771
833
rqMethod = PUT ,
772
- rqHeaders = Header HdrContentLength (show (BS .length body))
834
+ rqHeaders = Header HdrContentLength (show (LBS8 .length body))
773
835
: Header HdrAccept " text/plain"
774
836
: headers,
775
837
rqBody = body
@@ -783,7 +845,7 @@ plainHttpTransport =
783
845
case lookupHeader HdrContentType (rspHeaders resp) of
784
846
Just contenttype
785
847
| takeWhile (/= ' ;' ) contenttype == " text/plain"
786
- -> BS . unpack (rspBody resp)
848
+ -> LBS8 . unpack (rspBody resp)
787
849
_ -> rspReason resp
788
850
789
851
cabalBrowse verbosity auth act = do
@@ -829,17 +891,17 @@ trim = f . f
829
891
-- Multipart stuff partially taken from cgi package.
830
892
--
831
893
832
- generateMultipartBody :: FilePath -> IO (BS . ByteString , String )
894
+ generateMultipartBody :: FilePath -> IO (LBS . ByteString , String )
833
895
generateMultipartBody path = do
834
- content <- BS .readFile path
896
+ content <- LBS .readFile path
835
897
boundary <- genBoundary
836
- let ! body = formatBody content (BS . pack boundary)
898
+ let ! body = formatBody content (LBS8 . pack boundary)
837
899
return (body, boundary)
838
900
where
839
901
formatBody content boundary =
840
- BS .concat $
902
+ LBS8 .concat $
841
903
[ crlf, dd, boundary, crlf ]
842
- ++ [ BS . pack (show header) | header <- headers ]
904
+ ++ [ LBS8 . pack (show header) | header <- headers ]
843
905
++ [ crlf
844
906
, content
845
907
, crlf, dd, boundary, dd, crlf ]
@@ -851,8 +913,8 @@ generateMultipartBody path = do
851
913
, Header HdrContentType " application/x-gzip"
852
914
]
853
915
854
- crlf = BS . pack " \r\n "
855
- dd = BS . pack " --"
916
+ crlf = LBS8 . pack " \r\n "
917
+ dd = LBS8 . pack " --"
856
918
857
919
genBoundary :: IO String
858
920
genBoundary = do
0 commit comments