1 /*
2 * Licensed to the Apache Software Foundation (ASF) under one or more
3 * contributor license agreements. See the NOTICE file distributed with
4 * this work for additional information regarding copyright ownership.
5 * The ASF licenses this file to You under the Apache license, Version 2.0
6 * (the "License"); you may not use this file except in compliance with
7 * the License. You may obtain a copy of the License at
8 *
9 * http://www.apache.org/licenses/LICENSE-2.0
10 *
11 * Unless required by applicable law or agreed to in writing, software
12 * distributed under the License is distributed on an "AS IS" BASIS,
13 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14 * See the license for the specific language governing permissions and
15 * limitations under the license.
16 */
17 package org.apache.logging.log4j.core.util;
18
19 /**
20 * This class is borrowed from <a href="https://github.com/FasterXML/jackson-core">Jackson</a>.
21 */
22 public final class JsonUtils {
23
24 private static final char[] HC = "0123456789ABCDEF".toCharArray();
25
26 /**
27 * Read-only encoding table for first 128 Unicode code points (single-byte UTF-8 characters).
28 * Value of 0 means "no escaping"; other positive values that value is character
29 * to use after backslash; and negative values that generic (backslash - u)
30 * escaping is to be used.
31 */
32 private static final int[] ESC_CODES;
33 static {
34 final int[] table = new int[128];
35 // Control chars need generic escape sequence
36 for (int i = 0; i < 32; ++i) {
37 // 04-Mar-2011, tatu: Used to use "-(i + 1)", replaced with constant
38 table[i] = -1;
39 }
40 /* Others (and some within that range too) have explicit shorter
41 * sequences
42 */
43 table['"'] = '"';
44 table['\\'] = '\\';
45 // Escaping of slash is optional, so let's not add it
46 table[0x08] = 'b';
47 table[0x09] = 't';
48 table[0x0C] = 'f';
49 table[0x0A] = 'n';
50 table[0x0D] = 'r';
51 ESC_CODES = table;
52 }
53
54 /**
55 * Temporary buffer used for composing quote/escape sequences
56 */
57 private final static ThreadLocal<char[]> _qbufLocal = new ThreadLocal<>();
58
59 private static char[] getQBuf() {
60 char[] _qbuf = _qbufLocal.get();
61 if (_qbuf == null) {
62 _qbuf = new char[6];
63 _qbuf[0] = '\\';
64 _qbuf[2] = '0';
65 _qbuf[3] = '0';
66
67 _qbufLocal.set(_qbuf);
68 }
69 return _qbuf;
70 }
71
72 /**
73 * Quote text contents using JSON standard quoting, and append results to a supplied {@link StringBuilder}.
74 */
75 public static void quoteAsString(final CharSequence input, final StringBuilder output) {
76 final char[] qbuf = getQBuf();
77 final int escCodeCount = ESC_CODES.length;
78 int inPtr = 0;
79 final int inputLen = input.length();
80
81 outer:
82 while (inPtr < inputLen) {
83 tight_loop:
84 while (true) {
85 final char c = input.charAt(inPtr);
86 if (c < escCodeCount && ESC_CODES[c] != 0) {
87 break tight_loop;
88 }
89 output.append(c);
90 if (++inPtr >= inputLen) {
91 break outer;
92 }
93 }
94 // something to escape; 2 or 6-char variant?
95 final char d = input.charAt(inPtr++);
96 final int escCode = ESC_CODES[d];
97 final int length = (escCode < 0)
98 ? _appendNumeric(d, qbuf)
99 : _appendNamed(escCode, qbuf);
100
101 output.append(qbuf, 0, length);
102 }
103 }
104
105 private static int _appendNumeric(final int value, final char[] qbuf) {
106 qbuf[1] = 'u';
107 // We know it's a control char, so only the last 2 chars are non-0
108 qbuf[4] = HC[value >> 4];
109 qbuf[5] = HC[value & 0xF];
110 return 6;
111 }
112
113 private static int _appendNamed(final int esc, final char[] qbuf) {
114 qbuf[1] = (char) esc;
115 return 2;
116 }
117
118 }