Created
February 20, 2025 20:59
-
-
Save aneury1/3b1f12d44cb1a43ad68a827e5acc7354 to your computer and use it in GitHub Desktop.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
#include <stdio.h> | |
#include <stdlib.h> | |
#include <string.h> | |
#include <unistd.h> | |
#include <sys/socket.h> | |
#include <netinet/in.h> | |
#include <arpa/inet.h> | |
#include <openssl/sha.h> // For SHA-1 (link with -lcrypto) | |
#include <openssl/bio.h> // For Base64 encoding | |
#include <openssl/evp.h> | |
#include <openssl/buffer.h> | |
#define PORT 8080 | |
#define BUFFER_SIZE 1024 | |
#define MAGIC_STRING "258EAFA5-E914-47DA-95CA-C5AB0DC85B11" | |
// Compute Sec-WebSocket-Accept header value (unchanged from previous example) | |
char* compute_accept_key(const char* client_key) { | |
char combined[100]; | |
snprintf(combined, sizeof(combined), "%s%s", client_key, MAGIC_STRING); | |
unsigned char sha1[SHA_DIGEST_LENGTH]; | |
SHA1((unsigned char*)combined, strlen(combined), sha1); | |
BIO* b64 = BIO_new(BIO_f_base64()); | |
BIO* mem = BIO_new(BIO_s_mem()); | |
b64 = BIO_push(b64, mem); | |
BIO_set_flags(b64, BIO_FLAGS_BASE64_NO_NL); | |
BIO_write(b64, sha1, SHA_DIGEST_LENGTH); | |
BIO_flush(b64); | |
BUF_MEM* buffer; | |
BIO_get_mem_ptr(b64, &buffer); | |
char* accept_key = (char *)malloc(buffer->length + 1); | |
memcpy(accept_key, buffer->data, buffer->length); | |
accept_key[buffer->length] = '\0'; | |
BIO_free_all(b64); | |
return accept_key; | |
} | |
// Send HTTP handshake response (unchanged) | |
void send_handshake(int client_fd, const char* client_key) { | |
char* accept_key = compute_accept_key(client_key); | |
char response[BUFFER_SIZE]; | |
snprintf(response, sizeof(response), | |
"HTTP/1.1 101 Switching Protocols\r\n" | |
"Upgrade: websocket\r\n" | |
"Connection: Upgrade\r\n" | |
"Sec-WebSocket-Accept: %s\r\n\r\n", | |
accept_key); | |
write(client_fd, response, strlen(response)); | |
free(accept_key); | |
} | |
// Extract Sec-WebSocket-Key from HTTP request (unchanged) | |
char* extract_key(const char* request) { | |
const char* key_start = strstr(request, "Sec-WebSocket-Key: "); | |
if (!key_start) return NULL; | |
key_start += 19; | |
char* key_end = (char *)strstr(key_start, "\r\n"); | |
if (!key_end) return NULL; | |
int key_len = key_end - key_start; | |
char* key = (char *)malloc(key_len + 1); | |
strncpy(key, key_start, key_len); | |
key[key_len] = '\0'; | |
return key; | |
} | |
// Decode a masked payload | |
void decode_payload(char* payload, unsigned char* mask_key, int payload_len) { | |
for (int i = 0; i < payload_len; i++) { | |
payload[i] = payload[i] ^ mask_key[i % 4]; | |
} | |
} | |
int main() { | |
int server_fd, client_fd; | |
struct sockaddr_in address; | |
int addrlen = sizeof(address); | |
char buffer[BUFFER_SIZE] = {0}; | |
// Socket setup (unchanged) | |
if ((server_fd = socket(AF_INET, SOCK_STREAM, 0)) == 0) { | |
perror("Socket failed"); | |
exit(EXIT_FAILURE); | |
} | |
address.sin_family = AF_INET; | |
address.sin_addr.s_addr = INADDR_ANY; | |
address.sin_port = htons(PORT); | |
if (bind(server_fd, (struct sockaddr*)&address, sizeof(address)) < 0) { | |
perror("Bind failed"); | |
exit(EXIT_FAILURE); | |
} | |
if (listen(server_fd, 3) < 0) { | |
perror("Listen failed"); | |
exit(EXIT_FAILURE); | |
} | |
printf("Server listening on port %d\n", PORT); | |
// Accept client | |
if ((client_fd = accept(server_fd, (struct sockaddr*)&address, (socklen_t*)&addrlen)) < 0) { | |
perror("Accept failed"); | |
exit(EXIT_FAILURE); | |
} | |
// Handle handshake | |
read(client_fd, buffer, BUFFER_SIZE); | |
char* client_key = extract_key(buffer); | |
if (client_key) { | |
send_handshake(client_fd, client_key); | |
free(client_key); | |
} else { | |
printf("Invalid WebSocket request\n"); | |
close(client_fd); | |
close(server_fd); | |
return 1; | |
} | |
// WebSocket frame handling loop | |
while (1) { | |
memset(buffer, 0, BUFFER_SIZE); | |
int bytes_read = read(client_fd, buffer, BUFFER_SIZE); | |
if (bytes_read <= 0) break; | |
// Parse frame | |
unsigned char first_byte = buffer[0]; | |
unsigned char second_byte = buffer[1]; | |
int fin = (first_byte & 0x80) >> 7; // FIN bit | |
int opcode = first_byte & 0x0F; // Opcode (e.g., 0x1 for text) | |
int mask = (second_byte & 0x80) >> 7; // Mask bit | |
int payload_len = second_byte & 0x7F; // Payload length (basic case) | |
if (opcode == 0x1 && fin == 1) { // Text frame, FIN set | |
char* payload; | |
unsigned char* mask_key; | |
if (mask == 1) { | |
// Masked frame: mask key follows second byte | |
mask_key = (unsigned char*)buffer + 2; | |
payload = buffer + 6; // Payload starts after mask key | |
decode_payload(payload, mask_key, payload_len); | |
} else { | |
// Unmasked frame (shouldn’t happen from client, but handle for robustness) | |
payload = buffer + 2; | |
} | |
printf("Received: %.*s\n", payload_len, payload); | |
// Echo back (unmasked, FIN=1, opcode=0x1) | |
char frame[BUFFER_SIZE] = {(char)0x81, (char)payload_len}; | |
memcpy(frame + 2, payload, payload_len); | |
write(client_fd, frame, 2 + payload_len); | |
} else { | |
printf("Unsupported or fragmented frame\n"); | |
} | |
} | |
close(client_fd); | |
close(server_fd); | |
return 0; | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment