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  }