2018-02-10 13:24:32 +01:00
package http
2015-09-17 20:57:27 +02:00
import (
"io/ioutil"
2015-09-18 17:13:14 +02:00
"log"
2015-09-17 20:57:27 +02:00
"net"
"net/http"
"net/http/httptest"
2020-07-09 21:35:26 +02:00
"net/url"
2020-09-11 21:16:43 +02:00
"strings"
2015-09-17 20:57:27 +02:00
"testing"
2018-02-10 14:35:12 +01:00
2018-08-27 20:33:29 +02:00
"github.com/mpolden/echoip/iputil/geo"
2015-09-17 20:57:27 +02:00
)
2018-03-19 19:54:24 +01:00
func lookupAddr ( net . IP ) ( string , error ) { return "localhost" , nil }
func lookupPort ( net . IP , uint64 ) error { return nil }
2018-02-10 17:52:55 +01:00
type testDb struct { }
2018-08-14 21:00:46 +02:00
func ( t * testDb ) Country ( net . IP ) ( geo . Country , error ) {
2018-08-31 22:41:16 +02:00
return geo . Country { Name : "Elbonia" , ISO : "EB" , IsEU : new ( bool ) } , nil
2018-02-10 14:35:12 +01:00
}
2016-04-17 11:09:56 +02:00
2018-08-27 21:39:49 +02:00
func ( t * testDb ) City ( net . IP ) ( geo . City , error ) {
2020-05-10 14:23:50 +02:00
return geo . City { Name : "Bornyasherk" , RegionName : "North Elbonia" , RegionCode : "1234" , MetroCode : 1234 , PostalCode : "1234" , Latitude : 63.416667 , Longitude : 10.416667 , Timezone : "Europe/Bornyasherk" } , nil
2018-06-15 09:29:13 +02:00
}
2019-07-05 15:01:45 +02:00
func ( t * testDb ) ASN ( net . IP ) ( geo . ASN , error ) {
return geo . ASN { AutonomousSystemNumber : 59795 , AutonomousSystemOrganization : "Hosting4Real" } , nil
}
2018-08-27 21:39:49 +02:00
func ( t * testDb ) IsEmpty ( ) bool { return false }
2018-02-10 17:52:55 +01:00
func testServer ( ) * Server {
2019-12-25 21:04:26 +01:00
return & Server { cache : NewCache ( 100 ) , gr : & testDb { } , LookupAddr : lookupAddr , LookupPort : lookupPort }
2015-09-29 20:39:21 +02:00
}
2016-11-16 20:00:03 +01:00
func httpGet ( url string , acceptMediaType string , userAgent string ) ( string , int , error ) {
2015-09-17 20:57:27 +02:00
r , err := http . NewRequest ( "GET" , url , nil )
if err != nil {
2015-09-18 17:13:14 +02:00
return "" , 0 , err
2015-09-17 20:57:27 +02:00
}
2016-11-16 20:00:03 +01:00
if acceptMediaType != "" {
r . Header . Set ( "Accept" , acceptMediaType )
2015-09-17 20:57:27 +02:00
}
r . Header . Set ( "User-Agent" , userAgent )
res , err := http . DefaultClient . Do ( r )
if err != nil {
2015-09-18 17:13:14 +02:00
return "" , 0 , err
2015-09-17 20:57:27 +02:00
}
defer res . Body . Close ( )
data , err := ioutil . ReadAll ( res . Body )
if err != nil {
2015-09-18 17:13:14 +02:00
return "" , 0 , err
2015-09-17 20:57:27 +02:00
}
2015-09-18 17:13:14 +02:00
return string ( data ) , res . StatusCode , nil
2015-09-17 20:57:27 +02:00
}
2020-09-11 21:16:43 +02:00
func httpPost ( url , body string ) ( * http . Response , string , error ) {
r , err := http . NewRequest ( http . MethodPost , url , strings . NewReader ( body ) )
if err != nil {
return nil , "" , err
}
res , err := http . DefaultClient . Do ( r )
if err != nil {
return nil , "" , err
}
defer res . Body . Close ( )
data , err := ioutil . ReadAll ( res . Body )
if err != nil {
return nil , "" , err
}
return res , string ( data ) , nil
}
2016-09-06 19:35:23 +02:00
func TestCLIHandlers ( t * testing . T ) {
2016-04-16 09:18:21 +02:00
log . SetOutput ( ioutil . Discard )
2018-02-10 17:52:55 +01:00
s := httptest . NewServer ( testServer ( ) . Handler ( ) )
2016-04-16 09:52:43 +02:00
2015-09-17 20:57:27 +02:00
var tests = [ ] struct {
2016-11-16 20:00:03 +01:00
url string
out string
status int
userAgent string
acceptMediaType string
2015-09-17 20:57:27 +02:00
} {
2016-11-16 20:00:03 +01:00
{ s . URL , "127.0.0.1\n" , 200 , "curl/7.43.0" , "" } ,
{ s . URL , "127.0.0.1\n" , 200 , "foo/bar" , textMediaType } ,
{ s . URL + "/ip" , "127.0.0.1\n" , 200 , "" , "" } ,
{ s . URL + "/country" , "Elbonia\n" , 200 , "" , "" } ,
2018-02-09 20:41:30 +01:00
{ s . URL + "/country-iso" , "EB\n" , 200 , "" , "" } ,
2018-08-27 21:48:08 +02:00
{ s . URL + "/coordinates" , "63.416667,10.416667\n" , 200 , "" , "" } ,
2016-11-16 20:00:03 +01:00
{ s . URL + "/city" , "Bornyasherk\n" , 200 , "" , "" } ,
{ s . URL + "/foo" , "404 page not found" , 404 , "" , "" } ,
2019-07-05 15:01:45 +02:00
{ s . URL + "/asn" , "AS59795\n" , 200 , "" , "" } ,
2015-09-17 20:57:27 +02:00
}
2015-09-18 17:42:43 +02:00
2015-09-17 20:57:27 +02:00
for _ , tt := range tests {
2016-11-16 20:00:03 +01:00
out , status , err := httpGet ( tt . url , tt . acceptMediaType , tt . userAgent )
2015-09-17 20:57:27 +02:00
if err != nil {
t . Fatal ( err )
}
2015-09-18 17:13:14 +02:00
if status != tt . status {
t . Errorf ( "Expected %d, got %d" , tt . status , status )
}
2015-09-17 20:57:27 +02:00
if out != tt . out {
t . Errorf ( "Expected %q, got %q" , tt . out , out )
}
}
}
2018-02-10 17:52:55 +01:00
func TestDisabledHandlers ( t * testing . T ) {
log . SetOutput ( ioutil . Discard )
server := testServer ( )
2018-02-11 11:19:50 +01:00
server . LookupPort = nil
server . LookupAddr = nil
2019-07-05 15:01:45 +02:00
server . gr , _ = geo . Open ( "" , "" , "" )
2018-02-10 17:52:55 +01:00
s := httptest . NewServer ( server . Handler ( ) )
var tests = [ ] struct {
url string
out string
status int
} {
{ s . URL + "/port/1337" , "404 page not found" , 404 } ,
{ s . URL + "/country" , "404 page not found" , 404 } ,
{ s . URL + "/country-iso" , "404 page not found" , 404 } ,
{ s . URL + "/city" , "404 page not found" , 404 } ,
2020-11-09 21:52:18 +01:00
{ s . URL + "/json" , "{\n \"ip\": \"127.0.0.1\",\n \"ip_decimal\": 2130706433\n}" , 200 } ,
2018-02-10 17:52:55 +01:00
}
for _ , tt := range tests {
out , status , err := httpGet ( tt . url , "" , "" )
if err != nil {
t . Fatal ( err )
}
if status != tt . status {
t . Errorf ( "Expected %d, got %d" , tt . status , status )
}
if out != tt . out {
t . Errorf ( "Expected %q, got %q" , tt . out , out )
}
}
}
2016-04-16 09:52:43 +02:00
func TestJSONHandlers ( t * testing . T ) {
2015-09-29 20:39:21 +02:00
log . SetOutput ( ioutil . Discard )
2018-02-10 17:52:55 +01:00
s := httptest . NewServer ( testServer ( ) . Handler ( ) )
2015-09-29 20:39:21 +02:00
2016-04-16 09:52:43 +02:00
var tests = [ ] struct {
url string
out string
status int
} {
2020-11-09 21:52:18 +01:00
{ s . URL , "{\n \"ip\": \"127.0.0.1\",\n \"ip_decimal\": 2130706433,\n \"country\": \"Elbonia\",\n \"country_iso\": \"EB\",\n \"country_eu\": false,\n \"region_name\": \"North Elbonia\",\n \"region_code\": \"1234\",\n \"metro_code\": 1234,\n \"zip_code\": \"1234\",\n \"city\": \"Bornyasherk\",\n \"latitude\": 63.416667,\n \"longitude\": 10.416667,\n \"time_zone\": \"Europe/Bornyasherk\",\n \"asn\": \"AS59795\",\n \"asn_org\": \"Hosting4Real\",\n \"hostname\": \"localhost\",\n \"user_agent\": {\n \"product\": \"curl\",\n \"version\": \"7.2.6.0\",\n \"raw_value\": \"curl/7.2.6.0\"\n }\n}" , 200 } ,
{ s . URL + "/port/foo" , "{\n \"error\": \"invalid port: foo\"\n}" , 400 } ,
{ s . URL + "/port/0" , "{\n \"error\": \"invalid port: 0\"\n}" , 400 } ,
{ s . URL + "/port/65537" , "{\n \"error\": \"invalid port: 65537\"\n}" , 400 } ,
{ s . URL + "/port/31337" , "{\n \"ip\": \"127.0.0.1\",\n \"port\": 31337,\n \"reachable\": true\n}" , 200 } ,
{ s . URL + "/port/80" , "{\n \"ip\": \"127.0.0.1\",\n \"port\": 80,\n \"reachable\": true\n}" , 200 } , // checking that our test server is reachable on port 80
{ s . URL + "/port/80?ip=1.3.3.7" , "{\n \"ip\": \"127.0.0.1\",\n \"port\": 80,\n \"reachable\": true\n}" , 200 } , // ensuring that the "ip" parameter is not usable to check remote host ports
2020-12-09 21:09:41 +01:00
{ s . URL + "/?ip=1.3.3.7/country" , "{\n \"error\": \"could not parse IP: 1.3.3.7/country\"\n}" , 400 } ,
2020-11-09 21:52:18 +01:00
{ s . URL + "/foo" , "{\n \"error\": \"404 page not found\"\n}" , 404 } ,
2018-07-30 22:32:42 +02:00
{ s . URL + "/health" , ` { "status":"OK"} ` , 200 } ,
2015-09-29 20:39:21 +02:00
}
2016-04-16 09:52:43 +02:00
for _ , tt := range tests {
2016-11-16 20:00:03 +01:00
out , status , err := httpGet ( tt . url , jsonMediaType , "curl/7.2.6.0" )
2016-04-16 09:52:43 +02:00
if err != nil {
t . Fatal ( err )
}
if status != tt . status {
2018-03-18 22:15:51 +01:00
t . Errorf ( "Expected %d for %s, got %d" , tt . status , tt . url , status )
2016-04-16 09:52:43 +02:00
}
if out != tt . out {
2018-03-18 22:15:51 +01:00
t . Errorf ( "Expected %q for %s, got %q" , tt . out , tt . url , out )
2016-04-16 09:52:43 +02:00
}
2015-09-29 20:39:21 +02:00
}
}
2020-09-11 20:52:35 +02:00
func TestCacheHandler ( t * testing . T ) {
log . SetOutput ( ioutil . Discard )
srv := testServer ( )
srv . profile = true
s := httptest . NewServer ( srv . Handler ( ) )
got , _ , err := httpGet ( s . URL + "/debug/cache/" , jsonMediaType , "" )
if err != nil {
t . Fatal ( err )
}
2020-11-09 21:52:18 +01:00
want := "{\n \"size\": 0,\n \"capacity\": 100,\n \"evictions\": 0\n}"
2020-09-11 20:52:35 +02:00
if got != want {
t . Errorf ( "got %q, want %q" , got , want )
}
}
2020-09-11 21:16:43 +02:00
func TestCacheResizeHandler ( t * testing . T ) {
log . SetOutput ( ioutil . Discard )
srv := testServer ( )
srv . profile = true
s := httptest . NewServer ( srv . Handler ( ) )
_ , got , err := httpPost ( s . URL + "/debug/cache/resize" , "10" )
if err != nil {
t . Fatal ( err )
}
2020-11-09 21:52:18 +01:00
want := "{\n \"message\": \"Changed cache capacity to 10.\"\n}"
2020-09-11 21:16:43 +02:00
if got != want {
t . Errorf ( "got %q, want %q" , got , want )
}
}
2015-09-17 20:57:27 +02:00
func TestIPFromRequest ( t * testing . T ) {
var tests = [ ] struct {
2018-07-25 21:05:08 +02:00
remoteAddr string
headerKey string
headerValue string
trustedHeaders [ ] string
out string
2015-09-17 20:57:27 +02:00
} {
2020-07-09 21:35:26 +02:00
{ "127.0.0.1:9999" , "" , "" , nil , "127.0.0.1" } , // No header given
{ "127.0.0.1:9999" , "X-Real-IP" , "1.3.3.7" , nil , "127.0.0.1" } , // Trusted header is empty
{ "127.0.0.1:9999" , "X-Real-IP" , "1.3.3.7" , [ ] string { "X-Foo-Bar" } , "127.0.0.1" } , // Trusted header does not match
{ "127.0.0.1:9999" , "X-Real-IP" , "1.3.3.7" , [ ] string { "X-Real-IP" , "X-Forwarded-For" } , "1.3.3.7" } , // Trusted header matches
{ "127.0.0.1:9999" , "X-Forwarded-For" , "1.3.3.7" , [ ] string { "X-Real-IP" , "X-Forwarded-For" } , "1.3.3.7" } , // Second trusted header matches
{ "127.0.0.1:9999" , "X-Forwarded-For" , "1.3.3.7,4.2.4.2" , [ ] string { "X-Forwarded-For" } , "1.3.3.7" } , // X-Forwarded-For with multiple entries (commas separator)
{ "127.0.0.1:9999" , "X-Forwarded-For" , "1.3.3.7, 4.2.4.2" , [ ] string { "X-Forwarded-For" } , "1.3.3.7" } , // X-Forwarded-For with multiple entries (space+comma separator)
{ "127.0.0.1:9999" , "X-Forwarded-For" , "" , [ ] string { "X-Forwarded-For" } , "127.0.0.1" } , // Empty header
{ "127.0.0.1:9999?ip=1.2.3.4" , "" , "" , nil , "1.2.3.4" } , // passed in "ip" parameter
{ "127.0.0.1:9999?ip=1.2.3.4" , "X-Forwarded-For" , "1.3.3.7,4.2.4.2" , [ ] string { "X-Forwarded-For" } , "1.2.3.4" } , // ip parameter wins over X-Forwarded-For with multiple entries
2015-09-17 20:57:27 +02:00
}
for _ , tt := range tests {
2020-07-09 21:35:26 +02:00
u , err := url . Parse ( "http://" + tt . remoteAddr )
if err != nil {
t . Fatal ( err )
}
2016-04-17 15:52:06 +02:00
r := & http . Request {
2020-07-09 21:35:26 +02:00
RemoteAddr : u . Host ,
2016-04-17 15:52:06 +02:00
Header : http . Header { } ,
2020-07-09 21:35:26 +02:00
URL : u ,
2016-04-17 15:52:06 +02:00
}
r . Header . Add ( tt . headerKey , tt . headerValue )
2020-07-09 21:35:26 +02:00
ip , err := ipFromRequest ( tt . trustedHeaders , r , true )
2015-09-17 20:57:27 +02:00
if err != nil {
t . Fatal ( err )
}
2016-04-17 15:52:06 +02:00
out := net . ParseIP ( tt . out )
if ! ip . Equal ( out ) {
t . Errorf ( "Expected %s, got %s" , out , ip )
2015-09-17 20:57:27 +02:00
}
}
}
func TestCLIMatcher ( t * testing . T ) {
browserUserAgent := "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_8_4) " +
"AppleWebKit/537.36 (KHTML, like Gecko) Chrome/30.0.1599.28 " +
"Safari/537.36"
var tests = [ ] struct {
in string
out bool
} {
{ "curl/7.26.0" , true } ,
{ "Wget/1.13.4 (linux-gnu)" , true } ,
2017-05-27 15:31:50 +02:00
{ "Wget" , true } ,
2015-09-17 20:57:27 +02:00
{ "fetch libfetch/2.0" , true } ,
2016-04-15 20:19:14 +02:00
{ "HTTPie/0.9.3" , true } ,
2020-07-24 00:41:07 +02:00
{ "httpie-go/0.6.0" , true } ,
2016-04-16 09:52:43 +02:00
{ "Go 1.1 package http" , true } ,
{ "Go-http-client/1.1" , true } ,
{ "Go-http-client/2.0" , true } ,
2016-05-26 21:36:23 +02:00
{ "ddclient/3.8.3" , true } ,
2019-07-12 16:00:38 +02:00
{ "Mikrotik/6.x Fetch" , true } ,
2015-09-17 20:57:27 +02:00
{ browserUserAgent , false } ,
}
for _ , tt := range tests {
r := & http . Request { Header : http . Header { "User-Agent" : [ ] string { tt . in } } }
2018-03-18 22:15:51 +01:00
if got := cliMatcher ( r ) ; got != tt . out {
2015-09-17 20:57:27 +02:00
t . Errorf ( "Expected %t, got %t for %q" , tt . out , got , tt . in )
}
}
}