-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
0 parents
commit 8a14b01
Showing
9 changed files
with
425 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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 |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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 |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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 |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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. |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,29 @@ | ||
# image-check [](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 | ✔ | |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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) | ||
} |
Oops, something went wrong.