Skip to content

Commit

Permalink
Build scripts changed to support FreeBSD. File size check
Browse files Browse the repository at this point in the history
implemented for input files. Fixed error that caused data
corruption in the last few bytes of bannerless images of certain
sizes. Added and expanded code comments all around.
  • Loading branch information
dbohdan committed Jul 25, 2013
1 parent 5fcdebc commit dbd988a
Show file tree
Hide file tree
Showing 7 changed files with 108 additions and 38 deletions.
3 changes: 2 additions & 1 deletion .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,8 @@ doc-pak
s2png
*.deb
*.png
python github flavored markdown.py
pymd.py
README.html
s2png.o
rc4.o
core
Expand Down
9 changes: 5 additions & 4 deletions Makefile
Original file line number Diff line number Diff line change
@@ -1,11 +1,12 @@
CFLAGS=-g -O2 -Wall
CFLAGS=-g -O2 -Wall -I/usr/local/include/
LDFLAGS=-L/usr/local/lib/
INCS = `libpng-config --cflags`
LIBS = `libpng-config --libs` -lgd -lm
LIBS = `libpng-config --libs` -lgd

all: test readme

s2png: rc4.o s2png.o
$(CC) $^ -o $@ $(CFLAGS) $(INCS) $(LIBS)
s2png:
$(CC) -o s2png rc4.c s2png.c $(CFLAGS) $(LDFLAGS) $(INCS) $(LIBS)

install:
cp s2png /usr/local/bin
Expand Down
6 changes: 3 additions & 3 deletions README.in
Original file line number Diff line number Diff line change
Expand Up @@ -9,11 +9,11 @@ Building and installing
-----------------------

1. Install the dependencies. On Debian, Ubuntu and Linux Mint you can do so with
`sudo apt-get install libgd2-xpm-dev libgd2-xpm`.
`sudo apt-get install libgd2-xpm-dev libgd2-xpm libpng-dev`. On FreeBSD you will need to install `graphics/gd` and `graphics/png`.

2. Type `make` in your terminal and hit the enter key. Building has been tested on Linux Mint 13 and Ubuntu 12.10.
2. Type `make` in your terminal and hit the enter key. Building has been tested on Linux Mint 13, Ubuntu 12.10 and FreeBSD 9.1-RELEASE.

3. Install with `sudo make install`. On Debian-derived distributions use `sudo checkinstall` instead.
3. Install with `sudo make install`. On Debian-derived Linux distributions use `sudo checkinstall` instead.

Usage
-----
Expand Down
15 changes: 9 additions & 6 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -9,11 +9,11 @@ Building and installing
-----------------------

1. Install the dependencies. On Debian, Ubuntu and Linux Mint you can do so with
`sudo apt-get install libgd2-xpm-dev libgd2-xpm`.
`sudo apt-get install libgd2-xpm-dev libgd2-xpm libpng-dev`. On FreeBSD you will need to install `graphics/gd` and `graphics/png`.

2. Type `make` in your terminal and hit the enter key. Building has been tested on Linux Mint 13 and Ubuntu 12.10.
2. Type `make` in your terminal and hit the enter key. Building has been tested on Linux Mint 13, Ubuntu 12.10 and FreeBSD 9.1-RELEASE.

3. Install with `sudo make install`. On Debian-derived distributions use `sudo checkinstall` instead.
3. Install with `sudo make install`. On Debian-derived Linux distributions use `sudo checkinstall` instead.

Usage
-----
Expand All @@ -22,17 +22,20 @@ Usage
usage: s2png [-h] [-o filename] [-w width (600) | -s] [-b text]
[-p password] [-e | -d] file

Store any data in a PNG image.
This version can encode files of up to 16777215 bytes.

-h display this message and quit
-o filename output the converted data (image or binary) to filename
-w width set the width of PNG image output (600 by default)
-s make the output image roughly square
-b text custom banner text ("" for no banner)
-p password encrypt/decrypt the output with password using RC4
(Warning: do not use this if you need actual security!)
-e force encoding mode avoiding file type detection
Normally s2png detects which operation to perform by file type. You can
circumvent this with the following switches:
-e force encoding mode
-d force decoding mode

See README.md for details.

Basic examples
--------------
Expand Down
57 changes: 42 additions & 15 deletions s2png.c
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,7 @@
#define MODE_DECODE 2

#define RC4_DROP_N 3072
#define MAX_FILE_SIZE 0xFFFFFF

#define DEFAULT_BANNER ("This image contains binary data. \
To extract it get s2png on GitHub.")
Expand All @@ -52,11 +53,13 @@ int png_to_file(char *fin_fn, char *fout_fn, char *password)
gdImagePtr im = gdImageCreateFromPng(fin);
fclose(fin);

/* Has libgd been about to read and interpret fin? */
if (im == NULL) {
fprintf(stderr, "error: file `%s' not readable as PNG\n", fin_fn);
return EX_DATAERR;
}

/* Get data size from the bottom right pixel of the image. */
int c = gdImageGetPixel(im, gdImageSX(im) - 1, gdImageSY(im) - 1);
int data_size = (gdImageRed(im, c) << 8*2) +
(gdImageGreen(im, c) << 8*1) + (gdImageBlue(im, c));
Expand All @@ -76,7 +79,7 @@ int png_to_file(char *fin_fn, char *fout_fn, char *password)
struct rc4_key key;
init_rc4(password, &key);


/* For each pixel of the image... */
for (y = 0; y < gdImageSY(im); y++) {
for (x = 0; x < gdImageSX(im); x++) {
c = gdImageGetPixel(im, x, y);
Expand Down Expand Up @@ -112,6 +115,14 @@ int file_to_png(char *fin_fn, char *fout_fn, int image_width,
fin = fopen(fin_fn, "rb");
fstat(fileno(fin), &fin_stat);
int data_size = fin_stat.st_size; /* st_size is off_t, which is int. */
if (data_size > MAX_FILE_SIZE) {
fprintf(stderr,
"error: file `%s' too large to encode (over %u bytes)\n",
fin_fn, MAX_FILE_SIZE);
return EX_DATAERR;
}

/* If given no banner text hide the banner. */
int banner_height = (strcmp(banner, "") == 0 ? 0 : BANNER_HEIGHT);

if (make_square) {
Expand All @@ -124,7 +135,13 @@ int file_to_png(char *fin_fn, char *fout_fn, int image_width,
int image_height = ceil(((float) data_size / (float) image_width / 3.0)
+ (float) banner_height);

/* printf("%d %d\n", image_width, image_height); */
/* Prevent data corruption on images with no banner where the bottom
right pixel is used for data and could get overwritten by the file
size pixel. */
if (banner_height == 0 &&
image_width * image_height * 3 - data_size <= 2) {
image_width++;
}

im = gdImageCreateTrueColor(image_width, image_height);

Expand All @@ -137,6 +154,7 @@ int file_to_png(char *fin_fn, char *fout_fn, int image_width,
struct rc4_key key;
init_rc4(password, &key);

/* Read the input file in sets of three bytes. */
while ((bytes_read = fread(buf, 1, 3, fin)) > 0) {
if (password != NULL) {
rc4(buf, 3, &key);
Expand All @@ -145,6 +163,7 @@ int file_to_png(char *fin_fn, char *fout_fn, int image_width,
gdImageSetPixel(im, x, y, gdImageColorAllocate(im, buf[0], buf[1],
buf[2]));

/* Arrange the input data on a 2D grid of the image. */
if (x + 1 < image_width) {
x++;
} else {
Expand All @@ -155,18 +174,20 @@ int file_to_png(char *fin_fn, char *fout_fn, int image_width,

fclose(fin);

/* Add a banner at the bottom of the image. */
char *s = banner;
gdImageFilledRectangle(im, 0, gdImageSY(im) - banner_height,
image_width - 1, gdImageSY(im) + banner_height,
gdImageColorAllocate(im, 255, 255, 255));
gdImageString(im, (gdFontPtr) gdFontGetTiny(), 5,
gdImageSY(im) - banner_height, (unsigned char *) s,
gdImageColorAllocate(im, 0, 0, 0));
/* store data_size in the last pixel */
/* Store data_size in the bottom right ("last") pixel. */
gdImageSetPixel(im, gdImageSX(im) - 1, gdImageSY(im) - 1,
gdImageColorAllocate(im, (data_size & 0xff0000) >> 8*2,
(data_size & 0xff00) >> 8*1, data_size & 0xff));

/* Save the created image to a PNG file. */
fout = fopen(fout_fn, "wb");
if (fout == NULL) {
fprintf(stderr, "error: cannot open file `%s' for output\n",
Expand Down Expand Up @@ -212,18 +233,24 @@ void usage()
void help()
{
usage();
printf("\n\
printf("\
\n\
Store any data in a PNG image.\n\
This version can encode files of up to %u bytes.\n\
\n\
-h display this message and quit\n\
-o filename output the converted data (image or binary) to filename\n\
-w width set the width of PNG image output (600 by default)\n\
-s make the output image roughly square\n\
-b text custom banner text (\"\" for no banner)\n\
-p password encrypt/decrypt the output with password using RC4\n\
(Warning: do not use this if you need actual security!)\n\
-e force encoding mode avoiding file type detection \n\
Normally s2png detects which operation to perform by file type. You can\n\
circumvent this with the following switches:\n\
-e force encoding mode\n\
-d force decoding mode\n\
\n\
See README.md for details.\n");
See README.md for further details.\n", MAX_FILE_SIZE);
}

int main(int argc, char **argv)
Expand All @@ -233,7 +260,6 @@ int main(int argc, char **argv)
return EX_USAGE;
}

/* opts */
char *in_fn = NULL;
char *out_fn = NULL;
char *banner_text = DEFAULT_BANNER;
Expand All @@ -242,6 +268,7 @@ int main(int argc, char **argv)
int make_square = 0;
int mode = MODE_NONE;

/* Process command line options. */
int ch;
while ((ch = getopt(argc, argv, "w:o:hsb:p:ed")) != -1) {
switch (ch) {
Expand Down Expand Up @@ -279,15 +306,20 @@ int main(int argc, char **argv)
}

in_fn = argv[argc - 1];

/* Does the input file exist? */
if (access(in_fn, R_OK) != 0) {
fprintf(stderr, "error: can't open file `%s'\n", in_fn);
return EX_NOINPUT;
}

/* If we weren't specified what operation to perform explicitly through
* command line options we decide based on file type. */
if (mode == MODE_NONE) {
mode = is_png_file(in_fn) ? MODE_DECODE : MODE_ENCODE;
}

/* If no output file name was given we generate one. */
if (out_fn == NULL) {
out_fn = calloc(strlen(in_fn) + strlen(".orig"), sizeof(char));
if (mode == MODE_DECODE) {
Expand All @@ -305,24 +337,19 @@ int main(int argc, char **argv)
}
}

/* printf("in_fn: %s\nout_fn = %s\n", in_fn, out_fn); */

/* check opts */
/* Check for invalid image width. This code has provisions for reporting
other misc. errors. */
char *err = calloc(1, sizeof(char) * 255);
if (!image_width && mode == MODE_ENCODE) {
strcpy(err, "image width not set or invalid");
} else if (!in_fn) {
strcpy(err, "no input file");
} else if (!out_fn) {
strcpy(err, "no output file");
}

if (strlen(err) > 0) {
fprintf(stderr, "%s, see -h option for help\n", err);
return EX_USAGE;
}


/* Perform the chosen operation. */
if (mode == MODE_ENCODE) {
file_to_png(in_fn, out_fn, image_width, make_square, banner_text,
password);
Expand Down
7 changes: 5 additions & 2 deletions scripts/gen-readme.sh
Original file line number Diff line number Diff line change
@@ -1,7 +1,10 @@
#!/bin/sh
# Generate README.md based on README.in and the output of ./s2png -h

export tempfile=$(tempfile)
./s2png -h | sed -e "s/^/ /" > $tempfile
export tempfile=$(mktemp -t s2pngXXXXX)
# Below sed strips "See README.md for details." and the preceding
# empty line from from s2png's help.
./s2png -h | sed -e '$d' | sed -e 's/^/ /;$d' > $tempfile
sed -e "/S2PNGHELP/{
r $tempfile
d
Expand Down
49 changes: 42 additions & 7 deletions scripts/test.sh
Original file line number Diff line number Diff line change
@@ -1,26 +1,61 @@
#!/bin/sh
# Test s2png encoding and decoding operations, as well as encryption, for
# causing data corruption.

runtests() {
opstest() {
testfile=$1

./s2png $testfile -s -o $testfile.png || echo File encoding failed.
./s2png $testfile.png -o $testfile.out || echo File decoding failed.
./s2png -s -o $testfile.png $testfile || echo File encoding failed.
./s2png -o $testfile.out $testfile.png || echo File decoding failed.

./s2png $testfile -s -o $testfile.png -b "Hello" -p password || echo File encryption failed.
./s2png $testfile.png -o $testfile.out2 -p password || echo File decryption failed.
./s2png -s -o $testfile.png -b "Hello" -p password $testfile || echo File encryption failed.
./s2png -o $testfile.out2 -p password $testfile.png || echo File decryption failed.

diff $testfile $testfile.out || echo Basic test failed.
diff $testfile $testfile.out2 || echo Encryption test failed.

rm $testfile.png $testfile.out $testfile.out2
}

sizetest() {
n=$1
testfile=$2
encoded=$3
decoded=$4

dd bs=$1 count=1 if=/dev/urandom of=$testfile 2> /dev/null
./s2png -e -o $encoded -b "" -w 10 $testfile
./s2png -d -o $decoded $encoded
diff $testfile $decoded
if [ $? != 0 ]; then
echo Corruption at file size $n.
identify a.png
fi
}

echo Running basic operations tests.

# Test for data corruption with an 0xFFFF pattern.
echo -n -e \\0377\\0377\\0377\\0377\\0377\\0377\\0377\\0377 > test.bin
runtests test.bin
opstest test.bin
rm test.bin

# Test for data corruption with random data.
dd if=/dev/urandom of=test.bin count=2k
runtests test.bin
opstest test.bin
rm test.bin

echo Running integrity tests for bannerless images.

# Test for data corruption by file size pixel.
t1=$(mktemp -t s2pngXXXXX)
t2=$(mktemp -t s2pngXXXXX)
t3=$(mktemp -t s2pngXXXXX)
i=10
while [ $i -le 256 ]; do
sizetest $i $t1 $t2 $t3
i=`expr $i + 1`
done
rm $t1 $t2 $t3

echo Tests done.

0 comments on commit dbd988a

Please sign in to comment.