I am working on a project related to Content Security Policy (CSP) reports. As part of this project, I need to record POST requests made by Chrome that are initiated whenever a CSP policy is violated. When Chrome sends these reports, they send the following header:

Content-Type: application/csp-report

The body of the request is very basic JSON.

The application that I wrote to handle these POST requests is written in Node using the Hapi framework. I set up a simple route to handle the request:

server.route({
  method: 'POST',
  path:'/csp-report',
  handler: function (request, reply) {
    // Record the request
  }
});

All browsers other than Chrome worked just fine; however, Chrome produced a 415 HTTP response when the endpoint was invoked. Other browsers use a content type of application/json, whereas Chrome uses application/csp-report. A 415 HTTP status code stands for “Unsupported Media Type”. Simply put, the server cannot handle this unknown media type. I tried a number of ways of getting the server to support the media type, but the best result led to the content being parsed incorrectly, making the previously usable JSON a jumbled mess.

My solution to this issue was to map application/csp-report to application/json. In the end, the content really is just JSON, so there is no point in maintaining this content type.

To accomplish this goal, I utilized the onRequest event to map the content type:

server.ext('onRequest', function(request, reply) {
  if ('application/csp-report' === request.headers['content-type']) {
    request.headers['content-type'] = 'application/json';
    request.headers['x-content-type'] = 'application/csp-report';
  }

  return reply.continue();
});

This snippet catches the request before it is passed to the routes. If I find the application/csp-report content type, I simply change it to application/json. I also set a new x-content-type header to record the original content type in case I ever need that information in the future.

One small note is that reply.continue() was well documented in the Hapi docs; however, I found many other tutorials and snippets handling this incorrectly. I suspect that this difference is due to an API change at some point, so your mileage may vary with that call.

There’s nothing spectacular in this code, but I really couldn’t find any useful information about how to handle this issue. Hopefully this helps some wayward soul in the future.