Skip to content

Commit

Permalink
initial commit
Browse files Browse the repository at this point in the history
  • Loading branch information
delthas committed Feb 24, 2020
0 parents commit 8a14b01
Show file tree
Hide file tree
Showing 9 changed files with 425 additions and 0 deletions.
21 changes: 21 additions & 0 deletions .builds/linux.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
image: archlinux
packages:
- git
- go
- upx
sources:
- https://github.com/delthas/image-check.git
secrets:
- 63d7a0ac-2635-4d42-9709-efcd2cf74858 # ssh deploy key
tasks:
- setup: |
cd image-check
git checkout -q $GIT_COMMIT_ID
- build: |
cd image-check
GOARCH=amd64 go build -ldflags "-s -w" -v -o image-check ./cmd/image-check
upx image-check
- deploy: |
cd image-check
ssh -o StrictHostKeyChecking=no -q user@delthas.fr 'mkdir -p /srv/http/blog/image-check/linux/'
scp -o StrictHostKeyChecking=no -q image-check user@delthas.fr:/srv/http/blog/image-check/linux/image-check
21 changes: 21 additions & 0 deletions .builds/mac.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
image: archlinux
packages:
- git
- go
- upx
sources:
- https://github.com/delthas/image-check.git
secrets:
- 63d7a0ac-2635-4d42-9709-efcd2cf74858 # ssh deploy key
tasks:
- setup: |
cd image-check
git checkout -q $GIT_COMMIT_ID
- build: |
cd image-check
GOOS=darwin GOARCH=amd64 go build -ldflags "-s -w" -v -o image-check ./cmd/image-check
upx image-check
- deploy: |
cd image-check
ssh -o StrictHostKeyChecking=no -q user@delthas.fr 'mkdir -p /srv/http/blog/image-check/mac/'
scp -o StrictHostKeyChecking=no -q image-check user@delthas.fr:/srv/http/blog/image-check/mac/image-check
21 changes: 21 additions & 0 deletions .builds/windows.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
image: archlinux
packages:
- git
- go
- upx
sources:
- https://github.com/delthas/image-check.git
secrets:
- 63d7a0ac-2635-4d42-9709-efcd2cf74858 # ssh deploy key
tasks:
- setup: |
cd image-check
git checkout -q $GIT_COMMIT_ID
- build: |
cd image-check
GOOS=windows GOARCH=amd64 go build -ldflags "-s -w" -v -o image-check.exe ./cmd/image-check
upx image-check.exe
- deploy: |
cd image-check
ssh -p 2222 -o StrictHostKeyChecking=no -q user@delthas.fr 'mkdir -p /srv/http/blog/image-check/windows/'
scp -P 2222 -o StrictHostKeyChecking=no -q image-check.exe user@delthas.fr:/srv/http/blog/image-check/windows/image-check.exe
21 changes: 21 additions & 0 deletions LICENSE
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
MIT License

Copyright (c) 2020 delthas

Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is furnished
to do so, subject to the following conditions:

The above copyright notice and this permission notice (including the next
paragraph) shall be included in all copies or substantial portions of the
Software.

THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS
OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF
OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
29 changes: 29 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
# image-check [![builds.sr.ht status](https://builds.sr.ht/~delthas/image-check.svg)](https://builds.sr.ht/~delthas/image-check?)

A small tool to efficiently check whether an image file is **not truncated**.

*This tool **assumes that the file is not corrupted, only truncated**, i.e. that all bytes of the file are valid.*

Usage:
`image-check image.jpg`

Exits with 0 if the file is not truncated, otherwise exits with a code > 0 and an error message on stderr.

## Binaries

| OS | URL |
|---|---|
| Linux x64 | https://delthas.fr/image-check/linux/image-check |
| Mac OS X x64 | https://delthas.fr/image-check/mac/image-check |
| Windows x64 | https://delthas.fr/image-check/windows/image-check.exe |

You can also build it with: `go get github.com/delthas/image-check/cmd/image-check`

## Image file type support

| Type | Supported? |
| --- | --- |
| JPEG ||
| PNG ||
| GIF ||
| SWF ||
280 changes: 280 additions & 0 deletions check.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,280 @@
package imagecheck

import (
"bufio"
"bytes"
"compress/zlib"
"encoding/binary"
"errors"
"fmt"
"io"
"io/ioutil"
"os"

"github.com/ulikunitz/xz/lzma"
)

func wrap(f string, err error) error {
if err == nil {
return nil
}
return fmt.Errorf(f, err)
}

func checkJpeg(f *os.File) error {
_, err := f.Seek(0, io.SeekStart)
if err != nil {
return wrap("seeking to file start: %v", err)
}
bf := bufio.NewReader(f)
markerStart := false
for {
b, err := bf.ReadByte()
if err != nil {
return wrap("reading marker start or entropy-coded data: %v", err)
}
if b == 0xFF {
markerStart = true
continue
}
if b == 0x00 {
markerStart = false
continue
}
if markerStart {
if b == 0xD9 {
return nil
}
if b >= 0xD0 && b < 0xD9 {
markerStart = false
continue
}
buf := make([]byte, 2)
_, err := io.ReadFull(bf, buf)
if err != nil {
return wrap("reading marker size: %v", err)
}
size := int(buf[0])<<8 | int(buf[1])
_, err = bf.Discard(size)
if err != nil {
return wrap("skipping marker data: %v", err)
}
markerStart = false
}
}
}

func checkPng(f *os.File) error {
endSignature := []byte{0x00, 0x00, 0x00, 0x00, 0x49, 0x45, 0x4E, 0x44, 0xAE, 0x42, 0x60, 0x82}
iend := []byte{0x49, 0x45, 0x4E, 0x44}
_, err := f.Seek(int64(-len(endSignature)), io.SeekEnd)
if err != nil {
return wrap("seeking to trailing IEND block: %v", err)
}
end := make([]byte, len(endSignature))
_, err = io.ReadFull(f, end)
if err != nil {
return wrap("reading trailing IEND block: %v", err)
}
if !bytes.Equal(end, endSignature) {
return errors.New("file does not end with IEND block")
}
_, err = f.Seek(8, io.SeekStart)
if err != nil {
return wrap("seeking to first block: %v", err)
}
bf := bufio.NewReader(f)
buf := make([]byte, 4)
for {
_, err := io.ReadFull(bf, buf)
if err != nil {
return wrap("reading block size: %v", err)
}
length := int(binary.BigEndian.Uint32(buf))
_, err = io.ReadFull(bf, buf)
if err != nil {
return wrap("reading block type: %v", err)
}
if bytes.Equal(buf, iend) {
// IEND chunk
_, err := bf.Discard(4)
if err != nil {
return wrap("skipping IEND block: %v", err)
}
return nil
}
_, err = bf.Discard(4 + length)
if err != nil {
return wrap("skipping block data: %v", err)
}
}
}

func checkGif(f *os.File) error {
_, err := f.Seek(-1, io.SeekEnd)
if err != nil {
return wrap("seeking to GIF trailer: %v", err)
}
end := make([]byte, 1)
_, err = io.ReadFull(f, end)
if err != nil {
return wrap("reading GIF trailer: %v", err)
}
if end[0] != 0x3B {
return errors.New("file does not end with GIF trailer")
}
_, err = f.Seek(10, io.SeekStart)
if err != nil {
return wrap("seeking to header bitflag: %v", err)
}
bf := bufio.NewReader(f)
bitflag := make([]byte, 1)
_, err = io.ReadFull(bf, bitflag)
if err != nil {
return wrap("reading header bitflag: %v", err)
}
skip := 2
if bitflag[0]&0b10000000 != 0 {
skip += 3 * (1 << (1 + int(bitflag[0]&0b111)))
}
_, err = bf.Discard(skip)
if err != nil {
return wrap("skipping header: %v", err)
}
for {
blockType, err := bf.ReadByte()
if err != nil {
return wrap("reading block type: %v", err)
}
if blockType == 0x3B {
return nil
}
var skip int
if blockType == 0x2C {
_, err = bf.Discard(8)
if err != nil {
return wrap("skipping image block data: %v", err)
}
skip = 1
if bitflag[0]&0b10000000 != 0 {
skip += 3 * (1 << (1 + int(bitflag[0]&0b1111)))
}
} else {
skip = 1
}
_, err = bf.Discard(skip)
if err != nil {
return wrap("skipping block data: %v", err)
}
for {
size, err := bf.ReadByte()
if err != nil {
return wrap("reading sub-block size: %v", err)
}
if size == 0 {
break
}
_, err = bf.Discard(int(size))
if err != nil {
return wrap("skipping sub-block data: %v", err)
}
}
}
}

func checkSwf(f *os.File) error {
_, err := f.Seek(0, io.SeekStart)
if err != nil {
return wrap("seeking to start of file: %v", err)
}
buf := make([]byte, 4)
_, err = io.ReadFull(f, buf[:1])
if err != nil {
return wrap("reading SWF file signature: %v", err)
}
compressionType := buf[0]
_, err = f.Seek(4, io.SeekStart)
if err != nil {
return wrap("seeking to file size header: %v", err)
}
_, err = io.ReadFull(f, buf)
if err != nil {
return wrap("reading file size header: %v", err)
}
size := int64(binary.LittleEndian.Uint32(buf))
if compressionType == 0x46 {
total, err := f.Seek(0, io.SeekEnd)
if err != nil {
return wrap("seeking to file end: %v", err)
}
if total < size {
return errors.New("file length is less than file size header")
}
return nil
}
if compressionType == 0x43 {
zr, err := zlib.NewReader(f)
if err != nil {
return wrap("parsing ZLIB compressed data signature: %v", err)
}
n, err := io.Copy(ioutil.Discard, zr)
if err != nil {
return wrap("decompressing ZLIB compressed data: %v", err)
}
n += 8
if n < size {
return errors.New("ZLIB-decompressed data length is less than file size header")
}
return nil
}
if compressionType == 0x5A {
lr, err := lzma.NewReader(f)
if err != nil {
return wrap("parsing LZMA compressed data signature: %v", err)
}
n, err := io.Copy(ioutil.Discard, lr)
if err != nil {
return wrap("decompressing LZMA compressed data: %v", err)
}
n += 8
if n < size {
return errors.New("LZMA-compressed data length is less than file size header")
}
return nil
}
return errors.New("file has unknown compression type")
}

func checkFile(f *os.File) error {
signature := make([]byte, 8)
_, err := io.ReadFull(f, signature)
if err != nil {
return wrap("reading file signature: %v", err)
}
pngSignature := []byte{0x89, 0x50, 0x4E, 0x47, 0x0D, 0x0A, 0x1A, 0x0A}
gifSignature := []byte{0x47, 0x49, 0x46, 0x38}
jpegSignature := []byte{0xFF, 0xD8}
swfSignature := []byte{0x57, 0x53}
if bytes.HasPrefix(signature, pngSignature) {
return wrap("PNG file: %v", checkPng(f))
}
if bytes.HasPrefix(signature, gifSignature) {
return wrap("GIF file: %v", checkGif(f))
}
if bytes.HasPrefix(signature, jpegSignature) {
return wrap("JPEG file: %v", checkJpeg(f))
}
if bytes.HasPrefix(signature[1:], swfSignature) {
return wrap("SWF file: %v", checkSwf(f))
}
return errors.New("unknown image file format")
}

func Check(path string) error {
f, err := os.Open(path)
if err != nil {
return err
}
defer f.Close()
return checkFile(f)
}
Loading

0 comments on commit 8a14b01

Please sign in to comment.