• Home
  • Features
  • Pricing
  • Docs
  • Announcements
  • Sign In

lunarmodules / copas / 30401607642

28 Jul 2026 09:40PM UTC coverage: 85.624% (-0.1%) from 85.764%
30401607642

push

github

Tieske
fix(http): do not send literal IP addresses as the TLS SNI name

RFC 6066 forbids IP literals in the SNI extension, but the SNI
fallback used u.host unconditionally, so connecting (directly or via
redirect) to a bare IPv4/IPv6 address sent that address as the SNI
name instead of a real hostname.

Added ishostliteral() to detect IPv4/IPv6 literals and skip the
fallback for them. When no usable name is available, ssl_params.sni
is collapsed to false so copas.lua's connect() omits the SNI
extension entirely rather than send a bogus name.

5 of 8 new or added lines in 1 file covered. (62.5%)

24 existing lines in 6 files now uncovered.

1495 of 1746 relevant lines covered (85.62%)

55712.12 hits per line

Source File
Press 'n' to go to next uncovered line, 'b' for previous

93.75
/src/copas/timer.lua
1
local copas = require("copas")
18✔
2

3
local xpcall = xpcall
18✔
4
local coroutine_running = coroutine.running
18✔
5

6
if _VERSION=="Lua 5.1" and not jit then     -- obsolete: only for Lua 5.1 compatibility
18✔
UNCOV
7
  xpcall = require("coxpcall").xpcall
3✔
UNCOV
8
  coroutine_running = require("coxpcall").running
3✔
9
end
10

11

12
local timer = {}
18✔
13
timer.__index = timer
18✔
14

15

16
local new_name do
18✔
17
  local count = 0
18✔
18

19
  function new_name()
15✔
20
    count = count + 1
60✔
21
    return "copas_timer_" .. count
60✔
22
  end
23
end
24

25

26
do
27
  local function expire_func(self, initial_delay)
28
    local my_co = coroutine_running()
102✔
29
    if self.errorhandler then
102✔
30
      copas.seterrorhandler(self.errorhandler)
6✔
31
    end
32
    copas.pause(initial_delay)
102✔
33
    while true do
34
      if self.co ~= my_co then
174✔
35
        -- This generation was replaced (eg. the callback cancelled and
36
        -- rearmed the timer, synchronously, before returning). `self.co`
37
        -- and `self.cancelled` now belong to the new generation, so this
38
        -- coroutine must exit without touching them.
39
        return
18✔
40
      end
41

42
      if not self.cancelled then
156✔
43
        if not self.recurring then
156✔
44
          -- non-recurring timer
45
          self.cancelled = true
24✔
46
          self.co = nil
24✔
47

48
          self:callback(self.params)
24✔
49
          return
24✔
50

51
        else
52
          -- recurring timer
53
          self:callback(self.params)
132✔
54
        end
55
      end
56

57
      if self.co ~= my_co then
132✔
58
        -- replaced while the callback was running, see above
59
        return
60✔
60
      end
61

62
      if self.cancelled then
72✔
63
        -- clean up and exit the thread
64
        self.co = nil
×
65
        self.cancelled = true
×
66
        return
×
67
      end
68

69
      copas.pause(self.delay)
84✔
70
    end
71
  end
72

73

74
  --- Arms the timer object.
75
  -- @param initial_delay (optional) the first delay to use, if not provided uses the timer delay
76
  -- @return timer object, nil+error, or throws an error on bad input
77
  function timer:arm(initial_delay)
18✔
78
    assert(initial_delay == nil or initial_delay >= 0, "delay must be greater than or equal to 0")
108✔
79
    if self.co then
108✔
80
      return nil, "already armed"
6✔
81
    end
82

83
    self.cancelled = false
102✔
84
    self.co = copas.addnamedthread(self.name, expire_func, self, initial_delay or self.delay)
119✔
85
    return self
102✔
86
  end
87
end
88

89

90

91
--- Cancels a running timer.
92
-- If the timer callback is currently in progress (eg. yielded on socket I/O),
93
-- it is allowed to run to completion, it just won't be rescheduled. Only a
94
-- timer that is idle, waiting for its next recurrence, is woken up and
95
-- stopped immediately.
96
-- @return timer object, or nil+error
97
function timer:cancel()
18✔
98
  if not self.co then
90✔
99
    return nil, "not armed"
12✔
100
  end
101

102
  if self.cancelled then
78✔
103
    return nil, "already cancelled"
×
104
  end
105

106
  self.cancelled = true
78✔
107
  copas.wakeup(self.co) -- in case it's idle between recurrences, exit immediately
78✔
108
  self.co = nil
78✔
109
  return self
78✔
110
end
111

112

113
do
114
  -- xpcall error handler that forwards to the copas errorhandler
115
  local ehandler = function(err_obj)
116
    return copas.geterrorhandler()(err_obj, coroutine_running(), nil)
14✔
117
  end
118

119

120
  --- Creates a new timer object.
121
  -- Note: the callback signature is: `function(timer_obj, params)`.
122
  -- @param opts (table) `opts.delay` timer delay in seconds, `opts.callback` function to execute, `opts.recurring` boolean
123
  -- `opts.params` (optional) this value will be passed to the timer callback, `opts.initial_delay` (optional) the first delay to use, defaults to `delay`.
124
  -- @return timer object, or throws an error on bad input
125
  function timer.new(opts)
18✔
126
    assert((opts.delay or -1) >= 0, "delay must be greater than or equal to 0")
66✔
127
    assert(type(opts.callback) == "function", "expected callback to be a function")
66✔
128

129
    local callback = function(timer_obj, params)
130
      xpcall(opts.callback, ehandler, timer_obj, params)
156✔
131
    end
132

133
    return setmetatable({
154✔
134
      name = opts.name or new_name(),
76✔
135
      delay = opts.delay,
66✔
136
      callback = callback,
66✔
137
      recurring = not not opts.recurring,
66✔
138
      params = opts.params,
66✔
139
      cancelled = false,
55✔
140
      errorhandler = opts.errorhandler,
66✔
141
    }, timer):arm(opts.initial_delay)
132✔
142
  end
143
end
144

145

146

147
return timer
18✔
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE TRIAL · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2026 Coveralls, Inc