github.com/icyphox/x@v0.0.355-0.20220311094250-029bd783e8b8/urlx/parse.go (about)

     1  package urlx
     2  
     3  import (
     4  	"net/url"
     5  	"regexp"
     6  	"strings"
     7  
     8  	"github.com/ory/x/logrusx"
     9  )
    10  
    11  // winPathRegex is a regex for [DRIVE-LETTER]:
    12  var winPathRegex = regexp.MustCompile("^[A-Za-z]:.*")
    13  
    14  // Parse parses rawURL into a URL structure with special handling for file:// URLs
    15  //
    16  // File URLs with relative paths (file://../file, ../file) will be returned as a
    17  // url.URL object without the Scheme set to "file". This is because the file
    18  // scheme does not support relative paths. Make sure to check for
    19  // both "file" or "" (an empty string) in URL.Scheme if you are looking for
    20  // a file path.
    21  //
    22  // Use the companion function GetURLFilePath() to get a file path suitable
    23  // for the current operating system.
    24  func Parse(rawURL string) (*url.URL, error) {
    25  	lcRawURL := strings.ToLower(rawURL)
    26  	if strings.HasPrefix(lcRawURL, "file:///") {
    27  		return url.Parse(rawURL)
    28  	}
    29  
    30  	// Normally the first part after file:// is a hostname, but since
    31  	// this is often misused we interpret the URL like a normal path
    32  	// by removing the "file://" from the beginning (if it exists)
    33  	rawURL = trimPrefixIC(rawURL, "file://")
    34  
    35  	if winPathRegex.MatchString(rawURL) {
    36  		// Windows path
    37  		return url.Parse("file:///" + rawURL)
    38  	}
    39  
    40  	if strings.HasPrefix(lcRawURL, "\\\\") {
    41  		// Windows UNC path
    42  		// We extract the hostname and create an appropriate file:// URL
    43  		// based on the hostname and the path
    44  		host, path := extractUNCPathParts(rawURL)
    45  		// It is safe to replace the \ with / here because this is POSIX style path
    46  		return url.Parse("file://" + host + strings.ReplaceAll(path, "\\", "/"))
    47  	}
    48  
    49  	return url.Parse(rawURL)
    50  }
    51  
    52  // ParseOrPanic parses a url or panics.
    53  func ParseOrPanic(in string) *url.URL {
    54  	out, err := url.Parse(in)
    55  	if err != nil {
    56  		panic(err.Error())
    57  	}
    58  	return out
    59  }
    60  
    61  // ParseOrFatal parses a url or fatals.
    62  func ParseOrFatal(l *logrusx.Logger, in string) *url.URL {
    63  	out, err := url.Parse(in)
    64  	if err != nil {
    65  		l.WithError(err).Fatalf("Unable to parse url: %s", in)
    66  	}
    67  	return out
    68  }
    69  
    70  // ParseRequestURIOrPanic parses a request uri or panics.
    71  func ParseRequestURIOrPanic(in string) *url.URL {
    72  	out, err := url.ParseRequestURI(in)
    73  	if err != nil {
    74  		panic(err.Error())
    75  	}
    76  	return out
    77  }
    78  
    79  // ParseRequestURIOrFatal parses a request uri or fatals.
    80  func ParseRequestURIOrFatal(l *logrusx.Logger, in string) *url.URL {
    81  	out, err := url.ParseRequestURI(in)
    82  	if err != nil {
    83  		l.WithError(err).Fatalf("Unable to parse url: %s", in)
    84  	}
    85  	return out
    86  }
    87  
    88  func extractUNCPathParts(uncPath string) (host, path string) {
    89  	parts := strings.Split(strings.TrimPrefix(uncPath, "\\\\"), "\\")
    90  	host = parts[0]
    91  	if len(parts) > 0 {
    92  		path = "\\" + strings.Join(parts[1:], "\\")
    93  	}
    94  	return host, path
    95  }
    96  
    97  // trimPrefixIC returns s without the provided leading prefix string using
    98  // case insensitive matching.
    99  // If s doesn't start with prefix, s is returned unchanged.
   100  func trimPrefixIC(s, prefix string) string {
   101  	if strings.HasPrefix(strings.ToLower(s), prefix) {
   102  		return s[len(prefix):]
   103  	}
   104  	return s
   105  }