openwrt / asu

An image on demand server for OpenWrt based distributions
https://sysupgrade.openwrt.org
GNU General Public License v2.0
327 stars 82 forks source link

Incorrect Content-Type results in firmware files opening in the browser #1059

Open 0nelight opened 2 days ago

0nelight commented 2 days ago

Download-Links to a notable amount of built Images via the Browser-Interface of the public Firmware-Selector-Server on the public ASU are being advertised as content-type: text/stream; charset=utf-8 instead of from content-type: application/octet-stream the Webserver running on ASU.

This leads to the Issue where, when clicking on the Download-Button, instead of Firefox opening the Download-File-Dialog, it interprets the File as a Webpage and tries to render it. Workaround is clicking "Save Target as".

I am not sure if I should put this here or at firmware-selector-openwrt-org - therefore I created a Ticket there too.

While Chrome Browser seems to take care of this misconfiguration, Firefox doesn't.

AliveDevil commented 1 day ago

Context chain:

All that is to say: ASU should probably register mimetypes.add_type("application/octet-stream", ".img"). Godbolt sample%0Amimetypes.add_type(%22application/octet-stream%22,+%22.img%22)%0Aprint(%22After+Register+.img%22,+mimetypes.guess_type(!'name.img.gz!'))'),l:'5',n:'1',o:'Python+source+%231',t:'0')),k:50,l:'4',n:'0',o:'',s:0,t:'0'),(g:!((h:executor,i:(argsPanelShown:'1',compilationPanelShown:'0',compiler:python313,compilerName:'',compilerOutShown:'0',execArgs:'',execStdin:'',fontScale:14,fontUsePx:'0',j:1,lang:python,libs:!(),options:'',overrides:!(),runtimeTools:!(),source:1,stdinPanelShown:'1',wrap:'0'),l:'5',n:'0',o:'Executor+Python+3.13+(Python,+Editor+%231)',t:'0')),k:50,l:'4',n:'0',o:'',s:0,t:'0')),l:'2',n:'0',o:'',t:'0')),version:4)

0nelight commented 1 day ago

@AliveDevil So mimetypes not recognizing ".img" is one thing - but in my example the file ending is ".gz". It seems guess_type reads the mime-types from system files, which maybe are missing in the docker image?

python:3.6.4-slim can not guess extension well

AliveDevil commented 1 day ago

Check the godbolt output: Python can guess encoding and file type separately. Any .X.Y file is checked against the encoding list of file extensions (i.e. gz, xz, bz2) - this is ignored and dropped by starlette. So a .jpg.bz2 would be (application/jpeg, BZip).

Starlette only cares about the guessed type: in this case .img evaluates to None, which is then falled back to text/plain.

This could be fixed by editing the mime.types file, or just fixed in application code, which would be less error-prone for users.

0nelight commented 1 day ago

So this would mean we have to do mimetypes.add_type for other File Extensions too.

I queried the firmware names and found the following non-duplicated list:

Base Name Extension
netgear_wndap620-squashfs-factory.img img
netgear_wndr4700-device-tree.dtb dtb
wd_mybooklive-ext4-factory.img.gz img; gz
atmel_at91sam9263ek-ext4-root.ubi.gz ubi; gz
atmel_at91sam9263ek-fit-zImage.itb itb
atmel_at91sam9263ek-squashfs-root.ubi ubi
atmel_at91sam9g15ek-uImage
engenius_epg5000-squashfs-factory.dlf dlf
standard-squashfs.trx trx
netgear_r6200-v1-squashfs.chk chk
luxul_abr-4500-squashfs.lxl lxl
gateworks_ventana-squashfs-img.gz gz
toradex_apalis-recovery.scr scr
google_wifi-initramfs-fit-zImage.itb.vboot itb; vboot
netgear_wax218-web-ui-factory.fit fit
ctera_c200-v1-initramfs-factory.firm firm
iom_iconnect-1.1-initramfs-uImage 1-initramfs-uImage
vmlinux-initramfs.elf elf
bananapi_bpi-r3-emmc-bl31-uboot.fip fip
wevo_air-duo-initramfs.upload upload
netgear_gs750e-squashfs-factory.bix bix

So because anyway ASU provides Firmware-Files always it should always set content-type: application/octet-stream. But because the File Extensions are not a limited set, setting it with mimetypes.add_type() would be not reliable.

Maybe something along these lines?

class FirmwareFiles(StaticFiles):
    async def get_response(self, path: str, scope):
        response = await super().get_response(path, scope)
        response.headers["Content-Type"] = "application/octet-stream"
        return response

app.mount("/store", FirmwareFiles(directory=settings.public_path / "store"), name="store")