Python 使用file.readline(size)后,忽略读取的行的其余部分

Python 使用file.readline(size)后,忽略读取的行的其余部分,python,security,Python,Security,我遇到了一个问题 我有一个Python应用程序,它将部署在不同的地方。因此,斯泰克先生很可能会修补这个应用程序 因此,问题与安全有关。应用程序将接收从远程源接收的文件(纯文本)。该设备的RAM数量非常有限(Raspberry Pi) 非常有可能向脚本提供非常大的输入,这将是一个大麻烦。 我希望避免“按原样”读取文件的每一行,而是只读取限制为44字节的第一行,而忽略其余部分 因此,就本案而言,一个非常粗糙的样本: lines = [] with open("path/to/file.txt", "

我遇到了一个问题

我有一个Python应用程序,它将部署在不同的地方。因此,斯泰克先生很可能会修补这个应用程序

因此,问题与安全有关。应用程序将接收从远程源接收的文件(纯文本)。该设备的RAM数量非常有限(Raspberry Pi)

非常有可能向脚本提供非常大的输入,这将是一个大麻烦。
我希望避免“按原样”读取文件的每一行,而是只读取限制为44字节的第一行,而忽略其余部分

因此,就本案而言,一个非常粗糙的样本:

lines = []
with open("path/to/file.txt", "r") as fh:
    while True:
        line = fh.readline(44)
        if not line:
            break
        lines.append(line)
这是可行的,但如果一行的长度超过44个字符,下一次读取将是该行的其余部分,甚至是同一行的多个44字节长的部分。 说明:

print(lines)
['aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa',
 'aaaaaaaaaaaaaaaaaaaaaaaaa \n', 
 '11111111111111111111111111111111111111111111', 
 '111111111111111111111111111111111111111\n', 
 'bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb', 
 'bbbbbbbbbbbbbbb\n', 
 '22222222222222222222222222222222222222222\n',
 'cccccccccccccccccccccccccccccccccccccccccccc', 
 'cccccccccccccccccccccccccccccccccccccccccccc', 
 'cccc\n', 
 '333333333333\n', 
 'dddddddddddddddddddd\n']
这并不能避免我将整个内容读入变量,并可能导致整洁的DOS

我认为使用
file.next()
可能会跳到下一行

lines = []
with open("path/to/file.txt", "r") as fh:
    while True:
        line = fh.readline(44)
        if not line:
            break   
        if line != "":
            lines.append(line.strip())
            fh.next()
但这带来了一个错误:

Traceback (most recent call last):
  File "./test.py", line 7, in <module>
    line = fh.readline(44)
ValueError: Mixing iteration and read methods would lose data
因此,输出现在如下所示:

print(lines)
['aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa',
'11111111111111111111111111111111111111111111',
'bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb',
'22222222222222222222222222222222222222222\n',
'cccccccccccccccccccccccccccccccccccccccccccc',
'333333333333\n',
'dddddddddddddddddddd\n']
'''
$cat test.txt 
aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa
bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb
ccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
'''
from time import sleep # trust me on this one

lines = []
with open("test.txt", "r") as fh:
    while True:
        line = fh.readline(44)
        print (line.strip())
        if not line:
            #sleep(0.05)
            break
        lines.append(line.strip())
        if not line.endswith("\n"):
            while fh.readline(1) != "\n":
                pass
print(lines)
哪一个足够近

我不敢说这是最好的或是一个好的解决方案,但它似乎可以完成这项工作,而且我根本没有将行的冗余部分存储在变量中

但出于好奇,我实际上有个问题。 如上所述:

fh.readline()
当您调用这样一个方法而不将其输出重定向到变量或其他对象时,它在哪里存储输入,它的生存期是什么(我的意思是,如果它被存储,它将在什么时候被销毁)

谢谢大家的投入。我学到了一些有用的东西。 我真的不喜欢
file.read(n)
的工作方式,尽管大多数解决方案都依赖它

多亏了你们,我只使用
file.readline(n)
:

如果我的想法是正确的,内部while循环将读取相同的行块,直到它读取EOL char,同时,它将一次又一次地只使用一个大小变量。 这提供了一个输出:

['"Alright,"', 
 '"You\'re re', 
 '"Tell us!"', 
 '"Alright,"', 
 'Question .', 
 '"The Answe', 
 '"Yes ...!"', 
 '"Of Life,', 
 '"Yes ...!"', 
 '"Yes ...!"', 
 '"Is ..."', 
 '"Yes ...!!', 
 '"Forty-two'] 

"Alright," said the computer and settled into silence again. The two men fidgeted. The tension was unbearable.
"You're really not going to like it," observed Deep Thought.
"Tell us!"
"Alright," said Deep Thought.
Question ..."
"The Answer to the Great
"Yes ...!"
"Of Life, the Universe and Everything ..." said Deep Thought
"Yes ...!" "Is ..." said Deep Thought, and paused.
"Yes ...!"
"Is ..."
"Yes ...!!!...?"
"Forty-two," said Deep Thought, with infinite majesty and calm.
当你只是这样做:

f.readline()
从文件中读取一行,并分配、返回、然后丢弃一个字符串

如果您有非常大的行,即使您没有存储值,也可以通过调用
f.readline()
(某些文件损坏时会发生这种情况)来耗尽内存(在分配/重新分配阶段)

限制行的大小是可行的,但如果再次调用
f.readline()
,则会得到行的其余部分。诀窍是跳过剩余的字符,直到找到行终止字符。我将如何做的一个简单的独立示例:

max_size = 20
with open("test.txt") as f:
    while True:
        l = f.readline(max_size)
        if not l:
            break   # we reached the end of the file
        if l[-1] != '\n':
            # skip the rest of the line
            while True:
                c = f.read(1)
                if not c or c == "\n":  # end of file or end of line
                    break
        print(l.rstrip())
该示例读取一行的开头,如果该行已被截断(即,当它没有以行结尾时),我将读取该行的其余部分,并将其丢弃。即使行很长,也不会消耗内存。只是太慢了

关于组合
next()
readline()
:这些是并发机制(手动迭代与经典行读取),它们不能混合,因为一个方法的缓冲可能会被另一个方法忽略。但是您可以混合使用
read()
readline()
循环和
next()

试着这样做:

print(lines)
['aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa',
'11111111111111111111111111111111111111111111',
'bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb',
'22222222222222222222222222222222222222222\n',
'cccccccccccccccccccccccccccccccccccccccccccc',
'333333333333\n',
'dddddddddddddddddddd\n']
'''
$cat test.txt 
aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa
bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb
ccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc
'''
from time import sleep # trust me on this one

lines = []
with open("test.txt", "r") as fh:
    while True:
        line = fh.readline(44)
        print (line.strip())
        if not line:
            #sleep(0.05)
            break
        lines.append(line.strip())
        if not line.endswith("\n"):
            while fh.readline(1) != "\n":
                pass
print(lines)
非常简单,它将读取44个字符,如果它没有以新行结尾,它将一次读取1个字符,直到到达它,以避免内存中出现大块,只有到那时它才会处理接下来的44个字符并将它们附加到列表中


不要忘记使用
line.strip()
,以避免
\n
短于44个字符时成为字符串的一部分。

我假设您在这里问的是您的原始问题,而不是关于临时值(哪些)的附带问题

您现有的解决方案实际上并不能解决您的问题

假设攻击者创建了一行1亿个字符长的代码。因此:

  • 执行
    fh.readline(44)
    ,读取前44个字符
  • 然后执行
    fh.readline()
    以丢弃该行的其余部分。这需要将行的其余部分读入一个字符串以丢弃它,因此它会占用100MB
您可以通过在循环中一次读取一个字符直到
'\n'
,来处理这个问题,但是有一个更好的解决方案:在循环中读取fh.readline(44)
,直到
'\n'
。或者,
fh.readline(8192)
或者一些暂时浪费8KB的东西(实际上,重复使用8KB)对攻击者没有帮助

例如:

while True:
    line = fh.readline(20)
    if not line:
        break
    lines.append(line.strip())
    while line and not line.endswith('\n'):
        line = fh.readline(8192)
在实践中,这不会有多大的效率。Python2.x文件对象包装一个C stdio
文件
,该文件已经有一个缓冲区,并且默认参数为
open
,它是由平台选择的缓冲区。假设您的平台使用16KB

因此,无论您是
read(1)
还是
readline(8192)
,它实际上是一次从磁盘读取16KB的数据到某个隐藏的缓冲区,然后将缓冲区中的1或8192个字符复制到Python字符串中

而且,虽然循环16384次并构建16384个小字符串显然比循环两次并构建两个8K字符串需要更多的时间,但这一时间可能仍然比磁盘I/O时间要短

因此,如果您更好地理解
阅读(1)
代码,并且可以更轻松地调试和维护它,那么就这样做吧


然而,这里可能有更好的解决方案。如果您在64位平台上,或者您的最大文件小于2GB(或者在处理文件之前,大于2GB的文件出现错误是可以接受的),您可以
mmap
该文件,然后将其作为内存中的一个巨大字符串进行搜索:

from contextlib import closing
import mmap

lines = []
with open('ready.py') as f:
    with closing(mmap.mmap(f.fileno(), 0, access=mmap.ACCESS_READ)) as m:
        start = 0
        while True:
            end = m.find('\n', start)
            if end == -1:
                lines.append(m[start:start+44])
                break
            lines.append(m[start:min(start+44, end)])
            start = end + 1
<