• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 /*
2  * libwebsockets - small server side websockets and web server implementation
3  *
4  * Copyright (C) 2010 - 2020 Andy Green <andy@warmcat.com>
5  *
6  * Permission is hereby granted, free of charge, to any person obtaining a copy
7  * of this software and associated documentation files (the "Software"), to
8  * deal in the Software without restriction, including without limitation the
9  * rights to use, copy, modify, merge, publish, distribute, sublicense, and/or
10  * sell copies of the Software, and to permit persons to whom the Software is
11  * furnished to do so, subject to the following conditions:
12  *
13  * The above copyright notice and this permission notice shall be included in
14  * all copies or substantial portions of the Software.
15  *
16  * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
17  * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
18  * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
19  * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
20  * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
21  * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
22  * IN THE SOFTWARE.
23  */
24 
25 #if !defined (LWS_PLUGIN_STATIC)
26 #if !defined(LWS_DLL)
27 #define LWS_DLL
28 #endif
29 #if !defined(LWS_INTERNAL)
30 #define LWS_INTERNAL
31 #endif
32 #include <libwebsockets.h>
33 #endif
34 
35 #include <stdlib.h>
36 #include <string.h>
37 #include <fcntl.h>
38 #include <sys/types.h>
39 #include <sys/stat.h>
40 #include <dirent.h>
41 #ifdef WIN32
42 #include <io.h>
43 #endif
44 #include <stdio.h>
45 #include <errno.h>
46 
47 struct dir_entry {
48 	lws_list_ptr next; /* sorted by mtime */
49 	char user[32];
50 	unsigned long long size;
51 	time_t mtime;
52 };
53 /* filename follows */
54 
55 #define lp_to_dir_entry(p, _n) lws_list_ptr_container(p, struct dir_entry, _n)
56 
57 struct pss_deaddrop;
58 
59 struct vhd_deaddrop {
60 	struct lws_context *context;
61 	struct lws_vhost *vh;
62 	const struct lws_protocols *protocol;
63 
64 	struct pss_deaddrop *pss_head;
65 
66 	const char *upload_dir;
67 
68 	struct lwsac *lwsac_head;
69 	struct dir_entry *dire_head;
70 	int filelist_version;
71 
72 	unsigned long long max_size;
73 };
74 
75 struct pss_deaddrop {
76 	struct lws_spa *spa;
77 	struct vhd_deaddrop *vhd;
78 	struct lws *wsi;
79 	char result[LWS_PRE + LWS_RECOMMENDED_MIN_HEADER_SPACE];
80 	char filename[256];
81 	char user[32];
82 	unsigned long long file_length;
83 	lws_filefd_type fd;
84 	int response_code;
85 
86 	struct pss_deaddrop *pss_list;
87 
88 	struct lwsac *lwsac_head;
89 	struct dir_entry *dire;
90 	int filelist_version;
91 
92 	uint8_t completed:1;
93 	uint8_t sent_headers:1;
94 	uint8_t sent_body:1;
95 	uint8_t first:1;
96 };
97 
98 static const char * const param_names[] = {
99 	"text",
100 	"send",
101 	"file",
102 	"upload",
103 };
104 
105 enum enum_param_names {
106 	EPN_TEXT,
107 	EPN_SEND,
108 	EPN_FILE,
109 	EPN_UPLOAD,
110 };
111 
112 static int
de_mtime_sort(lws_list_ptr a,lws_list_ptr b)113 de_mtime_sort(lws_list_ptr a, lws_list_ptr b)
114 {
115 	struct dir_entry *p1 = lp_to_dir_entry(a, next),
116 			 *p2 = lp_to_dir_entry(b, next);
117 
118 	return (int)(p2->mtime - p1->mtime);
119 }
120 
121 static void
start_sending_dir(struct pss_deaddrop * pss)122 start_sending_dir(struct pss_deaddrop *pss)
123 {
124 	if (pss->vhd->lwsac_head)
125 		lwsac_reference(pss->vhd->lwsac_head);
126 	pss->lwsac_head = pss->vhd->lwsac_head;
127 	pss->dire = pss->vhd->dire_head;
128 	pss->filelist_version = pss->vhd->filelist_version;
129 	pss->first = 1;
130 }
131 
132 static int
scan_upload_dir(struct vhd_deaddrop * vhd)133 scan_upload_dir(struct vhd_deaddrop *vhd)
134 {
135 	char filepath[256], subdir[3][128], *p;
136 	struct lwsac *lwsac_head = NULL;
137 	lws_list_ptr sorted_head = NULL;
138 	int i, sp = 0, found = 0;
139 	struct dir_entry *dire;
140 	struct dirent *de;
141 	size_t initial, m;
142 	struct stat s;
143 	DIR *dir[3];
144 
145 	initial = strlen(vhd->upload_dir) + 1;
146 	lws_strncpy(subdir[sp], vhd->upload_dir, sizeof(subdir[sp]));
147 	dir[sp] = opendir(vhd->upload_dir);
148 	if (!dir[sp]) {
149 		lwsl_err("%s: Unable to walk upload dir '%s'\n", __func__,
150 			 vhd->upload_dir);
151 		return -1;
152 	}
153 
154 	do {
155 		de = readdir(dir[sp]);
156 		if (!de) {
157 			closedir(dir[sp]);
158 #if !defined(__COVERITY__)
159 			if (!sp)
160 #endif
161 				break;
162 #if !defined(__COVERITY__)
163 			sp--;
164 			continue;
165 #endif
166 		}
167 
168 		p = filepath;
169 
170 		for (i = 0; i <= sp; i++)
171 			p += lws_snprintf(p, lws_ptr_diff_size_t((filepath + sizeof(filepath)), p),
172 					  "%s/", subdir[i]);
173 
174 		lws_snprintf(p, lws_ptr_diff_size_t((filepath + sizeof(filepath)), p), "%s",
175 				  de->d_name);
176 
177 		/* ignore temp files */
178 		if (de->d_name[strlen(de->d_name) - 1] == '~')
179 			continue;
180 #if defined(__COVERITY__)
181 		s.st_size = 0;
182 		s.st_mtime = 0;
183 #else
184 		/* coverity[toctou] */
185 		if (stat(filepath, &s))
186 			continue;
187 
188 		if (S_ISDIR(s.st_mode)) {
189 			if (!strcmp(de->d_name, ".") ||
190 			    !strcmp(de->d_name, ".."))
191 				continue;
192 			sp++;
193 			if (sp == LWS_ARRAY_SIZE(dir)) {
194 				lwsl_err("%s: Skipping too-deep subdir %s\n",
195 					 __func__, filepath);
196 				sp--;
197 				continue;
198 			}
199 			lws_strncpy(subdir[sp], de->d_name, sizeof(subdir[sp]));
200 			dir[sp] = opendir(filepath);
201 			if (!dir[sp]) {
202 				lwsl_err("%s: Unable to open subdir '%s'\n",
203 					 __func__, filepath);
204 				goto bail;
205 			}
206 			continue;
207 		}
208 #endif
209 
210 		m = strlen(filepath + initial) + 1;
211 		dire = lwsac_use(&lwsac_head, sizeof(*dire) + m, 0);
212 		if (!dire) {
213 			lwsac_free(&lwsac_head);
214 
215 			goto bail;
216 		}
217 
218 		dire->next = NULL;
219 		dire->size = (unsigned long long)s.st_size;
220 		dire->mtime = s.st_mtime;
221 		dire->user[0] = '\0';
222 #if !defined(__COVERITY__)
223 		if (sp)
224 			lws_strncpy(dire->user, subdir[1], sizeof(dire->user));
225 #endif
226 
227 		found++;
228 
229 		memcpy(&dire[1], filepath + initial, m);
230 
231 		lws_list_ptr_insert(&sorted_head, &dire->next, de_mtime_sort);
232 	} while (1);
233 
234 	/* the old lwsac continues to live while someone else is consuming it */
235 	if (vhd->lwsac_head)
236 		lwsac_detach(&vhd->lwsac_head);
237 
238 	/* we replace it with the fresh one */
239 	vhd->lwsac_head = lwsac_head;
240 	if (sorted_head)
241 		vhd->dire_head = lp_to_dir_entry(sorted_head, next);
242 	else
243 		vhd->dire_head = NULL;
244 
245 	vhd->filelist_version++;
246 
247 	lwsl_info("%s: found %d\n", __func__, found);
248 
249 	lws_start_foreach_llp(struct pss_deaddrop **, ppss, vhd->pss_head) {
250 		start_sending_dir(*ppss);
251 		lws_callback_on_writable((*ppss)->wsi);
252 	} lws_end_foreach_llp(ppss, pss_list);
253 
254 	return 0;
255 
256 bail:
257 	while (sp >= 0)
258 		closedir(dir[sp--]);
259 
260 	return -1;
261 }
262 
263 static int
file_upload_cb(void * data,const char * name,const char * filename,char * buf,int _len,enum lws_spa_fileupload_states state)264 file_upload_cb(void *data, const char *name, const char *filename,
265 	       char *buf, int _len, enum lws_spa_fileupload_states state)
266 {
267 	struct pss_deaddrop *pss = (struct pss_deaddrop *)data;
268 	char filename2[256];
269 	size_t len = (size_t)_len;
270 	int n;
271 
272 	(void)n;
273 
274 	switch (state) {
275 	case LWS_UFS_OPEN:
276 		lws_urldecode(filename2, filename, sizeof(filename2) - 1);
277 		lws_filename_purify_inplace(filename2);
278 		if (pss->user[0]) {
279 			lws_filename_purify_inplace(pss->user);
280 			lws_snprintf(pss->filename, sizeof(pss->filename),
281 				     "%s/%s", pss->vhd->upload_dir, pss->user);
282 			if (mkdir(pss->filename
283 #if !defined(WIN32)
284 				, 0700
285 #endif
286 				) < 0)
287 				lwsl_debug("%s: mkdir failed\n", __func__);
288 			lws_snprintf(pss->filename, sizeof(pss->filename),
289 				     "%s/%s/%s~", pss->vhd->upload_dir,
290 				     pss->user, filename2);
291 		} else
292 			lws_snprintf(pss->filename, sizeof(pss->filename),
293 				     "%s/%s~", pss->vhd->upload_dir, filename2);
294 		lwsl_notice("%s: filename '%s'\n", __func__, pss->filename);
295 
296 		pss->fd = (lws_filefd_type)(long long)lws_open(pss->filename,
297 			      O_CREAT | O_TRUNC | O_RDWR, 0600);
298 		if (pss->fd == LWS_INVALID_FILE) {
299 			pss->response_code = HTTP_STATUS_INTERNAL_SERVER_ERROR;
300 			lwsl_err("%s: unable to open %s (errno %d)\n", __func__,
301 					pss->filename, errno);
302 			return -1;
303 		}
304 		break;
305 
306 	case LWS_UFS_FINAL_CONTENT:
307 	case LWS_UFS_CONTENT:
308 		if (len) {
309 			pss->file_length += (unsigned int)len;
310 
311 			/* if the file length is too big, drop it */
312 			if (pss->file_length > pss->vhd->max_size) {
313 				pss->response_code =
314 					HTTP_STATUS_REQ_ENTITY_TOO_LARGE;
315 				close((int)(lws_intptr_t)pss->fd);
316 				pss->fd = LWS_INVALID_FILE;
317 				unlink(pss->filename);
318 
319 				return -1;
320 			}
321 
322 			if (pss->fd != LWS_INVALID_FILE) {
323 				n = (int)write((int)(lws_intptr_t)pss->fd, buf, (unsigned int)len);
324 				lwsl_debug("%s: write %d says %d\n", __func__,
325 					   (int)len, n);
326 				lws_set_timeout(pss->wsi, PENDING_TIMEOUT_HTTP_CONTENT, 30);
327 			}
328 		}
329 		if (state == LWS_UFS_CONTENT)
330 			break;
331 
332 		if (pss->fd != LWS_INVALID_FILE)
333 			close((int)(lws_intptr_t)pss->fd);
334 
335 		/* the temp filename without the ~ */
336 		lws_strncpy(filename2, pss->filename, sizeof(filename2));
337 		filename2[strlen(filename2) - 1] = '\0';
338 		if (rename(pss->filename, filename2) < 0)
339 			lwsl_err("%s: unable to rename\n", __func__);
340 
341 		pss->fd = LWS_INVALID_FILE;
342 		pss->response_code = HTTP_STATUS_OK;
343 		scan_upload_dir(pss->vhd);
344 
345 		break;
346 	case LWS_UFS_CLOSE:
347 		break;
348 	}
349 
350 	return 0;
351 }
352 
353 /*
354  * returns length in bytes
355  */
356 
357 static int
format_result(struct pss_deaddrop * pss)358 format_result(struct pss_deaddrop *pss)
359 {
360 	unsigned char *p, *start, *end;
361 
362 	p = (unsigned char *)pss->result + LWS_PRE;
363 	start = p;
364 	end = p + sizeof(pss->result) - LWS_PRE - 1;
365 
366 	p += lws_snprintf((char *)p, lws_ptr_diff_size_t(end, p),
367 			"<!DOCTYPE html><html lang=\"en\"><head>"
368 			"<meta charset=utf-8 http-equiv=\"Content-Language\" "
369 			"content=\"en\"/>"
370 			"</head>");
371 	p += lws_snprintf((char *)p, lws_ptr_diff_size_t(end, p), "</body></html>");
372 
373 	return (int)lws_ptr_diff(p, start);
374 }
375 
376 static int
callback_deaddrop(struct lws * wsi,enum lws_callback_reasons reason,void * user,void * in,size_t len)377 callback_deaddrop(struct lws *wsi, enum lws_callback_reasons reason,
378 		  void *user, void *in, size_t len)
379 {
380 	struct vhd_deaddrop *vhd = (struct vhd_deaddrop *)
381 				lws_protocol_vh_priv_get(lws_get_vhost(wsi),
382 							 lws_get_protocol(wsi));
383 	struct pss_deaddrop *pss = (struct pss_deaddrop *)user;
384 	uint8_t buf[LWS_PRE + LWS_RECOMMENDED_MIN_HEADER_SPACE],
385 		*start = &buf[LWS_PRE], *p = start,
386 		*end = &buf[sizeof(buf) - LWS_PRE - 1];
387 	char fname[256], *wp;
388 	const char *cp;
389 	int n, m, was;
390 
391 	switch (reason) {
392 
393 	case LWS_CALLBACK_PROTOCOL_INIT: /* per vhost */
394 		lws_protocol_vh_priv_zalloc(lws_get_vhost(wsi),
395 					    lws_get_protocol(wsi),
396 					    sizeof(struct vhd_deaddrop));
397 
398 		vhd = (struct vhd_deaddrop *)
399 			lws_protocol_vh_priv_get(lws_get_vhost(wsi),
400 						 lws_get_protocol(wsi));
401 		if (!vhd)
402 			return 0;
403 
404 		vhd->context = lws_get_context(wsi);
405 		vhd->vh = lws_get_vhost(wsi);
406 		vhd->protocol = lws_get_protocol(wsi);
407 		vhd->max_size = 20 * 1024 * 1024; /* default without pvo */
408 
409 		if (!lws_pvo_get_str(in, "max-size", &cp))
410 			vhd->max_size = (unsigned long long)atoll(cp);
411 		if (lws_pvo_get_str(in, "upload-dir", &vhd->upload_dir)) {
412 			lwsl_warn("%s: requires 'upload-dir' pvo\n", __func__);
413 			return 0;
414 		}
415 
416 		scan_upload_dir(vhd);
417 
418 		lwsl_notice("  deaddrop: vh %s, upload dir %s, max size %llu\n",
419 			    lws_get_vhost_name(vhd->vh), vhd->upload_dir,
420 			    vhd->max_size);
421 		break;
422 
423 	case LWS_CALLBACK_PROTOCOL_DESTROY:
424 		if (vhd)
425 			lwsac_free(&vhd->lwsac_head);
426 		break;
427 
428 	/* WS-related */
429 
430 	case LWS_CALLBACK_ESTABLISHED:
431 		pss->vhd = vhd;
432 		pss->wsi = wsi;
433 		/* add ourselves to the list of live pss held in the vhd */
434 		pss->pss_list = vhd->pss_head;
435 		vhd->pss_head = pss;
436 
437 		m = lws_hdr_copy(wsi, pss->user, sizeof(pss->user),
438 				 WSI_TOKEN_HTTP_AUTHORIZATION);
439 		if (m > 0)
440 			lwsl_info("%s: basic auth user: %s\n",
441 				  __func__, pss->user);
442 		else
443 			pss->user[0] = '\0';
444 
445 		start_sending_dir(pss);
446 		lws_callback_on_writable(wsi);
447 		return 0;
448 
449 	case LWS_CALLBACK_CLOSED:
450 		if (pss->lwsac_head)
451 			lwsac_unreference(&pss->lwsac_head);
452 		/* remove our closing pss from the list of live pss */
453 		lws_start_foreach_llp(struct pss_deaddrop **,
454 				      ppss, vhd->pss_head) {
455 			if (*ppss == pss) {
456 				*ppss = pss->pss_list;
457 				break;
458 			}
459 		} lws_end_foreach_llp(ppss, pss_list);
460 		return 0;
461 
462 	case LWS_CALLBACK_RECEIVE:
463 		/* we get this kind of thing {"del":"agreen/no-entry.svg"} */
464 		if (!pss || len < 10)
465 			break;
466 
467 		if (strncmp((const char *)in, "{\"del\":\"", 8))
468 			break;
469 
470 		cp = strchr((const char *)in, '/');
471 		if (cp) {
472 			n = (int)(((void *)cp - in)) - 8;
473 
474 			if ((int)strlen(pss->user) != n ||
475 			    memcmp(pss->user, ((const char *)in) + 8, (unsigned int)n)) {
476 				lwsl_notice("%s: del: auth mismatch "
477 					    " '%s' '%s' (%d)\n",
478 					    __func__, pss->user,
479 					    ((const char *)in) + 8, n);
480 				break;
481 			}
482 		}
483 
484 		lws_strncpy(fname, ((const char *)in) + 8, sizeof(fname));
485 		lws_filename_purify_inplace(fname);
486 		wp = strchr((const char *)fname, '\"');
487 		if (wp)
488 			*wp = '\0';
489 
490 		lws_snprintf((char *)buf, sizeof(buf), "%s/%s", vhd->upload_dir,
491 			     fname);
492 
493 		lwsl_notice("%s: del: path %s\n", __func__, (const char *)buf);
494 
495 		if (unlink((const char *)buf) < 0)
496 			lwsl_err("%s: unlink %s failed\n", __func__,
497 					(const char *)buf);
498 
499 		scan_upload_dir(vhd);
500 		break;
501 
502 	case LWS_CALLBACK_SERVER_WRITEABLE:
503 		if (pss->lwsac_head && !pss->dire)
504 			return 0;
505 
506 		was = 0;
507 		if (pss->first) {
508 			p += lws_snprintf((char *)p, lws_ptr_diff_size_t(end, p),
509 					  "{\"max_size\":%llu, \"files\": [",
510 					  vhd->max_size);
511 			was = 1;
512 		}
513 
514 		m = 5;
515 		while (m-- && pss->dire) {
516 			p += lws_snprintf((char *)p, lws_ptr_diff_size_t(end, p),
517 					  "%c{\"name\":\"%s\", "
518 					  "\"size\":%llu,"
519 					  "\"mtime\":%llu,"
520 					  "\"yours\":%d}",
521 					  pss->first ? ' ' : ',',
522 					  (const char *)&pss->dire[1],
523 					  pss->dire->size,
524 					  (unsigned long long)pss->dire->mtime,
525 					  !strcmp(pss->user, pss->dire->user) &&
526 						  pss->user[0]);
527 			pss->first = 0;
528 			pss->dire = lp_to_dir_entry(pss->dire->next, next);
529 		}
530 
531 		if (!pss->dire) {
532 			p += lws_snprintf((char *)p, lws_ptr_diff_size_t(end, p),
533 					  "]}");
534 			if (pss->lwsac_head) {
535 				lwsac_unreference(&pss->lwsac_head);
536 				pss->lwsac_head = NULL;
537 			}
538 		}
539 
540 		n = lws_write(wsi, start, lws_ptr_diff_size_t(p, start),
541 				(enum lws_write_protocol)lws_write_ws_flags(LWS_WRITE_TEXT, was,
542 						 !pss->dire));
543 		if (n < 0) {
544 			lwsl_notice("%s: ws write failed\n", __func__);
545 			return 1;
546 		}
547 		if (pss->dire) {
548 			lws_callback_on_writable(wsi);
549 
550 			return 0;
551 		}
552 
553 		/* ie, we finished */
554 
555 		if (pss->filelist_version != pss->vhd->filelist_version) {
556 			lwsl_info("%s: restart send\n", __func__);
557 			/* what we just sent is already out of date */
558 			start_sending_dir(pss);
559 			lws_callback_on_writable(wsi);
560 		}
561 
562 		return 0;
563 
564 	/* POST-related */
565 
566 	case LWS_CALLBACK_HTTP_BODY:
567 
568 		/* create the POST argument parser if not already existing */
569 		if (!pss->spa) {
570 			pss->vhd = vhd;
571 			pss->wsi = wsi;
572 			pss->spa = lws_spa_create(wsi, param_names,
573 						  LWS_ARRAY_SIZE(param_names),
574 						  1024, file_upload_cb, pss);
575 			if (!pss->spa)
576 				return -1;
577 
578 			pss->filename[0] = '\0';
579 			pss->file_length = 0;
580 			/* catchall */
581 			pss->response_code = HTTP_STATUS_SERVICE_UNAVAILABLE;
582 
583 			m = lws_hdr_copy(wsi, pss->user, sizeof(pss->user),
584 					 WSI_TOKEN_HTTP_AUTHORIZATION);
585 			if (m > 0)
586 				lwsl_info("basic auth user: %s\n", pss->user);
587 			else
588 				pss->user[0] = '\0';
589 		}
590 
591 		/* let it parse the POST data */
592 		if (lws_spa_process(pss->spa, in, (int)len)) {
593 			lwsl_notice("spa saw a problem\n");
594 			/* some problem happened */
595 			lws_spa_finalize(pss->spa);
596 
597 			pss->completed = 1;
598 			lws_callback_on_writable(wsi);
599 		}
600 		break;
601 
602 	case LWS_CALLBACK_HTTP_BODY_COMPLETION:
603 		/* call to inform no more payload data coming */
604 		lws_spa_finalize(pss->spa);
605 
606 		pss->completed = 1;
607 		lws_callback_on_writable(wsi);
608 		break;
609 
610 	case LWS_CALLBACK_HTTP_WRITEABLE:
611 		if (!pss->completed)
612 			break;
613 
614 		p = (unsigned char *)pss->result + LWS_PRE;
615 		start = p;
616 		end = p + sizeof(pss->result) - LWS_PRE - 1;
617 
618 		if (!pss->sent_headers) {
619 			n = format_result(pss);
620 
621 			if (lws_add_http_header_status(wsi,
622 					(unsigned int)pss->response_code,
623 						       &p, end))
624 				goto bail;
625 
626 			if (lws_add_http_header_by_token(wsi,
627 					WSI_TOKEN_HTTP_CONTENT_TYPE,
628 					(unsigned char *)"text/html", 9,
629 					&p, end))
630 				goto bail;
631 			if (lws_add_http_header_content_length(wsi, (lws_filepos_t)n, &p, end))
632 				goto bail;
633 			if (lws_finalize_http_header(wsi, &p, end))
634 				goto bail;
635 
636 			/* first send the headers ... */
637 			n = lws_write(wsi, start, lws_ptr_diff_size_t(p, start),
638 				      LWS_WRITE_HTTP_HEADERS |
639 				      LWS_WRITE_H2_STREAM_END);
640 			if (n < 0)
641 				goto bail;
642 
643 			pss->sent_headers = 1;
644 			lws_callback_on_writable(wsi);
645 			break;
646 		}
647 
648 		if (!pss->sent_body) {
649 			n = format_result(pss);
650 			n = lws_write(wsi, (unsigned char *)start, (unsigned int)n,
651 				      LWS_WRITE_HTTP_FINAL);
652 
653 			pss->sent_body = 1;
654 			if (n < 0) {
655 				lwsl_err("%s: writing body failed\n", __func__);
656 				return 1;
657 			}
658 			goto try_to_reuse;
659 		}
660 		break;
661 
662 	case LWS_CALLBACK_HTTP_DROP_PROTOCOL:
663 		/* called when our wsi user_space is going to be destroyed */
664 		if (pss->spa) {
665 			lws_spa_destroy(pss->spa);
666 			pss->spa = NULL;
667 		}
668 		break;
669 
670 	default:
671 		break;
672 	}
673 
674 	return 0;
675 
676 bail:
677 
678 	return 1;
679 
680 try_to_reuse:
681 	if (lws_http_transaction_completed(wsi))
682 		return -1;
683 
684 	return 0;
685 }
686 
687 #define LWS_PLUGIN_PROTOCOL_DEADDROP \
688 	{ \
689 		"lws-deaddrop", \
690 		callback_deaddrop, \
691 		sizeof(struct pss_deaddrop), \
692 		1024, \
693 		0, NULL, 0 \
694 	}
695 
696 #if !defined (LWS_PLUGIN_STATIC)
697 
698 LWS_VISIBLE const struct lws_protocols deaddrop_protocols[] = {
699 	LWS_PLUGIN_PROTOCOL_DEADDROP
700 };
701 
702 LWS_VISIBLE const lws_plugin_protocol_t deaddrop = {
703 	.hdr = {
704 		"deaddrop",
705 		"lws_protocol_plugin",
706 		LWS_BUILD_HASH,
707 		LWS_PLUGIN_API_MAGIC
708 	},
709 
710 	.protocols = deaddrop_protocols,
711 	.count_protocols = LWS_ARRAY_SIZE(deaddrop_protocols),
712 	.extensions = NULL,
713 	.count_extensions = 0,
714 };
715 
716 #endif
717