从nginx日志原始二进制数据还原文件

服务器
access.log含有多条日志,每条日志都包含图片数据。日志较大,所以不使用readlines(),本来使用list也很占内存,且这样处理比较慢,要等程序把整个文件读完。按行读取日志后提取出图片数据后解码写入文件即可。

 nginx的access日志自定义格式记录了post请求数据,因为一些原因需要从原始数据恢复出jpg格式图片。

首先处理日志,筛选出含有图片数据的日志条目,取出其中一条进行分析,大致格式如下,为了便于查看,做一下换行处理:

- | 09/Dec/2017:08:00:19 +0000 | POST /some/api HTTP/1.1 | 200 | 461 | 
--SgX***yE7dwyg0smH-Tqpt-ggGQwTU9 
\x0D\x0AContent-Disposition: form-data; name=\x22name\x22\x0D\x0AContent-Type: text/plain; charset=UTF-8\x0D\x0A\x0D\x0value\x0D\x0A 
--SgX***yE7dwyg0smH-Tqpt-ggGQwTU9 
\x0D\x0AContent-Disposition: form-data; name=\x22file\x22; filename=\abc.jpg\x22 
\x0D\x0AContent-Type: application/octet-stream 
\x0D\x0AContent-Transfer-Encoding: binary\x0D\x0A\x0D\x0A\xFF\xD8\xFF\xE0\x00\x10JFIF\x00 ... \xD2_\xA0\x1A\x7F\xFF\xD9\x0D\x0A 
--SgX***yE7dwyg0smH-Tqpt-ggGQwTU9-- 
\x0D\x0A | 42097 | - | - | - | 1.1.1.1 | d1fkkbcd02eb | 127.0.0.1:8888 | 0.034 | 0.123 
  • 1.
  • 2.
  • 3.
  • 4.
  • 5.
  • 6.
  • 7.
  • 8.
  • 9.

[[214107]]

其中,--SgX***yE7dwyg0smH-Tqpt-ggGQwTU9是表单的分隔字段,\x0D\x0A是回车和换行的转义字符,分别等同于\r和\n,\xFF\xD8\xFF\xE0\x00\x10JFIF\x00 ... \xD2_\xA0\x1A\x7F\xFF\xD9部分就是图片原始数据。

使用上述单行日志文件tmp.log进行调试:

>>> f = open('tmp.log''rb'
>>> data = f.read() 
>>> data 
"- | 09/Dec/2017:08:00:19 +0000 | POST /some/api HTTP/1.1 | 200 | 461 | --SgX***yE7dwyg0smH-Tqpt-ggGQwTU9\x0D\x0AContent-Disposition: form-data; name=\x22name\x22\x0D\x0AContent-Type: text/plain; charset=UTF-8\x0D\x0A\x0D\x0value\x0D\x0A--SgX***yE7dwyg0smH-Tqpt-ggGQwTU9\x0D\x0AContent-Disposition: form-data; name=\x22file\x22; filename=\x221512806410245.jpg\x22\x0D\x0AContent-Type: application/octet-stream\x0D\x0AContent-Transfer-Encoding: binary\x0D\x0A\x0D\x0A\xFF\xD8\xFF\xE0\x00\x10JFIF\x00\x01\x01\x00\x00\x01\x00 ... \xBC'\xF1\x8C\xCC\x83,\xFAo\xD2_\xA0\x1A\x7F\xFF\xD9 
\x0D\x0A--SgX***yE7dwyg0smH-Tqpt-ggGQwTU9--\x0D\x0A | 42097 | - | - | - | 1.1.1.1 | d1fkkbcd02eb | 127.0.0.1:8888 | 0.034 | 0.123" 
>>> print data 
- | 09/Dec/2017:08:00:19 +0000 | POST /some/api HTTP/1.1 | 200 | 461 | --SgX***yE7dwyg0smH-Tqpt-ggGQwTU9\x0D\x0AContent-Disposition: form-data; name=\x22name\x22\x0D\x0AContent-Type: text/plain; charset=UTF-8\x0D\x0A\x0D\x0value\x0D\x0A--SgX***yE7dwyg0smH-Tqpt-ggGQwTU9\x0D\x0AContent-Disposition: form-data; name=\x22file\x22; filename=\x221512806410245.jpg\x22\x0D\x0AContent-Type: application/octet-stream\x0D\x0AContent-Transfer-Encoding: binary\x0D\x0A\x0D\x0A\xFF\xD8\xFF\xE0\x00\x10JFIF\x00\x01\x01\x00\x00\x01\x00 ... \xBC'\xF1\x8C\xCC\x83,\xFAo\xD2_\xA0\x1A\x7F\xFF\xD9\x0D\x0A--SgX***yE7dwyg0smH-Tqpt-ggGQwTU9--\x0D\x0A | 42097 | - | - | - | 1.1.1.1 | d1fkkbcd02eb | 127.0.0.1:8888 | 0.034 | 0.123 
>>> f.close() 
  • 1.
  • 2.
  • 3.
  • 4.
  • 5.
  • 6.
  • 7.
  • 8.

可以看出,打印到屏幕上的转义字符,如"\x0D\x0A"在文件中的原始字符串实际上是"\\x0D\\x0A"。因为""是用于转义的,所以想要显示反斜杠本身就必须转义自身。也就是说,print的时候,"\"被转义成"",所以我们看到的是"\x0D",实际文件中储存的是"\\0D",而我们真正需要写入文件的,是"\x0D"这个转义字符,而不是"\x0D"这个字符串。

由于文件是以二进制方式打开的,读取到的都是原始的流,所以在匹配的时候需要使用"\\\\"来表示"\\"。

使用re库处理日志:

原始数据前面是Content-Transfer-Encoding: binary加上两个\r\n,后面是一个\r\n跟上表单分割字符串--SgX***yE7dwyg0smH-Tqpt-ggGQwTU9--

>>> import re 
>>> pf = re.compile('^.*Content-Transfer-Encoding: binary\\\\x0D\\\\x0A\\\\x0D\\\\x0A'
>>> pb = re.compile('\\\\x0D\\\\x0A--.*$'
>>> data = re.sub(pf,'',data) 
>>> data 
"\\xFF\\xD8\\xFF\\xE0\\x00\\x10JFIF\\x00\\x01\\x01\\x00\\x00\\x01\\x00 ... \\xBC'\\xF1\\x8C\\xCC\\x83,\\xFAo\\xD2_\\xA0\\x1A\\x7F\\xFF\\xD9\\x0D\\x0A--SgX***yE7dwyg0smH-Tqpt-ggGQwTU9--\\x0D\\x0A | 42097 | - | - | - | 1.1.1.1 | d1fkkbcd02eb | 127.0.0.1:8888 | 0.034 | 0.123" 
>>> data = re.sub(pb,'',data) 
>>> data 
"\\xFF\\xD8\\xFF\\xE0\\x00\\x10JFIF\\x00\\x01\\x01\\x00\\x00\\x01\\x00 ... \\xBC'\\xF1\\x8C\\xCC\\x83,\\xFAo\\xD2_\\xA0\\x1A\\x7F\\xFF\\xD9" 
  • 1.
  • 2.
  • 3.
  • 4.
  • 5.
  • 6.
  • 7.
  • 8.
  • 9.

这样的匹配处理并不严谨,假设原始数据中本来就含有\\x0D\\x0A--的话,就会丢失原始数据,但目前为止还没遇到这种情况有效。更麻烦的做法是先匹配出表单分割字符串,然后以此为界分割出数据后,再删除两边多余的字符和回车换行。

成功提取出原始数据后,对数据进行解码,并写入.jpg文件。如果没有解码这一步,所有的数据都会被当做字符串写入文件,而不会被当成转义字符。简单做个测试:

把"\xFF\xD8\xFF\xE0\x00\x10JFIF\x00\x01"保存到文件test.log。

>>> f = open('test.log''rb'
>>> data = f.read() 
>>> data 
'\\xFF\\xD8\\xFF\\xE0\\x00\\x10JFIF\\x00\\x01\n' 
>>> print data 
\xFF\xD8\xFF\xE0\x00\x10JFIF\x00\x01 
 
>>> import codecs as c 
>>> c.decode(data, 'string_escape'
'\xff\xd8\xff\xe0\x00\x10JFIF\x00\x01\n' 
>>> print c.decode(data, 'string_escape'
����JFIF 
 
>>> 
  • 1.
  • 2.
  • 3.
  • 4.
  • 5.
  • 6.
  • 7.
  • 8.
  • 9.
  • 10.
  • 11.
  • 12.
  • 13.
  • 14.

读取的原始数据是两根反斜杠,print时因为反斜杠被转义成字符,所以变成一根。解码后的数据本身只有一根斜杠,print时打印出转义字符本身,也就是乱码。

接下来就可以分割文件,然后还原图片了。

#!/usr/bin/env python 
 
import codecs, re 
 
 
ifile = 'access.log' 
suffix = 'jpg' 
 
pf = re.compile('^.*Content-Transfer-Encoding: binary\\\\x0D\\\\x0A\\\\x0D\\\\x0A'
pb = re.compile('\\\\x0D\\\\x0A--.*$'
 
try: 
    with open(ifile, 'rb'as f: 
        number = 1 #***张图片序号为1 
        while True
            l = f.readline().strip() #读取一行并去掉末尾的换行符\n 
            if not l: #文件读完返回''时退出循环 
                break 
            l = re.sub(pf, '', l) #将数据前的字符替换为空 
            l = re.sub(pb, '', l) #将数据后的字符替换为空 
            img_file = '.'.join([str(number), suffix]) #图片文件名称 
            print img_file #打印名字方便看进度 
            with open(img_file, 'wb'as i: #解码并写入文件 
                i.write(codecs.decode(l, 'string_escape')) 
            number += 1 #下一张图片序号加1 
except IOError: 
    pass 
  • 1.
  • 2.
  • 3.
  • 4.
  • 5.
  • 6.
  • 7.
  • 8.
  • 9.
  • 10.
  • 11.
  • 12.
  • 13.
  • 14.
  • 15.
  • 16.
  • 17.
  • 18.
  • 19.
  • 20.
  • 21.
  • 22.
  • 23.
  • 24.
  • 25.
  • 26.
  • 27.

access.log含有多条日志,每条日志都包含图片数据。日志较大,所以不使用readlines(),本来使用list也很占内存,且这样处理比较慢,要等程序把整个文件读完。按行读取日志后提取出图片数据后解码写入文件即可。

责任编辑:武晓燕 来源: cnblogs
相关推荐

2010-06-09 13:02:29

MySQL启用二进制日

2010-10-13 15:45:23

MySQL二进制日志

2018-10-22 14:37:16

二进制数据存储

2009-12-16 10:49:42

Ruby操作二进制文件

2009-08-12 18:06:53

C#读取二进制文件

2014-08-06 10:10:52

MariaDB二进制日志

2024-02-01 09:04:12

2013-04-28 15:37:35

JBoss

2009-12-10 09:24:50

PHP函数fwrite

2023-09-18 23:50:25

二进制文件裁剪Layout

2013-07-29 11:19:16

iOS开发iOS开发学习FMDB更新二进制图片

2009-02-27 09:37:33

Google二进制代码

2022-10-31 08:02:42

二进制计算乘法

2023-12-26 15:10:00

处理二进制文件

2020-05-22 18:00:26

Go二进制文件编程语言

2009-11-02 11:27:42

VB.NET二进制文件

2025-05-13 08:10:00

MySQL二进制日志binlog

2018-03-12 14:33:49

数据库MySQL日志

2017-04-11 10:48:53

JS二进制

2022-07-26 13:00:01

安全符号源代码
点赞
收藏

51CTO技术栈公众号