Codeigniter encryption without slash - php

I know this may look like duplicate from this question: Ignore slash while using encryption in Codeigniter. But I still didn't have the answer from it.
I want to sent encrypted email name as URL to their email account.
Then that URL is decrypted to search if that email name is exist in my database to permit that email into my system.
The problem is:
If I use urlencode or base64_encode after encryption, it always resulted in empty value to search the database after decrypt. I think it because the encrypted value always changing.
If I use the casual encryption, it might have the ("/") character.
If I only use the encode, without the encryption, it might permit the email name to have access into my system.
Lastly, I found some library: Ignore Slash while using encryption in codeigniter - GitHub .
But it gave me this error: Undefined property: CI_Loader::$my_encrypt
I don't know what I've done wrong, I already:
Capitalized the class name first letter.
Using the same file name with the class name. (capitalized too)
Change the extend to CI_Encryption because the Encrypt class is already deprecated.
Insert the public function __construct() {parent::__construct();} before all method.
Place the file inside application/library.
Load the library $this->load->library('my_encrypt');
Load the method using $this->my_encrypt->encode($key); this is the line that gave me an error.
I know that this may sound like a simple mistake, but I'm using another third-party library too but it didn't give me an error at all.
Can anyone help me find the mistake / missing step there?
Update -
Before I load the library in the controller, I want to check the result first in view. But it doesn't give me any changes even when I put the code inside controller. Here is the code :
$key = 'example#gmail.com';
$this->load->library('my_encrypt');
$segment = $this->my_encrypt->encode($key);
echo $segment;
echo ( $this->my_encrypt->decode($segment) );
Update:
Fix library code to extend with CI_Encryption library

Have you loaded the library? Name librabry as MY_Encrypt.php in application libraries
<?php
class MY_Encrypt extends CI_Encrypt
{
/**
* Encodes a string.
*
* #param string $string The string to encrypt.
* #param string $key[optional] The key to encrypt with.
* #param bool $url_safe[optional] Specifies whether or not the
* returned string should be url-safe.
* #return string
*/
public function __construct() {
parent::__construct();
}
function encode($string, $key="", $url_safe=TRUE)
{
$ret = parent::encode($string, $key);
if ($url_safe)
{
$ret = strtr(
$ret,
array(
'+' => '.',
'=' => '-',
'/' => '~'
)
);
}
return $ret;
}
/**
* Decodes the given string.
*
* #access public
* #param string $string The encrypted string to decrypt.
* #param string $key[optional] The key to use for decryption.
* #return string
*/
function decode($string, $key="")
{
$string = strtr(
$string,
array(
'.' => '+',
'-' => '=',
'~' => '/'
)
);
return parent::decode($string, $key);
}
}
?>
Now call the encrypt library and use the encryption class instead of my_encrypt
$key='Welcome';
$this->load->library('encrypt');
$key1= $this->encrypt->encode($key);
echo $key1;

fixed to extend the CI_Encryption library, sorry for bothering. :)
class MY_Encrypt extends CI_Encryption
{
/**
* Encodes a string.
*
* #param string $string The string to encrypt.
* #param string $key[optional] The key to encrypt with.
* #param bool $url_safe[optional] Specifies whether or not the
* returned string should be url-safe.
* #return string
*/
public function __construct() {
parent::__construct();
}
function encode($string)
{
$ret = parent::encrypt($string);
if ( !empty($string) )
{
$ret = strtr(
$ret,
array(
'+' => '.',
'=' => '-',
'/' => '~'
)
);
}
return $ret;
}
/**
* Decodes the given string.
*
* #access public
* #param string $string The encrypted string to decrypt.
* #param string $key[optional] The key to use for decryption.
* #return string
*/
function decode($string)
{
$string = strtr(
$string,
array(
'.' => '+',
'-' => '=',
'~' => '/'
)
);
return parent::decrypt($string);
}
}
?>

Related

PHP AES encryption key

I have a PHP and MySQL system and I need to encrypt user data with AES-256. I know how to encrypt and decrypt the data using AES-encrypt/decrypt but I'm not sure how to securely store the AES encryption key. Would it be recommended to store the key inside of a file outside of the public website folder, then use
<?php include('')?>
to call the key for the encryption?
Thanks
Access to your data should currently be constrained by a username/password for MySQL - Where do you store that?
Adding encryption into the mix raises the possibility of splitting the things-you-need-to-know-to-access-the-data across different substrates - with different exposures.
The link in the comment by Mehdi covers some of the options at a fairly abstract level. It doesn't mention, for example, storing the key at the client. But the choice of which method(s) you use depends on the infrastructure, code management, deployment and operational processes in place. The right choice for a low end shared web-hosting service is not the right choice for a dedicated datacentre and vice versa.
You do propose a specific method for managing the key: storing it outside the document root limits access. If you go further and store it in something which is recognized as PHP code by your webserver then access via the webserver should only expose the output of the PHP code - conversely if it were stored in a text file, and someone could get the webserver to serve the file, they would have access to the key.
OTOH its not a great solution if the key hows up in your github repository, or if other people have access to your filesystem/backups/logs.
You need to think about about how you develop code, whom should be able to use the key, whom should be able to see the key itself, whom should definitely not be able to see the key, how your backups are managed, whom has access to your storage.....
It is impossible to provide sufficient information in a question here on SO to get an informed and definitive answer.
At the bottom of the above answer,
I've added:
/*
$key will store in the database in refrence of this content and this key will use to decrypt the data as given below
$
*/
$content = 'blahlol';
$aes = new AES_Encrypt();
$encryptedData = $aes->setData($content)->encrypt()->getEncryptedString();
$key = $aes->getKey();
echo $encryptedData;
echo '<br>';
$decryptedData = $aes->setData($encryptedData)->decrypt()->getDecryptedString();
echo $decryptedData;
//The code above outputs an encrypted string followed by "blahlol" which is my $content variable.
//Below, I'm trying to grab the encrypted string from the database and decrypt it. However it outputs nothing
echo '<br><br><br><br>Database:<br><br>';
$sql = "SELECT * from data WHERE id = '1'";
$result = $con->query($sql);
while($rowLol = $result->fetch_assoc()) {
echo $rowLol['data']; //Outputs encrypted string
$aes = new AES_Encrypt($key);
$decryptedData = $aes->setData($rowLol['data'])->decrypt()->getDecryptedString();
echo $decryptedData; //Meant to output decrypted string (blahlol)
}
?>
At the top, it outputs the encrypted string of "blahlol" followed by plaintext "blahlol" after decryption. However I'm trying to decrypt it by getting the encrypted string for the database.
As noted in the code, the decrypted part outputs nothing.
There are two way you can keep encrypted data.
File
Databse
If data is less you can manage encrypted data in the database and if data is large then it is good to store encrypted data in file outside the public folder.
To make data more secure, use unique key to encrypt every data and save that unique key in the database including data reference value, so when you will decrypt data using referred unique key from the database.
Create php class to handle this.
<?php
class AES_Encrypt {
/**
* #var string
*/
private $key;
/**
*
* #var String
*/
private $string;
/**
*
* #var String
*/
private $encryptedString;
/**
*
* #var String
*/
private $decryptedString;
/**
* Constructor
*/
public function __construct($key = null) {
if (empty($key)) {
$this->setKey(md5($this->randomStr(5)) . '.' . base64_encode(openssl_random_pseudo_bytes(32)));
} else {
$this->setKey($key);
}
}
/**
*
* #param String $key
* #return \AES_Encrypt
*/
public function setKey($key) {
$this->key = $key;
return $this;
}
/**
* Return security key
* #return string
*/
public function getKey() {
return $this->key;
}
/**
*
* #param type $string
* #return \AES_Encrypt
*/
public function setData($string) {
$this->string = $string;
return $this;
}
/**
*
* #return string
*/
public function getData() {
return $this->string;
}
/**
* Convert encrypt string from plain
* #return \AES_Encrypt
*/
public function encrypt() {
$privateKey = explode('.', $this->getKey(), 2);
// Remove the base64 encoding from our key
$encryption_key = base64_decode($privateKey[1]);
// Generate an initialization vector
$iv = openssl_random_pseudo_bytes(openssl_cipher_iv_length('aes-256-cbc'));
// Encrypt the data using AES 256 encryption in CBC mode using our encryption key and initialization vector.
$encrypted = openssl_encrypt($this->getData(), 'aes-256-cbc', $encryption_key, 0, $iv);
// The $iv is just as important as the key for decrypting, so save it with our encrypted data using a unique separator (::)
$this->encryptedString = base64_encode($encrypted . '::' . $iv);
return $this;
}
/**
*
* #return string
*/
public function getEncryptedString() {
return $this->encryptedString;
}
/**
*
* #return string
*/
public function getDecryptedString() {
return $this->decryptedString;
}
/**
* Convert decrypt string
*/
public function decrypt() {
$privateKey = explode('.', $this->getKey(), 2);
// Remove the base64 encoding from our key
$encryption_key = base64_decode($privateKey[1]);
// To decrypt, split the encrypted data from our IV - our unique separator used was "::"
list($encrypted_data, $iv) = explode('::', base64_decode($this->getData()), 2);
$this->decryptedString = openssl_decrypt($encrypted_data, 'aes-256-cbc', $encryption_key, 0, $iv);
return $this;
}
/**
*
* #param type $length
* #return string
*/
public function randomStr($length = 5) {
$string = 'abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789';
$charactersLength = strlen($string);
$str = '';
for ($i = 0; $i < $length; $i++) {
$str .= $string[rand(0, $charactersLength - 1)];
}
return $str;
}
}
$aes = new AES_Encrypt();
$encryptedData = $aes->setData($content)->encrypt()->getEncryptedString();
$key = $aes->getKey();
/*
$key will store in the databse in refrence of this content and this key will use to decrypt the data as given below
*/
$aes = new AES_Encrypt($key);
$decryptedData = $aes->setData($encryptedContent)->decrypt()->getDecryptedString();

codeigniter url encrypt not working

<a href="<?php echo base_url().'daily_report/index/'.$this->encrypt->encode($this->session->userdata('employee_id')) ?>">
i have encrypted the above url using the codeigniter encrypt
i set the encryption key in codeigniter config file
$config['encryption_key'] = 'gIoueTFDwGzbL2Bje9Bx5B0rlsD0gKDV';
and i called in the autoload
$autoload['libraries'] = array('session','form_validation','encrypt','encryption','database');
when the ulr(href) load into the url it look like this
http://localhost/hrms/daily_report/index/FVjGcz4qQztqAk0jaomJiAFBZ/vKVSBug1iGPQeKQCZ/K7+WUE4E/M9u1EjWh3uKTKeIhExjGKK1dJ2awL0+zQ==
but the url is not decoded, and i;m not getting the employee_id it shows empty.
public function index($employee_id) {
$save_employee_id = $employee_id;
// decoding the encrypted employee id
$get_employee_id = $this->encrypt->decode($save_employee_id);
echo $employee_id; // answer: FVjGcz4qQztqAk0jaomJiAFBZ
echo "<br>";
echo $get_employee_id; // is display the null
echo "<br>";
exit();
// get the employee daily report
$data['get_ind_report'] = $this->daily_report_model->get_ind_report($get_employee_id);
// daily report page
$data['header'] = "Daily Report";
$data['sub_header'] = "All";
$data['main_content'] = "daily_report/list";
$this->load->view('employeelayout/main',$data);
}
complete url(3) is
FVjGcz4qQztqAk0jaomJiAFBZ/vKVSBug1iGPQeKQCZ/K7+WUE4E/M9u1EjWh3uKTKeIhExjGKK1dJ2awL0+zQ==
it shows only
FVjGcz4qQztqAk0jaomJiAFBZ
i tried to change in the
$config['permitted_uri_chars'] = 'a-zA-Z 0-9~%.:_\-#=+';
by / in the permitted uri chars
but it throwing error
So, i need to encryption the $id in the url using the codeigniter encrypt class and decrypt in the server side to get the actual $id, So that i fetch data from the DB. any help would be appreciated
You have to extend encryption class and avoid the / to get it working. Place this class in your application/libraries folder. and name it as MY_Encrypt.php.
class MY_Encrypt extends CI_Encrypt
{
/**
* Encodes a string.
*
* #param string $string The string to encrypt.
* #param string $key[optional] The key to encrypt with.
* #param bool $url_safe[optional] Specifies whether or not the
* returned string should be url-safe.
* #return string
*/
function encode($string, $key="", $url_safe=TRUE)
{
$ret = parent::encode($string, $key);
if ($url_safe)
{
$ret = strtr(
$ret,
array(
'+' => '.',
'=' => '-',
'/' => '~'
)
);
}
return $ret;
}
/**
* Decodes the given string.
*
* #access public
* #param string $string The encrypted string to decrypt.
* #param string $key[optional] The key to use for decryption.
* #return string
*/
function decode($string, $key="")
{
$string = strtr(
$string,
array(
'.' => '+',
'-' => '=',
'~' => '/'
)
);
return parent::decode($string, $key);
}
}
FVjGcz4qQztqAk0jaomJiAFBZ/vKVSBug1iGPQeKQCZ/K7+WUE4E/M9u1EjWh3uKTKeIhExjGKK1dJ2awL0+zQ==
Shows
FVjGcz4qQztqAk0jaomJiAFBZ
If you look at your url closely, you could see that after the result which has been shown there is a '/' . Now any string after that will be treated as another segment. Hence it could not decode.
The encrypt library in this case would not work.
Either you stop passing that through the URL or use another different technique base_encode().
Hope that helps
This is happening as the character "/" is part of html uri delimiter. Instead you can work around it by avoiding that character in html url by rawurlencoding your encrytion output string before attaching it to url.
\edit:
I tried rawurlencode, but wasn't able to get the proper output.
Finally succeeded by using this code.
Define two functions:
function hex2str( $hex ) {
return pack('H*', $hex);
}
function str2hex( $str ) {
return array_shift( unpack('H*', $str) );
}
Then use call str2hex and pass it the encrypted user id to convert encrypted string into hexcode.
Reverse the process to get the correct string so that you can decrypt it.
I was able to properly encode and decode:
"FVjGcz4qQztqAk0jaomJiAFBZ/vKVSBug1iGPQeKQCZ/K7+WUE4E/M9u1EjWh3uKTKeIhExjGKK1dJ2awL0+zQ=="
to:
"46566a47637a3471517a7471416b306a616f6d4a694146425a2f764b56534275673169475051654b51435a2f4b372b57554534452f4d397531456a576833754b544b65496845786a474b4b31644a3261774c302b7a513d3d"
The url would become rather long though.

openssl_encrypt() randomly fails - IV passed is only ${x} bytes long, cipher expects an IV of precisely 16 bytes

This is the code I use to encrypt/decrypt the data:
// Set the method
$method = 'AES-128-CBC';
// Set the encryption key
$encryption_key = 'myencryptionkey';
// Generet a random initialisation vector
$iv = openssl_random_pseudo_bytes(openssl_cipher_iv_length($method));
// Define the date to be encrypted
$data = "Encrypt me, please!";
var_dump("Before encryption: $data");
// Encrypt the data
$encrypted = openssl_encrypt($data, $method, $encryption_key, 0, $iv);
var_dump("Encrypted: ${encrypted}");
// Append the vector at the end of the encrypted string
$encrypted = $encrypted . ':' . $iv;
// Explode the string using the `:` separator.
$parts = explode(':', $encrypted);
// Decrypt the data
$decrypted = openssl_decrypt($parts[0], $method, $encryption_key, 0, $parts[1]);
var_dump("Decrypted: ${decrypted}");
It ususaly works fine, but sometimes (1 in 10 or even less often) it fails. When it fails than the text is only partially encrypted:
This is the error message when it happens:
Warning: openssl_decrypt(): IV passed is only 10 bytes long, cipher expects an IV of precisely 16 bytes, padding with \0
And when it happens the encrypted text looks like:
Encrypt me���L�se!
I thought that it might be caused by a bug in PHP, but I've tested on different hosts: PHP 7.0.6 and PHP 5.6. I've also tried multiple online PHP parsers like phpfidle.org or 3v4l.org.
It seems that openssl_random_pseudo_bytes not always returns a string of a proper length, but I have no idea why.
Here's the sample: https://3v4l.org/RZV8d
Keep on refreshing the page, you'll get the error at some point.
When you generate a random IV, you get raw binary. There's a nonzero chance that the binary strings will contain a : or \0 character, which you're using to separate the IV from the ciphertext. Doing so makes explode() give you a shorter string. Demo: https://3v4l.org/3ObfJ
The trivial solution would be to add base64 encoding/decoding to this process.
That said, please don't roll your own crypto. In particular, unauthenticated encryption is dangerous and there are already secure libraries that solve this problem.
Instead of writing your own, consider just using defuse/php-encryption. This is the safe choice.
Here's the solution
I've updated the code from the first post and wrapped it in a class. This is fixed code based on the solution provided by Scott Arciszewski.
class Encryptor
{
/**
* Holds the Encryptor instance
* #var Encryptor
*/
private static $instance;
/**
* #var string
*/
private $method;
/**
* #var string
*/
private $key;
/**
* #var string
*/
private $separator;
/**
* Encryptor constructor.
*/
private function __construct()
{
$app = App::getInstance();
$this->method = $app->getConfig('encryption_method');
$this->key = $app->getConfig('encryption_key');
$this->separator = ':';
}
private function __clone()
{
}
/**
* Returns an instance of the Encryptor class or creates the new instance if the instance is not created yet.
* #return Encryptor
*/
public static function getInstance()
{
if (self::$instance === null) {
self::$instance = new Encryptor();
}
return self::$instance;
}
/**
* Generates the initialization vector
* #return string
*/
private function getIv()
{
return openssl_random_pseudo_bytes(openssl_cipher_iv_length($this->method));
}
/**
* #param string $data
* #return string
*/
public function encrypt($data)
{
$iv = $this->getIv();
return base64_encode(openssl_encrypt($data, $this->method, $this->key, 0, $iv) . $this->separator . base64_encode($iv));
}
/**
* #param string $dataAndVector
* #return string
*/
public function decrypt($dataAndVector)
{
$parts = explode($this->separator, base64_decode($dataAndVector));
// $parts[0] = encrypted data
// $parts[1] = initialization vector
return openssl_decrypt($parts[0], $this->method, $this->key, 0, base64_decode($parts[1]));
}
}
Usage
$encryptor = Encryptor::getInstance();
$encryptedData = $encryptor->encrypt('Encrypt me please!');
var_dump($encryptedData);
$decryptedData = $encryptor->decrypt($encryptedData);
var_dump($decryptedData);
This happened to me also. I got error like
openssl_decrypt(): IV passed is only 14 bytes long, cipher expects an IV of precisely 16 bytes, padding with \0
I was using lowercase method like openssl_cipher_iv_length('aes-128-cbc')
Lowercase aes-- method gives a result of length which varies between 12 to 16. Ref: https://www.php.net/manual/en/function.openssl-cipher-iv-length.php
Making the method to uppercase openssl_cipher_iv_length('AES-128-CBC') will give consistent value which is 16.
So while encrypting & decrypting the IV length stays the same as 16.

How to check if a domain is punycode or not?

Well, I use the idna_convert PHP class (http://idnaconv.net/index.html) in order to encode / decode domain names.
Unfortunately, it doesn't seem to provide an interface to check whether a domain name is already punycode or not.
What's the best way to achieve this? It would be nice if someone could post source code how to verify a domain is punycode or not (with explanation, because the idna_convert code is not really clear to me). I already know how to catch the exception from idna_convert. :-)
Btw.: idna_convert throws an exception when you try to convert a domain name to punycode that is already punycode (see https://github.com/phlylabs/idna-convert/blob/master/src/Punycode.php; line 157). Moreover, I do not really understand how their check works.
The simplest way - just convert it anyway and check if the result is equal to input.
EDIT: You can extend Punycode class with a check like this:
class PunycodeCheck extends Punycode
{
public function check_encoded($decoded)
{
$extract = self::byteLength(self::punycodePrefix);
$check_pref = $this->UnicodeTranscoder->utf8_ucs4array(self::punycodePrefix);
$check_deco = array_slice($decoded, 0, $extract);
if ($check_pref == $check_deco)
return true;
return false;
}
}
It depends on what exactly you want.
As first basic check, see if the domain name contains only ASCII characters. If yes, then the domain is "already punycode", in the sense that it can't be further transformed. For checking whether a string only contains ASCII characters, see Determine if UTF-8 text is all ASCII?.
If on top of that, you want to check wether the domain is in the IDN form, split the domain at the dots . and check if any of the substrings starts with xn--.
If in addition to that, you want to check if the domain is IDN and is valid, just attempt to decode it with the library's decode function.
It is not very easy to check if a domain is in Punycode or not. Several checks in needed to implement by rules that are already said by #Wladston.
This is the adapted code examples that I took from ValidateHelper class from the composition of my library: Helper classes for PrestaShop CMS. I have also added the test and the result of its execution.
/**
* Validate helper.
*
* #author Maksim T. <zapalm#yandex.com>
*/
class ValidateHelper
{
/**
* Checks if the given domain is in Punycode.
*
* #param string $domain The domain to check.
*
* #return bool Whether the domain is in Punycode.
*
* #see https://developer.mozilla.org/en-US/docs/Mozilla/Internationalized_domain_names_support_in_Mozilla#ASCII-compatible_encoding_.28ACE.29
*
* #author Maksim T. <zapalm#yandex.com>
*/
public static function isPunycodeDomain($domain)
{
$hasPunycode = false;
foreach (explode('.', $domain) as $part) {
if (false === static::isAscii($part)) {
return false;
}
if (static::isPunycode($part)) {
$hasPunycode = true;
}
}
return $hasPunycode;
}
/**
* Checks if the given value is in ASCII character encoding.
*
* #param string $value The value to check.
*
* #return bool Whether the value is in ASCII character encoding.
*
* #see https://en.wikipedia.org/wiki/ASCII
*
* #author Maksim T. <zapalm#yandex.com>
*/
public static function isAscii($value)
{
return ('ASCII' === mb_detect_encoding($value, 'ASCII', true));
}
/**
* Checks if the given value is in Punycode.
*
* #param string $value The value to check.
*
* #return bool Whether the value is in Punycode.
*
* #throws \LogicException If the string is not encoded by UTF-8.
*
* #see https://en.wikipedia.org/wiki/Punycode
*
* #author Maksim T. <zapalm#yandex.com>
*/
public static function isPunycode($value)
{
if (false === static::isAscii($value)) {
return false;
}
if ('UTF-8' !== mb_detect_encoding($value, 'UTF-8', true)) {
throw new \LogicException('The string should be encoded by UTF-8 to do the right check.');
}
return (0 === mb_stripos($value, 'xn--', 0, 'UTF-8'));
}
}
/**
* Test Punycode domain validator.
*
* #author Maksim T. <zapalm#yandex.com>
*/
class Test
{
/**
* Run the test.
*
* #author Maksim T. <zapalm#yandex.com>
*/
public static function run()
{
$domains = [
// White list
'почта#престашоп.рф' => false, // Russian, Unicode
'modulez.ru' => false, // English, ASCII
'xn--80aj2abdcii9c.xn--p1ai' => true, // Russian, ASCII
'xn--80a1acn3a.xn--j1amh' => true, // Ukrainian, ASCII
'xn--srensen-90a.example.com' => true, // German, ASCII
'xn--mxahbxey0c.xn--xxaf0a' => true, // Greek, ASCII
'xn--fsqu00a.xn--4rr70v' => true, // Chinese, ASCII
// Black List
'xn--престашоп.xn--рф' => false, // Russian, Unicode
'xn--prestashop.рф' => false, // Russian, Unicode
];
foreach ($domains as $domain => $isPunycode) {
echo 'TEST: ' . $domain . (ValidateHelper::isPunycodeDomain($domain)
? ' is in Punycode [' . ($isPunycode ? 'OK' : 'FAIL') . ']'
: ' is NOT in Punycode [' . (false === $isPunycode ? 'OK' : 'FAIL') . ']'
) . PHP_EOL;
}
}
}
Test::run();
// The output result:
//
// TEST: почта#престашоп.рф is NOT in Punycode [OK]
// TEST: modulez.ru is NOT in Punycode [OK]
// TEST: xn--80aj2abdcii9c.xn--p1ai is in Punycode [OK]
// TEST: xn--80a1acn3a.xn--j1amh is in Punycode [OK]
// TEST: xn--srensen-90a.example.com is in Punycode [OK]
// TEST: xn--mxahbxey0c.xn--xxaf0a is in Punycode [OK]
// TEST: xn--fsqu00a.xn--4rr70v is in Punycode [OK]
// TEST: xn--престашоп.xn--рф is NOT in Punycode [OK]
// TEST: xn--prestashop.рф is NOT in Punycode [OK]
The only exception that the encode() method throws is when the domain is already punycode. So you can do the following:
try {
$punycode->encode($decoded);
} catch (\InvalidArgumentException $e) {
//do whatever is needed when already punycode
//or do nothing
}
However it's a workaround solution.

Advice for implementing simple regex (for bbcode/geshi parsing)

I had made a personal note software in PHP so I can store and organize my notes and wished for a nice simple format to write them in.
I had done it in Markdown but found it was a little confusing and there was no simple syntax highlighting, so I did bbcode before and wished to implement that.
Now for GeSHi which I really wish to implement (the syntax highlighter), it requires the most simple code like this:
$geshi = new GeSHi($sourcecode, $language);
$geshi->parse_code();
Now this is the easy part , but what I wish to do is allow my bbcode to call it.
My current regular expression to match a made up [syntax=cpp][/syntax] bbcode is the following:
preg_replace('#\[syntax=(.*?)\](.*?)\[/syntax\]#si' , 'geshi(\\2,\\1)????', text);
You will notice I capture the language and the content, how on earth would I connect it to the GeSHi code?
preg_replace seems to just be able to replace it with a string not an 'expression', I am not sure how to use those two lines of code for GeSHi up there with the captured data..
I really am excited about this project and wish to overcome this.
I wrote this class a while back, the reason for the class was to allow easy customization / parsing. Maybe a little overkill, but works well and I needed it overkill for my application. The usage is pretty simple:
$geshiH = new Geshi_Helper();
$text = $geshiH->geshi($text); // this assumes that the text should be parsed (ie inline syntaxes)
---- OR ----
$geshiH = new Geshi_Helper();
$text = $geshiH->geshi($text, $lang); // assumes that you have the language, good for a snippets deal
I had to do some chopping from other custom items I had, but pending no syntax errors from the chopping it should work. Feel free to use it.
<?php
require_once 'Geshi/geshi.php';
class Geshi_Helper
{
/**
* #var array Array of matches from the code block.
*/
private $_codeMatches = array();
private $_token = "";
private $_count = 1;
public function __construct()
{
/* Generate a unique hash token for replacement) */
$this->_token = md5(time() . rand(9999,9999999));
}
/**
* Performs syntax highlights using geshi library to the content.
*
* #param string $content - The context to parse
* #return string Syntax Highlighted content
*/
public function geshi($content, $lang=null)
{
if (!is_null($lang)) {
/* Given the returned results 0 is not set, adding the "" should make this compatible */
$content = $this->_highlightSyntax(array("", strtolower($lang), $content));
}else {
/* Need to replace this prior to the code replace for nobbc */
$content = preg_replace('~\[nobbc\](.+?)\[/nobbc\]~ie', '\'[nobbc]\' . strtr(\'$1\', array(\'[\' => \'[\', \']\' => \']\', \':\' => \':\', \'#\' => \'#\')) . \'[/nobbc]\'', $content);
/* For multiple content we have to handle the br's, hence the replacement filters */
$content = $this->_preFilter($content);
/* Reverse the nobbc markup */
$content = preg_replace('~\[nobbc\](.+?)\[/nobbc\]~ie', 'strtr(\'$1\', array(\'&#91;\' => \'[\', \'&#93;\' => \']\', \'&#58;\' => \':\', \'&#64;\' => \'#\'))', $content);
$content = $this->_postFilter($content);
}
return $content;
}
/**
* Performs syntax highlights using geshi library to the content.
* If it is unknown the number of blocks, use highlightContent
* instead.
*
* #param string $content - The code block to parse
* #param string $language - The language to highlight with
* #return string Syntax Highlighted content
* #todo Add any extra / customization styling here.
*/
private function _highlightSyntax($contentArray)
{
$codeCount = $contentArray[1];
/* If the count is 2 we are working with the filter */
if (count($contentArray) == 2) {
$contentArray = $this->_codeMatches[$contentArray[1]];
}
/* for default [syntax] */
if ($contentArray[1] == "")
$contentArray[1] = "php";
/* Grab the language */
$language = (isset($contentArray[1]))?$contentArray[1]:'text';
/* Remove leading spaces to avoid problems */
$content = ltrim($contentArray[2]);
/* Parse the code to be highlighted */
$geshi = new GeSHi($content, strtolower($language));
return $geshi->parse_code();
}
/**
* Substitute the code blocks for formatting to be done without
* messing up the code.
*
* #param array $match - Referenced array of items to substitute
* #return string Substituted content
*/
private function _substitute(&$match)
{
$index = sprintf("%02d", $this->_count++);
$this->_codeMatches[$index] = $match;
return "----" . $this->_token . $index . "----";
}
/**
* Removes the code from the rest of the content to apply other filters.
*
* #param string $content - The content to filter out the code lines
* #return string Content with code removed.
*/
private function _preFilter($content)
{
return preg_replace_callback("#\s*\[syntax=(.*?)\](.*?)\[/syntax\]\s*#siU", array($this, "_substitute"), $content);
}
/**
* Replaces the code after the filters have been ran.
*
* #param string $content - The content to replace the code lines
* #return string Content with code re-applied.
*/
private function _postFilter($content)
{
/* using dashes to prevent the old filtered tag being escaped */
return preg_replace_callback("/----\s*" . $this->_token . "(\d{2})\s*----/si", array($this, "_highlightSyntax"), $content);
}
}
?>
It looks to me like you already got the regex right. Your problem lies in the invocation, so I suggest making a wrapper function:
function geshi($src, $l) {
$geshi = new GeSHi($sourcecode, $language);
$geshi->parse_code();
return $geshi->how_do_I_get_the_results();
}
Now this would normally suffice, but the source code is likely to contain single or dobule quotes itself. Therefore you cannot write preg_replace(".../e", "geshi('$2','$1')", ...) as you would need. (Note that '$1' and '$2' need quotes because preg_replace just substitutes the $1,$2 placeholders, but this needs to be valid php inline code).
That's why you need to use preg_replace_callback to avoid escaping issues in the /e exec replacement code.
So for example:
preg_replace_callback('#\[syntax=(.*?)\](.*?)\[/syntax\]#si' , 'geshi_replace', $text);
And I'd make a second wrapper, but you can combine it with the original code:
function geshi_replace($uu) {
return geshi($uu[2], $uu[1]);
}
Use preg_match:
$match = preg_match('#\[syntax=(.*?)\](.*?)\[/syntax\]#si', $text);
$geshi = new GeSHi($match[2], $match[1]);

Categories