Skip to content

Commit

Permalink
✨ エントリーポイントのバリデーションで、.appファイルに対応
Browse files Browse the repository at this point in the history
  • Loading branch information
ikura-hamu committed Nov 12, 2024
1 parent b561ca8 commit 8ca0fcb
Show file tree
Hide file tree
Showing 8 changed files with 136 additions and 6 deletions.
67 changes: 61 additions & 6 deletions src/service/v2/game_file.go
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,9 @@ import (
"io"
"net/url"
"os"
"path"
"slices"
"strings"
"time"

"github.com/traPtitech/trap-collection-server/src/domain"
Expand Down Expand Up @@ -72,6 +74,8 @@ func (*GameFile) checkZip(_ context.Context, reader io.Reader) (zr *zip.Reader,
return zr, true, nil
}

// エントリーポイントが存在し、それがディレクトリでないことを確認。
// 一般的なエントリーポイントの存在確認に使う。
func (*GameFile) checkEntryPointExist(_ context.Context, zr *zip.Reader, entryPoint values.GameFileEntryPoint) (bool, error) {
entryPointExists := slices.ContainsFunc(zr.File, func(zf *zip.File) bool {
return zf.Name == string(entryPoint) && !zf.FileInfo().IsDir()
Expand All @@ -84,6 +88,50 @@ func (*GameFile) checkEntryPointExist(_ context.Context, zr *zip.Reader, entryPo
return true, nil
}

// macOSのアプリケーション(*.app)のエントリーポイントが正しいか確認。
// 仕様は [Appleの開発者向けページ] を参照。
//
// 具体的には、
// - エントリーポイントがディレクトリで .app で終わること
// - エントリーポイント/Contents/MacOS というディレクトリが存在すること
// - エントリーポイント/Contents/Info.plist というファイルが存在すること
//
// [Appleの開発者向けページ]: https://developer.apple.com/library/archive/documentation/CoreFoundation/Conceptual/CFBundles/BundleTypes/BundleTypes.html#//apple_ref/doc/uid/10000123i-CH101-SW1
func (*GameFile) checkMacOSAppEntryPointValid(_ context.Context, zr *zip.Reader, entryPoint values.GameFileEntryPoint) (bool, error) {
if !strings.HasSuffix(string(entryPoint), ".app") {
return false, nil
}

requiredDirs := []string{
string(entryPoint),
path.Join(string(entryPoint), "Contents"),
path.Join(string(entryPoint), "Contents", "MacOS"),
}

for _, dir := range requiredDirs {
dir = dir + "/"
if !slices.ContainsFunc(zr.File, func(zf *zip.File) bool {
return zf.Name == dir && zf.FileInfo().IsDir()
}) {
return false, nil
}
}

requiredFiles := []string{
path.Join(string(entryPoint), "Contents", "Info.plist"),
}

for _, file := range requiredFiles {
if !slices.ContainsFunc(zr.File, func(zf *zip.File) bool {
return zf.Name == file && !zf.FileInfo().IsDir()
}) {
return false, nil
}
}

return true, nil
}

func (gameFile *GameFile) SaveGameFile(ctx context.Context, reader io.Reader, gameID values.GameID, fileType values.GameFileType, entryPoint values.GameFileEntryPoint) (*domain.GameFile, error) {

var file *domain.GameFile
Expand Down Expand Up @@ -149,15 +197,22 @@ func (gameFile *GameFile) SaveGameFile(ctx context.Context, reader io.Reader, ga
return service.ErrNotZipFile
}

ok, err = gameFile.checkEntryPointExist(ctx, zr, entryPoint)
if err != nil {
return fmt.Errorf("failed to check entry point exist: %w", err)
// これらのどれか一つで成功した場合(trueが返ってきた場合)、有効なエントリーポイントとして扱う
checkers := []func(context.Context, *zip.Reader, values.GameFileEntryPoint) (bool, error){
gameFile.checkEntryPointExist,
gameFile.checkMacOSAppEntryPointValid,
}
if !ok {
return service.ErrInvalidEntryPoint
for _, checker := range checkers {
ok, err = checker(ctx, zr, entryPoint)
if err != nil {
return fmt.Errorf("failed to check entry point: %w", err)
}
if ok {
return nil
}
}

return nil
return service.ErrInvalidEntryPoint
})

eg.Go(func() error {
Expand Down
75 changes: 75 additions & 0 deletions src/service/v2/game_file_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ import (
"errors"
"io"
"net/url"
"path"
"strings"
"testing"
"time"
Expand Down Expand Up @@ -141,6 +142,80 @@ func Test_checkEntryPointExist(t *testing.T) {
}
}

func Test_checkMacOSAppEntryPointValid(t *testing.T) {
t.Parallel()

testCases := map[string]struct {
zipFileName string
entryPoint values.GameFileEntryPoint
result bool
isErr bool
err error
}{
"特に問題ないのでエラー無し": {
zipFileName: "test.app.zip",
entryPoint: values.NewGameFileEntryPoint("test.app"),
result: true,
},
"entryPointが*.appでないのでfalse": {
zipFileName: "test.app2.zip",
entryPoint: values.NewGameFileEntryPoint("test.app2"),
result: false,
},
"Contentsフォルダが無いのでfalse": {
zipFileName: "NoContents.zip",
entryPoint: values.NewGameFileEntryPoint("test.app"),
result: false,
},
"MacOSフォルダが無いのでfalse": {
zipFileName: "NoMacOS.zip",
entryPoint: values.NewGameFileEntryPoint("test.app"),
result: false,
},
"Info.plistが無いのでfalse": {
zipFileName: "NoInfoPlist.zip",
entryPoint: values.NewGameFileEntryPoint("test.app"),
result: false,
},
"エントリーポイントに該当するファイルが無いのでfalse": {
zipFileName: "test.app.zip",
entryPoint: values.NewGameFileEntryPoint("invalid.app"),
result: false,
},
".appがフォルダでないのでfalse": {
zipFileName: "file.app.zip",
entryPoint: values.NewGameFileEntryPoint("file.app"),
result: false,
},
}
gameFile := &GameFile{}

for name, testCase := range testCases {
t.Run(name, func(t *testing.T) {
t.Parallel()

b, err := testdata.FS.ReadFile(path.Join("macOS_app", testCase.zipFileName))
require.NoError(t, err)

r, err := zip.NewReader(bytes.NewReader(b), int64(len(b)))
require.NoError(t, err)

ok, err := gameFile.checkMacOSAppEntryPointValid(context.Background(), r, testCase.entryPoint)
if testCase.isErr {
if testCase.err != nil {
assert.ErrorIs(t, err, testCase.err)
} else {
assert.Error(t, err)
}
} else {
assert.NoError(t, err)
}

assert.Equal(t, testCase.result, ok)
})
}
}

func TestSaveGameFile(t *testing.T) {
t.Parallel()

Expand Down
Binary file added testdata/macOS_app/NoContents.zip
Binary file not shown.
Binary file added testdata/macOS_app/NoInfoPlist.zip
Binary file not shown.
Binary file added testdata/macOS_app/NoMacOS.zip
Binary file not shown.
Binary file added testdata/macOS_app/file.app.zip
Binary file not shown.
Binary file added testdata/macOS_app/test.app.zip
Binary file not shown.
Binary file added testdata/macOS_app/test.app2.zip
Binary file not shown.

0 comments on commit 8ca0fcb

Please sign in to comment.