github.com/anchore/syft@v1.38.2/syft/format/syftjson/model/file.go (about)

     1  package model
     2  
     3  import (
     4  	"encoding/json"
     5  	"fmt"
     6  	"strconv"
     7  
     8  	stereoFile "github.com/anchore/stereoscope/pkg/file"
     9  	"github.com/anchore/syft/syft/file"
    10  	"github.com/anchore/syft/syft/license"
    11  )
    12  
    13  // File represents a file discovered during cataloging with its metadata, content digests, licenses, and relationships to packages.
    14  type File struct {
    15  	// ID is a unique identifier for this file within the SBOM.
    16  	ID string `json:"id"`
    17  
    18  	// Location is the file path and layer information where this file was found.
    19  	Location file.Coordinates `json:"location"`
    20  
    21  	// Metadata contains filesystem metadata such as permissions, ownership, and file type.
    22  	Metadata *FileMetadataEntry `json:"metadata,omitempty"`
    23  
    24  	// Contents is the file contents for small files.
    25  	Contents string `json:"contents,omitempty"`
    26  
    27  	// Digests contains cryptographic hashes of the file contents.
    28  	Digests []file.Digest `json:"digests,omitempty"`
    29  
    30  	// Licenses contains license information discovered within this file.
    31  	Licenses []FileLicense `json:"licenses,omitempty"`
    32  
    33  	// Executable contains executable metadata if this file is a binary.
    34  	Executable *file.Executable `json:"executable,omitempty"`
    35  
    36  	// Unknowns contains unknown fields for forward compatibility.
    37  	Unknowns []string `json:"unknowns,omitempty"`
    38  }
    39  
    40  // FileMetadataEntry contains filesystem-level metadata attributes such as permissions, ownership, type, and size for a cataloged file.
    41  type FileMetadataEntry struct {
    42  	// Mode is the Unix file permission mode in octal format.
    43  	Mode int `json:"mode"`
    44  
    45  	// Type is the file type (e.g., "RegularFile", "Directory", "SymbolicLink").
    46  	Type string `json:"type"`
    47  
    48  	// LinkDestination is the target path for symbolic links.
    49  	LinkDestination string `json:"linkDestination,omitempty"`
    50  
    51  	// UserID is the file owner user ID.
    52  	UserID int `json:"userID"`
    53  
    54  	// GroupID is the file owner group ID.
    55  	GroupID int `json:"groupID"`
    56  
    57  	// MIMEType is the MIME type of the file contents.
    58  	MIMEType string `json:"mimeType"`
    59  
    60  	// Size is the file size in bytes.
    61  	Size int64 `json:"size"`
    62  }
    63  
    64  type auxFileMetadataEntry FileMetadataEntry
    65  type fileMetadataEntryWithLegacyHint struct {
    66  	*auxFileMetadataEntry `json:",inline"`
    67  	LegacyHint            any `json:"FileInfo"`
    68  }
    69  
    70  func (f *FileMetadataEntry) UnmarshalJSON(data []byte) error {
    71  	aux := fileMetadataEntryWithLegacyHint{
    72  		auxFileMetadataEntry: (*auxFileMetadataEntry)(f),
    73  	}
    74  	if err := json.Unmarshal(data, &aux); err == nil {
    75  		fieldsSpecified := f.Mode != 0 || f.Type != "" || f.LinkDestination != "" ||
    76  			f.UserID != 0 || f.GroupID != 0 || f.MIMEType != "" || f.Size != 0
    77  		if aux.LegacyHint == nil && fieldsSpecified {
    78  			// we should have at least one field set to a non-zero value... (this is not a legacy shape)
    79  			return nil
    80  		}
    81  	}
    82  
    83  	var legacy sbomImportLegacyFileMetadataEntry
    84  	if err := json.Unmarshal(data, &legacy); err != nil {
    85  		return err
    86  	}
    87  
    88  	if !legacy.Type.WasInt {
    89  		// this occurs for document shapes from a non-import path and indicates that the mode has already been converted to octal.
    90  		// That being said, we want to handle all legacy shapes the same, so we will convert this to base 10 for consistency.
    91  		legacy.Mode = convertBase8ToBase10(legacy.Mode)
    92  	}
    93  
    94  	f.Mode = legacy.Mode
    95  	f.Type = legacy.Type.Value
    96  	f.LinkDestination = legacy.LinkDestination
    97  	f.UserID = legacy.UserID
    98  	f.GroupID = legacy.GroupID
    99  	f.MIMEType = legacy.MIMEType
   100  	f.Size = legacy.Size
   101  
   102  	return nil
   103  }
   104  
   105  type sbomImportLegacyFileMetadataEntry struct {
   106  	Mode            int                 `json:"Mode"`
   107  	Type            intOrStringFileType `json:"Type"`
   108  	LinkDestination string              `json:"LinkDestination"`
   109  	UserID          int                 `json:"UserID"`
   110  	GroupID         int                 `json:"GroupID"`
   111  	MIMEType        string              `json:"MIMEType"`
   112  	Size            int64               `json:"Size"`
   113  }
   114  
   115  // FileLicense represents license information discovered within a file's contents or metadata, including the matched license text and SPDX expression.
   116  type FileLicense struct {
   117  	// Value is the raw license identifier or text as found in the file.
   118  	Value string `json:"value"`
   119  
   120  	// SPDXExpression is the parsed SPDX license expression.
   121  	SPDXExpression string `json:"spdxExpression"`
   122  
   123  	// Type is the license type classification (e.g., declared, concluded, discovered).
   124  	Type license.Type `json:"type"`
   125  
   126  	// Evidence contains supporting evidence for this license detection.
   127  	Evidence *FileLicenseEvidence `json:"evidence,omitempty"`
   128  }
   129  
   130  // FileLicenseEvidence contains supporting evidence for a license detection in a file, including the byte offset, extent, and confidence level.
   131  type FileLicenseEvidence struct {
   132  	// Confidence is the confidence score for this license detection (0-100).
   133  	Confidence int `json:"confidence"`
   134  
   135  	// Offset is the byte offset where the license text starts in the file.
   136  	Offset int `json:"offset"`
   137  
   138  	// Extent is the length of the license text in bytes.
   139  	Extent int `json:"extent"`
   140  }
   141  
   142  type intOrStringFileType struct {
   143  	Value  string
   144  	WasInt bool
   145  }
   146  
   147  func (lt *intOrStringFileType) UnmarshalJSON(data []byte) error {
   148  	var str string
   149  	if err := json.Unmarshal(data, &str); err == nil {
   150  		lt.Value = str
   151  		return nil
   152  	}
   153  
   154  	var num stereoFile.Type
   155  	if err := json.Unmarshal(data, &num); err != nil {
   156  		return fmt.Errorf("file.Type must be either string or int, got: %s", string(data))
   157  	}
   158  
   159  	lt.Value = num.String()
   160  	lt.WasInt = true
   161  	return nil
   162  }
   163  
   164  func convertBase10ToBase8(rawMode int) int {
   165  	octalStr := fmt.Sprintf("%o", rawMode)
   166  	// we don't need to check that this is a valid octal string since the input is always an integer
   167  	result, _ := strconv.Atoi(octalStr)
   168  	return result
   169  }
   170  
   171  func convertBase8ToBase10(octalMode int) int {
   172  	octalStr := strconv.Itoa(octalMode)
   173  	result, _ := strconv.ParseInt(octalStr, 8, 64)
   174  
   175  	return int(result)
   176  }