generated from nathanwoodburn/python-webserver-template
Compare commits
19 Commits
bc3fc2862e
...
main
| Author | SHA1 | Date | |
|---|---|---|---|
|
218b8dc234
|
|||
|
8b95ee8332
|
|||
|
6a68432a08
|
|||
|
3370d01a54
|
|||
|
003e14343b
|
|||
|
edbfa66249
|
|||
|
7f0a13f44b
|
|||
|
6d77096a15
|
|||
|
50275ba482
|
|||
|
ec59656cad
|
|||
|
964779136c
|
|||
|
3998e1cafc
|
|||
|
7bde6e7bd2
|
|||
|
0e56b2609e
|
|||
|
c4b1be2149
|
|||
|
06749d2c07
|
|||
|
07daad516f
|
|||
|
1c7093307a
|
|||
|
a26eecf59e
|
@@ -1,54 +0,0 @@
|
|||||||
name: Build Docker fast
|
|
||||||
run-name: Build Docker Images fast
|
|
||||||
on:
|
|
||||||
push:
|
|
||||||
|
|
||||||
jobs:
|
|
||||||
BuildImage:
|
|
||||||
runs-on: ubuntu-latest
|
|
||||||
steps:
|
|
||||||
- name: Checkout
|
|
||||||
uses: actions/checkout@v3
|
|
||||||
|
|
||||||
- name: Set up Docker Buildx
|
|
||||||
uses: docker/setup-buildx-action@v2
|
|
||||||
|
|
||||||
- name: Login to Docker Registry
|
|
||||||
uses: docker/login-action@v2
|
|
||||||
with:
|
|
||||||
registry: git.woodburn.au
|
|
||||||
username: nathanwoodburn
|
|
||||||
password: ${{ secrets.DOCKERGIT_TOKEN }}
|
|
||||||
|
|
||||||
- name: Extract metadata
|
|
||||||
id: meta
|
|
||||||
run: |
|
|
||||||
echo "branch=${GITHUB_HEAD_REF:-${GITHUB_REF#refs/heads/}}" >> $GITHUB_OUTPUT
|
|
||||||
tag=${GITHUB_HEAD_REF:-${GITHUB_REF#refs/heads/}}
|
|
||||||
tag=${tag//\//-}
|
|
||||||
echo "tag=$tag" >> $GITHUB_OUTPUT
|
|
||||||
tag_num=${GITHUB_RUN_NUMBER}
|
|
||||||
echo "tag_num=$tag_num" >> $GITHUB_OUTPUT
|
|
||||||
|
|
||||||
if [[ "$tag" == "main" ]]; then
|
|
||||||
echo "final_tag=latest" >> $GITHUB_OUTPUT
|
|
||||||
else
|
|
||||||
echo "final_tag=${tag}-${tag_num}" >> $GITHUB_OUTPUT
|
|
||||||
fi
|
|
||||||
|
|
||||||
repo=$GITHUB_REPOSITORY
|
|
||||||
repo=${repo#*/}
|
|
||||||
repo=$(echo $repo | tr '[:upper:]' '[:lower:]')
|
|
||||||
echo "repo=$repo" >> $GITHUB_OUTPUT
|
|
||||||
|
|
||||||
- name: Build and push
|
|
||||||
uses: docker/build-push-action@v4
|
|
||||||
with:
|
|
||||||
context: .
|
|
||||||
push: true
|
|
||||||
tags: |
|
|
||||||
git.woodburn.au/nathanwoodburn/${{ steps.meta.outputs.repo }}:${{ steps.meta.outputs.final_tag }}
|
|
||||||
git.woodburn.au/nathanwoodburn/${{ steps.meta.outputs.repo }}:${{ steps.meta.outputs.tag_num }}
|
|
||||||
git.woodburn.au/nathanwoodburn/${{ steps.meta.outputs.repo }}:${{ steps.meta.outputs.tag }}
|
|
||||||
cache-from: type=gha
|
|
||||||
cache-to: type=gha,mode=max
|
|
||||||
@@ -3,13 +3,17 @@ FROM --platform=$BUILDPLATFORM python:3.10-alpine AS builder
|
|||||||
WORKDIR /app
|
WORKDIR /app
|
||||||
|
|
||||||
# Install openssl and delv
|
# Install openssl and delv
|
||||||
RUN apk add --no-cache openssl bind-tools
|
RUN apk add --no-cache openssl bind-tools curl git
|
||||||
|
|
||||||
COPY requirements.txt /app
|
COPY requirements.txt /app
|
||||||
RUN --mount=type=cache,target=/root/.cache/pip \
|
RUN --mount=type=cache,target=/root/.cache/pip \
|
||||||
python3 -m pip install -r requirements.txt
|
python3 -m pip install -r requirements.txt
|
||||||
|
|
||||||
COPY . /app
|
# Copy application files
|
||||||
|
COPY *.py /app/
|
||||||
|
COPY templates/ /app/templates/
|
||||||
|
COPY hsd-ksk /app
|
||||||
|
|
||||||
|
|
||||||
# Optionally mount /data to store the data
|
# Optionally mount /data to store the data
|
||||||
# VOLUME /data
|
# VOLUME /data
|
||||||
|
|||||||
@@ -4,4 +4,7 @@ requests
|
|||||||
python-dotenv
|
python-dotenv
|
||||||
dnspython
|
dnspython
|
||||||
cryptography
|
cryptography
|
||||||
datetime
|
datetime
|
||||||
|
beautifulsoup4
|
||||||
|
requests-doh
|
||||||
|
git+https://github.com/Nathanwoodburn/requests-doh.git
|
||||||
45
server.py
45
server.py
@@ -17,12 +17,15 @@ from datetime import datetime
|
|||||||
import dotenv
|
import dotenv
|
||||||
import re
|
import re
|
||||||
import tools
|
import tools
|
||||||
|
import urllib.parse
|
||||||
|
|
||||||
dotenv.load_dotenv()
|
dotenv.load_dotenv()
|
||||||
|
|
||||||
app = Flask(__name__)
|
app = Flask(__name__)
|
||||||
|
|
||||||
|
|
||||||
|
BLOCKED_PATHS = ["https.js"]
|
||||||
|
|
||||||
def find(name, path):
|
def find(name, path):
|
||||||
for root, dirs, files in os.walk(path):
|
for root, dirs, files in os.walk(path):
|
||||||
if name in files:
|
if name in files:
|
||||||
@@ -78,6 +81,39 @@ def wellknown(path):
|
|||||||
def index():
|
def index():
|
||||||
return render_template("index.html")
|
return render_template("index.html")
|
||||||
|
|
||||||
|
@app.route("/proxy/<path:url>")
|
||||||
|
def proxy(url: str):
|
||||||
|
# Decode the URL
|
||||||
|
url = urllib.parse.unquote(url)
|
||||||
|
# Get last path segment
|
||||||
|
path = url.split("/")[-1]
|
||||||
|
if path in BLOCKED_PATHS:
|
||||||
|
return render_template("404.html"), 403
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
content: requests.Response = tools.proxy(url)
|
||||||
|
if not content.status_code < 500:
|
||||||
|
print(content.text)
|
||||||
|
return render_template("500.html"), 500
|
||||||
|
|
||||||
|
# Get the content type
|
||||||
|
contentType = content.headers.get("Content-Type")
|
||||||
|
if "text/html" in contentType:
|
||||||
|
response = make_response(tools.cleanProxyContent(content.text,url,request.host_url))
|
||||||
|
response.headers["Content-Type"] = contentType
|
||||||
|
return response, content.status_code
|
||||||
|
|
||||||
|
# Clean JS
|
||||||
|
if "text/javascript" in contentType or 'application/javascript' in contentType:
|
||||||
|
response = make_response(tools.proxyCleanJS(content.text,url,request.host_url))
|
||||||
|
response.headers["Content-Type"] = contentType
|
||||||
|
return response, content.status_code
|
||||||
|
|
||||||
|
response = make_response(content.content)
|
||||||
|
response.headers["Content-Type"] = contentType
|
||||||
|
return response, content.status_code
|
||||||
|
|
||||||
|
|
||||||
@app.route("/<path:path>")
|
@app.route("/<path:path>")
|
||||||
def catch_all(path: str):
|
def catch_all(path: str):
|
||||||
@@ -106,12 +142,19 @@ def catch_all(path: str):
|
|||||||
#region API routes
|
#region API routes
|
||||||
@app.route("/api/v1/ssl/<domain>")
|
@app.route("/api/v1/ssl/<domain>")
|
||||||
def api_ssl(domain: str):
|
def api_ssl(domain: str):
|
||||||
regexmatch = re.match(r"^([a-z0-9]+(-[a-z0-9]+)*\.)*([a-z0-9]+(-[a-z0-9]+)*)$", domain)
|
regexmatch = re.match(r"^([a-z0-9]+(-[a-z0-9]+)*\.)*([a-z0-9]+([-a-z0-9])*)$", domain)
|
||||||
if not regexmatch:
|
if not regexmatch:
|
||||||
return api_error("Please provide a valid domain to check")
|
return api_error("Please provide a valid domain to check")
|
||||||
result = tools.check_ssl(domain)
|
result = tools.check_ssl(domain)
|
||||||
return jsonify(result)
|
return jsonify(result)
|
||||||
|
|
||||||
|
@app.route("/api/v1/curl/<path:url>")
|
||||||
|
def api_curl(url: str):
|
||||||
|
# Decode the URL
|
||||||
|
url = urllib.parse.unquote(url)
|
||||||
|
|
||||||
|
result = tools.curl(url)
|
||||||
|
return jsonify(result)
|
||||||
|
|
||||||
# endregion
|
# endregion
|
||||||
|
|
||||||
|
|||||||
21
templates/500.html
Normal file
21
templates/500.html
Normal file
@@ -0,0 +1,21 @@
|
|||||||
|
<!DOCTYPE html>
|
||||||
|
<html lang="en">
|
||||||
|
|
||||||
|
<head>
|
||||||
|
<meta charset="UTF-8">
|
||||||
|
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||||
|
<title>Nathan.Woodburn/</title>
|
||||||
|
<link rel="icon" href="/assets/img/favicon.png" type="image/png">
|
||||||
|
<link rel="stylesheet" href="/assets/css/404.css">
|
||||||
|
</head>
|
||||||
|
|
||||||
|
<body>
|
||||||
|
<div class="spacer"></div>
|
||||||
|
<div class="centre">
|
||||||
|
<h1>500 | Internal Server Error</h1>
|
||||||
|
<p>Sorry, we can't seem to display this page. Maybe try again or your request might not be valid</p>
|
||||||
|
<p><a href="/">Go back to the homepage</a></p>
|
||||||
|
</div>
|
||||||
|
</body>
|
||||||
|
|
||||||
|
</html>
|
||||||
242
templates/assets/js/curl.js
Normal file
242
templates/assets/js/curl.js
Normal file
@@ -0,0 +1,242 @@
|
|||||||
|
document.addEventListener('DOMContentLoaded', function () {
|
||||||
|
// Get references to elements
|
||||||
|
const curlButton = document.getElementById('curl');
|
||||||
|
const curlUrlInput = document.getElementById('curl-url');
|
||||||
|
const resultsContainer = document.getElementById('curl-results');
|
||||||
|
|
||||||
|
// Add click event listener to the button
|
||||||
|
curlButton.addEventListener('click', function () {
|
||||||
|
// Get the URL from the input
|
||||||
|
const url = curlUrlInput.value.trim();
|
||||||
|
|
||||||
|
// Validate URL
|
||||||
|
if (!url) {
|
||||||
|
showMessage('Please enter a URL', 'error');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Update the url
|
||||||
|
var params;
|
||||||
|
if (window.location.search){
|
||||||
|
params = new URLSearchParams(window.location.search);
|
||||||
|
params.set('url', url);
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
params = new URLSearchParams();
|
||||||
|
params.append('url', url);
|
||||||
|
}
|
||||||
|
history.pushState(null, null, `?${params.toString()}`);
|
||||||
|
|
||||||
|
// Show loading state
|
||||||
|
curlButton.disabled = true;
|
||||||
|
curlButton.innerHTML = '<span class="spinner-border spinner-border-sm me-2" role="status" aria-hidden="true"></span>Loading...';
|
||||||
|
|
||||||
|
// Clear previous results
|
||||||
|
resultsContainer.innerHTML = '';
|
||||||
|
showMessage('Fetching content...', 'info');
|
||||||
|
|
||||||
|
// Make the request
|
||||||
|
handleCurlRequest(url);
|
||||||
|
});
|
||||||
|
// Add enter key listener to input
|
||||||
|
curlUrlInput.addEventListener('keyup', function (event) {
|
||||||
|
if (event.key === 'Enter') {
|
||||||
|
curlButton.click();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Handle the curl API request
|
||||||
|
* @param {string} url - The URL to curl
|
||||||
|
*/
|
||||||
|
function handleCurlRequest(url) {
|
||||||
|
// Encode the URL for the API endpoint
|
||||||
|
const encodedUrl = encodeURIComponent(url);
|
||||||
|
const apiEndpoint = `/api/v1/curl/${encodedUrl}`;
|
||||||
|
|
||||||
|
fetch(apiEndpoint)
|
||||||
|
.then(response => response.json())
|
||||||
|
.then(data => {
|
||||||
|
// Clear loading message
|
||||||
|
resultsContainer.innerHTML = '';
|
||||||
|
|
||||||
|
if (data.success === true) {
|
||||||
|
// Create a result card with iframe
|
||||||
|
const resultCard = document.createElement('div');
|
||||||
|
resultCard.className = 'card shadow border-0 rounded-lg mb-5';
|
||||||
|
|
||||||
|
resultCard.innerHTML = `
|
||||||
|
<div class="card-header bg-success text-white py-3 d-flex justify-content-between align-items-center">
|
||||||
|
<h3 class="h5 mb-0">Result for ${escapeHtml(url)}</h3>
|
||||||
|
<div>
|
||||||
|
<button class="btn btn-sm btn-light ms-2" id="new-tab">
|
||||||
|
<i class="bi bi-code-slash me-1"></i>View in new tab
|
||||||
|
</button>
|
||||||
|
<button class="btn btn-sm btn-light ms-2" id="view-source">
|
||||||
|
<i class="bi bi-code-slash me-1"></i>View Source
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="card-body p-0">
|
||||||
|
<iframe id="result-iframe" style="width:100%; height:600px; border:0;"></iframe>
|
||||||
|
</div>
|
||||||
|
`;
|
||||||
|
|
||||||
|
resultsContainer.appendChild(resultCard);
|
||||||
|
|
||||||
|
// Get reference to the iframe and buttons
|
||||||
|
const iframe = document.getElementById('result-iframe');
|
||||||
|
const viewSourceButton = document.getElementById('view-source');
|
||||||
|
const newTabButton = document.getElementById('new-tab');
|
||||||
|
|
||||||
|
const cleanedHtml = removeScripts(data.result);
|
||||||
|
|
||||||
|
// Create a blob URL from the cleaned HTML content
|
||||||
|
const blob = new Blob([cleanedHtml], { type: 'text/html' });
|
||||||
|
const blobUrl = URL.createObjectURL(blob);
|
||||||
|
|
||||||
|
// Set the iframe src to the blob URL
|
||||||
|
iframe.src = blobUrl;
|
||||||
|
|
||||||
|
|
||||||
|
// Add event listener to view source button
|
||||||
|
viewSourceButton.addEventListener('click', function () {
|
||||||
|
// Show source code in a modal or new window
|
||||||
|
const sourceWindow = window.open('', '_blank');
|
||||||
|
sourceWindow.document.write(`
|
||||||
|
<!DOCTYPE html>
|
||||||
|
<html>
|
||||||
|
<head>
|
||||||
|
<title>Source for ${escapeHtml(url)}</title>
|
||||||
|
<style>
|
||||||
|
body { font-family: monospace; white-space: pre-wrap; padding: 20px; }
|
||||||
|
</style>
|
||||||
|
</head>
|
||||||
|
<body>${escapeHtml(data.result)}</body>
|
||||||
|
</html>
|
||||||
|
`);
|
||||||
|
sourceWindow.document.close();
|
||||||
|
});
|
||||||
|
|
||||||
|
// Add event listener to new tab button
|
||||||
|
newTabButton.addEventListener('click', function () {
|
||||||
|
// Get URL from window.location
|
||||||
|
const proxyurl = `${window.location.protocol}//${window.location.host}/proxy/${url}`;
|
||||||
|
|
||||||
|
// Open host/proxy/url in new tab
|
||||||
|
window.open(proxyurl, '_blank');
|
||||||
|
});
|
||||||
|
|
||||||
|
// Set up cleanup when iframe is no longer needed
|
||||||
|
window.addEventListener('beforeunload', function () {
|
||||||
|
URL.revokeObjectURL(blobUrl);
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
// Show error message
|
||||||
|
showMessage(data.error || 'Failed to get content', 'error');
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.catch(error => {
|
||||||
|
showMessage(`Request failed: ${error.message}`, 'error');
|
||||||
|
})
|
||||||
|
.finally(() => {
|
||||||
|
// Reset button state
|
||||||
|
curlButton.disabled = false;
|
||||||
|
curlButton.innerHTML = '<i class="bi bi-shield-check me-2"></i>HTTP Request';
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Remove all script tags and event handlers from HTML content
|
||||||
|
* @param {string} html - The HTML content
|
||||||
|
* @returns {string} - HTML content with scripts removed
|
||||||
|
*/
|
||||||
|
function removeScripts(html) {
|
||||||
|
// Create a DOM parser to work with the HTML
|
||||||
|
const parser = new DOMParser();
|
||||||
|
const doc = parser.parseFromString(html, 'text/html');
|
||||||
|
|
||||||
|
// Remove all script elements
|
||||||
|
const scripts = doc.getElementsByTagName('script');
|
||||||
|
while (scripts.length > 0) {
|
||||||
|
scripts[0].parentNode.removeChild(scripts[0]);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Remove inline event handlers from all elements
|
||||||
|
const allElements = doc.getElementsByTagName('*');
|
||||||
|
for (let i = 0; i < allElements.length; i++) {
|
||||||
|
const element = allElements[i];
|
||||||
|
const attrs = element.attributes;
|
||||||
|
const attrsToRemove = [];
|
||||||
|
|
||||||
|
// Collect all event handler attributes (on*)
|
||||||
|
for (let j = 0; j < attrs.length; j++) {
|
||||||
|
if (attrs[j].name.toLowerCase().startsWith('on')) {
|
||||||
|
attrsToRemove.push(attrs[j].name);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Remove the collected attributes
|
||||||
|
attrsToRemove.forEach(attr => {
|
||||||
|
element.removeAttribute(attr);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
// Return the cleaned HTML
|
||||||
|
return doc.documentElement.outerHTML;
|
||||||
|
}
|
||||||
|
/**
|
||||||
|
* Display a message in the results container
|
||||||
|
* @param {string} message - The message to display
|
||||||
|
* @param {string} type - Message type (error, success, info)
|
||||||
|
*/
|
||||||
|
function showMessage(message, type) {
|
||||||
|
// Clear previous content
|
||||||
|
resultsContainer.innerHTML = '';
|
||||||
|
|
||||||
|
// Create alert element
|
||||||
|
const alert = document.createElement('div');
|
||||||
|
alert.className = `alert alert-${getAlertClass(type)} shadow-sm`;
|
||||||
|
alert.innerHTML = message;
|
||||||
|
|
||||||
|
// Add the alert to the results container
|
||||||
|
resultsContainer.appendChild(alert);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get Bootstrap alert class based on message type
|
||||||
|
* @param {string} type - Message type
|
||||||
|
* @returns {string} - Bootstrap alert class
|
||||||
|
*/
|
||||||
|
function getAlertClass(type) {
|
||||||
|
switch (type) {
|
||||||
|
case 'error': return 'danger';
|
||||||
|
case 'success': return 'success';
|
||||||
|
case 'info': default: return 'info';
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Escape HTML special characters to prevent XSS
|
||||||
|
* @param {string} text - Text to escape
|
||||||
|
* @returns {string} - Escaped text
|
||||||
|
*/
|
||||||
|
function escapeHtml(text) {
|
||||||
|
const div = document.createElement('div');
|
||||||
|
div.textContent = text;
|
||||||
|
return div.innerHTML;
|
||||||
|
}
|
||||||
|
// Check if params are present
|
||||||
|
if (window.location.search) {
|
||||||
|
const params = new URLSearchParams(window.location.search);
|
||||||
|
const url = params.get('url');
|
||||||
|
if (url) {
|
||||||
|
// Add url to input
|
||||||
|
curlUrlInput.value = url;
|
||||||
|
// Show loading state
|
||||||
|
curlButton.disabled = true;
|
||||||
|
curlButton.innerHTML = '<span class="spinner-border spinner-border-sm me-2" role="status" aria-hidden="true"></span>Loading...';
|
||||||
|
handleCurlRequest(url);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
@@ -12,19 +12,27 @@ document.getElementById("domain").addEventListener("keyup", function (event) {
|
|||||||
|
|
||||||
function getSSL() {
|
function getSSL() {
|
||||||
// Get the input value
|
// Get the input value
|
||||||
const domain = document.getElementById("domain").value;
|
const domain = document.getElementById("domain").value.trim().toLowerCase();
|
||||||
// Set the domain parameter
|
// Set the domain parameter
|
||||||
const params = new URLSearchParams();
|
var params;
|
||||||
params.append("domain", domain);
|
if (window.location.search){
|
||||||
|
params = new URLSearchParams(window.location.search);
|
||||||
|
params.set("domain", domain);
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
params = new URLSearchParams();
|
||||||
|
params.append("domain", domain);
|
||||||
|
}
|
||||||
// Add the parameters to the URL
|
// Add the parameters to the URL
|
||||||
const url = `/?${params.toString()}`;
|
const url = `/?${params.toString()}`;
|
||||||
// Push the URL to the history
|
// Push the URL to the history
|
||||||
history.pushState(null, null, url);
|
history.pushState(null, null, url);
|
||||||
|
|
||||||
// Add a loading spinner
|
// Add a loading spinner
|
||||||
document.getElementById("results").innerHTML = `<div class="spinner-border text-primary" role="status">
|
document.getElementById("ssl-results").innerHTML = `<div style="text-align: center; margin-bottom: 3rem;">
|
||||||
|
<div class="spinner-border text-primary" role="status">
|
||||||
<span class="visually-hidden">Loading...</span>
|
<span class="visually-hidden">Loading...</span>
|
||||||
</div>`;
|
</div></div>`;
|
||||||
|
|
||||||
|
|
||||||
// Send a GET request to the API
|
// Send a GET request to the API
|
||||||
@@ -34,8 +42,8 @@ function getSSL() {
|
|||||||
// Check if the request was successful
|
// Check if the request was successful
|
||||||
if (data.success) {
|
if (data.success) {
|
||||||
// Display the results
|
// Display the results
|
||||||
document.getElementById("results").innerHTML = `
|
document.getElementById("ssl-results").innerHTML = `
|
||||||
<div class="card shadow-sm p-4" style="max-width: 950px;margin: auto;">
|
<div class="card shadow-sm p-4" style="max-width: 950px;margin: auto; margin-bottom: 3rem;">
|
||||||
<h2 class="mb-3">SSL Certificate Details</h2>
|
<h2 class="mb-3">SSL Certificate Details</h2>
|
||||||
<ul class="list-group">
|
<ul class="list-group">
|
||||||
<li class="list-group-item"><strong>IP Address:</strong> ${data.ip}</li>
|
<li class="list-group-item"><strong>IP Address:</strong> ${data.ip}</li>
|
||||||
@@ -93,7 +101,7 @@ function getSSL() {
|
|||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
<div class="dnssec-details mt-2" style="display:none;">
|
<div class="dnssec-details mt-2" style="display:none;">
|
||||||
<div class="card card-body bg-light"><pre class="mb-0" style="white-space: pre-wrap; text-align: left;">${data.dnssec.errors}${data.dnssec.output}</pre>
|
<div class="card card-body bg-light"><pre class="mb-0" style="white-space: pre-wrap; text-align: left;">${data.dnssec.output}</pre>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</li>
|
</li>
|
||||||
@@ -140,13 +148,13 @@ document.querySelector('.dnssec-toggle').addEventListener('click', function() {
|
|||||||
|
|
||||||
} else {
|
} else {
|
||||||
// Display an error message
|
// Display an error message
|
||||||
document.getElementById("results").innerHTML = `<h2>Error</h2>
|
document.getElementById("ssl-results").innerHTML = `<h2>Error</h2>
|
||||||
<p>${data.message}</p>`;
|
<p style="margin-bottom: 3rem;">${data.message}</p>`;
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
.catch(error => {
|
.catch(error => {
|
||||||
// Display an error message
|
// Display an error message
|
||||||
document.getElementById("results").innerHTML = `<h2>Error</h2>
|
document.getElementById("ssl-results").innerHTML = `<h2>Error</h2>
|
||||||
<p>${error.message}</p>`;
|
<p>${error.message}</p>`;
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -8,20 +8,92 @@
|
|||||||
<link rel="icon" href="/assets/img/favicon.png" type="image/png">
|
<link rel="icon" href="/assets/img/favicon.png" type="image/png">
|
||||||
<link href="https://cdn.jsdelivr.net/npm/bootstrap@5.3.0/dist/css/bootstrap.min.css" rel="stylesheet">
|
<link href="https://cdn.jsdelivr.net/npm/bootstrap@5.3.0/dist/css/bootstrap.min.css" rel="stylesheet">
|
||||||
<link rel="stylesheet" href="/assets/css/index.css">
|
<link rel="stylesheet" href="/assets/css/index.css">
|
||||||
<script src="/assets/js/index.js" defer></script>
|
<script src="/assets/js/ssl.js" defer></script>
|
||||||
|
<script src="/assets/js/curl.js" defer></script>
|
||||||
</head>
|
</head>
|
||||||
|
|
||||||
<body>
|
<body>
|
||||||
<div class="centre" style="margin-top: 20px;">
|
<div class="container py-5">
|
||||||
<h1>HNS Tools</h1>
|
<!-- Header Section -->
|
||||||
<h2>Nathan.Woodburn/</h2>
|
<div class="text-center mb-5">
|
||||||
|
<h1 class="display-4 fw-bold text-primary"><a href="/" class="text-decoration-none text-reset">HNS Tools</a></h1>
|
||||||
|
<h2 class="h4 fw-light text-secondary"><a href="https://nathan.woodburn.au" target="_blank" class="text-decoration-none text-reset">Nathan.Woodburn/</a></h2>
|
||||||
|
<div class="border-bottom mt-4 w-50 mx-auto"></div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- SSL check Tool Card -->
|
||||||
|
<div class="row justify-content-center">
|
||||||
|
<div class="col-md-8">
|
||||||
|
<div class="card shadow border-0 rounded-lg mb-5">
|
||||||
|
<div class="card-header bg-primary text-white py-3">
|
||||||
|
<h2 class="h4 mb-0 text-center">SSL/DANE Checker</h2>
|
||||||
|
</div>
|
||||||
|
<div class="card-body p-4">
|
||||||
|
<p class="text-muted mb-4 text-center">Validate SSL certificates and DANE records for any domain</p>
|
||||||
|
|
||||||
|
<div class="input-group mb-3 shadow-sm">
|
||||||
|
<input
|
||||||
|
type="text"
|
||||||
|
id="domain"
|
||||||
|
placeholder="Enter domain name (e.g., nathan.woodburn firewallet)"
|
||||||
|
class="form-control form-control-lg"
|
||||||
|
aria-label="Domain to check"
|
||||||
|
>
|
||||||
|
<button class="btn btn-primary px-4" id="ssl">
|
||||||
|
<i class="bi bi-shield-check me-2"></i>Validate
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="text-center text-muted small">
|
||||||
|
Enter a complete domain name without any slashes or leading .
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- Results Container -->
|
||||||
|
<div id="ssl-results" class="animate__animated animate__fadeIn"></div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- CURL Tool Card -->
|
||||||
|
<div class="row justify-content-center">
|
||||||
|
<div class="col-md-8">
|
||||||
|
<div class="card shadow border-0 rounded-lg mb-5">
|
||||||
|
<div class="card-header bg-primary text-white py-3">
|
||||||
|
<h2 class="h4 mb-0 text-center">Curl Website</h2>
|
||||||
|
</div>
|
||||||
|
<div class="card-body p-4">
|
||||||
|
<p class="text-muted mb-4 text-center">Do a http request for a domain</p>
|
||||||
|
|
||||||
|
<div class="input-group mb-3 shadow-sm">
|
||||||
|
<input
|
||||||
|
type="text"
|
||||||
|
id="curl-url"
|
||||||
|
placeholder="Enter url (e.g., https://nathan.woodburn http://firewallet)"
|
||||||
|
class="form-control form-control-lg"
|
||||||
|
aria-label="URL to check"
|
||||||
|
>
|
||||||
|
<button class="btn btn-primary px-4" id="curl">
|
||||||
|
<i class="bi bi-shield-check me-2"></i>HTTP Request
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="text-center text-muted small">
|
||||||
|
Enter a complete url including the http or https protocol
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- Results Container -->
|
||||||
|
<div id="curl-results" class="animate__animated animate__fadeIn"></div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- Footer -->
|
||||||
|
<footer class="text-center mt-5 pt-4 text-muted">
|
||||||
|
<small>© 2025 <a href="https://nathan.woodburn.au" target="_blank" class="text-decoration-none text-reset">Nathan.Woodburn/</a> - Handshake Name Services Tools</small>
|
||||||
|
</footer>
|
||||||
</div>
|
</div>
|
||||||
<div class="centre">
|
</body>
|
||||||
<h2>SSL/DANE Checker</h2>
|
|
||||||
<input type="text" id="domain" placeholder="Domain to check">
|
|
||||||
<button id="ssl">Validate SSL</button>
|
|
||||||
<div id="results" style="margin-top: 20px;"></div>
|
|
||||||
</div>
|
|
||||||
</body>
|
|
||||||
|
|
||||||
</html>
|
</html>
|
||||||
308
tools.py
308
tools.py
@@ -1,4 +1,5 @@
|
|||||||
import random
|
import random
|
||||||
|
from urllib.parse import urlparse
|
||||||
import dns.resolver
|
import dns.resolver
|
||||||
import subprocess
|
import subprocess
|
||||||
import tempfile
|
import tempfile
|
||||||
@@ -6,20 +7,31 @@ import binascii
|
|||||||
from cryptography import x509
|
from cryptography import x509
|
||||||
from cryptography.hazmat.backends import default_backend
|
from cryptography.hazmat.backends import default_backend
|
||||||
import datetime
|
import datetime
|
||||||
from dns import resolver, dnssec, name, exception
|
from dns import resolver
|
||||||
import time
|
import requests
|
||||||
|
import re
|
||||||
|
from bs4 import BeautifulSoup
|
||||||
|
import requests_doh
|
||||||
|
import urllib3
|
||||||
|
import socket
|
||||||
|
import os
|
||||||
|
|
||||||
resolver = dns.resolver.Resolver()
|
resolver = dns.resolver.Resolver()
|
||||||
resolver.nameservers = ["194.50.5.28","194.50.5.27","194.50.5.26"]
|
resolver.nameservers = os.getenv("DNS_SERVERS", "194.50.5.27").split(",")
|
||||||
resolver.port = 53
|
resolver.port = int(os.getenv("DNS_PORT", "53"))
|
||||||
|
DoHsession = requests_doh.DNSOverHTTPSSession("hnsdoh")
|
||||||
|
|
||||||
|
# Disable warnings
|
||||||
|
urllib3.disable_warnings()
|
||||||
|
|
||||||
|
|
||||||
def check_ssl(domain: str):
|
def check_ssl(domain: str):
|
||||||
domain_check = False
|
domain_check = False
|
||||||
returns = {"success": False,"valid":False}
|
returns = {"success": False,"valid":False}
|
||||||
try:
|
try:
|
||||||
# Query the DNS record
|
# Query the DNS record
|
||||||
response = resolver.resolve(domain, "A")
|
response = resolver.resolve(domain, "A",lifetime=10)
|
||||||
|
|
||||||
records = []
|
records = []
|
||||||
for record in response:
|
for record in response:
|
||||||
records.append(str(record))
|
records.append(str(record))
|
||||||
@@ -55,94 +67,98 @@ def check_ssl(domain: str):
|
|||||||
certificates = [cert[cert.find("-----BEGIN CERTIFICATE-----"):] for cert in certificates]
|
certificates = [cert[cert.find("-----BEGIN CERTIFICATE-----"):] for cert in certificates]
|
||||||
|
|
||||||
if not certificates:
|
if not certificates:
|
||||||
returns["message"] = "No certificate found on remote webserver"
|
returns["cert"] = {
|
||||||
return returns
|
"cert":"",
|
||||||
|
"domains": [],
|
||||||
cert = certificates[0]
|
"domain": False,
|
||||||
returns["cert"] = {"cert": cert}
|
"expired": False,
|
||||||
|
"valid": False,
|
||||||
with tempfile.NamedTemporaryFile(mode="w", delete=False) as temp_cert_file:
|
"expiry_date": ""
|
||||||
temp_cert_file.write(cert)
|
}
|
||||||
temp_cert_file.seek(0)
|
returns["tlsa"] = {
|
||||||
|
"server": "",
|
||||||
tlsa_command = ["openssl","x509","-in",temp_cert_file.name,"-pubkey","-noout","|","openssl","pkey","-pubin","-outform","der","|","openssl","dgst","-sha256","-binary",]
|
"nameserver": "",
|
||||||
|
"match": False
|
||||||
tlsa_process = subprocess.Popen(" ".join(tlsa_command), shell=True, stdout=subprocess.PIPE)
|
}
|
||||||
tlsa_output, _ = tlsa_process.communicate()
|
|
||||||
|
|
||||||
tlsa_server = "3 1 1 " + binascii.hexlify(tlsa_output).decode("utf-8")
|
|
||||||
|
|
||||||
|
|
||||||
returns["tlsa"] = {
|
|
||||||
"server": tlsa_server,
|
|
||||||
"nameserver": "",
|
|
||||||
"match": False
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
# Get domains
|
|
||||||
cert_obj = x509.load_pem_x509_certificate(cert.encode("utf-8"), default_backend())
|
|
||||||
|
|
||||||
domains = []
|
|
||||||
for ext in cert_obj.extensions:
|
|
||||||
if ext.oid == x509.ExtensionOID.SUBJECT_ALTERNATIVE_NAME:
|
|
||||||
san_list = ext.value.get_values_for_type(x509.DNSName)
|
|
||||||
domains.extend(san_list)
|
|
||||||
|
|
||||||
# Extract the common name (CN) from the subject
|
|
||||||
common_name = cert_obj.subject.get_attributes_for_oid(x509.NameOID.COMMON_NAME)
|
|
||||||
if common_name:
|
|
||||||
if common_name[0].value not in domains:
|
|
||||||
domains.append(common_name[0].value)
|
|
||||||
|
|
||||||
|
|
||||||
if domains:
|
|
||||||
cert_domains = []
|
|
||||||
for cn in domains:
|
|
||||||
cert_domains.append(cn)
|
|
||||||
if cn == domain:
|
|
||||||
domain_check = True
|
|
||||||
elif cn.startswith("*."):
|
|
||||||
if domain.endswith(cn[1:]):
|
|
||||||
domain_check = True
|
|
||||||
|
|
||||||
returns["cert"]["domains"] = cert_domains
|
|
||||||
returns["cert"]["domain"] = domain_check
|
|
||||||
|
|
||||||
expiry_date = cert_obj.not_valid_after_utc
|
|
||||||
# Check if expiry date is past
|
|
||||||
if expiry_date < datetime.datetime.now(datetime.timezone.utc):
|
|
||||||
returns["cert"]["expired"] = True
|
|
||||||
returns["cert"]["valid"] = False
|
|
||||||
|
|
||||||
else:
|
else:
|
||||||
returns["cert"]["expired"] = False
|
|
||||||
returns["cert"]["valid"] = True if domain_check else False
|
cert = certificates[0]
|
||||||
|
returns["cert"] = {"cert": cert}
|
||||||
|
|
||||||
|
with tempfile.NamedTemporaryFile(mode="w", delete=False) as temp_cert_file:
|
||||||
|
temp_cert_file.write(cert)
|
||||||
|
temp_cert_file.seek(0)
|
||||||
|
|
||||||
returns["cert"]["expiry_date"] = expiry_date.strftime("%d %B %Y %H:%M:%S")
|
tlsa_command = ["openssl","x509","-in",temp_cert_file.name,"-pubkey","-noout","|","openssl","pkey","-pubin","-outform","der","|","openssl","dgst","-sha256","-binary",]
|
||||||
|
|
||||||
|
tlsa_process = subprocess.Popen(" ".join(tlsa_command), shell=True, stdout=subprocess.PIPE)
|
||||||
|
tlsa_output, _ = tlsa_process.communicate()
|
||||||
|
|
||||||
|
tlsa_server = "3 1 1 " + binascii.hexlify(tlsa_output).decode("utf-8")
|
||||||
|
|
||||||
|
|
||||||
|
returns["tlsa"] = {
|
||||||
|
"server": tlsa_server,
|
||||||
|
"nameserver": "",
|
||||||
|
"match": False
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
# Get domains
|
||||||
|
cert_obj = x509.load_pem_x509_certificate(cert.encode("utf-8"), default_backend())
|
||||||
|
|
||||||
|
domains = []
|
||||||
|
for ext in cert_obj.extensions:
|
||||||
|
if ext.oid == x509.ExtensionOID.SUBJECT_ALTERNATIVE_NAME:
|
||||||
|
san_list = ext.value.get_values_for_type(x509.DNSName)
|
||||||
|
domains.extend(san_list)
|
||||||
|
|
||||||
|
# Extract the common name (CN) from the subject
|
||||||
|
common_name = cert_obj.subject.get_attributes_for_oid(x509.NameOID.COMMON_NAME)
|
||||||
|
if common_name:
|
||||||
|
if common_name[0].value not in domains:
|
||||||
|
domains.append(common_name[0].value)
|
||||||
|
|
||||||
|
|
||||||
|
if domains:
|
||||||
|
cert_domains = []
|
||||||
|
for cn in domains:
|
||||||
|
cert_domains.append(cn)
|
||||||
|
if cn == domain:
|
||||||
|
domain_check = True
|
||||||
|
elif cn.startswith("*."):
|
||||||
|
if domain.endswith(cn[1:]):
|
||||||
|
domain_check = True
|
||||||
|
|
||||||
|
returns["cert"]["domains"] = cert_domains
|
||||||
|
returns["cert"]["domain"] = domain_check
|
||||||
|
|
||||||
|
expiry_date = cert_obj.not_valid_after_utc
|
||||||
|
# Check if expiry date is past
|
||||||
|
if expiry_date < datetime.datetime.now(datetime.timezone.utc):
|
||||||
|
returns["cert"]["expired"] = True
|
||||||
|
returns["cert"]["valid"] = False
|
||||||
|
|
||||||
|
else:
|
||||||
|
returns["cert"]["expired"] = False
|
||||||
|
returns["cert"]["valid"] = True if domain_check else False
|
||||||
|
|
||||||
|
returns["cert"]["expiry_date"] = expiry_date.strftime("%d %B %Y %H:%M:%S")
|
||||||
|
|
||||||
try:
|
try:
|
||||||
# Check for TLSA record
|
# Check for TLSA record
|
||||||
response = resolver.resolve("_443._tcp."+domain, "TLSA")
|
response = resolver.resolve("_443._tcp."+domain, "TLSA",lifetime=10)
|
||||||
tlsa_records = []
|
tlsa_records = []
|
||||||
for record in response:
|
for record in response:
|
||||||
tlsa_records.append(str(record))
|
tlsa_records.append(str(record))
|
||||||
|
|
||||||
if not tlsa_records:
|
if tlsa_records:
|
||||||
returns["message"] = "No TLSA record found on DNS"
|
returns["tlsa"]["nameserver"] = tlsa_records[0]
|
||||||
return returns
|
if tlsa_server == tlsa_records[0]:
|
||||||
|
returns["tlsa"]["match"] = True
|
||||||
returns["tlsa"]["nameserver"] = tlsa_records[0]
|
|
||||||
if tlsa_server == tlsa_records[0]:
|
|
||||||
returns["tlsa"]["match"] = True
|
|
||||||
|
|
||||||
|
|
||||||
except:
|
except:
|
||||||
returns["message"] = "No TLSA record found on DNS"
|
returns["tlsa"]["error"] = "No TLSA record found on DNS"
|
||||||
return returns
|
|
||||||
|
|
||||||
# Check if valid
|
# Check if valid
|
||||||
if returns["cert"]["valid"] and returns["tlsa"]["match"] and returns["dnssec"]["valid"]:
|
if returns["cert"]["valid"] and returns["tlsa"]["match"] and returns["dnssec"]["valid"]:
|
||||||
@@ -152,6 +168,9 @@ def check_ssl(domain: str):
|
|||||||
return returns
|
return returns
|
||||||
|
|
||||||
# Catch all exceptions
|
# Catch all exceptions
|
||||||
|
except dns.exception.Timeout:
|
||||||
|
returns["message"] = f"DNS resolution timed out for {domain}. Please check your internet connection or try again later."
|
||||||
|
return returns
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
returns["message"] = f"An error occurred: {e}"
|
returns["message"] = f"An error occurred: {e}"
|
||||||
return returns
|
return returns
|
||||||
@@ -159,11 +178,130 @@ def check_ssl(domain: str):
|
|||||||
|
|
||||||
def validate_dnssec(domain):
|
def validate_dnssec(domain):
|
||||||
# Pick a random resolver
|
# Pick a random resolver
|
||||||
resolverIP = random.choice(resolver.nameservers)
|
resolverIP = resolver.nameservers[0]
|
||||||
|
resolverPort = resolver.port
|
||||||
# delv @194.50.5.28 -a hsd-ksk nathan.woodburn A +rtrace +vtrace
|
# delv @194.50.5.28 -a hsd-ksk nathan.woodburn A +rtrace +vtrace
|
||||||
command = f"delv @{resolverIP} -a hsd-ksk {domain} A +rtrace +vtrace"
|
command = f"delv @{resolverIP} -p {resolverPort} -a hsd-ksk {domain} A +rtrace +vtrace"
|
||||||
result = subprocess.run(command, shell=True, capture_output=True, text=True)
|
result = subprocess.run(command, shell=True, capture_output=True, text=True)
|
||||||
if "; fully validated" in result.stdout:
|
if "; fully validated" in result.stdout or "; negative response, fully validated" in result.stdout:
|
||||||
return {"valid": True, "message": "DNSSEC is valid", "output": result.stdout, "errors": result.stderr}
|
return {"valid": True, "message": "DNSSEC is valid", "output": result.stderr + result.stdout}
|
||||||
else:
|
else:
|
||||||
return {"valid": False, "message": "DNSSEC is not valid", "output": result.stdout, "errors": result.stderr}
|
return {"valid": False, "message": "DNSSEC is not valid", "output": result.stderr + result.stdout}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
def curl(url: str):
|
||||||
|
if not url.startswith("http"):
|
||||||
|
url = "http://" + url
|
||||||
|
try:
|
||||||
|
# curl --doh-url https://hnsdoh.com/dns-query {url} --insecure
|
||||||
|
command = f"curl --doh-url https://hnsdoh.com/dns-query {url} --insecure --silent -A 'Woodburn'"
|
||||||
|
response = subprocess.run(command, shell=True, capture_output=True, text=True, timeout=10)
|
||||||
|
if response.returncode != 0:
|
||||||
|
return {"success": False, "error": response.stderr}
|
||||||
|
else:
|
||||||
|
return {"success": True, "result": response.stdout}
|
||||||
|
|
||||||
|
except Exception as e:
|
||||||
|
return {"success": False, "error": "An error occurred", "message": str(e)}
|
||||||
|
|
||||||
|
|
||||||
|
class ProxyError(Exception):
|
||||||
|
def __init__(self, message):
|
||||||
|
self.message = message
|
||||||
|
self.text = message
|
||||||
|
self.ok = False
|
||||||
|
self.status_code = 500
|
||||||
|
super().__init__(self.message)
|
||||||
|
|
||||||
|
|
||||||
|
def proxy(url: str) -> requests.Response:
|
||||||
|
try:
|
||||||
|
r = DoHsession.get(url,verify=False,timeout=30)
|
||||||
|
return r
|
||||||
|
except Exception as e:
|
||||||
|
return ProxyError(str(e))
|
||||||
|
|
||||||
|
def cleanProxyContent(htmlContent: str,url:str, proxyHost: str):
|
||||||
|
# Set proxy host to https if not 127.0.0.1 or localhost
|
||||||
|
if ":5000" not in proxyHost:
|
||||||
|
proxyHost = proxyHost.replace("http","https")
|
||||||
|
|
||||||
|
# Find all instances of the url in the html
|
||||||
|
hostUrl = f"{urlparse(url).scheme}://{urlparse(url).netloc}"
|
||||||
|
proxyUrl = f"{proxyHost}proxy/{hostUrl}"
|
||||||
|
# htmlContent = htmlContent.replace(hostUrl,proxyUrl)
|
||||||
|
|
||||||
|
# parse html
|
||||||
|
soup = BeautifulSoup(htmlContent, 'html.parser')
|
||||||
|
# find all resources
|
||||||
|
|
||||||
|
|
||||||
|
for linkType in ['link','img','script', 'a']:
|
||||||
|
links = soup.find_all(linkType)
|
||||||
|
for link in links:
|
||||||
|
for attrib in ['src','href']:
|
||||||
|
if link.has_attr(attrib):
|
||||||
|
if str(link[attrib]).startswith('/'):
|
||||||
|
link.attrs[attrib] = proxyUrl + link[attrib]
|
||||||
|
continue
|
||||||
|
if str(link[attrib]).startswith('http'):
|
||||||
|
link.attrs[attrib] = str(link[attrib]).replace(hostUrl,proxyUrl)
|
||||||
|
continue
|
||||||
|
ignored = False
|
||||||
|
for ignore in ["data:", "mailto:", "tel:", "javascript:", "blob:"]:
|
||||||
|
if str(link[attrib]).startswith(ignore):
|
||||||
|
ignored = True
|
||||||
|
break
|
||||||
|
if not ignored:
|
||||||
|
if link[attrib].startswith("/"):
|
||||||
|
link.attrs[attrib] = f"{proxyUrl}{link[attrib]}"
|
||||||
|
else:
|
||||||
|
link.attrs[attrib] = f"{proxyUrl}/{urlparse(url).path}{link[attrib]}"
|
||||||
|
|
||||||
|
scripts = soup.find_all('script')
|
||||||
|
for script in scripts:
|
||||||
|
if script.has_attr("text"):
|
||||||
|
script.attrs["text"] = proxyCleanJS(script.text,url,proxyHost)
|
||||||
|
continue
|
||||||
|
if not script.has_attr("contents"):
|
||||||
|
continue
|
||||||
|
if len(script.contents) > 0:
|
||||||
|
newScript = soup.new_tag("script")
|
||||||
|
for content in script.contents:
|
||||||
|
newScript.append(proxyCleanJS(content,url,proxyHost))
|
||||||
|
script.replace_with(newScript)
|
||||||
|
|
||||||
|
return soup.prettify()
|
||||||
|
|
||||||
|
def proxyCleanJS(jsContent: str, url: str, proxyHost: str):
|
||||||
|
# Set proxy host to https if not 127.0.0.1 or localhost
|
||||||
|
if ":5000" not in proxyHost:
|
||||||
|
proxyHost = proxyHost.replace("http","https")
|
||||||
|
|
||||||
|
hostUrl = f"{urlparse(url).scheme}://{urlparse(url).netloc}"
|
||||||
|
proxyUrl = f"{proxyHost}proxy/{hostUrl}"
|
||||||
|
|
||||||
|
if "dprofile" in url:
|
||||||
|
jsContent = jsContent.replace("window.location.hostname", f"\"{urlparse(url).netloc}\"")
|
||||||
|
jsContent = jsContent.replace("src=\"img", f"src=\"{proxyUrl}/img")
|
||||||
|
|
||||||
|
return jsContent
|
||||||
|
|
||||||
|
|
||||||
|
# Replace all instances of the url with the proxy url
|
||||||
|
hostUrl = f"{urlparse(url).scheme}://{urlparse(url).netloc}"
|
||||||
|
proxyUrl = f"{proxyHost}proxy/{hostUrl}"
|
||||||
|
|
||||||
|
jsContent = jsContent.replace(hostUrl,proxyUrl)
|
||||||
|
# Common ways to get current url
|
||||||
|
for locator in ["window.location.href","window.location","location.href","location"]:
|
||||||
|
jsContent = jsContent.replace(locator,proxyUrl)
|
||||||
|
|
||||||
|
|
||||||
|
return jsContent
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
# if __name__ == "__main__":
|
||||||
|
# print(curl("https://dso.dprofile"))
|
||||||
Reference in New Issue
Block a user