From ed2f14f10c87d3eacbbb8af04c03d0a1123831e1 Mon Sep 17 00:00:00 2001 From: Pawan Pinjarkar Date: Wed, 10 Sep 2025 17:43:31 -0400 Subject: [PATCH 1/5] AGENT-1207: automate OVE cluster installation Automates OVE cluster installation using the Go-based library `go-rod` in headless mode. The workflow fills in cluster details, captures screenshots for each step, downloads credentials, and triggers the install action without requiring a visible browser. Verification steps include: - Confirming cluster installation completion - Running `oc get nodes`, `oc get clusterversion`, `oc get packagemanifests` and `oc wait clusterversion version --for=condition=Available=True --timeout=60m` - Ensuring the number of nodes matches values defined in dev-scripts --- agent/06_agent_create_cluster.sh | 38 +- agent/agent_post_install_validation.sh | 7 +- .../ui_driven_cluster_installation.go | 319 + go.mod | 14 +- go.sum | 42 + .../github.com/apparentlymart/go-cidr/LICENSE | 19 + .../apparentlymart/go-cidr/cidr/cidr.go | 236 + .../apparentlymart/go-cidr/cidr/wrangling.go | 37 + .../github.com/go-resty/resty/v2/.gitignore | 31 + .../github.com/go-resty/resty/v2/BUILD.bazel | 59 + vendor/github.com/go-resty/resty/v2/LICENSE | 21 + vendor/github.com/go-resty/resty/v2/README.md | 944 ++ vendor/github.com/go-resty/resty/v2/WORKSPACE | 31 + vendor/github.com/go-resty/resty/v2/client.go | 1500 +++ vendor/github.com/go-resty/resty/v2/digest.go | 327 + .../go-resty/resty/v2/middleware.go | 613 ++ .../github.com/go-resty/resty/v2/redirect.go | 106 + .../github.com/go-resty/resty/v2/request.go | 1190 +++ .../github.com/go-resty/resty/v2/response.go | 195 + vendor/github.com/go-resty/resty/v2/resty.go | 40 + vendor/github.com/go-resty/resty/v2/retry.go | 267 + .../go-resty/resty/v2/shellescape/BUILD.bazel | 14 + .../resty/v2/shellescape/shellescape.go | 40 + vendor/github.com/go-resty/resty/v2/trace.go | 124 + .../github.com/go-resty/resty/v2/transport.go | 36 + .../go-resty/resty/v2/transport112.go | 35 + .../go-resty/resty/v2/transport_js.go | 17 + .../go-resty/resty/v2/transport_other.go | 17 + vendor/github.com/go-resty/resty/v2/util.go | 389 + .../github.com/go-resty/resty/v2/util_curl.go | 78 + vendor/github.com/go-rod/rod/.eslintrc.yml | 9 + vendor/github.com/go-rod/rod/.gitignore | 9 + vendor/github.com/go-rod/rod/.golangci.yml | 110 + vendor/github.com/go-rod/rod/.prettierrc.yml | 3 + vendor/github.com/go-rod/rod/LICENSE | 9 + vendor/github.com/go-rod/rod/README.md | 51 + vendor/github.com/go-rod/rod/browser.go | 543 + vendor/github.com/go-rod/rod/context.go | 132 + vendor/github.com/go-rod/rod/dev_helpers.go | 264 + vendor/github.com/go-rod/rod/element.go | 754 ++ vendor/github.com/go-rod/rod/error.go | 193 + vendor/github.com/go-rod/rod/go.work | 8 + vendor/github.com/go-rod/rod/go.work.sum | 4 + vendor/github.com/go-rod/rod/hijack.go | 430 + vendor/github.com/go-rod/rod/input.go | 457 + .../go-rod/rod/lib/assets/README.md | 3 + .../go-rod/rod/lib/assets/assets.go | 189 + .../go-rod/rod/lib/assets/monitor-page.html | 103 + .../go-rod/rod/lib/assets/monitor.html | 53 + .../github.com/go-rod/rod/lib/cdp/README.md | 11 + .../github.com/go-rod/rod/lib/cdp/client.go | 175 + vendor/github.com/go-rod/rod/lib/cdp/error.go | 65 + .../github.com/go-rod/rod/lib/cdp/format.go | 53 + vendor/github.com/go-rod/rod/lib/cdp/utils.go | 46 + .../go-rod/rod/lib/cdp/websocket.go | 238 + .../go-rod/rod/lib/defaults/defaults.go | 203 + .../go-rod/rod/lib/devices/device.go | 101 + .../github.com/go-rod/rod/lib/devices/list.go | 690 ++ .../go-rod/rod/lib/devices/utils.go | 13 + .../github.com/go-rod/rod/lib/input/README.md | 3 + .../go-rod/rod/lib/input/keyboard.go | 138 + .../github.com/go-rod/rod/lib/input/keymap.go | 134 + .../go-rod/rod/lib/input/mac_comands.go | 125 + .../github.com/go-rod/rod/lib/input/mouse.go | 25 + vendor/github.com/go-rod/rod/lib/js/helper.go | 234 + vendor/github.com/go-rod/rod/lib/js/helper.js | 572 + vendor/github.com/go-rod/rod/lib/js/js.go | 21 + .../go-rod/rod/lib/launcher/README.md | 3 + .../go-rod/rod/lib/launcher/browser.go | 279 + .../go-rod/rod/lib/launcher/error.go | 6 + .../go-rod/rod/lib/launcher/flags/flags.go | 70 + .../go-rod/rod/lib/launcher/launcher.go | 556 + .../go-rod/rod/lib/launcher/manager.go | 211 + .../go-rod/rod/lib/launcher/os_unix.go | 26 + .../go-rod/rod/lib/launcher/os_windows.go | 28 + .../go-rod/rod/lib/launcher/revision.go | 9 + .../go-rod/rod/lib/launcher/url_parser.go | 139 + .../go-rod/rod/lib/launcher/utils.go | 49 + .../github.com/go-rod/rod/lib/proto/README.md | 7 + .../go-rod/rod/lib/proto/a_interface.go | 71 + .../go-rod/rod/lib/proto/a_patch.go | 180 + .../go-rod/rod/lib/proto/a_utils.go | 24 + .../go-rod/rod/lib/proto/accessibility.go | 588 + .../go-rod/rod/lib/proto/animation.go | 347 + .../github.com/go-rod/rod/lib/proto/audits.go | 1345 +++ .../go-rod/rod/lib/proto/autofill.go | 172 + .../rod/lib/proto/background_service.go | 157 + .../go-rod/rod/lib/proto/browser.go | 636 ++ .../go-rod/rod/lib/proto/cache_storage.go | 217 + .../github.com/go-rod/rod/lib/proto/cast.go | 135 + .../go-rod/rod/lib/proto/console.go | 135 + vendor/github.com/go-rod/rod/lib/proto/css.go | 1456 +++ .../go-rod/rod/lib/proto/database.go | 124 + .../go-rod/rod/lib/proto/debugger.go | 1256 +++ .../go-rod/rod/lib/proto/definitions.go | 1352 +++ .../go-rod/rod/lib/proto/device_access.go | 92 + .../rod/lib/proto/device_orientation.go | 44 + vendor/github.com/go-rod/rod/lib/proto/dom.go | 1712 +++ .../go-rod/rod/lib/proto/dom_debugger.go | 248 + .../go-rod/rod/lib/proto/dom_snapshot.go | 458 + .../go-rod/rod/lib/proto/dom_storage.go | 185 + .../go-rod/rod/lib/proto/emulation.go | 925 ++ .../go-rod/rod/lib/proto/event_breakpoints.go | 56 + .../go-rod/rod/lib/proto/extensions.go | 36 + .../github.com/go-rod/rod/lib/proto/fed_cm.go | 244 + .../github.com/go-rod/rod/lib/proto/fetch.go | 408 + .../rod/lib/proto/headless_experimental.go | 102 + .../go-rod/rod/lib/proto/heap_profiler.go | 346 + .../go-rod/rod/lib/proto/indexed_db.go | 392 + .../github.com/go-rod/rod/lib/proto/input.go | 626 ++ .../go-rod/rod/lib/proto/inspector.go | 58 + vendor/github.com/go-rod/rod/lib/proto/io.go | 84 + .../go-rod/rod/lib/proto/layer_tree.go | 332 + vendor/github.com/go-rod/rod/lib/proto/log.go | 221 + .../github.com/go-rod/rod/lib/proto/media.go | 197 + .../github.com/go-rod/rod/lib/proto/memory.go | 222 + .../go-rod/rod/lib/proto/network.go | 3140 ++++++ .../go-rod/rod/lib/proto/overlay.go | 921 ++ .../github.com/go-rod/rod/lib/proto/page.go | 3425 ++++++ .../go-rod/rod/lib/proto/performance.go | 113 + .../rod/lib/proto/performance_timeline.go | 115 + .../go-rod/rod/lib/proto/preload.go | 602 ++ .../go-rod/rod/lib/proto/profiler.go | 299 + vendor/github.com/go-rod/rod/lib/proto/pwa.go | 228 + .../go-rod/rod/lib/proto/runtime.go | 1508 +++ .../github.com/go-rod/rod/lib/proto/schema.go | 38 + .../go-rod/rod/lib/proto/security.go | 331 + .../go-rod/rod/lib/proto/service_worker.go | 350 + .../go-rod/rod/lib/proto/storage.go | 1624 +++ .../go-rod/rod/lib/proto/system_info.go | 232 + .../github.com/go-rod/rod/lib/proto/target.go | 585 + .../go-rod/rod/lib/proto/tethering.go | 53 + .../go-rod/rod/lib/proto/tracing.go | 298 + .../go-rod/rod/lib/proto/web_audio.go | 426 + .../go-rod/rod/lib/proto/web_authn.go | 429 + .../go-rod/rod/lib/utils/imageutil.go | 134 + .../go-rod/rod/lib/utils/sleeper.go | 149 + .../github.com/go-rod/rod/lib/utils/utils.go | 379 + vendor/github.com/go-rod/rod/must.go | 1172 ++ vendor/github.com/go-rod/rod/page.go | 1061 ++ vendor/github.com/go-rod/rod/page_eval.go | 380 + vendor/github.com/go-rod/rod/query.go | 543 + vendor/github.com/go-rod/rod/states.go | 119 + vendor/github.com/go-rod/rod/utils.go | 271 + vendor/github.com/openshift/installer/LICENSE | 202 + vendor/github.com/openshift/installer/NOTICE | 5 + .../openshift/installer/pkg/ipnet/ipnet.go | 97 + vendor/github.com/pkg/errors/.gitignore | 24 + vendor/github.com/pkg/errors/.travis.yml | 10 + vendor/github.com/pkg/errors/LICENSE | 23 + vendor/github.com/pkg/errors/Makefile | 44 + vendor/github.com/pkg/errors/README.md | 59 + vendor/github.com/pkg/errors/appveyor.yml | 32 + vendor/github.com/pkg/errors/errors.go | 288 + vendor/github.com/pkg/errors/go113.go | 38 + vendor/github.com/pkg/errors/stack.go | 177 + vendor/github.com/sirupsen/logrus/.gitignore | 4 + .../github.com/sirupsen/logrus/.golangci.yml | 40 + vendor/github.com/sirupsen/logrus/.travis.yml | 15 + .../github.com/sirupsen/logrus/CHANGELOG.md | 259 + vendor/github.com/sirupsen/logrus/LICENSE | 21 + vendor/github.com/sirupsen/logrus/README.md | 515 + vendor/github.com/sirupsen/logrus/alt_exit.go | 76 + .../github.com/sirupsen/logrus/appveyor.yml | 14 + .../github.com/sirupsen/logrus/buffer_pool.go | 43 + vendor/github.com/sirupsen/logrus/doc.go | 26 + vendor/github.com/sirupsen/logrus/entry.go | 442 + vendor/github.com/sirupsen/logrus/exported.go | 270 + .../github.com/sirupsen/logrus/formatter.go | 78 + vendor/github.com/sirupsen/logrus/hooks.go | 34 + .../sirupsen/logrus/json_formatter.go | 128 + vendor/github.com/sirupsen/logrus/logger.go | 417 + vendor/github.com/sirupsen/logrus/logrus.go | 186 + .../logrus/terminal_check_appengine.go | 11 + .../sirupsen/logrus/terminal_check_bsd.go | 13 + .../sirupsen/logrus/terminal_check_js.go | 7 + .../logrus/terminal_check_no_terminal.go | 11 + .../logrus/terminal_check_notappengine.go | 17 + .../sirupsen/logrus/terminal_check_solaris.go | 11 + .../sirupsen/logrus/terminal_check_unix.go | 13 + .../sirupsen/logrus/terminal_check_windows.go | 27 + .../sirupsen/logrus/text_formatter.go | 339 + vendor/github.com/sirupsen/logrus/writer.go | 102 + vendor/github.com/ysmood/fetchup/.gitignore | 2 + vendor/github.com/ysmood/fetchup/LICENSE | 21 + vendor/github.com/ysmood/fetchup/README.md | 3 + vendor/github.com/ysmood/fetchup/download.go | 230 + vendor/github.com/ysmood/fetchup/events.go | 10 + vendor/github.com/ysmood/fetchup/fetchup.go | 107 + vendor/github.com/ysmood/fetchup/utils.go | 201 + vendor/github.com/ysmood/goob/.gitignore | 1 + vendor/github.com/ysmood/goob/.golangci.yml | 18 + vendor/github.com/ysmood/goob/LICENSE | 9 + vendor/github.com/ysmood/goob/goob.go | 67 + vendor/github.com/ysmood/goob/pipe.go | 63 + vendor/github.com/ysmood/goob/readme.md | 25 + vendor/github.com/ysmood/got/LICENSE | 9 + vendor/github.com/ysmood/got/lib/lcs/lcs.go | 157 + .../github.com/ysmood/got/lib/lcs/sequence.go | 133 + vendor/github.com/ysmood/got/lib/lcs/utils.go | 64 + vendor/github.com/ysmood/gson/.gitignore | 2 + vendor/github.com/ysmood/gson/LICENSE | 9 + vendor/github.com/ysmood/gson/README.md | 7 + vendor/github.com/ysmood/gson/read.go | 261 + vendor/github.com/ysmood/gson/write.go | 175 + vendor/github.com/ysmood/leakless/.gitignore | 3 + .../github.com/ysmood/leakless/.golangci.yml | 12 + vendor/github.com/ysmood/leakless/LICENSE | 9 + .../ysmood/leakless/bin_amd64_darwin.go | 5 + .../ysmood/leakless/bin_amd64_linux.go | 5 + .../ysmood/leakless/bin_amd64_windows.go | 5 + .../ysmood/leakless/bin_arm64_darwin.go | 5 + .../ysmood/leakless/bin_arm64_linux.go | 5 + vendor/github.com/ysmood/leakless/leakless.go | 155 + .../ysmood/leakless/pkg/shared/message.go | 8 + .../ysmood/leakless/pkg/shared/version.go | 4 + .../ysmood/leakless/pkg/utils/target.go | 30 + .../ysmood/leakless/pkg/utils/utils.go | 139 + vendor/github.com/ysmood/leakless/readme.md | 26 + vendor/golang.org/x/net/LICENSE | 27 + vendor/golang.org/x/net/PATENTS | 22 + .../x/net/publicsuffix/data/children | Bin 0 -> 2976 bytes .../golang.org/x/net/publicsuffix/data/nodes | Bin 0 -> 46610 bytes .../golang.org/x/net/publicsuffix/data/text | 1 + vendor/golang.org/x/net/publicsuffix/list.go | 203 + vendor/golang.org/x/net/publicsuffix/table.go | 70 + vendor/golang.org/x/sys/LICENSE | 27 + vendor/golang.org/x/sys/PATENTS | 22 + vendor/golang.org/x/sys/unix/.gitignore | 2 + vendor/golang.org/x/sys/unix/README.md | 184 + .../golang.org/x/sys/unix/affinity_linux.go | 86 + vendor/golang.org/x/sys/unix/aliases.go | 13 + vendor/golang.org/x/sys/unix/asm_aix_ppc64.s | 17 + vendor/golang.org/x/sys/unix/asm_bsd_386.s | 27 + vendor/golang.org/x/sys/unix/asm_bsd_amd64.s | 27 + vendor/golang.org/x/sys/unix/asm_bsd_arm.s | 27 + vendor/golang.org/x/sys/unix/asm_bsd_arm64.s | 27 + vendor/golang.org/x/sys/unix/asm_bsd_ppc64.s | 29 + .../golang.org/x/sys/unix/asm_bsd_riscv64.s | 27 + vendor/golang.org/x/sys/unix/asm_linux_386.s | 65 + .../golang.org/x/sys/unix/asm_linux_amd64.s | 57 + vendor/golang.org/x/sys/unix/asm_linux_arm.s | 56 + .../golang.org/x/sys/unix/asm_linux_arm64.s | 50 + .../golang.org/x/sys/unix/asm_linux_loong64.s | 51 + .../golang.org/x/sys/unix/asm_linux_mips64x.s | 54 + .../golang.org/x/sys/unix/asm_linux_mipsx.s | 52 + .../golang.org/x/sys/unix/asm_linux_ppc64x.s | 42 + .../golang.org/x/sys/unix/asm_linux_riscv64.s | 47 + .../golang.org/x/sys/unix/asm_linux_s390x.s | 54 + .../x/sys/unix/asm_openbsd_mips64.s | 29 + .../golang.org/x/sys/unix/asm_solaris_amd64.s | 17 + vendor/golang.org/x/sys/unix/asm_zos_s390x.s | 382 + .../golang.org/x/sys/unix/bluetooth_linux.go | 36 + vendor/golang.org/x/sys/unix/bpxsvc_zos.go | 657 ++ vendor/golang.org/x/sys/unix/bpxsvc_zos.s | 192 + vendor/golang.org/x/sys/unix/cap_freebsd.go | 195 + vendor/golang.org/x/sys/unix/constants.go | 13 + vendor/golang.org/x/sys/unix/dev_aix_ppc.go | 26 + vendor/golang.org/x/sys/unix/dev_aix_ppc64.go | 28 + vendor/golang.org/x/sys/unix/dev_darwin.go | 24 + vendor/golang.org/x/sys/unix/dev_dragonfly.go | 30 + vendor/golang.org/x/sys/unix/dev_freebsd.go | 30 + vendor/golang.org/x/sys/unix/dev_linux.go | 42 + vendor/golang.org/x/sys/unix/dev_netbsd.go | 29 + vendor/golang.org/x/sys/unix/dev_openbsd.go | 29 + vendor/golang.org/x/sys/unix/dev_zos.go | 28 + vendor/golang.org/x/sys/unix/dirent.go | 102 + vendor/golang.org/x/sys/unix/endian_big.go | 9 + vendor/golang.org/x/sys/unix/endian_little.go | 9 + vendor/golang.org/x/sys/unix/env_unix.go | 31 + vendor/golang.org/x/sys/unix/fcntl.go | 36 + vendor/golang.org/x/sys/unix/fcntl_darwin.go | 24 + .../x/sys/unix/fcntl_linux_32bit.go | 13 + vendor/golang.org/x/sys/unix/fdset.go | 29 + vendor/golang.org/x/sys/unix/gccgo.go | 59 + vendor/golang.org/x/sys/unix/gccgo_c.c | 44 + .../x/sys/unix/gccgo_linux_amd64.go | 20 + vendor/golang.org/x/sys/unix/ifreq_linux.go | 141 + vendor/golang.org/x/sys/unix/ioctl_linux.go | 334 + vendor/golang.org/x/sys/unix/ioctl_signed.go | 69 + .../golang.org/x/sys/unix/ioctl_unsigned.go | 69 + vendor/golang.org/x/sys/unix/ioctl_zos.go | 71 + vendor/golang.org/x/sys/unix/mkall.sh | 249 + vendor/golang.org/x/sys/unix/mkerrors.sh | 805 ++ vendor/golang.org/x/sys/unix/mmap_nomremap.go | 13 + vendor/golang.org/x/sys/unix/mremap.go | 57 + vendor/golang.org/x/sys/unix/pagesize_unix.go | 15 + .../golang.org/x/sys/unix/pledge_openbsd.go | 111 + vendor/golang.org/x/sys/unix/ptrace_darwin.go | 11 + vendor/golang.org/x/sys/unix/ptrace_ios.go | 11 + vendor/golang.org/x/sys/unix/race.go | 30 + vendor/golang.org/x/sys/unix/race0.go | 25 + .../x/sys/unix/readdirent_getdents.go | 12 + .../x/sys/unix/readdirent_getdirentries.go | 19 + .../x/sys/unix/sockcmsg_dragonfly.go | 16 + .../golang.org/x/sys/unix/sockcmsg_linux.go | 85 + vendor/golang.org/x/sys/unix/sockcmsg_unix.go | 106 + .../x/sys/unix/sockcmsg_unix_other.go | 46 + vendor/golang.org/x/sys/unix/sockcmsg_zos.go | 58 + .../golang.org/x/sys/unix/symaddr_zos_s390x.s | 75 + vendor/golang.org/x/sys/unix/syscall.go | 86 + vendor/golang.org/x/sys/unix/syscall_aix.go | 582 + .../golang.org/x/sys/unix/syscall_aix_ppc.go | 52 + .../x/sys/unix/syscall_aix_ppc64.go | 83 + vendor/golang.org/x/sys/unix/syscall_bsd.go | 609 ++ .../golang.org/x/sys/unix/syscall_darwin.go | 707 ++ .../x/sys/unix/syscall_darwin_amd64.go | 50 + .../x/sys/unix/syscall_darwin_arm64.go | 50 + .../x/sys/unix/syscall_darwin_libSystem.go | 26 + .../x/sys/unix/syscall_dragonfly.go | 347 + .../x/sys/unix/syscall_dragonfly_amd64.go | 56 + .../golang.org/x/sys/unix/syscall_freebsd.go | 455 + .../x/sys/unix/syscall_freebsd_386.go | 64 + .../x/sys/unix/syscall_freebsd_amd64.go | 64 + .../x/sys/unix/syscall_freebsd_arm.go | 60 + .../x/sys/unix/syscall_freebsd_arm64.go | 60 + .../x/sys/unix/syscall_freebsd_riscv64.go | 60 + vendor/golang.org/x/sys/unix/syscall_hurd.go | 30 + .../golang.org/x/sys/unix/syscall_hurd_386.go | 28 + .../golang.org/x/sys/unix/syscall_illumos.go | 78 + vendor/golang.org/x/sys/unix/syscall_linux.go | 2657 +++++ .../x/sys/unix/syscall_linux_386.go | 314 + .../x/sys/unix/syscall_linux_alarm.go | 12 + .../x/sys/unix/syscall_linux_amd64.go | 145 + .../x/sys/unix/syscall_linux_amd64_gc.go | 12 + .../x/sys/unix/syscall_linux_arm.go | 216 + .../x/sys/unix/syscall_linux_arm64.go | 186 + .../golang.org/x/sys/unix/syscall_linux_gc.go | 14 + .../x/sys/unix/syscall_linux_gc_386.go | 16 + .../x/sys/unix/syscall_linux_gc_arm.go | 13 + .../x/sys/unix/syscall_linux_gccgo_386.go | 30 + .../x/sys/unix/syscall_linux_gccgo_arm.go | 20 + .../x/sys/unix/syscall_linux_loong64.go | 218 + .../x/sys/unix/syscall_linux_mips64x.go | 188 + .../x/sys/unix/syscall_linux_mipsx.go | 174 + .../x/sys/unix/syscall_linux_ppc.go | 204 + .../x/sys/unix/syscall_linux_ppc64x.go | 115 + .../x/sys/unix/syscall_linux_riscv64.go | 191 + .../x/sys/unix/syscall_linux_s390x.go | 296 + .../x/sys/unix/syscall_linux_sparc64.go | 112 + .../golang.org/x/sys/unix/syscall_netbsd.go | 371 + .../x/sys/unix/syscall_netbsd_386.go | 37 + .../x/sys/unix/syscall_netbsd_amd64.go | 37 + .../x/sys/unix/syscall_netbsd_arm.go | 37 + .../x/sys/unix/syscall_netbsd_arm64.go | 37 + .../golang.org/x/sys/unix/syscall_openbsd.go | 342 + .../x/sys/unix/syscall_openbsd_386.go | 41 + .../x/sys/unix/syscall_openbsd_amd64.go | 41 + .../x/sys/unix/syscall_openbsd_arm.go | 41 + .../x/sys/unix/syscall_openbsd_arm64.go | 41 + .../x/sys/unix/syscall_openbsd_libc.go | 26 + .../x/sys/unix/syscall_openbsd_mips64.go | 39 + .../x/sys/unix/syscall_openbsd_ppc64.go | 41 + .../x/sys/unix/syscall_openbsd_riscv64.go | 41 + .../golang.org/x/sys/unix/syscall_solaris.go | 1104 ++ .../x/sys/unix/syscall_solaris_amd64.go | 27 + vendor/golang.org/x/sys/unix/syscall_unix.go | 615 ++ .../golang.org/x/sys/unix/syscall_unix_gc.go | 14 + .../x/sys/unix/syscall_unix_gc_ppc64x.go | 22 + .../x/sys/unix/syscall_zos_s390x.go | 3213 ++++++ vendor/golang.org/x/sys/unix/sysvshm_linux.go | 20 + vendor/golang.org/x/sys/unix/sysvshm_unix.go | 51 + .../x/sys/unix/sysvshm_unix_other.go | 13 + vendor/golang.org/x/sys/unix/timestruct.go | 76 + .../golang.org/x/sys/unix/unveil_openbsd.go | 51 + .../golang.org/x/sys/unix/vgetrandom_linux.go | 13 + .../x/sys/unix/vgetrandom_unsupported.go | 11 + vendor/golang.org/x/sys/unix/xattr_bsd.go | 280 + .../golang.org/x/sys/unix/zerrors_aix_ppc.go | 1384 +++ .../x/sys/unix/zerrors_aix_ppc64.go | 1385 +++ .../x/sys/unix/zerrors_darwin_amd64.go | 1922 ++++ .../x/sys/unix/zerrors_darwin_arm64.go | 1922 ++++ .../x/sys/unix/zerrors_dragonfly_amd64.go | 1737 +++ .../x/sys/unix/zerrors_freebsd_386.go | 2042 ++++ .../x/sys/unix/zerrors_freebsd_amd64.go | 2039 ++++ .../x/sys/unix/zerrors_freebsd_arm.go | 2033 ++++ .../x/sys/unix/zerrors_freebsd_arm64.go | 2033 ++++ .../x/sys/unix/zerrors_freebsd_riscv64.go | 2147 ++++ vendor/golang.org/x/sys/unix/zerrors_linux.go | 3734 +++++++ .../x/sys/unix/zerrors_linux_386.go | 870 ++ .../x/sys/unix/zerrors_linux_amd64.go | 870 ++ .../x/sys/unix/zerrors_linux_arm.go | 875 ++ .../x/sys/unix/zerrors_linux_arm64.go | 871 ++ .../x/sys/unix/zerrors_linux_loong64.go | 862 ++ .../x/sys/unix/zerrors_linux_mips.go | 876 ++ .../x/sys/unix/zerrors_linux_mips64.go | 876 ++ .../x/sys/unix/zerrors_linux_mips64le.go | 876 ++ .../x/sys/unix/zerrors_linux_mipsle.go | 876 ++ .../x/sys/unix/zerrors_linux_ppc.go | 928 ++ .../x/sys/unix/zerrors_linux_ppc64.go | 932 ++ .../x/sys/unix/zerrors_linux_ppc64le.go | 932 ++ .../x/sys/unix/zerrors_linux_riscv64.go | 859 ++ .../x/sys/unix/zerrors_linux_s390x.go | 931 ++ .../x/sys/unix/zerrors_linux_sparc64.go | 974 ++ .../x/sys/unix/zerrors_netbsd_386.go | 1779 ++++ .../x/sys/unix/zerrors_netbsd_amd64.go | 1769 +++ .../x/sys/unix/zerrors_netbsd_arm.go | 1758 +++ .../x/sys/unix/zerrors_netbsd_arm64.go | 1769 +++ .../x/sys/unix/zerrors_openbsd_386.go | 1905 ++++ .../x/sys/unix/zerrors_openbsd_amd64.go | 1905 ++++ .../x/sys/unix/zerrors_openbsd_arm.go | 1905 ++++ .../x/sys/unix/zerrors_openbsd_arm64.go | 1905 ++++ .../x/sys/unix/zerrors_openbsd_mips64.go | 1905 ++++ .../x/sys/unix/zerrors_openbsd_ppc64.go | 1904 ++++ .../x/sys/unix/zerrors_openbsd_riscv64.go | 1903 ++++ .../x/sys/unix/zerrors_solaris_amd64.go | 1556 +++ .../x/sys/unix/zerrors_zos_s390x.go | 990 ++ .../x/sys/unix/zptrace_armnn_linux.go | 40 + .../x/sys/unix/zptrace_linux_arm64.go | 17 + .../x/sys/unix/zptrace_mipsnn_linux.go | 49 + .../x/sys/unix/zptrace_mipsnnle_linux.go | 49 + .../x/sys/unix/zptrace_x86_linux.go | 79 + .../x/sys/unix/zsymaddr_zos_s390x.s | 364 + .../golang.org/x/sys/unix/zsyscall_aix_ppc.go | 1461 +++ .../x/sys/unix/zsyscall_aix_ppc64.go | 1420 +++ .../x/sys/unix/zsyscall_aix_ppc64_gc.go | 1188 +++ .../x/sys/unix/zsyscall_aix_ppc64_gccgo.go | 1069 ++ .../x/sys/unix/zsyscall_darwin_amd64.go | 2644 +++++ .../x/sys/unix/zsyscall_darwin_amd64.s | 779 ++ .../x/sys/unix/zsyscall_darwin_arm64.go | 2644 +++++ .../x/sys/unix/zsyscall_darwin_arm64.s | 779 ++ .../x/sys/unix/zsyscall_dragonfly_amd64.go | 1666 +++ .../x/sys/unix/zsyscall_freebsd_386.go | 1886 ++++ .../x/sys/unix/zsyscall_freebsd_amd64.go | 1886 ++++ .../x/sys/unix/zsyscall_freebsd_arm.go | 1886 ++++ .../x/sys/unix/zsyscall_freebsd_arm64.go | 1886 ++++ .../x/sys/unix/zsyscall_freebsd_riscv64.go | 1886 ++++ .../x/sys/unix/zsyscall_illumos_amd64.go | 101 + .../golang.org/x/sys/unix/zsyscall_linux.go | 2240 ++++ .../x/sys/unix/zsyscall_linux_386.go | 486 + .../x/sys/unix/zsyscall_linux_amd64.go | 653 ++ .../x/sys/unix/zsyscall_linux_arm.go | 601 ++ .../x/sys/unix/zsyscall_linux_arm64.go | 552 + .../x/sys/unix/zsyscall_linux_loong64.go | 486 + .../x/sys/unix/zsyscall_linux_mips.go | 653 ++ .../x/sys/unix/zsyscall_linux_mips64.go | 647 ++ .../x/sys/unix/zsyscall_linux_mips64le.go | 636 ++ .../x/sys/unix/zsyscall_linux_mipsle.go | 653 ++ .../x/sys/unix/zsyscall_linux_ppc.go | 658 ++ .../x/sys/unix/zsyscall_linux_ppc64.go | 704 ++ .../x/sys/unix/zsyscall_linux_ppc64le.go | 704 ++ .../x/sys/unix/zsyscall_linux_riscv64.go | 548 + .../x/sys/unix/zsyscall_linux_s390x.go | 495 + .../x/sys/unix/zsyscall_linux_sparc64.go | 648 ++ .../x/sys/unix/zsyscall_netbsd_386.go | 1848 ++++ .../x/sys/unix/zsyscall_netbsd_amd64.go | 1848 ++++ .../x/sys/unix/zsyscall_netbsd_arm.go | 1848 ++++ .../x/sys/unix/zsyscall_netbsd_arm64.go | 1848 ++++ .../x/sys/unix/zsyscall_openbsd_386.go | 2323 ++++ .../x/sys/unix/zsyscall_openbsd_386.s | 699 ++ .../x/sys/unix/zsyscall_openbsd_amd64.go | 2323 ++++ .../x/sys/unix/zsyscall_openbsd_amd64.s | 699 ++ .../x/sys/unix/zsyscall_openbsd_arm.go | 2323 ++++ .../x/sys/unix/zsyscall_openbsd_arm.s | 699 ++ .../x/sys/unix/zsyscall_openbsd_arm64.go | 2323 ++++ .../x/sys/unix/zsyscall_openbsd_arm64.s | 699 ++ .../x/sys/unix/zsyscall_openbsd_mips64.go | 2323 ++++ .../x/sys/unix/zsyscall_openbsd_mips64.s | 699 ++ .../x/sys/unix/zsyscall_openbsd_ppc64.go | 2323 ++++ .../x/sys/unix/zsyscall_openbsd_ppc64.s | 838 ++ .../x/sys/unix/zsyscall_openbsd_riscv64.go | 2323 ++++ .../x/sys/unix/zsyscall_openbsd_riscv64.s | 699 ++ .../x/sys/unix/zsyscall_solaris_amd64.go | 2103 ++++ .../x/sys/unix/zsyscall_zos_s390x.go | 3458 ++++++ .../x/sys/unix/zsysctl_openbsd_386.go | 280 + .../x/sys/unix/zsysctl_openbsd_amd64.go | 280 + .../x/sys/unix/zsysctl_openbsd_arm.go | 280 + .../x/sys/unix/zsysctl_openbsd_arm64.go | 280 + .../x/sys/unix/zsysctl_openbsd_mips64.go | 280 + .../x/sys/unix/zsysctl_openbsd_ppc64.go | 280 + .../x/sys/unix/zsysctl_openbsd_riscv64.go | 281 + .../x/sys/unix/zsysnum_darwin_amd64.go | 439 + .../x/sys/unix/zsysnum_darwin_arm64.go | 437 + .../x/sys/unix/zsysnum_dragonfly_amd64.go | 316 + .../x/sys/unix/zsysnum_freebsd_386.go | 393 + .../x/sys/unix/zsysnum_freebsd_amd64.go | 393 + .../x/sys/unix/zsysnum_freebsd_arm.go | 393 + .../x/sys/unix/zsysnum_freebsd_arm64.go | 393 + .../x/sys/unix/zsysnum_freebsd_riscv64.go | 393 + .../x/sys/unix/zsysnum_linux_386.go | 461 + .../x/sys/unix/zsysnum_linux_amd64.go | 384 + .../x/sys/unix/zsysnum_linux_arm.go | 425 + .../x/sys/unix/zsysnum_linux_arm64.go | 328 + .../x/sys/unix/zsysnum_linux_loong64.go | 324 + .../x/sys/unix/zsysnum_linux_mips.go | 445 + .../x/sys/unix/zsysnum_linux_mips64.go | 375 + .../x/sys/unix/zsysnum_linux_mips64le.go | 375 + .../x/sys/unix/zsysnum_linux_mipsle.go | 445 + .../x/sys/unix/zsysnum_linux_ppc.go | 452 + .../x/sys/unix/zsysnum_linux_ppc64.go | 424 + .../x/sys/unix/zsysnum_linux_ppc64le.go | 424 + .../x/sys/unix/zsysnum_linux_riscv64.go | 329 + .../x/sys/unix/zsysnum_linux_s390x.go | 390 + .../x/sys/unix/zsysnum_linux_sparc64.go | 403 + .../x/sys/unix/zsysnum_netbsd_386.go | 274 + .../x/sys/unix/zsysnum_netbsd_amd64.go | 274 + .../x/sys/unix/zsysnum_netbsd_arm.go | 274 + .../x/sys/unix/zsysnum_netbsd_arm64.go | 274 + .../x/sys/unix/zsysnum_openbsd_386.go | 219 + .../x/sys/unix/zsysnum_openbsd_amd64.go | 219 + .../x/sys/unix/zsysnum_openbsd_arm.go | 219 + .../x/sys/unix/zsysnum_openbsd_arm64.go | 218 + .../x/sys/unix/zsysnum_openbsd_mips64.go | 221 + .../x/sys/unix/zsysnum_openbsd_ppc64.go | 217 + .../x/sys/unix/zsysnum_openbsd_riscv64.go | 218 + .../x/sys/unix/zsysnum_zos_s390x.go | 2852 +++++ .../golang.org/x/sys/unix/ztypes_aix_ppc.go | 353 + .../golang.org/x/sys/unix/ztypes_aix_ppc64.go | 357 + .../x/sys/unix/ztypes_darwin_amd64.go | 878 ++ .../x/sys/unix/ztypes_darwin_arm64.go | 878 ++ .../x/sys/unix/ztypes_dragonfly_amd64.go | 473 + .../x/sys/unix/ztypes_freebsd_386.go | 651 ++ .../x/sys/unix/ztypes_freebsd_amd64.go | 656 ++ .../x/sys/unix/ztypes_freebsd_arm.go | 642 ++ .../x/sys/unix/ztypes_freebsd_arm64.go | 636 ++ .../x/sys/unix/ztypes_freebsd_riscv64.go | 638 ++ vendor/golang.org/x/sys/unix/ztypes_linux.go | 6176 +++++++++++ .../golang.org/x/sys/unix/ztypes_linux_386.go | 689 ++ .../x/sys/unix/ztypes_linux_amd64.go | 703 ++ .../golang.org/x/sys/unix/ztypes_linux_arm.go | 683 ++ .../x/sys/unix/ztypes_linux_arm64.go | 682 ++ .../x/sys/unix/ztypes_linux_loong64.go | 683 ++ .../x/sys/unix/ztypes_linux_mips.go | 688 ++ .../x/sys/unix/ztypes_linux_mips64.go | 685 ++ .../x/sys/unix/ztypes_linux_mips64le.go | 685 ++ .../x/sys/unix/ztypes_linux_mipsle.go | 688 ++ .../golang.org/x/sys/unix/ztypes_linux_ppc.go | 696 ++ .../x/sys/unix/ztypes_linux_ppc64.go | 691 ++ .../x/sys/unix/ztypes_linux_ppc64le.go | 691 ++ .../x/sys/unix/ztypes_linux_riscv64.go | 770 ++ .../x/sys/unix/ztypes_linux_s390x.go | 705 ++ .../x/sys/unix/ztypes_linux_sparc64.go | 686 ++ .../x/sys/unix/ztypes_netbsd_386.go | 585 + .../x/sys/unix/ztypes_netbsd_amd64.go | 593 ++ .../x/sys/unix/ztypes_netbsd_arm.go | 590 + .../x/sys/unix/ztypes_netbsd_arm64.go | 593 ++ .../x/sys/unix/ztypes_openbsd_386.go | 568 + .../x/sys/unix/ztypes_openbsd_amd64.go | 568 + .../x/sys/unix/ztypes_openbsd_arm.go | 575 + .../x/sys/unix/ztypes_openbsd_arm64.go | 568 + .../x/sys/unix/ztypes_openbsd_mips64.go | 568 + .../x/sys/unix/ztypes_openbsd_ppc64.go | 570 + .../x/sys/unix/ztypes_openbsd_riscv64.go | 570 + .../x/sys/unix/ztypes_solaris_amd64.go | 516 + .../golang.org/x/sys/unix/ztypes_zos_s390x.go | 552 + vendor/golang.org/x/sys/windows/aliases.go | 12 + .../golang.org/x/sys/windows/dll_windows.go | 416 + .../golang.org/x/sys/windows/env_windows.go | 57 + vendor/golang.org/x/sys/windows/eventlog.go | 20 + .../golang.org/x/sys/windows/exec_windows.go | 248 + .../x/sys/windows/memory_windows.go | 48 + vendor/golang.org/x/sys/windows/mkerrors.bash | 70 + .../x/sys/windows/mkknownfolderids.bash | 27 + vendor/golang.org/x/sys/windows/mksyscall.go | 9 + vendor/golang.org/x/sys/windows/race.go | 30 + vendor/golang.org/x/sys/windows/race0.go | 25 + .../x/sys/windows/security_windows.go | 1458 +++ vendor/golang.org/x/sys/windows/service.go | 257 + .../x/sys/windows/setupapi_windows.go | 1425 +++ vendor/golang.org/x/sys/windows/str.go | 22 + vendor/golang.org/x/sys/windows/syscall.go | 104 + .../x/sys/windows/syscall_windows.go | 1932 ++++ .../golang.org/x/sys/windows/types_windows.go | 3603 +++++++ .../x/sys/windows/types_windows_386.go | 35 + .../x/sys/windows/types_windows_amd64.go | 34 + .../x/sys/windows/types_windows_arm.go | 35 + .../x/sys/windows/types_windows_arm64.go | 34 + .../x/sys/windows/zerrors_windows.go | 9468 +++++++++++++++++ .../x/sys/windows/zknownfolderids_windows.go | 149 + .../x/sys/windows/zsyscall_windows.go | 4686 ++++++++ vendor/modules.txt | 53 + 571 files changed, 274980 insertions(+), 35 deletions(-) create mode 100644 agent/isobuilder/ui_driven_cluster_installation.go create mode 100644 vendor/github.com/apparentlymart/go-cidr/LICENSE create mode 100644 vendor/github.com/apparentlymart/go-cidr/cidr/cidr.go create mode 100644 vendor/github.com/apparentlymart/go-cidr/cidr/wrangling.go create mode 100644 vendor/github.com/go-resty/resty/v2/.gitignore create mode 100644 vendor/github.com/go-resty/resty/v2/BUILD.bazel create mode 100644 vendor/github.com/go-resty/resty/v2/LICENSE create mode 100644 vendor/github.com/go-resty/resty/v2/README.md create mode 100644 vendor/github.com/go-resty/resty/v2/WORKSPACE create mode 100644 vendor/github.com/go-resty/resty/v2/client.go create mode 100644 vendor/github.com/go-resty/resty/v2/digest.go create mode 100644 vendor/github.com/go-resty/resty/v2/middleware.go create mode 100644 vendor/github.com/go-resty/resty/v2/redirect.go create mode 100644 vendor/github.com/go-resty/resty/v2/request.go create mode 100644 vendor/github.com/go-resty/resty/v2/response.go create mode 100644 vendor/github.com/go-resty/resty/v2/resty.go create mode 100644 vendor/github.com/go-resty/resty/v2/retry.go create mode 100644 vendor/github.com/go-resty/resty/v2/shellescape/BUILD.bazel create mode 100644 vendor/github.com/go-resty/resty/v2/shellescape/shellescape.go create mode 100644 vendor/github.com/go-resty/resty/v2/trace.go create mode 100644 vendor/github.com/go-resty/resty/v2/transport.go create mode 100644 vendor/github.com/go-resty/resty/v2/transport112.go create mode 100644 vendor/github.com/go-resty/resty/v2/transport_js.go create mode 100644 vendor/github.com/go-resty/resty/v2/transport_other.go create mode 100644 vendor/github.com/go-resty/resty/v2/util.go create mode 100644 vendor/github.com/go-resty/resty/v2/util_curl.go create mode 100644 vendor/github.com/go-rod/rod/.eslintrc.yml create mode 100644 vendor/github.com/go-rod/rod/.gitignore create mode 100644 vendor/github.com/go-rod/rod/.golangci.yml create mode 100644 vendor/github.com/go-rod/rod/.prettierrc.yml create mode 100644 vendor/github.com/go-rod/rod/LICENSE create mode 100644 vendor/github.com/go-rod/rod/README.md create mode 100644 vendor/github.com/go-rod/rod/browser.go create mode 100644 vendor/github.com/go-rod/rod/context.go create mode 100644 vendor/github.com/go-rod/rod/dev_helpers.go create mode 100644 vendor/github.com/go-rod/rod/element.go create mode 100644 vendor/github.com/go-rod/rod/error.go create mode 100644 vendor/github.com/go-rod/rod/go.work create mode 100644 vendor/github.com/go-rod/rod/go.work.sum create mode 100644 vendor/github.com/go-rod/rod/hijack.go create mode 100644 vendor/github.com/go-rod/rod/input.go create mode 100644 vendor/github.com/go-rod/rod/lib/assets/README.md create mode 100644 vendor/github.com/go-rod/rod/lib/assets/assets.go create mode 100644 vendor/github.com/go-rod/rod/lib/assets/monitor-page.html create mode 100644 vendor/github.com/go-rod/rod/lib/assets/monitor.html create mode 100644 vendor/github.com/go-rod/rod/lib/cdp/README.md create mode 100644 vendor/github.com/go-rod/rod/lib/cdp/client.go create mode 100644 vendor/github.com/go-rod/rod/lib/cdp/error.go create mode 100644 vendor/github.com/go-rod/rod/lib/cdp/format.go create mode 100644 vendor/github.com/go-rod/rod/lib/cdp/utils.go create mode 100644 vendor/github.com/go-rod/rod/lib/cdp/websocket.go create mode 100644 vendor/github.com/go-rod/rod/lib/defaults/defaults.go create mode 100644 vendor/github.com/go-rod/rod/lib/devices/device.go create mode 100644 vendor/github.com/go-rod/rod/lib/devices/list.go create mode 100644 vendor/github.com/go-rod/rod/lib/devices/utils.go create mode 100644 vendor/github.com/go-rod/rod/lib/input/README.md create mode 100644 vendor/github.com/go-rod/rod/lib/input/keyboard.go create mode 100644 vendor/github.com/go-rod/rod/lib/input/keymap.go create mode 100644 vendor/github.com/go-rod/rod/lib/input/mac_comands.go create mode 100644 vendor/github.com/go-rod/rod/lib/input/mouse.go create mode 100644 vendor/github.com/go-rod/rod/lib/js/helper.go create mode 100644 vendor/github.com/go-rod/rod/lib/js/helper.js create mode 100644 vendor/github.com/go-rod/rod/lib/js/js.go create mode 100644 vendor/github.com/go-rod/rod/lib/launcher/README.md create mode 100644 vendor/github.com/go-rod/rod/lib/launcher/browser.go create mode 100644 vendor/github.com/go-rod/rod/lib/launcher/error.go create mode 100644 vendor/github.com/go-rod/rod/lib/launcher/flags/flags.go create mode 100644 vendor/github.com/go-rod/rod/lib/launcher/launcher.go create mode 100644 vendor/github.com/go-rod/rod/lib/launcher/manager.go create mode 100644 vendor/github.com/go-rod/rod/lib/launcher/os_unix.go create mode 100644 vendor/github.com/go-rod/rod/lib/launcher/os_windows.go create mode 100644 vendor/github.com/go-rod/rod/lib/launcher/revision.go create mode 100644 vendor/github.com/go-rod/rod/lib/launcher/url_parser.go create mode 100644 vendor/github.com/go-rod/rod/lib/launcher/utils.go create mode 100644 vendor/github.com/go-rod/rod/lib/proto/README.md create mode 100644 vendor/github.com/go-rod/rod/lib/proto/a_interface.go create mode 100644 vendor/github.com/go-rod/rod/lib/proto/a_patch.go create mode 100644 vendor/github.com/go-rod/rod/lib/proto/a_utils.go create mode 100644 vendor/github.com/go-rod/rod/lib/proto/accessibility.go create mode 100644 vendor/github.com/go-rod/rod/lib/proto/animation.go create mode 100644 vendor/github.com/go-rod/rod/lib/proto/audits.go create mode 100644 vendor/github.com/go-rod/rod/lib/proto/autofill.go create mode 100644 vendor/github.com/go-rod/rod/lib/proto/background_service.go create mode 100644 vendor/github.com/go-rod/rod/lib/proto/browser.go create mode 100644 vendor/github.com/go-rod/rod/lib/proto/cache_storage.go create mode 100644 vendor/github.com/go-rod/rod/lib/proto/cast.go create mode 100644 vendor/github.com/go-rod/rod/lib/proto/console.go create mode 100644 vendor/github.com/go-rod/rod/lib/proto/css.go create mode 100644 vendor/github.com/go-rod/rod/lib/proto/database.go create mode 100644 vendor/github.com/go-rod/rod/lib/proto/debugger.go create mode 100644 vendor/github.com/go-rod/rod/lib/proto/definitions.go create mode 100644 vendor/github.com/go-rod/rod/lib/proto/device_access.go create mode 100644 vendor/github.com/go-rod/rod/lib/proto/device_orientation.go create mode 100644 vendor/github.com/go-rod/rod/lib/proto/dom.go create mode 100644 vendor/github.com/go-rod/rod/lib/proto/dom_debugger.go create mode 100644 vendor/github.com/go-rod/rod/lib/proto/dom_snapshot.go create mode 100644 vendor/github.com/go-rod/rod/lib/proto/dom_storage.go create mode 100644 vendor/github.com/go-rod/rod/lib/proto/emulation.go create mode 100644 vendor/github.com/go-rod/rod/lib/proto/event_breakpoints.go create mode 100644 vendor/github.com/go-rod/rod/lib/proto/extensions.go create mode 100644 vendor/github.com/go-rod/rod/lib/proto/fed_cm.go create mode 100644 vendor/github.com/go-rod/rod/lib/proto/fetch.go create mode 100644 vendor/github.com/go-rod/rod/lib/proto/headless_experimental.go create mode 100644 vendor/github.com/go-rod/rod/lib/proto/heap_profiler.go create mode 100644 vendor/github.com/go-rod/rod/lib/proto/indexed_db.go create mode 100644 vendor/github.com/go-rod/rod/lib/proto/input.go create mode 100644 vendor/github.com/go-rod/rod/lib/proto/inspector.go create mode 100644 vendor/github.com/go-rod/rod/lib/proto/io.go create mode 100644 vendor/github.com/go-rod/rod/lib/proto/layer_tree.go create mode 100644 vendor/github.com/go-rod/rod/lib/proto/log.go create mode 100644 vendor/github.com/go-rod/rod/lib/proto/media.go create mode 100644 vendor/github.com/go-rod/rod/lib/proto/memory.go create mode 100644 vendor/github.com/go-rod/rod/lib/proto/network.go create mode 100644 vendor/github.com/go-rod/rod/lib/proto/overlay.go create mode 100644 vendor/github.com/go-rod/rod/lib/proto/page.go create mode 100644 vendor/github.com/go-rod/rod/lib/proto/performance.go create mode 100644 vendor/github.com/go-rod/rod/lib/proto/performance_timeline.go create mode 100644 vendor/github.com/go-rod/rod/lib/proto/preload.go create mode 100644 vendor/github.com/go-rod/rod/lib/proto/profiler.go create mode 100644 vendor/github.com/go-rod/rod/lib/proto/pwa.go create mode 100644 vendor/github.com/go-rod/rod/lib/proto/runtime.go create mode 100644 vendor/github.com/go-rod/rod/lib/proto/schema.go create mode 100644 vendor/github.com/go-rod/rod/lib/proto/security.go create mode 100644 vendor/github.com/go-rod/rod/lib/proto/service_worker.go create mode 100644 vendor/github.com/go-rod/rod/lib/proto/storage.go create mode 100644 vendor/github.com/go-rod/rod/lib/proto/system_info.go create mode 100644 vendor/github.com/go-rod/rod/lib/proto/target.go create mode 100644 vendor/github.com/go-rod/rod/lib/proto/tethering.go create mode 100644 vendor/github.com/go-rod/rod/lib/proto/tracing.go create mode 100644 vendor/github.com/go-rod/rod/lib/proto/web_audio.go create mode 100644 vendor/github.com/go-rod/rod/lib/proto/web_authn.go create mode 100644 vendor/github.com/go-rod/rod/lib/utils/imageutil.go create mode 100644 vendor/github.com/go-rod/rod/lib/utils/sleeper.go create mode 100644 vendor/github.com/go-rod/rod/lib/utils/utils.go create mode 100644 vendor/github.com/go-rod/rod/must.go create mode 100644 vendor/github.com/go-rod/rod/page.go create mode 100644 vendor/github.com/go-rod/rod/page_eval.go create mode 100644 vendor/github.com/go-rod/rod/query.go create mode 100644 vendor/github.com/go-rod/rod/states.go create mode 100644 vendor/github.com/go-rod/rod/utils.go create mode 100644 vendor/github.com/openshift/installer/LICENSE create mode 100644 vendor/github.com/openshift/installer/NOTICE create mode 100644 vendor/github.com/openshift/installer/pkg/ipnet/ipnet.go create mode 100644 vendor/github.com/pkg/errors/.gitignore create mode 100644 vendor/github.com/pkg/errors/.travis.yml create mode 100644 vendor/github.com/pkg/errors/LICENSE create mode 100644 vendor/github.com/pkg/errors/Makefile create mode 100644 vendor/github.com/pkg/errors/README.md create mode 100644 vendor/github.com/pkg/errors/appveyor.yml create mode 100644 vendor/github.com/pkg/errors/errors.go create mode 100644 vendor/github.com/pkg/errors/go113.go create mode 100644 vendor/github.com/pkg/errors/stack.go create mode 100644 vendor/github.com/sirupsen/logrus/.gitignore create mode 100644 vendor/github.com/sirupsen/logrus/.golangci.yml create mode 100644 vendor/github.com/sirupsen/logrus/.travis.yml create mode 100644 vendor/github.com/sirupsen/logrus/CHANGELOG.md create mode 100644 vendor/github.com/sirupsen/logrus/LICENSE create mode 100644 vendor/github.com/sirupsen/logrus/README.md create mode 100644 vendor/github.com/sirupsen/logrus/alt_exit.go create mode 100644 vendor/github.com/sirupsen/logrus/appveyor.yml create mode 100644 vendor/github.com/sirupsen/logrus/buffer_pool.go create mode 100644 vendor/github.com/sirupsen/logrus/doc.go create mode 100644 vendor/github.com/sirupsen/logrus/entry.go create mode 100644 vendor/github.com/sirupsen/logrus/exported.go create mode 100644 vendor/github.com/sirupsen/logrus/formatter.go create mode 100644 vendor/github.com/sirupsen/logrus/hooks.go create mode 100644 vendor/github.com/sirupsen/logrus/json_formatter.go create mode 100644 vendor/github.com/sirupsen/logrus/logger.go create mode 100644 vendor/github.com/sirupsen/logrus/logrus.go create mode 100644 vendor/github.com/sirupsen/logrus/terminal_check_appengine.go create mode 100644 vendor/github.com/sirupsen/logrus/terminal_check_bsd.go create mode 100644 vendor/github.com/sirupsen/logrus/terminal_check_js.go create mode 100644 vendor/github.com/sirupsen/logrus/terminal_check_no_terminal.go create mode 100644 vendor/github.com/sirupsen/logrus/terminal_check_notappengine.go create mode 100644 vendor/github.com/sirupsen/logrus/terminal_check_solaris.go create mode 100644 vendor/github.com/sirupsen/logrus/terminal_check_unix.go create mode 100644 vendor/github.com/sirupsen/logrus/terminal_check_windows.go create mode 100644 vendor/github.com/sirupsen/logrus/text_formatter.go create mode 100644 vendor/github.com/sirupsen/logrus/writer.go create mode 100644 vendor/github.com/ysmood/fetchup/.gitignore create mode 100644 vendor/github.com/ysmood/fetchup/LICENSE create mode 100644 vendor/github.com/ysmood/fetchup/README.md create mode 100644 vendor/github.com/ysmood/fetchup/download.go create mode 100644 vendor/github.com/ysmood/fetchup/events.go create mode 100644 vendor/github.com/ysmood/fetchup/fetchup.go create mode 100644 vendor/github.com/ysmood/fetchup/utils.go create mode 100644 vendor/github.com/ysmood/goob/.gitignore create mode 100644 vendor/github.com/ysmood/goob/.golangci.yml create mode 100644 vendor/github.com/ysmood/goob/LICENSE create mode 100644 vendor/github.com/ysmood/goob/goob.go create mode 100644 vendor/github.com/ysmood/goob/pipe.go create mode 100644 vendor/github.com/ysmood/goob/readme.md create mode 100644 vendor/github.com/ysmood/got/LICENSE create mode 100644 vendor/github.com/ysmood/got/lib/lcs/lcs.go create mode 100644 vendor/github.com/ysmood/got/lib/lcs/sequence.go create mode 100644 vendor/github.com/ysmood/got/lib/lcs/utils.go create mode 100644 vendor/github.com/ysmood/gson/.gitignore create mode 100644 vendor/github.com/ysmood/gson/LICENSE create mode 100644 vendor/github.com/ysmood/gson/README.md create mode 100644 vendor/github.com/ysmood/gson/read.go create mode 100644 vendor/github.com/ysmood/gson/write.go create mode 100644 vendor/github.com/ysmood/leakless/.gitignore create mode 100644 vendor/github.com/ysmood/leakless/.golangci.yml create mode 100644 vendor/github.com/ysmood/leakless/LICENSE create mode 100644 vendor/github.com/ysmood/leakless/bin_amd64_darwin.go create mode 100644 vendor/github.com/ysmood/leakless/bin_amd64_linux.go create mode 100644 vendor/github.com/ysmood/leakless/bin_amd64_windows.go create mode 100644 vendor/github.com/ysmood/leakless/bin_arm64_darwin.go create mode 100644 vendor/github.com/ysmood/leakless/bin_arm64_linux.go create mode 100644 vendor/github.com/ysmood/leakless/leakless.go create mode 100644 vendor/github.com/ysmood/leakless/pkg/shared/message.go create mode 100644 vendor/github.com/ysmood/leakless/pkg/shared/version.go create mode 100644 vendor/github.com/ysmood/leakless/pkg/utils/target.go create mode 100644 vendor/github.com/ysmood/leakless/pkg/utils/utils.go create mode 100644 vendor/github.com/ysmood/leakless/readme.md create mode 100644 vendor/golang.org/x/net/LICENSE create mode 100644 vendor/golang.org/x/net/PATENTS create mode 100644 vendor/golang.org/x/net/publicsuffix/data/children create mode 100644 vendor/golang.org/x/net/publicsuffix/data/nodes create mode 100644 vendor/golang.org/x/net/publicsuffix/data/text create mode 100644 vendor/golang.org/x/net/publicsuffix/list.go create mode 100644 vendor/golang.org/x/net/publicsuffix/table.go create mode 100644 vendor/golang.org/x/sys/LICENSE create mode 100644 vendor/golang.org/x/sys/PATENTS create mode 100644 vendor/golang.org/x/sys/unix/.gitignore create mode 100644 vendor/golang.org/x/sys/unix/README.md create mode 100644 vendor/golang.org/x/sys/unix/affinity_linux.go create mode 100644 vendor/golang.org/x/sys/unix/aliases.go create mode 100644 vendor/golang.org/x/sys/unix/asm_aix_ppc64.s create mode 100644 vendor/golang.org/x/sys/unix/asm_bsd_386.s create mode 100644 vendor/golang.org/x/sys/unix/asm_bsd_amd64.s create mode 100644 vendor/golang.org/x/sys/unix/asm_bsd_arm.s create mode 100644 vendor/golang.org/x/sys/unix/asm_bsd_arm64.s create mode 100644 vendor/golang.org/x/sys/unix/asm_bsd_ppc64.s create mode 100644 vendor/golang.org/x/sys/unix/asm_bsd_riscv64.s create mode 100644 vendor/golang.org/x/sys/unix/asm_linux_386.s create mode 100644 vendor/golang.org/x/sys/unix/asm_linux_amd64.s create mode 100644 vendor/golang.org/x/sys/unix/asm_linux_arm.s create mode 100644 vendor/golang.org/x/sys/unix/asm_linux_arm64.s create mode 100644 vendor/golang.org/x/sys/unix/asm_linux_loong64.s create mode 100644 vendor/golang.org/x/sys/unix/asm_linux_mips64x.s create mode 100644 vendor/golang.org/x/sys/unix/asm_linux_mipsx.s create mode 100644 vendor/golang.org/x/sys/unix/asm_linux_ppc64x.s create mode 100644 vendor/golang.org/x/sys/unix/asm_linux_riscv64.s create mode 100644 vendor/golang.org/x/sys/unix/asm_linux_s390x.s create mode 100644 vendor/golang.org/x/sys/unix/asm_openbsd_mips64.s create mode 100644 vendor/golang.org/x/sys/unix/asm_solaris_amd64.s create mode 100644 vendor/golang.org/x/sys/unix/asm_zos_s390x.s create mode 100644 vendor/golang.org/x/sys/unix/bluetooth_linux.go create mode 100644 vendor/golang.org/x/sys/unix/bpxsvc_zos.go create mode 100644 vendor/golang.org/x/sys/unix/bpxsvc_zos.s create mode 100644 vendor/golang.org/x/sys/unix/cap_freebsd.go create mode 100644 vendor/golang.org/x/sys/unix/constants.go create mode 100644 vendor/golang.org/x/sys/unix/dev_aix_ppc.go create mode 100644 vendor/golang.org/x/sys/unix/dev_aix_ppc64.go create mode 100644 vendor/golang.org/x/sys/unix/dev_darwin.go create mode 100644 vendor/golang.org/x/sys/unix/dev_dragonfly.go create mode 100644 vendor/golang.org/x/sys/unix/dev_freebsd.go create mode 100644 vendor/golang.org/x/sys/unix/dev_linux.go create mode 100644 vendor/golang.org/x/sys/unix/dev_netbsd.go create mode 100644 vendor/golang.org/x/sys/unix/dev_openbsd.go create mode 100644 vendor/golang.org/x/sys/unix/dev_zos.go create mode 100644 vendor/golang.org/x/sys/unix/dirent.go create mode 100644 vendor/golang.org/x/sys/unix/endian_big.go create mode 100644 vendor/golang.org/x/sys/unix/endian_little.go create mode 100644 vendor/golang.org/x/sys/unix/env_unix.go create mode 100644 vendor/golang.org/x/sys/unix/fcntl.go create mode 100644 vendor/golang.org/x/sys/unix/fcntl_darwin.go create mode 100644 vendor/golang.org/x/sys/unix/fcntl_linux_32bit.go create mode 100644 vendor/golang.org/x/sys/unix/fdset.go create mode 100644 vendor/golang.org/x/sys/unix/gccgo.go create mode 100644 vendor/golang.org/x/sys/unix/gccgo_c.c create mode 100644 vendor/golang.org/x/sys/unix/gccgo_linux_amd64.go create mode 100644 vendor/golang.org/x/sys/unix/ifreq_linux.go create mode 100644 vendor/golang.org/x/sys/unix/ioctl_linux.go create mode 100644 vendor/golang.org/x/sys/unix/ioctl_signed.go create mode 100644 vendor/golang.org/x/sys/unix/ioctl_unsigned.go create mode 100644 vendor/golang.org/x/sys/unix/ioctl_zos.go create mode 100644 vendor/golang.org/x/sys/unix/mkall.sh create mode 100644 vendor/golang.org/x/sys/unix/mkerrors.sh create mode 100644 vendor/golang.org/x/sys/unix/mmap_nomremap.go create mode 100644 vendor/golang.org/x/sys/unix/mremap.go create mode 100644 vendor/golang.org/x/sys/unix/pagesize_unix.go create mode 100644 vendor/golang.org/x/sys/unix/pledge_openbsd.go create mode 100644 vendor/golang.org/x/sys/unix/ptrace_darwin.go create mode 100644 vendor/golang.org/x/sys/unix/ptrace_ios.go create mode 100644 vendor/golang.org/x/sys/unix/race.go create mode 100644 vendor/golang.org/x/sys/unix/race0.go create mode 100644 vendor/golang.org/x/sys/unix/readdirent_getdents.go create mode 100644 vendor/golang.org/x/sys/unix/readdirent_getdirentries.go create mode 100644 vendor/golang.org/x/sys/unix/sockcmsg_dragonfly.go create mode 100644 vendor/golang.org/x/sys/unix/sockcmsg_linux.go create mode 100644 vendor/golang.org/x/sys/unix/sockcmsg_unix.go create mode 100644 vendor/golang.org/x/sys/unix/sockcmsg_unix_other.go create mode 100644 vendor/golang.org/x/sys/unix/sockcmsg_zos.go create mode 100644 vendor/golang.org/x/sys/unix/symaddr_zos_s390x.s create mode 100644 vendor/golang.org/x/sys/unix/syscall.go create mode 100644 vendor/golang.org/x/sys/unix/syscall_aix.go create mode 100644 vendor/golang.org/x/sys/unix/syscall_aix_ppc.go create mode 100644 vendor/golang.org/x/sys/unix/syscall_aix_ppc64.go create mode 100644 vendor/golang.org/x/sys/unix/syscall_bsd.go create mode 100644 vendor/golang.org/x/sys/unix/syscall_darwin.go create mode 100644 vendor/golang.org/x/sys/unix/syscall_darwin_amd64.go create mode 100644 vendor/golang.org/x/sys/unix/syscall_darwin_arm64.go create mode 100644 vendor/golang.org/x/sys/unix/syscall_darwin_libSystem.go create mode 100644 vendor/golang.org/x/sys/unix/syscall_dragonfly.go create mode 100644 vendor/golang.org/x/sys/unix/syscall_dragonfly_amd64.go create mode 100644 vendor/golang.org/x/sys/unix/syscall_freebsd.go create mode 100644 vendor/golang.org/x/sys/unix/syscall_freebsd_386.go create mode 100644 vendor/golang.org/x/sys/unix/syscall_freebsd_amd64.go create mode 100644 vendor/golang.org/x/sys/unix/syscall_freebsd_arm.go create mode 100644 vendor/golang.org/x/sys/unix/syscall_freebsd_arm64.go create mode 100644 vendor/golang.org/x/sys/unix/syscall_freebsd_riscv64.go create mode 100644 vendor/golang.org/x/sys/unix/syscall_hurd.go create mode 100644 vendor/golang.org/x/sys/unix/syscall_hurd_386.go create mode 100644 vendor/golang.org/x/sys/unix/syscall_illumos.go create mode 100644 vendor/golang.org/x/sys/unix/syscall_linux.go create mode 100644 vendor/golang.org/x/sys/unix/syscall_linux_386.go create mode 100644 vendor/golang.org/x/sys/unix/syscall_linux_alarm.go create mode 100644 vendor/golang.org/x/sys/unix/syscall_linux_amd64.go create mode 100644 vendor/golang.org/x/sys/unix/syscall_linux_amd64_gc.go create mode 100644 vendor/golang.org/x/sys/unix/syscall_linux_arm.go create mode 100644 vendor/golang.org/x/sys/unix/syscall_linux_arm64.go create mode 100644 vendor/golang.org/x/sys/unix/syscall_linux_gc.go create mode 100644 vendor/golang.org/x/sys/unix/syscall_linux_gc_386.go create mode 100644 vendor/golang.org/x/sys/unix/syscall_linux_gc_arm.go create mode 100644 vendor/golang.org/x/sys/unix/syscall_linux_gccgo_386.go create mode 100644 vendor/golang.org/x/sys/unix/syscall_linux_gccgo_arm.go create mode 100644 vendor/golang.org/x/sys/unix/syscall_linux_loong64.go create mode 100644 vendor/golang.org/x/sys/unix/syscall_linux_mips64x.go create mode 100644 vendor/golang.org/x/sys/unix/syscall_linux_mipsx.go create mode 100644 vendor/golang.org/x/sys/unix/syscall_linux_ppc.go create mode 100644 vendor/golang.org/x/sys/unix/syscall_linux_ppc64x.go create mode 100644 vendor/golang.org/x/sys/unix/syscall_linux_riscv64.go create mode 100644 vendor/golang.org/x/sys/unix/syscall_linux_s390x.go create mode 100644 vendor/golang.org/x/sys/unix/syscall_linux_sparc64.go create mode 100644 vendor/golang.org/x/sys/unix/syscall_netbsd.go create mode 100644 vendor/golang.org/x/sys/unix/syscall_netbsd_386.go create mode 100644 vendor/golang.org/x/sys/unix/syscall_netbsd_amd64.go create mode 100644 vendor/golang.org/x/sys/unix/syscall_netbsd_arm.go create mode 100644 vendor/golang.org/x/sys/unix/syscall_netbsd_arm64.go create mode 100644 vendor/golang.org/x/sys/unix/syscall_openbsd.go create mode 100644 vendor/golang.org/x/sys/unix/syscall_openbsd_386.go create mode 100644 vendor/golang.org/x/sys/unix/syscall_openbsd_amd64.go create mode 100644 vendor/golang.org/x/sys/unix/syscall_openbsd_arm.go create mode 100644 vendor/golang.org/x/sys/unix/syscall_openbsd_arm64.go create mode 100644 vendor/golang.org/x/sys/unix/syscall_openbsd_libc.go create mode 100644 vendor/golang.org/x/sys/unix/syscall_openbsd_mips64.go create mode 100644 vendor/golang.org/x/sys/unix/syscall_openbsd_ppc64.go create mode 100644 vendor/golang.org/x/sys/unix/syscall_openbsd_riscv64.go create mode 100644 vendor/golang.org/x/sys/unix/syscall_solaris.go create mode 100644 vendor/golang.org/x/sys/unix/syscall_solaris_amd64.go create mode 100644 vendor/golang.org/x/sys/unix/syscall_unix.go create mode 100644 vendor/golang.org/x/sys/unix/syscall_unix_gc.go create mode 100644 vendor/golang.org/x/sys/unix/syscall_unix_gc_ppc64x.go create mode 100644 vendor/golang.org/x/sys/unix/syscall_zos_s390x.go create mode 100644 vendor/golang.org/x/sys/unix/sysvshm_linux.go create mode 100644 vendor/golang.org/x/sys/unix/sysvshm_unix.go create mode 100644 vendor/golang.org/x/sys/unix/sysvshm_unix_other.go create mode 100644 vendor/golang.org/x/sys/unix/timestruct.go create mode 100644 vendor/golang.org/x/sys/unix/unveil_openbsd.go create mode 100644 vendor/golang.org/x/sys/unix/vgetrandom_linux.go create mode 100644 vendor/golang.org/x/sys/unix/vgetrandom_unsupported.go create mode 100644 vendor/golang.org/x/sys/unix/xattr_bsd.go create mode 100644 vendor/golang.org/x/sys/unix/zerrors_aix_ppc.go create mode 100644 vendor/golang.org/x/sys/unix/zerrors_aix_ppc64.go create mode 100644 vendor/golang.org/x/sys/unix/zerrors_darwin_amd64.go create mode 100644 vendor/golang.org/x/sys/unix/zerrors_darwin_arm64.go create mode 100644 vendor/golang.org/x/sys/unix/zerrors_dragonfly_amd64.go create mode 100644 vendor/golang.org/x/sys/unix/zerrors_freebsd_386.go create mode 100644 vendor/golang.org/x/sys/unix/zerrors_freebsd_amd64.go create mode 100644 vendor/golang.org/x/sys/unix/zerrors_freebsd_arm.go create mode 100644 vendor/golang.org/x/sys/unix/zerrors_freebsd_arm64.go create mode 100644 vendor/golang.org/x/sys/unix/zerrors_freebsd_riscv64.go create mode 100644 vendor/golang.org/x/sys/unix/zerrors_linux.go create mode 100644 vendor/golang.org/x/sys/unix/zerrors_linux_386.go create mode 100644 vendor/golang.org/x/sys/unix/zerrors_linux_amd64.go create mode 100644 vendor/golang.org/x/sys/unix/zerrors_linux_arm.go create mode 100644 vendor/golang.org/x/sys/unix/zerrors_linux_arm64.go create mode 100644 vendor/golang.org/x/sys/unix/zerrors_linux_loong64.go create mode 100644 vendor/golang.org/x/sys/unix/zerrors_linux_mips.go create mode 100644 vendor/golang.org/x/sys/unix/zerrors_linux_mips64.go create mode 100644 vendor/golang.org/x/sys/unix/zerrors_linux_mips64le.go create mode 100644 vendor/golang.org/x/sys/unix/zerrors_linux_mipsle.go create mode 100644 vendor/golang.org/x/sys/unix/zerrors_linux_ppc.go create mode 100644 vendor/golang.org/x/sys/unix/zerrors_linux_ppc64.go create mode 100644 vendor/golang.org/x/sys/unix/zerrors_linux_ppc64le.go create mode 100644 vendor/golang.org/x/sys/unix/zerrors_linux_riscv64.go create mode 100644 vendor/golang.org/x/sys/unix/zerrors_linux_s390x.go create mode 100644 vendor/golang.org/x/sys/unix/zerrors_linux_sparc64.go create mode 100644 vendor/golang.org/x/sys/unix/zerrors_netbsd_386.go create mode 100644 vendor/golang.org/x/sys/unix/zerrors_netbsd_amd64.go create mode 100644 vendor/golang.org/x/sys/unix/zerrors_netbsd_arm.go create mode 100644 vendor/golang.org/x/sys/unix/zerrors_netbsd_arm64.go create mode 100644 vendor/golang.org/x/sys/unix/zerrors_openbsd_386.go create mode 100644 vendor/golang.org/x/sys/unix/zerrors_openbsd_amd64.go create mode 100644 vendor/golang.org/x/sys/unix/zerrors_openbsd_arm.go create mode 100644 vendor/golang.org/x/sys/unix/zerrors_openbsd_arm64.go create mode 100644 vendor/golang.org/x/sys/unix/zerrors_openbsd_mips64.go create mode 100644 vendor/golang.org/x/sys/unix/zerrors_openbsd_ppc64.go create mode 100644 vendor/golang.org/x/sys/unix/zerrors_openbsd_riscv64.go create mode 100644 vendor/golang.org/x/sys/unix/zerrors_solaris_amd64.go create mode 100644 vendor/golang.org/x/sys/unix/zerrors_zos_s390x.go create mode 100644 vendor/golang.org/x/sys/unix/zptrace_armnn_linux.go create mode 100644 vendor/golang.org/x/sys/unix/zptrace_linux_arm64.go create mode 100644 vendor/golang.org/x/sys/unix/zptrace_mipsnn_linux.go create mode 100644 vendor/golang.org/x/sys/unix/zptrace_mipsnnle_linux.go create mode 100644 vendor/golang.org/x/sys/unix/zptrace_x86_linux.go create mode 100644 vendor/golang.org/x/sys/unix/zsymaddr_zos_s390x.s create mode 100644 vendor/golang.org/x/sys/unix/zsyscall_aix_ppc.go create mode 100644 vendor/golang.org/x/sys/unix/zsyscall_aix_ppc64.go create mode 100644 vendor/golang.org/x/sys/unix/zsyscall_aix_ppc64_gc.go create mode 100644 vendor/golang.org/x/sys/unix/zsyscall_aix_ppc64_gccgo.go create mode 100644 vendor/golang.org/x/sys/unix/zsyscall_darwin_amd64.go create mode 100644 vendor/golang.org/x/sys/unix/zsyscall_darwin_amd64.s create mode 100644 vendor/golang.org/x/sys/unix/zsyscall_darwin_arm64.go create mode 100644 vendor/golang.org/x/sys/unix/zsyscall_darwin_arm64.s create mode 100644 vendor/golang.org/x/sys/unix/zsyscall_dragonfly_amd64.go create mode 100644 vendor/golang.org/x/sys/unix/zsyscall_freebsd_386.go create mode 100644 vendor/golang.org/x/sys/unix/zsyscall_freebsd_amd64.go create mode 100644 vendor/golang.org/x/sys/unix/zsyscall_freebsd_arm.go create mode 100644 vendor/golang.org/x/sys/unix/zsyscall_freebsd_arm64.go create mode 100644 vendor/golang.org/x/sys/unix/zsyscall_freebsd_riscv64.go create mode 100644 vendor/golang.org/x/sys/unix/zsyscall_illumos_amd64.go create mode 100644 vendor/golang.org/x/sys/unix/zsyscall_linux.go create mode 100644 vendor/golang.org/x/sys/unix/zsyscall_linux_386.go create mode 100644 vendor/golang.org/x/sys/unix/zsyscall_linux_amd64.go create mode 100644 vendor/golang.org/x/sys/unix/zsyscall_linux_arm.go create mode 100644 vendor/golang.org/x/sys/unix/zsyscall_linux_arm64.go create mode 100644 vendor/golang.org/x/sys/unix/zsyscall_linux_loong64.go create mode 100644 vendor/golang.org/x/sys/unix/zsyscall_linux_mips.go create mode 100644 vendor/golang.org/x/sys/unix/zsyscall_linux_mips64.go create mode 100644 vendor/golang.org/x/sys/unix/zsyscall_linux_mips64le.go create mode 100644 vendor/golang.org/x/sys/unix/zsyscall_linux_mipsle.go create mode 100644 vendor/golang.org/x/sys/unix/zsyscall_linux_ppc.go create mode 100644 vendor/golang.org/x/sys/unix/zsyscall_linux_ppc64.go create mode 100644 vendor/golang.org/x/sys/unix/zsyscall_linux_ppc64le.go create mode 100644 vendor/golang.org/x/sys/unix/zsyscall_linux_riscv64.go create mode 100644 vendor/golang.org/x/sys/unix/zsyscall_linux_s390x.go create mode 100644 vendor/golang.org/x/sys/unix/zsyscall_linux_sparc64.go create mode 100644 vendor/golang.org/x/sys/unix/zsyscall_netbsd_386.go create mode 100644 vendor/golang.org/x/sys/unix/zsyscall_netbsd_amd64.go create mode 100644 vendor/golang.org/x/sys/unix/zsyscall_netbsd_arm.go create mode 100644 vendor/golang.org/x/sys/unix/zsyscall_netbsd_arm64.go create mode 100644 vendor/golang.org/x/sys/unix/zsyscall_openbsd_386.go create mode 100644 vendor/golang.org/x/sys/unix/zsyscall_openbsd_386.s create mode 100644 vendor/golang.org/x/sys/unix/zsyscall_openbsd_amd64.go create mode 100644 vendor/golang.org/x/sys/unix/zsyscall_openbsd_amd64.s create mode 100644 vendor/golang.org/x/sys/unix/zsyscall_openbsd_arm.go create mode 100644 vendor/golang.org/x/sys/unix/zsyscall_openbsd_arm.s create mode 100644 vendor/golang.org/x/sys/unix/zsyscall_openbsd_arm64.go create mode 100644 vendor/golang.org/x/sys/unix/zsyscall_openbsd_arm64.s create mode 100644 vendor/golang.org/x/sys/unix/zsyscall_openbsd_mips64.go create mode 100644 vendor/golang.org/x/sys/unix/zsyscall_openbsd_mips64.s create mode 100644 vendor/golang.org/x/sys/unix/zsyscall_openbsd_ppc64.go create mode 100644 vendor/golang.org/x/sys/unix/zsyscall_openbsd_ppc64.s create mode 100644 vendor/golang.org/x/sys/unix/zsyscall_openbsd_riscv64.go create mode 100644 vendor/golang.org/x/sys/unix/zsyscall_openbsd_riscv64.s create mode 100644 vendor/golang.org/x/sys/unix/zsyscall_solaris_amd64.go create mode 100644 vendor/golang.org/x/sys/unix/zsyscall_zos_s390x.go create mode 100644 vendor/golang.org/x/sys/unix/zsysctl_openbsd_386.go create mode 100644 vendor/golang.org/x/sys/unix/zsysctl_openbsd_amd64.go create mode 100644 vendor/golang.org/x/sys/unix/zsysctl_openbsd_arm.go create mode 100644 vendor/golang.org/x/sys/unix/zsysctl_openbsd_arm64.go create mode 100644 vendor/golang.org/x/sys/unix/zsysctl_openbsd_mips64.go create mode 100644 vendor/golang.org/x/sys/unix/zsysctl_openbsd_ppc64.go create mode 100644 vendor/golang.org/x/sys/unix/zsysctl_openbsd_riscv64.go create mode 100644 vendor/golang.org/x/sys/unix/zsysnum_darwin_amd64.go create mode 100644 vendor/golang.org/x/sys/unix/zsysnum_darwin_arm64.go create mode 100644 vendor/golang.org/x/sys/unix/zsysnum_dragonfly_amd64.go create mode 100644 vendor/golang.org/x/sys/unix/zsysnum_freebsd_386.go create mode 100644 vendor/golang.org/x/sys/unix/zsysnum_freebsd_amd64.go create mode 100644 vendor/golang.org/x/sys/unix/zsysnum_freebsd_arm.go create mode 100644 vendor/golang.org/x/sys/unix/zsysnum_freebsd_arm64.go create mode 100644 vendor/golang.org/x/sys/unix/zsysnum_freebsd_riscv64.go create mode 100644 vendor/golang.org/x/sys/unix/zsysnum_linux_386.go create mode 100644 vendor/golang.org/x/sys/unix/zsysnum_linux_amd64.go create mode 100644 vendor/golang.org/x/sys/unix/zsysnum_linux_arm.go create mode 100644 vendor/golang.org/x/sys/unix/zsysnum_linux_arm64.go create mode 100644 vendor/golang.org/x/sys/unix/zsysnum_linux_loong64.go create mode 100644 vendor/golang.org/x/sys/unix/zsysnum_linux_mips.go create mode 100644 vendor/golang.org/x/sys/unix/zsysnum_linux_mips64.go create mode 100644 vendor/golang.org/x/sys/unix/zsysnum_linux_mips64le.go create mode 100644 vendor/golang.org/x/sys/unix/zsysnum_linux_mipsle.go create mode 100644 vendor/golang.org/x/sys/unix/zsysnum_linux_ppc.go create mode 100644 vendor/golang.org/x/sys/unix/zsysnum_linux_ppc64.go create mode 100644 vendor/golang.org/x/sys/unix/zsysnum_linux_ppc64le.go create mode 100644 vendor/golang.org/x/sys/unix/zsysnum_linux_riscv64.go create mode 100644 vendor/golang.org/x/sys/unix/zsysnum_linux_s390x.go create mode 100644 vendor/golang.org/x/sys/unix/zsysnum_linux_sparc64.go create mode 100644 vendor/golang.org/x/sys/unix/zsysnum_netbsd_386.go create mode 100644 vendor/golang.org/x/sys/unix/zsysnum_netbsd_amd64.go create mode 100644 vendor/golang.org/x/sys/unix/zsysnum_netbsd_arm.go create mode 100644 vendor/golang.org/x/sys/unix/zsysnum_netbsd_arm64.go create mode 100644 vendor/golang.org/x/sys/unix/zsysnum_openbsd_386.go create mode 100644 vendor/golang.org/x/sys/unix/zsysnum_openbsd_amd64.go create mode 100644 vendor/golang.org/x/sys/unix/zsysnum_openbsd_arm.go create mode 100644 vendor/golang.org/x/sys/unix/zsysnum_openbsd_arm64.go create mode 100644 vendor/golang.org/x/sys/unix/zsysnum_openbsd_mips64.go create mode 100644 vendor/golang.org/x/sys/unix/zsysnum_openbsd_ppc64.go create mode 100644 vendor/golang.org/x/sys/unix/zsysnum_openbsd_riscv64.go create mode 100644 vendor/golang.org/x/sys/unix/zsysnum_zos_s390x.go create mode 100644 vendor/golang.org/x/sys/unix/ztypes_aix_ppc.go create mode 100644 vendor/golang.org/x/sys/unix/ztypes_aix_ppc64.go create mode 100644 vendor/golang.org/x/sys/unix/ztypes_darwin_amd64.go create mode 100644 vendor/golang.org/x/sys/unix/ztypes_darwin_arm64.go create mode 100644 vendor/golang.org/x/sys/unix/ztypes_dragonfly_amd64.go create mode 100644 vendor/golang.org/x/sys/unix/ztypes_freebsd_386.go create mode 100644 vendor/golang.org/x/sys/unix/ztypes_freebsd_amd64.go create mode 100644 vendor/golang.org/x/sys/unix/ztypes_freebsd_arm.go create mode 100644 vendor/golang.org/x/sys/unix/ztypes_freebsd_arm64.go create mode 100644 vendor/golang.org/x/sys/unix/ztypes_freebsd_riscv64.go create mode 100644 vendor/golang.org/x/sys/unix/ztypes_linux.go create mode 100644 vendor/golang.org/x/sys/unix/ztypes_linux_386.go create mode 100644 vendor/golang.org/x/sys/unix/ztypes_linux_amd64.go create mode 100644 vendor/golang.org/x/sys/unix/ztypes_linux_arm.go create mode 100644 vendor/golang.org/x/sys/unix/ztypes_linux_arm64.go create mode 100644 vendor/golang.org/x/sys/unix/ztypes_linux_loong64.go create mode 100644 vendor/golang.org/x/sys/unix/ztypes_linux_mips.go create mode 100644 vendor/golang.org/x/sys/unix/ztypes_linux_mips64.go create mode 100644 vendor/golang.org/x/sys/unix/ztypes_linux_mips64le.go create mode 100644 vendor/golang.org/x/sys/unix/ztypes_linux_mipsle.go create mode 100644 vendor/golang.org/x/sys/unix/ztypes_linux_ppc.go create mode 100644 vendor/golang.org/x/sys/unix/ztypes_linux_ppc64.go create mode 100644 vendor/golang.org/x/sys/unix/ztypes_linux_ppc64le.go create mode 100644 vendor/golang.org/x/sys/unix/ztypes_linux_riscv64.go create mode 100644 vendor/golang.org/x/sys/unix/ztypes_linux_s390x.go create mode 100644 vendor/golang.org/x/sys/unix/ztypes_linux_sparc64.go create mode 100644 vendor/golang.org/x/sys/unix/ztypes_netbsd_386.go create mode 100644 vendor/golang.org/x/sys/unix/ztypes_netbsd_amd64.go create mode 100644 vendor/golang.org/x/sys/unix/ztypes_netbsd_arm.go create mode 100644 vendor/golang.org/x/sys/unix/ztypes_netbsd_arm64.go create mode 100644 vendor/golang.org/x/sys/unix/ztypes_openbsd_386.go create mode 100644 vendor/golang.org/x/sys/unix/ztypes_openbsd_amd64.go create mode 100644 vendor/golang.org/x/sys/unix/ztypes_openbsd_arm.go create mode 100644 vendor/golang.org/x/sys/unix/ztypes_openbsd_arm64.go create mode 100644 vendor/golang.org/x/sys/unix/ztypes_openbsd_mips64.go create mode 100644 vendor/golang.org/x/sys/unix/ztypes_openbsd_ppc64.go create mode 100644 vendor/golang.org/x/sys/unix/ztypes_openbsd_riscv64.go create mode 100644 vendor/golang.org/x/sys/unix/ztypes_solaris_amd64.go create mode 100644 vendor/golang.org/x/sys/unix/ztypes_zos_s390x.go create mode 100644 vendor/golang.org/x/sys/windows/aliases.go create mode 100644 vendor/golang.org/x/sys/windows/dll_windows.go create mode 100644 vendor/golang.org/x/sys/windows/env_windows.go create mode 100644 vendor/golang.org/x/sys/windows/eventlog.go create mode 100644 vendor/golang.org/x/sys/windows/exec_windows.go create mode 100644 vendor/golang.org/x/sys/windows/memory_windows.go create mode 100644 vendor/golang.org/x/sys/windows/mkerrors.bash create mode 100644 vendor/golang.org/x/sys/windows/mkknownfolderids.bash create mode 100644 vendor/golang.org/x/sys/windows/mksyscall.go create mode 100644 vendor/golang.org/x/sys/windows/race.go create mode 100644 vendor/golang.org/x/sys/windows/race0.go create mode 100644 vendor/golang.org/x/sys/windows/security_windows.go create mode 100644 vendor/golang.org/x/sys/windows/service.go create mode 100644 vendor/golang.org/x/sys/windows/setupapi_windows.go create mode 100644 vendor/golang.org/x/sys/windows/str.go create mode 100644 vendor/golang.org/x/sys/windows/syscall.go create mode 100644 vendor/golang.org/x/sys/windows/syscall_windows.go create mode 100644 vendor/golang.org/x/sys/windows/types_windows.go create mode 100644 vendor/golang.org/x/sys/windows/types_windows_386.go create mode 100644 vendor/golang.org/x/sys/windows/types_windows_amd64.go create mode 100644 vendor/golang.org/x/sys/windows/types_windows_arm.go create mode 100644 vendor/golang.org/x/sys/windows/types_windows_arm64.go create mode 100644 vendor/golang.org/x/sys/windows/zerrors_windows.go create mode 100644 vendor/golang.org/x/sys/windows/zknownfolderids_windows.go create mode 100644 vendor/golang.org/x/sys/windows/zsyscall_windows.go create mode 100644 vendor/modules.txt diff --git a/agent/06_agent_create_cluster.sh b/agent/06_agent_create_cluster.sh index a0cb5d379..f313957d7 100755 --- a/agent/06_agent_create_cluster.sh +++ b/agent/06_agent_create_cluster.sh @@ -639,41 +639,15 @@ case "${AGENT_E2E_TEST_BOOT_MODE}" in check_assisted_install_UI - # Temporarily create a dummy kubeconfig and kubeadmin-password file for the CI - auth_dir=$SCRIPTDIR/$OCP_DIR/auth - mkdir -p $auth_dir - cfg=$auth_dir/kubeconfig - cat << EOF >> ${cfg} -clusters: -- cluster: - certificate-authority-data: LS0tLS1CRUdJTiBGSUNBVLS0tLQo= - server: https://api.test.redhat.com:6443 - name: test -contexts: -- context: - cluster: test - user: admin - name: admin -current-context: admin -preferences: {} -users: -- name: admin - user: - client-certificate-data: LS0tLS1CRUdJTiBNBVEUtLS0tLQo= - client-key-data: LS0tLS1CRUdJTiURSBVktLS0tLQo= -EOF - echo "dummy-kubeadmin-password" > $auth_dir/kubeadmin-password + mkdir -p $OCP_DIR/auth + rendezvousIP=$(getRendezvousIP) + get_vips + # Simulate user actions as done on the webUI and start cluster installation + RENDEZVOUS_IP=$rendezvousIP OCP_DIR=$OCP_DIR INGRESS_VIP=$INGRESS_VIPS API_VIP=$API_VIPS go run -mod vendor agent/isobuilder/ui_driven_cluster_installation.go + exit 0 ;; esac -if [[ "${AGENT_E2E_TEST_BOOT_MODE}" == "ISO_NO_REGISTRY" ]]; then - # Current goal is to only verify if the nodes are booted fine, - # TUI sets the rendezvous IP correctly and UI is accessible. - # The next goal is to simulate adding the cluster details via UI - # and complete the cluster installation. - exit 0 -fi - if [ ! -z "${AGENT_TEST_CASES:-}" ]; then run_agent_test_cases fi diff --git a/agent/agent_post_install_validation.sh b/agent/agent_post_install_validation.sh index 817457c56..a28adde86 100755 --- a/agent/agent_post_install_validation.sh +++ b/agent/agent_post_install_validation.sh @@ -5,9 +5,10 @@ SCRIPTDIR="$( cd "$( dirname "${BASH_SOURCE[0]}" )/.." && pwd )" source $SCRIPTDIR/common.sh -# Temp code skip the execution flow as cluster is not really installed if [[ "${AGENT_E2E_TEST_BOOT_MODE}" == "ISO_NO_REGISTRY" ]]; then - exit 0 + oc wait clusterversion version --for=condition=Available=True --timeout=60m + oc get csv -A + oc get packagemanifests -n openshift-marketplace fi installed_control_plane_nodes=$(oc get nodes --selector=node-role.kubernetes.io/master | grep -v AGE | wc -l) @@ -18,3 +19,5 @@ if (( $NUM_MASTERS != $installed_control_plane_nodes )); then echo "Post install validation failed. Expected $NUM_MASTERS control plane nodes but found $installed_control_plane_nodes." exit 1 fi + +oc get clusterversion diff --git a/agent/isobuilder/ui_driven_cluster_installation.go b/agent/isobuilder/ui_driven_cluster_installation.go new file mode 100644 index 000000000..b2ca39dbc --- /dev/null +++ b/agent/isobuilder/ui_driven_cluster_installation.go @@ -0,0 +1,319 @@ +package main + +import ( + "fmt" + "log" + "os" + "path" + "path/filepath" + + "strings" + "time" + + resty "github.com/go-resty/resty/v2" + "github.com/go-rod/rod" + + "github.com/go-rod/rod/lib/launcher" + "github.com/go-rod/rod/lib/proto" + "github.com/go-rod/rod/lib/utils" + + "github.com/sirupsen/logrus" +) + +var ( + rendezvousIP = os.Getenv("RENDEZVOUS_IP") + ocpDir = os.Getenv("OCP_DIR") + baseURL = fmt.Sprintf("http://%s:3001", rendezvousIP) + clustersURL = fmt.Sprintf("%s%s", baseURL, path.Join("/api/assisted-install/v2/clusters")) +) + +func main() { + logrus.Info("Launching headless browser...") + url := launcher.New().Headless(true).MustLaunch() + browser := rod.New().ControlURL(url).MustConnect() + + defer browser.MustClose() + + page := browser.MustPage(baseURL) + page.MustWaitLoad() + + cwd, err := os.Getwd() + if err != nil { + log.Fatal(err) + } + screenshotPath := filepath.Join(cwd, ocpDir) + + logrus.Info("Enter cluster details") + err = clusterDetails(page, filepath.Join(screenshotPath, "01-cluster-details.png")) + if err != nil { + log.Fatalf("failed to enter cluster details: %v", err) + } + + next(page) + + logrus.Info("Select virtualization bundle") + err = virtualizationBundle(page, filepath.Join(screenshotPath, "02-operators.png")) + if err != nil { + log.Fatalf("failed to select virtualization bundle: %v", err) + } + + next(page) + + logrus.Info("Await host discovery") + err = hostDiscovery(page, filepath.Join(screenshotPath, "03-hostDiscovery.png")) + if err != nil { + log.Fatalf("failed awaiting host discovery: %v", err) + } + + next(page) + + logrus.Info("Verify storage") + err = verifyStorage(page, filepath.Join(screenshotPath, "04-storage.png")) + if err != nil { + log.Fatalf("failed awaiting host discovery: %v", err) + } + + next(page) + + logrus.Info("Enter networking details") + err = networkingDetails(page, filepath.Join(screenshotPath, "05-networking.png")) + if err != nil { + log.Fatalf("failed entering networking details: %v", err) + } + + next(page) + + logrus.Info("Download credentials") + client := resty.New() + err = downloadCredentials(page, client, filepath.Join(screenshotPath, "06-credentials.png")) + if err != nil { + log.Fatalf("failed downloading credentials: %v", err) + } + + next(page) + + logrus.Info("Review") + err = review(page, filepath.Join(screenshotPath, "07-review.png")) + if err != nil { + log.Fatalf("failed review page: %v", err) + } + + logrus.Info("Start Cluster Installation") + err = startInstallation(page, client, filepath.Join(screenshotPath, "08-installation.png")) + if err != nil { + log.Fatalf("failed to start installation page: %v", err) + } + logrus.Info("Cluster installation started successfully.") + + waitForClusterConsoleLink(page, filepath.Join(screenshotPath, "09-installation-progress.png")) +} + +func clusterDetails(page *rod.Page, path string) error { + page.MustElement("#form-input-name-field").MustInput("abi-ove-isobuilder") + page.MustElement("#form-input-baseDnsDomain-field").MustInput("redhat.com") + + pullSecretPath := os.Getenv("PULL_SECRET_FILE") + secretBytes, err := os.ReadFile(pullSecretPath) + if err != nil { + return fmt.Errorf("failed to read pull secret file: %v", err) + } + pullSecret := strings.TrimSpace(string(secretBytes)) + page.MustElement("#form-input-pullSecret-field").MustInput(pullSecret) + + err = saveFullPageScreenshot(page, path) + if err != nil { + return err + } + return nil +} + +func virtualizationBundle(page *rod.Page, path string) error { + page.MustElement(`#bundle-virtualization`).MustClick().MustWaitEnabled() + err := saveFullPageScreenshot(page, path) + if err != nil { + return err + } + return nil +} + +func hostDiscovery(page *rod.Page, path string) error { + page.MustElement(`button[name="next"]`).MustWaitEnabled() + err := saveFullPageScreenshot(page, path) + if err != nil { + return err + } + return nil +} + +func verifyStorage(page *rod.Page, path string) error { + err := saveFullPageScreenshot(page, path) + if err != nil { + return err + } + return nil +} + +func networkingDetails(page *rod.Page, path string) error { + apiVip := os.Getenv("API_VIP") + ingressVip := os.Getenv("INGRESS_VIP") + page.MustElement("#form-input-apiVips-0-ip-field").MustInput(apiVip) + page.MustElement("#form-input-ingressVips-0-ip-field").MustInput(ingressVip) + page.MustElement(`button[name="next"]`).MustWaitEnabled() + + err := saveFullPageScreenshot(page, path) + if err != nil { + return err + } + return nil +} + +func downloadCredentials(page *rod.Page, client *resty.Client, path string) error { + page.MustElement(`#credentials-download-agreement`).MustClick() + page.MustElement(`button[name="next"]`).MustWaitEnabled() + + err := saveFullPageScreenshot(page, path) + if err != nil { + return err + } + time.Sleep(30 * time.Second) + + clusterID, err := getClusterID(client, clustersURL) + if err != nil { + return err + } + + logrus.Info("Download credentials via api request") + + fileURL := fmt.Sprintf("%s/%s/downloads/credentials?file_name=", clustersURL, clusterID) + saveCredentials(client, fileURL, "kubeadmin-password") + time.Sleep(15 * time.Second) + + saveCredentials(client, fileURL, "kubeconfig") + time.Sleep(15 * time.Second) + return nil +} + +func review(page *rod.Page, path string) error { + err := saveFullPageScreenshot(page, path) + if err != nil { + return err + } + page.MustElement(`button[name="install"]`).MustClick() + logrus.Info("Install button clicked") + return nil +} + +func startInstallation(page *rod.Page, client *resty.Client, path string) error { + page.MustElementR(`h2[data-ouia-component-type="PF5/Text"]`, `Installation progress`) + err := saveFullPageScreenshot(page, path) + if err != nil { + return err + } + + return nil +} + +func waitForClusterConsoleLink(page *rod.Page, path string) error { + for { + failMsg, _ := page.Timeout(5 * time.Second).ElementR("div.pf-v5-c-empty-state__body", `Failed on`) + if failMsg != nil { + logrus.Error("Cluster installation failed.") + if err := saveFullPageScreenshot(page, path); err != nil { + return err + } + return fmt.Errorf("cluster installation failed") + } + + consoleURL, _ := page.Timeout(5 * time.Second).ElementR("button.pf-v5-c-button", `https://console-openshift-console.apps.abi-ove-isobuilder.redhat.com`) + if consoleURL != nil { + if visible, _ := consoleURL.Visible(); visible { + logrus.Info("Console URL is available.") + break + } + } + + logrus.Info("Cluster installation in progress. Waiting for console URL to be available.") + time.Sleep(5 * time.Minute) + } + + if err := saveFullPageScreenshot(page, path); err != nil { + return err + } + + return nil +} + +func next(page *rod.Page) { + page.MustElement(`button[name="next"]`).MustWaitEnabled().MustClick() +} + +// saveFullPageScreenshot captures a full-page screenshot and saves it to the given path. +func saveFullPageScreenshot(page *rod.Page, path string) error { + result, err := page.Evaluate(rod.Eval(`() => { + return { + width: document.body.scrollWidth, + height: document.body.scrollHeight + } + }`)) + if err != nil { + return fmt.Errorf("failed to evaluate page size: %w", err) + } + + width := int(result.Value.Get("width").Int()) + height := int(result.Value.Get("height").Int()) + + err = page.SetViewport(&proto.EmulationSetDeviceMetricsOverride{ + Width: int(width), + Height: int(height), + DeviceScaleFactor: 1, + Mobile: false, + }) + if err != nil { + return fmt.Errorf("failed to set viewport: %w", err) + } + + screenshot, err := page.Screenshot(false, nil) + if err != nil { + return fmt.Errorf("failed to take screenshot: %w", err) + } + + if err := utils.OutputFile(path, screenshot); err != nil { + return fmt.Errorf("failed to save screenshot: %w", err) + } + logrus.Info("Screenshot saved to", path, ", with type of image/png") + return nil +} + +// getClusterID fetches the first cluster ID from the given URL using the provided client. +func getClusterID(client *resty.Client, url string) (string, error) { + var clusters []struct { + ID string `json:"id"` + } + + _, err := client.R().SetResult(&clusters).Get(url) + if err != nil { + return "", err + } + + return clusters[0].ID, nil +} + +// saveCredentials downloads a file from the given URL and saves it under the auth directory. +func saveCredentials(client *resty.Client, url, filename string) { + logrus.Info("Downloading ", filename) + + fileURL := fmt.Sprintf("%s%s", url, filename) + resp, err := client.R().Get(fileURL) + if err != nil { + logrus.Info("Request failed:", err) + return + } + + downloadedFile := fmt.Sprintf("%s/auth/%s", ocpDir, filename) + err = os.WriteFile(downloadedFile, resp.Body(), 0644) + if err != nil { + logrus.Errorf("Failed to save file %s: %v", downloadedFile, err) + return + } + logrus.Info("File ", downloadedFile, " downloaded successfully") +} diff --git a/go.mod b/go.mod index 6b5458ad3..2e0555125 100644 --- a/go.mod +++ b/go.mod @@ -4,7 +4,19 @@ go 1.22.3 require ( github.com/apparentlymart/go-cidr v1.1.0 + github.com/go-resty/resty/v2 v2.16.5 + github.com/go-rod/rod v0.116.2 github.com/openshift/installer v1.4.17 + github.com/sirupsen/logrus v1.9.3 ) -require github.com/pkg/errors v0.9.1 // indirect +require ( + github.com/pkg/errors v0.9.1 // indirect + github.com/ysmood/fetchup v0.2.3 // indirect + github.com/ysmood/goob v0.4.0 // indirect + github.com/ysmood/got v0.40.0 // indirect + github.com/ysmood/gson v0.7.3 // indirect + github.com/ysmood/leakless v0.9.0 // indirect + golang.org/x/net v0.33.0 // indirect + golang.org/x/sys v0.28.0 // indirect +) diff --git a/go.sum b/go.sum index 08adacb78..786a9af94 100644 --- a/go.sum +++ b/go.sum @@ -1,6 +1,48 @@ github.com/apparentlymart/go-cidr v1.1.0 h1:2mAhrMoF+nhXqxTzSZMUzDHkLjmIHC+Zzn4tdgBZjnU= github.com/apparentlymart/go-cidr v1.1.0/go.mod h1:EBcsNrHc3zQeuaeCeCtQruQm+n9/YjEn/vI25Lg7Gwc= +github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= +github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= +github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc h1:U9qPSI2PIWSS1VwoXQT9A3Wy9MM3WgvqSxFWenqJduM= +github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= +github.com/go-resty/resty/v2 v2.16.5 h1:hBKqmWrr7uRc3euHVqmh1HTHcKn99Smr7o5spptdhTM= +github.com/go-resty/resty/v2 v2.16.5/go.mod h1:hkJtXbA2iKHzJheXYvQ8snQES5ZLGKMwQ07xAwp/fiA= +github.com/go-rod/rod v0.116.2 h1:A5t2Ky2A+5eD/ZJQr1EfsQSe5rms5Xof/qj296e+ZqA= +github.com/go-rod/rod v0.116.2/go.mod h1:H+CMO9SCNc2TJ2WfrG+pKhITz57uGNYU43qYHh438Mg= github.com/openshift/installer v1.4.17 h1:63iijBBgYqQX/p2+Q74gPqnfBN5VNSWX5LxQKuLlj6g= github.com/openshift/installer v1.4.17/go.mod h1:CtlMEGKJDVMZl4qVBC/xMUXM24YnleT6bakI+KXFAhk= github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4= github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= +github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= +github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 h1:Jamvg5psRIccs7FGNTlIRMkT8wgtp5eCXdBlqhYGL6U= +github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= +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/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= +github.com/stretchr/testify v1.9.0 h1:HtqpIVDClZ4nwg75+f6Lvsy/wHu+3BoSGCbBAcpTsTg= +github.com/stretchr/testify v1.9.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY= +github.com/ysmood/fetchup v0.2.3 h1:ulX+SonA0Vma5zUFXtv52Kzip/xe7aj4vqT5AJwQ+ZQ= +github.com/ysmood/fetchup v0.2.3/go.mod h1:xhibcRKziSvol0H1/pj33dnKrYyI2ebIvz5cOOkYGns= +github.com/ysmood/goob v0.4.0 h1:HsxXhyLBeGzWXnqVKtmT9qM7EuVs/XOgkX7T6r1o1AQ= +github.com/ysmood/goob v0.4.0/go.mod h1:u6yx7ZhS4Exf2MwciFr6nIM8knHQIE22lFpWHnfql18= +github.com/ysmood/gop v0.2.0 h1:+tFrG0TWPxT6p9ZaZs+VY+opCvHU8/3Fk6BaNv6kqKg= +github.com/ysmood/gop v0.2.0/go.mod h1:rr5z2z27oGEbyB787hpEcx4ab8cCiPnKxn0SUHt6xzk= +github.com/ysmood/got v0.40.0 h1:ZQk1B55zIvS7zflRrkGfPDrPG3d7+JOza1ZkNxcc74Q= +github.com/ysmood/got v0.40.0/go.mod h1:W7DdpuX6skL3NszLmAsC5hT7JAhuLZhByVzHTq874Qg= +github.com/ysmood/gotrace v0.6.0 h1:SyI1d4jclswLhg7SWTL6os3L1WOKeNn/ZtzVQF8QmdY= +github.com/ysmood/gotrace v0.6.0/go.mod h1:TzhIG7nHDry5//eYZDYcTzuJLYQIkykJzCRIo4/dzQM= +github.com/ysmood/gson v0.7.3 h1:QFkWbTH8MxyUTKPkVWAENJhxqdBa4lYTQWqZCiLG6kE= +github.com/ysmood/gson v0.7.3/go.mod h1:3Kzs5zDl21g5F/BlLTNcuAGAYLKt2lV5G8D1zF3RNmg= +github.com/ysmood/leakless v0.9.0 h1:qxCG5VirSBvmi3uynXFkcnLMzkphdh3xx5FtrORwDCU= +github.com/ysmood/leakless v0.9.0/go.mod h1:R8iAXPRaG97QJwqxs74RdwzcRHT1SWCGTNqY8q0JvMQ= +golang.org/x/net v0.33.0 h1:74SYHlV8BIgHIFC/LrYkOGIwL19eTYXQ5wc6TBuO36I= +golang.org/x/net v0.33.0/go.mod h1:HXLR5J+9DxmrqMwG9qjGCxZ+zKXxBru04zlTvWlWuN4= +golang.org/x/sys v0.0.0-20220715151400-c0bba94af5f8/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.28.0 h1:Fksou7UEQUWlKvIdsqzJmUmCX3cZuD2+P3XyyzwMhlA= +golang.org/x/sys v0.28.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= +golang.org/x/time v0.6.0 h1:eTDhh4ZXt5Qf0augr54TN6suAUudPcawVZeIAPU7D4U= +golang.org/x/time v0.6.0/go.mod h1:3BpzKBy/shNhVucY/MWOyx10tF3SFh9QdLuxbVysPQM= +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= +gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= diff --git a/vendor/github.com/apparentlymart/go-cidr/LICENSE b/vendor/github.com/apparentlymart/go-cidr/LICENSE new file mode 100644 index 000000000..212537886 --- /dev/null +++ b/vendor/github.com/apparentlymart/go-cidr/LICENSE @@ -0,0 +1,19 @@ +Copyright (c) 2015 Martin Atkins + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in +all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +THE SOFTWARE. diff --git a/vendor/github.com/apparentlymart/go-cidr/cidr/cidr.go b/vendor/github.com/apparentlymart/go-cidr/cidr/cidr.go new file mode 100644 index 000000000..20823af04 --- /dev/null +++ b/vendor/github.com/apparentlymart/go-cidr/cidr/cidr.go @@ -0,0 +1,236 @@ +// Package cidr is a collection of assorted utilities for computing +// network and host addresses within network ranges. +// +// It expects a CIDR-type address structure where addresses are divided into +// some number of prefix bits representing the network and then the remaining +// suffix bits represent the host. +// +// For example, it can help to calculate addresses for sub-networks of a +// parent network, or to calculate host addresses within a particular prefix. +// +// At present this package is prioritizing simplicity of implementation and +// de-prioritizing speed and memory usage. Thus caution is advised before +// using this package in performance-critical applications or hot codepaths. +// Patches to improve the speed and memory usage may be accepted as long as +// they do not result in a significant increase in code complexity. +package cidr + +import ( + "fmt" + "math/big" + "net" +) + +// Subnet takes a parent CIDR range and creates a subnet within it +// with the given number of additional prefix bits and the given +// network number. +// +// For example, 10.3.0.0/16, extended by 8 bits, with a network number +// of 5, becomes 10.3.5.0/24 . +func Subnet(base *net.IPNet, newBits int, num int) (*net.IPNet, error) { + return SubnetBig(base, newBits, big.NewInt(int64(num))) +} + +// SubnetBig takes a parent CIDR range and creates a subnet within it with the +// given number of additional prefix bits and the given network number. It +// differs from Subnet in that it takes a *big.Int for the num, instead of an int. +// +// For example, 10.3.0.0/16, extended by 8 bits, with a network number of 5, +// becomes 10.3.5.0/24 . +func SubnetBig(base *net.IPNet, newBits int, num *big.Int) (*net.IPNet, error) { + ip := base.IP + mask := base.Mask + + parentLen, addrLen := mask.Size() + newPrefixLen := parentLen + newBits + + if newPrefixLen > addrLen { + return nil, fmt.Errorf("insufficient address space to extend prefix of %d by %d", parentLen, newBits) + } + + maxNetNum := uint64(1< maxNetNum { + return nil, fmt.Errorf("prefix extension of %d does not accommodate a subnet numbered %d", newBits, num) + } + + return &net.IPNet{ + IP: insertNumIntoIP(ip, num, newPrefixLen), + Mask: net.CIDRMask(newPrefixLen, addrLen), + }, nil +} + +// Host takes a parent CIDR range and turns it into a host IP address with the +// given host number. +// +// For example, 10.3.0.0/16 with a host number of 2 gives 10.3.0.2. +func Host(base *net.IPNet, num int) (net.IP, error) { + return HostBig(base, big.NewInt(int64(num))) +} + +// HostBig takes a parent CIDR range and turns it into a host IP address with +// the given host number. It differs from Host in that it takes a *big.Int for +// the num, instead of an int. +// +// For example, 10.3.0.0/16 with a host number of 2 gives 10.3.0.2. +func HostBig(base *net.IPNet, num *big.Int) (net.IP, error) { + ip := base.IP + mask := base.Mask + + parentLen, addrLen := mask.Size() + hostLen := addrLen - parentLen + + maxHostNum := big.NewInt(int64(1)) + maxHostNum.Lsh(maxHostNum, uint(hostLen)) + maxHostNum.Sub(maxHostNum, big.NewInt(1)) + + numUint64 := big.NewInt(int64(num.Uint64())) + if num.Cmp(big.NewInt(0)) == -1 { + numUint64.Neg(num) + numUint64.Sub(numUint64, big.NewInt(int64(1))) + num.Sub(maxHostNum, numUint64) + } + + if numUint64.Cmp(maxHostNum) == 1 { + return nil, fmt.Errorf("prefix of %d does not accommodate a host numbered %d", parentLen, num) + } + var bitlength int + if ip.To4() != nil { + bitlength = 32 + } else { + bitlength = 128 + } + return insertNumIntoIP(ip, num, bitlength), nil +} + +// AddressRange returns the first and last addresses in the given CIDR range. +func AddressRange(network *net.IPNet) (net.IP, net.IP) { + // the first IP is easy + firstIP := network.IP + + // the last IP is the network address OR NOT the mask address + prefixLen, bits := network.Mask.Size() + if prefixLen == bits { + // Easy! + // But make sure that our two slices are distinct, since they + // would be in all other cases. + lastIP := make([]byte, len(firstIP)) + copy(lastIP, firstIP) + return firstIP, lastIP + } + + firstIPInt, bits := ipToInt(firstIP) + hostLen := uint(bits) - uint(prefixLen) + lastIPInt := big.NewInt(1) + lastIPInt.Lsh(lastIPInt, hostLen) + lastIPInt.Sub(lastIPInt, big.NewInt(1)) + lastIPInt.Or(lastIPInt, firstIPInt) + + return firstIP, intToIP(lastIPInt, bits) +} + +// AddressCount returns the number of distinct host addresses within the given +// CIDR range. +// +// Since the result is a uint64, this function returns meaningful information +// only for IPv4 ranges and IPv6 ranges with a prefix size of at least 65. +func AddressCount(network *net.IPNet) uint64 { + prefixLen, bits := network.Mask.Size() + return 1 << (uint64(bits) - uint64(prefixLen)) +} + +//VerifyNoOverlap takes a list subnets and supernet (CIDRBlock) and verifies +//none of the subnets overlap and all subnets are in the supernet +//it returns an error if any of those conditions are not satisfied +func VerifyNoOverlap(subnets []*net.IPNet, CIDRBlock *net.IPNet) error { + firstLastIP := make([][]net.IP, len(subnets)) + for i, s := range subnets { + first, last := AddressRange(s) + firstLastIP[i] = []net.IP{first, last} + } + for i, s := range subnets { + if !CIDRBlock.Contains(firstLastIP[i][0]) || !CIDRBlock.Contains(firstLastIP[i][1]) { + return fmt.Errorf("%s does not fully contain %s", CIDRBlock.String(), s.String()) + } + for j := 0; j < len(subnets); j++ { + if i == j { + continue + } + + first := firstLastIP[j][0] + last := firstLastIP[j][1] + if s.Contains(first) || s.Contains(last) { + return fmt.Errorf("%s overlaps with %s", subnets[j].String(), s.String()) + } + } + } + return nil +} + +// PreviousSubnet returns the subnet of the desired mask in the IP space +// just lower than the start of IPNet provided. If the IP space rolls over +// then the second return value is true +func PreviousSubnet(network *net.IPNet, prefixLen int) (*net.IPNet, bool) { + startIP := checkIPv4(network.IP) + previousIP := make(net.IP, len(startIP)) + copy(previousIP, startIP) + cMask := net.CIDRMask(prefixLen, 8*len(previousIP)) + previousIP = Dec(previousIP) + previous := &net.IPNet{IP: previousIP.Mask(cMask), Mask: cMask} + if startIP.Equal(net.IPv4zero) || startIP.Equal(net.IPv6zero) { + return previous, true + } + return previous, false +} + +// NextSubnet returns the next available subnet of the desired mask size +// starting for the maximum IP of the offset subnet +// If the IP exceeds the maxium IP then the second return value is true +func NextSubnet(network *net.IPNet, prefixLen int) (*net.IPNet, bool) { + _, currentLast := AddressRange(network) + mask := net.CIDRMask(prefixLen, 8*len(currentLast)) + currentSubnet := &net.IPNet{IP: currentLast.Mask(mask), Mask: mask} + _, last := AddressRange(currentSubnet) + last = Inc(last) + next := &net.IPNet{IP: last.Mask(mask), Mask: mask} + if last.Equal(net.IPv4zero) || last.Equal(net.IPv6zero) { + return next, true + } + return next, false +} + +//Inc increases the IP by one this returns a new []byte for the IP +func Inc(IP net.IP) net.IP { + IP = checkIPv4(IP) + incIP := make([]byte, len(IP)) + copy(incIP, IP) + for j := len(incIP) - 1; j >= 0; j-- { + incIP[j]++ + if incIP[j] > 0 { + break + } + } + return incIP +} + +//Dec decreases the IP by one this returns a new []byte for the IP +func Dec(IP net.IP) net.IP { + IP = checkIPv4(IP) + decIP := make([]byte, len(IP)) + copy(decIP, IP) + decIP = checkIPv4(decIP) + for j := len(decIP) - 1; j >= 0; j-- { + decIP[j]-- + if decIP[j] < 255 { + break + } + } + return decIP +} + +func checkIPv4(ip net.IP) net.IP { + // Go for some reason allocs IPv6len for IPv4 so we have to correct it + if v4 := ip.To4(); v4 != nil { + return v4 + } + return ip +} diff --git a/vendor/github.com/apparentlymart/go-cidr/cidr/wrangling.go b/vendor/github.com/apparentlymart/go-cidr/cidr/wrangling.go new file mode 100644 index 000000000..e5e6a2cf9 --- /dev/null +++ b/vendor/github.com/apparentlymart/go-cidr/cidr/wrangling.go @@ -0,0 +1,37 @@ +package cidr + +import ( + "fmt" + "math/big" + "net" +) + +func ipToInt(ip net.IP) (*big.Int, int) { + val := &big.Int{} + val.SetBytes([]byte(ip)) + if len(ip) == net.IPv4len { + return val, 32 + } else if len(ip) == net.IPv6len { + return val, 128 + } else { + panic(fmt.Errorf("Unsupported address length %d", len(ip))) + } +} + +func intToIP(ipInt *big.Int, bits int) net.IP { + ipBytes := ipInt.Bytes() + ret := make([]byte, bits/8) + // Pack our IP bytes into the end of the return array, + // since big.Int.Bytes() removes front zero padding. + for i := 1; i <= len(ipBytes); i++ { + ret[len(ret)-i] = ipBytes[len(ipBytes)-i] + } + return net.IP(ret) +} + +func insertNumIntoIP(ip net.IP, bigNum *big.Int, prefixLen int) net.IP { + ipInt, totalBits := ipToInt(ip) + bigNum.Lsh(bigNum, uint(totalBits-prefixLen)) + ipInt.Or(ipInt, bigNum) + return intToIP(ipInt, totalBits) +} diff --git a/vendor/github.com/go-resty/resty/v2/.gitignore b/vendor/github.com/go-resty/resty/v2/.gitignore new file mode 100644 index 000000000..7542ac89e --- /dev/null +++ b/vendor/github.com/go-resty/resty/v2/.gitignore @@ -0,0 +1,31 @@ +# Compiled Object files, Static and Dynamic libs (Shared Objects) +*.o +*.a +*.so + +# Folders +_obj +_test + +# Architecture specific extensions/prefixes +*.[568vq] +[568vq].out + +*.cgo1.go +*.cgo2.c +_cgo_defun.c +_cgo_gotypes.go +_cgo_export.* + +_testmain.go + +*.exe +*.test +*.prof + +coverage.out +coverage.txt + +# Exclude IDE folders +.idea/* +.vscode/* diff --git a/vendor/github.com/go-resty/resty/v2/BUILD.bazel b/vendor/github.com/go-resty/resty/v2/BUILD.bazel new file mode 100644 index 000000000..7248abc50 --- /dev/null +++ b/vendor/github.com/go-resty/resty/v2/BUILD.bazel @@ -0,0 +1,59 @@ +load("@bazel_gazelle//:def.bzl", "gazelle") +load("@io_bazel_rules_go//go:def.bzl", "go_library", "go_test") + +# gazelle:prefix github.com/go-resty/resty/v2 +# gazelle:go_naming_convention import_alias +gazelle(name = "gazelle") + +go_library( + name = "resty", + srcs = [ + "client.go", + "digest.go", + "middleware.go", + "redirect.go", + "request.go", + "response.go", + "resty.go", + "retry.go", + "trace.go", + "transport.go", + "transport112.go", + "transport_js.go", + "transport_other.go", + "util.go", + "util_curl.go", + ], + importpath = "github.com/go-resty/resty/v2", + visibility = ["//visibility:public"], + deps = [ + "//shellescape", + "@org_golang_x_net//publicsuffix:go_default_library", + ], +) + +go_test( + name = "resty_test", + srcs = [ + "client_test.go", + "context_test.go", + "example_test.go", + "middleware_test.go", + "request_test.go", + "resty_test.go", + "retry_test.go", + "util_test.go", + ], + data = glob([".testdata/*"]), + embed = [":resty"], + deps = [ + "@org_golang_x_net//proxy:go_default_library", + "@org_golang_x_time//rate:go_default_library", + ], +) + +alias( + name = "go_default_library", + actual = ":resty", + visibility = ["//visibility:public"], +) diff --git a/vendor/github.com/go-resty/resty/v2/LICENSE b/vendor/github.com/go-resty/resty/v2/LICENSE new file mode 100644 index 000000000..de30fea8f --- /dev/null +++ b/vendor/github.com/go-resty/resty/v2/LICENSE @@ -0,0 +1,21 @@ +The MIT License (MIT) + +Copyright (c) 2015-2024 Jeevanandam M., https://myjeeva.com + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. diff --git a/vendor/github.com/go-resty/resty/v2/README.md b/vendor/github.com/go-resty/resty/v2/README.md new file mode 100644 index 000000000..91c7bc89a --- /dev/null +++ b/vendor/github.com/go-resty/resty/v2/README.md @@ -0,0 +1,944 @@ +

+

Resty

+

Simple HTTP and REST client library for Go (inspired by Ruby rest-client)

+

Features section describes in detail about Resty capabilities

+

+

+

Build Status Code Coverage Go Report Card Release Version GoDoc License Mentioned in Awesome Go

+

+ +## News + + * v2.16.5 [released](https://github.com/go-resty/resty/releases/tag/v2.16.5) and tagged on Jan 22, 2025. + * v2.0.0 [released](https://github.com/go-resty/resty/releases/tag/v2.0.0) and tagged on Jul 16, 2019. + * v1.12.0 [released](https://github.com/go-resty/resty/releases/tag/v1.12.0) and tagged on Feb 27, 2019. + * v1.0 released and tagged on Sep 25, 2017. - Resty's first version was released on Sep 15, 2015 then it grew gradually as a very handy and helpful library. Its been a two years since first release. I'm very thankful to Resty users and its [contributors](https://github.com/go-resty/resty/graphs/contributors). + +## Features + + * GET, POST, PUT, DELETE, HEAD, PATCH, OPTIONS, etc. + * Simple and chainable methods for settings and request + * [Request](https://pkg.go.dev/github.com/go-resty/resty/v2#Request) Body can be `string`, `[]byte`, `struct`, `map`, `slice` and `io.Reader` too + * Auto detects `Content-Type` + * Buffer less processing for `io.Reader` + * Native `*http.Request` instance may be accessed during middleware and request execution via `Request.RawRequest` + * Request Body can be read multiple times via `Request.RawRequest.GetBody()` + * [Response](https://pkg.go.dev/github.com/go-resty/resty/v2#Response) object gives you more possibility + * Access as `[]byte` array - `response.Body()` OR Access as `string` - `response.String()` + * Know your `response.Time()` and when we `response.ReceivedAt()` + * Automatic marshal and unmarshal for `JSON` and `XML` content type + * Default is `JSON`, if you supply `struct/map` without header `Content-Type` + * For auto-unmarshal, refer to - + - Success scenario [Request.SetResult()](https://pkg.go.dev/github.com/go-resty/resty/v2#Request.SetResult) and [Response.Result()](https://pkg.go.dev/github.com/go-resty/resty/v2#Response.Result). + - Error scenario [Request.SetError()](https://pkg.go.dev/github.com/go-resty/resty/v2#Request.SetError) and [Response.Error()](https://pkg.go.dev/github.com/go-resty/resty/v2#Response.Error). + - Supports [RFC7807](https://tools.ietf.org/html/rfc7807) - `application/problem+json` & `application/problem+xml` + * Resty provides an option to override [JSON Marshal/Unmarshal and XML Marshal/Unmarshal](#override-json--xml-marshalunmarshal) + * Easy to upload one or more file(s) via `multipart/form-data` + * Auto detects file content type + * Request URL [Path Params (aka URI Params)](https://pkg.go.dev/github.com/go-resty/resty/v2#Request.SetPathParams) + * Backoff Retry Mechanism with retry condition function [reference](retry_test.go) + * Resty client HTTP & REST [Request](https://pkg.go.dev/github.com/go-resty/resty/v2#Client.OnBeforeRequest) and [Response](https://pkg.go.dev/github.com/go-resty/resty/v2#Client.OnAfterResponse) middlewares + * `Request.SetContext` supported + * Authorization option of `BasicAuth` and `Bearer` token + * Set request `ContentLength` value for all request or particular request + * Custom [Root Certificates](https://pkg.go.dev/github.com/go-resty/resty/v2#Client.SetRootCertificate) and Client [Certificates](https://pkg.go.dev/github.com/go-resty/resty/v2#Client.SetCertificates) + * Download/Save HTTP response directly into File, like `curl -o` flag. See [SetOutputDirectory](https://pkg.go.dev/github.com/go-resty/resty/v2#Client.SetOutputDirectory) & [SetOutput](https://pkg.go.dev/github.com/go-resty/resty/v2#Request.SetOutput). + * Cookies for your request and CookieJar support + * SRV Record based request instead of Host URL + * Client settings like `Timeout`, `RedirectPolicy`, `Proxy`, `TLSClientConfig`, `Transport`, etc. + * Optionally allows GET request with payload, see [SetAllowGetMethodPayload](https://pkg.go.dev/github.com/go-resty/resty/v2#Client.SetAllowGetMethodPayload) + * Supports registering external JSON library into resty, see [how to use](https://github.com/go-resty/resty/issues/76#issuecomment-314015250) + * Exposes Response reader without reading response (no auto-unmarshaling) if need be, see [how to use](https://github.com/go-resty/resty/issues/87#issuecomment-322100604) + * Option to specify expected `Content-Type` when response `Content-Type` header missing. Refer to [#92](https://github.com/go-resty/resty/issues/92) + * Resty design + * Have client level settings & options and also override at Request level if you want to + * Request and Response middleware + * Create Multiple clients if you want to `resty.New()` + * Supports `http.RoundTripper` implementation, see [SetTransport](https://pkg.go.dev/github.com/go-resty/resty/v2#Client.SetTransport) + * goroutine concurrent safe + * Resty Client trace, see [Client.EnableTrace](https://pkg.go.dev/github.com/go-resty/resty/v2#Client.EnableTrace) and [Request.EnableTrace](https://pkg.go.dev/github.com/go-resty/resty/v2#Request.EnableTrace) + * Since v2.4.0, trace info contains a `RequestAttempt` value, and the `Request` object contains an `Attempt` attribute + * Supports on-demand CURL command generation, see [Client.EnableGenerateCurlOnDebug](https://pkg.go.dev/github.com/go-resty/resty/v2#Client.EnableGenerateCurlOnDebug), [Request.EnableGenerateCurlOnDebug](https://pkg.go.dev/github.com/go-resty/resty/v2#Request.EnableGenerateCurlOnDebug). It requires debug mode to be enabled. + * Debug mode - clean and informative logging presentation + * Gzip - Go does it automatically also resty has fallback handling too + * Works fine with `HTTP/2` and `HTTP/1.1`, also `HTTP/3` can be used with Resty, see this [comment](https://github.com/go-resty/resty/issues/846#issuecomment-2329696110) + * [Bazel support](#bazel-support) + * Easily mock Resty for testing, [for e.g.](#mocking-http-requests-using-httpmock-library) + * Well tested client library + +### Included Batteries + + * Redirect Policies - see [how to use](#redirect-policy) + * NoRedirectPolicy + * FlexibleRedirectPolicy + * DomainCheckRedirectPolicy + * etc. [more info](redirect.go) + * Retry Mechanism [how to use](#retries) + * Backoff Retry + * Conditional Retry + * Since v2.6.0, Retry Hooks - [Client](https://pkg.go.dev/github.com/go-resty/resty/v2#Client.AddRetryHook), [Request](https://pkg.go.dev/github.com/go-resty/resty/v2#Request.AddRetryHook) + * SRV Record based request instead of Host URL [how to use](resty_test.go#L1412) + * etc (upcoming - throw your idea's [here](https://github.com/go-resty/resty/issues)). + + +#### Supported Go Versions + +Recommended to use `go1.20` and above. + +Initially Resty started supporting `go modules` since `v1.10.0` release. + +Starting Resty v2 and higher versions, it fully embraces [go modules](https://github.com/golang/go/wiki/Modules) package release. It requires a Go version capable of understanding `/vN` suffixed imports: + +- 1.9.7+ +- 1.10.3+ +- 1.11+ + + +## It might be beneficial for your project :smile: + +Resty author also published following projects for Go Community. + + * [go-model](https://github.com/jeevatkm/go-model) - Robust & Easy to use model mapper and utility methods for Go `struct`. + + +## Installation + +```bash +# Go Modules +require github.com/go-resty/resty/v2 v2.16.5 +``` + +## Usage + +The following samples will assist you to become as comfortable as possible with resty library. + +```go +// Import resty into your code and refer it as `resty`. +import "github.com/go-resty/resty/v2" +``` + +#### Simple GET + +```go +// Create a Resty Client +client := resty.New() + +resp, err := client.R(). + EnableTrace(). + Get("https://httpbin.org/get") + +// Explore response object +fmt.Println("Response Info:") +fmt.Println(" Error :", err) +fmt.Println(" Status Code:", resp.StatusCode()) +fmt.Println(" Status :", resp.Status()) +fmt.Println(" Proto :", resp.Proto()) +fmt.Println(" Time :", resp.Time()) +fmt.Println(" Received At:", resp.ReceivedAt()) +fmt.Println(" Body :\n", resp) +fmt.Println() + +// Explore trace info +fmt.Println("Request Trace Info:") +ti := resp.Request.TraceInfo() +fmt.Println(" DNSLookup :", ti.DNSLookup) +fmt.Println(" ConnTime :", ti.ConnTime) +fmt.Println(" TCPConnTime :", ti.TCPConnTime) +fmt.Println(" TLSHandshake :", ti.TLSHandshake) +fmt.Println(" ServerTime :", ti.ServerTime) +fmt.Println(" ResponseTime :", ti.ResponseTime) +fmt.Println(" TotalTime :", ti.TotalTime) +fmt.Println(" IsConnReused :", ti.IsConnReused) +fmt.Println(" IsConnWasIdle :", ti.IsConnWasIdle) +fmt.Println(" ConnIdleTime :", ti.ConnIdleTime) +fmt.Println(" RequestAttempt:", ti.RequestAttempt) +fmt.Println(" RemoteAddr :", ti.RemoteAddr.String()) + +/* Output +Response Info: + Error : + Status Code: 200 + Status : 200 OK + Proto : HTTP/2.0 + Time : 457.034718ms + Received At: 2020-09-14 15:35:29.784681 -0700 PDT m=+0.458137045 + Body : + { + "args": {}, + "headers": { + "Accept-Encoding": "gzip", + "Host": "httpbin.org", + "User-Agent": "go-resty/2.4.0 (https://github.com/go-resty/resty)", + "X-Amzn-Trace-Id": "Root=1-5f5ff031-000ff6292204aa6898e4de49" + }, + "origin": "0.0.0.0", + "url": "https://httpbin.org/get" + } + +Request Trace Info: + DNSLookup : 4.074657ms + ConnTime : 381.709936ms + TCPConnTime : 77.428048ms + TLSHandshake : 299.623597ms + ServerTime : 75.414703ms + ResponseTime : 79.337µs + TotalTime : 457.034718ms + IsConnReused : false + IsConnWasIdle : false + ConnIdleTime : 0s + RequestAttempt: 1 + RemoteAddr : 3.221.81.55:443 +*/ +``` + +#### Enhanced GET + +```go +// Create a Resty Client +client := resty.New() + +resp, err := client.R(). + SetQueryParams(map[string]string{ + "page_no": "1", + "limit": "20", + "sort":"name", + "order": "asc", + "random":strconv.FormatInt(time.Now().Unix(), 10), + }). + SetHeader("Accept", "application/json"). + SetAuthToken("BC594900518B4F7EAC75BD37F019E08FBC594900518B4F7EAC75BD37F019E08F"). + Get("/search_result") + + +// Sample of using Request.SetQueryString method +resp, err := client.R(). + SetQueryString("productId=232&template=fresh-sample&cat=resty&source=google&kw=buy a lot more"). + SetHeader("Accept", "application/json"). + SetAuthToken("BC594900518B4F7EAC75BD37F019E08FBC594900518B4F7EAC75BD37F019E08F"). + Get("/show_product") + + +// If necessary, you can force response content type to tell Resty to parse a JSON response into your struct +resp, err := client.R(). + SetResult(result). + ForceContentType("application/json"). + Get("v2/alpine/manifests/latest") +``` + +#### Various POST method combinations + +```go +// Create a Resty Client +client := resty.New() + +// POST JSON string +// No need to set content type, if you have client level setting +resp, err := client.R(). + SetHeader("Content-Type", "application/json"). + SetBody(`{"username":"testuser", "password":"testpass"}`). + SetResult(&AuthSuccess{}). // or SetResult(AuthSuccess{}). + Post("https://myapp.com/login") + +// POST []byte array +// No need to set content type, if you have client level setting +resp, err := client.R(). + SetHeader("Content-Type", "application/json"). + SetBody([]byte(`{"username":"testuser", "password":"testpass"}`)). + SetResult(&AuthSuccess{}). // or SetResult(AuthSuccess{}). + Post("https://myapp.com/login") + +// POST Struct, default is JSON content type. No need to set one +resp, err := client.R(). + SetBody(User{Username: "testuser", Password: "testpass"}). + SetResult(&AuthSuccess{}). // or SetResult(AuthSuccess{}). + SetError(&AuthError{}). // or SetError(AuthError{}). + Post("https://myapp.com/login") + +// POST Map, default is JSON content type. No need to set one +resp, err := client.R(). + SetBody(map[string]interface{}{"username": "testuser", "password": "testpass"}). + SetResult(&AuthSuccess{}). // or SetResult(AuthSuccess{}). + SetError(&AuthError{}). // or SetError(AuthError{}). + Post("https://myapp.com/login") + +// POST of raw bytes for file upload. For example: upload file to Dropbox +fileBytes, _ := os.ReadFile("/Users/jeeva/mydocument.pdf") + +// See we are not setting content-type header, since go-resty automatically detects Content-Type for you +resp, err := client.R(). + SetBody(fileBytes). + SetContentLength(true). // Dropbox expects this value + SetAuthToken(""). + SetError(&DropboxError{}). // or SetError(DropboxError{}). + Post("https://content.dropboxapi.com/1/files_put/auto/resty/mydocument.pdf") // for upload Dropbox supports PUT too + +// Note: resty detects Content-Type for request body/payload if content type header is not set. +// * For struct and map data type defaults to 'application/json' +// * Fallback is plain text content type +``` + +#### Sample PUT + +You can use various combinations of `PUT` method call like demonstrated for `POST`. + +```go +// Note: This is one sample of PUT method usage, refer POST for more combination + +// Create a Resty Client +client := resty.New() + +// Request goes as JSON content type +// No need to set auth token, error, if you have client level settings +resp, err := client.R(). + SetBody(Article{ + Title: "go-resty", + Content: "This is my article content, oh ya!", + Author: "Jeevanandam M", + Tags: []string{"article", "sample", "resty"}, + }). + SetAuthToken("C6A79608-782F-4ED0-A11D-BD82FAD829CD"). + SetError(&Error{}). // or SetError(Error{}). + Put("https://myapp.com/article/1234") +``` + +#### Sample PATCH + +You can use various combinations of `PATCH` method call like demonstrated for `POST`. + +```go +// Note: This is one sample of PUT method usage, refer POST for more combination + +// Create a Resty Client +client := resty.New() + +// Request goes as JSON content type +// No need to set auth token, error, if you have client level settings +resp, err := client.R(). + SetBody(Article{ + Tags: []string{"new tag1", "new tag2"}, + }). + SetAuthToken("C6A79608-782F-4ED0-A11D-BD82FAD829CD"). + SetError(&Error{}). // or SetError(Error{}). + Patch("https://myapp.com/articles/1234") +``` + +#### Sample DELETE, HEAD, OPTIONS + +```go +// Create a Resty Client +client := resty.New() + +// DELETE a article +// No need to set auth token, error, if you have client level settings +resp, err := client.R(). + SetAuthToken("C6A79608-782F-4ED0-A11D-BD82FAD829CD"). + SetError(&Error{}). // or SetError(Error{}). + Delete("https://myapp.com/articles/1234") + +// DELETE a articles with payload/body as a JSON string +// No need to set auth token, error, if you have client level settings +resp, err := client.R(). + SetAuthToken("C6A79608-782F-4ED0-A11D-BD82FAD829CD"). + SetError(&Error{}). // or SetError(Error{}). + SetHeader("Content-Type", "application/json"). + SetBody(`{article_ids: [1002, 1006, 1007, 87683, 45432] }`). + Delete("https://myapp.com/articles") + +// HEAD of resource +// No need to set auth token, if you have client level settings +resp, err := client.R(). + SetAuthToken("C6A79608-782F-4ED0-A11D-BD82FAD829CD"). + Head("https://myapp.com/videos/hi-res-video") + +// OPTIONS of resource +// No need to set auth token, if you have client level settings +resp, err := client.R(). + SetAuthToken("C6A79608-782F-4ED0-A11D-BD82FAD829CD"). + Options("https://myapp.com/servers/nyc-dc-01") +``` + +#### Override JSON & XML Marshal/Unmarshal + +User could register choice of JSON/XML library into resty or write your own. By default resty registers standard `encoding/json` and `encoding/xml` respectively. +```go +// Example of registering json-iterator +import jsoniter "github.com/json-iterator/go" + +json := jsoniter.ConfigCompatibleWithStandardLibrary + +client := resty.New(). + SetJSONMarshaler(json.Marshal). + SetJSONUnmarshaler(json.Unmarshal) + +// similarly user could do for XML too with - +client.SetXMLMarshaler(xml.Marshal). + SetXMLUnmarshaler(xml.Unmarshal) +``` + +### Multipart File(s) upload + +#### Using io.Reader + +```go +profileImgBytes, _ := os.ReadFile("/Users/jeeva/test-img.png") +notesBytes, _ := os.ReadFile("/Users/jeeva/text-file.txt") + +// Create a Resty Client +client := resty.New() + +resp, err := client.R(). + SetFileReader("profile_img", "test-img.png", bytes.NewReader(profileImgBytes)). + SetFileReader("notes", "text-file.txt", bytes.NewReader(notesBytes)). + SetFormData(map[string]string{ + "first_name": "Jeevanandam", + "last_name": "M", + }). + Post("http://myapp.com/upload") +``` + +#### Using File directly from Path + +```go +// Create a Resty Client +client := resty.New() + +// Single file scenario +resp, err := client.R(). + SetFile("profile_img", "/Users/jeeva/test-img.png"). + Post("http://myapp.com/upload") + +// Multiple files scenario +resp, err := client.R(). + SetFiles(map[string]string{ + "profile_img": "/Users/jeeva/test-img.png", + "notes": "/Users/jeeva/text-file.txt", + }). + Post("http://myapp.com/upload") + +// Multipart of form fields and files +resp, err := client.R(). + SetFiles(map[string]string{ + "profile_img": "/Users/jeeva/test-img.png", + "notes": "/Users/jeeva/text-file.txt", + }). + SetFormData(map[string]string{ + "first_name": "Jeevanandam", + "last_name": "M", + "zip_code": "00001", + "city": "my city", + "access_token": "C6A79608-782F-4ED0-A11D-BD82FAD829CD", + }). + Post("http://myapp.com/profile") +``` + +#### Sample Form submission + +```go +// Create a Resty Client +client := resty.New() + +// just mentioning about POST as an example with simple flow +// User Login +resp, err := client.R(). + SetFormData(map[string]string{ + "username": "jeeva", + "password": "mypass", + }). + Post("http://myapp.com/login") + +// Followed by profile update +resp, err := client.R(). + SetFormData(map[string]string{ + "first_name": "Jeevanandam", + "last_name": "M", + "zip_code": "00001", + "city": "new city update", + }). + Post("http://myapp.com/profile") + +// Multi value form data +criteria := url.Values{ + "search_criteria": []string{"book", "glass", "pencil"}, +} +resp, err := client.R(). + SetFormDataFromValues(criteria). + Post("http://myapp.com/search") +``` + +#### Save HTTP Response into File + +```go +// Create a Resty Client +client := resty.New() + +// Setting output directory path, If directory not exists then resty creates one! +// This is optional one, if you're planning using absolute path in +// `Request.SetOutput` and can used together. +client.SetOutputDirectory("/Users/jeeva/Downloads") + +// HTTP response gets saved into file, similar to curl -o flag +_, err := client.R(). + SetOutput("plugin/ReplyWithHeader-v5.1-beta.zip"). + Get("http://bit.ly/1LouEKr") + +// OR using absolute path +// Note: output directory path is not used for absolute path +_, err := client.R(). + SetOutput("/MyDownloads/plugin/ReplyWithHeader-v5.1-beta.zip"). + Get("http://bit.ly/1LouEKr") +``` + +#### Request URL Path Params + +Resty provides easy to use dynamic request URL path params. Params can be set at client and request level. Client level params value can be overridden at request level. + +```go +// Create a Resty Client +client := resty.New() + +client.R().SetPathParams(map[string]string{ + "userId": "sample@sample.com", + "subAccountId": "100002", +}). +Get("/v1/users/{userId}/{subAccountId}/details") + +// Result: +// Composed URL - /v1/users/sample@sample.com/100002/details +``` + +#### Request and Response Middleware + +Resty provides middleware ability to manipulate for Request and Response. It is more flexible than callback approach. + +```go +// Create a Resty Client +client := resty.New() + +// Registering Request Middleware +client.OnBeforeRequest(func(c *resty.Client, req *resty.Request) error { + // Now you have access to Client and current Request object + // manipulate it as per your need + + return nil // if its success otherwise return error + }) + +// Registering Response Middleware +client.OnAfterResponse(func(c *resty.Client, resp *resty.Response) error { + // Now you have access to Client and current Response object + // manipulate it as per your need + + return nil // if its success otherwise return error + }) +``` + +#### OnError Hooks + +Resty provides OnError hooks that may be called because: + +- The client failed to send the request due to connection timeout, TLS handshake failure, etc... +- The request was retried the maximum amount of times, and still failed. + +If there was a response from the server, the original error will be wrapped in `*resty.ResponseError` which contains the last response received. + +```go +// Create a Resty Client +client := resty.New() + +client.OnError(func(req *resty.Request, err error) { + if v, ok := err.(*resty.ResponseError); ok { + // v.Response contains the last response from the server + // v.Err contains the original error + } + // Log the error, increment a metric, etc... +}) +``` + +#### Generate CURL Command +>Refer: [curl_cmd_test.go](https://github.com/go-resty/resty/blob/v2/curl_cmd_test.go) + +```go +// Create a Resty Client +client := resty.New() + +resp, err := client.R(). + SetDebug(true). + EnableGenerateCurlOnDebug(). // CURL command generated when debug mode enabled with this option + SetBody(map[string]string{"name": "Alex"}). + Post("https://httpbin.org/post") + +curlCmdExecuted := resp.Request.GenerateCurlCommand() + +// Explore curl command +fmt.Println("Curl Command:\n ", curlCmdExecuted+"\n") + +/* Output +Curl Command: + curl -X POST -H 'Content-Type: application/json' -H 'User-Agent: go-resty/2.14.0 (https://github.com/go-resty/resty)' -d '{"name":"Alex"}' https://httpbin.org/post +*/ +``` + +#### Redirect Policy + +Resty provides few ready to use redirect policy(s) also it supports multiple policies together. + +```go +// Create a Resty Client +client := resty.New() + +// Assign Client Redirect Policy. Create one as per you need +client.SetRedirectPolicy(resty.FlexibleRedirectPolicy(15)) + +// Wanna multiple policies such as redirect count, domain name check, etc +client.SetRedirectPolicy(resty.FlexibleRedirectPolicy(20), + resty.DomainCheckRedirectPolicy("host1.com", "host2.org", "host3.net")) +``` + +##### Custom Redirect Policy + +Implement [RedirectPolicy](redirect.go#L20) interface and register it with resty client. Have a look [redirect.go](redirect.go) for more information. + +```go +// Create a Resty Client +client := resty.New() + +// Using raw func into resty.SetRedirectPolicy +client.SetRedirectPolicy(resty.RedirectPolicyFunc(func(req *http.Request, via []*http.Request) error { + // Implement your logic here + + // return nil for continue redirect otherwise return error to stop/prevent redirect + return nil +})) + +//--------------------------------------------------- + +// Using struct create more flexible redirect policy +type CustomRedirectPolicy struct { + // variables goes here +} + +func (c *CustomRedirectPolicy) Apply(req *http.Request, via []*http.Request) error { + // Implement your logic here + + // return nil for continue redirect otherwise return error to stop/prevent redirect + return nil +} + +// Registering in resty +client.SetRedirectPolicy(CustomRedirectPolicy{/* initialize variables */}) +``` + +#### Custom Root Certificates and Client Certificates + +```go +// Create a Resty Client +client := resty.New() + +// Custom Root certificates, just supply .pem file. +// you can add one or more root certificates, its get appended +client.SetRootCertificate("/path/to/root/pemFile1.pem") +client.SetRootCertificate("/path/to/root/pemFile2.pem") +// ... and so on! + +// Adding Client Certificates, you add one or more certificates +// Sample for creating certificate object +// Parsing public/private key pair from a pair of files. The files must contain PEM encoded data. +cert1, err := tls.LoadX509KeyPair("certs/client.pem", "certs/client.key") +if err != nil { + log.Fatalf("ERROR client certificate: %s", err) +} +// ... + +// You add one or more certificates +client.SetCertificates(cert1, cert2, cert3) +``` + +#### Custom Root Certificates and Client Certificates from string + +```go +// Custom Root certificates from string +// You can pass you certificates through env variables as strings +// you can add one or more root certificates, its get appended +client.SetRootCertificateFromString("-----BEGIN CERTIFICATE-----content-----END CERTIFICATE-----") +client.SetRootCertificateFromString("-----BEGIN CERTIFICATE-----content-----END CERTIFICATE-----") +// ... and so on! + +// Adding Client Certificates, you add one or more certificates +// Sample for creating certificate object +// Parsing public/private key pair from a pair of files. The files must contain PEM encoded data. +cert1, err := tls.X509KeyPair([]byte("-----BEGIN CERTIFICATE-----content-----END CERTIFICATE-----"), []byte("-----BEGIN CERTIFICATE-----content-----END CERTIFICATE-----")) +if err != nil { + log.Fatalf("ERROR client certificate: %s", err) +} +// ... + +// You add one or more certificates +client.SetCertificates(cert1, cert2, cert3) +``` + +#### Proxy Settings + +Default `Go` supports Proxy via environment variable `HTTP_PROXY`. Resty provides support via `SetProxy` & `RemoveProxy`. +Choose as per your need. + +**Client Level Proxy** settings applied to all the request + +```go +// Create a Resty Client +client := resty.New() + +// Setting a Proxy URL and Port +client.SetProxy("http://proxyserver:8888") + +// Want to remove proxy setting +client.RemoveProxy() +``` + +#### Retries + +Resty uses [backoff](http://www.awsarchitectureblog.com/2015/03/backoff.html) +to increase retry intervals after each attempt. + +Usage example: + +```go +// Create a Resty Client +client := resty.New() + +// Retries are configured per client +client. + // Set retry count to non zero to enable retries + SetRetryCount(3). + // You can override initial retry wait time. + // Default is 100 milliseconds. + SetRetryWaitTime(5 * time.Second). + // MaxWaitTime can be overridden as well. + // Default is 2 seconds. + SetRetryMaxWaitTime(20 * time.Second). + // SetRetryAfter sets callback to calculate wait time between retries. + // Default (nil) implies exponential backoff with jitter + SetRetryAfter(func(client *resty.Client, resp *resty.Response) (time.Duration, error) { + return 0, errors.New("quota exceeded") + }) +``` + +By default, resty will retry requests that return a non-nil error during execution. +Therefore, the above setup will result in resty retrying requests with non-nil errors up to 3 times, +with the delay increasing after each attempt. + +You can optionally provide client with [custom retry conditions](https://pkg.go.dev/github.com/go-resty/resty/v2#RetryConditionFunc): + +```go +// Create a Resty Client +client := resty.New() + +client.AddRetryCondition( + // RetryConditionFunc type is for retry condition function + // input: non-nil Response OR request execution error + func(r *resty.Response, err error) bool { + return r.StatusCode() == http.StatusTooManyRequests + }, +) +``` + +The above example will make resty retry requests that end with a `429 Too Many Requests` status code. +It's important to note that when you specify conditions using `AddRetryCondition`, +it will override the default retry behavior, which retries on errors encountered during the request. +If you want to retry on errors encountered during the request, similar to the default behavior, +you'll need to configure it as follows: + +```go +// Create a Resty Client +client := resty.New() + +client.AddRetryCondition( + func(r *resty.Response, err error) bool { + // Including "err != nil" emulates the default retry behavior for errors encountered during the request. + return err != nil || r.StatusCode() == http.StatusTooManyRequests + }, +) +``` + +Multiple retry conditions can be added. +Note that if multiple conditions are specified, a retry will occur if any of the conditions are met. + +It is also possible to use `resty.Backoff(...)` to get arbitrary retry scenarios +implemented. [Reference](retry_test.go). + +#### Allow GET request with Payload + +```go +// Create a Resty Client +client := resty.New() + +// Allow GET request with Payload. This is disabled by default. +client.SetAllowGetMethodPayload(true) +``` + +#### Wanna Multiple Clients + +```go +// Here you go! +// Client 1 +client1 := resty.New() +client1.R().Get("http://httpbin.org") +// ... + +// Client 2 +client2 := resty.New() +client2.R().Head("http://httpbin.org") +// ... + +// Bend it as per your need!!! +``` + +#### Remaining Client Settings & its Options + +```go +// Create a Resty Client +client := resty.New() + +// Unique settings at Client level +//-------------------------------- +// Enable debug mode +client.SetDebug(true) + +// Assign Client TLSClientConfig +// One can set custom root-certificate. Refer: http://golang.org/pkg/crypto/tls/#example_Dial +client.SetTLSClientConfig(&tls.Config{ RootCAs: roots }) + +// or One can disable security check (https) +client.SetTLSClientConfig(&tls.Config{ InsecureSkipVerify: true }) + +// Set client timeout as per your need +client.SetTimeout(1 * time.Minute) + + +// You can override all below settings and options at request level if you want to +//-------------------------------------------------------------------------------- +// Host URL for all request. So you can use relative URL in the request +client.SetBaseURL("http://httpbin.org") + +// Headers for all request +client.SetHeader("Accept", "application/json") +client.SetHeaders(map[string]string{ + "Content-Type": "application/json", + "User-Agent": "My custom User Agent String", + }) + +// Cookies for all request +client.SetCookie(&http.Cookie{ + Name:"go-resty", + Value:"This is cookie value", + Path: "/", + Domain: "sample.com", + MaxAge: 36000, + HttpOnly: true, + Secure: false, + }) +client.SetCookies(cookies) + +// URL query parameters for all request +client.SetQueryParam("user_id", "00001") +client.SetQueryParams(map[string]string{ // sample of those who use this manner + "api_key": "api-key-here", + "api_secret": "api-secret", + }) +client.R().SetQueryString("productId=232&template=fresh-sample&cat=resty&source=google&kw=buy a lot more") + +// Form data for all request. Typically used with POST and PUT +client.SetFormData(map[string]string{ + "access_token": "BC594900-518B-4F7E-AC75-BD37F019E08F", + }) + +// Basic Auth for all request +client.SetBasicAuth("myuser", "mypass") + +// Bearer Auth Token for all request +client.SetAuthToken("BC594900518B4F7EAC75BD37F019E08FBC594900518B4F7EAC75BD37F019E08F") + +// Enabling Content length value for all request +client.SetContentLength(true) + +// Registering global Error object structure for JSON/XML request +client.SetError(&Error{}) // or resty.SetError(Error{}) +``` + +#### Unix Socket + +```go +unixSocket := "/var/run/my_socket.sock" + +// Create a Go's http.Transport so we can set it in resty. +transport := http.Transport{ + Dial: func(_, _ string) (net.Conn, error) { + return net.Dial("unix", unixSocket) + }, +} + +// Create a Resty Client +client := resty.New() + +// Set the previous transport that we created, set the scheme of the communication to the +// socket and set the unixSocket as the HostURL. +client.SetTransport(&transport).SetScheme("http").SetBaseURL(unixSocket) + +// No need to write the host's URL on the request, just the path. +client.R().Get("http://localhost/index.html") +``` + +#### Bazel Support + +Resty can be built, tested and depended upon via [Bazel](https://bazel.build). +For example, to run all tests: + +```shell +bazel test :resty_test +``` + +#### Mocking http requests using [httpmock](https://github.com/jarcoal/httpmock) library + +In order to mock the http requests when testing your application you +could use the `httpmock` library. + +When using the default resty client, you should pass the client to the library as follow: + +```go +// Create a Resty Client +client := resty.New() + +// Get the underlying HTTP Client and set it to Mock +httpmock.ActivateNonDefault(client.GetClient()) +``` + +More detailed example of mocking resty http requests using ginko could be found [here](https://github.com/jarcoal/httpmock#ginkgo--resty-example). + +## Versioning + +Resty releases versions according to [Semantic Versioning](http://semver.org) + + * Resty v2 does not use `gopkg.in` service for library versioning. + * Resty fully adapted to `go mod` capabilities since `v1.10.0` release. + * Resty v1 series was using `gopkg.in` to provide versioning. `gopkg.in/resty.vX` points to appropriate tagged versions; `X` denotes version series number and it's a stable release for production use. For e.g. `gopkg.in/resty.v0`. + * Development takes place at the master branch. Although the code in master should always compile and test successfully, it might break API's. I aim to maintain backwards compatibility, but sometimes API's and behavior might be changed to fix a bug. + +## Contribution + +I would welcome your contribution! If you find any improvement or issue you want to fix, feel free to send a pull request, I like pull requests that include test cases for fix/enhancement. I have done my best to bring pretty good code coverage. Feel free to write tests. + +BTW, I'd like to know what you think about `Resty`. Kindly open an issue or send me an email; it'd mean a lot to me. + +## Creator + +[Jeevanandam M.](https://github.com/jeevatkm) (jeeva@myjeeva.com) + +## Core Team + +Have a look on [Members](https://github.com/orgs/go-resty/people) page. + +## Contributors + +Have a look on [Contributors](https://github.com/go-resty/resty/graphs/contributors) page. + +## License + +Resty released under MIT license, refer [LICENSE](LICENSE) file. diff --git a/vendor/github.com/go-resty/resty/v2/WORKSPACE b/vendor/github.com/go-resty/resty/v2/WORKSPACE new file mode 100644 index 000000000..504de1458 --- /dev/null +++ b/vendor/github.com/go-resty/resty/v2/WORKSPACE @@ -0,0 +1,31 @@ +workspace(name = "resty") + +load("@bazel_tools//tools/build_defs/repo:http.bzl", "http_archive") + +http_archive( + name = "io_bazel_rules_go", + sha256 = "80a98277ad1311dacd837f9b16db62887702e9f1d1c4c9f796d0121a46c8e184", + urls = [ + "https://mirror.bazel.build/github.com/bazelbuild/rules_go/releases/download/v0.46.0/rules_go-v0.46.0.zip", + "https://github.com/bazelbuild/rules_go/releases/download/v0.46.0/rules_go-v0.46.0.zip", + ], +) + +http_archive( + name = "bazel_gazelle", + sha256 = "62ca106be173579c0a167deb23358fdfe71ffa1e4cfdddf5582af26520f1c66f", + urls = [ + "https://mirror.bazel.build/github.com/bazelbuild/bazel-gazelle/releases/download/v0.23.0/bazel-gazelle-v0.23.0.tar.gz", + "https://github.com/bazelbuild/bazel-gazelle/releases/download/v0.23.0/bazel-gazelle-v0.23.0.tar.gz", + ], +) + +load("@io_bazel_rules_go//go:deps.bzl", "go_register_toolchains", "go_rules_dependencies") + +go_rules_dependencies() + +go_register_toolchains(version = "1.19") + +load("@bazel_gazelle//:deps.bzl", "gazelle_dependencies") + +gazelle_dependencies() diff --git a/vendor/github.com/go-resty/resty/v2/client.go b/vendor/github.com/go-resty/resty/v2/client.go new file mode 100644 index 000000000..ca0f8af89 --- /dev/null +++ b/vendor/github.com/go-resty/resty/v2/client.go @@ -0,0 +1,1500 @@ +// Copyright (c) 2015-2024 Jeevanandam M (jeeva@myjeeva.com), All rights reserved. +// resty source code and usage is governed by a MIT style +// license that can be found in the LICENSE file. + +package resty + +import ( + "bytes" + "compress/gzip" + "crypto/tls" + "crypto/x509" + "encoding/json" + "encoding/xml" + "errors" + "fmt" + "io" + "math" + "net/http" + "net/url" + "os" + "reflect" + "regexp" + "strings" + "sync" + "time" +) + +const ( + // MethodGet HTTP method + MethodGet = "GET" + + // MethodPost HTTP method + MethodPost = "POST" + + // MethodPut HTTP method + MethodPut = "PUT" + + // MethodDelete HTTP method + MethodDelete = "DELETE" + + // MethodPatch HTTP method + MethodPatch = "PATCH" + + // MethodHead HTTP method + MethodHead = "HEAD" + + // MethodOptions HTTP method + MethodOptions = "OPTIONS" +) + +var ( + hdrUserAgentKey = http.CanonicalHeaderKey("User-Agent") + hdrAcceptKey = http.CanonicalHeaderKey("Accept") + hdrContentTypeKey = http.CanonicalHeaderKey("Content-Type") + hdrContentLengthKey = http.CanonicalHeaderKey("Content-Length") + hdrContentEncodingKey = http.CanonicalHeaderKey("Content-Encoding") + hdrLocationKey = http.CanonicalHeaderKey("Location") + hdrAuthorizationKey = http.CanonicalHeaderKey("Authorization") + hdrWwwAuthenticateKey = http.CanonicalHeaderKey("WWW-Authenticate") + + plainTextType = "text/plain; charset=utf-8" + jsonContentType = "application/json" + formContentType = "application/x-www-form-urlencoded" + + jsonCheck = regexp.MustCompile(`(?i:(application|text)/(.*json.*)(;|$))`) + xmlCheck = regexp.MustCompile(`(?i:(application|text)/(.*xml.*)(;|$))`) + + hdrUserAgentValue = "go-resty/" + Version + " (https://github.com/go-resty/resty)" + bufPool = &sync.Pool{New: func() interface{} { return &bytes.Buffer{} }} +) + +type ( + // RequestMiddleware type is for request middleware, called before a request is sent + RequestMiddleware func(*Client, *Request) error + + // ResponseMiddleware type is for response middleware, called after a response has been received + ResponseMiddleware func(*Client, *Response) error + + // PreRequestHook type is for the request hook, called right before the request is sent + PreRequestHook func(*Client, *http.Request) error + + // RequestLogCallback type is for request logs, called before the request is logged + RequestLogCallback func(*RequestLog) error + + // ResponseLogCallback type is for response logs, called before the response is logged + ResponseLogCallback func(*ResponseLog) error + + // ErrorHook type is for reacting to request errors, called after all retries were attempted + ErrorHook func(*Request, error) + + // SuccessHook type is for reacting to request success + SuccessHook func(*Client, *Response) +) + +// Client struct is used to create a Resty client with client-level settings, +// these settings apply to all the requests raised from the client. +// +// Resty also provides an option to override most of the client settings +// at [Request] level. +type Client struct { + BaseURL string + HostURL string // Deprecated: use BaseURL instead. To be removed in v3.0.0 release. + QueryParam url.Values + FormData url.Values + PathParams map[string]string + RawPathParams map[string]string + Header http.Header + UserInfo *User + Token string + AuthScheme string + Cookies []*http.Cookie + Error reflect.Type + Debug bool + DisableWarn bool + AllowGetMethodPayload bool + RetryCount int + RetryWaitTime time.Duration + RetryMaxWaitTime time.Duration + RetryConditions []RetryConditionFunc + RetryHooks []OnRetryFunc + RetryAfter RetryAfterFunc + RetryResetReaders bool + JSONMarshal func(v interface{}) ([]byte, error) + JSONUnmarshal func(data []byte, v interface{}) error + XMLMarshal func(v interface{}) ([]byte, error) + XMLUnmarshal func(data []byte, v interface{}) error + + // HeaderAuthorizationKey is used to set/access Request Authorization header + // value when `SetAuthToken` option is used. + HeaderAuthorizationKey string + ResponseBodyLimit int + + jsonEscapeHTML bool + setContentLength bool + closeConnection bool + notParseResponse bool + trace bool + debugBodySizeLimit int64 + outputDirectory string + scheme string + log Logger + httpClient *http.Client + proxyURL *url.URL + beforeRequest []RequestMiddleware + udBeforeRequest []RequestMiddleware + udBeforeRequestLock *sync.RWMutex + preReqHook PreRequestHook + successHooks []SuccessHook + afterResponse []ResponseMiddleware + afterResponseLock *sync.RWMutex + requestLog RequestLogCallback + responseLog ResponseLogCallback + errorHooks []ErrorHook + invalidHooks []ErrorHook + panicHooks []ErrorHook + rateLimiter RateLimiter + generateCurlOnDebug bool + unescapeQueryParams bool +} + +// User type is to hold an username and password information +type User struct { + Username, Password string +} + +//‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾ +// Client methods +//___________________________________ + +// SetHostURL method sets the Host URL in the client instance. It will be used with a request +// raised from this client with a relative URL +// +// // Setting HTTP address +// client.SetHostURL("http://myjeeva.com") +// +// // Setting HTTPS address +// client.SetHostURL("https://myjeeva.com") +// +// Deprecated: use [Client.SetBaseURL] instead. To be removed in the v3.0.0 release. +func (c *Client) SetHostURL(url string) *Client { + c.SetBaseURL(url) + return c +} + +// SetBaseURL method sets the Base URL in the client instance. It will be used with a request +// raised from this client with a relative URL +// +// // Setting HTTP address +// client.SetBaseURL("http://myjeeva.com") +// +// // Setting HTTPS address +// client.SetBaseURL("https://myjeeva.com") +func (c *Client) SetBaseURL(url string) *Client { + c.BaseURL = strings.TrimRight(url, "/") + c.HostURL = c.BaseURL + return c +} + +// SetHeader method sets a single header field and its value in the client instance. +// These headers will be applied to all requests from this client instance. +// Also, it can be overridden by request-level header options. +// +// See [Request.SetHeader] or [Request.SetHeaders]. +// +// For Example: To set `Content-Type` and `Accept` as `application/json` +// +// client. +// SetHeader("Content-Type", "application/json"). +// SetHeader("Accept", "application/json") +func (c *Client) SetHeader(header, value string) *Client { + c.Header.Set(header, value) + return c +} + +// SetHeaders method sets multiple header fields and their values at one go in the client instance. +// These headers will be applied to all requests from this client instance. Also, it can be +// overridden at request level headers options. +// +// See [Request.SetHeaders] or [Request.SetHeader]. +// +// For Example: To set `Content-Type` and `Accept` as `application/json` +// +// client.SetHeaders(map[string]string{ +// "Content-Type": "application/json", +// "Accept": "application/json", +// }) +func (c *Client) SetHeaders(headers map[string]string) *Client { + for h, v := range headers { + c.Header.Set(h, v) + } + return c +} + +// SetHeaderVerbatim method sets a single header field and its value verbatim in the current request. +// +// For Example: To set `all_lowercase` and `UPPERCASE` as `available`. +// +// client. +// SetHeaderVerbatim("all_lowercase", "available"). +// SetHeaderVerbatim("UPPERCASE", "available") +func (c *Client) SetHeaderVerbatim(header, value string) *Client { + c.Header[header] = []string{value} + return c +} + +// SetCookieJar method sets custom [http.CookieJar] in the resty client. It's a way to override the default. +// +// For Example, sometimes we don't want to save cookies in API mode so that we can remove the default +// CookieJar in resty client. +// +// client.SetCookieJar(nil) +func (c *Client) SetCookieJar(jar http.CookieJar) *Client { + c.httpClient.Jar = jar + return c +} + +// SetCookie method appends a single cookie to the client instance. +// These cookies will be added to all the requests from this client instance. +// +// client.SetCookie(&http.Cookie{ +// Name:"go-resty", +// Value:"This is cookie value", +// }) +func (c *Client) SetCookie(hc *http.Cookie) *Client { + c.Cookies = append(c.Cookies, hc) + return c +} + +// SetCookies method sets an array of cookies in the client instance. +// These cookies will be added to all the requests from this client instance. +// +// cookies := []*http.Cookie{ +// &http.Cookie{ +// Name:"go-resty-1", +// Value:"This is cookie 1 value", +// }, +// &http.Cookie{ +// Name:"go-resty-2", +// Value:"This is cookie 2 value", +// }, +// } +// +// // Setting a cookies into resty +// client.SetCookies(cookies) +func (c *Client) SetCookies(cs []*http.Cookie) *Client { + c.Cookies = append(c.Cookies, cs...) + return c +} + +// SetQueryParam method sets a single parameter and its value in the client instance. +// It will be formed as a query string for the request. +// +// For Example: `search=kitchen%20papers&size=large` +// +// In the URL after the `?` mark. These query params will be added to all the requests raised from +// this client instance. Also, it can be overridden at the request level. +// +// See [Request.SetQueryParam] or [Request.SetQueryParams]. +// +// client. +// SetQueryParam("search", "kitchen papers"). +// SetQueryParam("size", "large") +func (c *Client) SetQueryParam(param, value string) *Client { + c.QueryParam.Set(param, value) + return c +} + +// SetQueryParams method sets multiple parameters and their values at one go in the client instance. +// It will be formed as a query string for the request. +// +// For Example: `search=kitchen%20papers&size=large` +// +// In the URL after the `?` mark. These query params will be added to all the requests raised from this +// client instance. Also, it can be overridden at the request level. +// +// See [Request.SetQueryParams] or [Request.SetQueryParam]. +// +// client.SetQueryParams(map[string]string{ +// "search": "kitchen papers", +// "size": "large", +// }) +func (c *Client) SetQueryParams(params map[string]string) *Client { + for p, v := range params { + c.SetQueryParam(p, v) + } + return c +} + +// SetUnescapeQueryParams method sets the unescape query parameters choice for request URL. +// To prevent broken URL, resty replaces space (" ") with "+" in the query parameters. +// +// See [Request.SetUnescapeQueryParams] +// +// NOTE: Request failure is possible due to non-standard usage of Unescaped Query Parameters. +func (c *Client) SetUnescapeQueryParams(unescape bool) *Client { + c.unescapeQueryParams = unescape + return c +} + +// SetFormData method sets Form parameters and their values in the client instance. +// It applies only to HTTP methods `POST` and `PUT`, and the request content type would be set as +// `application/x-www-form-urlencoded`. These form data will be added to all the requests raised from +// this client instance. Also, it can be overridden at the request level. +// +// See [Request.SetFormData]. +// +// client.SetFormData(map[string]string{ +// "access_token": "BC594900-518B-4F7E-AC75-BD37F019E08F", +// "user_id": "3455454545", +// }) +func (c *Client) SetFormData(data map[string]string) *Client { + for k, v := range data { + c.FormData.Set(k, v) + } + return c +} + +// SetBasicAuth method sets the basic authentication header in the HTTP request. For Example: +// +// Authorization: Basic +// +// For Example: To set the header for username "go-resty" and password "welcome" +// +// client.SetBasicAuth("go-resty", "welcome") +// +// This basic auth information is added to all requests from this client instance. +// It can also be overridden at the request level. +// +// See [Request.SetBasicAuth]. +func (c *Client) SetBasicAuth(username, password string) *Client { + c.UserInfo = &User{Username: username, Password: password} + return c +} + +// SetAuthToken method sets the auth token of the `Authorization` header for all HTTP requests. +// The default auth scheme is `Bearer`; it can be customized with the method [Client.SetAuthScheme]. For Example: +// +// Authorization: +// +// For Example: To set auth token BC594900518B4F7EAC75BD37F019E08FBC594900518B4F7EAC75BD37F019E08F +// +// client.SetAuthToken("BC594900518B4F7EAC75BD37F019E08FBC594900518B4F7EAC75BD37F019E08F") +// +// This auth token gets added to all the requests raised from this client instance. +// Also, it can be overridden at the request level. +// +// See [Request.SetAuthToken]. +func (c *Client) SetAuthToken(token string) *Client { + c.Token = token + return c +} + +// SetAuthScheme method sets the auth scheme type in the HTTP request. For Example: +// +// Authorization: +// +// For Example: To set the scheme to use OAuth +// +// client.SetAuthScheme("OAuth") +// +// This auth scheme gets added to all the requests raised from this client instance. +// Also, it can be overridden at the request level. +// +// Information about auth schemes can be found in [RFC 7235], IANA [HTTP Auth schemes]. +// +// See [Request.SetAuthToken]. +// +// [RFC 7235]: https://tools.ietf.org/html/rfc7235 +// [HTTP Auth schemes]: https://www.iana.org/assignments/http-authschemes/http-authschemes.xhtml#authschemes +func (c *Client) SetAuthScheme(scheme string) *Client { + c.AuthScheme = scheme + return c +} + +// SetDigestAuth method sets the Digest Access auth scheme for the client. If a server responds with 401 and sends +// a Digest challenge in the WWW-Authenticate Header, requests will be resent with the appropriate Authorization Header. +// +// For Example: To set the Digest scheme with user "Mufasa" and password "Circle Of Life" +// +// client.SetDigestAuth("Mufasa", "Circle Of Life") +// +// Information about Digest Access Authentication can be found in [RFC 7616]. +// +// See [Request.SetDigestAuth]. +// +// [RFC 7616]: https://datatracker.ietf.org/doc/html/rfc7616 +func (c *Client) SetDigestAuth(username, password string) *Client { + oldTransport := c.httpClient.Transport + c.OnBeforeRequest(func(c *Client, _ *Request) error { + c.httpClient.Transport = &digestTransport{ + digestCredentials: digestCredentials{username, password}, + transport: oldTransport, + } + return nil + }) + c.OnAfterResponse(func(c *Client, _ *Response) error { + c.httpClient.Transport = oldTransport + return nil + }) + return c +} + +// R method creates a new request instance; it's used for Get, Post, Put, Delete, Patch, Head, Options, etc. +func (c *Client) R() *Request { + r := &Request{ + QueryParam: url.Values{}, + FormData: url.Values{}, + Header: http.Header{}, + Cookies: make([]*http.Cookie, 0), + PathParams: map[string]string{}, + RawPathParams: map[string]string{}, + Debug: c.Debug, + AuthScheme: c.AuthScheme, + + client: c, + multipartFiles: []*File{}, + multipartFields: []*MultipartField{}, + jsonEscapeHTML: c.jsonEscapeHTML, + log: c.log, + responseBodyLimit: c.ResponseBodyLimit, + generateCurlOnDebug: c.generateCurlOnDebug, + unescapeQueryParams: c.unescapeQueryParams, + } + return r +} + +// NewRequest method is an alias for method `R()`. +func (c *Client) NewRequest() *Request { + return c.R() +} + +// OnBeforeRequest method appends a request middleware to the before request chain. +// The user-defined middlewares are applied before the default Resty request middlewares. +// After all middlewares have been applied, the request is sent from Resty to the host server. +// +// client.OnBeforeRequest(func(c *resty.Client, r *resty.Request) error { +// // Now you have access to the Client and Request instance +// // manipulate it as per your need +// +// return nil // if its successful otherwise return error +// }) +func (c *Client) OnBeforeRequest(m RequestMiddleware) *Client { + c.udBeforeRequestLock.Lock() + defer c.udBeforeRequestLock.Unlock() + + c.udBeforeRequest = append(c.udBeforeRequest, m) + + return c +} + +// OnAfterResponse method appends response middleware to the after-response chain. +// Once we receive a response from the host server, the default Resty response middleware +// gets applied, and then the user-assigned response middleware is applied. +// +// client.OnAfterResponse(func(c *resty.Client, r *resty.Response) error { +// // Now you have access to the Client and Response instance +// // manipulate it as per your need +// +// return nil // if its successful otherwise return error +// }) +func (c *Client) OnAfterResponse(m ResponseMiddleware) *Client { + c.afterResponseLock.Lock() + defer c.afterResponseLock.Unlock() + + c.afterResponse = append(c.afterResponse, m) + + return c +} + +// OnError method adds a callback that will be run whenever a request execution fails. +// This is called after all retries have been attempted (if any). +// If there was a response from the server, the error will be wrapped in [ResponseError] +// which has the last response received from the server. +// +// client.OnError(func(req *resty.Request, err error) { +// if v, ok := err.(*resty.ResponseError); ok { +// // Do something with v.Response +// } +// // Log the error, increment a metric, etc... +// }) +// +// Out of the [Client.OnSuccess], [Client.OnError], [Client.OnInvalid], [Client.OnPanic] +// callbacks, exactly one set will be invoked for each call to [Request.Execute] that completes. +func (c *Client) OnError(h ErrorHook) *Client { + c.errorHooks = append(c.errorHooks, h) + return c +} + +// OnSuccess method adds a callback that will be run whenever a request execution +// succeeds. This is called after all retries have been attempted (if any). +// +// Out of the [Client.OnSuccess], [Client.OnError], [Client.OnInvalid], [Client.OnPanic] +// callbacks, exactly one set will be invoked for each call to [Request.Execute] that completes. +func (c *Client) OnSuccess(h SuccessHook) *Client { + c.successHooks = append(c.successHooks, h) + return c +} + +// OnInvalid method adds a callback that will be run whenever a request execution +// fails before it starts because the request is invalid. +// +// Out of the [Client.OnSuccess], [Client.OnError], [Client.OnInvalid], [Client.OnPanic] +// callbacks, exactly one set will be invoked for each call to [Request.Execute] that completes. +func (c *Client) OnInvalid(h ErrorHook) *Client { + c.invalidHooks = append(c.invalidHooks, h) + return c +} + +// OnPanic method adds a callback that will be run whenever a request execution +// panics. +// +// Out of the [Client.OnSuccess], [Client.OnError], [Client.OnInvalid], [Client.OnPanic] +// callbacks, exactly one set will be invoked for each call to [Request.Execute] that completes. +// +// If an [Client.OnSuccess], [Client.OnError], or [Client.OnInvalid] callback panics, +// then exactly one rule can be violated. +func (c *Client) OnPanic(h ErrorHook) *Client { + c.panicHooks = append(c.panicHooks, h) + return c +} + +// SetPreRequestHook method sets the given pre-request function into a resty client. +// It is called right before the request is fired. +// +// NOTE: Only one pre-request hook can be registered. Use [Client.OnBeforeRequest] for multiple. +func (c *Client) SetPreRequestHook(h PreRequestHook) *Client { + if c.preReqHook != nil { + c.log.Warnf("Overwriting an existing pre-request hook: %s", functionName(h)) + } + c.preReqHook = h + return c +} + +// SetDebug method enables the debug mode on the Resty client. The client logs details +// of every request and response. +// +// client.SetDebug(true) +// +// Also, it can be enabled at the request level for a particular request; see [Request.SetDebug]. +// - For [Request], it logs information such as HTTP verb, Relative URL path, +// Host, Headers, and Body if it has one. +// - For [Response], it logs information such as Status, Response Time, Headers, +// and Body if it has one. +func (c *Client) SetDebug(d bool) *Client { + c.Debug = d + return c +} + +// SetDebugBodyLimit sets the maximum size in bytes for which the response and +// request body will be logged in debug mode. +// +// client.SetDebugBodyLimit(1000000) +func (c *Client) SetDebugBodyLimit(sl int64) *Client { + c.debugBodySizeLimit = sl + return c +} + +// OnRequestLog method sets the request log callback to Resty. Registered callback gets +// called before the resty logs the information. +func (c *Client) OnRequestLog(rl RequestLogCallback) *Client { + if c.requestLog != nil { + c.log.Warnf("Overwriting an existing on-request-log callback from=%s to=%s", + functionName(c.requestLog), functionName(rl)) + } + c.requestLog = rl + return c +} + +// OnResponseLog method sets the response log callback to Resty. Registered callback gets +// called before the resty logs the information. +func (c *Client) OnResponseLog(rl ResponseLogCallback) *Client { + if c.responseLog != nil { + c.log.Warnf("Overwriting an existing on-response-log callback from=%s to=%s", + functionName(c.responseLog), functionName(rl)) + } + c.responseLog = rl + return c +} + +// SetDisableWarn method disables the warning log message on the Resty client. +// +// For example, Resty warns users when BasicAuth is used in non-TLS mode. +// +// client.SetDisableWarn(true) +func (c *Client) SetDisableWarn(d bool) *Client { + c.DisableWarn = d + return c +} + +// SetAllowGetMethodPayload method allows the GET method with payload on the Resty client. +// +// For example, Resty allows the user to send a request with a payload using the HTTP GET method. +// +// client.SetAllowGetMethodPayload(true) +func (c *Client) SetAllowGetMethodPayload(a bool) *Client { + c.AllowGetMethodPayload = a + return c +} + +// SetLogger method sets given writer for logging Resty request and response details. +// +// Compliant to interface [resty.Logger] +func (c *Client) SetLogger(l Logger) *Client { + c.log = l + return c +} + +// SetContentLength method enables the HTTP header `Content-Length` value for every request. +// By default, Resty won't set `Content-Length`. +// +// client.SetContentLength(true) +// +// Also, you have the option to enable a particular request. See [Request.SetContentLength] +func (c *Client) SetContentLength(l bool) *Client { + c.setContentLength = l + return c +} + +// SetTimeout method sets the timeout for a request raised by the client. +// +// client.SetTimeout(time.Duration(1 * time.Minute)) +func (c *Client) SetTimeout(timeout time.Duration) *Client { + c.httpClient.Timeout = timeout + return c +} + +// SetError method registers the global or client common `Error` object into Resty. +// It is used for automatic unmarshalling if the response status code is greater than 399 and +// content type is JSON or XML. It can be a pointer or a non-pointer. +// +// client.SetError(&Error{}) +// // OR +// client.SetError(Error{}) +func (c *Client) SetError(err interface{}) *Client { + c.Error = typeOf(err) + return c +} + +// SetRedirectPolicy method sets the redirect policy for the client. Resty provides ready-to-use +// redirect policies. Wanna create one for yourself, refer to `redirect.go`. +// +// client.SetRedirectPolicy(FlexibleRedirectPolicy(20)) +// +// // Need multiple redirect policies together +// client.SetRedirectPolicy(FlexibleRedirectPolicy(20), DomainCheckRedirectPolicy("host1.com", "host2.net")) +func (c *Client) SetRedirectPolicy(policies ...interface{}) *Client { + for _, p := range policies { + if _, ok := p.(RedirectPolicy); !ok { + c.log.Errorf("%v does not implement resty.RedirectPolicy (missing Apply method)", + functionName(p)) + } + } + + c.httpClient.CheckRedirect = func(req *http.Request, via []*http.Request) error { + for _, p := range policies { + if err := p.(RedirectPolicy).Apply(req, via); err != nil { + return err + } + } + return nil // looks good, go ahead + } + + return c +} + +// SetRetryCount method enables retry on Resty client and allows you +// to set no. of retry count. Resty uses a Backoff mechanism. +func (c *Client) SetRetryCount(count int) *Client { + c.RetryCount = count + return c +} + +// SetRetryWaitTime method sets the default wait time for sleep before retrying +// request. +// +// Default is 100 milliseconds. +func (c *Client) SetRetryWaitTime(waitTime time.Duration) *Client { + c.RetryWaitTime = waitTime + return c +} + +// SetRetryMaxWaitTime method sets the max wait time for sleep before retrying +// request. +// +// Default is 2 seconds. +func (c *Client) SetRetryMaxWaitTime(maxWaitTime time.Duration) *Client { + c.RetryMaxWaitTime = maxWaitTime + return c +} + +// SetRetryAfter sets a callback to calculate the wait time between retries. +// Default (nil) implies exponential backoff with jitter +func (c *Client) SetRetryAfter(callback RetryAfterFunc) *Client { + c.RetryAfter = callback + return c +} + +// SetJSONMarshaler method sets the JSON marshaler function to marshal the request body. +// By default, Resty uses [encoding/json] package to marshal the request body. +func (c *Client) SetJSONMarshaler(marshaler func(v interface{}) ([]byte, error)) *Client { + c.JSONMarshal = marshaler + return c +} + +// SetJSONUnmarshaler method sets the JSON unmarshaler function to unmarshal the response body. +// By default, Resty uses [encoding/json] package to unmarshal the response body. +func (c *Client) SetJSONUnmarshaler(unmarshaler func(data []byte, v interface{}) error) *Client { + c.JSONUnmarshal = unmarshaler + return c +} + +// SetXMLMarshaler method sets the XML marshaler function to marshal the request body. +// By default, Resty uses [encoding/xml] package to marshal the request body. +func (c *Client) SetXMLMarshaler(marshaler func(v interface{}) ([]byte, error)) *Client { + c.XMLMarshal = marshaler + return c +} + +// SetXMLUnmarshaler method sets the XML unmarshaler function to unmarshal the response body. +// By default, Resty uses [encoding/xml] package to unmarshal the response body. +func (c *Client) SetXMLUnmarshaler(unmarshaler func(data []byte, v interface{}) error) *Client { + c.XMLUnmarshal = unmarshaler + return c +} + +// AddRetryCondition method adds a retry condition function to an array of functions +// that are checked to determine if the request is retried. The request will +// retry if any functions return true and the error is nil. +// +// NOTE: These retry conditions are applied on all requests made using this Client. +// For [Request] specific retry conditions, check [Request.AddRetryCondition] +func (c *Client) AddRetryCondition(condition RetryConditionFunc) *Client { + c.RetryConditions = append(c.RetryConditions, condition) + return c +} + +// AddRetryAfterErrorCondition adds the basic condition of retrying after encountering +// an error from the HTTP response +func (c *Client) AddRetryAfterErrorCondition() *Client { + c.AddRetryCondition(func(response *Response, err error) bool { + return response.IsError() + }) + return c +} + +// AddRetryHook adds a side-effecting retry hook to an array of hooks +// that will be executed on each retry. +func (c *Client) AddRetryHook(hook OnRetryFunc) *Client { + c.RetryHooks = append(c.RetryHooks, hook) + return c +} + +// SetRetryResetReaders method enables the Resty client to seek the start of all +// file readers are given as multipart files if the object implements [io.ReadSeeker]. +func (c *Client) SetRetryResetReaders(b bool) *Client { + c.RetryResetReaders = b + return c +} + +// SetTLSClientConfig method sets TLSClientConfig for underlying client Transport. +// +// For Example: +// +// // One can set a custom root certificate. Refer: http://golang.org/pkg/crypto/tls/#example_Dial +// client.SetTLSClientConfig(&tls.Config{ RootCAs: roots }) +// +// // or One can disable security check (https) +// client.SetTLSClientConfig(&tls.Config{ InsecureSkipVerify: true }) +// +// NOTE: This method overwrites existing [http.Transport.TLSClientConfig] +func (c *Client) SetTLSClientConfig(config *tls.Config) *Client { + transport, err := c.Transport() + if err != nil { + c.log.Errorf("%v", err) + return c + } + transport.TLSClientConfig = config + return c +} + +// SetProxy method sets the Proxy URL and Port for the Resty client. +// +// client.SetProxy("http://proxyserver:8888") +// +// OR you could also set Proxy via environment variable, refer to [http.ProxyFromEnvironment] +func (c *Client) SetProxy(proxyURL string) *Client { + transport, err := c.Transport() + if err != nil { + c.log.Errorf("%v", err) + return c + } + + pURL, err := url.Parse(proxyURL) + if err != nil { + c.log.Errorf("%v", err) + return c + } + + c.proxyURL = pURL + transport.Proxy = http.ProxyURL(c.proxyURL) + return c +} + +// RemoveProxy method removes the proxy configuration from the Resty client +// +// client.RemoveProxy() +func (c *Client) RemoveProxy() *Client { + transport, err := c.Transport() + if err != nil { + c.log.Errorf("%v", err) + return c + } + c.proxyURL = nil + transport.Proxy = nil + return c +} + +// SetCertificates method helps to conveniently set client certificates into Resty. +func (c *Client) SetCertificates(certs ...tls.Certificate) *Client { + config, err := c.tlsConfig() + if err != nil { + c.log.Errorf("%v", err) + return c + } + config.Certificates = append(config.Certificates, certs...) + return c +} + +// SetRootCertificate method helps to add one or more root certificates into the Resty client +// +// client.SetRootCertificate("/path/to/root/pemFile.pem") +func (c *Client) SetRootCertificate(pemFilePath string) *Client { + rootPemData, err := os.ReadFile(pemFilePath) + if err != nil { + c.log.Errorf("%v", err) + return c + } + c.handleCAs("root", rootPemData) + return c +} + +// SetRootCertificateFromString method helps to add one or more root certificates +// into the Resty client +// +// client.SetRootCertificateFromString("pem certs content") +func (c *Client) SetRootCertificateFromString(pemCerts string) *Client { + c.handleCAs("root", []byte(pemCerts)) + return c +} + +// SetClientRootCertificate method helps to add one or more client's root +// certificates into the Resty client +// +// client.SetClientRootCertificate("/path/to/root/pemFile.pem") +func (c *Client) SetClientRootCertificate(pemFilePath string) *Client { + rootPemData, err := os.ReadFile(pemFilePath) + if err != nil { + c.log.Errorf("%v", err) + return c + } + c.handleCAs("client", rootPemData) + return c +} + +// SetClientRootCertificateFromString method helps to add one or more clients +// root certificates into the Resty client +// +// client.SetClientRootCertificateFromString("pem certs content") +func (c *Client) SetClientRootCertificateFromString(pemCerts string) *Client { + c.handleCAs("client", []byte(pemCerts)) + return c +} + +func (c *Client) handleCAs(scope string, permCerts []byte) { + config, err := c.tlsConfig() + if err != nil { + c.log.Errorf("%v", err) + return + } + + switch scope { + case "root": + if config.RootCAs == nil { + config.RootCAs = x509.NewCertPool() + } + config.RootCAs.AppendCertsFromPEM(permCerts) + case "client": + if config.ClientCAs == nil { + config.ClientCAs = x509.NewCertPool() + } + config.ClientCAs.AppendCertsFromPEM(permCerts) + } +} + +// SetOutputDirectory method sets the output directory for saving HTTP responses in a file. +// Resty creates one if the output directory does not exist. This setting is optional, +// if you plan to use the absolute path in [Request.SetOutput] and can used together. +// +// client.SetOutputDirectory("/save/http/response/here") +func (c *Client) SetOutputDirectory(dirPath string) *Client { + c.outputDirectory = dirPath + return c +} + +// SetRateLimiter sets an optional [RateLimiter]. If set, the rate limiter will control +// all requests were made by this client. +func (c *Client) SetRateLimiter(rl RateLimiter) *Client { + c.rateLimiter = rl + return c +} + +// SetTransport method sets custom [http.Transport] or any [http.RoundTripper] +// compatible interface implementation in the Resty client. +// +// transport := &http.Transport{ +// // something like Proxying to httptest.Server, etc... +// Proxy: func(req *http.Request) (*url.URL, error) { +// return url.Parse(server.URL) +// }, +// } +// client.SetTransport(transport) +// +// NOTE: +// - If transport is not the type of `*http.Transport`, then you may not be able to +// take advantage of some of the Resty client settings. +// - It overwrites the Resty client transport instance and its configurations. +func (c *Client) SetTransport(transport http.RoundTripper) *Client { + if transport != nil { + c.httpClient.Transport = transport + } + return c +} + +// SetScheme method sets a custom scheme for the Resty client. It's a way to override the default. +// +// client.SetScheme("http") +func (c *Client) SetScheme(scheme string) *Client { + if !IsStringEmpty(scheme) { + c.scheme = strings.TrimSpace(scheme) + } + return c +} + +// SetCloseConnection method sets variable `Close` in HTTP request struct with the given +// value. More info: https://golang.org/src/net/http/request.go +func (c *Client) SetCloseConnection(close bool) *Client { + c.closeConnection = close + return c +} + +// SetDoNotParseResponse method instructs Resty not to parse the response body automatically. +// Resty exposes the raw response body as [io.ReadCloser]. If you use it, do not +// forget to close the body, otherwise, you might get into connection leaks, and connection +// reuse may not happen. +// +// NOTE: [Response] middlewares are not executed using this option. You have +// taken over the control of response parsing from Resty. +func (c *Client) SetDoNotParseResponse(notParse bool) *Client { + c.notParseResponse = notParse + return c +} + +// SetPathParam method sets a single URL path key-value pair in the +// Resty client instance. +// +// client.SetPathParam("userId", "sample@sample.com") +// +// Result: +// URL - /v1/users/{userId}/details +// Composed URL - /v1/users/sample@sample.com/details +// +// It replaces the value of the key while composing the request URL. +// The value will be escaped using [url.PathEscape] function. +// +// It can be overridden at the request level, +// see [Request.SetPathParam] or [Request.SetPathParams] +func (c *Client) SetPathParam(param, value string) *Client { + c.PathParams[param] = value + return c +} + +// SetPathParams method sets multiple URL path key-value pairs at one go in the +// Resty client instance. +// +// client.SetPathParams(map[string]string{ +// "userId": "sample@sample.com", +// "subAccountId": "100002", +// "path": "groups/developers", +// }) +// +// Result: +// URL - /v1/users/{userId}/{subAccountId}/{path}/details +// Composed URL - /v1/users/sample@sample.com/100002/groups%2Fdevelopers/details +// +// It replaces the value of the key while composing the request URL. +// The values will be escaped using [url.PathEscape] function. +// +// It can be overridden at the request level, +// see [Request.SetPathParam] or [Request.SetPathParams] +func (c *Client) SetPathParams(params map[string]string) *Client { + for p, v := range params { + c.SetPathParam(p, v) + } + return c +} + +// SetRawPathParam method sets a single URL path key-value pair in the +// Resty client instance. +// +// client.SetPathParam("userId", "sample@sample.com") +// +// Result: +// URL - /v1/users/{userId}/details +// Composed URL - /v1/users/sample@sample.com/details +// +// client.SetPathParam("path", "groups/developers") +// +// Result: +// URL - /v1/users/{userId}/details +// Composed URL - /v1/users/groups%2Fdevelopers/details +// +// It replaces the value of the key while composing the request URL. +// The value will be used as it is and will not be escaped. +// +// It can be overridden at the request level, +// see [Request.SetRawPathParam] or [Request.SetRawPathParams] +func (c *Client) SetRawPathParam(param, value string) *Client { + c.RawPathParams[param] = value + return c +} + +// SetRawPathParams method sets multiple URL path key-value pairs at one go in the +// Resty client instance. +// +// client.SetPathParams(map[string]string{ +// "userId": "sample@sample.com", +// "subAccountId": "100002", +// "path": "groups/developers", +// }) +// +// Result: +// URL - /v1/users/{userId}/{subAccountId}/{path}/details +// Composed URL - /v1/users/sample@sample.com/100002/groups/developers/details +// +// It replaces the value of the key while composing the request URL. +// The values will be used as they are and will not be escaped. +// +// It can be overridden at the request level, +// see [Request.SetRawPathParam] or [Request.SetRawPathParams] +func (c *Client) SetRawPathParams(params map[string]string) *Client { + for p, v := range params { + c.SetRawPathParam(p, v) + } + return c +} + +// SetJSONEscapeHTML method enables or disables the HTML escape on JSON marshal. +// By default, escape HTML is false. +// +// NOTE: This option only applies to the standard JSON Marshaller used by Resty. +// +// It can be overridden at the request level, see [Client.SetJSONEscapeHTML] +func (c *Client) SetJSONEscapeHTML(b bool) *Client { + c.jsonEscapeHTML = b + return c +} + +// SetResponseBodyLimit method sets a maximum body size limit in bytes on response, +// avoid reading too much data to memory. +// +// Client will return [resty.ErrResponseBodyTooLarge] if the body size of the body +// in the uncompressed response is larger than the limit. +// Body size limit will not be enforced in the following cases: +// - ResponseBodyLimit <= 0, which is the default behavior. +// - [Request.SetOutput] is called to save response data to the file. +// - "DoNotParseResponse" is set for client or request. +// +// It can be overridden at the request level; see [Request.SetResponseBodyLimit] +func (c *Client) SetResponseBodyLimit(v int) *Client { + c.ResponseBodyLimit = v + return c +} + +// EnableTrace method enables the Resty client trace for the requests fired from +// the client using [httptrace.ClientTrace] and provides insights. +// +// client := resty.New().EnableTrace() +// +// resp, err := client.R().Get("https://httpbin.org/get") +// fmt.Println("Error:", err) +// fmt.Println("Trace Info:", resp.Request.TraceInfo()) +// +// The method [Request.EnableTrace] is also available to get trace info for a single request. +func (c *Client) EnableTrace() *Client { + c.trace = true + return c +} + +// DisableTrace method disables the Resty client trace. Refer to [Client.EnableTrace]. +func (c *Client) DisableTrace() *Client { + c.trace = false + return c +} + +// EnableGenerateCurlOnDebug method enables the generation of CURL commands in the debug log. +// It works in conjunction with debug mode. +// +// NOTE: Use with care. +// - Potential to leak sensitive data from [Request] and [Response] in the debug log. +// - Beware of memory usage since the request body is reread. +func (c *Client) EnableGenerateCurlOnDebug() *Client { + c.generateCurlOnDebug = true + return c +} + +// DisableGenerateCurlOnDebug method disables the option set by [Client.EnableGenerateCurlOnDebug]. +func (c *Client) DisableGenerateCurlOnDebug() *Client { + c.generateCurlOnDebug = false + return c +} + +// IsProxySet method returns the true is proxy is set from the Resty client; otherwise +// false. By default, the proxy is set from the environment variable; refer to [http.ProxyFromEnvironment]. +func (c *Client) IsProxySet() bool { + return c.proxyURL != nil +} + +// GetClient method returns the underlying [http.Client] used by the Resty. +func (c *Client) GetClient() *http.Client { + return c.httpClient +} + +// Clone returns a clone of the original client. +// +// NOTE: Use with care: +// - Interface values are not deeply cloned. Thus, both the original and the +// clone will use the same value. +// - This function is not safe for concurrent use. You should only use this method +// when you are sure that any other goroutine is not using the client. +func (c *Client) Clone() *Client { + // dereference the pointer and copy the value + cc := *c + + // lock values should not be copied - thus new values are used. + cc.afterResponseLock = &sync.RWMutex{} + cc.udBeforeRequestLock = &sync.RWMutex{} + return &cc +} + +func (c *Client) executeBefore(req *Request) error { + // Lock the user-defined pre-request hooks. + c.udBeforeRequestLock.RLock() + defer c.udBeforeRequestLock.RUnlock() + + // Lock the post-request hooks. + c.afterResponseLock.RLock() + defer c.afterResponseLock.RUnlock() + + // Apply Request middleware + var err error + + // user defined on before request methods + // to modify the *resty.Request object + for _, f := range c.udBeforeRequest { + if err = f(c, req); err != nil { + return wrapNoRetryErr(err) + } + } + + // If there is a rate limiter set for this client, the Execute call + // will return an error if the rate limit is exceeded. + if req.client.rateLimiter != nil { + if !req.client.rateLimiter.Allow() { + return wrapNoRetryErr(ErrRateLimitExceeded) + } + } + + // resty middlewares + for _, f := range c.beforeRequest { + if err = f(c, req); err != nil { + return wrapNoRetryErr(err) + } + } + + if hostHeader := req.Header.Get("Host"); hostHeader != "" { + req.RawRequest.Host = hostHeader + } + + // call pre-request if defined + if c.preReqHook != nil { + if err = c.preReqHook(c, req.RawRequest); err != nil { + return wrapNoRetryErr(err) + } + } + + if err = requestLogger(c, req); err != nil { + return wrapNoRetryErr(err) + } + + return nil +} + +// Executes method executes the given `Request` object and returns +// response or error. +func (c *Client) execute(req *Request) (*Response, error) { + if err := c.executeBefore(req); err != nil { + return nil, err + } + + req.Time = time.Now() + resp, err := c.httpClient.Do(req.RawRequest) + + response := &Response{ + Request: req, + RawResponse: resp, + } + + if err != nil || req.notParseResponse || c.notParseResponse { + response.setReceivedAt() + if logErr := responseLogger(c, response); logErr != nil { + return response, wrapErrors(logErr, err) + } + if err != nil { + return response, err + } + return response, nil + } + + if !req.isSaveResponse { + defer closeq(resp.Body) + body := resp.Body + + // GitHub #142 & #187 + if strings.EqualFold(resp.Header.Get(hdrContentEncodingKey), "gzip") && resp.ContentLength != 0 { + if _, ok := body.(*gzip.Reader); !ok { + body, err = gzip.NewReader(body) + if err != nil { + err = wrapErrors(responseLogger(c, response), err) + response.setReceivedAt() + return response, err + } + defer closeq(body) + } + } + + if response.body, err = readAllWithLimit(body, req.responseBodyLimit); err != nil { + err = wrapErrors(responseLogger(c, response), err) + response.setReceivedAt() + return response, err + } + + response.size = int64(len(response.body)) + } + + response.setReceivedAt() // after we read the body + + // Apply Response middleware + err = responseLogger(c, response) + if err != nil { + return response, wrapNoRetryErr(err) + } + + for _, f := range c.afterResponse { + if err = f(c, response); err != nil { + break + } + } + + return response, wrapNoRetryErr(err) +} + +var ErrResponseBodyTooLarge = errors.New("resty: response body too large") + +// https://github.com/golang/go/issues/51115 +// [io.LimitedReader] can only return [io.EOF] +func readAllWithLimit(r io.Reader, maxSize int) ([]byte, error) { + if maxSize <= 0 { + return io.ReadAll(r) + } + + var buf [512]byte // make buf stack allocated + result := make([]byte, 0, 512) + total := 0 + for { + n, err := r.Read(buf[:]) + total += n + if total > maxSize { + return nil, ErrResponseBodyTooLarge + } + + if err != nil { + if err == io.EOF { + result = append(result, buf[:n]...) + break + } + return nil, err + } + + result = append(result, buf[:n]...) + } + + return result, nil +} + +// getting TLS client config if not exists then create one +func (c *Client) tlsConfig() (*tls.Config, error) { + transport, err := c.Transport() + if err != nil { + return nil, err + } + if transport.TLSClientConfig == nil { + transport.TLSClientConfig = &tls.Config{} + } + return transport.TLSClientConfig, nil +} + +// Transport method returns [http.Transport] currently in use or error +// in case the currently used `transport` is not a [http.Transport]. +// +// Since v2.8.0 has become exported method. +func (c *Client) Transport() (*http.Transport, error) { + if transport, ok := c.httpClient.Transport.(*http.Transport); ok { + return transport, nil + } + return nil, errors.New("current transport is not an *http.Transport instance") +} + +// just an internal helper method +func (c *Client) outputLogTo(w io.Writer) *Client { + c.log.(*logger).l.SetOutput(w) + return c +} + +// ResponseError is a wrapper that includes the server response with an error. +// Neither the err nor the response should be nil. +type ResponseError struct { + Response *Response + Err error +} + +func (e *ResponseError) Error() string { + return e.Err.Error() +} + +func (e *ResponseError) Unwrap() error { + return e.Err +} + +// Helper to run errorHooks hooks. +// It wraps the error in a [ResponseError] if the resp is not nil +// so hooks can access it. +func (c *Client) onErrorHooks(req *Request, resp *Response, err error) { + if err != nil { + if resp != nil { // wrap with ResponseError + err = &ResponseError{Response: resp, Err: err} + } + for _, h := range c.errorHooks { + h(req, err) + } + } else { + for _, h := range c.successHooks { + h(c, resp) + } + } +} + +// Helper to run panicHooks hooks. +func (c *Client) onPanicHooks(req *Request, err error) { + for _, h := range c.panicHooks { + h(req, err) + } +} + +// Helper to run invalidHooks hooks. +func (c *Client) onInvalidHooks(req *Request, err error) { + for _, h := range c.invalidHooks { + h(req, err) + } +} + +//‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾ +// File struct and its methods +//_______________________________________________________________________ + +// File struct represents file information for multipart request +type File struct { + Name string + ParamName string + io.Reader +} + +// String method returns the string value of current file details +func (f *File) String() string { + return fmt.Sprintf("ParamName: %v; FileName: %v", f.ParamName, f.Name) +} + +//‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾ +// MultipartField struct +//_______________________________________________________________________ + +// MultipartField struct represents the custom data part for a multipart request +type MultipartField struct { + Param string + FileName string + ContentType string + io.Reader +} + +func createClient(hc *http.Client) *Client { + if hc.Transport == nil { + hc.Transport = createTransport(nil) + } + + c := &Client{ // not setting lang default values + QueryParam: url.Values{}, + FormData: url.Values{}, + Header: http.Header{}, + Cookies: make([]*http.Cookie, 0), + RetryWaitTime: defaultWaitTime, + RetryMaxWaitTime: defaultMaxWaitTime, + PathParams: make(map[string]string), + RawPathParams: make(map[string]string), + JSONMarshal: json.Marshal, + JSONUnmarshal: json.Unmarshal, + XMLMarshal: xml.Marshal, + XMLUnmarshal: xml.Unmarshal, + HeaderAuthorizationKey: http.CanonicalHeaderKey("Authorization"), + AuthScheme: "Bearer", + + jsonEscapeHTML: true, + httpClient: hc, + debugBodySizeLimit: math.MaxInt32, + udBeforeRequestLock: &sync.RWMutex{}, + afterResponseLock: &sync.RWMutex{}, + } + + // Logger + c.SetLogger(createLogger()) + + // default before request middlewares + c.beforeRequest = []RequestMiddleware{ + parseRequestURL, + parseRequestHeader, + parseRequestBody, + createHTTPRequest, + addCredentials, + createCurlCmd, + } + + // user defined request middlewares + c.udBeforeRequest = []RequestMiddleware{} + + // default after response middlewares + c.afterResponse = []ResponseMiddleware{ + parseResponseBody, + saveResponseIntoFile, + } + + return c +} diff --git a/vendor/github.com/go-resty/resty/v2/digest.go b/vendor/github.com/go-resty/resty/v2/digest.go new file mode 100644 index 000000000..3a08477de --- /dev/null +++ b/vendor/github.com/go-resty/resty/v2/digest.go @@ -0,0 +1,327 @@ +// Copyright (c) 2015-2024 Jeevanandam M (jeeva@myjeeva.com) +// 2023 Segev Dagan (https://github.com/segevda) +// 2024 Philipp Wolfer (https://github.com/phw) +// All rights reserved. +// resty source code and usage is governed by a MIT style +// license that can be found in the LICENSE file. + +package resty + +import ( + "crypto/md5" + "crypto/rand" + "crypto/sha256" + "crypto/sha512" + "errors" + "fmt" + "hash" + "io" + "net/http" + "strings" +) + +var ( + ErrDigestBadChallenge = errors.New("digest: challenge is bad") + ErrDigestCharset = errors.New("digest: unsupported charset") + ErrDigestAlgNotSupported = errors.New("digest: algorithm is not supported") + ErrDigestQopNotSupported = errors.New("digest: no supported qop in list") + ErrDigestNoQop = errors.New("digest: qop must be specified") +) + +var hashFuncs = map[string]func() hash.Hash{ + "": md5.New, + "MD5": md5.New, + "MD5-sess": md5.New, + "SHA-256": sha256.New, + "SHA-256-sess": sha256.New, + "SHA-512-256": sha512.New, + "SHA-512-256-sess": sha512.New, +} + +type digestCredentials struct { + username, password string +} + +type digestTransport struct { + digestCredentials + transport http.RoundTripper +} + +func (dt *digestTransport) RoundTrip(req *http.Request) (*http.Response, error) { + // Copy the request, so we don't modify the input. + req2 := new(http.Request) + *req2 = *req + req2.Header = make(http.Header) + for k, s := range req.Header { + req2.Header[k] = s + } + + // Fix http: ContentLength=xxx with Body length 0 + if req2.Body == nil { + req2.ContentLength = 0 + } else if req2.GetBody != nil { + var err error + req2.Body, err = req2.GetBody() + if err != nil { + return nil, err + } + } + + // Make a request to get the 401 that contains the challenge. + resp, err := dt.transport.RoundTrip(req) + if err != nil || resp.StatusCode != http.StatusUnauthorized { + return resp, err + } + chal := resp.Header.Get(hdrWwwAuthenticateKey) + if chal == "" { + return resp, ErrDigestBadChallenge + } + + c, err := parseChallenge(chal) + if err != nil { + return resp, err + } + + // Form credentials based on the challenge + cr := dt.newCredentials(req2, c) + auth, err := cr.authorize() + if err != nil { + return resp, err + } + err = resp.Body.Close() + if err != nil { + return nil, err + } + + // Make authenticated request + req2.Header.Set(hdrAuthorizationKey, auth) + return dt.transport.RoundTrip(req2) +} + +func (dt *digestTransport) newCredentials(req *http.Request, c *challenge) *credentials { + return &credentials{ + username: dt.username, + userhash: c.userhash, + realm: c.realm, + nonce: c.nonce, + digestURI: req.URL.RequestURI(), + algorithm: c.algorithm, + sessionAlg: strings.HasSuffix(c.algorithm, "-sess"), + opaque: c.opaque, + messageQop: c.qop, + nc: 0, + method: req.Method, + password: dt.password, + } +} + +type challenge struct { + realm string + domain string + nonce string + opaque string + stale string + algorithm string + qop string + userhash string +} + +func (c *challenge) setValue(k, v string) error { + switch k { + case "realm": + c.realm = v + case "domain": + c.domain = v + case "nonce": + c.nonce = v + case "opaque": + c.opaque = v + case "stale": + c.stale = v + case "algorithm": + c.algorithm = v + case "qop": + c.qop = v + case "charset": + if strings.ToUpper(v) != "UTF-8" { + return ErrDigestCharset + } + case "userhash": + c.userhash = v + default: + return ErrDigestBadChallenge + } + return nil +} + +func parseChallenge(input string) (*challenge, error) { + const ws = " \n\r\t" + s := strings.Trim(input, ws) + if !strings.HasPrefix(s, "Digest ") { + return nil, ErrDigestBadChallenge + } + s = strings.Trim(s[7:], ws) + c := &challenge{} + b := strings.Builder{} + key := "" + quoted := false + for _, r := range s { + switch r { + case '"': + quoted = !quoted + case ',': + if quoted { + b.WriteRune(r) + } else { + val := strings.Trim(b.String(), ws) + b.Reset() + if err := c.setValue(key, val); err != nil { + return nil, err + } + key = "" + } + case '=': + if quoted { + b.WriteRune(r) + } else { + key = strings.Trim(b.String(), ws) + b.Reset() + } + default: + b.WriteRune(r) + } + } + if quoted || (key == "" && b.Len() > 0) { + return nil, ErrDigestBadChallenge + } + if key != "" { + val := strings.Trim(b.String(), ws) + if err := c.setValue(key, val); err != nil { + return nil, err + } + } + return c, nil +} + +type credentials struct { + username string + userhash string + realm string + nonce string + digestURI string + algorithm string + sessionAlg bool + cNonce string + opaque string + messageQop string + nc int + method string + password string +} + +func (c *credentials) authorize() (string, error) { + if _, ok := hashFuncs[c.algorithm]; !ok { + return "", ErrDigestAlgNotSupported + } + + if err := c.validateQop(); err != nil { + return "", err + } + + resp, err := c.resp() + if err != nil { + return "", err + } + + sl := make([]string, 0, 10) + if c.userhash == "true" { + // RFC 7616 3.4.4 + c.username = c.h(fmt.Sprintf("%s:%s", c.username, c.realm)) + sl = append(sl, fmt.Sprintf(`userhash=%s`, c.userhash)) + } + sl = append(sl, fmt.Sprintf(`username="%s"`, c.username)) + sl = append(sl, fmt.Sprintf(`realm="%s"`, c.realm)) + sl = append(sl, fmt.Sprintf(`nonce="%s"`, c.nonce)) + sl = append(sl, fmt.Sprintf(`uri="%s"`, c.digestURI)) + sl = append(sl, fmt.Sprintf(`response="%s"`, resp)) + sl = append(sl, fmt.Sprintf(`algorithm=%s`, c.algorithm)) + if c.opaque != "" { + sl = append(sl, fmt.Sprintf(`opaque="%s"`, c.opaque)) + } + if c.messageQop != "" { + sl = append(sl, fmt.Sprintf("qop=%s", c.messageQop)) + sl = append(sl, fmt.Sprintf("nc=%08x", c.nc)) + sl = append(sl, fmt.Sprintf(`cnonce="%s"`, c.cNonce)) + } + + return fmt.Sprintf("Digest %s", strings.Join(sl, ", ")), nil +} + +func (c *credentials) validateQop() error { + // Currently only supporting auth quality of protection. TODO: add auth-int support + // NOTE: cURL support auth-int qop for requests other than POST and PUT (i.e. w/o body) by hashing an empty string + // is this applicable for resty? see: https://github.com/curl/curl/blob/307b7543ea1e73ab04e062bdbe4b5bb409eaba3a/lib/vauth/digest.c#L774 + if c.messageQop == "" { + return ErrDigestNoQop + } + possibleQops := strings.Split(c.messageQop, ",") + var authSupport bool + for _, qop := range possibleQops { + qop = strings.TrimSpace(qop) + if qop == "auth" { + authSupport = true + break + } + } + if !authSupport { + return ErrDigestQopNotSupported + } + + c.messageQop = "auth" + + return nil +} + +func (c *credentials) h(data string) string { + hfCtor := hashFuncs[c.algorithm] + hf := hfCtor() + _, _ = hf.Write([]byte(data)) // Hash.Write never returns an error + return fmt.Sprintf("%x", hf.Sum(nil)) +} + +func (c *credentials) resp() (string, error) { + c.nc++ + + b := make([]byte, 16) + _, err := io.ReadFull(rand.Reader, b) + if err != nil { + return "", err + } + c.cNonce = fmt.Sprintf("%x", b)[:32] + + ha1 := c.ha1() + ha2 := c.ha2() + + return c.kd(ha1, fmt.Sprintf("%s:%08x:%s:%s:%s", + c.nonce, c.nc, c.cNonce, c.messageQop, ha2)), nil +} + +func (c *credentials) kd(secret, data string) string { + return c.h(fmt.Sprintf("%s:%s", secret, data)) +} + +// RFC 7616 3.4.2 +func (c *credentials) ha1() string { + ret := c.h(fmt.Sprintf("%s:%s:%s", c.username, c.realm, c.password)) + if c.sessionAlg { + return c.h(fmt.Sprintf("%s:%s:%s", ret, c.nonce, c.cNonce)) + } + + return ret +} + +// RFC 7616 3.4.3 +func (c *credentials) ha2() string { + // currently no auth-int support + return c.h(fmt.Sprintf("%s:%s", c.method, c.digestURI)) +} diff --git a/vendor/github.com/go-resty/resty/v2/middleware.go b/vendor/github.com/go-resty/resty/v2/middleware.go new file mode 100644 index 000000000..d94b8a1d5 --- /dev/null +++ b/vendor/github.com/go-resty/resty/v2/middleware.go @@ -0,0 +1,613 @@ +// Copyright (c) 2015-2024 Jeevanandam M (jeeva@myjeeva.com), All rights reserved. +// resty source code and usage is governed by a MIT style +// license that can be found in the LICENSE file. + +package resty + +import ( + "bytes" + "errors" + "fmt" + "io" + "mime/multipart" + "net/http" + "net/url" + "os" + "path/filepath" + "reflect" + "strconv" + "strings" + "time" +) + +const debugRequestLogKey = "__restyDebugRequestLog" + +//‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾ +// Request Middleware(s) +//_______________________________________________________________________ + +func parseRequestURL(c *Client, r *Request) error { + if l := len(c.PathParams) + len(c.RawPathParams) + len(r.PathParams) + len(r.RawPathParams); l > 0 { + params := make(map[string]string, l) + + // GitHub #103 Path Params + for p, v := range r.PathParams { + params[p] = url.PathEscape(v) + } + for p, v := range c.PathParams { + if _, ok := params[p]; !ok { + params[p] = url.PathEscape(v) + } + } + + // GitHub #663 Raw Path Params + for p, v := range r.RawPathParams { + if _, ok := params[p]; !ok { + params[p] = v + } + } + for p, v := range c.RawPathParams { + if _, ok := params[p]; !ok { + params[p] = v + } + } + + if len(params) > 0 { + var prev int + buf := acquireBuffer() + defer releaseBuffer(buf) + // search for the next or first opened curly bracket + for curr := strings.Index(r.URL, "{"); curr == 0 || curr > prev; curr = prev + strings.Index(r.URL[prev:], "{") { + // write everything from the previous position up to the current + if curr > prev { + buf.WriteString(r.URL[prev:curr]) + } + // search for the closed curly bracket from current position + next := curr + strings.Index(r.URL[curr:], "}") + // if not found, then write the remainder and exit + if next < curr { + buf.WriteString(r.URL[curr:]) + prev = len(r.URL) + break + } + // special case for {}, without parameter's name + if next == curr+1 { + buf.WriteString("{}") + } else { + // check for the replacement + key := r.URL[curr+1 : next] + value, ok := params[key] + /// keep the original string if the replacement not found + if !ok { + value = r.URL[curr : next+1] + } + buf.WriteString(value) + } + + // set the previous position after the closed curly bracket + prev = next + 1 + if prev >= len(r.URL) { + break + } + } + if buf.Len() > 0 { + // write remainder + if prev < len(r.URL) { + buf.WriteString(r.URL[prev:]) + } + r.URL = buf.String() + } + } + } + + // Parsing request URL + reqURL, err := url.Parse(r.URL) + if err != nil { + return err + } + + // If Request.URL is relative path then added c.HostURL into + // the request URL otherwise Request.URL will be used as-is + if !reqURL.IsAbs() { + r.URL = reqURL.String() + if len(r.URL) > 0 && r.URL[0] != '/' { + r.URL = "/" + r.URL + } + + // TODO: change to use c.BaseURL only in v3.0.0 + baseURL := c.BaseURL + if len(baseURL) == 0 { + baseURL = c.HostURL + } + reqURL, err = url.Parse(baseURL + r.URL) + if err != nil { + return err + } + } + + // GH #407 && #318 + if reqURL.Scheme == "" && len(c.scheme) > 0 { + reqURL.Scheme = c.scheme + } + + // Adding Query Param + if len(c.QueryParam)+len(r.QueryParam) > 0 { + for k, v := range c.QueryParam { + // skip query parameter if it was set in request + if _, ok := r.QueryParam[k]; ok { + continue + } + + r.QueryParam[k] = v[:] + } + + // GitHub #123 Preserve query string order partially. + // Since not feasible in `SetQuery*` resty methods, because + // standard package `url.Encode(...)` sorts the query params + // alphabetically + if len(r.QueryParam) > 0 { + if IsStringEmpty(reqURL.RawQuery) { + reqURL.RawQuery = r.QueryParam.Encode() + } else { + reqURL.RawQuery = reqURL.RawQuery + "&" + r.QueryParam.Encode() + } + } + } + + // GH#797 Unescape query parameters + if r.unescapeQueryParams && len(reqURL.RawQuery) > 0 { + // at this point, all errors caught up in the above operations + // so ignore the return error on query unescape; I realized + // while writing the unit test + unescapedQuery, _ := url.QueryUnescape(reqURL.RawQuery) + reqURL.RawQuery = strings.ReplaceAll(unescapedQuery, " ", "+") // otherwise request becomes bad request + } + + r.URL = reqURL.String() + + return nil +} + +func parseRequestHeader(c *Client, r *Request) error { + for k, v := range c.Header { + if _, ok := r.Header[k]; ok { + continue + } + r.Header[k] = v[:] + } + + if IsStringEmpty(r.Header.Get(hdrUserAgentKey)) { + r.Header.Set(hdrUserAgentKey, hdrUserAgentValue) + } + + if ct := r.Header.Get(hdrContentTypeKey); IsStringEmpty(r.Header.Get(hdrAcceptKey)) && !IsStringEmpty(ct) && (IsJSONType(ct) || IsXMLType(ct)) { + r.Header.Set(hdrAcceptKey, r.Header.Get(hdrContentTypeKey)) + } + + return nil +} + +func parseRequestBody(c *Client, r *Request) error { + if isPayloadSupported(r.Method, c.AllowGetMethodPayload) { + switch { + case r.isMultiPart: // Handling Multipart + if err := handleMultipart(c, r); err != nil { + return err + } + case len(c.FormData) > 0 || len(r.FormData) > 0: // Handling Form Data + handleFormData(c, r) + case r.Body != nil: // Handling Request body + handleContentType(c, r) + + if err := handleRequestBody(c, r); err != nil { + return err + } + } + } + + // by default resty won't set content length, you can if you want to :) + if c.setContentLength || r.setContentLength { + if r.bodyBuf == nil { + r.Header.Set(hdrContentLengthKey, "0") + } else { + r.Header.Set(hdrContentLengthKey, strconv.Itoa(r.bodyBuf.Len())) + } + } + + return nil +} + +func createHTTPRequest(c *Client, r *Request) (err error) { + if r.bodyBuf == nil { + if reader, ok := r.Body.(io.Reader); ok && isPayloadSupported(r.Method, c.AllowGetMethodPayload) { + r.RawRequest, err = http.NewRequest(r.Method, r.URL, reader) + } else if c.setContentLength || r.setContentLength { + r.RawRequest, err = http.NewRequest(r.Method, r.URL, http.NoBody) + } else { + r.RawRequest, err = http.NewRequest(r.Method, r.URL, nil) + } + } else { + // fix data race: must deep copy. + bodyBuf := bytes.NewBuffer(append([]byte{}, r.bodyBuf.Bytes()...)) + r.RawRequest, err = http.NewRequest(r.Method, r.URL, bodyBuf) + } + + if err != nil { + return + } + + // Assign close connection option + r.RawRequest.Close = c.closeConnection + + // Add headers into http request + r.RawRequest.Header = r.Header + + // Add cookies from client instance into http request + for _, cookie := range c.Cookies { + r.RawRequest.AddCookie(cookie) + } + + // Add cookies from request instance into http request + for _, cookie := range r.Cookies { + r.RawRequest.AddCookie(cookie) + } + + // Enable trace + if c.trace || r.trace { + r.clientTrace = &clientTrace{} + r.ctx = r.clientTrace.createContext(r.Context()) + } + + // Use context if it was specified + if r.ctx != nil { + r.RawRequest = r.RawRequest.WithContext(r.ctx) + } + + // assign get body func for the underlying raw request instance + if r.RawRequest.GetBody == nil { + bodyCopy, err := getBodyCopy(r) + if err != nil { + return err + } + if bodyCopy != nil { + buf := bodyCopy.Bytes() + r.RawRequest.GetBody = func() (io.ReadCloser, error) { + b := bytes.NewReader(buf) + return io.NopCloser(b), nil + } + } + } + + return +} + +func addCredentials(c *Client, r *Request) error { + var isBasicAuth bool + // Basic Auth + if r.UserInfo != nil { // takes precedence + r.RawRequest.SetBasicAuth(r.UserInfo.Username, r.UserInfo.Password) + isBasicAuth = true + } else if c.UserInfo != nil { + r.RawRequest.SetBasicAuth(c.UserInfo.Username, c.UserInfo.Password) + isBasicAuth = true + } + + if !c.DisableWarn { + if isBasicAuth && !strings.HasPrefix(r.URL, "https") { + r.log.Warnf("Using Basic Auth in HTTP mode is not secure, use HTTPS") + } + } + + // Build the token Auth header + if !IsStringEmpty(r.Token) { + r.RawRequest.Header.Set(c.HeaderAuthorizationKey, strings.TrimSpace(r.AuthScheme+" "+r.Token)) + } else if !IsStringEmpty(c.Token) { + r.RawRequest.Header.Set(c.HeaderAuthorizationKey, strings.TrimSpace(r.AuthScheme+" "+c.Token)) + } + + return nil +} + +func createCurlCmd(c *Client, r *Request) (err error) { + if r.Debug && r.generateCurlOnDebug { + if r.resultCurlCmd == nil { + r.resultCurlCmd = new(string) + } + *r.resultCurlCmd = buildCurlRequest(r.RawRequest, c.httpClient.Jar) + } + return nil +} + +func requestLogger(c *Client, r *Request) error { + if r.Debug { + rr := r.RawRequest + rh := copyHeaders(rr.Header) + if c.GetClient().Jar != nil { + for _, cookie := range c.GetClient().Jar.Cookies(r.RawRequest.URL) { + s := fmt.Sprintf("%s=%s", cookie.Name, cookie.Value) + if c := rh.Get("Cookie"); c != "" { + rh.Set("Cookie", c+"; "+s) + } else { + rh.Set("Cookie", s) + } + } + } + rl := &RequestLog{Header: rh, Body: r.fmtBodyString(c.debugBodySizeLimit)} + if c.requestLog != nil { + if err := c.requestLog(rl); err != nil { + return err + } + } + + reqLog := "\n==============================================================================\n" + + if r.Debug && r.generateCurlOnDebug { + reqLog += "~~~ REQUEST(CURL) ~~~\n" + + fmt.Sprintf(" %v\n", *r.resultCurlCmd) + } + + reqLog += "~~~ REQUEST ~~~\n" + + fmt.Sprintf("%s %s %s\n", r.Method, rr.URL.RequestURI(), rr.Proto) + + fmt.Sprintf("HOST : %s\n", rr.URL.Host) + + fmt.Sprintf("HEADERS:\n%s\n", composeHeaders(c, r, rl.Header)) + + fmt.Sprintf("BODY :\n%v\n", rl.Body) + + "------------------------------------------------------------------------------\n" + + r.initValuesMap() + r.values[debugRequestLogKey] = reqLog + } + + return nil +} + +//‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾ +// Response Middleware(s) +//_______________________________________________________________________ + +func responseLogger(c *Client, res *Response) error { + if res.Request.Debug { + rl := &ResponseLog{Header: copyHeaders(res.Header()), Body: res.fmtBodyString(c.debugBodySizeLimit)} + if c.responseLog != nil { + if err := c.responseLog(rl); err != nil { + return err + } + } + + debugLog := res.Request.values[debugRequestLogKey].(string) + debugLog += "~~~ RESPONSE ~~~\n" + + fmt.Sprintf("STATUS : %s\n", res.Status()) + + fmt.Sprintf("PROTO : %s\n", res.Proto()) + + fmt.Sprintf("RECEIVED AT : %v\n", res.ReceivedAt().Format(time.RFC3339Nano)) + + fmt.Sprintf("TIME DURATION: %v\n", res.Time()) + + "HEADERS :\n" + + composeHeaders(c, res.Request, rl.Header) + "\n" + if res.Request.isSaveResponse { + debugLog += "BODY :\n***** RESPONSE WRITTEN INTO FILE *****\n" + } else { + debugLog += fmt.Sprintf("BODY :\n%v\n", rl.Body) + } + debugLog += "==============================================================================\n" + + res.Request.log.Debugf("%s", debugLog) + } + + return nil +} + +func parseResponseBody(c *Client, res *Response) (err error) { + if res.StatusCode() == http.StatusNoContent { + res.Request.Error = nil + return + } + // Handles only JSON or XML content type + ct := firstNonEmpty(res.Request.forceContentType, res.Header().Get(hdrContentTypeKey), res.Request.fallbackContentType) + if IsJSONType(ct) || IsXMLType(ct) { + // HTTP status code > 199 and < 300, considered as Result + if res.IsSuccess() { + res.Request.Error = nil + if res.Request.Result != nil { + err = Unmarshalc(c, ct, res.body, res.Request.Result) + return + } + } + + // HTTP status code > 399, considered as Error + if res.IsError() { + // global error interface + if res.Request.Error == nil && c.Error != nil { + res.Request.Error = reflect.New(c.Error).Interface() + } + + if res.Request.Error != nil { + unmarshalErr := Unmarshalc(c, ct, res.body, res.Request.Error) + if unmarshalErr != nil { + c.log.Warnf("Cannot unmarshal response body: %s", unmarshalErr) + } + } + } + } + + return +} + +func handleMultipart(c *Client, r *Request) error { + r.bodyBuf = acquireBuffer() + w := multipart.NewWriter(r.bodyBuf) + + // Set boundary if not set by user + if r.multipartBoundary != "" { + if err := w.SetBoundary(r.multipartBoundary); err != nil { + return err + } + } + + for k, v := range c.FormData { + for _, iv := range v { + if err := w.WriteField(k, iv); err != nil { + return err + } + } + } + + for k, v := range r.FormData { + for _, iv := range v { + if strings.HasPrefix(k, "@") { // file + if err := addFile(w, k[1:], iv); err != nil { + return err + } + } else { // form value + if err := w.WriteField(k, iv); err != nil { + return err + } + } + } + } + + // #21 - adding io.Reader support + for _, f := range r.multipartFiles { + if err := addFileReader(w, f); err != nil { + return err + } + } + + // GitHub #130 adding multipart field support with content type + for _, mf := range r.multipartFields { + if err := addMultipartFormField(w, mf); err != nil { + return err + } + } + + r.Header.Set(hdrContentTypeKey, w.FormDataContentType()) + return w.Close() +} + +func handleFormData(c *Client, r *Request) { + for k, v := range c.FormData { + if _, ok := r.FormData[k]; ok { + continue + } + r.FormData[k] = v[:] + } + + r.bodyBuf = acquireBuffer() + r.bodyBuf.WriteString(r.FormData.Encode()) + r.Header.Set(hdrContentTypeKey, formContentType) + r.isFormData = true +} + +func handleContentType(c *Client, r *Request) { + contentType := r.Header.Get(hdrContentTypeKey) + if IsStringEmpty(contentType) { + contentType = DetectContentType(r.Body) + r.Header.Set(hdrContentTypeKey, contentType) + } +} + +func handleRequestBody(c *Client, r *Request) error { + var bodyBytes []byte + r.bodyBuf = nil + + switch body := r.Body.(type) { + case io.Reader: + if c.setContentLength || r.setContentLength { // keep backward compatibility + r.bodyBuf = acquireBuffer() + if _, err := r.bodyBuf.ReadFrom(body); err != nil { + return err + } + r.Body = nil + } else { + // Otherwise buffer less processing for `io.Reader`, sounds good. + return nil + } + case []byte: + bodyBytes = body + case string: + bodyBytes = []byte(body) + default: + contentType := r.Header.Get(hdrContentTypeKey) + kind := kindOf(r.Body) + var err error + if IsJSONType(contentType) && (kind == reflect.Struct || kind == reflect.Map || kind == reflect.Slice) { + r.bodyBuf, err = jsonMarshal(c, r, r.Body) + } else if IsXMLType(contentType) && (kind == reflect.Struct) { + bodyBytes, err = c.XMLMarshal(r.Body) + } + if err != nil { + return err + } + } + + if bodyBytes == nil && r.bodyBuf == nil { + return errors.New("unsupported 'Body' type/value") + } + + // []byte into Buffer + if bodyBytes != nil && r.bodyBuf == nil { + r.bodyBuf = acquireBuffer() + _, _ = r.bodyBuf.Write(bodyBytes) + } + + return nil +} + +func saveResponseIntoFile(c *Client, res *Response) error { + if res.Request.isSaveResponse { + file := "" + + if len(c.outputDirectory) > 0 && !filepath.IsAbs(res.Request.outputFile) { + file += c.outputDirectory + string(filepath.Separator) + } + + file = filepath.Clean(file + res.Request.outputFile) + if err := createDirectory(filepath.Dir(file)); err != nil { + return err + } + + outFile, err := os.Create(file) + if err != nil { + return err + } + defer closeq(outFile) + + // io.Copy reads maximum 32kb size, it is perfect for large file download too + defer closeq(res.RawResponse.Body) + + written, err := io.Copy(outFile, res.RawResponse.Body) + if err != nil { + return err + } + + res.size = written + } + + return nil +} + +func getBodyCopy(r *Request) (*bytes.Buffer, error) { + // If r.bodyBuf present, return the copy + if r.bodyBuf != nil { + bodyCopy := acquireBuffer() + if _, err := io.Copy(bodyCopy, bytes.NewReader(r.bodyBuf.Bytes())); err != nil { + // cannot use io.Copy(bodyCopy, r.bodyBuf) because io.Copy reset r.bodyBuf + return nil, err + } + return bodyCopy, nil + } + + // Maybe body is `io.Reader`. + // Note: Resty user have to watchout for large body size of `io.Reader` + if r.RawRequest.Body != nil { + b, err := io.ReadAll(r.RawRequest.Body) + if err != nil { + return nil, err + } + + // Restore the Body + closeq(r.RawRequest.Body) + r.RawRequest.Body = io.NopCloser(bytes.NewBuffer(b)) + + // Return the Body bytes + return bytes.NewBuffer(b), nil + } + return nil, nil +} diff --git a/vendor/github.com/go-resty/resty/v2/redirect.go b/vendor/github.com/go-resty/resty/v2/redirect.go new file mode 100644 index 000000000..bb016e277 --- /dev/null +++ b/vendor/github.com/go-resty/resty/v2/redirect.go @@ -0,0 +1,106 @@ +// Copyright (c) 2015-2024 Jeevanandam M (jeeva@myjeeva.com), All rights reserved. +// resty source code and usage is governed by a MIT style +// license that can be found in the LICENSE file. + +package resty + +import ( + "errors" + "fmt" + "net" + "net/http" + "strings" +) + +var ( + ErrAutoRedirectDisabled = errors.New("auto redirect is disabled") +) + +type ( + // RedirectPolicy to regulate the redirects in the Resty client. + // Objects implementing the [RedirectPolicy] interface can be registered as + // + // Apply function should return nil to continue the redirect journey; otherwise + // return error to stop the redirect. + RedirectPolicy interface { + Apply(req *http.Request, via []*http.Request) error + } + + // The [RedirectPolicyFunc] type is an adapter to allow the use of ordinary + // functions as [RedirectPolicy]. If `f` is a function with the appropriate + // signature, RedirectPolicyFunc(f) is a RedirectPolicy object that calls `f`. + RedirectPolicyFunc func(*http.Request, []*http.Request) error +) + +// Apply calls f(req, via). +func (f RedirectPolicyFunc) Apply(req *http.Request, via []*http.Request) error { + return f(req, via) +} + +// NoRedirectPolicy is used to disable redirects in the Resty client +// +// resty.SetRedirectPolicy(NoRedirectPolicy()) +func NoRedirectPolicy() RedirectPolicy { + return RedirectPolicyFunc(func(req *http.Request, via []*http.Request) error { + return ErrAutoRedirectDisabled + }) +} + +// FlexibleRedirectPolicy method is convenient for creating several redirect policies for Resty clients. +// +// resty.SetRedirectPolicy(FlexibleRedirectPolicy(20)) +func FlexibleRedirectPolicy(noOfRedirect int) RedirectPolicy { + return RedirectPolicyFunc(func(req *http.Request, via []*http.Request) error { + if len(via) >= noOfRedirect { + return fmt.Errorf("stopped after %d redirects", noOfRedirect) + } + checkHostAndAddHeaders(req, via[0]) + return nil + }) +} + +// DomainCheckRedirectPolicy method is convenient for defining domain name redirect rules in Resty clients. +// Redirect is allowed only for the host mentioned in the policy. +// +// resty.SetRedirectPolicy(DomainCheckRedirectPolicy("host1.com", "host2.org", "host3.net")) +func DomainCheckRedirectPolicy(hostnames ...string) RedirectPolicy { + hosts := make(map[string]bool) + for _, h := range hostnames { + hosts[strings.ToLower(h)] = true + } + + fn := RedirectPolicyFunc(func(req *http.Request, via []*http.Request) error { + if ok := hosts[getHostname(req.URL.Host)]; !ok { + return errors.New("redirect is not allowed as per DomainCheckRedirectPolicy") + } + + return nil + }) + + return fn +} + +func getHostname(host string) (hostname string) { + if strings.Index(host, ":") > 0 { + host, _, _ = net.SplitHostPort(host) + } + hostname = strings.ToLower(host) + return +} + +// By default, Golang will not redirect request headers. +// After reading through the various discussion comments from the thread - +// https://github.com/golang/go/issues/4800 +// Resty will add all the headers during a redirect for the same host and +// adds library user-agent if the Host is different. +func checkHostAndAddHeaders(cur *http.Request, pre *http.Request) { + curHostname := getHostname(cur.URL.Host) + preHostname := getHostname(pre.URL.Host) + if strings.EqualFold(curHostname, preHostname) { + for key, val := range pre.Header { + cur.Header[key] = val + } + } else { // only library User-Agent header is added + cur.Header.Set(hdrUserAgentKey, hdrUserAgentValue) + } +} diff --git a/vendor/github.com/go-resty/resty/v2/request.go b/vendor/github.com/go-resty/resty/v2/request.go new file mode 100644 index 000000000..6c6f9259e --- /dev/null +++ b/vendor/github.com/go-resty/resty/v2/request.go @@ -0,0 +1,1190 @@ +// Copyright (c) 2015-2024 Jeevanandam M (jeeva@myjeeva.com), All rights reserved. +// resty source code and usage is governed by a MIT style +// license that can be found in the LICENSE file. + +package resty + +import ( + "bytes" + "context" + "encoding/json" + "encoding/xml" + "fmt" + "io" + "net" + "net/http" + "net/url" + "reflect" + "strings" + "time" +) + +//‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾ +// Request struct and methods +//_______________________________________________________________________ + +// Request struct is used to compose and fire individual requests from +// Resty client. The [Request] provides an option to override client-level +// settings and also an option for the request composition. +type Request struct { + URL string + Method string + Token string + AuthScheme string + QueryParam url.Values + FormData url.Values + PathParams map[string]string + RawPathParams map[string]string + Header http.Header + Time time.Time + Body interface{} + Result interface{} + resultCurlCmd *string + Error interface{} + RawRequest *http.Request + SRV *SRVRecord + UserInfo *User + Cookies []*http.Cookie + Debug bool + + // Attempt is to represent the request attempt made during a Resty + // request execution flow, including retry count. + Attempt int + + isMultiPart bool + isFormData bool + setContentLength bool + isSaveResponse bool + notParseResponse bool + jsonEscapeHTML bool + trace bool + outputFile string + fallbackContentType string + forceContentType string + ctx context.Context + values map[string]interface{} + client *Client + bodyBuf *bytes.Buffer + clientTrace *clientTrace + log Logger + multipartBoundary string + multipartFiles []*File + multipartFields []*MultipartField + retryConditions []RetryConditionFunc + responseBodyLimit int + generateCurlOnDebug bool + unescapeQueryParams bool +} + +// GenerateCurlCommand method generates the CURL command for the request. +func (r *Request) GenerateCurlCommand() string { + if !(r.Debug && r.generateCurlOnDebug) { + return "" + } + + if r.resultCurlCmd != nil { + return *r.resultCurlCmd + } + + if r.RawRequest == nil { + r.client.executeBefore(r) // mock with r.Get("/") + } + if r.resultCurlCmd == nil { + r.resultCurlCmd = new(string) + } + *r.resultCurlCmd = buildCurlRequest(r.RawRequest, r.client.httpClient.Jar) + return *r.resultCurlCmd +} + +// Context method returns the Context if it is already set in the [Request] +// otherwise, it creates a new one using [context.Background]. +func (r *Request) Context() context.Context { + if r.ctx == nil { + return context.Background() + } + return r.ctx +} + +// SetContext method sets the [context.Context] for current [Request]. It allows +// to interrupt the request execution if `ctx.Done()` channel is closed. +// See https://blog.golang.org/context article and the package [context] +// documentation. +func (r *Request) SetContext(ctx context.Context) *Request { + r.ctx = ctx + return r +} + +// SetHeader method sets a single header field and its value in the current request. +// +// For Example: To set `Content-Type` and `Accept` as `application/json`. +// +// client.R(). +// SetHeader("Content-Type", "application/json"). +// SetHeader("Accept", "application/json") +// +// It overrides the header value set at the client instance level. +func (r *Request) SetHeader(header, value string) *Request { + r.Header.Set(header, value) + return r +} + +// SetHeaders method sets multiple header fields and their values at one go in the current request. +// +// For Example: To set `Content-Type` and `Accept` as `application/json` +// +// client.R(). +// SetHeaders(map[string]string{ +// "Content-Type": "application/json", +// "Accept": "application/json", +// }) +// +// It overrides the header value set at the client instance level. +func (r *Request) SetHeaders(headers map[string]string) *Request { + for h, v := range headers { + r.SetHeader(h, v) + } + return r +} + +// SetHeaderMultiValues sets multiple header fields and their values as a list of strings in the current request. +// +// For Example: To set `Accept` as `text/html, application/xhtml+xml, application/xml;q=0.9, image/webp, */*;q=0.8` +// +// client.R(). +// SetHeaderMultiValues(map[string][]string{ +// "Accept": []string{"text/html", "application/xhtml+xml", "application/xml;q=0.9", "image/webp", "*/*;q=0.8"}, +// }) +// +// It overrides the header value set at the client instance level. +func (r *Request) SetHeaderMultiValues(headers map[string][]string) *Request { + for key, values := range headers { + r.SetHeader(key, strings.Join(values, ", ")) + } + return r +} + +// SetHeaderVerbatim method sets a single header field and its value verbatim in the current request. +// +// For Example: To set `all_lowercase` and `UPPERCASE` as `available`. +// +// client.R(). +// SetHeaderVerbatim("all_lowercase", "available"). +// SetHeaderVerbatim("UPPERCASE", "available") +// +// It overrides the header value set at the client instance level. +func (r *Request) SetHeaderVerbatim(header, value string) *Request { + r.Header[header] = []string{value} + return r +} + +// SetQueryParam method sets a single parameter and its value in the current request. +// It will be formed as a query string for the request. +// +// For Example: `search=kitchen%20papers&size=large` in the URL after the `?` mark. +// +// client.R(). +// SetQueryParam("search", "kitchen papers"). +// SetQueryParam("size", "large") +// +// It overrides the query parameter value set at the client instance level. +func (r *Request) SetQueryParam(param, value string) *Request { + r.QueryParam.Set(param, value) + return r +} + +// SetQueryParams method sets multiple parameters and their values at one go in the current request. +// It will be formed as a query string for the request. +// +// For Example: `search=kitchen%20papers&size=large` in the URL after the `?` mark. +// +// client.R(). +// SetQueryParams(map[string]string{ +// "search": "kitchen papers", +// "size": "large", +// }) +// +// It overrides the query parameter value set at the client instance level. +func (r *Request) SetQueryParams(params map[string]string) *Request { + for p, v := range params { + r.SetQueryParam(p, v) + } + return r +} + +// SetUnescapeQueryParams method sets the unescape query parameters choice for request URL. +// To prevent broken URL, resty replaces space (" ") with "+" in the query parameters. +// +// This method overrides the value set by [Client.SetUnescapeQueryParams] +// +// NOTE: Request failure is possible due to non-standard usage of Unescaped Query Parameters. +func (r *Request) SetUnescapeQueryParams(unescape bool) *Request { + r.unescapeQueryParams = unescape + return r +} + +// SetQueryParamsFromValues method appends multiple parameters with multi-value +// ([url.Values]) at one go in the current request. It will be formed as +// query string for the request. +// +// For Example: `status=pending&status=approved&status=open` in the URL after the `?` mark. +// +// client.R(). +// SetQueryParamsFromValues(url.Values{ +// "status": []string{"pending", "approved", "open"}, +// }) +// +// It overrides the query parameter value set at the client instance level. +func (r *Request) SetQueryParamsFromValues(params url.Values) *Request { + for p, v := range params { + for _, pv := range v { + r.QueryParam.Add(p, pv) + } + } + return r +} + +// SetQueryString method provides the ability to use string as an input to set URL query string for the request. +// +// client.R(). +// SetQueryString("productId=232&template=fresh-sample&cat=resty&source=google&kw=buy a lot more") +// +// It overrides the query parameter value set at the client instance level. +func (r *Request) SetQueryString(query string) *Request { + params, err := url.ParseQuery(strings.TrimSpace(query)) + if err == nil { + for p, v := range params { + for _, pv := range v { + r.QueryParam.Add(p, pv) + } + } + } else { + r.log.Errorf("%v", err) + } + return r +} + +// SetFormData method sets Form parameters and their values for the current request. +// It applies only to HTTP methods `POST` and `PUT`, and by default requests +// content type would be set as `application/x-www-form-urlencoded`. +// +// client.R(). +// SetFormData(map[string]string{ +// "access_token": "BC594900-518B-4F7E-AC75-BD37F019E08F", +// "user_id": "3455454545", +// }) +// +// It overrides the form data value set at the client instance level. +func (r *Request) SetFormData(data map[string]string) *Request { + for k, v := range data { + r.FormData.Set(k, v) + } + return r +} + +// SetFormDataFromValues method appends multiple form parameters with multi-value +// ([url.Values]) at one go in the current request. +// +// client.R(). +// SetFormDataFromValues(url.Values{ +// "search_criteria": []string{"book", "glass", "pencil"}, +// }) +// +// It overrides the form data value set at the client instance level. +func (r *Request) SetFormDataFromValues(data url.Values) *Request { + for k, v := range data { + for _, kv := range v { + r.FormData.Add(k, kv) + } + } + return r +} + +// SetBody method sets the request body for the request. It supports various practical needs as easy. +// It's quite handy and powerful. Supported request body data types are `string`, +// `[]byte`, `struct`, `map`, `slice` and [io.Reader]. +// +// Body value can be pointer or non-pointer. Automatic marshalling for JSON and XML content type, if it is `struct`, `map`, or `slice`. +// +// NOTE: [io.Reader] is processed in bufferless mode while sending a request. +// +// For Example: +// +// `struct` gets marshaled based on the request header `Content-Type`. +// +// client.R(). +// SetBody(User{ +// Username: "jeeva@myjeeva.com", +// Password: "welcome2resty", +// }) +// +// 'map` gets marshaled based on the request header `Content-Type`. +// +// client.R(). +// SetBody(map[string]interface{}{ +// "username": "jeeva@myjeeva.com", +// "password": "welcome2resty", +// "address": &Address{ +// Address1: "1111 This is my street", +// Address2: "Apt 201", +// City: "My City", +// State: "My State", +// ZipCode: 00000, +// }, +// }) +// +// `string` as a body input. Suitable for any need as a string input. +// +// client.R(). +// SetBody(`{ +// "username": "jeeva@getrightcare.com", +// "password": "admin" +// }`) +// +// `[]byte` as a body input. Suitable for raw requests such as file upload, serialize & deserialize, etc. +// +// client.R(). +// SetBody([]byte("This is my raw request, sent as-is")) +// +// and so on. +func (r *Request) SetBody(body interface{}) *Request { + r.Body = body + return r +} + +// SetResult method is to register the response `Result` object for automatic +// unmarshalling of the HTTP response if the response status code is +// between 200 and 299, and the content type is JSON or XML. +// +// Note: [Request.SetResult] input can be a pointer or non-pointer. +// +// The pointer with handle +// +// authToken := &AuthToken{} +// client.R().SetResult(authToken) +// +// // Can be accessed via - +// fmt.Println(authToken) OR fmt.Println(response.Result().(*AuthToken)) +// +// OR - +// +// The pointer without handle or non-pointer +// +// client.R().SetResult(&AuthToken{}) +// // OR +// client.R().SetResult(AuthToken{}) +// +// // Can be accessed via - +// fmt.Println(response.Result().(*AuthToken)) +func (r *Request) SetResult(res interface{}) *Request { + if res != nil { + r.Result = getPointer(res) + } + return r +} + +// SetError method is to register the request `Error` object for automatic unmarshalling for the request, +// if the response status code is greater than 399 and the content type is either JSON or XML. +// +// NOTE: [Request.SetError] input can be a pointer or non-pointer. +// +// client.R().SetError(&AuthError{}) +// // OR +// client.R().SetError(AuthError{}) +// +// Accessing an error value from response instance. +// +// response.Error().(*AuthError) +// +// If this request Error object is nil, Resty will use the client-level error object Type if it is set. +func (r *Request) SetError(err interface{}) *Request { + r.Error = getPointer(err) + return r +} + +// SetFile method sets a single file field name and its path for multipart upload. +// +// client.R(). +// SetFile("my_file", "/Users/jeeva/Gas Bill - Sep.pdf") +func (r *Request) SetFile(param, filePath string) *Request { + r.isMultiPart = true + r.FormData.Set("@"+param, filePath) + return r +} + +// SetFiles method sets multiple file field names and their paths for multipart uploads. +// +// client.R(). +// SetFiles(map[string]string{ +// "my_file1": "/Users/jeeva/Gas Bill - Sep.pdf", +// "my_file2": "/Users/jeeva/Electricity Bill - Sep.pdf", +// "my_file3": "/Users/jeeva/Water Bill - Sep.pdf", +// }) +func (r *Request) SetFiles(files map[string]string) *Request { + r.isMultiPart = true + for f, fp := range files { + r.FormData.Set("@"+f, fp) + } + return r +} + +// SetFileReader method is to set a file using [io.Reader] for multipart upload. +// +// client.R(). +// SetFileReader("profile_img", "my-profile-img.png", bytes.NewReader(profileImgBytes)). +// SetFileReader("notes", "user-notes.txt", bytes.NewReader(notesBytes)) +func (r *Request) SetFileReader(param, fileName string, reader io.Reader) *Request { + r.isMultiPart = true + r.multipartFiles = append(r.multipartFiles, &File{ + Name: fileName, + ParamName: param, + Reader: reader, + }) + return r +} + +// SetMultipartFormData method allows simple form data to be attached to the request +// as `multipart:form-data` +func (r *Request) SetMultipartFormData(data map[string]string) *Request { + for k, v := range data { + r = r.SetMultipartField(k, "", "", strings.NewReader(v)) + } + + return r +} + +// SetMultipartField method sets custom data with Content-Type using [io.Reader] for multipart upload. +func (r *Request) SetMultipartField(param, fileName, contentType string, reader io.Reader) *Request { + r.isMultiPart = true + r.multipartFields = append(r.multipartFields, &MultipartField{ + Param: param, + FileName: fileName, + ContentType: contentType, + Reader: reader, + }) + return r +} + +// SetMultipartFields method sets multiple data fields using [io.Reader] for multipart upload. +// +// For Example: +// +// client.R().SetMultipartFields( +// &resty.MultipartField{ +// Param: "uploadManifest1", +// FileName: "upload-file-1.json", +// ContentType: "application/json", +// Reader: strings.NewReader(`{"input": {"name": "Uploaded document 1", "_filename" : ["file1.txt"]}}`), +// }, +// &resty.MultipartField{ +// Param: "uploadManifest2", +// FileName: "upload-file-2.json", +// ContentType: "application/json", +// Reader: strings.NewReader(`{"input": {"name": "Uploaded document 2", "_filename" : ["file2.txt"]}}`), +// }) +// +// If you have a `slice` of fields already, then call- +// +// client.R().SetMultipartFields(fields...) +func (r *Request) SetMultipartFields(fields ...*MultipartField) *Request { + r.isMultiPart = true + r.multipartFields = append(r.multipartFields, fields...) + return r +} + +// SetMultipartBoundary method sets the custom multipart boundary for the multipart request. +// Typically, the `mime/multipart` package generates a random multipart boundary if not provided. +func (r *Request) SetMultipartBoundary(boundary string) *Request { + r.multipartBoundary = boundary + return r +} + +// SetContentLength method sets the current request's HTTP header `Content-Length` value. +// By default, Resty won't set `Content-Length`. +// +// See [Client.SetContentLength] +// +// client.R().SetContentLength(true) +// +// It overrides the value set at the client instance level. +func (r *Request) SetContentLength(l bool) *Request { + r.setContentLength = l + return r +} + +// SetBasicAuth method sets the basic authentication header in the current HTTP request. +// +// For Example: +// +// Authorization: Basic +// +// To set the header for username "go-resty" and password "welcome" +// +// client.R().SetBasicAuth("go-resty", "welcome") +// +// It overrides the credentials set by method [Client.SetBasicAuth]. +func (r *Request) SetBasicAuth(username, password string) *Request { + r.UserInfo = &User{Username: username, Password: password} + return r +} + +// SetAuthToken method sets the auth token header(Default Scheme: Bearer) in the current HTTP request. Header example: +// +// Authorization: Bearer +// +// For Example: To set auth token BC594900518B4F7EAC75BD37F019E08FBC594900518B4F7EAC75BD37F019E08F +// +// client.R().SetAuthToken("BC594900518B4F7EAC75BD37F019E08FBC594900518B4F7EAC75BD37F019E08F") +// +// It overrides the Auth token set by method [Client.SetAuthToken]. +func (r *Request) SetAuthToken(token string) *Request { + r.Token = token + return r +} + +// SetAuthScheme method sets the auth token scheme type in the HTTP request. +// +// Example Header value structure: +// +// Authorization: +// +// For Example: To set the scheme to use OAuth +// +// client.R().SetAuthScheme("OAuth") +// +// // The outcome will be - +// Authorization: OAuth +// +// Information about Auth schemes can be found in [RFC 7235], IANA [HTTP Auth schemes] +// +// It overrides the `Authorization` scheme set by method [Client.SetAuthScheme]. +// +// [RFC 7235]: https://tools.ietf.org/html/rfc7235 +// [HTTP Auth schemes]: https://www.iana.org/assignments/http-authschemes/http-authschemes.xhtml#authschemes +func (r *Request) SetAuthScheme(scheme string) *Request { + r.AuthScheme = scheme + return r +} + +// SetDigestAuth method sets the Digest Access auth scheme for the HTTP request. +// If a server responds with 401 and sends a Digest challenge in the WWW-Authenticate Header, +// the request will be resent with the appropriate Authorization Header. +// +// For Example: To set the Digest scheme with username "Mufasa" and password "Circle Of Life" +// +// client.R().SetDigestAuth("Mufasa", "Circle Of Life") +// +// Information about Digest Access Authentication can be found in [RFC 7616] +// +// It overrides the digest username and password set by method [Client.SetDigestAuth]. +// +// [RFC 7616]: https://datatracker.ietf.org/doc/html/rfc7616 +func (r *Request) SetDigestAuth(username, password string) *Request { + oldTransport := r.client.httpClient.Transport + r.client.OnBeforeRequest(func(c *Client, _ *Request) error { + c.httpClient.Transport = &digestTransport{ + digestCredentials: digestCredentials{username, password}, + transport: oldTransport, + } + return nil + }) + r.client.OnAfterResponse(func(c *Client, _ *Response) error { + c.httpClient.Transport = oldTransport + return nil + }) + + return r +} + +// SetOutput method sets the output file for the current HTTP request. The current +// HTTP response will be saved in the given file. It is similar to the `curl -o` flag. +// +// Absolute path or relative path can be used. +// +// If it is a relative path, then the output file goes under the output directory, as mentioned +// in the [Client.SetOutputDirectory]. +// +// client.R(). +// SetOutput("/Users/jeeva/Downloads/ReplyWithHeader-v5.1-beta.zip"). +// Get("http://bit.ly/1LouEKr") +// +// NOTE: In this scenario [Response.Body] might be nil. +func (r *Request) SetOutput(file string) *Request { + r.outputFile = file + r.isSaveResponse = true + return r +} + +// SetSRV method sets the details to query the service SRV record and execute the +// request. +// +// client.R(). +// SetSRV(SRVRecord{"web", "testservice.com"}). +// Get("/get") +func (r *Request) SetSRV(srv *SRVRecord) *Request { + r.SRV = srv + return r +} + +// SetDoNotParseResponse method instructs Resty not to parse the response body automatically. +// Resty exposes the raw response body as [io.ReadCloser]. If you use it, do not +// forget to close the body, otherwise, you might get into connection leaks, and connection +// reuse may not happen. +// +// NOTE: [Response] middlewares are not executed using this option. You have +// taken over the control of response parsing from Resty. +func (r *Request) SetDoNotParseResponse(parse bool) *Request { + r.notParseResponse = parse + return r +} + +// SetResponseBodyLimit method sets a maximum body size limit in bytes on response, +// avoid reading too much data to memory. +// +// Client will return [resty.ErrResponseBodyTooLarge] if the body size of the body +// in the uncompressed response is larger than the limit. +// Body size limit will not be enforced in the following cases: +// - ResponseBodyLimit <= 0, which is the default behavior. +// - [Request.SetOutput] is called to save response data to the file. +// - "DoNotParseResponse" is set for client or request. +// +// It overrides the value set at the client instance level. see [Client.SetResponseBodyLimit] +func (r *Request) SetResponseBodyLimit(v int) *Request { + r.responseBodyLimit = v + return r +} + +// SetPathParam method sets a single URL path key-value pair in the +// Resty current request instance. +// +// client.R().SetPathParam("userId", "sample@sample.com") +// +// Result: +// URL - /v1/users/{userId}/details +// Composed URL - /v1/users/sample@sample.com/details +// +// client.R().SetPathParam("path", "groups/developers") +// +// Result: +// URL - /v1/users/{userId}/details +// Composed URL - /v1/users/groups%2Fdevelopers/details +// +// It replaces the value of the key while composing the request URL. +// The values will be escaped using function [url.PathEscape]. +// +// It overrides the path parameter set at the client instance level. +func (r *Request) SetPathParam(param, value string) *Request { + r.PathParams[param] = value + return r +} + +// SetPathParams method sets multiple URL path key-value pairs at one go in the +// Resty current request instance. +// +// client.R().SetPathParams(map[string]string{ +// "userId": "sample@sample.com", +// "subAccountId": "100002", +// "path": "groups/developers", +// }) +// +// Result: +// URL - /v1/users/{userId}/{subAccountId}/{path}/details +// Composed URL - /v1/users/sample@sample.com/100002/groups%2Fdevelopers/details +// +// It replaces the value of the key while composing the request URL. +// The values will be escaped using function [url.PathEscape]. +// +// It overrides the path parameter set at the client instance level. +func (r *Request) SetPathParams(params map[string]string) *Request { + for p, v := range params { + r.SetPathParam(p, v) + } + return r +} + +// SetRawPathParam method sets a single URL path key-value pair in the +// Resty current request instance. +// +// client.R().SetPathParam("userId", "sample@sample.com") +// +// Result: +// URL - /v1/users/{userId}/details +// Composed URL - /v1/users/sample@sample.com/details +// +// client.R().SetPathParam("path", "groups/developers") +// +// Result: +// URL - /v1/users/{userId}/details +// Composed URL - /v1/users/groups/developers/details +// +// It replaces the value of the key while composing the request URL. +// The value will be used as-is and has not been escaped. +// +// It overrides the raw path parameter set at the client instance level. +func (r *Request) SetRawPathParam(param, value string) *Request { + r.RawPathParams[param] = value + return r +} + +// SetRawPathParams method sets multiple URL path key-value pairs at one go in the +// Resty current request instance. +// +// client.R().SetPathParams(map[string]string{ +// "userId": "sample@sample.com", +// "subAccountId": "100002", +// "path": "groups/developers", +// }) +// +// Result: +// URL - /v1/users/{userId}/{subAccountId}/{path}/details +// Composed URL - /v1/users/sample@sample.com/100002/groups/developers/details +// +// It replaces the value of the key while composing the request URL. +// The value will be used as-is and has not been escaped. +// +// It overrides the raw path parameter set at the client instance level. +func (r *Request) SetRawPathParams(params map[string]string) *Request { + for p, v := range params { + r.SetRawPathParam(p, v) + } + return r +} + +// ExpectContentType method allows to provide fallback `Content-Type` for automatic unmarshalling +// when the `Content-Type` response header is unavailable. +func (r *Request) ExpectContentType(contentType string) *Request { + r.fallbackContentType = contentType + return r +} + +// ForceContentType method provides a strong sense of response `Content-Type` for +// automatic unmarshalling. Resty gives this a higher priority than the `Content-Type` +// response header. +// +// This means that if both [Request.ForceContentType] is set and +// the response `Content-Type` is available, `ForceContentType` will win. +func (r *Request) ForceContentType(contentType string) *Request { + r.forceContentType = contentType + return r +} + +// SetJSONEscapeHTML method enables or disables the HTML escape on JSON marshal. +// By default, escape HTML is false. +// +// NOTE: This option only applies to the standard JSON Marshaller used by Resty. +// +// It overrides the value set at the client instance level, see [Client.SetJSONEscapeHTML] +func (r *Request) SetJSONEscapeHTML(b bool) *Request { + r.jsonEscapeHTML = b + return r +} + +// SetCookie method appends a single cookie in the current request instance. +// +// client.R().SetCookie(&http.Cookie{ +// Name:"go-resty", +// Value:"This is cookie value", +// }) +// +// NOTE: Method appends the Cookie value into existing Cookie even if its already existing. +func (r *Request) SetCookie(hc *http.Cookie) *Request { + r.Cookies = append(r.Cookies, hc) + return r +} + +// SetCookies method sets an array of cookies in the current request instance. +// +// cookies := []*http.Cookie{ +// &http.Cookie{ +// Name:"go-resty-1", +// Value:"This is cookie 1 value", +// }, +// &http.Cookie{ +// Name:"go-resty-2", +// Value:"This is cookie 2 value", +// }, +// } +// +// // Setting a cookies into resty's current request +// client.R().SetCookies(cookies) +// +// NOTE: Method appends the Cookie value into existing Cookie even if its already existing. +func (r *Request) SetCookies(rs []*http.Cookie) *Request { + r.Cookies = append(r.Cookies, rs...) + return r +} + +// SetLogger method sets given writer for logging Resty request and response details. +// By default, requests and responses inherit their logger from the client. +// +// Compliant to interface [resty.Logger]. +// +// It overrides the logger value set at the client instance level. +func (r *Request) SetLogger(l Logger) *Request { + r.log = l + return r +} + +// SetDebug method enables the debug mode on the current request. It logs +// the details current request and response. +// +// client.SetDebug(true) +// +// Also, it can be enabled at the request level for a particular request; see [Request.SetDebug]. +// - For [Request], it logs information such as HTTP verb, Relative URL path, +// Host, Headers, and Body if it has one. +// - For [Response], it logs information such as Status, Response Time, Headers, +// and Body if it has one. +func (r *Request) SetDebug(d bool) *Request { + r.Debug = d + return r +} + +// AddRetryCondition method adds a retry condition function to the request's +// array of functions is checked to determine if the request can be retried. +// The request will retry if any functions return true and the error is nil. +// +// NOTE: The request level retry conditions are checked before all retry +// conditions from the client instance. +func (r *Request) AddRetryCondition(condition RetryConditionFunc) *Request { + r.retryConditions = append(r.retryConditions, condition) + return r +} + +//‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾ +// HTTP request tracing +//_______________________________________________________________________ + +// EnableTrace method enables trace for the current request +// using [httptrace.ClientTrace] and provides insights. +// +// client := resty.New() +// +// resp, err := client.R().EnableTrace().Get("https://httpbin.org/get") +// fmt.Println("Error:", err) +// fmt.Println("Trace Info:", resp.Request.TraceInfo()) +// +// See [Client.EnableTrace] is also available to get trace info for all requests. +func (r *Request) EnableTrace() *Request { + r.trace = true + return r +} + +// EnableGenerateCurlOnDebug method enables the generation of CURL commands in the debug log. +// It works in conjunction with debug mode. It overrides the options set by the [Client]. +// +// NOTE: Use with care. +// - Potential to leak sensitive data from [Request] and [Response] in the debug log. +// - Beware of memory usage since the request body is reread. +func (r *Request) EnableGenerateCurlOnDebug() *Request { + r.generateCurlOnDebug = true + return r +} + +// DisableGenerateCurlOnDebug method disables the option set by [Request.EnableGenerateCurlOnDebug]. +// It overrides the options set by the [Client]. +func (r *Request) DisableGenerateCurlOnDebug() *Request { + r.generateCurlOnDebug = false + return r +} + +// TraceInfo method returns the trace info for the request. +// If either the [Client.EnableTrace] or [Request.EnableTrace] function has not been called +// before the request is made, an empty [resty.TraceInfo] object is returned. +func (r *Request) TraceInfo() TraceInfo { + ct := r.clientTrace + + if ct == nil { + return TraceInfo{} + } + + ti := TraceInfo{ + DNSLookup: ct.dnsDone.Sub(ct.dnsStart), + TLSHandshake: ct.tlsHandshakeDone.Sub(ct.tlsHandshakeStart), + ServerTime: ct.gotFirstResponseByte.Sub(ct.gotConn), + IsConnReused: ct.gotConnInfo.Reused, + IsConnWasIdle: ct.gotConnInfo.WasIdle, + ConnIdleTime: ct.gotConnInfo.IdleTime, + RequestAttempt: r.Attempt, + } + + // Calculate the total time accordingly, + // when connection is reused + if ct.gotConnInfo.Reused { + ti.TotalTime = ct.endTime.Sub(ct.getConn) + } else { + ti.TotalTime = ct.endTime.Sub(ct.dnsStart) + } + + // Only calculate on successful connections + if !ct.connectDone.IsZero() { + ti.TCPConnTime = ct.connectDone.Sub(ct.dnsDone) + } + + // Only calculate on successful connections + if !ct.gotConn.IsZero() { + ti.ConnTime = ct.gotConn.Sub(ct.getConn) + } + + // Only calculate on successful connections + if !ct.gotFirstResponseByte.IsZero() { + ti.ResponseTime = ct.endTime.Sub(ct.gotFirstResponseByte) + } + + // Capture remote address info when connection is non-nil + if ct.gotConnInfo.Conn != nil { + ti.RemoteAddr = ct.gotConnInfo.Conn.RemoteAddr() + } + + return ti +} + +//‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾ +// HTTP verb method starts here +//_______________________________________________________________________ + +// Get method does GET HTTP request. It's defined in section 4.3.1 of RFC7231. +func (r *Request) Get(url string) (*Response, error) { + return r.Execute(MethodGet, url) +} + +// Head method does HEAD HTTP request. It's defined in section 4.3.2 of RFC7231. +func (r *Request) Head(url string) (*Response, error) { + return r.Execute(MethodHead, url) +} + +// Post method does POST HTTP request. It's defined in section 4.3.3 of RFC7231. +func (r *Request) Post(url string) (*Response, error) { + return r.Execute(MethodPost, url) +} + +// Put method does PUT HTTP request. It's defined in section 4.3.4 of RFC7231. +func (r *Request) Put(url string) (*Response, error) { + return r.Execute(MethodPut, url) +} + +// Delete method does DELETE HTTP request. It's defined in section 4.3.5 of RFC7231. +func (r *Request) Delete(url string) (*Response, error) { + return r.Execute(MethodDelete, url) +} + +// Options method does OPTIONS HTTP request. It's defined in section 4.3.7 of RFC7231. +func (r *Request) Options(url string) (*Response, error) { + return r.Execute(MethodOptions, url) +} + +// Patch method does PATCH HTTP request. It's defined in section 2 of RFC5789. +func (r *Request) Patch(url string) (*Response, error) { + return r.Execute(MethodPatch, url) +} + +// Send method performs the HTTP request using the method and URL already defined +// for current [Request]. +// +// req := client.R() +// req.Method = resty.MethodGet +// req.URL = "http://httpbin.org/get" +// resp, err := req.Send() +func (r *Request) Send() (*Response, error) { + return r.Execute(r.Method, r.URL) +} + +// Execute method performs the HTTP request with the given HTTP method and URL +// for current [Request]. +// +// resp, err := client.R().Execute(resty.MethodGet, "http://httpbin.org/get") +func (r *Request) Execute(method, url string) (*Response, error) { + var addrs []*net.SRV + var resp *Response + var err error + + defer func() { + if rec := recover(); rec != nil { + if err, ok := rec.(error); ok { + r.client.onPanicHooks(r, err) + } else { + r.client.onPanicHooks(r, fmt.Errorf("panic %v", rec)) + } + panic(rec) + } + }() + + if r.isMultiPart && !(method == MethodPost || method == MethodPut || method == MethodPatch) { + // No OnError hook here since this is a request validation error + err := fmt.Errorf("multipart content is not allowed in HTTP verb [%v]", method) + r.client.onInvalidHooks(r, err) + return nil, err + } + + if r.SRV != nil { + _, addrs, err = net.LookupSRV(r.SRV.Service, "tcp", r.SRV.Domain) + if err != nil { + r.client.onErrorHooks(r, nil, err) + return nil, err + } + } + + r.Method = method + r.URL = r.selectAddr(addrs, url, 0) + + if r.client.RetryCount == 0 { + r.Attempt = 1 + resp, err = r.client.execute(r) + r.client.onErrorHooks(r, resp, unwrapNoRetryErr(err)) + backToBufPool(r.bodyBuf) + return resp, unwrapNoRetryErr(err) + } + + err = Backoff( + func() (*Response, error) { + r.Attempt++ + + r.URL = r.selectAddr(addrs, url, r.Attempt) + + resp, err = r.client.execute(r) + if err != nil { + r.log.Warnf("%v, Attempt %v", err, r.Attempt) + } + + return resp, err + }, + Retries(r.client.RetryCount), + WaitTime(r.client.RetryWaitTime), + MaxWaitTime(r.client.RetryMaxWaitTime), + RetryConditions(append(r.retryConditions, r.client.RetryConditions...)), + RetryHooks(r.client.RetryHooks), + ResetMultipartReaders(r.client.RetryResetReaders), + ) + + if err != nil { + r.log.Errorf("%v", err) + } + + r.client.onErrorHooks(r, resp, unwrapNoRetryErr(err)) + backToBufPool(r.bodyBuf) + return resp, unwrapNoRetryErr(err) +} + +//‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾ +// SRVRecord struct +//_______________________________________________________________________ + +// SRVRecord struct holds the data to query the SRV record for the +// following service. +type SRVRecord struct { + Service string + Domain string +} + +func (r *Request) fmtBodyString(sl int64) (body string) { + body = "***** NO CONTENT *****" + if !isPayloadSupported(r.Method, r.client.AllowGetMethodPayload) { + return + } + + if _, ok := r.Body.(io.Reader); ok { + body = "***** BODY IS io.Reader *****" + return + } + + // multipart or form-data + if r.isMultiPart || r.isFormData { + bodySize := int64(r.bodyBuf.Len()) + if bodySize > sl { + body = fmt.Sprintf("***** REQUEST TOO LARGE (size - %d) *****", bodySize) + return + } + body = r.bodyBuf.String() + return + } + + // request body data + if r.Body == nil { + return + } + var prtBodyBytes []byte + var err error + + contentType := r.Header.Get(hdrContentTypeKey) + kind := kindOf(r.Body) + if canJSONMarshal(contentType, kind) { + var bodyBuf *bytes.Buffer + bodyBuf, err = noescapeJSONMarshalIndent(&r.Body) + if err == nil { + prtBodyBytes = bodyBuf.Bytes() + defer releaseBuffer(bodyBuf) + } + } else if IsXMLType(contentType) && (kind == reflect.Struct) { + prtBodyBytes, err = xml.MarshalIndent(&r.Body, "", " ") + } else if b, ok := r.Body.(string); ok { + if IsJSONType(contentType) { + bodyBytes := []byte(b) + out := acquireBuffer() + defer releaseBuffer(out) + if err = json.Indent(out, bodyBytes, "", " "); err == nil { + prtBodyBytes = out.Bytes() + } + } else { + body = b + } + } else if b, ok := r.Body.([]byte); ok { + body = fmt.Sprintf("***** BODY IS byte(s) (size - %d) *****", len(b)) + return + } + + if prtBodyBytes != nil && err == nil { + body = string(prtBodyBytes) + } + + if len(body) > 0 { + bodySize := int64(len([]byte(body))) + if bodySize > sl { + body = fmt.Sprintf("***** REQUEST TOO LARGE (size - %d) *****", bodySize) + } + } + + return +} + +func (r *Request) selectAddr(addrs []*net.SRV, path string, attempt int) string { + if addrs == nil { + return path + } + + idx := attempt % len(addrs) + domain := strings.TrimRight(addrs[idx].Target, ".") + path = strings.TrimLeft(path, "/") + + return fmt.Sprintf("%s://%s:%d/%s", r.client.scheme, domain, addrs[idx].Port, path) +} + +func (r *Request) initValuesMap() { + if r.values == nil { + r.values = make(map[string]interface{}) + } +} + +var noescapeJSONMarshal = func(v interface{}) (*bytes.Buffer, error) { + buf := acquireBuffer() + encoder := json.NewEncoder(buf) + encoder.SetEscapeHTML(false) + if err := encoder.Encode(v); err != nil { + releaseBuffer(buf) + return nil, err + } + + return buf, nil +} + +var noescapeJSONMarshalIndent = func(v interface{}) (*bytes.Buffer, error) { + buf := acquireBuffer() + encoder := json.NewEncoder(buf) + encoder.SetEscapeHTML(false) + encoder.SetIndent("", " ") + + if err := encoder.Encode(v); err != nil { + releaseBuffer(buf) + return nil, err + } + + return buf, nil +} diff --git a/vendor/github.com/go-resty/resty/v2/response.go b/vendor/github.com/go-resty/resty/v2/response.go new file mode 100644 index 000000000..f52b5c61f --- /dev/null +++ b/vendor/github.com/go-resty/resty/v2/response.go @@ -0,0 +1,195 @@ +// Copyright (c) 2015-2024 Jeevanandam M (jeeva@myjeeva.com), All rights reserved. +// resty source code and usage is governed by a MIT style +// license that can be found in the LICENSE file. + +package resty + +import ( + "encoding/json" + "fmt" + "io" + "net/http" + "strings" + "time" +) + +//‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾ +// Response struct and methods +//_______________________________________________________________________ + +// Response struct holds response values of executed requests. +type Response struct { + Request *Request + RawResponse *http.Response + + body []byte + size int64 + receivedAt time.Time +} + +// Body method returns the HTTP response as `[]byte` slice for the executed request. +// +// NOTE: [Response.Body] might be nil if [Request.SetOutput] is used. +// Also see [Request.SetDoNotParseResponse], [Client.SetDoNotParseResponse] +func (r *Response) Body() []byte { + if r.RawResponse == nil { + return []byte{} + } + return r.body +} + +// SetBody method sets [Response] body in byte slice. Typically, +// It is helpful for test cases. +// +// resp.SetBody([]byte("This is test body content")) +// resp.SetBody(nil) +func (r *Response) SetBody(b []byte) *Response { + r.body = b + return r +} + +// Status method returns the HTTP status string for the executed request. +// +// Example: 200 OK +func (r *Response) Status() string { + if r.RawResponse == nil { + return "" + } + return r.RawResponse.Status +} + +// StatusCode method returns the HTTP status code for the executed request. +// +// Example: 200 +func (r *Response) StatusCode() int { + if r.RawResponse == nil { + return 0 + } + return r.RawResponse.StatusCode +} + +// Proto method returns the HTTP response protocol used for the request. +func (r *Response) Proto() string { + if r.RawResponse == nil { + return "" + } + return r.RawResponse.Proto +} + +// Result method returns the response value as an object if it has one +// +// See [Request.SetResult] +func (r *Response) Result() interface{} { + return r.Request.Result +} + +// Error method returns the error object if it has one +// +// See [Request.SetError], [Client.SetError] +func (r *Response) Error() interface{} { + return r.Request.Error +} + +// Header method returns the response headers +func (r *Response) Header() http.Header { + if r.RawResponse == nil { + return http.Header{} + } + return r.RawResponse.Header +} + +// Cookies method to returns all the response cookies +func (r *Response) Cookies() []*http.Cookie { + if r.RawResponse == nil { + return make([]*http.Cookie, 0) + } + return r.RawResponse.Cookies() +} + +// String method returns the body of the HTTP response as a `string`. +// It returns an empty string if it is nil or the body is zero length. +func (r *Response) String() string { + if len(r.body) == 0 { + return "" + } + return strings.TrimSpace(string(r.body)) +} + +// Time method returns the duration of HTTP response time from the request we sent +// and received a request. +// +// See [Response.ReceivedAt] to know when the client received a response and see +// `Response.Request.Time` to know when the client sent a request. +func (r *Response) Time() time.Duration { + if r.Request.clientTrace != nil { + return r.Request.TraceInfo().TotalTime + } + return r.receivedAt.Sub(r.Request.Time) +} + +// ReceivedAt method returns the time we received a response from the server for the request. +func (r *Response) ReceivedAt() time.Time { + return r.receivedAt +} + +// Size method returns the HTTP response size in bytes. Yeah, you can rely on HTTP `Content-Length` +// header, however it won't be available for chucked transfer/compressed response. +// Since Resty captures response size details when processing the response body +// when possible. So that users get the actual size of response bytes. +func (r *Response) Size() int64 { + return r.size +} + +// RawBody method exposes the HTTP raw response body. Use this method in conjunction with +// [Client.SetDoNotParseResponse] or [Request.SetDoNotParseResponse] +// option; otherwise, you get an error as `read err: http: read on closed response body.` +// +// Do not forget to close the body, otherwise you might get into connection leaks, no connection reuse. +// You have taken over the control of response parsing from Resty. +func (r *Response) RawBody() io.ReadCloser { + if r.RawResponse == nil { + return nil + } + return r.RawResponse.Body +} + +// IsSuccess method returns true if HTTP status `code >= 200 and <= 299` otherwise false. +func (r *Response) IsSuccess() bool { + return r.StatusCode() > 199 && r.StatusCode() < 300 +} + +// IsError method returns true if HTTP status `code >= 400` otherwise false. +func (r *Response) IsError() bool { + return r.StatusCode() > 399 +} + +func (r *Response) setReceivedAt() { + r.receivedAt = time.Now() + if r.Request.clientTrace != nil { + r.Request.clientTrace.endTime = r.receivedAt + } +} + +func (r *Response) fmtBodyString(sl int64) string { + if r.Request.client.notParseResponse || r.Request.notParseResponse { + return "***** DO NOT PARSE RESPONSE - Enabled *****" + } + if len(r.body) > 0 { + if int64(len(r.body)) > sl { + return fmt.Sprintf("***** RESPONSE TOO LARGE (size - %d) *****", len(r.body)) + } + ct := r.Header().Get(hdrContentTypeKey) + if IsJSONType(ct) { + out := acquireBuffer() + defer releaseBuffer(out) + err := json.Indent(out, r.body, "", " ") + if err != nil { + return fmt.Sprintf("*** Error: Unable to format response body - \"%s\" ***\n\nLog Body as-is:\n%s", err, r.String()) + } + return out.String() + } + return r.String() + } + + return "***** NO CONTENT *****" +} diff --git a/vendor/github.com/go-resty/resty/v2/resty.go b/vendor/github.com/go-resty/resty/v2/resty.go new file mode 100644 index 000000000..4a191961a --- /dev/null +++ b/vendor/github.com/go-resty/resty/v2/resty.go @@ -0,0 +1,40 @@ +// Copyright (c) 2015-2024 Jeevanandam M (jeeva@myjeeva.com), All rights reserved. +// resty source code and usage is governed by a MIT style +// license that can be found in the LICENSE file. + +// Package resty provides Simple HTTP and REST client library for Go. +package resty + +import ( + "net" + "net/http" + "net/http/cookiejar" + + "golang.org/x/net/publicsuffix" +) + +// Version # of resty +const Version = "2.16.5" + +// New method creates a new Resty client. +func New() *Client { + cookieJar, _ := cookiejar.New(&cookiejar.Options{PublicSuffixList: publicsuffix.List}) + return createClient(&http.Client{ + Jar: cookieJar, + }) +} + +// NewWithClient method creates a new Resty client with given [http.Client]. +func NewWithClient(hc *http.Client) *Client { + return createClient(hc) +} + +// NewWithLocalAddr method creates a new Resty client with the given Local Address. +// to dial from. +func NewWithLocalAddr(localAddr net.Addr) *Client { + cookieJar, _ := cookiejar.New(&cookiejar.Options{PublicSuffixList: publicsuffix.List}) + return createClient(&http.Client{ + Jar: cookieJar, + Transport: createTransport(localAddr), + }) +} diff --git a/vendor/github.com/go-resty/resty/v2/retry.go b/vendor/github.com/go-resty/resty/v2/retry.go new file mode 100644 index 000000000..f3198a528 --- /dev/null +++ b/vendor/github.com/go-resty/resty/v2/retry.go @@ -0,0 +1,267 @@ +// Copyright (c) 2015-2024 Jeevanandam M (jeeva@myjeeva.com), All rights reserved. +// resty source code and usage is governed by a MIT style +// license that can be found in the LICENSE file. + +package resty + +import ( + "context" + "io" + "math" + "math/rand" + "sync" + "time" +) + +const ( + defaultMaxRetries = 3 + defaultWaitTime = time.Duration(100) * time.Millisecond + defaultMaxWaitTime = time.Duration(2000) * time.Millisecond +) + +type ( + // Option is to create convenient retry options like wait time, max retries, etc. + Option func(*Options) + + // RetryConditionFunc type is for the retry condition function + // input: non-nil Response OR request execution error + RetryConditionFunc func(*Response, error) bool + + // OnRetryFunc is for side-effecting functions triggered on retry + OnRetryFunc func(*Response, error) + + // RetryAfterFunc returns time to wait before retry + // For example, it can parse HTTP Retry-After header + // https://www.w3.org/Protocols/rfc2616/rfc2616-sec14.html + // Non-nil error is returned if it is found that the request is not retryable + // (0, nil) is a special result that means 'use default algorithm' + RetryAfterFunc func(*Client, *Response) (time.Duration, error) + + // Options struct is used to hold retry settings. + Options struct { + maxRetries int + waitTime time.Duration + maxWaitTime time.Duration + retryConditions []RetryConditionFunc + retryHooks []OnRetryFunc + resetReaders bool + } +) + +// Retries sets the max number of retries +func Retries(value int) Option { + return func(o *Options) { + o.maxRetries = value + } +} + +// WaitTime sets the default wait time to sleep between requests +func WaitTime(value time.Duration) Option { + return func(o *Options) { + o.waitTime = value + } +} + +// MaxWaitTime sets the max wait time to sleep between requests +func MaxWaitTime(value time.Duration) Option { + return func(o *Options) { + o.maxWaitTime = value + } +} + +// RetryConditions sets the conditions that will be checked for retry +func RetryConditions(conditions []RetryConditionFunc) Option { + return func(o *Options) { + o.retryConditions = conditions + } +} + +// RetryHooks sets the hooks that will be executed after each retry +func RetryHooks(hooks []OnRetryFunc) Option { + return func(o *Options) { + o.retryHooks = hooks + } +} + +// ResetMultipartReaders sets a boolean value which will lead the start being seeked out +// on all multipart file readers if they implement [io.ReadSeeker] +func ResetMultipartReaders(value bool) Option { + return func(o *Options) { + o.resetReaders = value + } +} + +// Backoff retries with increasing timeout duration up until X amount of retries +// (Default is 3 attempts, Override with option Retries(n)) +func Backoff(operation func() (*Response, error), options ...Option) error { + // Defaults + opts := Options{ + maxRetries: defaultMaxRetries, + waitTime: defaultWaitTime, + maxWaitTime: defaultMaxWaitTime, + retryConditions: []RetryConditionFunc{}, + } + + for _, o := range options { + o(&opts) + } + + var ( + resp *Response + err error + ) + + for attempt := 0; attempt <= opts.maxRetries; attempt++ { + resp, err = operation() + ctx := context.Background() + if resp != nil && resp.Request.ctx != nil { + ctx = resp.Request.ctx + } + if ctx.Err() != nil { + return err + } + + err1 := unwrapNoRetryErr(err) // raw error, it used for return users callback. + needsRetry := err != nil && err == err1 // retry on a few operation errors by default + + for _, condition := range opts.retryConditions { + needsRetry = condition(resp, err1) + if needsRetry { + break + } + } + + if !needsRetry { + return err + } + + if opts.resetReaders { + if err := resetFileReaders(resp.Request.multipartFiles); err != nil { + return err + } + if err := resetFieldReaders(resp.Request.multipartFields); err != nil { + return err + } + } + + for _, hook := range opts.retryHooks { + hook(resp, err) + } + + // Don't need to wait when no retries left. + // Still run retry hooks even on last retry to keep compatibility. + if attempt == opts.maxRetries { + return err + } + + waitTime, err2 := sleepDuration(resp, opts.waitTime, opts.maxWaitTime, attempt) + if err2 != nil { + if err == nil { + err = err2 + } + return err + } + + select { + case <-time.After(waitTime): + case <-ctx.Done(): + return ctx.Err() + } + } + + return err +} + +func sleepDuration(resp *Response, min, max time.Duration, attempt int) (time.Duration, error) { + const maxInt = 1<<31 - 1 // max int for arch 386 + if max < 0 { + max = maxInt + } + if resp == nil { + return jitterBackoff(min, max, attempt), nil + } + + retryAfterFunc := resp.Request.client.RetryAfter + + // Check for custom callback + if retryAfterFunc == nil { + return jitterBackoff(min, max, attempt), nil + } + + result, err := retryAfterFunc(resp.Request.client, resp) + if err != nil { + return 0, err // i.e. 'API quota exceeded' + } + if result == 0 { + return jitterBackoff(min, max, attempt), nil + } + if result < 0 || max < result { + result = max + } + if result < min { + result = min + } + return result, nil +} + +// Return capped exponential backoff with jitter +// https://aws.amazon.com/blogs/architecture/exponential-backoff-and-jitter/ +func jitterBackoff(min, max time.Duration, attempt int) time.Duration { + base := float64(min) + capLevel := float64(max) + + temp := math.Min(capLevel, base*math.Exp2(float64(attempt))) + ri := time.Duration(temp / 2) + if ri == 0 { + ri = time.Nanosecond + } + result := randDuration(ri) + + if result < min { + result = min + } + + return result +} + +var rnd = newRnd() +var rndMu sync.Mutex + +func randDuration(center time.Duration) time.Duration { + rndMu.Lock() + defer rndMu.Unlock() + + var ri = int64(center) + var jitter = rnd.Int63n(ri) + return time.Duration(math.Abs(float64(ri + jitter))) +} + +func newRnd() *rand.Rand { + var seed = time.Now().UnixNano() + var src = rand.NewSource(seed) + return rand.New(src) +} + +func resetFileReaders(files []*File) error { + for _, f := range files { + if rs, ok := f.Reader.(io.ReadSeeker); ok { + if _, err := rs.Seek(0, io.SeekStart); err != nil { + return err + } + } + } + + return nil +} + +func resetFieldReaders(fields []*MultipartField) error { + for _, f := range fields { + if rs, ok := f.Reader.(io.ReadSeeker); ok { + if _, err := rs.Seek(0, io.SeekStart); err != nil { + return err + } + } + } + + return nil +} diff --git a/vendor/github.com/go-resty/resty/v2/shellescape/BUILD.bazel b/vendor/github.com/go-resty/resty/v2/shellescape/BUILD.bazel new file mode 100644 index 000000000..fe829e397 --- /dev/null +++ b/vendor/github.com/go-resty/resty/v2/shellescape/BUILD.bazel @@ -0,0 +1,14 @@ +load("@io_bazel_rules_go//go:def.bzl", "go_library") + +go_library( + name = "shellescape", + srcs = ["shellescape.go"], + importpath = "github.com/go-resty/resty/v2/shellescape", + visibility = ["//visibility:public"], +) + +alias( + name = "go_default_library", + actual = ":shellescape", + visibility = ["//visibility:public"], +) diff --git a/vendor/github.com/go-resty/resty/v2/shellescape/shellescape.go b/vendor/github.com/go-resty/resty/v2/shellescape/shellescape.go new file mode 100644 index 000000000..1e835cb3b --- /dev/null +++ b/vendor/github.com/go-resty/resty/v2/shellescape/shellescape.go @@ -0,0 +1,40 @@ +// Copyright (c) 2015-present Jeevanandam M (jeeva@myjeeva.com) +// 2024 Ahuigo (https://github.com/ahuigo) +// All rights reserved. +// resty source code and usage is governed by a MIT style +// license that can be found in the LICENSE file. + +/* +Package shellescape provides the methods to escape arbitrary +strings for a safe use as command line arguments in the most common +POSIX shells. + +The original Python package which this work was inspired by can be found +at https://pypi.python.org/pypi/shellescape. +*/ +package shellescape + +import ( + "regexp" + "strings" +) + +var pattern *regexp.Regexp + +func init() { + pattern = regexp.MustCompile(`[^\w@%+=:,./-]`) +} + +// Quote method returns a shell-escaped version of the string. The returned value +// can safely be used as one token in a shell command line. +func Quote(s string) string { + if len(s) == 0 { + return "''" + } + + if pattern.MatchString(s) { + return "'" + strings.ReplaceAll(s, "'", "'\"'\"'") + "'" + } + + return s +} diff --git a/vendor/github.com/go-resty/resty/v2/trace.go b/vendor/github.com/go-resty/resty/v2/trace.go new file mode 100644 index 000000000..226bfbffc --- /dev/null +++ b/vendor/github.com/go-resty/resty/v2/trace.go @@ -0,0 +1,124 @@ +// Copyright (c) 2015-2024 Jeevanandam M (jeeva@myjeeva.com), All rights reserved. +// resty source code and usage is governed by a MIT style +// license that can be found in the LICENSE file. + +package resty + +import ( + "context" + "crypto/tls" + "net" + "net/http/httptrace" + "time" +) + +//‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾ +// TraceInfo struct +//_______________________________________________________________________ + +// TraceInfo struct is used to provide request trace info such as DNS lookup +// duration, Connection obtain duration, Server processing duration, etc. +type TraceInfo struct { + // DNSLookup is the duration that transport took to perform + // DNS lookup. + DNSLookup time.Duration + + // ConnTime is the duration it took to obtain a successful connection. + ConnTime time.Duration + + // TCPConnTime is the duration it took to obtain the TCP connection. + TCPConnTime time.Duration + + // TLSHandshake is the duration of the TLS handshake. + TLSHandshake time.Duration + + // ServerTime is the server's duration for responding to the first byte. + ServerTime time.Duration + + // ResponseTime is the duration since the first response byte from the server to + // request completion. + ResponseTime time.Duration + + // TotalTime is the duration of the total time request taken end-to-end. + TotalTime time.Duration + + // IsConnReused is whether this connection has been previously + // used for another HTTP request. + IsConnReused bool + + // IsConnWasIdle is whether this connection was obtained from an + // idle pool. + IsConnWasIdle bool + + // ConnIdleTime is the duration how long the connection that was previously + // idle, if IsConnWasIdle is true. + ConnIdleTime time.Duration + + // RequestAttempt is to represent the request attempt made during a Resty + // request execution flow, including retry count. + RequestAttempt int + + // RemoteAddr returns the remote network address. + RemoteAddr net.Addr +} + +//‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾ +// ClientTrace struct and its methods +//_______________________________________________________________________ + +// clientTrace struct maps the [httptrace.ClientTrace] hooks into Fields +// with the same naming for easy understanding. Plus additional insights +// [Request]. +type clientTrace struct { + getConn time.Time + dnsStart time.Time + dnsDone time.Time + connectDone time.Time + tlsHandshakeStart time.Time + tlsHandshakeDone time.Time + gotConn time.Time + gotFirstResponseByte time.Time + endTime time.Time + gotConnInfo httptrace.GotConnInfo +} + +func (t *clientTrace) createContext(ctx context.Context) context.Context { + return httptrace.WithClientTrace( + ctx, + &httptrace.ClientTrace{ + DNSStart: func(_ httptrace.DNSStartInfo) { + t.dnsStart = time.Now() + }, + DNSDone: func(_ httptrace.DNSDoneInfo) { + t.dnsDone = time.Now() + }, + ConnectStart: func(_, _ string) { + if t.dnsDone.IsZero() { + t.dnsDone = time.Now() + } + if t.dnsStart.IsZero() { + t.dnsStart = t.dnsDone + } + }, + ConnectDone: func(net, addr string, err error) { + t.connectDone = time.Now() + }, + GetConn: func(_ string) { + t.getConn = time.Now() + }, + GotConn: func(ci httptrace.GotConnInfo) { + t.gotConn = time.Now() + t.gotConnInfo = ci + }, + GotFirstResponseByte: func() { + t.gotFirstResponseByte = time.Now() + }, + TLSHandshakeStart: func() { + t.tlsHandshakeStart = time.Now() + }, + TLSHandshakeDone: func(_ tls.ConnectionState, _ error) { + t.tlsHandshakeDone = time.Now() + }, + }, + ) +} diff --git a/vendor/github.com/go-resty/resty/v2/transport.go b/vendor/github.com/go-resty/resty/v2/transport.go new file mode 100644 index 000000000..13c3de343 --- /dev/null +++ b/vendor/github.com/go-resty/resty/v2/transport.go @@ -0,0 +1,36 @@ +//go:build go1.13 +// +build go1.13 + +// Copyright (c) 2015-2024 Jeevanandam M (jeeva@myjeeva.com), All rights reserved. +// resty source code and usage is governed by a MIT style +// license that can be found in the LICENSE file. + +package resty + +import ( + "net" + "net/http" + "runtime" + "time" +) + +func createTransport(localAddr net.Addr) *http.Transport { + dialer := &net.Dialer{ + Timeout: 30 * time.Second, + KeepAlive: 30 * time.Second, + DualStack: true, + } + if localAddr != nil { + dialer.LocalAddr = localAddr + } + return &http.Transport{ + Proxy: http.ProxyFromEnvironment, + DialContext: transportDialContext(dialer), + ForceAttemptHTTP2: true, + MaxIdleConns: 100, + IdleConnTimeout: 90 * time.Second, + TLSHandshakeTimeout: 10 * time.Second, + ExpectContinueTimeout: 1 * time.Second, + MaxIdleConnsPerHost: runtime.GOMAXPROCS(0) + 1, + } +} diff --git a/vendor/github.com/go-resty/resty/v2/transport112.go b/vendor/github.com/go-resty/resty/v2/transport112.go new file mode 100644 index 000000000..beb0301a2 --- /dev/null +++ b/vendor/github.com/go-resty/resty/v2/transport112.go @@ -0,0 +1,35 @@ +//go:build !go1.13 +// +build !go1.13 + +// Copyright (c) 2015-2024 Jeevanandam M (jeeva@myjeeva.com), All rights reserved. +// resty source code and usage is governed by a MIT style +// license that can be found in the LICENSE file. + +package resty + +import ( + "net" + "net/http" + "runtime" + "time" +) + +func createTransport(localAddr net.Addr) *http.Transport { + dialer := &net.Dialer{ + Timeout: 30 * time.Second, + KeepAlive: 30 * time.Second, + DualStack: true, + } + if localAddr != nil { + dialer.LocalAddr = localAddr + } + return &http.Transport{ + Proxy: http.ProxyFromEnvironment, + DialContext: dialer.DialContext, + MaxIdleConns: 100, + IdleConnTimeout: 90 * time.Second, + TLSHandshakeTimeout: 10 * time.Second, + ExpectContinueTimeout: 1 * time.Second, + MaxIdleConnsPerHost: runtime.GOMAXPROCS(0) + 1, + } +} diff --git a/vendor/github.com/go-resty/resty/v2/transport_js.go b/vendor/github.com/go-resty/resty/v2/transport_js.go new file mode 100644 index 000000000..6227aa9ca --- /dev/null +++ b/vendor/github.com/go-resty/resty/v2/transport_js.go @@ -0,0 +1,17 @@ +// Copyright 2021 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +//go:build js && wasm +// +build js,wasm + +package resty + +import ( + "context" + "net" +) + +func transportDialContext(dialer *net.Dialer) func(context.Context, string, string) (net.Conn, error) { + return nil +} diff --git a/vendor/github.com/go-resty/resty/v2/transport_other.go b/vendor/github.com/go-resty/resty/v2/transport_other.go new file mode 100644 index 000000000..73553c36f --- /dev/null +++ b/vendor/github.com/go-resty/resty/v2/transport_other.go @@ -0,0 +1,17 @@ +// Copyright 2021 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +//go:build !(js && wasm) +// +build !js !wasm + +package resty + +import ( + "context" + "net" +) + +func transportDialContext(dialer *net.Dialer) func(context.Context, string, string) (net.Conn, error) { + return dialer.DialContext +} diff --git a/vendor/github.com/go-resty/resty/v2/util.go b/vendor/github.com/go-resty/resty/v2/util.go new file mode 100644 index 000000000..0ac470e43 --- /dev/null +++ b/vendor/github.com/go-resty/resty/v2/util.go @@ -0,0 +1,389 @@ +// Copyright (c) 2015-2024 Jeevanandam M (jeeva@myjeeva.com), All rights reserved. +// resty source code and usage is governed by a MIT style +// license that can be found in the LICENSE file. + +package resty + +import ( + "bytes" + "errors" + "fmt" + "io" + "log" + "mime/multipart" + "net/http" + "net/textproto" + "os" + "path/filepath" + "reflect" + "runtime" + "sort" + "strings" +) + +//‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾ +// Logger interface +//_______________________________________________________________________ + +// Logger interface is to abstract the logging from Resty. Gives control to +// the Resty users, choice of the logger. +type Logger interface { + Errorf(format string, v ...interface{}) + Warnf(format string, v ...interface{}) + Debugf(format string, v ...interface{}) +} + +func createLogger() *logger { + l := &logger{l: log.New(os.Stderr, "", log.Ldate|log.Lmicroseconds)} + return l +} + +var _ Logger = (*logger)(nil) + +type logger struct { + l *log.Logger +} + +func (l *logger) Errorf(format string, v ...interface{}) { + l.output("ERROR RESTY "+format, v...) +} + +func (l *logger) Warnf(format string, v ...interface{}) { + l.output("WARN RESTY "+format, v...) +} + +func (l *logger) Debugf(format string, v ...interface{}) { + l.output("DEBUG RESTY "+format, v...) +} + +func (l *logger) output(format string, v ...interface{}) { + if len(v) == 0 { + l.l.Print(format) + return + } + l.l.Printf(format, v...) +} + +//‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾ +// Rate Limiter interface +//_______________________________________________________________________ + +type RateLimiter interface { + Allow() bool +} + +var ErrRateLimitExceeded = errors.New("rate limit exceeded") + +//‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾ +// Package Helper methods +//_______________________________________________________________________ + +// IsStringEmpty method tells whether given string is empty or not +func IsStringEmpty(str string) bool { + return len(strings.TrimSpace(str)) == 0 +} + +// DetectContentType method is used to figure out `Request.Body` content type for request header +func DetectContentType(body interface{}) string { + contentType := plainTextType + kind := kindOf(body) + switch kind { + case reflect.Struct, reflect.Map: + contentType = jsonContentType + case reflect.String: + contentType = plainTextType + default: + if b, ok := body.([]byte); ok { + contentType = http.DetectContentType(b) + } else if kind == reflect.Slice { + contentType = jsonContentType + } + } + + return contentType +} + +// IsJSONType method is to check JSON content type or not +func IsJSONType(ct string) bool { + return jsonCheck.MatchString(ct) +} + +// IsXMLType method is to check XML content type or not +func IsXMLType(ct string) bool { + return xmlCheck.MatchString(ct) +} + +// Unmarshalc content into object from JSON or XML +func Unmarshalc(c *Client, ct string, b []byte, d interface{}) (err error) { + if IsJSONType(ct) { + err = c.JSONUnmarshal(b, d) + } else if IsXMLType(ct) { + err = c.XMLUnmarshal(b, d) + } + + return +} + +//‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾ +// RequestLog and ResponseLog type +//_______________________________________________________________________ + +// RequestLog struct is used to collected information from resty request +// instance for debug logging. It sent to request log callback before resty +// actually logs the information. +type RequestLog struct { + Header http.Header + Body string +} + +// ResponseLog struct is used to collected information from resty response +// instance for debug logging. It sent to response log callback before resty +// actually logs the information. +type ResponseLog struct { + Header http.Header + Body string +} + +// way to disable the HTML escape as opt-in +func jsonMarshal(c *Client, r *Request, d interface{}) (*bytes.Buffer, error) { + if !r.jsonEscapeHTML || !c.jsonEscapeHTML { + return noescapeJSONMarshal(d) + } + + data, err := c.JSONMarshal(d) + if err != nil { + return nil, err + } + + buf := acquireBuffer() + _, _ = buf.Write(data) + return buf, nil +} + +func firstNonEmpty(v ...string) string { + for _, s := range v { + if !IsStringEmpty(s) { + return s + } + } + return "" +} + +var quoteEscaper = strings.NewReplacer("\\", "\\\\", `"`, "\\\"") + +func escapeQuotes(s string) string { + return quoteEscaper.Replace(s) +} + +func createMultipartHeader(param, fileName, contentType string) textproto.MIMEHeader { + hdr := make(textproto.MIMEHeader) + + var contentDispositionValue string + if IsStringEmpty(fileName) { + contentDispositionValue = fmt.Sprintf(`form-data; name="%s"`, param) + } else { + contentDispositionValue = fmt.Sprintf(`form-data; name="%s"; filename="%s"`, + param, escapeQuotes(fileName)) + } + hdr.Set("Content-Disposition", contentDispositionValue) + + if !IsStringEmpty(contentType) { + hdr.Set(hdrContentTypeKey, contentType) + } + return hdr +} + +func addMultipartFormField(w *multipart.Writer, mf *MultipartField) error { + partWriter, err := w.CreatePart(createMultipartHeader(mf.Param, mf.FileName, mf.ContentType)) + if err != nil { + return err + } + + _, err = io.Copy(partWriter, mf.Reader) + return err +} + +func writeMultipartFormFile(w *multipart.Writer, fieldName, fileName string, r io.Reader) error { + // Auto detect actual multipart content type + cbuf := make([]byte, 512) + size, err := r.Read(cbuf) + if err != nil && err != io.EOF { + return err + } + + partWriter, err := w.CreatePart(createMultipartHeader(fieldName, fileName, http.DetectContentType(cbuf[:size]))) + if err != nil { + return err + } + + if _, err = partWriter.Write(cbuf[:size]); err != nil { + return err + } + + _, err = io.Copy(partWriter, r) + return err +} + +func addFile(w *multipart.Writer, fieldName, path string) error { + file, err := os.Open(path) + if err != nil { + return err + } + defer closeq(file) + return writeMultipartFormFile(w, fieldName, filepath.Base(path), file) +} + +func addFileReader(w *multipart.Writer, f *File) error { + return writeMultipartFormFile(w, f.ParamName, f.Name, f.Reader) +} + +func getPointer(v interface{}) interface{} { + vv := valueOf(v) + if vv.Kind() == reflect.Ptr { + return v + } + return reflect.New(vv.Type()).Interface() +} + +func isPayloadSupported(m string, allowMethodGet bool) bool { + return !(m == MethodHead || m == MethodOptions || (m == MethodGet && !allowMethodGet)) +} + +func typeOf(i interface{}) reflect.Type { + return indirect(valueOf(i)).Type() +} + +func valueOf(i interface{}) reflect.Value { + return reflect.ValueOf(i) +} + +func indirect(v reflect.Value) reflect.Value { + return reflect.Indirect(v) +} + +func kindOf(v interface{}) reflect.Kind { + return typeOf(v).Kind() +} + +func createDirectory(dir string) (err error) { + if _, err = os.Stat(dir); err != nil { + if os.IsNotExist(err) { + if err = os.MkdirAll(dir, 0755); err != nil { + return + } + } + } + return +} + +func canJSONMarshal(contentType string, kind reflect.Kind) bool { + return IsJSONType(contentType) && (kind == reflect.Struct || kind == reflect.Map || kind == reflect.Slice) +} + +func functionName(i interface{}) string { + return runtime.FuncForPC(reflect.ValueOf(i).Pointer()).Name() +} + +func acquireBuffer() *bytes.Buffer { + buf := bufPool.Get().(*bytes.Buffer) + if buf.Len() == 0 { + buf.Reset() + return buf + } + bufPool.Put(buf) + return new(bytes.Buffer) +} + +func releaseBuffer(buf *bytes.Buffer) { + if buf != nil { + buf.Reset() + bufPool.Put(buf) + } +} + +func backToBufPool(buf *bytes.Buffer) { + if buf != nil { + bufPool.Put(buf) + } +} + +func closeq(v interface{}) { + if c, ok := v.(io.Closer); ok { + silently(c.Close()) + } +} + +func silently(_ ...interface{}) {} + +func composeHeaders(c *Client, r *Request, hdrs http.Header) string { + str := make([]string, 0, len(hdrs)) + for _, k := range sortHeaderKeys(hdrs) { + str = append(str, "\t"+strings.TrimSpace(fmt.Sprintf("%25s: %s", k, strings.Join(hdrs[k], ", ")))) + } + return strings.Join(str, "\n") +} + +func sortHeaderKeys(hdrs http.Header) []string { + keys := make([]string, 0, len(hdrs)) + for key := range hdrs { + keys = append(keys, key) + } + sort.Strings(keys) + return keys +} + +func copyHeaders(hdrs http.Header) http.Header { + nh := http.Header{} + for k, v := range hdrs { + nh[k] = v + } + return nh +} + +func wrapErrors(n error, inner error) error { + if inner == nil { + return n + } + if n == nil { + return inner + } + return &restyError{ + err: n, + inner: inner, + } +} + +type restyError struct { + err error + inner error +} + +func (e *restyError) Error() string { + return e.err.Error() +} + +func (e *restyError) Unwrap() error { + return e.inner +} + +type noRetryErr struct { + err error +} + +func (e *noRetryErr) Error() string { + return e.err.Error() +} + +func wrapNoRetryErr(err error) error { + if err != nil { + err = &noRetryErr{err: err} + } + return err +} + +func unwrapNoRetryErr(err error) error { + if e, ok := err.(*noRetryErr); ok { + err = e.err + } + return err +} diff --git a/vendor/github.com/go-resty/resty/v2/util_curl.go b/vendor/github.com/go-resty/resty/v2/util_curl.go new file mode 100644 index 000000000..073d0492f --- /dev/null +++ b/vendor/github.com/go-resty/resty/v2/util_curl.go @@ -0,0 +1,78 @@ +package resty + +import ( + "bytes" + "io" + "net/http" + "net/http/cookiejar" + + "net/url" + "strings" + + "github.com/go-resty/resty/v2/shellescape" +) + +func buildCurlRequest(req *http.Request, httpCookiejar http.CookieJar) (curl string) { + // 1. Generate curl raw headers + + curl = "curl -X " + req.Method + " " + // req.Host + req.URL.Path + "?" + req.URL.RawQuery + " " + req.Proto + " " + headers := dumpCurlHeaders(req) + for _, kv := range *headers { + curl += `-H ` + shellescape.Quote(kv[0]+": "+kv[1]) + ` ` + } + + // 2. Generate curl cookies + // TODO validate this block of code, I think its not required since cookie captured via Headers + if cookieJar, ok := httpCookiejar.(*cookiejar.Jar); ok { + cookies := cookieJar.Cookies(req.URL) + if len(cookies) > 0 { + curl += `-H ` + shellescape.Quote(dumpCurlCookies(cookies)) + " " + } + } + + // 3. Generate curl body + if req.Body != nil { + buf, _ := io.ReadAll(req.Body) + req.Body = io.NopCloser(bytes.NewBuffer(buf)) // important!! + curl += `-d ` + shellescape.Quote(string(buf)) + " " + } + + urlString := shellescape.Quote(req.URL.String()) + if urlString == "''" { + urlString = "'http://unexecuted-request'" + } + curl += urlString + return curl +} + +// dumpCurlCookies dumps cookies to curl format +func dumpCurlCookies(cookies []*http.Cookie) string { + sb := strings.Builder{} + sb.WriteString("Cookie: ") + for _, cookie := range cookies { + sb.WriteString(cookie.Name + "=" + url.QueryEscape(cookie.Value) + "&") + } + return strings.TrimRight(sb.String(), "&") +} + +// dumpCurlHeaders dumps headers to curl format +func dumpCurlHeaders(req *http.Request) *[][2]string { + headers := [][2]string{} + for k, vs := range req.Header { + for _, v := range vs { + headers = append(headers, [2]string{k, v}) + } + } + n := len(headers) + for i := 0; i < n; i++ { + for j := n - 1; j > i; j-- { + jj := j - 1 + h1, h2 := headers[j], headers[jj] + if h1[0] < h2[0] { + headers[jj], headers[j] = headers[j], headers[jj] + } + } + } + return &headers +} diff --git a/vendor/github.com/go-rod/rod/.eslintrc.yml b/vendor/github.com/go-rod/rod/.eslintrc.yml new file mode 100644 index 000000000..308cf3376 --- /dev/null +++ b/vendor/github.com/go-rod/rod/.eslintrc.yml @@ -0,0 +1,9 @@ +extends: + - eslint:recommended +env: + browser: true + es6: true +parserOptions: + ecmaVersion: 2018 +plugins: + - html diff --git a/vendor/github.com/go-rod/rod/.gitignore b/vendor/github.com/go-rod/rod/.gitignore new file mode 100644 index 000000000..18f299f90 --- /dev/null +++ b/vendor/github.com/go-rod/rod/.gitignore @@ -0,0 +1,9 @@ +vendor/ +node_modules/ +tmp/ + +.git +.dockerignore +*.out +*.test +*.json diff --git a/vendor/github.com/go-rod/rod/.golangci.yml b/vendor/github.com/go-rod/rod/.golangci.yml new file mode 100644 index 000000000..4622b6c68 --- /dev/null +++ b/vendor/github.com/go-rod/rod/.golangci.yml @@ -0,0 +1,110 @@ +linters: + enable-all: true + disable: + - gochecknoinits + - paralleltest + - wrapcheck + - gosec + - gochecknoglobals + - musttag + - varnamelen + - wsl + - nonamedreturns + - tagliatelle + - nlreturn + - nakedret + - gomnd + - mnd + - err113 + - exhaustruct + - godox + - depguard + - testpackage + - exhaustive + - containedctx + - prealloc + - perfsprint + - ireturn + - contextcheck + - canonicalheader + - copyloopvar + - intrange + + # Deprecated ones: + - execinquery + - structcheck + - interfacer + - deadcode + - varcheck + - ifshort + - exhaustivestruct + - golint + - maligned + - nosnakecase + - scopelint + +linters-settings: + cyclop: + max-complexity: 15 + gocyclo: + min-complexity: 15 + nestif: + min-complexity: 6 + funlen: + lines: 120 + +issues: + exclude-use-default: false + + exclude-rules: + - path: _test.go$ + linters: + - lll + - funlen + - dupword + - goconst + - contextcheck + - errorlint + - testableexamples + - forcetypeassert + + # Generated code + - path: lib/proto/ + linters: + - lll + - gocritic + - dupword + - forcetypeassert + - path: lib/devices/list.go + linters: + - lll + - path: lib/js/helper.go + linters: + - lll + + - path: /fixtures/ + linters: + - forbidigo + + - path: lib/examples/ + linters: + - forbidigo + - noctx + - gocritic + + - path: examples?_test.go$ + linters: + - forbidigo + - noctx + - gocritic + + - path: main.go$ + linters: + - forbidigo + - noctx + - forcetypeassert + - lll + + - path: lib/assets/ + linters: + - lll diff --git a/vendor/github.com/go-rod/rod/.prettierrc.yml b/vendor/github.com/go-rod/rod/.prettierrc.yml new file mode 100644 index 000000000..eca9e731b --- /dev/null +++ b/vendor/github.com/go-rod/rod/.prettierrc.yml @@ -0,0 +1,3 @@ +semi: false +singleQuote: true +trailingComma: none diff --git a/vendor/github.com/go-rod/rod/LICENSE b/vendor/github.com/go-rod/rod/LICENSE new file mode 100644 index 000000000..2a0a30d29 --- /dev/null +++ b/vendor/github.com/go-rod/rod/LICENSE @@ -0,0 +1,9 @@ +The MIT License + +Copyright 2019 Yad Smood + +Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. \ No newline at end of file diff --git a/vendor/github.com/go-rod/rod/README.md b/vendor/github.com/go-rod/rod/README.md new file mode 100644 index 000000000..42590c9ec --- /dev/null +++ b/vendor/github.com/go-rod/rod/README.md @@ -0,0 +1,51 @@ +# Overview + +[![Go Reference](https://pkg.go.dev/badge/github.com/go-rod/rod.svg)](https://pkg.go.dev/github.com/go-rod/rod) +[![Discord Chat](https://img.shields.io/discord/719933559456006165.svg)][discord room] + +## [Documentation](https://go-rod.github.io/) | [API reference](https://pkg.go.dev/github.com/go-rod/rod?tab=doc) | [FAQ](https://go-rod.github.io/#/faq/README) + +Rod is a high-level driver directly based on [DevTools Protocol](https://chromedevtools.github.io/devtools-protocol). +It's designed for web automation and scraping for both high-level and low-level use, senior developers can use the low-level packages and functions to easily +customize or build up their own version of Rod, the high-level functions are just examples to build a default version of Rod. + +[中文 API 文档](https://pkg.go.dev/github.com/go-rod/go-rod-chinese) + +## Features + +- Chained context design, intuitive to timeout or cancel the long-running task +- Auto-wait elements to be ready +- Debugging friendly, auto input tracing, remote monitoring headless browser +- Thread-safe for all operations +- Automatically find or download [browser](lib/launcher) +- High-level helpers like WaitStable, WaitRequestIdle, HijackRequests, WaitDownload, etc +- Two-step WaitEvent design, never miss an event ([how it works](https://github.com/ysmood/goob)) +- Correctly handles nested iframes or shadow DOMs +- No zombie browser process after the crash ([how it works](https://github.com/ysmood/leakless)) +- [CI](https://github.com/go-rod/rod/actions) enforced 100% test coverage + +## Examples + +Please check the [examples_test.go](examples_test.go) file first, then check the [examples](lib/examples) folder. + +For more detailed examples, please search the unit tests. +Such as the usage of method `HandleAuth`, you can search all the `*_test.go` files that contain `HandleAuth`, +for example, use Github online [search in repository](https://github.com/go-rod/rod/search?q=HandleAuth&unscoped_q=HandleAuth). +You can also search the GitHub [issues](https://github.com/go-rod/rod/issues) or [discussions](https://github.com/go-rod/rod/discussions), +a lot of usage examples are recorded there. + +[Here](lib/examples/compare-chromedp) is a comparison of the examples between rod and Chromedp. + +If you have questions, please raise an [issues](https://github.com/go-rod/rod/issues)/[discussions](https://github.com/go-rod/rod/discussions) or join the [chat room][discord room]. + +## Join us + +Your help is more than welcome! Even just open an issue to ask a question may greatly help others. + +Please read [How To Ask Questions The Smart Way](http://www.catb.org/~esr/faqs/smart-questions.html) before you ask questions. + +We use Github Projects to manage tasks, you can see the priority and progress of the issues [here](https://github.com/go-rod/rod/projects). + +If you want to contribute please read the [Contributor Guide](.github/CONTRIBUTING.md). + +[discord room]: https://discord.gg/CpevuvY diff --git a/vendor/github.com/go-rod/rod/browser.go b/vendor/github.com/go-rod/rod/browser.go new file mode 100644 index 000000000..bd2045f59 --- /dev/null +++ b/vendor/github.com/go-rod/rod/browser.go @@ -0,0 +1,543 @@ +//go:generate go run ./lib/utils/setup +//go:generate go run ./lib/proto/generate +//go:generate go run ./lib/js/generate +//go:generate go run ./lib/assets/generate +//go:generate go run ./lib/utils/lint + +// Package rod is a high-level driver directly based on DevTools Protocol. +package rod + +import ( + "context" + "reflect" + "strings" + "sync" + "time" + + "github.com/go-rod/rod/lib/cdp" + "github.com/go-rod/rod/lib/defaults" + "github.com/go-rod/rod/lib/devices" + "github.com/go-rod/rod/lib/launcher" + "github.com/go-rod/rod/lib/proto" + "github.com/go-rod/rod/lib/utils" + "github.com/ysmood/goob" +) + +// Browser implements these interfaces. +var ( + _ proto.Client = &Browser{} + _ proto.Contextable = &Browser{} +) + +// Browser represents the browser. +// It doesn't depends on file system, it should work with remote browser seamlessly. +// To check the env var you can use to quickly enable options from CLI, check here: +// https://pkg.go.dev/github.com/go-rod/rod/lib/defaults +type Browser struct { + // BrowserContextID is the id for incognito window + BrowserContextID proto.BrowserBrowserContextID + + e eFunc + + ctx context.Context + + sleeper func() utils.Sleeper + + logger utils.Logger + + slowMotion time.Duration // see defaults.slow + trace bool // see defaults.Trace + monitor string + + defaultDevice devices.Device + + controlURL string + client CDPClient + event *goob.Observable // all the browser events from cdp client + targetsLock *sync.Mutex + + // stores all the previous cdp call of same type. Browser doesn't have enough API + // for us to retrieve all its internal states. This is an workaround to map them to local. + // For example you can't use cdp API to get the current position of mouse. + states *sync.Map +} + +// New creates a controller. +// DefaultDevice to emulate is set to [devices.LaptopWithMDPIScreen].Landscape(), it will change the default +// user-agent and can make the actual view area smaller than the browser window on headful mode, +// you can use [Browser.NoDefaultDevice] to disable it. +func New() *Browser { + return (&Browser{ + ctx: context.Background(), + sleeper: DefaultSleeper, + controlURL: defaults.URL, + slowMotion: defaults.Slow, + trace: defaults.Trace, + monitor: defaults.Monitor, + logger: DefaultLogger, + defaultDevice: devices.LaptopWithMDPIScreen.Landscape(), + targetsLock: &sync.Mutex{}, + states: &sync.Map{}, + }).WithPanic(utils.Panic) +} + +// Incognito creates a new incognito browser. +func (b *Browser) Incognito() (*Browser, error) { + res, err := proto.TargetCreateBrowserContext{}.Call(b) + if err != nil { + return nil, err + } + + incognito := *b + incognito.BrowserContextID = res.BrowserContextID + + return &incognito, nil +} + +// ControlURL set the url to remote control browser. +func (b *Browser) ControlURL(url string) *Browser { + b.controlURL = url + return b +} + +// SlowMotion set the delay for each control action, such as the simulation of the human inputs. +func (b *Browser) SlowMotion(delay time.Duration) *Browser { + b.slowMotion = delay + return b +} + +// Trace enables/disables the visual tracing of the input actions on the page. +func (b *Browser) Trace(enable bool) *Browser { + b.trace = enable + return b +} + +// Monitor address to listen if not empty. Shortcut for [Browser.ServeMonitor]. +func (b *Browser) Monitor(url string) *Browser { + b.monitor = url + return b +} + +// Logger overrides the default log functions for tracing. +func (b *Browser) Logger(l utils.Logger) *Browser { + b.logger = l + return b +} + +// Client set the cdp client. +func (b *Browser) Client(c CDPClient) *Browser { + b.client = c + return b +} + +// DefaultDevice sets the default device for new page to emulate in the future. +// Default is [devices.LaptopWithMDPIScreen]. +// Set it to [devices.Clear] to disable it. +func (b *Browser) DefaultDevice(d devices.Device) *Browser { + b.defaultDevice = d + return b +} + +// NoDefaultDevice is the same as [Browser.DefaultDevice](devices.Clear). +func (b *Browser) NoDefaultDevice() *Browser { + return b.DefaultDevice(devices.Clear) +} + +// Connect to the browser and start to control it. +// If fails to connect, try to launch a local browser, if local browser not found try to download one. +func (b *Browser) Connect() error { + if b.client == nil { + u := b.controlURL + if u == "" { + var err error + u, err = launcher.New().Context(b.ctx).Launch() + if err != nil { + return err + } + } + + c, err := cdp.StartWithURL(b.ctx, u, nil) + if err != nil { + return err + } + b.client = c + } else if b.controlURL != "" { + panic("Browser.Client and Browser.ControlURL can't be set at the same time") + } + + b.initEvents() + + if b.monitor != "" { + launcher.Open(b.ServeMonitor(b.monitor)) + } + + return proto.TargetSetDiscoverTargets{Discover: true}.Call(b) +} + +// Close the browser. +func (b *Browser) Close() error { + if b.BrowserContextID == "" { + return proto.BrowserClose{}.Call(b) + } + return proto.TargetDisposeBrowserContext{BrowserContextID: b.BrowserContextID}.Call(b) +} + +// Page creates a new browser tab. If opts.URL is empty, the default target will be "about:blank". +func (b *Browser) Page(opts proto.TargetCreateTarget) (p *Page, err error) { + req := opts + req.BrowserContextID = b.BrowserContextID + req.URL = "about:blank" + + target, err := req.Call(b) + if err != nil { + return nil, err + } + defer func() { + // If Navigate or PageFromTarget fails we should close the target to prevent leak + if err != nil { + _, _ = proto.TargetCloseTarget{TargetID: target.TargetID}.Call(b) + } + }() + + p, err = b.PageFromTarget(target.TargetID) + if err != nil { + return + } + + if opts.URL == "" { + return + } + + err = p.Navigate(opts.URL) + + return +} + +// Pages retrieves all visible pages. +func (b *Browser) Pages() (Pages, error) { + list, err := proto.TargetGetTargets{}.Call(b) + if err != nil { + return nil, err + } + + pageList := Pages{} + for _, target := range list.TargetInfos { + if target.Type != proto.TargetTargetInfoTypePage { + continue + } + + page, err := b.PageFromTarget(target.TargetID) + if err != nil { + return nil, err + } + pageList = append(pageList, page) + } + + return pageList, nil +} + +// Call implements the [proto.Client] to call raw cdp interface directly. +func (b *Browser) Call(ctx context.Context, sessionID, methodName string, params interface{}) (res []byte, err error) { + res, err = b.client.Call(ctx, sessionID, methodName, params) + if err != nil { + return nil, err + } + + b.set(proto.TargetSessionID(sessionID), methodName, params) + return +} + +// PageFromSession is used for low-level debugging. +func (b *Browser) PageFromSession(sessionID proto.TargetSessionID) *Page { + sessionCtx, cancel := context.WithCancel(b.ctx) + return &Page{ + e: b.e, + ctx: sessionCtx, + sessionCancel: cancel, + sleeper: b.sleeper, + browser: b, + SessionID: sessionID, + } +} + +// PageFromTarget gets or creates a Page instance. +func (b *Browser) PageFromTarget(targetID proto.TargetTargetID) (*Page, error) { + b.targetsLock.Lock() + defer b.targetsLock.Unlock() + + page := b.loadCachedPage(targetID) + if page != nil { + return page, nil + } + + session, err := proto.TargetAttachToTarget{ + TargetID: targetID, + Flatten: true, // if it's not set no response will return + }.Call(b) + if err != nil { + return nil, err + } + + sessionCtx, cancel := context.WithCancel(b.ctx) + + page = &Page{ + e: b.e, + ctx: sessionCtx, + sessionCancel: cancel, + sleeper: b.sleeper, + browser: b, + TargetID: targetID, + SessionID: session.SessionID, + FrameID: proto.PageFrameID(targetID), + jsCtxLock: &sync.Mutex{}, + jsCtxID: new(proto.RuntimeRemoteObjectID), + helpersLock: &sync.Mutex{}, + } + + page.root = page + page.newKeyboard().newMouse().newTouch() + + if !b.defaultDevice.IsClear() { + err = page.Emulate(b.defaultDevice) + if err != nil { + return nil, err + } + } + + b.cachePage(page) + + page.initEvents() + + // If we don't enable it, it will cause a lot of unexpected browser behavior. + // Such as proto.PageAddScriptToEvaluateOnNewDocument won't work. + page.EnableDomain(&proto.PageEnable{}) + + return page, nil +} + +// EachEvent is similar to [Page.EachEvent], but catches events of the entire browser. +func (b *Browser) EachEvent(callbacks ...interface{}) (wait func()) { + return b.eachEvent("", callbacks...) +} + +// WaitEvent waits for the next event for one time. It will also load the data into the event object. +func (b *Browser) WaitEvent(e proto.Event) (wait func()) { + return b.waitEvent("", e) +} + +// waits for the next event for one time. It will also load the data into the event object. +func (b *Browser) waitEvent(sessionID proto.TargetSessionID, e proto.Event) (wait func()) { + valE := reflect.ValueOf(e) + valTrue := reflect.ValueOf(true) + + if valE.Kind() != reflect.Ptr { + valE = reflect.New(valE.Type()) + } + + // dynamically creates a function on runtime: + // + // func(ee proto.Event) bool { + // *e = *ee + // return true + // } + fnType := reflect.FuncOf([]reflect.Type{valE.Type()}, []reflect.Type{valTrue.Type()}, false) + fnVal := reflect.MakeFunc(fnType, func(args []reflect.Value) []reflect.Value { + valE.Elem().Set(args[0].Elem()) + return []reflect.Value{valTrue} + }) + + return b.eachEvent(sessionID, fnVal.Interface()) +} + +// If the any callback returns true the event loop will stop. +// It will enable the related domains if not enabled, and restore them after wait ends. +func (b *Browser) eachEvent(sessionID proto.TargetSessionID, callbacks ...interface{}) (wait func()) { + cbMap := map[string]reflect.Value{} + restores := []func(){} + + for _, cb := range callbacks { + cbVal := reflect.ValueOf(cb) + eType := cbVal.Type().In(0) + name := reflect.New(eType.Elem()).Interface().(proto.Event).ProtoEvent() //nolint: forcetypeassert + cbMap[name] = cbVal + + // Only enabled domains will emit events to cdp client. + // We enable the domains for the event types if it's not enabled. + // We restore the domains to their previous states after the wait ends. + domain, _ := proto.ParseMethodName(name) + if req := proto.GetType(domain + ".enable"); req != nil { + enable := reflect.New(req).Interface().(proto.Request) //nolint: forcetypeassert + restores = append(restores, b.EnableDomain(sessionID, enable)) + } + } + + b, cancel := b.WithCancel() + messages := b.Event() + + return func() { + if messages == nil { + panic("can't use wait function twice") + } + + defer func() { + cancel() + messages = nil + for _, restore := range restores { + restore() + } + }() + + for msg := range messages { + if !(sessionID == "" || msg.SessionID == sessionID) { + continue + } + + if cbVal, has := cbMap[msg.Method]; has { + e := reflect.New(proto.GetType(msg.Method)) + msg.Load(e.Interface().(proto.Event)) //nolint: forcetypeassert + args := []reflect.Value{e} + if cbVal.Type().NumIn() == 2 { + args = append(args, reflect.ValueOf(msg.SessionID)) + } + res := cbVal.Call(args) + if len(res) > 0 { + if res[0].Bool() { + return + } + } + } + } + } +} + +// Event of the browser. +func (b *Browser) Event() <-chan *Message { + src := b.event.Subscribe(b.ctx) + dst := make(chan *Message) + go func() { + defer close(dst) + for { + select { + case <-b.ctx.Done(): + return + case e, ok := <-src: + if !ok { + return + } + select { + case <-b.ctx.Done(): + return + case dst <- e.(*Message): //nolint: forcetypeassert + } + } + } + }() + return dst +} + +func (b *Browser) initEvents() { + ctx, cancel := context.WithCancel(b.ctx) + b.event = goob.New(ctx) + event := b.client.Event() + + go func() { + defer cancel() + for e := range event { + b.event.Publish(&Message{ + SessionID: proto.TargetSessionID(e.SessionID), + Method: e.Method, + lock: &sync.Mutex{}, + data: e.Params, + }) + } + }() +} + +func (b *Browser) pageInfo(id proto.TargetTargetID) (*proto.TargetTargetInfo, error) { + res, err := proto.TargetGetTargetInfo{TargetID: id}.Call(b) + if err != nil { + return nil, err + } + return res.TargetInfo, nil +} + +func (b *Browser) isHeadless() (enabled bool) { + res, _ := proto.BrowserGetBrowserCommandLine{}.Call(b) + for _, v := range res.Arguments { + if strings.Contains(v, "headless") { + return true + } + } + return false +} + +// IgnoreCertErrors switch. If enabled, all certificate errors will be ignored. +func (b *Browser) IgnoreCertErrors(enable bool) error { + return proto.SecuritySetIgnoreCertificateErrors{Ignore: enable}.Call(b) +} + +// GetCookies from the browser. +func (b *Browser) GetCookies() ([]*proto.NetworkCookie, error) { + res, err := proto.StorageGetCookies{BrowserContextID: b.BrowserContextID}.Call(b) + if err != nil { + return nil, err + } + return res.Cookies, nil +} + +// SetCookies to the browser. If the cookies is nil it will clear all the cookies. +func (b *Browser) SetCookies(cookies []*proto.NetworkCookieParam) error { + if cookies == nil { + return proto.StorageClearCookies{BrowserContextID: b.BrowserContextID}.Call(b) + } + + return proto.StorageSetCookies{ + Cookies: cookies, + BrowserContextID: b.BrowserContextID, + }.Call(b) +} + +// WaitDownload returns a helper to get the next download file. +// The file path will be: +// +// filepath.Join(dir, info.GUID) +func (b *Browser) WaitDownload(dir string) func() (info *proto.PageDownloadWillBegin) { + var oldDownloadBehavior proto.BrowserSetDownloadBehavior + has := b.LoadState("", &oldDownloadBehavior) + + _ = proto.BrowserSetDownloadBehavior{ + Behavior: proto.BrowserSetDownloadBehaviorBehaviorAllowAndName, + BrowserContextID: b.BrowserContextID, + DownloadPath: dir, + }.Call(b) + + var start *proto.PageDownloadWillBegin + + waitProgress := b.EachEvent(func(e *proto.PageDownloadWillBegin) { + start = e + }, func(e *proto.PageDownloadProgress) bool { + return start != nil && start.GUID == e.GUID && e.State == proto.PageDownloadProgressStateCompleted + }) + + return func() *proto.PageDownloadWillBegin { + defer func() { + if has { + _ = oldDownloadBehavior.Call(b) + } else { + _ = proto.BrowserSetDownloadBehavior{ + Behavior: proto.BrowserSetDownloadBehaviorBehaviorDefault, + BrowserContextID: b.BrowserContextID, + }.Call(b) + } + }() + + waitProgress() + + return start + } +} + +// Version info of the browser. +func (b *Browser) Version() (*proto.BrowserGetVersionResult, error) { + return proto.BrowserGetVersion{}.Call(b) +} diff --git a/vendor/github.com/go-rod/rod/context.go b/vendor/github.com/go-rod/rod/context.go new file mode 100644 index 000000000..b77069541 --- /dev/null +++ b/vendor/github.com/go-rod/rod/context.go @@ -0,0 +1,132 @@ +package rod + +import ( + "context" + "time" + + "github.com/go-rod/rod/lib/utils" +) + +type ( + timeoutContextKey struct{} + timeoutContextVal struct { + parent context.Context + cancel context.CancelFunc + } +) + +// Context returns a clone with the specified ctx for chained sub-operations. +func (b *Browser) Context(ctx context.Context) *Browser { + newObj := *b + newObj.ctx = ctx + return &newObj +} + +// GetContext of current instance. +func (b *Browser) GetContext() context.Context { + return b.ctx +} + +// Timeout returns a clone with the specified total timeout of all chained sub-operations. +func (b *Browser) Timeout(d time.Duration) *Browser { + ctx, cancel := context.WithTimeout(b.ctx, d) + return b.Context(context.WithValue(ctx, timeoutContextKey{}, &timeoutContextVal{b.ctx, cancel})) +} + +// CancelTimeout cancels the current timeout context and returns a clone with the parent context. +func (b *Browser) CancelTimeout() *Browser { + val := b.ctx.Value(timeoutContextKey{}).(*timeoutContextVal) //nolint:forcetypeassert + val.cancel() + return b.Context(val.parent) +} + +// WithCancel returns a clone with a context cancel function. +func (b *Browser) WithCancel() (*Browser, func()) { + ctx, cancel := context.WithCancel(b.ctx) + return b.Context(ctx), cancel +} + +// Sleeper returns a clone with the specified sleeper for chained sub-operations. +func (b *Browser) Sleeper(sleeper func() utils.Sleeper) *Browser { + newObj := *b + newObj.sleeper = sleeper + return &newObj +} + +// Context returns a clone with the specified ctx for chained sub-operations. +func (p *Page) Context(ctx context.Context) *Page { + p.helpersLock.Lock() + newObj := *p + p.helpersLock.Unlock() + newObj.ctx = ctx + return &newObj +} + +// GetContext of current instance. +func (p *Page) GetContext() context.Context { + return p.ctx +} + +// Timeout returns a clone with the specified total timeout of all chained sub-operations. +func (p *Page) Timeout(d time.Duration) *Page { + ctx, cancel := context.WithTimeout(p.ctx, d) + return p.Context(context.WithValue(ctx, timeoutContextKey{}, &timeoutContextVal{p.ctx, cancel})) +} + +// CancelTimeout cancels the current timeout context and returns a clone with the parent context. +func (p *Page) CancelTimeout() *Page { + val := p.ctx.Value(timeoutContextKey{}).(*timeoutContextVal) //nolint: forcetypeassert + val.cancel() + return p.Context(val.parent) +} + +// WithCancel returns a clone with a context cancel function. +func (p *Page) WithCancel() (*Page, func()) { + ctx, cancel := context.WithCancel(p.ctx) + return p.Context(ctx), cancel +} + +// Sleeper returns a clone with the specified sleeper for chained sub-operations. +func (p *Page) Sleeper(sleeper func() utils.Sleeper) *Page { + newObj := *p + newObj.sleeper = sleeper + return &newObj +} + +// Context returns a clone with the specified ctx for chained sub-operations. +func (el *Element) Context(ctx context.Context) *Element { + newObj := *el + newObj.ctx = ctx + return &newObj +} + +// GetContext of current instance. +func (el *Element) GetContext() context.Context { + return el.ctx +} + +// Timeout returns a clone with the specified total timeout of all chained sub-operations. +func (el *Element) Timeout(d time.Duration) *Element { + ctx, cancel := context.WithTimeout(el.ctx, d) + return el.Context(context.WithValue(ctx, timeoutContextKey{}, &timeoutContextVal{el.ctx, cancel})) +} + +// CancelTimeout cancels the current timeout context and returns a clone with the parent context. +func (el *Element) CancelTimeout() *Element { + val := el.ctx.Value(timeoutContextKey{}).(*timeoutContextVal) //nolint: forcetypeassert + val.cancel() + return el.Context(val.parent) +} + +// WithCancel returns a clone with a context cancel function. +func (el *Element) WithCancel() (*Element, func()) { + ctx, cancel := context.WithCancel(el.ctx) + return el.Context(ctx), cancel +} + +// Sleeper returns a clone with the specified sleeper for chained sub-operations. +func (el *Element) Sleeper(sleeper func() utils.Sleeper) *Element { + newObj := *el + newObj.sleeper = sleeper + return &newObj +} diff --git a/vendor/github.com/go-rod/rod/dev_helpers.go b/vendor/github.com/go-rod/rod/dev_helpers.go new file mode 100644 index 000000000..c45275634 --- /dev/null +++ b/vendor/github.com/go-rod/rod/dev_helpers.go @@ -0,0 +1,264 @@ +// This file defines the helpers to develop automation. +// Such as when running automation we can use trace to visually +// see where the mouse going to click. + +package rod + +import ( + "encoding/json" + "fmt" + "html" + "net" + "net/http" + "strings" + "time" + + "github.com/go-rod/rod/lib/assets" + "github.com/go-rod/rod/lib/js" + "github.com/go-rod/rod/lib/proto" + "github.com/go-rod/rod/lib/utils" +) + +// TraceType for logger. +type TraceType string + +// String interface. +func (t TraceType) String() string { + return fmt.Sprintf("[%s]", string(t)) +} + +const ( + // TraceTypeWaitRequestsIdle type. + TraceTypeWaitRequestsIdle TraceType = "wait requests idle" + + // TraceTypeWaitRequests type. + TraceTypeWaitRequests TraceType = "wait requests" + + // TraceTypeQuery type. + TraceTypeQuery TraceType = "query" + + // TraceTypeWait type. + TraceTypeWait TraceType = "wait" + + // TraceTypeInput type. + TraceTypeInput TraceType = "input" +) + +// ServeMonitor starts the monitor server. +// The reason why not to use "chrome://inspect/#devices" is one target cannot be driven by multiple controllers. +func (b *Browser) ServeMonitor(host string) string { + u, mux, closeSvr := serve(host) + go func() { + <-b.ctx.Done() + utils.E(closeSvr()) + }() + + mux.HandleFunc("/", func(w http.ResponseWriter, _ *http.Request) { + httHTML(w, assets.Monitor) + }) + mux.HandleFunc("/api/pages", func(w http.ResponseWriter, _ *http.Request) { + res, err := proto.TargetGetTargets{}.Call(b) //nolint: contextcheck + utils.E(err) + + list := []*proto.TargetTargetInfo{} + for _, info := range res.TargetInfos { + if info.Type == proto.TargetTargetInfoTypePage { + list = append(list, info) + } + } + + w.WriteHeader(http.StatusOK) + utils.E(w.Write(utils.MustToJSONBytes(list))) + }) + mux.HandleFunc("/page/", func(w http.ResponseWriter, _ *http.Request) { + httHTML(w, assets.MonitorPage) + }) + mux.HandleFunc("/api/page/", func(w http.ResponseWriter, r *http.Request) { + id := r.URL.Path[strings.LastIndex(r.URL.Path, "/")+1:] + info, err := b.pageInfo(proto.TargetTargetID(id)) //nolint: contextcheck + utils.E(err) + w.WriteHeader(http.StatusOK) + utils.E(w.Write(utils.MustToJSONBytes(info))) + }) + mux.HandleFunc("/screenshot/", func(w http.ResponseWriter, r *http.Request) { + id := r.URL.Path[strings.LastIndex(r.URL.Path, "/")+1:] + target := proto.TargetTargetID(id) + p := b.MustPageFromTargetID(target) + + w.Header().Add("Content-Type", "image/png;") + utils.E(w.Write(p.MustScreenshot())) //nolint: contextcheck + }) + + return u +} + +// check method and sleep if needed. +func (b *Browser) trySlowMotion() { + if b.slowMotion == 0 { + return + } + + time.Sleep(b.slowMotion) +} + +// ExposeHelpers helper functions to page's js context so that we can use the Devtools' console to debug them. +func (p *Page) ExposeHelpers(list ...*js.Function) { + p.MustEvaluate(evalHelper(&js.Function{ + Name: "_" + utils.RandString(8), // use a random name so it won't hit the cache + Definition: "() => { window.rod = functions }", + Dependencies: list, + })) +} + +// Overlay a rectangle on the main frame with specified message. +func (p *Page) Overlay(left, top, width, height float64, msg string) (remove func()) { + id := utils.RandString(8) + + _, _ = p.root.Evaluate(evalHelper(js.Overlay, + id, + left, + top, + width, + height, + msg, + ).ByPromise()) + + remove = func() { + _, _ = p.root.Evaluate(evalHelper(js.RemoveOverlay, id)) + } + + return +} + +func (p *Page) tryTrace(typ TraceType, msg ...interface{}) func() { + if !p.browser.trace { + return func() {} + } + + msg = append([]interface{}{typ}, msg...) + msg = append(msg, p) + + p.browser.logger.Println(msg...) + + return p.Overlay(0, 0, 500, 0, fmt.Sprint(msg)) +} + +func (p *Page) tryTraceQuery(opts *EvalOptions) func() { + if !p.browser.trace { + return func() {} + } + + p.browser.logger.Println(TraceTypeQuery, opts, p) + + msg := fmt.Sprintf("%s", html.EscapeString(opts.String())) + return p.Overlay(0, 0, 500, 0, msg) +} + +func (p *Page) tryTraceReq(includes, excludes []string) func(map[proto.NetworkRequestID]string) { + if !p.browser.trace { + return func(map[proto.NetworkRequestID]string) {} + } + + msg := map[string][]string{ + "includes": includes, + "excludes": excludes, + } + p.browser.logger.Println(TraceTypeWaitRequestsIdle, msg, p) + cleanup := p.Overlay(0, 0, 500, 0, utils.MustToJSON(msg)) + + ch := make(chan map[string]string) + update := func(list map[proto.NetworkRequestID]string) { + clone := map[string]string{} + for k, v := range list { + clone[string(k)] = v + } + ch <- clone + } + + go func() { + var waitList map[string]string + t := time.NewTicker(time.Second) + for { + select { + case <-p.ctx.Done(): + t.Stop() + cleanup() + return + case waitList = <-ch: + case <-t.C: + p.browser.logger.Println(TraceTypeWaitRequests, p, waitList) + } + } + }() + + return update +} + +// Overlay msg on the element. +func (el *Element) Overlay(msg string) (removeOverlay func()) { + id := utils.RandString(8) + + _, _ = el.Evaluate(evalHelper(js.ElementOverlay, + id, + msg, + ).ByPromise()) + + removeOverlay = func() { + _, _ = el.Evaluate(evalHelper(js.RemoveOverlay, id)) + } + + return +} + +func (el *Element) tryTrace(typ TraceType, msg ...interface{}) func() { + if !el.page.browser.trace { + return func() {} + } + + msg = append([]interface{}{typ}, msg...) + msg = append(msg, el) + + el.page.browser.logger.Println(msg...) + + return el.Overlay(fmt.Sprint(msg)) +} + +func (m *Mouse) initMouseTracer() { + _, _ = m.page.Evaluate(evalHelper(js.InitMouseTracer, m.id, assets.MousePointer).ByPromise()) +} + +func (m *Mouse) updateMouseTracer() bool { + res, err := m.page.Evaluate(evalHelper(js.UpdateMouseTracer, m.id, m.pos.X, m.pos.Y)) + if err != nil { + return true + } + return res.Value.Bool() +} + +// Serve a port, if host is empty a random port will be used. +func serve(host string) (string, *http.ServeMux, func() error) { + if host == "" { + host = "127.0.0.1:0" + } + + mux := http.NewServeMux() + srv := &http.Server{Handler: http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + defer func() { + if err := recover(); err != nil { + w.WriteHeader(http.StatusBadRequest) + utils.E(json.NewEncoder(w).Encode(err)) + } + }() + + mux.ServeHTTP(w, r) + })} + + l, err := net.Listen("tcp", host) + utils.E(err) + + go func() { _ = srv.Serve(l) }() + + url := "http://" + l.Addr().String() + + return url, mux, srv.Close +} diff --git a/vendor/github.com/go-rod/rod/element.go b/vendor/github.com/go-rod/rod/element.go new file mode 100644 index 000000000..97fceed0f --- /dev/null +++ b/vendor/github.com/go-rod/rod/element.go @@ -0,0 +1,754 @@ +package rod + +import ( + "context" + "errors" + "fmt" + "reflect" + "strings" + "time" + + "github.com/go-rod/rod/lib/cdp" + "github.com/go-rod/rod/lib/input" + "github.com/go-rod/rod/lib/js" + "github.com/go-rod/rod/lib/proto" + "github.com/go-rod/rod/lib/utils" + "github.com/ysmood/gson" +) + +// Element implements these interfaces. +var ( + _ proto.Client = &Element{} + _ proto.Contextable = &Element{} + _ proto.Sessionable = &Element{} +) + +// Element represents the DOM element. +type Element struct { + Object *proto.RuntimeRemoteObject + + e eFunc + + ctx context.Context + + sleeper func() utils.Sleeper + + page *Page +} + +// GetSessionID interface. +func (el *Element) GetSessionID() proto.TargetSessionID { + return el.page.SessionID +} + +// String interface. +func (el *Element) String() string { + return fmt.Sprintf("<%s>", el.Object.Description) +} + +// Page of the element. +func (el *Element) Page() *Page { + return el.page +} + +// Focus sets focus on the specified element. +// Before the action, it will try to scroll to the element. +func (el *Element) Focus() error { + err := el.ScrollIntoView() + if err != nil { + return err + } + + _, err = el.Evaluate(Eval(`() => this.focus()`).ByUser()) + return err +} + +// ScrollIntoView scrolls the current element into the visible area of the browser +// window if it's not already within the visible area. +func (el *Element) ScrollIntoView() error { + defer el.tryTrace(TraceTypeInput, "scroll into view")() + el.page.browser.trySlowMotion() + + err := el.WaitStableRAF() + if err != nil { + return err + } + + return proto.DOMScrollIntoViewIfNeeded{ObjectID: el.id()}.Call(el) +} + +// Hover the mouse over the center of the element. +// Before the action, it will try to scroll to the element and wait until it's interactable. +func (el *Element) Hover() error { + pt, err := el.WaitInteractable() + if err != nil { + return err + } + + return el.page.Context(el.ctx).Mouse.MoveTo(*pt) +} + +// MoveMouseOut of the current element. +func (el *Element) MoveMouseOut() error { + shape, err := el.Shape() + if err != nil { + return err + } + box := shape.Box() + return el.page.Mouse.MoveTo(proto.NewPoint(box.X+box.Width, box.Y)) +} + +// Click will press then release the button just like a human. +// Before the action, it will try to scroll to the element, hover the mouse over it, +// wait until the it's interactable and enabled. +func (el *Element) Click(button proto.InputMouseButton, clickCount int) error { + err := el.Hover() + if err != nil { + return err + } + + err = el.WaitEnabled() + if err != nil { + return err + } + + defer el.tryTrace(TraceTypeInput, string(button)+" click")() + + return el.page.Context(el.ctx).Mouse.Click(button, clickCount) +} + +// Tap will scroll to the button and tap it just like a human. +// Before the action, it will try to scroll to the element and wait until it's interactable and enabled. +func (el *Element) Tap() error { + err := el.ScrollIntoView() + if err != nil { + return err + } + + err = el.WaitEnabled() + if err != nil { + return err + } + + pt, err := el.WaitInteractable() + if err != nil { + return err + } + + defer el.tryTrace(TraceTypeInput, "tap")() + + return el.page.Context(el.ctx).Touch.Tap(pt.X, pt.Y) +} + +// Interactable checks if the element is interactable with cursor. +// The cursor can be mouse, finger, stylus, etc. +// If not interactable err will be ErrNotInteractable, such as when covered by a modal,. +func (el *Element) Interactable() (pt *proto.Point, err error) { + noPointerEvents, err := el.Eval(`() => getComputedStyle(this).pointerEvents === 'none'`) + if err != nil { + return nil, err + } + + if noPointerEvents.Value.Bool() { + return nil, &NoPointerEventsError{el} + } + + shape, err := el.Shape() + if err != nil { + return nil, err + } + + pt = shape.OnePointInside() + if pt == nil { + err = &InvisibleShapeError{el} + return + } + + scroll, err := el.page.root.Context(el.ctx).Eval(`() => ({ x: window.scrollX, y: window.scrollY })`) + if err != nil { + return + } + + elAtPoint, err := el.page.Context(el.ctx).ElementFromPoint( + int(pt.X)+scroll.Value.Get("x").Int(), + int(pt.Y)+scroll.Value.Get("y").Int(), + ) + if err != nil { + if errors.Is(err, cdp.ErrNodeNotFoundAtPos) { + err = &InvisibleShapeError{el} + } + return + } + + isParent, err := el.ContainsElement(elAtPoint) + if err != nil { + return + } + + if !isParent { + err = &CoveredError{elAtPoint} + } + return +} + +// Shape of the DOM element content. The shape is a group of 4-sides polygons. +// A 4-sides polygon is not necessary a rectangle. 4-sides polygons can be apart from each other. +// For example, we use 2 4-sides polygons to describe the shape below: +// +// ____________ ____________ +// / ___/ = /___________/ + _________ +// /________/ /________/ +func (el *Element) Shape() (*proto.DOMGetContentQuadsResult, error) { + return proto.DOMGetContentQuads{ObjectID: el.id()}.Call(el) +} + +// Type is similar with Keyboard.Type. +// Before the action, it will try to scroll to the element and focus on it. +func (el *Element) Type(keys ...input.Key) error { + err := el.Focus() + if err != nil { + return err + } + return el.page.Context(el.ctx).Keyboard.Type(keys...) +} + +// KeyActions is similar with Page.KeyActions. +// Before the action, it will try to scroll to the element and focus on it. +func (el *Element) KeyActions() (*KeyActions, error) { + err := el.Focus() + if err != nil { + return nil, err + } + + return el.page.Context(el.ctx).KeyActions(), nil +} + +// SelectText selects the text that matches the regular expression. +// Before the action, it will try to scroll to the element and focus on it. +func (el *Element) SelectText(regex string) error { + err := el.Focus() + if err != nil { + return err + } + + defer el.tryTrace(TraceTypeInput, "select text: "+regex)() + el.page.browser.trySlowMotion() + + _, err = el.Evaluate(evalHelper(js.SelectText, regex).ByUser()) + return err +} + +// SelectAllText selects all text +// Before the action, it will try to scroll to the element and focus on it. +func (el *Element) SelectAllText() error { + err := el.Focus() + if err != nil { + return err + } + + defer el.tryTrace(TraceTypeInput, "select all text")() + el.page.browser.trySlowMotion() + + _, err = el.Evaluate(evalHelper(js.SelectAllText).ByUser()) + return err +} + +// Input focuses on the element and input text to it. +// Before the action, it will scroll to the element, wait until it's visible, enabled and writable. +// To empty the input you can use something like +// +// el.SelectAllText().MustInput("") +func (el *Element) Input(text string) error { + err := el.Focus() + if err != nil { + return err + } + + err = el.WaitEnabled() + if err != nil { + return err + } + + err = el.WaitWritable() + if err != nil { + return err + } + + err = el.page.Context(el.ctx).InsertText(text) + _, _ = el.Evaluate(evalHelper(js.InputEvent).ByUser()) + return err +} + +// InputTime focuses on the element and input time to it. +// Before the action, it will scroll to the element, wait until it's visible, enabled and writable. +// It will wait until the element is visible, enabled and writable. +func (el *Element) InputTime(t time.Time) error { + err := el.Focus() + if err != nil { + return err + } + + err = el.WaitEnabled() + if err != nil { + return err + } + + err = el.WaitWritable() + if err != nil { + return err + } + + defer el.tryTrace(TraceTypeInput, "input "+t.String())() + + _, err = el.Evaluate(evalHelper(js.InputTime, t.UnixNano()/1e6).ByUser()) + return err +} + +// InputColor focuses on the element and inputs a color string to it. +// Before the action, it will scroll to the element, wait until it's visible, enabled and writable. +func (el *Element) InputColor(color string) error { + err := el.Focus() + if err != nil { + return err + } + + err = el.WaitEnabled() + if err != nil { + return err + } + + err = el.WaitWritable() + if err != nil { + return err + } + + defer el.tryTrace(TraceTypeInput, "input "+color)() + + _, err = el.Evaluate(evalHelper(js.InputColor, color)) + return err +} + +// Blur removes focus from the element. +func (el *Element) Blur() error { + _, err := el.Evaluate(Eval("() => this.blur()").ByUser()) + return err +} + +// Select the children option elements that match the selectors. +// Before the action, it will scroll to the element, wait until it's visible. +// If no option matches the selectors, it will return [ErrElementNotFound]. +func (el *Element) Select(selectors []string, selected bool, t SelectorType) error { + err := el.Focus() + if err != nil { + return err + } + + defer el.tryTrace(TraceTypeInput, fmt.Sprintf(`select "%s"`, strings.Join(selectors, "; ")))() + el.page.browser.trySlowMotion() + + res, err := el.Evaluate(evalHelper(js.Select, selectors, selected, t).ByUser()) + if err != nil { + return err + } + if !res.Value.Bool() { + return &ElementNotFoundError{} + } + return nil +} + +// Matches checks if the element can be selected by the css selector. +func (el *Element) Matches(selector string) (bool, error) { + res, err := el.Eval(`s => this.matches(s)`, selector) + if err != nil { + return false, err + } + return res.Value.Bool(), nil +} + +// Attribute of the DOM object. +// Attribute vs Property: +// https://stackoverflow.com/questions/6003819/what-is-the-difference-between-properties-and-attributes-in-html +func (el *Element) Attribute(name string) (*string, error) { + attr, err := el.Eval("(n) => this.getAttribute(n)", name) + if err != nil { + return nil, err + } + + if attr.Value.Nil() { + return nil, nil //nolint: nilnil + } + + s := attr.Value.Str() + return &s, nil +} + +// Property of the DOM object. +// Property vs Attribute: +// https://stackoverflow.com/questions/6003819/what-is-the-difference-between-properties-and-attributes-in-html +func (el *Element) Property(name string) (gson.JSON, error) { + prop, err := el.Eval("(n) => this[n]", name) + if err != nil { + return gson.New(nil), err + } + + return prop.Value, nil +} + +// Disabled checks if the element is disabled. +func (el *Element) Disabled() (bool, error) { + prop, err := el.Property("disabled") + if err != nil { + return false, err + } + return prop.Bool(), nil +} + +// SetFiles of the current file input element. +func (el *Element) SetFiles(paths []string) error { + absPaths := utils.AbsolutePaths(paths) + + defer el.tryTrace(TraceTypeInput, fmt.Sprintf("set files: %v", absPaths))() + el.page.browser.trySlowMotion() + + err := proto.DOMSetFileInputFiles{ + Files: absPaths, + ObjectID: el.id(), + }.Call(el) + + return err +} + +// Describe the current element. The depth is the maximum depth at which children should be retrieved, defaults to 1, +// use -1 for the entire subtree or provide an integer larger than 0. +// The pierce decides whether or not iframes and shadow roots should be traversed when returning the subtree. +// The returned [proto.DOMNode.NodeID] will always be empty, +// because NodeID is not stable (when [proto.DOMDocumentUpdated] +// is fired all NodeID on the page will be reassigned to another value) +// we don't recommend using the NodeID, instead, use the [proto.DOMBackendNodeID] to identify the element. +func (el *Element) Describe(depth int, pierce bool) (*proto.DOMNode, error) { + val, err := proto.DOMDescribeNode{ObjectID: el.id(), Depth: gson.Int(depth), Pierce: pierce}.Call(el) + if err != nil { + return nil, err + } + return val.Node, nil +} + +// ShadowRoot returns the shadow root of this element. +func (el *Element) ShadowRoot() (*Element, error) { + node, err := el.Describe(1, false) + if err != nil { + return nil, err + } + + // though now it's an array, w3c changed the spec of it to be a single. + if len(node.ShadowRoots) == 0 { + return nil, &NoShadowRootError{el} + } + id := node.ShadowRoots[0].BackendNodeID + + shadowNode, err := proto.DOMResolveNode{BackendNodeID: id}.Call(el) + if err != nil { + return nil, err + } + + return el.page.Context(el.ctx).ElementFromObject(shadowNode.Object) +} + +// Frame creates a page instance that represents the iframe. +func (el *Element) Frame() (*Page, error) { + node, err := el.Describe(1, false) + if err != nil { + return nil, err + } + + clone := *el.page + clone.FrameID = node.FrameID + clone.jsCtxID = new(proto.RuntimeRemoteObjectID) + clone.element = el + clone.sleeper = el.sleeper + + return &clone, nil +} + +// ContainsElement check if the target is equal or inside the element. +func (el *Element) ContainsElement(target *Element) (bool, error) { + res, err := el.Evaluate(evalHelper(js.ContainsElement, target.Object)) + if err != nil { + return false, err + } + return res.Value.Bool(), nil +} + +// Text that the element displays. +func (el *Element) Text() (string, error) { + str, err := el.Evaluate(evalHelper(js.Text)) + if err != nil { + return "", err + } + return str.Value.String(), nil +} + +// HTML of the element. +func (el *Element) HTML() (string, error) { + res, err := proto.DOMGetOuterHTML{ObjectID: el.Object.ObjectID}.Call(el) + if err != nil { + return "", err + } + return res.OuterHTML, nil +} + +// Visible returns true if the element is visible on the page. +func (el *Element) Visible() (bool, error) { + res, err := el.Evaluate(evalHelper(js.Visible)) + if err != nil { + return false, err + } + return res.Value.Bool(), nil +} + +// WaitLoad for element like . +func (el *Element) WaitLoad() error { + defer el.tryTrace(TraceTypeWait, "load")() + _, err := el.Evaluate(evalHelper(js.WaitLoad).ByPromise()) + return err +} + +// WaitStable waits until no shape or position change for d duration. +// Be careful, d is not the max wait timeout, it's the least stable time. +// If you want to set a timeout you can use the [Element.Timeout] function. +func (el *Element) WaitStable(d time.Duration) error { + err := el.WaitVisible() + if err != nil { + return err + } + + defer el.tryTrace(TraceTypeWait, "stable")() + + shape, err := el.Shape() + if err != nil { + return err + } + + t := time.NewTicker(d) + defer t.Stop() + + for { + select { + case <-t.C: + case <-el.ctx.Done(): + return el.ctx.Err() + } + current, err := el.Shape() + if err != nil { + return err + } + if reflect.DeepEqual(shape, current) { + break + } + shape = current + } + return nil +} + +// WaitStableRAF waits until no shape or position change for 2 consecutive animation frames. +// If you want to wait animation that is triggered by JS not CSS, you'd better use [Element.WaitStable]. +// About animation frame: https://developer.mozilla.org/en-US/docs/Web/API/window/requestAnimationFrame +func (el *Element) WaitStableRAF() error { + err := el.WaitVisible() + if err != nil { + return err + } + + defer el.tryTrace(TraceTypeWait, "stable RAF")() + + var shape *proto.DOMGetContentQuadsResult + page := el.page.Context(el.ctx) + + for { + err = page.WaitRepaint() + if err != nil { + return err + } + + current, err := el.Shape() + if err != nil { + return err + } + if reflect.DeepEqual(shape, current) { + break + } + shape = current + } + return nil +} + +// WaitInteractable waits for the element to be interactable. +// It will try to scroll to the element on each try. +func (el *Element) WaitInteractable() (pt *proto.Point, err error) { + defer el.tryTrace(TraceTypeWait, "interactable")() + + err = utils.Retry(el.ctx, el.sleeper(), func() (bool, error) { + // For lazy loading page the element can be outside of the viewport. + // If we don't scroll to it, it will never be available. + err := el.ScrollIntoView() + if err != nil { + return true, err + } + + pt, err = el.Interactable() + if errors.Is(err, &CoveredError{}) { + return false, nil + } + return true, err + }) + return +} + +// Wait until the js returns true. +func (el *Element) Wait(opts *EvalOptions) error { + return el.page.Context(el.ctx).Sleeper(el.sleeper).Wait(opts.This(el.Object)) +} + +// WaitVisible until the element is visible. +func (el *Element) WaitVisible() error { + defer el.tryTrace(TraceTypeWait, "visible")() + return el.Wait(evalHelper(js.Visible)) +} + +// WaitEnabled until the element is not disabled. +// Doc for readonly: https://developer.mozilla.org/en-US/docs/Web/HTML/Attributes/readonly +func (el *Element) WaitEnabled() error { + defer el.tryTrace(TraceTypeWait, "enabled")() + return el.Wait(Eval(`() => !this.disabled`)) +} + +// WaitWritable until the element is not readonly. +// Doc for disabled: https://developer.mozilla.org/en-US/docs/Web/HTML/Attributes/disabled +func (el *Element) WaitWritable() error { + defer el.tryTrace(TraceTypeWait, "writable")() + return el.Wait(Eval(`() => !this.readonly`)) +} + +// WaitInvisible until the element invisible. +func (el *Element) WaitInvisible() error { + defer el.tryTrace(TraceTypeWait, "invisible")() + return el.Wait(evalHelper(js.Invisible)) +} + +// CanvasToImage get image data of a canvas. +// The default format is image/png. +// The default quality is 0.92. +// doc: https://developer.mozilla.org/en-US/docs/Web/API/HTMLCanvasElement/toDataURL +func (el *Element) CanvasToImage(format string, quality float64) ([]byte, error) { + res, err := el.Eval(`(format, quality) => this.toDataURL(format, quality)`, format, quality) + if err != nil { + return nil, err + } + + _, bin := parseDataURI(res.Value.Str()) + return bin, nil +} + +// Resource returns the "src" content of current element. Such as the jpg of . +func (el *Element) Resource() ([]byte, error) { + src, err := el.Evaluate(evalHelper(js.Resource).ByPromise()) + if err != nil { + return nil, err + } + + return el.page.Context(el.ctx).GetResource(src.Value.String()) +} + +// BackgroundImage returns the css background-image of the element. +func (el *Element) BackgroundImage() ([]byte, error) { + res, err := el.Eval(`() => window.getComputedStyle(this).backgroundImage.replace(/^url\("/, '').replace(/"\)$/, '')`) + if err != nil { + return nil, err + } + + u := res.Value.Str() + + return el.page.Context(el.ctx).GetResource(u) +} + +// Screenshot of the area of the element. +func (el *Element) Screenshot(format proto.PageCaptureScreenshotFormat, quality int) ([]byte, error) { + err := el.ScrollIntoView() + if err != nil { + return nil, err + } + + opts := &proto.PageCaptureScreenshot{ + Quality: gson.Int(quality), + Format: format, + } + + bin, err := el.page.Context(el.ctx).Screenshot(false, opts) + if err != nil { + return nil, err + } + + // so that it won't clip the css-transformed element + shape, err := el.Shape() + if err != nil { + return nil, err + } + + box := shape.Box() + + // TODO: proto.PageCaptureScreenshot has a Clip option, but it's buggy, so now we do in Go. + return utils.CropImage(bin, quality, + int(box.X), + int(box.Y), + int(box.Width), + int(box.Height), + ) +} + +// Release is a shortcut for [Page.Release] current element. +func (el *Element) Release() error { + return el.page.Context(el.ctx).Release(el.Object) +} + +// Remove the element from the page. +func (el *Element) Remove() error { + _, err := el.Eval(`() => this.remove()`) + if err != nil { + return err + } + return el.Release() +} + +// Call implements the [proto.Client]. +func (el *Element) Call(ctx context.Context, sessionID, methodName string, params interface{}) (res []byte, err error) { + return el.page.Call(ctx, sessionID, methodName, params) +} + +// Eval is a shortcut for [Element.Evaluate] with AwaitPromise, ByValue and AutoExp set to true. +func (el *Element) Eval(js string, params ...interface{}) (*proto.RuntimeRemoteObject, error) { + return el.Evaluate(Eval(js, params...).ByPromise()) +} + +// Evaluate is just a shortcut of [Page.Evaluate] with This set to current element. +func (el *Element) Evaluate(opts *EvalOptions) (*proto.RuntimeRemoteObject, error) { + return el.page.Context(el.ctx).Evaluate(opts.This(el.Object)) +} + +// Equal checks if the two elements are equal. +func (el *Element) Equal(elm *Element) (bool, error) { + res, err := el.Eval(`elm => this === elm`, elm.Object) + return res.Value.Bool(), err +} + +func (el *Element) id() proto.RuntimeRemoteObjectID { + return el.Object.ObjectID +} + +// GetXPath returns the xpath of the element. +func (el *Element) GetXPath(optimized bool) (string, error) { + str, err := el.Evaluate(evalHelper(js.GetXPath, optimized)) + if err != nil { + return "", err + } + return str.Value.String(), nil +} diff --git a/vendor/github.com/go-rod/rod/error.go b/vendor/github.com/go-rod/rod/error.go new file mode 100644 index 000000000..c6a43c06d --- /dev/null +++ b/vendor/github.com/go-rod/rod/error.go @@ -0,0 +1,193 @@ +package rod + +import ( + "context" + "fmt" + + "github.com/go-rod/rod/lib/proto" + "github.com/go-rod/rod/lib/utils" +) + +// TryError error. +type TryError struct { + Value interface{} + Stack string +} + +func (e *TryError) Error() string { + return fmt.Sprintf("error value: %#v\n%s", e.Value, e.Stack) +} + +// Is interface. +func (e *TryError) Is(err error) bool { _, ok := err.(*TryError); return ok } + +// Unwrap stdlib interface. +func (e *TryError) Unwrap() error { + if err, ok := e.Value.(error); ok { + return err + } + return fmt.Errorf("%v", e.Value) +} + +// ExpectElementError error. +type ExpectElementError struct { + *proto.RuntimeRemoteObject +} + +func (e *ExpectElementError) Error() string { + return fmt.Sprintf("expect js to return an element, but got: %s", utils.MustToJSON(e)) +} + +// Is interface. +func (e *ExpectElementError) Is(err error) bool { _, ok := err.(*ExpectElementError); return ok } + +// ExpectElementsError error. +type ExpectElementsError struct { + *proto.RuntimeRemoteObject +} + +func (e *ExpectElementsError) Error() string { + return fmt.Sprintf("expect js to return an array of elements, but got: %s", utils.MustToJSON(e)) +} + +// Is interface. +func (e *ExpectElementsError) Is(err error) bool { _, ok := err.(*ExpectElementsError); return ok } + +// ElementNotFoundError error. +type ElementNotFoundError struct{} + +func (e *ElementNotFoundError) Error() string { + return "cannot find element" +} + +// NotFoundSleeper returns ErrElementNotFound on the first call. +func NotFoundSleeper() utils.Sleeper { + return func(context.Context) error { + return &ElementNotFoundError{} + } +} + +// ObjectNotFoundError error. +type ObjectNotFoundError struct { + *proto.RuntimeRemoteObject +} + +func (e *ObjectNotFoundError) Error() string { + return fmt.Sprintf("cannot find object: %s", utils.MustToJSON(e)) +} + +// Is interface. +func (e *ObjectNotFoundError) Is(err error) bool { _, ok := err.(*ObjectNotFoundError); return ok } + +// EvalError error. +type EvalError struct { + *proto.RuntimeExceptionDetails +} + +func (e *EvalError) Error() string { + exp := e.Exception + return fmt.Sprintf("eval js error: %s %s", exp.Description, exp.Value) +} + +// Is interface. +func (e *EvalError) Is(err error) bool { _, ok := err.(*EvalError); return ok } + +// NavigationError error. +type NavigationError struct { + Reason string +} + +func (e *NavigationError) Error() string { + return "navigation failed: " + e.Reason +} + +// Is interface. +func (e *NavigationError) Is(err error) bool { _, ok := err.(*NavigationError); return ok } + +// PageCloseCanceledError error. +type PageCloseCanceledError struct{} + +func (e *PageCloseCanceledError) Error() string { + return "page close canceled" +} + +// NotInteractableError error. Check the doc of Element.Interactable for details. +type NotInteractableError struct{} + +func (e *NotInteractableError) Error() string { + return "element is not cursor interactable" +} + +// InvisibleShapeError error. +type InvisibleShapeError struct { + *Element +} + +// Error ... +func (e *InvisibleShapeError) Error() string { + return fmt.Sprintf("element has no visible shape or outside the viewport: %s", e.String()) +} + +// Is interface. +func (e *InvisibleShapeError) Is(err error) bool { _, ok := err.(*InvisibleShapeError); return ok } + +// Unwrap ... +func (e *InvisibleShapeError) Unwrap() error { + return &NotInteractableError{} +} + +// CoveredError error. +type CoveredError struct { + *Element +} + +// Error ... +func (e *CoveredError) Error() string { + return fmt.Sprintf("element covered by: %s", e.String()) +} + +// Unwrap ... +func (e *CoveredError) Unwrap() error { + return &NotInteractableError{} +} + +// Is interface. +func (e *CoveredError) Is(err error) bool { _, ok := err.(*CoveredError); return ok } + +// NoPointerEventsError error. +type NoPointerEventsError struct { + *Element +} + +// Error ... +func (e *NoPointerEventsError) Error() string { + return fmt.Sprintf("element's pointer-events is none: %s", e.String()) +} + +// Unwrap ... +func (e *NoPointerEventsError) Unwrap() error { + return &NotInteractableError{} +} + +// Is interface. +func (e *NoPointerEventsError) Is(err error) bool { _, ok := err.(*NoPointerEventsError); return ok } + +// PageNotFoundError error. +type PageNotFoundError struct{} + +func (e *PageNotFoundError) Error() string { + return "cannot find page" +} + +// NoShadowRootError error. +type NoShadowRootError struct { + *Element +} + +// Error ... +func (e *NoShadowRootError) Error() string { + return fmt.Sprintf("element has no shadow root: %s", e.String()) +} + +// Is interface. +func (e *NoShadowRootError) Is(err error) bool { _, ok := err.(*NoShadowRootError); return ok } diff --git a/vendor/github.com/go-rod/rod/go.work b/vendor/github.com/go-rod/rod/go.work new file mode 100644 index 000000000..a97f3b4dc --- /dev/null +++ b/vendor/github.com/go-rod/rod/go.work @@ -0,0 +1,8 @@ +go 1.22 + +use ( + . + ./lib/examples/custom-websocket + ./lib/examples/e2e-testing + ./lib/utils/check-issue +) diff --git a/vendor/github.com/go-rod/rod/go.work.sum b/vendor/github.com/go-rod/rod/go.work.sum new file mode 100644 index 000000000..c5ed75a69 --- /dev/null +++ b/vendor/github.com/go-rod/rod/go.work.sum @@ -0,0 +1,4 @@ +github.com/ysmood/fetchup v0.2.1 h1:n/NgIx92KOXFiKAhK3d+LlKpl8JuSjh5U27ULmHKtag= +github.com/ysmood/fetchup v0.2.1/go.mod h1:94ROLWpn5fmCD4LPlcZ+LOE/iE/kRTU3kL+0ue/V+Os= +github.com/ysmood/got v0.33.2 h1:mz0PaCMzR//YBtDDkDf6z0O09SfotXBHzw3zLrrS2sw= +github.com/ysmood/got v0.33.2/go.mod h1:P3C/Wwttv4uq/tpovaH+c8ANmHePyFPxEbNzdxcEGDU= diff --git a/vendor/github.com/go-rod/rod/hijack.go b/vendor/github.com/go-rod/rod/hijack.go new file mode 100644 index 000000000..477dd992d --- /dev/null +++ b/vendor/github.com/go-rod/rod/hijack.go @@ -0,0 +1,430 @@ +package rod + +import ( + "bytes" + "context" + "io" + "net/http" + "net/url" + "regexp" + "strings" + + "github.com/go-rod/rod/lib/proto" + "github.com/go-rod/rod/lib/utils" + "github.com/ysmood/gson" +) + +// HijackRequests same as Page.HijackRequests, but can intercept requests of the entire browser. +func (b *Browser) HijackRequests() *HijackRouter { + return newHijackRouter(b, b).initEvents() +} + +// HijackRequests creates a new router instance for requests hijacking. +// When use Fetch domain outside the router should be stopped. Enabling hijacking disables page caching, +// but such as 304 Not Modified will still work as expected. +// The entire process of hijacking one request: +// +// browser --req-> rod ---> server ---> rod --res-> browser +// +// The --req-> and --res-> are the parts that can be modified. +func (p *Page) HijackRequests() *HijackRouter { + return newHijackRouter(p.browser, p).initEvents() +} + +// HijackRouter context. +type HijackRouter struct { + run func() + stop func() + handlers []*hijackHandler + enable *proto.FetchEnable + client proto.Client + browser *Browser +} + +func newHijackRouter(browser *Browser, client proto.Client) *HijackRouter { + return &HijackRouter{ + enable: &proto.FetchEnable{}, + browser: browser, + client: client, + handlers: []*hijackHandler{}, + } +} + +func (r *HijackRouter) initEvents() *HijackRouter { //nolint: gocognit + ctx := r.browser.ctx + if cta, ok := r.client.(proto.Contextable); ok { + ctx = cta.GetContext() + } + + var sessionID proto.TargetSessionID + if tsa, ok := r.client.(proto.Sessionable); ok { + sessionID = tsa.GetSessionID() + } + + eventCtx, cancel := context.WithCancel(ctx) + r.stop = cancel + + _ = r.enable.Call(r.client) + + r.run = r.browser.Context(eventCtx).eachEvent(sessionID, func(e *proto.FetchRequestPaused) bool { + go func() { + ctx := r.new(eventCtx, e) + for _, h := range r.handlers { + if !h.regexp.MatchString(e.Request.URL) { + continue + } + + h.handler(ctx) + + if ctx.continueRequest != nil { + ctx.continueRequest.RequestID = e.RequestID + err := ctx.continueRequest.Call(r.client) + if err != nil { + ctx.OnError(err) + } + return + } + + if ctx.Skip { + continue + } + + if ctx.Response.fail.ErrorReason != "" { + err := ctx.Response.fail.Call(r.client) + if err != nil { + ctx.OnError(err) + } + return + } + + err := ctx.Response.payload.Call(r.client) + if err != nil { + ctx.OnError(err) + return + } + } + }() + + return false + }) + return r +} + +// Add a hijack handler to router, the doc of the pattern is the same as "proto.FetchRequestPattern.URLPattern". +func (r *HijackRouter) Add(pattern string, resourceType proto.NetworkResourceType, handler func(*Hijack)) error { + r.enable.Patterns = append(r.enable.Patterns, &proto.FetchRequestPattern{ + URLPattern: pattern, + ResourceType: resourceType, + }) + + reg := regexp.MustCompile(proto.PatternToReg(pattern)) + + r.handlers = append(r.handlers, &hijackHandler{ + pattern: pattern, + regexp: reg, + handler: handler, + }) + + return r.enable.Call(r.client) +} + +// Remove handler via the pattern. +func (r *HijackRouter) Remove(pattern string) error { + patterns := []*proto.FetchRequestPattern{} + handlers := []*hijackHandler{} + for _, h := range r.handlers { + if h.pattern != pattern { + patterns = append(patterns, &proto.FetchRequestPattern{URLPattern: h.pattern}) + handlers = append(handlers, h) + } + } + r.enable.Patterns = patterns + r.handlers = handlers + + return r.enable.Call(r.client) +} + +// new context. +func (r *HijackRouter) new(ctx context.Context, e *proto.FetchRequestPaused) *Hijack { + headers := http.Header{} + for k, v := range e.Request.Headers { + headers[k] = []string{v.String()} + } + + u, _ := url.Parse(e.Request.URL) + + req := &http.Request{ + Method: e.Request.Method, + URL: u, + Body: io.NopCloser(strings.NewReader(e.Request.PostData)), + Header: headers, + } + + return &Hijack{ + Request: &HijackRequest{ + event: e, + req: req.WithContext(ctx), + }, + Response: &HijackResponse{ + payload: &proto.FetchFulfillRequest{ + ResponseCode: 200, + RequestID: e.RequestID, + }, + fail: &proto.FetchFailRequest{ + RequestID: e.RequestID, + }, + }, + OnError: func(_ error) {}, + + browser: r.browser, + } +} + +// Run the router, after you call it, you shouldn't add new handler to it. +func (r *HijackRouter) Run() { + r.run() +} + +// Stop the router. +func (r *HijackRouter) Stop() error { + r.stop() + return proto.FetchDisable{}.Call(r.client) +} + +// hijackHandler to handle each request that match the regexp. +type hijackHandler struct { + pattern string + regexp *regexp.Regexp + handler func(*Hijack) +} + +// Hijack context. +type Hijack struct { + Request *HijackRequest + Response *HijackResponse + OnError func(error) + + // Skip to next handler + Skip bool + + continueRequest *proto.FetchContinueRequest + + // CustomState is used to store things for this context + CustomState interface{} + + browser *Browser +} + +// ContinueRequest without hijacking. The RequestID will be set by the router, you don't have to set it. +func (h *Hijack) ContinueRequest(cq *proto.FetchContinueRequest) { + h.continueRequest = cq +} + +// LoadResponse will send request to the real destination and load the response as default response to override. +func (h *Hijack) LoadResponse(client *http.Client, loadBody bool) error { + res, err := client.Do(h.Request.req) + if err != nil { + return err + } + + defer func() { _ = res.Body.Close() }() + + h.Response.payload.ResponseCode = res.StatusCode + h.Response.RawResponse = res + + for k, vs := range res.Header { + for _, v := range vs { + h.Response.SetHeader(k, v) + } + } + + if loadBody { + b, err := io.ReadAll(res.Body) + if err != nil { + return err + } + h.Response.payload.Body = b + } + + return nil +} + +// HijackRequest context. +type HijackRequest struct { + event *proto.FetchRequestPaused + req *http.Request +} + +// Type of the resource. +func (ctx *HijackRequest) Type() proto.NetworkResourceType { + return ctx.event.ResourceType +} + +// Method of the request. +func (ctx *HijackRequest) Method() string { + return ctx.event.Request.Method +} + +// URL of the request. +func (ctx *HijackRequest) URL() *url.URL { + u, _ := url.Parse(ctx.event.Request.URL) + return u +} + +// Header via a key. +func (ctx *HijackRequest) Header(key string) string { + return ctx.event.Request.Headers[key].String() +} + +// Headers of request. +func (ctx *HijackRequest) Headers() proto.NetworkHeaders { + return ctx.event.Request.Headers +} + +// Body of the request, devtools API doesn't support binary data yet, only string can be captured. +func (ctx *HijackRequest) Body() string { + return ctx.event.Request.PostData +} + +// JSONBody of the request. +func (ctx *HijackRequest) JSONBody() gson.JSON { + return gson.NewFrom(ctx.Body()) +} + +// Req returns the underlying http.Request instance that will be used to send the request. +func (ctx *HijackRequest) Req() *http.Request { + return ctx.req +} + +// SetContext of the underlying http.Request instance. +func (ctx *HijackRequest) SetContext(c context.Context) *HijackRequest { + ctx.req = ctx.req.WithContext(c) + return ctx +} + +// SetBody of the request, if obj is []byte or string, raw body will be used, else it will be encoded as json. +func (ctx *HijackRequest) SetBody(obj interface{}) *HijackRequest { + var b []byte + + switch body := obj.(type) { + case []byte: + b = body + case string: + b = []byte(body) + default: + b = utils.MustToJSONBytes(body) + } + + ctx.req.Body = io.NopCloser(bytes.NewBuffer(b)) + + return ctx +} + +// IsNavigation determines whether the request is a navigation request. +func (ctx *HijackRequest) IsNavigation() bool { + return ctx.Type() == proto.NetworkResourceTypeDocument +} + +// HijackResponse context. +type HijackResponse struct { + payload *proto.FetchFulfillRequest + RawResponse *http.Response + fail *proto.FetchFailRequest +} + +// Payload to respond the request from the browser. +func (ctx *HijackResponse) Payload() *proto.FetchFulfillRequest { + return ctx.payload +} + +// Body of the payload. +func (ctx *HijackResponse) Body() string { + return string(ctx.payload.Body) +} + +// Headers returns the clone of response headers. +// If you want to modify the response headers use HijackResponse.SetHeader . +func (ctx *HijackResponse) Headers() http.Header { + header := http.Header{} + + for _, h := range ctx.payload.ResponseHeaders { + header.Add(h.Name, h.Value) + } + + return header +} + +// SetHeader of the payload via key-value pairs. +func (ctx *HijackResponse) SetHeader(pairs ...string) *HijackResponse { + for i := 0; i < len(pairs); i += 2 { + ctx.payload.ResponseHeaders = append(ctx.payload.ResponseHeaders, &proto.FetchHeaderEntry{ + Name: pairs[i], + Value: pairs[i+1], + }) + } + return ctx +} + +// SetBody of the payload, if obj is []byte or string, raw body will be used, else it will be encoded as json. +func (ctx *HijackResponse) SetBody(obj interface{}) *HijackResponse { + switch body := obj.(type) { + case []byte: + ctx.payload.Body = body + case string: + ctx.payload.Body = []byte(body) + default: + ctx.payload.Body = utils.MustToJSONBytes(body) + } + return ctx +} + +// Fail request. +func (ctx *HijackResponse) Fail(reason proto.NetworkErrorReason) *HijackResponse { + ctx.fail.ErrorReason = reason + return ctx +} + +// HandleAuth for the next basic HTTP authentication. +// It will prevent the popup that requires user to input user name and password. +// Ref: https://developer.mozilla.org/en-US/docs/Web/HTTP/Authentication +func (b *Browser) HandleAuth(username, password string) func() error { + enable := b.DisableDomain("", &proto.FetchEnable{}) + disable := b.EnableDomain("", &proto.FetchEnable{ + HandleAuthRequests: true, + }) + + paused := &proto.FetchRequestPaused{} + auth := &proto.FetchAuthRequired{} + + ctx, cancel := context.WithCancel(b.ctx) + waitPaused := b.Context(ctx).WaitEvent(paused) + waitAuth := b.Context(ctx).WaitEvent(auth) + + return func() (err error) { + defer enable() + defer disable() + defer cancel() + + waitPaused() + + err = proto.FetchContinueRequest{ + RequestID: paused.RequestID, + }.Call(b) + if err != nil { + return + } + + waitAuth() + + err = proto.FetchContinueWithAuth{ + RequestID: auth.RequestID, + AuthChallengeResponse: &proto.FetchAuthChallengeResponse{ + Response: proto.FetchAuthChallengeResponseResponseProvideCredentials, + Username: username, + Password: password, + }, + }.Call(b) + + return + } +} diff --git a/vendor/github.com/go-rod/rod/input.go b/vendor/github.com/go-rod/rod/input.go new file mode 100644 index 000000000..c7d3f9fb5 --- /dev/null +++ b/vendor/github.com/go-rod/rod/input.go @@ -0,0 +1,457 @@ +package rod + +import ( + "fmt" + "sync" + + "github.com/go-rod/rod/lib/input" + "github.com/go-rod/rod/lib/proto" + "github.com/go-rod/rod/lib/utils" + "github.com/ysmood/gson" +) + +// Keyboard represents the keyboard on a page, it's always related the main frame. +type Keyboard struct { + sync.Mutex + + page *Page + + // pressed keys must be released before it can be pressed again + pressed map[input.Key]struct{} +} + +func (p *Page) newKeyboard() *Page { + p.Keyboard = &Keyboard{page: p, pressed: map[input.Key]struct{}{}} + return p +} + +func (k *Keyboard) getModifiers() int { + k.Lock() + defer k.Unlock() + return k.modifiers() +} + +func (k *Keyboard) modifiers() int { + ms := 0 + for key := range k.pressed { + ms |= key.Modifier() + } + return ms +} + +// Press the key down. +// To input characters that are not on the keyboard, such as Chinese or Japanese, you should +// use method like [Page.InsertText]. +func (k *Keyboard) Press(key input.Key) error { + defer k.page.tryTrace(TraceTypeInput, "press key: "+key.Info().Code)() + k.page.browser.trySlowMotion() + + k.Lock() + defer k.Unlock() + + k.pressed[key] = struct{}{} + + return key.Encode(proto.InputDispatchKeyEventTypeKeyDown, k.modifiers()).Call(k.page) +} + +// Release the key. +func (k *Keyboard) Release(key input.Key) error { + defer k.page.tryTrace(TraceTypeInput, "release key: "+key.Info().Code)() + + k.Lock() + defer k.Unlock() + + if _, has := k.pressed[key]; !has { + return nil + } + + delete(k.pressed, key) + + return key.Encode(proto.InputDispatchKeyEventTypeKeyUp, k.modifiers()).Call(k.page) +} + +// Type releases the key after the press. +func (k *Keyboard) Type(keys ...input.Key) (err error) { + for _, key := range keys { + err = k.Press(key) + if err != nil { + return + } + err = k.Release(key) + if err != nil { + return + } + } + return +} + +// KeyActionType enum. +type KeyActionType int + +// KeyActionTypes. +const ( + KeyActionPress KeyActionType = iota + KeyActionRelease + KeyActionTypeKey +) + +// KeyAction to perform. +type KeyAction struct { + Type KeyActionType + Key input.Key +} + +// KeyActions to simulate. +type KeyActions struct { + keyboard *Keyboard + + Actions []KeyAction +} + +// KeyActions simulates the type actions on a physical keyboard. +// Useful when input shortcuts like ctrl+enter . +func (p *Page) KeyActions() *KeyActions { + return &KeyActions{keyboard: p.Keyboard} +} + +// Press keys is guaranteed to have a release at the end of actions. +func (ka *KeyActions) Press(keys ...input.Key) *KeyActions { + for _, key := range keys { + ka.Actions = append(ka.Actions, KeyAction{KeyActionPress, key}) + } + return ka +} + +// Release keys. +func (ka *KeyActions) Release(keys ...input.Key) *KeyActions { + for _, key := range keys { + ka.Actions = append(ka.Actions, KeyAction{KeyActionRelease, key}) + } + return ka +} + +// Type will release the key immediately after the pressing. +func (ka *KeyActions) Type(keys ...input.Key) *KeyActions { + for _, key := range keys { + ka.Actions = append(ka.Actions, KeyAction{KeyActionTypeKey, key}) + } + return ka +} + +// Do the actions. +func (ka *KeyActions) Do() (err error) { + for _, a := range ka.balance() { + switch a.Type { + case KeyActionPress: + err = ka.keyboard.Press(a.Key) + case KeyActionRelease: + err = ka.keyboard.Release(a.Key) + case KeyActionTypeKey: + err = ka.keyboard.Type(a.Key) + } + if err != nil { + return + } + } + return +} + +// Make sure there's at least one release after the presses, such as: +// +// p1,p2,p1,r1 => p1,p2,p1,r1,r2 +func (ka *KeyActions) balance() []KeyAction { + actions := ka.Actions + + h := map[input.Key]bool{} + for _, a := range actions { + switch a.Type { + case KeyActionPress: + h[a.Key] = true + case KeyActionRelease, KeyActionTypeKey: + h[a.Key] = false + } + } + + for key, needRelease := range h { + if needRelease { + actions = append(actions, KeyAction{KeyActionRelease, key}) + } + } + + return actions +} + +// InsertText is like pasting text into the page. +func (p *Page) InsertText(text string) error { + defer p.tryTrace(TraceTypeInput, "insert text "+text)() + p.browser.trySlowMotion() + + err := proto.InputInsertText{Text: text}.Call(p) + return err +} + +// Mouse represents the mouse on a page, it's always related the main frame. +type Mouse struct { + sync.Mutex + + page *Page + + id string // mouse svg dom element id + + pos proto.Point + + // the buttons is currently being pressed, reflects the press order + buttons []proto.InputMouseButton +} + +func (p *Page) newMouse() *Page { + p.Mouse = &Mouse{page: p, id: utils.RandString(8)} + return p +} + +// Position of current cursor. +func (m *Mouse) Position() proto.Point { + m.Lock() + defer m.Unlock() + return m.pos +} + +// MoveTo the absolute position. +func (m *Mouse) MoveTo(p proto.Point) error { + m.Lock() + defer m.Unlock() + + button, buttons := input.EncodeMouseButton(m.buttons) + + m.page.browser.trySlowMotion() + + err := proto.InputDispatchMouseEvent{ + Type: proto.InputDispatchMouseEventTypeMouseMoved, + X: p.X, + Y: p.Y, + Button: button, + Buttons: gson.Int(buttons), + Modifiers: m.page.Keyboard.getModifiers(), + }.Call(m.page) + if err != nil { + return err + } + + // to make sure set only when call is successful + m.pos = p + + if m.page.browser.trace { + if !m.updateMouseTracer() { + m.initMouseTracer() + m.updateMouseTracer() + } + } + + return nil +} + +// MoveAlong the guide function. +// Every time the guide function is called it should return the next mouse position, return true to stop. +// Read the source code of [Mouse.MoveLinear] as an example to use this method. +func (m *Mouse) MoveAlong(guide func() (proto.Point, bool)) error { + for { + p, stop := guide() + if stop { + return m.MoveTo(p) + } + + err := m.MoveTo(p) + if err != nil { + return err + } + } +} + +// MoveLinear to the absolute position with the given steps. +// Such as move from (0,0) to (6,6) with 3 steps, the mouse will first move to (2,2) then (4,4) then (6,6). +func (m *Mouse) MoveLinear(to proto.Point, steps int) error { + p := m.Position() + step := to.Minus(p).Scale(1 / float64(steps)) + count := 0 + + return m.MoveAlong(func() (proto.Point, bool) { + count++ + if count == steps { + return to, true + } + + p = p.Add(step) + return p, false + }) +} + +// Scroll the relative offset with specified steps. +func (m *Mouse) Scroll(offsetX, offsetY float64, steps int) error { + m.Lock() + defer m.Unlock() + + defer m.page.tryTrace(TraceTypeInput, fmt.Sprintf("scroll (%.2f, %.2f)", offsetX, offsetY))() + m.page.browser.trySlowMotion() + + if steps < 1 { + steps = 1 + } + + button, buttons := input.EncodeMouseButton(m.buttons) + + stepX := offsetX / float64(steps) + stepY := offsetY / float64(steps) + + for i := 0; i < steps; i++ { + err := proto.InputDispatchMouseEvent{ + Type: proto.InputDispatchMouseEventTypeMouseWheel, + Button: button, + Buttons: gson.Int(buttons), + Modifiers: m.page.Keyboard.getModifiers(), + DeltaX: stepX, + DeltaY: stepY, + X: m.pos.X, + Y: m.pos.Y, + }.Call(m.page) + if err != nil { + return err + } + } + + return nil +} + +// Down holds the button down. +func (m *Mouse) Down(button proto.InputMouseButton, clickCount int) error { + m.Lock() + defer m.Unlock() + + toButtons := append(append([]proto.InputMouseButton{}, m.buttons...), button) + + _, buttons := input.EncodeMouseButton(toButtons) + + err := proto.InputDispatchMouseEvent{ + Type: proto.InputDispatchMouseEventTypeMousePressed, + Button: button, + Buttons: gson.Int(buttons), + ClickCount: clickCount, + Modifiers: m.page.Keyboard.getModifiers(), + X: m.pos.X, + Y: m.pos.Y, + }.Call(m.page) + if err != nil { + return err + } + m.buttons = toButtons + return nil +} + +// Up releases the button. +func (m *Mouse) Up(button proto.InputMouseButton, clickCount int) error { + m.Lock() + defer m.Unlock() + + toButtons := []proto.InputMouseButton{} + for _, btn := range m.buttons { + if btn == button { + continue + } + toButtons = append(toButtons, btn) + } + + _, buttons := input.EncodeMouseButton(toButtons) + + err := proto.InputDispatchMouseEvent{ + Type: proto.InputDispatchMouseEventTypeMouseReleased, + Button: button, + Buttons: gson.Int(buttons), + ClickCount: clickCount, + Modifiers: m.page.Keyboard.getModifiers(), + X: m.pos.X, + Y: m.pos.Y, + }.Call(m.page) + if err != nil { + return err + } + m.buttons = toButtons + return nil +} + +// Click the button. It's the combination of [Mouse.Down] and [Mouse.Up]. +func (m *Mouse) Click(button proto.InputMouseButton, clickCount int) error { + m.page.browser.trySlowMotion() + + err := m.Down(button, clickCount) + if err != nil { + return err + } + + return m.Up(button, clickCount) +} + +// Touch presents a touch device, such as a hand with fingers, each finger is a [proto.InputTouchPoint]. +// Touch events is stateless, we use the struct here only as a namespace to make the API style unified. +type Touch struct { + page *Page +} + +func (p *Page) newTouch() *Page { + p.Touch = &Touch{page: p} + return p +} + +// Start a touch action. +func (t *Touch) Start(points ...*proto.InputTouchPoint) error { + // TODO: https://crbug.com/613219 + _ = t.page.WaitRepaint() + _ = t.page.WaitRepaint() + + return proto.InputDispatchTouchEvent{ + Type: proto.InputDispatchTouchEventTypeTouchStart, + TouchPoints: points, + Modifiers: t.page.Keyboard.getModifiers(), + }.Call(t.page) +} + +// Move touch points. Use the [proto.InputTouchPoint.ID] (Touch.identifier) to track points. +// Doc: https://developer.mozilla.org/en-US/docs/Web/API/Touch_events +func (t *Touch) Move(points ...*proto.InputTouchPoint) error { + return proto.InputDispatchTouchEvent{ + Type: proto.InputDispatchTouchEventTypeTouchMove, + TouchPoints: points, + Modifiers: t.page.Keyboard.getModifiers(), + }.Call(t.page) +} + +// End touch action. +func (t *Touch) End() error { + return proto.InputDispatchTouchEvent{ + Type: proto.InputDispatchTouchEventTypeTouchEnd, + TouchPoints: []*proto.InputTouchPoint{}, + Modifiers: t.page.Keyboard.getModifiers(), + }.Call(t.page) +} + +// Cancel touch action. +func (t *Touch) Cancel() error { + return proto.InputDispatchTouchEvent{ + Type: proto.InputDispatchTouchEventTypeTouchCancel, + TouchPoints: []*proto.InputTouchPoint{}, + Modifiers: t.page.Keyboard.getModifiers(), + }.Call(t.page) +} + +// Tap dispatches a touchstart and touchend event. +func (t *Touch) Tap(x, y float64) error { + defer t.page.tryTrace(TraceTypeInput, "touch")() + t.page.browser.trySlowMotion() + + p := &proto.InputTouchPoint{X: x, Y: y} + + err := t.Start(p) + if err != nil { + return err + } + + return t.End() +} diff --git a/vendor/github.com/go-rod/rod/lib/assets/README.md b/vendor/github.com/go-rod/rod/lib/assets/README.md new file mode 100644 index 000000000..2948ee314 --- /dev/null +++ b/vendor/github.com/go-rod/rod/lib/assets/README.md @@ -0,0 +1,3 @@ +# Assets + +Static files for the project diff --git a/vendor/github.com/go-rod/rod/lib/assets/assets.go b/vendor/github.com/go-rod/rod/lib/assets/assets.go new file mode 100644 index 000000000..4dbb107f2 --- /dev/null +++ b/vendor/github.com/go-rod/rod/lib/assets/assets.go @@ -0,0 +1,189 @@ +// Package assets is generated by "lib/assets/generate" +package assets + +// MousePointer for rod. +const MousePointer = ` + + + mouse-pointer + Created with Sketch. + + + + + + + + + + + + + + + + + +` + +// Monitor for rod. +const Monitor = ` + + Rod Monitor - Pages + + + +

Choose a Page to Monitor

+ +
+ + + + +` + +// MonitorPage for rod. +const MonitorPage = ` + + + + + +

+    
+  
+  
+
+`
diff --git a/vendor/github.com/go-rod/rod/lib/assets/monitor-page.html b/vendor/github.com/go-rod/rod/lib/assets/monitor-page.html
new file mode 100644
index 000000000..facff0847
--- /dev/null
+++ b/vendor/github.com/go-rod/rod/lib/assets/monitor-page.html
@@ -0,0 +1,103 @@
+
+  
+    
+  
+  
+    
+    

+    
+  
+  
+
diff --git a/vendor/github.com/go-rod/rod/lib/assets/monitor.html b/vendor/github.com/go-rod/rod/lib/assets/monitor.html
new file mode 100644
index 000000000..226c8eb61
--- /dev/null
+++ b/vendor/github.com/go-rod/rod/lib/assets/monitor.html
@@ -0,0 +1,53 @@
+
+  
+    Rod Monitor - Pages
+    
+  
+  
+    

Choose a Page to Monitor

+ +
+ + + + diff --git a/vendor/github.com/go-rod/rod/lib/cdp/README.md b/vendor/github.com/go-rod/rod/lib/cdp/README.md new file mode 100644 index 000000000..ba2ea0db5 --- /dev/null +++ b/vendor/github.com/go-rod/rod/lib/cdp/README.md @@ -0,0 +1,11 @@ +# Overview + +This client is directly based on this [doc](https://chromedevtools.github.io/devtools-protocol/). + +You can treat it as a minimal example of how to use the DevTools Protocol, no complex abstraction. + +It's thread-safe, and context first. + +For basic usage, check this [file](example_test.go). + +For more info, check the unit tests. diff --git a/vendor/github.com/go-rod/rod/lib/cdp/client.go b/vendor/github.com/go-rod/rod/lib/cdp/client.go new file mode 100644 index 000000000..42f398946 --- /dev/null +++ b/vendor/github.com/go-rod/rod/lib/cdp/client.go @@ -0,0 +1,175 @@ +// Package cdp for application layer communication with browser. +package cdp + +import ( + "context" + "encoding/json" + "sync" + "sync/atomic" + + "github.com/go-rod/rod/lib/defaults" + "github.com/go-rod/rod/lib/utils" +) + +// Request to send to browser. +type Request struct { + ID int `json:"id"` + SessionID string `json:"sessionId,omitempty"` + Method string `json:"method"` + Params interface{} `json:"params,omitempty"` +} + +// Response from browser. +type Response struct { + ID int `json:"id"` + Result json.RawMessage `json:"result,omitempty"` + Error *Error `json:"error,omitempty"` +} + +// Event from browser. +type Event struct { + SessionID string `json:"sessionId,omitempty"` + Method string `json:"method"` + Params json.RawMessage `json:"params,omitempty"` +} + +// WebSocketable enables you to choose the websocket lib you want to use. +// Such as you can easily wrap gorilla/websocket and use it as the transport layer. +type WebSocketable interface { + // Send text message only + Send(data []byte) error + // Read returns text message only + Read() ([]byte, error) +} + +// Client is a devtools protocol connection instance. +type Client struct { + count uint64 + + ws WebSocketable + + pending sync.Map // pending requests + event chan *Event // events from browser + + logger utils.Logger +} + +// New creates a cdp connection, all messages from Client.Event must be received or they will block the client. +func New() *Client { + return &Client{ + event: make(chan *Event), + logger: defaults.CDP, + } +} + +// Logger sets the logger to log all the requests, responses, and events transferred between Rod and the browser. +// The default format for each type is in file format.go. +func (cdp *Client) Logger(l utils.Logger) *Client { + cdp.logger = l + return cdp +} + +// Start to browser. +func (cdp *Client) Start(ws WebSocketable) *Client { + cdp.ws = ws + + go cdp.consumeMessages() + + return cdp +} + +type result struct { + msg json.RawMessage + err error +} + +// Call a method and wait for its response. +func (cdp *Client) Call(ctx context.Context, sessionID, method string, params interface{}) ([]byte, error) { + req := &Request{ + ID: int(atomic.AddUint64(&cdp.count, 1)), + SessionID: sessionID, + Method: method, + Params: params, + } + + cdp.logger.Println(req) + + data, err := json.Marshal(req) + utils.E(err) + + done := make(chan result) + once := sync.Once{} + cdp.pending.Store(req.ID, func(res result) { + once.Do(func() { + select { + case <-ctx.Done(): + case done <- res: + } + }) + }) + defer cdp.pending.Delete(req.ID) + + err = cdp.ws.Send(data) + if err != nil { + return nil, err + } + + select { + case <-ctx.Done(): + return nil, ctx.Err() + case res := <-done: + return res.msg, res.err + } +} + +// Event returns a channel that will emit browser devtools protocol events. Must be consumed or will block producer. +func (cdp *Client) Event() <-chan *Event { + return cdp.event +} + +// Consume messages coming from the browser via the websocket. +func (cdp *Client) consumeMessages() { + defer close(cdp.event) + + for { + data, err := cdp.ws.Read() + if err != nil { + cdp.pending.Range(func(_, val interface{}) bool { + val.(func(result))(result{err: err}) //nolint: forcetypeassert + return true + }) + return + } + + var id struct { + ID int `json:"id"` + } + err = json.Unmarshal(data, &id) + utils.E(err) + + if id.ID == 0 { + var evt Event + err := json.Unmarshal(data, &evt) + utils.E(err) + cdp.logger.Println(&evt) + cdp.event <- &evt + continue + } + + var res Response + err = json.Unmarshal(data, &res) + utils.E(err) + + cdp.logger.Println(&res) + + val, ok := cdp.pending.Load(id.ID) + if !ok { + continue + } + if res.Error == nil { + val.(func(result))(result{res.Result, nil}) //nolint: forcetypeassert + } else { + val.(func(result))(result{nil, res.Error}) //nolint: forcetypeassert + } + } +} diff --git a/vendor/github.com/go-rod/rod/lib/cdp/error.go b/vendor/github.com/go-rod/rod/lib/cdp/error.go new file mode 100644 index 000000000..32cc522f0 --- /dev/null +++ b/vendor/github.com/go-rod/rod/lib/cdp/error.go @@ -0,0 +1,65 @@ +package cdp + +import ( + "fmt" +) + +// Error of the Response. +type Error struct { + Code int `json:"code"` + Message string `json:"message"` + Data string `json:"data"` +} + +// Error stdlib interface. +func (e *Error) Error() string { + return fmt.Sprintf("%v", *e) +} + +// Is stdlib interface. +func (e Error) Is(target error) bool { + err, ok := target.(*Error) + return ok && e == *err +} + +// ErrCtxNotFound type. +var ErrCtxNotFound = &Error{ + Code: -32000, + Message: "Cannot find context with specified id", +} + +// ErrSessionNotFound type. +var ErrSessionNotFound = &Error{ + Code: -32001, + Message: "Session with given id not found.", +} + +// ErrSearchSessionNotFound type. +var ErrSearchSessionNotFound = &Error{ + Code: -32000, + Message: "No search session with given id found", +} + +// ErrCtxDestroyed type. +var ErrCtxDestroyed = &Error{ + Code: -32000, + Message: "Execution context was destroyed.", +} + +// ErrObjNotFound type. +var ErrObjNotFound = &Error{ + Code: -32000, + Message: "Could not find object with given id", +} + +// ErrNodeNotFoundAtPos type. +var ErrNodeNotFoundAtPos = &Error{ + Code: -32000, + Message: "No node found at given location", +} + +// ErrNotAttachedToActivePage type. +var ErrNotAttachedToActivePage = &Error{ + Code: -32000, + Message: "Not attached to an active page", +} diff --git a/vendor/github.com/go-rod/rod/lib/cdp/format.go b/vendor/github.com/go-rod/rod/lib/cdp/format.go new file mode 100644 index 000000000..e9ca91dab --- /dev/null +++ b/vendor/github.com/go-rod/rod/lib/cdp/format.go @@ -0,0 +1,53 @@ +package cdp + +import ( + "fmt" + + "github.com/go-rod/rod/lib/utils" +) + +func (req Request) String() string { + return fmt.Sprintf( + "=> #%d %s %s %s", + req.ID, + fSessionID(req.SessionID), + req.Method, + dump(req.Params), + ) +} + +func (res Response) String() string { + if res.Error != nil { + return fmt.Sprintf( + "<= #%d error: %s", + res.ID, + dump(res.Error), + ) + } + return fmt.Sprintf( + "<= #%d %s", + res.ID, + dump(res.Result), + ) +} + +func (e Event) String() string { + return fmt.Sprintf( + "<- %s %s %s", + fSessionID(e.SessionID), + e.Method, + dump(e.Params), + ) +} + +func fSessionID(s string) string { + if s == "" { + s = "00000000" + } + s = s[:8] + return "@" + s +} + +func dump(v interface{}) string { + return utils.MustToJSON(v) +} diff --git a/vendor/github.com/go-rod/rod/lib/cdp/utils.go b/vendor/github.com/go-rod/rod/lib/cdp/utils.go new file mode 100644 index 000000000..ccdbea6b2 --- /dev/null +++ b/vendor/github.com/go-rod/rod/lib/cdp/utils.go @@ -0,0 +1,46 @@ +package cdp + +import ( + "context" + "crypto/tls" + "net" + "net/http" + + "github.com/go-rod/rod/lib/utils" +) + +// Dialer interface for WebSocket connection. +type Dialer interface { + DialContext(ctx context.Context, network, address string) (net.Conn, error) +} + +// TODO: replace it with tls.Dialer once golang v1.15 is widely used. +type tlsDialer struct{} + +func (d *tlsDialer) DialContext(_ context.Context, network, address string) (net.Conn, error) { + return tls.Dial(network, address, nil) +} + +// MustConnectWS helper to make a websocket connection. +func MustConnectWS(wsURL string) WebSocketable { + ws := &WebSocket{} + utils.E(ws.Connect(context.Background(), wsURL, nil)) + return ws +} + +// MustStartWithURL helper for ConnectURL. +func MustStartWithURL(ctx context.Context, u string, h http.Header) *Client { + c, err := StartWithURL(ctx, u, h) + utils.E(err) + return c +} + +// StartWithURL helper to connect to the u with the default websocket lib. +func StartWithURL(ctx context.Context, u string, h http.Header) (*Client, error) { + ws := &WebSocket{} + err := ws.Connect(ctx, u, h) + if err != nil { + return nil, err + } + return New().Start(ws), nil +} diff --git a/vendor/github.com/go-rod/rod/lib/cdp/websocket.go b/vendor/github.com/go-rod/rod/lib/cdp/websocket.go new file mode 100644 index 000000000..05454bf74 --- /dev/null +++ b/vendor/github.com/go-rod/rod/lib/cdp/websocket.go @@ -0,0 +1,238 @@ +package cdp + +import ( + "bufio" + "context" + "crypto/sha1" + "encoding/base64" + "fmt" + "io" + "net" + "net/http" + "net/url" + "sync" +) + +var _ WebSocketable = &WebSocket{} + +// WebSocket client for chromium. It only implements a subset of WebSocket protocol. +// Both the Read and Write are thread-safe. +// Limitation: https://bugs.chromium.org/p/chromium/issues/detail?id=1069431 +// Ref: https://tools.ietf.org/html/rfc6455 +type WebSocket struct { + // Dialer is usually used for proxy + Dialer Dialer + + lock sync.Mutex + conn net.Conn + r *bufio.Reader +} + +// Connect to browser. +func (ws *WebSocket) Connect(ctx context.Context, wsURL string, header http.Header) error { + if ws.conn != nil { + panic("duplicated connection: " + wsURL) + } + + u, err := url.Parse(wsURL) + if err != nil { + return err + } + + ws.initDialer(u) + + conn, err := ws.Dialer.DialContext(ctx, "tcp", u.Host) + if err != nil { + return err + } + + ws.conn = conn + ws.r = bufio.NewReader(conn) + return ws.handshake(ctx, u, header) +} + +// Close the underlying connection. +func (ws *WebSocket) Close() error { + return ws.conn.Close() +} + +func (ws *WebSocket) initDialer(u *url.URL) { + if ws.Dialer != nil { + return + } + + if u.Scheme == "wss" { + ws.Dialer = &tlsDialer{} + if u.Port() == "" { + u.Host += ":443" + } + } else { + ws.Dialer = &net.Dialer{} + } +} + +// Send a message to browser. +// Because we use zero-copy design, it will modify the content of the msg. +// It won't allocate new memory. +func (ws *WebSocket) Send(msg []byte) error { + err := ws.send(msg) + if err != nil { + _ = ws.Close() + } + return err +} + +func (ws *WebSocket) send(msg []byte) error { + // FIN is alway true, Opcode is always text frame. + header := [18]byte{0b1000_0001, 0b1000_0000} + mask := []byte{0, 1, 2, 3} + + size := len(msg) + fieldLen := 0 + switch { + case size <= 125: + header[1] |= byte(size) + case size < 65536: + header[1] |= 126 + fieldLen = 2 + default: + header[1] |= 127 + fieldLen = 8 + } + + var i int + for i = 0; i < fieldLen; i++ { + digit := (fieldLen - i - 1) * 8 + header[i+2] = byte((size >> digit) & 0xff) + } + + copy(header[i+2:], mask) + + for i := range msg { + msg[i] ^= mask[i%4] + } + + data := make([]byte, i+6+len(msg)) + copy(data, header[:i+6]) + copy(data[i+6:], msg) + + _, err := ws.conn.Write(data) + return err +} + +// Read a message from browser. +func (ws *WebSocket) Read() ([]byte, error) { + b, err := ws.read() + if err != nil { + _ = ws.Close() + return nil, err + } + return b, nil +} + +func (ws *WebSocket) read() ([]byte, error) { + ws.lock.Lock() + defer ws.lock.Unlock() + + _, err := ws.r.ReadByte() + if err != nil { + return nil, err + } + + b, err := ws.r.ReadByte() + if err != nil { + return nil, err + } + + size := 0 + fieldLen := 0 + + b &= 0x7f + switch { + case b <= 125: + size = int(b) + case b == 126: + fieldLen = 2 + case b == 127: + fieldLen = 8 + } + + for i := 0; i < fieldLen; i++ { + b, err := ws.r.ReadByte() + if err != nil { + return nil, err + } + + size = size<<8 + int(b) + } + + data := make([]byte, size) + _, err = io.ReadFull(ws.r, data) + return data, err +} + +// BadHandshakeError type. +type BadHandshakeError struct { + Status string + Body string +} + +func (e *BadHandshakeError) Error() string { + return fmt.Sprintf( + "websocket bad handshake: %s. %s", + e.Status, e.Body, + ) +} + +func verifyWebSocketAccept(responseHeaders http.Header, websocketKey string) bool { + expectedKey := websocketKey + "258EAFA5-E914-47DA-95CA-C5AB0DC85B11" + hash := sha1.New() + hash.Write([]byte(expectedKey)) + expectedAccept := base64.StdEncoding.EncodeToString(hash.Sum(nil)) + + return responseHeaders.Get("Sec-WebSocket-Accept") == expectedAccept +} + +func (ws *WebSocket) handshake(ctx context.Context, u *url.URL, header http.Header) error { + defaultSecKey := "nil" + req := (&http.Request{Method: http.MethodGet, URL: u, Header: http.Header{ + "Upgrade": {"websocket"}, + "Connection": {"Upgrade"}, + "Sec-WebSocket-Key": {defaultSecKey}, + "Sec-WebSocket-Version": {"13"}, + }}).WithContext(ctx) + + secKey := defaultSecKey + for k, vs := range header { + switch { + case k == "Host" && len(vs) > 0: + req.Host = vs[0] + case k == "Sec-WebSocket-Key" && len(vs) > 0: + secKey = vs[0] + req.Header[k] = vs + default: + req.Header[k] = vs + } + } + + err := req.Write(ws.conn) + if err != nil { + return err + } + + res, err := http.ReadResponse(ws.r, req) + if err != nil { + return err + } + defer func() { _ = res.Body.Close() }() + + if res.StatusCode != http.StatusSwitchingProtocols || !verifyWebSocketAccept(res.Header, secKey) { + body, _ := io.ReadAll(res.Body) + return &BadHandshakeError{ + Status: res.Status, + Body: string(body), + } + } + + return nil +} diff --git a/vendor/github.com/go-rod/rod/lib/defaults/defaults.go b/vendor/github.com/go-rod/rod/lib/defaults/defaults.go new file mode 100644 index 000000000..5474b4cf3 --- /dev/null +++ b/vendor/github.com/go-rod/rod/lib/defaults/defaults.go @@ -0,0 +1,203 @@ +// Package defaults of commonly used options parsed from environment. +// Check ResetWith for details. +package defaults + +import ( + "flag" + "log" + "os" + "regexp" + "strconv" + "strings" + "time" + + "github.com/go-rod/rod/lib/utils" +) + +// Trace is the default of rod.Browser.Trace . +// Option name is "trace". +var Trace bool + +// Slow is the default of rod.Browser.SlowMotion . +// The format is same as https://golang.org/pkg/time/#ParseDuration +// Option name is "slow". +var Slow time.Duration + +// Monitor is the default of rod.Browser.ServeMonitor . +// Option name is "monitor". +var Monitor string + +// Show is the default of launcher.Launcher.Headless . +// Option name is "show". +var Show bool + +// Devtools is the default of launcher.Launcher.Devtools . +// Option name is "devtools". +var Devtools bool + +// Dir is the default of launcher.Launcher.UserDataDir . +// Option name is "dir". +var Dir string + +// Port is the default of launcher.Launcher.RemoteDebuggingPort . +// Option name is "port". +var Port string + +// Bin is the default of launcher.Launcher.Bin . +// Option name is "bin". +var Bin string + +// Proxy is the default of launcher.Launcher.Proxy +// Option name is "proxy". +var Proxy string + +// LockPort is the default of launcher.Browser.LockPort +// Option name is "lock". +var LockPort int + +// URL is the default websocket url for remote control a browser. +// Option name is "url". +var URL string + +// CDP is the default of cdp.Client.Logger +// Option name is "cdp". +var CDP utils.Logger + +// Reset all flags to their init values. +func Reset() { + Trace = false + Slow = 0 + Monitor = "" + Show = false + Devtools = false + Dir = "" + Port = "0" + Bin = "" + Proxy = "" + LockPort = 2978 + URL = "" + CDP = utils.LoggerQuiet +} + +var envParsers = map[string]func(string){ + "trace": func(string) { + Trace = true + }, + "slow": func(v string) { + var err error + Slow, err = time.ParseDuration(v) + if err != nil { + msg := "invalid value for \"slow\": " + err.Error() + + " (learn format from https://golang.org/pkg/time/#ParseDuration)" + panic(msg) + } + }, + "monitor": func(v string) { + Monitor = ":0" + if v != "" { + Monitor = v + } + }, + "show": func(string) { + Show = true + }, + "devtools": func(string) { + Devtools = true + }, + "dir": func(v string) { + Dir = v + }, + "port": func(v string) { + Port = v + }, + "bin": func(v string) { + Bin = v + }, + "proxy": func(v string) { + Proxy = v + }, + "lock": func(v string) { + i, err := strconv.ParseInt(v, 10, 32) + if err == nil { + LockPort = int(i) + } + }, + "url": func(v string) { + URL = v + }, + "cdp": func(_ string) { + CDP = log.New(log.Writer(), "[cdp] ", log.LstdFlags) + }, +} + +// Parse the flags. +func init() { + ResetWith("") +} + +// ResetWith options and "-rod" command line flag. +// It will be called in an init() , so you don't have to call it manually. +// It will try to load the cli flag "-rod" and then the options, the later override the former. +// If you want to disable the global cli argument flag, set env DISABLE_ROD_FLAG. +// Values are separated by commas, key and value are separated by "=". For example: +// +// go run main.go -rod=show +// go run main.go -rod show,trace,slow=1s,monitor +// go run main.go --rod="slow=1s,dir=path/has /space,monitor=:9223" +func ResetWith(options string) { + Reset() + + if _, has := os.LookupEnv("DISABLE_ROD_FLAG"); !has { + if !flag.Parsed() && flag.Lookup("rod") == nil { + flag.String("rod", "", `Set the default value of options used by rod.`) + } + + parseFlag(os.Args) + } + + parse(options) +} + +func parseFlag(args []string) { + reg := regexp.MustCompile(`^--?rod$`) + regEq := regexp.MustCompile(`^--?rod=(.*)$`) + opts := "" + for i, arg := range args { + if reg.MatchString(arg) && i+1 < len(args) { + opts = args[i+1] + } else if m := regEq.FindStringSubmatch(arg); len(m) == 2 { + opts = m[1] + } + } + + parse(opts) +} + +// parse options and set them globally. +func parse(options string) { + if options == "" { + return + } + + reg := regexp.MustCompile(`[,\r\n]`) + + for _, str := range reg.Split(options, -1) { + kv := strings.SplitN(str, "=", 2) + + v := "" + if len(kv) == 2 { + v = kv[1] + } + + n := strings.TrimSpace(kv[0]) + if n == "" { + continue + } + + f := envParsers[n] + if f == nil { + panic("unknown rod env option: " + n) + } + f(v) + } +} diff --git a/vendor/github.com/go-rod/rod/lib/devices/device.go b/vendor/github.com/go-rod/rod/lib/devices/device.go new file mode 100644 index 000000000..5f6f21fca --- /dev/null +++ b/vendor/github.com/go-rod/rod/lib/devices/device.go @@ -0,0 +1,101 @@ +// Package devices ... +package devices + +import ( + "github.com/go-rod/rod/lib/proto" + "github.com/ysmood/gson" +) + +// Device represents a emulated device. +type Device struct { + Capabilities []string + UserAgent string + AcceptLanguage string + Screen Screen + Title string + + landscape bool + clear bool +} + +// Screen represents the screen of a device. +type Screen struct { + DevicePixelRatio float64 + Horizontal ScreenSize + Vertical ScreenSize +} + +// ScreenSize represents the size of the screen. +type ScreenSize struct { + Width int + Height int +} + +// Landscape clones the device and set it to landscape mode. +func (device Device) Landscape() Device { + d := device + d.landscape = true + return d +} + +// MetricsEmulation config. +func (device Device) MetricsEmulation() *proto.EmulationSetDeviceMetricsOverride { + if device.IsClear() { + return nil + } + + var screen ScreenSize + var orientation *proto.EmulationScreenOrientation + if device.landscape { + screen = device.Screen.Horizontal + orientation = &proto.EmulationScreenOrientation{ + Angle: 90, + Type: proto.EmulationScreenOrientationTypeLandscapePrimary, + } + } else { + screen = device.Screen.Vertical + orientation = &proto.EmulationScreenOrientation{ + Angle: 0, + Type: proto.EmulationScreenOrientationTypePortraitPrimary, + } + } + + return &proto.EmulationSetDeviceMetricsOverride{ + Width: screen.Width, + Height: screen.Height, + DeviceScaleFactor: device.Screen.DevicePixelRatio, + ScreenOrientation: orientation, + Mobile: has(device.Capabilities, "mobile"), + } +} + +// TouchEmulation config. +func (device Device) TouchEmulation() *proto.EmulationSetTouchEmulationEnabled { + if device.IsClear() { + return &proto.EmulationSetTouchEmulationEnabled{ + Enabled: false, + } + } + + return &proto.EmulationSetTouchEmulationEnabled{ + Enabled: has(device.Capabilities, "touch"), + MaxTouchPoints: gson.Int(5), + } +} + +// UserAgentEmulation config. +func (device Device) UserAgentEmulation() *proto.NetworkSetUserAgentOverride { + if device.IsClear() { + return nil + } + + return &proto.NetworkSetUserAgentOverride{ + UserAgent: device.UserAgent, + AcceptLanguage: device.AcceptLanguage, + } +} + +// IsClear type. +func (device Device) IsClear() bool { + return device.clear +} diff --git a/vendor/github.com/go-rod/rod/lib/devices/list.go b/vendor/github.com/go-rod/rod/lib/devices/list.go new file mode 100644 index 000000000..eb7c17731 --- /dev/null +++ b/vendor/github.com/go-rod/rod/lib/devices/list.go @@ -0,0 +1,690 @@ +// generated by "lib/devices/generate" + +package devices + +var ( + + // IPhone4 device. + IPhone4 = Device{ + Title: "iPhone 4", + Capabilities: []string{"touch", "mobile"}, + UserAgent: "Mozilla/5.0 (iPhone; CPU iPhone OS 7_1_2 like Mac OS X) AppleWebKit/537.51.2 (KHTML, like Gecko) Version/7.0 Mobile/11D257 Safari/9537.53", + AcceptLanguage: "en", + Screen: Screen{ + DevicePixelRatio: 2, + Horizontal: ScreenSize{ + Width: 480, + Height: 320, + }, + Vertical: ScreenSize{ + Width: 320, + Height: 480, + }, + }, + } + + // IPhone5orSE device. + IPhone5orSE = Device{ + Title: "iPhone 5/SE", + Capabilities: []string{"touch", "mobile"}, + UserAgent: "Mozilla/5.0 (iPhone; CPU iPhone OS 10_3_1 like Mac OS X) AppleWebKit/603.1.30 (KHTML, like Gecko) Version/10.0 Mobile/14E304 Safari/602.1", + AcceptLanguage: "en", + Screen: Screen{ + DevicePixelRatio: 2, + Horizontal: ScreenSize{ + Width: 568, + Height: 320, + }, + Vertical: ScreenSize{ + Width: 320, + Height: 568, + }, + }, + } + + // IPhone6or7or8 device. + IPhone6or7or8 = Device{ + Title: "iPhone 6/7/8", + Capabilities: []string{"touch", "mobile"}, + UserAgent: "Mozilla/5.0 (iPhone; CPU iPhone OS 13_2_3 like Mac OS X) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/13.0.3 Mobile/15E148 Safari/604.1", + AcceptLanguage: "en", + Screen: Screen{ + DevicePixelRatio: 2, + Horizontal: ScreenSize{ + Width: 667, + Height: 375, + }, + Vertical: ScreenSize{ + Width: 375, + Height: 667, + }, + }, + } + + // IPhone6or7or8Plus device. + IPhone6or7or8Plus = Device{ + Title: "iPhone 6/7/8 Plus", + Capabilities: []string{"touch", "mobile"}, + UserAgent: "Mozilla/5.0 (iPhone; CPU iPhone OS 13_2_3 like Mac OS X) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/13.0.3 Mobile/15E148 Safari/604.1", + AcceptLanguage: "en", + Screen: Screen{ + DevicePixelRatio: 3, + Horizontal: ScreenSize{ + Width: 736, + Height: 414, + }, + Vertical: ScreenSize{ + Width: 414, + Height: 736, + }, + }, + } + + // IPhoneX device. + IPhoneX = Device{ + Title: "iPhone X", + Capabilities: []string{"touch", "mobile"}, + UserAgent: "Mozilla/5.0 (iPhone; CPU iPhone OS 13_2_3 like Mac OS X) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/13.0.3 Mobile/15E148 Safari/604.1", + AcceptLanguage: "en", + Screen: Screen{ + DevicePixelRatio: 3, + Horizontal: ScreenSize{ + Width: 812, + Height: 375, + }, + Vertical: ScreenSize{ + Width: 375, + Height: 812, + }, + }, + } + + // BlackBerryZ30 device. + BlackBerryZ30 = Device{ + Title: "BlackBerry Z30", + Capabilities: []string{"touch", "mobile"}, + UserAgent: "Mozilla/5.0 (BB10; Touch) AppleWebKit/537.10+ (KHTML, like Gecko) Version/10.0.9.2372 Mobile Safari/537.10+", + AcceptLanguage: "en", + Screen: Screen{ + DevicePixelRatio: 2, + Horizontal: ScreenSize{ + Width: 640, + Height: 360, + }, + Vertical: ScreenSize{ + Width: 360, + Height: 640, + }, + }, + } + + // Nexus4 device. + Nexus4 = Device{ + Title: "Nexus 4", + Capabilities: []string{"touch", "mobile"}, + UserAgent: "Mozilla/5.0 (Linux; Android 4.4.2; Nexus 4 Build/KOT49H) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/114.0.0.0 Mobile Safari/537.36", + AcceptLanguage: "en", + Screen: Screen{ + DevicePixelRatio: 2, + Horizontal: ScreenSize{ + Width: 640, + Height: 384, + }, + Vertical: ScreenSize{ + Width: 384, + Height: 640, + }, + }, + } + + // Nexus5 device. + Nexus5 = Device{ + Title: "Nexus 5", + Capabilities: []string{"touch", "mobile"}, + UserAgent: "Mozilla/5.0 (Linux; Android 6.0; Nexus 5 Build/MRA58N) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/114.0.0.0 Mobile Safari/537.36", + AcceptLanguage: "en", + Screen: Screen{ + DevicePixelRatio: 3, + Horizontal: ScreenSize{ + Width: 640, + Height: 360, + }, + Vertical: ScreenSize{ + Width: 360, + Height: 640, + }, + }, + } + + // Nexus5X device. + Nexus5X = Device{ + Title: "Nexus 5X", + Capabilities: []string{"touch", "mobile"}, + UserAgent: "Mozilla/5.0 (Linux; Android 8.0.0; Nexus 5X Build/OPR4.170623.006) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/114.0.0.0 Mobile Safari/537.36", + AcceptLanguage: "en", + Screen: Screen{ + DevicePixelRatio: 2, + Horizontal: ScreenSize{ + Width: 732, + Height: 412, + }, + Vertical: ScreenSize{ + Width: 412, + Height: 732, + }, + }, + } + + // Nexus6 device. + Nexus6 = Device{ + Title: "Nexus 6", + Capabilities: []string{"touch", "mobile"}, + UserAgent: "Mozilla/5.0 (Linux; Android 7.1.1; Nexus 6 Build/N6F26U) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/114.0.0.0 Mobile Safari/537.36", + AcceptLanguage: "en", + Screen: Screen{ + DevicePixelRatio: 3, + Horizontal: ScreenSize{ + Width: 732, + Height: 412, + }, + Vertical: ScreenSize{ + Width: 412, + Height: 732, + }, + }, + } + + // Nexus6P device. + Nexus6P = Device{ + Title: "Nexus 6P", + Capabilities: []string{"touch", "mobile"}, + UserAgent: "Mozilla/5.0 (Linux; Android 8.0.0; Nexus 6P Build/OPP3.170518.006) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/114.0.0.0 Mobile Safari/537.36", + AcceptLanguage: "en", + Screen: Screen{ + DevicePixelRatio: 3, + Horizontal: ScreenSize{ + Width: 732, + Height: 412, + }, + Vertical: ScreenSize{ + Width: 412, + Height: 732, + }, + }, + } + + // Pixel2 device. + Pixel2 = Device{ + Title: "Pixel 2", + Capabilities: []string{"touch", "mobile"}, + UserAgent: "Mozilla/5.0 (Linux; Android 8.0; Pixel 2 Build/OPD3.170816.012) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/114.0.0.0 Mobile Safari/537.36", + AcceptLanguage: "en", + Screen: Screen{ + DevicePixelRatio: 2, + Horizontal: ScreenSize{ + Width: 731, + Height: 411, + }, + Vertical: ScreenSize{ + Width: 411, + Height: 731, + }, + }, + } + + // Pixel2XL device. + Pixel2XL = Device{ + Title: "Pixel 2 XL", + Capabilities: []string{"touch", "mobile"}, + UserAgent: "Mozilla/5.0 (Linux; Android 8.0.0; Pixel 2 XL Build/OPD1.170816.004) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/114.0.0.0 Mobile Safari/537.36", + AcceptLanguage: "en", + Screen: Screen{ + DevicePixelRatio: 3, + Horizontal: ScreenSize{ + Width: 823, + Height: 411, + }, + Vertical: ScreenSize{ + Width: 411, + Height: 823, + }, + }, + } + + // LGOptimusL70 device. + LGOptimusL70 = Device{ + Title: "LG Optimus L70", + Capabilities: []string{"touch", "mobile"}, + UserAgent: "Mozilla/5.0 (Linux; U; Android 4.4.2; en-us; LGMS323 Build/KOT49I.MS32310c) AppleWebKit/537.36 (KHTML, like Gecko) Version/4.0 Chrome/114.0.0.0 Mobile Safari/537.36", + AcceptLanguage: "en", + Screen: Screen{ + DevicePixelRatio: 1, + Horizontal: ScreenSize{ + Width: 640, + Height: 384, + }, + Vertical: ScreenSize{ + Width: 384, + Height: 640, + }, + }, + } + + // NokiaN9 device. + NokiaN9 = Device{ + Title: "Nokia N9", + Capabilities: []string{"touch", "mobile"}, + UserAgent: "Mozilla/5.0 (MeeGo; NokiaN9) AppleWebKit/534.13 (KHTML, like Gecko) NokiaBrowser/8.5.0 Mobile Safari/534.13", + AcceptLanguage: "en", + Screen: Screen{ + DevicePixelRatio: 1, + Horizontal: ScreenSize{ + Width: 854, + Height: 480, + }, + Vertical: ScreenSize{ + Width: 480, + Height: 854, + }, + }, + } + + // NokiaLumia520 device. + NokiaLumia520 = Device{ + Title: "Nokia Lumia 520", + Capabilities: []string{"touch", "mobile"}, + UserAgent: "Mozilla/5.0 (compatible; MSIE 10.0; Windows Phone 8.0; Trident/6.0; IEMobile/10.0; ARM; Touch; NOKIA; Lumia 520)", + AcceptLanguage: "en", + Screen: Screen{ + DevicePixelRatio: 1, + Horizontal: ScreenSize{ + Width: 533, + Height: 320, + }, + Vertical: ScreenSize{ + Width: 320, + Height: 533, + }, + }, + } + + // MicrosoftLumia550 device. + MicrosoftLumia550 = Device{ + Title: "Microsoft Lumia 550", + Capabilities: []string{"touch", "mobile"}, + UserAgent: "Mozilla/5.0 (Windows Phone 10.0; Android 4.2.1; Microsoft; Lumia 550) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/46.0.2486.0 Mobile Safari/537.36 Edge/14.14263", + AcceptLanguage: "en", + Screen: Screen{ + DevicePixelRatio: 2, + Horizontal: ScreenSize{ + Width: 640, + Height: 360, + }, + Vertical: ScreenSize{ + Width: 640, + Height: 360, + }, + }, + } + + // MicrosoftLumia950 device. + MicrosoftLumia950 = Device{ + Title: "Microsoft Lumia 950", + Capabilities: []string{"touch", "mobile"}, + UserAgent: "Mozilla/5.0 (Windows Phone 10.0; Android 4.2.1; Microsoft; Lumia 950) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/46.0.2486.0 Mobile Safari/537.36 Edge/14.14263", + AcceptLanguage: "en", + Screen: Screen{ + DevicePixelRatio: 4, + Horizontal: ScreenSize{ + Width: 640, + Height: 360, + }, + Vertical: ScreenSize{ + Width: 360, + Height: 640, + }, + }, + } + + // GalaxySIII device. + GalaxySIII = Device{ + Title: "Galaxy S III", + Capabilities: []string{"touch", "mobile"}, + UserAgent: "Mozilla/5.0 (Linux; U; Android 4.0; en-us; GT-I9300 Build/IMM76D) AppleWebKit/534.30 (KHTML, like Gecko) Version/4.0 Mobile Safari/534.30", + AcceptLanguage: "en", + Screen: Screen{ + DevicePixelRatio: 2, + Horizontal: ScreenSize{ + Width: 640, + Height: 360, + }, + Vertical: ScreenSize{ + Width: 360, + Height: 640, + }, + }, + } + + // GalaxyS5 device. + GalaxyS5 = Device{ + Title: "Galaxy S5", + Capabilities: []string{"touch", "mobile"}, + UserAgent: "Mozilla/5.0 (Linux; Android 5.0; SM-G900P Build/LRX21T) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/114.0.0.0 Mobile Safari/537.36", + AcceptLanguage: "en", + Screen: Screen{ + DevicePixelRatio: 3, + Horizontal: ScreenSize{ + Width: 640, + Height: 360, + }, + Vertical: ScreenSize{ + Width: 360, + Height: 640, + }, + }, + } + + // JioPhone2 device. + JioPhone2 = Device{ + Title: "JioPhone 2", + Capabilities: []string{"touch", "mobile"}, + UserAgent: "Mozilla/5.0 (Mobile; LYF/F300B/LYF-F300B-001-01-15-130718-i;Android; rv:48.0) Gecko/48.0 Firefox/48.0 KAIOS/2.5", + AcceptLanguage: "en", + Screen: Screen{ + DevicePixelRatio: 1, + Horizontal: ScreenSize{ + Width: 320, + Height: 240, + }, + Vertical: ScreenSize{ + Width: 240, + Height: 320, + }, + }, + } + + // KindleFireHDX device. + KindleFireHDX = Device{ + Title: "Kindle Fire HDX", + Capabilities: []string{"touch", "mobile"}, + UserAgent: "Mozilla/5.0 (Linux; U; en-us; KFAPWI Build/JDQ39) AppleWebKit/535.19 (KHTML, like Gecko) Silk/3.13 Safari/535.19 Silk-Accelerated=true", + AcceptLanguage: "en", + Screen: Screen{ + DevicePixelRatio: 2, + Horizontal: ScreenSize{ + Width: 1280, + Height: 800, + }, + Vertical: ScreenSize{ + Width: 800, + Height: 1280, + }, + }, + } + + // IPadMini device. + IPadMini = Device{ + Title: "iPad Mini", + Capabilities: []string{"touch", "mobile"}, + UserAgent: "Mozilla/5.0 (iPad; CPU OS 11_0 like Mac OS X) AppleWebKit/604.1.34 (KHTML, like Gecko) Version/11.0 Mobile/15A5341f Safari/604.1", + AcceptLanguage: "en", + Screen: Screen{ + DevicePixelRatio: 2, + Horizontal: ScreenSize{ + Width: 1024, + Height: 768, + }, + Vertical: ScreenSize{ + Width: 768, + Height: 1024, + }, + }, + } + + // IPad device. + IPad = Device{ + Title: "iPad", + Capabilities: []string{"touch", "mobile"}, + UserAgent: "Mozilla/5.0 (iPad; CPU OS 11_0 like Mac OS X) AppleWebKit/604.1.34 (KHTML, like Gecko) Version/11.0 Mobile/15A5341f Safari/604.1", + AcceptLanguage: "en", + Screen: Screen{ + DevicePixelRatio: 2, + Horizontal: ScreenSize{ + Width: 1024, + Height: 768, + }, + Vertical: ScreenSize{ + Width: 768, + Height: 1024, + }, + }, + } + + // IPadPro device. + IPadPro = Device{ + Title: "iPad Pro", + Capabilities: []string{"touch", "mobile"}, + UserAgent: "Mozilla/5.0 (iPad; CPU OS 11_0 like Mac OS X) AppleWebKit/604.1.34 (KHTML, like Gecko) Version/11.0 Mobile/15A5341f Safari/604.1", + AcceptLanguage: "en", + Screen: Screen{ + DevicePixelRatio: 2, + Horizontal: ScreenSize{ + Width: 1366, + Height: 1024, + }, + Vertical: ScreenSize{ + Width: 1024, + Height: 1366, + }, + }, + } + + // BlackberryPlayBook device. + BlackberryPlayBook = Device{ + Title: "Blackberry PlayBook", + Capabilities: []string{"touch", "mobile"}, + UserAgent: "Mozilla/5.0 (PlayBook; U; RIM Tablet OS 2.1.0; en-US) AppleWebKit/536.2+ (KHTML like Gecko) Version/7.2.1.0 Safari/536.2+", + AcceptLanguage: "en", + Screen: Screen{ + DevicePixelRatio: 1, + Horizontal: ScreenSize{ + Width: 1024, + Height: 600, + }, + Vertical: ScreenSize{ + Width: 600, + Height: 1024, + }, + }, + } + + // Nexus10 device. + Nexus10 = Device{ + Title: "Nexus 10", + Capabilities: []string{"touch", "mobile"}, + UserAgent: "Mozilla/5.0 (Linux; Android 6.0.1; Nexus 10 Build/MOB31T) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/114.0.0.0 Safari/537.36", + AcceptLanguage: "en", + Screen: Screen{ + DevicePixelRatio: 2, + Horizontal: ScreenSize{ + Width: 1280, + Height: 800, + }, + Vertical: ScreenSize{ + Width: 800, + Height: 1280, + }, + }, + } + + // Nexus7 device. + Nexus7 = Device{ + Title: "Nexus 7", + Capabilities: []string{"touch", "mobile"}, + UserAgent: "Mozilla/5.0 (Linux; Android 6.0.1; Nexus 7 Build/MOB30X) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/114.0.0.0 Safari/537.36", + AcceptLanguage: "en", + Screen: Screen{ + DevicePixelRatio: 2, + Horizontal: ScreenSize{ + Width: 960, + Height: 600, + }, + Vertical: ScreenSize{ + Width: 600, + Height: 960, + }, + }, + } + + // GalaxyNote3 device. + GalaxyNote3 = Device{ + Title: "Galaxy Note 3", + Capabilities: []string{"touch", "mobile"}, + UserAgent: "Mozilla/5.0 (Linux; U; Android 4.3; en-us; SM-N900T Build/JSS15J) AppleWebKit/534.30 (KHTML, like Gecko) Version/4.0 Mobile Safari/534.30", + AcceptLanguage: "en", + Screen: Screen{ + DevicePixelRatio: 3, + Horizontal: ScreenSize{ + Width: 640, + Height: 360, + }, + Vertical: ScreenSize{ + Width: 360, + Height: 640, + }, + }, + } + + // GalaxyNoteII device. + GalaxyNoteII = Device{ + Title: "Galaxy Note II", + Capabilities: []string{"touch", "mobile"}, + UserAgent: "Mozilla/5.0 (Linux; U; Android 4.1; en-us; GT-N7100 Build/JRO03C) AppleWebKit/534.30 (KHTML, like Gecko) Version/4.0 Mobile Safari/534.30", + AcceptLanguage: "en", + Screen: Screen{ + DevicePixelRatio: 2, + Horizontal: ScreenSize{ + Width: 640, + Height: 360, + }, + Vertical: ScreenSize{ + Width: 360, + Height: 640, + }, + }, + } + + // LaptopWithTouch device. + LaptopWithTouch = Device{ + Title: "Laptop with touch", + Capabilities: []string{"touch"}, + UserAgent: "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/114.0.0.0 Safari/537.36", + AcceptLanguage: "en", + Screen: Screen{ + DevicePixelRatio: 1, + Horizontal: ScreenSize{ + Width: 1280, + Height: 950, + }, + Vertical: ScreenSize{ + Width: 950, + Height: 1280, + }, + }, + } + + // LaptopWithHiDPIScreen device. + LaptopWithHiDPIScreen = Device{ + Title: "Laptop with HiDPI screen", + Capabilities: []string{}, + UserAgent: "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/114.0.0.0 Safari/537.36", + AcceptLanguage: "en", + Screen: Screen{ + DevicePixelRatio: 2, + Horizontal: ScreenSize{ + Width: 1440, + Height: 900, + }, + Vertical: ScreenSize{ + Width: 900, + Height: 1440, + }, + }, + } + + // LaptopWithMDPIScreen device. + LaptopWithMDPIScreen = Device{ + Title: "Laptop with MDPI screen", + Capabilities: []string{}, + UserAgent: "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/114.0.0.0 Safari/537.36", + AcceptLanguage: "en", + Screen: Screen{ + DevicePixelRatio: 1, + Horizontal: ScreenSize{ + Width: 1280, + Height: 800, + }, + Vertical: ScreenSize{ + Width: 800, + Height: 1280, + }, + }, + } + + // MotoG4 device. + MotoG4 = Device{ + Title: "Moto G4", + Capabilities: []string{"touch", "mobile"}, + UserAgent: "Mozilla/5.0 (Linux; Android 6.0.1; Moto G (4)) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/114.0.0.0 Mobile Safari/537.36", + AcceptLanguage: "en", + Screen: Screen{ + DevicePixelRatio: 3, + Horizontal: ScreenSize{ + Width: 640, + Height: 360, + }, + Vertical: ScreenSize{ + Width: 360, + Height: 640, + }, + }, + } + + // SurfaceDuo device. + SurfaceDuo = Device{ + Title: "Surface Duo", + Capabilities: []string{"touch", "mobile"}, + UserAgent: "Mozilla/5.0 (Linux; Android 8.0; Pixel 2 Build/OPD3.170816.012) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/114.0.0.0 Mobile Safari/537.36", + AcceptLanguage: "en", + Screen: Screen{ + DevicePixelRatio: 2, + Horizontal: ScreenSize{ + Width: 720, + Height: 540, + }, + Vertical: ScreenSize{ + Width: 540, + Height: 720, + }, + }, + } + + // GalaxyFold device. + GalaxyFold = Device{ + Title: "Galaxy Fold", + Capabilities: []string{"touch", "mobile"}, + UserAgent: "Mozilla/5.0 (Linux; Android 8.0; Pixel 2 Build/OPD3.170816.012) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/114.0.0.0 Mobile Safari/537.36", + AcceptLanguage: "en", + Screen: Screen{ + DevicePixelRatio: 3, + Horizontal: ScreenSize{ + Width: 653, + Height: 280, + }, + Vertical: ScreenSize{ + Width: 280, + Height: 653, + }, + }, + } +) diff --git a/vendor/github.com/go-rod/rod/lib/devices/utils.go b/vendor/github.com/go-rod/rod/lib/devices/utils.go new file mode 100644 index 000000000..20adc7f75 --- /dev/null +++ b/vendor/github.com/go-rod/rod/lib/devices/utils.go @@ -0,0 +1,13 @@ +package devices + +// Clear is used to clear overrides. +var Clear = Device{clear: true} + +func has(arr []string, str string) bool { + for _, item := range arr { + if item == str { + return true + } + } + return false +} diff --git a/vendor/github.com/go-rod/rod/lib/input/README.md b/vendor/github.com/go-rod/rod/lib/input/README.md new file mode 100644 index 000000000..36687bf3f --- /dev/null +++ b/vendor/github.com/go-rod/rod/lib/input/README.md @@ -0,0 +1,3 @@ +# input + +A lib to help encode inputs. diff --git a/vendor/github.com/go-rod/rod/lib/input/keyboard.go b/vendor/github.com/go-rod/rod/lib/input/keyboard.go new file mode 100644 index 000000000..85b366598 --- /dev/null +++ b/vendor/github.com/go-rod/rod/lib/input/keyboard.go @@ -0,0 +1,138 @@ +// Package input ... +package input + +import ( + "github.com/go-rod/rod/lib/proto" + "github.com/ysmood/gson" +) + +// Modifier values. +const ( + ModifierAlt = 1 + ModifierControl = 2 + ModifierMeta = 4 + ModifierShift = 8 +) + +// Key symbol. +type Key rune + +// keyMap for key description. +var keyMap = map[Key]KeyInfo{} + +// keyMapShifted for shifted key description. +var keyMapShifted = map[Key]KeyInfo{} + +var keyShiftedMap = map[Key]Key{} + +// AddKey to KeyMap. +func AddKey(key string, shiftedKey string, code string, keyCode int, location int) Key { + if len(key) == 1 { + r := Key(key[0]) + if _, has := keyMap[r]; !has { + keyMap[r] = KeyInfo{key, code, keyCode, location} + + if len(shiftedKey) == 1 { + rs := Key(shiftedKey[0]) + keyMapShifted[rs] = KeyInfo{shiftedKey, code, keyCode, location} + keyShiftedMap[r] = rs + } + return r + } + } + + k := Key(keyCode + (location+1)*256) + keyMap[k] = KeyInfo{key, code, keyCode, location} + + return k +} + +// Info of the key. +func (k Key) Info() KeyInfo { + if k, has := keyMap[k]; has { + return k + } + if k, has := keyMapShifted[k]; has { + return k + } + + panic("key not defined") +} + +// KeyInfo of a key +// https://developer.mozilla.org/en-US/docs/Web/API/KeyboardEvent +type KeyInfo struct { + // Here's the value for Shift key on the keyboard + + Key string // Shift + Code string // ShiftLeft + KeyCode int // 16 + Location int // 1 +} + +// Shift returns the shifted key, such as shifted "1" is "!". +func (k Key) Shift() (Key, bool) { + s, has := keyShiftedMap[k] + return s, has +} + +// Printable returns true if the key is printable. +func (k Key) Printable() bool { + return len(k.Info().Key) == 1 +} + +// Modifier returns the modifier value of the key. +func (k Key) Modifier() int { + switch k.Info().KeyCode { + case 18: + return ModifierAlt + case 17: + return ModifierControl + case 91, 92: + return ModifierMeta + case 16: + return ModifierShift + } + return 0 +} + +// Encode general key event. +func (k Key) Encode(t proto.InputDispatchKeyEventType, modifiers int) *proto.InputDispatchKeyEvent { + tp := t + if t == proto.InputDispatchKeyEventTypeKeyDown && !k.Printable() { + tp = proto.InputDispatchKeyEventTypeRawKeyDown + } + + info := k.Info() + l := gson.Int(info.Location) + keypad := false + if info.Location == 3 { + l = nil + keypad = true + } + + txt := "" + if k.Printable() { + txt = info.Key + } + + var cmd []string + if IsMac { + cmd = macCommands[info.Key] + } + + e := &proto.InputDispatchKeyEvent{ + Type: tp, + WindowsVirtualKeyCode: info.KeyCode, + Code: info.Code, + Key: info.Key, + Text: txt, + UnmodifiedText: txt, + Location: l, + IsKeypad: keypad, + Modifiers: modifiers, + Commands: cmd, + } + + return e +} diff --git a/vendor/github.com/go-rod/rod/lib/input/keymap.go b/vendor/github.com/go-rod/rod/lib/input/keymap.go new file mode 100644 index 000000000..d5d84de88 --- /dev/null +++ b/vendor/github.com/go-rod/rod/lib/input/keymap.go @@ -0,0 +1,134 @@ +package input + +// Key names +// Reference: https://github.com/microsoft/playwright/blob/main/packages/playwright-core/src/server/usKeyboardLayout.ts +var ( + // Functions row. + // + Escape = AddKey("Escape", "", "Escape", 27, 0) + F1 = AddKey("F1", "", "F1", 112, 0) + F2 = AddKey("F2", "", "F2", 113, 0) + F3 = AddKey("F3", "", "F3", 114, 0) + F4 = AddKey("F4", "", "F4", 115, 0) + F5 = AddKey("F5", "", "F5", 116, 0) + F6 = AddKey("F6", "", "F6", 117, 0) + F7 = AddKey("F7", "", "F7", 118, 0) + F8 = AddKey("F8", "", "F8", 119, 0) + F9 = AddKey("F9", "", "F9", 120, 0) + F10 = AddKey("F10", "", "F10", 121, 0) + F11 = AddKey("F11", "", "F11", 122, 0) + F12 = AddKey("F12", "", "F12", 123, 0) + + // Numbers row. + // + Backquote = AddKey("`", "~", "Backquote", 192, 0) + Digit1 = AddKey("1", "!", "Digit1", 49, 0) + Digit2 = AddKey("2", "@", "Digit2", 50, 0) + Digit3 = AddKey("3", "#", "Digit3", 51, 0) + Digit4 = AddKey("4", "$", "Digit4", 52, 0) + Digit5 = AddKey("5", "%", "Digit5", 53, 0) + Digit6 = AddKey("6", "^", "Digit6", 54, 0) + Digit7 = AddKey("7", "&", "Digit7", 55, 0) + Digit8 = AddKey("8", "*", "Digit8", 56, 0) + Digit9 = AddKey("9", "(", "Digit9", 57, 0) + Digit0 = AddKey("0", ")", "Digit0", 48, 0) + Minus = AddKey("-", "_", "Minus", 189, 0) + Equal = AddKey("=", "+", "Equal", 187, 0) + Backslash = AddKey(`\`, "|", "Backslash", 220, 0) + Backspace = AddKey("Backspace", "", "Backspace", 8, 0) + + // First row. + // + Tab = AddKey("\t", "", "Tab", 9, 0) + KeyQ = AddKey("q", "Q", "KeyQ", 81, 0) + KeyW = AddKey("w", "W", "KeyW", 87, 0) + KeyE = AddKey("e", "E", "KeyE", 69, 0) + KeyR = AddKey("r", "R", "KeyR", 82, 0) + KeyT = AddKey("t", "T", "KeyT", 84, 0) + KeyY = AddKey("y", "Y", "KeyY", 89, 0) + KeyU = AddKey("u", "U", "KeyU", 85, 0) + KeyI = AddKey("i", "I", "KeyI", 73, 0) + KeyO = AddKey("o", "O", "KeyO", 79, 0) + KeyP = AddKey("p", "P", "KeyP", 80, 0) + BracketLeft = AddKey("[", "{", "BracketLeft", 219, 0) + BracketRight = AddKey("]", "}", "BracketRight", 221, 0) + + // Second row. + // + CapsLock = AddKey("CapsLock", "", "CapsLock", 20, 0) + KeyA = AddKey("a", "A", "KeyA", 65, 0) + KeyS = AddKey("s", "S", "KeyS", 83, 0) + KeyD = AddKey("d", "D", "KeyD", 68, 0) + KeyF = AddKey("f", "F", "KeyF", 70, 0) + KeyG = AddKey("g", "G", "KeyG", 71, 0) + KeyH = AddKey("h", "H", "KeyH", 72, 0) + KeyJ = AddKey("j", "J", "KeyJ", 74, 0) + KeyK = AddKey("k", "K", "KeyK", 75, 0) + KeyL = AddKey("l", "L", "KeyL", 76, 0) + Semicolon = AddKey(";", ":", "Semicolon", 186, 0) + Quote = AddKey("'", `"`, "Quote", 222, 0) + Enter = AddKey("\r", "", "Enter", 13, 0) + + // Third row. + // + ShiftLeft = AddKey("Shift", "", "ShiftLeft", 16, 1) + KeyZ = AddKey("z", "Z", "KeyZ", 90, 0) + KeyX = AddKey("x", "X", "KeyX", 88, 0) + KeyC = AddKey("c", "C", "KeyC", 67, 0) + KeyV = AddKey("v", "V", "KeyV", 86, 0) + KeyB = AddKey("b", "B", "KeyB", 66, 0) + KeyN = AddKey("n", "N", "KeyN", 78, 0) + KeyM = AddKey("m", "M", "KeyM", 77, 0) + Comma = AddKey(",", "<", "Comma", 188, 0) + Period = AddKey(".", ">", "Period", 190, 0) + Slash = AddKey("/", "?", "Slash", 191, 0) + ShiftRight = AddKey("Shift", "", "ShiftRight", 16, 2) + + // Last row. + // + ControlLeft = AddKey("Control", "", "ControlLeft", 17, 1) + MetaLeft = AddKey("Meta", "", "MetaLeft", 91, 1) + AltLeft = AddKey("Alt", "", "AltLeft", 18, 1) + Space = AddKey(" ", "", "Space", 32, 0) + AltRight = AddKey("Alt", "", "AltRight", 18, 2) + AltGraph = AddKey("AltGraph", "", "AltGraph", 225, 0) + MetaRight = AddKey("Meta", "", "MetaRight", 92, 2) + ContextMenu = AddKey("ContextMenu", "", "ContextMenu", 93, 0) + ControlRight = AddKey("Control", "", "ControlRight", 17, 2) + + // Center block. + // + PrintScreen = AddKey("PrintScreen", "", "PrintScreen", 44, 0) + ScrollLock = AddKey("ScrollLock", "", "ScrollLock", 145, 0) + Pause = AddKey("Pause", "", "Pause", 19, 0) + PageUp = AddKey("PageUp", "", "PageUp", 33, 0) + PageDown = AddKey("PageDown", "", "PageDown", 34, 0) + Insert = AddKey("Insert", "", "Insert", 45, 0) + Delete = AddKey("Delete", "", "Delete", 46, 0) + Home = AddKey("Home", "", "Home", 36, 0) + End = AddKey("End", "", "End", 35, 0) + ArrowLeft = AddKey("ArrowLeft", "", "ArrowLeft", 37, 0) + ArrowUp = AddKey("ArrowUp", "", "ArrowUp", 38, 0) + ArrowRight = AddKey("ArrowRight", "", "ArrowRight", 39, 0) + ArrowDown = AddKey("ArrowDown", "", "ArrowDown", 40, 0) + + // Numpad. + // + NumLock = AddKey("NumLock", "", "NumLock", 144, 0) + NumpadDivide = AddKey("/", "", "NumpadDivide", 111, 3) + NumpadMultiply = AddKey("*", "", "NumpadMultiply", 106, 3) + NumpadSubtract = AddKey("-", "", "NumpadSubtract", 109, 3) + Numpad7 = AddKey("7", "", "Numpad7", 36, 3) + Numpad8 = AddKey("8", "", "Numpad8", 38, 3) + Numpad9 = AddKey("9", "", "Numpad9", 33, 3) + Numpad4 = AddKey("4", "", "Numpad4", 37, 3) + Numpad5 = AddKey("5", "", "Numpad5", 12, 3) + Numpad6 = AddKey("6", "", "Numpad6", 39, 3) + NumpadAdd = AddKey("+", "", "NumpadAdd", 107, 3) + Numpad1 = AddKey("1", "", "Numpad1", 35, 3) + Numpad2 = AddKey("2", "", "Numpad2", 40, 3) + Numpad3 = AddKey("3", "", "Numpad3", 34, 3) + Numpad0 = AddKey("0", "", "Numpad0", 45, 3) + NumpadDecimal = AddKey(".", "", "NumpadDecimal", 46, 3) + NumpadEnter = AddKey("\r", "", "NumpadEnter", 13, 3) +) diff --git a/vendor/github.com/go-rod/rod/lib/input/mac_comands.go b/vendor/github.com/go-rod/rod/lib/input/mac_comands.go new file mode 100644 index 000000000..0e1217232 --- /dev/null +++ b/vendor/github.com/go-rod/rod/lib/input/mac_comands.go @@ -0,0 +1,125 @@ +package input + +import "runtime" + +// IsMac OS. +var IsMac = runtime.GOOS == "darwin" + +// commands for macOS +// https://github.com/microsoft/playwright/blob/main/packages/playwright-core/src/server/macEditingCommands.ts +var macCommands = map[string][]string{ + "Backspace": {"deleteBackward"}, + "Enter": {"insertNewline"}, + "NumpadEnter": {"insertNewline"}, + "Escape": {"cancelOperation"}, + "ArrowUp": {"moveUp"}, + "ArrowDown": {"moveDown"}, + "ArrowLeft": {"moveLeft"}, + "ArrowRight": {"moveRight"}, + "F5": {"complete"}, + "Delete": {"deleteForward"}, + "Home": {"scrollToBeginningOfDocument"}, + "End": {"scrollToEndOfDocument"}, + "PageUp": {"scrollPageUp"}, + "PageDown": {"scrollPageDown"}, + "Shift+Backspace": {"deleteBackward"}, + "Shift+Enter": {"insertNewline"}, + "Shift+NumpadEnter": {"insertNewline"}, + "Shift+Escape": {"cancelOperation"}, + "Shift+ArrowUp": {"moveUpAndModifySelection"}, + "Shift+ArrowDown": {"moveDownAndModifySelection"}, + "Shift+ArrowLeft": {"moveLeftAndModifySelection"}, + "Shift+ArrowRight": {"moveRightAndModifySelection"}, + "Shift+F5": {"complete"}, + "Shift+Delete": {"deleteForward"}, + "Shift+Home": {"moveToBeginningOfDocumentAndModifySelection"}, + "Shift+End": {"moveToEndOfDocumentAndModifySelection"}, + "Shift+PageUp": {"pageUpAndModifySelection"}, + "Shift+PageDown": {"pageDownAndModifySelection"}, + "Shift+Numpad5": {"delete"}, + "Control+Tab": {"selectNextKeyView"}, + "Control+Enter": {"insertLineBreak"}, + "Control+NumpadEnter": {"insertLineBreak"}, + "Control+Quote": {"insertSingleQuoteIgnoringSubstitution"}, + "Control+KeyA": {"moveToBeginningOfParagraph"}, + "Control+KeyB": {"moveBackward"}, + "Control+KeyD": {"deleteForward"}, + "Control+KeyE": {"moveToEndOfParagraph"}, + "Control+KeyF": {"moveForward"}, + "Control+KeyH": {"deleteBackward"}, + "Control+KeyK": {"deleteToEndOfParagraph"}, + "Control+KeyL": {"centerSelectionInVisibleArea"}, + "Control+KeyN": {"moveDown"}, + "Control+KeyO": {"insertNewlineIgnoringFieldEditor", "moveBackward"}, + "Control+KeyP": {"moveUp"}, + "Control+KeyT": {"transpose"}, + "Control+KeyV": {"pageDown"}, + "Control+KeyY": {"yank"}, + "Control+Backspace": {"deleteBackwardByDecomposingPreviousCharacter"}, + "Control+ArrowUp": {"scrollPageUp"}, + "Control+ArrowDown": {"scrollPageDown"}, + "Control+ArrowLeft": {"moveToLeftEndOfLine"}, + "Control+ArrowRight": {"moveToRightEndOfLine"}, + "Shift+Control+Enter": {"insertLineBreak"}, + "Shift+Control+NumpadEnter": {"insertLineBreak"}, + "Shift+Control+Tab": {"selectPreviousKeyView"}, + "Shift+Control+Quote": {"insertDoubleQuoteIgnoringSubstitution"}, + "Shift+Control+KeyA": {"moveToBeginningOfParagraphAndModifySelection"}, + "Shift+Control+KeyB": {"moveBackwardAndModifySelection"}, + "Shift+Control+KeyE": {"moveToEndOfParagraphAndModifySelection"}, + "Shift+Control+KeyF": {"moveForwardAndModifySelection"}, + "Shift+Control+KeyN": {"moveDownAndModifySelection"}, + "Shift+Control+KeyP": {"moveUpAndModifySelection"}, + "Shift+Control+KeyV": {"pageDownAndModifySelection"}, + "Shift+Control+Backspace": {"deleteBackwardByDecomposingPreviousCharacter"}, + "Shift+Control+ArrowUp": {"scrollPageUp"}, + "Shift+Control+ArrowDown": {"scrollPageDown"}, + "Shift+Control+ArrowLeft": {"moveToLeftEndOfLineAndModifySelection"}, + "Shift+Control+ArrowRight": {"moveToRightEndOfLineAndModifySelection"}, + "Alt+Backspace": {"deleteWordBackward"}, + "Alt+Enter": {"insertNewlineIgnoringFieldEditor"}, + "Alt+NumpadEnter": {"insertNewlineIgnoringFieldEditor"}, + "Alt+Escape": {"complete"}, + "Alt+ArrowUp": {"moveBackward", "moveToBeginningOfParagraph"}, + "Alt+ArrowDown": {"moveForward", "moveToEndOfParagraph"}, + "Alt+ArrowLeft": {"moveWordLeft"}, + "Alt+ArrowRight": {"moveWordRight"}, + "Alt+Delete": {"deleteWordForward"}, + "Alt+PageUp": {"pageUp"}, + "Alt+PageDown": {"pageDown"}, + "Shift+Alt+Backspace": {"deleteWordBackward"}, + "Shift+Alt+Enter": {"insertNewlineIgnoringFieldEditor"}, + "Shift+Alt+NumpadEnter": {"insertNewlineIgnoringFieldEditor"}, + "Shift+Alt+Escape": {"complete"}, + "Shift+Alt+ArrowUp": {"moveParagraphBackwardAndModifySelection"}, + "Shift+Alt+ArrowDown": {"moveParagraphForwardAndModifySelection"}, + "Shift+Alt+ArrowLeft": {"moveWordLeftAndModifySelection"}, + "Shift+Alt+ArrowRight": {"moveWordRightAndModifySelection"}, + "Shift+Alt+Delete": {"deleteWordForward"}, + "Shift+Alt+PageUp": {"pageUp"}, + "Shift+Alt+PageDown": {"pageDown"}, + "Control+Alt+KeyB": {"moveWordBackward"}, + "Control+Alt+KeyF": {"moveWordForward"}, + "Control+Alt+Backspace": {"deleteWordBackward"}, + "Shift+Control+Alt+KeyB": {"moveWordBackwardAndModifySelection"}, + "Shift+Control+Alt+KeyF": {"moveWordForwardAndModifySelection"}, + "Shift+Control+Alt+Backspace": {"deleteWordBackward"}, + "Meta+NumpadSubtract": {"cancel"}, + "Meta+Backspace": {"deleteToBeginningOfLine"}, + "Meta+ArrowUp": {"moveToBeginningOfDocument"}, + "Meta+ArrowDown": {"moveToEndOfDocument"}, + "Meta+ArrowLeft": {"moveToLeftEndOfLine"}, + "Meta+ArrowRight": {"moveToRightEndOfLine"}, + "Shift+Meta+NumpadSubtract": {"cancel"}, + "Shift+Meta+Backspace": {"deleteToBeginningOfLine"}, + "Shift+Meta+ArrowUp": {"moveToBeginningOfDocumentAndModifySelection"}, + "Shift+Meta+ArrowDown": {"moveToEndOfDocumentAndModifySelection"}, + "Shift+Meta+ArrowLeft": {"moveToLeftEndOfLineAndModifySelection"}, + "Shift+Meta+ArrowRight": {"moveToRightEndOfLineAndModifySelection"}, + + "Meta+KeyA": {"selectAll"}, + "Meta+KeyC": {"copy"}, + "Meta+KeyV": {"paste"}, + "Meta+KeyZ": {"undo"}, + "Shift+Meta+KeyZ": {"redo"}, +} diff --git a/vendor/github.com/go-rod/rod/lib/input/mouse.go b/vendor/github.com/go-rod/rod/lib/input/mouse.go new file mode 100644 index 000000000..596891eff --- /dev/null +++ b/vendor/github.com/go-rod/rod/lib/input/mouse.go @@ -0,0 +1,25 @@ +package input + +import "github.com/go-rod/rod/lib/proto" + +// MouseKeys is the map for mouse keys. +var MouseKeys = map[proto.InputMouseButton]int{ + proto.InputMouseButtonLeft: 1, + proto.InputMouseButtonRight: 2, + proto.InputMouseButtonMiddle: 4, + proto.InputMouseButtonBack: 8, + proto.InputMouseButtonForward: 16, +} + +// EncodeMouseButton into button flag. +func EncodeMouseButton(buttons []proto.InputMouseButton) (proto.InputMouseButton, int) { + flag := int(0) + for _, btn := range buttons { + flag |= MouseKeys[btn] + } + btn := proto.InputMouseButton("none") + if len(buttons) > 0 { + btn = buttons[0] + } + return btn, flag +} diff --git a/vendor/github.com/go-rod/rod/lib/js/helper.go b/vendor/github.com/go-rod/rod/lib/js/helper.go new file mode 100644 index 000000000..c8e52823c --- /dev/null +++ b/vendor/github.com/go-rod/rod/lib/js/helper.go @@ -0,0 +1,234 @@ +// Package js generated by "lib/js/generate" +package js + +// Element ... +var Element = &Function{ + Name: "element", + Definition: `function(e){return functions.selectable(this).querySelector(e)}`, + Dependencies: []*Function{Selectable}, +} + +// TriggerFavicon ... +var TriggerFavicon = &Function{ + Name: "triggerFavicon", + Definition: `function(){return new Promise((e,t)=>{var n=document.querySelector("link[rel~=icon]"),n=n&&n.href||"/favicon.ico",n=new URL(n,window.location).toString();const r=new XMLHttpRequest;r.open("GET",n),r.ontimeout=function(){t({errorType:"timeout_error",xhr:r})},r.onreadystatechange=function(){4===r.readyState&&(200<=r.status&&r.status<300||304===r.status?e({status:r.status,statusText:r.statusText,responseText:r.responseText}):t({errorType:"status_error",xhr:r,status:r.status,statusText:r.statusText,responseText:r.responseText}))},r.onerror=function(){t({errorType:"onerror",xhr:r,status:r.status,statusText:r.statusText,responseText:r.responseText})},r.send()})}`, + Dependencies: []*Function{}, +} + +// Elements ... +var Elements = &Function{ + Name: "elements", + Definition: `function(e){return functions.selectable(this).querySelectorAll(e)}`, + Dependencies: []*Function{Selectable}, +} + +// ElementX ... +var ElementX = &Function{ + Name: "elementX", + Definition: `function(e){var t=functions.selectable(this);return document.evaluate(e,t,null,XPathResult.FIRST_ORDERED_NODE_TYPE).singleNodeValue}`, + Dependencies: []*Function{Selectable}, +} + +// ElementsX ... +var ElementsX = &Function{ + Name: "elementsX", + Definition: `function(e){for(var t,n=functions.selectable(this),r=document.evaluate(e,n,null,XPathResult.ORDERED_NODE_ITERATOR_TYPE),i=[];t=r.iterateNext();)i.push(t);return i}`, + Dependencies: []*Function{Selectable}, +} + +// ElementR ... +var ElementR = &Function{ + Name: "elementR", + Definition: `function(e,t){var n=t.match(/(\/?)(.+)\1([a-z]*)/i),r=n[3]&&!/^(?!.*?(.).*?\1)[gmixXsuUAJ]+$/.test(n[3])?new RegExp(t):new RegExp(n[2],n[3]),t=functions.selectable(this),n=Array.from(t.querySelectorAll(e)).find(e=>r.test(functions.text.call(e)));return n||null}`, + Dependencies: []*Function{Selectable, Text}, +} + +// Parents ... +var Parents = &Function{ + Name: "parents", + Definition: `function(e){let t=this.parentElement;for(var n=[];t;)t.matches(e)&&n.push(t),t=t.parentElement;return n}`, + Dependencies: []*Function{}, +} + +// ContainsElement ... +var ContainsElement = &Function{ + Name: "containsElement", + Definition: `function(e){for(var t=e;null!=t;){if(t===this)return!0;t=t.parentElement}return!1}`, + Dependencies: []*Function{}, +} + +// InitMouseTracer ... +var InitMouseTracer = &Function{ + Name: "initMouseTracer", + Definition: `async function(e,t){var n;await functions.waitLoad(),document.getElementById(e)||((n=document.createElement("div")).innerHTML=t,(t=n.lastChild).id=e,t.style="position: absolute; z-index: 2147483647; width: 17px; pointer-events: none;",t.removeAttribute("width"),t.removeAttribute("height"),document.body.parentElement.appendChild(t))}`, + Dependencies: []*Function{WaitLoad}, +} + +// UpdateMouseTracer ... +var UpdateMouseTracer = &Function{ + Name: "updateMouseTracer", + Definition: `function(e,t,n){e=document.getElementById(e);return!!e&&(e.style.left=t-2+"px",e.style.top=n-3+"px",!0)}`, + Dependencies: []*Function{}, +} + +// Rect ... +var Rect = &Function{ + Name: "rect", + Definition: `function(){var e=functions.tag(this).getBoundingClientRect();return{x:e.x,y:e.y,width:e.width,height:e.height}}`, + Dependencies: []*Function{Tag}, +} + +// Overlay ... +var Overlay = &Function{ + Name: "overlay", + Definition: `async function(e,t,n,r,i,o){await functions.waitLoad();var s=document.createElement("div");s.id=e,s.style=` + "`" + `position: fixed; z-index:2147483647; border: 2px dashed red; + border-radius: 3px; box-shadow: #5f3232 0 0 3px; pointer-events: none; + box-sizing: border-box; + left: ${t}px; + top: ${n}px; + height: ${i}px; + width: ${r}px;` + "`" + `,r*i==0&&(s.style.border="none"),o?((e=document.createElement("div")).style=` + "`" + `position: absolute; color: #cc26d6; font-size: 12px; background: #ffffffeb; + box-shadow: #333 0 0 3px; padding: 2px 5px; border-radius: 3px; white-space: nowrap; + top: ${i}px;` + "`" + `,e.innerHTML=o,s.appendChild(e),document.body.parentElement.appendChild(s),window.innerHeight{var e,t=document.getElementById(n);null!==t&&(e=i.getBoundingClientRect(),o.left===e.left&&o.top===e.top&&o.width===e.width&&o.height===e.height||(t.style.left=e.left+"px",t.style.top=e.top+"px",t.style.width=e.width+"px",t.style.height=e.height+"px",o=e),setTimeout(s,r))};setTimeout(s,r)}`, + Dependencies: []*Function{Tag, Overlay}, +} + +// RemoveOverlay ... +var RemoveOverlay = &Function{ + Name: "removeOverlay", + Definition: `function(e){e=document.getElementById(e);e&&Element.prototype.remove.call(e)}`, + Dependencies: []*Function{}, +} + +// WaitIdle ... +var WaitIdle = &Function{ + Name: "waitIdle", + Definition: `function(t){return new Promise(e=>{window.requestIdleCallback(e,{timeout:t})})}`, + Dependencies: []*Function{}, +} + +// WaitLoad ... +var WaitLoad = &Function{ + Name: "waitLoad", + Definition: `function(){const n=this===window;return new Promise((e,t)=>{if(n){if("complete"===document.readyState)return e();window.addEventListener("load",e)}else void 0===this.complete||this.complete?e():(this.addEventListener("load",e),this.addEventListener("error",t))})}`, + Dependencies: []*Function{}, +} + +// InputEvent ... +var InputEvent = &Function{ + Name: "inputEvent", + Definition: `function(){this.dispatchEvent(new Event("input",{bubbles:!0})),this.dispatchEvent(new Event("change",{bubbles:!0}))}`, + Dependencies: []*Function{}, +} + +// InputTime ... +var InputTime = &Function{ + Name: "inputTime", + Definition: `function(e){var e=new Date(e),t=e=>e.toString().padStart(2,"0"),n=e.getFullYear(),r=t(e.getMonth()+1),i=t(e.getDate()),o=t(e.getHours()),s=t(e.getMinutes());switch(this.type){case"date":this.value=n+` + "`" + `-${r}-` + "`" + `+i;break;case"datetime-local":this.value=n+` + "`" + `-${r}-${i}T${o}:` + "`" + `+s;break;case"month":this.value=n+"-"+r;break;case"time":this.value=o+":"+s}functions.inputEvent.call(this)}`, + Dependencies: []*Function{InputEvent}, +} + +// InputColor ... +var InputColor = &Function{ + Name: "inputColor", + Definition: `function(e){this.value=""+e,functions.inputEvent.call(this)}`, + Dependencies: []*Function{InputEvent}, +} + +// SelectText ... +var SelectText = &Function{ + Name: "selectText", + Definition: `function(e){e=this.value.match(new RegExp(e));e&&this.setSelectionRange(e.index,e.index+e[0].length)}`, + Dependencies: []*Function{}, +} + +// SelectAllText ... +var SelectAllText = &Function{ + Name: "selectAllText", + Definition: `function(){this.select()}`, + Dependencies: []*Function{}, +} + +// Select ... +var Select = &Function{ + Name: "select", + Definition: `function(e,t,n){let r;switch(n){case"regex":r=e.map(e=>{const t=new RegExp(e);return e=>t.test(e.innerText)});break;case"css-selector":r=e.map(t=>e=>e.matches(t));break;default:r=e.map(t=>e=>e.innerText.includes(t))}const i=Array.from(this.options);let o=!1;return r.forEach(e=>{e=i.find(e);e&&(e.selected=t,o=!0)}),this.dispatchEvent(new Event("input",{bubbles:!0})),this.dispatchEvent(new Event("change",{bubbles:!0})),o}`, + Dependencies: []*Function{}, +} + +// Visible ... +var Visible = &Function{ + Name: "visible", + Definition: `function(){var e=functions.tag(this),t=e.getBoundingClientRect(),e=window.getComputedStyle(e);return"none"!==e.display&&"hidden"!==e.visibility&&!!(t.top||t.bottom||t.width||t.height)}`, + Dependencies: []*Function{Tag}, +} + +// Invisible ... +var Invisible = &Function{ + Name: "invisible", + Definition: `function(){return!functions.visible.apply(this)}`, + Dependencies: []*Function{Visible}, +} + +// Text ... +var Text = &Function{ + Name: "text", + Definition: `function(){switch(this.tagName){case"INPUT":case"TEXTAREA":return this.value||this.placeholder;case"SELECT":return Array.from(this.selectedOptions).map(e=>e.innerText).join();case void 0:return this.textContent;default:return this.innerText}}`, + Dependencies: []*Function{}, +} + +// Resource ... +var Resource = &Function{ + Name: "resource", + Definition: `function(){return new Promise((e,t)=>{if(this.complete)return e(this.currentSrc);this.addEventListener("load",()=>e(this.currentSrc)),this.addEventListener("error",e=>t(e))})}`, + Dependencies: []*Function{}, +} + +// AddScriptTag ... +var AddScriptTag = &Function{ + Name: "addScriptTag", + Definition: `function(r,i,o){if(!document.getElementById(r))return new Promise((e,t)=>{var n=document.createElement("script");i?(n.src=i,n.onload=e):(n.type="text/javascript",n.text=o,e()),n.id=r,n.onerror=t,document.head.appendChild(n)})}`, + Dependencies: []*Function{}, +} + +// AddStyleTag ... +var AddStyleTag = &Function{ + Name: "addStyleTag", + Definition: `function(r,i,o){if(!document.getElementById(r))return new Promise((e,t)=>{var n;i?((n=document.createElement("link")).rel="stylesheet",n.href=i):((n=document.createElement("style")).type="text/css",n.appendChild(document.createTextNode(o)),e()),n.id=r,n.onload=e,n.onerror=t,document.head.appendChild(n)})}`, + Dependencies: []*Function{}, +} + +// Selectable ... +var Selectable = &Function{ + Name: "selectable", + Definition: `function(e){return e.querySelector?e:document}`, + Dependencies: []*Function{}, +} + +// Tag ... +var Tag = &Function{ + Name: "tag", + Definition: `function(e){return e.tagName?e:e.parentElement}`, + Dependencies: []*Function{}, +} + +// ExposeFunc ... +var ExposeFunc = &Function{ + Name: "exposeFunc", + Definition: `function(e,t){let o=0;window[e]=e=>new Promise((n,r)=>{const i=t+"_cb"+o++;window[i]=(e,t)=>{delete window[i],t?r(t):n(e)},window[t](JSON.stringify({req:e,cb:i}))})}`, + Dependencies: []*Function{}, +} + +// GetXPath ... +var GetXPath = &Function{ + Name: "getXPath", + Definition: `function(e){class i{constructor(e,t){this.value=e,this.optimized=t||!1}toString(){return this.value}}function o(t){function n(e,t){return e===t||(e.nodeType===Node.ELEMENT_NODE&&t.nodeType===Node.ELEMENT_NODE?e.localName===t.localName:e.nodeType===t.nodeType||(e.nodeType===Node.CDATA_SECTION_NODE?Node.TEXT_NODE:e.nodeType)===(t.nodeType===Node.CDATA_SECTION_NODE?Node.TEXT_NODE:t.nodeType))}var e=t.parentNode,r=e?e.children:null;if(!r)return 0;let i;for(let e=0;e { + const faviconElement = document.querySelector('link[rel~=icon]') + const href = (faviconElement && faviconElement.href) || '/favicon.ico' + const faviconUrl = new URL(href, window.location).toString() + const xhr = new XMLHttpRequest() + xhr.open('GET', faviconUrl) + + xhr.ontimeout = function () { + reject({ + errorType: 'timeout_error', + xhr: xhr + }) + } + + xhr.onreadystatechange = function () { + if (xhr.readyState === 4) { + if ((xhr.status >= 200 && xhr.status < 300) || xhr.status === 304) { + resolve({ + status: xhr.status, + statusText: xhr.statusText, + responseText: xhr.responseText + }) + } else { + reject({ + errorType: 'status_error', + xhr: xhr, + status: xhr.status, + statusText: xhr.statusText, + responseText: xhr.responseText + }) + } + } + } + + xhr.onerror = function () { + reject({ + errorType: 'onerror', + xhr: xhr, + status: xhr.status, + statusText: xhr.statusText, + responseText: xhr.responseText + }) + } + xhr.send() + }) + }, + + elements(selector) { + return functions.selectable(this).querySelectorAll(selector) + }, + + elementX(xPath) { + const s = functions.selectable(this) + return document.evaluate( + xPath, + s, + null, + XPathResult.FIRST_ORDERED_NODE_TYPE + ).singleNodeValue + }, + + elementsX(xpath) { + const s = functions.selectable(this) + const iter = document.evaluate( + xpath, + s, + null, + XPathResult.ORDERED_NODE_ITERATOR_TYPE + ) + const list = [] + let el + while ((el = iter.iterateNext())) list.push(el) + return list + }, + + elementR(selector, regex) { + var reg + var m = regex.match(/(\/?)(.+)\1([a-z]*)/i) + // cSpell:ignore gmix + if (m[3] && !/^(?!.*?(.).*?\1)[gmixXsuUAJ]+$/.test(m[3])) + reg = new RegExp(regex) + else reg = new RegExp(m[2], m[3]) + + const s = functions.selectable(this) + const el = Array.from(s.querySelectorAll(selector)).find((e) => + reg.test(functions.text.call(e)) + ) + return el ? el : null + }, + + parents(selector) { + let p = this.parentElement + const list = [] + while (p) { + if (p.matches(selector)) { + list.push(p) + } + p = p.parentElement + } + return list + }, + + containsElement(target) { + var node = target + while (node != null) { + if (node === this) { + return true + } + node = node.parentElement + } + return false + }, + + async initMouseTracer(iconId, icon) { + await functions.waitLoad() + + if (document.getElementById(iconId)) { + return + } + + const tmp = document.createElement('div') + tmp.innerHTML = icon + const svg = tmp.lastChild + svg.id = iconId + svg.style = + 'position: absolute; z-index: 2147483647; width: 17px; pointer-events: none;' + svg.removeAttribute('width') + svg.removeAttribute('height') + document.body.parentElement.appendChild(svg) + }, + + updateMouseTracer(iconId, x, y) { + const svg = document.getElementById(iconId) + if (!svg) { + return false + } + svg.style.left = x - 2 + 'px' + svg.style.top = y - 3 + 'px' + return true + }, + + rect() { + const b = functions.tag(this).getBoundingClientRect() + return { x: b.x, y: b.y, width: b.width, height: b.height } + }, + + async overlay(id, left, top, width, height, msg) { + await functions.waitLoad() + + const div = document.createElement('div') + div.id = id + div.style = `position: fixed; z-index:2147483647; border: 2px dashed red; + border-radius: 3px; box-shadow: #5f3232 0 0 3px; pointer-events: none; + box-sizing: border-box; + left: ${left}px; + top: ${top}px; + height: ${height}px; + width: ${width}px;` + + if (width * height === 0) { + div.style.border = 'none' + } + + if (!msg) { + document.body.parentElement.appendChild(div) + return + } + + const msgDiv = document.createElement('div') + msgDiv.style = `position: absolute; color: #cc26d6; font-size: 12px; background: #ffffffeb; + box-shadow: #333 0 0 3px; padding: 2px 5px; border-radius: 3px; white-space: nowrap; + top: ${height}px;` + + msgDiv.innerHTML = msg + div.appendChild(msgDiv) + document.body.parentElement.appendChild(div) + + if (window.innerHeight < msgDiv.offsetHeight + top + height) { + msgDiv.style.top = -msgDiv.offsetHeight - 2 + 'px' + } + + if (window.innerWidth < msgDiv.offsetWidth + left) { + msgDiv.style.left = window.innerWidth - msgDiv.offsetWidth - left + 'px' + } + }, + + async elementOverlay(id, msg) { + const interval = 100 + const el = functions.tag(this) + + let pre = el.getBoundingClientRect() + await functions.overlay(id, pre.left, pre.top, pre.width, pre.height, msg) + + const update = () => { + const overlay = document.getElementById(id) + if (overlay === null) return + + const box = el.getBoundingClientRect() + if ( + pre.left === box.left && + pre.top === box.top && + pre.width === box.width && + pre.height === box.height + ) { + setTimeout(update, interval) + return + } + + overlay.style.left = box.left + 'px' + overlay.style.top = box.top + 'px' + overlay.style.width = box.width + 'px' + overlay.style.height = box.height + 'px' + pre = box + + setTimeout(update, interval) + } + + setTimeout(update, interval) + }, + + removeOverlay(id) { + const el = document.getElementById(id) + // prevent override like prototype.js + el && Element.prototype.remove.call(el) + }, + + waitIdle(timeout) { + return new Promise((resolve) => { + window.requestIdleCallback(resolve, { timeout }) + }) + }, + + waitLoad() { + const isWin = this === window + return new Promise((resolve, reject) => { + if (isWin) { + if (document.readyState === 'complete') return resolve() + window.addEventListener('load', resolve) + } else { + if (this.complete === undefined || this.complete) { + resolve() + } else { + this.addEventListener('load', resolve) + this.addEventListener('error', reject) + } + } + }) + }, + + inputEvent() { + this.dispatchEvent(new Event('input', { bubbles: true })) + this.dispatchEvent(new Event('change', { bubbles: true })) + }, + + inputTime(stamp) { + const time = new Date(stamp) + + const pad = (n) => n.toString().padStart(2, '0') + + const y = time.getFullYear() + const mon = pad(time.getMonth() + 1) + const d = pad(time.getDate()) + const h = pad(time.getHours()) + const min = pad(time.getMinutes()) + + switch (this.type) { + case 'date': + this.value = `${y}-${mon}-${d}` + break + case 'datetime-local': + this.value = `${y}-${mon}-${d}T${h}:${min}` + break + case 'month': + this.value = `${y}-${mon}` + break + case 'time': + this.value = `${h}:${min}` + break + } + + functions.inputEvent.call(this) + }, + + inputColor(color) { + this.value = `${color}` + + functions.inputEvent.call(this) + }, + selectText(pattern) { + const m = this.value.match(new RegExp(pattern)) + if (m) { + this.setSelectionRange(m.index, m.index + m[0].length) + } + }, + + selectAllText() { + this.select() + }, + + select(selectors, selected, type) { + let matchers + switch (type) { + case 'regex': + matchers = selectors.map((s) => { + const reg = new RegExp(s) + return (el) => reg.test(el.innerText) + }) + break + case 'css-selector': + matchers = selectors.map((s) => (el) => el.matches(s)) + break + default: + matchers = selectors.map((s) => (el) => el.innerText.includes(s)) + break + } + + const opts = Array.from(this.options) + let has = false + matchers.forEach((s) => { + const el = opts.find(s) + if (el) { + el.selected = selected + has = true + return + } + }) + + this.dispatchEvent(new Event('input', { bubbles: true })) + this.dispatchEvent(new Event('change', { bubbles: true })) + + return has + }, + + visible() { + const el = functions.tag(this) + const box = el.getBoundingClientRect() + const style = window.getComputedStyle(el) + return ( + style.display !== 'none' && + style.visibility !== 'hidden' && + !!(box.top || box.bottom || box.width || box.height) + ) + }, + + invisible() { + return !functions.visible.apply(this) + }, + + text() { + switch (this.tagName) { + case 'INPUT': + case 'TEXTAREA': + return this.value || this.placeholder + case 'SELECT': + return Array.from(this.selectedOptions) + .map((el) => el.innerText) + .join() + case undefined: + return this.textContent + default: + return this.innerText + } + }, + + resource() { + return new Promise((resolve, reject) => { + if (this.complete) { + return resolve(this.currentSrc) + } + this.addEventListener('load', () => resolve(this.currentSrc)) + this.addEventListener('error', (e) => reject(e)) + }) + }, + + addScriptTag(id, url, content) { + if (document.getElementById(id)) return + + return new Promise((resolve, reject) => { + var s = document.createElement('script') + + if (url) { + s.src = url + s.onload = resolve + } else { + s.type = 'text/javascript' + s.text = content + resolve() + } + + s.id = id + s.onerror = reject + document.head.appendChild(s) + }) + }, + + addStyleTag(id, url, content) { + if (document.getElementById(id)) return + + return new Promise((resolve, reject) => { + var el + + if (url) { + el = document.createElement('link') + el.rel = 'stylesheet' + el.href = url + } else { + el = document.createElement('style') + el.type = 'text/css' + el.appendChild(document.createTextNode(content)) + resolve() + } + + el.id = id + el.onload = resolve + el.onerror = reject + document.head.appendChild(el) + }) + }, + + selectable(s) { + return s.querySelector ? s : document + }, + + tag(el) { + return el.tagName ? el : el.parentElement + }, + + exposeFunc(name, bind) { + let callbackCount = 0 + window[name] = (req) => + new Promise((resolve, reject) => { + const cb = bind + '_cb' + callbackCount++ + window[cb] = (res, err) => { + delete window[cb] + err ? reject(err) : resolve(res) + } + window[bind](JSON.stringify({ req, cb })) + }) + }, + + getXPath(optimized) { + class Step { + constructor(value, optimized) { + this.value = value + this.optimized = optimized || false + } + toString() { + return this.value + } + } + const xPathValue = function xPathValue(node, optimized) { + let ownValue + const ownIndex = xPathIndex(node) + if (ownIndex === -1) { + return null + } + switch (node.nodeType) { + case Node.ELEMENT_NODE: + if (optimized && node.id) { + return new Step(`//*[@id='${node.id}']`, true) + } + ownValue = node.localName + break + case Node.ATTRIBUTE_NODE: + ownValue = `@${node.nodeName}` + break + case Node.TEXT_NODE: + case Node.CDATA_SECTION_NODE: + ownValue = 'text()' + break + case Node.PROCESSING_INSTRUCTION_NODE: + ownValue = 'processing-instruction()' + break + case Node.COMMENT_NODE: + ownValue = 'comment()' + break + case Node.DOCUMENT_NODE: + ownValue = '' + break + default: + ownValue = '' + break + } + if (ownIndex > 0) { + ownValue += `[${ownIndex}]` + } + return new Step(ownValue, node.nodeType === Node.DOCUMENT_NODE) + } + const xPathIndex = function xPathIndex(node) { + function areNodesSimilar(left, right) { + if (left === right) { + return true + } + if ( + left.nodeType === Node.ELEMENT_NODE && + right.nodeType === Node.ELEMENT_NODE + ) { + return left.localName === right.localName + } + if (left.nodeType === right.nodeType) { + return true + } + const leftType = + left.nodeType === Node.CDATA_SECTION_NODE + ? Node.TEXT_NODE + : left.nodeType + const rightType = + right.nodeType === Node.CDATA_SECTION_NODE + ? Node.TEXT_NODE + : right.nodeType + return leftType === rightType + } + const parentNode = node.parentNode + const siblings = parentNode ? parentNode.children : null + if (!siblings) { + return 0 + } + let hasSameNamedElements + for (let i = 0; i < siblings.length; ++i) { + if (areNodesSimilar(node, siblings[i]) && !(siblings[i] === node)) { + hasSameNamedElements = true + break + } + } + if (!hasSameNamedElements) { + return 0 + } + let ownIndex = 1 + for (let i = 0; i < siblings.length; ++i) { + if (areNodesSimilar(node, siblings[i])) { + if (siblings[i] === node) { + return ownIndex + } + ++ownIndex + } + } + return -1 + } + const node = this + if (node.nodeType === Node.DOCUMENT_NODE) { + return '/' + } + const steps = [] + let contextNode = node + while (contextNode) { + const step = xPathValue(contextNode, optimized) + if (!step) { + break + } + steps.push(step) + if (step.optimized) { + break + } + contextNode = contextNode.parentNode + } + steps.reverse() + return (steps.length && steps[0].optimized ? '' : '/') + steps.join('/') + } +} diff --git a/vendor/github.com/go-rod/rod/lib/js/js.go b/vendor/github.com/go-rod/rod/lib/js/js.go new file mode 100644 index 000000000..72f36e112 --- /dev/null +++ b/vendor/github.com/go-rod/rod/lib/js/js.go @@ -0,0 +1,21 @@ +package js + +// Function definition. +type Function struct { + // Name must be unique and not conflict with the function names in "helper.js" + Name string + + // Definition holds the code of a js function from "helper.js", + // the js code is compressed by uglify-js. + Definition string + + // Dependencies will be preloaded and assigned to the global js object "functions" + Dependencies []*Function +} + +// Functions ... +var Functions = &Function{ + Name: "functions", + Definition: "() => ({})", + Dependencies: nil, +} diff --git a/vendor/github.com/go-rod/rod/lib/launcher/README.md b/vendor/github.com/go-rod/rod/lib/launcher/README.md new file mode 100644 index 000000000..32622ddc6 --- /dev/null +++ b/vendor/github.com/go-rod/rod/lib/launcher/README.md @@ -0,0 +1,3 @@ +# Overview + +A lib helps to find, launch or download the browser. You can also use it as a standalone lib without Rod. diff --git a/vendor/github.com/go-rod/rod/lib/launcher/browser.go b/vendor/github.com/go-rod/rod/lib/launcher/browser.go new file mode 100644 index 000000000..733af27d5 --- /dev/null +++ b/vendor/github.com/go-rod/rod/lib/launcher/browser.go @@ -0,0 +1,279 @@ +package launcher + +import ( + "bytes" + "context" + "errors" + "fmt" + "log" + "net/http" + "os" + "os/exec" + "path/filepath" + "runtime" + "strings" + + "github.com/go-rod/rod/lib/defaults" + "github.com/go-rod/rod/lib/utils" + "github.com/ysmood/fetchup" + "github.com/ysmood/leakless" +) + +// Host formats a revision number to a downloadable URL for the browser. +type Host func(revision int) string + +var hostConf = map[string]struct { + urlPrefix string + zipName string +}{ + "darwin_amd64": {"Mac", "chrome-mac.zip"}, + "darwin_arm64": {"Mac_Arm", "chrome-mac.zip"}, + "linux_amd64": {"Linux_x64", "chrome-linux.zip"}, + "windows_386": {"Win", "chrome-win.zip"}, + "windows_amd64": {"Win_x64", "chrome-win.zip"}, +}[runtime.GOOS+"_"+runtime.GOARCH] + +// HostGoogle to download browser. +func HostGoogle(revision int) string { + return fmt.Sprintf( + "https://storage.googleapis.com/chromium-browser-snapshots/%s/%d/%s", + hostConf.urlPrefix, + revision, + hostConf.zipName, + ) +} + +// HostNPM to download browser. +func HostNPM(revision int) string { + return fmt.Sprintf( + "https://registry.npmmirror.com/-/binary/chromium-browser-snapshots/%s/%d/%s", + hostConf.urlPrefix, + revision, + hostConf.zipName, + ) +} + +// HostPlaywright to download browser. +func HostPlaywright(revision int) string { + rev := RevisionPlaywright + if !(runtime.GOOS == "linux" && runtime.GOARCH == "arm64") { + rev = revision + } + return fmt.Sprintf( + "https://playwright.azureedge.net/builds/chromium/%d/chromium-linux-arm64.zip", + rev, + ) +} + +// DefaultBrowserDir for downloaded browser. For unix is "$HOME/.cache/rod/browser", +// for Windows it's "%APPDATA%\rod\browser". +var DefaultBrowserDir = filepath.Join(map[string]string{ + "windows": os.Getenv("APPDATA"), + "darwin": filepath.Join(os.Getenv("HOME"), ".cache"), + "linux": filepath.Join(os.Getenv("HOME"), ".cache"), +}[runtime.GOOS], "rod", "browser") + +// Browser is a helper to download browser smartly. +type Browser struct { + Context context.Context + + // Hosts are the candidates to download the browser. + // Such as [HostGoogle] or [HostNPM]. + Hosts []Host + + // Revision of the browser to use + Revision int + + // RootDir to download different browser versions. + RootDir string + + // Log to print output + Logger utils.Logger + + // LockPort a tcp port to prevent race downloading. Default is 2968 . + LockPort int + + // HTTPClient to download the browser + HTTPClient *http.Client +} + +// NewBrowser with default values. +func NewBrowser() *Browser { + return &Browser{ + Context: context.Background(), + Revision: RevisionDefault, + Hosts: []Host{HostGoogle, HostNPM, HostPlaywright}, + RootDir: DefaultBrowserDir, + Logger: log.New(os.Stdout, "[launcher.Browser]", log.LstdFlags), + LockPort: defaults.LockPort, + } +} + +// Dir to download the browser. +func (lc *Browser) Dir() string { + return filepath.Join(lc.RootDir, fmt.Sprintf("chromium-%d", lc.Revision)) +} + +// BinPath to download the browser executable. +func (lc *Browser) BinPath() string { + bin := map[string]string{ + "darwin": "Chromium.app/Contents/MacOS/Chromium", + "linux": "chrome", + "windows": "chrome.exe", + }[runtime.GOOS] + + return filepath.Join(lc.Dir(), filepath.FromSlash(bin)) +} + +// Download browser from the fastest host. +// It will race downloading a TCP packet from each host and use the fastest host. +func (lc *Browser) Download() error { + us := []string{} + for _, host := range lc.Hosts { + us = append(us, host(lc.Revision)) + } + + dir := lc.Dir() + + fu := fetchup.New(dir, us...) + fu.Ctx = lc.Context + fu.Logger = lc.Logger + if lc.HTTPClient != nil { + fu.HttpClient = lc.HTTPClient + } + + err := fu.Fetch() + if err != nil { + return fmt.Errorf("can't find a browser binary for your OS, the doc might help https://go-rod.github.io/#/compatibility?id=os : %w", err) //nolint: lll + } + + return fetchup.StripFirstDir(dir) +} + +// Get is a smart helper to get the browser executable path. +// If [Browser.BinPath] is not valid it will auto download the browser to [Browser.BinPath]. +func (lc *Browser) Get() (string, error) { + defer leakless.LockPort(lc.LockPort)() + + if lc.Validate() == nil { + return lc.BinPath(), nil + } + + // Try to cleanup before downloading + _ = os.RemoveAll(lc.Dir()) + + return lc.BinPath(), lc.Download() +} + +// MustGet is similar with Get. +func (lc *Browser) MustGet() string { + p, err := lc.Get() + utils.E(err) + return p +} + +// Validate returns nil if the browser executable is valid. +// If the executable is malformed it will return error. +func (lc *Browser) Validate() error { + _, err := os.Stat(lc.BinPath()) + if err != nil { + return err + } + + cmd := exec.Command(lc.BinPath(), "--headless", "--no-sandbox", + "--use-mock-keychain", "--disable-dev-shm-usage", + "--disable-gpu", "--dump-dom", "about:blank") + b, err := cmd.CombinedOutput() + if err != nil { + if strings.Contains(string(b), "error while loading shared libraries") { + // When the os is missing some dependencies for chromium we treat it as valid binary. + return nil + } + + return fmt.Errorf("failed to run the browser: %w\n%s", err, b) + } + if !bytes.Contains(b, []byte(``)) { + return errors.New("the browser executable doesn't support headless mode") + } + + return nil +} + +// LookPath searches for the browser executable from often used paths on current operating system. +func LookPath() (found string, has bool) { + list := map[string][]string{ + "darwin": { + "/Applications/Google Chrome.app/Contents/MacOS/Google Chrome", + "/Applications/Chromium.app/Contents/MacOS/Chromium", + "/Applications/Microsoft Edge.app/Contents/MacOS/Microsoft Edge", + "/Applications/Google Chrome Canary.app/Contents/MacOS/Google Chrome Canary", + "/usr/bin/google-chrome-stable", + "/usr/bin/google-chrome", + "/usr/bin/chromium", + "/usr/bin/chromium-browser", + }, + "linux": { + "chrome", + "google-chrome", + "/usr/bin/google-chrome", + "microsoft-edge", + "/usr/bin/microsoft-edge", + "chromium", + "chromium-browser", + "/usr/bin/google-chrome-stable", + "/usr/bin/chromium", + "/usr/bin/chromium-browser", + "/snap/bin/chromium", + "/data/data/com.termux/files/usr/bin/chromium-browser", + }, + "openbsd": { + "chrome", + "chromium", + }, + "windows": append([]string{"chrome", "edge"}, expandWindowsExePaths( + `Google\Chrome\Application\chrome.exe`, + `Chromium\Application\chrome.exe`, + `Microsoft\Edge\Application\msedge.exe`, + )...), + }[runtime.GOOS] + + for _, path := range list { + var err error + found, err = exec.LookPath(path) + has = err == nil + if has { + break + } + } + + return +} + +// interface for testing. +var openExec = exec.Command + +// Open tries to open the url via system's default browser. +func Open(url string) { + // Windows doesn't support format [::] + url = strings.Replace(url, "[::]", "[::1]", 1) + + if bin, has := LookPath(); has { + p := openExec(bin, url) + _ = p.Start() + _ = p.Process.Release() + } +} + +func expandWindowsExePaths(list ...string) []string { + newList := []string{} + for _, p := range list { + newList = append( + newList, + filepath.Join(os.Getenv("ProgramFiles"), p), + filepath.Join(os.Getenv("ProgramFiles(x86)"), p), + filepath.Join(os.Getenv("LocalAppData"), p), + ) + } + + return newList +} diff --git a/vendor/github.com/go-rod/rod/lib/launcher/error.go b/vendor/github.com/go-rod/rod/lib/launcher/error.go new file mode 100644 index 000000000..fd643b4c2 --- /dev/null +++ b/vendor/github.com/go-rod/rod/lib/launcher/error.go @@ -0,0 +1,6 @@ +package launcher + +import "errors" + +// ErrAlreadyLaunched is an error that indicates the launcher has already been launched. +var ErrAlreadyLaunched = errors.New("already launched") diff --git a/vendor/github.com/go-rod/rod/lib/launcher/flags/flags.go b/vendor/github.com/go-rod/rod/lib/launcher/flags/flags.go new file mode 100644 index 000000000..f21605da7 --- /dev/null +++ b/vendor/github.com/go-rod/rod/lib/launcher/flags/flags.go @@ -0,0 +1,70 @@ +// Package flags ... +package flags + +import "strings" + +// Flag name of a command line argument of the browser, also known as command line flag or switch. +// List of available flags: https://peter.sh/experiments/chromium-command-line-switches +type Flag string + +// TODO: we should automatically generate all the flags here. +const ( + // UserDataDir https://chromium.googlesource.com/chromium/src/+/master/docs/user_data_dir.md + UserDataDir Flag = "user-data-dir" + + // Headless mode. Whether to run browser in headless mode. A mode without visible UI. + Headless Flag = "headless" + + // App flag. + App Flag = "app" + + // RemoteDebuggingPort flag. + RemoteDebuggingPort Flag = "remote-debugging-port" + + // NoSandbox flag. + NoSandbox Flag = "no-sandbox" + + // ProxyServer flag. + ProxyServer Flag = "proxy-server" + + // WorkingDir flag. + WorkingDir Flag = "rod-working-dir" + + // Env flag. + Env Flag = "rod-env" + + // XVFB flag. + XVFB Flag = "rod-xvfb" + + // ProfileDir flag. + ProfileDir = "profile-directory" + + // Preferences flag. + Preferences Flag = "rod-preferences" + + // Leakless flag. + Leakless Flag = "rod-leakless" + + // Bin is the browser executable file path. If it's empty, launcher will automatically search or download the bin. + Bin Flag = "rod-bin" + + // KeepUserDataDir flag. + KeepUserDataDir Flag = "rod-keep-user-data-dir" + + // Arguments for the command. Such as + // chrome-bin http://a.com http://b.com + // The "http://a.com" and "http://b.com" are the arguments. + Arguments Flag = "" +) + +// Check if the flag name is valid. +func (f Flag) Check() { + if strings.Contains(string(f), "=") { + panic("flag name should not contain '='") + } +} + +// NormalizeFlag normalize the flag name, remove the leading dash. +func (f Flag) NormalizeFlag() Flag { + return Flag(strings.TrimLeft(string(f), "-")) +} diff --git a/vendor/github.com/go-rod/rod/lib/launcher/launcher.go b/vendor/github.com/go-rod/rod/lib/launcher/launcher.go new file mode 100644 index 000000000..92436c84f --- /dev/null +++ b/vendor/github.com/go-rod/rod/lib/launcher/launcher.go @@ -0,0 +1,556 @@ +// Package launcher for launching browser utils. +package launcher + +import ( + "context" + "crypto" + "errors" + "fmt" + "io" + "os" + "os/exec" + "path/filepath" + "sort" + "strings" + "sync/atomic" + + "github.com/go-rod/rod/lib/defaults" + "github.com/go-rod/rod/lib/launcher/flags" + "github.com/go-rod/rod/lib/utils" + "github.com/ysmood/leakless" +) + +// DefaultUserDataDirPrefix ... +var DefaultUserDataDirPrefix = filepath.Join(os.TempDir(), "rod", "user-data") + +// Launcher is a helper to launch browser binary smartly. +type Launcher struct { + Flags map[flags.Flag][]string `json:"flags"` + + ctx context.Context + ctxCancel func() + + logger io.Writer + + browser *Browser + parser *URLParser + pid int + exit chan struct{} + + managed bool + serviceURL string + + isLaunched int32 // zero means not launched +} + +// New returns the default arguments to start browser. +// Headless will be enabled by default. +// Leakless will be enabled by default. +// UserDataDir will use OS tmp dir by default, this folder will usually be cleaned up by the OS after reboot. +// It will auto download the browser binary according to the current platform, +// check [Launcher.Bin] and [Launcher.Revision] for more info. +func New() *Launcher { + dir := defaults.Dir + if dir == "" { + dir = filepath.Join(DefaultUserDataDirPrefix, utils.RandString(8)) + } + + defaultFlags := map[flags.Flag][]string{ + flags.Bin: {defaults.Bin}, + flags.Leakless: nil, + + flags.UserDataDir: {dir}, + + // use random port by default + flags.RemoteDebuggingPort: {defaults.Port}, + + // enable headless by default + flags.Headless: nil, + + // to disable the init blank window + "no-first-run": nil, + "no-startup-window": nil, + + // TODO: about the "site-per-process" see https://github.com/puppeteer/puppeteer/issues/2548 + "disable-features": {"site-per-process", "TranslateUI"}, + + "disable-dev-shm-usage": nil, + "disable-background-networking": nil, + "disable-background-timer-throttling": nil, + "disable-backgrounding-occluded-windows": nil, + "disable-breakpad": nil, + "disable-client-side-phishing-detection": nil, + "disable-component-extensions-with-background-pages": nil, + "disable-default-apps": nil, + "disable-hang-monitor": nil, + "disable-ipc-flooding-protection": nil, + "disable-popup-blocking": nil, + "disable-prompt-on-repost": nil, + "disable-renderer-backgrounding": nil, + "disable-sync": nil, + "disable-site-isolation-trials": nil, + "enable-automation": nil, + "enable-features": {"NetworkService", "NetworkServiceInProcess"}, + "force-color-profile": {"srgb"}, + "metrics-recording-only": nil, + "use-mock-keychain": nil, + } + + if defaults.Show { + delete(defaultFlags, flags.Headless) + } + if defaults.Devtools { + defaultFlags["auto-open-devtools-for-tabs"] = nil + } + if inContainer { + defaultFlags[flags.NoSandbox] = nil + } + if defaults.Proxy != "" { + defaultFlags[flags.ProxyServer] = []string{defaults.Proxy} + } + + ctx, cancel := context.WithCancel(context.Background()) + return &Launcher{ + ctx: ctx, + ctxCancel: cancel, + Flags: defaultFlags, + exit: make(chan struct{}), + browser: NewBrowser(), + parser: NewURLParser(), + logger: io.Discard, + } +} + +// NewUserMode is a preset to enable reusing current user data. Useful for automation of personal browser. +// If you see any error, it may because you can't launch debug port for existing browser, the solution is to +// completely close the running browser. Unfortunately, there's no API for rod to tell it automatically yet. +func NewUserMode() *Launcher { + ctx, cancel := context.WithCancel(context.Background()) + bin, _ := LookPath() + + return &Launcher{ + ctx: ctx, + ctxCancel: cancel, + Flags: map[flags.Flag][]string{ + flags.RemoteDebuggingPort: {"37712"}, + "no-startup-window": nil, + flags.Bin: {bin}, + }, + browser: NewBrowser(), + exit: make(chan struct{}), + parser: NewURLParser(), + logger: io.Discard, + } +} + +// NewAppMode is a preset to run the browser like a native application. +// The u should be a URL. +func NewAppMode(u string) *Launcher { + l := New() + l.Set(flags.App, u). + Set(flags.Env, "GOOGLE_API_KEY=no"). + Headless(false). + Delete("no-startup-window"). + Delete("enable-automation") + return l +} + +// Context sets the context. +func (l *Launcher) Context(ctx context.Context) *Launcher { + ctx, cancel := context.WithCancel(ctx) + l.ctx = ctx + l.parser.Context(ctx) + l.ctxCancel = cancel + return l +} + +// Set a command line argument when launching the browser. +// Be careful the first argument is a flag name, it shouldn't contain values. The values the will be joined with comma. +// A flag can have multiple values. If no values are provided the flag will be a boolean flag. +// You can use the [Launcher.FormatArgs] to debug the final CLI arguments. +// List of available flags: https://peter.sh/experiments/chromium-command-line-switches +func (l *Launcher) Set(name flags.Flag, values ...string) *Launcher { + name.Check() + l.Flags[name.NormalizeFlag()] = values + return l +} + +// Get flag's first value. +func (l *Launcher) Get(name flags.Flag) string { + if list, has := l.GetFlags(name); has { + return list[0] + } + return "" +} + +// Has flag or not. +func (l *Launcher) Has(name flags.Flag) bool { + _, has := l.GetFlags(name) + return has +} + +// GetFlags from settings. +func (l *Launcher) GetFlags(name flags.Flag) ([]string, bool) { + flag, has := l.Flags[name.NormalizeFlag()] + return flag, has +} + +// Append values to the flag. +func (l *Launcher) Append(name flags.Flag, values ...string) *Launcher { + flags, has := l.GetFlags(name) + if !has { + flags = []string{} + } + return l.Set(name, append(flags, values...)...) +} + +// Delete a flag. +func (l *Launcher) Delete(name flags.Flag) *Launcher { + delete(l.Flags, name.NormalizeFlag()) + return l +} + +// Bin of the browser binary path to launch, if the path is not empty the auto download will be disabled. +func (l *Launcher) Bin(path string) *Launcher { + return l.Set(flags.Bin, path) +} + +// Revision of the browser to auto download. +func (l *Launcher) Revision(rev int) *Launcher { + l.browser.Revision = rev + return l +} + +// Headless switch. Whether to run browser in headless mode. A mode without visible UI. +func (l *Launcher) Headless(enable bool) *Launcher { + if enable { + return l.Set(flags.Headless) + } + return l.Delete(flags.Headless) +} + +// HeadlessNew switch is the "--headless=new" switch: https://developer.chrome.com/docs/chromium/new-headless +func (l *Launcher) HeadlessNew(enable bool) *Launcher { + if enable { + return l.Set(flags.Headless, "new") + } + return l.Delete(flags.Headless) +} + +// NoSandbox switch. Whether to run browser in no-sandbox mode. +// Linux users may face "running as root without --no-sandbox is not supported" in some Linux/Chrome combinations. +// This function helps switch mode easily. +// Be aware disabling sandbox is not trivial. Use at your own risk. +// Related doc: https://bugs.chromium.org/p/chromium/issues/detail?id=638180 +func (l *Launcher) NoSandbox(enable bool) *Launcher { + if enable { + return l.Set(flags.NoSandbox) + } + return l.Delete(flags.NoSandbox) +} + +// XVFB enables to run browser in by XVFB. Useful when you want to run headful mode on linux. +func (l *Launcher) XVFB(args ...string) *Launcher { + return l.Set(flags.XVFB, args...) +} + +// Preferences set chromium user preferences, such as set the default search engine or disable the pdf viewer. +// The pref is a json string, the doc is here +// https://src.chromium.org/viewvc/chrome/trunk/src/chrome/common/pref_names.cc +func (l *Launcher) Preferences(pref string) *Launcher { + return l.Set(flags.Preferences, pref) +} + +// AlwaysOpenPDFExternally switch. +// It will set chromium user preferences to enable the always_open_pdf_externally option. +func (l *Launcher) AlwaysOpenPDFExternally() *Launcher { + return l.Set(flags.Preferences, `{"plugins":{"always_open_pdf_externally": true}}`) +} + +// Leakless switch. If enabled, the browser will be force killed after the Go process exits. +// The doc of leakless: https://github.com/ysmood/leakless. +func (l *Launcher) Leakless(enable bool) *Launcher { + if enable { + return l.Set(flags.Leakless) + } + return l.Delete(flags.Leakless) +} + +// Devtools switch to auto open devtools for each tab. +func (l *Launcher) Devtools(autoOpenForTabs bool) *Launcher { + if autoOpenForTabs { + return l.Set("auto-open-devtools-for-tabs") + } + return l.Delete("auto-open-devtools-for-tabs") +} + +// IgnoreCerts configure the Chrome's ignore-certificate-errors-spki-list argument with the public keys. +func (l *Launcher) IgnoreCerts(pks []crypto.PublicKey) error { + spkis := make([]string, 0, len(pks)) + + for _, pk := range pks { + spki, err := certSPKI(pk) + if err != nil { + return fmt.Errorf("certSPKI: %w", err) + } + spkis = append(spkis, string(spki)) + } + + l.Set("ignore-certificate-errors-spki-list", spkis...) + + return nil +} + +// UserDataDir is where the browser will look for all of its state, such as cookie and cache. +// When set to empty, browser will use current OS home dir. +// Related doc: https://chromium.googlesource.com/chromium/src/+/master/docs/user_data_dir.md +func (l *Launcher) UserDataDir(dir string) *Launcher { + if dir == "" { + l.Delete(flags.UserDataDir) + } else { + l.Set(flags.UserDataDir, dir) + } + return l +} + +// ProfileDir is the browser profile the browser will use. +// When set to empty, the profile 'Default' is used. +// Related article: https://superuser.com/a/377195 +func (l *Launcher) ProfileDir(dir string) *Launcher { + if dir == "" { + l.Delete(flags.ProfileDir) + } else { + l.Set(flags.ProfileDir, dir) + } + return l +} + +// RemoteDebuggingPort to launch the browser. Zero for a random port. Zero is the default value. +// If it's not zero and the Launcher.Leakless is disabled, the launcher will try to reconnect to it first, +// if the reconnection fails it will launch a new browser. +func (l *Launcher) RemoteDebuggingPort(port int) *Launcher { + return l.Set(flags.RemoteDebuggingPort, fmt.Sprintf("%d", port)) +} + +// Proxy for the browser. +func (l *Launcher) Proxy(host string) *Launcher { + return l.Set(flags.ProxyServer, host) +} + +// WorkingDir to launch the browser process. +func (l *Launcher) WorkingDir(path string) *Launcher { + return l.Set(flags.WorkingDir, path) +} + +// Env to launch the browser process. The default value is [os.Environ](). +// Usually you use it to set the timezone env. Such as: +// +// Env(append(os.Environ(), "TZ=Asia/Tokyo")...) +func (l *Launcher) Env(env ...string) *Launcher { + return l.Set(flags.Env, env...) +} + +// StartURL to launch. +func (l *Launcher) StartURL(u string) *Launcher { + return l.Set("", u) +} + +// FormatArgs returns the formatted arg list for cli. +func (l *Launcher) FormatArgs() []string { + execArgs := []string{} + for k, v := range l.Flags { + if k == flags.Arguments { + continue + } + + if strings.HasPrefix(string(k), "rod-") { + continue + } + + // fix a bug of chrome, if path is not absolute chrome will hang + if k == flags.UserDataDir { + abs, err := filepath.Abs(v[0]) + utils.E(err) + v[0] = abs + } + + str := "--" + string(k) + if v != nil { + str += "=" + strings.Join(v, ",") + } + execArgs = append(execArgs, str) + } + + execArgs = append(execArgs, l.Flags[flags.Arguments]...) + sort.Strings(execArgs) + return execArgs +} + +// Logger to handle stdout and stderr from browser. +// For example, pipe all browser output to stdout: +// +// launcher.New().Logger(os.Stdout) +func (l *Launcher) Logger(w io.Writer) *Launcher { + l.logger = w + return l +} + +// MustLaunch is similar to Launch. +func (l *Launcher) MustLaunch() string { + u, err := l.Launch() + utils.E(err) + return u +} + +// Launch a standalone temp browser instance and returns the debug url. +// bin and profileDir are optional, set them to empty to use the default values. +// If you want to reuse sessions, such as cookies, set the [Launcher.UserDataDir] to the same location. +// +// Please note launcher can only be used once. +func (l *Launcher) Launch() (string, error) { + if l.hasLaunched() { + return "", ErrAlreadyLaunched + } + + defer l.ctxCancel() + + bin, err := l.getBin() + if err != nil { + return "", err + } + + l.setupUserPreferences() + + var ll *leakless.Launcher + var cmd *exec.Cmd + + args := l.FormatArgs() + + if l.Has(flags.Leakless) && leakless.Support() { + ll = leakless.New() + cmd = ll.Command(bin, args...) + } else { + port := l.Get(flags.RemoteDebuggingPort) + u, err := ResolveURL(port) + if err == nil { + return u, nil + } + cmd = exec.Command(bin, args...) + } + + l.setupCmd(cmd) + + err = cmd.Start() + if err != nil { + return "", err + } + + if ll == nil { + l.pid = cmd.Process.Pid + } else { + l.pid = <-ll.Pid() + if ll.Err() != "" { + return "", errors.New(ll.Err()) + } + } + + go func() { + _ = cmd.Wait() + close(l.exit) + }() + + u, err := l.getURL() + if err != nil { + l.Kill() + return "", err + } + + return ResolveURL(u) +} + +func (l *Launcher) hasLaunched() bool { + return !atomic.CompareAndSwapInt32(&l.isLaunched, 0, 1) +} + +func (l *Launcher) setupUserPreferences() { + userDir := l.Get(flags.UserDataDir) + pref := l.Get(flags.Preferences) + + if userDir == "" || pref == "" { + return + } + + userDir, err := filepath.Abs(userDir) + utils.E(err) + + profile := l.Get(flags.ProfileDir) + if profile == "" { + profile = "Default" + } + + path := filepath.Join(userDir, profile, "Preferences") + + utils.E(utils.OutputFile(path, pref)) +} + +func (l *Launcher) setupCmd(cmd *exec.Cmd) { + l.osSetupCmd(cmd) + + dir := l.Get(flags.WorkingDir) + env, _ := l.GetFlags(flags.Env) + cmd.Dir = dir + cmd.Env = env + + cmd.Stdout = io.MultiWriter(l.logger, l.parser) + cmd.Stderr = io.MultiWriter(l.logger, l.parser) +} + +func (l *Launcher) getBin() (string, error) { + bin := l.Get(flags.Bin) + if bin == "" { + l.browser.Context = l.ctx + return l.browser.Get() + } + return bin, nil +} + +func (l *Launcher) getURL() (u string, err error) { + select { + case <-l.ctx.Done(): + err = l.ctx.Err() + case u = <-l.parser.URL: + case <-l.exit: + err = l.parser.Err() + } + return +} + +// PID returns the browser process pid. +func (l *Launcher) PID() int { + return l.pid +} + +// Kill the browser process. +func (l *Launcher) Kill() { + // TODO: If kill too fast, the browser's children processes may not be ready. + // Browser don't have an API to tell if the children processes are ready. + utils.Sleep(1) + + if l.PID() == 0 { // avoid killing the current process + return + } + + killGroup(l.PID()) + p, err := os.FindProcess(l.PID()) + if err == nil { + _ = p.Kill() + } +} + +// Cleanup wait until the Browser exits and remove [flags.UserDataDir]. +func (l *Launcher) Cleanup() { + <-l.exit + + dir := l.Get(flags.UserDataDir) + _ = os.RemoveAll(dir) +} diff --git a/vendor/github.com/go-rod/rod/lib/launcher/manager.go b/vendor/github.com/go-rod/rod/lib/launcher/manager.go new file mode 100644 index 000000000..1fbc7daf8 --- /dev/null +++ b/vendor/github.com/go-rod/rod/lib/launcher/manager.go @@ -0,0 +1,211 @@ +package launcher + +import ( + "encoding/json" + "fmt" + "net/http" + "net/http/httputil" + "net/url" + "os" + "strings" + + "github.com/go-rod/rod/lib/cdp" + "github.com/go-rod/rod/lib/launcher/flags" + "github.com/go-rod/rod/lib/utils" +) + +const ( + // HeaderName for remote launch. + HeaderName = "Rod-Launcher" +) + +// MustNewManaged is similar to NewManaged. +func MustNewManaged(serviceURL string) *Launcher { + l, err := NewManaged(serviceURL) + utils.E(err) + + // TODO: remove this after we have a better way to handle this + // The latest chromium in docker will crash pages that use http2 + l.Set("disable-http2") + + return l +} + +// NewManaged creates a default Launcher instance from launcher.Manager. +// The serviceURL must point to a launcher.Manager. It will send a http request to the serviceURL +// to get the default settings of the Launcher instance. For example if the launcher.Manager running on a +// Linux machine will return different default settings from the one on Mac. +// If Launcher.Leakless is enabled, the remote browser will be killed after the websocket is closed. +func NewManaged(serviceURL string) (*Launcher, error) { + if serviceURL == "" { + serviceURL = "ws://127.0.0.1:7317" + } + + u, err := url.Parse(serviceURL) + if err != nil { + return nil, err + } + + l := New() + l.managed = true + l.serviceURL = toWS(*u).String() + l.Flags = nil + + res, err := http.Get(toHTTP(*u).String()) //nolint: noctx + if err != nil { + return nil, err + } + defer func() { _ = res.Body.Close() }() + + return l, json.NewDecoder(res.Body).Decode(l) +} + +// KeepUserDataDir after remote browser is closed. By default launcher.FlagUserDataDir will be removed. +func (l *Launcher) KeepUserDataDir() *Launcher { + l.mustManaged() + l.Set(flags.KeepUserDataDir) + return l +} + +// JSON serialization. +func (l *Launcher) JSON() []byte { + return utils.MustToJSONBytes(l) +} + +// MustClient similar to Launcher.Client. +func (l *Launcher) MustClient() *cdp.Client { + u, h := l.ClientHeader() + return cdp.MustStartWithURL(l.ctx, u, h) +} + +// Client for launching browser remotely via the launcher.Manager. +func (l *Launcher) Client() (*cdp.Client, error) { + u, h := l.ClientHeader() + return cdp.StartWithURL(l.ctx, u, h) +} + +// ClientHeader for launching browser remotely via the launcher.Manager. +func (l *Launcher) ClientHeader() (string, http.Header) { + l.mustManaged() + header := http.Header{} + header.Add(string(HeaderName), utils.MustToJSON(l)) + return l.serviceURL, header +} + +func (l *Launcher) mustManaged() { + if !l.managed { + panic("Must be used with launcher.NewManaged") + } +} + +var _ http.Handler = &Manager{} + +// Manager is used to launch browsers via http server on another machine. +// The reason why we have Manager is after we launcher a browser, we can't dynamically change its +// CLI arguments, such as "--headless". The Manager allows us to decide what CLI arguments to +// pass to the browser when launch it remotely. +// The work flow looks like: +// +// | Machine X | Machine Y | +// | NewManaged("a.com") -|-> http.ListenAndServe("a.com", launcher.NewManager()) --> launch browser | +// +// 1. X send a http request to Y, Y respond default Launcher settings based the OS of Y. +// 2. X start a websocket connect to Y with the Launcher settings +// 3. Y launches a browser with the Launcher settings X +// 4. Y transparently proxy the websocket connect between X and the launched browser +type Manager struct { + // Logger for key events + Logger utils.Logger + + // Defaults should return the default Launcher settings + Defaults func(http.ResponseWriter, *http.Request) *Launcher + + // BeforeLaunch hook is called right before the launching with the Launcher instance that will be used + // to launch the browser. + // Such as use it to filter malicious values of Launcher.UserDataDir, Launcher.Bin, or Launcher.WorkingDir. + BeforeLaunch func(*Launcher, http.ResponseWriter, *http.Request) +} + +// NewManager instance. +func NewManager() *Manager { + allowedPath := map[flags.Flag]string{ + flags.Bin: DefaultBrowserDir, + flags.WorkingDir: func() string { + p, _ := os.Getwd() + return p + }(), + flags.UserDataDir: DefaultUserDataDirPrefix, + } + + return &Manager{ + Logger: utils.LoggerQuiet, + Defaults: func(_ http.ResponseWriter, _ *http.Request) *Launcher { return New() }, + BeforeLaunch: func(l *Launcher, w http.ResponseWriter, _ *http.Request) { + for f, allowed := range allowedPath { + p := l.Get(f) + if p != "" && !strings.HasPrefix(p, allowed) { + b := []byte(fmt.Sprintf("[rod-manager] not allowed %s path: %s (use --allow-all to disable the protection)", f, p)) + w.Header().Add("Content-Length", fmt.Sprintf("%d", len(b))) + w.WriteHeader(http.StatusBadRequest) + utils.E(w.Write(b)) + w.(http.Flusher).Flush() //nolint: forcetypeassert + panic(http.ErrAbortHandler) + } + } + }, + } +} + +func (m *Manager) ServeHTTP(w http.ResponseWriter, r *http.Request) { + if r.Header.Get("Upgrade") == "websocket" { + m.launch(w, r) + return + } + + l := m.Defaults(w, r) + utils.E(w.Write(l.JSON())) +} + +func (m *Manager) launch(w http.ResponseWriter, r *http.Request) { + l := New() + + options := r.Header.Get(string(HeaderName)) + if options != "" { + l.Flags = nil + utils.E(json.Unmarshal([]byte(options), l)) + } + + m.BeforeLaunch(l, w, r) + + kill := l.Has(flags.Leakless) + + // Always enable leakless so that if the Manager process crashes + // all the managed browsers will be killed. + u := l.Leakless(true).MustLaunch() + defer m.cleanup(l, kill) + + parsedURL, err := url.Parse(u) + utils.E(err) + + m.Logger.Println("Launch", u, options) + defer m.Logger.Println("Close", u) + + parsedWS, err := url.Parse(u) + utils.E(err) + parsedURL.Path = parsedWS.Path + + httputil.NewSingleHostReverseProxy(toHTTP(*parsedURL)).ServeHTTP(w, r) +} + +func (m *Manager) cleanup(l *Launcher, kill bool) { + if kill { + l.Kill() + m.Logger.Println("Killed PID:", l.PID()) + } + + if !l.Has(flags.KeepUserDataDir) { + l.Cleanup() + dir := l.Get(flags.UserDataDir) + m.Logger.Println("Removed", dir) + } +} diff --git a/vendor/github.com/go-rod/rod/lib/launcher/os_unix.go b/vendor/github.com/go-rod/rod/lib/launcher/os_unix.go new file mode 100644 index 000000000..68f0d675e --- /dev/null +++ b/vendor/github.com/go-rod/rod/lib/launcher/os_unix.go @@ -0,0 +1,26 @@ +//go:build !windows + +package launcher + +import ( + "os/exec" + "syscall" + + "github.com/go-rod/rod/lib/launcher/flags" +) + +func killGroup(pid int) { + _ = syscall.Kill(-pid, syscall.SIGKILL) +} + +func (l *Launcher) osSetupCmd(cmd *exec.Cmd) { + if flags, has := l.GetFlags(flags.XVFB); has { + var command []string + // flags must append before cmd.Args + command = append(command, flags...) + command = append(command, cmd.Args...) + + *cmd = *exec.Command("xvfb-run", command...) + } + cmd.SysProcAttr = &syscall.SysProcAttr{Setpgid: true} +} diff --git a/vendor/github.com/go-rod/rod/lib/launcher/os_windows.go b/vendor/github.com/go-rod/rod/lib/launcher/os_windows.go new file mode 100644 index 000000000..e7be8a69d --- /dev/null +++ b/vendor/github.com/go-rod/rod/lib/launcher/os_windows.go @@ -0,0 +1,28 @@ +//go:build windows + +package launcher + +import ( + "os/exec" + "syscall" +) + +func killGroup(pid int) { + terminateProcess(pid) +} + +func (l *Launcher) osSetupCmd(cmd *exec.Cmd) { + cmd.SysProcAttr = &syscall.SysProcAttr{ + CreationFlags: syscall.CREATE_NEW_PROCESS_GROUP, + } +} + +func terminateProcess(pid int) { + handle, err := syscall.OpenProcess(syscall.PROCESS_TERMINATE, true, uint32(pid)) + if err != nil { + return + } + + _ = syscall.TerminateProcess(handle, 0) + _ = syscall.CloseHandle(handle) +} diff --git a/vendor/github.com/go-rod/rod/lib/launcher/revision.go b/vendor/github.com/go-rod/rod/lib/launcher/revision.go new file mode 100644 index 000000000..d4b66b635 --- /dev/null +++ b/vendor/github.com/go-rod/rod/lib/launcher/revision.go @@ -0,0 +1,9 @@ +// generated by "lib/launcher/revision" + +package launcher + +// RevisionDefault for chromium. +const RevisionDefault = 1321438 + +// RevisionPlaywright for arm linux. +const RevisionPlaywright = 1124 diff --git a/vendor/github.com/go-rod/rod/lib/launcher/url_parser.go b/vendor/github.com/go-rod/rod/lib/launcher/url_parser.go new file mode 100644 index 000000000..3df8dd5d5 --- /dev/null +++ b/vendor/github.com/go-rod/rod/lib/launcher/url_parser.go @@ -0,0 +1,139 @@ +package launcher + +import ( + "context" + "errors" + "io" + "net/http" + "net/url" + "regexp" + "strings" + "sync" + + "github.com/go-rod/rod/lib/utils" + "github.com/ysmood/gson" +) + +var _ io.Writer = &URLParser{} + +// URLParser to get control url from stderr. +type URLParser struct { + URL chan string + Buffer string // buffer for the browser stdout + + lock *sync.Mutex + ctx context.Context + done bool +} + +// NewURLParser instance. +func NewURLParser() *URLParser { + return &URLParser{ + URL: make(chan string), + lock: &sync.Mutex{}, + ctx: context.Background(), + } +} + +var regWS = regexp.MustCompile(`ws://.+/`) + +// Context sets the context. +func (r *URLParser) Context(ctx context.Context) *URLParser { + r.ctx = ctx + return r +} + +// Write interface. +func (r *URLParser) Write(p []byte) (n int, err error) { + r.lock.Lock() + defer r.lock.Unlock() + + if !r.done { + r.Buffer += string(p) + + str := regWS.FindString(r.Buffer) + if str != "" { + u, err := url.Parse(strings.TrimSpace(str)) + utils.E(err) + + select { + case <-r.ctx.Done(): + case r.URL <- "http://" + u.Host: + } + + r.done = true + r.Buffer = "" + } + } + + return len(p), nil +} + +// Err returns the common error parsed from stdout and stderr. +func (r *URLParser) Err() error { + r.lock.Lock() + defer r.lock.Unlock() + + msg := "[launcher] Failed to get the debug url: " + + if strings.Contains(r.Buffer, "error while loading shared libraries") { + msg = "[launcher] Failed to launch the browser, the doc might help https://go-rod.github.io/#/compatibility?id=os: " + } + + return errors.New(msg + r.Buffer) +} + +// MustResolveURL is similar to ResolveURL. +func MustResolveURL(u string) string { + u, err := ResolveURL(u) + utils.E(err) + return u +} + +var ( + regPort = regexp.MustCompile(`^\:?(\d+)$`) + regProtocol = regexp.MustCompile(`^\w+://`) +) + +// ResolveURL by requesting the u, it will try best to normalize the u. +// The format of u can be "9222", ":9222", "host:9222", "ws://host:9222", "wss://host:9222", +// "https://host:9222" "http://host:9222". The return string will look like: +// "ws://host:9222/devtools/browser/4371405f-84df-4ad6-9e0f-eab81f7521cc" +func ResolveURL(u string) (string, error) { + if u == "" { + u = "9222" + } + + u = strings.TrimSpace(u) + u = regPort.ReplaceAllString(u, "127.0.0.1:$1") + + if !regProtocol.MatchString(u) { + u = "http://" + u + } + + parsed, err := url.Parse(u) + if err != nil { + return "", err + } + + parsed = toHTTP(*parsed) + parsed.Path = "/json/version" + + res, err := http.Get(parsed.String()) //nolint: noctx + if err != nil { + return "", err + } + defer func() { _ = res.Body.Close() }() + + data, err := io.ReadAll(res.Body) + utils.E(err) + + wsURL := gson.New(data).Get("webSocketDebuggerUrl").Str() + + parsedWS, err := url.Parse(wsURL) + utils.E(err) + + parsedWS.Host = parsed.Host + + return parsedWS.String(), nil +} diff --git a/vendor/github.com/go-rod/rod/lib/launcher/utils.go b/vendor/github.com/go-rod/rod/lib/launcher/utils.go new file mode 100644 index 000000000..421d98aaa --- /dev/null +++ b/vendor/github.com/go-rod/rod/lib/launcher/utils.go @@ -0,0 +1,49 @@ +package launcher + +import ( + "crypto" + "crypto/sha256" + "crypto/x509" + "encoding/base64" + "fmt" + "net/url" + + "github.com/go-rod/rod/lib/utils" +) + +var inContainer = utils.InContainer + +func toHTTP(u url.URL) *url.URL { + newURL := u + if newURL.Scheme == "ws" { + newURL.Scheme = "http" + } else if newURL.Scheme == "wss" { + newURL.Scheme = "https" + } + return &newURL +} + +func toWS(u url.URL) *url.URL { + newURL := u + if newURL.Scheme == "http" { + newURL.Scheme = "ws" + } else if newURL.Scheme == "https" { + newURL.Scheme = "wss" + } + return &newURL +} + +// certSPKI generates the SPKI of a certificate public key +// https://blog.afoolishmanifesto.com/posts/golang-self-signed-and-pinned-certs/ +func certSPKI(pk crypto.PublicKey) ([]byte, error) { + pubDER, err := x509.MarshalPKIXPublicKey(pk) + if err != nil { + return nil, fmt.Errorf("x509.MarshalPKIXPublicKey: %w", err) + } + + sum := sha256.Sum256(pubDER) + pin := make([]byte, base64.StdEncoding.EncodedLen(len(sum))) + base64.StdEncoding.Encode(pin, sum[:]) + + return pin, nil +} diff --git a/vendor/github.com/go-rod/rod/lib/proto/README.md b/vendor/github.com/go-rod/rod/lib/proto/README.md new file mode 100644 index 000000000..b1a78920c --- /dev/null +++ b/vendor/github.com/go-rod/rod/lib/proto/README.md @@ -0,0 +1,7 @@ +# Overview + +A lib to encode/decode the data of the cdp protocol. + +This lib is standalone and stateless, you can use it independently. Such as use it to encode/decode JSON with other libs that can drive browsers. + +Here's an [usage example](https://github.com/go-rod/rod/blob/9e847f3bab313a1d233c0c868fe5125e2e70de70/examples_test.go#L370-L393). diff --git a/vendor/github.com/go-rod/rod/lib/proto/a_interface.go b/vendor/github.com/go-rod/rod/lib/proto/a_interface.go new file mode 100644 index 000000000..eb793f2ae --- /dev/null +++ b/vendor/github.com/go-rod/rod/lib/proto/a_interface.go @@ -0,0 +1,71 @@ +// Package proto is a lib to encode/decode the data of the cdp protocol. +package proto + +import ( + "context" + "encoding/json" + "reflect" + "strings" +) + +// Client interface to send the request. +// So that this lib doesn't handle anything has side effect. +type Client interface { + Call(ctx context.Context, sessionID, methodName string, params interface{}) (res []byte, err error) +} + +// Sessionable type has a proto.TargetSessionID for its methods. +type Sessionable interface { + GetSessionID() TargetSessionID +} + +// Contextable type has a context.Context for its methods. +type Contextable interface { + GetContext() context.Context +} + +// Request represents a cdp.Request.Method. +type Request interface { + // ProtoReq returns the cdp.Request.Method + ProtoReq() string +} + +// Event represents a cdp.Event.Params. +type Event interface { + // ProtoEvent returns the cdp.Event.Method + ProtoEvent() string +} + +// GetType from method name of this package, +// such as proto.GetType("Page.enable") will return the type of proto.PageEnable. +func GetType(methodName string) reflect.Type { + return types[methodName] +} + +// ParseMethodName to domain and name. +func ParseMethodName(method string) (domain, name string) { + arr := strings.Split(method, ".") + return arr[0], arr[1] +} + +// call method with request and response containers. +func call(method string, req, res interface{}, c Client) error { + ctx := context.Background() + if cta, ok := c.(Contextable); ok { + ctx = cta.GetContext() + } + + sessionID := "" + if tsa, ok := c.(Sessionable); ok { + sessionID = string(tsa.GetSessionID()) + } + + bin, err := c.Call(ctx, sessionID, method, req) + if err != nil { + return err + } + if res == nil { + return nil + } + return json.Unmarshal(bin, res) +} diff --git a/vendor/github.com/go-rod/rod/lib/proto/a_patch.go b/vendor/github.com/go-rod/rod/lib/proto/a_patch.go new file mode 100644 index 000000000..3d5c5d54f --- /dev/null +++ b/vendor/github.com/go-rod/rod/lib/proto/a_patch.go @@ -0,0 +1,180 @@ +// Patches to normalize the proto types + +package proto + +import ( + "time" +) + +// TimeSinceEpoch UTC time in seconds, counted from January 1, 1970. +// To convert a time.Time to TimeSinceEpoch, for example: +// +// proto.TimeSinceEpoch(time.Now().Unix()) +// +// For session cookie, the value should be -1. +type TimeSinceEpoch float64 + +// Time interface. +func (t TimeSinceEpoch) Time() time.Time { + return (time.Unix(0, 0)).Add( + time.Duration(t * TimeSinceEpoch(time.Second)), + ) +} + +// String interface. +func (t TimeSinceEpoch) String() string { + return t.Time().String() +} + +// MonotonicTime Monotonically increasing time in seconds since an arbitrary point in the past. +type MonotonicTime float64 + +// Duration interface. +func (t MonotonicTime) Duration() time.Duration { + return time.Duration(t * MonotonicTime(time.Second)) +} + +// String interface. +func (t MonotonicTime) String() string { + return t.Duration().String() +} + +// Point from the origin (0, 0). +type Point struct { + X float64 `json:"x"` + Y float64 `json:"y"` +} + +// NewPoint instance. +func NewPoint(x, y float64) Point { + return Point{x, y} +} + +// Add v to p and returns a new Point. +func (p Point) Add(v Point) Point { + return NewPoint(p.X+v.X, p.Y+v.Y) +} + +// Minus v from p and returns a new Point. +func (p Point) Minus(v Point) Point { + return NewPoint(p.X-v.X, p.Y-v.Y) +} + +// Scale p with s and returns a new Point. +func (p Point) Scale(s float64) Point { + return NewPoint(p.X*s, p.Y*s) +} + +// Len is the number of vertices. +func (q DOMQuad) Len() int { + return len(q) / 2 //nolint: mnd +} + +// Each point. +func (q DOMQuad) Each(fn func(pt Point, i int)) { + for i := 0; i < q.Len(); i++ { + fn(Point{q[i*2], q[i*2+1]}, i) + } +} + +// Center of the polygon. +func (q DOMQuad) Center() Point { + var x, y float64 + q.Each(func(pt Point, _ int) { + x += pt.X + y += pt.Y + }) + return Point{x / float64(q.Len()), y / float64(q.Len())} +} + +// Area of the polygon +// https://en.wikipedia.org/wiki/Polygon#Area +func (q DOMQuad) Area() float64 { + area := 0.0 + l := len(q)/2 - 1 //nolint: mnd + + for i := 0; i < l; i++ { + area += q[i*2]*q[i*2+3] - q[i*2+2]*q[i*2+1] + } + area += q[l*2]*q[1] - q[0]*q[l*2+1] + + return area / 2 //nolint: mnd +} + +// OnePointInside the shape. +func (res *DOMGetContentQuadsResult) OnePointInside() *Point { + for _, q := range res.Quads { + if q.Area() >= 1 { + pt := q.Center() + return &pt + } + } + + return nil +} + +// Box returns the smallest leveled rectangle that can cover the whole shape. +func (res *DOMGetContentQuadsResult) Box() (box *DOMRect) { + return Shape(res.Quads).Box() +} + +// Shape is a list of DOMQuad. +type Shape []DOMQuad + +// Box returns the smallest leveled rectangle that can cover the whole shape. +func (qs Shape) Box() (box *DOMRect) { + if len(qs) == 0 { + return + } + + left := qs[0][0] + top := qs[0][1] + right := left + bottom := top + + for _, q := range qs { + q.Each(func(pt Point, _ int) { + if pt.X < left { + left = pt.X + } + if pt.Y < top { + top = pt.Y + } + if pt.X > right { + right = pt.X + } + if pt.Y > bottom { + bottom = pt.Y + } + }) + } + + box = &DOMRect{left, top, right - left, bottom - top} + + return +} + +// MoveTo X and Y to x and y. +func (p *InputTouchPoint) MoveTo(x, y float64) { + p.X = x + p.Y = y +} + +// CookiesToParams converts Cookies list to NetworkCookieParam list. +func CookiesToParams(cookies []*NetworkCookie) []*NetworkCookieParam { + list := []*NetworkCookieParam{} + for _, c := range cookies { + list = append(list, &NetworkCookieParam{ + Name: c.Name, + Value: c.Value, + Domain: c.Domain, + Path: c.Path, + Secure: c.Secure, + HTTPOnly: c.HTTPOnly, + SameSite: c.SameSite, + Expires: c.Expires, + Priority: c.Priority, + }) + } + return list +} diff --git a/vendor/github.com/go-rod/rod/lib/proto/a_utils.go b/vendor/github.com/go-rod/rod/lib/proto/a_utils.go new file mode 100644 index 000000000..a34867c28 --- /dev/null +++ b/vendor/github.com/go-rod/rod/lib/proto/a_utils.go @@ -0,0 +1,24 @@ +package proto + +import ( + "regexp" + "strings" +) + +var ( + regAsterisk = regexp.MustCompile(`([^\\])\*`) + regBackSlash = regexp.MustCompile(`([^\\])\?`) +) + +// PatternToReg FetchRequestPattern.URLPattern to regular expression. +func PatternToReg(pattern string) string { + if pattern == "" { + return "" + } + + pattern = " " + pattern + pattern = regAsterisk.ReplaceAllString(pattern, "$1.*") + pattern = regBackSlash.ReplaceAllString(pattern, "$1.") + + return `\A` + strings.TrimSpace(pattern) + `\z` +} diff --git a/vendor/github.com/go-rod/rod/lib/proto/accessibility.go b/vendor/github.com/go-rod/rod/lib/proto/accessibility.go new file mode 100644 index 000000000..9a93a6775 --- /dev/null +++ b/vendor/github.com/go-rod/rod/lib/proto/accessibility.go @@ -0,0 +1,588 @@ +// This file is generated by "./lib/proto/generate" + +package proto + +import ( + "github.com/ysmood/gson" +) + +/* + +Accessibility + +*/ + +// AccessibilityAXNodeID Unique accessibility node identifier. +type AccessibilityAXNodeID string + +// AccessibilityAXValueType Enum of possible property types. +type AccessibilityAXValueType string + +const ( + // AccessibilityAXValueTypeBoolean enum const. + AccessibilityAXValueTypeBoolean AccessibilityAXValueType = "boolean" + + // AccessibilityAXValueTypeTristate enum const. + AccessibilityAXValueTypeTristate AccessibilityAXValueType = "tristate" + + // AccessibilityAXValueTypeBooleanOrUndefined enum const. + AccessibilityAXValueTypeBooleanOrUndefined AccessibilityAXValueType = "booleanOrUndefined" + + // AccessibilityAXValueTypeIdref enum const. + AccessibilityAXValueTypeIdref AccessibilityAXValueType = "idref" + + // AccessibilityAXValueTypeIdrefList enum const. + AccessibilityAXValueTypeIdrefList AccessibilityAXValueType = "idrefList" + + // AccessibilityAXValueTypeInteger enum const. + AccessibilityAXValueTypeInteger AccessibilityAXValueType = "integer" + + // AccessibilityAXValueTypeNode enum const. + AccessibilityAXValueTypeNode AccessibilityAXValueType = "node" + + // AccessibilityAXValueTypeNodeList enum const. + AccessibilityAXValueTypeNodeList AccessibilityAXValueType = "nodeList" + + // AccessibilityAXValueTypeNumber enum const. + AccessibilityAXValueTypeNumber AccessibilityAXValueType = "number" + + // AccessibilityAXValueTypeString enum const. + AccessibilityAXValueTypeString AccessibilityAXValueType = "string" + + // AccessibilityAXValueTypeComputedString enum const. + AccessibilityAXValueTypeComputedString AccessibilityAXValueType = "computedString" + + // AccessibilityAXValueTypeToken enum const. + AccessibilityAXValueTypeToken AccessibilityAXValueType = "token" + + // AccessibilityAXValueTypeTokenList enum const. + AccessibilityAXValueTypeTokenList AccessibilityAXValueType = "tokenList" + + // AccessibilityAXValueTypeDomRelation enum const. + AccessibilityAXValueTypeDomRelation AccessibilityAXValueType = "domRelation" + + // AccessibilityAXValueTypeRole enum const. + AccessibilityAXValueTypeRole AccessibilityAXValueType = "role" + + // AccessibilityAXValueTypeInternalRole enum const. + AccessibilityAXValueTypeInternalRole AccessibilityAXValueType = "internalRole" + + // AccessibilityAXValueTypeValueUndefined enum const. + AccessibilityAXValueTypeValueUndefined AccessibilityAXValueType = "valueUndefined" +) + +// AccessibilityAXValueSourceType Enum of possible property sources. +type AccessibilityAXValueSourceType string + +const ( + // AccessibilityAXValueSourceTypeAttribute enum const. + AccessibilityAXValueSourceTypeAttribute AccessibilityAXValueSourceType = "attribute" + + // AccessibilityAXValueSourceTypeImplicit enum const. + AccessibilityAXValueSourceTypeImplicit AccessibilityAXValueSourceType = "implicit" + + // AccessibilityAXValueSourceTypeStyle enum const. + AccessibilityAXValueSourceTypeStyle AccessibilityAXValueSourceType = "style" + + // AccessibilityAXValueSourceTypeContents enum const. + AccessibilityAXValueSourceTypeContents AccessibilityAXValueSourceType = "contents" + + // AccessibilityAXValueSourceTypePlaceholder enum const. + AccessibilityAXValueSourceTypePlaceholder AccessibilityAXValueSourceType = "placeholder" + + // AccessibilityAXValueSourceTypeRelatedElement enum const. + AccessibilityAXValueSourceTypeRelatedElement AccessibilityAXValueSourceType = "relatedElement" +) + +// AccessibilityAXValueNativeSourceType Enum of possible native property sources (as a subtype of a particular AXValueSourceType). +type AccessibilityAXValueNativeSourceType string + +const ( + // AccessibilityAXValueNativeSourceTypeDescription enum const. + AccessibilityAXValueNativeSourceTypeDescription AccessibilityAXValueNativeSourceType = "description" + + // AccessibilityAXValueNativeSourceTypeFigcaption enum const. + AccessibilityAXValueNativeSourceTypeFigcaption AccessibilityAXValueNativeSourceType = "figcaption" + + // AccessibilityAXValueNativeSourceTypeLabel enum const. + AccessibilityAXValueNativeSourceTypeLabel AccessibilityAXValueNativeSourceType = "label" + + // AccessibilityAXValueNativeSourceTypeLabelfor enum const. + AccessibilityAXValueNativeSourceTypeLabelfor AccessibilityAXValueNativeSourceType = "labelfor" + + // AccessibilityAXValueNativeSourceTypeLabelwrapped enum const. + AccessibilityAXValueNativeSourceTypeLabelwrapped AccessibilityAXValueNativeSourceType = "labelwrapped" + + // AccessibilityAXValueNativeSourceTypeLegend enum const. + AccessibilityAXValueNativeSourceTypeLegend AccessibilityAXValueNativeSourceType = "legend" + + // AccessibilityAXValueNativeSourceTypeRubyannotation enum const. + AccessibilityAXValueNativeSourceTypeRubyannotation AccessibilityAXValueNativeSourceType = "rubyannotation" + + // AccessibilityAXValueNativeSourceTypeTablecaption enum const. + AccessibilityAXValueNativeSourceTypeTablecaption AccessibilityAXValueNativeSourceType = "tablecaption" + + // AccessibilityAXValueNativeSourceTypeTitle enum const. + AccessibilityAXValueNativeSourceTypeTitle AccessibilityAXValueNativeSourceType = "title" + + // AccessibilityAXValueNativeSourceTypeOther enum const. + AccessibilityAXValueNativeSourceTypeOther AccessibilityAXValueNativeSourceType = "other" +) + +// AccessibilityAXValueSource A single source for a computed AX property. +type AccessibilityAXValueSource struct { + // Type What type of source this is. + Type AccessibilityAXValueSourceType `json:"type"` + + // Value (optional) The value of this property source. + Value *AccessibilityAXValue `json:"value,omitempty"` + + // Attribute (optional) The name of the relevant attribute, if any. + Attribute string `json:"attribute,omitempty"` + + // AttributeValue (optional) The value of the relevant attribute, if any. + AttributeValue *AccessibilityAXValue `json:"attributeValue,omitempty"` + + // Superseded (optional) Whether this source is superseded by a higher priority source. + Superseded bool `json:"superseded,omitempty"` + + // NativeSource (optional) The native markup source for this value, e.g. a `