+ // "?frag=header" is special.
+ if (strcmp(ptr, "header") == 0) {
+ client->stream_pos = Client::STREAM_POS_HEADER_ONLY;
+ client->stream_pos_end = -1;
+ } else {
+ char *endptr;
+ long long frag_start = strtol(ptr, &endptr, 10);
+ if (ptr == endptr || frag_start < 0 || frag_start == LLONG_MAX) {
+ return 400; // Bad request.
+ }
+ if (*endptr != '-') {
+ return 400; // Bad request.
+ }
+ ptr = endptr + 1;
+
+ 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 {
+ multimap<string, string>::const_iterator connection_it = headers.find("Connection");
+ if (connection_it != headers.end() && connection_it->second == "close") {
+ client->close_after_response = true;
+ }
+ }
+
+ map<string, int>::const_iterator stream_url_map_it = stream_url_map.find(url);
+ if (stream_url_map_it == stream_url_map.end()) {
+ map<string, string>::const_iterator ping_url_map_it = ping_url_map.find(url);
+ if (ping_url_map_it == ping_url_map.end()) {
+ return 404; // Not found.
+ } else {
+ return 204; // No error.
+ }
+ }
+
+ Stream *stream = streams[stream_url_map_it->second].get();
+ if (stream->http_header.empty()) {
+ return 503; // Service unavailable.
+ }
+
+ if (client->stream_pos_end == Client::STREAM_POS_NO_END) {
+ // 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_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: %zu\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->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;