3

I have created a REST endpoint to receive JSON data via an HTTP post, via an XMLHttpRequest as my client. It works fine. However I wanted to test if my client is sending no data at all, i.e. null, so this would be handled gracefully by my server. Except ... it crashes the server with a syntax error when I try to perform a simple test on the request, or even just to echo it out using console.log. I can't seem to work out how to test for the null condition.

The server is Express (v 4.16.4) under node.js (v 11.4.0).

The error I get is:

SyntaxError: Unexpected token n in JSON at position 0

I have done a lot of Googling for this but very little luck with token 'n'.

I have tried: Testing existence of req with

if (req) {
    ...

or

try {
    console.log(req);
} 
catch(err) {
    console.log("Caught error: "+err);
}

And even a good old:

router.route('/api/endpoint/:filename').post((req, res, err) => {
    if (err) {
        console.log("Caught error: "+err);
    } else {
        ...

This is a bit worrying, as while I can control what I send to the endpoint from my client, and I know sending a null payload makes no sense and is invalid, what if anyone else (maliciously?) posts null to the API, and trivially breaks the server?

For completeness, here's my client code and server function.

Client:

// Testing sending null data to a REST endpoint
var XMLHttpRequest = require("xmlhttprequest").XMLHttpRequest;
var xhr = new XMLHttpRequest();
var url = "http://localhost:4001/api/endpoint/delme.txt";
xhr.open("POST", url, true);
xhr.setRequestHeader("Content-Type", "application/json");
xhr.onreadystatechange = function () {
    if (xhr.readyState === 4 && xhr.status === 200) {
        var json = JSON.parse(xhr.responseText);
        console.log(json.email + ", " + json.password);
    }
};

data = null;        // Normally I'd send a JSON object
                    // e.g. data = {"stack" : "overflow"}
// data = {"stack" : "overflow"};
xhr.send(JSON.stringify(data));

Server function:

router.route('/api/endpoint/:filename').post((req, res) => {
    if (req)
        console.log("Got req");
    else    
        console.log("nothing...");

    console.log("Body....");
    console.log(req.body);
    ....

Working correctly with data = {"stack" : "overflow"}:

Got req
Body....
{ stack: 'overflow' }

Error when I send data = null:

SyntaxError: Unexpected token n in JSON at position 0
    at JSON.parse (<anonymous>)
    at createStrictSyntaxError (C:\Users\roryc\source\repos\ang09server\node_modules\body-parser\lib\types\json.js:158:10)
    ....

Thanks for any help

[UPDATE: Providing code samples to help reproduce error]

I wasn't sure if it was relevant, but I am compiling using babel. Here are four code samples:

  • package.json
  • client.js
  • server.js
  • .babelrc

I build in the root directory (after npm install) with npm run-script build I execute the server with npm start I execute the client with a simple node dist/client.js

Package.json:

{
  "name": "server",
  "version": "1.0.0",
  "description": "",
  "main": "index.js",
  "scripts": {
    "start": "nodemon --exec babel-node src/server.js",
    "test": "echo \"Error: no test specified\" && exit 1",
    "dev": "babel-watch src/server.js",
    "build": "babel src --out-dir dist",
    "serve": "node dist/server.js"
  },
  "keywords": [],
  "author": "",
  "license": "ISC",
  "dependencies": {
    "express": "^4.16.4",
    "xmlhttprequest": "^1.8.0"
  },
  "devDependencies": {
    "@babel/cli": "^7.4.4",
    "@babel/core": "^7.4.4",
    "@babel/node": "^7.2.2",
    "@babel/preset-env": "^7.4.4",
    "nodemon": "^1.19.0"
  }
}

Server.js:

import express from 'express';
import bodyParser from 'body-parser';

const app = express();
const router = express.Router();
const http = require('http').Server(app);

app.use(bodyParser.json());
app.use('/', router);

http.listen(4001, function() {
    console.log('Express listening (+web sockets) on port 4001');
});

router.route('/api/endpoint/:filename').post((req, res) => {
    console.log(req.body);  // Error appears here...

    // Function logic goes here...

});

Client.js:

// Testing sending null data to a REST endpoint
var XMLHttpRequest = require("xmlhttprequest").XMLHttpRequest;
var xhr = new XMLHttpRequest();
var url = "http://localhost:4001/api/endpoint/delme.txt";
xhr.open("POST", url, true);
xhr.setRequestHeader("Content-Type", "application/json");
xhr.onreadystatechange = function () {
    if (xhr.readyState === 4 && xhr.status === 200) {
        var json = JSON.parse(xhr.responseText);
        console.log(json.email + ", " + json.password);
    }
};

var data = null;        // Normally I'd send a JSON object, e.g. data = {"stack" : "overflow"}
// var data = [{"stack" : "overflow"}];
xhr.send(JSON.stringify(data));

.babelrc:

// .babelrc
{
    "presets": ["@babel/preset-env"]
}

Thanks for the comments and suggestions, very much appreciated.

..................

[SOLVED] [bendataclear]1 provided both the answer and (nearly) working code, which I have made very minor adjustments to and now correctly catches the null data being sent and throws an error without the server failing. Reproducing the solution here in case it helps anyone else. Big thanks to bendataclear.

// Verification function for bodyparser to test for null data being sent
const vfy = (req, res, buf, encoding) => {
    console.log("Received buffer: "+buf);
    if (buf.equals(Buffer.from([0x6e, 0x75, 0x6c, 0x6c]))){ // Check for characters 'null' in buffer
        throw new Error('null body');
    }
};

app.use(bodyParser.json({
    verify: vfy
}));
Rory
  • 167
  • 1
  • 7
  • Looks like the problem might be your data is defined, but `null`. `null` when converted to JSON becomes a literal string containing "null", however REST expects either an object or an array - not just a null. You're going to need to modify your client so it conforms to REST – Tibrogargan May 08 '19 at 04:43
  • Sorry - that's old info. Any valid JSON value is acceptable, so `null` should be ok. [What is the minimum Valid JSON](https://stackoverflow.com/questions/18419428/what-is-the-minimum-valid-json) – Tibrogargan May 08 '19 at 04:52
  • I absolutely agree that the client request makes no sense, but what I'm trying to do is to prevent people from maliciously sending this malformed request and breaking my server. I was hoping there would be a way of performing this error checking "just in case" ... seems a weird vulnerability that we could all write an XMLHTTPRequest client sending null to REST servers across the Internet and break them. – Rory May 08 '19 at 04:54
  • Right, but barfing because the body wasn't valid JSON but rather the string "null" would have have explained the symptoms ... but not since ECMA-404 I guess. – Tibrogargan May 08 '19 at 04:59
  • "and trivially breaks the server" isn't really a concern, nobody is "breaking servers" here. Your server is still chugging along handling requests, and the attacker will have received a 500 error. There is no service interruption or leaking of data or any actual problem here. – user229044 May 08 '19 at 05:36
  • Express doesn't behave this way by default. You likely have some middleware that attempts to parse the request body as JSON, and is failing, when it shouldn't be, because you're not sending a null payload, you're sending the perfectly valid JSON document `"null"`. We can't really answer this without code that reproduces your problem. – user229044 May 08 '19 at 05:43
  • Have edited the original question to provide code samples to reproduce error. @meagar – Rory May 08 '19 at 06:21

2 Answers2

2

This error is being thrown by bodyparser and hence is hit before your route.

You can stop this error by disabling strict mode for bodyparser:

app.use(bodyParser.json({
    strict: false
}));

However this might not be the best option, another way to trap this error is to supply a verify option to bodyparser, this can then throw a more meaningful error (there's probably a much cleaner way to do this):

const vfy = (req, res, buf, encoding) => {
    if (buf.length === 4 && buf[0] === 110 && buf[1] === 117 
                         && buf[2] === 108 && buf[3] === 108){ // Check for characters 'null' in buffer
        throw new Error('null body');
    }
};

app.use(bodyParser.json({
    verify: vfy
}));

edit a cleaner way:

const vfy = (req, res, buf, encoding) => {
    if (buf.equal(Buffer.from(110,117,108,108))){ // Check for characters 'null' in buffer
        throw new Error('null body');
    }
};

app.use(bodyParser.json({
    verify: vfy
}));
bendataclear
  • 3,802
  • 3
  • 32
  • 51
0

Seems like the problem comes from your condition :

I have tried: Testing existence of req with if(req){ ... }

I think changing it to

if(req.body){
   //handle it here
}

might solve your problem. Hope it helps.

punjira
  • 818
  • 8
  • 21
  • Generally this sort of contribution should be a comment on the question, rather than an answer - since it's really just a guess. – Tibrogargan May 08 '19 at 04:39
  • Tnx for the tip. still catching up with roles around here – punjira May 08 '19 at 04:41
  • 1
    @Tibrogargan, Though he still does not have enough rep to comment – Krishna Prashatt May 08 '19 at 04:49
  • I did try checking for a few other permutations but didn't want to be too exhaustive in my initial question. So for instance if (req.body) didn't work, nor if (req != null), or if (req != undefined) etc. Still produces same error. But thank you for making the suggestion. – Rory May 08 '19 at 04:53
  • @KrishnaPrashatt He had enough at the time, but even if he didn't posting a comment as an answer is still not acceptable. The correct way would have been to test if the guess was correct and if so, then post it. – Tibrogargan May 08 '19 at 04:54