UNPKG

143 kBJavaScriptView Raw
1"use strict";
2// Copyright 2019 Google LLC
3//
4// Licensed under the Apache License, Version 2.0 (the "License");
5// you may not use this file except in compliance with the License.
6// You may obtain a copy of the License at
7//
8// http://www.apache.org/licenses/LICENSE-2.0
9//
10// Unless required by applicable law or agreed to in writing, software
11// distributed under the License is distributed on an "AS IS" BASIS,
12// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13// See the License for the specific language governing permissions and
14// limitations under the License.
15var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
16 if (k2 === undefined) k2 = k;
17 var desc = Object.getOwnPropertyDescriptor(m, k);
18 if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
19 desc = { enumerable: true, get: function() { return m[k]; } };
20 }
21 Object.defineProperty(o, k2, desc);
22}) : (function(o, m, k, k2) {
23 if (k2 === undefined) k2 = k;
24 o[k2] = m[k];
25}));
26var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
27 Object.defineProperty(o, "default", { enumerable: true, value: v });
28}) : function(o, v) {
29 o["default"] = v;
30});
31var __importStar = (this && this.__importStar) || function (mod) {
32 if (mod && mod.__esModule) return mod;
33 var result = {};
34 if (mod != null) for (var k in mod) if (k !== "default" && Object.prototype.hasOwnProperty.call(mod, k)) __createBinding(result, mod, k);
35 __setModuleDefault(result, mod);
36 return result;
37};
38var __classPrivateFieldGet = (this && this.__classPrivateFieldGet) || function (receiver, state, kind, f) {
39 if (kind === "a" && !f) throw new TypeError("Private accessor was defined without a getter");
40 if (typeof state === "function" ? receiver !== state || !f : !state.has(receiver)) throw new TypeError("Cannot read private member from an object whose class did not declare it");
41 return kind === "m" ? f : kind === "a" ? f.call(receiver) : f ? f.value : state.get(receiver);
42};
43var __importDefault = (this && this.__importDefault) || function (mod) {
44 return (mod && mod.__esModule) ? mod : { "default": mod };
45};
46var _File_instances, _File_validateIntegrity;
47Object.defineProperty(exports, "__esModule", { value: true });
48exports.File = exports.FileExceptionMessages = exports.RequestError = exports.STORAGE_POST_POLICY_BASE_URL = exports.ActionToHTTPMethod = void 0;
49const index_js_1 = require("./nodejs-common/index.js");
50const promisify_1 = require("@google-cloud/promisify");
51const crypto = __importStar(require("crypto"));
52const fs = __importStar(require("fs"));
53const mime_1 = __importDefault(require("mime"));
54const resumableUpload = __importStar(require("./resumable-upload.js"));
55const stream_1 = require("stream");
56const zlib = __importStar(require("zlib"));
57const storage_js_1 = require("./storage.js");
58const bucket_js_1 = require("./bucket.js");
59const acl_js_1 = require("./acl.js");
60const signer_js_1 = require("./signer.js");
61const util_js_1 = require("./nodejs-common/util.js");
62const duplexify_1 = __importDefault(require("duplexify"));
63const util_js_2 = require("./util.js");
64const crc32c_js_1 = require("./crc32c.js");
65const hash_stream_validator_js_1 = require("./hash-stream-validator.js");
66const async_retry_1 = __importDefault(require("async-retry"));
67var ActionToHTTPMethod;
68(function (ActionToHTTPMethod) {
69 ActionToHTTPMethod["read"] = "GET";
70 ActionToHTTPMethod["write"] = "PUT";
71 ActionToHTTPMethod["delete"] = "DELETE";
72 ActionToHTTPMethod["resumable"] = "POST";
73})(ActionToHTTPMethod || (exports.ActionToHTTPMethod = ActionToHTTPMethod = {}));
74/**
75 * @deprecated - no longer used
76 */
77exports.STORAGE_POST_POLICY_BASE_URL = 'https://storage.googleapis.com';
78/**
79 * @private
80 */
81const GS_URL_REGEXP = /^gs:\/\/([a-z0-9_.-]+)\/(.+)$/;
82/**
83 * @private
84 * This regex will match compressible content types. These are primarily text/*, +json, +text, +xml content types.
85 * This was based off of mime-db and may periodically need to be updated if new compressible content types become
86 * standards.
87 */
88const COMPRESSIBLE_MIME_REGEX = new RegExp([
89 /^text\/|application\/ecmascript|application\/javascript|application\/json/,
90 /|application\/postscript|application\/rtf|application\/toml|application\/vnd.dart/,
91 /|application\/vnd.ms-fontobject|application\/wasm|application\/x-httpd-php|application\/x-ns-proxy-autoconfig/,
92 /|application\/x-sh(?!ockwave-flash)|application\/x-tar|application\/x-virtualbox-hdd|application\/x-virtualbox-ova|application\/x-virtualbox-ovf/,
93 /|^application\/x-virtualbox-vbox$|application\/x-virtualbox-vdi|application\/x-virtualbox-vhd|application\/x-virtualbox-vmdk/,
94 /|application\/xml|application\/xml-dtd|font\/otf|font\/ttf|image\/bmp|image\/vnd.adobe.photoshop|image\/vnd.microsoft.icon/,
95 /|image\/vnd.ms-dds|image\/x-icon|image\/x-ms-bmp|message\/rfc822|model\/gltf-binary|\+json|\+text|\+xml|\+yaml/,
96]
97 .map(r => r.source)
98 .join(''), 'i');
99class RequestError extends Error {
100}
101exports.RequestError = RequestError;
102const SEVEN_DAYS = 7 * 24 * 60 * 60;
103const GS_UTIL_URL_REGEX = /(gs):\/\/([a-z0-9_.-]+)\/(.+)/g;
104const HTTPS_PUBLIC_URL_REGEX = /(https):\/\/(storage\.googleapis\.com)\/([a-z0-9_.-]+)\/(.+)/g;
105var FileExceptionMessages;
106(function (FileExceptionMessages) {
107 FileExceptionMessages["EXPIRATION_TIME_NA"] = "An expiration time is not available.";
108 FileExceptionMessages["DESTINATION_NO_NAME"] = "Destination file should have a name.";
109 FileExceptionMessages["INVALID_VALIDATION_FILE_RANGE"] = "Cannot use validation with file ranges (start/end).";
110 FileExceptionMessages["MD5_NOT_AVAILABLE"] = "MD5 verification was specified, but is not available for the requested object. MD5 is not available for composite objects.";
111 FileExceptionMessages["EQUALS_CONDITION_TWO_ELEMENTS"] = "Equals condition must be an array of 2 elements.";
112 FileExceptionMessages["STARTS_WITH_TWO_ELEMENTS"] = "StartsWith condition must be an array of 2 elements.";
113 FileExceptionMessages["CONTENT_LENGTH_RANGE_MIN_MAX"] = "ContentLengthRange must have numeric min & max fields.";
114 FileExceptionMessages["DOWNLOAD_MISMATCH"] = "The downloaded data did not match the data from the server. To be sure the content is the same, you should download the file again.";
115 FileExceptionMessages["UPLOAD_MISMATCH_DELETE_FAIL"] = "The uploaded data did not match the data from the server.\n As a precaution, we attempted to delete the file, but it was not successful.\n To be sure the content is the same, you should try removing the file manually,\n then uploading the file again.\n \n\nThe delete attempt failed with this message:\n\n ";
116 FileExceptionMessages["UPLOAD_MISMATCH"] = "The uploaded data did not match the data from the server.\n As a precaution, the file has been deleted.\n To be sure the content is the same, you should try uploading the file again.";
117 FileExceptionMessages["MD5_RESUMED_UPLOAD"] = "MD5 cannot be used with a continued resumable upload as MD5 cannot be extended from an existing value";
118 FileExceptionMessages["MISSING_RESUME_CRC32C_FINAL_UPLOAD"] = "The CRC32C is missing for the final portion of a resumed upload, which is required for validation. Please provide `resumeCRC32C` if validation is required, or disable `validation`.";
119})(FileExceptionMessages || (exports.FileExceptionMessages = FileExceptionMessages = {}));
120/**
121 * A File object is created from your {@link Bucket} object using
122 * {@link Bucket#file}.
123 *
124 * @class
125 */
126class File extends index_js_1.ServiceObject {
127 /**
128 * Cloud Storage uses access control lists (ACLs) to manage object and
129 * bucket access. ACLs are the mechanism you use to share objects with other
130 * users and allow other users to access your buckets and objects.
131 *
132 * An ACL consists of one or more entries, where each entry grants permissions
133 * to an entity. Permissions define the actions that can be performed against
134 * an object or bucket (for example, `READ` or `WRITE`); the entity defines
135 * who the permission applies to (for example, a specific user or group of
136 * users).
137 *
138 * The `acl` object on a File instance provides methods to get you a list of
139 * the ACLs defined on your bucket, as well as set, update, and delete them.
140 *
141 * See {@link http://goo.gl/6qBBPO| About Access Control lists}
142 *
143 * @name File#acl
144 * @mixes Acl
145 *
146 * @example
147 * ```
148 * const {Storage} = require('@google-cloud/storage');
149 * const storage = new Storage();
150 * const myBucket = storage.bucket('my-bucket');
151 *
152 * const file = myBucket.file('my-file');
153 * //-
154 * // Make a file publicly readable.
155 * //-
156 * const options = {
157 * entity: 'allUsers',
158 * role: storage.acl.READER_ROLE
159 * };
160 *
161 * file.acl.add(options, function(err, aclObject) {});
162 *
163 * //-
164 * // If the callback is omitted, we'll return a Promise.
165 * //-
166 * file.acl.add(options).then(function(data) {
167 * const aclObject = data[0];
168 * const apiResponse = data[1];
169 * });
170 * ```
171 */
172 /**
173 * The API-formatted resource description of the file.
174 *
175 * Note: This is not guaranteed to be up-to-date when accessed. To get the
176 * latest record, call the `getMetadata()` method.
177 *
178 * @name File#metadata
179 * @type {object}
180 */
181 /**
182 * The file's name.
183 * @name File#name
184 * @type {string}
185 */
186 /**
187 * @callback Crc32cGeneratorToStringCallback
188 * A method returning the CRC32C as a base64-encoded string.
189 *
190 * @returns {string}
191 *
192 * @example
193 * Hashing the string 'data' should return 'rth90Q=='
194 *
195 * ```js
196 * const buffer = Buffer.from('data');
197 * crc32c.update(buffer);
198 * crc32c.toString(); // 'rth90Q=='
199 * ```
200 **/
201 /**
202 * @callback Crc32cGeneratorValidateCallback
203 * A method validating a base64-encoded CRC32C string.
204 *
205 * @param {string} [value] base64-encoded CRC32C string to validate
206 * @returns {boolean}
207 *
208 * @example
209 * Should return `true` if the value matches, `false` otherwise
210 *
211 * ```js
212 * const buffer = Buffer.from('data');
213 * crc32c.update(buffer);
214 * crc32c.validate('DkjKuA=='); // false
215 * crc32c.validate('rth90Q=='); // true
216 * ```
217 **/
218 /**
219 * @callback Crc32cGeneratorUpdateCallback
220 * A method for passing `Buffer`s for CRC32C generation.
221 *
222 * @param {Buffer} [data] data to update CRC32C value with
223 * @returns {undefined}
224 *
225 * @example
226 * Hashing buffers from 'some ' and 'text\n'
227 *
228 * ```js
229 * const buffer1 = Buffer.from('some ');
230 * crc32c.update(buffer1);
231 *
232 * const buffer2 = Buffer.from('text\n');
233 * crc32c.update(buffer2);
234 *
235 * crc32c.toString(); // 'DkjKuA=='
236 * ```
237 **/
238 /**
239 * @typedef {object} CRC32CValidator
240 * @property {Crc32cGeneratorToStringCallback}
241 * @property {Crc32cGeneratorValidateCallback}
242 * @property {Crc32cGeneratorUpdateCallback}
243 */
244 /**
245 * @callback Crc32cGeneratorCallback
246 * @returns {CRC32CValidator}
247 */
248 /**
249 * @typedef {object} FileOptions Options passed to the File constructor.
250 * @property {string} [encryptionKey] A custom encryption key.
251 * @property {number} [generation] Generation to scope the file to.
252 * @property {string} [kmsKeyName] Cloud KMS Key used to encrypt this
253 * object, if the object is encrypted by such a key. Limited availability;
254 * usable only by enabled projects.
255 * @property {string} [userProject] The ID of the project which will be
256 * billed for all requests made from File object.
257 * @property {Crc32cGeneratorCallback} [callback] A function that generates a CRC32C Validator. Defaults to {@link CRC32C}
258 */
259 /**
260 * Constructs a file object.
261 *
262 * @param {Bucket} bucket The Bucket instance this file is
263 * attached to.
264 * @param {string} name The name of the remote file.
265 * @param {FileOptions} [options] Configuration options.
266 * @example
267 * ```
268 * const {Storage} = require('@google-cloud/storage');
269 * const storage = new Storage();
270 * const myBucket = storage.bucket('my-bucket');
271 *
272 * const file = myBucket.file('my-file');
273 * ```
274 */
275 constructor(bucket, name, options = {}) {
276 var _a, _b;
277 const requestQueryObject = {};
278 let generation;
279 if (options.generation !== null) {
280 if (typeof options.generation === 'string') {
281 generation = Number(options.generation);
282 }
283 else {
284 generation = options.generation;
285 }
286 if (!isNaN(generation)) {
287 requestQueryObject.generation = generation;
288 }
289 }
290 Object.assign(requestQueryObject, options.preconditionOpts);
291 const userProject = options.userProject || bucket.userProject;
292 if (typeof userProject === 'string') {
293 requestQueryObject.userProject = userProject;
294 }
295 const methods = {
296 /**
297 * @typedef {array} DeleteFileResponse
298 * @property {object} 0 The full API response.
299 */
300 /**
301 * @callback DeleteFileCallback
302 * @param {?Error} err Request error, if any.
303 * @param {object} apiResponse The full API response.
304 */
305 /**
306 * Delete the file.
307 *
308 * See {@link https://cloud.google.com/storage/docs/json_api/v1/objects/delete| Objects: delete API Documentation}
309 *
310 * @method File#delete
311 * @param {object} [options] Configuration options.
312 * @param {boolean} [options.ignoreNotFound = false] Ignore an error if
313 * the file does not exist.
314 * @param {string} [options.userProject] The ID of the project which will be
315 * billed for the request.
316 * @param {DeleteFileCallback} [callback] Callback function.
317 * @returns {Promise<DeleteFileResponse>}
318 *
319 * @example
320 * ```
321 * const {Storage} = require('@google-cloud/storage');
322 * const storage = new Storage();
323 * const myBucket = storage.bucket('my-bucket');
324 *
325 * const file = myBucket.file('my-file');
326 * file.delete(function(err, apiResponse) {});
327 *
328 * //-
329 * // If the callback is omitted, we'll return a Promise.
330 * //-
331 * file.delete().then(function(data) {
332 * const apiResponse = data[0];
333 * });
334 *
335 * ```
336 * @example <caption>include:samples/files.js</caption>
337 * region_tag:storage_delete_file
338 * Another example:
339 */
340 delete: {
341 reqOpts: {
342 qs: requestQueryObject,
343 },
344 },
345 /**
346 * @typedef {array} FileExistsResponse
347 * @property {boolean} 0 Whether the {@link File} exists.
348 */
349 /**
350 * @callback FileExistsCallback
351 * @param {?Error} err Request error, if any.
352 * @param {boolean} exists Whether the {@link File} exists.
353 */
354 /**
355 * Check if the file exists.
356 *
357 * @method File#exists
358 * @param {options} [options] Configuration options.
359 * @param {string} [options.userProject] The ID of the project which will be
360 * billed for the request.
361 * @param {FileExistsCallback} [callback] Callback function.
362 * @returns {Promise<FileExistsResponse>}
363 *
364 * @example
365 * ```
366 * const {Storage} = require('@google-cloud/storage');
367 * const storage = new Storage();
368 * const myBucket = storage.bucket('my-bucket');
369 *
370 * const file = myBucket.file('my-file');
371 *
372 * file.exists(function(err, exists) {});
373 *
374 * //-
375 * // If the callback is omitted, we'll return a Promise.
376 * //-
377 * file.exists().then(function(data) {
378 * const exists = data[0];
379 * });
380 * ```
381 */
382 exists: {
383 reqOpts: {
384 qs: requestQueryObject,
385 },
386 },
387 /**
388 * @typedef {array} GetFileResponse
389 * @property {File} 0 The {@link File}.
390 * @property {object} 1 The full API response.
391 */
392 /**
393 * @callback GetFileCallback
394 * @param {?Error} err Request error, if any.
395 * @param {File} file The {@link File}.
396 * @param {object} apiResponse The full API response.
397 */
398 /**
399 * Get a file object and its metadata if it exists.
400 *
401 * @method File#get
402 * @param {options} [options] Configuration options.
403 * @param {string} [options.userProject] The ID of the project which will be
404 * billed for the request.
405 * @param {number} [options.generation] The generation number to get
406 * @param {boolean} [options.softDeleted] If true, returns the soft-deleted object.
407 Object `generation` is required if `softDeleted` is set to True.
408 * @param {GetFileCallback} [callback] Callback function.
409 * @returns {Promise<GetFileResponse>}
410 *
411 * @example
412 * ```
413 * const {Storage} = require('@google-cloud/storage');
414 * const storage = new Storage();
415 * const myBucket = storage.bucket('my-bucket');
416 *
417 * const file = myBucket.file('my-file');
418 *
419 * file.get(function(err, file, apiResponse) {
420 * // file.metadata` has been populated.
421 * });
422 *
423 * //-
424 * // If the callback is omitted, we'll return a Promise.
425 * //-
426 * file.get().then(function(data) {
427 * const file = data[0];
428 * const apiResponse = data[1];
429 * });
430 * ```
431 */
432 get: {
433 reqOpts: {
434 qs: requestQueryObject,
435 },
436 },
437 /**
438 * @typedef {array} GetFileMetadataResponse
439 * @property {object} 0 The {@link File} metadata.
440 * @property {object} 1 The full API response.
441 */
442 /**
443 * @callback GetFileMetadataCallback
444 * @param {?Error} err Request error, if any.
445 * @param {object} metadata The {@link File} metadata.
446 * @param {object} apiResponse The full API response.
447 */
448 /**
449 * Get the file's metadata.
450 *
451 * See {@link https://cloud.google.com/storage/docs/json_api/v1/objects/get| Objects: get API Documentation}
452 *
453 * @method File#getMetadata
454 * @param {object} [options] Configuration options.
455 * @param {string} [options.userProject] The ID of the project which will be
456 * billed for the request.
457 * @param {GetFileMetadataCallback} [callback] Callback function.
458 * @returns {Promise<GetFileMetadataResponse>}
459 *
460 * @example
461 * ```
462 * const {Storage} = require('@google-cloud/storage');
463 * const storage = new Storage();
464 * const myBucket = storage.bucket('my-bucket');
465 *
466 * const file = myBucket.file('my-file');
467 *
468 * file.getMetadata(function(err, metadata, apiResponse) {});
469 *
470 * //-
471 * // If the callback is omitted, we'll return a Promise.
472 * //-
473 * file.getMetadata().then(function(data) {
474 * const metadata = data[0];
475 * const apiResponse = data[1];
476 * });
477 *
478 * ```
479 * @example <caption>include:samples/files.js</caption>
480 * region_tag:storage_get_metadata
481 * Another example:
482 */
483 getMetadata: {
484 reqOpts: {
485 qs: requestQueryObject,
486 },
487 },
488 /**
489 * @typedef {object} SetFileMetadataOptions Configuration options for File#setMetadata().
490 * @param {string} [userProject] The ID of the project which will be billed for the request.
491 */
492 /**
493 * @callback SetFileMetadataCallback
494 * @param {?Error} err Request error, if any.
495 * @param {object} apiResponse The full API response.
496 */
497 /**
498 * @typedef {array} SetFileMetadataResponse
499 * @property {object} 0 The full API response.
500 */
501 /**
502 * Merge the given metadata with the current remote file's metadata. This
503 * will set metadata if it was previously unset or update previously set
504 * metadata. To unset previously set metadata, set its value to null.
505 *
506 * You can set custom key/value pairs in the metadata key of the given
507 * object, however the other properties outside of this object must adhere
508 * to the {@link https://goo.gl/BOnnCK| official API documentation}.
509 *
510 *
511 * See the examples below for more information.
512 *
513 * See {@link https://cloud.google.com/storage/docs/json_api/v1/objects/patch| Objects: patch API Documentation}
514 *
515 * @method File#setMetadata
516 * @param {object} [metadata] The metadata you wish to update.
517 * @param {SetFileMetadataOptions} [options] Configuration options.
518 * @param {SetFileMetadataCallback} [callback] Callback function.
519 * @returns {Promise<SetFileMetadataResponse>}
520 *
521 * @example
522 * ```
523 * const {Storage} = require('@google-cloud/storage');
524 * const storage = new Storage();
525 * const myBucket = storage.bucket('my-bucket');
526 *
527 * const file = myBucket.file('my-file');
528 *
529 * const metadata = {
530 * contentType: 'application/x-font-ttf',
531 * metadata: {
532 * my: 'custom',
533 * properties: 'go here'
534 * }
535 * };
536 *
537 * file.setMetadata(metadata, function(err, apiResponse) {});
538 *
539 * // Assuming current metadata = { hello: 'world', unsetMe: 'will do' }
540 * file.setMetadata({
541 * metadata: {
542 * abc: '123', // will be set.
543 * unsetMe: null, // will be unset (deleted).
544 * hello: 'goodbye' // will be updated from 'world' to 'goodbye'.
545 * }
546 * }, function(err, apiResponse) {
547 * // metadata should now be { abc: '123', hello: 'goodbye' }
548 * });
549 *
550 * //-
551 * // Set a temporary hold on this file from its bucket's retention period
552 * // configuration.
553 * //
554 * file.setMetadata({
555 * temporaryHold: true
556 * }, function(err, apiResponse) {});
557 *
558 * //-
559 * // Alternatively, you may set a temporary hold. This will follow the
560 * // same behavior as an event-based hold, with the exception that the
561 * // bucket's retention policy will not renew for this file from the time
562 * // the hold is released.
563 * //-
564 * file.setMetadata({
565 * eventBasedHold: true
566 * }, function(err, apiResponse) {});
567 *
568 * //-
569 * // If the callback is omitted, we'll return a Promise.
570 * //-
571 * file.setMetadata(metadata).then(function(data) {
572 * const apiResponse = data[0];
573 * });
574 * ```
575 */
576 setMetadata: {
577 reqOpts: {
578 qs: requestQueryObject,
579 },
580 },
581 };
582 super({
583 parent: bucket,
584 baseUrl: '/o',
585 id: encodeURIComponent(name),
586 methods,
587 });
588 _File_instances.add(this);
589 this.bucket = bucket;
590 // eslint-disable-next-line @typescript-eslint/no-explicit-any
591 this.storage = bucket.parent;
592 // @TODO Can this duplicate code from above be avoided?
593 if (options.generation !== null) {
594 let generation;
595 if (typeof options.generation === 'string') {
596 generation = Number(options.generation);
597 }
598 else {
599 generation = options.generation;
600 }
601 if (!isNaN(generation)) {
602 this.generation = generation;
603 }
604 }
605 this.kmsKeyName = options.kmsKeyName;
606 this.userProject = userProject;
607 this.name = name;
608 if (options.encryptionKey) {
609 this.setEncryptionKey(options.encryptionKey);
610 }
611 this.acl = new acl_js_1.Acl({
612 request: this.request.bind(this),
613 pathPrefix: '/acl',
614 });
615 this.crc32cGenerator =
616 options.crc32cGenerator || this.bucket.crc32cGenerator;
617 this.instanceRetryValue = (_b = (_a = this.storage) === null || _a === void 0 ? void 0 : _a.retryOptions) === null || _b === void 0 ? void 0 : _b.autoRetry;
618 this.instancePreconditionOpts = options === null || options === void 0 ? void 0 : options.preconditionOpts;
619 }
620 /**
621 * The object's Cloud Storage URI (`gs://`)
622 *
623 * @example
624 * ```ts
625 * const {Storage} = require('@google-cloud/storage');
626 * const storage = new Storage();
627 * const bucket = storage.bucket('my-bucket');
628 * const file = bucket.file('image.png');
629 *
630 * // `gs://my-bucket/image.png`
631 * const href = file.cloudStorageURI.href;
632 * ```
633 */
634 get cloudStorageURI() {
635 const uri = this.bucket.cloudStorageURI;
636 uri.pathname = this.name;
637 return uri;
638 }
639 /**
640 * A helper method for determining if a request should be retried based on preconditions.
641 * This should only be used for methods where the idempotency is determined by
642 * `ifGenerationMatch`
643 * @private
644 *
645 * A request should not be retried under the following conditions:
646 * - if precondition option `ifGenerationMatch` is not set OR
647 * - if `idempotencyStrategy` is set to `RetryNever`
648 */
649 shouldRetryBasedOnPreconditionAndIdempotencyStrat(options) {
650 var _a;
651 return !(((options === null || options === void 0 ? void 0 : options.ifGenerationMatch) === undefined &&
652 ((_a = this.instancePreconditionOpts) === null || _a === void 0 ? void 0 : _a.ifGenerationMatch) === undefined &&
653 this.storage.retryOptions.idempotencyStrategy ===
654 storage_js_1.IdempotencyStrategy.RetryConditional) ||
655 this.storage.retryOptions.idempotencyStrategy ===
656 storage_js_1.IdempotencyStrategy.RetryNever);
657 }
658 /**
659 * @typedef {array} CopyResponse
660 * @property {File} 0 The copied {@link File}.
661 * @property {object} 1 The full API response.
662 */
663 /**
664 * @callback CopyCallback
665 * @param {?Error} err Request error, if any.
666 * @param {File} copiedFile The copied {@link File}.
667 * @param {object} apiResponse The full API response.
668 */
669 /**
670 * @typedef {object} CopyOptions Configuration options for File#copy(). See an
671 * {@link https://cloud.google.com/storage/docs/json_api/v1/objects#resource| Object resource}.
672 * @property {string} [cacheControl] The cacheControl setting for the new file.
673 * @property {string} [contentEncoding] The contentEncoding setting for the new file.
674 * @property {string} [contentType] The contentType setting for the new file.
675 * @property {string} [destinationKmsKeyName] Resource name of the Cloud
676 * KMS key, of the form
677 * `projects/my-project/locations/location/keyRings/my-kr/cryptoKeys/my-key`,
678 * that will be used to encrypt the object. Overwrites the object
679 * metadata's `kms_key_name` value, if any.
680 * @property {Metadata} [metadata] Metadata to specify on the copied file.
681 * @property {string} [predefinedAcl] Set the ACL for the new file.
682 * @property {string} [token] A previously-returned `rewriteToken` from an
683 * unfinished rewrite request.
684 * @property {string} [userProject] The ID of the project which will be
685 * billed for the request.
686 */
687 /**
688 * Copy this file to another file. By default, this will copy the file to the
689 * same bucket, but you can choose to copy it to another Bucket by providing
690 * a Bucket or File object or a URL starting with "gs://".
691 * The generation of the file will not be preserved.
692 *
693 * See {@link https://cloud.google.com/storage/docs/json_api/v1/objects/rewrite| Objects: rewrite API Documentation}
694 *
695 * @throws {Error} If the destination file is not provided.
696 *
697 * @param {string|Bucket|File} destination Destination file.
698 * @param {CopyOptions} [options] Configuration options. See an
699 * @param {CopyCallback} [callback] Callback function.
700 * @returns {Promise<CopyResponse>}
701 *
702 * @example
703 * ```
704 * const {Storage} = require('@google-cloud/storage');
705 * const storage = new Storage();
706 *
707 * //-
708 * // You can pass in a variety of types for the destination.
709 * //
710 * // For all of the below examples, assume we are working with the following
711 * // Bucket and File objects.
712 * //-
713 * const bucket = storage.bucket('my-bucket');
714 * const file = bucket.file('my-image.png');
715 *
716 * //-
717 * // If you pass in a string for the destination, the file is copied to its
718 * // current bucket, under the new name provided.
719 * //-
720 * file.copy('my-image-copy.png', function(err, copiedFile, apiResponse) {
721 * // `my-bucket` now contains:
722 * // - "my-image.png"
723 * // - "my-image-copy.png"
724 *
725 * // `copiedFile` is an instance of a File object that refers to your new
726 * // file.
727 * });
728 *
729 * //-
730 * // If you pass in a string starting with "gs://" for the destination, the
731 * // file is copied to the other bucket and under the new name provided.
732 * //-
733 * const newLocation = 'gs://another-bucket/my-image-copy.png';
734 * file.copy(newLocation, function(err, copiedFile, apiResponse) {
735 * // `my-bucket` still contains:
736 * // - "my-image.png"
737 * //
738 * // `another-bucket` now contains:
739 * // - "my-image-copy.png"
740 *
741 * // `copiedFile` is an instance of a File object that refers to your new
742 * // file.
743 * });
744 *
745 * //-
746 * // If you pass in a Bucket object, the file will be copied to that bucket
747 * // using the same name.
748 * //-
749 * const anotherBucket = storage.bucket('another-bucket');
750 * file.copy(anotherBucket, function(err, copiedFile, apiResponse) {
751 * // `my-bucket` still contains:
752 * // - "my-image.png"
753 * //
754 * // `another-bucket` now contains:
755 * // - "my-image.png"
756 *
757 * // `copiedFile` is an instance of a File object that refers to your new
758 * // file.
759 * });
760 *
761 * //-
762 * // If you pass in a File object, you have complete control over the new
763 * // bucket and filename.
764 * //-
765 * const anotherFile = anotherBucket.file('my-awesome-image.png');
766 * file.copy(anotherFile, function(err, copiedFile, apiResponse) {
767 * // `my-bucket` still contains:
768 * // - "my-image.png"
769 * //
770 * // `another-bucket` now contains:
771 * // - "my-awesome-image.png"
772 *
773 * // Note:
774 * // The `copiedFile` parameter is equal to `anotherFile`.
775 * });
776 *
777 * //-
778 * // If the callback is omitted, we'll return a Promise.
779 * //-
780 * file.copy(newLocation).then(function(data) {
781 * const newFile = data[0];
782 * const apiResponse = data[1];
783 * });
784 *
785 * ```
786 * @example <caption>include:samples/files.js</caption>
787 * region_tag:storage_copy_file
788 * Another example:
789 */
790 copy(destination, optionsOrCallback, callback) {
791 var _a, _b;
792 const noDestinationError = new Error(FileExceptionMessages.DESTINATION_NO_NAME);
793 if (!destination) {
794 throw noDestinationError;
795 }
796 let options = {};
797 if (typeof optionsOrCallback === 'function') {
798 callback = optionsOrCallback;
799 }
800 else if (optionsOrCallback) {
801 options = { ...optionsOrCallback };
802 }
803 callback = callback || index_js_1.util.noop;
804 let destBucket;
805 let destName;
806 let newFile;
807 if (typeof destination === 'string') {
808 const parsedDestination = GS_URL_REGEXP.exec(destination);
809 if (parsedDestination !== null && parsedDestination.length === 3) {
810 destBucket = this.storage.bucket(parsedDestination[1]);
811 destName = parsedDestination[2];
812 }
813 else {
814 destBucket = this.bucket;
815 destName = destination;
816 }
817 }
818 else if (destination instanceof bucket_js_1.Bucket) {
819 destBucket = destination;
820 destName = this.name;
821 }
822 else if (destination instanceof File) {
823 destBucket = destination.bucket;
824 destName = destination.name;
825 newFile = destination;
826 }
827 else {
828 throw noDestinationError;
829 }
830 const query = {};
831 if (this.generation !== undefined) {
832 query.sourceGeneration = this.generation;
833 }
834 if (options.token !== undefined) {
835 query.rewriteToken = options.token;
836 }
837 if (options.userProject !== undefined) {
838 query.userProject = options.userProject;
839 delete options.userProject;
840 }
841 if (options.predefinedAcl !== undefined) {
842 query.destinationPredefinedAcl = options.predefinedAcl;
843 delete options.predefinedAcl;
844 }
845 newFile = newFile || destBucket.file(destName);
846 const headers = {};
847 if (this.encryptionKey !== undefined) {
848 headers['x-goog-copy-source-encryption-algorithm'] = 'AES256';
849 headers['x-goog-copy-source-encryption-key'] = this.encryptionKeyBase64;
850 headers['x-goog-copy-source-encryption-key-sha256'] =
851 this.encryptionKeyHash;
852 }
853 if (newFile.encryptionKey !== undefined) {
854 this.setEncryptionKey(newFile.encryptionKey);
855 }
856 else if (options.destinationKmsKeyName !== undefined) {
857 query.destinationKmsKeyName = options.destinationKmsKeyName;
858 delete options.destinationKmsKeyName;
859 }
860 else if (newFile.kmsKeyName !== undefined) {
861 query.destinationKmsKeyName = newFile.kmsKeyName;
862 }
863 if (query.destinationKmsKeyName) {
864 this.kmsKeyName = query.destinationKmsKeyName;
865 const keyIndex = this.interceptors.indexOf(this.encryptionKeyInterceptor);
866 if (keyIndex > -1) {
867 this.interceptors.splice(keyIndex, 1);
868 }
869 }
870 if (!this.shouldRetryBasedOnPreconditionAndIdempotencyStrat(options === null || options === void 0 ? void 0 : options.preconditionOpts)) {
871 this.storage.retryOptions.autoRetry = false;
872 }
873 if (((_a = options.preconditionOpts) === null || _a === void 0 ? void 0 : _a.ifGenerationMatch) !== undefined) {
874 query.ifGenerationMatch = (_b = options.preconditionOpts) === null || _b === void 0 ? void 0 : _b.ifGenerationMatch;
875 delete options.preconditionOpts;
876 }
877 this.request({
878 method: 'POST',
879 uri: `/rewriteTo/b/${destBucket.name}/o/${encodeURIComponent(newFile.name)}`,
880 qs: query,
881 json: options,
882 headers,
883 }, (err, resp) => {
884 this.storage.retryOptions.autoRetry = this.instanceRetryValue;
885 if (err) {
886 callback(err, null, resp);
887 return;
888 }
889 if (resp.rewriteToken) {
890 const options = {
891 token: resp.rewriteToken,
892 };
893 if (query.userProject) {
894 options.userProject = query.userProject;
895 }
896 if (query.destinationKmsKeyName) {
897 options.destinationKmsKeyName = query.destinationKmsKeyName;
898 }
899 this.copy(newFile, options, callback);
900 return;
901 }
902 callback(null, newFile, resp);
903 });
904 }
905 /**
906 * @typedef {object} CreateReadStreamOptions Configuration options for File#createReadStream.
907 * @property {string} [userProject] The ID of the project which will be
908 * billed for the request.
909 * @property {string|boolean} [validation] Possible values: `"md5"`,
910 * `"crc32c"`, or `false`. By default, data integrity is validated with a
911 * CRC32c checksum. You may use MD5 if preferred, but that hash is not
912 * supported for composite objects. An error will be raised if MD5 is
913 * specified but is not available. You may also choose to skip validation
914 * completely, however this is **not recommended**.
915 * @property {number} [start] A byte offset to begin the file's download
916 * from. Default is 0. NOTE: Byte ranges are inclusive; that is,
917 * `options.start = 0` and `options.end = 999` represent the first 1000
918 * bytes in a file or object. NOTE: when specifying a byte range, data
919 * integrity is not available.
920 * @property {number} [end] A byte offset to stop reading the file at.
921 * NOTE: Byte ranges are inclusive; that is, `options.start = 0` and
922 * `options.end = 999` represent the first 1000 bytes in a file or object.
923 * NOTE: when specifying a byte range, data integrity is not available.
924 * @property {boolean} [decompress=true] Disable auto decompression of the
925 * received data. By default this option is set to `true`.
926 * Applicable in cases where the data was uploaded with
927 * `gzip: true` option. See {@link File#createWriteStream}.
928 */
929 /**
930 * Create a readable stream to read the contents of the remote file. It can be
931 * piped to a writable stream or listened to for 'data' events to read a
932 * file's contents.
933 *
934 * In the unlikely event there is a mismatch between what you downloaded and
935 * the version in your Bucket, your error handler will receive an error with
936 * code "CONTENT_DOWNLOAD_MISMATCH". If you receive this error, the best
937 * recourse is to try downloading the file again.
938 *
939 * NOTE: Readable streams will emit the `end` event when the file is fully
940 * downloaded.
941 *
942 * @param {CreateReadStreamOptions} [options] Configuration options.
943 * @returns {ReadableStream}
944 *
945 * @example
946 * ```
947 * //-
948 * // <h4>Downloading a File</h4>
949 * //
950 * // The example below demonstrates how we can reference a remote file, then
951 * // pipe its contents to a local file. This is effectively creating a local
952 * // backup of your remote data.
953 * //-
954 * const {Storage} = require('@google-cloud/storage');
955 * const storage = new Storage();
956 * const bucket = storage.bucket('my-bucket');
957 *
958 * const fs = require('fs');
959 * const remoteFile = bucket.file('image.png');
960 * const localFilename = '/Users/stephen/Photos/image.png';
961 *
962 * remoteFile.createReadStream()
963 * .on('error', function(err) {})
964 * .on('response', function(response) {
965 * // Server connected and responded with the specified status and headers.
966 * })
967 * .on('end', function() {
968 * // The file is fully downloaded.
969 * })
970 * .pipe(fs.createWriteStream(localFilename));
971 *
972 * //-
973 * // To limit the downloaded data to only a byte range, pass an options
974 * // object.
975 * //-
976 * const logFile = myBucket.file('access_log');
977 * logFile.createReadStream({
978 * start: 10000,
979 * end: 20000
980 * })
981 * .on('error', function(err) {})
982 * .pipe(fs.createWriteStream('/Users/stephen/logfile.txt'));
983 *
984 * //-
985 * // To read a tail byte range, specify only `options.end` as a negative
986 * // number.
987 * //-
988 * const logFile = myBucket.file('access_log');
989 * logFile.createReadStream({
990 * end: -100
991 * })
992 * .on('error', function(err) {})
993 * .pipe(fs.createWriteStream('/Users/stephen/logfile.txt'));
994 * ```
995 */
996 createReadStream(options = {}) {
997 options = Object.assign({ decompress: true }, options);
998 const rangeRequest = typeof options.start === 'number' || typeof options.end === 'number';
999 const tailRequest = options.end < 0;
1000 let validateStream = undefined;
1001 let request = undefined;
1002 const throughStream = new util_js_2.PassThroughShim();
1003 let crc32c = true;
1004 let md5 = false;
1005 if (typeof options.validation === 'string') {
1006 const value = options.validation.toLowerCase().trim();
1007 crc32c = value === 'crc32c';
1008 md5 = value === 'md5';
1009 }
1010 else if (options.validation === false) {
1011 crc32c = false;
1012 }
1013 const shouldRunValidation = !rangeRequest && (crc32c || md5);
1014 if (rangeRequest) {
1015 if (typeof options.validation === 'string' ||
1016 options.validation === true) {
1017 throw new Error(FileExceptionMessages.INVALID_VALIDATION_FILE_RANGE);
1018 }
1019 // Range requests can't receive data integrity checks.
1020 crc32c = false;
1021 md5 = false;
1022 }
1023 const onComplete = (err) => {
1024 if (err) {
1025 // There is an issue with node-fetch 2.x that if the stream errors the underlying socket connection is not closed.
1026 // This causes a memory leak, so cleanup the sockets manually here by destroying the agent.
1027 if (request === null || request === void 0 ? void 0 : request.agent) {
1028 request.agent.destroy();
1029 }
1030 throughStream.destroy(err);
1031 }
1032 };
1033 // We listen to the response event from the request stream so that we
1034 // can...
1035 //
1036 // 1) Intercept any data from going to the user if an error occurred.
1037 // 2) Calculate the hashes from the http.IncomingMessage response
1038 // stream,
1039 // which will return the bytes from the source without decompressing
1040 // gzip'd content. We then send it through decompressed, if
1041 // applicable, to the user.
1042 const onResponse = (err, _body, rawResponseStream) => {
1043 if (err) {
1044 // Get error message from the body.
1045 this.getBufferFromReadable(rawResponseStream).then(body => {
1046 err.message = body.toString('utf8');
1047 throughStream.destroy(err);
1048 });
1049 return;
1050 }
1051 request = rawResponseStream.request;
1052 const headers = rawResponseStream.toJSON().headers;
1053 const isCompressed = headers['content-encoding'] === 'gzip';
1054 const hashes = {};
1055 // The object is safe to validate if:
1056 // 1. It was stored gzip and returned to us gzip OR
1057 // 2. It was never stored as gzip
1058 const safeToValidate = (headers['x-goog-stored-content-encoding'] === 'gzip' &&
1059 isCompressed) ||
1060 headers['x-goog-stored-content-encoding'] === 'identity';
1061 const transformStreams = [];
1062 if (shouldRunValidation) {
1063 // The x-goog-hash header should be set with a crc32c and md5 hash.
1064 // ex: headers['x-goog-hash'] = 'crc32c=xxxx,md5=xxxx'
1065 if (typeof headers['x-goog-hash'] === 'string') {
1066 headers['x-goog-hash']
1067 .split(',')
1068 .forEach((hashKeyValPair) => {
1069 const delimiterIndex = hashKeyValPair.indexOf('=');
1070 const hashType = hashKeyValPair.substring(0, delimiterIndex);
1071 const hashValue = hashKeyValPair.substring(delimiterIndex + 1);
1072 hashes[hashType] = hashValue;
1073 });
1074 }
1075 validateStream = new hash_stream_validator_js_1.HashStreamValidator({
1076 crc32c,
1077 md5,
1078 crc32cGenerator: this.crc32cGenerator,
1079 crc32cExpected: hashes.crc32c,
1080 md5Expected: hashes.md5,
1081 });
1082 }
1083 if (md5 && !hashes.md5) {
1084 const hashError = new RequestError(FileExceptionMessages.MD5_NOT_AVAILABLE);
1085 hashError.code = 'MD5_NOT_AVAILABLE';
1086 throughStream.destroy(hashError);
1087 return;
1088 }
1089 if (safeToValidate && shouldRunValidation && validateStream) {
1090 transformStreams.push(validateStream);
1091 }
1092 if (isCompressed && options.decompress) {
1093 transformStreams.push(zlib.createGunzip());
1094 }
1095 (0, stream_1.pipeline)(rawResponseStream, ...transformStreams, throughStream, onComplete);
1096 };
1097 // Authenticate the request, then pipe the remote API request to the stream
1098 // returned to the user.
1099 const makeRequest = () => {
1100 const query = { alt: 'media' };
1101 if (this.generation) {
1102 query.generation = this.generation;
1103 }
1104 if (options.userProject) {
1105 query.userProject = options.userProject;
1106 }
1107 const headers = {
1108 'Accept-Encoding': 'gzip',
1109 'Cache-Control': 'no-store',
1110 };
1111 if (rangeRequest) {
1112 const start = typeof options.start === 'number' ? options.start : '0';
1113 const end = typeof options.end === 'number' ? options.end : '';
1114 headers.Range = `bytes=${tailRequest ? end : `${start}-${end}`}`;
1115 }
1116 const reqOpts = {
1117 uri: '',
1118 headers,
1119 qs: query,
1120 };
1121 if (options[util_js_1.GCCL_GCS_CMD_KEY]) {
1122 reqOpts[util_js_1.GCCL_GCS_CMD_KEY] = options[util_js_1.GCCL_GCS_CMD_KEY];
1123 }
1124 this.requestStream(reqOpts)
1125 .on('error', err => {
1126 throughStream.destroy(err);
1127 })
1128 .on('response', res => {
1129 throughStream.emit('response', res);
1130 index_js_1.util.handleResp(null, res, null, onResponse);
1131 })
1132 .resume();
1133 };
1134 throughStream.on('reading', makeRequest);
1135 return throughStream;
1136 }
1137 /**
1138 * @callback CreateResumableUploadCallback
1139 * @param {?Error} err Request error, if any.
1140 * @param {string} uri The resumable upload's unique session URI.
1141 */
1142 /**
1143 * @typedef {array} CreateResumableUploadResponse
1144 * @property {string} 0 The resumable upload's unique session URI.
1145 */
1146 /**
1147 * @typedef {object} CreateResumableUploadOptions
1148 * @property {object} [metadata] Metadata to set on the file.
1149 * @property {number} [offset] The starting byte of the upload stream for resuming an interrupted upload.
1150 * @property {string} [origin] Origin header to set for the upload.
1151 * @property {string} [predefinedAcl] Apply a predefined set of access
1152 * controls to this object.
1153 *
1154 * Acceptable values are:
1155 * - **`authenticatedRead`** - Object owner gets `OWNER` access, and
1156 * `allAuthenticatedUsers` get `READER` access.
1157 *
1158 * - **`bucketOwnerFullControl`** - Object owner gets `OWNER` access, and
1159 * project team owners get `OWNER` access.
1160 *
1161 * - **`bucketOwnerRead`** - Object owner gets `OWNER` access, and project
1162 * team owners get `READER` access.
1163 *
1164 * - **`private`** - Object owner gets `OWNER` access.
1165 *
1166 * - **`projectPrivate`** - Object owner gets `OWNER` access, and project
1167 * team members get access according to their roles.
1168 *
1169 * - **`publicRead`** - Object owner gets `OWNER` access, and `allUsers`
1170 * get `READER` access.
1171 * @property {boolean} [private] Make the uploaded file private. (Alias for
1172 * `options.predefinedAcl = 'private'`)
1173 * @property {boolean} [public] Make the uploaded file public. (Alias for
1174 * `options.predefinedAcl = 'publicRead'`)
1175 * @property {string} [userProject] The ID of the project which will be
1176 * billed for the request.
1177 * @property {string} [chunkSize] Create a separate request per chunk. This
1178 * value is in bytes and should be a multiple of 256 KiB (2^18).
1179 * {@link https://cloud.google.com/storage/docs/performing-resumable-uploads#chunked-upload| We recommend using at least 8 MiB for the chunk size.}
1180 */
1181 /**
1182 * Create a unique resumable upload session URI. This is the first step when
1183 * performing a resumable upload.
1184 *
1185 * See the {@link https://cloud.google.com/storage/docs/json_api/v1/how-tos/resumable-upload| Resumable upload guide}
1186 * for more on how the entire process works.
1187 *
1188 * <h4>Note</h4>
1189 *
1190 * If you are just looking to perform a resumable upload without worrying
1191 * about any of the details, see {@link File#createWriteStream}. Resumable
1192 * uploads are performed by default.
1193 *
1194 * See {@link https://cloud.google.com/storage/docs/json_api/v1/how-tos/resumable-upload| Resumable upload guide}
1195 *
1196 * @param {CreateResumableUploadOptions} [options] Configuration options.
1197 * @param {CreateResumableUploadCallback} [callback] Callback function.
1198 * @returns {Promise<CreateResumableUploadResponse>}
1199 *
1200 * @example
1201 * ```
1202 * const {Storage} = require('@google-cloud/storage');
1203 * const storage = new Storage();
1204 * const myBucket = storage.bucket('my-bucket');
1205 *
1206 * const file = myBucket.file('my-file');
1207 * file.createResumableUpload(function(err, uri) {
1208 * if (!err) {
1209 * // `uri` can be used to PUT data to.
1210 * }
1211 * });
1212 *
1213 * //-
1214 * // If the callback is omitted, we'll return a Promise.
1215 * //-
1216 * file.createResumableUpload().then(function(data) {
1217 * const uri = data[0];
1218 * });
1219 * ```
1220 */
1221 createResumableUpload(optionsOrCallback, callback) {
1222 var _a, _b;
1223 const options = typeof optionsOrCallback === 'object' ? optionsOrCallback : {};
1224 callback =
1225 typeof optionsOrCallback === 'function' ? optionsOrCallback : callback;
1226 const retryOptions = this.storage.retryOptions;
1227 if ((((_a = options === null || options === void 0 ? void 0 : options.preconditionOpts) === null || _a === void 0 ? void 0 : _a.ifGenerationMatch) === undefined &&
1228 ((_b = this.instancePreconditionOpts) === null || _b === void 0 ? void 0 : _b.ifGenerationMatch) === undefined &&
1229 this.storage.retryOptions.idempotencyStrategy ===
1230 storage_js_1.IdempotencyStrategy.RetryConditional) ||
1231 this.storage.retryOptions.idempotencyStrategy ===
1232 storage_js_1.IdempotencyStrategy.RetryNever) {
1233 retryOptions.autoRetry = false;
1234 }
1235 resumableUpload.createURI({
1236 authClient: this.storage.authClient,
1237 apiEndpoint: this.storage.apiEndpoint,
1238 bucket: this.bucket.name,
1239 customRequestOptions: this.getRequestInterceptors().reduce((reqOpts, interceptorFn) => interceptorFn(reqOpts), {}),
1240 file: this.name,
1241 generation: this.generation,
1242 key: this.encryptionKey,
1243 kmsKeyName: this.kmsKeyName,
1244 metadata: options.metadata,
1245 offset: options.offset,
1246 origin: options.origin,
1247 predefinedAcl: options.predefinedAcl,
1248 private: options.private,
1249 public: options.public,
1250 userProject: options.userProject || this.userProject,
1251 retryOptions: retryOptions,
1252 params: (options === null || options === void 0 ? void 0 : options.preconditionOpts) || this.instancePreconditionOpts,
1253 universeDomain: this.bucket.storage.universeDomain,
1254 [util_js_1.GCCL_GCS_CMD_KEY]: options[util_js_1.GCCL_GCS_CMD_KEY],
1255 }, callback);
1256 this.storage.retryOptions.autoRetry = this.instanceRetryValue;
1257 }
1258 /**
1259 * @typedef {object} CreateWriteStreamOptions Configuration options for File#createWriteStream().
1260 * @property {string} [contentType] Alias for
1261 * `options.metadata.contentType`. If set to `auto`, the file name is used
1262 * to determine the contentType.
1263 * @property {string|boolean} [gzip] If true, automatically gzip the file.
1264 * If set to `auto`, the contentType is used to determine if the file
1265 * should be gzipped. This will set `options.metadata.contentEncoding` to
1266 * `gzip` if necessary.
1267 * @property {object} [metadata] See the examples below or
1268 * {@link https://cloud.google.com/storage/docs/json_api/v1/objects/insert#request_properties_JSON| Objects: insert request body}
1269 * for more details.
1270 * @property {number} [offset] The starting byte of the upload stream, for
1271 * resuming an interrupted upload. Defaults to 0.
1272 * @property {string} [predefinedAcl] Apply a predefined set of access
1273 * controls to this object.
1274 *
1275 * Acceptable values are:
1276 * - **`authenticatedRead`** - Object owner gets `OWNER` access, and
1277 * `allAuthenticatedUsers` get `READER` access.
1278 *
1279 * - **`bucketOwnerFullControl`** - Object owner gets `OWNER` access, and
1280 * project team owners get `OWNER` access.
1281 *
1282 * - **`bucketOwnerRead`** - Object owner gets `OWNER` access, and project
1283 * team owners get `READER` access.
1284 *
1285 * - **`private`** - Object owner gets `OWNER` access.
1286 *
1287 * - **`projectPrivate`** - Object owner gets `OWNER` access, and project
1288 * team members get access according to their roles.
1289 *
1290 * - **`publicRead`** - Object owner gets `OWNER` access, and `allUsers`
1291 * get `READER` access.
1292 * @property {boolean} [private] Make the uploaded file private. (Alias for
1293 * `options.predefinedAcl = 'private'`)
1294 * @property {boolean} [public] Make the uploaded file public. (Alias for
1295 * `options.predefinedAcl = 'publicRead'`)
1296 * @property {boolean} [resumable] Force a resumable upload. NOTE: When
1297 * working with streams, the file format and size is unknown until it's
1298 * completely consumed. Because of this, it's best for you to be explicit
1299 * for what makes sense given your input.
1300 * @property {number} [timeout=60000] Set the HTTP request timeout in
1301 * milliseconds. This option is not available for resumable uploads.
1302 * Default: `60000`
1303 * @property {string} [uri] The URI for an already-created resumable
1304 * upload. See {@link File#createResumableUpload}.
1305 * @property {string} [userProject] The ID of the project which will be
1306 * billed for the request.
1307 * @property {string|boolean} [validation] Possible values: `"md5"`,
1308 * `"crc32c"`, or `false`. By default, data integrity is validated with a
1309 * CRC32c checksum. You may use MD5 if preferred, but that hash is not
1310 * supported for composite objects. An error will be raised if MD5 is
1311 * specified but is not available. You may also choose to skip validation
1312 * completely, however this is **not recommended**. In addition to specifying
1313 * validation type, providing `metadata.crc32c` or `metadata.md5Hash` will
1314 * cause the server to perform validation in addition to client validation.
1315 * NOTE: Validation is automatically skipped for objects that were
1316 * uploaded using the `gzip` option and have already compressed content.
1317 */
1318 /**
1319 * Create a writable stream to overwrite the contents of the file in your
1320 * bucket.
1321 *
1322 * A File object can also be used to create files for the first time.
1323 *
1324 * Resumable uploads are automatically enabled and must be shut off explicitly
1325 * by setting `options.resumable` to `false`.
1326 *
1327 *
1328 * <p class="notice">
1329 * There is some overhead when using a resumable upload that can cause
1330 * noticeable performance degradation while uploading a series of small
1331 * files. When uploading files less than 10MB, it is recommended that the
1332 * resumable feature is disabled.
1333 * </p>
1334 *
1335 * NOTE: Writable streams will emit the `finish` event when the file is fully
1336 * uploaded.
1337 *
1338 * See {@link https://cloud.google.com/storage/docs/json_api/v1/how-tos/upload Upload Options (Simple or Resumable)}
1339 * See {@link https://cloud.google.com/storage/docs/json_api/v1/objects/insert Objects: insert API Documentation}
1340 *
1341 * @param {CreateWriteStreamOptions} [options] Configuration options.
1342 * @returns {WritableStream}
1343 *
1344 * @example
1345 * ```
1346 * const fs = require('fs');
1347 * const {Storage} = require('@google-cloud/storage');
1348 * const storage = new Storage();
1349 * const myBucket = storage.bucket('my-bucket');
1350 *
1351 * const file = myBucket.file('my-file');
1352 *
1353 * //-
1354 * // <h4>Uploading a File</h4>
1355 * //
1356 * // Now, consider a case where we want to upload a file to your bucket. You
1357 * // have the option of using {@link Bucket#upload}, but that is just
1358 * // a convenience method which will do the following.
1359 * //-
1360 * fs.createReadStream('/Users/stephen/Photos/birthday-at-the-zoo/panda.jpg')
1361 * .pipe(file.createWriteStream())
1362 * .on('error', function(err) {})
1363 * .on('finish', function() {
1364 * // The file upload is complete.
1365 * });
1366 *
1367 * //-
1368 * // <h4>Uploading a File with gzip compression</h4>
1369 * //-
1370 * fs.createReadStream('/Users/stephen/site/index.html')
1371 * .pipe(file.createWriteStream({ gzip: true }))
1372 * .on('error', function(err) {})
1373 * .on('finish', function() {
1374 * // The file upload is complete.
1375 * });
1376 *
1377 * //-
1378 * // Downloading the file with `createReadStream` will automatically decode
1379 * // the file.
1380 * //-
1381 *
1382 * //-
1383 * // <h4>Uploading a File with Metadata</h4>
1384 * //
1385 * // One last case you may run into is when you want to upload a file to your
1386 * // bucket and set its metadata at the same time. Like above, you can use
1387 * // {@link Bucket#upload} to do this, which is just a wrapper around
1388 * // the following.
1389 * //-
1390 * fs.createReadStream('/Users/stephen/Photos/birthday-at-the-zoo/panda.jpg')
1391 * .pipe(file.createWriteStream({
1392 * metadata: {
1393 * contentType: 'image/jpeg',
1394 * metadata: {
1395 * custom: 'metadata'
1396 * }
1397 * }
1398 * }))
1399 * .on('error', function(err) {})
1400 * .on('finish', function() {
1401 * // The file upload is complete.
1402 * });
1403 * ```
1404 *
1405 * //-
1406 * // <h4>Continuing a Resumable Upload</h4>
1407 * //
1408 * // One can capture a `uri` from a resumable upload to reuse later.
1409 * // Additionally, for validation, one can also capture and pass `crc32c`.
1410 * //-
1411 * let uri: string | undefined = undefined;
1412 * let resumeCRC32C: string | undefined = undefined;
1413 *
1414 * fs.createWriteStream()
1415 * .on('uri', link => {uri = link})
1416 * .on('crc32', crc32c => {resumeCRC32C = crc32c});
1417 *
1418 * // later...
1419 * fs.createWriteStream({uri, resumeCRC32C});
1420 */
1421 // eslint-disable-next-line @typescript-eslint/no-explicit-any
1422 createWriteStream(options = {}) {
1423 var _a;
1424 (_a = options.metadata) !== null && _a !== void 0 ? _a : (options.metadata = {});
1425 if (options.contentType) {
1426 options.metadata.contentType = options.contentType;
1427 }
1428 if (!options.metadata.contentType ||
1429 options.metadata.contentType === 'auto') {
1430 const detectedContentType = mime_1.default.getType(this.name);
1431 if (detectedContentType) {
1432 options.metadata.contentType = detectedContentType;
1433 }
1434 }
1435 let gzip = options.gzip;
1436 if (gzip === 'auto') {
1437 gzip = COMPRESSIBLE_MIME_REGEX.test(options.metadata.contentType || '');
1438 }
1439 if (gzip) {
1440 options.metadata.contentEncoding = 'gzip';
1441 }
1442 let crc32c = true;
1443 let md5 = false;
1444 if (typeof options.validation === 'string') {
1445 options.validation = options.validation.toLowerCase();
1446 crc32c = options.validation === 'crc32c';
1447 md5 = options.validation === 'md5';
1448 }
1449 else if (options.validation === false) {
1450 crc32c = false;
1451 md5 = false;
1452 }
1453 if (options.offset) {
1454 if (md5) {
1455 throw new RangeError(FileExceptionMessages.MD5_RESUMED_UPLOAD);
1456 }
1457 if (crc32c && !options.isPartialUpload && !options.resumeCRC32C) {
1458 throw new RangeError(FileExceptionMessages.MISSING_RESUME_CRC32C_FINAL_UPLOAD);
1459 }
1460 }
1461 /**
1462 * A callback for determining when the underlying pipeline is complete.
1463 * It's possible the pipeline callback could error before the write stream
1464 * calls `final` so by default this will destroy the write stream unless the
1465 * write stream sets this callback via its `final` handler.
1466 * @param error An optional error
1467 */
1468 let pipelineCallback = error => {
1469 writeStream.destroy(error || undefined);
1470 };
1471 // A stream for consumer to write to
1472 const writeStream = new stream_1.Writable({
1473 final(cb) {
1474 // Set the pipeline callback to this callback so the pipeline's results
1475 // can be populated to the consumer
1476 pipelineCallback = cb;
1477 emitStream.end();
1478 },
1479 write(chunk, encoding, cb) {
1480 emitStream.write(chunk, encoding, cb);
1481 },
1482 });
1483 // If the write stream, which is returned to the caller, catches an error we need to make sure that
1484 // at least one of the streams in the pipeline below gets notified so that they
1485 // all get cleaned up / destroyed.
1486 writeStream.once('error', e => {
1487 emitStream.destroy(e);
1488 });
1489 // If the write stream is closed, cleanup the pipeline below by calling destroy on one of the streams.
1490 writeStream.once('close', () => {
1491 emitStream.destroy();
1492 });
1493 const transformStreams = [];
1494 if (gzip) {
1495 transformStreams.push(zlib.createGzip());
1496 }
1497 const emitStream = new util_js_2.PassThroughShim();
1498 let hashCalculatingStream = null;
1499 if (crc32c || md5) {
1500 const crc32cInstance = options.resumeCRC32C
1501 ? crc32c_js_1.CRC32C.from(options.resumeCRC32C)
1502 : undefined;
1503 hashCalculatingStream = new hash_stream_validator_js_1.HashStreamValidator({
1504 crc32c,
1505 crc32cInstance,
1506 md5,
1507 crc32cGenerator: this.crc32cGenerator,
1508 updateHashesOnly: true,
1509 });
1510 transformStreams.push(hashCalculatingStream);
1511 }
1512 const fileWriteStream = (0, duplexify_1.default)();
1513 let fileWriteStreamMetadataReceived = false;
1514 // Handing off emitted events to users
1515 emitStream.on('reading', () => writeStream.emit('reading'));
1516 emitStream.on('writing', () => writeStream.emit('writing'));
1517 fileWriteStream.on('uri', evt => writeStream.emit('uri', evt));
1518 fileWriteStream.on('progress', evt => writeStream.emit('progress', evt));
1519 fileWriteStream.on('response', resp => writeStream.emit('response', resp));
1520 fileWriteStream.once('metadata', () => {
1521 fileWriteStreamMetadataReceived = true;
1522 });
1523 writeStream.once('writing', () => {
1524 if (options.resumable === false) {
1525 this.startSimpleUpload_(fileWriteStream, options);
1526 }
1527 else {
1528 this.startResumableUpload_(fileWriteStream, options);
1529 }
1530 (0, stream_1.pipeline)(emitStream, ...transformStreams, fileWriteStream, async (e) => {
1531 if (e) {
1532 return pipelineCallback(e);
1533 }
1534 // We want to make sure we've received the metadata from the server in order
1535 // to properly validate the object's integrity. Depending on the type of upload,
1536 // the stream could close before the response is returned.
1537 if (!fileWriteStreamMetadataReceived) {
1538 try {
1539 await new Promise((resolve, reject) => {
1540 fileWriteStream.once('metadata', resolve);
1541 fileWriteStream.once('error', reject);
1542 });
1543 }
1544 catch (e) {
1545 return pipelineCallback(e);
1546 }
1547 }
1548 // Emit the local CRC32C value for future validation, if validation is enabled.
1549 if (hashCalculatingStream === null || hashCalculatingStream === void 0 ? void 0 : hashCalculatingStream.crc32c) {
1550 writeStream.emit('crc32c', hashCalculatingStream.crc32c);
1551 }
1552 try {
1553 // Metadata may not be ready if the upload is a partial upload,
1554 // nothing to validate yet.
1555 const metadataNotReady = options.isPartialUpload && !this.metadata;
1556 if (hashCalculatingStream && !metadataNotReady) {
1557 await __classPrivateFieldGet(this, _File_instances, "m", _File_validateIntegrity).call(this, hashCalculatingStream, {
1558 crc32c,
1559 md5,
1560 });
1561 }
1562 pipelineCallback();
1563 }
1564 catch (e) {
1565 pipelineCallback(e);
1566 }
1567 });
1568 });
1569 return writeStream;
1570 }
1571 delete(optionsOrCallback, cb) {
1572 const options = typeof optionsOrCallback === 'object' ? optionsOrCallback : {};
1573 cb = typeof optionsOrCallback === 'function' ? optionsOrCallback : cb;
1574 this.disableAutoRetryConditionallyIdempotent_(this.methods.delete, bucket_js_1.AvailableServiceObjectMethods.delete, options);
1575 super
1576 .delete(options)
1577 .then(resp => cb(null, ...resp))
1578 .catch(cb)
1579 .finally(() => {
1580 this.storage.retryOptions.autoRetry = this.instanceRetryValue;
1581 });
1582 }
1583 /**
1584 * @typedef {array} DownloadResponse
1585 * @property [0] The contents of a File.
1586 */
1587 /**
1588 * @callback DownloadCallback
1589 * @param err Request error, if any.
1590 * @param contents The contents of a File.
1591 */
1592 /**
1593 * Convenience method to download a file into memory or to a local
1594 * destination.
1595 *
1596 * @param {object} [options] Configuration options. The arguments match those
1597 * passed to {@link File#createReadStream}.
1598 * @param {string} [options.destination] Local file path to write the file's
1599 * contents to.
1600 * @param {string} [options.userProject] The ID of the project which will be
1601 * billed for the request.
1602 * @param {DownloadCallback} [callback] Callback function.
1603 * @returns {Promise<DownloadResponse>}
1604 *
1605 * @example
1606 * ```
1607 * const {Storage} = require('@google-cloud/storage');
1608 * const storage = new Storage();
1609 * const myBucket = storage.bucket('my-bucket');
1610 *
1611 * const file = myBucket.file('my-file');
1612 *
1613 * //-
1614 * // Download a file into memory. The contents will be available as the
1615 * second
1616 * // argument in the demonstration below, `contents`.
1617 * //-
1618 * file.download(function(err, contents) {});
1619 *
1620 * //-
1621 * // Download a file to a local destination.
1622 * //-
1623 * file.download({
1624 * destination: '/Users/me/Desktop/file-backup.txt'
1625 * }, function(err) {});
1626 *
1627 * //-
1628 * // If the callback is omitted, we'll return a Promise.
1629 * //-
1630 * file.download().then(function(data) {
1631 * const contents = data[0];
1632 * });
1633 *
1634 * ```
1635 * @example <caption>include:samples/files.js</caption>
1636 * region_tag:storage_download_file
1637 * Another example:
1638 *
1639 * @example <caption>include:samples/encryption.js</caption>
1640 * region_tag:storage_download_encrypted_file
1641 * Example of downloading an encrypted file:
1642 *
1643 * @example <caption>include:samples/requesterPays.js</caption>
1644 * region_tag:storage_download_file_requester_pays
1645 * Example of downloading a file where the requester pays:
1646 */
1647 download(optionsOrCallback, cb) {
1648 let options;
1649 if (typeof optionsOrCallback === 'function') {
1650 cb = optionsOrCallback;
1651 options = {};
1652 }
1653 else {
1654 options = optionsOrCallback;
1655 }
1656 let called = false;
1657 const callback = ((...args) => {
1658 if (!called)
1659 cb(...args);
1660 called = true;
1661 });
1662 const destination = options.destination;
1663 delete options.destination;
1664 const fileStream = this.createReadStream(options);
1665 let receivedData = false;
1666 if (destination) {
1667 fileStream
1668 .on('error', callback)
1669 .once('data', data => {
1670 receivedData = true;
1671 // We know that the file exists the server - now we can truncate/write to a file
1672 const writable = fs.createWriteStream(destination);
1673 writable.write(data);
1674 fileStream
1675 .pipe(writable)
1676 .on('error', callback)
1677 .on('finish', callback);
1678 })
1679 .on('end', () => {
1680 // In the case of an empty file no data will be received before the end event fires
1681 if (!receivedData) {
1682 const data = Buffer.alloc(0);
1683 try {
1684 fs.writeFileSync(destination, data);
1685 callback(null, data);
1686 }
1687 catch (e) {
1688 callback(e, data);
1689 }
1690 }
1691 });
1692 }
1693 else {
1694 this.getBufferFromReadable(fileStream)
1695 .then(contents => callback === null || callback === void 0 ? void 0 : callback(null, contents))
1696 .catch(callback);
1697 }
1698 }
1699 /**
1700 * The Storage API allows you to use a custom key for server-side encryption.
1701 *
1702 * See {@link https://cloud.google.com/storage/docs/encryption#customer-supplied| Customer-supplied Encryption Keys}
1703 *
1704 * @param {string|buffer} encryptionKey An AES-256 encryption key.
1705 * @returns {File}
1706 *
1707 * @example
1708 * ```
1709 * const crypto = require('crypto');
1710 * const {Storage} = require('@google-cloud/storage');
1711 * const storage = new Storage();
1712 * const myBucket = storage.bucket('my-bucket');
1713 *
1714 * const encryptionKey = crypto.randomBytes(32);
1715 *
1716 * const fileWithCustomEncryption = myBucket.file('my-file');
1717 * fileWithCustomEncryption.setEncryptionKey(encryptionKey);
1718 *
1719 * const fileWithoutCustomEncryption = myBucket.file('my-file');
1720 *
1721 * fileWithCustomEncryption.save('data', function(err) {
1722 * // Try to download with the File object that hasn't had
1723 * // `setEncryptionKey()` called:
1724 * fileWithoutCustomEncryption.download(function(err) {
1725 * // We will receive an error:
1726 * // err.message === 'Bad Request'
1727 *
1728 * // Try again with the File object we called `setEncryptionKey()` on:
1729 * fileWithCustomEncryption.download(function(err, contents) {
1730 * // contents.toString() === 'data'
1731 * });
1732 * });
1733 * });
1734 *
1735 * ```
1736 * @example <caption>include:samples/encryption.js</caption>
1737 * region_tag:storage_upload_encrypted_file
1738 * Example of uploading an encrypted file:
1739 *
1740 * @example <caption>include:samples/encryption.js</caption>
1741 * region_tag:storage_download_encrypted_file
1742 * Example of downloading an encrypted file:
1743 */
1744 setEncryptionKey(encryptionKey) {
1745 this.encryptionKey = encryptionKey;
1746 this.encryptionKeyBase64 = Buffer.from(encryptionKey).toString('base64');
1747 this.encryptionKeyHash = crypto
1748 .createHash('sha256')
1749 // eslint-disable-next-line @typescript-eslint/no-explicit-any
1750 .update(this.encryptionKeyBase64, 'base64')
1751 .digest('base64');
1752 this.encryptionKeyInterceptor = {
1753 request: reqOpts => {
1754 reqOpts.headers = reqOpts.headers || {};
1755 reqOpts.headers['x-goog-encryption-algorithm'] = 'AES256';
1756 reqOpts.headers['x-goog-encryption-key'] = this.encryptionKeyBase64;
1757 reqOpts.headers['x-goog-encryption-key-sha256'] =
1758 this.encryptionKeyHash;
1759 return reqOpts;
1760 },
1761 };
1762 this.interceptors.push(this.encryptionKeyInterceptor);
1763 return this;
1764 }
1765 /**
1766 * Gets a reference to a Cloud Storage {@link File} file from the provided URL in string format.
1767 * @param {string} publicUrlOrGsUrl the URL as a string. Must be of the format gs://bucket/file
1768 * or https://storage.googleapis.com/bucket/file.
1769 * @param {Storage} storageInstance an instance of a Storage object.
1770 * @param {FileOptions} [options] Configuration options
1771 * @returns {File}
1772 */
1773 static from(publicUrlOrGsUrl, storageInstance, options) {
1774 const gsMatches = [...publicUrlOrGsUrl.matchAll(GS_UTIL_URL_REGEX)];
1775 const httpsMatches = [...publicUrlOrGsUrl.matchAll(HTTPS_PUBLIC_URL_REGEX)];
1776 if (gsMatches.length > 0) {
1777 const bucket = new bucket_js_1.Bucket(storageInstance, gsMatches[0][2]);
1778 return new File(bucket, gsMatches[0][3], options);
1779 }
1780 else if (httpsMatches.length > 0) {
1781 const bucket = new bucket_js_1.Bucket(storageInstance, httpsMatches[0][3]);
1782 return new File(bucket, httpsMatches[0][4], options);
1783 }
1784 else {
1785 throw new Error('URL string must be of format gs://bucket/file or https://storage.googleapis.com/bucket/file');
1786 }
1787 }
1788 get(optionsOrCallback, cb) {
1789 // eslint-disable-next-line @typescript-eslint/no-explicit-any
1790 const options = typeof optionsOrCallback === 'object' ? optionsOrCallback : {};
1791 cb =
1792 typeof optionsOrCallback === 'function'
1793 ? optionsOrCallback
1794 : cb;
1795 super
1796 .get(options)
1797 .then(resp => cb(null, ...resp))
1798 .catch(cb);
1799 }
1800 /**
1801 * @typedef {array} GetExpirationDateResponse
1802 * @property {date} 0 A Date object representing the earliest time this file's
1803 * retention policy will expire.
1804 */
1805 /**
1806 * @callback GetExpirationDateCallback
1807 * @param {?Error} err Request error, if any.
1808 * @param {date} expirationDate A Date object representing the earliest time
1809 * this file's retention policy will expire.
1810 */
1811 /**
1812 * If this bucket has a retention policy defined, use this method to get a
1813 * Date object representing the earliest time this file will expire.
1814 *
1815 * @param {GetExpirationDateCallback} [callback] Callback function.
1816 * @returns {Promise<GetExpirationDateResponse>}
1817 *
1818 * @example
1819 * ```
1820 * const storage = require('@google-cloud/storage')();
1821 * const myBucket = storage.bucket('my-bucket');
1822 *
1823 * const file = myBucket.file('my-file');
1824 *
1825 * file.getExpirationDate(function(err, expirationDate) {
1826 * // expirationDate is a Date object.
1827 * });
1828 * ```
1829 */
1830 getExpirationDate(callback) {
1831 this.getMetadata((err, metadata, apiResponse) => {
1832 if (err) {
1833 callback(err, null, apiResponse);
1834 return;
1835 }
1836 if (!metadata.retentionExpirationTime) {
1837 const error = new Error(FileExceptionMessages.EXPIRATION_TIME_NA);
1838 callback(error, null, apiResponse);
1839 return;
1840 }
1841 callback(null, new Date(metadata.retentionExpirationTime), apiResponse);
1842 });
1843 }
1844 /**
1845 * @typedef {array} GenerateSignedPostPolicyV2Response
1846 * @property {object} 0 The document policy.
1847 */
1848 /**
1849 * @callback GenerateSignedPostPolicyV2Callback
1850 * @param {?Error} err Request error, if any.
1851 * @param {object} policy The document policy.
1852 */
1853 /**
1854 * Get a signed policy document to allow a user to upload data with a POST
1855 * request.
1856 *
1857 * In Google Cloud Platform environments, such as Cloud Functions and App
1858 * Engine, you usually don't provide a `keyFilename` or `credentials` during
1859 * instantiation. In those environments, we call the
1860 * {@link https://cloud.google.com/iam/docs/reference/credentials/rest/v1/projects.serviceAccounts/signBlob| signBlob API}
1861 * to create a signed policy. That API requires either the
1862 * `https://www.googleapis.com/auth/iam` or
1863 * `https://www.googleapis.com/auth/cloud-platform` scope, so be sure they are
1864 * enabled.
1865 *
1866 * See {@link https://cloud.google.com/storage/docs/xml-api/post-object-v2| POST Object with the V2 signing process}
1867 *
1868 * @throws {Error} If an expiration timestamp from the past is given.
1869 * @throws {Error} If options.equals has an array with less or more than two
1870 * members.
1871 * @throws {Error} If options.startsWith has an array with less or more than two
1872 * members.
1873 *
1874 * @param {object} options Configuration options.
1875 * @param {array|array[]} [options.equals] Array of request parameters and
1876 * their expected value (e.g. [['$<field>', '<value>']]). Values are
1877 * translated into equality constraints in the conditions field of the
1878 * policy document (e.g. ['eq', '$<field>', '<value>']). If only one
1879 * equality condition is to be specified, options.equals can be a one-
1880 * dimensional array (e.g. ['$<field>', '<value>']).
1881 * @param {*} options.expires - A timestamp when this policy will expire. Any
1882 * value given is passed to `new Date()`.
1883 * @param {array|array[]} [options.startsWith] Array of request parameters and
1884 * their expected prefixes (e.g. [['$<field>', '<value>']). Values are
1885 * translated into starts-with constraints in the conditions field of the
1886 * policy document (e.g. ['starts-with', '$<field>', '<value>']). If only
1887 * one prefix condition is to be specified, options.startsWith can be a
1888 * one- dimensional array (e.g. ['$<field>', '<value>']).
1889 * @param {string} [options.acl] ACL for the object from possibly predefined
1890 * ACLs.
1891 * @param {string} [options.successRedirect] The URL to which the user client
1892 * is redirected if the upload is successful.
1893 * @param {string} [options.successStatus] - The status of the Google Storage
1894 * response if the upload is successful (must be string).
1895 * @param {object} [options.contentLengthRange]
1896 * @param {number} [options.contentLengthRange.min] Minimum value for the
1897 * request's content length.
1898 * @param {number} [options.contentLengthRange.max] Maximum value for the
1899 * request's content length.
1900 * @param {GenerateSignedPostPolicyV2Callback} [callback] Callback function.
1901 * @returns {Promise<GenerateSignedPostPolicyV2Response>}
1902 *
1903 * @example
1904 * ```
1905 * const {Storage} = require('@google-cloud/storage');
1906 * const storage = new Storage();
1907 * const myBucket = storage.bucket('my-bucket');
1908 *
1909 * const file = myBucket.file('my-file');
1910 * const options = {
1911 * equals: ['$Content-Type', 'image/jpeg'],
1912 * expires: '10-25-2022',
1913 * contentLengthRange: {
1914 * min: 0,
1915 * max: 1024
1916 * }
1917 * };
1918 *
1919 * file.generateSignedPostPolicyV2(options, function(err, policy) {
1920 * // policy.string: the policy document in plain text.
1921 * // policy.base64: the policy document in base64.
1922 * // policy.signature: the policy signature in base64.
1923 * });
1924 *
1925 * //-
1926 * // If the callback is omitted, we'll return a Promise.
1927 * //-
1928 * file.generateSignedPostPolicyV2(options).then(function(data) {
1929 * const policy = data[0];
1930 * });
1931 * ```
1932 */
1933 generateSignedPostPolicyV2(optionsOrCallback, cb) {
1934 const args = (0, util_js_2.normalize)(optionsOrCallback, cb);
1935 let options = args.options;
1936 const callback = args.callback;
1937 const expires = new Date(options.expires);
1938 if (isNaN(expires.getTime())) {
1939 throw new Error(storage_js_1.ExceptionMessages.EXPIRATION_DATE_INVALID);
1940 }
1941 if (expires.valueOf() < Date.now()) {
1942 throw new Error(storage_js_1.ExceptionMessages.EXPIRATION_DATE_PAST);
1943 }
1944 options = Object.assign({}, options);
1945 const conditions = [
1946 ['eq', '$key', this.name],
1947 {
1948 bucket: this.bucket.name,
1949 },
1950 ];
1951 if (Array.isArray(options.equals)) {
1952 if (!Array.isArray(options.equals[0])) {
1953 options.equals = [options.equals];
1954 }
1955 options.equals.forEach(condition => {
1956 if (!Array.isArray(condition) || condition.length !== 2) {
1957 throw new Error(FileExceptionMessages.EQUALS_CONDITION_TWO_ELEMENTS);
1958 }
1959 conditions.push(['eq', condition[0], condition[1]]);
1960 });
1961 }
1962 if (Array.isArray(options.startsWith)) {
1963 if (!Array.isArray(options.startsWith[0])) {
1964 options.startsWith = [options.startsWith];
1965 }
1966 options.startsWith.forEach(condition => {
1967 if (!Array.isArray(condition) || condition.length !== 2) {
1968 throw new Error(FileExceptionMessages.STARTS_WITH_TWO_ELEMENTS);
1969 }
1970 conditions.push(['starts-with', condition[0], condition[1]]);
1971 });
1972 }
1973 if (options.acl) {
1974 conditions.push({
1975 acl: options.acl,
1976 });
1977 }
1978 if (options.successRedirect) {
1979 conditions.push({
1980 success_action_redirect: options.successRedirect,
1981 });
1982 }
1983 if (options.successStatus) {
1984 conditions.push({
1985 success_action_status: options.successStatus,
1986 });
1987 }
1988 if (options.contentLengthRange) {
1989 const min = options.contentLengthRange.min;
1990 const max = options.contentLengthRange.max;
1991 if (typeof min !== 'number' || typeof max !== 'number') {
1992 throw new Error(FileExceptionMessages.CONTENT_LENGTH_RANGE_MIN_MAX);
1993 }
1994 conditions.push(['content-length-range', min, max]);
1995 }
1996 const policy = {
1997 expiration: expires.toISOString(),
1998 conditions,
1999 };
2000 const policyString = JSON.stringify(policy);
2001 const policyBase64 = Buffer.from(policyString).toString('base64');
2002 this.storage.authClient.sign(policyBase64, options.signingEndpoint).then(signature => {
2003 callback(null, {
2004 string: policyString,
2005 base64: policyBase64,
2006 signature,
2007 });
2008 }, err => {
2009 callback(new signer_js_1.SigningError(err.message));
2010 });
2011 }
2012 /**
2013 * @typedef {object} SignedPostPolicyV4Output
2014 * @property {string} url The request URL.
2015 * @property {object} fields The form fields to include in the POST request.
2016 */
2017 /**
2018 * @typedef {array} GenerateSignedPostPolicyV4Response
2019 * @property {SignedPostPolicyV4Output} 0 An object containing the request URL and form fields.
2020 */
2021 /**
2022 * @callback GenerateSignedPostPolicyV4Callback
2023 * @param {?Error} err Request error, if any.
2024 * @param {SignedPostPolicyV4Output} output An object containing the request URL and form fields.
2025 */
2026 /**
2027 * Get a v4 signed policy document to allow a user to upload data with a POST
2028 * request.
2029 *
2030 * In Google Cloud Platform environments, such as Cloud Functions and App
2031 * Engine, you usually don't provide a `keyFilename` or `credentials` during
2032 * instantiation. In those environments, we call the
2033 * {@link https://cloud.google.com/iam/docs/reference/credentials/rest/v1/projects.serviceAccounts/signBlob| signBlob API}
2034 * to create a signed policy. That API requires either the
2035 * `https://www.googleapis.com/auth/iam` or
2036 * `https://www.googleapis.com/auth/cloud-platform` scope, so be sure they are
2037 * enabled.
2038 *
2039 * See {@link https://cloud.google.com/storage/docs/xml-api/post-object#policydocument| Policy Document Reference}
2040 *
2041 * @param {object} options Configuration options.
2042 * @param {Date|number|string} options.expires - A timestamp when this policy will expire. Any
2043 * value given is passed to `new Date()`.
2044 * @param {boolean} [config.virtualHostedStyle=false] Use virtual hosted-style
2045 * URLs ('https://mybucket.storage.googleapis.com/...') instead of path-style
2046 * ('https://storage.googleapis.com/mybucket/...'). Virtual hosted-style URLs
2047 * should generally be preferred instead of path-style URL.
2048 * Currently defaults to `false` for path-style, although this may change in a
2049 * future major-version release.
2050 * @param {string} [config.bucketBoundHostname] The bucket-bound hostname to return in
2051 * the result, e.g. "https://cdn.example.com".
2052 * @param {object} [config.fields] [Form fields]{@link https://cloud.google.com/storage/docs/xml-api/post-object#policydocument}
2053 * to include in the signed policy. Any fields with key beginning with 'x-ignore-'
2054 * will not be included in the policy to be signed.
2055 * @param {object[]} [config.conditions] [Conditions]{@link https://cloud.google.com/storage/docs/authentication/signatures#policy-document}
2056 * to include in the signed policy. All fields given in `config.fields` are
2057 * automatically included in the conditions array, adding the same entry
2058 * in both `fields` and `conditions` will result in duplicate entries.
2059 *
2060 * @param {GenerateSignedPostPolicyV4Callback} [callback] Callback function.
2061 * @returns {Promise<GenerateSignedPostPolicyV4Response>}
2062 *
2063 * @example
2064 * ```
2065 * const {Storage} = require('@google-cloud/storage');
2066 * const storage = new Storage();
2067 * const myBucket = storage.bucket('my-bucket');
2068 *
2069 * const file = myBucket.file('my-file');
2070 * const options = {
2071 * expires: '10-25-2022',
2072 * conditions: [
2073 * ['eq', '$Content-Type', 'image/jpeg'],
2074 * ['content-length-range', 0, 1024],
2075 * ],
2076 * fields: {
2077 * acl: 'public-read',
2078 * 'x-goog-meta-foo': 'bar',
2079 * 'x-ignore-mykey': 'data'
2080 * }
2081 * };
2082 *
2083 * file.generateSignedPostPolicyV4(options, function(err, response) {
2084 * // response.url The request URL
2085 * // response.fields The form fields (including the signature) to include
2086 * // to be used to upload objects by HTML forms.
2087 * });
2088 *
2089 * //-
2090 * // If the callback is omitted, we'll return a Promise.
2091 * //-
2092 * file.generateSignedPostPolicyV4(options).then(function(data) {
2093 * const response = data[0];
2094 * // response.url The request URL
2095 * // response.fields The form fields (including the signature) to include
2096 * // to be used to upload objects by HTML forms.
2097 * });
2098 * ```
2099 */
2100 generateSignedPostPolicyV4(optionsOrCallback, cb) {
2101 const args = (0, util_js_2.normalize)(optionsOrCallback, cb);
2102 let options = args.options;
2103 const callback = args.callback;
2104 const expires = new Date(options.expires);
2105 if (isNaN(expires.getTime())) {
2106 throw new Error(storage_js_1.ExceptionMessages.EXPIRATION_DATE_INVALID);
2107 }
2108 if (expires.valueOf() < Date.now()) {
2109 throw new Error(storage_js_1.ExceptionMessages.EXPIRATION_DATE_PAST);
2110 }
2111 if (expires.valueOf() - Date.now() > SEVEN_DAYS * 1000) {
2112 throw new Error(`Max allowed expiration is seven days (${SEVEN_DAYS} seconds).`);
2113 }
2114 options = Object.assign({}, options);
2115 let fields = Object.assign({}, options.fields);
2116 const now = new Date();
2117 const nowISO = (0, util_js_2.formatAsUTCISO)(now, true);
2118 const todayISO = (0, util_js_2.formatAsUTCISO)(now);
2119 const sign = async () => {
2120 const { client_email } = await this.storage.authClient.getCredentials();
2121 const credential = `${client_email}/${todayISO}/auto/storage/goog4_request`;
2122 fields = {
2123 ...fields,
2124 bucket: this.bucket.name,
2125 key: this.name,
2126 'x-goog-date': nowISO,
2127 'x-goog-credential': credential,
2128 'x-goog-algorithm': 'GOOG4-RSA-SHA256',
2129 };
2130 const conditions = options.conditions || [];
2131 Object.entries(fields).forEach(([key, value]) => {
2132 if (!key.startsWith('x-ignore-')) {
2133 conditions.push({ [key]: value });
2134 }
2135 });
2136 delete fields.bucket;
2137 const expiration = (0, util_js_2.formatAsUTCISO)(expires, true, '-', ':');
2138 const policy = {
2139 conditions,
2140 expiration,
2141 };
2142 const policyString = (0, util_js_2.unicodeJSONStringify)(policy);
2143 const policyBase64 = Buffer.from(policyString).toString('base64');
2144 try {
2145 const signature = await this.storage.authClient.sign(policyBase64, options.signingEndpoint);
2146 const signatureHex = Buffer.from(signature, 'base64').toString('hex');
2147 const universe = this.parent.storage.universeDomain;
2148 fields['policy'] = policyBase64;
2149 fields['x-goog-signature'] = signatureHex;
2150 let url;
2151 if (this.storage.customEndpoint) {
2152 url = this.storage.apiEndpoint;
2153 }
2154 else if (options.virtualHostedStyle) {
2155 url = `https://${this.bucket.name}.storage.${universe}/`;
2156 }
2157 else if (options.bucketBoundHostname) {
2158 url = `${options.bucketBoundHostname}/`;
2159 }
2160 else {
2161 url = `https://storage.${universe}/${this.bucket.name}/`;
2162 }
2163 return {
2164 url,
2165 fields,
2166 };
2167 }
2168 catch (err) {
2169 throw new signer_js_1.SigningError(err.message);
2170 }
2171 };
2172 sign().then(res => callback(null, res), callback);
2173 }
2174 /**
2175 * @typedef {array} GetSignedUrlResponse
2176 * @property {object} 0 The signed URL.
2177 */
2178 /**
2179 * @callback GetSignedUrlCallback
2180 * @param {?Error} err Request error, if any.
2181 * @param {object} url The signed URL.
2182 */
2183 /**
2184 * Get a signed URL to allow limited time access to the file.
2185 *
2186 * In Google Cloud Platform environments, such as Cloud Functions and App
2187 * Engine, you usually don't provide a `keyFilename` or `credentials` during
2188 * instantiation. In those environments, we call the
2189 * {@link https://cloud.google.com/iam/docs/reference/credentials/rest/v1/projects.serviceAccounts/signBlob| signBlob API}
2190 * to create a signed URL. That API requires either the
2191 * `https://www.googleapis.com/auth/iam` or
2192 * `https://www.googleapis.com/auth/cloud-platform` scope, so be sure they are
2193 * enabled.
2194 *
2195 * See {@link https://cloud.google.com/storage/docs/access-control/signed-urls| Signed URLs Reference}
2196 *
2197 * @throws {Error} if an expiration timestamp from the past is given.
2198 *
2199 * @param {object} config Configuration object.
2200 * @param {string} config.action "read" (HTTP: GET), "write" (HTTP: PUT), or
2201 * "delete" (HTTP: DELETE), "resumable" (HTTP: POST).
2202 * When using "resumable", the header `X-Goog-Resumable: start` has
2203 * to be sent when making a request with the signed URL.
2204 * @param {*} config.expires A timestamp when this link will expire. Any value
2205 * given is passed to `new Date()`.
2206 * Note: 'v4' supports maximum duration of 7 days (604800 seconds) from now.
2207 * See [reference]{@link https://cloud.google.com/storage/docs/access-control/signed-urls#example}
2208 * @param {string} [config.version='v2'] The signing version to use, either
2209 * 'v2' or 'v4'.
2210 * @param {boolean} [config.virtualHostedStyle=false] Use virtual hosted-style
2211 * URLs (e.g. 'https://mybucket.storage.googleapis.com/...') instead of path-style
2212 * (e.g. 'https://storage.googleapis.com/mybucket/...'). Virtual hosted-style URLs
2213 * should generally be preferred instaed of path-style URL.
2214 * Currently defaults to `false` for path-style, although this may change in a
2215 * future major-version release.
2216 * @param {string} [config.cname] The cname for this bucket, i.e.,
2217 * "https://cdn.example.com".
2218 * @param {string} [config.contentMd5] The MD5 digest value in base64. Just like
2219 * if you provide this, the client must provide this HTTP header with this same
2220 * value in its request, so to if this parameter is not provided here,
2221 * the client must not provide any value for this HTTP header in its request.
2222 * @param {string} [config.contentType] Just like if you provide this, the client
2223 * must provide this HTTP header with this same value in its request, so to if
2224 * this parameter is not provided here, the client must not provide any value
2225 * for this HTTP header in its request.
2226 * @param {object} [config.extensionHeaders] If these headers are used, the
2227 * server will check to make sure that the client provides matching
2228 * values. See {@link https://cloud.google.com/storage/docs/access-control/signed-urls#about-canonical-extension-headers| Canonical extension headers}
2229 * for the requirements of this feature, most notably:
2230 * - The header name must be prefixed with `x-goog-`
2231 * - The header name must be all lowercase
2232 *
2233 * Note: Multi-valued header passed as an array in the extensionHeaders
2234 * object is converted into a string, delimited by `,` with
2235 * no space. Requests made using the signed URL will need to
2236 * delimit multi-valued headers using a single `,` as well, or
2237 * else the server will report a mismatched signature.
2238 * @param {object} [config.queryParams] Additional query parameters to include
2239 * in the signed URL.
2240 * @param {string} [config.promptSaveAs] The filename to prompt the user to
2241 * save the file as when the signed url is accessed. This is ignored if
2242 * `config.responseDisposition` is set.
2243 * @param {string} [config.responseDisposition] The
2244 * {@link http://goo.gl/yMWxQV| response-content-disposition parameter} of the
2245 * signed url.
2246 * @param {*} [config.accessibleAt=Date.now()] A timestamp when this link became usable. Any value
2247 * given is passed to `new Date()`.
2248 * Note: Use for 'v4' only.
2249 * @param {string} [config.responseType] The response-content-type parameter
2250 * of the signed url.
2251 * @param {GetSignedUrlCallback} [callback] Callback function.
2252 * @returns {Promise<GetSignedUrlResponse>}
2253 *
2254 * @example
2255 * ```
2256 * const {Storage} = require('@google-cloud/storage');
2257 * const storage = new Storage();
2258 * const myBucket = storage.bucket('my-bucket');
2259 *
2260 * const file = myBucket.file('my-file');
2261 *
2262 * //-
2263 * // Generate a URL that allows temporary access to download your file.
2264 * //-
2265 * const request = require('request');
2266 *
2267 * const config = {
2268 * action: 'read',
2269 * expires: '03-17-2025',
2270 * };
2271 *
2272 * file.getSignedUrl(config, function(err, url) {
2273 * if (err) {
2274 * console.error(err);
2275 * return;
2276 * }
2277 *
2278 * // The file is now available to read from this URL.
2279 * request(url, function(err, resp) {
2280 * // resp.statusCode = 200
2281 * });
2282 * });
2283 *
2284 * //-
2285 * // Generate a URL that allows temporary access to download your file.
2286 * // Access will begin at accessibleAt and end at expires.
2287 * //-
2288 * const request = require('request');
2289 *
2290 * const config = {
2291 * action: 'read',
2292 * expires: '03-17-2025',
2293 * accessibleAt: '03-13-2025'
2294 * };
2295 *
2296 * file.getSignedUrl(config, function(err, url) {
2297 * if (err) {
2298 * console.error(err);
2299 * return;
2300 * }
2301 *
2302 * // The file will be available to read from this URL from 03-13-2025 to 03-17-2025.
2303 * request(url, function(err, resp) {
2304 * // resp.statusCode = 200
2305 * });
2306 * });
2307 *
2308 * //-
2309 * // Generate a URL to allow write permissions. This means anyone with this
2310 * URL
2311 * // can send a POST request with new data that will overwrite the file.
2312 * //-
2313 * file.getSignedUrl({
2314 * action: 'write',
2315 * expires: '03-17-2025'
2316 * }, function(err, url) {
2317 * if (err) {
2318 * console.error(err);
2319 * return;
2320 * }
2321 *
2322 * // The file is now available to be written to.
2323 * const writeStream = request.put(url);
2324 * writeStream.end('New data');
2325 *
2326 * writeStream.on('complete', function(resp) {
2327 * // Confirm the new content was saved.
2328 * file.download(function(err, fileContents) {
2329 * console.log('Contents:', fileContents.toString());
2330 * // Contents: New data
2331 * });
2332 * });
2333 * });
2334 *
2335 * //-
2336 * // If the callback is omitted, we'll return a Promise.
2337 * //-
2338 * file.getSignedUrl(config).then(function(data) {
2339 * const url = data[0];
2340 * });
2341 *
2342 * ```
2343 * @example <caption>include:samples/files.js</caption>
2344 * region_tag:storage_generate_signed_url
2345 * Another example:
2346 */
2347 getSignedUrl(cfg, callback) {
2348 const method = ActionToHTTPMethod[cfg.action];
2349 const extensionHeaders = (0, util_js_2.objectKeyToLowercase)(cfg.extensionHeaders || {});
2350 if (cfg.action === 'resumable') {
2351 extensionHeaders['x-goog-resumable'] = 'start';
2352 }
2353 const queryParams = Object.assign({}, cfg.queryParams);
2354 if (typeof cfg.responseType === 'string') {
2355 queryParams['response-content-type'] = cfg.responseType;
2356 }
2357 if (typeof cfg.promptSaveAs === 'string') {
2358 queryParams['response-content-disposition'] =
2359 'attachment; filename="' + cfg.promptSaveAs + '"';
2360 }
2361 if (typeof cfg.responseDisposition === 'string') {
2362 queryParams['response-content-disposition'] = cfg.responseDisposition;
2363 }
2364 if (this.generation) {
2365 queryParams['generation'] = this.generation.toString();
2366 }
2367 const signConfig = {
2368 method,
2369 expires: cfg.expires,
2370 accessibleAt: cfg.accessibleAt,
2371 extensionHeaders,
2372 queryParams,
2373 contentMd5: cfg.contentMd5,
2374 contentType: cfg.contentType,
2375 host: cfg.host,
2376 };
2377 if (cfg.cname) {
2378 signConfig.cname = cfg.cname;
2379 }
2380 if (cfg.version) {
2381 signConfig.version = cfg.version;
2382 }
2383 if (cfg.virtualHostedStyle) {
2384 signConfig.virtualHostedStyle = cfg.virtualHostedStyle;
2385 }
2386 if (!this.signer) {
2387 this.signer = new signer_js_1.URLSigner(this.storage.authClient, this.bucket, this, this.storage);
2388 }
2389 this.signer
2390 .getSignedUrl(signConfig)
2391 .then(signedUrl => callback(null, signedUrl), callback);
2392 }
2393 /**
2394 * @callback IsPublicCallback
2395 * @param {?Error} err Request error, if any.
2396 * @param {boolean} resp Whether file is public or not.
2397 */
2398 /**
2399 * @typedef {array} IsPublicResponse
2400 * @property {boolean} 0 Whether file is public or not.
2401 */
2402 /**
2403 * Check whether this file is public or not by sending
2404 * a HEAD request without credentials.
2405 * No errors from the server indicates that the current
2406 * file is public.
2407 * A 403-Forbidden error {@link https://cloud.google.com/storage/docs/json_api/v1/status-codes#403_Forbidden}
2408 * indicates that file is private.
2409 * Any other non 403 error is propagated to user.
2410 *
2411 * @param {IsPublicCallback} [callback] Callback function.
2412 * @returns {Promise<IsPublicResponse>}
2413 *
2414 * @example
2415 * ```
2416 * const {Storage} = require('@google-cloud/storage');
2417 * const storage = new Storage();
2418 * const myBucket = storage.bucket('my-bucket');
2419 *
2420 * const file = myBucket.file('my-file');
2421 *
2422 * //-
2423 * // Check whether the file is publicly accessible.
2424 * //-
2425 * file.isPublic(function(err, resp) {
2426 * if (err) {
2427 * console.error(err);
2428 * return;
2429 * }
2430 * console.log(`the file ${file.id} is public: ${resp}`) ;
2431 * })
2432 * //-
2433 * // If the callback is omitted, we'll return a Promise.
2434 * //-
2435 * file.isPublic().then(function(data) {
2436 * const resp = data[0];
2437 * });
2438 * ```
2439 */
2440 isPublic(callback) {
2441 var _a;
2442 // Build any custom headers based on the defined interceptors on the parent
2443 // storage object and this object
2444 const storageInterceptors = ((_a = this.storage) === null || _a === void 0 ? void 0 : _a.interceptors) || [];
2445 const fileInterceptors = this.interceptors || [];
2446 const allInterceptors = storageInterceptors.concat(fileInterceptors);
2447 const headers = allInterceptors.reduce((acc, curInterceptor) => {
2448 const currentHeaders = curInterceptor.request({
2449 uri: `${this.storage.apiEndpoint}/${this.bucket.name}/${encodeURIComponent(this.name)}`,
2450 });
2451 Object.assign(acc, currentHeaders.headers);
2452 return acc;
2453 }, {});
2454 index_js_1.util.makeRequest({
2455 method: 'GET',
2456 uri: `${this.storage.apiEndpoint}/${this.bucket.name}/${encodeURIComponent(this.name)}`,
2457 headers,
2458 }, {
2459 retryOptions: this.storage.retryOptions,
2460 }, (err) => {
2461 if (err) {
2462 const apiError = err;
2463 if (apiError.code === 403) {
2464 callback(null, false);
2465 }
2466 else {
2467 callback(err);
2468 }
2469 }
2470 else {
2471 callback(null, true);
2472 }
2473 });
2474 }
2475 /**
2476 * @typedef {object} MakeFilePrivateOptions Configuration options for File#makePrivate().
2477 * @property {Metadata} [metadata] Define custom metadata properties to define
2478 * along with the operation.
2479 * @property {boolean} [strict] If true, set the file to be private to
2480 * only the owner user. Otherwise, it will be private to the project.
2481 * @property {string} [userProject] The ID of the project which will be
2482 * billed for the request.
2483 */
2484 /**
2485 * @callback MakeFilePrivateCallback
2486 * @param {?Error} err Request error, if any.
2487 * @param {object} apiResponse The full API response.
2488 */
2489 /**
2490 * @typedef {array} MakeFilePrivateResponse
2491 * @property {object} 0 The full API response.
2492 */
2493 /**
2494 * Make a file private to the project and remove all other permissions.
2495 * Set `options.strict` to true to make the file private to only the owner.
2496 *
2497 * See {@link https://cloud.google.com/storage/docs/json_api/v1/objects/patch| Objects: patch API Documentation}
2498 *
2499 * @param {MakeFilePrivateOptions} [options] Configuration options.
2500 * @param {MakeFilePrivateCallback} [callback] Callback function.
2501 * @returns {Promise<MakeFilePrivateResponse>}
2502 *
2503 * @example
2504 * ```
2505 * const {Storage} = require('@google-cloud/storage');
2506 * const storage = new Storage();
2507 * const myBucket = storage.bucket('my-bucket');
2508 *
2509 * const file = myBucket.file('my-file');
2510 *
2511 * //-
2512 * // Set the file private so only project maintainers can see and modify it.
2513 * //-
2514 * file.makePrivate(function(err) {});
2515 *
2516 * //-
2517 * // Set the file private so only the owner can see and modify it.
2518 * //-
2519 * file.makePrivate({ strict: true }, function(err) {});
2520 *
2521 * //-
2522 * // If the callback is omitted, we'll return a Promise.
2523 * //-
2524 * file.makePrivate().then(function(data) {
2525 * const apiResponse = data[0];
2526 * });
2527 * ```
2528 */
2529 makePrivate(optionsOrCallback, callback) {
2530 var _a, _b;
2531 const options = typeof optionsOrCallback === 'object' ? optionsOrCallback : {};
2532 callback =
2533 typeof optionsOrCallback === 'function' ? optionsOrCallback : callback;
2534 const query = {
2535 predefinedAcl: options.strict ? 'private' : 'projectPrivate',
2536 // eslint-disable-next-line @typescript-eslint/no-explicit-any
2537 };
2538 if (((_a = options.preconditionOpts) === null || _a === void 0 ? void 0 : _a.ifMetagenerationMatch) !== undefined) {
2539 query.ifMetagenerationMatch =
2540 (_b = options.preconditionOpts) === null || _b === void 0 ? void 0 : _b.ifMetagenerationMatch;
2541 delete options.preconditionOpts;
2542 }
2543 if (options.userProject) {
2544 query.userProject = options.userProject;
2545 }
2546 // You aren't allowed to set both predefinedAcl & acl properties on a file,
2547 // so acl must explicitly be nullified, destroying all previous acls on the
2548 // file.
2549 const metadata = { ...options.metadata, acl: null };
2550 this.setMetadata(metadata, query, callback);
2551 }
2552 /**
2553 * @typedef {array} MakeFilePublicResponse
2554 * @property {object} 0 The full API response.
2555 */
2556 /**
2557 * @callback MakeFilePublicCallback
2558 * @param {?Error} err Request error, if any.
2559 * @param {object} apiResponse The full API response.
2560 */
2561 /**
2562 * Set a file to be publicly readable and maintain all previous permissions.
2563 *
2564 * See {@link https://cloud.google.com/storage/docs/json_api/v1/objectAccessControls/insert| ObjectAccessControls: insert API Documentation}
2565 *
2566 * @param {MakeFilePublicCallback} [callback] Callback function.
2567 * @returns {Promise<MakeFilePublicResponse>}
2568 *
2569 * @example
2570 * ```
2571 * const {Storage} = require('@google-cloud/storage');
2572 * const storage = new Storage();
2573 * const myBucket = storage.bucket('my-bucket');
2574 *
2575 * const file = myBucket.file('my-file');
2576 *
2577 * file.makePublic(function(err, apiResponse) {});
2578 *
2579 * //-
2580 * // If the callback is omitted, we'll return a Promise.
2581 * //-
2582 * file.makePublic().then(function(data) {
2583 * const apiResponse = data[0];
2584 * });
2585 *
2586 * ```
2587 * @example <caption>include:samples/files.js</caption>
2588 * region_tag:storage_make_public
2589 * Another example:
2590 */
2591 makePublic(callback) {
2592 callback = callback || index_js_1.util.noop;
2593 this.acl.add({
2594 entity: 'allUsers',
2595 role: 'READER',
2596 }, (err, acl, resp) => {
2597 callback(err, resp);
2598 });
2599 }
2600 /**
2601 * The public URL of this File
2602 * Use {@link File#makePublic} to enable anonymous access via the returned URL.
2603 *
2604 * @returns {string}
2605 *
2606 * @example
2607 * ```
2608 * const {Storage} = require('@google-cloud/storage');
2609 * const storage = new Storage();
2610 * const bucket = storage.bucket('albums');
2611 * const file = bucket.file('my-file');
2612 *
2613 * // publicUrl will be "https://storage.googleapis.com/albums/my-file"
2614 * const publicUrl = file.publicUrl();
2615 * ```
2616 */
2617 publicUrl() {
2618 return `${this.storage.apiEndpoint}/${this.bucket.name}/${encodeURIComponent(this.name)}`;
2619 }
2620 /**
2621 * @typedef {array} MoveResponse
2622 * @property {File} 0 The destination File.
2623 * @property {object} 1 The full API response.
2624 */
2625 /**
2626 * @callback MoveCallback
2627 * @param {?Error} err Request error, if any.
2628 * @param {?File} destinationFile The destination File.
2629 * @param {object} apiResponse The full API response.
2630 */
2631 /**
2632 * @typedef {object} MoveOptions Configuration options for File#move(). See an
2633 * {@link https://cloud.google.com/storage/docs/json_api/v1/objects#resource| Object resource}.
2634 * @param {string} [userProject] The ID of the project which will be
2635 * billed for the request.
2636 */
2637 /**
2638 * Move this file to another location. By default, this will rename the file
2639 * and keep it in the same bucket, but you can choose to move it to another
2640 * Bucket by providing a Bucket or File object or a URL beginning with
2641 * "gs://".
2642 *
2643 * **Warning**:
2644 * There is currently no atomic `move` method in the Cloud Storage API,
2645 * so this method is a composition of {@link File#copy} (to the new
2646 * location) and {@link File#delete} (from the old location). While
2647 * unlikely, it is possible that an error returned to your callback could be
2648 * triggered from either one of these API calls failing, which could leave a
2649 * duplicate file lingering. The error message will indicate what operation
2650 * has failed.
2651 *
2652 * See {@link https://cloud.google.com/storage/docs/json_api/v1/objects/copy| Objects: copy API Documentation}
2653 *
2654 * @throws {Error} If the destination file is not provided.
2655 *
2656 * @param {string|Bucket|File} destination Destination file.
2657 * @param {MoveCallback} [callback] Callback function.
2658 * @returns {Promise<MoveResponse>}
2659 *
2660 * @example
2661 * ```
2662 * const {Storage} = require('@google-cloud/storage');
2663 * const storage = new Storage();
2664 * //-
2665 * // You can pass in a variety of types for the destination.
2666 * //
2667 * // For all of the below examples, assume we are working with the following
2668 * // Bucket and File objects.
2669 * //-
2670 * const bucket = storage.bucket('my-bucket');
2671 * const file = bucket.file('my-image.png');
2672 *
2673 * //-
2674 * // If you pass in a string for the destination, the file is moved to its
2675 * // current bucket, under the new name provided.
2676 * //-
2677 * file.move('my-image-new.png', function(err, destinationFile, apiResponse) {
2678 * // `my-bucket` no longer contains:
2679 * // - "my-image.png"
2680 * // but contains instead:
2681 * // - "my-image-new.png"
2682 *
2683 * // `destinationFile` is an instance of a File object that refers to your
2684 * // new file.
2685 * });
2686 *
2687 * //-
2688 * // If you pass in a string starting with "gs://" for the destination, the
2689 * // file is copied to the other bucket and under the new name provided.
2690 * //-
2691 * const newLocation = 'gs://another-bucket/my-image-new.png';
2692 * file.move(newLocation, function(err, destinationFile, apiResponse) {
2693 * // `my-bucket` no longer contains:
2694 * // - "my-image.png"
2695 * //
2696 * // `another-bucket` now contains:
2697 * // - "my-image-new.png"
2698 *
2699 * // `destinationFile` is an instance of a File object that refers to your
2700 * // new file.
2701 * });
2702 *
2703 * //-
2704 * // If you pass in a Bucket object, the file will be moved to that bucket
2705 * // using the same name.
2706 * //-
2707 * const anotherBucket = gcs.bucket('another-bucket');
2708 *
2709 * file.move(anotherBucket, function(err, destinationFile, apiResponse) {
2710 * // `my-bucket` no longer contains:
2711 * // - "my-image.png"
2712 * //
2713 * // `another-bucket` now contains:
2714 * // - "my-image.png"
2715 *
2716 * // `destinationFile` is an instance of a File object that refers to your
2717 * // new file.
2718 * });
2719 *
2720 * //-
2721 * // If you pass in a File object, you have complete control over the new
2722 * // bucket and filename.
2723 * //-
2724 * const anotherFile = anotherBucket.file('my-awesome-image.png');
2725 *
2726 * file.move(anotherFile, function(err, destinationFile, apiResponse) {
2727 * // `my-bucket` no longer contains:
2728 * // - "my-image.png"
2729 * //
2730 * // `another-bucket` now contains:
2731 * // - "my-awesome-image.png"
2732 *
2733 * // Note:
2734 * // The `destinationFile` parameter is equal to `anotherFile`.
2735 * });
2736 *
2737 * //-
2738 * // If the callback is omitted, we'll return a Promise.
2739 * //-
2740 * file.move('my-image-new.png').then(function(data) {
2741 * const destinationFile = data[0];
2742 * const apiResponse = data[1];
2743 * });
2744 *
2745 * ```
2746 * @example <caption>include:samples/files.js</caption>
2747 * region_tag:storage_move_file
2748 * Another example:
2749 */
2750 move(destination, optionsOrCallback, callback) {
2751 const options = typeof optionsOrCallback === 'object' ? optionsOrCallback : {};
2752 callback =
2753 typeof optionsOrCallback === 'function' ? optionsOrCallback : callback;
2754 callback = callback || index_js_1.util.noop;
2755 this.copy(destination, options, (err, destinationFile, copyApiResponse) => {
2756 if (err) {
2757 err.message = 'file#copy failed with an error - ' + err.message;
2758 callback(err, null, copyApiResponse);
2759 return;
2760 }
2761 if (this.name !== destinationFile.name ||
2762 this.bucket.name !== destinationFile.bucket.name) {
2763 this.delete(options, (err, apiResponse) => {
2764 if (err) {
2765 err.message = 'file#delete failed with an error - ' + err.message;
2766 callback(err, destinationFile, apiResponse);
2767 return;
2768 }
2769 callback(null, destinationFile, copyApiResponse);
2770 });
2771 }
2772 else {
2773 callback(null, destinationFile, copyApiResponse);
2774 }
2775 });
2776 }
2777 /**
2778 * @typedef {array} RenameResponse
2779 * @property {File} 0 The destination File.
2780 * @property {object} 1 The full API response.
2781 */
2782 /**
2783 * @callback RenameCallback
2784 * @param {?Error} err Request error, if any.
2785 * @param {?File} destinationFile The destination File.
2786 * @param {object} apiResponse The full API response.
2787 */
2788 /**
2789 * @typedef {object} RenameOptions Configuration options for File#move(). See an
2790 * {@link https://cloud.google.com/storage/docs/json_api/v1/objects#resource| Object resource}.
2791 * @param {string} [userProject] The ID of the project which will be
2792 * billed for the request.
2793 */
2794 /**
2795 * Rename this file.
2796 *
2797 * **Warning**:
2798 * There is currently no atomic `rename` method in the Cloud Storage API,
2799 * so this method is an alias of {@link File#move}, which in turn is a
2800 * composition of {@link File#copy} (to the new location) and
2801 * {@link File#delete} (from the old location). While
2802 * unlikely, it is possible that an error returned to your callback could be
2803 * triggered from either one of these API calls failing, which could leave a
2804 * duplicate file lingering. The error message will indicate what operation
2805 * has failed.
2806 *
2807 * @param {string|File} destinationFile Destination file.
2808 * @param {RenameCallback} [callback] Callback function.
2809 * @returns {Promise<RenameResponse>}
2810 *
2811 * @example
2812 * ```
2813 * const {Storage} = require('@google-cloud/storage');
2814 * const storage = new Storage();
2815 *
2816 * //-
2817 * // You can pass in a string or a File object.
2818 * //
2819 * // For all of the below examples, assume we are working with the following
2820 * // Bucket and File objects.
2821 * //-
2822 *
2823 * const bucket = storage.bucket('my-bucket');
2824 * const file = bucket.file('my-image.png');
2825 *
2826 * //-
2827 * // You can pass in a string for the destinationFile.
2828 * //-
2829 * file.rename('renamed-image.png', function(err, renamedFile, apiResponse) {
2830 * // `my-bucket` no longer contains:
2831 * // - "my-image.png"
2832 * // but contains instead:
2833 * // - "renamed-image.png"
2834 *
2835 * // `renamedFile` is an instance of a File object that refers to your
2836 * // renamed file.
2837 * });
2838 *
2839 * //-
2840 * // You can pass in a File object.
2841 * //-
2842 * const anotherFile = anotherBucket.file('my-awesome-image.png');
2843 *
2844 * file.rename(anotherFile, function(err, renamedFile, apiResponse) {
2845 * // `my-bucket` no longer contains:
2846 * // - "my-image.png"
2847 *
2848 * // Note:
2849 * // The `renamedFile` parameter is equal to `anotherFile`.
2850 * });
2851 *
2852 * //-
2853 * // If the callback is omitted, we'll return a Promise.
2854 * //-
2855 * file.rename('my-renamed-image.png').then(function(data) {
2856 * const renamedFile = data[0];
2857 * const apiResponse = data[1];
2858 * });
2859 * ```
2860 */
2861 rename(destinationFile, optionsOrCallback, callback) {
2862 const options = typeof optionsOrCallback === 'object' ? optionsOrCallback : {};
2863 callback =
2864 typeof optionsOrCallback === 'function' ? optionsOrCallback : callback;
2865 callback = callback || index_js_1.util.noop;
2866 this.move(destinationFile, options, callback);
2867 }
2868 /**
2869 * @typedef {object} RestoreOptions Options for File#restore(). See an
2870 * {@link https://cloud.google.com/storage/docs/json_api/v1/objects#resource| Object resource}.
2871 * @param {string} [userProject] The ID of the project which will be
2872 * billed for the request.
2873 * @param {number} [generation] If present, selects a specific revision of this object.
2874 * @param {string} [projection] Specifies the set of properties to return. If used, must be 'full' or 'noAcl'.
2875 * @param {string | number} [ifGenerationMatch] Request proceeds if the generation of the target resource
2876 * matches the value used in the precondition.
2877 * If the values don't match, the request fails with a 412 Precondition Failed response.
2878 * @param {string | number} [ifGenerationNotMatch] Request proceeds if the generation of the target resource does
2879 * not match the value used in the precondition. If the values match, the request fails with a 304 Not Modified response.
2880 * @param {string | number} [ifMetagenerationMatch] Request proceeds if the meta-generation of the target resource
2881 * matches the value used in the precondition.
2882 * If the values don't match, the request fails with a 412 Precondition Failed response.
2883 * @param {string | number} [ifMetagenerationNotMatch] Request proceeds if the meta-generation of the target resource does
2884 * not match the value used in the precondition. If the values match, the request fails with a 304 Not Modified response.
2885 */
2886 /**
2887 * Restores a soft-deleted file
2888 * @param {RestoreOptions} options Restore options.
2889 * @returns {Promise<File>}
2890 */
2891 async restore(options) {
2892 const [file] = await this.request({
2893 method: 'POST',
2894 uri: '/restore',
2895 qs: options,
2896 });
2897 return file;
2898 }
2899 /**
2900 * Makes request and applies userProject query parameter if necessary.
2901 *
2902 * @private
2903 *
2904 * @param {object} reqOpts - The request options.
2905 * @param {function} callback - The callback function.
2906 */
2907 request(reqOpts, callback) {
2908 return this.parent.request.call(this, reqOpts, callback);
2909 }
2910 /**
2911 * @callback RotateEncryptionKeyCallback
2912 * @extends CopyCallback
2913 */
2914 /**
2915 * @typedef RotateEncryptionKeyResponse
2916 * @extends CopyResponse
2917 */
2918 /**
2919 * @param {string|buffer|object} RotateEncryptionKeyOptions Configuration options
2920 * for File#rotateEncryptionKey().
2921 * If a string or Buffer is provided, it is interpreted as an AES-256,
2922 * customer-supplied encryption key. If you'd like to use a Cloud KMS key
2923 * name, you must specify an options object with the property name:
2924 * `kmsKeyName`.
2925 * @param {string|buffer} [options.encryptionKey] An AES-256 encryption key.
2926 * @param {string} [options.kmsKeyName] A Cloud KMS key name.
2927 */
2928 /**
2929 * This method allows you to update the encryption key associated with this
2930 * file.
2931 *
2932 * See {@link https://cloud.google.com/storage/docs/encryption#customer-supplied| Customer-supplied Encryption Keys}
2933 *
2934 * @param {RotateEncryptionKeyOptions} [options] - Configuration options.
2935 * @param {RotateEncryptionKeyCallback} [callback]
2936 * @returns {Promise<File>}
2937 *
2938 * @example <caption>include:samples/encryption.js</caption>
2939 * region_tag:storage_rotate_encryption_key
2940 * Example of rotating the encryption key for this file:
2941 */
2942 rotateEncryptionKey(optionsOrCallback, callback) {
2943 var _a;
2944 callback =
2945 typeof optionsOrCallback === 'function' ? optionsOrCallback : callback;
2946 let options = {};
2947 if (typeof optionsOrCallback === 'string' ||
2948 optionsOrCallback instanceof Buffer) {
2949 options = {
2950 encryptionKey: optionsOrCallback,
2951 };
2952 }
2953 else if (typeof optionsOrCallback === 'object') {
2954 options = optionsOrCallback;
2955 }
2956 const newFile = this.bucket.file(this.id, options);
2957 const copyOptions = ((_a = options.preconditionOpts) === null || _a === void 0 ? void 0 : _a.ifGenerationMatch) !== undefined
2958 ? { preconditionOpts: options.preconditionOpts }
2959 : {};
2960 this.copy(newFile, copyOptions, callback);
2961 }
2962 /**
2963 * @typedef {object} SaveOptions
2964 * @extends CreateWriteStreamOptions
2965 */
2966 /**
2967 * @callback SaveCallback
2968 * @param {?Error} err Request error, if any.
2969 */
2970 /**
2971 * Write strings or buffers to a file.
2972 *
2973 * *This is a convenience method which wraps {@link File#createWriteStream}.*
2974 * To upload arbitrary data to a file, please use {@link File#createWriteStream} directly.
2975 *
2976 * Resumable uploads are automatically enabled and must be shut off explicitly
2977 * by setting `options.resumable` to `false`.
2978 *
2979 * Multipart uploads with retryable error codes will be retried 3 times with exponential backoff.
2980 *
2981 * <p class="notice">
2982 * There is some overhead when using a resumable upload that can cause
2983 * noticeable performance degradation while uploading a series of small
2984 * files. When uploading files less than 10MB, it is recommended that the
2985 * resumable feature is disabled.
2986 * </p>
2987 *
2988 * @param {SaveData} data The data to write to a file.
2989 * @param {SaveOptions} [options] See {@link File#createWriteStream}'s `options`
2990 * parameter.
2991 * @param {SaveCallback} [callback] Callback function.
2992 * @returns {Promise}
2993 *
2994 * @example
2995 * ```
2996 * const {Storage} = require('@google-cloud/storage');
2997 * const storage = new Storage();
2998 * const myBucket = storage.bucket('my-bucket');
2999 *
3000 * const file = myBucket.file('my-file');
3001 * const contents = 'This is the contents of the file.';
3002 *
3003 * file.save(contents, function(err) {
3004 * if (!err) {
3005 * // File written successfully.
3006 * }
3007 * });
3008 *
3009 * //-
3010 * // If the callback is omitted, we'll return a Promise.
3011 * //-
3012 * file.save(contents).then(function() {});
3013 * ```
3014 */
3015 save(data, optionsOrCallback, callback) {
3016 // tslint:enable:no-any
3017 callback =
3018 typeof optionsOrCallback === 'function' ? optionsOrCallback : callback;
3019 const options = typeof optionsOrCallback === 'object' ? optionsOrCallback : {};
3020 let maxRetries = this.storage.retryOptions.maxRetries;
3021 if (!this.shouldRetryBasedOnPreconditionAndIdempotencyStrat(options === null || options === void 0 ? void 0 : options.preconditionOpts)) {
3022 maxRetries = 0;
3023 }
3024 const returnValue = (0, async_retry_1.default)(async (bail) => {
3025 return new Promise((resolve, reject) => {
3026 if (maxRetries === 0) {
3027 this.storage.retryOptions.autoRetry = false;
3028 }
3029 const writable = this.createWriteStream(options);
3030 if (options.onUploadProgress) {
3031 writable.on('progress', options.onUploadProgress);
3032 }
3033 const handleError = (err) => {
3034 if (this.storage.retryOptions.autoRetry &&
3035 this.storage.retryOptions.retryableErrorFn(err)) {
3036 return reject(err);
3037 }
3038 return bail(err);
3039 };
3040 if (typeof data === 'string' ||
3041 Buffer.isBuffer(data) ||
3042 data instanceof Uint8Array) {
3043 writable
3044 .on('error', handleError)
3045 .on('finish', () => resolve())
3046 .end(data);
3047 }
3048 else {
3049 (0, stream_1.pipeline)(data, writable, err => {
3050 if (err) {
3051 if (typeof data !== 'function') {
3052 // Only PipelineSourceFunction can be retried. Async-iterables
3053 // and Readable streams can only be consumed once.
3054 return bail(err);
3055 }
3056 handleError(err);
3057 }
3058 else {
3059 resolve();
3060 }
3061 });
3062 }
3063 });
3064 }, {
3065 retries: maxRetries,
3066 factor: this.storage.retryOptions.retryDelayMultiplier,
3067 maxTimeout: this.storage.retryOptions.maxRetryDelay * 1000, //convert to milliseconds
3068 maxRetryTime: this.storage.retryOptions.totalTimeout * 1000, //convert to milliseconds
3069 });
3070 if (!callback) {
3071 return returnValue;
3072 }
3073 else {
3074 return returnValue
3075 .then(() => {
3076 if (callback) {
3077 return callback();
3078 }
3079 })
3080 .catch(callback);
3081 }
3082 }
3083 setMetadata(metadata, optionsOrCallback, cb) {
3084 // eslint-disable-next-line @typescript-eslint/no-explicit-any
3085 const options = typeof optionsOrCallback === 'object' ? optionsOrCallback : {};
3086 cb =
3087 typeof optionsOrCallback === 'function'
3088 ? optionsOrCallback
3089 : cb;
3090 this.disableAutoRetryConditionallyIdempotent_(this.methods.setMetadata, bucket_js_1.AvailableServiceObjectMethods.setMetadata, options);
3091 super
3092 .setMetadata(metadata, options)
3093 .then(resp => cb(null, ...resp))
3094 .catch(cb)
3095 .finally(() => {
3096 this.storage.retryOptions.autoRetry = this.instanceRetryValue;
3097 });
3098 }
3099 /**
3100 * @typedef {array} SetStorageClassResponse
3101 * @property {object} 0 The full API response.
3102 */
3103 /**
3104 * @typedef {object} SetStorageClassOptions Configuration options for File#setStorageClass().
3105 * @property {string} [userProject] The ID of the project which will be
3106 * billed for the request.
3107 */
3108 /**
3109 * @callback SetStorageClassCallback
3110 * @param {?Error} err Request error, if any.
3111 * @param {object} apiResponse The full API response.
3112 */
3113 /**
3114 * Set the storage class for this file.
3115 *
3116 * See {@link https://cloud.google.com/storage/docs/per-object-storage-class| Per-Object Storage Class}
3117 * See {@link https://cloud.google.com/storage/docs/storage-classes| Storage Classes}
3118 *
3119 * @param {string} storageClass The new storage class. (`standard`,
3120 * `nearline`, `coldline`, or `archive`)
3121 * **Note:** The storage classes `multi_regional` and `regional`
3122 * are now legacy and will be deprecated in the future.
3123 * @param {SetStorageClassOptions} [options] Configuration options.
3124 * @param {string} [options.userProject] The ID of the project which will be
3125 * billed for the request.
3126 * @param {SetStorageClassCallback} [callback] Callback function.
3127 * @returns {Promise<SetStorageClassResponse>}
3128 *
3129 * @example
3130 * ```
3131 * file.setStorageClass('nearline', function(err, apiResponse) {
3132 * if (err) {
3133 * // Error handling omitted.
3134 * }
3135 *
3136 * // The storage class was updated successfully.
3137 * });
3138 *
3139 * //-
3140 * // If the callback is omitted, we'll return a Promise.
3141 * //-
3142 * file.setStorageClass('nearline').then(function() {});
3143 * ```
3144 */
3145 setStorageClass(storageClass, optionsOrCallback, callback) {
3146 callback =
3147 typeof optionsOrCallback === 'function' ? optionsOrCallback : callback;
3148 const options = typeof optionsOrCallback === 'object' ? optionsOrCallback : {};
3149 const req = {
3150 ...options,
3151 // In case we get input like `storageClass`, convert to `storage_class`.
3152 storageClass: storageClass
3153 .replace(/-/g, '_')
3154 .replace(/([a-z])([A-Z])/g, (_, low, up) => {
3155 return low + '_' + up;
3156 })
3157 .toUpperCase(),
3158 };
3159 this.copy(this, req, (err, file, apiResponse) => {
3160 if (err) {
3161 callback(err, apiResponse);
3162 return;
3163 }
3164 this.metadata = file.metadata;
3165 callback(null, apiResponse);
3166 });
3167 }
3168 /**
3169 * Set a user project to be billed for all requests made from this File
3170 * object.
3171 *
3172 * @param {string} userProject The user project.
3173 *
3174 * @example
3175 * ```
3176 * const {Storage} = require('@google-cloud/storage');
3177 * const storage = new Storage();
3178 * const bucket = storage.bucket('albums');
3179 * const file = bucket.file('my-file');
3180 *
3181 * file.setUserProject('grape-spaceship-123');
3182 * ```
3183 */
3184 setUserProject(userProject) {
3185 this.bucket.setUserProject.call(this, userProject);
3186 }
3187 /**
3188 * This creates a resumable-upload upload stream.
3189 *
3190 * @param {Duplexify} stream - Duplexify stream of data to pipe to the file.
3191 * @param {object=} options - Configuration object.
3192 *
3193 * @private
3194 */
3195 startResumableUpload_(dup, options = {}) {
3196 var _a;
3197 (_a = options.metadata) !== null && _a !== void 0 ? _a : (options.metadata = {});
3198 const retryOptions = this.storage.retryOptions;
3199 if (!this.shouldRetryBasedOnPreconditionAndIdempotencyStrat(options.preconditionOpts)) {
3200 retryOptions.autoRetry = false;
3201 }
3202 const cfg = {
3203 authClient: this.storage.authClient,
3204 apiEndpoint: this.storage.apiEndpoint,
3205 bucket: this.bucket.name,
3206 customRequestOptions: this.getRequestInterceptors().reduce((reqOpts, interceptorFn) => interceptorFn(reqOpts), {}),
3207 file: this.name,
3208 generation: this.generation,
3209 isPartialUpload: options.isPartialUpload,
3210 key: this.encryptionKey,
3211 kmsKeyName: this.kmsKeyName,
3212 metadata: options.metadata,
3213 offset: options.offset,
3214 predefinedAcl: options.predefinedAcl,
3215 private: options.private,
3216 public: options.public,
3217 uri: options.uri,
3218 userProject: options.userProject || this.userProject,
3219 retryOptions: { ...retryOptions },
3220 params: (options === null || options === void 0 ? void 0 : options.preconditionOpts) || this.instancePreconditionOpts,
3221 chunkSize: options === null || options === void 0 ? void 0 : options.chunkSize,
3222 highWaterMark: options === null || options === void 0 ? void 0 : options.highWaterMark,
3223 universeDomain: this.bucket.storage.universeDomain,
3224 [util_js_1.GCCL_GCS_CMD_KEY]: options[util_js_1.GCCL_GCS_CMD_KEY],
3225 };
3226 let uploadStream;
3227 try {
3228 uploadStream = resumableUpload.upload(cfg);
3229 }
3230 catch (error) {
3231 dup.destroy(error);
3232 this.storage.retryOptions.autoRetry = this.instanceRetryValue;
3233 return;
3234 }
3235 uploadStream
3236 .on('response', resp => {
3237 dup.emit('response', resp);
3238 })
3239 .on('uri', uri => {
3240 dup.emit('uri', uri);
3241 })
3242 .on('metadata', metadata => {
3243 this.metadata = metadata;
3244 dup.emit('metadata');
3245 })
3246 .on('finish', () => {
3247 dup.emit('complete');
3248 })
3249 .on('progress', evt => dup.emit('progress', evt));
3250 dup.setWritable(uploadStream);
3251 this.storage.retryOptions.autoRetry = this.instanceRetryValue;
3252 }
3253 /**
3254 * Takes a readable stream and pipes it to a remote file. Unlike
3255 * `startResumableUpload_`, which uses the resumable upload technique, this
3256 * method uses a simple upload (all or nothing).
3257 *
3258 * @param {Duplexify} dup - Duplexify stream of data to pipe to the file.
3259 * @param {object=} options - Configuration object.
3260 *
3261 * @private
3262 */
3263 startSimpleUpload_(dup, options = {}) {
3264 var _a;
3265 (_a = options.metadata) !== null && _a !== void 0 ? _a : (options.metadata = {});
3266 const apiEndpoint = this.storage.apiEndpoint;
3267 const bucketName = this.bucket.name;
3268 const uri = `${apiEndpoint}/upload/storage/v1/b/${bucketName}/o`;
3269 const reqOpts = {
3270 qs: {
3271 name: this.name,
3272 },
3273 uri: uri,
3274 [util_js_1.GCCL_GCS_CMD_KEY]: options[util_js_1.GCCL_GCS_CMD_KEY],
3275 };
3276 if (this.generation !== undefined) {
3277 reqOpts.qs.ifGenerationMatch = this.generation;
3278 }
3279 if (this.kmsKeyName !== undefined) {
3280 reqOpts.qs.kmsKeyName = this.kmsKeyName;
3281 }
3282 if (typeof options.timeout === 'number') {
3283 reqOpts.timeout = options.timeout;
3284 }
3285 if (options.userProject || this.userProject) {
3286 reqOpts.qs.userProject = options.userProject || this.userProject;
3287 }
3288 if (options.predefinedAcl) {
3289 reqOpts.qs.predefinedAcl = options.predefinedAcl;
3290 }
3291 else if (options.private) {
3292 reqOpts.qs.predefinedAcl = 'private';
3293 }
3294 else if (options.public) {
3295 reqOpts.qs.predefinedAcl = 'publicRead';
3296 }
3297 Object.assign(reqOpts.qs, this.instancePreconditionOpts, options.preconditionOpts);
3298 index_js_1.util.makeWritableStream(dup, {
3299 makeAuthenticatedRequest: (reqOpts) => {
3300 this.request(reqOpts, (err, body, resp) => {
3301 if (err) {
3302 dup.destroy(err);
3303 return;
3304 }
3305 this.metadata = body;
3306 dup.emit('metadata', body);
3307 dup.emit('response', resp);
3308 dup.emit('complete');
3309 });
3310 },
3311 metadata: options.metadata,
3312 request: reqOpts,
3313 });
3314 }
3315 disableAutoRetryConditionallyIdempotent_(
3316 // eslint-disable-next-line @typescript-eslint/no-explicit-any
3317 coreOpts, methodType, localPreconditionOptions) {
3318 var _a, _b, _c, _d;
3319 if ((typeof coreOpts === 'object' &&
3320 ((_b = (_a = coreOpts === null || coreOpts === void 0 ? void 0 : coreOpts.reqOpts) === null || _a === void 0 ? void 0 : _a.qs) === null || _b === void 0 ? void 0 : _b.ifGenerationMatch) === undefined &&
3321 (localPreconditionOptions === null || localPreconditionOptions === void 0 ? void 0 : localPreconditionOptions.ifGenerationMatch) === undefined &&
3322 methodType === bucket_js_1.AvailableServiceObjectMethods.delete &&
3323 this.storage.retryOptions.idempotencyStrategy ===
3324 storage_js_1.IdempotencyStrategy.RetryConditional) ||
3325 this.storage.retryOptions.idempotencyStrategy ===
3326 storage_js_1.IdempotencyStrategy.RetryNever) {
3327 this.storage.retryOptions.autoRetry = false;
3328 }
3329 if ((typeof coreOpts === 'object' &&
3330 ((_d = (_c = coreOpts === null || coreOpts === void 0 ? void 0 : coreOpts.reqOpts) === null || _c === void 0 ? void 0 : _c.qs) === null || _d === void 0 ? void 0 : _d.ifMetagenerationMatch) === undefined &&
3331 (localPreconditionOptions === null || localPreconditionOptions === void 0 ? void 0 : localPreconditionOptions.ifMetagenerationMatch) === undefined &&
3332 methodType === bucket_js_1.AvailableServiceObjectMethods.setMetadata &&
3333 this.storage.retryOptions.idempotencyStrategy ===
3334 storage_js_1.IdempotencyStrategy.RetryConditional) ||
3335 this.storage.retryOptions.idempotencyStrategy ===
3336 storage_js_1.IdempotencyStrategy.RetryNever) {
3337 this.storage.retryOptions.autoRetry = false;
3338 }
3339 }
3340 async getBufferFromReadable(readable) {
3341 const buf = [];
3342 for await (const chunk of readable) {
3343 buf.push(chunk);
3344 }
3345 return Buffer.concat(buf);
3346 }
3347}
3348exports.File = File;
3349_File_instances = new WeakSet(), _File_validateIntegrity =
3350/**
3351 *
3352 * @param hashCalculatingStream
3353 * @param verify
3354 * @returns {boolean} Returns `true` if valid, throws with error otherwise
3355 */
3356async function _File_validateIntegrity(hashCalculatingStream, verify = {}) {
3357 const metadata = this.metadata;
3358 // If we're doing validation, assume the worst
3359 let dataMismatch = !!(verify.crc32c || verify.md5);
3360 if (verify.crc32c && metadata.crc32c) {
3361 dataMismatch = !hashCalculatingStream.test('crc32c', metadata.crc32c);
3362 }
3363 if (verify.md5 && metadata.md5Hash) {
3364 dataMismatch = !hashCalculatingStream.test('md5', metadata.md5Hash);
3365 }
3366 if (dataMismatch) {
3367 const errors = [];
3368 let code = '';
3369 let message = '';
3370 try {
3371 await this.delete();
3372 if (verify.md5 && !metadata.md5Hash) {
3373 code = 'MD5_NOT_AVAILABLE';
3374 message = FileExceptionMessages.MD5_NOT_AVAILABLE;
3375 }
3376 else {
3377 code = 'FILE_NO_UPLOAD';
3378 message = FileExceptionMessages.UPLOAD_MISMATCH;
3379 }
3380 }
3381 catch (e) {
3382 const error = e;
3383 code = 'FILE_NO_UPLOAD_DELETE';
3384 message = `${FileExceptionMessages.UPLOAD_MISMATCH_DELETE_FAIL}${error.message}`;
3385 errors.push(error);
3386 }
3387 const error = new RequestError(message);
3388 error.code = code;
3389 error.errors = errors;
3390 throw error;
3391 }
3392 return true;
3393};
3394/*! Developer Documentation
3395 *
3396 * All async methods (except for streams) will return a Promise in the event
3397 * that a callback is omitted.
3398 */
3399(0, promisify_1.promisifyAll)(File, {
3400 exclude: [
3401 'cloudStorageURI',
3402 'publicUrl',
3403 'request',
3404 'save',
3405 'setEncryptionKey',
3406 'shouldRetryBasedOnPreconditionAndIdempotencyStrat',
3407 'getBufferFromReadable',
3408 'restore',
3409 ],
3410});