1 /** 2 * Модуль реализации Middleware CORS 3 * 4 * Copyright: (c) 2015-2020, Milofon Project. 5 * License: Subject to the terms of the BSD 3-Clause License, as written in the included LICENSE.md file. 6 * Author: <m.galanin@milofon.pro> Maksim Galanin 7 * Date: 2020-04-30 8 */ 9 10 module dango.web.middlewares.cors; 11 12 private 13 { 14 import std.algorithm.iteration : splitter, filter; 15 import std.algorithm.searching : canFind; 16 import std.uni : toLower, toUpper; 17 import std.array : array, join; 18 import std.conv : to; 19 20 import dango.inject : DependencyContainer; 21 import dango.system.exception : enforceConfig; 22 import dango.system.properties; 23 import dango.web.middleware; 24 } 25 26 27 /// Проверка 28 alias AllowChecker = bool delegate(string val) @safe; 29 30 31 /** 32 * Middleware позволяет реализовать CORS доступ 33 */ 34 class CORSWebMiddleware : WebMiddleware 35 { 36 private 37 { 38 AllowChecker _originChecker; 39 AllowChecker _methodChecker; 40 AllowChecker _headerChecker; 41 ulong _maxAge; 42 } 43 44 /** 45 * Main constructor 46 */ 47 this(AllowChecker oCk, AllowChecker mCk, AllowChecker hCk, long maxAge) @safe 48 { 49 this._originChecker = oCk; 50 this._methodChecker = mCk; 51 this._headerChecker = hCk; 52 this._maxAge = maxAge; 53 } 54 55 /** 56 * Обработка запроса 57 */ 58 void handleRequest(scope HTTPServerRequest req, scope HTTPServerResponse res, 59 HTTPServerRequestDelegate next) @safe 60 { 61 auto origin = "Origin" in req.headers; 62 63 if (origin && _originChecker(*origin)) 64 { 65 res.headers["Access-Control-Allow-Origin"] = *origin; 66 res.headers["Access-Control-Allow-Credentials"] = "true"; 67 } 68 69 next(req, res); 70 } 71 72 /** 73 * Регистрация цепочек маршрутов middleware 74 */ 75 void registerHandlers(HTTPMethod method, string path, RegisterHandlerCallback dg) @safe 76 { 77 dg(HTTPMethod.OPTIONS, path, &handleOptionsRequest); 78 } 79 80 81 private: 82 83 84 void handleOptionsRequest(scope HTTPServerRequest req, scope HTTPServerResponse res) @safe 85 { 86 auto origin = "Origin" in req.headers; 87 88 if (origin && _originChecker(*origin)) 89 { 90 res.headers["Access-Control-Allow-Origin"] = *origin; 91 res.headers["Access-Control-Allow-Credentials"] = "true"; 92 93 res.headers["Access-Control-Max-Age"] = _maxAge.to!string; 94 95 auto method = "Access-Control-Request-Method" in req.headers; 96 if (method && _methodChecker(*method)) 97 res.headers["Access-Control-Allow-Methods"] = *method; 98 99 if (auto headers = "Access-Control-Request-Headers" in req.headers) 100 { 101 auto hds = splitter(*headers, ",").filter!(h=> _headerChecker(h)); 102 res.headers["Access-Control-Allow-Headers"] = hds.join(","); 103 } 104 } 105 106 res.writeBody(""); 107 } 108 } 109 110 111 /** 112 * Фабрика Middleware CORS 113 */ 114 class CORSWebMiddlewareFactory : WebMiddlewareFactory 115 { 116 WebMiddleware createComponent(DependencyContainer cont, UniConf config) @safe 117 { 118 AllowChecker oCk = createOriginChecker(config.toSequence("origin")); 119 AllowChecker mCk = createMethodChecker(config.toSequence("method")); 120 AllowChecker hCk = createHeaderChecker(config.toSequence("header")); 121 long maxAge = config.getOrElse!long("maxAge", 300); 122 return new CORSWebMiddleware(oCk, mCk, hCk, maxAge); 123 } 124 125 126 private: 127 128 129 AllowChecker createOriginChecker(UniConf[] origins) @safe 130 { 131 import std.regex; 132 133 enum replaceRx = ctRegex!(`\\\*`); 134 Regex!char[] regs; 135 136 foreach (UniConf origin; origins) 137 { 138 auto origStr = origin.opt!string(); 139 enforceConfig(!origStr.isNull, "Origin must be a string"); 140 141 string escapeOrig = origStr.get.escaper.array.to!string; 142 string repOrig = escapeOrig.replaceAll(replaceRx, "(.*?)"); 143 string regexOrig = "^" ~ repOrig ~ "$"; 144 145 regs ~= regex(regexOrig); 146 } 147 148 return (string val) 149 { 150 foreach (re; regs) 151 { 152 if (!match(val, re).empty) 153 return true; 154 } 155 return false; 156 }; 157 } 158 159 160 AllowChecker createMethodChecker(UniConf[] methodProps) @safe 161 { 162 string[] methods; 163 164 foreach (UniConf mp; methodProps) 165 { 166 auto m = mp.opt!string(); 167 enforceConfig(!m.isNull, "Method must be a string"); 168 methods ~= m.get.toUpper; 169 } 170 171 return (string val) 172 { 173 return methods.canFind(val.toUpper); 174 }; 175 } 176 177 178 AllowChecker createHeaderChecker(UniConf[] headerProps) @safe 179 { 180 string[] headers; 181 182 foreach (UniConf hp; headerProps) 183 { 184 auto h = hp.opt!string(); 185 enforceConfig(!h.isNull, "Header must be a string"); 186 headers ~= h.get.toLower; 187 } 188 189 return (string val) 190 { 191 return headers.canFind(val.toLower); 192 }; 193 } 194 } 195