267 lines
7.4 KiB
Go
267 lines
7.4 KiB
Go
package main
|
|
|
|
import (
|
|
"bufio"
|
|
"bytes"
|
|
"encoding/binary"
|
|
"encoding/json"
|
|
"fmt"
|
|
"io"
|
|
"log"
|
|
"os"
|
|
"os/exec"
|
|
"path/filepath"
|
|
"strings"
|
|
"time"
|
|
"unsafe"
|
|
|
|
"github.com/go-git/go-git/v5"
|
|
"github.com/go-git/go-git/v5/plumbing/object"
|
|
)
|
|
|
|
// this project started by copying:
|
|
// https://github.com/jfarleyx/chrome-native-messaging-golang
|
|
|
|
// constants for Logger
|
|
var (
|
|
// Trace logs general information messages.
|
|
Trace *log.Logger
|
|
// Error logs error messages.
|
|
Error *log.Logger
|
|
)
|
|
|
|
// nativeEndian used to detect native byte order
|
|
var nativeEndian binary.ByteOrder
|
|
|
|
// bufferSize used to set size of IO buffer - adjust to accommodate message payloads
|
|
var bufferSize = 8192 * 8
|
|
|
|
// IncomingMessage represents a message sent to the native host.
|
|
type IncomingMessage struct {
|
|
Hugopage string `json:"hugopage"`
|
|
Filepath string `json:"filepath"`
|
|
Gitpath string `json:"gitpath"`
|
|
Publishpath string `json:"publishpath"`
|
|
Publish bool `json:"publish"`
|
|
Offline bool `json:"offline"`
|
|
Protocol string `json:"protocol"`
|
|
}
|
|
|
|
// OutgoingMessage respresents a response to an incoming message query.
|
|
type OutgoingMessage struct {
|
|
Query string `json:"query"`
|
|
Response string `json:"response"`
|
|
}
|
|
|
|
// Init initializes logger and determines native byte order.
|
|
func Init(traceHandle io.Writer, errorHandle io.Writer) {
|
|
Trace = log.New(traceHandle, "TRACE: ", log.Ldate|log.Ltime|log.Lshortfile)
|
|
Error = log.New(errorHandle, "ERROR: ", log.Ldate|log.Ltime|log.Lshortfile)
|
|
|
|
// determine native byte order so that we can read message size correctly
|
|
var one int16 = 1
|
|
b := (*byte)(unsafe.Pointer(&one))
|
|
if *b == 0 {
|
|
nativeEndian = binary.BigEndian
|
|
} else {
|
|
nativeEndian = binary.LittleEndian
|
|
}
|
|
}
|
|
|
|
func main() {
|
|
file, err := os.OpenFile(filepath.Join(os.TempDir(), "sandpoints-chromeext-log.txt"), os.O_APPEND|os.O_CREATE|os.O_WRONLY, 0644)
|
|
if err != nil {
|
|
Init(os.Stdout, os.Stderr)
|
|
Error.Printf("Unable to create and/or open log file. Will log to Stdout and Stderr. Error: %v", err)
|
|
} else {
|
|
Init(file, file)
|
|
// ensure we close the log file when we're done
|
|
defer file.Close()
|
|
}
|
|
|
|
Trace.Printf("Chrome native messaging host started. Native byte order: %v.", nativeEndian)
|
|
read()
|
|
Trace.Print("Chrome native messaging host exited.")
|
|
}
|
|
|
|
// read Creates a new buffered I/O reader and reads messages from Stdin.
|
|
func read() {
|
|
v := bufio.NewReader(os.Stdin)
|
|
// adjust buffer size to accommodate your json payload size limits; default is 4096
|
|
s := bufio.NewReaderSize(v, bufferSize)
|
|
Trace.Printf("IO buffer reader created with buffer size of %v.", s.Size())
|
|
|
|
lengthBytes := make([]byte, 4)
|
|
lengthNum := int(0)
|
|
|
|
// we're going to indefinitely read the first 4 bytes in buffer, which gives us the message length.
|
|
// if stdIn is closed we'll exit the loop and shut down host
|
|
for b, err := s.Read(lengthBytes); b > 0 && err == nil; b, err = s.Read(lengthBytes) {
|
|
// convert message length bytes to integer value
|
|
lengthNum = readMessageLength(lengthBytes)
|
|
Trace.Printf("Message size in bytes: %v", lengthNum)
|
|
|
|
// If message length exceeds size of buffer, the message will be truncated.
|
|
// This will likely cause an error when we attempt to unmarshal message to JSON.
|
|
if lengthNum > bufferSize {
|
|
Error.Printf("Message size of %d exceeds buffer size of %d. Message will be truncated and is unlikely to unmarshal to JSON.", lengthNum, bufferSize)
|
|
}
|
|
|
|
// read the content of the message from buffer
|
|
content := make([]byte, lengthNum)
|
|
_, err := s.Read(content)
|
|
if err != nil && err != io.EOF {
|
|
Error.Fatal(err)
|
|
}
|
|
|
|
// message has been read, now parse and process
|
|
parseMessage(content)
|
|
}
|
|
|
|
Trace.Print("Stdin closed.")
|
|
}
|
|
|
|
// readMessageLength reads and returns the message length value in native byte order.
|
|
func readMessageLength(msg []byte) int {
|
|
var length uint32
|
|
buf := bytes.NewBuffer(msg)
|
|
err := binary.Read(buf, nativeEndian, &length)
|
|
if err != nil {
|
|
Error.Printf("Unable to read bytes representing message length: %v", err)
|
|
}
|
|
return int(length)
|
|
}
|
|
|
|
// write incoming message to file
|
|
func writeMessageToFile(fpath string, content string) {
|
|
f, err := os.Create(fpath)
|
|
if err != nil {
|
|
Error.Printf("Write to file failed: %v", err)
|
|
}
|
|
defer f.Close()
|
|
f.WriteString(content)
|
|
}
|
|
|
|
// commit edit to git repo
|
|
func commitChangeToGit(iMsg IncomingMessage) {
|
|
g, err := git.PlainOpen(iMsg.Gitpath)
|
|
if err != nil {
|
|
Error.Printf("ERROR: Go-git Plain Open:", err)
|
|
}
|
|
w, err := g.Worktree()
|
|
if err != nil {
|
|
Error.Printf("ERROR: Go-git Worktree:", err)
|
|
}
|
|
// filePath := filepath.Base(iMsg.Filepath)
|
|
// editFile := filepath.Join(iMsg.Gitpath, filePath)
|
|
relPathFile := filepath.Join(strings.ReplaceAll(iMsg.Filepath, iMsg.Gitpath+"/", ""))
|
|
// err = ioutil.WriteFile(iMsg.Filepath, []byte(iMsg.Hugopage), 0644)
|
|
// check(err)
|
|
writeMessageToFile(iMsg.Filepath, iMsg.Hugopage)
|
|
_, err = w.Add(relPathFile)
|
|
if err != nil {
|
|
Error.Printf("ERROR: Go-git Add:", err)
|
|
}
|
|
tn := time.Now()
|
|
w.Commit(fmt.Sprintf("sandpoints-ext - %s - editing via browser...", tn), &git.CommitOptions{
|
|
Author: &object.Signature{
|
|
Name: "Sandpoints Edit Page",
|
|
Email: "editpage@sandpoints.org",
|
|
When: tn,
|
|
},
|
|
})
|
|
}
|
|
|
|
// trigger git hook
|
|
func triggerGitHook(gitpath string, publish bool, offline bool, protocol string) {
|
|
cmd := exec.Command(filepath.Join(gitpath, ".git", "hooks", "post-commit"))
|
|
stdin, err := cmd.StdinPipe()
|
|
if err != nil {
|
|
Error.Printf("StdinPipe:", err)
|
|
}
|
|
pub := "nope"
|
|
if publish {
|
|
pub = "publish"
|
|
}
|
|
offl := "nope"
|
|
if offline {
|
|
offl = "offline"
|
|
}
|
|
|
|
go func() {
|
|
defer stdin.Close()
|
|
io.WriteString(stdin, fmt.Sprintf("sandpoints-ext %s %s %s", pub, offl, protocol))
|
|
}()
|
|
out, err := cmd.CombinedOutput()
|
|
if err != nil {
|
|
Error.Printf("ERROR Combined output: %s", err)
|
|
}
|
|
Trace.Printf("Git Hook Combined Output: %s", out)
|
|
}
|
|
|
|
// parseMessage parses incoming message
|
|
func parseMessage(msg []byte) {
|
|
iMsg := decodeMessage(msg)
|
|
Trace.Printf("Message received: %s", msg)
|
|
|
|
commitChangeToGit(iMsg)
|
|
triggerGitHook(iMsg.Gitpath, iMsg.Publish, iMsg.Offline, iMsg.Protocol)
|
|
// start building outgoing json message
|
|
oMsg := OutgoingMessage{
|
|
Query: iMsg.Protocol,
|
|
}
|
|
|
|
switch iMsg.Protocol {
|
|
case "file":
|
|
oMsg.Response = iMsg.Publishpath
|
|
default:
|
|
oMsg.Response = "false"
|
|
}
|
|
|
|
send(oMsg)
|
|
}
|
|
|
|
// decodeMessage unmarshals incoming json request and returns query value.
|
|
func decodeMessage(msg []byte) IncomingMessage {
|
|
var iMsg IncomingMessage
|
|
err := json.Unmarshal(msg, &iMsg)
|
|
if err != nil {
|
|
Error.Printf("Unable to unmarshal json to struct: %v", err)
|
|
}
|
|
return iMsg
|
|
}
|
|
|
|
// sends an OutgoingMessage to os.Stdout.
|
|
func send(msg OutgoingMessage) {
|
|
byteMsg := dataToBytes(msg)
|
|
writeMessageLength(byteMsg)
|
|
|
|
var msgBuf bytes.Buffer
|
|
_, err := msgBuf.Write(byteMsg)
|
|
if err != nil {
|
|
Error.Printf("Unable to write message length to message buffer: %v", err)
|
|
}
|
|
|
|
_, err = msgBuf.WriteTo(os.Stdout)
|
|
if err != nil {
|
|
Error.Printf("Unable to write message buffer to Stdout: %v", err)
|
|
}
|
|
}
|
|
|
|
// dataToBytes marshals OutgoingMessage struct to slice of bytes
|
|
func dataToBytes(msg OutgoingMessage) []byte {
|
|
byteMsg, err := json.Marshal(msg)
|
|
if err != nil {
|
|
Error.Printf("Unable to marshal OutgoingMessage struct to slice of bytes: %v", err)
|
|
}
|
|
return byteMsg
|
|
}
|
|
|
|
// writeMessageLength determines length of message and writes it to os.Stdout.
|
|
func writeMessageLength(msg []byte) {
|
|
err := binary.Write(os.Stdout, nativeEndian, uint32(len(msg)))
|
|
if err != nil {
|
|
Error.Printf("Unable to write message length to Stdout: %v", err)
|
|
}
|
|
}
|