-
Notifications
You must be signed in to change notification settings - Fork 689
fix!: Update storage to use newer versions of gaxios, teeny-request, and google-auth-library #8827
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: main
Are you sure you want to change the base?
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -12,7 +12,6 @@ | |
| // See the License for the specific language governing permissions and | ||
| // limitations under the License. | ||
|
|
||
| import AbortController from 'abort-controller'; | ||
| import {createHash} from 'crypto'; | ||
| import { | ||
| GaxiosOptions, | ||
|
|
@@ -46,6 +45,13 @@ const NOT_FOUND_STATUS_CODE = 404; | |
| const RESUMABLE_INCOMPLETE_STATUS_CODE = 308; | ||
| const packageJson = getPackageJSON(); | ||
|
|
||
| function getResponseHeader(headers: any, name: string): string | undefined { | ||
| if (headers && typeof headers.get === 'function') { | ||
| return headers.get(name) || undefined; | ||
| } | ||
| return headers?.[name] || headers?.[name.toLowerCase()] || undefined; | ||
| } | ||
|
|
||
| export const PROTOCOL_REGEX = /^(\w*):\/\//; | ||
|
|
||
| export interface ErrorWithCode extends Error { | ||
|
|
@@ -615,7 +621,7 @@ export class Upload extends Writable { | |
| * | ||
| * @param headers The headers object to modify. | ||
| */ | ||
| #applyChecksumHeaders(headers: GaxiosOptions['headers']) { | ||
| #applyChecksumHeaders(headers: Record<string, string>) { | ||
| const checksums: string[] = []; | ||
|
|
||
| if (this.#hashValidator?.crc32cEnabled) { | ||
|
|
@@ -631,7 +637,7 @@ export class Upload extends Writable { | |
| } | ||
|
|
||
| if (checksums.length > 0) { | ||
| headers!['X-Goog-Hash'] = checksums.join(','); | ||
| headers['X-Goog-Hash'] = checksums.join(','); | ||
| } | ||
| } | ||
|
|
||
|
|
@@ -792,7 +798,7 @@ export class Upload extends Writable { | |
|
|
||
| protected async createURIAsync(): Promise<string> { | ||
| const metadata = {...this.metadata}; | ||
| const headers: gaxios.Headers = {}; | ||
| const headers: Record<string, string> = {}; | ||
|
|
||
| // Delete content length and content type from metadata if they exist. | ||
| // These are headers and should not be sent as part of the metadata. | ||
|
|
@@ -802,7 +808,7 @@ export class Upload extends Writable { | |
| } | ||
|
|
||
| if (metadata.contentType) { | ||
| headers!['X-Upload-Content-Type'] = metadata.contentType; | ||
| headers['X-Upload-Content-Type'] = metadata.contentType; | ||
| delete metadata.contentType; | ||
| } | ||
|
|
||
|
|
@@ -833,13 +839,16 @@ export class Upload extends Writable { | |
| }, | ||
| }; | ||
|
|
||
| if (metadata.contentLength) { | ||
| reqOpts.headers!['X-Upload-Content-Length'] = | ||
| metadata.contentLength.toString(); | ||
| } | ||
| const reqHeaders = reqOpts.headers; | ||
| if (reqHeaders && !(reqHeaders instanceof Headers) && !Array.isArray(reqHeaders)) { | ||
| if (metadata.contentLength) { | ||
| reqHeaders['X-Upload-Content-Length'] = | ||
| metadata.contentLength.toString(); | ||
| } | ||
|
|
||
| if (metadata.contentType) { | ||
| reqOpts.headers!['X-Upload-Content-Type'] = metadata.contentType; | ||
| if (metadata.contentType) { | ||
| reqHeaders['X-Upload-Content-Type'] = metadata.contentType; | ||
| } | ||
| } | ||
|
Comment on lines
+842
to
852
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Use the new if (reqOpts.headers) {
if (metadata.contentLength) {
setHeader(reqOpts.headers, 'X-Upload-Content-Length', metadata.contentLength.toString());
}
if (metadata.contentType) {
setHeader(reqOpts.headers, 'X-Upload-Content-Type', metadata.contentType);
}
} |
||
|
|
||
| if (typeof this.generation !== 'undefined') { | ||
|
|
@@ -855,15 +864,18 @@ export class Upload extends Writable { | |
| } | ||
|
|
||
| if (this.origin) { | ||
| reqOpts.headers!.Origin = this.origin; | ||
| const reqHeaders = reqOpts.headers; | ||
| if (reqHeaders && !(reqHeaders instanceof Headers) && !Array.isArray(reqHeaders)) { | ||
| reqHeaders.Origin = this.origin; | ||
| } | ||
| } | ||
|
Comment on lines
+867
to
871
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. |
||
| const uri = await AsyncRetry( | ||
| async (bail: (err: Error) => void) => { | ||
| try { | ||
| const res = await this.makeRequest(reqOpts); | ||
| // We have successfully got a URI we can now create a new invocation id | ||
| this.currentInvocationId.uri = crypto.randomUUID(); | ||
| return res.headers.location; | ||
| return getResponseHeader(res.headers, 'location') || ''; | ||
| } catch (err) { | ||
| const e = err as GaxiosError; | ||
| const apiError = { | ||
|
|
@@ -882,7 +894,8 @@ export class Upload extends Writable { | |
| ) { | ||
| throw e; | ||
| } else { | ||
| return bail(e); | ||
| bail(e); | ||
| return ''; | ||
| } | ||
| } | ||
| }, | ||
|
|
@@ -1005,7 +1018,7 @@ export class Upload extends Writable { | |
| googAPIClient += ` gccl-gcs-cmd/${this.#gcclGcsCmd}`; | ||
| } | ||
|
|
||
| const headers: GaxiosOptions['headers'] = { | ||
| const headers: Record<string, string> = { | ||
| 'User-Agent': getUserAgentString(), | ||
| 'x-goog-api-client': googAPIClient, | ||
| }; | ||
|
|
@@ -1050,7 +1063,7 @@ export class Upload extends Writable { | |
|
|
||
| // `Content-Length` for multiple chunk uploads is the size of the chunk, | ||
| // not the overall object | ||
| headers['Content-Length'] = bytesToUpload; | ||
| headers['Content-Length'] = bytesToUpload.toString(); | ||
| headers['Content-Range'] = | ||
| `bytes ${this.offset}-${endingByte}/${totalObjectSize}`; | ||
|
|
||
|
|
@@ -1111,7 +1124,7 @@ export class Upload extends Writable { | |
| const shouldContinueWithNextMultiChunkRequest = | ||
| this.chunkSize && | ||
| resp.status === RESUMABLE_INCOMPLETE_STATUS_CODE && | ||
| resp.headers.range && | ||
| getResponseHeader(resp.headers, 'range') && | ||
| moreDataToUpload; | ||
|
|
||
| /** | ||
|
|
@@ -1127,7 +1140,7 @@ export class Upload extends Writable { | |
| // Use the upper value in this header to determine where to start the next chunk. | ||
| // We should not assume that the server received all bytes sent in the request. | ||
| // https://cloud.google.com/storage/docs/performing-resumable-uploads#chunked-upload | ||
| const range: string = resp.headers.range; | ||
| const range: string = getResponseHeader(resp.headers, 'range')!; | ||
| this.offset = Number(range.split('-')[1]) + 1; | ||
|
|
||
| // We should not assume that the server received all bytes sent in the request. | ||
|
|
@@ -1223,7 +1236,7 @@ export class Upload extends Writable { | |
| method: 'PUT', | ||
| url: this.uri, | ||
| headers: { | ||
| 'Content-Length': 0, | ||
| 'Content-Length': '0', | ||
| 'Content-Range': 'bytes */*', | ||
| 'User-Agent': getUserAgentString(), | ||
| 'x-goog-api-client': googAPIClient, | ||
|
|
@@ -1264,8 +1277,9 @@ export class Upload extends Writable { | |
| const resp = await this.checkUploadStatus({retry: false}); | ||
|
|
||
| if (resp.status === RESUMABLE_INCOMPLETE_STATUS_CODE) { | ||
| if (typeof resp.headers.range === 'string') { | ||
| this.offset = Number(resp.headers.range.split('-')[1]) + 1; | ||
| const range = getResponseHeader(resp.headers, 'range'); | ||
| if (typeof range === 'string') { | ||
| this.offset = Number(range.split('-')[1]) + 1; | ||
| return; | ||
| } | ||
| } | ||
|
|
@@ -1288,10 +1302,12 @@ export class Upload extends Writable { | |
| private async makeRequest(reqOpts: GaxiosOptions): GaxiosPromise { | ||
| if (this.encryption) { | ||
| reqOpts.headers = reqOpts.headers || {}; | ||
| reqOpts.headers['x-goog-encryption-algorithm'] = 'AES256'; | ||
| reqOpts.headers['x-goog-encryption-key'] = this.encryption.key.toString(); | ||
| reqOpts.headers['x-goog-encryption-key-sha256'] = | ||
| this.encryption.hash.toString(); | ||
| if (!(reqOpts.headers instanceof Headers) && !Array.isArray(reqOpts.headers)) { | ||
| reqOpts.headers['x-goog-encryption-algorithm'] = 'AES256'; | ||
| reqOpts.headers['x-goog-encryption-key'] = this.encryption.key.toString(); | ||
| reqOpts.headers['x-goog-encryption-key-sha256'] = | ||
| this.encryption.hash.toString(); | ||
| } | ||
| } | ||
|
Comment on lines
1304
to
1311
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Use the reqOpts.headers = reqOpts.headers || {};
setHeader(reqOpts.headers, 'x-goog-encryption-algorithm', 'AES256');
setHeader(reqOpts.headers, 'x-goog-encryption-key', this.encryption.key.toString());
setHeader(reqOpts.headers, 'x-goog-encryption-key-sha256', this.encryption.hash.toString()); |
||
|
|
||
| if (this.userProject) { | ||
|
|
||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The current implementation of
getResponseHeaderhas a case-sensitivity bug whenheadersis a plain object. For example, ifnameis'location'and the object contains{ Location: '...' }, bothheaders?.[name]andheaders?.[name.toLowerCase()]will look up'location'and returnundefined.Additionally, throughout this file, headers are conditionally set only if
reqOpts.headersis not an instance ofHeadersand not an array. IfreqOpts.headersis aHeadersobject or an array, these headers (including critical encryption headers) are silently ignored.Let's define robust
getResponseHeaderandsetHeaderhelper functions at the top of the file to handle all header types (Headersobject, array of entries, or plain object) correctly.