// Copyright (c) 2016-2022 Uber Technologies, Inc.
//
// 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.

package zap

import (
	
	
	
	
	
	
	
	

	
)

const schemeFile = "file"

var _sinkRegistry = newSinkRegistry()

// Sink defines the interface to write to and close logger destinations.
type Sink interface {
	zapcore.WriteSyncer
	io.Closer
}

type errSinkNotFound struct {
	scheme string
}

func ( *errSinkNotFound) () string {
	return fmt.Sprintf("no sink found for scheme %q", .scheme)
}

type nopCloserSink struct{ zapcore.WriteSyncer }

func (nopCloserSink) () error { return nil }

type sinkRegistry struct {
	mu        sync.Mutex
	factories map[string]func(*url.URL) (Sink, error)          // keyed by scheme
	openFile  func(string, int, os.FileMode) (*os.File, error) // type matches os.OpenFile
}

func newSinkRegistry() *sinkRegistry {
	 := &sinkRegistry{
		factories: make(map[string]func(*url.URL) (Sink, error)),
		openFile:  os.OpenFile,
	}
	// Infallible operation: the registry is empty, so we can't have a conflict.
	_ = .RegisterSink(schemeFile, .newFileSinkFromURL)
	return 
}

// RegisterScheme registers the given factory for the specific scheme.
func ( *sinkRegistry) ( string,  func(*url.URL) (Sink, error)) error {
	.mu.Lock()
	defer .mu.Unlock()

	if  == "" {
		return errors.New("can't register a sink factory for empty string")
	}
	,  := normalizeScheme()
	if  != nil {
		return fmt.Errorf("%q is not a valid scheme: %v", , )
	}
	if ,  := .factories[];  {
		return fmt.Errorf("sink factory already registered for scheme %q", )
	}
	.factories[] = 
	return nil
}

func ( *sinkRegistry) ( string) (Sink, error) {
	// URL parsing doesn't work well for Windows paths such as `c:\log.txt`, as scheme is set to
	// the drive, and path is unset unless `c:/log.txt` is used.
	// To avoid Windows-specific URL handling, we instead check IsAbs to open as a file.
	// filepath.IsAbs is OS-specific, so IsAbs('c:/log.txt') is false outside of Windows.
	if filepath.IsAbs() {
		return .newFileSinkFromPath()
	}

	,  := url.Parse()
	if  != nil {
		return nil, fmt.Errorf("can't parse %q as a URL: %v", , )
	}
	if .Scheme == "" {
		.Scheme = schemeFile
	}

	.mu.Lock()
	,  := .factories[.Scheme]
	.mu.Unlock()
	if ! {
		return nil, &errSinkNotFound{.Scheme}
	}
	return ()
}

// RegisterSink registers a user-supplied factory for all sinks with a
// particular scheme.
//
// All schemes must be ASCII, valid under section 0.1 of RFC 3986
// (https://tools.ietf.org/html/rfc3983#section-3.1), and must not already
// have a factory registered. Zap automatically registers a factory for the
// "file" scheme.
func ( string,  func(*url.URL) (Sink, error)) error {
	return _sinkRegistry.RegisterSink(, )
}

func ( *sinkRegistry) ( *url.URL) (Sink, error) {
	if .User != nil {
		return nil, fmt.Errorf("user and password not allowed with file URLs: got %v", )
	}
	if .Fragment != "" {
		return nil, fmt.Errorf("fragments not allowed with file URLs: got %v", )
	}
	if .RawQuery != "" {
		return nil, fmt.Errorf("query parameters not allowed with file URLs: got %v", )
	}
	// Error messages are better if we check hostname and port separately.
	if .Port() != "" {
		return nil, fmt.Errorf("ports not allowed with file URLs: got %v", )
	}
	if  := .Hostname();  != "" &&  != "localhost" {
		return nil, fmt.Errorf("file URLs must leave host empty or use localhost: got %v", )
	}

	return .newFileSinkFromPath(.Path)
}

func ( *sinkRegistry) ( string) (Sink, error) {
	switch  {
	case "stdout":
		return nopCloserSink{os.Stdout}, nil
	case "stderr":
		return nopCloserSink{os.Stderr}, nil
	}
	return .openFile(, os.O_WRONLY|os.O_APPEND|os.O_CREATE, 0o666)
}

func normalizeScheme( string) (string, error) {
	// https://tools.ietf.org/html/rfc3986#section-3.1
	 = strings.ToLower()
	if  := [0]; 'a' >  || 'z' <  {
		return "", errors.New("must start with a letter")
	}
	for  := 1;  < len(); ++ { // iterate over bytes, not runes
		 := []
		switch {
		case 'a' <=  &&  <= 'z':
			continue
		case '0' <=  &&  <= '9':
			continue
		case  == '.' ||  == '+' ||  == '-':
			continue
		}
		return "", fmt.Errorf("may not contain %q", )
	}
	return , nil
}