Новое в HTML и Flask. Как разрешить пользователю отправлять изображение и описание без входа в систему?
В настоящее время я занимаюсь разработкой стеганографического веб-приложения в рамках моей летней программы по компьютерным наукам для начинающих. С очень ограниченным пониманием HTML, Flask и взаимосвязанных языков, таких как SQL и jquery, я столкнулся с некоторым препятствием. До сих пор я смотрел много уроков для начинающих, но все они имеют тенденцию фокусироваться на создании форумов с пользователями и возможностью публикации.
Мое видение для веб-приложения состоит в том, чтобы иметь страницу под названием "шифровать", в которой есть два обязательных поля (текст / сообщение и изображение для вставки текста), и кнопку отправки, которая затем запускает мою программу на python и дает пользователю изображение с скрытое сообщение. Через массу поисковых запросов в Интернете я нашел способы сделать форму с двумя полями и загрузить ее в папку.
Однако где-то посередине мое веб-приложение стало сильно напоминать монстра Франкенштейна с кусочками кода, взятыми из каждого места, сшитого в один искаженный и, возможно, (читайте, вероятно) некогерентный код. Конечно, в результате этого мой код не может сделать то, что был создан.
Мой стеганографический код работает достаточно хорошо (показано, чтобы дать лучшее понимание того, чего я стремлюсь достичь. Большая цель этого более применима, поскольку она направлена на использование нескольких значений, представленных пользователем, для запуска программы на языке Python и не требует регистрации члены, желание, которым большинство учебников пренебрегают):
def packing(s):
'''converts a characters 8 bits into 4 two-bit values and adds them to a
list using a for loop'''
l = []
for i in range(len(s)):
x = ord(s[i])
top2 = (x & 0xC0) >> 6
middletop2 = (x & 0x30) >> 4
lowertop2 = (x & 0xC) >> 2
lower2 = (x & 0x3)
l.extend([top2, middletop2, lowertop2, lower2])
length = len(l)
h1 = (length & 0xC0000000) >> 30
h2 = (length & 0x30000000) >> 28
h3 = (length & 0x0C000000) >> 26
h4 = (length & 0x03000000) >> 24
h5 = (length & 0x00C00000) >> 22
h6 = (length & 0x00300000) >> 20
h7 = (length & 0x000C0000) >> 18
h8 = (length & 0x00030000) >> 16
h9 = (length & 0x0000C000) >> 14
hA = (length & 0x00003000) >> 12
hB = (length & 0x00000C00) >> 10
hC = (length & 0x00000300) >> 8
hD = (length & 0x000000C0) >> 6
hE = (length & 0x00000030) >> 4
hF = (length & 0x0000000C) >> 2
hF1 = (length & 0x00000003)
l = ([h1] + [h2] + [h3] + [h4] + [h5] + [h6] + [h7] + [h8] + [h9] +
[hA] + [hB] + [hC] + [hD] + [hE] + [hF] + [hF1] + l)
return l
def bitsIntoImage(pic, l):
'''wipes the last two bits of each R, G and B value for every pixel
nevessary to import the message. Then writes the rest of the image onto the
new image to return a complete image.'''
pic = Image.open( pic )
draw = ImageDraw.Draw(pic)
(width, height) = pic.size
newPic = Image.new('RGB', (width,height))
drawnewPic = ImageDraw.Draw(newPic)
if len(l) % 3 == 1:
l = l + [0,0]
if len(l) % 3 == 2:
l = l + [0]
redL = l[0::3]
greenL = l[1::3]
blueL = l[2::3]
for y in xrange(height):
for x in xrange(width):
if len(redL) > 0:
openRed = pic.getpixel((x,y))[0] &~ 0x3
openGreen = pic.getpixel((x,y))[1] &~ 0x3
openBlue = pic.getpixel((x,y))[2] &~ 0x3
codedRed = openRed | redL[0]
codedGreen = openGreen | greenL[0]
codedBlue = openBlue | blueL[0]
redL = redL[1:]
greenL = greenL[1:]
blueL = blueL[1:]
drawnewPic.point([x,y], (codedRed, codedGreen, codedBlue))
else:
(R, G, B) = pic.getpixel((x,y))
drawnewPic.point([x,y], (R, G, B))
return newPic
def step1(pic, s):
'''pic = picture, s = message/string. Turns the string into a list of
double-bit information, then imports that string of information into the image'''
l = packing(s)
picture = bitsIntoImage(pic, l)
return picture
Но я почти уверен, что мой реальный код не может удерживать отправленное пользователем изображение и сообщение таким образом, что моя программа стеганографии может фактически использовать значения:
import os
import tempfile
import re
from flask.ext.wtf import Form
from wtforms import StringField, TextAreaField, FileField, validators
from wtforms.validators import DataRequired
from flask_wtf.file import FileAllowed, FileRequired
from werkzeug import secure_filename
from PIL import Image, ImageDraw
from steganography import packing, bitsIntoImage, step1, MinaB, unpacking, step2
UPLOAD_FOLDER = '/Users/AustinMossEnnis/practice/uploads/'
ALLOWED_EXTENSIONS = set(['png'])
def allowed_file(filename):
return '.' in filename and \
filename.rsplit('.', 1)[1] in ALLOWED_EXTENSIONS
app = Flask(__name__)
app.config['SECRET_KEY'] = 'string'
app.config['UPLOAD_FOLDER'] = UPLOAD_FOLDER
app.config['MAX_CONTENT_LENGTH'] = 16 * 1024 * 1024
class EncryptForm(Form):
image = FileField(u'Upload your image:', validators = ['png'])
message = TextAreaField(u'Enter your message:', [validators.Length(min=1)])
def validate_image(form, field):
if field.data:
field.data = re.sub(r'[^a-z0-9_.-]', '_', field.data)
@app.route('/')
def index():
return render_template('index.html')
@app.route('/encrypt', methods=['GET', 'POST'])
def encrypt():
form = EncryptForm(request.form)
if request.method == "POST" and 'docs' in request.files:
#I had tried using "if form.image.data and form.validate():" to no avail
image_data = request.FILES[form.image.name].read()
step1(image_data, form.message())
file.save(os.path.join(app.config['UPLOAD_FOLDER'], newPic))
redirect(url_for('uploaded_file',
newPic=filename))
return render_template('encrypt.html',
title='Encrpyt',
form=form)
@app.route('/encrypt/<filename>')
def encrypted_file(filename):
return send_from_directory(app.config['UPLOAD_FOLDER'],
filename)
if __name__ == '__main__':
app.run(
host="0.0.0.0",
port=int("5000"),
debug=True
)
Как вы можете судить по моему остаточному импорту, я перепробовал множество вещей со смешанными результатами. По пути я получил "error, v # invalid expression", "TypeError: объект 'str' не вызывается", среди других сообщений об ошибках.
Итак, в целом, мой вопрос заключается в том, как я могу правильно взять значения, представленные пользователем, интегрировать их в мою программу, а затем вернуть продукт этой программы?
Было бы предпочтительнее иметь временную папку какого-либо рода? Нужно ли создавать базы данных независимо от того, есть ли у меня пользователи? Я много пробовал, но потерпел неудачу либо из-за неправильного выполнения текста, либо из-за непонимания кода, который пытался выполнить.
1 ответ
Вы хотите, чтобы пользователь предоставил свой собственный файл изображения, поэтому файл должен быть загружен. Первая ссылка с двумя текстовыми полями просто передает две строки из формы запроса. Если сервер не имеет локально файла с тем же именем файла, которое передано из текстового поля (и не должен, поскольку вы пытаетесь загрузить файл, которого у него нет), он не сможет получить доступ к данным. Вторая предоставленная вами ссылка содержит почти 90% вашего решения. Мы сосредоточимся на этом и немного доработаем под наши нужды.
Блок формы является единственной важной частью в HTML-файле. Он создает входные данные для загрузки файла и исходные данные для отправки формы. Вы можете прочитать больше о различных типах входов здесь. Нам просто нужен дополнительный текстовый ввод для нашего сообщения.
<form action="upload" method="post" enctype="multipart/form-data">
<input type="file" name="file"><br>
<input type="text" name="message"><br><br>
<input type="submit" value="Upload">
</form>
Что касается Python, нам нужно только изменить upload
функция. Но давайте разберемся, что он делает первым.
def upload():
file = request.files['file']
if file and allowed_file(file.filename):
filename = secure_filename(file.filename)
file.save(os.path.join(app.config['UPLOAD_FOLDER'], filename))
return redirect(url_for('uploaded_file', filename=filename))
request
Объект содержит все данные из представленной нами формы. Вы можете увидеть все его атрибуты / методы, напечатав dir(request)
, request.files
содержит данные из всех загруженных файлов. По расширению, request.values
содержит данные из текстовых полей. Обе они представляют собой специальные словарные структуры, но, как и обычный словарь, вы можете получить доступ к значению с помощью его ключа. Поскольку в html-файле мы называли наши входные данные "file" и "message", мы получаем к ним доступ.
Загруженный файл, доступ к которому осуществляется с request.files['file']
, хранится в другой специальной структуре данных, называемой FileStorage. Из этого класса мы можем получить доступ к имени файла, который мы загрузили с filename
атрибут, и мы можем сохранить фактические данные на наш сервер с save()
метод. os.path.join
Функция просто объединяет путь к папке загрузки с именем файла, чтобы мы могли определить место назначения сохраненного файла на нашем сервере.
Есть одна новая вещь, которую мы заинтересованы в этой структуре данных, и это stream
приписывать. Это возвращает фактические двоичные данные нашего загруженного файла в виде буферизованного потока. Мы можем использовать это вместе с нашим сообщением и передать их вашей функции стеганографии. В общем, наш модифицированный код должен выглядеть следующим образом.
def upload():
file = request.files['file']
text = request.values['message']
if file and allowed_file(file.filename) and text:
# =============================================================
# We do our embedding here and return the modified image stream
file.stream = embed(file.stream.read(), text)
# =============================================================
fname = secure_filename(file.filename)
file.save(os.path.join(app.config['UPLOAD_FOLDER'], fname))
return redirect(url_for('uploaded_file', filename=fname))
Последняя модификация связана с вашей функцией встраивания. Когда вы передаете имя файла или поток байтов в Image.open
Вы создаете объект Image. Чтобы создать поток файлов из этого, вы обычно сохраняете данные в файл и загружаете их снова с чем-то вроде open('filename', 'rb').read()
, Но так как мы хотим сделать все это в памяти, мы можем использовать StringIO. По сути, ваша функция встраивания должна выглядеть примерно так.
from io import BytesIO
from StringIO import StringIO
def embed(stream, text):
image = Image.open(stream)
bits = packing(text)
# pixel modification goes here...
output = StringIO.StringIO()
image.save(output, format='png')
contents = BytesIO(output.getvalue())
output.close()
return contents
Хотя это и необязательно, я предлагаю несколько советов по улучшению читабельности вашего встраиваемого кода. У вас есть несколько повторений, которые можно упростить, и вы можете избежать создания второго объекта Image, если вы все равно собираетесь отказаться от первого. Вы также не должны проходить все пиксели, если у вас есть только несколько бит для встраивания. Это может серьезно повлиять на большие изображения.
def packing(s):
'''Convert a message into 2-bit groups with a size header'''
# Store the size of the message bit groups first
length = len(s) * 4
bit_groups = [(length >> i) & 0x03 for i in xrange(30, -1, -2)]
for char in s:
byte = ord(char)
byte_decomposition = [(byte >> i) & 0x03 for i in xrange(6, -1, -2)]
bit_groups.extend(byte_decomposition)
return bit_groups
def embed(stream, text):
'''Embed a message in the RGB pixels of an image in groups of 2 bits.'''
image = Image.open(stream)
bits = packing(text)
mask = ~0x03
max_width = image.size[0]
height = 0
width = 0
color = 0
for b in bits:
if color == 0:
pixel = list(image.getpixel((width, height)))
pixel[color] = (pixel[color] & mask) | b
color += 1
if color == 3:
image.putpixel((width, height), tuple(pixel))
color = 0
width += 1
if width == max_width:
width = 0
height += 1
# Convert the Image object to a ByteIO stream as shown above and return
return contents