Skip to content

Commit

Permalink
maps: Improve the map packing routine. Add new noxtool.
Browse files Browse the repository at this point in the history
  • Loading branch information
dennwc committed Dec 23, 2023
1 parent ff20665 commit 2edb32a
Show file tree
Hide file tree
Showing 3 changed files with 180 additions and 38 deletions.
66 changes: 66 additions & 0 deletions cmd/noxtools/maps.go
Original file line number Diff line number Diff line change
@@ -1,14 +1,80 @@
package main

import (
"errors"
"fmt"
"os"
"path/filepath"
"strings"

"github.com/spf13/cobra"
"golang.org/x/exp/slices"

"github.com/noxworld-dev/opennox-lib/maps"
)

func init() {
cmd := &cobra.Command{
Use: "map command",
Short: "Tools for working with Nox maps",
Aliases: []string{"m", "maps"},
}
Root.AddCommand(cmd)

cmdCompress := &cobra.Command{
Use: "compress mapdir",
Short: "Compresses a Nox/OpenNox map to ZIP archive",
}
cmd.AddCommand(cmdCompress)
cmdCompressFormat := cmdCompress.Flags().StringP("format", "f", "zip", "format to use (only zip for now)")
cmdCompressOut := cmdCompress.Flags().StringP("out", "o", "", "output file name")
cmdCompress.RunE = func(cmd *cobra.Command, args []string) error {
if len(args) != 1 {
return errors.New("one file path expected")
}
return cmdMapCompress(cmd, args[0], *cmdCompressOut, *cmdCompressFormat)
}
}

func cmdMapCompress(cmd *cobra.Command, in, out string, format string) error {
fi, err := os.Stat(in)
if err != nil {
return err
}
isDir := fi.IsDir()
if out == "" {
base := filepath.Base(in)
if !isDir {
base = strings.TrimSuffix(base, filepath.Ext(base))
}
out = base + "." + format
}
switch format {
default:
return fmt.Errorf("unsupported format: %s", format)
case "nxz":
if isDir {
in = filepath.Join(in, filepath.Base(in)+maps.Ext)
}
// FIXME: support NXZ encoding
return fmt.Errorf("NXZ encoding is not supported yet")
case "zip":
if !isDir {
in = filepath.Dir(in)
}
f, err := os.Create(out)
if err != nil {
return err
}
defer f.Close()

if err = maps.CompressMap(f, nil, in); err != nil {
return err
}
return f.Close()
}
}

func mapReadRawSections(fname string) ([]maps.RawSection, *maps.Header, error) {
f, err := os.Open(fname)
if err != nil {
Expand Down
109 changes: 71 additions & 38 deletions maps/server.go
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ import (
"strings"

"github.com/julienschmidt/httprouter"
"golang.org/x/exp/slices"

"github.com/noxworld-dev/opennox-lib/ifs"
)
Expand Down Expand Up @@ -39,16 +40,31 @@ var (
}
allowedMapFiles = []string{
"go.mod", "go.sum", // part of the dev environment for map scripts
"LICENSE", // no extension to whitelist
}
excludeMapFiles = []string{
"user.rul", // user defined, should not be distributed
"temp.bmp", // temporary
}
lowerMapFileExt = []string{
".map",
".rul",
}
)

// IsAllowedFile checks if the file with a given name is allowed to be distributed with the map.
func IsAllowedFile(path string) bool {
path = strings.ToLower(filepath.Base(path))
if path == "" || path == "." {
return true
}
if strings.HasPrefix(path, ".") && !strings.HasPrefix(path, "./") {
return false
}
path = filepath.Base(path)
ext := strings.ToLower(filepath.Ext(path))
if slices.Contains(lowerMapFileExt, ext) {
path = strings.ToLower(path)
}
for _, name := range excludeMapFiles {
if path == name {
return false
Expand All @@ -59,7 +75,6 @@ func IsAllowedFile(path string) bool {
return true
}
}
ext := filepath.Ext(path)
for _, e := range excludeMapExt {
if e == ext {
return false
Expand All @@ -73,6 +88,59 @@ func IsAllowedFile(path string) bool {
return false // unrecognized
}

// CompressMap collects and compresses relevant files from Nox/OpenNox map directory.
func CompressMap(w io.Writer, fss fs.FS, dir string) error {
if fss == nil {
fss = os.DirFS(dir)
dir = "."
}
zw := zip.NewWriter(w)
defer zw.Close()
dir = lpath.Clean(dir)
pref := strings.TrimSuffix(dir, "/") + "/"
return fs.WalkDir(fss, dir, func(path string, d fs.DirEntry, err error) error {
if err != nil {
return err
}
name := strings.TrimPrefix(path, pref)
name = lpath.Clean(name)
if name != "." && strings.HasPrefix(name, ".") {
// Skip hidden files and folders.
if d.IsDir() {
return filepath.SkipDir
}
return nil
}
if d.IsDir() {
if d.Type()&fs.ModeSymlink != 0 {
return filepath.SkipDir // Always skip symlinks.
}
return nil // Continue into directories.
}
if !d.Type().IsRegular() {
return nil // Skip symlinks and other non-regular files.
}
ext := strings.ToLower(lpath.Ext(name))
if slices.Contains(lowerMapFileExt, ext) {
name = lpath.Join(lpath.Dir(name), strings.ToLower(lpath.Base(name)))
}
if !IsAllowedFile(path) {
return nil // skip
}
f, err := zw.Create(name)
if err != nil {
return err
}
r, err := fss.Open(path)
if err != nil {
return err
}
defer r.Close()
_, err = io.Copy(f, r)
return err
})
}

func NewServer(path string) *Server {
s := &Server{path: path, mux: httprouter.New()}
s.mux.Handle("HEAD", "/api/v0/maps/", s.handleMapList)
Expand Down Expand Up @@ -180,42 +248,7 @@ func (s *Server) handleMapDownload(w http.ResponseWriter, r *http.Request, p htt
}
// serve compressed map file
w.Header().Set("Content-Type", contentTypeZIP)
zw := zip.NewWriter(w)
defer zw.Close()
err = filepath.WalkDir(base, func(path string, d fs.DirEntry, err error) error {
if err != nil {
return err
}
if d.IsDir() {
if d.Type()&fs.ModeSymlink != 0 {
return filepath.SkipDir // Always skip symlinks.
}
return nil // Continue into directories.
}
if !d.Type().IsRegular() {
return nil // Skip symlinks and other non-regular files.
}
if !IsAllowedFile(path) {
return nil // skip
}
name, err := filepath.Rel(base, path)
if err != nil {
return err
}
name = strings.ToLower(name)
name = lpath.Clean(name)
f, err := zw.Create(name)
if err != nil {
return err
}
r, err := os.Open(path)
if err != nil {
return err
}
defer r.Close()
_, err = io.Copy(f, r)
return err
})
err = CompressMap(w, nil, base)
if err != nil {
Log.Printf("error serving map %q: %v", name, err)
w.WriteHeader(http.StatusInternalServerError)
Expand Down
43 changes: 43 additions & 0 deletions maps/server_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,49 @@ import (
"github.com/noxworld-dev/opennox-lib/noxtest"
)

func TestIsAllowedFile(t *testing.T) {
var cases = []struct {
path string
exp bool
}{
{"example.map", true},
{"example.nxz", false},
{"example.rul", true},
{"example.zip", false},
{"example.tar", false},
{"example.tar.gz", false},
{"user.rul", false},
{"some.lua", true},
{"go.mod", true},
{"go.sum", true},
{"some.go", true},
{"sub/some.go", true},
{"vendor/sub/some.go", true},
{"LICENSE", true},
{"README.md", true},
{"README.txt", true},
{"some.other", false},
{"some.json", true},
{"some.yaml", true},
{"some.yml", true},
{"some.png", true},
{"some.jpg", true},
{"some.mp3", true},
{"some.ogg", true},
{".git/config", false},
{".git/refs/heads/fake.go", false},
}
for _, c := range cases {
c := c
t.Run(c.path, func(t *testing.T) {
got := IsAllowedFile(c.path)
if got != c.exp {
t.FailNow()
}
})
}
}

func copyFile(t testing.TB, dst, src string) {
s, err := ifs.Open(src)
require.NoError(t, err)
Expand Down

0 comments on commit 2edb32a

Please sign in to comment.