message: Move upload string content type and content length related logic into own function, add case for common incorrect usage of `text/html`
diff: @@ -30,6 +30,42 @@ function getUtf8StringByteLength(input: string): string {
  return Buffer.byteLength(input, 'utf8').toString()
  }
  +/**
  + *
  + * @param postHeaders The fetch request headers object that may have its
  + * `Content-Type` and `Content-Length` properties set.
  + */
  +function determineStringContentHeaders(
  + contents: string,
  + contentType: string | undefined,
  + postHeaders: Record<string, string>) {
  + if (contentType === 'application/json') {
  + // According to https://www.ietf.org/rfc/rfc4627.txt :
  + // "JSON text SHALL be encoded in Unicode. The default encoding is UTF-8."
  + // For more details see discussion at https://stackoverflow.com/q/9254891/794962
  + postHeaders['Content-Length'] = getUtf8StringByteLength(contents)
  + } else if (!contentType) {
  + // If content is a string and contentType is not specified, then specify UTF8
  + // otherwise string parsers (web servers and browsers) have undefined behavior.
  + postHeaders['Content-Type'] = 'text/plain; charset=utf-8'
  + postHeaders['Content-Length'] = getUtf8StringByteLength(contents)
  + } else if (contentType.toLowerCase().includes('charset=utf-8')) {
  + // If contentType is some other format, but specified utf-8 charset,
  + // then the content-length is known.
  + postHeaders['Content-Length'] = getUtf8StringByteLength(contents)
  + } else if (contentType.toLowerCase() === 'text/html') {
  + // Another common contentType that is commonly not specified with the
  + // recommended charset encoding, resulting in undefined behavior in different
  + // browsers during unicode and localization related scenarios.
  + // See https://www.w3.org/International/articles/http-charset/index
  + postHeaders['Content-Type'] = 'text/html; charset=utf-8'
  + postHeaders['Content-Length'] = getUtf8StringByteLength(contents)
  + } else {
  + // TODO: should probably console.warn here about likely incorrect usage
  + // of string content type, and that content-length cannot be determined.
  + }
  +}
  +
  /**
  *
  * @param filename
  @@ -46,27 +82,13 @@ export async function uploadToGaiaHub(
  contentType?: string,
  ): Promise<string> {
  Logger.debug(`uploadToGaiaHub: uploading ${filename} to ${hubConfig.server}`)
  - const postHeaders: HeadersInit = {
  + const postHeaders: Record<string, string> = {
  'Content-Type': contentType || 'application/octet-stream',
  Authorization: `bearer ${hubConfig.token}`
  }
  if (typeof contents === 'string') {
  - if (contentType === 'application/json') {
  - // According to https://www.ietf.org/rfc/rfc4627.txt :
  - // "JSON text SHALL be encoded in Unicode. The default encoding is UTF-8."
  - // For more details see discussion at https://stackoverflow.com/q/9254891/794962
  - postHeaders['Content-Length'] = getUtf8StringByteLength(contents)
  - } else if (!contentType) {
  - // If content is a string and contentType is not specified, then specify UTF8
  - // otherwise string parsers (web servers and browsers) have undefined behavior.
  - postHeaders['Content-Type'] = 'text/plain; charset=UTF-8'
  - postHeaders['Content-Length'] = getUtf8StringByteLength(contents)
  - } else if (contentType.toLowerCase().includes('charset=utf-8')) {
  - // If contentType is some other text format, but specified utf-8 charset,
  - // then the content-length is known.
  - postHeaders['Content-Length'] = getUtf8StringByteLength(contents)
  - }
  + determineStringContentHeaders(contents, contentType, postHeaders)
  } else if (ArrayBuffer.isView(contents)) {
  postHeaders['Content-Length'] = contents.byteLength.toString()
  } else if (typeof Blob !== 'undefined' && contents instanceof Blob) {
  