allow other headers in http send file
[platform/upstream/libwebsockets.git] / lib / output.c
1 /*
2  * libwebsockets - small server side websockets and web server implementation
3  *
4  * Copyright (C) 2010-2013 Andy Green <andy@warmcat.com>
5  *
6  *  This library is free software; you can redistribute it and/or
7  *  modify it under the terms of the GNU Lesser General Public
8  *  License as published by the Free Software Foundation:
9  *  version 2.1 of the License.
10  *
11  *  This library is distributed in the hope that it will be useful,
12  *  but WITHOUT ANY WARRANTY; without even the implied warranty of
13  *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
14  *  Lesser General Public License for more details.
15  *
16  *  You should have received a copy of the GNU Lesser General Public
17  *  License along with this library; if not, write to the Free Software
18  *  Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston,
19  *  MA  02110-1301  USA
20  */
21
22 #include "private-libwebsockets.h"
23
24 #ifdef WIN32
25 #include <io.h>
26 #endif
27
28 static int
29 libwebsocket_0405_frame_mask_generate(struct libwebsocket *wsi)
30 {
31         int n;
32
33         /* fetch the per-frame nonce */
34
35         n = libwebsockets_get_random(wsi->protocol->owning_server,
36                                            wsi->u.ws.frame_masking_nonce_04, 4);
37         if (n != 4) {
38                 lwsl_parser("Unable to read from random device %s %d\n",
39                                                      SYSTEM_RANDOM_FILEPATH, n);
40                 return 1;
41         }
42
43         /* start masking from first byte of masking key buffer */
44         wsi->u.ws.frame_mask_index = 0;
45
46         return 0;
47 }
48
49 #ifdef _DEBUG
50
51 LWS_VISIBLE void lwsl_hexdump(void *vbuf, size_t len)
52 {
53         int n;
54         int m;
55         int start;
56         unsigned char *buf = (unsigned char *)vbuf;
57         char line[80];
58         char *p;
59
60         lwsl_parser("\n");
61
62         for (n = 0; n < len;) {
63                 start = n;
64                 p = line;
65
66                 p += sprintf(p, "%04X: ", start);
67
68                 for (m = 0; m < 16 && n < len; m++)
69                         p += sprintf(p, "%02X ", buf[n++]);
70                 while (m++ < 16)
71                         p += sprintf(p, "   ");
72
73                 p += sprintf(p, "   ");
74
75                 for (m = 0; m < 16 && (start + m) < len; m++) {
76                         if (buf[start + m] >= ' ' && buf[start + m] < 127)
77                                 *p++ = buf[start + m];
78                         else
79                                 *p++ = '.';
80                 }
81                 while (m++ < 16)
82                         *p++ = ' ';
83
84                 *p++ = '\n';
85                 *p = '\0';
86                 lwsl_debug("%s", line);
87         }
88         lwsl_debug("\n");
89 }
90
91 #endif
92
93 /*
94  * notice this returns number of bytes consumed, or -1
95  */
96
97 int lws_issue_raw(struct libwebsocket *wsi, unsigned char *buf, size_t len)
98 {
99         struct libwebsocket_context *context = wsi->protocol->owning_server;
100         int n;
101 #ifndef LWS_NO_EXTENSIONS
102         int m;
103
104         /*
105          * one of the extensions is carrying our data itself?  Like mux?
106          */
107
108         for (n = 0; n < wsi->count_active_extensions; n++) {
109                 /*
110                  * there can only be active extensions after handshake completed
111                  * so we can rely on protocol being set already in here
112                  */
113                 m = wsi->active_extensions[n]->callback(
114                                 wsi->protocol->owning_server,
115                                 wsi->active_extensions[n], wsi,
116                                 LWS_EXT_CALLBACK_PACKET_TX_DO_SEND,
117                                      wsi->active_extensions_user[n], &buf, len);
118                 if (m < 0) {
119                         lwsl_ext("Extension reports fatal error\n");
120                         return -1;
121                 }
122                 if (m) /* handled */ {
123 /*                      lwsl_ext("ext sent it\n"); */
124                         n = m;
125                         goto handle_truncated_send;
126                 }
127         }
128 #endif
129         if (wsi->sock < 0)
130                 lwsl_warn("** error invalid sock but expected to send\n");
131
132         /*
133          * nope, send it on the socket directly
134          */
135
136 #if 0
137         lwsl_debug("  TX: ");
138         lws_hexdump(buf, len);
139 #endif
140
141         lws_latency_pre(context, wsi);
142 #ifdef LWS_OPENSSL_SUPPORT
143         if (wsi->ssl) {
144                 n = SSL_write(wsi->ssl, buf, len);
145                 lws_latency(context, wsi, "SSL_write lws_issue_raw", n, n >= 0);
146                 if (n < 0) {
147                         lwsl_debug("ERROR writing to socket\n");
148                         return -1;
149                 }
150         } else {
151 #endif
152                 n = send(wsi->sock, buf, len, MSG_NOSIGNAL);
153                 lws_latency(context, wsi, "send lws_issue_raw", n, n == len);
154                 if (n < 0) {
155                         lwsl_debug("ERROR writing len %d to skt %d\n", len, n);
156                         return -1;
157                 }
158 #ifdef LWS_OPENSSL_SUPPORT
159         }
160 #endif
161
162 handle_truncated_send:
163
164         /*
165          * already handling a truncated send?
166          */
167         if (wsi->u.ws.truncated_send_malloc) {
168                 lwsl_info("***** partial send moved on by %d (vs %d)\n", n, len);
169                 wsi->u.ws.truncated_send_offset += n;
170                 wsi->u.ws.truncated_send_len -= n;
171
172                 if (!wsi->u.ws.truncated_send_len) {
173                         lwsl_info("***** partial send completed\n");
174                         /* done with it */
175                         free(wsi->u.ws.truncated_send_malloc);
176                         wsi->u.ws.truncated_send_malloc = NULL;
177                 } else
178                         libwebsocket_callback_on_writable(
179                                              wsi->protocol->owning_server, wsi);
180
181                 return n;
182         }
183
184         if (n < len) {
185                 if (wsi->u.ws.clean_buffer)
186                         /*
187                          * This buffer unaffected by extension rewriting.
188                          * It means the user code is expected to deal with
189                          * partial sends.  (lws knows the header was already
190                          * sent, so on next send will just resume sending
191                          * payload)
192                          */
193                          return n;
194
195                 /*
196                  * Newly truncated send.  Buffer the remainder (it will get
197                  * first priority next time the socket is writable)
198                  */
199                 lwsl_info("***** new partial send %d sent %d accepted\n", len, n);
200
201                 wsi->u.ws.truncated_send_malloc = malloc(len - n);
202                 if (!wsi->u.ws.truncated_send_malloc) {
203                         lwsl_err("truncated send: unable to malloc %d\n",
204                                                                        len - n);
205                         return -1;
206                 }
207
208                 wsi->u.ws.truncated_send_offset = 0;
209                 wsi->u.ws.truncated_send_len = len - n;
210                 memcpy(wsi->u.ws.truncated_send_malloc, buf + n, len - n);
211
212                 libwebsocket_callback_on_writable(
213                                              wsi->protocol->owning_server, wsi);
214
215                 return len;
216         }
217
218         return n;
219 }
220
221 #ifdef LWS_NO_EXTENSIONS
222 int
223 lws_issue_raw_ext_access(struct libwebsocket *wsi,
224                                                  unsigned char *buf, size_t len)
225 {
226         return lws_issue_raw(wsi, buf, len);
227 }
228 #else
229 int
230 lws_issue_raw_ext_access(struct libwebsocket *wsi,
231                                                  unsigned char *buf, size_t len)
232 {
233         int ret;
234         struct lws_tokens eff_buf;
235         int m;
236         int n;
237
238         eff_buf.token = (char *)buf;
239         eff_buf.token_len = len;
240
241         /*
242          * while we have original buf to spill ourselves, or extensions report
243          * more in their pipeline
244          */
245
246         ret = 1;
247         while (ret == 1) {
248
249                 /* default to nobody has more to spill */
250
251                 ret = 0;
252
253                 /* show every extension the new incoming data */
254
255                 for (n = 0; n < wsi->count_active_extensions; n++) {
256                         m = wsi->active_extensions[n]->callback(
257                                         wsi->protocol->owning_server,
258                                         wsi->active_extensions[n], wsi,
259                                         LWS_EXT_CALLBACK_PACKET_TX_PRESEND,
260                                    wsi->active_extensions_user[n], &eff_buf, 0);
261                         if (m < 0) {
262                                 lwsl_ext("Extension: fatal error\n");
263                                 return -1;
264                         }
265                         if (m)
266                                 /*
267                                  * at least one extension told us he has more
268                                  * to spill, so we will go around again after
269                                  */
270                                 ret = 1;
271                 }
272
273                 if ((char *)buf != eff_buf.token)
274                         wsi->u.ws.clean_buffer = 0; /* extension recreated it: we need to buffer this if not all sent */
275
276                 /* assuming they left us something to send, send it */
277
278                 if (eff_buf.token_len) {
279                         n = lws_issue_raw(wsi, (unsigned char *)eff_buf.token,
280                                                             eff_buf.token_len);
281                         if (n < 0)
282                                 return -1;
283
284                         /* always either sent it all or privately buffered */
285                 }
286
287                 lwsl_parser("written %d bytes to client\n", eff_buf.token_len);
288
289                 /* no extension has more to spill?  Then we can go */
290
291                 if (!ret)
292                         break;
293
294                 /* we used up what we had */
295
296                 eff_buf.token = NULL;
297                 eff_buf.token_len = 0;
298
299                 /*
300                  * Did that leave the pipe choked?
301                  * Or we had to hold on to some of it?
302                  */
303
304                 if (!lws_send_pipe_choked(wsi) &&
305                                         !wsi->u.ws.truncated_send_malloc)
306                         /* no we could add more, lets's do that */
307                         continue;
308
309                 lwsl_debug("choked\n");
310
311                 /*
312                  * Yes, he's choked.  Don't spill the rest now get a callback
313                  * when he is ready to send and take care of it there
314                  */
315                 libwebsocket_callback_on_writable(
316                                              wsi->protocol->owning_server, wsi);
317                 wsi->extension_data_pending = 1;
318                 ret = 0;
319         }
320
321         return len;
322 }
323 #endif
324
325 /**
326  * libwebsocket_write() - Apply protocol then write data to client
327  * @wsi:        Websocket instance (available from user callback)
328  * @buf:        The data to send.  For data being sent on a websocket
329  *              connection (ie, not default http), this buffer MUST have
330  *              LWS_SEND_BUFFER_PRE_PADDING bytes valid BEFORE the pointer
331  *              and an additional LWS_SEND_BUFFER_POST_PADDING bytes valid
332  *              in the buffer after (buf + len).  This is so the protocol
333  *              header and trailer data can be added in-situ.
334  * @len:        Count of the data bytes in the payload starting from buf
335  * @protocol:   Use LWS_WRITE_HTTP to reply to an http connection, and one
336  *              of LWS_WRITE_BINARY or LWS_WRITE_TEXT to send appropriate
337  *              data on a websockets connection.  Remember to allow the extra
338  *              bytes before and after buf if LWS_WRITE_BINARY or LWS_WRITE_TEXT
339  *              are used.
340  *
341  *      This function provides the way to issue data back to the client
342  *      for both http and websocket protocols.
343  *
344  *      In the case of sending using websocket protocol, be sure to allocate
345  *      valid storage before and after buf as explained above.  This scheme
346  *      allows maximum efficiency of sending data and protocol in a single
347  *      packet while not burdening the user code with any protocol knowledge.
348  *
349  *      Return may be -1 for a fatal error needing connection close, or a
350  *      positive number reflecting the amount of bytes actually sent.  This
351  *      can be less than the requested number of bytes due to OS memory
352  *      pressure at any given time.
353  */
354
355 LWS_VISIBLE int libwebsocket_write(struct libwebsocket *wsi, unsigned char *buf,
356                           size_t len, enum libwebsocket_write_protocol protocol)
357 {
358         int n;
359         int pre = 0;
360         int post = 0;
361         int masked7 = wsi->mode == LWS_CONNMODE_WS_CLIENT;
362         unsigned char *dropmask = NULL;
363         unsigned char is_masked_bit = 0;
364         size_t orig_len = len;
365 #ifndef LWS_NO_EXTENSIONS
366         struct lws_tokens eff_buf;
367         int m;
368 #endif
369
370         if (len == 0 && protocol != LWS_WRITE_CLOSE &&
371                      protocol != LWS_WRITE_PING && protocol != LWS_WRITE_PONG) {
372                 lwsl_warn("zero length libwebsocket_write attempt\n");
373                 return 0;
374         }
375
376         if (protocol == LWS_WRITE_HTTP)
377                 goto send_raw;
378
379         /* websocket protocol, either binary or text */
380
381         if (wsi->state != WSI_STATE_ESTABLISHED)
382                 return -1;
383
384         /* if we are continuing a frame that already had its header done */
385
386         if (wsi->u.ws.inside_frame)
387                 goto do_more_inside_frame;
388
389         /* if he wants all partials buffered, never have a clean_buffer */
390         wsi->u.ws.clean_buffer = !wsi->protocol->no_buffer_all_partial_tx;
391
392 #ifndef LWS_NO_EXTENSIONS
393         /*
394          * give a chance to the extensions to modify payload
395          * pre-TX mangling is not allowed to truncate
396          */
397         eff_buf.token = (char *)buf;
398         eff_buf.token_len = len;
399
400         switch (protocol) {
401         case LWS_WRITE_PING:
402         case LWS_WRITE_PONG:
403         case LWS_WRITE_CLOSE:
404                 break;
405         default:
406
407                 for (n = 0; n < wsi->count_active_extensions; n++) {
408                         m = wsi->active_extensions[n]->callback(
409                                 wsi->protocol->owning_server,
410                                 wsi->active_extensions[n], wsi,
411                                 LWS_EXT_CALLBACK_PAYLOAD_TX,
412                                 wsi->active_extensions_user[n], &eff_buf, 0);
413                         if (m < 0)
414                                 return -1;
415                 }
416         }
417
418         /*
419          * an extension did something we need to keep... for example, if
420          * compression extension, it has already updated its state according
421          * to this being issued
422          */
423         if ((char *)buf != eff_buf.token)
424                 wsi->u.ws.clean_buffer = 0; /* we need to buffer this if not all sent */
425
426         buf = (unsigned char *)eff_buf.token;
427         len = eff_buf.token_len;
428 #endif
429
430         switch (wsi->ietf_spec_revision) {
431         case 13:
432
433                 if (masked7) {
434                         pre += 4;
435                         dropmask = &buf[0 - pre];
436                         is_masked_bit = 0x80;
437                 }
438
439                 switch (protocol & 0xf) {
440                 case LWS_WRITE_TEXT:
441                         n = LWS_WS_OPCODE_07__TEXT_FRAME;
442                         break;
443                 case LWS_WRITE_BINARY:
444                         n = LWS_WS_OPCODE_07__BINARY_FRAME;
445                         break;
446                 case LWS_WRITE_CONTINUATION:
447                         n = LWS_WS_OPCODE_07__CONTINUATION;
448                         break;
449
450                 case LWS_WRITE_CLOSE:
451                         n = LWS_WS_OPCODE_07__CLOSE;
452
453                         /*
454                          * 06+ has a 2-byte status code in network order
455                          * we can do this because we demand post-buf
456                          */
457
458                         if (wsi->u.ws.close_reason) {
459                                 /* reason codes count as data bytes */
460                                 buf -= 2;
461                                 buf[0] = wsi->u.ws.close_reason >> 8;
462                                 buf[1] = wsi->u.ws.close_reason;
463                                 len += 2;
464                         }
465                         break;
466                 case LWS_WRITE_PING:
467                         n = LWS_WS_OPCODE_07__PING;
468                         break;
469                 case LWS_WRITE_PONG:
470                         n = LWS_WS_OPCODE_07__PONG;
471                         break;
472                 default:
473                         lwsl_warn("lws_write: unknown write opc / protocol\n");
474                         return -1;
475                 }
476
477                 if (!(protocol & LWS_WRITE_NO_FIN))
478                         n |= 1 << 7;
479
480                 if (len < 126) {
481                         pre += 2;
482                         buf[-pre] = n;
483                         buf[-pre + 1] = len | is_masked_bit;
484                 } else {
485                         if (len < 65536) {
486                                 pre += 4;
487                                 buf[-pre] = n;
488                                 buf[-pre + 1] = 126 | is_masked_bit;
489                                 buf[-pre + 2] = len >> 8;
490                                 buf[-pre + 3] = len;
491                         } else {
492                                 pre += 10;
493                                 buf[-pre] = n;
494                                 buf[-pre + 1] = 127 | is_masked_bit;
495 #if defined __LP64__
496                                         buf[-pre + 2] = (len >> 56) & 0x7f;
497                                         buf[-pre + 3] = len >> 48;
498                                         buf[-pre + 4] = len >> 40;
499                                         buf[-pre + 5] = len >> 32;
500 #else
501                                         buf[-pre + 2] = 0;
502                                         buf[-pre + 3] = 0;
503                                         buf[-pre + 4] = 0;
504                                         buf[-pre + 5] = 0;
505 #endif
506                                 buf[-pre + 6] = len >> 24;
507                                 buf[-pre + 7] = len >> 16;
508                                 buf[-pre + 8] = len >> 8;
509                                 buf[-pre + 9] = len;
510                         }
511                 }
512                 break;
513         }
514
515 do_more_inside_frame:
516
517         /*
518          * Deal with masking if we are in client -> server direction and
519          * the protocol demands it
520          */
521
522         if (wsi->mode == LWS_CONNMODE_WS_CLIENT) {
523
524                 if (!wsi->u.ws.inside_frame)
525                         if (libwebsocket_0405_frame_mask_generate(wsi)) {
526                                 lwsl_err("lws_write: frame mask generation failed\n");
527                                 return -1;
528                         }
529
530                 /*
531                  * in v7, just mask the payload
532                  */
533                 for (n = 4; n < (int)len + 4; n++)
534                         dropmask[n] = dropmask[n] ^
535                                 wsi->u.ws.frame_masking_nonce_04[
536                                         (wsi->u.ws.frame_mask_index++) & 3];
537
538                 if (dropmask) /* never set if already inside frame */
539                         /* copy the frame nonce into place */
540                         memcpy(dropmask, wsi->u.ws.frame_masking_nonce_04, 4);
541         }
542
543 send_raw:
544
545 #if 0
546         lwsl_debug("send %ld: ", len + post);
547         lwsl_hexdump(&buf[-pre], len + post);
548 #endif
549
550         switch (protocol) {
551         case LWS_WRITE_CLOSE:
552 /*              lwsl_hexdump(&buf[-pre], len + post); */
553         case LWS_WRITE_HTTP:
554         case LWS_WRITE_PONG:
555         case LWS_WRITE_PING:
556                 return lws_issue_raw(wsi, (unsigned char *)buf - pre,
557                                                               len + pre + post);
558         default:
559                 break;
560         }
561
562         wsi->u.ws.inside_frame = 1;
563
564         /*
565          * give any active extensions a chance to munge the buffer
566          * before send.  We pass in a pointer to an lws_tokens struct
567          * prepared with the default buffer and content length that's in
568          * there.  Rather than rewrite the default buffer, extensions
569          * that expect to grow the buffer can adapt .token to
570          * point to their own per-connection buffer in the extension
571          * user allocation.  By default with no extensions or no
572          * extension callback handling, just the normal input buffer is
573          * used then so it is efficient.
574          *
575          * callback returns 1 in case it wants to spill more buffers
576          *
577          * This takes care of holding the buffer if send is incomplete, ie,
578          * if wsi->u.ws.clean_buffer is 0 (meaning an extension meddled with
579          * the buffer).  If wsi->u.ws.clean_buffer is 1, it will instead
580          * return to the user code how much OF THE USER BUFFER was consumed.
581          */
582
583         n = lws_issue_raw_ext_access(wsi, buf - pre, len + pre + post);
584         if (n < 0)
585                 return n;
586
587         if (n == len + pre + post) {
588                 /* everything in the buffer was handled (or rebuffered...) */
589                 wsi->u.ws.inside_frame = 0;
590                 return orig_len;
591         }
592
593         /*
594          * it is how many bytes of user buffer got sent... may be < orig_len
595          * in which case callback when writable has already been arranged
596          * and user code can call libwebsocket_write() again with the rest
597          * later.
598          */
599
600         return n - (pre + post);
601 }
602
603 LWS_VISIBLE int libwebsockets_serve_http_file_fragment(
604                 struct libwebsocket_context *context, struct libwebsocket *wsi)
605 {
606         int n, m;
607
608         while (!lws_send_pipe_choked(wsi)) {
609                 n = read(wsi->u.http.fd, context->service_buffer,
610                                                sizeof(context->service_buffer));
611                 if (n > 0) {
612                         m = libwebsocket_write(wsi, context->service_buffer, n,
613                                                                 LWS_WRITE_HTTP);
614                         if (m < 0)
615                                 return -1;
616
617                         wsi->u.http.filepos += m;
618                         if (m != n)
619                                 /* adjust for what was not sent */
620                                 lseek(wsi->u.http.fd, m - n, SEEK_CUR);
621                 }
622
623                 if (n < 0)
624                         return -1; /* caller will close */
625
626                 if (wsi->u.http.filepos == wsi->u.http.filelen) {
627                         wsi->state = WSI_STATE_HTTP;
628
629                         if (wsi->protocol->callback)
630                                 /* ignore callback returned value */
631                                 user_callback_handle_rxflow(
632                                         wsi->protocol->callback, context, wsi,
633                                         LWS_CALLBACK_HTTP_FILE_COMPLETION,
634                                         wsi->user_space, NULL, 0);
635                         return 1;  /* >0 indicates completed */
636                 }
637         }
638
639         lwsl_notice("choked before able to send whole file (post)\n");
640         libwebsocket_callback_on_writable(context, wsi);
641
642         return 0; /* indicates further processing must be done */
643 }
644
645 /**
646  * libwebsockets_serve_http_file() - Send a file back to the client using http
647  * @context:            libwebsockets context
648  * @wsi:                Websocket instance (available from user callback)
649  * @file:               The file to issue over http
650  * @content_type:       The http content type, eg, text/html
651  * @other_headers:      NULL or pointer to \0-terminated other header string
652  *
653  *      This function is intended to be called from the callback in response
654  *      to http requests from the client.  It allows the callback to issue
655  *      local files down the http link in a single step.
656  *
657  *      Returning <0 indicates error and the wsi should be closed.  Returning
658  *      >0 indicates the file was completely sent and the wsi should be closed.
659  *      ==0 indicates the file transfer is started and needs more service later,
660  *      the wsi should be left alone.
661  */
662
663 LWS_VISIBLE int libwebsockets_serve_http_file(
664                 struct libwebsocket_context *context,
665                         struct libwebsocket *wsi, const char *file,
666                            const char *content_type, const char *other_headers)
667 {
668         struct stat stat_buf;
669         unsigned char *p = context->service_buffer;
670         int ret = 0;
671         int n;
672
673         wsi->u.http.fd = open(file, O_RDONLY
674 #ifdef WIN32
675                          | _O_BINARY
676 #endif
677         );
678
679         if (wsi->u.http.fd < 1) {
680                 lwsl_err("Unable to open '%s'\n", file);
681                 p += sprintf((char *)p,
682                  "HTTP/1.0 400 Bad\x0d\x0aServer: libwebsockets\x0d\x0a\x0d\x0a"
683                 );
684                 wsi->u.http.fd = 0;
685                 /* too small to care about partial, closing anyway */
686                 libwebsocket_write(wsi, context->service_buffer,
687                                 p - context->service_buffer, LWS_WRITE_HTTP);
688
689                 return -1;
690         }
691
692         fstat(wsi->u.http.fd, &stat_buf);
693         wsi->u.http.filelen = stat_buf.st_size;
694         p += sprintf((char *)p,
695 "HTTP/1.0 200 OK\x0d\x0aServer: libwebsockets\x0d\x0a""Content-Type: %s\x0d\x0a",
696                                                                   content_type);
697         if (other_headers) {
698                 n = strlen(other_headers);
699                 memcpy(p, other_headers, n);
700                 p += n;
701         }
702         p += sprintf((char *)p,
703                 "Content-Length: %u\x0d\x0a\x0d\x0a",
704                                         (unsigned int)stat_buf.st_size);
705
706         ret = libwebsocket_write(wsi, context->service_buffer,
707                                    p - context->service_buffer, LWS_WRITE_HTTP);
708         if (ret != (p - context->service_buffer)) {
709                 lwsl_err("_write returned %d from %d\n", ret, (p - context->service_buffer));
710                 return -1;
711         }
712
713         wsi->u.http.filepos = 0;
714         wsi->state = WSI_STATE_HTTP_ISSUING_FILE;
715
716         return libwebsockets_serve_http_file_fragment(context, wsi);
717 }
718