about summary refs log tree commit diff stats
path: root/src/types/cookie.nim
blob: b0578e44f52d5ee48f0018533dc21005a00d70aa (plain) (blame)
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
import options
import strutils
import times

import js/javascript
import utils/twtstr

type Cookie = ref object of RootObj
  name {.jsget.}: string
  value {.jsget.}: string
  expires {.jsget.}: int64 # unix time
  maxAge {.jsget.}: int64
  secure {.jsget.}: bool
  httponly {.jsget.}: bool
  samesite {.jsget.}: bool
  domain {.jsget.}: string
  path {.jsget.}: string

proc parseCookieDate(val: string): Option[DateTime] =
  # cookie-date
  const Delimiters = {'\t', ' '..'/', ';'..'@', '['..'`', '{'..'~'}
  const NonDigit = AllChars - AsciiDigit
  var foundTime = false
  var foundDayOfMonth = false
  var foundMonth = false
  var foundYear = false
  # date-token-list
  var time: array[3, int]
  var dayOfMonth: int
  var month: int
  var year: int
  for dateToken in val.split(Delimiters):
    if dateToken == "": continue # *delimiter
    if not foundTime:
      block timeBlock: # test for time
        let hmsTime = dateToken.until(NonDigit - {':'})
        var i = 0
        for timeField in hmsTime.split(':'):
          if i > 2: break timeBlock # too many time fields
          # 1*2DIGIT
          if timeField.len != 1 and timeField.len != 2: break timeBlock
          var timeFields: array[3, int]
          for c in timeField:
            if c notin AsciiDigit: break timeBlock
            timeFields[i] *= 10
            timeFields[i] += c.decValue
          time = timeFields
          inc i
        if i != 3: break timeBlock
        foundTime = true
        continue
    if not foundDayOfMonth:
      block dayOfMonthBlock: # test for day-of-month
        let digits = dateToken.until(NonDigit)
        if digits.len != 1 and digits.len != 2: break dayOfMonthBlock
        var n = 0
        for c in digits:
          if c notin AsciiDigit: break dayOfMonthBlock
          n *= 10
          n += c.decValue
        dayOfMonth = n
        foundDayOfMonth = true
        continue
    if not foundMonth:
      block monthBlock: # test for month
        if dateToken.len < 3: break monthBlock
        case dateToken.substr(0, 2).toLower()
        of "jan": month = 1
        of "feb": month = 2
        of "mar": month = 3
        of "apr": month = 4
        of "may": month = 5
        of "jun": month = 6
        of "jul": month = 7
        of "aug": month = 8
        of "sep": month = 9
        of "oct": month = 10
        of "nov": month = 11
        of "dec": month = 12
        else: break monthBlock
        foundMonth = true
        continue
    if not foundYear:
      block yearBlock: # test for year
        let digits = dateToken.until(NonDigit)
        if digits.len != 2 and digits.len != 4: break yearBlock
        var n = 0
        for c in digits:
          if c notin AsciiDigit: break yearBlock
          n *= 10
          n += c.decValue
        year = n
        foundYear = true
        continue
  if not (foundDayOfMonth and foundMonth and foundYear and foundTime): return none(DateTime)
  if dayOfMonth notin 0..31: return none(DateTime)
  if year < 1601: return none(DateTime)
  if time[0] > 23: return none(DateTime)
  if time[1] > 59: return none(DateTime)
  if time[2] > 59: return none(DateTime)
  var dateTime = dateTime(year, Month(month), MonthdayRange(dayOfMonth), HourRange(time[0]), MinuteRange(time[1]), SecondRange(time[2]))
  return some(dateTime)

proc newCookie(str: string): Cookie {.jsctor.} =
  let cookie = new(Cookie)
  var first = true
  for part in str.split(';'):
    if first:
      cookie.name = part.until('=')
      cookie.value = part.after('=')
      first = false
      continue
    let part = percentDecode(part).strip(leading = true, trailing = false, AsciiWhitespace)
    var n = 0
    for i in 0..part.high:
      if part[i] == '=':
        n = i
        break
    if n == 0:
      continue
    let key = part.substr(0, n - 1)
    let val = part.substr(n + 1)
    case key.toLower()
    of "expires":
      let date = parseCookieDate(val)
      if date.issome:
        cookie.expires = date.get.toTime().toUnix()
    of "max-age": cookie.maxAge = parseInt64(val)
    of "secure": cookie.secure = true
    of "httponly": cookie.httponly = true
    of "samesite": cookie.samesite = true
    of "path": cookie.path = val
    of "domain": cookie.domain = val
  return cookie

proc addCookieModule*(ctx: JSContext) =
  ctx.registerType(Cookie)