ws.c (5522B)
1 /* ws.c - WebSocket (RFC 6455) sobre buffers. Solo lo que necesita el servidor de 2 * lineas: handshake, desenmascarar frames del cliente y envolver respuestas. */ 3 #include <stdbool.h> 4 #include <stdio.h> 5 #include <string.h> 6 #include "sha1.h" 7 #include "ws.h" 8 9 #define WS_MAX_FRAME (1 << 20) /* mas que esto es un cliente roto o malicioso */ 10 11 static int lc(int ch) { return ch >= 'A' && ch <= 'Z' ? ch + 32 : ch; } 12 13 /* busca una cabecera (sin distinguir mayusculas) y copia su valor sin espacios */ 14 static int header(const char *req, int n, const char *name, char *out, int cap) 15 { 16 int nl = (int)strlen(name); 17 for (int i = 0; i + nl + 1 < n; i++) { 18 if (i > 0 && req[i - 1] != '\n') continue; 19 int k = 0; 20 while (k < nl && lc(req[i + k]) == lc(name[k])) k++; 21 if (k < nl || req[i + nl] != ':') continue; 22 int p = i + nl + 1; 23 while (p < n && (req[p] == ' ' || req[p] == '\t')) p++; 24 int o = 0; 25 while (p < n && req[p] != '\r' && req[p] != '\n' && o < cap - 1) out[o++] = req[p++]; 26 while (o > 0 && (out[o - 1] == ' ' || out[o - 1] == '\t')) o--; 27 out[o] = 0; 28 return o; 29 } 30 return -1; 31 } 32 33 int ws_handshake(const char *req, int n, int *consumed, char *resp, int cap) 34 { 35 const char *end = 0; 36 for (int i = 0; i + 3 < n; i++) 37 if (req[i] == '\r' && req[i + 1] == '\n' && req[i + 2] == '\r' && req[i + 3] == '\n') { end = req + i + 4; break; } 38 if (!end) return 0; 39 int len = (int)(end - req); 40 *consumed = len; 41 char key[128], proto[128], upgrade[32] = ""; 42 header(req, len, "Upgrade", upgrade, sizeof upgrade); 43 for (char *p = upgrade; *p; p++) *p = (char)(*p | 32); 44 bool ok = n >= 4 && !memcmp(req, "GET ", 4) && !strcmp(upgrade, "websocket") 45 && header(req, len, "Sec-WebSocket-Key", key, sizeof key) > 0; 46 if (!ok) { 47 snprintf(resp, (size_t)cap, "HTTP/1.1 400 Bad Request\r\nConnection: close\r\nContent-Length: 0\r\n\r\n"); 48 return -1; 49 } 50 /* Accept = base64(sha1(key + GUID)) */ 51 char cat[200]; 52 snprintf(cat, sizeof cat, "%s258EAFA5-E914-47DA-95CA-C5AB0DC85B11", key); 53 uint8_t dig[20]; 54 sha1(cat, strlen(cat), dig); 55 char accept[32]; 56 base64_encode(dig, 20, accept, sizeof accept); 57 /* el primer subprotocolo ofrecido, si hay (el cliente emscripten manda "binary"); 58 * la lista puede traer espacios alrededor de las comas */ 59 char sub[160] = ""; 60 if (header(req, len, "Sec-WebSocket-Protocol", proto, sizeof proto) > 0) { 61 char *p = proto; 62 while (*p == ' ' || *p == ',') p++; 63 char *e = p; 64 while (*e && *e != ',' && *e != ' ') e++; 65 *e = 0; 66 if (*p) snprintf(sub, sizeof sub, "Sec-WebSocket-Protocol: %s\r\n", p); 67 } 68 int r = snprintf(resp, (size_t)cap, 69 "HTTP/1.1 101 Switching Protocols\r\nUpgrade: websocket\r\nConnection: Upgrade\r\n" 70 "Sec-WebSocket-Accept: %s\r\n%s\r\n", accept, sub); 71 return r >= cap ? -1 : r; 72 } 73 74 int ws_unwrap(const uint8_t *buf, int n, char *out, int cap, int *outlen, int *ctl, uint8_t ping[WS_CTL_MAX], int *pinglen) 75 { 76 int pos = 0, o = 0; 77 *ctl = 0; 78 *pinglen = 0; 79 while (pos + 2 <= n) { 80 const uint8_t *f = buf + pos; 81 int op = f[0] & 0x0F, fin = f[0] & 0x80, masked = f[1] & 0x80, hdr = 2; 82 uint64_t len = f[1] & 0x7F; 83 if (len == 126) { 84 if (pos + 4 > n) break; 85 len = (uint64_t)f[2] << 8 | f[3]; 86 hdr = 4; 87 } else if (len == 127) { 88 if (pos + 10 > n) break; 89 len = 0; 90 for (int i = 0; i < 8; i++) len = len << 8 | f[2 + i]; 91 hdr = 10; 92 } 93 if (!masked || len > WS_MAX_FRAME || (f[0] & 0x70)) return -1; /* el cliente siempre enmascara; sin extensiones */ 94 bool data = op == WS_CONT || op == WS_TEXT || op == WS_BINARY; 95 bool control = op == WS_CLOSE || op == WS_PING || op == WS_PONG; 96 if (!data && !control) return -1; 97 if (control && (!fin || len > WS_CTL_MAX)) return -1; 98 if (data && len > (uint64_t)cap) return -1; /* nunca va a entrar */ 99 if (data && len > (uint64_t)(cap - o)) break; /* entra, pero en la proxima: primero vaciar out */ 100 hdr += 4; 101 if ((uint64_t)(n - pos) < (uint64_t)hdr + len) break; /* frame incompleto */ 102 const uint8_t *mask = f + hdr - 4, *pay = f + hdr; 103 if (data) { 104 for (uint64_t i = 0; i < len; i++) out[o++] = (char)(pay[i] ^ mask[i & 3]); 105 } else { 106 *ctl |= 1 << op; 107 if (op == WS_PING) { 108 *pinglen = (int)len; 109 for (uint64_t i = 0; i < len; i++) ping[i] = (uint8_t)(pay[i] ^ mask[i & 3]); 110 } 111 } 112 pos += hdr + (int)len; 113 if (op == WS_CLOSE) break; /* lo que siga ya no importa */ 114 } 115 *outlen = o; 116 return pos; 117 } 118 119 int ws_wrap(int opcode, const void *data, int n, uint8_t *out, int cap) 120 { 121 int hdr = n < 126 ? 2 : n < 65536 ? 4 : 10; 122 if (n < 0 || hdr + n > cap) return -1; 123 out[0] = (uint8_t)(0x80 | opcode); 124 if (hdr == 2) out[1] = (uint8_t)n; 125 else if (hdr == 4) { out[1] = 126; out[2] = (uint8_t)(n >> 8); out[3] = (uint8_t)n; } 126 else { 127 out[1] = 127; 128 for (int i = 0; i < 8; i++) out[2 + i] = (uint8_t)((uint64_t)n >> (8 * (7 - i))); 129 } 130 if (n) memcpy(out + hdr, data, (size_t)n); 131 return hdr + n; 132 }