-
Notifications
You must be signed in to change notification settings - Fork 70
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
1 parent
0acb36c
commit c324d41
Showing
7 changed files
with
478 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,60 @@ | ||
package badjson | ||
|
||
import ( | ||
E "github.com/sagernet/sing/common/exceptions" | ||
"github.com/sagernet/sing/common/json" | ||
) | ||
|
||
func MarshallObjects(objects ...any) ([]byte, error) { | ||
if len(objects) == 1 { | ||
return json.Marshal(objects[0]) | ||
} | ||
var content JSONObject | ||
for _, object := range objects { | ||
objectMap, err := newJSONObject(object) | ||
if err != nil { | ||
return nil, err | ||
} | ||
content.PutAll(objectMap) | ||
} | ||
return content.MarshalJSON() | ||
} | ||
|
||
func UnmarshallExcluded(inputContent []byte, parentObject any, object any) error { | ||
parentContent, err := newJSONObject(parentObject) | ||
if err != nil { | ||
return err | ||
} | ||
var content JSONObject | ||
err = content.UnmarshalJSON(inputContent) | ||
if err != nil { | ||
return err | ||
} | ||
for _, key := range parentContent.Keys() { | ||
content.Remove(key) | ||
} | ||
if object == nil { | ||
if content.IsEmpty() { | ||
return nil | ||
} | ||
return E.New("unexpected key: ", content.Keys()[0]) | ||
} | ||
inputContent, err = content.MarshalJSON() | ||
if err != nil { | ||
return err | ||
} | ||
return json.UnmarshalDisallowUnknownFields(inputContent, object) | ||
} | ||
|
||
func newJSONObject(object any) (*JSONObject, error) { | ||
inputContent, err := json.Marshal(object) | ||
if err != nil { | ||
return nil, err | ||
} | ||
var content JSONObject | ||
err = content.UnmarshalJSON(inputContent) | ||
if err != nil { | ||
return nil, err | ||
} | ||
return &content, nil | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,32 @@ | ||
package badoption | ||
|
||
import ( | ||
"time" | ||
|
||
"github.com/sagernet/sing/common/json" | ||
"github.com/sagernet/sing/common/json/badoption/internal/my_time" | ||
) | ||
|
||
type Duration time.Duration | ||
|
||
func (d Duration) Build() time.Duration { | ||
return time.Duration(d) | ||
} | ||
|
||
func (d Duration) MarshalJSON() ([]byte, error) { | ||
return json.Marshal((time.Duration)(d).String()) | ||
} | ||
|
||
func (d *Duration) UnmarshalJSON(bytes []byte) error { | ||
var value string | ||
err := json.Unmarshal(bytes, &value) | ||
if err != nil { | ||
return err | ||
} | ||
duration, err := my_time.ParseDuration(value) | ||
if err != nil { | ||
return err | ||
} | ||
*d = Duration(duration) | ||
return nil | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,15 @@ | ||
package badoption | ||
|
||
import "net/http" | ||
|
||
type HTTPHeader map[string]Listable[string] | ||
|
||
func (h HTTPHeader) Build() http.Header { | ||
header := make(http.Header) | ||
for name, values := range h { | ||
for _, value := range values { | ||
header.Add(name, value) | ||
} | ||
} | ||
return header | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,226 @@ | ||
package my_time | ||
|
||
import ( | ||
"errors" | ||
"time" | ||
) | ||
|
||
// Copyright 2010 The Go Authors. All rights reserved. | ||
// Use of this source code is governed by a BSD-style | ||
// license that can be found in the LICENSE file. | ||
|
||
const durationDay = 24 * time.Hour | ||
|
||
var unitMap = map[string]uint64{ | ||
"ns": uint64(time.Nanosecond), | ||
"us": uint64(time.Microsecond), | ||
"µs": uint64(time.Microsecond), // U+00B5 = micro symbol | ||
"μs": uint64(time.Microsecond), // U+03BC = Greek letter mu | ||
"ms": uint64(time.Millisecond), | ||
"s": uint64(time.Second), | ||
"m": uint64(time.Minute), | ||
"h": uint64(time.Hour), | ||
"d": uint64(durationDay), | ||
} | ||
|
||
// 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 uint64 | ||
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 " + quote(orig)) | ||
} | ||
for s != "" { | ||
var ( | ||
v, f uint64 // 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 " + quote(orig)) | ||
} | ||
// Consume [0-9]* | ||
pl := len(s) | ||
v, s, err = leadingInt(s) | ||
if err != nil { | ||
return 0, errors.New("time: invalid duration " + quote(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 " + quote(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 " + quote(orig)) | ||
} | ||
u := s[:i] | ||
s = s[i:] | ||
unit, ok := unitMap[u] | ||
if !ok { | ||
return 0, errors.New("time: unknown unit " + quote(u) + " in duration " + quote(orig)) | ||
} | ||
if v > 1<<63/unit { | ||
// overflow | ||
return 0, errors.New("time: invalid duration " + quote(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 += uint64(float64(f) * (float64(unit) / scale)) | ||
if v > 1<<63 { | ||
// overflow | ||
return 0, errors.New("time: invalid duration " + quote(orig)) | ||
} | ||
} | ||
d += v | ||
if d > 1<<63 { | ||
return 0, errors.New("time: invalid duration " + quote(orig)) | ||
} | ||
} | ||
if neg { | ||
return -time.Duration(d), nil | ||
} | ||
if d > 1<<63-1 { | ||
return 0, errors.New("time: invalid duration " + quote(orig)) | ||
} | ||
return time.Duration(d), nil | ||
} | ||
|
||
var errLeadingInt = errors.New("time: bad [0-9]*") // never printed | ||
|
||
// leadingInt consumes the leading [0-9]* from s. | ||
func leadingInt[bytes []byte | string](s bytes) (x uint64, rem bytes, err error) { | ||
i := 0 | ||
for ; i < len(s); i++ { | ||
c := s[i] | ||
if c < '0' || c > '9' { | ||
break | ||
} | ||
if x > 1<<63/10 { | ||
// overflow | ||
return 0, rem, errLeadingInt | ||
} | ||
x = x*10 + uint64(c) - '0' | ||
if x > 1<<63 { | ||
// overflow | ||
return 0, rem, 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 uint64, 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 + uint64(c) - '0' | ||
if y > 1<<63 { | ||
overflow = true | ||
continue | ||
} | ||
x = y | ||
scale *= 10 | ||
} | ||
return x, scale, s[i:] | ||
} | ||
|
||
// These are borrowed from unicode/utf8 and strconv and replicate behavior in | ||
// that package, since we can't take a dependency on either. | ||
const ( | ||
lowerhex = "0123456789abcdef" | ||
runeSelf = 0x80 | ||
runeError = '\uFFFD' | ||
) | ||
|
||
func quote(s string) string { | ||
buf := make([]byte, 1, len(s)+2) // slice will be at least len(s) + quotes | ||
buf[0] = '"' | ||
for i, c := range s { | ||
if c >= runeSelf || c < ' ' { | ||
// This means you are asking us to parse a time.Duration or | ||
// time.Location with unprintable or non-ASCII characters in it. | ||
// We don't expect to hit this case very often. We could try to | ||
// reproduce strconv.Quote's behavior with full fidelity but | ||
// given how rarely we expect to hit these edge cases, speed and | ||
// conciseness are better. | ||
var width int | ||
if c == runeError { | ||
width = 1 | ||
if i+2 < len(s) && s[i:i+3] == string(runeError) { | ||
width = 3 | ||
} | ||
} else { | ||
width = len(string(c)) | ||
} | ||
for j := 0; j < width; j++ { | ||
buf = append(buf, `\x`...) | ||
buf = append(buf, lowerhex[s[i+j]>>4]) | ||
buf = append(buf, lowerhex[s[i+j]&0xF]) | ||
} | ||
} else { | ||
if c == '"' || c == '\\' { | ||
buf = append(buf, '\\') | ||
} | ||
buf = append(buf, string(c)...) | ||
} | ||
} | ||
buf = append(buf, '"') | ||
return string(buf) | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,30 @@ | ||
package badoption | ||
|
||
import ( | ||
E "github.com/sagernet/sing/common/exceptions" | ||
"github.com/sagernet/sing/common/json" | ||
) | ||
|
||
type Listable[T any] []T | ||
|
||
func (l Listable[T]) MarshalJSON() ([]byte, error) { | ||
arrayList := []T(l) | ||
if len(arrayList) == 1 { | ||
return json.Marshal(arrayList[0]) | ||
} | ||
return json.Marshal(arrayList) | ||
} | ||
|
||
func (l *Listable[T]) UnmarshalJSON(content []byte) error { | ||
err := json.UnmarshalDisallowUnknownFields(content, (*[]T)(l)) | ||
if err == nil { | ||
return nil | ||
} | ||
var singleItem T | ||
newError := json.UnmarshalDisallowUnknownFields(content, &singleItem) | ||
if newError != nil { | ||
return E.Errors(err, newError) | ||
} | ||
*l = []T{singleItem} | ||
return nil | ||
} |
Oops, something went wrong.