1 /* GStreamer
2  * Copyright (C) <2005,2006> Wim Taymans <wim@fluendo.com>
3  *               <2006> Lutz Mueller <lutz at topfrose dot de>
4  *               <2015> Tim-Philipp Müller <tim@centricular.com>
5  *
6  * This library is free software; you can redistribute it and/or
7  * modify it under the terms of the GNU Library General Public
8  * License as published by the Free Software Foundation; either
9  * version 2 of the License, or (at your option) any later version.
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  * Library General Public License for more details.
15  *
16  * You should have received a copy of the GNU Library General Public
17  * License along with this library; if not, write to the
18  * Free Software Foundation, Inc., 51 Franklin St, Fifth Floor,
19  * Boston, MA 02110-1301, USA.
20  */
21 /*
22  * Unless otherwise indicated, Source Code is licensed under MIT license.
23  * See further explanation attached in License Statement (distributed in the file
24  * LICENSE).
25  *
26  * Permission is hereby granted, free of charge, to any person obtaining a copy of
27  * this software and associated documentation files (the "Software"), to deal in
28  * the Software without restriction, including without limitation the rights to
29  * use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies
30  * of the Software, and to permit persons to whom the Software is furnished to do
31  * so, subject to the following conditions:
32  *
33  * The above copyright notice and this permission notice shall be included in all
34  * copies or substantial portions of the Software.
35  *
36  * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
37  * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
38  * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
39  * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
40  * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
41  * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
42  * SOFTWARE.
43  */
44 
45 /**
46  * SECTION:gstrtspmessage
47  * @title: GstRTSPMessage
48  * @short_description: RTSP messages
49  * @see_also: gstrtspconnection
50  *
51  * Provides methods for creating and parsing request, response and data messages.
52  */
53 #ifdef HAVE_CONFIG_H
54 #include "config.h"
55 #endif
56 
57 #include <string.h>
58 
59 #include <gst/gstutils.h>
60 #include "gstrtspmessage.h"
61 
62 typedef struct _RTSPKeyValue
63 {
64   GstRTSPHeaderField field;
65   gchar *value;
66   gchar *custom_key;            /* custom header string (field is INVALID then) */
67 } RTSPKeyValue;
68 
69 static void
key_value_foreach(GArray * array,GFunc func,gpointer user_data)70 key_value_foreach (GArray * array, GFunc func, gpointer user_data)
71 {
72   guint i;
73 
74   g_return_if_fail (array != NULL);
75 
76   for (i = 0; i < array->len; i++) {
77     (*func) (&g_array_index (array, RTSPKeyValue, i), user_data);
78   }
79 }
80 
81 static void
key_value_append(const RTSPKeyValue * kv,GArray * array)82 key_value_append (const RTSPKeyValue * kv, GArray * array)
83 {
84   RTSPKeyValue kvcopy;
85   g_return_if_fail (kv != NULL);
86   g_return_if_fail (array != NULL);
87 
88   kvcopy.field = kv->field;
89   kvcopy.value = g_strdup (kv->value);
90   kvcopy.custom_key = g_strdup (kv->custom_key);
91 
92   g_array_append_val (array, kvcopy);
93 }
94 
95 static GstRTSPMessage *
gst_rtsp_message_boxed_copy(GstRTSPMessage * orig)96 gst_rtsp_message_boxed_copy (GstRTSPMessage * orig)
97 {
98   GstRTSPMessage *copy;
99 
100   if (gst_rtsp_message_copy (orig, &copy) == GST_RTSP_OK)
101     return copy;
102 
103   return NULL;
104 }
105 
106 static void
gst_rtsp_message_boxed_free(GstRTSPMessage * msg)107 gst_rtsp_message_boxed_free (GstRTSPMessage * msg)
108 {
109   gst_rtsp_message_free (msg);
110 }
111 
112 G_DEFINE_BOXED_TYPE (GstRTSPMessage, gst_rtsp_msg, gst_rtsp_message_boxed_copy,
113     gst_rtsp_message_boxed_free);
114 
115 /**
116  * gst_rtsp_message_new:
117  * @msg: (out) (transfer full): a location for the new #GstRTSPMessage
118  *
119  * Create a new initialized #GstRTSPMessage. Free with gst_rtsp_message_free().
120  *
121  * Returns: a #GstRTSPResult.
122  */
123 GstRTSPResult
gst_rtsp_message_new(GstRTSPMessage ** msg)124 gst_rtsp_message_new (GstRTSPMessage ** msg)
125 {
126   GstRTSPMessage *newmsg;
127 
128   g_return_val_if_fail (msg != NULL, GST_RTSP_EINVAL);
129 
130   newmsg = g_new0 (GstRTSPMessage, 1);
131 
132   *msg = newmsg;
133 
134   return gst_rtsp_message_init (newmsg);
135 }
136 
137 /**
138  * gst_rtsp_message_init:
139  * @msg: a #GstRTSPMessage
140  *
141  * Initialize @msg. This function is mostly used when @msg is allocated on the
142  * stack. The reverse operation of this is gst_rtsp_message_unset().
143  *
144  * Returns: a #GstRTSPResult.
145  */
146 GstRTSPResult
gst_rtsp_message_init(GstRTSPMessage * msg)147 gst_rtsp_message_init (GstRTSPMessage * msg)
148 {
149   g_return_val_if_fail (msg != NULL, GST_RTSP_EINVAL);
150 
151   gst_rtsp_message_unset (msg);
152 
153   msg->type = GST_RTSP_MESSAGE_INVALID;
154   msg->hdr_fields = g_array_new (FALSE, FALSE, sizeof (RTSPKeyValue));
155 
156   return GST_RTSP_OK;
157 }
158 
159 /**
160  * gst_rtsp_message_get_type:
161  * @msg: a #GstRTSPMessage
162  *
163  * Get the message type of @msg.
164  *
165  * Returns: the message type.
166  */
167 GstRTSPMsgType
gst_rtsp_message_get_type(GstRTSPMessage * msg)168 gst_rtsp_message_get_type (GstRTSPMessage * msg)
169 {
170   g_return_val_if_fail (msg != NULL, GST_RTSP_MESSAGE_INVALID);
171 
172   return msg->type;
173 }
174 
175 /**
176  * gst_rtsp_message_new_request:
177  * @msg: (out) (transfer full): a location for the new #GstRTSPMessage
178  * @method: the request method to use
179  * @uri: (transfer none): the uri of the request
180  *
181  * Create a new #GstRTSPMessage with @method and @uri and store the result
182  * request message in @msg. Free with gst_rtsp_message_free().
183  *
184  * Returns: a #GstRTSPResult.
185  */
186 GstRTSPResult
gst_rtsp_message_new_request(GstRTSPMessage ** msg,GstRTSPMethod method,const gchar * uri)187 gst_rtsp_message_new_request (GstRTSPMessage ** msg, GstRTSPMethod method,
188     const gchar * uri)
189 {
190   GstRTSPMessage *newmsg;
191 
192   g_return_val_if_fail (msg != NULL, GST_RTSP_EINVAL);
193   g_return_val_if_fail (uri != NULL, GST_RTSP_EINVAL);
194 
195   newmsg = g_new0 (GstRTSPMessage, 1);
196 
197   *msg = newmsg;
198 
199   return gst_rtsp_message_init_request (newmsg, method, uri);
200 }
201 
202 /**
203  * gst_rtsp_message_init_request:
204  * @msg: a #GstRTSPMessage
205  * @method: the request method to use
206  * @uri: (transfer none): the uri of the request
207  *
208  * Initialize @msg as a request message with @method and @uri. To clear @msg
209  * again, use gst_rtsp_message_unset().
210  *
211  * Returns: a #GstRTSPResult.
212  */
213 GstRTSPResult
gst_rtsp_message_init_request(GstRTSPMessage * msg,GstRTSPMethod method,const gchar * uri)214 gst_rtsp_message_init_request (GstRTSPMessage * msg, GstRTSPMethod method,
215     const gchar * uri)
216 {
217   g_return_val_if_fail (msg != NULL, GST_RTSP_EINVAL);
218   g_return_val_if_fail (uri != NULL, GST_RTSP_EINVAL);
219 
220   gst_rtsp_message_unset (msg);
221 
222   msg->type = GST_RTSP_MESSAGE_REQUEST;
223   msg->type_data.request.method = method;
224   msg->type_data.request.uri = g_strdup (uri);
225   msg->type_data.request.version = GST_RTSP_VERSION_1_0;
226   msg->hdr_fields = g_array_new (FALSE, FALSE, sizeof (RTSPKeyValue));
227 
228   return GST_RTSP_OK;
229 }
230 
231 /**
232  * gst_rtsp_message_parse_request:
233  * @msg: a #GstRTSPMessage
234  * @method: (out) (allow-none): location to hold the method
235  * @uri: (out) (allow-none) (transfer none): location to hold the uri
236  * @version: (out) (allow-none) (transfer none): location to hold the version
237  *
238  * Parse the request message @msg and store the values @method, @uri and
239  * @version. The result locations can be %NULL if one is not interested in its
240  * value.
241  *
242  * @uri remains valid for as long as @msg is valid and unchanged.
243  *
244  * Returns: a #GstRTSPResult.
245  */
246 GstRTSPResult
gst_rtsp_message_parse_request(GstRTSPMessage * msg,GstRTSPMethod * method,const gchar ** uri,GstRTSPVersion * version)247 gst_rtsp_message_parse_request (GstRTSPMessage * msg,
248     GstRTSPMethod * method, const gchar ** uri, GstRTSPVersion * version)
249 {
250   g_return_val_if_fail (msg != NULL, GST_RTSP_EINVAL);
251   g_return_val_if_fail (msg->type == GST_RTSP_MESSAGE_REQUEST ||
252       msg->type == GST_RTSP_MESSAGE_HTTP_REQUEST, GST_RTSP_EINVAL);
253 
254   if (method)
255     *method = msg->type_data.request.method;
256   if (uri)
257     *uri = msg->type_data.request.uri;
258   if (version)
259     *version = msg->type_data.request.version;
260 
261   return GST_RTSP_OK;
262 }
263 
264 /**
265  * gst_rtsp_message_new_response:
266  * @msg: (out) (transfer full): a location for the new #GstRTSPMessage
267  * @code: the status code
268  * @reason: (transfer none) (allow-none): the status reason or %NULL
269  * @request: (transfer none) (allow-none): the request that triggered the response or %NULL
270  *
271  * Create a new response #GstRTSPMessage with @code and @reason and store the
272  * result message in @msg. Free with gst_rtsp_message_free().
273  *
274  * When @reason is %NULL, the default reason for @code will be used.
275  *
276  * When @request is not %NULL, the relevant headers will be copied to the new
277  * response message.
278  *
279  * Returns: a #GstRTSPResult.
280  */
281 GstRTSPResult
gst_rtsp_message_new_response(GstRTSPMessage ** msg,GstRTSPStatusCode code,const gchar * reason,const GstRTSPMessage * request)282 gst_rtsp_message_new_response (GstRTSPMessage ** msg, GstRTSPStatusCode code,
283     const gchar * reason, const GstRTSPMessage * request)
284 {
285   GstRTSPMessage *newmsg;
286 
287   g_return_val_if_fail (msg != NULL, GST_RTSP_EINVAL);
288 
289   newmsg = g_new0 (GstRTSPMessage, 1);
290 
291   *msg = newmsg;
292 
293   return gst_rtsp_message_init_response (newmsg, code, reason, request);
294 }
295 
296 /**
297  * gst_rtsp_message_init_response:
298  * @msg: a #GstRTSPMessage
299  * @code: the status code
300  * @reason: (transfer none) (allow-none): the status reason or %NULL
301  * @request: (transfer none) (allow-none): the request that triggered the response or %NULL
302  *
303  * Initialize @msg with @code and @reason.
304  *
305  * When @reason is %NULL, the default reason for @code will be used.
306  *
307  * When @request is not %NULL, the relevant headers will be copied to the new
308  * response message.
309  *
310  * Returns: a #GstRTSPResult.
311  */
312 GstRTSPResult
gst_rtsp_message_init_response(GstRTSPMessage * msg,GstRTSPStatusCode code,const gchar * reason,const GstRTSPMessage * request)313 gst_rtsp_message_init_response (GstRTSPMessage * msg, GstRTSPStatusCode code,
314     const gchar * reason, const GstRTSPMessage * request)
315 {
316   g_return_val_if_fail (msg != NULL, GST_RTSP_EINVAL);
317 
318   gst_rtsp_message_unset (msg);
319 
320   if (reason == NULL)
321     reason = gst_rtsp_status_as_text (code);
322 
323   msg->type = GST_RTSP_MESSAGE_RESPONSE;
324   msg->type_data.response.code = code;
325   msg->type_data.response.reason = g_strdup (reason);
326   msg->type_data.response.version = GST_RTSP_VERSION_1_0;
327   msg->hdr_fields = g_array_new (FALSE, FALSE, sizeof (RTSPKeyValue));
328 
329   if (request) {
330     if (request->type == GST_RTSP_MESSAGE_HTTP_REQUEST) {
331       msg->type = GST_RTSP_MESSAGE_HTTP_RESPONSE;
332       if (request->type_data.request.version != GST_RTSP_VERSION_INVALID)
333         msg->type_data.response.version = request->type_data.request.version;
334       else
335         msg->type_data.response.version = GST_RTSP_VERSION_1_1;
336     } else {
337       gchar *header;
338 
339       /* copy CSEQ */
340       if (gst_rtsp_message_get_header (request, GST_RTSP_HDR_CSEQ, &header,
341               0) == GST_RTSP_OK) {
342         gst_rtsp_message_add_header (msg, GST_RTSP_HDR_CSEQ, header);
343       }
344 
345       /* copy session id */
346       if (gst_rtsp_message_get_header (request, GST_RTSP_HDR_SESSION, &header,
347               0) == GST_RTSP_OK) {
348         char *pos;
349 
350         header = g_strdup (header);
351         if ((pos = strchr (header, ';'))) {
352           *pos = '\0';
353         }
354         g_strchomp (header);
355         gst_rtsp_message_take_header (msg, GST_RTSP_HDR_SESSION, header);
356       }
357 
358       /* FIXME copy more headers? */
359     }
360   }
361 
362   return GST_RTSP_OK;
363 }
364 
365 /**
366  * gst_rtsp_message_parse_response:
367  * @msg: a #GstRTSPMessage
368  * @code: (out) (allow-none): location to hold the status code
369  * @reason: (out) (allow-none) (transfer none): location to hold the status reason
370  * @version: (out) (allow-none) (transfer none): location to hold the version
371  *
372  * Parse the response message @msg and store the values @code, @reason and
373  * @version. The result locations can be %NULL if one is not interested in its
374  * value.
375  *
376  * @reason remains valid for as long as @msg is valid and unchanged.
377  *
378  * Returns: a #GstRTSPResult.
379  */
380 GstRTSPResult
gst_rtsp_message_parse_response(GstRTSPMessage * msg,GstRTSPStatusCode * code,const gchar ** reason,GstRTSPVersion * version)381 gst_rtsp_message_parse_response (GstRTSPMessage * msg,
382     GstRTSPStatusCode * code, const gchar ** reason, GstRTSPVersion * version)
383 {
384   g_return_val_if_fail (msg != NULL, GST_RTSP_EINVAL);
385   g_return_val_if_fail (msg->type == GST_RTSP_MESSAGE_RESPONSE ||
386       msg->type == GST_RTSP_MESSAGE_HTTP_RESPONSE, GST_RTSP_EINVAL);
387 
388   if (code)
389     *code = msg->type_data.response.code;
390   if (reason)
391     *reason = msg->type_data.response.reason;
392   if (version)
393     *version = msg->type_data.response.version;
394 
395   return GST_RTSP_OK;
396 }
397 
398 /**
399  * gst_rtsp_message_new_data:
400  * @msg: (out) (transfer full): a location for the new #GstRTSPMessage
401  * @channel: the channel
402  *
403  * Create a new data #GstRTSPMessage with @channel and store the
404  * result message in @msg. Free with gst_rtsp_message_free().
405  *
406  * Returns: a #GstRTSPResult.
407  */
408 GstRTSPResult
gst_rtsp_message_new_data(GstRTSPMessage ** msg,guint8 channel)409 gst_rtsp_message_new_data (GstRTSPMessage ** msg, guint8 channel)
410 {
411   GstRTSPMessage *newmsg;
412 
413   g_return_val_if_fail (msg != NULL, GST_RTSP_EINVAL);
414 
415   newmsg = g_new0 (GstRTSPMessage, 1);
416 
417   *msg = newmsg;
418 
419   return gst_rtsp_message_init_data (newmsg, channel);
420 }
421 
422 /**
423  * gst_rtsp_message_init_data:
424  * @msg: a #GstRTSPMessage
425  * @channel: a channel
426  *
427  * Initialize a new data #GstRTSPMessage for @channel.
428  *
429  * Returns: a #GstRTSPResult.
430  */
431 GstRTSPResult
gst_rtsp_message_init_data(GstRTSPMessage * msg,guint8 channel)432 gst_rtsp_message_init_data (GstRTSPMessage * msg, guint8 channel)
433 {
434   g_return_val_if_fail (msg != NULL, GST_RTSP_EINVAL);
435 
436   gst_rtsp_message_unset (msg);
437 
438   msg->type = GST_RTSP_MESSAGE_DATA;
439   msg->type_data.data.channel = channel;
440 
441   return GST_RTSP_OK;
442 }
443 
444 /**
445  * gst_rtsp_message_parse_data:
446  * @msg: a #GstRTSPMessage
447  * @channel: (out): location to hold the channel
448  *
449  * Parse the data message @msg and store the channel in @channel.
450  *
451  * Returns: a #GstRTSPResult.
452  */
453 GstRTSPResult
gst_rtsp_message_parse_data(GstRTSPMessage * msg,guint8 * channel)454 gst_rtsp_message_parse_data (GstRTSPMessage * msg, guint8 * channel)
455 {
456   g_return_val_if_fail (msg != NULL, GST_RTSP_EINVAL);
457   g_return_val_if_fail (msg->type == GST_RTSP_MESSAGE_DATA, GST_RTSP_EINVAL);
458 
459   if (channel)
460     *channel = msg->type_data.data.channel;
461 
462   return GST_RTSP_OK;
463 }
464 
465 /**
466  * gst_rtsp_message_unset:
467  * @msg: a #GstRTSPMessage
468  *
469  * Unset the contents of @msg so that it becomes an uninitialized
470  * #GstRTSPMessage again. This function is mostly used in combination with
471  * gst_rtsp_message_init_request(), gst_rtsp_message_init_response() and
472  * gst_rtsp_message_init_data() on stack allocated #GstRTSPMessage structures.
473  *
474  * Returns: #GST_RTSP_OK.
475  */
476 GstRTSPResult
gst_rtsp_message_unset(GstRTSPMessage * msg)477 gst_rtsp_message_unset (GstRTSPMessage * msg)
478 {
479   g_return_val_if_fail (msg != NULL, GST_RTSP_EINVAL);
480 
481   switch (msg->type) {
482     case GST_RTSP_MESSAGE_INVALID:
483       break;
484     case GST_RTSP_MESSAGE_REQUEST:
485     case GST_RTSP_MESSAGE_HTTP_REQUEST:
486       g_free (msg->type_data.request.uri);
487       break;
488     case GST_RTSP_MESSAGE_RESPONSE:
489     case GST_RTSP_MESSAGE_HTTP_RESPONSE:
490       g_free (msg->type_data.response.reason);
491       break;
492     case GST_RTSP_MESSAGE_DATA:
493       break;
494     default:
495       g_return_val_if_reached (GST_RTSP_EINVAL);
496   }
497 
498   if (msg->hdr_fields != NULL) {
499     guint i;
500 
501     for (i = 0; i < msg->hdr_fields->len; i++) {
502       RTSPKeyValue *keyval = &g_array_index (msg->hdr_fields, RTSPKeyValue, i);
503 
504       g_free (keyval->value);
505       g_free (keyval->custom_key);
506     }
507     g_array_free (msg->hdr_fields, TRUE);
508   }
509   g_free (msg->body);
510   gst_buffer_replace (&msg->body_buffer, NULL);
511 
512   memset (msg, 0, sizeof (GstRTSPMessage));
513 
514   return GST_RTSP_OK;
515 }
516 
517 /**
518  * gst_rtsp_message_free:
519  * @msg: a #GstRTSPMessage
520  *
521  * Free the memory used by @msg.
522  *
523  * Returns: a #GstRTSPResult.
524  */
525 GstRTSPResult
gst_rtsp_message_free(GstRTSPMessage * msg)526 gst_rtsp_message_free (GstRTSPMessage * msg)
527 {
528   GstRTSPResult res;
529 
530   g_return_val_if_fail (msg != NULL, GST_RTSP_EINVAL);
531 
532   res = gst_rtsp_message_unset (msg);
533   if (res == GST_RTSP_OK)
534     g_free (msg);
535 
536   return res;
537 }
538 
539 /**
540  * gst_rtsp_message_copy:
541  * @msg: a #GstRTSPMessage
542  * @copy: (out) (transfer full): pointer to new #GstRTSPMessage
543  *
544  * Allocate a new copy of @msg and store the result in @copy. The value in
545  * @copy should be release with gst_rtsp_message_free function.
546  *
547  * Returns: a #GstRTSPResult
548  *
549  * Since: 1.14
550  */
551 GstRTSPResult
gst_rtsp_message_copy(const GstRTSPMessage * msg,GstRTSPMessage ** copy)552 gst_rtsp_message_copy (const GstRTSPMessage * msg, GstRTSPMessage ** copy)
553 {
554   GstRTSPResult ret;
555   GstRTSPMessage *cp;
556 
557   if (msg == NULL)
558     return GST_RTSP_EINVAL;
559 
560   ret = gst_rtsp_message_new (copy);
561   if (ret != GST_RTSP_OK)
562     return ret;
563 
564   cp = *copy;
565 
566   cp->type = msg->type;
567   switch (cp->type) {
568     case GST_RTSP_MESSAGE_INVALID:
569       break;
570     case GST_RTSP_MESSAGE_REQUEST:
571     case GST_RTSP_MESSAGE_HTTP_REQUEST:
572       cp->type_data.request.method = msg->type_data.request.method;
573       cp->type_data.request.uri = g_strdup (msg->type_data.request.uri);
574       cp->type_data.request.version = msg->type_data.request.version;
575       break;
576     case GST_RTSP_MESSAGE_RESPONSE:
577     case GST_RTSP_MESSAGE_HTTP_RESPONSE:
578       cp->type_data.response.code = msg->type_data.response.code;
579       cp->type_data.response.reason = g_strdup (msg->type_data.response.reason);
580       cp->type_data.response.version = msg->type_data.response.version;
581       break;
582     case GST_RTSP_MESSAGE_DATA:
583       cp->type_data.data.channel = msg->type_data.data.channel;
584       break;
585     default:
586       return GST_RTSP_EINVAL;
587   }
588 
589   key_value_foreach (msg->hdr_fields, (GFunc) key_value_append, cp->hdr_fields);
590   if (msg->body)
591     gst_rtsp_message_set_body (cp, msg->body, msg->body_size);
592   else
593     gst_rtsp_message_set_body_buffer (cp, msg->body_buffer);
594 
595   return GST_RTSP_OK;
596 }
597 
598 
599 /**
600  * gst_rtsp_message_take_header:
601  * @msg: a #GstRTSPMessage
602  * @field: a #GstRTSPHeaderField
603  * @value: (transfer full): the value of the header
604  *
605  * Add a header with key @field and @value to @msg. This function takes
606  * ownership of @value.
607  *
608  * Returns: a #GstRTSPResult.
609  */
610 GstRTSPResult
gst_rtsp_message_take_header(GstRTSPMessage * msg,GstRTSPHeaderField field,gchar * value)611 gst_rtsp_message_take_header (GstRTSPMessage * msg, GstRTSPHeaderField field,
612     gchar * value)
613 {
614   RTSPKeyValue key_value;
615 
616   g_return_val_if_fail (msg != NULL, GST_RTSP_EINVAL);
617   g_return_val_if_fail (value != NULL, GST_RTSP_EINVAL);
618 
619   key_value.field = field;
620   key_value.value = value;
621   key_value.custom_key = NULL;
622 
623   g_array_append_val (msg->hdr_fields, key_value);
624 
625   return GST_RTSP_OK;
626 }
627 
628 /**
629  * gst_rtsp_message_add_header:
630  * @msg: a #GstRTSPMessage
631  * @field: a #GstRTSPHeaderField
632  * @value: (transfer none): the value of the header
633  *
634  * Add a header with key @field and @value to @msg. This function takes a copy
635  * of @value.
636  *
637  * Returns: a #GstRTSPResult.
638  */
639 GstRTSPResult
gst_rtsp_message_add_header(GstRTSPMessage * msg,GstRTSPHeaderField field,const gchar * value)640 gst_rtsp_message_add_header (GstRTSPMessage * msg, GstRTSPHeaderField field,
641     const gchar * value)
642 {
643   return gst_rtsp_message_take_header (msg, field, g_strdup (value));
644 }
645 
646 /**
647  * gst_rtsp_message_remove_header:
648  * @msg: a #GstRTSPMessage
649  * @field: a #GstRTSPHeaderField
650  * @indx: the index of the header
651  *
652  * Remove the @indx header with key @field from @msg. If @indx equals -1, all
653  * headers will be removed.
654  *
655  * Returns: a #GstRTSPResult.
656  */
657 GstRTSPResult
gst_rtsp_message_remove_header(GstRTSPMessage * msg,GstRTSPHeaderField field,gint indx)658 gst_rtsp_message_remove_header (GstRTSPMessage * msg, GstRTSPHeaderField field,
659     gint indx)
660 {
661   GstRTSPResult res = GST_RTSP_ENOTIMPL;
662   guint i = 0;
663   gint cnt = 0;
664 
665   g_return_val_if_fail (msg != NULL, GST_RTSP_EINVAL);
666 
667   while (i < msg->hdr_fields->len) {
668     RTSPKeyValue *key_value = &g_array_index (msg->hdr_fields, RTSPKeyValue, i);
669 
670     if (key_value->field == field && (indx == -1 || cnt++ == indx)) {
671       g_free (key_value->value);
672       g_array_remove_index (msg->hdr_fields, i);
673       res = GST_RTSP_OK;
674       if (indx != -1)
675         break;
676     } else {
677       i++;
678     }
679   }
680   return res;
681 }
682 
683 /**
684  * gst_rtsp_message_get_header:
685  * @msg: a #GstRTSPMessage
686  * @field: a #GstRTSPHeaderField
687  * @value: (out) (transfer none): pointer to hold the result
688  * @indx: the index of the header
689  *
690  * Get the @indx header value with key @field from @msg. The result in @value
691  * stays valid as long as it remains present in @msg.
692  *
693  * Returns: #GST_RTSP_OK when @field was found, #GST_RTSP_ENOTIMPL if the key
694  * was not found.
695  */
696 GstRTSPResult
gst_rtsp_message_get_header(const GstRTSPMessage * msg,GstRTSPHeaderField field,gchar ** value,gint indx)697 gst_rtsp_message_get_header (const GstRTSPMessage * msg,
698     GstRTSPHeaderField field, gchar ** value, gint indx)
699 {
700   guint i;
701   gint cnt = 0;
702 
703   g_return_val_if_fail (msg != NULL, GST_RTSP_EINVAL);
704 
705   /* no header initialized, there are no headers */
706   if (msg->hdr_fields == NULL)
707     return GST_RTSP_ENOTIMPL;
708 
709   for (i = 0; i < msg->hdr_fields->len; i++) {
710     RTSPKeyValue *key_value = &g_array_index (msg->hdr_fields, RTSPKeyValue, i);
711 
712     if (key_value->field == field && cnt++ == indx) {
713       if (value)
714         *value = key_value->value;
715       return GST_RTSP_OK;
716     }
717   }
718 
719   return GST_RTSP_ENOTIMPL;
720 }
721 
722 /**
723  * gst_rtsp_message_add_header_by_name:
724  * @msg: a #GstRTSPMessage
725  * @header: (transfer none): header string
726  * @value: (transfer none): the value of the header
727  *
728  * Add a header with key @header and @value to @msg. This function takes a copy
729  * of @value.
730  *
731  * Returns: a #GstRTSPResult.
732  *
733  * Since: 1.6
734  */
735 GstRTSPResult
gst_rtsp_message_add_header_by_name(GstRTSPMessage * msg,const gchar * header,const gchar * value)736 gst_rtsp_message_add_header_by_name (GstRTSPMessage * msg,
737     const gchar * header, const gchar * value)
738 {
739   GstRTSPHeaderField field;
740 
741   g_return_val_if_fail (msg != NULL, GST_RTSP_EINVAL);
742   g_return_val_if_fail (header != NULL, GST_RTSP_EINVAL);
743   g_return_val_if_fail (value != NULL, GST_RTSP_EINVAL);
744 
745   field = gst_rtsp_find_header_field (header);
746   if (field != GST_RTSP_HDR_INVALID)
747     return gst_rtsp_message_take_header (msg, field, g_strdup (value));
748 
749   return gst_rtsp_message_take_header_by_name (msg, header, g_strdup (value));
750 }
751 
752 /**
753  * gst_rtsp_message_take_header_by_name:
754  * @msg: a #GstRTSPMessage
755  * @header: (transfer none): a header string
756  * @value: (transfer full): the value of the header
757  *
758  * Add a header with key @header and @value to @msg. This function takes
759  * ownership of @value, but not of @header.
760  *
761  * Returns: a #GstRTSPResult.
762  *
763  * Since: 1.6
764  */
765 GstRTSPResult
gst_rtsp_message_take_header_by_name(GstRTSPMessage * msg,const gchar * header,gchar * value)766 gst_rtsp_message_take_header_by_name (GstRTSPMessage * msg,
767     const gchar * header, gchar * value)
768 {
769   RTSPKeyValue key_value;
770 
771   g_return_val_if_fail (msg != NULL, GST_RTSP_EINVAL);
772   g_return_val_if_fail (header != NULL, GST_RTSP_EINVAL);
773   g_return_val_if_fail (value != NULL, GST_RTSP_EINVAL);
774 
775   key_value.field = GST_RTSP_HDR_INVALID;
776   key_value.value = value;
777   key_value.custom_key = g_strdup (header);
778 
779   g_array_append_val (msg->hdr_fields, key_value);
780 
781   return GST_RTSP_OK;
782 }
783 
784 /* returns -1 if not found, otherwise index position within msg->hdr_fields */
785 static gint
gst_rtsp_message_find_header_by_name(GstRTSPMessage * msg,const gchar * header,gint index)786 gst_rtsp_message_find_header_by_name (GstRTSPMessage * msg,
787     const gchar * header, gint index)
788 {
789   GstRTSPHeaderField field;
790   gint cnt = 0;
791   guint i;
792 
793   /* no header initialized, there are no headers */
794   if (msg->hdr_fields == NULL)
795     return -1;
796 
797   field = gst_rtsp_find_header_field (header);
798   for (i = 0; i < msg->hdr_fields->len; i++) {
799     RTSPKeyValue *key_val;
800 
801     key_val = &g_array_index (msg->hdr_fields, RTSPKeyValue, i);
802 
803     if (key_val->field != field)
804       continue;
805 
806     if (key_val->custom_key != NULL &&
807         g_ascii_strcasecmp (key_val->custom_key, header) != 0)
808       continue;
809 
810     if (index < 0 || cnt++ == index)
811       return i;
812   }
813 
814   return -1;
815 }
816 
817 /**
818  * gst_rtsp_message_remove_header_by_name:
819  * @msg: a #GstRTSPMessage
820  * @header: the header string
821  * @index: the index of the header
822  *
823  * Remove the @index header with key @header from @msg. If @index equals -1,
824  * all matching headers will be removed.
825  *
826  * Returns: a #GstRTSPResult
827  *
828  * Since: 1.6
829  */
830 GstRTSPResult
gst_rtsp_message_remove_header_by_name(GstRTSPMessage * msg,const gchar * header,gint index)831 gst_rtsp_message_remove_header_by_name (GstRTSPMessage * msg,
832     const gchar * header, gint index)
833 {
834   GstRTSPResult res = GST_RTSP_ENOTIMPL;
835   RTSPKeyValue *kv;
836   gint pos;
837 
838   g_return_val_if_fail (msg != NULL, GST_RTSP_EINVAL);
839   g_return_val_if_fail (header != NULL, GST_RTSP_EINVAL);
840 
841   do {
842     pos = gst_rtsp_message_find_header_by_name (msg, header, index);
843 
844     if (pos < 0)
845       break;
846 
847     kv = &g_array_index (msg->hdr_fields, RTSPKeyValue, pos);
848     g_free (kv->value);
849     g_free (kv->custom_key);
850     g_array_remove_index (msg->hdr_fields, pos);
851     res = GST_RTSP_OK;
852   } while (index < 0);
853 
854   return res;
855 }
856 
857 /**
858  * gst_rtsp_message_get_header_by_name:
859  * @msg: a #GstRTSPMessage
860  * @header: a #GstRTSPHeaderField
861  * @value: (out) (transfer none): pointer to hold the result
862  * @index: the index of the header
863  *
864  * Get the @index header value with key @header from @msg. The result in @value
865  * stays valid as long as it remains present in @msg.
866  *
867  * Returns: #GST_RTSP_OK when @field was found, #GST_RTSP_ENOTIMPL if the key
868  * was not found.
869  *
870  * Since: 1.6
871  */
872 GstRTSPResult
gst_rtsp_message_get_header_by_name(GstRTSPMessage * msg,const gchar * header,gchar ** value,gint index)873 gst_rtsp_message_get_header_by_name (GstRTSPMessage * msg,
874     const gchar * header, gchar ** value, gint index)
875 {
876   RTSPKeyValue *key_val;
877   gint pos;
878 
879   g_return_val_if_fail (msg != NULL, GST_RTSP_EINVAL);
880   g_return_val_if_fail (header != NULL, GST_RTSP_EINVAL);
881 
882   pos = gst_rtsp_message_find_header_by_name (msg, header, index);
883 
884   if (pos < 0)
885     return GST_RTSP_ENOTIMPL;
886 
887   key_val = &g_array_index (msg->hdr_fields, RTSPKeyValue, pos);
888 
889   if (value)
890     *value = key_val->value;
891 
892   return GST_RTSP_OK;
893 }
894 
895 /**
896  * gst_rtsp_message_append_headers:
897  * @msg: a #GstRTSPMessage
898  * @str: (transfer none): a string
899  *
900  * Append the currently configured headers in @msg to the #GString @str suitable
901  * for transmission.
902  *
903  * Returns: #GST_RTSP_OK.
904  */
905 GstRTSPResult
gst_rtsp_message_append_headers(const GstRTSPMessage * msg,GString * str)906 gst_rtsp_message_append_headers (const GstRTSPMessage * msg, GString * str)
907 {
908   guint i;
909 
910   g_return_val_if_fail (msg != NULL, GST_RTSP_EINVAL);
911   g_return_val_if_fail (str != NULL, GST_RTSP_EINVAL);
912 
913   for (i = 0; i < msg->hdr_fields->len; i++) {
914     RTSPKeyValue *key_value;
915     const gchar *keystr;
916 
917     key_value = &g_array_index (msg->hdr_fields, RTSPKeyValue, i);
918 
919     if (key_value->custom_key != NULL)
920       keystr = key_value->custom_key;
921     else
922       keystr = gst_rtsp_header_as_text (key_value->field);
923 
924     g_string_append_printf (str, "%s: %s\r\n", keystr, key_value->value);
925   }
926   return GST_RTSP_OK;
927 }
928 
929 /**
930  * gst_rtsp_message_set_body:
931  * @msg: a #GstRTSPMessage
932  * @data: (array length=size) (transfer none): the data
933  * @size: the size of @data
934  *
935  * Set the body of @msg to a copy of @data. Any existing body or body buffer
936  * will be replaced by the new body.
937  *
938  * Returns: #GST_RTSP_OK.
939  */
940 GstRTSPResult
gst_rtsp_message_set_body(GstRTSPMessage * msg,const guint8 * data,guint size)941 gst_rtsp_message_set_body (GstRTSPMessage * msg, const guint8 * data,
942     guint size)
943 {
944   g_return_val_if_fail (msg != NULL, GST_RTSP_EINVAL);
945 
946   return gst_rtsp_message_take_body (msg, g_memdup (data, size), size);
947 }
948 
949 /**
950  * gst_rtsp_message_take_body:
951  * @msg: a #GstRTSPMessage
952  * @data: (array length=size) (transfer full): the data
953  * @size: the size of @data
954  *
955  * Set the body of @msg to @data and @size. This method takes ownership of
956  * @data. Any existing body or body buffer will be replaced by the new body.
957  *
958  * Returns: #GST_RTSP_OK.
959  */
960 GstRTSPResult
gst_rtsp_message_take_body(GstRTSPMessage * msg,guint8 * data,guint size)961 gst_rtsp_message_take_body (GstRTSPMessage * msg, guint8 * data, guint size)
962 {
963   g_return_val_if_fail (msg != NULL, GST_RTSP_EINVAL);
964   g_return_val_if_fail (data != NULL || size == 0, GST_RTSP_EINVAL);
965 
966   gst_buffer_replace (&msg->body_buffer, NULL);
967   g_free (msg->body);
968 
969   msg->body = data;
970   msg->body_size = size;
971 
972   return GST_RTSP_OK;
973 }
974 
975 /**
976  * gst_rtsp_message_get_body:
977  * @msg: a #GstRTSPMessage
978  * @data: (out) (transfer none) (array length=size): location for the data
979  * @size: (out): location for the size of @data
980  *
981  * Get the body of @msg. @data remains valid for as long as @msg is valid and
982  * unchanged.
983  *
984  * If the message body was set as a #GstBuffer before this will cause the data
985  * to be copied and stored in the message. The #GstBuffer will no longer be
986  * kept in the message.
987  *
988  * Returns: #GST_RTSP_OK.
989  */
990 GstRTSPResult
gst_rtsp_message_get_body(const GstRTSPMessage * msg,guint8 ** data,guint * size)991 gst_rtsp_message_get_body (const GstRTSPMessage * msg, guint8 ** data,
992     guint * size)
993 {
994   g_return_val_if_fail (msg != NULL, GST_RTSP_EINVAL);
995   g_return_val_if_fail (data != NULL, GST_RTSP_EINVAL);
996   g_return_val_if_fail (size != NULL, GST_RTSP_EINVAL);
997 
998   if (msg->body_buffer) {
999     gsize size;
1000 
1001     gst_buffer_extract_dup (msg->body_buffer, 0,
1002         gst_buffer_get_size (msg->body_buffer),
1003         (gpointer *) & ((GstRTSPMessage *) msg)->body, &size);
1004     gst_buffer_replace (&((GstRTSPMessage *) msg)->body_buffer, NULL);
1005     ((GstRTSPMessage *) msg)->body_size = size;
1006   }
1007 
1008   *data = msg->body;
1009   *size = msg->body_size;
1010 
1011   return GST_RTSP_OK;
1012 }
1013 
1014 /**
1015  * gst_rtsp_message_steal_body:
1016  * @msg: a #GstRTSPMessage
1017  * @data: (out) (transfer full) (array length=size): location for the data
1018  * @size: (out): location for the size of @data
1019  *
1020  * Take the body of @msg and store it in @data and @size. After this method,
1021  * the body and size of @msg will be set to %NULL and 0 respectively.
1022  *
1023  * Returns: #GST_RTSP_OK.
1024  */
1025 GstRTSPResult
gst_rtsp_message_steal_body(GstRTSPMessage * msg,guint8 ** data,guint * size)1026 gst_rtsp_message_steal_body (GstRTSPMessage * msg, guint8 ** data, guint * size)
1027 {
1028   g_return_val_if_fail (msg != NULL, GST_RTSP_EINVAL);
1029   g_return_val_if_fail (data != NULL, GST_RTSP_EINVAL);
1030   g_return_val_if_fail (size != NULL, GST_RTSP_EINVAL);
1031 
1032   if (msg->body_buffer) {
1033     gsize size;
1034 
1035     gst_buffer_extract_dup (msg->body_buffer, 0,
1036         gst_buffer_get_size (msg->body_buffer),
1037         (gpointer *) & msg->body, &size);
1038     gst_buffer_replace (&msg->body_buffer, NULL);
1039     msg->body_size = size;
1040   }
1041 
1042   *data = msg->body;
1043   *size = msg->body_size;
1044 
1045   msg->body = NULL;
1046   msg->body_size = 0;
1047 
1048   return GST_RTSP_OK;
1049 }
1050 
1051 /**
1052  * gst_rtsp_message_set_body_buffer:
1053  * @msg: a #GstRTSPMessage
1054  * @buffer: a #GstBuffer
1055  *
1056  * Set the body of @msg to @buffer. Any existing body or body buffer
1057  * will be replaced by the new body.
1058  *
1059  * Returns: #GST_RTSP_OK.
1060  *
1061  * Since: 1.16
1062  */
1063 GstRTSPResult
gst_rtsp_message_set_body_buffer(GstRTSPMessage * msg,GstBuffer * buffer)1064 gst_rtsp_message_set_body_buffer (GstRTSPMessage * msg, GstBuffer * buffer)
1065 {
1066   g_return_val_if_fail (msg != NULL, GST_RTSP_EINVAL);
1067 
1068   return gst_rtsp_message_take_body_buffer (msg,
1069       buffer ? gst_buffer_ref (buffer) : NULL);
1070 }
1071 
1072 /**
1073  * gst_rtsp_message_take_body_buffer:
1074  * @msg: a #GstRTSPMessage
1075  * @buffer: (transfer full): a #GstBuffer
1076  *
1077  * Set the body of @msg to @buffer. This method takes ownership of @buffer.
1078  * Any existing body or body buffer will be replaced by the new body.
1079  *
1080  * Returns: #GST_RTSP_OK.
1081  *
1082  * Since: 1.16
1083  */
1084 GstRTSPResult
gst_rtsp_message_take_body_buffer(GstRTSPMessage * msg,GstBuffer * buffer)1085 gst_rtsp_message_take_body_buffer (GstRTSPMessage * msg, GstBuffer * buffer)
1086 {
1087   g_return_val_if_fail (msg != NULL, GST_RTSP_EINVAL);
1088 
1089   g_free (msg->body);
1090   msg->body = NULL;
1091   if (msg->body_buffer)
1092     gst_buffer_unref (msg->body_buffer);
1093   msg->body_buffer = buffer;
1094   msg->body_size = buffer ? gst_buffer_get_size (buffer) : 0;
1095 
1096   return GST_RTSP_OK;
1097 }
1098 
1099 /**
1100  * gst_rtsp_message_get_body_buffer:
1101  * @msg: a #GstRTSPMessage
1102  * @buffer: (out) (transfer none): location for the buffer
1103  *
1104  * Get the body of @msg. @buffer remains valid for as long as @msg is valid and
1105  * unchanged.
1106  *
1107  * If body data was set from raw memory instead of a #GstBuffer this function
1108  * will always return %NULL. The caller can check if there is a body buffer by
1109  * calling gst_rtsp_message_has_body_buffer().
1110  *
1111  * Returns: #GST_RTSP_OK.
1112  *
1113  * Since: 1.16
1114  */
1115 GstRTSPResult
gst_rtsp_message_get_body_buffer(const GstRTSPMessage * msg,GstBuffer ** buffer)1116 gst_rtsp_message_get_body_buffer (const GstRTSPMessage * msg,
1117     GstBuffer ** buffer)
1118 {
1119   g_return_val_if_fail (msg != NULL, GST_RTSP_EINVAL);
1120   g_return_val_if_fail (buffer != NULL, GST_RTSP_EINVAL);
1121 
1122   *buffer = msg->body_buffer;
1123 
1124   return GST_RTSP_OK;
1125 }
1126 
1127 /**
1128  * gst_rtsp_message_steal_body_buffer:
1129  * @msg: a #GstRTSPMessage
1130  * @buffer: (out) (transfer full): location for the buffer
1131  *
1132  * Take the body of @msg and store it in @buffer. After this method,
1133  * the body and size of @msg will be set to %NULL and 0 respectively.
1134  *
1135  * If body data was set from raw memory instead of a #GstBuffer this function
1136  * will always return %NULL. The caller can check if there is a body buffer by
1137  * calling gst_rtsp_message_has_body_buffer().
1138  *
1139  * Returns: #GST_RTSP_OK.
1140  *
1141  * Since: 1.16
1142  */
1143 GstRTSPResult
gst_rtsp_message_steal_body_buffer(GstRTSPMessage * msg,GstBuffer ** buffer)1144 gst_rtsp_message_steal_body_buffer (GstRTSPMessage * msg, GstBuffer ** buffer)
1145 {
1146   g_return_val_if_fail (msg != NULL, GST_RTSP_EINVAL);
1147   g_return_val_if_fail (buffer != NULL, GST_RTSP_EINVAL);
1148 
1149   if (msg->body_buffer) {
1150     *buffer = msg->body_buffer;
1151     msg->body_buffer = NULL;
1152     msg->body_size = 0;
1153   } else {
1154     *buffer = NULL;
1155   }
1156 
1157   return GST_RTSP_OK;
1158 }
1159 
1160 /**
1161  * gst_rtsp_message_has_body_buffer:
1162  * @msg: a #GstRTSPMessage
1163  *
1164  * Checks if @msg has a body and the body is stored as #GstBuffer.
1165  *
1166  * Returns: %TRUE if @msg has a body and it's stored as #GstBuffer, %FALSE
1167  * otherwise.
1168  *
1169  * Since: 1.16
1170  */
1171 gboolean
gst_rtsp_message_has_body_buffer(const GstRTSPMessage * msg)1172 gst_rtsp_message_has_body_buffer (const GstRTSPMessage * msg)
1173 {
1174   g_return_val_if_fail (msg != NULL, GST_RTSP_EINVAL);
1175 
1176   return msg->body_buffer != NULL;
1177 }
1178 
1179 static void
dump_key_value(gpointer data,gpointer user_data G_GNUC_UNUSED)1180 dump_key_value (gpointer data, gpointer user_data G_GNUC_UNUSED)
1181 {
1182   RTSPKeyValue *key_value = (RTSPKeyValue *) data;
1183   const gchar *key_string;
1184 
1185   if (key_value->custom_key != NULL)
1186     key_string = key_value->custom_key;
1187   else
1188     key_string = gst_rtsp_header_as_text (key_value->field);
1189 
1190   g_print ("   key: '%s', value: '%s'\n", key_string, key_value->value);
1191 }
1192 
1193 /**
1194  * gst_rtsp_message_dump:
1195  * @msg: a #GstRTSPMessage
1196  *
1197  * Dump the contents of @msg to stdout.
1198  *
1199  * Returns: #GST_RTSP_OK.
1200  */
1201 GstRTSPResult
gst_rtsp_message_dump(GstRTSPMessage * msg)1202 gst_rtsp_message_dump (GstRTSPMessage * msg)
1203 {
1204   guint8 *data = NULL;
1205   guint size;
1206   GstBuffer *body_buffer = NULL;
1207 
1208 #define PRINT_BODY G_STMT_START { \
1209   gst_rtsp_message_get_body_buffer (msg, &body_buffer); \
1210   if (body_buffer) { \
1211     gst_util_dump_buffer (body_buffer); \
1212   } else { \
1213     gst_rtsp_message_get_body (msg, &data, &size); \
1214     if (data) \
1215       gst_util_dump_mem (data, size); \
1216   } \
1217 } G_STMT_END;
1218 
1219   g_return_val_if_fail (msg != NULL, GST_RTSP_EINVAL);
1220 
1221   switch (msg->type) {
1222     case GST_RTSP_MESSAGE_REQUEST:
1223       g_print ("RTSP request message %p\n", msg);
1224       g_print (" request line:\n");
1225       g_print ("   method: '%s'\n",
1226           gst_rtsp_method_as_text (msg->type_data.request.method));
1227       g_print ("   uri:    '%s'\n", msg->type_data.request.uri);
1228       g_print ("   version: '%s'\n",
1229           gst_rtsp_version_as_text (msg->type_data.request.version));
1230       g_print (" headers:\n");
1231       key_value_foreach (msg->hdr_fields, dump_key_value, NULL);
1232       g_print (" body:\n");
1233       PRINT_BODY;
1234       break;
1235     case GST_RTSP_MESSAGE_RESPONSE:
1236       g_print ("RTSP response message %p\n", msg);
1237       g_print (" status line:\n");
1238       g_print ("   code:   '%d'\n", msg->type_data.response.code);
1239       g_print ("   reason: '%s'\n", msg->type_data.response.reason);
1240       g_print ("   version: '%s'\n",
1241           gst_rtsp_version_as_text (msg->type_data.response.version));
1242       g_print (" headers:\n");
1243       key_value_foreach (msg->hdr_fields, dump_key_value, NULL);
1244       PRINT_BODY;
1245       break;
1246     case GST_RTSP_MESSAGE_HTTP_REQUEST:
1247       g_print ("HTTP request message %p\n", msg);
1248       g_print (" request line:\n");
1249       g_print ("   method:  '%s'\n",
1250           gst_rtsp_method_as_text (msg->type_data.request.method));
1251       g_print ("   uri:     '%s'\n", msg->type_data.request.uri);
1252       g_print ("   version: '%s'\n",
1253           gst_rtsp_version_as_text (msg->type_data.request.version));
1254       g_print (" headers:\n");
1255       key_value_foreach (msg->hdr_fields, dump_key_value, NULL);
1256       g_print (" body:\n");
1257       PRINT_BODY;
1258       break;
1259     case GST_RTSP_MESSAGE_HTTP_RESPONSE:
1260       g_print ("HTTP response message %p\n", msg);
1261       g_print (" status line:\n");
1262       g_print ("   code:    '%d'\n", msg->type_data.response.code);
1263       g_print ("   reason:  '%s'\n", msg->type_data.response.reason);
1264       g_print ("   version: '%s'\n",
1265           gst_rtsp_version_as_text (msg->type_data.response.version));
1266       g_print (" headers:\n");
1267       key_value_foreach (msg->hdr_fields, dump_key_value, NULL);
1268       PRINT_BODY;
1269       break;
1270     case GST_RTSP_MESSAGE_DATA:
1271       g_print ("RTSP data message %p\n", msg);
1272       g_print (" channel: '%d'\n", msg->type_data.data.channel);
1273       g_print (" size:    '%d'\n", msg->body_size);
1274       PRINT_BODY;
1275       break;
1276     default:
1277       g_print ("unsupported message type %d\n", msg->type);
1278       return GST_RTSP_EINVAL;
1279   }
1280   return GST_RTSP_OK;
1281 
1282 #undef PRINT_BODY
1283 }
1284 
1285 
1286 static const gchar *
skip_lws(const gchar * s)1287 skip_lws (const gchar * s)
1288 {
1289   while (g_ascii_isspace (*s))
1290     s++;
1291   return s;
1292 }
1293 
1294 static const gchar *
skip_commas(const gchar * s)1295 skip_commas (const gchar * s)
1296 {
1297   /* The grammar allows for multiple commas */
1298   while (g_ascii_isspace (*s) || *s == ',')
1299     s++;
1300   return s;
1301 }
1302 
1303 static const gchar *
skip_scheme(const gchar * s)1304 skip_scheme (const gchar * s)
1305 {
1306   while (*s && !g_ascii_isspace (*s))
1307     s++;
1308   return s;
1309 }
1310 
1311 static const gchar *
skip_item(const gchar * s)1312 skip_item (const gchar * s)
1313 {
1314   gboolean quoted = FALSE;
1315 
1316   /* A list item ends at the last non-whitespace character
1317    * before a comma which is not inside a quoted-string. Or at
1318    * the end of the string.
1319    */
1320   while (*s) {
1321     if (*s == '"') {
1322       quoted = !quoted;
1323     } else if (quoted) {
1324       if (*s == '\\' && *(s + 1))
1325         s++;
1326     } else {
1327       if (*s == ',' || g_ascii_isspace (*s))
1328         break;
1329     }
1330     s++;
1331   }
1332 
1333   return s;
1334 }
1335 
1336 static void
decode_quoted_string(gchar * quoted_string)1337 decode_quoted_string (gchar * quoted_string)
1338 {
1339   gchar *src, *dst;
1340 
1341   src = quoted_string + 1;
1342   dst = quoted_string;
1343   while (*src && *src != '"') {
1344     if (*src == '\\' && *(src + 1))
1345       src++;
1346     *dst++ = *src++;
1347   }
1348   *dst = '\0';
1349 }
1350 
1351 static void
parse_auth_credentials(GPtrArray * auth_credentials,const gchar * header,GstRTSPHeaderField field)1352 parse_auth_credentials (GPtrArray * auth_credentials, const gchar * header,
1353     GstRTSPHeaderField field)
1354 {
1355   while (header[0] != '\0') {
1356     const gchar *end;
1357     GstRTSPAuthCredential *auth_credential;
1358 
1359     /* Skip whitespace at the start of the string */
1360     header = skip_lws (header);
1361     if (header[0] == '\0')
1362       break;
1363 
1364     /* Skip until end of string or whitespace: end of scheme */
1365     end = skip_scheme (header);
1366 
1367     auth_credential = g_new0 (GstRTSPAuthCredential, 1);
1368 
1369     if (g_ascii_strncasecmp (header, "basic", 5) == 0) {
1370       auth_credential->scheme = GST_RTSP_AUTH_BASIC;
1371     } else if (g_ascii_strncasecmp (header, "digest", 6) == 0) {
1372       auth_credential->scheme = GST_RTSP_AUTH_DIGEST;
1373     } else {
1374       /* Not supported, skip */
1375       g_free (auth_credential);
1376       header = end;
1377       continue;
1378     }
1379 
1380     /* Basic Authorization request has only an unformated blurb following, all
1381      * other variants have comma-separated name=value pairs */
1382     if (end[0] != '\0' && field == GST_RTSP_HDR_AUTHORIZATION
1383         && auth_credential->scheme == GST_RTSP_AUTH_BASIC) {
1384       auth_credential->authorization = g_strdup (end + 1);
1385       header = end;
1386     } else if (end[0] != '\0') {
1387       GPtrArray *params;
1388 
1389       params = g_ptr_array_new ();
1390 
1391       /* Space or start of param */
1392       header = end;
1393 
1394       /* Parse a header whose content is described by RFC2616 as
1395        * "#something", where "something" does not itself contain commas,
1396        * except as part of quoted-strings, into a list of allocated strings.
1397        */
1398       while (*header) {
1399         const gchar *item_end;
1400         const gchar *eq;
1401 
1402         header = skip_commas (header);
1403         item_end = skip_item (header);
1404 
1405         for (eq = header; *eq != '\0' && *eq != '=' && eq < item_end; eq++);
1406         if (eq[0] == '=') {
1407           GstRTSPAuthParam *auth_param = g_new0 (GstRTSPAuthParam, 1);
1408           const gchar *value;
1409 
1410           /* have an actual param */
1411           auth_param->name = g_strndup (header, eq - header);
1412 
1413           value = eq + 1;
1414           value = skip_lws (value);
1415           auth_param->value = g_strndup (value, item_end - value);
1416           if (value[0] == '"')
1417             decode_quoted_string (auth_param->value);
1418 
1419           g_ptr_array_add (params, auth_param);
1420           header = item_end;
1421         } else {
1422           /* at next scheme, header at start of it */
1423           break;
1424         }
1425       }
1426       if (params->len)
1427         g_ptr_array_add (params, NULL);
1428       auth_credential->params =
1429           (GstRTSPAuthParam **) g_ptr_array_free (params, FALSE);
1430     } else {
1431       header = end;
1432     }
1433     g_ptr_array_add (auth_credentials, auth_credential);
1434 
1435     /* WWW-Authenticate allows multiple, Authorization allows one */
1436     if (field == GST_RTSP_HDR_AUTHORIZATION)
1437       break;
1438   }
1439 }
1440 
1441 /**
1442  * gst_rtsp_message_parse_auth_credentials:
1443  * @msg: a #GstRTSPMessage
1444  * @field: a #GstRTSPHeaderField
1445  *
1446  * Parses the credentials given in a WWW-Authenticate or Authorization header.
1447  *
1448  * Returns: (array zero-terminated=1):
1449  *     %NULL-terminated array of GstRTSPAuthCredential or %NULL.
1450  *
1451  * Since: 1.12
1452  */
1453 GstRTSPAuthCredential **
gst_rtsp_message_parse_auth_credentials(GstRTSPMessage * msg,GstRTSPHeaderField field)1454 gst_rtsp_message_parse_auth_credentials (GstRTSPMessage * msg,
1455     GstRTSPHeaderField field)
1456 {
1457   gchar *header;
1458   GPtrArray *auth_credentials;
1459   gint i;
1460 
1461   g_return_val_if_fail (msg != NULL, NULL);
1462 
1463   auth_credentials = g_ptr_array_new ();
1464 
1465   i = 0;
1466   while (gst_rtsp_message_get_header (msg, field, &header, i) == GST_RTSP_OK) {
1467     parse_auth_credentials (auth_credentials, header, field);
1468     i++;
1469   }
1470 
1471   if (auth_credentials->len)
1472     g_ptr_array_add (auth_credentials, NULL);
1473 
1474   return (GstRTSPAuthCredential **) g_ptr_array_free (auth_credentials, FALSE);
1475 }
1476 
1477 GstRTSPAuthParam *
gst_rtsp_auth_param_copy(GstRTSPAuthParam * param)1478 gst_rtsp_auth_param_copy (GstRTSPAuthParam * param)
1479 {
1480   GstRTSPAuthParam *copy;
1481 
1482   if (param == NULL)
1483     return NULL;
1484 
1485   copy = g_new0 (GstRTSPAuthParam, 1);
1486   copy->name = g_strdup (param->name);
1487   copy->value = g_strdup (param->value);
1488 
1489   return copy;
1490 }
1491 
1492 void
gst_rtsp_auth_param_free(GstRTSPAuthParam * param)1493 gst_rtsp_auth_param_free (GstRTSPAuthParam * param)
1494 {
1495   if (param != NULL) {
1496     g_free (param->name);
1497     g_free (param->value);
1498     g_free (param);
1499   }
1500 }
1501 
1502 G_DEFINE_BOXED_TYPE (GstRTSPAuthParam, gst_rtsp_auth_param,
1503     (GBoxedCopyFunc) gst_rtsp_auth_param_copy,
1504     (GBoxedFreeFunc) gst_rtsp_auth_param_free);
1505 
1506 static void
gst_rtsp_auth_credential_free(GstRTSPAuthCredential * credential)1507 gst_rtsp_auth_credential_free (GstRTSPAuthCredential * credential)
1508 {
1509   GstRTSPAuthParam **p;
1510 
1511   if (credential == NULL)
1512     return;
1513 
1514   for (p = credential->params; p != NULL && *p != NULL; ++p)
1515     gst_rtsp_auth_param_free (*p);
1516 
1517   g_free (credential->params);
1518   g_free (credential->authorization);
1519   g_free (credential);
1520 }
1521 
1522 static GstRTSPAuthCredential *
gst_rtsp_auth_credential_copy(GstRTSPAuthCredential * cred)1523 gst_rtsp_auth_credential_copy (GstRTSPAuthCredential * cred)
1524 {
1525   GstRTSPAuthCredential *copy;
1526 
1527   if (cred == NULL)
1528     return NULL;
1529 
1530   copy = g_new0 (GstRTSPAuthCredential, 1);
1531   copy->scheme = cred->scheme;
1532   if (cred->params) {
1533     guint i, n_params = g_strv_length ((gchar **) cred->params);
1534 
1535     copy->params = g_new0 (GstRTSPAuthParam *, n_params + 1);
1536     for (i = 0; i < n_params; ++i)
1537       copy->params[i] = gst_rtsp_auth_param_copy (cred->params[i]);
1538   }
1539   copy->authorization = g_strdup (cred->authorization);
1540   return copy;
1541 }
1542 
1543 /**
1544  * gst_rtsp_auth_credentials_free:
1545  * @credentials: a %NULL-terminated array of #GstRTSPAuthCredential
1546  *
1547  * Free a %NULL-terminated array of credentials returned from
1548  * gst_rtsp_message_parse_auth_credentials().
1549  *
1550  * Since: 1.12
1551  */
1552 void
gst_rtsp_auth_credentials_free(GstRTSPAuthCredential ** credentials)1553 gst_rtsp_auth_credentials_free (GstRTSPAuthCredential ** credentials)
1554 {
1555   GstRTSPAuthCredential **p;
1556 
1557   if (!credentials)
1558     return;
1559 
1560   for (p = credentials; p != NULL && *p != NULL; ++p)
1561     gst_rtsp_auth_credential_free (*p);
1562 
1563   g_free (credentials);
1564 }
1565 
1566 G_DEFINE_BOXED_TYPE (GstRTSPAuthCredential, gst_rtsp_auth_credential,
1567     (GBoxedCopyFunc) gst_rtsp_auth_credential_copy,
1568     (GBoxedFreeFunc) gst_rtsp_auth_credential_free);
1569