@@ -23,8 +23,9 @@ export function getClientIpAddress(req: {
2323 // Handle comma-separated values (like X-Forwarded-For)
2424 const ips = headerValue . split ( "," ) . map ( ( ip ) => ip . trim ( ) ) ;
2525 for ( const ip of ips ) {
26- if ( isIP ( ip ) ) {
27- return ip ;
26+ const processedIp = normalizeIPAddress ( ip ) ;
27+ if ( isIP ( processedIp ) ) {
28+ return processedIp ;
2829 }
2930 }
3031 }
@@ -40,36 +41,24 @@ export function getClientIpAddress(req: {
4041 // https://github.com/pbojinov/request-ip/pull/71
4142 const forwardedHeader = getHeaderValue ( req . headers , "forwarded" ) ;
4243 if ( forwardedHeader ) {
43- // Split by comma for multiple forwarded entries
44- const forwardedEntries = forwardedHeader . split ( "," ) ;
44+ // Split by comma for multiple forwarded entries, trimming each entry
45+ const forwardedEntries = forwardedHeader . split ( "," ) . map ( entry => entry . trim ( ) ) ;
4546
4647 for ( const entry of forwardedEntries ) {
47- // Split by semicolon for parameters
48- const params = entry . split ( ";" ) ;
48+ // Split by semicolon for parameters, trimming each parameter
49+ const params = entry . split ( ";" ) . map ( param => param . trim ( ) ) ;
4950
5051 for ( const param of params ) {
51- const trimmed = param . trim ( ) ;
52- if ( trimmed . toLowerCase ( ) . startsWith ( "for=" ) ) {
53- let ipVal = trimmed . substring ( 4 ) . trim ( ) ;
52+ if ( param . toLowerCase ( ) . startsWith ( "for=" ) ) {
53+ let ipVal = param . substring ( 4 ) . trim ( ) ;
5454
5555 // Remove quotes if present
5656 if ( ipVal . startsWith ( '"' ) && ipVal . endsWith ( '"' ) ) {
5757 ipVal = ipVal . slice ( 1 , - 1 ) ;
5858 }
5959
60- // Handle IPv6 brackets
61- if ( ipVal . startsWith ( "[" ) && ipVal . endsWith ( "]" ) ) {
62- ipVal = ipVal . slice ( 1 , - 1 ) ;
63- }
64-
65- // Handle port stripping for IPv4 addresses
66- if ( ipVal . includes ( ":" ) ) {
67- const parts = ipVal . split ( ":" ) ;
68- // Only strip port if it looks like IPv4:port (not IPv6)
69- if ( parts . length === 2 && isIP ( parts [ 0 ] ) ) {
70- ipVal = parts [ 0 ] ;
71- }
72- }
60+ // Normalize IP address (remove brackets and ports)
61+ ipVal = normalizeIPAddress ( ipVal ) ;
7362
7463 if ( isIP ( ipVal ) ) {
7564 return ipVal ;
@@ -82,6 +71,40 @@ export function getClientIpAddress(req: {
8271 return undefined ;
8372}
8473
74+ // Helper function to normalize IP address by removing brackets and ports
75+ function normalizeIPAddress ( ip : string ) : string {
76+ let processedIp = ip . trim ( ) ;
77+
78+ // Remove IPv6 brackets if present (do this first!)
79+ const bracketStart = processedIp . startsWith ( "[" ) ;
80+ const closingBracketIndex = processedIp . indexOf ( "]" ) ;
81+ const hasPortAfterBracket = closingBracketIndex > 0 && processedIp [ closingBracketIndex + 1 ] === ":" ;
82+ if ( bracketStart && hasPortAfterBracket ) {
83+ // Extract IPv6 part and port: [2001:db8::1]:8080 -> 2001:db8::1:8080
84+ processedIp = processedIp . substring ( 1 , closingBracketIndex ) + processedIp . substring ( closingBracketIndex + 1 ) ;
85+ } else if ( processedIp . startsWith ( "[" ) && processedIp . endsWith ( "]" ) ) {
86+ // Simple bracket removal: [2001:db8::1] -> 2001:db8::1
87+ processedIp = processedIp . slice ( 1 , - 1 ) ;
88+ }
89+
90+ // Strip port if present (handles both IPv4:port and IPv6:port)
91+ if ( processedIp . includes ( ":" ) ) {
92+ const lastColonIndex = processedIp . lastIndexOf ( ":" ) ;
93+ if ( lastColonIndex > 0 ) {
94+ const potentialPort = processedIp . substring ( lastColonIndex + 1 ) ;
95+ // If the part after the last colon looks like a port number
96+ if ( / ^ \d + $ / . test ( potentialPort ) ) {
97+ const potentialIP = processedIp . substring ( 0 , lastColonIndex ) ;
98+ if ( isIP ( potentialIP ) ) {
99+ processedIp = potentialIP ;
100+ }
101+ }
102+ }
103+ }
104+
105+ return processedIp ;
106+ }
107+
85108// Helper function to get header value case-insensitively
86109function getHeaderValue (
87110 headers : Record < string , string | string [ ] | undefined > ,
0 commit comments