@@ -109,8 +109,8 @@ impl HeaderContext {
109
109
}
110
110
pub async fn handle_row ( self , data : JsonValue ) -> anyhow:: Result < PageContext > {
111
111
log:: debug!( "Handling header row: {data}" ) ;
112
- let comp_opt =
113
- get_object_str ( & data , "component" ) . and_then ( |s| HeaderComponent :: try_from ( s) . ok ( ) ) ;
112
+ let comp_opt = get_object_str_lower_or_upper ( & data , "component" , "COMPONENT" )
113
+ . and_then ( |s| HeaderComponent :: try_from ( s) . ok ( ) ) ;
114
114
match comp_opt {
115
115
Some ( HeaderComponent :: StatusCode ) => self . status_code ( & data) . map ( PageContext :: Header ) ,
116
116
Some ( HeaderComponent :: HttpHeader ) => {
@@ -141,9 +141,7 @@ impl HeaderContext {
141
141
}
142
142
143
143
fn status_code ( mut self , data : & JsonValue ) -> anyhow:: Result < Self > {
144
- let status_code = data
145
- . as_object ( )
146
- . and_then ( |m| m. get ( "status" ) )
144
+ let status_code = get_object_value_lower_or_upper ( data, "status" , "STATUS" )
147
145
. with_context ( || "status_code component requires a status" ) ?
148
146
. as_u64 ( )
149
147
. with_context ( || "status must be a number" ) ?;
@@ -157,7 +155,7 @@ impl HeaderContext {
157
155
fn add_http_header ( mut self , data : & JsonValue ) -> anyhow:: Result < Self > {
158
156
let obj = data. as_object ( ) . with_context ( || "expected object" ) ?;
159
157
for ( name, value) in obj {
160
- if name == "component" {
158
+ if name. eq_ignore_ascii_case ( "component" ) {
161
159
continue ;
162
160
}
163
161
let value_str = value
@@ -173,55 +171,51 @@ impl HeaderContext {
173
171
}
174
172
175
173
fn add_cookie ( mut self , data : & JsonValue ) -> anyhow:: Result < Self > {
176
- let obj = data. as_object ( ) . with_context ( || "expected object" ) ?;
177
- let name = obj
178
- . get ( "name" )
179
- . and_then ( JsonValue :: as_str)
174
+ data. as_object ( ) . with_context ( || "expected object" ) ?;
175
+ let name = get_object_str_lower_or_upper ( data, "name" , "NAME" )
180
176
. with_context ( || "cookie name must be a string" ) ?;
181
177
let mut cookie = actix_web:: cookie:: Cookie :: named ( name) ;
182
178
183
- let path = obj . get ( "path" ) . and_then ( JsonValue :: as_str ) ;
179
+ let path = get_object_str_lower_or_upper ( data , "path" , "PATH" ) ;
184
180
if let Some ( path) = path {
185
181
cookie. set_path ( path) ;
186
182
} else {
187
183
cookie. set_path ( "/" ) ;
188
184
}
189
- let domain = obj . get ( "domain" ) . and_then ( JsonValue :: as_str ) ;
185
+ let domain = get_object_str_lower_or_upper ( data , "domain" , "DOMAIN" ) ;
190
186
if let Some ( domain) = domain {
191
187
cookie. set_domain ( domain) ;
192
188
}
193
189
194
- let remove = obj . get ( "remove" ) ;
190
+ let remove = get_object_value_lower_or_upper ( data , "remove" , "REMOVE ") ;
195
191
if remove == Some ( & json ! ( true ) ) || remove == Some ( & json ! ( 1 ) ) {
196
192
cookie. make_removal ( ) ;
197
193
self . response . cookie ( cookie) ;
198
194
log:: trace!( "Removing cookie {name}" ) ;
199
195
return Ok ( self ) ;
200
196
}
201
197
202
- let value = obj
203
- . get ( "value" )
204
- . and_then ( JsonValue :: as_str)
198
+ let value = get_object_str_lower_or_upper ( data, "value" , "VALUE" )
205
199
. with_context ( || "The 'value' property of the cookie component is required (unless 'remove' is set) and must be a string." ) ?;
206
200
cookie. set_value ( value) ;
207
- let http_only = obj . get ( "http_only" ) ;
201
+ let http_only = get_object_value_lower_or_upper ( data , "http_only" , "HTTP_ONLY ") ;
208
202
cookie. set_http_only ( http_only != Some ( & json ! ( false ) ) && http_only != Some ( & json ! ( 0 ) ) ) ;
209
- let same_site = obj . get ( "same_site" ) . and_then ( Value :: as_str ) ;
203
+ let same_site = get_object_str_lower_or_upper ( data , "same_site" , "SAME_SITE" ) ;
210
204
cookie. set_same_site ( match same_site {
211
205
Some ( "none" ) => actix_web:: cookie:: SameSite :: None ,
212
206
Some ( "lax" ) => actix_web:: cookie:: SameSite :: Lax ,
213
207
None | Some ( "strict" ) => actix_web:: cookie:: SameSite :: Strict , // strict by default
214
208
Some ( other) => bail ! ( "Cookie: invalid value for same_site: {other}" ) ,
215
209
} ) ;
216
- let secure = obj . get ( "secure" ) ;
210
+ let secure = get_object_value_lower_or_upper ( data , "secure" , "SECURE ") ;
217
211
cookie. set_secure ( secure != Some ( & json ! ( false ) ) && secure != Some ( & json ! ( 0 ) ) ) ;
218
- if let Some ( max_age_json) = obj . get ( "max_age" ) {
212
+ if let Some ( max_age_json) = get_object_value_lower_or_upper ( data , "max_age" , "MAX_AGE ") {
219
213
let seconds = max_age_json
220
214
. as_i64 ( )
221
215
. ok_or_else ( || anyhow:: anyhow!( "max_age must be a number, not {max_age_json}" ) ) ?;
222
216
cookie. set_max_age ( Duration :: seconds ( seconds) ) ;
223
217
}
224
- let expires = obj . get ( "expires" ) ;
218
+ let expires = get_object_value_lower_or_upper ( data , "expires" , "EXPIRES ") ;
225
219
if let Some ( expires) = expires {
226
220
cookie. set_expires ( actix_web:: cookie:: Expiration :: DateTime ( match expires {
227
221
JsonValue :: String ( s) => OffsetDateTime :: parse ( s, & Rfc3339 ) ?,
@@ -240,7 +234,7 @@ impl HeaderContext {
240
234
fn redirect ( mut self , data : & JsonValue ) -> anyhow:: Result < HttpResponse > {
241
235
self . response . status ( StatusCode :: FOUND ) ;
242
236
self . has_status = true ;
243
- let link = get_object_str ( data, "link" )
237
+ let link = get_object_str_lower_or_upper ( data, "link" , "LINK ")
244
238
. with_context ( || "The redirect component requires a 'link' property" ) ?;
245
239
self . response . insert_header ( ( header:: LOCATION , link) ) ;
246
240
let response = self . response . body ( ( ) ) ;
@@ -251,15 +245,15 @@ impl HeaderContext {
251
245
fn json ( mut self , data : & JsonValue ) -> anyhow:: Result < PageContext > {
252
246
self . response
253
247
. insert_header ( ( header:: CONTENT_TYPE , "application/json" ) ) ;
254
- if let Some ( contents) = data . get ( "contents" ) {
248
+ if let Some ( contents) = get_object_value_lower_or_upper ( data , "contents" , "CONTENTS ") {
255
249
let json_response = if let Some ( s) = contents. as_str ( ) {
256
250
s. as_bytes ( ) . to_owned ( )
257
251
} else {
258
252
serde_json:: to_vec ( contents) ?
259
253
} ;
260
254
Ok ( PageContext :: Close ( self . response . body ( json_response) ) )
261
255
} else {
262
- let body_type = get_object_str ( data, "type" ) ;
256
+ let body_type = get_object_str_lower_or_upper ( data, "type" , "TYPE ") ;
263
257
let json_renderer = match body_type {
264
258
None | Some ( "array" ) => JsonBodyRenderer :: new_array ( self . writer ) ,
265
259
Some ( "jsonlines" ) => JsonBodyRenderer :: new_jsonlines ( self . writer ) ,
@@ -284,8 +278,8 @@ impl HeaderContext {
284
278
async fn csv ( mut self , options : & JsonValue ) -> anyhow:: Result < PageContext > {
285
279
self . response
286
280
. insert_header ( ( header:: CONTENT_TYPE , "text/csv; charset=utf-8" ) ) ;
287
- if let Some ( filename) =
288
- get_object_str ( options , "filename" ) . or_else ( || get_object_str ( options, "title" ) )
281
+ if let Some ( filename) = get_object_str_lower_or_upper ( options , "filename" , "FILENAME" )
282
+ . or_else ( || get_object_str_lower_or_upper ( options, "title" , "TITLE ") )
289
283
{
290
284
let extension = if filename. contains ( '.' ) { "" } else { ".csv" } ;
291
285
self . response . insert_header ( (
@@ -303,8 +297,8 @@ impl HeaderContext {
303
297
}
304
298
305
299
async fn authentication ( mut self , mut data : JsonValue ) -> anyhow:: Result < PageContext > {
306
- let password_hash = take_object_str ( & mut data, "password_hash" ) ;
307
- let password = take_object_str ( & mut data, "password" ) ;
300
+ let password_hash = take_object_str_lower_or_upper ( & mut data, "password_hash" , "PASSWORD_HASH ") ;
301
+ let password = take_object_str_lower_or_upper ( & mut data, "password" , "PASSWORD ") ;
308
302
if let ( Some ( password) , Some ( password_hash) ) = ( password, password_hash) {
309
303
log:: debug!( "Authentication with password_hash = {password_hash:?}" ) ;
310
304
match verify_password_async ( password_hash, password) . await ? {
@@ -314,7 +308,7 @@ impl HeaderContext {
314
308
}
315
309
log:: debug!( "Authentication failed" ) ;
316
310
// The authentication failed
317
- let http_response: HttpResponse = if let Some ( link) = get_object_str ( & data, "link" ) {
311
+ let http_response: HttpResponse = if let Some ( link) = get_object_str_lower_or_upper ( & data, "link" , "LINK ") {
318
312
self . response
319
313
. status ( StatusCode :: FOUND )
320
314
. insert_header ( ( header:: LOCATION , link) )
@@ -332,13 +326,13 @@ impl HeaderContext {
332
326
}
333
327
334
328
fn download ( mut self , options : & JsonValue ) -> anyhow:: Result < PageContext > {
335
- if let Some ( filename) = get_object_str ( options, "filename" ) {
329
+ if let Some ( filename) = get_object_str_lower_or_upper ( options, "filename" , "FILENAME ") {
336
330
self . response . insert_header ( (
337
331
header:: CONTENT_DISPOSITION ,
338
332
format ! ( "attachment; filename=\" {filename}\" " ) ,
339
333
) ) ;
340
334
}
341
- let data_url = get_object_str ( options, "data_url" )
335
+ let data_url = get_object_str_lower_or_upper ( options, "data_url" , "DATA_URL ")
342
336
. with_context ( || "The download component requires a 'data_url' property" ) ?;
343
337
let rest = data_url
344
338
. strip_prefix ( "data:" )
@@ -412,6 +406,39 @@ fn take_object_str(json: &mut JsonValue, key: &str) -> Option<String> {
412
406
}
413
407
}
414
408
409
+ #[ inline]
410
+ fn get_object_value_lower_or_upper < ' a > ( json : & ' a JsonValue , lower : & str , upper : & str ) -> Option < & ' a JsonValue > {
411
+ json. as_object ( )
412
+ . and_then ( |obj| obj. get ( lower) . or_else ( || obj. get ( upper) ) )
413
+ }
414
+
415
+ #[ inline]
416
+ fn get_object_str_lower_or_upper < ' a > ( json : & ' a JsonValue , lower : & str , upper : & str ) -> Option < & ' a str > {
417
+ get_object_value_lower_or_upper ( json, lower, upper) . and_then ( JsonValue :: as_str)
418
+ }
419
+
420
+ #[ inline]
421
+ fn take_object_str_lower_or_upper ( json : & mut JsonValue , lower : & str , upper : & str ) -> Option < String > {
422
+ if let Some ( v) = json. get_mut ( lower) {
423
+ match v. take ( ) {
424
+ JsonValue :: String ( s) => return Some ( s) ,
425
+ other => {
426
+ // put it back if not a string
427
+ * v = other;
428
+ }
429
+ }
430
+ }
431
+ if let Some ( v) = json. get_mut ( upper) {
432
+ match v. take ( ) {
433
+ JsonValue :: String ( s) => return Some ( s) ,
434
+ other => {
435
+ * v = other;
436
+ }
437
+ }
438
+ }
439
+ None
440
+ }
441
+
415
442
/**
416
443
* Can receive rows, and write them in a given format to an `io::Write`
417
444
*/
@@ -553,26 +580,25 @@ impl CsvBodyRenderer {
553
580
options : & JsonValue ,
554
581
) -> anyhow:: Result < CsvBodyRenderer > {
555
582
let mut builder = csv_async:: AsyncWriterBuilder :: new ( ) ;
556
- if let Some ( separator) = get_object_str ( options, "separator" ) {
583
+ if let Some ( separator) = get_object_str_lower_or_upper ( options, "separator" , "SEPARATOR ") {
557
584
let & [ separator_byte] = separator. as_bytes ( ) else {
558
585
bail ! ( "Invalid csv separator: {separator:?}. It must be a single byte." ) ;
559
586
} ;
560
587
builder. delimiter ( separator_byte) ;
561
588
}
562
- if let Some ( quote) = get_object_str ( options, "quote" ) {
589
+ if let Some ( quote) = get_object_str_lower_or_upper ( options, "quote" , "QUOTE ") {
563
590
let & [ quote_byte] = quote. as_bytes ( ) else {
564
591
bail ! ( "Invalid csv quote: {quote:?}. It must be a single byte." ) ;
565
592
} ;
566
593
builder. quote ( quote_byte) ;
567
594
}
568
- if let Some ( escape) = get_object_str ( options, "escape" ) {
595
+ if let Some ( escape) = get_object_str_lower_or_upper ( options, "escape" , "ESCAPE ") {
569
596
let & [ escape_byte] = escape. as_bytes ( ) else {
570
597
bail ! ( "Invalid csv escape: {escape:?}. It must be a single byte." ) ;
571
598
} ;
572
599
builder. escape ( escape_byte) ;
573
600
}
574
- if options
575
- . get ( "bom" )
601
+ if get_object_value_lower_or_upper ( options, "bom" , "BOM" )
576
602
. and_then ( JsonValue :: as_bool)
577
603
. unwrap_or ( false )
578
604
{
@@ -671,7 +697,7 @@ impl<W: std::io::Write> HtmlRenderContext<W> {
671
697
672
698
if !initial_rows
673
699
. first ( )
674
- . and_then ( |c| get_object_str ( c, "component" ) )
700
+ . and_then ( |c| get_object_str_lower_or_upper ( c, "component" , "COMPONENT ") )
675
701
. is_some_and ( Self :: is_shell_component)
676
702
{
677
703
let default_shell = if request_context. is_embedded {
@@ -690,8 +716,8 @@ impl<W: std::io::Write> HtmlRenderContext<W> {
690
716
let shell_row = rows_iter
691
717
. next ( )
692
718
. expect ( "shell row should exist at this point" ) ;
693
- let mut shell_component =
694
- get_object_str ( & shell_row , "component" ) . expect ( "shell should exist" ) ;
719
+ let mut shell_component = get_object_str_lower_or_upper ( & shell_row , "component" , "COMPONENT" )
720
+ . expect ( "shell should exist" ) ;
695
721
if request_context. is_embedded && shell_component != FRAGMENT_SHELL_COMPONENT {
696
722
log:: warn!(
697
723
"Embedded pages cannot use a shell component! Ignoring the '{shell_component}' component and its properties: {shell_row}"
@@ -759,7 +785,7 @@ impl<W: std::io::Write> HtmlRenderContext<W> {
759
785
}
760
786
761
787
pub async fn handle_row ( & mut self , data : & JsonValue ) -> anyhow:: Result < ( ) > {
762
- let new_component = get_object_str ( data, "component" ) ;
788
+ let new_component = get_object_str_lower_or_upper ( data, "component" , "COMPONENT ") ;
763
789
let current_component = self
764
790
. current_component
765
791
. as_ref ( )
@@ -914,15 +940,15 @@ fn handle_log_component(
914
940
current_statement : Option < usize > ,
915
941
data : & JsonValue ,
916
942
) -> anyhow:: Result < ( ) > {
917
- let level_name = get_object_str ( data, "level" ) . unwrap_or ( "info" ) ;
943
+ let level_name = get_object_str_lower_or_upper ( data, "level" , "LEVEL ") . unwrap_or ( "info" ) ;
918
944
let log_level = log:: Level :: from_str ( level_name) . with_context ( || "Invalid log level value" ) ?;
919
945
920
946
let mut target = format ! ( "sqlpage::log from \" {}\" " , source_path. display( ) ) ;
921
947
if let Some ( current_statement) = current_statement {
922
948
write ! ( & mut target, " statement {current_statement}" ) ?;
923
949
}
924
950
925
- let message = get_object_str ( data, "message" ) . context ( "log: missing property 'message'" ) ?;
951
+ let message = get_object_str_lower_or_upper ( data, "message" , "MESSAGE ") . context ( "log: missing property 'message'" ) ?;
926
952
log:: log!( target: & target, log_level, "{message}" ) ;
927
953
Ok ( ( ) )
928
954
}
0 commit comments