// DecompressHandler handles request of decompressing zip/tar.gz. func DecompressHandler(w http.ResponseWriter, r *http.Request) { result := util.NewResult() defer util.RetResult(w, r, result) var args map[string]interface{} if err := json.NewDecoder(r.Body).Decode(&args); err != nil { logger.Error(err) result.Succ = false return } path := args["path"].(string) // base := filepath.Base(path) dir := filepath.Dir(path) if !util.File.IsExist(path) { result.Succ = false result.Msg = "Can't find file [" + path + "] to descompress" return } err := util.Zip.Unzip(path, dir) if nil != err { logger.Error(err) result.Succ = false return } }
// LoginHandler handles request of user login. func LoginHandler(w http.ResponseWriter, r *http.Request) { if "GET" == r.Method { // show the login page model := map[string]interface{}{"conf": conf.Wide, "i18n": i18n.GetAll(conf.Wide.Locale), "locale": conf.Wide.Locale, "ver": conf.WideVersion, "year": time.Now().Year()} t, err := template.ParseFiles("views/login.html") if nil != err { logger.Error(err) http.Error(w, err.Error(), 500) return } t.Execute(w, model) return } // non-GET request as login request result := util.NewResult() defer util.RetResult(w, r, result) args := struct { Username string Password string }{} args.Username = r.FormValue("username") args.Password = r.FormValue("password") result.Succ = false for _, user := range conf.Users { if user.Name == args.Username && user.Password == conf.Salt(args.Password, user.Salt) { result.Succ = true break } } if !result.Succ { return } // create a HTTP session httpSession, _ := HTTPSession.Get(r, "wide-session") httpSession.Values["username"] = args.Username httpSession.Values["id"] = strconv.Itoa(rand.Int()) httpSession.Options.MaxAge = conf.Wide.HTTPSessionMaxAge if "" != conf.Wide.Context { httpSession.Options.Path = conf.Wide.Context } httpSession.Save(r, w) logger.Debugf("Created a HTTP session [%s] for user [%s]", httpSession.Values["id"].(string), args.Username) }
// UploadHandler handles request of file upload. func UploadHandler(w http.ResponseWriter, r *http.Request) { result := util.NewResult() defer util.RetResult(w, r, result) q := r.URL.Query() dir := q["path"][0] result.Data = handleUploads(r, dir) }
// LogoutHandler handles request of user logout (exit). func LogoutHandler(w http.ResponseWriter, r *http.Request) { result := util.NewResult() defer util.RetResult(w, r, result) httpSession, _ := HTTPSession.Get(r, "wide-session") httpSession.Options.MaxAge = -1 httpSession.Save(r, w) }
// SaveFileHandler handles request of saving file. func SaveFileHandler(w http.ResponseWriter, r *http.Request) { httpSession, _ := session.HTTPSession.Get(r, "wide-session") if httpSession.IsNew { http.Error(w, "Forbidden", http.StatusForbidden) return } username := httpSession.Values["username"].(string) result := util.NewResult() defer util.RetResult(w, r, result) var args map[string]interface{} if err := json.NewDecoder(r.Body).Decode(&args); err != nil { logger.Error(err) result.Succ = false return } filePath := args["file"].(string) sid := args["sid"].(string) if util.Go.IsAPI(filePath) || !session.CanAccess(username, filePath) { http.Error(w, "Forbidden", http.StatusForbidden) return } fout, err := os.Create(filePath) if nil != err { logger.Error(err) result.Succ = false return } code := args["code"].(string) fout.WriteString(code) if err := fout.Close(); nil != err { logger.Error(err) result.Succ = false wSession := session.WideSessions.Get(sid) wSession.EventQueue.Queue <- &event.Event{Code: event.EvtCodeServerInternalError, Sid: sid, Data: "can't save file " + filePath} return } }
// FindHandler handles request of find files under the specified directory with the specified filename pattern. func FindHandler(w http.ResponseWriter, r *http.Request) { httpSession, _ := session.HTTPSession.Get(r, "wide-session") if httpSession.IsNew { http.Error(w, "Forbidden", http.StatusForbidden) return } username := httpSession.Values["username"].(string) result := util.NewResult() defer util.RetResult(w, r, result) var args map[string]interface{} if err := json.NewDecoder(r.Body).Decode(&args); err != nil { logger.Error(err) result.Succ = false return } path := args["path"].(string) // path of selected file in file tree if !util.Go.IsAPI(path) && !session.CanAccess(username, path) { http.Error(w, "Forbidden", http.StatusForbidden) return } name := args["name"].(string) userWorkspace := conf.GetUserWorkspace(username) workspaces := filepath.SplitList(userWorkspace) if "" != path && !util.File.IsDir(path) { path = filepath.Dir(path) } founds := foundPaths{} for _, workspace := range workspaces { rs := find(workspace+conf.PathSeparator+"src", name, []*string{}) for _, r := range rs { substr := util.Str.LCS(path, *r) founds = append(founds, &foundPath{Path: filepath.ToSlash(*r), score: len(substr)}) } } sort.Sort(founds) result.Data = founds }
// RenameFileHandler handles request of renaming file or directory. func RenameFileHandler(w http.ResponseWriter, r *http.Request) { httpSession, _ := session.HTTPSession.Get(r, "wide-session") if httpSession.IsNew { http.Error(w, "Forbidden", http.StatusForbidden) return } username := httpSession.Values["username"].(string) result := util.NewResult() defer util.RetResult(w, r, result) var args map[string]interface{} if err := json.NewDecoder(r.Body).Decode(&args); err != nil { logger.Error(err) result.Succ = false return } oldPath := args["oldPath"].(string) if util.Go.IsAPI(oldPath) || !session.CanAccess(username, oldPath) { http.Error(w, "Forbidden", http.StatusForbidden) return } newPath := args["newPath"].(string) if util.Go.IsAPI(newPath) || !session.CanAccess(username, newPath) { http.Error(w, "Forbidden", http.StatusForbidden) return } sid := args["sid"].(string) wSession := session.WideSessions.Get(sid) if !renameFile(oldPath, newPath) { result.Succ = false wSession.EventQueue.Queue <- &event.Event{Code: event.EvtCodeServerInternalError, Sid: sid, Data: "can't rename file " + oldPath} return } logger.Debugf("Renamed a file [%s] to [%s] by user [%s]", oldPath, newPath, wSession.Username) }
// NewFileHandler handles request of creating file or directory. func NewFileHandler(w http.ResponseWriter, r *http.Request) { httpSession, _ := session.HTTPSession.Get(r, "wide-session") if httpSession.IsNew { http.Error(w, "Forbidden", http.StatusForbidden) return } username := httpSession.Values["username"].(string) result := util.NewResult() defer util.RetResult(w, r, result) var args map[string]interface{} if err := json.NewDecoder(r.Body).Decode(&args); err != nil { logger.Error(err) result.Succ = false return } path := args["path"].(string) if util.Go.IsAPI(path) || !session.CanAccess(username, path) { http.Error(w, "Forbidden", http.StatusForbidden) return } fileType := args["fileType"].(string) sid := args["sid"].(string) wSession := session.WideSessions.Get(sid) if !createFile(path, fileType) { result.Succ = false wSession.EventQueue.Queue <- &event.Event{Code: event.EvtCodeServerInternalError, Sid: sid, Data: "can't create file " + path} return } if "f" == fileType { logger.Debugf("Created a file [%s] by user [%s]", path, wSession.Username) } else { logger.Debugf("Created a dir [%s] by user [%s]", path, wSession.Username) } }
// CreateZipHandler handles request of creating zip. func CreateZipHandler(w http.ResponseWriter, r *http.Request) { data := util.NewResult() defer util.RetResult(w, r, data) var args map[string]interface{} if err := json.NewDecoder(r.Body).Decode(&args); err != nil { logger.Error(err) data.Succ = false return } path := args["path"].(string) var name string base := filepath.Base(path) if nil != args["name"] { name = args["name"].(string) } else { name = base } dir := filepath.Dir(path) if !util.File.IsExist(path) { data.Succ = false data.Msg = "Can't find file [" + path + "]" return } zipPath := filepath.Join(dir, name) zipFile, err := util.Zip.Create(zipPath + ".zip") if nil != err { logger.Error(err) data.Succ = false return } defer zipFile.Close() if util.File.IsDir(path) { zipFile.AddDirectory(base, path) } else { zipFile.AddEntry(base, path) } data.Data = zipPath }
// SearchTextHandler handles request of searching files under the specified directory with the specified keyword. func SearchTextHandler(w http.ResponseWriter, r *http.Request) { httpSession, _ := session.HTTPSession.Get(r, "wide-session") if httpSession.IsNew { http.Error(w, "Forbidden", http.StatusForbidden) return } result := util.NewResult() defer util.RetResult(w, r, result) var args map[string]interface{} if err := json.NewDecoder(r.Body).Decode(&args); err != nil { logger.Error(err) result.Succ = false return } sid := args["sid"].(string) wSession := session.WideSessions.Get(sid) if nil == wSession { result.Succ = false return } // XXX: just one directory dir := args["dir"].(string) if "" == dir { userWorkspace := conf.GetUserWorkspace(wSession.Username) workspaces := filepath.SplitList(userWorkspace) dir = workspaces[0] } extension := args["extension"].(string) text := args["text"].(string) founds := []*Snippet{} if util.File.IsDir(dir) { founds = search(dir, extension, text, []*Snippet{}) } else { founds = searchInFile(dir, text) } result.Data = founds }
// GetFilesHandler handles request of constructing user workspace file tree. // // The Go API source code package also as a child node, // so that users can easily view the Go API source code in file tree. func GetFilesHandler(w http.ResponseWriter, r *http.Request) { httpSession, _ := session.HTTPSession.Get(r, "wide-session") if httpSession.IsNew { http.Error(w, "Forbidden", http.StatusForbidden) return } username := httpSession.Values["username"].(string) result := util.NewResult() defer util.RetGzResult(w, r, result) userWorkspace := conf.GetUserWorkspace(username) workspaces := filepath.SplitList(userWorkspace) root := Node{Name: "root", Path: "", IconSkin: "ico-ztree-dir ", Type: "d", IsParent: true, Children: []*Node{}} if nil == apiNode { // lazy init initAPINode() } // workspace node process for _, workspace := range workspaces { workspacePath := workspace + conf.PathSeparator + "src" workspaceNode := Node{ Id: filepath.ToSlash(workspacePath), // jQuery API can't accept "\", so we convert it to "/" Name: workspace[strings.LastIndex(workspace, conf.PathSeparator)+1:], Path: filepath.ToSlash(workspacePath), IconSkin: "ico-ztree-dir-workspace ", Type: "d", Creatable: true, Removable: false, IsGoAPI: false, Children: []*Node{}} walk(workspacePath, &workspaceNode, true, true, false) // add workspace node root.Children = append(root.Children, &workspaceNode) } // add Go API node root.Children = append(root.Children, apiNode) result.Data = root }
// BuildHandler handles request of Playground building. func BuildHandler(w http.ResponseWriter, r *http.Request) { result := util.NewResult() defer util.RetResult(w, r, result) httpSession, _ := session.HTTPSession.Get(r, "wide-session") if httpSession.IsNew { http.Error(w, "Forbidden", http.StatusForbidden) return } var args map[string]interface{} if err := json.NewDecoder(r.Body).Decode(&args); err != nil { logger.Error(err) result.Succ = false return } fileName := args["fileName"].(string) filePath := filepath.Clean(conf.Wide.Playground + "/" + fileName) suffix := "" if util.OS.IsWindows() { suffix = ".exe" } data := map[string]interface{}{} executable := filepath.Clean(conf.Wide.Playground + "/" + strings.Replace(fileName, ".go", suffix, -1)) cmd := exec.Command("go", "build", "-o", executable, filePath) out, err := cmd.CombinedOutput() data["output"] = template.HTML(string(out)) if nil != err { result.Succ = false return } data["executable"] = executable result.Data = data }
// ShortURLHandler handles request of short URL. func ShortURLHandler(w http.ResponseWriter, r *http.Request) { result := util.NewResult() defer util.RetResult(w, r, result) session, _ := session.HTTPSession.Get(r, "wide-session") if session.IsNew { http.Error(w, "Forbidden", http.StatusForbidden) return } var args map[string]interface{} if err := json.NewDecoder(r.Body).Decode(&args); err != nil { logger.Error(err) result.Succ = false return } url := args["url"].(string) resp, _ := http.Post("http://dwz.cn/create.php", "application/x-www-form-urlencoded", strings.NewReader("url="+url)) var response map[string]interface{} if err := json.NewDecoder(resp.Body).Decode(&response); err != nil { logger.Error(err) result.Succ = false return } shortURL := url if 0 == response["status"].(float64) { shortURL = response["tinyurl"].(string) } result.Data = shortURL }
// SaveContentHandler handles request of session content string. func SaveContentHandler(w http.ResponseWriter, r *http.Request) { result := util.NewResult() defer util.RetResult(w, r, result) args := struct { Sid string *conf.LatestSessionContent }{} if err := json.NewDecoder(r.Body).Decode(&args); err != nil { logger.Error(err) result.Succ = false return } wSession := WideSessions.Get(args.Sid) if nil == wSession { result.Succ = false return } wSession.Content = args.LatestSessionContent for _, user := range conf.Users { if user.Name == wSession.Username { // update the variable in-memory, session.FixedTimeSave() function will persist it periodically user.LatestSessionContent = wSession.Content user.Lived = time.Now().UnixNano() wSession.Refresh() return } } }
// StopHandler handles request of stoping a running process. func StopHandler(w http.ResponseWriter, r *http.Request) { result := util.NewResult() defer util.RetResult(w, r, result) var args map[string]interface{} if err := json.NewDecoder(r.Body).Decode(&args); err != nil { logger.Error(err) result.Succ = false return } sid := args["sid"].(string) pid := int(args["pid"].(float64)) wSession := session.WideSessions.Get(sid) if nil == wSession { result.Succ = false return } output.Processes.Kill(wSession, pid) }
// RunHandler handles request of executing a binary file. func RunHandler(w http.ResponseWriter, r *http.Request) { result := util.NewResult() defer util.RetResult(w, r, result) var args map[string]interface{} if err := json.NewDecoder(r.Body).Decode(&args); err != nil { logger.Error(err) result.Succ = false } sid := args["sid"].(string) wSession := session.WideSessions.Get(sid) if nil == wSession { result.Succ = false } filePath := args["executable"].(string) cmd := exec.Command(filePath) if conf.Docker { output.SetNamespace(cmd) } stdout, err := cmd.StdoutPipe() if nil != err { logger.Error(err) result.Succ = false } stderr, err := cmd.StderrPipe() if nil != err { logger.Error(err) result.Succ = false } outReader := bufio.NewReader(stdout) errReader := bufio.NewReader(stderr) if err := cmd.Start(); nil != err { logger.Error(err) result.Succ = false } wsChannel := session.PlaygroundWS[sid] channelRet := map[string]interface{}{} if !result.Succ { if nil != wsChannel { channelRet["cmd"] = "run-done" channelRet["output"] = "" err := wsChannel.WriteJSON(&channelRet) if nil != err { logger.Warn(err) return } wsChannel.Refresh() } return } channelRet["pid"] = cmd.Process.Pid // add the process to user's process set output.Processes.Add(wSession, cmd.Process) go func(runningId int) { defer util.Recover() defer cmd.Wait() logger.Debugf("User [%s, %s] is running [id=%d, file=%s]", wSession.Username, sid, runningId, filePath) // push once for front-end to get the 'run' state and pid if nil != wsChannel { channelRet["cmd"] = "run" channelRet["output"] = "" err := wsChannel.WriteJSON(&channelRet) if nil != err { logger.Warn(err) return } wsChannel.Refresh() } go func() { defer util.Recover() buf := outputBuf{} for { wsChannel := session.PlaygroundWS[sid] if nil == wsChannel { break } r, _, err := outReader.ReadRune() if nil != err { // remove the exited process from user process set output.Processes.Remove(wSession, cmd.Process) logger.Debugf("User [%s, %s] 's running [id=%d, file=%s] has done [stdout %v], ", wSession.Username, sid, runningId, filePath, err) channelRet["cmd"] = "run-done" channelRet["output"] = buf.content err := wsChannel.WriteJSON(&channelRet) if nil != err { logger.Warn(err) break } wsChannel.Refresh() break } oneRuneStr := string(r) buf.content += oneRuneStr now := time.Now().UnixNano() / int64(time.Millisecond) if 0 == buf.millisecond { buf.millisecond = now } if now-outputTimeout >= buf.millisecond || len(buf.content) > outputBufMax || oneRuneStr == "\n" { channelRet["cmd"] = "run" channelRet["output"] = buf.content buf = outputBuf{} // a new buffer err = wsChannel.WriteJSON(&channelRet) if nil != err { logger.Warn(err) break } wsChannel.Refresh() } } }() buf := outputBuf{} for { r, _, err := errReader.ReadRune() wsChannel := session.PlaygroundWS[sid] if nil != err || nil == wsChannel { break } oneRuneStr := string(r) buf.content += oneRuneStr now := time.Now().UnixNano() / int64(time.Millisecond) if 0 == buf.millisecond { buf.millisecond = now } if now-outputTimeout >= buf.millisecond || len(buf.content) > outputBufMax || oneRuneStr == "\n" { channelRet["cmd"] = "run" channelRet["output"] = buf.content buf = outputBuf{} // a new buffer err = wsChannel.WriteJSON(&channelRet) if nil != err { logger.Warn(err) break } wsChannel.Refresh() } } }(rand.Int()) }
// GoVetHandler handles request of go vet. func GoVetHandler(w http.ResponseWriter, r *http.Request) { result := util.NewResult() defer util.RetResult(w, r, result) httpSession, _ := session.HTTPSession.Get(r, "wide-session") if httpSession.IsNew { http.Error(w, "Forbidden", http.StatusForbidden) return } username := httpSession.Values["username"].(string) locale := conf.GetUser(username).Locale var args map[string]interface{} if err := json.NewDecoder(r.Body).Decode(&args); err != nil { logger.Error(err) result.Succ = false return } sid := args["sid"].(string) filePath := args["file"].(string) curDir := filepath.Dir(filePath) cmd := exec.Command("go", "vet", ".") cmd.Dir = curDir setCmdEnv(cmd, username) stdout, err := cmd.StdoutPipe() if nil != err { logger.Error(err) result.Succ = false return } stderr, err := cmd.StderrPipe() if nil != err { logger.Error(err) result.Succ = false return } if !result.Succ { return } channelRet := map[string]interface{}{} if nil != session.OutputWS[sid] { // display "START [go vet]" in front-end browser channelRet["output"] = "<span class='start-vet'>" + i18n.Get(locale, "start-vet").(string) + "</span>\n" channelRet["cmd"] = "start-vet" wsChannel := session.OutputWS[sid] err := wsChannel.WriteJSON(&channelRet) if nil != err { logger.Warn(err) return } wsChannel.Refresh() } reader := bufio.NewReader(io.MultiReader(stdout, stderr)) if err := cmd.Start(); nil != err { logger.Error(err) result.Succ = false return } go func(runningId int) { defer util.Recover() logger.Debugf("User [%s, %s] is running [go vet] [runningId=%d]", username, sid, runningId) channelRet := map[string]interface{}{} channelRet["cmd"] = "go vet" // read all buf, _ := ioutil.ReadAll(reader) // waiting for go vet finished cmd.Wait() if !cmd.ProcessState.Success() { logger.Debugf("User [%s, %s] 's running [go vet] [runningId=%d] has done (with error)", username, sid, runningId) channelRet["output"] = "<span class='vet-error'>" + i18n.Get(locale, "vet-error").(string) + "</span>\n" + string(buf) } else { logger.Debugf("User [%s, %s] 's running [go vet] [runningId=%d] has done", username, sid, runningId) channelRet["output"] = "<span class='vet-succ'>" + i18n.Get(locale, "vet-succ").(string) + "</span>\n" + string(buf) } if nil != session.OutputWS[sid] { wsChannel := session.OutputWS[sid] err := wsChannel.WriteJSON(&channelRet) if nil != err { logger.Warn(err) } wsChannel.Refresh() } }(rand.Int()) }
// GoFmtHandler handles request of formatting Go source code. // // This function will select a format tooll based on user's configuration: // 1. gofmt // 2. goimports func GoFmtHandler(w http.ResponseWriter, r *http.Request) { result := util.NewResult() defer util.RetResult(w, r, result) session, _ := session.HTTPSession.Get(r, "wide-session") if session.IsNew { http.Error(w, "Forbidden", http.StatusForbidden) return } username := session.Values["username"].(string) var args map[string]interface{} if err := json.NewDecoder(r.Body).Decode(&args); err != nil { logger.Error(err) result.Succ = false return } filePath := args["file"].(string) if util.Go.IsAPI(filePath) { result.Succ = false return } fout, err := os.Create(filePath) if nil != err { logger.Error(err) result.Succ = false return } code := args["code"].(string) fout.WriteString(code) if err := fout.Close(); nil != err { logger.Error(err) result.Succ = false return } data := map[string]interface{}{} result.Data = &data data["code"] = code result.Data = data fmt := conf.GetGoFmt(username) argv := []string{filePath} cmd := exec.Command(fmt, argv...) bytes, _ := cmd.Output() output := string(bytes) if "" == output { // format error, returns the original content result.Succ = true return } code = string(output) data["code"] = code fout, err = os.Create(filePath) fout.WriteString(code) if err := fout.Close(); nil != err { logger.Error(err) result.Succ = false return } }
// PreferenceHandler handles request of preference page. func PreferenceHandler(w http.ResponseWriter, r *http.Request) { httpSession, _ := HTTPSession.Get(r, "wide-session") if httpSession.IsNew { http.Redirect(w, r, conf.Wide.Context+"login", http.StatusFound) return } httpSession.Options.MaxAge = conf.Wide.HTTPSessionMaxAge if "" != conf.Wide.Context { httpSession.Options.Path = conf.Wide.Context } httpSession.Save(r, w) username := httpSession.Values["username"].(string) user := conf.GetUser(username) if "GET" == r.Method { model := map[string]interface{}{"conf": conf.Wide, "i18n": i18n.GetAll(user.Locale), "user": user, "ver": conf.WideVersion, "goos": runtime.GOOS, "goarch": runtime.GOARCH, "gover": runtime.Version(), "locales": i18n.GetLocalesNames(), "gofmts": util.Go.GetGoFormats(), "themes": conf.GetThemes(), "editorThemes": conf.GetEditorThemes()} t, err := template.ParseFiles("views/preference.html") if nil != err { logger.Error(err) http.Error(w, err.Error(), 500) return } t.Execute(w, model) return } // non-GET request as save request result := util.NewResult() defer util.RetResult(w, r, result) args := struct { FontFamily string FontSize string GoFmt string Keymap string Workspace string Username string Password string Email string Locale string Theme string EditorFontFamily string EditorFontSize string EditorLineHeight string EditorTheme string EditorTabSize string }{} if err := json.NewDecoder(r.Body).Decode(&args); err != nil { logger.Error(err) result.Succ = false return } user.FontFamily = args.FontFamily user.FontSize = args.FontSize user.GoFormat = args.GoFmt user.Keymap = args.Keymap // XXX: disallow change workspace at present // user.Workspace = args.Workspace if user.Password != args.Password { user.Password = conf.Salt(args.Password, user.Salt) } user.Email = args.Email hash := md5.New() hash.Write([]byte(user.Email)) user.Gravatar = hex.EncodeToString(hash.Sum(nil)) user.Locale = args.Locale user.Theme = args.Theme user.Editor.FontFamily = args.EditorFontFamily user.Editor.FontSize = args.EditorFontSize user.Editor.LineHeight = args.EditorLineHeight user.Editor.Theme = args.EditorTheme user.Editor.TabSize = args.EditorTabSize conf.UpdateCustomizedConf(username) now := time.Now().UnixNano() user.Lived = now user.Updated = now result.Succ = user.Save() }
// SignUpUserHandler handles request of registering user. func SignUpUserHandler(w http.ResponseWriter, r *http.Request) { if "GET" == r.Method { // show the user sign up page firstUserWorkspace := conf.GetUserWorkspace(conf.Users[0].Name) dir := filepath.Dir(firstUserWorkspace) model := map[string]interface{}{"conf": conf.Wide, "i18n": i18n.GetAll(conf.Wide.Locale), "locale": conf.Wide.Locale, "ver": conf.WideVersion, "dir": dir, "pathSeparator": conf.PathSeparator, "year": time.Now().Year()} t, err := template.ParseFiles("views/sign_up.html") if nil != err { logger.Error(err) http.Error(w, err.Error(), 500) return } t.Execute(w, model) return } // non-GET request as add user request result := util.NewResult() defer util.RetResult(w, r, result) var args map[string]interface{} if err := json.NewDecoder(r.Body).Decode(&args); err != nil { logger.Error(err) result.Succ = false return } username := args["username"].(string) password := args["password"].(string) email := args["email"].(string) msg := addUser(username, password, email) if userCreated != msg { result.Succ = false result.Msg = msg return } // create a HTTP session httpSession, _ := HTTPSession.Get(r, "wide-session") httpSession.Values["username"] = username httpSession.Values["id"] = strconv.Itoa(rand.Int()) httpSession.Options.MaxAge = conf.Wide.HTTPSessionMaxAge if "" != conf.Wide.Context { httpSession.Options.Path = conf.Wide.Context } httpSession.Save(r, w) }
// SaveHandler handles request of Playground code save. func SaveHandler(w http.ResponseWriter, r *http.Request) { result := util.NewResult() defer util.RetResult(w, r, result) session, _ := session.HTTPSession.Get(r, "wide-session") if session.IsNew { http.Error(w, "Forbidden", http.StatusForbidden) return } var args map[string]interface{} if err := json.NewDecoder(r.Body).Decode(&args); err != nil { logger.Error(err) result.Succ = false return } code := args["code"].(string) // Step1. format code cmd := exec.Command("gofmt") stdin, err := cmd.StdinPipe() if nil != err { logger.Error(err) result.Succ = false return } io.WriteString(stdin, code) stdin.Close() bytes, _ := cmd.Output() output := string(bytes) if "" != output { code = string(output) } data := map[string]interface{}{} result.Data = &data data["code"] = code // Step2. generate file name hasher := md5.New() hasher.Write([]byte(code)) fileName := hex.EncodeToString(hasher.Sum(nil)) fileName += ".go" data["fileName"] = fileName // Step3. write file filePath := filepath.Clean(conf.Wide.Playground + "/" + fileName) fout, err := os.Create(filePath) fout.WriteString(code) if err := fout.Close(); nil != err { logger.Error(err) result.Succ = false return } }
// FindUsagesHandler handles request of finding usages. func FindUsagesHandler(w http.ResponseWriter, r *http.Request) { result := util.NewResult() defer util.RetResult(w, r, result) session, _ := session.HTTPSession.Get(r, "wide-session") if session.IsNew { http.Error(w, "Forbidden", http.StatusForbidden) return } username := session.Values["username"].(string) var args map[string]interface{} if err := json.NewDecoder(r.Body).Decode(&args); err != nil { logger.Error(err) http.Error(w, err.Error(), 500) return } filePath := args["path"].(string) curDir := filepath.Dir(filePath) filename := filepath.Base(filePath) fout, err := os.Create(filePath) if nil != err { logger.Error(err) result.Succ = false return } code := args["code"].(string) fout.WriteString(code) if err := fout.Close(); nil != err { logger.Error(err) result.Succ = false return } line := int(args["cursorLine"].(float64)) ch := int(args["cursorCh"].(float64)) offset := getCursorOffset(code, line, ch) logger.Tracef("offset [%d]", offset) ideStub := util.Go.GetExecutableInGOBIN("gotools") argv := []string{"types", "-pos", filename + ":" + strconv.Itoa(offset), "-use", "."} cmd := exec.Command(ideStub, argv...) cmd.Dir = curDir setCmdEnv(cmd, username) output, err := cmd.CombinedOutput() if nil != err { logger.Error(err) http.Error(w, err.Error(), 500) return } out := strings.TrimSpace(string(output)) if "" == out { result.Succ = false return } founds := strings.Split(out, "\n") usages := []*file.Snippet{} for _, found := range founds { found = strings.TrimSpace(found) part := found[:strings.LastIndex(found, ":")] cursorSep := strings.LastIndex(part, ":") path := filepath.ToSlash(found[:cursorSep]) cursorLine, _ := strconv.Atoi(found[cursorSep+1 : strings.LastIndex(found, ":")]) cursorCh, _ := strconv.Atoi(found[strings.LastIndex(found, ":")+1:]) usage := &file.Snippet{Path: path, Line: cursorLine, Ch: cursorCh, Contents: []string{""}} usages = append(usages, usage) } result.Data = usages }
// GetExprInfoHandler handles request of getting expression infomation. func GetExprInfoHandler(w http.ResponseWriter, r *http.Request) { result := util.NewResult() defer util.RetResult(w, r, result) session, _ := session.HTTPSession.Get(r, "wide-session") username := session.Values["username"].(string) var args map[string]interface{} if err := json.NewDecoder(r.Body).Decode(&args); err != nil { logger.Error(err) http.Error(w, err.Error(), 500) return } path := args["path"].(string) curDir := filepath.Dir(path) filename := filepath.Base(path) fout, err := os.Create(path) if nil != err { logger.Error(err) result.Succ = false return } code := args["code"].(string) fout.WriteString(code) if err := fout.Close(); nil != err { logger.Error(err) result.Succ = false return } line := int(args["cursorLine"].(float64)) ch := int(args["cursorCh"].(float64)) offset := getCursorOffset(code, line, ch) logger.Tracef("offset [%d]", offset) ideStub := util.Go.GetExecutableInGOBIN("gotools") argv := []string{"types", "-pos", filename + ":" + strconv.Itoa(offset), "-info", "."} cmd := exec.Command(ideStub, argv...) cmd.Dir = curDir setCmdEnv(cmd, username) output, err := cmd.CombinedOutput() if nil != err { logger.Error(err) http.Error(w, err.Error(), 500) return } exprInfo := strings.TrimSpace(string(output)) if "" == exprInfo { result.Succ = false return } result.Data = exprInfo }
// GetOutlineHandler gets outfile of a go file. func GetOutlineHandler(w http.ResponseWriter, r *http.Request) { result := util.NewResult() defer util.RetResult(w, r, result) var args map[string]interface{} if err := json.NewDecoder(r.Body).Decode(&args); err != nil { logger.Error(err) result.Succ = false return } code := args["code"].(string) fset := token.NewFileSet() f, err := parser.ParseFile(fset, "", code, 0) if err != nil { result.Succ = false return } data := map[string]interface{}{} result.Data = &data // ast.Print(fset, f) line, ch := getCursor(code, int(f.Name.Pos())) data["package"] = &element{Name: f.Name.Name, Line: line, Ch: ch} imports := []*element{} for _, astImport := range f.Imports { line, ch := getCursor(code, int(astImport.Path.Pos())) imports = append(imports, &element{Name: astImport.Path.Value, Line: line, Ch: ch}) } data["imports"] = imports funcDecls := []*element{} varDecls := []*element{} constDecls := []*element{} structDecls := []*element{} interfaceDecls := []*element{} typeDecls := []*element{} for _, decl := range f.Decls { switch decl.(type) { case *ast.FuncDecl: funcDecl := decl.(*ast.FuncDecl) line, ch := getCursor(code, int(funcDecl.Name.Pos())) funcDecls = append(funcDecls, &element{Name: funcDecl.Name.Name, Line: line, Ch: ch}) case *ast.GenDecl: genDecl := decl.(*ast.GenDecl) for _, spec := range genDecl.Specs { switch genDecl.Tok { case token.VAR: variableSpec := spec.(*ast.ValueSpec) for _, varName := range variableSpec.Names { line, ch := getCursor(code, int(varName.Pos())) varDecls = append(varDecls, &element{Name: varName.Name, Line: line, Ch: ch}) } case token.TYPE: typeSpec := spec.(*ast.TypeSpec) line, ch := getCursor(code, int(typeSpec.Pos())) switch typeSpec.Type.(type) { case *ast.StructType: structDecls = append(structDecls, &element{Name: typeSpec.Name.Name, Line: line, Ch: ch}) case *ast.InterfaceType: interfaceDecls = append(interfaceDecls, &element{Name: typeSpec.Name.Name, Line: line, Ch: ch}) case *ast.Ident: typeDecls = append(typeDecls, &element{Name: typeSpec.Name.Name, Line: line, Ch: ch}) } case token.CONST: constSpec := spec.(*ast.ValueSpec) for _, constName := range constSpec.Names { line, ch := getCursor(code, int(constName.Pos())) constDecls = append(constDecls, &element{Name: constName.Name, Line: line, Ch: ch}) } } } } } data["funcDecls"] = funcDecls data["varDecls"] = varDecls data["constDecls"] = constDecls data["structDecls"] = structDecls data["interfaceDecls"] = interfaceDecls data["typeDecls"] = typeDecls }
// BuildHandler handles request of building. func BuildHandler(w http.ResponseWriter, r *http.Request) { result := util.NewResult() defer util.RetResult(w, r, result) httpSession, _ := session.HTTPSession.Get(r, "wide-session") if httpSession.IsNew { http.Error(w, "Forbidden", http.StatusForbidden) return } username := httpSession.Values["username"].(string) locale := conf.GetUser(username).Locale var args map[string]interface{} if err := json.NewDecoder(r.Body).Decode(&args); err != nil { logger.Error(err) result.Succ = false return } sid := args["sid"].(string) filePath := args["file"].(string) if util.Go.IsAPI(filePath) || !session.CanAccess(username, filePath) { http.Error(w, "Forbidden", http.StatusForbidden) return } curDir := filepath.Dir(filePath) fout, err := os.Create(filePath) if nil != err { logger.Error(err) result.Succ = false return } code := args["code"].(string) fout.WriteString(code) if err := fout.Close(); nil != err { logger.Error(err) result.Succ = false return } suffix := "" if util.OS.IsWindows() { suffix = ".exe" } cmd := exec.Command("go", "build") cmd.Dir = curDir setCmdEnv(cmd, username) executable := filepath.Base(curDir) + suffix executable = filepath.Join(curDir, executable) stdout, err := cmd.StdoutPipe() if nil != err { logger.Error(err) result.Succ = false return } stderr, err := cmd.StderrPipe() if nil != err { logger.Error(err) result.Succ = false return } if !result.Succ { return } channelRet := map[string]interface{}{} if nil != session.OutputWS[sid] { // display "START [go build]" in front-end browser channelRet["output"] = "<span class='start-build'>" + i18n.Get(locale, "start-build").(string) + "</span>\n" channelRet["cmd"] = "start-build" wsChannel := session.OutputWS[sid] err := wsChannel.WriteJSON(&channelRet) if nil != err { logger.Error(err) return } wsChannel.Refresh() } reader := bufio.NewReader(io.MultiReader(stdout, stderr)) if err := cmd.Start(); nil != err { logger.Error(err) result.Succ = false return } go func(runningId int) { defer util.Recover() defer cmd.Wait() // logger.Debugf("User [%s, %s] is building [id=%d, dir=%s]", username, sid, runningId, curDir) // read all buf, _ := ioutil.ReadAll(reader) channelRet := map[string]interface{}{} channelRet["cmd"] = "build" channelRet["executable"] = executable if 0 == len(buf) { // build success channelRet["nextCmd"] = args["nextCmd"] channelRet["output"] = "<span class='build-succ'>" + i18n.Get(locale, "build-succ").(string) + "</span>\n" go func() { // go install, for subsequent gocode lib-path defer util.Recover() cmd := exec.Command("go", "install") cmd.Dir = curDir setCmdEnv(cmd, username) out, _ := cmd.CombinedOutput() if len(out) > 0 { logger.Warn(string(out)) } }() } else { // build error // build gutter lint errOut := string(buf) lines := strings.Split(errOut, "\n") // path process var errOutWithPath string for _, line := range lines { errOutWithPath += parsePath(curDir, line) + "\n" } channelRet["output"] = "<span class='build-error'>" + i18n.Get(locale, "build-error").(string) + "</span>\n" + "<span class='stderr'>" + errOutWithPath + "</span>" // lint process if lines[0][0] == '#' { lines = lines[1:] // skip the first line } lints := []*Lint{} for _, line := range lines { if len(line) < 1 { continue } if line[0] == '\t' { // append to the last lint last := len(lints) msg := lints[last-1].Msg msg += line lints[last-1].Msg = msg continue } file := line[:strings.Index(line, ":")] left := line[strings.Index(line, ":")+1:] index := strings.Index(left, ":") lineNo := 0 msg := left if index >= 0 { lineNo, err = strconv.Atoi(left[:index]) if nil != err { continue } msg = left[index+2:] } lint := &Lint{ File: filepath.Join(curDir, file), LineNo: lineNo - 1, Severity: lintSeverityError, Msg: msg, } lints = append(lints, lint) } channelRet["lints"] = lints } if nil != session.OutputWS[sid] { // logger.Debugf("User [%s, %s] 's build [id=%d, dir=%s] has done", username, sid, runningId, curDir) wsChannel := session.OutputWS[sid] err := wsChannel.WriteJSON(&channelRet) if nil != err { logger.Warn(err) } wsChannel.Refresh() } }(rand.Int()) }
// GetFileHandler handles request of opening file by editor. func GetFileHandler(w http.ResponseWriter, r *http.Request) { httpSession, _ := session.HTTPSession.Get(r, "wide-session") if httpSession.IsNew { http.Error(w, "Forbidden", http.StatusForbidden) return } username := httpSession.Values["username"].(string) result := util.NewResult() defer util.RetResult(w, r, result) var args map[string]interface{} if err := json.NewDecoder(r.Body).Decode(&args); err != nil { logger.Error(err) result.Succ = false return } path := args["path"].(string) if !util.Go.IsAPI(path) && !session.CanAccess(username, path) { http.Error(w, "Forbidden", http.StatusForbidden) return } size := util.File.GetFileSize(path) if size > 5242880 { // 5M result.Succ = false result.Msg = "This file is too large to open :(" return } data := map[string]interface{}{} buf, _ := ioutil.ReadFile(path) extension := filepath.Ext(path) if util.File.IsImg(extension) { // image file will be open in a browser tab data["mode"] = "img" username := conf.GetOwner(path) if "" == username { logger.Warnf("The path [%s] has no owner") data["path"] = "" return } user := conf.GetUser(username) data["path"] = "/workspace/" + user.Name + "/" + strings.Replace(path, user.GetWorkspace(), "", 1) return } content := string(buf) if util.File.IsBinary(content) { result.Succ = false result.Msg = "Can't open a binary file :(" } else { data["content"] = content data["path"] = path } result.Data = data }
// GoInstallHandler handles request of go install. func GoInstallHandler(w http.ResponseWriter, r *http.Request) { result := util.NewResult() defer util.RetResult(w, r, result) httpSession, _ := session.HTTPSession.Get(r, "wide-session") if httpSession.IsNew { http.Error(w, "Forbidden", http.StatusForbidden) return } username := httpSession.Values["username"].(string) locale := conf.GetUser(username).Locale var args map[string]interface{} if err := json.NewDecoder(r.Body).Decode(&args); err != nil { logger.Error(err) result.Succ = false return } sid := args["sid"].(string) filePath := args["file"].(string) curDir := filepath.Dir(filePath) cmd := exec.Command("go", "install") cmd.Dir = curDir setCmdEnv(cmd, username) logger.Debugf("go install %s", curDir) stdout, err := cmd.StdoutPipe() if nil != err { logger.Error(err) result.Succ = false return } stderr, err := cmd.StderrPipe() if nil != err { logger.Error(err) result.Succ = false return } if !result.Succ { return } channelRet := map[string]interface{}{} if nil != session.OutputWS[sid] { // display "START [go install]" in front-end browser channelRet["output"] = "<span class='start-install'>" + i18n.Get(locale, "start-install").(string) + "</span>\n" channelRet["cmd"] = "start-install" wsChannel := session.OutputWS[sid] err := wsChannel.WriteJSON(&channelRet) if nil != err { logger.Error(err) return } wsChannel.Refresh() } reader := bufio.NewReader(io.MultiReader(stdout, stderr)) if err := cmd.Start(); nil != err { logger.Error(err) result.Succ = false return } go func(runningId int) { defer util.Recover() defer cmd.Wait() logger.Debugf("User [%s, %s] is running [go install] [id=%d, dir=%s]", username, sid, runningId, curDir) // read all buf, _ := ioutil.ReadAll(reader) channelRet := map[string]interface{}{} channelRet["cmd"] = "go install" if 0 != len(buf) { // build error // build gutter lint errOut := string(buf) lines := strings.Split(errOut, "\n") if lines[0][0] == '#' { lines = lines[1:] // skip the first line } lints := []*Lint{} for _, line := range lines { if len(line) < 1 { continue } if line[0] == '\t' { // append to the last lint last := len(lints) msg := lints[last-1].Msg msg += line lints[last-1].Msg = msg continue } file := line[:strings.Index(line, ":")] left := line[strings.Index(line, ":")+1:] index := strings.Index(left, ":") lineNo := 0 msg := left if index >= 0 { lineNo, _ = strconv.Atoi(left[:index]) msg = left[index+2:] } lint := &Lint{ File: file, LineNo: lineNo - 1, Severity: lintSeverityError, Msg: msg, } lints = append(lints, lint) } channelRet["lints"] = lints channelRet["output"] = "<span class='install-error'>" + i18n.Get(locale, "install-error").(string) + "</span>\n" + errOut } else { channelRet["output"] = "<span class='install-succ'>" + i18n.Get(locale, "install-succ").(string) + "</span>\n" } if nil != session.OutputWS[sid] { logger.Debugf("User [%s, %s] 's running [go install] [id=%d, dir=%s] has done", username, sid, runningId, curDir) wsChannel := session.OutputWS[sid] err := wsChannel.WriteJSON(&channelRet) if nil != err { logger.Warn(err) } wsChannel.Refresh() } }(rand.Int()) }
// Clone handles request of git clone. func CloneHandler(w http.ResponseWriter, r *http.Request) { result := util.NewResult() defer util.RetResult(w, r, result) httpSession, _ := session.HTTPSession.Get(r, "wide-session") if httpSession.IsNew { http.Error(w, "Forbidden", http.StatusForbidden) return } username := httpSession.Values["username"].(string) locale := conf.GetUser(username).Locale var args map[string]interface{} if err := json.NewDecoder(r.Body).Decode(&args); err != nil { logger.Error(err) result.Succ = false return } sid := args["sid"].(string) path := args["path"].(string) repository := args["repository"].(string) cmd := exec.Command("git", "clone", repository) cmd.Dir = path stdout, err := cmd.StdoutPipe() if nil != err { logger.Error(err) result.Succ = false return } stderr, err := cmd.StderrPipe() if nil != err { logger.Error(err) result.Succ = false return } if !result.Succ { return } channelRet := map[string]interface{}{} if nil != session.OutputWS[sid] { // display "START [git clone]" in front-end browser channelRet["output"] = "<span class='start-get'>" + i18n.Get(locale, "start-git_clone").(string) + "</span>\n" channelRet["cmd"] = "start-git_clone" wsChannel := session.OutputWS[sid] err := wsChannel.WriteJSON(&channelRet) if nil != err { logger.Warn(err) return } wsChannel.Refresh() } reader := bufio.NewReader(io.MultiReader(stdout, stderr)) if err := cmd.Start(); nil != err { logger.Error(err) result.Succ = false return } go func(runningId int) { defer util.Recover() defer cmd.Wait() logger.Debugf("User [%s, %s] is running [git clone] [runningId=%d]", username, sid, runningId) channelRet := map[string]interface{}{} channelRet["cmd"] = "git clone" // read all buf, err := ioutil.ReadAll(reader) if nil != err { logger.Warn(err) // TODO: handle clone error } logger.Debugf("User [%s, %s] 's running [git clone] [runningId=%d] has done: %s", username, sid, runningId, string(buf)) channelRet["output"] = "<span class='get-succ'>" + i18n.Get(locale, "git_clone-done").(string) + "</span>\n" if nil != session.OutputWS[sid] { wsChannel := session.OutputWS[sid] err := wsChannel.WriteJSON(&channelRet) if nil != err { logger.Warn(err) } wsChannel.Refresh() } }(rand.Int()) }