dline/kline: Allow year/month/day durations

This commit is contained in:
Daniel Oaks 2017-03-07 19:56:21 +10:00
parent 8834de5b32
commit 0c86c454c2
4 changed files with 189 additions and 4 deletions

View File

@ -0,0 +1,183 @@
// Copyright 2010 The Go Authors. All rights reserved.
package custime
import (
"errors"
"time"
)
// see https://github.com/golang/go/blob/7ad512e7ffe576c4894ea84b02e954846fbda643/src/time/format.go#L1251
// This is a forked version of the ParseDuration function that also handles days/months/years
var errLeadingInt = errors.New("time: bad [0-9]*") // never printed
// leadingInt consumes the leading [0-9]* from s.
func leadingInt(s string) (x int64, rem string, err error) {
i := 0
for ; i < len(s); i++ {
c := s[i]
if c < '0' || c > '9' {
break
}
if x > (1<<63-1)/10 {
// overflow
return 0, "", errLeadingInt
}
x = x*10 + int64(c) - '0'
if x < 0 {
// overflow
return 0, "", errLeadingInt
}
}
return x, s[i:], nil
}
// leadingFraction consumes the leading [0-9]* from s.
// It is used only for fractions, so does not return an error on overflow,
// it just stops accumulating precision.
func leadingFraction(s string) (x int64, scale float64, rem string) {
i := 0
scale = 1
overflow := false
for ; i < len(s); i++ {
c := s[i]
if c < '0' || c > '9' {
break
}
if overflow {
continue
}
if x > (1<<63-1)/10 {
// It's possible for overflow to give a positive number, so take care.
overflow = true
continue
}
y := x*10 + int64(c) - '0'
if y < 0 {
overflow = true
continue
}
x = y
scale *= 10
}
return x, scale, s[i:]
}
var unitMap = map[string]int64{
"ns": int64(time.Nanosecond),
"us": int64(time.Microsecond),
"µs": int64(time.Microsecond), // U+00B5 = micro symbol
"μs": int64(time.Microsecond), // U+03BC = Greek letter mu
"ms": int64(time.Millisecond),
"s": int64(time.Second),
"m": int64(time.Minute),
"h": int64(time.Hour),
"d": int64(time.Hour * 24),
"mo": int64(time.Hour * 24 * 30),
"y": int64(time.Hour * 24 * 265),
}
// ParseDuration parses a duration string.
// A duration string is a possibly signed sequence of
// decimal numbers, each with optional fraction and a unit suffix,
// such as "300ms", "-1.5h" or "2h45m".
// Valid time units are "ns", "us" (or "µs"), "ms", "s", "m", "h".
func ParseDuration(s string) (time.Duration, error) {
// [-+]?([0-9]*(\.[0-9]*)?[a-z]+)+
orig := s
var d int64
neg := false
// Consume [-+]?
if s != "" {
c := s[0]
if c == '-' || c == '+' {
neg = c == '-'
s = s[1:]
}
}
// Special case: if all that is left is "0", this is zero.
if s == "0" {
return 0, nil
}
if s == "" {
return 0, errors.New("time: invalid duration " + orig)
}
for s != "" {
var (
v, f int64 // integers before, after decimal point
scale float64 = 1 // value = v + f/scale
)
var err error
// The next character must be [0-9.]
if !(s[0] == '.' || '0' <= s[0] && s[0] <= '9') {
return 0, errors.New("time: invalid duration " + orig)
}
// Consume [0-9]*
pl := len(s)
v, s, err = leadingInt(s)
if err != nil {
return 0, errors.New("time: invalid duration " + orig)
}
pre := pl != len(s) // whether we consumed anything before a period
// Consume (\.[0-9]*)?
post := false
if s != "" && s[0] == '.' {
s = s[1:]
pl := len(s)
f, scale, s = leadingFraction(s)
post = pl != len(s)
}
if !pre && !post {
// no digits (e.g. ".s" or "-.s")
return 0, errors.New("time: invalid duration " + orig)
}
// Consume unit.
i := 0
for ; i < len(s); i++ {
c := s[i]
if c == '.' || '0' <= c && c <= '9' {
break
}
}
if i == 0 {
return 0, errors.New("time: missing unit in duration " + orig)
}
u := s[:i]
s = s[i:]
unit, ok := unitMap[u]
if !ok {
return 0, errors.New("time: unknown unit " + u + " in duration " + orig)
}
if v > (1<<63-1)/unit {
// overflow
return 0, errors.New("time: invalid duration " + orig)
}
v *= unit
if f > 0 {
// float64 is needed to be nanosecond accurate for fractions of hours.
// v >= 0 && (f*unit/scale) <= 3.6e+12 (ns/h, h is the largest unit)
v += int64(float64(f) * (float64(unit) / scale))
if v < 0 {
// overflow
return 0, errors.New("time: invalid duration " + orig)
}
}
d += v
if d < 0 {
// overflow
return 0, errors.New("time: invalid duration " + orig)
}
}
if neg {
d = -d
}
return time.Duration(d), nil
}

View File

@ -14,6 +14,7 @@ import (
"encoding/json" "encoding/json"
"github.com/DanielOaks/girc-go/ircmsg" "github.com/DanielOaks/girc-go/ircmsg"
"github.com/DanielOaks/oragono/irc/custime"
"github.com/tidwall/buntdb" "github.com/tidwall/buntdb"
) )
@ -201,7 +202,7 @@ func dlineHandler(server *Server, client *Client, msg ircmsg.IrcMessage) bool {
} }
// duration // duration
duration, err := time.ParseDuration(msg.Params[currentArg]) duration, err := custime.ParseDuration(msg.Params[currentArg])
durationIsUsed := err == nil durationIsUsed := err == nil
if durationIsUsed { if durationIsUsed {
currentArg++ currentArg++

View File

@ -112,7 +112,7 @@ Bans are saved across subsequent launches of the server.
from. If "MYSELF" is not given, trying to DLINE yourself will result in an error. from. If "MYSELF" is not given, trying to DLINE yourself will result in an error.
[duration] can be of the following forms: [duration] can be of the following forms:
10h 8m 13s 1y 12mo 31d 10h 8m 13s
<net> is specified in typical CIDR notation. For example: <net> is specified in typical CIDR notation. For example:
127.0.0.1/8 127.0.0.1/8
@ -172,7 +172,7 @@ Bans are saved across subsequent launches of the server.
from. If "MYSELF" is not given, trying to KLINE yourself will result in an error. from. If "MYSELF" is not given, trying to KLINE yourself will result in an error.
[duration] can be of the following forms: [duration] can be of the following forms:
10h 8m 13s 1y 12mo 31d 10h 8m 13s
<mask> is specified in typical IRC format. For example: <mask> is specified in typical IRC format. For example:
dan dan

View File

@ -11,6 +11,7 @@ import (
"github.com/DanielOaks/girc-go/ircmatch" "github.com/DanielOaks/girc-go/ircmatch"
"github.com/DanielOaks/girc-go/ircmsg" "github.com/DanielOaks/girc-go/ircmsg"
"github.com/DanielOaks/oragono/irc/custime"
"github.com/tidwall/buntdb" "github.com/tidwall/buntdb"
) )
@ -128,7 +129,7 @@ func klineHandler(server *Server, client *Client, msg ircmsg.IrcMessage) bool {
} }
// duration // duration
duration, err := time.ParseDuration(msg.Params[currentArg]) duration, err := custime.ParseDuration(msg.Params[currentArg])
durationIsUsed := err == nil durationIsUsed := err == nil
if durationIsUsed { if durationIsUsed {
currentArg++ currentArg++