Coverage Report

Created: 2023-06-06 06:17

/src/uWebSockets/src/WebSocketExtensions.h
Line
Count
Source (jump to first uncovered line)
1
/*
2
 * Authored by Alex Hultman, 2018-2021.
3
 * Intellectual property of third-party.
4
5
 * Licensed under the Apache License, Version 2.0 (the "License");
6
 * you may not use this file except in compliance with the License.
7
 * 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
18
#ifndef UWS_WEBSOCKETEXTENSIONS_H
19
#define UWS_WEBSOCKETEXTENSIONS_H
20
21
/* There is a new, huge bug scenario that needs to be fixed:
22
 * pub/sub does not support being in DEDICATED_COMPRESSOR-mode while having
23
 * some clients downgraded to SHARED_COMPRESSOR - we cannot allow the client to
24
 * demand a downgrade to SHARED_COMPRESSOR (yet) until we fix that scenario in pub/sub */
25
// #define UWS_ALLOW_SHARED_AND_DEDICATED_COMPRESSOR_MIX
26
27
/* We forbid negotiating 8 windowBits since Zlib has a bug with this */
28
// #define UWS_ALLOW_8_WINDOW_BITS
29
30
#include <climits>
31
#include <cctype>
32
#include <string>
33
#include <string_view>
34
#include <tuple>
35
36
namespace uWS {
37
38
enum ExtensionTokens {
39
    /* Standard permessage-deflate tokens */
40
    TOK_PERMESSAGE_DEFLATE = 1838,
41
    TOK_SERVER_NO_CONTEXT_TAKEOVER = 2807,
42
    TOK_CLIENT_NO_CONTEXT_TAKEOVER = 2783,
43
    TOK_SERVER_MAX_WINDOW_BITS = 2372,
44
    TOK_CLIENT_MAX_WINDOW_BITS = 2348,
45
    /* Non-standard alias for Safari */
46
    TOK_X_WEBKIT_DEFLATE_FRAME = 2149,
47
    TOK_NO_CONTEXT_TAKEOVER = 2049,
48
    TOK_MAX_WINDOW_BITS = 1614
49
50
};
51
52
struct ExtensionsParser {
53
private:
54
    int *lastInteger = nullptr;
55
56
public:
57
    /* Standard */
58
    bool perMessageDeflate = false;
59
    bool serverNoContextTakeover = false;
60
    bool clientNoContextTakeover = false;
61
    int serverMaxWindowBits = 0;
62
    int clientMaxWindowBits = 0;
63
64
    /* Non-standard Safari */
65
    bool xWebKitDeflateFrame = false;
66
    bool noContextTakeover = false;
67
    int maxWindowBits = 0;
68
69
6.27k
    int getToken(const char *&in, const char *stop) {
70
9.97k
        while (in != stop && !isalnum(*in)) {
71
3.69k
            in++;
72
3.69k
        }
73
74
        /* Don't care more than this for now */
75
6.27k
        static_assert(SHRT_MIN > INT_MIN, "Integer overflow fix is invalid for this platform, report this as a bug!");
76
77
6.27k
        int hashedToken = 0;
78
4.31M
        while (in != stop && (isalnum(*in) || *in == '-' || *in == '_')) {
79
4.31M
            if (isdigit(*in)) {
80
                /* This check is a quick and incorrect fix for integer overflow
81
                 * in oss-fuzz but we don't care as it doesn't matter either way */
82
6.94k
                if (hashedToken > SHRT_MIN && hashedToken < SHRT_MAX) {
83
5.96k
                    hashedToken = hashedToken * 10 - (*in - '0');
84
5.96k
                }
85
4.30M
            } else {
86
4.30M
                hashedToken += *in;
87
4.30M
            }
88
4.31M
            in++;
89
4.31M
        }
90
6.27k
        return hashedToken;
91
6.27k
    }
92
93
1.32k
    ExtensionsParser(const char *data, size_t length) {
94
1.32k
        const char *stop = data + length;
95
1.32k
        int token = 1;
96
97
        /* Ignore anything before permessage-deflate or x-webkit-deflate-frame */
98
3.42k
        for (; token && token != TOK_PERMESSAGE_DEFLATE && token != TOK_X_WEBKIT_DEFLATE_FRAME; token = getToken(data, stop));
99
100
        /* What protocol are we going to use? */
101
1.32k
        perMessageDeflate = (token == TOK_PERMESSAGE_DEFLATE);
102
1.32k
        xWebKitDeflateFrame = (token == TOK_X_WEBKIT_DEFLATE_FRAME);
103
104
4.18k
        while ((token = getToken(data, stop))) {
105
2.85k
            switch (token) {
106
2
            case TOK_X_WEBKIT_DEFLATE_FRAME:
107
                /* Duplicates not allowed/supported */
108
2
                return;
109
192
            case TOK_NO_CONTEXT_TAKEOVER:
110
192
                noContextTakeover = true;
111
192
                break;
112
260
            case TOK_MAX_WINDOW_BITS:
113
260
                maxWindowBits = 1;
114
260
                lastInteger = &maxWindowBits;
115
260
                break;
116
2
            case TOK_PERMESSAGE_DEFLATE:
117
                /* Duplicates not allowed/supported */
118
2
                return;
119
192
            case TOK_SERVER_NO_CONTEXT_TAKEOVER:
120
192
                serverNoContextTakeover = true;
121
192
                break;
122
192
            case TOK_CLIENT_NO_CONTEXT_TAKEOVER:
123
192
                clientNoContextTakeover = true;
124
192
                break;
125
252
            case TOK_SERVER_MAX_WINDOW_BITS:
126
252
                serverMaxWindowBits = 1;
127
252
                lastInteger = &serverMaxWindowBits;
128
252
                break;
129
258
            case TOK_CLIENT_MAX_WINDOW_BITS:
130
258
                clientMaxWindowBits = 1;
131
258
                lastInteger = &clientMaxWindowBits;
132
258
                break;
133
1.50k
            default:
134
1.50k
                if (token < 0 && lastInteger) {
135
372
                    *lastInteger = -token;
136
372
                }
137
1.50k
                break;
138
2.85k
            }
139
2.85k
        }
140
1.32k
    }
141
};
142
143
/* Takes what we (the server) wants, returns what we got */
144
1.99k
static inline std::tuple<bool, int, int, std::string_view> negotiateCompression(bool wantCompression, int wantedCompressionWindow, int wantedInflationWindow, std::string_view offer) {
145
146
    /* If we don't want compression then we are done here */
147
1.99k
    if (!wantCompression) {
148
664
        return {false, 0, 0, ""};
149
664
    }
150
151
1.32k
    ExtensionsParser ep(offer.data(), offer.length());
152
153
1.32k
    static thread_local std::string response;
154
1.32k
    response = "";
155
156
1.32k
    int compressionWindow = wantedCompressionWindow;
157
1.32k
    int inflationWindow = wantedInflationWindow;
158
1.32k
    bool compression = false;
159
160
1.32k
    if (ep.xWebKitDeflateFrame) {
161
        /* We now have compression */
162
68
        compression = true;
163
68
        response = "x-webkit-deflate-frame";
164
165
        /* If the other peer has DEMANDED us no sliding window,
166
         * we cannot compress with anything other than shared compressor */
167
68
        if (ep.noContextTakeover) {
168
            /* We must fail here right now (fix pub/sub) */
169
2
#ifndef UWS_ALLOW_SHARED_AND_DEDICATED_COMPRESSOR_MIX
170
2
            if (wantedCompressionWindow != 0) {
171
1
                return {false, 0, 0, ""};
172
1
            }
173
1
#endif
174
175
1
            compressionWindow = 0;
176
1
        }
177
178
        /* If the other peer has DEMANDED us to use a limited sliding window,
179
         * we have to limit out compression sliding window */
180
67
        if (ep.maxWindowBits && ep.maxWindowBits < compressionWindow) {
181
8
            compressionWindow = ep.maxWindowBits;
182
8
#ifndef UWS_ALLOW_8_WINDOW_BITS
183
            /* We cannot really deny this, so we have to disable compression in this case */
184
8
            if (compressionWindow == 8) {
185
1
                return {false, 0, 0, ""};
186
1
            }
187
8
#endif
188
8
        }
189
190
        /* We decide our own inflation sliding window (and their compression sliding window) */
191
66
        if (wantedInflationWindow < 15) {
192
66
            if (!wantedInflationWindow) {
193
66
                response += "; no_context_takeover";
194
66
            } else {
195
0
                response += "; max_window_bits=" + std::to_string(wantedInflationWindow);
196
0
            }
197
66
        }
198
1.26k
    } else if (ep.perMessageDeflate) {
199
        /* We now have compression */
200
130
        compression = true;
201
130
        response = "permessage-deflate";
202
203
130
        if (ep.clientNoContextTakeover) {
204
2
            inflationWindow = 0;
205
128
        } else if (ep.clientMaxWindowBits && ep.clientMaxWindowBits != 1) {
206
62
            inflationWindow = std::min<int>(ep.clientMaxWindowBits, inflationWindow);
207
62
        }
208
209
        /* Whatever we have now, write */
210
130
        if (inflationWindow < 15) {
211
130
            if (!inflationWindow || !ep.clientMaxWindowBits) {
212
130
                response += "; client_no_context_takeover";
213
130
                inflationWindow = 0;
214
130
            } else {
215
0
                response += "; client_max_window_bits=" + std::to_string(inflationWindow);
216
0
            }
217
130
        }
218
219
        /* This block basically lets the client lower it */
220
130
        if (ep.serverNoContextTakeover) {
221
        /* This is an important (temporary) fix since we haven't allowed
222
         * these two modes to mix, and pub/sub will not handle this case (yet) */
223
#ifdef UWS_ALLOW_SHARED_AND_DEDICATED_COMPRESSOR_MIX
224
            compressionWindow = 0;
225
#endif
226
128
        } else if (ep.serverMaxWindowBits) {
227
60
            compressionWindow = std::min<int>(ep.serverMaxWindowBits, compressionWindow);
228
60
#ifndef UWS_ALLOW_8_WINDOW_BITS
229
            /* Zlib cannot do windowBits=8, memLevel=1 so we raise it up to 9 minimum */
230
60
            if (compressionWindow == 8) {
231
1
                compressionWindow = 9;
232
1
            }
233
60
#endif
234
60
        }
235
236
        /* Whatever we have now, write */
237
130
        if (compressionWindow < 15) {
238
130
            if (!compressionWindow) {
239
65
                response += "; server_no_context_takeover";
240
65
            } else {
241
65
                response += "; server_max_window_bits=" + std::to_string(compressionWindow);
242
65
            }
243
130
        }
244
130
    }
245
246
    /* A final sanity check (this check does not actually catch too high values!) */
247
1.32k
    if ((compressionWindow && compressionWindow < 8) || compressionWindow > 15 || (inflationWindow && inflationWindow < 8) || inflationWindow > 15) {
248
8
        return {false, 0, 0, ""};
249
8
    }
250
251
1.31k
    return {compression, compressionWindow, inflationWindow, response};
252
1.32k
}
253
254
}
255
256
#endif // UWS_WEBSOCKETEXTENSIONS_H