33package cmd
44
55import (
6- "errors"
76 "fmt"
8- "io/ioutil"
97 stdlog "log"
108 "os"
11- "regexp"
12- "strconv"
13- "strings"
149 "time"
15- "unicode"
1610
17- "github.com/AlecAivazis/survey/v2"
1811 "github.com/loophole/cli/config"
1912 "github.com/loophole/cli/internal/pkg/cache"
20- "github.com/loophole/cli/internal/pkg/closehandler"
21- "github.com/loophole/cli/internal/pkg/communication"
13+ "github.com/loophole/cli/internal/pkg/prompts"
2214 "github.com/mattn/go-colorable"
2315 "github.com/rs/zerolog"
2416 "github.com/rs/zerolog/log"
@@ -30,239 +22,18 @@ var signalChan chan os.Signal
3022
3123var alreadyRunning bool
3224
33- //Possible answers for prompts and error messages
34- const (
35- AnswerTunnelTypeHTTP string = "Expose an HTTP Port"
36- AnswerTunnelTypePath string = "Expose a local path"
37- AnswerTunnelTypeWebDAV string = "Expose a local path with WebDAV"
38- AnswerYes string = "Yes"
39- AnswerNo string = "No"
40- PortRangeErrorMsg string = "port must be between 0-65535"
41- PathValidityErrorMsg string = "enter an existing path without any quotation marks"
42- )
43-
4425var rootCmd = & cobra.Command {
4526 Use : "loophole" ,
4627 Short : "Loophole - End to end TLS encrypted TCP communication between you and your clients" ,
4728 Long : "Loophole - End to end TLS encrypted TCP communication between you and your clients" ,
4829 Run : func (cmd * cobra.Command , args []string ) {
4930 if ! alreadyRunning {
5031 alreadyRunning = true
51- interactivePrompt ( )
32+ prompts . StartInteractivePrompt ( httpCmd . Root (), signalChan )
5233 }
5334 },
5435}
5536
56- func getPortPrompt () []* survey.Question {
57- return []* survey.Question {
58- {
59- Name : "port" ,
60- Prompt : & survey.Input {Message : "Please enter the http port you want to expose: " },
61- Validate : func (val interface {}) error {
62- if port , ok := val .(string ); ! ok {
63- return errors .New (PortRangeErrorMsg )
64- } else { //else is necessary here to keep access to port
65- n , err := strconv .Atoi (port )
66- if err != nil {
67- return errors .New (PortRangeErrorMsg )
68- }
69- if (n < 0 ) || (n > 65535 ) {
70- return errors .New (PortRangeErrorMsg )
71- }
72- }
73-
74- return nil
75- },
76- },
77- }
78- }
79-
80- func getPathPrompt () []* survey.Question {
81- return []* survey.Question {
82- {
83- Name : "path" ,
84- Prompt : & survey.Input {Message : "Please enter the path you want to expose: " },
85- Validate : func (val interface {}) error {
86- if path , ok := val .(string ); ! ok {
87- return errors .New (PathValidityErrorMsg )
88- } else { //else is necessary here to keep access to path
89- _ , err := os .Stat (path )
90- if err == nil {
91- return nil
92- }
93- return errors .New (PathValidityErrorMsg )
94- }
95- },
96- },
97- }
98- }
99-
100- func getLastArgsPrompt (lastArgs string ) * survey.Select {
101- return & survey.Select {
102- Message : fmt .Sprintf ("Your last settings were: '%s', would you like to reuse them?" , lastArgs ),
103- Options : []string {AnswerYes , AnswerNo },
104- }
105- }
106-
107- func getInitialPrompt () * survey.Select {
108- return & survey.Select {
109- Message : "Welcome to loophole. What do you want to do?" ,
110- Options : []string {AnswerTunnelTypeHTTP , AnswerTunnelTypePath , AnswerTunnelTypeWebDAV },
111- }
112- }
113-
114- func askBasicAuth () string {
115- res := ""
116- prompt := & survey.Select {
117- Message : "Do you want to secure your tunnel using a username and password?" ,
118- Options : []string {AnswerNo , AnswerYes },
119- }
120- var usernamePrompt = []* survey.Question {
121- {
122- Name : "username" ,
123- Prompt : & survey.Input {Message : "Please enter the username you want to use: " }, //not asking for a password since it's already implemented in virtual-serve
124- },
125- }
126- err := survey .AskOne (prompt , & res )
127- if err != nil {
128- signalChan <- nil
129- }
130- if res == AnswerYes {
131- err = survey .Ask (usernamePrompt , & res )
132- if err != nil {
133- os .Exit (0 )
134- return err .Error ()
135- }
136- } else {
137- return ""
138- }
139- return res
140- }
141-
142- func askHostname () string {
143- res := ""
144- prompt := & survey.Select {
145- Message : "Do you want to use a custom hostname?" ,
146- Options : []string {AnswerNo , AnswerYes },
147- }
148- var hostnamePrompt = []* survey.Question {
149- {
150- Name : "hostname" ,
151- Prompt : & survey.Input {Message : "Please enter the hostname you want to use: " },
152- Validate : func (val interface {}) error {
153- var validChars = regexp .MustCompile (`^[a-z][a-z0-9]{0,30}$` ).MatchString
154- if hostname , ok := val .(string ); ! ok || len (hostname ) > 31 || ! validChars (hostname ) || ! unicode .IsLetter (rune (hostname [0 ])) {
155- return errors .New ("hostname must be up to 31 characters, may only contain lowercase letters and numbers and must start with a letter" )
156- }
157-
158- return nil
159- },
160- },
161- }
162- err := survey .AskOne (prompt , & res )
163- if err != nil {
164- signalChan <- nil
165- }
166- if res == AnswerYes {
167- err = survey .Ask (hostnamePrompt , & res )
168- if err != nil {
169- os .Exit (0 )
170- return err .Error ()
171- }
172- } else {
173- return ""
174- }
175- return res
176- }
177-
178- func interactivePrompt () {
179- argPath := cache .GetLocalStorageFile ("lastArgs" , "logs" )
180- var lastArgs string = ""
181- if _ , err := os .Stat (argPath ); err == nil {
182- argBytes , err := ioutil .ReadFile (argPath )
183- if err != nil {
184- communication .Fatal ("Error reading last used arguments:" + err .Error ())
185- }
186- lastArgs = string (argBytes )
187- }
188- var lastArgsPrompt = getLastArgsPrompt (lastArgs )
189- var initialPrompt = getInitialPrompt ()
190- var portPrompt = getPortPrompt ()
191- var pathPrompt = getPathPrompt ()
192-
193- var res string
194- var exposePort int
195- var exposePath string
196- var arguments []string
197-
198- cmd := httpCmd .Root () //TODO: find a better way to access rootCMD
199-
200- if lastArgs != "" {
201- err := survey .AskOne (lastArgsPrompt , & res )
202- if err != nil {
203- signalChan <- nil
204- }
205- if res == AnswerYes {
206- cmd .SetArgs (strings .Split (lastArgs , " " )) //needs validation
207- cmd .Execute ()
208- os .Exit (1 )
209- }
210- }
211- err := survey .AskOne (initialPrompt , & res )
212-
213- if err != nil {
214- signalChan <- nil
215- }
216- switch res {
217- case AnswerTunnelTypeHTTP :
218- err = survey .Ask (portPrompt , & exposePort )
219- if err != nil {
220- signalChan <- nil
221- }
222- arguments = []string {"http" , strconv .Itoa (exposePort )}
223- case AnswerTunnelTypePath :
224- err = survey .Ask (pathPrompt , & exposePath )
225- if err != nil {
226- signalChan <- nil
227- }
228- arguments = []string {"path" , exposePath }
229- case AnswerTunnelTypeWebDAV :
230- err = survey .Ask (pathPrompt , & exposePath )
231- if err != nil {
232- signalChan <- nil
233- }
234- arguments = []string {"webdav" , exposePath }
235- }
236-
237- hostname := askHostname ()
238- if hostname != "" {
239- arguments = append (arguments , "--hostname" , hostname )
240- }
241- basicAuth := askBasicAuth ()
242- if basicAuth != "" {
243- arguments = append (arguments , "--basic-auth-username" , basicAuth )
244- }
245- cmd .SetArgs (arguments )
246-
247- var argumentsWithQuotes []string
248- //setting the path argument in code doesn't work when it contains quotation marks,
249- //but they do need to be there when entered as a standalone command in a command line if the path contains spaces
250- //so, we give a copy of the arguments to the closehandler, with the path in quotation marks, where necessary
251- if strings .Contains (exposePath , " " ) {
252- for i := 0 ; i < len (arguments ); i ++ {
253- if arguments [i ] == exposePath {
254- argumentsWithQuotes = append (argumentsWithQuotes , fmt .Sprintf ("'%s'" , exposePath ))
255- } else {
256- argumentsWithQuotes = append (argumentsWithQuotes , arguments [i ])
257- }
258- }
259- closehandler .SaveArguments (argumentsWithQuotes )
260- } else {
261- closehandler .SaveArguments (arguments )
262- }
263- cmd .Execute ()
264- }
265-
26637func init () {
26738 cobra .OnInitialize (initLogger )
26839
0 commit comments