bundle.go 5.5 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287
  1. package vessel
  2. import (
  3. "archive/zip"
  4. "encoding/binary"
  5. "fmt"
  6. "io"
  7. "io/fs"
  8. "os"
  9. "path/filepath"
  10. "strings"
  11. "time"
  12. )
  13. const (
  14. BundleMagic = uint64(0xDEADBEEFDEAD0001)
  15. // 1. 0. 0. 0
  16. BundleVersion1 = uint64(0x0001000000000000)
  17. )
  18. // Bundle wraps the contents of a zip file for use
  19. type Bundle struct {
  20. offset int64
  21. size int64
  22. version uint64
  23. magic uint64
  24. file *os.File
  25. cacheTime time.Time
  26. }
  27. // Close close the bundle
  28. func (b *Bundle) Close() error {
  29. return b.file.Close()
  30. }
  31. // Size returns the size of the content in the bundle
  32. func (b *Bundle) Size() int64 {
  33. return b.size
  34. }
  35. // Version returns the bundles version info
  36. func (b *Bundle) Version() uint64 {
  37. return b.version
  38. }
  39. // List lists files in the bundle
  40. func (b *Bundle) List() []string {
  41. zr, err := zip.NewReader(b, b.Size())
  42. if err != nil {
  43. return nil
  44. }
  45. //name = path.Join(zr.File[0].Name, name)
  46. files := make([]string, 0, len(zr.File))
  47. for idx := range zr.File {
  48. files = append(files, zr.File[idx].Name)
  49. }
  50. return files
  51. }
  52. func (b *Bundle) FileInfo() map[string]fs.FileInfo {
  53. zr, err := zip.NewReader(b, b.Size())
  54. if err != nil {
  55. return nil
  56. }
  57. files := make(map[string]fs.FileInfo)
  58. for idx := range zr.File {
  59. files[zr.File[idx].Name] = zr.File[idx].FileInfo()
  60. }
  61. return files
  62. }
  63. // ReadAt implements io.ReaderAt
  64. func (b *Bundle) ReadAt(p []byte, off int64) (n int, err error) {
  65. off += b.offset
  66. return b.file.ReadAt(p, off)
  67. }
  68. // WithTimeOverride override the time on files in the filesystem ( to subvert caches)
  69. // with this set when a FS is created all files/directories will report this date
  70. func (b *Bundle) WithTimeOverride(modTime time.Time) {
  71. b.cacheTime = modTime
  72. }
  73. // FileSystem returns a handle to an bundle as a FileSystem that implements http.Filesystem
  74. func (b *Bundle) FileSystem() *FileSystem {
  75. f := &FileSystem{
  76. b: b,
  77. }
  78. f.initCaches(b.cacheTime)
  79. return f
  80. }
  81. // tryLoadEmbeddedBundle loads the bundle from an embedded file
  82. func (b *Bundle) tryLoadEmbeddedBundle() error {
  83. // Check version 1
  84. _, err := b.file.Seek(int64(-24), io.SeekEnd)
  85. if err != nil {
  86. return err
  87. }
  88. err = binary.Read(b.file, binary.LittleEndian, &b.size)
  89. if err != nil {
  90. return err
  91. }
  92. err = binary.Read(b.file, binary.LittleEndian, &b.version)
  93. if err != nil {
  94. return err
  95. }
  96. err = binary.Read(b.file, binary.LittleEndian, &b.magic)
  97. if err != nil {
  98. return err
  99. }
  100. if b.magic != BundleMagic {
  101. return fmt.Errorf("not a vessel bundle")
  102. }
  103. if b.version != BundleVersion1 {
  104. return fmt.Errorf("unsupported vessel bundle version")
  105. }
  106. stat, err := b.file.Stat()
  107. if err != nil {
  108. return err
  109. }
  110. b.offset = stat.Size() - int64(24) - int64(b.size)
  111. return nil
  112. }
  113. // Decompress extracts the contents of the bundle into the path provided
  114. func (b *Bundle) Decompress(path string) error {
  115. zr, err := zip.NewReader(b, b.Size())
  116. if err != nil {
  117. return err
  118. }
  119. for _, f := range zr.File {
  120. fpath := filepath.Join(path, f.Name)
  121. // Check for ZipSlip. More Info: http://bit.ly/2MsjAWE
  122. if !strings.HasPrefix(fpath, filepath.Clean(path)+string(os.PathSeparator)) {
  123. return fmt.Errorf("illegal file path: %s", fpath)
  124. }
  125. if f.FileInfo().IsDir() {
  126. // Make Folder
  127. os.MkdirAll(fpath, os.ModePerm)
  128. continue
  129. }
  130. if err = os.MkdirAll(filepath.Dir(fpath), os.ModePerm); err != nil {
  131. return err
  132. }
  133. outFile, err := os.OpenFile(fpath, os.O_WRONLY|os.O_CREATE|os.O_TRUNC, f.Mode())
  134. if err != nil {
  135. return err
  136. }
  137. rc, err := f.Open()
  138. if err != nil {
  139. outFile.Close()
  140. return err
  141. }
  142. _, err = io.Copy(outFile, rc)
  143. outFile.Close()
  144. rc.Close()
  145. if err != nil {
  146. return err
  147. }
  148. }
  149. return nil
  150. }
  151. func concatenate(dst string, src string) error {
  152. log.Log("Attaching: %s -> %s\n", src, dst)
  153. srcIn, err := os.Open(src)
  154. if err != nil {
  155. return err
  156. }
  157. defer srcIn.Close()
  158. stat, err := srcIn.Stat()
  159. if err != nil {
  160. return err
  161. }
  162. dstOut, err := os.OpenFile(dst, os.O_APPEND|os.O_WRONLY, 0644)
  163. if err != nil {
  164. return err
  165. }
  166. defer dstOut.Close()
  167. dstOut.Seek(0, io.SeekEnd)
  168. _, err = io.Copy(dstOut, srcIn)
  169. if err != nil {
  170. return fmt.Errorf("error glueing: %w", err)
  171. }
  172. err = binary.Write(dstOut, binary.LittleEndian, uint64(stat.Size()))
  173. if err != nil {
  174. return fmt.Errorf("storing bundle size: %w", err)
  175. }
  176. err = binary.Write(dstOut, binary.LittleEndian, BundleVersion1)
  177. if err != nil {
  178. return fmt.Errorf("storing bundle version: %w", err)
  179. }
  180. err = binary.Write(dstOut, binary.LittleEndian, BundleMagic)
  181. if err != nil {
  182. return fmt.Errorf("storing bundle magic: %w", err)
  183. }
  184. return err
  185. }
  186. func packInto(dst *os.File, src string, filter func(file string) bool) error {
  187. // Create a new zip archive.
  188. w := zip.NewWriter(dst)
  189. src = filepath.Clean(src)
  190. var walker func(path string, info os.FileInfo, err error) error
  191. walker = func(path string, info os.FileInfo, err error) error {
  192. if filter != nil {
  193. if filter(path) == false {
  194. return nil
  195. }
  196. }
  197. // truncated path
  198. spath := strings.TrimPrefix(path, src)
  199. log.Log("Crawling: [%s] %s as %s \n", src, path, spath)
  200. if err != nil {
  201. return err
  202. }
  203. if info.IsDir() {
  204. return nil
  205. }
  206. file, err := os.Open(path)
  207. if err != nil {
  208. return err
  209. }
  210. defer file.Close()
  211. header, err := zip.FileInfoHeader(info)
  212. if err != nil {
  213. return err
  214. }
  215. header.Name = spath
  216. fileWriter, err := w.CreateHeader(header)
  217. if err != nil {
  218. return err
  219. }
  220. _, err = io.Copy(fileWriter, file)
  221. if err != nil {
  222. return err
  223. }
  224. return nil
  225. }
  226. err := filepath.Walk(src, walker)
  227. if err != nil {
  228. w.Close()
  229. return err
  230. }
  231. // Make sure to check the error on Close.
  232. return w.Close()
  233. }