Skip to content

Commit

Permalink
FindExecutable reader func.
Browse files Browse the repository at this point in the history
entryHTML parses and returns program application metadata.
  • Loading branch information
bengarrett committed Aug 8, 2024
1 parent 5057122 commit 7d3b000
Show file tree
Hide file tree
Showing 4 changed files with 105 additions and 28 deletions.
1 change: 1 addition & 0 deletions docs/todo.md
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@
- [ ] If artifact is a text file displayed in readme, then delete image preview, these are often super long, large and not needed.
- [ ] If a #hash is appended to a /f/<id> URL while signed out, then return a 404 or a redirect to the sign in page. Post signing should return to the #hash URL?
- [ ] Delete all previews that are unused, such as textfiles that are displayed as a readme.
- [ ] REPLACE DOWNLOAD CONTENT, "Nothing to show as the artifact is not an archive (a ZIP or RAR file, etc.)" with a magic number check on a single file.

- [ ] On Demozoo or Pouet upload or reach, locally cache the JSON to the temp directory.

Expand Down
66 changes: 62 additions & 4 deletions handler/app/internal/mf/mf.go
Original file line number Diff line number Diff line change
Expand Up @@ -140,6 +140,7 @@ func Comment(art *models.File) string {

type entry struct {
sign magicnumber.Signature
exec magicnumber.Windows
size string
zeros int
bytes int64
Expand Down Expand Up @@ -175,6 +176,17 @@ func (e *entry) parse(path string, d fs.DirEntry, platform string) bool {
return skipEntry
}
e.image, e.text, e.program = isImage(e.sign), isText(e.sign), isProgram(e.sign, platform)
if e.program {
r, _ := os.Open(path)
if r == nil {
return skipEntry
}
defer r.Close()
exec, err := magicnumber.FindExecutable(r)
if err == nil {
e.exec = exec
}
}
return !skipEntry
}

Expand Down Expand Up @@ -224,7 +236,7 @@ func ListContent(art *models.File, src string) template.HTML {
return skipEntry
}
entries++
b.WriteString(entryHTML(e.image, e.program, e.text, rel, e.sign.String(), e.size, unid, e.bytes))
b.WriteString(entryHTML(e.exec, e.image, e.program, e.text, rel, e.sign.String(), e.size, unid, e.bytes))
if maxItems := 200; entries > maxItems {
more := fmt.Sprintf(`<div class="border-bottom row mb-1">... %d more files</div>`, files-entries)
b.WriteString(more)
Expand Down Expand Up @@ -276,8 +288,9 @@ func isText(sign magicnumber.Signature) bool {
return false
}

func entryHTML(images, programs, texts bool, rel, sign, size, unid string, bytes int64) string {
func entryHTML(exec magicnumber.Windows, images, programs, texts bool, rel, sign, size, unid string, bytes int64) string {
name := url.QueryEscape(rel)
ext := strings.ToLower(filepath.Ext(name))
htm := fmt.Sprintf(`<div class="col d-inline-block text-truncate" data-bs-toggle="tooltip" `+
`data-bs-title="%s">%s</div>`, rel, rel)
switch {
Expand All @@ -287,6 +300,8 @@ func entryHTML(images, programs, texts bool, rel, sign, size, unid string, bytes
`hx-target="#artifact-editor-comp-feedback" hx-patch="/editor/preview/copy/%s/%s">`, unid, name) +
`<svg width="16" height="16" fill="currentColor" aria-hidden="true">` +
`<use xlink:href="/svg/bootstrap-icons.svg#images"></use></svg></a></div>`
case texts && (ext == ".bat" || ext == ".cmd" || ext == ".ini"):
htm += `<div class="col col-1"></div>`
case texts:
htm += `<div class="col col-1 text-end">` +
fmt.Sprintf(`<a class="icon-link align-text-bottom" `+
Expand All @@ -297,24 +312,67 @@ func entryHTML(images, programs, texts bool, rel, sign, size, unid string, bytes
htm += `<div class="col col-1"></div>`
}
switch {
case texts && (ext == ".bat" || ext == ".cmd" || ext == ".ini"):
htm += `<div class="col col-1"></div>`
case texts:
htm += `<div class="col col-1 text-end">` +
fmt.Sprintf(`<a class="icon-link align-text-bottom" `+
`hx-target="#artifact-editor-comp-feedback" hx-patch="/editor/readme/copy/%s/%s">`, unid, name) +
`<svg class="bi" width="16" height="16" fill="currentColor" aria-hidden="true">` +
`<use xlink:href="/svg/bootstrap-icons.svg#file-text"></use></svg></a></div>`
case programs:
case programs && ext == ".exe", programs && ext == ".com":
htm += `<div class="col col-1 text-end"><svg width="16" height="16" fill="currentColor" aria-hidden="true">` +
`<use xlink:href="/svg/bootstrap-icons.svg#terminal-plus"></use></svg></div>`
default:
htm += `<div class="col col-1"></div>`
}
htm += fmt.Sprintf(`<div><small data-bs-toggle="tooltip" data-bs-title="%d bytes">%s</small>`, bytes, size)
htm += fmt.Sprintf(` <small class="">%s</small></div>`, sign)
switch {
case texts && (ext == ".bat" || ext == ".cmd"):
htm += fmt.Sprintf(` <small class="">%s</small></div>`, "command script")
case texts && (ext == ".ini"):
htm += fmt.Sprintf(` <small class="">%s</small></div>`, "configuration textfile")
case programs || ext == ".com":
htm = progr(exec, ext, htm, bytes)
default:
htm += fmt.Sprintf(` <small class="">%s</small></div>`, sign)
}
htm = fmt.Sprintf(`<div class="border-bottom row mb-1">%s</div>`, htm)
return htm
}

func progr(exec magicnumber.Windows, ext, htm string, bytes int64) string {
const epochYear = 1980
const x8086 = 64 * 1024
s := ""
fmt.Printf("exec: %+v %+v %s\n", exec.PE, exec.NE, ext)
switch {
case (ext == ".exe" || ext == ".com") && exec.PE != magicnumber.UnknownPE:
s = fmt.Sprintf("%s executable", exec)
case (ext == ".exe" || ext == ".com") && exec.NE == magicnumber.UnknownNE:
if x8086 >= bytes {
s = "Dos command"
} else {
s = "Dos executable"
}
case (ext == ".exe" || ext == ".com") && exec.NE != magicnumber.NoneNE:
s = fmt.Sprintf("%s executable", exec)
case ext == ".exe" || ext == ".com":
s = "MS Dos program"
case ext == ".dll" && exec.PE != magicnumber.UnknownPE:
s = "Windows dynamic-link library"
case exec.NE != magicnumber.NoneNE:
s = "NE program data"
default:
s = "PE program data"
}
if y := exec.TimeDateStamp.Year(); y >= epochYear && y <= time.Now().Year() {
s += fmt.Sprintf(", built %s", exec.TimeDateStamp.Format("2006-01-2"))
}
htm += fmt.Sprintf(` <small class="">%s</small></div>`, s)
return htm
}

var (
errIsDir = errors.New("error, directory")
errTooMany = errors.New("will not decompress this archive as it is very large")
Expand Down
40 changes: 29 additions & 11 deletions internal/magicnumber/magicnumber.go
Original file line number Diff line number Diff line change
Expand Up @@ -587,14 +587,33 @@ func Find(r io.Reader) (Signature, error) {
// Find512B reads the first 512 bytes from the reader and returns the file type signature.
// This is a less accurate method than Find but should be faster.
func Find512B(r io.Reader) (Signature, error) {
buf := make([]byte, 512)
const size = 512
buf := make([]byte, size)
_, err := io.ReadFull(r, buf)
if err != nil && !errors.Is(err, io.ErrUnexpectedEOF) {
return Unknown, fmt.Errorf("magic number find first 512 bytes: %w", err)
return Unknown, fmt.Errorf("magic number find first %d bytes: %w", size, err)
}
return FindBytes512B(buf), nil
}

// FindExecutable reads the first 1KB from the reader and returns the specific information contained
// within the executable headers. Both the New Executable and Portable Executable formats are supported,
// which are commonly used by IBM and Microsoft desktop operating systems from PC/MS-DOS to modern Windows.
func FindExecutable(r io.Reader) (Windows, error) {
win := Default()
const size = 1024
buf := make([]byte, size)
_, err := io.ReadFull(r, buf)
if err != nil && !errors.Is(err, io.ErrUnexpectedEOF) {
return win, fmt.Errorf("magic number find first %d bytes: %w", size, err)
}
win = NE(buf)
if win.NE == NoneNE {
win = PE(buf)
}
return win, nil
}

// Archive reads all the bytes from the reader and returns the file type signature if
// the file is a known archive of files or Unknown if the file is not an archive.
func Archive(r io.Reader) (Signature, error) {
Expand Down Expand Up @@ -794,7 +813,7 @@ func FindBytes512B(p []byte) Signature {
}
}

// FindExecutable returns the specific information contained within the executable headers.
// FindBytesExecutable returns the specific information contained within the executable headers.
// Both the New Executable and Portable Executable formats are supported, which are commonly
// used by IBM and Microsoft desktop operating systems from PC/MS-DOS to modern Windows.
//
Expand All @@ -806,7 +825,7 @@ func FindBytes512B(p []byte) Signature {
// a standard library that was not available until Windows XP (5.1).
//
// If the file is not a known executable format, an empty Windows struct is returned.
func FindExecutable(p []byte) Windows {
func FindBytesExecutable(p []byte) Windows {
win := NE(p)
if win.NE == NoneNE {
win = PE(p)
Expand Down Expand Up @@ -863,8 +882,10 @@ func (w Windows) String() string {
WindowsNT = 4
)
switch {
case w.NE == DOSv4Exe, w.NE == OS2Exe, w.NE == UnknownNE:
case w.NE == DOSv4Exe, w.NE == OS2Exe:
return fmt.Sprintf("%s v%d.%d", w.NE, w.Major, w.Minor)
case w.NE == UnknownNE:
return "Unknown NE executable"
}
switch {
case w.Major == Windows2x && w.NE == Windows286Exe:
Expand Down Expand Up @@ -951,21 +972,18 @@ func (ne NewExecutable) String() string {
// for example, a Windows 3.0 requirement would return 3 and 0.
func NE(p []byte) Windows {
none := Default()
fmt.Println("MZ", none.NE)
const min = 64
if len(p) < min {
fmt.Println("???")
return none
}
fmt.Println("MZ", none)
if p[0] != 'M' || p[1] != 'Z' {
return none
}
const segmentedHeaderIndex = 0x3c // the location of the segmented header
const executableTypeIndex = 0x36 // the executable type aka the operating system
const winMinorIndex = 0x3e // the location of the Windows minor version
const winMajorIndex = 0x3f // the location of the Windows major version
offset := int16(binary.LittleEndian.Uint16(p[segmentedHeaderIndex:]))
offset := uint16(binary.LittleEndian.Uint16(p[segmentedHeaderIndex:]))
if len(p) < int(offset)+int(winMajorIndex) {
return none
}
Expand Down Expand Up @@ -1044,7 +1062,7 @@ func PE(p []byte) Windows {
}
// the location of the portable executable header
const peHeaderIndex = 0x3c
offset := int16(binary.LittleEndian.Uint16(p[peHeaderIndex:]))
offset := uint16(binary.LittleEndian.Uint16(p[peHeaderIndex:]))
if len(p) < int(offset) {
return none
}
Expand All @@ -1058,7 +1076,7 @@ func PE(p []byte) Windows {
return none
}
// the location of the COFF (Common Object File Format) header
coffHeaderIndex := offset + int16(len(signature))
coffHeaderIndex := offset + uint16(len(signature))
const coffLen = 20
if len(p) < int(coffHeaderIndex)+coffLen {
return none
Expand Down
26 changes: 13 additions & 13 deletions internal/magicnumber/magicnumber_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -33,18 +33,18 @@ func tduncompress(name string) string {
return x
}

func TestFindExecutable(t *testing.T) {
func TestFindBytesExecutable(t *testing.T) {
t.Parallel()

w := magicnumber.FindExecutable([]byte{})
w := magicnumber.FindBytesExecutable([]byte{})
assert.Equal(t, magicnumber.UnknownPE, w.PE)
assert.Equal(t, magicnumber.NoneNE, w.NE)

freedos := []string{"/exe/EXE.EXE", "/exemenu/exemenu.exe", "/press/PRESS.EXE", "/rread/rread.exe"}
for _, v := range freedos {
p, err := os.ReadFile(td("binaries/freedos" + v))
require.NoError(t, err)
w = magicnumber.FindExecutable(p)
w = magicnumber.FindBytesExecutable(p)
assert.Equal(t, magicnumber.UnknownPE, w.PE)
assert.Equal(t, magicnumber.NoneNE, w.NE)
}
Expand All @@ -53,7 +53,7 @@ func TestFindExecutable(t *testing.T) {
for _, v := range vista {
p, err := os.ReadFile(td("binaries/windows" + v))
require.NoError(t, err)
w = magicnumber.FindExecutable(p)
w = magicnumber.FindBytesExecutable(p)
assert.Equal(t, magicnumber.AMD64PE, w.PE)
assert.Equal(t, 6, w.Major)
assert.Equal(t, 0, w.Minor)
Expand All @@ -66,7 +66,7 @@ func TestFindExecutable(t *testing.T) {
for _, v := range winv3 {
p, err := os.ReadFile(td("binaries/windows3x" + v))
require.NoError(t, err)
w = magicnumber.FindExecutable(p)
w = magicnumber.FindBytesExecutable(p)
assert.Equal(t, magicnumber.UnknownPE, w.PE)
assert.Equal(t, magicnumber.Windows286Exe, w.NE)
assert.Equal(t, 3, w.Major)
Expand All @@ -76,7 +76,7 @@ func TestFindExecutable(t *testing.T) {

p, err := os.ReadFile(td("binaries/windowsXP/CoreTempv13/32bit/Core Temp.exe"))
require.NoError(t, err)
w = magicnumber.FindExecutable(p)
w = magicnumber.FindBytesExecutable(p)
assert.Equal(t, magicnumber.Intel386PE, w.PE)
assert.Equal(t, magicnumber.NoneNE, w.NE)
assert.Equal(t, 5, w.Major)
Expand All @@ -85,7 +85,7 @@ func TestFindExecutable(t *testing.T) {

p, err = os.ReadFile(td("binaries/windowsXP/CoreTempv13/64bit/Core Temp.exe"))
require.NoError(t, err)
w = magicnumber.FindExecutable(p)
w = magicnumber.FindBytesExecutable(p)
assert.Equal(t, magicnumber.AMD64PE, w.PE)
assert.Equal(t, magicnumber.NoneNE, w.NE)
assert.Equal(t, 5, w.Major)
Expand All @@ -104,7 +104,7 @@ func TestFindExecutableWinNT(t *testing.T) {
for _, v := range win9x {
p, err := os.ReadFile(td("binaries/windows9x" + v))
require.NoError(t, err)
w := magicnumber.FindExecutable(p)
w := magicnumber.FindBytesExecutable(p)
assert.Equal(t, magicnumber.Intel386PE, w.PE)
assert.Equal(t, 4, w.Major)
assert.Equal(t, 0, w.Minor)
Expand All @@ -120,7 +120,7 @@ func TestFindExecutableWinNT(t *testing.T) {
for _, v := range unknown {
p, err := os.ReadFile(td("binaries/windows9x" + v))
require.NoError(t, err)
w := magicnumber.FindExecutable(p)
w := magicnumber.FindBytesExecutable(p)
assert.Equal(t, magicnumber.UnknownPE, w.PE)
assert.Equal(t, 0, w.Major)
assert.Equal(t, 0, w.Minor)
Expand All @@ -131,7 +131,7 @@ func TestFindExecutableWinNT(t *testing.T) {

p, err := os.ReadFile(td("binaries/windows9x/7z1604-extra/x64/7za.exe"))
require.NoError(t, err)
w := magicnumber.FindExecutable(p)
w := magicnumber.FindBytesExecutable(p)
assert.Equal(t, magicnumber.AMD64PE, w.PE)
assert.Equal(t, 4, w.Major)
assert.Equal(t, 0, w.Minor)
Expand Down Expand Up @@ -171,17 +171,17 @@ func TestXXX(t *testing.T) {

p, err = os.ReadFile("life.com")
require.NoError(t, err)
w = magicnumber.FindExecutable(p)
w = magicnumber.FindBytesExecutable(p)
fmt.Printf(">>%+v\n", w)

p, err = os.ReadFile("hello.com")
require.NoError(t, err)
w = magicnumber.FindExecutable(p)
w = magicnumber.FindBytesExecutable(p)
fmt.Printf(">>%+v\n", w)

p, err = os.ReadFile("hellojs.com")
require.NoError(t, err)
w = magicnumber.FindExecutable(p)
w = magicnumber.FindBytesExecutable(p)
fmt.Printf(">>%+v\n", w)

x := uint8(2)
Expand Down

0 comments on commit 7d3b000

Please sign in to comment.