nandi/oripublic Fork 0
b6cd929ab1425a4ef6073ea9c189917cca02cf3c
Commits
Clone
git clone https://git.rickub.com/nandi/ori.git
git clone ssh://git@rickub.com/nandi/ori.git

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

forked from bots-garden/ori

raw.go · 94 lines · 2.8 KBGo Blame HistoryRaw
✨ Workspace panel, selectors, previews, desktop app, sandbox template, resizable file tree, light/dark theme 76d62ac k33g yesterday1package files
2
3import (
4 "mime"
5 "net/http"
6 "os"
7 "path/filepath"
8 "strings"
9)
10
11// imageTypes pins the Content-Type of the image formats the preview pane
12// renders, independently of the host's mime database (which may lack
13// entries such as .webp or .avif, or spell .ico differently).
14var imageTypes = map[string]string{
15 ".avif": "image/avif",
16 ".bmp": "image/bmp",
17 ".gif": "image/gif",
18 ".ico": "image/x-icon",
19 ".jpeg": "image/jpeg",
20 ".jpg": "image/jpeg",
21 ".png": "image/png",
22 ".svg": "image/svg+xml",
23 ".webp": "image/webp",
24}
25
✨ Switch the ACP adapter to @agentclientprotocol/claude-agent-acp (template 0.0.2) 5d476ff k33g 21h ago26// textTypes pins the text formats the preview pane renders itself, for the
27// same reason as imageTypes: Go seeds its mime table from the host (macOS's
28// /etc/apache2/mime.types leaves text/markdown commented out), so without a
29// pin a .md file is served as text/plain on one machine and text/markdown on
30// another.
31var textTypes = map[string]string{
32 ".markdown": "text/markdown; charset=utf-8",
33 ".md": "text/markdown; charset=utf-8",
34}
35
✨ Workspace panel, selectors, previews, desktop app, sandbox template, resizable file tree, light/dark theme 76d62ac k33g yesterday36// ContentTypeFor returns the Content-Type the raw endpoint serves a file
✨ Switch the ACP adapter to @agentclientprotocol/claude-agent-acp (template 0.0.2) 5d476ff k33g 21h ago37// under: pinned image and text types first, then the host mime database, and
38// a content sniff of the first bytes as a last resort.
✨ Workspace panel, selectors, previews, desktop app, sandbox template, resizable file tree, light/dark theme 76d62ac k33g yesterday39//
40// Example:
41//
42// files.ContentTypeFor("logo.webp", nil) // "image/webp"
✨ Switch the ACP adapter to @agentclientprotocol/claude-agent-acp (template 0.0.2) 5d476ff k33g 21h ago43// files.ContentTypeFor("README.md", nil) // "text/markdown; charset=utf-8"
✨ Workspace panel, selectors, previews, desktop app, sandbox template, resizable file tree, light/dark theme 76d62ac k33g yesterday44func ContentTypeFor(path string, head []byte) string {
45 ext := strings.ToLower(filepath.Ext(path))
46 if typ, ok := imageTypes[ext]; ok {
47 return typ
48 }
✨ Switch the ACP adapter to @agentclientprotocol/claude-agent-acp (template 0.0.2) 5d476ff k33g 21h ago49 if typ, ok := textTypes[ext]; ok {
50 return typ
51 }
✨ Workspace panel, selectors, previews, desktop app, sandbox template, resizable file tree, light/dark theme 76d62ac k33g yesterday52 if typ := mime.TypeByExtension(ext); typ != "" {
53 return typ
54 }
55 return http.DetectContentType(head)
56}
57
58// rawFile streams a file's bytes as-is, for the browser to render (images)
59// or download. Unlike readFile it has no size cap and no text requirement:
60// http.ServeContent streams and honours Range requests.
61func (s *Service) rawFile(w http.ResponseWriter, r *http.Request) {
62 if r.URL.Query().Get("path") == "" {
63 writeError(w, http.StatusBadRequest, "path query parameter is required")
64 return
65 }
66 path := s.resolve(r.URL.Query().Get("path"))
67 info, err := os.Stat(path)
68 if err != nil {
69 writeFSError(w, err)
70 return
71 }
72 if info.IsDir() {
73 writeError(w, http.StatusBadRequest, "path is a directory")
74 return
75 }
76 file, err := os.Open(path)
77 if err != nil {
78 writeFSError(w, err)
79 return
80 }
81 // Read-only handle: a Close error carries no information the client
82 // could act on, and the response headers are already committed.
83 defer func() { _ = file.Close() }()
84
85 head := make([]byte, 512)
86 n, _ := file.Read(head)
87 if _, err := file.Seek(0, 0); err != nil {
88 writeFSError(w, err)
89 return
90 }
91 w.Header().Set("Content-Type", ContentTypeFor(path, head[:n]))
92 w.Header().Set("X-Content-Type-Options", "nosniff")
93 http.ServeContent(w, r, info.Name(), info.ModTime(), file)
94}