Forum Discussion

Sam_Novak's avatar
Sam_Novak
Icon for Altostratus rankAltostratus
Mar 10, 2016

Using switch statements on HTTP::headers

I'm trying to optimize some code that I've pulled from a couple of places and I was wondering if there's a way to use switch statements to check for the existence of headers, since I've read the switch statements are faster than if statements.

 

I'm mostly interested in combining my insertions into one switch statement (if possible) and my removal statements.

 

 This iRule combines Combine_cache_Control_Headers, Cookie_Encryption and removes some additional headers
 that might be useful to an attacker.

when RULE_INIT {
    
     This is for the BigIP cookie encryption
    
     Cookie name prefix
    set static::ck_pattern "BIGipServer*"
     Cookie encryption passphrase
     Change this to a custom string!
    set static::ck_pass "REMOVED"

    
     This is for the Cache header combiner
    
    set static::headers { "Cache-Control" "WWW-Authenticate" }
    set static::CacheHeaders { "Cache-Control" }

     Headers for removal
    set static::InfoHeaders { "Server" "X-Powered-By" }
}

when HTTP_REQUEST {

     Check if the cookie names in the request match our string glob pattern
    if {[set cookie_names [lsearch -all -inline [HTTP::cookie names] $static::ck_pattern]] ne ""}{
         We have at least one match so loop through the cookie(s) by name
        foreach cookie_name $cookie_names {
             Decrypt the cookie value and check if the decryption failed (null return value)
            if {[HTTP::cookie decrypt $cookie_name $static::ck_pass] eq ""}{
                HTTP::cookie remove $cookie_name
            }
        }
    }
}

when HTTP_RESPONSE { 

    
     Remove headers that provide information about the backend
    

    foreach header $static::InfoHeaders {
        if {[HTTP::header exists $header]} {
            HTTP::header remove $header
        }
    }

    
     Clickjacking mitigation
    
    if {![HTTP::header exists "Content-Security-Policy"] } {
       HTTP::header insert "Content-Security-Policy" "frame-ancestors 'self' example.com *.example.com"
    }

     MIME sniffing header
    if {![HTTP::header exists "X-Content-Type-Options"] } {
       HTTP::header insert "X-Content-Type-Options" "nosniff"
    }

     XSS header
    if {![HTTP::header exists "X-XSS-Protection"] } {
       HTTP::header insert "X-XSS-Protection" "1; mode=block"
    }

    
     Combine Cache headers so the acceleration profiles work correctly
    
    foreach header $static::CacheHeaders {
        if {[HTTP::header exists $header]} {
             Grab the values as a list, remove the existing headers, and
             replace with the values, joined as a comma-delimited string
            set from_values [HTTP::header values $header]
            set to_values {}
            foreach value $from_values {
                set value [string trim $value]
                if { $value ne ""} { lappend to_values $value }
            }
            set to_values [join $to_values ", "]
            HTTP::header remove $header
            HTTP::header insert "lws" $header $to_values
        }
    }

    
     Check if the cookie names in the request match our string glob pattern
    
    if {[set cookie_names [lsearch -all -inline [HTTP::cookie names] $static::ck_pattern]] ne ""}{
         We have at least one match so loop through the cookie(s) by name
        foreach cookie_name $cookie_names {
             Encrypt the cookie value
            HTTP::cookie encrypt $cookie_name $static::ck_pass
        }
    }
}
  • Hi Sam,

    when comparing just a few conditions (like you do), then [if] outperforms [switch] very much.

    The very simplyfied guideline would be to use [if] for less that 10 contions, to use use [switch] for more than 10 but less than 50 condition and to use [class] for more than 50 conditions. But only individual [timing on] tests would provide reliable data.

    Your code looks already very clean and straightforward. The only optimizations I could spot are within your [foreach] cookie encryption and [HTTP::header remove] syntax.

    [Foreach] Optimization:

     

    foreach cookie_name [lsearch -all -inline [HTTP::cookie names] $static::ck_pattern] {
         Decrypt the cookie value and check if the decryption failed (null return value)
        if {[HTTP::cookie decrypt $cookie_name $static::ck_pass] eq ""}{
            HTTP::cookie remove $cookie_name
        }
    }
    
    foreach cookie_name [lsearch -all -inline [HTTP::cookie names] $static::ck_pattern] {
         Encrypt the cookie value
        HTTP::cookie encrypt $cookie_name $static::ck_pass
    }
    

     

    Background: [foreach] has a build-in ne "" check.

    [HTTP::header remove] Optimization:

     

    foreach header $static::InfoHeaders {
        HTTP::header remove $header
    }
    

     

    Background: The required cpu cycles for remove and exists are almost identiacal and remove wouldn't trow an error if the header is not in place.

    Additional Note: You could free up some memory by unsetting the variable after usage. I would recommend to migrate your code to $temp(your_var_name) variables and then just issue an unset -nocomplain temp at the of HTTP_RESPONSE to be able to flush every variable in one step.

    Cheers, Kai