Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Fixed parsing messages with no headers #14

Merged
merged 7 commits into from
Sep 25, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 3 additions & 2 deletions src/Component.ts
Original file line number Diff line number Diff line change
Expand Up @@ -22,10 +22,11 @@ export class Component implements Part {
* @param data Component byte representation to parse
*/
public static parse(data: Uint8Array): Component {
const headersEndIndex = Multipart.findSequenceIndex(data, Multipart.combineArrays([Multipart.CRLF, Multipart.CRLF]));
const hasHeaders = Multipart.findSequenceIndex(data, Multipart.CRLF) !== 0;
const headersEndIndex = hasHeaders ? Multipart.findSequenceIndex(data, Multipart.combineArrays([Multipart.CRLF, Multipart.CRLF])) + 2 : 0;

const headersBuffer = data.slice(0, headersEndIndex);
const body = data.slice(headersEndIndex + 4);
const body = data.slice(headersEndIndex + 2);

const headersString = new TextDecoder().decode(headersBuffer);
const headers = new Headers();
Expand Down
21 changes: 16 additions & 5 deletions test/Component.test.js
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import {expect} from "chai";
import {Component} from "../dist/index.js";
import {Multipart, Component} from "../dist/index.js";

describe("Component", () => {

Expand Down Expand Up @@ -35,7 +35,7 @@ describe("Component", () => {
it("should parse headers and body correctly from Uint8Array", () => {
const headers = "Content-Type: text/plain\r\nContent-Length: 5\r\n\r\n";
const body = new Uint8Array([1, 2, 3, 4, 5]);
const data = new Uint8Array([...headers.split("").map(c => c.charCodeAt(0)), ...body]);
const data = Multipart.combineArrays([new TextEncoder().encode(headers), body]);

const component = Component.parse(data);

Expand All @@ -45,8 +45,8 @@ describe("Component", () => {
expect(component.body).to.deep.equal(body);
});

it("should handle missing headers and body", () => {
const data = new Uint8Array([0x0D, 0x0A, 0x0D, 0x0A]);
it("should handle missing headers and empty body", () => {
const data = new Uint8Array([0x0D, 0x0A]);

const component = Component.parse(data);

Expand All @@ -57,14 +57,25 @@ describe("Component", () => {

it("should handle headers with no body", () => {
const headers = "Content-Type: text/plain\r\n\r\n";
const data = new Uint8Array([...headers.split("").map(c => c.charCodeAt(0))]);
const data = new TextEncoder().encode(headers);

const component = Component.parse(data);

expect(component.headers.get("Content-Type")).to.equal("text/plain");

expect(component.body).to.deep.equal(new Uint8Array(0));
});

it("should handle body with no headers", () => {
const body = "\r\nGoal: No headers!\r\n\r\nReally none.\r\n";
const data = new TextEncoder().encode(body);

const component = Component.parse(data);

expect(component.headers).to.be.empty;

expect(new TextDecoder().decode(component.body)).to.equal("Goal: No headers!\r\n\r\nReally none.\r\n");
});
});

describe("#bytes", () => {
Expand Down
45 changes: 45 additions & 0 deletions test/Multipart.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,51 @@ describe("Multipart", function () {

expect(parsedMultipart).to.be.an.instanceof(Multipart);
expect(parsedMultipart.parts.length).to.equal(2);
const part1 = parsedMultipart.parts[0];
expect(part1.headers.get("x-foo")).to.equal("bar");
expect(part1.body).to.deep.equal(component1.body);
const part2 = parsedMultipart.parts[1];
expect(part2.headers.get("content-type")).to.equal("text/plain");
expect(part2.body).to.deep.equal(component2.body);
});

it("should parse Multipart data from RFC 2046 5.1.1 example body", function () {
const string =
'From: Nathaniel Borenstein <[email protected]>\r\n' +
'To: Ned Freed <[email protected]>\r\n' +
'Date: Sun, 21 Mar 1993 23:56:48 -0800 (PST)\r\n' +
'Subject: Sample message\r\n' +
'MIME-Version: 1.0\r\n' +
'Content-type: multipart/mixed; boundary="simple boundary"\r\n' +
'\r\n' +
'This is the preamble. It is to be ignored, though it\n' +
'is a handy place for composition agents to include an\r\n' +
'explanatory note to non-MIME conformant readers.\n' +
'\r\n' +
'--simple boundary\r\n' +
'\r\n' +
'This is implicitly typed plain US-ASCII text.\r\n' +
'It does NOT end with a linebreak.\r\n' +
'--simple boundary\r\n' +
'Content-type: text/plain; charset=us-ascii\r\n' +
'\r\n' +
'This is explicitly typed plain US-ASCII text.\r\n' +
'It DOES end with a linebreak.\r\n' +
'\r\n' +
'--simple boundary--\r\n' +
'\r\n' +
'This is the epilogue. It is also to be ignored.';

const bytes = new TextEncoder().encode(string);
const parsedMultipart = Multipart.parse(bytes);

expect(parsedMultipart).to.be.an.instanceof(Multipart);
expect(parsedMultipart.parts.length).to.equal(2);
const part1 = parsedMultipart.parts[0];
expect(new TextDecoder().decode(part1.body)).to.equal("This is implicitly typed plain US-ASCII text.\r\nIt does NOT end with a linebreak.");
const part2 = parsedMultipart.parts[1];
expect(part2.headers.get("content-type")).to.equal("text/plain; charset=us-ascii");
expect(new TextDecoder().decode(part2.body)).to.equal("This is explicitly typed plain US-ASCII text.\r\nIt DOES end with a linebreak.\r\n");
});

it("should handle nested multiparts", function () {
Expand Down