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()
for line in response.text.strip().split('\n'):
if line.strip():
parts = line.split('\t', 1)
if len(parts) == 2 and parts[0].strip().upper() == oui:
return parts[1].strip()
except Exception:
pass
return None
async def lookup_from_macvendors_api(mac_address: str) -> Optional[str]:
"""Lookup vendor from macvendors.com API"""
try:
formatted_mac = normalize_mac_for_api(mac_address)
url = f"https://api.macvendors.com/{formatted_mac}"
async with httpx.AsyncClient() as client:
response = await client.get(url, timeout=10.0)
if response.status_code == 200: if response.status_code == 200:
content_type = response.headers.get("content-type", "") for line in response.text.split('\n'):
if "application/json" in content_type: line = line.strip()
return None if line and ' ' in line:
else: prefix, vendor = line.split(' ', 1)
return response.text.strip() if prefix.upper() == mac_prefix:
return vendor
except Exception: except Exception:
pass pass
return None return None
def normalize_mac_for_api(mac_address: str) -> str: async def lookup_remote_api(normalized_mac: str) -> str:
"""Normalize MAC address for external API (XX-XX-XX format)""" mac_for_api = normalized_mac
clean_mac = re.sub(r'[^a-fA-F0-9]', '', mac_address.upper()) if len(mac_for_api.replace('-', '')) == 6:
if len(clean_mac) >= 6: mac_for_api = mac_for_api + '-00-00-00'
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: url = f"https://api.macvendors.com/{mac_for_api}"
"""Normalize MAC address for response format"""
clean_mac = re.sub(r'[^a-fA-F0-9]', '', mac_address.upper())
if len(clean_mac) == 6: try:
return f"{clean_mac[0:2]}-{clean_mac[2:4]}-{clean_mac[4:6]}" async with httpx.AsyncClient() as client:
elif len(clean_mac) == 12: response = await client.get(url, timeout=5.0)
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]}" if response.status_code == 200:
else: return response.text.strip()
raise ValueError("Invalid MAC address format") except Exception:
pass
def get_oui(mac_address: str) -> str: return None
"""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,
"vendor": None,
"detail": "Invalid MAC address format"
}
)
# 1. Try local file first normalized_mac = normalize_mac(mac_address)
if oui in local_mac_vendors: mac_prefix = get_mac_prefix(mac_address)
vendor = local_mac_vendors[oui]
# 2. Try GitHub gist if not found locally vendor = lookup_local_file(mac_prefix)
if not vendor:
vendor = await lookup_from_github(oui)
# 3. Try macvendors.com API if still not found if not vendor:
if not vendor: vendor = await lookup_remote_gist(mac_prefix)
vendor = await lookup_from_macvendors_api(mac_address)
# Default to Unknown if nothing found if not vendor:
if not vendor: vendor = await lookup_remote_api(normalized_mac)
vendor = "Unknown"
if vendor:
return { return {
"mac_address": formatted_mac, "mac_address": normalized_mac,
"vendor": vendor "vendor": vendor,
"detail": None
} }
else:
except ValueError as e: return JSONResponse(
raise HTTPException(status_code=400, detail=str(e)) status_code=404,
except Exception as e: content={
raise HTTPException(status_code=500, detail="Internal server error") "mac_address": normalized_mac,
"vendor": None,
"detail": "Vendor not found"
}
)
if __name__ == "__main__": if __name__ == "__main__":
import uvicorn import uvicorn