wim.go 22 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898
  1. //go:build windows || linux
  2. // +build windows linux
  3. // Package wim implements a WIM file parser.
  4. //
  5. // WIM files are used to distribute Windows file system and container images.
  6. // They are documented at https://msdn.microsoft.com/en-us/library/windows/desktop/dd861280.aspx.
  7. package wim
  8. import (
  9. "bytes"
  10. "crypto/sha1" //nolint:gosec // not used for secure application
  11. "encoding/binary"
  12. "encoding/xml"
  13. "errors"
  14. "fmt"
  15. "io"
  16. "strconv"
  17. "sync"
  18. "time"
  19. "unicode/utf16"
  20. )
  21. // File attribute constants from Windows.
  22. //
  23. //nolint:revive // var-naming: ALL_CAPS
  24. const (
  25. FILE_ATTRIBUTE_READONLY = 0x00000001
  26. FILE_ATTRIBUTE_HIDDEN = 0x00000002
  27. FILE_ATTRIBUTE_SYSTEM = 0x00000004
  28. FILE_ATTRIBUTE_DIRECTORY = 0x00000010
  29. FILE_ATTRIBUTE_ARCHIVE = 0x00000020
  30. FILE_ATTRIBUTE_DEVICE = 0x00000040
  31. FILE_ATTRIBUTE_NORMAL = 0x00000080
  32. FILE_ATTRIBUTE_TEMPORARY = 0x00000100
  33. FILE_ATTRIBUTE_SPARSE_FILE = 0x00000200
  34. FILE_ATTRIBUTE_REPARSE_POINT = 0x00000400
  35. FILE_ATTRIBUTE_COMPRESSED = 0x00000800
  36. FILE_ATTRIBUTE_OFFLINE = 0x00001000
  37. FILE_ATTRIBUTE_NOT_CONTENT_INDEXED = 0x00002000
  38. FILE_ATTRIBUTE_ENCRYPTED = 0x00004000
  39. FILE_ATTRIBUTE_INTEGRITY_STREAM = 0x00008000
  40. FILE_ATTRIBUTE_VIRTUAL = 0x00010000
  41. FILE_ATTRIBUTE_NO_SCRUB_DATA = 0x00020000
  42. FILE_ATTRIBUTE_EA = 0x00040000
  43. )
  44. // Windows processor architectures.
  45. //
  46. //nolint:revive // var-naming: ALL_CAPS
  47. const (
  48. PROCESSOR_ARCHITECTURE_INTEL = 0
  49. PROCESSOR_ARCHITECTURE_MIPS = 1
  50. PROCESSOR_ARCHITECTURE_ALPHA = 2
  51. PROCESSOR_ARCHITECTURE_PPC = 3
  52. PROCESSOR_ARCHITECTURE_SHX = 4
  53. PROCESSOR_ARCHITECTURE_ARM = 5
  54. PROCESSOR_ARCHITECTURE_IA64 = 6
  55. PROCESSOR_ARCHITECTURE_ALPHA64 = 7
  56. PROCESSOR_ARCHITECTURE_MSIL = 8
  57. PROCESSOR_ARCHITECTURE_AMD64 = 9
  58. PROCESSOR_ARCHITECTURE_IA32_ON_WIN64 = 10
  59. PROCESSOR_ARCHITECTURE_NEUTRAL = 11
  60. PROCESSOR_ARCHITECTURE_ARM64 = 12
  61. )
  62. var wimImageTag = [...]byte{'M', 'S', 'W', 'I', 'M', 0, 0, 0}
  63. // todo: replace this with pkg/guid.GUID (and add tests to make sure nothing breaks)
  64. type guid struct {
  65. Data1 uint32
  66. Data2 uint16
  67. Data3 uint16
  68. Data4 [8]byte
  69. }
  70. func (g guid) String() string {
  71. return fmt.Sprintf("%08x-%04x-%04x-%02x%02x-%02x%02x%02x%02x%02x%02x",
  72. g.Data1,
  73. g.Data2,
  74. g.Data3,
  75. g.Data4[0],
  76. g.Data4[1],
  77. g.Data4[2],
  78. g.Data4[3],
  79. g.Data4[4],
  80. g.Data4[5],
  81. g.Data4[6],
  82. g.Data4[7])
  83. }
  84. type resourceDescriptor struct {
  85. FlagsAndCompressedSize uint64
  86. Offset int64
  87. OriginalSize int64
  88. }
  89. type resFlag byte
  90. //nolint:deadcode,varcheck // need unused variables for iota to work
  91. const (
  92. resFlagFree resFlag = 1 << iota
  93. resFlagMetadata
  94. resFlagCompressed
  95. resFlagSpanned
  96. )
  97. const validate = false
  98. const supportedResFlags = resFlagMetadata | resFlagCompressed
  99. func (r *resourceDescriptor) Flags() resFlag {
  100. return resFlag(r.FlagsAndCompressedSize >> 56)
  101. }
  102. func (r *resourceDescriptor) CompressedSize() int64 {
  103. return int64(r.FlagsAndCompressedSize & 0xffffffffffffff)
  104. }
  105. func (r *resourceDescriptor) String() string {
  106. s := fmt.Sprintf("%d bytes at %d", r.CompressedSize(), r.Offset)
  107. if r.Flags()&4 != 0 {
  108. s += fmt.Sprintf(" (uncompresses to %d)", r.OriginalSize)
  109. }
  110. return s
  111. }
  112. // SHA1Hash contains the SHA1 hash of a file or stream.
  113. type SHA1Hash [20]byte
  114. type streamDescriptor struct {
  115. resourceDescriptor
  116. PartNumber uint16
  117. RefCount uint32
  118. Hash SHA1Hash
  119. }
  120. type hdrFlag uint32
  121. //nolint:deadcode,varcheck // need unused variables for iota to work
  122. const (
  123. hdrFlagReserved hdrFlag = 1 << iota
  124. hdrFlagCompressed
  125. hdrFlagReadOnly
  126. hdrFlagSpanned
  127. hdrFlagResourceOnly
  128. hdrFlagMetadataOnly
  129. hdrFlagWriteInProgress
  130. hdrFlagRpFix
  131. )
  132. //nolint:deadcode,varcheck // need unused variables for iota to work
  133. const (
  134. hdrFlagCompressReserved hdrFlag = 1 << (iota + 16)
  135. hdrFlagCompressXpress
  136. hdrFlagCompressLzx
  137. )
  138. const supportedHdrFlags = hdrFlagRpFix | hdrFlagReadOnly | hdrFlagCompressed | hdrFlagCompressLzx
  139. type wimHeader struct {
  140. ImageTag [8]byte
  141. Size uint32
  142. Version uint32
  143. Flags hdrFlag
  144. CompressionSize uint32
  145. WIMGuid guid
  146. PartNumber uint16
  147. TotalParts uint16
  148. ImageCount uint32
  149. OffsetTable resourceDescriptor
  150. XMLData resourceDescriptor
  151. BootMetadata resourceDescriptor
  152. BootIndex uint32
  153. Padding uint32
  154. Integrity resourceDescriptor
  155. Unused [60]byte
  156. }
  157. type securityblockDisk struct {
  158. TotalLength uint32
  159. NumEntries uint32
  160. }
  161. const securityblockDiskSize = 8
  162. type direntry struct {
  163. Attributes uint32
  164. SecurityID uint32
  165. SubdirOffset int64
  166. Unused1, Unused2 int64
  167. CreationTime Filetime
  168. LastAccessTime Filetime
  169. LastWriteTime Filetime
  170. Hash SHA1Hash
  171. Padding uint32
  172. ReparseHardLink int64
  173. StreamCount uint16
  174. ShortNameLength uint16
  175. FileNameLength uint16
  176. }
  177. var direntrySize = int64(binary.Size(direntry{}) + 8) // includes an 8-byte length prefix
  178. type streamentry struct {
  179. Unused int64
  180. Hash SHA1Hash
  181. NameLength int16
  182. }
  183. var streamentrySize = int64(binary.Size(streamentry{}) + 8) // includes an 8-byte length prefix
  184. // Filetime represents a Windows time.
  185. type Filetime struct {
  186. LowDateTime uint32
  187. HighDateTime uint32
  188. }
  189. // Time returns the time as time.Time.
  190. func (ft *Filetime) Time() time.Time {
  191. // 100-nanosecond intervals since January 1, 1601
  192. nsec := int64(ft.HighDateTime)<<32 + int64(ft.LowDateTime)
  193. // change starting time to the Epoch (00:00:00 UTC, January 1, 1970)
  194. nsec -= 116444736000000000
  195. // convert into nanoseconds
  196. nsec *= 100
  197. return time.Unix(0, nsec)
  198. }
  199. // UnmarshalXML unmarshalls the time from a WIM XML blob.
  200. func (ft *Filetime) UnmarshalXML(d *xml.Decoder, start xml.StartElement) error {
  201. type Time struct {
  202. Low string `xml:"LOWPART"`
  203. High string `xml:"HIGHPART"`
  204. }
  205. var t Time
  206. err := d.DecodeElement(&t, &start)
  207. if err != nil {
  208. return err
  209. }
  210. low, err := strconv.ParseUint(t.Low, 0, 32)
  211. if err != nil {
  212. return err
  213. }
  214. high, err := strconv.ParseUint(t.High, 0, 32)
  215. if err != nil {
  216. return err
  217. }
  218. ft.LowDateTime = uint32(low)
  219. ft.HighDateTime = uint32(high)
  220. return nil
  221. }
  222. type info struct {
  223. Image []ImageInfo `xml:"IMAGE"`
  224. }
  225. // ImageInfo contains information about the image.
  226. type ImageInfo struct {
  227. Name string `xml:"NAME"`
  228. Index int `xml:"INDEX,attr"`
  229. CreationTime Filetime `xml:"CREATIONTIME"`
  230. ModTime Filetime `xml:"LASTMODIFICATIONTIME"`
  231. Windows *WindowsInfo `xml:"WINDOWS"`
  232. }
  233. // WindowsInfo contains information about the Windows installation in the image.
  234. type WindowsInfo struct {
  235. Arch byte `xml:"ARCH"`
  236. ProductName string `xml:"PRODUCTNAME"`
  237. EditionID string `xml:"EDITIONID"`
  238. InstallationType string `xml:"INSTALLATIONTYPE"`
  239. ProductType string `xml:"PRODUCTTYPE"`
  240. Languages []string `xml:"LANGUAGES>LANGUAGE"`
  241. DefaultLanguage string `xml:"LANGUAGES>DEFAULT"`
  242. Version Version `xml:"VERSION"`
  243. SystemRoot string `xml:"SYSTEMROOT"`
  244. }
  245. // Version represents a Windows build version.
  246. type Version struct {
  247. Major int `xml:"MAJOR"`
  248. Minor int `xml:"MINOR"`
  249. Build int `xml:"BUILD"`
  250. SPBuild int `xml:"SPBUILD"`
  251. SPLevel int `xml:"SPLEVEL"`
  252. }
  253. // ParseError is returned when the WIM cannot be parsed.
  254. type ParseError struct {
  255. Oper string
  256. Path string
  257. Err error
  258. }
  259. func (e *ParseError) Error() string {
  260. if e.Path == "" {
  261. return "WIM parse error at " + e.Oper + ": " + e.Err.Error()
  262. }
  263. return fmt.Sprintf("WIM parse error: %s %s: %s", e.Oper, e.Path, e.Err.Error())
  264. }
  265. func (e *ParseError) Unwrap() error { return e.Err }
  266. // Reader provides functions to read a WIM file.
  267. type Reader struct {
  268. hdr wimHeader
  269. r io.ReaderAt
  270. fileData map[SHA1Hash]resourceDescriptor
  271. XMLInfo string // The XML information about the WIM.
  272. Image []*Image // The WIM's images.
  273. }
  274. // Image represents an image within a WIM file.
  275. type Image struct {
  276. wim *Reader
  277. offset resourceDescriptor
  278. sds [][]byte
  279. rootOffset int64
  280. r io.ReadCloser
  281. curOffset int64
  282. m sync.Mutex
  283. ImageInfo
  284. }
  285. // StreamHeader contains alternate data stream metadata.
  286. type StreamHeader struct {
  287. Name string
  288. Hash SHA1Hash
  289. Size int64
  290. }
  291. // Stream represents an alternate data stream or reparse point data stream.
  292. type Stream struct {
  293. StreamHeader
  294. wim *Reader
  295. offset resourceDescriptor
  296. }
  297. // FileHeader contains file metadata.
  298. type FileHeader struct {
  299. Name string
  300. ShortName string
  301. Attributes uint32
  302. SecurityDescriptor []byte
  303. CreationTime Filetime
  304. LastAccessTime Filetime
  305. LastWriteTime Filetime
  306. Hash SHA1Hash
  307. Size int64
  308. LinkID int64
  309. ReparseTag uint32
  310. ReparseReserved uint32
  311. }
  312. // File represents a file or directory in a WIM image.
  313. type File struct {
  314. FileHeader
  315. Streams []*Stream
  316. offset resourceDescriptor
  317. img *Image
  318. subdirOffset int64
  319. }
  320. // NewReader returns a Reader that can be used to read WIM file data.
  321. func NewReader(f io.ReaderAt) (*Reader, error) {
  322. r := &Reader{r: f}
  323. section := io.NewSectionReader(f, 0, 0xffff)
  324. err := binary.Read(section, binary.LittleEndian, &r.hdr)
  325. if err != nil {
  326. return nil, err
  327. }
  328. if r.hdr.ImageTag != wimImageTag {
  329. return nil, &ParseError{Oper: "image tag", Err: errors.New("not a WIM file")}
  330. }
  331. if r.hdr.Flags&^supportedHdrFlags != 0 {
  332. return nil, fmt.Errorf("unsupported WIM flags %x", r.hdr.Flags&^supportedHdrFlags)
  333. }
  334. if r.hdr.CompressionSize != 0x8000 {
  335. return nil, fmt.Errorf("unsupported compression size %d", r.hdr.CompressionSize)
  336. }
  337. if r.hdr.TotalParts != 1 {
  338. return nil, errors.New("multi-part WIM not supported")
  339. }
  340. fileData, images, err := r.readOffsetTable(&r.hdr.OffsetTable)
  341. if err != nil {
  342. return nil, err
  343. }
  344. xmlinfo, err := r.readXML()
  345. if err != nil {
  346. return nil, err
  347. }
  348. var inf info
  349. err = xml.Unmarshal([]byte(xmlinfo), &inf)
  350. if err != nil {
  351. return nil, &ParseError{Oper: "XML info", Err: err}
  352. }
  353. for i, img := range images {
  354. for _, imgInfo := range inf.Image {
  355. if imgInfo.Index == i+1 {
  356. img.ImageInfo = imgInfo
  357. break
  358. }
  359. }
  360. }
  361. r.fileData = fileData
  362. r.Image = images
  363. r.XMLInfo = xmlinfo
  364. return r, nil
  365. }
  366. // Close releases resources associated with the Reader.
  367. func (r *Reader) Close() error {
  368. for _, img := range r.Image {
  369. img.reset()
  370. }
  371. return nil
  372. }
  373. func (r *Reader) resourceReader(hdr *resourceDescriptor) (io.ReadCloser, error) {
  374. return r.resourceReaderWithOffset(hdr, 0)
  375. }
  376. func (r *Reader) resourceReaderWithOffset(hdr *resourceDescriptor, offset int64) (io.ReadCloser, error) {
  377. var sr io.ReadCloser
  378. section := io.NewSectionReader(r.r, hdr.Offset, hdr.CompressedSize())
  379. if hdr.Flags()&resFlagCompressed == 0 {
  380. _, _ = section.Seek(offset, 0)
  381. sr = io.NopCloser(section)
  382. } else {
  383. cr, err := newCompressedReader(section, hdr.OriginalSize, offset)
  384. if err != nil {
  385. return nil, err
  386. }
  387. sr = cr
  388. }
  389. return sr, nil
  390. }
  391. func (r *Reader) readResource(hdr *resourceDescriptor) ([]byte, error) {
  392. rsrc, err := r.resourceReader(hdr)
  393. if err != nil {
  394. return nil, err
  395. }
  396. defer rsrc.Close()
  397. return io.ReadAll(rsrc)
  398. }
  399. func (r *Reader) readXML() (string, error) {
  400. if r.hdr.XMLData.CompressedSize() == 0 {
  401. return "", nil
  402. }
  403. rsrc, err := r.resourceReader(&r.hdr.XMLData)
  404. if err != nil {
  405. return "", err
  406. }
  407. defer rsrc.Close()
  408. xmlData := make([]uint16, r.hdr.XMLData.OriginalSize/2)
  409. err = binary.Read(rsrc, binary.LittleEndian, xmlData)
  410. if err != nil {
  411. return "", &ParseError{Oper: "XML data", Err: err}
  412. }
  413. // The BOM will always indicate little-endian UTF-16.
  414. if xmlData[0] != 0xfeff {
  415. return "", &ParseError{Oper: "XML data", Err: errors.New("invalid BOM")}
  416. }
  417. return string(utf16.Decode(xmlData[1:])), nil
  418. }
  419. func (r *Reader) readOffsetTable(res *resourceDescriptor) (map[SHA1Hash]resourceDescriptor, []*Image, error) {
  420. fileData := make(map[SHA1Hash]resourceDescriptor)
  421. var images []*Image
  422. offsetTable, err := r.readResource(res)
  423. if err != nil {
  424. return nil, nil, &ParseError{Oper: "offset table", Err: err}
  425. }
  426. br := bytes.NewReader(offsetTable)
  427. for i := 0; ; i++ {
  428. var res streamDescriptor
  429. err := binary.Read(br, binary.LittleEndian, &res)
  430. if err == io.EOF { //nolint:errorlint
  431. break
  432. }
  433. if err != nil {
  434. return nil, nil, &ParseError{Oper: "offset table", Err: err}
  435. }
  436. if res.Flags()&^supportedResFlags != 0 {
  437. return nil, nil, &ParseError{Oper: "offset table", Err: errors.New("unsupported resource flag")}
  438. }
  439. // Validation for ad-hoc testing
  440. if validate {
  441. sec, err := r.resourceReader(&res.resourceDescriptor)
  442. if err != nil {
  443. panic(fmt.Sprint(i, err))
  444. }
  445. hash := sha1.New() //nolint:gosec // not used for secure application
  446. _, err = io.Copy(hash, sec)
  447. sec.Close()
  448. if err != nil {
  449. panic(fmt.Sprint(i, err))
  450. }
  451. var cmphash SHA1Hash
  452. copy(cmphash[:], hash.Sum(nil))
  453. if cmphash != res.Hash {
  454. panic(fmt.Sprint(i, "hash mismatch"))
  455. }
  456. }
  457. if res.Flags()&resFlagMetadata != 0 {
  458. image := &Image{
  459. wim: r,
  460. offset: res.resourceDescriptor,
  461. }
  462. images = append(images, image)
  463. } else {
  464. fileData[res.Hash] = res.resourceDescriptor
  465. }
  466. }
  467. if len(images) != int(r.hdr.ImageCount) {
  468. return nil, nil, &ParseError{Oper: "offset table", Err: errors.New("mismatched image count")}
  469. }
  470. return fileData, images, nil
  471. }
  472. func (*Reader) readSecurityDescriptors(rsrc io.Reader) (sds [][]byte, n int64, err error) {
  473. var secBlock securityblockDisk
  474. err = binary.Read(rsrc, binary.LittleEndian, &secBlock)
  475. if err != nil {
  476. return sds, 0, &ParseError{Oper: "security table", Err: err}
  477. }
  478. n += securityblockDiskSize
  479. secSizes := make([]int64, secBlock.NumEntries)
  480. err = binary.Read(rsrc, binary.LittleEndian, &secSizes)
  481. if err != nil {
  482. return sds, n, &ParseError{Oper: "security table sizes", Err: err}
  483. }
  484. n += int64(secBlock.NumEntries * 8)
  485. sds = make([][]byte, secBlock.NumEntries)
  486. for i, size := range secSizes {
  487. sd := make([]byte, size&0xffffffff)
  488. _, err = io.ReadFull(rsrc, sd)
  489. if err != nil {
  490. return sds, n, &ParseError{Oper: "security descriptor", Err: err}
  491. }
  492. n += int64(len(sd))
  493. sds[i] = sd
  494. }
  495. secsize := int64((secBlock.TotalLength + 7) &^ 7)
  496. if n > secsize {
  497. return sds, n, &ParseError{Oper: "security descriptor", Err: errors.New("security descriptor table too small")}
  498. }
  499. _, err = io.CopyN(io.Discard, rsrc, secsize-n)
  500. if err != nil {
  501. return sds, n, err
  502. }
  503. n = secsize
  504. return sds, n, nil
  505. }
  506. // Open parses the image and returns the root directory.
  507. func (img *Image) Open() (*File, error) {
  508. if img.sds == nil {
  509. rsrc, err := img.wim.resourceReaderWithOffset(&img.offset, img.rootOffset)
  510. if err != nil {
  511. return nil, err
  512. }
  513. sds, n, err := img.wim.readSecurityDescriptors(rsrc)
  514. if err != nil {
  515. rsrc.Close()
  516. return nil, err
  517. }
  518. img.sds = sds
  519. img.r = rsrc
  520. img.rootOffset = n
  521. img.curOffset = n
  522. }
  523. f, err := img.readdir(img.rootOffset)
  524. if err != nil {
  525. return nil, err
  526. }
  527. if len(f) != 1 {
  528. return nil, &ParseError{Oper: "root directory", Err: errors.New("expected exactly 1 root directory entry")}
  529. }
  530. return f[0], err
  531. }
  532. func (img *Image) reset() {
  533. if img.r != nil {
  534. img.r.Close()
  535. img.r = nil
  536. }
  537. img.curOffset = -1
  538. }
  539. func (img *Image) readdir(offset int64) ([]*File, error) {
  540. img.m.Lock()
  541. defer img.m.Unlock()
  542. if offset < img.curOffset || offset > img.curOffset+chunkSize {
  543. // Reset to seek backward or to seek forward very far.
  544. img.reset()
  545. }
  546. if img.r == nil {
  547. rsrc, err := img.wim.resourceReaderWithOffset(&img.offset, offset)
  548. if err != nil {
  549. return nil, err
  550. }
  551. img.r = rsrc
  552. img.curOffset = offset
  553. }
  554. if offset > img.curOffset {
  555. _, err := io.CopyN(io.Discard, img.r, offset-img.curOffset)
  556. if err != nil {
  557. img.reset()
  558. if err == io.EOF { //nolint:errorlint
  559. err = io.ErrUnexpectedEOF
  560. }
  561. return nil, err
  562. }
  563. }
  564. var entries []*File
  565. for {
  566. e, n, err := img.readNextEntry(img.r)
  567. img.curOffset += n
  568. if err == io.EOF { //nolint:errorlint
  569. break
  570. }
  571. if err != nil {
  572. img.reset()
  573. return nil, err
  574. }
  575. entries = append(entries, e)
  576. }
  577. return entries, nil
  578. }
  579. func (img *Image) readNextEntry(r io.Reader) (*File, int64, error) {
  580. var length int64
  581. err := binary.Read(r, binary.LittleEndian, &length)
  582. if err != nil {
  583. return nil, 0, &ParseError{Oper: "directory length check", Err: err}
  584. }
  585. if length == 0 {
  586. return nil, 8, io.EOF
  587. }
  588. left := length
  589. if left < direntrySize {
  590. return nil, 0, &ParseError{Oper: "directory entry", Err: errors.New("size too short")}
  591. }
  592. var dentry direntry
  593. err = binary.Read(r, binary.LittleEndian, &dentry)
  594. if err != nil {
  595. return nil, 0, &ParseError{Oper: "directory entry", Err: err}
  596. }
  597. left -= direntrySize
  598. namesLen := int64(dentry.FileNameLength + 2 + dentry.ShortNameLength)
  599. if left < namesLen {
  600. return nil, 0, &ParseError{Oper: "directory entry", Err: errors.New("size too short for names")}
  601. }
  602. names := make([]uint16, namesLen/2)
  603. err = binary.Read(r, binary.LittleEndian, names)
  604. if err != nil {
  605. return nil, 0, &ParseError{Oper: "file name", Err: err}
  606. }
  607. left -= namesLen
  608. var name, shortName string
  609. if dentry.FileNameLength > 0 {
  610. name = string(utf16.Decode(names[:dentry.FileNameLength/2]))
  611. }
  612. if dentry.ShortNameLength > 0 {
  613. shortName = string(utf16.Decode(names[dentry.FileNameLength/2+1:]))
  614. }
  615. var offset resourceDescriptor
  616. zerohash := SHA1Hash{}
  617. if dentry.Hash != zerohash {
  618. var ok bool
  619. offset, ok = img.wim.fileData[dentry.Hash]
  620. if !ok {
  621. return nil, 0, &ParseError{
  622. Oper: "directory entry",
  623. Path: name,
  624. Err: fmt.Errorf("could not find file data matching hash %#v", dentry),
  625. }
  626. }
  627. }
  628. f := &File{
  629. FileHeader: FileHeader{
  630. Attributes: dentry.Attributes,
  631. CreationTime: dentry.CreationTime,
  632. LastAccessTime: dentry.LastAccessTime,
  633. LastWriteTime: dentry.LastWriteTime,
  634. Hash: dentry.Hash,
  635. Size: offset.OriginalSize,
  636. Name: name,
  637. ShortName: shortName,
  638. },
  639. offset: offset,
  640. img: img,
  641. subdirOffset: dentry.SubdirOffset,
  642. }
  643. isDir := false
  644. if dentry.Attributes&FILE_ATTRIBUTE_REPARSE_POINT == 0 {
  645. f.LinkID = dentry.ReparseHardLink
  646. if dentry.Attributes&FILE_ATTRIBUTE_DIRECTORY != 0 {
  647. isDir = true
  648. }
  649. } else {
  650. f.ReparseTag = uint32(dentry.ReparseHardLink)
  651. f.ReparseReserved = uint32(dentry.ReparseHardLink >> 32)
  652. }
  653. if isDir && f.subdirOffset == 0 {
  654. return nil, 0, &ParseError{Oper: "directory entry", Path: name, Err: errors.New("no subdirectory data for directory")}
  655. } else if !isDir && f.subdirOffset != 0 {
  656. return nil, 0, &ParseError{Oper: "directory entry", Path: name, Err: errors.New("unexpected subdirectory data for non-directory")}
  657. }
  658. if dentry.SecurityID != 0xffffffff {
  659. f.SecurityDescriptor = img.sds[dentry.SecurityID]
  660. }
  661. _, err = io.CopyN(io.Discard, r, left)
  662. if err != nil {
  663. if err == io.EOF { //nolint:errorlint
  664. err = io.ErrUnexpectedEOF
  665. }
  666. return nil, 0, err
  667. }
  668. if dentry.StreamCount > 0 {
  669. var streams []*Stream
  670. for i := uint16(0); i < dentry.StreamCount; i++ {
  671. s, n, err := img.readNextStream(r)
  672. length += n
  673. if err != nil {
  674. return nil, 0, err
  675. }
  676. // The first unnamed stream should be treated as the file stream.
  677. if i == 0 && s.Name == "" {
  678. f.Hash = s.Hash
  679. f.Size = s.Size
  680. f.offset = s.offset
  681. } else if s.Name != "" {
  682. streams = append(streams, s)
  683. }
  684. }
  685. f.Streams = streams
  686. }
  687. if dentry.Attributes&FILE_ATTRIBUTE_REPARSE_POINT != 0 && f.Size == 0 {
  688. return nil, 0, &ParseError{
  689. Oper: "directory entry",
  690. Path: name,
  691. Err: errors.New("reparse point is missing reparse stream"),
  692. }
  693. }
  694. return f, length, nil
  695. }
  696. func (img *Image) readNextStream(r io.Reader) (*Stream, int64, error) {
  697. var length int64
  698. err := binary.Read(r, binary.LittleEndian, &length)
  699. if err != nil {
  700. if err == io.EOF { //nolint:errorlint
  701. err = io.ErrUnexpectedEOF
  702. }
  703. return nil, 0, &ParseError{Oper: "stream length check", Err: err}
  704. }
  705. left := length
  706. if left < streamentrySize {
  707. return nil, 0, &ParseError{Oper: "stream entry", Err: errors.New("size too short")}
  708. }
  709. var sentry streamentry
  710. err = binary.Read(r, binary.LittleEndian, &sentry)
  711. if err != nil {
  712. return nil, 0, &ParseError{Oper: "stream entry", Err: err}
  713. }
  714. left -= streamentrySize
  715. if left < int64(sentry.NameLength) {
  716. return nil, 0, &ParseError{Oper: "stream entry", Err: errors.New("size too short for name")}
  717. }
  718. names := make([]uint16, sentry.NameLength/2)
  719. err = binary.Read(r, binary.LittleEndian, names)
  720. if err != nil {
  721. return nil, 0, &ParseError{Oper: "file name", Err: err}
  722. }
  723. left -= int64(sentry.NameLength)
  724. name := string(utf16.Decode(names))
  725. var offset resourceDescriptor
  726. if sentry.Hash != (SHA1Hash{}) {
  727. var ok bool
  728. offset, ok = img.wim.fileData[sentry.Hash]
  729. if !ok {
  730. return nil, 0, &ParseError{
  731. Oper: "stream entry",
  732. Path: name,
  733. Err: fmt.Errorf("could not find file data matching hash %v", sentry.Hash),
  734. }
  735. }
  736. }
  737. s := &Stream{
  738. StreamHeader: StreamHeader{
  739. Hash: sentry.Hash,
  740. Size: offset.OriginalSize,
  741. Name: name,
  742. },
  743. wim: img.wim,
  744. offset: offset,
  745. }
  746. _, err = io.CopyN(io.Discard, r, left)
  747. if err != nil {
  748. if err == io.EOF { //nolint:errorlint
  749. err = io.ErrUnexpectedEOF
  750. }
  751. return nil, 0, err
  752. }
  753. return s, length, nil
  754. }
  755. // Open returns an io.ReadCloser that can be used to read the stream's contents.
  756. func (s *Stream) Open() (io.ReadCloser, error) {
  757. return s.wim.resourceReader(&s.offset)
  758. }
  759. // Open returns an io.ReadCloser that can be used to read the file's contents.
  760. func (f *File) Open() (io.ReadCloser, error) {
  761. return f.img.wim.resourceReader(&f.offset)
  762. }
  763. // Readdir reads the directory entries.
  764. func (f *File) Readdir() ([]*File, error) {
  765. if !f.IsDir() {
  766. return nil, errors.New("not a directory")
  767. }
  768. return f.img.readdir(f.subdirOffset)
  769. }
  770. // IsDir returns whether the given file is a directory. It returns false when it
  771. // is a directory reparse point.
  772. func (f *FileHeader) IsDir() bool {
  773. return f.Attributes&(FILE_ATTRIBUTE_DIRECTORY|FILE_ATTRIBUTE_REPARSE_POINT) == FILE_ATTRIBUTE_DIRECTORY
  774. }