New to HTML and Flask. How to let a user submit an image and description without having to sign in?

1

I am currently designing a steganography web app as a part of my beginner Computer Science summer program. With a very limited understanding of HTML, Flask, and the interconnected languages like SQL and jquery, I've ran into a bit of a roadblock. So far I've looked at a lot of beginner's flask tutorials, but they all have a tendency to focus on creating forums with users and the ability to post.

My vision for the web app is to have a page titled "encrypt" that has two fields required (text/message and an image to put the text in) and a submit button that then runs my python program and gives the user the image with the hidden message. Through a ton of web searches, I've found ways to make a form with two fields and how to upload to a folder.

Somewhere in the middle, however, my web app began to strongly resemble Frankenstein's monster with bits of code taken from every which place stitched into one disfigured and possibly (read probably) incoherent code. Of course, as a result of this, my code fails to do the very thing it was made to do.

My steganography code works well enough (shown to give a better understanding of what I aim to achieve. The larger goal of this is more applicable because it aims to use multiple user-submitted values in order to run a python program and does not involve registered members, a desire that most tutorials neglect):

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

But I'm pretty sure that my actual code fails to hold the user submitted image and message in such a way that my steganography program can actually use the values:

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
    )

As you can tell from my residual imports, I've tried a ton of things with mixed results. Along the way, I've gotten "error, v # invalid expression", "TypeError: 'str' object is not callable", amongst other error messages.

So, in summary, my question is how I can properly take the values submitted by the user, integrate them into my program, and then give back the product of that program?

Would it be preferable to have a temp folder of some sort? Is it necessary to create databases regardless of whether or not I have users? I've tried a lot but have failed either due to a failure to properly execute the text or as a result of not understanding the code I was trying to execute.

python
forms
flask
user-input
steganography
asked on Stack Overflow Sep 4, 2015 by Austin M. • edited Jun 20, 2020 by Community

1 Answer

2

You want the user to provide his own image file, so the file must be uploaded. The first link with the two text fields just passes on two strings from the request form. Unless the server has a file locally with the same file name as passed from the text field (and it shouldn't, since you're trying to upload a file it doesn't have), it can't access the data. The second link you provided has almost 90% of your solution. We'll focus on this and slightly modify it to our needs.

The form block is the only important part in the html file. It creates a file input to upload a file and a submit input to send the form. You can read more about the various types of inputs here. We just need an extra text input for our message.

<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>

On the python side of things, we only need to change the upload function. But let's understand what it does first.

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))

The request object contains all the data from our submitted form. You can see all its attributes/methods by printing dir(request). request.files holds the data from all uploaded files. By extension, request.values holds the data from text fields. Both of these are special dictionary structures, but just like a normal dictionary, you can access a value with its key. Since in the html file we called our inputs "file" and "message", that's how we access them.

The uploaded file, accessed with request.files['file'], is stored in another special data structure called FileStorage. From this class we can access the name of the file we uploaded with the filename attribute and we can save the actual data to our server with save() method. The os.path.join function just concatenates the upload folder path with the file name so we can define the destination of the saved file on our server.

There's one new thing we're interested in this data structure and that's the stream attribute. This returns the actual binary data of our uploaded file as a buffered stream. We can use this along with our message and pass them on to your steganography function. All in all, our modified code should look like this.

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))

The last modification has to do with your embedding function. When you feed a file name or byte stream to Image.open, you create an Image object. In order to create a file stream out of that, you would normally save the data to a file and load it again with something like open('filename', 'rb').read(). But since we want to do all of this in memory, we can use StringIO. Basically, your embedding function must look something like this.

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

While this is optional, I'm offering some suggestions to improve the readability of your embedding code. You have a few repetitions which can be streamlined and you can avoid creating a second Image object if you're going to discard the first one anyway. You should also not go through all of the pixels if you have only a few bits to embed. This can a serious impact with big images.

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
answered on Stack Overflow Sep 4, 2015 by Reti43 • edited May 23, 2017 by Community

User contributions licensed under CC BY-SA 3.0