cache.go raw

   1  // Copyright 2016 The Go Authors. All rights reserved.
   2  // Use of this source code is governed by a BSD-style
   3  // license that can be found in the LICENSE file.
   4  
   5  package autocert
   6  
   7  import (
   8  	"context"
   9  	"errors"
  10  	"os"
  11  	"path/filepath"
  12  )
  13  
  14  // ErrCacheMiss is returned when a certificate is not found in cache.
  15  var ErrCacheMiss = errors.New("acme/autocert: certificate cache miss")
  16  
  17  // Cache is used by Manager to store and retrieve previously obtained certificates
  18  // and other account data as opaque blobs.
  19  //
  20  // Cache implementations should not rely on the key naming pattern. Keys can
  21  // include any printable ASCII characters, except the following: \/:*?"<>|
  22  type Cache interface {
  23  	// Get returns a certificate data for the specified key.
  24  	// If there's no such key, Get returns ErrCacheMiss.
  25  	Get(ctx context.Context, key string) ([]byte, error)
  26  
  27  	// Put stores the data in the cache under the specified key.
  28  	// Underlying implementations may use any data storage format,
  29  	// as long as the reverse operation, Get, results in the original data.
  30  	Put(ctx context.Context, key string, data []byte) error
  31  
  32  	// Delete removes a certificate data from the cache under the specified key.
  33  	// If there's no such key in the cache, Delete returns nil.
  34  	Delete(ctx context.Context, key string) error
  35  }
  36  
  37  // DirCache implements Cache using a directory on the local filesystem.
  38  // If the directory does not exist, it will be created with 0700 permissions.
  39  type DirCache string
  40  
  41  // Get reads a certificate data from the specified file name.
  42  func (d DirCache) Get(ctx context.Context, name string) ([]byte, error) {
  43  	name = filepath.Join(string(d), filepath.Clean("/"+name))
  44  	var (
  45  		data []byte
  46  		err  error
  47  		done = make(chan struct{})
  48  	)
  49  	go func() {
  50  		data, err = os.ReadFile(name)
  51  		close(done)
  52  	}()
  53  	select {
  54  	case <-ctx.Done():
  55  		return nil, ctx.Err()
  56  	case <-done:
  57  	}
  58  	if os.IsNotExist(err) {
  59  		return nil, ErrCacheMiss
  60  	}
  61  	return data, err
  62  }
  63  
  64  // Put writes the certificate data to the specified file name.
  65  // The file will be created with 0600 permissions.
  66  func (d DirCache) Put(ctx context.Context, name string, data []byte) error {
  67  	if err := os.MkdirAll(string(d), 0700); err != nil {
  68  		return err
  69  	}
  70  
  71  	done := make(chan struct{})
  72  	var err error
  73  	go func() {
  74  		defer close(done)
  75  		var tmp string
  76  		if tmp, err = d.writeTempFile(name, data); err != nil {
  77  			return
  78  		}
  79  		defer os.Remove(tmp)
  80  		select {
  81  		case <-ctx.Done():
  82  			// Don't overwrite the file if the context was canceled.
  83  		default:
  84  			newName := filepath.Join(string(d), filepath.Clean("/"+name))
  85  			err = os.Rename(tmp, newName)
  86  		}
  87  	}()
  88  	select {
  89  	case <-ctx.Done():
  90  		return ctx.Err()
  91  	case <-done:
  92  	}
  93  	return err
  94  }
  95  
  96  // Delete removes the specified file name.
  97  func (d DirCache) Delete(ctx context.Context, name string) error {
  98  	name = filepath.Join(string(d), filepath.Clean("/"+name))
  99  	var (
 100  		err  error
 101  		done = make(chan struct{})
 102  	)
 103  	go func() {
 104  		err = os.Remove(name)
 105  		close(done)
 106  	}()
 107  	select {
 108  	case <-ctx.Done():
 109  		return ctx.Err()
 110  	case <-done:
 111  	}
 112  	if err != nil && !os.IsNotExist(err) {
 113  		return err
 114  	}
 115  	return nil
 116  }
 117  
 118  // writeTempFile writes b to a temporary file, closes the file and returns its path.
 119  func (d DirCache) writeTempFile(prefix string, b []byte) (name string, reterr error) {
 120  	// TempFile uses 0600 permissions
 121  	f, err := os.CreateTemp(string(d), prefix)
 122  	if err != nil {
 123  		return "", err
 124  	}
 125  	defer func() {
 126  		if reterr != nil {
 127  			os.Remove(f.Name())
 128  		}
 129  	}()
 130  	if _, err := f.Write(b); err != nil {
 131  		f.Close()
 132  		return "", err
 133  	}
 134  	return f.Name(), f.Close()
 135  }
 136