code.gitea.io/gitea@v1.19.3/modules/packages/nuget/symbol_extractor.go (about)

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