11import os
2+ import re
23import json
34import hashlib
45import logging
@@ -159,11 +160,35 @@ async def download(self):
159160
160161 return streams .ResponseStreamReader (response )
161162
163+ @staticmethod
164+ def _is_osf_download_route (url ):
165+ """True when ``url`` is OSF's persistent file download path ``/download/<id>/``."""
166+ path = urlparse (url ).path or ''
167+ osf_download_path_re = re .compile (r'^/download/[^/]+' , re .IGNORECASE )
168+ return bool (osf_download_path_re .match (path ))
169+
170+ async def _fetch_redirect (self , url , headers ):
171+ request = await self ._make_request (
172+ 'GET' ,
173+ url ,
174+ allow_redirects = False ,
175+ headers = headers ,
176+ )
177+ status = request .status
178+ location = request .headers .get ('Location' ) or request .headers .get ('location' )
179+ reason = request .reason
180+ await request .release ()
181+ return location , status , reason
182+
162183 async def _fetch_download_url (self ):
163184 """Provider needs a WaterButler URL to download and get metadata. If ``url`` is already
164185 a WaterButler url, return that. If not, then the url points to an OSF endpoint that will
165186 redirect to WB. Issue a GET request against it, then return the WB url stored in the
166187 Location header.
188+
189+ First ``GET`` may return **301** with ``Location`` for legacy download route. If ``Location``
190+ points at OSF's ``/download/<id>/`` route, perform a second ``GET`` there and expect **302**
191+ to WaterButler. If ``Location`` is not that route (e.g. already WaterButler), use it as ``download_url``.
167192 """
168193 if not self .download_url :
169194 # v1 Waterbutler url provided
@@ -173,26 +198,32 @@ async def _fetch_download_url(self):
173198 self .metrics .add ('download_url.orig_type' , 'wb_v1' )
174199 else :
175200 self .metrics .add ('download_url.orig_type' , 'osf' )
176- # make request to osf, don't follow, store waterbutler download url
177- request = await self ._make_request (
178- 'GET' ,
179- self .url ,
180- allow_redirects = False ,
181- headers = {
182- 'Content-Type' : 'application/json'
183- }
184- )
185- await request .release ()
201+ # make request to osf, store waterbutler download url
202+ headers = {'Content-Type' : 'application/json' }
186203
187- logger .debug (f'osf-download-resolver: request.status::{ request .status } ' )
188- if request .status != 302 :
204+ location , status , reason = await self ._fetch_redirect (self .url , headers )
205+ logger .debug (f'osf-download-resolver: url={ self .url } status={ status } location={ location } ' )
206+ if status not in (301 , 302 ) or not location :
189207 raise exceptions .MetadataError (
190- request . reason ,
208+ reason ,
191209 metadata_url = self .url ,
192210 provider = self .NAME ,
193- code = request . status ,
211+ code = status ,
194212 )
195- self .download_url = request .headers ['location' ]
213+
214+ if self ._is_osf_download_route (location ):
215+ location , status , reason = await self ._fetch_redirect (location , headers )
216+
217+ logger .debug (f'osf-download-resolver follow /download/: url={ location } status={ status } location={ location } ' )
218+ if status != 302 or not location :
219+ raise exceptions .MetadataError (
220+ reason ,
221+ metadata_url = location ,
222+ provider = self .NAME ,
223+ code = status ,
224+ )
225+
226+ self .download_url = location
196227
197228 self .metrics .add ('download_url.derived_url' , str (self .download_url ))
198229
0 commit comments