Closed atgreen closed 4 years ago
bq. snooze doesn't like parsing certain jwt parameters.
What are jwt parameters?
JSON Web Tokens, used for OIDC. For example...
...will cause the error I describe above.
Having a look at this. By the way, your workaround is also known as cl:ignore-errors
I can't reproduce this. Can you just try
(SNOOZE-SAFE-SIMPLE-READ::PARSE-INTEGER-THEN-FLOAT "7e338383-9712-42f8-ab5c-b9597e36847b.00af646c-8d59-428a-9c39-4a9e8975942d.3006bb38-909e-458e-a48d-6362bcf55e9e")
In a REPL?
Right. The problem is that the PARSE-FLOAT library has a bug, or at least different behaviour, in SBCL, it signals a aFLOATING-POINT-OVERFLOW
when given that junky string:
README-DEMO> (parse-float::parse-float "7e338383-9712-42f8-ab5c-b9597e36847b.00af646c-8d59-428a-9c39-4a9e8975942d.3006bb38-909e-458e-a48d-6362bcf55e9e" :junk-allowed t)
; Debugger entered on #<FLOATING-POINT-OVERFLOW {1001A813C3}>
Allegro ACL works, but returns "infinity". I'll come up with a fix soon, but you can use that one iof you wish in the meantime.
How comes that Snooze tries to convert this to a float? I guess some code by accident assumes this is a float number, but apparently it is not.
How comes that Snooze tries to convert this to a float?
That's part of the normal reader logic. Among other things, conversion to a float is tried (after integer). Mind you, 7e33
, the first 4 characters of the string at hand, is a valid float. But when you crank up the exponent it becomes some infinity number in ACL and signals a FLOAT-OVERFLOW
on Sbcl.
In general, when parsing (which is CL is called "reading") you can't easily know until you read enough that something in a string or a string isn't of a certain type.
the first 4 characters of the string at hand, is a valid float. But when you crank up the exponent it becomes some infinity number
Sure, looking at the first 4, or even 8 digits you have a number. But a dash '-' is not part of a number, so this should never even be tried to convert to a float or int.
In any case what would be the solution for this, to keep it as a string, and let the 'user' do the conversion, if any conversion must be done?
Anyway, I guess the OP has something working.
You're misunderstanding the problem. Try CL: READ on the same string and tell me what you get. The solution is to do what I did, which makes does the reader do what CL:READ does, except that it doesn't intern symbols, which is the point.
On Sat, Aug 1, 2020, 17:18 Manfred Bergmann notifications@github.com wrote:
the first 4 characters of the string at hand, is a valid float. But when you crank up the exponent it becomes some infinity number
Sure, looking at the first 4, or even 8 digits you have a number. But a dash '-' is not part of a number, so this should never even be tried to convert to a float or int.
In any case what would be the solution for this, to keep it as a string, and let the 'user' do the conversion?
— You are receiving this because you modified the open/close state. Reply to this email directly, view it on GitHub https://github.com/joaotavora/snooze/issues/24#issuecomment-667554587, or unsubscribe https://github.com/notifications/unsubscribe-auth/AAC6PQ7IBIUMERFY2EKN4XLR6Q557ANCNFSM4PL7QY7Q .
Could be I misunderstand. What I mean though is, and this is completely implementation independent, before any string goes into a try to convert to int or float function, it must be validated that it actually is an int or float number. Of course, when you put this string into some parser or reader function, something will come out. The question is if it is what you intended to get.
A result of 'infinity' for this string as a float is a completely wrong result. Any function trying any conversion on this string should come out at just string. Because it is no int, no float, no uuid, not anything that is anywhere parseable without knowing what the user expects, depending on the use-case.
Maybe this clears it up
SNOOZE-SAFE-SIMPLE-READ> (cl:read-from-string "123")
123 (7 bits, #x7B, #o173, #b1111011)
3 (2 bits, #x3, #o3, #b11)
SNOOZE-SAFE-SIMPLE-READ> (safe-simple-read-from-string "123")
123 (7 bits, #x7B, #o173, #b1111011)
3 (2 bits, #x3, #o3, #b11)
SNOOZE-SAFE-SIMPLE-READ> (cl:read-from-string "123.3e2")
12330.0
7 (3 bits, #x7, #o7, #b111)
SNOOZE-SAFE-SIMPLE-READ> (safe-simple-read-from-string "123.3e2")
12330.0
7 (3 bits, #x7, #o7, #b111)
SNOOZE-SAFE-SIMPLE-READ> (cl:read-from-string "123.3e2foo")
123.3E2FOO
10 (4 bits, #xA, #o12, #b1010)
SNOOZE-SAFE-SIMPLE-READ> (type-of *)
SYMBOL
SNOOZE-SAFE-SIMPLE-READ> (safe-simple-read-from-string "123.3e2foo")
123.3E2FOO
10 (4 bits, #xA, #o12, #b1010)
SNOOZE-SAFE-SIMPLE-READ> (type-of *)
SYMBOL
SNOOZE-SAFE-SIMPLE-READ> (safe-simple-read-from-string "123.3e2fooasd")
; Debugger entered on #<SNOOZE-READER-ERROR SAFE-SIMPLE-READ-FROM-STRING refusing to intern a new symbol "123.3e2fooasd" in #<PACKAGE "SNOOZE-SAFE-SIMPLE-READ">>
SNOOZE-SAFE-SIMPLE-READ> (safe-simple-read-from-string "123.3e2fooasd" t)
#:123.3E2FOOASD
13 (4 bits, #xD, #o15, #b1101)
SNOOZE-SAFE-SIMPLE-READ> (type-of *)
SYMBOL
A result of 'infinity' for this string as a float is a completely wrong result.
If you pass :junk-allowed
to parse-integer
or parse-float:parse-float
I don't agree. I think you misunderstand what those helper functions are doing in the context of the stream reader that exists in the package snooze-safe-simple-read
.
Because it is no int, no float, no uuid, not anything that is anywhere parseable without knowing what the user expects, depending on the use-case.
Sure, but snooze-safe-simple-read
does not try to solve that problem. CL:READ
and CL:READ-FROM-STRING
also do not solve that problem. You can say, Snooze as a whole solves that problem, along with other problems. But this is completely besides the context of this issues.
Anyway, I guess the OP has something working.
Also, I have fixed the problem in a proper commit.
If you pass :junk-allowed to parse-integer or parse-float:parse-float I don't agree.
I'm not sure what the purpose would be for junk-allowed
for integer or float parsing to be honest.
Either it is a number, int or float, or it is not. There is no 'maybe' it is a number. What would that junk be?
I think you misunderstand what those helper functions are doing
That may be true. I'm looking at that from an idealistic point of view. If I'm not mistaken then Snooze converts anything that is not a number in query parameters to a Common Lisp symbol. That's opinionated, but OK. One can override the functionality if desired. However I think that a plain string would be better, because a symbol is implementation dependent to Common Lisp but is not known to an HTTP context.
That's opinionated, but OK. One can override the functionality if desired.
You may call it opinionated, but I would note it is not "my" opinion. It is that of the Common Lisp designers. They are the ones who designed CL:READ
. And they designed it very carefully so it would be a reasonable inverse of CL:WRITE
. And they designed this language around symbols and symbolic expressions. Snooze is only following this closely (with notable exceptions that its default reader does not read lists, arrays, or intern new symbols, to prevent DOS attacks). But it does not follow it gratuitously, because of some formalist purity. It it does so for a reason, that brings an definite advantage: if you use the default way that Snooze gives you to read functions, can use the default SNOOZE:READ-FOR-RESOURCE
for reading arguments in the and the default SNOOZE:WRITE-FOR-RESOURCE
for writing those arguments.
Then you can generate perfect routes from the functions you create trivially with SNOOZE:DEFGENPATH
. Even though I tried, I sense that this is insufficiently clear in the README. But the reporter of #23 seems to have picked up on it, calling it the "really nice defgenpath thing", which made me happy. There's no other URL router in Common Lisp (or in any other Web Framework for any other language) that does this, to the best of my knowledge.
And yes, it's true, as I explain in the README, you can override at different levels of granularity it so that something you want designate an X becomes a X in your Lisp code as passed to your routes. By default, in Snooze, as in CL itself, things that don't designate numbers designate symbols. You can make a string designate a string, if you really need to. But most strings passed around in URIs designate other things, actual objects in your code. So before doing that, ask yourself it it's worth it and perhaps learn to embrace the symbol, which is the most understated and underrated feature in Common Lisp. And yes, that was my opinion.
I'm not sure what the purpose would be for junk-allowed for integer or float parsing to be honest.
Read the Hyperspec for parse-integer
and you will find it returns the number of characters read as the second value. So an application can continue reading what it junk as an integer but might make sense in some other way. The junk-allowed
to parse-integer
seems like a pretty good way to implement a float parser, for example.
They are the ones who designed CL:READ.
The cl:read
function is of course opinionated in order to fit to and read Common Lisp.
I probably wouldn't just use it on HTTP query parameters. Because that's where the realm of Common Lisp ends and the realm of HTTP starts.
The parsing and all the functionality that cl:read
has is not something I would expect an HTTP routing framework do, because it cannot know what the needs of the user are and should stay as generic as possible. Hence I don't see this as a responsibility of the HTTP framework but rather the application.
So my opinion is that Snooze does too much here.
The HTTP RFCs state that query keys are case-sensitive and also are the query parameters to be treated. When I get a query parameter as symbol and I need it as string I have to convert it to string, but then it is all upper-case, which is a loss of information for the application which might have special needs.
Some frameworks in the Java world (where I mostly work) allow to annotate the desired type of query parameter. Then the framework will try to convert the parameter to the desired type. However, it should not do this without any information about what the user expects. Maybe a declaim
could be used in Snooze.
The SNOOZE:DEFGENPATH
feature is surely a good thing. I have had a look at it.
I'm not sure what the purpose would be for junk-allowed for integer or float parsing to be honest.
Read the Hyperspec for
parse-integer
and you will find it returns the number of characters read as the second value. So an application can continue reading what it junk as an integer but might make sense in some other way. Thejunk-allowed
toparse-integer
seems like a pretty good way to implement a float parser, for example.
Sure thing. Maybe a 'taking junk data into account' is useful for certain scenarios. For HTTP however I believe it is not. In a worst case accepting junk data when reading a number could result is a wrong number, or a number where no number is expected, which could have serious consequences for higher-level structures and business-logic.
I probably wouldn't just use it on HTTP query parameters. Because that's where the realm of Common Lisp ends and the realm of HTTP starts.
Snooze is here to disprove you :-). If what you say were true, then Snooze would have been impossible to achieve, and yet, here it is. One thing I like in CL isn't bound by dogmatic theories in books, even the "Gang of four" sacred gospel, which was written for 90's Java and C++ and makes no sense in CL.
I'm not the first person to suggest a Common Lisp HTTP URL router, by the way. It had been suggested before I started writing Snooze, but I didn't know about it. It used method combinations and looked really nice. I'll try to find the reference.
The parsing and all the functionality that cl:read has is not something I would expect an HTTP routing framework do, because it cannot know what the needs of the user are and should stay as generic as possible. Hence I don't see this as a responsibility of the HTTP framework but rather the application. So my opinion is that Snooze does too much here.
I can't know in Snooze what the "user" means to do. It's all a question of defaults and suggestions: Snooze by defaults suggests a way to see strings using symbols, which are basically strings onto which many useful properties have been attached. I completely fail to see how any generality is lost in this step.
And as you yourself noted, if you'd rather override this suggestion and convert into monkeys or sausages inside your defroute
's body, Snooze lets you do that. But then you have to remember to do the reverse conversion just before you call the path generating function. This, in my opinion, is not as elegant.
But if you want to go the Java route, by all means. Last time I checked, Java wasn't the best language for concise code. I may be wrong, I haven't used it seriously in 10 years. By the way, I see you're knowledgeable in design patterns, and in the Java world you must be indeed. Have you read this 25 year old paper? by Peter Norvig? It tells you, among other things, that the singleton design pattern is transparent or invisible in Common Lisp, if you use a constant. Which a symbol is.
I wonder if you have an actual problem to show me or are just talking about hypothetical questions. If you have an actual REST designing problem, I'd be glad to look at it and maybe suggest how I would model it Lisp/Snooze. Then you can come up with a non-Snooze (and maybe non-Lisp) implementation and we can discuss the pros and cons of each alternative.
The HTTP RFCs state that query keys are case-sensitive and also are the query parameters to be treated
And don't know if that is what you are suggesting, but contrary to popular ignorance, Common Lisp is not case insensitive. If Snooze isn't answering to well to this, then Snooze can be fixed, surely, just open a new issue with an example where this makes a difference.
Sure thing. Maybe a 'taking junk data into account' is useful for certain scenarios. For HTTP however I believe it is not.
I'm not doing it "for HTTP". I'm doing it as a part of an implementation detail for a non-symbol-interning simplified reader. I'm quite sure you can get exactly the same results with a different implementation, that doesn't use anything but low-level binary manipulation functions or whatever. Submit your suggestion in a pull request: if it's faster, more maintainable, and doesn't break any tests, I will merge it.
Have you read this 25 year old paper? by Peter Norvig?
In fact I did. But it's not about design pattern and it's not about language either. It's about separation of concerns.
I wonder if you have an actual problem to show me or are just talking about hypothetical questions.
I had, yes. I've worked around it. Let me explain: I have this route definition:
(defroute blog (:get :text/html &optional name)
name
is a query parameter which is part of something that gets loaded from a repository.
Since the value that name
represents is a symbol (as available in the route handler/method) but the repository maintains string values I am forced to make a conversion somewhere. Either the repository makes an upper-case conversion, or the controller (MVC) and the repository make a lower-case conversion in order to make a comparison. This forces to build a case-insensitive system/application.
However, it is not the concern of the framework to determine what the rest of the application requires. Insofar it should pass on the query parameter as received by the HTTP call, as a default.
So my suggestion to give a type hint would actually be pretty nice. Not sure is this is possible to implement actually, like:
(declare (type string name))
(defroute blog (:get :text/html &optional name)
But it's not about design pattern
It is called "Design Patterns", so I think that's a pretty bold statement (that you can make to its author, it's besides the point of this issue).
something that gets loaded from a repository.
What is "the repository" in your example, Manfred? Is it a SQL database or something? Or is it a Lisp data structure, like an hash table, mapping blog "names" to blog
objects? Can you show a bit more of your code so I can see how you access "the repository"?
BTW, this in unrelated, but if you need a default value for the name
parameter, you can use the traditional techniques of Lisp function definition.
Not sure is this is possible to implement actually, like:
Your suggestion is of course possible to implement (though declaration forms usually go inside the function not outside them). But you can implement the macro MANFRED:DEFROUTE*
that does exactly what you want.This macro would emit not only the SNOOZE:DEFROUTE
form but also two method additions for the SNOOZE:URI-TO-ARGUMENTS
and SNOOZE:ARGUMENTS-TO-URI
generic functions. Those method additions would be specialized to the resource in question, begin by call-next-method
and then readjusting only the parameters that have been mentioned in the declaration. It's a nice project, try it. Maybe if it's solid enough I can merge it into Snooze itself.
can implement the macro MANFRED:DEFROUTE*
Actually, this isn't a good idea, for reasons I can explain. Better make a MANFRED:DEF-SNOOZE-TYPE
macro with this signature:
(defmacro manfred:def-snooze-type (resource argument-name to from) ...)
then use it like:
(manfred:def-snooze-type blog name #'string #'make-symbol)
or
(manfred:def-snooze-type blog name #'string #'intern)
You'll probably need to MOP for this, so you can grab the parameter by the name from the generic function's lambda list.
My curiosity led me to https://github.com/mdbergmann/cl-swbymabeweb, which is where you have this problem.
You could store blog entries by sym names, instead of strings. But if you find this akward for some reason, even in its current state, the string-equal
you have there in get-for-name
should work, at least to get the blog-entry
object. Your real problem seems to be the +
to #\Space
conversion. In the URI, you like the readable +
but in the Lisp objects, you like `, because that's how you read the file names. That's fine, if that's how you want your routes to work, and Snooze is not here to get in your way. But it can help you in
snooze:arguments-to-uriand
snooze:uri-to-arguments`. Just do the substitutions there. That way you could keep the "plus-for-space" URLish logic out of your "model" and your "controller". See the example in the README.
Also, and this is just an opinion, I think your system could be simplified to trigger errors as soon as possible so that you can follow the stack (instead of using a combination of :ok
return values and http-condition
).
That said, I like these dynamic systems which make blogs from the filesystem, I have designed a couple myself.
But it's not about It is called "Design Patterns", so I think that's a pretty bold statement (that you can make to its author, it's besides the point of this issue).
I think there was a miscommunication. Peter Norvig's paper of course is about design patterns, or the GoF book "Design Patterns".
My curiosity led me to
Indeed, that's where I use Snooze. I first used Caveman2, then step by step pretty much replaced any of it. That fact that I chose Snooze tells a story.
But it can help you in snooze:arguments-to-uri and snooze:uri-to-arguments. Just do the substitutions there.
Yeah, I have to look this up. Indeed, the decoding of URL encodings in the URI path components should be done on a system boundary, maybe by Snooze, or at the latest in the route handler.
In any case, in my simple application it works OK. However, path components and query parameters should be treated case-sensitive (https://tools.ietf.org/html/rfc7230#section-2.7.3). I know a lot of examples where a user lookup operates case-sensitive where it makes a difference if a user is looked up by 'Foo', 'foo', or 'FOO' (those are 3 different users). Whether that's good or bad is a different story. But I don't want to resume riding on that. If needed it can be achieved with Snooze. I just think that this actually should be a default.
DEF-SNOOZE-TYPE
I will check this out, thanks.
I think there was a miscommunication. Peter Norvig's paper of course is about design patterns, or the GoF book "Design Patterns".
Ah OK ealier you said:
Have you read this 25 year old paper by Peter Norvig?
In fact I did. But it's not about design pattern and it's not about language either.
Which puzzled me, since the presentation (not actually a paper) is called "Design Patterns", explains what they are, including how languages like Lisp and Dylan make some patterns "transparent".
However, path components and query parameters should be treated case-sensitive (https://tools.ietf.org/html/rfc7230#section-2.7.3). I know a lot of examples where a user lookup operates case-sensitive where it makes a difference if a user is looked up by 'Foo', 'foo', or 'FOO' (those are 3 different users). Whether that's good or bad is a different story.
Agree, I will make an issue. I think the best way is to work like Lisp itself by default, i.e. work with :invert
readtable
case, just like the normal Lisp reader.
I've made #25 to track the case-sensitivity problem
OK, thanks.
snooze doesn't like parsing certain jwt parameters. I worked around this thusly:
Here's the error: