text.ts 2.8 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103
  1. import { TextMatch } from 'app/types/explore';
  2. import xss from 'xss';
  3. /**
  4. * Adapt findMatchesInText for react-highlight-words findChunks handler.
  5. * See https://github.com/bvaughn/react-highlight-words#props
  6. */
  7. export function findHighlightChunksInText({ searchWords, textToHighlight }) {
  8. return searchWords.reduce((acc, term) => [...acc, ...findMatchesInText(textToHighlight, term)], []);
  9. }
  10. const cleanNeedle = (needle: string): string => {
  11. return needle.replace(/[[{(][\w,.-?:*+]+$/, '');
  12. };
  13. /**
  14. * Returns a list of substring regexp matches.
  15. */
  16. export function findMatchesInText(haystack: string, needle: string): TextMatch[] {
  17. // Empty search can send re.exec() into infinite loop, exit early
  18. if (!haystack || !needle) {
  19. return [];
  20. }
  21. const matches = [];
  22. const { cleaned, flags } = parseFlags(cleanNeedle(needle));
  23. let regexp: RegExp;
  24. try {
  25. regexp = new RegExp(`(?:${cleaned})`, flags);
  26. } catch (error) {
  27. return matches;
  28. }
  29. haystack.replace(regexp, (substring, ...rest) => {
  30. if (substring) {
  31. const offset = rest[rest.length - 2];
  32. matches.push({
  33. text: substring,
  34. start: offset,
  35. length: substring.length,
  36. end: offset + substring.length,
  37. });
  38. }
  39. return '';
  40. });
  41. return matches;
  42. }
  43. const CLEAR_FLAG = '-';
  44. const FLAGS_REGEXP = /\(\?([ims-]+)\)/g;
  45. /**
  46. * Converts any mode modifers in the text to the Javascript equivalent flag
  47. */
  48. export function parseFlags(text: string): { cleaned: string; flags: string } {
  49. const flags: Set<string> = new Set(['g']);
  50. const cleaned = text.replace(FLAGS_REGEXP, (str, group) => {
  51. const clearAll = group.startsWith(CLEAR_FLAG);
  52. for (let i = 0; i < group.length; ++i) {
  53. const flag = group.charAt(i);
  54. if (clearAll || group.charAt(i - 1) === CLEAR_FLAG) {
  55. flags.delete(flag);
  56. } else if (flag !== CLEAR_FLAG) {
  57. flags.add(flag);
  58. }
  59. }
  60. return ''; // Remove flag from text
  61. });
  62. return {
  63. cleaned: cleaned,
  64. flags: Array.from(flags).join(''),
  65. };
  66. }
  67. const XSSWL = Object.keys(xss.whiteList).reduce((acc, element) => {
  68. acc[element] = xss.whiteList[element].concat(['class', 'style']);
  69. return acc;
  70. }, {});
  71. const sanitizeXSS = new xss.FilterXSS({
  72. whiteList: XSSWL,
  73. });
  74. /**
  75. * Returns string safe from XSS attacks.
  76. *
  77. * Even though we allow the style-attribute, there's still default filtering applied to it
  78. * Info: https://github.com/leizongmin/js-xss#customize-css-filter
  79. * Whitelist: https://github.com/leizongmin/js-css-filter/blob/master/lib/default.js
  80. */
  81. export function sanitize(unsanitizedString: string): string {
  82. try {
  83. return sanitizeXSS.process(unsanitizedString);
  84. } catch (error) {
  85. console.log('String could not be sanitized', unsanitizedString);
  86. return unsanitizedString;
  87. }
  88. }
  89. export function hasAnsiCodes(input: string): boolean {
  90. return /\u001b\[\d{1,2}m/.test(input);
  91. }