| 🛟 Updated. 28d5985 k33g 12h ago | 1 | // 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. |
| 7 | package lsp |
| 8 | |
| 9 | import ( |
| 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. |
| 21 | const MaxMessageBytes = 64 << 20 |
| 22 | |
| 23 | // ErrMessageTooLarge is returned when a frame announces more bytes than |
| 24 | // MaxMessageBytes. |
| 25 | var ErrMessageTooLarge = errors.New("lsp: message too large") |
| 26 | |
| 27 | // contentLengthHeader is the one header the protocol actually requires. |
| 28 | const 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) |
| 36 | type Framing struct{} |
| 37 | |
| 38 | // ReadMessage reads one framed message and returns its body. |
| 39 | func (Framing) ReadMessage(r *bufio.Reader) ([]byte, error) { return ReadMessage(r) } |
| 40 | |
| 41 | // WriteMessage frames body and writes it out. |
| 42 | func (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. |
| 48 | func 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. |
| 62 | func 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. |
| 76 | func 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. |
| 89 | func 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 | } |