source: project/wiki/eggref/4/spiffy-request-vars @ 36372

Last change on this file since 36372 was 36372, checked in by Mario Domenech Goulart, 12 months ago

eggref/4/spiffy-request-vars: release notes for version 0.19

  • Property svnwiki:title set to spiffy-request-vars
File size: 16.7 KB
RevLine 
[18268]1== spiffy-request-vars
[16886]2
[23452]3[[toc:]]
4
[16886]5=== Introduction
6
7{{spiffy-request-vars}} provides easy access to variables from HTTP requests.
8
9=== Author
10
[27938]11[[/users/mario-domenech-goulart|Mario Domenech Goulart]]
[16886]12
[27938]13Thanks to [[/users/moritz-heidkamp|Moritz Heidkamp]] for the implementation of the content body reader and for several terminology suggestions (including the egg name).  Also thanks to [[/users/peter-bex|Peter Bex]] for the helpful discussions and lots of implementation suggestions.
[16886]14
15
[18020]16=== Procedures parameters and macros
[16886]17
[23456]18==== Procedure
19
20===== request-vars
[32335]21<procedure>(request-vars #!key (source 'request-method) max-content-length)</procedure>
[16886]22
[18020]23{{request-vars}} returns a procedure which can be used to access variables from the HTTP request.  The returned procedure takes the name of the variable (either a symbol or a string) as argument. You can (optionally) also pass a converter procedure to be used as a type converter for the variable value (see Converter procedures) or a default value.
[16886]24
25{{request-vars}} accepts some keyword arguments:
26
[32335]27; {{source}} : {{'query-string}} tells {{request-vars}} to parse the query string only (for GET variables). {{'request-body}} tells {{request-vars}} to parse the request body only (e.g., for POST variables). {{'both}} tells {{request-vars}} to parse both the request body and the query string. {{'request-method}} tells {{request-vars}} to parse only the source that matches the request method (e.g., for a {{GET}} request, only the query string will be read; for a {{POST}} request, only the request body will be read). The default value for {{source}} is {{'request-method}} (since version 0.18 -- previous versions used {{'both}}). Notice that when {{'both}} is used, variables from the request body have precedence over the ones from the query string.  '''Warning''': using {{'both}} as source for {{request-vars}} may be a security issue (see the release notes for version 0.18 for more details).
[16886]28
29; {{max-content-length}} : the maximum content length (in characters) to be read from the request body.  Default is {{#f}} (no limit).
30
[18020]31==== Converter procedures
32
33The following procedures are intended to be used by the procedure returned by {{request-vars}}.  The {{variables/values}} parameter is an alist mapping variable names to their corresponding values, resulting from parsing the request.
34
[23456]35===== as-string
36<procedure>(as-string variable variables/values)</procedure>
37
38If the given variable is set, return its value as a string (that's the default behavior if no converter is specified).
39
40
41===== as-symbol
42<procedure>(as-symbol variable variables/values)</procedure>
43
44If the given variable is set, convert its value to a symbol using {{string->symbol}}.
45
46
47===== as-number
[18020]48<procedure>(as-number variable variables/values)</procedure>
49
50If the given variable is set, convert its value to a number using {{string->number}}.
51
[23456]52
53===== as-boolean
[18020]54<procedure>(as-boolean variable variables/values)</procedure>
55
[19278]56If the variable is set and its value is one of the values yield by the {{true-boolean-values}} parameter, return {{#t}}, otherwise return {{#f}}. It also returns {{#t}} if the variable is passed in the request but is not bound to any value.
[18020]57
58
[23456]59===== as-list
[18020]60<procedure>(as-list variable variables/values)</procedure>
61
62If the variable is set once, returns a list with a single element (the value for the given variable).  If the variable is set multiple times, return a list with the multiple values for the variable.  If the variable is not set, return {{#f}}.
63
64
[23456]65===== as-alist
[18257]66<procedure>(as-alist variable variables/values)</procedure>
67
68Returns an alist represented by the request variables/values for the given variable.  The request representation for alists is {{variable.key=value}}.  Example: {{foo.a=1}} would result in {{'((a . "1"))}} for the {{foo}} variable.
69
70Example:
71
72<enscript highlight=scheme>
[19047]73;; considering a http://server:port/path?foo.a=0&foo.b=1 request
[18257]74
75(let (($ (request-vars)))
76  ($ 'foo as-alist))   ;; => ((a . "0") (b . "1"))
77</enscript>
78
79{{as-alist}} returns {{#f}} when the wanted variable is not sent in the request or it is sent not in the ''dot'' notation (e.g., {{foo=0}}).
80
81
[23456]82
83===== as-hash-table
[18257]84<procedure>(as-hash-table variable variables/values)</procedure>
85
86The same as {{as-alist}}, but returns a hash-table object instead of an alist.
87
88
89
[23456]90===== as-vector
[18257]91<procedure>(as-vector variable variables/values)</procedure>
92
93Returns a vectir represented by the request variables/values for the given variable.  The request representation for vectors is {{variable.numeric-index=value}}.  Example: {{foo.0=1}} would result in {{#("1")}} for the {{foo}} variable.
94
95Example:
96
97<enscript highlight=scheme>
98;; considering a http://server:port/path?foo.0=a&foo.1=b
99
100(let (($ (request-vars)))
101  ($ 'foo as-vector))   ;; => #("a" "b")
102</enscript>
103
104{{as-vector}} returns {{#f}} when the wanted variable is not sent in the request or it is sent not in the ''dot'' notation (e.g., {{foo=0}}).
105
106If the vector represented by the request is sparse, the missing items are unspecified values.
107
108
[23456]109==== Combinator
110
111===== nonempty
[23458]112<procedure>(nonempty converter)</procedure>
[23456]113
114A combinator to be used with converters.  Returns the converter value
115if the variable is set and its value is not null.  Returns {{#f}} if
116its value is null.
117
118It can be useful for handling values from form-submited data, when all
119form fields are submited, but some are null.  If you are only
120interested in values that are not null, you can just check if the
121return value of {{nonempty}} is not {{#f}} (otherwise you'd have to
122check if the variable was actually in the request and if its value is
123not null).
124
125Example:
126
127<enscript highlight=scheme>
128(let ((var (or ($ 'var (nonempty as-string)) "not set")))
129   var)
130</enscript>
131
132
[27889]133==== Parameters
[23456]134
135===== true-boolean-values
[27889]136<parameter>(true-boolean-values [list])</parameter>
[18020]137
138A list of values (strings) to be considered as {{#t}} for request variables when {{as-boolean}} is used as converter.
139
[19278]140The default value is {{'("y" "yes" "1" "on" "true")}}.  The values are compared using {{string-ci=?}}.
[18020]141
[27889]142
143===== compound-variable-separator
144<parameter>(compound-variable-separator [string])</parameter>
145
146A string representing the separator for request variable names bound
147to compound data types (vectors, alists, hash-tables).  The default
148value is {{"."}}.
149
150For example, if {{(compound-variable-separator)}} yields {{"."}} and
151the query string is {{?foo.A=0&foo.B=1}}, if you bind it as an alist,
152you'll get {{((A . 0) (B . 1))}}.  If you want the same behavior, but
153for query string with variables like {{?foo_A=0&foo_B=1}}, you can set
154{{compound-variable-separator}} to {{"_"}}.
155
156
[17219]157==== Example
[16886]158
[17219]159<enscript highlight=scheme>
[16886]160  (let (($ (request-vars)))
161    ($ 'var1)
162    ($ 'var2 "") ;; if var12 is not set, return ""
[18020]163    ($ 'var3 as-number)) ;; if var3 is not set, return #f; if it is
164                         ;; set, convert its value to a number
[17219]165</enscript>
[16886]166
167
[23456]168==== Macros
169
170===== with-request-vars
[17221]171<macro>(with-request-vars [getter] (var1 var2 ... varN) expr1 expr2 ... exprN)</macro>
[17219]172
[17931]173Bind the given identifiers to the corresponding query string and request body variable values and evaluate the expressions.  The optional {{getter}} argument (the return value of {{request-vars}}) may be used in situations when you already have the getter and don't want to reparse the query string and request body.  With {{with-request-vars*}}, the given getter will be used and no reparsing will be performed.  When the syntax is ambiguous (e.g., {{(with-request-vars (request-vars) (var1 var2) (body))}}, {{with-request-vars*}} can be used).
[17219]174
[17221]175==== Examples
[17219]176
177<enscript highlight=scheme>
178(with-request-vars (a b c)
179   (conc "a = " a
180         "b = " b
181         "c = " c))
182</enscript>
183
184<enscript highlight=scheme>
185(let (($ (request-vars)))
[17221]186  (with-request-vars $ (a b c)
[17219]187     (conc "a = " a
188           "b = " b
189           "c = " c)))
190</enscript>
191
[18020]192A converter procedure can also be used to specify the type of the variable values:
[17219]193
[18020]194<enscript highlight=scheme>
195(let (($ (request-vars)))
196  (with-request-vars $ (a (b as-list) (c as-number))
197     (conc "a = " a
198           "b = " b
199           "c = " c)))
200</enscript>
201
202
[23456]203===== with-request-vars*
[17931]204<macro>(with-request-vars* getter (var1 var2 ... varN) expr1 expr2 ... exprN)</macro>
205
206The same as {{with-request-vars}}, but the getter is mandatory.
207
208
[18020]209=== More examples
210
211Considering
212
213<enscript highlight=scheme>
214(define $ (request-vars))
215</enscript>
216
217here are some expected results for the given requests:
218
219<enscript highlight=scheme>
220;; http://host:port/
221
222($ 'foo)             => #f
223($ 'foo 'bar)        => bar
224($ 'foo as-list)     => #f
225($ 'foo as-boolean)  => #f
226($ 'foo as-number)   => #f
227
228
229;; http://host:port/?foo=bar
230
231($ 'foo)             => "bar"
232($ 'foo 'bar)        => "bar"
233($ 'foo as-list)     => ("bar")
234($ 'foo as-boolean)  => #f
235($ 'foo as-number)   => #f
236
237
238;; http://host:port/?foo=bar&foo=baz
239
240($ 'foo)             => "bar"
241($ 'foo 'bar)        => "bar"
242($ 'foo as-list)     => ("bar" "baz")
243($ 'foo as-boolean)  => #f
244($ 'foo as-number)   => #f
245
246
247;; http://host:port/?foo=0
248
249($ 'foo)             => "0"
250($ 'foo 'bar)        => "0"
251($ 'foo as-list)     => ("0")
252($ 'foo as-boolean)  => #f
253($ 'foo as-number)   => 0
254
255
256;; http://host:port/?foo=yes
257
258($ 'foo)             => "yes"
259($ 'foo 'bar)        => "yes"
260($ 'foo as-list)     => ("yes")
261($ 'foo as-boolean)  => #t
262($ 'foo as-number)   => #f
263
264
265;; http://host:port/
266
267(with-request-vars (foo (bar as-list) (baz 5))
268  (list foo bar baz) => (#f #f 5)
269
270
271;; http://host:port/?foo=10
272
273(with-request-vars (foo (bar as-list) (baz 5))
274  (list foo bar baz) => ("10" #f 5)
275
276
277;; http://host:port/?foo=10&bar=1
278
279(with-request-vars (foo (bar as-list) (baz 5))
[18253]280  (list foo bar baz) => ("10" ("1") 5)
[18020]281
282
283;; http://host:port/?foo=10&bar=1&bar=2
284
285(with-request-vars (foo (bar as-list) (baz 5))
[18253]286  (list foo bar baz) => ("10" ("1" "2") 5)
[18020]287
288
289;; http://host:port/?foo=10&bar=1&bar=2&baz=-8
290
291(with-request-vars (foo (bar as-list) (baz 5))
[18253]292  (list foo bar baz) => ("10" ("1" "2") "-8")
[18020]293
[18257]294
295;; http://host:port
296
297(with-request-vars ((foo as-alist) (bar as-number) (baz as-vector) (bool as-boolean))
298  (list foo bar baz bool)) => (#f #f #f #f)
299
300
301;; http://host:port/?foo.A=0&foo.B=1&bar=0&baz.0=a&baz.1=b&bool=yes
302
303(with-request-vars ((foo as-alist) (bar as-number) (baz as-vector) (bool as-boolean))
304  (list foo bar baz bool)) => (((A . "0") (B . "1")) 0 #("a" "b") #t)
305
306
307;; http://host:port/?foo=0&bar=a&baz=0&bool=3
308
309(with-request-vars ((foo as-alist) (bar as-number) (baz as-vector) (bool as-boolean))
310  (list foo bar baz bool)) => (#f #f #f #f)
311
312
[18020]313</enscript>
314
315
316=== Tips and tricks
317
318If you want to specify both converters and default values, you can use the following trick:
319
320<enscript highlight=scheme>
321
322;; Define a procedure to return the default value if the
323;; variable is not set.
324(define ((as-number/default default) var vars/vals)
325  (or (as-number var vars/vals) default))
326
327
328;; http://host:port/
329
330(with-request-vars (foo (bar as-list) (baz (as-number/default 3)))
331  (->string (list foo bar baz))) => (#f #f 3)
332
333
334;; http://host:port/?baz=9
335
336(with-request-vars (foo (bar as-list) (baz (as-number/default 3)))
337  (->string (list foo bar baz))) => (#f #f 9)
338</enscript>
339
[16886]340=== License
341
[36372]342 Copyright (c) 2008-2018, Mario Domenech Goulart
[23456]343 All rights reserved.
344 
345 Redistribution and use in source and binary forms, with or without
346 modification, are permitted provided that the following conditions are
347 met:
348 
349 Redistributions of source code must retain the above copyright
350 notice, this list of conditions and the following disclaimer.
351 
352 Redistributions in binary form must reproduce the above copyright
353 notice, this list of conditions and the following disclaimer in the
354 documentation and/or other materials provided with the distribution.
355 
356 Neither the name of the author nor the names of its contributors may
357 be used to endorse or promote products derived from this software
358 without specific prior written permission.
359 
360 THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
361 "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
362 LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS
363 FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE
364 COPYRIGHT HOLDERS OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT,
365 INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
366 (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
367 SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
368 HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT,
369 STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
370 ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED
371 OF THE POSSIBILITY OF SUCH DAMAGE.
[16886]372
373
374=== Requirements
375
376* [[spiffy]]
377* [[intarweb]]
378* [[uri-common]]
379
380
381=== Version history
382
[36372]383===== version 0.19
384
385* Drop awful as dependency for tests (fix circular dependency)
386* CHICKEN 5 support
387
[32335]388===== version 0.18
389
390* The default value for {{request-vars}}' {{source}} keyword parameter is now {{request-method}}.
391
392Using {{'both}} as the default value for {{request-vars}}' {{source}}
393keyword params is dangerous.  Consider the following example (in
394awful):
395
396<enscript highlight=scheme>
397(use awful)
398
399(enable-sxml #t)
400
401(define-page (main-page-path)
402  (lambda ()
403    (with-request-variables (user admin)
404      (if user
405          `(ul
406            (li ,user)
407            (li ,(or admin "--")))
408          `(form (@ (method "post"))
409                 (input (@ (type "text") (name "user")))
410                 (input (@ (type "submit")))))))
411  method: '(get post))
412</enscript>
413
414An attacker could maliciously make an user follow a link to the form
415with the query string set to {{?admin=bar}}.  If {{source}} is bound to
416{{'both}}, {{request-vars}} will read from both the query string and the
417request body, leading to {{user=<what user filled>}} and {{admin=bar}}.
418
419This change sets the default value for {{source}} to {{request-method}},
420that is, {{request-vars}} will read the query string or the request body
421depending on the request method (never both).
422
423With this change, in the example mentioned above, if an attacker makes
424an user follow a link to the form with the query string set to
425{{?admin=bar}}, {{request-vars}} will read the request body '''only''', since
426the form method is {{post}}.  Thus, the handler will get {{user=<what user filled>}}
[33268]427and {{admin=#f}}.
[32335]428
429Thanks to [[/users/peter-bex|Peter Bex]] for the heads-up and
430discussions on this issue.
431
432
[27889]433===== version 0.17
434
435* Add {{compound-variable-separator}} (suggested by Taylor Venable)
436
437
[26718]438===== version 0.16
439
440* Test fixes
441
[25681]442===== version 0.15
443
[27938]444* Allow calling getter returned by request-vars with no arguments to get an alist of all request vars (patch by [[/users/moritz-heidkamp|Moritz Heidkamp]] -- [[http://bugs.call-cc.org/ticket/719|#719]])
[25681]445
[25098]446===== version 0.14
447
448* Fixed multiple evaluation of {{request-vars}} in {{with-request-vars*}} (by [[/users/moritz-heidkamp|Moritz]])
449* Bug fix: {{source: 'both}} makes {{request-vars}} actually read from both the request body and query string (thanks to [[/users/moritz-heidkamp|Moritz]] for reporting that).
450
451
452===== version 0.13
[23456]453* Added {{as-astring}} and {{as-symbol}} converters and {{nonempty}} combinator
454
[25098]455===== version 0.12
[23452]456* Bug fixes: {{request-vars}} returns {{#f}} when the content-type is not {{application/x-www-form-urlencoded}} (thanks to Peter Bex for reporting this problem). {{req-vars/vals}} always return a list.
457
[25098]458===== version 0.11
[23452]459* Applied patch by [[/users/moritz-heidkamp|Moritz Heidkamp]] which enhances {{as-boolean}} in two ways: It adds {{"true"}} to the default list of {{true-boolean-values}} and it considers the mere presence of the parameter as {{#t}}, i.e. a query string like {{"?foo"}} would bind {{foo}} to {{#t}} when it is cast {{as-boolean}}.
460
[25098]461===== version 0.10
[23452]462* Bug fix for some corner cases
463
[25098]464===== version 0.9
[23452]465* Added {{as-vector}}, {{as-alist}} and {{as-hash-table}} as converters.
466
[25098]467===== version 0.8
[23452]468* Bug fix/improvement: don't bother reading the request body when the content-length is zero (fixes some awful/jquery ajax issues)
469
[25098]470===== version 0.7
[23452]471* Bug fix.  Interpret request body before query string.
472
[25098]473===== version 0.6
[18020]474* support for specifying types to variables
475* support for receiving lists from the URI
476* '''Warning''': compatibility with previous versions has been broken: now the procedure returned by {{request-vars}} accepts only '''two''' arguments, not '''three'''.  So, if you are using converter procedures as argument to the procedure returned by {{request-vars}}, beware that your code will break.
477
[25098]478===== version 0.5
[23452]479* {{with-request-vars}} accepts a getter as argument when the syntax is not ambiguous.
480
[25098]481===== version 0.4
[23452]482* {{with-request-vars*}} resurrected.  For the cases when the syntax of {{with-request-vars}} is ambiguous.  Thanks to Moritz Heidkamp for catching this bug.
483
[25098]484===== version 0.3
[23452]485* Removed {{with-request-vars*}}.  {{with-request-vars}} accepts an optional getter argument
486
[25098]487===== version 0.2
[23452]488* Added {{with-request-vars}} and {{with-request-vars*}}
489
[25098]490===== version 0.1
[23452]491* Initial release
Note: See TracBrowser for help on using the repository browser.