@@ -263,6 +263,15 @@ func (a *API) handleAdminConnection(ctx context.Context, sc server.SocketConn) {
263263 reader := bufio .NewReader (sc .Conn )
264264
265265 for {
266+ // Check if context is canceled (shutdown signal)
267+ select {
268+ case <- ctx .Done ():
269+ log .Debug ("Context canceled, shutting down admin connection handler" )
270+ return
271+ default :
272+ // Continue with normal processing
273+ }
274+
266275 // Read line by line instead of fixed buffer - more efficient for admin commands
267276 line , err := reader .ReadString ('\n' )
268277 if err != nil {
@@ -322,47 +331,82 @@ func (a *API) handleAdminConnection(ctx context.Context, sc server.SocketConn) {
322331 }
323332}
324333
325- // parseAdminCommand parses admin commands - optimized version using strings.Fields
334+ // parseAdminCommand parses admin commands using a smart pattern-matching approach
326335func (a * API ) parseAdminCommand (line string ) ([]string , []string , error ) {
327- // Split by whitespace - much more efficient than byte-by-byte parsing
328336 parts := strings .Fields (line )
329337 if len (parts ) == 0 {
330338 return nil , nil , fmt .Errorf ("empty command" )
331339 }
332340
333- // First part is always the verb
334- verb := parts [0 ]
335-
336341 if len (parts ) < 2 {
337342 return nil , nil , fmt .Errorf ("missing module" )
338343 }
339344
340- // Second part is the module
341- module := parts [1 ]
342-
343- apiCommand := []string {verb , module }
344- args := []string {}
345-
346- // Handle submodule and arguments
347- if len (parts ) > 2 {
348- // Check if third part could be a submodule (no spaces, reasonable length)
349- potentialSubmodule := parts [2 ]
350- if len (potentialSubmodule ) <= 16 && ! strings .Contains (potentialSubmodule , " " ) {
351- // Treat as submodule if it makes sense contextually
352- if (verb == "get" || verb == "set" || verb == "del" || verb == "val" ) &&
353- (module == "host" && (potentialSubmodule == "session" || potentialSubmodule == "cookie" || potentialSubmodule == "captcha" )) ||
354- (module == "geo" && potentialSubmodule == "iso" ) {
355- apiCommand = append (apiCommand , potentialSubmodule )
356- args = parts [3 :] // Remaining parts are arguments
357- } else {
358- args = parts [2 :] // All remaining parts are arguments
345+ // For 3-part commands, we need to handle cases where arguments come between command parts
346+ // e.g., "get host 127.0.0.1 session uuid key" should parse as "get:host:session"
347+ if len (parts ) >= 4 {
348+ // Try verb:module:submodule pattern with different positions for submodule
349+ verb := parts [0 ]
350+ module := parts [1 ]
351+
352+ // Look for known submodules in the remaining parts
353+ for i := 2 ; i < len (parts ); i ++ {
354+ candidateCmd := fmt .Sprintf ("%s:%s:%s" , verb , module , parts [i ])
355+ if a .isValidCommand (candidateCmd ) {
356+ // Found valid 3-part command, collect arguments from everywhere except the command parts
357+ cmdParts := []string {verb , module , parts [i ]}
358+ args := make ([]string , 0 )
359+
360+ // Add arguments that come before the submodule
361+ args = append (args , parts [2 :i ]... )
362+ // Add arguments that come after the submodule
363+ args = append (args , parts [i + 1 :]... )
364+
365+ return cmdParts , args , nil
359366 }
360- } else {
361- args = parts [2 :] // All remaining parts are arguments
362367 }
363368 }
364369
365- return apiCommand , args , nil
370+ // Try standard 3-part commands (verb:module:submodule at start)
371+ if len (parts ) >= 3 {
372+ threePartCmd := strings .Join (parts [:3 ], ":" )
373+ if a .isValidCommand (threePartCmd ) {
374+ return parts [:3 ], parts [3 :], nil
375+ }
376+ }
377+
378+ // Try 2-part commands (verb:module)
379+ twoPartCmd := strings .Join (parts [:2 ], ":" )
380+ if a .isValidCommand (twoPartCmd ) {
381+ return parts [:2 ], parts [2 :], nil
382+ }
383+
384+ // If no valid command found, return an explicit error
385+ return nil , nil , fmt .Errorf ("invalid command: %q" , line )
386+ }
387+
388+ // isValidCommand checks if a command string matches any of our defined APICommands
389+ func (a * API ) isValidCommand (cmdStr string ) bool {
390+ cmd := messages .CommandFromString (cmdStr )
391+
392+ // Use the actual APICommand constants - no duplication!
393+ switch cmd {
394+ case messages .GetIP ,
395+ messages .GetCN ,
396+ messages .GetGeoIso ,
397+ messages .GetHosts ,
398+ messages .GetHostCookie ,
399+ messages .GetHostSession ,
400+ messages .ValHostCookie ,
401+ messages .ValHostCaptcha ,
402+ messages .SetHostSession ,
403+ messages .DelHostSession ,
404+ messages .DelHosts ,
405+ messages .ValHostAppSec :
406+ return true
407+ default :
408+ return false
409+ }
366410}
367411
368412// handleAdminCommand directly dispatches admin commands using clean APICommand constants
0 commit comments