Новое в 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
Другие вопросы по тегам