Commenting system with lightweight JSON store

September 7, 2008

As I wrote this blog engine, the need for a commenting system arose and I reflected about a small and simple commenting system with just a flat file JSON store. This is my solution, which can be used on any static page on a server with PHP support.

JQuery Frontend

First of all we need a form, which will post the data to the PHP script. The comment rendering is done by my own Javascript templating system, which I will explain in a seperate post. For now we focus on the task of posting a form and saving it to our JSON store.

  1. <div class="comment-form">
  2. <form>
  3. <input type="hidden" name="guid" value="7ad04f10-5dd6-012b-b53c-001a92975b89"/>
  4. <p>
  5. <label>Name</label><br/>
  6. <input type="text" name="name" size="40"/>
  7. </p>
  8. <p>
  9. <label>Website</label><br/>
  10. <input type="text" name="website" size="40"/>
  11. </p>
  12. <p>
  13. <label>Comment</label><br/>
  14. <textarea name="text" cols="60" rows="10"></textarea>
  15. </p>
  16. <p>
  17. <input type="submit" value="Post comment"/>
  18. </p>
  19. </form>
  20. </div>

My blog engine generates a guid for each post, so this will be posted by the form as well. Ok, let’s have a look at the Javascript code:

  1. $('.comment-form form').ajaxForm({
  2. url: Blog.root + 'controllers/comments.php',
  3. type: 'POST',
  4. resetForm: true,
  5. beforeSubmit: function(values) {
  6. if (values[1].value && values[3].value) {
  7. return true;
  8. }
  9. else {
  10. alert('Please enter name and text!');
  11. return false;
  12. }
  13. },
  14. success: function(data) {
  15. renderComments(data);
  16. }
  17. });

This uses the jquery-form plugin to submit the form via AJAX, nothing special here, the input will be validated to have at least a name and comment text. After a successful comment post, the comments should be rendered, but this is another story.

PHP Backend

The backend is extremely YAGNI. Comments for one post, will be saved in one JSON file like comments/guid-of-the-post. The 4 fields will be encoded as json array and appended to the file. This happens only, if the request was a POST. Finally we read the whole file and send it back as response.

  1. <?php
  2. $guid_pattern = "/^(\{{0,1}([0-9a-fA-F]){8}-([0-9a-fA-F]){4}-([0-9a-fA-F]){4}-([0-9a-fA-F]){4}-([0-9a-fA-F]){12}\}{0,1})$/";
  3. $req = $_REQUEST;
  4. $guid = $req['guid'];
  5. preg_match($guid_pattern, $guid) or die("invalid guid");
  6. $file = 'comments/' . $guid;
  7. if ($_SERVER['REQUEST_METHOD'] == 'POST') {
  8. // create a comment record
  9. $record = array(date('Y-m-d H:i:s'),
  10. strip_tags(stripslashes($req['name'])),
  11. strip_tags(stripslashes($req['website'])),
  12. strip_tags(stripslashes($req['text'])));
  13. // encode as json string
  14. $json = json_encode($record) . "\n";
  15. // open the comment file for appending
  16. $fp = fopen($file, "a");
  17. // acquire a write lock
  18. flock($fp, LOCK_EX);
  19. // append the json line
  20. fwrite($fp, $json);
  21. // release lock
  22. flock($fp, LOCK_UN);
  23. // close file
  24. fclose($fp);
  25. }
  26. if (file_exists($file)) {
  27. // open the comment file for reading
  28. $fp = fopen($file, "r");
  29. // acquire a read lock
  30. flock($fp, LOCK_SH);
  31. // read whole file and print it out
  32. echo fread($fp, filesize($file));
  33. // release lock
  34. flock($fp, LOCK_UN);
  35. // close file
  36. fclose($fp);
  37. }
  38. ?>

One important thing to note is, that the comment file is not one big JSON array. It looks like this:

  1. ["2008-09-07 12:28:33","Hans","","**strong text**\n*emphasized text*"]
  2. ["2008-09-07 12:29:33","Hans","","**strong text**\n\n\n*emphasized text*"]
  3. ["2008-09-07 12:29:56","Hans","","**strong text**\n\n\n*emphasized text*"]

For each line, we have one JSON array. This way, the PHP script doesn’t need to read the whole JSON thing into memory. It just appends on every POST one line.

Escaping

Some blog comments showed strange escaping behviour, so I investigated further. PHP has a foolproof feature called Magic Quotes. It automatically escapes all dangerous characters from request parameters to protect dumb users from SQL Injection. This feature is deprecated and in version 6.0.0 it will be removed. Nevertheless it is activated in a default PHP installation.

To revert the escaping behaviour I have to call stripslashes. Also I have to care about stripping HTML tags from input. So to protect from malicious HTML, I filter all input through strip_tags.

Concurrency

As a commenter pointed out, concurrent access can be a headache. I hoped, that file_put_contents is an atomic function, but it is not. However, I use a simple file locking scheme, which is good enough. One caveat remains: in a multithreading environment this will not work reliably. But I think, most PHP installations run as CGI, so this will be ok.

Security

Seems that I had a serious security flaw in my first version. I didn’t check the guid parameter, so that you could pass a path like ../../../../../../etc/group. Now the guid is matched against a regular expression, so the script is now safe.

Conclusion

With a few lines you can hook up a simple commenting system for static pages powered by AJAX and PHP. Note, that rendering of comments is not discussed here and happens on Javascript side.

Read about my Javascript template engine which is used to render the comments.


Posted in category Javascript by Matthias Georgi. Tagged with database, json.
Similar Posts