Jump to content
Larry Ullman's Book Forums

Matt

Members
  • Posts

    173
  • Joined

  • Last visited

  • Days Won

    7

Posts posted by Matt

  1. Thanks for the help Larry!

     

    I had another question. I have been putting my ajax files in the "inc" directory, and since I added the .htaccess file to prevent directory browsing, a side consequence of this is that I can't call the ajax files with javascript any more. What I did was create another directory called "ajax" and put all the php files for handling ajax requests in there. Is this the best way to fix this problem? What do you recommend doing in this situation?

     

    Thanks again,

     

    Matt

  2. Larry,

     

    Sorry to keep bombarding you with posts, but I just wanted to post one more time to show you my code for the contact form. I have taken your code and added a few other checks to make it more secure. You know I am very strict about security (as you are), and I want this form to stop any kind of attack, while at the same time being easy to use. Please take a quick look at it and if you have any suggestions or advice, please let me know.

    <?php
        require('config.inc.php');
        require(SITE_FUNCTIONS);
        
        $model = array(
            'errors' => array()
        );
        
        if ($_SERVER['REQUEST_METHOD'] === 'POST') {
            if (isset($_POST['data'])) {
    
                $data = gpd(); // This function runs json_decode and converts the object to an array
    
                $inputs = $data['inputs'];
    
                function spam_scrubber($value) {
                    
                    // List of very bad values:
                    $very_bad = array('to:', 'cc:', 'bcc:', 'content-type:', 'mime-version:', 'multipart-mixed:', 'content-transfer-encoding:');
                
                    // If any of the very bad strings are in he submitted value, return an empty string:
                    foreach ($very_bad as $v) {
                        if (stripos($value, $v) !== false) {
                            return '';
                        }
                    }
                
                    // Replace any newline characters with spaces:
                    $value = str_replace(array( "\r", "\n", "%0a", "%0d"), ' ', $value);
                
                    // Return the value:
                    return trim($value);
            
                } // End of spam_scrubber() function.
                
                // Clean the form data:
                $scrubbed = array_map('spam_scrubber', $inputs);
                
                if (isset($scrubbed['name']) && isset($scrubbed['email']) && isset($scrubbed['message'])) {
    
                    $scrubbed['name'] = strip_tags($scrubbed['name']);
                    
                    if (!empty($scrubbed['name'])) {
                        /* The name input accepts characters from any language and this makes sure it is 40 characters or less. */
                        if (mb_strlen($scrubbed['name'], 'utf-8') < 41) {
                            $name = $scrubbed['name'];
                        } else {
                            $model['errors']['name'] = array(
                                'id' => 'name',
                                'message' => 'The Name field must not exceed 40 characters in length.'
                            );
                        }
                    } else {
                        $model['errors']['name'] = array(
                            'id' => 'name',
                            'message' => 'The Name field is required.'
                        );
                    }
                    
                    if (!empty($scrubbed['email'])) {
                        if (filter_var($scrubbed['email'], FILTER_VALIDATE_EMAIL)) {
                            $email = $scrubbed['email'];
                        } else {
                            $model['errors']['email'] = array(
                                'id' => 'email',
                                'message' => 'The Email field must contain a valid email address.'
                            );
                        }
                    } else {
                        $model['errors']['email'] = array(
                            'id' => 'email',
                            'message' => 'The email field is required.'
                        );
                    }
                    
                    if (!empty($scrubbed['message'])) {
                        /* The 'message' field accepts characters from any language and this makes sure that it is 250 characters or less. */
                        if (mb_strlen($scrubbed['message'], 'utf-8') < 251) {
                            $message = $scrubbed['message'];
                        } else {
                            $model['errors']['message'] = array(
                                'id' => 'message',
                                'message' => 'The Message ' . mb_strlen($scrubbed['message'], 'utf-8') . ' field must not exceed 250 characters in length.'
                            );
                        }
                    } else {
                        $model['errors']['message'] = array(
                            'id' => 'message',
                            'message' => 'Please enter a message for the Message field.'
                        );
                    }
                    
                    if (!empty($model['errors'])) {
    
                        http_response_code(400);
    
                        $failure = array(
                            'message' => 'There was a problem with your submission. Please complete the form and try again.',
                            'errors' => $model['errors']
                        );
                        
                        echo json_encode($failure);
                        
                        exit;
                    }
                    
                    /* Code to insert into database and send mail */
                    
                    if (/* Mail and insert were successful */) {
                        
                        http_response_code(200);
                        
                        $success = array(
                            'message' => 'Thank you! Your message has been sent.'
                        );
                        
                        echo json_encode($success);
                    } else {
                        
                        html_response_code(500);
                        
                        $failure = array(
                            'message' => 'Something went wrong and we couldn\'t send your message.'
                        );
                        
                        echo json_encode($failure);
                    }
                    
                } else {
                    
                    http_response_code(403);
                    
                    $failure = array(
                        'message' => 'There was a problem with your submission. Please try again.'
                    );
                    
                    echo json_encode($failure);
                }
            } else {
                
                http_response_code(403);
                
                $failure = array(
                    'message' => 'There was a problem with your submission. Please try again.'
                );
                
                echo json_encode($failure);
            }
        } else {
            
            http_response_code(403);
            
            $failure = array(
                'message' => 'There was a problem with your submission. Please try again.'
            );
            
            echo json_encode($failure);
        }
    

    Thanks again Larry for all your help! I really appreciate it!

     

    Matt

  3. Larry,

     

    Thanks for the advice! I implemented your spam_scrubber() function and everything works great!

     

    Also, the http_status_codes are useful in that I can listen for a failure in my ajax callback and react accordingly. I suppose I could also have done this by sending back a boolean and using a conditional in the success handler to decide what to do, but it does allow very precise control over the how to handle different types of errors.

     

    Also, have you heard of ReCAPTCHA? HartleySan told me about it the other day and it looks pretty amazing! I would still validate everything, but it does stop the bots from getting in.

     

    Thanks,

     

    Matt

  4. @Jonathan,

     

    Thanks for the response! I had a question about phpMailer, does it do any sanitizing for the headers to remove spam? I have heard that it is "safer" than using the standard mail() function, so I was wondering what it does besides having built in support for SMTP that makes it better.

     

    Thanks

     

    @Larry,

     

    You are right about IPs getting blacklisted. I was doing a few tests with my GlobeDomain account and tried to send mail to my outside mail accounts. Almost all of them blocked the email completely (gmail did accept it, but put it in the "spam" folder). This is totally unacceptable!

     

    I think that using a 3rd party mail server is probably the only solution to guarantee delivery! I won't be sending out the amount of email you are, but just want to make sure it gets delivered and doesn't cost a fortune.

     

    Also, I keep hearing about Digital Ocean! What is all the excitement about?

     

    Thanks,

     

    Matt

  5. Larry,

     

    Thanks for the response! I did some research on the subject, but I just want to clarify.

     

    1. SMTP is what I should be using for all email on my site.

     

    2. I should outsource all my email handling to a site like Mandrill (both mail sent to users when they register or change their password, as well as mail sent to the admin account on my site when a user completes the contact form).

     

    3). I should definitely use a professional mailer script like Zend/mail or PhpMailer.

     

    Thanks for any help or advice you can give!

     

    @Jonathan,

     

    Thanks for recommending Mandrill. I looked at their site and while their is a lot you can do, it does seem to be what I'm looking for. I am not doing anything complicated, so I don't think I need to use the API, but just want to send simple emails. I found the following script which I think will work fine for my use case:

     

    http://help.mandrill.com/entries/23737696-How-do-I-send-with-PHPMailer-

     

    Is this script good for what I want to do?

     

    Sorry to sound like a NOOB, but I am pretty new to the whole php email thing and I just want to make sure i am doing it right!

     

    Thanks,

     

    Matt

  6. Larry,

     

    Sorry for not giving more information about the error. Basically, when I was testing the ajax script, there were some bugs that were causing it to fail certain conditionals. When that happened it was sending the appropriate error message back as json data as well as the http_response_code. In the javascript console it would display an error like the following:

     

    Error with contact_form.php 403 (forbidden)

     

    Is it normal for this to be displayed in the console upon sending an http_response_code for an error?

     

    As far as the span_scrubber, I was looking at the code for email.php in chapter 11 (I don't have the newest edition of the book, so I was just looking at the downloadable code). You are right that version 13.1 does in fact include the spam scrubber, so my apologies for that.

     

    I notice that you are running all three input fields through it. I know that the only inputs to watch out for are the ones that are added to the mail header (i.e. 'email' or possibly 'name'), so is it necessary to run the 'comment' through it as well? Also, wouldn't running the email variable through FILTER_VALIDATE_EMAIL take care of the carriage returns or another email address being added to it?

     

    Thanks,

     

    Matt

  7. Larry,

     

    I created the .htaccess file you provide on page 70. I put it in the includes directory and it does prevent browsing the directory, however it also am getting an error:

     

    You don't have permission to access /inc on this server.

     

    Additionally, a 404 Not Found error was encountered while trying to use an ErrorDocument to handle the request.

     

    Why is it giving an error? I don't want it to handle the request. I want it to not allow people to browse the directory. Is there anything I can do to stop this?

     

    Thanks,

     

    Matt

  8. Larry,

     

    Thanks for the help and sorry for the late response!

     

    I started working on the contact form and using the http_response_codes. They seem to work fine, but when when the script fails, it displays an error in the console window showing the error and the ajax script which failed. Is this what is supposed to happen? The average user won't see it, but it is still showing an error which bugs me.

     

    Also, I am trying to follow all the modern best practices for implementing the form, as contact forms are a very popular attack vector. It seems like the sanitization people use is all over the place. Some remove new lines from the name field, others use strip_tags. In the PHP6 And MySql5 book, you had a contact form in Chapter 12 which used a spam_scrubber() function. It used a "blacklist" to clean values that might be used to send spam by altering the email's header. In the new book, you took this out. Was there a reason for this?

     

    On top of this, I can't use a regex on the name field as I am allowing all Western characters, as well as Japanese, to be entered. What is the best way to stop spam attempts?

     

    Thanks again,

     

    Matt

  9. Jonathan,

     

    Thanks for responding! Ok, then I will probably go with phpMailer then. Also, I know this might sound like a ridiculous question, but is it ok to use the regular mail() function for a contact form? The reason I ask is that the email will not be sent to a user, and doesn't need any fancy formatting, so I thought it was probably just fine for that. I thought that there is no way security can be breached because I am formatting the mail in php and it will be sent to the admin (myself).

     

    Also, when I was looking into phpMailer, almost every tutorial shows it using SMTP. I started reading about the different mail protocols and it seems like a very deep hole that I don't want to go down right now. The topic is very complicated (when it should actually be quite simple) and I would have to use a SMTP server like Postmark, which charges money after the first 1000 mails sent. Is SMTP worth using? What is wrong with standard Webmail?

     

    Thanks again,

     

    Matt

  10. Larry,

     

    I am looking into using a good php mail library/framework and am thinking about using Zend Mail. However, the instructions you gave for installing it with Composer are great, but only for a local server environment. How do I set this up on a hosted production server?

     

    Also, I have heard good things about PHPMailer. What is your impression of it? I want to send both plain text and HTML embedded emails. I would really like to stay away from setting up complex dependencies on any preinstalled modules on the server. A stand alone class package that I could download and install myself would be preferred.

     

    Thanks,

     

    Matt

  11. Larry,

     

    Thank you for the clear and well explained answer. By sending back the response codes, I am assuming that I can then display very specific error messages that are also styled for that error.

     

    Error checking needs to be done in JavaScript and PHP both. JavaScript is for convenience; PHP for security. 

     

    Sorry Larry, I wrote that last question quickly and didn't word it correctly. I was absolutely going to do error checking with php and just forgot when I was writing the post that the ajax script has to do error checking along with the unobtrusive version of the page. What I wanted to know is if it's better to just do all the error checking in the ajax script and send back the corresponding errors (like the script is doing), or do some error checking in javascript first before even sending off the request? If it's done in javascript, then it will reduce the number of server requests.

  12. Larry,

     

    I know this probably belongs in the Javascript forum as it involves AJAX, but because it focuses on HTTP Status Codes, I just decided to put it here instead!

     

    I was doing a quick search for "Contact Form" scripts because I wanted to see what the new trends were for submitting them and handling errors and I came across a good tutorial from Treehouse. I noticed that they were setting HTTP response code headers before sending the response back to the client.

     

    Here is the AJAX script:

    <?php
    
        // Only process POST reqeusts.
        if ($_SERVER["REQUEST_METHOD"] == "POST") {
            // Get the form fields and remove whitespace.
            $name = strip_tags(trim($_POST["name"]));
    				$name = str_replace(array("\r","\n"),array(" "," "),$name);
            $email = filter_var(trim($_POST["email"]), FILTER_SANITIZE_EMAIL);
            $message = trim($_POST["message"]);
    
            // Check that data was sent to the mailer.
            if ( empty($name) OR empty($message) OR !filter_var($email, FILTER_VALIDATE_EMAIL)) {
                // Set a 400 (bad request) response code and exit.
                http_response_code(400);
                echo "Oops! There was a problem with your submission. Please complete the form and try again.";
                exit;
            }
    
            // Set the recipient email address.
            // FIXME: Update this to your desired email address.
            $recipient = "hello@example.com";
    
            // Set the email subject.
            $subject = "New contact from $name";
    
            // Build the email content.
            $email_content = "Name: $name\n";
            $email_content .= "Email: $email\n\n";
            $email_content .= "Message:\n$message\n";
    
            // Build the email headers.
            $email_headers = "From: $name <$email>";
    
            // Send the email.
            if (mail($recipient, $subject, $email_content, $email_headers)) {
                // Set a 200 (okay) response code.
                http_response_code(200);
                echo "Thank You! Your message has been sent.";
            } else {
                // Set a 500 (internal server error) response code.
                http_response_code(500);
                echo "Oops! Something went wrong and we couldn't send your message.";
            }
    
        } else {
            // Not a POST request, set a 403 (forbidden) response code.
            http_response_code(403);
            echo "There was a problem with your submission, please try again.";
        }
    
    ?>
    

    What is the purpose of sending a response code? The script is already sending back an error message if there are any problems with the form submission, so why would you want to do this?

     

    I also had another question about error checking. Is it better to do this on the PHP side (i.e. with AJAX) or with javascript?

     

    Thanks for any help or advice you can give!

     

    Matt

  13. Larry,

     

    I am creating an "account settings" page and I had a design implementation question about it that had to do with these forums, and after talking with Hartleysan about it he suggested that I write a post here and get your thoughts.

     

    Basically, I am creating a page where a user can change their email, password, or other private information all in one form just like the account settings page in this forum. I modeled mine almost exactly like this one! All fields are blank until filled in and when they click the submit button, the sections that are filled out will get processed, and if there are any errors within them, then they will be displayed. I have noticed too, that aggregating all the account settings into one form is becoming more popular recently as users don't have to choose from several options just to change their password.

     

    Hartleysan thought it was a little confusing at first, and said that unless you read the help text at the top, it might not be clear what was expected from the user. I personally think that if a user is going to change any of their credentials than yes, they do need to read a couple of sentences if they don't fully understand what to do. I just think it adds clutter to have 3 separate links to the pages that would need to handle these account edits. What are your thoughts on this? Are there any good practices for implementing something like this?

     

    Thanks for any help or advice you may have,

     

    Matt

  14. Antonio, I do agree with you about the abstractions! It definitely cleans up the code when functionality is organized that way. I am not opposed to using a little OOP on a web site for this very purpose. It is why I made the Session class (which really isn't true OOP, but a namespaced set of static functions). Also, I am still not completely sure I know what you mean about the Sorter not accepting an object that doesn't implement the interface. How does the sorter know this? What I thought was that if the object you pass doesn't have the functions defined by the interface (i.e. it doesn't implement it), then when the Sorter tries to call them an error will be thrown.

    Larry, thanks for the response and please don't get me wrong. I am not opposed to OOP in general! I think it's a very great and powerful tool in many situations. However, I just don't think it's right for web development in most of the ways it is being used. Here's why I think that way, and it's something that I think is lost on developers who are using it to build web sites. As everyone knows (or should know), the web is "stateless" by it's very nature, so with every request the entire page must be rebuilt. If server side code is used to build the page, then that is run and then removed from memory. This is true even with an AJAX request as the server side file will be called once and then destroyed. This environment is greatly different than a native desktop or local machine environment where code is ran, variables are stored in memory, things are calculated and restored in memory to be used at some later time, etc... With the web, this all happens in one short time span. Then everything is cleared from memory (in theory) and the whole thing starts over with the next page request. Of course, there are ways to pass state between page requests with sessions, but this is simply a hack to get around the original problem that the web is "stateless". That being said, if you are using OOP then you are going to instantiate a bunch of classes and store them in memory. Objects certainly don't take up that much space (especially since memory is cheap these days), but it still takes time to write and retrieve them to / from memory. Then, you are going to call twice as many functions (this is on top of the constructor calls made to create the initial objects), pass objects all over the place, and do calculations which are stored in objects and rewritten to memory. This is much slower than just using procedural code (functions are already expensive in PHP). Lastly, after the whole page has been created and sent off to the client, you are going to destroy everything only to start this whole process over again with the next page request. Certainly, some PHP files are stored in cache to speed things up, but that's not the point. The point is that you are creating all this code and using all this processing in a situation that I feel doesn't really need it in the first place. And all to do what, to make it easier for the developer!

     

    That being said, I do agree with you that developer time is an important consideration, and in situations where there are customers with strict deadlines, then using OOP and frameworks definitely is acceptable, or even unavoidable.  I am merely talking about best case scenarios where I have full control over how a site is made.

     

    Also, as you know Hartley San and I are good friends and often work together on sites.  As you are well aware he is a very intelligent developer, and he has definitely influenced my coding practices in many ways, but please don't think at all that he has coerced me in any way in my attitudes towards OOP and frameworks.  I think the one reason we get along so well is that from the very beginning we shared the same overall attitude towards the increasing, and usually unnecessary, overcomplexity that exists in modern web development.  With the heavy reliance on frameworks like JQuery, Codeigniter, Doctrine, etc... nobody really knows how to code any more!  Who needs to though?  The framework will do all the thinking for me.  And even if I wanted to, I don't have any time because it's all spent learning 10 different framework APIs.  OOP is a different problem, but it all leads to the same result, over complex code and bloat!

  15. Antonio,

     

    Sorry it took me so long to respond.  I was ver busy recently with work!

     

    Thank you for your detailed response, but I was a little confused about the following:

     

    To take you first question, implementing an interface is the only real way for a Sorter class to trust the object has sorting capabilities. This is even more clear in a fully object-oriented language like Java where you need to define return type from a method. Because a class implements an interface like "Sortable" we can trust it to be sortable.

     

    So you mean to say that some object that you pass to the Sorter class should implement an interface so that we know that it can be sorted?  I could see that being done in some large company where there are a lot of developers and rules need to be enforced, but why on earth would you want to do that with a small web site?  And how does the Sorter class even know that you implemented the interface?  Correct me if I'm wrong, but you could technically pass it whatever you wanted.  An interface is nothing more than a description of the functions that an object should have.  If you choose not to implement the interface, then you don't necessarily have those functions.

     

    Also, I took a look at Laravel, and although I have heard that it is one of the better frameworks, I was absolutely appalled!  I looked at the session API and there are 6 CLASSES FOR SESSIONS!  Most of them contained what appeared to be useless methods (boot(), provides(), register(), etc...).  Of all those, I could only see one or two that made any sense at all (CookieSessionHandler and CacheBasedSessionHandler) and all of them extended or implemented an abstract class or an interface!  Antonio, what is going on?

    This is NOT the solution to modern web development.  It's the problem!!!  Instantiating class, after class, after class, and calling function, after function, after function, and subsequently getting thoroughly lost in code to accomplish what I'm not sure, is just a waste of time and energy.  And, I don't want to burst anyones bubbles, but OOP is also slow!  This is not up for debate, it is a fact!  Do you ever wonder why, when you go to a site ran by a CMS (i.e. Wordpress or Joomla) it takes a few seconds for the page to load?  It's because you have to instantiate 10 different classes and call 16 different methods to write something as simple as "Hello World" on the screen!  Doesn't this sort of thing sound pretty absurd to you?  If so, then why are you choosing to participate in it?

     

    Matt

    • Upvote 1
  16. Antonio,

     

    Thanks for the detailed response.  I understand most of what you said but I did have a couple of questions.

     

    Due to composition. Wouldn't it make you happy to pass your whole Session object into a JSON parser, being able to foreach a Session object, sort session values internally or similar? By providing interfaces, you Sorter class doesn't give a damn if it gets a Session or a Dog object. (It would probably like a dog better thought) A key concept here is seperations of concerns, and that is also why you'll see loads of classes for something as simple as a session.

     

    I am a little confused here.  I thought an interface was just a programming structure used to enforce certain properties on a class.  It isn't an object, and it isn't a class either.  That being the case, why would you want to pass one to a sorter?  Sorry to sound so naive, but things have gotten much more complicated with 00P in recent years!

     

     

    Maintainability, extensibility and other fancy words are very much more important from a business perspective. eZ Publish takes 12 seconds to load in dev enviroment (mainly due to logging) on a project I'm working on, but throw Varnish on top of it, and it can serve millions upon millions of requests a day. Best part? Load is BETTER the more traffic the site gets.

     

    I know what you're saying, but the site that Jon and I are making isn't Amazon.com, where we have a huge team of developers that all need to be on the same page.  It is a small - medium size site that doesn't have a tremendous amount of pages.  The traffic may get a little high at times, but nothing heavy.  That being said, I think going overboard with the OOP paradigm will just add more work and confusion to an already big project.  I just think that people make a simple thing very complicated by all the objects passing objects to other objects.  There is no reason for it.  We do have a lot of functions, but they are all organized into separate include files which are assigned to constants in the config file.  When we want to use a function we include the constant (i.e. file) and call the function.  Easy!  No instantiation, no confusion about why the response that came back from a complicated inherited class is what it is, no objects sitting in memory, etc...  I think that most people jump on the OOP train because it's become a trend, and if you're not doing it there is something wrong with you, or you're a NOOB.  Sorry, but I was doing OOP from the beginning and in most web development environments I just don't see the need for it!

     

    I did have one OOP question though (he, he).  I just changed the class so that it wasn't using static functions, and thus has to be instantiated with the "new" operator.  When I did that, I noticed that every function that used to use the static session class didn't have access to the session object anymore.  Don't worry, I changed everything from Session::read('id'); to $session->read('id');.  What I figured out is that the new $session object isn't global anymore.  I have to pass it to the function in order for the function to use it.  I think that is one more benefit to using a static class.  What do you think?

     

    Matt

  17. I found an error in this guys class!

     

    If you look at the _read() function, it is first checking if the $key exists in the session.  Then it calls the _age() function just before reading the session variable.  Normally this is fine, but if the session has gone past it's age limit, the _age() function calls the destroy() function, which destroys the session.  Then, when it reaches the point where it tries to read the session variable, it will give an error saying it can't find the variable blah, blah.  I discovered this by accidentally hardcoding the $SESSION_AGE to 30, then I was always getting an error after 30 seconds of no activity.  The guy who wrote the class probably never found this out.

     

    What I did to fix this was pass a boolean back from the _age() function:

        private function _age()
        {
            $last = isset($_SESSION['LAST_ACTIVE']) ? $_SESSION['LAST_ACTIVE'] : false ;
            
            if (false !== $last && (time() - $last > self::$SESSION_AGE))
            {
                return $this->destroy();
            }
    
            $_SESSION['LAST_ACTIVE'] = time();
            
            return false;
        }
    

    Then, in the read() function I check the result of the boolean to determine if it should get the value from the session:

        public function read($key, $child = false)
        {
            if ( !is_string($key) )
            {
                return false;
            }
            if (isset($_SESSION[$key]))
            {
                if(self::_age() === false) {
                    if (false == $child)
                    {
                        return $_SESSION[$key];
                    }
                    else
                    {
                        if (isset($_SESSION[$key][$child]))
                        {
                            return $_SESSION[$key][$child];
                        }
                    }
                }
            }
            return false;
        }
    

    Sorry for the confusion about that!

  18. Larry,

     

    Thanks for the kind reply!

     

    I have done more research and I had a couple of questions:

     

    1)  The properties/functions are all static, but should I change them into non-static properties/functions where the class has to be instantiated before using it?  Antonio said before that using "static" classes is bad, and I have read that on many sites as well, as you are doing nothing more than creating a namespace for the static properties and functions so they don't crowd the global namespace.  However, it just seems that a static class is the cleanest thing to do here.  There is only a single session per user at any time, and it just seems that a single, fixed class would be more appropriate.  What do you think?

     

    2)  I noticed that when I regenerate the session id, the save_path and cookie settings remain unchanged even though session_destroy() and session_start() have been called in the regenerate_session_id() function.  I thought all settings had to be done again each time the session is started.  If anyone could explain to me what is going on, I would be much appreciative!

     

    Thanks,

     

    Matt

  19. As per the previous topic on Session Best Practices, I have put together a class that I believe is the simplest, most comprehensive, and secure I have ever seen on the web or in books!

     

    It is an amalgamation of a session class I found on Github here: https://gist.github.com/Nilpo/5449999 and the session security functions Hartleysan wrote.  I also added a few other things as well!  It uses static functions, so it doesn't have to be instantiated.  To use it, just call the start() function with the following, optional, parameters:

     

    Session::start(boolean $regenerate_session_id = false, int $limit = 0, string $path = '/', string $domain = null, boolean $secure_cookies_only = null);

     

    The first parameter is used to tell the _init() function if you want to regenerate the session id.  The other 4 parameters deal with cookie settings and all correspond to those sent to the set_session_cookie_params() function.

     

    So, you can do something like :

    Session::start(true);
    
    Session::start(true, 0, '/my_public_pages/', 'www.example.com');
    
    etc...
    

    If you want to use the CustomException.php class, it can be copied from here: https://gist.github.com/Nilpo/4312309

     

    I have thought about adding the session name parameter to the start() function, as well as moving the session_save_path() out of the class and putting it before the session class is used as I've heard that it can cause problems with regenerating the session id (however, I haven't seen this in the tests that that I've done):

     

    ini_set('session.save_path', '/path/to/session/files');

     

    Here is the Session class:

    <?php
     
    /**
     * Session Helper Class
     *
     * A simple session wrapper class.
     *
     * Recommended for use with PHP 5.4.0 or higher. (Not required.)
     *
     * Copyright (c) 2013 Robert Dunham
     *
     * Additions by Jon Hartley and Matt Ulrich
     */
     
    include_once (CUSTOM_EXCEPTION_CLASS);
     
    if ( !class_exists('CustomException') ) {
        class CustomException extends Exception {}
    }
    class SessionHandlerException extends CustomException {}
    class SessionDisabledException extends SessionHandlerException {}
    class InvalidArgumentTypeException extends SessionHandlerException {}
    class ExpiredSessionException extends SessionHandlerException {}
     
    //defined('CHECK_ACCESS') or die('Direct access is not allowed.');
     
    class Session
    {
        /* The diecrotry name for the session */
        private static $SESSION_DIR = 'd79252d7dea8e2812b4ebf29ffc603ed/';
        
        /* The name used for the session */
        private static $SESSION_NAME = 'f7eac143c2e6c95e84a3e128e9ddcee6';
        
        /**
         * Session Age.
         * 
         * The number of seconds of inactivity before a session expires.
         * 
         * @var integer
         */
        protected static $SESSION_AGE = 1800;
        
        /**
         * Writes a value to the current session data.
         * 
         * @param string $key String identifier.
         * @param mixed $value Single value or array of values to be written.
         * @return mixed Value or array of values written.
         * @throws InvalidArgumentTypeException Session key is not a string value.
         */
        public static function write($key, $value)
        {
            if ( !is_string($key) )
                throw new InvalidArgumentTypeException('Session key must be string value');
            self::_init();
            $_SESSION[$key] = $value;
            self::_age();
            return $value;
        }
        
        /**
         * Reads a specific value from the current session data.
         * 
         * @param string $key String identifier.
         * @param boolean $child Optional child identifier for accessing array elements.
         * @return mixed Returns a string value upon success.  Returns false upon failure.
         * @throws InvalidArgumentTypeException Session key is not a string value.
         */
        public static function read($key, $child = false)
        {
            if ( !is_string($key) )
                throw new InvalidArgumentTypeException('Session key must be string value');
            self::_init();
            if (isset($_SESSION[$key]))
            {
                self::_age();
                
                if (false == $child)
                {
                    return $_SESSION[$key];
                }
                else
                {
                    if (isset($_SESSION[$key][$child]))
                    {
                        return $_SESSION[$key][$child];
                    }
                }
            }
            return false;
        }
        
        /**
         * Deletes a value from the current session data.
         * 
         * @param string $key String identifying the array key to delete.
         * @return void
         * @throws InvalidArgumentTypeException Session key is not a string value.
         */
        public static function delete($key)
        {
            if ( !is_string($key) )
                throw new InvalidArgumentTypeException('Session key must be string value');
            self::_init();
            unset($_SESSION[$key]);
            self::_age();
        }
        
        /**
         * Echos current session data.
         * 
         * @return void
         */
        public static function dump()
        {
            self::_init();
            echo nl2br(print_r($_SESSION));
        }
     
        /**
         * Starts or resumes a session by calling {@link Session::_init()}.
         * 
         * @see Session::_init()
         * @return boolean Returns true upon success and false upon failure.
         * @throws SessionDisabledException Sessions are disabled.
         */
        public static function start($regenerate_session_id = true, $limit = 0, $path = '/', $domain = null, $secure_cookies_only = null)
        {
            // this function is extraneous
            return self::_init($regenerate_session_id, $limit, $path, $domain, $secure_cookies_only);
        }
        
        /**
         * Expires a session if it has been inactive for a specified amount of time.
         * 
         * @return void
         * @throws ExpiredSessionException() Throws exception when read or write is attempted on an expired session.
         */
        private static function _age()
        {
            $last = isset($_SESSION['LAST_ACTIVE']) ? $_SESSION['LAST_ACTIVE'] : false ;
            
            if (false !== $last && (time() - $last > self::$SESSION_AGE))
            {
                self::destroy();
                throw new ExpiredSessionException();
            }
            $_SESSION['LAST_ACTIVE'] = time();
        }
        
        public static function regenerate_session_id() {
          
            $session = array();
            
            foreach ($_SESSION as $k => $v) {
            
                $session[$k] = $v;
                
            }
            
            session_destroy();
            
            session_id(bin2hex(openssl_random_pseudo_bytes(16)));
            
            session_start();
            
            foreach ($session as $k => $v) {
            
                $_SESSION[$k] = $v;
                
            }
          
        }
        
        /**
         * Returns current session cookie parameters or an empty array.
         * 
         * @return array Associative array of session cookie parameters.
         */
        public static function params()
        {
            $r = array();
            if ( '' !== session_id() )
            {
                $r = session_get_cookie_params();
            }
            return $r;
        }
        
        /**
         * Closes the current session and releases session file lock.
         * 
         * @return boolean Returns true upon success and false upon failure.
         */
        public static function close()
        {
            if ( '' !== session_id() )
            {
                return session_write_close();
            }
            return true;
        }
        
        /**
         * Alias for {@link Session::close()}.
         * 
         * @see Session::close()
         * @return boolean Returns true upon success and false upon failure.
         */
        public static function commit()
        {
            return self::close();
        }
        
        /**
         * Removes session data and destroys the current session.
         * 
         * @return void
         */
        public static function destroy()
        {
            if ( '' !== session_id() )
            {
                $_SESSION = array();
     
                // If it's desired to kill the session, also delete the session cookie.
                // Note: This will destroy the session, and not just the session data!
                if (ini_get("session.use_cookies")) {
                    $params = session_get_cookie_params();
                    setcookie(session_name(), '', time() - 42000,
                        $params["path"], $params["domain"],
                        $params["secure"], $params["httponly"]
                    );
                }
     
                session_destroy();
            }
        }
        
        /**
         * Initializes a new session or resumes an existing session.
         * 
         * @return boolean Returns true upon success and false upon failure.
         * @throws SessionDisabledException Sessions are disabled.
         */
        private static function _init($regenerate_session_id = false, $limit = 0, $path = '/', $domain = null, $secure_cookies_only = null)
        {
            if (function_exists('session_status'))
            {
                // PHP 5.4.0+
                if (session_status() == PHP_SESSION_DISABLED)
                    throw new SessionDisabledException();
            }
            
            if ( '' === session_id() )
            {
                $site_root = BASE_URI;
                $session_save_path = $site_root . self::$SESSION_DIR;
                session_save_path($session_save_path);
                
                session_name(self::$SESSION_NAME);
                
                $domain = isset($domain) ? $domain : $_SERVER['SERVER_NAME'];
                
                session_set_cookie_params($limit, $path, $domain, $secure_cookies_only, true);
                
                session_start();
                
                if ($regenerate_session_id) {
                    self::regenerate_session_id();
                }
                
                return true;
            
            }
            
            self::_age();
    
            if ($regenerate_session_id && rand(1, 100) <= 5) {
                self::regenerate_session_id();
                $_SESSION['regenerated_id'] = session_id();
            }
            
            return true;
            
        }
    }
    

    If anyone would like to make any edits to it, then please do so, otherwise, enjoy!

     

    Matt

  20. Antonio,

     

    Thanks for your input on this!  I just had a couple of questions:

     

     

    Those kind of things would be better suited to a Session object. To make it sweeter, I would also have the Session object implement some interfaces for handling stuff like serialization and etc.

     

    I'm curious why you would separate session handling functionality into a separate class?  A while back, when Jon and I were researching session best practices, I looked at the way (I think it was Zend) a framework was handling sessions, thinking that I could just copy over some code.  I was absolutely shocked!  It used not 1, not 2, NOT 3, NOT 4, but 5 classes to handle sessions!  I know it's a framework, and almost all frameworks by their very nature are pieces of bloatware that are designed to handle numerous different use cases, but this is simply ludicrous!  A session is a very simple thing when it comes down to it!  You create one, add data to it, take data out of it, and destroy it.  Why there needs to be any more than one, perhaps 2 at most, classes just amazes me.  And an interface?  Antonio, there is only ever ONE session per user visiting a web site.  Why in the world would you want to write an interface and then implement it with a class?  This kind of unnecessary abstraction is the exact reason why sites using frameworks are generally slow!  I`m not trying to be hard on you, but I really want to know what I am missing here.

     

     

    Usually, you don't want error messages or similar when say a session key isn't found, so I've focused more on that kind of stuff.

     

    Very good point!

     

     

    Due to that, I tend to build on Laravel or even the Symfony2-stack itself.

     

    Maybe Laravel has good session handling functionality, but here is one problem (among many) with using a framework for handling tasks that should be done by the developer.  I think it was OWASP which said the following (I'm paraphrasing) "You should never rely on a framework to implement the latest, or best, security when building a site."  The reason is that often times the developers who make the frameworks are either uninformed of new best practices, or just don't know about them altogether.  To illustrate my point, I looked through OWASP's security library and found a couple areas where even they were using security techniques that were about a year behind the currently recommended practices.  The message I'm trying to say here is YOU ARE ULTIMATELY RESPONSIBLE FOR YOUR SITE'S SECURITY!  If you trust a framework to do that for you, then it's like trusting a complete stranger on the street to go get money out of your bank account for you (well that's an extreme example, but you get my point).

     

    On another note, you had said before that you wouldn't advise using static functions for the session manager class.  Why is that?  I thought that since there is only one session, that it would best be handled by an immutable class / singleton.  What are the drawbacks of using static functions?

     

    Thanks again Antonio!

     

    Matt

  21. I finally got around to start writing a session manager class using Jon's functions.

     

    While I do have a lot of OOP experience, this is the first class I've written in PHP.  I wrote it in about 5 min., so there are probably some bad practices/errors here!  If anyone wants to use it, or make improvements to it, by all means, please do!

    <?php
      
      class SessionManager {
        
        private $SESSION_DIR = 'd79252d7dea8e2812b4ebf29ffc603ed\\';
        
        private $SESSION_NAME = 'f7eac143c2e6c95e84a3e128e9ddcee6';
        
        static function session_init($regenerate_session_id = true, $secure_cookies_only = false, $timeout = 600) {
            
            $SITE_ROOT = $_SERVER['DOCUMENT_ROOT'];
            
            $SESSION_SAVE_PATH = $SITE_ROOT . $this->$SESSION_NAME;
            
            session_save_path($SESSION_SAVE_PATH);
            
            session_name($this->$SESSION_NAME);
            
            if ($secure_cookies_only) {
              
              ini_set('session.cookie_secure', '1');
              
            }
            
            session_start();
            
            if ($regenerate_session_id) {
              
              self::regenerate_session_id();
              
            }
            
            if (isset($_SESSION['last_script_load_time']) && time() - $_SESSION['last_script_load_time'] > $timeout) {
              
              self::session_terminate();
              
              return false;
              
            }
            
            $_SESSION['last_script_load_time'] = time();
            
            return true;
            
        }
        
        static function regenerate_session_id() {
            
            $session = array();
            
            foreach ($_SESSION as $k => $v) {
              
              $session[$k] = $v;
              
            }
            
            session_destroy();
            
            session_id(bin2hex(openssl_random_pseudo_bytes(16)));
            
            session_start();
            
            foreach ($session as $k => $v) {
              
              $_SESSION[$k] = $v;
              
            }
            
        }
        
        static function session_terminate() {
            
            $_SESSION = array();
            
            session_destroy();
            
            setcookie($this->$SESSION_NAME, '', time() - 3600);
            
        }
      
    }
    

    The main concern I have about doing it this way, and I have also noticed this in a couple other session manager classes I found online, is that there aren't any functions for adding/deleting data from the SESSION array.  It's like people use this type of class just to initialize the session or regenerate session IDs.  Maybe I am thinking too much about this, but an object is supposed to encapsulate all the functionality needed by an autonomous "thing", and in this case it's a session.  I think that all transactions with the session should be done through the session manager object and that object alone!

     

    Larry or Antonio, please feel free to give any suggestions or helpful advice.

     

    Thanks,

     

    Matt

  22. Also, just in case you are not aware of this, when you perform the update and none of the values have changed from those previously stored in the database, the query will return 0 for the number of rows affected!  The best way to check that the update was successful is to test if the execute statement returned 'true'!

     

    if (!mysqli_stmt_execute($stmt)) {

     

    $errors['system_error'] = 'There was an error updating the database. Please try again.';

     

    }

×
×
  • Create New...