1
2
3
4
5
6
7
8
9
10
11
12
13 package cdb
14
15 import (
16 "crypto/md5"
17 "encoding/base64"
18 "encoding/json"
19 "io"
20 "path/filepath"
21 "strings"
22 "sync"
23
24 "github.com/go-kivik/kivik/v4/x/fsdb/filesystem"
25 )
26
27 var reservedKeys = map[string]struct{}{
28 "_id": {},
29 "_rev": {},
30 "_attachments": {},
31 "_revisions": {},
32 "_revs_info": {},
33 "_deleted": {},
34 "_conflicts": {},
35 "_deleted_conflicts": {},
36 "_local_seq": {},
37
38 }
39
40
41 func EscapeID(s string) string {
42 if s == "" {
43 return s
44 }
45 if strings.Contains(s, "/") || strings.Contains(s, "%2F") || strings.Contains(s, "%2f") {
46 s = strings.ReplaceAll(s, "%", "%25")
47 s = strings.ReplaceAll(s, "/", "%2F")
48 }
49 return s
50 }
51
52
53 func UnescapeID(s string) string {
54 if s == "" {
55 return s
56 }
57 if strings.Contains(s, "%2F") || strings.Contains(s, "%2f") || strings.Contains(s, "%25") {
58 s = strings.ReplaceAll(s, "%2F", "/")
59 s = strings.ReplaceAll(s, "%2f", "/")
60 s = strings.ReplaceAll(s, "%25", "%")
61 }
62 return s
63 }
64
65
66
67 func copyDigest(tgt io.Writer, dst io.Reader) (int64, string, error) {
68 h := md5.New()
69 tee := io.TeeReader(dst, h)
70 wg := sync.WaitGroup{}
71 wg.Add(1)
72 var written int64
73 var err error
74 go func() {
75 written, err = io.Copy(tgt, tee)
76 wg.Done()
77 }()
78 wg.Wait()
79
80 return written, "md5-" + base64.StdEncoding.EncodeToString(h.Sum(nil)), err
81 }
82
83 func digest(r io.Reader) (int64, string) {
84 h := md5.New()
85 written, _ := io.Copy(h, r)
86 return written, "md5-" + base64.StdEncoding.EncodeToString(h.Sum(nil))
87 }
88
89 func joinJSON(objects ...json.RawMessage) []byte {
90 var size int
91 for _, obj := range objects {
92 size += len(obj)
93 }
94 result := make([]byte, 0, size)
95 result = append(result, '{')
96 for _, obj := range objects {
97 if len(obj) == 4 && string(obj) == "null" {
98 continue
99 }
100 result = append(result, obj[1:len(obj)-1]...)
101 result = append(result, ',')
102 }
103 result[len(result)-1] = '}'
104 return result
105 }
106
107 func atomicWriteFile(fs filesystem.Filesystem, path string, r io.Reader) error {
108 f, err := fs.TempFile(filepath.Dir(path), ".tmp."+filepath.Base(path)+"-")
109 if err != nil {
110 return err
111 }
112 if _, err := io.Copy(f, r); err != nil {
113 return err
114 }
115 if err := f.Close(); err != nil {
116 return err
117 }
118 return fs.Rename(f.Name(), path)
119 }
120
121 type atomicWriter struct {
122 fs filesystem.Filesystem
123 path string
124 f filesystem.File
125 err error
126 }
127
128 func (w *atomicWriter) Write(p []byte) (int, error) {
129 if w.err != nil {
130 return 0, w.err
131 }
132 return w.f.Write(p)
133 }
134
135 func (w *atomicWriter) Close() error {
136 if w.err != nil {
137 return w.err
138 }
139 if err := w.f.Close(); err != nil {
140 return err
141 }
142 return w.fs.Rename(w.f.Name(), w.path)
143 }
144
145
146
147 func atomicFileWriter(fs filesystem.Filesystem, path string) io.WriteCloser {
148 f, err := fs.TempFile(filepath.Dir(path), ".tmp."+filepath.Base(path)+"-")
149 return &atomicWriter{
150 fs: fs,
151 path: path,
152 f: f,
153 err: err,
154 }
155 }
156
View as plain text