plugins/file/file.go

593 lines
16 KiB
Go

package file
import (
"apigo.cloud/git/apigo/plugin"
"bufio"
"errors"
"github.com/ssgo/u"
"gopkg.in/yaml.v3"
"os"
"path"
"runtime"
"sort"
"strings"
"sync"
)
var _allowPaths = make([]string, 0)
var _allowExtensions = make([]string, 0)
var notAllowMessage = ""
var fileConfigLock = sync.RWMutex{}
var lockFile = func(f *os.File) {}
var unlockFile = func(f *os.File) {}
type File struct {
name string
fd *os.File
}
type FileInfo struct {
Name string
Mtime int64
IsDir bool
Size int64
}
func init() {
plugin.Register(plugin.Plugin{
Id: "file",
Name: "文件操作",
ConfigSample: `allowPaths: # 允许操作的文件路径
- /
allowExtensions: # 允许操作的文件后缀,以.开头,例如 .json .txt .db
- .json
- .txt
notAllowMessage: no access for file # 当文件路径或文件后缀不被允许时返回的错误信息
`,
Init: func(conf map[string]interface{}) {
newAllowPaths := make([]string, 0)
newAllowExtensions := make([]string, 0)
newNotAllowMessage := "file not allow to access"
if conf["allowPaths"] != nil {
u.Convert(conf["allowPaths"], &newAllowPaths)
}
if conf["allowExtensions"] != nil {
u.Convert(conf["allowExtensions"], &newAllowExtensions)
}
if conf["notAllowMessage"] != nil {
newNotAllowMessage = u.String(conf["notAllowMessage"])
}
fileConfigLock.Lock()
_allowPaths = newAllowPaths
_allowExtensions = newAllowExtensions
notAllowMessage = newNotAllowMessage
fileConfigLock.Unlock()
},
Objects: map[string]interface{}{
// list 列出目录下的文件
// * dirname 目录名称
// list sortBy 排序依据[name|mtime|size],默认使用名称排序
// list limit 返回指定数量,默认返回全部
// list return 文件列表{name:文件名,mtime:最后修改时间,size:文件尺寸}
"list": func(dirname string, sortBy *string, limit *int) ([]FileInfo, error) {
dirname = fixPath(dirname)
if !checkDirAllow(dirname) {
return nil, errors.New(getNotAllowMessage(dirname))
}
u.CheckPath(path.Join(dirname, "_"))
if d, err := os.Open(dirname); err == nil {
out := make([]FileInfo, 0)
if files, err := d.Readdir(-1); err == nil {
for _, f := range files {
if !strings.HasPrefix(f.Name(), ".") {
out = append(out, FileInfo{
Name: f.Name(),
Mtime: f.ModTime().Unix(),
IsDir: f.IsDir(),
Size: f.Size(),
})
}
}
}
_ = d.Close()
if sortBy != nil {
sort.Slice(out, func(i, j int) bool {
if *sortBy == "mtime" {
return out[i].Mtime > out[j].Mtime
} else if *sortBy == "size" {
return out[i].Size > out[j].Size
} else {
return out[i].Name < out[j].Name
}
})
}
if limit != nil && *limit > 0 && *limit < len(out) {
return out[0:*limit], nil
}
return out, nil
} else {
return []FileInfo{}, err
}
},
// exists 判断文件是否存在
// exists return 是否存在
"exists": func(filename string) bool {
fi, err := os.Stat(filename)
return err == nil && fi != nil
},
// isDir 判断是否文件夹
// isDir return 是否文件夹
"isDir": func(filename string) (bool, error) {
filename = fixPath(filename)
if fi, err := getFileStat(filename); err == nil {
return fi.IsDir(), nil
} else {
return false, err
}
},
// getModTime 返回文件的修改时间(时间戳格式)
// getModTime return 修改时间的时间戳
"getModTime": func(filename string) (int64, error) {
filename = fixPath(filename)
if fi, err := getFileStat(filename); err == nil {
return fi.ModTime().Unix(), nil
} else {
return 0, err
}
},
// getModTimeStr 返回文件的修改时间(字符串格式)
// getModTimeStr return 修改时间的字符串
"getModTimeStr": func(filename string) (string, error) {
filename = fixPath(filename)
if fi, err := getFileStat(filename); err == nil {
return fi.ModTime().Format("2006-01-02 15:04:05"), nil
} else {
return "", err
}
},
// makeDir 创建文件夹
"makeDir": func(dirname string) error {
dirname = fixPath(dirname)
if !checkDirAllow(dirname) {
return errors.New(getNotAllowMessage(dirname))
}
return os.MkdirAll(dirname, 0700)
},
// read 读取一个文件
// * filename 文件名
// read return 文件内容,字符串格式
"read": func(filename string) (string, error) {
filename = fixPath(filename)
if f, err := openFileForRead(filename); err == nil {
defer f.Close()
return f.ReadAll()
} else {
return "", err
}
},
// readBytes 读取一个二进制文件
// readBytes return 文件内容,二进制格式
"readBytes": func(filename string) ([]byte, error) {
filename = fixPath(filename)
if f, err := openFileForRead(filename); err == nil {
defer f.Close()
return f.ReadAllBytes()
} else {
return nil, err
}
},
// readFileLines 按行读取文件
// readFileLines return 文件内容,返回字符串数组
"readFileLines": func(filename string) ([]string, error) {
filename = fixPath(filename)
if f, err := openFileForRead(filename); err == nil {
defer f.Close()
return f.ReadLines()
} else {
return nil, err
}
},
// write 写入一个文件
// write content 文件内容,字符串格式
// write return 写入的字节数
"write": func(filename, content string) (int, error) {
filename = fixPath(filename)
if f, err := openFileForWrite(filename); err == nil {
defer f.Close()
return f.Write(content)
} else {
return 0, err
}
},
// writeBytes 写入一个二进制文件
// writeBytes content 文件内容,二进制格式
// writeBytes return 写入的字节数
"writeBytes": func(filename string, content []byte) (int, error) {
filename = fixPath(filename)
if f, err := openFileForWrite(filename); err == nil {
defer f.Close()
return f.WriteBytes(content)
} else {
return 0, err
}
},
"openForRead": openFileForRead,
"openForWrite": openFileForWrite,
"openForAppend": openFileForAppend,
"open": openFile,
// remove 删除文件
"remove": func(filename string) error {
filename = fixPath(filename)
fi, err := getFileStat(filename)
if err == nil && fi.IsDir() {
if !checkDirAllow(filename) {
return errors.New(getNotAllowMessage(filename))
}
return os.RemoveAll(filename)
} else {
if !checkFileAllow(filename) {
return errors.New(getNotAllowMessage(filename))
}
return os.Remove(filename)
}
},
// rename 修改文件名
// * fileOldName 旧文件
// * fileNewName 新文件
"rename": func(fileOldName, fileNewName string) error {
fi, err := getFileStat(fileOldName)
if err == nil && fi.IsDir() {
if !checkDirAllow(fileOldName) {
return errors.New(getNotAllowMessage(fileOldName))
}
if !checkDirAllow(fileNewName) {
return errors.New(getNotAllowMessage(fileNewName))
}
} else {
if !checkFileAllow(fileOldName) {
return errors.New(getNotAllowMessage(fileOldName))
}
if !checkFileAllow(fileNewName) {
return errors.New(getNotAllowMessage(fileNewName))
}
}
return os.Rename(fileOldName, fileNewName)
},
// copy 复制文件
"copy": func(fileOldName, fileNewName string) error {
fileNewName = fixPath(fileNewName)
newFI, _ := getFileStat(fileNewName)
fileOldName = fixPath(fileOldName)
fi, err := getFileStat(fileOldName)
if err == nil && fi.IsDir() {
if !checkDirAllow(fileOldName) {
return errors.New(getNotAllowMessage(fileOldName))
}
if !checkDirAllow(fileNewName) {
return errors.New(getNotAllowMessage(fileNewName))
}
//if strings.HasSuffix(fileNewName, "/") {
// u.CheckPath(path.Join(fileNewName, "a.txt"))
//}else{
// u.CheckPath(fileNewName)
//}
//_, err = u.RunCommand("cp", "-rf", fileOldName, fileNewName)
//return err
return copyDir(fileNewName, fileOldName)
} else {
if !checkFileAllow(fileOldName) {
return errors.New(getNotAllowMessage(fileOldName))
}
if !checkFileAllow(fileNewName) {
return errors.New(getNotAllowMessage(fileNewName))
}
if newFI != nil && newFI.IsDir() {
fileNewName = path.Join(fileNewName, path.Base(fileOldName))
}
return copyFile(fileNewName, fileOldName)
}
},
// saveJson 将对象存储为JSON格式的文件
// saveJson content 要存储的对象
"saveJson": func(filename string, content interface{}) error {
filename = fixPath(filename)
if !checkFileAllow(filename) {
return errors.New(getNotAllowMessage(filename))
}
return u.SaveJsonP(filename, content)
},
// saveYaml 将对象存储为YAML格式的文件
// saveYaml content 要存储的对象
"saveYaml": func(filename string, content interface{}) error {
filename = fixPath(filename)
if !checkFileAllow(filename) {
return errors.New(getNotAllowMessage(filename))
}
return u.SaveYaml(filename, content)
},
// loadJson 读取JSON格式的文件并转化为对象
// loadJson return 对象
"loadJson": func(filename string) (interface{}, error) {
filename = fixPath(filename)
if !checkFileAllow(filename) {
return nil, errors.New(getNotAllowMessage(filename))
}
var data interface{}
err := u.LoadJson(filename, &data)
return data, err
},
// loadYaml 读取YAML格式的文件并转化为对象
// loadYaml return 对象
"loadYaml": func(filename string) (interface{}, error) {
filename = fixPath(filename)
if !checkFileAllow(filename) {
return nil, errors.New(getNotAllowMessage(filename))
}
var data interface{}
buf, err := u.ReadFileBytes(filename)
if err == nil {
err = yaml.Unmarshal(buf, &data)
}
return data, err
},
},
})
}
func copyDir(dst, src string) error {
if d, err := os.Open(src); err == nil {
defer d.Close()
if files, err := d.Readdir(-1); err == nil {
for _, f := range files {
if f.IsDir() {
if err2 := copyDir(path.Join(dst, f.Name()), path.Join(src, f.Name())); err2 != nil {
return err2
}
} else {
if err2 := copyFile(path.Join(dst, f.Name()), path.Join(src, f.Name())); err2 != nil {
return err2
}
}
}
}
return nil
} else {
return err
}
}
func copyFile(dst, src string) error {
if f, err := openFileForRead(src); err == nil {
defer f.Close()
if f2, err2 := openFileForWrite(dst); err == nil {
defer f2.Close()
for {
if buf, err3 := f.ReadBytes(10240); err3 != nil {
break
} else {
_, _ = f2.WriteBytes(buf)
}
}
return nil
} else {
return err2
}
} else {
return err
}
}
func fixPath(filename string) string {
if runtime.GOOS == "windows" {
return strings.ReplaceAll(filename, "/", "\\")
}
return filename
}
func getFileStat(filename string) (os.FileInfo, error) {
fi, err := os.Stat(filename)
if err == nil && fi.IsDir() {
if !checkDirAllow(filename) {
return nil, errors.New(getNotAllowMessage(filename))
}
} else {
if !checkFileAllow(filename) {
return nil, errors.New(getNotAllowMessage(filename))
}
}
return fi, err
}
// openFileForRead 打开一个用于读取的文件,若不存在会抛出异常
// openFileForRead return 文件对象,请务必在使用完成后关闭文件
func openFileForRead(filename string) (*File, error) {
return _openFile(filename, os.O_RDONLY, 0400)
}
// openFileForWrite 打开一个用于写入的文件,若不存在会自动创建
// openFileForWrite return 文件对象,请务必在使用完成后关闭文件
func openFileForWrite(filename string) (*File, error) {
return _openFile(filename, os.O_CREATE|os.O_WRONLY|os.O_TRUNC, 0600)
}
// openFileForAppend 打开一个用于追加写入的文件,若不存在会自动创建
// openFileForAppend return 文件对象,请务必在使用完成后关闭文件
func openFileForAppend(filename string) (*File, error) {
return _openFile(filename, os.O_CREATE|os.O_WRONLY|os.O_APPEND, 0600)
}
// openFile 打开一个用于追加写入的文件,若不存在会自动创建
// openFile return 文件对象,请务必在使用完成后关闭文件
func openFile(filename string) (*File, error) {
return _openFile(filename, os.O_CREATE|os.O_RDWR|os.O_SYNC, 0600)
}
func _openFile(filename string, flag int, perm os.FileMode) (*File, error) {
if !checkFileAllow(filename) {
return nil, errors.New(getNotAllowMessage(filename))
}
u.CheckPath(filename)
fd, err := os.OpenFile(filename, flag, perm)
if err != nil {
return nil, err
}
lockFile(fd)
return &File{name: filename, fd: fd}, nil
}
// Close 关闭文件
func (f *File) Close() error {
unlockFile(f.fd)
return f.fd.Close()
}
// Read 从文件中读取指定长度的内容
// * size 长度
// Read return 读取的内容,字符串格式
func (f *File) Read(size int) (string, error) {
str, err := f.ReadBytes(size)
return string(str), err
}
// ReadBytes 从二进制文件中读取指定长度的内容
// ReadBytes return 读取的内容,二进制格式
func (f *File) ReadBytes(size int) ([]byte, error) {
buf := make([]byte, size)
n, err := f.fd.Read(buf)
if err != nil {
return nil, err
}
return buf[0:n], nil
}
// ReadAll 从文件中读取全部内容
// ReadAll return 读取的内容,字符串格式
func (f *File) ReadAll() (string, error) {
str, err := f.ReadAllBytes()
return string(str), err
}
// ReadAllBytes 从二进制文件中读取全部内容
// ReadAllBytes return 读取的内容,二进制格式
func (f *File) ReadAllBytes() ([]byte, error) {
var maxLen int
if fi, _ := os.Stat(f.name); fi != nil {
maxLen = int(fi.Size())
} else {
maxLen = 1024000
}
return f.ReadBytes(maxLen)
}
// ReadLines 逐行文件中读取全部内容
// ReadLines return 读取的内容,字符串数组格式
func (f *File) ReadLines() ([]string, error) {
outs := make([]string, 0)
inputReader := bufio.NewReader(f.fd)
for {
line, err := inputReader.ReadString('\n')
line = strings.TrimRight(line, "\r\n")
outs = append(outs, line)
if err != nil {
break
}
}
return outs, nil
}
// Write 写入字符串
// Write return 写入的长度
func (f *File) Write(content string) (int, error) {
return f.WriteBytes([]byte(content))
}
// WriteBytes 写入二进制
// WriteBytes return 写入的长度
func (f *File) WriteBytes(content []byte) (int, error) {
return f.fd.Write(content)
}
// SeekStart 将文件指针移动到开头
func (f *File) SeekStart() error {
_, err := f.fd.Seek(0, 0)
return err
}
// SeekEnd 将文件指针移动到末尾
func (f *File) SeekEnd() error {
_, err := f.fd.Seek(0, 2)
return err
}
// Seek 将文件指针移动到指定位置(从文件开头计算)
func (f *File) Seek(offset int64) error {
_, err := f.fd.Seek(offset, 0)
return err
}
func getNotAllowMessage(filename string) string {
fileConfigLock.RLock()
defer fileConfigLock.RUnlock()
return notAllowMessage + ": " + filename
}
func getAllowPaths() []string {
fileConfigLock.RLock()
defer fileConfigLock.RUnlock()
allowPaths := make([]string, len(_allowPaths))
for i, v := range _allowPaths {
allowPaths[i] = v
}
return allowPaths
}
func getAllowExtensions() []string {
fileConfigLock.RLock()
defer fileConfigLock.RUnlock()
allowExtensions := make([]string, len(_allowExtensions))
for i, v := range _allowExtensions {
allowExtensions[i] = v
}
return allowExtensions
}
func checkDirAllow(filename string) bool {
allowPaths := getAllowPaths()
if len(allowPaths) > 0 {
ok := false
for _, allowPath := range allowPaths {
//fmt.Println(">>>", filename, allowPath, strings.HasPrefix(filename, allowPath))
if strings.HasPrefix(filename, allowPath) {
ok = true
break
}
}
if !ok {
return false
}
}
return true
}
func checkFileAllow(filename string) bool {
if !checkDirAllow(filename) {
return false
}
allowExtensions := getAllowExtensions()
if len(allowExtensions) > 0 {
ok := false
for _, allowExtension := range allowExtensions {
//fmt.Println(">>>", filename, allowExtension, strings.HasSuffix(filename, allowExtension))
if strings.HasSuffix(filename, allowExtension) {
ok = true
break
}
}
if !ok {
return false
}
}
return true
}