/* * Copyright 2020 National Library of Norway. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package url import "golang.org/x/text/encoding/charmap" var defaultSpecialSchemes = map[string]string{ "ftp": "21", "file": "", "http": "80", "https": "443", "ws": "80", "wss": "443", } // parserOptions configure a url parser. parserOptions are set by the ParserOption // values passed to NewParser. type parserOptions struct { reportValidationErrors bool failOnValidationError bool laxHostParsing bool collapseConsecutiveSlashes bool acceptInvalidCodepoints bool preParseHostFunc func(url *Url, host string) string postParseHostFunc func(url *Url, host string) string percentEncodeSinglePercentSign bool allowSettingPathForNonBaseUrl bool skipWindowsDriveLetterNormalization bool specialSchemes map[string]string skipTrailingSlashNormalization bool encodingOverride *charmap.Charmap pathPercentEncodeSet *PercentEncodeSet specialQueryPercentEncodeSet *PercentEncodeSet queryPercentEncodeSet *PercentEncodeSet specialFragmentPercentEncodeSet *PercentEncodeSet fragmentPercentEncodeSet *PercentEncodeSet skipEqualsForEmptySearchParamsValue bool } // ParserOption configures how we parse a URL. type ParserOption interface { apply(*parserOptions) } // EmptyParserOption does not alter the parser configuration. It can be embedded in // another structure to build custom parser options. type EmptyParserOption struct{} func (EmptyParserOption) apply(*parserOptions) {} // funcParserOption wraps a function that modifies parserOptions into an // implementation of the ParserOption interface. type funcParserOption struct { f func(*parserOptions) } func (fpo *funcParserOption) apply(po *parserOptions) { fpo.f(po) } func newFuncParserOption(f func(*parserOptions)) *funcParserOption { return &funcParserOption{ f: f, } } func defaultParserOptions() parserOptions { return parserOptions{ pathPercentEncodeSet: PathPercentEncodeSet, specialQueryPercentEncodeSet: SpecialQueryPercentEncodeSet, queryPercentEncodeSet: QueryPercentEncodeSet, specialFragmentPercentEncodeSet: FragmentPercentEncodeSet, fragmentPercentEncodeSet: FragmentPercentEncodeSet, specialSchemes: defaultSpecialSchemes, } } // WithReportValidationErrors records all non fatal validation errors so that they can be fetchd by a call to.... func WithReportValidationErrors() ParserOption { return newFuncParserOption(func(o *parserOptions) { o.reportValidationErrors = true }) } // WithFailOnValidationError makes the parser throw an error on non fatal validation errors. func WithFailOnValidationError() ParserOption { return newFuncParserOption(func(o *parserOptions) { o.failOnValidationError = true }) } // WithLaxHostParsing ignores some decoding errors and returns the host as is. // // This API is EXPERIMENTAL. func WithLaxHostParsing() ParserOption { return newFuncParserOption(func(o *parserOptions) { o.laxHostParsing = true }) } // WithCollapseConsecutiveSlashes collapses consecutive slashes in path into one // (e.g. http://example.com//foo///bar => http://example.com/foo/bar). func WithCollapseConsecutiveSlashes() ParserOption { return newFuncParserOption(func(o *parserOptions) { o.collapseConsecutiveSlashes = true }) } // WithAcceptInvalidCodepoints percent encodes values which are not valid UTF-8. // // This API is EXPERIMENTAL. func WithAcceptInvalidCodepoints() ParserOption { return newFuncParserOption(func(o *parserOptions) { o.acceptInvalidCodepoints = true }) } // WithPreParseHostFunc is a function which allows manipulation of host string before it is parsed. // // This API is EXPERIMENTAL. func WithPreParseHostFunc(f func(url *Url, host string) string) ParserOption { return newFuncParserOption(func(o *parserOptions) { o.preParseHostFunc = f }) } // WithPostParseHostFunc is a function which allows manipulation of host string after it is parsed. // It is called only if the host isn't an IP address. // // This API is EXPERIMENTAL. func WithPostParseHostFunc(f func(url *Url, host string) string) ParserOption { return newFuncParserOption(func(o *parserOptions) { o.postParseHostFunc = f }) } // WithPercentEncodeSinglePercentSign percent encodes a '%' which is not followed by two hexadecimal digits // instead of complaining about invalid percent encoding. // // This API is EXPERIMENTAL. func WithPercentEncodeSinglePercentSign() ParserOption { return newFuncParserOption(func(o *parserOptions) { o.percentEncodeSinglePercentSign = true }) } // WithAllowSettingPathForNonBaseUrl allows to set path for a url which cannot be a base url. // WhathWg standard says this should be illegal // // This API is EXPERIMENTAL. func WithAllowSettingPathForNonBaseUrl() ParserOption { return newFuncParserOption(func(o *parserOptions) { o.allowSettingPathForNonBaseUrl = true }) } // WithSkipWindowsDriveLetterNormalization skips conversion of 'C|' to 'C:'. // WhathWg standard says only a normalized Windows drive letter is conforming. // // This API is EXPERIMENTAL. func WithSkipWindowsDriveLetterNormalization() ParserOption { return newFuncParserOption(func(o *parserOptions) { o.skipWindowsDriveLetterNormalization = true }) } // WithSpecialSchemes allows overriding the notion of special schemes. // special is a map of 'scheme' => 'default port' // // WhatWg standard removed gopher from special schemes. This is how you add it back: // // special := map[string]string{ // "ftp": "21", // "file": "", // "http": "80", // "https": "443", // "ws": "80", // "wss": "443", // "gopher": "70", // } // // This API is EXPERIMENTAL. func WithSpecialSchemes(special map[string]string) ParserOption { return newFuncParserOption(func(o *parserOptions) { o.specialSchemes = special }) } // WithEncodingOverride allows to set an encoding other than UTF-8 when parsing. // // This API is EXPERIMENTAL. func WithEncodingOverride(cm *charmap.Charmap) ParserOption { return newFuncParserOption(func(o *parserOptions) { o.encodingOverride = cm }) } // WithPathPercentEncodeSet allows to set an alternative set of characters to percent encode in path component. // // This API is EXPERIMENTAL. func WithPathPercentEncodeSet(encodeSet *PercentEncodeSet) ParserOption { return newFuncParserOption(func(o *parserOptions) { o.pathPercentEncodeSet = encodeSet }) } // WithQueryPercentEncodeSet allows to set an alternative set of characters to percent encode in query component // when scheme is not special. // // This API is EXPERIMENTAL. func WithQueryPercentEncodeSet(encodeSet *PercentEncodeSet) ParserOption { return newFuncParserOption(func(o *parserOptions) { o.queryPercentEncodeSet = encodeSet }) } // WithSpecialQueryPercentEncodeSet allows to set an alternative set of characters to percent encode in query component // when scheme is special. // // This API is EXPERIMENTAL. func WithSpecialQueryPercentEncodeSet(encodeSet *PercentEncodeSet) ParserOption { return newFuncParserOption(func(o *parserOptions) { o.specialQueryPercentEncodeSet = encodeSet }) } // WithFragmentPathPercentEncodeSet allows to set an alternative set of characters to percent encode in fragment // component when scheme is not special. // // This API is EXPERIMENTAL. func WithFragmentPathPercentEncodeSet(encodeSet *PercentEncodeSet) ParserOption { return newFuncParserOption(func(o *parserOptions) { o.fragmentPercentEncodeSet = encodeSet }) } // WithSpecialFragmentPathPercentEncodeSet allows to set an alternative set of characters to percent encode in fragment // component when scheme is special. // // This API is EXPERIMENTAL. func WithSpecialFragmentPathPercentEncodeSet(encodeSet *PercentEncodeSet) ParserOption { return newFuncParserOption(func(o *parserOptions) { o.fragmentPercentEncodeSet = encodeSet }) } // WithSkipTrailingSlashNormalization skips normalizing of empty paths. // // This API is EXPERIMENTAL. func WithSkipTrailingSlashNormalization() ParserOption { return newFuncParserOption(func(o *parserOptions) { o.skipTrailingSlashNormalization = true }) } // WithSkipEqualsForEmptySearchParamsValue skips writing '=' when setting an empty value for a search parameter. // // e.g. url.SearchParams().Set("name", "") gives 'http://...?name' instead of 'http://...?name=' // // This API is EXPERIMENTAL. func WithSkipEqualsForEmptySearchParamsValue() ParserOption { return newFuncParserOption(func(o *parserOptions) { o.skipEqualsForEmptySearchParamsValue = true }) }