(jumpHostStr: string)
| 259 | * @throws Error if the input is empty or results in an empty/invalid host |
| 260 | */ |
| 261 | export function parseJumpHost(jumpHostStr: string): JumpHost { |
| 262 | let username: string | undefined; |
| 263 | let host: string; |
| 264 | let port = 22; |
| 265 | |
| 266 | let remaining = jumpHostStr.trim(); |
| 267 | |
| 268 | // Validate input is not empty |
| 269 | if (!remaining) { |
| 270 | throw new Error('Jump host string cannot be empty'); |
| 271 | } |
| 272 | |
| 273 | // Extract username if present (user@...) |
| 274 | const atIndex = remaining.indexOf('@'); |
| 275 | if (atIndex !== -1) { |
| 276 | const extractedUsername = remaining.substring(0, atIndex).trim(); |
| 277 | // Only set username if non-empty (handles case like "@host" or " @host") |
| 278 | if (extractedUsername) { |
| 279 | username = extractedUsername; |
| 280 | } |
| 281 | remaining = remaining.substring(atIndex + 1); |
| 282 | } |
| 283 | |
| 284 | // Extract port if present (...:port) |
| 285 | // Be careful with IPv6 addresses like [::1]:22 |
| 286 | if (remaining.startsWith('[')) { |
| 287 | // IPv6 address in brackets |
| 288 | const closeBracket = remaining.indexOf(']'); |
| 289 | if (closeBracket !== -1) { |
| 290 | host = remaining.substring(1, closeBracket); |
| 291 | const afterBracket = remaining.substring(closeBracket + 1); |
| 292 | if (afterBracket.startsWith(':')) { |
| 293 | const parsedPort = parseInt(afterBracket.substring(1), 10); |
| 294 | validatePort(parsedPort, jumpHostStr); |
| 295 | port = parsedPort; |
| 296 | } |
| 297 | } else { |
| 298 | // Malformed IPv6 address: missing closing bracket |
| 299 | throw new Error(`Invalid ProxyJump host "${jumpHostStr}": missing closing bracket in IPv6 address`); |
| 300 | } |
| 301 | } else { |
| 302 | // Regular hostname or IPv4 |
| 303 | const lastColon = remaining.lastIndexOf(':'); |
| 304 | if (lastColon !== -1) { |
| 305 | const potentialPort = remaining.substring(lastColon + 1); |
| 306 | // Only treat as port if it's a valid number |
| 307 | if (/^\d+$/.test(potentialPort)) { |
| 308 | host = remaining.substring(0, lastColon); |
| 309 | const parsedPort = parseInt(potentialPort, 10); |
| 310 | validatePort(parsedPort, jumpHostStr); |
| 311 | port = parsedPort; |
| 312 | } else { |
| 313 | host = remaining; |
| 314 | } |
| 315 | } else { |
| 316 | host = remaining; |
| 317 | } |
| 318 | } |
no test coverage detected