-
Notifications
You must be signed in to change notification settings - Fork 40
/
output.c
687 lines (579 loc) · 16 KB
/
output.c
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
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
/* $Id$ */
/*
* Copyright (c) 2015, 2016, 2020 Kristaps Dzonsons <[email protected]>
*
* Permission to use, copy, modify, and distribute this software for any
* purpose with or without fee is hereby granted, provided that the above
* copyright notice and this permission notice appear in all copies.
*
* THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
* WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
* MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
* ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
* WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
* ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
* OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
*/
#include "config.h"
#include <arpa/inet.h>
#include <assert.h>
#include <ctype.h>
#include <inttypes.h>
#include <stdarg.h>
#include <stdint.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <zlib.h>
#include "kcgi.h"
#include "extern.h"
/*
* The state of our HTTP response.
* We can be in KSTATE_HEAD, where we're printing HTTP headers; or
* KSTATE_BODY, where we're printing the body parts.
* So if we try to print a header when we're supposed to be in the body,
* this will be caught.
*/
enum kstate {
KSTATE_HEAD = 0,
KSTATE_BODY
};
/*
* Interior data.
* This is used for managing HTTP compression.
*/
struct kdata {
int debugging; /* debugging flags */
int fcgi; /* file descriptor or -1 */
int control; /* control socket or -1 */
char linebuf[80]; /* debugging output line buffer */
size_t linebufpos; /* position in line buffer */
uint64_t bytes; /* total bytes written */
uint16_t requestId; /* current requestId or 0 */
enum kstate state; /* see enum kstate */
gzFile gz; /* if not NULL, then compressor */
char *outbuf; /* buffered output */
size_t outbufpos; /* position in output buffer */
size_t outbufsz; /* size of output buffer */
int disabled; /* no more writers */
};
/*
* A writer is allocated for use by a front-end formatter.
* It is basically the bridge between the back-end writer (currently
* operated by kdata_write) and the front-ends like kcgijson.
*/
struct kcgi_writer {
struct kdata *kdata; /* the back-end writer context */
int type; /* currently unused */
};
static char *
fcgi_header(uint8_t type, uint16_t requestId,
uint16_t contentLength, uint8_t paddingLength)
{
static uint8_t header[8];
/* Masking probably not necessary: truncation. */
header[0] = 1;
header[1] = type;
header[2] = (requestId >> 8) & 0xff;
header[3] = requestId & 0xff;
header[4] = (contentLength >> 8) & 0xff;
header[5] = contentLength & 0xff;
header[6] = paddingLength;
header[7] = 0;
return((char *)header);
}
/*
* Write a `stdout' FastCGI packet.
* This involves writing the header, then the data itself, then padding.
* Returns KCGI_OK, KCGI_SYSTEM, or KCGI_HUP.
*/
static enum kcgi_err
fcgi_write(uint8_t type, const struct kdata *p, const char *buf, size_t sz)
{
const char *pad = "\0\0\0\0\0\0\0\0";
char *head;
size_t rsz, padlen;
enum kcgi_err er = KCGI_OK;
/*
* Break up the data stream into FastCGI-capable chunks of at
* most UINT16_MAX bytes.
* Send each of these in its own FastCGI frame.
* Pad to 8-byte boundary.
*/
do {
rsz = sz > UINT16_MAX ? UINT16_MAX : sz;
padlen = -rsz % 8;
head = fcgi_header(type, p->requestId, rsz, padlen);
if ((er = fullwritenoerr(p->fcgi, head, 8)) != KCGI_OK)
break;
if ((er = fullwritenoerr(p->fcgi, buf, rsz)) != KCGI_OK)
break;
if ((er = fullwritenoerr(p->fcgi, pad, padlen)) != KCGI_OK)
break;
sz -= rsz;
buf += rsz;
} while (sz > 0);
return er;
}
/*
* Flushes a buffer "buf" of size "sz" to the wire (stdout in the case
* of CGI, the socket for FastCGI, and a gz stream for compression IFF
* in body parts).
* If sz is zero or buf is NULL, this is a no-op.
* Return zero on failure (system error writing to output) or non-zero
* on success.
*/
static enum kcgi_err
kdata_flush(struct kdata *p, const char *buf, size_t sz)
{
if (sz == 0 || buf == NULL)
return KCGI_OK;
/*
* FIXME: make this work properly on all systems.
* This is known (?) to break on FreeBSD: we may need to break
* the uncompressed buffer into chunks that will not cause
* EAGAIN to be raised.
*/
if (p->gz != NULL && p->state != KSTATE_HEAD) {
if (gzwrite(p->gz, buf, sz) == 0) {
kutil_warnx(NULL, NULL, "gzwrite");
return KCGI_SYSTEM;
}
return KCGI_OK;
}
return (p->fcgi == -1) ?
fullwritenoerr(STDOUT_FILENO, buf, sz) :
fcgi_write(6, p, buf, sz);
}
/*
* Drain the output buffer with kdata_flush().
* Returns KCGI_OK, KCGI_SYSTEM, or KCGI_HUP.
* XXX: if errors occur, outbufpos is zeroed as if we wrote the data.
*/
static enum kcgi_err
kdata_drain(struct kdata *p)
{
enum kcgi_err er;
er = kdata_flush(p, p->outbuf, p->outbufpos);
p->outbufpos = 0;
return er;
}
/*
* In this function, we handle arbitrary writes of data to the output.
* In the event of CGI, this will be to stdout; in the event of FastCGI,
* this is to the wire.
* We want to handle buffering of output, so we maintain an output
* buffer that we fill and drain as needed.
* This will end up calling kdata_flush(), directly or indirectly.
* This will also handle debugging.
* Returns KCGI_ENOMEM if any allocation failures occur during the
* sequence, KCGI_SYSTEM if any errors occur writing to the output
* channel, KCGI_HUP on channel hangup, or KCGI_OK on success.
*/
static enum kcgi_err
kdata_write(struct kdata *p, const char *buf, size_t sz)
{
size_t i, max;
int newln;
enum kcgi_err er = KCGI_OK;
assert(p != NULL);
if (sz == 0 || buf == NULL)
return er;
/*
* If we're debugging writes, first copy as many bytes as
* possible into the output line buffer, stopping when it's full
* or when we have a newline. Keep flushing in these cases til
* we have nothing left to add. We'll flush any stray bytes
* when we get back here or close the connection.
*/
if (sz && (p->debugging & KREQ_DEBUG_WRITE))
for (i = 0, max = sizeof(p->linebuf); i < sz; ) {
newln = 0;
while (i < sz && p->linebufpos < max) {
p->linebuf[p->linebufpos] = buf[i++];
p->bytes++;
if (p->linebuf[p->linebufpos] == '\n') {
newln = 1;
break;
}
p->linebufpos++;
}
if (newln) {
kutil_info(NULL, NULL, "%lu-tx: %.*s",
(unsigned long)getpid(),
(int)p->linebufpos, p->linebuf);
p->linebufpos = 0;
} else if (p->linebufpos == max) {
kutil_info(NULL, NULL, "%lu-tx: %.*s...",
(unsigned long)getpid(),
(int)p->linebufpos, p->linebuf);
p->linebufpos = 0;
}
}
/*
* Short-circuit: if we have no output buffer, flush directly to
* the wire.
*/
if (p->outbufsz == 0)
return kdata_flush(p, buf, sz);
/*
* If we want to accept new data and it exceeds the buffer size,
* push out the entire existing buffer to start.
* Then re-check if we exceed our buffer size.
* If we do, then instead of filling into the temporary buffer
* and looping until the new buffer is exhausted, just push the
* whole thing out.
* If we don't, then copy it into the buffer.
*/
if (p->outbufpos + sz > p->outbufsz) {
if ((er = kdata_drain(p)) != KCGI_OK)
return er;
if (sz > p->outbufsz)
return kdata_flush(p, buf, sz);
}
assert(p->outbufpos + sz <= p->outbufsz);
assert(p->outbuf != NULL);
memcpy(p->outbuf + p->outbufpos, buf, sz);
p->outbufpos += sz;
return er;
}
enum kcgi_err
khttp_write(struct kreq *req, const char *buf, size_t sz)
{
assert(req->kdata != NULL);
if (req->kdata->state != KSTATE_BODY)
return KCGI_FORM;
assert(!req->kdata->disabled);
/* This protects against buf == NULL or sz == 0. */
return kdata_write(req->kdata, buf, sz);
}
enum kcgi_err
khttp_printf(struct kreq *req, const char *fmt, ...)
{
char *buf;
int len;
va_list ap;
enum kcgi_err er;
if (fmt == NULL)
return KCGI_OK;
va_start(ap, fmt);
len = kxvasprintf(&buf, fmt, ap);
va_end(ap);
if (len == -1)
return KCGI_ENOMEM;
er = khttp_write(req, buf, (size_t)len);
free(buf);
return er;
}
enum kcgi_err
khttp_puts(struct kreq *req, const char *cp)
{
if (cp == NULL)
return KCGI_OK;
return khttp_write(req, cp, strlen(cp));
}
enum kcgi_err
khttp_putc(struct kreq *req, int c)
{
unsigned char cc = c;
return khttp_write(req, (char *)&cc, 1);
}
enum kcgi_err
khttp_head(struct kreq *req, const char *key, const char *fmt, ...)
{
va_list ap;
char *buf;
size_t ksz;
int len;
enum kcgi_err er;
assert(req->kdata != NULL);
assert(req->kdata->state == KSTATE_HEAD);
va_start(ap, fmt);
len = kxvasprintf(&buf, fmt, ap);
va_end(ap);
if (len == -1)
return KCGI_ENOMEM;
ksz = strlen(key);
if ((er = kdata_write(req->kdata, key, ksz)) != KCGI_OK)
goto out;
if ((er = kdata_write(req->kdata, ": ", 2)) != KCGI_OK)
goto out;
if ((er = kdata_write(req->kdata, buf, len)) != KCGI_OK)
goto out;
if ((er = kdata_write(req->kdata, "\r\n", 2)) != KCGI_OK)
goto out;
out:
free(buf);
return er;
}
/*
* Allocate our output data.
* We accept the file descriptor for the FastCGI stream, if there's any.
*/
struct kdata *
kdata_alloc(int control, int fcgi, uint16_t requestId,
unsigned int debugging, const struct kopts *opts)
{
struct kdata *p;
if ((p = kxcalloc(1, sizeof(struct kdata))) == NULL)
return NULL;
p->debugging = debugging;
p->fcgi = fcgi;
p->control = control;
p->requestId = requestId;
if (opts->sndbufsz > 0) {
p->outbufsz = opts->sndbufsz;
if ((p->outbuf = kxmalloc(p->outbufsz)) == NULL) {
free(p);
return NULL;
}
}
return p;
}
/*
* Two ways of doing this: with or without "flush".
* If we're flushing, then we drain our output buffers to the output.
* Either way, we then release all of our internal memory.
*/
void
kdata_free(struct kdata *p, int flush)
{
char buf[8];
uint32_t appStatus;
if (p == NULL)
return;
/* Debugging messages. */
if (flush && (p->debugging & KREQ_DEBUG_WRITE)) {
if (p->linebufpos)
kutil_info(NULL, NULL, "%lu-tx: %.*s",
(unsigned long)getpid(),
(int)p->linebufpos, p->linebuf);
p->linebufpos = 0;
kutil_info(NULL, NULL, "%lu-tx: %" PRIu64 " B",
(unsigned long)getpid(), p->bytes);
}
/* Remaining buffered data. */
if (flush)
kdata_drain(p);
free(p->outbuf);
/*
* If we're not FastCGI and we're not going to flush, then close
* the file descriptors outright: we don't want gzclose()
* flushing anything to the wire.
*/
if (!flush && p->fcgi == -1) {
close(STDOUT_FILENO);
close(STDIN_FILENO);
}
if (p->gz != NULL)
gzclose(p->gz);
if (p->fcgi == -1) {
free(p);
return;
}
/*
* If flushing, end the stream.
* Note that we've already flushed our last FastCGI record to
* the stream, but the standard implies that we need a blank
* record to really shut this thing down.
*/
if (flush) {
fcgi_write(6, p, "", 0);
appStatus = htonl(EXIT_SUCCESS);
memset(buf, 0, 8);
memcpy(buf, &appStatus, sizeof(uint32_t));
/* End of request. */
fcgi_write(3, p, buf, 8);
/* Close out. */
close(p->fcgi);
fullwrite(p->control, &p->requestId, sizeof(uint16_t));
} else
close(p->fcgi);
free(p);
}
/*
* Try to enable compression on the output stream itself.
* This function is only available with zlib.
* We disallow compression on FastCGI streams because I don't yet have
* the structure in place to copy the compressed data into a buffer then
* write that out.
* Set "ret" to zero if compression is not enabled, non-zero if enabled.
* Returns zero if allocation errors occured (via gzdopen(3)), non-zero
* otherwise.
* On failure, "ret" is always zero.
*/
static int
kdata_compress(struct kdata *p, int *ret)
{
*ret = 0;
assert(p->state == KSTATE_HEAD);
if (p->fcgi != -1)
return 1;
assert(p->gz == NULL);
if ((p->gz = gzdopen(STDOUT_FILENO, "w")) == NULL) {
kutil_warn(NULL, NULL, "gzdopen");
return 0;
}
*ret = 1;
return 1;
}
/*
* Begin the body sequence by draining the headers to the wire and
* marking that the body has begun.
* Returns KCGI_OK on success, KCGI_ENOMEM on memory exhaustion, and
* KCGI_SYSTEM on wire-writing failure.
*/
static enum kcgi_err
kdata_body(struct kdata *p)
{
enum kcgi_err er;
assert(p->state == KSTATE_HEAD);
if ((er = kdata_write(p, "\r\n", 2)) != KCGI_OK)
return er;
/*
* XXX: we always drain our buffer after the headers have been
* written.
* This incurs more chat on the wire, but also ensures that our
* response gets to the server as quickly as possible.
* Should an option be added to disable this?
*/
if ((er = kdata_drain(p)) != KCGI_OK)
return er;
p->state = KSTATE_BODY;
return KCGI_OK;
}
enum kcgi_err
khttp_body(struct kreq *req)
{
int hasreq = 0;
enum kcgi_err er;
const char *cp;
/*
* First determine if the request wants HTTP compression.
* Use RFC 2616 14.3 as a guide for checking this: if we have
* the "gzip" accept encoding and a non-zero quality, then use
* compression.
*/
if (req->reqmap[KREQU_ACCEPT_ENCODING] != NULL) {
cp = req->reqmap[KREQU_ACCEPT_ENCODING]->val;
if ((cp = strstr(cp, "gzip")) != NULL) {
hasreq = 1;
cp += 4;
if (strncmp(cp, ";q=0", 4) == 0)
hasreq = '.' == cp[4];
}
}
/*
* Note: the underlying writing functions will not do any
* compression even if we have compression enabled when in
* header mode, so the order of these operations (enable
* compression then write headers) is ok.
*/
if (hasreq) {
/*
* We could just ignore this error, which means gzdopen
* failed, and just continue with hasreq=0.
* However, if gzdopen fails (memory allocation), it
* probably means other things are going to fail, so we
* might as well just die now.
*/
if (!kdata_compress(req->kdata, &hasreq))
return KCGI_ENOMEM;
if (hasreq) {
er = khttp_head(req,
kresps[KRESP_CONTENT_ENCODING], "gzip");
if (er != KCGI_OK)
return er;
}
}
return kdata_body(req->kdata);
}
enum kcgi_err
khttp_body_compress(struct kreq *req, int comp)
{
int didcomp;
/*
* First, if we didn't request compression, go directly into the
* body of the document.
*/
if (!comp)
return kdata_body(req->kdata);
/*
* If we do have compression requested, try enabling it on the
* output stream.
*/
if (!kdata_compress(req->kdata, &didcomp))
return KCGI_ENOMEM;
else if (!didcomp)
return KCGI_FORM;
return kdata_body(req->kdata);
}
/*
* Allocate a writer.
* This only works if we haven't disabled allocation of writers yet via
* kcgi_writer_disable(), otherwise we abort().
* Returns the writer or NULL on allocation (memory) failure.
*/
struct kcgi_writer *
kcgi_writer_get(struct kreq *r, int type)
{
struct kcgi_writer *p;
if (r->kdata->disabled) {
kutil_warnx(NULL, NULL,
"kcgi_writer_get after kcgi_writer_disable");
abort();
}
if ((p = kxmalloc(sizeof(struct kcgi_writer))) != NULL)
p->kdata = r->kdata;
return p;
}
/*
* Disable further allocation of writers with kcgi_writer_get().
* Following this, kcgi_writer_get() will abort.
* This may be called as many times as desired: only the first time
* makes a difference.
*/
void
kcgi_writer_disable(struct kreq *r)
{
r->kdata->disabled = 1;
}
/*
* Release an allocation by kcgi_writer_get().
* May be called with a NULL-valued "p".
*/
void
kcgi_writer_free(struct kcgi_writer *p)
{
free(p);
}
/*
* Write "sz" bytes of "buf" into the output.
* This doesn't necessarily mean that the output has been written: it
* may be further buffered.
* Returns KCGI_FORM and those from kdata_write().
*/
enum kcgi_err
kcgi_writer_write(struct kcgi_writer *p, const void *buf, size_t sz)
{
if (p->kdata->state != KSTATE_BODY)
return KCGI_FORM;
return kdata_write(p->kdata, buf, sz);
}
/*
* Like kcgi_writer_write but for the NUL-terminated string.
*/
enum kcgi_err
kcgi_writer_puts(struct kcgi_writer *p, const char *cp)
{
return kcgi_writer_write(p, cp, strlen(cp));
}
/*
* Like kcgi_writer_write but for a single character.
*/
enum kcgi_err
kcgi_writer_putc(struct kcgi_writer *p, char c)
{
return kcgi_writer_write(p, &c, 1);
}