Skip to content

Commit

Permalink
implemented new json escaper
Browse files Browse the repository at this point in the history
  • Loading branch information
mvdwerve committed Apr 1, 2020
1 parent 61a2743 commit 8646378
Show file tree
Hide file tree
Showing 7 changed files with 351 additions and 6 deletions.
48 changes: 48 additions & 0 deletions src/builtin/jsondecode.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
/**
* Jsondecode.h
*
* Built-in "|jsondecode" modifier
*
* @author Michael van der Werve <[email protected]>
* @copyright 2020 Copernica BV
*/

/**
* Namespace
*/
namespace SmartTpl { namespace Internal {

/**
* Class definition
*/
class JsondecodeModifier : public Modifier
{
public:
/**
* Destructor
*/
virtual ~JsondecodeModifier() {};

/**
* Modify a value object
* @param input
* @param params Parameters used for this modification
* @return Value
*/
VariantValue modify(const Value &input, const SmartTpl::Parameters &params) override
{
// Get the json encoder
const Escaper *escaper = Escaper::get("json");

// Turn our input into a string
std::string output(input.toString());

// Call encode and return the output
return escaper->decode(output);
}
};

/**
* End namespace
*/
}}
48 changes: 48 additions & 0 deletions src/builtin/jsonencode.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
/**
* Jsonencode.h
*
* Built-in "|jsonencode" modifier
*
* @author Michael van der Werve <[email protected]>
* @copyright 2020 Copernica BV
*/

/**
* Namespace
*/
namespace SmartTpl { namespace Internal {

/**
* Class definition
*/
class JsonencodeModifier : public Modifier
{
public:
/**
* Destructor
*/
virtual ~JsonencodeModifier() {};

/**
* Modify a value object
* @param input
* @param params Parameters used for this modification
* @return Value
*/
VariantValue modify(const Value &input, const SmartTpl::Parameters &params) override
{
// Get the json encoder
const Escaper *escaper = Escaper::get("json");

// Turn our input into a string
std::string output(input.toString());

// Call encode and return the output
return escaper->encode(output);
}
};

/**
* End namespace
*/
}}
6 changes: 5 additions & 1 deletion src/data.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@
* Implementation file for the Data class
*
* @author Emiel Bruijntjes <[email protected]>
* @copyright 2014 Copernica BV
* @copyright 2014 - 2020 Copernica BV
*/

/**
Expand Down Expand Up @@ -43,6 +43,8 @@ static Internal::StrPosModifier strpos;
static Internal::StrStrModifier strstr;
static Internal::NumberFormatModifier number_format;
static Internal::DateFormatModifier date_format;
static Internal::JsonencodeModifier jsonencode;
static Internal::JsondecodeModifier jsondecode;
static Internal::UrlencodeModifier urlencode;
static Internal::UrldecodeModifier urldecode;
static Internal::Md5Modifier md5;
Expand Down Expand Up @@ -84,6 +86,8 @@ Data::Data()
{"strpos", &strpos},
{"number_format", &number_format},
{"date_format", &date_format},
{"jsonencode", &jsonencode},
{"jsondecode", &jsondecode},
{"urlencode", &urlencode},
{"urldecode", &urldecode},
{"range", &range_modifier}}) // register built-in modifiers
Expand Down
5 changes: 3 additions & 2 deletions src/escaper.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@
* Implementation of the Escaper class
*
* @author Toon Schoenmakers <[email protected]>
* @copyright 2014 Copernica BV
* @copyright 2014 - 2020 Copernica BV
*/

#include "includes.h"
Expand Down Expand Up @@ -34,6 +34,7 @@ Escaper::Escaper(const char *name)
*/
static NullEscaper _null;
static HtmlEscaper _html;
static JsonEscaper _json;
static UrlEscaper _url;
static Base64Escaper _base64;

Expand All @@ -57,4 +58,4 @@ Escaper* Escaper::get(const std::string &encoding)
/**
* End namespace
*/
}}
}}
185 changes: 185 additions & 0 deletions src/escapers/json.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,185 @@
/**
* Json.h
*
* A JSON en/decoder
*
* @author Michael van der Werve <[email protected]>
* @copyright 2020 Copernica BV
*/

/**
* Namespace
*/
namespace SmartTpl { namespace Internal {

/**
* Class definition
*/
class JsonEscaper : public Escaper
{
private:
/**
* Hex lookup table
*/
static constexpr const char *hex = "0123456789abcdefABCDEF";

/**
* Helper method to convert a hex digit to decimal
* @param digit
* @return uint8_t
*/
inline uint8_t decimal(uint8_t digit) const
{
// convert hex digit to decimal (doesnt check for hex number validity)
return (digit <= 9) ? digit - '0' : ((digit & 7) + 9);
}

/**
* Helper method to convert two hex digits to a byte
* @param digit1
*/
uint8_t byte(uint8_t left, uint8_t right) const
{
// reverse the byte sequence
return decimal(left) << 4 & decimal(right);
}

public:
/**
* Constructor, in case the openssl library failed to load we simply are
* giving our base constructor a nullptr so it doesn't register us
*/
JsonEscaper() : Escaper("json") {};

/**
* Destructor
*/
virtual ~JsonEscaper() {}

/**
* Encode the given input
* It is probably a good idea to directly modify the input instead of making
* a copy and modifying that.
* @param input
*/
virtual std::string &encode(std::string &input) const override
{
// we need to make a copy, because there's no 1-1 transformation on characters
std::string output;

// reserve at least enough bytes
output.reserve(input.size());

// iterate over the characters
for (const uint8_t &c : input)
{
// in the first 32 bits we always have to escape
if (c < 32 || c == '"' || c == '\\')
{
switch(c) {
case '\b': output.append("\\b"); break;
case '\n': output.append("\\n"); break;
case '\r': output.append("\\r"); break;
case '\t': output.append("\\t"); break;
case '\f': output.append("\\f"); break;
case '"': output.append("\\\""); break;
case '\\': output.append("\\\\"); break;
default:
// write a byte in hex
output.append("\\x");
output.push_back(hex[c >> 4]);
output.push_back(hex[c & 0xf]);
}
}

// append character normally
else output.push_back(c);
}

// swap the output and the input
std::swap(output, input);

// return the input
return input;
}

/**
* Decode the given input
* It is probably a good idea to directly modify the input instead of making
* a copy and modifying that.
* @param input
*/
virtual std::string &decode(std::string &input) const override
{
// we need to make a copy, because there's no 1-1 transformation on characters
std::string output;

// reserve at least enough bytes
output.reserve(input.size());

// store the 'current' pointer
const char *current = input.data();

// the next character that is escaped
const char *next = nullptr;

// we always remove a backslash and move one forward, unless it is followed by an x
while ((next = strchr(current, '\\')))
{
// wirte everything since last
output.append(current, next - current);

// we now have a new current, because we'll consume two extra bytes, the found \ and a character (skip those)
current = next + 2;

// parse the next character at this point, it is special
switch (next[1]) {
case 'b': output.push_back('\b'); break;
case 'n': output.push_back('\n'); break;
case 'r': output.push_back('\r'); break;
case 't': output.push_back('\t'); break;
case 'f': output.push_back('\f'); break;
case '"': output.push_back('"'); break;
case '\\': output.push_back('\\'); break;
// solidus should also be escapable, but does not need to be escaped
case '/': output.push_back('/'); break;
case 'x':
// we consume two extra bytes, so check if they are actually valid. since this is a json string, we can easily
// check with simply null character checking
if (current[0] && !current[1] && isxdigit(current[0]) && isxdigit(current[1]))
{
// add a single byte, we consume 3
output.push_back(byte(current[0], current[1]));

// we consumed two extra bytes
current += 2;
}

// if we arrived here, there were no more bytes or they're not hex, and so we're in an invalid state.
// we fall through to the error-correction.
case '\0':
default:
// this is not allowed, some unknown character was escaped. return the result so far
std::swap(output, input);

// and we leap out of the function
return input;
}
}

// append the rest of the output
output.append(current);

// swap the output with the input
std::swap(output, input);

// and return the input
return input;
}

};

/**
* End namespace
*/
}}
3 changes: 3 additions & 0 deletions src/includes.h
Original file line number Diff line number Diff line change
Expand Up @@ -83,6 +83,7 @@
#include "escapers/html.h"
#include "escapers/url.h"
#include "escapers/base64.h"
#include "escapers/json.h"
#include "builtin/tolower.h"
#include "builtin/toupper.h"
#include "builtin/cat.h"
Expand All @@ -106,6 +107,8 @@
#include "builtin/strstr.h"
#include "builtin/strpos.h"
#include "builtin/number_format.h"
#include "builtin/jsonencode.h"
#include "builtin/jsondecode.h"
#include "builtin/urlencode.h"
#include "builtin/urldecode.h"
#include "builtin/md5.h"
Expand Down
Loading

0 comments on commit 8646378

Please sign in to comment.