Request.cxx 6.1 KB
Newer Older
1
/*
2
 * Copyright (C) 2008-2017 Max Kellermann <max.kellermann@gmail.com>
3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions
 * are met:
 *
 * - Redistributions of source code must retain the above copyright
 * notice, this list of conditions and the following disclaimer.
 *
 * - Redistributions in binary form must reproduce the above copyright
 * notice, this list of conditions and the following disclaimer in the
 * documentation and/or other materials provided with the
 * distribution.
 *
 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
 * ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
 * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS
 * FOR A PARTICULAR PURPOSE ARE DISCLAIMED.  IN NO EVENT SHALL THE
 * FOUNDATION OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
 * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
 * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
 * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
 * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT,
 * STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
 * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED
 * OF THE POSSIBILITY OF SUCH DAMAGE.
 */

#include "config.h"
#include "Request.hxx"
#include "Global.hxx"
#include "Version.hxx"
#include "Handler.hxx"
35
#include "event/Call.hxx"
36
#include "util/RuntimeError.hxx"
37
#include "util/StringStrip.hxx"
38 39 40 41 42 43 44 45 46 47
#include "util/StringView.hxx"
#include "util/CharUtil.hxx"

#include <curl/curl.h>

#include <algorithm>

#include <assert.h>
#include <string.h>

48
CurlRequest::CurlRequest(CurlGlobal &_global,
49
			 CurlResponseHandler &_handler)
50 51 52
	:global(_global), handler(_handler),
	 postpone_error_event(global.GetEventLoop(),
			      BIND_THIS_METHOD(OnPostponeError))
53 54 55 56 57 58 59 60 61 62 63 64 65 66
{
	error_buffer[0] = 0;

	easy.SetOption(CURLOPT_PRIVATE, (void *)this);
	easy.SetOption(CURLOPT_USERAGENT, "Music Player Daemon " VERSION);
	easy.SetOption(CURLOPT_HEADERFUNCTION, _HeaderFunction);
	easy.SetOption(CURLOPT_WRITEHEADER, this);
	easy.SetOption(CURLOPT_WRITEFUNCTION, WriteFunction);
	easy.SetOption(CURLOPT_WRITEDATA, this);
	easy.SetOption(CURLOPT_NETRC, 1l);
	easy.SetOption(CURLOPT_ERRORBUFFER, error_buffer);
	easy.SetOption(CURLOPT_NOPROGRESS, 1l);
	easy.SetOption(CURLOPT_NOSIGNAL, 1l);
	easy.SetOption(CURLOPT_CONNECTTIMEOUT, 10l);
67
	easy.SetOption(CURLOPT_HTTPAUTH, (long) CURLAUTH_ANY);
68 69
}

70
CurlRequest::~CurlRequest() noexcept
71 72 73 74
{
	FreeEasy();
}

75
void
76
CurlRequest::Start()
77 78 79 80 81 82 83
{
	assert(!registered);

	global.Add(easy.Get(), *this);
	registered = true;
}

84 85 86 87 88 89 90 91
void
CurlRequest::StartIndirect()
{
	BlockingCall(global.GetEventLoop(), [this](){
			Start();
		});
}

92
void
93
CurlRequest::Stop() noexcept
94
{
95 96
	if (!registered)
		return;
97 98 99 100 101

	global.Remove(easy.Get());
	registered = false;
}

102 103 104 105 106 107 108 109
void
CurlRequest::StopIndirect()
{
	BlockingCall(global.GetEventLoop(), [this](){
			Stop();
		});
}

110
void
111
CurlRequest::FreeEasy() noexcept
112 113 114 115
{
	if (!easy)
		return;

116
	Stop();
117 118 119 120
	easy = nullptr;
}

void
121
CurlRequest::Resume() noexcept
122
{
123 124
	assert(registered);

125 126 127 128 129 130 131 132 133 134 135
	curl_easy_pause(easy.Get(), CURLPAUSE_CONT);

	if (IsCurlOlderThan(0x072000))
		/* libcurl older than 7.32.0 does not update
		   its sockets after curl_easy_pause(); force
		   libcurl to do it now */
		global.ResumeSockets();

	global.InvalidateSockets();
}

136
void
137 138 139
CurlRequest::FinishHeaders()
{
	if (state != State::HEADERS)
140
		return;
141 142 143 144 145 146

	state = State::BODY;

	long status = 0;
	curl_easy_getinfo(easy.Get(), CURLINFO_RESPONSE_CODE, &status);

147
	handler.OnHeaders(status, std::move(headers));
148 149 150 151 152
}

void
CurlRequest::FinishBody()
{
153
	FinishHeaders();
154 155 156 157 158 159 160 161 162

	if (state != State::BODY)
		return;

	state = State::CLOSED;
	handler.OnEnd();
}

void
163
CurlRequest::Done(CURLcode result) noexcept
164
{
165
	Stop();
166 167 168 169 170 171 172 173 174 175

	try {
		if (result != CURLE_OK) {
			StripRight(error_buffer);
			const char *msg = error_buffer;
			if (*msg == 0)
				msg = curl_easy_strerror(result);
			throw FormatRuntimeError("CURL failed: %s", msg);
		}

176 177 178 179 180
		FinishBody();
	} catch (...) {
		state = State::CLOSED;
		handler.OnError(std::current_exception());
	}
181 182
}

183 184
gcc_pure
static bool
185
IsResponseBoundaryHeader(StringView s) noexcept
186
{
187
	return s.size > 5 && (s.StartsWith("HTTP/") ||
188 189
			      /* the proprietary "ICY 200 OK" is
				 emitted by Shoutcast */
190
			      s.StartsWith("ICY 2"));
191 192
}

193
inline void
194
CurlRequest::HeaderFunction(StringView s) noexcept
195 196 197 198
{
	if (state > State::HEADERS)
		return;

199
	if (IsResponseBoundaryHeader(s)) {
200 201 202 203 204 205 206 207 208 209 210 211 212 213
		/* this is the boundary to a new response, for example
		   after a redirect */
		headers.clear();
		return;
	}

	const char *header = s.data;
	const char *end = StripRight(header, header + s.size);

	const char *value = s.Find(':');
	if (value == nullptr)
		return;

	std::string name(header, value);
214 215
	std::transform(name.begin(), name.end(), name.begin(),
		       static_cast<char(*)(char)>(ToLowerASCII));
216 217 218 219 220 221 222 223 224 225 226 227 228 229

	/* skip the colon */

	++value;

	/* strip the value */

	value = StripLeft(value, end);
	end = StripRight(value, end);

	headers.emplace(std::move(name), std::string(value, end));
}

size_t
230 231
CurlRequest::_HeaderFunction(void *ptr, size_t size, size_t nmemb,
			     void *stream) noexcept
232 233 234 235 236 237 238 239 240 241
{
	CurlRequest &c = *(CurlRequest *)stream;

	size *= nmemb;

	c.HeaderFunction({(const char *)ptr, size});
	return size;
}

inline size_t
242
CurlRequest::DataReceived(const void *ptr, size_t received_size) noexcept
243 244 245 246
{
	assert(received_size > 0);

	try {
247
		FinishHeaders();
248 249 250 251 252 253
		handler.OnData({ptr, received_size});
		return received_size;
	} catch (Pause) {
		return CURL_WRITEFUNC_PAUSE;
	} catch (...) {
		state = State::CLOSED;
254 255 256
		/* move the CurlResponseHandler::OnError() call into a
		   "safe" stack frame */
		postponed_error = std::current_exception();
257
		postpone_error_event.Schedule();
258
		return CURL_WRITEFUNC_PAUSE;
259 260 261 262 263
	}

}

size_t
264 265
CurlRequest::WriteFunction(void *ptr, size_t size, size_t nmemb,
			   void *stream) noexcept
266 267 268 269 270 271 272 273 274
{
	CurlRequest &c = *(CurlRequest *)stream;

	size *= nmemb;
	if (size == 0)
		return 0;

	return c.DataReceived(ptr, size);
}
275 276

void
277
CurlRequest::OnPostponeError() noexcept
278 279 280 281 282
{
	assert(postponed_error);

	handler.OnError(postponed_error);
}