Skip to content

Commit e6c1de7

Browse files
authored
fix: resolve issue with processing content-length incorrectly (#520)
## Description This PR fixes issue with `content-length` header. ## Type of change - [x] Bug fix - [ ] New feature - [ ] Protocol integration - [ ] Documentation update - [ ] Other (please describe): ## Screenshots None. ## Testing `pnpm t` ## Related Issues Relates to: #451 ## Checklist - [x] My code follows the project's style guidelines - [x] I have added tests that prove my fix/feature works - [x] All tests pass locally and in CI - [ ] I have updated documentation as needed - [x] CI/CD checks pass - [ ] I have included screenshots for protocol screens (if applicable) - [ ] For security-related features, I have included links to related information <!-- ELLIPSIS_HIDDEN --> ---- > [!IMPORTANT] > Fixes handling of `Content-Length` header in `GET` function, ensuring correct behavior when present or absent, with tests added. > > - **Behavior**: > - Fixes handling of `Content-Length` header in `GET` function in `route.ts`. > - Preserves `Content-Length` if present; omits if absent. > - Returns 400 error if required headers are missing. > - **Tests**: > - Adds tests in `endpoint.test.ts` for responses with and without `Content-Length`. > - Verifies correct status and headers in both scenarios. > - **Misc**: > - Renames test suite from `metadata/[network] endpoint` to `Metadata Proxy Route`. > > <sup>This description was created by </sup>[<img alt="Ellipsis" src="https://img.shields.io/badge/Ellipsis-blue?color=175173">](https://www.ellipsis.dev?ref=solana-foundation%2Fexplorer&utm_source=github&utm_medium=referral)<sup> for 6252226. It will automatically update as commits are pushed.</sup> <!-- ELLIPSIS_HIDDEN -->
1 parent d7a3b7d commit e6c1de7

File tree

2 files changed

+89
-4
lines changed

2 files changed

+89
-4
lines changed

app/api/metadata/proxy/__tests__/endpoint.test.ts

Lines changed: 75 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -49,7 +49,7 @@ function requestFactory(uri?: string) {
4949
return { nextParams, request };
5050
}
5151

52-
describe('metadata/[network] endpoint', () => {
52+
describe('Metadata Proxy Route', () => {
5353
const validUrl = encodeURIComponent('http://external.resource/file.json');
5454
const unsupportedUri = encodeURIComponent('ftp://unsupported.resource/file.json');
5555

@@ -104,3 +104,77 @@ describe('metadata/[network] endpoint', () => {
104104
expect((await GET(request.request, request.nextParams)).status).toBe(200);
105105
});
106106
});
107+
108+
describe('Metadata Proxy Route :: resource fetching', () => {
109+
const testUri = 'http://google.com/metadata.json';
110+
const testData = { description: "Test Description", name: "Test NFT" };
111+
112+
beforeEach(() => {
113+
process.env.NEXT_PUBLIC_METADATA_ENABLED = 'true';
114+
});
115+
116+
it('should handle response without Content-Length header', async () => {
117+
const sourceHeaders = new Headers({
118+
'Cache-Control': 'max-age=3600',
119+
'Content-Type': 'application/json',
120+
'ETag': 'test-etag',
121+
});
122+
123+
// @ts-expect-error lookup does not have mocked fn
124+
dns.lookup.mockResolvedValueOnce([{ address: '8.8.8.8' }]);
125+
126+
// Mock fetch to return a response without Content-Length
127+
// @ts-expect-error fetch does not have mocked fn
128+
fetch.mockResolvedValueOnce({
129+
arrayBuffer: async () => new ArrayBuffer(8),
130+
headers: sourceHeaders,
131+
json: async () => testData
132+
});
133+
134+
const request = new Request(`http://localhost:3000/api/metadata/proxy?uri=${encodeURIComponent(testUri)}`);
135+
const response = await GET(request, { params: {} });
136+
137+
// Verify response
138+
expect(response.status).toBe(200);
139+
expect(response.headers.get('content-type')).toBe('application/json');
140+
expect(response.headers.get('cache-control')).toBe('max-age=3600');
141+
expect(response.headers.get('etag')).toBe('test-etag');
142+
143+
// Next.js should calculate and set Content-Length automatically in theory
144+
const contentLength = response.headers.get('content-length');
145+
expect(contentLength).toBeNull();
146+
});
147+
148+
it('should preserve original Content-Length header when present', async () => {
149+
const originalContentLength = '89'; // Length of testData JSON, but different from real one
150+
const sourceHeaders = new Headers({
151+
'Cache-Control': 'max-age=3600',
152+
'Content-Length': originalContentLength,
153+
'Content-Type': 'application/json',
154+
'ETag': 'test-etag',
155+
});
156+
157+
// @ts-expect-error lookup does not have mocked fn
158+
dns.lookup.mockResolvedValueOnce([{ address: '8.8.8.8' }]);
159+
160+
// Mock fetch to return a response with Content-Length
161+
// @ts-expect-error fetch does not have mocked fn
162+
fetch.mockResolvedValueOnce({
163+
arrayBuffer: async () => new ArrayBuffer(8),
164+
headers: sourceHeaders,
165+
json: async () => testData
166+
});
167+
168+
const request = new Request(`http://localhost:3000/api/metadata/proxy?uri=${encodeURIComponent(testUri)}`);
169+
const response = await GET(request, { params: {} });
170+
171+
// Verify response
172+
expect(response.status).toBe(200);
173+
expect(response.headers.get('content-type')).toBe('application/json');
174+
expect(response.headers.get('cache-control')).toBe('max-age=3600');
175+
expect(response.headers.get('etag')).toBe('test-etag');
176+
177+
// Content-Length should match original
178+
expect(response.headers.get('content-length')).toBe(originalContentLength);
179+
});
180+
});

app/api/metadata/proxy/route.ts

Lines changed: 14 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -89,13 +89,24 @@ export async function GET(
8989
}
9090

9191
// preserve original cache-control headers
92-
const responseHeaders = {
92+
const contentLength = resourceHeaders.get('content-length');
93+
const responseHeaders: Record<string, string> = {
9394
'Cache-Control': resourceHeaders.get('cache-control') ?? 'no-cache',
94-
'Content-Length': resourceHeaders.get('content-length') as string,
95-
'Content-Type': resourceHeaders.get('content-type') ?? 'application/json, charset=utf-8',
95+
'Content-Type': resourceHeaders.get('content-type') ?? 'application/json; charset=utf-8',
9696
Etag: resourceHeaders.get('etag') ?? 'no-etag',
9797
};
9898

99+
// Only set Content-Length if it exists in the original response
100+
if (contentLength) {
101+
responseHeaders['Content-Length'] = contentLength;
102+
}
103+
104+
// Validate that all required headers are present
105+
const hasMissingHeaders = Object.values(responseHeaders).some(value => value == null);
106+
if (hasMissingHeaders) {
107+
return respondWithError(400);
108+
}
109+
99110
if (data instanceof ArrayBuffer) {
100111
return new NextResponse(data, {
101112
headers: responseHeaders,

0 commit comments

Comments
 (0)