Imported Upstream version 3.2.0
[platform/upstream/libwebsockets.git] / plugins / generic-sessions / protocol_lws_messageboard.c
1 /*
2  * ws protocol handler plugin for messageboard "generic sessions" demo
3  *
4  * Copyright (C) 2010-2019 Andy Green <andy@warmcat.com>
5  *
6  * This program 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  * General Public License for more details.
15  *
16  * You should have received a copy of the GNU 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 #define LWS_DLL
23 #define LWS_INTERNAL
24 #include <libwebsockets.h>
25
26 #include <sqlite3.h>
27 #include <string.h>
28 #include <stdlib.h>
29
30 struct per_vhost_data__gs_mb {
31         struct lws_vhost *vh;
32         const struct lws_protocols *gsp;
33         sqlite3 *pdb;
34         char message_db[256];
35         unsigned long last_idx;
36 };
37
38 struct per_session_data__gs_mb {
39         void *pss_gs; /* for use by generic-sessions */
40         struct lws_session_info sinfo;
41         struct lws_spa *spa;
42         unsigned long last_idx;
43         unsigned int our_form:1;
44         char second_http_part;
45 };
46
47 static const char * const param_names[] = {
48         "send",
49         "msg",
50 };
51 enum {
52         MBSPA_SUBMIT,
53         MBSPA_MSG,
54 };
55
56 #define MAX_MSG_LEN 512
57
58 struct message {
59         unsigned long idx;
60         unsigned long time;
61         char username[32];
62         char email[100];
63         char ip[72];
64         char content[MAX_MSG_LEN];
65 };
66
67 static int
68 lookup_cb(void *priv, int cols, char **col_val, char **col_name)
69 {
70         struct message *m = (struct message *)priv;
71         int n;
72
73         for (n = 0; n < cols; n++) {
74
75                 if (!strcmp(col_name[n], "idx") ||
76                     !strcmp(col_name[n], "MAX(idx)")) {
77                         if (!col_val[n])
78                                 m->idx = 0;
79                         else
80                                 m->idx = atol(col_val[n]);
81                         continue;
82                 }
83                 if (!strcmp(col_name[n], "time")) {
84                         m->time = atol(col_val[n]);
85                         continue;
86                 }
87                 if (!strcmp(col_name[n], "username")) {
88                         lws_strncpy(m->username, col_val[n], sizeof(m->username));
89                         continue;
90                 }
91                 if (!strcmp(col_name[n], "email")) {
92                         lws_strncpy(m->email, col_val[n], sizeof(m->email));
93                         continue;
94                 }
95                 if (!strcmp(col_name[n], "ip")) {
96                         lws_strncpy(m->ip, col_val[n], sizeof(m->ip));
97                         continue;
98                 }
99                 if (!strcmp(col_name[n], "content")) {
100                         lws_strncpy(m->content, col_val[n], sizeof(m->content));
101                         continue;
102                 }
103         }
104         return 0;
105 }
106
107 static unsigned long
108 get_last_idx(struct per_vhost_data__gs_mb *vhd)
109 {
110         struct message m;
111
112         if (sqlite3_exec(vhd->pdb, "SELECT MAX(idx) FROM msg;",
113                          lookup_cb, &m, NULL) != SQLITE_OK) {
114                 lwsl_err("Unable to lookup token: %s\n",
115                          sqlite3_errmsg(vhd->pdb));
116                 return 0;
117         }
118
119         return m.idx;
120 }
121
122 static int
123 post_message(struct lws *wsi, struct per_vhost_data__gs_mb *vhd,
124              struct per_session_data__gs_mb *pss)
125 {
126         struct lws_session_info sinfo;
127         char s[MAX_MSG_LEN + 512];
128         char esc[MAX_MSG_LEN + 256];
129
130         vhd->gsp->callback(wsi, LWS_CALLBACK_SESSION_INFO,
131                            pss->pss_gs, &sinfo, 0);
132
133         lws_snprintf((char *)s, sizeof(s) - 1,
134                  "insert into msg(time, username, email, ip, content)"
135                  " values (%lu, '%s', '%s', '%s', '%s');",
136                  (unsigned long)lws_now_secs(), sinfo.username, sinfo.email, sinfo.ip,
137                  lws_sql_purify(esc, lws_spa_get_string(pss->spa, MBSPA_MSG),
138                                 sizeof(esc) - 1));
139         if (sqlite3_exec(vhd->pdb, s, NULL, NULL, NULL) != SQLITE_OK) {
140                 lwsl_err("Unable to insert msg: %s\n", sqlite3_errmsg(vhd->pdb));
141                 return 1;
142         }
143         vhd->last_idx = get_last_idx(vhd);
144
145         /* let everybody connected by this protocol on this vhost know */
146         lws_callback_on_writable_all_protocol_vhost(lws_get_vhost(wsi),
147                                                     lws_get_protocol(wsi));
148
149         return 0;
150 }
151
152 static int
153 callback_messageboard(struct lws *wsi, enum lws_callback_reasons reason,
154                       void *user, void *in, size_t len)
155 {
156         struct per_session_data__gs_mb *pss = (struct per_session_data__gs_mb *)user;
157         const struct lws_protocol_vhost_options *pvo;
158         struct per_vhost_data__gs_mb *vhd = (struct per_vhost_data__gs_mb *)
159                 lws_protocol_vh_priv_get(lws_get_vhost(wsi), lws_get_protocol(wsi));
160         unsigned char *p, *start, *end, buffer[LWS_PRE + 4096];
161         char s[512];
162         int n;
163
164         switch (reason) {
165         case LWS_CALLBACK_PROTOCOL_INIT: /* per vhost */
166
167                 vhd = lws_protocol_vh_priv_zalloc(lws_get_vhost(wsi),
168                         lws_get_protocol(wsi), sizeof(struct per_vhost_data__gs_mb));
169                 if (!vhd)
170                         return 1;
171                 vhd->vh = lws_get_vhost(wsi);
172                 vhd->gsp = lws_vhost_name_to_protocol(vhd->vh,
173                                                 "protocol-generic-sessions");
174                 if (!vhd->gsp) {
175                         lwsl_err("messageboard: requires generic-sessions\n");
176                         return 1;
177                 }
178
179                 pvo = (const struct lws_protocol_vhost_options *)in;
180                 while (pvo) {
181                         if (!strcmp(pvo->name, "message-db"))
182                                 strncpy(vhd->message_db, pvo->value,
183                                         sizeof(vhd->message_db) - 1);
184                         pvo = pvo->next;
185                 }
186                 if (!vhd->message_db[0]) {
187                         lwsl_err("messageboard: \"message-db\" pvo missing\n");
188                         return 1;
189                 }
190
191                 if (lws_struct_sq3_open(lws_get_context(wsi),
192                                         vhd->message_db, &vhd->pdb)) {
193                         lwsl_err("Unable to open message db %s: %s\n",
194                                  vhd->message_db, sqlite3_errmsg(vhd->pdb));
195
196                         return 1;
197                 }
198                 if (sqlite3_exec(vhd->pdb, "create table if not exists msg ("
199                                  " idx integer primary key, time integer,"
200                                  " username varchar(32), email varchar(100),"
201                                  " ip varchar(80), content blob);",
202                                  NULL, NULL, NULL) != SQLITE_OK) {
203                         lwsl_err("Unable to create msg table: %s\n",
204                                  sqlite3_errmsg(vhd->pdb));
205
206                         return 1;
207                 }
208
209                 vhd->last_idx = get_last_idx(vhd);
210                 break;
211
212         case LWS_CALLBACK_PROTOCOL_DESTROY:
213                 if (vhd && vhd->pdb)
214                         sqlite3_close(vhd->pdb);
215                 goto passthru;
216
217         case LWS_CALLBACK_ESTABLISHED:
218                 vhd->gsp->callback(wsi, LWS_CALLBACK_SESSION_INFO,
219                                    pss->pss_gs, &pss->sinfo, 0);
220                 if (!pss->sinfo.username[0]) {
221                         lwsl_notice("messageboard ws attempt with no session\n");
222
223                         return -1;
224                 }
225
226                 lws_callback_on_writable(wsi);
227                 break;
228
229         case LWS_CALLBACK_CLOSED:
230                 lwsl_debug("%s: LWS_CALLBACK_CLOSED\n", __func__);
231                 if (pss && pss->pss_gs) {
232                         free(pss->pss_gs);
233                         pss->pss_gs = NULL;
234                 }
235                 break;
236
237         case LWS_CALLBACK_SERVER_WRITEABLE:
238                 {
239                         struct message m;
240                         char j[MAX_MSG_LEN + 512], e[MAX_MSG_LEN + 512],
241                                 *p = j + LWS_PRE, *start = p,
242                                 *end = j + sizeof(j) - LWS_PRE;
243
244                         if (pss->last_idx == vhd->last_idx)
245                                 break;
246
247                         /* restrict to last 10 */
248                         if (!pss->last_idx)
249                                 if (vhd->last_idx >= 10)
250                                         pss->last_idx = vhd->last_idx - 10;
251
252                         sprintf(s, "select idx, time, username, email, ip, content "
253                                    "from msg where idx > %lu order by idx limit 1;",
254                                    pss->last_idx);
255                         if (sqlite3_exec(vhd->pdb, s, lookup_cb, &m, NULL) != SQLITE_OK) {
256                                 lwsl_err("Unable to lookup msg: %s\n",
257                                          sqlite3_errmsg(vhd->pdb));
258                                 return 0;
259                         }
260
261                         /* format in JSON */
262                         p += lws_snprintf(p, end - p,
263                                         "{\"idx\":\"%lu\",\"time\":\"%lu\",",
264                                         m.idx, m.time);
265                         p += lws_snprintf(p, end - p, " \"username\":\"%s\",",
266                                 lws_json_purify(e, m.username, sizeof(e)));
267                         p += lws_snprintf(p, end - p, " \"email\":\"%s\",",
268                                 lws_json_purify(e, m.email, sizeof(e)));
269                         p += lws_snprintf(p, end - p, " \"ip\":\"%s\",",
270                                 lws_json_purify(e, m.ip, sizeof(e)));
271                         p += lws_snprintf(p, end - p, " \"content\":\"%s\"}",
272                                 lws_json_purify(e, m.content, sizeof(e)));
273
274                         if (lws_write(wsi, (unsigned char *)start, p - start,
275                                       LWS_WRITE_TEXT) < 0)
276                                 return -1;
277
278                         pss->last_idx = m.idx;
279                         if (pss->last_idx == vhd->last_idx)
280                                 break;
281
282                         lws_callback_on_writable(wsi); /* more to do */
283                 }
284                 break;
285
286         case LWS_CALLBACK_HTTP:
287                 pss->our_form = 0;
288
289                 /* ie, it's our messageboard new message form */
290                 if (!strcmp((const char *)in, "/msg") ||
291                     !strcmp((const char *)in, "msg")) {
292                         pss->our_form = 1;
293                         break;
294                 }
295
296                 goto passthru;
297
298         case LWS_CALLBACK_HTTP_BODY:
299                 if (!pss->our_form)
300                         goto passthru;
301
302                 if (len < 2)
303                         break;
304                 if (!pss->spa) {
305                         pss->spa = lws_spa_create(wsi, param_names,
306                                         LWS_ARRAY_SIZE(param_names),
307                                                 MAX_MSG_LEN + 1024, NULL, NULL);
308                         if (!pss->spa)
309                                 return -1;
310                 }
311
312                 if (lws_spa_process(pss->spa, in, len)) {
313                         lwsl_notice("spa process blew\n");
314                         return -1;
315                 }
316                 break;
317
318         case LWS_CALLBACK_HTTP_WRITEABLE:
319                 if (!pss->second_http_part)
320                         goto passthru;
321
322                 s[0] = '0';
323                 n = lws_write(wsi, (unsigned char *)s, 1, LWS_WRITE_HTTP|
324                                 LWS_WRITE_H2_STREAM_END);
325                 if (n != 1)
326                         return -1;
327
328                 goto try_to_reuse;
329
330         case LWS_CALLBACK_HTTP_BODY_COMPLETION:
331                 if (!pss->our_form)
332                         goto passthru;
333
334                 if (post_message(wsi, vhd, pss))
335                         return -1;
336
337                 p = buffer + LWS_PRE;
338                 start = p;
339                 end = p + sizeof(buffer) - LWS_PRE;
340
341                 if (lws_add_http_header_status(wsi, HTTP_STATUS_OK, &p, end))
342                         return -1;
343                 if (lws_add_http_header_by_token(wsi, WSI_TOKEN_HTTP_CONTENT_TYPE,
344                                 (unsigned char *)"text/plain", 10, &p, end))
345                         return -1;
346                 if (lws_add_http_header_content_length(wsi, 1, &p, end))
347                         return -1;
348                 if (lws_finalize_http_header(wsi, &p, end))
349                         return -1;
350
351                 n = lws_write(wsi, start, p - start, LWS_WRITE_HTTP_HEADERS);
352                 if (n != (p - start)) {
353                         lwsl_err("_write returned %d from %ld\n", n, (long)(p - start));
354                         return -1;
355                 }
356                 pss->second_http_part = 1;
357                 lws_callback_on_writable(wsi);
358                 break;
359
360         case LWS_CALLBACK_HTTP_BIND_PROTOCOL:
361                 if (!pss || !vhd || pss->pss_gs)
362                         break;
363
364                 pss->pss_gs = malloc(vhd->gsp->per_session_data_size);
365                 if (!pss->pss_gs)
366                         return -1;
367
368                 memset(pss->pss_gs, 0, vhd->gsp->per_session_data_size);
369                 break;
370
371         case LWS_CALLBACK_HTTP_DROP_PROTOCOL:
372                 if (vhd->gsp->callback(wsi, reason, pss ? pss->pss_gs : NULL, in, len))
373                         return -1;
374
375                 if (pss && pss->spa) {
376                         lws_spa_destroy(pss->spa);
377                         pss->spa = NULL;
378                 }
379                 if (pss && pss->pss_gs) {
380                         free(pss->pss_gs);
381                         pss->pss_gs = NULL;
382                 }
383                 break;
384
385         default:
386 passthru:
387                 if (!pss || !vhd)
388                         break;
389
390                 return vhd->gsp->callback(wsi, reason, pss->pss_gs, in, len);
391         }
392
393         return 0;
394
395
396 try_to_reuse:
397         if (lws_http_transaction_completed(wsi))
398                 return -1;
399
400         return 0;
401 }
402
403 static const struct lws_protocols protocols[] = {
404         {
405                 "protocol-lws-messageboard",
406                 callback_messageboard,
407                 sizeof(struct per_session_data__gs_mb),
408                 4096,
409         },
410 };
411
412 LWS_EXTERN LWS_VISIBLE int
413 init_protocol_lws_messageboard(struct lws_context *context,
414                                struct lws_plugin_capability *c)
415 {
416         if (c->api_magic != LWS_PLUGIN_API_MAGIC) {
417                 lwsl_err("Plugin API %d, library API %d", LWS_PLUGIN_API_MAGIC,
418                          c->api_magic);
419                 return 1;
420         }
421
422         c->protocols = protocols;
423         c->count_protocols = LWS_ARRAY_SIZE(protocols);
424         c->extensions = NULL;
425         c->count_extensions = 0;
426
427         return 0;
428 }
429
430 LWS_EXTERN LWS_VISIBLE int
431 destroy_protocol_lws_messageboard(struct lws_context *context)
432 {
433         return 0;
434 }