File speedtest-cli-1.0.11.obscpio of Package librespeed-cli

07070100000000000081A400000000000000000000000166E1605B00000A29000000000000000000000000000000000000002000000000speedtest-cli-1.0.11/.gitignore# Created by .ignore support plugin (hsz.mobi)
### Linux template
*~

# temporary files which can be created if a process still has a handle open of a deleted file
.fuse_hidden*

# KDE directory preferences
.directory

# Linux trash folder which might appear on any partition or disk
.Trash-*

# .nfs files are created when an open file is removed but is still being accessed
.nfs*

### macOS template
# General
.DS_Store
.AppleDouble
.LSOverride

# Icon must end with two \r
Icon

# Thumbnails
._*

# Files that might appear in the root of a volume
.DocumentRevisions-V100
.fseventsd
.Spotlight-V100
.TemporaryItems
.Trashes
.VolumeIcon.icns
.com.apple.timemachine.donotpresent

# Directories potentially created on remote AFP share
.AppleDB
.AppleDesktop
Network Trash Folder
Temporary Items
.apdisk

### JetBrains template
# Covers JetBrains IDEs: IntelliJ, RubyMine, PhpStorm, AppCode, PyCharm, CLion, Android Studio and WebStorm
# Reference: https://intellij-support.jetbrains.com/hc/en-us/articles/206544839

# User-specific stuff
.idea/**/workspace.xml
.idea/**/tasks.xml
.idea/**/usage.statistics.xml
.idea/**/dictionaries
.idea/**/shelf

# Generated files
.idea/**/contentModel.xml

# Sensitive or high-churn files
.idea/**/dataSources/
.idea/**/dataSources.ids
.idea/**/dataSources.local.xml
.idea/**/sqlDataSources.xml
.idea/**/dynamic.xml
.idea/**/uiDesigner.xml
.idea/**/dbnavigator.xml

# Gradle
.idea/**/gradle.xml
.idea/**/libraries

# Gradle and Maven with auto-import
# When using Gradle or Maven with auto-import, you should exclude module files,
# since they will be recreated, and may cause churn.  Uncomment if using
# auto-import.
# .idea/artifacts
# .idea/compiler.xml
# .idea/modules.xml
# .idea/*.iml
# .idea/modules
# *.iml
# *.ipr

# CMake
cmake-build-*/

# Mongo Explorer plugin
.idea/**/mongoSettings.xml

# File-based project format
*.iws

# IntelliJ
out/

# mpeltonen/sbt-idea plugin
.idea_modules/

# JIRA plugin
atlassian-ide-plugin.xml

# Cursive Clojure plugin
.idea/replstate.xml

# Crashlytics plugin (for Android Studio and IntelliJ)
com_crashlytics_export_strings.xml
crashlytics.properties
crashlytics-build.properties
fabric.properties

# Editor-based Rest Client
.idea/httpRequests

# Android studio 3.1+ serialized cache file
.idea/caches/build_file_checksums.ser

### Go template
# Binaries for programs and plugins
*.exe
*.exe~
*.dll
*.so
*.dylib

# Test binary, built with `go test -c`
*.test

# Output of the go coverage tool, specifically when used with LiteIDE
*.out

# Dependency directories (remove the comment below to include it)
# vendor/

dist/
07070100000001000081A400000000000000000000000166E1605B00000691000000000000000000000000000000000000002500000000speedtest-cli-1.0.11/.goreleaser.ymlversion: 2
project_name: "librespeed-cli"
#dist: ./out
before:
  hooks:
    - go mod download
builds:
  - main: ./main.go
    id: upx
    env:
      - CGO_ENABLED=0
    flags:
      - -trimpath
    ldflags:
      - -w -s -X "librespeed-cli/defs.ProgName={{ .ProjectName }}" -X "librespeed-cli/defs.ProgVersion=v{{ .Version }}" -X "librespeed-cli/defs.BuildDate={{ .Date }}"
    goos:
      - linux
      - darwin
      - freebsd
    goarch:
      - "386"
      - amd64
      - arm
      - arm64
    goarm:
      - "5"
      - "6"
      - "7"
    ignore:
      - goos: darwin
        goarch: "386"
      - goos: darwin
        goarch: arm64
    hooks:
      post:
        - ./upx.sh -9 "{{ .Path }}"
  - main: ./main.go
    id: no-upx
    env:
      - CGO_ENABLED=0
    flags:
      - -trimpath
    ldflags:
      - -w -s -X "librespeed-cli/defs.ProgName={{ .ProjectName }}" -X "librespeed-cli/defs.ProgVersion=v{{ .Version }}" -X "librespeed-cli/defs.BuildDate={{ .Date }}"
    goos:
      - linux
      - windows
      - darwin
    goarch:
      - "386"
      - amd64
      - arm64
      - mips
      - mipsle
      - mips64
      - mips64le
    gomips:
      - hardfloat
      - softfloat
    ignore:
      - goos: linux
        goarch: "386"
      - goos: linux
        goarch: amd64
      - goos: linux
        goarch: arm64
      - goos: darwin
        goarch: "386"
      - goos: darwin
        goarch: amd64
archives:
  - format_overrides:
      - goos: windows
        format: zip
    files:
      - LICENSE
checksum:
  name_template: "checksums.txt"
changelog:
  disable: false
  sort: asc
release:
  github:
    owner: librespeed
    name: speedtest-cli
  disable: false
07070100000002000081A400000000000000000000000166E1605B00001DE4000000000000000000000000000000000000001D00000000speedtest-cli-1.0.11/LICENSE                   GNU LESSER GENERAL PUBLIC LICENSE
                       Version 3, 29 June 2007

 Copyright (C) 2007 Free Software Foundation, Inc. <https://fsf.org/>
 Everyone is permitted to copy and distribute verbatim copies
 of this license document, but changing it is not allowed.


  This version of the GNU Lesser General Public License incorporates
the terms and conditions of version 3 of the GNU General Public
License, supplemented by the additional permissions listed below.

  0. Additional Definitions.

  As used herein, "this License" refers to version 3 of the GNU Lesser
General Public License, and the "GNU GPL" refers to version 3 of the GNU
General Public License.

  "The Library" refers to a covered work governed by this License,
other than an Application or a Combined Work as defined below.

  An "Application" is any work that makes use of an interface provided
by the Library, but which is not otherwise based on the Library.
Defining a subclass of a class defined by the Library is deemed a mode
of using an interface provided by the Library.

  A "Combined Work" is a work produced by combining or linking an
Application with the Library.  The particular version of the Library
with which the Combined Work was made is also called the "Linked
Version".

  The "Minimal Corresponding Source" for a Combined Work means the
Corresponding Source for the Combined Work, excluding any source code
for portions of the Combined Work that, considered in isolation, are
based on the Application, and not on the Linked Version.

  The "Corresponding Application Code" for a Combined Work means the
object code and/or source code for the Application, including any data
and utility programs needed for reproducing the Combined Work from the
Application, but excluding the System Libraries of the Combined Work.

  1. Exception to Section 3 of the GNU GPL.

  You may convey a covered work under sections 3 and 4 of this License
without being bound by section 3 of the GNU GPL.

  2. Conveying Modified Versions.

  If you modify a copy of the Library, and, in your modifications, a
facility refers to a function or data to be supplied by an Application
that uses the facility (other than as an argument passed when the
facility is invoked), then you may convey a copy of the modified
version:

   a) under this License, provided that you make a good faith effort to
   ensure that, in the event an Application does not supply the
   function or data, the facility still operates, and performs
   whatever part of its purpose remains meaningful, or

   b) under the GNU GPL, with none of the additional permissions of
   this License applicable to that copy.

  3. Object Code Incorporating Material from Library Header Files.

  The object code form of an Application may incorporate material from
a header file that is part of the Library.  You may convey such object
code under terms of your choice, provided that, if the incorporated
material is not limited to numerical parameters, data structure
layouts and accessors, or small macros, inline functions and templates
(ten or fewer lines in length), you do both of the following:

   a) Give prominent notice with each copy of the object code that the
   Library is used in it and that the Library and its use are
   covered by this License.

   b) Accompany the object code with a copy of the GNU GPL and this license
   document.

  4. Combined Works.

  You may convey a Combined Work under terms of your choice that,
taken together, effectively do not restrict modification of the
portions of the Library contained in the Combined Work and reverse
engineering for debugging such modifications, if you also do each of
the following:

   a) Give prominent notice with each copy of the Combined Work that
   the Library is used in it and that the Library and its use are
   covered by this License.

   b) Accompany the Combined Work with a copy of the GNU GPL and this license
   document.

   c) For a Combined Work that displays copyright notices during
   execution, include the copyright notice for the Library among
   these notices, as well as a reference directing the user to the
   copies of the GNU GPL and this license document.

   d) Do one of the following:

       0) Convey the Minimal Corresponding Source under the terms of this
       License, and the Corresponding Application Code in a form
       suitable for, and under terms that permit, the user to
       recombine or relink the Application with a modified version of
       the Linked Version to produce a modified Combined Work, in the
       manner specified by section 6 of the GNU GPL for conveying
       Corresponding Source.

       1) Use a suitable shared library mechanism for linking with the
       Library.  A suitable mechanism is one that (a) uses at run time
       a copy of the Library already present on the user's computer
       system, and (b) will operate properly with a modified version
       of the Library that is interface-compatible with the Linked
       Version.

   e) Provide Installation Information, but only if you would otherwise
   be required to provide such information under section 6 of the
   GNU GPL, and only to the extent that such information is
   necessary to install and execute a modified version of the
   Combined Work produced by recombining or relinking the
   Application with a modified version of the Linked Version. (If
   you use option 4d0, the Installation Information must accompany
   the Minimal Corresponding Source and Corresponding Application
   Code. If you use option 4d1, you must provide the Installation
   Information in the manner specified by section 6 of the GNU GPL
   for conveying Corresponding Source.)

  5. Combined Libraries.

  You may place library facilities that are a work based on the
Library side by side in a single library together with other library
facilities that are not Applications and are not covered by this
License, and convey such a combined library under terms of your
choice, if you do both of the following:

   a) Accompany the combined library with a copy of the same work based
   on the Library, uncombined with any other library facilities,
   conveyed under the terms of this License.

   b) Give prominent notice with the combined library that part of it
   is a work based on the Library, and explaining where to find the
   accompanying uncombined form of the same work.

  6. Revised Versions of the GNU Lesser General Public License.

  The Free Software Foundation may publish revised and/or new versions
of the GNU Lesser General Public License from time to time. Such new
versions will be similar in spirit to the present version, but may
differ in detail to address new problems or concerns.

  Each version is given a distinguishing version number. If the
Library as you received it specifies that a certain numbered version
of the GNU Lesser General Public License "or any later version"
applies to it, you have the option of following the terms and
conditions either of that published version or of any later version
published by the Free Software Foundation. If the Library as you
received it does not specify a version number of the GNU Lesser
General Public License, you may choose any version of the GNU Lesser
General Public License ever published by the Free Software Foundation.

  If the Library as you received it specifies that a proxy can decide
whether future versions of the GNU Lesser General Public License shall
apply, that proxy's public statement of acceptance of any version is
permanent authorization for you to choose that version for the
Library.
07070100000003000081A400000000000000000000000166E1605B00002F86000000000000000000000000000000000000001F00000000speedtest-cli-1.0.11/README.md![LibreSpeed Logo](https://github.com/librespeed/speedtest/blob/master/.logo/logo3.png?raw=true)

# LibreSpeed command line tool
Don't have a GUI but want to use LibreSpeed servers to test your Internet speed? 🚀

`librespeed-cli` comes to rescue!

This is a command line interface for LibreSpeed speed test backends, written in Go.

## Features
- Ping
- Jitter
- Download
- Upload
- IP address
- ISP Information
- Result sharing (telemetry) *[optional]*
- Test with multiple servers in a single run
- Use your own server list or telemetry endpoints
- Tested with PHP and Go backends

[![asciicast](https://asciinema.org/a/J17bUAilWI3qR12JyhfGvPwu2.svg)](https://asciinema.org/a/J17bUAilWI3qR12JyhfGvPwu2)

## Requirements for compiling
- Go 1.14+

## Runtime requirements
- Any [Go supported platforms](https://github.com/golang/go/wiki/MinimumRequirements)

## Use prebuilt binaries

If you don't want to build `librespeed-cli` yourself, you can find different binaries compiled for various platforms in
the [releases page](https://github.com/librespeed/speedtest-cli/releases).

## Building `librespeed-cli`

1. First, you'll have to install Go (at least version 1.11). For Windows users, [you can download an installer from golang.org](https://golang.org/dl/).
For Linux users, you can use either the archive from golang.org, or install from your distribution's package manager.

    For example, Arch Linux:

    ```shell script
    # pacman -S go
    ```

2. Then, clone the repository:

    ```shell script
    $ git clone -b v1.0.0 https://github.com/librespeed/speedtest-cli
    ```

3. After you have Go installed on your system (and added to your `$PATH` if you're using the archive from golang.org), you
can now proceed to build `librespeed-cli` with the build script:

    ```shell script
    $ cd speedtest-cli
    $ ./build.sh
    ```

    If you want to build for another operating system or system architecture, use the `GOOS` and `GOARCH` environment
    variables. Run `go tool dist list` to get a list of possible combinations of `GOOS` and `GOARCH`.

    Note: Technically, the CLI can be compiled with older Go versions that support Go modules, with `GO111MODULE=on`
    set. If you're compiling with an older Go runtime, you might have to change the Go version in `go.mod`.

    ```shell script
    # Let's say we're building for 64-bit Windows on Linux
    $ GOOS=windows GOARCH=amd64 ./build.sh
    ```

4. When the build script finishes, if everything went smoothly, you can find the built binary under directory `out`.

    ```shell script
    $ ls out
    librespeed-cli-windows-amd64.exe
    ```

5. Now you can use the `librespeed-cli` and test your Internet speed!

## Install from AUR

To install `librespeed-cli` from AUR, use your favorite AUR helper and install package `librespeed-cli-bin`

```shell script
$ yay librespeed-cli-bin
```

... or, clone it and build it yourself:

```shell script
$ git clone https://aur.archlinux.org/librespeed-cli-bin.git
$ cd librespeed-cli-bin
$ makepkg -si
```

## Install from Homebrew

See the [librespeed-cli Homebrew tap](https://github.com/librespeed/homebrew-tap#setup).

## Install on Windows

If you have either [Scoop](https://scoop.sh/) or [Chocolatey](https://chocolatey.org/) installed you can use one of the following commands:

- Scoop (ensure you have the `extras` bucket added):
  ```
  > scoop install librespeed-cli
  ```

- Chocolatey:
  ```
  > choco install librespeed-cli
  ```

## Container Image

You can run `librespeed-cli` in a container.

1. Build the container image:

    ```shell script
    docker build -t librespeed-cli:latest .
    ```

2. Run the container:

    ```shell script
    docker run --rm --name librespeed-cli librespeed-cli:latest
    # With options
    docker run --rm --name librespeed-cli librespeed-cli:latest --telemetry-level disabled --no-upload
    # To avoid "Failed to ping target host: socket: permission denied" errors when using --verbose
    docker run --rm --name librespeed-cli --sysctl net.ipv4.ping_group_range="0 2147483647" librespeed-cli:latest --verbose
    ```

## Usage

You can see the full list of supported options with `librespeed-cli -h`:

```
$ librespeed-cli -h
NAME:
   librespeed-cli - Test your Internet speed with LibreSpeed 🚀

USAGE:
   librespeed-cli [global options] [arguments...]

GLOBAL OPTIONS:
   --help, -h                     show help (default: false)
   --version                      Show the version number and exit (default: false)
   --ipv4, -4                     Force IPv4 only (default: false)
   --ipv6, -6                     Force IPv6 only (default: false)
   --no-download                  Do not perform download test (default: false)
   --no-upload                    Do not perform upload test (default: false)
   --no-icmp                      Do not use ICMP ping. ICMP doesn't work well under Linux
                                  at this moment, so you might want to disable it (default: false)
   --concurrent value             Concurrent HTTP requests being made (default: 3)
   --bytes                        Display values in bytes instead of bits. Does not affect
                                  the image generated by --share, nor output from
                                  --json or --csv (default: false)
   --mebibytes                    Use 1024 bytes as 1 kilobyte instead of 1000 (default: false)
   --distance value               Change distance unit shown in ISP info, use 'mi' for miles,
                                  'km' for kilometres, 'NM' for nautical miles (default: "km")
   --share                        Generate and provide a URL to the LibreSpeed.org share results
                                  image, not displayed with --csv (default: false)
   --simple                       Suppress verbose output, only show basic information
                                  (default: false)
   --csv                          Suppress verbose output, only show basic information in CSV
                                  format. Speeds listed in bit/s and not affected by --bytes
                                  (default: false)
   --csv-delimiter CSV_DELIMITER  Single character delimiter (CSV_DELIMITER) to use in
                                  CSV output. (default: ",")
   --csv-header                   Print CSV headers (default: false)
   --json                         Suppress verbose output, only show basic information
                                  in JSON format. Speeds listed in bit/s and not
                                  affected by --bytes (default: false)
   --list                         Display a list of LibreSpeed.org servers (default: false)
   --server SERVER                Specify a SERVER ID to test against. Can be supplied
                                  multiple times. Cannot be used with --exclude
   --exclude EXCLUDE              EXCLUDE a server from selection. Can be supplied
                                  multiple times. Cannot be used with --server
   --server-json value            Use an alternative server list from remote JSON file
   --local-json value             Use an alternative server list from local JSON file,
                                  or read from stdin with "--local-json -".
   --source SOURCE                SOURCE IP address to bind to. Incompatible with --interface.
   --interface INTERFACE          The name of the network interface to bind to. Example: "enp0s3".
                                  Not supported on Windows and incompatible with --source.
                                  Implies --no-icmp.
   --timeout TIMEOUT              HTTP TIMEOUT in seconds. (default: 15)
   --duration value               Upload and download test duration in seconds (default: 15)
   --chunks value                 Chunks to download from server, chunk size depends on server configuration (default: 100)
   --upload-size value            Size of payload being uploaded in KiB (default: 1024)
   --secure                       Use HTTPS instead of HTTP when communicating with
                                  LibreSpeed.org operated servers (default: false)
   --ca-cert value                Use the specified CA certificate PEM bundle file instead
                                  of the system certificate trust store
   --skip-cert-verify             Skip verifying SSL certificate for HTTPS connections (self-signed certs) (default: false)
   --no-pre-allocate              Do not pre allocate upload data. Pre allocation is
                                  enabled by default to improve upload performance. To
                                  support systems with insufficient memory, use this
                                  option to avoid out of memory errors (default: false)
   --telemetry-json value         Load telemetry server settings from a JSON file. This
                                  options overrides --telemetry-level, --telemetry-server,
                                  --telemetry-path, and --telemetry-share. Implies --share
   --telemetry-level value        Set telemetry data verbosity, available values are:
                                  disabled, basic, full, debug. Implies --share
   --telemetry-server value       Set the telemetry server base URL. Implies --share
   --telemetry-path value         Set the telemetry upload path. Implies --share
   --telemetry-share value        Set the telemetry share link path. Implies --share
   --telemetry-extra value        Send a custom message along with the telemetry results.
                                  Implies --share
```

## Use a custom backend server list
The `librespeed-cli` supports loading custom backend server list from a JSON file (remotely via `--server-json` or
locally via `--local-json`). The format is as below:

```json
[
  {
    "id": 1,
    "name": "PHP Backend",
    "server": "https://example.com/",
    "dlURL": "garbage.php",
    "ulURL": "empty.php",
    "pingURL": "empty.php",
    "getIpURL": "getIP.php"
  },
  {
    "id": 2,
    "name": "Go Backend",
    "server": "http://example.com/speedtest/",
    "dlURL": "garbage",
    "ulURL": "empty",
    "pingURL": "empty",
    "getIpURL": "getIP"
  }
]
```

The `--local-json` option can also read from `stdin`:

`echo '[{"id": 1,"name": "a","server": "https://speedtest.example.com/","dlURL": "garbage.php","ulURL": "empty.php","pingURL": "empty.php","getIpURL": "getIP.php"}]' | librespeed-cli --local-json - `

As you can see in the example, all servers have their schemes defined. In case of undefined scheme (e.g. `//example.com`),
`librespeed-cli` will use `http` by default, or `https` when the `--secure` option is enabled.

## Use a custom telemetry server
By default, the telemetry result will be sent to `librespeed.org`. You can also customize your telemetry settings
via the `--telemetry` prefixed options. In order to load a custom telemetry endpoint configuration, you'll have to use the
`--telemetry-json` option to specify a local JSON file containing the configuration bits. The format is as below:

```json
{
  "telemetryLevel": "full",
  "server": "https://example.com",
  "path": "/results/telemetry.php",
  "shareURL": "/results/"
}
```

For `telemetryLevel`, four values are available:

- `disabled` to disable telemetry
- `basic` to enable telemetry with result only)
- `full` to enable telemetry with result and timing
- `debug` to enable the most verbose telemetry information

`server` defines the base URL for the backend's endpoints. `path` is the path for uploading the telemetry result, and
`shareURL` is the path for fetching the uploaded result in PNG format.

Currently, `--telemetry-json` only supports loading a local JSON file.

## Bugs?

Although we have tested the cli, it's still in its early days. Please open an issue if you encounter any bugs, or even
better, submit a PR.

## How to contribute

If you have some good ideas on improving `librespeed-cli`, you can always submit a PR via GitHub.

## License

`librespeed-cli` is licensed under [GNU Lesser General Public License v3.0](https://github.com/librespeed/speedtest-cli/blob/master/LICENSE)
07070100000004000081ED00000000000000000000000166E1605B00000441000000000000000000000000000000000000001E00000000speedtest-cli-1.0.11/build.sh#!/usr/bin/env bash

if [[ -z "$1" ]]; then
  PROGVER="$(git describe --tag)"
else
  PROGVER="$1"
fi

CURRENT_DIR=$(pwd)
OUT_DIR=${CURRENT_DIR}/out

PROGNAME="librespeed-cli"
DEFS_PATH="github.com/librespeed/speedtest-cli"
BINARY=${PROGNAME}-$(go env GOOS)-$(go env GOARCH)
BUILD_DATE=$(date -u "+%Y-%m-%d %H:%M:%S %Z")
LDFLAGS="-w -s -X \"${DEFS_PATH}/defs.ProgName=${PROGNAME}\" -X \"${DEFS_PATH}/defs.ProgVersion=${PROGVER}\" -X \"${DEFS_PATH}/defs.BuildDate=${BUILD_DATE}\""

if [[ -n "${GOARM}" ]] && [[ "${GOARM}" -gt 0 ]]; then
  BINARY=${BINARY}v${GOARM}
fi

if [[ -n "${GOMIPS}" ]]; then
  BINARY=${BINARY}-${GOMIPS}
fi

if [[ -n "${GOMIPS64}" ]]; then
  BINARY=${BINARY}-${GOMIPS64}
fi

if [[ "$(go env GOOS)" = "windows" ]]; then
  BINARY=${BINARY}.exe
fi

if [[ ! -d ${OUT_DIR} ]]; then
  mkdir "${OUT_DIR}"
fi

if [[ -e ${OUT_DIR}/${BINARY} ]]; then
  rm -f "${OUT_DIR}/${BINARY}"
fi

go build -o "${OUT_DIR}/${BINARY}" -ldflags "${LDFLAGS}" -trimpath main.go

if [[ ! $(go env GOARCH) == mips64* ]] && [[ -x $(command -v upx) ]]; then
  upx -qqq -9 "${OUT_DIR}/${BINARY}"
fi
07070100000005000041ED00000000000000000000000266E1605B00000000000000000000000000000000000000000000001A00000000speedtest-cli-1.0.11/defs07070100000006000081A400000000000000000000000166E1605B00000DBA000000000000000000000000000000000000002B00000000speedtest-cli-1.0.11/defs/bytes_counter.gopackage defs

import (
	"bytes"
	"crypto/rand"
	"fmt"
	"io"
	"log"
	"sync"
	"time"
)

// BytesCounter implements io.Reader and io.Writer interface, for counting bytes being read/written in HTTP requests
type BytesCounter struct {
	start      time.Time
	pos        int
	total      uint64
	payload    []byte
	reader     io.ReadSeeker
	mebi       bool
	uploadSize int

	lock *sync.Mutex
}

func NewCounter() *BytesCounter {
	return &BytesCounter{
		lock: &sync.Mutex{},
	}
}

// Write implements io.Writer
func (c *BytesCounter) Write(p []byte) (int, error) {
	n := len(p)
	c.lock.Lock()
	c.total += uint64(n)
	c.lock.Unlock()

	return n, nil
}

// Read implements io.Reader
func (c *BytesCounter) Read(p []byte) (int, error) {
	n, err := c.reader.Read(p)
	c.lock.Lock()
	c.total += uint64(n)
	c.pos += n
	if c.pos == c.uploadSize {
		c.resetReader()
	}
	c.lock.Unlock()

	return n, err
}

// SetBase sets the base for dividing bytes into megabyte or mebibyte
func (c *BytesCounter) SetMebi(mebi bool) {
	c.mebi = mebi
}

// SetUploadSize sets the size of payload being uploaded
func (c *BytesCounter) SetUploadSize(uploadSize int) {
	c.uploadSize = uploadSize * 1024
}

// AvgBytes returns the average bytes/second
func (c *BytesCounter) AvgBytes() float64 {
	return float64(c.total) / time.Now().Sub(c.start).Seconds()
}

// AvgMbps returns the average mbits/second
func (c *BytesCounter) AvgMbps() float64 {
	var base float64 = 125000
	if c.mebi {
		base = 131072
	}
	return c.AvgBytes() / base
}

// AvgHumanize returns the average bytes/kilobytes/megabytes/gigabytes (or bytes/kibibytes/mebibytes/gibibytes) per second
func (c *BytesCounter) AvgHumanize() string {
	val := c.AvgBytes()

	var base float64 = 1000
	if c.mebi {
		base = 1024
	}

	if val < base {
		return fmt.Sprintf("%.2f bytes/s", val)
	} else if val/base < base {
		return fmt.Sprintf("%.2f KB/s", val/base)
	} else if val/base/base < base {
		return fmt.Sprintf("%.2f MB/s", val/base/base)
	} else {
		return fmt.Sprintf("%.2f GB/s", val/base/base/base)
	}
}

// GenerateBlob generates a random byte array of `uploadSize` in the `payload` field, and sets the `reader` field to
// read from it
func (c *BytesCounter) GenerateBlob() {
	c.payload = getRandomData(c.uploadSize)
	c.reader = bytes.NewReader(c.payload)
}

// resetReader resets the `reader` field to 0 position
func (c *BytesCounter) resetReader() (int64, error) {
	c.pos = 0
	return c.reader.Seek(0, 0)
}

// Start will set the `start` field to current time
func (c *BytesCounter) Start() {
	c.start = time.Now()
}

// Total returns the total bytes read/written
func (c *BytesCounter) Total() uint64 {
	return c.total
}

// CurrentSpeed returns the current bytes/second
func (c *BytesCounter) CurrentSpeed() float64 {
	return float64(c.total) / time.Now().Sub(c.start).Seconds()
}

// SeekWrapper is a wrapper around io.Reader to give it a noop io.Seeker interface
type SeekWrapper struct {
	io.Reader
}

// Seek implements the io.Seeker interface
func (r *SeekWrapper) Seek(offset int64, whence int) (int64, error) {
	return 0, nil
}

// getAvg returns the average value of an float64 array
func getAvg(vals []float64) float64 {
	var total float64
	for _, v := range vals {
		total += v
	}

	return total / float64(len(vals))
}

// getRandomData returns an `length` sized array of random bytes
func getRandomData(length int) []byte {
	data := make([]byte, length)
	if _, err := rand.Read(data); err != nil {
		log.Fatalf("Failed to generate random data: %s", err)
	}
	return data
}
07070100000007000081A400000000000000000000000166E1605B00000362000000000000000000000000000000000000002200000000speedtest-cli-1.0.11/defs/defs.gopackage defs

var (
	// values to be filled in by build script
	BuildDate   string
	ProgName    string
	ProgVersion string
	UserAgent   = ProgName + "/" + ProgVersion
)

// GetIPResults represents the returned JSON from backend server's getIP.php endpoint
type GetIPResult struct {
	ProcessedString string         `json:"processedString"`
	RawISPInfo      IPInfoResponse `json:"rawIspInfo"`
}

// IPInfoResponse represents the returned JSON from IPInfo.io's API
type IPInfoResponse struct {
	IP           string `json:"ip"`
	Hostname     string `json:"hostname"`
	City         string `json:"city"`
	Region       string `json:"region"`
	Country      string `json:"country"`
	Location     string `json:"loc"`
	Organization string `json:"org"`
	Postal       string `json:"postal"`
	Timezone     string `json:"timezone"`
	Readme       string `json:"readme,omitempty"`
}
07070100000008000081A400000000000000000000000166E1605B00000147000000000000000000000000000000000000002B00000000speedtest-cli-1.0.11/defs/log_formatter.gopackage defs

import (
	"fmt"

	log "github.com/sirupsen/logrus"
)

// NoFormatter is the formatter for logrus
type NoFormatter struct{}

// Format prints the log message without timestamp/log level etc.
func (f *NoFormatter) Format(entry *log.Entry) ([]byte, error) {
	return []byte(fmt.Sprintf("%s\n", entry.Message)), nil
}
07070100000009000081A400000000000000000000000166E1605B000005EC000000000000000000000000000000000000002500000000speedtest-cli-1.0.11/defs/options.gopackage defs

const (
	OptionHelp            = "help"
	OptionIPv4            = "ipv4"
	OptionIPv4Alt         = "4"
	OptionIPv6            = "ipv6"
	OptionIPv6Alt         = "6"
	OptionNoDownload      = "no-download"
	OptionNoUpload        = "no-upload"
	OptionNoICMP          = "no-icmp"
	OptionConcurrent      = "concurrent"
	OptionBytes           = "bytes"
	OptionMebiBytes       = "mebibytes"
	OptionDistance        = "distance"
	OptionShare           = "share"
	OptionSimple          = "simple"
	OptionCSV             = "csv"
	OptionCSVDelimiter    = "csv-delimiter"
	OptionCSVHeader       = "csv-header"
	OptionJSON            = "json"
	OptionList            = "list"
	OptionServer          = "server"
	OptionExclude         = "exclude"
	OptionServerJSON      = "server-json"
	OptionSource          = "source"
	OptionInterface       = "interface"
	OptionTimeout         = "timeout"
	OptionChunks          = "chunks"
	OptionUploadSize      = "upload-size"
	OptionDuration        = "duration"
	OptionSecure          = "secure"
	OptionCACert          = "ca-cert"
	OptionSkipCertVerify  = "skip-cert-verify"
	OptionNoPreAllocate   = "no-pre-allocate"
	OptionVersion         = "version"
	OptionLocalJSON       = "local-json"
	OptionDebug           = "debug"
	OptionTelemetryJSON   = "telemetry-json"
	OptionTelemetryLevel  = "telemetry-level"
	OptionTelemetryServer = "telemetry-server"
	OptionTelemetryPath   = "telemetry-path"
	OptionTelemetryShare  = "telemetry-share"
	OptionTelemetryExtra  = "telemetry-extra"
)
0707010000000A000081A400000000000000000000000166E1605B00002C02000000000000000000000000000000000000002400000000speedtest-cli-1.0.11/defs/server.gopackage defs

import (
	"context"
	"crypto/rand"
	"encoding/json"
	"errors"
	"fmt"
	"io"
	"io/ioutil"
	"math"
	"net/http"
	"net/url"
	"path"
	"strconv"
	"time"

	"github.com/briandowns/spinner"
	"github.com/go-ping/ping"
	log "github.com/sirupsen/logrus"
)

// Server represents a speed test server
type Server struct {
	ID          int    `json:"id"`
	Name        string `json:"name"`
	Server      string `json:"server"`
	DownloadURL string `json:"dlURL"`
	UploadURL   string `json:"ulURL"`
	PingURL     string `json:"pingURL"`
	GetIPURL    string `json:"getIpURL"`
	SponsorName string `json:"sponsorName"`
	SponsorURL  string `json:"sponsorURL"`

	NoICMP bool         `json:"-"`
	TLog   TelemetryLog `json:"-"`
}

// IsUp checks the speed test backend is up by accessing the ping URL
func (s *Server) IsUp() bool {
	t := time.Now()
	defer func() {
		s.TLog.Logf("Check backend is up took %s", time.Now().Sub(t).String())
	}()

	u, _ := s.GetURL()
	u.Path = path.Join(u.Path, s.PingURL)

	req, err := http.NewRequest(http.MethodGet, u.String(), nil)
	if err != nil {
		log.Debugf("Failed when creating HTTP request: %s", err)
		return false
	}
	req.Header.Set("User-Agent", UserAgent)

	resp, err := http.DefaultClient.Do(req)
	if err != nil {
		log.Debugf("Error checking for server status: %s", err)
		return false
	}
	defer resp.Body.Close()
	b, _ := ioutil.ReadAll(resp.Body)
	if len(b) > 0 {
		log.Debugf("Failed when parsing get IP result: %s", b)
	}
	// only return online if the ping URL returns nothing and 200
	return resp.StatusCode == http.StatusOK
}

// ICMPPingAndJitter pings the server via ICMP echos and calculate the average ping and jitter
func (s *Server) ICMPPingAndJitter(count int, srcIp, network string) (float64, float64, error) {
	t := time.Now()
	defer func() {
		s.TLog.Logf("ICMP ping took %s", time.Now().Sub(t).String())
	}()

	if s.NoICMP {
		log.Debugf("Skipping ICMP for server %s, will use HTTP ping", s.Name)
		return s.PingAndJitter(count + 2)
	}

	u, err := s.GetURL()
	if err != nil {
		log.Debugf("Failed to get server URL: %s", err)
		return 0, 0, err
	}

	p := ping.New(u.Hostname())
	p.SetNetwork(network)
	p.Count = count
	p.Timeout = time.Duration(count) * time.Second
	if srcIp != "" {
		p.Source = srcIp
	}
	if log.GetLevel() == log.DebugLevel {
		p.Debug = true
	}
	if err := p.Run(); err != nil {
		log.Debugf("Failed to ping target host: %s", err)
		log.Debug("Will try TCP ping")
		return s.PingAndJitter(count + 2)
	}

	stats := p.Statistics()

	var lastPing, jitter float64
	for idx, rtt := range stats.Rtts {
		if idx != 0 {
			instJitter := math.Abs(lastPing - float64(rtt.Milliseconds()))
			if idx > 1 {
				if jitter > instJitter {
					jitter = jitter*0.7 + instJitter*0.3
				} else {
					jitter = instJitter*0.2 + jitter*0.8
				}
			}
		}
		lastPing = float64(rtt.Milliseconds())
	}

	if len(stats.Rtts) == 0 {
		s.NoICMP = true
		log.Debugf("No ICMP pings returned for server %s (%s), trying TCP ping", s.Name, u.Hostname())
		return s.PingAndJitter(count + 2)
	}

	return float64(stats.AvgRtt.Milliseconds()), jitter, nil
}

// PingAndJitter pings the server via accessing ping URL and calculate the average ping and jitter
func (s *Server) PingAndJitter(count int) (float64, float64, error) {
	t := time.Now()
	defer func() {
		s.TLog.Logf("TCP ping took %s", time.Now().Sub(t).String())
	}()

	u, err := s.GetURL()
	if err != nil {
		log.Debugf("Failed to get server URL: %s", err)
		return 0, 0, err
	}
	u.Path = path.Join(u.Path, s.PingURL)

	var pings []float64

	req, err := http.NewRequest(http.MethodGet, u.String(), nil)
	if err != nil {
		log.Debugf("Failed when creating HTTP request: %s", err)
		return 0, 0, err
	}
	req.Header.Set("User-Agent", UserAgent)

	for i := 0; i < count; i++ {
		start := time.Now()
		resp, err := http.DefaultClient.Do(req)
		if err != nil {
			log.Debugf("Failed when making HTTP request: %s", err)
			return 0, 0, err
		}
		io.Copy(ioutil.Discard, resp.Body)
		resp.Body.Close()
		end := time.Now()

		pings = append(pings, float64(end.Sub(start).Milliseconds()))
	}

	// discard first result due to handshake overhead
	if len(pings) > 1 {
		pings = pings[1:]
	}

	var lastPing, jitter float64
	for idx, p := range pings {
		if idx != 0 {
			instJitter := math.Abs(lastPing - p)
			if idx > 1 {
				if jitter > instJitter {
					jitter = jitter*0.7 + instJitter*0.3
				} else {
					jitter = instJitter*0.2 + jitter*0.8
				}
			}
		}
		lastPing = p
	}

	return getAvg(pings), jitter, nil
}

// Download performs the actual download test
func (s *Server) Download(silent bool, useBytes, useMebi bool, requests int, chunks int, duration time.Duration) (float64, uint64, error) {
	t := time.Now()
	defer func() {
		s.TLog.Logf("Download took %s", time.Now().Sub(t).String())
	}()

	counter := NewCounter()
	counter.SetMebi(useMebi)

	ctx, cancel := context.WithCancel(context.Background())
	defer cancel()

	u, err := s.GetURL()
	if err != nil {
		log.Debugf("Failed to get server URL: %s", err)
		return 0, 0, err
	}

	u.Path = path.Join(u.Path, s.DownloadURL)
	req, err := http.NewRequestWithContext(ctx, http.MethodGet, u.String(), nil)
	if err != nil {
		log.Debugf("Failed when creating HTTP request: %s", err)
		return 0, 0, err
	}
	q := req.URL.Query()
	q.Set("ckSize", strconv.Itoa(chunks))
	req.URL.RawQuery = q.Encode()
	req.Header.Set("User-Agent", UserAgent)
	req.Header.Set("Accept-Encoding", "identity")

	downloadDone := make(chan struct{}, requests)

	doDownload := func() {
		resp, err := http.DefaultClient.Do(req)
		if err != nil {
			log.Debugf("Failed when making HTTP request: %s", err)
		} else {
			defer resp.Body.Close()

			if _, err = io.Copy(ioutil.Discard, io.TeeReader(resp.Body, counter)); err != nil {
				if !errors.Is(err, context.Canceled) && !errors.Is(err, context.DeadlineExceeded) {
					log.Debugf("Failed when reading HTTP response: %s", err)
				}
			}

			downloadDone <- struct{}{}
		}
	}

	counter.Start()
	if !silent {
		pb := spinner.New(spinner.CharSets[11], 100*time.Millisecond)
		pb.Prefix = "Downloading...  "
		pb.PostUpdate = func(s *spinner.Spinner) {
			if useBytes {
				s.Suffix = fmt.Sprintf("  %s", counter.AvgHumanize())
			} else {
				s.Suffix = fmt.Sprintf("  %.2f Mbps", counter.AvgMbps())
			}
		}

		pb.Start()
		defer func() {
			if useBytes {
				pb.FinalMSG = fmt.Sprintf("Download rate:\t%s\n", counter.AvgHumanize())
			} else {
				pb.FinalMSG = fmt.Sprintf("Download rate:\t%.2f Mbps\n", counter.AvgMbps())
			}
			pb.Stop()
		}()
	}

	for i := 0; i < requests; i++ {
		go doDownload()
		time.Sleep(200 * time.Millisecond)
	}
	timeout := time.After(duration)
Loop:
	for {
		select {
		case <-timeout:
			ctx.Done()
			break Loop
		case <-downloadDone:
			go doDownload()
		}
	}

	return counter.AvgMbps(), counter.Total(), nil
}

// Upload performs the actual upload test
func (s *Server) Upload(noPrealloc, silent, useBytes, useMebi bool, requests int, uploadSize int, duration time.Duration) (float64, uint64, error) {
	t := time.Now()
	defer func() {
		s.TLog.Logf("Upload took %s", time.Now().Sub(t).String())
	}()

	counter := NewCounter()
	counter.SetMebi(useMebi)
	counter.SetUploadSize(uploadSize)

	if noPrealloc {
		log.Info("Pre-allocation is disabled, performance might be lower!")
		counter.reader = &SeekWrapper{rand.Reader}
	} else {
		counter.GenerateBlob()
	}

	u, err := s.GetURL()
	if err != nil {
		log.Debugf("Failed to get server URL: %s", err)
		return 0, 0, err
	}

	ctx, cancel := context.WithCancel(context.Background())
	defer cancel()
	u.Path = path.Join(u.Path, s.UploadURL)
	req, err := http.NewRequestWithContext(ctx, http.MethodPost, u.String(), counter)
	if err != nil {
		log.Debugf("Failed when creating HTTP request: %s", err)
		return 0, 0, err
	}
	req.Header.Set("User-Agent", UserAgent)
	req.Header.Set("Accept-Encoding", "identity")

	uploadDone := make(chan struct{}, requests)

	doUpload := func() {
		resp, err := http.DefaultClient.Do(req)
		if err != nil && !errors.Is(err, context.Canceled) && !errors.Is(err, context.DeadlineExceeded) {
			log.Debugf("Failed when making HTTP request: %s", err)
		} else if err == nil {
			defer resp.Body.Close()
			if _, err := io.Copy(ioutil.Discard, resp.Body); err != nil {
				log.Debugf("Failed when reading HTTP response: %s", err)
			}

			uploadDone <- struct{}{}
		}
	}

	counter.Start()
	if !silent {
		pb := spinner.New(spinner.CharSets[11], 100*time.Millisecond)
		pb.Prefix = "Uploading...  "
		pb.PostUpdate = func(s *spinner.Spinner) {
			if useBytes {
				s.Suffix = fmt.Sprintf("  %s", counter.AvgHumanize())
			} else {
				s.Suffix = fmt.Sprintf("  %.2f Mbps", counter.AvgMbps())
			}
		}

		pb.Start()
		defer func() {
			if useBytes {
				pb.FinalMSG = fmt.Sprintf("Upload rate:\t%s\n", counter.AvgHumanize())
			} else {
				pb.FinalMSG = fmt.Sprintf("Upload rate:\t%.2f Mbps\n", counter.AvgMbps())
			}
			pb.Stop()
		}()
	}

	for i := 0; i < requests; i++ {
		go doUpload()
		time.Sleep(200 * time.Millisecond)
	}
	timeout := time.After(duration)
Loop:
	for {
		select {
		case <-timeout:
			ctx.Done()
			break Loop
		case <-uploadDone:
			go doUpload()
		}
	}

	return counter.AvgMbps(), counter.Total(), nil
}

// GetIPInfo accesses the backend's getIP.php endpoint and get current client's IP information
func (s *Server) GetIPInfo(distanceUnit string) (*GetIPResult, error) {
	t := time.Now()
	defer func() {
		s.TLog.Logf("Get IP info took %s", time.Now().Sub(t).String())
	}()

	var ipInfo GetIPResult
	u, err := s.GetURL()
	if err != nil {
		log.Debugf("Failed to get server URL: %s", err)
		return nil, err
	}
	u.Path = path.Join(u.Path, s.GetIPURL)
	q := u.Query()
	q.Set("isp", "true")
	q.Set("distance", distanceUnit)
	u.RawQuery = q.Encode()

	req, err := http.NewRequest(http.MethodGet, u.String(), nil)
	if err != nil {
		log.Debugf("Failed when creating HTTP request: %s", err)
		return nil, err
	}
	req.Header.Set("User-Agent", UserAgent)

	resp, err := http.DefaultClient.Do(req)
	if err != nil {
		log.Debugf("Failed when making HTTP request: %s", err)
		return nil, err
	}
	defer resp.Body.Close()

	b, err := ioutil.ReadAll(resp.Body)
	if err != nil {
		log.Debugf("Failed when reading HTTP response: %s", err)
		return nil, err
	}

	if len(b) > 0 {
		if err := json.Unmarshal(b, &ipInfo); err != nil {
			log.Debugf("Failed when parsing get IP result: %s", err)
			log.Debugf("Received payload: %s", b)
			ipInfo.ProcessedString = string(b[:])
		}
	}

	return &ipInfo, nil
}

// GetURL parses the server's URL into a url.URL
func (s *Server) GetURL() (*url.URL, error) {
	t := time.Now()
	defer func() {
		s.TLog.Logf("Parse server URL took %s", time.Now().Sub(t).String())
	}()

	u, err := url.Parse(s.Server)
	if err != nil {
		log.Debugf("Failed when parsing server URL: %s", err)
		return u, err
	}
	return u, nil
}

// Sponsor returns the sponsor's info
func (s *Server) Sponsor() string {
	var sponsorMsg string
	if s.SponsorName != "" {
		sponsorMsg += s.SponsorName

		if s.SponsorURL != "" {
			su, err := url.Parse(s.SponsorURL)
			if err != nil {
				log.Debugf("Sponsor URL is invalid: %s", s.SponsorURL)
			} else {
				if su.Scheme == "" {
					su.Scheme = "https"
				}
				sponsorMsg += " @ " + su.String()
			}
		}
	}
	return sponsorMsg
}
0707010000000B000081A400000000000000000000000166E1605B00000C2C000000000000000000000000000000000000002700000000speedtest-cli-1.0.11/defs/telemetry.gopackage defs

import (
	"fmt"
	"net/url"
	"path"
	"strings"
	"time"
)

const (
	TelemetryLevelDisabled = "disabled"
	TelemetryLevelBasic    = "basic"
	TelemetryLevelFull     = "full"
	TelemetryLevelDebug    = "debug"
)

// TelemetryLog is the logger for `log` field in telemetry data
type TelemetryLog struct {
	level   int
	content []string
}

// SetLevel sets the log level
func (t *TelemetryLog) SetLevel(level int) {
	t.level = level
}

// Logf logs when log level is higher than or equal to "full"
func (t *TelemetryLog) Logf(format string, a ...interface{}) {
	if t.level >= 2 {
		t.content = append(t.content, fmt.Sprintf("%s: %s", time.Now().String(), fmt.Sprintf(format, a...)))
	}
}

// Warnf logs when log level is higher than or equal to "full", with a WARN prefix
func (t *TelemetryLog) Warnf(format string, a ...interface{}) {
	if t.level >= 2 {
		t.content = append(t.content, fmt.Sprintf("%s: WARN: %s", time.Now().String(), fmt.Sprintf(format, a...)))
	}
}

// Verbosef logs when log level is higher than or equal to "debug"
func (t *TelemetryLog) Verbosef(format string, a ...interface{}) {
	if t.level >= 3 {
		t.content = append(t.content, fmt.Sprintf("%s: %s", time.Now().String(), fmt.Sprintf(format, a...)))
	}
}

// String returns the concatenated string of field `content`
func (t *TelemetryLog) String() string {
	return strings.Join(t.content, "\n")
}

// TelemetryExtra represents the `extra` field in the telemetry data
type TelemetryExtra struct {
	ServerName string `json:"server"`
	Extra      string `json:"extra,omitempty"`
}

// TelemetryServer represents the telemetry server configuration
type TelemetryServer struct {
	Level  string `json:"telemetryLevel"`
	Server string `json:"server"`
	Path   string `json:"path"`
	Share  string `json:"shareURL"`
}

// GetLevel translates the level string to corresponding integer value
func (t *TelemetryServer) GetLevel() int {
	switch t.Level {
	default:
		fallthrough
	case TelemetryLevelDisabled:
		return 0
	case TelemetryLevelBasic:
		return 1
	case TelemetryLevelFull:
		return 2
	case TelemetryLevelDebug:
		return 3
	}
}

// Disabled checks if the telemetry level is "disabled"
func (t *TelemetryServer) Disabled() bool {
	return t.Level == TelemetryLevelDisabled
}

// Basic checks if the telemetry level is "basic"
func (t *TelemetryServer) Basic() bool {
	return t.Level == TelemetryLevelBasic
}

// Full checks if the telemetry level is "full"
func (t *TelemetryServer) Full() bool {
	return t.Level == TelemetryLevelFull
}

// Debug checks if the telemetry level is "debug"
func (t *TelemetryServer) Debug() bool {
	return t.Level == TelemetryLevelDebug
}

// GetPath parses and returns the telemetry path
func (t *TelemetryServer) GetPath() (*url.URL, error) {
	u, err := url.Parse(t.Server)
	if err != nil {
		return nil, err
	}
	u.Path = path.Join(u.Path, t.Path)
	return u, nil
}

// GetShare parses and returns the telemetry share path
func (t *TelemetryServer) GetShare() (*url.URL, error) {
	u, err := url.Parse(t.Server)
	if err != nil {
		return nil, err
	}
	u.Path = path.Join(u.Path, t.Share) + "/"
	return u, nil
}
0707010000000C000081A400000000000000000000000166E1605B00000167000000000000000000000000000000000000002000000000speedtest-cli-1.0.11/dockerfileFROM golang:1.20.3-alpine as builder

RUN apk add --no-cache bash upx

# Set working directory
WORKDIR /usr/src/librespeed-cli

# Copy librespeed-cli
COPY . .

# Build librespeed-cli
RUN ./build.sh

FROM alpine:3.17

# Copy librespeed-cli binary
COPY --from=builder /usr/src/librespeed-cli/out/librespeed-cli* /bin/librespeed-cli

CMD ["/bin/librespeed-cli"]
0707010000000D000081A400000000000000000000000166E1605B00000315000000000000000000000000000000000000001C00000000speedtest-cli-1.0.11/go.modmodule github.com/librespeed/speedtest-cli

go 1.18

require (
	github.com/briandowns/spinner v1.23.1
	github.com/go-ping/ping v1.1.0
	github.com/gocarina/gocsv v0.0.0-20240520201108-78e41c74b4b1
	github.com/sirupsen/logrus v1.9.3
	github.com/urfave/cli/v2 v2.27.4
	golang.org/x/sys v0.25.0
)

require (
	github.com/cpuguy83/go-md2man/v2 v2.0.4 // indirect
	github.com/fatih/color v1.17.0 // indirect
	github.com/google/uuid v1.6.0 // indirect
	github.com/mattn/go-colorable v0.1.13 // indirect
	github.com/mattn/go-isatty v0.0.20 // indirect
	github.com/russross/blackfriday/v2 v2.1.0 // indirect
	github.com/xrash/smetrics v0.0.0-20240521201337-686a1a2994c1 // indirect
	golang.org/x/net v0.29.0 // indirect
	golang.org/x/sync v0.8.0 // indirect
	golang.org/x/term v0.24.0 // indirect
)
0707010000000E000081A400000000000000000000000166E1605B00001C11000000000000000000000000000000000000001C00000000speedtest-cli-1.0.11/go.sumgithub.com/briandowns/spinner v1.23.0 h1:alDF2guRWqa/FOZZYWjlMIx2L6H0wyewPxo/CH4Pt2A=
github.com/briandowns/spinner v1.23.0/go.mod h1:rPG4gmXeN3wQV/TsAY4w8lPdIM6RX3yqeBQJSrbXjuE=
github.com/briandowns/spinner v1.23.1 h1:t5fDPmScwUjozhDj4FA46p5acZWIPXYE30qW2Ptu650=
github.com/briandowns/spinner v1.23.1/go.mod h1:LaZeM4wm2Ywy6vO571mvhQNRcWfRUnXOs0RcKV0wYKM=
github.com/cpuguy83/go-md2man/v2 v2.0.2 h1:p1EgwI/C7NhT0JmVkwCD2ZBK8j4aeHQX2pMHHBfMQ6w=
github.com/cpuguy83/go-md2man/v2 v2.0.2/go.mod h1:tgQtvFlXSQOSOSIRvRPT7W67SCa46tRHOmNcaadrF8o=
github.com/cpuguy83/go-md2man/v2 v2.0.4 h1:wfIWP927BUkWJb2NmU/kNDYIBTh/ziUX91+lVfRxZq4=
github.com/cpuguy83/go-md2man/v2 v2.0.4/go.mod h1:tgQtvFlXSQOSOSIRvRPT7W67SCa46tRHOmNcaadrF8o=
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/fatih/color v1.15.0 h1:kOqh6YHBtK8aywxGerMG2Eq3H6Qgoqeo13Bk2Mv/nBs=
github.com/fatih/color v1.15.0/go.mod h1:0h5ZqXfHYED7Bhv2ZJamyIOUej9KtShiJESRwBDUSsw=
github.com/fatih/color v1.17.0 h1:GlRw1BRJxkpqUCBKzKOw098ed57fEsKeNjpTe3cSjK4=
github.com/fatih/color v1.17.0/go.mod h1:YZ7TlrGPkiz6ku9fK3TLD/pl3CpsiFyu8N92HLgmosI=
github.com/go-ping/ping v1.1.0 h1:3MCGhVX4fyEUuhsfwPrsEdQw6xspHkv5zHsiSoDFZYw=
github.com/go-ping/ping v1.1.0/go.mod h1:xIFjORFzTxqIV/tDVGO4eDy/bLuSyawEeojSm3GfRGk=
github.com/gocarina/gocsv v0.0.0-20230406101422-6445c2b15027 h1:LCGzZb4kMUUjMUzLxxqSJBwo9szUO0tK8cOxnEOT4Jc=
github.com/gocarina/gocsv v0.0.0-20230406101422-6445c2b15027/go.mod h1:5YoVOkjYAQumqlV356Hj3xeYh4BdZuLE0/nRkf2NKkI=
github.com/gocarina/gocsv v0.0.0-20240520201108-78e41c74b4b1 h1:FWNFq4fM1wPfcK40yHE5UO3RUdSNPaBC+j3PokzA6OQ=
github.com/gocarina/gocsv v0.0.0-20240520201108-78e41c74b4b1/go.mod h1:5YoVOkjYAQumqlV356Hj3xeYh4BdZuLE0/nRkf2NKkI=
github.com/google/uuid v1.2.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
github.com/google/uuid v1.3.0 h1:t6JiXgmwXMjEs8VusXIJk2BXHsn+wx8BZdTaoZ5fu7I=
github.com/google/uuid v1.3.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0=
github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
github.com/mattn/go-colorable v0.1.13 h1:fFA4WZxdEF4tXPZVKMLwD8oUnCTTo08duU7wxecdEvA=
github.com/mattn/go-colorable v0.1.13/go.mod h1:7S9/ev0klgBDR4GtXTXX8a3vIGJpMovkB8vQcUbaXHg=
github.com/mattn/go-isatty v0.0.16/go.mod h1:kYGgaQfpe5nmfYZH+SKPsOc2e4SrIfOl2e/yFXSvRLM=
github.com/mattn/go-isatty v0.0.18 h1:DOKFKCQ7FNG2L1rbrmstDN4QVRdS89Nkh85u68Uwp98=
github.com/mattn/go-isatty v0.0.18/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y=
github.com/mattn/go-isatty v0.0.20 h1:xfD0iDuEKnDkl03q4limB+vH+GxLEtL/jb4xVJSWWEY=
github.com/mattn/go-isatty v0.0.20/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y=
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
github.com/russross/blackfriday/v2 v2.1.0 h1:JIOH55/0cWyOuilr9/qlrm0BSXldqnqwMsf35Ld67mk=
github.com/russross/blackfriday/v2 v2.1.0/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM=
github.com/sirupsen/logrus v1.9.0 h1:trlNQbNUG3OdDrDil03MCb1H2o9nJ1x4/5LYw7byDE0=
github.com/sirupsen/logrus v1.9.0/go.mod h1:naHLuLoDiP4jHNo9R0sCBMtWGeIprob74mVsIT4qYEQ=
github.com/sirupsen/logrus v1.9.3 h1:dueUQJ1C2q9oE3F7wvmSGAaVtTmUizReu6fjN8uqzbQ=
github.com/sirupsen/logrus v1.9.3/go.mod h1:naHLuLoDiP4jHNo9R0sCBMtWGeIprob74mVsIT4qYEQ=
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
github.com/stretchr/testify v1.7.0 h1:nwc3DEeHmmLAfoZucVR881uASk0Mfjw8xYJ99tb5CcY=
github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
github.com/urfave/cli/v2 v2.25.1 h1:zw8dSP7ghX0Gmm8vugrs6q9Ku0wzweqPyshy+syu9Gw=
github.com/urfave/cli/v2 v2.25.1/go.mod h1:GHupkWPMM0M/sj1a2b4wUrWBPzazNrIjouW6fmdJLxc=
github.com/urfave/cli/v2 v2.27.4 h1:o1owoI+02Eb+K107p27wEX9Bb8eqIoZCfLXloLUSWJ8=
github.com/urfave/cli/v2 v2.27.4/go.mod h1:m4QzxcD2qpra4z7WhzEGn74WZLViBnMpb1ToCAKdGRQ=
github.com/xrash/smetrics v0.0.0-20201216005158-039620a65673 h1:bAn7/zixMGCfxrRTfdpNzjtPYqr8smhKouy9mxVdGPU=
github.com/xrash/smetrics v0.0.0-20201216005158-039620a65673/go.mod h1:N3UwUGtsrSj3ccvlPHLoLsHnpR27oXr4ZE984MbSER8=
github.com/xrash/smetrics v0.0.0-20240521201337-686a1a2994c1 h1:gEOO8jv9F4OT7lGCjxCBTO/36wtF6j2nSip77qHd4x4=
github.com/xrash/smetrics v0.0.0-20240521201337-686a1a2994c1/go.mod h1:Ohn+xnUBiLI6FVj/9LpzZWtj1/D6lUovWYBkxHVV3aM=
golang.org/x/net v0.0.0-20210316092652-d523dce5a7f4/go.mod h1:RBQZq4jEuRlivfhVLdyRGr576XBO4/greRjx4P4O3yc=
golang.org/x/net v0.9.0 h1:aWJ/m6xSmxWBx+V0XRHTlrYrPG56jKsLdTFmsSsCzOM=
golang.org/x/net v0.9.0/go.mod h1:d48xBJpPfHeWQsugry2m+kC02ZBRGRgulfHnEXEuWns=
golang.org/x/net v0.29.0 h1:5ORfpBpCs4HzDYoodCDBbwHzdR5UrLBZ3sOnUJmFoHo=
golang.org/x/net v0.29.0/go.mod h1:gLkgy8jTGERgjzMic6DS9+SP0ajcu6Xu3Orq/SpETg0=
golang.org/x/sync v0.0.0-20210220032951-036812b2e83c h1:5KslGYwFpkhGh+Q16bwMP3cOontH8FOep7tGV86Y7SQ=
golang.org/x/sync v0.0.0-20210220032951-036812b2e83c/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.8.0 h1:3NFvSEYkUoMifnESzZl15y791HH1qU2xm6eCJU5ZPXQ=
golang.org/x/sync v0.8.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk=
golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20210315160823-c6e025ad8005/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20220715151400-c0bba94af5f8/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20220811171246-fbc7d0a398ab/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.7.0 h1:3jlCCIQZPdOYu1h8BkNvLz8Kgwtae2cagcG/VamtZRU=
golang.org/x/sys v0.7.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.25.0 h1:r+8e+loiHxRqhXVl6ML1nO3l1+oFoWbnlu2Ehimmi34=
golang.org/x/sys v0.25.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
golang.org/x/term v0.7.0 h1:BEvjmm5fURWqcfbSKTdpkDXYBrUS1c0m8agp14W48vQ=
golang.org/x/term v0.7.0/go.mod h1:P32HKFT3hSsZrRxla30E9HqToFYAQPCMs/zFMBUFqPY=
golang.org/x/term v0.24.0 h1:Mh5cbb+Zk2hqqXNO7S1iTjEphVL+jb8ZWaqh/g+JWkM=
golang.org/x/term v0.24.0/go.mod h1:lOBK/LVxemqiMij05LGJ0tzNr8xlmwBRJ81PX6wVLH8=
golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
0707010000000F000081A400000000000000000000000166E1605B00001B16000000000000000000000000000000000000001D00000000speedtest-cli-1.0.11/main.gopackage main

import (
	"os"

	log "github.com/sirupsen/logrus"
	"github.com/urfave/cli/v2"

	"github.com/librespeed/speedtest-cli/defs"
	"github.com/librespeed/speedtest-cli/speedtest"
)

// init sets up the essential bits on start up
func init() {
	// set logrus formatter and default log level
	formatter := &defs.NoFormatter{}

	// debug level is for --debug messages
	// info level is for non-suppress mode
	// warn level is for suppress modes
	// error level is for errors

	log.SetOutput(os.Stderr)
	log.SetFormatter(formatter)
	log.SetLevel(log.InfoLevel)
}

func main() {
	// define cli options
	app := &cli.App{
		Name:     "librespeed-cli",
		Usage:    "Test your Internet speed with LibreSpeed",
		Action:   speedtest.SpeedTest,
		HideHelp: true,
		Flags: []cli.Flag{
			cli.HelpFlag,
			&cli.BoolFlag{
				Name:  defs.OptionVersion,
				Usage: "Show the version number and exit",
			},
			&cli.BoolFlag{
				Name:    defs.OptionIPv4,
				Aliases: []string{defs.OptionIPv4Alt},
				Usage:   "Force IPv4 only",
			},
			&cli.BoolFlag{
				Name:    defs.OptionIPv6,
				Aliases: []string{defs.OptionIPv6Alt},
				Usage:   "Force IPv6 only",
			},
			&cli.BoolFlag{
				Name:  defs.OptionNoDownload,
				Usage: "Do not perform download test",
			},
			&cli.BoolFlag{
				Name:  defs.OptionNoUpload,
				Usage: "Do not perform upload test",
			},
			&cli.BoolFlag{
				Name: defs.OptionNoICMP,
				Usage: "Do not use ICMP ping. ICMP doesn't work well under Linux\n" +
					"\tat this moment, so you might want to disable it",
			},
			&cli.IntFlag{
				Name:  defs.OptionConcurrent,
				Usage: "Concurrent HTTP requests being made",
				Value: 3,
			},
			&cli.BoolFlag{
				Name: defs.OptionBytes,
				Usage: "Display values in bytes instead of bits. Does not affect\n" +
					"\tthe image generated by --share, nor output from\n" +
					"\t--json or --csv",
			},
			&cli.BoolFlag{
				Name:  defs.OptionMebiBytes,
				Usage: "Use 1024 bytes as 1 kilobyte instead of 1000",
			},
			&cli.StringFlag{
				Name: defs.OptionDistance,
				Usage: "Change distance unit shown in ISP info, use 'mi' for miles,\n" +
					"\t'km' for kilometres, 'NM' for nautical miles",
				Value: "km",
			},
			&cli.BoolFlag{
				Name: defs.OptionShare,
				Usage: "Generate and provide a URL to the LibreSpeed.org share results\n" +
					"\timage, not displayed with --csv",
			},
			&cli.BoolFlag{
				Name:  defs.OptionSimple,
				Usage: "Suppress verbose output, only show basic information\n\t",
			},
			&cli.BoolFlag{
				Name: defs.OptionCSV,
				Usage: "Suppress verbose output, only show basic information in CSV\n" +
					"\tformat. Speeds listed in bit/s and not affected by --bytes\n\t",
			},
			&cli.StringFlag{
				Name: defs.OptionCSVDelimiter,
				Usage: "Single character delimiter (`CSV_DELIMITER`) to use in\n" +
					"\tCSV output.",
				Value: ",",
			},
			&cli.BoolFlag{
				Name:  defs.OptionCSVHeader,
				Usage: "Print CSV headers",
			},
			&cli.BoolFlag{
				Name: defs.OptionJSON,
				Usage: "Suppress verbose output, only show basic information\n" +
					"\tin JSON format. Speeds listed in bit/s and not\n" +
					"\taffected by --bytes",
			},
			&cli.BoolFlag{
				Name:  defs.OptionList,
				Usage: "Display a list of LibreSpeed.org servers",
			},
			&cli.IntSliceFlag{
				Name: defs.OptionServer,
				Usage: "Specify a `SERVER` ID to test against. Can be supplied\n" +
					"\tmultiple times. Cannot be used with --exclude",
			},
			&cli.IntSliceFlag{
				Name: defs.OptionExclude,
				Usage: "`EXCLUDE` a server from selection. Can be supplied\n" +
					"\tmultiple times. Cannot be used with --server",
			},
			&cli.StringFlag{
				Name:  defs.OptionServerJSON,
				Usage: "Use an alternative server list from remote JSON file",
			},
			&cli.StringFlag{
				Name: defs.OptionLocalJSON,
				Usage: "Use an alternative server list from local JSON file,\n" +
					"\tor read from stdin with \"--" + defs.OptionLocalJSON + " -\".",
			},
			&cli.StringFlag{
				Name:  defs.OptionSource,
				Usage: "`SOURCE` IP address to bind to",
			},
			&cli.StringFlag{
				Name:  defs.OptionInterface,
				Usage: "network INTERFACE to bind to",
			},
			&cli.IntFlag{
				Name:  defs.OptionTimeout,
				Usage: "HTTP `TIMEOUT` in seconds.",
				Value: 15,
			},
			&cli.IntFlag{
				Name:  defs.OptionDuration,
				Usage: "Upload and download test duration in seconds",
				Value: 15,
			},
			&cli.IntFlag{
				Name:  defs.OptionChunks,
				Usage: "Chunks to download from server, chunk size depends on server configuration",
				Value: 100,
			},
			&cli.IntFlag{
				Name:  defs.OptionUploadSize,
				Usage: "Size of payload being uploaded in KiB",
				Value: 1024,
			},
			&cli.BoolFlag{
				Name: defs.OptionSecure,
				Usage: "Use HTTPS instead of HTTP when communicating with\n" +
					"\tLibreSpeed.org operated servers",
			},
			&cli.StringFlag{
				Name: defs.OptionCACert,
				Usage: "Use the specified CA certificate PEM bundle file instead\n" +
				    "\tof the system certificate trust store",
			},
			&cli.BoolFlag{
				Name:  defs.OptionSkipCertVerify,
				Usage: "Skip verifying SSL certificate for HTTPS connections (self-signed certs)",
			},
			&cli.BoolFlag{
				Name: defs.OptionNoPreAllocate,
				Usage: "Do not pre allocate upload data. Pre allocation is\n" +
					"\tenabled by default to improve upload performance. To\n" +
					"\tsupport systems with insufficient memory, use this\n" +
					"\toption to avoid out of memory errors",
			},
			&cli.BoolFlag{
				Name:    defs.OptionDebug,
				Aliases: []string{"verbose"},
				Usage:   "Debug mode (verbose logging)",
				Hidden:  true,
			},
			&cli.StringFlag{
				Name: defs.OptionTelemetryJSON,
				Usage: "Load telemetry server settings from a JSON file. This\n" +
					"\toptions overrides --" + defs.OptionTelemetryLevel + ", --" + defs.OptionTelemetryServer + ",\n" +
					"\t--" + defs.OptionTelemetryPath + ", and --" + defs.OptionTelemetryShare + ". Implies --" + defs.OptionShare,
			},
			&cli.StringFlag{
				Name: defs.OptionTelemetryLevel,
				Usage: "Set telemetry data verbosity, available values are:\n" +
					"\tdisabled, basic, full, debug. Implies --" + defs.OptionShare,
			},
			&cli.StringFlag{
				Name:  defs.OptionTelemetryServer,
				Usage: "Set the telemetry server base URL. Implies --" + defs.OptionShare,
			},
			&cli.StringFlag{
				Name:  defs.OptionTelemetryPath,
				Usage: "Set the telemetry upload path. Implies --" + defs.OptionShare,
			},
			&cli.StringFlag{
				Name:  defs.OptionTelemetryShare,
				Usage: "Set the telemetry share link path. Implies --" + defs.OptionShare,
			},
			&cli.StringFlag{
				Name: defs.OptionTelemetryExtra,
				Usage: "Send a custom message along with the telemetry results.\n" +
					"\tImplies --" + defs.OptionShare,
			},
		},
	}

	// run main function with cli options
	err := app.Run(os.Args)
	if err != nil {
		log.Fatal("Terminated due to error")
	}
}
07070100000010000041ED00000000000000000000000266E1605B00000000000000000000000000000000000000000000001C00000000speedtest-cli-1.0.11/report07070100000011000081A400000000000000000000000166E1605B000001C3000000000000000000000000000000000000002300000000speedtest-cli-1.0.11/report/csv.gopackage report

import (
	"time"
)

// CSVReport represents the output data fields in a CSV file
type CSVReport struct {
	Timestamp time.Time `csv:"Timestamp"`
	Name      string    `csv:"Server Name"`
	Address   string    `csv:"Address"`
	Ping      float64   `csv:"Ping"`
	Jitter    float64   `csv:"Jitter"`
	Download  float64   `csv:"Download"`
	Upload    float64   `csv:"Upload"`
	Share     string    `csv:"Share"`
	IP        string    `csv:"IP"`
}
07070100000012000081A400000000000000000000000166E1605B0000033F000000000000000000000000000000000000002400000000speedtest-cli-1.0.11/report/json.gopackage report

import (
	"time"

	"github.com/librespeed/speedtest-cli/defs"
)

// JSONReport represents the output data fields in a JSON file
type JSONReport struct {
	Timestamp     time.Time `json:"timestamp"`
	Server        Server    `json:"server"`
	Client        Client    `json:"client"`
	BytesSent     uint64    `json:"bytes_sent"`
	BytesReceived uint64    `json:"bytes_received"`
	Ping          float64   `json:"ping"`
	Jitter        float64   `json:"jitter"`
	Upload        float64   `json:"upload"`
	Download      float64   `json:"download"`
	Share         string    `json:"share"`
}

// Server represents the speed test server's information
type Server struct {
	Name string `json:"name"`
	URL  string `json:"url"`
}

// Client represents the speed test client's information
type Client struct {
	defs.IPInfoResponse
}
07070100000013000041ED00000000000000000000000266E1605B00000000000000000000000000000000000000000000001F00000000speedtest-cli-1.0.11/speedtest07070100000014000081A400000000000000000000000166E1605B0000284C000000000000000000000000000000000000002900000000speedtest-cli-1.0.11/speedtest/helper.gopackage speedtest

import (
	"bytes"
	"encoding/json"
	"fmt"
	"io/ioutil"
	"math"
	"mime/multipart"
	"net/http"
	"os"
	"strconv"
	"strings"
	"time"

	"github.com/briandowns/spinner"
	"github.com/gocarina/gocsv"
	"github.com/librespeed/speedtest-cli/defs"
	"github.com/librespeed/speedtest-cli/report"
	log "github.com/sirupsen/logrus"
"github.com/urfave/cli/v2"
)

const (
	// the default ping count for measuring ping and jitter
	pingCount = 10
)

// doSpeedTest is where the actual speed test happens
func doSpeedTest(c *cli.Context, servers []defs.Server, telemetryServer defs.TelemetryServer, network string, silent bool, noICMP bool) error {
	if serverCount := len(servers); serverCount > 1 {
		log.Infof("Testing against %d servers", serverCount)
	}

	var reps_json []report.JSONReport
	var reps_csv []report.CSVReport

	// fetch current user's IP info
	for _, currentServer := range servers {
		// get telemetry level
		currentServer.TLog.SetLevel(telemetryServer.GetLevel())

		u, err := currentServer.GetURL()
		if err != nil {
			log.Errorf("Failed to get server URL: %s", err)
			return err
		}

		log.Infof("Selected server: %s [%s]", currentServer.Name, u.Hostname())

		if sponsorMsg := currentServer.Sponsor(); sponsorMsg != "" {
			log.Infof("Sponsored by: %s", sponsorMsg)
		}

		if currentServer.IsUp() {
			ispInfo, err := currentServer.GetIPInfo(c.String(defs.OptionDistance))
			if err != nil {
				log.Errorf("Failed to get IP info: %s", err)
				return err
			}
			log.Infof("You're testing from: %s", ispInfo.ProcessedString)

			// get ping and jitter value
			var pb *spinner.Spinner
			if !silent {
				pb = spinner.New(spinner.CharSets[11], 100*time.Millisecond)
				pb.Prefix = "Pinging server...  "
				pb.Start()
			}

			// skip ICMP if option given
			currentServer.NoICMP = noICMP

			p, jitter, err := currentServer.ICMPPingAndJitter(pingCount, c.String(defs.OptionSource), network)
			if err != nil {
				log.Errorf("Failed to get ping and jitter: %s", err)
				return err
			}

			if pb != nil {
				pb.FinalMSG = fmt.Sprintf("Ping: %.2f ms\tJitter: %.2f ms\n", p, jitter)
				pb.Stop()
			}

			// get download value
			var downloadValue float64
			var bytesRead uint64
			if c.Bool(defs.OptionNoDownload) {
				log.Info("Download test is disabled")
			} else {
				download, br, err := currentServer.Download(silent, c.Bool(defs.OptionBytes), c.Bool(defs.OptionMebiBytes), c.Int(defs.OptionConcurrent), c.Int(defs.OptionChunks), time.Duration(c.Int(defs.OptionDuration))*time.Second)
				if err != nil {
					log.Errorf("Failed to get download speed: %s", err)
					return err
				}
				downloadValue = download
				bytesRead = uint64(br)
			}

			// get upload value
			var uploadValue float64
			var bytesWritten uint64
			if c.Bool(defs.OptionNoUpload) {
				log.Info("Upload test is disabled")
			} else {
				upload, bw, err := currentServer.Upload(c.Bool(defs.OptionNoPreAllocate), silent, c.Bool(defs.OptionBytes), c.Bool(defs.OptionMebiBytes), c.Int(defs.OptionConcurrent), c.Int(defs.OptionUploadSize), time.Duration(c.Int(defs.OptionDuration))*time.Second)
				if err != nil {
					log.Errorf("Failed to get upload speed: %s", err)
					return err
				}
				uploadValue = upload
				bytesWritten = uint64(bw)
			}

			// print result if --simple is given
			if c.Bool(defs.OptionSimple) {
				if c.Bool(defs.OptionBytes) {
					useMebi := c.Bool(defs.OptionMebiBytes)
					log.Warnf("Ping:\t%.2f ms\tJitter:\t%.2f ms\nDownload rate:\t%s\nUpload rate:\t%s", p, jitter, humanizeMbps(downloadValue, useMebi), humanizeMbps(uploadValue, useMebi))
				} else {
					log.Warnf("Ping:\t%.2f ms\tJitter:\t%.2f ms\nDownload rate:\t%.2f Mbps\nUpload rate:\t%.2f Mbps", p, jitter, downloadValue, uploadValue)
				}
			}

			// print share link if --share is given
			var shareLink string
			if telemetryServer.GetLevel() > 0 {
				var extra defs.TelemetryExtra
				extra.ServerName = currentServer.Name
				extra.Extra = c.String(defs.OptionTelemetryExtra)

				if link, err := sendTelemetry(telemetryServer, ispInfo, downloadValue, uploadValue, p, jitter, currentServer.TLog.String(), extra); err != nil {
					log.Errorf("Error when sending telemetry data: %s", err)
				} else {
					shareLink = link
					// only print to stdout when --json and --csv are not used
					if !c.Bool(defs.OptionJSON) && !c.Bool(defs.OptionCSV) {
						log.Warnf("Share your result: %s", link)
					}
				}
			}

			// check for --csv or --json. the program prioritize the --csv before the --json. this is the same behavior as speedtest-cli
			if c.Bool(defs.OptionCSV) {
				// print csv if --csv is given
				var rep report.CSVReport
				rep.Timestamp = time.Now()

				rep.Name = currentServer.Name
				rep.Address = u.String()
				rep.Ping = math.Round(p*100) / 100
				rep.Jitter = math.Round(jitter*100) / 100
				rep.Download = math.Round(downloadValue*100) / 100
				rep.Upload = math.Round(uploadValue*100) / 100
				rep.Share = shareLink
				rep.IP = ispInfo.RawISPInfo.IP

				reps_csv = append(reps_csv, rep)
			} else if c.Bool(defs.OptionJSON) {
				// print json if --json is given
				var rep report.JSONReport
				rep.Timestamp = time.Now()

				rep.Ping = math.Round(p*100) / 100
				rep.Jitter = math.Round(jitter*100) / 100
				rep.Download = math.Round(downloadValue*100) / 100
				rep.Upload = math.Round(uploadValue*100) / 100
				rep.BytesReceived = bytesRead
				rep.BytesSent = bytesWritten
				rep.Share = shareLink

				rep.Server.Name = currentServer.Name
				rep.Server.URL = u.String()

				rep.Client = report.Client{ispInfo.RawISPInfo}
				rep.Client.Readme = ""

				reps_json = append(reps_json, rep)
			}
		} else {
			log.Infof("Selected server %s (%s) is not responding at the moment, try again later", currentServer.Name, u.Hostname())
		}

		//add a new line after each test if testing multiple servers
		if len(servers) > 1 && !silent {
			log.Warn()
		}
	}

	// check for --csv or --json. the program prioritize the --csv before the --json. this is the same behavior as speedtest-cli
	if c.Bool(defs.OptionCSV) {
		var buf bytes.Buffer
		if err := gocsv.MarshalWithoutHeaders(&reps_csv, &buf); err != nil {
			log.Errorf("Error generating CSV report: %s", err)
		} else {
			os.Stdout.WriteString(buf.String())
		}
	} else if c.Bool(defs.OptionJSON) {
		if b, err := json.Marshal(&reps_json); err != nil {
			log.Errorf("Error generating JSON report: %s", err)
		} else {
			os.Stdout.Write(b[:])
		}
	}

	return nil
}

// sendTelemetry sends the telemetry result to server, if --share is given
func sendTelemetry(telemetryServer defs.TelemetryServer, ispInfo *defs.GetIPResult, download, upload, pingVal, jitter float64, logs string, extra defs.TelemetryExtra) (string, error) {
	var buf bytes.Buffer
	wr := multipart.NewWriter(&buf)

	b, _ := json.Marshal(ispInfo)
	if fIspInfo, err := wr.CreateFormField("ispinfo"); err != nil {
		log.Debugf("Error creating form field: %s", err)
		return "", err
	} else if _, err = fIspInfo.Write(b); err != nil {
		log.Debugf("Error writing form field: %s", err)
		return "", err
	}

	if fDownload, err := wr.CreateFormField("dl"); err != nil {
		log.Debugf("Error creating form field: %s", err)
		return "", err
	} else if _, err = fDownload.Write([]byte(strconv.FormatFloat(download, 'f', 2, 64))); err != nil {
		log.Debugf("Error writing form field: %s", err)
		return "", err
	}

	if fUpload, err := wr.CreateFormField("ul"); err != nil {
		log.Debugf("Error creating form field: %s", err)
		return "", err
	} else if _, err = fUpload.Write([]byte(strconv.FormatFloat(upload, 'f', 2, 64))); err != nil {
		log.Debugf("Error writing form field: %s", err)
		return "", err
	}

	if fPing, err := wr.CreateFormField("ping"); err != nil {
		log.Debugf("Error creating form field: %s", err)
		return "", err
	} else if _, err = fPing.Write([]byte(strconv.FormatFloat(pingVal, 'f', 2, 64))); err != nil {
		log.Debugf("Error writing form field: %s", err)
		return "", err
	}

	if fJitter, err := wr.CreateFormField("jitter"); err != nil {
		log.Debugf("Error creating form field: %s", err)
		return "", err
	} else if _, err = fJitter.Write([]byte(strconv.FormatFloat(jitter, 'f', 2, 64))); err != nil {
		log.Debugf("Error writing form field: %s", err)
		return "", err
	}

	if fLog, err := wr.CreateFormField("log"); err != nil {
		log.Debugf("Error creating form field: %s", err)
		return "", err
	} else if _, err = fLog.Write([]byte(logs)); err != nil {
		log.Debugf("Error writing form field: %s", err)
		return "", err
	}

	b, _ = json.Marshal(extra)
	if fExtra, err := wr.CreateFormField("extra"); err != nil {
		log.Debugf("Error creating form field: %s", err)
		return "", err
	} else if _, err = fExtra.Write(b); err != nil {
		log.Debugf("Error writing form field: %s", err)
		return "", err
	}

	if err := wr.Close(); err != nil {
		log.Debugf("Error flushing form field writer: %s", err)
		return "", err
	}

	telemetryUrl, err := telemetryServer.GetPath()
	if err != nil {
		return "", err
	}

	req, err := http.NewRequest(http.MethodPost, telemetryUrl.String(), &buf)
	if err != nil {
		log.Debugf("Error when creating HTTP request: %s", err)
		return "", err
	}
	req.Header.Set("Content-Type", wr.FormDataContentType())
	req.Header.Set("User-Agent", defs.UserAgent)

	resp, err := http.DefaultClient.Do(req)
	if err != nil {
		log.Debugf("Error when making HTTP request: %s", err)
		return "", err
	}
	defer resp.Body.Close()

	id, err := ioutil.ReadAll(resp.Body)
	if err != nil {
		log.Errorf("Error when reading HTTP request: %s", err)
		return "", err
	}

	resultUrl, err := telemetryServer.GetShare()
	if err != nil {
		return "", err
	}

	if str := strings.Split(string(id), " "); len(str) != 2 {
		return "", fmt.Errorf("server returned invalid response: %s", id)
	} else {
		q := resultUrl.Query()
		q.Set("id", str[1])
		resultUrl.RawQuery = q.Encode()

		return resultUrl.String(), nil
	}
}

func humanizeMbps(mbps float64, useMebi bool) string {
	val := mbps / 8
	var base float64 = 1000
	if useMebi {
		base = 1024
	}

	if val < 1 {
		if kb := val * base; kb < 1 {
			return fmt.Sprintf("%.2f bytes/s", kb*base)
		} else {
			return fmt.Sprintf("%.2f KB/s", kb)
		}
	} else if val > base {
		return fmt.Sprintf("%.2f GB/s", val/base)
	} else {
		return fmt.Sprintf("%.2f MB/s", val)
	}
}
07070100000015000081A400000000000000000000000166E1605B000039F9000000000000000000000000000000000000002C00000000speedtest-cli-1.0.11/speedtest/speedtest.gopackage speedtest

import (
	"context"
	"crypto/tls"
	"crypto/x509"
	"encoding/json"
	"errors"
	"fmt"
	"io"
	"io/ioutil"
	"net"
	"net/http"
	"os"
	"strings"
	"sync"
	"time"

	"github.com/gocarina/gocsv"
	log "github.com/sirupsen/logrus"
	"github.com/urfave/cli/v2"

	"github.com/librespeed/speedtest-cli/defs"
	"github.com/librespeed/speedtest-cli/report"
)

const (
	// serverListUrl is the default remote server JSON URL
	serverListUrl = `https://librespeed.org/backend-servers/servers.php`

	defaultTelemetryLevel  = "basic"
	defaultTelemetryServer = "https://librespeed.org"
	defaultTelemetryPath   = "/results/telemetry.php"
	defaultTelemetryShare  = "/results/"
)

type PingJob struct {
	Index  int
	Server defs.Server
}

type PingResult struct {
	Index int
	Ping  float64
}

// SpeedTest is the actual main function that handles the speed test(s)
func SpeedTest(c *cli.Context) error {
	// check for suppressed output flags
	var silent bool
	if c.Bool(defs.OptionSimple) || c.Bool(defs.OptionJSON) || c.Bool(defs.OptionCSV) {
		log.SetLevel(log.WarnLevel)
		silent = true
	}

	// check for debug flag
	if c.Bool(defs.OptionDebug) {
		log.SetLevel(log.DebugLevel)
	}

	// print help
	if c.Bool(defs.OptionHelp) {
		return cli.ShowAppHelp(c)
	}

	// print version
	if c.Bool(defs.OptionVersion) {
		log.SetOutput(os.Stdout)
		log.Warnf("%s %s (built on %s)", defs.ProgName, defs.ProgVersion, defs.BuildDate)
		log.Warn("https://github.com/librespeed/speedtest-cli")
		log.Warn("Licensed under GNU Lesser General Public License v3.0")
		log.Warn("LibreSpeed\tCopyright (C) 2016-2020 Federico Dossena")
		log.Warn("librespeed-cli\tCopyright (C) 2020 Maddie Zhan")
		log.Warn("librespeed.org\tCopyright (C)")
		return nil
	}

	if c.String(defs.OptionSource) != "" && c.String(defs.OptionInterface) != "" {
		return fmt.Errorf("incompatible options '%s' and '%s'", defs.OptionSource, defs.OptionInterface)
	}

	// set CSV delimiter
	gocsv.TagSeparator = c.String(defs.OptionCSVDelimiter)

	// if --csv-header is given, print the header and exit (same behavior speedtest-cli)
	if c.Bool(defs.OptionCSVHeader) {
		var rep []report.CSVReport
		b, _ := gocsv.MarshalBytes(&rep)
		os.Stdout.WriteString(string(b))
		return nil
	}

	// read telemetry settings if --share or any --telemetry option is given
	var telemetryServer defs.TelemetryServer
	telemetryJSON := c.String(defs.OptionTelemetryJSON)
	telemetryLevel := c.String(defs.OptionTelemetryLevel)
	telemetryServerString := c.String(defs.OptionTelemetryServer)
	telemetryPath := c.String(defs.OptionTelemetryPath)
	telemetryShare := c.String(defs.OptionTelemetryShare)
	if c.Bool(defs.OptionShare) || telemetryJSON != "" || telemetryLevel != "" || telemetryServerString != "" || telemetryPath != "" || telemetryShare != "" {
		if telemetryJSON != "" {
			b, err := ioutil.ReadFile(telemetryJSON)
			if err != nil {
				log.Errorf("Cannot read %s: %s", telemetryJSON, err)
				return err
			}
			if err := json.Unmarshal(b, &telemetryServer); err != nil {
				log.Errorf("Error parsing %s: %s", err)
				return err
			}
		}

		if telemetryLevel != "" {
			if telemetryLevel != "disabled" && telemetryLevel != "basic" && telemetryLevel != "full" && telemetryLevel != "debug" {
				log.Fatalf("Unsupported telemetry level: %s", telemetryLevel)
			}
			telemetryServer.Level = telemetryLevel
		} else if telemetryServer.Level == "" {
			telemetryServer.Level = defaultTelemetryLevel
		}

		if telemetryServerString != "" {
			telemetryServer.Server = telemetryServerString
		} else if telemetryServer.Server == "" {
			telemetryServer.Server = defaultTelemetryServer
		}

		if telemetryPath != "" {
			telemetryServer.Path = telemetryPath
		} else if telemetryServer.Path == "" {
			telemetryServer.Path = defaultTelemetryPath
		}

		if telemetryShare != "" {
			telemetryServer.Share = telemetryShare
		} else if telemetryServer.Share == "" {
			telemetryServer.Share = defaultTelemetryShare
		}
	}

	if req := c.Int(defs.OptionConcurrent); req <= 0 {
		log.Errorf("Concurrent requests cannot be lower than 1: %d is given", req)
		return errors.New("invalid concurrent requests setting")
	}

	noICMP := c.Bool(defs.OptionNoICMP)

	// HTTP requests timeout
	http.DefaultClient.Timeout = time.Duration(c.Int(defs.OptionTimeout)) * time.Second

	forceIPv4 := c.Bool(defs.OptionIPv4)
	forceIPv6 := c.Bool(defs.OptionIPv6)

	var network string
	switch {
	case forceIPv4:
		network = "ip4"
	case forceIPv6:
		network = "ip6"
	default:
		network = "ip"
	}

    transport := http.DefaultTransport.(*http.Transport).Clone()

    if caCertFileName := c.String(defs.OptionCACert); caCertFileName != "" {
        caCert, err := ioutil.ReadFile(caCertFileName)
        if err != nil {
            log.Fatal(err)
        }
        caCertPool := x509.NewCertPool()
        caCertPool.AppendCertsFromPEM(caCert)

        transport.TLSClientConfig = &tls.Config{
            InsecureSkipVerify: c.Bool(defs.OptionSkipCertVerify),
            RootCAs: caCertPool,
        }
    } else {
        transport.TLSClientConfig = &tls.Config{
            InsecureSkipVerify: c.Bool(defs.OptionSkipCertVerify),
        }
    }

	dialer := &net.Dialer{
		Timeout:   30 * time.Second,
		KeepAlive: 30 * time.Second,
	}
	// bind to source IP address if given
	if src := c.String(defs.OptionSource); src != "" {
		var err error
		dialer, err = newDialerAddressBound(src, network)
		if err != nil {
			return err
		}
	}

	// bind to interface if given
	if iface := c.String(defs.OptionInterface); iface != "" {
		var err error
		dialer, err = newDialerInterfaceBound(iface)
		if err != nil {
			return err
		}
		// ICMP ping does not support interface binding.
		noICMP = true
	}

	// enforce if ipv4/ipv6 is forced
	var dialContext func(context.Context, string, string) (net.Conn, error)
	switch {
	case forceIPv4:
		dialContext = func(ctx context.Context, network, address string) (conn net.Conn, err error) {
			return dialer.DialContext(ctx, "tcp4", address)
		}
	case forceIPv6:
		dialContext = func(ctx context.Context, network, address string) (conn net.Conn, err error) {
			return dialer.DialContext(ctx, "tcp6", address)
		}
	default:
		dialContext = dialer.DialContext
	}

	// set default HTTP client's Transport to the one that binds the source address
	// this is modified from http.DefaultTransport
	transport.DialContext = dialContext
	http.DefaultClient.Transport = transport

	// load server list
	var servers []defs.Server
	var err error
	if str := c.String(defs.OptionLocalJSON); str != "" {
		switch str {
		case "-":
			// load server list from stdin
			log.Info("Using local JSON server list from stdin")
			servers, err = getLocalServersReader(c.Bool(defs.OptionSecure), os.Stdin, c.IntSlice(defs.OptionExclude), c.IntSlice(defs.OptionServer), !c.Bool(defs.OptionList))
		default:
			// load server list from local JSON file
			log.Infof("Using local JSON server list: %s", str)
			servers, err = getLocalServers(c.Bool(defs.OptionSecure), str, c.IntSlice(defs.OptionExclude), c.IntSlice(defs.OptionServer), !c.Bool(defs.OptionList))
		}
	} else {
		// fetch the server list JSON and parse it into the `servers` array
		serverUrl := serverListUrl
		if str := c.String(defs.OptionServerJSON); str != "" {
			serverUrl = str
		}
		log.Infof("Retrieving server list from %s", serverUrl)

		servers, err = getServerList(c.Bool(defs.OptionSecure), serverUrl, c.IntSlice(defs.OptionExclude), c.IntSlice(defs.OptionServer), !c.Bool(defs.OptionList))

		if err != nil {
			log.Info("Retry with /.well-known/librespeed")
			servers, err = getServerList(c.Bool(defs.OptionSecure), serverUrl+"/.well-known/librespeed", c.IntSlice(defs.OptionExclude), c.IntSlice(defs.OptionServer), !c.Bool(defs.OptionList))
		}
	}
	if err != nil {
		log.Errorf("Error when fetching server list: %s", err)
		return err
	}

	// if --list is given, list all the servers fetched and exit
	if c.Bool(defs.OptionList) {
		for _, svr := range servers {
			var sponsorMsg string
			if svr.Sponsor() != "" {
				sponsorMsg = fmt.Sprintf(" [Sponsor: %s]", svr.Sponsor())
			}
			log.Warnf("%d: %s (%s) %s", svr.ID, svr.Name, svr.Server, sponsorMsg)
		}
		return nil
	}

	// if --server is given, do speed tests with all of them
	if len(c.IntSlice(defs.OptionServer)) > 0 {
		return doSpeedTest(c, servers, telemetryServer, network, silent, noICMP)
	} else {
		// else select the fastest server from the list
		log.Info("Selecting the fastest server based on ping")

		var wg sync.WaitGroup
		jobs := make(chan PingJob, len(servers))
		results := make(chan PingResult, len(servers))
		done := make(chan struct{})

		pingList := make(map[int]float64)

		// spawn 10 concurrent pingers
		for i := 0; i < 10; i++ {
			go pingWorker(jobs, results, &wg, c.String(defs.OptionSource), network, noICMP)
		}

		// send ping jobs to workers
		for idx, server := range servers {
			wg.Add(1)
			jobs <- PingJob{Index: idx, Server: server}
		}

		go func() {
			wg.Wait()
			close(done)
		}()

	Loop:
		for {
			select {
			case result := <-results:
				pingList[result.Index] = result.Ping
			case <-done:
				break Loop
			}
		}

		if len(pingList) == 0 {
			log.Fatal("No server is currently available, please try again later.")
		}

		// get the fastest server's index in the `servers` array
		var serverIdx int
		for idx, ping := range pingList {
			if ping > 0 && ping <= pingList[serverIdx] {
				serverIdx = idx
			}
		}

		// do speed test on the server
		return doSpeedTest(c, []defs.Server{servers[serverIdx]}, telemetryServer, network, silent, noICMP)
	}
}

func pingWorker(jobs <-chan PingJob, results chan<- PingResult, wg *sync.WaitGroup, srcIp, network string, noICMP bool) {
	for {
		job := <-jobs
		server := job.Server
		// get the URL of the speed test server from the JSON
		u, err := server.GetURL()
		if err != nil {
			log.Debugf("Server URL is invalid for %s (%s), skipping", server.Name, server.Server)
			wg.Done()
			return
		}

		// check the server is up by accessing the ping URL and checking its returned value == empty and status code == 200
		if server.IsUp() {
			// skip ICMP if option given
			server.NoICMP = noICMP

			// if server is up, get ping
			ping, _, err := server.ICMPPingAndJitter(1, srcIp, network)
			if err != nil {
				log.Debugf("Can't ping server %s (%s), skipping", server.Name, u.Hostname())
				wg.Done()
				return
			}
			// return result
			results <- PingResult{Index: job.Index, Ping: ping}
			wg.Done()
		} else {
			log.Debugf("Server %s (%s) doesn't seem to be up, skipping", server.Name, u.Hostname())
			wg.Done()
		}
	}
}

// getServerList fetches the server JSON from a remote server
func getServerList(forceHTTPS bool, serverList string, excludes, specific []int, filter bool) ([]defs.Server, error) {
	// --exclude and --server cannot be used at the same time
	if len(excludes) > 0 && len(specific) > 0 {
		return nil, errors.New("either --exclude or --server can be used")
	}

	// getting the server list from remote
	var servers []defs.Server
	req, err := http.NewRequest(http.MethodGet, serverList, nil)
	if err != nil {
		return nil, err
	}
	req.Header.Set("User-Agent", defs.UserAgent)

	resp, err := http.DefaultClient.Do(req)
	if err != nil {
		return nil, err
	}

	b, err := ioutil.ReadAll(resp.Body)
	if err != nil {
		return nil, err
	}
	defer resp.Body.Close()

	if err := json.Unmarshal(b, &servers); err != nil {
		return nil, err
	}

	return preprocessServers(servers, forceHTTPS, excludes, specific, filter)
}

// getLocalServersReader loads the server JSON from an io.Reader
func getLocalServersReader(forceHTTPS bool, reader io.ReadCloser, excludes, specific []int, filter bool) ([]defs.Server, error) {
	defer reader.Close()

	var servers []defs.Server

	b, err := ioutil.ReadAll(reader)
	if err != nil {
		return nil, err
	}

	if err := json.Unmarshal(b, &servers); err != nil {
		return nil, err
	}

	return preprocessServers(servers, forceHTTPS, excludes, specific, filter)
}

// getLocalServers loads the server JSON from a local file
func getLocalServers(forceHTTPS bool, jsonFile string, excludes, specific []int, filter bool) ([]defs.Server, error) {
	f, err := os.OpenFile(jsonFile, os.O_RDONLY, 0644)
	if err != nil {
		return nil, err
	}
	return getLocalServersReader(forceHTTPS, f, excludes, specific, filter)
}

// preprocessServers makes some needed modifications to the servers fetched
func preprocessServers(servers []defs.Server, forceHTTPS bool, excludes, specific []int, filter bool) ([]defs.Server, error) {
	for i := range servers {
		u, err := servers[i].GetURL()
		if err != nil {
			return nil, err
		}

		// if no scheme is defined, use http as default, or https when --secure is given in cli options
		// if the scheme is predefined and --secure is not given, we will use it as-is
		if forceHTTPS {
			u.Scheme = "https"
		} else if u.Scheme == "" {
			// if `secure` is not used and no scheme is defined, use http
			u.Scheme = "http"
		}

		// modify the server struct in the array in place
		servers[i].Server = u.String()
	}

	if len(excludes) > 0 && len(specific) > 0 {
		return nil, errors.New("either --exclude or --specific can be used")
	}

	if filter {
		// exclude servers from --exclude
		if len(excludes) > 0 {
			var ret []defs.Server
			for _, server := range servers {
				if contains(excludes, server.ID) {
					continue
				}
				ret = append(ret, server)
			}
			return ret, nil
		}

		// use only servers from --server
		// special value -1 will test all servers
		if len(specific) > 0 && !contains(specific, -1) {
			var ret []defs.Server
			for _, server := range servers {
				if contains(specific, server.ID) {
					ret = append(ret, server)
				}
			}
			if len(ret) == 0 {
				error_message := fmt.Sprintf("specified server(s) not found: %v", specific)
				return nil, errors.New(error_message)
			}
			return ret, nil
		}
	}

	return servers, nil
}

// contains is a helper function to check if an int is in an int array
func contains(arr []int, val int) bool {
	for _, v := range arr {
		if v == val {
			return true
		}
	}
	return false
}

func newDialerAddressBound(src string, network string) (dialer *net.Dialer, err error) {
	// first we parse the IP to see if it's valid
	addr, err := net.ResolveIPAddr(network, src)
	if err != nil {
		if strings.Contains(err.Error(), "no suitable address") {
			if network == "ip6" {
				log.Errorf("Address %s is not a valid IPv6 address", src)
			} else {
				log.Errorf("Address %s is not a valid IPv4 address", src)
			}
		} else {
			log.Errorf("Error parsing source IP: %s", err)
		}
		return nil, err
	}

	log.Debugf("Using %s as source IP", src)
	localTCPAddr := &net.TCPAddr{IP: addr.IP}

	defaultDialer := &net.Dialer{
		Timeout:   30 * time.Second,
		KeepAlive: 30 * time.Second,
	}

	defaultDialer.LocalAddr = localTCPAddr
	return defaultDialer, nil
}
07070100000016000081A400000000000000000000000166E1605B000000E6000000000000000000000000000000000000002700000000speedtest-cli-1.0.11/speedtest/util.go//go:build !linux
// +build !linux

package speedtest

import (
	"fmt"
	"net"
)

func newDialerInterfaceBound(iface string) (dialer *net.Dialer, err error) {
	return nil, fmt.Errorf("cannot bound to interface on this platform")
}
07070100000017000081A400000000000000000000000166E1605B000002D9000000000000000000000000000000000000002D00000000speedtest-cli-1.0.11/speedtest/util_linux.gopackage speedtest

import (
	"net"
	"syscall"
	"time"

	"golang.org/x/sys/unix"
)

func newDialerInterfaceBound(iface string) (dialer *net.Dialer, err error) {
	// In linux there is the socket option SO_BINDTODEVICE.
	// Therefore we can really bind the socket to the device instead of binding to the address that
	// would be affected by the default routes.
	control := func(network, address string, c syscall.RawConn) error {
		var errSock error
		err := c.Control((func(fd uintptr) {
			errSock = unix.BindToDevice(int(fd), iface)
		}))
		if err != nil {
			return err
		}
		return errSock
	}

	dialer = &net.Dialer{
		Timeout:   30 * time.Second,
		KeepAlive: 30 * time.Second,
		Control:   control,
	}
	return dialer, nil
}
07070100000018000081ED00000000000000000000000166E1605B0000001A000000000000000000000000000000000000001C00000000speedtest-cli-1.0.11/upx.sh#!/bin/sh
upx "$@" || true07070100000000000000000000000000000000000000010000000000000000000000000000000000000000000000000000000B00000000TRAILER!!!181 blocks
openSUSE Build Service is sponsored by