# Copyright (c) 2023 Contrast Security, Inc. See https://www.contrastsecurity.com/enduser-terms-0317a for more details. # frozen_string_literal: true module Contrast module Utils # Utilities for encoding and normalizing strings module StringUtils class << self UTF8 = 'utf-8' HTTP_PREFIX = 'HTTP_' # Convenience method. We assume that we're working on Strings or tags # String representations of things. To that end, we'll to_s anything # that comes in before returning its length. # # But don't worry though, String.to_s just returns self. teehee def ret_length string string.nil? ? 0 : string.to_s.length end def present? str !str.nil? && !str.to_s.empty? end # Protobuf has a very strict typing. Nil is not a String and will throw # an exception if you try to set it. Use this to be safe. # Uses the object share to avoid creating several new strings per request def protobuf_safe_string string string.nil? ? Contrast::Utils::ObjectShare::EMPTY_STRING : string.to_s end # Truncate a string to 255 characters max length # # @param str [String] the string tt truncate # @param default [String] what to default to # @return [String] def truncate str, default = Contrast::Utils::ObjectShare::EMPTY_STRING return default if str.nil? str.to_s[0..255] end # Cast the given object, which should be a String, into a UTF-8 String for reporting. All given objects will be # cast to their to_s form, except nil which will become the ObjectShare::EMPTY_STRING, and then cast. # # @param str [String, Object, nil] # @return [String] def force_utf8 str return Contrast::Utils::ObjectShare::EMPTY_STRING unless str str = str.to_s if str.encoding == Encoding::UTF_8 str = str.encode(UTF8, invalid: :replace, undef: :replace) unless str.valid_encoding? else str = str.encode(UTF8, str.encoding, invalid: :replace, undef: :replace) end str.to_s rescue StandardError => e # We were unable to switch the String to a UTF-8 format. # Return non-nil so as not to throw an exception later when trying # to do regexp or other compares on the String Contrast::CONFIG.proto_logger.trace('Unable to cast String to UTF-8 format', e, value: str) Contrast::Utils::ObjectShare::EMPTY_STRING end # Given a string return a normalized version of that string. # Keys are memoized so that the normalization process doesn't need # to happen every time. # # @param str [String] the String to normalize # @return [String] a copy of the given String, upper cased, trimmed, # dashes replaced with underscore, and HTTP trimmed def normalized_key str return unless str str = str.to_s @_normalized_keys ||= {} if @_normalized_keys.key?(str) @_normalized_keys[str] else upped = str.upcase stripped = upped.strip! || upped trimmed = stripped.tr!('-', '_') || stripped cut = trimmed.start_with?(HTTP_PREFIX) ? trimmed[5..] : trimmed @_normalized_keys[str] = cut end end # transform string from snake_case to Capitalized Text # # @param str[String] string to transform # @return [String] def transform_string str return unless str str.split('-').map(&:capitalize).join(' ') end end end end end