demux: adaptive: fix out of range exception
[vlc.git] / modules / demux / adaptive / http / HTTPConnection.cpp
blobf68bdab7c424a686a081e5303afbe5aba9c8cad7
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 lines;
239 for( ;; )
241 std::string l = readLine();
242 if(l.empty())
243 break;
244 lines.append(l);
246 size_t split = lines.find_first_of(':');
247 if(split != std::string::npos)
249 size_t value = lines.find_first_not_of(' ', split + 1);
250 if(value == std::string::npos)
251 value = lines.length();
252 onHeader(lines.substr(0, split), lines.substr(value));
253 lines = std::string();
257 if((replycode == 301 || replycode == 302 || replycode == 307 || replycode == 308) &&
258 !locationparams.getUrl().empty())
260 msg_Info(p_object, "%d redirection to %s", replycode, locationparams.getUrl().c_str());
261 return VLC_ETIMEOUT;
263 else if (replycode != 200 && replycode != 206)
265 msg_Err(p_object, "Failed reading %s: %s", params.getUrl().c_str(), statusline.c_str());
266 return VLC_ENOOBJ;
269 return VLC_SUCCESS;
272 ssize_t HTTPConnection::readChunk(void *p_buffer, size_t len)
274 size_t copied = 0;
276 for( ; copied < len && !chunked_eof; )
278 /* adapted from access/http/chunked.c */
279 if(chunkLength == 0)
281 std::string line = readLine();
282 int end;
283 if (std::sscanf(line.c_str(), "%zx%n", &chunkLength, &end) < 1
284 || (line[end] != '\0' && line[end] != ';' /* ignore extension(s) */))
285 return -1;
288 if(chunkLength > 0)
290 size_t toread = len - copied;
291 if(toread > chunkLength)
292 toread = chunkLength;
294 ssize_t in = socket->read(p_object, &((uint8_t*)p_buffer)[copied], toread);
295 if(in < 0)
297 return (copied == 0) ? in : copied;
299 else if((size_t)in < toread)
301 return copied + in;
303 copied += in;
304 chunkLength -= in;
306 else chunked_eof = true;
308 if(chunkLength == 0)
310 char crlf[2];
311 ssize_t in = socket->read(p_object, &crlf, 2);
312 if(in < 2 || memcmp(crlf, "\r\n", 2))
313 return (copied == 0) ? -1 : copied;
317 return copied;
320 std::string HTTPConnection::readLine()
322 return socket->readline(p_object);
325 void HTTPConnection::setUsed( bool b )
327 available = !b;
328 if(available)
330 if(!connectionClose && contentLength == bytesRead )
332 queryOk = false;
333 bytesRead = 0;
334 contentLength = 0;
335 bytesRange = BytesRange();
337 else /* We can't resend request if we haven't finished reading */
338 disconnect();
342 void HTTPConnection::onHeader(const std::string &key,
343 const std::string &value)
345 if(key == "Content-Length")
347 std::istringstream ss(value);
348 ss.imbue(std::locale("C"));
349 size_t length;
350 ss >> length;
351 contentLength = length;
353 else if (key == "Connection" && value =="close")
355 connectionClose = true;
357 else if (key == "Transfer-Encoding" && value == "chunked")
359 chunked = true;
361 else if(key == "Location")
363 locationparams = ConnectionParams( value );
367 std::string HTTPConnection::buildRequestHeader(const std::string &path) const
369 std::stringstream req;
370 req << "GET " << path << " HTTP/1.1\r\n";
371 if((params.getScheme() == "http" && params.getPort() != 80) ||
372 (params.getScheme() == "https" && params.getPort() != 443))
374 req << "Host: " << params.getHostname() << ":" << params.getPort() << "\r\n";
376 else
378 req << "Host: " << params.getHostname() << "\r\n";
380 req << "Cache-Control: no-cache" << "\r\n" <<
381 "User-Agent: " << std::string(psz_useragent) << "\r\n";
382 req << extraRequestHeaders();
383 return req.str();
386 std::string HTTPConnection::extraRequestHeaders() const
388 std::stringstream ss;
389 ss.imbue(std::locale("C"));
390 if(bytesRange.isValid())
392 ss << "Range: bytes=" << bytesRange.getStartByte() << "-";
393 if(bytesRange.getEndByte())
394 ss << bytesRange.getEndByte();
395 ss << "\r\n";
397 return ss.str();
400 StreamUrlConnection::StreamUrlConnection(vlc_object_t *p_object)
401 : AbstractConnection(p_object)
403 p_streamurl = NULL;
404 bytesRead = 0;
405 contentLength = 0;
408 StreamUrlConnection::~StreamUrlConnection()
410 reset();
413 void StreamUrlConnection::reset()
415 if(p_streamurl)
416 vlc_stream_Delete(p_streamurl);
417 p_streamurl = NULL;
418 bytesRead = 0;
419 contentLength = 0;
420 bytesRange = BytesRange();
423 bool StreamUrlConnection::canReuse(const ConnectionParams &) const
425 return available;
428 int StreamUrlConnection::request(const std::string &path, const BytesRange &range)
430 reset();
432 /* Set new path for this query */
433 params.setPath(path);
435 msg_Dbg(p_object, "Retrieving %s @%zu", params.getUrl().c_str(),
436 range.isValid() ? range.getStartByte() : 0);
438 p_streamurl = vlc_stream_NewURL(p_object, params.getUrl().c_str());
439 if(!p_streamurl)
440 return VLC_EGENERIC;
442 stream_t *p_chain = vlc_stream_FilterNew( p_streamurl, "inflate" );
443 if( p_chain )
444 p_streamurl = p_chain;
446 if(range.isValid() && range.getEndByte() > 0)
448 if(vlc_stream_Seek(p_streamurl, range.getStartByte()) != VLC_SUCCESS)
450 vlc_stream_Delete(p_streamurl);
451 return VLC_EGENERIC;
453 bytesRange = range;
454 contentLength = range.getEndByte() - range.getStartByte() + 1;
457 int64_t i_size = stream_Size(p_streamurl);
458 if(i_size > -1)
460 if(!range.isValid() || contentLength > (size_t) i_size)
461 contentLength = (size_t) i_size;
463 return VLC_SUCCESS;
466 ssize_t StreamUrlConnection::read(void *p_buffer, size_t len)
468 if( !p_streamurl )
469 return VLC_EGENERIC;
471 if(len == 0)
472 return VLC_SUCCESS;
474 const size_t toRead = (contentLength) ? contentLength - bytesRead : len;
475 if (toRead == 0)
476 return VLC_SUCCESS;
478 if(len > toRead)
479 len = toRead;
481 ssize_t ret = vlc_stream_Read(p_streamurl, p_buffer, len);
482 if(ret >= 0)
483 bytesRead += ret;
485 if(ret < 0 || (size_t)ret < len || /* set EOF */
486 contentLength == bytesRead )
488 reset();
489 return ret;
492 return ret;
495 void StreamUrlConnection::setUsed( bool b )
497 available = !b;
498 if(available && contentLength == bytesRead)
499 reset();
502 ConnectionFactory::ConnectionFactory()
506 ConnectionFactory::~ConnectionFactory()
510 AbstractConnection * ConnectionFactory::createConnection(vlc_object_t *p_object,
511 const ConnectionParams &params)
513 if((params.getScheme() != "http" && params.getScheme() != "https") || params.getHostname().empty())
514 return NULL;
516 const int sockettype = (params.getScheme() == "https") ? TLSSocket::TLS : Socket::REGULAR;
517 Socket *socket = (sockettype == TLSSocket::TLS) ? new (std::nothrow) TLSSocket()
518 : new (std::nothrow) Socket();
519 if(!socket)
520 return NULL;
522 /* disable pipelined tls until we have ticket/resume session support */
523 HTTPConnection *conn = new (std::nothrow)
524 HTTPConnection(p_object, socket, sockettype != TLSSocket::TLS);
525 if(!conn)
527 delete socket;
528 return NULL;
531 return conn;
534 AbstractConnection * StreamUrlConnectionFactory::createConnection(vlc_object_t *p_object,
535 const ConnectionParams &)
537 return new (std::nothrow) StreamUrlConnection(p_object);