diff --git a/package.json b/package.json index cd6933f..5621d88 100644 --- a/package.json +++ b/package.json @@ -21,6 +21,8 @@ "@radix-ui/react-slot": "^1.1.0", "class-variance-authority": "^0.7.0", "clsx": "^2.1.1", + "iconv-lite": "^0.6.3", + "jschardet": "^3.1.4", "lucide-react": "^0.447.0", "next": "14.2.14", "react": "^18", diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 3d11be1..ab91e98 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -44,6 +44,12 @@ importers: clsx: specifier: ^2.1.1 version: 2.1.1 + iconv-lite: + specifier: ^0.6.3 + version: 0.6.3 + jschardet: + specifier: ^3.1.4 + version: 3.1.4 lucide-react: specifier: ^0.447.0 version: 0.447.0(react@18.3.1) @@ -777,6 +783,10 @@ packages: htmlparser2@9.1.0: resolution: {integrity: sha512-5zfg6mHUoaer/97TxnGpxmbR7zJtPwIYFMZ/H5ucTlPZhKvtum05yiPK3Mgai3a0DyVxv7qYqoweaEd2nrYQzQ==} + iconv-lite@0.6.3: + resolution: {integrity: sha512-4fCk79wshMdzMp2rH06qWrJE4iolqLhCUH+OiuIgU++RB0+94NlDL81atO7GX55uUKueo0txHNtvEyI6D7WdMw==} + engines: {node: '>=0.10.0'} + invariant@2.2.4: resolution: {integrity: sha512-phJfQVBuaJM5raOpJjSfkiD6BpbCE4Ns//LaXl6wGYtUBY83nWS6Rf9tXm2e8VaK60JEjYldbPif/A2B1C2gNA==} @@ -821,6 +831,10 @@ packages: js-tokens@4.0.0: resolution: {integrity: sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==} + jschardet@3.1.4: + resolution: {integrity: sha512-/kmVISmrwVwtyYU40iQUOp3SUPk2dhNCMsZBQX0R1/jZ8maaXJ/oZIzUOiyOqcgtLnETFKYChbJ5iDC/eWmFHg==} + engines: {node: '>=0.1.90'} + lilconfig@2.1.0: resolution: {integrity: sha512-utWOt/GHzuUxnLKxB6dk81RoOeoNeHgbrXiuGk4yyF5qlRz+iIVWu56E2fqGHFrXz0QNUhLB/8nKqvRH66JKGQ==} engines: {node: '>=10'} @@ -1053,6 +1067,9 @@ packages: run-parallel@1.2.0: resolution: {integrity: sha512-5l4VyZR86LZ/lDxZTR6jqL8AFE2S0IFLMP26AbjsLVADxHdhB/c0GUsH+y39UfCi3dzz8OlQuPmnaJOMoDHQBA==} + safer-buffer@2.1.2: + resolution: {integrity: sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==} + sanitize-html@2.13.0: resolution: {integrity: sha512-Xff91Z+4Mz5QiNSLdLWwjgBDm5b1RU6xBT0+12rapjiaR7SwfRdjw8f+6Rir2MXKLrDicRFHdb51hGOAxmsUIA==} @@ -1853,6 +1870,10 @@ snapshots: domutils: 3.1.0 entities: 4.5.0 + iconv-lite@0.6.3: + dependencies: + safer-buffer: 2.1.2 + invariant@2.2.4: dependencies: loose-envify: 1.4.0 @@ -1889,6 +1910,8 @@ snapshots: js-tokens@4.0.0: {} + jschardet@3.1.4: {} + lilconfig@2.1.0: {} lilconfig@3.1.2: {} @@ -2102,6 +2125,8 @@ snapshots: dependencies: queue-microtask: 1.2.3 + safer-buffer@2.1.2: {} + sanitize-html@2.13.0: dependencies: deepmerge: 4.3.1 diff --git a/src/app/api/fetchFeed/route.js b/src/app/api/fetchFeed/route.js index 056f57b..2578d9e 100644 --- a/src/app/api/fetchFeed/route.js +++ b/src/app/api/fetchFeed/route.js @@ -1,33 +1,47 @@ -// route.js +import jschardet from "jschardet"; +import iconv from "iconv-lite"; export async function GET(req) { const { searchParams } = new URL(req.url); const url = searchParams.get("url"); if (!url) { - return new Response('Missing "url" parameter', { status: 400 }); + return new Response("Request missing required parameter 'url'.", { + status: 400, + }); } if (url.endsWith("/api/fetchFeed")) { - return new Response("nice try", { status: 400 }); + return new Response("You can't recursively call this endpoint.", { + status: 400, + }); } try { const response = await fetch(url); if (!response.ok) { - return new Response("Failed to fetch the URL", { + return new Response("Failed to fetch the feed.", { status: response.status, }); } - const text = await response.text(); - return new Response(text, { + const buffer = await response.arrayBuffer(); + + const detectedEncoding = jschardet.detect(Buffer.from(buffer)); + let encoding = detectedEncoding.encoding || "utf-8"; + + const decodedText = iconv.decode(Buffer.from(buffer), encoding); + + return new Response(decodedText, { headers: { - "Content-Type": response.headers.get("Content-Type"), + "Content-Type": response.headers.get("Content-Type") || "text/plain", }, }); } catch (error) { - return new Response("Error fetching the URL", { status: 500 }); + console.error("There was an error while fetching this feed: ", error); + return new Response("There was an error while fetching this feed", { + status: 500, + }); } }