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