| 1 | // Copyright (c) .NET Foundation and contributors. All rights reserved. Licensed under the Microsoft Reciprocal License. See LICENSE.TXT file in the project root for full license information. |
| 2 | |
| 3 | #include "precomp.h" |
| 4 | |
| 5 | enum eHttpHeaderQuery { hhqName = 1, hhqParentType, hhqParentValue, hhqValue, hhqAttributes}; |
| 6 | |
| 7 | static HRESULT AddHttpHeaderToList( |
| 8 | __in SCA_HTTP_HEADER** ppshhList |
| 9 | ); |
| 10 | |
| 11 | |
| 12 | void ScaHttpHeaderFreeList( |
| 13 | __in SCA_HTTP_HEADER* pshhList |
| 14 | ) |
| 15 | { |
| 16 | SCA_HTTP_HEADER* pshhDelete = pshhList; |
| 17 | while (pshhList) |
| 18 | { |
| 19 | pshhDelete = pshhList; |
| 20 | pshhList = pshhList->pshhNext; |
| 21 | |
| 22 | MemFree(pshhDelete); |
| 23 | } |
| 24 | } |
| 25 | |
| 26 | |
| 27 | HRESULT ScaHttpHeaderRead( |
| 28 | __in SCA_HTTP_HEADER** ppshhList, |
| 29 | __inout LPWSTR *ppwzCustomActionData |
| 30 | ) |
| 31 | { |
| 32 | Assert(ppshhList); |
| 33 | |
| 34 | HRESULT hr = S_OK; |
| 35 | MSIHANDLE hRec; |
| 36 | LPWSTR pwzData = NULL; |
| 37 | SCA_HTTP_HEADER* pshh = NULL; |
| 38 | WCA_WRAPQUERY_HANDLE hWrapQuery = NULL; |
| 39 | |
| 40 | hr = WcaBeginUnwrapQuery(&hWrapQuery, ppwzCustomActionData); |
| 41 | ExitOnFailure(hr, "Failed to unwrap query for ScaAppPoolRead"); |
| 42 | |
| 43 | if (0 == WcaGetQueryRecords(hWrapQuery)) |
| 44 | { |
| 45 | WcaLog(LOGMSG_VERBOSE, "Skipping ScaHttpHeaderRead() - required tables not present."); |
| 46 | ExitFunction1(hr = S_FALSE); |
| 47 | } |
| 48 | |
| 49 | // loop through all the HTTP headers |
| 50 | while (S_OK == (hr = WcaFetchWrappedRecord(hWrapQuery, &hRec))) |
| 51 | { |
| 52 | hr = AddHttpHeaderToList(ppshhList); |
| 53 | ExitOnFailure(hr, "failed to add http header to list"); |
| 54 | |
| 55 | pshh = *ppshhList; |
| 56 | |
| 57 | hr = WcaGetRecordInteger(hRec, hhqParentType, &(pshh->iParentType)); |
| 58 | ExitOnFailure(hr, "failed to get IIsHttpHeader.ParentType"); |
| 59 | |
| 60 | hr = WcaGetRecordString(hRec, hhqParentValue, &pwzData); |
| 61 | ExitOnFailure(hr, "Failed to get IIsHttpHeader.ParentValue"); |
| 62 | hr = ::StringCchCopyW(pshh->wzParentValue, countof(pshh->wzParentValue), pwzData); |
| 63 | ExitOnFailure(hr, "Failed to copy IIsHttpHeader.ParentValue"); |
| 64 | |
| 65 | hr = WcaGetRecordString(hRec, hhqName, &pwzData); |
| 66 | ExitOnFailure(hr, "Failed to get IIsHttpHeader.Name"); |
| 67 | hr = ::StringCchCopyW(pshh->wzName, countof(pshh->wzName), pwzData); |
| 68 | ExitOnFailure(hr, "Failed to copy IIsHttpHeader.Name"); |
| 69 | |
| 70 | hr = WcaGetRecordString(hRec, hhqValue, &pwzData); |
| 71 | ExitOnFailure(hr, "Failed to get IIsHttpHeader.Value"); |
| 72 | hr = ::StringCchCopyW(pshh->wzValue, countof(pshh->wzValue), pwzData); |
| 73 | ExitOnFailure(hr, "Failed to copy IIsHttpHeader.Value"); |
| 74 | |
| 75 | hr = WcaGetRecordInteger(hRec, hhqAttributes, &(pshh->iAttributes)); |
| 76 | ExitOnFailure(hr, "failed to get IIsHttpHeader.Attributes"); |
| 77 | } |
| 78 | |
| 79 | if (E_NOMOREITEMS == hr) |
| 80 | { |
| 81 | hr = S_OK; |
| 82 | } |
| 83 | ExitOnFailure(hr, "Failure while processing web errors"); |
| 84 | |
| 85 | LExit: |
| 86 | WcaFinishUnwrapQuery(hWrapQuery); |
| 87 | |
| 88 | ReleaseStr(pwzData); |
| 89 | |
| 90 | return hr; |
| 91 | } |
| 92 | |
| 93 | |
| 94 | HRESULT ScaGetHttpHeader( |
| 95 | __in int iParentType, |
| 96 | __in LPCWSTR wzParentValue, |
| 97 | __in SCA_HTTP_HEADER** ppshhList, |
| 98 | __out SCA_HTTP_HEADER** ppshhOut |
| 99 | ) |
| 100 | { |
| 101 | HRESULT hr = S_OK; |
| 102 | SCA_HTTP_HEADER* pshhAdd = NULL; |
| 103 | SCA_HTTP_HEADER* pshhLast = NULL; |
| 104 | |
| 105 | *ppshhOut = NULL; |
| 106 | |
| 107 | if (!*ppshhList) |
| 108 | { |
| 109 | return hr; |
| 110 | } |
| 111 | |
| 112 | SCA_HTTP_HEADER* pshh = *ppshhList; |
| 113 | while (pshh) |
| 114 | { |
| 115 | if (iParentType == pshh->iParentType && CSTR_EQUAL == ::CompareStringW(LOCALE_SYSTEM_DEFAULT, 0, wzParentValue, -1, pshh->wzParentValue, -1)) |
| 116 | { |
| 117 | // Found a match, take this one out of the list and add it to the matched out list |
| 118 | pshhAdd = pshh; |
| 119 | |
| 120 | if (pshhLast) |
| 121 | { |
| 122 | // If we're not at the beginning of the list tell the last node about it's new next (since we're taking away it's current next) |
| 123 | pshhLast->pshhNext = pshhAdd->pshhNext; |
| 124 | } |
| 125 | else |
| 126 | { |
| 127 | // If we are at the beginning (no pshhLast) update the beginning (since we're taking it) |
| 128 | *ppshhList = pshh->pshhNext; |
| 129 | } |
| 130 | pshh = pshh->pshhNext; // move on |
| 131 | |
| 132 | // Add the one we've removed to the beginning of the out list |
| 133 | pshhAdd->pshhNext = *ppshhOut; |
| 134 | *ppshhOut = pshhAdd; |
| 135 | } |
| 136 | else |
| 137 | { |
| 138 | pshhLast = pshh; // remember the last we that didn't match |
| 139 | pshh = pshh->pshhNext; // move on |
| 140 | } |
| 141 | } |
| 142 | |
| 143 | return hr; |
| 144 | } |
| 145 | |
| 146 | |
| 147 | HRESULT ScaWriteHttpHeader( |
| 148 | __in IMSAdminBase* piMetabase, |
| 149 | __in LPCWSTR wzRoot, |
| 150 | __in SCA_HTTP_HEADER* pshhList |
| 151 | ) |
| 152 | { |
| 153 | Assert(piMetabase && pshhList); |
| 154 | |
| 155 | HRESULT hr = S_OK; |
| 156 | METADATA_RECORD mr = { 0 }; |
| 157 | DWORD cchData = 0; |
| 158 | LPWSTR pwzSearchKey = NULL; |
| 159 | LPWSTR pwz = NULL; |
| 160 | LPWSTR pwzHeaders = NULL; |
| 161 | LPWSTR pwzNewHeader = NULL; |
| 162 | DWORD_PTR dwFoundHeaderIndex = 0; |
| 163 | LPCWSTR wzFoundHeader = NULL; |
| 164 | BOOL fOldValueFound = FALSE; |
| 165 | |
| 166 | ExitOnNull(wzRoot, hr, E_INVALIDARG, "Failed to write HTTP header, because no root was provided"); |
| 167 | |
| 168 | Assert(*wzRoot); |
| 169 | |
| 170 | // Check if HTTP header already exist here. |
| 171 | mr.dwMDIdentifier = MD_HTTP_CUSTOM; |
| 172 | mr.dwMDAttributes = METADATA_INHERIT; |
| 173 | mr.dwMDUserType = IIS_MD_UT_SERVER; |
| 174 | mr.dwMDDataType = ALL_METADATA; |
| 175 | mr.dwMDDataLen = cchData = 0; |
| 176 | mr.pbMDData = NULL; |
| 177 | |
| 178 | hr = MetaGetValue(piMetabase, METADATA_MASTER_ROOT_HANDLE, wzRoot, &mr); |
| 179 | if (HRESULT_FROM_WIN32(ERROR_PATH_NOT_FOUND) == hr || MD_ERROR_DATA_NOT_FOUND == hr) |
| 180 | { |
| 181 | // |
| 182 | // If we don't have any HTTP Headers already, move up to get the parent headers. |
| 183 | // TODO: Make it configurable to not inherit HTTP Headers |
| 184 | // |
| 185 | hr = StrAllocConcat(&pwzSearchKey, wzRoot, 0); |
| 186 | ExitOnFailure(hr, "Failed to copy root string: %ls", wzRoot); |
| 187 | |
| 188 | pwz = pwzSearchKey + lstrlenW(pwzSearchKey); |
| 189 | while (NULL == pwzHeaders) |
| 190 | { |
| 191 | // find the last slash |
| 192 | while (*pwz != '/' && pwz != pwzSearchKey) |
| 193 | { |
| 194 | --pwz; |
| 195 | } |
| 196 | |
| 197 | if (pwz == pwzSearchKey) |
| 198 | { |
| 199 | break; |
| 200 | } |
| 201 | |
| 202 | *pwz = L'\0'; |
| 203 | |
| 204 | // Try here. If it's not found, keep walking up the path |
| 205 | hr = MetaGetValue(piMetabase, METADATA_MASTER_ROOT_HANDLE, pwzSearchKey, &mr); |
| 206 | if (HRESULT_FROM_WIN32(ERROR_PATH_NOT_FOUND) == hr || MD_ERROR_DATA_NOT_FOUND == hr) |
| 207 | { |
| 208 | hr = S_FALSE; |
| 209 | } |
| 210 | ExitOnFailure(hr, "Failed to find search for HTTP headers for web root: %ls while walking up the tree", wzRoot); |
| 211 | |
| 212 | if (S_OK == hr) |
| 213 | { |
| 214 | hr = StrAllocString(&pwzHeaders, reinterpret_cast<LPWSTR>(mr.pbMDData), 0); |
| 215 | ExitOnFailure(hr, "Failed to allocate parent HTTP header string"); |
| 216 | break; |
| 217 | } |
| 218 | } |
| 219 | } |
| 220 | else |
| 221 | { |
| 222 | hr = StrAllocString(&pwzHeaders, reinterpret_cast<LPWSTR>(mr.pbMDData), 0); |
| 223 | ExitOnFailure(hr, "Failed to allocate HTTP header string"); |
| 224 | } |
| 225 | ExitOnFailure(hr, "Failed while searching for default HTTP headers to start with for web root: %ls", wzRoot); |
| 226 | |
| 227 | // Loop through the HTTP headers |
| 228 | for (SCA_HTTP_HEADER* pshh = pshhList; pshh; pshh = pshh->pshhNext) |
| 229 | { |
| 230 | fOldValueFound = FALSE; // assume a HTTP Header match will not be found |
| 231 | |
| 232 | hr = StrAllocFormatted(&pwzNewHeader, L"%s: ", pshh->wzName); |
| 233 | ExitOnFailure(hr, "Failed to allocate header name"); |
| 234 | |
| 235 | if (NULL != pwzHeaders && *pwzHeaders) |
| 236 | { |
| 237 | // Try to find a matching header already in the list |
| 238 | hr = MultiSzFindSubstring(pwzHeaders, pwzNewHeader, &dwFoundHeaderIndex, &wzFoundHeader); |
| 239 | ExitOnFailure(hr, "Failed while searching for existing HTTP header."); |
| 240 | |
| 241 | // If there was a substring HTTP header match, make sure the match was at the beginning |
| 242 | // of the string because that is the HTTP header name. |
| 243 | if (S_OK == hr) |
| 244 | { |
| 245 | DWORD cchMatch = lstrlenW(pwzNewHeader); |
| 246 | if (CSTR_EQUAL == ::CompareStringW(LOCALE_SYSTEM_DEFAULT, NORM_IGNORECASE, pwzNewHeader, cchMatch, wzFoundHeader, cchMatch)) |
| 247 | { |
| 248 | fOldValueFound = TRUE; |
| 249 | break; |
| 250 | } |
| 251 | } |
| 252 | } |
| 253 | |
| 254 | // Add the value on to the header name now. |
| 255 | hr = StrAllocConcat(&pwzNewHeader, pshh->wzValue, 0); |
| 256 | ExitOnFailure(hr, "Failed to add value on to HTTP header name."); |
| 257 | |
| 258 | // If we have something to replace, replace it, otherwise, put it at the beginning (order shouldn't matter) |
| 259 | if (fOldValueFound) |
| 260 | { |
| 261 | if (NULL == pwzHeaders) |
| 262 | { |
| 263 | ExitOnFailure(hr = E_INVALIDARG, "While attempting to replace old HTTP header with new HTTP header, it was discovered that the old HTTP header was NULL!"); |
| 264 | } |
| 265 | hr = MultiSzReplaceString(&pwzHeaders, dwFoundHeaderIndex, pwzNewHeader); |
| 266 | ExitOnFailure(hr, "Failed to replace old HTTP header with new HTTP header"); |
| 267 | } |
| 268 | else |
| 269 | { |
| 270 | hr = MultiSzPrepend(&pwzHeaders, NULL, pwzNewHeader); |
| 271 | ExitOnFailure(hr, "Failed to prepend new HTTP header"); |
| 272 | } |
| 273 | } |
| 274 | |
| 275 | // now write the HttpCustom to the metabase |
| 276 | hr = ScaWriteMetabaseValue(piMetabase, wzRoot, NULL, MD_HTTP_CUSTOM, METADATA_INHERIT, IIS_MD_UT_FILE, MULTISZ_METADATA, pwzHeaders); |
| 277 | ExitOnFailure(hr, "Failed to write HTTP Headers to metabase"); |
| 278 | |
| 279 | LExit: |
| 280 | MetaFreeValue(&mr); |
| 281 | |
| 282 | ReleaseStr(pwzNewHeader); |
| 283 | ReleaseStr(pwzHeaders); |
| 284 | ReleaseStr(pwzSearchKey); |
| 285 | |
| 286 | return hr; |
| 287 | } |
| 288 | |
| 289 | |
| 290 | HRESULT ScaHttpHeaderCheckList( |
| 291 | __in SCA_HTTP_HEADER* pshhList |
| 292 | ) |
| 293 | { |
| 294 | if (!pshhList) |
| 295 | { |
| 296 | return S_OK; |
| 297 | } |
| 298 | |
| 299 | while (pshhList) |
| 300 | { |
| 301 | WcaLog(LOGMSG_STANDARD, "Http Header: %ls for parent: %ls not used!", pshhList->wzName, pshhList->wzParentValue); |
| 302 | pshhList = pshhList->pshhNext; |
| 303 | } |
| 304 | |
| 305 | return E_FAIL; |
| 306 | } |
| 307 | |
| 308 | |
| 309 | static HRESULT AddHttpHeaderToList( |
| 310 | __in SCA_HTTP_HEADER** ppshhList |
| 311 | ) |
| 312 | { |
| 313 | HRESULT hr = S_OK; |
| 314 | |
| 315 | SCA_HTTP_HEADER* pshh = static_cast<SCA_HTTP_HEADER*>(MemAlloc(sizeof(SCA_HTTP_HEADER), TRUE)); |
| 316 | ExitOnNull(pshh, hr, E_OUTOFMEMORY, "failed to allocate memory for new http header list element"); |
| 317 | |
| 318 | pshh->pshhNext = *ppshhList; |
| 319 | *ppshhList = pshh; |
| 320 | |
| 321 | LExit: |
| 322 | return hr; |
| 323 | } |