Skip to content

Commit 448a023

Browse files
committed
Add lots more httpbin endpoints
1 parent 6400a59 commit 448a023

20 files changed

Lines changed: 839 additions & 9 deletions

src/endpoints/groups.ts

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -43,6 +43,21 @@ export const httpTlsMetadata: EndpointGroup = {
4343
name: 'TLS Metadata'
4444
};
4545

46+
export const httpRedirects: EndpointGroup = {
47+
id: 'redirects',
48+
name: 'Redirects'
49+
};
50+
51+
export const httpResponseInspection: EndpointGroup = {
52+
id: 'response-inspection',
53+
name: 'Response Inspection'
54+
};
55+
56+
export const httpDynamicData: EndpointGroup = {
57+
id: 'dynamic-data',
58+
name: 'Dynamic Data'
59+
};
60+
4661
// WebSocket endpoint groups
4762
export const wsMessaging: EndpointGroup = {
4863
id: 'messaging',

src/endpoints/http-index.ts

Lines changed: 10 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -35,7 +35,7 @@ export * from './http/user-agent.js';
3535
export * from './http/robots.txt.js';
3636
export * from './http/delay.js';
3737
export * from './http/cookies.js'
38-
export * from './http/basic-auth.js';
38+
export { basicAuth } from './http/basic-auth.js';
3939
export * from './http/json.js';
4040
export * from './http/xml.js';
4141
export * from './http/trailers.js';
@@ -49,4 +49,12 @@ export * from './http/encoding/zstd.js';
4949
export * from './http/encoding/brotli.js';
5050
export * from './http/encoding/identity.js';
5151
export * from './http/tls-fingerprint.js';
52-
export * from './http/tls-client-hello.js';
52+
export * from './http/tls-client-hello.js';
53+
export * from './http/uuid.js';
54+
export * from './http/deny.js';
55+
export * from './http/html.js';
56+
export * from './http/encoding/utf8.js';
57+
export * from './http/bearer.js';
58+
export * from './http/hidden-basic-auth.js';
59+
export * from './http/response-headers.js';
60+
export * from './http/base64.js';

src/endpoints/http/base64.ts

Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,32 @@
1+
import { HttpEndpoint } from '../http-index.js';
2+
import { httpDynamicData } from '../groups.js';
3+
4+
export const base64Decode: HttpEndpoint = {
5+
matchPath: (path) => path.startsWith('/base64/') && path.length > '/base64/'.length,
6+
handle: (_req, res, { path }) => {
7+
const encoded = path.slice('/base64/'.length);
8+
9+
const normalized = encoded.replace(/-/g, '+').replace(/_/g, '/');
10+
11+
try {
12+
const decoded = Buffer.from(normalized, 'base64');
13+
14+
// Verify validity by round-tripping, since Buffer.from silently ignores invalid chars
15+
if (decoded.toString('base64').replace(/=+$/, '') !== normalized.replace(/=+$/, '')) {
16+
throw new Error('Invalid base64');
17+
}
18+
19+
res.writeHead(200, { 'content-type': 'text/html; charset=utf-8' });
20+
res.end(decoded);
21+
} catch {
22+
res.writeHead(200, { 'content-type': 'text/html; charset=utf-8' });
23+
res.end('Incorrect Base64 data try: SGVsbG8gd29ybGQ= which decodes to: Hello world');
24+
}
25+
},
26+
meta: {
27+
path: '/base64/{value}',
28+
description: 'Decodes a base64url-encoded string and returns the decoded value.',
29+
examples: ['/base64/SGVsbG8gd29ybGQ='],
30+
group: httpDynamicData
31+
}
32+
};

src/endpoints/http/basic-auth.ts

Lines changed: 13 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,24 +1,30 @@
11
import { serializeJson } from '../../util.js';
2-
import { HttpEndpoint, HttpHandler } from '../http-index.js';
2+
import { HttpEndpoint, HttpHandler, HttpRequest } from '../http-index.js';
33
import { httpAuthentication } from '../groups.js';
44

5+
export const checkBasicAuth = (req: HttpRequest, username: string, password: string): boolean => {
6+
const authHeader = req.headers['authorization'];
7+
if (!authHeader) return false;
8+
9+
const expectedAuth = Buffer.from(`${username}:${password}`).toString('base64');
10+
const [authType, authValue] = authHeader.split(' ');
11+
return authType === 'Basic' && authValue === expectedAuth;
12+
};
13+
514
const matchPath = (path: string) =>
615
!!path.match(/^\/basic-auth\/([^\/]+)\/([^\/]+)$/);
716

817
const handle: HttpHandler = (req, res, { path }) => {
918
const [username, password] = path.split('/').slice(2);
10-
const authHeader = req.headers['authorization'];
1119

12-
if (authHeader === undefined) {
20+
if (!req.headers['authorization']) {
1321
res.writeHead(401, {
1422
'www-authenticate': 'Basic realm="Fake Realm"'
1523
}).end();
1624
return;
1725
}
1826

19-
const expectedAuth = Buffer.from(`${username}:${password}`).toString('base64');
20-
const [authType, authValue] = authHeader.split(' ');
21-
if (authType === 'Basic' && authValue === expectedAuth) {
27+
if (checkBasicAuth(req, username, password)) {
2228
res.writeHead(200, {
2329
'content-type': 'application/json'
2430
});
@@ -42,4 +48,4 @@ export const basicAuth: HttpEndpoint = {
4248
examples: ['/basic-auth/admin/secret'],
4349
group: httpAuthentication
4450
}
45-
};
51+
};

src/endpoints/http/bearer.ts

Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,33 @@
1+
import { serializeJson } from '../../util.js';
2+
import { HttpEndpoint } from '../http-index.js';
3+
import { httpAuthentication } from '../groups.js';
4+
5+
export const bearer: HttpEndpoint = {
6+
matchPath: (path) => path === '/bearer',
7+
handle: (req, res) => {
8+
const authHeader = req.headers['authorization'];
9+
10+
const token = authHeader?.startsWith('Bearer ')
11+
? authHeader.slice('Bearer '.length)
12+
: undefined;
13+
14+
if (token) {
15+
res.writeHead(200, { 'content-type': 'application/json' });
16+
res.end(serializeJson({
17+
authenticated: true,
18+
token
19+
}));
20+
return;
21+
}
22+
23+
res.writeHead(401, {
24+
'www-authenticate': 'Bearer'
25+
}).end();
26+
},
27+
meta: {
28+
path: '/bearer',
29+
description: 'Checks for a Bearer token in the Authorization header. Returns 200 with token info on success, 401 if missing.',
30+
examples: ['/bearer'],
31+
group: httpAuthentication
32+
}
33+
};

src/endpoints/http/deny.ts

Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,29 @@
1+
import { HttpEndpoint } from '../http-index.js';
2+
import { httpContentExamples } from '../groups.js';
3+
4+
const DENY_TEXT = `
5+
.-''''''-.
6+
.' _ _ '.
7+
/ O O \\
8+
: :
9+
| |
10+
: __ :
11+
\\ .-"\` \`"-. /
12+
'. .'
13+
'-......-'
14+
YOU SHOULDN'T BE HERE
15+
`;
16+
17+
export const deny: HttpEndpoint = {
18+
matchPath: (path) => path === '/deny',
19+
handle: (_req, res) => {
20+
res.writeHead(200, { 'content-type': 'text/plain' });
21+
res.end(DENY_TEXT);
22+
},
23+
meta: {
24+
path: '/deny',
25+
description: 'Returns a page denied by robots.txt.',
26+
examples: ['/deny'],
27+
group: httpContentExamples
28+
}
29+
};

0 commit comments

Comments
 (0)