in httpcore5/src/main/java/org/apache/hc/core5/reactor/SocksProxyProtocolHandler.java [155:287]
public void inputReady(final IOSession session, final ByteBuffer src) throws IOException {
if (src != null) {
try {
this.buffer.put(src);
} catch (final BufferOverflowException ex) {
throw new IOException("Unexpected input data");
}
}
switch (this.state) {
case RECEIVE_AUTH_METHOD:
if (fillBuffer(session)) {
this.buffer.flip();
final byte serverVersion = this.buffer.get();
final byte serverMethod = this.buffer.get();
if (serverVersion != CLIENT_VERSION) {
throw new IOException("SOCKS server returned unsupported version: " + serverVersion);
}
if (serverMethod == USERNAME_PASSWORD) {
this.buffer.clear();
final byte[] username = cred(reactorConfig.getSocksProxyUsername());
final byte[] password = cred(reactorConfig.getSocksProxyPassword());
setBufferLimit(username.length + password.length + 3);
this.buffer.put(USERNAME_PASSWORD_VERSION);
this.buffer.put((byte) username.length);
this.buffer.put(username);
this.buffer.put((byte) password.length);
this.buffer.put(password);
this.buffer.flip();
session.setEventMask(SelectionKey.OP_WRITE);
this.state = State.SEND_USERNAME_PASSWORD;
} else if (serverMethod == NO_AUTHENTICATION_REQUIRED) {
prepareConnectCommand();
session.setEventMask(SelectionKey.OP_WRITE);
this.state = State.SEND_CONNECT;
} else {
throw new IOException("SOCKS server return unsupported authentication method: " + serverMethod);
}
}
break;
case RECEIVE_AUTH:
if (fillBuffer(session)) {
this.buffer.flip();
this.buffer.get(); // skip server auth version
final byte status = this.buffer.get();
if (status != SUCCESS) {
throw new IOException("Authentication failed for external SOCKS proxy");
}
prepareConnectCommand();
session.setEventMask(SelectionKey.OP_WRITE);
this.state = State.SEND_CONNECT;
}
break;
case RECEIVE_RESPONSE_CODE:
if (fillBuffer(session)) {
this.buffer.flip();
final byte serverVersion = this.buffer.get();
final byte responseCode = this.buffer.get();
if (serverVersion != CLIENT_VERSION) {
throw new IOException("SOCKS server returned unsupported version: " + serverVersion);
}
switch (responseCode) {
case SUCCESS:
break;
case 1:
throw new IOException("SOCKS: General SOCKS server failure");
case 2:
throw new IOException("SOCKS5: Connection not allowed by ruleset");
case 3:
throw new IOException("SOCKS5: Network unreachable");
case 4:
throw new IOException("SOCKS5: Host unreachable");
case 5:
throw new IOException("SOCKS5: Connection refused");
case 6:
throw new IOException("SOCKS5: TTL expired");
case 7:
throw new IOException("SOCKS5: Command not supported");
case 8:
throw new IOException("SOCKS5: Address type not supported");
default:
throw new IOException("SOCKS5: Unexpected SOCKS response code " + responseCode);
}
this.buffer.compact();
this.buffer.limit(3);
this.state = State.RECEIVE_ADDRESS_TYPE;
// deliberate fall-through
} else {
break;
}
case RECEIVE_ADDRESS_TYPE:
if (fillBuffer(session)) {
this.buffer.flip();
this.buffer.get(); // reserved byte that has no purpose
final byte aType = this.buffer.get();
final int addressSize;
if (aType == InetAddressUtils.IPV4) {
addressSize = 4;
} else if (aType == InetAddressUtils.IPV6) {
addressSize = 16;
} else if (aType == ATYP_DOMAINNAME) {
// mask with 0xFF to convert to unsigned byte value
addressSize = this.buffer.get() & 0xFF;
} else {
throw new IOException("SOCKS server returned unsupported address type: " + aType);
}
final int remainingResponseSize = addressSize + 2;
this.buffer.compact();
// make sure we only read what we need to, don't read too much
this.buffer.limit(remainingResponseSize);
this.state = State.RECEIVE_ADDRESS;
// deliberate fall-through
} else {
break;
}
case RECEIVE_ADDRESS:
if (fillBuffer(session)) {
this.buffer.clear();
this.state = State.COMPLETE;
final IOEventHandler newHandler = this.eventHandlerFactory.createHandler(dataChannel, sessionRequest.attachment);
dataChannel.upgrade(newHandler);
sessionRequest.completed(dataChannel);
dataChannel.handleIOEvent(SelectionKey.OP_CONNECT);
}
break;
case SEND_AUTH:
case SEND_USERNAME_PASSWORD:
case SEND_CONNECT:
session.setEventMask(SelectionKey.OP_WRITE);
break;
case COMPLETE:
break;
}
}