aboutsummaryrefslogtreecommitdiffstats
path: root/vespaclient-container-plugin/src/main/java/com/yahoo/vespa/http/server/Encoder.java
blob: b30b733c9cfa7f6af9dc4436d1c7321c1dee4134 (plain) (blame)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
// Copyright Vespa.ai. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
package com.yahoo.vespa.http.server;

/**
 * Simple encoding scheme to remove space, linefeed, control characters and
 * anything outside ISO 646.irv:1991 from strings. The scheme is supposed to be
 * human readable and debugging friendly. Opening and closing curly braces are
 * used as quoting characters, the output is by definition US-ASCII only
 * characters.
 *
 * @author Steinar Knutsen
 */
final class Encoder {

    /**
     * ISO 646.irv:1991 safe quoting into a StringBuilder instance.
     *
     * @param input the string to encode
     * @param output the destination buffer
     * @return the destination buffer given as input
     */
    public static StringBuilder encode(String input, StringBuilder output) {
        for (int i = 0; i < input.length(); i = input.offsetByCodePoints(i, 1)) {
            int c = input.codePointAt(i);
            if (c <= '~') {
                if (c <= ' ') {
                    encode(c, output);
                } else {
                    switch (c) {
                        case '{':
                        case '}':
                            encode(c, output);
                            break;
                        default:
                            output.append((char) c);
                    }
                }
            } else {
                encode(c, output);
            }
        }
        return output;
    }

    /**
     * ISO 646.irv:1991 safe unquoting into a StringBuilder instance.
     *
     * @param input the string to decode
     * @param output the destination buffer
     * @return the destination buffer given as input
     * @throws IllegalArgumentException if the input string contains unexpected or invalid data
     */
    public static StringBuilder decode(String input, StringBuilder output) {
        for (int i = 0; i < input.length(); i = input.offsetByCodePoints(i, 1)) {
            int c = input.codePointAt(i);
            if (c > '~')
                throw new IllegalArgumentException("Input contained character above printable ASCII at position " + i);
            if (c == '{')
                i = decode(input, i, output);
            else
                output.append((char) c);
        }
        return output;
    }

    private static int decode(String input, int offset, StringBuilder output) {
        char c = 0;
        int end = offset;
        int start = offset + 1;
        int codePoint;

        while ('}' != c) {
            if (++end >= input.length()) {
                throw new IllegalArgumentException("Unterminated quoted character or empty quoting.");
            }
            c = input.charAt(end);
        }
        try {
            codePoint = Integer.parseInt(input.substring(start, end), 16);
        } catch (NumberFormatException e) {
            throw new IllegalArgumentException("Unexpected quoted data: [" + input.substring(start, end) + "]", e);
        }
        if (Character.charCount(codePoint) > 1) {
            try {
                output.append(Character.toChars(codePoint));
            } catch (IllegalArgumentException e) {
                throw new IllegalArgumentException("Unexpected quoted data: [" + input.substring(start, end) + "]", e);
            }
        } else {
            output.append((char) codePoint);
        }
        return end;

    }

    private static void encode(int c, StringBuilder output) {
        output.append("{").append(Integer.toHexString(c)).append("}");
    }

}