turbo-editors/turbo-corepublic Fork 0
v1.0.1
Commits
Clone
git clone https://git.rickub.com/turbo-editors/turbo-core.git
git clone ssh://git@rickub.com/turbo-editors/turbo-core.git

Host key fingerprint (ed25519): SHA256:iycHnxEyq0Q7uyVpB7JlznP0G7JrTPXLYRcAU5CSLhc — verify it before your first connect.

framing.go · 102 lines · 3.5 KBGo Blame HistoryRaw
🛟 Updated. 28d5985 k33g 14h ago1// Package lsp is a small Language Server Protocol client: enough of it to ask
2// a language server for completions, hovers, definitions and diagnostics.
3//
4// The protocol is JSON-RPC 2.0 inside HTTP-style frames. The JSON-RPC half is
5// jsonrpc's, shared with every other protocol built on it; what is here is the
6// framing, the methods, and the conversation a language server expects.
7package lsp
8
9import (
10 "bufio"
11 "errors"
12 "fmt"
13 "io"
14 "net/textproto"
15 "strconv"
16)
17
18// MaxMessageBytes caps how large a single message may be. A language server
19// should never send anything near this; the limit is there so that a corrupt
20// or hostile Content-Length cannot make the editor allocate without bound.
21const MaxMessageBytes = 64 << 20
22
23// ErrMessageTooLarge is returned when a frame announces more bytes than
24// MaxMessageBytes.
25var ErrMessageTooLarge = errors.New("lsp: message too large")
26
27// contentLengthHeader is the one header the protocol actually requires.
28const contentLengthHeader = "Content-Length"
29
30// Framing is the Language Server Protocol's way of marking one message off
31// from the next: a Content-Length header, a blank line, then the JSON.
32//
33// It is the jsonrpc.Framer a language server connection is built with.
34//
35// conn := jsonrpc.NewConn(stream, lsp.Framing{}, onNotify, onRequest)
36type Framing struct{}
37
38// ReadMessage reads one framed message and returns its body.
39func (Framing) ReadMessage(r *bufio.Reader) ([]byte, error) { return ReadMessage(r) }
40
41// WriteMessage frames body and writes it out.
42func (Framing) WriteMessage(w io.Writer, body []byte) error { return WriteMessage(w, body) }
43
44// WriteMessage frames body and writes it out.
45//
46// The frame is the header, a blank line, then the JSON — the same shape HTTP
47// uses, which is what the specification asks for.
48func WriteMessage(w io.Writer, body []byte) error {
49 if _, err := fmt.Fprintf(w, "%s: %d\r\n\r\n", contentLengthHeader, len(body)); err != nil {
50 return fmt.Errorf("lsp: writing the header: %w", err)
51 }
52 if _, err := w.Write(body); err != nil {
53 return fmt.Errorf("lsp: writing the body: %w", err)
54 }
55 return nil
56}
57
58// ReadMessage reads one framed message and returns its body.
59//
60// It returns io.EOF when the stream ends cleanly between messages, which is
61// how a server shutting down is told apart from one that died mid-message.
62func ReadMessage(r *bufio.Reader) ([]byte, error) {
63 length, err := readContentLength(r)
64 if err != nil {
65 return nil, err
66 }
67
68 body := make([]byte, length)
69 if _, err := io.ReadFull(r, body); err != nil {
70 return nil, fmt.Errorf("lsp: reading a %d-byte body: %w", length, err)
71 }
72 return body, nil
73}
74
75// readContentLength reads the header block and returns the announced size.
76func readContentLength(r *bufio.Reader) (int, error) {
77 header, err := textproto.NewReader(r).ReadMIMEHeader()
78 if err != nil {
79 if errors.Is(err, io.EOF) {
80 return 0, io.EOF
81 }
82 return 0, fmt.Errorf("lsp: reading the header: %w", err)
83 }
84 return parseContentLength(header.Get(contentLengthHeader))
85}
86
87// parseContentLength turns the header's value into a size, rejecting the ones
88// that are missing, malformed, or large enough to be an attack.
89func parseContentLength(raw string) (int, error) {
90 if raw == "" {
91 return 0, fmt.Errorf("lsp: the frame has no %s header", contentLengthHeader)
92 }
93
94 length, err := strconv.Atoi(raw)
95 if err != nil || length < 0 {
96 return 0, fmt.Errorf("lsp: %s is %q, which is not a size", contentLengthHeader, raw)
97 }
98 if length > MaxMessageBytes {
99 return 0, fmt.Errorf("%w: %d bytes", ErrMessageTooLarge, length)
100 }
101 return length, nil
102}