powershell.js (5475B)
1 /* This Source Code Form is subject to the terms of the Mozilla Public 2 * License, v. 2.0. If a copy of the MPL was not distributed with this 3 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ 4 5 /* 6 * Copyright (C) 2007, 2008 Apple Inc. All rights reserved. 7 * Copyright (C) 2008, 2009 Anthony Ricaud <rik@webkit.org> 8 * Copyright (C) 2011 Google Inc. All rights reserved. 9 * Copyright (C) 2022 Mozilla Foundation. All rights reserved. 10 * 11 * Redistribution and use in source and binary forms, with or without 12 * modification, are permitted provided that the following conditions 13 * are met: 14 * 15 * 1. Redistributions of source code must retain the above copyright 16 * notice, this list of conditions and the following disclaimer. 17 * 2. Redistributions in binary form must reproduce the above copyright 18 * notice, this list of conditions and the following disclaimer in the 19 * documentation and/or other materials provided with the distribution. 20 * 3. Neither the name of Apple Computer, Inc. ("Apple") nor the names of 21 * its contributors may be used to endorse or promote products derived 22 * from this software without specific prior written permission. 23 * 24 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" 25 * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 26 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE 27 * DISCLAIMED. IN NO EVENT SHALL APPLE OR ITS CONTRIBUTORS BE LIABLE FOR ANY 28 * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES 29 * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; 30 * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND 31 * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT 32 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF 33 * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 34 */ 35 36 // Utility to generate commands to invoke a request for powershell 37 "use strict"; 38 39 // Some of these headers are passed in as seperate `Invoke-WebRequest` parameters so ignore 40 // when building the headers list, others are not to neccesarily restrict the request. 41 const IGNORED_HEADERS = [ 42 "connection", 43 "proxy-connection", 44 "content-length", 45 "expect", 46 "range", 47 "host", 48 "content-type", 49 "user-agent", 50 "cookie", 51 ]; 52 /** 53 * This escapes strings for the powershell command 54 * 55 * 1. Escape the backtick, dollar sign and the double quotes See https://www.rlmueller.net/PowerShellEscape.htm 56 * 2. Convert any non printing ASCII characters found, using the ASCII code. 57 */ 58 function escapeStr(str) { 59 return `"${str 60 .replace(/[`\$"]/g, "`$&") 61 .replace(/[^\x20-\x7E]/g, char => "$([char]" + char.charCodeAt(0) + ")")}"`; 62 } 63 64 const PowerShell = { 65 generateCommand(url, method, headers, postData, cookies) { 66 const parameters = []; 67 68 // Create a WebSession to pass the information about cookies 69 // See https://learn.microsoft.com/en-us/powershell/module/microsoft.powershell.utility/invoke-webrequest?view=powershell-7.2#-websession 70 const session = []; 71 for (const { name, value, domain } of cookies) { 72 if (!session.length) { 73 session.push( 74 "$session = New-Object Microsoft.PowerShell.Commands.WebRequestSession" 75 ); 76 } 77 session.push( 78 `$session.Cookies.Add((New-Object System.Net.Cookie(${escapeStr( 79 name 80 )}, ${escapeStr(value)}, "/", ${escapeStr( 81 domain || new URL(url).host 82 )})))` 83 ); 84 } 85 86 parameters.push(`-Uri ${escapeStr(url)}`); 87 88 if (method !== "GET") { 89 parameters.push(`-Method ${escapeStr(method)}`); 90 } 91 92 if (session.length) { 93 parameters.push("-WebSession $session"); 94 } 95 96 const userAgent = headers.find( 97 ({ name }) => name.toLowerCase() === "user-agent" 98 ); 99 if (userAgent) { 100 parameters.push("-UserAgent " + escapeStr(userAgent.value)); 101 } 102 103 const headersStr = []; 104 for (let { name, value } of headers) { 105 // Translate any HTTP2 pseudo headers to HTTP headers 106 name = name.replace(/^:/, ""); 107 108 if (IGNORED_HEADERS.includes(name.toLowerCase())) { 109 continue; 110 } 111 headersStr.push(`${escapeStr(name)} = ${escapeStr(value)}`); 112 } 113 if (headersStr.length) { 114 parameters.push(`-Headers @{\n${headersStr.join("\n ")}\n}`); 115 } 116 117 const contentType = headers.find( 118 header => header.name.toLowerCase() === "content-type" 119 ); 120 if (contentType) { 121 parameters.push("-ContentType " + escapeStr(contentType.value)); 122 } 123 124 const formData = postData.text; 125 if (formData) { 126 // Encode bytes if any of the characters is not an ASCII printing character (not between Space character and ~ character) 127 // a-zA-Z0-9 etc. See http://www.asciitable.com/ 128 const body = /[^\x20-\x7E]/.test(formData) 129 ? "([System.Text.Encoding]::UTF8.GetBytes(" + escapeStr(formData) + "))" 130 : escapeStr(formData); 131 parameters.push("-Body " + body); 132 } 133 134 return `${ 135 session.length ? session.join("\n").concat("\n") : "" 136 // -UseBasicParsing is added for backward compatibility. 137 // See https://learn.microsoft.com/en-us/powershell/module/microsoft.powershell.utility/invoke-webrequest?view=powershell-7.2#-usebasicparsing 138 }Invoke-WebRequest -UseBasicParsing ${parameters.join(" `\n")}`; 139 }, 140 }; 141 142 exports.PowerShell = PowerShell;