llhttp 0.0.1 → 0.3.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/CHANGELOG.md +44 -0
- data/LICENSE +353 -22
- data/ext/llhttp/api.c +386 -0
- data/ext/llhttp/extconf.rb +2 -3
- data/ext/llhttp/http.c +172 -0
- data/ext/llhttp/llhttp.c +14949 -0
- data/ext/llhttp/llhttp.h +587 -0
- data/ext/llhttp/llhttp_ext.c +314 -0
- data/lib/llhttp/delegate.rb +79 -19
- data/lib/llhttp/error.rb +2 -0
- data/lib/llhttp/parser.rb +27 -6
- data/lib/llhttp/version.rb +3 -1
- metadata +12 -8
- data/lib/llhttp/llhttp_ext.bundle +0 -0
@@ -0,0 +1,314 @@
|
|
1
|
+
/*
|
2
|
+
This software is licensed under the MPL-2.0 License.
|
3
|
+
|
4
|
+
Copyright Bryan Powell, 2020.
|
5
|
+
*/
|
6
|
+
|
7
|
+
#include <ruby/ruby.h>
|
8
|
+
|
9
|
+
#include "llhttp.h"
|
10
|
+
|
11
|
+
static VALUE mLLHttp, cParser, eError;
|
12
|
+
|
13
|
+
static ID rb_llhttp_callback_on_message_begin;
|
14
|
+
static ID rb_llhttp_callback_on_url;
|
15
|
+
static ID rb_llhttp_callback_on_status;
|
16
|
+
static ID rb_llhttp_callback_on_header_field;
|
17
|
+
static ID rb_llhttp_callback_on_header_value;
|
18
|
+
static ID rb_llhttp_callback_on_headers_complete;
|
19
|
+
static ID rb_llhttp_callback_on_body;
|
20
|
+
static ID rb_llhttp_callback_on_message_complete;
|
21
|
+
static ID rb_llhttp_callback_on_chunk_header;
|
22
|
+
static ID rb_llhttp_callback_on_chunk_complete;
|
23
|
+
static ID rb_llhttp_callback_on_url_complete;
|
24
|
+
static ID rb_llhttp_callback_on_status_complete;
|
25
|
+
static ID rb_llhttp_callback_on_header_field_complete;
|
26
|
+
static ID rb_llhttp_callback_on_header_value_complete;
|
27
|
+
|
28
|
+
static void rb_llhttp_free(llhttp_t *parser) {
|
29
|
+
if (parser) {
|
30
|
+
free(parser->settings);
|
31
|
+
free(parser);
|
32
|
+
}
|
33
|
+
}
|
34
|
+
|
35
|
+
VALUE rb_llhttp_allocate(VALUE klass) {
|
36
|
+
llhttp_t *parser = (llhttp_t *)malloc(sizeof(llhttp_t));
|
37
|
+
llhttp_settings_t *settings = (llhttp_settings_t *)malloc(sizeof(llhttp_settings_t));
|
38
|
+
|
39
|
+
llhttp_settings_init(settings);
|
40
|
+
llhttp_init(parser, HTTP_BOTH, settings);
|
41
|
+
|
42
|
+
return Data_Wrap_Struct(klass, 0, rb_llhttp_free, parser);
|
43
|
+
}
|
44
|
+
|
45
|
+
VALUE rb_llhttp_callback_call(VALUE delegate, ID method) {
|
46
|
+
return rb_funcall(delegate, method, 0);
|
47
|
+
}
|
48
|
+
|
49
|
+
void rb_llhttp_data_callback_call(VALUE delegate, ID method, char *data, size_t length) {
|
50
|
+
rb_funcall(delegate, method, 1, rb_str_new(data, length));
|
51
|
+
}
|
52
|
+
|
53
|
+
int rb_llhttp_on_message_begin(llhttp_t *parser) {
|
54
|
+
return NUM2INT(rb_llhttp_callback_call((VALUE)parser->data, rb_llhttp_callback_on_message_begin));
|
55
|
+
}
|
56
|
+
|
57
|
+
int rb_llhttp_on_headers_complete(llhttp_t *parser) {
|
58
|
+
return NUM2INT(rb_llhttp_callback_call((VALUE)parser->data, rb_llhttp_callback_on_headers_complete));
|
59
|
+
}
|
60
|
+
|
61
|
+
int rb_llhttp_on_message_complete(llhttp_t *parser) {
|
62
|
+
return NUM2INT(rb_llhttp_callback_call((VALUE)parser->data, rb_llhttp_callback_on_message_complete));
|
63
|
+
}
|
64
|
+
|
65
|
+
int rb_llhttp_on_chunk_header(llhttp_t *parser) {
|
66
|
+
return NUM2INT(rb_llhttp_callback_call((VALUE)parser->data, rb_llhttp_callback_on_chunk_header));
|
67
|
+
}
|
68
|
+
|
69
|
+
int rb_llhttp_on_url(llhttp_t *parser, char *data, size_t length) {
|
70
|
+
rb_llhttp_data_callback_call((VALUE)parser->data, rb_llhttp_callback_on_url, data, length);
|
71
|
+
|
72
|
+
return 0;
|
73
|
+
}
|
74
|
+
|
75
|
+
int rb_llhttp_on_status(llhttp_t *parser, char *data, size_t length) {
|
76
|
+
rb_llhttp_data_callback_call((VALUE)parser->data, rb_llhttp_callback_on_status, data, length);
|
77
|
+
|
78
|
+
return 0;
|
79
|
+
}
|
80
|
+
|
81
|
+
int rb_llhttp_on_header_field(llhttp_t *parser, char *data, size_t length) {
|
82
|
+
rb_llhttp_data_callback_call((VALUE)parser->data, rb_llhttp_callback_on_header_field, data, length);
|
83
|
+
|
84
|
+
return 0;
|
85
|
+
}
|
86
|
+
|
87
|
+
int rb_llhttp_on_header_value(llhttp_t *parser, char *data, size_t length) {
|
88
|
+
rb_llhttp_data_callback_call((VALUE)parser->data, rb_llhttp_callback_on_header_value, data, length);
|
89
|
+
|
90
|
+
return 0;
|
91
|
+
}
|
92
|
+
|
93
|
+
int rb_llhttp_on_body(llhttp_t *parser, char *data, size_t length) {
|
94
|
+
rb_llhttp_data_callback_call((VALUE)parser->data, rb_llhttp_callback_on_body, data, length);
|
95
|
+
|
96
|
+
return 0;
|
97
|
+
}
|
98
|
+
|
99
|
+
int rb_llhttp_on_chunk_complete(llhttp_t *parser) {
|
100
|
+
rb_llhttp_callback_call((VALUE)parser->data, rb_llhttp_callback_on_chunk_complete);
|
101
|
+
|
102
|
+
return 0;
|
103
|
+
}
|
104
|
+
|
105
|
+
int rb_llhttp_on_url_complete(llhttp_t *parser) {
|
106
|
+
rb_llhttp_callback_call((VALUE)parser->data, rb_llhttp_callback_on_url_complete);
|
107
|
+
|
108
|
+
return 0;
|
109
|
+
}
|
110
|
+
|
111
|
+
int rb_llhttp_on_status_complete(llhttp_t *parser) {
|
112
|
+
rb_llhttp_callback_call((VALUE)parser->data, rb_llhttp_callback_on_status_complete);
|
113
|
+
|
114
|
+
return 0;
|
115
|
+
}
|
116
|
+
|
117
|
+
int rb_llhttp_on_header_field_complete(llhttp_t *parser) {
|
118
|
+
rb_llhttp_callback_call((VALUE)parser->data, rb_llhttp_callback_on_header_field_complete);
|
119
|
+
|
120
|
+
return 0;
|
121
|
+
}
|
122
|
+
|
123
|
+
int rb_llhttp_on_header_value_complete(llhttp_t *parser) {
|
124
|
+
rb_llhttp_callback_call((VALUE)parser->data, rb_llhttp_callback_on_header_value_complete);
|
125
|
+
|
126
|
+
return 0;
|
127
|
+
}
|
128
|
+
|
129
|
+
VALUE rb_llhttp_parse(VALUE self, VALUE data) {
|
130
|
+
llhttp_t *parser;
|
131
|
+
|
132
|
+
Data_Get_Struct(self, llhttp_t, parser);
|
133
|
+
|
134
|
+
enum llhttp_errno err = llhttp_execute(parser, RSTRING_PTR(data), RSTRING_LEN(data));
|
135
|
+
|
136
|
+
if (err != HPE_OK) {
|
137
|
+
rb_raise(eError, "Error Parsing data: %s %s", llhttp_errno_name(err), parser->reason);
|
138
|
+
}
|
139
|
+
|
140
|
+
return Qtrue;
|
141
|
+
}
|
142
|
+
|
143
|
+
VALUE rb_llhttp_finish(VALUE self) {
|
144
|
+
llhttp_t *parser;
|
145
|
+
|
146
|
+
Data_Get_Struct(self, llhttp_t, parser);
|
147
|
+
|
148
|
+
enum llhttp_errno err = llhttp_finish(parser);
|
149
|
+
|
150
|
+
if (err != HPE_OK) {
|
151
|
+
rb_raise(eError, "Error Parsing data: %s %s", llhttp_errno_name(err), parser->reason);
|
152
|
+
}
|
153
|
+
|
154
|
+
return Qtrue;
|
155
|
+
}
|
156
|
+
|
157
|
+
VALUE rb_llhttp_content_length(VALUE self) {
|
158
|
+
llhttp_t *parser;
|
159
|
+
|
160
|
+
Data_Get_Struct(self, llhttp_t, parser);
|
161
|
+
|
162
|
+
return ULL2NUM(parser->content_length);
|
163
|
+
}
|
164
|
+
|
165
|
+
VALUE rb_llhttp_method_name(VALUE self) {
|
166
|
+
llhttp_t *parser;
|
167
|
+
|
168
|
+
Data_Get_Struct(self, llhttp_t, parser);
|
169
|
+
|
170
|
+
return rb_str_new_cstr(llhttp_method_name(parser->method));
|
171
|
+
}
|
172
|
+
|
173
|
+
VALUE rb_llhttp_status_code(VALUE self) {
|
174
|
+
llhttp_t *parser;
|
175
|
+
|
176
|
+
Data_Get_Struct(self, llhttp_t, parser);
|
177
|
+
|
178
|
+
return UINT2NUM(parser->status_code);
|
179
|
+
}
|
180
|
+
|
181
|
+
VALUE rb_llhttp_http_major(VALUE self) {
|
182
|
+
llhttp_t *parser;
|
183
|
+
|
184
|
+
Data_Get_Struct(self, llhttp_t, parser);
|
185
|
+
|
186
|
+
return UINT2NUM(parser->http_major);
|
187
|
+
}
|
188
|
+
|
189
|
+
VALUE rb_llhttp_http_minor(VALUE self) {
|
190
|
+
llhttp_t *parser;
|
191
|
+
|
192
|
+
Data_Get_Struct(self, llhttp_t, parser);
|
193
|
+
|
194
|
+
return UINT2NUM(parser->http_minor);
|
195
|
+
}
|
196
|
+
|
197
|
+
VALUE rb_llhttp_keep_alive(VALUE self) {
|
198
|
+
llhttp_t *parser;
|
199
|
+
|
200
|
+
Data_Get_Struct(self, llhttp_t, parser);
|
201
|
+
|
202
|
+
int ret = llhttp_should_keep_alive(parser);
|
203
|
+
|
204
|
+
return ret == 1 ? Qtrue : Qfalse;
|
205
|
+
}
|
206
|
+
|
207
|
+
static VALUE rb_llhttp_init(VALUE self, VALUE type) {
|
208
|
+
llhttp_t *parser;
|
209
|
+
|
210
|
+
Data_Get_Struct(self, llhttp_t, parser);
|
211
|
+
|
212
|
+
llhttp_settings_t *settings = parser->settings;
|
213
|
+
|
214
|
+
VALUE delegate = rb_iv_get(self, "@delegate");
|
215
|
+
|
216
|
+
rb_llhttp_callback_on_message_begin = rb_intern("internal_on_message_begin");
|
217
|
+
rb_llhttp_callback_on_headers_complete = rb_intern("internal_on_headers_complete");
|
218
|
+
rb_llhttp_callback_on_message_complete = rb_intern("internal_on_message_complete");
|
219
|
+
rb_llhttp_callback_on_chunk_header = rb_intern("internal_on_chunk_header");
|
220
|
+
rb_llhttp_callback_on_url = rb_intern("on_url");
|
221
|
+
rb_llhttp_callback_on_status = rb_intern("on_status");
|
222
|
+
rb_llhttp_callback_on_header_field = rb_intern("on_header_field");
|
223
|
+
rb_llhttp_callback_on_header_value = rb_intern("on_header_value");
|
224
|
+
rb_llhttp_callback_on_body = rb_intern("on_body");
|
225
|
+
rb_llhttp_callback_on_chunk_complete = rb_intern("on_chunk_complete");
|
226
|
+
rb_llhttp_callback_on_url_complete = rb_intern("on_url_complete");
|
227
|
+
rb_llhttp_callback_on_status_complete = rb_intern("on_status_complete");
|
228
|
+
rb_llhttp_callback_on_header_field_complete = rb_intern("on_header_field_complete");
|
229
|
+
rb_llhttp_callback_on_header_value_complete = rb_intern("on_header_value_complete");
|
230
|
+
|
231
|
+
if (rb_respond_to(delegate, rb_intern("on_message_begin"))) {
|
232
|
+
settings->on_message_begin = (llhttp_cb)rb_llhttp_on_message_begin;
|
233
|
+
}
|
234
|
+
|
235
|
+
if (rb_respond_to(delegate, rb_intern("on_headers_complete"))) {
|
236
|
+
settings->on_headers_complete = (llhttp_cb)rb_llhttp_on_headers_complete;
|
237
|
+
}
|
238
|
+
|
239
|
+
if (rb_respond_to(delegate, rb_intern("on_message_complete"))) {
|
240
|
+
settings->on_message_complete = (llhttp_cb)rb_llhttp_on_message_complete;
|
241
|
+
}
|
242
|
+
|
243
|
+
if (rb_respond_to(delegate, rb_intern("on_chunk_header"))) {
|
244
|
+
settings->on_chunk_header = (llhttp_cb)rb_llhttp_on_chunk_header;
|
245
|
+
}
|
246
|
+
|
247
|
+
if (rb_respond_to(delegate, rb_llhttp_callback_on_url)) {
|
248
|
+
settings->on_url = (llhttp_data_cb)rb_llhttp_on_url;
|
249
|
+
}
|
250
|
+
|
251
|
+
if (rb_respond_to(delegate, rb_llhttp_callback_on_status)) {
|
252
|
+
settings->on_status = (llhttp_data_cb)rb_llhttp_on_status;
|
253
|
+
}
|
254
|
+
|
255
|
+
if (rb_respond_to(delegate, rb_llhttp_callback_on_header_field)) {
|
256
|
+
settings->on_header_field = (llhttp_data_cb)rb_llhttp_on_header_field;
|
257
|
+
}
|
258
|
+
|
259
|
+
if (rb_respond_to(delegate, rb_llhttp_callback_on_header_value)) {
|
260
|
+
settings->on_header_value = (llhttp_data_cb)rb_llhttp_on_header_value;
|
261
|
+
}
|
262
|
+
|
263
|
+
if (rb_respond_to(delegate, rb_llhttp_callback_on_body)) {
|
264
|
+
settings->on_body = (llhttp_data_cb)rb_llhttp_on_body;
|
265
|
+
}
|
266
|
+
|
267
|
+
if (rb_respond_to(delegate, rb_llhttp_callback_on_chunk_complete)) {
|
268
|
+
settings->on_chunk_complete = (llhttp_cb)rb_llhttp_on_chunk_complete;
|
269
|
+
}
|
270
|
+
|
271
|
+
if (rb_respond_to(delegate, rb_llhttp_callback_on_url_complete)) {
|
272
|
+
settings->on_url_complete = (llhttp_cb)rb_llhttp_on_url_complete;
|
273
|
+
}
|
274
|
+
|
275
|
+
if (rb_respond_to(delegate, rb_llhttp_callback_on_status_complete)) {
|
276
|
+
settings->on_status_complete = (llhttp_cb)rb_llhttp_on_status_complete;
|
277
|
+
}
|
278
|
+
|
279
|
+
if (rb_respond_to(delegate, rb_llhttp_callback_on_header_field_complete)) {
|
280
|
+
settings->on_header_field_complete = (llhttp_cb)rb_llhttp_on_header_field_complete;
|
281
|
+
}
|
282
|
+
|
283
|
+
if (rb_respond_to(delegate, rb_llhttp_callback_on_header_value_complete)) {
|
284
|
+
settings->on_header_value_complete = (llhttp_cb)rb_llhttp_on_header_value_complete;
|
285
|
+
}
|
286
|
+
|
287
|
+
llhttp_init(parser, FIX2INT(type), settings);
|
288
|
+
|
289
|
+
parser->data = (void*)delegate;
|
290
|
+
|
291
|
+
return Qtrue;
|
292
|
+
}
|
293
|
+
|
294
|
+
void Init_llhttp_ext(void) {
|
295
|
+
mLLHttp = rb_const_get(rb_cObject, rb_intern("LLHttp"));
|
296
|
+
cParser = rb_const_get(mLLHttp, rb_intern("Parser"));
|
297
|
+
eError = rb_const_get(mLLHttp, rb_intern("Error"));
|
298
|
+
|
299
|
+
rb_define_alloc_func(cParser, rb_llhttp_allocate);
|
300
|
+
|
301
|
+
rb_define_method(cParser, "<<", rb_llhttp_parse, 1);
|
302
|
+
rb_define_method(cParser, "parse", rb_llhttp_parse, 1);
|
303
|
+
rb_define_method(cParser, "finish", rb_llhttp_finish, 0);
|
304
|
+
|
305
|
+
rb_define_method(cParser, "content_length", rb_llhttp_content_length, 0);
|
306
|
+
rb_define_method(cParser, "method_name", rb_llhttp_method_name, 0);
|
307
|
+
rb_define_method(cParser, "status_code", rb_llhttp_status_code, 0);
|
308
|
+
rb_define_method(cParser, "http_major", rb_llhttp_http_major, 0);
|
309
|
+
rb_define_method(cParser, "http_minor", rb_llhttp_http_minor, 0);
|
310
|
+
|
311
|
+
rb_define_method(cParser, "keep_alive?", rb_llhttp_keep_alive, 0);
|
312
|
+
|
313
|
+
rb_define_private_method(cParser, "llhttp_init", rb_llhttp_init, 1);
|
314
|
+
}
|
data/lib/llhttp/delegate.rb
CHANGED
@@ -1,37 +1,97 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
3
|
module LLHttp
|
4
|
-
# Delegate for handling callbacks. Subclass this
|
4
|
+
# [public] Delegate for handling callbacks. Subclass this object and implement necessary methods.
|
5
|
+
#
|
6
|
+
# class Delegate < LLHttp::Delegate
|
7
|
+
# def on_message_begin
|
8
|
+
# ...
|
9
|
+
# end
|
10
|
+
#
|
11
|
+
# def on_url(url)
|
12
|
+
# ...
|
13
|
+
# end
|
14
|
+
#
|
15
|
+
# def on_status(status)
|
16
|
+
# ...
|
17
|
+
# end
|
18
|
+
#
|
19
|
+
# def on_header_field(field)
|
20
|
+
# ...
|
21
|
+
# end
|
22
|
+
#
|
23
|
+
# def on_header_value(value)
|
24
|
+
# ...
|
25
|
+
# end
|
26
|
+
#
|
27
|
+
# def on_headers_complete
|
28
|
+
# ...
|
29
|
+
# end
|
30
|
+
#
|
31
|
+
# def on_body(body)
|
32
|
+
# ...
|
33
|
+
# end
|
34
|
+
#
|
35
|
+
# def on_message_complete
|
36
|
+
# ...
|
37
|
+
# end
|
38
|
+
#
|
39
|
+
# def on_chunk_header
|
40
|
+
# ...
|
41
|
+
# end
|
42
|
+
#
|
43
|
+
# def on_chunk_complete
|
44
|
+
# ...
|
45
|
+
# end
|
46
|
+
#
|
47
|
+
# def on_url_complete
|
48
|
+
# ...
|
49
|
+
# end
|
50
|
+
#
|
51
|
+
# def on_status_complete
|
52
|
+
# ...
|
53
|
+
# end
|
54
|
+
#
|
55
|
+
# def on_header_field_complete
|
56
|
+
# ...
|
57
|
+
# end
|
58
|
+
#
|
59
|
+
# def on_header_value_complete
|
60
|
+
# ...
|
61
|
+
# end
|
62
|
+
# end
|
5
63
|
#
|
6
64
|
class Delegate
|
7
|
-
def
|
8
|
-
|
65
|
+
private def internal_on_message_begin
|
66
|
+
on_message_begin
|
9
67
|
|
10
|
-
|
68
|
+
0
|
69
|
+
rescue
|
70
|
+
-1
|
11
71
|
end
|
12
72
|
|
13
|
-
def
|
14
|
-
|
73
|
+
private def internal_on_headers_complete
|
74
|
+
on_headers_complete
|
15
75
|
|
16
|
-
|
76
|
+
0
|
77
|
+
rescue
|
78
|
+
-1
|
17
79
|
end
|
18
80
|
|
19
|
-
def
|
20
|
-
|
81
|
+
private def internal_on_message_complete
|
82
|
+
on_message_complete
|
21
83
|
|
22
|
-
|
84
|
+
0
|
85
|
+
rescue
|
86
|
+
-1
|
23
87
|
end
|
24
88
|
|
25
|
-
def
|
26
|
-
|
27
|
-
|
28
|
-
def on_message_complete
|
29
|
-
end
|
30
|
-
|
31
|
-
def on_chunk_header
|
32
|
-
end
|
89
|
+
private def internal_on_chunk_header
|
90
|
+
on_chunk_header
|
33
91
|
|
34
|
-
|
92
|
+
0
|
93
|
+
rescue
|
94
|
+
-1
|
35
95
|
end
|
36
96
|
end
|
37
97
|
end
|
data/lib/llhttp/error.rb
CHANGED
data/lib/llhttp/parser.rb
CHANGED
@@ -1,19 +1,40 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
3
|
module LLHttp
|
4
|
-
# Wraps an llhttp context for parsing http requests and responses.
|
4
|
+
# [public] Wraps an llhttp context for parsing http requests and responses.
|
5
5
|
#
|
6
|
-
#
|
6
|
+
# class Delegate < LLHttp::Delegate
|
7
|
+
# def on_message_begin
|
8
|
+
# ...
|
9
|
+
# end
|
7
10
|
#
|
8
|
-
#
|
11
|
+
# ...
|
12
|
+
# end
|
9
13
|
#
|
10
|
-
# =
|
14
|
+
# parser = LLHttp::Parser.new(Delegate.new, type: :request)
|
15
|
+
# parser << "GET / HTTP/1.1\r\n\r\n"
|
16
|
+
# parser.finish
|
11
17
|
#
|
12
|
-
#
|
18
|
+
# ...
|
19
|
+
#
|
20
|
+
# Introspection
|
21
|
+
#
|
22
|
+
# * `LLHttp::Parser#content_length` returns the content length of the current request.
|
23
|
+
# * `LLHttp::Parser#method_name` returns the method name of the current response.
|
24
|
+
# * `LLHttp::Parser#status_code` returns the status code of the current response.
|
25
|
+
# * `LLHttp::Parser#http_major` returns the major http version of the current request/response.
|
26
|
+
# * `LLHttp::Parser#http_minor` returns the minor http version of the current request/response.
|
27
|
+
# * `LLHttp::Parser#keep_alive?` returns `true` if there might be more messages.
|
28
|
+
#
|
29
|
+
# Finishing
|
30
|
+
#
|
31
|
+
# Call `LLHttp::Parser#finish` when processing is complete for the current request or response.
|
13
32
|
#
|
14
33
|
class Parser
|
15
34
|
LLHTTP_TYPES = {both: 0, request: 1, response: 2}.freeze
|
16
35
|
|
36
|
+
# [public] The parser type; one of: `both`, `request`, or `response`.
|
37
|
+
#
|
17
38
|
attr_reader :type
|
18
39
|
|
19
40
|
def initialize(delegate, type: :both)
|
@@ -24,4 +45,4 @@ module LLHttp
|
|
24
45
|
end
|
25
46
|
end
|
26
47
|
|
27
|
-
require_relative "llhttp_ext"
|
48
|
+
require_relative "../llhttp_ext"
|