├── .editorconfig ├── .github ├── FUNDING.yml ├── ISSUE_TEMPLATE │ ├── bug_report.md │ ├── config.yaml │ └── feature_request.md ├── PULL_REQUEST_TEMPLATE.md ├── dependabot.yaml ├── install.sh └── workflows │ ├── gosum.yaml │ ├── pr.yaml │ ├── release.yaml │ └── stale.yaml ├── .gitignore ├── .goreleaser.yaml ├── LICENSE ├── README.md ├── go.mod ├── go.sum ├── internal └── validator.go ├── main.go └── pkg └── stare └── stare.go /.editorconfig: -------------------------------------------------------------------------------- 1 | root = true 2 | 3 | [*] 4 | end_of_line = lf 5 | indent_size = 4 6 | indent_style = tab 7 | insert_final_newline = true 8 | trim_trailing_whitespace = true 9 | insert_final_newline = true 10 | charset = utf-8 11 | 12 | [*.{md,yaml}] 13 | indent_size = 2 14 | indent_style = space 15 | -------------------------------------------------------------------------------- /.github/FUNDING.yml: -------------------------------------------------------------------------------- 1 | github: ["dwisiswant0"] 2 | custom: ["https://paypal.me/dw1s", "https://saweria.co/dwisiswant0", "https://unstoppabledomains.com/d/dwisiswant0.crypto"] 3 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/bug_report.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: Bug report 3 | about: Create a report to help us improve 4 | title: "[BUG]" 5 | labels: 'bug' 6 | assignees: dwisiswant0 7 | 8 | --- 9 | 10 | **Describe the bug** 11 | 12 | A clear and concise description of what the bug is. 13 | 14 | **To Reproduce** 15 | 16 | Steps to reproduce the behavior: 17 | 18 | **Expected behavior** 19 | 20 | A clear and concise description of what you expected to happen. 21 | 22 | **Screenshots** 23 | 24 | If applicable, add screenshots to help explain your problem. 25 | 26 | **Environment (please complete the following information):** 27 | 28 | - OS: [e.g. mac, linux] 29 | - OS version: [uname -a] 30 | - go-stare version: 31 | 32 | **Additional context** 33 | Add any other context about the problem here. Full output log is probably a helpful thing to add here. 34 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/config.yaml: -------------------------------------------------------------------------------- 1 | blank_issues_enabled: false 2 | contact_links: 3 | - name: Ask a question 4 | url: https://twitter.com/dwisiswant0 5 | about: Ask questions and discuss with author 6 | 7 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/feature_request.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: Feature request 3 | about: Suggest an idea for this project 4 | title: "[FEATURE]" 5 | labels: 'enhancement' 6 | assignees: dwisiswant0 7 | 8 | --- 9 | 10 | **Is your feature request related to a problem? Please describe.** 11 | A clear and concise description of what the problem is. Ex. I'm always frustrated when [...] 12 | 13 | **Describe the solution you'd like** 14 | A clear and concise description of what you want to happen. 15 | 16 | **Describe alternatives you've considered** 17 | A clear and concise description of any alternative solutions or features you've considered. 18 | 19 | **Additional context** 20 | Add any other context or screenshots about the feature request here. 21 | -------------------------------------------------------------------------------- /.github/PULL_REQUEST_TEMPLATE.md: -------------------------------------------------------------------------------- 1 | **IMPORTANT: Please do not create a Pull Request without creating an issue first!** 2 | 3 | _(Any change needs to be discussed before proceeding. Failure to do so may result in the rejection of the pull request)._ 4 | 5 | ### Summary 6 | 7 | 8 | 9 | _Explains the information and/ motivation for making this changes..._ 10 | 11 | 12 | ### Proposed of changes 13 | 14 | This PR fixes/implements the following **bugs/features**: 15 | 16 | - Bug 1 17 | - Bug 2 18 | - Feature 1 19 | - Feature 2 20 | - Breaking changes 21 | 22 | 23 | 24 | ### How has this been tested? 25 | 26 | Proof: 27 | 28 | 29 | 30 | ### Closing issues 31 | 32 | Fixes # 33 | 34 | ### Checklist: 35 | 36 | 37 | 38 | 39 | - [ ] My code follows the code style of this project. 40 | - [ ] My change requires a change to the documentation. 41 | - [ ] I have updated the documentation accordingly. 42 | - [ ] I have written new tests for my changes. 43 | - [ ] My changes successfully ran and pass tests locally. -------------------------------------------------------------------------------- /.github/dependabot.yaml: -------------------------------------------------------------------------------- 1 | version: 2 2 | updates: 3 | - package-ecosystem: "gomod" 4 | directory: "/" 5 | schedule: 6 | interval: "daily" 7 | labels: 8 | - "bug" 9 | 10 | - package-ecosystem: "github-actions" 11 | directory: "/" 12 | schedule: 13 | interval: "daily" 14 | labels: 15 | - "bug" -------------------------------------------------------------------------------- /.github/install.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | set -e 3 | # Code generated by godownloader on 2020-09-14T07:38:56Z. DO NOT EDIT. 4 | # 5 | 6 | usage() { 7 | this=$1 8 | cat </dev/null 130 | } 131 | echoerr() { 132 | echo "$@" 1>&2 133 | } 134 | log_prefix() { 135 | echo "$0" 136 | } 137 | _logp=6 138 | log_set_priority() { 139 | _logp="$1" 140 | } 141 | log_priority() { 142 | if test -z "$1"; then 143 | echo "$_logp" 144 | return 145 | fi 146 | [ "$1" -le "$_logp" ] 147 | } 148 | log_tag() { 149 | case $1 in 150 | 0) echo "emerg" ;; 151 | 1) echo "alert" ;; 152 | 2) echo "crit" ;; 153 | 3) echo "err" ;; 154 | 4) echo "warning" ;; 155 | 5) echo "notice" ;; 156 | 6) echo "info" ;; 157 | 7) echo "debug" ;; 158 | *) echo "$1" ;; 159 | esac 160 | } 161 | log_debug() { 162 | log_priority 7 || return 0 163 | echoerr "$(log_prefix)" "$(log_tag 7)" "$@" 164 | } 165 | log_info() { 166 | log_priority 6 || return 0 167 | echoerr "$(log_prefix)" "$(log_tag 6)" "$@" 168 | } 169 | log_err() { 170 | log_priority 3 || return 0 171 | echoerr "$(log_prefix)" "$(log_tag 3)" "$@" 172 | } 173 | log_crit() { 174 | log_priority 2 || return 0 175 | echoerr "$(log_prefix)" "$(log_tag 2)" "$@" 176 | } 177 | uname_os() { 178 | os=$(uname -s | tr '[:upper:]' '[:lower:]') 179 | case "$os" in 180 | cygwin_nt*) os="windows" ;; 181 | mingw*) os="windows" ;; 182 | msys_nt*) os="windows" ;; 183 | esac 184 | echo "$os" 185 | } 186 | uname_arch() { 187 | arch=$(uname -m) 188 | case $arch in 189 | x86_64) arch="amd64" ;; 190 | x86) arch="386" ;; 191 | i686) arch="386" ;; 192 | i386) arch="386" ;; 193 | aarch64) arch="arm64" ;; 194 | armv5*) arch="armv5" ;; 195 | armv6*) arch="armv6" ;; 196 | armv7*) arch="armv7" ;; 197 | esac 198 | echo ${arch} 199 | } 200 | uname_os_check() { 201 | os=$(uname_os) 202 | case "$os" in 203 | darwin) return 0 ;; 204 | dragonfly) return 0 ;; 205 | freebsd) return 0 ;; 206 | linux) return 0 ;; 207 | android) return 0 ;; 208 | nacl) return 0 ;; 209 | netbsd) return 0 ;; 210 | openbsd) return 0 ;; 211 | plan9) return 0 ;; 212 | solaris) return 0 ;; 213 | windows) return 0 ;; 214 | esac 215 | log_crit "uname_os_check '$(uname -s)' got converted to '$os' which is not a GOOS value. Please file bug at https://github.com/client9/shlib" 216 | return 1 217 | } 218 | uname_arch_check() { 219 | arch=$(uname_arch) 220 | case "$arch" in 221 | 386) return 0 ;; 222 | amd64) return 0 ;; 223 | arm64) return 0 ;; 224 | armv5) return 0 ;; 225 | armv6) return 0 ;; 226 | armv7) return 0 ;; 227 | ppc64) return 0 ;; 228 | ppc64le) return 0 ;; 229 | mips) return 0 ;; 230 | mipsle) return 0 ;; 231 | mips64) return 0 ;; 232 | mips64le) return 0 ;; 233 | s390x) return 0 ;; 234 | amd64p32) return 0 ;; 235 | esac 236 | log_crit "uname_arch_check '$(uname -m)' got converted to '$arch' which is not a GOARCH value. Please file bug report at https://github.com/client9/shlib" 237 | return 1 238 | } 239 | untar() { 240 | tarball=$1 241 | case "${tarball}" in 242 | *.tar.gz | *.tgz) tar --no-same-owner -xzf "${tarball}" ;; 243 | *.tar) tar --no-same-owner -xf "${tarball}" ;; 244 | *.zip) unzip "${tarball}" ;; 245 | *) 246 | log_err "untar unknown archive format for ${tarball}" 247 | return 1 248 | ;; 249 | esac 250 | } 251 | http_download_curl() { 252 | local_file=$1 253 | source_url=$2 254 | header=$3 255 | if [ -z "$header" ]; then 256 | code=$(curl -w '%{http_code}' -sL -o "$local_file" "$source_url") 257 | else 258 | code=$(curl -w '%{http_code}' -sL -H "$header" -o "$local_file" "$source_url") 259 | fi 260 | if [ "$code" != "200" ]; then 261 | log_debug "http_download_curl received HTTP status $code" 262 | return 1 263 | fi 264 | return 0 265 | } 266 | http_download_wget() { 267 | local_file=$1 268 | source_url=$2 269 | header=$3 270 | if [ -z "$header" ]; then 271 | wget -q -O "$local_file" "$source_url" 272 | else 273 | wget -q --header "$header" -O "$local_file" "$source_url" 274 | fi 275 | } 276 | http_download() { 277 | log_debug "http_download $2" 278 | if is_command curl; then 279 | http_download_curl "$@" 280 | return 281 | elif is_command wget; then 282 | http_download_wget "$@" 283 | return 284 | fi 285 | log_crit "http_download unable to find wget or curl" 286 | return 1 287 | } 288 | http_copy() { 289 | tmp=$(mktemp) 290 | http_download "${tmp}" "$1" "$2" || return 1 291 | body=$(cat "$tmp") 292 | rm -f "${tmp}" 293 | echo "$body" 294 | } 295 | github_release() { 296 | owner_repo=$1 297 | version=$2 298 | test -z "$version" && version="latest" 299 | giturl="https://github.com/${owner_repo}/releases/${version}" 300 | json=$(http_copy "$giturl" "Accept:application/json") 301 | test -z "$json" && return 1 302 | version=$(echo "$json" | tr -s '\n' ' ' | sed 's/.*"tag_name":"//' | sed 's/".*//') 303 | test -z "$version" && return 1 304 | echo "$version" 305 | } 306 | hash_sha256() { 307 | TARGET=${1:-/dev/stdin} 308 | if is_command gsha256sum; then 309 | hash=$(gsha256sum "$TARGET") || return 1 310 | echo "$hash" | cut -d ' ' -f 1 311 | elif is_command sha256sum; then 312 | hash=$(sha256sum "$TARGET") || return 1 313 | echo "$hash" | cut -d ' ' -f 1 314 | elif is_command shasum; then 315 | hash=$(shasum -a 256 "$TARGET" 2>/dev/null) || return 1 316 | echo "$hash" | cut -d ' ' -f 1 317 | elif is_command openssl; then 318 | hash=$(openssl -dst openssl dgst -sha256 "$TARGET") || return 1 319 | echo "$hash" | cut -d ' ' -f a 320 | else 321 | log_crit "hash_sha256 unable to find command to compute sha-256 hash" 322 | return 1 323 | fi 324 | } 325 | hash_sha256_verify() { 326 | TARGET=$1 327 | checksums=$2 328 | if [ -z "$checksums" ]; then 329 | log_err "hash_sha256_verify checksum file not specified in arg2" 330 | return 1 331 | fi 332 | BASENAME=${TARGET##*/} 333 | want=$(grep "${BASENAME}" "${checksums}" 2>/dev/null | tr '\t' ' ' | cut -d ' ' -f 1) 334 | if [ -z "$want" ]; then 335 | log_err "hash_sha256_verify unable to find checksum for '${TARGET}' in '${checksums}'" 336 | return 1 337 | fi 338 | got=$(hash_sha256 "$TARGET") 339 | if [ "$want" != "$got" ]; then 340 | log_err "hash_sha256_verify checksum for '$TARGET' did not verify ${want} vs $got" 341 | return 1 342 | fi 343 | } 344 | cat /dev/null < 12 | 13 | --- 14 | 15 | ## Resources 16 | 17 | - [Installation](#installation) 18 | - [from Binary](#from-binary) 19 | - [from Source](#from-source) 20 | - [from GitHub](#from-github) 21 | - [Usage](#usage) 22 | - [Basic Usage](#basic-usage) 23 | - [Flags](#flags) 24 | - [Target](#target) 25 | - [Single URL](#single-url) 26 | - [URLs from list](#urls-from-list) 27 | - [from Stdin](#from-stdin) 28 | - [Help & Bugs](#help--bugs) 29 | - [License](#license) 30 | - [Version](#version) 31 | 32 | ## Installation 33 | 34 | ### from Binary 35 | 36 | The installation is easy. You can download a prebuilt binary from [releases page](https://github.com/dwisiswant0/go-stare/releases), unpack and run! or with 37 | 38 | ```bash 39 | ▶ curl -sSfL https://git.io/go-stare | sh -s -- -b /usr/local/bin 40 | ``` 41 | 42 | ### from Source 43 | 44 | If you have go1.14+ compiler installed and configured: 45 | 46 | ```bash 47 | ▶ GO111MODULE=on go get -v github.com/dwisiswant0/go-stare 48 | ``` 49 | 50 | In order to update the tool, you can use `-u` flag with go get command. 51 | 52 | ### from GitHub 53 | 54 | ```bash 55 | ▶ git clone https://github.com/dwisiswant0/go-stare 56 | ▶ cd go-stare 57 | ▶ go build . 58 | ▶ mv go-stare /usr/local/bin 59 | ``` 60 | 61 | ## Usage 62 | 63 | ### Basic Usage 64 | 65 | Simply, go-stare can be run with: 66 | 67 | ```bash 68 | ▶ go-stare -t "http://domain.tld" 69 | ``` 70 | 71 | ### Flags 72 | 73 | ```bash 74 | ▶ go-stare -h 75 | ``` 76 | 77 | This will display help for the tool. Here are all the switches it supports. 78 | 79 | | **Flag** | **Description** | 80 | |------------------- |----------------------------------------------------------- | 81 | | -t, --target | Target to captures _(single target URL or list)_ | 82 | | -c, --concurrency | Set the concurrency level _(default: 5)_ | 83 | | -o, --output | Screenshot directory output results _(default: ./out)_ | 84 | | -q, --quality | Image quality to produce _(default: 75)_ | 85 | | -T, --timeout | Maximum time (seconds) allowed for connection _(default: 10)_ | 86 | | -v, --verbose | Verbose mode | 87 | 88 | ### Target 89 | 90 | You can define a target in 3 ways: 91 | 92 | #### Single URL 93 | 94 | ```bash 95 | ▶ go-stare -t "http://domain.tld" 96 | ``` 97 | 98 | #### URLs from list 99 | 100 | ```bash 101 | ▶ go-stare -t /path/to/urls.txt 102 | ``` 103 | 104 | #### from Stdin 105 | 106 | In case you want to chained with other tools. 107 | 108 | ```bash 109 | ▶ subfinder -d domain.tld -silent | httpx -silent | go-stare -o ./out 110 | # or 111 | ▶ gau domain.tld | go-stare -o ./out -q 50 112 | ``` 113 | 114 | ## Help & Bugs 115 | 116 | If you are still confused or found a bug, please [open the issue](https://github.com/dwisiswant0/go-stare/issues). All bug reports are appreciated, some features have not been tested yet due to lack of free time. 117 | 118 | ## License 119 | 120 | **go-stare** released under MIT. See `LICENSE` for more details. 121 | 122 | ## Version 123 | 124 | **Current version is 0.0.2** and still development. 125 | -------------------------------------------------------------------------------- /go.mod: -------------------------------------------------------------------------------- 1 | module github.com/dwisiswant0/go-stare 2 | 3 | go 1.15 4 | 5 | require ( 6 | github.com/chromedp/cdproto v0.0.0-20210104223854-2cc87dae3ee3 7 | github.com/chromedp/chromedp v0.6.0 8 | github.com/logrusorgru/aurora v0.0.0-20200102142835-e9ef32dff381 9 | github.com/projectdiscovery/gologger v1.0.1 10 | ) 11 | -------------------------------------------------------------------------------- /go.sum: -------------------------------------------------------------------------------- 1 | github.com/chromedp/cdproto v0.0.0-20210104223854-2cc87dae3ee3 h1:XeGYLuu3Yu3/2/FLDXyObe6lBYtUFDTJgjjNPcfcU40= 2 | github.com/chromedp/cdproto v0.0.0-20210104223854-2cc87dae3ee3/go.mod h1:55pim6Ht4LJKdVLlyFJV/g++HsEA1hQxPbB5JyNdZC0= 3 | github.com/chromedp/chromedp v0.6.0 h1:jjzHzXW5pNdKt1D9cEDAKZM/yZ2EwL/hLyGbCUFldBI= 4 | github.com/chromedp/chromedp v0.6.0/go.mod h1:Yay7TUDCNOQBK8EJDUon6AUaQI12VEBOuULcGtY4uDY= 5 | github.com/chromedp/sysutil v1.0.0 h1:+ZxhTpfpZlmchB58ih/LBHX52ky7w2VhQVKQMucy3Ic= 6 | github.com/chromedp/sysutil v1.0.0/go.mod h1:kgWmDdq8fTzXYcKIBqIYvRRTnYb9aNS9moAV0xufSww= 7 | github.com/davecgh/go-spew v1.1.0 h1:ZDRjVQ15GmhC3fiQ8ni8+OwkZQO4DARzQgrnXU1Liz8= 8 | github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= 9 | github.com/gobwas/httphead v0.1.0 h1:exrUm0f4YX0L7EBwZHuCF4GDp8aJfVeBrlLQrs6NqWU= 10 | github.com/gobwas/httphead v0.1.0/go.mod h1:O/RXo79gxV8G+RqlR/otEwx4Q36zl9rqC5u12GKvMCM= 11 | github.com/gobwas/pool v0.2.1 h1:xfeeEhW7pwmX8nuLVlqbzVc7udMDrwetjEv+TZIz1og= 12 | github.com/gobwas/pool v0.2.1/go.mod h1:q8bcK0KcYlCgd9e7WYLm9LpyS+YeLd8JVDW6WezmKEw= 13 | github.com/gobwas/ws v1.0.4 h1:5eXU1CZhpQdq5kXbKb+sECH5Ia5KiO6CYzIzdlVx6Bs= 14 | github.com/gobwas/ws v1.0.4/go.mod h1:szmBTxLgaFppYjEmNtny/v3w89xOydFnnZMcgRRu/EM= 15 | github.com/josharian/intern v1.0.0 h1:vlS4z54oSdjm0bgjRigI+G1HpF+tI+9rE5LLzOg8HmY= 16 | github.com/josharian/intern v1.0.0/go.mod h1:5DoeVV0s6jJacbCEi61lwdGj/aVlrQvzHFFd8Hwg//Y= 17 | github.com/logrusorgru/aurora v0.0.0-20200102142835-e9ef32dff381 h1:bqDmpDG49ZRnB5PcgP0RXtQvnMSgIF14M7CBd2shtXs= 18 | github.com/logrusorgru/aurora v0.0.0-20200102142835-e9ef32dff381/go.mod h1:7rIyQOR62GCctdiQpZ/zOJlFyk6y+94wXzv6RNZgaR4= 19 | github.com/mailru/easyjson v0.7.6 h1:8yTIVnZgCoiM1TgqoeTl+LfU5Jg6/xL3QhGQnimLYnA= 20 | github.com/mailru/easyjson v0.7.6/go.mod h1:xzfreul335JAWq5oZzymOObrkdz5UnU4kGfJJLY9Nlc= 21 | github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= 22 | github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= 23 | github.com/projectdiscovery/gologger v1.0.1 h1:FzoYQZnxz9DCvSi/eg5A6+ET4CQ0CDUs27l6Exr8zMQ= 24 | github.com/projectdiscovery/gologger v1.0.1/go.mod h1:Ok+axMqK53bWNwDSU1nTNwITLYMXMdZtRc8/y1c7sWE= 25 | github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= 26 | github.com/stretchr/testify v1.5.1 h1:nOGnQDM7FYENwehXlg/kFVnos3rEvtKTjRvOWSzb6H4= 27 | github.com/stretchr/testify v1.5.1/go.mod h1:5W2xD1RspED5o8YsWQXVCued0rvSQ+mT+I5cxcmMvtA= 28 | golang.org/x/sys v0.0.0-20210104204734-6f8348627aad h1:MCsdmFSdEd4UEa5TKS5JztCRHK/WtvNei1edOj5RSRo= 29 | golang.org/x/sys v0.0.0-20210104204734-6f8348627aad/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 30 | gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM= 31 | gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= 32 | gopkg.in/yaml.v2 v2.2.2 h1:ZCJp+EgiOT7lHqUV2J862kp8Qj64Jo6az82+3Td9dZw= 33 | gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= 34 | -------------------------------------------------------------------------------- /internal/validator.go: -------------------------------------------------------------------------------- 1 | package internal 2 | 3 | import ( 4 | "bufio" 5 | "os" 6 | "strings" 7 | 8 | "github.com/dwisiswant0/go-stare/pkg/stare" 9 | "github.com/projectdiscovery/gologger" 10 | ) 11 | 12 | // Validator to validate options 13 | func Validator(cfg *stare.Config) { 14 | if isStdin() { 15 | cfg.URL = bufio.NewScanner(os.Stdin) 16 | } else if cfg.Target != "" { 17 | if strings.HasPrefix(cfg.Target, "http") { 18 | cfg.URL = bufio.NewScanner(strings.NewReader(cfg.Target)) 19 | } else { 20 | r, err := os.Open(cfg.Target) 21 | if err != nil { 22 | gologger.Errorf("Invalid '%s' URL or file!", cfg.Target) 23 | gologger.Infof("Use -h flag for more info about command.") 24 | os.Exit(1) 25 | } 26 | cfg.URL = bufio.NewScanner(r) 27 | } 28 | } else { 29 | gologger.Errorf("No target inputs provided!") 30 | gologger.Infof("Use -h flag for more info about command.") 31 | os.Exit(1) 32 | } 33 | } 34 | 35 | func isStdin() bool { 36 | f, e := os.Stdin.Stat() 37 | if e != nil { 38 | return false 39 | } 40 | 41 | if f.Mode()&os.ModeNamedPipe == 0 { 42 | return false 43 | } 44 | 45 | return true 46 | } 47 | -------------------------------------------------------------------------------- /main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "flag" 5 | "fmt" 6 | "os" 7 | "runtime" 8 | 9 | "github.com/dwisiswant0/go-stare/internal" 10 | "github.com/dwisiswant0/go-stare/pkg/stare" 11 | "github.com/logrusorgru/aurora" 12 | "github.com/projectdiscovery/gologger" 13 | ) 14 | 15 | const ( 16 | author = "dwisiswant0" 17 | version = "0.0.2" 18 | banner = ` 19 | _ 20 | __ _ ___ ____ ___| |_ __ _ _ __ ___ 21 | / _' |/ _ \____/ __| __/ _' | '__/ _ \ 22 | | (_| | (_) | \__ \ || (_| | | | __/ 23 | \__, |\___/ |___/\__\__,_|_| \___| 24 | __/ | 25 | |___/ v` + version + ` - @` + author + ` 26 | 27 | ` 28 | ) 29 | 30 | var cfg *stare.Config 31 | 32 | func init() { 33 | cpu := runtime.NumCPU() 34 | runtime.GOMAXPROCS(cpu + 1) 35 | 36 | cfg = &stare.Config{} 37 | 38 | flag.StringVar(&cfg.Target, "target", "", "") 39 | flag.StringVar(&cfg.Target, "t", "", "") 40 | 41 | flag.IntVar(&cfg.Concurrency, "concurrency", 5, "") 42 | flag.IntVar(&cfg.Concurrency, "c", 5, "") 43 | 44 | flag.StringVar(&cfg.Output, "output", "./out", "") 45 | flag.StringVar(&cfg.Output, "o", "./out", "") 46 | 47 | flag.Int64Var(&cfg.Quality, "quality", 75, "") 48 | flag.Int64Var(&cfg.Quality, "q", 75, "") 49 | 50 | flag.IntVar(&cfg.Timeout, "timeout", 10, "") 51 | flag.IntVar(&cfg.Timeout, "T", 10, "") 52 | 53 | flag.BoolVar(&cfg.Verbose, "verbose", false, "") 54 | flag.BoolVar(&cfg.Verbose, "v", false, "") 55 | 56 | fmt.Fprintf(os.Stderr, "%s", aurora.Bold(aurora.Cyan(banner))) 57 | 58 | flag.Usage = func() { 59 | h := "A fast & light web screenshot without headless browser but Chrome DevTools Protocol!\n\n" 60 | 61 | h += "Usage:\n" 62 | h += " go-stare -t [URL|URLs.txt] -o [outputDir]\n\n" 63 | 64 | h += "Options:\n" 65 | h += " -t, --target Target to captures (single target URL or list)\n" 66 | h += " -c, --concurrency Set the concurrency level (default: 50)\n" 67 | h += " -o, --output Screenshot directory output results (default: ./out)\n" 68 | h += " -q, --quality Image quality to produce (default: 75)\n" 69 | h += " -T, --timeout Maximum time (seconds) allowed for connection (default: 10)\n" 70 | h += " -v, --verbose Verbose mode\n\n" 71 | 72 | h += "Examples:\n" 73 | h += " go-stare -t http://domain.tld\n" 74 | h += " go-stare -t urls.txt -o ./out\n" 75 | h += " cat urls.txt | go-stare -o ./out -q 90\n\n" 76 | 77 | fmt.Fprint(os.Stderr, h) 78 | } 79 | 80 | flag.Parse() 81 | } 82 | 83 | func main() { 84 | internal.Validator(cfg) 85 | 86 | if err := os.MkdirAll(cfg.Output, 0750); err != nil { 87 | gologger.Fatalf("Failed to create output directory: %s", err.Error()) 88 | os.Exit(1) 89 | } 90 | 91 | stare.New(cfg) 92 | } 93 | -------------------------------------------------------------------------------- /pkg/stare/stare.go: -------------------------------------------------------------------------------- 1 | package stare 2 | 3 | import ( 4 | "bufio" 5 | "context" 6 | "fmt" 7 | "io/ioutil" 8 | "math" 9 | "net/url" 10 | "os" 11 | "path" 12 | "regexp" 13 | "sync" 14 | "time" 15 | 16 | "github.com/chromedp/cdproto/emulation" 17 | "github.com/chromedp/cdproto/page" 18 | "github.com/chromedp/chromedp" 19 | "github.com/logrusorgru/aurora" 20 | "github.com/projectdiscovery/gologger" 21 | ) 22 | 23 | // Config declare its options 24 | type Config struct { 25 | Concurrency, Timeout int 26 | Quality int64 27 | Target, Output string 28 | Buffer []byte 29 | URL *bufio.Scanner 30 | Verbose bool 31 | Context context.Context 32 | CtxCancel context.CancelFunc 33 | } 34 | 35 | // New to proceed screenshots 36 | func New(cfg *Config) { 37 | var wg sync.WaitGroup 38 | jobs := make(chan string) 39 | 40 | cfg.Context, _ = chromedp.NewContext(context.Background()) 41 | cfg.Context, cfg.CtxCancel = context.WithTimeout(cfg.Context, time.Duration(cfg.Timeout)*time.Second) 42 | defer cfg.CtxCancel() 43 | 44 | for i := 0; i < cfg.Concurrency; i++ { 45 | wg.Add(1) 46 | go func() { 47 | for url := range jobs { 48 | cfg.exec(url) 49 | } 50 | defer wg.Done() 51 | }() 52 | } 53 | 54 | for cfg.URL.Scan() { 55 | u := cfg.URL.Text() 56 | if isURL(u) { 57 | jobs <- u 58 | } else { 59 | if cfg.Verbose { 60 | fmt.Fprintf(os.Stderr, "[%s] Invalid URL of %s\n", aurora.Red("!"), aurora.Red(u)) 61 | } 62 | } 63 | } 64 | 65 | close(jobs) 66 | wg.Wait() 67 | } 68 | 69 | func (cfg *Config) exec(url string) { 70 | if err := chromedp.Run(cfg.Context, screenshot(url, cfg.Quality, &cfg.Buffer)); err != nil { 71 | if cfg.Verbose { 72 | fmt.Fprintf(os.Stderr, "[%s] %s\n", aurora.Red("!"), err.Error()) 73 | } 74 | } 75 | 76 | out := path.Join(cfg.Output, replacer(url)+".png") 77 | if err := ioutil.WriteFile(out, cfg.Buffer, 0644); err != nil { 78 | if cfg.Verbose { 79 | fmt.Fprintf(os.Stderr, "[%s] Failed to create output screenshot: %s\n", aurora.Red("!"), err.Error()) 80 | } 81 | } else { 82 | fmt.Printf("[%s] Screenshot taken for %s (Output: %s)\n", aurora.Green("+"), aurora.Green(url), aurora.Magenta(out)) 83 | } 84 | } 85 | 86 | func screenshot(url string, quality int64, res *[]byte) chromedp.Tasks { 87 | return chromedp.Tasks{ 88 | chromedp.Navigate(url), 89 | chromedp.ActionFunc(func(ctx context.Context) error { 90 | _, _, contentSize, err := page.GetLayoutMetrics().Do(ctx) 91 | if err != nil { 92 | return err 93 | } 94 | 95 | width, height := int64(math.Ceil(contentSize.Width)), int64(math.Ceil(contentSize.Height)) 96 | 97 | err = emulation.SetDeviceMetricsOverride(width, height, 1, false). 98 | WithScreenOrientation(&emulation.ScreenOrientation{ 99 | Type: emulation.OrientationTypePortraitPrimary, 100 | Angle: 0, 101 | }). 102 | Do(ctx) 103 | if err != nil { 104 | return err 105 | } 106 | 107 | *res, err = page.CaptureScreenshot(). 108 | WithQuality(quality). 109 | WithClip(&page.Viewport{ 110 | X: contentSize.X, 111 | Y: contentSize.Y, 112 | Width: contentSize.Width, 113 | Height: contentSize.Height, 114 | Scale: 1, 115 | }).Do(ctx) 116 | if err != nil { 117 | return err 118 | } 119 | 120 | return nil 121 | }), 122 | } 123 | } 124 | 125 | func replacer(url string) string { 126 | reg, err := regexp.Compile("[^a-zA-Z0-9]+") 127 | if err != nil { 128 | gologger.Errorf("Failed to replace non-alphas URL: %s", err.Error()) 129 | os.Exit(2) 130 | } 131 | return reg.ReplaceAllString(url, "_") 132 | } 133 | 134 | func isURL(s string) bool { 135 | _, e := url.ParseRequestURI(s) 136 | if e != nil { 137 | return false 138 | } 139 | 140 | u, e := url.Parse(s) 141 | if e != nil || u.Scheme == "" || u.Host == "" { 142 | return false 143 | } 144 | 145 | return true 146 | } 147 | --------------------------------------------------------------------------------