httpfs.go 7.0 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301
  1. // Copyright (c) 2014 ql 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. package ql
  5. import (
  6. "fmt"
  7. "io"
  8. "net/http"
  9. "os"
  10. "path"
  11. "path/filepath"
  12. "strings"
  13. "time"
  14. "github.com/cznic/mathutil"
  15. )
  16. var (
  17. _ http.FileSystem = (*HTTPFS)(nil)
  18. _ http.File = (*HTTPFile)(nil)
  19. _ os.FileInfo = (*HTTPFile)(nil)
  20. _ os.FileInfo = (*dirEntry)(nil)
  21. )
  22. type dirEntry string
  23. func (d dirEntry) Name() string { return string(d) }
  24. func (d dirEntry) Size() int64 { return -1 }
  25. func (d dirEntry) Mode() os.FileMode { return os.ModeDir }
  26. func (d dirEntry) ModTime() time.Time { return time.Time{} }
  27. func (d dirEntry) IsDir() bool { return true }
  28. func (d dirEntry) Sys() interface{} { return interface{}(nil) }
  29. // A HTTPFile is returned by the HTTPFS's Open method and can be served by the
  30. // http.FileServer implementation.
  31. type HTTPFile struct {
  32. closed bool
  33. content []byte
  34. dirEntries []os.FileInfo
  35. isFile bool
  36. name string
  37. off int
  38. }
  39. // Close implements http.File.
  40. func (f *HTTPFile) Close() error {
  41. if f.closed {
  42. return os.ErrInvalid
  43. }
  44. f.closed = true
  45. return nil
  46. }
  47. // IsDir implements os.FileInfo
  48. func (f *HTTPFile) IsDir() bool { return !f.isFile }
  49. // Mode implements os.FileInfo
  50. func (f *HTTPFile) Mode() os.FileMode {
  51. switch f.isFile {
  52. case false:
  53. return os.FileMode(0444)
  54. default:
  55. return os.ModeDir
  56. }
  57. }
  58. // ModTime implements os.FileInfo
  59. func (f *HTTPFile) ModTime() time.Time {
  60. return time.Time{}
  61. }
  62. // Name implements os.FileInfo
  63. func (f *HTTPFile) Name() string { return path.Base(f.name) }
  64. // Size implements os.FileInfo
  65. func (f *HTTPFile) Size() int64 {
  66. switch f.isFile {
  67. case false:
  68. return -1
  69. default:
  70. return int64(len(f.content))
  71. }
  72. }
  73. // Stat implements http.File.
  74. func (f *HTTPFile) Stat() (os.FileInfo, error) { return f, nil }
  75. // Sys implements os.FileInfo
  76. func (f *HTTPFile) Sys() interface{} { return interface{}(nil) }
  77. // Readdir implements http.File.
  78. func (f *HTTPFile) Readdir(count int) ([]os.FileInfo, error) {
  79. if f.isFile {
  80. return nil, fmt.Errorf("not a directory: %s", f.name)
  81. }
  82. if count <= 0 {
  83. r := f.dirEntries
  84. f.dirEntries = f.dirEntries[:0]
  85. return r, nil
  86. }
  87. rq := mathutil.Min(count, len(f.dirEntries))
  88. r := f.dirEntries[:rq]
  89. f.dirEntries = f.dirEntries[rq:]
  90. if len(r) != 0 {
  91. return r, nil
  92. }
  93. return nil, io.EOF
  94. }
  95. // Read implements http.File.
  96. func (f *HTTPFile) Read(b []byte) (int, error) {
  97. if f.closed {
  98. return 0, os.ErrInvalid
  99. }
  100. n := copy(b, f.content[f.off:])
  101. f.off += n
  102. if n != 0 {
  103. return n, nil
  104. }
  105. return 0, io.EOF
  106. }
  107. // Seek implements http.File.
  108. func (f *HTTPFile) Seek(offset int64, whence int) (int64, error) {
  109. if f.closed {
  110. return 0, os.ErrInvalid
  111. }
  112. if offset < 0 {
  113. return int64(f.off), fmt.Errorf("cannot seek before start of file")
  114. }
  115. switch whence {
  116. case 0:
  117. noff := int64(f.off) + offset
  118. if noff > mathutil.MaxInt {
  119. return int64(f.off), fmt.Errorf("seek target overflows int: %d", noff)
  120. }
  121. f.off = mathutil.Min(int(offset), len(f.content))
  122. if f.off == int(offset) {
  123. return offset, nil
  124. }
  125. return int64(f.off), io.EOF
  126. case 1:
  127. noff := int64(f.off) + offset
  128. if noff > mathutil.MaxInt {
  129. return int64(f.off), fmt.Errorf("seek target overflows int: %d", noff)
  130. }
  131. off := mathutil.Min(f.off+int(offset), len(f.content))
  132. if off == f.off+int(offset) {
  133. f.off = off
  134. return int64(off), nil
  135. }
  136. f.off = off
  137. return int64(off), io.EOF
  138. case 2:
  139. noff := int64(f.off) - offset
  140. if noff < 0 {
  141. return int64(f.off), fmt.Errorf("cannot seek before start of file")
  142. }
  143. f.off = len(f.content) - int(offset)
  144. return int64(f.off), nil
  145. default:
  146. return int64(f.off), fmt.Errorf("seek: invalid whence %d", whence)
  147. }
  148. }
  149. // HTTPFS implements a http.FileSystem backed by data in a DB.
  150. type HTTPFS struct {
  151. db *DB
  152. dir, get List
  153. }
  154. // NewHTTPFS returns a http.FileSystem backed by a result record set of query.
  155. // The record set provides two mandatory fields: path and content (the field
  156. // names are case sensitive). Type of name must be string and type of content
  157. // must be blob (ie. []byte). Field 'path' value is the "file" pathname, which
  158. // must be rooted; and field 'content' value is its "data".
  159. func (db *DB) NewHTTPFS(query string) (*HTTPFS, error) {
  160. if _, err := Compile(query); err != nil {
  161. return nil, err
  162. }
  163. dir, err := Compile(fmt.Sprintf("SELECT path FROM (%s) WHERE hasPrefix(path, $1)", query))
  164. if err != nil {
  165. return nil, err
  166. }
  167. get, err := Compile(fmt.Sprintf("SELECT content FROM (%s) WHERE path == $1", query))
  168. if err != nil {
  169. return nil, err
  170. }
  171. return &HTTPFS{db: db, dir: dir, get: get}, nil
  172. }
  173. // Open implements http.FileSystem. The name parameter represents a file path.
  174. // The elements in a file path are separated by slash ('/', U+002F) characters,
  175. // regardless of host operating system convention.
  176. func (f *HTTPFS) Open(name string) (http.File, error) {
  177. if filepath.Separator != '/' && strings.Contains(name, string(filepath.Separator)) ||
  178. strings.Contains(name, "\x00") {
  179. return nil, fmt.Errorf("invalid character in file path: %q", name)
  180. }
  181. name = path.Clean("/" + name)
  182. rs, _, err := f.db.Execute(nil, f.get, name)
  183. if err != nil {
  184. return nil, err
  185. }
  186. n := 0
  187. var fdata []byte
  188. if err = rs[0].Do(false, func(data []interface{}) (more bool, err error) {
  189. switch n {
  190. case 0:
  191. var ok bool
  192. fdata, ok = data[0].([]byte)
  193. if !ok {
  194. return false, fmt.Errorf("open: expected blob, got %T", data[0])
  195. }
  196. n++
  197. return true, nil
  198. default:
  199. return false, fmt.Errorf("open: more than one result was returned for %s", name)
  200. }
  201. }); err != nil {
  202. return nil, err
  203. }
  204. if n == 1 { // file found
  205. return &HTTPFile{name: name, isFile: true, content: fdata}, nil
  206. }
  207. dirName := name
  208. if dirName[len(dirName)-1] != filepath.Separator {
  209. dirName += string(filepath.Separator)
  210. }
  211. // Open("/a/b"): {/a/b/c.x,/a/b/d.x,/a/e.x,/a/b/f/g.x} -> {c.x,d.x,f}
  212. rs, _, err = f.db.Execute(nil, f.dir, dirName)
  213. if err != nil {
  214. return nil, err
  215. }
  216. n = 0
  217. r := &HTTPFile{name: dirName}
  218. m := map[string]bool{}
  219. x := len(dirName)
  220. if err = rs[0].Do(false, func(data []interface{}) (more bool, err error) {
  221. n++
  222. switch name := data[0].(type) {
  223. case string:
  224. if filepath.Separator != '/' && strings.Contains(name, string(filepath.Separator)) ||
  225. strings.Contains(name, "\x00") {
  226. return false, fmt.Errorf("invalid character in file path: %q", name)
  227. }
  228. name = path.Clean("/" + name)
  229. rest := name[x:]
  230. parts := strings.Split(rest, "/")
  231. if len(parts) == 0 {
  232. return true, nil
  233. }
  234. nm := parts[0]
  235. switch len(parts) {
  236. case 1: // file
  237. r.dirEntries = append(r.dirEntries, &HTTPFile{isFile: true, name: nm})
  238. default: // directory
  239. if !m[nm] {
  240. r.dirEntries = append(r.dirEntries, dirEntry(nm))
  241. }
  242. m[nm] = true
  243. }
  244. return true, nil
  245. default:
  246. return false, fmt.Errorf("expected string path, got %T(%v)", name, name)
  247. }
  248. }); err != nil {
  249. return nil, err
  250. }
  251. if n != 0 {
  252. return r, nil
  253. }
  254. return nil, os.ErrNotExist
  255. }