fs.go 3.7 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146
  1. // Copyright 2014 Google Inc. All Rights Reserved.
  2. //
  3. // Licensed under the Apache License, Version 2.0 (the "License");
  4. // you may not use this file except in compliance with the License.
  5. // You may obtain a copy of the License at
  6. //
  7. // http://www.apache.org/licenses/LICENSE-2.0
  8. //
  9. // Unless required by applicable law or agreed to in writing, software
  10. // distributed under the License is distributed on an "AS IS" BASIS,
  11. // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  12. // See the License for the specific language governing permissions and
  13. // limitations under the License.
  14. // Package fs contains an HTTP file system that works with zip contents.
  15. package fs
  16. import (
  17. "archive/zip"
  18. "bytes"
  19. "errors"
  20. "fmt"
  21. "io/ioutil"
  22. "net/http"
  23. "os"
  24. "strings"
  25. )
  26. var zipData string
  27. // file holds unzipped read-only file contents and file metadata.
  28. type file struct {
  29. os.FileInfo
  30. data []byte
  31. }
  32. type statikFS struct {
  33. files map[string]file
  34. }
  35. // Register registers zip contents data, later used to initialize
  36. // the statik file system.
  37. func Register(data string) {
  38. zipData = data
  39. }
  40. // New creates a new file system with the registered zip contents data.
  41. // It unzips all files and stores them in an in-memory map.
  42. func New() (http.FileSystem, error) {
  43. if zipData == "" {
  44. return nil, errors.New("statik/fs: no zip data registered")
  45. }
  46. zipReader, err := zip.NewReader(strings.NewReader(zipData), int64(len(zipData)))
  47. if err != nil {
  48. return nil, err
  49. }
  50. files := make(map[string]file)
  51. for _, zipFile := range zipReader.File {
  52. unzipped, err := unzip(zipFile)
  53. if err != nil {
  54. return nil, fmt.Errorf("statik/fs: error unzipping file %q: %s", zipFile.Name, err)
  55. }
  56. files["/"+zipFile.Name] = file{
  57. FileInfo: zipFile.FileInfo(),
  58. data: unzipped,
  59. }
  60. }
  61. return &statikFS{files: files}, nil
  62. }
  63. func unzip(zf *zip.File) ([]byte, error) {
  64. rc, err := zf.Open()
  65. if err != nil {
  66. return nil, err
  67. }
  68. defer rc.Close()
  69. return ioutil.ReadAll(rc)
  70. }
  71. // Open returns a file matching the given file name, or os.ErrNotExists if
  72. // no file matching the given file name is found in the archive.
  73. // If a directory is requested, Open returns the file named "index.html"
  74. // in the requested directory, if that file exists.
  75. func (fs *statikFS) Open(name string) (http.File, error) {
  76. name = strings.Replace(name, "//", "/", -1)
  77. f, ok := fs.files[name]
  78. if ok {
  79. return newHTTPFile(f, false), nil
  80. }
  81. // The file doesn't match, but maybe it's a directory,
  82. // thus we should look for index.html
  83. indexName := strings.Replace(name+"/index.html", "//", "/", -1)
  84. f, ok = fs.files[indexName]
  85. if !ok {
  86. return nil, os.ErrNotExist
  87. }
  88. return newHTTPFile(f, true), nil
  89. }
  90. func newHTTPFile(file file, isDir bool) *httpFile {
  91. return &httpFile{
  92. file: file,
  93. reader: bytes.NewReader(file.data),
  94. isDir: isDir,
  95. }
  96. }
  97. // httpFile represents an HTTP file and acts as a bridge
  98. // between file and http.File.
  99. type httpFile struct {
  100. file
  101. reader *bytes.Reader
  102. isDir bool
  103. }
  104. // Read reads bytes into p, returns the number of read bytes.
  105. func (f *httpFile) Read(p []byte) (n int, err error) {
  106. return f.reader.Read(p)
  107. }
  108. // Seek seeks to the offset.
  109. func (f *httpFile) Seek(offset int64, whence int) (ret int64, err error) {
  110. return f.reader.Seek(offset, whence)
  111. }
  112. // Stat stats the file.
  113. func (f *httpFile) Stat() (os.FileInfo, error) {
  114. return f, nil
  115. }
  116. // IsDir returns true if the file location represents a directory.
  117. func (f *httpFile) IsDir() bool {
  118. return f.isDir
  119. }
  120. // Readdir returns an empty slice of files, directory
  121. // listing is disabled.
  122. func (f *httpFile) Readdir(count int) ([]os.FileInfo, error) {
  123. // directory listing is disabled.
  124. return make([]os.FileInfo, 0), nil
  125. }
  126. func (f *httpFile) Close() error {
  127. return nil
  128. }