HttpdClient.cxx 9.59 KB
Newer Older
1
/*
Max Kellermann's avatar
Max Kellermann committed
2
 * Copyright 2003-2017 The Music Player Daemon Project
3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22
 * http://www.musicpd.org
 *
 * This program is free software; you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation; either version 2 of the License, or
 * (at your option) any later version.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License along
 * with this program; if not, write to the Free Software Foundation, Inc.,
 * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
 */

#include "config.h"
#include "HttpdClient.hxx"
#include "HttpdInternal.hxx"
23
#include "util/ASCII.hxx"
24
#include "util/AllocatedString.hxx"
Max Kellermann's avatar
Max Kellermann committed
25
#include "Page.hxx"
26
#include "IcyMetaDataServer.hxx"
27
#include "net/SocketError.hxx"
28
#include "net/UniqueSocketDescriptor.hxx"
29
#include "Log.hxx"
30 31 32

#include <assert.h>
#include <string.h>
33
#include <stdio.h>
34 35 36

HttpdClient::~HttpdClient()
{
37 38
	if (IsDefined())
		BufferedSocket::Close();
39 40 41 42 43
}

void
HttpdClient::Close()
{
44
	httpd.RemoveClient(*this);
45 46 47 48 49
}

void
HttpdClient::LockClose()
{
50
	const std::lock_guard<Mutex> protect(httpd.mutex);
51 52 53 54 55 56
	Close();
}

void
HttpdClient::BeginResponse()
{
57
	assert(state != State::RESPONSE);
58

59
	state = State::RESPONSE;
60 61
	current_page = nullptr;

62
	if (!head_method)
63
		httpd.SendHeader(*this);
64 65 66 67 68 69 70 71
}

/**
 * Handle a line of the HTTP request.
 */
bool
HttpdClient::HandleLine(const char *line)
{
72
	assert(state != State::RESPONSE);
73

74
	if (state == State::REQUEST) {
75 76 77 78 79 80
		if (memcmp(line, "HEAD /", 6) == 0) {
			line += 6;
			head_method = true;
		} else if (memcmp(line, "GET /", 5) == 0) {
			line += 5;
		} else {
81
			/* only GET is supported */
82 83
			LogWarning(httpd_output_domain,
				   "malformed request line from client");
84 85 86
			return false;
		}

87
		line = strchr(line, ' ');
88
		if (line == nullptr || memcmp(line + 1, "HTTP/", 5) != 0) {
89
			/* HTTP/0.9 without request headers */
90 91 92 93

			if (head_method)
				return false;

94 95 96 97 98
			BeginResponse();
			return true;
		}

		/* after the request line, request headers follow */
99
		state = State::HEADERS;
100 101 102 103
		return true;
	} else {
		if (*line == 0) {
			/* empty line: request is finished */
104

105 106 107 108
			BeginResponse();
			return true;
		}

109 110
		if (StringEqualsCaseASCII(line, "Icy-MetaData: 1", 15) ||
		    StringEqualsCaseASCII(line, "Icy-MetaData:1", 14)) {
111 112 113 114 115
			/* Send icy metadata */
			metadata_requested = metadata_supported;
			return true;
		}

116
		if (StringEqualsCaseASCII(line, "transferMode.dlna.org: Streaming", 32)) {
117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135
			/* Send as dlna */
			dlna_streaming_requested = true;
			/* metadata is not supported by dlna streaming, so disable it */
			metadata_supported = false;
			metadata_requested = false;
			return true;
		}

		/* expect more request headers */
		return true;
	}
}

/**
 * Sends the status line and response headers to the client.
 */
bool
HttpdClient::SendResponse()
{
136 137
	char buffer[1024];
	AllocatedString<> allocated = nullptr;
138 139
	const char *response;

140
	assert(state == State::RESPONSE);
141 142

	if (dlna_streaming_requested) {
143 144 145 146 147 148 149 150 151 152 153
		snprintf(buffer, sizeof(buffer),
			 "HTTP/1.1 206 OK\r\n"
			 "Content-Type: %s\r\n"
			 "Content-Length: 10000\r\n"
			 "Content-RangeX: 0-1000000/1000000\r\n"
			 "transferMode.dlna.org: Streaming\r\n"
			 "Accept-Ranges: bytes\r\n"
			 "Connection: close\r\n"
			 "realTimeInfo.dlna.org: DLNA.ORG_TLAG=*\r\n"
			 "contentFeatures.dlna.org: DLNA.ORG_OP=01;DLNA.ORG_CI=0\r\n"
			 "\r\n",
154
			 httpd.content_type);
155
		response = buffer;
156 157

	} else if (metadata_requested) {
158
		allocated =
159 160 161
			icy_server_metadata_header(httpd.name, httpd.genre,
						   httpd.website,
						   httpd.content_type,
162
						   metaint);
163
		response = allocated.c_str();
164
       } else { /* revert to a normal HTTP request */
165 166 167 168 169 170 171
		snprintf(buffer, sizeof(buffer),
			 "HTTP/1.1 200 OK\r\n"
			 "Content-Type: %s\r\n"
			 "Connection: close\r\n"
			 "Pragma: no-cache\r\n"
			 "Cache-Control: no-cache, no-store\r\n"
			 "\r\n",
172
			 httpd.content_type);
173
		response = buffer;
174 175
	}

176
	ssize_t nbytes = GetSocket().Write(response, strlen(response));
177 178
	if (gcc_unlikely(nbytes < 0)) {
		const SocketErrorMessage msg;
179 180 181
		FormatWarning(httpd_output_domain,
			      "failed to write to client: %s",
			      (const char *)msg);
182 183 184 185 186 187 188
		Close();
		return false;
	}

	return true;
}

189
HttpdClient::HttpdClient(HttpdOutput &_httpd, UniqueSocketDescriptor _fd,
190
			 EventLoop &_loop,
191
			 bool _metadata_supported)
192
	:BufferedSocket(_fd.Release(), _loop),
193
	 httpd(_httpd),
194
	 metadata_supported(_metadata_supported)
195 196 197 198
{
}

void
199
HttpdClient::ClearQueue()
200
{
201
	assert(state == State::RESPONSE);
202

203 204
	while (!pages.empty()) {
#ifndef NDEBUG
205
		auto &page = pages.front();
206 207
		assert(queue_size >= page->GetSize());
		queue_size -= page->GetSize();
208 209
#endif

210
		pages.pop();
211 212 213
	}

	assert(queue_size == 0);
214 215 216 217 218
}

void
HttpdClient::CancelQueue()
{
219
	if (state != State::RESPONSE)
220 221 222
		return;

	ClearQueue();
223

224 225
	if (current_page == nullptr)
		CancelWrite();
226 227
}

228 229
ssize_t
HttpdClient::TryWritePage(const Page &page, size_t position)
230
{
231
	assert(position < page.GetSize());
232

233 234
	return GetSocket().Write(page.GetData() + position,
				 page.GetSize() - position);
235 236
}

237 238
ssize_t
HttpdClient::TryWritePageN(const Page &page, size_t position, ssize_t n)
239
{
240
	return n >= 0
241
		? GetSocket().Write(page.GetData() + position, n)
242
		: TryWritePage(page, position);
243 244
}

245
ssize_t
246
HttpdClient::GetBytesTillMetaData() const noexcept
247 248
{
	if (metadata_requested &&
249
	    current_page->GetSize() - current_position > metaint - metadata_fill)
250 251 252 253 254 255
		return metaint - metadata_fill;

	return -1;
}

inline bool
256
HttpdClient::TryWrite()
257
{
258
	const std::lock_guard<Mutex> protect(httpd.mutex);
259

260
	assert(state == State::RESPONSE);
261 262

	if (current_page == nullptr) {
263 264 265
		if (pages.empty()) {
			/* another thread has removed the event source
			   while this thread was waiting for
266
			   httpd.mutex */
267 268 269 270
			CancelWrite();
			return true;
		}

271
		current_page = pages.front();
272
		pages.pop();
273
		current_position = 0;
274

275 276
		assert(queue_size >= current_page->GetSize());
		queue_size -= current_page->GetSize();
277 278
	}

279
	const ssize_t bytes_to_write = GetBytesTillMetaData();
280 281
	if (bytes_to_write == 0) {
		if (!metadata_sent) {
282 283 284 285 286 287 288 289 290
			ssize_t nbytes = TryWritePage(*metadata,
						      metadata_current_position);
			if (nbytes < 0) {
				auto e = GetSocketError();
				if (IsSocketErrorAgain(e))
					return true;

				if (!IsSocketErrorClosed(e)) {
					SocketErrorMessage msg(e);
291 292 293
					FormatWarning(httpd_output_domain,
						      "failed to write to client: %s",
						      (const char *)msg);
294 295 296 297 298
				}

				Close();
				return false;
			}
299

300
			metadata_current_position += nbytes;
301

302
			if (metadata->GetSize() - metadata_current_position == 0) {
303 304 305 306 307
				metadata_fill = 0;
				metadata_current_position = 0;
				metadata_sent = true;
			}
		} else {
308
			char empty_data = 0;
309

310
			ssize_t nbytes = GetSocket().Write(&empty_data, 1);
311 312 313 314
			if (nbytes < 0) {
				auto e = GetSocketError();
				if (IsSocketErrorAgain(e))
					return true;
315

316 317
				if (!IsSocketErrorClosed(e)) {
					SocketErrorMessage msg(e);
318 319 320
					FormatWarning(httpd_output_domain,
						      "failed to write to client: %s",
						      (const char *)msg);
321
				}
322

323 324
				Close();
				return false;
325
			}
Max Kellermann's avatar
Max Kellermann committed
326

327 328
			metadata_fill = 0;
			metadata_current_position = 0;
329 330
		}
	} else {
331 332 333 334 335 336 337 338 339 340
		ssize_t nbytes =
			TryWritePageN(*current_page, current_position,
				      bytes_to_write);
		if (nbytes < 0) {
			auto e = GetSocketError();
			if (IsSocketErrorAgain(e))
				return true;

			if (!IsSocketErrorClosed(e)) {
				SocketErrorMessage msg(e);
341 342 343
				FormatWarning(httpd_output_domain,
					      "failed to write to client: %s",
					      (const char *)msg);
344
			}
345

346 347 348 349 350
			Close();
			return false;
		}

		current_position += nbytes;
351
		assert(current_position <= current_page->GetSize());
352 353

		if (metadata_requested)
354
			metadata_fill += nbytes;
355

356
		if (current_position >= current_page->GetSize()) {
357
			current_page.reset();
358

359
			if (pages.empty())
360 361
				/* all pages are sent: remove the
				   event source */
362
				CancelWrite();
363 364 365
		}
	}

366
	return true;
367 368 369
}

void
370
HttpdClient::PushPage(PagePtr page)
371
{
372
	if (state != State::RESPONSE)
373 374 375
		/* the client is still writing the HTTP request */
		return;

376 377 378 379 380 381
	if (queue_size > 256 * 1024) {
		FormatDebug(httpd_output_domain,
			    "client is too slow, flushing its queue");
		ClearQueue();
	}

382
	queue_size += page->GetSize();
383
	pages.emplace(std::move(page));
384

385
	ScheduleWrite();
386 387 388
}

void
389
HttpdClient::PushMetaData(PagePtr page)
390
{
391 392
	assert(page != nullptr);

393
	metadata = std::move(page);
394 395
	metadata_sent = false;
}
396 397

bool
398
HttpdClient::OnSocketReady(unsigned flags) noexcept
399 400 401 402 403 404 405 406 407 408 409 410
{
	if (!BufferedSocket::OnSocketReady(flags))
		return false;

	if (flags & WRITE)
		if (!TryWrite())
			return false;

	return true;
}

BufferedSocket::InputResult
411
HttpdClient::OnSocketInput(void *data, size_t length) noexcept
412
{
413
	if (state == State::RESPONSE) {
414 415
		LogWarning(httpd_output_domain,
			   "unexpected input from client");
416 417 418 419
		LockClose();
		return InputResult::CLOSED;
	}

420 421
	char *line = (char *)data;
	char *newline = (char *)memchr(line, '\n', length);
422 423 424 425 426 427 428 429
	if (newline == nullptr)
		return InputResult::MORE;

	ConsumeInput(newline + 1 - line);

	if (newline > line && newline[-1] == '\r')
		--newline;

430 431
	/* terminate the string at the end of the line */
	*newline = 0;
432 433 434 435 436 437

	if (!HandleLine(line)) {
		LockClose();
		return InputResult::CLOSED;
	}

438
	if (state == State::RESPONSE) {
439 440 441 442 443 444 445 446
		if (!SendResponse())
			return InputResult::CLOSED;

		if (head_method) {
			LockClose();
			return InputResult::CLOSED;
		}
	}
447 448 449 450 451

	return InputResult::AGAIN;
}

void
452
HttpdClient::OnSocketError(std::exception_ptr ep) noexcept
453
{
454
	LogError(ep);
455 456 457
}

void
458
HttpdClient::OnSocketClosed() noexcept
459 460 461
{
	LockClose();
}