/************************************************************************* * UrBackup - Client/Server backup system * Copyright (C) 2020 Martin Raiber * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU Affero General Public License as published by * the Free Software Foundation, either version 3 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 Affero General Public License for more details. * * You should have received a copy of the GNU Affero General Public License * along with this program. If not, see . **************************************************************************/ #include "WebSocketPipe.h" #include "../Interface/Server.h" #include #include #include "../stringtools.h" #include WebSocketPipe::WebSocketPipe(IPipe* pipe, const bool mask_writes, const bool expect_read_mask, std::string pipe_add, bool destroy_pipe) : pipe(pipe), mask_writes(mask_writes), expect_read_mask(expect_read_mask), has_error(false), pipe_add(pipe_add), read_state(EReadState_Header1), destroy_pipe(destroy_pipe), read_mutex(Server->createMutex()), write_mutex(Server->createMutex()) { memset(masking_key, 0, sizeof(masking_key)); if (mask_writes) { char zero_mask[4] = {}; /* * Just use a fixed non-random masking key. We are not a browser, so the security implications * are a bit different */ while (memcmp(zero_mask, masking_key, sizeof(masking_key)) == 0) { Server->randomFill(masking_key, sizeof(masking_key)); } } } WebSocketPipe::~WebSocketPipe() { if (destroy_pipe) delete pipe; } size_t WebSocketPipe::Read(char* buffer, size_t bsize, int timeoutms) { IScopedLock lock(read_mutex.get()); if (!pipe_add.empty()) { size_t consumed_out = 0; size_t data_size = consume(&pipe_add[0], (std::min)(bsize, pipe_add.size()), timeoutms, &consumed_out); if (data_size > 0) { memcpy(buffer, pipe_add.data(), data_size); } if (consumed_out > 0) { pipe_add.erase(0, consumed_out); } if (data_size > 0) { return data_size; } } int64 starttime = 0; if (timeoutms > 0) { starttime = Server->getTimeMS(); } do { int curr_timeoutms = static_cast(timeoutms > 0 ? (timeoutms - (Server->getTimeMS() - starttime)) : timeoutms); size_t read = pipe->Read(buffer, bsize, curr_timeoutms); if (read > 0) { size_t data_size = consume(buffer, read, curr_timeoutms, NULL); if (data_size > 0) { return data_size; } } else { return 0; } } while (timeoutms == -1 || (timeoutms > 0 && Server->getTimeMS() - starttime < timeoutms)); return 0; } bool WebSocketPipe::Write(const char* buffer, size_t bsize, int timeoutms, bool flush) { IScopedLock lock(write_mutex.get()); size_t payload_len_size = 1; if (bsize > 65535) { payload_len_size = 8; } else if (bsize > 125) { payload_len_size = 2; } char header[2 + 8 + 4]; unsigned char bits = 0; bits |= 1 << 7; //FIN bit unsigned char opcode = 2; //binary frame; bits |= opcode; header[0] = static_cast(bits); size_t header_pos = 1; if (bsize > 65535) { header[header_pos] = 127; ++header_pos; uint64 payload_size = bsize; memcpy(&header[header_pos], &payload_size, sizeof(payload_size)); header_pos += sizeof(payload_size); } else if (bsize > 125) { header[header_pos] = 126; ++header_pos; unsigned short payload_size = static_cast(bsize); memcpy(&header[header_pos], &payload_size, sizeof(payload_size)); header_pos += sizeof(payload_size); } else { header[header_pos] = static_cast(bsize); ++header_pos; } if (mask_writes) { header[1] |= 1 << 7; memcpy(&header[header_pos], &masking_key, sizeof(masking_key)); header_pos += sizeof(masking_key); std::vector new_buf; new_buf.resize(header_pos + bsize); memcpy(new_buf.data(), header, header_pos); Server->Log("Masking key: " + convert(*((unsigned int*)masking_key))); for (size_t i = 0; i < bsize; ++i) { size_t j = i % 4; new_buf[header_pos + i] = buffer[i] ^ masking_key[j]; } return pipe->Write(new_buf.data(), header_pos + bsize, timeoutms, flush); } else { if (!pipe->Write(header, header_pos, timeoutms, false)) { return false; } return pipe->Write(buffer, bsize, timeoutms, flush); } } size_t WebSocketPipe::Read(std::string* ret, int timeoutms) { IScopedLock lock(read_mutex.get()); if (!pipe_add.empty()) { size_t consumed_out = 0; size_t data_size = consume(&pipe_add[0], pipe_add.size(), timeoutms, &consumed_out); if (data_size > 0) { ret->assign(pipe_add.data(), data_size); } if (consumed_out > 0) { pipe_add.erase(0, consumed_out); } if (data_size > 0) { return data_size; } } int64 starttime = 0; if (timeoutms > 0) { starttime = Server->getTimeMS(); } do { int curr_timeoutms = static_cast(timeoutms > 0 ? (timeoutms - (Server->getTimeMS() - starttime)) : timeoutms); size_t read = pipe->Read(ret, curr_timeoutms); if (read > 0) { size_t data_size = consume(&(*ret)[0], ret->size(), curr_timeoutms, NULL); if (data_size > 0) { ret->resize(data_size); return ret->size(); } } else { return 0; } } while (timeoutms < 0 || (timeoutms > 0 && Server->getTimeMS() - starttime < timeoutms)); return 0; } bool WebSocketPipe::isReadable(int timeoutms) { { IScopedLock lock(read_mutex.get()); if (!pipe_add.empty()) return true; } return pipe->isReadable(timeoutms); } size_t WebSocketPipe::consume(char* buffer, size_t bsize, int write_timeoutms, size_t* consumed_out) { size_t consumed = 0; size_t out_off = 0; while (bsize > consumed) { switch (read_state) { case EReadState_Header1: { header_bits1 = buffer[consumed]; ++consumed; read_state = EReadState_HeaderSize1; unsigned char opcode = get_opcode(); if (opcode != 0 && opcode != 1 && opcode != 2 && opcode != 8 && opcode != 9 && opcode != 10) { has_error = true; } if (!(header_bits1 & 1) && opcode != 0 && opcode != 1 && opcode != 2) { has_error = true; } }break; case EReadState_HeaderSize1: { header_bits2 = buffer[consumed]; if (expect_read_mask && !has_read_mask()) { has_error = true; } unsigned char tmp_payload_size = header_bits2 & 0x7F; ++consumed; if (tmp_payload_size < 126) { payload_size = tmp_payload_size; if (has_read_mask()) { read_state = EReadState_HeaderMask; remaining_size_bytes = 4; consumed_size_bytes = 0; curr_has_read_mask = true; } else { curr_has_read_mask = false; read_state = EReadState_Body; } } else if (tmp_payload_size == 126) { remaining_size_bytes = 2; consumed_size_bytes = 0; payload_size = 0; read_state = EReadState_HeaderSize2; } else if (tmp_payload_size == 127) { remaining_size_bytes = 8; consumed_size_bytes = 0; payload_size = 0; read_state = EReadState_HeaderSize2; } else { has_error = true; } }break; case EReadState_HeaderSize2: { //PERF: In EReadState_HeaderSize2 and EReadState_HeaderMask read multiple via memcpy if available unsigned char size_byte = buffer[consumed]; ++consumed; --remaining_size_bytes; payload_size |= size_byte << (consumed_size_bytes * 8); ++consumed_size_bytes; if (remaining_size_bytes == 0) { if (has_read_mask()) { read_state = EReadState_HeaderMask; curr_has_read_mask = true; remaining_size_bytes = 4; consumed_size_bytes = 0; } else { curr_has_read_mask = false; read_state = EReadState_Body; } } }break; case EReadState_HeaderMask: { unsigned char mask_byte = buffer[consumed]; ++consumed; --remaining_size_bytes; read_mask[consumed_size_bytes] = mask_byte; ++consumed_size_bytes; if (remaining_size_bytes == 0) { read_state = EReadState_Body; read_mask_idx = 0; } } break; case EReadState_Body: { size_t toread = static_cast((std::min)(static_cast(bsize - consumed), payload_size)); payload_size -= toread; unsigned char opcode = get_opcode(); if (opcode != 0 && opcode!=1 && opcode != 2) { //Ignore payload consumed += toread; } else if (out_off == consumed) { if (curr_has_read_mask) { for (size_t i = 0; i < toread; ++i) { buffer[out_off] = buffer[out_off] ^ read_mask[read_mask_idx % 4]; ++read_mask_idx; ++out_off; } consumed = out_off; } else { consumed += toread; out_off += toread; } } else { assert(out_off < consumed); if (curr_has_read_mask) { for (size_t i = 0; i < toread; ++i) { buffer[out_off] = buffer[consumed] ^ read_mask[read_mask_idx % 4]; ++read_mask_idx; ++out_off; ++consumed; } } else { memmove(&buffer[out_off], &buffer[consumed], toread); consumed += toread; out_off += toread; } } if (payload_size == 0) { read_state = EReadState_Header1; unsigned char opcode = get_opcode(); if (opcode == 8) { //Close char msg[2]; msg[0] = header_bits1; msg[1] = header_bits2 | (1<<7); pipe->Write(msg, 2, write_timeoutms, true); has_error = true; } else if (opcode == 9) { //Close char msg[2]; unsigned char opcode = 10; //pong msg[0] = opcode | (1 << 7); msg[1] = static_cast(1<<7); if (!pipe->Write(msg, 2, write_timeoutms, true)) { has_error = true; } } } }break; } } if (consumed_out != NULL) *consumed_out = consumed; return out_off; }