UNPKG

@azure/storage-file-share

Version:
1,344 lines (1,331 loc) 590 kB
'use strict'; Object.defineProperty(exports, '__esModule', { value: true }); var coreRestPipeline = require('@azure/core-rest-pipeline'); var coreUtil = require('@azure/core-util'); var tslib = require('tslib'); var coreAuth = require('@azure/core-auth'); var coreHttpCompat = require('@azure/core-http-compat'); var coreClient = require('@azure/core-client'); var coreXml = require('@azure/core-xml'); var logger$1 = require('@azure/logger'); var abortController = require('@azure/abort-controller'); var crypto = require('crypto'); var coreTracing = require('@azure/core-tracing'); var stream = require('stream'); var events = require('events'); var fs = require('fs'); var util = require('util'); function _interopNamespaceDefault(e) { var n = Object.create(null); if (e) { Object.keys(e).forEach(function (k) { if (k !== 'default') { var d = Object.getOwnPropertyDescriptor(e, k); Object.defineProperty(n, k, d.get ? d : { enumerable: true, get: function () { return e[k]; } }); } }); } n.default = e; return Object.freeze(n); } var coreHttpCompat__namespace = /*#__PURE__*/_interopNamespaceDefault(coreHttpCompat); var coreClient__namespace = /*#__PURE__*/_interopNamespaceDefault(coreClient); var fs__namespace = /*#__PURE__*/_interopNamespaceDefault(fs); var util__namespace = /*#__PURE__*/_interopNamespaceDefault(util); // Copyright (c) Microsoft Corporation. // Licensed under the MIT License. /** * ONLY AVAILABLE IN NODE.JS RUNTIME. * * This is a helper class to construct a string representing the permissions granted by an AccountSAS. Setting a value * to true means that any SAS which uses these permissions will grant permissions for that operation. Once all the * values are set, this should be serialized with toString and set as the permissions field on an * {@link AccountSASSignatureValues} object. It is possible to construct the permissions string without this class, but * the order of the permissions is particular and this class guarantees correctness. */ class AccountSASPermissions { constructor() { /** * Permission to read resources and list queues and tables granted. */ this.read = false; /** * Permission to write resources granted. */ this.write = false; /** * Permission to delete blobs and files granted. */ this.delete = false; /** * Permission to list blob containers, blobs, shares, directories, and files granted. */ this.list = false; /** * Permission to add messages, table entities, and append to blobs granted. */ this.add = false; /** * Permission to create blobs and files granted. */ this.create = false; /** * Permissions to update messages and table entities granted. */ this.update = false; /** * Permission to get and delete messages granted. */ this.process = false; } /** * Parse initializes the AccountSASPermissions fields from a string. * * @param permissions - */ static parse(permissions) { const accountSASPermissions = new AccountSASPermissions(); for (const c of permissions) { switch (c) { case "r": accountSASPermissions.read = true; break; case "w": accountSASPermissions.write = true; break; case "d": accountSASPermissions.delete = true; break; case "l": accountSASPermissions.list = true; break; case "a": accountSASPermissions.add = true; break; case "c": accountSASPermissions.create = true; break; case "u": accountSASPermissions.update = true; break; case "p": accountSASPermissions.process = true; break; default: throw new RangeError(`Invalid permission character: ${c}`); } } return accountSASPermissions; } /** * Produces the SAS permissions string for an Azure Storage account. * Call this method to set AccountSASSignatureValues Permissions field. * * Using this method will guarantee the resource types are in * an order accepted by the service. * * @see https://learn.microsoft.com/en-us/rest/api/storageservices/constructing-an-account-sas * */ toString() { // The order of the characters should be as specified here to ensure correctness: // https://learn.microsoft.com/en-us/rest/api/storageservices/constructing-an-account-sas // Use a string array instead of string concatenating += operator for performance const permissions = []; if (this.read) { permissions.push("r"); } if (this.write) { permissions.push("w"); } if (this.delete) { permissions.push("d"); } if (this.list) { permissions.push("l"); } if (this.add) { permissions.push("a"); } if (this.create) { permissions.push("c"); } if (this.update) { permissions.push("u"); } if (this.process) { permissions.push("p"); } return permissions.join(""); } } // Copyright (c) Microsoft Corporation. // Licensed under the MIT License. /** * ONLY AVAILABLE IN NODE.JS RUNTIME. * * This is a helper class to construct a string representing the resources accessible by an AccountSAS. Setting a value * to true means that any SAS which uses these permissions will grant access to that resource type. Once all the * values are set, this should be serialized with toString and set as the resources field on an * {@link AccountSASSignatureValues} object. It is possible to construct the resources string without this class, but * the order of the resources is particular and this class guarantees correctness. */ class AccountSASResourceTypes { constructor() { /** * Permission to access service level APIs granted. */ this.service = false; /** * Permission to access container level APIs (Blob Containers, Tables, Queues, File Shares) granted. */ this.container = false; /** * Permission to access object level APIs (Blobs, Table Entities, Queue Messages, Files) granted. */ this.object = false; } /** * Creates an {@link AccountSASResourceTypes} from the specified resource types string. This method will throw an * Error if it encounters a character that does not correspond to a valid resource type. * * @param resourceTypes - */ static parse(resourceTypes) { const accountSASResourceTypes = new AccountSASResourceTypes(); for (const c of resourceTypes) { switch (c) { case "s": accountSASResourceTypes.service = true; break; case "c": accountSASResourceTypes.container = true; break; case "o": accountSASResourceTypes.object = true; break; default: throw new RangeError(`Invalid resource type: ${c}`); } } return accountSASResourceTypes; } /** * Converts the given resource types to a string. * * @see https://learn.microsoft.com/en-us/rest/api/storageservices/constructing-an-account-sas * */ toString() { const resourceTypes = []; if (this.service) { resourceTypes.push("s"); } if (this.container) { resourceTypes.push("c"); } if (this.object) { resourceTypes.push("o"); } return resourceTypes.join(""); } } // Copyright (c) Microsoft Corporation. // Licensed under the MIT License. /** * ONLY AVAILABLE IN NODE.JS RUNTIME. * * This is a helper class to construct a string representing the services accessible by an AccountSAS. Setting a value * to true means that any SAS which uses these permissions will grant access to that service. Once all the * values are set, this should be serialized with toString and set as the services field on an * {@link AccountSASSignatureValues} object. It is possible to construct the services string without this class, but * the order of the services is particular and this class guarantees correctness. */ class AccountSASServices { constructor() { /** * Permission to access blob resources granted. */ this.blob = false; /** * Permission to access file resources granted. */ this.file = false; /** * Permission to access queue resources granted. */ this.queue = false; /** * Permission to access table resources granted. */ this.table = false; } /** * Creates an {@link AccountSASServices} from the specified services string. This method will throw an * Error if it encounters a character that does not correspond to a valid service. * * @param services - */ static parse(services) { const accountSASServices = new AccountSASServices(); for (const c of services) { switch (c) { case "b": accountSASServices.blob = true; break; case "f": accountSASServices.file = true; break; case "q": accountSASServices.queue = true; break; case "t": accountSASServices.table = true; break; default: throw new RangeError(`Invalid service character: ${c}`); } } return accountSASServices; } /** * Converts the given services to a string. * */ toString() { const services = []; if (this.blob) { services.push("b"); } if (this.table) { services.push("t"); } if (this.queue) { services.push("q"); } if (this.file) { services.push("f"); } return services.join(""); } } // Copyright (c) Microsoft Corporation. // Licensed under the MIT License. /** * Generate SasIPRange format string. For example: * * "8.8.8.8" or "1.1.1.1-255.255.255.255" * * @param ipRange - A range of IP addresses. * @returns string representation of the IP range. */ function ipRangeToString(ipRange) { return ipRange.end ? `${ipRange.start}-${ipRange.end}` : ipRange.start; } // Copyright (c) Microsoft Corporation. // Licensed under the MIT License. const SDK_VERSION = "12.27.0"; const SERVICE_VERSION = "2025-05-05"; const FILE_MAX_SIZE_BYTES = 4 * 1024 * 1024 * 1024 * 1024; // 4TB const FILE_RANGE_MAX_SIZE_BYTES = 4 * 1024 * 1024; // 4MB const DEFAULT_MAX_DOWNLOAD_RETRY_REQUESTS = 5; const DEFAULT_HIGH_LEVEL_CONCURRENCY = 5; const REQUEST_TIMEOUT = 100 * 1000; // In ms /** * The OAuth scope to use with Azure Storage. */ const StorageOAuthScopes = "https://storage.azure.com/.default"; const URLConstants$1 = { Parameters: { SHARE_SNAPSHOT: "sharesnapshot", TIMEOUT: "timeout", }, }; const StorageFileLoggingAllowedHeaderNames = [ "Access-Control-Allow-Origin", "Cache-Control", "Content-Length", "Content-Type", "Date", "Request-Id", "traceparent", "Transfer-Encoding", "User-Agent", "x-ms-client-request-id", "x-ms-date", "x-ms-error-code", "x-ms-request-id", "x-ms-return-client-request-id", "x-ms-version", "Accept-Ranges", "Content-Disposition", "Content-Encoding", "Content-Language", "Content-MD5", "Content-Range", "ETag", "Last-Modified", "Server", "Vary", "x-ms-content-crc64", "x-ms-copy-action", "x-ms-copy-completion-time", "x-ms-copy-id", "x-ms-copy-progress", "x-ms-copy-status", "x-ms-has-immutability-policy", "x-ms-has-legal-hold", "x-ms-lease-state", "x-ms-lease-status", "x-ms-range", "x-ms-request-server-encrypted", "x-ms-server-encrypted", "x-ms-snapshot", "x-ms-source-range", "x-ms-cache-control", "x-ms-content-disposition", "x-ms-content-encoding", "x-ms-content-language", "x-ms-content-length", "x-ms-content-md5", "x-ms-content-type", "x-ms-copy-source-error-code", "x-ms-copy-source-status-code", "x-ms-file-attributes", "x-ms-file-change-time", "x-ms-file-creation-time", "x-ms-file-id", "x-ms-file-last-write-time", "x-ms-file-parent-id", "x-ms-handle-id", "x-ms-number-of-handles-closed", "x-ms-recursive", "x-ms-share-quota", "x-ms-type", "x-ms-write", ]; const StorageFileLoggingAllowedQueryParameters = [ "comp", "maxresults", "rscc", "rscd", "rsce", "rscl", "rsct", "se", "si", "sip", "sp", "spr", "sr", "srt", "ss", "st", "sv", "copyid", "restype", ]; /// List of ports used for path style addressing. /// Path style addressing means that storage account is put in URI's Path segment in instead of in host. const PathStylePorts = [ "10000", "10001", "10002", "10003", "10004", "10100", "10101", "10102", "10103", "10104", "11000", "11001", "11002", "11003", "11004", "11100", "11101", "11102", "11103", "11104", ]; // Copyright (c) Microsoft Corporation. // Licensed under the MIT License. /** * Reserved URL characters must be properly escaped for Storage services like Blob or File. * * ## URL encode and escape strategy for JS SDKs * * When customers pass a URL string into XXXClient classes constructor, the URL string may already be URL encoded or not. * But before sending to Azure Storage server, the URL must be encoded. However, it's hard for a SDK to guess whether the URL * string has been encoded or not. We have 2 potential strategies, and chose strategy two for the XXXClient constructors. * * ### Strategy One: Assume the customer URL string is not encoded, and always encode URL string in SDK. * * This is what legacy V2 SDK does, simple and works for most of the cases. * - When customer URL string is "http://account.blob.core.windows.net/con/b:", * SDK will encode it to "http://account.blob.core.windows.net/con/b%3A" and send to server. A blob named "b:" will be created. * - When customer URL string is "http://account.blob.core.windows.net/con/b%3A", * SDK will encode it to "http://account.blob.core.windows.net/con/b%253A" and send to server. A blob named "b%3A" will be created. * * But this strategy will make it not possible to create a blob with "?" in it's name. Because when customer URL string is * "http://account.blob.core.windows.net/con/blob?name", the "?name" will be treated as URL paramter instead of blob name. * If customer URL string is "http://account.blob.core.windows.net/con/blob%3Fname", a blob named "blob%3Fname" will be created. * V2 SDK doesn't have this issue because it doesn't allow customer pass in a full URL, it accepts a separate blob name and encodeURIComponent for it. * We cannot accept a SDK cannot create a blob name with "?". So we implement strategy two: * * ### Strategy Two: SDK doesn't assume the URL has been encoded or not. It will just escape the special characters. * * This is what V10 Blob Go SDK does. It accepts a URL type in Go, and call url.EscapedPath() to escape the special chars unescaped. * - When customer URL string is "http://account.blob.core.windows.net/con/b:", * SDK will escape ":" like "http://account.blob.core.windows.net/con/b%3A" and send to server. A blob named "b:" will be created. * - When customer URL string is "http://account.blob.core.windows.net/con/b%3A", * There is no special characters, so send "http://account.blob.core.windows.net/con/b%3A" to server. A blob named "b:" will be created. * - When customer URL string is "http://account.blob.core.windows.net/con/b%253A", * There is no special characters, so send "http://account.blob.core.windows.net/con/b%253A" to server. A blob named "b%3A" will be created. * * This strategy gives us flexibility to create with any special characters. But "%" will be treated as a special characters, if the URL string * is not encoded, there shouldn't a "%" in the URL string, otherwise the URL is not a valid URL. * If customer needs to create a blob with "%" in it's blob name, use "%25" insead of "%". Just like above 3rd sample. * And following URL strings are invalid: * - "http://account.blob.core.windows.net/con/b%" * - "http://account.blob.core.windows.net/con/b%2" * - "http://account.blob.core.windows.net/con/b%G" * * Another special character is "?", use "%2F" to represent a blob name with "?" in a URL string. * * ### Strategy for containerName, blobName or other specific XXXName parameters in methods such as `ContainerClient.getBlobClient(blobName)` * * We will apply strategy one, and call encodeURIComponent for these parameters like blobName. Because what customers passes in is a plain name instead of a URL. * * @see https://learn.microsoft.com/en-us/rest/api/storageservices/naming-and-referencing-containers--blobs--and-metadata * @see https://learn.microsoft.com/en-us/rest/api/storageservices/naming-and-referencing-shares--directories--files--and-metadata * * @param url - */ function escapeURLPath(url) { const urlParsed = new URL(url); let path = urlParsed.pathname; path = path || "/"; path = escape(path); urlParsed.pathname = path; return urlParsed.toString(); } function getValueInConnString(connectionString, argument) { const elements = connectionString.split(";"); for (const element of elements) { if (element.trim().startsWith(argument)) { return element.trim().match(argument + "=(.*)")[1]; } } return ""; } /** * Extracts the parts of an Azure Storage account connection string. * * @param connectionString - Connection string. * @returns String key value pairs of the storage account's url and credentials. */ function extractConnectionStringParts(connectionString) { // Matching FileEndpoint in the Account connection string let fileEndpoint = getValueInConnString(connectionString, "FileEndpoint"); // Slicing off '/' at the end if exists // (The methods that use `extractConnectionStringParts` expect the url to not have `/` at the end) fileEndpoint = fileEndpoint.endsWith("/") ? fileEndpoint.slice(0, -1) : fileEndpoint; if (connectionString.search("DefaultEndpointsProtocol=") !== -1 && connectionString.search("AccountKey=") !== -1) { // Account connection string let defaultEndpointsProtocol = ""; let accountName = ""; let accountKey = Buffer.from("accountKey", "base64"); let endpointSuffix = ""; // Get account name and key accountName = getValueInConnString(connectionString, "AccountName"); accountKey = Buffer.from(getValueInConnString(connectionString, "AccountKey"), "base64"); if (!fileEndpoint) { // FileEndpoint is not present in the Account connection string // Can be obtained from `${defaultEndpointsProtocol}://${accountName}.file.${endpointSuffix}` defaultEndpointsProtocol = getValueInConnString(connectionString, "DefaultEndpointsProtocol"); const protocol = defaultEndpointsProtocol.toLowerCase(); if (protocol !== "https" && protocol !== "http") { throw new Error("Invalid DefaultEndpointsProtocol in the provided Connection String. Expecting 'https' or 'http'"); } endpointSuffix = getValueInConnString(connectionString, "EndpointSuffix"); if (!endpointSuffix) { throw new Error("Invalid EndpointSuffix in the provided Connection String"); } fileEndpoint = `${defaultEndpointsProtocol}://${accountName}.file.${endpointSuffix}`; } if (!accountName) { throw new Error("Invalid AccountName in the provided Connection String"); } else if (accountKey.length === 0) { throw new Error("Invalid AccountKey in the provided Connection String"); } return { kind: "AccountConnString", url: fileEndpoint, accountName, accountKey, }; } else { // SAS connection string const accountSas = getValueInConnString(connectionString, "SharedAccessSignature"); let accountName = getValueInConnString(connectionString, "AccountName"); // if accountName is empty, try to read it from BlobEndpoint if (!accountName) { accountName = getAccountNameFromUrl(fileEndpoint); } if (!fileEndpoint) { throw new Error("Invalid FileEndpoint in the provided SAS Connection String"); } else if (!accountSas) { throw new Error("Invalid SharedAccessSignature in the provided SAS Connection String"); } return { kind: "SASConnString", url: fileEndpoint, accountName, accountSas }; } } /** * Internal escape method implemented Strategy Two mentioned in escapeURL() description. * * @param text - */ function escape(text) { return encodeURIComponent(text) .replace(/%2F/g, "/") // Don't escape for "/" .replace(/'/g, "%27") // Escape for "'" .replace(/\+/g, "%20") .replace(/%25/g, "%"); // Revert encoded "%" } /** * Append a string to URL path. Will remove duplicated "/" in front of the string * when URL path ends with a "/". * * @param url - Source URL string * @param name - String to be appended to URL * @returns An updated URL string */ function appendToURLPath(url, name) { const urlParsed = new URL(url); let path = urlParsed.pathname; path = path ? (path.endsWith("/") ? `${path}${name}` : `${path}/${name}`) : name; urlParsed.pathname = path; return urlParsed.toString(); } /** * Append a string to URL query. * * @param url - Source URL string. * @param queryParts - String to be appended to the URL query. * @returns An updated URL string. */ function appendToURLQuery(url, queryParts) { const urlParsed = new URL(url); let query = urlParsed.search; if (query) { query += "&" + queryParts; } else { query = queryParts; } urlParsed.search = query; return urlParsed.toString(); } /** * Set URL parameter name and value. If name exists in URL parameters, old value * will be replaced by name key. If not provide value, the parameter will be deleted. * * @param url - Source URL string * @param name - Parameter name * @param value - Parameter value * @returns An updated URL string */ function setURLParameter$1(url, name, value) { const urlParsed = new URL(url); const encodedName = encodeURIComponent(name); const encodedValue = value ? encodeURIComponent(value) : undefined; // mutating searchParams will change the encoding, so we have to do this ourselves const searchString = urlParsed.search === "" ? "?" : urlParsed.search; const searchPieces = []; for (const pair of searchString.slice(1).split("&")) { if (pair) { const [key] = pair.split("=", 2); if (key !== encodedName) { searchPieces.push(pair); } } } if (encodedValue) { searchPieces.push(`${encodedName}=${encodedValue}`); } urlParsed.search = searchPieces.length ? `?${searchPieces.join("&")}` : ""; return urlParsed.toString(); } /** * Rounds a date off to seconds. * * @param date - * @param withMilliseconds - If true, YYYY-MM-DDThh:mm:ss.fffffffZ will be returned; * If false, YYYY-MM-DDThh:mm:ssZ will be returned. * @returns Date string in ISO8061 format, with or without 7 milliseconds component */ function truncatedISO8061Date(date, withMilliseconds = true) { // Date.toISOString() will return like "2018-10-29T06:34:36.139Z" const dateString = date.toISOString(); return withMilliseconds ? dateString.substring(0, dateString.length - 1) + "0000" + "Z" : dateString.substring(0, dateString.length - 5) + "Z"; } /** * Delay specified time interval. * * @param timeInMs - * @param aborter - * @param abortError - */ async function delay(timeInMs, aborter, abortError) { return new Promise((resolve, reject) => { /* eslint-disable-next-line prefer-const */ let timeout; const abortHandler = () => { if (timeout !== undefined) { clearTimeout(timeout); } reject(abortError); }; const resolveHandler = () => { if (aborter !== undefined) { aborter.removeEventListener("abort", abortHandler); } resolve(); }; timeout = setTimeout(resolveHandler, timeInMs); if (aborter !== undefined) { aborter.addEventListener("abort", abortHandler); } }); } /** * Extracts account name from the url * @param url - url to extract the account name from * @returns with the account name */ function getAccountNameFromUrl(url) { const parsedUrl = new URL(url); let accountName; try { if (parsedUrl.hostname.split(".")[1] === "file") { // `${defaultEndpointsProtocol}://${accountName}.file.${endpointSuffix}`; // Slicing off '/' at the end if exists url = url.endsWith("/") ? url.slice(0, -1) : url; accountName = parsedUrl.hostname.split(".")[0]; } else if (isIpEndpointStyle(parsedUrl)) { // IPv4/IPv6 address hosts... Example - http://192.0.0.10:10001/devstoreaccount1/ // Single word domain without a [dot] in the endpoint... Example - http://localhost:10001/devstoreaccount1/ // .getPath() -> /devstoreaccount1/ accountName = parsedUrl.pathname.split("/")[1]; } else { // Custom domain case: "https://customdomain.com/containername/blob". accountName = ""; } return accountName; } catch (error) { throw new Error("Unable to extract accountName with provided information."); } } function isIpEndpointStyle(parsedUrl) { const host = parsedUrl.host; // Case 1: Ipv6, use a broad regex to find out candidates whose host contains two ':'. // Case 2: localhost(:port) or host.docker.internal, use broad regex to match port part. // Case 3: Ipv4, use broad regex which just check if host contains Ipv4. // For valid host please refer to https://man7.org/linux/man-pages/man7/hostname.7.html. return (/^.*:.*:.*$|^(localhost|host.docker.internal)(:[0-9]+)?$|^(\d|[1-9]\d|1\d\d|2[0-4]\d|25[0-5])(\.(\d|[1-9]\d|1\d\d|2[0-4]\d|25[0-5])){3}(:[0-9]+)?$/.test(host) || (Boolean(parsedUrl.port) && PathStylePorts.includes(parsedUrl.port))); } function getShareNameAndPathFromUrl(url) { // URL may look like the following // "https://myaccount.file.core.windows.net/myshare/mydirectory/file?sasString"; // "https://myaccount.file.core.windows.net/myshare/mydirectory/file"; // "https://myaccount.file.core.windows.net/myshare/mydirectory?sasString"; // "https://myaccount.file.core.windows.net/myshare/mydirectory"; // "https://myaccount.file.core.windows.net/myshare?sasString"; // "https://myaccount.file.core.windows.net/myshare"; // IPv4/IPv6 address hosts, Endpoints - `http://187.24.0.1:1000/devstoreaccount1/mydirectory/file` // http://localhost:1000/devstoreaccount1/mydirectory/file // mydirectory can consist of multiple directories - dir1/dir2/dir3 let shareName; let path; let baseName; try { const parsedUrl = new URL(url); if (parsedUrl.hostname.split(".")[1] === "file") { // "https://myaccount.file.core.windows.net/myshare/mydirectory/file"; // .getPath() -> /myshare/mydirectory/file const pathComponents = parsedUrl.pathname.match("/([^/]*)(/(.*))?"); shareName = pathComponents[1]; path = pathComponents[3]; } else if (isIpEndpointStyle(parsedUrl)) { // IPv4/IPv6 address hosts... Example - http://187.24.0.1:1000/devstoreaccount1/mydirectory/file // Single word domain without a [dot] in the endpoint... Example - http://localhost:1000/devstoreaccount1/mydirectory/file // .getPath() -> /devstoreaccount1/mydirectory/file const pathComponents = parsedUrl.pathname.match("/([^/]*)/([^/]*)(/(.*))?"); shareName = pathComponents[2]; path = pathComponents[4]; } else { // "https://customdomain.com/myshare/mydirectory/file"; // .getPath() -> /myshare/mydirectory/file const pathComponents = parsedUrl.pathname.match("/([^/]*)(/(.*))?"); shareName = pathComponents[1]; path = pathComponents[3]; } // decode the encoded shareName and filePath - to get all the special characters that might be present in it shareName = decodeURIComponent(shareName); path = decodeURIComponent(path); // Cast to string is required as TypeScript cannot infer that split() always returns // an array with length >= 1 baseName = path.split("/").pop(); if (!shareName) { throw new Error("Provided shareName is invalid."); } else { return { baseName, shareName, path }; } } catch (error) { throw new Error("Unable to extract shareName and filePath/directoryPath with provided information."); } } function httpAuthorizationToString(httpAuthorization) { return httpAuthorization ? httpAuthorization.scheme + " " + httpAuthorization.value : undefined; } /** * Set URL path. * * @param url - URL to change path to. * @param path - Path to set into the URL. */ function setURLPath(url, path) { const urlParsed = new URL(url); urlParsed.pathname = path; return urlParsed.toString(); } /** * Set URL query string. * * @param url - URL to set query string to. * @param queryString - Query string to set to the URL. */ function setURLQueries(url, queryString) { const urlParsed = new URL(url); urlParsed.search = queryString; return urlParsed.toString(); } /** * Escape the file or directory name but keep path separator ('/'). */ function EscapePath(pathName) { const split = pathName.split("/"); for (let i = 0; i < split.length; i++) { split[i] = encodeURIComponent(split[i]); } return split.join("/"); } /** * A typesafe helper for ensuring that a given response object has * the original _response attached. * @param response - A response object from calling a client operation * @returns The same object, but with known _response property */ function assertResponse(response) { if (`_response` in response) { return response; } throw new TypeError(`Unexpected response object ${response}`); } function StringEncodedToString(name) { if (name.encoded) { return decodeURIComponent(name.content); } else { return name.content; } } function ConvertInternalResponseOfListFiles(internalResponse) { const wrappedResponse = Object.assign(Object.assign({}, internalResponse), { prefix: undefined, directoryPath: StringEncodedToString({ encoded: internalResponse.encoded, content: internalResponse.directoryPath, }), segment: { fileItems: internalResponse.segment.fileItems.map((fileItemInternal) => { const fileItem = Object.assign(Object.assign({}, fileItemInternal), { name: StringEncodedToString(fileItemInternal.name) }); return fileItem; }), directoryItems: internalResponse.segment.directoryItems.map((directoryItemInternal) => { const directoryItem = Object.assign(Object.assign({}, directoryItemInternal), { name: StringEncodedToString(directoryItemInternal.name) }); return directoryItem; }), } }); delete wrappedResponse.encoded; const listResponse = wrappedResponse; if (internalResponse.prefix) { listResponse.prefix = StringEncodedToString(internalResponse.prefix); } return listResponse; } function ConvertInternalResponseOfListHandles(internalResponse) { const wrappedResponse = Object.assign(Object.assign({}, internalResponse), { handleList: internalResponse.handleList ? internalResponse.handleList.map((handleItemInternal) => { const handleItem = Object.assign(Object.assign({}, handleItemInternal), { path: StringEncodedToString(handleItemInternal.path) }); return handleItem; }) : undefined }); return wrappedResponse; } /** * A small helper to handle converting an empty string "" into undefined * This is used in the case of query parameters (like continuation token) where * we don't want to send an empty query parameter to the service since the signing * policy for shared key will fail. * @internal */ function removeEmptyString(value) { return value ? value : undefined; } function asSharePermission(value) { const castSharePermission = value; if (castSharePermission["permission"] !== undefined) { return { permission: castSharePermission.permission, format: castSharePermission.format, }; } return { permission: value, }; } /** * Parse 4-digit octal string representation of a File Mode to a {@link NfsFileMode} structure. */ function parseOctalFileMode(input) { if (input === undefined) { return undefined; } if ((input === null || input === void 0 ? void 0 : input.length) !== 4) { throw new Error("Invalid format of input string"); } const nfsFileMode = { owner: parseOctalRolePermissions(input[1]), group: parseOctalRolePermissions(input[2]), other: parseOctalRolePermissions(input[3]), effectiveUserIdentity: false, effectiveGroupIdentity: false, stickyBit: false, }; const value = Number.parseInt(input[0]); if ((value & 4) > 0) { nfsFileMode.effectiveUserIdentity = true; } if ((value & 2) > 0) { nfsFileMode.effectiveGroupIdentity = true; } if ((value & 1) > 0) { nfsFileMode.stickyBit = true; } return nfsFileMode; } /** * Convert {@link NfsFileMode} structure to a 4-digit octal string represenation. */ function toOctalFileMode(input) { if (input === undefined) return undefined; let higherOrderDigit = 0; if (input === null || input === void 0 ? void 0 : input.effectiveUserIdentity) { higherOrderDigit |= 4; } if (input === null || input === void 0 ? void 0 : input.effectiveGroupIdentity) { higherOrderDigit |= 2; } if (input === null || input === void 0 ? void 0 : input.stickyBit) { higherOrderDigit |= 1; } let stringFileMode = higherOrderDigit.toString(); stringFileMode += toOctalRolePermissions(input.owner); stringFileMode += toOctalRolePermissions(input.group); stringFileMode += toOctalRolePermissions(input.other); return stringFileMode; } /** * Convert a {@link NfsFileMode} to a string in symbolic notation. */ function toSymbolicFileMode(input) { if (input === undefined) return undefined; let ownerPermissions = toSymbolicRolePermissions(input.owner); let groupPermissions = toSymbolicRolePermissions(input.group); let otherPermissions = toSymbolicRolePermissions(input.other); if (input.effectiveUserIdentity) { if (ownerPermissions[2] === "x") { ownerPermissions = ownerPermissions.substring(0, 2) + "s"; } else { ownerPermissions = ownerPermissions.substring(0, 2) + "S"; } } if (input.effectiveGroupIdentity) { if (groupPermissions[2] === "x") { groupPermissions = groupPermissions.substring(0, 2) + "s"; } else { groupPermissions = groupPermissions.substring(0, 2) + "S"; } } if (input.stickyBit) { if (otherPermissions[2] === "x") { otherPermissions = otherPermissions.substring(0, 2) + "t"; } else { otherPermissions = otherPermissions.substring(0, 2) + "T"; } } return ownerPermissions + groupPermissions + otherPermissions; } /** * Parse a 9-character symbolic string representation of a File Mode to a {@link NfsFileMode} structure. */ function parseSymbolicFileMode(input) { if (input === undefined) return undefined; if ((input === null || input === void 0 ? void 0 : input.length) !== 9) { throw new Error("Invalid format of input string"); } const ownerPermissions = parseSymbolicRolePermissions(input.substring(0, 3)); const groupPermissions = parseSymbolicRolePermissions(input.substring(3, 6)); const otherPermissions = parseSymbolicRolePermissions(input.substring(6, 9)); const nfsFileMode = { owner: ownerPermissions.rolePermissions, group: groupPermissions.rolePermissions, other: otherPermissions.rolePermissions, effectiveUserIdentity: ownerPermissions.setSticky, effectiveGroupIdentity: groupPermissions.setSticky, stickyBit: otherPermissions.setSticky, }; return nfsFileMode; } function parseOctalRolePermissions(c) { const rolePermissions = { read: false, write: false, execute: false, }; const value = Number.parseInt(c); if (value < 0 || value > 7) { throw new Error("MustBeBetweenInclusive"); } if ((value & 4) > 0) { rolePermissions.read = true; } if ((value & 2) > 0) { rolePermissions.write = true; } if ((value & 1) > 0) { rolePermissions.execute = true; } return rolePermissions; } function toOctalRolePermissions(rolePermissions) { let result = 0; if (rolePermissions.read === true) { result |= 4; } if (rolePermissions.write === true) { result |= 2; } if (rolePermissions.execute === true) { result |= 1; } return result.toString(); } function toSymbolicRolePermissions(rolePermissions) { let symbolicRolePermissions = ""; if (rolePermissions.read === true) { symbolicRolePermissions += "r"; } else { symbolicRolePermissions += "-"; } if (rolePermissions.write === true) { symbolicRolePermissions += "w"; } else { symbolicRolePermissions += "-"; } if (rolePermissions.execute === true) { symbolicRolePermissions += "x"; } else { symbolicRolePermissions += "-"; } return symbolicRolePermissions; } function parseSymbolicRolePermissions(input) { if (input.length !== 3) { throw new Error("input must be 3 characters long"); } const rolePermissions = { read: false, write: false, execute: false, }; let setSticky = false; // Read character if (input[0] === "r") { rolePermissions.read = true; } else if (input[0] !== "-") { throw new Error(`Invalid character in symbolic role permission: ${input[0]}`); } // Write character if (input[1] === "w") { rolePermissions.write = true; } else if (input[1] !== "-") { throw new Error(`Invalid character in symbolic role permission: ${input[1]}`); } // Execute character if (input[2] === "x" || input[2] === "s" || input[2] === "t") { rolePermissions.execute = true; if (input[2] === "s" || input[2] === "t") { setSticky = true; } } if (input[2] === "S" || input[2] === "T") { setSticky = true; } if (input[2] !== "x" && input[2] !== "s" && input[2] !== "S" && input[2] !== "t" && input[2] !== "T" && input[2] !== "-") { throw new Error(`Invalid character in symbolic role permission: ${input[2]}`); } return { rolePermissions, setSticky, }; } // Copyright (c) Microsoft Corporation. // Licensed under the MIT License. /** * Protocols for generated SAS. */ exports.SASProtocol = void 0; (function (SASProtocol) { /** * Protocol that allows HTTPS only */ SASProtocol["Https"] = "https"; /** * Protocol that allows both HTTPS and HTTP */ SASProtocol["HttpsAndHttp"] = "https,http"; })(exports.SASProtocol || (exports.SASProtocol = {})); /** * Represents the components that make up an Azure Storage SAS' query parameters. This type is not constructed directly * by the user; it is only generated by the {@link AccountSASSignatureValues} and {@link FileSASSignatureValues} * types. Once generated, it can be encoded into a {@link String} and appended to a URL directly (though caution should * be taken here in case there are existing query parameters, which might affect the appropriate means of appending * these query parameters). * * NOTE: Instances of this class are immutable. */ class SASQueryParameters { /** * Optional. IP range allowed for this SAS. * * @readonly */ get ipRange() { if (this.ipRangeInner) { return { end: this.ipRangeInner.end, start: this.ipRangeInner.start, }; } return undefined; } /** * Creates an instance of SASQueryParameters. * * @param version - Representing the storage version * @param signature - Representing the signature for the SAS token * @param permissions - Representing the storage permissions * @param services - Representing the storage services being accessed (only for Account SAS) * @param resourceTypes - Representing the storage resource types being accessed (only for Account SAS) * @param protocol - Representing the allowed HTTP protocol(s) * @param startsOn - Representing the start time for this SAS token * @param expiresOn - Representing the expiry time for this SAS token * @param ipRange - Representing the range of valid IP addresses for this SAS token * @param identifier - Representing the signed identifier (only for Service SAS) * @param resource - Representing the storage container or blob (only for Service SAS) * @param cacheControl - Representing the cache-control header (only for Blob/File Service SAS) * @param contentDisposition - Representing the content-disposition header (only for Blob/File Service SAS) * @param contentEncoding - Representing the content-encoding header (only for Blob/File Service SAS) * @param contentLanguage - Representing the content-language header (only for Blob/File Service SAS) * @param contentType - Representing the content-type header (only for Blob/File Service SAS) */ constructor(version, signature, permissions, services, resourceTypes, protocol, startsOn, expiresOn, ipRange, identifier, resource, cacheControl, contentDisposition, contentEncoding, contentLanguage, contentType) { this.version = version; this.services = services; this.resourceTypes = resourceTypes; this.expiresOn = expiresOn; this.permissions = permissions; this.protocol = protocol; this.startsOn = startsOn; this.ipRangeInner = ipRange; this.identifier = identifier; this.resource = resource; this.signature = signature; this.cacheControl = cacheControl; this.contentDisposition = contentDisposition; this.contentEncoding = contentEncoding; this.contentLanguage = contentLanguage; this.contentType = contentType; } /** * Encodes all SAS query parameters into a string that can be appended to a URL. * */ toString() { const params = [ "sv", "ss", "srt", "spr", "st", "se", "sip", "si", "sr", "sp", "sig", "rscc", "rscd", "rsce", "rscl", "rsct", ]; const queries = []; for (const param of params) { switch (param) { case "sv": this.tryAppendQueryParameter(queries, param, this.version); break; case "ss": this.tryAppendQueryParameter(queries, param, this.services); break; case "srt": this.tryAppendQueryParameter(queries, param, this.resourceTypes); break; case "spr": this.tryAppendQueryParameter(queries, param, this.protocol); break; case "st": this.tryAppendQueryParameter(queries, param, this.startsOn ? truncatedISO8061Date(this.startsOn, false) : undefined); break; case "se": this.tryAppendQueryParameter(queries, param, this.expiresOn ? truncatedISO8061Date(this.expiresOn, false) : undefined); break; case "sip": this.tryAppendQueryParameter(queries, param, this.ipRange ? ipRangeToString(this.ipRange) : undefined); break; case "si": this.tryAppendQueryParameter(queries, param, this.identifier); break; case "sr": this.tryAppendQueryParameter(queries, param, this.resource); break; case "sp": this.tryAppendQueryParameter(queries, param, this.permissions); break; case "sig": this.tryAppendQueryParameter(queries, param, this.signature); break; case "rscc": this.tryAppendQueryParameter(queries, param, this.cacheControl); break; case "rscd": this.tryAppendQueryParameter(queries, param, this.contentDisposition); break; case "rsce": this.tryAppendQueryParameter(queries, param, this.contentEncoding); break; case "rscl": this.tryAppendQueryParameter(queries, param, this.contentLanguage); break; case "rsct": this.tryAppendQueryParameter(queries, param, this.contentType); break; } } return queries.join("&"); } /** * A private helper method used to filter and append query key/value pairs into an array. * * @param queries - * @param key - * @param value - */ tryAppendQueryParameter(queries, key, value) { if (!value) { return; } key = encodeURIComponent(key); value = encodeURIComponent(value); if (key.length > 0 && value.length > 0) { queries.push(`${key}=${value}`); } } } // Copyright (c) Microsoft Corporation. // Licensed under the MIT License. /** * ONLY AVAILABLE IN NODE.JS RUNTIME. * * Generates a {@link SASQueryParameters} object which contains all SAS query parameters needed to make an actual * REST request. * * @see https://learn.microsoft.com/en-us/rest/api/storageservices/constructing-an-account-sas * * @param sharedKeyCredential - */ function generateAccountSASQueryParameters(accountSASSignatureValues, sharedKeyCredential) { return generateAccountSASQueryParametersInternal(accountSASSignatureValues, sharedKeyCredential) .sasQueryParameters; } function generateAccountSASQueryParametersInternal(accountSASSignatureValues, sharedKeyCredential) { const version = accountSASSignatureValues.version ? accountSASSignatureValues.version : SERVICE_VERSION; const parsedPermissions = AccountSASPermissions.parse(accountSASSignatureValues.permissions.toString()).toString(); const parsedServices = AccountSASServices.parse(accountSASSignatureValues.services).toString(); const parsedResourceTypes = AccountSASResourceTypes.parse(accountSASSignatureValues.resourceTypes).toString(); let stringToSign; if (version >= "2020-12-06") { stringToSign = [ sharedKeyCredential.accountName, parsedPermissions, parsedServices, parsedResourceTypes, accountSASSignatureValues.startsOn ? truncatedISO8061Date(accountSASSignatureValues.startsOn, false)