github.com/cozy/cozy-stack@v0.0.0-20240603063001-31110fa4cae1/pkg/assets/statik/statik.go (about)

     1  // Copyright 2014 Google Inc. All Rights Reserved.
     2  //
     3  // Licensed under the Apache License, Version 2.0 (the "License");
     4  // you may not use this file except in compliance with the License.
     5  // You may obtain a copy of the License at
     6  //
     7  //      http://www.apache.org/licenses/LICENSE-2.0
     8  //
     9  // Unless required by applicable law or agreed to in writing, software
    10  // distributed under the License is distributed on an "AS IS" BASIS,
    11  // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
    12  // See the License for the specific language governing permissions and
    13  // limitations under the License.
    14  
    15  // Package statik contains an HTTP file system that works with zip contents.
    16  package statik
    17  
    18  import (
    19  	"bytes"
    20  	"crypto/sha256"
    21  	"encoding/hex"
    22  	"encoding/pem"
    23  	"fmt"
    24  	"io"
    25  	"sync"
    26  
    27  	"github.com/andybalholm/brotli"
    28  	"github.com/cozy/cozy-stack/pkg/assets/model"
    29  	"github.com/cozy/cozy-stack/pkg/config/config"
    30  )
    31  
    32  var globalAssets sync.Map // {context:path -> *Asset}
    33  
    34  // Register registers brotli contents data, later used to
    35  // initialize the statik file system.
    36  func Register(brotliData string) {
    37  	if brotliData == "" {
    38  		panic("statik/fs: no zip data registered")
    39  	}
    40  	if err := uncompress([]byte(brotliData)); err != nil {
    41  		panic(fmt.Errorf("statik/fs: error uncompressed data: %s", err))
    42  	}
    43  }
    44  
    45  func uncompress(data []byte) error {
    46  	for {
    47  		block, rest := pem.Decode(data)
    48  		if block == nil {
    49  			break
    50  		}
    51  		brotliData := block.Bytes
    52  		br := brotli.NewReader(bytes.NewReader(brotliData))
    53  		h := sha256.New()
    54  		r := io.TeeReader(br, h)
    55  		rawData, err := io.ReadAll(r)
    56  		if err != nil {
    57  			return err
    58  		}
    59  
    60  		name := block.Headers["Name"]
    61  		opt := model.AssetOption{
    62  			Name:    name,
    63  			Context: config.DefaultInstanceContext,
    64  			Shasum:  hex.EncodeToString(h.Sum(nil)),
    65  		}
    66  		asset := model.NewAsset(opt, rawData, brotliData)
    67  		StoreAsset(asset)
    68  		data = rest
    69  	}
    70  	return nil
    71  }
    72  
    73  // StoreAsset stores in memory a static asset
    74  func StoreAsset(asset *model.Asset) {
    75  	globalAssets.Store(asset.Name, asset)
    76  }
    77  
    78  // UnstoreAsset removes a static asset from the memory list
    79  func UnstoreAsset(asset *model.Asset) {
    80  	globalAssets.Delete(asset.Name)
    81  }
    82  
    83  // GetAsset returns the asset with the given name.
    84  func GetAsset(name string) *model.Asset {
    85  	if v, ok := globalAssets.Load(name); ok {
    86  		return v.(*model.Asset)
    87  	}
    88  	return nil
    89  }
    90  
    91  // Foreach iterates on the static assets.
    92  func Foreach(predicate func(name string, f *model.Asset)) {
    93  	globalAssets.Range(func(key interface{}, v interface{}) bool {
    94  		predicate(key.(string), v.(*model.Asset))
    95  		return true
    96  	})
    97  }