Skip to content

Commit 737af66

Browse files
authored
Merge pull request #94 from ppomes/feat/api-ergonomics
feat(api): return 1 on disruption, add bulk headers, add coraza_free_string
2 parents 9a55fe7 + 81011ad commit 737af66

4 files changed

Lines changed: 143 additions & 20 deletions

File tree

coraza.i

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,7 @@
2525
*/
2626
%ignore coraza_add_debug_log_callback;
2727
%ignore coraza_add_error_callback;
28+
%ignore coraza_free_string;
2829

2930
/*
3031
* Handle the char** output parameter for error messages.
@@ -330,6 +331,12 @@ typedef struct coraza_intervention_t {
330331
char *data;
331332
} coraza_intervention_t;
332333

334+
typedef enum coraza_result_t {
335+
CORAZA_ERROR = -1,
336+
CORAZA_OK = 0,
337+
CORAZA_INTERRUPTION = 1,
338+
} coraza_result_t;
339+
333340
typedef enum coraza_debug_log_level_t {
334341
CORAZA_DEBUG_LOG_LEVEL_UNKNOWN,
335342
CORAZA_DEBUG_LOG_LEVEL_TRACE,
@@ -401,3 +408,10 @@ extern int coraza_free_waf(coraza_waf_t t);
401408
extern coraza_severity_t coraza_matched_rule_get_severity(
402409
coraza_matched_rule_t r);
403410
extern char *coraza_matched_rule_get_error_log(coraza_matched_rule_t r);
411+
extern int coraza_add_request_headers(coraza_transaction_t t,
412+
const char *packed, int packed_len,
413+
int count);
414+
extern int coraza_add_response_headers(coraza_transaction_t t,
415+
const char *packed, int packed_len,
416+
int count);
417+
extern void coraza_free_string(char *s);

examples/java/SimpleGet.java

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -102,21 +102,21 @@ static void testLifecycle() throws IOException {
102102
ret = coraza.coraza_process_uri(tx, "/someurl?foo=bar", "GET", "HTTP/1.1");
103103
check(ret == 0, "coraza_process_uri failed: " + ret);
104104

105-
// coraza_process_request_headers
105+
// coraza_process_request_headers (returns CORAZA_INTERRUPTION for the deny rule)
106106
ret = coraza.coraza_process_request_headers(tx);
107-
check(ret == 0, "coraza_process_request_headers failed: " + ret);
107+
check(ret == 1, "coraza_process_request_headers: expected CORAZA_INTERRUPTION (1), got " + ret);
108108

109109
// coraza_append_request_body (byte[] typemap: single array arg)
110110
ret = coraza.coraza_append_request_body(tx, "hello=world".getBytes());
111111
check(ret == 0, "coraza_append_request_body failed: " + ret);
112112

113113
// coraza_process_request_body
114114
ret = coraza.coraza_process_request_body(tx);
115-
check(ret == 0, "coraza_process_request_body failed: " + ret);
115+
check(ret >= 0, "coraza_process_request_body failed: " + ret);
116116

117117
// coraza_process_response_headers
118118
ret = coraza.coraza_process_response_headers(tx, 200, "HTTP/1.1");
119-
check(ret == 0, "coraza_process_response_headers failed: " + ret);
119+
check(ret >= 0, "coraza_process_response_headers failed: " + ret);
120120

121121
// coraza_add_response_header
122122
String cname = "Content-Type", cvalue = "text/plain";
@@ -130,7 +130,7 @@ static void testLifecycle() throws IOException {
130130

131131
// coraza_process_response_body
132132
ret = coraza.coraza_process_response_body(tx);
133-
check(ret == 0, "coraza_process_response_body failed: " + ret);
133+
check(ret >= 0, "coraza_process_response_body failed: " + ret);
134134

135135
// coraza_update_status_code
136136
coraza.coraza_update_status_code(tx, 200);

examples/python/simple_get.py

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -108,9 +108,9 @@ def test_lifecycle():
108108
ret = _c.coraza_process_uri(tx, "/someurl?foo=bar", "GET", "HTTP/1.1")
109109
_check(ret == 0, f"coraza_process_uri failed: {ret}")
110110

111-
# coraza_process_request_headers
111+
# coraza_process_request_headers (returns CORAZA_INTERRUPTION for the deny rule)
112112
ret = _c.coraza_process_request_headers(tx)
113-
_check(ret == 0, f"coraza_process_request_headers failed: {ret}")
113+
_check(ret == 1, f"coraza_process_request_headers: expected CORAZA_INTERRUPTION (1), got {ret}")
114114

115115
# coraza_append_request_body (bytes typemap: single argument)
116116
body = b"hello=world"
@@ -119,11 +119,11 @@ def test_lifecycle():
119119

120120
# coraza_process_request_body
121121
ret = _c.coraza_process_request_body(tx)
122-
_check(ret == 0, f"coraza_process_request_body failed: {ret}")
122+
_check(ret >= 0, f"coraza_process_request_body failed: {ret}")
123123

124124
# coraza_process_response_headers
125125
ret = _c.coraza_process_response_headers(tx, 200, "HTTP/1.1")
126-
_check(ret == 0, f"coraza_process_response_headers failed: {ret}")
126+
_check(ret >= 0, f"coraza_process_response_headers failed: {ret}")
127127

128128
# coraza_add_response_header
129129
cname, cvalue = "Content-Type", "text/plain"
@@ -139,7 +139,7 @@ def test_lifecycle():
139139

140140
# coraza_process_response_body
141141
ret = _c.coraza_process_response_body(tx)
142-
_check(ret == 0, f"coraza_process_response_body failed: {ret}")
142+
_check(ret >= 0, f"coraza_process_response_body failed: {ret}")
143143

144144
# coraza_update_status_code
145145
_c.coraza_update_status_code(tx, 200)

libcoraza/coraza.go

Lines changed: 119 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,12 @@ typedef uintptr_t coraza_waf_t;
2222
typedef uintptr_t coraza_transaction_t;
2323
typedef uintptr_t coraza_matched_rule_t;
2424
25+
typedef enum coraza_result_t {
26+
CORAZA_ERROR = -1,
27+
CORAZA_OK = 0,
28+
CORAZA_INTERRUPTION = 1,
29+
} coraza_result_t;
30+
2531
typedef enum coraza_debug_log_level_t {
2632
CORAZA_DEBUG_LOG_LEVEL_UNKNOWN,
2733
CORAZA_DEBUG_LOG_LEVEL_TRACE,
@@ -239,10 +245,14 @@ func coraza_process_connection(t C.coraza_transaction_t, sourceAddress *C.char,
239245
//export coraza_process_request_body
240246
func coraza_process_request_body(t C.coraza_transaction_t) C.int {
241247
tx := fromRaw[types.Transaction](t)
242-
if _, err := tx.ProcessRequestBody(); err != nil {
243-
return 1
248+
it, err := tx.ProcessRequestBody()
249+
if err != nil {
250+
return C.CORAZA_ERROR
244251
}
245-
return 0
252+
if it != nil {
253+
return C.CORAZA_INTERRUPTION
254+
}
255+
return C.CORAZA_OK
246256
}
247257

248258
//export coraza_update_status_code
@@ -277,11 +287,54 @@ func coraza_add_request_header(t C.coraza_transaction_t, name *C.char, name_len
277287
return 0
278288
}
279289

290+
// coraza_add_request_headers adds multiple request headers from a packed buffer.
291+
// Encoding: [name_len u16][name_bytes][value_len u32][value_bytes] × count
292+
//
293+
//export coraza_add_request_headers
294+
func coraza_add_request_headers(t C.coraza_transaction_t, packed *C.char, packed_len C.int, count C.int) C.int {
295+
if packed_len < 0 || count < 0 {
296+
return C.CORAZA_ERROR
297+
}
298+
tx := fromRaw[types.Transaction](t)
299+
buf := C.GoBytes(unsafe.Pointer(packed), packed_len)
300+
off := 0
301+
for i := 0; i < int(count); i++ {
302+
if off+2 > len(buf) {
303+
return C.CORAZA_ERROR
304+
}
305+
nameLen := int(uint16(buf[off])<<8 | uint16(buf[off+1]))
306+
off += 2
307+
if off+nameLen > len(buf) {
308+
return C.CORAZA_ERROR
309+
}
310+
name := string(buf[off : off+nameLen])
311+
off += nameLen
312+
if off+4 > len(buf) {
313+
return C.CORAZA_ERROR
314+
}
315+
vl := uint32(buf[off])<<24 | uint32(buf[off+1])<<16 | uint32(buf[off+2])<<8 | uint32(buf[off+3])
316+
if vl > uint32(len(buf)) {
317+
return C.CORAZA_ERROR
318+
}
319+
valueLen := int(vl)
320+
off += 4
321+
if off+valueLen > len(buf) {
322+
return C.CORAZA_ERROR
323+
}
324+
value := string(buf[off : off+valueLen])
325+
off += valueLen
326+
tx.AddRequestHeader(name, value)
327+
}
328+
return C.CORAZA_OK
329+
}
330+
280331
//export coraza_process_request_headers
281332
func coraza_process_request_headers(t C.coraza_transaction_t) C.int {
282333
tx := fromRaw[types.Transaction](t)
283-
tx.ProcessRequestHeaders()
284-
return 0
334+
if it := tx.ProcessRequestHeaders(); it != nil {
335+
return C.CORAZA_INTERRUPTION
336+
}
337+
return C.CORAZA_OK
285338
}
286339

287340
//export coraza_process_logging
@@ -314,6 +367,47 @@ func coraza_add_response_header(t C.coraza_transaction_t, name *C.char, name_len
314367
return 0
315368
}
316369

370+
// coraza_add_response_headers adds multiple response headers from a packed buffer.
371+
// Same encoding as coraza_add_request_headers.
372+
//
373+
//export coraza_add_response_headers
374+
func coraza_add_response_headers(t C.coraza_transaction_t, packed *C.char, packed_len C.int, count C.int) C.int {
375+
if packed_len < 0 || count < 0 {
376+
return C.CORAZA_ERROR
377+
}
378+
tx := fromRaw[types.Transaction](t)
379+
buf := C.GoBytes(unsafe.Pointer(packed), packed_len)
380+
off := 0
381+
for i := 0; i < int(count); i++ {
382+
if off+2 > len(buf) {
383+
return C.CORAZA_ERROR
384+
}
385+
nameLen := int(uint16(buf[off])<<8 | uint16(buf[off+1]))
386+
off += 2
387+
if off+nameLen > len(buf) {
388+
return C.CORAZA_ERROR
389+
}
390+
name := string(buf[off : off+nameLen])
391+
off += nameLen
392+
if off+4 > len(buf) {
393+
return C.CORAZA_ERROR
394+
}
395+
vl := uint32(buf[off])<<24 | uint32(buf[off+1])<<16 | uint32(buf[off+2])<<8 | uint32(buf[off+3])
396+
if vl > uint32(len(buf)) {
397+
return C.CORAZA_ERROR
398+
}
399+
valueLen := int(vl)
400+
off += 4
401+
if off+valueLen > len(buf) {
402+
return C.CORAZA_ERROR
403+
}
404+
value := string(buf[off : off+valueLen])
405+
off += valueLen
406+
tx.AddResponseHeader(name, value)
407+
}
408+
return C.CORAZA_OK
409+
}
410+
317411
//export coraza_append_response_body
318412
func coraza_append_response_body(t C.coraza_transaction_t, data *C.uchar, length C.int) C.int {
319413
tx := fromRaw[types.Transaction](t)
@@ -326,17 +420,23 @@ func coraza_append_response_body(t C.coraza_transaction_t, data *C.uchar, length
326420
//export coraza_process_response_body
327421
func coraza_process_response_body(t C.coraza_transaction_t) C.int {
328422
tx := fromRaw[types.Transaction](t)
329-
if _, err := tx.ProcessResponseBody(); err != nil {
330-
return 1
423+
it, err := tx.ProcessResponseBody()
424+
if err != nil {
425+
return C.CORAZA_ERROR
331426
}
332-
return 0
427+
if it != nil {
428+
return C.CORAZA_INTERRUPTION
429+
}
430+
return C.CORAZA_OK
333431
}
334432

335433
//export coraza_process_response_headers
336434
func coraza_process_response_headers(t C.coraza_transaction_t, status C.int, proto *C.char) C.int {
337435
tx := fromRaw[types.Transaction](t)
338-
tx.ProcessResponseHeaders(int(status), C.GoString(proto))
339-
return 0
436+
if it := tx.ProcessResponseHeaders(int(status), C.GoString(proto)); it != nil {
437+
return C.CORAZA_INTERRUPTION
438+
}
439+
return C.CORAZA_OK
340440
}
341441

342442
//export coraza_is_response_body_processable
@@ -417,6 +517,15 @@ func coraza_free_waf(t C.coraza_waf_t) C.int {
417517
return 0
418518
}
419519

520+
// coraza_free_string frees a string returned by libcoraza (e.g. from
521+
// coraza_matched_rule_get_error_log). Callers must use this instead of
522+
// libc free() to avoid allocator mismatches on Windows.
523+
//
524+
//export coraza_free_string
525+
func coraza_free_string(s *C.char) {
526+
C.free(unsafe.Pointer(s))
527+
}
528+
420529
/**
421530
* Returns the severity of a matched rule.
422531
* @param[in] pointer to matched rule

0 commit comments

Comments
 (0)