v. vanilla 2.0

restored/websocket_protocol.inc

← All source files · Download this file

14,943 bytes · SHA256 d6c157de7f247c3765329c2c48839eff7e39fa2197a1522d2875669140c8c4b9

// Original bounded RFC6455 codec; transport is in websocket.inc.
static bool wsTokenChar(unsigned char ch) {
    return (ch>='a'&&ch<='z') || (ch>='A'&&ch<='Z') || (ch>='0'&&ch<='9') ||
        std::string("!#$%&'*+-.^_`|~").find(static_cast<char>(ch))!=std::string::npos;
}
static bool wsToken(const std::string& text) {
    if(text.empty()) return false;
    for(unsigned char ch:text) if(!wsTokenChar(ch)) return false;
    return true;
}
// RFC6455 9.1: syntax only, never extension selection. The containing HTTP
// header is capped at 8192 bytes; this scanner is linear with constant storage.
static bool wsExtensionOffer(const std::string& text) {
    std::size_t pos=0; bool found=false;
    auto space=[&]() { while(pos<text.size() && (text[pos]==' '||text[pos]=='\t')) ++pos; };
    auto token=[&]() { auto begin=pos; while(pos<text.size() && wsTokenChar(static_cast<unsigned char>(text[pos]))) ++pos; return pos!=begin; };
    while(true) {
        space();
        // RFC2616 #rule permits empty list elements, but 1# needs a real one.
        while(pos<text.size() && text[pos]==',') { ++pos; space(); }
        if(pos==text.size()) return found;
        if(!token()) return false;
        found=true; space();
        while(pos<text.size() && text[pos]==';') {
            ++pos; space(); if(!token()) return false; space();
            if(pos<text.size() && text[pos]=='=') {
                ++pos; space();
                if(pos<text.size() && text[pos]=='"') {
                    ++pos; bool nonempty=false;
                    while(pos<text.size() && text[pos]!='"') {
                        unsigned char ch=static_cast<unsigned char>(text[pos++]);
                        if(ch=='\\') {
                            if(pos==text.size()) return false;
                            ch=static_cast<unsigned char>(text[pos++]);
                        }
                        // After quoted-pair unescaping the value MUST be token.
                        if(!wsTokenChar(ch)) return false;
                        nonempty=true;
                    }
                    if(!nonempty || pos==text.size()) return false;
                    ++pos;
                } else if(!token()) return false;
                space();
            }
        }
        if(pos==text.size()) return true;
        if(text[pos]!=',') return false;
        ++pos;
    }
}
static std::string wsLower(std::string text) { for(char& ch:text) if(ch>='A'&&ch<='Z') ch=static_cast<char>(ch-'A'+'a'); return text; }
static std::string wsTrim(const std::string& text) {
    auto begin=text.find_first_not_of(" \t"); if(begin==std::string::npos) return "";
    return text.substr(begin,text.find_last_not_of(" \t")-begin+1);
}
static std::vector<std::string> wsTokens(const std::string& text,const Source& s,bool lower=false) {
    std::vector<std::string> out; std::size_t pos=0;
    do { auto end=text.find(',',pos); auto t=wsTrim(text.substr(pos,end==std::string::npos ? end : end-pos));
        if(!wsToken(t)) fail(s,"websocket invalid HTTP token list");
        out.push_back(lower ? wsLower(t) : t);
        if(out.size()>64) fail(s,"websocket HTTP token count limit");
        if(end==std::string::npos) break;
        pos=end+1;
    } while(true);
    return out;
}
static bool wsContains(const std::string& text,const std::string& token,const Source& s,bool lower=false) {
    auto list=wsTokens(text,s,lower); return std::find(list.begin(),list.end(),token)!=list.end();
}
// Continuation bounds reject impossible prefixes before a codepoint finishes.
// A valid incomplete prefix is retained across frames (and interleaved controls).
struct WSUTF8State {
    unsigned left=0; unsigned char low=0x80,high=0xbf;
    bool feed(const std::string& text) {
        for(unsigned char ch:text) {
            if(left) {
                if(ch<low || ch>high) return false;
                --left; low=0x80; high=0xbf;
            } else if(ch<0x80) continue;
            else if(ch>=0xc2 && ch<=0xdf) left=1;
            else if(ch>=0xe0 && ch<=0xef) {
                left=2; low=ch==0xe0 ? 0xa0 : 0x80; high=ch==0xed ? 0x9f : 0xbf;
            } else if(ch>=0xf0 && ch<=0xf4) {
                left=3; low=ch==0xf0 ? 0x90 : 0x80; high=ch==0xf4 ? 0x8f : 0xbf;
            } else return false;
        }
        return true;
    }
    bool complete() const { return left==0; }
};
static bool wsUTF8(const std::string& text) {
    WSUTF8State state; return state.feed(text) && state.complete();
}
static bool wsCode(unsigned code) { return (code>=1000&&code<=1014&&code!=1004&&code!=1005&&code!=1006) || (code>=3000&&code<=4999); }
static std::string wsBase64(const unsigned char* data,int n) {
    std::string result(static_cast<std::size_t>(4*((n+2)/3))+1,'\0');
    int size=EVP_EncodeBlock(reinterpret_cast<unsigned char*>(result.data()),data,n); result.resize(static_cast<std::size_t>(size)); return result;
}
static std::string wsAccept(const std::string& key,const Source& s) {
    auto text=key+"258EAFA5-E914-47DA-95CA-C5AB0DC85B11"; unsigned char hash[EVP_MAX_MD_SIZE]; unsigned n=0;
    if(EVP_Digest(text.data(),text.size(),hash,&n,EVP_sha1(),nullptr)!=1) fail(s,"websocket SHA1 failed");
    return wsBase64(hash,static_cast<int>(n));
}
static bool wsKey(const std::string& key) {
    if(key.size()!=24 || key.substr(22)!="==") return false;
    unsigned char raw[24]; int n=EVP_DecodeBlock(raw,reinterpret_cast<const unsigned char*>(key.data()),24);
    return n==18 && wsBase64(raw,16)==key;
}
static void wsSafeField(const std::string& text,const Source& s) {
    if(text.size()>1024) fail(s,"websocket HTTP config field limit");
    for(unsigned char ch:text) if(ch<=32 || ch>=127) fail(s,"websocket unsafe HTTP config field");
}
struct SocketWebSocket {
    enum Phase { receiving, sending, open, closing } phase=receiving;
    bool client=false, parseReady=false, closeSent=false, closeReceived=false, applicationPending=false;
    unsigned fragment=0, closeCode=0;
    WSUTF8State textUTF8;
    std::string host,path,origin,protocol,accept,input,message;
    struct Output { std::string wire; std::size_t payload=0; unsigned opcode=0; };
    std::vector<Output> queue;
    std::size_t activePayload=0; unsigned activeOpcode=0;
    SocketClock::time_point deadline{};
    bool timed() const { return phase!=open; }
    std::size_t storage() const {
        std::size_t n=sizeof(*this)+host.capacity()+path.capacity()+origin.capacity()+protocol.capacity()+accept.capacity()+input.capacity()+message.capacity()+queue.capacity()*sizeof(Output);
        for(const auto& q:queue) n+=q.wire.capacity();
        return n;
    }
    void enqueue(unsigned opcode,const std::string& data,const Source& s) {
        if(queue.size()>=8) fail(s,"websocket control/output queue limit");
        Output out; out.opcode=opcode; out.payload=(opcode==1||opcode==2) ? data.size() : 0;
        out.wire.push_back(static_cast<char>(0x80|opcode)); unsigned mask=client ? 0x80 : 0;
        if(data.size()<126) out.wire.push_back(static_cast<char>(mask|data.size()));
        else if(data.size()<=65535) { out.wire.push_back(static_cast<char>(mask|126)); out.wire.push_back(static_cast<char>(data.size()>>8)); out.wire.push_back(static_cast<char>(data.size())); }
        else { out.wire.push_back(static_cast<char>(mask|127)); for(int i=7;i>=0;--i) out.wire.push_back(static_cast<char>(static_cast<std::uint64_t>(data.size())>>(i*8))); }
        unsigned char key[4]{};
        if(client) { if(RAND_bytes(key,4)!=1) fail(s,"websocket random mask failed"); out.wire.append(reinterpret_cast<char*>(key),4); }
        for(std::size_t i=0;i<data.size();++i) out.wire.push_back(static_cast<char>(static_cast<unsigned char>(data[i])^(client ? key[i%4] : 0)));
        queue.push_back(std::move(out));
    }
    // Complete HTTP header only; all bytes after its delimiter stay in input.
    bool upgrade(const Source& s) {
        auto end=input.find("\r\n\r\n");
        if(end==std::string::npos) { if(input.size()>=8192) fail(s,"websocket HTTP header limit"); parseReady=false; return false; }
        if(end+4>8192) fail(s,"websocket HTTP header limit");
        auto first=input.find("\r\n"); auto start=input.substr(0,first);
        if(client) { if(start.size()<13 || start.substr(0,13)!="HTTP/1.1 101 ") fail(s,"websocket expected HTTP 101"); }
        else if(start!="GET "+path+" HTTP/1.1") fail(s,"websocket request path/method mismatch");
        for(unsigned char ch:start) if(ch<32||ch>=127) fail(s,"websocket invalid HTTP start line");
        std::map<std::string,std::string> headers; std::size_t pos=first+2,countHeaders=0;
        while(pos<end) {
            auto e=input.find("\r\n",pos); auto line=input.substr(pos,e-pos); auto colon=line.find(':');
            if(colon==std::string::npos || !wsToken(line.substr(0,colon))) fail(s,"websocket invalid HTTP header");
            auto name=wsLower(line.substr(0,colon)); auto content=wsTrim(line.substr(colon+1));
            for(unsigned char ch:content) if((ch<32&&ch!=9)||ch==127) fail(s,"websocket invalid HTTP header value");
            if(++countHeaders>64) fail(s,"websocket HTTP header count limit");
            if(headers.count(name)) {
                if(name!="connection"&&name!="upgrade"&&name!="sec-websocket-protocol") fail(s,"websocket duplicate HTTP header");
                headers[name]+=","+content;
            } else headers[name]=content;
            pos=e+2;
        }
        if(!wsContains(headers["upgrade"],"websocket",s,true) || !wsContains(headers["connection"],"upgrade",s,true)) fail(s,"websocket missing upgrade tokens");
        if(headers.count("content-length") || headers.count("transfer-encoding")) fail(s,"websocket HTTP body forbidden");
        if(client) {
            if(headers["sec-websocket-accept"]!=accept) fail(s,"websocket accept verification failed");
            if(headers.count("sec-websocket-extensions")) fail(s,"websocket extensions unsupported");
            if(protocol.empty() ? headers.count("sec-websocket-protocol")!=0 : headers["sec-websocket-protocol"]!=protocol) fail(s,"websocket subprotocol mismatch");
            phase=open;
        } else {
            if(headers["host"]!=host || (origin.empty() ? headers.count("origin")!=0 : (!headers.count("origin") || headers["origin"]!=origin))) fail(s,"websocket Host/Origin mismatch");
            if(headers["sec-websocket-version"]!="13" || !wsKey(headers["sec-websocket-key"])) fail(s,"websocket invalid version/key");
            if(headers.count("sec-websocket-protocol")) (void)wsTokens(headers["sec-websocket-protocol"],s);
            if(headers.count("sec-websocket-extensions") && !wsExtensionOffer(headers["sec-websocket-extensions"])) fail(s,"websocket invalid extension offer");
            if(!protocol.empty() && !wsContains(headers["sec-websocket-protocol"],protocol,s)) fail(s,"websocket subprotocol mismatch");
            Output response; response.wire="HTTP/1.1 101 Switching Protocols\r\nUpgrade: websocket\r\nConnection: Upgrade\r\nSec-WebSocket-Accept: "+wsAccept(headers["sec-websocket-key"],s)+"\r\n";
            if(!protocol.empty()) response.wire+="Sec-WebSocket-Protocol: "+protocol+"\r\n";
            response.wire+="\r\n"; queue.push_back(std::move(response)); phase=sending;
        }
        input=std::string(input.data()+end+4,input.size()-end-4); parseReady=!input.empty(); return true;
    }
    // At most one frame, returning an event (empty status means need more bytes).
    std::string parse(std::string& data,std::string& type,const Source& s) {
        if(input.size()<2) { parseReady=false; return ""; }
        auto b0=static_cast<unsigned char>(input[0]),b1=static_cast<unsigned char>(input[1]); unsigned op=b0&15; bool fin=(b0&128)!=0,mask=(b1&128)!=0;
        if((b0&112) || (op!=0&&op!=1&&op!=2&&op!=8&&op!=9&&op!=10)) fail(s,"websocket invalid RSV/opcode");
        if(mask==client) fail(s,"websocket wrong masking direction");
        std::uint64_t n=b1&127; std::size_t header=2;
        if(op>=8 && (!fin || n>125)) fail(s,"websocket invalid control frame");
        if(n==126) { if(input.size()<4) { parseReady=false; return ""; } n=(static_cast<unsigned char>(input[2])<<8)|static_cast<unsigned char>(input[3]); header=4; if(n<126) fail(s,"websocket noncanonical length"); }
        else if(n==127) { if(input.size()<10) { parseReady=false; return ""; } n=0; for(std::size_t i=2;i<10;++i) n=(n<<8)|static_cast<unsigned char>(input[i]); header=10; if(n<65536 || (static_cast<unsigned char>(input[2])&128)) fail(s,"websocket noncanonical/overflow length"); }
        if(n>65536) fail(s,"websocket frame size limit (65536)");
        if(mask) header+=4;
        if(input.size()<header+static_cast<std::size_t>(n)) { parseReady=false; return ""; }
        data=input.substr(header,static_cast<std::size_t>(n));
        if(mask) for(std::size_t i=0;i<data.size();++i) data[i]=static_cast<char>(static_cast<unsigned char>(data[i])^static_cast<unsigned char>(input[header-4+i%4]));
        input=std::string(input.data()+header+n,input.size()-header-static_cast<std::size_t>(n)); parseReady=!input.empty();
        if(op>=8) {
            if(op==8) {
                if(data.size()==1) fail(s,"websocket invalid close payload");
                closeCode=data.empty() ? 1005U : (static_cast<unsigned char>(data[0])<<8)|static_cast<unsigned char>(data[1]);
                if(!data.empty() && (!wsCode(closeCode)||!wsUTF8(data.substr(2)))) fail(s,"websocket invalid close code/reason");
                closeReceived=true; return "closing";
            }
            return op==9 ? "ping" : "pong";
        }
        // Data already in flight when we sent Close is legal. Validate its
        // framing/text, but do not deliver application messages while closing.
        bool discard=phase==closing;
        if(op==0 ? fragment==0 : fragment!=0) fail(s,"websocket invalid fragmentation sequence");
        if(message.size()+data.size()>65536) fail(s,"websocket message size limit (65536)");
        if(op) { fragment=op; textUTF8=WSUTF8State{}; }
        if(fragment==1 && (!textUTF8.feed(data) || (fin && !textUTF8.complete()))) fail(s,"websocket invalid UTF8 text");
        std::string joined=message; joined+=data; message.swap(joined);
        data.clear(); if(!fin) return "progress";
        type=fragment==1 ? "text" : "binary";
        data.swap(message); std::string().swap(message); fragment=0;
        if(discard) { data.clear(); type.clear(); return "progress"; }
        return "message";
    }
};
static void websocketValidate(const CodeLine& c) {
    count(c,2,7); auto sub=c.arg[1].text;
    if(sub=="abort") { count(c,3,3); return; }
    if(sub=="client"||sub=="server"||sub=="send") count(c,6,6);
    else if(sub=="step") { count(c,5,5); destination(c.arg[3],c.source); }
    else if(sub=="close") count(c,7,7);
    else fail(c.source,"unknown websocket operation");
    destination(c.arg[2],c.source);
}