From 2fdc13bfe6e5081fb46dd5cfea26ab20dfed9b36 Mon Sep 17 00:00:00 2001
From: Renan Bernordi <altendorfme@gmail.com>
Date: Fri, 22 Nov 2024 17:41:07 -0300
Subject: [PATCH] commit inicial

---
 .github/workflows/release.yml |  87 +++++
 .gitignore                    |  93 +++++
 Dockerfile                    |  28 ++
 LICENSE.md                    | 674 +++++++++++++++++++++++++++++++++
 README.md                     | 115 ++++++
 TEST_URLS.md                  |  49 +++
 app/.env.sample               |  16 +
 app/api.php                   | 109 ++++++
 app/assets/js/scripts.js      |  67 ++++
 app/assets/opengraph.png      | Bin 0 -> 7367 bytes
 app/assets/svg/archive.svg    |   1 +
 app/assets/svg/bookmark.svg   |   1 +
 app/assets/svg/bypass.svg     |   1 +
 app/assets/svg/code.svg       |   1 +
 app/assets/svg/error.svg      |   1 +
 app/assets/svg/link.svg       |   1 +
 app/assets/svg/marreta.svg    |   1 +
 app/assets/svg/search.svg     |   1 +
 app/assets/svg/warning.svg    |   1 +
 app/composer.json             |   5 +
 app/config.php                | 196 ++++++++++
 app/inc/Cache.php             |  78 ++++
 app/inc/Rules.php             | 352 +++++++++++++++++
 app/inc/URLAnalyzer.php       | 686 ++++++++++++++++++++++++++++++++++
 app/index.php                 | 205 ++++++++++
 app/p.php                     |  65 ++++
 default.conf                  |  43 +++
 docker-compose.yml            |  19 +
 env.sh                        |  19 +
 start.sh                      |  55 +++
 30 files changed, 2970 insertions(+)
 create mode 100644 .github/workflows/release.yml
 create mode 100644 .gitignore
 create mode 100644 Dockerfile
 create mode 100644 LICENSE.md
 create mode 100644 README.md
 create mode 100644 TEST_URLS.md
 create mode 100644 app/.env.sample
 create mode 100644 app/api.php
 create mode 100644 app/assets/js/scripts.js
 create mode 100644 app/assets/opengraph.png
 create mode 100644 app/assets/svg/archive.svg
 create mode 100644 app/assets/svg/bookmark.svg
 create mode 100644 app/assets/svg/bypass.svg
 create mode 100644 app/assets/svg/code.svg
 create mode 100644 app/assets/svg/error.svg
 create mode 100644 app/assets/svg/link.svg
 create mode 100644 app/assets/svg/marreta.svg
 create mode 100644 app/assets/svg/search.svg
 create mode 100644 app/assets/svg/warning.svg
 create mode 100644 app/composer.json
 create mode 100644 app/config.php
 create mode 100644 app/inc/Cache.php
 create mode 100644 app/inc/Rules.php
 create mode 100644 app/inc/URLAnalyzer.php
 create mode 100644 app/index.php
 create mode 100644 app/p.php
 create mode 100644 default.conf
 create mode 100644 docker-compose.yml
 create mode 100644 env.sh
 create mode 100644 start.sh

diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml
new file mode 100644
index 0000000..74cb9a9
--- /dev/null
+++ b/.github/workflows/release.yml
@@ -0,0 +1,87 @@
+name: 🛠️ Main
+run-name: 🚀 Deploy de versão
+
+on:
+  push:
+    tags:
+      - '*.*.*'
+
+env:
+  DOCKER_REGISTRY: ghcr.io
+  DOCKER_IMAGE_NAME: ${{ github.repository }}
+
+jobs:
+  docker-build:
+    name: 🐳 Build e Push
+    runs-on: ubuntu-latest
+    permissions:
+      contents: read
+      packages: write
+    
+    steps:
+      - name: 📥 Checkout código
+        uses: actions/checkout@v4
+
+      - name: 🏷️ Extrair versão da tag
+        id: get_version
+        run: echo "VERSION=${GITHUB_REF#refs/tags/}" >> $GITHUB_OUTPUT
+
+      - name: 🔧 Configurar QEMU
+        uses: docker/setup-qemu-action@v3
+
+      - name: 🛠️ Configurar Docker Buildx
+        uses: docker/setup-buildx-action@v3
+        with:
+          platforms: linux/amd64,linux/arm64,linux/arm/v7
+
+      - name: 📋 Extrair metadata Docker
+        id: meta
+        uses: docker/metadata-action@v5
+        with:
+          images: ${{ env.DOCKER_REGISTRY }}/${{ env.DOCKER_IMAGE_NAME }}
+          tags: |
+            type=semver,pattern={{version}}
+            type=semver,pattern={{major}}.{{minor}}
+            type=sha
+
+      - name: 🔐 Login no Registry
+        uses: docker/login-action@v3
+        with:
+          registry: ${{ env.DOCKER_REGISTRY }}
+          username: ${{ github.actor }}
+          password: ${{ secrets.GITHUB_TOKEN }}
+
+      - name: 🏗️ Build e Push
+        uses: docker/build-push-action@v5
+        with:
+          context: .
+          platforms: linux/amd64,linux/arm64,linux/arm/v7
+          push: true
+          tags: ${{ steps.meta.outputs.tags }}
+          labels: ${{ steps.meta.outputs.labels }}
+          cache-from: type=gha
+          cache-to: type=gha,mode=max
+
+  publish-release:
+    name: 📦 Publicar Release
+    runs-on: ubuntu-latest
+    needs: docker-build
+    permissions:
+      contents: write
+    
+    steps:
+      - name: 📥 Checkout código
+        uses: actions/checkout@v4
+
+      - name: 🏷️ Extrair versão da tag
+        id: get_version
+        run: echo "VERSION=${GITHUB_REF#refs/tags/}" >> $GITHUB_OUTPUT
+
+      - name: 📝 Criar Release
+        uses: softprops/action-gh-release@v1
+        with:
+          name: "🎉 Release v${{ steps.get_version.outputs.VERSION }}"
+          tag_name: ${{ steps.get_version.outputs.VERSION }}
+          generate_release_notes: true
+          draft: false
+          prerelease: false
\ No newline at end of file
diff --git a/.gitignore b/.gitignore
new file mode 100644
index 0000000..13b3549
--- /dev/null
+++ b/.gitignore
@@ -0,0 +1,93 @@
+vendor/
+composer.lock
+.env
+app/logs/*.log
+app/cache/*.html
+TODO.md
+
+# Created by https://www.toptal.com/developers/gitignore/api/composer,windows,macos,linux
+# Edit at https://www.toptal.com/developers/gitignore?templates=composer,windows,macos,linux
+
+### Composer ###
+composer.phar
+/vendor/
+
+# Commit your application's lock file https://getcomposer.org/doc/01-basic-usage.md#commit-your-composer-lock-file-to-version-control
+# You may choose to ignore a library lock file http://getcomposer.org/doc/02-libraries.md#lock-file
+# composer.lock
+
+### Linux ###
+*~
+
+# temporary files which can be created if a process still has a handle open of a deleted file
+.fuse_hidden*
+
+# KDE directory preferences
+.directory
+
+# Linux trash folder which might appear on any partition or disk
+.Trash-*
+
+# .nfs files are created when an open file is removed but is still being accessed
+.nfs*
+
+### macOS ###
+# General
+.DS_Store
+.AppleDouble
+.LSOverride
+
+# Icon must end with two \r
+Icon
+
+
+# Thumbnails
+._*
+
+# Files that might appear in the root of a volume
+.DocumentRevisions-V100
+.fseventsd
+.Spotlight-V100
+.TemporaryItems
+.Trashes
+.VolumeIcon.icns
+.com.apple.timemachine.donotpresent
+
+# Directories potentially created on remote AFP share
+.AppleDB
+.AppleDesktop
+Network Trash Folder
+Temporary Items
+.apdisk
+
+### macOS Patch ###
+# iCloud generated files
+*.icloud
+
+### Windows ###
+# Windows thumbnail cache files
+Thumbs.db
+Thumbs.db:encryptable
+ehthumbs.db
+ehthumbs_vista.db
+
+# Dump file
+*.stackdump
+
+# Folder config file
+[Dd]esktop.ini
+
+# Recycle Bin used on file shares
+$RECYCLE.BIN/
+
+# Windows Installer files
+*.cab
+*.msi
+*.msix
+*.msm
+*.msp
+
+# Windows shortcuts
+*.lnk
+
+# End of https://www.toptal.com/developers/gitignore/api/composer,windows,macos,linux
\ No newline at end of file
diff --git a/Dockerfile b/Dockerfile
new file mode 100644
index 0000000..50f3342
--- /dev/null
+++ b/Dockerfile
@@ -0,0 +1,28 @@
+FROM php:8.0-fpm
+
+RUN apt-get update && apt-get install -y nginx nano procps unzip git htop
+	
+RUN curl -sS https://getcomposer.org/installer | php -- --install-dir=/usr/local/bin --filename=composer
+
+COPY default.conf /etc/nginx/sites-available/default
+
+RUN mkdir -p /app
+COPY app/ /app/
+
+WORKDIR /app
+RUN composer install --no-interaction --optimize-autoloader
+
+COPY env.sh /usr/local/bin/env.sh
+RUN chmod +x /usr/local/bin/env.sh
+
+COPY start.sh /usr/local/bin/start.sh
+RUN chmod +x /usr/local/bin/start.sh
+
+RUN mkdir -p /app/cache /app/logs
+RUN chown -R www-data:www-data /app && chmod -R 755 /app
+
+VOLUME ["/app/cache", "/app/logs"]
+
+EXPOSE 80
+
+CMD ["/bin/bash", "-c", "/usr/local/bin/env.sh && /usr/local/bin/start.sh"]
diff --git a/LICENSE.md b/LICENSE.md
new file mode 100644
index 0000000..e72bfdd
--- /dev/null
+++ b/LICENSE.md
@@ -0,0 +1,674 @@
+                    GNU GENERAL PUBLIC LICENSE
+                       Version 3, 29 June 2007
+
+ Copyright (C) 2007 Free Software Foundation, Inc. <https://fsf.org/>
+ Everyone is permitted to copy and distribute verbatim copies
+ of this license document, but changing it is not allowed.
+
+                            Preamble
+
+  The GNU General Public License is a free, copyleft license for
+software and other kinds of works.
+
+  The licenses for most software and other practical works are designed
+to take away your freedom to share and change the works.  By contrast,
+the GNU General Public License is intended to guarantee your freedom to
+share and change all versions of a program--to make sure it remains free
+software for all its users.  We, the Free Software Foundation, use the
+GNU General Public License for most of our software; it applies also to
+any other work released this way by its authors.  You can apply it to
+your programs, too.
+
+  When we speak of free software, we are referring to freedom, not
+price.  Our General Public Licenses are designed to make sure that you
+have the freedom to distribute copies of free software (and charge for
+them if you wish), that you receive source code or can get it if you
+want it, that you can change the software or use pieces of it in new
+free programs, and that you know you can do these things.
+
+  To protect your rights, we need to prevent others from denying you
+these rights or asking you to surrender the rights.  Therefore, you have
+certain responsibilities if you distribute copies of the software, or if
+you modify it: responsibilities to respect the freedom of others.
+
+  For example, if you distribute copies of such a program, whether
+gratis or for a fee, you must pass on to the recipients the same
+freedoms that you received.  You must make sure that they, too, receive
+or can get the source code.  And you must show them these terms so they
+know their rights.
+
+  Developers that use the GNU GPL protect your rights with two steps:
+(1) assert copyright on the software, and (2) offer you this License
+giving you legal permission to copy, distribute and/or modify it.
+
+  For the developers' and authors' protection, the GPL clearly explains
+that there is no warranty for this free software.  For both users' and
+authors' sake, the GPL requires that modified versions be marked as
+changed, so that their problems will not be attributed erroneously to
+authors of previous versions.
+
+  Some devices are designed to deny users access to install or run
+modified versions of the software inside them, although the manufacturer
+can do so.  This is fundamentally incompatible with the aim of
+protecting users' freedom to change the software.  The systematic
+pattern of such abuse occurs in the area of products for individuals to
+use, which is precisely where it is most unacceptable.  Therefore, we
+have designed this version of the GPL to prohibit the practice for those
+products.  If such problems arise substantially in other domains, we
+stand ready to extend this provision to those domains in future versions
+of the GPL, as needed to protect the freedom of users.
+
+  Finally, every program is threatened constantly by software patents.
+States should not allow patents to restrict development and use of
+software on general-purpose computers, but in those that do, we wish to
+avoid the special danger that patents applied to a free program could
+make it effectively proprietary.  To prevent this, the GPL assures that
+patents cannot be used to render the program non-free.
+
+  The precise terms and conditions for copying, distribution and
+modification follow.
+
+                       TERMS AND CONDITIONS
+
+  0. Definitions.
+
+  "This License" refers to version 3 of the GNU General Public License.
+
+  "Copyright" also means copyright-like laws that apply to other kinds of
+works, such as semiconductor masks.
+
+  "The Program" refers to any copyrightable work licensed under this
+License.  Each licensee is addressed as "you".  "Licensees" and
+"recipients" may be individuals or organizations.
+
+  To "modify" a work means to copy from or adapt all or part of the work
+in a fashion requiring copyright permission, other than the making of an
+exact copy.  The resulting work is called a "modified version" of the
+earlier work or a work "based on" the earlier work.
+
+  A "covered work" means either the unmodified Program or a work based
+on the Program.
+
+  To "propagate" a work means to do anything with it that, without
+permission, would make you directly or secondarily liable for
+infringement under applicable copyright law, except executing it on a
+computer or modifying a private copy.  Propagation includes copying,
+distribution (with or without modification), making available to the
+public, and in some countries other activities as well.
+
+  To "convey" a work means any kind of propagation that enables other
+parties to make or receive copies.  Mere interaction with a user through
+a computer network, with no transfer of a copy, is not conveying.
+
+  An interactive user interface displays "Appropriate Legal Notices"
+to the extent that it includes a convenient and prominently visible
+feature that (1) displays an appropriate copyright notice, and (2)
+tells the user that there is no warranty for the work (except to the
+extent that warranties are provided), that licensees may convey the
+work under this License, and how to view a copy of this License.  If
+the interface presents a list of user commands or options, such as a
+menu, a prominent item in the list meets this criterion.
+
+  1. Source Code.
+
+  The "source code" for a work means the preferred form of the work
+for making modifications to it.  "Object code" means any non-source
+form of a work.
+
+  A "Standard Interface" means an interface that either is an official
+standard defined by a recognized standards body, or, in the case of
+interfaces specified for a particular programming language, one that
+is widely used among developers working in that language.
+
+  The "System Libraries" of an executable work include anything, other
+than the work as a whole, that (a) is included in the normal form of
+packaging a Major Component, but which is not part of that Major
+Component, and (b) serves only to enable use of the work with that
+Major Component, or to implement a Standard Interface for which an
+implementation is available to the public in source code form.  A
+"Major Component", in this context, means a major essential component
+(kernel, window system, and so on) of the specific operating system
+(if any) on which the executable work runs, or a compiler used to
+produce the work, or an object code interpreter used to run it.
+
+  The "Corresponding Source" for a work in object code form means all
+the source code needed to generate, install, and (for an executable
+work) run the object code and to modify the work, including scripts to
+control those activities.  However, it does not include the work's
+System Libraries, or general-purpose tools or generally available free
+programs which are used unmodified in performing those activities but
+which are not part of the work.  For example, Corresponding Source
+includes interface definition files associated with source files for
+the work, and the source code for shared libraries and dynamically
+linked subprograms that the work is specifically designed to require,
+such as by intimate data communication or control flow between those
+subprograms and other parts of the work.
+
+  The Corresponding Source need not include anything that users
+can regenerate automatically from other parts of the Corresponding
+Source.
+
+  The Corresponding Source for a work in source code form is that
+same work.
+
+  2. Basic Permissions.
+
+  All rights granted under this License are granted for the term of
+copyright on the Program, and are irrevocable provided the stated
+conditions are met.  This License explicitly affirms your unlimited
+permission to run the unmodified Program.  The output from running a
+covered work is covered by this License only if the output, given its
+content, constitutes a covered work.  This License acknowledges your
+rights of fair use or other equivalent, as provided by copyright law.
+
+  You may make, run and propagate covered works that you do not
+convey, without conditions so long as your license otherwise remains
+in force.  You may convey covered works to others for the sole purpose
+of having them make modifications exclusively for you, or provide you
+with facilities for running those works, provided that you comply with
+the terms of this License in conveying all material for which you do
+not control copyright.  Those thus making or running the covered works
+for you must do so exclusively on your behalf, under your direction
+and control, on terms that prohibit them from making any copies of
+your copyrighted material outside their relationship with you.
+
+  Conveying under any other circumstances is permitted solely under
+the conditions stated below.  Sublicensing is not allowed; section 10
+makes it unnecessary.
+
+  3. Protecting Users' Legal Rights From Anti-Circumvention Law.
+
+  No covered work shall be deemed part of an effective technological
+measure under any applicable law fulfilling obligations under article
+11 of the WIPO copyright treaty adopted on 20 December 1996, or
+similar laws prohibiting or restricting circumvention of such
+measures.
+
+  When you convey a covered work, you waive any legal power to forbid
+circumvention of technological measures to the extent such circumvention
+is effected by exercising rights under this License with respect to
+the covered work, and you disclaim any intention to limit operation or
+modification of the work as a means of enforcing, against the work's
+users, your or third parties' legal rights to forbid circumvention of
+technological measures.
+
+  4. Conveying Verbatim Copies.
+
+  You may convey verbatim copies of the Program's source code as you
+receive it, in any medium, provided that you conspicuously and
+appropriately publish on each copy an appropriate copyright notice;
+keep intact all notices stating that this License and any
+non-permissive terms added in accord with section 7 apply to the code;
+keep intact all notices of the absence of any warranty; and give all
+recipients a copy of this License along with the Program.
+
+  You may charge any price or no price for each copy that you convey,
+and you may offer support or warranty protection for a fee.
+
+  5. Conveying Modified Source Versions.
+
+  You may convey a work based on the Program, or the modifications to
+produce it from the Program, in the form of source code under the
+terms of section 4, provided that you also meet all of these conditions:
+
+    a) The work must carry prominent notices stating that you modified
+    it, and giving a relevant date.
+
+    b) The work must carry prominent notices stating that it is
+    released under this License and any conditions added under section
+    7.  This requirement modifies the requirement in section 4 to
+    "keep intact all notices".
+
+    c) You must license the entire work, as a whole, under this
+    License to anyone who comes into possession of a copy.  This
+    License will therefore apply, along with any applicable section 7
+    additional terms, to the whole of the work, and all its parts,
+    regardless of how they are packaged.  This License gives no
+    permission to license the work in any other way, but it does not
+    invalidate such permission if you have separately received it.
+
+    d) If the work has interactive user interfaces, each must display
+    Appropriate Legal Notices; however, if the Program has interactive
+    interfaces that do not display Appropriate Legal Notices, your
+    work need not make them do so.
+
+  A compilation of a covered work with other separate and independent
+works, which are not by their nature extensions of the covered work,
+and which are not combined with it such as to form a larger program,
+in or on a volume of a storage or distribution medium, is called an
+"aggregate" if the compilation and its resulting copyright are not
+used to limit the access or legal rights of the compilation's users
+beyond what the individual works permit.  Inclusion of a covered work
+in an aggregate does not cause this License to apply to the other
+parts of the aggregate.
+
+  6. Conveying Non-Source Forms.
+
+  You may convey a covered work in object code form under the terms
+of sections 4 and 5, provided that you also convey the
+machine-readable Corresponding Source under the terms of this License,
+in one of these ways:
+
+    a) Convey the object code in, or embodied in, a physical product
+    (including a physical distribution medium), accompanied by the
+    Corresponding Source fixed on a durable physical medium
+    customarily used for software interchange.
+
+    b) Convey the object code in, or embodied in, a physical product
+    (including a physical distribution medium), accompanied by a
+    written offer, valid for at least three years and valid for as
+    long as you offer spare parts or customer support for that product
+    model, to give anyone who possesses the object code either (1) a
+    copy of the Corresponding Source for all the software in the
+    product that is covered by this License, on a durable physical
+    medium customarily used for software interchange, for a price no
+    more than your reasonable cost of physically performing this
+    conveying of source, or (2) access to copy the
+    Corresponding Source from a network server at no charge.
+
+    c) Convey individual copies of the object code with a copy of the
+    written offer to provide the Corresponding Source.  This
+    alternative is allowed only occasionally and noncommercially, and
+    only if you received the object code with such an offer, in accord
+    with subsection 6b.
+
+    d) Convey the object code by offering access from a designated
+    place (gratis or for a charge), and offer equivalent access to the
+    Corresponding Source in the same way through the same place at no
+    further charge.  You need not require recipients to copy the
+    Corresponding Source along with the object code.  If the place to
+    copy the object code is a network server, the Corresponding Source
+    may be on a different server (operated by you or a third party)
+    that supports equivalent copying facilities, provided you maintain
+    clear directions next to the object code saying where to find the
+    Corresponding Source.  Regardless of what server hosts the
+    Corresponding Source, you remain obligated to ensure that it is
+    available for as long as needed to satisfy these requirements.
+
+    e) Convey the object code using peer-to-peer transmission, provided
+    you inform other peers where the object code and Corresponding
+    Source of the work are being offered to the general public at no
+    charge under subsection 6d.
+
+  A separable portion of the object code, whose source code is excluded
+from the Corresponding Source as a System Library, need not be
+included in conveying the object code work.
+
+  A "User Product" is either (1) a "consumer product", which means any
+tangible personal property which is normally used for personal, family,
+or household purposes, or (2) anything designed or sold for incorporation
+into a dwelling.  In determining whether a product is a consumer product,
+doubtful cases shall be resolved in favor of coverage.  For a particular
+product received by a particular user, "normally used" refers to a
+typical or common use of that class of product, regardless of the status
+of the particular user or of the way in which the particular user
+actually uses, or expects or is expected to use, the product.  A product
+is a consumer product regardless of whether the product has substantial
+commercial, industrial or non-consumer uses, unless such uses represent
+the only significant mode of use of the product.
+
+  "Installation Information" for a User Product means any methods,
+procedures, authorization keys, or other information required to install
+and execute modified versions of a covered work in that User Product from
+a modified version of its Corresponding Source.  The information must
+suffice to ensure that the continued functioning of the modified object
+code is in no case prevented or interfered with solely because
+modification has been made.
+
+  If you convey an object code work under this section in, or with, or
+specifically for use in, a User Product, and the conveying occurs as
+part of a transaction in which the right of possession and use of the
+User Product is transferred to the recipient in perpetuity or for a
+fixed term (regardless of how the transaction is characterized), the
+Corresponding Source conveyed under this section must be accompanied
+by the Installation Information.  But this requirement does not apply
+if neither you nor any third party retains the ability to install
+modified object code on the User Product (for example, the work has
+been installed in ROM).
+
+  The requirement to provide Installation Information does not include a
+requirement to continue to provide support service, warranty, or updates
+for a work that has been modified or installed by the recipient, or for
+the User Product in which it has been modified or installed.  Access to a
+network may be denied when the modification itself materially and
+adversely affects the operation of the network or violates the rules and
+protocols for communication across the network.
+
+  Corresponding Source conveyed, and Installation Information provided,
+in accord with this section must be in a format that is publicly
+documented (and with an implementation available to the public in
+source code form), and must require no special password or key for
+unpacking, reading or copying.
+
+  7. Additional Terms.
+
+  "Additional permissions" are terms that supplement the terms of this
+License by making exceptions from one or more of its conditions.
+Additional permissions that are applicable to the entire Program shall
+be treated as though they were included in this License, to the extent
+that they are valid under applicable law.  If additional permissions
+apply only to part of the Program, that part may be used separately
+under those permissions, but the entire Program remains governed by
+this License without regard to the additional permissions.
+
+  When you convey a copy of a covered work, you may at your option
+remove any additional permissions from that copy, or from any part of
+it.  (Additional permissions may be written to require their own
+removal in certain cases when you modify the work.)  You may place
+additional permissions on material, added by you to a covered work,
+for which you have or can give appropriate copyright permission.
+
+  Notwithstanding any other provision of this License, for material you
+add to a covered work, you may (if authorized by the copyright holders of
+that material) supplement the terms of this License with terms:
+
+    a) Disclaiming warranty or limiting liability differently from the
+    terms of sections 15 and 16 of this License; or
+
+    b) Requiring preservation of specified reasonable legal notices or
+    author attributions in that material or in the Appropriate Legal
+    Notices displayed by works containing it; or
+
+    c) Prohibiting misrepresentation of the origin of that material, or
+    requiring that modified versions of such material be marked in
+    reasonable ways as different from the original version; or
+
+    d) Limiting the use for publicity purposes of names of licensors or
+    authors of the material; or
+
+    e) Declining to grant rights under trademark law for use of some
+    trade names, trademarks, or service marks; or
+
+    f) Requiring indemnification of licensors and authors of that
+    material by anyone who conveys the material (or modified versions of
+    it) with contractual assumptions of liability to the recipient, for
+    any liability that these contractual assumptions directly impose on
+    those licensors and authors.
+
+  All other non-permissive additional terms are considered "further
+restrictions" within the meaning of section 10.  If the Program as you
+received it, or any part of it, contains a notice stating that it is
+governed by this License along with a term that is a further
+restriction, you may remove that term.  If a license document contains
+a further restriction but permits relicensing or conveying under this
+License, you may add to a covered work material governed by the terms
+of that license document, provided that the further restriction does
+not survive such relicensing or conveying.
+
+  If you add terms to a covered work in accord with this section, you
+must place, in the relevant source files, a statement of the
+additional terms that apply to those files, or a notice indicating
+where to find the applicable terms.
+
+  Additional terms, permissive or non-permissive, may be stated in the
+form of a separately written license, or stated as exceptions;
+the above requirements apply either way.
+
+  8. Termination.
+
+  You may not propagate or modify a covered work except as expressly
+provided under this License.  Any attempt otherwise to propagate or
+modify it is void, and will automatically terminate your rights under
+this License (including any patent licenses granted under the third
+paragraph of section 11).
+
+  However, if you cease all violation of this License, then your
+license from a particular copyright holder is reinstated (a)
+provisionally, unless and until the copyright holder explicitly and
+finally terminates your license, and (b) permanently, if the copyright
+holder fails to notify you of the violation by some reasonable means
+prior to 60 days after the cessation.
+
+  Moreover, your license from a particular copyright holder is
+reinstated permanently if the copyright holder notifies you of the
+violation by some reasonable means, this is the first time you have
+received notice of violation of this License (for any work) from that
+copyright holder, and you cure the violation prior to 30 days after
+your receipt of the notice.
+
+  Termination of your rights under this section does not terminate the
+licenses of parties who have received copies or rights from you under
+this License.  If your rights have been terminated and not permanently
+reinstated, you do not qualify to receive new licenses for the same
+material under section 10.
+
+  9. Acceptance Not Required for Having Copies.
+
+  You are not required to accept this License in order to receive or
+run a copy of the Program.  Ancillary propagation of a covered work
+occurring solely as a consequence of using peer-to-peer transmission
+to receive a copy likewise does not require acceptance.  However,
+nothing other than this License grants you permission to propagate or
+modify any covered work.  These actions infringe copyright if you do
+not accept this License.  Therefore, by modifying or propagating a
+covered work, you indicate your acceptance of this License to do so.
+
+  10. Automatic Licensing of Downstream Recipients.
+
+  Each time you convey a covered work, the recipient automatically
+receives a license from the original licensors, to run, modify and
+propagate that work, subject to this License.  You are not responsible
+for enforcing compliance by third parties with this License.
+
+  An "entity transaction" is a transaction transferring control of an
+organization, or substantially all assets of one, or subdividing an
+organization, or merging organizations.  If propagation of a covered
+work results from an entity transaction, each party to that
+transaction who receives a copy of the work also receives whatever
+licenses to the work the party's predecessor in interest had or could
+give under the previous paragraph, plus a right to possession of the
+Corresponding Source of the work from the predecessor in interest, if
+the predecessor has it or can get it with reasonable efforts.
+
+  You may not impose any further restrictions on the exercise of the
+rights granted or affirmed under this License.  For example, you may
+not impose a license fee, royalty, or other charge for exercise of
+rights granted under this License, and you may not initiate litigation
+(including a cross-claim or counterclaim in a lawsuit) alleging that
+any patent claim is infringed by making, using, selling, offering for
+sale, or importing the Program or any portion of it.
+
+  11. Patents.
+
+  A "contributor" is a copyright holder who authorizes use under this
+License of the Program or a work on which the Program is based.  The
+work thus licensed is called the contributor's "contributor version".
+
+  A contributor's "essential patent claims" are all patent claims
+owned or controlled by the contributor, whether already acquired or
+hereafter acquired, that would be infringed by some manner, permitted
+by this License, of making, using, or selling its contributor version,
+but do not include claims that would be infringed only as a
+consequence of further modification of the contributor version.  For
+purposes of this definition, "control" includes the right to grant
+patent sublicenses in a manner consistent with the requirements of
+this License.
+
+  Each contributor grants you a non-exclusive, worldwide, royalty-free
+patent license under the contributor's essential patent claims, to
+make, use, sell, offer for sale, import and otherwise run, modify and
+propagate the contents of its contributor version.
+
+  In the following three paragraphs, a "patent license" is any express
+agreement or commitment, however denominated, not to enforce a patent
+(such as an express permission to practice a patent or covenant not to
+sue for patent infringement).  To "grant" such a patent license to a
+party means to make such an agreement or commitment not to enforce a
+patent against the party.
+
+  If you convey a covered work, knowingly relying on a patent license,
+and the Corresponding Source of the work is not available for anyone
+to copy, free of charge and under the terms of this License, through a
+publicly available network server or other readily accessible means,
+then you must either (1) cause the Corresponding Source to be so
+available, or (2) arrange to deprive yourself of the benefit of the
+patent license for this particular work, or (3) arrange, in a manner
+consistent with the requirements of this License, to extend the patent
+license to downstream recipients.  "Knowingly relying" means you have
+actual knowledge that, but for the patent license, your conveying the
+covered work in a country, or your recipient's use of the covered work
+in a country, would infringe one or more identifiable patents in that
+country that you have reason to believe are valid.
+
+  If, pursuant to or in connection with a single transaction or
+arrangement, you convey, or propagate by procuring conveyance of, a
+covered work, and grant a patent license to some of the parties
+receiving the covered work authorizing them to use, propagate, modify
+or convey a specific copy of the covered work, then the patent license
+you grant is automatically extended to all recipients of the covered
+work and works based on it.
+
+  A patent license is "discriminatory" if it does not include within
+the scope of its coverage, prohibits the exercise of, or is
+conditioned on the non-exercise of one or more of the rights that are
+specifically granted under this License.  You may not convey a covered
+work if you are a party to an arrangement with a third party that is
+in the business of distributing software, under which you make payment
+to the third party based on the extent of your activity of conveying
+the work, and under which the third party grants, to any of the
+parties who would receive the covered work from you, a discriminatory
+patent license (a) in connection with copies of the covered work
+conveyed by you (or copies made from those copies), or (b) primarily
+for and in connection with specific products or compilations that
+contain the covered work, unless you entered into that arrangement,
+or that patent license was granted, prior to 28 March 2007.
+
+  Nothing in this License shall be construed as excluding or limiting
+any implied license or other defenses to infringement that may
+otherwise be available to you under applicable patent law.
+
+  12. No Surrender of Others' Freedom.
+
+  If conditions are imposed on you (whether by court order, agreement or
+otherwise) that contradict the conditions of this License, they do not
+excuse you from the conditions of this License.  If you cannot convey a
+covered work so as to satisfy simultaneously your obligations under this
+License and any other pertinent obligations, then as a consequence you may
+not convey it at all.  For example, if you agree to terms that obligate you
+to collect a royalty for further conveying from those to whom you convey
+the Program, the only way you could satisfy both those terms and this
+License would be to refrain entirely from conveying the Program.
+
+  13. Use with the GNU Affero General Public License.
+
+  Notwithstanding any other provision of this License, you have
+permission to link or combine any covered work with a work licensed
+under version 3 of the GNU Affero General Public License into a single
+combined work, and to convey the resulting work.  The terms of this
+License will continue to apply to the part which is the covered work,
+but the special requirements of the GNU Affero General Public License,
+section 13, concerning interaction through a network will apply to the
+combination as such.
+
+  14. Revised Versions of this License.
+
+  The Free Software Foundation may publish revised and/or new versions of
+the GNU General Public License from time to time.  Such new versions will
+be similar in spirit to the present version, but may differ in detail to
+address new problems or concerns.
+
+  Each version is given a distinguishing version number.  If the
+Program specifies that a certain numbered version of the GNU General
+Public License "or any later version" applies to it, you have the
+option of following the terms and conditions either of that numbered
+version or of any later version published by the Free Software
+Foundation.  If the Program does not specify a version number of the
+GNU General Public License, you may choose any version ever published
+by the Free Software Foundation.
+
+  If the Program specifies that a proxy can decide which future
+versions of the GNU General Public License can be used, that proxy's
+public statement of acceptance of a version permanently authorizes you
+to choose that version for the Program.
+
+  Later license versions may give you additional or different
+permissions.  However, no additional obligations are imposed on any
+author or copyright holder as a result of your choosing to follow a
+later version.
+
+  15. Disclaimer of Warranty.
+
+  THERE IS NO WARRANTY FOR THE PROGRAM, TO THE EXTENT PERMITTED BY
+APPLICABLE LAW.  EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT
+HOLDERS AND/OR OTHER PARTIES PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY
+OF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO,
+THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+PURPOSE.  THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE PROGRAM
+IS WITH YOU.  SHOULD THE PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF
+ALL NECESSARY SERVICING, REPAIR OR CORRECTION.
+
+  16. Limitation of Liability.
+
+  IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING
+WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MODIFIES AND/OR CONVEYS
+THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, INCLUDING ANY
+GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING OUT OF THE
+USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED TO LOSS OF
+DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD
+PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER PROGRAMS),
+EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF
+SUCH DAMAGES.
+
+  17. Interpretation of Sections 15 and 16.
+
+  If the disclaimer of warranty and limitation of liability provided
+above cannot be given local legal effect according to their terms,
+reviewing courts shall apply local law that most closely approximates
+an absolute waiver of all civil liability in connection with the
+Program, unless a warranty or assumption of liability accompanies a
+copy of the Program in return for a fee.
+
+                     END OF TERMS AND CONDITIONS
+
+            How to Apply These Terms to Your New Programs
+
+  If you develop a new program, and you want it to be of the greatest
+possible use to the public, the best way to achieve this is to make it
+free software which everyone can redistribute and change under these terms.
+
+  To do so, attach the following notices to the program.  It is safest
+to attach them to the start of each source file to most effectively
+state the exclusion of warranty; and each file should have at least
+the "copyright" line and a pointer to where the full notice is found.
+
+    <one line to give the program's name and a brief idea of what it does.>
+    Copyright (C) <year>  <name of author>
+
+    This program is free software: you can redistribute it and/or modify
+    it under the terms of the GNU General Public License as published by
+    the Free Software Foundation, either version 3 of the License, or
+    (at your option) any later version.
+
+    This program is distributed in the hope that it will be useful,
+    but WITHOUT ANY WARRANTY; without even the implied warranty of
+    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+    GNU General Public License for more details.
+
+    You should have received a copy of the GNU General Public License
+    along with this program.  If not, see <https://www.gnu.org/licenses/>.
+
+Also add information on how to contact you by electronic and paper mail.
+
+  If the program does terminal interaction, make it output a short
+notice like this when it starts in an interactive mode:
+
+    <program>  Copyright (C) <year>  <name of author>
+    This program comes with ABSOLUTELY NO WARRANTY; for details type `show w'.
+    This is free software, and you are welcome to redistribute it
+    under certain conditions; type `show c' for details.
+
+The hypothetical commands `show w' and `show c' should show the appropriate
+parts of the General Public License.  Of course, your program's commands
+might be different; for a GUI interface, you would use an "about box".
+
+  You should also get your employer (if you work as a programmer) or school,
+if any, to sign a "copyright disclaimer" for the program, if necessary.
+For more information on this, and how to apply and follow the GNU GPL, see
+<https://www.gnu.org/licenses/>.
+
+  The GNU General Public License does not permit incorporating your program
+into proprietary programs.  If your program is a subroutine library, you
+may consider it more useful to permit linking proprietary applications with
+the library.  If this is what you want to do, use the GNU Lesser General
+Public License instead of this License.  But first, please read
+<https://www.gnu.org/licenses/why-not-lgpl.html>.
\ No newline at end of file
diff --git a/README.md b/README.md
new file mode 100644
index 0000000..041b0ca
--- /dev/null
+++ b/README.md
@@ -0,0 +1,115 @@
+# 🛠️ Marreta
+
+[![pt-br](https://img.shields.io/badge/lang-pt--br-green.svg)](https://github.com/manualdousuario/marreta/blob/master/README.md)
+
+Marreta é uma ferramenta para analisar URLs e acessar conteúdo na web sem dor de cabeça.
+
+## ✨ O que tem de legal?
+
+- Limpa e arruma URLs automaticamente
+- Remove parâmetros chatos de rastreamento
+- Força HTTPS pra manter tudo seguro
+- Troca de user agent pra evitar bloqueios
+- DNS esperto
+- Deixa o HTML limpinho e otimizado
+- Conserta URLs relativas sozinho
+- Permite colocar seus próprios estilos
+- Remove elementos indesejados
+- Cache, cache!
+- Bloqueia domínios que você não quer
+- Permite configurar headers e cookies do seu jeito
+- Tudo com SSL/TLS
+- PHP-FPM
+- OPcache ligado
+
+## 🐳 Docker
+
+### Antes de começar
+
+Só precisa ter instalado:
+- Docker e docker compose
+
+### Produção
+
+`curl -o ./docker-compose.yml https://raw.githubusercontent.com/manualdousuario/marreta/main/docker-compose.yml`
+
+Se necessario
+
+`nano docker-compose.yml`
+
+```
+services:
+  marreta:
+    container_name: marreta
+    image: ghcr.io/manualdousuario/marreta/marreta:latest
+    ports:
+      - "80:80"
+    environment:
+      - SITE_NAME=
+      - SITE_DESCRIPTION=
+      - SITE_URL=
+      - DNS_SERVERS=
+```
+
+- `SITE_NAME`: Nome do seu Marreta
+- `SITE_DESCRIPTION`: Conta pra que serve
+- `SITE_URL`: Onde vai rodar, endereço completo com `https://`
+- `DNS_SERVERS`: Quais servidores DNS usar `94.140.14.14, 94.140.15.15`
+
+Agora pode rodar `docker compose up -d`
+
+#### Desenvolvimento
+
+1. Primeiro, clona o projeto:
+```bash
+git clone https://github.com/manualdousuario/marreta/
+cd marreta
+```
+
+2. Cria o arquivo de configuração:
+```bash
+cp app/.env.sample app/.env
+```
+
+3. Configura do seu jeito no `app/.env`:
+```env
+SITE_NAME="Marreta"
+SITE_DESCRIPTION="Sua arma secreta contra sites sovinas!"
+SITE_URL=http://localhost
+DNS_SERVERS=94.140.14.14, 94.140.15.15
+```
+
+4. Roda tudo:
+```bash
+docker-compose up -d
+```
+
+Pronto! Vai estar rodando em `http://localhost` 🎉
+
+## ⚙️ Personalizando
+
+No `Rules.php` você pode configurar regras diferentes pra cada site e regras globais
+
+Em `config.php` você tem a lista os sites que não quer permitir ou não permitem extrair dados e configurações de User Agents
+
+## 🛠️ Manutenção
+
+### Logs
+
+Ver o que tá acontecendo:
+```bash
+docker-compose logs app
+```
+
+### Limpando o cache
+
+Quando precisar limpar:
+```bash
+docker-compose exec app rm -rf /app/cache/*
+```
+
+---
+
+Feito com ❤️! Se tiver dúvidas ou sugestões, abre uma issue que a gente ajuda! 😉
+
+Instancia publica em [marreta.pcdomanual.com](https://marreta.pcdomanual.com)!
diff --git a/TEST_URLS.md b/TEST_URLS.md
new file mode 100644
index 0000000..0ed8e92
--- /dev/null
+++ b/TEST_URLS.md
@@ -0,0 +1,49 @@
+# Bloqueados
+wsj.com
+bloomberg.com
+piaui.folha.uol.com.br
+jota.info
+haaretz.com
+haaretz.co.il
+washingtonpost.com
+gauchazh.clicrbs.com.br
+economist.com
+
+# Testados:
+## Brasil
+https://www1.folha.uol.com.br/poder/2024/11/justica-argentina-emite-mandados-de-prisao-contra-61-foragidos-do-81.shtml
+https://g1.globo.com/politica/noticia/2024/11/20/pf-devera-concluir-inquerito-contra-atos-do-8-de-janeiro-nesta-semana.ghtml
+https://www.estadao.com.br/politica/planalto-entregou-para-pf-acesso-a-computadores-de-grupo-que-tentou-matar-lula-alckmin-e-moraes/
+https://www.correio24horas.com.br/minha-bahia/bahia-registra-primeira-morte-por-coqueluche-em-cinco-anos-1124
+https://correio.rac.com.br/campinasermc/iphan-analisa-pedido-de-tombamento-emergencial-da-fazenda-santa-elisa-1.1591950
+https://veja.abril.com.br/politica/pt-pede-arquivamento-de-projeto-de-anistia-apos-pf-revelar-plano-para-matar-lula/
+https://super.abril.com.br/cultura/consciencia-negra-conheca-os-adinkras-simbolos-africanos-que-estao-no-seu-cotidiano/
+https://quatrorodas.abril.com.br/noticias/latinncap-c3aircross-corolla-teste-seguranca/#google_vignette
+https://www.abcmais.com/policia/casa-e-destruida-por-incendio-em-novo-hamburgo-2/
+https://www.agazeta.com.br/es/cotidiano/negros-sao-a-maioria-entre-pobres-e-analfabetos-no-es-aponta-estudo-1124
+https://www.gazetadopovo.com.br/republica/moraes-nao-tem-condicoes-de-julgar-suposto-plano-golpista-diz-rogerio-marinho/
+https://revistagalileu.globo.com/colunistas/the-conversation/noticia/2024/11/por-que-nos-sentimos-melhor-quando-acordamos-sem-despertador-neurologista-explica.ghtml
+https://oglobo.globo.com/epoca/noticia/2024/09/07/moeda-rara-do-seculo-xviii-encontrada-dentro-de-lata-de-caramelo-sera-leiloada-na-inglaterra-e-pode-valer-ate-r-220-mil.ghtml
+https://valor.globo.com/financas/esg/noticia/2024/11/18/anbima-propoe-novas-regras-para-ofertas-de-titulos-de-renda-fixa-sustentaveis.ghtml
+https://www.seudinheiro.com/2024/patrocinado/conteudo-eqi/fii-arrecada-r-137-milhoes-com-venda-de-galpoes-e-e-recomendado-por-analistas-veja/
+https://www.nsctotal.com.br/noticias/veja-os-bairros-mais-populosos-de-florianopolis-segundo-o-ibge
+https://exame.com/invest/mercados/nvidia-bate-consenso-mais-uma-vez-e-reporta-lucro-de-us-193-bilhoes-no-terceiro-trimestre/
+https://www.dgabc.com.br/Noticia/4177263/casa-de-acolhimento-a-mulheres-vitimas-de-violencia-e-invadida-em-sto-andre
+https://diarinho.net/materia/657650/Gasolina-subiu-mais-de-R--0-50-desde-janeiro--veja-os-precos-da-pesquisa-
+https://www.em.com.br/gerais/2024/11/6993175-incendio-fecha-shopping-na-regiao-centro-sul-de-bh.html
+https://mais.opovo.com.br/colunistas/ariadne-araujo/2024/11/20/quem-quer-casar-com-um-heroi-de-guerra-ucraniano.html
+https://www.folhadelondrina.com.br/folha-2/cena-musical-em-londrina-idosos-no-palco-e-na-plateia-3267044e.html?d=1
+https://www.uol.com.br/esporte/futebol/ultimas-noticias/2024/11/20/bahia-x-palmeiras-campeonato-brasileiro-2024-rodada-34.htm
+https://www.opovo.com.br/noticias/politica/2024/11/21/atuacao-de-faccoes-nas-eleicoes-teve-compra-de-votos-e-ate-enviado-do-rj-para-intimidacoes-no-ceara.html
+
+## Validar
+
+https://www1-folha-uol-com-br.cdn.ampproject.org/v/s/www1.folha.uol.com.br/amp/colunas/tatibernardi/2024/10/e-se-amo-tanto-que-amo-errado.shtml
+
+## Internacional
+https://www.nytimes.com/2024/11/20/us/politics/matt-gaetz-venmo-payments-sex.html
+https://www.ft.com/content/cab2e834-e5fa-4dd0-b757-76c243ac2eeb
+https://foreignpolicy.com/2024/11/20/trump-energy-policy-electric-vehicles-power-grid-infrastructure-oil-gas-climate/
+https://www.wired.com/story/waymo-robotaxi-driverless-future/
+https://www.forbes.com/sites/cyrusfarivar/2024/11/20/under-trump-tariffs-made-in-vietnam-will-be-the-new-made-in-china/
+https://observador.pt/especiais/mais-formacao-investimento-na-frota-reforco-dos-tecnicos-de-emergencia-e-autonomia-o-que-precisa-o-inem-segundo-varios-especialistas/
\ No newline at end of file
diff --git a/app/.env.sample b/app/.env.sample
new file mode 100644
index 0000000..3069463
--- /dev/null
+++ b/app/.env.sample
@@ -0,0 +1,16 @@
+# Arquivo de exemplo para configuração de variáveis de ambiente
+# Copie este arquivo para .env e ajuste os valores conforme necessário
+
+# Nome do site exibido no cabeçalho e meta tags
+SITE_NAME=Marreta
+
+# Descrição do site usada em meta tags e SEO
+SITE_DESCRIPTION="Sua arma secreta contra sites sovinas!"
+
+# URL base do site (sem barra no final)
+# Use https://localhost para desenvolvimento local
+SITE_URL=https://localhost
+
+# Lista de servidores DNS para resolução de domínios
+# Recomendado: AdGuard DNS (94.140.14.14, 94.140.15.15)
+DNS_SERVERS='94.140.14.14,94.140.15.15'
diff --git a/app/api.php b/app/api.php
new file mode 100644
index 0000000..509ad6d
--- /dev/null
+++ b/app/api.php
@@ -0,0 +1,109 @@
+<?php
+/**
+ * API para análise de URLs
+ * 
+ * Este arquivo implementa um endpoint REST que recebe URLs via GET
+ * e retorna resultados processados em formato JSON.
+ * 
+ * Funcionalidades:
+ * - Validação de URLs
+ * - Análise de conteúdo
+ * - Tratamento de erros
+ * - Suporte a CORS
+ */
+
+require_once 'config.php';
+require_once 'inc/URLAnalyzer.php';
+
+// Define o tipo de conteúdo como JSON
+header('Content-Type: application/json');
+
+// Habilita CORS (Cross-Origin Resource Sharing)
+header('Access-Control-Allow-Origin: *');
+header('Access-Control-Allow-Methods: GET');
+
+// Obtém a URL da requisição a partir do path
+$path = $_SERVER['REQUEST_URI'];
+$prefix = '/api/';
+
+if (strpos($path, $prefix) === 0) {
+    $url = urldecode(substr($path, strlen($prefix)));
+    
+    /**
+     * Função para enviar resposta JSON padronizada
+     * 
+     * @param array $data Dados a serem enviados na resposta
+     * @param int $statusCode Código de status HTTP
+     */
+    function sendResponse($data, $statusCode = 200) {
+        http_response_code($statusCode);
+        $response = [
+            'status' => $statusCode
+        ];
+        
+        if (isset($data['error'])) {
+            $response['error'] = $data['error'];
+        } else if (isset($data['url'])) {
+            $response['url'] = $data['url'];
+        }
+        
+        echo json_encode($response);
+        exit;
+    }
+
+    // Validação básica da URL
+    if (!$url || !filter_var($url, FILTER_VALIDATE_URL)) {
+        sendResponse([
+            'error' => [
+                'code' => 'INVALID_URL',
+                'message' => MESSAGES['INVALID_URL']['message']
+            ]
+        ], 400);
+    }
+
+    try {
+        // Instancia o analisador de URLs
+        $analyzer = new URLAnalyzer();
+        
+        // Tenta analisar a URL fornecida
+        $analyzer->analyze($url);
+        
+        // Se a análise for bem-sucedida, retorna a URL processada
+        sendResponse([
+            'url' => SITE_URL . '/p/' . $url
+        ], 200);
+
+    } catch (Exception $e) {
+        // Tratamento de erros com mapeamento para códigos HTTP apropriados
+        $message = $e->getMessage();
+        $statusCode = 400;
+        $errorCode = 'GENERIC_ERROR';
+        
+        // Mapeia a mensagem de erro para o código e status apropriados
+        foreach (MESSAGES as $key => $value) {
+            if (strpos($message, $value['message']) !== false) {
+                $statusCode = ($value['type'] === 'error') ? 400 : 503;
+                $errorCode = $key;
+                break;
+            }
+        }
+        
+        // Adiciona header de erro para melhor tratamento no cliente
+        header('X-Error-Message: ' . $message);
+        
+        sendResponse([
+            'error' => [
+                'code' => $errorCode,
+                'message' => $message
+            ]
+        ], $statusCode);
+    }
+} else {
+    // Retorna erro 404 para endpoints não encontrados
+    sendResponse([
+        'error' => [
+            'code' => 'NOT_FOUND',
+            'message' => MESSAGES['NOT_FOUND']['message']
+        ]
+    ], 404);
+}
diff --git a/app/assets/js/scripts.js b/app/assets/js/scripts.js
new file mode 100644
index 0000000..a4b49ea
--- /dev/null
+++ b/app/assets/js/scripts.js
@@ -0,0 +1,67 @@
+/**
+ * Funções JavaScript para validação de formulário e manipulação de erros
+ */
+
+/**
+ * Valida o formulário antes do envio
+ * 
+ * Verifica:
+ * - Se a URL não está vazia
+ * - Se a URL começa com http:// ou https://
+ * - Se a URL tem um formato válido
+ * 
+ * @returns {boolean} True se a URL for válida, False caso contrário
+ */
+function validateForm() {
+    const urlInput = document.getElementById('url');
+    const url = urlInput.value.trim();
+    
+    // Verifica se a URL não está vazia
+    if (!url) {
+        showError('Por favor, insira uma URL');
+        return false;
+    }
+
+    // Verifica se a URL começa com http:// ou https://
+    if (!/^https?:\/\//i.test(url)) {
+        showError('A URL deve começar com http:// ou https://');
+        return false;
+    }
+
+    // Tenta criar um objeto URL para validar o formato
+    try {
+        new URL(url);
+    } catch (e) {
+        showError('Formato de URL inválido');
+        return false;
+    }
+
+    return true;
+}
+
+/**
+ * Exibe uma mensagem de erro abaixo do formulário
+ * 
+ * Remove qualquer mensagem de erro existente antes de exibir a nova.
+ * A mensagem é exibida com um ícone de erro e formatação em vermelho.
+ * 
+ * @param {string} message Mensagem de erro a ser exibida
+ */
+function showError(message) {
+    const form = document.getElementById('urlForm');
+    const existingError = form.querySelector('.error-message');
+    
+    // Remove mensagem de erro anterior se existir
+    if (existingError) {
+        existingError.remove();
+    }
+
+    // Cria e adiciona nova mensagem de erro
+    const errorDiv = document.createElement('div');
+    errorDiv.className = 'error-message mt-4 text-base text-red-600';
+    errorDiv.innerHTML = `
+        <img src="assets/svgs/error.svg" class="inline-block w-5 h-5 mr-2" alt="Error icon">
+        ${message}`;
+    
+    form.appendChild(errorDiv);
+}
diff --git a/app/assets/opengraph.png b/app/assets/opengraph.png
new file mode 100644
index 0000000000000000000000000000000000000000..3374a71460352140886db03ea69938ca5966532b
GIT binary patch
literal 7367
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
O7OmiRW)Dq}xc>ojyi8>P

literal 0
HcmV?d00001

diff --git a/app/assets/svg/archive.svg b/app/assets/svg/archive.svg
new file mode 100644
index 0000000..bf61a2a
--- /dev/null
+++ b/app/assets/svg/archive.svg
@@ -0,0 +1 @@
+<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 512 512"><path fill="#3B82F6" d="M75 75L41 41C25.9 25.9 0 36.6 0 57.9V168c0 13.3 10.7 24 24 24H134.1c21.4 0 32.1-25.9 17-41l-34-34c41.4-41.4 98.6-67 161.9-67c95.2 0 174.2 69.3 189.4 160.3c2.2 13 14.3 21.7 27.4 19.5s21.7-14.3 19.5-27.4C497.3 102.7 395.2 16 279 16C196.5 16 121.7 47.4 75 75zm367 367l34 34c15.1 15.1 41 4.4 41-16.9V349c0-13.3-10.7-24-24-24H377.9c-21.4 0-32.1 25.9-17 41l34 34c-41.4 41.4-98.6 67-161.9 67c-95.2 0-174.2-69.3-189.4-160.3c-2.2-13-14.3-21.7-27.4-19.5s-21.7 14.3-19.5 27.4C14.7 409.3 116.8 496 233 496c82.5 0 157.3-31.4 204-59z"/></svg>
diff --git a/app/assets/svg/bookmark.svg b/app/assets/svg/bookmark.svg
new file mode 100644
index 0000000..ae5aa25
--- /dev/null
+++ b/app/assets/svg/bookmark.svg
@@ -0,0 +1 @@
+<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 384 512"><path fill="#FBBF24" d="M0 48C0 21.5 21.5 0 48 0l0 48V441.4l130.1-92.9c8.3-6 19.6-6 27.9 0L336 441.4V48H48V0H336c26.5 0 48 21.5 48 48V488c0 9-5 17.2-13 21.3s-17.6 3.4-24.9-1.8L192 397.5 37.9 507.5c-7.3 5.2-16.9 5.9-24.9 1.8S0 497 0 488V48z"/></svg>
diff --git a/app/assets/svg/bypass.svg b/app/assets/svg/bypass.svg
new file mode 100644
index 0000000..278657f
--- /dev/null
+++ b/app/assets/svg/bypass.svg
@@ -0,0 +1 @@
+<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 576 512"><path fill="#10B981" d="M128 32H32c-17.7 0-32 14.3-32 32s14.3 32 32 32h64v64c0 17.7 14.3 32 32 32s32-14.3 32-32V96c0-35.3-28.7-64-64-64zM448 32h96c17.7 0 32 14.3 32 32s-14.3 32-32 32H480v64c0 17.7-14.3 32-32 32s-32-14.3-32-32V96c0-35.3 28.7-64 64-64zM448 480h96c17.7 0 32 14.3 32 32s-14.3 32-32 32H448c-35.3 0-64-28.7-64-64V416c0-17.7 14.3-32 32-32s32 14.3 32 32v64zM128 480H32c-17.7 0-32 14.3-32 32s14.3 32 32 32h96c35.3 0 64-28.7 64-64V416c0-17.7-14.3-32-32-32s-32 14.3-32 32v64zM320 256a64 64 0 1 0 -128 0 64 64 0 1 0 128 0zM209.1 359.2L187.7 376h200.5l-21.4-16.8c-1.4-1.1-2.7-2.3-3.9-3.6l-48-48c-7.9-7.9-20.7-7.9-28.6 0l-48 48c-1.2 1.2-2.5 2.4-3.9 3.6zM576 208c0-79.5-64.5-144-144-144s-144 64.5-144 144c0 27.7 7.8 53.5 21.3 75.5c-1.6-1.3-3.3-2.5-4.9-3.8L99.5 80.4c-10.1-8.1-24.9-6.4-32.9 3.7s-6.4 24.9 3.7 32.9L459.2 416.7c.2 .2 .4 .3 .6 .5c9.6 7.8 23.8 6.4 31.6-3.2c40.2-49.6 40.2-122.5 0-172.1c9.7-18.8 15.2-40.1 15.2-62.7L608.8 288.8c10.1 8.1 24.9 6.4 32.9-3.7s6.4-24.9-3.7-32.9L576 208z"/></svg>
diff --git a/app/assets/svg/code.svg b/app/assets/svg/code.svg
new file mode 100644
index 0000000..4e37657
--- /dev/null
+++ b/app/assets/svg/code.svg
@@ -0,0 +1 @@
+<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 640 512"><path fill="#6B7280" d="M392.8 1.2c-17-4.9-34.7 5-39.6 22l-128 448c-4.9 17 5 34.7 22 39.6s34.7-5 39.6-22l128-448c4.9-17-5-34.7-22-39.6zm80.6 120.1c-12.5 12.5-12.5 32.8 0 45.3L562.7 256l-89.4 89.4c-12.5 12.5-12.5 32.8 0 45.3s32.8 12.5 45.3 0l112-112c12.5-12.5 12.5-32.8 0-45.3l-112-112c-12.5-12.5-32.8-12.5-45.3 0zm-306.7 0c-12.5-12.5-32.8-12.5-45.3 0l-112 112c-12.5 12.5-12.5 32.8 0 45.3l112 112c12.5 12.5 32.8 12.5 45.3 0s12.5-32.8 0-45.3L77.3 256l89.4-89.4c12.5-12.5 12.5-32.8 0-45.3z"/></svg>
diff --git a/app/assets/svg/error.svg b/app/assets/svg/error.svg
new file mode 100644
index 0000000..3be2e0b
--- /dev/null
+++ b/app/assets/svg/error.svg
@@ -0,0 +1 @@
+<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 512 512"><path fill="#EF4444" d="M256 512A256 256 0 1 0 256 0a256 256 0 1 0 0 512zM216 336h80c13.3 0 24-10.7 24-24s-10.7-24-24-24h-80c-13.3 0-24 10.7-24 24s10.7 24 24 24zm-24-72h128c13.3 0 24-10.7 24-24s-10.7-24-24-24H192c-13.3 0-24 10.7-24 24s10.7 24 24 24z"/></svg>
diff --git a/app/assets/svg/link.svg b/app/assets/svg/link.svg
new file mode 100644
index 0000000..a3fc594
--- /dev/null
+++ b/app/assets/svg/link.svg
@@ -0,0 +1 @@
+<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 640 512"><path fill="#3B82F6" d="M579.8 267.7c56.5-56.5 56.5-148 0-204.5c-50-50-128.8-56.5-186.3-15.4l-1.6 1.1c-14.4 10.3-17.7 30.3-7.4 44.6s30.3 17.7 44.6 7.4l1.6-1.1c32.1-22.9 76-19.3 103.8 8.6c31.5 31.5 31.5 82.5 0 114L422.3 334.8c-31.5 31.5-82.5 31.5-114 0c-27.9-27.9-31.5-71.8-8.6-103.8l1.1-1.6c10.3-14.4 6.9-34.4-7.4-44.6s-34.4-6.9-44.6 7.4l-1.1 1.6C206.5 251.2 213 330 263 380c56.5 56.5 148 56.5 204.5 0L579.8 267.7zM60.2 244.3c-56.5 56.5-56.5 148 0 204.5c50 50 128.8 56.5 186.3 15.4l1.6-1.1c14.4-10.3 17.7-30.3 7.4-44.6s-30.3-17.7-44.6-7.4l-1.6 1.1c-32.1 22.9-76 19.3-103.8-8.6C74 372 74 321 105.5 289.5L217.7 177.2c31.5-31.5 82.5-31.5 114 0c27.9 27.9 31.5 71.8 8.6 103.9l-1.1 1.6c-10.3 14.4-6.9 34.4 7.4 44.6s34.4 6.9 44.6-7.4l1.1-1.6C433.5 260.8 427 182 377 132c-56.5-56.5-148-56.5-204.5 0L60.2 244.3z"/></svg>
diff --git a/app/assets/svg/marreta.svg b/app/assets/svg/marreta.svg
new file mode 100644
index 0000000..867e0ea
--- /dev/null
+++ b/app/assets/svg/marreta.svg
@@ -0,0 +1 @@
+<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 640 512"><path fill="#3B82F6" d="M283.9 378.6l18.3-60.1c18-4.1 34.2-16 43.1-33.8l64-128c10.5-21.1 8.4-45.2-3.7-63.6l52.7-76.6c3.7-5.4 10.4-8 16.7-6.5s11.2 6.7 12.2 13.1l16.2 104.1 105.1-7.4c6.5-.5 12.7 3.1 15.5 9s1.8 12.9-2.6 17.8L550.1 224l71.3 77.5c4.4 4.8 5.5 11.9 2.6 17.8s-9 9.5-15.5 9l-105.1-7.4L487.3 425c-1 6.5-5.9 11.7-12.2 13.1s-13-1.1-16.7-6.5l-59.7-86.7-91.4 52.2c-5.7 3.3-12.8 2.7-17.9-1.4s-7.2-10.9-5.3-17.2zm28.3-101.7c-9.3 10.9-25.2 14.4-38.6 7.7l-65.9-32.9s0 0 0 0L122 208.8s0 0 0 0L17.7 156.6C1.9 148.7-4.5 129.5 3.4 113.7l40-80C48.8 22.8 59.9 16 72 16l120 0c5 0 9.9 1.2 14.3 3.4l78.2 39.1 81.8 40.9c15.8 7.9 22.2 27.1 14.3 42.9l-64 128c-1.2 2.4-2.7 4.6-4.4 6.6zM107.6 237.4l85.9 42.9L90.9 485.5c-11.9 23.7-40.7 33.3-64.4 21.5S-6.8 466.2 5.1 442.5L107.6 237.4z"/></svg>
\ No newline at end of file
diff --git a/app/assets/svg/search.svg b/app/assets/svg/search.svg
new file mode 100644
index 0000000..e296446
--- /dev/null
+++ b/app/assets/svg/search.svg
@@ -0,0 +1 @@
+<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 512 512"><path fill="#FFFFFF" d="M416 208c0 45.9-14.9 88.3-40 122.7L502.6 457.4c12.5 12.5 12.5 32.8 0 45.3s-32.8 12.5-45.3 0L330.7 376c-34.4 25.2-76.8 40-122.7 40C93.1 416 0 322.9 0 208S93.1 0 208 0S416 93.1 416 208zM208 352a144 144 0 1 0 0-288 144 144 0 1 0 0 288z"/></svg>
diff --git a/app/assets/svg/warning.svg b/app/assets/svg/warning.svg
new file mode 100644
index 0000000..18801c7
--- /dev/null
+++ b/app/assets/svg/warning.svg
@@ -0,0 +1 @@
+<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 512 512"><path fill="#FBBF24" d="M256 32c14.2 0 27.3 7.5 34.5 19.8l216 368c7.3 12.4 7.3 27.7 .2 40.1S486.3 480 472 480H40c-14.3 0-27.6-7.7-34.7-20.1s-7-27.8 .2-40.1l216-368C228.7 39.5 241.8 32 256 32zm0 128c-13.3 0-24 10.7-24 24V296c0 13.3 10.7 24 24 24s24-10.7 24-24V184c0-13.3-10.7-24-24-24zm32 224a32 32 0 1 0 -64 0 32 32 0 1 0 64 0z"/></svg>
diff --git a/app/composer.json b/app/composer.json
new file mode 100644
index 0000000..c5b7e79
--- /dev/null
+++ b/app/composer.json
@@ -0,0 +1,5 @@
+{
+    "require": {
+        "vlucas/phpdotenv": "^5.6.1"
+    }
+}
diff --git a/app/config.php b/app/config.php
new file mode 100644
index 0000000..381ac93
--- /dev/null
+++ b/app/config.php
@@ -0,0 +1,196 @@
+<?php
+/**
+ * Arquivo de configuração principal
+ * 
+ * Este arquivo contém todas as configurações globais do sistema, incluindo:
+ * - Carregamento de variáveis de ambiente
+ * - Definições de constantes do sistema
+ * - Configurações de segurança
+ * - Mensagens do sistema
+ * - Configurações de bots e user agents
+ * - Lista de domínios bloqueados
+ */
+
+require_once __DIR__ . '/vendor/autoload.php';
+
+// Carrega as variáveis de ambiente do arquivo .env
+$dotenv = Dotenv\Dotenv::createImmutable(__DIR__);
+$dotenv->load();
+
+/**
+ * Configurações básicas do sistema
+ */
+define('SITE_NAME', isset($_ENV['SITE_NAME']) ? $_ENV['SITE_NAME'] : 'Marreta');
+define('SITE_DESCRIPTION', isset($_ENV['SITE_DESCRIPTION']) ? $_ENV['SITE_DESCRIPTION'] : 'Sua arma secreta contra sites sovinas!');
+define('SITE_URL', isset($_ENV['SITE_URL']) ? $_ENV['SITE_URL'] : 'https://' . $_SERVER['HTTP_HOST']);
+define('MAX_ATTEMPTS', 3);  // Número máximo de tentativas para acessar uma URL
+define('DNS_SERVERS', isset($_ENV['DNS_SERVERS']) ? $_ENV['DNS_SERVERS'] : '94.140.14.14, 94.140.15.15');
+define('CACHE_DIR', __DIR__ . '/cache');
+
+/**
+ * Mensagens do sistema
+ * 
+ * Array associativo contendo todas as mensagens de erro e avisos
+ * que podem ser exibidas ao usuário durante a execução do sistema
+ */
+define('MESSAGES', [
+    'BLOCKED_DOMAIN' => [
+        'message' => 'Este domínio está bloqueado para extração.',
+        'type' => 'error'
+    ],
+    'DNS_FAILURE' => [
+        'message' => 'Falha ao resolver DNS para o domínio. Verifique se a URL está correta.',
+        'type' => 'warning'
+    ],
+    'HTTP_ERROR' => [
+        'message' => 'O servidor retornou um erro ao tentar acessar a página. Tente novamente mais tarde.',
+        'type' => 'warning'
+    ],
+    'CONNECTION_ERROR' => [
+        'message' => 'Erro ao conectar com o servidor. Verifique sua conexão e tente novamente.',
+        'type' => 'warning'
+    ],
+    'CONTENT_ERROR' => [
+        'message' => 'Não foi possível obter o conteúdo. Tente usar os serviços de arquivo.',
+        'type' => 'warning'
+    ],
+    'INVALID_URL' => [
+        'message' => 'Formato de URL inválido',
+        'type' => 'error'
+    ],
+    'NOT_FOUND' => [
+        'message' => 'Página não encontrada',
+        'type' => 'error'
+    ],
+    'GENERIC_ERROR' => [
+        'message' => 'Ocorreu um erro ao processar sua solicitação.',
+        'type' => 'warning'
+    ]
+]);
+
+/**
+ * Configurações dos bots
+ * 
+ * Define os user agents e headers específicos para diferentes bots
+ * que podem ser utilizados para fazer requisições
+ */
+define('BOT_CONFIGS', [
+    'Googlebot' => [
+        'user_agent' => 'Mozilla/5.0 (compatible; Googlebot/2.1; +http://www.google.com/bot.html)',
+        'headers' => [
+            'From' => 'googlebot(at)googlebot.com',
+            'X-Robots-Tag' => 'noindex'
+        ]
+    ],
+    'Bingbot' => [
+        'user_agent' => 'Mozilla/5.0 (compatible; bingbot/2.0; +http://www.bing.com/bingbot.htm)',
+        'headers' => [
+            'From' => 'bingbot(at)microsoft.com',
+            'X-Robots-Tag' => 'noindex',
+            'X-MSEdge-Bot' => 'true'
+        ]
+    ],
+    'GPTBot' => [
+        'user_agent' => 'Mozilla/5.0 (compatible; GPTBot/1.0; +https://openai.com/gptbot)',
+        'headers' => [
+            'From' => 'gptbot(at)openai.com',
+            'X-Robots-Tag' => 'noindex',
+            'X-OpenAI-Bot' => 'true'
+        ]
+    ]
+]);
+
+/**
+ * Lista de User Agents
+ * 
+ * Extrai os user agents da configuração dos bots
+ * Mantido para compatibilidade com código legado
+ */
+define('USER_AGENTS', array_column(BOT_CONFIGS, 'user_agent'));
+
+/**
+ * Lista de domínios bloqueados
+ * 
+ * Define os domínios que não podem ser acessados pelo sistema
+ * por questões de política de uso ou restrições técnicas
+ */
+define('BLOCKED_DOMAINS', [
+    // Sites de notícias
+    'wsj.com',
+    'bloomberg.com',
+    'piaui.folha.uol.com.br',
+    'jota.info',
+    'haaretz.com',
+    'haaretz.co.il',
+    'washingtonpost.com',
+    'gauchazh.clicrbs.com.br',
+    'economist.com',
+    // Tracking
+    'metaffiliation.com',
+    'google-analytics.com',
+    'googletagmanager.com',
+    'doubleclick.net',
+    'analytics.google.com',
+    'mixpanel.com',
+    'segment.com',
+    'amplitude.com',
+    'hotjar.com',
+    'kissmetrics.com',
+    'crazyegg.com',
+    'optimizely.com',
+    'newrelic.com',
+    'pingdom.com',
+    'statcounter.com',
+    'chartbeat.com',
+    'mouseflow.com',
+    'fullstory.com',
+    'heap.io',
+    'clearbrain.com',
+    // Redes sociais
+    'facebook.com',
+    'instagram.com',
+    'twitter.com',
+    'x.com',
+    'linkedin.com',
+    'tiktok.com',
+    'pinterest.com',
+    'snapchat.com',
+    'reddit.com',
+    'bsky.app',
+    'threads.net',    
+    // Streaming
+    'netflix.com',
+    'hulu.com',
+    'disneyplus.com',
+    'primevideo.com',
+    'spotify.com',
+    'youtube.com',
+    'twitch.tv',
+    // E-commerce
+    'amazon.com',
+    'ebay.com',
+    'aliexpress.com',
+    'mercadolivre.com.br',
+    'shopify.com',
+    // Compartilhamento de arquivos
+    'mega.nz',
+    'mediafire.com',
+    'wetransfer.com',
+    'dropbox.com',
+    'torrent9.pe',
+    'thepiratebay.org',
+    // Sites adultos
+    'pornhub.com',
+    'xvideos.com',
+    'xnxx.com',
+    'onlyfans.com',
+    // Apostas e jogos
+    'bet365.com',
+    'betfair.com',
+    'pokerstars.com',
+    'casino.com',
+    // Outros sites populares
+    'github.com',
+    'stackoverflow.com',
+    'wikipedia.org'
+]);
diff --git a/app/inc/Cache.php b/app/inc/Cache.php
new file mode 100644
index 0000000..db4fb0d
--- /dev/null
+++ b/app/inc/Cache.php
@@ -0,0 +1,78 @@
+<?php
+/**
+ * Classe responsável pelo gerenciamento de cache do sistema
+ * 
+ * Esta classe implementa funcionalidades para armazenar e recuperar
+ * conteúdo em cache, utilizando o sistema de arquivos como storage.
+ * O cache é organizado por URLs convertidas em IDs únicos usando SHA-256.
+ */
+class Cache {
+    /**
+     * @var string Diretório onde os arquivos de cache serão armazenados
+     */
+    private $cacheDir;
+
+    /**
+     * Construtor da classe
+     * 
+     * Inicializa o diretório de cache e cria-o se não existir
+     */
+    public function __construct() {
+        $this->cacheDir = CACHE_DIR;
+        if (!file_exists($this->cacheDir)) {
+            mkdir($this->cacheDir, 0777, true);
+        }
+    }
+
+    /**
+     * Gera um ID único para uma URL
+     * 
+     * @param string $url URL para qual será gerado o ID
+     * @return string Hash SHA-256 da URL normalizada
+     */
+    public function generateId($url) {
+        // Remove protocolo e www
+        $url = preg_replace('#^https?://(www\.)?#', '', $url);
+        // Gera ID único usando SHA-256
+        return hash('sha256', $url);
+    }
+
+    /**
+     * Verifica se existe cache para uma determinada URL
+     * 
+     * @param string $url URL a ser verificada
+     * @return bool True se existir cache, False caso contrário
+     */
+    public function exists($url) {
+        $id = $this->generateId($url);
+        $cachePath = $this->cacheDir . '/' . $id . '.html';
+        return file_exists($cachePath);
+    }
+
+    /**
+     * Recupera o conteúdo em cache de uma URL
+     * 
+     * @param string $url URL do conteúdo a ser recuperado
+     * @return string|null Conteúdo em cache ou null se não existir
+     */
+    public function get($url) {
+        if (!$this->exists($url)) {
+            return null;
+        }
+        $id = $this->generateId($url);
+        $cachePath = $this->cacheDir . '/' . $id . '.html';
+        return file_get_contents($cachePath);
+    }
+
+    /**
+     * Armazena conteúdo em cache para uma URL
+     * 
+     * @param string $url URL associada ao conteúdo
+     * @param string $content Conteúdo a ser armazenado em cache
+     */
+    public function set($url, $content) {
+        $id = $this->generateId($url);
+        $cachePath = $this->cacheDir . '/' . $id . '.html';
+        file_put_contents($cachePath, $content);
+    }
+}
diff --git a/app/inc/Rules.php b/app/inc/Rules.php
new file mode 100644
index 0000000..75a2785
--- /dev/null
+++ b/app/inc/Rules.php
@@ -0,0 +1,352 @@
+<?php
+/**
+ * Classe responsável pelo gerenciamento de regras de manipulação de conteúdo
+ * 
+ * Esta classe implementa um sistema de regras para diferentes domínios web,
+ * permitindo a personalização do comportamento do sistema para cada site.
+ * Inclui funcionalidades para remoção de paywalls, elementos específicos,
+ * manipulação de cookies e execução de códigos customizados.
+ */
+class Rules {
+    /**
+     * Array associativo contendo regras específicas para cada domínio
+     * 
+     * Configurações possíveis para cada domínio:
+     * @var array
+     * 
+     * - idElementRemove: IDs de elementos HTML que devem ser removidos
+     * - classElementRemove: Classes de elementos HTML que devem ser removidos
+     * - scriptTagRemove: Scripts que devem ser removidos
+     * - cookies: Cookies que devem ser definidos ou removidos
+     * - classAttrRemove: Classes que devem ser removidas de elementos
+     * - clearStorage: Se deve limpar o storage do navegador
+     * - customCode: Código JavaScript personalizado para execução
+     * - excludeGlobalRules: Array de regras globais a serem excluídas
+     * - userAgent: User Agent personalizado
+     * - headers: Headers HTTP personalizados
+     * - fixRelativeUrls: Habilita correção de URLs relativas
+     */
+    private $domainRules = [
+        'nsctotal.com.br' => [
+            'userAgent' => '',
+            'headers' => ''
+        ],
+        'globo.com' => [
+            'idElementRemove' => ['cookie-banner-lgpd', 'paywall-cpt', 'mc-read-more-wrapper', 'paywall-cookie-content', 'paywall-cpt'],
+            'classElementRemove' => ['banner-lgpd', 'article-related-link__title', 'article-related-link__picture', 'paywall-denied', 'banner-subscription'],
+            'scriptTagRemove' => ['tiny.js', 'signup.js', 'paywall.js'],
+            'cookies' => [
+                'piano_d' => null,
+                'piano_if' => null,
+                'piano_user_id' => null
+            ],
+            'classAttrRemove' => ['wall', 'protected-content', 'cropped-block'],
+            'clearStorage' => true,
+        ],
+        'folha.uol.com.br' => [
+            'idElementRemove' => ['paywall-flutuante', 'paywall', 'paywall-signup'],
+            'classElementRemove' => ['banner-assinatura', 'paywall-container'],
+            'scriptTagRemove' => ['paywall.js', 'content-gate.js'],
+            'cookies' => [
+                'paywall_visit' => null,
+                'folha_id' => null,
+                'paywall_access' => 'true'
+            ],
+            'clearStorage' => true
+        ],
+        'estadao.com.br' => [
+            'idElementRemove' => ['paywall', 'paywall-container'],
+            'classElementRemove' => ['paywall-content', 'signin-wall', 'pay-wall'],
+            'scriptTagRemove' => ['paywall.js', 'pywll.js'],
+            'cookies' => [
+                'estadao_paywall' => null
+            ],
+            'clearStorage' => true
+        ],
+        'exame.com' => [
+            'fixRelativeUrls' => true,
+        ],
+        'diarinho.net' => [
+            'fixRelativeUrls' => true,
+        ],
+        'em.com.br' => [
+            'fixRelativeUrls' => true,
+        ],
+        'opovo.com.br' => [
+            'fixRelativeUrls' => true,
+            'classElementRemove' => ['screen-loading', 'overlay-advise'],
+        ],
+        'folhadelondrina.com.br' => [
+            'fixRelativeUrls' => true,
+        ],
+        'crusoe.com.br' => [
+            'cookies' => [
+                'crs_subscriber' => '1'
+            ]
+        ],
+        'economist.com' => [
+            'cookies' => [
+                'ec_limit' => 'allow'
+            ],
+            'scriptTagRemove' => ['wrapperMessagingWithoutDetection.js'],
+            'customCode' => '
+                var artBodyContainer = document.querySelector("article.article");
+                var artBody = artBodyContainer.innerHTML;
+                checkPaywall();
+                function checkPaywall() {
+                    let paywallBox = document.querySelector(".layout-article-regwall");
+                    if (paywallBox) {
+                        artBodyContainer.innerHTML = artBody;
+                    }
+                }
+            '
+        ],
+        'ft.com' => [
+            'cookies' => [
+                'next-flags' => null,
+                'next:ads' => null
+            ],
+            'clearStorage' => true,
+            'customHeaders' => [
+                'Referer' => 'https://www.google.com.br/'
+            ]
+        ],
+        'nytimes.com' => [
+            'cookies' => [
+                'nyt-gdpr' => '1',
+                'nyt-purr' => 'cfh'
+            ],
+            'clearStorage' => true
+        ],
+        'correio24horas.com.br' => [
+            'idElementRemove' => ['paywall'],
+            'classElementRemove' => ['paywall'],
+            'classAttrRemove' => ['hide', 'is-active'],
+            'cookies' => [
+                'premium_access' => '1'
+            ]
+        ],
+        'abril.com.br' => [
+            'cookies' => [
+                'paywall_access' => 'true'
+            ],
+            'classElementRemove' => ['piano-offer-overlay'],
+            'classAttrRemove' => ['disabledByPaywall'],
+            'idElementRemove' => ['piano_offer']
+        ],
+        'foreignpolicy.com' => [
+            'idElementRemove' => ['paywall_bg'],
+            'classAttrRemove' => ['overlay-no-scroll', 'overlay-no-scroll'],
+        ],
+        'wired.com' => [
+            'clearStorage' => true
+        ],
+        'dgabc.com.br' => [
+            'customCode' => '
+                var email = "colaborador@dgabc.com.br";
+                $(".NoticiaExclusivaNaoLogado").hide();
+                $(".NoticiaExclusivaLogadoSemPermissao").hide();
+                $(".linhaSuperBanner").show();
+                $(".footer").show();
+                $(".NoticiaExclusivaLogado").show();
+            ',
+            'fixRelativeUrls' => true,
+        ],
+        'forbes.com' => [
+            'classElementRemove' => ['zephr-backdrop', 'zephr-generic-modal'],
+            'excludeGlobalRules' => [
+                'classElementRemove' => [
+                    'paywall' => [
+                        'premium-article',
+                    ],
+                ],
+            ],
+        ],
+        'seudinheiro.com' => [
+            'idElementRemove' => ['premium-paywall'],
+        ],
+        'technologyreview.com' => [
+            'cookies' => [
+                'xbc' => null,
+                '_pcid' => null,
+                '_pcus' => null,
+                '__tbc' => null,
+                '__pvi' => null,
+                '_pctx' => null
+            ],
+            'clearStorage' => true
+        ]
+    ];
+
+    // Regras globais expandidas
+    private $globalRules = [
+        'classElementRemove' => [
+            'paywall' => [
+                'subscription',
+                'subscriber-content',
+                'premium-content',
+                'signin-wall',
+                'register-wall',
+                'paid-content',
+                'premium-article',
+                'subscription-box',
+                'piano-offer',
+                'piano-inline',
+                'piano-modal',
+                'paywall-container',
+                'paywall-overlay',
+                'paywall-wrapper',
+                'paywall-notification'
+            ],
+            'social' => [
+                'social-share',
+                'social-buttons',
+                'share-container'
+            ],
+            'newsletter' => [
+                'newsletter-popup',
+                'subscribe-form',
+                'signup-overlay'
+            ]
+        ],
+        'scriptTagRemove' => [
+            'tracking' => [
+                'ga.js',
+                'fbevents.js',
+                'pixel.js',
+                'chartbeat',
+                'analytics.js',
+            ],
+            'paywall' => [
+                'wall.js',
+                'paywall.js',
+                'subscriber.js',
+                'piano.js',
+                'tiny.js',
+                'pywll.js',
+                'content-gate.js',
+                'signwall.js',
+                'pw.js',
+                'pw-',
+                'piano-',
+                'tinypass.js',
+                'tinypass.min.js',
+                'tp.min.js',
+                'premium.js'
+            ],
+            'cookies' => [
+                'cookie',
+                'gdpr',
+                'lgpd'
+            ],
+            'misc' => [
+                'push',
+                'sw.js',
+                'stats.js'
+            ]
+        ]
+    ];
+
+    /**
+     * Obtém o domínio base removendo o prefixo www
+     * 
+     * @param string $domain Domínio completo
+     * @return string Domínio base sem www
+     */
+    private function getBaseDomain($domain) {
+        return preg_replace('/^www\./', '', $domain);
+    }
+
+    /**
+     * Divide um domínio em suas partes constituintes
+     * 
+     * @param string $domain Domínio a ser dividido
+     * @return array Array com todas as combinações possíveis do domínio
+     */
+    private function getDomainParts($domain) {
+        $domain = $this->getBaseDomain($domain);
+        $parts = explode('.', $domain);
+        
+        $combinations = [];
+        for ($i = 0; $i < count($parts) - 1; $i++) {
+            $combinations[] = implode('.', array_slice($parts, $i));
+        }
+        
+        usort($combinations, function($a, $b) {
+            return strlen($b) - strlen($a);
+        });
+        
+        return $combinations;
+    }
+
+    /**
+     * Obtém as regras específicas para um domínio
+     * 
+     * @param string $domain Domínio para buscar regras
+     * @return array|null Array com regras mescladas ou null se não encontrar
+     */
+    public function getDomainRules($domain) {
+        $domainParts = $this->getDomainParts($domain);
+        
+        foreach ($this->domainRules as $pattern => $rules) {
+            if ($this->getBaseDomain($domain) === $this->getBaseDomain($pattern)) {
+                return $this->mergeWithGlobalRules($rules);
+            }
+        }
+        
+        foreach ($domainParts as $part) {
+            foreach ($this->domainRules as $pattern => $rules) {
+                if ($part === $this->getBaseDomain($pattern)) {
+                    return $this->mergeWithGlobalRules($rules);
+                }
+            }
+        }
+        
+        return null;
+    }
+
+    /**
+     * Mescla regras específicas do domínio com regras globais
+     * 
+     * @param array $rules Regras específicas do domínio
+     * @return array Regras mescladas
+     */
+    private function mergeWithGlobalRules($rules) {
+        $globalRules = $this->getGlobalRules();
+
+        if (isset($rules['excludeGlobalRules']) && is_array($rules['excludeGlobalRules'])) {
+            foreach ($rules['excludeGlobalRules'] as $ruleType => $categories) {
+                if (isset($globalRules[$ruleType])) {
+                    foreach ($categories as $category => $itemsToExclude) {
+                        if (isset($globalRules[$ruleType][$category])) {
+                            $globalRules[$ruleType][$category] = array_diff(
+                                $globalRules[$ruleType][$category],
+                                $itemsToExclude
+                            );
+                        }
+                    }
+                }
+            }
+        }
+
+        foreach ($globalRules as $ruleType => $categories) {
+            if (!isset($rules[$ruleType])) {
+                $rules[$ruleType] = [];
+            }
+            foreach ($categories as $category => $items) {
+                $rules[$ruleType] = array_merge($rules[$ruleType], $items);
+            }
+        }
+
+        return $rules;
+    }
+
+    /**
+     * Retorna todas as regras globais
+     * 
+     * @return array Array com todas as regras globais
+     */
+    public function getGlobalRules() {
+        return $this->globalRules;
+    }
+}
diff --git a/app/inc/URLAnalyzer.php b/app/inc/URLAnalyzer.php
new file mode 100644
index 0000000..309ac3e
--- /dev/null
+++ b/app/inc/URLAnalyzer.php
@@ -0,0 +1,686 @@
+<?php
+/**
+ * Classe responsável pela análise e processamento de URLs
+ * 
+ * Esta classe implementa funcionalidades para:
+ * - Análise e limpeza de URLs
+ * - Cache de conteúdo
+ * - Resolução DNS
+ * - Requisições HTTP com múltiplas tentativas
+ * - Processamento de conteúdo baseado em regras específicas por domínio
+ */
+
+require_once 'Rules.php';
+require_once 'Cache.php';
+
+class URLAnalyzer {
+    /**
+     * @var array Lista de User Agents disponíveis para requisições
+     */
+    private $userAgents;
+
+    /**
+     * @var int Número máximo de tentativas para obter conteúdo
+     */
+    private $maxAttempts;
+
+    /**
+     * @var array Lista de servidores DNS para resolução
+     */
+    private $dnsServers;
+
+    /**
+     * @var Rules Instância da classe de regras
+     */
+    private $rules;
+
+    /**
+     * @var Cache Instância da classe de cache
+     */
+    private $cache;
+
+    /**
+     * Construtor da classe
+     * Inicializa as dependências necessárias
+     */
+    public function __construct() {
+        $this->userAgents = USER_AGENTS;
+        $this->maxAttempts = MAX_ATTEMPTS;
+        $this->dnsServers = explode(',', DNS_SERVERS);
+        $this->rules = new Rules();
+        $this->cache = new Cache();
+    }
+
+    /**
+     * Registra erros no arquivo de log
+     * 
+     * @param string $url URL que gerou o erro
+     * @param string $error Mensagem de erro
+     */
+    private function logError($url, $error) {
+        $timestamp = date('Y-m-d H:i:s');
+        $logEntry = "[{$timestamp}] URL: {$url} - Error: {$error}" . PHP_EOL;
+        file_put_contents(__DIR__ . '/../logs/error.log', $logEntry, FILE_APPEND);
+    }
+
+    /**
+     * Método principal para análise de URLs
+     * 
+     * @param string $url URL a ser analisada
+     * @return string Conteúdo processado da URL
+     * @throws Exception Em caso de erros durante o processamento
+     */
+    public function analyze($url) {
+        try {
+            $cleanUrl = $this->cleanUrl($url);
+            
+            if ($this->cache->exists($cleanUrl)) {
+                return $this->cache->get($cleanUrl);
+            }
+            
+            $parsedUrl = parse_url($cleanUrl);
+            $domain = $parsedUrl['host'];
+            
+            // Verificação de domínios bloqueados
+            foreach (BLOCKED_DOMAINS as $blockedDomain) {
+                if (strpos($domain, $blockedDomain) !== false) {
+                    $error = 'Este domínio está bloqueado para extração.';
+                    $this->logError($cleanUrl, $error);
+                    throw new Exception($error);
+                }
+            }
+            
+            $resolvedIp = $this->resolveDns($cleanUrl);
+            if (!$resolvedIp) {
+                $error = 'Falha ao resolver DNS para o domínio';
+                $this->logError($cleanUrl, $error);
+                throw new Exception($error);
+            }
+            
+            $content = $this->fetchWithMultipleAttempts($cleanUrl, $resolvedIp);
+            
+            if (empty($content)) {
+                $error = 'Não foi possível obter o conteúdo. Tente usar serviços de arquivo.';
+                $this->logError($cleanUrl, $error);
+                throw new Exception($error);
+            }
+            
+            $content = $this->processContent($content, $domain, $cleanUrl);
+            
+            $this->cache->set($cleanUrl, $content);
+            
+            return $content;
+        } catch (Exception $e) {
+            $this->logError($url, $e->getMessage());
+            throw $e;
+        }
+    }
+
+    /**
+     * Tenta obter o conteúdo da URL com múltiplas tentativas
+     * 
+     * @param string $url URL para buscar conteúdo
+     * @param string $resolvedIp IP resolvido do domínio
+     * @return string Conteúdo obtido
+     * @throws Exception Se todas as tentativas falharem
+     */
+    private function fetchWithMultipleAttempts($url, $resolvedIp) {
+        $attempts = 0;
+        $errors = [];
+
+        while ($attempts < $this->maxAttempts) {
+            try {               
+                $content = $this->fetchWithCurl($url, $resolvedIp, $attempts);
+                if (!empty($content)) {
+                    return $content;
+                }
+            } catch (Exception $e) {
+                $errors[] = $e->getMessage();
+            }
+
+            $attempts++;
+            usleep(500000); // 0.5 segundo de espera entre tentativas
+        }
+
+        throw new Exception("Falha ao obter conteúdo após {$this->maxAttempts} tentativas. Erros: " . implode(', ', $errors));
+    }
+
+    /**
+     * Realiza requisição HTTP usando cURL
+     * 
+     * @param string $url URL para requisição
+     * @param string $resolvedIp IP resolvido do domínio
+     * @param int $attempts Número da tentativa atual
+     * @return string Conteúdo obtido
+     * @throws Exception Em caso de erro na requisição
+     */
+    private function fetchWithCurl($url, $resolvedIp, $attempts) {
+        $parsedUrl = parse_url($url);
+        $host = $parsedUrl['host'];
+
+        $domainRules = $this->getDomainRules(parse_url($url, PHP_URL_HOST));
+
+        $userAgent = $this->userAgents[$attempts % count($this->userAgents)];
+        if (isset($domainRules['userAgent'])) {
+            $userAgent = $domainRules['userAgent'];
+        }
+
+        $curlOptions = [
+            CURLOPT_URL => $url,
+            CURLOPT_RETURNTRANSFER => true,
+            CURLOPT_FOLLOWLOCATION => true,
+            CURLOPT_MAXREDIRS => 5,
+            CURLOPT_TIMEOUT => 30,
+            CURLOPT_ENCODING => '',
+            CURLOPT_USERAGENT => $userAgent,
+            CURLOPT_SSL_VERIFYPEER => false,
+            CURLOPT_RESOLVE => ["{$host}:80:{$resolvedIp}", "{$host}:443:{$resolvedIp}"],
+            CURLOPT_DNS_SERVERS => implode(',', $this->dnsServers)
+        ];
+
+        if (!isset($domainRules['headers'])) {
+            $headers = [
+                'Host: ' . $host,
+                'Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,*/*;q=0.8',
+                'Accept-Language: pt-BR,pt;q=0.9,en-US;q=0.8,en;q=0.7',
+                'Cache-Control: no-cache',
+                'Pragma: no-cache'
+            ];
+
+            if ($domainRules !== null && isset($domainRules['customHeaders'])) {
+                foreach ($domainRules['customHeaders'] as $headerName => $headerValue) {
+                    $headers[] = $headerName . ': ' . $headerValue;
+                }
+            }
+
+            $curlOptions[CURLOPT_HTTPHEADER] = $headers;
+            $curlOptions[CURLOPT_COOKIESESSION] = true;
+            $curlOptions[CURLOPT_FRESH_CONNECT] = true;
+        }
+
+        if ($domainRules !== null && isset($domainRules['cookies'])) {
+            $cookies = [];
+            foreach ($domainRules['cookies'] as $name => $value) {
+                if ($value !== null) {
+                    $cookies[] = $name . '=' . $value;
+                }
+            }
+            if (!empty($cookies)) {
+                $curlOptions[CURLOPT_COOKIE] = implode('; ', $cookies);
+            }
+        }
+
+        $ch = curl_init();
+        curl_setopt_array($ch, $curlOptions);
+
+        $content = curl_exec($ch);
+        $httpCode = curl_getinfo($ch, CURLINFO_HTTP_CODE);
+        $error = curl_error($ch);
+        
+        curl_close($ch);
+
+        if ($error) {
+            throw new Exception("Erro CURL: " . $error);
+        }
+
+        if ($httpCode >= 400) {
+            throw new Exception("Erro HTTP: " . $httpCode);
+        }
+        
+        return $content;
+    }
+
+    /**
+     * Limpa e normaliza uma URL
+     * 
+     * @param string $url URL para limpar
+     * @return string URL limpa e normalizada
+     */
+    private function cleanUrl($url) {
+        $url = strtolower($url);
+        $url = trim($url);
+        
+        $parsedUrl = parse_url($url);
+        
+        if (!isset($parsedUrl['scheme'])) {
+            $url = 'https://' . $url;
+            $parsedUrl = parse_url($url);
+        }
+        
+        $cleanUrl = $parsedUrl['scheme'] . '://' . $parsedUrl['host'];
+        
+        if (isset($parsedUrl['path'])) {
+            $path = preg_replace('#/+#', '/', $parsedUrl['path']);
+            $cleanUrl .= $path;
+        }
+        
+        if (isset($parsedUrl['query'])) {
+            parse_str($parsedUrl['query'], $params);
+            $params = $this->filterUrlParams($params);
+            
+            if (!empty($params)) {
+                ksort($params);
+                $cleanUrl .= '?' . http_build_query($params);
+            }
+        }
+        
+        return rtrim($cleanUrl, '/');
+    }
+
+    /**
+     * Filtra parâmetros da URL removendo tracking e sessão
+     * 
+     * @param array $params Parâmetros da URL
+     * @return array Parâmetros filtrados
+     */
+    private function filterUrlParams($params) {
+        $filteredParams = [];
+        
+        foreach ($params as $key => $value) {
+            if (empty($value) && $value !== '0') {
+                continue;
+            }
+            
+            if ($this->isTrackingParam($key)) {
+                continue;
+            }
+            
+            if ($this->isSessionParam($key)) {
+                continue;
+            }
+            
+            if ($this->isCacheParam($key)) {
+                continue;
+            }
+            
+            if ($this->isContentParam($key)) {
+                $filteredParams[$key] = $value;
+            }
+        }
+        
+        return $filteredParams;
+    }
+
+    /**
+     * Verifica se um parâmetro é de tracking
+     * 
+     * @param string $param Nome do parâmetro
+     * @return bool True se for parâmetro de tracking
+     */
+    private function isTrackingParam($param) {
+        $trackingPatterns = [
+            '/^utm_/',      // Google Analytics
+            '/^fbclid$/',   // Facebook
+            '/^gclid$/',    // Google Ads
+            '/^msclkid$/',  // Microsoft
+            '/^mc_/',       // Mailchimp
+            '/^pk_/',       // Piwik/Matomo
+            '/^n_/',        // Navegg
+            '/^dclid$/',    // DoubleClick
+            '/^_hs/',       // HubSpot
+            '/^_ga/',       // Google Analytics
+            '/^_gl/',       // Google Analytics linker
+            '/^ref$/',      // Referrer
+            '/^source$/',   // Source tracking
+            '/^medium$/',   // Medium tracking
+            '/^campaign$/', // Campaign tracking
+            '/^affiliate$/', // Affiliate tracking
+            '/^partner$/',  // Partner tracking
+            '/^_openstat$/', // OpenStat
+            '/^yclid$/',    // Yandex
+            '/^_hsenc$/',   // HubSpot
+            '/^_hsmi$/',    // HubSpot
+            '/^mkt_tok$/',  // Marketo
+            '/^igshid$/',   // Instagram
+        ];
+        
+        foreach ($trackingPatterns as $pattern) {
+            if (preg_match($pattern, $param)) {
+                return true;
+            }
+        }
+        
+        return false;
+    }
+
+    /**
+     * Verifica se um parâmetro é de sessão
+     * 
+     * @param string $param Nome do parâmetro
+     * @return bool True se for parâmetro de sessão
+     */
+    private function isSessionParam($param) {
+        $sessionPatterns = [
+            '/sess(ion)?[_-]?id/i',
+            '/^sid$/',
+            '/^s$/',
+            '/_?sess$/',
+            '/^PHPSESSID$/',
+            '/^JSESSIONID$/',
+            '/^ASP\.NET_SessionId$/',
+            '/^CFID$/',
+            '/^CFTOKEN$/',
+            '/^skey$/',
+            '/^token$/',
+            '/^auth[_-]?token$/',
+            '/^access[_-]?token$/',
+        ];
+        
+        foreach ($sessionPatterns as $pattern) {
+            if (preg_match($pattern, $param)) {
+                return true;
+            }
+        }
+        
+        return false;
+    }
+
+    /**
+     * Verifica se um parâmetro é de cache
+     * 
+     * @param string $param Nome do parâmetro
+     * @return bool True se for parâmetro de cache
+     */
+    private function isCacheParam($param) {
+        $cachePatterns = [
+            '/^v$/',
+            '/^ver$/',
+            '/^version$/',
+            '/^rev$/',
+            '/^revision$/',
+            '/^cache$/',
+            '/^nocache$/',
+            '/^_t$/',
+            '/^timestamp$/',
+            '/^time$/',
+            '/^[0-9]+$/',
+            '/^_=[0-9]+$/',
+        ];
+        
+        foreach ($cachePatterns as $pattern) {
+            if (preg_match($pattern, $param)) {
+                return true;
+            }
+        }
+        
+        return false;
+    }
+
+    /**
+     * Verifica se um parâmetro é de conteúdo
+     * 
+     * @param string $param Nome do parâmetro
+     * @return bool True se for parâmetro de conteúdo
+     */
+    private function isContentParam($param) {
+        $contentPatterns = [
+            '/^id$/',
+            '/^page$/',
+            '/^category$/',
+            '/^cat$/',
+            '/^tag$/',
+            '/^type$/',
+            '/^format$/',
+            '/^view$/',
+            '/^layout$/',
+            '/^style$/',
+            '/^lang$/',
+            '/^locale$/',
+            '/^currency$/',
+            '/^filter$/',
+            '/^sort$/',
+            '/^order$/',
+            '/^q$/',
+            '/^search$/',
+            '/^query$/',
+            '/^year$/',
+            '/^month$/',
+            '/^day$/',
+            '/^date$/',
+            '/^author$/',
+            '/^topic$/',
+            '/^section$/',
+        ];
+        
+        foreach ($contentPatterns as $pattern) {
+            if (preg_match($pattern, $param)) {
+                return true;
+            }
+        }
+        
+        if (preg_match('/^[a-z0-9]{4,}$/i', $param)) {
+            return true;
+        }
+        
+        return false;
+    }
+
+    /**
+     * Resolve DNS para um domínio
+     * 
+     * @param string $url URL para resolver DNS
+     * @return string|false IP resolvido ou false em caso de falha
+     */
+    private function resolveDns($url) {
+        $parsedUrl = parse_url($url);
+        $domain = $parsedUrl['host'];
+        
+        foreach ($this->dnsServers as $dnsServer) {
+            $dnsQuery = [
+                'name' => $domain,
+                'type' => 'A',
+                'do' => true,
+                'cd' => false
+            ];
+
+            $ch = curl_init();
+            curl_setopt_array($ch, [
+                CURLOPT_URL => $dnsServer . '?' . http_build_query($dnsQuery),
+                CURLOPT_RETURNTRANSFER => true,
+                CURLOPT_HTTPHEADER => [
+                    'Accept: application/dns-json',
+                ],
+                CURLOPT_SSL_VERIFYPEER => true,
+                CURLOPT_SSL_VERIFYHOST => 2
+            ]);
+
+            $response = curl_exec($ch);
+            $error = curl_error($ch);
+            curl_close($ch);
+
+            if (!$error && $response) {
+                $dnsData = json_decode($response, true);
+                if (isset($dnsData['Answer'])) {
+                    foreach ($dnsData['Answer'] as $record) {
+                        if ($record['type'] === 1) {
+                            return $record['data'];
+                        }
+                    }
+                }
+            }
+        }
+
+        $ip = gethostbyname($domain);
+        return ($ip !== $domain) ? $ip : false;
+    }
+
+    /**
+     * Obtém regras específicas para um domínio
+     * 
+     * @param string $domain Domínio para buscar regras
+     * @return array|null Regras do domínio ou null se não encontrar
+     */
+    private function getDomainRules($domain) {
+        return $this->rules->getDomainRules($domain);
+    }
+
+    /**
+     * Remove classes específicas de um elemento
+     * 
+     * @param DOMElement $element Elemento DOM
+     * @param array $classesToRemove Classes a serem removidas
+     */
+    private function removeClassNames($element, $classesToRemove) {
+        if (!$element->hasAttribute('class')) {
+            return;
+        }
+        
+        $classes = explode(' ', $element->getAttribute('class'));
+        $newClasses = array_filter($classes, function($class) use ($classesToRemove) {
+            return !in_array(trim($class), $classesToRemove);
+        });
+        
+        if (empty($newClasses)) {
+            $element->removeAttribute('class');
+        } else {
+            $element->setAttribute('class', implode(' ', $newClasses));
+        }
+    }
+
+    /**
+     * Corrige URLs relativas em um documento DOM
+     * 
+     * @param DOMDocument $dom Documento DOM
+     * @param DOMXPath $xpath Objeto XPath
+     * @param string $baseUrl URL base para correção
+     */
+    private function fixRelativeUrls($dom, $xpath, $baseUrl) {
+        $parsedBase = parse_url($baseUrl);
+        $baseHost = $parsedBase['scheme'] . '://' . $parsedBase['host'];
+
+        $elements = $xpath->query("//*[@src]");
+        if ($elements !== false) {
+            foreach ($elements as $element) {
+                if ($element instanceof DOMElement) {
+                    $src = $element->getAttribute('src');
+                    if (strpos($src, 'http') !== 0 && strpos($src, '//') !== 0) {
+                        $src = ltrim($src, '/');
+                        $element->setAttribute('src', $baseHost . '/' . $src);
+                    }
+                }
+            }
+        }
+
+        $elements = $xpath->query("//*[@href]");
+        if ($elements !== false) {
+            foreach ($elements as $element) {
+                if ($element instanceof DOMElement) {
+                    $href = $element->getAttribute('href');
+                    if (strpos($href, 'http') !== 0 && strpos($href, '//') !== 0 && strpos($href, '#') !== 0 && strpos($href, 'mailto:') !== 0) {
+                        $href = ltrim($href, '/');
+                        $element->setAttribute('href', $baseHost . '/' . $href);
+                    }
+                }
+            }
+        }
+    }
+
+    /**
+     * Processa o conteúdo HTML aplicando regras do domínio
+     * 
+     * @param string $content Conteúdo HTML
+     * @param string $domain Domínio do conteúdo
+     * @param string $url URL completa
+     * @return string Conteúdo processado
+     */
+    private function processContent($content, $domain, $url) {
+        $dom = new DOMDocument();
+        $dom->preserveWhiteSpace = true;
+        libxml_use_internal_errors(true);
+        @$dom->loadHTML(mb_convert_encoding($content, 'HTML-ENTITIES', 'UTF-8'), LIBXML_HTML_NOIMPLIED | LIBXML_HTML_NODEFDTD);
+        libxml_clear_errors();
+        
+        $xpath = new DOMXPath($dom);
+        
+        $domainRules = $this->getDomainRules($domain);
+        if ($domainRules !== null) {
+            if (isset($domainRules['fixRelativeUrls']) && $domainRules['fixRelativeUrls'] === true) {
+                $this->fixRelativeUrls($dom, $xpath, $url);
+            }
+
+            if (isset($domainRules['customStyle'])) {
+                $styleElement = $dom->createElement('style');
+                $styleContent = '';
+                foreach ($domainRules['customStyle'] as $selector => $rules) {
+                    if (is_array($rules)) {
+                        $styleContent .= $selector . ' { ' . implode('; ', $rules) . ' } ';
+                    } else {
+                        $styleContent .= $selector . ' { ' . $rules . ' } ';
+                    }
+                }
+                $styleElement->appendChild($dom->createTextNode($styleContent));
+                $dom->getElementsByTagName('head')[0]->appendChild($styleElement);
+            }
+
+            if (isset($domainRules['customCode'])) {
+                $scriptElement = $dom->createElement('script');
+                $scriptElement->setAttribute('type', 'text/javascript');
+                $scriptElement->appendChild($dom->createTextNode($domainRules['customCode']));
+                $dom->getElementsByTagName('body')[0]->appendChild($scriptElement);
+            }
+
+            if (isset($domainRules['classAttrRemove'])) {
+                foreach ($domainRules['classAttrRemove'] as $class) {
+                    $elements = $xpath->query("//*[contains(@class, '$class')]");
+                    if ($elements !== false) {
+                        foreach ($elements as $element) {
+                            $this->removeClassNames($element, [$class]);
+                        }
+                    }
+                }
+            }
+
+            if (isset($domainRules['idElementRemove'])) {
+                foreach ($domainRules['idElementRemove'] as $id) {
+                    $elements = $xpath->query("//*[@id='$id']");
+                    if ($elements !== false) {
+                        foreach ($elements as $element) {
+                            if ($element->parentNode) {
+                                $element->parentNode->removeChild($element);
+                            }
+                        }
+                    }
+                }
+            }
+
+            if (isset($domainRules['classElementRemove'])) {
+                foreach ($domainRules['classElementRemove'] as $class) {
+                    $elements = $xpath->query("//*[contains(@class, '$class')]");
+                    if ($elements !== false) {
+                        foreach ($elements as $element) {
+                            if ($element->parentNode) {
+                                $element->parentNode->removeChild($element);
+                            }
+                        }
+                    }
+                }
+            }
+
+            if (isset($domainRules['scriptTagRemove'])) {
+                foreach ($domainRules['scriptTagRemove'] as $script) {
+                    $elements = $xpath->query("//script[contains(@src, '$script')] | //script[contains(text(), '$script')]");
+                    if ($elements !== false) {
+                        foreach ($elements as $element) {
+                            if ($element->parentNode) {
+                                $element->parentNode->removeChild($element);
+                            }
+                        }
+                    }
+                }
+            }
+        }
+
+        $elements = $xpath->query("//*[@style]");
+        if ($elements !== false) {
+            foreach ($elements as $element) {
+                if ($element instanceof DOMElement) {
+                    $style = $element->getAttribute('style');
+                    $style = preg_replace('/(max-height|height|overflow|position|display)\s*:\s*[^;]+;?/', '', $style);
+                    $element->setAttribute('style', $style);
+                }
+            }
+        }
+
+        return $dom->saveHTML();
+    }
+}
diff --git a/app/index.php b/app/index.php
new file mode 100644
index 0000000..ff9d8ca
--- /dev/null
+++ b/app/index.php
@@ -0,0 +1,205 @@
+<?php
+/**
+ * Página principal do sistema
+ * 
+ * Este arquivo implementa a interface web principal, incluindo:
+ * - Formulário para análise de URLs
+ * - Tratamento de mensagens de erro/sucesso
+ * - Documentação da API
+ * - Interface para bookmarklet
+ * - Links para serviços alternativos
+ */
+
+require_once 'config.php';
+
+// Inicialização de variáveis
+$message = '';
+$message_type = '';
+$url = '';
+
+// Processa mensagens de erro/alerta da query string
+if (isset($_GET['message']) && isset(MESSAGES[$_GET['message']])) {
+    $message_key = $_GET['message'];
+    $message = MESSAGES[$message_key]['message'];
+    $message_type = MESSAGES[$message_key]['type'];
+}
+
+// Processa submissão do formulário
+if ($_SERVER['REQUEST_METHOD'] === 'POST' && isset($_POST['url'])) {
+    $url = filter_var($_POST['url'], FILTER_SANITIZE_URL);
+    if (filter_var($url, FILTER_VALIDATE_URL)) {
+        header('Location: ' . SITE_URL . '/p/' . urlencode($url));
+        exit;
+    } else {
+        $message = MESSAGES['INVALID_URL']['message'];
+        $message_type = MESSAGES['INVALID_URL']['type'];
+    }
+}
+?>
+<!DOCTYPE html>
+<html lang="pt-BR">
+<head>
+    <meta charset="UTF-8">
+    <meta name="viewport" content="width=device-width, initial-scale=1.0">
+    <title><?php echo SITE_NAME; ?></title>
+    <link rel="icon" href="assets/svg/marreta.svg" type="image/svg+xml">
+    <meta property="og:type" content="website" />
+    <meta property="og:url" content="<?php echo SITE_URL; ?>" />
+    <meta property="og:title" content="<?php echo SITE_NAME; ?>" />
+    <meta property="og:description" content="<?php echo htmlspecialchars(SITE_DESCRIPTION); ?>" />
+    <meta property="og:image" content="/assets/opengraph.png" />
+    <script src="https://cdn.tailwindcss.com"></script>
+</head>
+<body class="bg-gray-50 min-h-screen">
+    <div class="container mx-auto px-4 py-8 max-w-4xl">
+        <!-- Cabeçalho da página -->
+        <div class="text-center mb-8">
+            <h1 class="text-4xl font-bold text-gray-800 mb-4">
+                <img src="assets/svg/marreta.svg" class="inline-block w-12 h-12 mb-2" alt="Marreta icon">
+                <?php echo SITE_NAME; ?>
+            </h1>
+            <p class="text-gray-600 text-lg"><?php echo SITE_DESCRIPTION; ?></p>
+        </div>
+
+        <!-- Formulário principal de análise de URLs -->
+        <div class="bg-white rounded-xl shadow-lg p-8 mb-8">
+            <form id="urlForm" method="POST" onsubmit="return validateForm()" class="space-y-6">
+                <div class="relative">
+                    <div class="flex items-stretch">
+                        <span class="inline-flex items-center px-5 rounded-l-lg border border-r-0 border-gray-300 bg-gray-50 text-gray-500">
+                            <img src="assets/svg/link.svg" class="w-6 h-6" alt="Link icon">
+                        </span>
+                        <input type="url" 
+                               name="url" 
+                               id="url" 
+                               class="flex-1 block w-full rounded-none rounded-r-lg text-lg py-4 border border-l-0 border-gray-300 bg-gray-50 focus:border-blue-500 focus:ring-blue-500 shadow-sm bg-gray-50"
+                               placeholder="Digite a URL (ex: https://exemplo.com)" 
+                               value="<?php echo htmlspecialchars($url); ?>"
+                               required 
+                               pattern="https?://.+"
+                               title="Por favor, insira uma URL válida começando com http:// ou https://">
+                    </div>
+                    <button type="submit" 
+                            class="mt-4 w-full inline-flex justify-center items-center px-6 py-4 border border-transparent text-lg font-medium rounded-lg text-white bg-blue-600 hover:bg-blue-700 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-blue-500 transition-colors duration-200">
+                        <img src="assets/svg/search.svg" class="w-6 h-6 mr-3" alt="Search icon">
+                        Analisar
+                    </button>
+                </div>
+            </form>
+
+            <!-- Área de mensagens de erro/alerta -->
+            <?php if ($message): ?>
+                <div class="mt-6 <?php echo $message_type === 'error' ? 'bg-red-50 border-red-400' : 'bg-yellow-50 border-yellow-400'; ?> border-l-4 p-4 rounded-r">
+                    <div class="flex">
+                        <div class="flex-shrink-0">
+                            <?php if ($message_type === 'error'): ?>
+                                <img src="assets/svg/error.svg" class="w-6 h-6" alt="Error icon">
+                            <?php else: ?>
+                                <img src="assets/svg/warning.svg" class="w-6 h-6" alt="Warning icon">
+                            <?php endif; ?>
+                        </div>
+                        <div class="ml-3">
+                            <p class="text-base <?php echo $message_type === 'error' ? 'text-red-700' : 'text-yellow-700'; ?>">
+                                <?php echo htmlspecialchars($message); ?>
+                            </p>
+                        </div>
+                    </div>
+                </div>
+            <?php endif; ?>
+        </div>
+        
+        <!-- Exemplo de uso direto -->
+        <div class="mt-8 text-center text-base text-gray-500">
+            <p>
+                <img src="assets/svg/code.svg" class="inline-block w-5 h-5 mr-2" alt="Code icon">
+                Acesso direto: 
+                <pre class="bg-gray-100 p-3 rounded-lg text-sm overflow-x-auto"><?php echo SITE_URL; ?>/p/https://exemplo.com</pre>
+            </p>
+        </div>
+
+        <!-- Documentação da API -->
+        <div class="bg-white rounded-xl shadow-lg p-8 mt-8">
+            <h2 class="text-xl font-semibold text-gray-800 mb-6 flex items-center">
+                <img src="assets/svg/code.svg" class="w-6 h-6 mr-3" alt="Code icon">
+                API REST
+            </h2>
+            <div class="space-y-4">
+                <p class="text-gray-600">
+                    O <?php echo SITE_NAME; ?> disponibiliza uma API REST para integração com outros sistemas:
+                </p>
+                <div class="bg-gray-50 rounded-lg p-4">
+                    <h3 class="font-medium text-gray-800 mb-2">Endpoint:</h3>
+                    <pre class="bg-gray-100 p-3 rounded-lg text-sm overflow-x-auto">GET <?php echo SITE_URL; ?>/api/https://exemplo.com</pre>
+                </div>
+                <div class="bg-gray-50 rounded-lg p-4">
+                    <h3 class="font-medium text-gray-800 mb-2">Resposta de sucesso:</h3>
+                    <pre class="bg-gray-100 p-3 rounded-lg text-sm overflow-x-auto">
+{
+    "status": 200,
+    "url": "<?php echo SITE_URL; ?>/p/https://exemplo.com"
+}</pre>
+                </div>
+                <div class="bg-gray-50 rounded-lg p-4">
+                    <h3 class="font-medium text-gray-800 mb-2">Resposta de erro:</h3>
+                    <pre class="bg-gray-100 p-3 rounded-lg text-sm overflow-x-auto">
+{
+    "status": 400,
+    "error": {
+        "code": "INVALID_URL",
+        "message": "URL inválida"
+    }
+}</pre>
+                </div>
+            </div>
+        </div>
+
+        <!-- Seção de Bookmarklet -->
+        <div class="bg-white rounded-xl shadow-lg p-8 mt-8 mb-8">
+            <h2 class="text-xl font-semibold text-gray-800 mb-6 flex items-center">
+                <img src="assets/svg/bookmark.svg" class="w-6 h-6 mr-3" alt="Bookmark icon">
+                Adicione aos Favoritos
+            </h2>
+            <div class="space-y-4">
+                <p class="text-gray-600">
+                    Arraste o botão abaixo para sua barra de favoritos para acessar o <?php echo SITE_NAME; ?> rapidamente em qualquer página:
+                </p>
+                <div class="flex justify-center">
+                    <a href="javascript:(function(){let currentUrl=window.location.href;window.location.href='<?php echo SITE_URL; ?>/p/'+encodeURIComponent(currentUrl);})()" 
+                       class="inline-flex items-center px-6 py-3 border-2 border-blue-500 font-medium rounded-lg text-blue-600 bg-white hover:bg-blue-50 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-blue-500 transition-colors duration-200 cursor-move"
+                       onclick="return false;">
+                        <img src="assets/svg/marreta.svg" class="w-5 h-5 mr-2" alt="Marreta icon">
+                        Abrir no <?php echo SITE_NAME; ?>
+                    </a>
+                </div>
+            </div>
+        </div>
+
+        <!-- Serviços alternativos -->
+        <div class="bg-white rounded-xl shadow-lg p-8 mt-8">
+            <h2 class="text-xl font-semibold text-gray-800 mb-6">
+                <img src="assets/svg/archive.svg" class="inline-block w-6 h-6 mr-3" alt="Archive icon">
+                Serviços alternativos
+            </h2>
+            <div class="grid grid-cols-1 md:grid-cols-2 gap-6">
+                <a href="https://archive.today" target="_blank" 
+                   class="flex items-center p-4 rounded-lg border-2 border-gray-200 hover:border-blue-500 hover:bg-blue-50 transition-all duration-200">
+                    <img src="assets/svg/archive.svg" class="w-8 h-8 mr-4" alt="Archive.today icon">
+                    <div>
+                        <div class="font-medium text-lg text-gray-800">archive.today</div>
+                    </div>
+                </a>
+                <a href="https://12ft.io" target="_blank"
+                   class="flex items-center p-4 rounded-lg border-2 border-gray-200 hover:border-green-500 hover:bg-green-50 transition-all duration-200">
+                    <img src="assets/svg/bypass.svg" class="w-8 h-8 mr-4" alt="12ft.io icon">
+                    <div>
+                        <div class="font-medium text-lg text-gray-800">12ft.io</div>
+                    </div>
+                </a>
+            </div>
+        </div>
+    </div>
+
+    <!-- Scripts JavaScript -->
+    <script src="assets/js/scripts.js"></script>
+</body>
+</html>
diff --git a/app/p.php b/app/p.php
new file mode 100644
index 0000000..f7e4a5e
--- /dev/null
+++ b/app/p.php
@@ -0,0 +1,65 @@
+<?php
+/**
+ * Processador de URLs
+ * 
+ * Este arquivo é responsável por:
+ * - Receber URLs através do path /p/
+ * - Validar o formato da URL
+ * - Processar o conteúdo usando o URLAnalyzer
+ * - Exibir o conteúdo processado ou redirecionar em caso de erro
+ * 
+ * Exemplo de uso:
+ * /p/https://exemplo.com
+ */
+
+require_once 'config.php';
+require_once 'inc/URLAnalyzer.php';
+
+// Extrai a URL do path da requisição
+$path = $_SERVER['REQUEST_URI'];
+$prefix = '/p/';
+
+if (strpos($path, $prefix) === 0) {
+    // Remove o prefixo e decodifica a URL
+    $url = urldecode(substr($path, strlen($prefix)));
+    
+    // Valida o formato da URL
+    if (filter_var($url, FILTER_VALIDATE_URL)) {
+        $analyzer = new URLAnalyzer();
+        try {
+            // Tenta analisar e processar a URL
+            $content = $analyzer->analyze($url);
+            // Exibe o conteúdo processado
+            echo $content;
+            exit;
+        } catch (Exception $e) {
+            $errorMessage = $e->getMessage();
+            $errorType = 'GENERIC_ERROR'; // Tipo padrão de erro
+            
+            // Mapeia a mensagem de erro para um tipo específico
+            if (strpos($errorMessage, 'bloqueado') !== false) {
+                $errorType = 'BLOCKED_DOMAIN';
+            } elseif (strpos($errorMessage, 'DNS') !== false) {
+                $errorType = 'DNS_FAILURE';
+            } elseif (strpos($errorMessage, 'HTTP: 4') !== false || strpos($errorMessage, 'HTTP: 5') !== false) {
+                $errorType = 'HTTP_ERROR';
+            } elseif (strpos($errorMessage, 'CURL') !== false) {
+                $errorType = 'CONNECTION_ERROR';
+            } elseif (strpos($errorMessage, 'obter conteúdo') !== false) {
+                $errorType = 'CONTENT_ERROR';
+            }
+            
+            // Redireciona para a página inicial com mensagem de erro
+            header('Location: /?message=' . $errorType);
+            exit;
+        }
+    } else {
+        // URL inválida
+        header('Location: /?message=INVALID_URL');
+        exit;
+    }
+} else {
+    // Path inválido
+    header('Location: /?message=NOT_FOUND');
+    exit;
+}
diff --git a/default.conf b/default.conf
new file mode 100644
index 0000000..01d9930
--- /dev/null
+++ b/default.conf
@@ -0,0 +1,43 @@
+server {
+    listen 80 default_server;
+    listen [::]:80 default_server;
+
+    root /app;
+    index index.php index.html index.htm;
+
+    server_name _;
+
+    add_header Strict-Transport-Security "max-age=31536000; includeSubDomains" always;
+    add_header X-Frame-Options "SAMEORIGIN" always;
+    add_header X-XSS-Protection "1; mode=block" always;
+    add_header X-Content-Type-Options "nosniff" always;
+
+    location ^~ /logs/ {
+        deny all;
+        return 403;
+    }
+
+    location ^~ /cache/ {
+        deny all;
+        return 403;
+    }
+
+    location / {
+        try_files $uri $uri/ $uri/index.php?$args;
+    }
+
+    location ~ ^/(api|p)/ {
+        try_files $uri $uri/ /$1.php;
+    }
+
+    location ~ \.php$ {
+        include snippets/fastcgi-php.conf;
+        fastcgi_pass 127.0.0.1:9000;
+    }
+
+    location ~ /\.ht {
+        deny all;
+    }
+
+    access_log /dev/null;
+}
diff --git a/docker-compose.yml b/docker-compose.yml
new file mode 100644
index 0000000..0179e77
--- /dev/null
+++ b/docker-compose.yml
@@ -0,0 +1,19 @@
+services:
+  marreta:
+    container_name: marreta
+    image: ghcr.io/manualdousuario/marreta:latest
+    ports:
+      - "80:80"
+    volumes:
+      - marreta_cache:/app/cache
+      - marreta_logs:/app/logs
+    environment:
+      - SITE_NAME=${SITE_NAME:-}
+      - SITE_DESCRIPTION=${SITE_DESCRIPTION:-}
+      - SITE_URL=${SITE_URL:-}
+      - DNS_SERVERS=${DNS_SERVERS:-}
+    restart: unless-stopped
+
+volumes:
+  marreta_cache:
+  marreta_logs:
diff --git a/env.sh b/env.sh
new file mode 100644
index 0000000..61f85a3
--- /dev/null
+++ b/env.sh
@@ -0,0 +1,19 @@
+#!/bin/bash
+
+if [ -n "${SITE_NAME}" ]; then
+    echo "SITE_NAME=${SITE_NAME}" >> /app/.env
+fi
+
+if [ -n "${SITE_DESCRIPTION}" ]; then
+    echo "SITE_DESCRIPTION=${SITE_DESCRIPTION}" >> /app/.env
+fi
+
+if [ -n "${SITE_URL}" ]; then
+    echo "SITE_URL=${SITE_URL}" >> /app/.env
+fi
+
+if [ -n "${DNS_SERVERS}" ]; then
+    echo "DNS_SERVERS=${DNS_SERVERS}" >> /app/.env
+fi
+
+echo "Variáveis de ambiente salvas com sucesso."
diff --git a/start.sh b/start.sh
new file mode 100644
index 0000000..ee7fd2e
--- /dev/null
+++ b/start.sh
@@ -0,0 +1,55 @@
+#!/bin/bash
+
+echo "Iniciando rotinas."
+
+check_nginx() {
+    if ! pgrep nginx > /dev/null; then
+        echo "Falha ao iniciar webservice."
+        exit 1
+    else
+        echo "Webservice iniciou."
+    fi
+}
+
+check_php_fpm() {
+    if ! pgrep php-fpm > /dev/null; then
+        echo "Falha ao iniciar o PHP."
+        exit 1
+    else
+        echo "PHP iniciou."
+    fi
+}
+
+if [ ! -d /var/run/php ]; then
+    mkdir -p /var/run/php
+    chown -R www-data:www-data /var/run/php
+fi
+
+echo "Iniciando PHP..."
+php-fpm &
+
+sleep 3
+
+check_php_fpm
+
+sleep 3
+
+echo "Testando configuração do webservice..."
+nginx -t
+if [ $? -ne 0 ]; then
+    echo "Configuração do webservice invalida."
+    exit 1
+else
+    echo "Configuração valida do webservice."
+fi
+
+echo "Iniciando webservice..."
+nginx -g "daemon off;" &
+
+sleep 3
+
+check_nginx
+
+wait -n
+
+exit $?