# frozen_string_literal: true # # Copyright (c) 2006-2023 Hal Brodigan (postmodern.mod3 at gmail.com) # # ronin-support is free software: you can redistribute it and/or modify # it under the terms of the GNU Lesser General Public License as published # by the Free Software Foundation, either version 3 of the License, or # (at your option) any later version. # # ronin-support 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 Lesser General Public License for more details. # # You should have received a copy of the GNU Lesser General Public License # along with ronin-support. If not, see . # require 'ronin/support/binary/memory' module Ronin module Support module Binary # # Represents a null terminated C string. # # ## Examples # # ### Initializing C Strings # # From a String: # # str = Binary::CString.new("hello ") # # => # # # From a binary C string: # # str = Binary::CString.new("world\0".b) # # => # # # From characters: # # str = Binary::CString['A', 'B', 'C'] # # => # # # ### Modifying C Strings # # Concating Strings to a C String: # # str = Binary::CString.new("hello ") # str.concat("world") # # => # # str.to_s # # => "hello world" # # Appending two C Strings: # # str1 = Binary::CString.new("hello ") # str2 = Binary::CString.new("world\0") # str = str1 + str2 # # => # # # Setting characters: # # str = Binary::CString.new("hello") # str[0] = 'X' # str.to_s # # => "Xello" # # @api public # # @since 1.0.0 # class CString < Memory # Null byte NULL = "\0".encode(Encoding::ASCII_8BIT).freeze # # Initializes the C string. # # @param [String, ByteSlice, nil] value # The contents of the C string. # def initialize(value=nil) case value when String if value.include?(NULL) super(value) else # ensure the C String ends in or contains a NULL byte. super("#{value}#{NULL}") end when nil # initialize with a single \0 byte super(1) else super(value) end end # # Creates a C string. # # @param [Array, Array] values # The bytes or characters for the new C string. # # @return [CString] # The newly created C string. # # @example Create a C string from a series of bytes: # Binary::CString[0x41, 0x41, 0x41, 0x00, 0x00, 0x00] # # @example Create a C string from a series of chars: # Binary::CString['A', 'A', 'A'] # # @example Create a C string from a String: # Binary::CString["AAA"] # # @see #initialize # def self.[](*values) buffer = String.new values.each do |element| buffer << case element when Integer then element.chr else element.to_s end end # ensure the C String ends in or contains a NULL byte. buffer << NULL unless buffer.include?(NULL) return new(buffer) end # # Concatinates a character, byte, or String to the C string. # # @param [String, Integer, #to_s] value # The other String to concat to the C string. # # @return [self] # def concat(value) value = case value when Integer then value.chr else value.to_s end value_size = value.bytesize unless value.include?(NULL) value = "#{value}#{NULL}" value_size += 1 end self[null_index,value_size] = value return self end alias << concat # # Creates a new C string by adding two C strings together. # # @param [#to_s, Integer] other # The other String or an offset. # # @return [CString, ByteSlice] # The new combined C string, or a {ByteSlice} if an offset Integer # was given. # def +(other) case other when Integer then super(other) else CString.new(to_s + other.to_s) end end # # Enumerates over each characters within the C string. # # @yield [char] # If a block is given, it will be passed each character within the C # string. # # @yieldparam [String] char # A character within the C string. # # @return [Enumerator] # If no block is given, an Enumerator will be returned. # def each_char return enum_for(__method__) unless block_given? @string.each_char do |char| break if char == NULL yield char end end # # The characters within the C string. # # @return [Array] # The Array of characters within the C string. # def chars each_char.to_a end # # Enumerates over each byte within the C string. # # @yield [byte] # If a block is given, it will be passed each character within the C # string. # # @yieldparam [Integer] byte # A byte within the C string. # # @return [Enumerator] # If no block is given, an Enumerator will be returned. # def each_byte(&block) return enum_for(__method__) unless block_given? @string.each_byte do |byte| break if byte == 0x00 yield byte end end # # The bytes within the C string. # # @return [Array] # The Array of bytes within the C string. # def bytes each_byte.to_a end # # Searches for the char, byte, substring, or regexp within the C string. # # @param [String, Integer, Regexp] key # The char, byte, substring, or Regexp to search for. # # @return [Integer, nil] # The index of the char, byte, substring, or Regexp. # def index(key) if (index = @string.index(key)) if index < null_index return index end end end # # The length of the C string. # # @return [Integer] # The number of non-null characters in the String. # def length null_index || size end alias len length # # Converts the C stirng into a regular String. # # @return [String] # The C string without it's terminating null byte. # def to_s if (length = null_index) @string[0,length] else @string.to_s end end private # # Finds the index of the first null byte (`\0`). # # @return [Integer, nil] # def null_index @string.index(NULL) end end end end end