/** * Puts a file on RouterOS's file system. * * Puts a file on RouterOS's file system, regardless of the current menu. * Note that this is a **VERY VERY VERY** time consuming method - it takes a * minimum of a little over 4 seconds, most of which are in sleep. It waits * 2 seconds after a file is first created (required to actually start * writing to the file), and another 2 seconds after its contents is written * (performed in order to verify success afterwards). * Similarly for removal (when $data is NULL) - there are two seconds in * sleep, used to verify the file was really deleted. * * If you want an efficient way of transferring files, use (T)FTP. * If you want an efficient way of removing files, use * {@link static::setMenu()} to move to the "/file" menu, and call * {@link static::remove()} without performing verification afterwards. * * @param string $filename The filename to write data in. * @param string|resource|null $data The data the file is going to have * as a string or a seekable stream. * Setting the value to NULL removes a file of this name. * If a seekable stream is provided, it is sent from its current * posistion to its end, and the pointer is seeked back to its current * position after sending. * Non seekable streams, as well as all other types, are casted to a * string. * @param bool $overwrite Whether to overwrite the file if * it exists. * * @return bool TRUE on success, FALSE on failure. */ public function filePutContents($filename, $data, $overwrite = false) { $printRequest = new Request('/file/print .proplist=""', Query::where('name', $filename)); $fileExists = count($this->client->sendSync($printRequest)) > 1; if (null === $data) { if (!$fileExists) { return false; } $removeRequest = new Request('/file/remove'); $this->client->sendSync($removeRequest->setArgument('numbers', $filename)); //Required for RouterOS to REALLY remove the file. sleep(2); return !(count($this->client->sendSync($printRequest)) > 1); } if (!$overwrite && $fileExists) { return false; } $result = $this->client->sendSync($printRequest->setArgument('file', $filename)); if (count($result->getAllOfType(Response::TYPE_ERROR)) > 0) { return false; } //Required for RouterOS to write the initial file. sleep(2); $setRequest = new Request('/file/set contents=""'); $setRequest->setArgument('numbers', $filename); $this->client->sendSync($setRequest); $this->client->sendSync($setRequest->setArgument('contents', $data)); //Required for RouterOS to write the file's new contents. sleep(2); $fileSize = $this->client->sendSync($printRequest->setArgument('file', null)->setArgument('.proplist', 'size'))->getProperty('size'); if (Stream::isStream($fileSize)) { $fileSize = stream_get_contents($fileSize); } if (Communicator::isSeekableStream($data)) { return Communicator::seekableStreamLength($data) == $fileSize; } else { return sprintf('%u', strlen((string) $data)) === $fileSize; } }
/** * Extracts a new response from a communicator. * * This is the function that performs the actual receiving, while the * constructor is also involved in locks and registry sync. * * @param Communicator $com The communicator from which to extract * the new response. * @param bool $asStream Whether to populate the argument values * with streams instead of strings. * @param int $sTimeout If a response is not immediatly * available, wait this many seconds. If NULL, wait indefinetly. * Note that if an empty sentence is received, the timeout will be * reset for another sentence receiving. * @param int $usTimeout Microseconds to add to the waiting time. * * @return void */ private function _receive(Communicator $com, $asStream = false, $sTimeout = 0, $usTimeout = null) { do { if (!$com->getTransmitter()->isDataAwaiting($sTimeout, $usTimeout)) { throw new SocketException('No data within the time limit', SocketException::CODE_NO_DATA); } $type = $com->getNextWord(); } while ('' === $type); $this->setType($type); if ($asStream) { for ($word = $com->getNextWordAsStream(), fseek($word, 0, SEEK_END); ftell($word) !== 0; $word = $com->getNextWordAsStream(), fseek($word, 0, SEEK_END)) { rewind($word); $ind = fread($word, 1); if ('=' === $ind || '.' === $ind) { $prefix = stream_get_line($word, null, '='); } if ('=' === $ind) { $value = fopen('php://temp', 'r+b'); $bytesCopied = ftell($word); while (!feof($word)) { $bytesCopied += stream_copy_to_stream($word, $value, 0xfffff, $bytesCopied); } rewind($value); $this->setAttribute($prefix, $value); continue; } if ('.' === $ind && 'tag' === $prefix) { $this->setTag(stream_get_contents($word, -1, -1)); continue; } rewind($word); $this->unrecognizedWords[] = $word; } } else { for ($word = $com->getNextWord(); '' !== $word; $word = $com->getNextWord()) { if (preg_match('/^=([^=]+)=(.*)$/sS', $word, $matches)) { $this->setAttribute($matches[1], $matches[2]); } elseif (preg_match('/^\\.tag=(.*)$/sS', $word, $matches)) { $this->setTag($matches[1]); } else { $this->unrecognizedWords[] = $word; } } } }
/** * Sends a request over a communicator. * * The only difference with the non private equivalent is that this one does * not do locking. * * @param Communicator $com The communicator to send the request over. * * @return int The number of bytes sent. * @see Client::sendSync() * @see Client::sendAsync() */ private function _send(Communicator $com) { if (!$com->getTransmitter()->isAcceptingData()) { throw new SocketException('Transmitter is invalid. Sending aborted.', SocketException::CODE_REQUEST_SEND_FAIL); } $bytes = 0; $bytes += $com->sendWord($this->getCommand()); if (null !== ($tag = $this->getTag())) { $bytes += $com->sendWord('.tag=' . $tag); } foreach ($this->attributes as $name => $value) { $prefix = '=' . $name . '='; if (is_string($value)) { $bytes += $com->sendWord($prefix . $value); } else { $bytes += $com->sendWordFromStream($prefix, $value); } } $query = $this->getQuery(); if ($query instanceof Query) { $bytes += $query->send($com); } $bytes += $com->sendWord(''); return $bytes; }
/** * Sanitizes a value of an attribute (message or query one). * * @param mixed $value The value to sanitize. * * @return string The sanitized value. */ public static function sanitizeAttributeValue($value) { if (Communicator::isSeekableStream($value)) { return $value; } else { return (string) $value; } }
public function testSetDefaultCharset() { $com = new Communicator(HOSTNAME, PORT); $this->assertNull($com->getCharset(Communicator::CHARSET_REMOTE)); $this->assertNull($com->getCharset(Communicator::CHARSET_LOCAL)); Communicator::setDefaultCharset('windows-1251'); $this->assertNull($com->getCharset(Communicator::CHARSET_REMOTE)); $this->assertNull($com->getCharset(Communicator::CHARSET_LOCAL)); $com = new Communicator(HOSTNAME, PORT); $this->assertEquals('windows-1251', $com->getCharset(Communicator::CHARSET_REMOTE)); $this->assertEquals('windows-1251', $com->getCharset(Communicator::CHARSET_LOCAL)); Communicator::setDefaultCharset(array(Communicator::CHARSET_REMOTE => 'ISO-8859-1', Communicator::CHARSET_LOCAL => 'ISO-8859-1')); $this->assertEquals('windows-1251', $com->getCharset(Communicator::CHARSET_REMOTE)); $this->assertEquals('windows-1251', $com->getCharset(Communicator::CHARSET_LOCAL)); $com = new Communicator(HOSTNAME, PORT); $this->assertEquals('ISO-8859-1', $com->getCharset(Communicator::CHARSET_REMOTE)); $this->assertEquals('ISO-8859-1', $com->getCharset(Communicator::CHARSET_LOCAL)); Communicator::setDefaultCharset(null); $this->assertEquals('ISO-8859-1', $com->getCharset(Communicator::CHARSET_REMOTE)); $this->assertEquals('ISO-8859-1', $com->getCharset(Communicator::CHARSET_LOCAL)); $com = new Communicator(HOSTNAME, PORT); $this->assertNull($com->getCharset(Communicator::CHARSET_REMOTE)); $this->assertNull($com->getCharset(Communicator::CHARSET_LOCAL)); Communicator::setDefaultCharset('windows-1251', Communicator::CHARSET_REMOTE); Communicator::setDefaultCharset('ISO-8859-1', Communicator::CHARSET_LOCAL); $this->assertNull($com->getCharset(Communicator::CHARSET_REMOTE)); $this->assertNull($com->getCharset(Communicator::CHARSET_LOCAL)); $com = new Communicator(HOSTNAME, PORT); $this->assertEquals('windows-1251', $com->getCharset(Communicator::CHARSET_REMOTE)); $this->assertEquals('ISO-8859-1', $com->getCharset(Communicator::CHARSET_LOCAL)); Communicator::setDefaultCharset(null); }
fwrite(STDOUT, implode("\n", array(implode($cSep, array(str_pad('MODE', $cColumns['mode'], ' ', STR_PAD_RIGHT), str_pad('LENGTH', $cColumns['length'], ' ', STR_PAD_BOTH), str_pad('LENGTH', $cColumns['encodedLength'], ' ', STR_PAD_BOTH), ' CONTENTS')), implode($cSep, array(str_repeat(' ', $cColumns['mode']), str_pad('(decoded)', $cColumns['length'], ' ', STR_PAD_BOTH), str_pad('(encoded)', $cColumns['encodedLength'], ' ', STR_PAD_BOTH), '')), implode('-|-', array(str_repeat('-', $cColumns['mode']), str_repeat('-', $cColumns['length']), str_repeat('-', $cColumns['encodedLength']), str_repeat('-', $cColumns['contents']))))) . "\n"); $cRegexWrap = '/([^\\n]{1,' . $cColumns['contents'] . '})/sS'; $printWord = function ($mode, $word, $msg = '') use($cSep, $cColumns, $cRegexWrap, $cColors) { $wordFragments = preg_split($cRegexWrap, $word, null, PREG_SPLIT_DELIM_CAPTURE); for ($i = 0, $l = count($wordFragments); $i < $l; $i += 2) { unset($wordFragments[$i]); } if ('' !== $cColors['']) { $wordFragments = str_replace("", "[27@", $wordFragments); } $isAbnormal = 'ERR' === $mode || 'NOTE' === $mode; if ($isAbnormal) { $details = str_pad($msg, $cColumns['length'] + $cColumns['encodedLength'] + 3, ' ', STR_PAD_BOTH); } else { $length = strlen($word); $lengthBytes = RouterOS\Communicator::encodeLength($length); $encodedLength = ''; for ($i = 0, $l = strlen($lengthBytes); $i < $l; ++$i) { $encodedLength .= str_pad(dechex(ord($lengthBytes[$i])), 2, '0', STR_PAD_LEFT); } $details = str_pad($length, $cColumns['length'], ' ', STR_PAD_LEFT) . $cSep . str_pad('0x' . strtoupper($encodedLength), $cColumns['encodedLength'], ' ', STR_PAD_LEFT); } fwrite(STDOUT, $cColors[$mode] . str_pad($mode, $cColumns['mode'], ' ', STR_PAD_RIGHT) . $cColors[''] . "{$cSep}{$details}{$cSep}{$cColors[$mode]}" . implode("\n{$cColors['']}" . str_repeat(' ', $cColumns['mode']) . $cSep . implode($isAbnormal ? ' ' : $cSep, array(str_repeat(' ', $cColumns['length']), str_repeat(' ', $cColumns['encodedLength']))) . $cSep . $cColors[$mode], $wordFragments) . "\n{$cColors['']}"); }; } else { $printWord = function ($mode, $word, $msg = '') use($cColors) { if ('' !== $cColors['']) { $word = str_replace("", "[27@", $word); $msg = str_replace("", "[27@", $msg); } if ('ERR' === $mode || 'NOTE' === $mode) {
public function testNormalAnsiConnection() { $oldCharsets = Communicator::setDefaultCharset(array(Communicator::CHARSET_LOCAL => 'UTF-8', Communicator::CHARSET_REMOTE => ANSI_PASSWORD_CHARSET)); try { $routerOS = new Client(\HOSTNAME, ANSI_USERNAME, ANSI_PASSWORD, PORT); $this->assertInstanceOf(ROS_NAMESPACE . '\\Client', $routerOS, 'Object initialization failed.'); Communicator::setDefaultCharset($oldCharsets); } catch (Exception $e) { Communicator::setDefaultCharset($oldCharsets); $this->fail('Unable to connect normally:' . (string) $e); } }
/** * Login to a RouterOS connection. * * @param Communicator $com The communicator to attempt to login to. * @param string $username The RouterOS username. * @param string $password The RouterOS password. * @param int|null $timeout The time to wait for each response. NULL * waits indefinetly. * * @return bool TRUE on success, FALSE on failure. */ public static function login(Communicator $com, $username, $password = '', $timeout = null) { if (null !== ($remoteCharset = $com->getCharset($com::CHARSET_REMOTE)) && null !== ($localCharset = $com->getCharset($com::CHARSET_LOCAL))) { $password = iconv($localCharset, $remoteCharset . '//IGNORE//TRANSLIT', $password); } $old = null; try { if ($com->getTransmitter()->isPersistent()) { $old = $com->getTransmitter()->lock(S::DIRECTION_ALL); $result = self::_login($com, $username, $password, $timeout); $com->getTransmitter()->lock($old, true); return $result; } return self::_login($com, $username, $password, $timeout); } catch (E $e) { if ($com->getTransmitter()->isPersistent() && null !== $old) { $com->getTransmitter()->lock($old, true); } throw $e instanceof NotSupportedException || $e instanceof UnexpectedValueException || !$com->getTransmitter()->isDataAwaiting() ? new SocketException('This is not a compatible RouterOS service', SocketException::CODE_SERVICE_INCOMPATIBLE, $e) : $e; } }
/** * Sends the query over a communicator. * * The only difference with the non private equivalent is that this one does * not do locking. * * @param Communicator $com The communicator to send the query over. * * @return int The number of bytes sent. */ private function _send(Communicator $com) { if (!$com->getTransmitter()->isAcceptingData()) { throw new SocketException('Transmitter is invalid. Sending aborted.', SocketException::CODE_QUERY_SEND_FAIL); } $bytes = 0; foreach ($this->words as $queryWord) { list($predicate, $value) = $queryWord; $prefix = '?' . $predicate; if (null === $value) { $bytes += $com->sendWord($prefix); } else { $prefix .= '='; if (is_string($value)) { $bytes += $com->sendWord($prefix . $value); } else { $bytes += $com->sendWordFromStream($prefix, $value); } } } return $bytes; }
public function testDefaultCharsets() { $this->assertNull($this->object->getCharset(Communicator::CHARSET_REMOTE)); $this->assertNull($this->object->getCharset(Communicator::CHARSET_LOCAL)); $this->assertEquals(array(Communicator::CHARSET_REMOTE => null, Communicator::CHARSET_LOCAL => null), $this->object->getCharset(Communicator::CHARSET_ALL)); $this->assertEquals(array(Communicator::CHARSET_REMOTE => null, Communicator::CHARSET_LOCAL => null), Communicator::getDefaultCharset(Communicator::CHARSET_ALL)); $this->assertNull(Communicator::getDefaultCharset(Communicator::CHARSET_REMOTE)); $this->assertNull(Communicator::getDefaultCharset(Communicator::CHARSET_LOCAL)); }