github.com/iotexproject/iotex-core@v1.14.1-rc1/ioctl/cmd/contract/contractshare.go (about) 1 // Copyright (c) 2022 IoTeX Foundation 2 // This source code is provided 'as is' and no warranties are given as to title or non-infringement, merchantability 3 // or fitness for purpose and, to the extent permitted by law, all liability for your use of the code is disclaimed. 4 // This source code is governed by Apache License 2.0 that can be found in the LICENSE file. 5 6 package contract 7 8 import ( 9 "flag" 10 "log" 11 "net/http" 12 "os" 13 "path/filepath" 14 "reflect" 15 "strings" 16 17 "github.com/gorilla/websocket" 18 "github.com/spf13/cobra" 19 20 "github.com/iotexproject/iotex-core/ioctl/config" 21 "github.com/iotexproject/iotex-core/ioctl/output" 22 ) 23 24 var ( 25 _iotexIDE string 26 _fileList []string 27 _givenPath string 28 29 _addr = flag.String("_addr", "localhost:65520", "http service _address") 30 31 _upgrade = websocket.Upgrader{ 32 CheckOrigin: func(r *http.Request) bool { 33 return _iotexIDE == r.Header["Origin"][0] 34 }, 35 } 36 ) 37 38 // Multi-language support 39 var ( 40 _contractShareCmdUses = map[config.Language]string{ 41 config.English: "share LOCAL_FOLDER_PATH [--iotex-ide YOUR_IOTEX_IDE_URL_INSTANCE]", 42 config.Chinese: "share 本地文件路径 [--iotex-ide 你的IOTEX_IDE的URL]", 43 } 44 _contractShareCmdShorts = map[config.Language]string{ 45 config.English: "share a folder from your local computer to the IoTex smart contract dev.(default to https://ide.iotex.io)", 46 config.Chinese: "share 将本地文件夹内容分享到IoTex在线智能合约IDE(默认为https://ide.iotex.io)", 47 } 48 _flagIoTexIDEUrlUsage = map[config.Language]string{ 49 config.English: "set your IoTeX IDE url instance", 50 config.Chinese: "设置自定义IoTeX IDE Url", 51 } 52 ) 53 54 // _contractShareCmd represents the contract share command 55 var _contractShareCmd = &cobra.Command{ 56 Use: config.TranslateInLang(_contractShareCmdUses, config.UILanguage), 57 Short: config.TranslateInLang(_contractShareCmdShorts, config.UILanguage), 58 Args: cobra.ExactArgs(1), 59 RunE: func(cmd *cobra.Command, args []string) error { 60 cmd.SilenceUsage = true 61 err := share(args) 62 return output.PrintError(err) 63 }, 64 } 65 66 func init() { 67 _contractShareCmd.Flags().StringVar(&_iotexIDE, "iotex-ide", "https://ide.iotex.io", config.TranslateInLang(_flagIoTexIDEUrlUsage, config.UILanguage)) 68 } 69 70 type requestMessage struct { 71 ID string `json:"id"` 72 Action string `json:"action"` 73 Key string `json:"key"` 74 Payload []interface{} `json:"payload"` 75 } 76 77 type responseMessage struct { 78 ID string `json:"id"` 79 Action string `json:"action"` 80 Key string `json:"key"` 81 Payload interface{} `json:"payload"` 82 } 83 84 func isDir(path string) bool { 85 s, err := os.Stat(path) 86 if err != nil { 87 return false 88 } 89 return s.IsDir() 90 } 91 92 func isReadOnly(path string) bool { 93 var readOnly = false 94 file, err := os.OpenFile(filepath.Clean(path), os.O_WRONLY, 0600) 95 if err != nil { 96 if os.IsPermission(err) { 97 log.Println("Error: Write permission denied.") 98 } 99 if os.IsNotExist(err) { 100 log.Println("Error: File doesn't exist.") 101 } 102 readOnly = true 103 } 104 if err = file.Close(); err != nil { 105 log.Printf("fialed to close file: %v", err) 106 } 107 return readOnly 108 } 109 110 func isExist(path string) bool { 111 _, err := os.Stat(path) 112 if err != nil { 113 if os.IsNotExist(err) { 114 return false 115 } 116 } 117 return true 118 } 119 120 func rename(oldPath string, newPath string, c chan bool) { 121 if isExist(oldPath) { 122 if err := os.Rename(oldPath, newPath); err != nil { 123 log.Println("Rename file failed: ", err) 124 } 125 c <- false 126 } 127 c <- true 128 } 129 130 func share(args []string) error { 131 _givenPath = filepath.Clean(args[0]) 132 if len(_givenPath) == 0 { 133 return output.NewError(output.ReadFileError, "failed to get directory", nil) 134 } 135 136 if !isDir(_givenPath) { 137 return output.NewError(output.InputError, "given file rather than directory", nil) 138 } 139 140 if len(_iotexIDE) == 0 { 141 return output.NewError(output.FlagError, "failed to get IoTeX ide url instance", nil) 142 } 143 144 if err := filepath.Walk(_givenPath, func(path string, info os.FileInfo, err error) error { 145 if !isDir(path) { 146 relPath, err := filepath.Rel(_givenPath, path) 147 if err != nil { 148 return err 149 } 150 151 if !strings.HasPrefix(relPath, ".") { 152 _fileList = append(_fileList, relPath) 153 } 154 } 155 return nil 156 }); err != nil { 157 return output.NewError(output.ReadFileError, "failed to walk directory", err) 158 } 159 160 log.Printf("Listening on 127.0.0.1:65520, Please open your IDE ( %s ) to connect to local files", _iotexIDE) 161 162 http.HandleFunc("/", func(writer http.ResponseWriter, request *http.Request) { 163 conn, err := _upgrade.Upgrade(writer, request, nil) 164 165 if err != nil { 166 log.Println("websocket error:", err) 167 return 168 } 169 170 for { 171 var request requestMessage 172 var response responseMessage 173 174 if err := conn.ReadJSON(&request); err != nil { 175 log.Println("read json error: ", err) 176 return 177 } 178 response.ID = request.ID 179 response.Action = "response" 180 response.Key = request.Key 181 182 switch request.Key { 183 184 case "handshake": 185 response.Payload = nil 186 if err := conn.WriteJSON(&response); err != nil { 187 log.Println("send handshake response", err) 188 } 189 case "list": 190 payload := make(map[string]bool) 191 for _, ele := range _fileList { 192 payload[ele] = isReadOnly(filepath.Join(_givenPath, ele)) 193 } 194 response.Payload = payload 195 if err := conn.WriteJSON(&response); err != nil { 196 log.Println("send list response", err) 197 } 198 case "get": 199 payload := map[string]interface{}{} 200 201 t := request.Payload 202 getPayload := reflect.ValueOf(t).Index(0).Interface().(map[string]interface{}) 203 getPayloadPath, err := cleanPath(getPayload["path"].(string)) 204 if err != nil { 205 log.Println("clean file path failed: ", err) 206 break 207 } 208 getPayloadPath = filepath.Join(_givenPath, getPayloadPath) 209 upload, err := os.ReadFile(filepath.Clean(getPayloadPath)) 210 if err != nil { 211 log.Println("read file failed: ", err) 212 break 213 } 214 payload["content"] = string(upload) 215 payload["readonly"] = isReadOnly(getPayloadPath) 216 response.Payload = payload 217 if err := conn.WriteJSON(&response); err != nil { 218 log.Println("send get response: ", err) 219 break 220 } 221 log.Printf("share: %s\n", easpcapeString(getPayloadPath)) 222 223 case "rename": 224 c := make(chan bool) 225 t := request.Payload 226 renamePayload := reflect.ValueOf(t).Index(0).Interface().(map[string]interface{}) 227 oldRenamePath, err := cleanPath(renamePayload["oldPath"].(string)) 228 if err != nil { 229 log.Println("clean file path failed: ", err) 230 break 231 } 232 newRenamePath, err := cleanPath(renamePayload["newPath"].(string)) 233 if err != nil { 234 log.Println("clean file path failed: ", err) 235 break 236 } 237 oldRenamePath = filepath.Join(_givenPath, oldRenamePath) 238 newRenamePath = filepath.Join(_givenPath, newRenamePath) 239 go rename(oldRenamePath, newRenamePath, c) 240 response.Payload = <-c 241 if err := conn.WriteJSON(&response); err != nil { 242 log.Println("send get response: ", err) 243 break 244 } 245 log.Printf("rename: %s to %s\n", easpcapeString(oldRenamePath), easpcapeString(newRenamePath)) 246 247 case "set": 248 t := request.Payload 249 setPayload := reflect.ValueOf(t).Index(0).Interface().(map[string]interface{}) 250 content := setPayload["content"].(string) 251 setPath, err := cleanPath(setPayload["path"].(string)) 252 if err != nil { 253 log.Println("clean file path failed: ", err) 254 break 255 } 256 setPath = filepath.Join(_givenPath, setPath) 257 if err := os.MkdirAll(filepath.Dir(setPath), 0750); err != nil { 258 log.Println("mkdir failed: ", err) 259 break 260 } 261 if err := os.WriteFile(setPath, []byte(content), 0600); err != nil { 262 log.Println("set file failed: ", err) 263 break 264 } 265 if err := conn.WriteJSON(&response); err != nil { 266 log.Println("send set response: ", err) 267 break 268 } 269 log.Printf("set: %s\n", easpcapeString(setPath)) 270 271 default: 272 log.Printf("Don't support this IDE yet. Can not handle websocket method: %s\n", easpcapeString(request.Key)) 273 274 } 275 } 276 }) 277 log.Fatal(http.ListenAndServe(*_addr, nil)) 278 279 return nil 280 } 281 282 func easpcapeString(str string) string { 283 escaped := strings.Replace(str, "\n", "", -1) 284 return strings.Replace(escaped, "\r", "", -1) 285 } 286 287 func cleanPath(path string) (string, error) { 288 path = filepath.Clean(filepath.Join("/", path)) 289 real, err := filepath.Rel("/", path) 290 if err != nil { 291 return "", err 292 } 293 return real, nil 294 }