From e4bfd233ef78a40744c420d8c0e99263626e736d Mon Sep 17 00:00:00 2001 From: Ted Pier Date: Mon, 25 Aug 2025 13:46:32 -0700 Subject: [PATCH] buh --- main.go | 303 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 303 insertions(+) create mode 100644 main.go diff --git a/main.go b/main.go new file mode 100644 index 0000000..e532112 --- /dev/null +++ b/main.go @@ -0,0 +1,303 @@ +package noteslib + +import ( + "bytes" + "crypto/aes" + "crypto/cipher" + "crypto/pbkdf2" + "crypto/rand" + "crypto/sha256" + "database/sql" + "encoding/base64" + "encoding/json" + "fmt" + "io" + "net/http" + "net/url" + "strconv" + "strings" + "sync" + "time" + + _ "modernc.org/sqlite" +) + +type Note struct { + Body string `json:"body"` + Color int64 `json:"color"` + Date string `json:"date"` + ID string `json:"id"` + Title string `json:"title"` +} + +func DecodeColorInt(color int64) (r int64, g int64, b int64, a int64) { + // https://developer.android.com/reference/android/graphics/Color#color-ints + if color == 0 { + return 255, 255, 255, 255 + } + a = (color >> 24) & 0xff // or color >>> 24 + r = (color >> 16) & 0xff + g = (color >> 8) & 0xff + b = (color) & 0xff + return +} + +func EncodeToColorInt(r int64, g int64, b int64, a int64) int64 { + // https://developer.android.com/reference/android/graphics/Color#color-ints + return (a&0xff)<<24 | (r&0xff)<<16 | (g&0xff)<<8 | (b & 0xff) +} + +func Fetch(serverAddress string, username string) (jsonBody [][2]string, err error) { + response, err := http.Get( + fmt.Sprintf("https://%s/get?user=%s", + serverAddress, + url.QueryEscape(username)), + ) + if err != nil { + return + } + defer response.Body.Close() + + responseBody, err := io.ReadAll(response.Body) + if err != nil { + return + } + + err = json.Unmarshal(responseBody, &jsonBody) + if err != nil { + return + } + + return +} + +func Load(db *sql.DB) (notes [][2]string, err error) { + var rowCount int + err = db.QueryRow("SELECT COUNT(*) FROM notes").Scan(&rowCount) + if err != nil { + return + } + + const limit = 100 + if rowCount > limit { // change this if there's ever an option to increase limit + rowCount = limit // change this if there's ever an option to increase limit + } + + rows, err := db.Query("SELECT * FROM notes LIMIT ?", rowCount) + if err != nil { + return + } + defer rows.Close() + + notes = make([][2]string, rowCount) + + for i := 0; rows.Next(); i++ { + var currentRow [2]string + if err != nil { + return + } + notes[i] = currentRow + } + + return +} + +func List(password string, jsonBody [2]string, notes *[]Note, wg *sync.WaitGroup) (err error) { + defer wg.Done() + + decryptedData, err := decrypt(jsonBody[1], password) + if err != nil { + return + } + + var decryptedBody Note + err = json.Unmarshal(decryptedData, &decryptedBody) + if err != nil { + return + } + + decryptedBody.ID = jsonBody[0] + if len(decryptedBody.Title) > 0 && len(decryptedBody.Body) > 0 { + *notes = append(*notes, decryptedBody) // ew + return + } + + return +} + +func Del(serverAddress string, username string, uuid string, db *sql.DB) (err error) { + response, err := http.Get(fmt.Sprintf("https://%s/remove?user=%s&id=%s", serverAddress, url.QueryEscape(username), uuid)) + if err != nil { + return + } + defer response.Body.Close() + + _, err = io.ReadAll(response.Body) + if err != nil { + return + } + + stmt, err := db.Prepare("DELETE FROM notes WHERE id=?") + if err != nil { + return + } + defer stmt.Close() + + _, err = stmt.Exec(uuid) + return +} + +func Create(title string, body string, newColor string, serverAddress string, username string, password string, templateNote Note) (err error) { + aInt, err := strconv.ParseInt(newColor[0:2], 16, 64) + if err != nil { + return + } + + rInt, err := strconv.ParseInt(newColor[2:4], 16, 64) + if err != nil { + return + } + + gInt, err := strconv.ParseInt(newColor[4:6], 16, 64) + if err != nil { + return + } + + bInt, err := strconv.ParseInt(newColor[6:8], 16, 64) + if err != nil { + return + } + + note := Note{ + Title: title, + Body: strings.ReplaceAll(body, "/", "\n"), + Color: EncodeToColorInt(rInt, gInt, bInt, aInt), + Date: strconv.FormatInt(time.Now().UnixMilli(), 10), + } + jsonNote, err := json.Marshal(note) + if err != nil { + return + } + + data, err := encrypt(jsonNote, password) + if err != nil { + return + } + + response, err := http.Get( + fmt.Sprintf( + "https://%s/new?user=%s&data=%s", + serverAddress, url.QueryEscape(username), + url.QueryEscape(data), + ), + ) + if err != nil { + return + } + + defer response.Body.Close() + _, err = io.ReadAll(response.Body) + return +} + +func deriveKey(password string, salt []byte) (block cipher.Block, err error) { + key, err := pbkdf2.Key(sha256.New, password, salt, 65536, 32) + if err != nil { + return + } + + block, err = aes.NewCipher(key) + return +} + +func decrypt(data string, password string) (strippedPlaintext []byte, err error) { + dataBytes, err := base64.StdEncoding.DecodeString(data) + if err != nil { + return + } + + dataBytesLength := len(dataBytes) + if dataBytesLength < 32 { + err = fmt.Errorf("Data is of length %d, 32 required.", dataBytesLength) + return + } + + iv := dataBytes[0:16] + salt := dataBytes[16:32] + ciphertext := dataBytes[32:] + + ciphertextLength := len(ciphertext) + if ciphertextLength%aes.BlockSize != 0 { + err = fmt.Errorf("Ciphertext length %d is not a multiple of block size %d\n", ciphertextLength, aes.BlockSize) + return + } + + plaintext := make([]byte, ciphertextLength) + block, err := deriveKey(password, salt) + if err != nil { + return + } + + mode := cipher.NewCBCDecrypter(block, iv) + mode.CryptBlocks(plaintext, ciphertext) + + strippedPlaintext, err = unpad(plaintext, 16) + return +} + +func encrypt(data []byte, password string) (encrypted string, err error) { + salt := make([]byte, 16) + iv := make([]byte, 16) + rand.Read(salt) + rand.Read(iv) + + plaintext, err := pad(data, 16) + if err != nil { + return + } + + block, err := deriveKey(password, salt) + if err != nil { + return + } + + mode := cipher.NewCBCEncrypter(block, iv) + ciphertext := make([]byte, len(plaintext)) + mode.CryptBlocks(ciphertext, plaintext) + + encrypted = base64.StdEncoding.EncodeToString( + bytes.Join( + [][]byte{ + iv, + salt, + ciphertext, + }, + []byte(""), + ), + ) + return +} + +func pad(buf []byte, size int) ([]byte, error) { + // https://github.com/mergermarket/go-pkcs7/blob/master/pkcs7.go + bufLen := len(buf) + padLen := size - bufLen%size + padded := make([]byte, bufLen+padLen) + copy(padded, buf) + for i := range padLen { + padded[bufLen+i] = byte(padLen) + } + return padded, nil +} + +func unpad(padded []byte, size int) (buf []byte, err error) { + if len(padded)%size != 0 { + err = fmt.Errorf("Padded value wasn't in correct size for pkcs7") + return + } + + bufLen := len(padded) - int(padded[len(padded)-1]) + buf = make([]byte, bufLen) + copy(buf, padded[:bufLen]) + return +}