github.com/openshift-online/ocm-sdk-go@v0.1.473/internal/check_content_type.go (about)

     1  /*
     2  Copyright (c) 2021 Red Hat, Inc.
     3  
     4  Licensed under the Apache License, Version 2.0 (the "License");
     5  you may not use this file except in compliance with the License.
     6  You may obtain a copy of the License at
     7  
     8    http://www.apache.org/licenses/LICENSE-2.0
     9  
    10  Unless required by applicable law or agreed to in writing, software
    11  distributed under the License is distributed on an "AS IS" BASIS,
    12  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
    13  See the License for the specific language governing permissions and
    14  limitations under the License.
    15  */
    16  
    17  // This file contains the functions used to check content types.
    18  
    19  package internal
    20  
    21  import (
    22  	"fmt"
    23  	"html"
    24  	"io"
    25  	"mime"
    26  	"net/http"
    27  	"regexp"
    28  	"strings"
    29  
    30  	"github.com/microcosm-cc/bluemonday"
    31  )
    32  
    33  var wsRegex = regexp.MustCompile(`\s+`)
    34  
    35  // CheckContentType checks that the content type of the given response is JSON. Note that if the
    36  // content type isn't JSON this method will consume the complete body in order to generate an error
    37  // message containing a summary of the content.
    38  func CheckContentType(response *http.Response) error {
    39  	var err error
    40  	var mediaType string
    41  	if response.StatusCode == http.StatusNoContent {
    42  		return nil
    43  	}
    44  	contentType := response.Header.Get("Content-Type")
    45  	if contentType != "" {
    46  		mediaType, _, err = mime.ParseMediaType(contentType)
    47  		if err != nil {
    48  			return err
    49  		}
    50  	} else {
    51  		mediaType = contentType
    52  	}
    53  	if !strings.EqualFold(mediaType, "application/json") {
    54  		var summary string
    55  		summary, err = contentSummary(mediaType, response)
    56  		if err != nil {
    57  			return fmt.Errorf(
    58  				"expected response content type 'application/json' but received "+
    59  					"'%s' and couldn't obtain content summary: %w",
    60  				mediaType, err,
    61  			)
    62  		}
    63  		return fmt.Errorf(
    64  			"expected response content type 'application/json' but received '%s' and "+
    65  				"content '%s'",
    66  			mediaType, summary,
    67  		)
    68  	}
    69  	return nil
    70  }
    71  
    72  // contentSummary reads the body of the given response and returns a summary it. The summary will
    73  // be the complete body if it isn't too log. If it is too long then the summary will be the
    74  // beginning of the content followed by ellipsis.
    75  func contentSummary(mediaType string, response *http.Response) (summary string, err error) {
    76  	var body []byte
    77  	body, err = io.ReadAll(response.Body)
    78  	if err != nil {
    79  		return
    80  	}
    81  	limit := 250
    82  	runes := []rune(string(body))
    83  	if strings.EqualFold(mediaType, "text/html") && len(runes) > limit {
    84  		content := html.UnescapeString(bluemonday.StrictPolicy().Sanitize(string(body)))
    85  		content = wsRegex.ReplaceAllString(strings.TrimSpace(content), " ")
    86  		runes = []rune(content)
    87  	}
    88  	if len(runes) > limit {
    89  		summary = fmt.Sprintf("%s...", string(runes[:limit]))
    90  	} else {
    91  		summary = string(runes)
    92  	}
    93  	return
    94  }