| // Copyright 2016 The Go Authors. All rights reserved. |
| // Use of this source code is governed by a BSD-style |
| // license that can be found in the LICENSE file. |
| |
| package autocert |
| |
| import ( |
| "context" |
| "errors" |
| "io/ioutil" |
| "os" |
| "path/filepath" |
| ) |
| |
| // ErrCacheMiss is returned when a certificate is not found in cache. |
| var ErrCacheMiss = errors.New("acme/autocert: certificate cache miss") |
| |
| // Cache is used by Manager to store and retrieve previously obtained certificates |
| // and other account data as opaque blobs. |
| // |
| // Cache implementations should not rely on the key naming pattern. Keys can |
| // include any printable ASCII characters, except the following: \/:*?"<>| |
| type Cache interface { |
| // Get returns a certificate data for the specified key. |
| // If there's no such key, Get returns ErrCacheMiss. |
| Get(ctx context.Context, key string) ([]byte, error) |
| |
| // Put stores the data in the cache under the specified key. |
| // Underlying implementations may use any data storage format, |
| // as long as the reverse operation, Get, results in the original data. |
| Put(ctx context.Context, key string, data []byte) error |
| |
| // Delete removes a certificate data from the cache under the specified key. |
| // If there's no such key in the cache, Delete returns nil. |
| Delete(ctx context.Context, key string) error |
| } |
| |
| // DirCache implements Cache using a directory on the local filesystem. |
| // If the directory does not exist, it will be created with 0700 permissions. |
| type DirCache string |
| |
| // Get reads a certificate data from the specified file name. |
| func (d DirCache) Get(ctx context.Context, name string) ([]byte, error) { |
| name = filepath.Join(string(d), name) |
| var ( |
| data []byte |
| err error |
| done = make(chan struct{}) |
| ) |
| go func() { |
| data, err = ioutil.ReadFile(name) |
| close(done) |
| }() |
| select { |
| case <-ctx.Done(): |
| return nil, ctx.Err() |
| case <-done: |
| } |
| if os.IsNotExist(err) { |
| return nil, ErrCacheMiss |
| } |
| return data, err |
| } |
| |
| // Put writes the certificate data to the specified file name. |
| // The file will be created with 0600 permissions. |
| func (d DirCache) Put(ctx context.Context, name string, data []byte) error { |
| if err := os.MkdirAll(string(d), 0700); err != nil { |
| return err |
| } |
| |
| done := make(chan struct{}) |
| var err error |
| go func() { |
| defer close(done) |
| var tmp string |
| if tmp, err = d.writeTempFile(name, data); err != nil { |
| return |
| } |
| defer os.Remove(tmp) |
| select { |
| case <-ctx.Done(): |
| // Don't overwrite the file if the context was canceled. |
| default: |
| newName := filepath.Join(string(d), name) |
| err = os.Rename(tmp, newName) |
| } |
| }() |
| select { |
| case <-ctx.Done(): |
| return ctx.Err() |
| case <-done: |
| } |
| return err |
| } |
| |
| // Delete removes the specified file name. |
| func (d DirCache) Delete(ctx context.Context, name string) error { |
| name = filepath.Join(string(d), name) |
| var ( |
| err error |
| done = make(chan struct{}) |
| ) |
| go func() { |
| err = os.Remove(name) |
| close(done) |
| }() |
| select { |
| case <-ctx.Done(): |
| return ctx.Err() |
| case <-done: |
| } |
| if err != nil && !os.IsNotExist(err) { |
| return err |
| } |
| return nil |
| } |
| |
| // writeTempFile writes b to a temporary file, closes the file and returns its path. |
| func (d DirCache) writeTempFile(prefix string, b []byte) (name string, reterr error) { |
| // TempFile uses 0600 permissions |
| f, err := ioutil.TempFile(string(d), prefix) |
| if err != nil { |
| return "", err |
| } |
| defer func() { |
| if reterr != nil { |
| os.Remove(f.Name()) |
| } |
| }() |
| if _, err := f.Write(b); err != nil { |
| f.Close() |
| return "", err |
| } |
| return f.Name(), f.Close() |
| } |