From ad542cf9407b1334ab325e24f5245951840ee743 Mon Sep 17 00:00:00 2001 From: Andrew Gioia Date: Thu, 7 Dec 2023 13:45:54 -0500 Subject: [PATCH] Initial commit with base functionality --- index.php | 362 ++++++++++++++++++++++++++++++++++++++++++++++++++++++ style.css | 255 ++++++++++++++++++++++++++++++++++++++ 2 files changed, 617 insertions(+) create mode 100644 index.php create mode 100644 style.css diff --git a/index.php b/index.php new file mode 100644 index 0000000..602531d --- /dev/null +++ b/index.php @@ -0,0 +1,362 @@ +url = $url; + $this->next = ''; + $this->code = 0; + $this->step = 1; + $this->redirect = false; + $this->error = []; + $this->path[$this->step] = [ + 'step' => $this->step, + 'url' => $this->url, + 'code' => null, + 'next' => null ]; + } + + // create an error message + private function setError(array $error): void + { + $this->error = $error; + } + + // add an entry to the URL path + private function addPath(int $step, array $path): void + { + $this->path[$step] = $path; + } + + // update a path entry + public function updatePath(int $step, string $key, string $value): void + { + $this->path[$step][$key] = $value; + } + + // create a curl request for a URL + public function getHttpCode() + { + // initate curl request + $ch = curl_init(); + if (!$ch) { + $this->setError = ['type' => 'curl', 'message' => self::ERROR_CURL_INIT]; + return false; + } + + // set request headers and execute + $response = curl_setopt($ch, CURLOPT_URL, $this->url); + $response = curl_setopt($ch, CURLOPT_HEADER, true); // enable this for debugging + $response = curl_setopt($ch, CURLOPT_HTTPGET, true); // redundant but making sure it's a GET + $response = curl_setopt($ch, CURLOPT_NOBODY, false); // settings this to true was returning 405s + $response = curl_setopt($ch, CURLOPT_FOLLOWLOCATION, false); + $response = curl_setopt($ch, CURLOPT_RETURNTRANSFER, true); // return output instead of going to screen + $response = curl_setopt($ch, CURLOPT_TIMEOUT, 60); + $response = curl_setopt($ch, CURLOPT_USERAGENT, self::USER_AGENT); + $response = curl_exec($ch); + + // check for a response + if (empty($response)) + { + $this->setError(['type' => 'curl', 'message' => curl_error($ch)]); + curl_close($ch); + } + else + { + // get the http status code + if (curl_getinfo($ch, CURLINFO_HTTP_CODE)) + { + $code = curl_getinfo($ch, CURLINFO_HTTP_CODE); + $this->code = ($code) ? $code : 0; + if ($code == 200) { + $this->updatePath($this->step, 'code', $this->code); + return true; + } + } + else + { + $this->code = 0; + $this->setError(['type' => 'curl', 'message' => self::ERROR_CURL_CODE]); + return false; + } + + // get any redirect url to follow next + if (curl_getinfo($ch, CURLINFO_REDIRECT_URL)) + { + $next = curl_getinfo($ch, CURLINFO_REDIRECT_URL); + $this->redirect = ($next) ? true : false; + $this->next = ($next) ? $next : ''; + } + else + { + $this->redirect = false; + $this->next = ''; + } + + // close the session + curl_close($ch); + + // update the current path + $this->updatePath($this->step, 'code', $this->code); + $this->updatePath($this->step, 'next', $this->next); + + // start the next path + $this->step++; + $this->addPath( + $this->step, + [ + 'step' => $this->step, + 'url' => $this->next, + 'code' => null, + 'next' => null + ]); + + return true; + } + + // return false if we get here + return false; + } + + // get the final URL redirect + public function getFinalRedirect(): string + { + $last = end($this->path); + return $last['url']; + } +} + +// handle form submissions +if (isset($_POST['url'])) +{ + // check that we got a valid URL + $url = (filter_var(trim($_POST['url']), FILTER_VALIDATE_URL)) + ? trim($_POST['url']) + : false; + + // if so, start up the redirect checks + if ($url) + { + // make a request for this url and add to the path + $request = new RedirectCheck($url); + $code = ''; + + do { + // set the URL + $request->url = $url; + + // make the curl request and update the path + $request->getHttpCode(); + + // end on an error + if ($request->error) + { + break; + } + + // if we have a redirect to follow, update our working $url + $url = ($request->next) ? $request->next : false; + + // update our code + $code = ($request->code) ? $request->code : false; + + } while ($code != 200); + } +} +?> + + + + + Redirect Checker + + + + +
+ +

Redirect Checker

+ +
+
+ + +
+
+
+path) > 0) +{ + $steps = count($request->path) - 1; + $count = ($steps == 1) ? '1 redirect' : $steps.' redirects'; + echo " +
+

+ Final destination +

+

+ The URL you traced had ".$count." and ended here: +

+

+ getFinalRedirect()."\" target=\"blank\"> + ".$request->getFinalRedirect()." + +

+

+ Redirect trace +

+ + + + + + + + + + "; + foreach ($request->path as $step) + { + $item = $step['step']; + $url = $step['url']; + $code = $step['code']; + $next = ($step['next']) ? 'Yes' : '--'; + echo " + + + + + + "; + } + echo " + + +
StepRequestCodeRedirect
".$item."".$url."".$code."".$next."
+ User agent: ".$request::USER_AGENT." +
+
"; +} +else +{ + echo " +
+

+ Trace a URL's redirects +

+

+ Enter a URL in the search box above to derive the final resolved URL after all redirects. The script runs recursive cURL requests until we get a 200 status code. This is helpful to get around link tracking or original URLs that Pi-hole outright blocks (like email links). +

+

+ I used to use wheregoes.com which is a good, reliable service, but decided to roll my own for privacy reasons. Absolutely nothing is logged as all URL searches are via POST and that's not currently included in my nginx logs. +

+

+ Technical details +

+

+ User agent +

+

+ Other cURL settings +

+
+ "; +} +?> + +
+ + \ No newline at end of file diff --git a/style.css b/style.css new file mode 100644 index 0000000..990c7c7 --- /dev/null +++ b/style.css @@ -0,0 +1,255 @@ +html { + font-size: 16px; + height: 100%; + margin: 0; + padding: 0; +} + +body { + align-items: center; + background: #f5f5f5; + border-top: 5px solid #1a1a1a; + color: #1a1a1a; + display: flex; + flex-direction: column; + font-family: Inter, ui-sans-serif, system-ui, -apple-system, BlinkMacSystemFont, Segoe UI, Roboto, Helvetica Neue, Arial, Noto Sans, sans-serif, Apple Color Emoji, Segoe UI Emoji, Segoe UI Symbol, Noto Color Emoji; + font-size: 1rem; + min-height: calc(100% - 5px); + margin: 0; +} + +header { + align-items: center; + background: #fff; + display: flex; + flex-direction: column; + padding-bottom: 3rem; + width: 100%; +} + +menu { + align-items: center; + align-self: stretch; + display: flex; + flex-direction: row; + justify-content: space-between; + padding: 1.5rem min(5.5%, 2.5rem) 3rem; + margin: 0; +} + +a { + color: #1a1a1a; + text-decoration: underline; + text-decoration-color: transparent; + transition: text-decoration-color 150ms ease-in-out; +} +a:hover { + color: #000; + text-decoration-color: #000; +} +nav a { + font-size: 1.1rem; + line-height: 1.6rem; +} + +h1 { + font-size: 1.25rem; + margin: 0; +} + +form { + align-items: stretch; + display: flex; + justify-content: center; + position: relative; + width: 90%; +} +input[type="search"] { + background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='24' height='24' viewBox='0 0 24 24' fill='none' stroke='currentColor' stroke-width='2' stroke-linecap='round' stroke-linejoin='round'%3E%3Ccircle cx='11' cy='11' r='8'%3E%3C/circle%3E%3Cline x1='21' y1='21' x2='16.65' y2='16.65'%3E%3C/line%3E%3C/svg%3E"); + background-repeat: no-repeat; + background-position: 1.25rem 50%; + border: 0.15rem solid #1a1a1a; + border-radius: 2rem; + box-shadow: inset 0 0.25rem 0 0 #f0eded; + color: #1a1a1a; + font-size: 1.25rem; + height: 3.6rem; + padding: 0 8.5rem 0 3.5rem; + transition: box-shadow 150ms ease-in-out; + width: 100%; +} +input[type="search"]:focus { + outline: none; + box-shadow: inset 0 0.25rem 0 0 #f0eded, 0 0 0 1px #fff, 0 0 0 0.25rem #39b9e4; +} +button[type="submit"] { + background: #fff; + border: 0.15rem solid #1a1a1a; + border-radius: 0 2rem 2rem 0; + box-shadow: inset 0 0.25rem 0 0 #f0eded; + color: #1a1a1a; + cursor: pointer; + height: 100%; + font-size: 1.25rem; + padding: 0.75rem 2rem 0.8rem 1.75rem; + position: absolute; + top: 0; + transition: background 150ms ease-in-out; + right: 0; +} +button[type="submit"]:hover { + background-color: #f0eded; +} + +main { + align-items: center; + align-self: stretch; + display: flex; + flex-direction: column; + justify-content: flex-start; + gap: 4rem; + padding-top: 2rem; + height: 100%; +} + +article { + width: 86%; +} + +aside { + width: 86%; +} +details { + margin-bottom: 0.5rem; +} +summary { + cursor: pointer; +} +summary:hover { + color: #000; +} +details dl { + margin: 0.5rem 0 0; + padding-left: 1.1rem; +} +details dl dt { + margin: 0 0 0.5rem; +} +details dl dd { + display: none; + font-size: 0.875rem; + line-height: 1.2rem; + margin-left: 0; + padding: 0 0 1rem 0.1rem; +} +details dl input[type="checkbox"] { + display: none; +} +details dl input[type="checkbox"]:checked + dd { + display: block; +} +details[open] + details { + margin-top: 1rem; +} + +code { + font-family: Menlo, monospace; + font-size: 0.9rem; + background: #fff; + padding: 0.1rem 0.2rem; + border-radius: 0.25rem; +} + +h2 { + margin: 0 0 1.5rem; +} + +p { + line-height: 1.5rem; +} +p a { + font-weight: 600; +} +p.final { + background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='24' height='24' viewBox='0 0 24 24' fill='none' stroke='currentColor' stroke-width='2' stroke-linecap='round' stroke-linejoin='round' class='lucide lucide-arrow-right'%3E%3Cpath d='M5 12h14'/%3E%3Cpath d='m12 5 7 7-7 7'/%3E%3C/svg%3E"); + background-repeat: no-repeat; + background-position: 0 0.1rem; + font-size: 1.2rem; + padding: 0 0 2rem 2rem; + word-wrap: anywhere; +} + +h3 { + margin: 0 0 1.5rem; +} +p + h3 { + margin-top: 2rem; +} + +table { + border-spacing: 0; + padding: 0; + width: 100%; +} +th { + border-bottom: 2px solid #1a1a1a; + color: #4a4a4a; + font-size: 0.9rem; + padding: 0 1rem 0.75rem; + text-transform: uppercase; + text-align: left; +} +td { + background-color: #fafafa; + border-bottom: 1px solid #1a1a1a; + padding: 1rem; + vertical-align: top; + word-wrap: anywhere; +} +tbody tr:nth-child(odd) td { + background-color: #fff; +} +caption { + caption-side: bottom; + line-height: 1.5rem; + padding: 1rem; + text-align: left; +} + +/* laptop */ +@media screen and (min-width: 1024px) { + menu { + padding: 1.5rem 2.5rem 3rem; + } + h1 { + font-size: 1.75rem; + } + form { + width: 75%; + } + main { + align-items: flex-start; + flex-direction: row; + justify-content: center; + gap: 4rem; + } + article { + width: 60%; + } + aside { + width: 20%; + } +} + +/* desktop */ +@media screen and (min-width: 1440px) { + form { + width: 66.6666666667%; + } + article { + width: calc(50% - 5rem); + } + aside{ + width: 16.6667%; + } +} \ No newline at end of file