mirror of
https://github.com/denoland/deno.git
synced 2025-01-10 16:11:13 -05:00
feat(std/http/server): Respond with 400 on request parse failure (#4551)
This commit is contained in:
parent
857d96001d
commit
017a611131
4 changed files with 50 additions and 14 deletions
|
@ -342,17 +342,38 @@ export function parseHTTPVersion(vers: string): [number, number] {
|
||||||
|
|
||||||
export async function readRequest(
|
export async function readRequest(
|
||||||
conn: Deno.Conn,
|
conn: Deno.Conn,
|
||||||
bufr: BufReader
|
// The reader and writer buffers may be constructed externally so they can be
|
||||||
|
// shared by requests on the same connection -- see `Server`.
|
||||||
|
reader?: BufReader,
|
||||||
|
writer?: BufWriter
|
||||||
): Promise<ServerRequest | Deno.EOF> {
|
): Promise<ServerRequest | Deno.EOF> {
|
||||||
const tp = new TextProtoReader(bufr);
|
reader = reader ?? new BufReader(conn);
|
||||||
const firstLine = await tp.readLine(); // e.g. GET /index.html HTTP/1.0
|
writer = writer ?? new BufWriter(conn);
|
||||||
if (firstLine === Deno.EOF) return Deno.EOF;
|
const tp = new TextProtoReader(reader);
|
||||||
const headers = await tp.readMIMEHeader();
|
let firstLine: string | Deno.EOF;
|
||||||
if (headers === Deno.EOF) throw new Deno.errors.UnexpectedEof();
|
let headers: Headers | Deno.EOF;
|
||||||
|
try {
|
||||||
|
firstLine = await tp.readLine(); // e.g. GET /index.html HTTP/1.0
|
||||||
|
if (firstLine == Deno.EOF) {
|
||||||
|
return Deno.EOF;
|
||||||
|
}
|
||||||
|
headers = await tp.readMIMEHeader();
|
||||||
|
if (headers == Deno.EOF) {
|
||||||
|
throw new Deno.errors.UnexpectedEof();
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
// An error was thrown while parsing request headers.
|
||||||
|
await writeResponse(writer, {
|
||||||
|
status: 400,
|
||||||
|
body: encoder.encode(`${error.message}\r\n\r\n`),
|
||||||
|
});
|
||||||
|
throw error;
|
||||||
|
}
|
||||||
|
|
||||||
const req = new ServerRequest();
|
const req = new ServerRequest();
|
||||||
req.conn = conn;
|
req.conn = conn;
|
||||||
req.r = bufr;
|
req.r = reader;
|
||||||
|
req.w = writer;
|
||||||
[req.method, req.url, req.proto] = firstLine.split(" ", 3);
|
[req.method, req.url, req.proto] = firstLine.split(" ", 3);
|
||||||
[req.protoMinor, req.protoMajor] = parseHTTPVersion(req.proto);
|
[req.protoMinor, req.protoMajor] = parseHTTPVersion(req.proto);
|
||||||
req.headers = headers;
|
req.headers = headers;
|
||||||
|
|
|
@ -5,6 +5,7 @@ import {
|
||||||
assert,
|
assert,
|
||||||
assertNotEOF,
|
assertNotEOF,
|
||||||
assertNotEquals,
|
assertNotEquals,
|
||||||
|
assertMatch,
|
||||||
} from "../testing/asserts.ts";
|
} from "../testing/asserts.ts";
|
||||||
import {
|
import {
|
||||||
bodyReader,
|
bodyReader,
|
||||||
|
@ -349,13 +350,28 @@ malformedHeader
|
||||||
`;
|
`;
|
||||||
const reader = new BufReader(new StringReader(input));
|
const reader = new BufReader(new StringReader(input));
|
||||||
let err;
|
let err;
|
||||||
|
let responseString: string;
|
||||||
try {
|
try {
|
||||||
await readRequest(mockConn(), reader);
|
// Capture whatever `readRequest()` attempts to write to the connection on
|
||||||
|
// error. We expect it to be a 400 response.
|
||||||
|
await readRequest(
|
||||||
|
mockConn({
|
||||||
|
write(p: Uint8Array): Promise<number> {
|
||||||
|
responseString = decode(p);
|
||||||
|
return Promise.resolve(p.length);
|
||||||
|
},
|
||||||
|
}),
|
||||||
|
reader
|
||||||
|
);
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
err = e;
|
err = e;
|
||||||
}
|
}
|
||||||
assert(err instanceof Error);
|
assert(err instanceof Error);
|
||||||
assertEquals(err.message, "malformed MIME header line: malformedHeader");
|
assertEquals(err.message, "malformed MIME header line: malformedHeader");
|
||||||
|
assertMatch(
|
||||||
|
responseString!,
|
||||||
|
/^HTTP\/1\.1 400 Bad Request\r\ncontent-length: \d+\r\n\r\n.*\r\n\r\n$/ms
|
||||||
|
);
|
||||||
});
|
});
|
||||||
|
|
||||||
// Ported from Go
|
// Ported from Go
|
||||||
|
|
|
@ -17,8 +17,8 @@ export function mockConn(base: Partial<Deno.Conn> = {}): Deno.Conn {
|
||||||
read: (): Promise<number | Deno.EOF> => {
|
read: (): Promise<number | Deno.EOF> => {
|
||||||
return Promise.resolve(0);
|
return Promise.resolve(0);
|
||||||
},
|
},
|
||||||
write: (): Promise<number> => {
|
write: (p: Uint8Array): Promise<number> => {
|
||||||
return Promise.resolve(-1);
|
return Promise.resolve(p.length);
|
||||||
},
|
},
|
||||||
close: (): void => {},
|
close: (): void => {},
|
||||||
...base,
|
...base,
|
||||||
|
|
|
@ -146,14 +146,14 @@ export class Server implements AsyncIterable<ServerRequest> {
|
||||||
private async *iterateHttpRequests(
|
private async *iterateHttpRequests(
|
||||||
conn: Conn
|
conn: Conn
|
||||||
): AsyncIterableIterator<ServerRequest> {
|
): AsyncIterableIterator<ServerRequest> {
|
||||||
const bufr = new BufReader(conn);
|
const reader = new BufReader(conn);
|
||||||
const w = new BufWriter(conn);
|
const writer = new BufWriter(conn);
|
||||||
let req: ServerRequest | Deno.EOF = Deno.EOF;
|
let req: ServerRequest | Deno.EOF = Deno.EOF;
|
||||||
let err: Error | undefined;
|
let err: Error | undefined;
|
||||||
|
|
||||||
while (!this.closing) {
|
while (!this.closing) {
|
||||||
try {
|
try {
|
||||||
req = await readRequest(conn, bufr);
|
req = await readRequest(conn, reader, writer);
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
err = e;
|
err = e;
|
||||||
}
|
}
|
||||||
|
@ -161,7 +161,6 @@ export class Server implements AsyncIterable<ServerRequest> {
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
req.w = w;
|
|
||||||
yield req;
|
yield req;
|
||||||
|
|
||||||
// Wait for the request to be processed before we accept a new request on
|
// Wait for the request to be processed before we accept a new request on
|
||||||
|
|
Loading…
Reference in a new issue