mirror of
https://github.com/anotherhadi/spilltea.git
synced 2026-07-06 12:42:32 +02:00
@@ -86,7 +86,7 @@ func Load(path string) error {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func WriteDefaultConfig(path string) error {
|
func WriteDefaultConfig(path string) error {
|
||||||
if err := os.MkdirAll(filepath.Dir(path), 0o755); err != nil {
|
if err := os.MkdirAll(filepath.Dir(path), 0o750); err != nil {
|
||||||
return fmt.Errorf("create config dir: %w", err)
|
return fmt.Errorf("create config dir: %w", err)
|
||||||
}
|
}
|
||||||
if err := os.WriteFile(path, defaultConfig, 0o600); err != nil {
|
if err := os.WriteFile(path, defaultConfig, 0o600); err != nil {
|
||||||
|
|||||||
+16
-5
@@ -2,6 +2,7 @@ package db
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"database/sql"
|
"database/sql"
|
||||||
|
"log"
|
||||||
"sync"
|
"sync"
|
||||||
|
|
||||||
_ "modernc.org/sqlite"
|
_ "modernc.org/sqlite"
|
||||||
@@ -25,17 +26,25 @@ func Open(path string) (*DB, error) {
|
|||||||
conn.SetMaxOpenConns(1)
|
conn.SetMaxOpenConns(1)
|
||||||
d := &DB{conn: conn, path: path}
|
d := &DB{conn: conn, path: path}
|
||||||
if err := d.migrate(); err != nil {
|
if err := d.migrate(); err != nil {
|
||||||
conn.Close()
|
if cerr := conn.Close(); cerr != nil {
|
||||||
|
log.Printf("db: close conn: %v", cerr)
|
||||||
|
}
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
roConn, err := sql.Open("sqlite", path)
|
roConn, err := sql.Open("sqlite", path)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
conn.Close()
|
if cerr := conn.Close(); cerr != nil {
|
||||||
|
log.Printf("db: close conn: %v", cerr)
|
||||||
|
}
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
if _, err := roConn.Exec("PRAGMA query_only=ON"); err != nil {
|
if _, err := roConn.Exec("PRAGMA query_only=ON"); err != nil {
|
||||||
conn.Close()
|
if cerr := conn.Close(); cerr != nil {
|
||||||
roConn.Close()
|
log.Printf("db: close conn: %v", cerr)
|
||||||
|
}
|
||||||
|
if cerr := roConn.Close(); cerr != nil {
|
||||||
|
log.Printf("db: close roConn: %v", cerr)
|
||||||
|
}
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
d.roConn = roConn
|
d.roConn = roConn
|
||||||
@@ -114,6 +123,8 @@ func CountEntriesAt(path string) int {
|
|||||||
}
|
}
|
||||||
defer conn.Close()
|
defer conn.Close()
|
||||||
var n int
|
var n int
|
||||||
conn.QueryRow(`SELECT COUNT(*) FROM entries`).Scan(&n)
|
if err := conn.QueryRow(`SELECT COUNT(*) FROM entries`).Scan(&n); err != nil {
|
||||||
|
log.Printf("db: count entries: %v", err)
|
||||||
|
}
|
||||||
return n
|
return n
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -121,7 +121,7 @@ func (d *DB) SearchEntries(term string) ([]Entry, error) {
|
|||||||
// Uses the persistent read-only connection (PRAGMA query_only=ON) so that any
|
// Uses the persistent read-only connection (PRAGMA query_only=ON) so that any
|
||||||
// DML or DDL in the user-supplied expression is rejected by SQLite before it executes.
|
// DML or DDL in the user-supplied expression is rejected by SQLite before it executes.
|
||||||
func (d *DB) QueryEntries(where string) ([]Entry, error) {
|
func (d *DB) QueryEntries(where string) ([]Entry, error) {
|
||||||
q := "SELECT id, timestamp, method, host, path, status_code, request_raw, response_raw, flagged FROM entries WHERE " + strings.TrimSpace(where)
|
q := "SELECT id, timestamp, method, host, path, status_code, request_raw, response_raw, flagged FROM entries WHERE " + strings.TrimSpace(where) // #nosec G202 -- intentional: user-supplied WHERE clause on a PRAGMA query_only=ON connection
|
||||||
rows, err := d.roConn.Query(q)
|
rows, err := d.roConn.Query(q)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
|
|||||||
@@ -281,7 +281,7 @@ func registerUtilities(L *lua.LState, mgr *Manager, p *Plugin) {
|
|||||||
ctx, cancel := context.WithTimeout(context.Background(), 30*time.Second)
|
ctx, cancel := context.WithTimeout(context.Background(), 30*time.Second)
|
||||||
defer cancel()
|
defer cancel()
|
||||||
|
|
||||||
c := exec.CommandContext(ctx, "sh", "-c", cmd)
|
c := exec.CommandContext(ctx, "sh", "-c", cmd) // #nosec G204 -- intentional: shell_pipe is a Lua plugin API for user-written scripts
|
||||||
c.Stdin = strings.NewReader(input)
|
c.Stdin = strings.NewReader(input)
|
||||||
|
|
||||||
var stdout, stderr bytes.Buffer
|
var stdout, stderr bytes.Buffer
|
||||||
|
|||||||
@@ -27,7 +27,7 @@ func OpenPluginsFile(dbPath string) (*PluginsFile, error) {
|
|||||||
path: path,
|
path: path,
|
||||||
data: pluginsFileData{Plugins: make(map[string]pluginFileEntry)},
|
data: pluginsFileData{Plugins: make(map[string]pluginFileEntry)},
|
||||||
}
|
}
|
||||||
raw, err := os.ReadFile(path)
|
raw, err := os.ReadFile(path) // #nosec G304 -- path is filepath.Join(dbDir, "plugins.yaml"), hardcoded filename
|
||||||
if os.IsNotExist(err) {
|
if os.IsNotExist(err) {
|
||||||
return pf, nil
|
return pf, nil
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -178,7 +178,7 @@ func (m Model) Update(msg tea.Msg) (tea.Model, tea.Cmd) {
|
|||||||
editor = "vi"
|
editor = "vi"
|
||||||
}
|
}
|
||||||
logPath := filepath.Join(filepath.Dir(m.projectPath), "logs.log")
|
logPath := filepath.Join(filepath.Dir(m.projectPath), "logs.log")
|
||||||
return m, tea.ExecProcess(exec.Command(editor, logPath), nil)
|
return m, tea.ExecProcess(exec.Command(editor, logPath), nil) // #nosec G204 G702 -- editor from trusted $EDITOR env var, logPath is a fixed path
|
||||||
}
|
}
|
||||||
|
|
||||||
if !m.activeIsEditing() {
|
if !m.activeIsEditing() {
|
||||||
|
|||||||
@@ -2,6 +2,7 @@ package history
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"fmt"
|
"fmt"
|
||||||
|
"log"
|
||||||
"net/http"
|
"net/http"
|
||||||
|
|
||||||
"charm.land/bubbles/v2/key"
|
"charm.land/bubbles/v2/key"
|
||||||
@@ -234,7 +235,9 @@ func (m Model) Update(msg tea.Msg) (tea.Model, tea.Cmd) {
|
|||||||
|
|
||||||
case key.Matches(msg, h.Flag):
|
case key.Matches(msg, h.Flag):
|
||||||
if len(m.entries) > 0 && m.database != nil {
|
if len(m.entries) > 0 && m.database != nil {
|
||||||
m.database.ToggleFlag(m.entries[m.cursor].ID)
|
if err := m.database.ToggleFlag(m.entries[m.cursor].ID); err != nil {
|
||||||
|
log.Printf("history: toggle flag: %v", err)
|
||||||
|
}
|
||||||
return m, m.RefreshCmd()
|
return m, m.RefreshCmd()
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -242,7 +245,9 @@ func (m Model) Update(msg tea.Msg) (tea.Model, tea.Cmd) {
|
|||||||
if len(m.entries) > 0 {
|
if len(m.entries) > 0 {
|
||||||
id := m.entries[m.cursor].ID
|
id := m.entries[m.cursor].ID
|
||||||
if m.database != nil {
|
if m.database != nil {
|
||||||
m.database.DeleteEntry(id)
|
if err := m.database.DeleteEntry(id); err != nil {
|
||||||
|
log.Printf("history: delete entry: %v", err)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
return m, LoadEntriesCmd(m.database)
|
return m, LoadEntriesCmd(m.database)
|
||||||
}
|
}
|
||||||
@@ -261,10 +266,14 @@ func (m Model) Update(msg tea.Msg) (tea.Model, tea.Cmd) {
|
|||||||
if hasUnflagged && e.Flagged {
|
if hasUnflagged && e.Flagged {
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
m.database.DeleteEntry(e.ID)
|
if err := m.database.DeleteEntry(e.ID); err != nil {
|
||||||
|
log.Printf("history: delete entry: %v", err)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
m.database.DeleteAllExceptFlagged()
|
if err := m.database.DeleteAllExceptFlagged(); err != nil {
|
||||||
|
log.Printf("history: delete all unflagged: %v", err)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return m, m.clearSearch()
|
return m, m.clearSearch()
|
||||||
|
|||||||
@@ -3,6 +3,7 @@ package home
|
|||||||
import (
|
import (
|
||||||
crypto "crypto/rand"
|
crypto "crypto/rand"
|
||||||
"fmt"
|
"fmt"
|
||||||
|
"log"
|
||||||
"os"
|
"os"
|
||||||
"path/filepath"
|
"path/filepath"
|
||||||
"strings"
|
"strings"
|
||||||
@@ -72,7 +73,7 @@ func (m Model) handleSelection() (tea.Model, tea.Cmd) {
|
|||||||
return m, m.nameInput.Focus()
|
return m, m.nameInput.Focus()
|
||||||
case kindTemp:
|
case kindTemp:
|
||||||
dir := tempDir()
|
dir := tempDir()
|
||||||
if err := os.MkdirAll(dir, 0o755); err != nil {
|
if err := os.MkdirAll(dir, 0o750); err != nil {
|
||||||
return m, nil
|
return m, nil
|
||||||
}
|
}
|
||||||
initProjectFiles(dir)
|
initProjectFiles(dir)
|
||||||
@@ -90,7 +91,9 @@ func (m Model) deleteSelected() (tea.Model, tea.Cmd) {
|
|||||||
return m, nil
|
return m, nil
|
||||||
}
|
}
|
||||||
dir := filepath.Dir(item.path) // parent dir of data.db
|
dir := filepath.Dir(item.path) // parent dir of data.db
|
||||||
os.RemoveAll(dir)
|
if err := os.RemoveAll(dir); err != nil {
|
||||||
|
log.Printf("home: remove project dir: %v", err)
|
||||||
|
}
|
||||||
idx := m.list.GlobalIndex()
|
idx := m.list.GlobalIndex()
|
||||||
m.list.RemoveItem(idx)
|
m.list.RemoveItem(idx)
|
||||||
if idx > 0 && idx >= len(m.list.Items()) {
|
if idx > 0 && idx >= len(m.list.Items()) {
|
||||||
@@ -113,7 +116,7 @@ func (m Model) updateNaming(msg tea.KeyPressMsg) (tea.Model, tea.Cmd) {
|
|||||||
m.mode = modeSelect
|
m.mode = modeSelect
|
||||||
m.nameInput.Blur()
|
m.nameInput.Blur()
|
||||||
dir := filepath.Join(m.projectDir, name)
|
dir := filepath.Join(m.projectDir, name)
|
||||||
if err := os.MkdirAll(dir, 0o755); err != nil {
|
if err := os.MkdirAll(dir, 0o750); err != nil {
|
||||||
return m, nil
|
return m, nil
|
||||||
}
|
}
|
||||||
initProjectFiles(dir)
|
initProjectFiles(dir)
|
||||||
@@ -147,14 +150,14 @@ func IsValidProjectName(s string) bool {
|
|||||||
func OpenProject(projectDir, name string) (*Project, error) {
|
func OpenProject(projectDir, name string) (*Project, error) {
|
||||||
if name == "tmp" || name == "temp" || name == "temporary" {
|
if name == "tmp" || name == "temp" || name == "temporary" {
|
||||||
dir := tempDir()
|
dir := tempDir()
|
||||||
if err := os.MkdirAll(dir, 0o755); err != nil {
|
if err := os.MkdirAll(dir, 0o750); err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
initProjectFiles(dir)
|
initProjectFiles(dir)
|
||||||
return &Project{Name: "temporary", Path: filepath.Join(dir, "data.db")}, nil
|
return &Project{Name: "temporary", Path: filepath.Join(dir, "data.db")}, nil
|
||||||
}
|
}
|
||||||
dir := filepath.Join(projectDir, name)
|
dir := filepath.Join(projectDir, name)
|
||||||
if err := os.MkdirAll(dir, 0o755); err != nil {
|
if err := os.MkdirAll(dir, 0o750); err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
initProjectFiles(dir)
|
initProjectFiles(dir)
|
||||||
@@ -171,9 +174,11 @@ func initProjectFiles(dir string) {
|
|||||||
for _, name := range []string{"data.db", "logs.log"} {
|
for _, name := range []string{"data.db", "logs.log"} {
|
||||||
p := filepath.Join(dir, name)
|
p := filepath.Join(dir, name)
|
||||||
if _, err := os.Stat(p); os.IsNotExist(err) {
|
if _, err := os.Stat(p); os.IsNotExist(err) {
|
||||||
f, err := os.Create(p)
|
f, err := os.Create(p) // #nosec G304 -- p is filepath.Join(dir, hardcoded_name), no traversal possible
|
||||||
if err == nil {
|
if err == nil {
|
||||||
f.Close()
|
if cerr := f.Close(); cerr != nil {
|
||||||
|
log.Printf("home: close project file: %v", cerr)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -7,6 +7,7 @@ import (
|
|||||||
"crypto/tls"
|
"crypto/tls"
|
||||||
"fmt"
|
"fmt"
|
||||||
"io"
|
"io"
|
||||||
|
"log"
|
||||||
"net/http"
|
"net/http"
|
||||||
"strings"
|
"strings"
|
||||||
"time"
|
"time"
|
||||||
@@ -79,7 +80,9 @@ func (m Model) Update(msg tea.Msg) (tea.Model, tea.Cmd) {
|
|||||||
e.ResponseRaw = "Error: " + msg.err.Error()
|
e.ResponseRaw = "Error: " + msg.err.Error()
|
||||||
}
|
}
|
||||||
if m.database != nil && e.DBID != 0 {
|
if m.database != nil && e.DBID != 0 {
|
||||||
m.database.UpdateReplayEntry(entryToDB(*e))
|
if err := m.database.UpdateReplayEntry(entryToDB(*e)); err != nil {
|
||||||
|
log.Printf("replay: update entry: %v", err)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
m.refreshListViewport()
|
m.refreshListViewport()
|
||||||
@@ -216,7 +219,9 @@ func (m Model) updateNormalMode(msg tea.KeyPressMsg) (tea.Model, tea.Cmd) {
|
|||||||
if len(m.entries) > 0 {
|
if len(m.entries) > 0 {
|
||||||
e := m.entries[m.cursor]
|
e := m.entries[m.cursor]
|
||||||
if m.database != nil && e.DBID != 0 {
|
if m.database != nil && e.DBID != 0 {
|
||||||
m.database.DeleteReplayEntry(e.DBID)
|
if err := m.database.DeleteReplayEntry(e.DBID); err != nil {
|
||||||
|
log.Printf("replay: delete entry: %v", err)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
m.entries = append(m.entries[:m.cursor], m.entries[m.cursor+1:]...)
|
m.entries = append(m.entries[:m.cursor], m.entries[m.cursor+1:]...)
|
||||||
if m.cursor >= len(m.entries) && m.cursor > 0 {
|
if m.cursor >= len(m.entries) && m.cursor > 0 {
|
||||||
@@ -229,7 +234,9 @@ func (m Model) updateNormalMode(msg tea.KeyPressMsg) (tea.Model, tea.Cmd) {
|
|||||||
|
|
||||||
case key.Matches(msg, r.DeleteAll):
|
case key.Matches(msg, r.DeleteAll):
|
||||||
if m.database != nil {
|
if m.database != nil {
|
||||||
m.database.DeleteAllReplayEntries()
|
if err := m.database.DeleteAllReplayEntries(); err != nil {
|
||||||
|
log.Printf("replay: delete all entries: %v", err)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
m.entries = nil
|
m.entries = nil
|
||||||
m.cursor = 0
|
m.cursor = 0
|
||||||
@@ -399,7 +406,7 @@ func doSend(entry Entry) (responseRaw string, statusCode int, err error) {
|
|||||||
client := &http.Client{
|
client := &http.Client{
|
||||||
Timeout: 30 * time.Second,
|
Timeout: 30 * time.Second,
|
||||||
Transport: &http.Transport{
|
Transport: &http.Transport{
|
||||||
TLSClientConfig: &tls.Config{InsecureSkipVerify: true}, //nolint:gosec
|
TLSClientConfig: &tls.Config{InsecureSkipVerify: true}, // #nosec G402 -- intentional for replay feature
|
||||||
},
|
},
|
||||||
CheckRedirect: func(_ *http.Request, _ []*http.Request) error {
|
CheckRedirect: func(_ *http.Request, _ []*http.Request) error {
|
||||||
return http.ErrUseLastResponse
|
return http.ErrUseLastResponse
|
||||||
|
|||||||
@@ -35,8 +35,10 @@ func openWithEditor(content string, callback func(string, error) tea.Msg) tea.Cm
|
|||||||
if _, err := f.WriteString(content); err != nil {
|
if _, err := f.WriteString(content); err != nil {
|
||||||
log.Printf("editor: writing temp file: %v", err)
|
log.Printf("editor: writing temp file: %v", err)
|
||||||
}
|
}
|
||||||
f.Close()
|
if err := f.Close(); err != nil {
|
||||||
return tea.ExecProcess(exec.Command(resolveEditor(), tmpPath), func(err error) tea.Msg {
|
log.Printf("editor: closing temp file: %v", err)
|
||||||
|
}
|
||||||
|
return tea.ExecProcess(exec.Command(resolveEditor(), tmpPath), func(err error) tea.Msg { // #nosec G204 -- editor from trusted config/$EDITOR, tmpPath from os.CreateTemp
|
||||||
defer os.Remove(tmpPath)
|
defer os.Remove(tmpPath)
|
||||||
return callback(tmpPath, err)
|
return callback(tmpPath, err)
|
||||||
})
|
})
|
||||||
@@ -47,7 +49,7 @@ func OpenExternalEditor(content string) tea.Cmd {
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
return EditorFinishedMsg{Err: err}
|
return EditorFinishedMsg{Err: err}
|
||||||
}
|
}
|
||||||
data, readErr := os.ReadFile(tmpPath)
|
data, readErr := os.ReadFile(tmpPath) // #nosec G304 -- tmpPath is from os.CreateTemp, controlled by this process
|
||||||
if readErr != nil {
|
if readErr != nil {
|
||||||
return EditorFinishedMsg{Err: readErr}
|
return EditorFinishedMsg{Err: readErr}
|
||||||
}
|
}
|
||||||
|
|||||||
+2
-2
@@ -17,7 +17,7 @@ var PluginsFS embed.FS
|
|||||||
// InstallDefaultPlugins copies embedded default plugins into dir, skipping
|
// InstallDefaultPlugins copies embedded default plugins into dir, skipping
|
||||||
// files that already exist. Returns the number of files written.
|
// files that already exist. Returns the number of files written.
|
||||||
func InstallDefaultPlugins(dir string) (int, error) {
|
func InstallDefaultPlugins(dir string) (int, error) {
|
||||||
if err := os.MkdirAll(dir, 0o755); err != nil {
|
if err := os.MkdirAll(dir, 0o750); err != nil {
|
||||||
return 0, fmt.Errorf("create plugins dir: %w", err)
|
return 0, fmt.Errorf("create plugins dir: %w", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -39,7 +39,7 @@ func InstallDefaultPlugins(dir string) (int, error) {
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
return written, fmt.Errorf("read embedded %s: %w", e.Name(), err)
|
return written, fmt.Errorf("read embedded %s: %w", e.Name(), err)
|
||||||
}
|
}
|
||||||
if err := os.WriteFile(dst, data, 0o644); err != nil {
|
if err := os.WriteFile(dst, data, 0o600); err != nil {
|
||||||
return written, fmt.Errorf("write %s: %w", dst, err)
|
return written, fmt.Errorf("write %s: %w", dst, err)
|
||||||
}
|
}
|
||||||
written++
|
written++
|
||||||
|
|||||||
Reference in New Issue
Block a user