import urllib.request import urllib.error import struct import re import webbrowser import traceback import sys import os import hashlib import base64 import time NON_B = 'a c d e g gif h hr k m o p r s t u v w wg i ic cm y 3 adv an cgl ck co fa fit int jp lit mu n po sci soc sp tg toy trv tv vp x'.split(' ') SAVE_PATH = 'found' LOG_FILE = 'log.txt' SHORT_LENGTH = 100 ENDS_LENGTH = 4 MIN_JPEG_FF = 3 MAX_JPG_WO_FF = 1000 MAX_NON_ASCII = 0.01 MIN_FRAC_JPEG = 0.9 scanned = set(fag) def content_found(cat, info, board, post, uri, data, log): """Called when hidden content is found.""" if not cat in ['nothing', 'unsupported_type']: if cat in ['4chan_sounds', 'WinRAR', 'ChanGrouper', 'wetherbed', 'HTML', 'text', 'JPEG', 'MPEG_audio', 'unrecognized']: name, new = save_data(cat, data, uri.split('.')[-1]) if new: print(post, uri, cat, info, '->', name, file=log) webbrowser.open(post_uri(board, post)) else: print(post, uri, cat, info, ':', name, file=log) else: print(post, uri, cat, info, file=log) def page_uri(board, page=0): return 'http://boards.4chan.org/' + board + '/' + ('' if page == 0 else str(page)) def post_uri(board, post): return 'http://sys.4chan.org/' + board + '/imgboard.php?res=' + post def save_data(cat, data, ext): """ Save file in appropriate directory. Return name, whether file was new. """ if not os.path.exists(SAVE_PATH): os.mkdir(SAVE_PATH) if not os.path.exists(SAVE_PATH + '/' + cat): os.mkdir(SAVE_PATH + '/' + cat) hash = base64.b64encode(hashlib.md5(data).digest(),b'-_')[:-2].decode('ASCII') name = SAVE_PATH + '/' + cat + '/' + hash + '.' + ext if not os.path.exists(name): with open(name, 'wb') as f: f.write(data) return name, True else: return name, False def color_table_length(c): """Read the flags in a GIF file to get the length of the color table.""" if c & 0x80: return 3 << ((c & 0x07) + 1) else: return 0 def end_of_blocks(data, start): """Find the end of a sequence of GIF data sub-blocks.""" i = start while i < len(data): if data[i] == 0: return i + 1 else: i += data[i] + 1 return len(data) def gif_end(data): """Find the end of GIF data in a file.""" if len(data) <= 10: return len(data) i = color_table_length(data[10]) + 13 while i < len(data): if data[i] == 0x2c: i += 9 if i >= len(data): return len(data) i += color_table_length(data[i]) + 2 i = end_of_blocks(data, i) elif data[i] == 0x21: i = end_of_blocks(data, i+2) elif data[i] == 0x3b: return i + 1 else: return i return min(i, len(data)) def jpg_end(data): """Find the end of JPEG data in a file.""" i = 2 while i + 2 <= len(data): if data[i] == 0xff and data[i+1] >= 0xc0: if data[i+1] == 0xd9: return i + 2 elif data[i+1] == 0xd8: return i elif 0xD0 <= data[i+1] <= 0xD7: i += 2 else: if i + 4 > len(data): return len(data) i += struct.unpack_from('>H', data, i+2)[0] + 2 else: i += 1 return min(i, len(data)) def png_end(data): """Find the end of PNG data in a file.""" i = 8 while i + 8 < len(data): length, chunk_type = struct.unpack_from('>L4s', data, i) if chunk_type == b'IEND': return min(i + 12, len(data)) i += length + 12 return min(i, len(data)) ENDF = {'gif': gif_end, 'jpg': jpg_end, 'png': png_end} TERM = {'gif': b';', 'jpg': b'\xff\xd9', 'png': b'\0\0\0\0IEND\xaeB`\x82'} MPEG_BITRATE = [ 16*[-1], [-1,8,16,24,32,40,48,56,64,80,96,112,128,144,160,-1], [-1,8,16,24,32,40,48,56,64,80,96,112,128,144,160,-1], [-1,32,48,56,64,80,96,112,128,144,160,176,192,224,256,-1], 16*[-1], [-1,32,40,48,56,64,80,96,112,128,160,192,224,256,320,-1], [-1,32,48,56,64,80,96,112,128,160,192,224,256,320,384,-1], [-1,32,64,96,128,160,192,224,256,288,320,352,384,416,448,-1]] MPEG_SAMPLINGRATE = [ [11025,12000,8000,-1], 4*[-1], [22050,24000,16000,-1], [44100,48000,32000,-1]] def cat_hidden(data, ext): """ Determines type of appended data. Returns type name, other info to print. """ if not ext in ENDF: return 'unsupported_type', '' if data[-4:] == b'\xa1\xb2\xc3\xd4': return 'FotoStation', '' if data[-12:-8] == b'phtv': return 'phtv', '' if data[-12:-8] == b'AXS!' or data[-12:-8] == b'AXS*': return 'AFCP', '' if data[-8:] == b'cbipcbbl': return 'PhotoMechanic', '' if data[-9:] == b'': return 'pImgDB', '' if data.find(b'Motorola', 0, 478) != -1: return 'Droid', '' end = ENDF[ext](data) if end == len(data): return 'nothing', '' if all(c in [9, 10, 13, 32] for c in data[end:]): return 'whitespace', '' if end == len(data) - 1: return 'single_byte', '' if data[end:] == bytes((len(data) - end) * [data[-1]]): return 'repeating_byte', '' if data[end:end+2] == b'\x10\x88' and not any(data[end+2:]): return '10_88_nulls', '' if data[end:end+29] == b'This file has been marked by ': return 'cagedgirl', '' if data[end:end+16] == b'\x80\x3F\xE0\x50\x38\x24\x16\x0D\x07\x84\x42\x61\x50\xB8\x64\x36': return '80_3F_E0_50', '' if data[end:end+11] == b'SplashPhoto': return 'SplashPhoto', '' if data[end] == 91 and data.find(b'OggS', end, end+9): return '4chan_sounds', '' if data.find(b'ar!\x1A\x07\x00') != -1: return 'WinRAR', '' if data.find(b'