Friday 19 May 2017

Node mysql not escaping the ? after XMLHttpRequest at a different time

So I'm trying to get a list of events from my database using a filter. You type in what you want to filter by and hit submit, then the browser makes an XMLHttpRequest to a get route, which sends back the list of events, and the browser renders them on the page without reloading. Supposedly, you should be able to then click on one of these events and it will bring you to that event's page with more detail - except when it tries to query the database for that specific event, the node mysql module doesn't even replace the question mark like it's intended to. If I go straight to the specific event page without running the filter, it makes the query just fine, but if I do the filter first, it leaves the query as a literal string, without adding the insert, even though I know it's there. That's the mystery - it's just not escaping on the ? after the filter query.

The maddening part is that it was working just fine, then I changed some stuff I would have thought was unrelated (mostly how to insert new events), then it broke. But I don't know how long ago that was.

I'm using node js with express, the mysql library from npm, and jade for my template engine. I would love to include a small code snippet, but I'm so clueless about what the problem is, I can't figure out which parts are expendable. So here's a big code snippet.

The jade file that shows all the events:

h1 All Events go here

div(id = 'filter')
    span Distance:
    select(id = 'distance')
        option(value = '10') 10mi
        option(value = '25') 25mi
        option(value = '50') 50mi
    br

    span Tags:
    div(id = 'tag-control')
    br

    input(type = 'hidden', id = 'lat')
    input(type = 'hidden', id = 'lon')

    button(type = 'button', onClick = 'getEvents()', style = 'display: none;' id = 'filter-button') Submit
    div(id = 'filter-message')

ul(id = 'event-list')
    if events
        for event in events
            li: a(href = 'events/#{event.eventID}') #{event.name}
a(href = 'events/new') New event

script(src = 'js/tags.js')
script(src = 'js/events.js') // I'll show this next

script.
    (function getLocation(){
        if(navigator.geolocation){
            navigator.geolocation.getCurrentPosition((position) => {
                var lat = document.getElementById('lat');
                var lon = document.getElementById('lon');
                lat.value = position.coords.latitude;
                lon.value = position.coords.longitude;
                var button = document.getElementById('filter-button');
                button.removeAttribute('style'); // this ensures the filter button is invisible until your location is found
            });
        } else {
            alert('Could not get your location. Try manually typing in your address');
        }
    })();

The js files:

function eventFilterQuery() {
    var distance = document.getElementById('distance');
    distance = distance.options[distance.selectedIndex].value;

    var query = '?';
    distance = 'distance=' + distance;
    query += distance;

    var lat = document.getElementById('lat').value;
    var lon = document.getElementById('lon').value;

    lat = 'lat=' + lat;
    lon = 'lon=' + lon;
    query += '&' + lat + '&' + lon;

    var tagblock = document.getElementById('tags');
    var tags = [];
    tagblock.childNodes.forEach((tagspan) => {
        tags.push(tagspan.innerText);
    });

    tags.forEach((tag, i, tags) => {
        tags[i] = 'tags[]=' + tags[i];
    });
    query += '&' + tags.join('&');

    return query;
}

function getEvents(){
    var xhr = new XMLHttpRequest();
    xhr.onreadystatechange = () => {
        if(xhr.readyState == 4){
            if(xhr.status == 200){
                // clear the current list
                var eventlist = document.getElementById('event-list');
                while(eventlist.firstChild){
                    eventlist.removeChild(eventlist.firstChild);
                }

                // clear the message if there is one
                var message = document.getElementById('filter-message');
                if(message.firstChild){
                    message.removeChild(message.firstChild);
                }

                // parse the server response 
                var events = JSON.parse(xhr.responseText);

                // if there are any events, add them
                if(events.length > 0){
                    events.forEach((event) => {
                        var li = document.createElement('li');
                        var a = document.createElement('a');
                        a.setAttribute('href', 'events/' + event.eventID);
                        var text = document.createTextNode(event.name);

                        a.appendChild(text);
                        li.appendChild(a);
                        eventlist.appendChild(li);
                    });
                } else { // otherwise, let us know on the message
                    var text = document.createTextNode('No events like that yet');
                    message.appendChild(text);
                }
            }
        }
    };
    xhr.open('get', '/events/filter' + eventFilterQuery());
    xhr.send();
}

the code from the backend

router.get('/filter', (req, res) => {
    pool.getConnection((err, con) => {
        con.config.queryFormat = queryconfig; // I took this straight from the node mysql docs, a function I stored on another file so I could insert objects into the 
        var query = 'SELECT DISTINCT events.eventID, events.name, (3959 * acos(cos(radians(:lat)) * cos(radians(lat)) * cos(radians(lon) - radians(:lon)) + sin(radians(:lat)) * sin(radians(lat)))) AS distance FROM events ';

        var inserts = {
            lat: req.query.lat, 
            lon: req.query.lon, 
            distance: req.query.distance,
            tags: [req.query.tags]
        };
        if(req.query.tags){
            query += 'JOIN events_tags ON events.eventID = events_tags.eventID JOIN tags ON events_tags.tagID = tags.tagID WHERE tags.name IN :tags HAVING distance < :distance';
        } else {
            query += 'HAVING distance < :distance';
            delete inserts.tags;
        }

        con.query(query, inserts, (err, result) => {
            res.send(result);
        });
        con.release(); // I tried moving this around because I don't really know where you're supposed to put it in relation to your query, but it didn't seem to matter
    });
});

router.get('/:id', (req, res) => {
    pool.getConnection((err, con) => {
        console.log(req.params.id);

        var q = con.query('SELECT * FROM events WHERE eventID = ?', req.params.id, (err, result) => {
            console.log(q.sql); // THIS RIGHT HERE IS THE PROBLEM. If I've used the filter route at all before, this prints 'SELECT * FROM events WHERE eventID = ?' exactly, question mark and all. If I haven't used the filter route, it substitutes the id number in no problem.
            var event = result[0];
            con.query('SELECT name FROM tags JOIN events_tags ON tags.tagID = events_tags.tagID WHERE eventID = ?', event.eventID, (err, tags) => {
                event.tags = tags;
                res.render('events/show', {event: event});
            });
        });
        con.release();
    });
});

PS this is that queryFormat thing I mentioned: https://github.com/mysqljs/mysql#custom-format

Thanks in advance



via Cordello

No comments:

Post a Comment