first-commit

This commit is contained in:
2025-08-25 15:46:12 +08:00
commit f4d95dfff4
5665 changed files with 705359 additions and 0 deletions

View File

@@ -0,0 +1,276 @@
// Copyright 2021 The Gitea Authors. All rights reserved.
// SPDX-License-Identifier: MIT
package nuget
import (
"archive/zip"
"bytes"
"encoding/xml"
"fmt"
"io"
"path/filepath"
"regexp"
"strings"
"code.gitea.io/gitea/modules/util"
"code.gitea.io/gitea/modules/validation"
"github.com/hashicorp/go-version"
)
var (
// ErrMissingNuspecFile indicates a missing Nuspec file
ErrMissingNuspecFile = util.NewInvalidArgumentErrorf("Nuspec file is missing")
// ErrNuspecFileTooLarge indicates a Nuspec file which is too large
ErrNuspecFileTooLarge = util.NewInvalidArgumentErrorf("Nuspec file is too large")
// ErrNuspecInvalidID indicates an invalid id in the Nuspec file
ErrNuspecInvalidID = util.NewInvalidArgumentErrorf("Nuspec file contains an invalid id")
// ErrNuspecInvalidVersion indicates an invalid version in the Nuspec file
ErrNuspecInvalidVersion = util.NewInvalidArgumentErrorf("Nuspec file contains an invalid version")
)
// PackageType specifies the package type the metadata describes
type PackageType int
const (
// DependencyPackage represents a package (*.nupkg)
DependencyPackage PackageType = iota + 1
// SymbolsPackage represents a symbol package (*.snupkg)
SymbolsPackage
PropertySymbolID = "nuget.symbol.id"
)
var idmatch = regexp.MustCompile(`\A\w+(?:[.-]\w+)*\z`)
const maxNuspecFileSize = 3 * 1024 * 1024
// Package represents a Nuget package
type Package struct {
PackageType PackageType
ID string
Version string
Metadata *Metadata
NuspecContent *bytes.Buffer
}
// Metadata represents the metadata of a Nuget package
type Metadata struct {
Authors string `json:"authors,omitempty"`
Copyright string `json:"copyright,omitempty"`
Description string `json:"description,omitempty"`
DevelopmentDependency bool `json:"development_dependency,omitempty"`
IconURL string `json:"icon_url,omitempty"`
Language string `json:"language,omitempty"`
LicenseURL string `json:"license_url,omitempty"`
MinClientVersion string `json:"min_client_version,omitempty"`
Owners string `json:"owners,omitempty"`
ProjectURL string `json:"project_url,omitempty"`
Readme string `json:"readme,omitempty"`
ReleaseNotes string `json:"release_notes,omitempty"`
RepositoryURL string `json:"repository_url,omitempty"`
RequireLicenseAcceptance bool `json:"require_license_acceptance"`
Summary string `json:"summary,omitempty"`
Tags string `json:"tags,omitempty"`
Title string `json:"title,omitempty"`
Dependencies map[string][]Dependency `json:"dependencies,omitempty"`
}
// Dependency represents a dependency of a Nuget package
type Dependency struct {
ID string `json:"id"`
Version string `json:"version"`
}
// https://learn.microsoft.com/en-us/nuget/reference/nuspec
// https://github.com/NuGet/NuGet.Client/blob/dev/src/NuGet.Core/NuGet.Packaging/compiler/resources/nuspec.xsd
type nuspecPackage struct {
Metadata struct {
// required fields
Authors string `xml:"authors"`
Description string `xml:"description"`
ID string `xml:"id"`
Version string `xml:"version"`
// optional fields
Copyright string `xml:"copyright"`
DevelopmentDependency bool `xml:"developmentDependency"`
IconURL string `xml:"iconUrl"`
Language string `xml:"language"`
LicenseURL string `xml:"licenseUrl"`
MinClientVersion string `xml:"minClientVersion,attr"`
Owners string `xml:"owners"`
ProjectURL string `xml:"projectUrl"`
Readme string `xml:"readme"`
ReleaseNotes string `xml:"releaseNotes"`
RequireLicenseAcceptance bool `xml:"requireLicenseAcceptance"`
Summary string `xml:"summary"`
Tags string `xml:"tags"`
Title string `xml:"title"`
Dependencies struct {
Dependency []struct {
ID string `xml:"id,attr"`
Version string `xml:"version,attr"`
Exclude string `xml:"exclude,attr"`
} `xml:"dependency"`
Group []struct {
TargetFramework string `xml:"targetFramework,attr"`
Dependency []struct {
ID string `xml:"id,attr"`
Version string `xml:"version,attr"`
Exclude string `xml:"exclude,attr"`
} `xml:"dependency"`
} `xml:"group"`
} `xml:"dependencies"`
PackageTypes struct {
PackageType []struct {
Name string `xml:"name,attr"`
} `xml:"packageType"`
} `xml:"packageTypes"`
Repository struct {
URL string `xml:"url,attr"`
} `xml:"repository"`
} `xml:"metadata"`
}
// ParsePackageMetaData parses the metadata of a Nuget package file
func ParsePackageMetaData(r io.ReaderAt, size int64) (*Package, error) {
archive, err := zip.NewReader(r, size)
if err != nil {
return nil, err
}
for _, file := range archive.File {
if filepath.Dir(file.Name) != "." {
continue
}
if strings.HasSuffix(strings.ToLower(file.Name), ".nuspec") {
if file.UncompressedSize64 > maxNuspecFileSize {
return nil, ErrNuspecFileTooLarge
}
f, err := archive.Open(file.Name)
if err != nil {
return nil, err
}
defer f.Close()
return ParseNuspecMetaData(archive, f)
}
}
return nil, ErrMissingNuspecFile
}
// ParseNuspecMetaData parses a Nuspec file to retrieve the metadata of a Nuget package
func ParseNuspecMetaData(archive *zip.Reader, r io.Reader) (*Package, error) {
var nuspecBuf bytes.Buffer
var p nuspecPackage
if err := xml.NewDecoder(io.TeeReader(r, &nuspecBuf)).Decode(&p); err != nil {
return nil, err
}
if !idmatch.MatchString(p.Metadata.ID) {
return nil, ErrNuspecInvalidID
}
v, err := version.NewSemver(p.Metadata.Version)
if err != nil {
return nil, ErrNuspecInvalidVersion
}
if !validation.IsValidURL(p.Metadata.ProjectURL) {
p.Metadata.ProjectURL = ""
}
packageType := DependencyPackage
for _, pt := range p.Metadata.PackageTypes.PackageType {
if pt.Name == "SymbolsPackage" {
packageType = SymbolsPackage
break
}
}
m := &Metadata{
Authors: p.Metadata.Authors,
Copyright: p.Metadata.Copyright,
Description: p.Metadata.Description,
DevelopmentDependency: p.Metadata.DevelopmentDependency,
IconURL: p.Metadata.IconURL,
Language: p.Metadata.Language,
LicenseURL: p.Metadata.LicenseURL,
MinClientVersion: p.Metadata.MinClientVersion,
Owners: p.Metadata.Owners,
ProjectURL: p.Metadata.ProjectURL,
ReleaseNotes: p.Metadata.ReleaseNotes,
RepositoryURL: p.Metadata.Repository.URL,
RequireLicenseAcceptance: p.Metadata.RequireLicenseAcceptance,
Summary: p.Metadata.Summary,
Tags: p.Metadata.Tags,
Title: p.Metadata.Title,
Dependencies: make(map[string][]Dependency),
}
if p.Metadata.Readme != "" {
f, err := archive.Open(p.Metadata.Readme)
if err == nil {
buf, _ := io.ReadAll(f)
m.Readme = string(buf)
_ = f.Close()
}
}
if len(p.Metadata.Dependencies.Dependency) > 0 {
deps := make([]Dependency, 0, len(p.Metadata.Dependencies.Dependency))
for _, dep := range p.Metadata.Dependencies.Dependency {
if dep.ID == "" || dep.Version == "" {
continue
}
deps = append(deps, Dependency{
ID: dep.ID,
Version: dep.Version,
})
}
m.Dependencies[""] = deps
}
for _, group := range p.Metadata.Dependencies.Group {
deps := make([]Dependency, 0, len(group.Dependency))
for _, dep := range group.Dependency {
if dep.ID == "" || dep.Version == "" {
continue
}
deps = append(deps, Dependency{
ID: dep.ID,
Version: dep.Version,
})
}
if len(deps) > 0 {
m.Dependencies[group.TargetFramework] = deps
}
}
return &Package{
PackageType: packageType,
ID: p.Metadata.ID,
Version: toNormalizedVersion(v),
Metadata: m,
NuspecContent: &nuspecBuf,
}, nil
}
// https://learn.microsoft.com/en-us/nuget/concepts/package-versioning#normalized-version-numbers
// https://github.com/NuGet/NuGet.Client/blob/dccbd304b11103e08b97abf4cf4bcc1499d9235a/src/NuGet.Core/NuGet.Versioning/VersionFormatter.cs#L121
func toNormalizedVersion(v *version.Version) string {
var buf bytes.Buffer
segments := v.Segments64()
_, _ = fmt.Fprintf(&buf, "%d.%d.%d", segments[0], segments[1], segments[2])
if len(segments) > 3 && segments[3] > 0 {
_, _ = fmt.Fprintf(&buf, ".%d", segments[3])
}
pre := v.Prerelease()
if pre != "" {
_, _ = fmt.Fprint(&buf, "-", pre)
}
return buf.String()
}

View File

@@ -0,0 +1,217 @@
// Copyright 2021 The Gitea Authors. All rights reserved.
// SPDX-License-Identifier: MIT
package nuget
import (
"archive/zip"
"bytes"
"testing"
"github.com/stretchr/testify/assert"
)
const (
authors = "Gitea Authors"
copyright = "Package Copyright"
dependencyID = "System.Text.Json"
dependencyVersion = "5.0.0"
developmentDependency = true
description = "Package Description"
iconURL = "https://gitea.io/favicon.png"
id = "System.Gitea"
language = "Package Language"
licenseURL = "https://gitea.io/license"
minClientVersion = "1.0.0.0"
owners = "Package Owners"
projectURL = "https://gitea.io"
readme = "Readme"
releaseNotes = "Package Release Notes"
repositoryURL = "https://gitea.io/gitea/gitea"
requireLicenseAcceptance = true
tags = "tag_1 tag_2 tag_3"
targetFramework = ".NETStandard2.1"
title = "Package Title"
versionStr = "1.0.1"
)
const nuspecContent = `<?xml version="1.0" encoding="utf-8"?>
<package xmlns="http://schemas.microsoft.com/packaging/2013/05/nuspec.xsd">
<metadata minClientVersion="` + minClientVersion + `">
<authors>` + authors + `</authors>
<copyright>` + copyright + `</copyright>
<description>` + description + `</description>
<developmentDependency>true</developmentDependency>
<iconUrl>` + iconURL + `</iconUrl>
<id>` + id + `</id>
<language>` + language + `</language>
<licenseUrl>` + licenseURL + `</licenseUrl>
<owners>` + owners + `</owners>
<projectUrl>` + projectURL + `</projectUrl>
<readme>README.md</readme>
<releaseNotes>` + releaseNotes + `</releaseNotes>
<repository url="` + repositoryURL + `" />
<requireLicenseAcceptance>true</requireLicenseAcceptance>
<tags>` + tags + `</tags>
<title>` + title + `</title>
<version>` + versionStr + `</version>
<dependencies>
<group targetFramework="` + targetFramework + `">
<dependency id="` + dependencyID + `" version="` + dependencyVersion + `" exclude="Build,Analyzers" />
</group>
</dependencies>
</metadata>
</package>`
const symbolsNuspecContent = `<?xml version="1.0" encoding="utf-8"?>
<package xmlns="http://schemas.microsoft.com/packaging/2013/05/nuspec.xsd">
<metadata>
<id>` + id + `</id>
<version>` + versionStr + `</version>
<description>` + description + `</description>
<packageTypes>
<packageType name="SymbolsPackage" />
</packageTypes>
<dependencies>
<group targetFramework="` + targetFramework + `" />
</dependencies>
</metadata>
</package>`
func TestParsePackageMetaData(t *testing.T) {
createArchive := func(files map[string]string) []byte {
var buf bytes.Buffer
archive := zip.NewWriter(&buf)
for name, content := range files {
w, _ := archive.Create(name)
w.Write([]byte(content))
}
archive.Close()
return buf.Bytes()
}
t.Run("MissingNuspecFile", func(t *testing.T) {
data := createArchive(map[string]string{"dummy.txt": ""})
np, err := ParsePackageMetaData(bytes.NewReader(data), int64(len(data)))
assert.Nil(t, np)
assert.ErrorIs(t, err, ErrMissingNuspecFile)
})
t.Run("MissingNuspecFileInRoot", func(t *testing.T) {
data := createArchive(map[string]string{"sub/package.nuspec": ""})
np, err := ParsePackageMetaData(bytes.NewReader(data), int64(len(data)))
assert.Nil(t, np)
assert.ErrorIs(t, err, ErrMissingNuspecFile)
})
t.Run("InvalidNuspecFile", func(t *testing.T) {
data := createArchive(map[string]string{"package.nuspec": ""})
np, err := ParsePackageMetaData(bytes.NewReader(data), int64(len(data)))
assert.Nil(t, np)
assert.Error(t, err)
})
t.Run("InvalidPackageId", func(t *testing.T) {
data := createArchive(map[string]string{"package.nuspec": `<?xml version="1.0" encoding="utf-8"?>
<package xmlns="http://schemas.microsoft.com/packaging/2013/05/nuspec.xsd">
<metadata></metadata>
</package>`})
np, err := ParsePackageMetaData(bytes.NewReader(data), int64(len(data)))
assert.Nil(t, np)
assert.ErrorIs(t, err, ErrNuspecInvalidID)
})
t.Run("InvalidPackageVersion", func(t *testing.T) {
data := createArchive(map[string]string{"package.nuspec": `<?xml version="1.0" encoding="utf-8"?>
<package xmlns="http://schemas.microsoft.com/packaging/2013/05/nuspec.xsd">
<metadata>
<id>` + id + `</id>
</metadata>
</package>`})
np, err := ParsePackageMetaData(bytes.NewReader(data), int64(len(data)))
assert.Nil(t, np)
assert.ErrorIs(t, err, ErrNuspecInvalidVersion)
})
t.Run("MissingReadme", func(t *testing.T) {
data := createArchive(map[string]string{"package.nuspec": nuspecContent})
np, err := ParsePackageMetaData(bytes.NewReader(data), int64(len(data)))
assert.NoError(t, err)
assert.NotNil(t, np)
assert.Empty(t, np.Metadata.Readme)
})
t.Run("Dependency Package", func(t *testing.T) {
data := createArchive(map[string]string{
"package.nuspec": nuspecContent,
"README.md": readme,
})
np, err := ParsePackageMetaData(bytes.NewReader(data), int64(len(data)))
assert.NoError(t, err)
assert.NotNil(t, np)
assert.Equal(t, DependencyPackage, np.PackageType)
assert.Equal(t, authors, np.Metadata.Authors)
assert.Equal(t, description, np.Metadata.Description)
assert.Equal(t, id, np.ID)
assert.Equal(t, versionStr, np.Version)
assert.Equal(t, copyright, np.Metadata.Copyright)
assert.Equal(t, developmentDependency, np.Metadata.DevelopmentDependency)
assert.Equal(t, iconURL, np.Metadata.IconURL)
assert.Equal(t, language, np.Metadata.Language)
assert.Equal(t, licenseURL, np.Metadata.LicenseURL)
assert.Equal(t, minClientVersion, np.Metadata.MinClientVersion)
assert.Equal(t, owners, np.Metadata.Owners)
assert.Equal(t, projectURL, np.Metadata.ProjectURL)
assert.Equal(t, readme, np.Metadata.Readme)
assert.Equal(t, releaseNotes, np.Metadata.ReleaseNotes)
assert.Equal(t, repositoryURL, np.Metadata.RepositoryURL)
assert.Equal(t, requireLicenseAcceptance, np.Metadata.RequireLicenseAcceptance)
assert.Equal(t, tags, np.Metadata.Tags)
assert.Equal(t, title, np.Metadata.Title)
assert.Len(t, np.Metadata.Dependencies, 1)
assert.Contains(t, np.Metadata.Dependencies, targetFramework)
deps := np.Metadata.Dependencies[targetFramework]
assert.Len(t, deps, 1)
assert.Equal(t, dependencyID, deps[0].ID)
assert.Equal(t, dependencyVersion, deps[0].Version)
t.Run("NormalizedVersion", func(t *testing.T) {
data := createArchive(map[string]string{"package.nuspec": `<?xml version="1.0" encoding="utf-8"?>
<package xmlns="http://schemas.microsoft.com/packaging/2013/05/nuspec.xsd">
<metadata>
<id>test</id>
<version>1.04.5.2.5-rc.1+metadata</version>
</metadata>
</package>`})
np, err := ParsePackageMetaData(bytes.NewReader(data), int64(len(data)))
assert.NoError(t, err)
assert.NotNil(t, np)
assert.Equal(t, "1.4.5.2-rc.1", np.Version)
})
})
t.Run("Symbols Package", func(t *testing.T) {
data := createArchive(map[string]string{"package.nuspec": symbolsNuspecContent})
np, err := ParsePackageMetaData(bytes.NewReader(data), int64(len(data)))
assert.NoError(t, err)
assert.NotNil(t, np)
assert.Equal(t, SymbolsPackage, np.PackageType)
assert.Equal(t, id, np.ID)
assert.Equal(t, versionStr, np.Version)
assert.Equal(t, description, np.Metadata.Description)
assert.Empty(t, np.Metadata.Dependencies)
})
}

View File

@@ -0,0 +1,186 @@
// Copyright 2022 The Gitea Authors. All rights reserved.
// SPDX-License-Identifier: MIT
package nuget
import (
"archive/zip"
"bytes"
"encoding/binary"
"fmt"
"io"
"path"
"path/filepath"
"strings"
"code.gitea.io/gitea/modules/packages"
"code.gitea.io/gitea/modules/util"
)
var (
ErrMissingPdbFiles = util.NewInvalidArgumentErrorf("package does not contain PDB files")
ErrInvalidFiles = util.NewInvalidArgumentErrorf("package contains invalid files")
ErrInvalidPdbMagicNumber = util.NewInvalidArgumentErrorf("invalid Portable PDB magic number")
ErrMissingPdbStream = util.NewInvalidArgumentErrorf("missing PDB stream")
)
type PortablePdb struct {
Name string
ID string
Content *packages.HashedBuffer
}
type PortablePdbList []*PortablePdb
func (l PortablePdbList) Close() {
for _, pdb := range l {
_ = pdb.Content.Close()
}
}
// ExtractPortablePdb extracts PDB files from a .snupkg file
func ExtractPortablePdb(r io.ReaderAt, size int64) (PortablePdbList, error) {
archive, err := zip.NewReader(r, size)
if err != nil {
return nil, err
}
var pdbs PortablePdbList
err = func() error {
for _, file := range archive.File {
if strings.HasSuffix(file.Name, "/") {
continue
}
ext := strings.ToLower(filepath.Ext(file.Name))
switch ext {
case ".nuspec", ".xml", ".psmdcp", ".rels", ".p7s":
continue
case ".pdb":
f, err := archive.Open(file.Name)
if err != nil {
return err
}
buf, err := packages.CreateHashedBufferFromReader(f)
_ = f.Close()
if err != nil {
return err
}
id, err := ParseDebugHeaderID(buf)
if err != nil {
_ = buf.Close()
return fmt.Errorf("Invalid PDB file: %w", err)
}
if _, err := buf.Seek(0, io.SeekStart); err != nil {
_ = buf.Close()
return err
}
pdbs = append(pdbs, &PortablePdb{
Name: path.Base(file.Name),
ID: id,
Content: buf,
})
default:
return ErrInvalidFiles
}
}
return nil
}()
if err != nil {
pdbs.Close()
return nil, err
}
if len(pdbs) == 0 {
return nil, ErrMissingPdbFiles
}
return pdbs, nil
}
// ParseDebugHeaderID TODO
func ParseDebugHeaderID(r io.ReadSeeker) (string, error) {
var magic uint32
if err := binary.Read(r, binary.LittleEndian, &magic); err != nil {
return "", err
}
if magic != 0x424A5342 {
return "", ErrInvalidPdbMagicNumber
}
if _, err := r.Seek(8, io.SeekCurrent); err != nil {
return "", err
}
var versionStringSize int32
if err := binary.Read(r, binary.LittleEndian, &versionStringSize); err != nil {
return "", err
}
if _, err := r.Seek(int64(versionStringSize), io.SeekCurrent); err != nil {
return "", err
}
if _, err := r.Seek(2, io.SeekCurrent); err != nil {
return "", err
}
var streamCount int16
if err := binary.Read(r, binary.LittleEndian, &streamCount); err != nil {
return "", err
}
read4ByteAlignedString := func(r io.Reader) (string, error) {
b := make([]byte, 4)
var buf bytes.Buffer
for {
if _, err := r.Read(b); err != nil {
return "", err
}
if i := bytes.IndexByte(b, 0); i != -1 {
buf.Write(b[:i])
return buf.String(), nil
}
buf.Write(b)
}
}
for i := 0; i < int(streamCount); i++ {
var offset uint32
if err := binary.Read(r, binary.LittleEndian, &offset); err != nil {
return "", err
}
if _, err := r.Seek(4, io.SeekCurrent); err != nil {
return "", err
}
name, err := read4ByteAlignedString(r)
if err != nil {
return "", err
}
if name == "#Pdb" {
if _, err := r.Seek(int64(offset), io.SeekStart); err != nil {
return "", err
}
b := make([]byte, 16)
if _, err := r.Read(b); err != nil {
return "", err
}
data1 := binary.LittleEndian.Uint32(b[0:4])
data2 := binary.LittleEndian.Uint16(b[4:6])
data3 := binary.LittleEndian.Uint16(b[6:8])
data4 := b[8:16]
return fmt.Sprintf("%08x%04x%04x%04x%012x", data1, data2, data3, data4[:2], data4[2:]), nil
}
}
return "", ErrMissingPdbStream
}

View File

@@ -0,0 +1,84 @@
// Copyright 2022 The Gitea Authors. All rights reserved.
// SPDX-License-Identifier: MIT
package nuget
import (
"archive/zip"
"bytes"
"encoding/base64"
"testing"
"code.gitea.io/gitea/modules/setting"
"github.com/stretchr/testify/assert"
)
const pdbContent = `QlNKQgEAAQAAAAAADAAAAFBEQiB2MS4wAAAAAAAABgB8AAAAWAAAACNQZGIAAAAA1AAAAAgBAAAj
fgAA3AEAAAQAAAAjU3RyaW5ncwAAAADgAQAABAAAACNVUwDkAQAAMAAAACNHVUlEAAAAFAIAACgB
AAAjQmxvYgAAAGm7ENm9SGxMtAFVvPUsPJTF6PbtAAAAAFcVogEJAAAAAQAAAA==`
func TestExtractPortablePdb(t *testing.T) {
setting.AppDataPath = t.TempDir()
createArchive := func(name string, content []byte) []byte {
var buf bytes.Buffer
archive := zip.NewWriter(&buf)
w, _ := archive.Create(name)
_, _ = w.Write(content)
_ = archive.Close()
return buf.Bytes()
}
t.Run("MissingPdbFiles", func(t *testing.T) {
var buf bytes.Buffer
_ = zip.NewWriter(&buf).Close()
pdbs, err := ExtractPortablePdb(bytes.NewReader(buf.Bytes()), int64(buf.Len()))
assert.ErrorIs(t, err, ErrMissingPdbFiles)
assert.Empty(t, pdbs)
})
t.Run("InvalidFiles", func(t *testing.T) {
data := createArchive("sub/test.bin", []byte{})
pdbs, err := ExtractPortablePdb(bytes.NewReader(data), int64(len(data)))
assert.ErrorIs(t, err, ErrInvalidFiles)
assert.Empty(t, pdbs)
})
t.Run("Valid", func(t *testing.T) {
b, _ := base64.StdEncoding.DecodeString(pdbContent)
data := createArchive("test.pdb", b)
pdbs, err := ExtractPortablePdb(bytes.NewReader(data), int64(len(data)))
assert.NoError(t, err)
assert.Len(t, pdbs, 1)
assert.Equal(t, "test.pdb", pdbs[0].Name)
assert.Equal(t, "d910bb6948bd4c6cb40155bcf52c3c94", pdbs[0].ID)
pdbs.Close()
})
}
func TestParseDebugHeaderID(t *testing.T) {
t.Run("InvalidPdbMagicNumber", func(t *testing.T) {
id, err := ParseDebugHeaderID(bytes.NewReader([]byte{0, 0, 0, 0}))
assert.ErrorIs(t, err, ErrInvalidPdbMagicNumber)
assert.Empty(t, id)
})
t.Run("MissingPdbStream", func(t *testing.T) {
b, _ := base64.StdEncoding.DecodeString(`QlNKQgEAAQAAAAAADAAAAFBEQiB2MS4wAAAAAAAAAQB8AAAAWAAAACNVUwA=`)
id, err := ParseDebugHeaderID(bytes.NewReader(b))
assert.ErrorIs(t, err, ErrMissingPdbStream)
assert.Empty(t, id)
})
t.Run("Valid", func(t *testing.T) {
b, _ := base64.StdEncoding.DecodeString(pdbContent)
id, err := ParseDebugHeaderID(bytes.NewReader(b))
assert.NoError(t, err)
assert.Equal(t, "d910bb6948bd4c6cb40155bcf52c3c94", id)
})
}