CVE-2015-10094 (published 2023-03-06T15:15Z)

A vulnerability was found in Fastly Plugin up to 0.97 on WordPress. It has been rated as problematic. Affected by this issue is the function post of the file lib/api.php. The manipulation of the argument url leads to cross site scripting. The attack may be launched remotely. Upgrading to version 0.98 is able to address this issue. The patch is identified as d7fe42538f4d4af500e3af9678b6b06fba731656. It is recommended to upgrade the affected component. VDB-222326 is the identifier assigned to this vulnerability.

CVSS v3 base score: 6.1, MEDIUM (CVSS:3.1/AV:N/AC:L/PR:N/UI:R/S:C/C:L/I:L/A:N)

Weakness information:
cwe_id: CWE-79
cwe_name: Improper Neutralization of Input During Web Page Generation ('Cross-site Scripting')
description: The product does not neutralize or incorrectly neutralizes user-controllable input before it is placed in output that is used as a web page that is served to other users.
extended_description: 
            Cross-site scripting (XSS) vulnerabilities occur when:
            
               Untrusted data enters a web application, typically from a web request.
               The web application dynamically generates a web page that contains this untrusted data.
               During page generation, the application does not prevent the data from containing content that is executable by a web browser, such as JavaScript, HTML tags, HTML attributes, mouse events, Flash, ActiveX, etc.
               A victim visits the generated web page through a web browser, which contains malicious script that was injected using the untrusted data.
               Since the script comes from a web page that was sent by the web server, the victim's web browser executes the malicious script in the context of the web server's domain.
               This effectively violates the intention of the web browser's same-origin policy, which states that scripts in one domain should not be able to access resources or run code in a different domain.
            
            There are three main kinds of XSS:
            
               
                  Type 1: Reflected XSS (or Non-Persistent) - 
         			The server reads data directly from the HTTP request and reflects it back in the HTTP response. Reflected XSS exploits occur when an attacker causes a victim to supply dangerous content to a vulnerable web application, which is then reflected back to the victim and executed by the web browser. The most common mechanism for delivering malicious content is to include it as a parameter in a URL that is posted publicly or e-mailed directly to the victim. URLs constructed in this manner constitute the core of many phishing schemes, whereby an attacker convinces a victim to visit a URL that refers to a vulnerable site. After the site reflects the attacker's content back to the victim, the content is executed by the victim's browser.
               
                  Type 2: Stored XSS (or Persistent) - 
               The application stores dangerous data in a database, message forum, visitor log, or other trusted data store. At a later time, the dangerous data is subsequently read back into the application and included in dynamic content. From an attacker's perspective, the optimal place to inject malicious content is in an area that is displayed to either many users or particularly interesting users. Interesting users typically have elevated privileges in the application or interact with sensitive data that is valuable to the attacker. If one of these users executes malicious content, the attacker may be able to perform privileged operations on behalf of the user or gain access to sensitive data belonging to the user. For example, the attacker might inject XSS into a log message, which might not be handled properly when an administrator views the logs.
            
               
                  Type 0: DOM-Based XSS - 
               In DOM-based XSS, the client performs the injection of XSS into the page; in the other types, the server performs the injection. DOM-based XSS generally involves server-controlled, trusted script that is sent to the client, such as Javascript that performs sanity checks on a form before the user submits it. If the server-supplied script processes user-supplied data and then injects it back into the web page (such as with dynamic HTML), then DOM-based XSS is possible.
            
            
            Once the malicious script is injected, the attacker can perform a variety of malicious activities. The attacker could transfer private information, such as cookies that may include session information, from the victim's machine to the attacker. The attacker could send malicious requests to a web site on behalf of the victim, which could be especially dangerous to the site if the victim has administrator privileges to manage that site. Phishing attacks could be used to emulate trusted web sites and trick the victim into entering a password, allowing the attacker to compromise the victim's account on that web site. Finally, the script could exploit a vulnerability in the web browser itself possibly taking over the victim's machine, sometimes referred to as "drive-by hacking."
            In many cases, the attack can be launched without the victim even being aware of it. Even with careful users, attackers frequently use a variety of methods to encode the malicious portion of the attack, such as URL encoding or Unicode, so the request looks less suspicious.
         
         

Fix information:
repo_url: https://github.com/wp-plugins/fastly
commit_details: repo_url: https://github.com/wp-plugins/fastly
author: fastly
author_date: 2015-02-03 18:15:01+00:00
msg: Version 0.98 - CSRF/XSS Security Fixes

Bumps version to 0.98. Addresses potential XSS/CSRF security issues in plugin.

Changes admin.php to use the esc_attr() and esc_url() on the Fastly
option values prior to outputting them in the context of the HTML to be
rendered in the admin panel. While it appears that the values of these
options could not be set from an unprivileged context we should be
escaping them prior to rendering irregardless.

Previously the FastlyAdmin::ajaxSetPage() and FastlyAdmin::ajaxSignUp()
handlers in lib/admin.php were not enforcing the presence of a CSRF
token. This commit also modifies these handlers to use the
wp_verify_nonce() function. The static/fastly.js AJAX posts were
modified to supply a wp_create_nonce() token injected into the scripts
context by wp_localize_script(). The wp_register_script() and associated
calls were moved to a wp_enqueue_scripts event handler in order to
ensure that wp_create_nonce() is defined/loaded at the time we try to
use it.

Changes all calls to update_option() with dynamic content to use
esc_sql() on the data before passing it to update_option() to avoid
potential for SQLi.

Thanks to Zack Tollman for the original vulnerability reports.




git-svn-id: https://plugins.svn.wordpress.org/fastly/trunk@1081712 b8457f37-d9ea-0310-8a92-e5e31aec5664
repository: repo_url: https://github.com/wp-plugins/fastly
repo_name: wp-plugins/fastly
description: WordPress.org Plugin Mirror
repo_language: PHP
owner: wp-plugins
file_changes: filename: README
change_type: ModificationType.DELETE
diff: @@ -1,16 +0,0 @@
-Fastly.com WordPress Plugin
-Copyright (C) 2011,2012,2013 Fastly.com
-
-This program is free software: you can redistribute it and/or modify
-it under the terms of the GNU General Public License as published by
-the Free Software Foundation, either version 3 of the License, or
-(at your option) any later version.
-
-This program is distributed in the hope that it will be useful,
-but WITHOUT ANY WARRANTY; without even the implied warranty of
-MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
-GNU General Public License for more details.
-
-You should have received a copy of the GNU General Public License
-along with this program.  If not, see <http://www.gnu.org/licenses/>.
-

programming_language: unknown
filename: README.md
change_type: ModificationType.ADD
diff: @@ -0,0 +1,45 @@
+# The Wordpress Plugin
+
+Integrates Fastly with Wordpress' publishing tools.
+
+## Installation
+
+You can either install from source (you're looking at it), or from the Wordpress [plugin directory](http://wordpress.org/plugins/fastly/).
+
+0. If you don't already have it send us a support request asking to have the Wordpress feature turned on for your account.
+1. Add a new WordPress config to a Service and set up the path to the Wordpress install. Examples:
+  - If your blog is at `http://blog.example.com/`, your path is `/`
+  - If your blog is at `http://example.com/blog/`, your path is `/blog/`
+2. Deploy the new Version of the Service.
+3. With your API key and the Service id in hand, install the plugin under Wordpress.
+4. Set up the Fastly plugin inside your Wordpress config panel - you should just have to input the API key and the Service id that you noted in the last step.
+5. That's it! Everything should just work. :metal: If you have any problems, email us.
+
+_Note: you may have to disable other caching plugins like W3TotalCache to avoid getting odd cache behaviour._
+
+## What's going on?
+
+Take a look at the inline comments in the [code](https://github.com/fastly/WordPress-Plugin/tree/master/lib) for an in depth description. But, the plugin:
+
+- Pulls in the [Fastly API](http://docs.fastly.com/api)
+- Wires Instant Purging into the publishing process, keeping content up to date
+- Includes an admin panel in `wp-admin`
+
+## License
+
+Fastly.com WordPress Plugin
+Copyright (C) 2011,2012,2013 Fastly.com
+
+This program is free software: you can redistribute it and/or modify
+it under the terms of the GNU General Public License as published by
+the Free Software Foundation, either version 3 of the License, or
+(at your option) any later version.
+
+This program is distributed in the hope that it will be useful,
+but WITHOUT ANY WARRANTY; without even the implied warranty of
+MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+GNU General Public License for more details.
+
+You should have received a copy of the GNU General Public License
+along with this program.  If not, see <http://www.gnu.org/licenses/>.
+

programming_language: Markdown
filename: fastly.php
change_type: ModificationType.MODIFY
diff: @@ -1,68 +1,68 @@
-<?php
-
-/*
-Plugin Name: Fastly
-Plugin URI: http://fastly.com/
-Description: Configuration and cache purging for the Fastly CDN.
-Author: Fastly.com
-Version: 0.97
-Author URI: http://fastly.com/
-*/
-
-/**
- * Main plugin code.
- * @package Fastly
- * @version 0.97
- * @author Ryan Sandor Richards
- * @copyright 2011 Fastly.com, All Rights Reserved
- */
-
-// Basic plugin definitions
-define('FASTLY_VERSION', '0.97');
-define('FASTLY_PLUGIN_URL', plugin_dir_url( __FILE__ ));
-
-// Includes
-include_once dirname( __FILE__ ) . '/lib/purge.php';
-include_once dirname( __FILE__ ) . '/lib/admin.php';
-include_once dirname( __FILE__ ) . '/lib/api.php';
-
-// Check for JSON support
-if (!function_exists('json_decode')) {
-  require_once dirname( __FILE__ ) . '/lib/JSON.php';
-  define('FASTLY_JSON', false);
-}
-
-// Plugin Options
-add_option('fastly_hostname', '');
-add_option('fastly_api_key', '');
-add_option('fastly_service_id', '');
-add_option('fastly_api_hostname', 'https://api.fastly.com');
-add_option('fastly_api_port', null);
-add_option('fastly_page', 'welcome');
-add_option('fastly_log_purges', '0');
-
-// Setup Purging
-new FastlyPurge();
-
-// Setup admin (if needed)
-if (is_admin()) {
-  new FastlyAdmin();
-}
-
-// Custom action links for the plugin.
-function fastly_action_links($links, $file) {
-  static $this_plugin;
-  if (!$this_plugin) {
-    $this_plugin = plugin_basename(__FILE__);
-  }
-  if ($file == $this_plugin) {
-    $settings_link = '<a href="' . get_bloginfo('wpurl') . '/wp-admin/options-general.php?page=fastly-admin-panel">Settings</a>';
-    array_unshift($links, $settings_link);
-  }
-  return $links;
-}
-add_filter('plugin_action_links', 'fastly_action_links', 10, 2);
-
-// "Look out honey, cause I'm using technology..." - Iggy Pop
-
-?>
\ No newline at end of file
+<?php
+
+/*
+Plugin Name: Fastly
+Plugin URI: http://fastly.com/
+Description: Configuration and cache purging for the Fastly CDN.
+Author: Fastly.com
+Version: 0.98
+Author URI: http://fastly.com/
+*/
+
+/**
+ * Main plugin code.
+ * @package Fastly
+ * @version 0.98
+ * @author Ryan Sandor Richards
+ * @copyright 2011 Fastly.com, All Rights Reserved
+ */
+
+// Basic plugin definitions
+define('FASTLY_VERSION', '0.98');
+define('FASTLY_PLUGIN_URL', plugin_dir_url( __FILE__ ));
+
+// Includes
+include_once dirname( __FILE__ ) . '/lib/purge.php';
+include_once dirname( __FILE__ ) . '/lib/admin.php';
+include_once dirname( __FILE__ ) . '/lib/api.php';
+
+// Check for JSON support
+if (!function_exists('json_decode')) {
+  require_once dirname( __FILE__ ) . '/lib/JSON.php';
+  define('FASTLY_JSON', false);
+}
+
+// Plugin Options
+add_option('fastly_hostname', '');
+add_option('fastly_api_key', '');
+add_option('fastly_service_id', '');
+add_option('fastly_api_hostname', 'https://api.fastly.com');
+add_option('fastly_api_port', null);
+add_option('fastly_page', 'welcome');
+add_option('fastly_log_purges', '0');
+
+// Setup Purging
+new FastlyPurge();
+
+// Setup admin (if needed)
+if (is_admin()) {
+  new FastlyAdmin();
+}
+
+// Custom action links for the plugin.
+function fastly_action_links($links, $file) {
+  static $this_plugin;
+  if (!$this_plugin) {
+    $this_plugin = plugin_basename(__FILE__);
+  }
+  if ($file == $this_plugin) {
+    $settings_link = '<a href="' . get_bloginfo('wpurl') . '/wp-admin/options-general.php?page=fastly-admin-panel">Settings</a>';
+    array_unshift($links, $settings_link);
+  }
+  return $links;
+}
+add_filter('plugin_action_links', 'fastly_action_links', 10, 2);
+
+// "Look out honey, cause I'm using technology..." - Iggy Pop
+
+?>

programming_language: PHP
filename: JSON.php
change_type: ModificationType.MODIFY
diff: @@ -1,806 +1,806 @@
-<?php
-/* vim: set expandtab tabstop=4 shiftwidth=4 softtabstop=4: */
-
-/**
- * Converts to and from JSON format.
- *
- * JSON (JavaScript Object Notation) is a lightweight data-interchange
- * format. It is easy for humans to read and write. It is easy for machines
- * to parse and generate. It is based on a subset of the JavaScript
- * Programming Language, Standard ECMA-262 3rd Edition - December 1999.
- * This feature can also be found in  Python. JSON is a text format that is
- * completely language independent but uses conventions that are familiar
- * to programmers of the C-family of languages, including C, C++, C#, Java,
- * JavaScript, Perl, TCL, and many others. These properties make JSON an
- * ideal data-interchange language.
- *
- * This package provides a simple encoder and decoder for JSON notation. It
- * is intended for use with client-side Javascript applications that make
- * use of HTTPRequest to perform server communication functions - data can
- * be encoded into JSON notation for use in a client-side javascript, or
- * decoded from incoming Javascript requests. JSON format is native to
- * Javascript, and can be directly eval()'ed with no further parsing
- * overhead
- *
- * All strings should be in ASCII or UTF-8 format!
- *
- * LICENSE: Redistribution and use in source and binary forms, with or
- * without modification, are permitted provided that the following
- * conditions are met: Redistributions of source code must retain the
- * above copyright notice, this list of conditions and the following
- * disclaimer. Redistributions in binary form must reproduce the above
- * copyright notice, this list of conditions and the following disclaimer
- * in the documentation and/or other materials provided with the
- * distribution.
- *
- * THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESS OR IMPLIED
- * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
- * MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN
- * NO EVENT SHALL CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
- * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,
- * BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS
- * OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
- * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR
- * TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE
- * USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH
- * DAMAGE.
- *
- * @category
- * @package     Services_JSON
- * @author      Michal Migurski <mike-json@teczno.com>
- * @author      Matt Knapp <mdknapp[at]gmail[dot]com>
- * @author      Brett Stimmerman <brettstimmerman[at]gmail[dot]com>
- * @copyright   2005 Michal Migurski
- * @version     CVS: $Id: JSON.php,v 1.31 2006/06/28 05:54:17 migurski Exp $
- * @license     http://www.opensource.org/licenses/bsd-license.php
- * @link        http://pear.php.net/pepr/pepr-proposal-show.php?id=198
- */
-
-/**
- * Marker constant for Services_JSON::decode(), used to flag stack state
- */
-define('SERVICES_JSON_SLICE',   1);
-
-/**
- * Marker constant for Services_JSON::decode(), used to flag stack state
- */
-define('SERVICES_JSON_IN_STR',  2);
-
-/**
- * Marker constant for Services_JSON::decode(), used to flag stack state
- */
-define('SERVICES_JSON_IN_ARR',  3);
-
-/**
- * Marker constant for Services_JSON::decode(), used to flag stack state
- */
-define('SERVICES_JSON_IN_OBJ',  4);
-
-/**
- * Marker constant for Services_JSON::decode(), used to flag stack state
- */
-define('SERVICES_JSON_IN_CMT', 5);
-
-/**
- * Behavior switch for Services_JSON::decode()
- */
-define('SERVICES_JSON_LOOSE_TYPE', 16);
-
-/**
- * Behavior switch for Services_JSON::decode()
- */
-define('SERVICES_JSON_SUPPRESS_ERRORS', 32);
-
-/**
- * Converts to and from JSON format.
- *
- * Brief example of use:
- *
- * <code>
- * // create a new instance of Services_JSON
- * $json = new Services_JSON();
- *
- * // convert a complexe value to JSON notation, and send it to the browser
- * $value = array('foo', 'bar', array(1, 2, 'baz'), array(3, array(4)));
- * $output = $json->encode($value);
- *
- * print($output);
- * // prints: ["foo","bar",[1,2,"baz"],[3,[4]]]
- *
- * // accept incoming POST data, assumed to be in JSON notation
- * $input = file_get_contents('php://input', 1000000);
- * $value = $json->decode($input);
- * </code>
- */
-class Services_JSON
-{
-   /**
-    * constructs a new JSON instance
-    *
-    * @param    int     $use    object behavior flags; combine with boolean-OR
-    *
-    *                           possible values:
-    *                           - SERVICES_JSON_LOOSE_TYPE:  loose typing.
-    *                                   "{...}" syntax creates associative arrays
-    *                                   instead of objects in decode().
-    *                           - SERVICES_JSON_SUPPRESS_ERRORS:  error suppression.
-    *                                   Values which can't be encoded (e.g. resources)
-    *                                   appear as NULL instead of throwing errors.
-    *                                   By default, a deeply-nested resource will
-    *                                   bubble up with an error, so all return values
-    *                                   from encode() should be checked with isError()
-    */
-    function Services_JSON($use = 0)
-    {
-        $this->use = $use;
-    }
-
-   /**
-    * convert a string from one UTF-16 char to one UTF-8 char
-    *
-    * Normally should be handled by mb_convert_encoding, but
-    * provides a slower PHP-only method for installations
-    * that lack the multibye string extension.
-    *
-    * @param    string  $utf16  UTF-16 character
-    * @return   string  UTF-8 character
-    * @access   private
-    */
-    function utf162utf8($utf16)
-    {
-        // oh please oh please oh please oh please oh please
-        if(function_exists('mb_convert_encoding')) {
-            return mb_convert_encoding($utf16, 'UTF-8', 'UTF-16');
-        }
-
-        $bytes = (ord($utf16{0}) << 8) | ord($utf16{1});
-
-        switch(true) {
-            case ((0x7F & $bytes) == $bytes):
-                // this case should never be reached, because we are in ASCII range
-                // see: http://www.cl.cam.ac.uk/~mgk25/unicode.html#utf-8
-                return chr(0x7F & $bytes);
-
-            case (0x07FF & $bytes) == $bytes:
-                // return a 2-byte UTF-8 character
-                // see: http://www.cl.cam.ac.uk/~mgk25/unicode.html#utf-8
-                return chr(0xC0 | (($bytes >> 6) & 0x1F))
-                     . chr(0x80 | ($bytes & 0x3F));
-
-            case (0xFFFF & $bytes) == $bytes:
-                // return a 3-byte UTF-8 character
-                // see: http://www.cl.cam.ac.uk/~mgk25/unicode.html#utf-8
-                return chr(0xE0 | (($bytes >> 12) & 0x0F))
-                     . chr(0x80 | (($bytes >> 6) & 0x3F))
-                     . chr(0x80 | ($bytes & 0x3F));
-        }
-
-        // ignoring UTF-32 for now, sorry
-        return '';
-    }
-
-   /**
-    * convert a string from one UTF-8 char to one UTF-16 char
-    *
-    * Normally should be handled by mb_convert_encoding, but
-    * provides a slower PHP-only method for installations
-    * that lack the multibye string extension.
-    *
-    * @param    string  $utf8   UTF-8 character
-    * @return   string  UTF-16 character
-    * @access   private
-    */
-    function utf82utf16($utf8)
-    {
-        // oh please oh please oh please oh please oh please
-        if(function_exists('mb_convert_encoding')) {
-            return mb_convert_encoding($utf8, 'UTF-16', 'UTF-8');
-        }
-
-        switch(strlen($utf8)) {
-            case 1:
-                // this case should never be reached, because we are in ASCII range
-                // see: http://www.cl.cam.ac.uk/~mgk25/unicode.html#utf-8
-                return $utf8;
-
-            case 2:
-                // return a UTF-16 character from a 2-byte UTF-8 char
-                // see: http://www.cl.cam.ac.uk/~mgk25/unicode.html#utf-8
-                return chr(0x07 & (ord($utf8{0}) >> 2))
-                     . chr((0xC0 & (ord($utf8{0}) << 6))
-                         | (0x3F & ord($utf8{1})));
-
-            case 3:
-                // return a UTF-16 character from a 3-byte UTF-8 char
-                // see: http://www.cl.cam.ac.uk/~mgk25/unicode.html#utf-8
-                return chr((0xF0 & (ord($utf8{0}) << 4))
-                         | (0x0F & (ord($utf8{1}) >> 2)))
-                     . chr((0xC0 & (ord($utf8{1}) << 6))
-                         | (0x7F & ord($utf8{2})));
-        }
-
-        // ignoring UTF-32 for now, sorry
-        return '';
-    }
-
-   /**
-    * encodes an arbitrary variable into JSON format
-    *
-    * @param    mixed   $var    any number, boolean, string, array, or object to be encoded.
-    *                           see argument 1 to Services_JSON() above for array-parsing behavior.
-    *                           if var is a strng, note that encode() always expects it
-    *                           to be in ASCII or UTF-8 format!
-    *
-    * @return   mixed   JSON string representation of input var or an error if a problem occurs
-    * @access   public
-    */
-    function encode($var)
-    {
-        switch (gettype($var)) {
-            case 'boolean':
-                return $var ? 'true' : 'false';
-
-            case 'NULL':
-                return 'null';
-
-            case 'integer':
-                return (int) $var;
-
-            case 'double':
-            case 'float':
-                return (float) $var;
-
-            case 'string':
-                // STRINGS ARE EXPECTED TO BE IN ASCII OR UTF-8 FORMAT
-                $ascii = '';
-                $strlen_var = strlen($var);
-
-               /*
-                * Iterate over every character in the string,
-                * escaping with a slash or encoding to UTF-8 where necessary
-                */
-                for ($c = 0; $c < $strlen_var; ++$c) {
-
-                    $ord_var_c = ord($var{$c});
-
-                    switch (true) {
-                        case $ord_var_c == 0x08:
-                            $ascii .= '\b';
-                            break;
-                        case $ord_var_c == 0x09:
-                            $ascii .= '\t';
-                            break;
-                        case $ord_var_c == 0x0A:
-                            $ascii .= '\n';
-                            break;
-                        case $ord_var_c == 0x0C:
-                            $ascii .= '\f';
-                            break;
-                        case $ord_var_c == 0x0D:
-                            $ascii .= '\r';
-                            break;
-
-                        case $ord_var_c == 0x22:
-                        case $ord_var_c == 0x2F:
-                        case $ord_var_c == 0x5C:
-                            // double quote, slash, slosh
-                            $ascii .= '\\'.$var{$c};
-                            break;
-
-                        case (($ord_var_c >= 0x20) && ($ord_var_c <= 0x7F)):
-                            // characters U-00000000 - U-0000007F (same as ASCII)
-                            $ascii .= $var{$c};
-                            break;
-
-                        case (($ord_var_c & 0xE0) == 0xC0):
-                            // characters U-00000080 - U-000007FF, mask 110XXXXX
-                            // see http://www.cl.cam.ac.uk/~mgk25/unicode.html#utf-8
-                            $char = pack('C*', $ord_var_c, ord($var{$c + 1}));
-                            $c += 1;
-                            $utf16 = $this->utf82utf16($char);
-                            $ascii .= sprintf('\u%04s', bin2hex($utf16));
-                            break;
-
-                        case (($ord_var_c & 0xF0) == 0xE0):
-                            // characters U-00000800 - U-0000FFFF, mask 1110XXXX
-                            // see http://www.cl.cam.ac.uk/~mgk25/unicode.html#utf-8
-                            $char = pack('C*', $ord_var_c,
-                                         ord($var{$c + 1}),
-                                         ord($var{$c + 2}));
-                            $c += 2;
-                            $utf16 = $this->utf82utf16($char);
-                            $ascii .= sprintf('\u%04s', bin2hex($utf16));
-                            break;
-
-                        case (($ord_var_c & 0xF8) == 0xF0):
-                            // characters U-00010000 - U-001FFFFF, mask 11110XXX
-                            // see http://www.cl.cam.ac.uk/~mgk25/unicode.html#utf-8
-                            $char = pack('C*', $ord_var_c,
-                                         ord($var{$c + 1}),
-                                         ord($var{$c + 2}),
-                                         ord($var{$c + 3}));
-                            $c += 3;
-                            $utf16 = $this->utf82utf16($char);
-                            $ascii .= sprintf('\u%04s', bin2hex($utf16));
-                            break;
-
-                        case (($ord_var_c & 0xFC) == 0xF8):
-                            // characters U-00200000 - U-03FFFFFF, mask 111110XX
-                            // see http://www.cl.cam.ac.uk/~mgk25/unicode.html#utf-8
-                            $char = pack('C*', $ord_var_c,
-                                         ord($var{$c + 1}),
-                                         ord($var{$c + 2}),
-                                         ord($var{$c + 3}),
-                                         ord($var{$c + 4}));
-                            $c += 4;
-                            $utf16 = $this->utf82utf16($char);
-                            $ascii .= sprintf('\u%04s', bin2hex($utf16));
-                            break;
-
-                        case (($ord_var_c & 0xFE) == 0xFC):
-                            // characters U-04000000 - U-7FFFFFFF, mask 1111110X
-                            // see http://www.cl.cam.ac.uk/~mgk25/unicode.html#utf-8
-                            $char = pack('C*', $ord_var_c,
-                                         ord($var{$c + 1}),
-                                         ord($var{$c + 2}),
-                                         ord($var{$c + 3}),
-                                         ord($var{$c + 4}),
-                                         ord($var{$c + 5}));
-                            $c += 5;
-                            $utf16 = $this->utf82utf16($char);
-                            $ascii .= sprintf('\u%04s', bin2hex($utf16));
-                            break;
-                    }
-                }
-
-                return '"'.$ascii.'"';
-
-            case 'array':
-               /*
-                * As per JSON spec if any array key is not an integer
-                * we must treat the the whole array as an object. We
-                * also try to catch a sparsely populated associative
-                * array with numeric keys here because some JS engines
-                * will create an array with empty indexes up to
-                * max_index which can cause memory issues and because
-                * the keys, which may be relevant, will be remapped
-                * otherwise.
-                *
-                * As per the ECMA and JSON specification an object may
-                * have any string as a property. Unfortunately due to
-                * a hole in the ECMA specification if the key is a
-                * ECMA reserved word or starts with a digit the
-                * parameter is only accessible using ECMAScript's
-                * bracket notation.
-                */
-
-                // treat as a JSON object
-                if (is_array($var) && count($var) && (array_keys($var) !== range(0, sizeof($var) - 1))) {
-                    $properties = array_map(array($this, 'name_value'),
-                                            array_keys($var),
-                                            array_values($var));
-
-                    foreach($properties as $property) {
-                        if(Services_JSON::isError($property)) {
-                            return $property;
-                        }
-                    }
-
-                    return '{' . join(',', $properties) . '}';
-                }
-
-                // treat it like a regular array
-                $elements = array_map(array($this, 'encode'), $var);
-
-                foreach($elements as $element) {
-                    if(Services_JSON::isError($element)) {
-                        return $element;
-                    }
-                }
-
-                return '[' . join(',', $elements) . ']';
-
-            case 'object':
-                $vars = get_object_vars($var);
-
-                $properties = array_map(array($this, 'name_value'),
-                                        array_keys($vars),
-                                        array_values($vars));
-
-                foreach($properties as $property) {
-                    if(Services_JSON::isError($property)) {
-                        return $property;
-                    }
-                }
-
-                return '{' . join(',', $properties) . '}';
-
-            default:
-                return ($this->use & SERVICES_JSON_SUPPRESS_ERRORS)
-                    ? 'null'
-                    : new Services_JSON_Error(gettype($var)." can not be encoded as JSON string");
-        }
-    }
-
-   /**
-    * array-walking function for use in generating JSON-formatted name-value pairs
-    *
-    * @param    string  $name   name of key to use
-    * @param    mixed   $value  reference to an array element to be encoded
-    *
-    * @return   string  JSON-formatted name-value pair, like '"name":value'
-    * @access   private
-    */
-    function name_value($name, $value)
-    {
-        $encoded_value = $this->encode($value);
-
-        if(Services_JSON::isError($encoded_value)) {
-            return $encoded_value;
-        }
-
-        return $this->encode(strval($name)) . ':' . $encoded_value;
-    }
-
-   /**
-    * reduce a string by removing leading and trailing comments and whitespace
-    *
-    * @param    $str    string      string value to strip of comments and whitespace
-    *
-    * @return   string  string value stripped of comments and whitespace
-    * @access   private
-    */
-    function reduce_string($str)
-    {
-        $str = preg_replace(array(
-
-                // eliminate single line comments in '// ...' form
-                '#^\s*//(.+)$#m',
-
-                // eliminate multi-line comments in '/* ... */' form, at start of string
-                '#^\s*/\*(.+)\*/#Us',
-
-                // eliminate multi-line comments in '/* ... */' form, at end of string
-                '#/\*(.+)\*/\s*$#Us'
-
-            ), '', $str);
-
-        // eliminate extraneous space
-        return trim($str);
-    }
-
-   /**
-    * decodes a JSON string into appropriate variable
-    *
-    * @param    string  $str    JSON-formatted string
-    *
-    * @return   mixed   number, boolean, string, array, or object
-    *                   corresponding to given JSON input string.
-    *                   See argument 1 to Services_JSON() above for object-output behavior.
-    *                   Note that decode() always returns strings
-    *                   in ASCII or UTF-8 format!
-    * @access   public
-    */
-    function decode($str)
-    {
-        $str = $this->reduce_string($str);
-
-        switch (strtolower($str)) {
-            case 'true':
-                return true;
-
-            case 'false':
-                return false;
-
-            case 'null':
-                return null;
-
-            default:
-                $m = array();
-
-                if (is_numeric($str)) {
-                    // Lookie-loo, it's a number
-
-                    // This would work on its own, but I'm trying to be
-                    // good about returning integers where appropriate:
-                    // return (float)$str;
-
-                    // Return float or int, as appropriate
-                    return ((float)$str == (integer)$str)
-                        ? (integer)$str
-                        : (float)$str;
-
-                } elseif (preg_match('/^("|\').*(\1)$/s', $str, $m) && $m[1] == $m[2]) {
-                    // STRINGS RETURNED IN UTF-8 FORMAT
-                    $delim = substr($str, 0, 1);
-                    $chrs = substr($str, 1, -1);
-                    $utf8 = '';
-                    $strlen_chrs = strlen($chrs);
-
-                    for ($c = 0; $c < $strlen_chrs; ++$c) {
-
-                        $substr_chrs_c_2 = substr($chrs, $c, 2);
-                        $ord_chrs_c = ord($chrs{$c});
-
-                        switch (true) {
-                            case $substr_chrs_c_2 == '\b':
-                                $utf8 .= chr(0x08);
-                                ++$c;
-                                break;
-                            case $substr_chrs_c_2 == '\t':
-                                $utf8 .= chr(0x09);
-                                ++$c;
-                                break;
-                            case $substr_chrs_c_2 == '\n':
-                                $utf8 .= chr(0x0A);
-                                ++$c;
-                                break;
-                            case $substr_chrs_c_2 == '\f':
-                                $utf8 .= chr(0x0C);
-                                ++$c;
-                                break;
-                            case $substr_chrs_c_2 == '\r':
-                                $utf8 .= chr(0x0D);
-                                ++$c;
-                                break;
-
-                            case $substr_chrs_c_2 == '\\"':
-                            case $substr_chrs_c_2 == '\\\'':
-                            case $substr_chrs_c_2 == '\\\\':
-                            case $substr_chrs_c_2 == '\\/':
-                                if (($delim == '"' && $substr_chrs_c_2 != '\\\'') ||
-                                   ($delim == "'" && $substr_chrs_c_2 != '\\"')) {
-                                    $utf8 .= $chrs{++$c};
-                                }
-                                break;
-
-                            case preg_match('/\\\u[0-9A-F]{4}/i', substr($chrs, $c, 6)):
-                                // single, escaped unicode character
-                                $utf16 = chr(hexdec(substr($chrs, ($c + 2), 2)))
-                                       . chr(hexdec(substr($chrs, ($c + 4), 2)));
-                                $utf8 .= $this->utf162utf8($utf16);
-                                $c += 5;
-                                break;
-
-                            case ($ord_chrs_c >= 0x20) && ($ord_chrs_c <= 0x7F):
-                                $utf8 .= $chrs{$c};
-                                break;
-
-                            case ($ord_chrs_c & 0xE0) == 0xC0:
-                                // characters U-00000080 - U-000007FF, mask 110XXXXX
-                                //see http://www.cl.cam.ac.uk/~mgk25/unicode.html#utf-8
-                                $utf8 .= substr($chrs, $c, 2);
-                                ++$c;
-                                break;
-
-                            case ($ord_chrs_c & 0xF0) == 0xE0:
-                                // characters U-00000800 - U-0000FFFF, mask 1110XXXX
-                                // see http://www.cl.cam.ac.uk/~mgk25/unicode.html#utf-8
-                                $utf8 .= substr($chrs, $c, 3);
-                                $c += 2;
-                                break;
-
-                            case ($ord_chrs_c & 0xF8) == 0xF0:
-                                // characters U-00010000 - U-001FFFFF, mask 11110XXX
-                                // see http://www.cl.cam.ac.uk/~mgk25/unicode.html#utf-8
-                                $utf8 .= substr($chrs, $c, 4);
-                                $c += 3;
-                                break;
-
-                            case ($ord_chrs_c & 0xFC) == 0xF8:
-                                // characters U-00200000 - U-03FFFFFF, mask 111110XX
-                                // see http://www.cl.cam.ac.uk/~mgk25/unicode.html#utf-8
-                                $utf8 .= substr($chrs, $c, 5);
-                                $c += 4;
-                                break;
-
-                            case ($ord_chrs_c & 0xFE) == 0xFC:
-                                // characters U-04000000 - U-7FFFFFFF, mask 1111110X
-                                // see http://www.cl.cam.ac.uk/~mgk25/unicode.html#utf-8
-                                $utf8 .= substr($chrs, $c, 6);
-                                $c += 5;
-                                break;
-
-                        }
-
-                    }
-
-                    return $utf8;
-
-                } elseif (preg_match('/^\[.*\]$/s', $str) || preg_match('/^\{.*\}$/s', $str)) {
-                    // array, or object notation
-
-                    if ($str{0} == '[') {
-                        $stk = array(SERVICES_JSON_IN_ARR);
-                        $arr = array();
-                    } else {
-                        if ($this->use & SERVICES_JSON_LOOSE_TYPE) {
-                            $stk = array(SERVICES_JSON_IN_OBJ);
-                            $obj = array();
-                        } else {
-                            $stk = array(SERVICES_JSON_IN_OBJ);
-                            $obj = new stdClass();
-                        }
-                    }
-
-                    array_push($stk, array('what'  => SERVICES_JSON_SLICE,
-                                           'where' => 0,
-                                           'delim' => false));
-
-                    $chrs = substr($str, 1, -1);
-                    $chrs = $this->reduce_string($chrs);
-
-                    if ($chrs == '') {
-                        if (reset($stk) == SERVICES_JSON_IN_ARR) {
-                            return $arr;
-
-                        } else {
-                            return $obj;
-
-                        }
-                    }
-
-                    //print("\nparsing {$chrs}\n");
-
-                    $strlen_chrs = strlen($chrs);
-
-                    for ($c = 0; $c <= $strlen_chrs; ++$c) {
-
-                        $top = end($stk);
-                        $substr_chrs_c_2 = substr($chrs, $c, 2);
-
-                        if (($c == $strlen_chrs) || (($chrs{$c} == ',') && ($top['what'] == SERVICES_JSON_SLICE))) {
-                            // found a comma that is not inside a string, array, etc.,
-                            // OR we've reached the end of the character list
-                            $slice = substr($chrs, $top['where'], ($c - $top['where']));
-                            array_push($stk, array('what' => SERVICES_JSON_SLICE, 'where' => ($c + 1), 'delim' => false));
-                            //print("Found split at {$c}: ".substr($chrs, $top['where'], (1 + $c - $top['where']))."\n");
-
-                            if (reset($stk) == SERVICES_JSON_IN_ARR) {
-                                // we are in an array, so just push an element onto the stack
-                                array_push($arr, $this->decode($slice));
-
-                            } elseif (reset($stk) == SERVICES_JSON_IN_OBJ) {
-                                // we are in an object, so figure
-                                // out the property name and set an
-                                // element in an associative array,
-                                // for now
-                                $parts = array();
-                                
-                                if (preg_match('/^\s*(["\'].*[^\\\]["\'])\s*:\s*(\S.*),?$/Uis', $slice, $parts)) {
-                                    // "name":value pair
-                                    $key = $this->decode($parts[1]);
-                                    $val = $this->decode($parts[2]);
-
-                                    if ($this->use & SERVICES_JSON_LOOSE_TYPE) {
-                                        $obj[$key] = $val;
-                                    } else {
-                                        $obj->$key = $val;
-                                    }
-                                } elseif (preg_match('/^\s*(\w+)\s*:\s*(\S.*),?$/Uis', $slice, $parts)) {
-                                    // name:value pair, where name is unquoted
-                                    $key = $parts[1];
-                                    $val = $this->decode($parts[2]);
-
-                                    if ($this->use & SERVICES_JSON_LOOSE_TYPE) {
-                                        $obj[$key] = $val;
-                                    } else {
-                                        $obj->$key = $val;
-                                    }
-                                }
-
-                            }
-
-                        } elseif ((($chrs{$c} == '"') || ($chrs{$c} == "'")) && ($top['what'] != SERVICES_JSON_IN_STR)) {
-                            // found a quote, and we are not inside a string
-                            array_push($stk, array('what' => SERVICES_JSON_IN_STR, 'where' => $c, 'delim' => $chrs{$c}));
-                            //print("Found start of string at {$c}\n");
-
-                        } elseif (($chrs{$c} == $top['delim']) &&
-                                 ($top['what'] == SERVICES_JSON_IN_STR) &&
-                                 ((strlen(substr($chrs, 0, $c)) - strlen(rtrim(substr($chrs, 0, $c), '\\'))) % 2 != 1)) {
-                            // found a quote, we're in a string, and it's not escaped
-                            // we know that it's not escaped becase there is _not_ an
-                            // odd number of backslashes at the end of the string so far
-                            array_pop($stk);
-                            //print("Found end of string at {$c}: ".substr($chrs, $top['where'], (1 + 1 + $c - $top['where']))."\n");
-
-                        } elseif (($chrs{$c} == '[') &&
-                                 in_array($top['what'], array(SERVICES_JSON_SLICE, SERVICES_JSON_IN_ARR, SERVICES_JSON_IN_OBJ))) {
-                            // found a left-bracket, and we are in an array, object, or slice
-                            array_push($stk, array('what' => SERVICES_JSON_IN_ARR, 'where' => $c, 'delim' => false));
-                            //print("Found start of array at {$c}\n");
-
-                        } elseif (($chrs{$c} == ']') && ($top['what'] == SERVICES_JSON_IN_ARR)) {
-                            // found a right-bracket, and we're in an array
-                            array_pop($stk);
-                            //print("Found end of array at {$c}: ".substr($chrs, $top['where'], (1 + $c - $top['where']))."\n");
-
-                        } elseif (($chrs{$c} == '{') &&
-                                 in_array($top['what'], array(SERVICES_JSON_SLICE, SERVICES_JSON_IN_ARR, SERVICES_JSON_IN_OBJ))) {
-                            // found a left-brace, and we are in an array, object, or slice
-                            array_push($stk, array('what' => SERVICES_JSON_IN_OBJ, 'where' => $c, 'delim' => false));
-                            //print("Found start of object at {$c}\n");
-
-                        } elseif (($chrs{$c} == '}') && ($top['what'] == SERVICES_JSON_IN_OBJ)) {
-                            // found a right-brace, and we're in an object
-                            array_pop($stk);
-                            //print("Found end of object at {$c}: ".substr($chrs, $top['where'], (1 + $c - $top['where']))."\n");
-
-                        } elseif (($substr_chrs_c_2 == '/*') &&
-                                 in_array($top['what'], array(SERVICES_JSON_SLICE, SERVICES_JSON_IN_ARR, SERVICES_JSON_IN_OBJ))) {
-                            // found a comment start, and we are in an array, object, or slice
-                            array_push($stk, array('what' => SERVICES_JSON_IN_CMT, 'where' => $c, 'delim' => false));
-                            $c++;
-                            //print("Found start of comment at {$c}\n");
-
-                        } elseif (($substr_chrs_c_2 == '*/') && ($top['what'] == SERVICES_JSON_IN_CMT)) {
-                            // found a comment end, and we're in one now
-                            array_pop($stk);
-                            $c++;
-
-                            for ($i = $top['where']; $i <= $c; ++$i)
-                                $chrs = substr_replace($chrs, ' ', $i, 1);
-
-                            //print("Found end of comment at {$c}: ".substr($chrs, $top['where'], (1 + $c - $top['where']))."\n");
-
-                        }
-
-                    }
-
-                    if (reset($stk) == SERVICES_JSON_IN_ARR) {
-                        return $arr;
-
-                    } elseif (reset($stk) == SERVICES_JSON_IN_OBJ) {
-                        return $obj;
-
-                    }
-
-                }
-        }
-    }
-
-    /**
-     * @todo Ultimately, this should just call PEAR::isError()
-     */
-    function isError($data, $code = null)
-    {
-        if (class_exists('pear')) {
-            return PEAR::isError($data, $code);
-        } elseif (is_object($data) && (get_class($data) == 'services_json_error' ||
-                                 is_subclass_of($data, 'services_json_error'))) {
-            return true;
-        }
-
-        return false;
-    }
-}
-
-if (class_exists('PEAR_Error')) {
-
-    class Services_JSON_Error extends PEAR_Error
-    {
-        function Services_JSON_Error($message = 'unknown error', $code = null,
-                                     $mode = null, $options = null, $userinfo = null)
-        {
-            parent::PEAR_Error($message, $code, $mode, $options, $userinfo);
-        }
-    }
-
-} else {
-
-    /**
-     * @todo Ultimately, this class shall be descended from PEAR_Error
-     */
-    class Services_JSON_Error
-    {
-        function Services_JSON_Error($message = 'unknown error', $code = null,
-                                     $mode = null, $options = null, $userinfo = null)
-        {
-
-        }
-    }
-
-}
-    
-?>
+<?php
+/* vim: set expandtab tabstop=4 shiftwidth=4 softtabstop=4: */
+
+/**
+ * Converts to and from JSON format.
+ *
+ * JSON (JavaScript Object Notation) is a lightweight data-interchange
+ * format. It is easy for humans to read and write. It is easy for machines
+ * to parse and generate. It is based on a subset of the JavaScript
+ * Programming Language, Standard ECMA-262 3rd Edition - December 1999.
+ * This feature can also be found in  Python. JSON is a text format that is
+ * completely language independent but uses conventions that are familiar
+ * to programmers of the C-family of languages, including C, C++, C#, Java,
+ * JavaScript, Perl, TCL, and many others. These properties make JSON an
+ * ideal data-interchange language.
+ *
+ * This package provides a simple encoder and decoder for JSON notation. It
+ * is intended for use with client-side Javascript applications that make
+ * use of HTTPRequest to perform server communication functions - data can
+ * be encoded into JSON notation for use in a client-side javascript, or
+ * decoded from incoming Javascript requests. JSON format is native to
+ * Javascript, and can be directly eval()'ed with no further parsing
+ * overhead
+ *
+ * All strings should be in ASCII or UTF-8 format!
+ *
+ * LICENSE: Redistribution and use in source and binary forms, with or
+ * without modification, are permitted provided that the following
+ * conditions are met: Redistributions of source code must retain the
+ * above copyright notice, this list of conditions and the following
+ * disclaimer. Redistributions in binary form must reproduce the above
+ * copyright notice, this list of conditions and the following disclaimer
+ * in the documentation and/or other materials provided with the
+ * distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESS OR IMPLIED
+ * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
+ * MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN
+ * NO EVENT SHALL CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
+ * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,
+ * BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS
+ * OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
+ * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR
+ * TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE
+ * USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH
+ * DAMAGE.
+ *
+ * @category
+ * @package     Services_JSON
+ * @author      Michal Migurski <mike-json@teczno.com>
+ * @author      Matt Knapp <mdknapp[at]gmail[dot]com>
+ * @author      Brett Stimmerman <brettstimmerman[at]gmail[dot]com>
+ * @copyright   2005 Michal Migurski
+ * @version     CVS: $Id: JSON.php,v 1.31 2006/06/28 05:54:17 migurski Exp $
+ * @license     http://www.opensource.org/licenses/bsd-license.php
+ * @link        http://pear.php.net/pepr/pepr-proposal-show.php?id=198
+ */
+
+/**
+ * Marker constant for Services_JSON::decode(), used to flag stack state
+ */
+define('SERVICES_JSON_SLICE',   1);
+
+/**
+ * Marker constant for Services_JSON::decode(), used to flag stack state
+ */
+define('SERVICES_JSON_IN_STR',  2);
+
+/**
+ * Marker constant for Services_JSON::decode(), used to flag stack state
+ */
+define('SERVICES_JSON_IN_ARR',  3);
+
+/**
+ * Marker constant for Services_JSON::decode(), used to flag stack state
+ */
+define('SERVICES_JSON_IN_OBJ',  4);
+
+/**
+ * Marker constant for Services_JSON::decode(), used to flag stack state
+ */
+define('SERVICES_JSON_IN_CMT', 5);
+
+/**
+ * Behavior switch for Services_JSON::decode()
+ */
+define('SERVICES_JSON_LOOSE_TYPE', 16);
+
+/**
+ * Behavior switch for Services_JSON::decode()
+ */
+define('SERVICES_JSON_SUPPRESS_ERRORS', 32);
+
+/**
+ * Converts to and from JSON format.
+ *
+ * Brief example of use:
+ *
+ * <code>
+ * // create a new instance of Services_JSON
+ * $json = new Services_JSON();
+ *
+ * // convert a complexe value to JSON notation, and send it to the browser
+ * $value = array('foo', 'bar', array(1, 2, 'baz'), array(3, array(4)));
+ * $output = $json->encode($value);
+ *
+ * print($output);
+ * // prints: ["foo","bar",[1,2,"baz"],[3,[4]]]
+ *
+ * // accept incoming POST data, assumed to be in JSON notation
+ * $input = file_get_contents('php://input', 1000000);
+ * $value = $json->decode($input);
+ * </code>
+ */
+class Services_JSON
+{
+   /**
+    * constructs a new JSON instance
+    *
+    * @param    int     $use    object behavior flags; combine with boolean-OR
+    *
+    *                           possible values:
+    *                           - SERVICES_JSON_LOOSE_TYPE:  loose typing.
+    *                                   "{...}" syntax creates associative arrays
+    *                                   instead of objects in decode().
+    *                           - SERVICES_JSON_SUPPRESS_ERRORS:  error suppression.
+    *                                   Values which can't be encoded (e.g. resources)
+    *                                   appear as NULL instead of throwing errors.
+    *                                   By default, a deeply-nested resource will
+    *                                   bubble up with an error, so all return values
+    *                                   from encode() should be checked with isError()
+    */
+    function Services_JSON($use = 0)
+    {
+        $this->use = $use;
+    }
+
+   /**
+    * convert a string from one UTF-16 char to one UTF-8 char
+    *
+    * Normally should be handled by mb_convert_encoding, but
+    * provides a slower PHP-only method for installations
+    * that lack the multibye string extension.
+    *
+    * @param    string  $utf16  UTF-16 character
+    * @return   string  UTF-8 character
+    * @access   private
+    */
+    function utf162utf8($utf16)
+    {
+        // oh please oh please oh please oh please oh please
+        if(function_exists('mb_convert_encoding')) {
+            return mb_convert_encoding($utf16, 'UTF-8', 'UTF-16');
+        }
+
+        $bytes = (ord($utf16{0}) << 8) | ord($utf16{1});
+
+        switch(true) {
+            case ((0x7F & $bytes) == $bytes):
+                // this case should never be reached, because we are in ASCII range
+                // see: http://www.cl.cam.ac.uk/~mgk25/unicode.html#utf-8
+                return chr(0x7F & $bytes);
+
+            case (0x07FF & $bytes) == $bytes:
+                // return a 2-byte UTF-8 character
+                // see: http://www.cl.cam.ac.uk/~mgk25/unicode.html#utf-8
+                return chr(0xC0 | (($bytes >> 6) & 0x1F))
+                     . chr(0x80 | ($bytes & 0x3F));
+
+            case (0xFFFF & $bytes) == $bytes:
+                // return a 3-byte UTF-8 character
+                // see: http://www.cl.cam.ac.uk/~mgk25/unicode.html#utf-8
+                return chr(0xE0 | (($bytes >> 12) & 0x0F))
+                     . chr(0x80 | (($bytes >> 6) & 0x3F))
+                     . chr(0x80 | ($bytes & 0x3F));
+        }
+
+        // ignoring UTF-32 for now, sorry
+        return '';
+    }
+
+   /**
+    * convert a string from one UTF-8 char to one UTF-16 char
+    *
+    * Normally should be handled by mb_convert_encoding, but
+    * provides a slower PHP-only method for installations
+    * that lack the multibye string extension.
+    *
+    * @param    string  $utf8   UTF-8 character
+    * @return   string  UTF-16 character
+    * @access   private
+    */
+    function utf82utf16($utf8)
+    {
+        // oh please oh please oh please oh please oh please
+        if(function_exists('mb_convert_encoding')) {
+            return mb_convert_encoding($utf8, 'UTF-16', 'UTF-8');
+        }
+
+        switch(strlen($utf8)) {
+            case 1:
+                // this case should never be reached, because we are in ASCII range
+                // see: http://www.cl.cam.ac.uk/~mgk25/unicode.html#utf-8
+                return $utf8;
+
+            case 2:
+                // return a UTF-16 character from a 2-byte UTF-8 char
+                // see: http://www.cl.cam.ac.uk/~mgk25/unicode.html#utf-8
+                return chr(0x07 & (ord($utf8{0}) >> 2))
+                     . chr((0xC0 & (ord($utf8{0}) << 6))
+                         | (0x3F & ord($utf8{1})));
+
+            case 3:
+                // return a UTF-16 character from a 3-byte UTF-8 char
+                // see: http://www.cl.cam.ac.uk/~mgk25/unicode.html#utf-8
+                return chr((0xF0 & (ord($utf8{0}) << 4))
+                         | (0x0F & (ord($utf8{1}) >> 2)))
+                     . chr((0xC0 & (ord($utf8{1}) << 6))
+                         | (0x7F & ord($utf8{2})));
+        }
+
+        // ignoring UTF-32 for now, sorry
+        return '';
+    }
+
+   /**
+    * encodes an arbitrary variable into JSON format
+    *
+    * @param    mixed   $var    any number, boolean, string, array, or object to be encoded.
+    *                           see argument 1 to Services_JSON() above for array-parsing behavior.
+    *                           if var is a strng, note that encode() always expects it
+    *                           to be in ASCII or UTF-8 format!
+    *
+    * @return   mixed   JSON string representation of input var or an error if a problem occurs
+    * @access   public
+    */
+    function encode($var)
+    {
+        switch (gettype($var)) {
+            case 'boolean':
+                return $var ? 'true' : 'false';
+
+            case 'NULL':
+                return 'null';
+
+            case 'integer':
+                return (int) $var;
+
+            case 'double':
+            case 'float':
+                return (float) $var;
+
+            case 'string':
+                // STRINGS ARE EXPECTED TO BE IN ASCII OR UTF-8 FORMAT
+                $ascii = '';
+                $strlen_var = strlen($var);
+
+               /*
+                * Iterate over every character in the string,
+                * escaping with a slash or encoding to UTF-8 where necessary
+                */
+                for ($c = 0; $c < $strlen_var; ++$c) {
+
+                    $ord_var_c = ord($var{$c});
+
+                    switch (true) {
+                        case $ord_var_c == 0x08:
+                            $ascii .= '\b';
+                            break;
+                        case $ord_var_c == 0x09:
+                            $ascii .= '\t';
+                            break;
+                        case $ord_var_c == 0x0A:
+                            $ascii .= '\n';
+                            break;
+                        case $ord_var_c == 0x0C:
+                            $ascii .= '\f';
+                            break;
+                        case $ord_var_c == 0x0D:
+                            $ascii .= '\r';
+                            break;
+
+                        case $ord_var_c == 0x22:
+                        case $ord_var_c == 0x2F:
+                        case $ord_var_c == 0x5C:
+                            // double quote, slash, slosh
+                            $ascii .= '\\'.$var{$c};
+                            break;
+
+                        case (($ord_var_c >= 0x20) && ($ord_var_c <= 0x7F)):
+                            // characters U-00000000 - U-0000007F (same as ASCII)
+                            $ascii .= $var{$c};
+                            break;
+
+                        case (($ord_var_c & 0xE0) == 0xC0):
+                            // characters U-00000080 - U-000007FF, mask 110XXXXX
+                            // see http://www.cl.cam.ac.uk/~mgk25/unicode.html#utf-8
+                            $char = pack('C*', $ord_var_c, ord($var{$c + 1}));
+                            $c += 1;
+                            $utf16 = $this->utf82utf16($char);
+                            $ascii .= sprintf('\u%04s', bin2hex($utf16));
+                            break;
+
+                        case (($ord_var_c & 0xF0) == 0xE0):
+                            // characters U-00000800 - U-0000FFFF, mask 1110XXXX
+                            // see http://www.cl.cam.ac.uk/~mgk25/unicode.html#utf-8
+                            $char = pack('C*', $ord_var_c,
+                                         ord($var{$c + 1}),
+                                         ord($var{$c + 2}));
+                            $c += 2;
+                            $utf16 = $this->utf82utf16($char);
+                            $ascii .= sprintf('\u%04s', bin2hex($utf16));
+                            break;
+
+                        case (($ord_var_c & 0xF8) == 0xF0):
+                            // characters U-00010000 - U-001FFFFF, mask 11110XXX
+                            // see http://www.cl.cam.ac.uk/~mgk25/unicode.html#utf-8
+                            $char = pack('C*', $ord_var_c,
+                                         ord($var{$c + 1}),
+                                         ord($var{$c + 2}),
+                                         ord($var{$c + 3}));
+                            $c += 3;
+                            $utf16 = $this->utf82utf16($char);
+                            $ascii .= sprintf('\u%04s', bin2hex($utf16));
+                            break;
+
+                        case (($ord_var_c & 0xFC) == 0xF8):
+                            // characters U-00200000 - U-03FFFFFF, mask 111110XX
+                            // see http://www.cl.cam.ac.uk/~mgk25/unicode.html#utf-8
+                            $char = pack('C*', $ord_var_c,
+                                         ord($var{$c + 1}),
+                                         ord($var{$c + 2}),
+                                         ord($var{$c + 3}),
+                                         ord($var{$c + 4}));
+                            $c += 4;
+                            $utf16 = $this->utf82utf16($char);
+                            $ascii .= sprintf('\u%04s', bin2hex($utf16));
+                            break;
+
+                        case (($ord_var_c & 0xFE) == 0xFC):
+                            // characters U-04000000 - U-7FFFFFFF, mask 1111110X
+                            // see http://www.cl.cam.ac.uk/~mgk25/unicode.html#utf-8
+                            $char = pack('C*', $ord_var_c,
+                                         ord($var{$c + 1}),
+                                         ord($var{$c + 2}),
+                                         ord($var{$c + 3}),
+                                         ord($var{$c + 4}),
+                                         ord($var{$c + 5}));
+                            $c += 5;
+                            $utf16 = $this->utf82utf16($char);
+                            $ascii .= sprintf('\u%04s', bin2hex($utf16));
+                            break;
+                    }
+                }
+
+                return '"'.$ascii.'"';
+
+            case 'array':
+               /*
+                * As per JSON spec if any array key is not an integer
+                * we must treat the the whole array as an object. We
+                * also try to catch a sparsely populated associative
+                * array with numeric keys here because some JS engines
+                * will create an array with empty indexes up to
+                * max_index which can cause memory issues and because
+                * the keys, which may be relevant, will be remapped
+                * otherwise.
+                *
+                * As per the ECMA and JSON specification an object may
+                * have any string as a property. Unfortunately due to
+                * a hole in the ECMA specification if the key is a
+                * ECMA reserved word or starts with a digit the
+                * parameter is only accessible using ECMAScript's
+                * bracket notation.
+                */
+
+                // treat as a JSON object
+                if (is_array($var) && count($var) && (array_keys($var) !== range(0, sizeof($var) - 1))) {
+                    $properties = array_map(array($this, 'name_value'),
+                                            array_keys($var),
+                                            array_values($var));
+
+                    foreach($properties as $property) {
+                        if(Services_JSON::isError($property)) {
+                            return $property;
+                        }
+                    }
+
+                    return '{' . join(',', $properties) . '}';
+                }
+
+                // treat it like a regular array
+                $elements = array_map(array($this, 'encode'), $var);
+
+                foreach($elements as $element) {
+                    if(Services_JSON::isError($element)) {
+                        return $element;
+                    }
+                }
+
+                return '[' . join(',', $elements) . ']';
+
+            case 'object':
+                $vars = get_object_vars($var);
+
+                $properties = array_map(array($this, 'name_value'),
+                                        array_keys($vars),
+                                        array_values($vars));
+
+                foreach($properties as $property) {
+                    if(Services_JSON::isError($property)) {
+                        return $property;
+                    }
+                }
+
+                return '{' . join(',', $properties) . '}';
+
+            default:
+                return ($this->use & SERVICES_JSON_SUPPRESS_ERRORS)
+                    ? 'null'
+                    : new Services_JSON_Error(gettype($var)." can not be encoded as JSON string");
+        }
+    }
+
+   /**
+    * array-walking function for use in generating JSON-formatted name-value pairs
+    *
+    * @param    string  $name   name of key to use
+    * @param    mixed   $value  reference to an array element to be encoded
+    *
+    * @return   string  JSON-formatted name-value pair, like '"name":value'
+    * @access   private
+    */
+    function name_value($name, $value)
+    {
+        $encoded_value = $this->encode($value);
+
+        if(Services_JSON::isError($encoded_value)) {
+            return $encoded_value;
+        }
+
+        return $this->encode(strval($name)) . ':' . $encoded_value;
+    }
+
+   /**
+    * reduce a string by removing leading and trailing comments and whitespace
+    *
+    * @param    $str    string      string value to strip of comments and whitespace
+    *
+    * @return   string  string value stripped of comments and whitespace
+    * @access   private
+    */
+    function reduce_string($str)
+    {
+        $str = preg_replace(array(
+
+                // eliminate single line comments in '// ...' form
+                '#^\s*//(.+)$#m',
+
+                // eliminate multi-line comments in '/* ... */' form, at start of string
+                '#^\s*/\*(.+)\*/#Us',
+
+                // eliminate multi-line comments in '/* ... */' form, at end of string
+                '#/\*(.+)\*/\s*$#Us'
+
+            ), '', $str);
+
+        // eliminate extraneous space
+        return trim($str);
+    }
+
+   /**
+    * decodes a JSON string into appropriate variable
+    *
+    * @param    string  $str    JSON-formatted string
+    *
+    * @return   mixed   number, boolean, string, array, or object
+    *                   corresponding to given JSON input string.
+    *                   See argument 1 to Services_JSON() above for object-output behavior.
+    *                   Note that decode() always returns strings
+    *                   in ASCII or UTF-8 format!
+    * @access   public
+    */
+    function decode($str)
+    {
+        $str = $this->reduce_string($str);
+
+        switch (strtolower($str)) {
+            case 'true':
+                return true;
+
+            case 'false':
+                return false;
+
+            case 'null':
+                return null;
+
+            default:
+                $m = array();
+
+                if (is_numeric($str)) {
+                    // Lookie-loo, it's a number
+
+                    // This would work on its own, but I'm trying to be
+                    // good about returning integers where appropriate:
+                    // return (float)$str;
+
+                    // Return float or int, as appropriate
+                    return ((float)$str == (integer)$str)
+                        ? (integer)$str
+                        : (float)$str;
+
+                } elseif (preg_match('/^("|\').*(\1)$/s', $str, $m) && $m[1] == $m[2]) {
+                    // STRINGS RETURNED IN UTF-8 FORMAT
+                    $delim = substr($str, 0, 1);
+                    $chrs = substr($str, 1, -1);
+                    $utf8 = '';
+                    $strlen_chrs = strlen($chrs);
+
+                    for ($c = 0; $c < $strlen_chrs; ++$c) {
+
+                        $substr_chrs_c_2 = substr($chrs, $c, 2);
+                        $ord_chrs_c = ord($chrs{$c});
+
+                        switch (true) {
+                            case $substr_chrs_c_2 == '\b':
+                                $utf8 .= chr(0x08);
+                                ++$c;
+                                break;
+                            case $substr_chrs_c_2 == '\t':
+                                $utf8 .= chr(0x09);
+                                ++$c;
+                                break;
+                            case $substr_chrs_c_2 == '\n':
+                                $utf8 .= chr(0x0A);
+                                ++$c;
+                                break;
+                            case $substr_chrs_c_2 == '\f':
+                                $utf8 .= chr(0x0C);
+                                ++$c;
+                                break;
+                            case $substr_chrs_c_2 == '\r':
+                                $utf8 .= chr(0x0D);
+                                ++$c;
+                                break;
+
+                            case $substr_chrs_c_2 == '\\"':
+                            case $substr_chrs_c_2 == '\\\'':
+                            case $substr_chrs_c_2 == '\\\\':
+                            case $substr_chrs_c_2 == '\\/':
+                                if (($delim == '"' && $substr_chrs_c_2 != '\\\'') ||
+                                   ($delim == "'" && $substr_chrs_c_2 != '\\"')) {
+                                    $utf8 .= $chrs{++$c};
+                                }
+                                break;
+
+                            case preg_match('/\\\u[0-9A-F]{4}/i', substr($chrs, $c, 6)):
+                                // single, escaped unicode character
+                                $utf16 = chr(hexdec(substr($chrs, ($c + 2), 2)))
+                                       . chr(hexdec(substr($chrs, ($c + 4), 2)));
+                                $utf8 .= $this->utf162utf8($utf16);
+                                $c += 5;
+                                break;
+
+                            case ($ord_chrs_c >= 0x20) && ($ord_chrs_c <= 0x7F):
+                                $utf8 .= $chrs{$c};
+                                break;
+
+                            case ($ord_chrs_c & 0xE0) == 0xC0:
+                                // characters U-00000080 - U-000007FF, mask 110XXXXX
+                                //see http://www.cl.cam.ac.uk/~mgk25/unicode.html#utf-8
+                                $utf8 .= substr($chrs, $c, 2);
+                                ++$c;
+                                break;
+
+                            case ($ord_chrs_c & 0xF0) == 0xE0:
+                                // characters U-00000800 - U-0000FFFF, mask 1110XXXX
+                                // see http://www.cl.cam.ac.uk/~mgk25/unicode.html#utf-8
+                                $utf8 .= substr($chrs, $c, 3);
+                                $c += 2;
+                                break;
+
+                            case ($ord_chrs_c & 0xF8) == 0xF0:
+                                // characters U-00010000 - U-001FFFFF, mask 11110XXX
+                                // see http://www.cl.cam.ac.uk/~mgk25/unicode.html#utf-8
+                                $utf8 .= substr($chrs, $c, 4);
+                                $c += 3;
+                                break;
+
+                            case ($ord_chrs_c & 0xFC) == 0xF8:
+                                // characters U-00200000 - U-03FFFFFF, mask 111110XX
+                                // see http://www.cl.cam.ac.uk/~mgk25/unicode.html#utf-8
+                                $utf8 .= substr($chrs, $c, 5);
+                                $c += 4;
+                                break;
+
+                            case ($ord_chrs_c & 0xFE) == 0xFC:
+                                // characters U-04000000 - U-7FFFFFFF, mask 1111110X
+                                // see http://www.cl.cam.ac.uk/~mgk25/unicode.html#utf-8
+                                $utf8 .= substr($chrs, $c, 6);
+                                $c += 5;
+                                break;
+
+                        }
+
+                    }
+
+                    return $utf8;
+
+                } elseif (preg_match('/^\[.*\]$/s', $str) || preg_match('/^\{.*\}$/s', $str)) {
+                    // array, or object notation
+
+                    if ($str{0} == '[') {
+                        $stk = array(SERVICES_JSON_IN_ARR);
+                        $arr = array();
+                    } else {
+                        if ($this->use & SERVICES_JSON_LOOSE_TYPE) {
+                            $stk = array(SERVICES_JSON_IN_OBJ);
+                            $obj = array();
+                        } else {
+                            $stk = array(SERVICES_JSON_IN_OBJ);
+                            $obj = new stdClass();
+                        }
+                    }
+
+                    array_push($stk, array('what'  => SERVICES_JSON_SLICE,
+                                           'where' => 0,
+                                           'delim' => false));
+
+                    $chrs = substr($str, 1, -1);
+                    $chrs = $this->reduce_string($chrs);
+
+                    if ($chrs == '') {
+                        if (reset($stk) == SERVICES_JSON_IN_ARR) {
+                            return $arr;
+
+                        } else {
+                            return $obj;
+
+                        }
+                    }
+
+                    //print("\nparsing {$chrs}\n");
+
+                    $strlen_chrs = strlen($chrs);
+
+                    for ($c = 0; $c <= $strlen_chrs; ++$c) {
+
+                        $top = end($stk);
+                        $substr_chrs_c_2 = substr($chrs, $c, 2);
+
+                        if (($c == $strlen_chrs) || (($chrs{$c} == ',') && ($top['what'] == SERVICES_JSON_SLICE))) {
+                            // found a comma that is not inside a string, array, etc.,
+                            // OR we've reached the end of the character list
+                            $slice = substr($chrs, $top['where'], ($c - $top['where']));
+                            array_push($stk, array('what' => SERVICES_JSON_SLICE, 'where' => ($c + 1), 'delim' => false));
+                            //print("Found split at {$c}: ".substr($chrs, $top['where'], (1 + $c - $top['where']))."\n");
+
+                            if (reset($stk) == SERVICES_JSON_IN_ARR) {
+                                // we are in an array, so just push an element onto the stack
+                                array_push($arr, $this->decode($slice));
+
+                            } elseif (reset($stk) == SERVICES_JSON_IN_OBJ) {
+                                // we are in an object, so figure
+                                // out the property name and set an
+                                // element in an associative array,
+                                // for now
+                                $parts = array();
+                                
+                                if (preg_match('/^\s*(["\'].*[^\\\]["\'])\s*:\s*(\S.*),?$/Uis', $slice, $parts)) {
+                                    // "name":value pair
+                                    $key = $this->decode($parts[1]);
+                                    $val = $this->decode($parts[2]);
+
+                                    if ($this->use & SERVICES_JSON_LOOSE_TYPE) {
+                                        $obj[$key] = $val;
+                                    } else {
+                                        $obj->$key = $val;
+                                    }
+                                } elseif (preg_match('/^\s*(\w+)\s*:\s*(\S.*),?$/Uis', $slice, $parts)) {
+                                    // name:value pair, where name is unquoted
+                                    $key = $parts[1];
+                                    $val = $this->decode($parts[2]);
+
+                                    if ($this->use & SERVICES_JSON_LOOSE_TYPE) {
+                                        $obj[$key] = $val;
+                                    } else {
+                                        $obj->$key = $val;
+                                    }
+                                }
+
+                            }
+
+                        } elseif ((($chrs{$c} == '"') || ($chrs{$c} == "'")) && ($top['what'] != SERVICES_JSON_IN_STR)) {
+                            // found a quote, and we are not inside a string
+                            array_push($stk, array('what' => SERVICES_JSON_IN_STR, 'where' => $c, 'delim' => $chrs{$c}));
+                            //print("Found start of string at {$c}\n");
+
+                        } elseif (($chrs{$c} == $top['delim']) &&
+                                 ($top['what'] == SERVICES_JSON_IN_STR) &&
+                                 ((strlen(substr($chrs, 0, $c)) - strlen(rtrim(substr($chrs, 0, $c), '\\'))) % 2 != 1)) {
+                            // found a quote, we're in a string, and it's not escaped
+                            // we know that it's not escaped becase there is _not_ an
+                            // odd number of backslashes at the end of the string so far
+                            array_pop($stk);
+                            //print("Found end of string at {$c}: ".substr($chrs, $top['where'], (1 + 1 + $c - $top['where']))."\n");
+
+                        } elseif (($chrs{$c} == '[') &&
+                                 in_array($top['what'], array(SERVICES_JSON_SLICE, SERVICES_JSON_IN_ARR, SERVICES_JSON_IN_OBJ))) {
+                            // found a left-bracket, and we are in an array, object, or slice
+                            array_push($stk, array('what' => SERVICES_JSON_IN_ARR, 'where' => $c, 'delim' => false));
+                            //print("Found start of array at {$c}\n");
+
+                        } elseif (($chrs{$c} == ']') && ($top['what'] == SERVICES_JSON_IN_ARR)) {
+                            // found a right-bracket, and we're in an array
+                            array_pop($stk);
+                            //print("Found end of array at {$c}: ".substr($chrs, $top['where'], (1 + $c - $top['where']))."\n");
+
+                        } elseif (($chrs{$c} == '{') &&
+                                 in_array($top['what'], array(SERVICES_JSON_SLICE, SERVICES_JSON_IN_ARR, SERVICES_JSON_IN_OBJ))) {
+                            // found a left-brace, and we are in an array, object, or slice
+                            array_push($stk, array('what' => SERVICES_JSON_IN_OBJ, 'where' => $c, 'delim' => false));
+                            //print("Found start of object at {$c}\n");
+
+                        } elseif (($chrs{$c} == '}') && ($top['what'] == SERVICES_JSON_IN_OBJ)) {
+                            // found a right-brace, and we're in an object
+                            array_pop($stk);
+                            //print("Found end of object at {$c}: ".substr($chrs, $top['where'], (1 + $c - $top['where']))."\n");
+
+                        } elseif (($substr_chrs_c_2 == '/*') &&
+                                 in_array($top['what'], array(SERVICES_JSON_SLICE, SERVICES_JSON_IN_ARR, SERVICES_JSON_IN_OBJ))) {
+                            // found a comment start, and we are in an array, object, or slice
+                            array_push($stk, array('what' => SERVICES_JSON_IN_CMT, 'where' => $c, 'delim' => false));
+                            $c++;
+                            //print("Found start of comment at {$c}\n");
+
+                        } elseif (($substr_chrs_c_2 == '*/') && ($top['what'] == SERVICES_JSON_IN_CMT)) {
+                            // found a comment end, and we're in one now
+                            array_pop($stk);
+                            $c++;
+
+                            for ($i = $top['where']; $i <= $c; ++$i)
+                                $chrs = substr_replace($chrs, ' ', $i, 1);
+
+                            //print("Found end of comment at {$c}: ".substr($chrs, $top['where'], (1 + $c - $top['where']))."\n");
+
+                        }
+
+                    }
+
+                    if (reset($stk) == SERVICES_JSON_IN_ARR) {
+                        return $arr;
+
+                    } elseif (reset($stk) == SERVICES_JSON_IN_OBJ) {
+                        return $obj;
+
+                    }
+
+                }
+        }
+    }
+
+    /**
+     * @todo Ultimately, this should just call PEAR::isError()
+     */
+    function isError($data, $code = null)
+    {
+        if (class_exists('pear')) {
+            return PEAR::isError($data, $code);
+        } elseif (is_object($data) && (get_class($data) == 'services_json_error' ||
+                                 is_subclass_of($data, 'services_json_error'))) {
+            return true;
+        }
+
+        return false;
+    }
+}
+
+if (class_exists('PEAR_Error')) {
+
+    class Services_JSON_Error extends PEAR_Error
+    {
+        function Services_JSON_Error($message = 'unknown error', $code = null,
+                                     $mode = null, $options = null, $userinfo = null)
+        {
+            parent::PEAR_Error($message, $code, $mode, $options, $userinfo);
+        }
+    }
+
+} else {
+
+    /**
+     * @todo Ultimately, this class shall be descended from PEAR_Error
+     */
+    class Services_JSON_Error
+    {
+        function Services_JSON_Error($message = 'unknown error', $code = null,
+                                     $mode = null, $options = null, $userinfo = null)
+        {
+
+        }
+    }
+
+}
+    
+?>

programming_language: PHP
filename: admin.php
change_type: ModificationType.MODIFY
diff: @@ -1,324 +1,350 @@
-<?php
-
-/**
- * Fastly Admin Panel.
- * @package Fastly
- * @author Ryan Sandor Richards
- * @copyright 2011 Fastly.com, All Rights Reserved
- */
-class FastlyAdmin {
-  /**
-   * Initializes the fastly admin panel.
-   */
-  function FastlyAdmin() {
-    // Setup admin interface
-    add_action('admin_menu', array(&$this, 'adminPanel'));
-    add_action('admin_init', array(&$this, 'adminInit'));
-    
-    // Add scripts and styles
-    wp_register_style('fastly.css', $this->resource('fastly.css'));
-    wp_enqueue_style('fastly.css');
-    wp_register_script('fastly.js', $this->resource('fastly.js'));
-    wp_enqueue_script('fastly.js');
-    
-    // Ajax Actions
-    add_action('wp_ajax_set_page', array(&$this, 'ajaxSetPage'));
-    add_action('wp_ajax_sign_up', array(&$this, 'ajaxSignUp'));
-    
-    #update_option('fastly_page', 'welcome');
-    update_option('fastly_page', 'configure');
-    
-    /* Point to CI API Server
-    update_option('fastly_api_hostname', '184.106.66.217');
-    update_option('fastly_api_port', 5500);
-    //*/
-    
-    /* Point to Dev API Server
-    update_option('fastly_api_hostname', '10.235.5.18');
-    update_option('fastly_api_port', 80);
-    //*/
-    
-    // Grab an instance of the API adapter
-    $this->api = new FastlyAPI(
-      get_option('fastly_api_key'),
-      get_option('fastly_api_hostname'),
-      get_option('fastly_api_port')
-    );
-  }
-  
-  /**
-   * @param $p Page name to test.
-   * @return True if the given page is valid, false otherwise.
-   */
-  function validPage($p) {
-    return in_array($p, array_keys($this->templates));
-  }
-  
-  /**
-   * Set the user's default page.
-   */
-  function ajaxSetPage() {
-    if (isset($_REQUEST['page']) && $this->validPage($_REQUEST['page'])) {
-      update_option('fastly_page', $_REQUEST['page']);
-      die(1);
-    }
-    die();
-  }
-  
-  /**
-   * Make a sign up request to teh fastly API.
-   */
-  function ajaxSignUp() {
-    unset($_REQUEST['action']);
-    $_REQUEST['wizard'] = 'wordpress';
-    
-    $response = $this->api->post('/signup', $_REQUEST);
-        
-    if ($response === -1)
-      wp_die('Could not connect to Fastly API.');
-      
-    $code = $response['code'];
-    $body = $this->decode($response['body']);
-    $defaultError = "An error occurred while connecting to the fastly API, please try your request again.";
-    
-    switch ($code) {
-      case 200:
-        update_option('fastly_api_key', $body['api_key']);
-        update_option('fastly_service_id', $body['service_id']);
-        update_option('fastly_page', 'configure');
-        
-        // Update internal host name
-        $parts = explode('/', $_REQUEST['website_address']);
-        if (count($parts) >= 3) {
-          update_option('fastly_hostname', $parts[2]);
-        }
-        
-        $response = array('status' => 'success');
-        break;
-      case 400:
-        if ($body['class'] == "Customer")
-          $msg = "A customer with that name already exists.";
-        else if ($body['class'] == "User") {
-          if (!empty($body['errors']['login']))
-            $msg = "Invalid e-mail address.";
-          else
-            $msg = "A user with the given e-mail address already exists.";
-        }
-        else
-          $msg = $defaultError;
-        $response = array('status' => 'error', 'msg' => $msg);
-        break;
-      case 404:
-        $response = array('status' => 'error', 'msg' => $defaultError);
-        break;
-    }
-    
-    die($this->encode($response));
-  }
-  
-  
-  /**
-   * Called when admin is initialized by wordpress.
-   */
-  function adminInit() {
-    // Config form group
-    register_setting('fastly-group', 'fastly_hostname');
-    register_setting('fastly-group', 'fastly_api_hostname');
-    register_setting('fastly-group', 'fastly_api_port');
-    register_setting('fastly-group', 'fastly_api_key');
-    register_setting('fastly-group', 'fastly_service_id');
-    register_setting('fastly-group', 'fastly_log_purges');
-    
-    // Page change group
-    register_setting('fastly-page-group', 'fastly_page');
-    
-    // Generate front-end templates
-    $this->templates = array(
-      'welcome' => $this->welcome(),
-      'configure' => $this->configure(),
-    );
-
-    // Get the current page
-    $this->page = get_option('fastly_page');
-    if (!$this->validPage($this->page)) {
-      $this->page = 'welcome';
-      update_option('fastly_page', 'welcome');
-    }
-  }
-  
-  /**
-   * Adds the admin panel for the plugin.
-   */
-  function adminPanel() {
-    add_options_page('Configure Fastly', 'Fastly', 'manage_options', 'fastly-admin-panel', array(&$this, 'render'));
-  }
-  
-  /**
-   * Fetches various static resources for the fastly plugin (js, css, images, etc.)
-   * @param $name Name of the resource to fetch.
-   * @return The URL to the resource in the plugin directory.
-   */
-  function resource($name='') {
-    return FASTLY_PLUGIN_URL . 'static/' . $name;
-  }
-  
-  /**
-   * Backwards compatible JSON encoder.
-   * @param $obj Object to encode.
-   * @return The JSON encoding of the given object.
-   */
-  function encode($obj) {
-    if (function_exists('json_encode')) {
-      return json_encode($obj);
-    }
-    else {
-      $json = new Services_JSON();
-      return $json->encode($obj);
-    }
-  }
-
-  /**
-   * Backwards compatible JSON decode.
-   * @param $str A json string.
-   * @return The object represented by the json.
-   */
-  function decode($str) {
-    if (function_exists('json_decode')) {
-      return json_decode($str, true);
-    }
-    else {
-      $json = new Services_JSON();
-      return $json->decode($str);
-    }
-  }
-  
-  /**
-   * @return Sign Up / Welcome page markup.
-   */
-  function welcome() {
-    $customer = get_bloginfo('name');
-    $address = $_SERVER['SERVER_ADDR'];
-    $website_address = get_bloginfo('wpurl');
-      
-    return '
-      <div class="signup fastly-admin-page">
-        <h2>Sign Up</h2>
-        <p class="error-flash"></p>
-        <p>To create your free Fastly account enter your information, click the checkbox, and press the &quot;Sign Up&quot; button.</p>
-        
-        <fieldset>
-          <p><b>Blog Name</b></p>
-          <p><input class="text" id="customer" type="text" value="' . $customer . '"></p>
-          <p><b>Your Name</b></p>
-          <p><input class="text" id="name" type="text"></p>
-          <p><b>Email Address</b></p>
-          <p><input class="text" id="email" type="text"></p>
-        </fieldset>
-        
-        <br>
-        
-        <fieldset>
-          <p><b>Blog Address</b></p>
-          <p><input class="text" id="website_address" type="text" value="' . $website_address . '"></p>
-          <p><b>Server Address</b></p>
-          <p><input class="text" id="address" type="text" value="' . $address . '"></p>
-        </fieldset>
-        
-        <p><label id="agree_tos_label" for="agree_tos"><input id="agree_tos" type="checkbox"> I agree to the
-          <a href="#" target="_blank">terms of service</a></label></p>
-        
-        <p class="button-row"><a href="#" class="button submit">Sign Up</a> <img class="loading" src="' . $this->resource('loading.gif') . '"></p>
-      </div>
-      
-      <div class="welcome fastly-admin-page">
-        <br>
-        <p><a href="#" class="configure">Click here if you already have a Fastly account for your site.</a></p>
-      </div>
-    ';
-  }
-  
-  /**
-   * @return Configuration page markup.
-   */
-  function configure() {
-    // TODO NEEDS TEH: $_SERVER['SERVER_ADDR']
-    ob_start();    
-    echo '
-      <div class="configure fastly-admin-page">
-        <h2>Configure</h2>
-        <form method="post" action="options.php">
-    ';
-    
-    settings_fields('fastly-group');
-    $parts = parse_url( get_bloginfo('wpurl') );
-    $testUrl = 'http://' . $parts['host'] . '.global.prod.fastly.net';
-    if( !empty($parts['path']) ) {
-      $testUrl .= $parts['path'];
-    }
-    
-    echo '
-          <fieldset>
-            <p><b>Fastly API Key</b></p>
-            <p><input class="text" type="text" name="fastly_api_key" value="' . get_option('fastly_api_key') . '"></p>
-            <p><b>Service Id</b></p>
-            <p><input class="text" type="text" name="fastly_service_id" value="' . get_option('fastly_service_id') . '"></p>
-          </fieldset>
-      
-          <p><a href="#" class="advanced">Advanced Configuration</a></p>
-      
-          <fieldset class="advanced">
-            <p><b>Fastly API Hostname</b></p>
-            <p><input class="text" name="fastly_api_hostname" type="text" value="' . get_option('fastly_api_hostname') . '"></p>
-            <p><b>Fastly API Port</b></p>
-            <p><input class="text" name="fastly_api_port" type="text" value="' . get_option('fastly_api_port') . '"></p>
-            <p><input class="checkbox" name="fastly_log_purges" type="checkbox" value="1" ' . ((int)get_option('fastly_log_purges')?"checked='checked'":"") . '> <b>Log purges to PHP errorlog</b></p>
-            
-            <!--
-            <p><b></b></p>
-            <p><input type="text" value=""></p>
-            <p><b></b></p>
-            <p><input type="text" value=""></p>
-            -->
-          </fieldset>
-      
-          <p><input type="submit" class="button" value="Save Settings"></p>
-        </form>
-      </div>
-      <p>Test your site: <a href="' . $testUrl . '">' . $testUrl . '</a></p>
-    ';
-    
-    $form = ob_get_contents();
-    ob_end_clean();
-    
-    return $form;
-  }
-  
-  /**
-   * Initializes the JS for the page.
-   */
-  function initJS() {
-    echo '<script type="text/javascript">
-      Fastly.init("' . $this->page . '", ' . $this->encode($this->templates) . ');
-    </script>';
-  }
-  
-  /**
-   * Renders the admin panel for the plugin.
-   */
-  function render() {
-    if (!current_user_can('manage_options'))  {
-      wp_die( __('You do not have sufficient permissions to access this page.') );
-    }
-    
-    echo '<div id="fastly-admin" class="wrap">';
-      echo '<h1><img alt="fastly" src="' . $this->resource('logo_white.gif') . '"><br><span style="font-size: x-small;">version: '. FASTLY_VERSION .'</span></h1>';
-      echo '<div class="content">' . $this->templates[$this->page] . '</div>';
-    echo '</div>';
-    
-    $this->initJS();
-  }
-}
-
-// "Some people call me the space cowboy, yeah | Some call me the gangster of love | Some people call me Maurice" -- Steve Miller
-
-?>
\ No newline at end of file
+<?php
+
+/**
+ * Fastly Admin Panel.
+ * @package Fastly
+ * @author Ryan Sandor Richards
+ * @copyright 2011 Fastly.com, All Rights Reserved
+ */
+class FastlyAdmin {
+  /**
+   * Initializes the fastly admin panel.
+   */
+  function FastlyAdmin() {
+    // Setup admin interface
+    add_action('admin_menu', array(&$this, 'adminPanel'));
+    add_action('admin_init', array(&$this, 'adminInit'));
+    
+    // Register scripts and styles
+    add_action('admin_enqueue_scripts', array($this, 'admin_enqueue_scripts'));
+    
+    // Ajax Actions
+    add_action('wp_ajax_set_page', array(&$this, 'ajaxSetPage'));
+    add_action('wp_ajax_sign_up', array(&$this, 'ajaxSignUp'));
+    
+    #update_option('fastly_page', 'welcome');
+    update_option('fastly_page', 'configure');
+    
+    /* Point to CI API Server
+    update_option('fastly_api_hostname', '184.106.66.217');
+    update_option('fastly_api_port', 5500);
+    //*/
+    
+    /* Point to Dev API Server
+    update_option('fastly_api_hostname', '10.235.5.18');
+    update_option('fastly_api_port', 80);
+    //*/
+    
+    // Grab an instance of the API adapter
+    $this->api = new FastlyAPI(
+      get_option('fastly_api_key'),
+      get_option('fastly_api_hostname'),
+      get_option('fastly_api_port')
+    );
+  }
+
+  /**
+   * Register scripts and styles needed for the admin option page.
+   *
+   * @return void
+   */
+  function admin_enqueue_scripts( $hook_suffix ) {
+    if ('settings_page_fastly-admin-panel' !== $hook_suffix) {
+      return;
+    }
+
+    // Add scripts and styles
+    wp_register_style('fastly.css', $this->resource('fastly.css'));
+    wp_enqueue_style('fastly.css');
+
+    wp_register_script('fastly.js', $this->resource('fastly.js'));
+    // Expose a WP CSRF nonce to the fastly.js script
+    $nonce = wp_create_nonce('fastly-admin');
+    wp_localize_script('fastly.js', 'fastlyNonce', $nonce);
+    wp_enqueue_script('fastly.js');
+  }
+  
+  /**
+   * @param $p Page name to test.
+   * @return True if the given page is valid, false otherwise.
+   */
+  function validPage($p) {
+    return in_array($p, array_keys($this->templates));
+  }
+  
+  /**
+   * Set the user's default page.
+   */
+  function ajaxSetPage() {
+    if (!isset($_REQUEST['nonce']) || !wp_verify_nonce($_REQUEST['nonce'], 'fastly-admin')) {
+      wp_die('Bad CSRF Nonce.');
+    }
+
+    if (isset($_REQUEST['page']) && $this->validPage($_REQUEST['page'])) {
+      update_option('fastly_page', esc_sql($_REQUEST['page']));
+      die(1);
+    }
+    die();
+  }
+  
+  /**
+   * Make a sign up request to teh fastly API.
+   */
+  function ajaxSignUp() {
+    if (!isset($_REQUEST['nonce']) || !wp_verify_nonce($_REQUEST['nonce'], 'fastly-admin')) {
+      wp_die('Bad CSRF Nonce.');
+    }
+
+    unset($_REQUEST['action']);
+    $_REQUEST['wizard'] = 'wordpress';
+    
+    $response = $this->api->post('/signup', $_REQUEST);
+        
+    if ($response === -1)
+      wp_die('Could not connect to Fastly API.');
+      
+    $code = $response['code'];
+    $body = $this->decode($response['body']);
+    $defaultError = "An error occurred while connecting to the fastly API, please try your request again.";
+    
+    switch ($code) {
+      case 200:
+        update_option('fastly_api_key', esc_sql($body['api_key']));
+        update_option('fastly_service_id', esc_sql($body['service_id']));
+        update_option('fastly_page', 'configure');
+        
+        // Update internal host name
+        $parts = explode('/', $_REQUEST['website_address']);
+        if (count($parts) >= 3) {
+          update_option('fastly_hostname', esc_sql($parts[2]));
+        }
+        
+        $response = array('status' => 'success');
+        break;
+      case 400:
+        if ($body['class'] == "Customer")
+          $msg = "A customer with that name already exists.";
+        else if ($body['class'] == "User") {
+          if (!empty($body['errors']['login']))
+            $msg = "Invalid e-mail address.";
+          else
+            $msg = "A user with the given e-mail address already exists.";
+        }
+        else
+          $msg = $defaultError;
+        $response = array('status' => 'error', 'msg' => $msg);
+        break;
+      case 404:
+        $response = array('status' => 'error', 'msg' => $defaultError);
+        break;
+    }
+    
+    die($this->encode($response));
+  }
+  
+  
+  /**
+   * Called when admin is initialized by wordpress.
+   */
+  function adminInit() {
+    // Config form group
+    register_setting('fastly-group', 'fastly_hostname');
+    register_setting('fastly-group', 'fastly_api_hostname');
+    register_setting('fastly-group', 'fastly_api_port');
+    register_setting('fastly-group', 'fastly_api_key');
+    register_setting('fastly-group', 'fastly_service_id');
+    register_setting('fastly-group', 'fastly_log_purges');
+    
+    // Page change group
+    register_setting('fastly-page-group', 'fastly_page');
+    
+    // Generate front-end templates
+    $this->templates = array(
+      'welcome' => $this->welcome(),
+      'configure' => $this->configure(),
+    );
+
+    // Get the current page
+    $this->page = get_option('fastly_page');
+    if (!$this->validPage($this->page)) {
+      $this->page = 'welcome';
+      update_option('fastly_page', 'welcome');
+    }
+  }
+  
+  /**
+   * Adds the admin panel for the plugin.
+   */
+  function adminPanel() {
+    add_options_page('Configure Fastly', 'Fastly', 'manage_options', 'fastly-admin-panel', array(&$this, 'render'));
+  }
+  
+  /**
+   * Fetches various static resources for the fastly plugin (js, css, images, etc.)
+   * @param $name Name of the resource to fetch.
+   * @return The URL to the resource in the plugin directory.
+   */
+  function resource($name='') {
+    return FASTLY_PLUGIN_URL . 'static/' . $name;
+  }
+  
+  /**
+   * Backwards compatible JSON encoder.
+   * @param $obj Object to encode.
+   * @return The JSON encoding of the given object.
+   */
+  function encode($obj) {
+    if (function_exists('json_encode')) {
+      return json_encode($obj);
+    }
+    else {
+      $json = new Services_JSON();
+      return $json->encode($obj);
+    }
+  }
+
+  /**
+   * Backwards compatible JSON decode.
+   * @param $str A json string.
+   * @return The object represented by the json.
+   */
+  function decode($str) {
+    if (function_exists('json_decode')) {
+      return json_decode($str, true);
+    }
+    else {
+      $json = new Services_JSON();
+      return $json->decode($str);
+    }
+  }
+  
+  /**
+   * @return Sign Up / Welcome page markup.
+   */
+  function welcome() {
+    $customer = get_bloginfo('name');
+    $address = $_SERVER['SERVER_ADDR'];
+    $website_address = get_bloginfo('wpurl');
+      
+    return '
+      <div class="signup fastly-admin-page">
+        <h2>Sign Up</h2>
+        <p class="error-flash"></p>
+        <p>To create your free Fastly account enter your information, click the checkbox, and press the &quot;Sign Up&quot; button.</p>
+        
+        <fieldset>
+          <p><b>Blog Name</b></p>
+          <p><input class="text" id="customer" type="text" value="' . esc_attr($customer) . '"></p>
+          <p><b>Your Name</b></p>
+          <p><input class="text" id="name" type="text"></p>
+          <p><b>Email Address</b></p>
+          <p><input class="text" id="email" type="text"></p>
+        </fieldset>
+        
+        <br>
+        
+        <fieldset>
+          <p><b>Blog Address</b></p>
+          <p><input class="text" id="website_address" type="text" value="' . esc_url($website_address) . '"></p>
+          <p><b>Server Address</b></p>
+          <p><input class="text" id="address" type="text" value="' . esc_attr($address) . '"></p>
+        </fieldset>
+        
+        <p><label id="agree_tos_label" for="agree_tos"><input id="agree_tos" type="checkbox"> I agree to the
+          <a href="#" target="_blank">terms of service</a></label></p>
+        
+        <p class="button-row"><a href="#" class="button submit">Sign Up</a> <img class="loading" src="' . $this->resource('loading.gif') . '"></p>
+      </div>
+      
+      <div class="welcome fastly-admin-page">
+        <br>
+        <p><a href="#" class="configure">Click here if you already have a Fastly account for your site.</a></p>
+      </div>
+    ';
+  }
+  
+  /**
+   * @return Configuration page markup.
+   */
+  function configure() {
+    // TODO NEEDS TEH: $_SERVER['SERVER_ADDR']
+    ob_start();    
+    echo '
+      <div class="configure fastly-admin-page">
+        <h2>Configure</h2>
+        <form method="post" action="options.php">
+    ';
+    
+    settings_fields('fastly-group');
+    $parts = parse_url( get_bloginfo('wpurl') );
+    $testUrl = 'http://' . $parts['host'] . '.global.prod.fastly.net';
+    if( !empty($parts['path']) ) {
+      $testUrl .= $parts['path'];
+    }
+    
+    echo '
+          <fieldset>
+            <p><b>Fastly API Key</b></p>
+            <p><input class="text" type="text" name="fastly_api_key" value="' . esc_attr(get_option('fastly_api_key')) . '"></p>
+            <p><b>Service Id</b></p>
+            <p><input class="text" type="text" name="fastly_service_id" value="' . esc_attr(get_option('fastly_service_id')) . '"></p>
+          </fieldset>
+      
+          <p><a href="#" class="advanced">Advanced Configuration</a></p>
+      
+          <fieldset class="advanced">
+            <p><b>Fastly API Hostname</b></p>
+            <p><input class="text" name="fastly_api_hostname" type="text" value="' . esc_url(get_option('fastly_api_hostname')) . '"></p>
+            <p><b>Fastly API Port</b></p>
+            <p><input class="text" name="fastly_api_port" type="text" value="' . esc_attr(get_option('fastly_api_port')) . '"></p>
+            <p><input class="checkbox" name="fastly_log_purges" type="checkbox" value="1" ' . ((int)get_option('fastly_log_purges')?"checked='checked'":"") . '> <b>Log purges to PHP errorlog</b></p>
+            
+            <!--
+            <p><b></b></p>
+            <p><input type="text" value=""></p>
+            <p><b></b></p>
+            <p><input type="text" value=""></p>
+            -->
+          </fieldset>
+      
+          <p><input type="submit" class="button" value="Save Settings"></p>
+        </form>
+      </div>
+      <p>Test your site: <a href="' . esc_url($testUrl) . '">' . esc_url($testUrl) . '</a></p>
+    ';
+    
+    $form = ob_get_contents();
+    ob_end_clean();
+    
+    return $form;
+  }
+  
+  /**
+   * Initializes the JS for the page.
+   */
+  function initJS() {
+    echo '<script type="text/javascript">
+      Fastly.init("' . $this->page . '", ' . $this->encode($this->templates) . ');
+    </script>';
+  }
+  
+  /**
+   * Renders the admin panel for the plugin.
+   */
+  function render() {
+    if (!current_user_can('manage_options'))  {
+      wp_die( __('You do not have sufficient permissions to access this page.') );
+    }
+    
+    echo '<div id="fastly-admin" class="wrap">';
+      echo '<h1><img alt="fastly" src="' . $this->resource('logo_white.gif') . '"><br><span style="font-size: x-small;">version: '. FASTLY_VERSION .'</span></h1>';
+      echo '<div class="content">' . $this->templates[$this->page] . '</div>';
+    echo '</div>';
+    
+    $this->initJS();
+  }
+}
+
+// "Some people call me the space cowboy, yeah | Some call me the gangster of love | Some people call me Maurice" -- Steve Miller
+
+?>

programming_language: PHP
filename: api.php
change_type: ModificationType.MODIFY
diff: @@ -1,97 +1,93 @@
-<?php
-
-/**
- * Fastly API for PHP.
- * @package Fastly
- * @author Ryan Sandor Richards
- * @copyright 2011 Fastly.com, All Rights Reserved
- */
-class FastlyAPI {  
-  /**
-   * Default constructor.
-   * @param $api_key Fastly API key.
-   * @param $host Hostname of the API server.
-   * @param $port Port for the API server.
-   */
-  function FastlyAPI($api_key='', $host='https://app.fastly.com', $port=null) {
-    $this->api_key   = $api_key;
-    $this->host      = $host;
-    $this->port      = $port;
-    $this->host_name = preg_replace('/^(ssl|https?):\/\//', '', $host);
-  }
-  
-  /**
-   * Sends a purge request to the Fastly API.
-   * @param $uri URI to purge.
-   */
-  function purge($uris) {
-    // TODO How can we handle this more elegantly?
-    if (!$this->api_key)
-      return;
-    
-    if (!is_array($uris)) {
-      $uris = array($uris);
-    }
-    // TODO - change this to a curl_multi_exec at some point
-	$logPurges = (bool)get_option('fastly_log_purges');
-    foreach ($uris as $uri) {
-      #$uri = preg_replace("/^https?:\/\//", '', $uri); 
-	  if( $logPurges ) {
-        error_log("Purging " . $uri);
-      }
-      $this->post($uri);
-    }
-  }
-
-  /**
-   * Sends a purge all request to the Fastly API.
-   */
-  function purgeAll($service_id) {
-    $url = $this->host;
-    if (!is_null($this->port) && is_numeric($this->port)) {
-      $url .= ":" . $this->port; 
-    } 
-    $url .= '/service/' . $service_id . '/purge_all';
-      
-    return $this->post($url, true);
-  }
-
-  /** 
-   * Sends a post request to the Fastly API.
-   * @param $path Path to call on the remote host.
-   * @param $data Data for the body for the post request.
-   * @return The response from the server or -1 if an error occurred.
-   */
-  function post($url, $do_post = true) {
-
-    $headers = array();
-    if ($this->api_key) {
-      $headers[] = "Fastly-Key: " . $this->api_key;
-    }
-
-    $ch  = curl_init();
-    # Temporary workaround to fix purging. Use POST instead of PURGE method.
-    # Strip off protocol
-    $url = get_option('fastly_api_hostname') . "/purge/" . preg_replace("/^http(s?):\/\//",'', $url);
-
-	if( (bool)get_option('fastly_log_purges') ) {
-      error_log("Purging using POST for " . $url);
-    }
-
-    curl_setopt($ch, CURLOPT_URL, $url );
-    if ($do_post) {
-      curl_setopt($ch, CURLOPT_POST, 1);
-    } else {
-      curl_setopt($ch, CURLOPT_CUSTOMREQUEST, "PURGE");      
-    }
-    curl_setopt($ch, CURLOPT_HTTPHEADER, $headers);
-    curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
-    $response = curl_exec($ch);
-    curl_close($ch);
-    return !!$response;
-    
-  }
-} 
-
-// "WHITE LIGHT, doo-doo doo-doo doo, WHITE LIGHT" -- Gorillaz
-?>
+<?php
+
+/**
+ * Fastly API for PHP.
+ * @package Fastly
+ * @author Ryan Sandor Richards
+ * @copyright 2011 Fastly.com, All Rights Reserved
+ */
+class FastlyAPI {  
+  /**
+   * Default constructor.
+   * @param $api_key Fastly API key.
+   * @param $host Hostname of the API server.
+   * @param $port Port for the API server.
+   */
+  function FastlyAPI($api_key='', $host='https://app.fastly.com', $port=null) {
+    $this->api_key   = $api_key;
+    $this->host      = $host;
+    $this->port      = $port;
+    $this->host_name = preg_replace('/^(ssl|https?):\/\//', '', $host);
+  }
+  
+  /**
+   * Sends a purge request to the Fastly API.
+   * @param $uri URI to purge.
+   */
+  function purge($uris) {
+    // TODO How can we handle this more elegantly?
+    if (!$this->api_key)
+      return;
+    
+    if (!is_array($uris)) {
+      $uris = array($uris);
+    }
+    // TODO - change this to a curl_multi_exec at some point
+	$logPurges = (bool)get_option('fastly_log_purges');
+    foreach ($uris as $uri) {
+      #$uri = preg_replace("/^https?:\/\//", '', $uri); 
+	  if( $logPurges ) {
+        error_log("Purging " . $uri);
+      }
+      $this->post($uri);
+    }
+  }
+
+  /**
+   * Sends a purge all request to the Fastly API.
+   */
+  function purgeAll($service_id) {
+    $url = $this->host;
+    if (!is_null($this->port) && is_numeric($this->port)) {
+      $url .= ":" . $this->port; 
+    } 
+    $url .= '/service/' . $service_id . '/purge_all';
+      
+    return $this->post($url, true);
+  }
+
+  /** 
+   * Sends a post request to the Fastly API.
+   * @param $path Path to call on the remote host.
+   * @param $data Data for the body for the post request.
+   * @return The response from the server or -1 if an error occurred.
+   */
+  function post($url, $do_post = true) {
+
+    $headers = array();
+    if ($this->api_key) {
+      $headers[] = "Fastly-Key: " . $this->api_key;
+    }
+
+    $ch  = curl_init();
+    # Temporary workaround to fix purging. Use POST instead of PURGE method.
+    # Strip off protocol
+    $url = get_option('fastly_api_hostname') . "/purge/" . preg_replace("/^http(s?):\/\//",'', $url);
+
+	if( (bool)get_option('fastly_log_purges') ) {
+      error_log("Purging using POST for " . esc_url($url));
+    }
+
+    $args = array(
+        'headers' => $headers,
+        'method'  => (true === $do_post) ? 'POST' : 'PURGE',
+    );
+
+    $response = wp_remote_request($url, $args);
+
+    return ( is_wp_error( $response ) ) ? -1 : $response;
+  }
+} 
+
+// "WHITE LIGHT, doo-doo doo-doo doo, WHITE LIGHT" -- Gorillaz
+?>

programming_language: PHP
filename: purge.php
change_type: ModificationType.MODIFY
diff: @@ -1,221 +1,221 @@
-<?php
-
-/**
- * Fastly Purger - Detects changes in content and automatically sends purge requests.
- * @package Fastly
- * @author Ryan Sandor Richards
- * @copyright 2011 Fastly.com, All Rights Reserved
- */
-class FastlyPurge {
-  /** 
-   * Default constructor - configures purge listeners.
-   */
-  function FastlyPurge() {
-    // Posts and Pages
-    add_action('edit_post', array(&$this, 'purgePost'), 99);
-    add_action('edit_post', array(&$this, 'purgePostDependencies'), 99);
-    add_action('transition_post_status', array(&$this,'purgePostStatus'),99, 3);
-    add_action('deleted_post', array(&$this, 'purgePost'), 99);
-    add_action('deleted_post', array(&$this, 'purgeCommon'), 99);
-    
-    // Comments
-    add_action('comment_post', array(&$this, 'purgeComments'),99);
-    add_action('edit_comment', array(&$this, 'purgeComments'),99);
-    add_action('trashed_comment', array(&$this, 'purgeComments'),99);
-    add_action('untrashed_comment', array(&$this, 'purgeComments'),99);
-    add_action('deleted_comment', array(&$this, 'purgeComments'),99);
-    
-    // Full purges (theme changes, etc.)
-    add_action('switch_theme', array(&$this, 'purgeAll'), 99);
-    add_action('update_option_sidebars_widgets', array(&$this, 'purgeAll'), 99);
-    add_action('widgets.php', array(&$this, 'purgeAll'), 99);
-    add_action("update_option_theme_mods_".get_option('stylesheet'), array(&$this, 'purgeAll'), 99);
-    
-    // Links
-    add_action("deleted_link",array(&$this, 'purgeLinks'), 99);
-    add_action("edit_link",array(&$this, 'purgeLinks'), 99);
-    add_action("add_link",array(&$this, 'purgeLinks'), 99);
-    
-    // Categories
-    add_action("edit_category",array(&$this, 'purgeCategory'), 99);       
-    add_action("edit_link_category",array(&$this, 'purgeLinkCategory'), 99);
-    add_action("edit_post_tag",array(&$this, 'purgeTagCategory'), 99);
-    
-    // Setup API
-    $this->api = new FastlyAPI(
-      get_option('fastly_api_key'), 
-      get_option('fastly_api_hostname'),
-      get_option('fastly_api_port')
-    );
-  }
-  
-  /**
-   * Sends a purge request for the given url.
-   * @param $url URL to purge from the cache server.
-   */
-  function purge($urls) {
-    $this->api->purge($urls);   
-  }
-  
-  /**
-   * Purges all pages on the site.
-   */
-  function purgeAll() {
-    $this->api->purgeAll( get_option('fastly_service_id') );
-  }
-  
-  /**
-   * Purges common pages.
-   */
-  function purgeCommon($call=true) {
-    $urls = array(get_bloginfo('wpurl')."/", get_bloginfo('wpurl')."/feed", get_bloginfo('wpurl')."/feed/atom");
-
-    // TODO Need Regex Support
-    /*
-      if (get_site_option($this->wpv_update_pagenavi_optname) == 1) {
-         $this->WPVarnishPurgerPurgeObject("/page/(.*)");
-      }
-    */
-    return $call ? $this->purge($urls) : $urls;
-  }
-  
-  /**
-   * Purges posts and pages on update.
-   */
-  function purgePost($postId, $call=true) {
-    $urls = array( get_permalink($postId) );
-    return $call ? $this->purge($urls) : $urls;
-  }
-
-  /**
-   * Purges objects that depend on the post.
-   */
-  function purgePostDependencies($postId, $call=true) { 
-    $urls = array();
-    $urls = array_merge($urls, $this->purgeCommon(false));
-    $urls = array_merge($urls, $this->purgeCategories($postId, false));
-    $urls = array_merge($urls, $this->purgeArchives($postId, false));
-    $urls = array_merge($urls, $this->purgeTags($postId, false));
-    return $call ? $this->purge($urls) : $urls;
-  }
-  
-  /**
-   * Purges categories associated with a post.
-   * @param $postId Id of the post.
-   */
-  function purgeCategories($postId, $call=true) {
-    $urls       = array();
-    $categories = get_the_category($postId);
-    foreach ($categories as $cat) {
-      $urls = array_merge($urls, $this->purgeCategory($cat->cat_ID, false));
-    }
-    return $call ? $this->purge($urls) : $urls;
-  }
-  
-  /**
-   * Purges post comments.
-   */
-  function purgeComments($commentId, $call=true) {
-    $comment  = get_comment($commentId);
-    $approved = $comment->comment_approved;
-    $urls     = array();
-
-    if ($approved == 1 || $approved == 'trash') {
-      $postId = $comment->comment_post_ID;
-      #$this->purge('/\\\?comments_popup=' . $postId);
-      $urls[] = get_bloginfo('wpurl') . '/?comments_popup=' . $postId;
-      
-      // TODO Need Regex Support
-      /*
-      if (get_site_option($this->wpv_update_commentnavi_optname) == 1) {
-        $this->purge('/\\\?comments_popup=' . $postId . '&(.*)');
-      }
-      */
-    }
-    return $call ? $this->purge($urls) : $urls;
-  }
-  
-  /**
-   * Purges links.
-   */
-  function purgeLinks() {
-    if (is_active_widget(false, false, 'links')) {
-        $this->purgeAll();
-    }
-  }
-  
-  /**
-   * Purges post categories.
-   * @param $categoryId Id of the category to purge.
-   */
-  function purgeCategory($categoryId, $call=true) {
-    $urls = array();
-    if (is_active_widget(false, false, 'categories')) {
-      $this->purgeAll();
-    }
-    else {
-      $urls[] = get_category_link($categoryId);
-    }
-    return $call ? $this->purge($urls) : $urls;
-  }
-
-  /**
-   * Purges link categories.
-   * @param $categoryId Id of the category to purge.
-   */
-  function purgeLinkCategory($categoryId) {
-    if (is_active_widget(false,false,'links')){
-      $this->purgeAll();
-    }
-  }
-
-  /**
-   * Purges a tag category.
-   * @param $categoryId Id of the category to purge.
-   */
-  function purgeTagCategory($categoryId, $call=true) {
-    $urls = array( get_tag_link($categoryId) );
-    return $call ? $this->purge($urls) : $urls;
-  }
-  
-  /**
-   * Purges archives pages.
-   * @param $postId Id of the post that triggered the purge.
-   */
-  function purgeArchives($postId, $call=true) {
-    $urls = array(
-      get_day_link(get_post_time('Y',false,$postId), get_post_time('m',true,$postId),get_post_time('d',true,$postId)),
-      get_month_link(get_post_time('Y',false,$postId), get_post_time('m',true,$postId)),
-      get_year_link(get_post_time('Y',false,$postId)),
-    );
-
-    return $call ? $this->purge($urls) : $urls;
-  }  
-
-  /**
-   * Purges tags associated with a post.
-   * @param $postId Id of the post being purged.
-   */
-  function purgeTags($postId, $call=true) {
-    $urls = array();
-    $tags = wp_get_post_tags($postId);
-    foreach ($tags as $tag) {
-      $urls = array_merge($urls, $this->purgeTagCategory($tag->term_id, false));
-    }
-    return $call ? $this->purge($urls) : $urls;
-  }
-
-  /**
-   * Handles post status purges.
-   */
-  function purgePostStatus($new_status, $old_status, $post, $call=true) {
-    $urls = array();
-    $urls = array_merge($urls, $this->purgePost($post->ID, false));
-    $urls = array_merge($urls, $this->purgePostDependencies($post->ID, false));
-    return $call ? $this->purge($urls) : $urls;
-  }
-}
-
-// "While mona lisas and mad hatters, sons of bankers, sons of lawyers turn around and say good morning to the night..." -- Elton John
-
+<?php
+
+/**
+ * Fastly Purger - Detects changes in content and automatically sends purge requests.
+ * @package Fastly
+ * @author Ryan Sandor Richards
+ * @copyright 2011 Fastly.com, All Rights Reserved
+ */
+class FastlyPurge {
+  /** 
+   * Default constructor - configures purge listeners.
+   */
+  function FastlyPurge() {
+    // Posts and Pages
+    add_action('edit_post', array(&$this, 'purgePost'), 99);
+    add_action('edit_post', array(&$this, 'purgePostDependencies'), 99);
+    add_action('transition_post_status', array(&$this,'purgePostStatus'),99, 3);
+    add_action('deleted_post', array(&$this, 'purgePost'), 99);
+    add_action('deleted_post', array(&$this, 'purgeCommon'), 99);
+    
+    // Comments
+    add_action('comment_post', array(&$this, 'purgeComments'),99);
+    add_action('edit_comment', array(&$this, 'purgeComments'),99);
+    add_action('trashed_comment', array(&$this, 'purgeComments'),99);
+    add_action('untrashed_comment', array(&$this, 'purgeComments'),99);
+    add_action('deleted_comment', array(&$this, 'purgeComments'),99);
+    
+    // Full purges (theme changes, etc.)
+    add_action('switch_theme', array(&$this, 'purgeAll'), 99);
+    add_action('update_option_sidebars_widgets', array(&$this, 'purgeAll'), 99);
+    add_action('widgets.php', array(&$this, 'purgeAll'), 99);
+    add_action("update_option_theme_mods_".get_option('stylesheet'), array(&$this, 'purgeAll'), 99);
+    
+    // Links
+    add_action("deleted_link",array(&$this, 'purgeLinks'), 99);
+    add_action("edit_link",array(&$this, 'purgeLinks'), 99);
+    add_action("add_link",array(&$this, 'purgeLinks'), 99);
+    
+    // Categories
+    add_action("edit_category",array(&$this, 'purgeCategory'), 99);       
+    add_action("edit_link_category",array(&$this, 'purgeLinkCategory'), 99);
+    add_action("edit_post_tag",array(&$this, 'purgeTagCategory'), 99);
+    
+    // Setup API
+    $this->api = new FastlyAPI(
+      get_option('fastly_api_key'), 
+      get_option('fastly_api_hostname'),
+      get_option('fastly_api_port')
+    );
+  }
+  
+  /**
+   * Sends a purge request for the given url.
+   * @param $url URL to purge from the cache server.
+   */
+  function purge($urls) {
+    $this->api->purge($urls);   
+  }
+  
+  /**
+   * Purges all pages on the site.
+   */
+  function purgeAll() {
+    $this->api->purgeAll( get_option('fastly_service_id') );
+  }
+  
+  /**
+   * Purges common pages.
+   */
+  function purgeCommon($call=true) {
+    $urls = array(get_bloginfo('wpurl')."/", get_bloginfo('wpurl')."/feed", get_bloginfo('wpurl')."/feed/atom");
+
+    // TODO Need Regex Support
+    /*
+      if (get_site_option($this->wpv_update_pagenavi_optname) == 1) {
+         $this->WPVarnishPurgerPurgeObject("/page/(.*)");
+      }
+    */
+    return $call ? $this->purge($urls) : $urls;
+  }
+  
+  /**
+   * Purges posts and pages on update.
+   */
+  function purgePost($postId, $call=true) {
+    $urls = array( get_permalink($postId) );
+    return $call ? $this->purge($urls) : $urls;
+  }
+
+  /**
+   * Purges objects that depend on the post.
+   */
+  function purgePostDependencies($postId, $call=true) { 
+    $urls = array();
+    $urls = array_merge($urls, $this->purgeCommon(false));
+    $urls = array_merge($urls, $this->purgeCategories($postId, false));
+    $urls = array_merge($urls, $this->purgeArchives($postId, false));
+    $urls = array_merge($urls, $this->purgeTags($postId, false));
+    return $call ? $this->purge($urls) : $urls;
+  }
+  
+  /**
+   * Purges categories associated with a post.
+   * @param $postId Id of the post.
+   */
+  function purgeCategories($postId, $call=true) {
+    $urls       = array();
+    $categories = get_the_category($postId);
+    foreach ($categories as $cat) {
+      $urls = array_merge($urls, $this->purgeCategory($cat->cat_ID, false));
+    }
+    return $call ? $this->purge($urls) : $urls;
+  }
+  
+  /**
+   * Purges post comments.
+   */
+  function purgeComments($commentId, $call=true) {
+    $comment  = get_comment($commentId);
+    $approved = $comment->comment_approved;
+    $urls     = array();
+
+    if ($approved == 1 || $approved == 'trash') {
+      $postId = $comment->comment_post_ID;
+      #$this->purge('/\\\?comments_popup=' . $postId);
+      $urls[] = get_bloginfo('wpurl') . '/?comments_popup=' . $postId;
+      
+      // TODO Need Regex Support
+      /*
+      if (get_site_option($this->wpv_update_commentnavi_optname) == 1) {
+        $this->purge('/\\\?comments_popup=' . $postId . '&(.*)');
+      }
+      */
+    }
+    return $call ? $this->purge($urls) : $urls;
+  }
+  
+  /**
+   * Purges links.
+   */
+  function purgeLinks() {
+    if (is_active_widget(false, false, 'links')) {
+        $this->purgeAll();
+    }
+  }
+  
+  /**
+   * Purges post categories.
+   * @param $categoryId Id of the category to purge.
+   */
+  function purgeCategory($categoryId, $call=true) {
+    $urls = array();
+    if (is_active_widget(false, false, 'categories')) {
+      $this->purgeAll();
+    }
+    else {
+      $urls[] = get_category_link($categoryId);
+    }
+    return $call ? $this->purge($urls) : $urls;
+  }
+
+  /**
+   * Purges link categories.
+   * @param $categoryId Id of the category to purge.
+   */
+  function purgeLinkCategory($categoryId) {
+    if (is_active_widget(false,false,'links')){
+      $this->purgeAll();
+    }
+  }
+
+  /**
+   * Purges a tag category.
+   * @param $categoryId Id of the category to purge.
+   */
+  function purgeTagCategory($categoryId, $call=true) {
+    $urls = array( get_tag_link($categoryId) );
+    return $call ? $this->purge($urls) : $urls;
+  }
+  
+  /**
+   * Purges archives pages.
+   * @param $postId Id of the post that triggered the purge.
+   */
+  function purgeArchives($postId, $call=true) {
+    $urls = array(
+      get_day_link(get_post_time('Y',false,$postId), get_post_time('m',true,$postId),get_post_time('d',true,$postId)),
+      get_month_link(get_post_time('Y',false,$postId), get_post_time('m',true,$postId)),
+      get_year_link(get_post_time('Y',false,$postId)),
+    );
+
+    return $call ? $this->purge($urls) : $urls;
+  }  
+
+  /**
+   * Purges tags associated with a post.
+   * @param $postId Id of the post being purged.
+   */
+  function purgeTags($postId, $call=true) {
+    $urls = array();
+    $tags = wp_get_post_tags($postId);
+    foreach ($tags as $tag) {
+      $urls = array_merge($urls, $this->purgeTagCategory($tag->term_id, false));
+    }
+    return $call ? $this->purge($urls) : $urls;
+  }
+
+  /**
+   * Handles post status purges.
+   */
+  function purgePostStatus($new_status, $old_status, $post, $call=true) {
+    $urls = array();
+    $urls = array_merge($urls, $this->purgePost($post->ID, false));
+    $urls = array_merge($urls, $this->purgePostDependencies($post->ID, false));
+    return $call ? $this->purge($urls) : $urls;
+  }
+}
+
+// "While mona lisas and mad hatters, sons of bankers, sons of lawyers turn around and say good morning to the night..." -- Elton John
+
 ?>
\ No newline at end of file

programming_language: PHP
filename: fastly.css
change_type: ModificationType.MODIFY
diff: @@ -1,65 +1,65 @@
-/*
- * Fastly Wordpress Plugin
- * Admin Interface Styles
- * @author Ryan Sandor Richards
- */
-#fastly-admin h1 {
-  float: left;
-}
-
-#fastly-admin .content {
-  padding: 10px 0 0 20px;
-  margin: 10px 0 0 200px;
-}
-
-#fastly-admin .content .button-row {
-  margin: 20px 0;
-  line-height: 20px;
-}
-
-#fastly-admin .error {
-  background-color: #faa;
-  padding: 4px;
-}
-
-#fastly-admin .error-flash {
-  color: red;
-  font-weight: bold;
-}
-
-#fastly-admin a.disabled {
-  cursor: default !important;
-}
-
-#fastly-admin .loading {
-  display: none;
-  margin-bottom: -3px;
-  margin-left: 4px;
-}
-
-#fastly-admin a.advanced {
-  font-size: 9px;
-}
-
-#fastly-admin fieldset {
-  -moz-border-radius: 6px;
-  border-radius: 6px;
-  background: #efefef;
-  padding: 6px;
-  width: 500px;
-}
-
-#fastly-admin fieldset.advanced {
-  display: none;
-}
-
-#fastly-admin input.text {
-  font-size: 18px;
-  padding: 4px;
-  width: 300px;
-}
-
-#fastly-admin fieldset input.text {
-  width: 99%;
-}
-
+/*
+ * Fastly Wordpress Plugin
+ * Admin Interface Styles
+ * @author Ryan Sandor Richards
+ */
+#fastly-admin h1 {
+  float: left;
+}
+
+#fastly-admin .content {
+  padding: 10px 0 0 20px;
+  margin: 10px 0 0 200px;
+}
+
+#fastly-admin .content .button-row {
+  margin: 20px 0;
+  line-height: 20px;
+}
+
+#fastly-admin .error {
+  background-color: #faa;
+  padding: 4px;
+}
+
+#fastly-admin .error-flash {
+  color: red;
+  font-weight: bold;
+}
+
+#fastly-admin a.disabled {
+  cursor: default !important;
+}
+
+#fastly-admin .loading {
+  display: none;
+  margin-bottom: -3px;
+  margin-left: 4px;
+}
+
+#fastly-admin a.advanced {
+  font-size: 9px;
+}
+
+#fastly-admin fieldset {
+  -moz-border-radius: 6px;
+  border-radius: 6px;
+  background: #efefef;
+  padding: 6px;
+  width: 500px;
+}
+
+#fastly-admin fieldset.advanced {
+  display: none;
+}
+
+#fastly-admin input.text {
+  font-size: 18px;
+  padding: 4px;
+  width: 300px;
+}
+
+#fastly-admin fieldset input.text {
+  width: 99%;
+}
+

programming_language: CSS
filename: fastly.js
change_type: ModificationType.MODIFY
diff: @@ -1,200 +1,207 @@
-/*
- * Fastly Wordpress Plugin
- * Admin Interface Scripting
- * @author Ryan Sandor Richards
- */
-window.Fastly = (function($) {
-  var content, page, templates;
-
-  /**
-   * Validation information (emancipation proclaimation)
-   */
-  var validation = {
-    '#email': {
-      regex: /^[A-Z0-9._%+-]+@[A-Z0-9.-]+\.[A-Z]{2,4}$/i,
-      message: 'Please provide a valid email address.'
-    },
-    '#name': {
-      message: 'Please enter your name.'
-    },
-    '#agree_tos': {
-      checkbox: 'checked',
-      message: 'Please agree to the Fastly terms of service.'
-    },
-    '#customer': {
-      message: 'Please enter a customer (blog) name.'
-    },
-    '#website_address': {
-      regex: /^http:\/\/.+$/,
-      message: 'Please enter a valid website address'
-    },
-    '#address': {
-      regex: /(\d+\.)+/,
-      message: 'Please provide a valid IP address.'
-    }
-  }
-
-  /**
-   * UI Event Handlers
-   */
-  var events = {
-    /**
-     * Welcome page "existing account" button.
-     */
-    'click .welcome .configure': function(e) {
-      loadPage('configure');
-      $.ajax({
-        url: ajaxurl, 
-        data: {action: 'set_page', page: 'configure'}
-      });
-    },
-    
-    /**
-     * Signup Form Submit.
-     */
-    'click .signup .submit': function(e) {
-      var email = $('#email'),
-        agree_tos = $('#agree_tos'),
-        name = $('#name'),
-        customer = $('#customer'),
-        address = $('#address'),
-        website_address = $('#website_address'),
-        flash = $('#fastly-admin .error-flash'),
-        messages = [];
-      
-      flash.html('');
-      $('#email, #agree_tos_label').removeClass('error');
-
-      // Perform validation checks
-      for (var sel in validation) {
-        var element = $(sel),
-          value = $.trim( element.val() ),
-          rules = validation[sel];
-        
-        if (typeof rules.regex != "undefined") {
-          if (!value.match(rules.regex)) {
-            messages.push(rules.message);
-          }
-        }
-        else if (typeof rules.checkbox != "undefined") {
-          if (typeof element.attr('checked') == "undefined" || element.attr('checked') == null) {
-            messages.push(rules.message);
-          }
-        }
-        else if (!value) {
-          messages.push(rules.message);
-        }
-      }
-
-      if (messages.length > 0) {
-        flash.html( messages.join('<br>') );
-        return;
-      }
-      
-      // Send the sign up request
-      function disable() {
-        $(e.target).addClass('disabled');
-        $('fieldset input').attr('disabled', true);
-        $('.loading').show();
-      }
-      
-      function enable() {
-        $(e.target).removeClass('disabled');
-        $('fieldset input').attr('disabled', false);
-        $('.loading').hide();
-      }
-      
-      disable();
-      
-      $.ajax({
-        url: ajaxurl,
-        data: {
-          action: 'sign_up',
-          customer: customer.val(),
-          name: name.val(),
-          email: email.val(),
-          address: address.val(),
-          website_address: website_address.val()
-        },
-        dataType: 'json',
-        success: function(response) {
-          if (response.status == "success") {
-            window.location.reload();
-          }
-          else {
-            flash.html(response.msg);
-            enable();
-          }
-        },
-        error: function() {
-          flash.html("An error occurred while connecting to the fastly API, please try your request again.");
-          enable();
-        }
-      });
-    },
-    
-    /**
-     * Toggle advanced settings.
-     */
-    'click .configure a.advanced': function(e) {
-      $('fieldset.advanced').toggle(250);
-    }
-  };
-  
-  /**
-   * Initalizes events for each of the pages.
-   */
-  function initEvents() {
-    for (var k in events) {
-      var parts = k.match(/([^\s]+)\s+(.*)/),
-        name = parts[1],
-        selector = parts[2];
-        
-      // Trixy hobittses...
-      $(selector).live(name, (function(handler) {
-        return function(e) {
-          if ($(e.target).hasClass('disabled') && $(e.target).hasClass('button')) {
-            e.stopPropagation();
-            e.preventDefault();
-            return;
-          }
-          return handler(e);
-        };
-      })(events[k]));
-    }
-  }
-  
-  /**
-   * Loads the appropriate page given a name.
-   */
-  function loadPage(name) {
-    if (typeof templates[name] == "undefined" || !templates[name]) {
-      throw "Fastly.loadPage(): Undefined template '" + name + "'";
-    }
-    content.html( templates[name] );
-  }
-  
-  
-  // Set content element
-  $(function() { content = $('#fastly-admin .content'); });
-  
-  /**
-   * Public interface.
-   */
-  return {
-    /**
-     * Initializes the fastly pluging configuration page.
-     * @param p Current page the user is on.
-     * @param t A string containing the JSON representation of the page templates object.
-     */
-    init: function(p, t) {
-      page = p;
-      templates = t;
-      initEvents();
-    },
-    
-    loadPage: loadPage
-  };
-})(jQuery)
-
-// We're the last living souls...
\ No newline at end of file
+/*
+ * Fastly Wordpress Plugin
+ * Admin Interface Scripting
+ * @author Ryan Sandor Richards
+ */
+window.Fastly = (function($) {
+  var content, page, templates;
+
+  /**
+   * Validation information (emancipation proclaimation)
+   */
+  var validation = {
+    '#email': {
+      regex: /^[A-Z0-9._%+-]+@[A-Z0-9.-]+\.[A-Z]{2,4}$/i,
+      message: 'Please provide a valid email address.'
+    },
+    '#name': {
+      message: 'Please enter your name.'
+    },
+    '#agree_tos': {
+      checkbox: 'checked',
+      message: 'Please agree to the Fastly terms of service.'
+    },
+    '#customer': {
+      message: 'Please enter a customer (blog) name.'
+    },
+    '#website_address': {
+      regex: /^http:\/\/.+$/,
+      message: 'Please enter a valid website address'
+    },
+    '#address': {
+      regex: /(\d+\.)+/,
+      message: 'Please provide a valid IP address.'
+    }
+  }
+
+  /**
+   * UI Event Handlers
+   */
+  var events = {
+    /**
+     * Welcome page "existing account" button.
+     */
+    'click .welcome .configure': function(e) {
+      loadPage('configure');
+      $.ajax({
+        url: ajaxurl, 
+        data: {
+          action: 'set_page',
+          page: 'configure',
+          //fastlyNonce provided by wp_localize_script() in admin.php
+          nonce: fastlyNonce
+        }
+      });
+    },
+    
+    /**
+     * Signup Form Submit.
+     */
+    'click .signup .submit': function(e) {
+      var email = $('#email'),
+        agree_tos = $('#agree_tos'),
+        name = $('#name'),
+        customer = $('#customer'),
+        address = $('#address'),
+        website_address = $('#website_address'),
+        flash = $('#fastly-admin .error-flash'),
+        messages = [];
+      
+      flash.html('');
+      $('#email, #agree_tos_label').removeClass('error');
+
+      // Perform validation checks
+      for (var sel in validation) {
+        var element = $(sel),
+          value = $.trim( element.val() ),
+          rules = validation[sel];
+        
+        if (typeof rules.regex != "undefined") {
+          if (!value.match(rules.regex)) {
+            messages.push(rules.message);
+          }
+        }
+        else if (typeof rules.checkbox != "undefined") {
+          if (typeof element.attr('checked') == "undefined" || element.attr('checked') == null) {
+            messages.push(rules.message);
+          }
+        }
+        else if (!value) {
+          messages.push(rules.message);
+        }
+      }
+
+      if (messages.length > 0) {
+        flash.html( messages.join('<br>') );
+        return;
+      }
+      
+      // Send the sign up request
+      function disable() {
+        $(e.target).addClass('disabled');
+        $('fieldset input').attr('disabled', true);
+        $('.loading').show();
+      }
+      
+      function enable() {
+        $(e.target).removeClass('disabled');
+        $('fieldset input').attr('disabled', false);
+        $('.loading').hide();
+      }
+      
+      disable();
+      
+      $.ajax({
+        url: ajaxurl,
+        data: {
+          action: 'sign_up',
+          customer: customer.val(),
+          name: name.val(),
+          email: email.val(),
+          address: address.val(),
+          website_address: website_address.val(),
+          //fastlyNonce provided by wp_localize_script() in admin.php
+          nonce: fastlyNonce
+        },
+        dataType: 'json',
+        success: function(response) {
+          if (response.status == "success") {
+            window.location.reload();
+          }
+          else {
+            flash.html(response.msg);
+            enable();
+          }
+        },
+        error: function() {
+          flash.html("An error occurred while connecting to the fastly API, please try your request again.");
+          enable();
+        }
+      });
+    },
+    
+    /**
+     * Toggle advanced settings.
+     */
+    'click .configure a.advanced': function(e) {
+      $('fieldset.advanced').toggle(250);
+    }
+  };
+  
+  /**
+   * Initalizes events for each of the pages.
+   */
+  function initEvents() {
+    for (var k in events) {
+      var parts = k.match(/([^\s]+)\s+(.*)/),
+        name = parts[1],
+        selector = parts[2];
+        
+      // Trixy hobittses...
+      $(selector).live(name, (function(handler) {
+        return function(e) {
+          if ($(e.target).hasClass('disabled') && $(e.target).hasClass('button')) {
+            e.stopPropagation();
+            e.preventDefault();
+            return;
+          }
+          return handler(e);
+        };
+      })(events[k]));
+    }
+  }
+  
+  /**
+   * Loads the appropriate page given a name.
+   */
+  function loadPage(name) {
+    if (typeof templates[name] == "undefined" || !templates[name]) {
+      throw "Fastly.loadPage(): Undefined template '" + name + "'";
+    }
+    content.html( templates[name] );
+  }
+  
+  
+  // Set content element
+  $(function() { content = $('#fastly-admin .content'); });
+  
+  /**
+   * Public interface.
+   */
+  return {
+    /**
+     * Initializes the fastly pluging configuration page.
+     * @param p Current page the user is on.
+     * @param t A string containing the JSON representation of the page templates object.
+     */
+    init: function(p, t) {
+      page = p;
+      templates = t;
+      initEvents();
+    },
+    
+    loadPage: loadPage
+  };
+})(jQuery)
+
+// We're the last living souls...

programming_language: JavaScript