HttpdClient.cxx 9.79 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 36 37

HttpdClient::~HttpdClient()
{
	if (state == RESPONSE) {
		if (current_page != nullptr)
Max Kellermann's avatar
Max Kellermann committed
38
			current_page->Unref();
39

40
		ClearQueue();
41
	}
42 43

	if (metadata)
Max Kellermann's avatar
Max Kellermann committed
44
		metadata->Unref();
45 46 47

	if (IsDefined())
		BufferedSocket::Close();
48 49 50 51 52
}

void
HttpdClient::Close()
{
53
	httpd.RemoveClient(*this);
54 55 56 57 58
}

void
HttpdClient::LockClose()
{
59
	const std::lock_guard<Mutex> protect(httpd.mutex);
60 61 62 63 64 65 66 67 68 69 70
	Close();
}

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

	state = RESPONSE;
	current_page = nullptr;

71
	if (!head_method)
72
		httpd.SendHeader(*this);
73 74 75 76 77 78 79 80 81 82 83
}

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

	if (state == REQUEST) {
84 85 86 87 88 89
		if (memcmp(line, "HEAD /", 6) == 0) {
			line += 6;
			head_method = true;
		} else if (memcmp(line, "GET /", 5) == 0) {
			line += 5;
		} else {
90
			/* only GET is supported */
91 92
			LogWarning(httpd_output_domain,
				   "malformed request line from client");
93 94 95
			return false;
		}

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

			if (head_method)
				return false;

103 104 105 106 107 108 109 110 111 112
			BeginResponse();
			return true;
		}

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

114 115 116 117
			BeginResponse();
			return true;
		}

118 119
		if (StringEqualsCaseASCII(line, "Icy-MetaData: 1", 15) ||
		    StringEqualsCaseASCII(line, "Icy-MetaData:1", 14)) {
120 121 122 123 124
			/* Send icy metadata */
			metadata_requested = metadata_supported;
			return true;
		}

125
		if (StringEqualsCaseASCII(line, "transferMode.dlna.org: Streaming", 32)) {
126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144
			/* 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()
{
145 146
	char buffer[1024];
	AllocatedString<> allocated = nullptr;
147 148
	const char *response;

149 150 151
	assert(state == RESPONSE);

	if (dlna_streaming_requested) {
152 153 154 155 156 157 158 159 160 161 162
		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",
163
			 httpd.content_type);
164
		response = buffer;
165 166

	} else if (metadata_requested) {
167
		allocated =
168 169 170
			icy_server_metadata_header(httpd.name, httpd.genre,
						   httpd.website,
						   httpd.content_type,
171
						   metaint);
172
		response = allocated.c_str();
173
       } else { /* revert to a normal HTTP request */
174 175 176 177 178 179 180
		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",
181
			 httpd.content_type);
182
		response = buffer;
183 184
	}

185
	ssize_t nbytes = SocketMonitor::Write(response, strlen(response));
186 187
	if (gcc_unlikely(nbytes < 0)) {
		const SocketErrorMessage msg;
188 189 190
		FormatWarning(httpd_output_domain,
			      "failed to write to client: %s",
			      (const char *)msg);
191 192 193 194 195 196 197
		Close();
		return false;
	}

	return true;
}

198
HttpdClient::HttpdClient(HttpdOutput &_httpd, int _fd, EventLoop &_loop,
199
			 bool _metadata_supported)
200 201
	:BufferedSocket(_fd, _loop),
	 httpd(_httpd),
202
	 state(REQUEST),
203
	 queue_size(0),
204
	 head_method(false),
205 206 207 208 209 210 211 212 213 214
	 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
215
HttpdClient::ClearQueue()
216
{
217
	assert(state == RESPONSE);
218

219 220 221 222 223 224 225 226 227
	while (!pages.empty()) {
		Page *page = pages.front();
		pages.pop();

#ifndef NDEBUG
		assert(queue_size >= page->size);
		queue_size -= page->size;
#endif

Max Kellermann's avatar
Max Kellermann committed
228
		page->Unref();
229 230 231
	}

	assert(queue_size == 0);
232 233 234 235 236 237 238 239 240
}

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

	ClearQueue();
241

242 243
	if (current_page == nullptr)
		CancelWrite();
244 245
}

246 247
ssize_t
HttpdClient::TryWritePage(const Page &page, size_t position)
248
{
Max Kellermann's avatar
Max Kellermann committed
249
	assert(position < page.size);
250

251
	return Write(page.data + position, page.size - position);
252 253
}

254 255
ssize_t
HttpdClient::TryWritePageN(const Page &page, size_t position, ssize_t n)
256
{
257 258 259
	return n >= 0
		? Write(page.data + position, n)
		: TryWritePage(page, position);
260 261
}

262
ssize_t
263 264 265 266 267 268 269 270 271 272
HttpdClient::GetBytesTillMetaData() const
{
	if (metadata_requested &&
	    current_page->size - current_position > metaint - metadata_fill)
		return metaint - metadata_fill;

	return -1;
}

inline bool
273
HttpdClient::TryWrite()
274
{
275
	const std::lock_guard<Mutex> protect(httpd.mutex);
276 277 278 279

	assert(state == RESPONSE);

	if (current_page == nullptr) {
280 281 282
		if (pages.empty()) {
			/* another thread has removed the event source
			   while this thread was waiting for
283
			   httpd.mutex */
284 285 286 287
			CancelWrite();
			return true;
		}

288
		current_page = pages.front();
289
		pages.pop();
290
		current_position = 0;
291 292 293

		assert(queue_size >= current_page->size);
		queue_size -= current_page->size;
294 295
	}

296
	const ssize_t bytes_to_write = GetBytesTillMetaData();
297 298
	if (bytes_to_write == 0) {
		if (!metadata_sent) {
299 300 301 302 303 304 305 306 307
			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);
308 309 310
					FormatWarning(httpd_output_domain,
						      "failed to write to client: %s",
						      (const char *)msg);
311 312 313 314 315
				}

				Close();
				return false;
			}
316

317
			metadata_current_position += nbytes;
318 319 320 321 322 323 324

			if (metadata->size - metadata_current_position == 0) {
				metadata_fill = 0;
				metadata_current_position = 0;
				metadata_sent = true;
			}
		} else {
325
			char empty_data = 0;
326

327 328 329 330 331
			ssize_t nbytes = Write(&empty_data, 1);
			if (nbytes < 0) {
				auto e = GetSocketError();
				if (IsSocketErrorAgain(e))
					return true;
332

333 334
				if (!IsSocketErrorClosed(e)) {
					SocketErrorMessage msg(e);
335 336 337
					FormatWarning(httpd_output_domain,
						      "failed to write to client: %s",
						      (const char *)msg);
338
				}
339

340 341
				Close();
				return false;
342
			}
Max Kellermann's avatar
Max Kellermann committed
343

344 345
			metadata_fill = 0;
			metadata_current_position = 0;
346 347
		}
	} else {
348 349 350 351 352 353 354 355 356 357
		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);
358 359 360
				FormatWarning(httpd_output_domain,
					      "failed to write to client: %s",
					      (const char *)msg);
361
			}
362

363 364 365 366 367
			Close();
			return false;
		}

		current_position += nbytes;
368 369 370
		assert(current_position <= current_page->size);

		if (metadata_requested)
371
			metadata_fill += nbytes;
372 373

		if (current_position >= current_page->size) {
Max Kellermann's avatar
Max Kellermann committed
374
			current_page->Unref();
375 376
			current_page = nullptr;

377
			if (pages.empty())
378 379
				/* all pages are sent: remove the
				   event source */
380
				CancelWrite();
381 382 383
		}
	}

384
	return true;
385 386 387
}

void
Max Kellermann's avatar
Max Kellermann committed
388
HttpdClient::PushPage(Page *page)
389 390 391 392 393
{
	if (state != RESPONSE)
		/* the client is still writing the HTTP request */
		return;

394 395 396 397 398 399
	if (queue_size > 256 * 1024) {
		FormatDebug(httpd_output_domain,
			    "client is too slow, flushing its queue");
		ClearQueue();
	}

Max Kellermann's avatar
Max Kellermann committed
400
	page->Ref();
401
	pages.push(page);
402
	queue_size += page->size;
403

404
	ScheduleWrite();
405 406 407
}

void
Max Kellermann's avatar
Max Kellermann committed
408
HttpdClient::PushMetaData(Page *page)
409
{
410 411
	assert(page != nullptr);

412
	if (metadata) {
Max Kellermann's avatar
Max Kellermann committed
413
		metadata->Unref();
414 415 416
		metadata = nullptr;
	}

Max Kellermann's avatar
Max Kellermann committed
417
	page->Ref();
418 419 420
	metadata = page;
	metadata_sent = false;
}
421 422 423 424 425 426 427 428 429 430 431 432 433 434 435

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

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

	return true;
}

BufferedSocket::InputResult
436
HttpdClient::OnSocketInput(void *data, size_t length)
437 438
{
	if (state == RESPONSE) {
439 440
		LogWarning(httpd_output_domain,
			   "unexpected input from client");
441 442 443 444
		LockClose();
		return InputResult::CLOSED;
	}

445 446
	char *line = (char *)data;
	char *newline = (char *)memchr(line, '\n', length);
447 448 449 450 451 452 453 454
	if (newline == nullptr)
		return InputResult::MORE;

	ConsumeInput(newline + 1 - line);

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

455 456
	/* terminate the string at the end of the line */
	*newline = 0;
457 458 459 460 461 462

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

463 464 465 466 467 468 469 470 471
	if (state == RESPONSE) {
		if (!SendResponse())
			return InputResult::CLOSED;

		if (head_method) {
			LockClose();
			return InputResult::CLOSED;
		}
	}
472 473 474 475 476

	return InputResult::AGAIN;
}

void
477
HttpdClient::OnSocketError(std::exception_ptr ep)
478
{
479
	LogError(ep);
480 481 482 483 484 485 486
}

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