`GET /token/{tokenId}/request/{requestId}/raw` crashes with 500 Internal Server Error when request lacks a `Content-Type` header

Open Beginner friendly
#197 0 comments 0 reactions 0 assignees View on GitHub

Nobody has claimed this yet.

Assessment

Difficulty
2/5
Estimated time
1-3 hours
Newbie friendliness
88/100
Issue type
Bug
Clarity
Clearly specified
Activity status
Active
Tech stack
laravel, php
Domain
api, backend

Research direction

Start in app/Storage/Request.php and inspect isJson(), then review how app/Http/Controllers/RequestController.php uses it in raw(). Add the regression test in tests/RequestControllerTest.php and verify that a captured request without a Content-Type returns 200, preserves its body, and uses text/plain.

Written by the indexing model from the issue text.

Description

📋 Overview

When attempting to view the raw content of an incoming webhook or HTTP request via GET /token/{tokenId}/request/{requestId}/raw (or clicking Raw content in the Web UI), the endpoint crashes with a 500 Internal Server Error (ErrorException: Undefined index: content-type) whenever the captured request did not include an explicit Content-Type header.


🔍 Root Cause Analysis

In app/Http/Controllers/RequestController.php (lines 138–146), the raw() method determines the response Content-Type by calling $request->isJson():

public function raw($tokenId, $requestId)
{
    $token = $this->tokens->find($tokenId);
    $request = $this->requests->find($token, $requestId);

    $contentType = $request->isJson() ? 'application/json' : 'text/plain';

    return new Response($request->content, Response::HTTP_OK, ['content-type' => $contentType]);
}

In app/Storage/Request.php (lines 56–59), isJson() is implemented as:

/**
 * @return bool
 */
public function isJson()
{
    return $this->headers['content-type'][0] === 'application/json';
}

There are two key problems in this implementation:

  1. Unchecked Array Access on Missing Headers:
    Many standard HTTP requests (e.g. GET requests, standard webhook pings, plain curl requests, or health checks) do not send a Content-Type header. When stored, $this->headers contains no 'content-type' key.
    Accessing $this->headers['content-type'] directly without checking if the key exists raises:

    ErrorException: Undefined index: content-type
    

    (or Undefined array key "content-type" in newer PHP runtimes), causing the request to fail with an unhandled 500 Internal Server Error.

  2. Fragile Exact Matching for Valid JSON Types:
    The strict comparison === 'application/json' fails to detect valid JSON requests containing parameters such as charset or vendor types (e.g. application/json; charset=utf-8 or application/problem+json).


🔁 Steps to Reproduce
  1. Create a new token:

    TOKEN_ID=$(curl -s -X POST http://localhost:8084/token | grep -o '"uuid":"[^"]*' | cut -d'"' -f4)
    
  2. Send an HTTP request without a Content-Type header (e.g. standard GET or plain text POST):

    curl -X POST http://localhost:8084/${TOKEN_ID} -d "sample body without content type header"
    
  3. Retrieve the captured request's UUID:

    REQUEST_ID=$(curl -s http://localhost:8084/token/${TOKEN_ID}/requests | grep -o '"uuid":"[^"]*' | head -n 1 | cut -d'"' -f4)
    
  4. Attempt to fetch the raw content:

    curl -i http://localhost:8084/token/${TOKEN_ID}/request/${REQUEST_ID}/raw
    
  5. Observe the result:

    • Actual Response: HTTP/1.1 500 Internal Server Error
    • Expected Response: HTTP/1.1 200 OK with content body and Content-Type: text/plain

🛠️ Proposed Fix / Patch

Safely check for the existence of the content-type header and use case-insensitive substring matching in app/Storage/Request.php.

Unified Diff:
--- a/app/Storage/Request.php
+++ b/app/Storage/Request.php
@@ -53,8 +53,12 @@ public static function createFromRequest(HttpRequest $httpRequest)
      */
     public function isJson()
     {
-        return $this->headers['content-type'][0] === 'application/json';
+        if (empty($this->headers['content-type'][0])) {
+            return false;
+        }
+
+        return stripos($this->headers['content-type'][0], 'application/json') !== false;
     }
 }

🧪 Unit Test

This test can be added to tests/RequestControllerTest.php to verify the fix and prevent regressions:

public function testRawContentWithoutContentTypeHeaderReturns200()
{
    $this->withoutMiddleware();

    $tokenId = $this->json('POST', 'token')->json()['uuid'];

    // Send a request without Content-Type header
    $this->call('POST', $tokenId, [], [], [], [], 'Plain text content body');

    $requests = $this->json('GET', "token/{$tokenId}/requests")->json()['data'];
    $requestId = $requests[0]['uuid'];

    // Fetch raw content
    $response = $this->call('GET', "token/{$tokenId}/request/{$requestId}/raw");

    $response->assertStatus(200);
    $this->assertEquals('Plain text content body', $response->getContent());
    $this->assertStringStartsWith('text/plain', $response->headers->get('content-type'));
}
Dominant language
JavaScript
Stars
6.8k
Forks
530
PR merge metrics
No merged PRs in 30d

Contributor guide

No contributing guide indexed for this repository

First steps

  1. Read the whole issue, then the project's contributing guide.
  2. Comment on the issue to say you are picking it up — it saves two people doing the same work.
  3. Fork the repository and make your change on a branch.
  4. Open a pull request that references the issue number.

More from webhooksite/webhook.site

All issues in webhooksite/webhook.site

Similar issues

More JavaScript issues

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.