proper status code, added detail in reposne

This commit is contained in:
2025-05-31 21:12:51 +05:00
parent b56cfd59a3
commit 247eece2a2

View File

@@ -1,144 +1,120 @@
from fastapi import FastAPI, HTTPException from fastapi import FastAPI, HTTPException
from contextlib import asynccontextmanager from fastapi.responses import JSONResponse
import httpx
import re import re
import os import os
from typing import Dict, Optional import httpx
from urllib.parse import unquote
local_mac_vendors: Dict[str, str] = {} app = FastAPI(title="MAC Address Vendor Lookup API")
@asynccontextmanager def normalize_mac(mac_address: str) -> str:
async def lifespan(app: FastAPI): mac_clean = unquote(mac_address)
load_local_mac_vendors() mac_clean = re.sub(r'[^a-fA-F0-9]', '', mac_clean)
yield
app = FastAPI(title="MAC Address Vendor Lookup", version="1.0.0", lifespan=lifespan) if len(mac_clean) < 6:
return None
local_mac_vendors: Dict[str, str] = {} return '-'.join([mac_clean[i:i+2].upper() for i in range(0, len(mac_clean), 2)])
def load_local_mac_vendors(): def is_valid_mac(mac_address: str) -> bool:
"""Load MAC vendor data from local file""" mac_clean = unquote(mac_address)
global local_mac_vendors mac_clean = re.sub(r'[^a-fA-F0-9]', '', mac_clean)
if local_mac_vendors or not os.path.exists("mac_list.txt"): return len(mac_clean) >= 6 and len(mac_clean) <= 12 and all(c in '0123456789abcdefABCDEF' for c in mac_clean)
return
def get_mac_prefix(mac_address: str) -> str:
mac_clean = re.sub(r'[^a-fA-F0-9]', '', mac_address)
return mac_clean[:6].upper()
def lookup_local_file(mac_prefix: str) -> str:
if not os.path.exists('mac_list.txt'):
return None
try: try:
with open("mac_list.txt", "r", encoding="utf-8") as file: with open('mac_list.txt', 'r') as f:
for line in file: for line in f:
line = line.strip() line = line.strip()
if line: if line and ' ' in line:
parts = line.split('\t', 1) prefix, vendor = line.split(' ', 1)
if len(parts) == 2: if prefix.upper() == mac_prefix:
oui = parts[0].strip().upper() return vendor
vendor = parts[1].strip()
local_mac_vendors[oui] = vendor
except Exception: except Exception:
pass pass
async def lookup_from_github(oui: str) -> Optional[str]: return None
"""Lookup vendor from GitHub gist"""
async def lookup_remote_gist(mac_prefix: str) -> str:
url = "https://gist.githubusercontent.com/aallan/b4bb86db86079509e6159810ae9bd3e4/raw/846ae1b646ab0f4d646af9115e47365f4118e5f6/mac-vendor.txt" url = "https://gist.githubusercontent.com/aallan/b4bb86db86079509e6159810ae9bd3e4/raw/846ae1b646ab0f4d646af9115e47365f4118e5f6/mac-vendor.txt"
try: try:
async with httpx.AsyncClient() as client: async with httpx.AsyncClient() as client:
response = await client.get(url, timeout=10.0) response = await client.get(url, timeout=5.0)
response.raise_for_status() if response.status_code == 200:
for line in response.text.split('\n'):
for line in response.text.strip().split('\n'): line = line.strip()
if line.strip(): if line and ' ' in line:
parts = line.split('\t', 1) prefix, vendor = line.split(' ', 1)
if len(parts) == 2 and parts[0].strip().upper() == oui: if prefix.upper() == mac_prefix:
return parts[1].strip() return vendor
except Exception: except Exception:
pass pass
return None return None
async def lookup_from_macvendors_api(mac_address: str) -> Optional[str]: async def lookup_remote_api(normalized_mac: str) -> str:
"""Lookup vendor from macvendors.com API""" mac_for_api = normalized_mac
if len(mac_for_api.replace('-', '')) == 6:
mac_for_api = mac_for_api + '-00-00-00'
url = f"https://api.macvendors.com/{mac_for_api}"
try: try:
formatted_mac = normalize_mac_for_api(mac_address)
url = f"https://api.macvendors.com/{formatted_mac}"
async with httpx.AsyncClient() as client: async with httpx.AsyncClient() as client:
response = await client.get(url, timeout=10.0) response = await client.get(url, timeout=5.0)
if response.status_code == 200: if response.status_code == 200:
content_type = response.headers.get("content-type", "")
if "application/json" in content_type:
return None
else:
return response.text.strip() return response.text.strip()
except Exception: except Exception:
pass pass
return None return None
def normalize_mac_for_api(mac_address: str) -> str:
"""Normalize MAC address for external API (XX-XX-XX format)"""
clean_mac = re.sub(r'[^a-fA-F0-9]', '', mac_address.upper())
if len(clean_mac) >= 6:
return f"{clean_mac[0:2]}-{clean_mac[2:4]}-{clean_mac[4:6]}"
else:
raise ValueError("Invalid MAC address format")
def normalize_mac_response(mac_address: str) -> str:
"""Normalize MAC address for response format"""
clean_mac = re.sub(r'[^a-fA-F0-9]', '', mac_address.upper())
if len(clean_mac) == 6:
return f"{clean_mac[0:2]}-{clean_mac[2:4]}-{clean_mac[4:6]}"
elif len(clean_mac) == 12:
return f"{clean_mac[0:2]}-{clean_mac[2:4]}-{clean_mac[4:6]}-{clean_mac[6:8]}-{clean_mac[8:10]}-{clean_mac[10:12]}"
else:
raise ValueError("Invalid MAC address format")
def get_oui(mac_address: str) -> str:
"""Extract OUI (first 6 hex digits) from MAC address"""
clean_mac = re.sub(r'[^a-fA-F0-9]', '', mac_address.upper())
if len(clean_mac) >= 6:
return clean_mac[:6]
else:
raise ValueError("MAC address too short")
@app.get("/")
async def root():
return {"message": "MAC Address Vendor Lookup API"}
@app.get("/lookup/{mac_address}") @app.get("/lookup/{mac_address}")
async def lookup_vendor(mac_address: str): async def lookup_mac_vendor(mac_address: str):
try: if not is_valid_mac(mac_address):
oui = get_oui(mac_address) return JSONResponse(
formatted_mac = normalize_mac_response(mac_address) status_code=400,
vendor = None content={
"mac_address": mac_address,
# 1. Try local file first "vendor": None,
if oui in local_mac_vendors: "detail": "Invalid MAC address format"
vendor = local_mac_vendors[oui]
# 2. Try GitHub gist if not found locally
if not vendor:
vendor = await lookup_from_github(oui)
# 3. Try macvendors.com API if still not found
if not vendor:
vendor = await lookup_from_macvendors_api(mac_address)
# Default to Unknown if nothing found
if not vendor:
vendor = "Unknown"
return {
"mac_address": formatted_mac,
"vendor": vendor
} }
)
except ValueError as e: normalized_mac = normalize_mac(mac_address)
raise HTTPException(status_code=400, detail=str(e)) mac_prefix = get_mac_prefix(mac_address)
except Exception as e:
raise HTTPException(status_code=500, detail="Internal server error") vendor = lookup_local_file(mac_prefix)
if not vendor:
vendor = await lookup_remote_gist(mac_prefix)
if not vendor:
vendor = await lookup_remote_api(normalized_mac)
if vendor:
return {
"mac_address": normalized_mac,
"vendor": vendor,
"detail": None
}
else:
return JSONResponse(
status_code=404,
content={
"mac_address": normalized_mac,
"vendor": None,
"detail": "Vendor not found"
}
)
if __name__ == "__main__": if __name__ == "__main__":
import uvicorn import uvicorn