package encfs import ( "crypto/rand" "encoding/json" "errors" "io" "os" "syscall" "github.com/spf13/afero" ) const EncFileExt = ".__encfile" var ( ErrFileForbiddenFileExt = errors.New("File ext is forbidden") ) type EncFileMeta struct { Name string `json:"name"` Iv []byte `json:"iv"` } func newEncFileMeta(name string) (*EncFileMeta, error) { iv := make([]byte, 16) _, err := rand.Read(iv) if err != nil { return nil, err } encFileMeta := &EncFileMeta{ Name: name, Iv: iv, } encFileMetaName := name + EncFileExt encFileMetaFile, err := os.Create(encFileMetaName) if err != nil { return nil, err } defer func() { _ = encFileMetaFile.Close() }() encFileMetaBytes, err := marshalEncFileMeta(encFileMeta) if err != nil { return nil, err } _, err = encFileMetaFile.Write(encFileMetaBytes) if err != nil { return nil, err } return encFileMeta, nil } func openEncFileMeta(name string) (*EncFileMeta, error) { encFileMetaName := name + "." + EncFileExt encFileMetaFile, err := os.Open(encFileMetaName) if err != nil { if os.IsNotExist(err) { return nil, nil } return nil, err } defer func() { _ = encFileMetaFile.Close() }() encFileMetaBytes, err := io.ReadAll(encFileMetaFile) if err != nil { return nil, err } encFileMeta, err := unmarchalEncFileMeta(encFileMetaBytes) if err != nil { return nil, err } return encFileMeta, nil } func marshalEncFileMeta(encFileMeata *EncFileMeta) ([]byte, error) { return json.Marshal(encFileMeata) } func unmarchalEncFileMeta(data []byte) (*EncFileMeta, error) { var encFileMeta EncFileMeta err := json.Unmarshal(data, &encFileMeta) if err != nil { return nil, err } return &encFileMeta, nil } type EncFile struct { isDir bool closed bool encFileMeta *EncFileMeta file *os.File } func NewEncFile(name string, file *os.File, isCreate bool) (*EncFile, error) { fileInfo, err := file.Stat() if err != nil { return nil, err } isDir := fileInfo.IsDir() var encFileMeta *EncFileMeta = nil if !isDir { if isCreate { encFileMeta, err = newEncFileMeta(name) } else { encFileMeta, err = openEncFileMeta(name) } if err != nil { return nil, err } } return &EncFile{ isDir: isDir, closed: false, encFileMeta: encFileMeta, file: file, }, nil } func (f *EncFile) Close() error { if f.closed { return afero.ErrFileClosed } f.closed = true return f.file.Close() } func (f *EncFile) Read(p []byte) (n int, err error) { checkIsFileErr := f.checkIsFile() if checkIsFileErr != nil { return 0, checkIsFileErr } // TODO decrypt return f.file.Read(p) } func (f *EncFile) ReadAt(p []byte, off int64) (n int, err error) { checkIsFileErr := f.checkIsFile() if checkIsFileErr != nil { return 0, checkIsFileErr } // TODO decrypt return f.file.ReadAt(p, off) } func (f *EncFile) Seek(offset int64, whence int) (int64, error) { checkIsFileErr := f.checkIsFile() if checkIsFileErr != nil { return 0, checkIsFileErr } // TODO decrypt return f.file.Seek(offset, whence) } func (f *EncFile) Write(p []byte) (n int, err error) { checkIsFileErr := f.checkIsFile() if checkIsFileErr != nil { return 0, checkIsFileErr } // TODO encrypt return f.file.Write(p) } func (f *EncFile) WriteAt(p []byte, off int64) (n int, err error) { checkIsFileErr := f.checkIsFile() if checkIsFileErr != nil { return 0, checkIsFileErr } // TODO encrypt return f.file.WriteAt(p, off) } func (f *EncFile) Name() string { return f.file.Name() } func (f *EncFile) Readdir(count int) ([]os.FileInfo, error) { if f.closed { return nil, afero.ErrFileClosed } return f.file.Readdir(count) } func (f *EncFile) Readdirnames(n int) ([]string, error) { fi, err := f.Readdir(n) if err != nil { return nil, err } var names []string for _, f := range fi { names = append(names, f.Name()) } return names, nil } func (f *EncFile) Stat() (os.FileInfo, error) { return f.file.Stat() } func (f *EncFile) Sync() error { return f.file.Sync() } func (f *EncFile) Truncate(size int64) error { return f.file.Truncate(size) } func (f *EncFile) WriteString(s string) (ret int, err error) { // TODO encrypt return f.file.WriteString(s) } func (f *EncFile) checkIsFile() error { if f.closed { return afero.ErrFileClosed } if f.isDir { return syscall.EISDIR } return nil }