The little things give you away... A collection of various small helper stuff
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
 
 
 

108 lines
3.7 KiB

  1. #!/bin/bash
  2. set -euo pipefail
  3. # Options
  4. if [[ $# -eq 0 || "$1" == '-h' || "$1" == '--help' ]]
  5. then
  6. echo 'Usage: b64grep [OPTIONS] PATTERN [PATTERN...]' >&2
  7. echo >&2
  8. echo 'Options: --ignore-case (-i), --invert-match (-v)' >&2
  9. echo >&2
  10. echo 'grep that handles base64-encoding: `b64grep foo` matches any input line on stdin that contains a base64-encoded string whose decoded data contains foo.' >&2
  11. echo 'Note that b64grep does not actually decode the base64 data or even check that the input is base64. It merely transforms the search string(s) into the appropriate patterns.' >&2
  12. echo 'Only fixed-string searches are supported, i.e. --fixed-strings is implied. --ignore-case and --invert-match function as with grep.' >&2
  13. echo 'Patterns must be at least two characters long. Due to how base64 works, short patterns are inherently unreliable, so use of long patterns is advised.' >&2
  14. echo 'However, --ignore-case incurs a significant performance impact when used with long patterns.' >&2
  15. exit 1
  16. fi
  17. ignoreCase=
  18. invertMatch=
  19. while :
  20. do
  21. if [[ "$1" == '--ignore-case' ]]
  22. then
  23. ignoreCase=yes
  24. shift
  25. elif [[ "$1" == '--invert-match' ]]
  26. then
  27. invertMatch=yes
  28. shift
  29. elif [[ "$1" =~ ^-[iv][iv]*$ ]]
  30. then
  31. if [[ "$1" == *i* ]]
  32. then
  33. ignoreCase=yes
  34. fi
  35. if [[ "$1" == *v* ]]
  36. then
  37. invertMatch=yes
  38. fi
  39. shift
  40. else
  41. break
  42. fi
  43. done
  44. # Handle --ignore-case
  45. if [[ "${ignoreCase}" ]]
  46. then
  47. # Generate all combinations of lowercase and uppercase characters in the patterns
  48. # Algorithm inspired by https://www.geeksforgeeks.org/permute-string-changing-case/ (with the additional handling of characters that have no case variations)
  49. patterns=()
  50. for pattern in "$@"
  51. do
  52. pattern="${pattern,,}"
  53. upPattern="${pattern^^}"
  54. maxComb=$((1 << ${#pattern}))
  55. for (( i = 0; i < ${maxComb}; ++i ))
  56. do
  57. combination="${pattern}"
  58. for (( j = 0; j < ${#pattern}; ++j ))
  59. do
  60. if [[ $(((${i} >> ${j}) & 1)) -eq 1 ]]
  61. then
  62. if [[ "${pattern:${j}:1}" == "${upPattern:${j}:1}" ]]
  63. then
  64. # Skip combinations where the bit for a caseless character is on
  65. continue 2
  66. fi
  67. combination="${combination:0:${j}}${upPattern:${j}:1}${combination:$((${j} + 1))}"
  68. fi
  69. done
  70. patterns+=("${combination}")
  71. done
  72. done
  73. else
  74. patterns=("$@")
  75. fi
  76. # The core: transform pattern to base64...
  77. # When a string X of length L bytes is contained in base64-encoded data D, it will be 4*L/3 characters in D.
  78. # Depending on the position in D, it may however be encoded in three different ways, and the beginning of the occurrence may be shifted by two or four bits on either end.
  79. # This means that the first four and last four bits of the search string cannot be easily used for searches because they may get mixed with the preceding/following data.
  80. # In this implementation, those bits are simply discarded. This is why patterns must be at least two characters (since that means there are two or more base64 chars entirely determined by the pattern).
  81. # A more clever way would be to generate character classes for the corresponding bits. However, the benefit of this is marginal because it would still not take shifts into account.
  82. b64patterns=()
  83. for pattern in "${patterns[@]}"
  84. do
  85. for prefix in '' x xx
  86. do
  87. b64="$(printf '%s' "${prefix}${pattern}" | base64)"
  88. if [[ "${b64}" == *== ]]; then b64="${b64::-3}"; elif [[ "${b64}" == *= ]]; then b64="${b64::-2}"; fi
  89. if [[ "${prefix}" == x ]]; then b64="${b64:2}"; elif [[ "${prefix}" == xx ]]; then b64="${b64:3}"; fi
  90. b64patterns+=("${b64}")
  91. done
  92. done
  93. # Assemble command
  94. cmd=(grep --fixed-strings)
  95. if [[ "${invertMatch}" ]]; then cmd+=(--invert-match); fi
  96. for pattern in "${b64patterns[@]}"
  97. do
  98. cmd+=(-e "${pattern}")
  99. done
  100. # Off we go!
  101. exec "${cmd[@]}"