File: cookie-parse.rkt

package info (click to toggle)
racket 6.7-3
  • links: PTS, VCS
  • area: main
  • in suites: stretch
  • size: 97,344 kB
  • ctags: 39,484
  • sloc: ansic: 277,847; sh: 33,512; asm: 13,558; lisp: 7,113; cpp: 2,872; makefile: 2,421; pascal: 2,262; exp: 499; python: 274; xml: 11
file content (147 lines) | stat: -rw-r--r-- 5,266 bytes parent folder | download | duplicates (4)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
#lang racket/base
(require racket/port
         racket/match
         web-server/http/request-structs
         net/cookie
         web-server/private/util         
         racket/contract)

(define-struct client-cookie 
  (name value domain path)
  #:prefab)

(provide/contract
 [struct client-cookie 
         ([name string?]
          [value string?]
          [domain (or/c false/c valid-domain?)]
          [path (or/c false/c string?)])]
 [request-cookies (request? . -> . (listof client-cookie?))])

;; ============================================================
;; utilities for retrieving cookies

(require parser-tools/lex
         parser-tools/yacc
         (prefix-in : parser-tools/lex-sre))

#|
   cookie          =       "Cookie:" cookie-version
                           1*((";" | ",") cookie-value)
   cookie-value    =       NAME "=" VALUE [";" path] [";" domain]
   cookie-version  =       "$Version" "=" value
   NAME            =       attr
   VALUE           =       value
   path            =       "$Path" "=" value
   domain          =       "$Domain" "=" value

   value          = token | quoted-string

   token          = 1*<any CHAR except CTLs or tspecials>

   quoted-string  = ( <"> *(qdtext) <"> )
   qdtext         = <any TEXT except <">>
|#
(define-lex-abbrevs
  (illegal (char-set "()<>@:/[]?{}"))
  (tspecial (:or (char-set "()<>@,;\\\"/[]?={}") whitespace #\tab))
  (token-char (:- any-char tspecial iso-control)))

(define-tokens regular (TOKEN QUOTED-STRING ILLEGAL))
(define-empty-tokens keywords (EQUALS SEMI COMMA PATH DOMAIN VERSION EOF))

(define lex-cookie
  (lexer-src-pos
   [(eof) (token-EOF)]
   [whitespace (return-without-pos (lex-cookie input-port))]
   ["=" (token-EQUALS)]
   [";" (token-SEMI)]
   ["," (token-COMMA)]
   [(:+ illegal) (token-ILLEGAL lexeme)]
   ["$Path" (token-PATH)]
   ["$Domain" (token-DOMAIN)]
   ["$Version" (token-VERSION)]
   [(:: #\" (:* (:or (:~ #\") "\\\"")) #\")
    (token-QUOTED-STRING (substring lexeme 1 (- (string-length lexeme) 1)))]
   [(:+ token-char) (token-TOKEN lexeme)]))

(define current-source-name (make-parameter #f))

(define (make-srcloc start-pos end-pos)
  (list (current-source-name) 
        (position-line start-pos)
        (position-col start-pos)
        (position-offset start-pos)
        (- (position-offset end-pos) (position-offset start-pos))))

(define parse-raw-cookies
  (parser (src-pos)
          (start items)
          (tokens regular keywords)
          (grammar (items [(item separator items) (cons $1 $3)]
                          [(item) (list $1)])
                   (separator [(COMMA) #t]
                              [(SEMI) #t])
                   (item [(lhs EQUALS rhs) (cons $1 $3)]
                         ; This is not part of the spec. It is illegal
                         [(lhs EQUALS) (cons $1 "")])
                   (lhs [(VERSION) "$Version"]
                        [(DOMAIN) 'domain]
                        [(PATH) 'path]
                        [(TOKEN) $1])
                   (rhs [(TOKEN) $1] ; This is legal, but is subsumed by the illegal rule
                        [(QUOTED-STRING) (regexp-replace* (regexp-quote "\\\"") $1 "\"")]
                        ; This is not part of the spec. It is illegal
                        [(illegal) $1])
                   (illegal
                    [(EQUALS) "="]
                    [(ILLEGAL) $1]
                    [(illegal illegal) (string-append $1 $2)]
                    [(TOKEN) $1]))
          (suppress) ; The illegal rule creates many conflicts
          (end EOF)
          (error (lambda (tok-ok? tok-name tok-value start-pos end-pos)
                   (raise-syntax-error
                    'parse-cookies
                    (format 
                     (if tok-ok? 
                         "Did not expect token ~a"
                         "Invalid token ~a")
                     tok-name)
                    (datum->syntax #f tok-value (make-srcloc start-pos end-pos)))))))

(define (parse-cookie-likes ip)
  (parse-raw-cookies (λ () (lex-cookie ip))))

(define (parse-cookies str)
  (with-input-from-string 
      str
    (λ () 
      (define ip (current-input-port))
      (port-count-lines! ip)
      (parameterize ([current-source-name (object-name ip)])
        (raw->cookies (parse-cookie-likes ip))))))

;; raw->cookies : flat-property-list -> (listof cookie)
(define raw->cookies
  (match-lambda
    [(list-rest (cons (? string? key) val) l)
     (let loop ([l l] [c (make-client-cookie key val #f #f)])
       (match l
         [(list)
          (list c)]
         [(list-rest (cons (? string? key) val) l)
          (list* c (loop l (make-client-cookie key val #f #f)))]
         [(list-rest (cons 'domain val) l)
          (loop l (struct-copy client-cookie c [domain val]))]
         [(list-rest (cons 'path val) l)
          (loop l (struct-copy client-cookie c [path val]))]))]))

;; request-cookies* : request -> (listof cookie)
(define (request-cookies req)
  (define hdrs (request-headers/raw req))
  (apply append
         (map (compose parse-cookies bytes->string/utf-8 header-value)
              (filter (lambda (h)
                        (bytes-ci=? #"Cookie" (header-field h)))
                      hdrs))))