HttpdClient.cxx 9.62 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 "Log.hxx"
29 30 31

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

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

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

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

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

	state = RESPONSE;
	current_page = nullptr;

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

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

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

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

			if (head_method)
				return false;

93 94 95 96 97 98 99 100 101 102
			BeginResponse();
			return true;
		}

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

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

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

115
		if (StringEqualsCaseASCII(line, "transferMode.dlna.org: Streaming", 32)) {
116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134
			/* 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()
{
135 136
	char buffer[1024];
	AllocatedString<> allocated = nullptr;
137 138
	const char *response;

139 140 141
	assert(state == RESPONSE);

	if (dlna_streaming_requested) {
142 143 144 145 146 147 148 149 150 151 152
		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",
153
			 httpd.content_type);
154
		response = buffer;
155 156

	} else if (metadata_requested) {
157
		allocated =
158 159 160
			icy_server_metadata_header(httpd.name, httpd.genre,
						   httpd.website,
						   httpd.content_type,
161
						   metaint);
162
		response = allocated.c_str();
163
       } else { /* revert to a normal HTTP request */
164 165 166 167 168 169 170
		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",
171
			 httpd.content_type);
172
		response = buffer;
173 174
	}

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

	return true;
}

188
HttpdClient::HttpdClient(HttpdOutput &_httpd, int _fd, EventLoop &_loop,
189
			 bool _metadata_supported)
190 191
	:BufferedSocket(_fd, _loop),
	 httpd(_httpd),
192
	 state(REQUEST),
193
	 queue_size(0),
194
	 head_method(false),
195 196 197 198 199 200 201 202 203 204
	 dlna_streaming_requested(false),
	 metadata_supported(_metadata_supported),
	 metadata_requested(false), metadata_sent(true),
	 metaint(8192), /*TODO: just a std value */
	 metadata(nullptr),
	 metadata_current_position(0), metadata_fill(0)
{
}

void
205
HttpdClient::ClearQueue()
206
{
207
	assert(state == RESPONSE);
208

209 210
	while (!pages.empty()) {
#ifndef NDEBUG
211
		auto &page = pages.front();
212 213
		assert(queue_size >= page->GetSize());
		queue_size -= page->GetSize();
214 215
#endif

216
		pages.pop();
217 218 219
	}

	assert(queue_size == 0);
220 221 222 223 224 225 226 227 228
}

void
HttpdClient::CancelQueue()
{
	if (state != RESPONSE)
		return;

	ClearQueue();
229

230 231
	if (current_page == nullptr)
		CancelWrite();
232 233
}

234 235
ssize_t
HttpdClient::TryWritePage(const Page &page, size_t position)
236
{
237
	assert(position < page.GetSize());
238

239 240
	return Write(page.GetData() + position,
		     page.GetSize() - position);
241 242
}

243 244
ssize_t
HttpdClient::TryWritePageN(const Page &page, size_t position, ssize_t n)
245
{
246
	return n >= 0
247
		? Write(page.GetData() + position, n)
248
		: TryWritePage(page, position);
249 250
}

251
ssize_t
252
HttpdClient::GetBytesTillMetaData() const noexcept
253 254
{
	if (metadata_requested &&
255
	    current_page->GetSize() - current_position > metaint - metadata_fill)
256 257 258 259 260 261
		return metaint - metadata_fill;

	return -1;
}

inline bool
262
HttpdClient::TryWrite()
263
{
264
	const std::lock_guard<Mutex> protect(httpd.mutex);
265 266 267 268

	assert(state == RESPONSE);

	if (current_page == nullptr) {
269 270 271
		if (pages.empty()) {
			/* another thread has removed the event source
			   while this thread was waiting for
272
			   httpd.mutex */
273 274 275 276
			CancelWrite();
			return true;
		}

277
		current_page = pages.front();
278
		pages.pop();
279
		current_position = 0;
280

281 282
		assert(queue_size >= current_page->GetSize());
		queue_size -= current_page->GetSize();
283 284
	}

285
	const ssize_t bytes_to_write = GetBytesTillMetaData();
286 287
	if (bytes_to_write == 0) {
		if (!metadata_sent) {
288 289 290 291 292 293 294 295 296
			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);
297 298 299
					FormatWarning(httpd_output_domain,
						      "failed to write to client: %s",
						      (const char *)msg);
300 301 302 303 304
				}

				Close();
				return false;
			}
305

306
			metadata_current_position += nbytes;
307

308
			if (metadata->GetSize() - metadata_current_position == 0) {
309 310 311 312 313
				metadata_fill = 0;
				metadata_current_position = 0;
				metadata_sent = true;
			}
		} else {
314
			char empty_data = 0;
315

316 317 318 319 320
			ssize_t nbytes = Write(&empty_data, 1);
			if (nbytes < 0) {
				auto e = GetSocketError();
				if (IsSocketErrorAgain(e))
					return true;
321

322 323
				if (!IsSocketErrorClosed(e)) {
					SocketErrorMessage msg(e);
324 325 326
					FormatWarning(httpd_output_domain,
						      "failed to write to client: %s",
						      (const char *)msg);
327
				}
328

329 330
				Close();
				return false;
331
			}
Max Kellermann's avatar
Max Kellermann committed
332

333 334
			metadata_fill = 0;
			metadata_current_position = 0;
335 336
		}
	} else {
337 338 339 340 341 342 343 344 345 346
		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);
347 348 349
				FormatWarning(httpd_output_domain,
					      "failed to write to client: %s",
					      (const char *)msg);
350
			}
351

352 353 354 355 356
			Close();
			return false;
		}

		current_position += nbytes;
357
		assert(current_position <= current_page->GetSize());
358 359

		if (metadata_requested)
360
			metadata_fill += nbytes;
361

362
		if (current_position >= current_page->GetSize()) {
363
			current_page.reset();
364

365
			if (pages.empty())
366 367
				/* all pages are sent: remove the
				   event source */
368
				CancelWrite();
369 370 371
		}
	}

372
	return true;
373 374 375
}

void
376
HttpdClient::PushPage(PagePtr page)
377 378 379 380 381
{
	if (state != RESPONSE)
		/* the client is still writing the HTTP request */
		return;

382 383 384 385 386 387
	if (queue_size > 256 * 1024) {
		FormatDebug(httpd_output_domain,
			    "client is too slow, flushing its queue");
		ClearQueue();
	}

388
	queue_size += page->GetSize();
389
	pages.emplace(std::move(page));
390

391
	ScheduleWrite();
392 393 394
}

void
395
HttpdClient::PushMetaData(PagePtr page)
396
{
397 398
	assert(page != nullptr);

399
	metadata = std::move(page);
400 401
	metadata_sent = false;
}
402 403 404 405 406 407 408 409 410 411 412 413 414 415 416

bool
HttpdClient::OnSocketReady(unsigned flags)
{
	if (!BufferedSocket::OnSocketReady(flags))
		return false;

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

	return true;
}

BufferedSocket::InputResult
417
HttpdClient::OnSocketInput(void *data, size_t length)
418 419
{
	if (state == RESPONSE) {
420 421
		LogWarning(httpd_output_domain,
			   "unexpected input from client");
422 423 424 425
		LockClose();
		return InputResult::CLOSED;
	}

426 427
	char *line = (char *)data;
	char *newline = (char *)memchr(line, '\n', length);
428 429 430 431 432 433 434 435
	if (newline == nullptr)
		return InputResult::MORE;

	ConsumeInput(newline + 1 - line);

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

436 437
	/* terminate the string at the end of the line */
	*newline = 0;
438 439 440 441 442 443

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

444 445 446 447 448 449 450 451 452
	if (state == RESPONSE) {
		if (!SendResponse())
			return InputResult::CLOSED;

		if (head_method) {
			LockClose();
			return InputResult::CLOSED;
		}
	}
453 454 455 456 457

	return InputResult::AGAIN;
}

void
458
HttpdClient::OnSocketError(std::exception_ptr ep)
459
{
460
	LogError(ep);
461 462 463 464 465 466 467
}

void
HttpdClient::OnSocketClosed()
{
	LockClose();
}