From 0e56b2609e6483797035372e7cb9550fea579eed Mon Sep 17 00:00:00 2001 From: Nathan Woodburn Date: Thu, 27 Feb 2025 14:14:27 +1100 Subject: [PATCH] feat: Add curl --- server.py | 8 ++ templates/assets/js/curl.js | 190 ++++++++++++++++++++++++++++++++++++ templates/index.html | 38 +++++++- tools.py | 25 ++++- 4 files changed, 256 insertions(+), 5 deletions(-) create mode 100644 templates/assets/js/curl.js diff --git a/server.py b/server.py index 37b582d..2423c94 100644 --- a/server.py +++ b/server.py @@ -17,6 +17,7 @@ from datetime import datetime import dotenv import re import tools +import urllib.parse dotenv.load_dotenv() @@ -112,6 +113,13 @@ def api_ssl(domain: str): result = tools.check_ssl(domain) return jsonify(result) +@app.route("/api/v1/curl/") +def api_curl(url: str): + # Decode the URL + url = urllib.parse.unquote(url) + + result = tools.curl(url) + return jsonify(result) # endregion diff --git a/templates/assets/js/curl.js b/templates/assets/js/curl.js new file mode 100644 index 0000000..0d8c42d --- /dev/null +++ b/templates/assets/js/curl.js @@ -0,0 +1,190 @@ +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 + const params = new URLSearchParams(); + params.append('url', url); + history.pushState(null, null, `?${params.toString()}`); + + // Show loading state + curlButton.disabled = true; + curlButton.innerHTML = '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 = ` +
+

Result for ${escapeHtml(url)}

+
+ + +
+
+
+ +
+ `; + + resultsContainer.appendChild(resultCard); + + // Get reference to the iframe and buttons + const iframe = document.getElementById('result-iframe'); + const refreshButton = document.getElementById('refresh-iframe'); + const viewSourceButton = document.getElementById('view-source'); + + // Create a blob URL from the HTML content + const blob = new Blob([data.result], { type: 'text/html' }); + const blobUrl = URL.createObjectURL(blob); + + // Set the iframe src to the blob URL + iframe.src = blobUrl; + + // Add event listener to refresh button + refreshButton.addEventListener('click', function() { + // Reload the content + handleCurlRequest(url); + }); + + // 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(` + + + + Source for ${escapeHtml(url)} + + + ${escapeHtml(data.result)} + + `); + sourceWindow.document.close(); + }); + + // 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 = 'HTTP Request'; + }); + } + + /** + * 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 = 'Loading...'; + handleCurlRequest(url); + } + } + }); \ No newline at end of file diff --git a/templates/index.html b/templates/index.html index 1b3ec24..45d968a 100644 --- a/templates/index.html +++ b/templates/index.html @@ -9,7 +9,7 @@ - + @@ -21,7 +21,7 @@
- +
@@ -54,6 +54,40 @@
+ + +
+
+
+
+

Curl Website

+
+
+

Do a http request for a domain

+ +
+ + +
+ +
+ Enter a complete url including the http or https protocol +
+
+
+ + +
+
+