text: package assets
  
  import (
  	"bytes"
  	"compress/gzip"
  	"encoding/hex"
  	"fmt"
  	"io"
  	"net/http"
  	"path"
  	"regexp"
  	"sort"
  	"strings"
  	"text/template"
  
  	"github.com/openshift/origin/pkg/quota/admission/clusterresourceoverride/api"
  	utilruntime "k8s.io/kubernetes/pkg/util/runtime"
  )
  
  var varyHeaderRegexp = regexp.MustCompile("\\s*,\\s*")
  
  type gzipResponseWriter struct {
  	io.Writer
  	http.ResponseWriter
  	sniffDone bool
  }
  
  func (w *gzipResponseWriter) Write(b []byte) (int, error) {
  	if !w.sniffDone {
  		if w.Header().Get("Content-Type") == "" {
  			w.Header().Set("Content-Type", http.DetectContentType(b))
  		}
  		w.sniffDone = true
  	}
  	return w.Writer.Write(b)
  }
  
  // GzipHandler wraps a http.Handler to support transparent gzip encoding.
  func GzipHandler(h http.Handler) http.Handler {
  	return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
  		w.Header().Add("Vary", "Accept-Encoding")
  		if !strings.Contains(r.Header.Get("Accept-Encoding"), "gzip") {
  			h.ServeHTTP(w, r)
  			return
  		}
  		// Normalize the Accept-Encoding header for improved caching
  		r.Header.Set("Accept-Encoding", "gzip")
  		w.Header().Set("Content-Encoding", "gzip")
  		gz := gzip.NewWriter(w)
  		defer gz.Close()
  		h.ServeHTTP(&gzipResponseWriter{Writer: gz, ResponseWriter: w}, r)
  	})
  }
  
  func generateEtag(r *http.Request, version string, varyHeaders []string) string {
  	varyHeaderValues := ""
  	for _, varyHeader := range varyHeaders {
  		varyHeaderValues += r.Header.Get(varyHeader)
  	}
  	return fmt.Sprintf("W/\"%s_%s\"", version, hex.EncodeToString([]byte(varyHeaderValues)))
  }
  
  func CacheControlHandler(version string, h http.Handler) http.Handler {
  	return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
  		vary := w.Header().Get("Vary")
  		varyHeaders := []string{}
  		if vary != "" {
  			varyHeaders = varyHeaderRegexp.Split(vary, -1)
  		}
  		etag := generateEtag(r, version, varyHeaders)
  
  		if r.Header.Get("If-None-Match") == etag {
  			w.WriteHeader(http.StatusNotModified)
  			return
  		}
  
  		// Clients must revalidate their cached copy every time.
  		w.Header().Add("Cache-Control", "public, max-age=0, must-revalidate")
  		w.Header().Add("ETag", etag)
  		h.ServeHTTP(w, r)
  
  	})
  }
  
  type LongestToShortest []string
  
  func (s LongestToShortest) Len() int {
  	return len(s)
  }
  func (s LongestToShortest) Swap(i, j int) {
  	s[i], s[j] = s[j], s[i]
  }
  func (s LongestToShortest) Less(i, j int) bool {
  	return len(s[i]) > len(s[j])
  }
  
  // HTML5ModeHandler will serve any static assets we know about, all other paths
  // are assumed to be HTML5 paths for the console application and index.html will
  // be served.
  // contextRoot must contain leading and trailing slashes, e.g. /console/
  //
  // subcontextMap is a map of keys (subcontexts, no leading or trailing slashes) to the asset path (no
  // leading slash) to serve for that subcontext if a resource that does not exist is requested
  func HTML5ModeHandler(contextRoot string, subcontextMap map[string]string, h http.Handler, getAsset AssetFunc) (http.Handler, error) {
  	subcontextData := map[string][]byte{}
  	subcontexts := []string{}
  
  	for subcontext, index := range subcontextMap {
  		b, err := getAsset(index)
  		if err != nil {
  			return nil, err
  		}
  		base := path.Join(contextRoot, subcontext)
  		// Make sure the base always ends in a trailing slash but don't end up with a double trailing slash
  		if !strings.HasSuffix(base, "/") {
  			base += "/"
  		}
  		b = bytes.Replace(b, []byte(`<base href="/">`), []byte(fmt.Sprintf(`<base href="%s">`, base)), 1)
  		subcontextData[subcontext] = b
  		subcontexts = append(subcontexts, subcontext)
  	}
  
  	// Sort by length, longest first
  	sort.Sort(LongestToShortest(subcontexts))
  
  	return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
  		urlPath := strings.TrimPrefix(r.URL.Path, "/")
  		if _, err := getAsset(urlPath); err != nil {
  			// find the index we want to serve instead
  			for _, subcontext := range subcontexts {
  				prefix := subcontext
  				if subcontext != "" {
  					prefix += "/"
  				}
  				if urlPath == subcontext || strings.HasPrefix(urlPath, prefix) {
  					w.Write(subcontextData[subcontext])
  					return
  				}
  			}
  		}
  		h.ServeHTTP(w, r)
  	}), nil
  }
  
  var versionTemplate = template.Must(template.New("webConsoleVersion").Parse(`
  window.OPENSHIFT_VERSION = {
    openshift: "{{ .OpenShiftVersion | js}}",
    kubernetes: "{{ .KubernetesVersion | js}}"
  };
  `))
  
  type WebConsoleVersion struct {
  	KubernetesVersion string
  	OpenShiftVersion  string
  }
  
  var extensionPropertiesTemplate = template.Must(template.New("webConsoleExtensionProperties").Parse(`
  window.OPENSHIFT_EXTENSION_PROPERTIES = {
  {{ range $i, $property := .ExtensionProperties }}{{ if $i }},{{ end }}
    "{{ $property.Key | js }}": "{{ $property.Value | js }}"{{ end }}
  };
  `))
  
  type WebConsoleExtensionProperty struct {
  	Key   string
  	Value string
  }
  
  type WebConsoleExtensionProperties struct {
  	ExtensionProperties []WebConsoleExtensionProperty
  }
  
  var configTemplate = template.Must(template.New("webConsoleConfig").Parse(`
  window.OPENSHIFT_CONFIG = {
    apis: {
      hostPort: "{{ .APIGroupAddr | js}}",
      prefix: "{{ .APIGroupPrefix | js}}"
    },
    api: {
      openshift: {
        hostPort: "{{ .MasterAddr | js}}",
        prefix: "{{ .MasterPrefix | js}}"
      },
      k8s: {
        hostPort: "{{ .KubernetesAddr | js}}",
        prefix: "{{ .KubernetesPrefix | js}}"
      }
    },
    auth: {
    	oauth_authorize_uri: "{{ .OAuthAuthorizeURI | js}}",
    	oauth_redirect_base: "{{ .OAuthRedirectBase | js}}",
    	oauth_client_id: "{{ .OAuthClientID | js}}",
    	logout_uri: "{{ .LogoutURI | js}}"
    },
    {{ with .LimitRequestOverrides }}
    limitRequestOverrides: {
  	limitCPUToMemoryPercent: {{ .LimitCPUToMemoryPercent }},
  	cpuRequestToLimitPercent: {{ .CPURequestToLimitPercent }},
  	memoryRequestToLimitPercent: {{ .MemoryRequestToLimitPercent }}
    },
    {{ end }}
    loggingURL: "{{ .LoggingURL | js}}",
    metricsURL: "{{ .MetricsURL | js}}"
  };
  `))
  
  type WebConsoleConfig struct {
  	// APIGroupAddr is the host:port the UI should call the API groups on. Scheme is derived from the scheme the UI is served on, so they must be the same.
  	APIGroupAddr string
  	// APIGroupPrefix is the API group context root
  	APIGroupPrefix string
  	// MasterAddr is the host:port the UI should call the master API on. Scheme is derived from the scheme the UI is served on, so they must be the same.
  	MasterAddr string
  	// MasterPrefix is the OpenShift API context root
  	MasterPrefix string
  	// MasterResources holds resource names for the OpenShift API
  	MasterResources []string
  	// KubernetesAddr is the host:port the UI should call the kubernetes API on. Scheme is derived from the scheme the UI is served on, so they must be the same.
  	// TODO this is probably unneeded since everything goes through the openshift master's proxy
  	KubernetesAddr string
  	// KubernetesPrefix is the Kubernetes API context root
  	KubernetesPrefix string
  	// KubernetesResources holds resource names for the Kubernetes API
  	KubernetesResources []string
  	// OAuthAuthorizeURI is the OAuth2 endpoint to use to request an API token. It must support request_type=token.
  	OAuthAuthorizeURI string
  	// OAuthRedirectBase is the base URI of the web console. It must be a valid redirect_uri for the OAuthClientID
  	OAuthRedirectBase string
  	// OAuthClientID is the OAuth2 client_id to use to request an API token. It must be authorized to redirect to the web console URL.
  	OAuthClientID string
  	// LogoutURI is an optional (absolute) URI to redirect to after completing a logout. If not specified, the built-in logout page is shown.
  	LogoutURI string
  	// LoggingURL is the endpoint for logging (optional)
  	LoggingURL string
  	// MetricsURL is the endpoint for metrics (optional)
  	MetricsURL string
  	// LimitRequestOverrides contains the ratios for overriding request/limit on containers.
  	// Applied in order:
  	//   LimitCPUToMemoryPercent
  	//   CPURequestToLimitPercent
  	//   MemoryRequestToLimitPercent
  	LimitRequestOverrides *api.ClusterResourceOverrideConfig
  }
  
  func GeneratedConfigHandler(config WebConsoleConfig, version WebConsoleVersion, extensionProps WebConsoleExtensionProperties) (http.Handler, error) {
  	var buffer bytes.Buffer
  	if err := configTemplate.Execute(&buffer, config); err != nil {
  		return nil, err
  	}
  	if err := versionTemplate.Execute(&buffer, version); err != nil {
  		return nil, err
  	}
  
  	// We include the extension properties in config.js and not extensions.js because we
  	// want them treated with the same caching behavior as the rest of the values in config.js
  	if err := extensionPropertiesTemplate.Execute(&buffer, extensionProps); err != nil {
  		return nil, err
  	}
  	content := buffer.Bytes()
  
  	return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
  		w.Header().Add("Cache-Control", "no-cache, no-store")
  		w.Header().Add("Content-Type", "application/javascript")
  		if _, err := w.Write(content); err != nil {
  			utilruntime.HandleError(fmt.Errorf("Error serving Web Console config and version: %v", err))
  		}
  	}), nil
  }
  