Monorepo for Tangled tangled.org
757
fork

Configure Feed

Select the types of activity you want to include in your feed.

knotserver: limit request size

Signed-off-by: oppiliappan <me@oppi.li>

authored by

oppiliappan and committed by
Tangled
df86f772 8bbe4a6a

+59 -21
+1
knotserver/config/config.go
··· 23 23 JetstreamEndpoint string `env:"JETSTREAM_ENDPOINT, default=wss://jetstream1.us-west.bsky.network/subscribe"` 24 24 Owner string `env:"OWNER, required"` 25 25 LogDids bool `env:"LOG_DIDS, default=true"` 26 + MaxResponseKB int `env:"MAX_RESPONSE_KB, default=5120"` 26 27 27 28 // This disables signature verification so use with caution. 28 29 Dev bool `env:"DEV, default=false"`
+2 -2
knotserver/xrpc/create_repo.go
··· 109 109 if _, statErr := os.Stat(didRepoPath); statErr == nil { 110 110 l.Info("repo already exists from previous attempt", "repoDid", existingDid) 111 111 output := tangled.RepoCreate_Output{RepoDid: &existingDid} 112 - writeJson(w, &output) 112 + h.writeJson(w, &output) 113 113 return 114 114 } 115 115 l.Warn("stale repo key found without directory, cleaning up", "repoDid", existingDid) ··· 250 250 } 251 251 }() 252 252 253 - writeJson(w, &tangled.RepoCreate_Output{RepoDid: &repoDid}) 253 + h.writeJson(w, &tangled.RepoCreate_Output{RepoDid: &repoDid}) 254 254 } 255 255 256 256 func (h *Xrpc) requestCrawl(ctx context.Context, input *tangled.SyncRequestCrawl_Input) error {
+1 -1
knotserver/xrpc/list_keys.go
··· 45 45 response.Cursor = &nextCursor 46 46 } 47 47 48 - writeJson(w, response) 48 + x.writeJson(w, response) 49 49 }
+1 -1
knotserver/xrpc/owner.go
··· 18 18 Owner: owner, 19 19 } 20 20 21 - writeJson(w, response) 21 + x.writeJson(w, response) 22 22 }
+2 -2
knotserver/xrpc/repo_blob.go
··· 58 58 Branch: &submodule.Branch, 59 59 }, 60 60 } 61 - writeJson(w, response) 61 + x.writeJson(w, response) 62 62 return 63 63 } 64 64 ··· 173 173 } 174 174 } 175 175 176 - writeJson(w, response) 176 + x.writeJson(w, response) 177 177 } 178 178 179 179 // isTextualMimeType returns true if the MIME type represents textual content
+1 -1
knotserver/xrpc/repo_branch.go
··· 81 81 When: commit.Author.When.Format(time.RFC3339), 82 82 } 83 83 84 - writeJson(w, response) 84 + x.writeJson(w, response) 85 85 }
+1 -1
knotserver/xrpc/repo_branches.go
··· 45 45 Branches: branches, 46 46 } 47 47 48 - writeJson(w, response) 48 + x.writeJson(w, response) 49 49 }
+1 -1
knotserver/xrpc/repo_compare.go
··· 99 99 CombinedPatchRaw: combinedPatchRaw, 100 100 } 101 101 102 - writeJson(w, response) 102 + x.writeJson(w, response) 103 103 }
+1 -1
knotserver/xrpc/repo_diff.go
··· 37 37 Diff: diff, 38 38 } 39 39 40 - writeJson(w, response) 40 + x.writeJson(w, response) 41 41 }
+1 -1
knotserver/xrpc/repo_get_default_branch.go
··· 35 35 When: time.UnixMicro(0).Format(time.RFC3339), 36 36 } 37 37 38 - writeJson(w, response) 38 + x.writeJson(w, response) 39 39 }
+1 -1
knotserver/xrpc/repo_languages.go
··· 72 72 response.TotalFiles = &totalFiles 73 73 } 74 74 75 - writeJson(w, response) 75 + x.writeJson(w, response) 76 76 }
+1 -1
knotserver/xrpc/repo_log.go
··· 82 82 83 83 response.Log = true 84 84 85 - writeJson(w, response) 85 + x.writeJson(w, response) 86 86 }
+1 -1
knotserver/xrpc/repo_tag.go
··· 81 81 Tag: &tr, 82 82 } 83 83 84 - writeJson(w, response) 84 + x.writeJson(w, response) 85 85 }
+1 -1
knotserver/xrpc/repo_tags.go
··· 75 75 Tags: rtags, 76 76 } 77 77 78 - writeJson(w, response) 78 + x.writeJson(w, response) 79 79 }
+1 -1
knotserver/xrpc/repo_tree.go
··· 142 142 } 143 143 } 144 144 145 - writeJson(w, response) 145 + x.writeJson(w, response) 146 146 }
+1 -1
knotserver/xrpc/version.go
··· 56 56 Version: version, 57 57 } 58 58 59 - writeJson(w, response) 59 + x.writeJson(w, response) 60 60 }
+29 -4
knotserver/xrpc/xrpc.go
··· 1 1 package xrpc 2 2 3 3 import ( 4 + "bytes" 4 5 "encoding/json" 6 + "errors" 5 7 "log/slog" 6 8 "net/http" 7 9 "os" ··· 122 124 json.NewEncoder(w).Encode(e) 123 125 } 124 126 125 - func writeJson(w http.ResponseWriter, response any) { 126 - w.Header().Set("Content-Type", "application/json") 127 - if err := json.NewEncoder(w).Encode(response); err != nil { 128 - writeError(w, xrpcerr.GenericError(err), http.StatusInternalServerError) 127 + type limitWriter struct { 128 + buf bytes.Buffer 129 + limit int 130 + written int 131 + } 132 + 133 + var errResponseTooLarge = errors.New("response too large") 134 + 135 + func (lw *limitWriter) Write(p []byte) (int, error) { 136 + if lw.written+len(p) > lw.limit { 137 + return 0, errResponseTooLarge 138 + } 139 + n, err := lw.buf.Write(p) 140 + lw.written += n 141 + return n, err 142 + } 143 + 144 + func (x *Xrpc) writeJson(w http.ResponseWriter, response any) { 145 + lw := &limitWriter{limit: x.Config.Server.MaxResponseKB * 1024} 146 + if err := json.NewEncoder(lw).Encode(response); err != nil { 147 + if errors.Is(err, errResponseTooLarge) { 148 + writeError(w, xrpcerr.RequestTooLargeError, http.StatusRequestEntityTooLarge) 149 + } else { 150 + writeError(w, xrpcerr.GenericError(err), http.StatusInternalServerError) 151 + } 129 152 return 130 153 } 154 + w.Header().Set("Content-Type", "application/json") 155 + w.Write(lw.buf.Bytes()) 131 156 }
+7
nix/modules/knot.nix
··· 177 177 default = false; 178 178 description = "Enable development mode (disables signature verification)"; 179 179 }; 180 + 181 + maxResponseKB = mkOption { 182 + type = types.int; 183 + default = 5120; 184 + description = "Maximum response size in kilobytes"; 185 + }; 180 186 }; 181 187 }; 182 188 }; ··· 282 288 then "true" 283 289 else "false" 284 290 }" 291 + "KNOT_SERVER_MAX_RESPONSE_KB=${toString cfg.server.maxResponseKB}" 285 292 ]; 286 293 ExecStart = "${cfg.package}/bin/knot server"; 287 294 Restart = "always";
+5
xrpc/errors/errors.go
··· 66 66 WithMessage("failed to access ref"), 67 67 ) 68 68 69 + var RequestTooLargeError = NewXrpcError( 70 + WithTag("RequestTooLarge"), 71 + WithMessage("request was too large"), 72 + ) 73 + 69 74 var AuthError = func(err error) XrpcError { 70 75 return NewXrpcError( 71 76 WithTag("Auth"),