aboutsummaryrefslogtreecommitdiff
path: root/src/buffer.c
diff options
context:
space:
mode:
Diffstat (limited to 'src/buffer.c')
-rw-r--r--src/buffer.c110
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\*=========================================================================*/
11static int recvraw(p_buffer buf, size_t wanted, luaL_Buffer *b); 11static int recvraw(p_buffer buf, size_t wanted, luaL_Buffer *b);
12static int recvline(p_buffer buf, luaL_Buffer *b); 12static int recvline(p_buffer buf, luaL_Buffer *b, size_t budget);
13static int recvall(p_buffer buf, luaL_Buffer *b); 13static int recvall(p_buffer buf, luaL_Buffer *b, size_t budget);
14static int buffer_get(p_buffer buf, const char **data, size_t *count); 14static int buffer_get(p_buffer buf, const char **data, size_t *count);
15static void buffer_skip(p_buffer buf, size_t count); 15static void buffer_skip(p_buffer buf, size_t count);
16static int sendraw(p_buffer buf, const char *data, size_t count, size_t *sent); 16static 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) {
105int buffer_meth_receive(lua_State *L, p_buffer buf) { 117int 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\*-------------------------------------------------------------------------*/
205static int recvall(p_buffer buf, luaL_Buffer *b) { 251static 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\*-------------------------------------------------------------------------*/
225static int recvline(p_buffer buf, luaL_Buffer *b) { 284static 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/*-------------------------------------------------------------------------*\