Newer
Older
* that is already at least the start of another header set, simply
* reset the existing header table and keep it.
Andy Green
committed
if (wsi->http.ah) {
// lws_buflist_describe(&wsi->buflist, wsi);
if (!lws_buflist_next_segment_len(&wsi->buflist, NULL)) {
/*
* additionally... if we are hogging an SSL instance
* with no pending pipelined headers (or ah now), and
* SSL is scarce, drop this connection without waiting
*/
if (wsi->vhost->tls.use_ssl &&
wsi->context->simultaneous_ssl_restriction &&
wsi->context->simultaneous_ssl ==
wsi->context->simultaneous_ssl_restriction) {
lwsl_info("%s: simultaneous_ssl_restriction\n",
__func__);
lwsl_info("%s: %p: resetting and keeping ah as pipeline\n",
__func__, wsi);
/*
* If we kept the ah, we should restrict the amount
* of time we are willing to keep it. Otherwise it
* will be bound the whole time the connection remains
* open.
*/
lws_set_timeout(wsi, PENDING_TIMEOUT_HOLDING_AH,
wsi->vhost->keepalive_timeout);
}
/* If we're (re)starting on headers, need other implied init */
Andy Green
committed
if (wsi->http.ah)
wsi->http.ah->ues = URIES_IDLE;
if (lws_buflist_next_segment_len(&wsi->buflist, NULL))
if (lws_header_table_attach(wsi, 0))
lwsl_debug("acquired ah\n");
lwsl_debug("%s: %p: keep-alive await new transaction (state 0x%x)\n",
__func__, wsi, wsi->wsistate);
lws_callback_on_writable(wsi);
LWS_VISIBLE int
lws_serve_http_file(struct lws *wsi, const char *file, const char *content_type,
const char *other_headers, int other_headers_len)
struct lws_context *context = lws_get_context(wsi);
struct lws_context_per_thread *pt = &context->pt[(int)wsi->tsi];
struct lws_range_parsing *rp = &wsi->http.range;
unsigned char *end = p + context->pt_serv_buf_size - LWS_PRE;
int ret = 0, cclen = 8, n = HTTP_STATUS_OK;
#if defined(LWS_WITH_RANGES)
int ranges;
#endif
if (wsi->handling_404)
n = HTTP_STATUS_NOT_FOUND;
/*
* We either call the platform fops .open with first arg platform fops,
* or we call fops_zip .open with first arg platform fops, and fops_zip
* open will decide whether to switch to fops_zip or stay with fops_def.
*
* If wsi->http.fop_fd is already set, the caller already opened it
fops = lws_vfs_select_fops(wsi->context->fops, file, &vpath);
fflags |= lws_vfs_prepare_flags(wsi);
wsi->http.fop_fd = fops->LWS_FOP_OPEN(wsi->context->fops,
lwsl_info("%s: Unable to open: '%s': errno %d\n",
__func__, file, errno);
if (lws_return_http_status(wsi, HTTP_STATUS_NOT_FOUND, NULL))
return -1;
return !wsi->http2_substream;
wsi->http.filelen = lws_vfs_get_length(wsi->http.fop_fd);
total_content_length = wsi->http.filelen;
ranges = lws_ranges_init(wsi, rp, wsi->http.filelen);
lwsl_debug("Range count %d\n", ranges);
/*
* no ranges -> 200;
* 1 range -> 206 + Content-Type: normal; Content-Range;
* more -> 206 + Content-Type: multipart/byteranges
* Repeat the true Content-Type in each multipart header
* along with Content-Range
*/
if (ranges < 0) {
/* it means he expressed a range in Range:, but it was illegal */
lws_return_http_status(wsi, HTTP_STATUS_REQ_RANGE_NOT_SATISFIABLE,
NULL);
if (lws_http_transaction_completed(wsi))
return -1; /* <0 means just hang up */
return 0; /* == 0 means we dealt with the transaction complete */
}
if (ranges)
n = HTTP_STATUS_PARTIAL_CONTENT;
#endif
if (lws_add_http_header_status(wsi, n, &p, end))
if ((wsi->http.fop_fd->flags & (LWS_FOP_FLAG_COMPR_ACCEPTABLE_GZIP |
LWS_FOP_FLAG_COMPR_IS_GZIP)) ==
(LWS_FOP_FLAG_COMPR_ACCEPTABLE_GZIP | LWS_FOP_FLAG_COMPR_IS_GZIP)) {
if (lws_add_http_header_by_token(wsi,
WSI_TOKEN_HTTP_CONTENT_ENCODING,
(unsigned char *)"gzip", 4, &p, end))
return -1;
lwsl_info("file is being provided in gzip\n");
}
#if defined(LWS_WITH_HTTP_STREAM_COMPRESSION)
else {
/*
* if we know its very compressible, and we can use
* compression, then use the most preferred compression
* method that the client said he will accept
*/
if (!strncmp(content_type, "text/", 5) ||
!strcmp(content_type, "application/javascript") ||
!strcmp(content_type, "image/svg+xml"))
lws_http_compression_apply(wsi, NULL, &p, end, 0);
}
#endif
ranges < 2 &&
#endif
content_type && content_type[0])
if (lws_add_http_header_by_token(wsi,
WSI_TOKEN_HTTP_CONTENT_TYPE,
(unsigned char *)content_type,
if (ranges >= 2) { /* multipart byteranges */
lws_strncpy(wsi->http.multipart_content_type, content_type,
sizeof(wsi->http.multipart_content_type));
if (lws_add_http_header_by_token(wsi,
WSI_TOKEN_HTTP_CONTENT_TYPE,
(unsigned char *)
"multipart/byteranges; "
"boundary=_lws",
20, &p, end))
return -1;
/*
* our overall content length has to include
*
* - (n + 1) x "_lws\r\n"
* - n x Content-Type: xxx/xxx\r\n
* - n x Content-Range: bytes xxx-yyy/zzz\r\n
* - n x /r/n
* - the actual payloads (aggregated in rp->agg)
*
* Precompute it for the main response header
*/
total_content_length = (lws_filepos_t)rp->agg +
6 /* final _lws\r\n */;
lws_ranges_reset(rp);
while (lws_ranges_next(rp)) {
n = lws_snprintf(cache_control, sizeof(cache_control),
"bytes %llu-%llu/%llu",
rp->start, rp->end, rp->extent);
6 /* header _lws\r\n */ +
/* Content-Type: xxx/xxx\r\n */
14 + strlen(content_type) + 2 +
/* Content-Range: xxxx\r\n */
15 + n + 2 +
2; /* /r/n */
}
lws_ranges_reset(rp);
lws_ranges_next(rp);
n = lws_snprintf(cache_control, sizeof(cache_control),
"bytes %llu-%llu/%llu",
rp->start, rp->end, rp->extent);
if (lws_add_http_header_by_token(wsi,
WSI_TOKEN_HTTP_CONTENT_RANGE,
(unsigned char *)cache_control,
n, &p, end))
return -1;
}
if (lws_add_http_header_by_token(wsi, WSI_TOKEN_HTTP_ACCEPT_RANGES,
(unsigned char *)"bytes", 5, &p, end))
return -1;
#endif
if (!wsi->http2_substream) {
/* for http/1.1 ... */
if (!wsi->sending_chunked
#if defined(LWS_WITH_HTTP_STREAM_COMPRESSION)
&& !wsi->http.lcs
#endif
) {
/* ... if not already using chunked and not using an
* http compression translation, then send the naive
* content length
*/
if (lws_add_http_header_content_length(wsi,
return -1;
} else {
#if defined(LWS_WITH_HTTP_STREAM_COMPRESSION)
if (wsi->http.lcs) {
/* ...otherwise, for http 1 it must go chunked.
* For the compression case, the reason is we
* compress on the fly and do not know the
* compressed content-length until it has all
* been sent. Http/1.1 pipelining must be able
* to know where the transaction boundaries are
* ... so chunking...
*/
if (lws_add_http_header_by_token(wsi,
WSI_TOKEN_HTTP_TRANSFER_ENCODING,
(unsigned char *)"chunked", 7,
&p, end))
return -1;
/*
* ...this is fun, isn't it :-) For h1 that is
* using an http compression translation, the
* compressor must chunk its output privately.
*
* h2 doesn't need (or support) any of this
* crap.
*/
lwsl_debug("setting chunking\n");
wsi->http.comp_ctx.chunking = 1;
}
#endif
cclen = sprintf(cache_control, "%s, max-age=%u",
intermediates[wsi->cache_intermediaries],
wsi->cache_secs);
} else {
cclen = sprintf(cache_control,
"must-revalidate, %s, max-age=%u",
intermediates[wsi->cache_intermediaries],
wsi->cache_secs);
Alexander Bruines
committed
/* Only add cache control if its not specified by any other_headers. */
if (!other_headers ||
(!strstr(other_headers, "cache-control") &&
!strstr(other_headers, "Cache-Control"))) {
Alexander Bruines
committed
if (lws_add_http_header_by_token(wsi, WSI_TOKEN_HTTP_CACHE_CONTROL,
(unsigned char *)cc, cclen, &p, end))
return -1;
}
if ((end - p) < other_headers_len)
return -1;
memcpy(p, other_headers, other_headers_len);
p += other_headers_len;
if (lws_finalize_http_header(wsi, &p, end))
ret = lws_write(wsi, response, p - response, LWS_WRITE_HTTP_HEADERS);
lwsl_err("_write returned %d from %ld\n", ret,
(long)(p - response));
lwsi_set_state(wsi, LRS_ISSUING_FILE);
LWS_VISIBLE int lws_serve_http_file_fragment(struct lws *wsi)
{
struct lws_context *context = wsi->context;
struct lws_context_per_thread *pt = &context->pt[(int)wsi->tsi];
struct lws_process_html_args args;
lws_filepos_t amount, poss;
unsigned char *p, *pstart;
#if defined(LWS_WITH_RANGES)
unsigned char finished = 0;
#endif
int n, m;
lwsl_debug("wsi->http2_substream %d\n", wsi->http2_substream);
do {
if (lws_has_buffered_out(wsi)) {
if (lws_issue_raw(wsi, NULL, 0) < 0) {
lwsl_info("%s: closing\n", __func__);
goto file_had_it;
}
break;
}
/* priority 2: buffered pre-compression-transform */
#if defined(LWS_WITH_HTTP_STREAM_COMPRESSION)
if (wsi->http.comp_ctx.buflist_comp ||
wsi->http.comp_ctx.may_have_more) {
enum lws_write_protocol wp = LWS_WRITE_HTTP;
lwsl_info("%s: completing comp partial (buflist_comp %p, may %d)\n",
__func__, wsi->http.comp_ctx.buflist_comp,
wsi->http.comp_ctx.may_have_more);
if (wsi->role_ops->write_role_protocol(wsi, NULL, 0, &wp) < 0) {
lwsl_info("%s signalling to close\n", __func__);
goto file_had_it;
}
lws_callback_on_writable(wsi);
break;
}
#endif
2382
2383
2384
2385
2386
2387
2388
2389
2390
2391
2392
2393
2394
2395
2396
2397
2398
2399
2400
2401
2402
2403
2404
2405
2406
2407
2408
2409
2410
2411
2412
2413
2414
2415
2416
2417
2418
2419
2420
2421
2422
2423
2424
2425
2426
2427
2428
2429
2430
2431
2432
2433
2434
2435
2436
2437
2438
2439
2440
2441
2442
2443
2444
2445
2446
2447
2448
2449
2450
2451
2452
2453
2454
2455
2456
2457
2458
2459
2460
2461
2462
2463
2464
2465
2466
2467
2468
2469
2470
2471
2472
2473
2474
2475
2476
2477
2478
2479
2480
2481
2482
2483
2484
2485
2486
2487
2488
2489
2490
2491
2492
2493
2494
2495
2496
2497
2498
2499
2500
2501
2502
2503
2504
2505
2506
2507
2508
2509
2510
2511
2512
2513
2514
2515
2516
2517
2518
2519
2520
2521
2522
2523
2524
2525
2526
2527
2528
2529
2530
2531
2532
2533
2534
2535
2536
2537
2538
2539
2540
2541
2542
2543
2544
2545
2546
2547
2548
if (wsi->http.filepos == wsi->http.filelen)
goto all_sent;
n = 0;
pstart = pt->serv_buf + LWS_H2_FRAME_HEADER_LENGTH;
p = pstart;
#if defined(LWS_WITH_RANGES)
if (wsi->http.range.count_ranges && !wsi->http.range.inside) {
lwsl_notice("%s: doing range start %llu\n", __func__,
wsi->http.range.start);
if ((long long)lws_vfs_file_seek_cur(wsi->http.fop_fd,
wsi->http.range.start -
wsi->http.filepos) < 0)
goto file_had_it;
wsi->http.filepos = wsi->http.range.start;
if (wsi->http.range.count_ranges > 1) {
n = lws_snprintf((char *)p,
context->pt_serv_buf_size -
LWS_H2_FRAME_HEADER_LENGTH,
"_lws\x0d\x0a"
"Content-Type: %s\x0d\x0a"
"Content-Range: bytes %llu-%llu/%llu\x0d\x0a"
"\x0d\x0a",
wsi->http.multipart_content_type,
wsi->http.range.start,
wsi->http.range.end,
wsi->http.range.extent);
p += n;
}
wsi->http.range.budget = wsi->http.range.end -
wsi->http.range.start + 1;
wsi->http.range.inside = 1;
}
#endif
poss = context->pt_serv_buf_size - n - LWS_H2_FRAME_HEADER_LENGTH;
if (wsi->http.tx_content_length)
if (poss > wsi->http.tx_content_remain)
poss = wsi->http.tx_content_remain;
/*
* if there is a hint about how much we will do well to send at
* one time, restrict ourselves to only trying to send that.
*/
if (wsi->protocol->tx_packet_size &&
poss > wsi->protocol->tx_packet_size)
poss = wsi->protocol->tx_packet_size;
if (wsi->role_ops->tx_credit) {
lws_filepos_t txc = wsi->role_ops->tx_credit(wsi);
if (!txc) {
lwsl_info("%s: came here with no tx credit\n",
__func__);
return 0;
}
if (txc < poss)
poss = txc;
/*
* consumption of the actual payload amount sent will be
* handled when the role data frame is sent
*/
}
#if defined(LWS_WITH_RANGES)
if (wsi->http.range.count_ranges) {
if (wsi->http.range.count_ranges > 1)
poss -= 7; /* allow for final boundary */
if (poss > wsi->http.range.budget)
poss = wsi->http.range.budget;
}
#endif
if (wsi->sending_chunked) {
/* we need to drop the chunk size in here */
p += 10;
/* allow for the chunk to grow by 128 in translation */
poss -= 10 + 128;
}
if (lws_vfs_file_read(wsi->http.fop_fd, &amount, p, poss) < 0)
goto file_had_it; /* caller will close */
if (wsi->sending_chunked)
n = (int)amount;
else
n = lws_ptr_diff(p, pstart) + (int)amount;
lwsl_debug("%s: sending %d\n", __func__, n);
if (n) {
lws_set_timeout(wsi, PENDING_TIMEOUT_HTTP_CONTENT,
context->timeout_secs);
if (wsi->interpreting) {
args.p = (char *)p;
args.len = n;
args.max_len = (unsigned int)poss + 128;
args.final = wsi->http.filepos + n ==
wsi->http.filelen;
args.chunked = wsi->sending_chunked;
if (user_callback_handle_rxflow(
wsi->vhost->protocols[
(int)wsi->protocol_interpret_idx].callback,
wsi, LWS_CALLBACK_PROCESS_HTML,
wsi->user_space, &args, 0) < 0)
goto file_had_it;
n = args.len;
p = (unsigned char *)args.p;
} else
p = pstart;
#if defined(LWS_WITH_RANGES)
if (wsi->http.range.send_ctr + 1 ==
wsi->http.range.count_ranges && // last range
wsi->http.range.count_ranges > 1 && // was 2+ ranges (ie, multipart)
wsi->http.range.budget - amount == 0) {// final part
n += lws_snprintf((char *)pstart + n, 6,
"_lws\x0d\x0a"); // append trailing boundary
lwsl_debug("added trailing boundary\n");
}
#endif
m = lws_write(wsi, p, n,
wsi->http.filepos + amount == wsi->http.filelen ?
LWS_WRITE_HTTP_FINAL :
LWS_WRITE_HTTP
);
if (m < 0)
goto file_had_it;
wsi->http.filepos += amount;
#if defined(LWS_WITH_RANGES)
if (wsi->http.range.count_ranges >= 1) {
wsi->http.range.budget -= amount;
if (wsi->http.range.budget == 0) {
lwsl_notice("range budget exhausted\n");
wsi->http.range.inside = 0;
wsi->http.range.send_ctr++;
if (lws_ranges_next(&wsi->http.range) < 1) {
finished = 1;
goto all_sent;
}
}
}
#endif
if (m != n) {
/* adjust for what was not sent */
if (lws_vfs_file_seek_cur(wsi->http.fop_fd,
m - n) ==
(lws_fileofs_t)-1)
goto file_had_it;
}
}
all_sent:
if ((!lws_has_buffered_out(wsi)
#if defined(LWS_WITH_HTTP_STREAM_COMPRESSION)
&& !wsi->http.comp_ctx.buflist_comp &&
!wsi->http.comp_ctx.may_have_more
#endif
) && (wsi->http.filepos >= wsi->http.filelen
#if defined(LWS_WITH_RANGES)
|| finished)
#else
)
#endif
2561
2562
2563
2564
2565
2566
2567
2568
2569
2570
2571
2572
2573
2574
2575
2576
2577
2578
2579
2580
2581
2582
2583
2584
2585
2586
2587
2588
2589
2590
2591
2592
2593
2594
{
lwsi_set_state(wsi, LRS_ESTABLISHED);
/* we might be in keepalive, so close it off here */
lws_vfs_file_close(&wsi->http.fop_fd);
lwsl_debug("file completed\n");
if (wsi->protocol->callback &&
user_callback_handle_rxflow(wsi->protocol->callback,
wsi, LWS_CALLBACK_HTTP_FILE_COMPLETION,
wsi->user_space, NULL,
0) < 0) {
/*
* For http/1.x, the choices from
* transaction_completed are either
* 0 to use the connection for pipelined
* or nonzero to hang it up.
*
* However for http/2. while we are
* still interested in hanging up the
* nwsi if there was a network-level
* fatal error, simply completing the
* transaction is a matter of the stream
* state, not the root connection at the
* network level
*/
if (wsi->http2_substream)
return 1;
else
return -1;
}
return 1; /* >0 indicates completed */
}
lws_callback_on_writable(wsi);
return 0; /* indicates further processing must be done */
file_had_it:
lws_vfs_file_close(&wsi->http.fop_fd);
return -1;
}
lws_server_get_canonical_hostname(struct lws_context *context,
const struct lws_context_creation_info *info)
if (lws_check_opt(info->options,
LWS_SERVER_OPTION_SKIP_SERVER_CANONICAL_NAME))
/* find canonical hostname */
gethostname((char *)context->canonical_hostname,
lwsl_info(" canonical_hostname = %s\n", context->canonical_hostname);
2627
2628
2629
2630
2631
2632
2633
2634
2635
2636
2637
2638
2639
2640
2641
2642
2643
2644
2645
2646
2647
LWS_VISIBLE LWS_EXTERN int
lws_chunked_html_process(struct lws_process_html_args *args,
struct lws_process_html_state *s)
{
char *sp, buffer[32];
const char *pc;
int old_len, n;
/* do replacements */
sp = args->p;
old_len = args->len;
args->len = 0;
s->start = sp;
while (sp < args->p + old_len) {
if (args->len + 7 >= args->max_len) {
lwsl_err("Used up interpret padding\n");
return -1;
}
if ((!s->pos && *sp == '$') || s->pos) {
if (!s->pos)
s->start = sp;
s->swallow[s->pos++] = *sp;
if (s->pos == sizeof(s->swallow) - 1)
goto skip;
for (n = 0; n < s->count_vars; n++)
if (!strncmp(s->swallow, s->vars[n], s->pos)) {
hits++;
hit = n;
}
if (!hits) {
skip:
s->swallow[s->pos] = '\0';
memcpy(s->start, s->swallow, s->pos);
args->len++;
s->pos = 0;
sp = s->start + 1;
continue;
}
if (hits == 1 && s->pos == (int)strlen(s->vars[hit])) {
pc = s->replace(s->data, hit);
if (!pc)
pc = "NULL";
2674
2675
2676
2677
2678
2679
2680
2681
2682
2683
2684
2685
2686
2687
2688
2689
2690
2691
2692
2693
2694
s->swallow[s->pos] = '\0';
if (n != s->pos) {
memmove(s->start + n,
s->start + s->pos,
old_len - (sp - args->p));
old_len += (n - s->pos) + 1;
}
memcpy(s->start, pc, n);
args->len++;
sp = s->start + 1;
s->pos = 0;
}
sp++;
continue;
}
args->len++;
sp++;
}
if (args->chunked) {
/* no space left for final chunk trailer */
if (args->final && args->len + 7 >= args->max_len)
return -1;
2700
2701
2702
2703
2704
2705
2706
2707
2708
2709
2710
2711
2712
2713
2714
2715
2716
2717
2718
2719
2720
2721
n = sprintf(buffer, "%X\x0d\x0a", args->len);
args->p -= n;
memcpy(args->p, buffer, n);
args->len += n;
if (args->final) {
sp = args->p + args->len;
*sp++ = '\x0d';
*sp++ = '\x0a';
*sp++ = '0';
*sp++ = '\x0d';
*sp++ = '\x0a';
*sp++ = '\x0d';
*sp++ = '\x0a';
args->len += 7;
} else {
sp = args->p + args->len;
*sp++ = '\x0d';
*sp++ = '\x0a';
args->len += 2;
}