1

I want to change an image when I click on it with an image from another drive. For this I created this python function:

@main.route('/tester/', methods=['GET', 'POST'])
def tester():
    if request.method == "GET":
        test_img = "D:\TBV_Data\MS_CO_Front_20120403_140154_0001140_006.png"
        return send_file(test_img, mimetype='image/png')  

I request this data by the following function:

function testFunc() {
    $.ajax({
        url: '/tester', //server url
        type: 'GET',    //passing data as post method
        async: false,
        success: function(data) {
            $("#myimage9").attr("src", "data:image/png;base64," + data);
        },
    });
};

The outcome of the "src" of the image is, unfortunately, a load of weird data:

<img id="myimage9" src="data:image/png;base64,�PNG
IHDR�I!�IDATx���׮mK�&amp;�}�;��morg��c�V��)C�� B��.�(z�� ��� ��*��B�y2�I��^~��]D�1��ÁDb�9��&amp;�E����o-���OZl��/_���NJ��%�%�т���6�ݴw�~��EE���-�[p�z^3Y����8��#�

I can imagine that I didn't encode the image correctly. Could somebody please tell me what I am doing wrong?

EDIT: I tried to encode the data to base64 with:

function utoa(str) {
    return window.btoa(unescape(encodeURIComponent(str)));
};

This will unfortunately just changes the data to the following, but will not show the image:

<img id="myimage9" src="data:image/png;base64,/VBORw0KGgoAAAANSUhEUgAABRAAAAP9CAIAAAAUSSH9AAEAAElEQVR4/f397m1L/Sb9ff0OO/39bW9yZ/39Y/0dVv39KUP9/SBC/f0u/Sh6A
Brian Barbieri
  • 293
  • 2
  • 15
  • When you use `` what happens? – Tomalak Oct 23 '19 at 13:44
  • This was it! Wished I had read your answer sooner, but thanks for the help anyway! – Brian Barbieri Oct 23 '19 at 14:14
  • BTW, did you notice that your Ajax call says `'GET'`, directly contradicting the comment on that line, which says `//passing data as post method`? Never comment the obvious, then these things cannot happen. – Tomalak Oct 23 '19 at 15:26

2 Answers2

1

On the server side, add a filename variable to your route:

import os
from flask import request, send_from_directory

IMAGE_DIR = r"D:\TBV_Data"

@main.route('/tester/<filename>')
def tester(filename):
    return send_from_directory(IMAGE_DIR, filename)

This uses the send_from_directory function to make sure that the client cannot download files from outside the designated directory by entering filenames that contain things like \..\ (this is known as "directory traversal attack").

And on the client side:

function testFunc(selector, filename) {
    $(selector).attr("src", "/tester/" + encodeURIComponent(filename) );
}

The use of encodeURIComponent() is good style and prevents problems with filenames that contain special characters. The browser will request the image as soon as its src attribute changes, there is no need to do anything via Ajax here.


Preventing a directory traversal attack with send_file() alone is a bit more involved:

filepath = os.path.realpath(os.path.join(IMAGE_DIR, filename))
if os.path.commonprefix([filepath, IMAGE_DIR]) != IMAGE_DIR:
    abort(404)

return send_file(filepath)

File paths on Windows are case-insensitive, so IMAGE_DIR should contain the actual case of the path, otherwise this test will fail.

Tomalak
  • 332,285
  • 67
  • 532
  • 628
0

Solved:

function testFunc(args) {
        selector = args.data[0];
        filename = args.data[1];
        var obj = JSON.stringify({"data" : filename});
        console.log(obj);
        $.ajax({
            url: '/tester2/', //server url
            type: 'GET',    //passing data as post method
            data: obj,
            async: false,
            success: function(data) {
                $(selector).attr("src", "/tester2/");
            },
        });
    }; 

I was assigning the raw data to the src attribute, but I needed to assign the URL of the GET request to the src attribute.

Brian Barbieri
  • 293
  • 2
  • 15
  • 1
    Not a good solution. There is no reason whatsoever to call `$.ajax()` at all here. Remove that entire Ajax call and replace it by `$("#myimage9").attr("src", "/tester/");` – Tomalak Oct 23 '19 at 14:25
  • But what if I want to change the image in "/tester/" on the server-side by data send from the client-side? Is there a better way than an ajax call? – Brian Barbieri Oct 23 '19 at 14:45
  • 1
    Just set the `src` attribute again. The browser will issue a new HTTP request when the `src` attribute of an image changes. You can set the img src to something with URL parameters, if you want to. – Tomalak Oct 23 '19 at 14:47
  • 1
    If you want to prevent the client from caching the image, let the server send an appropriate `Cache-Control` header along with the image. This can be useful if you want serve changing images under the same URL, but *ideally* the same URL always loads the same image, in which case the client may cache it. – Tomalak Oct 23 '19 at 14:49
  • Yes but how will I send the file name from the client-side to the server-side to before I change the src attribute? – Brian Barbieri Oct 23 '19 at 14:59
  • I'm not following. Your code samples send nothing to the server. – Tomalak Oct 23 '19 at 15:24
  • Sorry I have updated my Ajax call it is the following now (see my anwer) }; ` – Brian Barbieri Oct 23 '19 at 16:08
  • You are making things much too complicated. Edit your server side route handler so that it [accepts query string parameters](https://stackoverflow.com/q/24892035/18771) (don't use JSON, there is no need for JSON here). Test the server side by opening `/tester/?data=some_filename.jpg` in the browser and load a few different files by changing the URL parameter. When that works, use `$("#myimage9").attr("src", "/tester/" + $.param({data: filename}) );` in the client. – Tomalak Oct 23 '19 at 16:28
  • Let us [continue this discussion in chat](https://chat.stackoverflow.com/rooms/201341/discussion-between-brian-barbieri-and-tomalak). – Brian Barbieri Oct 23 '19 at 16:43
  • I've just added my own answer, have a look. – Tomalak Oct 23 '19 at 16:48