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 *****************************************************************************/
24 #include "HTTPConnection.hpp"
25 #include "ConnectionParams.hpp"
26 #include "Sockets.hpp"
27 #include "../adaptive/tools/Helper.h"
31 #include <vlc_stream.h>
33 using namespace adaptive::http
;
35 AbstractConnection::AbstractConnection(vlc_object_t
*p_object_
)
43 AbstractConnection::~AbstractConnection()
48 bool AbstractConnection::prepare(const ConnectionParams
¶ms_
)
57 size_t AbstractConnection::getContentLength() const
62 HTTPConnection::HTTPConnection(vlc_object_t
*p_object_
, Socket
*socket_
, bool persistent
)
63 : AbstractConnection( p_object_
)
66 psz_useragent
= var_InheritString(p_object_
, "http-user-agent");
69 connectionClose
= !persistent
;
75 HTTPConnection::~HTTPConnection()
81 bool HTTPConnection::canReuse(const ConnectionParams
¶ms_
) const
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(),
95 bool HTTPConnection::connected() const
97 return socket
->connected();
100 void HTTPConnection::disconnect()
107 bytesRange
= BytesRange();
108 socket
->disconnect();
111 int HTTPConnection::request(const std::string
&path
, const BytesRange
&range
)
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() ))
128 if(range
.isValid() && range
.getEndByte() > 0)
129 contentLength
= range
.getEndByte() - range
.getStartByte() + 1;
131 std::string header
= buildRequestHeader(path
);
133 header
.append("Connection: close\r\n");
134 header
.append("\r\n");
138 socket
->disconnect();
141 /* server closed connection pipeline after last req. need new */
142 connectionClose
= true;
143 return request(path
, range
);
148 int i_ret
= parseReply();
149 if(i_ret
== VLC_SUCCESS
)
153 else if(i_ret
== VLC_ETIMEOUT
) /* redir */
155 socket
->disconnect();
156 if(locationparams
.getScheme().empty())
157 params
.setPath(locationparams
.getPath());
159 params
= locationparams
;
160 locationparams
= ConnectionParams();
162 else if(i_ret
== VLC_EGENERIC
)
164 socket
->disconnect();
167 connectionClose
= true;
168 return request(path
, range
);
175 ssize_t
HTTPConnection::read(void *p_buffer
, size_t len
)
178 (!queryOk
&& bytesRead
== 0) )
186 const size_t toRead
= (contentLength
) ? contentLength
- bytesRead
: len
;
193 ssize_t ret
= ( chunked
) ? readChunk(p_buffer
, len
)
194 : socket
->read(p_object
, p_buffer
, len
);
198 if(ret
< 0 || (size_t)ret
< len
|| /* set EOF */
199 (contentLength
== bytesRead
&& connectionClose
))
201 socket
->disconnect();
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())
225 if (statusline
.compare(0, 9, "HTTP/1.1 ")!=0)
227 if(statusline
.compare(0, 9, "HTTP/1.0 ")!=0)
230 connectionClose
= true;
233 std::istringstream
ss(statusline
.substr(9));
234 ss
.imbue(std::locale("C"));
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
) == ' ')
248 onHeader(line
.substr(0, split
), line
.substr(value
));
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());
258 else if (replycode
!= 200 && replycode
!= 206)
260 msg_Err(p_object
, "Failed reading %s: %s", params
.getUrl().c_str(), statusline
.c_str());
267 ssize_t
HTTPConnection::readChunk(void *p_buffer
, size_t len
)
271 for( ; copied
< len
&& !chunked_eof
; )
273 /* adapted from access/http/chunked.c */
276 std::string line
= readLine();
278 if (std::sscanf(line
.c_str(), "%zx%n", &chunkLength
, &end
) < 1
279 || (line
[end
] != '\0' && line
[end
] != ';' /* ignore extension(s) */))
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
);
292 return (copied
== 0) ? in
: copied
;
294 else if((size_t)in
< toread
)
301 else chunked_eof
= true;
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
;
315 std::string
HTTPConnection::readLine()
317 return socket
->readline(p_object
);
320 void HTTPConnection::setUsed( bool b
)
325 if(!connectionClose
&& contentLength
== bytesRead
)
330 bytesRange
= BytesRange();
332 else /* We can't resend request if we haven't finished reading */
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"));
346 contentLength
= length
;
348 else if (key
== "Connection" && value
=="close")
350 connectionClose
= true;
352 else if (key
== "Transfer-Encoding" && value
== "chunked")
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";
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();
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();
395 StreamUrlConnection::StreamUrlConnection(vlc_object_t
*p_object
)
396 : AbstractConnection(p_object
)
403 StreamUrlConnection::~StreamUrlConnection()
408 void StreamUrlConnection::reset()
411 vlc_stream_Delete(p_streamurl
);
415 bytesRange
= BytesRange();
418 bool StreamUrlConnection::canReuse(const ConnectionParams
&) const
423 int StreamUrlConnection::request(const std::string
&path
, const BytesRange
&range
)
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());
437 stream_t
*p_chain
= vlc_stream_FilterNew( p_streamurl
, "inflate" );
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
);
449 contentLength
= range
.getEndByte() - range
.getStartByte() + 1;
452 int64_t i_size
= stream_Size(p_streamurl
);
455 if(!range
.isValid() || contentLength
> (size_t) i_size
)
456 contentLength
= (size_t) i_size
;
461 ssize_t
StreamUrlConnection::read(void *p_buffer
, size_t len
)
469 const size_t toRead
= (contentLength
) ? contentLength
- bytesRead
: len
;
476 ssize_t ret
= vlc_stream_Read(p_streamurl
, p_buffer
, len
);
480 if(ret
< 0 || (size_t)ret
< len
|| /* set EOF */
481 contentLength
== bytesRead
)
490 void StreamUrlConnection::setUsed( bool b
)
493 if(available
&& contentLength
== bytesRead
)
497 ConnectionFactory::ConnectionFactory()
501 ConnectionFactory::~ConnectionFactory()
505 AbstractConnection
* ConnectionFactory::createConnection(vlc_object_t
*p_object
,
506 const ConnectionParams
¶ms
)
508 if((params
.getScheme() != "http" && params
.getScheme() != "https") || params
.getHostname().empty())
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();
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
);
529 AbstractConnection
* StreamUrlConnectionFactory::createConnection(vlc_object_t
*p_object
,
530 const ConnectionParams
&)
532 return new (std::nothrow
) StreamUrlConnection(p_object
);