forked from egirna/icap-client
-
Notifications
You must be signed in to change notification settings - Fork 0
/
Copy pathparser.go
179 lines (134 loc) · 5.17 KB
/
parser.go
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
package icapclient
import (
"fmt"
"regexp"
"strconv"
"strings"
)
// getStatusWithCode prepares the status code and status text from two given strings
func getStatusWithCode(str1, str2 string) (int, string, error) {
statusCode, err := strconv.Atoi(str1)
if err != nil {
return 0, "", err
}
status := strings.TrimSpace(str2)
return statusCode, status, nil
}
// getHeaderVal parses the header and its value from a tcp message string
func getHeaderVal(str string) (string, string) {
headerVals := strings.SplitN(str, ":", 2)
header := headerVals[0]
val := ""
if len(headerVals) >= 2 {
val = strings.TrimSpace(headerVals[1])
}
return header, val
}
// isRequestLine determines if the tcp message string is a request line, i.e the first line of the message or not
func isRequestLine(str string) bool {
return strings.Contains(str, ICAPVersion) || strings.Contains(str, HTTPVersion)
}
// setEncapsulatedHeaderValue generates the Encapsulated values and assigns to the ICAP request string
func setEncapsulatedHeaderValue(icapReqStr *string, httpReqStr, httpRespStr string) {
encpVal := " "
if strings.HasPrefix(*icapReqStr, MethodOPTIONS) { // if the request method is OPTIONS
if httpReqStr == "" && httpRespStr == "" { // the most common case for OPTIONS method, no Encapsulated body
encpVal += "null-body=0"
} else {
encpVal += "opt-body=0" // if there is an Encapsulated body
}
}
if strings.HasPrefix(*icapReqStr, MethodREQMOD) || strings.HasPrefix(*icapReqStr, MethodRESPMOD) { // if the request method is RESPMOD or REQMOD
re := regexp.MustCompile(DoubleCRLF) // looking for the match of the string \r\n\r\n, as that is the expression that seperates each blocks, i.e headers and bodies
reqIndices := re.FindAllStringIndex(httpReqStr, -1) // getting the offsets of the matches, tells us the starting/ending point of headers and bodies
reqEndsAt := 0 // this is needed to calculate the response headers by adding the last offset of the request block
if reqIndices != nil {
encpVal += "req-hdr=0"
reqEndsAt = reqIndices[0][1]
if len(reqIndices) > 1 { // indicating there is a body present for the request block, as length would have been 1 for a single match of \r\n\r\n
encpVal += fmt.Sprintf(", req-body=%d", reqIndices[0][1]) // assigning the starting point of the body
reqEndsAt = reqIndices[1][1]
} else if httpRespStr == "" {
encpVal += fmt.Sprintf(", null-body=%d", reqIndices[0][1])
}
if httpRespStr != "" {
encpVal += ", "
}
}
respIndices := re.FindAllStringIndex(httpRespStr, -1)
if respIndices != nil {
encpVal += fmt.Sprintf("res-hdr=%d", reqEndsAt)
if len(respIndices) > 1 {
encpVal += fmt.Sprintf(", res-body=%d", reqEndsAt+respIndices[0][1])
} else {
encpVal += fmt.Sprintf(", null-body=%d", reqEndsAt+respIndices[0][1])
}
}
}
*icapReqStr = fmt.Sprintf(*icapReqStr, encpVal) // formatting the ICAP request Encapsulated header with the value
}
// replaceRequestURIWithActualURL replaces the just the escaped portion of the url with the entire URL in the dumped request message
func replaceRequestURIWithActualURL(str *string, uri, url string) {
if uri == "" {
uri = "/"
}
*str = strings.Replace(*str, uri, url, 1)
}
// addFullBodyInPreviewIndicator adds 0; ieof\r\n\r\n which indicates the entire body fitted in preview
func addFullBodyInPreviewIndicator(str *string) {
*str = strings.TrimSuffix(*str, DoubleCRLF)
*str += fullBodyEndIndicatorPreviewMode
}
// splitBodyAndHeader separates header and body from a http message
func splitBodyAndHeader(str string) (string, string, bool) {
ss := strings.SplitN(str, DoubleCRLF, 2)
if len(ss) < 2 || ss[1] == "" {
return "", "", false
}
headerStr := ss[0]
bodyStr := ss[1]
return headerStr, bodyStr, true
}
// bodyAlreadyChunked determines if the http body is already chunked from the origin server or not
func bodyAlreadyChunked(str string) bool {
_, bodyStr, ok := splitBodyAndHeader(str)
if !ok {
return false
}
r := regexp.MustCompile("\\r\\n0(\\r\\n)+$")
return r.MatchString(bodyStr)
}
// parsePreviewBodyBytes parses the preview portion of the body and only keeps that in the message
func parsePreviewBodyBytes(str *string, pb int) {
headerStr, bodyStr, ok := splitBodyAndHeader(*str)
if !ok {
return
}
bodyStr = bodyStr[:pb]
*str = headerStr + DoubleCRLF + bodyStr
}
// addHexaBodyByteNotations adds the hexadecimal byte notaions in the messages
// for example: Hello World, becomes
// b
// Hello World
// 0
func addHexaBodyByteNotations(bodyStr *string) {
bodyBytes := []byte(*bodyStr)
*bodyStr = fmt.Sprintf("%x%s%s%s", len(bodyBytes), CRLF, *bodyStr, bodyEndIndicator)
}
// mergeHeaderAndBody merges the header and body of the http message togather
func mergeHeaderAndBody(src *string, headerStr, bodyStr string) {
*src = headerStr + DoubleCRLF + bodyStr
}
func chunkBodyByBytes(bdyByte []byte, cl int) []byte {
newBytes := []byte{}
for i := 0; i < len(bdyByte); i += cl {
end := i + cl
if end > len(bdyByte) {
end = len(bdyByte)
}
newBytes = append(newBytes, []byte(fmt.Sprintf("%x\r\n", len(bdyByte[i:end]))+string(bdyByte[i:end]))...)
}
newBytes = append(newBytes, []byte(bodyEndIndicator)...)
return newBytes
}