pFad - Phone/Frame/Anonymizer/Declutterfier! Saves Data!


--- a PPN by Garber Painting Akron. With Image Size Reduction included!

URL: http://github.com/HTTPArchive/tech-report-apis/commit/328a182f4f2070ad2db79a21ac381cd79b88aa72

link crossorigen="anonymous" media="all" rel="stylesheet" href="https://github.githubassets.com/assets/global-a469e846088cc1bf.css" /> fix headers · HTTPArchive/tech-report-apis@328a182 · GitHub
Skip to content

Commit 328a182

Browse files
committed
fix headers
Signed-off-by: Max Ostapenko <1611259+max-ostapenko@users.noreply.github.com>
1 parent 682c0ab commit 328a182

File tree

3 files changed

+2
-292
lines changed

3 files changed

+2
-292
lines changed

src/index.js

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -86,7 +86,7 @@ const sendJSONResponse = (res, data, statusCode = 200) => {
8686

8787
// Helper function to check if resource is modified
8888
const isModified = (req, etag) => {
89-
const ifNoneMatch = req.headers['If-None-Match'] || req.get('If-None-Match');
89+
const ifNoneMatch = req.headers['if-none-match'] || (req.get && req.get('if-none-match'));
9090
return !ifNoneMatch || ifNoneMatch !== `"${etag}"`;
9191
};
9292

src/tests/headers.test.js

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -39,8 +39,7 @@ describe('CDN Headers', () => {
3939
expect(res.headers['access-control-allow-origen']).toBe('*');
4040
expect(res.headers['access-control-allow-headers']).toContain('Content-Type');
4141
expect(res.headers['access-control-allow-headers']).toContain('If-None-Match');
42-
expect(res.headers['access-control-expose-headers']).toContain('ETag');
43-
expect(res.headers['access-control-expose-headers']).toContain('Cloud-CDN-Cache-Tag');
42+
expect(res.headers['access-control-expose-headers']).toBe('*');
4443
});
4544

4645
it('should set correct headers for static file proxy', async () => {

src/tests/routes.test.js

Lines changed: 0 additions & 289 deletions
Original file line numberDiff line numberDiff line change
@@ -673,293 +673,4 @@ describe('API Routes', () => {
673673
});
674674
});
675675
});
676-
677-
describe('GET /v1/static/*', () => {
678-
beforeEach(() => {
679-
// Reset all mocks before each test
680-
mockFileExists.mockReset();
681-
mockGetMetadata.mockReset();
682-
mockCreateReadStream.mockReset();
683-
mockFile.mockClear();
684-
mockBucket.mockClear();
685-
});
686-
687-
describe('Valid file requests', () => {
688-
it('should return file content for valid path', async () => {
689-
const fileContent = JSON.stringify({ data: 'test' });
690-
const readable = Readable.from([fileContent]);
691-
692-
mockFileExists.mockResolvedValue([true]);
693-
mockGetMetadata.mockResolvedValue([{
694-
contentType: 'application/json',
695-
etag: '"abc123"',
696-
size: fileContent.length
697-
}]);
698-
mockCreateReadStream.mockReturnValue(readable);
699-
700-
const res = await request(app)
701-
.get('/v1/static/reports/2024/data.json')
702-
.expect(200);
703-
704-
expect(res.headers['content-type']).toContain('application/json');
705-
expect(res.headers['cache-control']).toContain('public');
706-
expect(res.headers['access-control-allow-origen']).toEqual('*');
707-
});
708-
709-
it('should infer MIME type from file extension when not in metadata', async () => {
710-
const fileContent = '{"test": true}';
711-
const readable = Readable.from([fileContent]);
712-
713-
mockFileExists.mockResolvedValue([true]);
714-
mockGetMetadata.mockResolvedValue([{
715-
etag: '"abc123"',
716-
size: fileContent.length
717-
}]);
718-
mockCreateReadStream.mockReturnValue(readable);
719-
720-
const res = await request(app)
721-
.get('/v1/static/reports/data.json')
722-
.expect(200);
723-
724-
expect(res.headers['content-type']).toContain('application/json');
725-
});
726-
727-
it('should handle CORS preflight requests', async () => {
728-
const res = await request(app)
729-
.options('/v1/static/reports/data.json')
730-
.set('Origin', 'http://example.com')
731-
.set('Access-Control-Request-Method', 'GET')
732-
.set('Access-Control-Request-Headers', 'Content-Type');
733-
734-
expect(res.statusCode).toEqual(204);
735-
expect(res.headers['access-control-allow-origen']).toEqual('*');
736-
});
737-
});
738-
739-
describe('Invalid file paths (directory traversal attempts)', () => {
740-
it('should reject paths containing double dot sequences', async () => {
741-
// Test with '..' embedded in the path that won't be normalized away
742-
const res = await request(app)
743-
.get('/v1/static/reports/..hidden/passwd')
744-
745-
expect(res.body).toHaveProperty('error', 'Invalid file path');
746-
});
747-
748-
it('should reject paths with double slashes', async () => {
749-
const res = await request(app)
750-
.get('/v1/static/reports//data.json')
751-
.expect(400);
752-
753-
expect(res.body).toHaveProperty('error', 'Invalid file path');
754-
});
755-
756-
it('should reject paths with encoded double dots', async () => {
757-
// URL-encoded '..' = %2e%2e
758-
mockFileExists.mockResolvedValue([false]); // Will be checked after validation
759-
760-
const res = await request(app)
761-
.get('/v1/static/reports/%2e%2e/secret/passwd');
762-
763-
// Should either be rejected as invalid or not found
764-
expect([400, 404]).toContain(res.statusCode);
765-
});
766-
});
767-
768-
describe('Non-existent files (404 handling)', () => {
769-
it('should return 404 for non-existent files', async () => {
770-
mockFileExists.mockResolvedValue([false]);
771-
772-
const res = await request(app)
773-
.get('/v1/static/reports/nonexistent.json')
774-
.expect(404);
775-
776-
expect(res.body).toHaveProperty('error', 'File not found');
777-
});
778-
779-
it('should return 400 for empty file path', async () => {
780-
const res = await request(app)
781-
.get('/v1/static/')
782-
.expect(400);
783-
784-
expect(res.body).toHaveProperty('error', 'File path required');
785-
});
786-
});
787-
788-
describe('Conditional requests (ETag/If-None-Match)', () => {
789-
it('should return 304 when ETag matches If-None-Match header', async () => {
790-
const etag = '"abc123"';
791-
792-
mockFileExists.mockResolvedValue([true]);
793-
mockGetMetadata.mockResolvedValue([{
794-
contentType: 'application/json',
795-
etag: etag,
796-
size: 100
797-
}]);
798-
799-
const res = await request(app)
800-
.get('/v1/static/reports/data.json')
801-
.set('If-None-Match', etag)
802-
.expect(304);
803-
804-
// 304 responses have no body
805-
expect(res.text).toEqual('');
806-
});
807-
808-
it('should return 200 with content when ETag does not match', async () => {
809-
const fileContent = JSON.stringify({ data: 'test' });
810-
const readable = Readable.from([fileContent]);
811-
812-
mockFileExists.mockResolvedValue([true]);
813-
mockGetMetadata.mockResolvedValue([{
814-
contentType: 'application/json',
815-
etag: '"abc123"',
816-
size: fileContent.length
817-
}]);
818-
mockCreateReadStream.mockReturnValue(readable);
819-
820-
const res = await request(app)
821-
.get('/v1/static/reports/data.json')
822-
.set('If-None-Match', '"different-etag"')
823-
.expect(200);
824-
825-
expect(res.headers['etag']).toEqual('"abc123"');
826-
});
827-
828-
it('should include ETag in response headers', async () => {
829-
const fileContent = JSON.stringify({ data: 'test' });
830-
const readable = Readable.from([fileContent]);
831-
832-
mockFileExists.mockResolvedValue([true]);
833-
mockGetMetadata.mockResolvedValue([{
834-
contentType: 'application/json',
835-
etag: '"abc123"',
836-
size: fileContent.length
837-
}]);
838-
mockCreateReadStream.mockReturnValue(readable);
839-
840-
const res = await request(app)
841-
.get('/v1/static/reports/data.json')
842-
.expect(200);
843-
844-
expect(res.headers).toHaveProperty('etag', '"abc123"');
845-
});
846-
});
847-
848-
describe('Error scenarios (GCS failures)', () => {
849-
it('should handle GCS exists() failure', async () => {
850-
mockFileExists.mockRejectedValue(new Error('GCS connection failed'));
851-
852-
const res = await request(app)
853-
.get('/v1/static/reports/data.json')
854-
.expect(500);
855-
856-
expect(res.body).toHaveProperty('error', 'Server failed to respond');
857-
expect(res.body).toHaveProperty('details');
858-
});
859-
860-
it('should handle GCS getMetadata() failure', async () => {
861-
mockFileExists.mockResolvedValue([true]);
862-
mockGetMetadata.mockRejectedValue(new Error('Metadata retrieval failed'));
863-
864-
const res = await request(app)
865-
.get('/v1/static/reports/data.json')
866-
.expect(500);
867-
868-
expect(res.body).toHaveProperty('error', 'Server failed to respond');
869-
expect(res.body).toHaveProperty('details');
870-
});
871-
872-
it('should handle stream errors during file read', async () => {
873-
mockFileExists.mockResolvedValue([true]);
874-
mockGetMetadata.mockResolvedValue([{
875-
contentType: 'application/json',
876-
etag: '"abc123"',
877-
size: 100
878-
}]);
879-
880-
// Create a stream that emits an error after a delay
881-
const errorStream = new Readable({
882-
read() {
883-
// Emit error asynchronously
884-
process.nextTick(() => {
885-
this.destroy(new Error('Stream read error'));
886-
});
887-
}
888-
});
889-
mockCreateReadStream.mockReturnValue(errorStream);
890-
891-
// Use try-catch since stream errors may cause connection issues
892-
try {
893-
const res = await request(app)
894-
.get('/v1/static/reports/data.json')
895-
.timeout(1000);
896-
897-
// If we get a response, verify error handling
898-
expect([200, 500]).toContain(res.statusCode);
899-
} catch (err) {
900-
// Connection aborted due to stream error is expected behavior
901-
expect(err.message).toMatch(/aborted|ECONNRESET|socket hang up/i);
902-
}
903-
});
904-
});
905-
906-
describe('MIME type detection', () => {
907-
it('should detect application/json for .json files', async () => {
908-
const content = '{"test":true}';
909-
const readable = Readable.from([content]);
910-
911-
mockFileExists.mockResolvedValue([true]);
912-
mockGetMetadata.mockResolvedValue([{ size: content.length }]);
913-
mockCreateReadStream.mockReturnValue(readable);
914-
915-
const res = await request(app)
916-
.get('/v1/static/reports/data.json')
917-
.expect(200);
918-
919-
expect(res.headers['content-type']).toContain('application/json');
920-
});
921-
922-
it('should detect image/png for .png files', async () => {
923-
const content = Buffer.from([0x89, 0x50, 0x4E, 0x47]); // PNG magic bytes
924-
const readable = Readable.from([content]);
925-
926-
mockFileExists.mockResolvedValue([true]);
927-
mockGetMetadata.mockResolvedValue([{ size: content.length }]);
928-
mockCreateReadStream.mockReturnValue(readable);
929-
930-
const res = await request(app)
931-
.get('/v1/static/reports/chart.png')
932-
.buffer(true)
933-
.parse((res, callback) => {
934-
const chunks = [];
935-
res.on('data', chunk => chunks.push(chunk));
936-
res.on('end', () => callback(null, Buffer.concat(chunks)));
937-
});
938-
939-
expect(res.statusCode).toEqual(200);
940-
expect(res.headers['content-type']).toContain('image/png');
941-
});
942-
943-
it('should use application/octet-stream for unknown extensions', async () => {
944-
const content = Buffer.from([0x00, 0x01, 0x02]);
945-
const readable = Readable.from([content]);
946-
947-
mockFileExists.mockResolvedValue([true]);
948-
mockGetMetadata.mockResolvedValue([{ size: content.length }]);
949-
mockCreateReadStream.mockReturnValue(readable);
950-
951-
const res = await request(app)
952-
.get('/v1/static/reports/file.xyz')
953-
.buffer(true)
954-
.parse((res, callback) => {
955-
const chunks = [];
956-
res.on('data', chunk => chunks.push(chunk));
957-
res.on('end', () => callback(null, Buffer.concat(chunks)));
958-
});
959-
960-
expect(res.statusCode).toEqual(200);
961-
expect(res.headers['content-type']).toContain('application/octet-stream');
962-
});
963-
});
964-
});
965676
});

0 commit comments

Comments
 (0)
pFad - Phonifier reborn

Pfad - The Proxy pFad © 2024 Your Company Name. All rights reserved.





Check this box to remove all script contents from the fetched content.



Check this box to remove all images from the fetched content.


Check this box to remove all CSS styles from the fetched content.


Check this box to keep images inefficiently compressed and original size.

Note: This service is not intended for secure transactions such as banking, social media, email, or purchasing. Use at your own risk. We assume no liability whatsoever for broken pages.


Alternative Proxies:

Alternative Proxy

pFad Proxy

pFad v3 Proxy

pFad v4 Proxy