- From: CPK Smithies <c.1@smithies.org>
- Date: Sat, 14 Feb 2009 06:15:08 -0000
- To: <www-amaya@w3.org>
The following may help those who are wishing to use the HTTP PUT method on an Apache-based server with PHP (v. 5) available. This scenario supports access to a single server directory with a fixed password-list. Adaptation to use a proper user/password database is left as an exercise for the reader. In this e-mail, sample file contents will appear between double-bracketed statements thus: ((sample file begins)) ((sample file ends)) First, a utility file to support HTTP digest authentication, which can be located in the target directory (or a common PHP include directory if you can set one up): ((dauth.php begins)) <?php class auth_digest { private $data; function __construct($txt) { // protect against missing data $needed_parts = array('nonce'=>1, 'nc'=>1, 'cnonce'=>1, 'qop'=>1, 'username'=>1, 'uri'=>1, 'response'=>1); $this->data = array(); $txt = explode(',', $txt); foreach ($txt as $param) { list($k, $v) = explode("=", $param, 2); $this->data[$k] = trim($v, "\"'"); unset($needed_parts[$k]); } if ($needed_parts) throw new InvalidArgumentException($txt, 1); } function __get($k) { if (!isset($this->data[$k])) throw new InvalidArgumentException("Digest does not contain '$k'", 2); return $this->data[$k]; } function valid_response($A1) { $A2 = md5($_SERVER['REQUEST_METHOD'] . ':' . $this->data['uri']); $valid_response = md5( $A1 . ':' . $this->data['nonce'] . ':' . $this->data['nc'] . ':' . $this->data['cnonce'] . ':' . $this->data['qop'] . ':' . $A2 ); return $this->data['response'] == $valid_response; } } class authorizer { private $realm; private $users; // arrayIterator over array ('username' => 'password') private $plainpass; // TRUE if $users stores passwords in plain text function __construct($auth_realm, ArrayIterator $userIterator, $plainpw = TRUE) { $this->realm = $auth_realm; $this->users = $userIterator; $this->plainpass = $plainpw; } static private function parse_digest($txt) { // parse the http auth header // protect against missing data $needed_parts = array('nonce'=>1, 'nc'=>1, 'cnonce'=>1, 'qop'=>1, 'username'=>1, 'uri'=>1, 'response'=>1); $data = array(); $txt = explode(',', $txt); foreach ($txt as $param) { list($k, $v) = explode("=", $param, 2); $data[$k] = trim($v, "\"'"); unset($needed_parts[$k]); } return $needed_parts ? FALSE : $data; } private static function gen_nonce() { return date('Ymd') . uniqid(); } private static function nonceok($nonce) { return date('Ymd') == substr($nonce, 0, 8); } private static function unauth($status_text, $body) { header("HTTP/1.1 403 $status_text"); die($body); } private function getlogin() { // dies header('HTTP/1.1 401 Unauthorized'); header('WWW-Authenticate: Digest realm="' . $this->realm . '",qop="auth",nonce="' . self::gen_nonce() . '",opaque="' . md5($this->realm) . '"'); // display if user hits Cancel die('OK, you obviously know when you are beaten.'); } function check() { if (empty($_SERVER['PHP_AUTH_DIGEST'])) $this->getlogin(); // analyze the PHP_AUTH_DIGEST variable try { $d = new auth_digest($digest_text = $_SERVER['PHP_AUTH_DIGEST']); } catch (InvalidArgumentException $e) { self::unauth('Bad Credentials', "Parse error: $digest_text"); } if (!isset($this->users[$uname = $d->username])) self::unauth('Unauthorized user', "Wrong credentials: user '$uname' unknown."); // check valid response $A1 = $this->users[$uname]; if ($this->plainpass) $A1 = md5("$uname:$this->realm:$A1"); if (!$d->valid_response($A1)) self::unauth('Invalid Response', "Wrong credentials: digest=$digest_text"); if (!self::nonceok($d->nonce)) $this->getlogin(); return $uname; } } ((dauth.php ends)) Second, in the directory concerned, place the following PHP script: ((put.php begins)) <?php function write_log($txt) { file_put_contents("put_log.txt", $txt); chmod("put_log.txt", 0666); } require 'dauth.php'; // customize the following defines:- define('AUTH_REALM', 'myserver/mydirectory'); define('URL_BASE', 'http://myserver/mydirectory'); /* --- The following sample authorization function uses an array of user names and passwords defined and fixed here. An actual implementation might read the array from an external resource. The use of an ArrayIterator makes it moderately easy to extend this mechanism. --- */ function authorize() { $auth = new authorizer(AUTH_REALM, new ArrayIterator(array('user1' => 'password1', 'user2' => 'password2'))); $auth->check(); // dies if not OK } function puterror($status, $body, $log = FALSE) { header ("HTTP/1.1 $status"); if ($log) write_log($log); die("<html><head><title>Error $status</title></head><body>$body</body></html>"); } function putfile() { $f = pathinfo($fname = $_SERVER['REQUEST_URI']); if ($f['extension'] != 'html') puterror('403 Forbidden', "Bad file type in $fname"); $f = fopen($fname = $f['basename'], 'w'); if (!$f) puterror('409 Create error', "Couldn't create file"); $s = fopen('php://input', 'r'); // read from standard input if (!$s) puterror('404 Input Unavailable', "Couldn't open input"); while($kb = fread($s, 1024)) fwrite($f, $kb, 1024); fclose($f); fclose($s); chmod($fname, 0666); $fname = URL_BASE . $fname; header("Location: $fname"); header("HTTP/1.1 201 Created"); echo "<html><head><title>Success</title></head><body>"; echo "<p>Created <a href='$fname'>$fname</a> OK.</p></body></html>"; } if ($_SERVER['REQUEST_METHOD'] != 'PUT') header("HTTP/1.1 403 Bad Request"); else { authorize(); putfile(); // uncommment the next line to debug misbehaviour //write_log(date('c') . "\n" . $_SERVER['REQUEST_URI'] . "\nStatus: $retcode"; } ((put.php ends)) Third, store the following .htaccess file in the target directory, replacing '/path-to-target-directory' with the domain-relative path (e.g. if a target file can be read at http://mydomain/x/y/z.html, then the /path-to-target-directory would be '/x/y'): ((.htaccess begins)) Options FollowSymLinks RewriteEngine on RewriteBase /path-to-target-directory RewriteCond %{REQUEST_METHOD} !PUT RewriteRule ^/put\.php$ - [F] RewriteCond %{REQUEST_METHOD} PUT RewriteCond %{QUERY_STRING} ^$ RewriteRule ^/put\.php$ - [F] RewriteCond %{REQUEST_METHOD} PUT RewriteRule ^(.*)$ put.php?url=$1 [L] ((.htaccess ends)) I hope this provides some clues. Regards to all, CPKS
Received on Saturday, 14 February 2009 06:22:35 UTC