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 "AuthStorage.hpp"
27 #include "Sockets.hpp"
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_
, AuthStorage
*auth
,
63 Socket
*socket_
, const ConnectionParams
&proxy
, bool persistent
)
64 : AbstractConnection( p_object_
)
67 psz_useragent
= var_InheritString(p_object_
, "http-user-agent");
71 connectionClose
= !persistent
;
78 HTTPConnection::~HTTPConnection()
84 bool HTTPConnection::canReuse(const ConnectionParams
¶ms_
) const
89 char *psz_proxy_url
= vlc_getProxyUrl(params_
.getUrl().c_str());
92 ConnectionParams
proxy(psz_proxy_url
);
94 return (proxyparams
.getHostname() == proxy
.getHostname() &&
95 proxyparams
.getScheme() == proxy
.getScheme() &&
96 proxyparams
.getPort() == proxy
.getPort());
98 else return (params
.getHostname() == params_
.getHostname() &&
99 params
.getScheme() == params_
.getScheme() &&
100 params
.getPort() == params_
.getPort());
103 bool HTTPConnection::connect()
105 if(proxyparams
.getHostname().empty())
106 return socket
->connect(p_object
, params
.getHostname().c_str(),
109 return socket
->connect(p_object
, proxyparams
.getHostname().c_str(),
110 proxyparams
.getPort());
113 bool HTTPConnection::connected() const
115 return socket
->connected();
118 void HTTPConnection::disconnect()
125 bytesRange
= BytesRange();
126 socket
->disconnect();
129 int HTTPConnection::request(const std::string
&path
, const BytesRange
&range
)
136 /* Set new path for this query */
137 params
.setPath(path
);
139 msg_Dbg(p_object
, "Retrieving %s @%zu", params
.getUrl().c_str(),
140 range
.isValid() ? range
.getStartByte() : 0);
142 if(!proxyparams
.getHostname().empty())
143 msg_Dbg(p_object
, "Using proxy %s", proxyparams
.getUrl().c_str());
145 if(!connected() && ( params
.getHostname().empty() || !connect() ))
149 if(range
.isValid() && range
.getEndByte() > 0)
150 contentLength
= range
.getEndByte() - range
.getStartByte() + 1;
152 std::string header
= buildRequestHeader(path
);
154 header
.append("Connection: close\r\n");
155 header
.append("\r\n");
159 socket
->disconnect();
162 /* server closed connection pipeline after last req. need new */
163 connectionClose
= true;
164 return request(path
, range
);
169 int i_ret
= parseReply();
170 if(i_ret
== VLC_SUCCESS
)
174 else if(i_ret
== VLC_ETIMEOUT
) /* redir */
176 socket
->disconnect();
177 if(locationparams
.getScheme().empty())
178 params
.setPath(locationparams
.getPath());
180 params
= locationparams
;
181 locationparams
= ConnectionParams();
183 else if(i_ret
== VLC_EGENERIC
)
185 socket
->disconnect();
188 connectionClose
= true;
189 return request(path
, range
);
196 ssize_t
HTTPConnection::read(void *p_buffer
, size_t len
)
199 (!queryOk
&& bytesRead
== 0) )
207 const size_t toRead
= (contentLength
) ? contentLength
- bytesRead
: len
;
214 ssize_t ret
= ( chunked
) ? readChunk(p_buffer
, len
)
215 : socket
->read(p_object
, p_buffer
, len
);
219 if(ret
< 0 || (size_t)ret
< len
|| /* set EOF */
220 (contentLength
== bytesRead
&& connectionClose
))
222 socket
->disconnect();
229 bool HTTPConnection::send(const std::string
&data
)
231 return send(data
.c_str(), data
.length());
234 bool HTTPConnection::send(const void *buf
, size_t size
)
236 return socket
->send(p_object
, buf
, size
);
239 int HTTPConnection::parseReply()
241 std::string statusline
= readLine();
243 if(statusline
.empty())
246 if (statusline
.compare(0, 9, "HTTP/1.1 ")!=0)
248 if(statusline
.compare(0, 9, "HTTP/1.0 ")!=0)
251 connectionClose
= true;
254 std::istringstream
ss(statusline
.substr(9));
255 ss
.imbue(std::locale("C"));
262 std::string l
= readLine();
267 size_t split
= lines
.find_first_of(':');
268 if(split
!= std::string::npos
)
270 size_t value
= lines
.find_first_not_of(' ', split
+ 1);
271 if(value
== std::string::npos
)
272 value
= lines
.length();
273 onHeader(lines
.substr(0, split
), lines
.substr(value
));
274 lines
= std::string();
278 if((replycode
== 301 || replycode
== 302 || replycode
== 307 || replycode
== 308) &&
279 !locationparams
.getUrl().empty())
281 msg_Info(p_object
, "%d redirection to %s", replycode
, locationparams
.getUrl().c_str());
284 else if (replycode
!= 200 && replycode
!= 206)
286 msg_Err(p_object
, "Failed reading %s: %s", params
.getUrl().c_str(), statusline
.c_str());
293 ssize_t
HTTPConnection::readChunk(void *p_buffer
, size_t len
)
297 for( ; copied
< len
&& !chunked_eof
; )
299 /* adapted from access/http/chunked.c */
302 std::string line
= readLine();
304 if (std::sscanf(line
.c_str(), "%zx%n", &chunkLength
, &end
) < 1
305 || (line
[end
] != '\0' && line
[end
] != ';' /* ignore extension(s) */))
311 size_t toread
= len
- copied
;
312 if(toread
> chunkLength
)
313 toread
= chunkLength
;
315 ssize_t in
= socket
->read(p_object
, &((uint8_t*)p_buffer
)[copied
], toread
);
318 return (copied
== 0) ? in
: copied
;
320 else if((size_t)in
< toread
)
327 else chunked_eof
= true;
332 ssize_t in
= socket
->read(p_object
, &crlf
, 2);
333 if(in
< 2 || memcmp(crlf
, "\r\n", 2))
334 return (copied
== 0) ? -1 : copied
;
341 std::string
HTTPConnection::readLine()
343 return socket
->readline(p_object
);
346 void HTTPConnection::setUsed( bool b
)
351 if(!connectionClose
&& contentLength
== bytesRead
)
356 bytesRange
= BytesRange();
358 else /* We can't resend request if we haven't finished reading */
363 void HTTPConnection::onHeader(const std::string
&key
,
364 const std::string
&value
)
366 if(key
== "Content-Length")
368 std::istringstream
ss(value
);
369 ss
.imbue(std::locale("C"));
372 contentLength
= length
;
374 else if (key
== "Connection" && value
=="close")
376 connectionClose
= true;
378 else if (key
== "Transfer-Encoding" && value
== "chunked")
382 else if(key
== "Location")
384 locationparams
= ConnectionParams( value
);
386 else if(key
== "Set-Cookie" && authStorage
)
388 authStorage
->addCookie( value
, params
);
392 std::string
HTTPConnection::buildRequestHeader(const std::string
&path
) const
394 std::stringstream req
;
395 req
.imbue(std::locale("C"));
396 req
<< "GET " << path
<< " HTTP/1.1\r\n";
397 if((params
.getScheme() == "http" && params
.getPort() != 80) ||
398 (params
.getScheme() == "https" && params
.getPort() != 443))
400 req
<< "Host: " << params
.getHostname() << ":" << params
.getPort() << "\r\n";
404 req
<< "Host: " << params
.getHostname() << "\r\n";
408 std::string cookie
= authStorage
->getCookie(params
,
409 params
.getScheme() == "https" ||
410 params
.getPort() == 443);
412 req
<< "Cookie: " << cookie
<< "\r\n";
414 req
<< "Cache-Control: no-cache" << "\r\n" <<
415 "User-Agent: " << std::string(psz_useragent
) << "\r\n";
416 req
<< extraRequestHeaders();
420 std::string
HTTPConnection::extraRequestHeaders() const
422 std::stringstream ss
;
423 ss
.imbue(std::locale("C"));
424 if(bytesRange
.isValid())
426 ss
<< "Range: bytes=" << bytesRange
.getStartByte() << "-";
427 if(bytesRange
.getEndByte())
428 ss
<< bytesRange
.getEndByte();
434 StreamUrlConnection::StreamUrlConnection(vlc_object_t
*p_object
)
435 : AbstractConnection(p_object
)
442 StreamUrlConnection::~StreamUrlConnection()
447 void StreamUrlConnection::reset()
450 vlc_stream_Delete(p_streamurl
);
454 bytesRange
= BytesRange();
457 bool StreamUrlConnection::canReuse(const ConnectionParams
&) const
462 int StreamUrlConnection::request(const std::string
&path
, const BytesRange
&range
)
466 /* Set new path for this query */
467 params
.setPath(path
);
469 msg_Dbg(p_object
, "Retrieving %s @%zu", params
.getUrl().c_str(),
470 range
.isValid() ? range
.getStartByte() : 0);
472 p_streamurl
= vlc_stream_NewURL(p_object
, params
.getUrl().c_str());
476 stream_t
*p_chain
= vlc_stream_FilterNew( p_streamurl
, "inflate" );
478 p_streamurl
= p_chain
;
480 if(range
.isValid() && range
.getEndByte() > 0)
482 if(vlc_stream_Seek(p_streamurl
, range
.getStartByte()) != VLC_SUCCESS
)
484 vlc_stream_Delete(p_streamurl
);
488 contentLength
= range
.getEndByte() - range
.getStartByte() + 1;
491 int64_t i_size
= stream_Size(p_streamurl
);
494 if(!range
.isValid() || contentLength
> (size_t) i_size
)
495 contentLength
= (size_t) i_size
;
500 ssize_t
StreamUrlConnection::read(void *p_buffer
, size_t len
)
508 const size_t toRead
= (contentLength
) ? contentLength
- bytesRead
: len
;
515 ssize_t ret
= vlc_stream_Read(p_streamurl
, p_buffer
, len
);
519 if(ret
< 0 || (size_t)ret
< len
|| /* set EOF */
520 contentLength
== bytesRead
)
529 void StreamUrlConnection::setUsed( bool b
)
532 if(available
&& contentLength
== bytesRead
)
536 ConnectionFactory::ConnectionFactory( AuthStorage
*auth
)
541 ConnectionFactory::~ConnectionFactory()
545 AbstractConnection
* ConnectionFactory::createConnection(vlc_object_t
*p_object
,
546 const ConnectionParams
¶ms
)
548 if((params
.getScheme() != "http" && params
.getScheme() != "https") || params
.getHostname().empty())
551 ConnectionParams proxy
;
554 char *psz_proxy_url
= vlc_getProxyUrl(params
.getUrl().c_str());
557 proxy
= ConnectionParams(psz_proxy_url
);
559 scheme
= proxy
.getScheme();
561 else scheme
= params
.getScheme();
563 const int sockettype
= (params
.getScheme() == "https") ? TLSSocket::TLS
: Socket::REGULAR
;
564 Socket
*socket
= (sockettype
== TLSSocket::TLS
) ? new (std::nothrow
) TLSSocket()
565 : new (std::nothrow
) Socket();
569 /* disable pipelined tls until we have ticket/resume session support */
570 HTTPConnection
*conn
= new (std::nothrow
)
571 HTTPConnection(p_object
, authStorage
, socket
, proxy
, sockettype
!= TLSSocket::TLS
);
581 StreamUrlConnectionFactory::StreamUrlConnectionFactory()
582 : ConnectionFactory( NULL
)
587 AbstractConnection
* StreamUrlConnectionFactory::createConnection(vlc_object_t
*p_object
,
588 const ConnectionParams
&)
590 return new (std::nothrow
) StreamUrlConnection(p_object
);