-
-
Notifications
You must be signed in to change notification settings - Fork 2.6k
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 of x-forwarded-* headers #12130
Merged
Merged
Changes from all commits
Commits
Show all changes
13 commits
Select commit
Hold shift + click to select a range
b383b65
Fixed parsing of x-forwarded-* headers
thehansys e051eea
changeset
thehansys 880e84f
remotePort
thehansys ac0a712
port fix
thehansys 416b4f2
Merge branch 'main' into main
thehansys 5b70225
port fix
thehansys 3013aa6
Merge branch 'main' of https://github.com/thehansys/astro
thehansys 23ed31c
port fix
thehansys ba12175
Merge branch 'main' into main
thehansys d251feb
Merge branch 'main' into main
thehansys fe8c473
Update .changeset/slimy-buses-agree.md
thehansys 1a889cb
Update packages/astro/src/core/app/node.ts
thehansys af15d0c
Reverted formating change
thehansys File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,7 @@ | ||
--- | ||
'astro': patch | ||
--- | ||
|
||
Fixes a bug in the parsing of `x-forwarded-\*` `Request` headers, where multiple values assigned to those headers were not correctly parsed. | ||
|
||
Now, headers like `x-forwarded-proto: https,http` are correctly parsed. |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,175 @@ | ||
import * as assert from 'node:assert/strict'; | ||
import { describe, it } from 'node:test'; | ||
import { NodeApp } from '../../../dist/core/app/node.js'; | ||
|
||
const mockNodeRequest = { | ||
url: '/', | ||
method: 'GET', | ||
headers: { | ||
host: 'example.com', | ||
}, | ||
socket: { | ||
encrypted: true, | ||
remoteAddress: '2.2.2.2', | ||
}, | ||
}; | ||
|
||
describe('NodeApp', () => { | ||
describe('createRequest', () => { | ||
describe('x-forwarded-for', () => { | ||
it('parses client IP from single-value x-forwarded-for header', () => { | ||
const result = NodeApp.createRequest({ | ||
...mockNodeRequest, | ||
headers: { | ||
'x-forwarded-for': '1.1.1.1', | ||
}, | ||
}); | ||
assert.equal(result[Symbol.for('astro.clientAddress')], '1.1.1.1'); | ||
}); | ||
|
||
it('parses client IP from multi-value x-forwarded-for header', () => { | ||
const result = NodeApp.createRequest({ | ||
...mockNodeRequest, | ||
headers: { | ||
'x-forwarded-for': '1.1.1.1,8.8.8.8', | ||
}, | ||
}); | ||
assert.equal(result[Symbol.for('astro.clientAddress')], '1.1.1.1'); | ||
}); | ||
|
||
it('parses client IP from multi-value x-forwarded-for header with spaces', () => { | ||
const result = NodeApp.createRequest({ | ||
...mockNodeRequest, | ||
headers: { | ||
'x-forwarded-for': ' 1.1.1.1, 8.8.8.8, 8.8.8.2', | ||
}, | ||
}); | ||
assert.equal(result[Symbol.for('astro.clientAddress')], '1.1.1.1'); | ||
}); | ||
|
||
it('fallbacks to remoteAddress when no x-forwarded-for header is present', () => { | ||
const result = NodeApp.createRequest({ | ||
...mockNodeRequest, | ||
headers: {}, | ||
}); | ||
assert.equal(result[Symbol.for('astro.clientAddress')], '2.2.2.2'); | ||
}); | ||
}); | ||
|
||
describe('x-forwarded-host', () => { | ||
it('parses host from single-value x-forwarded-host header', () => { | ||
const result = NodeApp.createRequest({ | ||
...mockNodeRequest, | ||
headers: { | ||
'x-forwarded-host': 'www2.example.com', | ||
}, | ||
}); | ||
assert.equal(result.url, 'https://www2.example.com/'); | ||
}); | ||
|
||
it('parses host from multi-value x-forwarded-host header', () => { | ||
const result = NodeApp.createRequest({ | ||
...mockNodeRequest, | ||
headers: { | ||
'x-forwarded-host': 'www2.example.com,www3.example.com', | ||
}, | ||
}); | ||
assert.equal(result.url, 'https://www2.example.com/'); | ||
}); | ||
|
||
it('fallbacks to host header when no x-forwarded-host header is present', () => { | ||
const result = NodeApp.createRequest({ | ||
...mockNodeRequest, | ||
headers: { | ||
host: 'example.com', | ||
}, | ||
}); | ||
assert.equal(result.url, 'https://example.com/'); | ||
}); | ||
}); | ||
|
||
describe('x-forwarded-proto', () => { | ||
it('parses protocol from single-value x-forwarded-proto header', () => { | ||
const result = NodeApp.createRequest({ | ||
...mockNodeRequest, | ||
headers: { | ||
host: 'example.com', | ||
'x-forwarded-proto': 'http', | ||
'x-forwarded-port': '80', | ||
}, | ||
}); | ||
assert.equal(result.url, 'http://example.com/'); | ||
}); | ||
|
||
it('parses protocol from multi-value x-forwarded-proto header', () => { | ||
const result = NodeApp.createRequest({ | ||
...mockNodeRequest, | ||
headers: { | ||
host: 'example.com', | ||
'x-forwarded-proto': 'http,https', | ||
'x-forwarded-port': '80,443', | ||
}, | ||
}); | ||
assert.equal(result.url, 'http://example.com/'); | ||
}); | ||
|
||
it('fallbacks to encrypted property when no x-forwarded-proto header is present', () => { | ||
const result = NodeApp.createRequest({ | ||
...mockNodeRequest, | ||
headers: { | ||
host: 'example.com', | ||
}, | ||
}); | ||
assert.equal(result.url, 'https://example.com/'); | ||
}); | ||
}); | ||
|
||
describe('x-forwarded-port', () => { | ||
it('parses port from single-value x-forwarded-port header', () => { | ||
const result = NodeApp.createRequest({ | ||
...mockNodeRequest, | ||
headers: { | ||
host: 'example.com', | ||
'x-forwarded-port': '8443', | ||
}, | ||
}); | ||
assert.equal(result.url, 'https://example.com:8443/'); | ||
}); | ||
|
||
it('parses port from multi-value x-forwarded-port header', () => { | ||
const result = NodeApp.createRequest({ | ||
...mockNodeRequest, | ||
headers: { | ||
host: 'example.com', | ||
'x-forwarded-port': '8443,3000', | ||
}, | ||
}); | ||
assert.equal(result.url, 'https://example.com:8443/'); | ||
}); | ||
|
||
it('prefers port from host', () => { | ||
const result = NodeApp.createRequest({ | ||
...mockNodeRequest, | ||
headers: { | ||
host: 'example.com:3000', | ||
'x-forwarded-port': '443', | ||
}, | ||
}); | ||
assert.equal(result.url, 'https://example.com:3000/'); | ||
}); | ||
|
||
|
||
it('prefers port from x-forwarded-host', () => { | ||
const result = NodeApp.createRequest({ | ||
...mockNodeRequest, | ||
headers: { | ||
host: 'example.com:443', | ||
'x-forwarded-host': 'example.com:3000', | ||
'x-forwarded-port': '443', | ||
}, | ||
}); | ||
assert.equal(result.url, 'https://example.com:3000/'); | ||
}); | ||
}); | ||
}); | ||
}); |
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This isn't part of the PR description; why was this changed? What are we fixing?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The old code was adding port to URL only when
x-forwarded-port
header was present and part of host header. This change handles edge-case when the port isn't part of the "host" header, the server listens on a non-standard port and there's no x-forward-port header in req. In that case it should fallback toreq.socket.remotePort
,default port
for protocol in the following order and add it to URL (default ports such as 80 and 443 are hidden in the URL).Althought, it's unlikely this would happen in a real-world scenario without broken proxy config / missing request headers.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Thank you for the thorough explanation!