Repository: nifi-minifi-cpp Updated Branches: refs/heads/master 15d9a6911 -> 20339fbee
MINIFICPP-469 Added encode/decode base64 EL functions This closes #307. Signed-off-by: Aldrin Piri <[email protected]> Project: http://git-wip-us.apache.org/repos/asf/nifi-minifi-cpp/repo Commit: http://git-wip-us.apache.org/repos/asf/nifi-minifi-cpp/commit/20339fbe Tree: http://git-wip-us.apache.org/repos/asf/nifi-minifi-cpp/tree/20339fbe Diff: http://git-wip-us.apache.org/repos/asf/nifi-minifi-cpp/diff/20339fbe Branch: refs/heads/master Commit: 20339fbee1d915b03baeda288d9bae94d69f07f1 Parents: 15d9a69 Author: Andrew I. Christianson <[email protected]> Authored: Tue Apr 24 13:06:46 2018 -0400 Committer: Aldrin Piri <[email protected]> Committed: Mon May 14 16:37:12 2018 -0400 ---------------------------------------------------------------------- EXPRESSIONS.md | 38 +++- extensions/expression-language/Expression.cpp | 32 ++++ extensions/expression-language/base64.h | 188 +++++++++++++++++++ .../ExpressionLanguageTests.cpp | 24 +++ 4 files changed, 280 insertions(+), 2 deletions(-) ---------------------------------------------------------------------- http://git-wip-us.apache.org/repos/asf/nifi-minifi-cpp/blob/20339fbe/EXPRESSIONS.md ---------------------------------------------------------------------- diff --git a/EXPRESSIONS.md b/EXPRESSIONS.md index f2eebb2..d5a34bb 100644 --- a/EXPRESSIONS.md +++ b/EXPRESSIONS.md @@ -215,6 +215,8 @@ token, filename. - [`unescapeCsv`](#unescapecsv) - [`urlEncode`](#urlencode) - [`urlDecode`](#urldecode) +- [`base64Encode`](#base64encode) +- [`base64Decode`](#base64decode) ### Subjectless Functions @@ -235,8 +237,6 @@ token, filename. - `escapeHtml4` - `unescapeHtml3` - `unescapeHtml4` -- `base64Encode` -- `base64Decode` ### Date Manipulation @@ -1477,6 +1477,40 @@ If we have a URL-Encoded attribute named "url" with the value "some%20value%20with%20spaces", then the Expression `${url:urlDecode()}` will return "some value with spaces". +### base64Encode + +**Description**: Returns a Base64 encoded string. This is useful for being able +to transfer binary data as ascii. + +**Subject Type**: String + +**Arguments**: No arguments + +**Return Type**: String + +**Examples**: + +We can Base64-Encoded an attribute named "payload" by using the Expression +`${payload:base64Encode()}` If the attribute payload had a value of +"admin:admin" then the Expression `${payload:base64Encode()}` will return +"YWRtaW46YWRtaW4=". + +### base64Decode + +**Description**: Reverses the Base64 encoding on given string. + +**Subject Type**: String + +**Arguments**: No arguments + +**Return Type**: String + +**Examples**: + +If we have a Base64-Encoded attribute named "payload" with the value +"YWRtaW46YWRtaW4=", then the Expression `${payload:base64Decode()}` will return +"admin:admin". + ## Subjectless Functions While the majority of functions in the Expression Language are called by using http://git-wip-us.apache.org/repos/asf/nifi-minifi-cpp/blob/20339fbe/extensions/expression-language/Expression.cpp ---------------------------------------------------------------------- diff --git a/extensions/expression-language/Expression.cpp b/extensions/expression-language/Expression.cpp index b26a515..4a93b1c 100644 --- a/extensions/expression-language/Expression.cpp +++ b/extensions/expression-language/Expression.cpp @@ -31,6 +31,7 @@ #include <curl/curl.h> #include <netdb.h> #include <arpa/inet.h> +#include "base64.h" #include "Driver.h" namespace org { @@ -1345,6 +1346,33 @@ Value expr_urlDecode(const std::vector<Value> &args) { } } +Value expr_base64Encode(const std::vector<Value> &args) { + auto arg_0 = args[0].asString(); + char *b64_out = nullptr; + auto b64_len = Curl_base64_encode(arg_0.c_str(), arg_0.length(), &b64_out); + if (b64_out) { + std::string result(b64_out, b64_len); + free(b64_out); + return Value(result); + } else { + throw std::runtime_error("Failed to encode base64"); + } +} + +Value expr_base64Decode(const std::vector<Value> &args) { + auto arg_0 = args[0].asString(); + unsigned char *decode_out = nullptr; + // size_t Curl_base64_decode(const char *src, unsigned char **outptr) + auto out_len = Curl_base64_decode(arg_0.c_str(), &decode_out); + if (decode_out) { + std::string result(reinterpret_cast<char *>(decode_out), out_len); + free(decode_out); + return Value(result); + } else { + throw std::runtime_error("Failed to encode base64"); + } +} + #ifdef EXPRESSION_LANGUAGE_USE_REGEX Value expr_replace(const std::vector<Value> &args) { @@ -1722,6 +1750,10 @@ Expression make_dynamic_function(const std::string &function_name, return make_dynamic_function_incomplete<expr_urlEncode>(function_name, args, 0); } else if (function_name == "urlDecode") { return make_dynamic_function_incomplete<expr_urlDecode>(function_name, args, 0); + } else if (function_name == "base64Encode") { + return make_dynamic_function_incomplete<expr_base64Encode>(function_name, args, 0); + } else if (function_name == "base64Decode") { + return make_dynamic_function_incomplete<expr_base64Decode>(function_name, args, 0); #ifdef EXPRESSION_LANGUAGE_USE_REGEX } else if (function_name == "replace") { return make_dynamic_function_incomplete<expr_replace>(function_name, args, 2); http://git-wip-us.apache.org/repos/asf/nifi-minifi-cpp/blob/20339fbe/extensions/expression-language/base64.h ---------------------------------------------------------------------- diff --git a/extensions/expression-language/base64.h b/extensions/expression-language/base64.h new file mode 100644 index 0000000..c2c48ba --- /dev/null +++ b/extensions/expression-language/base64.h @@ -0,0 +1,188 @@ +/*************************************************************************** + * _ _ ____ _ + * Project ___| | | | _ \| | + * / __| | | | |_) | | + * | (__| |_| | _ <| |___ + * \___|\___/|_| \_\_____| + * + * Copyright (C) 1998 - 2007, Daniel Stenberg, <[email protected]>, et al. + * + * This software is licensed as described in the file COPYING, which + * you should have received as part of this distribution. The terms + * are also available at http://curl.haxx.se/docs/copyright.html. + * + * You may opt to use, copy, modify, merge, publish, distribute and/or sell + * copies of the Software, and permit persons to whom the Software is + * furnished to do so, under the terms of the COPYING file. + * + * This software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY + * KIND, either express or implied. + * + * $Id$ + ***************************************************************************/ + +#ifndef NIFI_MINIFI_CPP_BASE64_H +#define NIFI_MINIFI_CPP_BASE64_H + +/* ---- Base64 Encoding/Decoding Table --- */ +static const char table64[]= + "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/"; + +static void decodeQuantum(unsigned char *dest, const char *src) +{ + unsigned int x = 0; + int i; + char *found; + + for(i = 0; i < 4; i++) { + if((found = strchr((char *)table64, src[i]))) + x = (x << 6) + (unsigned int)(found - table64); + else if(src[i] == '=') + x = (x << 6); + } + + dest[2] = (unsigned char)(x & 255); + x >>= 8; + dest[1] = (unsigned char)(x & 255); + x >>= 8; + dest[0] = (unsigned char)(x & 255); +} + + +/* + * Curl_base64_decode() + * + * Given a base64 string at src, decode it and return an allocated memory in + * the *outptr. Returns the length of the decoded data. + */ +size_t Curl_base64_decode(const char *src, unsigned char **outptr) +{ + int length = 0; + int equalsTerm = 0; + int i; + int numQuantums; + unsigned char lastQuantum[3]; + size_t rawlen=0; + unsigned char *newstr; + + *outptr = NULL; + + while((src[length] != '=') && src[length]) + length++; + /* A maximum of two = padding characters is allowed */ + if(src[length] == '=') { + equalsTerm++; + if(src[length+equalsTerm] == '=') + equalsTerm++; + } + numQuantums = (length + equalsTerm) / 4; + + /* Don't allocate a buffer if the decoded length is 0 */ + if (numQuantums <= 0) + return 0; + + rawlen = (numQuantums * 3) - equalsTerm; + + /* The buffer must be large enough to make room for the last quantum + (which may be partially thrown out) and the zero terminator. */ + newstr = static_cast<unsigned char *>(malloc(rawlen+4)); + if(!newstr) + return 0; + + *outptr = newstr; + + /* Decode all but the last quantum (which may not decode to a + multiple of 3 bytes) */ + for(i = 0; i < numQuantums - 1; i++) { + decodeQuantum((unsigned char *)newstr, src); + newstr += 3; src += 4; + } + + /* This final decode may actually read slightly past the end of the buffer + if the input string is missing pad bytes. This will almost always be + harmless. */ + decodeQuantum(lastQuantum, src); + for(i = 0; i < 3 - equalsTerm; i++) + newstr[i] = lastQuantum[i]; + + newstr[i] = 0; /* zero terminate */ + return rawlen; +} + +/* + * Curl_base64_encode() + * + * Returns the length of the newly created base64 string. The third argument + * is a pointer to an allocated area holding the base64 data. If something + * went wrong, -1 is returned. + * + */ +size_t Curl_base64_encode(const char *inp, size_t insize, char **outptr) +{ + unsigned char ibuf[3]; + unsigned char obuf[4]; + int i; + int inputparts; + char *output; + char *base64data; + + char *indata = (char *)inp; + + *outptr = NULL; /* set to NULL in case of failure before we reach the end */ + + if(0 == insize) + insize = strlen(indata); + + base64data = output = (char*)malloc(insize*4/3+4); + if(NULL == output) + return 0; + + while(insize > 0) { + for (i = inputparts = 0; i < 3; i++) { + if(insize > 0) { + inputparts++; + ibuf[i] = *indata; + indata++; + insize--; + } + else + ibuf[i] = 0; + } + + obuf[0] = (unsigned char) ((ibuf[0] & 0xFC) >> 2); + obuf[1] = (unsigned char) (((ibuf[0] & 0x03) << 4) | \ + ((ibuf[1] & 0xF0) >> 4)); + obuf[2] = (unsigned char) (((ibuf[1] & 0x0F) << 2) | \ + ((ibuf[2] & 0xC0) >> 6)); + obuf[3] = (unsigned char) (ibuf[2] & 0x3F); + + switch(inputparts) { + case 1: /* only one byte read */ + snprintf(output, 5, "%c%c==", + table64[obuf[0]], + table64[obuf[1]]); + break; + case 2: /* two bytes read */ + snprintf(output, 5, "%c%c%c=", + table64[obuf[0]], + table64[obuf[1]], + table64[obuf[2]]); + break; + default: + snprintf(output, 5, "%c%c%c%c", + table64[obuf[0]], + table64[obuf[1]], + table64[obuf[2]], + table64[obuf[3]] ); + break; + } + output += 4; + } + *output=0; + *outptr = base64data; /* make it return the actual data memory */ + + return strlen(base64data); /* return the length of the new data */ +} +/* ---- End of Base64 Encoding ---- */ + +#endif //NIFI_MINIFI_CPP_BASE64_H http://git-wip-us.apache.org/repos/asf/nifi-minifi-cpp/blob/20339fbe/libminifi/test/expression-language-tests/ExpressionLanguageTests.cpp ---------------------------------------------------------------------- diff --git a/libminifi/test/expression-language-tests/ExpressionLanguageTests.cpp b/libminifi/test/expression-language-tests/ExpressionLanguageTests.cpp index 5c331be..3d99fd0 100644 --- a/libminifi/test/expression-language-tests/ExpressionLanguageTests.cpp +++ b/libminifi/test/expression-language-tests/ExpressionLanguageTests.cpp @@ -1245,3 +1245,27 @@ TEST_CASE("Length", "[expressionLength]") { // NOLINT flow_file_a->addAttribute("message", "a brand new filename.txt"); REQUIRE(24 == expr({flow_file_a}).asUnsignedLong()); } + +TEST_CASE("Encode B64", "[expressionEncodeB64]") { // NOLINT + auto expr = expression::compile("${message:base64Encode()}"); + + auto flow_file_a = std::make_shared<MockFlowFile>(); + flow_file_a->addAttribute("message", "admin:admin"); + REQUIRE("YWRtaW46YWRtaW4=" == expr({flow_file_a}).asString()); +} + +TEST_CASE("Decode B64", "[expressionDecodeB64]") { // NOLINT + auto expr = expression::compile("${message:base64Decode()}"); + + auto flow_file_a = std::make_shared<MockFlowFile>(); + flow_file_a->addAttribute("message", "YWRtaW46YWRtaW4="); + REQUIRE("admin:admin" == expr({flow_file_a}).asString()); +} + +TEST_CASE("Encode Decode B64", "[expressionEncodeDecodeB64]") { // NOLINT + auto expr = expression::compile("${message:base64Encode():base64Decode()}"); + + auto flow_file_a = std::make_shared<MockFlowFile>(); + flow_file_a->addAttribute("message", "Zero > One < \"two!\" & 'true'"); + REQUIRE("Zero > One < \"two!\" & 'true'" == expr({flow_file_a}).asString()); +}
