Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
12 changes: 6 additions & 6 deletions src/app.py
Original file line number Diff line number Diff line change
Expand Up @@ -637,9 +637,9 @@ def resolve(note):
item = note.get(chat_read.drive_item_path(ref["contentUrl"], "name,size,file,@microsoft.graph.downloadUrl"))
if (item.get("size") or 0) > MAX_CHAT_FILE_BYTES:
raise HTTPException(status_code=413, detail="The file is larger than 100 MB.")
url = item.get("@microsoft.graph.downloadUrl")
url = teams_files.download_url(note, f"shares/{chat_read.share_id(ref['contentUrl'])}/driveItem", item)
if not url:
raise HTTPException(status_code=404, detail="The file has no download link.")
raise HTTPException(status_code=502, detail="Microsoft did not offer a download link for this file.")
return item, url

carrier = Response()
Expand Down Expand Up @@ -886,9 +886,9 @@ def resolve(note):
raise HTTPException(status_code=404, detail="File not found.")
if (item.get("size") or 0) > teams_files.MAX_DOWNLOAD_BYTES:
raise HTTPException(status_code=413, detail="The file is larger than 100 MB.")
url = item.get("@microsoft.graph.downloadUrl")
url = teams_files.download_url(note, teams_files.item_base(drive_id, item_id), item)
if not url:
raise HTTPException(status_code=404, detail="The file has no download link.")
raise HTTPException(status_code=502, detail="Microsoft did not offer a download link for this file.")
if not teams_files.is_download_url(url):
raise HTTPException(status_code=502, detail="The file has an unexpected download location.")
return item, url
Expand Down Expand Up @@ -938,9 +938,9 @@ def _file_text(note, drive_id: str | None, item_id: str, offset: int, max_chars:
name = posixpath.splitext(name)[0] + ".pdf"
notes.append("Converted to PDF by Microsoft 365 before reading.")
else:
url = item.get("@microsoft.graph.downloadUrl")
url = teams_files.download_url(note, teams_files.item_base(drive_id, item_id), item)
if not url:
raise HTTPException(status_code=404, detail="The file has no download link.")
raise HTTPException(status_code=502, detail="Microsoft did not offer a download link for this file.")
data = teams_files.fetch_bytes(url, file_text.MAX_SOURCE_BYTES)
result = file_text.extract(name, data, offset, max_chars, sheet, row_offset, max_rows, notes)
except file_text.TextError as ex:
Expand Down
15 changes: 15 additions & 0 deletions src/teams_files.py
Original file line number Diff line number Diff line change
Expand Up @@ -76,6 +76,21 @@ def fetch_bytes(url: str, cap: int) -> bytes:
return bytes(out)


def download_url(note, path: str, item: dict) -> str | None:
"""The pre-authorized link of the item at `path`. Graph can leave it out of a `$select` answer, so ask again without one, then through `/content`."""
url = item.get("@microsoft.graph.downloadUrl")
if url:
return url
full = note.get(path)
url = full.get("@microsoft.graph.downloadUrl") if isinstance(full, dict) else None
if url:
return url
try:
return note.redirect(f"{path}/content")
except GraphError:
return None


def item_base(drive_id: str | None, item_id: str) -> str:
return f"drives/{seg(drive_id)}/items/{seg(item_id)}" if drive_id else f"me/drive/items/{seg(item_id)}"

Expand Down
39 changes: 39 additions & 0 deletions tests/test_teams_files.py
Original file line number Diff line number Diff line change
Expand Up @@ -279,3 +279,42 @@ def test_convert_text_body_cap(env, monkeypatch):
monkeypatch.setattr(app_module, "MAX_TEXT_SOURCE_BYTES", 100)
assert client.post("/api/convert/text?name=x.txt", content=b"a" * 101, headers=H).status_code == 413
assert client.post("/api/convert/text?name=x.txt", content=b"a" * 100, headers=H).json()["text"] == "a" * 100


class LinkNote:
def __init__(self, plain=None, redirect=None):
self.plain, self.location, self.paths = plain, redirect, []

def get(self, path):
self.paths.append(path)
return self.plain

def redirect(self, path):
self.paths.append(path)
if self.location is None:
raise teams_files.GraphError(404, "no content")
return self.location


LINK = "https://contoso.sharepoint.com/download?tempauth=x"


def test_download_url_uses_the_link_from_the_select_answer():
note = LinkNote()
assert teams_files.download_url(note, "me/drive/items/I", {"@microsoft.graph.downloadUrl": LINK}) == LINK
assert note.paths == []


def test_download_url_asks_again_without_select():
note = LinkNote(plain={"@microsoft.graph.downloadUrl": LINK})
assert teams_files.download_url(note, "me/drive/items/I", {"name": "Plan.xlsx"}) == LINK


def test_download_url_falls_back_to_the_content_redirect():
note = LinkNote(plain={"name": "Plan.xlsx"}, redirect=LINK)
assert teams_files.download_url(note, "me/drive/items/I", {}) == LINK
assert note.paths == ["me/drive/items/I", "me/drive/items/I/content"]


def test_download_url_is_none_when_microsoft_offers_nothing():
assert teams_files.download_url(LinkNote(plain={}), "me/drive/items/I", {}) is None
Loading