1 /* -*- c-basic-offset: 8; -*- */
2 /* proto_http.c: Implementation of protocol HTTP.
3  *
4  *  Copyright (C) 2002-2004 the Icecast team <team@icecast.org>,
5  *  Copyright (C) 2012-2019 Philipp "ph3-der-loewe" Schafft <lion@lion.leolix.org>
6  *
7  *  This library is free software; you can redistribute it and/or
8  *  modify it under the terms of the GNU Library General Public
9  *  License as published by the Free Software Foundation; either
10  *  version 2 of the License, or (at your option) any later version.
11  *
12  *  This library is distributed in the hope that it will be useful,
13  *  but WITHOUT ANY WARRANTY; without even the implied warranty of
14  *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
15  *  Library General Public License for more details.
16  *
17  *  You should have received a copy of the GNU Library General Public
18  *  License along with this library; if not, write to the Free
19  *  Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
20  *
21  * $Id$
22  */
23 
24 #ifdef HAVE_CONFIG_H
25 #   include <config.h>
26 #endif
27 
28 #include <stdio.h>
29 #include <stdlib.h>
30 #include <string.h>
31 #ifdef HAVE_STRINGS_H
32 #   include <strings.h>
33 #endif
34 
35 #include <shout/shout.h>
36 #include "shout_private.h"
37 #include "common/httpp/httpp.h"
38 
39 typedef enum {
40     STATE_CHALLENGE = 0,
41     STATE_SOURCE,
42     STATE_UPGRADE,
43     STATE_POKE
44 } shout_http_protocol_state_t;
45 
shout_http_basic_authorization(shout_t * self)46 static char *shout_http_basic_authorization(shout_t *self)
47 {
48     char *out, *in;
49     int   len;
50 
51     if (!self || !self->user || !self->password)
52         return NULL;
53 
54     len = strlen(self->user) + strlen(self->password) + 2;
55     if (!(in = malloc(len)))
56         return NULL;
57     snprintf(in, len, "%s:%s", self->user, self->password);
58     out = _shout_util_base64_encode(in);
59     free(in);
60 
61     len = strlen(out) + 24;
62     if (!(in = malloc(len))) {
63         free(out);
64         return NULL;
65     }
66     snprintf(in, len, "Authorization: Basic %s\r\n", out);
67     free(out);
68 
69     return in;
70 }
71 
shout_parse_http_select_next_state(shout_t * self,shout_connection_t * connection,int can_reuse,shout_http_protocol_state_t state)72 static shout_connection_return_state_t shout_parse_http_select_next_state(shout_t *self, shout_connection_t *connection, int can_reuse, shout_http_protocol_state_t state)
73 {
74     if (!can_reuse) {
75         shout_connection_disconnect(connection);
76         shout_connection_connect(connection, self);
77     }
78     connection->current_message_state = SHOUT_MSGSTATE_CREATING0;
79     connection->target_message_state = SHOUT_MSGSTATE_SENDING1;
80     connection->current_protocol_state = state;
81     return SHOUT_RS_NOTNOW;
82 }
83 
shout_create_http_request_source(shout_t * self,shout_connection_t * connection,int auth,int poke)84 static shout_connection_return_state_t shout_create_http_request_source(shout_t *self, shout_connection_t *connection, int auth, int poke)
85 {
86     char        *basic_auth;
87     char        *ai;
88     int          ret = SHOUTERR_MALLOC;
89     util_dict   *dict;
90     const char  *key, *val;
91     const char  *mimetype;
92     char        *mount = NULL;
93 
94     mimetype = shout_get_mimetype_from_self(self);
95     if (!mimetype) {
96         shout_connection_set_error(connection, SHOUTERR_INSANE);
97         return SHOUT_RS_ERROR;
98     }
99 
100     /* this is lazy code that relies on the only error from queue_* being
101      * SHOUTERR_MALLOC
102      */
103     do {
104         if (!(mount = _shout_util_url_encode_resource(self->mount)))
105             break;
106         if (connection->server_caps & LIBSHOUT_CAP_PUT) {
107             if (shout_queue_printf(connection, "PUT %s HTTP/1.1\r\n", mount))
108                 break;
109         } else {
110             if (shout_queue_printf(connection, "SOURCE %s HTTP/1.0\r\n", mount))
111                 break;
112         }
113         if (self->password && auth) {
114             if (! (basic_auth = shout_http_basic_authorization(self)))
115                 break;
116             if (shout_queue_str(connection, basic_auth)) {
117                 free(basic_auth);
118                 break;
119             }
120             free(basic_auth);
121         }
122         if (shout_queue_printf(connection, "Host: %s:%i\r\n", self->host, self->port))
123             break;
124         if (self->useragent && shout_queue_printf(connection, "User-Agent: %s\r\n", self->useragent))
125             break;
126         if (shout_queue_printf(connection, "Content-Type: %s\r\n", mimetype))
127             break;
128         if (self->content_language && shout_queue_printf(connection, "Content-Language: %s\r\n", self->content_language))
129             break;
130         if (poke) {
131             if (shout_queue_str(connection, "Content-Length: 0\r\nConnection: Keep-Alive\r\n"))
132                 break;
133         } else if (connection->server_caps & LIBSHOUT_CAP_PUT) {
134             if (shout_queue_printf(connection, "Expect: 100-continue\r\n", mount))
135                 break;
136             /* Set timeout for 100-continue to 4s = 4000 ms. This is a little less than the default source_timeout/2. */
137             shout_connection_set_wait_timeout(connection, self, 4000 /* [ms] */);
138         }
139         if (shout_queue_printf(connection, "ice-public: %d\r\n", self->public))
140             break;
141 
142         _SHOUT_DICT_FOREACH(self->meta, dict, key, val) {
143             if (val && shout_queue_printf(connection, "ice-%s: %s\r\n", key, val))
144                 break;
145         }
146 
147         if ((ai = _shout_util_dict_urlencode(self->audio_info, ';'))) {
148             if (shout_queue_printf(connection, "ice-audio-info: %s\r\n", ai)) {
149                 free(ai);
150                 break;
151             }
152             free(ai);
153         }
154         if (shout_queue_str(connection, "Prefer: return=minimal\r\n"))
155             break;
156         if (shout_queue_str(connection, "\r\n"))
157             break;
158 
159         ret = SHOUTERR_SUCCESS;
160     } while (0);
161 
162     if (mount)
163         free(mount);
164 
165     shout_connection_set_error(connection, ret);
166     return ret == SHOUTERR_SUCCESS ? SHOUT_RS_DONE : SHOUT_RS_ERROR;
167 }
168 
shout_create_http_request_generic(shout_t * self,shout_connection_t * connection,const char * method,const char * res,const char * param,int fake_ua,const char * upgrade,int auth)169 static shout_connection_return_state_t shout_create_http_request_generic(shout_t *self, shout_connection_t *connection, const char *method, const char *res, const char *param, int fake_ua, const char *upgrade, int auth)
170 {
171     int          ret = SHOUTERR_MALLOC;
172     int          is_post = 0;
173     char        *basic_auth;
174 
175     if (method) {
176         is_post = strcmp(method, "POST") == 0;
177     } else {
178         if (connection->server_caps & LIBSHOUT_CAP_POST) {
179             method = "POST";
180             is_post = 1;
181         } else {
182             method = "GET";
183             is_post = 0;
184         }
185     }
186 
187     /* this is lazy code that relies on the only error from queue_* being
188      * SHOUTERR_MALLOC
189      */
190     do {
191         ret = SHOUTERR_SUCCESS;
192 
193         if (!param || is_post) {
194             if (shout_queue_printf(connection, "%s %s HTTP/1.1\r\n", method, res))
195                 break;
196         } else {
197             if (shout_queue_printf(connection, "%s %s?%s HTTP/1.1\r\n", method, res, param))
198                 break;
199         }
200 
201         /* Send Host:-header as this one may be used to select cert! */
202         if (shout_queue_printf(connection, "Host: %s:%i\r\n", self->host, self->port))
203             break;
204 
205         if (fake_ua) {
206             /* Thank you Nullsoft for your broken software. */
207             if (self->useragent && shout_queue_printf(connection, "User-Agent: %s (Mozilla compatible)\r\n", self->useragent))
208                 break;
209         } else {
210             if (self->useragent && shout_queue_printf(connection, "User-Agent: %s\r\n", self->useragent))
211                 break;
212         }
213 
214         if (self->password && auth) {
215             if (! (basic_auth = shout_http_basic_authorization(self)))
216                 break;
217             if (shout_queue_str(connection, basic_auth)) {
218                 free(basic_auth);
219                 break;
220             }
221             free(basic_auth);
222         }
223 
224         if (upgrade) {
225             if (shout_queue_printf(connection, "Connection: Upgrade\r\nUpgrade: %s\r\n", upgrade))
226                 break;
227         }
228 
229         if (param && is_post) {
230             if (shout_queue_printf(connection, "Content-Type: application/x-www-form-urlencoded\r\nContent-Length: %llu\r\n", (long long unsigned int)strlen(param)))
231                 break;
232         }
233 
234         /* End of request */
235         if (shout_queue_str(connection, "\r\n"))
236             break;
237         if (param && is_post) {
238             if (shout_queue_str(connection, param))
239                 break;
240         }
241     } while (0);
242 
243     shout_connection_set_error(connection, ret);
244     return ret == SHOUTERR_SUCCESS ? SHOUT_RS_DONE : SHOUT_RS_ERROR;
245 }
246 
shout_create_http_request(shout_t * self,shout_connection_t * connection)247 static shout_connection_return_state_t shout_create_http_request(shout_t *self, shout_connection_t *connection)
248 {
249     const shout_http_plan_t *plan = connection->plan;
250 
251     if (!plan) {
252         shout_connection_set_error(connection, SHOUTERR_INSANE);
253         return SHOUT_RS_ERROR;
254     }
255 
256 #ifdef HAVE_OPENSSL
257     if (!connection->tls) {
258         /* Why not try Upgrade? */
259         if ((connection->selected_tls_mode == SHOUT_TLS_AUTO || connection->selected_tls_mode == SHOUT_TLS_AUTO_NO_PLAIN) &&
260                 !(connection->server_caps & LIBSHOUT_CAP_GOTCAPS) &&
261                 connection->current_protocol_state == STATE_CHALLENGE) {
262             connection->current_protocol_state = STATE_UPGRADE;
263         }
264 
265         if (connection->selected_tls_mode == SHOUT_TLS_RFC2817) {
266             connection->current_protocol_state = STATE_UPGRADE;
267         }
268     }
269 #endif
270 
271     switch ((shout_http_protocol_state_t)connection->current_protocol_state) {
272         case STATE_CHALLENGE:
273             connection->server_caps |= LIBSHOUT_CAP_CHALLENGED;
274             if (plan->is_source) {
275                 return shout_create_http_request_source(self, connection, 0, 1);
276             } else {
277                 return shout_create_http_request_generic(self, connection, plan->method, plan->resource, plan->param, plan->fake_ua, NULL, 0);
278             }
279         break;
280         case STATE_SOURCE:
281             /* Just an extra layer of safety */
282             switch (connection->selected_tls_mode) {
283                 case SHOUT_TLS_AUTO_NO_PLAIN:
284                 case SHOUT_TLS_RFC2817:
285                 case SHOUT_TLS_RFC2818:
286 #ifdef HAVE_OPENSSL
287                     if (!connection->tls) {
288                         /* TLS requested but for some reason not established. NOT sending credentials. */
289                         shout_connection_set_error(connection, SHOUTERR_INSANE);
290                         return SHOUT_RS_ERROR;
291                     }
292 #else
293                     shout_connection_set_error(connection, SHOUTERR_UNSUPPORTED);
294                     return SHOUT_RS_ERROR;
295 #endif
296                 break;
297             }
298 
299             if (plan->is_source) {
300                 return shout_create_http_request_source(self, connection, 1, 0);
301             } else {
302                 return shout_create_http_request_generic(self, connection, plan->method, plan->resource, plan->param, plan->fake_ua, NULL, plan->auth);
303             }
304         break;
305         case STATE_UPGRADE:
306             return shout_create_http_request_generic(self, connection, "OPTIONS", "*", NULL, 0, "TLS/1.0, HTTP/1.1", 0);
307         break;
308         case STATE_POKE:
309             return shout_create_http_request_generic(self, connection, "GET", "/admin/!POKE", NULL, 0, NULL, 0);
310         break;
311         default:
312             shout_connection_set_error(connection, SHOUTERR_INSANE);
313             return SHOUT_RS_ERROR;
314         break;
315     }
316 }
317 
shout_get_http_response(shout_t * self,shout_connection_t * connection)318 static shout_connection_return_state_t shout_get_http_response(shout_t *self, shout_connection_t *connection)
319 {
320     int          blen;
321     char        *pc;
322     shout_buf_t *queue;
323     int          newlines = 0;
324 
325     if (!connection->rqueue.len) {
326 #ifdef HAVE_OPENSSL
327         if (!connection->tls && (connection->selected_tls_mode == SHOUT_TLS_AUTO || connection->selected_tls_mode == SHOUT_TLS_AUTO_NO_PLAIN)) {
328             if (connection->current_protocol_state == STATE_POKE) {
329                 shout_connection_select_tlsmode(connection, SHOUT_TLS_RFC2818);
330                 return shout_parse_http_select_next_state(self, connection, 0, STATE_CHALLENGE);
331             } else {
332                 return shout_parse_http_select_next_state(self, connection, 0, STATE_POKE);
333             }
334         }
335 #endif
336         shout_connection_set_error(connection, SHOUTERR_SOCKET);
337         return SHOUT_RS_ERROR;
338     }
339 
340     /* work from the back looking for \r?\n\r?\n. Anything else means more
341      * is coming.
342      */
343     for (queue = connection->rqueue.head; queue->next; queue = queue->next) ;
344     pc = (char*)queue->data + queue->len - 1;
345     blen = queue->len;
346     while (blen) {
347         if (*pc == '\n') {
348             newlines++;
349         } else if (*pc != '\r') {
350             /* we may have to scan the entire queue if we got a response with
351              * data after the head line (this can happen with eg 401)
352              */
353             newlines = 0;
354         }
355 
356         if (newlines == 2) {
357             return SHOUT_RS_DONE;
358         }
359 
360         blen--;
361         pc--;
362 
363         if (!blen && queue->prev) {
364             queue = queue->prev;
365             pc = (char*)queue->data + queue->len - 1;
366             blen = queue->len;
367         }
368     }
369 
370     return SHOUT_RS_NOTNOW;
371 }
372 
parse_http_response_caps(shout_t * self,shout_connection_t * connection,const char * header,const char * str)373 static inline void parse_http_response_caps(shout_t *self, shout_connection_t *connection, const char *header, const char *str) {
374     const char *end;
375     size_t      len;
376     char        buf[64];
377 
378     if (!self || !header || !str)
379         return;
380 
381     do {
382         for (; *str == ' '; str++) ;
383         end = strstr(str, ",");
384         if (end) {
385             len = end - str;
386         } else {
387             len = strlen(str);
388         }
389 
390         if (len > (sizeof(buf) - 1))
391             return;
392         memcpy(buf, str, len);
393         buf[len] = 0;
394 
395         if (strcmp(header, "Allow") == 0) {
396             if (strcasecmp(buf, "SOURCE") == 0) {
397                 connection->server_caps |= LIBSHOUT_CAP_SOURCE;
398             } else if (strcasecmp(buf, "PUT") == 0) {
399                 connection->server_caps |= LIBSHOUT_CAP_PUT;
400             } else if (strcasecmp(buf, "POST") == 0) {
401                 connection->server_caps |= LIBSHOUT_CAP_POST;
402             } else if (strcasecmp(buf, "GET") == 0) {
403                 connection->server_caps |= LIBSHOUT_CAP_GET;
404             } else if (strcasecmp(buf, "OPTIONS") == 0) {
405                 connection->server_caps |= LIBSHOUT_CAP_OPTIONS;
406             }
407         } else if (strcmp(header, "Accept-Encoding") == 0) {
408             if (strcasecmp(buf, "chunked") == 0) {
409                 connection->server_caps |= LIBSHOUT_CAP_CHUNKED;
410             }
411         } else if (strcmp(header, "Upgrade") == 0) {
412             if (strcasecmp(buf, "TLS/1.0") == 0) {
413                 connection->server_caps |= LIBSHOUT_CAP_UPGRADETLS;
414             }
415         } else {
416             return;             /* unknown header */
417         }
418 
419         str += len + 1;
420     } while (end);
421 
422     return;
423 }
424 
eat_body(shout_t * self,shout_connection_t * connection,size_t len,const char * buf,size_t buflen)425 static inline int eat_body(shout_t *self, shout_connection_t *connection, size_t len, const char *buf, size_t buflen)
426 {
427     const char  *p;
428     size_t       header_len = 0;
429     char         buffer[256];
430     ssize_t      got;
431 
432     if (!len)
433         return 0;
434 
435     for (p = buf; p < (buf + buflen - 3); p++) {
436         if (p[0] == '\r' && p[1] == '\n' && p[2] == '\r' && p[3] == '\n') {
437             header_len = p - buf + 4;
438             break;
439         } else if (p[0] == '\n' && p[1] == '\n') {
440             header_len = p - buf + 2;
441             break;
442         }
443     }
444     if (!header_len && buflen >= 3 && buf[buflen - 2] == '\n' && buf[buflen - 3] == '\n') {
445         header_len = buflen - 1;
446     } else if (!header_len && buflen >= 2 && buf[buflen - 1] == '\n' && buf[buflen - 2] == '\n') {
447         header_len = buflen;
448     }
449 
450     if ((buflen - header_len) > len)
451         return -1;
452 
453     len -= buflen - header_len;
454 
455     while (len) {
456         got = shout_connection__read(connection, self, buffer, len > sizeof(buffer) ? sizeof(buffer) : len);
457         if (got == -1 && shout_connection__recoverable(connection, self)) {
458             continue;
459         } else if (got == -1) {
460             return -1;
461         }
462 
463         len -= got;
464     }
465 
466     return 0;
467 }
468 
shout_parse_http_response(shout_t * self,shout_connection_t * connection)469 static shout_connection_return_state_t shout_parse_http_response(shout_t *self, shout_connection_t *connection)
470 {
471     http_parser_t   *parser;
472     char            *header = NULL;
473     ssize_t          hlen;
474     int              code;
475     const char      *retcode;
476     int              ret;
477     char            *mount;
478     int              consider_retry = 0;
479     int              can_reuse = 0;
480 #ifdef HAVE_STRCASESTR
481     const char      *tmp;
482 #endif
483 
484     /* all this copying! */
485     hlen = shout_queue_collect(connection->rqueue.head, &header);
486     if (hlen <= 0) {
487         if (connection->current_protocol_state == STATE_SOURCE && shout_connection_get_wait_timeout_happened(connection, self) > 0) {
488             connection->current_message_state = SHOUT_MSGSTATE_SENDING1;
489             connection->target_message_state = SHOUT_MSGSTATE_WAITING1;
490             return SHOUT_RS_DONE;
491         } else {
492             shout_connection_set_error(connection, SHOUTERR_MALLOC);
493             return SHOUT_RS_ERROR;
494         }
495     }
496     shout_queue_free(&connection->rqueue);
497 
498     parser = httpp_create_parser();
499     httpp_initialize(parser, NULL);
500 
501     if (!(mount = _shout_util_url_encode(self->mount))) {
502         httpp_destroy(parser);
503         free(header);
504         shout_connection_set_error(connection, SHOUTERR_MALLOC);
505         return SHOUT_RS_ERROR;
506     }
507 
508     ret = httpp_parse_response(parser, header, hlen, mount);
509     free(mount);
510 
511     if (ret) {
512         /* TODO: Headers to Handle:
513          * Allow:, Accept-Encoding:, Warning:, Upgrade:
514          */
515         parse_http_response_caps(self, connection, "Allow", httpp_getvar(parser, "allow"));
516         parse_http_response_caps(self, connection, "Accept-Encoding", httpp_getvar(parser, "accept-encoding"));
517         parse_http_response_caps(self, connection, "Upgrade", httpp_getvar(parser, "upgrade"));
518         connection->server_caps |= LIBSHOUT_CAP_GOTCAPS;
519         retcode = httpp_getvar(parser, HTTPP_VAR_ERROR_CODE);
520         code = atoi(retcode);
521 
522 #ifdef HAVE_STRCASESTR
523         tmp = httpp_getvar(parser, HTTPP_VAR_VERSION);
524         if (tmp && strcmp(tmp, "1.1") == 0) {
525             can_reuse = 1;
526         }
527         tmp = httpp_getvar(parser, "connection");
528         if (tmp && strcasestr(tmp, "keep-alive")) {
529             can_reuse = 1;
530         }
531         if (tmp && strcasestr(tmp, "close")) {
532             can_reuse = 0;
533         }
534 #else
535         /* get a real OS */
536         can_reuse = 0;
537 #endif
538 
539         if ((code == 100 || (code >= 200 && code < 300)) && connection->current_protocol_state == STATE_SOURCE) {
540             httpp_destroy(parser);
541             free(header);
542             connection->current_message_state = SHOUT_MSGSTATE_SENDING1;
543             connection->target_message_state = SHOUT_MSGSTATE_WAITING1;
544             return SHOUT_RS_DONE;
545         } else if ((code >= 200 && code < 300) || code == 400 || code == 401 || code == 405 || code == 426 || code == 101) {
546             const char *content_length = httpp_getvar(parser, "content-length");
547             if (content_length) {
548                 if (eat_body(self, connection, atoi(content_length), header, hlen) == -1) {
549                     can_reuse = 0;
550                     goto failure;
551                 }
552             }
553 #ifdef HAVE_OPENSSL
554             switch (code) {
555                 case 400:
556                     if (connection->current_protocol_state != STATE_UPGRADE && connection->current_protocol_state != STATE_POKE) {
557                         free(header);
558                         httpp_destroy(parser);
559                         shout_connection_set_error(connection, SHOUTERR_NOLOGIN);
560                         return SHOUT_RS_ERROR;
561                     }
562                     if (connection->selected_tls_mode == SHOUT_TLS_AUTO_NO_PLAIN) {
563                         can_reuse = 0;
564                         shout_connection_select_tlsmode(connection, SHOUT_TLS_RFC2818);
565                     }
566                 break;
567 
568                 case 426:
569                     if (connection->tls) {
570                         free(header);
571                         httpp_destroy(parser);
572                         shout_connection_set_error(connection, SHOUTERR_NOLOGIN);
573                         return SHOUT_RS_ERROR;
574                     } else if (connection->selected_tls_mode == SHOUT_TLS_DISABLED) {
575                         free(header);
576                         httpp_destroy(parser);
577                         shout_connection_set_error(connection, SHOUTERR_NOCONNECT);
578                         return SHOUT_RS_ERROR;
579                     } else {
580                         /* Reset challenge state here as we do not know if it's the same inside TLS */
581                         connection->server_caps |= LIBSHOUT_CAP_CHALLENGED;
582                         connection->server_caps -= LIBSHOUT_CAP_CHALLENGED;
583                         shout_connection_select_tlsmode(connection, SHOUT_TLS_RFC2817);
584                         free(header);
585                         httpp_destroy(parser);
586                         return shout_parse_http_select_next_state(self, connection, can_reuse, STATE_UPGRADE);
587                     }
588                 break;
589 
590                 case 101:
591                     shout_connection_select_tlsmode(connection, SHOUT_TLS_RFC2817);
592                     shout_connection_starttls(connection, self);
593                 break;
594             }
595 #endif
596             consider_retry = 1;
597         }
598 
599         if (code >= 100 && code < 200) {
600             connection->current_message_state = SHOUT_MSGSTATE_WAITING0;
601             return SHOUT_RS_NOTNOW;
602         }
603     }
604 
605 failure:
606     free(header);
607     httpp_destroy(parser);
608 
609     if (consider_retry) {
610         switch ((shout_http_protocol_state_t)connection->current_protocol_state) {
611             case STATE_CHALLENGE:
612                 return shout_parse_http_select_next_state(self, connection, can_reuse, STATE_SOURCE);
613             break;
614             case STATE_UPGRADE:
615             case STATE_POKE:
616                 if (connection->server_caps & LIBSHOUT_CAP_CHALLENGED) {
617                     return shout_parse_http_select_next_state(self, connection, can_reuse, STATE_SOURCE);
618                 } else {
619                     return shout_parse_http_select_next_state(self, connection, can_reuse, STATE_CHALLENGE);
620                 }
621             break;
622             case STATE_SOURCE:
623                 /* no-op */
624             break;
625         }
626     }
627     shout_connection_set_error(connection, SHOUTERR_NOLOGIN);
628     return SHOUT_RS_ERROR;
629 }
630 
631 static const shout_protocol_impl_t shout_http_impl_real = {
632     .msg_create = shout_create_http_request,
633     .msg_get = shout_get_http_response,
634     .msg_parse = shout_parse_http_response
635 };
636 const shout_protocol_impl_t * shout_http_impl = &shout_http_impl_real;
637