+ long long frag_end = strtol(ptr, &endptr, 10);
+ if (ptr == endptr || frag_end < frag_start || frag_end == LLONG_MAX) {
+ return 400; // Bad request.
+ }
+
+ if (*endptr != '\0') {
+ return 400; // Bad request.
+ }
+
+ client->stream_pos = frag_start;
+ client->stream_pos_end = frag_end;
+ }
+ url = url.substr(0, pos);
+ } else {
+ client->stream_pos = -1;
+ client->stream_pos_end = -1;
+ }
+ }
+
+ // Figure out if we're supposed to close the socket after we've delivered the response.
+ string protocol = request_tokens[2];
+ if (protocol.find("HTTP/") != 0) {
+ return 400; // Bad request.
+ }
+ client->close_after_response = false;
+ client->http_11 = true;
+ if (protocol == "HTTP/1.0") {
+ // No persistent connections.
+ client->close_after_response = true;
+ client->http_11 = false;
+ } else {
+ const auto connection_it = headers.find("Connection");
+ if (connection_it != headers.end() && connection_it->second == "close") {
+ client->close_after_response = true;
+ }
+ }
+
+ const auto stream_url_map_it = stream_url_map.find(url);
+ if (stream_url_map_it != stream_url_map.end()) {
+ // Serve a regular stream..
+ client->stream = streams[stream_url_map_it->second].get();
+ client->serving_hls_playlist = false;
+ } else {
+ const auto stream_hls_url_map_it = stream_hls_url_map.find(url);
+ if (stream_hls_url_map_it != stream_hls_url_map.end()) {
+ // Serve HLS playlist.
+ client->stream = streams[stream_hls_url_map_it->second].get();
+ client->serving_hls_playlist = true;
+ } else {
+ const auto ping_url_map_it = ping_url_map.find(url);
+ if (ping_url_map_it == ping_url_map.end()) {
+ return 404; // Not found.
+ } else {
+ // Serve a ping (204 no error).
+ return 204;
+ }
+ }
+ }
+
+ Stream *stream = client->stream;
+
+ if (client->serving_hls_playlist) {
+ if (stream->encoding == Stream::STREAM_ENCODING_METACUBE) {
+ // This doesn't make any sense, and is hard to implement, too.
+ return 404;
+ } else {
+ return 200;
+ }
+ }
+
+ if (client->stream_pos_end == Client::STREAM_POS_NO_END) {
+ if (stream->unavailable) {
+ return 503; // Service unavailable.
+ }
+
+ // This stream won't end, so we don't have a content-length,
+ // and can just as well tell the client it's Connection: close
+ // (otherwise, we'd have to implement chunking TE for no good reason).
+ client->close_after_response = true;
+ } else {
+ if (stream->encoding == Stream::STREAM_ENCODING_METACUBE) {
+ // This doesn't make any sense, and is hard to implement, too.
+ return 416; // Range not satisfiable.
+ }
+
+ // Check that we have the requested fragment in our backlog.
+ size_t buffer_end = stream->bytes_received;
+ size_t buffer_start = (buffer_end <= stream->backlog_size) ? 0 : buffer_end - stream->backlog_size;
+
+ if (client->stream_pos_end > buffer_end ||
+ client->stream_pos < buffer_start) {
+ return 416; // Range not satisfiable.
+ }
+ }
+
+ client->stream = stream;
+ if (setsockopt(client->sock, SOL_SOCKET, SO_MAX_PACING_RATE, &client->stream->pacing_rate, sizeof(client->stream->pacing_rate)) == -1) {
+ if (client->stream->pacing_rate != ~0U) {
+ log_perror("setsockopt(SO_MAX_PACING_RATE)");
+ }
+ }
+ client->request.clear();
+
+ return 200; // OK!
+}
+
+void Server::construct_stream_header(Client *client)
+{
+ Stream *stream = client->stream;
+ string response = stream->http_header;
+ if (client->stream_pos == Client::STREAM_POS_HEADER_ONLY) {
+ char buf[64];
+ snprintf(buf, sizeof(buf), "Content-Length: %zu\r\n", stream->stream_header.size());
+ response.append(buf);
+ } else if (client->stream_pos_end != Client::STREAM_POS_NO_END) {
+ char buf[64];
+ snprintf(buf, sizeof(buf), "Content-Length: %" PRIu64 "\r\n", client->stream_pos_end - client->stream_pos);
+ response.append(buf);
+ }
+ if (client->http_11) {
+ assert(response.find("HTTP/1.0") == 0);
+ response[7] = '1'; // Change to HTTP/1.1.
+ if (client->close_after_response) {
+ response.append("Connection: close\r\n");
+ }
+ } else {
+ assert(client->close_after_response);
+ }
+ if (!stream->allow_origin.empty()) {
+ response.append("Access-Control-Allow-Origin: ");
+ response.append(stream->allow_origin);
+ response.append("\r\n");
+ }
+ if (stream->encoding == Stream::STREAM_ENCODING_RAW) {
+ response.append("\r\n");
+ } else if (stream->encoding == Stream::STREAM_ENCODING_METACUBE) {
+ response.append("Content-Encoding: metacube\r\n\r\n");
+ if (!stream->stream_header.empty()) {
+ metacube2_block_header hdr;
+ memcpy(hdr.sync, METACUBE2_SYNC, sizeof(hdr.sync));
+ hdr.size = htonl(stream->stream_header.size());
+ hdr.flags = htons(METACUBE_FLAGS_HEADER);
+ hdr.csum = htons(metacube2_compute_crc(&hdr));
+ response.append(string(reinterpret_cast<char *>(&hdr), sizeof(hdr)));
+ }
+ } else {
+ assert(false);
+ }
+ if (client->stream_pos == Client::STREAM_POS_HEADER_ONLY) {
+ client->state = Client::SENDING_SHORT_RESPONSE;
+ response.append(stream->stream_header);
+ } else {
+ client->state = Client::SENDING_HEADER;
+ if (client->stream_pos_end == Client::STREAM_POS_NO_END) { // Fragments don't contain stream headers.
+ response.append(stream->stream_header);
+ }
+ }
+
+ client->header_or_short_response_holder = move(response);
+ client->header_or_short_response = &client->header_or_short_response_holder;
+
+ // Switch states.
+ change_epoll_events(client, EPOLLOUT | EPOLLET | EPOLLRDHUP);
+}
+
+void Server::construct_error(Client *client, int error_code)
+{
+ char error[256];
+ if (client->http_11 && client->close_after_response) {
+ snprintf(error, sizeof(error),
+ "HTTP/1.1 %d Error\r\nContent-Type: text/plain\r\nConnection: close\r\n\r\nSomething went wrong. Sorry.\r\n",
+ error_code);
+ } else {
+ snprintf(error, sizeof(error),
+ "HTTP/1.%d %d Error\r\nContent-Type: text/plain\r\nContent-Length: 30\r\n\r\nSomething went wrong. Sorry.\r\n",
+ client->http_11, error_code);
+ }
+ client->header_or_short_response_holder = error;
+ client->header_or_short_response = &client->header_or_short_response_holder;