diff --git a/server/modules/filter/masking/maskingfiltersession.cc b/server/modules/filter/masking/maskingfiltersession.cc index ea5e3fc1c..a9d2e409f 100644 --- a/server/modules/filter/masking/maskingfiltersession.cc +++ b/server/modules/filter/masking/maskingfiltersession.cc @@ -197,23 +197,23 @@ void MaskingFilterSession::handle_row(GWBUF* pPacket) { case MYSQL_COM_QUERY: { - ComQueryResponse::Row row(response); + ComQueryResponse::TextResultsetRow row(response, m_res.types()); - ComQueryResponse::Row::iterator i = row.begin(); + ComQueryResponse::TextResultsetRow::iterator i = row.begin(); while (i != row.end()) { const MaskingRules::Rule* pRule = m_res.get_rule(); if (pRule) { - LEncString s = *i; + ComQueryResponse::TextResultsetRow::Value value = *i; - if (!s.is_null()) + if (value.is_string()) { + LEncString s = value.as_string(); pRule->rewrite(s); } - MXS_NOTICE("String: %s", (*i).to_string().c_str()); } ++i; } @@ -222,16 +222,16 @@ void MaskingFilterSession::handle_row(GWBUF* pPacket) case MYSQL_COM_STMT_EXECUTE: { - ComQueryResponse::BinaryRow row(response, m_res.types()); + ComQueryResponse::BinaryResultsetRow row(response, m_res.types()); - ComQueryResponse::BinaryRow::iterator i = row.begin(); + ComQueryResponse::BinaryResultsetRow::iterator i = row.begin(); while (i != row.end()) { const MaskingRules::Rule* pRule = m_res.get_rule(); if (pRule) { - ComQueryResponse::BinaryRow::Value value = *i; + ComQueryResponse::BinaryResultsetRow::Value value = *i; if (value.is_string()) { diff --git a/server/modules/filter/masking/mysql.hh b/server/modules/filter/masking/mysql.hh index 98aaf94a9..26b26fcf7 100644 --- a/server/modules/filter/masking/mysql.hh +++ b/server/modules/filter/masking/mysql.hh @@ -473,7 +473,11 @@ inline LEncString::iterator operator - (const LEncString::iterator& it, ptrdiff_ return it; } - +/** + * @class ComPacket + * + * Base-class of all packet classes. + */ class ComPacket { public: @@ -490,45 +494,51 @@ public: protected: ComPacket(GWBUF* pPacket) : m_pPacket(pPacket) - , m_pI(GWBUF_DATA(pPacket)) - , m_packet_len(MYSQL_GET_PAYLOAD_LEN(m_pI)) - , m_packet_no(MYSQL_GET_PACKET_NO(m_pI)) + , m_pData(GWBUF_DATA(pPacket)) + , m_packet_len(MYSQL_GET_PAYLOAD_LEN(m_pData)) + , m_packet_no(MYSQL_GET_PACKET_NO(m_pData)) { - m_pI += MYSQL_HEADER_LEN; + m_pData += MYSQL_HEADER_LEN; } ComPacket(const ComPacket& packet) : m_pPacket(packet.m_pPacket) - , m_pI(GWBUF_DATA(m_pPacket)) + , m_pData(GWBUF_DATA(m_pPacket)) , m_packet_len(packet.m_packet_len) , m_packet_no(packet.m_packet_no) { - m_pI += MYSQL_HEADER_LEN; + m_pData += MYSQL_HEADER_LEN; } GWBUF* m_pPacket; - uint8_t* m_pI; + uint8_t* m_pData; private: uint32_t m_packet_len; uint8_t m_packet_no; }; + +/** + * @class ComResponse + * + * Base-class of all response packet classes. + */ class ComResponse : public ComPacket { public: ComResponse(GWBUF* pPacket) : ComPacket(pPacket) - , m_type(*m_pI) + , m_type(*m_pData) { - ++m_pI; + ++m_pData; } ComResponse(const ComResponse& packet) : ComPacket(packet) , m_type(packet.m_type) { - ++m_pI; + ++m_pData; } uint8_t type() const @@ -555,14 +565,19 @@ protected: uint8_t m_type; }; +/** + * @class ComRequest + * + * Base-class of all request packet classes. + */ class ComRequest : public ComPacket { public: ComRequest(GWBUF* pPacket) : ComPacket(pPacket) - , m_command(*m_pI) + , m_command(*m_pData) { - ++m_pI; + ++m_pData; } uint8_t command() const { return m_command; } @@ -571,33 +586,41 @@ protected: uint8_t m_command; }; -class ComQueryResponseColumnDef : public ComPacket +/** + * @class CQRColumnDef + * + * The column definition of the response of a @c ComQuery. + * + * @attention The name should not be used as such, but always using the + * typedef @c ComQueryResponse::ColumnDef. + */ +class CQRColumnDef : public ComPacket { public: - ComQueryResponseColumnDef(GWBUF* pPacket) + CQRColumnDef(GWBUF* pPacket) : ComPacket(pPacket) - , m_catalog(&m_pI) - , m_schema(&m_pI) - , m_table(&m_pI) - , m_org_table(&m_pI) - , m_name(&m_pI) - , m_org_name(&m_pI) - , m_length_fixed_fields(&m_pI) + , m_catalog(&m_pData) + , m_schema(&m_pData) + , m_table(&m_pData) + , m_org_table(&m_pData) + , m_name(&m_pData) + , m_org_name(&m_pData) + , m_length_fixed_fields(&m_pData) { - m_character_set = *reinterpret_cast(m_pI); - m_pI += 2; + m_character_set = *reinterpret_cast(m_pData); + m_pData += 2; - m_column_length = *reinterpret_cast(m_pI); - m_pI += 4; + m_column_length = *reinterpret_cast(m_pData); + m_pData += 4; - m_type = static_cast(*m_pI); - m_pI += 1; + m_type = static_cast(*m_pData); + m_pData += 1; - m_flags = *reinterpret_cast(m_pI); - m_pI += 2; + m_flags = *reinterpret_cast(m_pData); + m_pData += 2; - m_decimals = *m_pI; - m_pI += 1; + m_decimals = *m_pData; + m_pData += 1; } const LEncString& catalog() const { return m_catalog; } @@ -641,350 +664,402 @@ private: uint8_t m_decimals; }; -class ComQueryResponseRow : public ComPacket +/** + * @class CQRResultsetValue + * + * An instance of this class represents a value in a resultset row. As this + * currently is for the purpose of the masking filter, it effectively is useful + * for accessing NULL and string values. + * + * @attention The name should not be used as such, but instead either + * @c ComQueryResponse::TextResultsetRow::Value or + * @c ComQueryResponse::TextResultsetRow::Value. + */ +class CQRResultsetValue { public: - class iterator : public std::iterator - { - public: - iterator(uint8_t* pI = NULL) - : m_pI(pI) - {} - - iterator& operator++() - { - LEncString s(&m_pI); - return *this; - } - - iterator operator++(int) - { - iterator rv(*this); - ++(*this); - return rv; - } - - bool operator == (const iterator& rhs) const - { - return m_pI == rhs.m_pI; - } - - bool operator != (const iterator& rhs) const - { - return !(*this == rhs); - } - - reference operator*() - { - return LEncString(m_pI); - } - - private: - uint8_t* m_pI; - }; - - ComQueryResponseRow(GWBUF* pPacket) - : ComPacket(pPacket) + CQRResultsetValue() + : m_type(MYSQL_TYPE_NULL) + , m_pData(NULL) { } - ComQueryResponseRow(const ComResponse& packet) - : ComPacket(packet) + CQRResultsetValue(enum_field_types type, uint8_t* pData) + : m_type(type) + , m_pData(pData) { } - iterator begin() + LEncString as_string() { - return iterator(m_pI); + ss_dassert(is_string()); + return LEncString(m_pData); } - iterator end() + bool is_null() const { - uint8_t* pEnd = GWBUF_DATA(m_pPacket) + GWBUF_LENGTH(m_pPacket); - return iterator(pEnd); + return m_type == MYSQL_TYPE_NULL; + } + + bool is_string() const + { + return is_string(m_type); + } + + static bool is_string(enum_field_types type) + { + switch (type) + { + case MYSQL_TYPE_BLOB: + case MYSQL_TYPE_LONG_BLOB: + case MYSQL_TYPE_MEDIUM_BLOB: + case MYSQL_TYPE_STRING: + case MYSQL_TYPE_TINY_BLOB: + case MYSQL_TYPE_VARCHAR: + case MYSQL_TYPE_VAR_STRING: + return true; + + // These, although returned as length-encoded strings, also in the case of + // a binary resultset row, are not are not considered to be strings from the + // perspective of masking. + case MYSQL_TYPE_BIT: + case MYSQL_TYPE_DECIMAL: + case MYSQL_TYPE_ENUM: + case MYSQL_TYPE_GEOMETRY: + case MYSQL_TYPE_NEWDECIMAL: + case MYSQL_TYPE_SET: + return false; + + default: + // Nothing else is considered to be strings even though, in the case of + // a textual resultset, that's what they all are. + return false; + } + } + +protected: + enum_field_types m_type; + uint8_t* m_pData; +}; + +/** + * @class CQRTextResultsetValue + * + * An instance of this class represents a value in a textual resultset row. + * + * @attention The name should not be used as such, but always using the + * typedef @c ComQueryResponse::TextResultsetRow::Value. + */ +class CQRTextResultsetValue : public CQRResultsetValue +{ +public: + CQRTextResultsetValue(enum_field_types type, uint8_t* pData) + : CQRResultsetValue(type, pData) + { + if (*pData == 0xfb) + { + m_type = MYSQL_TYPE_NULL; + } } }; -class ComQueryResponseBinaryRow : public ComPacket +/** + * @class CQRBinaryResultsetValue + * + * An instance of this class represents a value in a binary resultset row. + * + * @attention The name should not be used as such, but always using the + * typedef @c ComQueryResponse::BinaryResultsetRow::Value. + */ +typedef CQRResultsetValue CQRBinaryResultsetValue; + +/** + * @class CQRTextResultsetRowIterator + * + * An STL compatible iterator that iterates over the values in a textual resultset. + * + * @attention The name should not be used as such, but always using the + * typedef @c ComQueryResponse::TextResultset::iterator. + */ +class CQRTextResultsetRowIterator : public std::iterator { public: - /** - * An instance of Value represents a value in a binary resultset. - */ - class Value + typedef CQRTextResultsetValue Value; + + CQRTextResultsetRowIterator(uint8_t* pData, const std::vector& types) + : m_pData(pData) + , m_iTypes(types.begin()) + {} + + CQRTextResultsetRowIterator(uint8_t* pData) + : m_pData(pData) + {} + + CQRTextResultsetRowIterator& operator++() { - public: - Value() - : m_type(MYSQL_TYPE_NULL) - , m_pData(NULL) - { - } + // In the textual protocol, every value is a length encoded string. + LEncString s(&m_pData); + ++m_iTypes; + return *this; + } - Value(enum_field_types type, uint8_t* pData) - : m_type(type) - , m_pData(pData) - { - } + CQRTextResultsetRowIterator operator++(int) + { + CQRTextResultsetRowIterator rv(*this); + ++(*this); + return rv; + } - enum_field_types type() const - { - return m_type; - } + bool operator == (const CQRTextResultsetRowIterator& rhs) const + { + return m_pData == rhs.m_pData; + } - LEncString as_string() - { - ss_dassert(is_string(m_type)); - return LEncString(m_pData); - } + bool operator != (const CQRTextResultsetRowIterator& rhs) const + { + return !(*this == rhs); + } - bool is_string() const - { - return is_string(m_type); - } + CQRTextResultsetValue operator*() + { + return Value(*m_iTypes, m_pData); + } - static bool is_string(enum_field_types type) - { - switch (type) - { - case MYSQL_TYPE_STRING: - case MYSQL_TYPE_VARCHAR: - case MYSQL_TYPE_VAR_STRING: - return true; +private: + uint8_t* m_pData; + std::vector::const_iterator m_iTypes; +}; - // These, although returned as length-encoded strings are not considered - // to be strings from the perspective of masking. - case MYSQL_TYPE_BIT: - case MYSQL_TYPE_BLOB: - case MYSQL_TYPE_DECIMAL: - case MYSQL_TYPE_ENUM: - case MYSQL_TYPE_GEOMETRY: - case MYSQL_TYPE_LONG_BLOB: - case MYSQL_TYPE_MEDIUM_BLOB: - case MYSQL_TYPE_NEWDECIMAL: - case MYSQL_TYPE_SET: - case MYSQL_TYPE_TINY_BLOB: - return false; - - default: - return false; - } - } - - private: - enum_field_types m_type; - uint8_t* m_pData; - }; +/** + * @class CQRBinaryResultsetRowIterator + * + * An STL compatible iterator that iterates over the values in a binary resultset. + * + * @attention The name should not be used as such, but always using the + * typedef @c ComQueryResponse::BinaryResultset::iterator. + */ +class CQRBinaryResultsetRowIterator : public std::iterator +{ +public: + typedef CQRBinaryResultsetValue Value; /** - * iterator is an iterator to values in a binary resultset. + * A bit_iterator is an iterator to bits in an array of bytes. + * + * Specifically, it is capable of iterating across the NULL bitmask of + * a binary resultset. */ - class iterator : public std::iterator + class bit_iterator { public: + bit_iterator(uint8_t* pData = 0) + : m_pData(pData) + , m_mask(1 << 2) // The two first bits are not used. + { + } + /** - * A bit_iterator is an iterator to bits in an array of bytes. - * - * Specifically, it is capable of iterating across the NULL bitmask of - * a binary resultset. + * @return True, if the current bit is on. That is, if the corresponding + * column value is NULL. */ - class bit_iterator + bool operator * () const { - public: - bit_iterator(uint8_t* pData = 0) - : m_pData(pData) - , m_mask(1 << 2) // The two first bits are not used. - { - } - - /** - * @return True, if the current bit is on. That is, if the corresponding - * column value is NULL. - */ - bool operator * () const - { - return (*m_pData & m_mask) ? true : false; - } - - bit_iterator& operator ++ () - { - m_mask <<= 1; // Move to the next bit. - if (m_mask == 0) - { - // We moved past the byte, so advance to next byte and the first bit of that. - ++m_pData; - m_mask = 1; - } - - return *this; - } - - bit_iterator operator ++ (int) - { - bit_iterator rv(*this); - ++(*this); - return rv; - } - - private: - uint8_t* m_pData; /*< Pointer to the NULL bitmap of a binary resultset row. */ - uint8_t m_mask; /*< Mask representing the current bit of the current byte. */ - }; - - iterator(uint8_t* pData, const std::vector& types) - : m_pData(pData) - , m_iTypes(types.begin()) - , m_iNulls(pData + 1) - { - ss_dassert(*m_pData == 0); - ++m_pData; - - // See https://dev.mysql.com/doc/internals/en/binary-protocol-resultset-row.html - size_t nNull_bytes = (types.size() + 7 + 2) / 8; - - m_pData += nNull_bytes; + return (*m_pData & m_mask) ? true : false; } - iterator(uint8_t* pData) - : m_pData(pData) + bit_iterator& operator ++ () { - } - - iterator& operator++() - { - // See https://dev.mysql.com/doc/internals/en/binary-protocol-value.html - switch (*m_iTypes) + m_mask <<= 1; // Move to the next bit. + if (m_mask == 0) { - case MYSQL_TYPE_BIT: - case MYSQL_TYPE_BLOB: - case MYSQL_TYPE_DECIMAL: - case MYSQL_TYPE_ENUM: - case MYSQL_TYPE_GEOMETRY: - case MYSQL_TYPE_LONG_BLOB: - case MYSQL_TYPE_MEDIUM_BLOB: - case MYSQL_TYPE_NEWDATE: - case MYSQL_TYPE_NEWDECIMAL: - case MYSQL_TYPE_SET: - case MYSQL_TYPE_STRING: - case MYSQL_TYPE_TINY_BLOB: - case MYSQL_TYPE_VARCHAR: - case MYSQL_TYPE_VAR_STRING: - { - LEncString s(&m_pData); // Advance m_pData to the byte following the string. - } - break; - - case MYSQL_TYPE_LONGLONG: - m_pData += 8; - break; - - case MYSQL_TYPE_LONG: - case MYSQL_TYPE_INT24: - m_pData += 4; - break; - - case MYSQL_TYPE_SHORT: - case MYSQL_TYPE_YEAR: - m_pData += 2; - break; - - case MYSQL_TYPE_TINY: - m_pData += 1; - break; - - case MYSQL_TYPE_DOUBLE: - m_pData += 8; - break; - - case MYSQL_TYPE_FLOAT: - m_pData += 4; - break; - - case MYSQL_TYPE_DATE: - case MYSQL_TYPE_DATETIME: - case MYSQL_TYPE_TIMESTAMP: - { - // A byte specifying the length, followed by that many bytes. - // Either 0, 4, 7 or 11. - uint8_t len = *m_pData++; - m_pData += len; - } - break; - - case MYSQL_TYPE_TIME: - { - // A byte specifying the length, followed by that many bytes. - // Either 0, 8 or 12. - uint8_t len = *m_pData++; - m_pData += len; - } - break; - - case MYSQL_TYPE_NULL: - break; - - case MAX_NO_FIELD_TYPES: - ss_dassert(!true); - break; + // We moved past the byte, so advance to next byte and the first bit of that. + ++m_pData; + m_mask = 1; } - ++m_iNulls; - ++m_iTypes; - return *this; } - iterator operator++(int) + bit_iterator operator ++ (int) { - iterator rv(*this); + bit_iterator rv(*this); ++(*this); return rv; } - bool operator == (const iterator& rhs) const - { - return m_pData == rhs.m_pData; - } - - bool operator != (const iterator& rhs) const - { - return !(*this == rhs); - } - - reference operator*() - { - if (*m_iNulls) - { - return Value(); - } - else - { - return Value(*m_iTypes, m_pData); - } - } - private: - uint8_t* m_pData; - std::vector::const_iterator m_iTypes; - bit_iterator m_iNulls; + uint8_t* m_pData; /*< Pointer to the NULL bitmap of a binary resultset row. */ + uint8_t m_mask; /*< Mask representing the current bit of the current byte. */ }; - ComQueryResponseBinaryRow(GWBUF* pPacket, - const std::vector& types) + CQRBinaryResultsetRowIterator(uint8_t* pData, const std::vector& types) + : m_pData(pData) + , m_iTypes(types.begin()) + , m_iNulls(pData + 1) + { + ss_dassert(*m_pData == 0); + ++m_pData; + + // See https://dev.mysql.com/doc/internals/en/binary-protocol-resultset-row.html + size_t nNull_bytes = (types.size() + 7 + 2) / 8; + + m_pData += nNull_bytes; + } + + CQRBinaryResultsetRowIterator(uint8_t* pData) + : m_pData(pData) + { + } + + CQRBinaryResultsetRowIterator& operator++() + { + // See https://dev.mysql.com/doc/internals/en/binary-protocol-value.html + switch (*m_iTypes) + { + case MYSQL_TYPE_BIT: + case MYSQL_TYPE_BLOB: + case MYSQL_TYPE_DECIMAL: + case MYSQL_TYPE_ENUM: + case MYSQL_TYPE_GEOMETRY: + case MYSQL_TYPE_LONG_BLOB: + case MYSQL_TYPE_MEDIUM_BLOB: + case MYSQL_TYPE_NEWDATE: + case MYSQL_TYPE_NEWDECIMAL: + case MYSQL_TYPE_SET: + case MYSQL_TYPE_STRING: + case MYSQL_TYPE_TINY_BLOB: + case MYSQL_TYPE_VARCHAR: + case MYSQL_TYPE_VAR_STRING: + { + LEncString s(&m_pData); // Advance m_pData to the byte following the string. + } + break; + + case MYSQL_TYPE_LONGLONG: + m_pData += 8; + break; + + case MYSQL_TYPE_LONG: + case MYSQL_TYPE_INT24: + m_pData += 4; + break; + + case MYSQL_TYPE_SHORT: + case MYSQL_TYPE_YEAR: + m_pData += 2; + break; + + case MYSQL_TYPE_TINY: + m_pData += 1; + break; + + case MYSQL_TYPE_DOUBLE: + m_pData += 8; + break; + + case MYSQL_TYPE_FLOAT: + m_pData += 4; + break; + + case MYSQL_TYPE_DATE: + case MYSQL_TYPE_DATETIME: + case MYSQL_TYPE_TIMESTAMP: + { + // A byte specifying the length, followed by that many bytes. + // Either 0, 4, 7 or 11. + uint8_t len = *m_pData++; + m_pData += len; + } + break; + + case MYSQL_TYPE_TIME: + { + // A byte specifying the length, followed by that many bytes. + // Either 0, 8 or 12. + uint8_t len = *m_pData++; + m_pData += len; + } + break; + + case MYSQL_TYPE_NULL: + break; + + case MAX_NO_FIELD_TYPES: + ss_dassert(!true); + break; + } + + ++m_iNulls; + ++m_iTypes; + + return *this; + } + + CQRBinaryResultsetRowIterator operator++(int) + { + CQRBinaryResultsetRowIterator rv(*this); + ++(*this); + return rv; + } + + bool operator == (const CQRBinaryResultsetRowIterator& rhs) const + { + return m_pData == rhs.m_pData; + } + + bool operator != (const CQRBinaryResultsetRowIterator& rhs) const + { + return !(*this == rhs); + } + + reference operator*() + { + if (*m_iNulls) + { + return Value(); + } + else + { + return Value(*m_iTypes, m_pData); + } + } + +private: + uint8_t* m_pData; + std::vector::const_iterator m_iTypes; + bit_iterator m_iNulls; +}; + +/** + * @template CQRResultsetRow + * + * A template that when instantiated either represents a textual or a + * binary resultset row. + */ +template +class CQRResultsetRow : public ComPacket +{ +public: + typedef typename Iterator::Value Value; + typedef Iterator iterator; + + CQRResultsetRow(GWBUF* pPacket, + const std::vector& types) : ComPacket(pPacket) , m_types(types) { } - ComQueryResponseBinaryRow(const ComResponse& packet, - const std::vector& types) + CQRResultsetRow(const ComResponse& packet, + const std::vector& types) : ComPacket(packet) , m_types(types) { @@ -992,7 +1067,7 @@ public: iterator begin() { - return iterator(m_pI, m_types); + return iterator(m_pData, m_types); } iterator end() @@ -1005,22 +1080,41 @@ private: const std::vector& m_types; }; +/** + * @class CQRTextResultsetRow + * + * An instance of this class represents a textual resultset row. + */ +typedef CQRResultsetRow CQRTextResultsetRow; + +/** + * @class CQRBinaryResultsetRow + * + * An instance of this class represents a binary resultset row. + */ +typedef CQRResultsetRow CQRBinaryResultsetRow; + +/** + * @class ComQueryResponse + * + * An instance of this class represents the response to a @c ComQuery. + */ class ComQueryResponse : public ComPacket { public: - typedef ComQueryResponseColumnDef ColumnDef; - typedef ComQueryResponseRow Row; - typedef ComQueryResponseBinaryRow BinaryRow; + typedef CQRColumnDef ColumnDef; + typedef CQRTextResultsetRow TextResultsetRow; + typedef CQRBinaryResultsetRow BinaryResultsetRow; ComQueryResponse(GWBUF* pPacket) : ComPacket(pPacket) - , m_nFields(&m_pI) + , m_nFields(&m_pData) { } ComQueryResponse(const ComResponse& packet) : ComPacket(packet) - , m_nFields(&m_pI) + , m_nFields(&m_pData) { }