1 /****************************************************************************** 2 3 Escapes characters in a string, that is, prepends '\' to special characters. 4 5 Copyright: 6 Copyright (c) 2009-2016 dunnhumby Germany GmbH. 7 All rights reserved. 8 9 License: 10 Boost Software License Version 1.0. See LICENSE_BOOST.txt for details. 11 Alternatively, this file may be distributed under the terms of the Tango 12 3-Clause BSD License (see LICENSE_BSD.txt for details). 13 14 ******************************************************************************/ 15 16 module ocean.text.util.EscapeChars; 17 18 import ocean.core.Array: concat; 19 import ocean.core.Verify; 20 version (unittest) import ocean.core.Test; 21 import ocean.meta.types.Qualifiers; 22 23 import core.stdc.string: strcspn, memmove, memcpy, memchr, strlen; 24 25 /******************************************************************************/ 26 27 struct EscapeChars 28 { 29 /************************************************************************** 30 31 Tokens string consisting of the default special characters to escape 32 33 **************************************************************************/ 34 35 enum Tokens = `"'\`; 36 37 /************************************************************************** 38 39 List of special characters to escape 40 41 **************************************************************************/ 42 43 private mstring tokens; 44 45 /************************************************************************** 46 47 List of occurrences 48 49 **************************************************************************/ 50 51 private size_t[] occurrences; 52 53 /************************************************************************** 54 55 Escapes each occurrence of an element of Tokens in str by inserting 56 the escape pattern escape into str before the occurrence. 57 58 Params: 59 str = string with characters to escape; changed in-place 60 escape = escape pattern to prepend to each token occurrence 61 tokens = List of special characters to escape; empty string 62 indicates to do nothing. '\0' tokens are not allowed. 63 64 Returns: 65 resulting string 66 67 **************************************************************************/ 68 69 public mstring opCall ( ref mstring str, cstring escape = `\`, 70 cstring tokens = Tokens ) 71 { 72 if (tokens.length) 73 { 74 this.copyTokens(tokens); 75 76 // append a 0 to the end, as it is stripped in the scope(exit) 77 str ~= '\0'; 78 79 scope (exit) 80 { 81 verify (str.length > 0); 82 verify (!str[$ - 1]); 83 str.length = str.length - 1; 84 assumeSafeAppend(str); 85 } 86 87 size_t end = str.length - 1; 88 89 this.occurrences.length = 0; 90 assumeSafeAppend(this.occurrences); 91 92 for (size_t pos = strcspn(str.ptr, tokens.ptr); pos < end;) 93 { 94 this.occurrences ~= pos; 95 96 pos += strcspn(str.ptr + ++pos, tokens.ptr); 97 } 98 99 str.length = str.length + (this.occurrences.length * escape.length); 100 101 // append a 0 to the end, as it is stripped in the scope(exit) 102 str[$ - 1] = '\0'; 103 104 foreach_reverse (i, occurrence; this.occurrences) 105 { 106 char* src = str.ptr + occurrence; 107 char* dst = src + ((i + 1) * escape.length); 108 109 memmove(dst, src, end - occurrence); 110 memcpy(dst - escape.length, escape.ptr, escape.length); 111 112 end = occurrence; 113 } 114 } 115 116 return str; 117 } 118 119 /************************************************************************** 120 121 Copies tok to this.tokens and appends a NUL terminator. 122 123 Params: 124 tokens = list of character tokens 125 126 **************************************************************************/ 127 128 private void copyTokens ( cstring tokens ) 129 out 130 { 131 assert (this.tokens.length); 132 assert (!this.tokens[$ - 1]); 133 assert (this.tokens.length - 1 == strlen(this.tokens.ptr)); 134 } 135 do 136 { 137 verify (tokens.ptr !is null); 138 verify (!memchr(tokens.ptr, '\0', tokens.length), 139 typeof (this).stringof ~ ": NUL characters not allowed in tokens"); 140 this.tokens.concat(tokens, "\0"[]); 141 } 142 } 143 144 unittest 145 { 146 EscapeChars ec; 147 auto to_escape = `Some \ special char`.dup; 148 test!("==")(ec(to_escape), `Some \\ special char`); 149 // Test for stomping prevention 150 test!("==")(ec(to_escape), `Some \\\\ special char`); 151 }