Skip to content

Commit a7792b2

Browse files
authored
Merge pull request #30 from freema/fix/network-headers-bidi-parsing
fix: enhance BiDi header parsing to handle various value types and ed…
2 parents 3cd3d24 + 349131e commit a7792b2

File tree

2 files changed

+138
-23
lines changed

2 files changed

+138
-23
lines changed

src/firefox/events/network.ts

Lines changed: 38 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -279,23 +279,50 @@ export class NetworkEvents {
279279

280280
/**
281281
* Parse BiDi headers array to object
282-
* BiDi headers can have value as string or as object { type: "string", value: "..." }
283282
*/
284283
private parseHeaders(headers: any[]): Record<string, string> {
285284
const result: Record<string, string> = {};
286285

286+
const normalizeValue = (value: unknown): string | null => {
287+
if (value === null || value === undefined) {
288+
return null;
289+
}
290+
if (typeof value === 'string' || typeof value === 'number' || typeof value === 'boolean') {
291+
return String(value);
292+
}
293+
if (Array.isArray(value)) {
294+
const parts = value
295+
.map((item) => normalizeValue(item))
296+
.filter((item): item is string => !!item);
297+
return parts.length > 0 ? parts.join(', ') : null;
298+
}
299+
if (typeof value === 'object') {
300+
const obj = value as Record<string, unknown>;
301+
if ('value' in obj) {
302+
return normalizeValue(obj.value);
303+
}
304+
if ('bytes' in obj) {
305+
return normalizeValue(obj.bytes);
306+
}
307+
try {
308+
return JSON.stringify(obj);
309+
} catch {
310+
return null;
311+
}
312+
}
313+
return String(value);
314+
};
315+
287316
if (Array.isArray(headers)) {
288317
for (const h of headers) {
289-
if (h.name && h.value !== undefined) {
290-
// BiDi returns header values as { type: "string", value: "actual value" }
291-
const value = h.value;
292-
if (typeof value === 'string') {
293-
result[h.name.toLowerCase()] = value;
294-
} else if (value && typeof value === 'object' && 'value' in value) {
295-
result[h.name.toLowerCase()] = String(value.value);
296-
} else {
297-
result[h.name.toLowerCase()] = String(value);
298-
}
318+
const name = h?.name ? String(h.name).toLowerCase() : '';
319+
if (!name) {
320+
continue;
321+
}
322+
323+
const normalizedValue = normalizeValue(h?.value);
324+
if (normalizedValue !== null) {
325+
result[name] = normalizedValue;
299326
}
300327
}
301328
}

tests/firefox/events/network.test.ts

Lines changed: 100 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -9,23 +9,51 @@ import { describe, it, expect } from 'vitest';
99
// Or sometimes as: { name: "header-name", value: "actual-value" }
1010

1111
/**
12-
* Replica of the parseHeaders logic for testing
12+
* Replica of the parseHeaders logic for testing (matches src/firefox/events/network.ts)
1313
*/
1414
function parseHeaders(headers: any[]): Record<string, string> {
1515
const result: Record<string, string> = {};
1616

17+
const normalizeValue = (value: unknown): string | null => {
18+
if (value === null || value === undefined) {
19+
return null;
20+
}
21+
if (typeof value === 'string' || typeof value === 'number' || typeof value === 'boolean') {
22+
return String(value);
23+
}
24+
if (Array.isArray(value)) {
25+
const parts = value
26+
.map((item) => normalizeValue(item))
27+
.filter((item): item is string => !!item);
28+
return parts.length > 0 ? parts.join(', ') : null;
29+
}
30+
if (typeof value === 'object') {
31+
const obj = value as Record<string, unknown>;
32+
if ('value' in obj) {
33+
return normalizeValue(obj.value);
34+
}
35+
if ('bytes' in obj) {
36+
return normalizeValue(obj.bytes);
37+
}
38+
try {
39+
return JSON.stringify(obj);
40+
} catch {
41+
return null;
42+
}
43+
}
44+
return String(value);
45+
};
46+
1747
if (Array.isArray(headers)) {
1848
for (const h of headers) {
19-
if (h.name && h.value !== undefined) {
20-
// BiDi returns header values as { type: "string", value: "actual value" }
21-
const value = h.value;
22-
if (typeof value === 'string') {
23-
result[h.name.toLowerCase()] = value;
24-
} else if (value && typeof value === 'object' && 'value' in value) {
25-
result[h.name.toLowerCase()] = String(value.value);
26-
} else {
27-
result[h.name.toLowerCase()] = String(value);
28-
}
49+
const name = h?.name ? String(h.name).toLowerCase() : '';
50+
if (!name) {
51+
continue;
52+
}
53+
54+
const normalizedValue = normalizeValue(h?.value);
55+
if (normalizedValue !== null) {
56+
result[name] = normalizedValue;
2957
}
3058
}
3159
}
@@ -103,22 +131,82 @@ describe('NetworkEvents Header Parsing', () => {
103131
expect(parseHeaders('not-an-array' as any)).toEqual({});
104132
});
105133

106-
it('should skip headers without name or value', () => {
134+
it('should skip headers without name or with null/undefined value', () => {
107135
const headers = [
108136
{ name: 'Valid', value: 'value' },
109137
{ name: 'NoValue' },
110138
{ value: 'no-name' },
139+
{ name: 'NullValue', value: null },
140+
{ name: 'UndefinedValue', value: undefined },
111141
{ name: 'Empty', value: '' },
112142
];
113143

114144
const result = parseHeaders(headers);
115145

116146
expect(result['valid']).toBe('value');
117147
expect(result['novalue']).toBeUndefined();
148+
expect(result['nullvalue']).toBeUndefined();
149+
expect(result['undefinedvalue']).toBeUndefined();
118150
expect(result['empty']).toBe('');
119151
expect(Object.keys(result)).toHaveLength(2);
120152
});
121153

154+
it('should handle BiDi bytes format (binary data)', () => {
155+
const headers = [
156+
{ name: 'X-Binary', value: { type: 'base64', bytes: 'SGVsbG8gV29ybGQ=' } },
157+
];
158+
159+
const result = parseHeaders(headers);
160+
161+
expect(result['x-binary']).toBe('SGVsbG8gV29ybGQ=');
162+
});
163+
164+
it('should handle array values (multi-value headers)', () => {
165+
const headers = [
166+
{ name: 'Set-Cookie', value: ['cookie1=value1', 'cookie2=value2'] },
167+
{
168+
name: 'X-Multi',
169+
value: [
170+
{ type: 'string', value: 'first' },
171+
{ type: 'string', value: 'second' },
172+
],
173+
},
174+
];
175+
176+
const result = parseHeaders(headers);
177+
178+
expect(result['set-cookie']).toBe('cookie1=value1, cookie2=value2');
179+
expect(result['x-multi']).toBe('first, second');
180+
});
181+
182+
it('should handle empty array values', () => {
183+
const headers = [{ name: 'X-Empty-Array', value: [] }];
184+
185+
const result = parseHeaders(headers);
186+
187+
expect(result['x-empty-array']).toBeUndefined();
188+
});
189+
190+
it('should handle unknown object format with JSON.stringify fallback', () => {
191+
const headers = [{ name: 'X-Unknown', value: { foo: 'bar', baz: 123 } }];
192+
193+
const result = parseHeaders(headers);
194+
195+
expect(result['x-unknown']).toBe('{"foo":"bar","baz":123}');
196+
});
197+
198+
it('should handle boolean values', () => {
199+
const headers = [
200+
{ name: 'X-True', value: true },
201+
{ name: 'X-False', value: false },
202+
];
203+
204+
const result = parseHeaders(headers);
205+
206+
expect(result['x-true']).toBe('true');
207+
expect(result['x-false']).toBe('false');
208+
});
209+
122210
it('should handle numeric values in BiDi format', () => {
123211
const headers = [
124212
{ name: 'Content-Length', value: { type: 'string', value: 12345 } },

0 commit comments

Comments
 (0)