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 }