github.com/go-kivik/kivik/v4@v4.3.2/x/fsdb/cdb/util.go (about) 1 // Licensed under the Apache License, Version 2.0 (the "License"); you may not 2 // use this file except in compliance with the License. You may obtain a copy of 3 // the License at 4 // 5 // http://www.apache.org/licenses/LICENSE-2.0 6 // 7 // Unless required by applicable law or agreed to in writing, software 8 // distributed under the License is distributed on an "AS IS" BASIS, WITHOUT 9 // WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the 10 // License for the specific language governing permissions and limitations under 11 // the License. 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 // * Can these be PUT? 38 } 39 40 // EscapeID escapes a document or database ID to be a safe filename. 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 // UnescapeID unescapes a filename into a document or database ID. 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 // copyDigest works the same as io.Copy, but also returns the md5sum 66 // digest of the copied file. 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 // atomicFileWriter returns an io.WriteCloser, which writes to a temp file, then 146 // when Close() is called, it renames to the originally requested path. 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 }