<commit_before>package http

import (
	"bytes"
	"crypto/md5"
	"encoding/base64"
	"encoding/json"
	"fmt"
	"io"
	"io/ioutil"
	"net/http"
	"net/url"

	"github.com/hashicorp/go-retryablehttp"
	"github.com/hashicorp/terraform/internal/states/remote"
	"github.com/hashicorp/terraform/internal/states/statemgr"
)

// httpClient is a remote client that stores data in Consul or HTTP REST.
type httpClient struct {
	// Update & Retrieve
	URL          *url.URL
	UpdateMethod string

	// Locking
	LockURL      *url.URL
	LockMethod   string
	UnlockURL    *url.URL
	UnlockMethod string

	// HTTP
	Client   *retryablehttp.Client
	Username string
	Password string

	lockID       string
	jsonLockInfo []byte
}

func (c *httpClient) httpRequest(method string, url *url.URL, data *[]byte, what string) (*http.Response, error) {
	// If we have data we need a reader
	var reader io.Reader = nil
	if data != nil {
		reader = bytes.NewReader(*data)
	}

	// Create the request
	req, err := retryablehttp.NewRequest(method, url.String(), reader)
	if err != nil {
		return nil, fmt.Errorf("Failed to make %s HTTP request: %s", what, err)
	}
	// Set up basic auth
	if c.Username != "" {
		req.SetBasicAuth(c.Username, c.Password)
	}

	// Work with data/body
	if data != nil {
		req.Header.Set("Content-Type", "application/json")
		req.ContentLength = int64(len(*data))

		// Generate the MD5
		hash := md5.Sum(*data)
		b64 := base64.StdEncoding.EncodeToString(hash[:])
		req.Header.Set("Content-MD5", b64)
	}

	// Make the request
	resp, err := c.Client.Do(req)
	if err != nil {
		return nil, fmt.Errorf("Failed to %s: %v", what, err)
	}

	return resp, nil
}

func (c *httpClient) Lock(info *statemgr.LockInfo) (string, error) {
	if c.LockURL == nil {
		return "", nil
	}
	c.lockID = ""

	jsonLockInfo := info.Marshal()
	resp, err := c.httpRequest(c.LockMethod, c.LockURL, &jsonLockInfo, "lock")
	if err != nil {
		return "", err
	}
	defer resp.Body.Close()

	switch resp.StatusCode {
	case http.StatusOK:
		c.lockID = info.ID
		c.jsonLockInfo = jsonLockInfo
		return info.ID, nil
	case http.StatusUnauthorized:
		return "", fmt.Errorf("HTTP remote state endpoint requires auth")
	case http.StatusForbidden:
		return "", fmt.Errorf("HTTP remote state endpoint invalid auth")
	case http.StatusConflict, http.StatusLocked:
		defer resp.Body.Close()
		body, err := ioutil.ReadAll(resp.Body)
		if err != nil {
			return "", fmt.Errorf("HTTP remote state already locked, failed to read body")
		}
		existing := statemgr.LockInfo{}
		err = json.Unmarshal(body, &existing)
		if err != nil {
			return "", fmt.Errorf("HTTP remote state already locked, failed to unmarshal body")
		}
		return "", fmt.Errorf("HTTP remote state already locked: ID=%s", existing.ID)
	default:
		return "", fmt.Errorf("Unexpected HTTP response code %d", resp.StatusCode)
	}
}

func (c *httpClient) Unlock(id string) error {
	if c.UnlockURL == nil {
		return nil
	}

	resp, err := c.httpRequest(c.UnlockMethod, c.UnlockURL, &c.jsonLockInfo, "unlock")
	if err != nil {
		return err
	}
	defer resp.Body.Close()

	switch resp.StatusCode {
	case http.StatusOK:
		return nil
	default:
		return fmt.Errorf("Unexpected HTTP response code %d", resp.StatusCode)
	}
}
<commit_msg>http-backend: http backend lock error return LockError instead of generic error<commit_after>package http

import (
	"bytes"
	"crypto/md5"
	"encoding/base64"
	"encoding/json"
	"fmt"
	"io"
	"io/ioutil"
	"net/http"
	"net/url"

	"github.com/hashicorp/go-retryablehttp"
	"github.com/hashicorp/terraform/internal/states/remote"
	"github.com/hashicorp/terraform/internal/states/statemgr"
)

// httpClient is a remote client that stores data in Consul or HTTP REST.
type httpClient struct {
	// Update & Retrieve
	URL          *url.URL
	UpdateMethod string

	// Locking
	LockURL      *url.URL
	LockMethod   string
	UnlockURL    *url.URL
	UnlockMethod string

	// HTTP
	Client   *retryablehttp.Client
	Username string
	Password string

	lockID       string
	jsonLockInfo []byte
}

func (c *httpClient) httpRequest(method string, url *url.URL, data *[]byte, what string) (*http.Response, error) {
	// If we have data we need a reader
	var reader io.Reader = nil
	if data != nil {
		reader = bytes.NewReader(*data)
	}

	// Create the request
	req, err := retryablehttp.NewRequest(method, url.String(), reader)
	if err != nil {
		return nil, fmt.Errorf("Failed to make %s HTTP request: %s", what, err)
	}
	// Set up basic auth
	if c.Username != "" {
		req.SetBasicAuth(c.Username, c.Password)
	}

	// Work with data/body
	if data != nil {
		req.Header.Set("Content-Type", "application/json")
		req.ContentLength = int64(len(*data))

		// Generate the MD5
		hash := md5.Sum(*data)
		b64 := base64.StdEncoding.EncodeToString(hash[:])
		req.Header.Set("Content-MD5", b64)
	}

	// Make the request
	resp, err := c.Client.Do(req)
	if err != nil {
		return nil, fmt.Errorf("Failed to %s: %v", what, err)
	}

	return resp, nil
}

func (c *httpClient) Lock(info *statemgr.LockInfo) (string, error) {
	if c.LockURL == nil {
		return "", nil
	}
	c.lockID = ""

	jsonLockInfo := info.Marshal()
	resp, err := c.httpRequest(c.LockMethod, c.LockURL, &jsonLockInfo, "lock")
	if err != nil {
		return "", err
	}
	defer resp.Body.Close()

	switch resp.StatusCode {
	case http.StatusOK:
		c.lockID = info.ID
		c.jsonLockInfo = jsonLockInfo
		return info.ID, nil
	case http.StatusUnauthorized:
		return "", fmt.Errorf("HTTP remote state endpoint requires auth")
	case http.StatusForbidden:
		return "", fmt.Errorf("HTTP remote state endpoint invalid auth")
	case http.StatusConflict, http.StatusLocked:
		defer resp.Body.Close()
		body, err := ioutil.ReadAll(resp.Body)
		if err != nil {
			return "", &statemgr.LockError{
				Info: info,
				Err:  fmt.Errorf("HTTP remote state already locked, failed to read body"),
			}
		}
		existing := statemgr.LockInfo{}
		err = json.Unmarshal(body, &existing)
		if err != nil {
			return "", &statemgr.LockError{
				Info: info,
				Err:  fmt.Errorf("HTTP remote state already locked, failed to unmarshal body"),
			}
		}
		return "", &statemgr.LockError{
			Info: info,
			Err:  fmt.Errorf("HTTP remote state already locked: ID=%s", existing.ID),
		}
	default:
		return "", fmt.Errorf("Unexpected HTTP response code %d", resp.StatusCode)
	}
}

func (c *httpClient) Unlock(id string) error {
	if c.UnlockURL == nil {
		return nil
	}

	resp, err := c.httpRequest(c.UnlockMethod, c.UnlockURL, &c.jsonLockInfo, "unlock")
	if err != nil {
		return err
	}
	defer resp.Body.Close()

	switch resp.StatusCode {
	case http.StatusOK:
		return nil
	default:
		return fmt.Errorf("Unexpected HTTP response code %d", resp.StatusCode)
	}
}

{'fraction_non_alphanumeric': 0.1350375939849624, 'fraction_numerical': 0.004210526315789474, 'mean_word_length': 3.6091476091476093, 'pattern_counts': {'":': 0, '<': 3, '<?xml version=': 0, '>': 3, 'https://': 0, 'lorem ipsum': 0, 'www.': 0, 'xml': 0}, 'pii_count': 0, 'substrings_counts': 0, 'word_list_counts': {'cursed_substrings.json': 0, 'profanity_word_list.json': 0, 'sexual_word_list.json': 0, 'zh_pornsignals.json': 0}}
{'dir': 'git-commits-cleaned', 'id': '4219717', 'n_tokens_mistral': 2414, 'n_tokens_neox': 2298, 'n_words': 634}