4
\$\begingroup\$

I just re-wrote a library to easily retrieve the proxy settings of Internet Explorer (self answered SO question: https://stackoverflow.com/q/41764614/3207406).

Git repository

GoDoc (with an example)

package ieproxy

import (
    "os"
    "strings"
    "sync"

    "golang.org/x/sys/windows/registry"
)

// StaticProxyConf containes the Windows 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, like linux)
    NoProxy string
}

// AutomaticProxyConf contains the Windows configuration for automatic proxy
type AutomaticProxyConf struct {
    URL string // url of the .pac file
}

// WindowsProxyConf gathers the Windows configuration for proxy
type WindowsProxyConf struct {
    Static    StaticProxyConf    // static configuration
    Automatic AutomaticProxyConf // automatic configuration
}


type regeditValues struct {
    ProxyServer   string
    ProxyOverride string
    ProxyEnable   uint64
    AutoConfigURL string
}

var once sync.Once
var windowsProxyConf WindowsProxyConf

// GetConf retrieves the proxy configuration from the Windows Regedit
func getConf() WindowsProxyConf {
    once.Do(parseRegedit)
    return windowsProxyConf
}

// 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 := getConf()
    if conf.Static.Active {
        for _, scheme := range []string{"http", "https"} {
            url, ok := conf.Static.Protocols[scheme]
            if !ok {
                url, ok = conf.Static.Protocols[""] // fallback conf
            }
            if ok {
                os.Setenv(scheme+"_proxy", url)
            }
        }
        if conf.Static.NoProxy != "" {
            os.Setenv("no_proxy", conf.Static.NoProxy)
        }
    }
}

func parseRegedit() {
    regedit, _ := readRegedit()

    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]
        }
    }

    windowsProxyConf.Static = StaticProxyConf{
        Active:    regedit.ProxyEnable > 0,
        Protocols: protocol,
        NoProxy:   strings.Replace(regedit.ProxyOverride, ";", ",", -1), // to match linux style
    }
    windowsProxyConf.Automatic = AutomaticProxyConf{
        URL: 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
}

I'm interested to know if the "go-style" can be improved and any other suggestions!

\$\endgroup\$
  • \$\begingroup\$ cough-cough .... does this work? getConf vs GetConf .... hmmm \$\endgroup\$ – rolfl Jan 20 '17 at 13:34
  • 1
    \$\begingroup\$ @rolfl: actually yes, because GetConf calls getConf (for platform independency), but you are right that it isn't nice to read! \$\endgroup\$ – oliverpool Jan 20 '17 at 13:37
  • 1
    \$\begingroup\$ GetConf is/was not defined in the code anywhere... so now it works after your edit, but I see that I had to go to the git repository to find that other file with the GetConf declaration. \$\endgroup\$ – rolfl Jan 20 '17 at 13:45
  • \$\begingroup\$ Yes, sorry about that. I also included the struct definitions in my question (which are in the platform independent file as well) \$\endgroup\$ – oliverpool Jan 20 '17 at 13:48
2
\$\begingroup\$

As is, the (my) code is really hard to test (needs actual registry modifications or env variable edition).

To ease the testing, one could change some functions:

func overrideEnvWithStaticProxy(conf ProxyConf, setenv envSetter) {
    ...
}
type envSetter func(string, string) error

func parseRegedit(regedit regeditValues) ProxyConf {
   ...
}

With this change overrideEnvWithStaticProxy and parseRegedit become self contained and can be easily tested!

This incurs a minor rewrite of getConf (which does only some plumbing):

func getConf() ProxyConf {
    once.Do(writeConf)
    return windowsProxyConf
}

func writeConf() {
    regedit, _ := readRegedit()
    windowsProxyConf = parseRegedit(regedit)
}
\$\endgroup\$
1
\$\begingroup\$

The field StaticProxyConf.NoProxy should not be a single "comma-separated string", but rather a string slice. That makes the code easier that uses this field.

In readRegedit, the err = nil is not necessary.

\$\endgroup\$
  • \$\begingroup\$ If StaticProxyConf.NoProxy becomes a slice, then it has to be joined when the no_proxyenv variable is set. Without err = nil, if the last error is registry.ErrNotExist, then this error will be returned (instead of nil). A better approach would be to return nil at the end (and remove all unreadable naked returns) \$\endgroup\$ – oliverpool Aug 25 at 19:40

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.