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

29 30
#include <glib.h>

31 32 33 34 35 36 37
#include <assert.h>
#include <string.h>

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

		for (auto page : pages)
Max Kellermann's avatar
Max Kellermann committed
41
			page->Unref();
42
	}
43 44

	if (metadata)
Max Kellermann's avatar
Max Kellermann committed
45
		metadata->Unref();
46 47 48 49 50
}

void
HttpdClient::Close()
{
51
	httpd->RemoveClient(*this);
52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68
}

void
HttpdClient::LockClose()
{
	const ScopeLock protect(httpd->mutex);
	Close();
}

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

	state = RESPONSE;
	current_page = nullptr;

69
	httpd->SendHeader(*this);
70 71 72 73 74 75 76 77 78 79 80 81 82
}

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

	if (state == REQUEST) {
		if (strncmp(line, "GET /", 5) != 0) {
			/* only GET is supported */
83 84
			LogWarning(httpd_output_domain,
				   "malformed request line from client");
85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134
			return false;
		}

		line = strchr(line + 5, ' ');
		if (line == nullptr || strncmp(line + 1, "HTTP/", 5) != 0) {
			/* HTTP/0.9 without request headers */
			BeginResponse();
			return true;
		}

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

		if (g_ascii_strncasecmp(line, "Icy-MetaData: 1", 15) == 0) {
			/* Send icy metadata */
			metadata_requested = metadata_supported;
			return true;
		}

		if (g_ascii_strncasecmp(line, "transferMode.dlna.org: Streaming", 32) == 0) {
			/* 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()
{
	char buffer[1024];
	assert(state == RESPONSE);

	if (dlna_streaming_requested) {
135 136 137 138 139 140 141 142 143 144 145 146
		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",
			 httpd->content_type);
147 148 149 150 151 152 153 154 155 156 157 158 159 160 161

	} else if (metadata_requested) {
		gchar *metadata_header;

		metadata_header =
			icy_server_metadata_header(httpd->name, httpd->genre,
						   httpd->website,
						   httpd->content_type,
						   metaint);

		g_strlcpy(buffer, metadata_header, sizeof(buffer));

		g_free(metadata_header);

       } else { /* revert to a normal HTTP request */
162 163 164 165 166 167 168 169
		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",
			 httpd->content_type);
170 171
	}

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

	return true;
}

185
HttpdClient::HttpdClient(HttpdOutput *_httpd, int _fd, EventLoop &_loop,
186
			 bool _metadata_supported)
187 188
	:BufferedSocket(_fd, _loop),
	 httpd(_httpd),
189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217
	 state(REQUEST),
	 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)
{
}

size_t
HttpdClient::GetQueueSize() const
{
	if (state != RESPONSE)
		return 0;

	size_t size = 0;
	for (auto page : pages)
		size += page->size;
	return size;
}

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

	for (auto page : pages)
Max Kellermann's avatar
Max Kellermann committed
218
		page->Unref();
219 220
	pages.clear();

221 222
	if (current_page == nullptr)
		CancelWrite();
223 224
}

225 226
ssize_t
HttpdClient::TryWritePage(const Page &page, size_t position)
227
{
Max Kellermann's avatar
Max Kellermann committed
228
	assert(position < page.size);
229

230
	return Write(page.data + position, page.size - position);
231 232
}

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

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

	return -1;
}

inline bool
252
HttpdClient::TryWrite()
253 254 255 256 257 258
{
	const ScopeLock protect(httpd->mutex);

	assert(state == RESPONSE);

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

267 268 269 270 271
		current_page = pages.front();
		pages.pop_front();
		current_position = 0;
	}

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

				Close();
				return false;
			}
292

293
			metadata_current_position += nbytes;
294 295 296 297 298 299 300 301 302

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

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

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

316 317
				Close();
				return false;
318
			}
Max Kellermann's avatar
Max Kellermann committed
319

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

339 340 341 342 343
			Close();
			return false;
		}

		current_position += nbytes;
344 345 346
		assert(current_position <= current_page->size);

		if (metadata_requested)
347
			metadata_fill += nbytes;
348 349

		if (current_position >= current_page->size) {
Max Kellermann's avatar
Max Kellermann committed
350
			current_page->Unref();
351 352
			current_page = nullptr;

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

360
	return true;
361 362 363
}

void
Max Kellermann's avatar
Max Kellermann committed
364
HttpdClient::PushPage(Page *page)
365 366 367 368 369
{
	if (state != RESPONSE)
		/* the client is still writing the HTTP request */
		return;

Max Kellermann's avatar
Max Kellermann committed
370
	page->Ref();
371 372
	pages.push_back(page);

373
	ScheduleWrite();
374 375 376
}

void
Max Kellermann's avatar
Max Kellermann committed
377
HttpdClient::PushMetaData(Page *page)
378 379
{
	if (metadata) {
Max Kellermann's avatar
Max Kellermann committed
380
		metadata->Unref();
381 382 383 384 385
		metadata = nullptr;
	}

	g_return_if_fail (page);

Max Kellermann's avatar
Max Kellermann committed
386
	page->Ref();
387 388 389
	metadata = page;
	metadata_sent = false;
}
390 391 392 393 394 395 396 397 398 399 400 401 402 403 404 405 406 407

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

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

	return true;
}

BufferedSocket::InputResult
HttpdClient::OnSocketInput(const void *data, size_t length)
{
	if (state == RESPONSE) {
408 409
		LogWarning(httpd_output_domain,
			   "unexpected input from client");
410 411 412 413 414 415 416 417 418 419 420 421 422 423 424 425 426 427 428 429 430 431 432 433 434 435 436 437 438 439 440
		LockClose();
		return InputResult::CLOSED;
	}

	const char *line = (const char *)data;
	const char *newline = (const char *)memchr(line, '\n', length);
	if (newline == nullptr)
		return InputResult::MORE;

	ConsumeInput(newline + 1 - line);

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

	/* terminate the string at the end of the line; the const_cast
	   is a dirty hack */
	*const_cast<char *>(newline) = 0;

	if (!HandleLine(line)) {
		assert(state == RESPONSE);
		LockClose();
		return InputResult::CLOSED;
	}

	if (state == RESPONSE && !SendResponse())
		return InputResult::CLOSED;

	return InputResult::AGAIN;
}

void
441
HttpdClient::OnSocketError(Error &&error)
442
{
443
	LogError(error);
444 445 446 447 448 449 450
}

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