package oonirun // // OONI Run v1 implementation // import ( "context" "encoding/json" "errors" "net/url" ) var ( // ErrInvalidV1URLScheme indicates a v1 OONI Run URL has an invalid scheme. ErrInvalidV1URLScheme = errors.New("oonirun: invalid v1 URL scheme") // ErrInvalidV1URLHost indicates a v1 OONI Run URL has an invalid host. ErrInvalidV1URLHost = errors.New("oonirun: invalid v1 URL host") // ErrInvalidV1URLPath indicates a v1 OONI Run URL has an invalid path. ErrInvalidV1URLPath = errors.New("oonirun: invalid v1 URL path") // ErrInvalidV1URLQueryArgument indicates a v1 OONI Run URL query argument is invalid. ErrInvalidV1URLQueryArgument = errors.New("oonirun: invalid v1 URL query argument") ) // v1Arguments contains arguments for a v1 OONI Run URL. These arguments are // always encoded inside of the "ta" field, which is optional. type v1Arguments struct { URLs []string `json:"urls"` } // v1Measure performs a measurement using the given v1 OONI Run URL. func v1Measure(ctx context.Context, config *LinkConfig, URL string) error { config.Session.Logger().Infof("oonirun/v1: running %s", URL) pu, err := url.Parse(URL) if err != nil { return err } switch pu.Scheme { case "https": if pu.Host != "run.ooni.io" { return ErrInvalidV1URLHost } if pu.Path != "/nettest" { return ErrInvalidV1URLPath } case "ooni": if pu.Host != "nettest" { return ErrInvalidV1URLHost } if pu.Path != "" { return ErrInvalidV1URLPath } default: return ErrInvalidV1URLScheme } name := pu.Query().Get("tn") if name == "" { return ErrInvalidV1URLQueryArgument } var inputs []string if ra := pu.Query().Get("ta"); ra != "" { pa, err := url.QueryUnescape(ra) if err != nil { return err } var arguments v1Arguments if err := json.Unmarshal([]byte(pa), &arguments); err != nil { return err } inputs = arguments.URLs } // TODO(bassosimone): reject mv < 1.2.0 exp := &Experiment{ Annotations: config.Annotations, ExtraOptions: nil, // no way to specify with v1 URLs Inputs: inputs, InputFilePaths: nil, MaxRuntime: config.MaxRuntime, Name: name, NoCollector: config.NoCollector, NoJSON: config.NoJSON, Random: config.Random, ReportFile: config.ReportFile, Session: config.Session, } return exp.Run(ctx) }