PHP

From Leo's Notes
Last edited on 14 June 2020, at 23:25.

PHP is a server-side scripting language.

Installation

Refer to the Dockerfiles at https://git.steamr.com/docker/php-fpm if you wish to install PHP-FPM manually.


Interpreter

The PHP interpreter consists of two parts: The PHP Core and the Zend Engine.

The PHP core handles communication with bindings, SAPIs, file and network IO, and handling of the many PHP extensions.

The Zend Engine is responsible for interpreting the PHP code into bytecode. The interpretation process involves splitting the code into tokens using a tokenizer (initiated by PHP's core function zend_compile_file()) and converting these tokens into bytecode. zend_compile_string() can also be used to compiles a string and is used for functions such as eval().

The PHP bytecode is executed by the PHP's virtual machine. The VM has a virtual CPU with its own set of instructions. Execution is initiated by passing an array of opcodes to zend_execute().

Bytecode

The register-based bytecode of PHP consists of opcodes, constants, variables, and meta information. PHP has around 200 different opcodes that cover all existing language constructs. (see https://www.php.net/manual/en/internals2.opcodes.php).

Each opcode conforms to the zend_op data structure and has:

  1. an opcode number (znode_uchar) used to find the corresponding opcode handler in a lookup table.
  2. two operand parameters (value as znode_op, type as znode_uchar)
  3. a result operand to store return values (znode_op, type as znode_uchar)

5 types:

  1. IS_UNUSED: Operand not used
  2. IS_CONST: Constants are literals in code
  3. IS_TMPVAR: Temporary variable, as result of $foo+$bar for example. ~0, where 0 would be the first, 1 the second, an so on.
  4. IS_VAR: Non-simple variables or variables requiring a lookup. $n.
  5. IS_CV: Compiled variable to save PHP from looking up variables in hash table. !n, where n is the offset to the compiled-variable array.

Index to retrieve the handler address in the lookup table is:

See Also:

Encoders

  • ionCube
  • Zend Guard
  • SourceGuardian

ionCube header looks like:

<?php //0xxxx
*load extension / print message code*
binary encoded data using custom Base64

The file is checked using Adler32 and binary contents are encoded using a custom Base64 format.

The ionCube loader hooks zend_compile_file() and tests for <?php // at the beginning of an executed PHP file. The hexadecimal value immediately following this specifies the size of the fallback code which the loader skips. The binary encoded data is then loaded and parsed by the ionCube extension.

Stuff

Error Reporting

To always show all errors and warnings:

error_reporting(E_ALL);
ini_set('display_errors', 1);

In more detail:

// Turn off all error reporting
error_reporting(0);

// Report simple running errors
error_reporting(E_ERROR | E_WARNING | E_PARSE);

// Reporting E_NOTICE can be good too (to report uninitialized
// variables or catch variable name misspellings ...)
error_reporting(E_ERROR | E_WARNING | E_PARSE | E_NOTICE);

// Report all errors except E_NOTICE
// This is the default value set in php.ini
error_reporting(E_ALL ^ E_NOTICE);

// Report all PHP errors (see changelog)
error_reporting(E_ALL);

// Report all PHP errors
error_reporting(-1);

// Same as error_reporting(E_ALL);
ini_set('error_reporting', E_ALL);


UTF-8 to hex

There is basically NO good way.


Troubleshooting

HTTP POST Array Empty

I had an issue where a PHP POST array was empty when a POST request was made. It turns out that PHP now needs the Content-Type header set for it to be populated regardless of the HTTP method being used.

In detail, when the following request was made, $_POST was empty:

POST /print HTTP/1.1
Connection: Keep-Alive
User-Agent: WebSlipPrinting
Content-Length: 56
Host: 172.20.1.151

template_data=Hello There!&Username=test&Password=123456

The data though was still sent since $_SERVER['CONTENT_LENGTH'] was 56 as in the original request. The data was also present in the php://input stream. ie:

var_dump(file_get_contents("php://input"));
# Returned:
# string(56) "template_data=Hello There!&Username=test&Password=123456"

Adding the Content-Type value to the header fixes the issue, with the final request as:

POST /print HTTP/1.1
Connection: Keep-Alive
Content-Type: application/x-www-form-urlencoded
User-Agent: WebSlipPrinting
Content-Length: 56
Host: 172.20.1.151

template_data=Hello There!&Username=test&Password=123456


This may have something to do with the enable_post_data_reading option in PHP. From the config file:

647 ; Whether PHP will read the POST data.
 648 ; This option is enabled by default.
 649 ; Most likely, you won't want to disable this option globally. It causes $_POST
 650 ; and $_FILES to always be empty; the only way you will be able to read the
 651 ; POST data will be through the php://input stream wrapper. This can be useful
 652 ; to proxy requests or to process the POST data in a memory efficient fashion.
 653 ; http://php.net/enable-post-data-reading
 654 ;enable_post_data_reading = Off

PDOException : could not find driver

You need to have the pdo driver installed. In my case, I was trying to use the pdo-mysql connector which wasn't compiled with PHP. To fix this, install the PDO driver or compile it manually with:

--with-pdo-<driver>

In my case, I needed mysql:

--with-pdo-mysql

mail() issues

If mail() returns sh: -t: command not found when attempting to run the mail() function, you probably did not set the sendmail_path variable in your php.ini.

Change:

; sendmail_path =

To:

sendmail_path = /usr/sbin/sendmail -t -i

If you get Recipient names must be specified when calling mail(), you need to set the -t flag in sendmail_path in php.ini.