github.com/safing/portbase@v0.19.5/updater/indexes.go (about) 1 package updater 2 3 import ( 4 "encoding/json" 5 "errors" 6 "fmt" 7 "time" 8 ) 9 10 const ( 11 baseIndexExtension = ".json" 12 v2IndexExtension = ".v2.json" 13 ) 14 15 // Index describes an index file pulled by the updater. 16 type Index struct { 17 // Path is the path to the index file 18 // on the update server. 19 Path string 20 21 // Channel holds the release channel name of the index. 22 // It must match the filename without extension. 23 Channel string 24 25 // PreRelease signifies that all versions of this index should be marked as 26 // pre-releases, no matter if the versions actually have a pre-release tag or 27 // not. 28 PreRelease bool 29 30 // AutoDownload specifies whether new versions should be automatically downloaded. 31 AutoDownload bool 32 33 // LastRelease holds the time of the last seen release of this index. 34 LastRelease time.Time 35 } 36 37 // IndexFile represents an index file. 38 type IndexFile struct { 39 Channel string 40 Published time.Time 41 42 Releases map[string]string 43 } 44 45 var ( 46 // ErrIndexChecksumMismatch is returned when an index does not match its 47 // signed checksum. 48 ErrIndexChecksumMismatch = errors.New("index checksum does mot match signature") 49 50 // ErrIndexFromFuture is returned when an index is parsed with a 51 // Published timestamp that lies in the future. 52 ErrIndexFromFuture = errors.New("index is from the future") 53 54 // ErrIndexIsOlder is returned when an index is parsed with an older 55 // Published timestamp than the current Published timestamp. 56 ErrIndexIsOlder = errors.New("index is older than the current one") 57 58 // ErrIndexChannelMismatch is returned when an index is parsed with a 59 // different channel that the expected one. 60 ErrIndexChannelMismatch = errors.New("index does not match the expected channel") 61 ) 62 63 // ParseIndexFile parses an index file and checks if it is valid. 64 func ParseIndexFile(indexData []byte, channel string, lastIndexRelease time.Time) (*IndexFile, error) { 65 // Load into struct. 66 indexFile := &IndexFile{} 67 err := json.Unmarshal(indexData, indexFile) 68 if err != nil { 69 return nil, fmt.Errorf("failed to parse signed index data: %w", err) 70 } 71 72 // Fallback to old format if there are no releases and no channel is defined. 73 // TODO: Remove in v1 74 if len(indexFile.Releases) == 0 && indexFile.Channel == "" { 75 return loadOldIndexFormat(indexData, channel) 76 } 77 78 // Check the index metadata. 79 switch { 80 case !indexFile.Published.IsZero() && time.Now().Before(indexFile.Published): 81 return indexFile, ErrIndexFromFuture 82 83 case !indexFile.Published.IsZero() && 84 !lastIndexRelease.IsZero() && 85 lastIndexRelease.After(indexFile.Published): 86 return indexFile, ErrIndexIsOlder 87 88 case channel != "" && 89 indexFile.Channel != "" && 90 channel != indexFile.Channel: 91 return indexFile, ErrIndexChannelMismatch 92 } 93 94 return indexFile, nil 95 } 96 97 func loadOldIndexFormat(indexData []byte, channel string) (*IndexFile, error) { 98 releases := make(map[string]string) 99 err := json.Unmarshal(indexData, &releases) 100 if err != nil { 101 return nil, err 102 } 103 104 return &IndexFile{ 105 Channel: channel, 106 // Do NOT define `Published`, as this would break the "is newer" check. 107 Releases: releases, 108 }, nil 109 }