diff --git a/backend/global/file.go b/backend/global/file.go index b629760..ba02344 100644 --- a/backend/global/file.go +++ b/backend/global/file.go @@ -1,9 +1,17 @@ package global import ( + "crypto/md5" + "encoding/binary" + "errors" + "io" "os" + "regexp" "strconv" + "strings" "time" + + "github.com/fumiama/go-docx" ) const ( @@ -11,6 +19,10 @@ const ( FileTableQuestion = "question" ) +var ( + ErrMajorSplitsTooShort = errors.New("major splits too short") +) + // PaperType [4 开 一页纸 闭] [4 上下] [4 中末] [4 AB] type PaperType uint16 @@ -161,6 +173,144 @@ type File struct { Questions []byte // Questions is for json struct QuestionJSON } +// AddFile from FileFolder+tempath and copy it to File.Path. +// The para res must belong to a valid user +func (f *FileDatabase) AddFile(tempath string, reg *Regex) (*File, error) { + user, err := UserDB.GetUserByID(reg.ID) + if err != nil { + return nil, err + } + if !user.IsFileManager() { + return nil, ErrInvalidRole + } + if strings.Contains(tempath, "..") { + return nil, os.ErrNotExist + } + tempath = FileFolder + tempath + docf, err := os.Open(tempath) + if err != nil { + return nil, err + } + defer docf.Close() + h := md5.New() + _, err = io.Copy(h, docf) + if err != nil { + return nil, err + } + var buf [md5.Size]byte + id := binary.LittleEndian.Uint64(h.Sum(buf[:0])) + _, err = docf.Seek(0, io.SeekStart) + if err != nil { + return nil, err + } + stat, err := docf.Stat() + if err != nil { + return nil, err + } + sz := stat.Size() + doc, err := docx.Parse(docf, sz) + if err != nil { + return nil, err + } + majorre, err := regexp.Compile(reg.Major) + if err != nil { + return nil, err + } + docs := doc.SplitByParagraph(docx.SplitDocxByPlainTextRegex(majorre)) + if len(docs) < 2 { + return nil, ErrMajorSplitsTooShort + } + file := &File{ + ID: id, + UID: *user.ID, + UpTime: time.Now().Unix(), + Size: sz, + } + titlere, err := regexp.Compile(reg.Title) + if err != nil { + return nil, err + } + classre, err := regexp.Compile(reg.Class) + if err != nil { + return nil, err + } + opclre, err := regexp.Compile(reg.OpenCl) + if err != nil { + return nil, err + } + datere, err := regexp.Compile(reg.Date) + if err != nil { + return nil, err + } + timere, err := regexp.Compile(reg.Time) + if err != nil { + return nil, err + } + ratere, err := regexp.Compile(reg.Rate) + if err != nil { + return nil, err + } + for _, it := range docs[0].Document.Body.Items { + if p, ok := it.(*docx.Paragraph); ok { + text := p.String() + title := titlere.FindStringSubmatch(text) + if len(title) >= 5 { + years, semesters, mfs, abs := title[1], title[2], title[3], title[4] + y, err := strconv.Atoi(years) + if err != nil { + return nil, err + } + file.Year = StudyYear(y) + if len(semesters) > 0 { + file.Type = file.Type.SetFirstSecond(semesters[0]) + } + file.Type = file.Type.SetMiddleFinal(mfs) + if len(abs) > 0 { + file.Type = file.Type.SetAB(abs[0]) + } + } + class := classre.FindStringSubmatch(text) + if len(class) >= 2 { + file.Class = class[1] + } + opcl := opclre.FindStringSubmatch(text) + if len(opcl) >= 2 { + file.Type = file.Type.SetOpenClose(opcl[1]) + } + date := datere.FindStringSubmatch(text) + if len(date) >= 4 { + y, m, d := date[1], date[2], date[3] + if y != "" && m != "" && d != "" { + yyyy, err := strconv.ParseUint(y, 10, 64) + if err == nil && yyyy > 1600 { + mm, err := strconv.ParseUint(m, 10, 64) + if err == nil && mm >= 1 && mm <= 12 { + dd, err := strconv.ParseUint(d, 10, 64) + if err == nil && dd >= 1 && dd <= 31 { + file.Date = uint32(yyyy*10000 + mm*100 + dd) + } + } + } + } + } + times := timere.FindStringSubmatch(text) + if len(times) >= 2 { + min, err := strconv.Atoi(times[1]) + if err == nil && min > 0 { + file.Time = time.Minute * time.Duration(min) + } + } + rate := ratere.FindStringSubmatch(text) + if len(rate) >= 2 { + file.Rate = rate[1] + } + } + } + docs = docs[1:] + + return file, nil +} + // QuestionJSON is the struct representation of File.Questions type QuestionJSON struct { Name string `json:"name"` // Name is name or Question ID diff --git a/backend/global/regex.go b/backend/global/regex.go index 5b2f5b5..c28b79a 100644 --- a/backend/global/regex.go +++ b/backend/global/regex.go @@ -9,7 +9,7 @@ import ( // Regex stores user's config of splitting docx file type Regex struct { ID int // ID is User(ID) - Title string // Title default `.*(\d{4})\s*-.*学年.*(\d).*([中末]).*([AB])\s*卷` + Title string // Title default `.*(\d{4})\s*-.*学年.*(\d?).*([中末]?).*([AB]?)\s*卷` Class string // Class default `考试科目:\s*(\S+)\s*` OpenCl string // OpenCl default `考试形式:\s*(\S+)\s*` Date string // Date default `考试日期:\s*(\d+)\s*年\s*(\d+)\s*月\s*(\d+)\s*日` @@ -20,7 +20,7 @@ type Regex struct { } func newRegex() (reg Regex) { - reg.Title = `.*(\d{4})\s*-.*学年.*(\d).*([中末]).*([AB])\s*卷` + reg.Title = `.*(\d{4})\s*-.*学年.*(\d?).*([中末]?).*([AB]?)\s*卷` reg.Class = `考试科目:\s*(\S+)\s*` reg.OpenCl = `考试形式:\s*(\S+)\s*` reg.Date = `考试日期:\s*(\d+)\s*年\s*(\d+)\s*月\s*(\d+)\s*日` diff --git a/backend/global/user.go b/backend/global/user.go index 6d6b44b..871d547 100644 --- a/backend/global/user.go +++ b/backend/global/user.go @@ -4,6 +4,7 @@ import ( "errors" "os" "strconv" + "strings" "time" "github.com/fumiama/paper-manager/backend/utils" @@ -23,6 +24,7 @@ var ( ErrEmptyName = errors.New("empty name") ErrInvalidUsersCount = errors.New("invalid users count") ErrInvalidUserID = errors.New("invalid user ID") + ErrInvalidAvatar = errors.New("invalid avatar") ErrEmptyUserID = errors.New("empty user ID") ErrEmptyContact = errors.New("empty contact") ErrUsernameExists = errors.New("username exists") @@ -147,6 +149,9 @@ func (u *UserDatabase) UpdateUserInfo(id int, opname, nick, avtr, desc string) e user.Nick = nick } if avtr != "" { + if strings.Contains(avtr, "..") { + return ErrInvalidAvatar + } user.Avtr = avtr } if desc != "" { diff --git a/backend/user.go b/backend/user.go index f0c026a..7cfdcf6 100644 --- a/backend/user.go +++ b/backend/user.go @@ -179,7 +179,7 @@ func setUserInfo(id int, nick, desc, avtr *string) error { } if a == "" { *avtr = user.Avtr - } else if utils.IsNotExist(global.DataFolder + a) { + } else if strings.Contains(a, "..") || utils.IsNotExist(global.DataFolder+a) { return os.ErrNotExist } if a == user.Avtr { diff --git a/go.mod b/go.mod index f5f5160..87587fc 100644 --- a/go.mod +++ b/go.mod @@ -7,6 +7,7 @@ require ( github.com/FloatTech/ttl v0.0.0-20220715042055-15612be72f5b github.com/RomiChan/syncx v0.0.0-20221202055724-5f842c53020e github.com/fumiama/go-base16384 v1.6.4 + github.com/fumiama/go-docx v0.0.0-20230310052825-daf7190ea69b github.com/fumiama/imgsz v0.0.2 github.com/sirupsen/logrus v1.9.0 ) diff --git a/go.sum b/go.sum index ff38d52..03ff942 100644 --- a/go.sum +++ b/go.sum @@ -11,6 +11,8 @@ github.com/fumiama/bigfft v0.0.0-20211011143303-6e0bfa3c836b h1:Zt3pFQditAdWTHCO github.com/fumiama/bigfft v0.0.0-20211011143303-6e0bfa3c836b/go.mod h1:qqbHyh8v60DhA7CoWK5oRCqLrMHRGoxYCSS9EjAz6Eo= github.com/fumiama/go-base16384 v1.6.4 h1:rYDRwD/th2cG4U7QLokpzmST1cCxZGXtHmolOUePt5o= github.com/fumiama/go-base16384 v1.6.4/go.mod h1:OEn+947GV5gsbTAnyuUW/SrfxJYUdYupSIQXOuGOcXM= +github.com/fumiama/go-docx v0.0.0-20230310052825-daf7190ea69b h1:1KYtEitNxRbh+O8mgGlji4nQKzf7SQHYxiuqDAwRCc8= +github.com/fumiama/go-docx v0.0.0-20230310052825-daf7190ea69b/go.mod h1:ssRF0IaB1hCcKIObp3FkZOsjTcAHpgii70JelNb4H8M= github.com/fumiama/imgsz v0.0.2 h1:fAkC0FnIscdKOXwAxlyw3EUba5NzxZdSxGaq3Uyfxak= github.com/fumiama/imgsz v0.0.2/go.mod h1:dR71mI3I2O5u6+PCpd47M9TZptzP+39tRBcbdIkoqM4= github.com/fumiama/sqlite3 v1.20.0-with-win386 h1:ZR1AXGBEtkfq9GAXehOVcwn+aaCG8itrkgEsz4ggx5k=