diff options
| author | Caleb Maclennan <caleb@alerque.com> | 2026-08-31 10:57:58 +0300 |
|---|---|---|
| committer | GitHub <noreply@github.com> | 2026-08-31 10:57:58 +0300 |
| commit | 8f18ce95bb38c7f5c4bef5b3684cf1b0df1fc266 (patch) | |
| tree | c11ae97c461d7f9c3cfe84773b9975de48d5ba9c /src | |
| parent | 535178a3f0e2cff59f4d59ee3a655bee263a5c90 (diff) | |
| parent | 4863f32b358a8d533f629084a86fb4932ebbd2f3 (diff) | |
| download | luasocket-8f18ce95bb38c7f5c4bef5b3684cf1b0df1fc266.tar.gz luasocket-8f18ce95bb38c7f5c4bef5b3684cf1b0df1fc266.tar.bz2 luasocket-8f18ce95bb38c7f5c4bef5b3684cf1b0df1fc266.zip | |
Merge pull request #463 from lunarmodules/feat/receive-limit
feat(receive): add maxsize argument to bound memory usage
Diffstat (limited to 'src')
| -rw-r--r-- | src/buffer.c | 110 |
1 files changed, 90 insertions, 20 deletions
diff --git a/src/buffer.c b/src/buffer.c index 7148be3..3d48a09 100644 --- a/src/buffer.c +++ b/src/buffer.c | |||
| @@ -9,12 +9,24 @@ | |||
| 9 | * Internal function prototypes | 9 | * Internal function prototypes |
| 10 | \*=========================================================================*/ | 10 | \*=========================================================================*/ |
| 11 | static int recvraw(p_buffer buf, size_t wanted, luaL_Buffer *b); | 11 | static int recvraw(p_buffer buf, size_t wanted, luaL_Buffer *b); |
| 12 | static int recvline(p_buffer buf, luaL_Buffer *b); | 12 | static int recvline(p_buffer buf, luaL_Buffer *b, size_t budget); |
| 13 | static int recvall(p_buffer buf, luaL_Buffer *b); | 13 | static int recvall(p_buffer buf, luaL_Buffer *b, size_t budget); |
| 14 | static int buffer_get(p_buffer buf, const char **data, size_t *count); | 14 | static int buffer_get(p_buffer buf, const char **data, size_t *count); |
| 15 | static void buffer_skip(p_buffer buf, size_t count); | 15 | static void buffer_skip(p_buffer buf, size_t count); |
| 16 | static int sendraw(p_buffer buf, const char *data, size_t count, size_t *sent); | 16 | static int sendraw(p_buffer buf, const char *data, size_t count, size_t *sent); |
| 17 | 17 | ||
| 18 | /* Internal completion code for buffer_meth_receive. err is not confined to | ||
| 19 | * the IO_* enum: socket_recv/socket_send (usocket.c/wsocket.c) propagate raw | ||
| 20 | * platform errors (POSIX errno, Windows WSA codes) straight through, and | ||
| 21 | * those are always positive, so a positive sentinel here could collide with | ||
| 22 | * a genuine transport error (e.g. errno 1 == EPERM) and get misreported as | ||
| 23 | * "oversized". Chosen negative and outside {IO_DONE, IO_TIMEOUT, IO_CLOSED, | ||
| 24 | * IO_UNKNOWN} (0, -1, -2, -3) so it can never collide with anything err | ||
| 25 | * legitimately takes. | ||
| 26 | * MUST be handled before buf->io->error() is called -- it is not a transport | ||
| 27 | * error. */ | ||
| 28 | #define BUF_OVERSIZED (-1000) | ||
| 29 | |||
| 18 | /* min and max macros */ | 30 | /* min and max macros */ |
| 19 | #ifndef MIN | 31 | #ifndef MIN |
| 20 | #define MIN(x, y) ((x) < (y) ? x : y) | 32 | #define MIN(x, y) ((x) < (y) ? x : y) |
| @@ -105,8 +117,35 @@ int buffer_meth_send(lua_State *L, p_buffer buf) { | |||
| 105 | int buffer_meth_receive(lua_State *L, p_buffer buf) { | 117 | int buffer_meth_receive(lua_State *L, p_buffer buf) { |
| 106 | int err = IO_DONE, top; | 118 | int err = IO_DONE, top; |
| 107 | luaL_Buffer b; | 119 | luaL_Buffer b; |
| 108 | size_t size; | 120 | size_t size, wanted = 0, maxsize = 0; |
| 121 | size_t budget = 0; /* 0 == unlimited */ | ||
| 122 | int numeric = lua_isnumber(L, 2); | ||
| 109 | const char *part = luaL_optlstring(L, 3, "", &size); | 123 | const char *part = luaL_optlstring(L, 3, "", &size); |
| 124 | |||
| 125 | /* ---- validation: must precede timeout_markstart() and any I/O ---- */ | ||
| 126 | if (numeric) { | ||
| 127 | double n = lua_tonumber(L, 2); | ||
| 128 | luaL_argcheck(L, n >= 0 && n < (lua_Number) ((size_t) -1), 2, | ||
| 129 | "invalid receive pattern"); | ||
| 130 | wanted = (size_t) n; | ||
| 131 | } else { | ||
| 132 | const char *p = luaL_optstring(L, 2, "*l"); | ||
| 133 | luaL_argcheck(L, p[0] == '*' && (p[1] == 'l' || p[1] == 'a'), | ||
| 134 | 2, "invalid receive pattern"); | ||
| 135 | } | ||
| 136 | if (!lua_isnoneornil(L, 4)) { | ||
| 137 | double m = luaL_checknumber(L, 4); | ||
| 138 | luaL_argcheck(L, m >= 1 && m < (lua_Number) ((size_t) -1), 4, | ||
| 139 | "maxsize must be a positive number"); | ||
| 140 | maxsize = (size_t) m; | ||
| 141 | luaL_argcheck(L, size < maxsize, 4, | ||
| 142 | "prefix length >= maxsize (drain with prefix=\"\" or raise maxsize)"); | ||
| 143 | if (numeric) | ||
| 144 | luaL_argcheck(L, wanted <= maxsize, 4, | ||
| 145 | "maxsize smaller than requested byte count"); | ||
| 146 | budget = maxsize - size; | ||
| 147 | } | ||
| 148 | |||
| 110 | timeout_markstart(buf->tm); | 149 | timeout_markstart(buf->tm); |
| 111 | /* make sure we don't confuse buffer stuff with arguments */ | 150 | /* make sure we don't confuse buffer stuff with arguments */ |
| 112 | lua_settop(L, 3); | 151 | lua_settop(L, 3); |
| @@ -116,24 +155,28 @@ int buffer_meth_receive(lua_State *L, p_buffer buf) { | |||
| 116 | luaL_buffinit(L, &b); | 155 | luaL_buffinit(L, &b); |
| 117 | luaL_addlstring(&b, part, size); | 156 | luaL_addlstring(&b, part, size); |
| 118 | /* receive new patterns */ | 157 | /* receive new patterns */ |
| 119 | if (!lua_isnumber(L, 2)) { | 158 | if (!numeric) { |
| 120 | const char *p= luaL_optstring(L, 2, "*l"); | 159 | const char *p= luaL_optstring(L, 2, "*l"); |
| 121 | if (p[0] == '*' && p[1] == 'l') err = recvline(buf, &b); | 160 | if (p[0] == '*' && p[1] == 'l') err = recvline(buf, &b, budget); |
| 122 | else if (p[0] == '*' && p[1] == 'a') err = recvall(buf, &b); | 161 | else err = recvall(buf, &b, budget); |
| 123 | else luaL_argcheck(L, 0, 2, "invalid receive pattern"); | ||
| 124 | /* get a fixed number of bytes (minus what was already partially | 162 | /* get a fixed number of bytes (minus what was already partially |
| 125 | * received) */ | 163 | * received) */ |
| 126 | } else { | 164 | } else { |
| 127 | double n = lua_tonumber(L, 2); | ||
| 128 | size_t wanted = (size_t) n; | ||
| 129 | luaL_argcheck(L, n >= 0, 2, "invalid receive pattern"); | ||
| 130 | if (size == 0 || wanted > size) | 165 | if (size == 0 || wanted > size) |
| 131 | err = recvraw(buf, wanted-size, &b); | 166 | err = recvraw(buf, wanted-size, &b); |
| 132 | } | 167 | } |
| 133 | /* check if there was an error */ | 168 | /* check if there was an error */ |
| 134 | if (err != IO_DONE) { | 169 | /* luaL_pushresult(&b) must come first (its accumulator lives on the |
| 135 | /* we can't push anyting in the stack before pushing the | 170 | * stack), but the partial it produces belongs in slot 3, not 1 -- so |
| 136 | * contents of the buffer. this is the reason for the complication */ | 171 | * both error branches push buffer/error/buffer-copy/nil, then |
| 172 | * lua_replace the nil into slot 1. */ | ||
| 173 | if (err == BUF_OVERSIZED) { | ||
| 174 | luaL_pushresult(&b); | ||
| 175 | lua_pushliteral(L, "oversized"); | ||
| 176 | lua_pushvalue(L, -2); | ||
| 177 | lua_pushnil(L); | ||
| 178 | lua_replace(L, -4); | ||
| 179 | } else if (err != IO_DONE) { | ||
| 137 | luaL_pushresult(&b); | 180 | luaL_pushresult(&b); |
| 138 | lua_pushstring(L, buf->io->error(buf->io->ctx, err)); | 181 | lua_pushstring(L, buf->io->error(buf->io->ctx, err)); |
| 139 | lua_pushvalue(L, -2); | 182 | lua_pushvalue(L, -2); |
| @@ -201,36 +244,61 @@ static int recvraw(p_buffer buf, size_t wanted, luaL_Buffer *b) { | |||
| 201 | 244 | ||
| 202 | /*-------------------------------------------------------------------------*\ | 245 | /*-------------------------------------------------------------------------*\ |
| 203 | * Reads everything until the connection is closed (buffered) | 246 | * Reads everything until the connection is closed (buffered) |
| 247 | * budget == 0 means unlimited; otherwise the number of payload bytes still | ||
| 248 | * allowed. Completion (connection closed) beats the cap: filling the cap | ||
| 249 | * exactly and then seeing EOF means the whole stream was received. | ||
| 204 | \*-------------------------------------------------------------------------*/ | 250 | \*-------------------------------------------------------------------------*/ |
| 205 | static int recvall(p_buffer buf, luaL_Buffer *b) { | 251 | static int recvall(p_buffer buf, luaL_Buffer *b, size_t budget) { |
| 206 | int err = IO_DONE; | 252 | int err = IO_DONE; |
| 207 | size_t total = 0; | 253 | size_t total = 0; |
| 208 | while (err == IO_DONE) { | 254 | while (err == IO_DONE) { |
| 209 | const char *data; size_t count; | 255 | const char *data; size_t count; |
| 210 | err = buffer_get(buf, &data, &count); | 256 | err = buffer_get(buf, &data, &count); |
| 257 | if (budget && count > budget - total) { /* strictly more than fits */ | ||
| 258 | count = budget - total; | ||
| 259 | luaL_addlstring(b, data, count); | ||
| 260 | buffer_skip(buf, count); | ||
| 261 | return BUF_OVERSIZED; | ||
| 262 | } | ||
| 211 | total += count; | 263 | total += count; |
| 212 | luaL_addlstring(b, data, count); | 264 | luaL_addlstring(b, data, count); |
| 213 | buffer_skip(buf, count); | 265 | buffer_skip(buf, count); |
| 214 | } | 266 | } |
| 215 | if (err == IO_CLOSED) { | 267 | if (err == IO_CLOSED) { /* completion beats the cap */ |
| 216 | if (total > 0) return IO_DONE; | 268 | if (total > 0) return IO_DONE; |
| 217 | else return IO_CLOSED; | 269 | else return IO_CLOSED; |
| 218 | } else return err; | 270 | } |
| 271 | if (budget && total == budget) return BUF_OVERSIZED; | ||
| 272 | return err; | ||
| 219 | } | 273 | } |
| 220 | 274 | ||
| 221 | /*-------------------------------------------------------------------------*\ | 275 | /*-------------------------------------------------------------------------*\ |
| 222 | * Reads a line terminated by a CR LF pair or just by a LF. The CR and LF | 276 | * Reads a line terminated by a CR LF pair or just by a LF. The CR and LF |
| 223 | * are not returned by the function and are discarded from the buffer | 277 | * are not returned by the function and are discarded from the buffer |
| 278 | * budget == 0 means unlimited; otherwise the number of payload bytes still | ||
| 279 | * allowed. The cap test sits before consuming a byte, so a line of exactly | ||
| 280 | * budget payload bytes succeeds while budget+1 reports oversized. A timeout | ||
| 281 | * or close with the payload exactly at the cap and no terminator yet also | ||
| 282 | * resolves to oversized, never to timeout/closed. | ||
| 224 | \*-------------------------------------------------------------------------*/ | 283 | \*-------------------------------------------------------------------------*/ |
| 225 | static int recvline(p_buffer buf, luaL_Buffer *b) { | 284 | static int recvline(p_buffer buf, luaL_Buffer *b, size_t budget) { |
| 226 | int err = IO_DONE; | 285 | int err = IO_DONE; |
| 286 | size_t total = 0; | ||
| 227 | while (err == IO_DONE) { | 287 | while (err == IO_DONE) { |
| 228 | size_t count, pos; const char *data; | 288 | size_t count, pos; const char *data; |
| 229 | err = buffer_get(buf, &data, &count); | 289 | err = buffer_get(buf, &data, &count); |
| 230 | pos = 0; | 290 | pos = 0; |
| 231 | while (pos < count && data[pos] != '\n') { | 291 | while (pos < count && data[pos] != '\n') { |
| 232 | /* we ignore all \r's */ | 292 | /* we ignore all \r's -- they are consumed but never counted */ |
| 233 | if (data[pos] != '\r') luaL_addchar(b, data[pos]); | 293 | if (data[pos] != '\r') { |
| 294 | if (budget && total == budget) { | ||
| 295 | /* leave the offending byte in the buffer for the next call */ | ||
| 296 | buffer_skip(buf, pos); | ||
| 297 | return BUF_OVERSIZED; | ||
| 298 | } | ||
| 299 | luaL_addchar(b, data[pos]); | ||
| 300 | total++; | ||
| 301 | } | ||
| 234 | pos++; | 302 | pos++; |
| 235 | } | 303 | } |
| 236 | if (pos < count) { /* found '\n' */ | 304 | if (pos < count) { /* found '\n' */ |
| @@ -239,7 +307,9 @@ static int recvline(p_buffer buf, luaL_Buffer *b) { | |||
| 239 | } else /* reached the end of the buffer */ | 307 | } else /* reached the end of the buffer */ |
| 240 | buffer_skip(buf, pos); | 308 | buffer_skip(buf, pos); |
| 241 | } | 309 | } |
| 242 | return err; | 310 | if (err == IO_DONE) return IO_DONE; /* '\n' found: success, regardless of total */ |
| 311 | if (budget && total == budget) return BUF_OVERSIZED; /* stalled/closed exactly at the cap: I1 */ | ||
| 312 | return err; /* real timeout/closed, below the cap */ | ||
| 243 | } | 313 | } |
| 244 | 314 | ||
| 245 | /*-------------------------------------------------------------------------*\ | 315 | /*-------------------------------------------------------------------------*\ |
