MCPcopy Create free account
hub / github.com/diillson/chatcli / ExecuteWithStream

Method ExecuteWithStream

cli/plugins/builtin_webfetch.go:111–212  ·  view source on GitHub ↗
(ctx context.Context, args []string, onOutput func(string))

Source from the content-addressed store, hash-verified

109}
110
111func (p *BuiltinWebFetchPlugin) ExecuteWithStream(ctx context.Context, args []string, onOutput func(string)) (string, error) {
112 if len(args) == 0 {
113 return "", fmt.Errorf("url required. Usage: @webfetch fetch --url <URL>")
114 }
115
116 parsed, err := parseFetchArgs(args)
117 if err != nil {
118 return "", err
119 }
120 if parsed.URL == "" {
121 return "", fmt.Errorf("url required")
122 }
123 if parsed.MaxLength <= 0 {
124 parsed.MaxLength = defaultWebFetchMaxLength
125 }
126
127 if onOutput != nil {
128 onOutput(fmt.Sprintf("Fetching %s...", parsed.URL))
129 }
130
131 // Create HTTP request with timeout
132 reqCtx, cancel := context.WithTimeout(ctx, 30*time.Second)
133 defer cancel()
134
135 // SSRF guard: validate (and canonicalize) the target before it reaches the
136 // HTTP client. Blocks cloud-metadata/link-local always; private/loopback
137 // only when CHATCLI_WEBFETCH_BLOCK_PRIVATE is set.
138 safeURL, err := validateWebTarget(parsed.URL)
139 if err != nil {
140 return "", fmt.Errorf("refusing to fetch %q: %w", parsed.URL, err)
141 }
142
143 // webGet sends a browser UA by default (avoids CDN bot-blocks) and
144 // auto-retries with a neutral tool UA on a 401/407 gateway challenge —
145 // the behavior of TLS-intercepting corporate proxies toward "browsers".
146 resp, err := webGet(reqCtx, safeURL, map[string]string{
147 "Accept": "text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8",
148 "Accept-Language": "en-US,en;q=0.9",
149 })
150 if err != nil {
151 return "", fmt.Errorf("fetching URL: %w", err)
152 }
153 defer resp.Body.Close()
154
155 if resp.StatusCode != 200 {
156 return "", fmt.Errorf("HTTP %d: %s", resp.StatusCode, resp.Status)
157 }
158
159 // Read body with hard cap of 10MB to avoid memory blowup.
160 body, err := io.ReadAll(io.LimitReader(resp.Body, 10*1024*1024))
161 if err != nil {
162 return "", fmt.Errorf("reading body: %w", err)
163 }
164
165 fullContent := string(body)
166 if !parsed.Raw {
167 fullContent = p.extractWithRenderEscalation(reqCtx, parsed, safeURL, fullContent, onOutput)
168 }

Callers 1

ExecuteMethod · 0.95

Calls 10

parseFetchArgsFunction · 0.85
validateWebTargetFunction · 0.85
webGetFunction · 0.85
applyLineFiltersFunction · 0.85
saveFetchToScratchFunction · 0.85
buildFetchOutputFunction · 0.85
ErrorfMethod · 0.80
CloseMethod · 0.65

Tested by

no test coverage detected