response.hpp 9.0 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274
  1. #pragma once
  2. #include <nall/http/message.hpp>
  3. namespace nall::HTTP {
  4. struct Response : Message {
  5. using type = Response;
  6. Response() = default;
  7. Response(const Request& request) { setRequest(request); }
  8. explicit operator bool() const { return responseType() != 0; }
  9. auto operator()(uint responseType) -> type& { return setResponseType(responseType); }
  10. inline auto head(const function<bool (const uint8_t* data, uint size)>& callback) const -> bool override;
  11. inline auto setHead() -> bool override;
  12. inline auto body(const function<bool (const uint8_t* data, uint size)>& callback) const -> bool override;
  13. inline auto setBody() -> bool override;
  14. auto request() const -> const Request* { return _request; }
  15. auto setRequest(const Request& value) -> type& { _request = &value; return *this; }
  16. auto responseType() const -> uint { return _responseType; }
  17. auto setResponseType(uint value) -> type& { _responseType = value; return *this; }
  18. auto hasData() const -> bool { return (bool)_data; }
  19. auto data() const -> const vector<uint8_t>& { return _data; }
  20. inline auto setData(const vector<uint8_t>& value) -> type&;
  21. auto hasFile() const -> bool { return (bool)_file; }
  22. auto file() const -> const string& { return _file; }
  23. inline auto setFile(const string& value) -> type&;
  24. auto hasText() const -> bool { return (bool)_text; }
  25. auto text() const -> const string& { return _text; }
  26. inline auto setText(const string& value) -> type&;
  27. inline auto hasBody() const -> bool;
  28. inline auto findContentLength() const -> uint;
  29. inline auto findContentType() const -> string;
  30. inline auto findContentType(const string& suffix) const -> string;
  31. inline auto findResponseType() const -> string;
  32. inline auto setFileETag() -> void;
  33. const Request* _request = nullptr;
  34. uint _responseType = 0;
  35. vector<uint8_t> _data;
  36. string _file;
  37. string _text;
  38. };
  39. auto Response::head(const function<bool (const uint8_t*, uint)>& callback) const -> bool {
  40. if(!callback) return false;
  41. string output;
  42. if(auto request = this->request()) {
  43. if(auto eTag = header["ETag"]) {
  44. if(eTag.value() == request->header["If-None-Match"].value()) {
  45. output.append("HTTP/1.1 304 Not Modified\r\n");
  46. output.append("Connection: close\r\n");
  47. output.append("\r\n");
  48. return callback(output.data<uint8_t>(), output.size());
  49. }
  50. }
  51. }
  52. output.append("HTTP/1.1 ", findResponseType(), "\r\n");
  53. for(auto& variable : header) {
  54. output.append(variable.name(), ": ", variable.value(), "\r\n");
  55. }
  56. if(hasBody()) {
  57. if(!header["Content-Length"] && !header["Transfer-Encoding"].value().iequals("chunked")) {
  58. output.append("Content-Length: ", findContentLength(), "\r\n");
  59. }
  60. if(!header["Content-Type"]) {
  61. output.append("Content-Type: ", findContentType(), "\r\n");
  62. }
  63. }
  64. if(!header["Connection"]) {
  65. output.append("Connection: close\r\n");
  66. }
  67. output.append("\r\n");
  68. return callback(output.data<uint8_t>(), output.size());
  69. }
  70. auto Response::setHead() -> bool {
  71. auto headers = _head.split("\n");
  72. string response = headers.takeLeft().trimRight("\r");
  73. if(response.ibeginsWith("HTTP/1.0 ")) response.itrimLeft("HTTP/1.0 ", 1L);
  74. else if(response.ibeginsWith("HTTP/1.1 ")) response.itrimLeft("HTTP/1.1 ", 1L);
  75. else return false;
  76. setResponseType(response.natural());
  77. for(auto& header : headers) {
  78. if(header.beginsWith(" ") || header.beginsWith("\t")) continue;
  79. auto variable = header.split(":", 1L).strip();
  80. if(variable.size() != 2) continue;
  81. this->header.append(variable[0], variable[1]);
  82. }
  83. return true;
  84. }
  85. auto Response::body(const function<bool (const uint8_t*, uint)>& callback) const -> bool {
  86. if(!callback) return false;
  87. if(!hasBody()) return true;
  88. bool chunked = header["Transfer-Encoding"].value() == "chunked";
  89. if(chunked) {
  90. string prefix = {hex(findContentLength()), "\r\n"};
  91. if(!callback(prefix.data<uint8_t>(), prefix.size())) return false;
  92. }
  93. if(_body) {
  94. if(!callback(_body.data<uint8_t>(), _body.size())) return false;
  95. } else if(hasData()) {
  96. if(!callback(data().data(), data().size())) return false;
  97. } else if(hasFile()) {
  98. file_map map(file(), file_map::mode::read);
  99. if(!callback(map.data(), map.size())) return false;
  100. } else if(hasText()) {
  101. if(!callback(text().data<uint8_t>(), text().size())) return false;
  102. } else {
  103. string response = findResponseType();
  104. if(!callback(response.data<uint8_t>(), response.size())) return false;
  105. }
  106. if(chunked) {
  107. string suffix = {"\r\n0\r\n\r\n"};
  108. if(!callback(suffix.data<uint8_t>(), suffix.size())) return false;
  109. }
  110. return true;
  111. }
  112. auto Response::setBody() -> bool {
  113. return true;
  114. }
  115. auto Response::hasBody() const -> bool {
  116. if(auto request = this->request()) {
  117. if(request->requestType() == Request::RequestType::Head) return false;
  118. }
  119. if(responseType() == 301) return false;
  120. if(responseType() == 302) return false;
  121. if(responseType() == 303) return false;
  122. if(responseType() == 304) return false;
  123. if(responseType() == 307) return false;
  124. return true;
  125. }
  126. auto Response::findContentLength() const -> uint {
  127. if(auto contentLength = header["Content-Length"]) return contentLength.value().natural();
  128. if(_body) return _body.size();
  129. if(hasData()) return data().size();
  130. if(hasFile()) return file::size(file());
  131. if(hasText()) return text().size();
  132. return findResponseType().size();
  133. }
  134. auto Response::findContentType() const -> string {
  135. if(auto contentType = header["Content-Type"]) return contentType.value();
  136. if(hasData()) return "application/octet-stream";
  137. if(hasFile()) return findContentType(Location::suffix(file()));
  138. return "text/html; charset=utf-8";
  139. }
  140. auto Response::findContentType(const string& s) const -> string {
  141. if(s == ".7z" ) return "application/x-7z-compressed";
  142. if(s == ".avi" ) return "video/avi";
  143. if(s == ".bml" ) return "text/plain; charset=utf-8";
  144. if(s == ".bz2" ) return "application/x-bzip2";
  145. if(s == ".css" ) return "text/css; charset=utf-8";
  146. if(s == ".gif" ) return "image/gif";
  147. if(s == ".gz" ) return "application/gzip";
  148. if(s == ".htm" ) return "text/html; charset=utf-8";
  149. if(s == ".html") return "text/html; charset=utf-8";
  150. if(s == ".ico" ) return "image/x-icon";
  151. if(s == ".jpg" ) return "image/jpeg";
  152. if(s == ".jpeg") return "image/jpeg";
  153. if(s == ".js" ) return "application/javascript";
  154. if(s == ".mka" ) return "audio/x-matroska";
  155. if(s == ".mkv" ) return "video/x-matroska";
  156. if(s == ".mp3" ) return "audio/mpeg";
  157. if(s == ".mp4" ) return "video/mp4";
  158. if(s == ".mpeg") return "video/mpeg";
  159. if(s == ".mpg" ) return "video/mpeg";
  160. if(s == ".ogg" ) return "audio/ogg";
  161. if(s == ".pdf" ) return "application/pdf";
  162. if(s == ".png" ) return "image/png";
  163. if(s == ".rar" ) return "application/x-rar-compressed";
  164. if(s == ".svg" ) return "image/svg+xml";
  165. if(s == ".tar" ) return "application/x-tar";
  166. if(s == ".txt" ) return "text/plain; charset=utf-8";
  167. if(s == ".wav" ) return "audio/vnd.wave";
  168. if(s == ".webm") return "video/webm";
  169. if(s == ".xml" ) return "text/xml; charset=utf-8";
  170. if(s == ".xz" ) return "application/x-xz";
  171. if(s == ".zip" ) return "application/zip";
  172. return "application/octet-stream"; //binary
  173. }
  174. auto Response::findResponseType() const -> string {
  175. switch(responseType()) {
  176. case 200: return "200 OK";
  177. case 301: return "301 Moved Permanently";
  178. case 302: return "302 Found";
  179. case 303: return "303 See Other";
  180. case 304: return "304 Not Modified";
  181. case 307: return "307 Temporary Redirect";
  182. case 400: return "400 Bad Request";
  183. case 403: return "403 Forbidden";
  184. case 404: return "404 Not Found";
  185. case 500: return "500 Internal Server Error";
  186. case 501: return "501 Not Implemented";
  187. case 503: return "503 Service Unavailable";
  188. }
  189. return "501 Not Implemented";
  190. }
  191. auto Response::setData(const vector<uint8_t>& value) -> type& {
  192. _data = value;
  193. header.assign("Content-Length", value.size());
  194. return *this;
  195. }
  196. auto Response::setFile(const string& value) -> type& {
  197. //block path escalation exploits ("../" and "..\" in the file location)
  198. bool valid = true;
  199. for(uint n : range(value.size())) {
  200. if(value(n + 0, '\0') != '.') continue;
  201. if(value(n + 1, '\0') != '.') continue;
  202. if(value(n + 2, '\0') != '/' && value(n + 2, '\0') != '\\') continue;
  203. valid = false;
  204. break;
  205. }
  206. if(!valid) return *this;
  207. //cache images for seven days
  208. auto suffix = Location::suffix(value);
  209. uint maxAge = 0;
  210. if(suffix == ".svg"
  211. || suffix == ".ico"
  212. || suffix == ".png"
  213. || suffix == ".gif"
  214. || suffix == ".jpg"
  215. || suffix == ".jpeg") {
  216. maxAge = 7 * 24 * 60 * 60;
  217. }
  218. _file = value;
  219. header.assign("Content-Length", file::size(value));
  220. header.assign("ETag", {"\"", chrono::utc::datetime(file::timestamp(value, file::time::modify)), "\""});
  221. if(maxAge == 0) {
  222. header.assign("Cache-Control", {"public"});
  223. } else {
  224. header.assign("Cache-Control", {"public, max-age=", maxAge});
  225. }
  226. return *this;
  227. }
  228. auto Response::setText(const string& value) -> type& {
  229. _text = value;
  230. header.assign("Content-Length", value.size());
  231. return *this;
  232. }
  233. }