001    /*
002     * Licensed to the Apache Software Foundation (ASF) under one or more
003     * contributor license agreements.  See the NOTICE file distributed with
004     * this work for additional information regarding copyright ownership.
005     * The ASF licenses this file to You under the Apache License, Version 2.0
006     * (the "License"); you may not use this file except in compliance with
007     * the License.  You may obtain a copy of the License at
008     * 
009     *      http://www.apache.org/licenses/LICENSE-2.0
010     * 
011     * Unless required by applicable law or agreed to in writing, software
012     * distributed under the License is distributed on an "AS IS" BASIS,
013     * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
014     * See the License for the specific language governing permissions and
015     * limitations under the License.
016     */
017    package org.apache.commons.lang;
018    
019    import java.io.IOException;
020    import java.io.StringWriter;
021    import java.io.Writer;
022    import java.util.Locale;
023    
024    import org.apache.commons.lang.exception.NestableRuntimeException;
025    import org.apache.commons.lang.text.StrBuilder;
026    
027    /**
028     * <p>Escapes and unescapes <code>String</code>s for
029     * Java, Java Script, HTML, XML, and SQL.</p>
030     *
031     * <p>#ThreadSafe#</p>
032     * @author Apache Software Foundation
033     * @author Apache Jakarta Turbine
034     * @author Purple Technology
035     * @author <a href="mailto:alex@purpletech.com">Alexander Day Chaffee</a>
036     * @author Antony Riley
037     * @author Helge Tesgaard
038     * @author <a href="sean@boohai.com">Sean Brown</a>
039     * @author <a href="mailto:ggregory@seagullsw.com">Gary Gregory</a>
040     * @author Phil Steitz
041     * @author Pete Gieser
042     * @since 2.0
043     * @version $Id: StringEscapeUtils.java 1057072 2011-01-10 01:55:57Z niallp $
044     */
045    public class StringEscapeUtils {
046    
047        private static final char CSV_DELIMITER = ',';
048        private static final char CSV_QUOTE = '"';
049        private static final String CSV_QUOTE_STR = String.valueOf(CSV_QUOTE);
050        private static final char[] CSV_SEARCH_CHARS = new char[] {CSV_DELIMITER, CSV_QUOTE, CharUtils.CR, CharUtils.LF};
051    
052        /**
053         * <p><code>StringEscapeUtils</code> instances should NOT be constructed in
054         * standard programming.</p>
055         *
056         * <p>Instead, the class should be used as:
057         * <pre>StringEscapeUtils.escapeJava("foo");</pre></p>
058         *
059         * <p>This constructor is public to permit tools that require a JavaBean
060         * instance to operate.</p>
061         */
062        public StringEscapeUtils() {
063          super();
064        }
065    
066        // Java and JavaScript
067        //--------------------------------------------------------------------------
068        /**
069         * <p>Escapes the characters in a <code>String</code> using Java String rules.</p>
070         *
071         * <p>Deals correctly with quotes and control-chars (tab, backslash, cr, ff, etc.) </p>
072         *
073         * <p>So a tab becomes the characters <code>'\\'</code> and
074         * <code>'t'</code>.</p>
075         *
076         * <p>The only difference between Java strings and JavaScript strings
077         * is that in JavaScript, a single quote must be escaped.</p>
078         *
079         * <p>Example:
080         * <pre>
081         * input string: He didn't say, "Stop!"
082         * output string: He didn't say, \"Stop!\"
083         * </pre>
084         * </p>
085         *
086         * @param str  String to escape values in, may be null
087         * @return String with escaped values, <code>null</code> if null string input
088         */
089        public static String escapeJava(String str) {
090            return escapeJavaStyleString(str, false, false);
091        }
092    
093        /**
094         * <p>Escapes the characters in a <code>String</code> using Java String rules to
095         * a <code>Writer</code>.</p>
096         * 
097         * <p>A <code>null</code> string input has no effect.</p>
098         * 
099         * @see #escapeJava(java.lang.String)
100         * @param out  Writer to write escaped string into
101         * @param str  String to escape values in, may be null
102         * @throws IllegalArgumentException if the Writer is <code>null</code>
103         * @throws IOException if error occurs on underlying Writer
104         */
105        public static void escapeJava(Writer out, String str) throws IOException {
106            escapeJavaStyleString(out, str, false, false);
107        }
108    
109        /**
110         * <p>Escapes the characters in a <code>String</code> using JavaScript String rules.</p>
111         * <p>Escapes any values it finds into their JavaScript String form.
112         * Deals correctly with quotes and control-chars (tab, backslash, cr, ff, etc.) </p>
113         *
114         * <p>So a tab becomes the characters <code>'\\'</code> and
115         * <code>'t'</code>.</p>
116         *
117         * <p>The only difference between Java strings and JavaScript strings
118         * is that in JavaScript, a single quote must be escaped.</p>
119         *
120         * <p>Example:
121         * <pre>
122         * input string: He didn't say, "Stop!"
123         * output string: He didn\'t say, \"Stop!\"
124         * </pre>
125         * </p>
126         *
127         * @param str  String to escape values in, may be null
128         * @return String with escaped values, <code>null</code> if null string input
129         */
130        public static String escapeJavaScript(String str) {
131            return escapeJavaStyleString(str, true, true);
132        }
133    
134        /**
135         * <p>Escapes the characters in a <code>String</code> using JavaScript String rules
136         * to a <code>Writer</code>.</p>
137         * 
138         * <p>A <code>null</code> string input has no effect.</p>
139         * 
140         * @see #escapeJavaScript(java.lang.String)
141         * @param out  Writer to write escaped string into
142         * @param str  String to escape values in, may be null
143         * @throws IllegalArgumentException if the Writer is <code>null</code>
144         * @throws IOException if error occurs on underlying Writer
145         **/
146        public static void escapeJavaScript(Writer out, String str) throws IOException {
147            escapeJavaStyleString(out, str, true, true);
148        }
149    
150        /**
151         * <p>Worker method for the {@link #escapeJavaScript(String)} method.</p>
152         * 
153         * @param str String to escape values in, may be null
154         * @param escapeSingleQuotes escapes single quotes if <code>true</code>
155         * @param escapeForwardSlash TODO
156         * @return the escaped string
157         */
158        private static String escapeJavaStyleString(String str, boolean escapeSingleQuotes, boolean escapeForwardSlash) {
159            if (str == null) {
160                return null;
161            }
162            try {
163                StringWriter writer = new StringWriter(str.length() * 2);
164                escapeJavaStyleString(writer, str, escapeSingleQuotes, escapeForwardSlash);
165                return writer.toString();
166            } catch (IOException ioe) {
167                // this should never ever happen while writing to a StringWriter
168                throw new UnhandledException(ioe);
169            }
170        }
171    
172        /**
173         * <p>Worker method for the {@link #escapeJavaScript(String)} method.</p>
174         * 
175         * @param out write to receieve the escaped string
176         * @param str String to escape values in, may be null
177         * @param escapeSingleQuote escapes single quotes if <code>true</code>
178         * @param escapeForwardSlash TODO
179         * @throws IOException if an IOException occurs
180         */
181        private static void escapeJavaStyleString(Writer out, String str, boolean escapeSingleQuote,
182                boolean escapeForwardSlash) throws IOException {
183            if (out == null) {
184                throw new IllegalArgumentException("The Writer must not be null");
185            }
186            if (str == null) {
187                return;
188            }
189            int sz;
190            sz = str.length();
191            for (int i = 0; i < sz; i++) {
192                char ch = str.charAt(i);
193    
194                // handle unicode
195                if (ch > 0xfff) {
196                    out.write("\\u" + hex(ch));
197                } else if (ch > 0xff) {
198                    out.write("\\u0" + hex(ch));
199                } else if (ch > 0x7f) {
200                    out.write("\\u00" + hex(ch));
201                } else if (ch < 32) {
202                    switch (ch) {
203                        case '\b' :
204                            out.write('\\');
205                            out.write('b');
206                            break;
207                        case '\n' :
208                            out.write('\\');
209                            out.write('n');
210                            break;
211                        case '\t' :
212                            out.write('\\');
213                            out.write('t');
214                            break;
215                        case '\f' :
216                            out.write('\\');
217                            out.write('f');
218                            break;
219                        case '\r' :
220                            out.write('\\');
221                            out.write('r');
222                            break;
223                        default :
224                            if (ch > 0xf) {
225                                out.write("\\u00" + hex(ch));
226                            } else {
227                                out.write("\\u000" + hex(ch));
228                            }
229                            break;
230                    }
231                } else {
232                    switch (ch) {
233                        case '\'' :
234                            if (escapeSingleQuote) {
235                                out.write('\\');
236                            }
237                            out.write('\'');
238                            break;
239                        case '"' :
240                            out.write('\\');
241                            out.write('"');
242                            break;
243                        case '\\' :
244                            out.write('\\');
245                            out.write('\\');
246                            break;
247                        case '/' :
248                            if (escapeForwardSlash) {
249                                out.write('\\');
250                            }
251                            out.write('/');
252                            break;
253                        default :
254                            out.write(ch);
255                            break;
256                    }
257                }
258            }
259        }
260    
261        /**
262         * <p>Returns an upper case hexadecimal <code>String</code> for the given
263         * character.</p>
264         * 
265         * @param ch The character to convert.
266         * @return An upper case hexadecimal <code>String</code>
267         */
268        private static String hex(char ch) {
269            return Integer.toHexString(ch).toUpperCase(Locale.ENGLISH);
270        }
271    
272        /**
273         * <p>Unescapes any Java literals found in the <code>String</code>.
274         * For example, it will turn a sequence of <code>'\'</code> and
275         * <code>'n'</code> into a newline character, unless the <code>'\'</code>
276         * is preceded by another <code>'\'</code>.</p>
277         * 
278         * @param str  the <code>String</code> to unescape, may be null
279         * @return a new unescaped <code>String</code>, <code>null</code> if null string input
280         */
281        public static String unescapeJava(String str) {
282            if (str == null) {
283                return null;
284            }
285            try {
286                StringWriter writer = new StringWriter(str.length());
287                unescapeJava(writer, str);
288                return writer.toString();
289            } catch (IOException ioe) {
290                // this should never ever happen while writing to a StringWriter
291                throw new UnhandledException(ioe);
292            }
293        }
294    
295        /**
296         * <p>Unescapes any Java literals found in the <code>String</code> to a
297         * <code>Writer</code>.</p>
298         *
299         * <p>For example, it will turn a sequence of <code>'\'</code> and
300         * <code>'n'</code> into a newline character, unless the <code>'\'</code>
301         * is preceded by another <code>'\'</code>.</p>
302         * 
303         * <p>A <code>null</code> string input has no effect.</p>
304         * 
305         * @param out  the <code>Writer</code> used to output unescaped characters
306         * @param str  the <code>String</code> to unescape, may be null
307         * @throws IllegalArgumentException if the Writer is <code>null</code>
308         * @throws IOException if error occurs on underlying Writer
309         */
310        public static void unescapeJava(Writer out, String str) throws IOException {
311            if (out == null) {
312                throw new IllegalArgumentException("The Writer must not be null");
313            }
314            if (str == null) {
315                return;
316            }
317            int sz = str.length();
318            StrBuilder unicode = new StrBuilder(4);
319            boolean hadSlash = false;
320            boolean inUnicode = false;
321            for (int i = 0; i < sz; i++) {
322                char ch = str.charAt(i);
323                if (inUnicode) {
324                    // if in unicode, then we're reading unicode
325                    // values in somehow
326                    unicode.append(ch);
327                    if (unicode.length() == 4) {
328                        // unicode now contains the four hex digits
329                        // which represents our unicode character
330                        try {
331                            int value = Integer.parseInt(unicode.toString(), 16);
332                            out.write((char) value);
333                            unicode.setLength(0);
334                            inUnicode = false;
335                            hadSlash = false;
336                        } catch (NumberFormatException nfe) {
337                            throw new NestableRuntimeException("Unable to parse unicode value: " + unicode, nfe);
338                        }
339                    }
340                    continue;
341                }
342                if (hadSlash) {
343                    // handle an escaped value
344                    hadSlash = false;
345                    switch (ch) {
346                        case '\\':
347                            out.write('\\');
348                            break;
349                        case '\'':
350                            out.write('\'');
351                            break;
352                        case '\"':
353                            out.write('"');
354                            break;
355                        case 'r':
356                            out.write('\r');
357                            break;
358                        case 'f':
359                            out.write('\f');
360                            break;
361                        case 't':
362                            out.write('\t');
363                            break;
364                        case 'n':
365                            out.write('\n');
366                            break;
367                        case 'b':
368                            out.write('\b');
369                            break;
370                        case 'u':
371                            {
372                                // uh-oh, we're in unicode country....
373                                inUnicode = true;
374                                break;
375                            }
376                        default :
377                            out.write(ch);
378                            break;
379                    }
380                    continue;
381                } else if (ch == '\\') {
382                    hadSlash = true;
383                    continue;
384                }
385                out.write(ch);
386            }
387            if (hadSlash) {
388                // then we're in the weird case of a \ at the end of the
389                // string, let's output it anyway.
390                out.write('\\');
391            }
392        }
393    
394        /**
395         * <p>Unescapes any JavaScript literals found in the <code>String</code>.</p>
396         *
397         * <p>For example, it will turn a sequence of <code>'\'</code> and <code>'n'</code>
398         * into a newline character, unless the <code>'\'</code> is preceded by another
399         * <code>'\'</code>.</p>
400         *
401         * @see #unescapeJava(String)
402         * @param str  the <code>String</code> to unescape, may be null
403         * @return A new unescaped <code>String</code>, <code>null</code> if null string input
404         */
405        public static String unescapeJavaScript(String str) {
406            return unescapeJava(str);
407        }
408    
409        /**
410         * <p>Unescapes any JavaScript literals found in the <code>String</code> to a
411         * <code>Writer</code>.</p>
412         *
413         * <p>For example, it will turn a sequence of <code>'\'</code> and <code>'n'</code>
414         * into a newline character, unless the <code>'\'</code> is preceded by another
415         * <code>'\'</code>.</p>
416         *
417         * <p>A <code>null</code> string input has no effect.</p>
418         * 
419         * @see #unescapeJava(Writer,String)
420         * @param out  the <code>Writer</code> used to output unescaped characters
421         * @param str  the <code>String</code> to unescape, may be null
422         * @throws IllegalArgumentException if the Writer is <code>null</code>
423         * @throws IOException if error occurs on underlying Writer
424         */
425        public static void unescapeJavaScript(Writer out, String str) throws IOException {
426            unescapeJava(out, str);
427        }
428    
429        // HTML and XML
430        //--------------------------------------------------------------------------
431        /**
432         * <p>Escapes the characters in a <code>String</code> using HTML entities.</p>
433         *
434         * <p>
435         * For example:
436         * </p> 
437         * <p><code>"bread" & "butter"</code></p>
438         * becomes:
439         * <p>
440         * <code>&amp;quot;bread&amp;quot; &amp;amp; &amp;quot;butter&amp;quot;</code>.
441         * </p>
442         *
443         * <p>Supports all known HTML 4.0 entities, including funky accents.
444         * Note that the commonly used apostrophe escape character (&amp;apos;)
445         * is not a legal entity and so is not supported). </p>
446         *
447         * @param str  the <code>String</code> to escape, may be null
448         * @return a new escaped <code>String</code>, <code>null</code> if null string input
449         * 
450         * @see #unescapeHtml(String)
451         * @see <a href="http://hotwired.lycos.com/webmonkey/reference/special_characters/">ISO Entities</a>
452         * @see <a href="http://www.w3.org/TR/REC-html32#latin1">HTML 3.2 Character Entities for ISO Latin-1</a>
453         * @see <a href="http://www.w3.org/TR/REC-html40/sgml/entities.html">HTML 4.0 Character entity references</a>
454         * @see <a href="http://www.w3.org/TR/html401/charset.html#h-5.3">HTML 4.01 Character References</a>
455         * @see <a href="http://www.w3.org/TR/html401/charset.html#code-position">HTML 4.01 Code positions</a>
456         */
457        public static String escapeHtml(String str) {
458            if (str == null) {
459                return null;
460            }
461            try {
462                StringWriter writer = new StringWriter ((int)(str.length() * 1.5));
463                escapeHtml(writer, str);
464                return writer.toString();
465            } catch (IOException ioe) {
466                //should be impossible
467                throw new UnhandledException(ioe);
468            }
469        }
470    
471        /**
472         * <p>Escapes the characters in a <code>String</code> using HTML entities and writes
473         * them to a <code>Writer</code>.</p>
474         *
475         * <p>
476         * For example:
477         * </p> 
478         * <code>"bread" & "butter"</code>
479         * <p>becomes:</p>
480         * <code>&amp;quot;bread&amp;quot; &amp;amp; &amp;quot;butter&amp;quot;</code>.
481         *
482         * <p>Supports all known HTML 4.0 entities, including funky accents.
483         * Note that the commonly used apostrophe escape character (&amp;apos;)
484         * is not a legal entity and so is not supported). </p>
485         *
486         * @param writer  the writer receiving the escaped string, not null
487         * @param string  the <code>String</code> to escape, may be null
488         * @throws IllegalArgumentException if the writer is null
489         * @throws IOException when <code>Writer</code> passed throws the exception from
490         *                                       calls to the {@link Writer#write(int)} methods.
491         * 
492         * @see #escapeHtml(String)
493         * @see #unescapeHtml(String)
494         * @see <a href="http://hotwired.lycos.com/webmonkey/reference/special_characters/">ISO Entities</a>
495         * @see <a href="http://www.w3.org/TR/REC-html32#latin1">HTML 3.2 Character Entities for ISO Latin-1</a>
496         * @see <a href="http://www.w3.org/TR/REC-html40/sgml/entities.html">HTML 4.0 Character entity references</a>
497         * @see <a href="http://www.w3.org/TR/html401/charset.html#h-5.3">HTML 4.01 Character References</a>
498         * @see <a href="http://www.w3.org/TR/html401/charset.html#code-position">HTML 4.01 Code positions</a>
499         */
500        public static void escapeHtml(Writer writer, String string) throws IOException {
501            if (writer == null ) {
502                throw new IllegalArgumentException ("The Writer must not be null.");
503            }
504            if (string == null) {
505                return;
506            }
507            Entities.HTML40.escape(writer, string);
508        }
509    
510        //-----------------------------------------------------------------------
511        /**
512         * <p>Unescapes a string containing entity escapes to a string
513         * containing the actual Unicode characters corresponding to the
514         * escapes. Supports HTML 4.0 entities.</p>
515         *
516         * <p>For example, the string "&amp;lt;Fran&amp;ccedil;ais&amp;gt;"
517         * will become "&lt;Fran&ccedil;ais&gt;"</p>
518         *
519         * <p>If an entity is unrecognized, it is left alone, and inserted
520         * verbatim into the result string. e.g. "&amp;gt;&amp;zzzz;x" will
521         * become "&gt;&amp;zzzz;x".</p>
522         *
523         * @param str  the <code>String</code> to unescape, may be null
524         * @return a new unescaped <code>String</code>, <code>null</code> if null string input
525         * @see #escapeHtml(Writer, String)
526         */
527        public static String unescapeHtml(String str) {
528            if (str == null) {
529                return null;
530            }
531            try {
532                StringWriter writer = new StringWriter ((int)(str.length() * 1.5));
533                unescapeHtml(writer, str);
534                return writer.toString();
535            } catch (IOException ioe) {
536                //should be impossible
537                throw new UnhandledException(ioe);
538            }
539        }
540    
541        /**
542         * <p>Unescapes a string containing entity escapes to a string
543         * containing the actual Unicode characters corresponding to the
544         * escapes. Supports HTML 4.0 entities.</p>
545         *
546         * <p>For example, the string "&amp;lt;Fran&amp;ccedil;ais&amp;gt;"
547         * will become "&lt;Fran&ccedil;ais&gt;"</p>
548         *
549         * <p>If an entity is unrecognized, it is left alone, and inserted
550         * verbatim into the result string. e.g. "&amp;gt;&amp;zzzz;x" will
551         * become "&gt;&amp;zzzz;x".</p>
552         *
553         * @param writer  the writer receiving the unescaped string, not null
554         * @param string  the <code>String</code> to unescape, may be null
555         * @throws IllegalArgumentException if the writer is null
556         * @throws IOException if an IOException occurs
557         * @see #escapeHtml(String)
558         */
559        public static void unescapeHtml(Writer writer, String string) throws IOException {
560            if (writer == null ) {
561                throw new IllegalArgumentException ("The Writer must not be null.");
562            }
563            if (string == null) {
564                return;
565            }
566            Entities.HTML40.unescape(writer, string);
567        }
568    
569        //-----------------------------------------------------------------------
570        /**
571         * <p>Escapes the characters in a <code>String</code> using XML entities.</p>
572         *
573         * <p>For example: <tt>"bread" & "butter"</tt> =>
574         * <tt>&amp;quot;bread&amp;quot; &amp;amp; &amp;quot;butter&amp;quot;</tt>.
575         * </p>
576         *
577         * <p>Supports only the five basic XML entities (gt, lt, quot, amp, apos).
578         * Does not support DTDs or external entities.</p>
579         *
580         * <p>Note that unicode characters greater than 0x7f are currently escaped to 
581         *    their numerical \\u equivalent. This may change in future releases. </p>
582         *
583         * @param writer  the writer receiving the unescaped string, not null
584         * @param str  the <code>String</code> to escape, may be null
585         * @throws IllegalArgumentException if the writer is null
586         * @throws IOException if there is a problem writing
587         * @see #unescapeXml(java.lang.String)
588         */
589        public static void escapeXml(Writer writer, String str) throws IOException {
590            if (writer == null ) {
591                throw new IllegalArgumentException ("The Writer must not be null.");
592            }
593            if (str == null) {
594                return;
595            }
596            Entities.XML.escape(writer, str);
597        }
598    
599        /**
600         * <p>Escapes the characters in a <code>String</code> using XML entities.</p>
601         *
602         * <p>For example: <tt>"bread" & "butter"</tt> =>
603         * <tt>&amp;quot;bread&amp;quot; &amp;amp; &amp;quot;butter&amp;quot;</tt>.
604         * </p>
605         *
606         * <p>Supports only the five basic XML entities (gt, lt, quot, amp, apos).
607         * Does not support DTDs or external entities.</p>
608         *
609         * <p>Note that unicode characters greater than 0x7f are currently escaped to 
610         *    their numerical \\u equivalent. This may change in future releases. </p>
611         *
612         * @param str  the <code>String</code> to escape, may be null
613         * @return a new escaped <code>String</code>, <code>null</code> if null string input
614         * @see #unescapeXml(java.lang.String)
615         */
616        public static String escapeXml(String str) {
617            if (str == null) {
618                return null;
619            }
620            return Entities.XML.escape(str);
621        }
622    
623        //-----------------------------------------------------------------------
624        /**
625         * <p>Unescapes a string containing XML entity escapes to a string
626         * containing the actual Unicode characters corresponding to the
627         * escapes.</p>
628         *
629         * <p>Supports only the five basic XML entities (gt, lt, quot, amp, apos).
630         * Does not support DTDs or external entities.</p>
631         *
632         * <p>Note that numerical \\u unicode codes are unescaped to their respective 
633         *    unicode characters. This may change in future releases. </p>
634         *
635         * @param writer  the writer receiving the unescaped string, not null
636         * @param str  the <code>String</code> to unescape, may be null
637         * @throws IllegalArgumentException if the writer is null
638         * @throws IOException if there is a problem writing
639         * @see #escapeXml(String)
640         */
641        public static void unescapeXml(Writer writer, String str) throws IOException {
642            if (writer == null ) {
643                throw new IllegalArgumentException ("The Writer must not be null.");
644            }
645            if (str == null) {
646                return;
647            }
648            Entities.XML.unescape(writer, str);
649        }
650    
651        /**
652         * <p>Unescapes a string containing XML entity escapes to a string
653         * containing the actual Unicode characters corresponding to the
654         * escapes.</p>
655         *
656         * <p>Supports only the five basic XML entities (gt, lt, quot, amp, apos).
657         * Does not support DTDs or external entities.</p>
658         *
659         * <p>Note that numerical \\u unicode codes are unescaped to their respective 
660         *    unicode characters. This may change in future releases. </p>
661         *
662         * @param str  the <code>String</code> to unescape, may be null
663         * @return a new unescaped <code>String</code>, <code>null</code> if null string input
664         * @see #escapeXml(String)
665         */
666        public static String unescapeXml(String str) {
667            if (str == null) {
668                return null;
669            }
670            return Entities.XML.unescape(str);
671        }
672    
673        //-----------------------------------------------------------------------
674        /**
675         * <p>Escapes the characters in a <code>String</code> to be suitable to pass to
676         * an SQL query.</p>
677         *
678         * <p>For example,
679         * <pre>statement.executeQuery("SELECT * FROM MOVIES WHERE TITLE='" + 
680         *   StringEscapeUtils.escapeSql("McHale's Navy") + 
681         *   "'");</pre>
682         * </p>
683         *
684         * <p>At present, this method only turns single-quotes into doubled single-quotes
685         * (<code>"McHale's Navy"</code> => <code>"McHale''s Navy"</code>). It does not
686         * handle the cases of percent (%) or underscore (_) for use in LIKE clauses.</p>
687         *
688         * see http://www.jguru.com/faq/view.jsp?EID=8881
689         * @param str  the string to escape, may be null
690         * @return a new String, escaped for SQL, <code>null</code> if null string input
691         */
692        public static String escapeSql(String str) {
693            if (str == null) {
694                return null;
695            }
696            return StringUtils.replace(str, "'", "''");
697        }
698    
699        //-----------------------------------------------------------------------
700    
701        /**
702         * <p>Returns a <code>String</code> value for a CSV column enclosed in double quotes,
703         * if required.</p>
704         *
705         * <p>If the value contains a comma, newline or double quote, then the
706         *    String value is returned enclosed in double quotes.</p>
707         * </p>
708         *
709         * <p>Any double quote characters in the value are escaped with another double quote.</p>
710         *
711         * <p>If the value does not contain a comma, newline or double quote, then the
712         *    String value is returned unchanged.</p>
713         * </p>
714         *
715         * see <a href="http://en.wikipedia.org/wiki/Comma-separated_values">Wikipedia</a> and
716         * <a href="http://tools.ietf.org/html/rfc4180">RFC 4180</a>.
717         *
718         * @param str the input CSV column String, may be null
719         * @return the input String, enclosed in double quotes if the value contains a comma,
720         * newline or double quote, <code>null</code> if null string input
721         * @since 2.4
722         */
723        public static String escapeCsv(String str) {
724            if (StringUtils.containsNone(str, CSV_SEARCH_CHARS)) {
725                return str;
726            }
727            try {
728                StringWriter writer = new StringWriter();
729                escapeCsv(writer, str);
730                return writer.toString();
731            } catch (IOException ioe) {
732                // this should never ever happen while writing to a StringWriter
733                throw new UnhandledException(ioe);
734            }
735        }
736    
737        /**
738         * <p>Writes a <code>String</code> value for a CSV column enclosed in double quotes,
739         * if required.</p>
740         *
741         * <p>If the value contains a comma, newline or double quote, then the
742         *    String value is written enclosed in double quotes.</p>
743         * </p>
744         *
745         * <p>Any double quote characters in the value are escaped with another double quote.</p>
746         *
747         * <p>If the value does not contain a comma, newline or double quote, then the
748         *    String value is written unchanged (null values are ignored).</p>
749         * </p>
750         *
751         * see <a href="http://en.wikipedia.org/wiki/Comma-separated_values">Wikipedia</a> and
752         * <a href="http://tools.ietf.org/html/rfc4180">RFC 4180</a>.
753         *
754         * @param str the input CSV column String, may be null
755         * @param out Writer to write input string to, enclosed in double quotes if it contains
756         * a comma, newline or double quote
757         * @throws IOException if error occurs on underlying Writer
758         * @since 2.4
759         */
760        public static void escapeCsv(Writer out, String str) throws IOException {
761            if (StringUtils.containsNone(str, CSV_SEARCH_CHARS)) {
762                if (str != null) {
763                    out.write(str);
764                }
765                return;
766            }
767            out.write(CSV_QUOTE);
768            for (int i = 0; i < str.length(); i++) {
769                char c = str.charAt(i);
770                if (c == CSV_QUOTE) {
771                    out.write(CSV_QUOTE); // escape double quote
772                }
773                out.write(c);
774            }
775            out.write(CSV_QUOTE);
776        }
777    
778        /**
779         * <p>Returns a <code>String</code> value for an unescaped CSV column. </p>
780         *
781         * <p>If the value is enclosed in double quotes, and contains a comma, newline 
782         *    or double quote, then quotes are removed. 
783         * </p>
784         *
785         * <p>Any double quote escaped characters (a pair of double quotes) are unescaped 
786         *    to just one double quote. </p>
787         *
788         * <p>If the value is not enclosed in double quotes, or is and does not contain a 
789         *    comma, newline or double quote, then the String value is returned unchanged.</p>
790         * </p>
791         *
792         * see <a href="http://en.wikipedia.org/wiki/Comma-separated_values">Wikipedia</a> and
793         * <a href="http://tools.ietf.org/html/rfc4180">RFC 4180</a>.
794         *
795         * @param str the input CSV column String, may be null
796         * @return the input String, with enclosing double quotes removed and embedded double 
797         * quotes unescaped, <code>null</code> if null string input
798         * @since 2.4
799         */
800        public static String unescapeCsv(String str) {
801            if (str == null) {
802                return null;
803            }
804            try {
805                StringWriter writer = new StringWriter();
806                unescapeCsv(writer, str);
807                return writer.toString();
808            } catch (IOException ioe) {
809                // this should never ever happen while writing to a StringWriter
810                throw new UnhandledException(ioe);
811            }
812        }
813    
814        /**
815         * <p>Returns a <code>String</code> value for an unescaped CSV column. </p>
816         *
817         * <p>If the value is enclosed in double quotes, and contains a comma, newline 
818         *    or double quote, then quotes are removed. 
819         * </p>
820         *
821         * <p>Any double quote escaped characters (a pair of double quotes) are unescaped 
822         *    to just one double quote. </p>
823         *
824         * <p>If the value is not enclosed in double quotes, or is and does not contain a 
825         *    comma, newline or double quote, then the String value is returned unchanged.</p>
826         * </p>
827         *
828         * see <a href="http://en.wikipedia.org/wiki/Comma-separated_values">Wikipedia</a> and
829         * <a href="http://tools.ietf.org/html/rfc4180">RFC 4180</a>.
830         *
831         * @param str the input CSV column String, may be null
832         * @param out Writer to write the input String to, with enclosing double quotes 
833         * removed and embedded double quotes unescaped, <code>null</code> if null string input
834         * @throws IOException if error occurs on underlying Writer
835         * @since 2.4
836         */
837        public static void unescapeCsv(Writer out, String str) throws IOException {
838            if (str == null) {
839                return;
840            }
841            if (str.length() < 2) {
842                out.write(str);
843                return;
844            }
845            if ( str.charAt(0) != CSV_QUOTE || str.charAt(str.length() - 1) != CSV_QUOTE ) {
846                out.write(str);
847                return;
848            }
849    
850            // strip quotes
851            String quoteless = str.substring(1, str.length() - 1);
852    
853            if ( StringUtils.containsAny(quoteless, CSV_SEARCH_CHARS) ) {
854                // deal with escaped quotes; ie) ""
855                str = StringUtils.replace(quoteless, CSV_QUOTE_STR + CSV_QUOTE_STR, CSV_QUOTE_STR);
856            }
857    
858            out.write(str);
859        }
860    
861    }