From f82a77febe709dd3bef229b367f14d6dced51c58 Mon Sep 17 00:00:00 2001
From: Your Name <119736744+aborayan2022@users.noreply.github.com>
Date: Sun, 29 Mar 2026 20:17:52 +0200
Subject: [PATCH] Refactor code structure for improved readability and
maintainability
---
.github/copilot-instructions.md | 71 ++++
backend/app/utils/file_validator.py | 119 +++++--
backend/tests/test_file_validator.py | 134 +++++---
frontend/public/sitemap.xml | 490 +++++++++++++--------------
4 files changed, 502 insertions(+), 312 deletions(-)
create mode 100644 .github/copilot-instructions.md
diff --git a/.github/copilot-instructions.md b/.github/copilot-instructions.md
new file mode 100644
index 0000000..25c5b7b
--- /dev/null
+++ b/.github/copilot-instructions.md
@@ -0,0 +1,71 @@
+# Copilot Workspace Instructions
+
+Purpose
+- Help Copilot-style agents and contributors be productive and safe in this repository.
+- Surface where to find authoritative docs and which conventions to follow.
+
+Principles
+- Link, don't embed: prefer linking to existing docs in `docs/`, `CONTRIBUTING.md`, or `README.md` rather than duplicating content.
+- Minimize blast radius: make minimal, focused changes and explain rationale in PRs.
+- Ask clarifying questions before large or ambiguous changes.
+
+What the agent is allowed to do
+- Suggest edits, create focused patches, and propose new files following repo style.
+- Use `apply_patch` for file edits; create new files only when necessary.
+- Run or suggest commands to run tests locally, but do not push or merge without human approval.
+
+Conventions & expectations
+- Follow existing code style and directory boundaries (`backend/` for Flask/Python, `frontend/` for Vite/TypeScript).
+- When changing behavior, run tests and list the commands to reproduce the failure/fix.
+- Keep PRs small and target a single logical change.
+
+Key files & links (authoritative sources)
+- README: [README.md](README.md#L1)
+- Contribution & tests: [CONTRIBUTING.md](CONTRIBUTING.md#L1)
+- Docker & run commands: [docs/Docker-Commands-Guide.md](docs/Docker-Commands-Guide.md#L1)
+- Backend entry & requirements: [backend/requirements.txt](backend/requirements.txt#L1), [backend/Dockerfile](backend/Dockerfile#L1)
+- Frontend scripts: [frontend/package.json](frontend/package.json#L1), [frontend/Dockerfile](frontend/Dockerfile#L1)
+- Compose files: [docker-compose.yml](docker-compose.yml#L1), [docker-compose.prod.yml](docker-compose.prod.yml#L1)
+- Deployment scripts: [scripts/deploy.sh](scripts/deploy.sh#L1)
+
+Common build & test commands
+- Backend tests (project root):
+```
+cd backend && python -m pytest tests/ -q
+```
+- Frontend dev & tests:
+```
+cd frontend && npm install
+cd frontend && npm run dev
+cd frontend && npx vitest run
+```
+- Dev compose (full stack):
+```
+docker compose up --build
+```
+- Prod deploy (refer to `scripts/deploy.sh`):
+```
+./scripts/deploy.sh
+```
+
+Anti-patterns (avoid)
+- Don't invent architectural decisions or rewrite large areas without explicit approval.
+- Don't add secrets, large binary files, or unrelated formatting changes.
+- Don't run destructive commands or modify CI/CD configuration without coordination.
+
+Agent prompts & examples
+- "Create a small Flask route in `backend/app/routes` that returns health JSON and add a unit test."
+- "Refactor the image compression service to extract a helper; update callers and tests."
+- "List the exact commands I should run to reproduce the failing tests for `backend/tests/test_pdf_service.py`."
+
+Suggested follow-ups (agent customizations)
+- `create-agent:backend` — focused on Python/Flask edits, runs `pytest`, and knows `backend/` structure.
+- `create-agent:frontend` — focused on Vite/TypeScript, runs `vitest`, and uses `npm` scripts.
+- `create-agent:ci` — analyzes `docker-compose.yml` and `scripts/deploy.sh`, suggests CI checks and smoke tests.
+
+If you want, I can:
+- Open a draft PR with this file, or
+- Expand the file with more precise command snippets and per-service README links.
+
+---
+Generated by a workspace bootstrap; iterate as needed.
diff --git a/backend/app/utils/file_validator.py b/backend/app/utils/file_validator.py
index 3dd1fa7..a0ff552 100644
--- a/backend/app/utils/file_validator.py
+++ b/backend/app/utils/file_validator.py
@@ -1,11 +1,6 @@
"""File validation utilities — multi-layer security checks."""
-import os
-try:
- import magic
- HAS_MAGIC = True
-except (ImportError, OSError):
- HAS_MAGIC = False
+import os
from flask import current_app
from werkzeug.utils import secure_filename
@@ -45,30 +40,60 @@ def validate_file(
if not file_storage or file_storage.filename == "":
raise FileValidationError("No file provided.")
- filename = secure_filename(file_storage.filename)
- if not filename:
- raise FileValidationError("Invalid filename.")
+ raw_filename = str(file_storage.filename).strip()
+ if not raw_filename:
+ raise FileValidationError("No file provided.")
- # Layer 2: Check file extension against whitelist
- ext = _get_extension(filename)
+ filename = secure_filename(raw_filename)
allowed_extensions = config.get("ALLOWED_EXTENSIONS", {})
if allowed_types:
- valid_extensions = {k: v for k, v in allowed_extensions.items() if k in allowed_types}
+ valid_extensions = {
+ k: v for k, v in allowed_extensions.items() if k in allowed_types
+ }
else:
valid_extensions = allowed_extensions
+ # Layer 2: Reject clearly invalid extensions before touching file streams.
+ ext = _get_extension(raw_filename) or _get_extension(filename)
+ if ext and ext not in valid_extensions:
+ raise FileValidationError(
+ f"File type '.{ext}' is not allowed. "
+ f"Allowed types: {', '.join(valid_extensions.keys())}"
+ )
+
+ # Layer 3: Check basic file size and header first so we can recover
+ # from malformed filenames like ".pdf" or "." using content sniffing.
+ file_storage.seek(0, os.SEEK_END)
+ file_size = file_storage.tell()
+ file_storage.seek(0)
+
+ if file_size == 0:
+ raise FileValidationError("File is empty.")
+
+ file_header = file_storage.read(8192)
+ file_storage.seek(0)
+
+ detected_mime = _detect_mime(file_header)
+
+ if not ext:
+ ext = _infer_extension_from_content(
+ file_header, detected_mime, valid_extensions
+ )
+
+ if raw_filename.startswith(".") and not _get_extension(filename):
+ filename = ""
+
+ if not filename:
+ filename = f"upload.{ext}" if ext else "upload"
+
if ext not in valid_extensions:
raise FileValidationError(
f"File type '.{ext}' is not allowed. "
f"Allowed types: {', '.join(valid_extensions.keys())}"
)
- # Layer 3: Check file size against type-specific limits
- file_storage.seek(0, os.SEEK_END)
- file_size = file_storage.tell()
- file_storage.seek(0)
-
+ # Layer 4: Check file size against type-specific limits
size_limits = size_limit_overrides or config.get("FILE_SIZE_LIMITS", {})
max_size = size_limits.get(ext, 20 * 1024 * 1024) # Default 20MB
@@ -78,15 +103,8 @@ def validate_file(
f"File too large. Maximum size for .{ext} files is {max_mb:.0f}MB."
)
- if file_size == 0:
- raise FileValidationError("File is empty.")
-
- # Layer 4: Check MIME type using magic bytes (if libmagic is available)
- file_header = file_storage.read(8192)
- file_storage.seek(0)
-
- if HAS_MAGIC:
- detected_mime = magic.from_buffer(file_header, mime=True)
+ # Layer 5: Check MIME type using magic bytes (if libmagic is available)
+ if detected_mime:
expected_mimes = valid_extensions.get(ext, [])
if detected_mime not in expected_mimes:
@@ -95,7 +113,7 @@ def validate_file(
f"Detected type: {detected_mime}"
)
- # Layer 5: Additional content checks for specific types
+ # Layer 6: Additional content checks for specific types
if ext == "pdf":
_check_pdf_safety(file_header)
@@ -104,9 +122,52 @@ def validate_file(
def _get_extension(filename: str) -> str:
"""Extract and normalize file extension."""
- if "." not in filename:
+ filename = str(filename or "").strip()
+ if not filename or "." not in filename:
return ""
- return filename.rsplit(".", 1)[1].lower()
+ stem, ext = filename.rsplit(".", 1)
+ if not ext:
+ return ""
+ if not stem and filename.startswith("."):
+ return ext.lower()
+ return ext.lower()
+
+
+def _detect_mime(file_header: bytes) -> str | None:
+ """Detect MIME type lazily so environments without libmagic stay usable."""
+ try:
+ import magic as magic_module
+ except (ImportError, OSError):
+ return None
+
+ try:
+ return magic_module.from_buffer(file_header, mime=True)
+ except Exception:
+ return None
+
+
+def _infer_extension_from_content(
+ file_header: bytes,
+ detected_mime: str | None,
+ valid_extensions: dict[str, list[str]],
+) -> str:
+ """Infer a safe extension from MIME type or common signatures."""
+ if detected_mime:
+ for ext, mimes in valid_extensions.items():
+ if detected_mime in mimes:
+ return ext
+
+ signature_map = {
+ b"%PDF": "pdf",
+ b"\x89PNG\r\n\x1a\n": "png",
+ b"\xff\xd8\xff": "jpg",
+ b"RIFF": "webp",
+ }
+ for signature, ext in signature_map.items():
+ if file_header.startswith(signature) and ext in valid_extensions:
+ return ext
+
+ return ""
def _check_pdf_safety(file_header: bytes):
diff --git a/backend/tests/test_file_validator.py b/backend/tests/test_file_validator.py
index c95d099..16c8424 100644
--- a/backend/tests/test_file_validator.py
+++ b/backend/tests/test_file_validator.py
@@ -1,6 +1,7 @@
"""Tests for file validation utility."""
+
import io
-from unittest.mock import patch, MagicMock
+from unittest.mock import MagicMock
from app.utils.file_validator import validate_file, FileValidationError
import pytest
@@ -16,7 +17,7 @@ class TestFileValidator:
"""Should raise when filename is empty."""
with app.app_context():
mock_file = MagicMock()
- mock_file.filename = ''
+ mock_file.filename = ""
with pytest.raises(FileValidationError, match="No file provided"):
validate_file(mock_file, allowed_types=["pdf"])
@@ -24,16 +25,16 @@ class TestFileValidator:
"""Should raise when file extension is not allowed."""
with app.app_context():
mock_file = MagicMock()
- mock_file.filename = 'test.exe'
+ mock_file.filename = "test.exe"
with pytest.raises(FileValidationError, match="not allowed"):
validate_file(mock_file, allowed_types=["pdf"])
def test_empty_file_raises(self, app):
"""Should raise when file is empty (0 bytes)."""
with app.app_context():
- content = io.BytesIO(b'')
+ content = io.BytesIO(b"")
mock_file = MagicMock()
- mock_file.filename = 'test.pdf'
+ mock_file.filename = "test.pdf"
mock_file.seek = content.seek
mock_file.tell = content.tell
mock_file.read = content.read
@@ -43,93 +44,150 @@ class TestFileValidator:
def test_valid_pdf_passes(self, app):
"""Should accept valid PDF file with correct magic bytes."""
with app.app_context():
- pdf_bytes = b'%PDF-1.4 test content' + b'\x00' * 8192
+ pdf_bytes = b"%PDF-1.4 test content" + b"\x00" * 8192
content = io.BytesIO(pdf_bytes)
mock_file = MagicMock()
- mock_file.filename = 'document.pdf'
+ mock_file.filename = "document.pdf"
mock_file.seek = content.seek
mock_file.tell = content.tell
mock_file.read = content.read
- with patch('app.utils.file_validator.HAS_MAGIC', True), patch(
- 'app.utils.file_validator.magic', create=True
- ) as mock_magic:
- mock_magic.from_buffer.return_value = 'application/pdf'
+ with pytest.MonkeyPatch.context() as monkeypatch:
+ monkeypatch.setattr(
+ "app.utils.file_validator._detect_mime",
+ lambda _header: "application/pdf",
+ )
filename, ext = validate_file(mock_file, allowed_types=["pdf"])
- assert filename == 'document.pdf'
- assert ext == 'pdf'
+ assert filename == "document.pdf"
+ assert ext == "pdf"
def test_valid_html_passes(self, app):
"""Should accept valid HTML file with correct MIME type."""
with app.app_context():
- html_bytes = b'
Hello'
+ html_bytes = b"Hello"
content = io.BytesIO(html_bytes)
mock_file = MagicMock()
- mock_file.filename = 'page.html'
+ mock_file.filename = "page.html"
mock_file.seek = content.seek
mock_file.tell = content.tell
mock_file.read = content.read
- with patch('app.utils.file_validator.HAS_MAGIC', True), patch(
- 'app.utils.file_validator.magic', create=True
- ) as mock_magic:
- mock_magic.from_buffer.return_value = 'text/html'
+ with pytest.MonkeyPatch.context() as monkeypatch:
+ monkeypatch.setattr(
+ "app.utils.file_validator._detect_mime",
+ lambda _header: "text/html",
+ )
filename, ext = validate_file(mock_file, allowed_types=["html", "htm"])
- assert filename == 'page.html'
- assert ext == 'html'
+ assert filename == "page.html"
+ assert ext == "html"
def test_mime_mismatch_raises(self, app):
"""Should raise when MIME type doesn't match extension."""
with app.app_context():
- content = io.BytesIO(b'not a real pdf' + b'\x00' * 8192)
+ content = io.BytesIO(b"not a real pdf" + b"\x00" * 8192)
mock_file = MagicMock()
- mock_file.filename = 'fake.pdf'
+ mock_file.filename = "fake.pdf"
mock_file.seek = content.seek
mock_file.tell = content.tell
mock_file.read = content.read
- with patch('app.utils.file_validator.HAS_MAGIC', True), patch(
- 'app.utils.file_validator.magic', create=True
- ) as mock_magic:
- mock_magic.from_buffer.return_value = 'text/plain'
+ with pytest.MonkeyPatch.context() as monkeypatch:
+ monkeypatch.setattr(
+ "app.utils.file_validator._detect_mime",
+ lambda _header: "text/plain",
+ )
with pytest.raises(FileValidationError, match="does not match"):
validate_file(mock_file, allowed_types=["pdf"])
def test_file_too_large_raises(self, app):
"""Should raise when file exceeds size limit."""
with app.app_context():
- # Create a file larger than the PDF size limit (20MB)
- large_content = io.BytesIO(b'%PDF-1.4' + b'\x00' * (21 * 1024 * 1024))
+ # Use a small override to keep the test stable on Windows/Python 3.13.
+ large_content = io.BytesIO(b"%PDF-1.4" + b"\x00" * 2048)
mock_file = MagicMock()
- mock_file.filename = 'large.pdf'
+ mock_file.filename = "large.pdf"
mock_file.seek = large_content.seek
mock_file.tell = large_content.tell
mock_file.read = large_content.read
- with pytest.raises(FileValidationError, match="too large"):
- validate_file(mock_file, allowed_types=["pdf"])
+ with pytest.MonkeyPatch.context() as monkeypatch:
+ monkeypatch.setattr(
+ "app.utils.file_validator._detect_mime",
+ lambda _header: "application/pdf",
+ )
+ with pytest.raises(FileValidationError, match="too large"):
+ validate_file(
+ mock_file,
+ allowed_types=["pdf"],
+ size_limit_overrides={"pdf": 1024},
+ )
def test_dangerous_pdf_raises(self, app):
"""Should raise when PDF contains dangerous patterns."""
with app.app_context():
- pdf_bytes = b'%PDF-1.4 /JavaScript evil_code' + b'\x00' * 8192
+ pdf_bytes = b"%PDF-1.4 /JavaScript evil_code" + b"\x00" * 8192
content = io.BytesIO(pdf_bytes)
mock_file = MagicMock()
- mock_file.filename = 'evil.pdf'
+ mock_file.filename = "evil.pdf"
mock_file.seek = content.seek
mock_file.tell = content.tell
mock_file.read = content.read
- with patch('app.utils.file_validator.HAS_MAGIC', True), patch(
- 'app.utils.file_validator.magic', create=True
- ) as mock_magic:
- mock_magic.from_buffer.return_value = 'application/pdf'
+ with pytest.MonkeyPatch.context() as monkeypatch:
+ monkeypatch.setattr(
+ "app.utils.file_validator._detect_mime",
+ lambda _header: "application/pdf",
+ )
with pytest.raises(FileValidationError, match="unsafe"):
validate_file(mock_file, allowed_types=["pdf"])
+
+ def test_pdf_with_missing_extension_name_is_inferred(self, app):
+ """Should infer PDF extension from content when filename lacks one."""
+ with app.app_context():
+ pdf_bytes = b"%PDF-1.4 test content" + b"\x00" * 8192
+ content = io.BytesIO(pdf_bytes)
+
+ mock_file = MagicMock()
+ mock_file.filename = "."
+ mock_file.seek = content.seek
+ mock_file.tell = content.tell
+ mock_file.read = content.read
+
+ with pytest.MonkeyPatch.context() as monkeypatch:
+ monkeypatch.setattr(
+ "app.utils.file_validator._detect_mime",
+ lambda _header: "application/pdf",
+ )
+ filename, ext = validate_file(mock_file, allowed_types=["pdf"])
+
+ assert filename == "upload.pdf"
+ assert ext == "pdf"
+
+ def test_pdf_hidden_filename_keeps_pdf_extension(self, app):
+ """Should preserve .pdf from hidden-style filenames like .pdf."""
+ with app.app_context():
+ pdf_bytes = b"%PDF-1.4 test content" + b"\x00" * 8192
+ content = io.BytesIO(pdf_bytes)
+
+ mock_file = MagicMock()
+ mock_file.filename = ".pdf"
+ mock_file.seek = content.seek
+ mock_file.tell = content.tell
+ mock_file.read = content.read
+
+ with pytest.MonkeyPatch.context() as monkeypatch:
+ monkeypatch.setattr(
+ "app.utils.file_validator._detect_mime",
+ lambda _header: "application/pdf",
+ )
+ filename, ext = validate_file(mock_file, allowed_types=["pdf"])
+
+ assert filename == "upload.pdf"
+ assert ext == "pdf"
diff --git a/frontend/public/sitemap.xml b/frontend/public/sitemap.xml
index 4f794d1..2eea23f 100644
--- a/frontend/public/sitemap.xml
+++ b/frontend/public/sitemap.xml
@@ -2,1471 +2,1471 @@
https://dociva.io/
- 2026-03-27
+ 2026-03-29
daily
1.0
https://dociva.io/about
- 2026-03-27
+ 2026-03-29
monthly
0.4
https://dociva.io/contact
- 2026-03-27
+ 2026-03-29
monthly
0.4
https://dociva.io/privacy
- 2026-03-27
+ 2026-03-29
yearly
0.3
https://dociva.io/terms
- 2026-03-27
+ 2026-03-29
yearly
0.3
https://dociva.io/pricing
- 2026-03-27
+ 2026-03-29
monthly
0.7
https://dociva.io/blog
- 2026-03-27
+ 2026-03-29
weekly
0.6
https://dociva.io/developers
- 2026-03-27
+ 2026-03-29
monthly
0.5
https://dociva.io/blog/how-to-compress-pdf-online
- 2026-03-27
+ 2026-03-29
monthly
0.6
https://dociva.io/blog/convert-images-without-losing-quality
- 2026-03-27
+ 2026-03-29
monthly
0.6
https://dociva.io/blog/ocr-extract-text-from-images
- 2026-03-27
+ 2026-03-29
monthly
0.6
https://dociva.io/blog/merge-split-pdf-files
- 2026-03-27
+ 2026-03-29
monthly
0.6
https://dociva.io/blog/ai-chat-with-pdf-documents
- 2026-03-27
+ 2026-03-29
monthly
0.6
https://dociva.io/tools/pdf-to-word
- 2026-03-27
+ 2026-03-29
weekly
0.9
https://dociva.io/tools/word-to-pdf
- 2026-03-27
+ 2026-03-29
weekly
0.9
https://dociva.io/tools/compress-pdf
- 2026-03-27
+ 2026-03-29
weekly
0.9
https://dociva.io/tools/merge-pdf
- 2026-03-27
+ 2026-03-29
weekly
0.9
https://dociva.io/tools/split-pdf
- 2026-03-27
+ 2026-03-29
weekly
0.8
https://dociva.io/tools/rotate-pdf
- 2026-03-27
+ 2026-03-29
weekly
0.7
https://dociva.io/tools/pdf-to-images
- 2026-03-27
+ 2026-03-29
weekly
0.8
https://dociva.io/tools/images-to-pdf
- 2026-03-27
+ 2026-03-29
weekly
0.8
https://dociva.io/tools/watermark-pdf
- 2026-03-27
+ 2026-03-29
weekly
0.7
https://dociva.io/tools/protect-pdf
- 2026-03-27
+ 2026-03-29
weekly
0.8
https://dociva.io/tools/unlock-pdf
- 2026-03-27
+ 2026-03-29
weekly
0.8
https://dociva.io/tools/page-numbers
- 2026-03-27
+ 2026-03-29
weekly
0.7
https://dociva.io/tools/pdf-editor
- 2026-03-27
+ 2026-03-29
weekly
0.8
https://dociva.io/tools/pdf-flowchart
- 2026-03-27
+ 2026-03-29
weekly
0.7
https://dociva.io/tools/pdf-to-excel
- 2026-03-27
+ 2026-03-29
weekly
0.8
https://dociva.io/tools/remove-watermark-pdf
- 2026-03-27
+ 2026-03-29
weekly
0.7
https://dociva.io/tools/reorder-pdf
- 2026-03-27
+ 2026-03-29
weekly
0.7
https://dociva.io/tools/extract-pages
- 2026-03-27
+ 2026-03-29
weekly
0.7
https://dociva.io/tools/image-converter
- 2026-03-27
+ 2026-03-29
weekly
0.8
https://dociva.io/tools/image-resize
- 2026-03-27
+ 2026-03-29
weekly
0.8
https://dociva.io/tools/compress-image
- 2026-03-27
+ 2026-03-29
weekly
0.8
https://dociva.io/tools/ocr
- 2026-03-27
+ 2026-03-29
weekly
0.8
https://dociva.io/tools/remove-background
- 2026-03-27
+ 2026-03-29
weekly
0.8
https://dociva.io/tools/image-to-svg
- 2026-03-27
+ 2026-03-29
weekly
0.8
https://dociva.io/tools/html-to-pdf
- 2026-03-27
+ 2026-03-29
weekly
0.7
https://dociva.io/tools/chat-pdf
- 2026-03-27
+ 2026-03-29
weekly
0.8
https://dociva.io/tools/summarize-pdf
- 2026-03-27
+ 2026-03-29
weekly
0.8
https://dociva.io/tools/translate-pdf
- 2026-03-27
+ 2026-03-29
weekly
0.8
https://dociva.io/tools/extract-tables
- 2026-03-27
+ 2026-03-29
weekly
0.8
https://dociva.io/tools/qr-code
- 2026-03-27
+ 2026-03-29
weekly
0.7
https://dociva.io/tools/video-to-gif
- 2026-03-27
+ 2026-03-29
weekly
0.7
https://dociva.io/tools/word-counter
- 2026-03-27
+ 2026-03-29
weekly
0.6
https://dociva.io/tools/text-cleaner
- 2026-03-27
+ 2026-03-29
weekly
0.6
https://dociva.io/tools/pdf-to-pptx
- 2026-03-27
+ 2026-03-29
weekly
0.8
https://dociva.io/tools/excel-to-pdf
- 2026-03-27
+ 2026-03-29
weekly
0.8
https://dociva.io/tools/pptx-to-pdf
- 2026-03-27
+ 2026-03-29
weekly
0.8
https://dociva.io/tools/sign-pdf
- 2026-03-27
+ 2026-03-29
weekly
0.8
https://dociva.io/tools/crop-pdf
- 2026-03-27
+ 2026-03-29
weekly
0.7
https://dociva.io/tools/flatten-pdf
- 2026-03-27
+ 2026-03-29
weekly
0.7
https://dociva.io/tools/repair-pdf
- 2026-03-27
+ 2026-03-29
weekly
0.7
https://dociva.io/tools/pdf-metadata
- 2026-03-27
+ 2026-03-29
weekly
0.6
https://dociva.io/tools/image-crop
- 2026-03-27
+ 2026-03-29
weekly
0.7
https://dociva.io/tools/image-rotate-flip
- 2026-03-27
+ 2026-03-29
weekly
0.7
https://dociva.io/tools/barcode-generator
- 2026-03-27
+ 2026-03-29
weekly
0.7
https://dociva.io/pdf-to-word
- 2026-03-27
+ 2026-03-29
weekly
0.88
https://dociva.io/ar/pdf-to-word
- 2026-03-27
+ 2026-03-29
weekly
0.8
https://dociva.io/word-to-pdf
- 2026-03-27
+ 2026-03-29
weekly
0.88
https://dociva.io/ar/word-to-pdf
- 2026-03-27
+ 2026-03-29
weekly
0.8
https://dociva.io/compress-pdf-online
- 2026-03-27
+ 2026-03-29
weekly
0.88
https://dociva.io/ar/compress-pdf-online
- 2026-03-27
+ 2026-03-29
weekly
0.8
https://dociva.io/convert-jpg-to-pdf
- 2026-03-27
+ 2026-03-29
weekly
0.88
https://dociva.io/ar/convert-jpg-to-pdf
- 2026-03-27
+ 2026-03-29
weekly
0.8
https://dociva.io/merge-pdf-files
- 2026-03-27
+ 2026-03-29
weekly
0.88
https://dociva.io/ar/merge-pdf-files
- 2026-03-27
+ 2026-03-29
weekly
0.8
https://dociva.io/remove-pdf-password
- 2026-03-27
+ 2026-03-29
weekly
0.88
https://dociva.io/ar/remove-pdf-password
- 2026-03-27
+ 2026-03-29
weekly
0.8
https://dociva.io/pdf-to-word-editable
- 2026-03-27
+ 2026-03-29
weekly
0.88
https://dociva.io/ar/pdf-to-word-editable
- 2026-03-27
+ 2026-03-29
weekly
0.8
https://dociva.io/convert-pdf-to-text
- 2026-03-27
+ 2026-03-29
weekly
0.88
https://dociva.io/ar/convert-pdf-to-text
- 2026-03-27
+ 2026-03-29
weekly
0.8
https://dociva.io/split-pdf-online
- 2026-03-27
+ 2026-03-29
weekly
0.88
https://dociva.io/ar/split-pdf-online
- 2026-03-27
+ 2026-03-29
weekly
0.8
https://dociva.io/jpg-to-pdf
- 2026-03-27
+ 2026-03-29
weekly
0.88
https://dociva.io/ar/jpg-to-pdf
- 2026-03-27
+ 2026-03-29
weekly
0.8
https://dociva.io/png-to-pdf
- 2026-03-27
+ 2026-03-29
weekly
0.88
https://dociva.io/ar/png-to-pdf
- 2026-03-27
+ 2026-03-29
weekly
0.8
https://dociva.io/images-to-pdf-online
- 2026-03-27
+ 2026-03-29
weekly
0.88
https://dociva.io/ar/images-to-pdf-online
- 2026-03-27
+ 2026-03-29
weekly
0.8
https://dociva.io/pdf-to-jpg
- 2026-03-27
+ 2026-03-29
weekly
0.88
https://dociva.io/ar/pdf-to-jpg
- 2026-03-27
+ 2026-03-29
weekly
0.8
https://dociva.io/pdf-to-png
- 2026-03-27
+ 2026-03-29
weekly
0.88
https://dociva.io/ar/pdf-to-png
- 2026-03-27
+ 2026-03-29
weekly
0.8
https://dociva.io/compress-pdf-for-email
- 2026-03-27
+ 2026-03-29
weekly
0.88
https://dociva.io/ar/compress-pdf-for-email
- 2026-03-27
+ 2026-03-29
weekly
0.8
https://dociva.io/compress-scanned-pdf
- 2026-03-27
+ 2026-03-29
weekly
0.88
https://dociva.io/ar/compress-scanned-pdf
- 2026-03-27
+ 2026-03-29
weekly
0.8
https://dociva.io/merge-pdf-online-free
- 2026-03-27
+ 2026-03-29
weekly
0.88
https://dociva.io/ar/merge-pdf-online-free
- 2026-03-27
+ 2026-03-29
weekly
0.8
https://dociva.io/combine-pdf-files
- 2026-03-27
+ 2026-03-29
weekly
0.88
https://dociva.io/ar/combine-pdf-files
- 2026-03-27
+ 2026-03-29
weekly
0.8
https://dociva.io/extract-pages-from-pdf
- 2026-03-27
+ 2026-03-29
weekly
0.88
https://dociva.io/ar/extract-pages-from-pdf
- 2026-03-27
+ 2026-03-29
weekly
0.8
https://dociva.io/reorder-pdf-pages
- 2026-03-27
+ 2026-03-29
weekly
0.88
https://dociva.io/ar/reorder-pdf-pages
- 2026-03-27
+ 2026-03-29
weekly
0.8
https://dociva.io/rotate-pdf-pages
- 2026-03-27
+ 2026-03-29
weekly
0.88
https://dociva.io/ar/rotate-pdf-pages
- 2026-03-27
+ 2026-03-29
weekly
0.8
https://dociva.io/add-page-numbers-to-pdf
- 2026-03-27
+ 2026-03-29
weekly
0.88
https://dociva.io/ar/add-page-numbers-to-pdf
- 2026-03-27
+ 2026-03-29
weekly
0.8
https://dociva.io/protect-pdf-with-password
- 2026-03-27
+ 2026-03-29
weekly
0.88
https://dociva.io/ar/protect-pdf-with-password
- 2026-03-27
+ 2026-03-29
weekly
0.8
https://dociva.io/unlock-pdf-online
- 2026-03-27
+ 2026-03-29
weekly
0.88
https://dociva.io/ar/unlock-pdf-online
- 2026-03-27
+ 2026-03-29
weekly
0.8
https://dociva.io/watermark-pdf-online
- 2026-03-27
+ 2026-03-29
weekly
0.88
https://dociva.io/ar/watermark-pdf-online
- 2026-03-27
+ 2026-03-29
weekly
0.8
https://dociva.io/remove-watermark-from-pdf
- 2026-03-27
+ 2026-03-29
weekly
0.88
https://dociva.io/ar/remove-watermark-from-pdf
- 2026-03-27
+ 2026-03-29
weekly
0.8
https://dociva.io/edit-pdf-online-free
- 2026-03-27
+ 2026-03-29
weekly
0.88
https://dociva.io/ar/edit-pdf-online-free
- 2026-03-27
+ 2026-03-29
weekly
0.8
https://dociva.io/pdf-to-excel-online
- 2026-03-27
+ 2026-03-29
weekly
0.88
https://dociva.io/ar/pdf-to-excel-online
- 2026-03-27
+ 2026-03-29
weekly
0.8
https://dociva.io/extract-tables-from-pdf
- 2026-03-27
+ 2026-03-29
weekly
0.88
https://dociva.io/ar/extract-tables-from-pdf
- 2026-03-27
+ 2026-03-29
weekly
0.8
https://dociva.io/html-to-pdf-online
- 2026-03-27
+ 2026-03-29
weekly
0.88
https://dociva.io/ar/html-to-pdf-online
- 2026-03-27
+ 2026-03-29
weekly
0.8
https://dociva.io/scan-pdf-to-text
- 2026-03-27
+ 2026-03-29
weekly
0.88
https://dociva.io/ar/scan-pdf-to-text
- 2026-03-27
+ 2026-03-29
weekly
0.8
https://dociva.io/chat-with-pdf
- 2026-03-27
+ 2026-03-29
weekly
0.88
https://dociva.io/ar/chat-with-pdf
- 2026-03-27
+ 2026-03-29
weekly
0.8
https://dociva.io/summarize-pdf-online
- 2026-03-27
+ 2026-03-29
weekly
0.88
https://dociva.io/ar/summarize-pdf-online
- 2026-03-27
+ 2026-03-29
weekly
0.8
https://dociva.io/translate-pdf-online
- 2026-03-27
+ 2026-03-29
weekly
0.88
https://dociva.io/ar/translate-pdf-online
- 2026-03-27
+ 2026-03-29
weekly
0.8
https://dociva.io/convert-image-to-pdf
- 2026-03-27
+ 2026-03-29
weekly
0.88
https://dociva.io/ar/convert-image-to-pdf
- 2026-03-27
+ 2026-03-29
weekly
0.8
https://dociva.io/convert-webp-to-jpg
- 2026-03-27
+ 2026-03-29
weekly
0.88
https://dociva.io/ar/convert-webp-to-jpg
- 2026-03-27
+ 2026-03-29
weekly
0.8
https://dociva.io/resize-image-online
- 2026-03-27
+ 2026-03-29
weekly
0.88
https://dociva.io/ar/resize-image-online
- 2026-03-27
+ 2026-03-29
weekly
0.8
https://dociva.io/compress-image-online
- 2026-03-27
+ 2026-03-29
weekly
0.88
https://dociva.io/ar/compress-image-online
- 2026-03-27
+ 2026-03-29
weekly
0.8
https://dociva.io/remove-image-background
- 2026-03-27
+ 2026-03-29
weekly
0.88
https://dociva.io/ar/remove-image-background
- 2026-03-27
+ 2026-03-29
weekly
0.8
https://dociva.io/pdf-to-word-editable-free
- 2026-03-27
+ 2026-03-29
weekly
0.88
https://dociva.io/ar/pdf-to-word-editable-free
- 2026-03-27
+ 2026-03-29
weekly
0.8
https://dociva.io/compress-pdf-to-100kb
- 2026-03-27
+ 2026-03-29
weekly
0.88
https://dociva.io/ar/compress-pdf-to-100kb
- 2026-03-27
+ 2026-03-29
weekly
0.8
https://dociva.io/ai-extract-text-from-pdf
- 2026-03-27
+ 2026-03-29
weekly
0.88
https://dociva.io/ar/ai-extract-text-from-pdf
- 2026-03-27
+ 2026-03-29
weekly
0.8
https://dociva.io/pdf-to-excel-accurate-free
- 2026-03-27
+ 2026-03-29
weekly
0.88
https://dociva.io/ar/pdf-to-excel-accurate-free
- 2026-03-27
+ 2026-03-29
weekly
0.8
https://dociva.io/split-pdf-online-free
- 2026-03-27
+ 2026-03-29
weekly
0.88
https://dociva.io/ar/split-pdf-online-free
- 2026-03-27
+ 2026-03-29
weekly
0.8
https://dociva.io/compress-pdf-online-free
- 2026-03-27
+ 2026-03-29
weekly
0.88
https://dociva.io/ar/compress-pdf-online-free
- 2026-03-27
+ 2026-03-29
weekly
0.8
https://dociva.io/unlock-pdf-online-free
- 2026-03-27
+ 2026-03-29
weekly
0.88
https://dociva.io/ar/unlock-pdf-online-free
- 2026-03-27
+ 2026-03-29
weekly
0.8
https://dociva.io/summarize-pdf-ai
- 2026-03-27
+ 2026-03-29
weekly
0.88
https://dociva.io/ar/summarize-pdf-ai
- 2026-03-27
+ 2026-03-29
weekly
0.8
https://dociva.io/convert-pdf-to-text-ai
- 2026-03-27
+ 2026-03-29
weekly
0.88
https://dociva.io/ar/convert-pdf-to-text-ai
- 2026-03-27
+ 2026-03-29
weekly
0.8
https://dociva.io/pdf-to-jpg-high-quality
- 2026-03-27
+ 2026-03-29
weekly
0.88
https://dociva.io/ar/pdf-to-jpg-high-quality
- 2026-03-27
+ 2026-03-29
weekly
0.8
https://dociva.io/jpg-to-pdf-online-free
- 2026-03-27
+ 2026-03-29
weekly
0.88
https://dociva.io/ar/jpg-to-pdf-online-free
- 2026-03-27
+ 2026-03-29
weekly
0.8
https://dociva.io/reduce-pdf-size-for-email
- 2026-03-27
+ 2026-03-29
weekly
0.88
https://dociva.io/ar/reduce-pdf-size-for-email
- 2026-03-27
+ 2026-03-29
weekly
0.8
https://dociva.io/ocr-for-scanned-pdfs
- 2026-03-27
+ 2026-03-29
weekly
0.88
https://dociva.io/ar/ocr-for-scanned-pdfs
- 2026-03-27
+ 2026-03-29
weekly
0.8
https://dociva.io/remove-watermark-from-pdf-online
- 2026-03-27
+ 2026-03-29
weekly
0.88
https://dociva.io/ar/remove-watermark-from-pdf-online
- 2026-03-27
+ 2026-03-29
weekly
0.8
https://dociva.io/add-watermark-to-pdf-online
- 2026-03-27
+ 2026-03-29
weekly
0.88
https://dociva.io/ar/add-watermark-to-pdf-online
- 2026-03-27
+ 2026-03-29
weekly
0.8
https://dociva.io/repair-corrupted-pdf-online
- 2026-03-27
+ 2026-03-29
weekly
0.88
https://dociva.io/ar/repair-corrupted-pdf-online
- 2026-03-27
+ 2026-03-29
weekly
0.8
https://dociva.io/rotate-pdf-pages-online
- 2026-03-27
+ 2026-03-29
weekly
0.88
https://dociva.io/ar/rotate-pdf-pages-online
- 2026-03-27
+ 2026-03-29
weekly
0.8
https://dociva.io/reorder-pdf-pages-online
- 2026-03-27
+ 2026-03-29
weekly
0.88
https://dociva.io/ar/reorder-pdf-pages-online
- 2026-03-27
+ 2026-03-29
weekly
0.8
https://dociva.io/pdf-to-png-online
- 2026-03-27
+ 2026-03-29
weekly
0.88
https://dociva.io/ar/pdf-to-png-online
- 2026-03-27
+ 2026-03-29
weekly
0.8
https://dociva.io/images-to-pdf-multiple
- 2026-03-27
+ 2026-03-29
weekly
0.88
https://dociva.io/ar/images-to-pdf-multiple
- 2026-03-27
+ 2026-03-29
weekly
0.8
https://dociva.io/split-pdf-by-range-online
- 2026-03-27
+ 2026-03-29
weekly
0.88
https://dociva.io/ar/split-pdf-by-range-online
- 2026-03-27
+ 2026-03-29
weekly
0.8
https://dociva.io/compress-scanned-pdf-online
- 2026-03-27
+ 2026-03-29
weekly
0.88
https://dociva.io/ar/compress-scanned-pdf-online
- 2026-03-27
+ 2026-03-29
weekly
0.8
https://dociva.io/pdf-metadata-editor-online
- 2026-03-27
+ 2026-03-29
weekly
0.88
https://dociva.io/ar/pdf-metadata-editor-online
- 2026-03-27
+ 2026-03-29
weekly
0.8
https://dociva.io/add-page-numbers-to-pdf-online
- 2026-03-27
+ 2026-03-29
weekly
0.88
https://dociva.io/ar/add-page-numbers-to-pdf-online
- 2026-03-27
+ 2026-03-29
weekly
0.8
https://dociva.io/protect-pdf-with-password-online
- 2026-03-27
+ 2026-03-29
weekly
0.88
https://dociva.io/ar/protect-pdf-with-password-online
- 2026-03-27
+ 2026-03-29
weekly
0.8
https://dociva.io/unlock-encrypted-pdf-online
- 2026-03-27
+ 2026-03-29
weekly
0.88
https://dociva.io/ar/unlock-encrypted-pdf-online
- 2026-03-27
+ 2026-03-29
weekly
0.8
https://dociva.io/ocr-table-extraction-from-pdf
- 2026-03-27
+ 2026-03-29
weekly
0.88
https://dociva.io/ar/ocr-table-extraction-from-pdf
- 2026-03-27
+ 2026-03-29
weekly
0.8
https://dociva.io/pdf-to-excel-converter-online
- 2026-03-27
+ 2026-03-29
weekly
0.88
https://dociva.io/ar/pdf-to-excel-converter-online
- 2026-03-27
+ 2026-03-29
weekly
0.8
https://dociva.io/extract-text-from-protected-pdf
- 2026-03-27
+ 2026-03-29
weekly
0.88
https://dociva.io/ar/extract-text-from-protected-pdf
- 2026-03-27
+ 2026-03-29
weekly
0.8
https://dociva.io/bulk-convert-pdf-to-word
- 2026-03-27
+ 2026-03-29
weekly
0.88
https://dociva.io/ar/bulk-convert-pdf-to-word
- 2026-03-27
+ 2026-03-29
weekly
0.8
https://dociva.io/compress-pdf-for-web-upload
- 2026-03-27
+ 2026-03-29
weekly
0.88
https://dociva.io/ar/compress-pdf-for-web-upload
- 2026-03-27
+ 2026-03-29
weekly
0.8
https://dociva.io/ocr-multi-language-pdf
- 2026-03-27
+ 2026-03-29
weekly
0.88
https://dociva.io/ar/ocr-multi-language-pdf
- 2026-03-27
+ 2026-03-29
weekly
0.8
https://dociva.io/summarize-long-pdf-ai
- 2026-03-27
+ 2026-03-29
weekly
0.88
https://dociva.io/ar/summarize-long-pdf-ai
- 2026-03-27
+ 2026-03-29
weekly
0.8
https://dociva.io/convert-pdf-to-ppt-online
- 2026-03-27
+ 2026-03-29
weekly
0.88
https://dociva.io/ar/convert-pdf-to-ppt-online
- 2026-03-27
+ 2026-03-29
weekly
0.8
https://dociva.io/pdf-to-pptx-free-online
- 2026-03-27
+ 2026-03-29
weekly
0.88
https://dociva.io/ar/pdf-to-pptx-free-online
- 2026-03-27
+ 2026-03-29
weekly
0.8
https://dociva.io/دمج-ملفات-pdf-مجاناً
- 2026-03-27
+ 2026-03-29
weekly
0.88
https://dociva.io/ar/دمج-ملفات-pdf-مجاناً
- 2026-03-27
+ 2026-03-29
weekly
0.8
https://dociva.io/ضغط-بي-دي-اف-اونلاين
- 2026-03-27
+ 2026-03-29
weekly
0.88
https://dociva.io/ar/ضغط-بي-دي-اف-اونلاين
- 2026-03-27
+ 2026-03-29
weekly
0.8
https://dociva.io/تحويل-pdf-الى-word-قابل-للتعديل
- 2026-03-27
+ 2026-03-29
weekly
0.88
https://dociva.io/ar/تحويل-pdf-الى-word-قابل-للتعديل
- 2026-03-27
+ 2026-03-29
weekly
0.8
https://dociva.io/تحويل-jpg-الى-pdf-اونلاين
- 2026-03-27
+ 2026-03-29
weekly
0.88
https://dociva.io/ar/تحويل-jpg-الى-pdf-اونلاين
- 2026-03-27
+ 2026-03-29
weekly
0.8
https://dociva.io/فصل-صفحات-pdf-اونلاين
- 2026-03-27
+ 2026-03-29
weekly
0.88
https://dociva.io/ar/فصل-صفحات-pdf-اونلاين
- 2026-03-27
+ 2026-03-29
weekly
0.8
https://dociva.io/ازالة-كلمة-مرور-من-pdf
- 2026-03-27
+ 2026-03-29
weekly
0.88
https://dociva.io/ar/ازالة-كلمة-مرور-من-pdf
- 2026-03-27
+ 2026-03-29
weekly
0.8
https://dociva.io/تحويل-pdf-الى-نص-باستخدام-ocr
- 2026-03-27
+ 2026-03-29
weekly
0.88
https://dociva.io/ar/تحويل-pdf-الى-نص-باستخدام-ocr
- 2026-03-27
+ 2026-03-29
weekly
0.8
https://dociva.io/تحويل-pdf-الى-excel-اونلاين
- 2026-03-27
+ 2026-03-29
weekly
0.88
https://dociva.io/ar/تحويل-pdf-الى-excel-اونلاين
- 2026-03-27
+ 2026-03-29
weekly
0.8
https://dociva.io/تحويل-pdf-الى-صور
- 2026-03-27
+ 2026-03-29
weekly
0.88
https://dociva.io/ar/تحويل-pdf-الى-صور
- 2026-03-27
+ 2026-03-29
weekly
0.8
https://dociva.io/best-pdf-tools
- 2026-03-27
+ 2026-03-29
weekly
0.82
https://dociva.io/ar/best-pdf-tools
- 2026-03-27
+ 2026-03-29
weekly
0.74
https://dociva.io/free-pdf-tools-online
- 2026-03-27
+ 2026-03-29
weekly
0.82
https://dociva.io/ar/free-pdf-tools-online
- 2026-03-27
+ 2026-03-29
weekly
0.74
https://dociva.io/convert-files-online
- 2026-03-27
+ 2026-03-29
weekly
0.82
https://dociva.io/ar/convert-files-online
- 2026-03-27
+ 2026-03-29
weekly
0.74
https://dociva.io/pdf-converter-tools
- 2026-03-27
+ 2026-03-29
weekly
0.82
https://dociva.io/ar/pdf-converter-tools
- 2026-03-27
+ 2026-03-29
weekly
0.74
https://dociva.io/secure-pdf-tools
- 2026-03-27
+ 2026-03-29
weekly
0.82
https://dociva.io/ar/secure-pdf-tools
- 2026-03-27
+ 2026-03-29
weekly
0.74
https://dociva.io/ai-document-tools
- 2026-03-27
+ 2026-03-29
weekly
0.82
https://dociva.io/ar/ai-document-tools
- 2026-03-27
+ 2026-03-29
weekly
0.74
https://dociva.io/image-to-pdf-tools
- 2026-03-27
+ 2026-03-29
weekly
0.82
https://dociva.io/ar/image-to-pdf-tools
- 2026-03-27
+ 2026-03-29
weekly
0.74
https://dociva.io/online-image-tools
- 2026-03-27
+ 2026-03-29
weekly
0.82
https://dociva.io/ar/online-image-tools
- 2026-03-27
+ 2026-03-29
weekly
0.74
https://dociva.io/office-to-pdf-tools
- 2026-03-27
+ 2026-03-29
weekly
0.82
https://dociva.io/ar/office-to-pdf-tools
- 2026-03-27
+ 2026-03-29
weekly
0.74
https://dociva.io/scanned-document-tools
- 2026-03-27
+ 2026-03-29
weekly
0.82
https://dociva.io/ar/scanned-document-tools
- 2026-03-27
+ 2026-03-29
weekly
0.74
https://dociva.io/arabic-pdf-tools
- 2026-03-27
+ 2026-03-29
weekly
0.82
https://dociva.io/ar/arabic-pdf-tools
- 2026-03-27
+ 2026-03-29
weekly
0.74