Skip to content

Commit

Permalink
The changes commited are intended to provide reading system proxies t…
Browse files Browse the repository at this point in the history
…hrough scutil command. There are some unit tests added as well to test out the feature added (#3)
  • Loading branch information
kasadzadeh-r7 authored and tosmun-r7 committed Oct 25, 2018
1 parent 94cfda2 commit f8e269c
Show file tree
Hide file tree
Showing 3 changed files with 332 additions and 1 deletion.
28 changes: 28 additions & 0 deletions proxy/provider.go
Original file line number Diff line number Diff line change
Expand Up @@ -13,13 +13,15 @@
package proxy

import (
"context"
"encoding/json"
"errors"
"fmt"
"io/ioutil"
"log"
"net/url"
"os"
"os/exec"
"path/filepath"
"strings"
)
Expand Down Expand Up @@ -56,14 +58,18 @@ type Provider interface {

type getEnvAdapter func(string) (string)

type commandAdapter func (context.Context, string, ...string) *exec.Cmd

type provider struct {
configFile string
getEnv getEnvAdapter
proc commandAdapter
}

func (p *provider) init(configFile string) {
p.configFile = configFile
p.getEnv = os.Getenv
p.proc = exec.CommandContext
}

/*
Expand Down Expand Up @@ -299,6 +305,12 @@ func (e notFoundError) Error() (string) {
return "No proxy found"
}

type timeoutError struct {}

func (e timeoutError) Error() (string) {
return "Timed out"
}

/*
Returns:
true: The error represents a Proxy not being found
Expand All @@ -314,3 +326,19 @@ func isNotFound(e error) (bool) {
return false
}
}

/*
Returns:
true: The error represents a Time out
false: Otherwise
s*/
func isTimedOut(e error) (bool) {
switch e.(type) {
case *timeoutError:
return true
case timeoutError:
return true
default:
return false
}
}
130 changes: 129 additions & 1 deletion proxy/provider_darwin.go
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,16 @@
// without specific prior written permission.
package proxy

import (
"bufio"
"bytes"
"context"
"log"
"regexp"
"strings"
"time"
)

type providerDarwin struct {
provider
}
Expand Down Expand Up @@ -42,7 +52,10 @@ Returns:
nil: A proxy was not found, or an error occurred
*/
func (p *providerDarwin) Get(protocol string, targetUrlStr string) (Proxy) {
return p.provider.get(protocol, ParseTargetURL(targetUrlStr))
if proxy := p.provider.get(protocol, ParseTargetURL(targetUrlStr)); proxy != nil {
return proxy
}
return p.readDarwinNetworkSettingProxy(protocol)
}

/*
Expand All @@ -60,4 +73,119 @@ Returns:
*/
func (p *providerDarwin) GetHTTPS(targetUrl string) (Proxy) {
return p.Get("https", targetUrl)
}


/*
Returns the Network Setting Proxy found.
If none is found, or an error occurs, nil is returned.
Params:
protocol: The protocol of interest
Returns:
Proxy: A proxy was found
nil: A proxy was not found, or an error occurred
*/
func (p *providerDarwin) readDarwinNetworkSettingProxy(protocol string) (Proxy) {
proxy, err := p.parseScutildata(protocol, "scutil", "--proxy")
if err != nil {
if isNotFound(err){
log.Printf("[proxy.Provider.readDarwinNetworkSettingProxy]: %s proxy is not enabled.\n", protocol)
}else if isTimedOut(err){
log.Printf("[proxy.Provider.readDarwinNetworkSettingProxy]: Operation timed out. \n")
} else {
log.Printf("[proxy.Provider.readDarwinNetworkSettingProxy]: Failed to parse Scutil data, %s\n", err)
}
}
return proxy
}

/*
Returns the Proxy found by parsing the Scutil output.
If none is found, or an error occurs, nil is returned.
Params:
protocol: The protocol of interest
name: The name of the program (scutil)
arg: The list of the arguments (--proxy)
Returns:
Proxy: A proxy was found
nil: A proxy was not found, or an error occurred
*/
func (p *providerDarwin) parseScutildata(protocol string, name string, arg ...string) (Proxy, error) {
lookupProtocol := strings.ToUpper(protocol) // to cover search for http, HTTP, https, HTTPS

ctx, cancel := context.WithTimeout(context.Background(), time.Second * 1) // Die after one second
defer cancel()

cmd := p.proc(ctx, name, arg...)
var out bytes.Buffer
cmd.Stdout = &out
err := cmd.Run()
if err != nil {
return nil, new(timeoutError)
}

scanner := bufio.NewScanner(strings.NewReader(out.String()))
/* init values */
var enable bool
var port string
var host string

regexEnable, err := regexp.Compile(lookupProtocol + "Enable:1")
if err != nil {
return nil, err
}
regexDisable, err := regexp.Compile(lookupProtocol + "Enable:0")
if err != nil {
return nil, err
}
regexPort, err := regexp.Compile(lookupProtocol + "Port:")
if err != nil {
return nil, err
}
regexProxy, err := regexp.Compile(lookupProtocol + "Proxy:")
if err != nil {
return nil, err
}

for scanner.Scan() {
str := strings.Replace(scanner.Text(), " ", "", -1) // removing spaces
if !enable { // don't search if already found
// if proxy is disabled, stop the search
protocolDisableFound := regexDisable.FindStringIndex(str)
if protocolDisableFound != nil {
break
}
protocolEnableFound := regexEnable.FindStringIndex(str)
if protocolEnableFound != nil {
enable = true
}
}
if port == "" { // don't search if already found
portFoundLoc := regexPort.FindStringIndex(str)
if portFoundLoc != nil {
port = str[portFoundLoc[1]:]
}
}
if host == "" { // don't search if already found
proxyFoundLoc := regexProxy.FindStringIndex(str)
if proxyFoundLoc != nil {
host = str[proxyFoundLoc[1]:]
}
}
}
if !enable {
return nil, new(notFoundError)
}

proxyUrlStr := host + ":" + port
proxyUrl, err := ParseURL(proxyUrlStr, protocol)
if err != nil {
return nil, err
}
src := "State:/Network/Global/Proxies"
proxy, err := NewProxy(protocol, proxyUrl, src)
if err != nil {
return nil, err
}
return proxy, nil
}
175 changes: 175 additions & 0 deletions proxy/provider_darwin_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,175 @@
package proxy

import (
"context"
"fmt"
"github.com/stretchr/testify/assert"
"os/exec"
"strings"
"testing"
)

const (
ScutilDataHttpsHttp = "ScutilDataHttpsHttp"
ScutilDataHttps = "ScutilDataHttps"
ScutilDataHttp = "ScutilDataHttp"
)

var providerDarwinTestCases = []struct {
testType string
test string
}{
{ScutilDataHttpsHttp,
fmt.Sprintf("<dictionary> {\n HTTPSEnable : 1\n HTTPSPort : %s\n HTTPSProxy : %s\n" +
" HTTPEnable : 1\n HTTPPort : %s\n HTTPProxy : %s\n}", "1234", "1.2.3.4", "1234", "1.2.3.4")},
{ScutilDataHttpsHttp,
fmt.Sprintf(" <dictionary> { \n HTTPSEnable:1 \nHTTPSPort : %s\n HTTPSProxy : %s\n " +
"HTTPEnable : 1 \n HTTPPort : %s\nHTTPProxy: %s \n }", "1234", "1.2.3.4", "1234", "1.2.3.4")},
{ScutilDataHttps,
fmt.Sprintf("<dictionary> {\n HTTPEnable : 0\n HTTPSEnable : 1\n HTTPSPort : %s\n " +
"HTTPSProxy : %s\n}", "1234", "1.2.3.4")},
{ScutilDataHttps,
fmt.Sprintf("<dictionary> {\n HTTPEnable: 0\n HTTPSEnable: 1\n " +
"HTTPSPort : %s\n HTTPSProxy: %s\n}", "1234", "1.2.3.4")},
{ScutilDataHttp,
fmt.Sprintf("<dictionary> {\n HTTPSEnable : 0\n HTTPEnable : 1\n HTTPPort : %s\n " +
"HTTPProxy : %s\n}", "1234", "1.2.3.4")},
{ScutilDataHttp, fmt.Sprintf("<dictionary> {\n HTTPSEnable: 0\n HTTPEnable: 1\n " +
"HTTPPort : %s\n HTTPProxy: %s\n}", "1234", "1.2.3.4")},
}

func getDarwinProviderTests(key string)([]string){
var s []string
for _, v := range providerDarwinTestCases {
if v.testType == key {
s = append(s, v.test)
}
}
return s
}

/*
Below tests cover cases when both https and http proxies are present.
following tests are being performed:
- Test https and http proxies are not nil,
- Test https and http proxies match expected,
- Test for lower case and upper case, i.e. https/HTTPS/...,
- Test no errors are returned
*/
func TestParseScutildata_Read_HTTPS_HTTP(t *testing.T) {
a := assert.New(t)

c := newDarwinTestProvider()

commands := getDarwinProviderTests(ScutilDataHttpsHttp)

protocols := [4]string{"http", "https", "HTTP", "HTTPS"}
for _, protocol := range protocols {
for _, command := range commands {
expectedProxy, err := c.parseScutildata(protocol, "echo", command)
// test error is nil
a.Nil(err)
// test expected https proxy matches hardcoded proxy, test lowercase
a.Equal(&proxy{src: "State:/Network/Global/Proxies", protocol: strings.ToLower(protocol), host: "1.2.3.4", port: 1234},
expectedProxy)
// test https and https proxies are not nil
a.NotNil(c.parseScutildata(protocol, "echo", command))
}
}
}

/*
Below tests cover cases when only https proxy is present.
following tests are being performed:
- Test https proxy is not nil,
- Test http proxy is nil,
- Test https proxy match expected,
- Test for lower case and upper case, i.e. https/HTTPS/...,
- Test no errors are returned
*/
func TestParseScutildata_Read_HTTPS(t *testing.T) {
a := assert.New(t)

c := newDarwinTestProvider()

commands := getDarwinProviderTests(ScutilDataHttps)

protocols := [4]string{"http", "https", "HTTP", "HTTPS"}
for _, protocol := range protocols {
for _, command := range commands {
if strings.ToLower(protocol) == "https" {
expectedProxy, err := c.parseScutildata(protocol, "echo", command)
// test error is nil
a.Nil(err)
// test expected https proxy matches hardcoded proxy
a.Equal(&proxy{src: "State:/Network/Global/Proxies", protocol: strings.ToLower(protocol), host: "1.2.3.4", port: 1234},
expectedProxy)
// test https proxy is not nil
a.NotNil(c.parseScutildata(protocol, "echo", command))
}else{
// test http proxy is nil
a.Nil(c.parseScutildata(protocol, "echo", command))
}

}
}
}

/*
Below tests cover cases when only http proxy is present.
following tests are being performed:
- Test http proxy is not nil,
- Test https proxy is nil,
- Test http proxy match expected,
- Test for lower case and upper case, i.e. https/HTTPS/...,
- Test no errors are returned
*/
func TestParseScutildata_Read_HTTP(t *testing.T) {
a := assert.New(t)

c := newDarwinTestProvider()

commands := getDarwinProviderTests(ScutilDataHttp)

protocols := [4]string{"http", "https", "HTTP", "HTTPS"}
for _, protocol := range protocols {
for _, command := range commands {
if strings.ToLower(protocol) == "http" {
expectedProxy, err := c.parseScutildata(protocol, "echo", command)
// test error is nil
a.Nil(err)
// test expected http proxy matches hardcoded proxy
a.Equal(&proxy{src: "State:/Network/Global/Proxies", protocol: strings.ToLower(protocol), host: "1.2.3.4", port: 1234},
expectedProxy)
// test http proxy is not nil
a.NotNil(c.parseScutildata(protocol, "echo", command))
}else{
// test https proxy is nil
a.Nil(c.parseScutildata(protocol, "echo", command))
}
}
}
}

/*
Tests whether the timeout property functions as expected
*/
func TestExecCommandsHandledProperly(t *testing.T) {
a := assert.New(t)

c := newDarwinTestProvider()

expectedProxy, err := c.parseScutildata("", "exit", "")

a.Equal(isTimedOut(err), true)
a.Equal(expectedProxy, nil)
}

func newDarwinTestProvider() (*providerDarwin) {
Cmd := func (ctx context.Context, name string, args ...string) *exec.Cmd {
return exec.CommandContext(ctx, name, args...)
}
c := new(providerDarwin)
c.proc = Cmd
return c
}

0 comments on commit f8e269c

Please sign in to comment.