github.com/pietrocarrara/hugo@v0.47.1/resource/integrity/integrity.go (about)

     1  // Copyright 2018 The Hugo Authors. 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  // http://www.apache.org/licenses/LICENSE-2.0
     7  //
     8  // Unless required by applicable law or agreed to in writing, software
     9  // distributed under the License is distributed on an "AS IS" BASIS,
    10  // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
    11  // See the License for the specific language governing permissions and
    12  // limitations under the License.
    13  
    14  package integrity
    15  
    16  import (
    17  	"crypto/md5"
    18  	"crypto/sha256"
    19  	"crypto/sha512"
    20  	"encoding/base64"
    21  	"encoding/hex"
    22  	"fmt"
    23  	"hash"
    24  	"io"
    25  
    26  	"github.com/gohugoio/hugo/resource"
    27  )
    28  
    29  const defaultHashAlgo = "sha256"
    30  
    31  // Client contains methods to fingerprint (cachebusting) and other integrity-related
    32  // methods.
    33  type Client struct {
    34  	rs *resource.Spec
    35  }
    36  
    37  // New creates a new Client with the given specification.
    38  func New(rs *resource.Spec) *Client {
    39  	return &Client{rs: rs}
    40  }
    41  
    42  type fingerprintTransformation struct {
    43  	algo string
    44  }
    45  
    46  func (t *fingerprintTransformation) Key() resource.ResourceTransformationKey {
    47  	return resource.NewResourceTransformationKey("fingerprint", t.algo)
    48  }
    49  
    50  // Transform creates a MD5 hash of the Resource content and inserts that hash before
    51  // the extension in the filename.
    52  func (t *fingerprintTransformation) Transform(ctx *resource.ResourceTransformationCtx) error {
    53  	algo := t.algo
    54  
    55  	var h hash.Hash
    56  
    57  	switch algo {
    58  	case "md5":
    59  		h = md5.New()
    60  	case "sha256":
    61  		h = sha256.New()
    62  	case "sha512":
    63  		h = sha512.New()
    64  	default:
    65  		return fmt.Errorf("unsupported crypto algo: %q, use either md5, sha256 or sha512", algo)
    66  	}
    67  
    68  	io.Copy(io.MultiWriter(h, ctx.To), ctx.From)
    69  	d, err := digest(h)
    70  	if err != nil {
    71  		return err
    72  	}
    73  
    74  	ctx.Data["Integrity"] = integrity(algo, d)
    75  	ctx.AddOutPathIdentifier("." + hex.EncodeToString(d[:]))
    76  	return nil
    77  }
    78  
    79  // Fingerprint applies fingerprinting of the given resource and hash algorithm.
    80  // It defaults to sha256 if none given, and the options are md5, sha256 or sha512.
    81  // The same algo is used for both the fingerprinting part (aka cache busting) and
    82  // the base64-encoded Subresource Integrity hash, so you will have to stay away from
    83  // md5 if you plan to use both.
    84  // See https://developer.mozilla.org/en-US/docs/Web/Security/Subresource_Integrity
    85  func (c *Client) Fingerprint(res resource.Resource, algo string) (resource.Resource, error) {
    86  	if algo == "" {
    87  		algo = defaultHashAlgo
    88  	}
    89  
    90  	return c.rs.Transform(
    91  		res,
    92  		&fingerprintTransformation{algo: algo},
    93  	)
    94  }
    95  
    96  func integrity(algo string, sum []byte) string {
    97  	encoded := base64.StdEncoding.EncodeToString(sum)
    98  	return fmt.Sprintf("%s-%s", algo, encoded)
    99  
   100  }
   101  
   102  func digest(h hash.Hash) ([]byte, error) {
   103  	sum := h.Sum(nil)
   104  	return sum, nil
   105  }