diff --git a/config.ini b/config.ini index f0639f2c6..dae4bce0e 100644 --- a/config.ini +++ b/config.ini @@ -7,3 +7,4 @@ v = true # show debug logging appName = CreateBridge updateUrl = http://downloads.arduino.cc/ #updateUrl = http://localhost/ +origins = http://webide.arduino.cc:8080 \ No newline at end of file diff --git a/conn.go b/conn.go index e6981a75c..f8285633a 100644 --- a/conn.go +++ b/conn.go @@ -57,6 +57,9 @@ func uploadHandler(c *gin.Context) { extraInfo.authdata.UserName = c.PostForm("auth_user") extraInfo.authdata.Password = c.PostForm("auth_pass") commandline := c.PostForm("commandline") + if commandline == "undefined" { + commandline = "" + } extraInfo.use_1200bps_touch, _ = strconv.ParseBool(c.PostForm("use_1200bps_touch")) extraInfo.wait_for_upload_port, _ = strconv.ParseBool(c.PostForm("wait_for_upload_port")) extraInfo.networkPort, _ = strconv.ParseBool(c.PostForm("network")) @@ -78,7 +81,11 @@ func uploadHandler(c *gin.Context) { c.String(http.StatusBadRequest, err.Error()) } - go spProgramRW(port, board, board_rewrite, path, commandline, extraInfo) + if board_rewrite != "" { + board = board_rewrite + } + + go spProgramRW(port, board, path, commandline, extraInfo) } } diff --git a/hub.go b/hub.go index 421989701..b03ec0a75 100755 --- a/hub.go +++ b/hub.go @@ -57,6 +57,7 @@ func (h *hub) run() { c.send <- []byte("{\"Version\" : \"" + version + "\"} ") c.send <- []byte("{\"Commands\" : [\"list\", \"open [portName] [baud] [bufferAlgorithm (optional)]\", \"send [portName] [cmd]\", \"sendnobuf [portName] [cmd]\", \"close [portName]\", \"bufferalgorithms\", \"baudrates\", \"restart\", \"exit\", \"program [portName] [board:name] [$path/to/filename/without/extension]\", \"programfromurl [portName] [board:name] [urlToHexFile]\"]} ") c.send <- []byte("{\"Hostname\" : \"" + *hostname + "\"} ") + c.send <- []byte("{\"OS\" : \"" + runtime.GOOS + "\"} ") case c := <-h.unregister: delete(h.connections, c) // put close in func cuz it was creating panics and want @@ -216,11 +217,10 @@ func checkCmd(m []byte) { //log.Print("Done with checkCmd") } -var multi_writer = io.MultiWriter(&logger_ws, os.Stderr) - func logAction(sl string) { if strings.HasPrefix(sl, "log on") { *logDump = "on" + multi_writer := io.MultiWriter(&logger_ws, os.Stderr) log.SetOutput(multi_writer) } else if strings.HasPrefix(sl, "log off") { *logDump = "off" diff --git a/main.go b/main.go index 99391714d..c92bfe525 100755 --- a/main.go +++ b/main.go @@ -43,6 +43,7 @@ var ( tempToolsPath = createToolsDir() port string portSSL string + origins = flag.String("origins", "", "Allowed origin list for CORS") ) type NullWriter int @@ -53,7 +54,7 @@ type logWriter struct{} func (u *logWriter) Write(p []byte) (n int, err error) { h.broadcastSys <- p - return 0, nil + return len(p), nil } var logger_ws logWriter @@ -109,6 +110,20 @@ func main() { iniflags.Parse() } + // move CORS to config file compatibility, Vagrant version + if *origins == "" { + log.Println("Patching config.ini for compatibility") + f, err := os.OpenFile(dest+"/"+*configIni, os.O_APPEND|os.O_WRONLY, 0666) + if err != nil { + panic(err) + } + _, err = f.WriteString("\norigins = http://webide.arduino.cc:8080\n") + if err != nil { + panic(err) + } + f.Close() + restart("") + } //log.SetFormatter(&log.JSONFormatter{}) log.SetLevel(log.InfoLevel) @@ -194,8 +209,14 @@ func main() { socketHandler := wsHandler().ServeHTTP + extraOriginStr := "https://create.arduino.cc, http://create.arduino.cc, https://create-dev.arduino.cc, http://create-dev.arduino.cc, http://create-staging.arduino.cc, https://create-staging.arduino.cc," + + for i := 8990; i < 9001; i++ { + extraOriginStr = extraOriginStr + " http://localhost:" + strconv.Itoa(i) + ", https://localhost:" + strconv.Itoa(i) + } + r.Use(cors.Middleware(cors.Config{ - Origins: "https://create.arduino.cc, http://create.arduino.cc, https://create-dev.arduino.cc, http://create-dev.arduino.cc, http://webide.arduino.cc:8080, http://create-staging.arduino.cc, https://create-staging.arduino.cc, http://localhost:8989, https://localhost:8990", + Origins: *origins + "," + extraOriginStr, Methods: "GET, PUT, POST, DELETE", RequestHeaders: "Origin, Authorization, Content-Type", ExposedHeaders: "", @@ -270,7 +291,7 @@ const homeTemplateHtml = ` var log = document.getElementById('log'); var pause = document.getElementById('myCheck'); var messages = []; - var only_log = false; + var only_log = true; function appendLog(msg) { diff --git a/programmer.go b/programmer.go index 167b2e703..8d4a5b04f 100644 --- a/programmer.go +++ b/programmer.go @@ -7,8 +7,8 @@ import ( "fmt" log "github.com/Sirupsen/logrus" "github.com/facchinm/go-serial" - "github.com/kardianos/osext" "github.com/mattn/go-shellwords" + "github.com/sfreiberg/simplessh" "io" "mime/multipart" "net/http" @@ -41,11 +41,105 @@ type boardExtraInfo struct { authdata basicAuthData } +// Scp uploads sourceFile to remote machine like native scp console app. +func Scp(client *simplessh.Client, sourceFile, targetFile string) error { + + session, err := client.SSHClient.NewSession() + if err != nil { + return err + } + defer session.Close() + + src, srcErr := os.Open(sourceFile) + + if srcErr != nil { + return srcErr + } + + srcStat, statErr := src.Stat() + + if statErr != nil { + return statErr + } + + go func() { + w, _ := session.StdinPipe() + + fmt.Fprintln(w, "C0644", srcStat.Size(), filepath.Base(targetFile)) + + if srcStat.Size() > 0 { + io.Copy(w, src) + fmt.Fprint(w, "\x00") + w.Close() + } else { + fmt.Fprint(w, "\x00") + w.Close() + } + + }() + + if err := session.Run("scp -t " + targetFile); err != nil { + return err + } + + return nil +} + +func spProgramSSHNetwork(portname string, boardname string, filePath string, commandline string, authdata basicAuthData) error { + log.Println("Starting network upload") + log.Println("Board Name: " + boardname) + + if authdata.UserName == "" { + authdata.UserName = "root" + } + + if authdata.Password == "" { + authdata.Password = "arduino" + } + + ssh_client, err := simplessh.ConnectWithPassword(portname+":22", authdata.UserName, authdata.Password) + if err != nil { + log.Println("Error connecting via ssh") + return err + } + defer ssh_client.Close() + + err = Scp(ssh_client, filePath, "/tmp/sketch"+filepath.Ext(filePath)) + if err != nil { + log.Printf("Upload: %s\n", err) + return err + } + + if commandline == "" { + // very special case for Yun (remove once AVR boards.txt is fixed) + commandline = "merge-sketch-with-bootloader.lua /tmp/sketch.hex && /usr/bin/run-avrdude /tmp/sketch.hex" + } + + fmt.Println(commandline) + + ssh_output, err := ssh_client.Exec(commandline) + if err == nil { + log.Printf("Flash: %s\n", ssh_output) + mapD := map[string]string{"ProgrammerStatus": "Busy", "Msg": string(ssh_output)} + mapB, _ := json.Marshal(mapD) + h.broadcastSys <- mapB + } + return err +} + func spProgramNetwork(portname string, boardname string, filePath string, authdata basicAuthData) error { log.Println("Starting network upload") log.Println("Board Name: " + boardname) + if authdata.UserName == "" { + authdata.UserName = "root" + } + + if authdata.Password == "" { + authdata.Password = "arduino" + } + // Prepare a form that you will submit to that URL. _url := "http://" + portname + "/data/upload_sketch_silent" var b bytes.Buffer @@ -110,23 +204,10 @@ func spProgramNetwork(portname string, boardname string, filePath string, authda log.Errorf("bad status: %s", res.Status) err = fmt.Errorf("bad status: %s", res.Status) } - - if err != nil { - log.Printf("Command finished with error: %v ", err) - mapD := map[string]string{"ProgrammerStatus": "Error", "Msg": "Could not program the board", "Output": "", "Err": "Could not program the board"} - mapB, _ := json.Marshal(mapD) - h.broadcastSys <- mapB - } else { - log.Printf("Finished without error. Good stuff.") - mapD := map[string]string{"ProgrammerStatus": "Done", "Flash": "Ok", "Output": ""} - mapB, _ := json.Marshal(mapD) - h.broadcastSys <- mapB - // analyze stdin - } return err } -func spProgramLocal(portname string, boardname string, filePath string, commandline string, extraInfo boardExtraInfo) { +func spProgramLocal(portname string, boardname string, filePath string, commandline string, extraInfo boardExtraInfo) error { var err error if extraInfo.use_1200bps_touch { @@ -135,7 +216,7 @@ func spProgramLocal(portname string, boardname string, filePath string, commandl if err != nil { log.Println("Could not touch the port") - return + return err } log.Printf("Received commandline (unresolved):" + commandline) @@ -156,15 +237,10 @@ func spProgramLocal(portname string, boardname string, filePath string, commandl } z, _ := shellwords.Parse(commandline) - spHandlerProgram(z[0], z[1:]) -} - -func spProgram(portname string, boardname string, filePath string, commandline string, extraInfo boardExtraInfo) { - - spProgramRW(portname, boardname, "", filePath, commandline, extraInfo) + return spHandlerProgram(z[0], z[1:]) } -func spProgramRW(portname string, boardname string, boardname_rewrite string, filePath string, commandline string, extraInfo boardExtraInfo) { +func spProgramRW(portname string, boardname string, filePath string, commandline string, extraInfo boardExtraInfo) { compiling = true defer func() { @@ -175,24 +251,32 @@ func spProgramRW(portname string, boardname string, boardname_rewrite string, fi var err error if extraInfo.networkPort { - if boardname_rewrite != "" { - err = spProgramNetwork(portname, boardname_rewrite, filePath, extraInfo.authdata) - } else { - err = spProgramNetwork(portname, boardname, filePath, extraInfo.authdata) - } + err = spProgramNetwork(portname, boardname, filePath, extraInfo.authdata) if err != nil { - mapD := map[string]string{"ProgrammerStatus": "Error", "Msg": "Could not program the board", "Output": "", "Err": err.Error()} - mapB, _ := json.Marshal(mapD) - h.broadcastSys <- mapB + // no http method available, try ssh upload + err = spProgramSSHNetwork(portname, boardname, filePath, commandline, extraInfo.authdata) } } else { - spProgramLocal(portname, boardname, filePath, commandline, extraInfo) + err = spProgramLocal(portname, boardname, filePath, commandline, extraInfo) + } + + if err != nil { + log.Printf("Command finished with error: %v", err) + mapD := map[string]string{"ProgrammerStatus": "Error", "Msg": "Could not program the board"} + mapB, _ := json.Marshal(mapD) + h.broadcastSys <- mapB + } else { + log.Printf("Finished without error. Good stuff") + mapD := map[string]string{"ProgrammerStatus": "Done", "Flash": "Ok"} + mapB, _ := json.Marshal(mapD) + h.broadcastSys <- mapB + // analyze stdin } } var oscmd *exec.Cmd -func spHandlerProgram(flasher string, cmdString []string) { +func spHandlerProgram(flasher string, cmdString []string) error { // if runtime.GOOS == "darwin" { // sh, _ := exec.LookPath("sh") @@ -213,18 +297,18 @@ func spHandlerProgram(flasher string, cmdString []string) { extension = ".exe" } - oscmd = exec.Command(flasher+extension, cmdString...) + oscmd = exec.Command(flasher, cmdString...) tellCommandNotToSpawnShell(oscmd) stdout, err := oscmd.StdoutPipe() if err != nil { - return + return err } stderr, err := oscmd.StderrPipe() if err != nil { - return + return err } multi := io.MultiReader(stderr, stdout) @@ -253,19 +337,7 @@ func spHandlerProgram(flasher string, cmdString []string) { err = oscmd.Wait() - if err != nil { - log.Printf("Command finished with error: %v", err) - mapD := map[string]string{"ProgrammerStatus": "Error", "Msg": "Could not program the board"} - mapB, _ := json.Marshal(mapD) - h.broadcastSys <- mapB - } else { - log.Printf("Finished without error. Good stuff") - mapD := map[string]string{"ProgrammerStatus": "Done", "Flash": "Ok"} - mapB, _ := json.Marshal(mapD) - h.broadcastSys <- mapB - // analyze stdin - - } + return err } func spHandlerProgramKill() { @@ -341,6 +413,11 @@ func touch_port_1200bps(portname string, wait_for_upload_port bool) (string, err initialPortName := portname log.Println("Restarting in bootloader mode") + before_reset_ports, _ := serial.GetPortsList() + log.Println(before_reset_ports) + + var ports []string + mode := &serial.Mode{ BaudRate: 1200, Vmin: 0, @@ -356,220 +433,49 @@ func touch_port_1200bps(portname string, wait_for_upload_port bool) (string, err log.Println(err) } port.Close() - time.Sleep(time.Second / 2.0) timeout := false go func() { - time.Sleep(2 * time.Second) + time.Sleep(10 * time.Second) timeout = true }() - // time.Sleep(time.Second / 4) - // wait for port to reappear + // wait for port to disappear if wait_for_upload_port { - after_reset_ports, _ := serial.GetPortsList() - log.Println(after_reset_ports) - var ports []string for { ports, _ = serial.GetPortsList() log.Println(ports) - time.Sleep(time.Millisecond * 200) - portname = findNewPortName(ports, after_reset_ports) + portname = findNewPortName(ports, before_reset_ports) if portname != "" { break } if timeout { break } + time.Sleep(time.Millisecond * 100) } } - if portname == "" { - portname = initialPortName - } - return portname, nil -} - -func assembleCompilerCommand(boardname string, portname string, filePath string) (bool, string, []string) { - - // get executable (self)path and use it as base for all other paths - execPath, _ := osext.Executable() - - boardFields := strings.Split(boardname, ":") - if len(boardFields) != 3 { - mapD := map[string]string{"Err": "Board need to be specified in core:architecture:name format"} - mapB, _ := json.Marshal(mapD) - h.broadcastSys <- mapB - return false, "", nil - } - tempPath := (filepath.Dir(execPath) + "/" + boardFields[0] + "/hardware/" + boardFields[1] + "/boards.txt") - file, err := os.Open(tempPath) - if err != nil { - mapD := map[string]string{"Err": "Could not find board: " + boardname} - mapB, _ := json.Marshal(mapD) - h.broadcastSys <- mapB - log.Println("Error:", err) - return false, "", nil - } - scanner := bufio.NewScanner(file) - - boardOptions := make(map[string]string) - uploadOptions := make(map[string]string) - - for scanner.Scan() { - // map everything matching with boardname - if strings.Contains(scanner.Text(), boardFields[2]) { - arr := strings.Split(scanner.Text(), "=") - arr[0] = strings.Replace(arr[0], boardFields[2]+".", "", 1) - boardOptions[arr[0]] = arr[1] - } - } - - if len(boardOptions) == 0 { - mapD := map[string]string{"Err": "Board " + boardFields[2] + " is not part of " + boardFields[0] + ":" + boardFields[1]} - mapB, _ := json.Marshal(mapD) - h.broadcastSys <- mapB - return false, "", nil - } - - // filepath need special care; the project_name var is the filename minus its extension (hex or bin) - // if we are going to modify standard IDE files we also could pass ALL filename - filePath = strings.Trim(filePath, "\n") - boardOptions["build.path"] = filepath.Dir(filePath) - boardOptions["build.project_name"] = strings.TrimSuffix(filepath.Base(filePath), filepath.Ext(filepath.Base(filePath))) - - file.Close() - - // get infos about the programmer - tempPath = (filepath.Dir(execPath) + "/" + boardFields[0] + "/hardware/" + boardFields[1] + "/platform.txt") - file, err = os.Open(tempPath) - if err != nil { - mapD := map[string]string{"Err": "Could not find board: " + boardname} - mapB, _ := json.Marshal(mapD) - h.broadcastSys <- mapB - log.Println("Error:", err) - return false, "", nil - } - scanner = bufio.NewScanner(file) - - tool := boardOptions["upload.tool"] - - for scanner.Scan() { - // map everything matching with upload - if strings.Contains(scanner.Text(), tool) { - arr := strings.Split(scanner.Text(), "=") - uploadOptions[arr[0]] = arr[1] - arr[0] = strings.Replace(arr[0], "tools."+tool+".", "", 1) - boardOptions[arr[0]] = arr[1] - // we have a "=" in command line - if len(arr) > 2 { - boardOptions[arr[0]] = arr[1] + "=" + arr[2] + // wait for port to reappear + if wait_for_upload_port { + after_reset_ports, _ := serial.GetPortsList() + log.Println(after_reset_ports) + for { + ports, _ = serial.GetPortsList() + log.Println(ports) + portname = findNewPortName(ports, after_reset_ports) + if portname != "" { + break } - } - } - file.Close() - - // multiple verisons of the same programmer can be handled if "version" is specified - version := uploadOptions["runtime.tools."+tool+".version"] - path := (filepath.Dir(execPath) + "/" + boardFields[0] + "/tools/" + tool + "/" + version) - if err != nil { - mapD := map[string]string{"Err": "Could not find board: " + boardname} - mapB, _ := json.Marshal(mapD) - h.broadcastSys <- mapB - log.Println("Error:", err) - return false, "", nil - } - - boardOptions["runtime.tools."+tool+".path"] = path - - cmdline := boardOptions["upload.pattern"] - // remove cmd.path as it is handled differently - cmdline = strings.Replace(cmdline, "\"{cmd.path}\"", " ", 1) - cmdline = strings.Replace(cmdline, "\"{path}/{cmd}\"", " ", 1) - cmdline = strings.Replace(cmdline, "\"", "", -1) - - initialPortName := portname - - // some boards (eg. Leonardo, Yun) need a special procedure to enter bootloader - if boardOptions["upload.use_1200bps_touch"] == "true" { - // triggers bootloader mode - // the portname could change in this occasion (expecially on Windows) so change portname - // with the port which will reappear - log.Println("Restarting in bootloader mode") - - mode := &serial.Mode{ - BaudRate: 1200, - Vmin: 1, - Vtimeout: 0, - } - port, err := serial.OpenPort(portname, mode) - if err != nil { - log.Println(err) - return false, "", nil - } - //port.SetDTR(false) - port.Close() - time.Sleep(time.Second / 2.0) - - timeout := false - go func() { - time.Sleep(2 * time.Second) - timeout = true - }() - - // time.Sleep(time.Second / 4) - // wait for port to reappear - if boardOptions["upload.wait_for_upload_port"] == "true" { - after_reset_ports, _ := serial.GetPortsList() - log.Println(after_reset_ports) - var ports []string - for { - ports, _ = serial.GetPortsList() - log.Println(ports) - time.Sleep(time.Millisecond * 200) - portname = findNewPortName(ports, after_reset_ports) - if portname != "" { - break - } - if timeout { - break - } + if timeout { + break } + time.Sleep(time.Millisecond * 100) } } if portname == "" { portname = initialPortName } - - boardOptions["serial.port"] = portname - boardOptions["serial.port.file"] = filepath.Base(portname) - - // split the commandline in substrings and recursively replace mapped strings - cmdlineSlice := strings.Split(cmdline, " ") - var winded = true - for index, _ := range cmdlineSlice { - winded = true - for winded != false { - cmdlineSlice[index], winded = formatCmdline(cmdlineSlice[index], boardOptions) - } - } - - tool = (filepath.Dir(execPath) + "/" + boardFields[0] + "/tools/" + tool + "/bin/" + tool) - // the file doesn't exist, we are on windows - if _, err := os.Stat(tool); err != nil { - tool = tool + ".exe" - // convert all "/" to "\" - tool = strings.Replace(tool, "/", "\\", -1) - } - - // remove blanks from cmdlineSlice - var cmdlineSliceOut []string - for _, element := range cmdlineSlice { - if element != "" { - cmdlineSliceOut = append(cmdlineSliceOut, element) - } - } - - return (tool != ""), tool, cmdlineSliceOut + return portname, nil }