123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287 |
- package vessel
- import (
- "archive/zip"
- "encoding/binary"
- "fmt"
- "io"
- "io/fs"
- "os"
- "path/filepath"
- "strings"
- "time"
- )
- const (
- BundleMagic = uint64(0xDEADBEEFDEAD0001)
- // 1. 0. 0. 0
- BundleVersion1 = uint64(0x0001000000000000)
- )
- // Bundle wraps the contents of a zip file for use
- type Bundle struct {
- offset int64
- size int64
- version uint64
- magic uint64
- file *os.File
- cacheTime time.Time
- }
- // Close close the bundle
- func (b *Bundle) Close() error {
- return b.file.Close()
- }
- // Size returns the size of the content in the bundle
- func (b *Bundle) Size() int64 {
- return b.size
- }
- // Version returns the bundles version info
- func (b *Bundle) Version() uint64 {
- return b.version
- }
- // List lists files in the bundle
- func (b *Bundle) List() []string {
- zr, err := zip.NewReader(b, b.Size())
- if err != nil {
- return nil
- }
- //name = path.Join(zr.File[0].Name, name)
- files := make([]string, 0, len(zr.File))
- for idx := range zr.File {
- files = append(files, zr.File[idx].Name)
- }
- return files
- }
- func (b *Bundle) FileInfo() map[string]fs.FileInfo {
- zr, err := zip.NewReader(b, b.Size())
- if err != nil {
- return nil
- }
- files := make(map[string]fs.FileInfo)
- for idx := range zr.File {
- files[zr.File[idx].Name] = zr.File[idx].FileInfo()
- }
- return files
- }
- // ReadAt implements io.ReaderAt
- func (b *Bundle) ReadAt(p []byte, off int64) (n int, err error) {
- off += b.offset
- return b.file.ReadAt(p, off)
- }
- // WithTimeOverride override the time on files in the filesystem ( to subvert caches)
- // with this set when a FS is created all files/directories will report this date
- func (b *Bundle) WithTimeOverride(modTime time.Time) {
- b.cacheTime = modTime
- }
- // FileSystem returns a handle to an bundle as a FileSystem that implements http.Filesystem
- func (b *Bundle) FileSystem() *FileSystem {
- f := &FileSystem{
- b: b,
- }
- f.initCaches(b.cacheTime)
- return f
- }
- // tryLoadEmbeddedBundle loads the bundle from an embedded file
- func (b *Bundle) tryLoadEmbeddedBundle() error {
- // Check version 1
- _, err := b.file.Seek(int64(-24), io.SeekEnd)
- if err != nil {
- return err
- }
- err = binary.Read(b.file, binary.LittleEndian, &b.size)
- if err != nil {
- return err
- }
- err = binary.Read(b.file, binary.LittleEndian, &b.version)
- if err != nil {
- return err
- }
- err = binary.Read(b.file, binary.LittleEndian, &b.magic)
- if err != nil {
- return err
- }
- if b.magic != BundleMagic {
- return fmt.Errorf("not a vessel bundle")
- }
- if b.version != BundleVersion1 {
- return fmt.Errorf("unsupported vessel bundle version")
- }
- stat, err := b.file.Stat()
- if err != nil {
- return err
- }
- b.offset = stat.Size() - int64(24) - int64(b.size)
- return nil
- }
- // Decompress extracts the contents of the bundle into the path provided
- func (b *Bundle) Decompress(path string) error {
- zr, err := zip.NewReader(b, b.Size())
- if err != nil {
- return err
- }
- for _, f := range zr.File {
- fpath := filepath.Join(path, f.Name)
- // Check for ZipSlip. More Info: http://bit.ly/2MsjAWE
- if !strings.HasPrefix(fpath, filepath.Clean(path)+string(os.PathSeparator)) {
- return fmt.Errorf("illegal file path: %s", fpath)
- }
- if f.FileInfo().IsDir() {
- // Make Folder
- os.MkdirAll(fpath, os.ModePerm)
- continue
- }
- if err = os.MkdirAll(filepath.Dir(fpath), os.ModePerm); err != nil {
- return err
- }
- outFile, err := os.OpenFile(fpath, os.O_WRONLY|os.O_CREATE|os.O_TRUNC, f.Mode())
- if err != nil {
- return err
- }
- rc, err := f.Open()
- if err != nil {
- outFile.Close()
- return err
- }
- _, err = io.Copy(outFile, rc)
- outFile.Close()
- rc.Close()
- if err != nil {
- return err
- }
- }
- return nil
- }
- func concatenate(dst string, src string) error {
- log.Log("Attaching: %s -> %s\n", src, dst)
- srcIn, err := os.Open(src)
- if err != nil {
- return err
- }
- defer srcIn.Close()
- stat, err := srcIn.Stat()
- if err != nil {
- return err
- }
- dstOut, err := os.OpenFile(dst, os.O_APPEND|os.O_WRONLY, 0644)
- if err != nil {
- return err
- }
- defer dstOut.Close()
- dstOut.Seek(0, io.SeekEnd)
- _, err = io.Copy(dstOut, srcIn)
- if err != nil {
- return fmt.Errorf("error glueing: %w", err)
- }
- err = binary.Write(dstOut, binary.LittleEndian, uint64(stat.Size()))
- if err != nil {
- return fmt.Errorf("storing bundle size: %w", err)
- }
- err = binary.Write(dstOut, binary.LittleEndian, BundleVersion1)
- if err != nil {
- return fmt.Errorf("storing bundle version: %w", err)
- }
- err = binary.Write(dstOut, binary.LittleEndian, BundleMagic)
- if err != nil {
- return fmt.Errorf("storing bundle magic: %w", err)
- }
- return err
- }
- func packInto(dst *os.File, src string, filter func(file string) bool) error {
- // Create a new zip archive.
- w := zip.NewWriter(dst)
- src = filepath.Clean(src)
- var walker func(path string, info os.FileInfo, err error) error
- walker = func(path string, info os.FileInfo, err error) error {
- if filter != nil {
- if filter(path) == false {
- return nil
- }
- }
- // truncated path
- spath := strings.TrimPrefix(path, src)
- log.Log("Crawling: [%s] %s as %s \n", src, path, spath)
- if err != nil {
- return err
- }
- if info.IsDir() {
- return nil
- }
- file, err := os.Open(path)
- if err != nil {
- return err
- }
- defer file.Close()
- header, err := zip.FileInfoHeader(info)
- if err != nil {
- return err
- }
- header.Name = spath
- fileWriter, err := w.CreateHeader(header)
- if err != nil {
- return err
- }
- _, err = io.Copy(fileWriter, file)
- if err != nil {
- return err
- }
- return nil
- }
- err := filepath.Walk(src, walker)
- if err != nil {
- w.Close()
- return err
- }
- // Make sure to check the error on Close.
- return w.Close()
- }
|