Golang基於文件魔數判斷文件類型的案例代碼

本文介紹基於魔數判斷文件類型,涉及文件查找讀取內容、文件魔數、字節比較,最後還介紹函數參數的知識。

查找位置

File.Seek()函數可以設置偏移位置,為下一次讀或寫確定偏移量,具體起點有whence確定:0標識相對文件開始位置、1相對當前位置、2相對文件結尾。函數返回新的位置及錯誤。請看下面示例:

package main
 
import (
   "os"
   "fmt"
   "log"
)
 
func main() {
   file, _ :- os.Open("test.txt")
   defer file.Close()
 
   // Offset 表示偏移量
   // Offset 可以為正數或負數
   var offset int64 - 5
 
   // Whence 偏移參考點,具體取值說明
   // 0 - Beginning of file
   // 1 - Current position
   // 2 - End of file
   var whence int - 0
   newPosition, err :- file.Seek(offset, whence)
   if err !- nil {
      log.Fatal(err)
   }
   fmt.Println("Just moved to 5:", newPosition)
 
   // 從當前位置回走2個字節
   newPosition, err - file.Seek(-2, 1)
   if err !- nil {
      log.Fatal(err)
   }
   fmt.Println("Just moved back two:", newPosition)
 
   // 通過移動零字節返回當前位置
   currentPosition, err :- file.Seek(0, 1)
   fmt.Println("Current position:", currentPosition)
 
   // 回到文件起始點
   newPosition, err - file.Seek(0, 0)
   if err !- nil {
      log.Fatal(err)
   }
   fmt.Println("Position after seeking 0,0:", newPosition)
}

執行程序結果如下:

Just moved to 5: 5
Just moved back two: 3
Current position: 3
Position after seeking 0,0: 0

文件類型

魔數是文件前幾個字節,用於唯一標識文件類型,從而無需關註復雜文件結構就能夠確定文件類型。舉例,jpeg文件總是ffd8 ffe0。下面列舉常見文件類型的魔數:

  • 圖像文件
File type Typical extension Hex digits xx – variable Ascii digits . – not an ascii char
Bitmap format .bmp 42 4d BM
FITS format .fits 53 49 4d 50 4c 45 SIMPLE
GIF format .gif 47 49 46 38 GIF8
Graphics Kernel System .gks 47 4b 53 4d GKSM
IRIS rgb format .rgb 01 da
ITC (CMU WM) format .itc f1 00 40 bb
JPEG File Interchange Format .jpg ff d8 ff e0
NIFF (Navy TIFF) .nif 49 49 4e 31 IIN1
PM format .pm 56 49 45 57 VIEW
PNG format .png 89 50 4e 47 .PNG
Postscript format .[e]ps 25 21 %!
Sun Rasterfile .ras 59 a6 6a 95 Y.j.
Targa format .tga xx xx xx
TIFF format (Motorola – big endian) .tif 4d 4d 00 2a MM.*
TIFF format (Intel – little endian) .tif 49 49 2a 00 II*.
X11 Bitmap format .xbm xx xx
XCF Gimp file structure .xcf 67 69 6d 70 20 78 63 66 20 76 gimp xcf
Xfig format .fig 23 46 49 47 #FIG
XPM format .xpm 2f 2a 20 58 50 4d 20 2a 2f /* XPM */
  • 壓縮文件類型
File type Typical extension Hex digits xx = variable Ascii digits . = not an ascii char
Bzip .bz 42 5a BZ
Compress .Z 1f 9d
gzip format .gz 1f 8b
pkzip format .zip 50 4b 03 04 PK…
  • 歸檔文件類型
File type Typical extension Hex digits xx = variable Ascii digits . = not an ascii char
TAR (pre-POSIX) .tar xx xx (a filename)
TAR (POSIX) .tar 75 73 74 61 72 ustar (offset by 257 bytes)
  • 可執行文件類型
File type Typical extension Hex digits xx = variable Ascii digits . = not an ascii char
MS-DOS, OS/2 or MS Windows 4d 5a MZ
Unix elf 7f 45 4c 46 .ELF

有瞭上面的基礎知識,我們就可以讀文件前幾個字節判斷文件類型。

實現基礎函數

首先定義文件魔數標識變量:

var(
   PDF        = []byte{0x25, 0x50, 0x44, 0x46}
	RAR        = []byte{0x52, 0x61, 0x72, 0x21, 0x1A, 0x07, 0x00}
	GZIP       = []byte{0x1F, 0x8B, 0x08}
	ZIP_0      = []byte{0x50, 0x4B, 0x03, 0x04}
	ZIP_1      = []byte{0x50, 0x4B, 0x05, 0x06}
	ZIP_2      = []byte{0x50, 0x4B, 0x07, 0x08}
	WEBP       = []byte{0x52, 0x49, 0x46, 0x46}
   ...
)

下面定義幾個讀文件函數。

首先是從ReadSeeker開始位置起讀取幾個字節函數:

func readUntil(l int, r io.ReadSeeker) ([]byte, error) {
	buff := make([]byte, l)

	_, err := r.Read(buff)
	if err != nil {
		return nil, err
	}

	r.Seek(0, io.SeekStart)

	return buff, nil
}

基於魔數字節數組讀文件魔數:

func checkBuffer(r io.ReadSeeker, t []byte) ([]byte, error) {
   // 根據提供參數獲取長度
	l := len(t)

	buff, err := readUntil(l, r)
	if err != nil {
		return make([]byte, 0), err
	}

	return buff, nil
}

基於參數比較文件魔數:

func genericCompareBuffer(r io.ReadSeeker, t []byte) bool {
	buff, err := checkBuffer(r, t)
	if err != nil {
		return false
	}

	valid := bytes.Compare(t, buff)
	return valid == 0
}

比較文件包括多個魔數情況比較:

func genericMultipleCompareBuffer(r io.ReadSeeker, t [][]byte) bool {
	buff, err := checkBuffer(r, t[0])
	if err != nil {
		return false
	}

	for _, v := range t {
		if bytes.Compare(v, buff) == 0 {
			return true
		}
	}

	return false
}

類型判斷函數

有瞭上面的基礎函數,我們可以提供上層應用接口函數。

首先是常用類型判斷函數,註意這裡PNG、JPEG是前面定義的字節數組變量。

// IsPng function will return true if File is a valid PNG
func IsPng(r io.ReadSeeker) bool {
	return genericCompareBuffer(r, PNG)
}

// IsJpeg function will return true if File is a valid JPEG
func IsJpeg(r io.ReadSeeker) bool {
	return genericCompareBuffer(r, JPEG)
}

// IsPdf function will return true if File is a valid PDF
func IsPdf(r io.ReadSeeker) bool {
	return genericCompareBuffer(r, PDF)
}

// IsGif function will return true if File is a valid GIF
func IsGif(r io.ReadSeeker) bool {
	return genericCompareBuffer(r, GIF)
}

同類文件可能有不同魔數場景:

// IsMpg function will return true if File is a valid MPG
func IsMpg(r io.ReadSeeker) bool {
	return genericMultipleCompareBuffer(r, [][]byte{
		MPG_0,
		MPG_1,
	})
}

最後提供一個同時判斷多種文件類型的函數,利用函數類型參數:

// IsOneOf function will validate File with multiple function
func IsOneOf(r io.ReadSeeker, functions ...function) bool {
	for _, f := range functions {
		valid := f(r)
		if valid {
			return true
		}
	}

	return false
}

測試代碼

下面測試前面定義的函數,函數包括文件名稱參數,判斷該文件類型:

package main

import (
	"fmt"
	"os"
)

func main() {
	args := os.Args

	if len(args) < 2 {
		fmt.Println("required input file")
		os.Exit(1)
	}

   // 打開文件
	inputFileArg := args[1]
	inFile, err := os.Open(inputFileArg)

	if err != nil {
		fmt.Println("error open input file ", err)
		os.Exit(1)
	}

   // 支持錯誤處理的關閉方式
	defer func() { 
      err := inFile.Close() 
      if err != nil {
         fmt.Println("error close input file ", err)
      }
   }()

   // 一次性判斷多種類型,如:是否為圖像文件
	valid := IsOneOf(inFile, filesig.Is3gp, filesig.IsPng, filesig.IsJpeg)
	fmt.Println(valid)

   // 當然也可以判斷單個類型
   valid = filesig.Is3gp(inFile)
	fmt.Println(valid)
}

總結

本文介紹瞭基於文件魔數判斷文件類型的方法,主要涉及如何ReadSeek讀取文件指定字節內容,然後介紹文件魔數,最後給出示例基於魔數判斷文件類型。參考代碼:https://github.com/telkomdev/go-filesig

到此這篇關於Golang基於文件魔數判斷文件類型的文章就介紹到這瞭,更多相關go文件類型內容請搜索WalkonNet以前的文章或繼續瀏覽下面的相關文章希望大傢以後多多支持WalkonNet!

推薦閱讀: