Skip to content

[Bug] SharePointService.CreateFile() binary upload still corrupted after 1.1.3 fix #340

@emibanana

Description

@emibanana

Describe the bug

SharePointService.CreateFile() still saves corrupted content (raw base64 ASCII text) to SharePoint when uploading binary files (images, PDFs, etc.) after upgrading to @microsoft/power-apps 1.1.3, which was supposed to resolve this in issue #306 / #230.

Steps to Reproduce

Install @microsoft/power-apps@1.1.3

Call SharePointService.CreateFile(dataset, folderPath, 'photo.jpg', base64String) where base64String is a valid base64-encoded JPEG

Open the uploaded file in SharePoint — it is not a valid image; it contains raw base64 ASCII text

Additional context

The 1.1.3 fix was incomplete. It corrected only one of two problems:

Problem 1 — Fixed in 1.1.3 ✅
In _buildOperationBodyParam, the body value was previously always passed through JSON.stringify(), which double-encoded a base64 string into ""iVBOR..."". The 1.1.3 fix adds a format === 'binary' guard that skips JSON.stringify and returns the raw base64 string as-is.

Problem 2 — NOT fixed in 1.1.3 ❌
_executeRequest decides how to wrap the body into a Blob based on whether a Content-Type: application/octet-stream header is present:

// runtimeDataClient.js — _executeRequest
const requestBody = config.body
? config.headers?.['Content-Type'] === 'application/octet-stream'
? new Blob([config.body], { type: 'application/octet-stream' })
: new Blob([config.body], { type: 'application/json' })
: '';

Because executeAsync never sets Content-Type: application/octet-stream for binary connector operations, _executeRequest always falls into the application/json branch. The raw base64 string is wrapped in a JSON blob and the Power Platform gateway forwards the ASCII content verbatim to SharePoint instead of binary bytes.

Additionally, retrieveDataAsync still JSON-serialises non-string bodies:

// runtimeDataClient.js — retrieveDataAsync (line 206)
body: body ? (typeof body === 'string' ? body : JSON.stringify(body)) : undefined,

This means if a binary body is passed as a Uint8Array, it would be incorrectly serialised.

Why the old workaround for 1.1.1 broke on 1.1.3
Community patches (including my own) for 1.1.1 detected the double-JSON-stringified form by calling JSON.parse() on the body — if the parsed result was still a string, it decoded it to a Uint8Array. In 1.1.3, the value is no longer double-stringified, so JSON.parse("iVBOR...") throws a SyntaxError, the catch block silently discards it, and the binary passthrough never triggers.

Proposed fix

Two changes are needed in connectorDataOperationExecutor.js, plus one in runtimeDataClient.js:

  1. _buildOperationBodyParam — decode base64 → Uint8Array instead of returning a raw string:

// Before (1.1.3):
if (bodyParam.format === 'binary' && typeof value === 'string') {
return value; // still a string → _executeRequest uses application/json blob
}

// After:
if (bodyParam.format === 'binary' && typeof value === 'string') {
const binary = atob(value);
const bytes = new Uint8Array(binary.length);
for (let i = 0; i < binary.length; i++) bytes[i] = binary.charCodeAt(i);
return bytes; // Uint8Array → can be detected downstream to set octet-stream
}

  1. executeAsync — detect Uint8Array body and inject Content-Type: application/octet-stream:

const bodyParam = await this._buildOperationBody(operation, tableName);
const headers = await this._buildOperationHeader(operation, tableName);

// NEW: signal binary upload to _executeRequest
const resolvedHeaders = bodyParam instanceof Uint8Array
? { ...(headers ?? {}), 'Content-Type': 'application/octet-stream' }
: headers;

const result = await dataClient.retrieveDataAsync(
requestUrl, config.apiId, tableName, httpMethod, resolvedHeaders, bodyParam, { ... }
);

  1. retrieveDataAsync — pass Uint8Array/ArrayBuffer through without serialisation:

// Before:
body: body ? (typeof body === 'string' ? body : JSON.stringify(body)) : undefined,

// After:
body: body
? (body instanceof Uint8Array || body instanceof ArrayBuffer
? body
: typeof body === 'string' ? body : JSON.stringify(body))
: undefined,

With these three changes, the flow becomes:

_buildOperationBodyParam → base64 decoded to Uint8Array
executeAsync → detects Uint8Array, adds Content-Type: application/octet-stream
retrieveDataAsync → passes Uint8Array through as-is
_executeRequest → sees Content-Type: application/octet-stream → creates a binary Blob → SharePoint receives actual binary bytes ✅

Reference:
#306 — original bug report
#230 — upstream tracking issue

Metadata

Metadata

Assignees

No one assigned

    Type

    No type
    No fields configured for issues without a type.

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions