github.com/gitbundle/modules@v0.0.0-20231025071548-85b91c5c3b01/packages/nuget/symbol_extractor.go (about)

     1  // Copyright 2023 The GitBundle Inc. All rights reserved.
     2  // Copyright 2017 The Gitea Authors. All rights reserved.
     3  // Use of this source code is governed by a MIT-style
     4  // license that can be found in the LICENSE file.
     5  
     6  package nuget
     7  
     8  import (
     9  	"archive/zip"
    10  	"bytes"
    11  	"encoding/binary"
    12  	"errors"
    13  	"fmt"
    14  	"io"
    15  	"path"
    16  	"path/filepath"
    17  	"strings"
    18  
    19  	"github.com/gitbundle/modules/packages"
    20  )
    21  
    22  var (
    23  	ErrMissingPdbFiles       = errors.New("Package does not contain PDB files")
    24  	ErrInvalidFiles          = errors.New("Package contains invalid files")
    25  	ErrInvalidPdbMagicNumber = errors.New("Invalid Portable PDB magic number")
    26  	ErrMissingPdbStream      = errors.New("Missing PDB stream")
    27  )
    28  
    29  type PortablePdb struct {
    30  	Name    string
    31  	ID      string
    32  	Content *packages.HashedBuffer
    33  }
    34  
    35  type PortablePdbList []*PortablePdb
    36  
    37  func (l PortablePdbList) Close() {
    38  	for _, pdb := range l {
    39  		pdb.Content.Close()
    40  	}
    41  }
    42  
    43  // ExtractPortablePdb extracts PDB files from a .snupkg file
    44  func ExtractPortablePdb(r io.ReaderAt, size int64) (PortablePdbList, error) {
    45  	archive, err := zip.NewReader(r, size)
    46  	if err != nil {
    47  		return nil, err
    48  	}
    49  
    50  	var pdbs PortablePdbList
    51  
    52  	err = func() error {
    53  		for _, file := range archive.File {
    54  			if strings.HasSuffix(file.Name, "/") {
    55  				continue
    56  			}
    57  			ext := strings.ToLower(filepath.Ext(file.Name))
    58  
    59  			switch ext {
    60  			case ".nuspec", ".xml", ".psmdcp", ".rels", ".p7s":
    61  				continue
    62  			case ".pdb":
    63  				f, err := archive.Open(file.Name)
    64  				if err != nil {
    65  					return err
    66  				}
    67  
    68  				buf, err := packages.CreateHashedBufferFromReader(f, 32*1024*1024)
    69  
    70  				f.Close()
    71  
    72  				if err != nil {
    73  					return err
    74  				}
    75  
    76  				id, err := ParseDebugHeaderID(buf)
    77  				if err != nil {
    78  					buf.Close()
    79  					return fmt.Errorf("Invalid PDB file: %v", err)
    80  				}
    81  
    82  				if _, err := buf.Seek(0, io.SeekStart); err != nil {
    83  					buf.Close()
    84  					return err
    85  				}
    86  
    87  				pdbs = append(pdbs, &PortablePdb{
    88  					Name:    path.Base(file.Name),
    89  					ID:      id,
    90  					Content: buf,
    91  				})
    92  			default:
    93  				return ErrInvalidFiles
    94  			}
    95  		}
    96  		return nil
    97  	}()
    98  	if err != nil {
    99  		pdbs.Close()
   100  		return nil, err
   101  	}
   102  
   103  	if len(pdbs) == 0 {
   104  		return nil, ErrMissingPdbFiles
   105  	}
   106  
   107  	return pdbs, nil
   108  }
   109  
   110  // ParseDebugHeaderID TODO
   111  func ParseDebugHeaderID(r io.ReadSeeker) (string, error) {
   112  	var magic uint32
   113  	if err := binary.Read(r, binary.LittleEndian, &magic); err != nil {
   114  		return "", err
   115  	}
   116  	if magic != 0x424A5342 {
   117  		return "", ErrInvalidPdbMagicNumber
   118  	}
   119  
   120  	if _, err := r.Seek(8, io.SeekCurrent); err != nil {
   121  		return "", err
   122  	}
   123  
   124  	var versionStringSize int32
   125  	if err := binary.Read(r, binary.LittleEndian, &versionStringSize); err != nil {
   126  		return "", err
   127  	}
   128  	if _, err := r.Seek(int64(versionStringSize), io.SeekCurrent); err != nil {
   129  		return "", err
   130  	}
   131  	if _, err := r.Seek(2, io.SeekCurrent); err != nil {
   132  		return "", err
   133  	}
   134  
   135  	var streamCount int16
   136  	if err := binary.Read(r, binary.LittleEndian, &streamCount); err != nil {
   137  		return "", err
   138  	}
   139  
   140  	read4ByteAlignedString := func(r io.Reader) (string, error) {
   141  		b := make([]byte, 4)
   142  		var buf bytes.Buffer
   143  		for {
   144  			if _, err := r.Read(b); err != nil {
   145  				return "", err
   146  			}
   147  			if i := bytes.IndexByte(b, 0); i != -1 {
   148  				buf.Write(b[:i])
   149  				return buf.String(), nil
   150  			}
   151  			buf.Write(b)
   152  		}
   153  	}
   154  
   155  	for i := 0; i < int(streamCount); i++ {
   156  		var offset uint32
   157  		if err := binary.Read(r, binary.LittleEndian, &offset); err != nil {
   158  			return "", err
   159  		}
   160  		if _, err := r.Seek(4, io.SeekCurrent); err != nil {
   161  			return "", err
   162  		}
   163  		name, err := read4ByteAlignedString(r)
   164  		if err != nil {
   165  			return "", err
   166  		}
   167  
   168  		if name == "#Pdb" {
   169  			if _, err := r.Seek(int64(offset), io.SeekStart); err != nil {
   170  				return "", err
   171  			}
   172  
   173  			b := make([]byte, 16)
   174  			if _, err := r.Read(b); err != nil {
   175  				return "", err
   176  			}
   177  
   178  			data1 := binary.LittleEndian.Uint32(b[0:4])
   179  			data2 := binary.LittleEndian.Uint16(b[4:6])
   180  			data3 := binary.LittleEndian.Uint16(b[6:8])
   181  			data4 := b[8:16]
   182  
   183  			return fmt.Sprintf("%08x%04x%04x%04x%012x", data1, data2, data3, data4[:2], data4[2:]), nil
   184  		}
   185  	}
   186  
   187  	return "", ErrMissingPdbStream
   188  }