networking · intermediate · ~12 min · safe pentest lab
Build a valid HTTP/1.0 request as a string — and enforce the localhost-only safety boundary at the byte level.
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.
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.
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
int build_http_get(const char *host, const char *path, char *out, int cap);
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.snprintf and check both that it didn't fail (n < 0) and that the output fit (n < cap).| 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 |
?query is fine — only the leading / is validated.cap must return -1, not produce a truncated request.-1 (defensive check).GET <path> HTTP/1.0), the Host: header, and a blank line. That's it.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.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.` at the end).
Host: localhost instead of using the validated host argument.cap — silently truncating produces a malformed request.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.
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.
HTTP is just bytes. Building them by hand once removes the mystery and makes every web-related lesson click.
Four args: host, path, output buffer, capacity.
Bytes written (excluding NUL) on success, -1 on any failure.
Host MUST be 127.0.0.1. Path MUST start with /. Use snprintf and check both branches.
#include <stdio.h>
#include <string.h>
int build_http_get(const char *host, const char *path, char *out, int cap) {
/* TODO */
return -1;
}
Missing the trailing blank line. Hardcoding localhost instead of host. Not rejecting a too-small cap.
Query string in path is fine (only leading / checked). cap = 0 must return -1. NULL inputs reject.
Solve this exercise in the browser editor — compile and run against the test harness, no setup required.