2017-11-11 04:44:02 +00:00
package writer
import (
2021-02-02 22:03:33 +00:00
"encoding/json"
2024-02-05 15:46:41 +00:00
"fmt"
2017-11-11 04:44:02 +00:00
"strings"
2017-11-21 20:46:30 +00:00
"github.com/rancher/norman/api/builtin"
2017-11-11 04:44:02 +00:00
"github.com/rancher/norman/types"
)
2018-10-27 04:57:54 +00:00
const (
JSURL = "https://releases.rancher.com/api-ui/%API_UI_VERSION%/ui.min.js"
CSSURL = "https://releases.rancher.com/api-ui/%API_UI_VERSION%/ui.min.css"
2024-02-05 15:46:41 +00:00
DefaultVersion = "1.1.11"
2018-10-27 04:57:54 +00:00
)
2017-11-11 04:44:02 +00:00
var (
start = `
< ! DOCTYPE html >
< ! -- If you are reading this , there is a good chance you would prefer sending an
"Accept: application/json" header and receiving actual JSON responses . -- >
2018-10-27 04:57:54 +00:00
< link rel = "stylesheet" type = "text/css" href = "%CSSURL%" / >
< script src = "%JSURL%" > < / script >
2017-11-11 04:44:02 +00:00
< script >
var user = "admin" ;
var curlUser = ' $ { CATTLE_ACCESS_KEY } : $ { CATTLE_SECRET_KEY } ' ;
2021-02-02 22:03:33 +00:00
var schemas = % SCHEMAS % ;
2017-11-11 04:44:02 +00:00
var data =
`
end = [ ] byte ( ` < / script >
` )
)
2018-10-27 04:57:54 +00:00
type StringGetter func ( ) string
2017-11-11 04:44:02 +00:00
type HTMLResponseWriter struct {
2018-05-24 17:28:05 +00:00
EncodingResponseWriter
2018-10-27 04:57:54 +00:00
CSSURL StringGetter
JSURL StringGetter
APIUIVersion StringGetter
2017-11-11 04:44:02 +00:00
}
func ( h * HTMLResponseWriter ) start ( apiContext * types . APIContext , code int , obj interface { } ) {
2022-07-11 22:38:47 +00:00
_ = AddCommonResponseHeader ( apiContext )
2017-11-11 04:44:02 +00:00
apiContext . Response . Header ( ) . Set ( "content-type" , "text/html" )
2021-04-23 00:08:42 +00:00
apiContext . Response . Header ( ) . Set ( "X-Frame-Options" , "SAMEORIGIN" )
2017-11-11 04:44:02 +00:00
apiContext . Response . WriteHeader ( code )
}
func ( h * HTMLResponseWriter ) Write ( apiContext * types . APIContext , code int , obj interface { } ) {
h . start ( apiContext , code , obj )
2017-11-21 20:46:30 +00:00
schemaSchema := apiContext . Schemas . Schema ( & builtin . Version , "schema" )
2018-10-27 04:57:54 +00:00
headerString := start
2017-11-11 04:44:02 +00:00
if schemaSchema != nil {
2021-02-02 22:03:33 +00:00
headerString = strings . Replace ( headerString , "%SCHEMAS%" , jsonEncodeURL ( apiContext . URLBuilder . Collection ( schemaSchema , apiContext . Version ) ) , 1 )
2017-11-11 04:44:02 +00:00
}
2018-10-27 04:57:54 +00:00
var jsurl , cssurl string
if h . CSSURL != nil && h . JSURL != nil && h . CSSURL ( ) != "" && h . JSURL ( ) != "" {
jsurl = h . JSURL ( )
cssurl = h . CSSURL ( )
} else if h . APIUIVersion != nil && h . APIUIVersion ( ) != "" {
jsurl = strings . Replace ( JSURL , "%API_UI_VERSION%" , h . APIUIVersion ( ) , 1 )
cssurl = strings . Replace ( CSSURL , "%API_UI_VERSION%" , h . APIUIVersion ( ) , 1 )
} else {
jsurl = strings . Replace ( JSURL , "%API_UI_VERSION%" , DefaultVersion , 1 )
cssurl = strings . Replace ( CSSURL , "%API_UI_VERSION%" , DefaultVersion , 1 )
}
2024-02-05 15:46:41 +00:00
// jsurl and cssurl are added to the document as attributes not entities which requires special encoding.
jsurl , _ = encodeAttribute ( jsurl )
cssurl , _ = encodeAttribute ( cssurl )
2018-10-27 04:57:54 +00:00
headerString = strings . Replace ( headerString , "%JSURL%" , jsurl , 1 )
headerString = strings . Replace ( headerString , "%CSSURL%" , cssurl , 1 )
2022-07-11 22:38:47 +00:00
_ , _ = apiContext . Response . Write ( [ ] byte ( headerString ) )
_ = h . Body ( apiContext , apiContext . Response , obj )
2017-11-11 04:44:02 +00:00
if schemaSchema != nil {
2022-07-11 22:38:47 +00:00
_ , _ = apiContext . Response . Write ( end )
2017-11-11 04:44:02 +00:00
}
}
2021-02-02 22:03:33 +00:00
func jsonEncodeURL ( str string ) string {
data , _ := json . Marshal ( str )
return string ( data )
}
2024-02-05 15:46:41 +00:00
// encodeAttribute encodes all characters with the HTML Entity &#xHH; format, including spaces, where HH represents the hexadecimal value of the character in Unicode.
// For example, A becomes A. All alphanumeric characters (letters A to Z, a to z, and digits 0 to 9) remain unencoded.
// more info: https://cheatsheetseries.owasp.org/cheatsheets/Cross_Site_Scripting_Prevention_Cheat_Sheet.html#output-encoding-rules-summary
func encodeAttribute ( raw string ) ( string , error ) {
var builder strings . Builder
for _ , r := range raw {
if ( 'A' <= r && r <= 'Z' ) || ( 'a' <= r && r <= 'z' ) || ( '0' <= r && r <= '9' ) {
_ , err := builder . WriteRune ( r )
if err != nil {
return "" , fmt . Errorf ( "failed to write: %w" , err )
}
} else {
// encode non-alphanumeric rune to hex.
_ , err := fmt . Fprintf ( & builder , "&#x%X;" , r )
if err != nil {
return "" , fmt . Errorf ( "failed to write: %w" , err )
}
}
}
return builder . String ( ) , nil
}