function chunkedUploadContinue($uploadId, $byteOffset, $data)
    {
        Checker::argStringNonEmpty("uploadId", $uploadId);
        Checker::argNat("byteOffset", $byteOffset);
        Checker::argString("data", $data);

        $response = $this->_chunkedUpload(
            array("upload_id" => $uploadId, "offset" => $byteOffset), $data);

        if ($response->statusCode === 404) {
            // The server doesn't know our upload ID.  Maybe it expired?
            return false;
        }

        $correction = self::_chunkedUploadCheckForOffsetCorrection($response);
        if ($correction !== null) {
            list($correctedUploadId, $correctedByteOffset) = $correction;
            if ($correctedUploadId !== $uploadId) throw new Exception_BadResponse(
                "Corrective 400 upload_id mismatch: us=".
                Util::q($uploadId)." server=".Util::q($correctedUploadId));
            if ($correctedByteOffset === $byteOffset) throw new Exception_BadResponse(
                "Corrective 400 offset is the same as ours: $byteOffset");
            return $correctedByteOffset;
        }

        if ($response->statusCode !== 200) throw RequestUtil::unexpectedStatus($response);
        list($retUploadId, $retByteOffset) = self::_chunkedUploadParse200Response($response->body);

        $nextByteOffset = $byteOffset + strlen($data);
        if ($uploadId !== $retUploadId) throw new Exception_BadResponse(
                "upload_id mismatch: us=".Util::q($uploadId) .", server=".Util::q($uploadId));
        if ($nextByteOffset !== $retByteOffset) throw new Exception_BadResponse(
                "next-offset mismatch: us=$nextByteOffset, server=$retByteOffset");

        return true;
    }