mirror of https://github.com/ethereum/go-ethereum
vendor, internal/build: fix OpenBSD by bumping Azure libs (#17966)
* bump azure-storage-blob-go dependency to 0.3.0 release * update azure-storage-blob-go module import path * fix multiple return values on azblob.NewSharedKeyCredential * vendor: bump Azure libs to latest from upstreampull/19669/head
parent
4f56790efc
commit
57d9c93dcd
@ -0,0 +1,14 @@ |
||||
package pipeline |
||||
|
||||
|
||||
// ForceLog should rarely be used. It forceable logs an entry to the
|
||||
// Windows Event Log (on Windows) or to the SysLog (on Linux)
|
||||
func ForceLog(level LogLevel, msg string) { |
||||
if !enableForceLog { |
||||
return |
||||
} |
||||
if sanitizer != nil { |
||||
msg = sanitizer.SanitizeLogMessage(msg) |
||||
} |
||||
forceLog(level, msg) |
||||
} |
@ -1,3 +0,0 @@ |
||||
package azblob |
||||
|
||||
const serviceLibVersion = "0.1" |
122
vendor/github.com/Azure/azure-storage-blob-go/2018-03-28/azblob/zc_retry_reader.go
generated
vendored
122
vendor/github.com/Azure/azure-storage-blob-go/2018-03-28/azblob/zc_retry_reader.go
generated
vendored
@ -1,122 +0,0 @@ |
||||
package azblob |
||||
|
||||
import ( |
||||
"context" |
||||
"io" |
||||
"net" |
||||
"net/http" |
||||
) |
||||
|
||||
const CountToEnd = 0 |
||||
|
||||
// HTTPGetter is a function type that refers to a method that performs an HTTP GET operation.
|
||||
type HTTPGetter func(ctx context.Context, i HTTPGetterInfo) (*http.Response, error) |
||||
|
||||
// HTTPGetterInfo is passed to an HTTPGetter function passing it parameters
|
||||
// that should be used to make an HTTP GET request.
|
||||
type HTTPGetterInfo struct { |
||||
// Offset specifies the start offset that should be used when
|
||||
// creating the HTTP GET request's Range header
|
||||
Offset int64 |
||||
|
||||
// Count specifies the count of bytes that should be used to calculate
|
||||
// the end offset when creating the HTTP GET request's Range header
|
||||
Count int64 |
||||
|
||||
// ETag specifies the resource's etag that should be used when creating
|
||||
// the HTTP GET request's If-Match header
|
||||
ETag ETag |
||||
} |
||||
|
||||
// RetryReaderOptions contains properties which can help to decide when to do retry.
|
||||
type RetryReaderOptions struct { |
||||
// MaxRetryRequests specifies the maximum number of HTTP GET requests that will be made
|
||||
// while reading from a RetryReader. A value of zero means that no additional HTTP
|
||||
// GET requests will be made.
|
||||
MaxRetryRequests int |
||||
doInjectError bool |
||||
doInjectErrorRound int |
||||
} |
||||
|
||||
// retryReader implements io.ReaderCloser methods.
|
||||
// retryReader tries to read from response, and if there is retriable network error
|
||||
// returned during reading, it will retry according to retry reader option through executing
|
||||
// user defined action with provided data to get a new response, and continue the overall reading process
|
||||
// through reading from the new response.
|
||||
type retryReader struct { |
||||
ctx context.Context |
||||
response *http.Response |
||||
info HTTPGetterInfo |
||||
countWasBounded bool |
||||
o RetryReaderOptions |
||||
getter HTTPGetter |
||||
} |
||||
|
||||
// NewRetryReader creates a retry reader.
|
||||
func NewRetryReader(ctx context.Context, initialResponse *http.Response, |
||||
info HTTPGetterInfo, o RetryReaderOptions, getter HTTPGetter) io.ReadCloser { |
||||
if getter == nil { |
||||
panic("getter must not be nil") |
||||
} |
||||
if info.Count < 0 { |
||||
panic("info.Count must be >= 0") |
||||
} |
||||
if o.MaxRetryRequests < 0 { |
||||
panic("o.MaxRetryRequests must be >= 0") |
||||
} |
||||
return &retryReader{ctx: ctx, getter: getter, info: info, countWasBounded: info.Count != CountToEnd, response: initialResponse, o: o} |
||||
} |
||||
|
||||
func (s *retryReader) Read(p []byte) (n int, err error) { |
||||
for try := 0; ; try++ { |
||||
//fmt.Println(try) // Comment out for debugging.
|
||||
if s.countWasBounded && s.info.Count == CountToEnd { |
||||
// User specified an original count and the remaining bytes are 0, return 0, EOF
|
||||
return 0, io.EOF |
||||
} |
||||
|
||||
if s.response == nil { // We don't have a response stream to read from, try to get one.
|
||||
response, err := s.getter(s.ctx, s.info) |
||||
if err != nil { |
||||
return 0, err |
||||
} |
||||
// Successful GET; this is the network stream we'll read from.
|
||||
s.response = response |
||||
} |
||||
n, err := s.response.Body.Read(p) // Read from the stream
|
||||
|
||||
// Injection mechanism for testing.
|
||||
if s.o.doInjectError && try == s.o.doInjectErrorRound { |
||||
err = &net.DNSError{IsTemporary: true} |
||||
} |
||||
|
||||
// We successfully read data or end EOF.
|
||||
if err == nil || err == io.EOF { |
||||
s.info.Offset += int64(n) // Increments the start offset in case we need to make a new HTTP request in the future
|
||||
if s.info.Count != CountToEnd { |
||||
s.info.Count -= int64(n) // Decrement the count in case we need to make a new HTTP request in the future
|
||||
} |
||||
return n, err // Return the return to the caller
|
||||
} |
||||
s.Close() // Error, close stream
|
||||
s.response = nil // Our stream is no longer good
|
||||
|
||||
// Check the retry count and error code, and decide whether to retry.
|
||||
if try >= s.o.MaxRetryRequests { |
||||
return n, err // All retries exhausted
|
||||
} |
||||
|
||||
if netErr, ok := err.(net.Error); ok && (netErr.Timeout() || netErr.Temporary()) { |
||||
continue |
||||
// Loop around and try to get and read from new stream.
|
||||
} |
||||
return n, err // Not retryable, just return
|
||||
} |
||||
} |
||||
|
||||
func (s *retryReader) Close() error { |
||||
if s.response != nil && s.response.Body != nil { |
||||
return s.response.Body.Close() |
||||
} |
||||
return nil |
||||
} |
File diff suppressed because it is too large
Load Diff
8
vendor/github.com/Azure/azure-storage-blob-go/azblob/storage_account_credential.go
generated
vendored
8
vendor/github.com/Azure/azure-storage-blob-go/azblob/storage_account_credential.go
generated
vendored
@ -0,0 +1,8 @@ |
||||
package azblob |
||||
|
||||
// StorageAccountCredential is a wrapper interface for SharedKeyCredential and UserDelegationCredential
|
||||
type StorageAccountCredential interface { |
||||
AccountName() string |
||||
ComputeHMACSHA256(message string) (base64String string) |
||||
getUDKParams() *UserDelegationKey |
||||
} |
38
vendor/github.com/Azure/azure-storage-blob-go/azblob/user_delegation_credential.go
generated
vendored
38
vendor/github.com/Azure/azure-storage-blob-go/azblob/user_delegation_credential.go
generated
vendored
@ -0,0 +1,38 @@ |
||||
package azblob |
||||
|
||||
import ( |
||||
"crypto/hmac" |
||||
"crypto/sha256" |
||||
"encoding/base64" |
||||
) |
||||
|
||||
// NewUserDelegationCredential creates a new UserDelegationCredential using a Storage account's name and a user delegation key from it
|
||||
func NewUserDelegationCredential(accountName string, key UserDelegationKey) UserDelegationCredential { |
||||
return UserDelegationCredential{ |
||||
accountName: accountName, |
||||
accountKey: key, |
||||
} |
||||
} |
||||
|
||||
type UserDelegationCredential struct { |
||||
accountName string |
||||
accountKey UserDelegationKey |
||||
} |
||||
|
||||
// AccountName returns the Storage account's name
|
||||
func (f UserDelegationCredential) AccountName() string { |
||||
return f.accountName |
||||
} |
||||
|
||||
// ComputeHMAC
|
||||
func (f UserDelegationCredential) ComputeHMACSHA256(message string) (base64String string) { |
||||
bytes, _ := base64.StdEncoding.DecodeString(f.accountKey.Value) |
||||
h := hmac.New(sha256.New, bytes) |
||||
h.Write([]byte(message)) |
||||
return base64.StdEncoding.EncodeToString(h.Sum(nil)) |
||||
} |
||||
|
||||
// Private method to return important parameters for NewSASQueryParameters
|
||||
func (f UserDelegationCredential) getUDKParams() *UserDelegationKey { |
||||
return &f.accountKey |
||||
} |
@ -0,0 +1,3 @@ |
||||
package azblob |
||||
|
||||
const serviceLibVersion = "0.7" |
@ -0,0 +1,178 @@ |
||||
package azblob |
||||
|
||||
import ( |
||||
"context" |
||||
"io" |
||||
"net" |
||||
"net/http" |
||||
"strings" |
||||
"sync" |
||||
) |
||||
|
||||
const CountToEnd = 0 |
||||
|
||||
// HTTPGetter is a function type that refers to a method that performs an HTTP GET operation.
|
||||
type HTTPGetter func(ctx context.Context, i HTTPGetterInfo) (*http.Response, error) |
||||
|
||||
// HTTPGetterInfo is passed to an HTTPGetter function passing it parameters
|
||||
// that should be used to make an HTTP GET request.
|
||||
type HTTPGetterInfo struct { |
||||
// Offset specifies the start offset that should be used when
|
||||
// creating the HTTP GET request's Range header
|
||||
Offset int64 |
||||
|
||||
// Count specifies the count of bytes that should be used to calculate
|
||||
// the end offset when creating the HTTP GET request's Range header
|
||||
Count int64 |
||||
|
||||
// ETag specifies the resource's etag that should be used when creating
|
||||
// the HTTP GET request's If-Match header
|
||||
ETag ETag |
||||
} |
||||
|
||||
// FailedReadNotifier is a function type that represents the notification function called when a read fails
|
||||
type FailedReadNotifier func(failureCount int, lastError error, offset int64, count int64, willRetry bool) |
||||
|
||||
// RetryReaderOptions contains properties which can help to decide when to do retry.
|
||||
type RetryReaderOptions struct { |
||||
// MaxRetryRequests specifies the maximum number of HTTP GET requests that will be made
|
||||
// while reading from a RetryReader. A value of zero means that no additional HTTP
|
||||
// GET requests will be made.
|
||||
MaxRetryRequests int |
||||
doInjectError bool |
||||
doInjectErrorRound int |
||||
|
||||
// NotifyFailedRead is called, if non-nil, after any failure to read. Expected usage is diagnostic logging.
|
||||
NotifyFailedRead FailedReadNotifier |
||||
|
||||
// TreatEarlyCloseAsError can be set to true to prevent retries after "read on closed response body". By default,
|
||||
// retryReader has the following special behaviour: closing the response body before it is all read is treated as a
|
||||
// retryable error. This is to allow callers to force a retry by closing the body from another goroutine (e.g. if the =
|
||||
// read is too slow, caller may want to force a retry in the hope that the retry will be quicker). If
|
||||
// TreatEarlyCloseAsError is true, then retryReader's special behaviour is suppressed, and "read on closed body" is instead
|
||||
// treated as a fatal (non-retryable) error.
|
||||
// Note that setting TreatEarlyCloseAsError only guarantees that Closing will produce a fatal error if the Close happens
|
||||
// from the same "thread" (goroutine) as Read. Concurrent Close calls from other goroutines may instead produce network errors
|
||||
// which will be retried.
|
||||
TreatEarlyCloseAsError bool |
||||
} |
||||
|
||||
// retryReader implements io.ReaderCloser methods.
|
||||
// retryReader tries to read from response, and if there is retriable network error
|
||||
// returned during reading, it will retry according to retry reader option through executing
|
||||
// user defined action with provided data to get a new response, and continue the overall reading process
|
||||
// through reading from the new response.
|
||||
type retryReader struct { |
||||
ctx context.Context |
||||
info HTTPGetterInfo |
||||
countWasBounded bool |
||||
o RetryReaderOptions |
||||
getter HTTPGetter |
||||
|
||||
// we support Close-ing during Reads (from other goroutines), so we protect the shared state, which is response
|
||||
responseMu *sync.Mutex |
||||
response *http.Response |
||||
} |
||||
|
||||
// NewRetryReader creates a retry reader.
|
||||
func NewRetryReader(ctx context.Context, initialResponse *http.Response, |
||||
info HTTPGetterInfo, o RetryReaderOptions, getter HTTPGetter) io.ReadCloser { |
||||
return &retryReader{ |
||||
ctx: ctx, |
||||
getter: getter, |
||||
info: info, |
||||
countWasBounded: info.Count != CountToEnd, |
||||
response: initialResponse, |
||||
responseMu: &sync.Mutex{}, |
||||
o: o} |
||||
} |
||||
|
||||
func (s *retryReader) setResponse(r *http.Response) { |
||||
s.responseMu.Lock() |
||||
defer s.responseMu.Unlock() |
||||
s.response = r |
||||
} |
||||
|
||||
func (s *retryReader) Read(p []byte) (n int, err error) { |
||||
for try := 0; ; try++ { |
||||
//fmt.Println(try) // Comment out for debugging.
|
||||
if s.countWasBounded && s.info.Count == CountToEnd { |
||||
// User specified an original count and the remaining bytes are 0, return 0, EOF
|
||||
return 0, io.EOF |
||||
} |
||||
|
||||
s.responseMu.Lock() |
||||
resp := s.response |
||||
s.responseMu.Unlock() |
||||
if resp == nil { // We don't have a response stream to read from, try to get one.
|
||||
newResponse, err := s.getter(s.ctx, s.info) |
||||
if err != nil { |
||||
return 0, err |
||||
} |
||||
// Successful GET; this is the network stream we'll read from.
|
||||
s.setResponse(newResponse) |
||||
resp = newResponse |
||||
} |
||||
n, err := resp.Body.Read(p) // Read from the stream (this will return non-nil err if forceRetry is called, from another goroutine, while it is running)
|
||||
|
||||
// Injection mechanism for testing.
|
||||
if s.o.doInjectError && try == s.o.doInjectErrorRound { |
||||
err = &net.DNSError{IsTemporary: true} |
||||
} |
||||
|
||||
// We successfully read data or end EOF.
|
||||
if err == nil || err == io.EOF { |
||||
s.info.Offset += int64(n) // Increments the start offset in case we need to make a new HTTP request in the future
|
||||
if s.info.Count != CountToEnd { |
||||
s.info.Count -= int64(n) // Decrement the count in case we need to make a new HTTP request in the future
|
||||
} |
||||
return n, err // Return the return to the caller
|
||||
} |
||||
s.Close() // Error, close stream
|
||||
s.setResponse(nil) // Our stream is no longer good
|
||||
|
||||
// Check the retry count and error code, and decide whether to retry.
|
||||
retriesExhausted := try >= s.o.MaxRetryRequests |
||||
_, isNetError := err.(net.Error) |
||||
willRetry := (isNetError || s.wasRetryableEarlyClose(err)) && !retriesExhausted |
||||
|
||||
// Notify, for logging purposes, of any failures
|
||||
if s.o.NotifyFailedRead != nil { |
||||
failureCount := try + 1 // because try is zero-based
|
||||
s.o.NotifyFailedRead(failureCount, err, s.info.Offset, s.info.Count, willRetry) |
||||
} |
||||
|
||||
if willRetry { |
||||
continue |
||||
// Loop around and try to get and read from new stream.
|
||||
} |
||||
return n, err // Not retryable, or retries exhausted, so just return
|
||||
} |
||||
} |
||||
|
||||
// By default, we allow early Closing, from another concurrent goroutine, to be used to force a retry
|
||||
// Is this safe, to close early from another goroutine? Early close ultimately ends up calling
|
||||
// net.Conn.Close, and that is documented as "Any blocked Read or Write operations will be unblocked and return errors"
|
||||
// which is exactly the behaviour we want.
|
||||
// NOTE: that if caller has forced an early Close from a separate goroutine (separate from the Read)
|
||||
// then there are two different types of error that may happen - either the one one we check for here,
|
||||
// or a net.Error (due to closure of connection). Which one happens depends on timing. We only need this routine
|
||||
// to check for one, since the other is a net.Error, which our main Read retry loop is already handing.
|
||||
func (s *retryReader) wasRetryableEarlyClose(err error) bool { |
||||
if s.o.TreatEarlyCloseAsError { |
||||
return false // user wants all early closes to be errors, and so not retryable
|
||||
} |
||||
// unfortunately, http.errReadOnClosedResBody is private, so the best we can do here is to check for its text
|
||||
return strings.HasSuffix(err.Error(), ReadOnClosedBodyMessage) |
||||
} |
||||
|
||||
const ReadOnClosedBodyMessage = "read on closed response body" |
||||
|
||||
func (s *retryReader) Close() error { |
||||
s.responseMu.Lock() |
||||
defer s.responseMu.Unlock() |
||||
if s.response != nil && s.response.Body != nil { |
||||
return s.response.Body.Close() |
||||
} |
||||
return nil |
||||
} |
File diff suppressed because it is too large
Load Diff
@ -0,0 +1,29 @@ |
||||
// +build appengine
|
||||
|
||||
package colorable |
||||
|
||||
import ( |
||||
"io" |
||||
"os" |
||||
|
||||
_ "github.com/mattn/go-isatty" |
||||
) |
||||
|
||||
// NewColorable return new instance of Writer which handle escape sequence.
|
||||
func NewColorable(file *os.File) io.Writer { |
||||
if file == nil { |
||||
panic("nil passed instead of *os.File to NewColorable()") |
||||
} |
||||
|
||||
return file |
||||
} |
||||
|
||||
// NewColorableStdout return new instance of Writer which handle escape sequence for stdout.
|
||||
func NewColorableStdout() io.Writer { |
||||
return os.Stdout |
||||
} |
||||
|
||||
// NewColorableStderr return new instance of Writer which handle escape sequence for stderr.
|
||||
func NewColorableStderr() io.Writer { |
||||
return os.Stderr |
||||
} |
@ -0,0 +1,11 @@ |
||||
package ieproxy |
||||
|
||||
import ( |
||||
"net/http" |
||||
"net/url" |
||||
) |
||||
|
||||
// GetProxyFunc is a forwarder for the OS-Exclusive proxyMiddleman_os.go files
|
||||
func GetProxyFunc() func(*http.Request) (*url.URL, error) { |
||||
return proxyMiddleman() |
||||
} |
@ -0,0 +1,23 @@ |
||||
MIT License |
||||
|
||||
Copyright (c) 2014 mattn |
||||
Copyright (c) 2017 oliverpool |
||||
Copyright (c) 2019 Adele Reed |
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy |
||||
of this software and associated documentation files (the "Software"), to deal |
||||
in the Software without restriction, including without limitation the rights |
||||
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell |
||||
copies of the Software, and to permit persons to whom the Software is |
||||
furnished to do so, subject to the following conditions: |
||||
|
||||
The above copyright notice and this permission notice shall be included in all |
||||
copies or substantial portions of the Software. |
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR |
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, |
||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE |
||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER |
||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, |
||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE |
||||
SOFTWARE. |
@ -0,0 +1,49 @@ |
||||
# ieproxy |
||||
|
||||
Go package to detect the proxy settings on Windows platform. |
||||
|
||||
The settings are initially attempted to be read from the [`WinHttpGetIEProxyConfigForCurrentUser` DLL call](https://docs.microsoft.com/en-us/windows/desktop/api/winhttp/nf-winhttp-winhttpgetieproxyconfigforcurrentuser), but falls back to the registry (`CURRENT_USER\Software\Microsoft\Windows\CurrentVersion\Internet Settings`) in the event the DLL call fails. |
||||
|
||||
For more information, take a look at the [documentation](https://godoc.org/github.com/mattn/go-ieproxy) |
||||
|
||||
## Methods |
||||
|
||||
You can either obtain a `net/http` compatible proxy function using `ieproxy.GetProxyFunc()`, set environment variables using `ieproxy.OverrideEnvWithStaticProxy()` (though no automatic configuration is available this way), or obtain the proxy settings via `ieproxy.GetConf()`. |
||||
|
||||
| Method | Supported configuration options: | |
||||
|----------------------------------------|-----------------------------------------------| |
||||
| `ieproxy.GetProxyFunc()` | Static, Specified script, and fully automatic | |
||||
| `ieproxy.OverrideEnvWithStaticProxy()` | Static | |
||||
| `ieproxy.GetConf()` | Depends on how you use it | |
||||
|
||||
## Examples |
||||
|
||||
### Using GetProxyFunc(): |
||||
|
||||
```go |
||||
func init() { |
||||
http.DefaultTransport.(*http.Transport).Proxy = ieproxy.GetProxyFunc() |
||||
} |
||||
``` |
||||
|
||||
GetProxyFunc acts as a middleman between `net/http` and `mattn/go-ieproxy` in order to select the correct proxy configuration based off the details supplied in the config. |
||||
|
||||
### Using OverrideEnvWithStaticProxy(): |
||||
|
||||
```go |
||||
func init() { |
||||
ieproxy.OverrideEnvWithStaticProxy() |
||||
http.DefaultTransport.(*http.Transport).Proxy = http.ProxyFromEnvironment |
||||
} |
||||
``` |
||||
|
||||
OverrideEnvWithStaticProxy overrides the relevant environment variables (`HTTP_PROXY`, `HTTPS_PROXY`, `NO_PROXY`) with the **static, manually configured** proxy details typically found in the registry. |
||||
|
||||
### Using GetConf(): |
||||
|
||||
```go |
||||
func main() { |
||||
conf := ieproxy.GetConf() |
||||
//Handle proxies how you want to. |
||||
} |
||||
``` |
@ -0,0 +1,51 @@ |
||||
// Package ieproxy is a utility to retrieve the proxy parameters (especially of Internet Explorer on windows)
|
||||
//
|
||||
// On windows, it gathers the parameters from the registry (regedit), while it uses env variable on other platforms
|
||||
package ieproxy |
||||
|
||||
import "os" |
||||
|
||||
// ProxyConf gathers the configuration for proxy
|
||||
type ProxyConf struct { |
||||
Static StaticProxyConf // static configuration
|
||||
Automatic ProxyScriptConf // script configuration
|
||||
} |
||||
|
||||
// StaticProxyConf contains the configuration for static proxy
|
||||
type StaticProxyConf struct { |
||||
// Is the proxy active?
|
||||
Active bool |
||||
// Proxy address for each scheme (http, https)
|
||||
// "" (empty string) is the fallback proxy
|
||||
Protocols map[string]string |
||||
// Addresses not to be browsed via the proxy (comma-separated, linux-like)
|
||||
NoProxy string |
||||
} |
||||
|
||||
// ProxyScriptConf contains the configuration for automatic proxy
|
||||
type ProxyScriptConf struct { |
||||
// Is the proxy active?
|
||||
Active bool |
||||
// PreConfiguredURL of the .pac file.
|
||||
// If this is empty and Active is true, auto-configuration should be assumed.
|
||||
PreConfiguredURL string |
||||
} |
||||
|
||||
// GetConf retrieves the proxy configuration from the Windows Regedit
|
||||
func GetConf() ProxyConf { |
||||
return getConf() |
||||
} |
||||
|
||||
// OverrideEnvWithStaticProxy writes new values to the
|
||||
// `http_proxy`, `https_proxy` and `no_proxy` environment variables.
|
||||
// The values are taken from the Windows Regedit (should be called in `init()` function - see example)
|
||||
func OverrideEnvWithStaticProxy() { |
||||
overrideEnvWithStaticProxy(GetConf(), os.Setenv) |
||||
} |
||||
|
||||
// FindProxyForURL computes the proxy for a given URL according to the pac file
|
||||
func (psc *ProxyScriptConf) FindProxyForURL(URL string) string { |
||||
return psc.findProxyForURL(URL) |
||||
} |
||||
|
||||
type envSetter func(string, string) error |
@ -0,0 +1,10 @@ |
||||
// +build !windows
|
||||
|
||||
package ieproxy |
||||
|
||||
func getConf() ProxyConf { |
||||
return ProxyConf{} |
||||
} |
||||
|
||||
func overrideEnvWithStaticProxy(pc ProxyConf, setenv envSetter) { |
||||
} |
@ -0,0 +1,164 @@ |
||||
package ieproxy |
||||
|
||||
import ( |
||||
"strings" |
||||
"sync" |
||||
"unsafe" |
||||
|
||||
"golang.org/x/sys/windows/registry" |
||||
) |
||||
|
||||
type regeditValues struct { |
||||
ProxyServer string |
||||
ProxyOverride string |
||||
ProxyEnable uint64 |
||||
AutoConfigURL string |
||||
} |
||||
|
||||
var once sync.Once |
||||
var windowsProxyConf ProxyConf |
||||
|
||||
// GetConf retrieves the proxy configuration from the Windows Regedit
|
||||
func getConf() ProxyConf { |
||||
once.Do(writeConf) |
||||
return windowsProxyConf |
||||
} |
||||
|
||||
func writeConf() { |
||||
var ( |
||||
cfg *tWINHTTP_CURRENT_USER_IE_PROXY_CONFIG |
||||
err error |
||||
) |
||||
|
||||
if cfg, err = getUserConfigFromWindowsSyscall(); err != nil { |
||||
regedit, _ := readRegedit() // If the syscall fails, backup to manual detection.
|
||||
windowsProxyConf = parseRegedit(regedit) |
||||
return |
||||
} |
||||
|
||||
defer globalFreeWrapper(cfg.lpszProxy) |
||||
defer globalFreeWrapper(cfg.lpszProxyBypass) |
||||
defer globalFreeWrapper(cfg.lpszAutoConfigUrl) |
||||
|
||||
windowsProxyConf = ProxyConf{ |
||||
Static: StaticProxyConf{ |
||||
Active: cfg.lpszProxy != nil, |
||||
}, |
||||
Automatic: ProxyScriptConf{ |
||||
Active: cfg.lpszAutoConfigUrl != nil || cfg.fAutoDetect, |
||||
}, |
||||
} |
||||
|
||||
if windowsProxyConf.Static.Active { |
||||
protocol := make(map[string]string) |
||||
for _, s := range strings.Split(StringFromUTF16Ptr(cfg.lpszProxy), ";") { |
||||
s = strings.TrimSpace(s) |
||||
if s == "" { |
||||
continue |
||||
} |
||||
pair := strings.SplitN(s, "=", 2) |
||||
if len(pair) > 1 { |
||||
protocol[pair[0]] = pair[1] |
||||
} else { |
||||
protocol[""] = pair[0] |
||||
} |
||||
} |
||||
|
||||
windowsProxyConf.Static.Protocols = protocol |
||||
if cfg.lpszProxyBypass != nil { |
||||
windowsProxyConf.Static.NoProxy = strings.Replace(StringFromUTF16Ptr(cfg.lpszProxyBypass), ";", ",", -1) |
||||
} |
||||
} |
||||
|
||||
if windowsProxyConf.Automatic.Active { |
||||
windowsProxyConf.Automatic.PreConfiguredURL = StringFromUTF16Ptr(cfg.lpszAutoConfigUrl) |
||||
} |
||||
} |
||||
|
||||
func getUserConfigFromWindowsSyscall() (*tWINHTTP_CURRENT_USER_IE_PROXY_CONFIG, error) { |
||||
handle, _, err := winHttpOpen.Call(0, 0, 0, 0, 0) |
||||
if handle == 0 { |
||||
return &tWINHTTP_CURRENT_USER_IE_PROXY_CONFIG{}, err |
||||
} |
||||
defer winHttpCloseHandle.Call(handle) |
||||
|
||||
config := new(tWINHTTP_CURRENT_USER_IE_PROXY_CONFIG) |
||||
|
||||
ret, _, err := winHttpGetIEProxyConfigForCurrentUser.Call(uintptr(unsafe.Pointer(config))) |
||||
if ret > 0 { |
||||
err = nil |
||||
} |
||||
|
||||
return config, err |
||||
} |
||||
|
||||
// OverrideEnvWithStaticProxy writes new values to the
|
||||
// http_proxy, https_proxy and no_proxy environment variables.
|
||||
// The values are taken from the Windows Regedit (should be called in init() function)
|
||||
func overrideEnvWithStaticProxy(conf ProxyConf, setenv envSetter) { |
||||
if conf.Static.Active { |
||||
for _, scheme := range []string{"http", "https"} { |
||||
url := mapFallback(scheme, "", conf.Static.Protocols) |
||||
setenv(scheme+"_proxy", url) |
||||
} |
||||
if conf.Static.NoProxy != "" { |
||||
setenv("no_proxy", conf.Static.NoProxy) |
||||
} |
||||
} |
||||
} |
||||
|
||||
func parseRegedit(regedit regeditValues) ProxyConf { |
||||
protocol := make(map[string]string) |
||||
for _, s := range strings.Split(regedit.ProxyServer, ";") { |
||||
if s == "" { |
||||
continue |
||||
} |
||||
pair := strings.SplitN(s, "=", 2) |
||||
if len(pair) > 1 { |
||||
protocol[pair[0]] = pair[1] |
||||
} else { |
||||
protocol[""] = pair[0] |
||||
} |
||||
} |
||||
|
||||
return ProxyConf{ |
||||
Static: StaticProxyConf{ |
||||
Active: regedit.ProxyEnable > 0, |
||||
Protocols: protocol, |
||||
NoProxy: strings.Replace(regedit.ProxyOverride, ";", ",", -1), // to match linux style
|
||||
}, |
||||
Automatic: ProxyScriptConf{ |
||||
Active: regedit.AutoConfigURL != "", |
||||
PreConfiguredURL: regedit.AutoConfigURL, |
||||
}, |
||||
} |
||||
} |
||||
|
||||
func readRegedit() (values regeditValues, err error) { |
||||
k, err := registry.OpenKey(registry.CURRENT_USER, `Software\Microsoft\Windows\CurrentVersion\Internet Settings`, registry.QUERY_VALUE) |
||||
if err != nil { |
||||
return |
||||
} |
||||
defer k.Close() |
||||
|
||||
values.ProxyServer, _, err = k.GetStringValue("ProxyServer") |
||||
if err != nil && err != registry.ErrNotExist { |
||||
return |
||||
} |
||||
values.ProxyOverride, _, err = k.GetStringValue("ProxyOverride") |
||||
if err != nil && err != registry.ErrNotExist { |
||||
return |
||||
} |
||||
|
||||
values.ProxyEnable, _, err = k.GetIntegerValue("ProxyEnable") |
||||
if err != nil && err != registry.ErrNotExist { |
||||
return |
||||
} |
||||
|
||||
values.AutoConfigURL, _, err = k.GetStringValue("AutoConfigURL") |
||||
if err != nil && err != registry.ErrNotExist { |
||||
return |
||||
} |
||||
err = nil |
||||
return |
||||
} |
@ -0,0 +1,15 @@ |
||||
package ieproxy |
||||
|
||||
import ( |
||||
"golang.org/x/sys/windows" |
||||
"unsafe" |
||||
) |
||||
|
||||
var kernel32 = windows.NewLazySystemDLL("kernel32.dll") |
||||
var globalFree = kernel32.NewProc("GlobalFree") |
||||
|
||||
func globalFreeWrapper(ptr *uint16) { |
||||
if ptr != nil { |
||||
_, _, _ = globalFree.Call(uintptr(unsafe.Pointer(ptr))) |
||||
} |
||||
} |
@ -0,0 +1,7 @@ |
||||
// +build !windows
|
||||
|
||||
package ieproxy |
||||
|
||||
func (psc *ProxyScriptConf) findProxyForURL(URL string) string { |
||||
return "" |
||||
} |
@ -0,0 +1,72 @@ |
||||
package ieproxy |
||||
|
||||
import ( |
||||
"strings" |
||||
"syscall" |
||||
"unsafe" |
||||
) |
||||
|
||||
func (psc *ProxyScriptConf) findProxyForURL(URL string) string { |
||||
if !psc.Active { |
||||
return "" |
||||
} |
||||
proxy, _ := getProxyForURL(psc.PreConfiguredURL, URL) |
||||
i := strings.Index(proxy, ";") |
||||
if i >= 0 { |
||||
return proxy[:i] |
||||
} |
||||
return proxy |
||||
} |
||||
|
||||
func getProxyForURL(pacfileURL, URL string) (string, error) { |
||||
pacfileURLPtr, err := syscall.UTF16PtrFromString(pacfileURL) |
||||
if err != nil { |
||||
return "", err |
||||
} |
||||
URLPtr, err := syscall.UTF16PtrFromString(URL) |
||||
if err != nil { |
||||
return "", err |
||||
} |
||||
|
||||
handle, _, err := winHttpOpen.Call(0, 0, 0, 0, 0) |
||||
if handle == 0 { |
||||
return "", err |
||||
} |
||||
defer winHttpCloseHandle.Call(handle) |
||||
|
||||
dwFlags := fWINHTTP_AUTOPROXY_CONFIG_URL |
||||
dwAutoDetectFlags := autoDetectFlag(0) |
||||
pfURLptr := pacfileURLPtr |
||||
|
||||
if pacfileURL == "" { |
||||
dwFlags = fWINHTTP_AUTOPROXY_AUTO_DETECT |
||||
dwAutoDetectFlags = fWINHTTP_AUTO_DETECT_TYPE_DNS_A | fWINHTTP_AUTO_DETECT_TYPE_DHCP |
||||
pfURLptr = nil |
||||
} |
||||
|
||||
options := tWINHTTP_AUTOPROXY_OPTIONS{ |
||||
dwFlags: dwFlags, // adding cache might cause issues: https://github.com/mattn/go-ieproxy/issues/6
|
||||
dwAutoDetectFlags: dwAutoDetectFlags, |
||||
lpszAutoConfigUrl: pfURLptr, |
||||
lpvReserved: nil, |
||||
dwReserved: 0, |
||||
fAutoLogonIfChallenged: true, // may not be optimal https://msdn.microsoft.com/en-us/library/windows/desktop/aa383153(v=vs.85).aspx
|
||||
} // lpszProxyBypass isn't used as this only executes in cases where there (may) be a pac file (autodetect can fail), where lpszProxyBypass couldn't be returned.
|
||||
// in the case that autodetect fails and no pre-specified pacfile is present, no proxy is returned.
|
||||
|
||||
info := new(tWINHTTP_PROXY_INFO) |
||||
|
||||
ret, _, err := winHttpGetProxyForURL.Call( |
||||
handle, |
||||
uintptr(unsafe.Pointer(URLPtr)), |
||||
uintptr(unsafe.Pointer(&options)), |
||||
uintptr(unsafe.Pointer(info)), |
||||
) |
||||
if ret > 0 { |
||||
err = nil |
||||
} |
||||
|
||||
defer globalFreeWrapper(info.lpszProxyBypass) |
||||
defer globalFreeWrapper(info.lpszProxy) |
||||
return StringFromUTF16Ptr(info.lpszProxy), err |
||||
} |
@ -0,0 +1,13 @@ |
||||
// +build !windows
|
||||
|
||||
package ieproxy |
||||
|
||||
import ( |
||||
"net/http" |
||||
"net/url" |
||||
) |
||||
|
||||
func proxyMiddleman() func(req *http.Request) (i *url.URL, e error) { |
||||
// Fallthrough to ProxyFromEnvironment on all other OSes.
|
||||
return http.ProxyFromEnvironment |
||||
} |
@ -0,0 +1,51 @@ |
||||
package ieproxy |
||||
|
||||
import ( |
||||
"net/http" |
||||
"net/url" |
||||
|
||||
"golang.org/x/net/http/httpproxy" |
||||
) |
||||
|
||||
func proxyMiddleman() func(req *http.Request) (i *url.URL, e error) { |
||||
// Get the proxy configuration
|
||||
conf := GetConf() |
||||
envcfg := httpproxy.FromEnvironment() |
||||
|
||||
if envcfg.HTTPProxy != "" || envcfg.HTTPSProxy != "" { |
||||
// If the user manually specifies environment variables, prefer those over the Windows config.
|
||||
return http.ProxyFromEnvironment |
||||
} else if conf.Automatic.Active { |
||||
// If automatic proxy obtaining is specified
|
||||
return func(req *http.Request) (i *url.URL, e error) { |
||||
host := conf.Automatic.FindProxyForURL(req.URL.String()) |
||||
if host == "" { |
||||
return nil, nil |
||||
} |
||||
return &url.URL{Host: host}, nil |
||||
} |
||||
} else if conf.Static.Active { |
||||
// If static proxy obtaining is specified
|
||||
prox := httpproxy.Config{ |
||||
HTTPSProxy: mapFallback("https", "", conf.Static.Protocols), |
||||
HTTPProxy: mapFallback("http", "", conf.Static.Protocols), |
||||
NoProxy: conf.Static.NoProxy, |
||||
} |
||||
|
||||
return func(req *http.Request) (i *url.URL, e error) { |
||||
return prox.ProxyFunc()(req.URL) |
||||
} |
||||
} else { |
||||
// Final fallthrough case; use the environment variables.
|
||||
return http.ProxyFromEnvironment |
||||
} |
||||
} |
||||
|
||||
// Return oKey or fbKey if oKey doesn't exist in the map.
|
||||
func mapFallback(oKey, fbKey string, m map[string]string) string { |
||||
if v, ok := m[oKey]; ok { |
||||
return v |
||||
} else { |
||||
return m[fbKey] |
||||
} |
||||
} |
@ -0,0 +1,23 @@ |
||||
package ieproxy |
||||
|
||||
import ( |
||||
"unicode/utf16" |
||||
"unsafe" |
||||
) |
||||
|
||||
// StringFromUTF16Ptr converts a *uint16 C string to a Go String
|
||||
func StringFromUTF16Ptr(s *uint16) string { |
||||
if s == nil { |
||||
return "" |
||||
} |
||||
|
||||
p := (*[1<<30 - 1]uint16)(unsafe.Pointer(s)) |
||||
|
||||
// find the string length
|
||||
sz := 0 |
||||
for p[sz] != 0 { |
||||
sz++ |
||||
} |
||||
|
||||
return string(utf16.Decode(p[:sz:sz])) |
||||
} |
@ -0,0 +1,50 @@ |
||||
package ieproxy |
||||
|
||||
import "golang.org/x/sys/windows" |
||||
|
||||
var winHttp = windows.NewLazySystemDLL("winhttp.dll") |
||||
var winHttpGetProxyForURL = winHttp.NewProc("WinHttpGetProxyForUrl") |
||||
var winHttpOpen = winHttp.NewProc("WinHttpOpen") |
||||
var winHttpCloseHandle = winHttp.NewProc("WinHttpCloseHandle") |
||||
var winHttpGetIEProxyConfigForCurrentUser = winHttp.NewProc("WinHttpGetIEProxyConfigForCurrentUser") |
||||
|
||||
type tWINHTTP_AUTOPROXY_OPTIONS struct { |
||||
dwFlags autoProxyFlag |
||||
dwAutoDetectFlags autoDetectFlag |
||||
lpszAutoConfigUrl *uint16 |
||||
lpvReserved *uint16 |
||||
dwReserved uint32 |
||||
fAutoLogonIfChallenged bool |
||||
} |
||||
type autoProxyFlag uint32 |
||||
|
||||
const ( |
||||
fWINHTTP_AUTOPROXY_AUTO_DETECT = autoProxyFlag(0x00000001) |
||||
fWINHTTP_AUTOPROXY_CONFIG_URL = autoProxyFlag(0x00000002) |
||||
fWINHTTP_AUTOPROXY_NO_CACHE_CLIENT = autoProxyFlag(0x00080000) |
||||
fWINHTTP_AUTOPROXY_NO_CACHE_SVC = autoProxyFlag(0x00100000) |
||||
fWINHTTP_AUTOPROXY_NO_DIRECTACCESS = autoProxyFlag(0x00040000) |
||||
fWINHTTP_AUTOPROXY_RUN_INPROCESS = autoProxyFlag(0x00010000) |
||||
fWINHTTP_AUTOPROXY_RUN_OUTPROCESS_ONLY = autoProxyFlag(0x00020000) |
||||
fWINHTTP_AUTOPROXY_SORT_RESULTS = autoProxyFlag(0x00400000) |
||||
) |
||||
|
||||
type autoDetectFlag uint32 |
||||
|
||||
const ( |
||||
fWINHTTP_AUTO_DETECT_TYPE_DHCP = autoDetectFlag(0x00000001) |
||||
fWINHTTP_AUTO_DETECT_TYPE_DNS_A = autoDetectFlag(0x00000002) |
||||
) |
||||
|
||||
type tWINHTTP_PROXY_INFO struct { |
||||
dwAccessType uint32 |
||||
lpszProxy *uint16 |
||||
lpszProxyBypass *uint16 |
||||
} |
||||
|
||||
type tWINHTTP_CURRENT_USER_IE_PROXY_CONFIG struct { |
||||
fAutoDetect bool |
||||
lpszAutoConfigUrl *uint16 |
||||
lpszProxy *uint16 |
||||
lpszProxyBypass *uint16 |
||||
} |
File diff suppressed because it is too large
Load Diff
@ -0,0 +1,370 @@ |
||||
// Copyright 2017 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.
|
||||
|
||||
// Package httpproxy provides support for HTTP proxy determination
|
||||
// based on environment variables, as provided by net/http's
|
||||
// ProxyFromEnvironment function.
|
||||
//
|
||||
// The API is not subject to the Go 1 compatibility promise and may change at
|
||||
// any time.
|
||||
package httpproxy |
||||
|
||||
import ( |
||||
"errors" |
||||
"fmt" |
||||
"net" |
||||
"net/url" |
||||
"os" |
||||
"strings" |
||||
"unicode/utf8" |
||||
|
||||
"golang.org/x/net/idna" |
||||
) |
||||
|
||||
// Config holds configuration for HTTP proxy settings. See
|
||||
// FromEnvironment for details.
|
||||
type Config struct { |
||||
// HTTPProxy represents the value of the HTTP_PROXY or
|
||||
// http_proxy environment variable. It will be used as the proxy
|
||||
// URL for HTTP requests and HTTPS requests unless overridden by
|
||||
// HTTPSProxy or NoProxy.
|
||||
HTTPProxy string |
||||
|
||||
// HTTPSProxy represents the HTTPS_PROXY or https_proxy
|
||||
// environment variable. It will be used as the proxy URL for
|
||||
// HTTPS requests unless overridden by NoProxy.
|
||||
HTTPSProxy string |
||||
|
||||
// NoProxy represents the NO_PROXY or no_proxy environment
|
||||
// variable. It specifies a string that contains comma-separated values
|
||||
// specifying hosts that should be excluded from proxying. Each value is
|
||||
// represented by an IP address prefix (1.2.3.4), an IP address prefix in
|
||||
// CIDR notation (1.2.3.4/8), a domain name, or a special DNS label (*).
|
||||
// An IP address prefix and domain name can also include a literal port
|
||||
// number (1.2.3.4:80).
|
||||
// A domain name matches that name and all subdomains. A domain name with
|
||||
// a leading "." matches subdomains only. For example "foo.com" matches
|
||||
// "foo.com" and "bar.foo.com"; ".y.com" matches "x.y.com" but not "y.com".
|
||||
// A single asterisk (*) indicates that no proxying should be done.
|
||||
// A best effort is made to parse the string and errors are
|
||||
// ignored.
|
||||
NoProxy string |
||||
|
||||
// CGI holds whether the current process is running
|
||||
// as a CGI handler (FromEnvironment infers this from the
|
||||
// presence of a REQUEST_METHOD environment variable).
|
||||
// When this is set, ProxyForURL will return an error
|
||||
// when HTTPProxy applies, because a client could be
|
||||
// setting HTTP_PROXY maliciously. See https://golang.org/s/cgihttpproxy.
|
||||
CGI bool |
||||
} |
||||
|
||||
// config holds the parsed configuration for HTTP proxy settings.
|
||||
type config struct { |
||||
// Config represents the original configuration as defined above.
|
||||
Config |
||||
|
||||
// httpsProxy is the parsed URL of the HTTPSProxy if defined.
|
||||
httpsProxy *url.URL |
||||
|
||||
// httpProxy is the parsed URL of the HTTPProxy if defined.
|
||||
httpProxy *url.URL |
||||
|
||||
// ipMatchers represent all values in the NoProxy that are IP address
|
||||
// prefixes or an IP address in CIDR notation.
|
||||
ipMatchers []matcher |
||||
|
||||
// domainMatchers represent all values in the NoProxy that are a domain
|
||||
// name or hostname & domain name
|
||||
domainMatchers []matcher |
||||
} |
||||
|
||||
// FromEnvironment returns a Config instance populated from the
|
||||
// environment variables HTTP_PROXY, HTTPS_PROXY and NO_PROXY (or the
|
||||
// lowercase versions thereof). HTTPS_PROXY takes precedence over
|
||||
// HTTP_PROXY for https requests.
|
||||
//
|
||||
// The environment values may be either a complete URL or a
|
||||
// "host[:port]", in which case the "http" scheme is assumed. An error
|
||||
// is returned if the value is a different form.
|
||||
func FromEnvironment() *Config { |
||||
return &Config{ |
||||
HTTPProxy: getEnvAny("HTTP_PROXY", "http_proxy"), |
||||
HTTPSProxy: getEnvAny("HTTPS_PROXY", "https_proxy"), |
||||
NoProxy: getEnvAny("NO_PROXY", "no_proxy"), |
||||
CGI: os.Getenv("REQUEST_METHOD") != "", |
||||
} |
||||
} |
||||
|
||||
func getEnvAny(names ...string) string { |
||||
for _, n := range names { |
||||
if val := os.Getenv(n); val != "" { |
||||
return val |
||||
} |
||||
} |
||||
return "" |
||||
} |
||||
|
||||
// ProxyFunc returns a function that determines the proxy URL to use for
|
||||
// a given request URL. Changing the contents of cfg will not affect
|
||||
// proxy functions created earlier.
|
||||
//
|
||||
// A nil URL and nil error are returned if no proxy is defined in the
|
||||
// environment, or a proxy should not be used for the given request, as
|
||||
// defined by NO_PROXY.
|
||||
//
|
||||
// As a special case, if req.URL.Host is "localhost" (with or without a
|
||||
// port number), then a nil URL and nil error will be returned.
|
||||
func (cfg *Config) ProxyFunc() func(reqURL *url.URL) (*url.URL, error) { |
||||
// Preprocess the Config settings for more efficient evaluation.
|
||||
cfg1 := &config{ |
||||
Config: *cfg, |
||||
} |
||||
cfg1.init() |
||||
return cfg1.proxyForURL |
||||
} |
||||
|
||||
func (cfg *config) proxyForURL(reqURL *url.URL) (*url.URL, error) { |
||||
var proxy *url.URL |
||||
if reqURL.Scheme == "https" { |
||||
proxy = cfg.httpsProxy |
||||
} |
||||
if proxy == nil { |
||||
proxy = cfg.httpProxy |
||||
if proxy != nil && cfg.CGI { |
||||
return nil, errors.New("refusing to use HTTP_PROXY value in CGI environment; see golang.org/s/cgihttpproxy") |
||||
} |
||||
} |
||||
if proxy == nil { |
||||
return nil, nil |
||||
} |
||||
if !cfg.useProxy(canonicalAddr(reqURL)) { |
||||
return nil, nil |
||||
} |
||||
|
||||
return proxy, nil |
||||
} |
||||
|
||||
func parseProxy(proxy string) (*url.URL, error) { |
||||
if proxy == "" { |
||||
return nil, nil |
||||
} |
||||
|
||||
proxyURL, err := url.Parse(proxy) |
||||
if err != nil || |
||||
(proxyURL.Scheme != "http" && |
||||
proxyURL.Scheme != "https" && |
||||
proxyURL.Scheme != "socks5") { |
||||
// proxy was bogus. Try prepending "http://" to it and
|
||||
// see if that parses correctly. If not, we fall
|
||||
// through and complain about the original one.
|
||||
if proxyURL, err := url.Parse("http://" + proxy); err == nil { |
||||
return proxyURL, nil |
||||
} |
||||
} |
||||
if err != nil { |
||||
return nil, fmt.Errorf("invalid proxy address %q: %v", proxy, err) |
||||
} |
||||
return proxyURL, nil |
||||
} |
||||
|
||||
// useProxy reports whether requests to addr should use a proxy,
|
||||
// according to the NO_PROXY or no_proxy environment variable.
|
||||
// addr is always a canonicalAddr with a host and port.
|
||||
func (cfg *config) useProxy(addr string) bool { |
||||
if len(addr) == 0 { |
||||
return true |
||||
} |
||||
host, port, err := net.SplitHostPort(addr) |
||||
if err != nil { |
||||
return false |
||||
} |
||||
if host == "localhost" { |
||||
return false |
||||
} |
||||
ip := net.ParseIP(host) |
||||
if ip != nil { |
||||
if ip.IsLoopback() { |
||||
return false |
||||
} |
||||
} |
||||
|
||||
addr = strings.ToLower(strings.TrimSpace(host)) |
||||
|
||||
if ip != nil { |
||||
for _, m := range cfg.ipMatchers { |
||||
if m.match(addr, port, ip) { |
||||
return false |
||||
} |
||||
} |
||||
} |
||||
for _, m := range cfg.domainMatchers { |
||||
if m.match(addr, port, ip) { |
||||
return false |
||||
} |
||||
} |
||||
return true |
||||
} |
||||
|
||||
func (c *config) init() { |
||||
if parsed, err := parseProxy(c.HTTPProxy); err == nil { |
||||
c.httpProxy = parsed |
||||
} |
||||
if parsed, err := parseProxy(c.HTTPSProxy); err == nil { |
||||
c.httpsProxy = parsed |
||||
} |
||||
|
||||
for _, p := range strings.Split(c.NoProxy, ",") { |
||||
p = strings.ToLower(strings.TrimSpace(p)) |
||||
if len(p) == 0 { |
||||
continue |
||||
} |
||||
|
||||
if p == "*" { |
||||
c.ipMatchers = []matcher{allMatch{}} |
||||
c.domainMatchers = []matcher{allMatch{}} |
||||
return |
||||
} |
||||
|
||||
// IPv4/CIDR, IPv6/CIDR
|
||||
if _, pnet, err := net.ParseCIDR(p); err == nil { |
||||
c.ipMatchers = append(c.ipMatchers, cidrMatch{cidr: pnet}) |
||||
continue |
||||
} |
||||
|
||||
// IPv4:port, [IPv6]:port
|
||||
phost, pport, err := net.SplitHostPort(p) |
||||
if err == nil { |
||||
if len(phost) == 0 { |
||||
// There is no host part, likely the entry is malformed; ignore.
|
||||
continue |
||||
} |
||||
if phost[0] == '[' && phost[len(phost)-1] == ']' { |
||||
phost = phost[1 : len(phost)-1] |
||||
} |
||||
} else { |
||||
phost = p |
||||
} |
||||
// IPv4, IPv6
|
||||
if pip := net.ParseIP(phost); pip != nil { |
||||
c.ipMatchers = append(c.ipMatchers, ipMatch{ip: pip, port: pport}) |
||||
continue |
||||
} |
||||
|
||||
if len(phost) == 0 { |
||||
// There is no host part, likely the entry is malformed; ignore.
|
||||
continue |
||||
} |
||||
|
||||
// domain.com or domain.com:80
|
||||
// foo.com matches bar.foo.com
|
||||
// .domain.com or .domain.com:port
|
||||
// *.domain.com or *.domain.com:port
|
||||
if strings.HasPrefix(phost, "*.") { |
||||
phost = phost[1:] |
||||
} |
||||
matchHost := false |
||||
if phost[0] != '.' { |
||||
matchHost = true |
||||
phost = "." + phost |
||||
} |
||||
c.domainMatchers = append(c.domainMatchers, domainMatch{host: phost, port: pport, matchHost: matchHost}) |
||||
} |
||||
} |
||||
|
||||
var portMap = map[string]string{ |
||||
"http": "80", |
||||
"https": "443", |
||||
"socks5": "1080", |
||||
} |
||||
|
||||
// canonicalAddr returns url.Host but always with a ":port" suffix
|
||||
func canonicalAddr(url *url.URL) string { |
||||
addr := url.Hostname() |
||||
if v, err := idnaASCII(addr); err == nil { |
||||
addr = v |
||||
} |
||||
port := url.Port() |
||||
if port == "" { |
||||
port = portMap[url.Scheme] |
||||
} |
||||
return net.JoinHostPort(addr, port) |
||||
} |
||||
|
||||
// Given a string of the form "host", "host:port", or "[ipv6::address]:port",
|
||||
// return true if the string includes a port.
|
||||
func hasPort(s string) bool { return strings.LastIndex(s, ":") > strings.LastIndex(s, "]") } |
||||
|
||||
func idnaASCII(v string) (string, error) { |
||||
// TODO: Consider removing this check after verifying performance is okay.
|
||||
// Right now punycode verification, length checks, context checks, and the
|
||||
// permissible character tests are all omitted. It also prevents the ToASCII
|
||||
// call from salvaging an invalid IDN, when possible. As a result it may be
|
||||
// possible to have two IDNs that appear identical to the user where the
|
||||
// ASCII-only version causes an error downstream whereas the non-ASCII
|
||||
// version does not.
|
||||
// Note that for correct ASCII IDNs ToASCII will only do considerably more
|
||||
// work, but it will not cause an allocation.
|
||||
if isASCII(v) { |
||||
return v, nil |
||||
} |
||||
return idna.Lookup.ToASCII(v) |
||||
} |
||||
|
||||
func isASCII(s string) bool { |
||||
for i := 0; i < len(s); i++ { |
||||
if s[i] >= utf8.RuneSelf { |
||||
return false |
||||
} |
||||
} |
||||
return true |
||||
} |
||||
|
||||
// matcher represents the matching rule for a given value in the NO_PROXY list
|
||||
type matcher interface { |
||||
// match returns true if the host and optional port or ip and optional port
|
||||
// are allowed
|
||||
match(host, port string, ip net.IP) bool |
||||
} |
||||
|
||||
// allMatch matches on all possible inputs
|
||||
type allMatch struct{} |
||||
|
||||
func (a allMatch) match(host, port string, ip net.IP) bool { |
||||
return true |
||||
} |
||||
|
||||
type cidrMatch struct { |
||||
cidr *net.IPNet |
||||
} |
||||
|
||||
func (m cidrMatch) match(host, port string, ip net.IP) bool { |
||||
return m.cidr.Contains(ip) |
||||
} |
||||
|
||||
type ipMatch struct { |
||||
ip net.IP |
||||
port string |
||||
} |
||||
|
||||
func (m ipMatch) match(host, port string, ip net.IP) bool { |
||||
if m.ip.Equal(ip) { |
||||
return m.port == "" || m.port == port |
||||
} |
||||
return false |
||||
} |
||||
|
||||
type domainMatch struct { |
||||
host string |
||||
port string |
||||
|
||||
matchHost bool |
||||
} |
||||
|
||||
func (m domainMatch) match(host, port string, ip net.IP) bool { |
||||
if strings.HasSuffix(host, m.host) || (m.matchHost && host == m.host[1:]) { |
||||
return m.port == "" || m.port == port |
||||
} |
||||
return false |
||||
} |
@ -0,0 +1,734 @@ |
||||
// Code generated by running "go generate" in golang.org/x/text. DO NOT EDIT.
|
||||
|
||||
// Copyright 2016 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.
|
||||
|
||||
// +build go1.10
|
||||
|
||||
// Package idna implements IDNA2008 using the compatibility processing
|
||||
// defined by UTS (Unicode Technical Standard) #46, which defines a standard to
|
||||
// deal with the transition from IDNA2003.
|
||||
//
|
||||
// IDNA2008 (Internationalized Domain Names for Applications), is defined in RFC
|
||||
// 5890, RFC 5891, RFC 5892, RFC 5893 and RFC 5894.
|
||||
// UTS #46 is defined in https://www.unicode.org/reports/tr46.
|
||||
// See https://unicode.org/cldr/utility/idna.jsp for a visualization of the
|
||||
// differences between these two standards.
|
||||
package idna // import "golang.org/x/net/idna"
|
||||
|
||||
import ( |
||||
"fmt" |
||||
"strings" |
||||
"unicode/utf8" |
||||
|
||||
"golang.org/x/text/secure/bidirule" |
||||
"golang.org/x/text/unicode/bidi" |
||||
"golang.org/x/text/unicode/norm" |
||||
) |
||||
|
||||
// NOTE: Unlike common practice in Go APIs, the functions will return a
|
||||
// sanitized domain name in case of errors. Browsers sometimes use a partially
|
||||
// evaluated string as lookup.
|
||||
// TODO: the current error handling is, in my opinion, the least opinionated.
|
||||
// Other strategies are also viable, though:
|
||||
// Option 1) Return an empty string in case of error, but allow the user to
|
||||
// specify explicitly which errors to ignore.
|
||||
// Option 2) Return the partially evaluated string if it is itself a valid
|
||||
// string, otherwise return the empty string in case of error.
|
||||
// Option 3) Option 1 and 2.
|
||||
// Option 4) Always return an empty string for now and implement Option 1 as
|
||||
// needed, and document that the return string may not be empty in case of
|
||||
// error in the future.
|
||||
// I think Option 1 is best, but it is quite opinionated.
|
||||
|
||||
// ToASCII is a wrapper for Punycode.ToASCII.
|
||||
func ToASCII(s string) (string, error) { |
||||
return Punycode.process(s, true) |
||||
} |
||||
|
||||
// ToUnicode is a wrapper for Punycode.ToUnicode.
|
||||
func ToUnicode(s string) (string, error) { |
||||
return Punycode.process(s, false) |
||||
} |
||||
|
||||
// An Option configures a Profile at creation time.
|
||||
type Option func(*options) |
||||
|
||||
// Transitional sets a Profile to use the Transitional mapping as defined in UTS
|
||||
// #46. This will cause, for example, "ß" to be mapped to "ss". Using the
|
||||
// transitional mapping provides a compromise between IDNA2003 and IDNA2008
|
||||
// compatibility. It is used by most browsers when resolving domain names. This
|
||||
// option is only meaningful if combined with MapForLookup.
|
||||
func Transitional(transitional bool) Option { |
||||
return func(o *options) { o.transitional = true } |
||||
} |
||||
|
||||
// VerifyDNSLength sets whether a Profile should fail if any of the IDN parts
|
||||
// are longer than allowed by the RFC.
|
||||
func VerifyDNSLength(verify bool) Option { |
||||
return func(o *options) { o.verifyDNSLength = verify } |
||||
} |
||||
|
||||
// RemoveLeadingDots removes leading label separators. Leading runes that map to
|
||||
// dots, such as U+3002 IDEOGRAPHIC FULL STOP, are removed as well.
|
||||
//
|
||||
// This is the behavior suggested by the UTS #46 and is adopted by some
|
||||
// browsers.
|
||||
func RemoveLeadingDots(remove bool) Option { |
||||
return func(o *options) { o.removeLeadingDots = remove } |
||||
} |
||||
|
||||
// ValidateLabels sets whether to check the mandatory label validation criteria
|
||||
// as defined in Section 5.4 of RFC 5891. This includes testing for correct use
|
||||
// of hyphens ('-'), normalization, validity of runes, and the context rules.
|
||||
func ValidateLabels(enable bool) Option { |
||||
return func(o *options) { |
||||
// Don't override existing mappings, but set one that at least checks
|
||||
// normalization if it is not set.
|
||||
if o.mapping == nil && enable { |
||||
o.mapping = normalize |
||||
} |
||||
o.trie = trie |
||||
o.validateLabels = enable |
||||
o.fromPuny = validateFromPunycode |
||||
} |
||||
} |
||||
|
||||
// StrictDomainName limits the set of permissible ASCII characters to those
|
||||
// allowed in domain names as defined in RFC 1034 (A-Z, a-z, 0-9 and the
|
||||
// hyphen). This is set by default for MapForLookup and ValidateForRegistration.
|
||||
//
|
||||
// This option is useful, for instance, for browsers that allow characters
|
||||
// outside this range, for example a '_' (U+005F LOW LINE). See
|
||||
// http://www.rfc-editor.org/std/std3.txt for more details This option
|
||||
// corresponds to the UseSTD3ASCIIRules option in UTS #46.
|
||||
func StrictDomainName(use bool) Option { |
||||
return func(o *options) { |
||||
o.trie = trie |
||||
o.useSTD3Rules = use |
||||
o.fromPuny = validateFromPunycode |
||||
} |
||||
} |
||||
|
||||
// NOTE: the following options pull in tables. The tables should not be linked
|
||||
// in as long as the options are not used.
|
||||
|
||||
// BidiRule enables the Bidi rule as defined in RFC 5893. Any application
|
||||
// that relies on proper validation of labels should include this rule.
|
||||
func BidiRule() Option { |
||||
return func(o *options) { o.bidirule = bidirule.ValidString } |
||||
} |
||||
|
||||
// ValidateForRegistration sets validation options to verify that a given IDN is
|
||||
// properly formatted for registration as defined by Section 4 of RFC 5891.
|
||||
func ValidateForRegistration() Option { |
||||
return func(o *options) { |
||||
o.mapping = validateRegistration |
||||
StrictDomainName(true)(o) |
||||
ValidateLabels(true)(o) |
||||
VerifyDNSLength(true)(o) |
||||
BidiRule()(o) |
||||
} |
||||
} |
||||
|
||||
// MapForLookup sets validation and mapping options such that a given IDN is
|
||||
// transformed for domain name lookup according to the requirements set out in
|
||||
// Section 5 of RFC 5891. The mappings follow the recommendations of RFC 5894,
|
||||
// RFC 5895 and UTS 46. It does not add the Bidi Rule. Use the BidiRule option
|
||||
// to add this check.
|
||||
//
|
||||
// The mappings include normalization and mapping case, width and other
|
||||
// compatibility mappings.
|
||||
func MapForLookup() Option { |
||||
return func(o *options) { |
||||
o.mapping = validateAndMap |
||||
StrictDomainName(true)(o) |
||||
ValidateLabels(true)(o) |
||||
} |
||||
} |
||||
|
||||
type options struct { |
||||
transitional bool |
||||
useSTD3Rules bool |
||||
validateLabels bool |
||||
verifyDNSLength bool |
||||
removeLeadingDots bool |
||||
|
||||
trie *idnaTrie |
||||
|
||||
// fromPuny calls validation rules when converting A-labels to U-labels.
|
||||
fromPuny func(p *Profile, s string) error |
||||
|
||||
// mapping implements a validation and mapping step as defined in RFC 5895
|
||||
// or UTS 46, tailored to, for example, domain registration or lookup.
|
||||
mapping func(p *Profile, s string) (mapped string, isBidi bool, err error) |
||||
|
||||
// bidirule, if specified, checks whether s conforms to the Bidi Rule
|
||||
// defined in RFC 5893.
|
||||
bidirule func(s string) bool |
||||
} |
||||
|
||||
// A Profile defines the configuration of an IDNA mapper.
|
||||
type Profile struct { |
||||
options |
||||
} |
||||
|
||||
func apply(o *options, opts []Option) { |
||||
for _, f := range opts { |
||||
f(o) |
||||
} |
||||
} |
||||
|
||||
// New creates a new Profile.
|
||||
//
|
||||
// With no options, the returned Profile is the most permissive and equals the
|
||||
// Punycode Profile. Options can be passed to further restrict the Profile. The
|
||||
// MapForLookup and ValidateForRegistration options set a collection of options,
|
||||
// for lookup and registration purposes respectively, which can be tailored by
|
||||
// adding more fine-grained options, where later options override earlier
|
||||
// options.
|
||||
func New(o ...Option) *Profile { |
||||
p := &Profile{} |
||||
apply(&p.options, o) |
||||
return p |
||||
} |
||||
|
||||
// ToASCII converts a domain or domain label to its ASCII form. For example,
|
||||
// ToASCII("bücher.example.com") is "xn--bcher-kva.example.com", and
|
||||
// ToASCII("golang") is "golang". If an error is encountered it will return
|
||||
// an error and a (partially) processed result.
|
||||
func (p *Profile) ToASCII(s string) (string, error) { |
||||
return p.process(s, true) |
||||
} |
||||
|
||||
// ToUnicode converts a domain or domain label to its Unicode form. For example,
|
||||
// ToUnicode("xn--bcher-kva.example.com") is "bücher.example.com", and
|
||||
// ToUnicode("golang") is "golang". If an error is encountered it will return
|
||||
// an error and a (partially) processed result.
|
||||
func (p *Profile) ToUnicode(s string) (string, error) { |
||||
pp := *p |
||||
pp.transitional = false |
||||
return pp.process(s, false) |
||||
} |
||||
|
||||
// String reports a string with a description of the profile for debugging
|
||||
// purposes. The string format may change with different versions.
|
||||
func (p *Profile) String() string { |
||||
s := "" |
||||
if p.transitional { |
||||
s = "Transitional" |
||||
} else { |
||||
s = "NonTransitional" |
||||
} |
||||
if p.useSTD3Rules { |
||||
s += ":UseSTD3Rules" |
||||
} |
||||
if p.validateLabels { |
||||
s += ":ValidateLabels" |
||||
} |
||||
if p.verifyDNSLength { |
||||
s += ":VerifyDNSLength" |
||||
} |
||||
return s |
||||
} |
||||
|
||||
var ( |
||||
// Punycode is a Profile that does raw punycode processing with a minimum
|
||||
// of validation.
|
||||
Punycode *Profile = punycode |
||||
|
||||
// Lookup is the recommended profile for looking up domain names, according
|
||||
// to Section 5 of RFC 5891. The exact configuration of this profile may
|
||||
// change over time.
|
||||
Lookup *Profile = lookup |
||||
|
||||
// Display is the recommended profile for displaying domain names.
|
||||
// The configuration of this profile may change over time.
|
||||
Display *Profile = display |
||||
|
||||
// Registration is the recommended profile for checking whether a given
|
||||
// IDN is valid for registration, according to Section 4 of RFC 5891.
|
||||
Registration *Profile = registration |
||||
|
||||
punycode = &Profile{} |
||||
lookup = &Profile{options{ |
||||
transitional: true, |
||||
useSTD3Rules: true, |
||||
validateLabels: true, |
||||
trie: trie, |
||||
fromPuny: validateFromPunycode, |
||||
mapping: validateAndMap, |
||||
bidirule: bidirule.ValidString, |
||||
}} |
||||
display = &Profile{options{ |
||||
useSTD3Rules: true, |
||||
validateLabels: true, |
||||
trie: trie, |
||||
fromPuny: validateFromPunycode, |
||||
mapping: validateAndMap, |
||||
bidirule: bidirule.ValidString, |
||||
}} |
||||
registration = &Profile{options{ |
||||
useSTD3Rules: true, |
||||
validateLabels: true, |
||||
verifyDNSLength: true, |
||||
trie: trie, |
||||
fromPuny: validateFromPunycode, |
||||
mapping: validateRegistration, |
||||
bidirule: bidirule.ValidString, |
||||
}} |
||||
|
||||
// TODO: profiles
|
||||
// Register: recommended for approving domain names: don't do any mappings
|
||||
// but rather reject on invalid input. Bundle or block deviation characters.
|
||||
) |
||||
|
||||
type labelError struct{ label, code_ string } |
||||
|
||||
func (e labelError) code() string { return e.code_ } |
||||
func (e labelError) Error() string { |
||||
return fmt.Sprintf("idna: invalid label %q", e.label) |
||||
} |
||||
|
||||
type runeError rune |
||||
|
||||
func (e runeError) code() string { return "P1" } |
||||
func (e runeError) Error() string { |
||||
return fmt.Sprintf("idna: disallowed rune %U", e) |
||||
} |
||||
|
||||
// process implements the algorithm described in section 4 of UTS #46,
|
||||
// see https://www.unicode.org/reports/tr46.
|
||||
func (p *Profile) process(s string, toASCII bool) (string, error) { |
||||
var err error |
||||
var isBidi bool |
||||
if p.mapping != nil { |
||||
s, isBidi, err = p.mapping(p, s) |
||||
} |
||||
// Remove leading empty labels.
|
||||
if p.removeLeadingDots { |
||||
for ; len(s) > 0 && s[0] == '.'; s = s[1:] { |
||||
} |
||||
} |
||||
// TODO: allow for a quick check of the tables data.
|
||||
// It seems like we should only create this error on ToASCII, but the
|
||||
// UTS 46 conformance tests suggests we should always check this.
|
||||
if err == nil && p.verifyDNSLength && s == "" { |
||||
err = &labelError{s, "A4"} |
||||
} |
||||
labels := labelIter{orig: s} |
||||
for ; !labels.done(); labels.next() { |
||||
label := labels.label() |
||||
if label == "" { |
||||
// Empty labels are not okay. The label iterator skips the last
|
||||
// label if it is empty.
|
||||
if err == nil && p.verifyDNSLength { |
||||
err = &labelError{s, "A4"} |
||||
} |
||||
continue |
||||
} |
||||
if strings.HasPrefix(label, acePrefix) { |
||||
u, err2 := decode(label[len(acePrefix):]) |
||||
if err2 != nil { |
||||
if err == nil { |
||||
err = err2 |
||||
} |
||||
// Spec says keep the old label.
|
||||
continue |
||||
} |
||||
isBidi = isBidi || bidirule.DirectionString(u) != bidi.LeftToRight |
||||
labels.set(u) |
||||
if err == nil && p.validateLabels { |
||||
err = p.fromPuny(p, u) |
||||
} |
||||
if err == nil { |
||||
// This should be called on NonTransitional, according to the
|
||||
// spec, but that currently does not have any effect. Use the
|
||||
// original profile to preserve options.
|
||||
err = p.validateLabel(u) |
||||
} |
||||
} else if err == nil { |
||||
err = p.validateLabel(label) |
||||
} |
||||
} |
||||
if isBidi && p.bidirule != nil && err == nil { |
||||
for labels.reset(); !labels.done(); labels.next() { |
||||
if !p.bidirule(labels.label()) { |
||||
err = &labelError{s, "B"} |
||||
break |
||||
} |
||||
} |
||||
} |
||||
if toASCII { |
||||
for labels.reset(); !labels.done(); labels.next() { |
||||
label := labels.label() |
||||
if !ascii(label) { |
||||
a, err2 := encode(acePrefix, label) |
||||
if err == nil { |
||||
err = err2 |
||||
} |
||||
label = a |
||||
labels.set(a) |
||||
} |
||||
n := len(label) |
||||
if p.verifyDNSLength && err == nil && (n == 0 || n > 63) { |
||||
err = &labelError{label, "A4"} |
||||
} |
||||
} |
||||
} |
||||
s = labels.result() |
||||
if toASCII && p.verifyDNSLength && err == nil { |
||||
// Compute the length of the domain name minus the root label and its dot.
|
||||
n := len(s) |
||||
if n > 0 && s[n-1] == '.' { |
||||
n-- |
||||
} |
||||
if len(s) < 1 || n > 253 { |
||||
err = &labelError{s, "A4"} |
||||
} |
||||
} |
||||
return s, err |
||||
} |
||||
|
||||
func normalize(p *Profile, s string) (mapped string, isBidi bool, err error) { |
||||
// TODO: consider first doing a quick check to see if any of these checks
|
||||
// need to be done. This will make it slower in the general case, but
|
||||
// faster in the common case.
|
||||
mapped = norm.NFC.String(s) |
||||
isBidi = bidirule.DirectionString(mapped) == bidi.RightToLeft |
||||
return mapped, isBidi, nil |
||||
} |
||||
|
||||
func validateRegistration(p *Profile, s string) (idem string, bidi bool, err error) { |
||||
// TODO: filter need for normalization in loop below.
|
||||
if !norm.NFC.IsNormalString(s) { |
||||
return s, false, &labelError{s, "V1"} |
||||
} |
||||
for i := 0; i < len(s); { |
||||
v, sz := trie.lookupString(s[i:]) |
||||
if sz == 0 { |
||||
return s, bidi, runeError(utf8.RuneError) |
||||
} |
||||
bidi = bidi || info(v).isBidi(s[i:]) |
||||
// Copy bytes not copied so far.
|
||||
switch p.simplify(info(v).category()) { |
||||
// TODO: handle the NV8 defined in the Unicode idna data set to allow
|
||||
// for strict conformance to IDNA2008.
|
||||
case valid, deviation: |
||||
case disallowed, mapped, unknown, ignored: |
||||
r, _ := utf8.DecodeRuneInString(s[i:]) |
||||
return s, bidi, runeError(r) |
||||
} |
||||
i += sz |
||||
} |
||||
return s, bidi, nil |
||||
} |
||||
|
||||
func (c info) isBidi(s string) bool { |
||||
if !c.isMapped() { |
||||
return c&attributesMask == rtl |
||||
} |
||||
// TODO: also store bidi info for mapped data. This is possible, but a bit
|
||||
// cumbersome and not for the common case.
|
||||
p, _ := bidi.LookupString(s) |
||||
switch p.Class() { |
||||
case bidi.R, bidi.AL, bidi.AN: |
||||
return true |
||||
} |
||||
return false |
||||
} |
||||
|
||||
func validateAndMap(p *Profile, s string) (vm string, bidi bool, err error) { |
||||
var ( |
||||
b []byte |
||||
k int |
||||
) |
||||
// combinedInfoBits contains the or-ed bits of all runes. We use this
|
||||
// to derive the mayNeedNorm bit later. This may trigger normalization
|
||||
// overeagerly, but it will not do so in the common case. The end result
|
||||
// is another 10% saving on BenchmarkProfile for the common case.
|
||||
var combinedInfoBits info |
||||
for i := 0; i < len(s); { |
||||
v, sz := trie.lookupString(s[i:]) |
||||
if sz == 0 { |
||||
b = append(b, s[k:i]...) |
||||
b = append(b, "\ufffd"...) |
||||
k = len(s) |
||||
if err == nil { |
||||
err = runeError(utf8.RuneError) |
||||
} |
||||
break |
||||
} |
||||
combinedInfoBits |= info(v) |
||||
bidi = bidi || info(v).isBidi(s[i:]) |
||||
start := i |
||||
i += sz |
||||
// Copy bytes not copied so far.
|
||||
switch p.simplify(info(v).category()) { |
||||
case valid: |
||||
continue |
||||
case disallowed: |
||||
if err == nil { |
||||
r, _ := utf8.DecodeRuneInString(s[start:]) |
||||
err = runeError(r) |
||||
} |
||||
continue |
||||
case mapped, deviation: |
||||
b = append(b, s[k:start]...) |
||||
b = info(v).appendMapping(b, s[start:i]) |
||||
case ignored: |
||||
b = append(b, s[k:start]...) |
||||
// drop the rune
|
||||
case unknown: |
||||
b = append(b, s[k:start]...) |
||||
b = append(b, "\ufffd"...) |
||||
} |
||||
k = i |
||||
} |
||||
if k == 0 { |
||||
// No changes so far.
|
||||
if combinedInfoBits&mayNeedNorm != 0 { |
||||
s = norm.NFC.String(s) |
||||
} |
||||
} else { |
||||
b = append(b, s[k:]...) |
||||
if norm.NFC.QuickSpan(b) != len(b) { |
||||
b = norm.NFC.Bytes(b) |
||||
} |
||||
// TODO: the punycode converters require strings as input.
|
||||
s = string(b) |
||||
} |
||||
return s, bidi, err |
||||
} |
||||
|
||||
// A labelIter allows iterating over domain name labels.
|
||||
type labelIter struct { |
||||
orig string |
||||
slice []string |
||||
curStart int |
||||
curEnd int |
||||
i int |
||||
} |
||||
|
||||
func (l *labelIter) reset() { |
||||
l.curStart = 0 |
||||
l.curEnd = 0 |
||||
l.i = 0 |
||||
} |
||||
|
||||
func (l *labelIter) done() bool { |
||||
return l.curStart >= len(l.orig) |
||||
} |
||||
|
||||
func (l *labelIter) result() string { |
||||
if l.slice != nil { |
||||
return strings.Join(l.slice, ".") |
||||
} |
||||
return l.orig |
||||
} |
||||
|
||||
func (l *labelIter) label() string { |
||||
if l.slice != nil { |
||||
return l.slice[l.i] |
||||
} |
||||
p := strings.IndexByte(l.orig[l.curStart:], '.') |
||||
l.curEnd = l.curStart + p |
||||
if p == -1 { |
||||
l.curEnd = len(l.orig) |
||||
} |
||||
return l.orig[l.curStart:l.curEnd] |
||||
} |
||||
|
||||
// next sets the value to the next label. It skips the last label if it is empty.
|
||||
func (l *labelIter) next() { |
||||
l.i++ |
||||
if l.slice != nil { |
||||
if l.i >= len(l.slice) || l.i == len(l.slice)-1 && l.slice[l.i] == "" { |
||||
l.curStart = len(l.orig) |
||||
} |
||||
} else { |
||||
l.curStart = l.curEnd + 1 |
||||
if l.curStart == len(l.orig)-1 && l.orig[l.curStart] == '.' { |
||||
l.curStart = len(l.orig) |
||||
} |
||||
} |
||||
} |
||||
|
||||
func (l *labelIter) set(s string) { |
||||
if l.slice == nil { |
||||
l.slice = strings.Split(l.orig, ".") |
||||
} |
||||
l.slice[l.i] = s |
||||
} |
||||
|
||||
// acePrefix is the ASCII Compatible Encoding prefix.
|
||||
const acePrefix = "xn--" |
||||
|
||||
func (p *Profile) simplify(cat category) category { |
||||
switch cat { |
||||
case disallowedSTD3Mapped: |
||||
if p.useSTD3Rules { |
||||
cat = disallowed |
||||
} else { |
||||
cat = mapped |
||||
} |
||||
case disallowedSTD3Valid: |
||||
if p.useSTD3Rules { |
||||
cat = disallowed |
||||
} else { |
||||
cat = valid |
||||
} |
||||
case deviation: |
||||
if !p.transitional { |
||||
cat = valid |
||||
} |
||||
case validNV8, validXV8: |
||||
// TODO: handle V2008
|
||||
cat = valid |
||||
} |
||||
return cat |
||||
} |
||||
|
||||
func validateFromPunycode(p *Profile, s string) error { |
||||
if !norm.NFC.IsNormalString(s) { |
||||
return &labelError{s, "V1"} |
||||
} |
||||
// TODO: detect whether string may have to be normalized in the following
|
||||
// loop.
|
||||
for i := 0; i < len(s); { |
||||
v, sz := trie.lookupString(s[i:]) |
||||
if sz == 0 { |
||||
return runeError(utf8.RuneError) |
||||
} |
||||
if c := p.simplify(info(v).category()); c != valid && c != deviation { |
||||
return &labelError{s, "V6"} |
||||
} |
||||
i += sz |
||||
} |
||||
return nil |
||||
} |
||||
|
||||
const ( |
||||
zwnj = "\u200c" |
||||
zwj = "\u200d" |
||||
) |
||||
|
||||
type joinState int8 |
||||
|
||||
const ( |
||||
stateStart joinState = iota |
||||
stateVirama |
||||
stateBefore |
||||
stateBeforeVirama |
||||
stateAfter |
||||
stateFAIL |
||||
) |
||||
|
||||
var joinStates = [][numJoinTypes]joinState{ |
||||
stateStart: { |
||||
joiningL: stateBefore, |
||||
joiningD: stateBefore, |
||||
joinZWNJ: stateFAIL, |
||||
joinZWJ: stateFAIL, |
||||
joinVirama: stateVirama, |
||||
}, |
||||
stateVirama: { |
||||
joiningL: stateBefore, |
||||
joiningD: stateBefore, |
||||
}, |
||||
stateBefore: { |
||||
joiningL: stateBefore, |
||||
joiningD: stateBefore, |
||||
joiningT: stateBefore, |
||||
joinZWNJ: stateAfter, |
||||
joinZWJ: stateFAIL, |
||||
joinVirama: stateBeforeVirama, |
||||
}, |
||||
stateBeforeVirama: { |
||||
joiningL: stateBefore, |
||||
joiningD: stateBefore, |
||||
joiningT: stateBefore, |
||||
}, |
||||
stateAfter: { |
||||
joiningL: stateFAIL, |
||||
joiningD: stateBefore, |
||||
joiningT: stateAfter, |
||||
joiningR: stateStart, |
||||
joinZWNJ: stateFAIL, |
||||
joinZWJ: stateFAIL, |
||||
joinVirama: stateAfter, // no-op as we can't accept joiners here
|
||||
}, |
||||
stateFAIL: { |
||||
0: stateFAIL, |
||||
joiningL: stateFAIL, |
||||
joiningD: stateFAIL, |
||||
joiningT: stateFAIL, |
||||
joiningR: stateFAIL, |
||||
joinZWNJ: stateFAIL, |
||||
joinZWJ: stateFAIL, |
||||
joinVirama: stateFAIL, |
||||
}, |
||||
} |
||||
|
||||
// validateLabel validates the criteria from Section 4.1. Item 1, 4, and 6 are
|
||||
// already implicitly satisfied by the overall implementation.
|
||||
func (p *Profile) validateLabel(s string) (err error) { |
||||
if s == "" { |
||||
if p.verifyDNSLength { |
||||
return &labelError{s, "A4"} |
||||
} |
||||
return nil |
||||
} |
||||
if !p.validateLabels { |
||||
return nil |
||||
} |
||||
trie := p.trie // p.validateLabels is only set if trie is set.
|
||||
if len(s) > 4 && s[2] == '-' && s[3] == '-' { |
||||
return &labelError{s, "V2"} |
||||
} |
||||
if s[0] == '-' || s[len(s)-1] == '-' { |
||||
return &labelError{s, "V3"} |
||||
} |
||||
// TODO: merge the use of this in the trie.
|
||||
v, sz := trie.lookupString(s) |
||||
x := info(v) |
||||
if x.isModifier() { |
||||
return &labelError{s, "V5"} |
||||
} |
||||
// Quickly return in the absence of zero-width (non) joiners.
|
||||
if strings.Index(s, zwj) == -1 && strings.Index(s, zwnj) == -1 { |
||||
return nil |
||||
} |
||||
st := stateStart |
||||
for i := 0; ; { |
||||
jt := x.joinType() |
||||
if s[i:i+sz] == zwj { |
||||
jt = joinZWJ |
||||
} else if s[i:i+sz] == zwnj { |
||||
jt = joinZWNJ |
||||
} |
||||
st = joinStates[st][jt] |
||||
if x.isViramaModifier() { |
||||
st = joinStates[st][joinVirama] |
||||
} |
||||
if i += sz; i == len(s) { |
||||
break |
||||
} |
||||
v, sz = trie.lookupString(s[i:]) |
||||
x = info(v) |
||||
} |
||||
if st == stateFAIL || st == stateAfter { |
||||
return &labelError{s, "C"} |
||||
} |
||||
return nil |
||||
} |
||||
|
||||
func ascii(s string) bool { |
||||
for i := 0; i < len(s); i++ { |
||||
if s[i] >= utf8.RuneSelf { |
||||
return false |
||||
} |
||||
} |
||||
return true |
||||
} |
@ -0,0 +1,682 @@ |
||||
// Code generated by running "go generate" in golang.org/x/text. DO NOT EDIT.
|
||||
|
||||
// Copyright 2016 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.
|
||||
|
||||
// +build !go1.10
|
||||
|
||||
// Package idna implements IDNA2008 using the compatibility processing
|
||||
// defined by UTS (Unicode Technical Standard) #46, which defines a standard to
|
||||
// deal with the transition from IDNA2003.
|
||||
//
|
||||
// IDNA2008 (Internationalized Domain Names for Applications), is defined in RFC
|
||||
// 5890, RFC 5891, RFC 5892, RFC 5893 and RFC 5894.
|
||||
// UTS #46 is defined in https://www.unicode.org/reports/tr46.
|
||||
// See https://unicode.org/cldr/utility/idna.jsp for a visualization of the
|
||||
// differences between these two standards.
|
||||
package idna // import "golang.org/x/net/idna"
|
||||
|
||||
import ( |
||||
"fmt" |
||||
"strings" |
||||
"unicode/utf8" |
||||
|
||||
"golang.org/x/text/secure/bidirule" |
||||
"golang.org/x/text/unicode/norm" |
||||
) |
||||
|
||||
// NOTE: Unlike common practice in Go APIs, the functions will return a
|
||||
// sanitized domain name in case of errors. Browsers sometimes use a partially
|
||||
// evaluated string as lookup.
|
||||
// TODO: the current error handling is, in my opinion, the least opinionated.
|
||||
// Other strategies are also viable, though:
|
||||
// Option 1) Return an empty string in case of error, but allow the user to
|
||||
// specify explicitly which errors to ignore.
|
||||
// Option 2) Return the partially evaluated string if it is itself a valid
|
||||
// string, otherwise return the empty string in case of error.
|
||||
// Option 3) Option 1 and 2.
|
||||
// Option 4) Always return an empty string for now and implement Option 1 as
|
||||
// needed, and document that the return string may not be empty in case of
|
||||
// error in the future.
|
||||
// I think Option 1 is best, but it is quite opinionated.
|
||||
|
||||
// ToASCII is a wrapper for Punycode.ToASCII.
|
||||
func ToASCII(s string) (string, error) { |
||||
return Punycode.process(s, true) |
||||
} |
||||
|
||||
// ToUnicode is a wrapper for Punycode.ToUnicode.
|
||||
func ToUnicode(s string) (string, error) { |
||||
return Punycode.process(s, false) |
||||
} |
||||
|
||||
// An Option configures a Profile at creation time.
|
||||
type Option func(*options) |
||||
|
||||
// Transitional sets a Profile to use the Transitional mapping as defined in UTS
|
||||
// #46. This will cause, for example, "ß" to be mapped to "ss". Using the
|
||||
// transitional mapping provides a compromise between IDNA2003 and IDNA2008
|
||||
// compatibility. It is used by most browsers when resolving domain names. This
|
||||
// option is only meaningful if combined with MapForLookup.
|
||||
func Transitional(transitional bool) Option { |
||||
return func(o *options) { o.transitional = true } |
||||
} |
||||
|
||||
// VerifyDNSLength sets whether a Profile should fail if any of the IDN parts
|
||||
// are longer than allowed by the RFC.
|
||||
func VerifyDNSLength(verify bool) Option { |
||||
return func(o *options) { o.verifyDNSLength = verify } |
||||
} |
||||
|
||||
// RemoveLeadingDots removes leading label separators. Leading runes that map to
|
||||
// dots, such as U+3002 IDEOGRAPHIC FULL STOP, are removed as well.
|
||||
//
|
||||
// This is the behavior suggested by the UTS #46 and is adopted by some
|
||||
// browsers.
|
||||
func RemoveLeadingDots(remove bool) Option { |
||||
return func(o *options) { o.removeLeadingDots = remove } |
||||
} |
||||
|
||||
// ValidateLabels sets whether to check the mandatory label validation criteria
|
||||
// as defined in Section 5.4 of RFC 5891. This includes testing for correct use
|
||||
// of hyphens ('-'), normalization, validity of runes, and the context rules.
|
||||
func ValidateLabels(enable bool) Option { |
||||
return func(o *options) { |
||||
// Don't override existing mappings, but set one that at least checks
|
||||
// normalization if it is not set.
|
||||
if o.mapping == nil && enable { |
||||
o.mapping = normalize |
||||
} |
||||
o.trie = trie |
||||
o.validateLabels = enable |
||||
o.fromPuny = validateFromPunycode |
||||
} |
||||
} |
||||
|
||||
// StrictDomainName limits the set of permissable ASCII characters to those
|
||||
// allowed in domain names as defined in RFC 1034 (A-Z, a-z, 0-9 and the
|
||||
// hyphen). This is set by default for MapForLookup and ValidateForRegistration.
|
||||
//
|
||||
// This option is useful, for instance, for browsers that allow characters
|
||||
// outside this range, for example a '_' (U+005F LOW LINE). See
|
||||
// http://www.rfc-editor.org/std/std3.txt for more details This option
|
||||
// corresponds to the UseSTD3ASCIIRules option in UTS #46.
|
||||
func StrictDomainName(use bool) Option { |
||||
return func(o *options) { |
||||
o.trie = trie |
||||
o.useSTD3Rules = use |
||||
o.fromPuny = validateFromPunycode |
||||
} |
||||
} |
||||
|
||||
// NOTE: the following options pull in tables. The tables should not be linked
|
||||
// in as long as the options are not used.
|
||||
|
||||
// BidiRule enables the Bidi rule as defined in RFC 5893. Any application
|
||||
// that relies on proper validation of labels should include this rule.
|
||||
func BidiRule() Option { |
||||
return func(o *options) { o.bidirule = bidirule.ValidString } |
||||
} |
||||
|
||||
// ValidateForRegistration sets validation options to verify that a given IDN is
|
||||
// properly formatted for registration as defined by Section 4 of RFC 5891.
|
||||
func ValidateForRegistration() Option { |
||||
return func(o *options) { |
||||
o.mapping = validateRegistration |
||||
StrictDomainName(true)(o) |
||||
ValidateLabels(true)(o) |
||||
VerifyDNSLength(true)(o) |
||||
BidiRule()(o) |
||||
} |
||||
} |
||||
|
||||
// MapForLookup sets validation and mapping options such that a given IDN is
|
||||
// transformed for domain name lookup according to the requirements set out in
|
||||
// Section 5 of RFC 5891. The mappings follow the recommendations of RFC 5894,
|
||||
// RFC 5895 and UTS 46. It does not add the Bidi Rule. Use the BidiRule option
|
||||
// to add this check.
|
||||
//
|
||||
// The mappings include normalization and mapping case, width and other
|
||||
// compatibility mappings.
|
||||
func MapForLookup() Option { |
||||
return func(o *options) { |
||||
o.mapping = validateAndMap |
||||
StrictDomainName(true)(o) |
||||
ValidateLabels(true)(o) |
||||
RemoveLeadingDots(true)(o) |
||||
} |
||||
} |
||||
|
||||
type options struct { |
||||
transitional bool |
||||
useSTD3Rules bool |
||||
validateLabels bool |
||||
verifyDNSLength bool |
||||
removeLeadingDots bool |
||||
|
||||
trie *idnaTrie |
||||
|
||||
// fromPuny calls validation rules when converting A-labels to U-labels.
|
||||
fromPuny func(p *Profile, s string) error |
||||
|
||||
// mapping implements a validation and mapping step as defined in RFC 5895
|
||||
// or UTS 46, tailored to, for example, domain registration or lookup.
|
||||
mapping func(p *Profile, s string) (string, error) |
||||
|
||||
// bidirule, if specified, checks whether s conforms to the Bidi Rule
|
||||
// defined in RFC 5893.
|
||||
bidirule func(s string) bool |
||||
} |
||||
|
||||
// A Profile defines the configuration of a IDNA mapper.
|
||||
type Profile struct { |
||||
options |
||||
} |
||||
|
||||
func apply(o *options, opts []Option) { |
||||
for _, f := range opts { |
||||
f(o) |
||||
} |
||||
} |
||||
|
||||
// New creates a new Profile.
|
||||
//
|
||||
// With no options, the returned Profile is the most permissive and equals the
|
||||
// Punycode Profile. Options can be passed to further restrict the Profile. The
|
||||
// MapForLookup and ValidateForRegistration options set a collection of options,
|
||||
// for lookup and registration purposes respectively, which can be tailored by
|
||||
// adding more fine-grained options, where later options override earlier
|
||||
// options.
|
||||
func New(o ...Option) *Profile { |
||||
p := &Profile{} |
||||
apply(&p.options, o) |
||||
return p |
||||
} |
||||
|
||||
// ToASCII converts a domain or domain label to its ASCII form. For example,
|
||||
// ToASCII("bücher.example.com") is "xn--bcher-kva.example.com", and
|
||||
// ToASCII("golang") is "golang". If an error is encountered it will return
|
||||
// an error and a (partially) processed result.
|
||||
func (p *Profile) ToASCII(s string) (string, error) { |
||||
return p.process(s, true) |
||||
} |
||||
|
||||
// ToUnicode converts a domain or domain label to its Unicode form. For example,
|
||||
// ToUnicode("xn--bcher-kva.example.com") is "bücher.example.com", and
|
||||
// ToUnicode("golang") is "golang". If an error is encountered it will return
|
||||
// an error and a (partially) processed result.
|
||||
func (p *Profile) ToUnicode(s string) (string, error) { |
||||
pp := *p |
||||
pp.transitional = false |
||||
return pp.process(s, false) |
||||
} |
||||
|
||||
// String reports a string with a description of the profile for debugging
|
||||
// purposes. The string format may change with different versions.
|
||||
func (p *Profile) String() string { |
||||
s := "" |
||||
if p.transitional { |
||||
s = "Transitional" |
||||
} else { |
||||
s = "NonTransitional" |
||||
} |
||||
if p.useSTD3Rules { |
||||
s += ":UseSTD3Rules" |
||||
} |
||||
if p.validateLabels { |
||||
s += ":ValidateLabels" |
||||
} |
||||
if p.verifyDNSLength { |
||||
s += ":VerifyDNSLength" |
||||
} |
||||
return s |
||||
} |
||||
|
||||
var ( |
||||
// Punycode is a Profile that does raw punycode processing with a minimum
|
||||
// of validation.
|
||||
Punycode *Profile = punycode |
||||
|
||||
// Lookup is the recommended profile for looking up domain names, according
|
||||
// to Section 5 of RFC 5891. The exact configuration of this profile may
|
||||
// change over time.
|
||||
Lookup *Profile = lookup |
||||
|
||||
// Display is the recommended profile for displaying domain names.
|
||||
// The configuration of this profile may change over time.
|
||||
Display *Profile = display |
||||
|
||||
// Registration is the recommended profile for checking whether a given
|
||||
// IDN is valid for registration, according to Section 4 of RFC 5891.
|
||||
Registration *Profile = registration |
||||
|
||||
punycode = &Profile{} |
||||
lookup = &Profile{options{ |
||||
transitional: true, |
||||
useSTD3Rules: true, |
||||
validateLabels: true, |
||||
removeLeadingDots: true, |
||||
trie: trie, |
||||
fromPuny: validateFromPunycode, |
||||
mapping: validateAndMap, |
||||
bidirule: bidirule.ValidString, |
||||
}} |
||||
display = &Profile{options{ |
||||
useSTD3Rules: true, |
||||
validateLabels: true, |
||||
removeLeadingDots: true, |
||||
trie: trie, |
||||
fromPuny: validateFromPunycode, |
||||
mapping: validateAndMap, |
||||
bidirule: bidirule.ValidString, |
||||
}} |
||||
registration = &Profile{options{ |
||||
useSTD3Rules: true, |
||||
validateLabels: true, |
||||
verifyDNSLength: true, |
||||
trie: trie, |
||||
fromPuny: validateFromPunycode, |
||||
mapping: validateRegistration, |
||||
bidirule: bidirule.ValidString, |
||||
}} |
||||
|
||||
// TODO: profiles
|
||||
// Register: recommended for approving domain names: don't do any mappings
|
||||
// but rather reject on invalid input. Bundle or block deviation characters.
|
||||
) |
||||
|
||||
type labelError struct{ label, code_ string } |
||||
|
||||
func (e labelError) code() string { return e.code_ } |
||||
func (e labelError) Error() string { |
||||
return fmt.Sprintf("idna: invalid label %q", e.label) |
||||
} |
||||
|
||||
type runeError rune |
||||
|
||||
func (e runeError) code() string { return "P1" } |
||||
func (e runeError) Error() string { |
||||
return fmt.Sprintf("idna: disallowed rune %U", e) |
||||
} |
||||
|
||||
// process implements the algorithm described in section 4 of UTS #46,
|
||||
// see https://www.unicode.org/reports/tr46.
|
||||
func (p *Profile) process(s string, toASCII bool) (string, error) { |
||||
var err error |
||||
if p.mapping != nil { |
||||
s, err = p.mapping(p, s) |
||||
} |
||||
// Remove leading empty labels.
|
||||
if p.removeLeadingDots { |
||||
for ; len(s) > 0 && s[0] == '.'; s = s[1:] { |
||||
} |
||||
} |
||||
// It seems like we should only create this error on ToASCII, but the
|
||||
// UTS 46 conformance tests suggests we should always check this.
|
||||
if err == nil && p.verifyDNSLength && s == "" { |
||||
err = &labelError{s, "A4"} |
||||
} |
||||
labels := labelIter{orig: s} |
||||
for ; !labels.done(); labels.next() { |
||||
label := labels.label() |
||||
if label == "" { |
||||
// Empty labels are not okay. The label iterator skips the last
|
||||
// label if it is empty.
|
||||
if err == nil && p.verifyDNSLength { |
||||
err = &labelError{s, "A4"} |
||||
} |
||||
continue |
||||
} |
||||
if strings.HasPrefix(label, acePrefix) { |
||||
u, err2 := decode(label[len(acePrefix):]) |
||||
if err2 != nil { |
||||
if err == nil { |
||||
err = err2 |
||||
} |
||||
// Spec says keep the old label.
|
||||
continue |
||||
} |
||||
labels.set(u) |
||||
if err == nil && p.validateLabels { |
||||
err = p.fromPuny(p, u) |
||||
} |
||||
if err == nil { |
||||
// This should be called on NonTransitional, according to the
|
||||
// spec, but that currently does not have any effect. Use the
|
||||
// original profile to preserve options.
|
||||
err = p.validateLabel(u) |
||||
} |
||||
} else if err == nil { |
||||
err = p.validateLabel(label) |
||||
} |
||||
} |
||||
if toASCII { |
||||
for labels.reset(); !labels.done(); labels.next() { |
||||
label := labels.label() |
||||
if !ascii(label) { |
||||
a, err2 := encode(acePrefix, label) |
||||
if err == nil { |
||||
err = err2 |
||||
} |
||||
label = a |
||||
labels.set(a) |
||||
} |
||||
n := len(label) |
||||
if p.verifyDNSLength && err == nil && (n == 0 || n > 63) { |
||||
err = &labelError{label, "A4"} |
||||
} |
||||
} |
||||
} |
||||
s = labels.result() |
||||
if toASCII && p.verifyDNSLength && err == nil { |
||||
// Compute the length of the domain name minus the root label and its dot.
|
||||
n := len(s) |
||||
if n > 0 && s[n-1] == '.' { |
||||
n-- |
||||
} |
||||
if len(s) < 1 || n > 253 { |
||||
err = &labelError{s, "A4"} |
||||
} |
||||
} |
||||
return s, err |
||||
} |
||||
|
||||
func normalize(p *Profile, s string) (string, error) { |
||||
return norm.NFC.String(s), nil |
||||
} |
||||
|
||||
func validateRegistration(p *Profile, s string) (string, error) { |
||||
if !norm.NFC.IsNormalString(s) { |
||||
return s, &labelError{s, "V1"} |
||||
} |
||||
for i := 0; i < len(s); { |
||||
v, sz := trie.lookupString(s[i:]) |
||||
// Copy bytes not copied so far.
|
||||
switch p.simplify(info(v).category()) { |
||||
// TODO: handle the NV8 defined in the Unicode idna data set to allow
|
||||
// for strict conformance to IDNA2008.
|
||||
case valid, deviation: |
||||
case disallowed, mapped, unknown, ignored: |
||||
r, _ := utf8.DecodeRuneInString(s[i:]) |
||||
return s, runeError(r) |
||||
} |
||||
i += sz |
||||
} |
||||
return s, nil |
||||
} |
||||
|
||||
func validateAndMap(p *Profile, s string) (string, error) { |
||||
var ( |
||||
err error |
||||
b []byte |
||||
k int |
||||
) |
||||
for i := 0; i < len(s); { |
||||
v, sz := trie.lookupString(s[i:]) |
||||
start := i |
||||
i += sz |
||||
// Copy bytes not copied so far.
|
||||
switch p.simplify(info(v).category()) { |
||||
case valid: |
||||
continue |
||||
case disallowed: |
||||
if err == nil { |
||||
r, _ := utf8.DecodeRuneInString(s[start:]) |
||||
err = runeError(r) |
||||
} |
||||
continue |
||||
case mapped, deviation: |
||||
b = append(b, s[k:start]...) |
||||
b = info(v).appendMapping(b, s[start:i]) |
||||
case ignored: |
||||
b = append(b, s[k:start]...) |
||||
// drop the rune
|
||||
case unknown: |
||||
b = append(b, s[k:start]...) |
||||
b = append(b, "\ufffd"...) |
||||
} |
||||
k = i |
||||
} |
||||
if k == 0 { |
||||
// No changes so far.
|
||||
s = norm.NFC.String(s) |
||||
} else { |
||||
b = append(b, s[k:]...) |
||||
if norm.NFC.QuickSpan(b) != len(b) { |
||||
b = norm.NFC.Bytes(b) |
||||
} |
||||
// TODO: the punycode converters require strings as input.
|
||||
s = string(b) |
||||
} |
||||
return s, err |
||||
} |
||||
|
||||
// A labelIter allows iterating over domain name labels.
|
||||
type labelIter struct { |
||||
orig string |
||||
slice []string |
||||
curStart int |
||||
curEnd int |
||||
i int |
||||
} |
||||
|
||||
func (l *labelIter) reset() { |
||||
l.curStart = 0 |
||||
l.curEnd = 0 |
||||
l.i = 0 |
||||
} |
||||
|
||||
func (l *labelIter) done() bool { |
||||
return l.curStart >= len(l.orig) |
||||
} |
||||
|
||||
func (l *labelIter) result() string { |
||||
if l.slice != nil { |
||||
return strings.Join(l.slice, ".") |
||||
} |
||||
return l.orig |
||||
} |
||||
|
||||
func (l *labelIter) label() string { |
||||
if l.slice != nil { |
||||
return l.slice[l.i] |
||||
} |
||||
p := strings.IndexByte(l.orig[l.curStart:], '.') |
||||
l.curEnd = l.curStart + p |
||||
if p == -1 { |
||||
l.curEnd = len(l.orig) |
||||
} |
||||
return l.orig[l.curStart:l.curEnd] |
||||
} |
||||
|
||||
// next sets the value to the next label. It skips the last label if it is empty.
|
||||
func (l *labelIter) next() { |
||||
l.i++ |
||||
if l.slice != nil { |
||||
if l.i >= len(l.slice) || l.i == len(l.slice)-1 && l.slice[l.i] == "" { |
||||
l.curStart = len(l.orig) |
||||
} |
||||
} else { |
||||
l.curStart = l.curEnd + 1 |
||||
if l.curStart == len(l.orig)-1 && l.orig[l.curStart] == '.' { |
||||
l.curStart = len(l.orig) |
||||
} |
||||
} |
||||
} |
||||
|
||||
func (l *labelIter) set(s string) { |
||||
if l.slice == nil { |
||||
l.slice = strings.Split(l.orig, ".") |
||||
} |
||||
l.slice[l.i] = s |
||||
} |
||||
|
||||
// acePrefix is the ASCII Compatible Encoding prefix.
|
||||
const acePrefix = "xn--" |
||||
|
||||
func (p *Profile) simplify(cat category) category { |
||||
switch cat { |
||||
case disallowedSTD3Mapped: |
||||
if p.useSTD3Rules { |
||||
cat = disallowed |
||||
} else { |
||||
cat = mapped |
||||
} |
||||
case disallowedSTD3Valid: |
||||
if p.useSTD3Rules { |
||||
cat = disallowed |
||||
} else { |
||||
cat = valid |
||||
} |
||||
case deviation: |
||||
if !p.transitional { |
||||
cat = valid |
||||
} |
||||
case validNV8, validXV8: |
||||
// TODO: handle V2008
|
||||
cat = valid |
||||
} |
||||
return cat |
||||
} |
||||
|
||||
func validateFromPunycode(p *Profile, s string) error { |
||||
if !norm.NFC.IsNormalString(s) { |
||||
return &labelError{s, "V1"} |
||||
} |
||||
for i := 0; i < len(s); { |
||||
v, sz := trie.lookupString(s[i:]) |
||||
if c := p.simplify(info(v).category()); c != valid && c != deviation { |
||||
return &labelError{s, "V6"} |
||||
} |
||||
i += sz |
||||
} |
||||
return nil |
||||
} |
||||
|
||||
const ( |
||||
zwnj = "\u200c" |
||||
zwj = "\u200d" |
||||
) |
||||
|
||||
type joinState int8 |
||||
|
||||
const ( |
||||
stateStart joinState = iota |
||||
stateVirama |
||||
stateBefore |
||||
stateBeforeVirama |
||||
stateAfter |
||||
stateFAIL |
||||
) |
||||
|
||||
var joinStates = [][numJoinTypes]joinState{ |
||||
stateStart: { |
||||
joiningL: stateBefore, |
||||
joiningD: stateBefore, |
||||
joinZWNJ: stateFAIL, |
||||
joinZWJ: stateFAIL, |
||||
joinVirama: stateVirama, |
||||
}, |
||||
stateVirama: { |
||||
joiningL: stateBefore, |
||||
joiningD: stateBefore, |
||||
}, |
||||
stateBefore: { |
||||
joiningL: stateBefore, |
||||
joiningD: stateBefore, |
||||
joiningT: stateBefore, |
||||
joinZWNJ: stateAfter, |
||||
joinZWJ: stateFAIL, |
||||
joinVirama: stateBeforeVirama, |
||||
}, |
||||
stateBeforeVirama: { |
||||
joiningL: stateBefore, |
||||
joiningD: stateBefore, |
||||
joiningT: stateBefore, |
||||
}, |
||||
stateAfter: { |
||||
joiningL: stateFAIL, |
||||
joiningD: stateBefore, |
||||
joiningT: stateAfter, |
||||
joiningR: stateStart, |
||||
joinZWNJ: stateFAIL, |
||||
joinZWJ: stateFAIL, |
||||
joinVirama: stateAfter, // no-op as we can't accept joiners here
|
||||
}, |
||||
stateFAIL: { |
||||
0: stateFAIL, |
||||
joiningL: stateFAIL, |
||||
joiningD: stateFAIL, |
||||
joiningT: stateFAIL, |
||||
joiningR: stateFAIL, |
||||
joinZWNJ: stateFAIL, |
||||
joinZWJ: stateFAIL, |
||||
joinVirama: stateFAIL, |
||||
}, |
||||
} |
||||
|
||||
// validateLabel validates the criteria from Section 4.1. Item 1, 4, and 6 are
|
||||
// already implicitly satisfied by the overall implementation.
|
||||
func (p *Profile) validateLabel(s string) error { |
||||
if s == "" { |
||||
if p.verifyDNSLength { |
||||
return &labelError{s, "A4"} |
||||
} |
||||
return nil |
||||
} |
||||
if p.bidirule != nil && !p.bidirule(s) { |
||||
return &labelError{s, "B"} |
||||
} |
||||
if !p.validateLabels { |
||||
return nil |
||||
} |
||||
trie := p.trie // p.validateLabels is only set if trie is set.
|
||||
if len(s) > 4 && s[2] == '-' && s[3] == '-' { |
||||
return &labelError{s, "V2"} |
||||
} |
||||
if s[0] == '-' || s[len(s)-1] == '-' { |
||||
return &labelError{s, "V3"} |
||||
} |
||||
// TODO: merge the use of this in the trie.
|
||||
v, sz := trie.lookupString(s) |
||||
x := info(v) |
||||
if x.isModifier() { |
||||
return &labelError{s, "V5"} |
||||
} |
||||
// Quickly return in the absence of zero-width (non) joiners.
|
||||
if strings.Index(s, zwj) == -1 && strings.Index(s, zwnj) == -1 { |
||||
return nil |
||||
} |
||||
st := stateStart |
||||
for i := 0; ; { |
||||
jt := x.joinType() |
||||
if s[i:i+sz] == zwj { |
||||
jt = joinZWJ |
||||
} else if s[i:i+sz] == zwnj { |
||||
jt = joinZWNJ |
||||
} |
||||
st = joinStates[st][jt] |
||||
if x.isViramaModifier() { |
||||
st = joinStates[st][joinVirama] |
||||
} |
||||
if i += sz; i == len(s) { |
||||
break |
||||
} |
||||
v, sz = trie.lookupString(s[i:]) |
||||
x = info(v) |
||||
} |
||||
if st == stateFAIL || st == stateAfter { |
||||
return &labelError{s, "C"} |
||||
} |
||||
return nil |
||||
} |
||||
|
||||
func ascii(s string) bool { |
||||
for i := 0; i < len(s); i++ { |
||||
if s[i] >= utf8.RuneSelf { |
||||
return false |
||||
} |
||||
} |
||||
return true |
||||
} |
@ -0,0 +1,203 @@ |
||||
// Code generated by running "go generate" in golang.org/x/text. DO NOT EDIT.
|
||||
|
||||
// Copyright 2016 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.
|
||||
|
||||
package idna |
||||
|
||||
// This file implements the Punycode algorithm from RFC 3492.
|
||||
|
||||
import ( |
||||
"math" |
||||
"strings" |
||||
"unicode/utf8" |
||||
) |
||||
|
||||
// These parameter values are specified in section 5.
|
||||
//
|
||||
// All computation is done with int32s, so that overflow behavior is identical
|
||||
// regardless of whether int is 32-bit or 64-bit.
|
||||
const ( |
||||
base int32 = 36 |
||||
damp int32 = 700 |
||||
initialBias int32 = 72 |
||||
initialN int32 = 128 |
||||
skew int32 = 38 |
||||
tmax int32 = 26 |
||||
tmin int32 = 1 |
||||
) |
||||
|
||||
func punyError(s string) error { return &labelError{s, "A3"} } |
||||
|
||||
// decode decodes a string as specified in section 6.2.
|
||||
func decode(encoded string) (string, error) { |
||||
if encoded == "" { |
||||
return "", nil |
||||
} |
||||
pos := 1 + strings.LastIndex(encoded, "-") |
||||
if pos == 1 { |
||||
return "", punyError(encoded) |
||||
} |
||||
if pos == len(encoded) { |
||||
return encoded[:len(encoded)-1], nil |
||||
} |
||||
output := make([]rune, 0, len(encoded)) |
||||
if pos != 0 { |
||||
for _, r := range encoded[:pos-1] { |
||||
output = append(output, r) |
||||
} |
||||
} |
||||
i, n, bias := int32(0), initialN, initialBias |
||||
for pos < len(encoded) { |
||||
oldI, w := i, int32(1) |
||||
for k := base; ; k += base { |
||||
if pos == len(encoded) { |
||||
return "", punyError(encoded) |
||||
} |
||||
digit, ok := decodeDigit(encoded[pos]) |
||||
if !ok { |
||||
return "", punyError(encoded) |
||||
} |
||||
pos++ |
||||
i += digit * w |
||||
if i < 0 { |
||||
return "", punyError(encoded) |
||||
} |
||||
t := k - bias |
||||
if t < tmin { |
||||
t = tmin |
||||
} else if t > tmax { |
||||
t = tmax |
||||
} |
||||
if digit < t { |
||||
break |
||||
} |
||||
w *= base - t |
||||
if w >= math.MaxInt32/base { |
||||
return "", punyError(encoded) |
||||
} |
||||
} |
||||
x := int32(len(output) + 1) |
||||
bias = adapt(i-oldI, x, oldI == 0) |
||||
n += i / x |
||||
i %= x |
||||
if n > utf8.MaxRune || len(output) >= 1024 { |
||||
return "", punyError(encoded) |
||||
} |
||||
output = append(output, 0) |
||||
copy(output[i+1:], output[i:]) |
||||
output[i] = n |
||||
i++ |
||||
} |
||||
return string(output), nil |
||||
} |
||||
|
||||
// encode encodes a string as specified in section 6.3 and prepends prefix to
|
||||
// the result.
|
||||
//
|
||||
// The "while h < length(input)" line in the specification becomes "for
|
||||
// remaining != 0" in the Go code, because len(s) in Go is in bytes, not runes.
|
||||
func encode(prefix, s string) (string, error) { |
||||
output := make([]byte, len(prefix), len(prefix)+1+2*len(s)) |
||||
copy(output, prefix) |
||||
delta, n, bias := int32(0), initialN, initialBias |
||||
b, remaining := int32(0), int32(0) |
||||
for _, r := range s { |
||||
if r < 0x80 { |
||||
b++ |
||||
output = append(output, byte(r)) |
||||
} else { |
||||
remaining++ |
||||
} |
||||
} |
||||
h := b |
||||
if b > 0 { |
||||
output = append(output, '-') |
||||
} |
||||
for remaining != 0 { |
||||
m := int32(0x7fffffff) |
||||
for _, r := range s { |
||||
if m > r && r >= n { |
||||
m = r |
||||
} |
||||
} |
||||
delta += (m - n) * (h + 1) |
||||
if delta < 0 { |
||||
return "", punyError(s) |
||||
} |
||||
n = m |
||||
for _, r := range s { |
||||
if r < n { |
||||
delta++ |
||||
if delta < 0 { |
||||
return "", punyError(s) |
||||
} |
||||
continue |
||||
} |
||||
if r > n { |
||||
continue |
||||
} |
||||
q := delta |
||||
for k := base; ; k += base { |
||||
t := k - bias |
||||
if t < tmin { |
||||
t = tmin |
||||
} else if t > tmax { |
||||
t = tmax |
||||
} |
||||
if q < t { |
||||
break |
||||
} |
||||
output = append(output, encodeDigit(t+(q-t)%(base-t))) |
||||
q = (q - t) / (base - t) |
||||
} |
||||
output = append(output, encodeDigit(q)) |
||||
bias = adapt(delta, h+1, h == b) |
||||
delta = 0 |
||||
h++ |
||||
remaining-- |
||||
} |
||||
delta++ |
||||
n++ |
||||
} |
||||
return string(output), nil |
||||
} |
||||
|
||||
func decodeDigit(x byte) (digit int32, ok bool) { |
||||
switch { |
||||
case '0' <= x && x <= '9': |
||||
return int32(x - ('0' - 26)), true |
||||
case 'A' <= x && x <= 'Z': |
||||
return int32(x - 'A'), true |
||||
case 'a' <= x && x <= 'z': |
||||
return int32(x - 'a'), true |
||||
} |
||||
return 0, false |
||||
} |
||||
|
||||
func encodeDigit(digit int32) byte { |
||||
switch { |
||||
case 0 <= digit && digit < 26: |
||||
return byte(digit + 'a') |
||||
case 26 <= digit && digit < 36: |
||||
return byte(digit + ('0' - 26)) |
||||
} |
||||
panic("idna: internal error in punycode encoding") |
||||
} |
||||
|
||||
// adapt is the bias adaptation function specified in section 6.1.
|
||||
func adapt(delta, numPoints int32, firstTime bool) int32 { |
||||
if firstTime { |
||||
delta /= damp |
||||
} else { |
||||
delta /= 2 |
||||
} |
||||
delta += delta / numPoints |
||||
k := int32(0) |
||||
for delta > ((base-tmin)*tmax)/2 { |
||||
delta /= base - tmin |
||||
k += base |
||||
} |
||||
return k + (base-tmin+1)*delta/(delta+skew) |
||||
} |
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
@ -0,0 +1,72 @@ |
||||
// Code generated by running "go generate" in golang.org/x/text. DO NOT EDIT.
|
||||
|
||||
// Copyright 2016 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.
|
||||
|
||||
package idna |
||||
|
||||
// appendMapping appends the mapping for the respective rune. isMapped must be
|
||||
// true. A mapping is a categorization of a rune as defined in UTS #46.
|
||||
func (c info) appendMapping(b []byte, s string) []byte { |
||||
index := int(c >> indexShift) |
||||
if c&xorBit == 0 { |
||||
s := mappings[index:] |
||||
return append(b, s[1:s[0]+1]...) |
||||
} |
||||
b = append(b, s...) |
||||
if c&inlineXOR == inlineXOR { |
||||
// TODO: support and handle two-byte inline masks
|
||||
b[len(b)-1] ^= byte(index) |
||||
} else { |
||||
for p := len(b) - int(xorData[index]); p < len(b); p++ { |
||||
index++ |
||||
b[p] ^= xorData[index] |
||||
} |
||||
} |
||||
return b |
||||
} |
||||
|
||||
// Sparse block handling code.
|
||||
|
||||
type valueRange struct { |
||||
value uint16 // header: value:stride
|
||||
lo, hi byte // header: lo:n
|
||||
} |
||||
|
||||
type sparseBlocks struct { |
||||
values []valueRange |
||||
offset []uint16 |
||||
} |
||||
|
||||
var idnaSparse = sparseBlocks{ |
||||
values: idnaSparseValues[:], |
||||
offset: idnaSparseOffset[:], |
||||
} |
||||
|
||||
// Don't use newIdnaTrie to avoid unconditional linking in of the table.
|
||||
var trie = &idnaTrie{} |
||||
|
||||
// lookup determines the type of block n and looks up the value for b.
|
||||
// For n < t.cutoff, the block is a simple lookup table. Otherwise, the block
|
||||
// is a list of ranges with an accompanying value. Given a matching range r,
|
||||
// the value for b is by r.value + (b - r.lo) * stride.
|
||||
func (t *sparseBlocks) lookup(n uint32, b byte) uint16 { |
||||
offset := t.offset[n] |
||||
header := t.values[offset] |
||||
lo := offset + 1 |
||||
hi := lo + uint16(header.lo) |
||||
for lo < hi { |
||||
m := lo + (hi-lo)/2 |
||||
r := t.values[m] |
||||
if r.lo <= b && b <= r.hi { |
||||
return r.value + uint16(b-r.lo)*header.value |
||||
} |
||||
if b < r.lo { |
||||
hi = m |
||||
} else { |
||||
lo = m + 1 |
||||
} |
||||
} |
||||
return 0 |
||||
} |
@ -0,0 +1,119 @@ |
||||
// Code generated by running "go generate" in golang.org/x/text. DO NOT EDIT.
|
||||
|
||||
package idna |
||||
|
||||
// This file contains definitions for interpreting the trie value of the idna
|
||||
// trie generated by "go run gen*.go". It is shared by both the generator
|
||||
// program and the resultant package. Sharing is achieved by the generator
|
||||
// copying gen_trieval.go to trieval.go and changing what's above this comment.
|
||||
|
||||
// info holds information from the IDNA mapping table for a single rune. It is
|
||||
// the value returned by a trie lookup. In most cases, all information fits in
|
||||
// a 16-bit value. For mappings, this value may contain an index into a slice
|
||||
// with the mapped string. Such mappings can consist of the actual mapped value
|
||||
// or an XOR pattern to be applied to the bytes of the UTF8 encoding of the
|
||||
// input rune. This technique is used by the cases packages and reduces the
|
||||
// table size significantly.
|
||||
//
|
||||
// The per-rune values have the following format:
|
||||
//
|
||||
// if mapped {
|
||||
// if inlinedXOR {
|
||||
// 15..13 inline XOR marker
|
||||
// 12..11 unused
|
||||
// 10..3 inline XOR mask
|
||||
// } else {
|
||||
// 15..3 index into xor or mapping table
|
||||
// }
|
||||
// } else {
|
||||
// 15..14 unused
|
||||
// 13 mayNeedNorm
|
||||
// 12..11 attributes
|
||||
// 10..8 joining type
|
||||
// 7..3 category type
|
||||
// }
|
||||
// 2 use xor pattern
|
||||
// 1..0 mapped category
|
||||
//
|
||||
// See the definitions below for a more detailed description of the various
|
||||
// bits.
|
||||
type info uint16 |
||||
|
||||
const ( |
||||
catSmallMask = 0x3 |
||||
catBigMask = 0xF8 |
||||
indexShift = 3 |
||||
xorBit = 0x4 // interpret the index as an xor pattern
|
||||
inlineXOR = 0xE000 // These bits are set if the XOR pattern is inlined.
|
||||
|
||||
joinShift = 8 |
||||
joinMask = 0x07 |
||||
|
||||
// Attributes
|
||||
attributesMask = 0x1800 |
||||
viramaModifier = 0x1800 |
||||
modifier = 0x1000 |
||||
rtl = 0x0800 |
||||
|
||||
mayNeedNorm = 0x2000 |
||||
) |
||||
|
||||
// A category corresponds to a category defined in the IDNA mapping table.
|
||||
type category uint16 |
||||
|
||||
const ( |
||||
unknown category = 0 // not currently defined in unicode.
|
||||
mapped category = 1 |
||||
disallowedSTD3Mapped category = 2 |
||||
deviation category = 3 |
||||
) |
||||
|
||||
const ( |
||||
valid category = 0x08 |
||||
validNV8 category = 0x18 |
||||
validXV8 category = 0x28 |
||||
disallowed category = 0x40 |
||||
disallowedSTD3Valid category = 0x80 |
||||
ignored category = 0xC0 |
||||
) |
||||
|
||||
// join types and additional rune information
|
||||
const ( |
||||
joiningL = (iota + 1) |
||||
joiningD |
||||
joiningT |
||||
joiningR |
||||
|
||||
//the following types are derived during processing
|
||||
joinZWJ |
||||
joinZWNJ |
||||
joinVirama |
||||
numJoinTypes |
||||
) |
||||
|
||||
func (c info) isMapped() bool { |
||||
return c&0x3 != 0 |
||||
} |
||||
|
||||
func (c info) category() category { |
||||
small := c & catSmallMask |
||||
if small != 0 { |
||||
return category(small) |
||||
} |
||||
return category(c & catBigMask) |
||||
} |
||||
|
||||
func (c info) joinType() info { |
||||
if c.isMapped() { |
||||
return 0 |
||||
} |
||||
return (c >> joinShift) & joinMask |
||||
} |
||||
|
||||
func (c info) isModifier() bool { |
||||
return c&(modifier|catSmallMask) == modifier |
||||
} |
||||
|
||||
func (c info) isViramaModifier() bool { |
||||
return c&(attributesMask|catSmallMask) == viramaModifier |
||||
} |
@ -0,0 +1,198 @@ |
||||
// Copyright 2015 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.
|
||||
|
||||
// +build windows
|
||||
|
||||
// Package registry provides access to the Windows registry.
|
||||
//
|
||||
// Here is a simple example, opening a registry key and reading a string value from it.
|
||||
//
|
||||
// k, err := registry.OpenKey(registry.LOCAL_MACHINE, `SOFTWARE\Microsoft\Windows NT\CurrentVersion`, registry.QUERY_VALUE)
|
||||
// if err != nil {
|
||||
// log.Fatal(err)
|
||||
// }
|
||||
// defer k.Close()
|
||||
//
|
||||
// s, _, err := k.GetStringValue("SystemRoot")
|
||||
// if err != nil {
|
||||
// log.Fatal(err)
|
||||
// }
|
||||
// fmt.Printf("Windows system root is %q\n", s)
|
||||
//
|
||||
package registry |
||||
|
||||
import ( |
||||
"io" |
||||
"syscall" |
||||
"time" |
||||
) |
||||
|
||||
const ( |
||||
// Registry key security and access rights.
|
||||
// See https://msdn.microsoft.com/en-us/library/windows/desktop/ms724878.aspx
|
||||
// for details.
|
||||
ALL_ACCESS = 0xf003f |
||||
CREATE_LINK = 0x00020 |
||||
CREATE_SUB_KEY = 0x00004 |
||||
ENUMERATE_SUB_KEYS = 0x00008 |
||||
EXECUTE = 0x20019 |
||||
NOTIFY = 0x00010 |
||||
QUERY_VALUE = 0x00001 |
||||
READ = 0x20019 |
||||
SET_VALUE = 0x00002 |
||||
WOW64_32KEY = 0x00200 |
||||
WOW64_64KEY = 0x00100 |
||||
WRITE = 0x20006 |
||||
) |
||||
|
||||
// Key is a handle to an open Windows registry key.
|
||||
// Keys can be obtained by calling OpenKey; there are
|
||||
// also some predefined root keys such as CURRENT_USER.
|
||||
// Keys can be used directly in the Windows API.
|
||||
type Key syscall.Handle |
||||
|
||||
const ( |
||||
// Windows defines some predefined root keys that are always open.
|
||||
// An application can use these keys as entry points to the registry.
|
||||
// Normally these keys are used in OpenKey to open new keys,
|
||||
// but they can also be used anywhere a Key is required.
|
||||
CLASSES_ROOT = Key(syscall.HKEY_CLASSES_ROOT) |
||||
CURRENT_USER = Key(syscall.HKEY_CURRENT_USER) |
||||
LOCAL_MACHINE = Key(syscall.HKEY_LOCAL_MACHINE) |
||||
USERS = Key(syscall.HKEY_USERS) |
||||
CURRENT_CONFIG = Key(syscall.HKEY_CURRENT_CONFIG) |
||||
PERFORMANCE_DATA = Key(syscall.HKEY_PERFORMANCE_DATA) |
||||
) |
||||
|
||||
// Close closes open key k.
|
||||
func (k Key) Close() error { |
||||
return syscall.RegCloseKey(syscall.Handle(k)) |
||||
} |
||||
|
||||
// OpenKey opens a new key with path name relative to key k.
|
||||
// It accepts any open key, including CURRENT_USER and others,
|
||||
// and returns the new key and an error.
|
||||
// The access parameter specifies desired access rights to the
|
||||
// key to be opened.
|
||||
func OpenKey(k Key, path string, access uint32) (Key, error) { |
||||
p, err := syscall.UTF16PtrFromString(path) |
||||
if err != nil { |
||||
return 0, err |
||||
} |
||||
var subkey syscall.Handle |
||||
err = syscall.RegOpenKeyEx(syscall.Handle(k), p, 0, access, &subkey) |
||||
if err != nil { |
||||
return 0, err |
||||
} |
||||
return Key(subkey), nil |
||||
} |
||||
|
||||
// OpenRemoteKey opens a predefined registry key on another
|
||||
// computer pcname. The key to be opened is specified by k, but
|
||||
// can only be one of LOCAL_MACHINE, PERFORMANCE_DATA or USERS.
|
||||
// If pcname is "", OpenRemoteKey returns local computer key.
|
||||
func OpenRemoteKey(pcname string, k Key) (Key, error) { |
||||
var err error |
||||
var p *uint16 |
||||
if pcname != "" { |
||||
p, err = syscall.UTF16PtrFromString(`\\` + pcname) |
||||
if err != nil { |
||||
return 0, err |
||||
} |
||||
} |
||||
var remoteKey syscall.Handle |
||||
err = regConnectRegistry(p, syscall.Handle(k), &remoteKey) |
||||
if err != nil { |
||||
return 0, err |
||||
} |
||||
return Key(remoteKey), nil |
||||
} |
||||
|
||||
// ReadSubKeyNames returns the names of subkeys of key k.
|
||||
// The parameter n controls the number of returned names,
|
||||
// analogous to the way os.File.Readdirnames works.
|
||||
func (k Key) ReadSubKeyNames(n int) ([]string, error) { |
||||
names := make([]string, 0) |
||||
// Registry key size limit is 255 bytes and described there:
|
||||
// https://msdn.microsoft.com/library/windows/desktop/ms724872.aspx
|
||||
buf := make([]uint16, 256) //plus extra room for terminating zero byte
|
||||
loopItems: |
||||
for i := uint32(0); ; i++ { |
||||
if n > 0 { |
||||
if len(names) == n { |
||||
return names, nil |
||||
} |
||||
} |
||||
l := uint32(len(buf)) |
||||
for { |
||||
err := syscall.RegEnumKeyEx(syscall.Handle(k), i, &buf[0], &l, nil, nil, nil, nil) |
||||
if err == nil { |
||||
break |
||||
} |
||||
if err == syscall.ERROR_MORE_DATA { |
||||
// Double buffer size and try again.
|
||||
l = uint32(2 * len(buf)) |
||||
buf = make([]uint16, l) |
||||
continue |
||||
} |
||||
if err == _ERROR_NO_MORE_ITEMS { |
||||
break loopItems |
||||
} |
||||
return names, err |
||||
} |
||||
names = append(names, syscall.UTF16ToString(buf[:l])) |
||||
} |
||||
if n > len(names) { |
||||
return names, io.EOF |
||||
} |
||||
return names, nil |
||||
} |
||||
|
||||
// CreateKey creates a key named path under open key k.
|
||||
// CreateKey returns the new key and a boolean flag that reports
|
||||
// whether the key already existed.
|
||||
// The access parameter specifies the access rights for the key
|
||||
// to be created.
|
||||
func CreateKey(k Key, path string, access uint32) (newk Key, openedExisting bool, err error) { |
||||
var h syscall.Handle |
||||
var d uint32 |
||||
err = regCreateKeyEx(syscall.Handle(k), syscall.StringToUTF16Ptr(path), |
||||
0, nil, _REG_OPTION_NON_VOLATILE, access, nil, &h, &d) |
||||
if err != nil { |
||||
return 0, false, err |
||||
} |
||||
return Key(h), d == _REG_OPENED_EXISTING_KEY, nil |
||||
} |
||||
|
||||
// DeleteKey deletes the subkey path of key k and its values.
|
||||
func DeleteKey(k Key, path string) error { |
||||
return regDeleteKey(syscall.Handle(k), syscall.StringToUTF16Ptr(path)) |
||||
} |
||||
|
||||
// A KeyInfo describes the statistics of a key. It is returned by Stat.
|
||||
type KeyInfo struct { |
||||
SubKeyCount uint32 |
||||
MaxSubKeyLen uint32 // size of the key's subkey with the longest name, in Unicode characters, not including the terminating zero byte
|
||||
ValueCount uint32 |
||||
MaxValueNameLen uint32 // size of the key's longest value name, in Unicode characters, not including the terminating zero byte
|
||||
MaxValueLen uint32 // longest data component among the key's values, in bytes
|
||||
lastWriteTime syscall.Filetime |
||||
} |
||||
|
||||
// ModTime returns the key's last write time.
|
||||
func (ki *KeyInfo) ModTime() time.Time { |
||||
return time.Unix(0, ki.lastWriteTime.Nanoseconds()) |
||||
} |
||||
|
||||
// Stat retrieves information about the open key k.
|
||||
func (k Key) Stat() (*KeyInfo, error) { |
||||
var ki KeyInfo |
||||
err := syscall.RegQueryInfoKey(syscall.Handle(k), nil, nil, nil, |
||||
&ki.SubKeyCount, &ki.MaxSubKeyLen, nil, &ki.ValueCount, |
||||
&ki.MaxValueNameLen, &ki.MaxValueLen, nil, &ki.lastWriteTime) |
||||
if err != nil { |
||||
return nil, err |
||||
} |
||||
return &ki, nil |
||||
} |
@ -0,0 +1,9 @@ |
||||
// Copyright 2015 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.
|
||||
|
||||
// +build generate
|
||||
|
||||
package registry |
||||
|
||||
//go:generate go run $GOROOT/src/syscall/mksyscall_windows.go -output zsyscall_windows.go syscall.go
|
@ -0,0 +1,32 @@ |
||||
// Copyright 2015 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.
|
||||
|
||||
// +build windows
|
||||
|
||||
package registry |
||||
|
||||
import "syscall" |
||||
|
||||
const ( |
||||
_REG_OPTION_NON_VOLATILE = 0 |
||||
|
||||
_REG_CREATED_NEW_KEY = 1 |
||||
_REG_OPENED_EXISTING_KEY = 2 |
||||
|
||||
_ERROR_NO_MORE_ITEMS syscall.Errno = 259 |
||||
) |
||||
|
||||
func LoadRegLoadMUIString() error { |
||||
return procRegLoadMUIStringW.Find() |
||||
} |
||||
|
||||
//sys regCreateKeyEx(key syscall.Handle, subkey *uint16, reserved uint32, class *uint16, options uint32, desired uint32, sa *syscall.SecurityAttributes, result *syscall.Handle, disposition *uint32) (regerrno error) = advapi32.RegCreateKeyExW
|
||||
//sys regDeleteKey(key syscall.Handle, subkey *uint16) (regerrno error) = advapi32.RegDeleteKeyW
|
||||
//sys regSetValueEx(key syscall.Handle, valueName *uint16, reserved uint32, vtype uint32, buf *byte, bufsize uint32) (regerrno error) = advapi32.RegSetValueExW
|
||||
//sys regEnumValue(key syscall.Handle, index uint32, name *uint16, nameLen *uint32, reserved *uint32, valtype *uint32, buf *byte, buflen *uint32) (regerrno error) = advapi32.RegEnumValueW
|
||||
//sys regDeleteValue(key syscall.Handle, name *uint16) (regerrno error) = advapi32.RegDeleteValueW
|
||||
//sys regLoadMUIString(key syscall.Handle, name *uint16, buf *uint16, buflen uint32, buflenCopied *uint32, flags uint32, dir *uint16) (regerrno error) = advapi32.RegLoadMUIStringW
|
||||
//sys regConnectRegistry(machinename *uint16, key syscall.Handle, result *syscall.Handle) (regerrno error) = advapi32.RegConnectRegistryW
|
||||
|
||||
//sys expandEnvironmentStrings(src *uint16, dst *uint16, size uint32) (n uint32, err error) = kernel32.ExpandEnvironmentStringsW
|
@ -0,0 +1,387 @@ |
||||
// Copyright 2015 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.
|
||||
|
||||
// +build windows
|
||||
|
||||
package registry |
||||
|
||||
import ( |
||||
"errors" |
||||
"io" |
||||
"syscall" |
||||
"unicode/utf16" |
||||
"unsafe" |
||||
) |
||||
|
||||
const ( |
||||
// Registry value types.
|
||||
NONE = 0 |
||||
SZ = 1 |
||||
EXPAND_SZ = 2 |
||||
BINARY = 3 |
||||
DWORD = 4 |
||||
DWORD_BIG_ENDIAN = 5 |
||||
LINK = 6 |
||||
MULTI_SZ = 7 |
||||
RESOURCE_LIST = 8 |
||||
FULL_RESOURCE_DESCRIPTOR = 9 |
||||
RESOURCE_REQUIREMENTS_LIST = 10 |
||||
QWORD = 11 |
||||
) |
||||
|
||||
var ( |
||||
// ErrShortBuffer is returned when the buffer was too short for the operation.
|
||||
ErrShortBuffer = syscall.ERROR_MORE_DATA |
||||
|
||||
// ErrNotExist is returned when a registry key or value does not exist.
|
||||
ErrNotExist = syscall.ERROR_FILE_NOT_FOUND |
||||
|
||||
// ErrUnexpectedType is returned by Get*Value when the value's type was unexpected.
|
||||
ErrUnexpectedType = errors.New("unexpected key value type") |
||||
) |
||||
|
||||
// GetValue retrieves the type and data for the specified value associated
|
||||
// with an open key k. It fills up buffer buf and returns the retrieved
|
||||
// byte count n. If buf is too small to fit the stored value it returns
|
||||
// ErrShortBuffer error along with the required buffer size n.
|
||||
// If no buffer is provided, it returns true and actual buffer size n.
|
||||
// If no buffer is provided, GetValue returns the value's type only.
|
||||
// If the value does not exist, the error returned is ErrNotExist.
|
||||
//
|
||||
// GetValue is a low level function. If value's type is known, use the appropriate
|
||||
// Get*Value function instead.
|
||||
func (k Key) GetValue(name string, buf []byte) (n int, valtype uint32, err error) { |
||||
pname, err := syscall.UTF16PtrFromString(name) |
||||
if err != nil { |
||||
return 0, 0, err |
||||
} |
||||
var pbuf *byte |
||||
if len(buf) > 0 { |
||||
pbuf = (*byte)(unsafe.Pointer(&buf[0])) |
||||
} |
||||
l := uint32(len(buf)) |
||||
err = syscall.RegQueryValueEx(syscall.Handle(k), pname, nil, &valtype, pbuf, &l) |
||||
if err != nil { |
||||
return int(l), valtype, err |
||||
} |
||||
return int(l), valtype, nil |
||||
} |
||||
|
||||
func (k Key) getValue(name string, buf []byte) (data []byte, valtype uint32, err error) { |
||||
p, err := syscall.UTF16PtrFromString(name) |
||||
if err != nil { |
||||
return nil, 0, err |
||||
} |
||||
var t uint32 |
||||
n := uint32(len(buf)) |
||||
for { |
||||
err = syscall.RegQueryValueEx(syscall.Handle(k), p, nil, &t, (*byte)(unsafe.Pointer(&buf[0])), &n) |
||||
if err == nil { |
||||
return buf[:n], t, nil |
||||
} |
||||
if err != syscall.ERROR_MORE_DATA { |
||||
return nil, 0, err |
||||
} |
||||
if n <= uint32(len(buf)) { |
||||
return nil, 0, err |
||||
} |
||||
buf = make([]byte, n) |
||||
} |
||||
} |
||||
|
||||
// GetStringValue retrieves the string value for the specified
|
||||
// value name associated with an open key k. It also returns the value's type.
|
||||
// If value does not exist, GetStringValue returns ErrNotExist.
|
||||
// If value is not SZ or EXPAND_SZ, it will return the correct value
|
||||
// type and ErrUnexpectedType.
|
||||
func (k Key) GetStringValue(name string) (val string, valtype uint32, err error) { |
||||
data, typ, err2 := k.getValue(name, make([]byte, 64)) |
||||
if err2 != nil { |
||||
return "", typ, err2 |
||||
} |
||||
switch typ { |
||||
case SZ, EXPAND_SZ: |
||||
default: |
||||
return "", typ, ErrUnexpectedType |
||||
} |
||||
if len(data) == 0 { |
||||
return "", typ, nil |
||||
} |
||||
u := (*[1 << 29]uint16)(unsafe.Pointer(&data[0]))[:] |
||||
return syscall.UTF16ToString(u), typ, nil |
||||
} |
||||
|
||||
// GetMUIStringValue retrieves the localized string value for
|
||||
// the specified value name associated with an open key k.
|
||||
// If the value name doesn't exist or the localized string value
|
||||
// can't be resolved, GetMUIStringValue returns ErrNotExist.
|
||||
// GetMUIStringValue panics if the system doesn't support
|
||||
// regLoadMUIString; use LoadRegLoadMUIString to check if
|
||||
// regLoadMUIString is supported before calling this function.
|
||||
func (k Key) GetMUIStringValue(name string) (string, error) { |
||||
pname, err := syscall.UTF16PtrFromString(name) |
||||
if err != nil { |
||||
return "", err |
||||
} |
||||
|
||||
buf := make([]uint16, 1024) |
||||
var buflen uint32 |
||||
var pdir *uint16 |
||||
|
||||
err = regLoadMUIString(syscall.Handle(k), pname, &buf[0], uint32(len(buf)), &buflen, 0, pdir) |
||||
if err == syscall.ERROR_FILE_NOT_FOUND { // Try fallback path
|
||||
|
||||
// Try to resolve the string value using the system directory as
|
||||
// a DLL search path; this assumes the string value is of the form
|
||||
// @[path]\dllname,-strID but with no path given, e.g. @tzres.dll,-320.
|
||||
|
||||
// This approach works with tzres.dll but may have to be revised
|
||||
// in the future to allow callers to provide custom search paths.
|
||||
|
||||
var s string |
||||
s, err = ExpandString("%SystemRoot%\\system32\\") |
||||
if err != nil { |
||||
return "", err |
||||
} |
||||
pdir, err = syscall.UTF16PtrFromString(s) |
||||
if err != nil { |
||||
return "", err |
||||
} |
||||
|
||||
err = regLoadMUIString(syscall.Handle(k), pname, &buf[0], uint32(len(buf)), &buflen, 0, pdir) |
||||
} |
||||
|
||||
for err == syscall.ERROR_MORE_DATA { // Grow buffer if needed
|
||||
if buflen <= uint32(len(buf)) { |
||||
break // Buffer not growing, assume race; break
|
||||
} |
||||
buf = make([]uint16, buflen) |
||||
err = regLoadMUIString(syscall.Handle(k), pname, &buf[0], uint32(len(buf)), &buflen, 0, pdir) |
||||
} |
||||
|
||||
if err != nil { |
||||
return "", err |
||||
} |
||||
|
||||
return syscall.UTF16ToString(buf), nil |
||||
} |
||||
|
||||
// ExpandString expands environment-variable strings and replaces
|
||||
// them with the values defined for the current user.
|
||||
// Use ExpandString to expand EXPAND_SZ strings.
|
||||
func ExpandString(value string) (string, error) { |
||||
if value == "" { |
||||
return "", nil |
||||
} |
||||
p, err := syscall.UTF16PtrFromString(value) |
||||
if err != nil { |
||||
return "", err |
||||
} |
||||
r := make([]uint16, 100) |
||||
for { |
||||
n, err := expandEnvironmentStrings(p, &r[0], uint32(len(r))) |
||||
if err != nil { |
||||
return "", err |
||||
} |
||||
if n <= uint32(len(r)) { |
||||
u := (*[1 << 29]uint16)(unsafe.Pointer(&r[0]))[:] |
||||
return syscall.UTF16ToString(u), nil |
||||
} |
||||
r = make([]uint16, n) |
||||
} |
||||
} |
||||
|
||||
// GetStringsValue retrieves the []string value for the specified
|
||||
// value name associated with an open key k. It also returns the value's type.
|
||||
// If value does not exist, GetStringsValue returns ErrNotExist.
|
||||
// If value is not MULTI_SZ, it will return the correct value
|
||||
// type and ErrUnexpectedType.
|
||||
func (k Key) GetStringsValue(name string) (val []string, valtype uint32, err error) { |
||||
data, typ, err2 := k.getValue(name, make([]byte, 64)) |
||||
if err2 != nil { |
||||
return nil, typ, err2 |
||||
} |
||||
if typ != MULTI_SZ { |
||||
return nil, typ, ErrUnexpectedType |
||||
} |
||||
if len(data) == 0 { |
||||
return nil, typ, nil |
||||
} |
||||
p := (*[1 << 29]uint16)(unsafe.Pointer(&data[0]))[:len(data)/2] |
||||
if len(p) == 0 { |
||||
return nil, typ, nil |
||||
} |
||||
if p[len(p)-1] == 0 { |
||||
p = p[:len(p)-1] // remove terminating null
|
||||
} |
||||
val = make([]string, 0, 5) |
||||
from := 0 |
||||
for i, c := range p { |
||||
if c == 0 { |
||||
val = append(val, string(utf16.Decode(p[from:i]))) |
||||
from = i + 1 |
||||
} |
||||
} |
||||
return val, typ, nil |
||||
} |
||||
|
||||
// GetIntegerValue retrieves the integer value for the specified
|
||||
// value name associated with an open key k. It also returns the value's type.
|
||||
// If value does not exist, GetIntegerValue returns ErrNotExist.
|
||||
// If value is not DWORD or QWORD, it will return the correct value
|
||||
// type and ErrUnexpectedType.
|
||||
func (k Key) GetIntegerValue(name string) (val uint64, valtype uint32, err error) { |
||||
data, typ, err2 := k.getValue(name, make([]byte, 8)) |
||||
if err2 != nil { |
||||
return 0, typ, err2 |
||||
} |
||||
switch typ { |
||||
case DWORD: |
||||
if len(data) != 4 { |
||||
return 0, typ, errors.New("DWORD value is not 4 bytes long") |
||||
} |
||||
var val32 uint32 |
||||
copy((*[4]byte)(unsafe.Pointer(&val32))[:], data) |
||||
return uint64(val32), DWORD, nil |
||||
case QWORD: |
||||
if len(data) != 8 { |
||||
return 0, typ, errors.New("QWORD value is not 8 bytes long") |
||||
} |
||||
copy((*[8]byte)(unsafe.Pointer(&val))[:], data) |
||||
return val, QWORD, nil |
||||
default: |
||||
return 0, typ, ErrUnexpectedType |
||||
} |
||||
} |
||||
|
||||
// GetBinaryValue retrieves the binary value for the specified
|
||||
// value name associated with an open key k. It also returns the value's type.
|
||||
// If value does not exist, GetBinaryValue returns ErrNotExist.
|
||||
// If value is not BINARY, it will return the correct value
|
||||
// type and ErrUnexpectedType.
|
||||
func (k Key) GetBinaryValue(name string) (val []byte, valtype uint32, err error) { |
||||
data, typ, err2 := k.getValue(name, make([]byte, 64)) |
||||
if err2 != nil { |
||||
return nil, typ, err2 |
||||
} |
||||
if typ != BINARY { |
||||
return nil, typ, ErrUnexpectedType |
||||
} |
||||
return data, typ, nil |
||||
} |
||||
|
||||
func (k Key) setValue(name string, valtype uint32, data []byte) error { |
||||
p, err := syscall.UTF16PtrFromString(name) |
||||
if err != nil { |
||||
return err |
||||
} |
||||
if len(data) == 0 { |
||||
return regSetValueEx(syscall.Handle(k), p, 0, valtype, nil, 0) |
||||
} |
||||
return regSetValueEx(syscall.Handle(k), p, 0, valtype, &data[0], uint32(len(data))) |
||||
} |
||||
|
||||
// SetDWordValue sets the data and type of a name value
|
||||
// under key k to value and DWORD.
|
||||
func (k Key) SetDWordValue(name string, value uint32) error { |
||||
return k.setValue(name, DWORD, (*[4]byte)(unsafe.Pointer(&value))[:]) |
||||
} |
||||
|
||||
// SetQWordValue sets the data and type of a name value
|
||||
// under key k to value and QWORD.
|
||||
func (k Key) SetQWordValue(name string, value uint64) error { |
||||
return k.setValue(name, QWORD, (*[8]byte)(unsafe.Pointer(&value))[:]) |
||||
} |
||||
|
||||
func (k Key) setStringValue(name string, valtype uint32, value string) error { |
||||
v, err := syscall.UTF16FromString(value) |
||||
if err != nil { |
||||
return err |
||||
} |
||||
buf := (*[1 << 29]byte)(unsafe.Pointer(&v[0]))[:len(v)*2] |
||||
return k.setValue(name, valtype, buf) |
||||
} |
||||
|
||||
// SetStringValue sets the data and type of a name value
|
||||
// under key k to value and SZ. The value must not contain a zero byte.
|
||||
func (k Key) SetStringValue(name, value string) error { |
||||
return k.setStringValue(name, SZ, value) |
||||
} |
||||
|
||||
// SetExpandStringValue sets the data and type of a name value
|
||||
// under key k to value and EXPAND_SZ. The value must not contain a zero byte.
|
||||
func (k Key) SetExpandStringValue(name, value string) error { |
||||
return k.setStringValue(name, EXPAND_SZ, value) |
||||
} |
||||
|
||||
// SetStringsValue sets the data and type of a name value
|
||||
// under key k to value and MULTI_SZ. The value strings
|
||||
// must not contain a zero byte.
|
||||
func (k Key) SetStringsValue(name string, value []string) error { |
||||
ss := "" |
||||
for _, s := range value { |
||||
for i := 0; i < len(s); i++ { |
||||
if s[i] == 0 { |
||||
return errors.New("string cannot have 0 inside") |
||||
} |
||||
} |
||||
ss += s + "\x00" |
||||
} |
||||
v := utf16.Encode([]rune(ss + "\x00")) |
||||
buf := (*[1 << 29]byte)(unsafe.Pointer(&v[0]))[:len(v)*2] |
||||
return k.setValue(name, MULTI_SZ, buf) |
||||
} |
||||
|
||||
// SetBinaryValue sets the data and type of a name value
|
||||
// under key k to value and BINARY.
|
||||
func (k Key) SetBinaryValue(name string, value []byte) error { |
||||
return k.setValue(name, BINARY, value) |
||||
} |
||||
|
||||
// DeleteValue removes a named value from the key k.
|
||||
func (k Key) DeleteValue(name string) error { |
||||
return regDeleteValue(syscall.Handle(k), syscall.StringToUTF16Ptr(name)) |
||||
} |
||||
|
||||
// ReadValueNames returns the value names of key k.
|
||||
// The parameter n controls the number of returned names,
|
||||
// analogous to the way os.File.Readdirnames works.
|
||||
func (k Key) ReadValueNames(n int) ([]string, error) { |
||||
ki, err := k.Stat() |
||||
if err != nil { |
||||
return nil, err |
||||
} |
||||
names := make([]string, 0, ki.ValueCount) |
||||
buf := make([]uint16, ki.MaxValueNameLen+1) // extra room for terminating null character
|
||||
loopItems: |
||||
for i := uint32(0); ; i++ { |
||||
if n > 0 { |
||||
if len(names) == n { |
||||
return names, nil |
||||
} |
||||
} |
||||
l := uint32(len(buf)) |
||||
for { |
||||
err := regEnumValue(syscall.Handle(k), i, &buf[0], &l, nil, nil, nil, nil) |
||||
if err == nil { |
||||
break |
||||
} |
||||
if err == syscall.ERROR_MORE_DATA { |
||||
// Double buffer size and try again.
|
||||
l = uint32(2 * len(buf)) |
||||
buf = make([]uint16, l) |
||||
continue |
||||
} |
||||
if err == _ERROR_NO_MORE_ITEMS { |
||||
break loopItems |
||||
} |
||||
return names, err |
||||
} |
||||
names = append(names, syscall.UTF16ToString(buf[:l])) |
||||
} |
||||
if n > len(names) { |
||||
return names, io.EOF |
||||
} |
||||
return names, nil |
||||
} |
@ -0,0 +1,120 @@ |
||||
// Code generated by 'go generate'; DO NOT EDIT.
|
||||
|
||||
package registry |
||||
|
||||
import ( |
||||
"syscall" |
||||
"unsafe" |
||||
|
||||
"golang.org/x/sys/windows" |
||||
) |
||||
|
||||
var _ unsafe.Pointer |
||||
|
||||
// Do the interface allocations only once for common
|
||||
// Errno values.
|
||||
const ( |
||||
errnoERROR_IO_PENDING = 997 |
||||
) |
||||
|
||||
var ( |
||||
errERROR_IO_PENDING error = syscall.Errno(errnoERROR_IO_PENDING) |
||||
) |
||||
|
||||
// errnoErr returns common boxed Errno values, to prevent
|
||||
// allocations at runtime.
|
||||
func errnoErr(e syscall.Errno) error { |
||||
switch e { |
||||
case 0: |
||||
return nil |
||||
case errnoERROR_IO_PENDING: |
||||
return errERROR_IO_PENDING |
||||
} |
||||
// TODO: add more here, after collecting data on the common
|
||||
// error values see on Windows. (perhaps when running
|
||||
// all.bat?)
|
||||
return e |
||||
} |
||||
|
||||
var ( |
||||
modadvapi32 = windows.NewLazySystemDLL("advapi32.dll") |
||||
modkernel32 = windows.NewLazySystemDLL("kernel32.dll") |
||||
|
||||
procRegCreateKeyExW = modadvapi32.NewProc("RegCreateKeyExW") |
||||
procRegDeleteKeyW = modadvapi32.NewProc("RegDeleteKeyW") |
||||
procRegSetValueExW = modadvapi32.NewProc("RegSetValueExW") |
||||
procRegEnumValueW = modadvapi32.NewProc("RegEnumValueW") |
||||
procRegDeleteValueW = modadvapi32.NewProc("RegDeleteValueW") |
||||
procRegLoadMUIStringW = modadvapi32.NewProc("RegLoadMUIStringW") |
||||
procRegConnectRegistryW = modadvapi32.NewProc("RegConnectRegistryW") |
||||
procExpandEnvironmentStringsW = modkernel32.NewProc("ExpandEnvironmentStringsW") |
||||
) |
||||
|
||||
func regCreateKeyEx(key syscall.Handle, subkey *uint16, reserved uint32, class *uint16, options uint32, desired uint32, sa *syscall.SecurityAttributes, result *syscall.Handle, disposition *uint32) (regerrno error) { |
||||
r0, _, _ := syscall.Syscall9(procRegCreateKeyExW.Addr(), 9, uintptr(key), uintptr(unsafe.Pointer(subkey)), uintptr(reserved), uintptr(unsafe.Pointer(class)), uintptr(options), uintptr(desired), uintptr(unsafe.Pointer(sa)), uintptr(unsafe.Pointer(result)), uintptr(unsafe.Pointer(disposition))) |
||||
if r0 != 0 { |
||||
regerrno = syscall.Errno(r0) |
||||
} |
||||
return |
||||
} |
||||
|
||||
func regDeleteKey(key syscall.Handle, subkey *uint16) (regerrno error) { |
||||
r0, _, _ := syscall.Syscall(procRegDeleteKeyW.Addr(), 2, uintptr(key), uintptr(unsafe.Pointer(subkey)), 0) |
||||
if r0 != 0 { |
||||
regerrno = syscall.Errno(r0) |
||||
} |
||||
return |
||||
} |
||||
|
||||
func regSetValueEx(key syscall.Handle, valueName *uint16, reserved uint32, vtype uint32, buf *byte, bufsize uint32) (regerrno error) { |
||||
r0, _, _ := syscall.Syscall6(procRegSetValueExW.Addr(), 6, uintptr(key), uintptr(unsafe.Pointer(valueName)), uintptr(reserved), uintptr(vtype), uintptr(unsafe.Pointer(buf)), uintptr(bufsize)) |
||||
if r0 != 0 { |
||||
regerrno = syscall.Errno(r0) |
||||
} |
||||
return |
||||
} |
||||
|
||||
func regEnumValue(key syscall.Handle, index uint32, name *uint16, nameLen *uint32, reserved *uint32, valtype *uint32, buf *byte, buflen *uint32) (regerrno error) { |
||||
r0, _, _ := syscall.Syscall9(procRegEnumValueW.Addr(), 8, uintptr(key), uintptr(index), uintptr(unsafe.Pointer(name)), uintptr(unsafe.Pointer(nameLen)), uintptr(unsafe.Pointer(reserved)), uintptr(unsafe.Pointer(valtype)), uintptr(unsafe.Pointer(buf)), uintptr(unsafe.Pointer(buflen)), 0) |
||||
if r0 != 0 { |
||||
regerrno = syscall.Errno(r0) |
||||
} |
||||
return |
||||
} |
||||
|
||||
func regDeleteValue(key syscall.Handle, name *uint16) (regerrno error) { |
||||
r0, _, _ := syscall.Syscall(procRegDeleteValueW.Addr(), 2, uintptr(key), uintptr(unsafe.Pointer(name)), 0) |
||||
if r0 != 0 { |
||||
regerrno = syscall.Errno(r0) |
||||
} |
||||
return |
||||
} |
||||
|
||||
func regLoadMUIString(key syscall.Handle, name *uint16, buf *uint16, buflen uint32, buflenCopied *uint32, flags uint32, dir *uint16) (regerrno error) { |
||||
r0, _, _ := syscall.Syscall9(procRegLoadMUIStringW.Addr(), 7, uintptr(key), uintptr(unsafe.Pointer(name)), uintptr(unsafe.Pointer(buf)), uintptr(buflen), uintptr(unsafe.Pointer(buflenCopied)), uintptr(flags), uintptr(unsafe.Pointer(dir)), 0, 0) |
||||
if r0 != 0 { |
||||
regerrno = syscall.Errno(r0) |
||||
} |
||||
return |
||||
} |
||||
|
||||
func regConnectRegistry(machinename *uint16, key syscall.Handle, result *syscall.Handle) (regerrno error) { |
||||
r0, _, _ := syscall.Syscall(procRegConnectRegistryW.Addr(), 3, uintptr(unsafe.Pointer(machinename)), uintptr(key), uintptr(unsafe.Pointer(result))) |
||||
if r0 != 0 { |
||||
regerrno = syscall.Errno(r0) |
||||
} |
||||
return |
||||
} |
||||
|
||||
func expandEnvironmentStrings(src *uint16, dst *uint16, size uint32) (n uint32, err error) { |
||||
r0, _, e1 := syscall.Syscall(procExpandEnvironmentStringsW.Addr(), 3, uintptr(unsafe.Pointer(src)), uintptr(unsafe.Pointer(dst)), uintptr(size)) |
||||
n = uint32(r0) |
||||
if n == 0 { |
||||
if e1 != 0 { |
||||
err = errnoErr(e1) |
||||
} else { |
||||
err = syscall.EINVAL |
||||
} |
||||
} |
||||
return |
||||
} |
@ -0,0 +1,336 @@ |
||||
// Copyright 2016 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.
|
||||
|
||||
// Package bidirule implements the Bidi Rule defined by RFC 5893.
|
||||
//
|
||||
// This package is under development. The API may change without notice and
|
||||
// without preserving backward compatibility.
|
||||
package bidirule |
||||
|
||||
import ( |
||||
"errors" |
||||
"unicode/utf8" |
||||
|
||||
"golang.org/x/text/transform" |
||||
"golang.org/x/text/unicode/bidi" |
||||
) |
||||
|
||||
// This file contains an implementation of RFC 5893: Right-to-Left Scripts for
|
||||
// Internationalized Domain Names for Applications (IDNA)
|
||||
//
|
||||
// A label is an individual component of a domain name. Labels are usually
|
||||
// shown separated by dots; for example, the domain name "www.example.com" is
|
||||
// composed of three labels: "www", "example", and "com".
|
||||
//
|
||||
// An RTL label is a label that contains at least one character of class R, AL,
|
||||
// or AN. An LTR label is any label that is not an RTL label.
|
||||
//
|
||||
// A "Bidi domain name" is a domain name that contains at least one RTL label.
|
||||
//
|
||||
// The following guarantees can be made based on the above:
|
||||
//
|
||||
// o In a domain name consisting of only labels that satisfy the rule,
|
||||
// the requirements of Section 3 are satisfied. Note that even LTR
|
||||
// labels and pure ASCII labels have to be tested.
|
||||
//
|
||||
// o In a domain name consisting of only LDH labels (as defined in the
|
||||
// Definitions document [RFC5890]) and labels that satisfy the rule,
|
||||
// the requirements of Section 3 are satisfied as long as a label
|
||||
// that starts with an ASCII digit does not come after a
|
||||
// right-to-left label.
|
||||
//
|
||||
// No guarantee is given for other combinations.
|
||||
|
||||
// ErrInvalid indicates a label is invalid according to the Bidi Rule.
|
||||
var ErrInvalid = errors.New("bidirule: failed Bidi Rule") |
||||
|
||||
type ruleState uint8 |
||||
|
||||
const ( |
||||
ruleInitial ruleState = iota |
||||
ruleLTR |
||||
ruleLTRFinal |
||||
ruleRTL |
||||
ruleRTLFinal |
||||
ruleInvalid |
||||
) |
||||
|
||||
type ruleTransition struct { |
||||
next ruleState |
||||
mask uint16 |
||||
} |
||||
|
||||
var transitions = [...][2]ruleTransition{ |
||||
// [2.1] The first character must be a character with Bidi property L, R, or
|
||||
// AL. If it has the R or AL property, it is an RTL label; if it has the L
|
||||
// property, it is an LTR label.
|
||||
ruleInitial: { |
||||
{ruleLTRFinal, 1 << bidi.L}, |
||||
{ruleRTLFinal, 1<<bidi.R | 1<<bidi.AL}, |
||||
}, |
||||
ruleRTL: { |
||||
// [2.3] In an RTL label, the end of the label must be a character with
|
||||
// Bidi property R, AL, EN, or AN, followed by zero or more characters
|
||||
// with Bidi property NSM.
|
||||
{ruleRTLFinal, 1<<bidi.R | 1<<bidi.AL | 1<<bidi.EN | 1<<bidi.AN}, |
||||
|
||||
// [2.2] In an RTL label, only characters with the Bidi properties R,
|
||||
// AL, AN, EN, ES, CS, ET, ON, BN, or NSM are allowed.
|
||||
// We exclude the entries from [2.3]
|
||||
{ruleRTL, 1<<bidi.ES | 1<<bidi.CS | 1<<bidi.ET | 1<<bidi.ON | 1<<bidi.BN | 1<<bidi.NSM}, |
||||
}, |
||||
ruleRTLFinal: { |
||||
// [2.3] In an RTL label, the end of the label must be a character with
|
||||
// Bidi property R, AL, EN, or AN, followed by zero or more characters
|
||||
// with Bidi property NSM.
|
||||
{ruleRTLFinal, 1<<bidi.R | 1<<bidi.AL | 1<<bidi.EN | 1<<bidi.AN | 1<<bidi.NSM}, |
||||
|
||||
// [2.2] In an RTL label, only characters with the Bidi properties R,
|
||||
// AL, AN, EN, ES, CS, ET, ON, BN, or NSM are allowed.
|
||||
// We exclude the entries from [2.3] and NSM.
|
||||
{ruleRTL, 1<<bidi.ES | 1<<bidi.CS | 1<<bidi.ET | 1<<bidi.ON | 1<<bidi.BN}, |
||||
}, |
||||
ruleLTR: { |
||||
// [2.6] In an LTR label, the end of the label must be a character with
|
||||
// Bidi property L or EN, followed by zero or more characters with Bidi
|
||||
// property NSM.
|
||||
{ruleLTRFinal, 1<<bidi.L | 1<<bidi.EN}, |
||||
|
||||
// [2.5] In an LTR label, only characters with the Bidi properties L,
|
||||
// EN, ES, CS, ET, ON, BN, or NSM are allowed.
|
||||
// We exclude the entries from [2.6].
|
||||
{ruleLTR, 1<<bidi.ES | 1<<bidi.CS | 1<<bidi.ET | 1<<bidi.ON | 1<<bidi.BN | 1<<bidi.NSM}, |
||||
}, |
||||
ruleLTRFinal: { |
||||
// [2.6] In an LTR label, the end of the label must be a character with
|
||||
// Bidi property L or EN, followed by zero or more characters with Bidi
|
||||
// property NSM.
|
||||
{ruleLTRFinal, 1<<bidi.L | 1<<bidi.EN | 1<<bidi.NSM}, |
||||
|
||||
// [2.5] In an LTR label, only characters with the Bidi properties L,
|
||||
// EN, ES, CS, ET, ON, BN, or NSM are allowed.
|
||||
// We exclude the entries from [2.6].
|
||||
{ruleLTR, 1<<bidi.ES | 1<<bidi.CS | 1<<bidi.ET | 1<<bidi.ON | 1<<bidi.BN}, |
||||
}, |
||||
ruleInvalid: { |
||||
{ruleInvalid, 0}, |
||||
{ruleInvalid, 0}, |
||||
}, |
||||
} |
||||
|
||||
// [2.4] In an RTL label, if an EN is present, no AN may be present, and
|
||||
// vice versa.
|
||||
const exclusiveRTL = uint16(1<<bidi.EN | 1<<bidi.AN) |
||||
|
||||
// From RFC 5893
|
||||
// An RTL label is a label that contains at least one character of type
|
||||
// R, AL, or AN.
|
||||
//
|
||||
// An LTR label is any label that is not an RTL label.
|
||||
|
||||
// Direction reports the direction of the given label as defined by RFC 5893.
|
||||
// The Bidi Rule does not have to be applied to labels of the category
|
||||
// LeftToRight.
|
||||
func Direction(b []byte) bidi.Direction { |
||||
for i := 0; i < len(b); { |
||||
e, sz := bidi.Lookup(b[i:]) |
||||
if sz == 0 { |
||||
i++ |
||||
} |
||||
c := e.Class() |
||||
if c == bidi.R || c == bidi.AL || c == bidi.AN { |
||||
return bidi.RightToLeft |
||||
} |
||||
i += sz |
||||
} |
||||
return bidi.LeftToRight |
||||
} |
||||
|
||||
// DirectionString reports the direction of the given label as defined by RFC
|
||||
// 5893. The Bidi Rule does not have to be applied to labels of the category
|
||||
// LeftToRight.
|
||||
func DirectionString(s string) bidi.Direction { |
||||
for i := 0; i < len(s); { |
||||
e, sz := bidi.LookupString(s[i:]) |
||||
if sz == 0 { |
||||
i++ |
||||
continue |
||||
} |
||||
c := e.Class() |
||||
if c == bidi.R || c == bidi.AL || c == bidi.AN { |
||||
return bidi.RightToLeft |
||||
} |
||||
i += sz |
||||
} |
||||
return bidi.LeftToRight |
||||
} |
||||
|
||||
// Valid reports whether b conforms to the BiDi rule.
|
||||
func Valid(b []byte) bool { |
||||
var t Transformer |
||||
if n, ok := t.advance(b); !ok || n < len(b) { |
||||
return false |
||||
} |
||||
return t.isFinal() |
||||
} |
||||
|
||||
// ValidString reports whether s conforms to the BiDi rule.
|
||||
func ValidString(s string) bool { |
||||
var t Transformer |
||||
if n, ok := t.advanceString(s); !ok || n < len(s) { |
||||
return false |
||||
} |
||||
return t.isFinal() |
||||
} |
||||
|
||||
// New returns a Transformer that verifies that input adheres to the Bidi Rule.
|
||||
func New() *Transformer { |
||||
return &Transformer{} |
||||
} |
||||
|
||||
// Transformer implements transform.Transform.
|
||||
type Transformer struct { |
||||
state ruleState |
||||
hasRTL bool |
||||
seen uint16 |
||||
} |
||||
|
||||
// A rule can only be violated for "Bidi Domain names", meaning if one of the
|
||||
// following categories has been observed.
|
||||
func (t *Transformer) isRTL() bool { |
||||
const isRTL = 1<<bidi.R | 1<<bidi.AL | 1<<bidi.AN |
||||
return t.seen&isRTL != 0 |
||||
} |
||||
|
||||
// Reset implements transform.Transformer.
|
||||
func (t *Transformer) Reset() { *t = Transformer{} } |
||||
|
||||
// Transform implements transform.Transformer. This Transformer has state and
|
||||
// needs to be reset between uses.
|
||||
func (t *Transformer) Transform(dst, src []byte, atEOF bool) (nDst, nSrc int, err error) { |
||||
if len(dst) < len(src) { |
||||
src = src[:len(dst)] |
||||
atEOF = false |
||||
err = transform.ErrShortDst |
||||
} |
||||
n, err1 := t.Span(src, atEOF) |
||||
copy(dst, src[:n]) |
||||
if err == nil || err1 != nil && err1 != transform.ErrShortSrc { |
||||
err = err1 |
||||
} |
||||
return n, n, err |
||||
} |
||||
|
||||
// Span returns the first n bytes of src that conform to the Bidi rule.
|
||||
func (t *Transformer) Span(src []byte, atEOF bool) (n int, err error) { |
||||
if t.state == ruleInvalid && t.isRTL() { |
||||
return 0, ErrInvalid |
||||
} |
||||
n, ok := t.advance(src) |
||||
switch { |
||||
case !ok: |
||||
err = ErrInvalid |
||||
case n < len(src): |
||||
if !atEOF { |
||||
err = transform.ErrShortSrc |
||||
break |
||||
} |
||||
err = ErrInvalid |
||||
case !t.isFinal(): |
||||
err = ErrInvalid |
||||
} |
||||
return n, err |
||||
} |
||||
|
||||
// Precomputing the ASCII values decreases running time for the ASCII fast path
|
||||
// by about 30%.
|
||||
var asciiTable [128]bidi.Properties |
||||
|
||||
func init() { |
||||
for i := range asciiTable { |
||||
p, _ := bidi.LookupRune(rune(i)) |
||||
asciiTable[i] = p |
||||
} |
||||
} |
||||
|
||||
func (t *Transformer) advance(s []byte) (n int, ok bool) { |
||||
var e bidi.Properties |
||||
var sz int |
||||
for n < len(s) { |
||||
if s[n] < utf8.RuneSelf { |
||||
e, sz = asciiTable[s[n]], 1 |
||||
} else { |
||||
e, sz = bidi.Lookup(s[n:]) |
||||
if sz <= 1 { |
||||
if sz == 1 { |
||||
// We always consider invalid UTF-8 to be invalid, even if
|
||||
// the string has not yet been determined to be RTL.
|
||||
// TODO: is this correct?
|
||||
return n, false |
||||
} |
||||
return n, true // incomplete UTF-8 encoding
|
||||
} |
||||
} |
||||
// TODO: using CompactClass would result in noticeable speedup.
|
||||
// See unicode/bidi/prop.go:Properties.CompactClass.
|
||||
c := uint16(1 << e.Class()) |
||||
t.seen |= c |
||||
if t.seen&exclusiveRTL == exclusiveRTL { |
||||
t.state = ruleInvalid |
||||
return n, false |
||||
} |
||||
switch tr := transitions[t.state]; { |
||||
case tr[0].mask&c != 0: |
||||
t.state = tr[0].next |
||||
case tr[1].mask&c != 0: |
||||
t.state = tr[1].next |
||||
default: |
||||
t.state = ruleInvalid |
||||
if t.isRTL() { |
||||
return n, false |
||||
} |
||||
} |
||||
n += sz |
||||
} |
||||
return n, true |
||||
} |
||||
|
||||
func (t *Transformer) advanceString(s string) (n int, ok bool) { |
||||
var e bidi.Properties |
||||
var sz int |
||||
for n < len(s) { |
||||
if s[n] < utf8.RuneSelf { |
||||
e, sz = asciiTable[s[n]], 1 |
||||
} else { |
||||
e, sz = bidi.LookupString(s[n:]) |
||||
if sz <= 1 { |
||||
if sz == 1 { |
||||
return n, false // invalid UTF-8
|
||||
} |
||||
return n, true // incomplete UTF-8 encoding
|
||||
} |
||||
} |
||||
// TODO: using CompactClass results in noticeable speedup.
|
||||
// See unicode/bidi/prop.go:Properties.CompactClass.
|
||||
c := uint16(1 << e.Class()) |
||||
t.seen |= c |
||||
if t.seen&exclusiveRTL == exclusiveRTL { |
||||
t.state = ruleInvalid |
||||
return n, false |
||||
} |
||||
switch tr := transitions[t.state]; { |
||||
case tr[0].mask&c != 0: |
||||
t.state = tr[0].next |
||||
case tr[1].mask&c != 0: |
||||
t.state = tr[1].next |
||||
default: |
||||
t.state = ruleInvalid |
||||
if t.isRTL() { |
||||
return n, false |
||||
} |
||||
} |
||||
n += sz |
||||
} |
||||
return n, true |
||||
} |
@ -0,0 +1,11 @@ |
||||
// Copyright 2016 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.
|
||||
|
||||
// +build go1.10
|
||||
|
||||
package bidirule |
||||
|
||||
func (t *Transformer) isFinal() bool { |
||||
return t.state == ruleLTRFinal || t.state == ruleRTLFinal || t.state == ruleInitial |
||||
} |
@ -0,0 +1,14 @@ |
||||
// Copyright 2016 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.
|
||||
|
||||
// +build !go1.10
|
||||
|
||||
package bidirule |
||||
|
||||
func (t *Transformer) isFinal() bool { |
||||
if !t.isRTL() { |
||||
return true |
||||
} |
||||
return t.state == ruleLTRFinal || t.state == ruleRTLFinal || t.state == ruleInitial |
||||
} |
@ -0,0 +1,198 @@ |
||||
// Copyright 2015 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.
|
||||
|
||||
//go:generate go run gen.go gen_trieval.go gen_ranges.go
|
||||
|
||||
// Package bidi contains functionality for bidirectional text support.
|
||||
//
|
||||
// See https://www.unicode.org/reports/tr9.
|
||||
//
|
||||
// NOTE: UNDER CONSTRUCTION. This API may change in backwards incompatible ways
|
||||
// and without notice.
|
||||
package bidi // import "golang.org/x/text/unicode/bidi"
|
||||
|
||||
// TODO:
|
||||
// The following functionality would not be hard to implement, but hinges on
|
||||
// the definition of a Segmenter interface. For now this is up to the user.
|
||||
// - Iterate over paragraphs
|
||||
// - Segmenter to iterate over runs directly from a given text.
|
||||
// Also:
|
||||
// - Transformer for reordering?
|
||||
// - Transformer (validator, really) for Bidi Rule.
|
||||
|
||||
// This API tries to avoid dealing with embedding levels for now. Under the hood
|
||||
// these will be computed, but the question is to which extent the user should
|
||||
// know they exist. We should at some point allow the user to specify an
|
||||
// embedding hierarchy, though.
|
||||
|
||||
// A Direction indicates the overall flow of text.
|
||||
type Direction int |
||||
|
||||
const ( |
||||
// LeftToRight indicates the text contains no right-to-left characters and
|
||||
// that either there are some left-to-right characters or the option
|
||||
// DefaultDirection(LeftToRight) was passed.
|
||||
LeftToRight Direction = iota |
||||
|
||||
// RightToLeft indicates the text contains no left-to-right characters and
|
||||
// that either there are some right-to-left characters or the option
|
||||
// DefaultDirection(RightToLeft) was passed.
|
||||
RightToLeft |
||||
|
||||
// Mixed indicates text contains both left-to-right and right-to-left
|
||||
// characters.
|
||||
Mixed |
||||
|
||||
// Neutral means that text contains no left-to-right and right-to-left
|
||||
// characters and that no default direction has been set.
|
||||
Neutral |
||||
) |
||||
|
||||
type options struct{} |
||||
|
||||
// An Option is an option for Bidi processing.
|
||||
type Option func(*options) |
||||
|
||||
// ICU allows the user to define embedding levels. This may be used, for example,
|
||||
// to use hierarchical structure of markup languages to define embeddings.
|
||||
// The following option may be a way to expose this functionality in this API.
|
||||
// // LevelFunc sets a function that associates nesting levels with the given text.
|
||||
// // The levels function will be called with monotonically increasing values for p.
|
||||
// func LevelFunc(levels func(p int) int) Option {
|
||||
// panic("unimplemented")
|
||||
// }
|
||||
|
||||
// DefaultDirection sets the default direction for a Paragraph. The direction is
|
||||
// overridden if the text contains directional characters.
|
||||
func DefaultDirection(d Direction) Option { |
||||
panic("unimplemented") |
||||
} |
||||
|
||||
// A Paragraph holds a single Paragraph for Bidi processing.
|
||||
type Paragraph struct { |
||||
// buffers
|
||||
} |
||||
|
||||
// SetBytes configures p for the given paragraph text. It replaces text
|
||||
// previously set by SetBytes or SetString. If b contains a paragraph separator
|
||||
// it will only process the first paragraph and report the number of bytes
|
||||
// consumed from b including this separator. Error may be non-nil if options are
|
||||
// given.
|
||||
func (p *Paragraph) SetBytes(b []byte, opts ...Option) (n int, err error) { |
||||
panic("unimplemented") |
||||
} |
||||
|
||||
// SetString configures p for the given paragraph text. It replaces text
|
||||
// previously set by SetBytes or SetString. If b contains a paragraph separator
|
||||
// it will only process the first paragraph and report the number of bytes
|
||||
// consumed from b including this separator. Error may be non-nil if options are
|
||||
// given.
|
||||
func (p *Paragraph) SetString(s string, opts ...Option) (n int, err error) { |
||||
panic("unimplemented") |
||||
} |
||||
|
||||
// IsLeftToRight reports whether the principle direction of rendering for this
|
||||
// paragraphs is left-to-right. If this returns false, the principle direction
|
||||
// of rendering is right-to-left.
|
||||
func (p *Paragraph) IsLeftToRight() bool { |
||||
panic("unimplemented") |
||||
} |
||||
|
||||
// Direction returns the direction of the text of this paragraph.
|
||||
//
|
||||
// The direction may be LeftToRight, RightToLeft, Mixed, or Neutral.
|
||||
func (p *Paragraph) Direction() Direction { |
||||
panic("unimplemented") |
||||
} |
||||
|
||||
// RunAt reports the Run at the given position of the input text.
|
||||
//
|
||||
// This method can be used for computing line breaks on paragraphs.
|
||||
func (p *Paragraph) RunAt(pos int) Run { |
||||
panic("unimplemented") |
||||
} |
||||
|
||||
// Order computes the visual ordering of all the runs in a Paragraph.
|
||||
func (p *Paragraph) Order() (Ordering, error) { |
||||
panic("unimplemented") |
||||
} |
||||
|
||||
// Line computes the visual ordering of runs for a single line starting and
|
||||
// ending at the given positions in the original text.
|
||||
func (p *Paragraph) Line(start, end int) (Ordering, error) { |
||||
panic("unimplemented") |
||||
} |
||||
|
||||
// An Ordering holds the computed visual order of runs of a Paragraph. Calling
|
||||
// SetBytes or SetString on the originating Paragraph invalidates an Ordering.
|
||||
// The methods of an Ordering should only be called by one goroutine at a time.
|
||||
type Ordering struct{} |
||||
|
||||
// Direction reports the directionality of the runs.
|
||||
//
|
||||
// The direction may be LeftToRight, RightToLeft, Mixed, or Neutral.
|
||||
func (o *Ordering) Direction() Direction { |
||||
panic("unimplemented") |
||||
} |
||||
|
||||
// NumRuns returns the number of runs.
|
||||
func (o *Ordering) NumRuns() int { |
||||
panic("unimplemented") |
||||
} |
||||
|
||||
// Run returns the ith run within the ordering.
|
||||
func (o *Ordering) Run(i int) Run { |
||||
panic("unimplemented") |
||||
} |
||||
|
||||
// TODO: perhaps with options.
|
||||
// // Reorder creates a reader that reads the runes in visual order per character.
|
||||
// // Modifiers remain after the runes they modify.
|
||||
// func (l *Runs) Reorder() io.Reader {
|
||||
// panic("unimplemented")
|
||||
// }
|
||||
|
||||
// A Run is a continuous sequence of characters of a single direction.
|
||||
type Run struct { |
||||
} |
||||
|
||||
// String returns the text of the run in its original order.
|
||||
func (r *Run) String() string { |
||||
panic("unimplemented") |
||||
} |
||||
|
||||
// Bytes returns the text of the run in its original order.
|
||||
func (r *Run) Bytes() []byte { |
||||
panic("unimplemented") |
||||
} |
||||
|
||||
// TODO: methods for
|
||||
// - Display order
|
||||
// - headers and footers
|
||||
// - bracket replacement.
|
||||
|
||||
// Direction reports the direction of the run.
|
||||
func (r *Run) Direction() Direction { |
||||
panic("unimplemented") |
||||
} |
||||
|
||||
// Position of the Run within the text passed to SetBytes or SetString of the
|
||||
// originating Paragraph value.
|
||||
func (r *Run) Pos() (start, end int) { |
||||
panic("unimplemented") |
||||
} |
||||
|
||||
// AppendReverse reverses the order of characters of in, appends them to out,
|
||||
// and returns the result. Modifiers will still follow the runes they modify.
|
||||
// Brackets are replaced with their counterparts.
|
||||
func AppendReverse(out, in []byte) []byte { |
||||
panic("unimplemented") |
||||
} |
||||
|
||||
// ReverseString reverses the order of characters in s and returns a new string.
|
||||
// Modifiers will still follow the runes they modify. Brackets are replaced with
|
||||
// their counterparts.
|
||||
func ReverseString(s string) string { |
||||
panic("unimplemented") |
||||
} |
@ -0,0 +1,335 @@ |
||||
// Copyright 2015 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.
|
||||
|
||||
package bidi |
||||
|
||||
import ( |
||||
"container/list" |
||||
"fmt" |
||||
"sort" |
||||
) |
||||
|
||||
// This file contains a port of the reference implementation of the
|
||||
// Bidi Parentheses Algorithm:
|
||||
// https://www.unicode.org/Public/PROGRAMS/BidiReferenceJava/BidiPBAReference.java
|
||||
//
|
||||
// The implementation in this file covers definitions BD14-BD16 and rule N0
|
||||
// of UAX#9.
|
||||
//
|
||||
// Some preprocessing is done for each rune before data is passed to this
|
||||
// algorithm:
|
||||
// - opening and closing brackets are identified
|
||||
// - a bracket pair type, like '(' and ')' is assigned a unique identifier that
|
||||
// is identical for the opening and closing bracket. It is left to do these
|
||||
// mappings.
|
||||
// - The BPA algorithm requires that bracket characters that are canonical
|
||||
// equivalents of each other be able to be substituted for each other.
|
||||
// It is the responsibility of the caller to do this canonicalization.
|
||||
//
|
||||
// In implementing BD16, this implementation departs slightly from the "logical"
|
||||
// algorithm defined in UAX#9. In particular, the stack referenced there
|
||||
// supports operations that go beyond a "basic" stack. An equivalent
|
||||
// implementation based on a linked list is used here.
|
||||
|
||||
// Bidi_Paired_Bracket_Type
|
||||
// BD14. An opening paired bracket is a character whose
|
||||
// Bidi_Paired_Bracket_Type property value is Open.
|
||||
//
|
||||
// BD15. A closing paired bracket is a character whose
|
||||
// Bidi_Paired_Bracket_Type property value is Close.
|
||||
type bracketType byte |
||||
|
||||
const ( |
||||
bpNone bracketType = iota |
||||
bpOpen |
||||
bpClose |
||||
) |
||||
|
||||
// bracketPair holds a pair of index values for opening and closing bracket
|
||||
// location of a bracket pair.
|
||||
type bracketPair struct { |
||||
opener int |
||||
closer int |
||||
} |
||||
|
||||
func (b *bracketPair) String() string { |
||||
return fmt.Sprintf("(%v, %v)", b.opener, b.closer) |
||||
} |
||||
|
||||
// bracketPairs is a slice of bracketPairs with a sort.Interface implementation.
|
||||
type bracketPairs []bracketPair |
||||
|
||||
func (b bracketPairs) Len() int { return len(b) } |
||||
func (b bracketPairs) Swap(i, j int) { b[i], b[j] = b[j], b[i] } |
||||
func (b bracketPairs) Less(i, j int) bool { return b[i].opener < b[j].opener } |
||||
|
||||
// resolvePairedBrackets runs the paired bracket part of the UBA algorithm.
|
||||
//
|
||||
// For each rune, it takes the indexes into the original string, the class the
|
||||
// bracket type (in pairTypes) and the bracket identifier (pairValues). It also
|
||||
// takes the direction type for the start-of-sentence and the embedding level.
|
||||
//
|
||||
// The identifiers for bracket types are the rune of the canonicalized opening
|
||||
// bracket for brackets (open or close) or 0 for runes that are not brackets.
|
||||
func resolvePairedBrackets(s *isolatingRunSequence) { |
||||
p := bracketPairer{ |
||||
sos: s.sos, |
||||
openers: list.New(), |
||||
codesIsolatedRun: s.types, |
||||
indexes: s.indexes, |
||||
} |
||||
dirEmbed := L |
||||
if s.level&1 != 0 { |
||||
dirEmbed = R |
||||
} |
||||
p.locateBrackets(s.p.pairTypes, s.p.pairValues) |
||||
p.resolveBrackets(dirEmbed, s.p.initialTypes) |
||||
} |
||||
|
||||
type bracketPairer struct { |
||||
sos Class // direction corresponding to start of sequence
|
||||
|
||||
// The following is a restatement of BD 16 using non-algorithmic language.
|
||||
//
|
||||
// A bracket pair is a pair of characters consisting of an opening
|
||||
// paired bracket and a closing paired bracket such that the
|
||||
// Bidi_Paired_Bracket property value of the former equals the latter,
|
||||
// subject to the following constraints.
|
||||
// - both characters of a pair occur in the same isolating run sequence
|
||||
// - the closing character of a pair follows the opening character
|
||||
// - any bracket character can belong at most to one pair, the earliest possible one
|
||||
// - any bracket character not part of a pair is treated like an ordinary character
|
||||
// - pairs may nest properly, but their spans may not overlap otherwise
|
||||
|
||||
// Bracket characters with canonical decompositions are supposed to be
|
||||
// treated as if they had been normalized, to allow normalized and non-
|
||||
// normalized text to give the same result. In this implementation that step
|
||||
// is pushed out to the caller. The caller has to ensure that the pairValue
|
||||
// slices contain the rune of the opening bracket after normalization for
|
||||
// any opening or closing bracket.
|
||||
|
||||
openers *list.List // list of positions for opening brackets
|
||||
|
||||
// bracket pair positions sorted by location of opening bracket
|
||||
pairPositions bracketPairs |
||||
|
||||
codesIsolatedRun []Class // directional bidi codes for an isolated run
|
||||
indexes []int // array of index values into the original string
|
||||
|
||||
} |
||||
|
||||
// matchOpener reports whether characters at given positions form a matching
|
||||
// bracket pair.
|
||||
func (p *bracketPairer) matchOpener(pairValues []rune, opener, closer int) bool { |
||||
return pairValues[p.indexes[opener]] == pairValues[p.indexes[closer]] |
||||
} |
||||
|
||||
const maxPairingDepth = 63 |
||||
|
||||
// locateBrackets locates matching bracket pairs according to BD16.
|
||||
//
|
||||
// This implementation uses a linked list instead of a stack, because, while
|
||||
// elements are added at the front (like a push) they are not generally removed
|
||||
// in atomic 'pop' operations, reducing the benefit of the stack archetype.
|
||||
func (p *bracketPairer) locateBrackets(pairTypes []bracketType, pairValues []rune) { |
||||
// traverse the run
|
||||
// do that explicitly (not in a for-each) so we can record position
|
||||
for i, index := range p.indexes { |
||||
|
||||
// look at the bracket type for each character
|
||||
if pairTypes[index] == bpNone || p.codesIsolatedRun[i] != ON { |
||||
// continue scanning
|
||||
continue |
||||
} |
||||
switch pairTypes[index] { |
||||
case bpOpen: |
||||
// check if maximum pairing depth reached
|
||||
if p.openers.Len() == maxPairingDepth { |
||||
p.openers.Init() |
||||
return |
||||
} |
||||
// remember opener location, most recent first
|
||||
p.openers.PushFront(i) |
||||
|
||||
case bpClose: |
||||
// see if there is a match
|
||||
count := 0 |
||||
for elem := p.openers.Front(); elem != nil; elem = elem.Next() { |
||||
count++ |
||||
opener := elem.Value.(int) |
||||
if p.matchOpener(pairValues, opener, i) { |
||||
// if the opener matches, add nested pair to the ordered list
|
||||
p.pairPositions = append(p.pairPositions, bracketPair{opener, i}) |
||||
// remove up to and including matched opener
|
||||
for ; count > 0; count-- { |
||||
p.openers.Remove(p.openers.Front()) |
||||
} |
||||
break |
||||
} |
||||
} |
||||
sort.Sort(p.pairPositions) |
||||
// if we get here, the closing bracket matched no openers
|
||||
// and gets ignored
|
||||
} |
||||
} |
||||
} |
||||
|
||||
// Bracket pairs within an isolating run sequence are processed as units so
|
||||
// that both the opening and the closing paired bracket in a pair resolve to
|
||||
// the same direction.
|
||||
//
|
||||
// N0. Process bracket pairs in an isolating run sequence sequentially in
|
||||
// the logical order of the text positions of the opening paired brackets
|
||||
// using the logic given below. Within this scope, bidirectional types EN
|
||||
// and AN are treated as R.
|
||||
//
|
||||
// Identify the bracket pairs in the current isolating run sequence
|
||||
// according to BD16. For each bracket-pair element in the list of pairs of
|
||||
// text positions:
|
||||
//
|
||||
// a Inspect the bidirectional types of the characters enclosed within the
|
||||
// bracket pair.
|
||||
//
|
||||
// b If any strong type (either L or R) matching the embedding direction is
|
||||
// found, set the type for both brackets in the pair to match the embedding
|
||||
// direction.
|
||||
//
|
||||
// o [ e ] o -> o e e e o
|
||||
//
|
||||
// o [ o e ] -> o e o e e
|
||||
//
|
||||
// o [ NI e ] -> o e NI e e
|
||||
//
|
||||
// c Otherwise, if a strong type (opposite the embedding direction) is
|
||||
// found, test for adjacent strong types as follows: 1 First, check
|
||||
// backwards before the opening paired bracket until the first strong type
|
||||
// (L, R, or sos) is found. If that first preceding strong type is opposite
|
||||
// the embedding direction, then set the type for both brackets in the pair
|
||||
// to that type. 2 Otherwise, set the type for both brackets in the pair to
|
||||
// the embedding direction.
|
||||
//
|
||||
// o [ o ] e -> o o o o e
|
||||
//
|
||||
// o [ o NI ] o -> o o o NI o o
|
||||
//
|
||||
// e [ o ] o -> e e o e o
|
||||
//
|
||||
// e [ o ] e -> e e o e e
|
||||
//
|
||||
// e ( o [ o ] NI ) e -> e e o o o o NI e e
|
||||
//
|
||||
// d Otherwise, do not set the type for the current bracket pair. Note that
|
||||
// if the enclosed text contains no strong types the paired brackets will
|
||||
// both resolve to the same level when resolved individually using rules N1
|
||||
// and N2.
|
||||
//
|
||||
// e ( NI ) o -> e ( NI ) o
|
||||
|
||||
// getStrongTypeN0 maps character's directional code to strong type as required
|
||||
// by rule N0.
|
||||
//
|
||||
// TODO: have separate type for "strong" directionality.
|
||||
func (p *bracketPairer) getStrongTypeN0(index int) Class { |
||||
switch p.codesIsolatedRun[index] { |
||||
// in the scope of N0, number types are treated as R
|
||||
case EN, AN, AL, R: |
||||
return R |
||||
case L: |
||||
return L |
||||
default: |
||||
return ON |
||||
} |
||||
} |
||||
|
||||
// classifyPairContent reports the strong types contained inside a Bracket Pair,
|
||||
// assuming the given embedding direction.
|
||||
//
|
||||
// It returns ON if no strong type is found. If a single strong type is found,
|
||||
// it returns this type. Otherwise it returns the embedding direction.
|
||||
//
|
||||
// TODO: use separate type for "strong" directionality.
|
||||
func (p *bracketPairer) classifyPairContent(loc bracketPair, dirEmbed Class) Class { |
||||
dirOpposite := ON |
||||
for i := loc.opener + 1; i < loc.closer; i++ { |
||||
dir := p.getStrongTypeN0(i) |
||||
if dir == ON { |
||||
continue |
||||
} |
||||
if dir == dirEmbed { |
||||
return dir // type matching embedding direction found
|
||||
} |
||||
dirOpposite = dir |
||||
} |
||||
// return ON if no strong type found, or class opposite to dirEmbed
|
||||
return dirOpposite |
||||
} |
||||
|
||||
// classBeforePair determines which strong types are present before a Bracket
|
||||
// Pair. Return R or L if strong type found, otherwise ON.
|
||||
func (p *bracketPairer) classBeforePair(loc bracketPair) Class { |
||||
for i := loc.opener - 1; i >= 0; i-- { |
||||
if dir := p.getStrongTypeN0(i); dir != ON { |
||||
return dir |
||||
} |
||||
} |
||||
// no strong types found, return sos
|
||||
return p.sos |
||||
} |
||||
|
||||
// assignBracketType implements rule N0 for a single bracket pair.
|
||||
func (p *bracketPairer) assignBracketType(loc bracketPair, dirEmbed Class, initialTypes []Class) { |
||||
// rule "N0, a", inspect contents of pair
|
||||
dirPair := p.classifyPairContent(loc, dirEmbed) |
||||
|
||||
// dirPair is now L, R, or N (no strong type found)
|
||||
|
||||
// the following logical tests are performed out of order compared to
|
||||
// the statement of the rules but yield the same results
|
||||
if dirPair == ON { |
||||
return // case "d" - nothing to do
|
||||
} |
||||
|
||||
if dirPair != dirEmbed { |
||||
// case "c": strong type found, opposite - check before (c.1)
|
||||
dirPair = p.classBeforePair(loc) |
||||
if dirPair == dirEmbed || dirPair == ON { |
||||
// no strong opposite type found before - use embedding (c.2)
|
||||
dirPair = dirEmbed |
||||
} |
||||
} |
||||
// else: case "b", strong type found matching embedding,
|
||||
// no explicit action needed, as dirPair is already set to embedding
|
||||
// direction
|
||||
|
||||
// set the bracket types to the type found
|
||||
p.setBracketsToType(loc, dirPair, initialTypes) |
||||
} |
||||
|
||||
func (p *bracketPairer) setBracketsToType(loc bracketPair, dirPair Class, initialTypes []Class) { |
||||
p.codesIsolatedRun[loc.opener] = dirPair |
||||
p.codesIsolatedRun[loc.closer] = dirPair |
||||
|
||||
for i := loc.opener + 1; i < loc.closer; i++ { |
||||
index := p.indexes[i] |
||||
if initialTypes[index] != NSM { |
||||
break |
||||
} |
||||
p.codesIsolatedRun[i] = dirPair |
||||
} |
||||
|
||||
for i := loc.closer + 1; i < len(p.indexes); i++ { |
||||
index := p.indexes[i] |
||||
if initialTypes[index] != NSM { |
||||
break |
||||
} |
||||
p.codesIsolatedRun[i] = dirPair |
||||
} |
||||
} |
||||
|
||||
// resolveBrackets implements rule N0 for a list of pairs.
|
||||
func (p *bracketPairer) resolveBrackets(dirEmbed Class, initialTypes []Class) { |
||||
for _, loc := range p.pairPositions { |
||||
p.assignBracketType(loc, dirEmbed, initialTypes) |
||||
} |
||||
} |
File diff suppressed because it is too large
Load Diff
@ -0,0 +1,206 @@ |
||||
// Copyright 2016 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.
|
||||
|
||||
package bidi |
||||
|
||||
import "unicode/utf8" |
||||
|
||||
// Properties provides access to BiDi properties of runes.
|
||||
type Properties struct { |
||||
entry uint8 |
||||
last uint8 |
||||
} |
||||
|
||||
var trie = newBidiTrie(0) |
||||
|
||||
// TODO: using this for bidirule reduces the running time by about 5%. Consider
|
||||
// if this is worth exposing or if we can find a way to speed up the Class
|
||||
// method.
|
||||
//
|
||||
// // CompactClass is like Class, but maps all of the BiDi control classes
|
||||
// // (LRO, RLO, LRE, RLE, PDF, LRI, RLI, FSI, PDI) to the class Control.
|
||||
// func (p Properties) CompactClass() Class {
|
||||
// return Class(p.entry & 0x0F)
|
||||
// }
|
||||
|
||||
// Class returns the Bidi class for p.
|
||||
func (p Properties) Class() Class { |
||||
c := Class(p.entry & 0x0F) |
||||
if c == Control { |
||||
c = controlByteToClass[p.last&0xF] |
||||
} |
||||
return c |
||||
} |
||||
|
||||
// IsBracket reports whether the rune is a bracket.
|
||||
func (p Properties) IsBracket() bool { return p.entry&0xF0 != 0 } |
||||
|
||||
// IsOpeningBracket reports whether the rune is an opening bracket.
|
||||
// IsBracket must return true.
|
||||
func (p Properties) IsOpeningBracket() bool { return p.entry&openMask != 0 } |
||||
|
||||
// TODO: find a better API and expose.
|
||||
func (p Properties) reverseBracket(r rune) rune { |
||||
return xorMasks[p.entry>>xorMaskShift] ^ r |
||||
} |
||||
|
||||
var controlByteToClass = [16]Class{ |
||||
0xD: LRO, // U+202D LeftToRightOverride,
|
||||
0xE: RLO, // U+202E RightToLeftOverride,
|
||||
0xA: LRE, // U+202A LeftToRightEmbedding,
|
||||
0xB: RLE, // U+202B RightToLeftEmbedding,
|
||||
0xC: PDF, // U+202C PopDirectionalFormat,
|
||||
0x6: LRI, // U+2066 LeftToRightIsolate,
|
||||
0x7: RLI, // U+2067 RightToLeftIsolate,
|
||||
0x8: FSI, // U+2068 FirstStrongIsolate,
|
||||
0x9: PDI, // U+2069 PopDirectionalIsolate,
|
||||
} |
||||
|
||||
// LookupRune returns properties for r.
|
||||
func LookupRune(r rune) (p Properties, size int) { |
||||
var buf [4]byte |
||||
n := utf8.EncodeRune(buf[:], r) |
||||
return Lookup(buf[:n]) |
||||
} |
||||
|
||||
// TODO: these lookup methods are based on the generated trie code. The returned
|
||||
// sizes have slightly different semantics from the generated code, in that it
|
||||
// always returns size==1 for an illegal UTF-8 byte (instead of the length
|
||||
// of the maximum invalid subsequence). Most Transformers, like unicode/norm,
|
||||
// leave invalid UTF-8 untouched, in which case it has performance benefits to
|
||||
// do so (without changing the semantics). Bidi requires the semantics used here
|
||||
// for the bidirule implementation to be compatible with the Go semantics.
|
||||
// They ultimately should perhaps be adopted by all trie implementations, for
|
||||
// convenience sake.
|
||||
// This unrolled code also boosts performance of the secure/bidirule package by
|
||||
// about 30%.
|
||||
// So, to remove this code:
|
||||
// - add option to trie generator to define return type.
|
||||
// - always return 1 byte size for ill-formed UTF-8 runes.
|
||||
|
||||
// Lookup returns properties for the first rune in s and the width in bytes of
|
||||
// its encoding. The size will be 0 if s does not hold enough bytes to complete
|
||||
// the encoding.
|
||||
func Lookup(s []byte) (p Properties, sz int) { |
||||
c0 := s[0] |
||||
switch { |
||||
case c0 < 0x80: // is ASCII
|
||||
return Properties{entry: bidiValues[c0]}, 1 |
||||
case c0 < 0xC2: |
||||
return Properties{}, 1 |
||||
case c0 < 0xE0: // 2-byte UTF-8
|
||||
if len(s) < 2 { |
||||
return Properties{}, 0 |
||||
} |
||||
i := bidiIndex[c0] |
||||
c1 := s[1] |
||||
if c1 < 0x80 || 0xC0 <= c1 { |
||||
return Properties{}, 1 |
||||
} |
||||
return Properties{entry: trie.lookupValue(uint32(i), c1)}, 2 |
||||
case c0 < 0xF0: // 3-byte UTF-8
|
||||
if len(s) < 3 { |
||||
return Properties{}, 0 |
||||
} |
||||
i := bidiIndex[c0] |
||||
c1 := s[1] |
||||
if c1 < 0x80 || 0xC0 <= c1 { |
||||
return Properties{}, 1 |
||||
} |
||||
o := uint32(i)<<6 + uint32(c1) |
||||
i = bidiIndex[o] |
||||
c2 := s[2] |
||||
if c2 < 0x80 || 0xC0 <= c2 { |
||||
return Properties{}, 1 |
||||
} |
||||
return Properties{entry: trie.lookupValue(uint32(i), c2), last: c2}, 3 |
||||
case c0 < 0xF8: // 4-byte UTF-8
|
||||
if len(s) < 4 { |
||||
return Properties{}, 0 |
||||
} |
||||
i := bidiIndex[c0] |
||||
c1 := s[1] |
||||
if c1 < 0x80 || 0xC0 <= c1 { |
||||
return Properties{}, 1 |
||||
} |
||||
o := uint32(i)<<6 + uint32(c1) |
||||
i = bidiIndex[o] |
||||
c2 := s[2] |
||||
if c2 < 0x80 || 0xC0 <= c2 { |
||||
return Properties{}, 1 |
||||
} |
||||
o = uint32(i)<<6 + uint32(c2) |
||||
i = bidiIndex[o] |
||||
c3 := s[3] |
||||
if c3 < 0x80 || 0xC0 <= c3 { |
||||
return Properties{}, 1 |
||||
} |
||||
return Properties{entry: trie.lookupValue(uint32(i), c3)}, 4 |
||||
} |
||||
// Illegal rune
|
||||
return Properties{}, 1 |
||||
} |
||||
|
||||
// LookupString returns properties for the first rune in s and the width in
|
||||
// bytes of its encoding. The size will be 0 if s does not hold enough bytes to
|
||||
// complete the encoding.
|
||||
func LookupString(s string) (p Properties, sz int) { |
||||
c0 := s[0] |
||||
switch { |
||||
case c0 < 0x80: // is ASCII
|
||||
return Properties{entry: bidiValues[c0]}, 1 |
||||
case c0 < 0xC2: |
||||
return Properties{}, 1 |
||||
case c0 < 0xE0: // 2-byte UTF-8
|
||||
if len(s) < 2 { |
||||
return Properties{}, 0 |
||||
} |
||||
i := bidiIndex[c0] |
||||
c1 := s[1] |
||||
if c1 < 0x80 || 0xC0 <= c1 { |
||||
return Properties{}, 1 |
||||
} |
||||
return Properties{entry: trie.lookupValue(uint32(i), c1)}, 2 |
||||
case c0 < 0xF0: // 3-byte UTF-8
|
||||
if len(s) < 3 { |
||||
return Properties{}, 0 |
||||
} |
||||
i := bidiIndex[c0] |
||||
c1 := s[1] |
||||
if c1 < 0x80 || 0xC0 <= c1 { |
||||
return Properties{}, 1 |
||||
} |
||||
o := uint32(i)<<6 + uint32(c1) |
||||
i = bidiIndex[o] |
||||
c2 := s[2] |
||||
if c2 < 0x80 || 0xC0 <= c2 { |
||||
return Properties{}, 1 |
||||
} |
||||
return Properties{entry: trie.lookupValue(uint32(i), c2), last: c2}, 3 |
||||
case c0 < 0xF8: // 4-byte UTF-8
|
||||
if len(s) < 4 { |
||||
return Properties{}, 0 |
||||
} |
||||
i := bidiIndex[c0] |
||||
c1 := s[1] |
||||
if c1 < 0x80 || 0xC0 <= c1 { |
||||
return Properties{}, 1 |
||||
} |
||||
o := uint32(i)<<6 + uint32(c1) |
||||
i = bidiIndex[o] |
||||
c2 := s[2] |
||||
if c2 < 0x80 || 0xC0 <= c2 { |
||||
return Properties{}, 1 |
||||
} |
||||
o = uint32(i)<<6 + uint32(c2) |
||||
i = bidiIndex[o] |
||||
c3 := s[3] |
||||
if c3 < 0x80 || 0xC0 <= c3 { |
||||
return Properties{}, 1 |
||||
} |
||||
return Properties{entry: trie.lookupValue(uint32(i), c3)}, 4 |
||||
} |
||||
// Illegal rune
|
||||
return Properties{}, 1 |
||||
} |
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
@ -0,0 +1,60 @@ |
||||
// Code generated by running "go generate" in golang.org/x/text. DO NOT EDIT.
|
||||
|
||||
package bidi |
||||
|
||||
// Class is the Unicode BiDi class. Each rune has a single class.
|
||||
type Class uint |
||||
|
||||
const ( |
||||
L Class = iota // LeftToRight
|
||||
R // RightToLeft
|
||||
EN // EuropeanNumber
|
||||
ES // EuropeanSeparator
|
||||
ET // EuropeanTerminator
|
||||
AN // ArabicNumber
|
||||
CS // CommonSeparator
|
||||
B // ParagraphSeparator
|
||||
S // SegmentSeparator
|
||||
WS // WhiteSpace
|
||||
ON // OtherNeutral
|
||||
BN // BoundaryNeutral
|
||||
NSM // NonspacingMark
|
||||
AL // ArabicLetter
|
||||
Control // Control LRO - PDI
|
||||
|
||||
numClass |
||||
|
||||
LRO // LeftToRightOverride
|
||||
RLO // RightToLeftOverride
|
||||
LRE // LeftToRightEmbedding
|
||||
RLE // RightToLeftEmbedding
|
||||
PDF // PopDirectionalFormat
|
||||
LRI // LeftToRightIsolate
|
||||
RLI // RightToLeftIsolate
|
||||
FSI // FirstStrongIsolate
|
||||
PDI // PopDirectionalIsolate
|
||||
|
||||
unknownClass = ^Class(0) |
||||
) |
||||
|
||||
var controlToClass = map[rune]Class{ |
||||
0x202D: LRO, // LeftToRightOverride,
|
||||
0x202E: RLO, // RightToLeftOverride,
|
||||
0x202A: LRE, // LeftToRightEmbedding,
|
||||
0x202B: RLE, // RightToLeftEmbedding,
|
||||
0x202C: PDF, // PopDirectionalFormat,
|
||||
0x2066: LRI, // LeftToRightIsolate,
|
||||
0x2067: RLI, // RightToLeftIsolate,
|
||||
0x2068: FSI, // FirstStrongIsolate,
|
||||
0x2069: PDI, // PopDirectionalIsolate,
|
||||
} |
||||
|
||||
// A trie entry has the following bits:
|
||||
// 7..5 XOR mask for brackets
|
||||
// 4 1: Bracket open, 0: Bracket close
|
||||
// 3..0 Class type
|
||||
|
||||
const ( |
||||
openMask = 0x10 |
||||
xorMaskShift = 5 |
||||
) |
Loading…
Reference in new issue