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  }