This is a fork of: https://github.com/roglew/puppy
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

570 lines
13 KiB

package puppy
import (
"database/sql"
"encoding/json"
"fmt"
"log"
"runtime"
"sort"
"strings"
)
type schemaUpdater func(tx *sql.Tx) error
type tableNameRow struct {
name string
}
var schemaUpdaters = []schemaUpdater{
schema8,
schema9,
schema10,
}
func UpdateSchema(db *sql.DB, logger *log.Logger) error {
currSchemaVersion := 0
var tableName string
if err := db.QueryRow("SELECT name FROM sqlite_master WHERE type='table' AND name='schema_meta';").Scan(&tableName); err == sql.ErrNoRows {
logger.Println("No datafile schema, initializing schema")
currSchemaVersion = -1
} else if err != nil {
return err
} else {
svr := new(int)
if err := db.QueryRow("SELECT version FROM schema_meta;").Scan(svr); err != nil {
return err
}
currSchemaVersion = *svr
if currSchemaVersion-7 < len(schemaUpdaters) {
logger.Println("Schema out of date. Updating...")
}
}
if currSchemaVersion >= 0 && currSchemaVersion < 8 {
return fmt.Errorf("This is a PappyProxy datafile that is not the most recent schema version supported by PappyProxy. Load this datafile using the most recent version of Pappy to upgrade the schema and try importing it again.")
}
var updaterInd = 0
if currSchemaVersion > 0 {
updaterInd = currSchemaVersion - 7
}
if currSchemaVersion-7 < len(schemaUpdaters) {
tx, err := db.Begin()
if err != nil {
return err
}
for i := updaterInd; i < len(schemaUpdaters); i++ {
logger.Printf("Updating schema to version %d...", i+8)
err := schemaUpdaters[i](tx)
if err != nil {
logger.Println("Error updating schema:", err)
logger.Println("Rolling back")
tx.Rollback()
return err
}
}
logger.Printf("Schema update successful")
tx.Commit()
}
return nil
}
func execute(tx *sql.Tx, cmd string) error {
err := executeNoDebug(tx, cmd)
if err != nil {
_, f, ln, _ := runtime.Caller(1)
return fmt.Errorf("sql error at %s:%d: %s", f, ln, err.Error())
}
return nil
}
func executeNoDebug(tx *sql.Tx, cmd string) error {
stmt, err := tx.Prepare(cmd)
defer stmt.Close()
if err != nil {
return err
}
if _, err := tx.Stmt(stmt).Exec(); err != nil {
return err
}
return nil
}
func executeMultiple(tx *sql.Tx, cmds []string) error {
for _, cmd := range cmds {
err := executeNoDebug(tx, cmd)
if err != nil {
_, f, ln, _ := runtime.Caller(1)
return fmt.Errorf("sql error at %s:%d: %s", f, ln, err.Error())
}
}
return nil
}
/*
SCHEMA 8 / INITIAL
*/
func schema8(tx *sql.Tx) error {
// Create a schema that is the same as pappy's last version
cmds := []string{
`
CREATE TABLE schema_meta (
version INTEGER NOT NULL
);
`,
`
INSERT INTO "schema_meta" VALUES(8);
`,
`
CREATE TABLE responses (
id INTEGER PRIMARY KEY AUTOINCREMENT,
full_response BLOB NOT NULL,
unmangled_id INTEGER REFERENCES responses(id)
);
`,
`
CREATE TABLE scope (
filter_order INTEGER NOT NULL,
filter_string TEXT NOT NULL
);
`,
`
CREATE TABLE tags (
id INTEGER PRIMARY KEY AUTOINCREMENT,
tag TEXT NOT NULL
);
`,
`
CREATE TABLE tagged (
reqid INTEGER,
tagid INTEGER
);
`,
`
CREATE TABLE "requests" (
id INTEGER PRIMARY KEY AUTOINCREMENT,
full_request BLOB NOT NULL,
submitted INTEGER NOT NULL,
response_id INTEGER REFERENCES responses(id),
unmangled_id INTEGER REFERENCES requests(id),
port INTEGER,
is_ssl INTEGER,
host TEXT,
plugin_data TEXT,
start_datetime REAL,
end_datetime REAL
);
`,
`
CREATE TABLE saved_contexts (
id INTEGER PRIMARY KEY AUTOINCREMENT,
context_name TEXT UNIQUE,
filter_strings TEXT
);
`,
`
CREATE TABLE websocket_messages (
id INTEGER PRIMARY KEY AUTOINCREMENT,
parent_request INTEGER REFERENCES requests(id),
unmangled_id INTEGER REFERENCES websocket_messages(id),
is_binary INTEGER,
direction INTEGER,
time_sent REAL,
contents BLOB
);
`,
`
CREATE INDEX ind_start_time ON requests(start_datetime);
`,
}
err := executeMultiple(tx, cmds)
if err != nil {
return err
}
return nil
}
/*
SCHEMA 9
*/
func pappyFilterToStrArgList(f string) ([]string, error) {
parts := strings.Split(f, " ")
// Validate the arguments
goArgs, err := CheckArgsStrToGo(parts)
if err != nil {
return nil, fmt.Errorf("error converting filter string \"%s\": %s", f, err)
}
strArgs, err := CheckArgsGoToStr(goArgs)
if err != nil {
return nil, fmt.Errorf("error converting filter string \"%s\": %s", f, err)
}
return strArgs, nil
}
func pappyListToStrMessageQuery(f []string) (StrMessageQuery, error) {
retFilter := make(StrMessageQuery, len(f))
for i, s := range f {
strArgs, err := pappyFilterToStrArgList(s)
if err != nil {
return nil, err
}
newPhrase := make(StrQueryPhrase, 1)
newPhrase[0] = strArgs
retFilter[i] = newPhrase
}
return retFilter, nil
}
type s9ScopeStr struct {
Order int64
Filter string
}
type s9ScopeSort []*s9ScopeStr
func (ls s9ScopeSort) Len() int {
return len(ls)
}
func (ls s9ScopeSort) Swap(i int, j int) {
ls[i], ls[j] = ls[j], ls[i]
}
func (ls s9ScopeSort) Less(i int, j int) bool {
return ls[i].Order < ls[j].Order
}
func schema9(tx *sql.Tx) error {
/*
Converts the floating point timestamps into integers representing nanoseconds from jan 1 1970
*/
// Rename the old requests table
if err := execute(tx, "ALTER TABLE requests RENAME TO requests_old"); err != nil {
return err
}
if err := execute(tx, "ALTER TABLE websocket_messages RENAME TO websocket_messages_old"); err != nil {
return err
}
// Create new requests table with integer datetime
cmds := []string{`
CREATE TABLE "requests" (
id INTEGER PRIMARY KEY AUTOINCREMENT,
full_request BLOB NOT NULL,
submitted INTEGER NOT NULL,
response_id INTEGER REFERENCES responses(id),
unmangled_id INTEGER REFERENCES requests(id),
port INTEGER,
is_ssl INTEGER,
host TEXT,
plugin_data TEXT,
start_datetime INTEGER,
end_datetime INTEGER
);
`,
`
INSERT INTO requests
SELECT id, full_request, submitted, response_id, unmangled_id, port, is_ssl, host, plugin_data, 0, 0
FROM requests_old
`,
`
CREATE TABLE websocket_messages (
id INTEGER PRIMARY KEY AUTOINCREMENT,
parent_request INTEGER REFERENCES requests(id),
unmangled_id INTEGER REFERENCES websocket_messages(id),
is_binary INTEGER,
direction INTEGER,
time_sent INTEGER,
contents BLOB
);
`,
`
INSERT INTO websocket_messages
SELECT id, parent_request, unmangled_id, is_binary, direction, 0, contents
FROM websocket_messages_old
`,
}
if err := executeMultiple(tx, cmds); err != nil {
return err
}
// Update time values to use unix time nanoseconds
rows, err := tx.Query("SELECT id, start_datetime, end_datetime FROM requests_old;")
if err != nil {
return err
}
defer rows.Close()
var reqid int64
var startDT sql.NullFloat64
var endDT sql.NullFloat64
var newStartDT int64
var newEndDT int64
for rows.Next() {
if err := rows.Scan(&reqid, &startDT, &endDT); err != nil {
return err
}
if startDT.Valid {
// Convert to nanoseconds
newStartDT = int64(startDT.Float64 * 1000000000)
} else {
newStartDT = 0
}
if endDT.Valid {
newEndDT = int64(endDT.Float64 * 1000000000)
} else {
newEndDT = 0
}
// Save the new value
stmt, err := tx.Prepare("UPDATE requests SET start_datetime=?, end_datetime=? WHERE id=?")
if err != nil {
return err
}
defer stmt.Close()
if _, err := tx.Stmt(stmt).Exec(newStartDT, newEndDT, reqid); err != nil {
return err
}
}
// Update websocket time values to use unix time nanoseconds
rows, err = tx.Query("SELECT id, time_sent FROM websocket_messages_old;")
if err != nil {
return err
}
defer rows.Close()
var wsid int64
var sentDT sql.NullFloat64
var newSentDT int64
for rows.Next() {
if err := rows.Scan(&wsid, &sentDT); err != nil {
return err
}
if sentDT.Valid {
// Convert to nanoseconds
newSentDT = int64(startDT.Float64 * 1000000000)
} else {
newSentDT = 0
}
// Save the new value
stmt, err := tx.Prepare("UPDATE websocket_messages SET time_sent=? WHERE id=?")
if err != nil {
return err
}
defer stmt.Close()
if _, err := tx.Stmt(stmt).Exec(newSentDT, reqid); err != nil {
return err
}
}
err = rows.Err()
if err != nil {
return err
}
if err := execute(tx, "DROP TABLE requests_old"); err != nil {
return err
}
if err := execute(tx, "DROP TABLE websocket_messages_old"); err != nil {
return err
}
// Update saved contexts
rows, err = tx.Query("SELECT id, context_name, filter_strings FROM saved_contexts")
if err != nil {
return err
}
defer rows.Close()
var contextId int64
var contextName sql.NullString
var filterStrings sql.NullString
for rows.Next() {
if err := rows.Scan(&contextId, &contextName, &filterStrings); err != nil {
return err
}
if !contextName.Valid {
continue
}
if !filterStrings.Valid {
continue
}
if contextName.String == "__scope" {
// hopefully this doesn't break anything critical, but we want to store the scope
// as a saved context now with the name __scope
continue
}
var pappyFilters []string
err = json.Unmarshal([]byte(filterStrings.String), &pappyFilters)
if err != nil {
return err
}
newFilter, err := pappyListToStrMessageQuery(pappyFilters)
if err != nil {
// We're just ignoring filters that we can't convert :|
continue
}
newFilterStr, err := json.Marshal(newFilter)
if err != nil {
return err
}
stmt, err := tx.Prepare("UPDATE saved_contexts SET filter_strings=? WHERE id=?")
if err != nil {
return err
}
defer stmt.Close()
if _, err := tx.Stmt(stmt).Exec(newFilterStr, contextId); err != nil {
return err
}
}
err = rows.Err()
if err != nil {
return err
}
// Move scope to a saved context
rows, err = tx.Query("SELECT filter_order, filter_string FROM scope")
if err != nil {
return err
}
defer rows.Close()
var filterOrder sql.NullInt64
var filterString sql.NullString
vals := make([]*s9ScopeStr, 0)
for rows.Next() {
if err := rows.Scan(&filterOrder, &filterString); err != nil {
return err
}
if !filterOrder.Valid {
continue
}
if !filterString.Valid {
continue
}
vals = append(vals, &s9ScopeStr{filterOrder.Int64, filterString.String})
}
err = rows.Err()
if err != nil {
return err
}
// Put the scope in the right order
sort.Sort(s9ScopeSort(vals))
// Convert it into a list of filters
filterList := make([]string, len(vals))
for i, ss := range vals {
filterList[i] = ss.Filter
}
newScopeStrFilter, err := pappyListToStrMessageQuery(filterList)
if err != nil {
// We'll only convert the scope if we can, otherwise we'll drop it
err := execute(tx, `INSERT INTO saved_contexts (context_name, filter_strings) VALUES("__scope", "[]")`)
if err != nil {
return err
}
} else {
stmt, err := tx.Prepare(`INSERT INTO saved_contexts (context_name, filter_strings) VALUES("__scope", ?)`)
if err != nil {
return err
}
defer stmt.Close()
newScopeFilterStr, err := json.Marshal(newScopeStrFilter)
if err != nil {
return err
}
if _, err := tx.Stmt(stmt).Exec(newScopeFilterStr); err != nil {
return err
}
}
if err := execute(tx, "DROP TABLE scope"); err != nil {
return err
}
// Update schema number
if err := execute(tx, `UPDATE schema_meta SET version=9`); err != nil {
return err
}
return nil
}
func schema10(tx *sql.Tx) error {
/*
Create a "plugin data" table to let applications store app-specific data in the datafile
*/
cmds := []string{`
CREATE TABLE plugin_data (
id INTEGER PRIMARY KEY AUTOINCREMENT,
key TEXT UNIQUE,
value STRING
);
CREATE INDEX plugin_key_ind ON plugin_data(key);
`,
`UPDATE schema_meta SET version=10`,
}
err := executeMultiple(tx, cmds)
if err != nil {
return err
}
return nil
}