mirror of
https://github.com/opencloud-eu/opencloud.git
synced 2025-12-30 09:38:26 -05:00
220 lines
4.9 KiB
Go
220 lines
4.9 KiB
Go
package icapclient
|
|
|
|
import (
|
|
"bytes"
|
|
"context"
|
|
"errors"
|
|
"io"
|
|
"net/http"
|
|
"net/url"
|
|
"os"
|
|
"slices"
|
|
"strconv"
|
|
"strings"
|
|
)
|
|
|
|
// Request represents the icap client request data.
|
|
type Request struct {
|
|
Method string
|
|
URL *url.URL
|
|
Header http.Header
|
|
HTTPRequest *http.Request
|
|
HTTPResponse *http.Response
|
|
ChunkLength int
|
|
PreviewBytes int
|
|
ctx context.Context
|
|
previewSet bool
|
|
bodyFittedInPreview bool
|
|
remainingPreviewBytes []byte
|
|
}
|
|
|
|
// NewRequest returns a new Request given a context, method, url, http request and http response
|
|
// todo: method iota
|
|
func NewRequest(ctx context.Context, method, urlStr string, httpReq *http.Request, httpResp *http.Response) (Request, error) {
|
|
u, err := url.Parse(urlStr)
|
|
if err != nil {
|
|
return Request{}, err
|
|
}
|
|
|
|
req := Request{
|
|
Method: strings.ToUpper(method),
|
|
URL: u,
|
|
Header: make(map[string][]string),
|
|
HTTPRequest: httpReq,
|
|
HTTPResponse: httpResp,
|
|
ctx: ctx,
|
|
}
|
|
|
|
if err := req.validate(); err != nil {
|
|
return Request{}, err
|
|
}
|
|
|
|
return req, nil
|
|
}
|
|
|
|
// SetPreview sets the preview bytes in the icap header
|
|
// todo: defer close error
|
|
func (r *Request) SetPreview(maxBytes int) (err error) {
|
|
var bodyBytes []byte
|
|
var previewBytes int
|
|
|
|
// receiving the body bites to determine the preview bytes depending on the request ICAP method
|
|
if r.Method == MethodREQMOD {
|
|
if r.HTTPRequest == nil {
|
|
return nil
|
|
}
|
|
|
|
if r.HTTPRequest.Body != nil {
|
|
b, err := io.ReadAll(r.HTTPRequest.Body)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
bodyBytes = b
|
|
|
|
defer func() {
|
|
err = errors.Join(err, r.HTTPRequest.Body.Close())
|
|
}()
|
|
}
|
|
}
|
|
|
|
if r.Method == MethodRESPMOD {
|
|
if r.HTTPResponse == nil {
|
|
return nil
|
|
}
|
|
|
|
if r.HTTPResponse.Body != nil {
|
|
b, err := io.ReadAll(r.HTTPResponse.Body)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
bodyBytes = b
|
|
|
|
defer func() {
|
|
err = errors.Join(err, r.HTTPResponse.Body.Close())
|
|
}()
|
|
}
|
|
}
|
|
|
|
previewBytes = len(bodyBytes)
|
|
|
|
// if the preview byte is 0 or less, there is no question of the body-fitting insides
|
|
if previewBytes > 0 {
|
|
r.bodyFittedInPreview = true
|
|
}
|
|
|
|
// if the preview bytes are greater than what was mentioned by the ICAP Server (did not fit in the body)
|
|
if previewBytes > maxBytes {
|
|
previewBytes = maxBytes
|
|
r.bodyFittedInPreview = false
|
|
// storing the rest of the body byte which was not sent as preview for further operations
|
|
r.remainingPreviewBytes = bodyBytes[maxBytes:]
|
|
}
|
|
|
|
// set the body to the http message depending on the request method
|
|
if r.Method == MethodREQMOD {
|
|
r.HTTPRequest.Body = io.NopCloser(bytes.NewReader(bodyBytes))
|
|
}
|
|
|
|
if r.Method == MethodRESPMOD {
|
|
r.HTTPResponse.Body = io.NopCloser(bytes.NewReader(bodyBytes))
|
|
}
|
|
|
|
// assign preview byte information to the header
|
|
r.Header.Set("Preview", strconv.Itoa(previewBytes))
|
|
r.PreviewBytes = previewBytes
|
|
r.previewSet = true
|
|
|
|
return err
|
|
}
|
|
|
|
// to the ICAP server to ensure all required headers are set.
|
|
func (r *Request) setDefaultRequestHeaders() {
|
|
if _, exists := r.Header["Allow"]; !exists {
|
|
r.Header.Add("Allow", "204") // assigning 204 by default if Allow not provided
|
|
}
|
|
|
|
if _, exists := r.Header["Host"]; !exists {
|
|
hostName, _ := os.Hostname()
|
|
r.Header.Add("Host", hostName)
|
|
}
|
|
}
|
|
|
|
// extendHeader extends the current ICAP Request header with a new header.
|
|
func (r *Request) extendHeader(hdr http.Header) error {
|
|
for header, values := range hdr {
|
|
if header == previewHeader && r.previewSet {
|
|
continue
|
|
}
|
|
|
|
if header == encapsulatedHeader {
|
|
continue
|
|
}
|
|
|
|
for _, value := range values {
|
|
if header == previewHeader {
|
|
pb, err := strconv.Atoi(value)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
err = r.SetPreview(pb)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
continue
|
|
}
|
|
r.Header.Add(header, value)
|
|
}
|
|
}
|
|
|
|
return nil
|
|
}
|
|
|
|
// validate checks if the ICAP request is valid or not.
|
|
func (r *Request) validate() error {
|
|
var err error
|
|
|
|
// check if the ICAP request has a context
|
|
if r.ctx == nil {
|
|
err = errors.Join(err, ErrNoContext)
|
|
}
|
|
|
|
// check if the ICAP request method is allowed
|
|
if methodAllowed := slices.Contains([]string{
|
|
MethodOPTIONS,
|
|
MethodRESPMOD,
|
|
MethodREQMOD,
|
|
}, r.Method); !methodAllowed {
|
|
err = errors.Join(err, ErrMethodNotAllowed)
|
|
}
|
|
|
|
// check if the ICAP url is valid and contains all required fields
|
|
{
|
|
if r.URL.Scheme != schemeICAP {
|
|
err = errors.Join(err, ErrInvalidScheme)
|
|
}
|
|
|
|
if r.URL.Host == "" {
|
|
err = errors.Join(err, ErrInvalidHost)
|
|
}
|
|
}
|
|
|
|
// check if the ICAP request method is aligned with the http messages
|
|
{
|
|
if r.Method == MethodREQMOD && r.HTTPRequest == nil {
|
|
err = errors.Join(err, ErrREQMODWithoutReq)
|
|
}
|
|
|
|
if r.Method == MethodREQMOD && r.HTTPResponse != nil {
|
|
err = errors.Join(err, ErrREQMODWithResp)
|
|
}
|
|
|
|
if r.Method == MethodRESPMOD && r.HTTPResponse == nil {
|
|
err = errors.Join(err, ErrRESPMODWithoutResp)
|
|
}
|
|
}
|
|
|
|
return err
|
|
}
|