commit e0e19240b5be5204709f1ea5705c23ec6291b006 Author: Romain de Laage Date: Tue May 4 14:17:12 2021 +0200 Initial commit diff --git a/go.mod b/go.mod new file mode 100644 index 0000000..b48dafc --- /dev/null +++ b/go.mod @@ -0,0 +1,3 @@ +module main + +go 1.16 diff --git a/main.go b/main.go new file mode 100644 index 0000000..e20dd12 --- /dev/null +++ b/main.go @@ -0,0 +1,227 @@ +package main + +import ( + "fmt" + "os" + "archive/zip" + "bufio" + "strings" + "time" + "strconv" +) + +type BookInfo struct { + Title string + GpubVersion string + Index string + Author string + Language string + Charset string + Description string + Published string + PublishDate string + RevisionDate string + Copyright string + License string + Version string + Cover string +} + +var fileSystem map[string]*zip.File +var bookInfo BookInfo + +func main() { + if len(os.Args) < 2 { + fmt.Println("USAGE:", os.Args[0], "filename") + os.Exit(1) + } + + fmt.Println("Opening", os.Args[1]) + file, err := zip.OpenReader(os.Args[1]) + if err != nil { + fmt.Println("Failed to open", os.Args[1], err) + } + defer file.Close() + + fileSystem = make(map[string]*zip.File) + if !validate(&file.Reader) { + fmt.Println(os.Args[1], "could not be validated...") + os.Exit(1) + } else { + fmt.Println(os.Args[1], "has been validated") + printBookInfo() + } +} + +func printBookInfo() { + if bookInfo.Title != "" { + fmt.Println("Title:", bookInfo.Title) + } + if bookInfo.GpubVersion != "" { + fmt.Println("GpubVersion:", bookInfo.GpubVersion) + } +} + +func validate(archive *zip.Reader) bool { + isThereMetadata := false + + for _, f := range archive.File { // looking for metadata first, and put all files in map + if f.Name == "metadata.txt" { + if !isValidMetadata(f) { + fmt.Println("FATAL:", f.Name,"present but not valid") + return false + } + + isThereMetadata = true + } + + fileSystem[f.Name] = f + } + + if isThereMetadata { + if bookInfo.Index == "" { + fmt.Println("WARNING: No index provided, checking default index.gmi") + return isValidContent("index.gmi") + } else { + return isValidContent(bookInfo.Index) + } + } else { + fmt.Println("WARNING: This is not a book but a capsule archive") + + f, ok := fileSystem["index.gmi"] + if !ok { + fmt.Println("FATAL: This is a capsule archive but index.gmi is not present at root") + return false + } + + if f.FileHeader.FileInfo().IsDir() { + fmt.Println("FATAL:", f.Name, "is a directory") + return false + } + + return true + } +} + +func isValidMetadata(file *zip.File) bool { + isThereTitle := false + isThereGpubVersion := false + + if file.FileHeader.FileInfo().IsDir() { + fmt.Println("FATAL:", file.Name, "is a directory !") + return false + } + + reader, err := file.Open() + if err != nil { + fmt.Println("FATAL:", file.Name, "can't be opened") + return false + } + defer reader.Close() + + scanner := bufio.NewScanner(reader) + for scanner.Scan() { + line := scanner.Text() + if strings.Trim(line, " ") != "" { + columnPosition := strings.Index(line, ":") + if columnPosition == -1 { + fmt.Println("FATAL: invalid line in", file.Name, line) + return false + } + + key := strings.Trim(line[:columnPosition], " ") + value := strings.Trim(line[columnPosition+1:], " ") + + switch key { + case "title": + bookInfo.Title = value + isThereTitle = true + case "gpubVersion": + bookInfo.GpubVersion = value + isThereGpubVersion = true + case "index": + bookInfo.Index = value + case "author": + bookInfo.Author = value + case "language": + //todo is language tag valid ? BCP 47 + bookInfo.Language = value + case "charset": + bookInfo.Charset = value + case "description": + bookInfo.Description = value + case "published": + _, err := strconv.ParseUint(value, 10, 32) + if err != nil { + fmt.Println("FATAL: Bad format in", key, err) + } + bookInfo.Published = value + case "publishDate": + _, err := time.Parse(time.RFC3339, value) + if err != nil { + fmt.Println("FATAL: Bad date format in", key, err) + return false + } + bookInfo.PublishDate = value + case "revisionDate": + _, err := time.Parse(time.RFC3339, value) + if err != nil { + fmt.Println("FATAL: Bad date format in", key, err) + return false + } + bookInfo.RevisionDate = value + case "copyright": + bookInfo.Copyright = value + case "license": + bookInfo.License = value + case "version": + bookInfo.Version = value + case "cover": + bookInfo.Cover = value + default: + fmt.Println("FATAL: Unknown key in", file.Name, key) + return false + } + } + } + if err := scanner.Err(); err != nil { + fmt.Println("FATAL: error while scanning", file.Name, err) + return false + } + + if !isThereTitle { // only two fields are mandatory + fmt.Println("FATAL: No title provided !") + return false + } else if !isThereGpubVersion { + fmt.Println("FATAL: No GpubVersion provided !") + return false + } else { // the last thing we need to test is version validity + return isVersionValid() + } +} + +func isValidContent(filePath string) bool { + if filePath == "" || filePath == "/" { //we want index + filePath = "index.gmi" + } else if strings.HasPrefix(filePath, "/") { //we must ingore the leading slash + filePath = filePath[1:] + } + + file, ok := fileSystem[filePath] + if !ok { + fmt.Println("FATAL:", filePath, "doesn't exist !") + return false + } + + if file.FileHeader.FileInfo().IsDir() { //this is a dir, we look at index.gmi + return isValidContent(filePath + "index.gmi") + } + + //todo + + return true +} + +func isVersionValid() bool { + return bookInfo.GpubVersion == "1.0.0" +}