diff --git a/go.mod b/go.mod
index 40ce9cc18f..1f5f5b1a19 100644
--- a/go.mod
+++ b/go.mod
@@ -6,7 +6,6 @@ require (
bazil.org/fuse v0.0.0-20180421153158-65cc252bf669
github.com/360EntSecGroup-Skylar/excelize v1.4.0
github.com/LeeEirc/terminalparser v0.0.0-20240205084113-fbf78c8480f2
- github.com/Microsoft/go-winio v0.6.2
github.com/aliyun/alibaba-cloud-sdk-go v1.61.684
github.com/anacrolix/torrent v1.57.0
github.com/aws/aws-sdk-go-v2 v1.41.5
@@ -113,7 +112,7 @@ require (
k8s.io/cri-api v0.28.15
k8s.io/klog/v2 v2.90.1
moul.io/http2curl/v2 v2.3.0
- yunion.io/x/cloudmux v0.3.10-0-alpha.1.0.20260722023537-f41e01f2eee3
+ yunion.io/x/cloudmux v0.3.10-0-alpha.1.0.20260812062623-2b79dfea19bd
yunion.io/x/executor v0.0.0-20260312022053-f538abd2b005
yunion.io/x/jsonutils v1.0.1-0.20260715075349-615cfb44ff7c
yunion.io/x/log v1.0.1-0.20240305175729-7cf2d6cd5a91
@@ -141,6 +140,7 @@ require (
github.com/DataDog/datadog-go/v5 v5.0.2 // indirect
github.com/DataDog/go-tuf v0.3.0--fix-localmeta-fork // indirect
github.com/DataDog/sketches-go v1.2.1 // indirect
+ github.com/Microsoft/go-winio v0.6.2 // indirect
github.com/Microsoft/hcsshim v0.11.4 // indirect
github.com/RoaringBitmap/roaring v1.2.3 // indirect
github.com/StackExchange/wmi v1.2.1 // indirect
diff --git a/go.sum b/go.sum
index 0adba24151..695617da1e 100644
--- a/go.sum
+++ b/go.sum
@@ -1754,8 +1754,8 @@ sigs.k8s.io/yaml v1.1.0/go.mod h1:UJmg0vDUVViEyp3mgSv9WPwZCDxu4rQW1olrI1uml+o=
sigs.k8s.io/yaml v1.2.0/go.mod h1:yfXDCHCao9+ENCvLSE62v9VSji2MKu5jeNfTrofGhJc=
sigs.k8s.io/yaml v1.3.0 h1:a2VclLzOGrwOHDiV8EfBGhvjHvP46CtW5j6POvhYGGo=
sigs.k8s.io/yaml v1.3.0/go.mod h1:GeOyir5tyXNByN85N/dRIT9es5UQNerPYEKK56eTBm8=
-yunion.io/x/cloudmux v0.3.10-0-alpha.1.0.20260722023537-f41e01f2eee3 h1:QBX44ByHjCYbstpxDejNPI+GqJmcsJIE8nTQCGfxUsw=
-yunion.io/x/cloudmux v0.3.10-0-alpha.1.0.20260722023537-f41e01f2eee3/go.mod h1:QgrcekfkD3fELNYipZnlUfhR8dPI8CCUsuzC2SkP3YE=
+yunion.io/x/cloudmux v0.3.10-0-alpha.1.0.20260812062623-2b79dfea19bd h1:rtRzbOZP24DqQ0u0SYfrSdYWLX6Taov0AwkVQyv95p0=
+yunion.io/x/cloudmux v0.3.10-0-alpha.1.0.20260812062623-2b79dfea19bd/go.mod h1:QgrcekfkD3fELNYipZnlUfhR8dPI8CCUsuzC2SkP3YE=
yunion.io/x/executor v0.0.0-20260312022053-f538abd2b005 h1:3sWwcjGXGjG9mLBWa7AyLq+QSi0udTAx21pfVQRFMBE=
yunion.io/x/executor v0.0.0-20260312022053-f538abd2b005/go.mod h1:Uxuou9WQIeJXNpy7t2fPLL0BYLvLiMvGQwY7Qc6aSws=
yunion.io/x/jsonutils v0.0.0-20190625054549-a964e1e8a051/go.mod h1:4N0/RVzsYL3kH3WE/H1BjUQdFiWu50JGCFQuuy+Z634=
diff --git a/pkg/util/isoutils/isoutils.go b/pkg/util/isoutils/isoutils.go
index 4667ac642a..6febec9665 100644
--- a/pkg/util/isoutils/isoutils.go
+++ b/pkg/util/isoutils/isoutils.go
@@ -186,8 +186,8 @@ func DetectOSFromISO(r io.Reader) (*ISOInfo, error) {
return nil, err
}
- // ========== 识别Windows系列 ==========
- if reader.FileExists("sources/install.wim") {
+ // ========== 识别Windows系列(install.wim 或 install.esd) ==========
+ if reader.FileExists("sources/install.wim") || reader.FileExists("sources/install.esd") {
return DetectWindowsEdition(reader)
}
diff --git a/pkg/util/isoutils/wim_xml.go b/pkg/util/isoutils/wim_xml.go
new file mode 100644
index 0000000000..ce2830f37b
--- /dev/null
+++ b/pkg/util/isoutils/wim_xml.go
@@ -0,0 +1,211 @@
+// Copyright 2019 Yunion
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package isoutils
+
+import (
+ "encoding/binary"
+ "encoding/xml"
+ "fmt"
+ "io"
+ "unicode/utf16"
+
+ "yunion.io/x/log"
+ "yunion.io/x/pkg/util/imagetools"
+)
+
+var wimImageTag = [8]byte{'M', 'S', 'W', 'I', 'M', 0, 0, 0}
+
+const (
+ wimResFlagCompressed = 0x04
+)
+
+// wimResourceDesc matches the on-disk WIM resource descriptor (24 bytes).
+type wimResourceDesc struct {
+ FlagsAndCompressedSize uint64
+ Offset int64
+ OriginalSize int64
+}
+
+func (r wimResourceDesc) flags() byte {
+ return byte(r.FlagsAndCompressedSize >> 56)
+}
+
+func (r wimResourceDesc) compressedSize() int64 {
+ return int64(r.FlagsAndCompressedSize & 0xffffffffffffff)
+}
+
+// wimHeaderDisk is the on-disk WIM header (208 bytes / 0xd0).
+type wimHeaderDisk struct {
+ ImageTag [8]byte
+ Size uint32
+ Version uint32
+ Flags uint32
+ CompressionSize uint32
+ WIMGuid [16]byte
+ PartNumber uint16
+ TotalParts uint16
+ ImageCount uint32
+ OffsetTable wimResourceDesc
+ XMLData wimResourceDesc
+ BootMetadata wimResourceDesc
+ BootIndex uint32
+ Padding uint32
+ Integrity wimResourceDesc
+ Unused [60]byte
+}
+
+type wimXMLInfo struct {
+ Image []wimXMLImage `xml:"IMAGE"`
+}
+
+type wimXMLImage struct {
+ Name string `xml:"NAME"`
+ Index int `xml:"INDEX,attr"`
+ Windows *wimWindowsInfo `xml:"WINDOWS"`
+}
+
+type wimWindowsInfo struct {
+ Arch byte `xml:"ARCH"`
+ ProductName string `xml:"PRODUCTNAME"`
+ EditionID string `xml:"EDITIONID"`
+ ProductType string `xml:"PRODUCTTYPE"`
+ DefaultLanguage string `xml:"LANGUAGES>DEFAULT"`
+ Version wimXMLVersion `xml:"VERSION"`
+}
+
+type wimXMLVersion struct {
+ Major int `xml:"MAJOR"`
+ Minor int `xml:"MINOR"`
+ Build int `xml:"BUILD"`
+}
+
+// parseWimXmlMetadata reads WIM/ESD header + uncompressed XML metadata and maps Windows version.
+// Content compression (LZMS/XPRESS) is ignored; only the XML blob is required for edition detection.
+func parseWimXmlMetadata(r io.ReaderAt) (*ISOInfo, error) {
+ var hdr wimHeaderDisk
+ if err := binary.Read(io.NewSectionReader(r, 0, int64(binary.Size(hdr))), binary.LittleEndian, &hdr); err != nil {
+ return nil, fmt.Errorf("read WIM header: %w", err)
+ }
+ if hdr.ImageTag != wimImageTag {
+ return nil, fmt.Errorf("not a WIM/ESD file")
+ }
+ if hdr.XMLData.compressedSize() == 0 || hdr.XMLData.OriginalSize == 0 {
+ return nil, fmt.Errorf("WIM/ESD has no XML metadata")
+ }
+ if hdr.XMLData.flags()&wimResFlagCompressed != 0 {
+ return nil, fmt.Errorf("compressed WIM XML metadata is not supported")
+ }
+
+ xmlBytes := make([]byte, hdr.XMLData.OriginalSize)
+ if _, err := r.ReadAt(xmlBytes, hdr.XMLData.Offset); err != nil {
+ return nil, fmt.Errorf("read WIM XML metadata: %w", err)
+ }
+
+ xmlStr, err := decodeWimUTF16XML(xmlBytes)
+ if err != nil {
+ return nil, err
+ }
+
+ var info wimXMLInfo
+ if err := xml.Unmarshal([]byte(xmlStr), &info); err != nil {
+ return nil, fmt.Errorf("parse WIM XML: %w", err)
+ }
+
+ for _, image := range info.Image {
+ if image.Windows == nil {
+ continue
+ }
+ result := mapWindowsVersion(image.Windows)
+ ver := fmt.Sprintf("%d.%d.%d", image.Windows.Version.Major, image.Windows.Version.Minor, image.Windows.Version.Build)
+ log.Debugf("识别到 %s 版本: %s -> %s", result.Distro, ver, result.Version)
+ return result, nil
+ }
+ return nil, fmt.Errorf("no WINDOWS metadata found in WIM/ESD XML")
+}
+
+func decodeWimUTF16XML(data []byte) (string, error) {
+ if len(data) < 2 || len(data)%2 != 0 {
+ return "", fmt.Errorf("invalid WIM XML encoding")
+ }
+ u16 := make([]uint16, len(data)/2)
+ for i := 0; i < len(u16); i++ {
+ u16[i] = binary.LittleEndian.Uint16(data[i*2:])
+ }
+ // BOM is little-endian UTF-16 (0xFEFF)
+ if u16[0] != 0xfeff {
+ return "", fmt.Errorf("invalid WIM XML BOM")
+ }
+ return string(utf16.Decode(u16[1:])), nil
+}
+
+func mapWindowsVersion(win *wimWindowsInfo) *ISOInfo {
+ result := &ISOInfo{
+ Distro: imagetools.OS_DIST_WINDOWS,
+ Language: win.DefaultLanguage,
+ }
+ switch win.Arch {
+ case 9:
+ result.Arch = "x86_64"
+ case 12:
+ result.Arch = "arm64"
+ case 0:
+ result.Arch = "x86"
+ }
+
+ majMin := fmt.Sprintf("%d.%d", win.Version.Major, win.Version.Minor)
+ switch majMin {
+ case "6.0":
+ result.Version = "Windows Vista"
+ case "6.1":
+ result.Version = "Windows 7"
+ case "6.2":
+ result.Version = "Windows 8"
+ case "6.3":
+ result.Version = "Windows 8.1"
+ case "10.0":
+ if win.Version.Build >= 27500 {
+ result.Version = "Windows 12"
+ } else if win.Version.Build >= 22000 {
+ result.Version = "Windows 11"
+ } else {
+ result.Version = "Windows 10"
+ }
+ }
+
+ if win.ProductType == "ServerNT" {
+ result.Distro = imagetools.OS_DIST_WINDOWS_SERVER
+ switch majMin {
+ case "6.0":
+ result.Version = "Windows Server 2008"
+ case "6.1":
+ result.Version = "Windows Server 2008 R2"
+ case "6.2":
+ result.Version = "Windows Server 2012"
+ case "6.3":
+ result.Version = "Windows Server 2012 R2"
+ case "10.0":
+ if win.Version.Build >= 26040 {
+ result.Version = "Windows Server 2025"
+ } else if win.Version.Build >= 20348 {
+ result.Version = "Windows Server 2022"
+ } else if win.Version.Build >= 17763 {
+ result.Version = "Windows Server 2019"
+ } else if win.Version.Build >= 14393 {
+ result.Version = "Windows Server 2016"
+ }
+ }
+ }
+ return result
+}
diff --git a/pkg/util/isoutils/wim_xml_test.go b/pkg/util/isoutils/wim_xml_test.go
new file mode 100644
index 0000000000..330da10a8f
--- /dev/null
+++ b/pkg/util/isoutils/wim_xml_test.go
@@ -0,0 +1,197 @@
+// Copyright 2019 Yunion
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package isoutils
+
+import (
+ "bytes"
+ "encoding/binary"
+ "testing"
+ "unicode/utf16"
+
+ "yunion.io/x/pkg/util/imagetools"
+)
+
+func encodeWimUTF16XML(s string) []byte {
+ u16 := utf16.Encode([]rune(s))
+ out := make([]byte, 2+len(u16)*2)
+ binary.LittleEndian.PutUint16(out[0:], 0xfeff) // BOM
+ for i, v := range u16 {
+ binary.LittleEndian.PutUint16(out[2+i*2:], v)
+ }
+ return out
+}
+
+func buildMinimalWimWithXML(xmlASCII string) []byte {
+ xmlData := encodeWimUTF16XML(xmlASCII)
+ hdrSize := binary.Size(wimHeaderDisk{})
+ offset := int64(hdrSize)
+
+ var hdr wimHeaderDisk
+ hdr.ImageTag = wimImageTag
+ hdr.Size = uint32(hdrSize)
+ hdr.Version = 0x10d00
+ hdr.PartNumber = 1
+ hdr.TotalParts = 1
+ hdr.ImageCount = 1
+ // uncompressed XML resource: flags=0, compressed size = original size
+ hdr.XMLData = wimResourceDesc{
+ FlagsAndCompressedSize: uint64(len(xmlData)),
+ Offset: offset,
+ OriginalSize: int64(len(xmlData)),
+ }
+
+ buf := &bytes.Buffer{}
+ _ = binary.Write(buf, binary.LittleEndian, &hdr)
+ buf.Write(xmlData)
+ return buf.Bytes()
+}
+
+func TestParseWimXmlMetadataWindows11(t *testing.T) {
+ xml := `
+
+
+ Windows 11 Pro
+
+ 9
+ Microsoft® Windows® Operating System
+ Professional
+ WinNT
+
+ zh-CN
+ zh-CN
+
+
+ 10
+ 0
+ 22631
+
+
+
+`
+ data := buildMinimalWimWithXML(xml)
+ info, err := parseWimXmlMetadata(bytes.NewReader(data))
+ if err != nil {
+ t.Fatalf("parseWimXmlMetadata: %v", err)
+ }
+ if info.Distro != imagetools.OS_DIST_WINDOWS {
+ t.Fatalf("distro: got %s want %s", info.Distro, imagetools.OS_DIST_WINDOWS)
+ }
+ if info.Version != "Windows 11" {
+ t.Fatalf("version: got %s want Windows 11", info.Version)
+ }
+ if info.Arch != "x86_64" {
+ t.Fatalf("arch: got %s want x86_64", info.Arch)
+ }
+ if info.Language != "zh-CN" {
+ t.Fatalf("language: got %s want zh-CN", info.Language)
+ }
+}
+
+func TestParseWimXmlMetadataWindowsServer2022(t *testing.T) {
+ xml := `
+
+
+ Windows Server 2022 SERVERSTANDARD
+
+ 9
+ ServerNT
+
+ en-US
+
+
+ 10
+ 0
+ 20348
+
+
+
+`
+ data := buildMinimalWimWithXML(xml)
+ info, err := parseWimXmlMetadata(bytes.NewReader(data))
+ if err != nil {
+ t.Fatalf("parseWimXmlMetadata: %v", err)
+ }
+ if info.Distro != imagetools.OS_DIST_WINDOWS_SERVER {
+ t.Fatalf("distro: got %s want %s", info.Distro, imagetools.OS_DIST_WINDOWS_SERVER)
+ }
+ if info.Version != "Windows Server 2022" {
+ t.Fatalf("version: got %s want Windows Server 2022", info.Version)
+ }
+}
+
+func TestParseWimXmlMetadataRejectsCompressedXML(t *testing.T) {
+ xmlData := encodeWimUTF16XML(``)
+ hdrSize := binary.Size(wimHeaderDisk{})
+ var hdr wimHeaderDisk
+ hdr.ImageTag = wimImageTag
+ hdr.Size = uint32(hdrSize)
+ hdr.PartNumber = 1
+ hdr.TotalParts = 1
+ hdr.XMLData = wimResourceDesc{
+ FlagsAndCompressedSize: uint64(wimResFlagCompressed)<<56 | uint64(len(xmlData)),
+ Offset: int64(hdrSize),
+ OriginalSize: int64(len(xmlData)),
+ }
+ buf := &bytes.Buffer{}
+ _ = binary.Write(buf, binary.LittleEndian, &hdr)
+ buf.Write(xmlData)
+
+ _, err := parseWimXmlMetadata(bytes.NewReader(buf.Bytes()))
+ if err == nil {
+ t.Fatal("expected error for compressed XML")
+ }
+}
+
+func TestMapWindowsVersion(t *testing.T) {
+ cases := []struct {
+ name string
+ win wimWindowsInfo
+ distro string
+ version string
+ }{
+ {
+ name: "win10",
+ win: wimWindowsInfo{
+ Arch: 9, ProductType: "WinNT",
+ Version: wimXMLVersion{Major: 10, Minor: 0, Build: 19045},
+ },
+ distro: imagetools.OS_DIST_WINDOWS, version: "Windows 10",
+ },
+ {
+ name: "win7",
+ win: wimWindowsInfo{
+ Arch: 9, ProductType: "WinNT",
+ Version: wimXMLVersion{Major: 6, Minor: 1, Build: 7601},
+ },
+ distro: imagetools.OS_DIST_WINDOWS, version: "Windows 7",
+ },
+ {
+ name: "server2019",
+ win: wimWindowsInfo{
+ Arch: 9, ProductType: "ServerNT",
+ Version: wimXMLVersion{Major: 10, Minor: 0, Build: 17763},
+ },
+ distro: imagetools.OS_DIST_WINDOWS_SERVER, version: "Windows Server 2019",
+ },
+ }
+ for _, c := range cases {
+ t.Run(c.name, func(t *testing.T) {
+ info := mapWindowsVersion(&c.win)
+ if info.Distro != c.distro || info.Version != c.version {
+ t.Fatalf("got %s/%s want %s/%s", info.Distro, info.Version, c.distro, c.version)
+ }
+ })
+ }
+}
diff --git a/pkg/util/isoutils/winiso_unix.go b/pkg/util/isoutils/winiso_unix.go
index ce10f23316..a0478a8474 100644
--- a/pkg/util/isoutils/winiso_unix.go
+++ b/pkg/util/isoutils/winiso_unix.go
@@ -19,80 +19,30 @@ package isoutils
import (
"fmt"
-
- "github.com/Microsoft/go-winio/wim"
-
- "yunion.io/x/log"
- "yunion.io/x/pkg/util/imagetools"
)
-// ========== 7. 保留Windows版本识别函数(适配新结构) ==========
+// DetectWindowsEdition reads sources/install.wim or sources/install.esd XML metadata
+// to determine Windows edition/version. Prefer .wim, fall back to .esd.
func DetectWindowsEdition(r *ISOFileReader) (*ISOInfo, error) {
- wimFile, err := r.GetFile("sources/install.wim")
- if err != nil {
- return nil, err
- }
- wim, err := wim.NewReader(wimFile.NewReader())
- if err != nil {
- return nil, err
- }
- result := &ISOInfo{}
- for _, image := range wim.Image {
- version := fmt.Sprintf("%d.%d.%d", image.Windows.Version.Major, image.Windows.Version.Minor, image.Windows.Version.Build)
- if image.Windows != nil {
- if image.Windows.Arch == 9 {
- result.Arch = "x86_64"
- } else if image.Windows.Arch == 12 {
- result.Arch = "arm64"
- } else if image.Windows.Arch == 0 {
- result.Arch = "x86"
- }
- result.Distro = imagetools.OS_DIST_WINDOWS
- result.Language = image.Windows.DefaultLanguage
- switch fmt.Sprintf("%d.%d", image.Windows.Version.Major, image.Windows.Version.Minor) {
- case "6.0":
- result.Version = "Windows Vista"
- case "6.1":
- result.Version = "Windows 7"
- case "6.2":
- result.Version = "Windows 8"
- case "6.3":
- result.Version = "Windows 8.1"
- case "10.0":
- if image.Windows.Version.Build >= 27500 {
- result.Version = "Windows 12"
- } else if image.Windows.Version.Build >= 22000 {
- result.Version = "Windows 11"
- } else {
- result.Version = "Windows 10"
- }
- }
- if image.Windows.ProductType == "ServerNT" {
- result.Distro = imagetools.OS_DIST_WINDOWS_SERVER
- switch fmt.Sprintf("%d.%d", image.Windows.Version.Major, image.Windows.Version.Minor) {
- case "6.0":
- result.Version = "Windows Server 2008"
- case "6.1":
- result.Version = "Windows Server 2008 R2"
- case "6.2":
- result.Version = "Windows Server 2012"
- case "6.3":
- result.Version = "Windows Server 2012 R2"
- case "10.0":
- if image.Windows.Version.Build >= 26040 {
- result.Version = "Windows Server 2025"
- } else if image.Windows.Version.Build >= 20348 {
- result.Version = "Windows Server 2022"
- } else if image.Windows.Version.Build >= 17763 {
- result.Version = "Windows Server 2019"
- } else if image.Windows.Version.Build >= 14393 {
- result.Version = "Windows Server 2016"
- }
- }
- }
- log.Debugf("识别到 %s 版本: %s -> %s", result.Distro, version, result.Version)
- break
+ var lastErr error
+ for _, path := range []string{"sources/install.wim", "sources/install.esd"} {
+ if !r.FileExists(path) {
+ continue
}
+ f, err := r.GetFile(path)
+ if err != nil {
+ lastErr = fmt.Errorf("open %s: %w", path, err)
+ continue
+ }
+ info, err := parseWimXmlMetadata(f.NewReader())
+ if err != nil {
+ lastErr = fmt.Errorf("parse %s: %w", path, err)
+ continue
+ }
+ return info, nil
}
- return result, nil
+ if lastErr != nil {
+ return nil, lastErr
+ }
+ return nil, fmt.Errorf("sources/install.wim or sources/install.esd not found")
}
diff --git a/vendor/github.com/Microsoft/go-winio/wim/decompress.go b/vendor/github.com/Microsoft/go-winio/wim/decompress.go
deleted file mode 100644
index 993a413187..0000000000
--- a/vendor/github.com/Microsoft/go-winio/wim/decompress.go
+++ /dev/null
@@ -1,139 +0,0 @@
-//go:build windows || linux
-// +build windows linux
-
-package wim
-
-import (
- "encoding/binary"
- "io"
-
- "github.com/Microsoft/go-winio/wim/lzx"
-)
-
-const chunkSize = 32768 // Compressed resource chunk size
-
-type compressedReader struct {
- r *io.SectionReader
- d io.ReadCloser
- chunks []int64
- curChunk int
- originalSize int64
-}
-
-func newCompressedReader(r *io.SectionReader, originalSize int64, offset int64) (*compressedReader, error) {
- nchunks := (originalSize + chunkSize - 1) / chunkSize
- var base int64
- chunks := make([]int64, nchunks)
- if originalSize <= 0xffffffff {
- // 32-bit chunk offsets
- base = (nchunks - 1) * 4
- chunks32 := make([]uint32, nchunks-1)
- err := binary.Read(r, binary.LittleEndian, chunks32)
- if err != nil {
- return nil, err
- }
- for i, n := range chunks32 {
- chunks[i+1] = int64(n)
- }
- } else {
- // 64-bit chunk offsets
- base = (nchunks - 1) * 8
- err := binary.Read(r, binary.LittleEndian, chunks[1:])
- if err != nil {
- return nil, err
- }
- }
-
- for i, c := range chunks {
- chunks[i] = c + base
- }
-
- cr := &compressedReader{
- r: r,
- chunks: chunks,
- originalSize: originalSize,
- }
-
- err := cr.reset(int(offset / chunkSize))
- if err != nil {
- return nil, err
- }
-
- suboff := offset % chunkSize
- if suboff != 0 {
- _, err := io.CopyN(io.Discard, cr.d, suboff)
- if err != nil {
- return nil, err
- }
- }
- return cr, nil
-}
-
-func (r *compressedReader) chunkOffset(n int) int64 {
- if n == len(r.chunks) {
- return r.r.Size()
- }
- return r.chunks[n]
-}
-
-func (r *compressedReader) chunkSize(n int) int {
- return int(r.chunkOffset(n+1) - r.chunkOffset(n))
-}
-
-func (r *compressedReader) uncompressedSize(n int) int {
- if n < len(r.chunks)-1 {
- return chunkSize
- }
- size := int(r.originalSize % chunkSize)
- if size == 0 {
- size = chunkSize
- }
- return size
-}
-
-func (r *compressedReader) reset(n int) error {
- if n >= len(r.chunks) {
- return io.EOF
- }
- if r.d != nil {
- r.d.Close()
- }
- r.curChunk = n
- size := r.chunkSize(n)
- uncompressedSize := r.uncompressedSize(n)
- section := io.NewSectionReader(r.r, r.chunkOffset(n), int64(size))
- if size != uncompressedSize {
- d, err := lzx.NewReader(section, uncompressedSize)
- if err != nil {
- return err
- }
- r.d = d
- } else {
- r.d = io.NopCloser(section)
- }
-
- return nil
-}
-
-func (r *compressedReader) Read(b []byte) (int, error) {
- for {
- n, err := r.d.Read(b)
- if err != io.EOF { //nolint:errorlint
- return n, err
- }
-
- err = r.reset(r.curChunk + 1)
- if err != nil {
- return n, err
- }
- }
-}
-
-func (r *compressedReader) Close() error {
- var err error
- if r.d != nil {
- err = r.d.Close()
- r.d = nil
- }
- return err
-}
diff --git a/vendor/github.com/Microsoft/go-winio/wim/lzx/lzx.go b/vendor/github.com/Microsoft/go-winio/wim/lzx/lzx.go
deleted file mode 100644
index e5db82602c..0000000000
--- a/vendor/github.com/Microsoft/go-winio/wim/lzx/lzx.go
+++ /dev/null
@@ -1,598 +0,0 @@
-// Package lzx implements a decompressor for the the WIM variant of the
-// LZX compression algorithm.
-//
-// The LZX algorithm is an earlier variant of LZX DELTA, which is documented
-// at https://msdn.microsoft.com/en-us/library/cc483133(v=exchg.80).aspx.
-package lzx
-
-import (
- "bytes"
- "encoding/binary"
- "errors"
- "io"
-)
-
-const (
- maincodecount = 496
- maincodesplit = 256
- lencodecount = 249
- lenshift = 9
- codemask = 0x1ff
- tablebits = 9
- tablesize = 1 << tablebits
-
- maxBlockSize = 32768
- windowSize = 32768
-
- maxTreePathLen = 16
-
- e8filesize = 12000000
- maxe8offset = 0x3fffffff
-
- verbatimBlock = 1
- alignedOffsetBlock = 2
- uncompressedBlock = 3
-)
-
-var footerBits = [...]byte{
- 0, 0, 0, 0, 1, 1, 2, 2,
- 3, 3, 4, 4, 5, 5, 6, 6,
- 7, 7, 8, 8, 9, 9, 10, 10,
- 11, 11, 12, 12, 13, 13, 14,
-}
-
-var basePosition = [...]uint16{
- 0, 1, 2, 3, 4, 6, 8, 12,
- 16, 24, 32, 48, 64, 96, 128, 192,
- 256, 384, 512, 768, 1024, 1536, 2048, 3072,
- 4096, 6144, 8192, 12288, 16384, 24576, 32768,
-}
-
-var (
- errCorrupt = errors.New("LZX data corrupt")
-)
-
-// Reader is an interface used by the decompressor to access
-// the input stream. If the provided io.Reader does not implement
-// Reader, then a bufio.Reader is used.
-type Reader interface {
- io.Reader
- io.ByteReader
-}
-
-type decompressor struct {
- r io.Reader
- err error
- unaligned bool
- nbits byte
- c uint32
- lru [3]uint16
- uncompressed int
- windowReader *bytes.Reader
- mainlens [maincodecount]byte
- lenlens [lencodecount]byte
- window [windowSize]byte
- b []byte
- bv int
- bo int
-}
-
-//go:noinline
-func (f *decompressor) fail(err error) {
- if f.err == nil {
- f.err = err
- }
- f.bo = 0
- f.bv = 0
-}
-
-func (f *decompressor) ensureAtLeast(n int) error {
- if f.bv-f.bo >= n {
- return nil
- }
-
- if f.err != nil {
- return f.err
- }
-
- if f.bv != f.bo {
- copy(f.b[:f.bv-f.bo], f.b[f.bo:f.bv])
- }
- n, err := io.ReadAtLeast(f.r, f.b[f.bv-f.bo:], n)
- if err != nil {
- if err == io.EOF { //nolint:errorlint
- err = io.ErrUnexpectedEOF
- } else {
- f.fail(err)
- }
- return err
- }
- f.bv = f.bv - f.bo + n
- f.bo = 0
- return nil
-}
-
-// feed retrieves another 16-bit word from the stream and consumes
-// it into f.c. It returns false if there are no more bytes available.
-// Otherwise, on error, it sets f.err.
-func (f *decompressor) feed() bool {
- err := f.ensureAtLeast(2)
- if err == io.ErrUnexpectedEOF { //nolint:errorlint // returns io.ErrUnexpectedEOF by contract
- return false
- }
- f.c |= (uint32(f.b[f.bo+1])<<8 | uint32(f.b[f.bo])) << (16 - f.nbits)
- f.nbits += 16
- f.bo += 2
- return true
-}
-
-// getBits retrieves the next n bits from the byte stream. n
-// must be <= 16. It sets f.err on error.
-func (f *decompressor) getBits(n byte) uint16 {
- if f.nbits < n {
- if !f.feed() {
- f.fail(io.ErrUnexpectedEOF)
- }
- }
- c := uint16(f.c >> (32 - n))
- f.c <<= n
- f.nbits -= n
- return c
-}
-
-type huffman struct {
- extra [][]uint16
- maxbits byte
- table [tablesize]uint16
-}
-
-// buildTable builds a huffman decoding table from a slice of code lengths,
-// one per code, in order. Each code length must be <= maxTreePathLen.
-// See https://en.wikipedia.org/wiki/Canonical_Huffman_code.
-func buildTable(codelens []byte) *huffman {
- // Determine the number of codes of each length, and the
- // maximum length.
- var count [maxTreePathLen + 1]uint
- var max byte
- for _, cl := range codelens {
- count[cl]++
- if max < cl {
- max = cl
- }
- }
-
- if max == 0 {
- return &huffman{}
- }
-
- // Determine the first code of each length.
- var first [maxTreePathLen + 1]uint
- code := uint(0)
- for i := byte(1); i <= max; i++ {
- code <<= 1
- first[i] = code
- code += count[i]
- }
-
- if code != 1< tablebits, split long codes into additional tables
- // of suffixes of max-tablebits length.
- h := &huffman{maxbits: max}
- if max > tablebits {
- core := first[tablebits+1] / 2 // Number of codes that fit without extra tables
- nextra := 1<> (cl - tablebits)
- suffix := code & (1<<(cl-tablebits) - 1)
- extendedCode := suffix << (max - cl)
- for j := uint(0); j < 1<<(max-cl); j++ {
- h.extra[h.table[prefix]][extendedCode+j] = v
- }
- }
- }
- }
-
- return h
-}
-
-// getCode retrieves the next code using the provided
-// huffman tree. It sets f.err on error.
-func (f *decompressor) getCode(h *huffman) uint16 {
- if h.maxbits > 0 {
- if f.nbits < maxTreePathLen {
- f.feed()
- }
-
- // For codes with length < tablebits, it doesn't matter
- // what the remainder of the bits used for table lookup
- // are, since entries with all possible suffixes were
- // added to the table.
- c := h.table[f.c>>(32-tablebits)]
- if !(c >= 1<>(32-(h.maxbits-tablebits))]
- }
-
- n := byte(c >> lenshift)
- if f.nbits >= n {
- // Only consume the length of the code, not the maximum
- // code length.
- f.c <<= n
- f.nbits -= n
- return c & codemask
- }
-
- f.fail(io.ErrUnexpectedEOF)
- return 0
- }
-
- // This is an empty tree. It should not be used.
- f.fail(errCorrupt)
- return 0
-}
-
-// readTree updates the huffman tree path lengths in lens by
-// reading and decoding lengths from the byte stream. lens
-// should be prepopulated with the previous block's tree's path
-// lengths. For the first block, lens should be zero.
-func (f *decompressor) readTree(lens []byte) error {
- // Get the pre-tree for the main tree.
- var pretreeLen [20]byte
- for i := range pretreeLen {
- pretreeLen[i] = byte(f.getBits(4))
- }
- if f.err != nil {
- return f.err
- }
- h := buildTable(pretreeLen[:])
-
- // The lengths are encoded as a series of huffman codes
- // encoded by the pre-tree.
- for i := 0; i < len(lens); {
- c := byte(f.getCode(h))
- if f.err != nil {
- return f.err
- }
- switch {
- case c <= 16: // length is delta from previous length
- lens[i] = (lens[i] + 17 - c) % 17
- i++
- case c == 17: // next n + 4 lengths are zero
- zeroes := int(f.getBits(4)) + 4
- if i+zeroes > len(lens) {
- return errCorrupt
- }
- for j := 0; j < zeroes; j++ {
- lens[i+j] = 0
- }
- i += zeroes
- case c == 18: // next n + 20 lengths are zero
- zeroes := int(f.getBits(5)) + 20
- if i+zeroes > len(lens) {
- return errCorrupt
- }
- for j := 0; j < zeroes; j++ {
- lens[i+j] = 0
- }
- i += zeroes
- case c == 19: // next n + 4 lengths all have the same value
- same := int(f.getBits(1)) + 4
- if i+same > len(lens) {
- return errCorrupt
- }
- c = byte(f.getCode(h))
- if c > 16 {
- return errCorrupt
- }
- l := (lens[i] + 17 - c) % 17
- for j := 0; j < same; j++ {
- lens[i+j] = l
- }
- i += same
- default:
- return errCorrupt
- }
- }
-
- if f.err != nil {
- return f.err
- }
- return nil
-}
-
-func (f *decompressor) readBlockHeader() (byte, uint16, error) {
- // If the previous block was an unaligned uncompressed block, restore
- // 2-byte alignment.
- if f.unaligned {
- err := f.ensureAtLeast(1)
- if err != nil {
- return 0, 0, err
- }
- f.bo++
- f.unaligned = false
- }
-
- blockType := f.getBits(3)
- full := f.getBits(1)
- var blockSize uint16
- if full != 0 {
- blockSize = maxBlockSize
- } else {
- blockSize = f.getBits(16)
- if blockSize > maxBlockSize {
- return 0, 0, errCorrupt
- }
- }
-
- if f.err != nil {
- return 0, 0, f.err
- }
-
- switch blockType {
- case verbatimBlock, alignedOffsetBlock:
- // The caller will read the huffman trees.
- case uncompressedBlock:
- if f.nbits > 16 {
- panic("impossible: more than one 16-bit word remains")
- }
-
- // Drop the remaining bits in the current 16-bit word
- // If there are no bits left, discard a full 16-bit word.
- n := f.nbits
- if n == 0 {
- n = 16
- }
-
- f.getBits(n)
-
- // Read the LRU values for the next block.
- err := f.ensureAtLeast(12)
- if err != nil {
- return 0, 0, err
- }
-
- f.lru[0] = uint16(binary.LittleEndian.Uint32(f.b[f.bo : f.bo+4]))
- f.lru[1] = uint16(binary.LittleEndian.Uint32(f.b[f.bo+4 : f.bo+8]))
- f.lru[2] = uint16(binary.LittleEndian.Uint32(f.b[f.bo+8 : f.bo+12]))
- f.bo += 12
-
- default:
- return 0, 0, errCorrupt
- }
-
- return byte(blockType), blockSize, nil
-}
-
-// readTrees reads the two or three huffman trees for the current block.
-// readAligned specifies whether to read the aligned offset tree.
-func (f *decompressor) readTrees(readAligned bool) (main *huffman, length *huffman, aligned *huffman, err error) {
- // Aligned offset blocks start with a small aligned offset tree.
- if readAligned {
- var alignedLen [8]byte
- for i := range alignedLen {
- alignedLen[i] = byte(f.getBits(3))
- }
- aligned = buildTable(alignedLen[:])
- if aligned == nil {
- return main, length, aligned, errors.New("corrupt")
- }
- }
-
- // The main tree is encoded in two parts.
- err = f.readTree(f.mainlens[:maincodesplit])
- if err != nil {
- return main, length, aligned, err
- }
- err = f.readTree(f.mainlens[maincodesplit:])
- if err != nil {
- return main, length, aligned, err
- }
-
- main = buildTable(f.mainlens[:])
- if main == nil {
- return main, length, aligned, errors.New("corrupt")
- }
-
- // The length tree is encoding in a single part.
- err = f.readTree(f.lenlens[:])
- if err != nil {
- return main, length, aligned, err
- }
-
- length = buildTable(f.lenlens[:])
- if length == nil {
- return main, length, aligned, errors.New("corrupt")
- }
-
- return main, length, aligned, f.err
-}
-
-// readCompressedBlock decodes a compressed block, writing into the window
-// starting at start and ending at end, and using the provided huffman trees.
-func (f *decompressor) readCompressedBlock(start, end uint16, hmain, hlength, haligned *huffman) (int, error) {
- i := start
- for i < end {
- main := f.getCode(hmain)
- if f.err != nil {
- break
- }
- if main < 256 {
- // Literal byte.
- f.window[i] = byte(main)
- i++
- continue
- }
-
- // This is a match backward in the window. Determine
- // the offset and dlength.
- matchlen := (main - 256) % 8
- slot := (main - 256) / 8
-
- // The length is either the low bits of the code,
- // or if this is 7, is encoded with the length tree.
- if matchlen == 7 {
- matchlen += f.getCode(hlength)
- }
- matchlen += 2
-
- var matchoffset uint16
- if slot < 3 { //nolint:nestif // todo: simplify nested complexity
- // The offset is one of the LRU values.
- matchoffset = f.lru[slot]
- f.lru[slot] = f.lru[0]
- f.lru[0] = matchoffset
- } else {
- // The offset is encoded as a combination of the
- // slot and more bits from the bit stream.
- offsetbits := footerBits[slot]
- var verbatimbits, alignedbits uint16
- if offsetbits > 0 {
- if haligned != nil && offsetbits >= 3 {
- // This is an aligned offset block. Combine
- // the bits written verbatim with the aligned
- // offset tree code.
- verbatimbits = f.getBits(offsetbits-3) * 8
- alignedbits = f.getCode(haligned)
- } else {
- // There are no aligned offset bits to read,
- // only verbatim bits.
- verbatimbits = f.getBits(offsetbits)
- alignedbits = 0
- }
- }
- matchoffset = basePosition[slot] + verbatimbits + alignedbits - 2
- // Update the LRU cache.
- f.lru[2] = f.lru[1]
- f.lru[1] = f.lru[0]
- f.lru[0] = matchoffset
- }
-
- if !(matchoffset <= i && matchlen <= end-i) {
- f.fail(errCorrupt)
- break
- }
- copyend := i + matchlen
- for ; i < copyend; i++ {
- f.window[i] = f.window[i-matchoffset]
- }
- }
- return int(i - start), f.err
-}
-
-// readBlock decodes the current block and returns the number of uncompressed bytes.
-func (f *decompressor) readBlock(start uint16) (int, error) {
- blockType, size, err := f.readBlockHeader()
- if err != nil {
- return 0, err
- }
-
- if blockType == uncompressedBlock {
- if size%2 == 1 {
- // Remember to realign the byte stream at the next block.
- f.unaligned = true
- }
- copied := 0
- if f.bo < f.bv {
- copied = int(size)
- s := int(start)
- if copied > f.bv-f.bo {
- copied = f.bv - f.bo
- }
- copy(f.window[s:s+copied], f.b[f.bo:f.bo+copied])
- f.bo += copied
- }
- n, err := io.ReadFull(f.r, f.window[start+uint16(copied):start+size])
- return copied + n, err
- }
-
- hmain, hlength, haligned, err := f.readTrees(blockType == alignedOffsetBlock)
- if err != nil {
- return 0, err
- }
-
- return f.readCompressedBlock(start, start+size, hmain, hlength, haligned)
-}
-
-// decodeE8 reverses the 0xe8 x86 instruction encoding that was performed
-// to the uncompressed data before it was compressed.
-func decodeE8(b []byte, off int64) {
- if off > maxe8offset || len(b) < 10 {
- return
- }
- for i := 0; i < len(b)-10; i++ {
- if b[i] == 0xe8 {
- currentPtr := int32(off) + int32(i)
- abs := int32(binary.LittleEndian.Uint32(b[i+1 : i+5]))
- if abs >= -currentPtr && abs < e8filesize {
- var rel int32
- if abs >= 0 {
- rel = abs - currentPtr
- } else {
- rel = abs + e8filesize
- }
- binary.LittleEndian.PutUint32(b[i+1:i+5], uint32(rel))
- }
- i += 4
- }
- }
-}
-
-func (f *decompressor) Read(b []byte) (int, error) {
- // Read and uncompress everything.
- if f.windowReader == nil {
- n := 0
- for n < f.uncompressed {
- k, err := f.readBlock(uint16(n))
- if err != nil {
- return 0, err
- }
- n += k
- }
- decodeE8(f.window[:f.uncompressed], 0)
- f.windowReader = bytes.NewReader(f.window[:f.uncompressed])
- }
-
- // Just read directly from the window.
- return f.windowReader.Read(b)
-}
-
-func (*decompressor) Close() error {
- return nil
-}
-
-// NewReader returns a new io.ReadCloser that decompresses a
-// WIM LZX stream until uncompressedSize bytes have been returned.
-func NewReader(r io.Reader, uncompressedSize int) (io.ReadCloser, error) {
- if uncompressedSize > windowSize {
- return nil, errors.New("uncompressed size is limited to 32KB")
- }
- f := &decompressor{
- lru: [3]uint16{1, 1, 1},
- uncompressed: uncompressedSize,
- b: make([]byte, 4096),
- r: r,
- }
- return f, nil
-}
diff --git a/vendor/github.com/Microsoft/go-winio/wim/wim.go b/vendor/github.com/Microsoft/go-winio/wim/wim.go
deleted file mode 100644
index 8f272f3da8..0000000000
--- a/vendor/github.com/Microsoft/go-winio/wim/wim.go
+++ /dev/null
@@ -1,898 +0,0 @@
-//go:build windows || linux
-// +build windows linux
-
-// Package wim implements a WIM file parser.
-//
-// WIM files are used to distribute Windows file system and container images.
-// They are documented at https://msdn.microsoft.com/en-us/library/windows/desktop/dd861280.aspx.
-package wim
-
-import (
- "bytes"
- "crypto/sha1" //nolint:gosec // not used for secure application
- "encoding/binary"
- "encoding/xml"
- "errors"
- "fmt"
- "io"
- "strconv"
- "sync"
- "time"
- "unicode/utf16"
-)
-
-// File attribute constants from Windows.
-//
-//nolint:revive // var-naming: ALL_CAPS
-const (
- FILE_ATTRIBUTE_READONLY = 0x00000001
- FILE_ATTRIBUTE_HIDDEN = 0x00000002
- FILE_ATTRIBUTE_SYSTEM = 0x00000004
- FILE_ATTRIBUTE_DIRECTORY = 0x00000010
- FILE_ATTRIBUTE_ARCHIVE = 0x00000020
- FILE_ATTRIBUTE_DEVICE = 0x00000040
- FILE_ATTRIBUTE_NORMAL = 0x00000080
- FILE_ATTRIBUTE_TEMPORARY = 0x00000100
- FILE_ATTRIBUTE_SPARSE_FILE = 0x00000200
- FILE_ATTRIBUTE_REPARSE_POINT = 0x00000400
- FILE_ATTRIBUTE_COMPRESSED = 0x00000800
- FILE_ATTRIBUTE_OFFLINE = 0x00001000
- FILE_ATTRIBUTE_NOT_CONTENT_INDEXED = 0x00002000
- FILE_ATTRIBUTE_ENCRYPTED = 0x00004000
- FILE_ATTRIBUTE_INTEGRITY_STREAM = 0x00008000
- FILE_ATTRIBUTE_VIRTUAL = 0x00010000
- FILE_ATTRIBUTE_NO_SCRUB_DATA = 0x00020000
- FILE_ATTRIBUTE_EA = 0x00040000
-)
-
-// Windows processor architectures.
-//
-//nolint:revive // var-naming: ALL_CAPS
-const (
- PROCESSOR_ARCHITECTURE_INTEL = 0
- PROCESSOR_ARCHITECTURE_MIPS = 1
- PROCESSOR_ARCHITECTURE_ALPHA = 2
- PROCESSOR_ARCHITECTURE_PPC = 3
- PROCESSOR_ARCHITECTURE_SHX = 4
- PROCESSOR_ARCHITECTURE_ARM = 5
- PROCESSOR_ARCHITECTURE_IA64 = 6
- PROCESSOR_ARCHITECTURE_ALPHA64 = 7
- PROCESSOR_ARCHITECTURE_MSIL = 8
- PROCESSOR_ARCHITECTURE_AMD64 = 9
- PROCESSOR_ARCHITECTURE_IA32_ON_WIN64 = 10
- PROCESSOR_ARCHITECTURE_NEUTRAL = 11
- PROCESSOR_ARCHITECTURE_ARM64 = 12
-)
-
-var wimImageTag = [...]byte{'M', 'S', 'W', 'I', 'M', 0, 0, 0}
-
-// todo: replace this with pkg/guid.GUID (and add tests to make sure nothing breaks)
-
-type guid struct {
- Data1 uint32
- Data2 uint16
- Data3 uint16
- Data4 [8]byte
-}
-
-func (g guid) String() string {
- return fmt.Sprintf("%08x-%04x-%04x-%02x%02x-%02x%02x%02x%02x%02x%02x",
- g.Data1,
- g.Data2,
- g.Data3,
- g.Data4[0],
- g.Data4[1],
- g.Data4[2],
- g.Data4[3],
- g.Data4[4],
- g.Data4[5],
- g.Data4[6],
- g.Data4[7])
-}
-
-type resourceDescriptor struct {
- FlagsAndCompressedSize uint64
- Offset int64
- OriginalSize int64
-}
-
-type resFlag byte
-
-//nolint:deadcode,varcheck // need unused variables for iota to work
-const (
- resFlagFree resFlag = 1 << iota
- resFlagMetadata
- resFlagCompressed
- resFlagSpanned
-)
-
-const validate = false
-
-const supportedResFlags = resFlagMetadata | resFlagCompressed
-
-func (r *resourceDescriptor) Flags() resFlag {
- return resFlag(r.FlagsAndCompressedSize >> 56)
-}
-
-func (r *resourceDescriptor) CompressedSize() int64 {
- return int64(r.FlagsAndCompressedSize & 0xffffffffffffff)
-}
-
-func (r *resourceDescriptor) String() string {
- s := fmt.Sprintf("%d bytes at %d", r.CompressedSize(), r.Offset)
- if r.Flags()&4 != 0 {
- s += fmt.Sprintf(" (uncompresses to %d)", r.OriginalSize)
- }
- return s
-}
-
-// SHA1Hash contains the SHA1 hash of a file or stream.
-type SHA1Hash [20]byte
-
-type streamDescriptor struct {
- resourceDescriptor
- PartNumber uint16
- RefCount uint32
- Hash SHA1Hash
-}
-
-type hdrFlag uint32
-
-//nolint:deadcode,varcheck // need unused variables for iota to work
-const (
- hdrFlagReserved hdrFlag = 1 << iota
- hdrFlagCompressed
- hdrFlagReadOnly
- hdrFlagSpanned
- hdrFlagResourceOnly
- hdrFlagMetadataOnly
- hdrFlagWriteInProgress
- hdrFlagRpFix
-)
-
-//nolint:deadcode,varcheck // need unused variables for iota to work
-const (
- hdrFlagCompressReserved hdrFlag = 1 << (iota + 16)
- hdrFlagCompressXpress
- hdrFlagCompressLzx
-)
-
-const supportedHdrFlags = hdrFlagRpFix | hdrFlagReadOnly | hdrFlagCompressed | hdrFlagCompressLzx
-
-type wimHeader struct {
- ImageTag [8]byte
- Size uint32
- Version uint32
- Flags hdrFlag
- CompressionSize uint32
- WIMGuid guid
- PartNumber uint16
- TotalParts uint16
- ImageCount uint32
- OffsetTable resourceDescriptor
- XMLData resourceDescriptor
- BootMetadata resourceDescriptor
- BootIndex uint32
- Padding uint32
- Integrity resourceDescriptor
- Unused [60]byte
-}
-
-type securityblockDisk struct {
- TotalLength uint32
- NumEntries uint32
-}
-
-const securityblockDiskSize = 8
-
-type direntry struct {
- Attributes uint32
- SecurityID uint32
- SubdirOffset int64
- Unused1, Unused2 int64
- CreationTime Filetime
- LastAccessTime Filetime
- LastWriteTime Filetime
- Hash SHA1Hash
- Padding uint32
- ReparseHardLink int64
- StreamCount uint16
- ShortNameLength uint16
- FileNameLength uint16
-}
-
-var direntrySize = int64(binary.Size(direntry{}) + 8) // includes an 8-byte length prefix
-
-type streamentry struct {
- Unused int64
- Hash SHA1Hash
- NameLength int16
-}
-
-var streamentrySize = int64(binary.Size(streamentry{}) + 8) // includes an 8-byte length prefix
-
-// Filetime represents a Windows time.
-type Filetime struct {
- LowDateTime uint32
- HighDateTime uint32
-}
-
-// Time returns the time as time.Time.
-func (ft *Filetime) Time() time.Time {
- // 100-nanosecond intervals since January 1, 1601
- nsec := int64(ft.HighDateTime)<<32 + int64(ft.LowDateTime)
- // change starting time to the Epoch (00:00:00 UTC, January 1, 1970)
- nsec -= 116444736000000000
- // convert into nanoseconds
- nsec *= 100
- return time.Unix(0, nsec)
-}
-
-// UnmarshalXML unmarshalls the time from a WIM XML blob.
-func (ft *Filetime) UnmarshalXML(d *xml.Decoder, start xml.StartElement) error {
- type Time struct {
- Low string `xml:"LOWPART"`
- High string `xml:"HIGHPART"`
- }
- var t Time
- err := d.DecodeElement(&t, &start)
- if err != nil {
- return err
- }
-
- low, err := strconv.ParseUint(t.Low, 0, 32)
- if err != nil {
- return err
- }
- high, err := strconv.ParseUint(t.High, 0, 32)
- if err != nil {
- return err
- }
-
- ft.LowDateTime = uint32(low)
- ft.HighDateTime = uint32(high)
- return nil
-}
-
-type info struct {
- Image []ImageInfo `xml:"IMAGE"`
-}
-
-// ImageInfo contains information about the image.
-type ImageInfo struct {
- Name string `xml:"NAME"`
- Index int `xml:"INDEX,attr"`
- CreationTime Filetime `xml:"CREATIONTIME"`
- ModTime Filetime `xml:"LASTMODIFICATIONTIME"`
- Windows *WindowsInfo `xml:"WINDOWS"`
-}
-
-// WindowsInfo contains information about the Windows installation in the image.
-type WindowsInfo struct {
- Arch byte `xml:"ARCH"`
- ProductName string `xml:"PRODUCTNAME"`
- EditionID string `xml:"EDITIONID"`
- InstallationType string `xml:"INSTALLATIONTYPE"`
- ProductType string `xml:"PRODUCTTYPE"`
- Languages []string `xml:"LANGUAGES>LANGUAGE"`
- DefaultLanguage string `xml:"LANGUAGES>DEFAULT"`
- Version Version `xml:"VERSION"`
- SystemRoot string `xml:"SYSTEMROOT"`
-}
-
-// Version represents a Windows build version.
-type Version struct {
- Major int `xml:"MAJOR"`
- Minor int `xml:"MINOR"`
- Build int `xml:"BUILD"`
- SPBuild int `xml:"SPBUILD"`
- SPLevel int `xml:"SPLEVEL"`
-}
-
-// ParseError is returned when the WIM cannot be parsed.
-type ParseError struct {
- Oper string
- Path string
- Err error
-}
-
-func (e *ParseError) Error() string {
- if e.Path == "" {
- return "WIM parse error at " + e.Oper + ": " + e.Err.Error()
- }
- return fmt.Sprintf("WIM parse error: %s %s: %s", e.Oper, e.Path, e.Err.Error())
-}
-
-func (e *ParseError) Unwrap() error { return e.Err }
-
-// Reader provides functions to read a WIM file.
-type Reader struct {
- hdr wimHeader
- r io.ReaderAt
- fileData map[SHA1Hash]resourceDescriptor
-
- XMLInfo string // The XML information about the WIM.
- Image []*Image // The WIM's images.
-}
-
-// Image represents an image within a WIM file.
-type Image struct {
- wim *Reader
- offset resourceDescriptor
- sds [][]byte
- rootOffset int64
- r io.ReadCloser
- curOffset int64
- m sync.Mutex
-
- ImageInfo
-}
-
-// StreamHeader contains alternate data stream metadata.
-type StreamHeader struct {
- Name string
- Hash SHA1Hash
- Size int64
-}
-
-// Stream represents an alternate data stream or reparse point data stream.
-type Stream struct {
- StreamHeader
- wim *Reader
- offset resourceDescriptor
-}
-
-// FileHeader contains file metadata.
-type FileHeader struct {
- Name string
- ShortName string
- Attributes uint32
- SecurityDescriptor []byte
- CreationTime Filetime
- LastAccessTime Filetime
- LastWriteTime Filetime
- Hash SHA1Hash
- Size int64
- LinkID int64
- ReparseTag uint32
- ReparseReserved uint32
-}
-
-// File represents a file or directory in a WIM image.
-type File struct {
- FileHeader
- Streams []*Stream
- offset resourceDescriptor
- img *Image
- subdirOffset int64
-}
-
-// NewReader returns a Reader that can be used to read WIM file data.
-func NewReader(f io.ReaderAt) (*Reader, error) {
- r := &Reader{r: f}
- section := io.NewSectionReader(f, 0, 0xffff)
- err := binary.Read(section, binary.LittleEndian, &r.hdr)
- if err != nil {
- return nil, err
- }
-
- if r.hdr.ImageTag != wimImageTag {
- return nil, &ParseError{Oper: "image tag", Err: errors.New("not a WIM file")}
- }
-
- if r.hdr.Flags&^supportedHdrFlags != 0 {
- return nil, fmt.Errorf("unsupported WIM flags %x", r.hdr.Flags&^supportedHdrFlags)
- }
-
- if r.hdr.CompressionSize != 0x8000 {
- return nil, fmt.Errorf("unsupported compression size %d", r.hdr.CompressionSize)
- }
-
- if r.hdr.TotalParts != 1 {
- return nil, errors.New("multi-part WIM not supported")
- }
-
- fileData, images, err := r.readOffsetTable(&r.hdr.OffsetTable)
- if err != nil {
- return nil, err
- }
-
- xmlinfo, err := r.readXML()
- if err != nil {
- return nil, err
- }
-
- var inf info
- err = xml.Unmarshal([]byte(xmlinfo), &inf)
- if err != nil {
- return nil, &ParseError{Oper: "XML info", Err: err}
- }
-
- for i, img := range images {
- for _, imgInfo := range inf.Image {
- if imgInfo.Index == i+1 {
- img.ImageInfo = imgInfo
- break
- }
- }
- }
-
- r.fileData = fileData
- r.Image = images
- r.XMLInfo = xmlinfo
- return r, nil
-}
-
-// Close releases resources associated with the Reader.
-func (r *Reader) Close() error {
- for _, img := range r.Image {
- img.reset()
- }
- return nil
-}
-
-func (r *Reader) resourceReader(hdr *resourceDescriptor) (io.ReadCloser, error) {
- return r.resourceReaderWithOffset(hdr, 0)
-}
-
-func (r *Reader) resourceReaderWithOffset(hdr *resourceDescriptor, offset int64) (io.ReadCloser, error) {
- var sr io.ReadCloser
- section := io.NewSectionReader(r.r, hdr.Offset, hdr.CompressedSize())
- if hdr.Flags()&resFlagCompressed == 0 {
- _, _ = section.Seek(offset, 0)
- sr = io.NopCloser(section)
- } else {
- cr, err := newCompressedReader(section, hdr.OriginalSize, offset)
- if err != nil {
- return nil, err
- }
- sr = cr
- }
-
- return sr, nil
-}
-
-func (r *Reader) readResource(hdr *resourceDescriptor) ([]byte, error) {
- rsrc, err := r.resourceReader(hdr)
- if err != nil {
- return nil, err
- }
- defer rsrc.Close()
- return io.ReadAll(rsrc)
-}
-
-func (r *Reader) readXML() (string, error) {
- if r.hdr.XMLData.CompressedSize() == 0 {
- return "", nil
- }
- rsrc, err := r.resourceReader(&r.hdr.XMLData)
- if err != nil {
- return "", err
- }
- defer rsrc.Close()
-
- xmlData := make([]uint16, r.hdr.XMLData.OriginalSize/2)
- err = binary.Read(rsrc, binary.LittleEndian, xmlData)
- if err != nil {
- return "", &ParseError{Oper: "XML data", Err: err}
- }
-
- // The BOM will always indicate little-endian UTF-16.
- if xmlData[0] != 0xfeff {
- return "", &ParseError{Oper: "XML data", Err: errors.New("invalid BOM")}
- }
- return string(utf16.Decode(xmlData[1:])), nil
-}
-
-func (r *Reader) readOffsetTable(res *resourceDescriptor) (map[SHA1Hash]resourceDescriptor, []*Image, error) {
- fileData := make(map[SHA1Hash]resourceDescriptor)
- var images []*Image
-
- offsetTable, err := r.readResource(res)
- if err != nil {
- return nil, nil, &ParseError{Oper: "offset table", Err: err}
- }
-
- br := bytes.NewReader(offsetTable)
- for i := 0; ; i++ {
- var res streamDescriptor
- err := binary.Read(br, binary.LittleEndian, &res)
- if err == io.EOF { //nolint:errorlint
- break
- }
- if err != nil {
- return nil, nil, &ParseError{Oper: "offset table", Err: err}
- }
- if res.Flags()&^supportedResFlags != 0 {
- return nil, nil, &ParseError{Oper: "offset table", Err: errors.New("unsupported resource flag")}
- }
-
- // Validation for ad-hoc testing
- if validate {
- sec, err := r.resourceReader(&res.resourceDescriptor)
- if err != nil {
- panic(fmt.Sprint(i, err))
- }
- hash := sha1.New() //nolint:gosec // not used for secure application
- _, err = io.Copy(hash, sec)
- sec.Close()
- if err != nil {
- panic(fmt.Sprint(i, err))
- }
- var cmphash SHA1Hash
- copy(cmphash[:], hash.Sum(nil))
- if cmphash != res.Hash {
- panic(fmt.Sprint(i, "hash mismatch"))
- }
- }
-
- if res.Flags()&resFlagMetadata != 0 {
- image := &Image{
- wim: r,
- offset: res.resourceDescriptor,
- }
- images = append(images, image)
- } else {
- fileData[res.Hash] = res.resourceDescriptor
- }
- }
-
- if len(images) != int(r.hdr.ImageCount) {
- return nil, nil, &ParseError{Oper: "offset table", Err: errors.New("mismatched image count")}
- }
-
- return fileData, images, nil
-}
-
-func (*Reader) readSecurityDescriptors(rsrc io.Reader) (sds [][]byte, n int64, err error) {
- var secBlock securityblockDisk
- err = binary.Read(rsrc, binary.LittleEndian, &secBlock)
- if err != nil {
- return sds, 0, &ParseError{Oper: "security table", Err: err}
- }
-
- n += securityblockDiskSize
-
- secSizes := make([]int64, secBlock.NumEntries)
- err = binary.Read(rsrc, binary.LittleEndian, &secSizes)
- if err != nil {
- return sds, n, &ParseError{Oper: "security table sizes", Err: err}
- }
-
- n += int64(secBlock.NumEntries * 8)
-
- sds = make([][]byte, secBlock.NumEntries)
- for i, size := range secSizes {
- sd := make([]byte, size&0xffffffff)
- _, err = io.ReadFull(rsrc, sd)
- if err != nil {
- return sds, n, &ParseError{Oper: "security descriptor", Err: err}
- }
- n += int64(len(sd))
- sds[i] = sd
- }
-
- secsize := int64((secBlock.TotalLength + 7) &^ 7)
- if n > secsize {
- return sds, n, &ParseError{Oper: "security descriptor", Err: errors.New("security descriptor table too small")}
- }
-
- _, err = io.CopyN(io.Discard, rsrc, secsize-n)
- if err != nil {
- return sds, n, err
- }
-
- n = secsize
- return sds, n, nil
-}
-
-// Open parses the image and returns the root directory.
-func (img *Image) Open() (*File, error) {
- if img.sds == nil {
- rsrc, err := img.wim.resourceReaderWithOffset(&img.offset, img.rootOffset)
- if err != nil {
- return nil, err
- }
- sds, n, err := img.wim.readSecurityDescriptors(rsrc)
- if err != nil {
- rsrc.Close()
- return nil, err
- }
- img.sds = sds
- img.r = rsrc
- img.rootOffset = n
- img.curOffset = n
- }
-
- f, err := img.readdir(img.rootOffset)
- if err != nil {
- return nil, err
- }
- if len(f) != 1 {
- return nil, &ParseError{Oper: "root directory", Err: errors.New("expected exactly 1 root directory entry")}
- }
- return f[0], err
-}
-
-func (img *Image) reset() {
- if img.r != nil {
- img.r.Close()
- img.r = nil
- }
- img.curOffset = -1
-}
-
-func (img *Image) readdir(offset int64) ([]*File, error) {
- img.m.Lock()
- defer img.m.Unlock()
-
- if offset < img.curOffset || offset > img.curOffset+chunkSize {
- // Reset to seek backward or to seek forward very far.
- img.reset()
- }
- if img.r == nil {
- rsrc, err := img.wim.resourceReaderWithOffset(&img.offset, offset)
- if err != nil {
- return nil, err
- }
- img.r = rsrc
- img.curOffset = offset
- }
- if offset > img.curOffset {
- _, err := io.CopyN(io.Discard, img.r, offset-img.curOffset)
- if err != nil {
- img.reset()
- if err == io.EOF { //nolint:errorlint
- err = io.ErrUnexpectedEOF
- }
- return nil, err
- }
- }
-
- var entries []*File
- for {
- e, n, err := img.readNextEntry(img.r)
- img.curOffset += n
- if err == io.EOF { //nolint:errorlint
- break
- }
- if err != nil {
- img.reset()
- return nil, err
- }
- entries = append(entries, e)
- }
- return entries, nil
-}
-
-func (img *Image) readNextEntry(r io.Reader) (*File, int64, error) {
- var length int64
- err := binary.Read(r, binary.LittleEndian, &length)
- if err != nil {
- return nil, 0, &ParseError{Oper: "directory length check", Err: err}
- }
-
- if length == 0 {
- return nil, 8, io.EOF
- }
-
- left := length
- if left < direntrySize {
- return nil, 0, &ParseError{Oper: "directory entry", Err: errors.New("size too short")}
- }
-
- var dentry direntry
- err = binary.Read(r, binary.LittleEndian, &dentry)
- if err != nil {
- return nil, 0, &ParseError{Oper: "directory entry", Err: err}
- }
-
- left -= direntrySize
-
- namesLen := int64(dentry.FileNameLength + 2 + dentry.ShortNameLength)
- if left < namesLen {
- return nil, 0, &ParseError{Oper: "directory entry", Err: errors.New("size too short for names")}
- }
-
- names := make([]uint16, namesLen/2)
- err = binary.Read(r, binary.LittleEndian, names)
- if err != nil {
- return nil, 0, &ParseError{Oper: "file name", Err: err}
- }
-
- left -= namesLen
-
- var name, shortName string
- if dentry.FileNameLength > 0 {
- name = string(utf16.Decode(names[:dentry.FileNameLength/2]))
- }
-
- if dentry.ShortNameLength > 0 {
- shortName = string(utf16.Decode(names[dentry.FileNameLength/2+1:]))
- }
-
- var offset resourceDescriptor
- zerohash := SHA1Hash{}
- if dentry.Hash != zerohash {
- var ok bool
- offset, ok = img.wim.fileData[dentry.Hash]
- if !ok {
- return nil, 0, &ParseError{
- Oper: "directory entry",
- Path: name,
- Err: fmt.Errorf("could not find file data matching hash %#v", dentry),
- }
- }
- }
-
- f := &File{
- FileHeader: FileHeader{
- Attributes: dentry.Attributes,
- CreationTime: dentry.CreationTime,
- LastAccessTime: dentry.LastAccessTime,
- LastWriteTime: dentry.LastWriteTime,
- Hash: dentry.Hash,
- Size: offset.OriginalSize,
- Name: name,
- ShortName: shortName,
- },
-
- offset: offset,
- img: img,
- subdirOffset: dentry.SubdirOffset,
- }
-
- isDir := false
-
- if dentry.Attributes&FILE_ATTRIBUTE_REPARSE_POINT == 0 {
- f.LinkID = dentry.ReparseHardLink
- if dentry.Attributes&FILE_ATTRIBUTE_DIRECTORY != 0 {
- isDir = true
- }
- } else {
- f.ReparseTag = uint32(dentry.ReparseHardLink)
- f.ReparseReserved = uint32(dentry.ReparseHardLink >> 32)
- }
-
- if isDir && f.subdirOffset == 0 {
- return nil, 0, &ParseError{Oper: "directory entry", Path: name, Err: errors.New("no subdirectory data for directory")}
- } else if !isDir && f.subdirOffset != 0 {
- return nil, 0, &ParseError{Oper: "directory entry", Path: name, Err: errors.New("unexpected subdirectory data for non-directory")}
- }
-
- if dentry.SecurityID != 0xffffffff {
- f.SecurityDescriptor = img.sds[dentry.SecurityID]
- }
-
- _, err = io.CopyN(io.Discard, r, left)
- if err != nil {
- if err == io.EOF { //nolint:errorlint
- err = io.ErrUnexpectedEOF
- }
- return nil, 0, err
- }
-
- if dentry.StreamCount > 0 {
- var streams []*Stream
- for i := uint16(0); i < dentry.StreamCount; i++ {
- s, n, err := img.readNextStream(r)
- length += n
- if err != nil {
- return nil, 0, err
- }
- // The first unnamed stream should be treated as the file stream.
- if i == 0 && s.Name == "" {
- f.Hash = s.Hash
- f.Size = s.Size
- f.offset = s.offset
- } else if s.Name != "" {
- streams = append(streams, s)
- }
- }
- f.Streams = streams
- }
-
- if dentry.Attributes&FILE_ATTRIBUTE_REPARSE_POINT != 0 && f.Size == 0 {
- return nil, 0, &ParseError{
- Oper: "directory entry",
- Path: name,
- Err: errors.New("reparse point is missing reparse stream"),
- }
- }
-
- return f, length, nil
-}
-
-func (img *Image) readNextStream(r io.Reader) (*Stream, int64, error) {
- var length int64
- err := binary.Read(r, binary.LittleEndian, &length)
- if err != nil {
- if err == io.EOF { //nolint:errorlint
- err = io.ErrUnexpectedEOF
- }
- return nil, 0, &ParseError{Oper: "stream length check", Err: err}
- }
-
- left := length
- if left < streamentrySize {
- return nil, 0, &ParseError{Oper: "stream entry", Err: errors.New("size too short")}
- }
-
- var sentry streamentry
- err = binary.Read(r, binary.LittleEndian, &sentry)
- if err != nil {
- return nil, 0, &ParseError{Oper: "stream entry", Err: err}
- }
-
- left -= streamentrySize
-
- if left < int64(sentry.NameLength) {
- return nil, 0, &ParseError{Oper: "stream entry", Err: errors.New("size too short for name")}
- }
-
- names := make([]uint16, sentry.NameLength/2)
- err = binary.Read(r, binary.LittleEndian, names)
- if err != nil {
- return nil, 0, &ParseError{Oper: "file name", Err: err}
- }
-
- left -= int64(sentry.NameLength)
- name := string(utf16.Decode(names))
-
- var offset resourceDescriptor
- if sentry.Hash != (SHA1Hash{}) {
- var ok bool
- offset, ok = img.wim.fileData[sentry.Hash]
- if !ok {
- return nil, 0, &ParseError{
- Oper: "stream entry",
- Path: name,
- Err: fmt.Errorf("could not find file data matching hash %v", sentry.Hash),
- }
- }
- }
-
- s := &Stream{
- StreamHeader: StreamHeader{
- Hash: sentry.Hash,
- Size: offset.OriginalSize,
- Name: name,
- },
- wim: img.wim,
- offset: offset,
- }
-
- _, err = io.CopyN(io.Discard, r, left)
- if err != nil {
- if err == io.EOF { //nolint:errorlint
- err = io.ErrUnexpectedEOF
- }
- return nil, 0, err
- }
-
- return s, length, nil
-}
-
-// Open returns an io.ReadCloser that can be used to read the stream's contents.
-func (s *Stream) Open() (io.ReadCloser, error) {
- return s.wim.resourceReader(&s.offset)
-}
-
-// Open returns an io.ReadCloser that can be used to read the file's contents.
-func (f *File) Open() (io.ReadCloser, error) {
- return f.img.wim.resourceReader(&f.offset)
-}
-
-// Readdir reads the directory entries.
-func (f *File) Readdir() ([]*File, error) {
- if !f.IsDir() {
- return nil, errors.New("not a directory")
- }
- return f.img.readdir(f.subdirOffset)
-}
-
-// IsDir returns whether the given file is a directory. It returns false when it
-// is a directory reparse point.
-func (f *FileHeader) IsDir() bool {
- return f.Attributes&(FILE_ATTRIBUTE_DIRECTORY|FILE_ATTRIBUTE_REPARSE_POINT) == FILE_ATTRIBUTE_DIRECTORY
-}
diff --git a/vendor/modules.txt b/vendor/modules.txt
index ce372e8d91..3878596729 100644
--- a/vendor/modules.txt
+++ b/vendor/modules.txt
@@ -98,8 +98,6 @@ github.com/Microsoft/go-winio/internal/stringbuffer
github.com/Microsoft/go-winio/pkg/bindfilter
github.com/Microsoft/go-winio/pkg/guid
github.com/Microsoft/go-winio/vhd
-github.com/Microsoft/go-winio/wim
-github.com/Microsoft/go-winio/wim/lzx
# github.com/Microsoft/hcsshim v0.11.4
## explicit; go 1.18
github.com/Microsoft/hcsshim
@@ -2547,7 +2545,7 @@ sigs.k8s.io/structured-merge-diff/v4/value
# sigs.k8s.io/yaml v1.3.0
## explicit; go 1.12
sigs.k8s.io/yaml
-# yunion.io/x/cloudmux v0.3.10-0-alpha.1.0.20260722023537-f41e01f2eee3
+# yunion.io/x/cloudmux v0.3.10-0-alpha.1.0.20260812062623-2b79dfea19bd
## explicit; go 1.24
yunion.io/x/cloudmux/pkg/apis
yunion.io/x/cloudmux/pkg/apis/billing
diff --git a/vendor/yunion.io/x/cloudmux/pkg/multicloud/aliyun/monitor.go b/vendor/yunion.io/x/cloudmux/pkg/multicloud/aliyun/monitor.go
index 0e69156c15..b35fdd7c63 100644
--- a/vendor/yunion.io/x/cloudmux/pkg/multicloud/aliyun/monitor.go
+++ b/vendor/yunion.io/x/cloudmux/pkg/multicloud/aliyun/monitor.go
@@ -376,12 +376,12 @@ func (self *SAliyunClient) GetEcsMetrics(opts *cloudprovider.MetricListOptions)
}
case cloudprovider.VM_METRIC_TYPE_PROCESS_NUMBER:
metricTags = map[string]string{
- "process.count_processname": "",
+ "process.number": "",
}
tagKey = cloudprovider.METRIC_TAG_PROCESS_NAME
case cloudprovider.VM_METRIC_TYPE_NET_TCP_CONNECTION:
metricTags = map[string]string{
- "network.tcp.connection_state": "",
+ "net_tcpconnection": "",
}
tagKey = cloudprovider.METRIC_TAG_STATE
default:
diff --git a/vendor/yunion.io/x/cloudmux/pkg/multicloud/aliyun/notice.go b/vendor/yunion.io/x/cloudmux/pkg/multicloud/aliyun/notice.go
index 6656e288b6..4b39afa47e 100644
--- a/vendor/yunion.io/x/cloudmux/pkg/multicloud/aliyun/notice.go
+++ b/vendor/yunion.io/x/cloudmux/pkg/multicloud/aliyun/notice.go
@@ -34,7 +34,17 @@ import (
const aliyunNoticeRssURL = "https://www.aliyun.com/rss/notice/zh.xml"
-var htmlTagRe = regexp.MustCompile(`<[^>]*>`)
+var (
+ // 仅匹配真实 HTML 标签,避免把版本比较里的 <= / >= 当成标签删掉
+ htmlTagRe = regexp.MustCompile(`(?i)?[a-z][a-z0-9]*\b[^>]*>`)
+ htmlBreakTagRe = regexp.MustCompile(`(?i)
`)
+ htmlLinkRe = regexp.MustCompile(`(?i)]*href\s*=\s*["']([^"']+)["'][^>]*>([\s\S]*?)`)
+ htmlLiTagRe = regexp.MustCompile(`(?i)]*>`)
+ htmlTdThTagRe = regexp.MustCompile(`(?i)`)
+ htmlBlockTagRe = regexp.MustCompile(`(?i)?(p|div|tr|ul|ol|table|thead|tbody|tfoot|h[1-6]|section|article|blockquote|hr)\b[^>]*>`)
+ htmlMultiLineRe = regexp.MustCompile(`\n{3,}`)
+ htmlSpaceRe = regexp.MustCompile(`[^\S\n]{2,}`)
+)
type SNotice struct {
title string
@@ -137,16 +147,45 @@ func stripHTML(s string) string {
if len(s) == 0 {
return ""
}
+ // 阿里云 RSS 常把 误用作标签内属性分隔符,需先还原成空格再解析标签
+ s = strings.ReplaceAll(s, " ", " ")
+ s = strings.ReplaceAll(s, " ", " ")
s = html.UnescapeString(s)
- s = htmlTagRe.ReplaceAllString(s, "")
s = strings.ReplaceAll(s, "\u00a0", " ")
+ s = strings.ReplaceAll(s, "\r\n", "\n")
+ s = strings.ReplaceAll(s, "\r", "\n")
+
+ // 保留超链接:文案 (url);文案与 url 相同时只保留 url
+ s = htmlLinkRe.ReplaceAllStringFunc(s, func(m string) string {
+ parts := htmlLinkRe.FindStringSubmatch(m)
+ if len(parts) < 3 {
+ return m
+ }
+ url := strings.TrimSpace(parts[1])
+ text := strings.TrimSpace(htmlTagRe.ReplaceAllString(parts[2], ""))
+ text = strings.Join(strings.Fields(text), " ")
+ if len(text) == 0 || text == url {
+ return url
+ }
+ return fmt.Sprintf("%s (%s)", text, url)
+ })
+
+ s = htmlBreakTagRe.ReplaceAllString(s, "\n")
+ s = htmlLiTagRe.ReplaceAllString(s, "\n- ")
+ s = htmlTdThTagRe.ReplaceAllString(s, " | ")
+ s = htmlBlockTagRe.ReplaceAllString(s, "\n")
+ s = htmlTagRe.ReplaceAllString(s, "")
+
lines := strings.Split(s, "\n")
- parts := []string{}
+ parts := make([]string, 0, len(lines))
for _, line := range lines {
line = strings.TrimSpace(line)
- if len(line) > 0 {
- parts = append(parts, line)
- }
+ line = strings.Trim(line, "|")
+ line = strings.TrimSpace(line)
+ line = htmlSpaceRe.ReplaceAllString(line, " ")
+ parts = append(parts, line)
}
- return strings.Join(parts, "\n")
+ s = strings.Join(parts, "\n")
+ s = htmlMultiLineRe.ReplaceAllString(s, "\n\n")
+ return strings.TrimSpace(s)
}