Advertisement
Inquisitor

Implementation of RFC 7233 (HTTP Range Requests)

Sep 15th, 2015
141
0
Never
Not a member of Pastebin yet? Sign Up, it unlocks many cool features!
Batch 6.22 KB | None | 0 0
  1. @Echo Off
  2. Set DB=%__ServerRoot%\data\uploads.db
  3.  
  4. Set Data=%*
  5. :Next
  6. For /F "tokens=1,* delims=;" %%A In ("%Data%") Do (
  7.     For /F "tokens=1,2 delims==" %%C In ("%%A") Do (Set $%%C=%%D)
  8.     If Not "%%B"=="" Set Data=%%B&GoTo :Next
  9. )
  10.  
  11. :: Проверки
  12. If Not Defined $key (Set "Message=Не указан ключ" & Call :Die 403)
  13. If Not Exist "%~dp0%$key%\." (Set "Message=Этот файл приватный и ваш ключ неправильный" & Call :Die 403)
  14.  
  15. :: Информация о файле
  16. For /F "eol= tokens=1-3 delims=|" %%A In ('Echo SELECT `filename`^, `filesize_bytes`^, `timestamp` FROM `uploads` WHERE `key` like "%$key%"^;^|sqlite3 "%DB%"') Do (
  17.     Set "name=%%A"
  18.     Set "size=%%B"
  19.     Set "timestamp=%%C"
  20. )
  21.  
  22. :: Проверки
  23. If Not Exist "%~dp0%$key%\%name%" (Set "Message=Файл не найден" & Call :Die 404)
  24.  
  25. :: Меняем кодировку для имени файла, на случай если он содержит кириллицу
  26. Set "_$name=%name%"
  27. For /F "eol= tokens=1,* delims==" %%A In ('Set _$name^|recode cp866..utf8') Do Set "nameUTF8=%%B"
  28. :: Конвертируем время для Last-Modified
  29. For /F "delims=" %%A In ('UnixTime2HTTPDate %timestamp%') Do (Set timestamp=%%A)
  30. :: Получаем текущее время
  31. For /F "delims=" %%A In ('GetHTTPDate') Do (Set datetime=%%A)
  32. :: Получаем MIME-тип из расширения
  33. For /F "delims=" %%A In ('GetMIMEType "%name%"') Do Set "mimetype=%%A"
  34.  
  35. :: Если указан Range переходим к обработчику запроса
  36. If Defined H-Range GoTo :Range
  37.  
  38. :: Отдаём заголовки и файл, если не поступало запросов на частичную отдачу
  39. Echo HTTP/1.0 200 OK
  40. Echo Server: OWS/%__OWS_version%
  41. Echo Date: %datetime%
  42. Echo Accept-Ranges: bytes
  43. Echo Content-Type: %mimetype%
  44. Echo Content-Length: %size%
  45. Echo Content-Disposition: attachment; filename="%nameUTF8%"; size=%size%; creation-date=%timestamp%; modification-date=%timestamp%; read-date=%datetime%;
  46. Echo Date: %datetime%
  47. Echo Last-Modified: %timestamp%
  48. Echo Connection: close
  49. Echo.
  50. Type "%~dp0%$key%\%name%"
  51. Exit
  52.  
  53. :Range
  54. SetLocal EnableDelayedExpansion
  55. :: Случайная строка для использования в качестве разделителя
  56. For /F "delims=" %%A In ('RandomID 16') Do (Set boundary=%%A)
  57. :: Максимальное значение диапазона - размер-1байт
  58. For /F "delims=" %%- In ('cc %size%-1') Do Set MaxRange=%%-
  59. :: Имя временного файла, используемого если в одном запросе несколько диапазонов
  60. Set TmpFile="%Temp%\~part%boundary%.tmp"
  61. :: Выделяем диапазоны из заголовка, если тип не bytes - отдаём HTTP 416
  62. For /F "tokens=1,* delims==" %%A In ("%H-Range%") Do If /I "%%A"=="bytes" (Set "Ranges=%%B") Else (Call :Err416)
  63. Set Parts=0
  64. For %%A In (%Ranges%) Do (
  65.     Set rejected=false
  66.     Set Range="%%A"
  67.     For /F "tokens=1,2 delims=-" %%B In ("!Range:-="-"!") Do (
  68.         Set "rMin=%%~B"
  69.         Set "rMax=%%~C"
  70.         :: Разворачиваем диапазоны с пустыми значениями
  71.         If "!rMax!"=="" Set rMax=%MaxRange%
  72.         cc !rMax! ^>= %size%|ExitCode&&Set rMax=%MaxRange%
  73.         If "!rMin!"=="" For /F "delims=" %%- In ('cc %MaxRange%-!rMax!+1') Do (
  74.             Set rMax=%MaxRange%
  75.             Set rMin=%%-
  76.         )
  77.     )
  78.     :: Проверки
  79.     If Not Defined rMin Set rejected=true
  80.     If Not Defined rMax Set rejected=true
  81.     cc !rMin! ^<= !rMax!|ExitCode||Set rejected=true
  82.     :: Если диапазон проходит по всем условиям, записываем аргументы для функции, выдающей часть файла
  83.     If "!rejected!"=="false" (
  84.         Set /A Parts+=1
  85.         Set $Range=!rMin!-!rMax!/%size%
  86.         Set Skip=!rMin!
  87.         For /F "delims=" %%- In ('cc !rMax!+1-!rMin!') Do Set Length=%%-
  88.         Set Part[!Parts!]="!Skip!" "!Length!" "!$Range!" "%boundary%" "!Parts!"
  89.     )
  90.  
  91. )
  92. :: Есть заголовок Range, но ни один из диапазонов невалиден? Отдаём HTTP 416
  93. If "%Parts%"=="0" Call :Err416
  94. :: Начинаем отдавать заголовок
  95.     Echo HTTP/1.1 206 Partial Content
  96.     Echo Server: OWS/%__OWS_version%
  97.     Echo Date: %datetime%
  98.     Echo Last-Modified: %timestamp%
  99.     Echo Accept-Ranges: bytes
  100. If "%Parts%"=="1" (
  101.     Echo Content-Type: %mimetype%
  102. ) Else (
  103.     Echo Content-Type: multipart/byteranges; boundary=%boundary%
  104. )
  105. If "%Parts%"=="1" (
  106.     Echo Content-Length: !Length!
  107.     Echo Content-Range: bytes !rMin!-!rMax!/%size%
  108.     Echo.
  109.     Call :PartialContent %Part[1]% "1"
  110. ) Else (
  111.     For /L %%A In (1,1,!Parts!) Do (
  112.         Call :PartialContent !Part[%%A]! !Parts!>>%TmpFile%
  113.     )
  114.     :: Подсчитываем размер получившихся частей ВМЕСТЕ с заголовками и разделителями
  115.     Call :PartsSize %TmpFile%
  116.     Echo.
  117. rem Выводим контент и удаляем временный файл
  118.     Type "%TmpFile%"
  119.     Del "%TmpFile%" 2>nul
  120. )
  121. EndLocal
  122. Exit
  123.  
  124. :: Выдаём часть файла согласно аргументам
  125. :PartialContent (skip, length, range, boundary, part, parts)
  126. If "%__Debug%"=="true" (
  127.     Echo [%Time%] %~nx0: %H-REQUEST_METHOD% %H-REQUEST_URI%  Range: %H-Range%
  128.     Echo Skip: "%~1", Length: "%~2", Range: "%~3" --%~4-- [%~5 of %~6]
  129. )>>"%__DebugLogFile%"
  130. :: Костыль для tail
  131. For /F "delims=" %%+ In ('cc %~1+1') Do Set tSkip=%%+
  132. If "%~6"=="1" (
  133.     tail --bytes=+!tSkip! "%~dp0%$key%\%name%!!|%~2
  134.     Exit /B
  135. )
  136. Echo --%~4
  137. Echo Content-Type: %mimetype%
  138. Echo Content-Range: bytes %~3
  139. Echo.
  140. tail --bytes=+!tSkip! "%~dp0%$key%\%name%!!|%~2
  141. If "%~5"=="%~6" <nul Set /P "Echo=--%~4--"
  142. Exit /B
  143.  
  144. :PartsSize (file)
  145. For %%A In ("%~1") Do Echo Content-Length: %%~zA
  146. Exit /B
  147.  
  148. :Err416
  149.     Echo HTTP/1.1 416 Range Not Satisfiable
  150.     Echo Server: OWS/%__OWS_version%
  151.     Echo Date: %datetime%
  152.     Echo Accept-Ranges: bytes
  153.     Echo Content-Range: bytes */%size%
  154.     Echo.
  155. Exit
  156.  
  157. :Die
  158. If "%~1"=="403" Echo HTTP/1.0 403 Forbidden
  159. If "%~1"=="404" Echo HTTP/1.0 404 Not Found
  160.     Echo Server: OWS/%__OWS_version%
  161.     Echo Content-Type: text/html;charset=UTF-8
  162.     Echo Connection: close
  163.     Echo.
  164.     Echo.%Message%
  165. Exit
Advertisement
Add Comment
Please, Sign In to add comment
Advertisement