Related
I am using this php class to make websocket server and i succeed to connect to websocket server using ws:// protocol but I need to connect using wss://
I am using this websocket server to build chat script and i have not informations about another websocket services like websocket.io node.js and other libraries i want to create the websocket server in pure php only
the php class is below
<?php
class PHPWebSocket
{
// maximum amount of clients that can be connected at one time
const WS_MAX_CLIENTS = 100;
// maximum amount of clients that can be connected at one time on the same IP v4 address
const WS_MAX_CLIENTS_PER_IP = 15;
// amount of seconds a client has to send data to the server, before a ping request is sent to the client,
// if the client has not completed the opening handshake, the ping request is skipped and the client connection is closed
const WS_TIMEOUT_RECV = 10;
// amount of seconds a client has to reply to a ping request, before the client connection is closed
const WS_TIMEOUT_PONG = 5;
// the maximum length, in bytes, of a frame's payload data (a message consists of 1 or more frames), this is also internally limited to 2,147,479,538
const WS_MAX_FRAME_PAYLOAD_RECV = 100000;
// the maximum length, in bytes, of a message's payload data, this is also internally limited to 2,147,483,647
const WS_MAX_MESSAGE_PAYLOAD_RECV = 500000;
// internal
const WS_FIN = 128;
const WS_MASK = 128;
const WS_OPCODE_CONTINUATION = 0;
const WS_OPCODE_TEXT = 1;
const WS_OPCODE_BINARY = 2;
const WS_OPCODE_CLOSE = 8;
const WS_OPCODE_PING = 9;
const WS_OPCODE_PONG = 10;
const WS_PAYLOAD_LENGTH_16 = 126;
const WS_PAYLOAD_LENGTH_63 = 127;
const WS_READY_STATE_CONNECTING = 0;
const WS_READY_STATE_OPEN = 1;
const WS_READY_STATE_CLOSING = 2;
const WS_READY_STATE_CLOSED = 3;
const WS_STATUS_NORMAL_CLOSE = 1000;
const WS_STATUS_GONE_AWAY = 1001;
const WS_STATUS_PROTOCOL_ERROR = 1002;
const WS_STATUS_UNSUPPORTED_MESSAGE_TYPE = 1003;
const WS_STATUS_MESSAGE_TOO_BIG = 1004;
const WS_STATUS_TIMEOUT = 3000;
// global vars
public $wsClients = array();
public $wsRead = array();
public $wsClientCount = 0;
public $wsClientIPCount = array();
public $wsOnEvents = array();
// server state functions
function wsStartServer($host, $port) {
if (isset($this->wsRead[0])) return false;
if (!$this->wsRead[0] = socket_create(AF_INET, SOCK_STREAM, SOL_TCP)) {
return false;
}
if (!socket_set_option($this->wsRead[0], SOL_SOCKET, SO_REUSEADDR, 1)) {
socket_close($this->wsRead[0]);
return false;
}
if (!socket_bind($this->wsRead[0], $host, $port)) {
socket_close($this->wsRead[0]);
return false;
}
if (!socket_listen($this->wsRead[0], 10)) {
socket_close($this->wsRead[0]);
return false;
}
$write = array();
$except = array();
$nextPingCheck = time() + 1;
while (isset($this->wsRead[0])) {
$changed = $this->wsRead;
$result = socket_select($changed, $write, $except, 1);
if ($result === false) {
socket_close($this->wsRead[0]);
return false;
}
elseif ($result > 0) {
foreach ($changed as $clientID => $socket) {
if ($clientID != 0) {
// client socket changed
$buffer = '';
$bytes = #socket_recv($socket, $buffer, 4096, 0);
if ($bytes === false) {
// error on recv, remove client socket (will check to send close frame)
$this->wsSendClientClose($clientID, self::WS_STATUS_PROTOCOL_ERROR);
}
elseif ($bytes > 0) {
// process handshake or frame(s)
if (!$this->wsProcessClient($clientID, $buffer, $bytes)) {
$this->wsSendClientClose($clientID, self::WS_STATUS_PROTOCOL_ERROR);
}
}
else {
// 0 bytes received from client, meaning the client closed the TCP connection
$this->wsRemoveClient($clientID);
}
}
else {
// listen socket changed
$client = socket_accept($this->wsRead[0]);
if ($client !== false) {
// fetch client IP as integer
$clientIP = '';
$result = socket_getpeername($client, $clientIP);
$clientIP = ip2long($clientIP);
if ($result !== false && $this->wsClientCount < self::WS_MAX_CLIENTS && (!isset($this->wsClientIPCount[$clientIP]) || $this->wsClientIPCount[$clientIP] < self::WS_MAX_CLIENTS_PER_IP)) {
$this->wsAddClient($client, $clientIP);
}
else {
socket_close($client);
}
}
}
}
}
if (time() >= $nextPingCheck) {
$this->wsCheckIdleClients();
$nextPingCheck = time() + 1;
}
}
return true; // returned when wsStopServer() is called
}
function wsStopServer() {
// check if server is not running
if (!isset($this->wsRead[0])) return false;
// close all client connections
foreach ($this->wsClients as $clientID => $client) {
// if the client's opening handshake is complete, tell the client the server is 'going away'
if ($client[2] != self::WS_READY_STATE_CONNECTING) {
$this->wsSendClientClose($clientID, self::WS_STATUS_GONE_AWAY);
}
socket_close($client[0]);
}
// close the socket which listens for incoming clients
socket_close($this->wsRead[0]);
// reset variables
$this->wsRead = array();
$this->wsClients = array();
$this->wsClientCount = 0;
$this->wsClientIPCount = array();
return true;
}
// client timeout functions
function wsCheckIdleClients() {
$time = time();
foreach ($this->wsClients as $clientID => $client) {
if ($client[2] != self::WS_READY_STATE_CLOSED) {
// client ready state is not closed
if ($client[4] !== false) {
// ping request has already been sent to client, pending a pong reply
if ($time >= $client[4] + self::WS_TIMEOUT_PONG) {
// client didn't respond to the server's ping request in self::WS_TIMEOUT_PONG seconds
$this->wsSendClientClose($clientID, self::WS_STATUS_TIMEOUT);
$this->wsRemoveClient($clientID);
}
}
elseif ($time >= $client[3] + self::WS_TIMEOUT_RECV) {
// last data was received >= self::WS_TIMEOUT_RECV seconds ago
if ($client[2] != self::WS_READY_STATE_CONNECTING) {
// client ready state is open or closing
$this->wsClients[$clientID][4] = time();
$this->wsSendClientMessage($clientID, self::WS_OPCODE_PING, '');
}
else {
// client ready state is connecting
$this->wsRemoveClient($clientID);
}
}
}
}
}
// client existence functions
function wsAddClient($socket, $clientIP) {
// increase amount of clients connected
$this->wsClientCount++;
// increase amount of clients connected on this client's IP
if (isset($this->wsClientIPCount[$clientIP])) {
$this->wsClientIPCount[$clientIP]++;
}
else {
$this->wsClientIPCount[$clientIP] = 1;
}
// fetch next client ID
$clientID = $this->wsGetNextClientID();
// store initial client data
$this->wsClients[$clientID] = array($socket, '', self::WS_READY_STATE_CONNECTING, time(), false, 0, $clientIP, false, 0, '', 0, 0);
// store socket - used for socket_select()
$this->wsRead[$clientID] = $socket;
}
function wsRemoveClient($clientID) {
// fetch close status (which could be false), and call wsOnClose
$closeStatus = $this->wsClients[$clientID][5];
if ( array_key_exists('close', $this->wsOnEvents) )
foreach ( $this->wsOnEvents['close'] as $func )
$func($clientID, $closeStatus);
// close socket
$socket = $this->wsClients[$clientID][0];
socket_close($socket);
// decrease amount of clients connected on this client's IP
$clientIP = $this->wsClients[$clientID][6];
if ($this->wsClientIPCount[$clientIP] > 1) {
$this->wsClientIPCount[$clientIP]--;
}
else {
unset($this->wsClientIPCount[$clientIP]);
}
// decrease amount of clients connected
$this->wsClientCount--;
// remove socket and client data from arrays
unset($this->wsRead[$clientID], $this->wsClients[$clientID]);
}
// client data functions
function wsGetNextClientID() {
$i = 1; // starts at 1 because 0 is the listen socket
while (isset($this->wsRead[$i])) $i++;
return $i;
}
function wsGetClientSocket($clientID) {
return $this->wsClients[$clientID][0];
}
// client read functions
function wsProcessClient($clientID, &$buffer, $bufferLength) {
if ($this->wsClients[$clientID][2] == self::WS_READY_STATE_OPEN) {
// handshake completed
$result = $this->wsBuildClientFrame($clientID, $buffer, $bufferLength);
}
elseif ($this->wsClients[$clientID][2] == self::WS_READY_STATE_CONNECTING) {
// handshake not completed
$result = $this->wsProcessClientHandshake($clientID, $buffer);
if ($result) {
$this->wsClients[$clientID][2] = self::WS_READY_STATE_OPEN;
if ( array_key_exists('open', $this->wsOnEvents) )
foreach ( $this->wsOnEvents['open'] as $func )
$func($clientID);
}
}
else {
// ready state is set to closed
$result = false;
}
return $result;
}
function wsBuildClientFrame($clientID, &$buffer, $bufferLength) {
// increase number of bytes read for the frame, and join buffer onto end of the frame buffer
$this->wsClients[$clientID][8] += $bufferLength;
$this->wsClients[$clientID][9] .= $buffer;
// check if the length of the frame's payload data has been fetched, if not then attempt to fetch it from the frame buffer
if ($this->wsClients[$clientID][7] !== false || $this->wsCheckSizeClientFrame($clientID) == true) {
// work out the header length of the frame
$headerLength = ($this->wsClients[$clientID][7] <= 125 ? 0 : ($this->wsClients[$clientID][7] <= 65535 ? 2 : 8)) + 6;
// check if all bytes have been received for the frame
$frameLength = $this->wsClients[$clientID][7] + $headerLength;
if ($this->wsClients[$clientID][8] >= $frameLength) {
// check if too many bytes have been read for the frame (they are part of the next frame)
$nextFrameBytesLength = $this->wsClients[$clientID][8] - $frameLength;
if ($nextFrameBytesLength > 0) {
$this->wsClients[$clientID][8] -= $nextFrameBytesLength;
$nextFrameBytes = substr($this->wsClients[$clientID][9], $frameLength);
$this->wsClients[$clientID][9] = substr($this->wsClients[$clientID][9], 0, $frameLength);
}
// process the frame
$result = $this->wsProcessClientFrame($clientID);
// check if the client wasn't removed, then reset frame data
if (isset($this->wsClients[$clientID])) {
$this->wsClients[$clientID][7] = false;
$this->wsClients[$clientID][8] = 0;
$this->wsClients[$clientID][9] = '';
}
// if there's no extra bytes for the next frame, or processing the frame failed, return the result of processing the frame
if ($nextFrameBytesLength <= 0 || !$result) return $result;
// build the next frame with the extra bytes
return $this->wsBuildClientFrame($clientID, $nextFrameBytes, $nextFrameBytesLength);
}
}
return true;
}
function wsCheckSizeClientFrame($clientID) {
// check if at least 2 bytes have been stored in the frame buffer
if ($this->wsClients[$clientID][8] > 1) {
// fetch payload length in byte 2, max will be 127
$payloadLength = ord(substr($this->wsClients[$clientID][9], 1, 1)) & 127;
if ($payloadLength <= 125) {
// actual payload length is <= 125
$this->wsClients[$clientID][7] = $payloadLength;
}
elseif ($payloadLength == 126) {
// actual payload length is <= 65,535
if (substr($this->wsClients[$clientID][9], 3, 1) !== false) {
// at least another 2 bytes are set
$payloadLengthExtended = substr($this->wsClients[$clientID][9], 2, 2);
$array = unpack('na', $payloadLengthExtended);
$this->wsClients[$clientID][7] = $array['a'];
}
}
else {
// actual payload length is > 65,535
if (substr($this->wsClients[$clientID][9], 9, 1) !== false) {
// at least another 8 bytes are set
$payloadLengthExtended = substr($this->wsClients[$clientID][9], 2, 8);
// check if the frame's payload data length exceeds 2,147,483,647 (31 bits)
// the maximum integer in PHP is "usually" this number. More info: http://php.net/manual/en/language.types.integer.php
$payloadLengthExtended32_1 = substr($payloadLengthExtended, 0, 4);
$array = unpack('Na', $payloadLengthExtended32_1);
if ($array['a'] != 0 || ord(substr($payloadLengthExtended, 4, 1)) & 128) {
$this->wsSendClientClose($clientID, self::WS_STATUS_MESSAGE_TOO_BIG);
return false;
}
// fetch length as 32 bit unsigned integer, not as 64 bit
$payloadLengthExtended32_2 = substr($payloadLengthExtended, 4, 4);
$array = unpack('Na', $payloadLengthExtended32_2);
// check if the payload data length exceeds 2,147,479,538 (2,147,483,647 - 14 - 4095)
// 14 for header size, 4095 for last recv() next frame bytes
if ($array['a'] > 2147479538) {
$this->wsSendClientClose($clientID, self::WS_STATUS_MESSAGE_TOO_BIG);
return false;
}
// store frame payload data length
$this->wsClients[$clientID][7] = $array['a'];
}
}
// check if the frame's payload data length has now been stored
if ($this->wsClients[$clientID][7] !== false) {
// check if the frame's payload data length exceeds self::WS_MAX_FRAME_PAYLOAD_RECV
if ($this->wsClients[$clientID][7] > self::WS_MAX_FRAME_PAYLOAD_RECV) {
$this->wsClients[$clientID][7] = false;
$this->wsSendClientClose($clientID, self::WS_STATUS_MESSAGE_TOO_BIG);
return false;
}
// check if the message's payload data length exceeds 2,147,483,647 or self::WS_MAX_MESSAGE_PAYLOAD_RECV
// doesn't apply for control frames, where the payload data is not internally stored
$controlFrame = (ord(substr($this->wsClients[$clientID][9], 0, 1)) & 8) == 8;
if (!$controlFrame) {
$newMessagePayloadLength = $this->wsClients[$clientID][11] + $this->wsClients[$clientID][7];
if ($newMessagePayloadLength > self::WS_MAX_MESSAGE_PAYLOAD_RECV || $newMessagePayloadLength > 2147483647) {
$this->wsSendClientClose($clientID, self::WS_STATUS_MESSAGE_TOO_BIG);
return false;
}
}
return true;
}
}
return false;
}
function wsProcessClientFrame($clientID) {
// store the time that data was last received from the client
$this->wsClients[$clientID][3] = time();
// fetch frame buffer
$buffer = &$this->wsClients[$clientID][9];
// check at least 6 bytes are set (first 2 bytes and 4 bytes for the mask key)
if (substr($buffer, 5, 1) === false) return false;
// fetch first 2 bytes of header
$octet0 = ord(substr($buffer, 0, 1));
$octet1 = ord(substr($buffer, 1, 1));
$fin = $octet0 & self::WS_FIN;
$opcode = $octet0 & 15;
$mask = $octet1 & self::WS_MASK;
if (!$mask) return false; // close socket, as no mask bit was sent from the client
// fetch byte position where the mask key starts
$seek = $this->wsClients[$clientID][7] <= 125 ? 2 : ($this->wsClients[$clientID][7] <= 65535 ? 4 : 10);
// read mask key
$maskKey = substr($buffer, $seek, 4);
$array = unpack('Na', $maskKey);
$maskKey = $array['a'];
$maskKey = array(
$maskKey >> 24,
($maskKey >> 16) & 255,
($maskKey >> 8) & 255,
$maskKey & 255
);
$seek += 4;
// decode payload data
if (substr($buffer, $seek, 1) !== false) {
$data = str_split(substr($buffer, $seek));
foreach ($data as $key => $byte) {
$data[$key] = chr(ord($byte) ^ ($maskKey[$key % 4]));
}
$data = implode('', $data);
}
else {
$data = '';
}
// check if this is not a continuation frame and if there is already data in the message buffer
if ($opcode != self::WS_OPCODE_CONTINUATION && $this->wsClients[$clientID][11] > 0) {
// clear the message buffer
$this->wsClients[$clientID][11] = 0;
$this->wsClients[$clientID][1] = '';
}
// check if the frame is marked as the final frame in the message
if ($fin == self::WS_FIN) {
// check if this is the first frame in the message
if ($opcode != self::WS_OPCODE_CONTINUATION) {
// process the message
return $this->wsProcessClientMessage($clientID, $opcode, $data, $this->wsClients[$clientID][7]);
}
else {
// increase message payload data length
$this->wsClients[$clientID][11] += $this->wsClients[$clientID][7];
// push frame payload data onto message buffer
$this->wsClients[$clientID][1] .= $data;
// process the message
$result = $this->wsProcessClientMessage($clientID, $this->wsClients[$clientID][10], $this->wsClients[$clientID][1], $this->wsClients[$clientID][11]);
// check if the client wasn't removed, then reset message buffer and message opcode
if (isset($this->wsClients[$clientID])) {
$this->wsClients[$clientID][1] = '';
$this->wsClients[$clientID][10] = 0;
$this->wsClients[$clientID][11] = 0;
}
return $result;
}
}
else {
// check if the frame is a control frame, control frames cannot be fragmented
if ($opcode & 8) return false;
// increase message payload data length
$this->wsClients[$clientID][11] += $this->wsClients[$clientID][7];
// push frame payload data onto message buffer
$this->wsClients[$clientID][1] .= $data;
// if this is the first frame in the message, store the opcode
if ($opcode != self::WS_OPCODE_CONTINUATION) {
$this->wsClients[$clientID][10] = $opcode;
}
}
return true;
}
function wsProcessClientMessage($clientID, $opcode, &$data, $dataLength) {
// check opcodes
if ($opcode == self::WS_OPCODE_PING) {
// received ping message
return $this->wsSendClientMessage($clientID, self::WS_OPCODE_PONG, $data);
}
elseif ($opcode == self::WS_OPCODE_PONG) {
// received pong message (it's valid if the server did not send a ping request for this pong message)
if ($this->wsClients[$clientID][4] !== false) {
$this->wsClients[$clientID][4] = false;
}
}
elseif ($opcode == self::WS_OPCODE_CLOSE) {
// received close message
if (substr($data, 1, 1) !== false) {
$array = unpack('na', substr($data, 0, 2));
$status = $array['a'];
}
else {
$status = false;
}
if ($this->wsClients[$clientID][2] == self::WS_READY_STATE_CLOSING) {
// the server already sent a close frame to the client, this is the client's close frame reply
// (no need to send another close frame to the client)
$this->wsClients[$clientID][2] = self::WS_READY_STATE_CLOSED;
}
else {
// the server has not already sent a close frame to the client, send one now
$this->wsSendClientClose($clientID, self::WS_STATUS_NORMAL_CLOSE);
}
$this->wsRemoveClient($clientID);
}
elseif ($opcode == self::WS_OPCODE_TEXT || $opcode == self::WS_OPCODE_BINARY) {
if ( array_key_exists('message', $this->wsOnEvents) )
foreach ( $this->wsOnEvents['message'] as $func )
$func($clientID, $data, $dataLength, $opcode == self::WS_OPCODE_BINARY);
}
else {
// unknown opcode
return false;
}
return true;
}
function wsProcessClientHandshake($clientID, &$buffer) {
// fetch headers and request line
$sep = strpos($buffer, "\r\n\r\n");
if (!$sep) return false;
$headers = explode("\r\n", substr($buffer, 0, $sep));
$headersCount = sizeof($headers); // includes request line
if ($headersCount < 1) return false;
// fetch request and check it has at least 3 parts (space tokens)
$request = &$headers[0];
$requestParts = explode(' ', $request);
$requestPartsSize = sizeof($requestParts);
if ($requestPartsSize < 3) return false;
// check request method is GET
if (strtoupper($requestParts[0]) != 'GET') return false;
// check request HTTP version is at least 1.1
$httpPart = &$requestParts[$requestPartsSize - 1];
$httpParts = explode('/', $httpPart);
if (!isset($httpParts[1]) || (float) $httpParts[1] < 1.1) return false;
// store headers into a keyed array: array[headerKey] = headerValue
$headersKeyed = array();
for ($i=1; $i<$headersCount; $i++) {
$parts = explode(':', $headers[$i]);
if (!isset($parts[1])) return false;
$headersKeyed[trim($parts[0])] = trim($parts[1]);
}
// check Host header was received
if (!isset($headersKeyed['Host'])) return false;
// check Sec-WebSocket-Key header was received and decoded value length is 16
if (!isset($headersKeyed['Sec-WebSocket-Key'])) return false;
$key = $headersKeyed['Sec-WebSocket-Key'];
if (strlen(base64_decode($key)) != 16) return false;
// check Sec-WebSocket-Version header was received and value is 7
if (!isset($headersKeyed['Sec-WebSocket-Version']) || (int) $headersKeyed['Sec-WebSocket-Version'] < 7) return false; // should really be != 7, but Firefox 7 beta users send 8
// work out hash to use in Sec-WebSocket-Accept reply header
$hash = base64_encode(sha1($key.'258EAFA5-E914-47DA-95CA-C5AB0DC85B11', true));
// build headers
$headers = array(
'HTTP/1.1 101 Switching Protocols',
'Upgrade: websocket',
'Connection: Upgrade',
'Sec-WebSocket-Accept: '.$hash
);
$headers = implode("\r\n", $headers)."\r\n\r\n";
// send headers back to client
$socket = $this->wsClients[$clientID][0];
$left = strlen($headers);
do {
$sent = #socket_send($socket, $headers, $left, 0);
if ($sent === false) return false;
$left -= $sent;
if ($sent > 0) $headers = substr($headers, $sent);
}
while ($left > 0);
return true;
}
// client write functions
function wsSendClientMessage($clientID, $opcode, $message) {
// check if client ready state is already closing or closed
if ($this->wsClients[$clientID][2] == self::WS_READY_STATE_CLOSING || $this->wsClients[$clientID][2] == self::WS_READY_STATE_CLOSED) return true;
// fetch message length
$messageLength = strlen($message);
// set max payload length per frame
$bufferSize = 4096;
// work out amount of frames to send, based on $bufferSize
$frameCount = ceil($messageLength / $bufferSize);
if ($frameCount == 0) $frameCount = 1;
// set last frame variables
$maxFrame = $frameCount - 1;
$lastFrameBufferLength = ($messageLength % $bufferSize) != 0 ? ($messageLength % $bufferSize) : ($messageLength != 0 ? $bufferSize : 0);
// loop around all frames to send
for ($i=0; $i<$frameCount; $i++) {
// fetch fin, opcode and buffer length for frame
$fin = $i != $maxFrame ? 0 : self::WS_FIN;
$opcode = $i != 0 ? self::WS_OPCODE_CONTINUATION : $opcode;
$bufferLength = $i != $maxFrame ? $bufferSize : $lastFrameBufferLength;
// set payload length variables for frame
if ($bufferLength <= 125) {
$payloadLength = $bufferLength;
$payloadLengthExtended = '';
$payloadLengthExtendedLength = 0;
}
elseif ($bufferLength <= 65535) {
$payloadLength = self::WS_PAYLOAD_LENGTH_16;
$payloadLengthExtended = pack('n', $bufferLength);
$payloadLengthExtendedLength = 2;
}
else {
$payloadLength = self::WS_PAYLOAD_LENGTH_63;
$payloadLengthExtended = pack('xxxxN', $bufferLength); // pack 32 bit int, should really be 64 bit int
$payloadLengthExtendedLength = 8;
}
// set frame bytes
$buffer = pack('n', (($fin | $opcode) << 8) | $payloadLength) . $payloadLengthExtended . substr($message, $i*$bufferSize, $bufferLength);
// send frame
$socket = $this->wsClients[$clientID][0];
$left = 2 + $payloadLengthExtendedLength + $bufferLength;
do {
$sent = #socket_send($socket, $buffer, $left, 0);
if ($sent === false) return false;
$left -= $sent;
if ($sent > 0) $buffer = substr($buffer, $sent);
}
while ($left > 0);
}
return true;
}
function wsSendClientClose($clientID, $status=false) {
// check if client ready state is already closing or closed
if ($this->wsClients[$clientID][2] == self::WS_READY_STATE_CLOSING || $this->wsClients[$clientID][2] == self::WS_READY_STATE_CLOSED) return true;
// store close status
$this->wsClients[$clientID][5] = $status;
// send close frame to client
$status = $status !== false ? pack('n', $status) : '';
$this->wsSendClientMessage($clientID, self::WS_OPCODE_CLOSE, $status);
// set client ready state to closing
$this->wsClients[$clientID][2] = self::WS_READY_STATE_CLOSING;
}
// client non-internal functions
function wsClose($clientID) {
return $this->wsSendClientClose($clientID, self::WS_STATUS_NORMAL_CLOSE);
}
function wsSend($clientID, $message, $binary=false) {
return $this->wsSendClientMessage($clientID, $binary ? self::WS_OPCODE_BINARY : self::WS_OPCODE_TEXT, $message);
}
function log( $message )
{
echo date('Y-m-d H:i:s: ') . $message . "\n";
}
function bind( $type, $func )
{
if ( !isset($this->wsOnEvents[$type]) )
$this->wsOnEvents[$type] = array();
$this->wsOnEvents[$type][] = $func;
}
function unbind( $type='' )
{
if ( $type ) unset($this->wsOnEvents[$type]);
else $this->wsOnEvents = array();
}
}
?>
I have this PHP code which is supposed to increase a URL shortener mask on each new entry.
My problem is that it dosen't append a new char when it hits the last one (z).
(I know incrementing is a safety issue since you can guess earlier entries, but this is not a problem in this instance)
If i add 00, it can figure out 01 and so on... but is there a simple fix to why it won't do it on its own?
(The param is the last entry)
<?php
class shortener
{
public function ShortURL($str = null)
{
if (!is_null($str))
{
for($i = (strlen($str) - 1);$i >= 0;$i--)
{
if($str[$i] != 'Z')
{
$str[$i] = $this->_increase($str[$i]);
#var_dump($str[$i]);
break;
}
else
{
$str[$i] = '0';
if($i == 0)
{
$str = '0'.$str;
}
}
}
return $str;
}
else {
return '0';
}
}
private function _increase($letter)
{
//Lowercase: 97 - 122
//Uppercase: 65 - 90
// 0 - 9 : 48 - 57
$ord = ord($letter);
if($ord == 122)
{
$ord = 65;
}
elseif ($ord == 57)
{
$ord = 97;
}
else
{
$ord++;
}
return chr($ord);
}
}
?>
Effectively, all you are doing is encoding a number into Base62. So if we take the string, decode it into base 10, increment it, and reencode it into Base62, it will be much easier to know what we are doing, and the length of the string will take care of itself.
class shortener
{
public function ShortURL($str = null)
{
if ($str==null) return 0;
$int_val = $this->toBase10($str);
$int_val++;
return $this->toBase62($int_val);
}
public function toBase62($num, $b=62) {
$base='0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ';
$r = $num % $b ;
$res = $base[$r];
$q = floor($num/$b);
while ($q) {
$r = $q % $b;
$q =floor($q/$b);
$res = $base[$r].$res;
}
return $res;
}
function toBase10( $num, $b=62) {
$base='0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ';
$limit = strlen($num);
$res=strpos($base,$num[0]);
for($i=1;$i<$limit;$i++) {
$res = $b * $res + strpos($base,$num[$i]);
}
return $res;
}
}
I'm searching for a php algorithm that efficiently test if one cidr notated network overlaps another.
Basically I have the following situation:
Array of cidr adresses:
$cidrNetworks = array(
'192.168.10.0/24',
'10.10.0.30/20',
etc.
);
I have a method that adds networks to the array, but this method should throw an exception when a network is added that overlaps with a network allready in the array.
So ie. if 192.168.10.0/25 is added an exception should be thrown.
Does anyone have/know/"can think of" an method to test this efficiently?
Here is an updated version of the class previously discussed in chat. It can do what you require, as well as many other useful things.
<?php
class IPv4Subnet implements ArrayAccess, Iterator {
/*
* Address format constants
*/
const ADDRESS_BINARY = 0x01;
const ADDRESS_INT = 0x02;
const ADDRESS_DOTDEC = 0x04;
const ADDRESS_SUBNET = 0x08;
/*
* Constants to control whether getHosts() returns the network/broadcast addresses
*/
const HOSTS_WITH_NETWORK = 0x10;
const HOSTS_WITH_BROADCAST = 0x20;
const HOSTS_ALL = 0x30;
/*
* Properties to store base address and subnet mask as binary strings
*/
protected $address;
protected $mask;
/*
* Counter to track the current iteration offset
*/
private $iteratorOffset = 0;
/*
* Array to hold values retrieved via ArrayAccess
*/
private $arrayAccessObjects = array();
/*
* Helper methods
*/
private function longToBinary ($long) {
return pack('N', $long);
}
private function longToDottedDecimal ($long) {
return ($long >> 24 & 0xFF).'.'.($long >> 16 & 0xFF).'.'.($long >> 8 & 0xFF).'.'.($long & 0xFF);
}
private function longToByteArray ($long) {
return array(
$long >> 24 & 0xFF,
$long >> 16 & 0xFF,
$long >> 8 & 0xFF,
$long & 0xFF
);
}
private function longToSubnet ($long) {
if (!isset($this->arrayAccessObjects[$long])) {
$this->arrayAccessObjects[$long] = new self($long);
}
return $this->arrayAccessObjects[$long];
}
private function binaryToLong ($binary) {
return current(unpack('N', $binary));
}
private function binaryToDottedDecimal ($binary) {
return implode('.', unpack('C*', $binary));
}
private function binaryToX ($binary, $mode) {
if ($mode & self::ADDRESS_BINARY) {
$result = $binary;
} else if ($mode & self::ADDRESS_INT) {
$result = $this->binaryToLong($binary);
} else if ($mode & self::ADDRESS_DOTDEC) {
$result = $this->binaryToDottedDecimal($binary);
} else {
$result = $this->longToSubnet($this->binaryToLong($binary));
}
return $result;
}
private function byteArrayToLong($bytes) {
return ($bytes[0] << 24) | ($bytes[1] << 16) | ($bytes[2] << 8) | $bytes[3];
}
private function byteArrayToBinary($bytes) {
return pack('C*', $bytes[0], $bytes[1], $bytes[2], $bytes[3]);
}
private function normaliseComparisonSubject (&$subject) {
if (!is_object($subject)) {
$subject = new self($subject);
}
if (!($subject instanceof self)) {
throw new InvalidArgumentException('Subject must be an instance of IPv4Subnet');
}
}
private function validateOctetArray (&$octets) {
foreach ($octets as &$octet) {
$octet = (int) $octet;
if ($octet < 0 || $octet > 255) {
return FALSE;
}
}
return TRUE;
}
/*
* Constructor
*/
public function __construct ($address = NULL, $mask = NULL) {
if ($address === NULL || (is_string($address) && trim($address) === '')) {
$address = array(0, 0, 0, 0);
} else if (is_int($address)) {
$address = $this->longToByteArray($address);
} else if (is_string($address)) {
$parts = preg_split('#\s*/\s*#', trim($address), -1, PREG_SPLIT_NO_EMPTY);
if (count($parts) > 2) {
throw new InvalidArgumentException('No usable IP address supplied: Syntax error');
} else if ($parts[0] === '') {
throw new InvalidArgumentException('No usable IP address supplied: IP address empty');
}
if (!empty($parts[1]) && !isset($mask)) {
$mask = $parts[1];
}
$address = preg_split('#\s*\.\s*#', $parts[0], -1, PREG_SPLIT_NO_EMPTY);
} else if (is_array($address)) {
$address = array_values($address);
} else {
throw new InvalidArgumentException('No usable IP address supplied: Value must be a string or an integer');
}
$suppliedAddressOctets = count($address);
$address += array(0, 0, 0, 0);
if ($suppliedAddressOctets > 4) {
throw new InvalidArgumentException('No usable IP address supplied: IP address has more than 4 octets');
} else if (!$this->validateOctetArray($address)) {
throw new InvalidArgumentException('No usable IP address supplied: At least one octet value outside acceptable range 0 - 255');
}
if ($mask === NULL) {
$mask = array_pad(array(), $suppliedAddressOctets, 255) + array(0, 0, 0, 0);
} else if (is_int($mask)) {
$mask = $this->longToByteArray($mask);
} else if (is_string($mask)) {
$mask = preg_split('#\s*\.\s*#', trim($mask), -1, PREG_SPLIT_NO_EMPTY);
switch (count($mask)) {
case 1: // CIDR
$cidr = (int) $mask[0];
if ($cidr === 0) {
// Shifting 32 bits on a 32 bit system doesn't work, so treat this as a special case
$mask = array(0, 0, 0, 0);
} else if ($cidr <= 32) {
// This looks odd, but it's the nicest way I have found to get the 32 least significant bits set in a
// way that works on both 32 and 64 bit platforms
$base = ~((~0 << 16) << 16);
$mask = $this->longToByteArray($base << (32 - $cidr));
} else {
throw new InvalidArgumentException('Supplied mask invalid: CIDR outside acceptable range 0 - 32');
}
break;
case 4: break; // Dotted decimal
default: throw new InvalidArgumentException('Supplied mask invalid: Must be either a full dotted-decimal or a CIDR');
}
} else if (is_array($mask)) {
$mask = array_values($mask);
} else {
throw new InvalidArgumentException('Supplied mask invalid: Type invalid');
}
if (!$this->validateOctetArray($mask)) {
throw new InvalidArgumentException('Supplied mask invalid: At least one octet value outside acceptable range 0 - 255');
}
// Check bits are contiguous from left
// TODO: Improve this mechanism
$asciiBits = sprintf('%032b', $this->byteArrayToLong($mask));
if (strpos(rtrim($asciiBits, '0'), '0') !== FALSE) {
throw new InvalidArgumentException('Supplied mask invalid: Set bits are not contiguous from the most significant bit');
}
$this->mask = $this->byteArrayToBinary($mask);
$this->address = $this->byteArrayToBinary($address) & $this->mask;
}
/*
* ArrayAccess interface methods (read only)
*/
public function offsetExists ($offset) {
if ($offset === 'network' || $offset === 'broadcast') {
return TRUE;
}
$offset = filter_var($offset, FILTER_VALIDATE_INT);
if ($offset === FALSE || $offset < 0) {
return FALSE;
}
return $offset < $this->getHostsCount();
}
public function offsetGet ($offset) {
if (!$this->offsetExists($offset)) {
return NULL;
}
if ($offset === 'network') {
$address = $this->getNetworkAddress(self::ADDRESS_INT);
} else if ($offset === 'broadcast') {
$address = $this->getBroadcastAddress(self::ADDRESS_INT);
} else {
// How much the address needs to be adjusted by to account for network address
$adjustment = (int) ($this->getHostsCount() > 2);
$address = $this->binaryToLong($this->address) + $offset + $adjustment;
}
return $this->longToSubnet($address);
}
public function offsetSet ($offset, $value) {}
public function offsetUnset ($offset) {}
/*
* Iterator interface methods
*/
public function current () {
return $this->offsetGet($this->iteratorOffset);
}
public function key () {
return $this->iteratorOffset;
}
public function next () {
$this->iteratorOffset++;
}
public function rewind () {
$this->iteratorOffset = 0;
}
public function valid () {
return $this->iteratorOffset < $this->getHostsCount();
}
/*
* Data access methods
*/
public function getHosts ($mode = self::ADDRESS_SUBNET) {
// Parse flags and initialise vars
$bin = (bool) ($mode & self::ADDRESS_BINARY);
$int = (bool) ($mode & self::ADDRESS_INT);
$dd = (bool) ($mode & self::ADDRESS_DOTDEC);
$base = $this->binaryToLong($this->address);
$mask = $this->binaryToLong($this->mask);
$hasNwBc = !($mask & 0x03);
$result = array();
// Get network address if requested
if (($mode & self::HOSTS_WITH_NETWORK) && $hasNwBc) {
$result[] = $base;
}
// Get hosts
for ($current = $hasNwBc ? $base + 1 : $base; ($current & $mask) === $base; $current++) {
$result[] = $current;
}
// Remove broadcast address if present and not requested
if ($hasNwBc && !($mode & self::HOSTS_WITH_BROADCAST)) {
array_pop($result);
}
// Convert to the correct type
if ($bin) {
$result = array_map(array($this, 'longToBinary'), $result);
} else if ($dd) {
$result = array_map(array($this, 'longToDottedDecimal'), $result);
} else if (!$int) {
$result = array_map(array($this, 'longToSubnet'), $result);
}
return $result;
}
public function getHostsCount () {
$count = $this->getBroadcastAddress(self::ADDRESS_INT) - $this->getNetworkAddress(self::ADDRESS_INT);
return $count > 2 ? $count - 1 : $count + 1; // Adjust return value to exclude network/broadcast addresses
}
public function getNetworkAddress ($mode = self::ADDRESS_SUBNET) {
return $this->binaryToX($this->address, $mode);
}
public function getBroadcastAddress ($mode = self::ADDRESS_SUBNET) {
return $this->binaryToX($this->address | ~$this->mask, $mode);
}
public function getMask ($mode = self::ADDRESS_DOTDEC) {
return $this->binaryToX($this->mask, $mode);
}
/*
* Stringify methods
*/
public function __toString () {
if ($this->getHostsCount() === 1) {
$result = $this->toDottedDecimal();
} else {
$result = $this->toCIDR();
}
return $result;
}
public function toDottedDecimal () {
$result = $this->getNetworkAddress(self::ADDRESS_DOTDEC);
if ($this->mask !== "\xFF\xFF\xFF\xFF") {
$result .= '/'.$this->getMask(self::ADDRESS_DOTDEC);
}
return $result;
}
public function toCIDR () {
$address = $this->getNetworkAddress(self::ADDRESS_DOTDEC);
$cidr = strlen(trim(sprintf('%b', $this->getMask(self::ADDRESS_INT)), '0')); // TODO: Improve this mechanism
return $address.'/'.$cidr;
}
/*
* Comparison methods
*/
public function contains ($subject) {
$this->normaliseComparisonSubject($subject);
$subjectAddress = $subject->getNetworkAddress(self::ADDRESS_BINARY);
$subjectMask = $subject->getMask(self::ADDRESS_BINARY);
return $this->mask !== $subjectMask && ($this->mask | ($this->mask ^ $subjectMask)) !== $this->mask && ($subjectAddress & $this->mask) === $this->address;
}
public function within ($subject) {
$this->normaliseComparisonSubject($subject);
$subjectAddress = $subject->getNetworkAddress(self::ADDRESS_BINARY);
$subjectMask = $subject->getMask(self::ADDRESS_BINARY);
return $this->mask !== $subjectMask && ($this->mask | ($this->mask ^ $subjectMask)) === $this->mask && ($this->address & $subjectMask) === $subjectAddress;
}
public function equalTo ($subject) {
$this->normaliseComparisonSubject($subject);
return $this->address === $subject->getNetworkAddress(self::ADDRESS_BINARY) && $this->mask === $subject->getMask(self::ADDRESS_BINARY);
}
public function intersect ($subject) {
$this->normaliseComparisonSubject($subject);
return $this->equalTo($subject) || $this->contains($subject) || $this->within($subject);
}
}
In order to do what you desire, the class provides 4 methods:
contains()
within()
equalTo()
intersect()
Example usage of these:
// Also accepts dotted decimal mask. The mask may also be passed to the second
// argument. Any valid combination of dotted decimal, CIDR and integers will be
// accepted
$subnet = new IPv4Subnet('192.168.0.0/24');
// These methods will accept a string or another instance
var_dump($subnet->contains('192.168.0.1')); //TRUE
var_dump($subnet->contains('192.168.1.1')); //FALSE
var_dump($subnet->contains('192.168.0.0/16')); //FALSE
var_dump($subnet->within('192.168.0.0/16')); //TRUE
// ...hopefully you get the picture. intersect() returns TRUE if any of the
// other three match.
The class also implements the Iterator interface, allowing you to iterate over all the addresses in a subnet. The iterator excludes the network and broadcast addresses, which can be retrieved separately.
Example:
$subnet = new IPv4Subnet('192.168.0.0/28');
echo "Network: ", $subnet->getNetworkAddress(),
"; Broadcast: ", $subnet->getBroadcastAddress(),
"\nHosts:\n";
foreach ($subnet as $host) {
echo $host, "\n";
}
The class also implements ArrayAccess, allowing you to treat it as an array:
$subnet = new IPv4Subnet('192.168.0.0/28');
echo $subnet['network'], "\n"; // 192.168.0.0
echo $subnet[0], "\n"; // 192.168.0.1
// ...
echo $subnet[13], "\n"; // 192.168.0.14
echo $subnet['broadcast'], "\n"; // 192.168.0.15
NB: The iterator/array methods of accessing the subnet's host addresses will return another IPv4Subnet object. The class implements __toString(), which will return the IP address as a dotted decimal if it represents a single address, or the CIDR if it represents more than one. The data can be accessed directly as a string or an integer by calling the relevant get*() method and passing the desired flag(s) (see constants defined at the top of the class).
All operations are 32- and 64-bit safe. Compatibility should be (although not thoroughly tested) 5.2+
See it working
For completeness, I imagine your use case would be implemented something along these lines:
public function addSubnet ($newSubnet) {
$newSubnet = new IPv4Subnet($newSubnet);
foreach ($this->subnets as &$existingSubnet) {
if ($existingSubnet->contains($newSubnet)) {
throw new Exception('Subnet already added');
} else if ($existingSubnet->within($newSubnet)) {
$existingSubnet = $newSubnet;
return;
}
}
$this->subnets[] = $newSubnet;
}
See it working
As discussed briefly in PHP chat, here's how I would implement it, to compare any two addresses.
Convert the IP addresses to their binary form
Extract the masks from the CIDR format
Take the minimum mask of the two (least specific =
contains more addresses)
Use the mask on both binary representations.
Compare the two.
If there is a match, then one is contained within the other.
Here's some example code, it's not very pretty and you'll want to adapt it to cater for your array.
function bin_pad($num)
{
return str_pad(decbin($num), 8, '0', STR_PAD_LEFT);
}
$ip1 = '192.168.0.0/23';
$ip2 = '192.168.1.0/24';
$regex = '~(\d+)\.(\d+)\.(\d+)\.(\d+)/(\d+)~';
preg_match($regex, $ip1, $ip1);
preg_match($regex, $ip2, $ip2);
$mask = min($ip1[5], $ip2[5]);
$ip1 = substr(
bin_pad($ip1[1]) . bin_pad($ip1[2]) .
bin_pad($ip1[3]) . bin_pad($ip1[4]),
0, $mask
);
$ip2 = substr(
bin_pad($ip2[1]) . bin_pad($ip2[2]) .
bin_pad($ip2[3]) . bin_pad($ip2[4]),
0, $mask
);
var_dump($ip1, $ip2, $ip1 === $ip2);
I had trouble making it 32 bit compatible, which is why I eventually opted for converting each octet of the IP address into binary individually, and then using substr.
I started off using pack('C4', $ip[1] .. $ip[4]) but when it came to using a full 32 bit mask I ran into problems converting it into binary (since PHP integers are signed). Thought for a future implementation though!
Intuitively I would suggest you'd want to do something like:
Let the new entry be X
Convert X to single integer form, let that integer be Y
Let mask length of any entry A be mask(A)
Compare any existing entries where mask(entry) = mask(Y)
Mask off existing entries where mask(entry) > mask(Y) and compare with Y
Mask off Y for each existing entry where mask(entry) < mask(X), such that mask(Y) = mask(entry) and compare
Provided you encounter no collisions, all is well.
Of course this does not check if the proposed subnet is valid.
My proposition of correctness here is that I can't think of a counter-example, but there may well be one so I offer this as a basis for further thought - hope this helps.
<?php
function checkOverlap ($net1, $net2) {
$mask1 = explode("/", $net1)[1];
$net1 = explode("/", $net1)[0];
$netArr1 = explode(".",$net1);
$mask2 = explode("/", $net2)[1];
$net2 = explode("/", $net2)[0];
$netArr2 = explode(".",$net2);
$newnet1 = $newnet2 = "";
foreach($netArr1 as $num) {
$binnum = decbin($num);
$length = strlen($binnum);
for ($i = 0; $i < 8-$length; $i++) {
$binnum = '0'.$binnum;
}
$newnet1 .= $binnum;
}
foreach($netArr2 as $num) {
$binnum = decbin($num);
$length = strlen($binnum);
for ($i = 0; $i < 8-$length; $i++) {
$binnum = '0'.$binnum;
}
$newnet2 .= $binnum;
}
$length = min($mask1, $mask2);
$newnet1 = substr($newnet1,0,$length);
$newnet2 = substr($newnet2,0,$length);
$overlap = 0;
if ($newnet1 == $newnet2) $overlap = 1;
return $overlap;
}
function networksOverlap ($networks, $newnet) {
$overlap = false;
foreach ($networks as $network) {
$overlap = checkOverlap($network, $newnet);
if ($overlap) return 1;
}
return $overlap;
}
$cidrNetworks = array(
'192.168.10.0/24',
'10.10.0.30/20'
);
$newnet = "192.168.10.0/25";
$overlap = networksOverlap($cidrNetworks, $newnet);
?>
Not sure if this is 100% correct but try it out see if it works.
I want to use the PHP function openssl_verify() to verify the signatures of different X.509 certificates.
I have all it needs (certificate, $data, $signature, $pub_key_id) except of the signature algorithm but which is stored in the certificate.
My simple question is: How can I extract signature algorithm from certificates?
How about this?
$cer = file_get_contents('certificate.cer');
$res = openssl_x509_read($cer);
openssl_x509_export($res, $out, FALSE);
$signature_algorithm = null;
if(preg_match('/^\s+Signature Algorithm:\s*(.*)\s*$/m', $out, $match)) $signature_algorithm = $match[1];
var_dump($signature_algorithm);
It produces the output:
string(21) "sha1WithRSAEncryption"
Which you would have to map to OPENSSL_ALGO_SHA1 yourself.
Look at this question, you can do it similar, try this:
private function GetCertSignatureAlgorithm($certSignatureBinary, $pubKeyResourceId)
{
if(false === openssl_public_decrypt($certSignatureBinary, $sigString, $pubKeyResourceId))
{
return false;
}
if (empty($sigString) ||
strlen($sigString) < 5)
{
return false;
}
if (ord($sigString[0]) !== 0x30 ||
ord($sigString[2]) !== 0x30 ||
ord($sigString[4]) !== 0x06)
{
return false;
}
$sigString = substr($sigString, 4);
$len = ord($sigString[1]);
$bytes = 0;
if ($len & 0x80)
{
$bytes = ($len & 0x7f);
$len = 0;
for ($i = 0; $i < $bytes; $i++)
{
$len = ($len << 8) | ord($sigString[$i + 2]);
}
}
$oidData = substr($sigString, 2 + $bytes, $len);
$hashOid = floor(ord($oidData[0]) / 40) . '.' . ord($oidData[0]) % 40;
$value = 0;
for ($i = 1; $i < strlen($oidData); $i++)
{
$value = $value << 7;
$value = $value | (ord($oidData[$i]) & 0x7f);
if (!(ord($oidData[$i]) & 0x80))
{
$hashOid .= '.' . $value;
$value = 0;
}
}
//www.iana.org/assignments/hash-function-text-names/hash-function-text-names.xml
//www.php.net/manual/en/openssl.signature-algos.php
switch($hashOid)
{
case '1.2.840.113549.2.5': return 'md5';
case '1.3.14.3.2.26': return 'sha1';
case '2.16.840.1.101.3.4.2.1': return 'sha256';
case '2.16.840.1.101.3.4.2.2': return 'sha384';
case '2.16.840.1.101.3.4.2.3': return 'sha512';
//not secure = not accepted
//case '1.2.840.113549.2.2': //'md2';
//case '1.2.840.113549.2.4': //'md4';
//case '1.3.14.3.2.18': //'sha';
}
throw new Exception('CertSignatureAlgorithm not found');
}
One way might be openssl x509 -text -noout < $certfile | grep "Signature Algorithm"
Using phpseclib, a pure PHP X.509 parser...
<?php
include('File/X509.php');
$x509 = new File_X509();
$cert = $x509->loadX509(file_get_contents('sample.pem'));
echo $cert['signatureAlgorithm']['algorithm'];
I would like to know whether there is a way of restricting the users of a site such that they can only access the inner pages of a site if they are within a certain range of IP addresses or a certain network?
The current PHP scripts I am getting cant differentiate the real IPs from the Proxies?
Thanks
i wouldn’t restrict on ip addresses. as you said, you can’t know if it’s a proxy. furthermore, ip addresses can be easily spoofed.
Have you considered using apache .htaccess files for that?
IP restriction with htaccess
You can try out a script I created that allows very advanced IP rules. I coded it years ago so I apologize in advance for the current shape of it.
Edit:
If you're looking for an "&" operator in the syntax don't bother. I forgot to add it when I coded this and looking back at this script now makes me cringe at the thought of touching it again.
<?php
##############################################################
# IP Expression Class #
# Easy IP-based Access Restrictions #
# Change Log: #
# - Added Range and limited IPv6 support #
# - Changed name from IPAR to IPEX #
# #
##############################################################
# Example Rules: #
# 69.[10-20].[^50].* #
# 69.*.[1-5 | 10-20 |^30].* #
# 60.12.2.* #
# 127.* #
# 69.1.1.1-70.1.1.1 <-- This is a range #
# #
# Usage: #
# Ipex::IsMatch($rule, $ip); #
# #
# [range] - Defines a range for a section of the IP #
# | - OR token. IP can match this range/number #
# ^ - NOT token. IP can not match this range/number #
# x-y - Defines a range from x to y #
# x - Exactly match x (x = a hex or dec number) #
# * - Match any number #
# #
#----------===============================-------------------#
# [ Written by Chris Tarquini ] #
#----------===============================-------------------#
##############################################################
define('IPR_DENY', false);
define('IPR_ALLOW', true);
define('IPR_ERR_MISMATCH',-1);
define('IPR_ERR_RANGE_MISMATCH',-2);
define('IPR_ERR_RANGE_INVALID',-3);
define('IPR_ERR_INVALID_RULE',-4);
class IPEX
{
const TOKEN_RANGE_BEGIN = '[';
const TOKEN_RANGE_END = ']';
const TOKEN_WILDCARD = '*';
const TOKEN_RANGE_SPLIT = '-';
const TOKEN_OR = '|';
const TOKEN_NOT = '^';
const DEBUG_MODE = TRUE;
private static function trace($err){if(self::DEBUG_MODE) echo "$err\r\n";}
private static function FixRule($rule,$count = 4, $split='.')
{
$rule = explode($split,$rule);
$filler = 0;
$size = sizeof($rule);
for($i = 0; $i < $count; $i++)
{
if($i > $size) { $rule[] = $filler; $size++;}
else if(empty($rule[$i])) { $filler = self::TOKEN_WILDCARD; $rule[$i] = $filler;}
}
return $rule;
}
private static function FixIP($rule,$count = 4, $split='.')
{
$rule = explode($split,$rule);
$size = sizeof($rule);
for($i = 0; $i < $count; $i++)
{
if($i > $size) { $rule[] = 0; $size++;}
else if(empty($rule[$i])) { $rule[$i] = 0;}
}
return $rule;
}
private static function GetIpType(&$ip)
{
$mode = IPID::Identify($ip,$newip);
if($mode == IPID_IPv4_Embed) { $ip = $newip; return IPID_IPv4;}
return $mode;
}
private static function FixIPRange(&$start, &$stop)
{
$count = 4; $split = '.';
if(self::GetIpType($start) == IPID_IPv6) {$count = 8; $split = ':';}
$q = 0;
while($q < 2)
{
$filler = ($q == 0) ? 0 : 255;
$arr = explode($split,($q == 0) ? $start : $stop);
$size = sizeof($arr);
for($i = 0; $i < $count; $i++)
{
if($i > $size){ $arr[] = $filler; $size++;}
else if(empty($arr[$i])){ $arr[$i] = $filler; }
}
if($q == 0) $start = implode($split, $arr);
else $stop = implode($split,$arr);
$q++;
}
}
public static function IsInRange($start, $stop, $ip)
{
//Sorry guys we only support IPv4 for this ;(
self::FixIPRange($start,$stop);
self::trace("fixed: start = $start, stop = $stop");
$start = ip2long($start); $stop = ip2long($stop);
$ip = ip2long($ip);
self::trace("start = $start, stop = $stop, ip = $ip");
return ($ip >= $start && $ip <= $stop);
}
public static function IsAllowed($rule, $ip){return self::IsMatch($rule,$ip);}
public static function IsMatch($rule,$ip)
{
$mode = self::GetIpType($ip);
self::trace("ip type: $mode");
if(strpos($rule, self::TOKEN_RANGE_SPLIT) !== false && strpos($rule,self::TOKEN_RANGE_BEGIN) === false)
{
self::trace("ip range mode");
$test = explode(self::TOKEN_RANGE_SPLIT, $rule);
self::trace("range size: ".sizeof($test));
print_r($test);
if(sizeof($test) != 2) return IPR_ERR_RANGE_INVALID;
$start = $test[0]; $end = $test[1];
if(empty($start) || empty($end)) return IPR_ERR_RANGE_INVALID;
self::trace("range start: $start, range stop: $end");
$rm1 = (self::IsHex($start)) ? $mode : self::GetIpType($start);
$rm2 = (self::IsHex($end)) ? $mode : self::GetIpType($end);
self::trace("range types: $rm1, $rm2\r\nip type: $mode");
if($rm1 != $rm2 || $rm1 != $mode) return IPR_ERR_RANGE_MISMATCH;
if($mode == IPID_IPv6) { return IPR_ERR_IPv6_NOTSUPPORTED;}
return self::IsInRange($start,$end,$ip);
}
if(self::GetIpType($rule) != $mode) return IPR_ERR_MISMATCH;
//all is good so far
$count = 4;
$split = '.'; if($mode==IPID_IPv6){$count = 8; $split=':';}
$rule = self::FixRule($rule, $count,$split);
$ip = self::FixIp($ip,$count,$split);
self::trace("ip: ".implode($split,$ip));
self::trace('rule: '.implode($split,$rule));
for($i = 0; $i < $count; $i++)
{
$r = str_replace(' ', '', $rule[$i]);
$ri = false;
if($r == self::TOKEN_WILDCARD) continue;
if($mode == IPPID_IPv6 && self::IsHex($r)) { $ri = hexdec($r);}else if(is_numeric($r)) $ri = $r;
$x = $ip[$i];
if($mode == IPPID_IPv6) $x = hexdec($x);
//* Exact Match *//
self::trace("rule[$i]: $ri");
self::trace("ip[$i]: $x");
if($ri !== false && $ri != $x) return IPR_DENY;
$len = strlen($r);
for($y = 0; $y < $len; $y++)
{
self::trace("y = $y");
if(substr($r, $y,1) == self::TOKEN_RANGE_BEGIN)
{
++$y;
self::trace("found range, y = $y");
$negflag = false;
$start = false;
$stop = false;
$allows = 0;
$denys = 0;
$q = 0;
$c = substr($r,$y,1);
while($c !== false)
{
self::trace("in range, char: $c");
//* Flags *//
$break = false;
$exec = false;
$toggle = false;
$reset = false;
if($c === self::TOKEN_RANGE_END) {$skiphex = true;$break = true; $exec = true; self::trace("found end of range");}
if($c === self::TOKEN_NOT) {if($q > 0){ $toggle = true; $exec = true;} else $negflag = !$negflag; $skiphex =false; self::trace("found TOKEN_NOT");}
if($c === self::TOKEN_OR) { $exec = true; $reset = true;$skiphex=true;self::trace("found TOKEN_OR");}
if($c === self::TOKEN_RANGE_SPLIT){ $skiphex = false;++$q; self::trace("found range split");}
//* Read Hex Tokens *//
if(!$skiphex && self::IsHexChar($c))
{
$n = self::ReadNextHexToken($r,$y);
if($mode == IPID_IPv6) $n = hexdec($n);
if($q == 0) $start = $n;
else if($q == 1) $stop = $n;
--$y; //fixes error
self::trace("parsed number: $n, y = $y");
}
if($reset) {$negflag = false; $start = false; $stop = false; $q = 0;}
if($exec)
{
self::trace("executing: start = $start, stop = $stop, x = $x");
self::trace("negflag = $negflag");
if($stop !== false && $x >= $start && $x <= $stop)
{
if($negflag) { ++$denys; $allows = 0; break;}
else ++$allows;
}
else if($stop === false && $start == $x)
{
if($negflag) { ++$denys; $allows = 0; break;}
else ++$allows;
}
self::trace("exec complete: allows = $allows, denys = $denys");
$q = 0;
}
if($toggle) $negflag = !$negflag;
if($break) break;
++$y;
$c = substr($r,$y,1);
}
if(!$allows) return IPR_DENY;
}
}
}
return IPR_ALLOW;
}
private static function ReadNextHexToken($buff, &$offset, $max = -1)
{
$str = '';
if($max == -1) { $max = strlen($buff);}
for(; $offset < $max; $offset++)
{
$c = substr($buff,$offset, 1);
if(self::IsHexChar($c))
$str .= $c;
else
return $str;
}
return $str;
}
private static function IsHex($x){ $len = strlen($x); for($i = 0; $i < $len; $i++) if(!self::IsHexChar(substr($x,$i,1))) return false; return true;}
private static function IsHexChar($x){self::trace("isHex($x);"); return (in_array(strtoupper($x),array('0','1','2','3','4','5','6','7','8','9','A','B','C','D','E','F')));
}
}
######################
# IP Identify Class #
#####################
define('IPID_INVALID',false);
define('IPID_IPv4',2);
define('IPID_IPv6',3);
define('IPID_IPv4_Embed',6);
class IPID
{
public static function Identify($ip,&$ipconvert = false)
{
$ip = strtoupper($ip);
$ipconvert = $ip;
// Check if we are IPv4
if(strpos($ip,':') === false && strpos($ip,'.') !== false)
return IPID_IPv4;
//Is it one of those hybrids?
else if(strpos($ip,':FFFF') !== false && strpos($ip,'.') !== false)
{
$ipconvert = substr($ip,strpos($ip,':FFFF:')+6);
return IPID_IPv4_Embed;
}
// Is it IPv6?
else if(strpos($ip,':') !== false) return IPID_IPv6;
// What the...?
return IPID_INVALID;
}
}
?>
You can use it as long as you don't try and resell it and you keep the header as is.
<?php
//This function returns True if visitor IP is allowed.
//Otherwise it returns False.
function CheckAccess()
{
//allowed IP. Change it to your static IP
$allowedip = '127.0.0.1';
$ip = $_SERVER['REMOTE_ADDR'];
return ($ip == $allowedip);
}
Proxy servers should set the X-Forwarded-For HTTP header, which you could look up with $_SERVER['HTTP_X_FORWARDED_FOR']. Otherwise $_SERVER['REMOTE_ADDR'] can be used to get the IP address. As others have noted, both of these can be easily spoofed, and there is no requirement for proxies to set the X-Forwarded-For request header.
There is a ip2long() function in PHP which give you an integer to use for range checking.
To get the location of an IP address you need a lookup table which maps IP address ranges to approximate geographical locations (such lookup tables are typically not free). There are many services which offer IP address geolocation, some of which are mentioned here and here.