networking · intermediate · ~12 min · safe pentest lab

Hand-build a minimal HTTP/1.0 GET request

Build a valid HTTP/1.0 request as a string — and enforce the localhost-only safety boundary at the byte level.

Challenge

Build a tiny HTTP/1.0 GET request — by hand

HTTP is bytes-over-TCP. There's no magic library; the request is just a text string the server reads. Knowing exactly what those bytes look like demystifies the protocol forever.

Real-world frame

Every HTTP client — curl, wget, your browser, Python requests — eventually builds these same bytes. Doing it once by hand makes every "what does this HTTP header even mean?" question stop being scary.

Task

Implement:

int build_http_get(const char *host, const char *path, char *out, int cap);

Write a minimal HTTP/1.0 GET request into out, including the trailing blank line. Return the number of bytes written (excluding the NUL terminator), or -1 on any validation failure or buffer overflow.

The expected format:

GET /path HTTP/1.0
Host: 127.0.0.1

Function signature

int build_http_get(const char *host, const char *path, char *out, int cap);

Rules

  • host must be "127.0.0.1" exactly. Reject anything else with -1. This is the platform's safety lock — the function can only build requests for localhost.
  • path must start with /. Reject otherwise with -1.
  • Use snprintf and check both that it didn't fail (n < 0) and that the output fit (n < cap).
  • Don't touch the network. This is pure string-building.

Examples

host path out (truncated) returns
"127.0.0.1" "/" `GET / HTTP/1.0
Host: 127.0.0.1

| 36 | |"127.0.0.1"|"/api/v1"|GET /api/v1 HTTP/1.0 Host: 127.0.0.1

| 42 | |"example.com"|"/"| (rejected) | -1 | |"127.0.0.1"|"missing-slash"| (rejected) | -1 | |"127.0.0.1"|"/"withcap=8` | (won't fit) | -1 |

Edge cases

  • A path with a ?query is fine — only the leading / is validated.
  • A tiny cap must return -1, not produce a truncated request.
  • NULL inputs should return -1 (defensive check).

Hints

  1. Conceptual: HTTP/1.0 is the simplest version. Three things in the request: the start line (GET <path> HTTP/1.0), the Host: header, and a blank line. That's it.
  2. Implementation: validate first (host matches, path starts with /), then snprintf(out, cap, "GET %s HTTP/1.0\r\nHost: %s\r\n\r\n", path, host). Check the return value.
  3. Common bug: trusting snprintf's return value blindly. If the output didn't fit it returns the count it would have written — bigger than cap. Compare against cap explicitly.

Common mistakes

  • Forgetting the trailing blank line (the `

` at the end).

  • Hardcoding Host: localhost instead of using the validated host argument.
  • Not rejecting a small cap — silently truncating produces a malformed request.

Learning connection

You can now write the simplest possible "is the server up?" tool: build the request with this helper, send it, read the response. Pair with port-checker-localhost and you have a basic localhost diagnostic kit.

Security note

The strcmp against "127.0.0.1" is the structural guarantee that this function can only ever build requests to localhost. This exercise is for defensive learning in a local lab only. Do not use any variant of this technique to scan or contact systems you do not own or have explicit permission to test.

Why this matters

HTTP is just bytes. Building them by hand once removes the mystery and makes every web-related lesson click.

Input format

Four args: host, path, output buffer, capacity.

Output format

Bytes written (excluding NUL) on success, -1 on any failure.

Constraints

Host MUST be 127.0.0.1. Path MUST start with /. Use snprintf and check both branches.

Starter code

#include <stdio.h>
#include <string.h>
int build_http_get(const char *host, const char *path, char *out, int cap) {
    /* TODO */
    return -1;
}

Common mistakes

Missing the trailing blank line. Hardcoding localhost instead of host. Not rejecting a too-small cap.

Edge cases to handle

Query string in path is fine (only leading / checked). cap = 0 must return -1. NULL inputs reject.

Background lessons

Solve this exercise in the browser editor — compile and run against the test harness, no setup required.