1 /*****************************************************************************
2 * h1conn.c: HTTP 1.x connection handling
3 *****************************************************************************
4 * Copyright © 2015 Rémi Denis-Courmont
6 * This program is free software; you can redistribute it and/or modify it
7 * under the terms of the GNU Lesser General Public License as published by
8 * the Free Software Foundation; either version 2.1 of the License, or
9 * (at your option) any later version.
11 * This program 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
14 * GNU Lesser General Public License for more details.
16 * You should have received a copy of the GNU Lesser General Public License
17 * along with this program; if not, write to the Free Software Foundation,
18 * Inc., 51 Franklin Street, Fifth Floor, Boston MA 02110-1301, USA.
19 *****************************************************************************/
32 #include <vlc_common.h>
34 #include <vlc_block.h>
39 static unsigned vlc_http_can_read(const char *buf
, size_t len
)
41 static const char end
[4] = { '\r', '\n', '\r', '\n' };
43 for (unsigned i
= 4; i
> 0; i
--)
44 if (len
>= i
&& !memcmp(buf
+ len
- i
, end
, i
))
50 * Receives HTTP headers.
52 * Receives HTTP 1.x response line and headers.
54 * @return A heap-allocated null-terminated string contained the full
55 * headers, including the final CRLF.
57 static char *vlc_https_headers_recv(struct vlc_tls
*tls
, size_t *restrict lenp
)
59 size_t size
= 0, len
= 0, canread
;
62 while ((canread
= vlc_http_can_read(buf
, len
)) > 0)
66 if (len
+ canread
>= size
)
72 char *newbuf
= realloc(buf
, size
);
73 if (unlikely(newbuf
== NULL
))
79 assert(size
- len
>= canread
);
80 //vlc_cleanup_push(free, buf);
81 val
= vlc_tls_Read(tls
, buf
+ len
, canread
, true);
84 if (val
!= (ssize_t
)canread
)
90 assert(size
- len
>= 1);
91 buf
[len
] = '\0'; /* for convenience */
100 /** Gets minor HTTP version */
101 static int vlc_http_minor(const char *msg
)
105 if (sscanf(msg
, "HTTP/1.%1d", &minor
) == 1)
112 struct vlc_http_conn conn
;
113 struct vlc_http_stream stream
;
114 uintmax_t content_length
;
115 bool connection_close
;
122 #define CO(conn) ((conn)->opaque)
124 static void vlc_h1_conn_destroy(struct vlc_h1_conn
*conn
);
126 static void *vlc_h1_stream_fatal(struct vlc_h1_conn
*conn
)
128 if (conn
->conn
.tls
!= NULL
)
130 vlc_http_dbg(CO(conn
), "connection failed");
131 vlc_tls_Shutdown(conn
->conn
.tls
, true);
132 vlc_tls_Close(conn
->conn
.tls
);
133 conn
->conn
.tls
= NULL
;
138 static struct vlc_h1_conn
*vlc_h1_stream_conn(struct vlc_http_stream
*stream
)
140 return container_of(stream
, struct vlc_h1_conn
, stream
);
143 static struct vlc_http_stream
*vlc_h1_stream_open(struct vlc_http_conn
*c
,
144 const struct vlc_http_msg
*req
)
146 struct vlc_h1_conn
*conn
= container_of(c
, struct vlc_h1_conn
, conn
);
150 if (conn
->active
|| conn
->conn
.tls
== NULL
)
153 char *payload
= vlc_http_msg_format(req
, &len
, conn
->proxy
);
154 if (unlikely(payload
== NULL
))
157 vlc_http_dbg(CO(conn
), "outgoing request:\n%.*s", (int)len
, payload
);
158 val
= vlc_tls_Write(conn
->conn
.tls
, payload
, len
);
161 if (val
< (ssize_t
)len
)
162 return vlc_h1_stream_fatal(conn
);
165 conn
->content_length
= 0;
166 conn
->connection_close
= false;
167 return &conn
->stream
;
170 static struct vlc_http_msg
*vlc_h1_stream_wait(struct vlc_http_stream
*stream
)
172 struct vlc_h1_conn
*conn
= vlc_h1_stream_conn(stream
);
173 struct vlc_http_msg
*resp
;
178 assert(conn
->active
);
180 if (conn
->conn
.tls
== NULL
)
183 char *payload
= vlc_https_headers_recv(conn
->conn
.tls
, &len
);
185 return vlc_h1_stream_fatal(conn
);
187 vlc_http_dbg(CO(conn
), "incoming response:\n%.*s", (int)len
, payload
);
189 resp
= vlc_http_msg_headers(payload
);
190 minor
= vlc_http_minor(payload
);
194 return vlc_h1_stream_fatal(conn
);
198 conn
->content_length
= vlc_http_msg_get_size(resp
);
199 conn
->connection_close
= false;
203 if (vlc_http_msg_get_token(resp
, "Connection", "close") != NULL
)
204 conn
->connection_close
= true;
206 str
= vlc_http_msg_get_token(resp
, "Transfer-Encoding", "chunked");
209 if (vlc_http_next_token(str
) != NULL
)
211 vlc_http_msg_destroy(resp
);
212 return vlc_h1_stream_fatal(conn
); /* unsupported TE */
215 assert(conn
->content_length
== UINTMAX_MAX
);
216 stream
= vlc_chunked_open(stream
, conn
->conn
.tls
);
217 if (unlikely(stream
== NULL
))
219 vlc_http_msg_destroy(resp
);
220 return vlc_h1_stream_fatal(conn
);
225 conn
->connection_close
= true;
227 vlc_http_msg_attach(resp
, stream
);
231 static block_t
*vlc_h1_stream_read(struct vlc_http_stream
*stream
)
233 struct vlc_h1_conn
*conn
= vlc_h1_stream_conn(stream
);
236 assert(conn
->active
);
238 if (conn
->conn
.tls
== NULL
)
239 return vlc_http_error
;
241 if (size
> conn
->content_length
)
242 size
= conn
->content_length
;
246 block_t
*block
= block_Alloc(size
);
247 if (unlikely(block
== NULL
))
248 return vlc_http_error
;
250 ssize_t val
= vlc_tls_Read(conn
->conn
.tls
, block
->p_buffer
, size
, false);
253 block_Release(block
);
255 return vlc_http_error
;
256 if (conn
->content_length
!= UINTMAX_MAX
)
259 return vlc_http_error
;
264 block
->i_buffer
= val
;
265 if (conn
->content_length
!= UINTMAX_MAX
)
266 conn
->content_length
-= val
;
271 static void vlc_h1_stream_close(struct vlc_http_stream
*stream
, bool abort
)
273 struct vlc_h1_conn
*conn
= vlc_h1_stream_conn(stream
);
275 assert(conn
->active
);
278 vlc_h1_stream_fatal(conn
);
280 conn
->active
= false;
283 vlc_h1_conn_destroy(conn
);
286 static const struct vlc_http_stream_cbs vlc_h1_stream_callbacks
=
293 static void vlc_h1_conn_destroy(struct vlc_h1_conn
*conn
)
295 assert(!conn
->active
);
296 assert(conn
->released
);
298 if (conn
->conn
.tls
!= NULL
)
300 vlc_tls_Shutdown(conn
->conn
.tls
, true);
301 vlc_tls_Close(conn
->conn
.tls
);
306 static void vlc_h1_conn_release(struct vlc_http_conn
*c
)
308 struct vlc_h1_conn
*conn
= container_of(c
, struct vlc_h1_conn
, conn
);
310 assert(!conn
->released
);
311 conn
->released
= true;
314 vlc_h1_conn_destroy(conn
);
317 static const struct vlc_http_conn_cbs vlc_h1_conn_callbacks
=
323 struct vlc_http_conn
*vlc_h1_conn_create(void *ctx
, vlc_tls_t
*tls
, bool proxy
)
325 struct vlc_h1_conn
*conn
= malloc(sizeof (*conn
));
326 if (unlikely(conn
== NULL
))
329 conn
->conn
.cbs
= &vlc_h1_conn_callbacks
;
330 conn
->conn
.tls
= tls
;
331 conn
->stream
.cbs
= &vlc_h1_stream_callbacks
;
332 conn
->active
= false;
333 conn
->released
= false;
340 struct vlc_http_stream
*vlc_h1_request(void *ctx
, const char *hostname
,
341 unsigned port
, bool proxy
,
342 const struct vlc_http_msg
*req
,
344 struct vlc_http_conn
**restrict connp
)
346 struct addrinfo hints
=
348 .ai_socktype
= SOCK_STREAM
,
349 .ai_protocol
= IPPROTO_TCP
,
352 vlc_http_dbg(ctx
, "resolving %s ...", hostname
);
354 int val
= vlc_getaddrinfo_i11e(hostname
, port
, &hints
, &res
);
356 { /* TODO: C locale for gai_strerror() */
357 vlc_http_err(ctx
, "cannot resolve %s: %s", hostname
,
362 for (const struct addrinfo
*p
= res
; p
!= NULL
; p
= p
->ai_next
)
364 vlc_tls_t
*tcp
= vlc_tls_SocketOpenAddrInfo(p
, idempotent
);
367 vlc_http_err(ctx
, "socket error: %s", vlc_strerror_c(errno
));
371 struct vlc_http_conn
*conn
= vlc_h1_conn_create(ctx
, tcp
, proxy
);
372 if (unlikely(conn
== NULL
))
374 vlc_tls_SessionDelete(tcp
);
378 /* Send the HTTP request */
379 struct vlc_http_stream
*stream
= vlc_http_stream_open(conn
, req
);
386 vlc_http_conn_release(conn
);
392 vlc_http_conn_release(conn
);
395 break; /* If the request is nonidempotent, it cannot be resent. */
398 /* All address info failed. */