HttpdClient.cxx 9.55 KB
Newer Older
1
/*
Max Kellermann's avatar
Max Kellermann committed
2
 * Copyright 2003-2020 The Music Player Daemon Project
3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21
 * 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 "HttpdClient.hxx"
#include "HttpdInternal.hxx"
22
#include "util/ASCII.hxx"
23
#include "util/AllocatedString.hxx"
Max Kellermann's avatar
Max Kellermann committed
24
#include "Page.hxx"
25
#include "IcyMetaDataServer.hxx"
26
#include "net/SocketError.hxx"
27
#include "net/UniqueSocketDescriptor.hxx"
28
#include "Log.hxx"
29

30 31
#include <cassert>

32
#include <string.h>
33
#include <stdio.h>
34

35
HttpdClient::~HttpdClient() noexcept
36
{
37 38
	if (IsDefined())
		BufferedSocket::Close();
39 40 41
}

void
42
HttpdClient::Close() noexcept
43
{
44
	httpd.RemoveClient(*this);
45 46 47
}

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

void
55
HttpdClient::BeginResponse() noexcept
56
{
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
}

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

74
	if (state == State::REQUEST) {
75
		if (strncmp(line, "HEAD /", 6) == 0) {
76 77
			line += 6;
			head_method = true;
78
		} else if (strncmp(line, "GET /", 5) == 0) {
79 80
			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 88 89 90 91 92 93 94 95 96 97
		/* blacklist some well-known request paths */
		if ((strncmp(line, "favicon.ico", 11) == 0 &&
		     (line[11] == '\0' || line[11] == ' ')) ||
		    (strncmp(line, "robots.txt", 10) == 0 &&
		     (line[10] == '\0' || line[10] == ' ')) ||
		    (strncmp(line, "sitemap.xml", 11) == 0 &&
		     (line[11] == '\0' || line[11] == ' ')) ||
		    (strncmp(line, ".well-known/", 12) == 0)) {
			should_reject = true;
		}

98
		line = strchr(line, ' ');
99
		if (line == nullptr || strncmp(line + 1, "HTTP/", 5) != 0) {
100
			/* HTTP/0.9 without request headers */
101 102 103 104

			if (head_method)
				return false;

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

		/* after the request line, request headers follow */
110
		state = State::HEADERS;
111 112 113 114
		return true;
	} else {
		if (*line == 0) {
			/* empty line: request is finished */
115

116 117 118 119
			BeginResponse();
			return true;
		}

120 121
		if (StringEqualsCaseASCII(line, "Icy-MetaData: 1", 15) ||
		    StringEqualsCaseASCII(line, "Icy-MetaData:1", 14)) {
122 123 124 125 126 127 128 129 130 131 132 133 134 135
			/* Send icy metadata */
			metadata_requested = metadata_supported;
			return true;
		}

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

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

142
	assert(state == State::RESPONSE);
143

144 145 146 147 148 149 150 151
	if (should_reject) {
		response =
			"HTTP/1.1 404 not found\r\n"
			"Content-Type: text/plain\r\n"
			"Connection: close\r\n"
			"\r\n"
			"404 not found";
	} else if (metadata_requested) {
152
		allocated =
153 154 155
			icy_server_metadata_header(httpd.name, httpd.genre,
						   httpd.website,
						   httpd.content_type,
156
						   metaint);
157
		response = allocated.c_str();
158
	} else { /* revert to a normal HTTP request */
159 160 161 162 163 164 165
		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",
166
			 httpd.content_type);
167
		response = buffer;
168 169
	}

170
	ssize_t nbytes = GetSocket().Write(response, strlen(response));
171 172
	if (gcc_unlikely(nbytes < 0)) {
		const SocketErrorMessage msg;
173 174 175
		FormatWarning(httpd_output_domain,
			      "failed to write to client: %s",
			      (const char *)msg);
176
		LockClose();
177 178 179 180 181 182
		return false;
	}

	return true;
}

183
HttpdClient::HttpdClient(HttpdOutput &_httpd, UniqueSocketDescriptor _fd,
184
			 EventLoop &_loop,
185
			 bool _metadata_supported)
186
	:BufferedSocket(_fd.Release(), _loop),
187
	 httpd(_httpd),
188
	 metadata_supported(_metadata_supported)
189 190 191 192
{
}

void
193
HttpdClient::ClearQueue() noexcept
194
{
195
	assert(state == State::RESPONSE);
196

197 198
	while (!pages.empty()) {
#ifndef NDEBUG
199
		auto &page = pages.front();
200 201
		assert(queue_size >= page->GetSize());
		queue_size -= page->GetSize();
202 203
#endif

204
		pages.pop();
205 206 207
	}

	assert(queue_size == 0);
208 209 210
}

void
211
HttpdClient::CancelQueue() noexcept
212
{
213
	if (state != State::RESPONSE)
214 215 216
		return;

	ClearQueue();
217

218 219
	if (current_page == nullptr)
		CancelWrite();
220 221
}

222
ssize_t
223
HttpdClient::TryWritePage(const Page &page, size_t position) noexcept
224
{
225
	assert(position < page.GetSize());
226

227 228
	return GetSocket().Write(page.GetData() + position,
				 page.GetSize() - position);
229 230
}

231
ssize_t
232 233
HttpdClient::TryWritePageN(const Page &page,
			   size_t position, ssize_t n) noexcept
234
{
235
	return n >= 0
236
		? GetSocket().Write(page.GetData() + position, n)
237
		: TryWritePage(page, position);
238 239
}

240
ssize_t
241
HttpdClient::GetBytesTillMetaData() const noexcept
242 243
{
	if (metadata_requested &&
244
	    current_page->GetSize() - current_position > metaint - metadata_fill)
245 246 247 248 249 250
		return metaint - metadata_fill;

	return -1;
}

inline bool
251
HttpdClient::TryWrite() noexcept
252
{
253
	const std::lock_guard<Mutex> protect(httpd.mutex);
254

255
	assert(state == State::RESPONSE);
256 257

	if (current_page == nullptr) {
258 259 260
		if (pages.empty()) {
			/* another thread has removed the event source
			   while this thread was waiting for
261
			   httpd.mutex */
262 263 264 265
			CancelWrite();
			return true;
		}

266
		current_page = pages.front();
267
		pages.pop();
268
		current_position = 0;
269

270 271
		assert(queue_size >= current_page->GetSize());
		queue_size -= current_page->GetSize();
272 273
	}

274
	const ssize_t bytes_to_write = GetBytesTillMetaData();
275 276
	if (bytes_to_write == 0) {
		if (!metadata_sent) {
277 278 279 280 281 282 283 284 285
			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);
286 287 288
					FormatWarning(httpd_output_domain,
						      "failed to write to client: %s",
						      (const char *)msg);
289 290 291 292 293
				}

				Close();
				return false;
			}
294

295
			metadata_current_position += nbytes;
296

297
			if (metadata->GetSize() - metadata_current_position == 0) {
298 299 300 301 302
				metadata_fill = 0;
				metadata_current_position = 0;
				metadata_sent = true;
			}
		} else {
303
			char empty_data = 0;
304

305
			ssize_t nbytes = GetSocket().Write(&empty_data, 1);
306 307 308 309
			if (nbytes < 0) {
				auto e = GetSocketError();
				if (IsSocketErrorAgain(e))
					return true;
310

311 312
				if (!IsSocketErrorClosed(e)) {
					SocketErrorMessage msg(e);
313 314 315
					FormatWarning(httpd_output_domain,
						      "failed to write to client: %s",
						      (const char *)msg);
316
				}
317

318 319
				Close();
				return false;
320
			}
Max Kellermann's avatar
Max Kellermann committed
321

322 323
			metadata_fill = 0;
			metadata_current_position = 0;
324 325
		}
	} else {
326 327 328 329 330 331 332 333 334 335
		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);
336 337 338
				FormatWarning(httpd_output_domain,
					      "failed to write to client: %s",
					      (const char *)msg);
339
			}
340

341 342 343 344 345
			Close();
			return false;
		}

		current_position += nbytes;
346
		assert(current_position <= current_page->GetSize());
347 348

		if (metadata_requested)
349
			metadata_fill += nbytes;
350

351
		if (current_position >= current_page->GetSize()) {
352
			current_page.reset();
353

354
			if (pages.empty())
355 356
				/* all pages are sent: remove the
				   event source */
357
				CancelWrite();
358 359 360
		}
	}

361
	return true;
362 363 364
}

void
365
HttpdClient::PushPage(PagePtr page) noexcept
366
{
367
	if (state != State::RESPONSE)
368 369 370
		/* the client is still writing the HTTP request */
		return;

371 372 373 374 375 376
	if (queue_size > 256 * 1024) {
		FormatDebug(httpd_output_domain,
			    "client is too slow, flushing its queue");
		ClearQueue();
	}

377
	queue_size += page->GetSize();
378
	pages.emplace(std::move(page));
379

380
	ScheduleWrite();
381 382 383
}

void
384
HttpdClient::PushMetaData(PagePtr page) noexcept
385
{
386 387
	assert(page != nullptr);

388
	metadata = std::move(page);
389 390
	metadata_sent = false;
}
391 392

bool
393
HttpdClient::OnSocketReady(unsigned flags) noexcept
394 395 396 397 398 399 400 401 402 403 404 405
{
	if (!BufferedSocket::OnSocketReady(flags))
		return false;

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

	return true;
}

BufferedSocket::InputResult
406
HttpdClient::OnSocketInput(void *data, size_t length) noexcept
407
{
408
	if (state == State::RESPONSE) {
409 410
		LogWarning(httpd_output_domain,
			   "unexpected input from client");
411 412 413 414
		LockClose();
		return InputResult::CLOSED;
	}

415 416
	char *line = (char *)data;
	char *newline = (char *)memchr(line, '\n', length);
417 418 419 420 421 422 423 424
	if (newline == nullptr)
		return InputResult::MORE;

	ConsumeInput(newline + 1 - line);

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

425 426
	/* terminate the string at the end of the line */
	*newline = 0;
427 428 429 430 431 432

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

433
	if (state == State::RESPONSE) {
434 435 436
		if (!SendResponse())
			return InputResult::CLOSED;

437
		if (head_method || should_reject) {
438 439 440 441
			LockClose();
			return InputResult::CLOSED;
		}
	}
442 443 444 445 446

	return InputResult::AGAIN;
}

void
447
HttpdClient::OnSocketError(std::exception_ptr ep) noexcept
448
{
449
	LogError(ep);
450
	LockClose();
451 452 453
}

void
454
HttpdClient::OnSocketClosed() noexcept
455 456 457
{
	LockClose();
}