Un entier en guise de nom de propriété d'une instance

Marqué :

Dans l'usage courant de PHP, il est impossible de créer une propriété dont le nom est un entier.

Par exemple :

<?php
$instance = new stdClass();
$instance->12 = 'ma clé est un entier';
print $instance->12;

Doit lever une exception du type :

Parse error: syntax error, unexpected T_LNUMBER, \
    expecting T_STRING or T_VARIABLE or '{' or '$' [...]

Cependant, il est possible de contourner cette limitation en déclarant le nom de la variable avec d'autres manières.

La première, en encapsulant l'entier entre chevrons:

<?php
$instance = new stdClass();
$instance->{12} = 'ma clé est un entier';
print $instance->{12};

die("\n");

La deuxième en passant l'entier par une variable:

<?php
$property_name = 12;

$instance = new stdClass();
$instance->$property_name = 'ma clé est un entier';
print $instance->$property_name;

J'ai testé sur PHP 5.2.6 (cli) (built: Sep 19 2008 11:28:54)

HTTP Basic and Digest authentication with PHP

HTTP authentication is quite popular for web applications. It is pretty easy to implement and works for a range of http applications; not to mention your browser.

Basic Auth

The two main authentication schemes are 'basic' and 'digest'. Basic is pretty easy to implement and appears to be the most common:

<?php

$username = null; 
$password = null;

// mod_php 
if (isset($_SERVER['PHP_AUTH_USER'])) { 
    $username = $_SERVER['PHP_AUTH_USER']; 
    $password = $_SERVER['PHP_AUTH_PW'];

// most other servers 
} elseif (isset($_SERVER['HTTP_AUTHENTICATION'])) {

        if (strpos(strtolower($_SERVER['HTTP_AUTHENTICATION']),'basic')===0)  
          list($username,$password) = explode(':',base64_decode(substr($_SERVER['HTTP_AUTHORIZATION'], 6)));

}

if (is_null($username)) {

    header('WWW-Authenticate: Basic realm="My Realm"'); 
    header('HTTP/1.0 401 Unauthorized'); 
    echo 'Text to send if user hits Cancel button';

    die();

} else { 
    echo "<p>Hello {$username}.</p>"; 
    echo "<p>You entered {$password} as your password.</p>"; 
}

?>

Well it's a bit difficult I suppose, but you might have noticed the username and password are sent over the wire using base64 encoding. Not really secure, unless you have SSL in place.

Digest

Digest is designed to be more secure. The password is never sent over the wire in plain text, but rather as a hash. The implications of the usage of a hash is that it can never be decrypted. We can only validate the hash by applying the same hash function to the password we have. If the hashes match, the password was correct.

Lets first see how Digest auth should work:

Client requests url

GET / HTTP/1.1

Server requires authentication

HTTP/1.1 401 Unauthorized 
WWW-Authenticate: Digest realm="The batcave", 
  qop="auth", 
  nonce="4993927ba6279", 
  opaque="d8ea7aa61a1693024c4cc3a516f49b3c"

Client authenticates

GET / HTTP/1.1 Authorization: Digest username="admin",
realm="The batcave",
nonce=49938e61ccaa4,
uri="/",
response="98ccab4542f284c00a79b5957baaff23",
opaque="d8ea7aa61a1693024c4cc3a516f49b3c",
qop=auth, nc=00000001,
cnonce="8d1b34edb475994b"

Information coming from the server:

|realm |A string which will be used within the UI and as part of the hash. |qop |Can be auth and auth-int and has influence on how the hash is created. We use auth. |nonce |A unique code, which will be used within the hash and needs to be sent back by the client. |opaque |This can be treated as a session id. If this changes the browser will deauthenticate the user.

Information from the client:

|username |The supplied username |realm |Same as server response. |nonce |Same as server response. |uri |The authentication uri |response |The validation hash. |opaque |Same as server response. |qop |Same as server response. |nc |Nonce-count. This a hexadecimal serial number for the request. The client should increase this number by one for every request. |cnonce A unique id generated by the client

So how do we know if the password was correct? We van validate using the following formula (pseudo code).

A1 = md5(username:realm:password) 
A2 = md5(request-method:uri) // request method = GET, POST, etc. 
Hash = md5(A1:nonce:nc:cnonce:qop:A2)

if (Hash == response) 
  //success! 
else  
  //failure!

Or, using PHP:

<?php

$realm = 'The batcave';

// Just a random id 
$nonce = uniqid();

// Get the digest from the http header 
$digest = getDigest();

// If there was no digest, show login 
if (is_null($digest)) requireLogin($realm,$nonce);

$digestParts = digestParse($digest);

$validUser = 'admin'; 
$validPass = '1234';

// Based on all the info we gathered we can figure out what the response should be  
$A1 = md5("{$digestParts['username']}:{$realm}:{$validPass}"); 
$A2 = md5("{$_SERVER['REQUEST_METHOD']}:{$digestParts['uri']}");

$validResponse = md5("{$A1}:{$digestParts['nonce']}:{$digestParts['nc']}:{$digestParts['cnonce']}:{$digestParts['qop']}:{$A2}");

if ($digestParts['response']!=$validResponse) requireLogin($realm,$nonce);

// We're in! 
echo 'Well done sir, you made it all the way through the login!';

// This function returns the digest string 
function getDigest() {

    // mod_php 
    if (isset($_SERVER['PHP_AUTH_DIGEST'])) { 
        $digest = $_SERVER['PHP_AUTH_DIGEST']; 
    // most other servers 
    } elseif (isset($_SERVER['HTTP_AUTHENTICATION'])) {

            if (strpos(strtolower($_SERVER['HTTP_AUTHENTICATION']),'digest')===0)  
              $digest = substr($_SERVER['HTTP_AUTHORIZATION'], 7); 
    }

    return $digest;

}

// This function forces a login prompt 
function requireLogin($realm,$nonce) { 
    header('WWW-Authenticate: Digest realm="' . $realm . '",qop="auth",nonce="' . $nonce . '",opaque="' . md5($realm) . '"'); 
    header('HTTP/1.0 401 Unauthorized'); 
    echo 'Text to send if user hits Cancel button'; 
    die(); 
}

// This function extracts the separate values from the digest string 
function digestParse($digest) { 
    // protect against missing data 
    $needed_parts = array('nonce'=>1, 'nc'=>1, 'cnonce'=>1, 'qop'=>1, 'username'=>1, 'uri'=>1, 'response'=>1); 
    $data = array();

    preg_match_all('@(\w+)=(?:(?:")([^"]+)"|([^\s,$]+))@', $digest, $matches, PREG_SET_ORDER);

    foreach ($matches as $m) { 
        $data[$m[1]] = $m[2] ? $m[2] : $m[3]; 
        unset($needed_parts[$m[1]]); 
    }

    return $needed_parts ? false : $data; 
}

?>

As you can see we need to have a plain-text version of the password in order to validate the user. It's not a good idea to store the plain-text password, therefore it's strongly recommended to store the result of $A1 instead.

Security improvements

  • It's smart to validate the contents of opaque, nonce and realm. If you have the data stored on the server, why not check it.
  • The nc should be an ever increasing number. You could store the number and track to make sure it doesn't make any big jumps. It's not wanted to be extremely strict about the sequence, because you might miss a number, and requests could come in be out of order.
  • 'qop' is quality of protection. This serves as an integrity code for the request. A hacker could steal all your HTTP Digest headers and simply change the body to make it do something else. If 'qop' is set to 'auth', only the requested uri will be taken into consideration. If 'qop' is 'auth-int' the body of the request will also be used in the hash. (A2 = md5(request-method:uri:md5(request-body))).

References:

Structure idéale de PHP sur Leopard

Marqué :

Installer PEAR

cf http://api-meal.eu/memo/146-install-pear-on-osx-leopard/

php.ini

Vérifier la présence du php.ini. Pour le créer, faire

sudo cp /etc/php.ini.default /etc/php.ini

remplacer l'option extensions dir

extension_dir = "./"

par

extension_dir = "/usr/lib/php/extensions/no-debug-non-zts-20060613/"

remplacer la directive include_path

;include_path = ".:/php/includes"

par

include_path = ".:/usr/share/pear:/usr/lib/php/pear"

Install PEAR on OSX Leopard

Marqué :

Unlike previous version of OS X, Leopard doesn’t come with PHP’s PEAR repository installed by default. Luckily, installing is quick and painless. From a command line:

curl http://pear.php.net/go-pear > go-pear.php
sudo php -q go-pear.php

Just press enter to select all the default choices except for the installation directory. For that, use /usr/local. (Thanks, Steve.)

Next we need to modify our php.ini file to include the new PEAR files:

sudo cp /etc/php.ini.default /etc/php.ini

Edit /etc/php.ini and change

;include_path = ".:/php/includes"

to read

include_path = ".:/usr/share/pear"

Restart Apache and you’re done!

Install PHPUnit

Marqué :

With PEAR

PHPUnit should be installed using the PEAR Installer. This installer is the backbone of PEAR, which provides a distribution system for PHP packages, and is shipped with every release of PHP since version 4.3.0.

The PEAR channel (pear.phpunit.de) that is used to distribute PHPUnit needs to be registered with the local PEAR environment:

pear channel-discover pear.phpunit.de

This has to be done only once. Now the PEAR Installer can be used to install packages from the PHPUnit channel:

pear install phpunit/PHPUnit

After the installation you can find the PHPUnit source files inside your local PEAR directory; the path is usually /usr/lib/php/PHPUnit.

Manually

Although using the PEAR Installer is the only supported way to install PHPUnit, you can install PHPUnit manually. For manual installation, do the following:

  1. Download a release archive from http://pear.phpunit.de/get/ and extract it to a directory that is listed in the include_path of your php.ini configuration file.

  2. Prepare the phpunit script:

    Rename the phpunit.php script to phpunit.

    Replace the @php_bin@ string in it with the path to your PHP command-line interpreter (usually /usr/bin/php).

    Copy it to a directory that is in your path and make it executable (chmod +x phpunit).

  3. Prepare the PHPUnit/Util/Fileloader.php script:

    Replace the @php_bin@ string in it with the path to your PHP command-line interpreter (usually /usr/bin/php).

Faceted search with Sphinx and PHP

Marqué :
<?php
/*
 * Faceted implementation in Sphinx
 * 
 * the 10 best match will be stored into 
 * $result_list = array()
 * 
 * the 100 values fore each filters will be stored into
 * $bundle_filters_results = array()
 */

// search exemple
$query_search = "your query";
$attrs_search = array("one"=>10, "three"=>2);

// parameters
$config_index = "your_index";
$config_filters = array('one', 'two', 'three');

//
// 1. get the 10 best matchs documents
//

// Prepare the query
$sphinx_instance->SetLimits( 0, 10, 10 );
$sphinx_instance->SetRankingMode( SPH_RANK_PROXIMITY_BM25 );

foreach($attrs_search as $key => $value) {
    $sphinx_instance->SetFilter($key, $value);
}

// Fetch the results
$result_list = $sphinx_instance->Query($query, $config_index);

//
// 2. get 100 values for each filters tied to the current search
//

// Prepare the queries
$sphinx_instance->SetLimits( 0, 100, 100 );
$sphinx_instance->SetRankingMode ( SPH_RANK_NONE );

for($i=0; $i<count($config_filters); $i++) {
    $current_filter = $config_filters[$i];

    $sphinx_instance->ResetFilters();
    $sphinx_instance->ResetGroupBy();
    $sphinx_instance->ResetQueryString();

    // query every others filters
    foreach($attrs_search as $key => $value) {
        if( $key != $current_filter) {
            $sphinx_instance->SetFilter( $key, $value );
        }

    }
    $sphinx_instance->SetGroupBy($current_filter, SPH_GROUPBY_ATTR, "@count desc");
    $sphinx_instance->AddQuery( $query_search, $config_index );
}

// Fetch all results
$sphinx_instance->SetArrayResult(true);
$tmp_filters_results = $sphinx_instance->RunQueries();

$bundle_filters_results = array();
for($i=0; $i<count($config_filters); $i++) {
    $current_filter = $config_filters[$i];

    $bundle_filters_results[$current_filter] = $tmp_filters_results[0];
}
?>