demux: adaptive: don't disconnect socket at EOF if connectionClose is false
[vlc.git] / modules / demux / adaptive / http / HTTPConnection.cpp
blob91e3709a3c40a5a0a3b308520a59ef4fba4c1650
1 /*
2 * HTTPConnection.cpp
3 *****************************************************************************
4 * Copyright (C) 2014-2015 - VideoLAN and VLC Authors
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
8 * by 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 *****************************************************************************/
20 #ifdef HAVE_CONFIG_H
21 # include "config.h"
22 #endif
24 #include "HTTPConnection.hpp"
25 #include "ConnectionParams.hpp"
26 #include "Sockets.hpp"
27 #include "../adaptive/tools/Helper.h"
29 #include <cstdio>
30 #include <sstream>
31 #include <vlc_stream.h>
33 using namespace adaptive::http;
35 AbstractConnection::AbstractConnection(vlc_object_t *p_object_)
37 p_object = p_object_;
38 available = true;
39 bytesRead = 0;
40 contentLength = 0;
43 AbstractConnection::~AbstractConnection()
48 bool AbstractConnection::prepare(const ConnectionParams &params_)
50 if (!available)
51 return false;
52 params = params_;
53 available = false;
54 return true;
57 size_t AbstractConnection::getContentLength() const
59 return contentLength;
62 HTTPConnection::HTTPConnection(vlc_object_t *p_object_, Socket *socket_, bool persistent)
63 : AbstractConnection( p_object_ )
65 socket = socket_;
66 psz_useragent = var_InheritString(p_object_, "http-user-agent");
67 queryOk = false;
68 retries = 0;
69 connectionClose = !persistent;
70 chunked = false;
71 chunked_eof = false;
72 chunkLength = 0;
75 HTTPConnection::~HTTPConnection()
77 free(psz_useragent);
78 delete socket;
81 bool HTTPConnection::canReuse(const ConnectionParams &params_) const
83 return ( available &&
84 params.getHostname() == params_.getHostname() &&
85 params.getScheme() == params_.getScheme() &&
86 params.getPort() == params_.getPort() );
89 bool HTTPConnection::connect()
91 return socket->connect(p_object, params.getHostname().c_str(),
92 params.getPort());
95 bool HTTPConnection::connected() const
97 return socket->connected();
100 void HTTPConnection::disconnect()
102 queryOk = false;
103 bytesRead = 0;
104 contentLength = 0;
105 chunked = false;
106 chunkLength = 0;
107 bytesRange = BytesRange();
108 socket->disconnect();
111 int HTTPConnection::request(const std::string &path, const BytesRange &range)
113 queryOk = false;
114 chunked = false;
115 chunked_eof = false;
116 chunkLength = 0;
118 /* Set new path for this query */
119 params.setPath(path);
121 msg_Dbg(p_object, "Retrieving %s @%zu", params.getUrl().c_str(),
122 range.isValid() ? range.getStartByte() : 0);
124 if(!connected() && ( params.getHostname().empty() || !connect() ))
125 return VLC_EGENERIC;
127 bytesRange = range;
128 if(range.isValid() && range.getEndByte() > 0)
129 contentLength = range.getEndByte() - range.getStartByte() + 1;
131 std::string header = buildRequestHeader(path);
132 if(connectionClose)
133 header.append("Connection: close\r\n");
134 header.append("\r\n");
136 if(!send( header ))
138 socket->disconnect();
139 if(!connectionClose)
141 /* server closed connection pipeline after last req. need new */
142 connectionClose = true;
143 return request(path, range);
145 return VLC_EGENERIC;
148 int i_ret = parseReply();
149 if(i_ret == VLC_SUCCESS)
151 queryOk = true;
153 else if(i_ret == VLC_ETIMEOUT) /* redir */
155 socket->disconnect();
156 if(locationparams.getScheme().empty())
157 params.setPath(locationparams.getPath());
158 else
159 params = locationparams;
160 locationparams = ConnectionParams();
162 else if(i_ret == VLC_EGENERIC)
164 socket->disconnect();
165 if(!connectionClose)
167 connectionClose = true;
168 return request(path, range);
172 return i_ret;
175 ssize_t HTTPConnection::read(void *p_buffer, size_t len)
177 if( !connected() ||
178 (!queryOk && bytesRead == 0) )
179 return VLC_EGENERIC;
181 if(len == 0)
182 return VLC_SUCCESS;
184 queryOk = false;
186 const size_t toRead = (contentLength) ? contentLength - bytesRead : len;
187 if (toRead == 0)
188 return VLC_SUCCESS;
190 if(len > toRead)
191 len = toRead;
193 ssize_t ret = ( chunked ) ? readChunk(p_buffer, len)
194 : socket->read(p_object, p_buffer, len);
195 if(ret >= 0)
196 bytesRead += ret;
198 if(ret < 0 || (size_t)ret < len || /* set EOF */
199 (contentLength == bytesRead && connectionClose))
201 socket->disconnect();
202 return ret;
205 return ret;
208 bool HTTPConnection::send(const std::string &data)
210 return send(data.c_str(), data.length());
213 bool HTTPConnection::send(const void *buf, size_t size)
215 return socket->send(p_object, buf, size);
218 int HTTPConnection::parseReply()
220 std::string statusline = readLine();
222 if(statusline.empty())
223 return VLC_EGENERIC;
225 if (statusline.compare(0, 9, "HTTP/1.1 ")!=0)
227 if(statusline.compare(0, 9, "HTTP/1.0 ")!=0)
228 return VLC_ENOOBJ;
229 else
230 connectionClose = true;
233 std::istringstream ss(statusline.substr(9));
234 ss.imbue(std::locale("C"));
235 int replycode;
236 ss >> replycode;
238 std::string line = readLine();
240 while(!line.empty() && line.compare("\r\n"))
242 size_t split = line.find_first_of(':');
243 size_t value = split + 1;
245 while(line.at(value) == ' ')
246 value++;
248 onHeader(line.substr(0, split), line.substr(value));
249 line = readLine();
252 if((replycode == 301 || replycode == 302 || replycode == 307 || replycode == 308) &&
253 !locationparams.getUrl().empty())
255 msg_Info(p_object, "%d redirection to %s", replycode, locationparams.getUrl().c_str());
256 return VLC_ETIMEOUT;
258 else if (replycode != 200 && replycode != 206)
260 msg_Err(p_object, "Failed reading %s: %s", params.getUrl().c_str(), statusline.c_str());
261 return VLC_ENOOBJ;
264 return VLC_SUCCESS;
267 ssize_t HTTPConnection::readChunk(void *p_buffer, size_t len)
269 size_t copied = 0;
271 for( ; copied < len && !chunked_eof; )
273 /* adapted from access/http/chunked.c */
274 if(chunkLength == 0)
276 std::string line = readLine();
277 int end;
278 if (std::sscanf(line.c_str(), "%zx%n", &chunkLength, &end) < 1
279 || (line[end] != '\0' && line[end] != ';' /* ignore extension(s) */))
280 return -1;
283 if(chunkLength > 0)
285 size_t toread = len - copied;
286 if(toread > chunkLength)
287 toread = chunkLength;
289 ssize_t in = socket->read(p_object, &((uint8_t*)p_buffer)[copied], toread);
290 if(in < 0)
292 return (copied == 0) ? in : copied;
294 else if((size_t)in < toread)
296 return copied + in;
298 copied += in;
299 chunkLength -= in;
301 else chunked_eof = true;
303 if(chunkLength == 0)
305 char crlf[2];
306 ssize_t in = socket->read(p_object, &crlf, 2);
307 if(in < 2 || memcmp(crlf, "\r\n", 2))
308 return (copied == 0) ? -1 : copied;
312 return copied;
315 std::string HTTPConnection::readLine()
317 return socket->readline(p_object);
320 void HTTPConnection::setUsed( bool b )
322 available = !b;
323 if(available)
325 if(!connectionClose && contentLength == bytesRead )
327 queryOk = false;
328 bytesRead = 0;
329 contentLength = 0;
330 bytesRange = BytesRange();
332 else /* We can't resend request if we haven't finished reading */
333 disconnect();
337 void HTTPConnection::onHeader(const std::string &key,
338 const std::string &value)
340 if(key == "Content-Length")
342 std::istringstream ss(value);
343 ss.imbue(std::locale("C"));
344 size_t length;
345 ss >> length;
346 contentLength = length;
348 else if (key == "Connection" && value =="close")
350 connectionClose = true;
352 else if (key == "Transfer-Encoding" && value == "chunked")
354 chunked = true;
356 else if(key == "Location")
358 locationparams = ConnectionParams( value );
362 std::string HTTPConnection::buildRequestHeader(const std::string &path) const
364 std::stringstream req;
365 req << "GET " << path << " HTTP/1.1\r\n";
366 if((params.getScheme() == "http" && params.getPort() != 80) ||
367 (params.getScheme() == "https" && params.getPort() != 443))
369 req << "Host: " << params.getHostname() << ":" << params.getPort() << "\r\n";
371 else
373 req << "Host: " << params.getHostname() << "\r\n";
375 req << "Cache-Control: no-cache" << "\r\n" <<
376 "User-Agent: " << std::string(psz_useragent) << "\r\n";
377 req << extraRequestHeaders();
378 return req.str();
381 std::string HTTPConnection::extraRequestHeaders() const
383 std::stringstream ss;
384 ss.imbue(std::locale("C"));
385 if(bytesRange.isValid())
387 ss << "Range: bytes=" << bytesRange.getStartByte() << "-";
388 if(bytesRange.getEndByte())
389 ss << bytesRange.getEndByte();
390 ss << "\r\n";
392 return ss.str();
395 StreamUrlConnection::StreamUrlConnection(vlc_object_t *p_object)
396 : AbstractConnection(p_object)
398 p_streamurl = NULL;
399 bytesRead = 0;
400 contentLength = 0;
403 StreamUrlConnection::~StreamUrlConnection()
405 reset();
408 void StreamUrlConnection::reset()
410 if(p_streamurl)
411 vlc_stream_Delete(p_streamurl);
412 p_streamurl = NULL;
413 bytesRead = 0;
414 contentLength = 0;
415 bytesRange = BytesRange();
418 bool StreamUrlConnection::canReuse(const ConnectionParams &) const
420 return available;
423 int StreamUrlConnection::request(const std::string &path, const BytesRange &range)
425 reset();
427 /* Set new path for this query */
428 params.setPath(path);
430 msg_Dbg(p_object, "Retrieving %s @%zu", params.getUrl().c_str(),
431 range.isValid() ? range.getStartByte() : 0);
433 p_streamurl = vlc_stream_NewURL(p_object, params.getUrl().c_str());
434 if(!p_streamurl)
435 return VLC_EGENERIC;
437 stream_t *p_chain = vlc_stream_FilterNew( p_streamurl, "inflate" );
438 if( p_chain )
439 p_streamurl = p_chain;
441 if(range.isValid() && range.getEndByte() > 0)
443 if(vlc_stream_Seek(p_streamurl, range.getStartByte()) != VLC_SUCCESS)
445 vlc_stream_Delete(p_streamurl);
446 return VLC_EGENERIC;
448 bytesRange = range;
449 contentLength = range.getEndByte() - range.getStartByte() + 1;
452 int64_t i_size = stream_Size(p_streamurl);
453 if(i_size > -1)
455 if(!range.isValid() || contentLength > (size_t) i_size)
456 contentLength = (size_t) i_size;
458 return VLC_SUCCESS;
461 ssize_t StreamUrlConnection::read(void *p_buffer, size_t len)
463 if( !p_streamurl )
464 return VLC_EGENERIC;
466 if(len == 0)
467 return VLC_SUCCESS;
469 const size_t toRead = (contentLength) ? contentLength - bytesRead : len;
470 if (toRead == 0)
471 return VLC_SUCCESS;
473 if(len > toRead)
474 len = toRead;
476 ssize_t ret = vlc_stream_Read(p_streamurl, p_buffer, len);
477 if(ret >= 0)
478 bytesRead += ret;
480 if(ret < 0 || (size_t)ret < len || /* set EOF */
481 contentLength == bytesRead )
483 reset();
484 return ret;
487 return ret;
490 void StreamUrlConnection::setUsed( bool b )
492 available = !b;
493 if(available && contentLength == bytesRead)
494 reset();
497 ConnectionFactory::ConnectionFactory()
501 ConnectionFactory::~ConnectionFactory()
505 AbstractConnection * ConnectionFactory::createConnection(vlc_object_t *p_object,
506 const ConnectionParams &params)
508 if((params.getScheme() != "http" && params.getScheme() != "https") || params.getHostname().empty())
509 return NULL;
511 const int sockettype = (params.getScheme() == "https") ? TLSSocket::TLS : Socket::REGULAR;
512 Socket *socket = (sockettype == TLSSocket::TLS) ? new (std::nothrow) TLSSocket()
513 : new (std::nothrow) Socket();
514 if(!socket)
515 return NULL;
517 /* disable pipelined tls until we have ticket/resume session support */
518 HTTPConnection *conn = new (std::nothrow)
519 HTTPConnection(p_object, socket, sockettype != TLSSocket::TLS);
520 if(!conn)
522 delete socket;
523 return NULL;
526 return conn;
529 AbstractConnection * StreamUrlConnectionFactory::createConnection(vlc_object_t *p_object,
530 const ConnectionParams &)
532 return new (std::nothrow) StreamUrlConnection(p_object);