Skip to content
Draft
8 changes: 7 additions & 1 deletion Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,9 @@ test-kubo-subdomains: provision-kubo gateway-conformance
test-kubo: provision-kubo fixtures.car gateway-conformance
./gateway-conformance test --json output.json --gateway-url http://127.0.0.1:8080 --specs -subdomain-gateway

test-randomizer: provision-kubo fixtures.car gateway-conformance
./gateway-conformance test --json output.json --gateway-url http://127.0.0.1:4242 --specs -subdomain-gateway

provision-cargateway: ./fixtures.car
# cd go-libipfs/examples/car && go install
car -c ./fixtures.car &
Expand All @@ -21,6 +24,9 @@ provision-kubo:
fixtures.car: gateway-conformance
./gateway-conformance extract-fixtures --merged=true --dir=.

randomizer:
go build -o ./randomizer ./cmd/randomizer

gateway-conformance:
go build -o ./gateway-conformance ./cmd/gateway-conformance

Expand All @@ -37,4 +43,4 @@ output.html: output.xml
docker run --rm -v "${PWD}:/workspace" -w "/workspace" ghcr.io/pl-strflt/saxon:v1 -s:output.xml -xsl:/etc/junit-noframes-saxon.xsl -o:output.html
open ./output.html

.PHONY: gateway-conformance
.PHONY: gateway-conformance randomizer
84 changes: 84 additions & 0 deletions cmd/randomizer/main.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,84 @@
package main

import (
"flag"
"fmt"
"net/http"
"net/http/httputil"
"net/url"
"strings"
)

// list of all headers we want to mess with.
// It's a map from string to bool to make finding a header quick
type Messer func([]string)

var messedHeaders = map[string]Messer{
"Content-Type": swapRandomStrings,
"Content-Length": messRandomNumbers,
"Content-Encoding": swapRandomStrings,
"Content-Language": swapRandomStrings,
"Content-Location": swapRandomStrings,
"Content-MD5": swapRandomStrings,
"Content-Range": swapRandomStrings,
"Content-Disposition": swapRandomStrings,
"Content-Features": swapRandomStrings,
"Content-Security-Policy": swapRandomStrings,
"Cache-Control": swapRandomStrings,
"X-Ipfs-Path": swapRandomStrings,
"X-Ipfs-Roots": swapRandomStrings,
"X-Content-Type-Options": swapRandomStrings,
"Etag": swapRandomStrings,
"Location": swapRandomStrings,
"Accept-Ranges": swapRandomStrings,
"If-None-Match": swapRandomStrings,
}

var addedHeaders = map[string]string{
"Cache-Control": "no-cache",
}

func init() {
// go through all the headers and just switch to lowercase keys:
for k, v := range messedHeaders {
kk := strings.ToLower(k)
fmt.Println("Replacing:", k, "with:", kk)
delete(messedHeaders, k)
messedHeaders[kk] = v
}

// print all kv in messedHeaders
for k, v := range messedHeaders {
fmt.Println("messedHeaders:", k, v)
}
}

func main() {
// Parse command-line arguments for target URL and proxy address
var targetUrlStr, proxyAddr string
flag.StringVar(&targetUrlStr, "target", "", "Target URL to proxy requests to")
flag.StringVar(&proxyAddr, "proxy", "", "Address to listen for proxy requests")
flag.Parse()

if targetUrlStr == "" || proxyAddr == "" {
fmt.Println("Usage: randomizer -target <target-url> -proxy <proxy-addr>")
return
}

// Parse the target URL and create a reverse proxy
targetUrl, err := url.Parse(targetUrlStr)
if err != nil {
panic(err)
}
proxy := httputil.NewSingleHostReverseProxy(targetUrl)

// Modify the response headers and body
proxy.ModifyResponse = ResponseMesser

// Start the reverse proxy server on the given address
fmt.Printf("Listening for proxy requests on %s\n", proxyAddr)
err = http.ListenAndServe(proxyAddr, proxy)
if err != nil {
panic(err)
}
}
122 changes: 122 additions & 0 deletions cmd/randomizer/messer.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,122 @@
package main

import (
"fmt"
"math/rand"
"net/http"
"strconv"
"strings"
)

func ResponseMesser(resp *http.Response) error {
// Swap two random bytes in the response headers
for k, v := range resp.Header {
// ignore most headers
kk := strings.ToLower(k)
if _, ok := messedHeaders[kk]; !ok {
fmt.Println("could not find:", kk, "in messedHeaders")
continue
}

swapRandomStrings(v)
fmt.Println("messed:", k, v)
resp.Header[k] = v
}

// randomly add headers that do not exists
for k, v := range addedHeaders {
if rand.Intn(10) > 1 || resp.Header.Get(k) != "" {
continue
}

resp.Header[k] = []string{v}
}

// Swap two random bytes in the response body
length := -1
var err error

// if resp has content length header,
// extract the new length and store it.
if resp.Header.Get("Content-Length") != "" {
cl := resp.Header.Get("Content-Length")
length, err = strconv.Atoi(cl)
if err != nil {
panic(err)
}
}

swapRandomBytesReader := &swapRandomBytesReader{
Reader: resp.Body,
}

resp.Body = MyLimitReader(swapRandomBytesReader, int64(length))

resp.StatusCode = resp.StatusCode + 1

return nil
}

func randPlaces(max int) (int, int) {
i := rand.Intn(max)
j := rand.Intn(max)
if i == j {
j = (j + 1) % max
}

if i < j {
return i, j
}
return j, i
}

// Shuffle bytes
func shuffleBytes(b []byte) {
for i := range b {
j := rand.Intn(i + 1)
b[i], b[j] = b[j], b[i]
}
}

// Swaps two random bytes in the input
func swapRandomBytes(b []byte) {
if len(b) < 2 {
return
}

i, j := randPlaces(len(b))
b[i], b[j] = b[j], b[i]
}

// Swaps two random characters in the input string
func swapRandomStrings(s []string) {
for i := range s {
s[i] = swapRandomString(s[i])
}
}

func swapRandomString(s string) string {
if len(s) < 2 {
return s
}

i, j := randPlaces(len(s))

return s[:i] + string(s[j]) + s[i+1:j] + string(s[i]) + s[j+1:]
}

func messRandomNumbers(s []string) {
for i := range s {
s[i] = messRandomNumber(s[i])
}
}

func messRandomNumber(s string) string {
d, err := strconv.ParseInt(s, 10, 64)
if err != nil {
panic(err)
}
r := rand.Int63n(d)

return strconv.FormatInt(r, 10)
}
66 changes: 66 additions & 0 deletions cmd/randomizer/streams.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,66 @@
package main

import (
"fmt"
"io"
)

// A reader that swaps two random bytes in the input
type swapRandomBytesReader struct {
io.Reader
Length string
}

func (r *swapRandomBytesReader) Read(p []byte) (int, error) {
n, err := r.Reader.Read(p)

if err != nil && err != io.EOF {
return n, err
}

fmt.Println("before:", string(p[:n]))
shuffleBytes(p[:n])
fmt.Println("after:", string(p[:n]))
// swapRandomBytes(p[:n])
return n, err
}

func (r *swapRandomBytesReader) Close() error {
if closer, ok := r.Reader.(io.Closer); ok {
return closer.Close()
}
return nil
}

// LimitReader returns a Reader that reads from r
// but stops with EOF after n bytes.
// The underlying implementation is a *LimitedReader.
func MyLimitReader(r io.Reader, n int64) *MyLimitedReader { return &MyLimitedReader{r, n} }

// A LimitedReader reads from R but limits the amount of
// data returned to just N bytes. Each call to Read
// updates N to reflect the new amount remaining.
// Read returns EOF when N <= 0 or when the underlying R returns EOF.
type MyLimitedReader struct {
R io.Reader // underlying reader
N int64 // max bytes remaining
}

func (l *MyLimitedReader) Read(p []byte) (n int, err error) {
if l.N <= 0 {
return 0, io.EOF
}
if int64(len(p)) > l.N {
p = p[0:l.N]
}
n, err = l.R.Read(p)
l.N -= int64(n)
return
}

func (r *MyLimitedReader) Close() error {
if closer, ok := r.R.(io.Closer); ok {
return closer.Close()
}
return nil
}
23 changes: 0 additions & 23 deletions fixtures/t0123/dag-pb.json

This file was deleted.

48 changes: 48 additions & 0 deletions test.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
#! /usr/bin/env bash
set -x

make test-kubo; mv output.json output-kubo.json
cat output-kubo.json | jq --raw-output 'select(.Action == "run") | .Package + " - " + .Test' | sort > test-kubo.run

# if test-kubo.run is empty, something went wrong
if [ ! -s test-kubo.run ]; then
echo "test-kubo.run is empty"
exit 1
fi

make test-randomizer; mv output.json output-randomizer.json
cat output-randomizer.json | jq --raw-output 'select(.Action == "run") | .Package + " - " + .Test' | sort > test-randomizer.run
cat output-randomizer.json | jq --raw-output 'select(.Action == "pass") | .Package + " - " + .Test' | sort > test-randomizer.pass

# detect tests that are not running during randomizer
# if there is a difference, something went wrong
diff test-kubo.run test-randomizer.run

if [ $? -ne 0 ]; then
echo "test-kubo.run and test-randomizer.run are different"
exit 1
fi

# run randomizer test 5 times and detect the tests that are always passing:
cp test-randomizer.pass always_passing

for i in {1..3}; do
# if always_passing is empty -> we're done.
if [ ! -s always_passing ]; then
break
fi

make test-randomizer; mv output.json output-randomizer.json
cat output-randomizer.json | jq --raw-output 'select(.Action == "pass") | .Package + " - " + .Test' | sort > test-randomizer.pass
comm -12 always_passing test-randomizer.pass > temp.log
mv temp.log always_passing
done

# if always_passing is not empty, something went wrong:
if [ -s always_passing ]; then
echo "always_passing is not empty"
exit 1
else
echo "all tests failed at least once :clap:"
exit 0
fi
2 changes: 2 additions & 0 deletions tests/t0114_gateway_subdomains_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -371,6 +371,8 @@ func TestGatewaySubdomains(t *testing.T) {

if SubdomainGateway.IsEnabled() {
Run(t, tests)
} else {
t.Skip("Skipping subdomain gateway tests")
}
}

Expand Down
6 changes: 1 addition & 5 deletions tests/t0122_gateway_tar_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,12 +2,8 @@ package tests

import (
"testing"

"github.com/ipfs/gateway-conformance/tooling/test"
)

func TestGatewayTar(t *testing.T) {
tests := []test.CTest{}

test.Run(t, tests)
t.Skip()
}
Loading