语音通话-1
This commit is contained in:
parent
0c9fb211d1
commit
91493c36d4
2
.env
2
.env
|
|
@ -7,7 +7,7 @@ DEBUG=True
|
|||
BACKEND_URL=http://127.0.0.1:8000
|
||||
|
||||
# ===== 数据库配置 =====
|
||||
DATABASE_URL=mysql+pymysql://fastadmin:root@1.15.149.240:3306/fastadmin?charset=utf8mb4
|
||||
DATABASE_URL=mysql+pymysql://root:rootx77@localhost:3306/fastadmin?charset=utf8mb4
|
||||
|
||||
# ===== 用户信息接口 (PHP后端) =====
|
||||
# PHP 后端地址,用于用户认证
|
||||
|
|
|
|||
53
0.4
Normal file
53
0.4
Normal file
|
|
@ -0,0 +1,53 @@
|
|||
Looking in indexes: https://pypi.tuna.tsinghua.edu.cn/simple
|
||||
Requirement already satisfied: fastapi in d:\environment\python\lib\site-packages (0.133.1)
|
||||
Requirement already satisfied: sqlalchemy in d:\environment\python\lib\site-packages (2.0.47)
|
||||
Requirement already satisfied: pymysql in d:\environment\python\lib\site-packages (1.1.2)
|
||||
Requirement already satisfied: pydantic in d:\environment\python\lib\site-packages (2.12.5)
|
||||
Requirement already satisfied: pydantic-settings in d:\environment\python\lib\site-packages (2.13.1)
|
||||
Requirement already satisfied: python-dotenv in d:\environment\python\lib\site-packages (1.2.1)
|
||||
Requirement already satisfied: requests in d:\environment\python\lib\site-packages (2.32.5)
|
||||
Requirement already satisfied: oss2 in d:\environment\python\lib\site-packages (2.19.1)
|
||||
Requirement already satisfied: dashscope in d:\environment\python\lib\site-packages (1.25.12)
|
||||
Requirement already satisfied: pyyaml in d:\environment\python\lib\site-packages (6.0.3)
|
||||
Requirement already satisfied: imageio-ffmpeg in d:\environment\python\lib\site-packages (0.6.0)
|
||||
Collecting python-multipart
|
||||
Downloading https://pypi.tuna.tsinghua.edu.cn/packages/1b/d0/397f9626e711ff749a95d96b7af99b9c566a9bb5129b8e4c10fc4d100304/python_multipart-0.0.22-py3-none-any.whl (24 kB)
|
||||
Requirement already satisfied: uvicorn[standard] in d:\environment\python\lib\site-packages (0.41.0)
|
||||
Requirement already satisfied: starlette>=0.40.0 in d:\environment\python\lib\site-packages (from fastapi) (0.52.1)
|
||||
Requirement already satisfied: typing-extensions>=4.8.0 in d:\environment\python\lib\site-packages (from fastapi) (4.15.0)
|
||||
Requirement already satisfied: typing-inspection>=0.4.2 in d:\environment\python\lib\site-packages (from fastapi) (0.4.2)
|
||||
Requirement already satisfied: annotated-doc>=0.0.2 in d:\environment\python\lib\site-packages (from fastapi) (0.0.4)
|
||||
Requirement already satisfied: click>=7.0 in d:\environment\python\lib\site-packages (from uvicorn[standard]) (8.3.1)
|
||||
Requirement already satisfied: h11>=0.8 in d:\environment\python\lib\site-packages (from uvicorn[standard]) (0.16.0)
|
||||
Requirement already satisfied: colorama>=0.4 in d:\environment\python\lib\site-packages (from uvicorn[standard]) (0.4.6)
|
||||
Requirement already satisfied: httptools>=0.6.3 in d:\environment\python\lib\site-packages (from uvicorn[standard]) (0.7.1)
|
||||
Requirement already satisfied: watchfiles>=0.20 in d:\environment\python\lib\site-packages (from uvicorn[standard]) (1.1.1)
|
||||
Requirement already satisfied: websockets>=10.4 in d:\environment\python\lib\site-packages (from uvicorn[standard]) (16.0)
|
||||
Requirement already satisfied: greenlet>=1 in d:\environment\python\lib\site-packages (from sqlalchemy) (3.3.2)
|
||||
Requirement already satisfied: annotated-types>=0.6.0 in d:\environment\python\lib\site-packages (from pydantic) (0.7.0)
|
||||
Requirement already satisfied: pydantic-core==2.41.5 in d:\environment\python\lib\site-packages (from pydantic) (2.41.5)
|
||||
Requirement already satisfied: charset_normalizer<4,>=2 in d:\environment\python\lib\site-packages (from requests) (3.4.4)
|
||||
Requirement already satisfied: idna<4,>=2.5 in d:\environment\python\lib\site-packages (from requests) (3.11)
|
||||
Requirement already satisfied: urllib3<3,>=1.21.1 in d:\environment\python\lib\site-packages (from requests) (2.6.3)
|
||||
Requirement already satisfied: certifi>=2017.4.17 in d:\environment\python\lib\site-packages (from requests) (2026.2.25)
|
||||
Requirement already satisfied: crcmod>=1.7 in d:\environment\python\lib\site-packages (from oss2) (1.7)
|
||||
Requirement already satisfied: pycryptodome>=3.4.7 in d:\environment\python\lib\site-packages (from oss2) (3.23.0)
|
||||
Requirement already satisfied: aliyun-python-sdk-kms>=2.4.1 in d:\environment\python\lib\site-packages (from oss2) (2.16.5)
|
||||
Requirement already satisfied: aliyun-python-sdk-core>=2.13.12 in d:\environment\python\lib\site-packages (from oss2) (2.16.0)
|
||||
Requirement already satisfied: six in d:\environment\python\lib\site-packages (from oss2) (1.17.0)
|
||||
Requirement already satisfied: aiohttp in d:\environment\python\lib\site-packages (from dashscope) (3.13.3)
|
||||
Requirement already satisfied: websocket-client in d:\environment\python\lib\site-packages (from dashscope) (1.9.0)
|
||||
Requirement already satisfied: cryptography in d:\environment\python\lib\site-packages (from dashscope) (46.0.5)
|
||||
Requirement already satisfied: jmespath<1.0.0,>=0.9.3 in d:\environment\python\lib\site-packages (from aliyun-python-sdk-core>=2.13.12->oss2) (0.10.0)
|
||||
Requirement already satisfied: cffi>=2.0.0 in d:\environment\python\lib\site-packages (from cryptography->dashscope) (2.0.0)
|
||||
Requirement already satisfied: pycparser in d:\environment\python\lib\site-packages (from cffi>=2.0.0->cryptography->dashscope) (3.0)
|
||||
Requirement already satisfied: anyio<5,>=3.6.2 in d:\environment\python\lib\site-packages (from starlette>=0.40.0->fastapi) (4.12.1)
|
||||
Requirement already satisfied: aiohappyeyeballs>=2.5.0 in d:\environment\python\lib\site-packages (from aiohttp->dashscope) (2.6.1)
|
||||
Requirement already satisfied: aiosignal>=1.4.0 in d:\environment\python\lib\site-packages (from aiohttp->dashscope) (1.4.0)
|
||||
Requirement already satisfied: attrs>=17.3.0 in d:\environment\python\lib\site-packages (from aiohttp->dashscope) (25.4.0)
|
||||
Requirement already satisfied: frozenlist>=1.1.1 in d:\environment\python\lib\site-packages (from aiohttp->dashscope) (1.8.0)
|
||||
Requirement already satisfied: multidict<7.0,>=4.5 in d:\environment\python\lib\site-packages (from aiohttp->dashscope) (6.7.1)
|
||||
Requirement already satisfied: propcache>=0.2.0 in d:\environment\python\lib\site-packages (from aiohttp->dashscope) (0.4.1)
|
||||
Requirement already satisfied: yarl<2.0,>=1.17.0 in d:\environment\python\lib\site-packages (from aiohttp->dashscope) (1.22.0)
|
||||
Installing collected packages: python-multipart
|
||||
Successfully installed python-multipart-0.0.22
|
||||
|
|
@ -1,2 +1,2 @@
|
|||
DATABASE_URL=mysql+pymysql://fastadmin:root@1.15.149.240:3306/fastadmin?charset=utf8mb4
|
||||
USER_INFO_API=http://1.15.149.240:30100/api/user_basic/get_user_basic
|
||||
DATABASE_URL=mysql+pymysql://root:rootx77@localhost:3306/fastadmin?charset=utf8mb4
|
||||
USER_INFO_API=http://127.0.0.1:30100/api/user_basic/get_user_basic
|
||||
|
|
@ -26,7 +26,7 @@
|
|||
"customPlaygroundType" : "device",
|
||||
"localRepoPath" : "C:/Users/Administrator/Desktop/Project/AI_GirlFriend/xuniYou",
|
||||
"packageName" : "uni.app.UNIF098CA6",
|
||||
"playground" : "standard",
|
||||
"playground" : "custom",
|
||||
"type" : "uni-app:app-android"
|
||||
},
|
||||
{
|
||||
|
|
|
|||
|
|
@ -43,7 +43,9 @@
|
|||
"<uses-permission android:name=\"android.permission.READ_PHONE_STATE\"/>",
|
||||
"<uses-permission android:name=\"android.permission.VIBRATE\"/>",
|
||||
"<uses-permission android:name=\"android.permission.WAKE_LOCK\"/>",
|
||||
"<uses-permission android:name=\"android.permission.WRITE_SETTINGS\"/>"
|
||||
"<uses-permission android:name=\"android.permission.WRITE_SETTINGS\"/>",
|
||||
"<uses-permission android:name=\"android.permission.RECORD_AUDIO\"/>",
|
||||
"<uses-permission android:name=\"android.permission.MODIFY_AUDIO_SETTINGS\"/>"
|
||||
],
|
||||
"minSdkVersion" : 24
|
||||
},
|
||||
|
|
|
|||
|
|
@ -7,6 +7,10 @@
|
|||
:src="loverBasicList.image_url ? loverBasicList.image_url : 'https://nvlovers.oss-cn-qingdao.aliyuncs.com/uploads/20251226/39c6f8899c15f60fc59207835f95e07a.png'"
|
||||
mode="aspectFill"></image>
|
||||
</view>
|
||||
<!-- 麦克风权限开关移到右上角 -->
|
||||
<view class="mic-permission-switch" @click="toggleMicPermission">
|
||||
<text class="mic-emoji">{{ micEnabled ? '🎤' : '🔇' }}</text>
|
||||
</view>
|
||||
<view class="header">
|
||||
<view class="header_content">
|
||||
<view class="header_time">{{ isVip ? 'VIP 无限通话' : '通话剩余时间' }}</view>
|
||||
|
|
@ -23,14 +27,19 @@
|
|||
<view class="dynamic-wave">
|
||||
<view class="wave-bar" v-for="n in 15" :key="n" :style="{ 'animation-delay': n * 0.1 + 's' }"></view>
|
||||
</view>
|
||||
<view class="opt fa sb">
|
||||
<view class="opt fa">
|
||||
<view @click="hangUp()" class="opt_item">
|
||||
<image class="opt_image" src="/static/images/phone_a2.png" mode="widthFix"></image>
|
||||
<view class="opt_name">挂断</view>
|
||||
</view>
|
||||
<view class="opt_item" @click="openMicPermission()">
|
||||
<!-- 按住说话按钮 -->
|
||||
<view class="opt_item mic-button"
|
||||
@touchstart="startTalking"
|
||||
@touchend="stopTalking"
|
||||
@touchcancel="stopTalking"
|
||||
:class="{ 'talking': isTalking }">
|
||||
<image class="opt_image" src="/static/images/phone_a1.png" mode="widthFix"></image>
|
||||
<view class="opt_name">麦克风权限</view>
|
||||
<view class="opt_name">{{ isTalking ? '松开结束' : '按住说话' }}</view>
|
||||
</view>
|
||||
</view>
|
||||
<view class="black"></view>
|
||||
|
|
@ -58,7 +67,10 @@
|
|||
import notHave from '@/components/not-have.vue';
|
||||
import { baseURLPy } from '@/utils/request.js'
|
||||
import topSafety from '@/components/top-safety.vue';
|
||||
const recorderManager = uni.getRecorderManager();
|
||||
|
||||
// 在组件外部初始化 recorderManager
|
||||
let recorderManager = null;
|
||||
|
||||
export default {
|
||||
components: {
|
||||
notHave,
|
||||
|
|
@ -81,7 +93,9 @@
|
|||
totalDuration: 300000, // 默认 5 分钟
|
||||
remainingTime: 300000,
|
||||
timer: null,
|
||||
isVip: false
|
||||
isVip: false,
|
||||
isTalking: false, // 是否正在说话
|
||||
micEnabled: true // 麦克风是否开启
|
||||
}
|
||||
},
|
||||
onLoad() {
|
||||
|
|
@ -90,6 +104,12 @@
|
|||
console.log('systemInfo', systemInfo)
|
||||
// console.log('plus', plus)
|
||||
this.isApp = systemInfo.uniPlatform === 'app'
|
||||
|
||||
// 初始化 recorderManager(非 App 端)
|
||||
if (!this.isApp) {
|
||||
recorderManager = uni.getRecorderManager();
|
||||
}
|
||||
|
||||
this.getCallDuration()
|
||||
this.initAudio()
|
||||
},
|
||||
|
|
@ -222,8 +242,9 @@
|
|||
success: () => console.log('WS 连接成功')
|
||||
});
|
||||
this.socketTask.onOpen((res) => {
|
||||
console.log('onOpen:', res)
|
||||
this.startRecording();
|
||||
console.log('WebSocket onOpen:', res)
|
||||
// 不要在这里自动开始录音,等用户按住按钮时再开始
|
||||
// this.startRecording();
|
||||
this.startTimer();
|
||||
});
|
||||
this.socketTask.onMessage((res) => {
|
||||
|
|
@ -234,65 +255,138 @@
|
|||
console.error('WS 错误', err);
|
||||
});
|
||||
this.socketTask.onClose((res) => {
|
||||
console.log('关闭:', res)
|
||||
if (this.isApp) {
|
||||
console.log('关闭1:', recorder)
|
||||
console.log('WebSocket 关闭:', res)
|
||||
if (this.isApp && this.isRecording) {
|
||||
console.log('关闭录音')
|
||||
recorder.stop()
|
||||
}
|
||||
})
|
||||
},
|
||||
// 开始录制
|
||||
async startRecording() {
|
||||
console.log('开始录制', )
|
||||
if (this.isRecording) return;
|
||||
console.log('=== startRecording 被调用 ===')
|
||||
console.log('isRecording:', this.isRecording)
|
||||
console.log('isApp:', this.isApp)
|
||||
console.log('socketTask 状态:', this.socketTask ? this.socketTask.readyState : 'null')
|
||||
|
||||
if (this.isRecording) {
|
||||
console.log('录音已在进行中,跳过')
|
||||
return;
|
||||
}
|
||||
|
||||
// App 端不支持 onFrameRecorded
|
||||
this.isRecording = true;
|
||||
this.status = 'Call Started';
|
||||
|
||||
if (this.isApp) {
|
||||
console.log('this.isApp:', this.isApp)
|
||||
console.log('App 端:启动原生录音')
|
||||
this.startRecord()
|
||||
} else {
|
||||
// 小程序和 H5 端支持 onFrameRecorded
|
||||
if (!recorderManager) {
|
||||
console.error('recorderManager 未初始化')
|
||||
uni.showToast({
|
||||
title: '录音功能初始化失败',
|
||||
icon: 'none'
|
||||
})
|
||||
this.isRecording = false
|
||||
return
|
||||
}
|
||||
|
||||
console.log('小程序/H5 端:设置录音监听器')
|
||||
|
||||
// 监听录音开始
|
||||
recorderManager.onStart(() => {
|
||||
console.log('✅ 录音已开始')
|
||||
})
|
||||
|
||||
// 监听录音错误
|
||||
recorderManager.onError((err) => {
|
||||
console.error('❌ 录音错误:', err)
|
||||
uni.showToast({
|
||||
title: '录音失败: ' + (err.errMsg || '未知错误'),
|
||||
icon: 'none'
|
||||
})
|
||||
this.isRecording = false
|
||||
})
|
||||
|
||||
// 监听录音停止
|
||||
recorderManager.onStop((res) => {
|
||||
console.log('录音已停止:', res)
|
||||
})
|
||||
|
||||
// 监听音频帧
|
||||
recorderManager.onFrameRecorded((res) => {
|
||||
// console.log('onFrameRecorded:', res)
|
||||
const {
|
||||
frameBuffer,
|
||||
isLastFrame
|
||||
} = res;
|
||||
if (this.socketTask && this.socketTask.readyState === 1) {
|
||||
|
||||
console.log('收到音频帧, isTalking:', this.isTalking, 'frameBuffer size:', frameBuffer.byteLength)
|
||||
|
||||
// 只有在说话状态下才发送音频数据
|
||||
if (this.isTalking && this.socketTask && this.socketTask.readyState === 1) {
|
||||
console.log('✅ 发送音频数据到服务器')
|
||||
this.socketTask.send({
|
||||
data: frameBuffer
|
||||
data: frameBuffer,
|
||||
success: () => {
|
||||
console.log('音频数据发送成功')
|
||||
},
|
||||
fail: (err) => {
|
||||
console.error('音频数据发送失败:', err)
|
||||
}
|
||||
});
|
||||
} else {
|
||||
console.log('⏸️ 不发送音频数据 - isTalking:', this.isTalking, 'socketTask.readyState:', this.socketTask ? this.socketTask.readyState : 'null')
|
||||
}
|
||||
});
|
||||
recorderManager.start({
|
||||
duration: this.totalDuration,
|
||||
format: 'pcm', // ⚠️ 必须用 PCM,Paraformer 实时版只吃 PCM
|
||||
sampleRate: 16000, // ⚠️ 必须 16000Hz,这是 ASR 的标准
|
||||
numberOfChannels: 1, // 单声道
|
||||
frameSize: 2, // 单位是 KB。设置小一点(2-4KB)延迟低,设置太大延迟高
|
||||
audioSource: 'voice_communication'
|
||||
});
|
||||
|
||||
console.log('启动 recorderManager')
|
||||
try {
|
||||
recorderManager.start({
|
||||
duration: this.totalDuration,
|
||||
format: 'pcm', // ⚠️ 必须用 PCM,Paraformer 实时版只吃 PCM
|
||||
sampleRate: 16000, // ⚠️ 必须 16000Hz,这是 ASR 的标准
|
||||
numberOfChannels: 1, // 单声道
|
||||
frameSize: 2, // 单位是 KB。设置小一点(2-4KB)延迟低,设置太大延迟高
|
||||
audioSource: 'voice_communication'
|
||||
});
|
||||
console.log('recorderManager.start 已调用')
|
||||
} catch (err) {
|
||||
console.error('启动录音失败:', err)
|
||||
this.isRecording = false
|
||||
uni.showToast({
|
||||
title: '启动录音失败',
|
||||
icon: 'none'
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
},
|
||||
startRecord() {
|
||||
console.log('=== startRecord (App原生) 被调用 ===')
|
||||
const doStart = () => {
|
||||
console.log('开始启动原生录音器')
|
||||
recorder.start({
|
||||
sampleRate: 16000,
|
||||
frameSize: 640,
|
||||
source: 'mic'
|
||||
}, (res) => {
|
||||
// console.log('socket---res',res)
|
||||
if (res.type === 'frame') {
|
||||
const ab = uni.base64ToArrayBuffer(res.data)
|
||||
// console.log('ab',ab)
|
||||
this.socketTask.send({
|
||||
data: ab
|
||||
})
|
||||
|
||||
console.log('收到原生音频帧, isTalking:', this.isTalking, 'buffer size:', ab.byteLength)
|
||||
|
||||
// 只有在说话状态下才发送音频数据
|
||||
if (this.isTalking && this.socketTask && this.socketTask.readyState === 1) {
|
||||
console.log('发送原生音频数据到服务器')
|
||||
this.socketTask.send({
|
||||
data: ab
|
||||
})
|
||||
} else {
|
||||
console.log('不发送原生音频数据 - isTalking:', this.isTalking)
|
||||
}
|
||||
}
|
||||
})
|
||||
console.log('原生录音器已启动')
|
||||
}
|
||||
|
||||
if (uni.getSystemInfoSync().platform !== 'android') {
|
||||
|
|
@ -302,27 +396,89 @@
|
|||
|
||||
if (typeof plus === 'undefined') {
|
||||
// 还没到 plusready
|
||||
console.log('等待 plusready')
|
||||
document.addEventListener('plusready', () => {
|
||||
plus.android.requestPermissions(['android.permission.RECORD_AUDIO'], (e) => {
|
||||
if (e.granted && e.granted.length) doStart()
|
||||
else uni.showModal({
|
||||
title: '权限不足',
|
||||
content: '请允许麦克风权限'
|
||||
})
|
||||
if (e.granted && e.granted.length) {
|
||||
console.log('录音权限已授予')
|
||||
doStart()
|
||||
} else {
|
||||
console.error('录音权限被拒绝')
|
||||
uni.showModal({
|
||||
title: '权限不足',
|
||||
content: '请允许麦克风权限'
|
||||
})
|
||||
}
|
||||
})
|
||||
})
|
||||
return
|
||||
}
|
||||
|
||||
console.log('请求录音权限')
|
||||
plus.android.requestPermissions(['android.permission.RECORD_AUDIO'], (e) => {
|
||||
console.log('e.granted', e.granted)
|
||||
if (e.granted && e.granted.length) doStart()
|
||||
else uni.showModal({
|
||||
title: '权限不足',
|
||||
content: '请允许麦克风权限'
|
||||
})
|
||||
console.log('权限请求结果:', e)
|
||||
if (e.granted && e.granted.length) {
|
||||
console.log('录音权限已授予')
|
||||
doStart()
|
||||
} else {
|
||||
console.error('录音权限被拒绝')
|
||||
uni.showModal({
|
||||
title: '权限不足',
|
||||
content: '请允许麦克风权限'
|
||||
})
|
||||
}
|
||||
})
|
||||
},
|
||||
// 切换麦克风权限开关
|
||||
toggleMicPermission() {
|
||||
this.micEnabled = !this.micEnabled
|
||||
if (this.micEnabled) {
|
||||
uni.showToast({
|
||||
title: '麦克风已开启',
|
||||
icon: 'none'
|
||||
})
|
||||
} else {
|
||||
uni.showToast({
|
||||
title: '麦克风已关闭',
|
||||
icon: 'none'
|
||||
})
|
||||
// 如果正在说话,停止录音
|
||||
if (this.isTalking) {
|
||||
this.stopTalking()
|
||||
}
|
||||
}
|
||||
},
|
||||
// 开始说话(按住)
|
||||
startTalking() {
|
||||
console.log('startTalking 被调用, micEnabled:', this.micEnabled, 'isRecording:', this.isRecording)
|
||||
|
||||
if (!this.micEnabled) {
|
||||
uni.showToast({
|
||||
title: '请先开启麦克风权限',
|
||||
icon: 'none'
|
||||
})
|
||||
return
|
||||
}
|
||||
|
||||
this.isTalking = true
|
||||
console.log('开始说话, isTalking 设置为:', this.isTalking)
|
||||
|
||||
// 如果录音还没开始,先启动录音
|
||||
if (!this.isRecording) {
|
||||
console.log('录音未启动,开始启动录音')
|
||||
this.startRecording()
|
||||
} else {
|
||||
console.log('录音已在运行')
|
||||
}
|
||||
},
|
||||
// 停止说话(松开)
|
||||
stopTalking() {
|
||||
this.isTalking = false
|
||||
console.log('停止说话, isTalking 设置为:', this.isTalking)
|
||||
|
||||
// 注意:不要停止录音,只是停止发送数据
|
||||
// 录音会持续进行,但数据不会被发送到服务器
|
||||
},
|
||||
openMicPermission() {
|
||||
plus.android.requestPermissions(['android.permission.RECORD_AUDIO'], (e) => {
|
||||
console.log('e.granted', e)
|
||||
|
|
@ -341,7 +497,9 @@
|
|||
},
|
||||
stopCall() {
|
||||
this.isRecording = false;
|
||||
recorderManager.stop();
|
||||
if (recorderManager) {
|
||||
recorderManager.stop();
|
||||
}
|
||||
if (this.socketTask) {
|
||||
this.socketTask.close();
|
||||
}
|
||||
|
|
@ -908,13 +1066,36 @@
|
|||
}
|
||||
}
|
||||
|
||||
/* 麦克风权限开关样式 */
|
||||
.mic-permission-switch {
|
||||
position: absolute;
|
||||
right: 28rpx;
|
||||
top: 100rpx;
|
||||
z-index: 10;
|
||||
width: 80rpx;
|
||||
height: 80rpx;
|
||||
background: rgba(0, 0, 0, 0.3);
|
||||
border-radius: 50%;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
backdrop-filter: blur(10rpx);
|
||||
}
|
||||
|
||||
.mic-emoji {
|
||||
font-size: 48rpx;
|
||||
}
|
||||
|
||||
.opt {
|
||||
position: absolute;
|
||||
left: 0;
|
||||
right: 0;
|
||||
bottom: 15%;
|
||||
margin: 0 140rpx;
|
||||
margin: 0 auto;
|
||||
z-index: 5;
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
gap: 100rpx;
|
||||
}
|
||||
|
||||
.opt_item {
|
||||
|
|
@ -937,6 +1118,19 @@
|
|||
text-align: center;
|
||||
}
|
||||
|
||||
/* 按住说话按钮样式 */
|
||||
.mic-button {
|
||||
transition: transform 0.2s;
|
||||
}
|
||||
|
||||
.mic-button.talking {
|
||||
transform: scale(1.1);
|
||||
}
|
||||
|
||||
.mic-button.talking .opt_image {
|
||||
box-shadow: 0 0 20rpx rgba(159, 71, 255, 0.8);
|
||||
}
|
||||
|
||||
.black {
|
||||
position: absolute;
|
||||
left: 0;
|
||||
|
|
|
|||
|
|
@ -1,7 +1,7 @@
|
|||
// Windows 本地开发 - 混合架构
|
||||
export const baseURL = 'http://192.168.1.164:30100' // PHP 处理用户管理和界面
|
||||
export const baseURL = 'http://192.168.1.141:30100' // PHP 处理用户管理和界面
|
||||
// export const baseURL = 'http://1.15.149.240:30100' // PHP 处理用户管理和界面
|
||||
export const baseURLPy = 'http://192.168.1.164:30101' // FastAPI 处理 AI 功能
|
||||
export const baseURLPy = 'http://192.168.1.141:30101' // FastAPI 处理 AI 功能
|
||||
// export const baseURLPy = 'http://1.15.149.240:30101' // FastAPI 处理 AI 功能
|
||||
|
||||
// 远程服务器 - 需要时取消注释
|
||||
|
|
|
|||
161
xuniYou/按住说话调试指南.md
Normal file
161
xuniYou/按住说话调试指南.md
Normal file
|
|
@ -0,0 +1,161 @@
|
|||
# 按住说话功能调试指南
|
||||
|
||||
## 问题现象
|
||||
按住说话按钮后,日志显示"开始说话"和"停止说话",但没有音频数据发送。
|
||||
|
||||
## 已添加的调试日志
|
||||
|
||||
### 1. 按住按钮时
|
||||
```
|
||||
startTalking 被调用, micEnabled: true, isRecording: false
|
||||
开始说话, isTalking 设置为: true
|
||||
录音未启动,开始启动录音
|
||||
```
|
||||
|
||||
### 2. 录音启动时
|
||||
```
|
||||
=== startRecording 被调用 ===
|
||||
isRecording: false
|
||||
isApp: true/false
|
||||
socketTask 状态: 1 (1表示已连接)
|
||||
```
|
||||
|
||||
### 3. App 端原生录音
|
||||
```
|
||||
=== startRecord (App原生) 被调用 ===
|
||||
请求录音权限
|
||||
权限请求结果: {...}
|
||||
录音权限已授予
|
||||
开始启动原生录音器
|
||||
原生录音器已启动
|
||||
收到原生音频帧, isTalking: true, buffer size: xxx
|
||||
✅ 发送原生音频数据到服务器
|
||||
```
|
||||
|
||||
### 4. 小程序/H5 端录音
|
||||
```
|
||||
小程序/H5 端:设置录音监听器
|
||||
启动 recorderManager
|
||||
recorderManager.start 已调用
|
||||
✅ 录音已开始
|
||||
收到音频帧, isTalking: true, frameBuffer size: xxx
|
||||
✅ 发送音频数据到服务器
|
||||
音频数据发送成功
|
||||
```
|
||||
|
||||
## 检查清单
|
||||
|
||||
### 1. WebSocket 连接状态
|
||||
查看日志中的 `socketTask 状态`:
|
||||
- `0` = CONNECTING (连接中)
|
||||
- `1` = OPEN (已连接) ✅
|
||||
- `2` = CLOSING (关闭中)
|
||||
- `3` = CLOSED (已关闭)
|
||||
|
||||
如果不是 `1`,说明 WebSocket 未连接成功。
|
||||
|
||||
### 2. 录音权限
|
||||
App 端查看:
|
||||
```
|
||||
权限请求结果: { granted: [...], deniedAlways: [], deniedPresent: [] }
|
||||
```
|
||||
- `granted` 数组应该包含 `android.permission.RECORD_AUDIO`
|
||||
- 如果在 `deniedAlways` 或 `deniedPresent` 中,需要手动授权
|
||||
|
||||
### 3. 录音是否真正启动
|
||||
查看是否有:
|
||||
- App 端:`原生录音器已启动`
|
||||
- 小程序端:`✅ 录音已开始`
|
||||
|
||||
如果没有,可能是:
|
||||
- 权限被拒绝
|
||||
- recorderManager 初始化失败
|
||||
- 录音参数不支持
|
||||
|
||||
### 4. 音频帧是否产生
|
||||
查看是否有:
|
||||
- `收到音频帧` 或 `收到原生音频帧`
|
||||
|
||||
如果没有,说明录音器没有产生音频数据。
|
||||
|
||||
### 5. isTalking 状态
|
||||
查看日志中的 `isTalking` 值:
|
||||
- 按住时应该是 `true`
|
||||
- 松开时应该是 `false`
|
||||
|
||||
如果一直是 `false`,说明状态没有正确更新。
|
||||
|
||||
## 常见问题及解决方案
|
||||
|
||||
### 问题1:录音权限被拒绝
|
||||
**现象**:日志显示 `录音权限被拒绝`
|
||||
|
||||
**解决**:
|
||||
1. 进入手机设置 → 应用管理 → 找到你的应用
|
||||
2. 权限管理 → 麦克风 → 允许
|
||||
|
||||
### 问题2:recorderManager 未初始化
|
||||
**现象**:日志显示 `recorderManager 未初始化`
|
||||
|
||||
**解决**:
|
||||
1. 确认 manifest.json 中 `Record` 模块已启用
|
||||
2. 重新编译项目
|
||||
3. 检查是否在 App 端(App 端不使用 recorderManager)
|
||||
|
||||
### 问题3:WebSocket 未连接
|
||||
**现象**:`socketTask 状态: null` 或不是 `1`
|
||||
|
||||
**解决**:
|
||||
1. 检查后端服务是否启动
|
||||
2. 检查 WebSocket URL 是否正确
|
||||
3. 检查 token 是否有效
|
||||
|
||||
### 问题4:音频帧不发送
|
||||
**现象**:有 `收到音频帧` 但没有 `发送音频数据`
|
||||
|
||||
**解决**:
|
||||
1. 检查 `isTalking` 是否为 `true`
|
||||
2. 检查 `socketTask.readyState` 是否为 `1`
|
||||
3. 确认按住按钮时没有松开
|
||||
|
||||
### 问题5:录音格式不支持
|
||||
**现象**:录音启动失败或报错
|
||||
|
||||
**解决**:
|
||||
尝试修改录音参数:
|
||||
```javascript
|
||||
recorderManager.start({
|
||||
format: 'mp3', // 改为 mp3 试试
|
||||
sampleRate: 16000,
|
||||
numberOfChannels: 1,
|
||||
frameSize: 1 // 改小一点
|
||||
});
|
||||
```
|
||||
|
||||
## 测试步骤
|
||||
|
||||
1. **清空控制台**
|
||||
2. **进入语音通话页面**
|
||||
- 查看 WebSocket 是否连接成功
|
||||
- 查看是否有 `WebSocket onOpen` 日志
|
||||
|
||||
3. **按住"按住说话"按钮**
|
||||
- 应该看到 `startTalking 被调用`
|
||||
- 应该看到 `isTalking 设置为: true`
|
||||
- 应该看到 `=== startRecording 被调用 ===`
|
||||
- 应该看到录音启动成功的日志
|
||||
|
||||
4. **保持按住 2-3 秒**
|
||||
- 应该看到多个 `收到音频帧` 日志
|
||||
- 应该看到多个 `✅ 发送音频数据到服务器` 日志
|
||||
|
||||
5. **松开按钮**
|
||||
- 应该看到 `停止说话, isTalking 设置为: false`
|
||||
- 之后的音频帧应该显示 `⏸️ 不发送音频数据`
|
||||
|
||||
## 下一步
|
||||
|
||||
如果按照上述步骤仍然无法工作,请:
|
||||
1. 截图完整的控制台日志
|
||||
2. 说明是在什么平台测试(App/小程序/H5)
|
||||
3. 说明具体卡在哪一步
|
||||
101
xuniYou/语音通话配置说明.md
Normal file
101
xuniYou/语音通话配置说明.md
Normal file
|
|
@ -0,0 +1,101 @@
|
|||
# 语音通话功能配置说明
|
||||
|
||||
## 已完成的配置
|
||||
|
||||
### 1. manifest.json 配置
|
||||
|
||||
已添加以下权限:
|
||||
|
||||
#### Android 权限
|
||||
```json
|
||||
"<uses-permission android:name=\"android.permission.RECORD_AUDIO\"/>",
|
||||
"<uses-permission android:name=\"android.permission.MODIFY_AUDIO_SETTINGS\"/>"
|
||||
```
|
||||
|
||||
#### iOS 权限
|
||||
```json
|
||||
"privacyDescription" : {
|
||||
"NSMicrophoneUsageDescription" : "与模拟女友聊天"
|
||||
}
|
||||
```
|
||||
|
||||
#### 模块配置
|
||||
```json
|
||||
"modules" : {
|
||||
"Record" : {},
|
||||
"Camera" : {},
|
||||
"VideoPlayer" : {},
|
||||
"OAuth" : {}
|
||||
}
|
||||
```
|
||||
|
||||
### 2. 小程序配置
|
||||
|
||||
微信小程序权限已配置:
|
||||
```json
|
||||
"permission" : {
|
||||
"scope.record" : {
|
||||
"desc" : "你的麦克风将用于音频录制"
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
## 使用说明
|
||||
|
||||
### 按住说话功能
|
||||
|
||||
1. **进入语音通话页面**:录音会自动初始化
|
||||
2. **按住麦克风按钮**:开始发送音频数据
|
||||
3. **松开按钮**:停止发送音频数据(录音继续但不发送)
|
||||
4. **右上角开关**:控制麦克风权限的开启/关闭
|
||||
|
||||
### 交互逻辑
|
||||
|
||||
- 🎤 表示麦克风已开启
|
||||
- 🔇 表示麦克风已关闭
|
||||
- 按住说话时按钮会放大并发光
|
||||
- 如果麦克风关闭,按住说话会提示"请先开启麦克风权限"
|
||||
|
||||
## 重新编译
|
||||
|
||||
修改 manifest.json 后需要:
|
||||
|
||||
1. **App 端**:重新打包(云打包或本地打包)
|
||||
2. **小程序端**:重新编译并上传
|
||||
3. **H5 端**:重新编译即可
|
||||
|
||||
## 调试建议
|
||||
|
||||
### 控制台日志
|
||||
- 查看 "开始录制" 日志确认录音启动
|
||||
- 查看 "开始说话" / "停止说话" 日志确认按钮事件
|
||||
- 查看 WebSocket 连接状态
|
||||
|
||||
### 常见问题
|
||||
|
||||
1. **录音权限被拒绝**
|
||||
- Android:检查应用权限设置
|
||||
- iOS:检查隐私设置中的麦克风权限
|
||||
- 小程序:首次使用会弹出授权提示
|
||||
|
||||
2. **recorderManager 未定义**
|
||||
- 已修复:现在会在 onLoad 时根据平台初始化
|
||||
- App 端使用原生插件,小程序端使用 uni.getRecorderManager()
|
||||
|
||||
3. **音频数据未发送**
|
||||
- 确保 WebSocket 连接成功
|
||||
- 确保按住了说话按钮
|
||||
- 确保麦克风权限已开启
|
||||
|
||||
## 技术细节
|
||||
|
||||
### 录音参数
|
||||
- 格式:PCM
|
||||
- 采样率:16000Hz
|
||||
- 声道:单声道
|
||||
- 帧大小:2KB(低延迟)
|
||||
|
||||
### 按住说话实现
|
||||
- 使用 `@touchstart` 和 `@touchend` 事件
|
||||
- 录音持续进行,但只在按住时发送数据
|
||||
- 避免频繁启停录音造成的延迟
|
||||
41
xuniYou/麦克风图标说明.md
Normal file
41
xuniYou/麦克风图标说明.md
Normal file
|
|
@ -0,0 +1,41 @@
|
|||
# 麦克风图标说明
|
||||
|
||||
语音通话页面需要以下两个图标文件:
|
||||
|
||||
## 需要的图标
|
||||
|
||||
1. `/static/images/mic_on.png` - 麦克风开启状态图标
|
||||
2. `/static/images/mic_off.png` - 麦克风关闭状态图标
|
||||
|
||||
## 图标规格建议
|
||||
|
||||
- 尺寸:48x48 像素(或更高分辨率)
|
||||
- 格式:PNG(支持透明背景)
|
||||
- 颜色:白色或浅色(因为背景是半透明黑色)
|
||||
|
||||
## 临时解决方案
|
||||
|
||||
如果暂时没有这些图标,可以:
|
||||
|
||||
1. 使用现有的 `/static/images/phone_a1.png` 作为临时图标
|
||||
2. 或者在代码中使用文字代替图标
|
||||
|
||||
## 修改方式(如果使用现有图标)
|
||||
|
||||
在 `xuniYou/pages/chat/phone.vue` 中找到:
|
||||
|
||||
```vue
|
||||
<image class="mic-icon" :src="micEnabled ? '/static/images/mic_on.png' : '/static/images/mic_off.png'" mode="widthFix"></image>
|
||||
```
|
||||
|
||||
替换为:
|
||||
|
||||
```vue
|
||||
<image class="mic-icon" src="/static/images/phone_a1.png" mode="widthFix"></image>
|
||||
```
|
||||
|
||||
或使用文字:
|
||||
|
||||
```vue
|
||||
<text class="mic-text">{{ micEnabled ? '🎤' : '🔇' }}</text>
|
||||
```
|
||||
|
|
@ -3,10 +3,10 @@ app_trace = false
|
|||
|
||||
[database]
|
||||
type = mysql
|
||||
hostname = 1.15.149.240
|
||||
hostname = localhost
|
||||
database = fastadmin
|
||||
username = fastadmin
|
||||
password = root
|
||||
username = root
|
||||
password = rootx77
|
||||
hostport = 3306
|
||||
charset = utf8mb4
|
||||
prefix = nf_
|
||||
|
|
|
|||
|
|
@ -69,7 +69,7 @@ class Api
|
|||
* @access public
|
||||
* @param Request $request Request 对象
|
||||
*/
|
||||
public function __construct(Request $request = null)
|
||||
public function __construct(?Request $request = null)
|
||||
{
|
||||
$this->request = is_null($request) ? Request::instance() : $request;
|
||||
|
||||
|
|
|
|||
3133
xunifriend_RaeeC/composer.lock
generated
Normal file
3133
xunifriend_RaeeC/composer.lock
generated
Normal file
File diff suppressed because it is too large
Load Diff
|
|
@ -74,7 +74,7 @@ class App
|
|||
* @return Response
|
||||
* @throws Exception
|
||||
*/
|
||||
public static function run(Request $request = null)
|
||||
public static function run(?Request $request = null)
|
||||
{
|
||||
$request = is_null($request) ? Request::instance() : $request;
|
||||
|
||||
|
|
|
|||
|
|
@ -1151,7 +1151,7 @@ class Request
|
|||
break;
|
||||
// 布尔
|
||||
case 'b':
|
||||
$data = (boolean) $data;
|
||||
$data = (bool) $data;
|
||||
break;
|
||||
// 字符串
|
||||
case 's':
|
||||
|
|
|
|||
|
|
@ -47,7 +47,7 @@ class Html
|
|||
$request = Request::instance();
|
||||
$contentType = $response->getHeader('Content-Type');
|
||||
$accept = $request->header('accept');
|
||||
if (strpos($accept, 'application/json') === 0 || $request->isAjax()) {
|
||||
if (strpos($accept ?? '', 'application/json') === 0 || $request->isAjax()) {
|
||||
return false;
|
||||
} elseif (!empty($contentType) && strpos($contentType, 'html') === false) {
|
||||
return false;
|
||||
|
|
|
|||
5
xunifriend_RaeeC/vendor/autoload.php
vendored
5
xunifriend_RaeeC/vendor/autoload.php
vendored
|
|
@ -14,10 +14,7 @@ if (PHP_VERSION_ID < 50600) {
|
|||
echo $err;
|
||||
}
|
||||
}
|
||||
trigger_error(
|
||||
$err,
|
||||
E_USER_ERROR
|
||||
);
|
||||
throw new RuntimeException($err);
|
||||
}
|
||||
|
||||
require_once __DIR__ . '/composer/autoload_real.php';
|
||||
|
|
|
|||
|
|
@ -26,12 +26,23 @@ use Composer\Semver\VersionParser;
|
|||
*/
|
||||
class InstalledVersions
|
||||
{
|
||||
/**
|
||||
* @var string|null if set (by reflection by Composer), this should be set to the path where this class is being copied to
|
||||
* @internal
|
||||
*/
|
||||
private static $selfDir = null;
|
||||
|
||||
/**
|
||||
* @var mixed[]|null
|
||||
* @psalm-var array{root: array{name: string, pretty_version: string, version: string, reference: string|null, type: string, install_path: string, aliases: string[], dev: bool}, versions: array<string, array{pretty_version?: string, version?: string, reference?: string|null, type?: string, install_path?: string, aliases?: string[], dev_requirement: bool, replaced?: string[], provided?: string[]}>}|array{}|null
|
||||
*/
|
||||
private static $installed;
|
||||
|
||||
/**
|
||||
* @var bool
|
||||
*/
|
||||
private static $installedIsLocalDir;
|
||||
|
||||
/**
|
||||
* @var bool|null
|
||||
*/
|
||||
|
|
@ -309,6 +320,24 @@ class InstalledVersions
|
|||
{
|
||||
self::$installed = $data;
|
||||
self::$installedByVendor = array();
|
||||
|
||||
// when using reload, we disable the duplicate protection to ensure that self::$installed data is
|
||||
// always returned, but we cannot know whether it comes from the installed.php in __DIR__ or not,
|
||||
// so we have to assume it does not, and that may result in duplicate data being returned when listing
|
||||
// all installed packages for example
|
||||
self::$installedIsLocalDir = false;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return string
|
||||
*/
|
||||
private static function getSelfDir()
|
||||
{
|
||||
if (self::$selfDir === null) {
|
||||
self::$selfDir = strtr(__DIR__, '\\', '/');
|
||||
}
|
||||
|
||||
return self::$selfDir;
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
@ -322,19 +351,27 @@ class InstalledVersions
|
|||
}
|
||||
|
||||
$installed = array();
|
||||
$copiedLocalDir = false;
|
||||
|
||||
if (self::$canGetVendors) {
|
||||
$selfDir = self::getSelfDir();
|
||||
foreach (ClassLoader::getRegisteredLoaders() as $vendorDir => $loader) {
|
||||
$vendorDir = strtr($vendorDir, '\\', '/');
|
||||
if (isset(self::$installedByVendor[$vendorDir])) {
|
||||
$installed[] = self::$installedByVendor[$vendorDir];
|
||||
} elseif (is_file($vendorDir.'/composer/installed.php')) {
|
||||
/** @var array{root: array{name: string, pretty_version: string, version: string, reference: string|null, type: string, install_path: string, aliases: string[], dev: bool}, versions: array<string, array{pretty_version?: string, version?: string, reference?: string|null, type?: string, install_path?: string, aliases?: string[], dev_requirement: bool, replaced?: string[], provided?: string[]}>} $required */
|
||||
$required = require $vendorDir.'/composer/installed.php';
|
||||
$installed[] = self::$installedByVendor[$vendorDir] = $required;
|
||||
if (null === self::$installed && strtr($vendorDir.'/composer', '\\', '/') === strtr(__DIR__, '\\', '/')) {
|
||||
self::$installed = $installed[count($installed) - 1];
|
||||
self::$installedByVendor[$vendorDir] = $required;
|
||||
$installed[] = $required;
|
||||
if (self::$installed === null && $vendorDir.'/composer' === $selfDir) {
|
||||
self::$installed = $required;
|
||||
self::$installedIsLocalDir = true;
|
||||
}
|
||||
}
|
||||
if (self::$installedIsLocalDir && $vendorDir.'/composer' === $selfDir) {
|
||||
$copiedLocalDir = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -350,7 +387,7 @@ class InstalledVersions
|
|||
}
|
||||
}
|
||||
|
||||
if (self::$installed !== array()) {
|
||||
if (self::$installed !== array() && !$copiedLocalDir) {
|
||||
$installed[] = self::$installed;
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -10,7 +10,7 @@ return array(
|
|||
'Composer\\InstalledVersions' => $vendorDir . '/composer/InstalledVersions.php',
|
||||
'JsonException' => $vendorDir . '/symfony/polyfill-php73/Resources/stubs/JsonException.php',
|
||||
'PhpToken' => $vendorDir . '/symfony/polyfill-php80/Resources/stubs/PhpToken.php',
|
||||
'Stringable' => $vendorDir . '/myclabs/php-enum/stubs/Stringable.php',
|
||||
'Stringable' => $vendorDir . '/symfony/polyfill-php80/Resources/stubs/Stringable.php',
|
||||
'UnhandledMatchError' => $vendorDir . '/symfony/polyfill-php80/Resources/stubs/UnhandledMatchError.php',
|
||||
'ValueError' => $vendorDir . '/symfony/polyfill-php80/Resources/stubs/ValueError.php',
|
||||
);
|
||||
|
|
|
|||
|
|
@ -9,7 +9,7 @@ return array(
|
|||
'think\\helper\\' => array($vendorDir . '/topthink/think-helper/src'),
|
||||
'think\\composer\\' => array($vendorDir . '/topthink/think-installer/src'),
|
||||
'think\\captcha\\' => array($vendorDir . '/topthink/think-captcha/src'),
|
||||
'think\\' => array($baseDir . '/thinkphp/library/think', $vendorDir . '/topthink/think-queue/src', $vendorDir . '/fastadminnet/fastadmin-addons/src'),
|
||||
'think\\' => array($vendorDir . '/topthink/think-queue/src', $vendorDir . '/fastadminnet/fastadmin-addons/src', $baseDir . '/thinkphp/library/think'),
|
||||
'tests\\' => array($vendorDir . '/maniac/easemob-php/tests'),
|
||||
'addons\\' => array($baseDir . '/addons'),
|
||||
'ZipStream\\' => array($vendorDir . '/maennchen/zipstream-php/src'),
|
||||
|
|
@ -28,7 +28,7 @@ return array(
|
|||
'Symfony\\Bridge\\PsrHttpMessage\\' => array($vendorDir . '/symfony/psr-http-message-bridge'),
|
||||
'Psr\\SimpleCache\\' => array($vendorDir . '/psr/simple-cache/src'),
|
||||
'Psr\\Log\\' => array($vendorDir . '/psr/log/Psr/Log'),
|
||||
'Psr\\Http\\Message\\' => array($vendorDir . '/psr/http-message/src', $vendorDir . '/psr/http-factory/src'),
|
||||
'Psr\\Http\\Message\\' => array($vendorDir . '/psr/http-factory/src', $vendorDir . '/psr/http-message/src'),
|
||||
'Psr\\Http\\Client\\' => array($vendorDir . '/psr/http-client/src'),
|
||||
'Psr\\EventDispatcher\\' => array($vendorDir . '/psr/event-dispatcher/src'),
|
||||
'Psr\\Container\\' => array($vendorDir . '/psr/container/src'),
|
||||
|
|
@ -37,7 +37,6 @@ return array(
|
|||
'PhpOffice\\PhpSpreadsheet\\' => array($vendorDir . '/phpoffice/phpspreadsheet/src/PhpSpreadsheet'),
|
||||
'Overtrue\\Socialite\\' => array($vendorDir . '/overtrue/socialite/src'),
|
||||
'Overtrue\\Pinyin\\' => array($vendorDir . '/overtrue/pinyin/src'),
|
||||
'MyCLabs\\Enum\\' => array($vendorDir . '/myclabs/php-enum/src'),
|
||||
'Monolog\\' => array($vendorDir . '/monolog/monolog/src/Monolog'),
|
||||
'Matrix\\' => array($vendorDir . '/markbaker/matrix/classes/src'),
|
||||
'GuzzleHttp\\Psr7\\' => array($vendorDir . '/guzzlehttp/psr7/src'),
|
||||
|
|
|
|||
129
xunifriend_RaeeC/vendor/composer/autoload_static.php
vendored
129
xunifriend_RaeeC/vendor/composer/autoload_static.php
vendored
|
|
@ -24,7 +24,7 @@ class ComposerStaticInitf3106b6ef3260b6914241eab0bed11c1
|
|||
);
|
||||
|
||||
public static $prefixLengthsPsr4 = array (
|
||||
't' =>
|
||||
't' =>
|
||||
array (
|
||||
'think\\helper\\' => 13,
|
||||
'think\\composer\\' => 15,
|
||||
|
|
@ -32,19 +32,19 @@ class ComposerStaticInitf3106b6ef3260b6914241eab0bed11c1
|
|||
'think\\' => 6,
|
||||
'tests\\' => 6,
|
||||
),
|
||||
'a' =>
|
||||
'a' =>
|
||||
array (
|
||||
'addons\\' => 7,
|
||||
),
|
||||
'Z' =>
|
||||
'Z' =>
|
||||
array (
|
||||
'ZipStream\\' => 10,
|
||||
),
|
||||
'T' =>
|
||||
'T' =>
|
||||
array (
|
||||
'Tx\\' => 3,
|
||||
),
|
||||
'S' =>
|
||||
'S' =>
|
||||
array (
|
||||
'Symfony\\Polyfill\\Php80\\' => 23,
|
||||
'Symfony\\Polyfill\\Php73\\' => 23,
|
||||
|
|
@ -59,7 +59,7 @@ class ComposerStaticInitf3106b6ef3260b6914241eab0bed11c1
|
|||
'Symfony\\Component\\Cache\\' => 24,
|
||||
'Symfony\\Bridge\\PsrHttpMessage\\' => 30,
|
||||
),
|
||||
'P' =>
|
||||
'P' =>
|
||||
array (
|
||||
'Psr\\SimpleCache\\' => 16,
|
||||
'Psr\\Log\\' => 8,
|
||||
|
|
@ -71,30 +71,29 @@ class ComposerStaticInitf3106b6ef3260b6914241eab0bed11c1
|
|||
'PhpZip\\' => 7,
|
||||
'PhpOffice\\PhpSpreadsheet\\' => 25,
|
||||
),
|
||||
'O' =>
|
||||
'O' =>
|
||||
array (
|
||||
'Overtrue\\Socialite\\' => 19,
|
||||
'Overtrue\\Pinyin\\' => 16,
|
||||
),
|
||||
'M' =>
|
||||
'M' =>
|
||||
array (
|
||||
'MyCLabs\\Enum\\' => 13,
|
||||
'Monolog\\' => 8,
|
||||
'Matrix\\' => 7,
|
||||
),
|
||||
'G' =>
|
||||
'G' =>
|
||||
array (
|
||||
'GuzzleHttp\\Psr7\\' => 16,
|
||||
'GuzzleHttp\\Promise\\' => 19,
|
||||
'GuzzleHttp\\' => 11,
|
||||
),
|
||||
'E' =>
|
||||
'E' =>
|
||||
array (
|
||||
'EasyWeChat\\' => 11,
|
||||
'EasyWeChatComposer\\' => 19,
|
||||
'Easemob\\' => 8,
|
||||
),
|
||||
'C' =>
|
||||
'C' =>
|
||||
array (
|
||||
'Composer\\Pcre\\' => 14,
|
||||
'Complex\\' => 8,
|
||||
|
|
@ -102,190 +101,186 @@ class ComposerStaticInitf3106b6ef3260b6914241eab0bed11c1
|
|||
);
|
||||
|
||||
public static $prefixDirsPsr4 = array (
|
||||
'think\\helper\\' =>
|
||||
'think\\helper\\' =>
|
||||
array (
|
||||
0 => __DIR__ . '/..' . '/topthink/think-helper/src',
|
||||
),
|
||||
'think\\composer\\' =>
|
||||
'think\\composer\\' =>
|
||||
array (
|
||||
0 => __DIR__ . '/..' . '/topthink/think-installer/src',
|
||||
),
|
||||
'think\\captcha\\' =>
|
||||
'think\\captcha\\' =>
|
||||
array (
|
||||
0 => __DIR__ . '/..' . '/topthink/think-captcha/src',
|
||||
),
|
||||
'think\\' =>
|
||||
'think\\' =>
|
||||
array (
|
||||
0 => __DIR__ . '/../..' . '/thinkphp/library/think',
|
||||
1 => __DIR__ . '/..' . '/topthink/think-queue/src',
|
||||
2 => __DIR__ . '/..' . '/fastadminnet/fastadmin-addons/src',
|
||||
0 => __DIR__ . '/..' . '/topthink/think-queue/src',
|
||||
1 => __DIR__ . '/..' . '/fastadminnet/fastadmin-addons/src',
|
||||
2 => __DIR__ . '/../..' . '/thinkphp/library/think',
|
||||
),
|
||||
'tests\\' =>
|
||||
'tests\\' =>
|
||||
array (
|
||||
0 => __DIR__ . '/..' . '/maniac/easemob-php/tests',
|
||||
),
|
||||
'addons\\' =>
|
||||
'addons\\' =>
|
||||
array (
|
||||
0 => __DIR__ . '/../..' . '/addons',
|
||||
),
|
||||
'ZipStream\\' =>
|
||||
'ZipStream\\' =>
|
||||
array (
|
||||
0 => __DIR__ . '/..' . '/maennchen/zipstream-php/src',
|
||||
),
|
||||
'Tx\\' =>
|
||||
'Tx\\' =>
|
||||
array (
|
||||
0 => __DIR__ . '/..' . '/fastadminnet/fastadmin-mailer/src',
|
||||
),
|
||||
'Symfony\\Polyfill\\Php80\\' =>
|
||||
'Symfony\\Polyfill\\Php80\\' =>
|
||||
array (
|
||||
0 => __DIR__ . '/..' . '/symfony/polyfill-php80',
|
||||
),
|
||||
'Symfony\\Polyfill\\Php73\\' =>
|
||||
'Symfony\\Polyfill\\Php73\\' =>
|
||||
array (
|
||||
0 => __DIR__ . '/..' . '/symfony/polyfill-php73',
|
||||
),
|
||||
'Symfony\\Polyfill\\Mbstring\\' =>
|
||||
'Symfony\\Polyfill\\Mbstring\\' =>
|
||||
array (
|
||||
0 => __DIR__ . '/..' . '/symfony/polyfill-mbstring',
|
||||
),
|
||||
'Symfony\\Contracts\\Service\\' =>
|
||||
'Symfony\\Contracts\\Service\\' =>
|
||||
array (
|
||||
0 => __DIR__ . '/..' . '/symfony/service-contracts',
|
||||
),
|
||||
'Symfony\\Contracts\\EventDispatcher\\' =>
|
||||
'Symfony\\Contracts\\EventDispatcher\\' =>
|
||||
array (
|
||||
0 => __DIR__ . '/..' . '/symfony/event-dispatcher-contracts',
|
||||
),
|
||||
'Symfony\\Contracts\\Cache\\' =>
|
||||
'Symfony\\Contracts\\Cache\\' =>
|
||||
array (
|
||||
0 => __DIR__ . '/..' . '/symfony/cache-contracts',
|
||||
),
|
||||
'Symfony\\Component\\VarExporter\\' =>
|
||||
'Symfony\\Component\\VarExporter\\' =>
|
||||
array (
|
||||
0 => __DIR__ . '/..' . '/symfony/var-exporter',
|
||||
),
|
||||
'Symfony\\Component\\HttpFoundation\\' =>
|
||||
'Symfony\\Component\\HttpFoundation\\' =>
|
||||
array (
|
||||
0 => __DIR__ . '/..' . '/symfony/http-foundation',
|
||||
),
|
||||
'Symfony\\Component\\Finder\\' =>
|
||||
'Symfony\\Component\\Finder\\' =>
|
||||
array (
|
||||
0 => __DIR__ . '/..' . '/symfony/finder',
|
||||
),
|
||||
'Symfony\\Component\\EventDispatcher\\' =>
|
||||
'Symfony\\Component\\EventDispatcher\\' =>
|
||||
array (
|
||||
0 => __DIR__ . '/..' . '/symfony/event-dispatcher',
|
||||
),
|
||||
'Symfony\\Component\\Cache\\' =>
|
||||
'Symfony\\Component\\Cache\\' =>
|
||||
array (
|
||||
0 => __DIR__ . '/..' . '/symfony/cache',
|
||||
),
|
||||
'Symfony\\Bridge\\PsrHttpMessage\\' =>
|
||||
'Symfony\\Bridge\\PsrHttpMessage\\' =>
|
||||
array (
|
||||
0 => __DIR__ . '/..' . '/symfony/psr-http-message-bridge',
|
||||
),
|
||||
'Psr\\SimpleCache\\' =>
|
||||
'Psr\\SimpleCache\\' =>
|
||||
array (
|
||||
0 => __DIR__ . '/..' . '/psr/simple-cache/src',
|
||||
),
|
||||
'Psr\\Log\\' =>
|
||||
'Psr\\Log\\' =>
|
||||
array (
|
||||
0 => __DIR__ . '/..' . '/psr/log/Psr/Log',
|
||||
),
|
||||
'Psr\\Http\\Message\\' =>
|
||||
'Psr\\Http\\Message\\' =>
|
||||
array (
|
||||
0 => __DIR__ . '/..' . '/psr/http-message/src',
|
||||
1 => __DIR__ . '/..' . '/psr/http-factory/src',
|
||||
0 => __DIR__ . '/..' . '/psr/http-factory/src',
|
||||
1 => __DIR__ . '/..' . '/psr/http-message/src',
|
||||
),
|
||||
'Psr\\Http\\Client\\' =>
|
||||
'Psr\\Http\\Client\\' =>
|
||||
array (
|
||||
0 => __DIR__ . '/..' . '/psr/http-client/src',
|
||||
),
|
||||
'Psr\\EventDispatcher\\' =>
|
||||
'Psr\\EventDispatcher\\' =>
|
||||
array (
|
||||
0 => __DIR__ . '/..' . '/psr/event-dispatcher/src',
|
||||
),
|
||||
'Psr\\Container\\' =>
|
||||
'Psr\\Container\\' =>
|
||||
array (
|
||||
0 => __DIR__ . '/..' . '/psr/container/src',
|
||||
),
|
||||
'Psr\\Cache\\' =>
|
||||
'Psr\\Cache\\' =>
|
||||
array (
|
||||
0 => __DIR__ . '/..' . '/psr/cache/src',
|
||||
),
|
||||
'PhpZip\\' =>
|
||||
'PhpZip\\' =>
|
||||
array (
|
||||
0 => __DIR__ . '/..' . '/nelexa/zip/src',
|
||||
),
|
||||
'PhpOffice\\PhpSpreadsheet\\' =>
|
||||
'PhpOffice\\PhpSpreadsheet\\' =>
|
||||
array (
|
||||
0 => __DIR__ . '/..' . '/phpoffice/phpspreadsheet/src/PhpSpreadsheet',
|
||||
),
|
||||
'Overtrue\\Socialite\\' =>
|
||||
'Overtrue\\Socialite\\' =>
|
||||
array (
|
||||
0 => __DIR__ . '/..' . '/overtrue/socialite/src',
|
||||
),
|
||||
'Overtrue\\Pinyin\\' =>
|
||||
'Overtrue\\Pinyin\\' =>
|
||||
array (
|
||||
0 => __DIR__ . '/..' . '/overtrue/pinyin/src',
|
||||
),
|
||||
'MyCLabs\\Enum\\' =>
|
||||
array (
|
||||
0 => __DIR__ . '/..' . '/myclabs/php-enum/src',
|
||||
),
|
||||
'Monolog\\' =>
|
||||
'Monolog\\' =>
|
||||
array (
|
||||
0 => __DIR__ . '/..' . '/monolog/monolog/src/Monolog',
|
||||
),
|
||||
'Matrix\\' =>
|
||||
'Matrix\\' =>
|
||||
array (
|
||||
0 => __DIR__ . '/..' . '/markbaker/matrix/classes/src',
|
||||
),
|
||||
'GuzzleHttp\\Psr7\\' =>
|
||||
'GuzzleHttp\\Psr7\\' =>
|
||||
array (
|
||||
0 => __DIR__ . '/..' . '/guzzlehttp/psr7/src',
|
||||
),
|
||||
'GuzzleHttp\\Promise\\' =>
|
||||
'GuzzleHttp\\Promise\\' =>
|
||||
array (
|
||||
0 => __DIR__ . '/..' . '/guzzlehttp/promises/src',
|
||||
),
|
||||
'GuzzleHttp\\' =>
|
||||
'GuzzleHttp\\' =>
|
||||
array (
|
||||
0 => __DIR__ . '/..' . '/guzzlehttp/guzzle/src',
|
||||
),
|
||||
'EasyWeChat\\' =>
|
||||
'EasyWeChat\\' =>
|
||||
array (
|
||||
0 => __DIR__ . '/..' . '/overtrue/wechat/src',
|
||||
),
|
||||
'EasyWeChatComposer\\' =>
|
||||
'EasyWeChatComposer\\' =>
|
||||
array (
|
||||
0 => __DIR__ . '/..' . '/easywechat-composer/easywechat-composer/src',
|
||||
),
|
||||
'Easemob\\' =>
|
||||
'Easemob\\' =>
|
||||
array (
|
||||
0 => __DIR__ . '/..' . '/maniac/easemob-php/src',
|
||||
),
|
||||
'Composer\\Pcre\\' =>
|
||||
'Composer\\Pcre\\' =>
|
||||
array (
|
||||
0 => __DIR__ . '/..' . '/composer/pcre/src',
|
||||
),
|
||||
'Complex\\' =>
|
||||
'Complex\\' =>
|
||||
array (
|
||||
0 => __DIR__ . '/..' . '/markbaker/complex/classes/src',
|
||||
),
|
||||
);
|
||||
|
||||
public static $prefixesPsr0 = array (
|
||||
'P' =>
|
||||
'P' =>
|
||||
array (
|
||||
'Pimple' =>
|
||||
'Pimple' =>
|
||||
array (
|
||||
0 => __DIR__ . '/..' . '/pimple/pimple/src',
|
||||
),
|
||||
),
|
||||
'H' =>
|
||||
'H' =>
|
||||
array (
|
||||
'HTMLPurifier' =>
|
||||
'HTMLPurifier' =>
|
||||
array (
|
||||
0 => __DIR__ . '/..' . '/ezyang/htmlpurifier/library',
|
||||
),
|
||||
|
|
@ -297,7 +292,7 @@ class ComposerStaticInitf3106b6ef3260b6914241eab0bed11c1
|
|||
'Composer\\InstalledVersions' => __DIR__ . '/..' . '/composer/InstalledVersions.php',
|
||||
'JsonException' => __DIR__ . '/..' . '/symfony/polyfill-php73/Resources/stubs/JsonException.php',
|
||||
'PhpToken' => __DIR__ . '/..' . '/symfony/polyfill-php80/Resources/stubs/PhpToken.php',
|
||||
'Stringable' => __DIR__ . '/..' . '/myclabs/php-enum/stubs/Stringable.php',
|
||||
'Stringable' => __DIR__ . '/..' . '/symfony/polyfill-php80/Resources/stubs/Stringable.php',
|
||||
'UnhandledMatchError' => __DIR__ . '/..' . '/symfony/polyfill-php80/Resources/stubs/UnhandledMatchError.php',
|
||||
'ValueError' => __DIR__ . '/..' . '/symfony/polyfill-php80/Resources/stubs/ValueError.php',
|
||||
);
|
||||
|
|
|
|||
520
xunifriend_RaeeC/vendor/composer/installed.json
vendored
520
xunifriend_RaeeC/vendor/composer/installed.json
vendored
File diff suppressed because it is too large
Load Diff
123
xunifriend_RaeeC/vendor/composer/installed.php
vendored
123
xunifriend_RaeeC/vendor/composer/installed.php
vendored
|
|
@ -1,9 +1,9 @@
|
|||
<?php return array(
|
||||
'root' => array(
|
||||
'name' => 'fastadminnet/fastadmin',
|
||||
'pretty_version' => 'dev-master',
|
||||
'version' => 'dev-master',
|
||||
'reference' => '50cdd33546641ce244c4733aeb5578edcb07aab8',
|
||||
'pretty_version' => 'dev-main',
|
||||
'version' => 'dev-main',
|
||||
'reference' => '0c9fb211d1eb61cb95c42c34900db3d3774c0245',
|
||||
'type' => 'project',
|
||||
'install_path' => __DIR__ . '/../../',
|
||||
'aliases' => array(),
|
||||
|
|
@ -38,18 +38,18 @@
|
|||
'dev_requirement' => false,
|
||||
),
|
||||
'fastadminnet/fastadmin' => array(
|
||||
'pretty_version' => 'dev-master',
|
||||
'version' => 'dev-master',
|
||||
'reference' => '50cdd33546641ce244c4733aeb5578edcb07aab8',
|
||||
'pretty_version' => 'dev-main',
|
||||
'version' => 'dev-main',
|
||||
'reference' => '0c9fb211d1eb61cb95c42c34900db3d3774c0245',
|
||||
'type' => 'project',
|
||||
'install_path' => __DIR__ . '/../../',
|
||||
'aliases' => array(),
|
||||
'dev_requirement' => false,
|
||||
),
|
||||
'fastadminnet/fastadmin-addons' => array(
|
||||
'pretty_version' => '1.4.2',
|
||||
'version' => '1.4.2.0',
|
||||
'reference' => '14af178a62fb4cc897f954fa9d7d53798ad2cf37',
|
||||
'pretty_version' => '1.4.4',
|
||||
'version' => '1.4.4.0',
|
||||
'reference' => '5a02139c773821c9ac646b7950fedf6950330054',
|
||||
'type' => 'library',
|
||||
'install_path' => __DIR__ . '/../fastadminnet/fastadmin-addons',
|
||||
'aliases' => array(),
|
||||
|
|
@ -65,9 +65,9 @@
|
|||
'dev_requirement' => false,
|
||||
),
|
||||
'guzzlehttp/guzzle' => array(
|
||||
'pretty_version' => '7.9.2',
|
||||
'version' => '7.9.2.0',
|
||||
'reference' => 'd281ed313b989f213357e3be1a179f02196ac99b',
|
||||
'pretty_version' => '7.10.0',
|
||||
'version' => '7.10.0.0',
|
||||
'reference' => 'b51ac707cfa420b7bfd4e4d5e510ba8008e822b4',
|
||||
'type' => 'library',
|
||||
'install_path' => __DIR__ . '/../guzzlehttp/guzzle',
|
||||
'aliases' => array(),
|
||||
|
|
@ -92,9 +92,9 @@
|
|||
'dev_requirement' => false,
|
||||
),
|
||||
'maennchen/zipstream-php' => array(
|
||||
'pretty_version' => '2.2.6',
|
||||
'version' => '2.2.6.0',
|
||||
'reference' => '30ad6f93cf3efe4192bc7a4c9cad11ff8f4f237f',
|
||||
'pretty_version' => '3.2.1',
|
||||
'version' => '3.2.1.0',
|
||||
'reference' => '682f1098a8fddbaf43edac2306a691c7ad508ec5',
|
||||
'type' => 'library',
|
||||
'install_path' => __DIR__ . '/../maennchen/zipstream-php',
|
||||
'aliases' => array(),
|
||||
|
|
@ -128,23 +128,14 @@
|
|||
'dev_requirement' => false,
|
||||
),
|
||||
'monolog/monolog' => array(
|
||||
'pretty_version' => '2.10.0',
|
||||
'version' => '2.10.0.0',
|
||||
'reference' => '5cf826f2991858b54d5c3809bee745560a1042a7',
|
||||
'pretty_version' => '2.11.0',
|
||||
'version' => '2.11.0.0',
|
||||
'reference' => '37308608e599f34a1a4845b16440047ec98a172a',
|
||||
'type' => 'library',
|
||||
'install_path' => __DIR__ . '/../monolog/monolog',
|
||||
'aliases' => array(),
|
||||
'dev_requirement' => false,
|
||||
),
|
||||
'myclabs/php-enum' => array(
|
||||
'pretty_version' => '1.8.4',
|
||||
'version' => '1.8.4.0',
|
||||
'reference' => 'a867478eae49c9f59ece437ae7f9506bfaa27483',
|
||||
'type' => 'library',
|
||||
'install_path' => __DIR__ . '/../myclabs/php-enum',
|
||||
'aliases' => array(),
|
||||
'dev_requirement' => false,
|
||||
),
|
||||
'nelexa/zip' => array(
|
||||
'pretty_version' => '4.0.2',
|
||||
'version' => '4.0.2.0',
|
||||
|
|
@ -182,27 +173,27 @@
|
|||
'dev_requirement' => false,
|
||||
),
|
||||
'phpoffice/phpspreadsheet' => array(
|
||||
'pretty_version' => '1.30.1',
|
||||
'version' => '1.30.1.0',
|
||||
'reference' => 'fa8257a579ec623473eabfe49731de5967306c4c',
|
||||
'pretty_version' => '1.30.0',
|
||||
'version' => '1.30.0.0',
|
||||
'reference' => '2f39286e0136673778b7a142b3f0d141e43d1714',
|
||||
'type' => 'library',
|
||||
'install_path' => __DIR__ . '/../phpoffice/phpspreadsheet',
|
||||
'aliases' => array(),
|
||||
'dev_requirement' => false,
|
||||
),
|
||||
'pimple/pimple' => array(
|
||||
'pretty_version' => 'v3.6.0',
|
||||
'version' => '3.6.0.0',
|
||||
'reference' => 'a70f552d338f9266eec6606c1f0b324da5514c96',
|
||||
'pretty_version' => 'v3.6.2',
|
||||
'version' => '3.6.2.0',
|
||||
'reference' => '8cfe7f74ac22a433d303914eba9ea4c2a834edce',
|
||||
'type' => 'library',
|
||||
'install_path' => __DIR__ . '/../pimple/pimple',
|
||||
'aliases' => array(),
|
||||
'dev_requirement' => false,
|
||||
),
|
||||
'psr/cache' => array(
|
||||
'pretty_version' => '1.0.1',
|
||||
'version' => '1.0.1.0',
|
||||
'reference' => 'd11b50ad223250cf17b86e38383413f5a6764bf8',
|
||||
'pretty_version' => '2.0.0',
|
||||
'version' => '2.0.0.0',
|
||||
'reference' => '213f9dbc5b9bfbc4f8db86d2838dc968752ce13b',
|
||||
'type' => 'library',
|
||||
'install_path' => __DIR__ . '/../psr/cache',
|
||||
'aliases' => array(),
|
||||
|
|
@ -254,9 +245,9 @@
|
|||
),
|
||||
),
|
||||
'psr/http-factory' => array(
|
||||
'pretty_version' => '1.0.2',
|
||||
'version' => '1.0.2.0',
|
||||
'reference' => 'e616d01114759c4c489f93b099585439f795fe35',
|
||||
'pretty_version' => '1.1.0',
|
||||
'version' => '1.1.0.0',
|
||||
'reference' => '2b4765fddfe3b508ac62f829e852b1501d3f6e8a',
|
||||
'type' => 'library',
|
||||
'install_path' => __DIR__ . '/../psr/http-factory',
|
||||
'aliases' => array(),
|
||||
|
|
@ -269,9 +260,9 @@
|
|||
),
|
||||
),
|
||||
'psr/http-message' => array(
|
||||
'pretty_version' => '1.1',
|
||||
'version' => '1.1.0.0',
|
||||
'reference' => 'cb6ce4845ce34a8ad9e68117c10ee90a29919eba',
|
||||
'pretty_version' => '2.0',
|
||||
'version' => '2.0.0.0',
|
||||
'reference' => '402d35bcb92c70c026d1a6a9883f06b2ead23d71',
|
||||
'type' => 'library',
|
||||
'install_path' => __DIR__ . '/../psr/http-message',
|
||||
'aliases' => array(),
|
||||
|
|
@ -347,9 +338,9 @@
|
|||
),
|
||||
),
|
||||
'symfony/deprecation-contracts' => array(
|
||||
'pretty_version' => 'v2.5.4',
|
||||
'version' => '2.5.4.0',
|
||||
'reference' => '605389f2a7e5625f273b53960dc46aeaf9c62918',
|
||||
'pretty_version' => 'v3.6.0',
|
||||
'version' => '3.6.0.0',
|
||||
'reference' => '63afe740e99a13ba87ec199bb07bbdee937a5b62',
|
||||
'type' => 'library',
|
||||
'install_path' => __DIR__ . '/../symfony/deprecation-contracts',
|
||||
'aliases' => array(),
|
||||
|
|
@ -365,9 +356,9 @@
|
|||
'dev_requirement' => false,
|
||||
),
|
||||
'symfony/event-dispatcher-contracts' => array(
|
||||
'pretty_version' => 'v2.5.4',
|
||||
'version' => '2.5.4.0',
|
||||
'reference' => 'e0fe3d79b516eb75126ac6fa4cbf19b79b08c99f',
|
||||
'pretty_version' => 'v3.6.0',
|
||||
'version' => '3.6.0.0',
|
||||
'reference' => '59eb412e93815df44f05f342958efa9f46b1e586',
|
||||
'type' => 'library',
|
||||
'install_path' => __DIR__ . '/../symfony/event-dispatcher-contracts',
|
||||
'aliases' => array(),
|
||||
|
|
@ -380,26 +371,26 @@
|
|||
),
|
||||
),
|
||||
'symfony/finder' => array(
|
||||
'pretty_version' => 'v5.4.45',
|
||||
'version' => '5.4.45.0',
|
||||
'reference' => '63741784cd7b9967975eec610b256eed3ede022b',
|
||||
'pretty_version' => 'v8.0.6',
|
||||
'version' => '8.0.6.0',
|
||||
'reference' => '441404f09a54de6d1bd6ad219e088cdf4c91f97c',
|
||||
'type' => 'library',
|
||||
'install_path' => __DIR__ . '/../symfony/finder',
|
||||
'aliases' => array(),
|
||||
'dev_requirement' => false,
|
||||
),
|
||||
'symfony/http-foundation' => array(
|
||||
'pretty_version' => 'v5.4.48',
|
||||
'version' => '5.4.48.0',
|
||||
'reference' => '3f38b8af283b830e1363acd79e5bc3412d055341',
|
||||
'pretty_version' => 'v5.4.50',
|
||||
'version' => '5.4.50.0',
|
||||
'reference' => '1a0706e8b8041046052ea2695eb8aeee04f97609',
|
||||
'type' => 'library',
|
||||
'install_path' => __DIR__ . '/../symfony/http-foundation',
|
||||
'aliases' => array(),
|
||||
'dev_requirement' => false,
|
||||
),
|
||||
'symfony/polyfill-mbstring' => array(
|
||||
'pretty_version' => 'v1.32.0',
|
||||
'version' => '1.32.0.0',
|
||||
'pretty_version' => 'v1.33.0',
|
||||
'version' => '1.33.0.0',
|
||||
'reference' => '6d857f4d76bd4b343eac26d6b539585d2bc56493',
|
||||
'type' => 'library',
|
||||
'install_path' => __DIR__ . '/../symfony/polyfill-mbstring',
|
||||
|
|
@ -407,8 +398,8 @@
|
|||
'dev_requirement' => false,
|
||||
),
|
||||
'symfony/polyfill-php73' => array(
|
||||
'pretty_version' => 'v1.32.0',
|
||||
'version' => '1.32.0.0',
|
||||
'pretty_version' => 'v1.33.0',
|
||||
'version' => '1.33.0.0',
|
||||
'reference' => '0f68c03565dcaaf25a890667542e8bd75fe7e5bb',
|
||||
'type' => 'library',
|
||||
'install_path' => __DIR__ . '/../symfony/polyfill-php73',
|
||||
|
|
@ -416,8 +407,8 @@
|
|||
'dev_requirement' => false,
|
||||
),
|
||||
'symfony/polyfill-php80' => array(
|
||||
'pretty_version' => 'v1.32.0',
|
||||
'version' => '1.32.0.0',
|
||||
'pretty_version' => 'v1.33.0',
|
||||
'version' => '1.33.0.0',
|
||||
'reference' => '0cc9dd0f17f61d8131e7df6b84bd344899fe2608',
|
||||
'type' => 'library',
|
||||
'install_path' => __DIR__ . '/../symfony/polyfill-php80',
|
||||
|
|
@ -434,18 +425,18 @@
|
|||
'dev_requirement' => false,
|
||||
),
|
||||
'symfony/service-contracts' => array(
|
||||
'pretty_version' => 'v1.1.2',
|
||||
'version' => '1.1.2.0',
|
||||
'reference' => '191afdcb5804db960d26d8566b7e9a2843cab3a0',
|
||||
'pretty_version' => 'v3.6.1',
|
||||
'version' => '3.6.1.0',
|
||||
'reference' => '45112560a3ba2d715666a509a0bc9521d10b6c43',
|
||||
'type' => 'library',
|
||||
'install_path' => __DIR__ . '/../symfony/service-contracts',
|
||||
'aliases' => array(),
|
||||
'dev_requirement' => false,
|
||||
),
|
||||
'symfony/var-exporter' => array(
|
||||
'pretty_version' => 'v5.4.45',
|
||||
'version' => '5.4.45.0',
|
||||
'reference' => '862700068db0ddfd8c5b850671e029a90246ec75',
|
||||
'pretty_version' => 'v6.4.26',
|
||||
'version' => '6.4.26.0',
|
||||
'reference' => '466fcac5fa2e871f83d31173f80e9c2684743bfc',
|
||||
'type' => 'library',
|
||||
'install_path' => __DIR__ . '/../symfony/var-exporter',
|
||||
'aliases' => array(),
|
||||
|
|
|
|||
|
|
@ -4,8 +4,12 @@
|
|||
|
||||
$issues = array();
|
||||
|
||||
if (!(PHP_VERSION_ID >= 70400)) {
|
||||
$issues[] = 'Your Composer dependencies require a PHP version ">= 7.4.0". You are running ' . PHP_VERSION . '.';
|
||||
if (!(PHP_VERSION_ID >= 80400)) {
|
||||
$issues[] = 'Your Composer dependencies require a PHP version ">= 8.4.0". You are running ' . PHP_VERSION . '.';
|
||||
}
|
||||
|
||||
if (PHP_INT_SIZE !== 8) {
|
||||
$issues[] = 'Your Composer dependencies require a 64-bit build of PHP.';
|
||||
}
|
||||
|
||||
if ($issues) {
|
||||
|
|
@ -19,8 +23,7 @@ if ($issues) {
|
|||
echo 'Composer detected issues in your platform:' . PHP_EOL.PHP_EOL . str_replace('You are running '.PHP_VERSION.'.', '', implode(PHP_EOL, $issues)) . PHP_EOL.PHP_EOL;
|
||||
}
|
||||
}
|
||||
trigger_error(
|
||||
'Composer detected issues in your platform: ' . implode(' ', $issues),
|
||||
E_USER_ERROR
|
||||
throw new \RuntimeException(
|
||||
'Composer detected issues in your platform: ' . implode(' ', $issues)
|
||||
);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -3,7 +3,7 @@
|
|||
"description": "addons package for fastadmin",
|
||||
"homepage": "https://github.com/fastadminnet/fastadmin-addons",
|
||||
"license": "Apache-2.0",
|
||||
"version": "1.4.2",
|
||||
"version": "1.4.4",
|
||||
"authors": [
|
||||
{
|
||||
"name": "Karson",
|
||||
|
|
@ -18,7 +18,7 @@
|
|||
"issues": "https://github.com/fastadminnet/fastadmin-addons/issues"
|
||||
},
|
||||
"require": {
|
||||
"php": ">=7.0.0",
|
||||
"php": ">=7.1.0",
|
||||
"nelexa/zip": "^3.3 || ^4.0"
|
||||
},
|
||||
"autoload": {
|
||||
|
|
|
|||
|
|
@ -63,7 +63,7 @@ class Controller extends \think\Controller
|
|||
* @param Request $request Request对象
|
||||
* @access public
|
||||
*/
|
||||
public function __construct(Request $request = null)
|
||||
public function __construct(?Request $request = null)
|
||||
{
|
||||
if (is_null($request)) {
|
||||
$request = Request::instance();
|
||||
|
|
|
|||
|
|
@ -10,7 +10,6 @@ use PhpZip\Exception\ZipException;
|
|||
use PhpZip\ZipFile;
|
||||
use RecursiveDirectoryIterator;
|
||||
use RecursiveIteratorIterator;
|
||||
use Symfony\Component\VarExporter\VarExporter;
|
||||
use think\Cache;
|
||||
use think\Db;
|
||||
use think\Exception;
|
||||
|
|
@ -408,7 +407,7 @@ EOD;
|
|||
throw new Exception(__("Unable to open file '%s' for writing", "addons.php"));
|
||||
}
|
||||
|
||||
file_put_contents($file, "<?php\n\n" . "return " . VarExporter::export($config) . ";\n", LOCK_EX);
|
||||
file_put_contents($file, "<?php\n\n" . "return " . var_export_short($config, true) . ";\n", LOCK_EX);
|
||||
return true;
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -10,7 +10,6 @@
|
|||
// | Author: Byron Sampson <xiaobo.sun@qq.com>
|
||||
// +----------------------------------------------------------------------
|
||||
|
||||
use Symfony\Component\VarExporter\VarExporter;
|
||||
use think\addons\Service;
|
||||
use think\App;
|
||||
use think\Cache;
|
||||
|
|
@ -476,6 +475,7 @@ function set_addon_info($name, $array)
|
|||
$res[] = "$key = " . (is_numeric($val) ? $val : $val);
|
||||
}
|
||||
}
|
||||
Hook::listen($name . '_info_before_write', $array);
|
||||
if (file_put_contents($file, implode("\n", $res) . "\n", LOCK_EX)) {
|
||||
//清空当前配置缓存
|
||||
Config::set($name, null, 'addoninfo');
|
||||
|
|
@ -521,8 +521,15 @@ function set_addon_config($name, $config, $writefile = true)
|
|||
*/
|
||||
function set_addon_fullconfig($name, $array)
|
||||
{
|
||||
$config = [];
|
||||
if (is_array($array)) {
|
||||
foreach ($array as $key => $value) {
|
||||
$config[$value['name']] = $value['value'];
|
||||
}
|
||||
}
|
||||
Hook::listen($name . '_config_before_write', $config, $array);
|
||||
$file = ADDON_PATH . $name . DS . 'config.php';
|
||||
$ret = file_put_contents($file, "<?php\n\n" . "return " . VarExporter::export($array) . ";\n", LOCK_EX);
|
||||
$ret = file_put_contents($file, "<?php\n\n" . "return " . var_export_short($array, true) . ";\n", LOCK_EX);
|
||||
if (!$ret) {
|
||||
throw new Exception("配置写入失败");
|
||||
}
|
||||
|
|
|
|||
|
|
@ -2,6 +2,25 @@
|
|||
|
||||
Please refer to [UPGRADING](UPGRADING.md) guide for upgrading to a major version.
|
||||
|
||||
## 7.10.0 - 2025-08-23
|
||||
|
||||
### Added
|
||||
|
||||
- Support for PHP 8.5
|
||||
|
||||
### Changed
|
||||
|
||||
- Adjusted `guzzlehttp/promises` version constraint to `^2.3`
|
||||
- Adjusted `guzzlehttp/psr7` version constraint to `^2.8`
|
||||
|
||||
|
||||
## 7.9.3 - 2025-03-27
|
||||
|
||||
### Changed
|
||||
|
||||
- Remove explicit content-length header for GET requests
|
||||
- Improve compatibility with bad servers for boolean cookie values
|
||||
|
||||
|
||||
## 7.9.2 - 2024-07-24
|
||||
|
||||
|
|
|
|||
|
|
@ -81,8 +81,8 @@
|
|||
"require": {
|
||||
"php": "^7.2.5 || ^8.0",
|
||||
"ext-json": "*",
|
||||
"guzzlehttp/promises": "^1.5.3 || ^2.0.3",
|
||||
"guzzlehttp/psr7": "^2.7.0",
|
||||
"guzzlehttp/promises": "^2.3",
|
||||
"guzzlehttp/psr7": "^2.8",
|
||||
"psr/http-client": "^1.0",
|
||||
"symfony/deprecation-contracts": "^2.2 || ^3.0"
|
||||
},
|
||||
|
|
|
|||
|
|
@ -62,6 +62,10 @@ class SetCookie
|
|||
if (is_numeric($value)) {
|
||||
$data[$search] = (int) $value;
|
||||
}
|
||||
} elseif ($search === 'Secure' || $search === 'Discard' || $search === 'HttpOnly') {
|
||||
if ($value) {
|
||||
$data[$search] = true;
|
||||
}
|
||||
} else {
|
||||
$data[$search] = $value;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -125,7 +125,9 @@ class CurlFactory implements CurlFactoryInterface
|
|||
unset($easy->handle);
|
||||
|
||||
if (\count($this->handles) >= $this->maxHandles) {
|
||||
\curl_close($resource);
|
||||
if (PHP_VERSION_ID < 80000) {
|
||||
\curl_close($resource);
|
||||
}
|
||||
} else {
|
||||
// Remove all callback functions as they can hold onto references
|
||||
// and are not cleaned up by curl_reset. Using curl_setopt_array
|
||||
|
|
@ -729,7 +731,10 @@ class CurlFactory implements CurlFactoryInterface
|
|||
public function __destruct()
|
||||
{
|
||||
foreach ($this->handles as $id => $handle) {
|
||||
\curl_close($handle);
|
||||
if (PHP_VERSION_ID < 80000) {
|
||||
\curl_close($handle);
|
||||
}
|
||||
|
||||
unset($this->handles[$id]);
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -240,7 +240,10 @@ class CurlMultiHandler
|
|||
$handle = $this->handles[$id]['easy']->handle;
|
||||
unset($this->delays[$id], $this->handles[$id]);
|
||||
\curl_multi_remove_handle($this->_mh, $handle);
|
||||
\curl_close($handle);
|
||||
|
||||
if (PHP_VERSION_ID < 80000) {
|
||||
\curl_close($handle);
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -17,10 +17,10 @@ class Proxy
|
|||
* Sends synchronous requests to a specific handler while sending all other
|
||||
* requests to another handler.
|
||||
*
|
||||
* @param callable(\Psr\Http\Message\RequestInterface, array): \GuzzleHttp\Promise\PromiseInterface $default Handler used for normal responses
|
||||
* @param callable(\Psr\Http\Message\RequestInterface, array): \GuzzleHttp\Promise\PromiseInterface $sync Handler used for synchronous responses.
|
||||
* @param callable(RequestInterface, array): PromiseInterface $default Handler used for normal responses
|
||||
* @param callable(RequestInterface, array): PromiseInterface $sync Handler used for synchronous responses.
|
||||
*
|
||||
* @return callable(\Psr\Http\Message\RequestInterface, array): \GuzzleHttp\Promise\PromiseInterface Returns the composed handler.
|
||||
* @return callable(RequestInterface, array): PromiseInterface Returns the composed handler.
|
||||
*/
|
||||
public static function wrapSync(callable $default, callable $sync): callable
|
||||
{
|
||||
|
|
@ -37,10 +37,10 @@ class Proxy
|
|||
* performance benefits of curl while still supporting true streaming
|
||||
* through the StreamHandler.
|
||||
*
|
||||
* @param callable(\Psr\Http\Message\RequestInterface, array): \GuzzleHttp\Promise\PromiseInterface $default Handler used for non-streaming responses
|
||||
* @param callable(\Psr\Http\Message\RequestInterface, array): \GuzzleHttp\Promise\PromiseInterface $streaming Handler used for streaming responses
|
||||
* @param callable(RequestInterface, array): PromiseInterface $default Handler used for non-streaming responses
|
||||
* @param callable(RequestInterface, array): PromiseInterface $streaming Handler used for streaming responses
|
||||
*
|
||||
* @return callable(\Psr\Http\Message\RequestInterface, array): \GuzzleHttp\Promise\PromiseInterface Returns the composed handler.
|
||||
* @return callable(RequestInterface, array): PromiseInterface Returns the composed handler.
|
||||
*/
|
||||
public static function wrapStreaming(callable $default, callable $streaming): callable
|
||||
{
|
||||
|
|
|
|||
|
|
@ -53,8 +53,14 @@ class StreamHandler
|
|||
$request = $request->withoutHeader('Expect');
|
||||
|
||||
// Append a content-length header if body size is zero to match
|
||||
// cURL's behavior.
|
||||
if (0 === $request->getBody()->getSize()) {
|
||||
// the behavior of `CurlHandler`
|
||||
if (
|
||||
(
|
||||
0 === \strcasecmp('PUT', $request->getMethod())
|
||||
|| 0 === \strcasecmp('POST', $request->getMethod())
|
||||
)
|
||||
&& 0 === $request->getBody()->getSize()
|
||||
) {
|
||||
$request = $request->withHeader('Content-Length', '0');
|
||||
}
|
||||
|
||||
|
|
@ -327,8 +333,15 @@ class StreamHandler
|
|||
);
|
||||
|
||||
return $this->createResource(
|
||||
function () use ($uri, &$http_response_header, $contextResource, $context, $options, $request) {
|
||||
function () use ($uri, $contextResource, $context, $options, $request) {
|
||||
$resource = @\fopen((string) $uri, 'r', false, $contextResource);
|
||||
|
||||
// See https://wiki.php.net/rfc/deprecations_php_8_5#deprecate_the_http_response_header_predefined_variable
|
||||
if (function_exists('http_get_last_response_headers')) {
|
||||
/** @var array|null */
|
||||
$http_response_header = \http_get_last_response_headers();
|
||||
}
|
||||
|
||||
$this->lastHeaders = $http_response_header ?? [];
|
||||
|
||||
if (false === $resource) {
|
||||
|
|
|
|||
|
|
@ -187,12 +187,12 @@ final class Middleware
|
|||
* Middleware that logs requests, responses, and errors using a message
|
||||
* formatter.
|
||||
*
|
||||
* @phpstan-param \Psr\Log\LogLevel::* $logLevel Level at which to log requests.
|
||||
*
|
||||
* @param LoggerInterface $logger Logs messages.
|
||||
* @param MessageFormatterInterface|MessageFormatter $formatter Formatter used to create message strings.
|
||||
* @param string $logLevel Level at which to log requests.
|
||||
*
|
||||
* @phpstan-param \Psr\Log\LogLevel::* $logLevel Level at which to log requests.
|
||||
*
|
||||
* @return callable Returns a function that accepts the next handler.
|
||||
*/
|
||||
public static function log(LoggerInterface $logger, $formatter, string $logLevel = 'info'): callable
|
||||
|
|
|
|||
|
|
@ -86,7 +86,7 @@ class Pool implements PromisorInterface
|
|||
* @param ClientInterface $client Client used to send the requests
|
||||
* @param array|\Iterator $requests Requests to send concurrently.
|
||||
* @param array $options Passes through the options available in
|
||||
* {@see \GuzzleHttp\Pool::__construct}
|
||||
* {@see Pool::__construct}
|
||||
*
|
||||
* @return array Returns an array containing the response or an exception
|
||||
* in the same order that the requests were sent.
|
||||
|
|
|
|||
|
|
@ -79,7 +79,7 @@ final class Utils
|
|||
*
|
||||
* The returned handler is not wrapped by any default middlewares.
|
||||
*
|
||||
* @return callable(\Psr\Http\Message\RequestInterface, array): \GuzzleHttp\Promise\PromiseInterface Returns the best handler for the given system.
|
||||
* @return callable(\Psr\Http\Message\RequestInterface, array): Promise\PromiseInterface Returns the best handler for the given system.
|
||||
*
|
||||
* @throws \RuntimeException if no viable Handler is available.
|
||||
*/
|
||||
|
|
|
|||
|
|
@ -50,7 +50,7 @@ function debug_resource($value = null)
|
|||
*
|
||||
* The returned handler is not wrapped by any default middlewares.
|
||||
*
|
||||
* @return callable(\Psr\Http\Message\RequestInterface, array): \GuzzleHttp\Promise\PromiseInterface Returns the best handler for the given system.
|
||||
* @return callable(\Psr\Http\Message\RequestInterface, array): Promise\PromiseInterface Returns the best handler for the given system.
|
||||
*
|
||||
* @throws \RuntimeException if no viable Handler is available.
|
||||
*
|
||||
|
|
|
|||
22
xunifriend_RaeeC/vendor/maennchen/zipstream-php/.editorconfig
vendored
Normal file
22
xunifriend_RaeeC/vendor/maennchen/zipstream-php/.editorconfig
vendored
Normal file
|
|
@ -0,0 +1,22 @@
|
|||
root = true
|
||||
|
||||
[*]
|
||||
end_of_line = lf
|
||||
insert_final_newline = true
|
||||
charset = utf-8
|
||||
|
||||
[*.{yml,md,xml}]
|
||||
indent_style = space
|
||||
indent_size = 2
|
||||
|
||||
[*.{rst,php}]
|
||||
indent_style = space
|
||||
indent_size = 4
|
||||
|
||||
[composer.json]
|
||||
indent_style = space
|
||||
indent_size = 2
|
||||
|
||||
[composer.lock]
|
||||
indent_style = space
|
||||
indent_size = 4
|
||||
|
|
@ -1,4 +1,4 @@
|
|||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<phive xmlns="https://phar.io/phive">
|
||||
<phar name="phpdocumentor" version="^3.3.1" installed="3.3.1" location="./tools/phpdocumentor" copy="false"/>
|
||||
<phar name="phpdocumentor" version="^3.3.1" installed="3.8.0" location="./tools/phpdocumentor" copy="false"/>
|
||||
</phive>
|
||||
|
|
|
|||
|
|
@ -13,6 +13,7 @@ declare(strict_types=1);
|
|||
|
||||
use PhpCsFixer\Config;
|
||||
use PhpCsFixer\Finder;
|
||||
use PhpCsFixer\Runner;
|
||||
|
||||
$finder = Finder::create()
|
||||
->exclude('.github')
|
||||
|
|
@ -26,7 +27,9 @@ $config = new Config();
|
|||
return $config->setRules([
|
||||
'@PER' => true,
|
||||
'@PER:risky' => true,
|
||||
'@PHP81Migration' => true,
|
||||
'@PHP83Migration' => true,
|
||||
// Enable once PHP 8.4 is the minimum version
|
||||
// '@PHP84Migration' => true,
|
||||
'@PHPUnit84Migration:risky' => true,
|
||||
'array_syntax' => ['syntax' => 'short'],
|
||||
'class_attributes_separation' => true,
|
||||
|
|
@ -50,7 +53,6 @@ return $config->setRules([
|
|||
'semicolon_after_instruction' => true,
|
||||
'short_scalar_cast' => true,
|
||||
'simplified_null_return' => true,
|
||||
'single_blank_line_before_namespace' => true,
|
||||
'single_class_element_per_statement' => true,
|
||||
'single_line_comment_style' => true,
|
||||
'single_quote' => true,
|
||||
|
|
@ -68,4 +70,6 @@ return $config->setRules([
|
|||
],
|
||||
])
|
||||
->setFinder($finder)
|
||||
->setRiskyAllowed(true);
|
||||
->setRiskyAllowed(true)
|
||||
->setUnsupportedPhpVersionAllowed(true)
|
||||
->setParallelConfig(Runner\Parallel\ParallelConfigFactory::detect());
|
||||
|
|
|
|||
|
|
@ -9,7 +9,7 @@
|
|||
"social": [
|
||||
{ "iconClass": "fab fa-github", "url": "https://github.com/maennchen/ZipStream-PHP"},
|
||||
{ "iconClass": "fas fa-envelope-open-text", "url": "https://github.com/maennchen/ZipStream-PHP/discussions"},
|
||||
{ "iconClass": "fas fa-money-bill", "url": "https://opencollective.com/zipstream"},
|
||||
{ "iconClass": "fas fa-money-bill", "url": "https://github.com/sponsors/maennchen"},
|
||||
]
|
||||
}
|
||||
%}
|
||||
|
|
@ -1 +1 @@
|
|||
php 8.1.13
|
||||
php 8.5.0
|
||||
|
|
|
|||
|
|
@ -4,7 +4,8 @@
|
|||
[](https://coveralls.io/github/maennchen/ZipStream-PHP?branch=main)
|
||||
[](https://packagist.org/packages/maennchen/zipstream-php)
|
||||
[](https://packagist.org/packages/maennchen/zipstream-php)
|
||||
[](https://opencollective.com/zipstream) [](LICENSE)
|
||||
[](https://www.bestpractices.dev/projects/9524)
|
||||
[](https://scorecard.dev/viewer/?uri=github.com/maennchen/ZipStream-PHP)
|
||||
|
||||
## Unstable Branch
|
||||
|
||||
|
|
@ -14,13 +15,17 @@ version.
|
|||
|
||||
## Overview
|
||||
|
||||
A fast and simple streaming zip file downloader for PHP. Using this library will save you from having to write the Zip to disk. You can directly send it to the user, which is much faster. It can work with S3 buckets or any PSR7 Stream.
|
||||
A fast and simple streaming zip file downloader for PHP. Using this library will
|
||||
save you from having to write the Zip to disk. You can directly send it to the
|
||||
user, which is much faster. It can work with S3 buckets or any PSR7 Stream.
|
||||
|
||||
Please see the [LICENSE](LICENSE) file for licensing and warranty information.
|
||||
|
||||
## Installation
|
||||
|
||||
Simply add a dependency on maennchen/zipstream-php to your project's composer.json file if you use Composer to manage the dependencies of your project. Use following command to add the package to your project's dependencies:
|
||||
Simply add a dependency on maennchen/zipstream-php to your project's
|
||||
`composer.json` file if you use Composer to manage the dependencies of your
|
||||
project. Use following command to add the package to your project's dependencies:
|
||||
|
||||
```bash
|
||||
composer require maennchen/zipstream-php
|
||||
|
|
@ -29,51 +34,164 @@ composer require maennchen/zipstream-php
|
|||
## Usage
|
||||
|
||||
For detailed instructions, please check the
|
||||
[Documentation](https://maennchen.dev/ZipStream-PHP/).
|
||||
|
||||
Here's a simple example:
|
||||
[Documentation](https://maennchen.github.io/ZipStream-PHP/).
|
||||
|
||||
```php
|
||||
// Autoload the dependencies
|
||||
require 'vendor/autoload.php';
|
||||
|
||||
// enable output of HTTP headers
|
||||
$options = new ZipStream\Option\Archive();
|
||||
$options->setSendHttpHeaders(true);
|
||||
|
||||
// create a new zipstream object
|
||||
$zip = new ZipStream\ZipStream('example.zip', $options);
|
||||
$zip = new ZipStream\ZipStream(
|
||||
outputName: 'example.zip',
|
||||
|
||||
// enable output of HTTP headers
|
||||
sendHttpHeaders: true,
|
||||
);
|
||||
|
||||
// create a file named 'hello.txt'
|
||||
$zip->addFile('hello.txt', 'This is the contents of hello.txt');
|
||||
$zip->addFile(
|
||||
fileName: 'hello.txt',
|
||||
data: 'This is the contents of hello.txt',
|
||||
);
|
||||
|
||||
// add a file named 'some_image.jpg' from a local file 'path/to/image.jpg'
|
||||
$zip->addFileFromPath('some_image.jpg', 'path/to/image.jpg');
|
||||
$zip->addFileFromPath(
|
||||
fileName: 'some_image.jpg',
|
||||
path: 'path/to/image.jpg',
|
||||
);
|
||||
|
||||
// finish the zip stream
|
||||
$zip->finish();
|
||||
```
|
||||
|
||||
### Callback Output
|
||||
|
||||
You can stream ZIP data to a custom callback function instead of directly to the browser:
|
||||
|
||||
```php
|
||||
use ZipStream\ZipStream;
|
||||
use ZipStream\Stream\CallbackStreamWrapper;
|
||||
|
||||
// Stream to a callback function with proper file handling
|
||||
$outputFile = fopen('output.zip', 'wb');
|
||||
$backupFile = fopen('backup.zip', 'wb');
|
||||
|
||||
$zip = new ZipStream(
|
||||
outputStream: CallbackStreamWrapper::open(function (string $data) use ($outputFile, $backupFile) {
|
||||
// Handle ZIP data as it's generated
|
||||
fwrite($outputFile, $data);
|
||||
|
||||
// Send to multiple destinations efficiently
|
||||
echo $data; // Browser
|
||||
fwrite($backupFile, $data); // Backup file
|
||||
}),
|
||||
sendHttpHeaders: false,
|
||||
);
|
||||
|
||||
$zip->addFile('hello.txt', 'Hello World!');
|
||||
$zip->finish();
|
||||
|
||||
// Clean up resources
|
||||
fclose($outputFile);
|
||||
fclose($backupFile);
|
||||
```
|
||||
|
||||
## Questions
|
||||
|
||||
**💬 Questions? Please Read This First!**
|
||||
|
||||
If you have a question about using this library, please *do not email the
|
||||
authors directly*. Instead, head over to the
|
||||
[GitHub Discussions](https://github.com/maennchen/ZipStream-PHP/discussions)
|
||||
page — your question might already be answered there! Using Discussions helps
|
||||
build a shared knowledge base, so others can also benefit from the answers. If
|
||||
you need dedicated 1:1 support, check out the options available on
|
||||
[@maennchen's sponsorship page](https://github.com/sponsors/maennchen?frequency=one-time&sponsor=maennchen).
|
||||
|
||||
## Upgrade to version 3.1.2
|
||||
|
||||
- Minimum PHP Version: `8.2`
|
||||
|
||||
## Upgrade to version 3.0.0
|
||||
|
||||
### General
|
||||
|
||||
- Minimum PHP Version: `8.1`
|
||||
- Only 64bit Architecture is supported.
|
||||
- The class `ZipStream\Option\Method` has been replaced with the enum
|
||||
`ZipStream\CompressionMethod`.
|
||||
- Most classes have been flagged as `@internal` and should not be used from the
|
||||
outside.
|
||||
If you're using internal resources to extend this library, please open an
|
||||
issue so that a clean interface can be added & published.
|
||||
The externally available classes & enums are:
|
||||
- `ZipStream\CompressionMethod`
|
||||
- `ZipStream\Exception*`
|
||||
- `ZipStream\ZipStream`
|
||||
|
||||
### Archive Options
|
||||
|
||||
- The class `ZipStream\Option\Archive` has been replaced in favor of named
|
||||
arguments in the `ZipStream\ZipStream` constructor.
|
||||
- The archive options `largeFileSize` & `largeFileMethod` has been removed. If
|
||||
you want different `compressionMethods` based on the file size, you'll have to
|
||||
implement this yourself.
|
||||
- The archive option `httpHeaderCallback` changed the type from `callable` to
|
||||
`Closure`.
|
||||
- The archive option `zeroHeader` has been replaced with the option
|
||||
`defaultEnableZeroHeader` and can be overridden for every file. Its default
|
||||
value changed from `false` to `true`.
|
||||
- The archive option `statFiles` was removed since the library no longer checks
|
||||
filesizes this way.
|
||||
- The archive option `deflateLevel` has been replaced with the option
|
||||
`defaultDeflateLevel` and can be overridden for every file.
|
||||
- The first argument (`name`) of the `ZipStream\ZipStream` constructor has been
|
||||
replaced with the named argument `outputName`.
|
||||
- Headers are now also sent if the `outputName` is empty. If you do not want to
|
||||
automatically send http headers, set `sendHttpHeaders` to `false`.
|
||||
|
||||
### File Options
|
||||
|
||||
- The class `ZipStream\Option\File` has been replaced in favor of named
|
||||
arguments in the `ZipStream\ZipStream->addFile*` functions.
|
||||
- The file option `method` has been renamed to `compressionMethod`.
|
||||
- The file option `time` has been renamed to `lastModificationDateTime`.
|
||||
- The file option `size` has been renamed to `maxSize`.
|
||||
|
||||
## Upgrade to version 2.0.0
|
||||
|
||||
- Only the self opened streams will be closed (#139)
|
||||
If you were relying on ZipStream to close streams that the library didn't open,
|
||||
you'll need to close them yourself now.
|
||||
https://github.com/maennchen/ZipStream-PHP/tree/2.0.0#upgrade-to-version-200
|
||||
|
||||
## Upgrade to version 1.0.0
|
||||
|
||||
- All options parameters to all function have been moved from an `array` to structured option objects. See [the wiki](https://github.com/maennchen/ZipStream-PHP/wiki/Available-options) for examples.
|
||||
- The whole library has been refactored. The minimal PHP requirement has been raised to PHP 7.1.
|
||||
|
||||
## Usage with Symfony and S3
|
||||
|
||||
You can find example code on [the wiki](https://github.com/maennchen/ZipStream-PHP/wiki/Symfony-example).
|
||||
https://github.com/maennchen/ZipStream-PHP/tree/2.0.0#upgrade-to-version-100
|
||||
|
||||
## Contributing
|
||||
|
||||
ZipStream-PHP is a collaborative project. Please take a look at the
|
||||
[.github/CONTRIBUTING.md](.github/CONTRIBUTING.md) file.
|
||||
|
||||
## Version Support
|
||||
|
||||
Versions are supported according to the table below.
|
||||
|
||||
Please do not open any pull requests contradicting the current version support
|
||||
status.
|
||||
|
||||
Careful: Always check the `README` on `main` for up-to-date information.
|
||||
|
||||
| Version | New Features | Bugfixes | Security |
|
||||
|---------|--------------|----------|----------|
|
||||
| *3* | ✓ | ✓ | ✓ |
|
||||
| *2* | ✗ | ✗ | ✓ |
|
||||
| *1* | ✗ | ✗ | ✗ |
|
||||
| *0* | ✗ | ✗ | ✗ |
|
||||
|
||||
This library aligns itself with the PHP core support. New features and bugfixes
|
||||
will only target PHP versions according to their current status.
|
||||
|
||||
See: https://www.php.net/supported-versions.php
|
||||
|
||||
## About the Authors
|
||||
|
||||
- Paul Duncan <pabs@pablotron.org> - https://pablotron.org/
|
||||
|
|
@ -81,34 +199,3 @@ ZipStream-PHP is a collaborative project. Please take a look at the
|
|||
- Jesse G. Donat <donatj@gmail.com> - https://donatstudios.com
|
||||
- Nicolas CARPi <nico-git@deltablot.email> - https://www.deltablot.com
|
||||
- Nik Barham <nik@brokencube.co.uk> - https://www.brokencube.co.uk
|
||||
|
||||
## Contributors
|
||||
|
||||
### Code Contributors
|
||||
|
||||
This project exists thanks to all the people who contribute.
|
||||
[[Contribute](.github/CONTRIBUTING.md)].
|
||||
<a href="https://github.com/maennchen/ZipStream-PHP/graphs/contributors"><img src="https://opencollective.com/zipstream/contributors.svg?width=890&button=false" /></a>
|
||||
|
||||
### Financial Contributors
|
||||
|
||||
Become a financial contributor and help us sustain our community. [[Contribute](https://opencollective.com/zipstream/contribute)]
|
||||
|
||||
#### Individuals
|
||||
|
||||
<a href="https://opencollective.com/zipstream"><img src="https://opencollective.com/zipstream/individuals.svg?width=890"></a>
|
||||
|
||||
#### Organizations
|
||||
|
||||
Support this project with your organization. Your logo will show up here with a link to your website. [[Contribute](https://opencollective.com/zipstream/contribute)]
|
||||
|
||||
<a href="https://opencollective.com/zipstream/organization/0/website"><img src="https://opencollective.com/zipstream/organization/0/avatar.svg"></a>
|
||||
<a href="https://opencollective.com/zipstream/organization/1/website"><img src="https://opencollective.com/zipstream/organization/1/avatar.svg"></a>
|
||||
<a href="https://opencollective.com/zipstream/organization/2/website"><img src="https://opencollective.com/zipstream/organization/2/avatar.svg"></a>
|
||||
<a href="https://opencollective.com/zipstream/organization/3/website"><img src="https://opencollective.com/zipstream/organization/3/avatar.svg"></a>
|
||||
<a href="https://opencollective.com/zipstream/organization/4/website"><img src="https://opencollective.com/zipstream/organization/4/avatar.svg"></a>
|
||||
<a href="https://opencollective.com/zipstream/organization/5/website"><img src="https://opencollective.com/zipstream/organization/5/avatar.svg"></a>
|
||||
<a href="https://opencollective.com/zipstream/organization/6/website"><img src="https://opencollective.com/zipstream/organization/6/avatar.svg"></a>
|
||||
<a href="https://opencollective.com/zipstream/organization/7/website"><img src="https://opencollective.com/zipstream/organization/7/avatar.svg"></a>
|
||||
<a href="https://opencollective.com/zipstream/organization/8/website"><img src="https://opencollective.com/zipstream/organization/8/avatar.svg"></a>
|
||||
<a href="https://opencollective.com/zipstream/organization/9/website"><img src="https://opencollective.com/zipstream/organization/9/avatar.svg"></a>
|
||||
|
|
|
|||
|
|
@ -22,28 +22,42 @@
|
|||
}
|
||||
],
|
||||
"require": {
|
||||
"php": "^7.4 || ^8.0",
|
||||
"symfony/polyfill-mbstring": "^1.0",
|
||||
"psr/http-message": "^1.0",
|
||||
"myclabs/php-enum": "^1.5"
|
||||
"php-64bit": "^8.3",
|
||||
"ext-mbstring": "*",
|
||||
"ext-zlib": "*"
|
||||
},
|
||||
"require-dev": {
|
||||
"phpunit/phpunit": "^8.5.8 || ^9.4.2",
|
||||
"guzzlehttp/guzzle": "^6.5.3 || ^7.2.0",
|
||||
"phpunit/phpunit": "^12.0",
|
||||
"guzzlehttp/guzzle": "^7.5",
|
||||
"ext-zip": "*",
|
||||
"mikey179/vfsstream": "^1.6",
|
||||
"vimeo/psalm": "^4.1",
|
||||
"php-coveralls/php-coveralls": "^2.4",
|
||||
"friendsofphp/php-cs-fixer": "^3.9"
|
||||
"php-coveralls/php-coveralls": "^2.5",
|
||||
"friendsofphp/php-cs-fixer": "^3.86",
|
||||
"vimeo/psalm": "^6.0",
|
||||
"brianium/paratest": "^7.7"
|
||||
},
|
||||
"suggest": {
|
||||
"psr/http-message": "^2.0",
|
||||
"guzzlehttp/psr7": "^2.4"
|
||||
},
|
||||
"scripts": {
|
||||
"format": "php-cs-fixer fix",
|
||||
"test": "composer run test:unit && composer run test:formatted && composer run test:lint",
|
||||
"test:unit": "phpunit --coverage-clover=coverage.clover.xml",
|
||||
"test:formatted": "composer run format -- --dry-run --stop-on-violation --using-cache=no",
|
||||
"test:lint": "psalm --stats --show-info --find-unused-psalm-suppress",
|
||||
"coverage:report": "php-coveralls --coverage_clover=coverage.clover.xml --json_path=coveralls-upload.json -v",
|
||||
"install:tools": "phive install --trust-gpg-keys 0x67F861C3D889C656",
|
||||
"test": [
|
||||
"@test:unit",
|
||||
"@test:formatted",
|
||||
"@test:lint"
|
||||
],
|
||||
"test:unit:setup-cov": "@putenv XDEBUG_MODE=coverage",
|
||||
"test:unit": "paratest --functional",
|
||||
"test:unit:cov": ["@test:unit:setup-cov", "@test:unit --coverage-clover=coverage.clover.xml --coverage-html cov"],
|
||||
"test:unit:slow": "@test:unit --group slow",
|
||||
"test:unit:slow:cov": ["@test:unit:setup-cov", "@test:unit --coverage-clover=coverage.clover.xml --coverage-html cov --group slow"],
|
||||
"test:unit:fast": "@test:unit --exclude-group slow",
|
||||
"test:unit:fast:cov": ["@test:unit:setup-cov", "@test:unit --coverage-clover=coverage.clover.xml --coverage-html cov --exclude-group slow"],
|
||||
"test:formatted": "@format --dry-run --stop-on-violation --using-cache=no",
|
||||
"test:lint": "psalm --stats --show-info=true --find-unused-psalm-suppress",
|
||||
"coverage:report": "php-coveralls --coverage_clover=coverage.clover.xml --json_path=coveralls-upload.json --insecure",
|
||||
"install:tools": "phive install --trust-gpg-keys 0x67F861C3D889C656 --trust-gpg-keys 0x8AC0BAA79732DD42 --trust-gpg-keys 0x6DA3ACC4991FFAE5",
|
||||
"docs:generate": "tools/phpdocumentor --sourcecode"
|
||||
},
|
||||
"autoload": {
|
||||
|
|
@ -51,6 +65,9 @@
|
|||
"ZipStream\\": "src/"
|
||||
}
|
||||
},
|
||||
"autoload-dev": {
|
||||
"psr-4": { "ZipStream\\Test\\": "test/" }
|
||||
},
|
||||
"archive": {
|
||||
"exclude": [
|
||||
"/composer.lock",
|
||||
|
|
|
|||
|
|
@ -1,79 +1,47 @@
|
|||
Adding Content-Length header
|
||||
=============
|
||||
|
||||
Adding a ``Content-Length`` header for ``ZipStream`` is not trivial since the
|
||||
size is not known beforehand.
|
||||
Adding a ``Content-Length`` header for ``ZipStream`` can be achieved by
|
||||
using the options ``SIMULATION_STRICT`` or ``SIMULATION_LAX`` in the
|
||||
``operationMode`` parameter.
|
||||
|
||||
The following workaround adds an approximated header:
|
||||
In the ``SIMULATION_STRICT`` mode, ``ZipStream`` will not allow to calculate the
|
||||
size based on reading the whole file. ``SIMULATION_LAX`` will read the whole
|
||||
file if necessary.
|
||||
|
||||
``SIMULATION_STRICT`` is therefore useful to make sure that the size can be
|
||||
calculated efficiently.
|
||||
|
||||
.. code-block:: php
|
||||
use ZipStream\OperationMode;
|
||||
use ZipStream\ZipStream;
|
||||
|
||||
class Zip
|
||||
{
|
||||
/** @var string */
|
||||
private $name;
|
||||
$zip = new ZipStream(
|
||||
operationMode: OperationMode::SIMULATE_STRICT, // or SIMULATE_LAX
|
||||
defaultEnableZeroHeader: false,
|
||||
sendHttpHeaders: true,
|
||||
outputStream: $stream,
|
||||
);
|
||||
|
||||
private $files = [];
|
||||
// Normally add files
|
||||
$zip->addFile('sample.txt', 'Sample String Data');
|
||||
|
||||
public function __construct($name)
|
||||
{
|
||||
$this->name = $name;
|
||||
// Use addFileFromCallback and exactSize if you want to defer opening of
|
||||
// the file resource
|
||||
$zip->addFileFromCallback(
|
||||
'sample.txt',
|
||||
exactSize: 18,
|
||||
callback: function () {
|
||||
return fopen('...');
|
||||
}
|
||||
);
|
||||
|
||||
public function addFile($name, $data)
|
||||
{
|
||||
$this->files[] = ['type' => 'addFile', 'name' => $name, 'data' => $data];
|
||||
}
|
||||
// Read resulting file size
|
||||
$size = $zip->finish();
|
||||
|
||||
// Tell it to the browser
|
||||
header('Content-Length: '. $size);
|
||||
|
||||
// Execute the Simulation and stream the actual zip to the client
|
||||
$zip->executeSimulation();
|
||||
|
||||
public function addFileFromPath($name, $path)
|
||||
{
|
||||
$this->files[] = ['type' => 'addFileFromPath', 'name' => $name, 'path' => $path];
|
||||
}
|
||||
|
||||
public function getEstimate()
|
||||
{
|
||||
$estimate = 22;
|
||||
foreach ($this->files as $file) {
|
||||
$estimate += 76 + 2 * strlen($file['name']);
|
||||
if ($file['type'] === 'addFile') {
|
||||
$estimate += strlen($file['data']);
|
||||
}
|
||||
if ($file['type'] === 'addFileFromPath') {
|
||||
$estimate += filesize($file['path']);
|
||||
}
|
||||
}
|
||||
return $estimate;
|
||||
}
|
||||
|
||||
public function finish()
|
||||
{
|
||||
header('Content-Length: ' . $this->getEstimate());
|
||||
$options = new \ZipStream\Option\Archive();
|
||||
$options->setSendHttpHeaders(true);
|
||||
$options->setEnableZip64(false);
|
||||
$options->setDeflateLevel(-1);
|
||||
$zip = new \ZipStream\ZipStream($this->name, $options);
|
||||
|
||||
$fileOptions = new \ZipStream\Option\File();
|
||||
$fileOptions->setMethod(\ZipStream\Option\Method::STORE());
|
||||
foreach ($this->files as $file) {
|
||||
if ($file['type'] === 'addFile') {
|
||||
$zip->addFile($file['name'], $file['data'], $fileOptions);
|
||||
}
|
||||
if ($file['type'] === 'addFileFromPath') {
|
||||
$zip->addFileFromPath($file['name'], $file['path'], $fileOptions);
|
||||
}
|
||||
}
|
||||
$zip->finish();
|
||||
exit;
|
||||
}
|
||||
}
|
||||
|
||||
It only works with the following constraints:
|
||||
|
||||
- All file content is known beforehand.
|
||||
- Content Deflation is disabled
|
||||
|
||||
Thanks to
|
||||
`partiellkorrekt <https://github.com/maennchen/ZipStream-PHP/issues/89#issuecomment-1047949274>`_
|
||||
for this workaround.
|
||||
|
|
@ -14,20 +14,21 @@ default one, and pass it to Flysystem ``putStream`` method.
|
|||
// the content is lost when closing the stream / opening another one
|
||||
$tempStream = fopen('php://memory', 'w+');
|
||||
|
||||
// Init Options
|
||||
$zipStreamOptions = new Archive();
|
||||
$zipStreamOptions->setOutputStream($tempStream);
|
||||
|
||||
// Create Zip Archive
|
||||
$zipStream = new ZipStream('test.zip', $zipStreamOptions);
|
||||
$zipStream = new ZipStream(
|
||||
outputStream: $tempStream,
|
||||
outputName: 'test.zip',
|
||||
);
|
||||
$zipStream->addFile('test.txt', 'text');
|
||||
$zipStream->finish();
|
||||
|
||||
// Store File (see Flysystem documentation, and all its framework integration)
|
||||
$adapter = new Local(__DIR__.'/path/to/folder'); // Can be any adapter (AWS, Google, Ftp, etc.)
|
||||
// Store File
|
||||
// (see Flysystem documentation, and all its framework integration)
|
||||
// Can be any adapter (AWS, Google, Ftp, etc.)
|
||||
$adapter = new Local(__DIR__.'/path/to/folder');
|
||||
$filesystem = new Filesystem($adapter);
|
||||
|
||||
$filesystem->putStream('test.zip', $tempStream)
|
||||
$filesystem->writeStream('test.zip', $tempStream)
|
||||
|
||||
// Close Stream
|
||||
fclose($tempStream);
|
||||
fclose($tempStream);
|
||||
|
|
|
|||
|
|
@ -2,60 +2,68 @@ Available options
|
|||
===============
|
||||
|
||||
Here is the full list of options available to you. You can also have a look at
|
||||
``src/Option/Archive.php`` file.
|
||||
|
||||
First, an instance of ``ZipStream\Option\Archive`` needs to be created, and
|
||||
after that you use setters methods to modify the values.
|
||||
``src/ZipStream.php`` file.
|
||||
|
||||
.. code-block:: php
|
||||
|
||||
use ZipStream\ZipStream;
|
||||
use ZipStream\Option\Archive as ArchiveOptions;
|
||||
|
||||
require_once 'vendor/autoload.php';
|
||||
|
||||
$opt = new ArchiveOptions();
|
||||
$zip = new ZipStream(
|
||||
// Define output stream
|
||||
// (argument is either a resource or implementing
|
||||
// `Psr\Http\Message\StreamInterface`)
|
||||
//
|
||||
// Setup with `psr/http-message` & `guzzlehttp/psr7` dependencies
|
||||
// required when using `Psr\Http\Message\StreamInterface`.
|
||||
//
|
||||
// Can also use CallbackStreamWrapper for custom output handling:
|
||||
// outputStream: CallbackStreamWrapper::open(function($data) { /* handle data */ }),
|
||||
outputStream: $filePointer,
|
||||
|
||||
// Define output stream (argument is of type resource)
|
||||
$opt->setOutputStream($fd);
|
||||
// Set the deflate level (default is 6; use -1 to disable it)
|
||||
defaultDeflateLevel: 6,
|
||||
|
||||
// Set the deflate level (default is 6; use -1 to disable it)
|
||||
$opt->setDeflateLevel(6);
|
||||
// Add a comment to the zip file
|
||||
comment: 'This is a comment.',
|
||||
|
||||
// Add a comment to the zip file
|
||||
$opt->setComment('This is a comment.');
|
||||
// Send http headers (default is true)
|
||||
sendHttpHeaders: false,
|
||||
|
||||
// Size, in bytes, of the largest file to try and load into memory (used by addFileFromPath()). Large files may also be compressed differently; see the 'largeFileMethod' option.
|
||||
$opt->setLargeFileSize(30000000);
|
||||
// HTTP Content-Disposition.
|
||||
// Defaults to 'attachment', where FILENAME is the specified filename.
|
||||
// Note that this does nothing if you are not sending HTTP headers.
|
||||
contentDisposition: 'attachment',
|
||||
|
||||
// How to handle large files. Legal values are STORE (the default), or DEFLATE. Store sends the file raw and is significantly faster, while DEFLATE compresses the file and is much, much slower. Note that deflate must compress the file twice and is extremely slow.
|
||||
$opt->setLargeFileMethod(ZipStream\Option\Method::STORE());
|
||||
$opt->setLargeFileMethod(ZipStream\Option\Method::DEFLATE());
|
||||
// Output Name for HTTP Content-Disposition
|
||||
// Defaults to no name
|
||||
outputName: "example.zip",
|
||||
|
||||
// Send http headers (default is false)
|
||||
$opt->setSendHttpHeaders(false);
|
||||
// HTTP Content-Type.
|
||||
// Defaults to 'application/x-zip'.
|
||||
// Note that this does nothing if you are not sending HTTP headers.
|
||||
contentType: 'application/x-zip',
|
||||
|
||||
// HTTP Content-Disposition. Defaults to 'attachment', where FILENAME is the specified filename. Note that this does nothing if you are not sending HTTP headers.
|
||||
$opt->setContentDisposition('attachment');
|
||||
// Set the function called for setting headers.
|
||||
// Default is the `header()` of PHP
|
||||
httpHeaderCallback: header(...),
|
||||
|
||||
// Set the content type (does nothing if you are not sending HTTP headers)
|
||||
$opt->setContentType('application/x-zip');
|
||||
// Enable streaming files with single read where general purpose bit 3
|
||||
// indicates local file header contain zero values in crc and size
|
||||
// fields, these appear only after file contents in data descriptor
|
||||
// block.
|
||||
// Set to true if your input stream is remote
|
||||
// (used with addFileFromStream()).
|
||||
// Default is false.
|
||||
defaultEnableZeroHeader: false,
|
||||
|
||||
// Set the function called for setting headers. Default is the `header()` of PHP
|
||||
$opt->setHttpHeaderCallback('header');
|
||||
// Enable zip64 extension, allowing very large archives
|
||||
// (> 4Gb or file count > 64k)
|
||||
// Default is true
|
||||
enableZip64: true,
|
||||
|
||||
// Enable streaming files with single read where general purpose bit 3 indicates local file header contain zero values in crc and size fields, these appear only after file contents in data descriptor block. Default is false. Set to true if your input stream is remote (used with addFileFromStream()).
|
||||
$opt->setZeroHeader(false);
|
||||
|
||||
// Enable reading file stat for determining file size. When a 32-bit system reads file size that is over 2 GB, invalid value appears in file size due to integer overflow. Should be disabled on 32-bit systems with method addFileFromPath if any file may exceed 2 GB. In this case file will be read in blocks and correct size will be determined from content. Default is true.
|
||||
$opt->setStatFiles(true);
|
||||
|
||||
// Enable zip64 extension, allowing very large archives (> 4Gb or file count > 64k)
|
||||
// default is true
|
||||
$opt->setEnableZip64(true);
|
||||
|
||||
// Flush output buffer after every write
|
||||
// default is false
|
||||
$opt->setFlushOutput(true);
|
||||
|
||||
// Now that everything is set you can pass the options to the ZipStream instance
|
||||
$zip = new ZipStream('example.zip', $opt);
|
||||
// Flush output buffer after every write
|
||||
// Default is false
|
||||
flushOutput: true,
|
||||
);
|
||||
|
|
|
|||
|
|
@ -12,7 +12,10 @@ Example
|
|||
---------------
|
||||
|
||||
.. code-block:: php
|
||||
|
||||
|
||||
$stream = $response->getBody();
|
||||
// add a file named 'streamfile.txt' from the content of the stream
|
||||
$zip->addFileFromPsr7Stream('streamfile.txt', $stream);
|
||||
$zip->addFileFromPsr7Stream(
|
||||
fileName: 'streamfile.txt',
|
||||
stream: $stream,
|
||||
);
|
||||
|
|
|
|||
|
|
@ -5,9 +5,9 @@ Stream to S3 Bucket
|
|||
---------------
|
||||
|
||||
.. code-block:: php
|
||||
|
||||
use Aws\S3\S3Client;
|
||||
use Aws\Credentials\CredentialProvider;
|
||||
use ZipStream\Option\Archive;
|
||||
use ZipStream\ZipStream;
|
||||
|
||||
$bucket = 'your bucket name';
|
||||
|
|
@ -21,13 +21,93 @@ Stream to S3 Bucket
|
|||
|
||||
$zipFile = fopen("s3://$bucket/example.zip", 'w');
|
||||
|
||||
$options = new Archive();
|
||||
$options->setEnableZip64(false);
|
||||
$options->setOutputStream($zipFile);
|
||||
$zip = new ZipStream(
|
||||
enableZip64: false,
|
||||
outputStream: $zipFile,
|
||||
);
|
||||
|
||||
$zip = new ZipStream(null, $options);
|
||||
$zip->addFile('file1.txt', 'File1 data');
|
||||
$zip->addFile('file2.txt', 'File2 data');
|
||||
$zip->addFile(
|
||||
fileName: 'file1.txt',
|
||||
data: 'File1 data',
|
||||
);
|
||||
$zip->addFile(
|
||||
fileName: 'file2.txt',
|
||||
data: 'File2 data',
|
||||
);
|
||||
$zip->finish();
|
||||
|
||||
fclose($zipFile);
|
||||
fclose($zipFile);
|
||||
|
||||
Stream to Callback Function
|
||||
---------------------------
|
||||
|
||||
The CallbackStreamWrapper allows you to stream ZIP data to a custom callback function,
|
||||
enabling flexible output handling such as streaming to multiple destinations,
|
||||
progress tracking, or data transformation.
|
||||
|
||||
.. code-block:: php
|
||||
|
||||
use ZipStream\ZipStream;
|
||||
use ZipStream\Stream\CallbackStreamWrapper;
|
||||
|
||||
// Example 1: Stream to multiple destinations with proper file handling
|
||||
$backupFile = fopen('backup.zip', 'wb');
|
||||
$logFile = fopen('transfer.log', 'ab');
|
||||
|
||||
$zip = new ZipStream(
|
||||
outputStream: CallbackStreamWrapper::open(function (string $data) use ($backupFile, $logFile) {
|
||||
// Send to browser
|
||||
echo $data;
|
||||
|
||||
// Save to file efficiently
|
||||
fwrite($backupFile, $data);
|
||||
|
||||
// Log transfer progress
|
||||
fwrite($logFile, "Transferred " . strlen($data) . " bytes\n");
|
||||
}),
|
||||
sendHttpHeaders: false,
|
||||
);
|
||||
|
||||
$zip->addFile('hello.txt', 'Hello World!');
|
||||
$zip->finish();
|
||||
|
||||
// Clean up resources
|
||||
fclose($backupFile);
|
||||
fclose($logFile);
|
||||
|
||||
.. code-block:: php
|
||||
|
||||
// Example 2: Progress tracking
|
||||
$totalBytes = 0;
|
||||
$zip = new ZipStream(
|
||||
outputStream: CallbackStreamWrapper::open(function (string $data) use (&$totalBytes) {
|
||||
$totalBytes += strlen($data);
|
||||
reportProgress($totalBytes); // Report progress to your tracking system
|
||||
|
||||
// Your actual output handling
|
||||
echo $data;
|
||||
}),
|
||||
sendHttpHeaders: false,
|
||||
);
|
||||
|
||||
$zip->addFile('large_file.txt', str_repeat('A', 10000));
|
||||
$zip->finish();
|
||||
|
||||
.. code-block:: php
|
||||
|
||||
// Example 3: Data transformation using PHP stream filters
|
||||
// For data transformations, prefer PHP's built-in stream filters
|
||||
$outputStream = fopen('php://output', 'w');
|
||||
stream_filter_append($outputStream, 'convert.base64-encode');
|
||||
|
||||
$zip = new ZipStream(
|
||||
outputStream: $outputStream,
|
||||
sendHttpHeaders: false,
|
||||
);
|
||||
|
||||
$zip->addFile('secret.txt', 'Confidential data');
|
||||
$zip->finish();
|
||||
fclose($outputStream);
|
||||
|
||||
.. note::
|
||||
For data transformations, PHP's built-in stream filters are preferred over callback transformations. Stream filters operate at the stream level and maintain data integrity. You can register custom filters using ``stream_filter_register()`` for specialized transformations.
|
||||
|
|
|
|||
|
|
@ -31,7 +31,7 @@ stored in an AWS S3 bucket by key:
|
|||
*/
|
||||
public function zipStreamAction()
|
||||
{
|
||||
//sample test file on s3
|
||||
// sample test file on s3
|
||||
$s3keys = array(
|
||||
"ziptestfolder/file1.txt"
|
||||
);
|
||||
|
|
@ -39,18 +39,18 @@ stored in an AWS S3 bucket by key:
|
|||
$s3Client = $this->get('app.amazon.s3'); //s3client service
|
||||
$s3Client->registerStreamWrapper(); //required
|
||||
|
||||
//using StreamedResponse to wrap ZipStream functionality for files on AWS s3.
|
||||
// using StreamedResponse to wrap ZipStream functionality
|
||||
// for files on AWS s3.
|
||||
$response = new StreamedResponse(function() use($s3keys, $s3Client)
|
||||
{
|
||||
// Define suitable options for ZipStream Archive.
|
||||
$options = new \ZipStream\Option\Archive();
|
||||
$options->setContentType('application/octet-stream');
|
||||
// this is needed to prevent issues with truncated zip files
|
||||
$options->setZeroHeader(true);
|
||||
$options->setComment('test zip file.');
|
||||
|
||||
//initialise zipstream with output zip filename and options.
|
||||
$zip = new ZipStream\ZipStream('test.zip', $options);
|
||||
$zip = new ZipStream\ZipStream(
|
||||
outputName: 'test.zip',
|
||||
defaultEnableZeroHeader: true,
|
||||
contentType: 'application/octet-stream',
|
||||
);
|
||||
|
||||
//loop keys - useful for multiple files
|
||||
foreach ($s3keys as $key) {
|
||||
|
|
@ -58,15 +58,19 @@ stored in an AWS S3 bucket by key:
|
|||
//file using the same name.
|
||||
$fileName = basename($key);
|
||||
|
||||
//concatenate s3path.
|
||||
$bucket = 'bucketname'; //replace with your bucket name or get from parameters file.
|
||||
// concatenate s3path.
|
||||
// replace with your bucket name or get from parameters file.
|
||||
$bucket = 'bucketname';
|
||||
$s3path = "s3://" . $bucket . "/" . $key;
|
||||
|
||||
//addFileFromStream
|
||||
if ($streamRead = fopen($s3path, 'r')) {
|
||||
$zip->addFileFromStream($fileName, $streamRead);
|
||||
$zip->addFileFromStream(
|
||||
fileName: $fileName,
|
||||
stream: $streamRead,
|
||||
);
|
||||
} else {
|
||||
die('Could not open stream for reading');
|
||||
die('Could not open stream for reading');
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -123,4 +127,4 @@ You need to add correct permissions
|
|||
's3' => ['ACL' => 'public-read'],
|
||||
]);
|
||||
|
||||
fopen($path, 'w', null, $outputContext);
|
||||
fopen($path, 'w', null, $outputContext);
|
||||
|
|
|
|||
|
|
@ -22,11 +22,35 @@ Installation
|
|||
|
||||
Simply add a dependency on ``maennchen/zipstream-php`` to your project's
|
||||
``composer.json`` file if you use Composer to manage the dependencies of your
|
||||
project. Use following command to add the package to your project's dependencies:
|
||||
project. Use following command to add the package to your project's
|
||||
dependencies:
|
||||
|
||||
.. code-block:: sh
|
||||
composer require maennchen/zipstream-php
|
||||
|
||||
If you want to use``addFileFromPsr7Stream```
|
||||
(``Psr\Http\Message\StreamInterface``) or use a stream instead of a
|
||||
``resource`` as ``outputStream``, the following dependencies must be installed
|
||||
as well:
|
||||
|
||||
.. code-block:: sh
|
||||
composer require psr/http-message guzzlehttp/psr7
|
||||
|
||||
If ``composer install`` yields the following error, your installation is missing
|
||||
the `mbstring extension <https://www.php.net/manual/en/book.mbstring.php>`_,
|
||||
either `install it <https://www.php.net/manual/en/mbstring.installation.php>`_
|
||||
or run the following command:
|
||||
|
||||
.. code-block::
|
||||
Your requirements could not be resolved to an installable set of packages.
|
||||
|
||||
Problem 1
|
||||
- Root composer.json requires PHP extension ext-mbstring * but it is
|
||||
missing from your system. Install or enable PHP's mbstrings extension.
|
||||
|
||||
.. code-block:: sh
|
||||
composer require symfony/polyfill-mbstring
|
||||
|
||||
Usage Intro
|
||||
---------------
|
||||
|
||||
|
|
@ -37,25 +61,42 @@ Here's a simple example:
|
|||
// Autoload the dependencies
|
||||
require 'vendor/autoload.php';
|
||||
|
||||
// enable output of HTTP headers
|
||||
$options = new ZipStream\Option\Archive();
|
||||
$options->setSendHttpHeaders(true);
|
||||
|
||||
// create a new zipstream object
|
||||
$zip = new ZipStream\ZipStream('example.zip', $options);
|
||||
$zip = new ZipStream\ZipStream(
|
||||
outputName: 'example.zip',
|
||||
|
||||
// enable output of HTTP headers
|
||||
sendHttpHeaders: true,
|
||||
);
|
||||
|
||||
// create a file named 'hello.txt'
|
||||
$zip->addFile('hello.txt', 'This is the contents of hello.txt');
|
||||
$zip->addFile(
|
||||
fileName: 'hello.txt',
|
||||
data: 'This is the contents of hello.txt',
|
||||
);
|
||||
|
||||
// add a file named 'some_image.jpg' from a local file 'path/to/image.jpg'
|
||||
$zip->addFileFromPath('some_image.jpg', 'path/to/image.jpg');
|
||||
$zip->addFileFromPath(
|
||||
fileName: 'some_image.jpg',
|
||||
path: 'path/to/image.jpg',
|
||||
);
|
||||
|
||||
// add a file named 'goodbye.txt' from an open stream resource
|
||||
$fp = tmpfile();
|
||||
fwrite($fp, 'The quick brown fox jumped over the lazy dog.');
|
||||
rewind($fp);
|
||||
$zip->addFileFromStream('goodbye.txt', $fp);
|
||||
fclose($fp);
|
||||
$filePointer = tmpfile();
|
||||
fwrite($filePointer, 'The quick brown fox jumped over the lazy dog.');
|
||||
rewind($filePointer);
|
||||
$zip->addFileFromStream(
|
||||
fileName: 'goodbye.txt',
|
||||
stream: $filePointer,
|
||||
);
|
||||
fclose($filePointer);
|
||||
|
||||
// add a file named 'streamfile.txt' from the body of a `guzzle` response
|
||||
// Setup with `psr/http-message` & `guzzlehttp/psr7` dependencies required.
|
||||
$zip->addFileFromPsr7Stream(
|
||||
fileName: 'streamfile.txt',
|
||||
stream: $response->getBody(),
|
||||
);
|
||||
|
||||
// finish the zip stream
|
||||
$zip->finish();
|
||||
|
|
@ -72,14 +113,14 @@ The native Mac OS archive extraction tool prior to macOS 10.15 might not open
|
|||
archives in some conditions. A workaround is to disable the Zip64 feature with
|
||||
the option ``enableZip64: false``. This limits the archive to 4 Gb and 64k files
|
||||
but will allow users on macOS 10.14 and below to open them without issue.
|
||||
See `#116 <https://github.com/maennchen/ZipStream-PHP/issues/146>`_.
|
||||
See `#116 <https://github.com/maennchen/ZipStream-PHP/issues/116>`_.
|
||||
|
||||
The linux ``unzip`` utility might not handle properly unicode characters.
|
||||
It is recommended to extract with another tool like
|
||||
`7-zip <https://www.7-zip.org/>`_.
|
||||
See `#146 <https://github.com/maennchen/ZipStream-PHP/issues/146>`_.
|
||||
|
||||
It is the responsability of the client code to make sure that files are not
|
||||
It is the responsibility of the client code to make sure that files are not
|
||||
saved with the same path, as it is not possible for the library to figure it out
|
||||
while streaming a zip.
|
||||
See `#154 <https://github.com/maennchen/ZipStream-PHP/issues/154>`_.
|
||||
See `#154 <https://github.com/maennchen/ZipStream-PHP/issues/154>`_.
|
||||
|
|
|
|||
|
|
@ -5,7 +5,7 @@
|
|||
xmlns="https://www.phpdoc.org"
|
||||
xsi:noNamespaceSchemaLocation="https://raw.githubusercontent.com/phpDocumentor/phpDocumentor/master/data/xsd/phpdoc.xsd"
|
||||
>
|
||||
<title>ZipStream-PHP</title>
|
||||
<title>💾 ZipStream-PHP</title>
|
||||
<paths>
|
||||
<output>docs</output>
|
||||
</paths>
|
||||
|
|
|
|||
|
|
@ -1,14 +1,15 @@
|
|||
<?xml version="1.0"?>
|
||||
<phpunit xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" bootstrap="test/bootstrap.php" xsi:noNamespaceSchemaLocation="https://schema.phpunit.de/9.3/phpunit.xsd">
|
||||
<coverage processUncoveredFiles="true">
|
||||
<include>
|
||||
<directory suffix=".php">src</directory>
|
||||
</include>
|
||||
</coverage>
|
||||
<phpunit xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" bootstrap="test/bootstrap.php" xsi:noNamespaceSchemaLocation="https://schema.phpunit.de/10.1/phpunit.xsd" cacheDirectory=".phpunit.cache">
|
||||
<coverage/>
|
||||
<testsuites>
|
||||
<testsuite name="Application">
|
||||
<directory>test</directory>
|
||||
</testsuite>
|
||||
</testsuites>
|
||||
<logging/>
|
||||
<source>
|
||||
<include>
|
||||
<directory suffix=".php">src</directory>
|
||||
</include>
|
||||
</source>
|
||||
</phpunit>
|
||||
|
|
|
|||
|
|
@ -1,53 +1,25 @@
|
|||
<?xml version="1.0"?>
|
||||
<psalm
|
||||
errorLevel="1"
|
||||
resolveFromConfigFile="true"
|
||||
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
|
||||
xmlns="https://getpsalm.org/schema/config"
|
||||
xsi:schemaLocation="https://getpsalm.org/schema/config vendor/vimeo/psalm/config.xsd"
|
||||
findUnusedBaselineEntry="true"
|
||||
findUnusedCode="true"
|
||||
phpVersion="8.2.0"
|
||||
>
|
||||
<!-- TODO: Update phpVersion when raising the minimum supported version -->
|
||||
<projectFiles>
|
||||
<directory name="src" />
|
||||
<ignoreFiles>
|
||||
<directory name="vendor" />
|
||||
</ignoreFiles>
|
||||
</projectFiles>
|
||||
|
||||
<issueHandlers>
|
||||
<LessSpecificReturnType errorLevel="info" />
|
||||
|
||||
<!-- level 3 issues - slightly lazy code writing, but provably low false-negatives -->
|
||||
|
||||
<DeprecatedMethod errorLevel="info" />
|
||||
<DeprecatedProperty errorLevel="info" />
|
||||
<DeprecatedClass errorLevel="info" />
|
||||
<DeprecatedConstant errorLevel="info" />
|
||||
<DeprecatedFunction errorLevel="info" />
|
||||
<DeprecatedInterface errorLevel="info" />
|
||||
<DeprecatedTrait errorLevel="info" />
|
||||
|
||||
<InternalMethod errorLevel="info" />
|
||||
<InternalProperty errorLevel="info" />
|
||||
<InternalClass errorLevel="info" />
|
||||
|
||||
<MissingClosureReturnType errorLevel="info" />
|
||||
<MissingReturnType errorLevel="info" />
|
||||
<MissingPropertyType errorLevel="info" />
|
||||
<InvalidDocblock errorLevel="info" />
|
||||
|
||||
<PropertyNotSetInConstructor errorLevel="info" />
|
||||
<MissingConstructor errorLevel="info" />
|
||||
<MissingClosureParamType errorLevel="info" />
|
||||
<MissingParamType errorLevel="info" />
|
||||
|
||||
<RedundantCondition errorLevel="info" />
|
||||
|
||||
<DocblockTypeContradiction errorLevel="info" />
|
||||
<RedundantConditionGivenDocblockType errorLevel="info" />
|
||||
|
||||
<UnresolvableInclude errorLevel="info" />
|
||||
|
||||
<RawObjectIteration errorLevel="info" />
|
||||
|
||||
<InvalidStringClass errorLevel="info" />
|
||||
<!-- Turn off dead code warnings for externally called functions -->
|
||||
<PossiblyUnusedProperty errorLevel="suppress" />
|
||||
<PossiblyUnusedMethod errorLevel="suppress" />
|
||||
<PossiblyUnusedReturnValue errorLevel="suppress" />
|
||||
</issueHandlers>
|
||||
</psalm>
|
||||
|
|
|
|||
|
|
@ -1,174 +0,0 @@
|
|||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace ZipStream;
|
||||
|
||||
use OverflowException;
|
||||
|
||||
class Bigint
|
||||
{
|
||||
/**
|
||||
* @var int[]
|
||||
*/
|
||||
private $bytes = [0, 0, 0, 0, 0, 0, 0, 0];
|
||||
|
||||
/**
|
||||
* Initialize the bytes array
|
||||
*
|
||||
* @param int $value
|
||||
*/
|
||||
public function __construct(int $value = 0)
|
||||
{
|
||||
$this->fillBytes($value, 0, 8);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get an instance
|
||||
*
|
||||
* @param int $value
|
||||
* @return Bigint
|
||||
*/
|
||||
public static function init(int $value = 0): self
|
||||
{
|
||||
return new self($value);
|
||||
}
|
||||
|
||||
/**
|
||||
* Fill bytes from low to high
|
||||
*
|
||||
* @param int $low
|
||||
* @param int $high
|
||||
* @return Bigint
|
||||
*/
|
||||
public static function fromLowHigh(int $low, int $high): self
|
||||
{
|
||||
$bigint = new self();
|
||||
$bigint->fillBytes($low, 0, 4);
|
||||
$bigint->fillBytes($high, 4, 4);
|
||||
return $bigint;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get high 32
|
||||
*
|
||||
* @return int
|
||||
*/
|
||||
public function getHigh32(): int
|
||||
{
|
||||
return $this->getValue(4, 4);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get value from bytes array
|
||||
*
|
||||
* @param int $end
|
||||
* @param int $length
|
||||
* @return int
|
||||
*/
|
||||
public function getValue(int $end = 0, int $length = 8): int
|
||||
{
|
||||
$result = 0;
|
||||
for ($i = $end + $length - 1; $i >= $end; $i--) {
|
||||
$result <<= 8;
|
||||
$result |= $this->bytes[$i];
|
||||
}
|
||||
return $result;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get low FF
|
||||
*
|
||||
* @param bool $force
|
||||
* @return float
|
||||
*/
|
||||
public function getLowFF(bool $force = false): float
|
||||
{
|
||||
if ($force || $this->isOver32()) {
|
||||
return (float)0xFFFFFFFF;
|
||||
}
|
||||
return (float)$this->getLow32();
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if is over 32
|
||||
*
|
||||
* @psalm-suppress ArgumentTypeCoercion
|
||||
* @param bool $force
|
||||
* @return bool
|
||||
*/
|
||||
public function isOver32(bool $force = false): bool
|
||||
{
|
||||
// value 0xFFFFFFFF already needs a Zip64 header
|
||||
return $force ||
|
||||
max(array_slice($this->bytes, 4, 4)) > 0 ||
|
||||
min(array_slice($this->bytes, 0, 4)) === 0xFF;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get low 32
|
||||
*
|
||||
* @return int
|
||||
*/
|
||||
public function getLow32(): int
|
||||
{
|
||||
return $this->getValue(0, 4);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get hexadecimal
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function getHex64(): string
|
||||
{
|
||||
$result = '0x';
|
||||
for ($i = 7; $i >= 0; $i--) {
|
||||
$result .= sprintf('%02X', $this->bytes[$i]);
|
||||
}
|
||||
return $result;
|
||||
}
|
||||
|
||||
/**
|
||||
* Add
|
||||
*
|
||||
* @param Bigint $other
|
||||
* @return Bigint
|
||||
*/
|
||||
public function add(self $other): self
|
||||
{
|
||||
$result = clone $this;
|
||||
$overflow = false;
|
||||
for ($i = 0; $i < 8; $i++) {
|
||||
$result->bytes[$i] += $other->bytes[$i];
|
||||
if ($overflow) {
|
||||
$result->bytes[$i]++;
|
||||
$overflow = false;
|
||||
}
|
||||
if ($result->bytes[$i] & 0x100) {
|
||||
$overflow = true;
|
||||
$result->bytes[$i] &= 0xFF;
|
||||
}
|
||||
}
|
||||
if ($overflow) {
|
||||
throw new OverflowException();
|
||||
}
|
||||
return $result;
|
||||
}
|
||||
|
||||
/**
|
||||
* Fill the bytes field with int
|
||||
*
|
||||
* @param int $value
|
||||
* @param int $start
|
||||
* @param int $count
|
||||
* @return void
|
||||
*/
|
||||
protected function fillBytes(int $value, int $start, int $count): void
|
||||
{
|
||||
for ($i = 0; $i < $count; $i++) {
|
||||
$this->bytes[$start + $i] = $i >= PHP_INT_SIZE ? 0 : $value & 0xFF;
|
||||
$value >>= 8;
|
||||
}
|
||||
}
|
||||
}
|
||||
52
xunifriend_RaeeC/vendor/maennchen/zipstream-php/src/CentralDirectoryFileHeader.php
vendored
Normal file
52
xunifriend_RaeeC/vendor/maennchen/zipstream-php/src/CentralDirectoryFileHeader.php
vendored
Normal file
|
|
@ -0,0 +1,52 @@
|
|||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace ZipStream;
|
||||
|
||||
use DateTimeInterface;
|
||||
|
||||
/**
|
||||
* @internal
|
||||
*/
|
||||
abstract class CentralDirectoryFileHeader
|
||||
{
|
||||
private const SIGNATURE = 0x02014b50;
|
||||
|
||||
public static function generate(
|
||||
int $versionMadeBy,
|
||||
int $versionNeededToExtract,
|
||||
int $generalPurposeBitFlag,
|
||||
CompressionMethod $compressionMethod,
|
||||
DateTimeInterface $lastModificationDateTime,
|
||||
int $crc32,
|
||||
int $compressedSize,
|
||||
int $uncompressedSize,
|
||||
string $fileName,
|
||||
string $extraField,
|
||||
string $fileComment,
|
||||
int $diskNumberStart,
|
||||
int $internalFileAttributes,
|
||||
int $externalFileAttributes,
|
||||
int $relativeOffsetOfLocalHeader,
|
||||
): string {
|
||||
return PackField::pack(
|
||||
new PackField(format: 'V', value: self::SIGNATURE),
|
||||
new PackField(format: 'v', value: $versionMadeBy),
|
||||
new PackField(format: 'v', value: $versionNeededToExtract),
|
||||
new PackField(format: 'v', value: $generalPurposeBitFlag),
|
||||
new PackField(format: 'v', value: $compressionMethod->value),
|
||||
new PackField(format: 'V', value: Time::dateTimeToDosTime($lastModificationDateTime)),
|
||||
new PackField(format: 'V', value: $crc32),
|
||||
new PackField(format: 'V', value: $compressedSize),
|
||||
new PackField(format: 'V', value: $uncompressedSize),
|
||||
new PackField(format: 'v', value: strlen($fileName)),
|
||||
new PackField(format: 'v', value: strlen($extraField)),
|
||||
new PackField(format: 'v', value: strlen($fileComment)),
|
||||
new PackField(format: 'v', value: $diskNumberStart),
|
||||
new PackField(format: 'v', value: $internalFileAttributes),
|
||||
new PackField(format: 'V', value: $externalFileAttributes),
|
||||
new PackField(format: 'V', value: $relativeOffsetOfLocalHeader),
|
||||
) . $fileName . $extraField . $fileComment;
|
||||
}
|
||||
}
|
||||
109
xunifriend_RaeeC/vendor/maennchen/zipstream-php/src/CompressionMethod.php
vendored
Normal file
109
xunifriend_RaeeC/vendor/maennchen/zipstream-php/src/CompressionMethod.php
vendored
Normal file
|
|
@ -0,0 +1,109 @@
|
|||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace ZipStream;
|
||||
|
||||
/**
|
||||
* @api
|
||||
*/
|
||||
enum CompressionMethod: int
|
||||
{
|
||||
/**
|
||||
* The file is stored (no compression)
|
||||
*/
|
||||
case STORE = 0x00;
|
||||
|
||||
// 0x01: legacy algorithm - The file is Shrunk
|
||||
// 0x02: legacy algorithm - The file is Reduced with compression factor 1
|
||||
// 0x03: legacy algorithm - The file is Reduced with compression factor 2
|
||||
// 0x04: legacy algorithm - The file is Reduced with compression factor 3
|
||||
// 0x05: legacy algorithm - The file is Reduced with compression factor 4
|
||||
// 0x06: legacy algorithm - The file is Imploded
|
||||
// 0x07: Reserved for Tokenizing compression algorithm
|
||||
|
||||
/**
|
||||
* The file is Deflated
|
||||
*/
|
||||
case DEFLATE = 0x08;
|
||||
|
||||
// /**
|
||||
// * Enhanced Deflating using Deflate64(tm)
|
||||
// */
|
||||
// case DEFLATE_64 = 0x09;
|
||||
|
||||
// /**
|
||||
// * PKWARE Data Compression Library Imploding (old IBM TERSE)
|
||||
// */
|
||||
// case PKWARE = 0x0a;
|
||||
|
||||
// // 0x0b: Reserved by PKWARE
|
||||
|
||||
// /**
|
||||
// * File is compressed using BZIP2 algorithm
|
||||
// */
|
||||
// case BZIP2 = 0x0c;
|
||||
|
||||
// // 0x0d: Reserved by PKWARE
|
||||
|
||||
// /**
|
||||
// * LZMA
|
||||
// */
|
||||
// case LZMA = 0x0e;
|
||||
|
||||
// // 0x0f: Reserved by PKWARE
|
||||
|
||||
// /**
|
||||
// * IBM z/OS CMPSC Compression
|
||||
// */
|
||||
// case IBM_ZOS_CMPSC = 0x10;
|
||||
|
||||
// // 0x11: Reserved by PKWARE
|
||||
|
||||
// /**
|
||||
// * File is compressed using IBM TERSE
|
||||
// */
|
||||
// case IBM_TERSE = 0x12;
|
||||
|
||||
// /**
|
||||
// * IBM LZ77 z Architecture
|
||||
// */
|
||||
// case IBM_LZ77 = 0x13;
|
||||
|
||||
// // 0x14: deprecated (use method 93 for zstd)
|
||||
|
||||
// /**
|
||||
// * Zstandard (zstd) Compression
|
||||
// */
|
||||
// case ZSTD = 0x5d;
|
||||
|
||||
// /**
|
||||
// * MP3 Compression
|
||||
// */
|
||||
// case MP3 = 0x5e;
|
||||
|
||||
// /**
|
||||
// * XZ Compression
|
||||
// */
|
||||
// case XZ = 0x5f;
|
||||
|
||||
// /**
|
||||
// * JPEG variant
|
||||
// */
|
||||
// case JPEG = 0x60;
|
||||
|
||||
// /**
|
||||
// * WavPack compressed data
|
||||
// */
|
||||
// case WAV_PACK = 0x61;
|
||||
|
||||
// /**
|
||||
// * PPMd version I, Rev 1
|
||||
// */
|
||||
// case PPMD_1_1 = 0x62;
|
||||
|
||||
// /**
|
||||
// * AE-x encryption marker
|
||||
// */
|
||||
// case AE_X_ENCRYPTION = 0x63;
|
||||
}
|
||||
26
xunifriend_RaeeC/vendor/maennchen/zipstream-php/src/DataDescriptor.php
vendored
Normal file
26
xunifriend_RaeeC/vendor/maennchen/zipstream-php/src/DataDescriptor.php
vendored
Normal file
|
|
@ -0,0 +1,26 @@
|
|||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace ZipStream;
|
||||
|
||||
/**
|
||||
* @internal
|
||||
*/
|
||||
abstract class DataDescriptor
|
||||
{
|
||||
private const SIGNATURE = 0x08074b50;
|
||||
|
||||
public static function generate(
|
||||
int $crc32UncompressedData,
|
||||
int $compressedSize,
|
||||
int $uncompressedSize,
|
||||
): string {
|
||||
return PackField::pack(
|
||||
new PackField(format: 'V', value: self::SIGNATURE),
|
||||
new PackField(format: 'V', value: $crc32UncompressedData),
|
||||
new PackField(format: 'V', value: $compressedSize),
|
||||
new PackField(format: 'V', value: $uncompressedSize),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
|
@ -1,71 +0,0 @@
|
|||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace ZipStream;
|
||||
|
||||
class DeflateStream extends Stream
|
||||
{
|
||||
protected $filter;
|
||||
|
||||
/**
|
||||
* @var Option\File
|
||||
*/
|
||||
protected $options;
|
||||
|
||||
/**
|
||||
* Rewind stream
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function rewind(): void
|
||||
{
|
||||
// deflate filter needs to be removed before rewind
|
||||
if ($this->filter) {
|
||||
$this->removeDeflateFilter();
|
||||
$this->seek(0);
|
||||
$this->addDeflateFilter($this->options);
|
||||
} else {
|
||||
rewind($this->stream);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Remove the deflate filter
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function removeDeflateFilter(): void
|
||||
{
|
||||
if (!$this->filter) {
|
||||
return;
|
||||
}
|
||||
stream_filter_remove($this->filter);
|
||||
$this->filter = null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Add a deflate filter
|
||||
*
|
||||
* @param Option\File $options
|
||||
* @return void
|
||||
*/
|
||||
public function addDeflateFilter(Option\File $options): void
|
||||
{
|
||||
$this->options = $options;
|
||||
// parameter 4 for stream_filter_append expects array
|
||||
// so we convert the option object in an array
|
||||
$optionsArr = [
|
||||
'comment' => $options->getComment(),
|
||||
'method' => $options->getMethod(),
|
||||
'deflateLevel' => $options->getDeflateLevel(),
|
||||
'time' => $options->getTime(),
|
||||
];
|
||||
$this->filter = stream_filter_append(
|
||||
$this->stream,
|
||||
'zlib.deflate',
|
||||
STREAM_FILTER_READ,
|
||||
$optionsArr
|
||||
);
|
||||
}
|
||||
}
|
||||
35
xunifriend_RaeeC/vendor/maennchen/zipstream-php/src/EndOfCentralDirectory.php
vendored
Normal file
35
xunifriend_RaeeC/vendor/maennchen/zipstream-php/src/EndOfCentralDirectory.php
vendored
Normal file
|
|
@ -0,0 +1,35 @@
|
|||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace ZipStream;
|
||||
|
||||
/**
|
||||
* @internal
|
||||
*/
|
||||
abstract class EndOfCentralDirectory
|
||||
{
|
||||
private const SIGNATURE = 0x06054b50;
|
||||
|
||||
public static function generate(
|
||||
int $numberOfThisDisk,
|
||||
int $numberOfTheDiskWithCentralDirectoryStart,
|
||||
int $numberOfCentralDirectoryEntriesOnThisDisk,
|
||||
int $numberOfCentralDirectoryEntries,
|
||||
int $sizeOfCentralDirectory,
|
||||
int $centralDirectoryStartOffsetOnDisk,
|
||||
string $zipFileComment,
|
||||
): string {
|
||||
/** @psalm-suppress MixedArgument */
|
||||
return PackField::pack(
|
||||
new PackField(format: 'V', value: static::SIGNATURE),
|
||||
new PackField(format: 'v', value: $numberOfThisDisk),
|
||||
new PackField(format: 'v', value: $numberOfTheDiskWithCentralDirectoryStart),
|
||||
new PackField(format: 'v', value: $numberOfCentralDirectoryEntriesOnThisDisk),
|
||||
new PackField(format: 'v', value: $numberOfCentralDirectoryEntries),
|
||||
new PackField(format: 'V', value: $sizeOfCentralDirectory),
|
||||
new PackField(format: 'V', value: $centralDirectoryStartOffsetOnDisk),
|
||||
new PackField(format: 'v', value: strlen($zipFileComment)),
|
||||
) . $zipFileComment;
|
||||
}
|
||||
}
|
||||
|
|
@ -5,8 +5,6 @@ declare(strict_types=1);
|
|||
namespace ZipStream;
|
||||
|
||||
/**
|
||||
* This class is only for inheriting
|
||||
* @api
|
||||
*/
|
||||
abstract class Exception extends \Exception
|
||||
{
|
||||
}
|
||||
abstract class Exception extends \Exception {}
|
||||
|
|
|
|||
25
xunifriend_RaeeC/vendor/maennchen/zipstream-php/src/Exception/DosTimeOverflowException.php
vendored
Normal file
25
xunifriend_RaeeC/vendor/maennchen/zipstream-php/src/Exception/DosTimeOverflowException.php
vendored
Normal file
|
|
@ -0,0 +1,25 @@
|
|||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace ZipStream\Exception;
|
||||
|
||||
use DateTimeInterface;
|
||||
use ZipStream\Exception;
|
||||
|
||||
/**
|
||||
* This Exception gets invoked if a DOS time is overflowing
|
||||
*
|
||||
* @api
|
||||
*/
|
||||
class DosTimeOverflowException extends Exception
|
||||
{
|
||||
/**
|
||||
* @internal
|
||||
*/
|
||||
public function __construct(
|
||||
public readonly DateTimeInterface $dateTime
|
||||
) {
|
||||
parent::__construct('The date ' . $dateTime->format(DateTimeInterface::ATOM) . " can't be represented as DOS time / date.");
|
||||
}
|
||||
}
|
||||
|
|
@ -1,14 +0,0 @@
|
|||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace ZipStream\Exception;
|
||||
|
||||
use ZipStream\Exception;
|
||||
|
||||
/**
|
||||
* This Exception gets invoked if file or comment encoding is incorrect
|
||||
*/
|
||||
class EncodingException extends Exception
|
||||
{
|
||||
}
|
||||
|
|
@ -8,16 +8,17 @@ use ZipStream\Exception;
|
|||
|
||||
/**
|
||||
* This Exception gets invoked if a file wasn't found
|
||||
*
|
||||
* @api
|
||||
*/
|
||||
class FileNotFoundException extends Exception
|
||||
{
|
||||
/**
|
||||
* Constructor of the Exception
|
||||
*
|
||||
* @param String $path - The path which wasn't found
|
||||
* @internal
|
||||
*/
|
||||
public function __construct(string $path)
|
||||
{
|
||||
public function __construct(
|
||||
public readonly string $path
|
||||
) {
|
||||
parent::__construct("The file with the path $path wasn't found.");
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -7,17 +7,18 @@ namespace ZipStream\Exception;
|
|||
use ZipStream\Exception;
|
||||
|
||||
/**
|
||||
* This Exception gets invoked if a file wasn't found
|
||||
* This Exception gets invoked if a file isn't readable
|
||||
*
|
||||
* @api
|
||||
*/
|
||||
class FileNotReadableException extends Exception
|
||||
{
|
||||
/**
|
||||
* Constructor of the Exception
|
||||
*
|
||||
* @param String $path - The path which wasn't found
|
||||
* @internal
|
||||
*/
|
||||
public function __construct(string $path)
|
||||
{
|
||||
public function __construct(
|
||||
public readonly string $path
|
||||
) {
|
||||
parent::__construct("The file with the path $path isn't readable.");
|
||||
}
|
||||
}
|
||||
|
|
|
|||
25
xunifriend_RaeeC/vendor/maennchen/zipstream-php/src/Exception/FileSizeIncorrectException.php
vendored
Normal file
25
xunifriend_RaeeC/vendor/maennchen/zipstream-php/src/Exception/FileSizeIncorrectException.php
vendored
Normal file
|
|
@ -0,0 +1,25 @@
|
|||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace ZipStream\Exception;
|
||||
|
||||
use ZipStream\Exception;
|
||||
|
||||
/**
|
||||
* This Exception gets invoked if a file is not as large as it was specified.
|
||||
*
|
||||
* @api
|
||||
*/
|
||||
class FileSizeIncorrectException extends Exception
|
||||
{
|
||||
/**
|
||||
* @internal
|
||||
*/
|
||||
public function __construct(
|
||||
public readonly int $expectedSize,
|
||||
public readonly int $actualSize
|
||||
) {
|
||||
parent::__construct("File is {$actualSize} instead of {$expectedSize} bytes large. Adjust `exactSize` parameter.");
|
||||
}
|
||||
}
|
||||
|
|
@ -1,14 +0,0 @@
|
|||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace ZipStream\Exception;
|
||||
|
||||
use ZipStream\Exception;
|
||||
|
||||
/**
|
||||
* This Exception gets invoked if options are incompatible
|
||||
*/
|
||||
class IncompatibleOptionsException extends Exception
|
||||
{
|
||||
}
|
||||
|
|
@ -8,9 +8,14 @@ use ZipStream\Exception;
|
|||
|
||||
/**
|
||||
* This Exception gets invoked if a counter value exceeds storage size
|
||||
*
|
||||
* @api
|
||||
*/
|
||||
class OverflowException extends Exception
|
||||
{
|
||||
/**
|
||||
* @internal
|
||||
*/
|
||||
public function __construct()
|
||||
{
|
||||
parent::__construct('File size exceeds limit of 32 bit integer. Please enable "zip64" option.');
|
||||
|
|
|
|||
31
xunifriend_RaeeC/vendor/maennchen/zipstream-php/src/Exception/ResourceActionException.php
vendored
Normal file
31
xunifriend_RaeeC/vendor/maennchen/zipstream-php/src/Exception/ResourceActionException.php
vendored
Normal file
|
|
@ -0,0 +1,31 @@
|
|||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace ZipStream\Exception;
|
||||
|
||||
use ZipStream\Exception;
|
||||
|
||||
/**
|
||||
* This Exception gets invoked if a resource like `fread` returns false
|
||||
*
|
||||
* @api
|
||||
*/
|
||||
class ResourceActionException extends Exception
|
||||
{
|
||||
/**
|
||||
* @var ?resource
|
||||
*/
|
||||
public $resource;
|
||||
|
||||
/**
|
||||
* @param resource $resource
|
||||
*/
|
||||
public function __construct(
|
||||
public readonly string $function,
|
||||
$resource = null,
|
||||
) {
|
||||
$this->resource = $resource;
|
||||
parent::__construct('Function ' . $function . 'failed on resource.');
|
||||
}
|
||||
}
|
||||
21
xunifriend_RaeeC/vendor/maennchen/zipstream-php/src/Exception/SimulationFileUnknownException.php
vendored
Normal file
21
xunifriend_RaeeC/vendor/maennchen/zipstream-php/src/Exception/SimulationFileUnknownException.php
vendored
Normal file
|
|
@ -0,0 +1,21 @@
|
|||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace ZipStream\Exception;
|
||||
|
||||
use ZipStream\Exception;
|
||||
|
||||
/**
|
||||
* This Exception gets invoked if a strict simulation is executed and the file
|
||||
* information can't be determined without reading the entire file.
|
||||
*
|
||||
* @api
|
||||
*/
|
||||
class SimulationFileUnknownException extends Exception
|
||||
{
|
||||
public function __construct()
|
||||
{
|
||||
parent::__construct('The details of the strict simulation file could not be determined without reading the entire file.');
|
||||
}
|
||||
}
|
||||
|
|
@ -7,17 +7,17 @@ namespace ZipStream\Exception;
|
|||
use ZipStream\Exception;
|
||||
|
||||
/**
|
||||
* This Exception gets invoked if `fread` fails on a stream.
|
||||
* This Exception gets invoked if a stream can't be read.
|
||||
*
|
||||
* @api
|
||||
*/
|
||||
class StreamNotReadableException extends Exception
|
||||
{
|
||||
/**
|
||||
* Constructor of the Exception
|
||||
*
|
||||
* @param string $fileName - The name of the file which the stream belongs to.
|
||||
* @internal
|
||||
*/
|
||||
public function __construct(string $fileName)
|
||||
public function __construct()
|
||||
{
|
||||
parent::__construct("The stream for $fileName could not be read.");
|
||||
parent::__construct('The stream could not be read.');
|
||||
}
|
||||
}
|
||||
|
|
|
|||
24
xunifriend_RaeeC/vendor/maennchen/zipstream-php/src/Exception/StreamNotSeekableException.php
vendored
Normal file
24
xunifriend_RaeeC/vendor/maennchen/zipstream-php/src/Exception/StreamNotSeekableException.php
vendored
Normal file
|
|
@ -0,0 +1,24 @@
|
|||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace ZipStream\Exception;
|
||||
|
||||
use ZipStream\Exception;
|
||||
|
||||
/**
|
||||
* This Exception gets invoked if a non seekable stream is
|
||||
* provided and zero headers are disabled.
|
||||
*
|
||||
* @api
|
||||
*/
|
||||
class StreamNotSeekableException extends Exception
|
||||
{
|
||||
/**
|
||||
* @internal
|
||||
*/
|
||||
public function __construct()
|
||||
{
|
||||
parent::__construct('enableZeroHeader must be enable to add non seekable streams');
|
||||
}
|
||||
}
|
||||
|
|
@ -4,480 +4,427 @@ declare(strict_types=1);
|
|||
|
||||
namespace ZipStream;
|
||||
|
||||
use HashContext;
|
||||
use Psr\Http\Message\StreamInterface;
|
||||
use ZipStream\Exception\FileNotFoundException;
|
||||
use ZipStream\Exception\FileNotReadableException;
|
||||
use Closure;
|
||||
use DateTimeInterface;
|
||||
use DeflateContext;
|
||||
use RuntimeException;
|
||||
use ZipStream\Exception\FileSizeIncorrectException;
|
||||
use ZipStream\Exception\OverflowException;
|
||||
use ZipStream\Option\File as FileOptions;
|
||||
use ZipStream\Option\Method;
|
||||
use ZipStream\Option\Version;
|
||||
use ZipStream\Exception\ResourceActionException;
|
||||
use ZipStream\Exception\SimulationFileUnknownException;
|
||||
use ZipStream\Exception\StreamNotReadableException;
|
||||
use ZipStream\Exception\StreamNotSeekableException;
|
||||
|
||||
class File
|
||||
/**
|
||||
* @internal
|
||||
*/
|
||||
final class File
|
||||
{
|
||||
public const HASH_ALGORITHM = 'crc32b';
|
||||
private const CHUNKED_READ_BLOCK_SIZE = 0x1000000;
|
||||
|
||||
public const BIT_ZERO_HEADER = 0x0008;
|
||||
private Version $version;
|
||||
|
||||
public const BIT_EFS_UTF8 = 0x0800;
|
||||
private int $compressedSize = 0;
|
||||
|
||||
public const COMPUTE = 1;
|
||||
private int $uncompressedSize = 0;
|
||||
|
||||
public const SEND = 2;
|
||||
private int $crc = 0;
|
||||
|
||||
private const CHUNKED_READ_BLOCK_SIZE = 1048576;
|
||||
private int $generalPurposeBitFlag = 0;
|
||||
|
||||
private readonly string $fileName;
|
||||
|
||||
/**
|
||||
* @var string
|
||||
* @var resource|null
|
||||
*/
|
||||
public $name;
|
||||
private $stream;
|
||||
|
||||
/**
|
||||
* @var FileOptions
|
||||
* @param Closure $dataCallback
|
||||
* @psalm-param Closure(): resource $dataCallback
|
||||
*/
|
||||
public $opt;
|
||||
public function __construct(
|
||||
string $fileName,
|
||||
private readonly Closure $dataCallback,
|
||||
private readonly OperationMode $operationMode,
|
||||
private readonly int $startOffset,
|
||||
private readonly CompressionMethod $compressionMethod,
|
||||
private readonly string $comment,
|
||||
private readonly DateTimeInterface $lastModificationDateTime,
|
||||
private readonly int $deflateLevel,
|
||||
private readonly ?int $maxSize,
|
||||
private readonly ?int $exactSize,
|
||||
private readonly bool $enableZip64,
|
||||
private readonly bool $enableZeroHeader,
|
||||
private readonly Closure $send,
|
||||
private readonly Closure $recordSentBytes,
|
||||
) {
|
||||
$this->fileName = self::filterFilename($fileName);
|
||||
$this->checkEncoding();
|
||||
|
||||
/**
|
||||
* @var Bigint
|
||||
*/
|
||||
public $len;
|
||||
if ($this->enableZeroHeader) {
|
||||
$this->generalPurposeBitFlag |= GeneralPurposeBitFlag::ZERO_HEADER;
|
||||
}
|
||||
|
||||
/**
|
||||
* @var Bigint
|
||||
*/
|
||||
public $zlen;
|
||||
|
||||
/** @var int */
|
||||
public $crc;
|
||||
|
||||
/**
|
||||
* @var Bigint
|
||||
*/
|
||||
public $hlen;
|
||||
|
||||
/**
|
||||
* @var Bigint
|
||||
*/
|
||||
public $ofs;
|
||||
|
||||
/**
|
||||
* @var int
|
||||
*/
|
||||
public $bits;
|
||||
|
||||
/**
|
||||
* @var Version
|
||||
*/
|
||||
public $version;
|
||||
|
||||
/**
|
||||
* @var ZipStream
|
||||
*/
|
||||
public $zip;
|
||||
|
||||
/**
|
||||
* @var resource
|
||||
*/
|
||||
private $deflate;
|
||||
|
||||
/**
|
||||
* @var HashContext
|
||||
*/
|
||||
private $hash;
|
||||
|
||||
/**
|
||||
* @var Method
|
||||
*/
|
||||
private $method;
|
||||
|
||||
/**
|
||||
* @var Bigint
|
||||
*/
|
||||
private $totalLength;
|
||||
|
||||
public function __construct(ZipStream $zip, string $name, ?FileOptions $opt = null)
|
||||
{
|
||||
$this->zip = $zip;
|
||||
|
||||
$this->name = $name;
|
||||
$this->opt = $opt ?: new FileOptions();
|
||||
$this->method = $this->opt->getMethod();
|
||||
$this->version = Version::STORE();
|
||||
$this->ofs = new Bigint();
|
||||
$this->version = $this->compressionMethod === CompressionMethod::DEFLATE ? Version::DEFLATE : Version::STORE;
|
||||
}
|
||||
|
||||
public function processPath(string $path): void
|
||||
public function cloneSimulationExecution(): self
|
||||
{
|
||||
if (!is_readable($path)) {
|
||||
if (!file_exists($path)) {
|
||||
throw new FileNotFoundException($path);
|
||||
}
|
||||
throw new FileNotReadableException($path);
|
||||
}
|
||||
if ($this->zip->isLargeFile($path) === false) {
|
||||
$data = file_get_contents($path);
|
||||
$this->processData($data);
|
||||
return new self(
|
||||
$this->fileName,
|
||||
$this->dataCallback,
|
||||
OperationMode::NORMAL,
|
||||
$this->startOffset,
|
||||
$this->compressionMethod,
|
||||
$this->comment,
|
||||
$this->lastModificationDateTime,
|
||||
$this->deflateLevel,
|
||||
$this->maxSize,
|
||||
$this->exactSize,
|
||||
$this->enableZip64,
|
||||
$this->enableZeroHeader,
|
||||
$this->send,
|
||||
$this->recordSentBytes,
|
||||
);
|
||||
}
|
||||
|
||||
public function process(): string
|
||||
{
|
||||
$forecastSize = $this->forecastSize();
|
||||
|
||||
if ($this->enableZeroHeader) {
|
||||
// No calculation required
|
||||
} elseif ($this->isSimulation() && $forecastSize !== null) {
|
||||
$this->uncompressedSize = $forecastSize;
|
||||
$this->compressedSize = $forecastSize;
|
||||
} else {
|
||||
$this->method = $this->zip->opt->getLargeFileMethod();
|
||||
|
||||
$stream = new DeflateStream(fopen($path, 'rb'));
|
||||
$this->processStream($stream);
|
||||
$stream->close();
|
||||
$this->readStream(send: false);
|
||||
if (rewind($this->unpackStream()) === false) {
|
||||
throw new ResourceActionException('rewind', $this->unpackStream());
|
||||
}
|
||||
}
|
||||
|
||||
$this->addFileHeader();
|
||||
|
||||
$detectedSize = $forecastSize ?? ($this->compressedSize > 0 ? $this->compressedSize : null);
|
||||
|
||||
if (
|
||||
$this->isSimulation()
|
||||
&& $detectedSize !== null
|
||||
) {
|
||||
$this->uncompressedSize = $detectedSize;
|
||||
$this->compressedSize = $detectedSize;
|
||||
($this->recordSentBytes)($detectedSize);
|
||||
} else {
|
||||
$this->readStream(send: true);
|
||||
}
|
||||
|
||||
$this->addFileFooter();
|
||||
return $this->getCdrFile();
|
||||
}
|
||||
|
||||
public function processData(string $data): void
|
||||
/**
|
||||
* @return resource
|
||||
*/
|
||||
private function unpackStream()
|
||||
{
|
||||
$this->len = new Bigint(strlen($data));
|
||||
$this->crc = crc32($data);
|
||||
|
||||
// compress data if needed
|
||||
if ($this->method->equals(Method::DEFLATE())) {
|
||||
$data = gzdeflate($data);
|
||||
if ($this->stream) {
|
||||
return $this->stream;
|
||||
}
|
||||
|
||||
$this->zlen = new Bigint(strlen($data));
|
||||
$this->addFileHeader();
|
||||
$this->zip->send($data);
|
||||
$this->addFileFooter();
|
||||
if ($this->operationMode === OperationMode::SIMULATE_STRICT) {
|
||||
throw new SimulationFileUnknownException();
|
||||
}
|
||||
|
||||
$this->stream = ($this->dataCallback)();
|
||||
|
||||
if (!$this->enableZeroHeader && !stream_get_meta_data($this->stream)['seekable']) {
|
||||
throw new StreamNotSeekableException();
|
||||
}
|
||||
if (!(
|
||||
str_contains(stream_get_meta_data($this->stream)['mode'], 'r')
|
||||
|| str_contains(stream_get_meta_data($this->stream)['mode'], 'w+')
|
||||
|| str_contains(stream_get_meta_data($this->stream)['mode'], 'a+')
|
||||
|| str_contains(stream_get_meta_data($this->stream)['mode'], 'x+')
|
||||
|| str_contains(stream_get_meta_data($this->stream)['mode'], 'c+')
|
||||
)) {
|
||||
throw new StreamNotReadableException();
|
||||
}
|
||||
|
||||
return $this->stream;
|
||||
}
|
||||
|
||||
private function forecastSize(): ?int
|
||||
{
|
||||
if ($this->compressionMethod !== CompressionMethod::STORE) {
|
||||
return null;
|
||||
}
|
||||
if ($this->exactSize !== null) {
|
||||
return $this->exactSize;
|
||||
}
|
||||
$fstat = fstat($this->unpackStream());
|
||||
if (!$fstat || !array_key_exists('size', $fstat) || $fstat['size'] < 1) {
|
||||
return null;
|
||||
}
|
||||
|
||||
if ($this->maxSize !== null && $this->maxSize < $fstat['size']) {
|
||||
return $this->maxSize;
|
||||
}
|
||||
|
||||
return $fstat['size'];
|
||||
}
|
||||
|
||||
/**
|
||||
* Create and send zip header for this file.
|
||||
*
|
||||
* @return void
|
||||
* @throws \ZipStream\Exception\EncodingException
|
||||
*/
|
||||
public function addFileHeader(): void
|
||||
private function addFileHeader(): void
|
||||
{
|
||||
$name = static::filterFilename($this->name);
|
||||
$forceEnableZip64 = $this->enableZeroHeader && $this->enableZip64;
|
||||
|
||||
// calculate name length
|
||||
$nameLength = strlen($name);
|
||||
$footer = $this->buildZip64ExtraBlock($forceEnableZip64);
|
||||
|
||||
// create dos timestamp
|
||||
$time = static::dosTime($this->opt->getTime()->getTimestamp());
|
||||
$zip64Enabled = $footer !== '';
|
||||
|
||||
$comment = $this->opt->getComment();
|
||||
|
||||
if (!mb_check_encoding($name, 'ASCII') ||
|
||||
!mb_check_encoding($comment, 'ASCII')) {
|
||||
// Sets Bit 11: Language encoding flag (EFS). If this bit is set,
|
||||
// the filename and comment fields for this file
|
||||
// MUST be encoded using UTF-8. (see APPENDIX D)
|
||||
if (mb_check_encoding($name, 'UTF-8') &&
|
||||
mb_check_encoding($comment, 'UTF-8')) {
|
||||
$this->bits |= self::BIT_EFS_UTF8;
|
||||
}
|
||||
if ($zip64Enabled) {
|
||||
$this->version = Version::ZIP64;
|
||||
}
|
||||
|
||||
if ($this->method->equals(Method::DEFLATE())) {
|
||||
$this->version = Version::DEFLATE();
|
||||
if ($this->generalPurposeBitFlag & GeneralPurposeBitFlag::EFS) {
|
||||
// Put the tricky entry to
|
||||
// force Linux unzip to lookup EFS flag.
|
||||
$footer .= Zs\ExtendedInformationExtraField::generate();
|
||||
}
|
||||
|
||||
$force = (bool)($this->bits & self::BIT_ZERO_HEADER) &&
|
||||
$this->zip->opt->isEnableZip64();
|
||||
$data = LocalFileHeader::generate(
|
||||
versionNeededToExtract: $this->version->value,
|
||||
generalPurposeBitFlag: $this->generalPurposeBitFlag,
|
||||
compressionMethod: $this->compressionMethod,
|
||||
lastModificationDateTime: $this->lastModificationDateTime,
|
||||
crc32UncompressedData: $this->crc,
|
||||
compressedSize: $zip64Enabled
|
||||
? 0xFFFFFFFF
|
||||
: $this->compressedSize,
|
||||
uncompressedSize: $zip64Enabled
|
||||
? 0xFFFFFFFF
|
||||
: $this->uncompressedSize,
|
||||
fileName: $this->fileName,
|
||||
extraField: $footer,
|
||||
);
|
||||
|
||||
$footer = $this->buildZip64ExtraBlock($force);
|
||||
|
||||
// If this file will start over 4GB limit in ZIP file,
|
||||
// CDR record will have to use Zip64 extension to describe offset
|
||||
// to keep consistency we use the same value here
|
||||
if ($this->zip->ofs->isOver32()) {
|
||||
$this->version = Version::ZIP64();
|
||||
}
|
||||
|
||||
$fields = [
|
||||
['V', ZipStream::FILE_HEADER_SIGNATURE],
|
||||
['v', $this->version->getValue()], // Version needed to Extract
|
||||
['v', $this->bits], // General purpose bit flags - data descriptor flag set
|
||||
['v', $this->method->getValue()], // Compression method
|
||||
['V', $time], // Timestamp (DOS Format)
|
||||
['V', $this->crc], // CRC32 of data (0 -> moved to data descriptor footer)
|
||||
['V', $this->zlen->getLowFF($force)], // Length of compressed data (forced to 0xFFFFFFFF for zero header)
|
||||
['V', $this->len->getLowFF($force)], // Length of original data (forced to 0xFFFFFFFF for zero header)
|
||||
['v', $nameLength], // Length of filename
|
||||
['v', strlen($footer)], // Extra data (see above)
|
||||
];
|
||||
|
||||
// pack fields and calculate "total" length
|
||||
$header = ZipStream::packFields($fields);
|
||||
|
||||
// print header and filename
|
||||
$data = $header . $name . $footer;
|
||||
$this->zip->send($data);
|
||||
|
||||
// save header length
|
||||
$this->hlen = Bigint::init(strlen($data));
|
||||
($this->send)($data);
|
||||
}
|
||||
|
||||
/**
|
||||
* Strip characters that are not legal in Windows filenames
|
||||
* to prevent compatibility issues
|
||||
*
|
||||
* @param string $filename Unprocessed filename
|
||||
* @return string
|
||||
*/
|
||||
public static function filterFilename(string $filename): string
|
||||
{
|
||||
private static function filterFilename(
|
||||
/**
|
||||
* Unprocessed filename
|
||||
*/
|
||||
string $fileName
|
||||
): string {
|
||||
// strip leading slashes from file name
|
||||
// (fixes bug in windows archive viewer)
|
||||
$filename = preg_replace('/^\\/+/', '', $filename);
|
||||
$fileName = ltrim($fileName, '/');
|
||||
|
||||
return str_replace(['\\', ':', '*', '?', '"', '<', '>', '|'], '_', $filename);
|
||||
return str_replace(['\\', ':', '*', '?', '"', '<', '>', '|'], '_', $fileName);
|
||||
}
|
||||
|
||||
/**
|
||||
* Create and send data descriptor footer for this file.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function addFileFooter(): void
|
||||
private function checkEncoding(): void
|
||||
{
|
||||
if ($this->bits & self::BIT_ZERO_HEADER) {
|
||||
// compressed and uncompressed size
|
||||
$sizeFormat = 'V';
|
||||
if ($this->zip->opt->isEnableZip64()) {
|
||||
$sizeFormat = 'P';
|
||||
// Sets Bit 11: Language encoding flag (EFS). If this bit is set,
|
||||
// the filename and comment fields for this file
|
||||
// MUST be encoded using UTF-8. (see APPENDIX D)
|
||||
if (mb_check_encoding($this->fileName, 'UTF-8')
|
||||
&& mb_check_encoding($this->comment, 'UTF-8')) {
|
||||
$this->generalPurposeBitFlag |= GeneralPurposeBitFlag::EFS;
|
||||
}
|
||||
}
|
||||
|
||||
private function buildZip64ExtraBlock(bool $force = false): string
|
||||
{
|
||||
$outputZip64ExtraBlock = false;
|
||||
|
||||
$originalSize = null;
|
||||
if ($force || $this->uncompressedSize > 0xFFFFFFFF) {
|
||||
$outputZip64ExtraBlock = true;
|
||||
$originalSize = $this->uncompressedSize;
|
||||
}
|
||||
|
||||
$compressedSize = null;
|
||||
if ($force || $this->compressedSize > 0xFFFFFFFF) {
|
||||
$outputZip64ExtraBlock = true;
|
||||
$compressedSize = $this->compressedSize;
|
||||
}
|
||||
|
||||
// If this file will start over 4GB limit in ZIP file,
|
||||
// CDR record will have to use Zip64 extension to describe offset
|
||||
// to keep consistency we use the same value here
|
||||
$relativeHeaderOffset = null;
|
||||
if ($this->startOffset > 0xFFFFFFFF) {
|
||||
$outputZip64ExtraBlock = true;
|
||||
$relativeHeaderOffset = $this->startOffset;
|
||||
}
|
||||
|
||||
if (!$outputZip64ExtraBlock) {
|
||||
return '';
|
||||
}
|
||||
|
||||
if (!$this->enableZip64) {
|
||||
throw new OverflowException();
|
||||
}
|
||||
|
||||
return Zip64\ExtendedInformationExtraField::generate(
|
||||
originalSize: $originalSize,
|
||||
compressedSize: $compressedSize,
|
||||
relativeHeaderOffset: $relativeHeaderOffset,
|
||||
diskStartNumber: null,
|
||||
);
|
||||
}
|
||||
|
||||
private function addFileFooter(): void
|
||||
{
|
||||
if (($this->compressedSize > 0xFFFFFFFF || $this->uncompressedSize > 0xFFFFFFFF) && $this->version !== Version::ZIP64) {
|
||||
throw new OverflowException();
|
||||
}
|
||||
|
||||
if (!$this->enableZeroHeader) {
|
||||
return;
|
||||
}
|
||||
|
||||
if ($this->version === Version::ZIP64) {
|
||||
$footer = Zip64\DataDescriptor::generate(
|
||||
crc32UncompressedData: $this->crc,
|
||||
compressedSize: $this->compressedSize,
|
||||
uncompressedSize: $this->uncompressedSize,
|
||||
);
|
||||
} else {
|
||||
$footer = DataDescriptor::generate(
|
||||
crc32UncompressedData: $this->crc,
|
||||
compressedSize: $this->compressedSize,
|
||||
uncompressedSize: $this->uncompressedSize,
|
||||
);
|
||||
}
|
||||
|
||||
($this->send)($footer);
|
||||
}
|
||||
|
||||
private function readStream(bool $send): void
|
||||
{
|
||||
$this->compressedSize = 0;
|
||||
$this->uncompressedSize = 0;
|
||||
$hash = hash_init('crc32b');
|
||||
|
||||
$deflate = $this->compressionInit();
|
||||
|
||||
while (
|
||||
!feof($this->unpackStream())
|
||||
&& ($this->maxSize === null || $this->uncompressedSize < $this->maxSize)
|
||||
&& ($this->exactSize === null || $this->uncompressedSize < $this->exactSize)
|
||||
) {
|
||||
$readLength = min(
|
||||
($this->maxSize ?? PHP_INT_MAX) - $this->uncompressedSize,
|
||||
($this->exactSize ?? PHP_INT_MAX) - $this->uncompressedSize,
|
||||
self::CHUNKED_READ_BLOCK_SIZE
|
||||
);
|
||||
|
||||
$data = fread($this->unpackStream(), $readLength);
|
||||
|
||||
if ($data === false) {
|
||||
throw new ResourceActionException('fread', $this->unpackStream());
|
||||
}
|
||||
$fields = [
|
||||
['V', ZipStream::DATA_DESCRIPTOR_SIGNATURE],
|
||||
['V', $this->crc], // CRC32
|
||||
[$sizeFormat, $this->zlen], // Length of compressed data
|
||||
[$sizeFormat, $this->len], // Length of original data
|
||||
];
|
||||
|
||||
$footer = ZipStream::packFields($fields);
|
||||
$this->zip->send($footer);
|
||||
} else {
|
||||
$footer = '';
|
||||
hash_update($hash, $data);
|
||||
|
||||
$this->uncompressedSize += strlen($data);
|
||||
|
||||
if ($deflate) {
|
||||
$data = deflate_add(
|
||||
$deflate,
|
||||
$data,
|
||||
feof($this->unpackStream()) ? ZLIB_FINISH : ZLIB_NO_FLUSH
|
||||
);
|
||||
|
||||
if ($data === false) {
|
||||
throw new RuntimeException('deflate_add failed');
|
||||
}
|
||||
}
|
||||
|
||||
$this->compressedSize += strlen($data);
|
||||
|
||||
if ($send) {
|
||||
($this->send)($data);
|
||||
}
|
||||
}
|
||||
$this->totalLength = $this->hlen->add($this->zlen)->add(Bigint::init(strlen($footer)));
|
||||
$this->zip->addToCdr($this);
|
||||
|
||||
if ($this->exactSize !== null && $this->uncompressedSize !== $this->exactSize) {
|
||||
throw new FileSizeIncorrectException(expectedSize: $this->exactSize, actualSize: $this->uncompressedSize);
|
||||
}
|
||||
|
||||
$this->crc = hexdec(hash_final($hash));
|
||||
}
|
||||
|
||||
public function processStream(StreamInterface $stream): void
|
||||
private function compressionInit(): ?DeflateContext
|
||||
{
|
||||
$this->zlen = new Bigint();
|
||||
$this->len = new Bigint();
|
||||
switch ($this->compressionMethod) {
|
||||
case CompressionMethod::STORE:
|
||||
// Noting to do
|
||||
return null;
|
||||
case CompressionMethod::DEFLATE:
|
||||
$deflateContext = deflate_init(
|
||||
ZLIB_ENCODING_RAW,
|
||||
['level' => $this->deflateLevel]
|
||||
);
|
||||
|
||||
if ($this->zip->opt->isZeroHeader()) {
|
||||
$this->processStreamWithZeroHeader($stream);
|
||||
} else {
|
||||
$this->processStreamWithComputedHeader($stream);
|
||||
if (!$deflateContext) {
|
||||
// @codeCoverageIgnoreStart
|
||||
throw new RuntimeException("Can't initialize deflate context.");
|
||||
// @codeCoverageIgnoreEnd
|
||||
}
|
||||
|
||||
// False positive, resource is no longer returned from this function
|
||||
return $deflateContext;
|
||||
default:
|
||||
// @codeCoverageIgnoreStart
|
||||
throw new RuntimeException('Unsupported Compression Method ' . print_r($this->compressionMethod, true));
|
||||
// @codeCoverageIgnoreEnd
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Send CDR record for specified file.
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function getCdrFile(): string
|
||||
private function getCdrFile(): string
|
||||
{
|
||||
$name = static::filterFilename($this->name);
|
||||
|
||||
// get attributes
|
||||
$comment = $this->opt->getComment();
|
||||
|
||||
// get dos timestamp
|
||||
$time = static::dosTime($this->opt->getTime()->getTimestamp());
|
||||
|
||||
$footer = $this->buildZip64ExtraBlock();
|
||||
|
||||
$fields = [
|
||||
['V', ZipStream::CDR_FILE_SIGNATURE], // Central file header signature
|
||||
['v', ZipStream::ZIP_VERSION_MADE_BY], // Made by version
|
||||
['v', $this->version->getValue()], // Extract by version
|
||||
['v', $this->bits], // General purpose bit flags - data descriptor flag set
|
||||
['v', $this->method->getValue()], // Compression method
|
||||
['V', $time], // Timestamp (DOS Format)
|
||||
['V', $this->crc], // CRC32
|
||||
['V', $this->zlen->getLowFF()], // Compressed Data Length
|
||||
['V', $this->len->getLowFF()], // Original Data Length
|
||||
['v', strlen($name)], // Length of filename
|
||||
['v', strlen($footer)], // Extra data len (see above)
|
||||
['v', strlen($comment)], // Length of comment
|
||||
['v', 0], // Disk number
|
||||
['v', 0], // Internal File Attributes
|
||||
['V', 32], // External File Attributes
|
||||
['V', $this->ofs->getLowFF()], // Relative offset of local header
|
||||
];
|
||||
|
||||
// pack fields, then append name and comment
|
||||
$header = ZipStream::packFields($fields);
|
||||
|
||||
return $header . $name . $footer . $comment;
|
||||
return CentralDirectoryFileHeader::generate(
|
||||
versionMadeBy: ZipStream::ZIP_VERSION_MADE_BY,
|
||||
versionNeededToExtract: $this->version->value,
|
||||
generalPurposeBitFlag: $this->generalPurposeBitFlag,
|
||||
compressionMethod: $this->compressionMethod,
|
||||
lastModificationDateTime: $this->lastModificationDateTime,
|
||||
crc32: $this->crc,
|
||||
compressedSize: $this->compressedSize > 0xFFFFFFFF
|
||||
? 0xFFFFFFFF
|
||||
: $this->compressedSize,
|
||||
uncompressedSize: $this->uncompressedSize > 0xFFFFFFFF
|
||||
? 0xFFFFFFFF
|
||||
: $this->uncompressedSize,
|
||||
fileName: $this->fileName,
|
||||
extraField: $footer,
|
||||
fileComment: $this->comment,
|
||||
diskNumberStart: 0,
|
||||
internalFileAttributes: 0,
|
||||
externalFileAttributes: 32,
|
||||
relativeOffsetOfLocalHeader: $this->startOffset > 0xFFFFFFFF
|
||||
? 0xFFFFFFFF
|
||||
: $this->startOffset,
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* @return Bigint
|
||||
*/
|
||||
public function getTotalLength(): Bigint
|
||||
private function isSimulation(): bool
|
||||
{
|
||||
return $this->totalLength;
|
||||
}
|
||||
|
||||
/**
|
||||
* Convert a UNIX timestamp to a DOS timestamp.
|
||||
*
|
||||
* @param int $when
|
||||
* @return int DOS Timestamp
|
||||
*/
|
||||
final protected static function dosTime(int $when): int
|
||||
{
|
||||
// get date array for timestamp
|
||||
$d = getdate($when);
|
||||
|
||||
// set lower-bound on dates
|
||||
if ($d['year'] < 1980) {
|
||||
$d = [
|
||||
'year' => 1980,
|
||||
'mon' => 1,
|
||||
'mday' => 1,
|
||||
'hours' => 0,
|
||||
'minutes' => 0,
|
||||
'seconds' => 0,
|
||||
];
|
||||
}
|
||||
|
||||
// remove extra years from 1980
|
||||
$d['year'] -= 1980;
|
||||
|
||||
// return date string
|
||||
return
|
||||
($d['year'] << 25) |
|
||||
($d['mon'] << 21) |
|
||||
($d['mday'] << 16) |
|
||||
($d['hours'] << 11) |
|
||||
($d['minutes'] << 5) |
|
||||
($d['seconds'] >> 1);
|
||||
}
|
||||
|
||||
protected function buildZip64ExtraBlock(bool $force = false): string
|
||||
{
|
||||
$fields = [];
|
||||
if ($this->len->isOver32($force)) {
|
||||
$fields[] = ['P', $this->len]; // Length of original data
|
||||
}
|
||||
|
||||
if ($this->len->isOver32($force)) {
|
||||
$fields[] = ['P', $this->zlen]; // Length of compressed data
|
||||
}
|
||||
|
||||
if ($this->ofs->isOver32()) {
|
||||
$fields[] = ['P', $this->ofs]; // Offset of local header record
|
||||
}
|
||||
|
||||
if (!empty($fields)) {
|
||||
if (!$this->zip->opt->isEnableZip64()) {
|
||||
throw new OverflowException();
|
||||
}
|
||||
|
||||
array_unshift(
|
||||
$fields,
|
||||
['v', 0x0001], // 64 bit extension
|
||||
['v', count($fields) * 8] // Length of data block
|
||||
);
|
||||
$this->version = Version::ZIP64();
|
||||
}
|
||||
|
||||
if ($this->bits & self::BIT_EFS_UTF8) {
|
||||
// Put the tricky entry to
|
||||
// force Linux unzip to lookup EFS flag.
|
||||
$fields[] = ['v', 0x5653]; // Choose 'ZS' for proprietary usage
|
||||
$fields[] = ['v', 0x0000]; // zero length
|
||||
}
|
||||
|
||||
return ZipStream::packFields($fields);
|
||||
}
|
||||
|
||||
protected function processStreamWithZeroHeader(StreamInterface $stream): void
|
||||
{
|
||||
$this->bits |= self::BIT_ZERO_HEADER;
|
||||
$this->addFileHeader();
|
||||
$this->readStream($stream, self::COMPUTE | self::SEND);
|
||||
$this->addFileFooter();
|
||||
}
|
||||
|
||||
protected function readStream(StreamInterface $stream, ?int $options = null): void
|
||||
{
|
||||
$this->deflateInit();
|
||||
$total = 0;
|
||||
$size = $this->opt->getSize();
|
||||
while (!$stream->eof() && ($size === 0 || $total < $size)) {
|
||||
$data = $stream->read(self::CHUNKED_READ_BLOCK_SIZE);
|
||||
$total += strlen($data);
|
||||
if ($size > 0 && $total > $size) {
|
||||
$data = substr($data, 0, strlen($data)-($total - $size));
|
||||
}
|
||||
$this->deflateData($stream, $data, $options);
|
||||
if ($options & self::SEND) {
|
||||
$this->zip->send($data);
|
||||
}
|
||||
}
|
||||
$this->deflateFinish($options);
|
||||
}
|
||||
|
||||
protected function deflateInit(): void
|
||||
{
|
||||
$hash = hash_init(self::HASH_ALGORITHM);
|
||||
$this->hash = $hash;
|
||||
if ($this->method->equals(Method::DEFLATE())) {
|
||||
$this->deflate = deflate_init(
|
||||
ZLIB_ENCODING_RAW,
|
||||
['level' => $this->opt->getDeflateLevel()]
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
protected function deflateData(StreamInterface $stream, string &$data, ?int $options = null): void
|
||||
{
|
||||
if ($options & self::COMPUTE) {
|
||||
$this->len = $this->len->add(Bigint::init(strlen($data)));
|
||||
hash_update($this->hash, $data);
|
||||
}
|
||||
if ($this->deflate) {
|
||||
$data = deflate_add(
|
||||
$this->deflate,
|
||||
$data,
|
||||
$stream->eof()
|
||||
? ZLIB_FINISH
|
||||
: ZLIB_NO_FLUSH
|
||||
);
|
||||
}
|
||||
if ($options & self::COMPUTE) {
|
||||
$this->zlen = $this->zlen->add(Bigint::init(strlen($data)));
|
||||
}
|
||||
}
|
||||
|
||||
protected function deflateFinish(?int $options = null): void
|
||||
{
|
||||
if ($options & self::COMPUTE) {
|
||||
$this->crc = hexdec(hash_final($this->hash));
|
||||
}
|
||||
}
|
||||
|
||||
protected function processStreamWithComputedHeader(StreamInterface $stream): void
|
||||
{
|
||||
$this->readStream($stream, self::COMPUTE);
|
||||
$stream->rewind();
|
||||
|
||||
// incremental compression with deflate_add
|
||||
// makes this second read unnecessary
|
||||
// but it is only available from PHP 7.0
|
||||
if (!$this->deflate && $stream instanceof DeflateStream && $this->method->equals(Method::DEFLATE())) {
|
||||
$stream->addDeflateFilter($this->opt);
|
||||
$this->zlen = new Bigint();
|
||||
while (!$stream->eof()) {
|
||||
$data = $stream->read(self::CHUNKED_READ_BLOCK_SIZE);
|
||||
$this->zlen = $this->zlen->add(Bigint::init(strlen($data)));
|
||||
}
|
||||
$stream->rewind();
|
||||
}
|
||||
|
||||
$this->addFileHeader();
|
||||
$this->readStream($stream, self::SEND);
|
||||
$this->addFileFooter();
|
||||
return $this->operationMode === OperationMode::SIMULATE_LAX || $this->operationMode === OperationMode::SIMULATE_STRICT;
|
||||
}
|
||||
}
|
||||
|
|
|
|||
89
xunifriend_RaeeC/vendor/maennchen/zipstream-php/src/GeneralPurposeBitFlag.php
vendored
Normal file
89
xunifriend_RaeeC/vendor/maennchen/zipstream-php/src/GeneralPurposeBitFlag.php
vendored
Normal file
|
|
@ -0,0 +1,89 @@
|
|||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace ZipStream;
|
||||
|
||||
/**
|
||||
* @internal
|
||||
*/
|
||||
abstract class GeneralPurposeBitFlag
|
||||
{
|
||||
/**
|
||||
* If set, indicates that the file is encrypted.
|
||||
*/
|
||||
public const ENCRYPTED = 1 << 0;
|
||||
|
||||
/**
|
||||
* (For Methods 8 and 9 - Deflating)
|
||||
* Normal (-en) compression option was used.
|
||||
*/
|
||||
public const DEFLATE_COMPRESSION_NORMAL = 0 << 1;
|
||||
|
||||
/**
|
||||
* (For Methods 8 and 9 - Deflating)
|
||||
* Maximum (-exx/-ex) compression option was used.
|
||||
*/
|
||||
public const DEFLATE_COMPRESSION_MAXIMUM = 1 << 1;
|
||||
|
||||
/**
|
||||
* (For Methods 8 and 9 - Deflating)
|
||||
* Fast (-ef) compression option was used.
|
||||
*/
|
||||
public const DEFLATE_COMPRESSION_FAST = 10 << 1;
|
||||
|
||||
/**
|
||||
* (For Methods 8 and 9 - Deflating)
|
||||
* Super Fast (-es) compression option was used.
|
||||
*/
|
||||
public const DEFLATE_COMPRESSION_SUPERFAST = 11 << 1;
|
||||
|
||||
/**
|
||||
* If the compression method used was type 14,
|
||||
* LZMA, then this bit, if set, indicates
|
||||
* an end-of-stream (EOS) marker is used to
|
||||
* mark the end of the compressed data stream.
|
||||
* If clear, then an EOS marker is not present
|
||||
* and the compressed data size must be known
|
||||
* to extract.
|
||||
*/
|
||||
public const LZMA_EOS = 1 << 1;
|
||||
|
||||
/**
|
||||
* If this bit is set, the fields crc-32, compressed
|
||||
* size and uncompressed size are set to zero in the
|
||||
* local header. The correct values are put in the
|
||||
* data descriptor immediately following the compressed
|
||||
* data.
|
||||
*/
|
||||
public const ZERO_HEADER = 1 << 3;
|
||||
|
||||
/**
|
||||
* If this bit is set, this indicates that the file is
|
||||
* compressed patched data.
|
||||
*/
|
||||
public const COMPRESSED_PATCHED_DATA = 1 << 5;
|
||||
|
||||
/**
|
||||
* Strong encryption. If this bit is set, you MUST
|
||||
* set the version needed to extract value to at least
|
||||
* 50 and you MUST also set bit 0. If AES encryption
|
||||
* is used, the version needed to extract value MUST
|
||||
* be at least 51.
|
||||
*/
|
||||
public const STRONG_ENCRYPTION = 1 << 6;
|
||||
|
||||
/**
|
||||
* Language encoding flag (EFS). If this bit is set,
|
||||
* the filename and comment fields for this file
|
||||
* MUST be encoded using UTF-8.
|
||||
*/
|
||||
public const EFS = 1 << 11;
|
||||
|
||||
/**
|
||||
* Set when encrypting the Central Directory to indicate
|
||||
* selected data values in the Local Header are masked to
|
||||
* hide their actual values.
|
||||
*/
|
||||
public const ENCRYPT_CENTRAL_DIRECTORY = 1 << 13;
|
||||
}
|
||||
40
xunifriend_RaeeC/vendor/maennchen/zipstream-php/src/LocalFileHeader.php
vendored
Normal file
40
xunifriend_RaeeC/vendor/maennchen/zipstream-php/src/LocalFileHeader.php
vendored
Normal file
|
|
@ -0,0 +1,40 @@
|
|||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace ZipStream;
|
||||
|
||||
use DateTimeInterface;
|
||||
|
||||
/**
|
||||
* @internal
|
||||
*/
|
||||
abstract class LocalFileHeader
|
||||
{
|
||||
private const SIGNATURE = 0x04034b50;
|
||||
|
||||
public static function generate(
|
||||
int $versionNeededToExtract,
|
||||
int $generalPurposeBitFlag,
|
||||
CompressionMethod $compressionMethod,
|
||||
DateTimeInterface $lastModificationDateTime,
|
||||
int $crc32UncompressedData,
|
||||
int $compressedSize,
|
||||
int $uncompressedSize,
|
||||
string $fileName,
|
||||
string $extraField,
|
||||
): string {
|
||||
return PackField::pack(
|
||||
new PackField(format: 'V', value: self::SIGNATURE),
|
||||
new PackField(format: 'v', value: $versionNeededToExtract),
|
||||
new PackField(format: 'v', value: $generalPurposeBitFlag),
|
||||
new PackField(format: 'v', value: $compressionMethod->value),
|
||||
new PackField(format: 'V', value: Time::dateTimeToDosTime($lastModificationDateTime)),
|
||||
new PackField(format: 'V', value: $crc32UncompressedData),
|
||||
new PackField(format: 'V', value: $compressedSize),
|
||||
new PackField(format: 'V', value: $uncompressedSize),
|
||||
new PackField(format: 'v', value: strlen($fileName)),
|
||||
new PackField(format: 'v', value: strlen($extraField)),
|
||||
) . $fileName . $extraField;
|
||||
}
|
||||
}
|
||||
37
xunifriend_RaeeC/vendor/maennchen/zipstream-php/src/OperationMode.php
vendored
Normal file
37
xunifriend_RaeeC/vendor/maennchen/zipstream-php/src/OperationMode.php
vendored
Normal file
|
|
@ -0,0 +1,37 @@
|
|||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace ZipStream;
|
||||
|
||||
/**
|
||||
* ZipStream execution operation modes
|
||||
*
|
||||
* @api
|
||||
*/
|
||||
enum OperationMode
|
||||
{
|
||||
/**
|
||||
* Stream file into output stream
|
||||
*/
|
||||
case NORMAL;
|
||||
|
||||
/**
|
||||
* Simulate the zip to figure out the resulting file size
|
||||
*
|
||||
* This only supports entries where the file size is known beforehand and
|
||||
* deflation is disabled.
|
||||
*/
|
||||
case SIMULATE_STRICT;
|
||||
|
||||
/**
|
||||
* Simulate the zip to figure out the resulting file size
|
||||
*
|
||||
* If the file size is not known beforehand or deflation is enabled, the
|
||||
* entry streams will be read and rewound.
|
||||
*
|
||||
* If the entry does not support rewinding either, you will not be able to
|
||||
* use the same stream in a later operation mode like `NORMAL`.
|
||||
*/
|
||||
case SIMULATE_LAX;
|
||||
}
|
||||
|
|
@ -1,276 +0,0 @@
|
|||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace ZipStream\Option;
|
||||
|
||||
use Psr\Http\Message\StreamInterface;
|
||||
|
||||
final class Archive
|
||||
{
|
||||
public const DEFAULT_DEFLATE_LEVEL = 6;
|
||||
|
||||
/**
|
||||
* @var string
|
||||
*/
|
||||
private $comment = '';
|
||||
|
||||
/**
|
||||
* Size, in bytes, of the largest file to try
|
||||
* and load into memory (used by
|
||||
* addFileFromPath()). Large files may also
|
||||
* be compressed differently; see the
|
||||
* 'largeFileMethod' option. Default is ~20 Mb.
|
||||
*
|
||||
* @var int
|
||||
*/
|
||||
private $largeFileSize = 20 * 1024 * 1024;
|
||||
|
||||
/**
|
||||
* How to handle large files. Legal values are
|
||||
* Method::STORE() (the default), or
|
||||
* Method::DEFLATE(). STORE sends the file
|
||||
* raw and is significantly
|
||||
* faster, while DEFLATE compresses the file
|
||||
* and is much, much slower. Note that DEFLATE
|
||||
* must compress the file twice and is extremely slow.
|
||||
*
|
||||
* @var Method
|
||||
*/
|
||||
private $largeFileMethod;
|
||||
|
||||
/**
|
||||
* Boolean indicating whether or not to send
|
||||
* the HTTP headers for this file.
|
||||
*
|
||||
* @var bool
|
||||
*/
|
||||
private $sendHttpHeaders = false;
|
||||
|
||||
/**
|
||||
* The method called to send headers
|
||||
*
|
||||
* @var Callable
|
||||
*/
|
||||
private $httpHeaderCallback = 'header';
|
||||
|
||||
/**
|
||||
* Enable Zip64 extension, supporting very large
|
||||
* archives (any size > 4 GB or file count > 64k)
|
||||
*
|
||||
* @var bool
|
||||
*/
|
||||
private $enableZip64 = true;
|
||||
|
||||
/**
|
||||
* Enable streaming files with single read where
|
||||
* general purpose bit 3 indicates local file header
|
||||
* contain zero values in crc and size fields,
|
||||
* these appear only after file contents
|
||||
* in data descriptor block.
|
||||
*
|
||||
* @var bool
|
||||
*/
|
||||
private $zeroHeader = false;
|
||||
|
||||
/**
|
||||
* Enable reading file stat for determining file size.
|
||||
* When a 32-bit system reads file size that is
|
||||
* over 2 GB, invalid value appears in file size
|
||||
* due to integer overflow. Should be disabled on
|
||||
* 32-bit systems with method addFileFromPath
|
||||
* if any file may exceed 2 GB. In this case file
|
||||
* will be read in blocks and correct size will be
|
||||
* determined from content.
|
||||
*
|
||||
* @var bool
|
||||
*/
|
||||
private $statFiles = true;
|
||||
|
||||
/**
|
||||
* Enable flush after every write to output stream.
|
||||
* @var bool
|
||||
*/
|
||||
private $flushOutput = false;
|
||||
|
||||
/**
|
||||
* HTTP Content-Disposition. Defaults to
|
||||
* 'attachment', where
|
||||
* FILENAME is the specified filename.
|
||||
*
|
||||
* Note that this does nothing if you are
|
||||
* not sending HTTP headers.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
private $contentDisposition = 'attachment';
|
||||
|
||||
/**
|
||||
* Note that this does nothing if you are
|
||||
* not sending HTTP headers.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
private $contentType = 'application/x-zip';
|
||||
|
||||
/**
|
||||
* @var int
|
||||
*/
|
||||
private $deflateLevel = 6;
|
||||
|
||||
/**
|
||||
* @var StreamInterface|resource
|
||||
*/
|
||||
private $outputStream;
|
||||
|
||||
/**
|
||||
* Options constructor.
|
||||
*/
|
||||
public function __construct()
|
||||
{
|
||||
$this->largeFileMethod = Method::STORE();
|
||||
$this->outputStream = fopen('php://output', 'wb');
|
||||
}
|
||||
|
||||
public function getComment(): string
|
||||
{
|
||||
return $this->comment;
|
||||
}
|
||||
|
||||
public function setComment(string $comment): void
|
||||
{
|
||||
$this->comment = $comment;
|
||||
}
|
||||
|
||||
public function getLargeFileSize(): int
|
||||
{
|
||||
return $this->largeFileSize;
|
||||
}
|
||||
|
||||
public function setLargeFileSize(int $largeFileSize): void
|
||||
{
|
||||
$this->largeFileSize = $largeFileSize;
|
||||
}
|
||||
|
||||
public function getLargeFileMethod(): Method
|
||||
{
|
||||
return $this->largeFileMethod;
|
||||
}
|
||||
|
||||
public function setLargeFileMethod(Method $largeFileMethod): void
|
||||
{
|
||||
$this->largeFileMethod = $largeFileMethod;
|
||||
}
|
||||
|
||||
public function isSendHttpHeaders(): bool
|
||||
{
|
||||
return $this->sendHttpHeaders;
|
||||
}
|
||||
|
||||
public function setSendHttpHeaders(bool $sendHttpHeaders): void
|
||||
{
|
||||
$this->sendHttpHeaders = $sendHttpHeaders;
|
||||
}
|
||||
|
||||
public function getHttpHeaderCallback(): callable
|
||||
{
|
||||
return $this->httpHeaderCallback;
|
||||
}
|
||||
|
||||
public function setHttpHeaderCallback(callable $httpHeaderCallback): void
|
||||
{
|
||||
$this->httpHeaderCallback = $httpHeaderCallback;
|
||||
}
|
||||
|
||||
public function isEnableZip64(): bool
|
||||
{
|
||||
return $this->enableZip64;
|
||||
}
|
||||
|
||||
public function setEnableZip64(bool $enableZip64): void
|
||||
{
|
||||
$this->enableZip64 = $enableZip64;
|
||||
}
|
||||
|
||||
public function isZeroHeader(): bool
|
||||
{
|
||||
return $this->zeroHeader;
|
||||
}
|
||||
|
||||
public function setZeroHeader(bool $zeroHeader): void
|
||||
{
|
||||
$this->zeroHeader = $zeroHeader;
|
||||
}
|
||||
|
||||
public function isFlushOutput(): bool
|
||||
{
|
||||
return $this->flushOutput;
|
||||
}
|
||||
|
||||
public function setFlushOutput(bool $flushOutput): void
|
||||
{
|
||||
$this->flushOutput = $flushOutput;
|
||||
}
|
||||
|
||||
public function isStatFiles(): bool
|
||||
{
|
||||
return $this->statFiles;
|
||||
}
|
||||
|
||||
public function setStatFiles(bool $statFiles): void
|
||||
{
|
||||
$this->statFiles = $statFiles;
|
||||
}
|
||||
|
||||
public function getContentDisposition(): string
|
||||
{
|
||||
return $this->contentDisposition;
|
||||
}
|
||||
|
||||
public function setContentDisposition(string $contentDisposition): void
|
||||
{
|
||||
$this->contentDisposition = $contentDisposition;
|
||||
}
|
||||
|
||||
public function getContentType(): string
|
||||
{
|
||||
return $this->contentType;
|
||||
}
|
||||
|
||||
public function setContentType(string $contentType): void
|
||||
{
|
||||
$this->contentType = $contentType;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return StreamInterface|resource
|
||||
*/
|
||||
public function getOutputStream()
|
||||
{
|
||||
return $this->outputStream;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param StreamInterface|resource $outputStream
|
||||
*/
|
||||
public function setOutputStream($outputStream): void
|
||||
{
|
||||
$this->outputStream = $outputStream;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return int
|
||||
*/
|
||||
public function getDeflateLevel(): int
|
||||
{
|
||||
return $this->deflateLevel;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param int $deflateLevel
|
||||
*/
|
||||
public function setDeflateLevel(int $deflateLevel): void
|
||||
{
|
||||
$this->deflateLevel = $deflateLevel;
|
||||
}
|
||||
}
|
||||
|
|
@ -1,122 +0,0 @@
|
|||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace ZipStream\Option;
|
||||
|
||||
use DateTime;
|
||||
use DateTimeInterface;
|
||||
|
||||
final class File
|
||||
{
|
||||
/**
|
||||
* @var string
|
||||
*/
|
||||
private $comment = '';
|
||||
|
||||
/**
|
||||
* @var Method
|
||||
*/
|
||||
private $method;
|
||||
|
||||
/**
|
||||
* @var int
|
||||
*/
|
||||
private $deflateLevel;
|
||||
|
||||
/**
|
||||
* @var DateTimeInterface
|
||||
*/
|
||||
private $time;
|
||||
|
||||
/**
|
||||
* @var int
|
||||
*/
|
||||
private $size = 0;
|
||||
|
||||
public function defaultTo(Archive $archiveOptions): void
|
||||
{
|
||||
$this->deflateLevel = $this->deflateLevel ?: $archiveOptions->getDeflateLevel();
|
||||
$this->time = $this->time ?: new DateTime();
|
||||
}
|
||||
|
||||
/**
|
||||
* @return string
|
||||
*/
|
||||
public function getComment(): string
|
||||
{
|
||||
return $this->comment;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string $comment
|
||||
*/
|
||||
public function setComment(string $comment): void
|
||||
{
|
||||
$this->comment = $comment;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return Method
|
||||
*/
|
||||
public function getMethod(): Method
|
||||
{
|
||||
return $this->method ?: Method::DEFLATE();
|
||||
}
|
||||
|
||||
/**
|
||||
* @param Method $method
|
||||
*/
|
||||
public function setMethod(Method $method): void
|
||||
{
|
||||
$this->method = $method;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return int
|
||||
*/
|
||||
public function getDeflateLevel(): int
|
||||
{
|
||||
return $this->deflateLevel ?: Archive::DEFAULT_DEFLATE_LEVEL;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param int $deflateLevel
|
||||
*/
|
||||
public function setDeflateLevel(int $deflateLevel): void
|
||||
{
|
||||
$this->deflateLevel = $deflateLevel;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return DateTimeInterface
|
||||
*/
|
||||
public function getTime(): DateTimeInterface
|
||||
{
|
||||
return $this->time;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param DateTimeInterface $time
|
||||
*/
|
||||
public function setTime(DateTimeInterface $time): void
|
||||
{
|
||||
$this->time = $time;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return int
|
||||
*/
|
||||
public function getSize(): int
|
||||
{
|
||||
return $this->size;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param int $size
|
||||
*/
|
||||
public function setSize(int $size): void
|
||||
{
|
||||
$this->size = $size;
|
||||
}
|
||||
}
|
||||
|
|
@ -1,21 +0,0 @@
|
|||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace ZipStream\Option;
|
||||
|
||||
use MyCLabs\Enum\Enum;
|
||||
|
||||
/**
|
||||
* Methods enum
|
||||
*
|
||||
* @method static STORE(): Method
|
||||
* @method static DEFLATE(): Method
|
||||
* @psalm-immutable
|
||||
*/
|
||||
class Method extends Enum
|
||||
{
|
||||
public const STORE = 0x00;
|
||||
|
||||
public const DEFLATE = 0x08;
|
||||
}
|
||||
|
|
@ -1,25 +0,0 @@
|
|||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace ZipStream\Option;
|
||||
|
||||
use MyCLabs\Enum\Enum;
|
||||
|
||||
/**
|
||||
* Class Version
|
||||
* @package ZipStream\Option
|
||||
*
|
||||
* @method static STORE(): Version
|
||||
* @method static DEFLATE(): Version
|
||||
* @method static ZIP64(): Version
|
||||
* @psalm-immutable
|
||||
*/
|
||||
class Version extends Enum
|
||||
{
|
||||
public const STORE = 0x000A; // 1.00
|
||||
|
||||
public const DEFLATE = 0x0014; // 2.00
|
||||
|
||||
public const ZIP64 = 0x002D; // 4.50
|
||||
}
|
||||
56
xunifriend_RaeeC/vendor/maennchen/zipstream-php/src/PackField.php
vendored
Normal file
56
xunifriend_RaeeC/vendor/maennchen/zipstream-php/src/PackField.php
vendored
Normal file
|
|
@ -0,0 +1,56 @@
|
|||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace ZipStream;
|
||||
|
||||
use RuntimeException;
|
||||
|
||||
/**
|
||||
* @internal
|
||||
* TODO: Make class readonly when requiring PHP 8.2 exclusively
|
||||
*/
|
||||
final class PackField
|
||||
{
|
||||
public const MAX_V = 0xFFFFFFFF;
|
||||
|
||||
public const MAX_v = 0xFFFF;
|
||||
|
||||
public function __construct(
|
||||
public readonly string $format,
|
||||
public readonly int|string $value
|
||||
) {}
|
||||
|
||||
/**
|
||||
* Create a format string and argument list for pack(), then call
|
||||
* pack() and return the result.
|
||||
*/
|
||||
public static function pack(self ...$fields): string
|
||||
{
|
||||
$fmt = array_reduce($fields, function (string $acc, self $field) {
|
||||
return $acc . $field->format;
|
||||
}, '');
|
||||
|
||||
$args = array_map(function (self $field) {
|
||||
switch ($field->format) {
|
||||
case 'V':
|
||||
if ($field->value > self::MAX_V) {
|
||||
throw new RuntimeException(print_r($field->value, true) . ' is larger than 32 bits');
|
||||
}
|
||||
break;
|
||||
case 'v':
|
||||
if ($field->value > self::MAX_v) {
|
||||
throw new RuntimeException(print_r($field->value, true) . ' is larger than 16 bits');
|
||||
}
|
||||
break;
|
||||
case 'P': break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
|
||||
return $field->value;
|
||||
}, $fields);
|
||||
|
||||
return pack($fmt, ...$args);
|
||||
}
|
||||
}
|
||||
|
|
@ -1,265 +0,0 @@
|
|||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace ZipStream;
|
||||
|
||||
use function mb_strlen;
|
||||
|
||||
use Psr\Http\Message\StreamInterface;
|
||||
use RuntimeException;
|
||||
|
||||
/**
|
||||
* Describes a data stream.
|
||||
*
|
||||
* Typically, an instance will wrap a PHP stream; this interface provides
|
||||
* a wrapper around the most common operations, including serialization of
|
||||
* the entire stream to a string.
|
||||
*/
|
||||
class Stream implements StreamInterface
|
||||
{
|
||||
protected $stream;
|
||||
|
||||
public function __construct($stream)
|
||||
{
|
||||
$this->stream = $stream;
|
||||
}
|
||||
|
||||
/**
|
||||
* Reads all data from the stream into a string, from the beginning to end.
|
||||
*
|
||||
* This method MUST attempt to seek to the beginning of the stream before
|
||||
* reading data and read the stream until the end is reached.
|
||||
*
|
||||
* Warning: This could attempt to load a large amount of data into memory.
|
||||
*
|
||||
* This method MUST NOT raise an exception in order to conform with PHP's
|
||||
* string casting operations.
|
||||
*
|
||||
* @see http://php.net/manual/en/language.oop5.magic.php#object.tostring
|
||||
* @return string
|
||||
*/
|
||||
public function __toString(): string
|
||||
{
|
||||
try {
|
||||
$this->seek(0);
|
||||
} catch (RuntimeException $e) {
|
||||
}
|
||||
return (string) stream_get_contents($this->stream);
|
||||
}
|
||||
|
||||
/**
|
||||
* Closes the stream and any underlying resources.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function close(): void
|
||||
{
|
||||
if (is_resource($this->stream)) {
|
||||
fclose($this->stream);
|
||||
}
|
||||
$this->detach();
|
||||
}
|
||||
|
||||
/**
|
||||
* Separates any underlying resources from the stream.
|
||||
*
|
||||
* After the stream has been detached, the stream is in an unusable state.
|
||||
*
|
||||
* @return resource|null Underlying PHP stream, if any
|
||||
*/
|
||||
public function detach()
|
||||
{
|
||||
$result = $this->stream;
|
||||
$this->stream = null;
|
||||
return $result;
|
||||
}
|
||||
|
||||
/**
|
||||
* Seek to a position in the stream.
|
||||
*
|
||||
* @link http://www.php.net/manual/en/function.fseek.php
|
||||
* @param int $offset Stream offset
|
||||
* @param int $whence Specifies how the cursor position will be calculated
|
||||
* based on the seek offset. Valid values are identical to the built-in
|
||||
* PHP $whence values for `fseek()`. SEEK_SET: Set position equal to
|
||||
* offset bytes SEEK_CUR: Set position to current location plus offset
|
||||
* SEEK_END: Set position to end-of-stream plus offset.
|
||||
* @throws RuntimeException on failure.
|
||||
*/
|
||||
public function seek($offset, $whence = SEEK_SET): void
|
||||
{
|
||||
if (!$this->isSeekable()) {
|
||||
throw new RuntimeException();
|
||||
}
|
||||
if (fseek($this->stream, $offset, $whence) !== 0) {
|
||||
throw new RuntimeException();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns whether or not the stream is seekable.
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
public function isSeekable(): bool
|
||||
{
|
||||
return (bool)$this->getMetadata('seekable');
|
||||
}
|
||||
|
||||
/**
|
||||
* Get stream metadata as an associative array or retrieve a specific key.
|
||||
*
|
||||
* The keys returned are identical to the keys returned from PHP's
|
||||
* stream_get_meta_data() function.
|
||||
*
|
||||
* @link http://php.net/manual/en/function.stream-get-meta-data.php
|
||||
* @param string $key Specific metadata to retrieve.
|
||||
* @return array|mixed|null Returns an associative array if no key is
|
||||
* provided. Returns a specific key value if a key is provided and the
|
||||
* value is found, or null if the key is not found.
|
||||
*/
|
||||
public function getMetadata($key = null)
|
||||
{
|
||||
$metadata = stream_get_meta_data($this->stream);
|
||||
return $key !== null ? @$metadata[$key] : $metadata;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the size of the stream if known.
|
||||
*
|
||||
* @return int|null Returns the size in bytes if known, or null if unknown.
|
||||
*/
|
||||
public function getSize(): ?int
|
||||
{
|
||||
$stats = fstat($this->stream);
|
||||
return $stats['size'];
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the current position of the file read/write pointer
|
||||
*
|
||||
* @return int Position of the file pointer
|
||||
* @throws RuntimeException on error.
|
||||
*/
|
||||
public function tell(): int
|
||||
{
|
||||
$position = ftell($this->stream);
|
||||
if ($position === false) {
|
||||
throw new RuntimeException();
|
||||
}
|
||||
return $position;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns true if the stream is at the end of the stream.
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
public function eof(): bool
|
||||
{
|
||||
return feof($this->stream);
|
||||
}
|
||||
|
||||
/**
|
||||
* Seek to the beginning of the stream.
|
||||
*
|
||||
* If the stream is not seekable, this method will raise an exception;
|
||||
* otherwise, it will perform a seek(0).
|
||||
*
|
||||
* @see seek()
|
||||
* @link http://www.php.net/manual/en/function.fseek.php
|
||||
* @throws RuntimeException on failure.
|
||||
*/
|
||||
public function rewind(): void
|
||||
{
|
||||
$this->seek(0);
|
||||
}
|
||||
|
||||
/**
|
||||
* Write data to the stream.
|
||||
*
|
||||
* @param string $string The string that is to be written.
|
||||
* @return int Returns the number of bytes written to the stream.
|
||||
* @throws RuntimeException on failure.
|
||||
*/
|
||||
public function write($string): int
|
||||
{
|
||||
if (!$this->isWritable()) {
|
||||
throw new RuntimeException();
|
||||
}
|
||||
if (fwrite($this->stream, $string) === false) {
|
||||
throw new RuntimeException();
|
||||
}
|
||||
return mb_strlen($string);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns whether or not the stream is writable.
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
public function isWritable(): bool
|
||||
{
|
||||
$mode = $this->getMetadata('mode');
|
||||
if (!is_string($mode)) {
|
||||
throw new RuntimeException('Could not get stream mode from metadata!');
|
||||
}
|
||||
return preg_match('/[waxc+]/', $mode) === 1;
|
||||
}
|
||||
|
||||
/**
|
||||
* Read data from the stream.
|
||||
*
|
||||
* @param int $length Read up to $length bytes from the object and return
|
||||
* them. Fewer than $length bytes may be returned if underlying stream
|
||||
* call returns fewer bytes.
|
||||
* @return string Returns the data read from the stream, or an empty string
|
||||
* if no bytes are available.
|
||||
* @throws RuntimeException if an error occurs.
|
||||
*/
|
||||
public function read($length): string
|
||||
{
|
||||
if (!$this->isReadable()) {
|
||||
throw new RuntimeException();
|
||||
}
|
||||
$result = fread($this->stream, $length);
|
||||
if ($result === false) {
|
||||
throw new RuntimeException();
|
||||
}
|
||||
return $result;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns whether or not the stream is readable.
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
public function isReadable(): bool
|
||||
{
|
||||
$mode = $this->getMetadata('mode');
|
||||
if (!is_string($mode)) {
|
||||
throw new RuntimeException('Could not get stream mode from metadata!');
|
||||
}
|
||||
return preg_match('/[r+]/', $mode) === 1;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the remaining contents in a string
|
||||
*
|
||||
* @return string
|
||||
* @throws RuntimeException if unable to read or an error occurs while
|
||||
* reading.
|
||||
*/
|
||||
public function getContents(): string
|
||||
{
|
||||
if (!$this->isReadable()) {
|
||||
throw new RuntimeException();
|
||||
}
|
||||
$result = stream_get_contents($this->stream);
|
||||
if ($result === false) {
|
||||
throw new RuntimeException();
|
||||
}
|
||||
return $result;
|
||||
}
|
||||
}
|
||||
253
xunifriend_RaeeC/vendor/maennchen/zipstream-php/src/Stream/CallbackStreamWrapper.php
vendored
Normal file
253
xunifriend_RaeeC/vendor/maennchen/zipstream-php/src/Stream/CallbackStreamWrapper.php
vendored
Normal file
|
|
@ -0,0 +1,253 @@
|
|||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace ZipStream\Stream;
|
||||
|
||||
use RuntimeException;
|
||||
use Throwable;
|
||||
|
||||
/**
|
||||
* Stream wrapper that allows writing data to a callback function.
|
||||
*
|
||||
* This wrapper creates a virtual stream that forwards all written data
|
||||
* to a provided callback function, enabling custom output handling
|
||||
* such as streaming to HTTP responses, files, or other destinations.
|
||||
*
|
||||
* @psalm-suppress UnusedClass Used dynamically through stream_wrapper_register
|
||||
*/
|
||||
final class CallbackStreamWrapper
|
||||
{
|
||||
public const PROTOCOL = 'zipcb';
|
||||
|
||||
/** @var resource|null */
|
||||
public $context;
|
||||
|
||||
/** @var array<string, callable(string):void> Map of stream IDs to callback functions */
|
||||
private static array $callbacks = [];
|
||||
|
||||
/** @var string|null Unique identifier for this stream instance */
|
||||
private ?string $id = null;
|
||||
|
||||
/** @var int Current position in the stream */
|
||||
private int $pos = 0;
|
||||
|
||||
/**
|
||||
* Destructor - ensures cleanup even if stream_close() isn't called.
|
||||
* Prevents memory leaks in long-running processes.
|
||||
*/
|
||||
public function __destruct()
|
||||
{
|
||||
$this->stream_close();
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a new callback stream.
|
||||
*
|
||||
* @param callable(string):void $callback Function to call with written data
|
||||
* @return resource|false Stream resource or false on failure
|
||||
*/
|
||||
public static function open(callable $callback)
|
||||
{
|
||||
if (!in_array(self::PROTOCOL, stream_get_wrappers(), true)) {
|
||||
if (!stream_wrapper_register(self::PROTOCOL, self::class)) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
// Generate cryptographically secure unique ID to prevent collisions
|
||||
$id = 'cb_' . bin2hex(random_bytes(16));
|
||||
self::$callbacks[$id] = $callback;
|
||||
|
||||
return fopen(self::PROTOCOL . "://{$id}", 'wb');
|
||||
}
|
||||
|
||||
/**
|
||||
* Clean up all registered callbacks (useful for testing).
|
||||
*
|
||||
* @internal
|
||||
*/
|
||||
public static function cleanup(): void
|
||||
{
|
||||
self::$callbacks = [];
|
||||
}
|
||||
|
||||
/**
|
||||
* Open the stream.
|
||||
*
|
||||
* @param string $path Stream path containing the callback ID
|
||||
* @param string $mode File mode (must contain 'w' for writing)
|
||||
* @param int $options Stream options (required by interface, unused)
|
||||
* @param string|null $opened_path Opened path reference (required by interface, unused)
|
||||
* @return bool True if stream opened successfully
|
||||
* @psalm-suppress UnusedParam $options and $opened_path are required by the stream wrapper interface
|
||||
*/
|
||||
public function stream_open(string $path, string $mode, int $options, ?string &$opened_path): bool
|
||||
{
|
||||
if (!str_contains($mode, 'w')) {
|
||||
return false;
|
||||
}
|
||||
|
||||
$host = parse_url($path, PHP_URL_HOST);
|
||||
if ($host === false || $host === null) {
|
||||
return false;
|
||||
}
|
||||
|
||||
$this->id = $host;
|
||||
return isset(self::$callbacks[$this->id]);
|
||||
}
|
||||
|
||||
/**
|
||||
* Write data to the callback.
|
||||
*
|
||||
* @param string $data Data to write
|
||||
* @return int Number of bytes written
|
||||
* @throws RuntimeException If callback execution fails
|
||||
*/
|
||||
public function stream_write(string $data): int
|
||||
{
|
||||
if ($this->id === null) {
|
||||
trigger_error('Stream not properly initialized', E_USER_WARNING);
|
||||
return 0;
|
||||
}
|
||||
|
||||
$callback = self::$callbacks[$this->id] ?? null;
|
||||
if ($callback === null) {
|
||||
trigger_error('Callback not found for stream', E_USER_WARNING);
|
||||
return 0;
|
||||
}
|
||||
|
||||
try {
|
||||
$callback($data);
|
||||
} catch (Throwable $e) {
|
||||
throw new RuntimeException(
|
||||
'Callback function failed during stream write: ' . $e->getMessage(),
|
||||
0,
|
||||
$e
|
||||
);
|
||||
}
|
||||
|
||||
$length = strlen($data);
|
||||
$this->pos += $length;
|
||||
return $length;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get current position in stream.
|
||||
*
|
||||
* @return int Current position
|
||||
*/
|
||||
public function stream_tell(): int
|
||||
{
|
||||
return $this->pos;
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if stream has reached end of file.
|
||||
*
|
||||
* @return bool Always false for write-only streams
|
||||
*/
|
||||
public function stream_eof(): bool
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Flush stream buffers.
|
||||
*
|
||||
* @return bool Always true (no buffering)
|
||||
*/
|
||||
public function stream_flush(): bool
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Close the stream and clean up callback.
|
||||
*/
|
||||
public function stream_close(): void
|
||||
{
|
||||
if ($this->id !== null) {
|
||||
unset(self::$callbacks[$this->id]);
|
||||
$this->id = null;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Get stream statistics.
|
||||
*
|
||||
* @return array<string, mixed> Stream statistics
|
||||
*/
|
||||
public function stream_stat(): array
|
||||
{
|
||||
return [
|
||||
'dev' => 0,
|
||||
'ino' => 0,
|
||||
'mode' => 0o100666, // Regular file, read/write permissions
|
||||
'nlink' => 1,
|
||||
'uid' => 0,
|
||||
'gid' => 0,
|
||||
'rdev' => 0,
|
||||
'size' => $this->pos,
|
||||
'atime' => time(),
|
||||
'mtime' => time(),
|
||||
'ctime' => time(),
|
||||
'blksize' => 4096,
|
||||
'blocks' => ceil($this->pos / 4096),
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* Read data from stream (not supported - write-only stream).
|
||||
*
|
||||
* @param int $count Number of bytes to read (required by interface, unused)
|
||||
* @return string Always empty string
|
||||
* @psalm-suppress UnusedParam $count is required by the stream wrapper interface
|
||||
*/
|
||||
public function stream_read(int $count): string
|
||||
{
|
||||
trigger_error('Read operations not supported on callback streams', E_USER_WARNING);
|
||||
return '';
|
||||
}
|
||||
|
||||
/**
|
||||
* Seek to position in stream (not supported).
|
||||
*
|
||||
* @param int $offset Offset to seek to (required by interface, unused)
|
||||
* @param int $whence Seek mode (required by interface, unused)
|
||||
* @return bool Always false
|
||||
* @psalm-suppress UnusedParam $offset and $whence are required by the stream wrapper interface
|
||||
*/
|
||||
public function stream_seek(int $offset, int $whence = SEEK_SET): bool
|
||||
{
|
||||
trigger_error('Seek operations not supported on callback streams', E_USER_WARNING);
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set options on stream (not supported).
|
||||
*
|
||||
* @param int $option Option to set (required by interface, unused)
|
||||
* @param int $arg1 First argument (required by interface, unused)
|
||||
* @param int $arg2 Second argument (required by interface, unused)
|
||||
* @return bool Always false
|
||||
* @psalm-suppress UnusedParam All parameters are required by the stream wrapper interface
|
||||
*/
|
||||
public function stream_set_option(int $option, int $arg1, int $arg2): bool
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Truncate stream (not supported).
|
||||
*
|
||||
* @param int $new_size New size (required by interface, unused)
|
||||
* @return bool Always false
|
||||
* @psalm-suppress UnusedParam $new_size is required by the stream wrapper interface
|
||||
*/
|
||||
public function stream_truncate(int $new_size): bool
|
||||
{
|
||||
trigger_error('Truncate operations not supported on callback streams', E_USER_WARNING);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
39
xunifriend_RaeeC/vendor/maennchen/zipstream-php/src/Time.php
vendored
Normal file
39
xunifriend_RaeeC/vendor/maennchen/zipstream-php/src/Time.php
vendored
Normal file
|
|
@ -0,0 +1,39 @@
|
|||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace ZipStream;
|
||||
|
||||
use DateInterval;
|
||||
use DateTimeImmutable;
|
||||
use DateTimeInterface;
|
||||
use ZipStream\Exception\DosTimeOverflowException;
|
||||
|
||||
/**
|
||||
* @internal
|
||||
*/
|
||||
abstract class Time
|
||||
{
|
||||
private const DOS_MINIMUM_DATE = '1980-01-01 00:00:00Z';
|
||||
|
||||
public static function dateTimeToDosTime(DateTimeInterface $dateTime): int
|
||||
{
|
||||
$dosMinimumDate = new DateTimeImmutable(self::DOS_MINIMUM_DATE);
|
||||
|
||||
if ($dateTime->getTimestamp() < $dosMinimumDate->getTimestamp()) {
|
||||
throw new DosTimeOverflowException(dateTime: $dateTime);
|
||||
}
|
||||
|
||||
$dateTime = DateTimeImmutable::createFromInterface($dateTime)->sub(new DateInterval('P1980Y'));
|
||||
|
||||
[$year, $month, $day, $hour, $minute, $second] = explode(' ', $dateTime->format('Y n j G i s'));
|
||||
|
||||
return
|
||||
((int) $year << 25)
|
||||
| ((int) $month << 21)
|
||||
| ((int) $day << 16)
|
||||
| ((int) $hour << 11)
|
||||
| ((int) $minute << 5)
|
||||
| ((int) $second >> 1);
|
||||
}
|
||||
}
|
||||
15
xunifriend_RaeeC/vendor/maennchen/zipstream-php/src/Version.php
vendored
Normal file
15
xunifriend_RaeeC/vendor/maennchen/zipstream-php/src/Version.php
vendored
Normal file
|
|
@ -0,0 +1,15 @@
|
|||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace ZipStream;
|
||||
|
||||
/**
|
||||
* @api
|
||||
*/
|
||||
enum Version: int
|
||||
{
|
||||
case STORE = 0x000A; // 1.00
|
||||
case DEFLATE = 0x0014; // 2.00
|
||||
case ZIP64 = 0x002D; // 4.50
|
||||
}
|
||||
28
xunifriend_RaeeC/vendor/maennchen/zipstream-php/src/Zip64/DataDescriptor.php
vendored
Normal file
28
xunifriend_RaeeC/vendor/maennchen/zipstream-php/src/Zip64/DataDescriptor.php
vendored
Normal file
|
|
@ -0,0 +1,28 @@
|
|||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace ZipStream\Zip64;
|
||||
|
||||
use ZipStream\PackField;
|
||||
|
||||
/**
|
||||
* @internal
|
||||
*/
|
||||
abstract class DataDescriptor
|
||||
{
|
||||
private const SIGNATURE = 0x08074b50;
|
||||
|
||||
public static function generate(
|
||||
int $crc32UncompressedData,
|
||||
int $compressedSize,
|
||||
int $uncompressedSize,
|
||||
): string {
|
||||
return PackField::pack(
|
||||
new PackField(format: 'V', value: self::SIGNATURE),
|
||||
new PackField(format: 'V', value: $crc32UncompressedData),
|
||||
new PackField(format: 'P', value: $compressedSize),
|
||||
new PackField(format: 'P', value: $uncompressedSize),
|
||||
);
|
||||
}
|
||||
}
|
||||
43
xunifriend_RaeeC/vendor/maennchen/zipstream-php/src/Zip64/EndOfCentralDirectory.php
vendored
Normal file
43
xunifriend_RaeeC/vendor/maennchen/zipstream-php/src/Zip64/EndOfCentralDirectory.php
vendored
Normal file
|
|
@ -0,0 +1,43 @@
|
|||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace ZipStream\Zip64;
|
||||
|
||||
use ZipStream\PackField;
|
||||
|
||||
/**
|
||||
* @internal
|
||||
*/
|
||||
abstract class EndOfCentralDirectory
|
||||
{
|
||||
private const SIGNATURE = 0x06064b50;
|
||||
|
||||
public static function generate(
|
||||
int $versionMadeBy,
|
||||
int $versionNeededToExtract,
|
||||
int $numberOfThisDisk,
|
||||
int $numberOfTheDiskWithCentralDirectoryStart,
|
||||
int $numberOfCentralDirectoryEntriesOnThisDisk,
|
||||
int $numberOfCentralDirectoryEntries,
|
||||
int $sizeOfCentralDirectory,
|
||||
int $centralDirectoryStartOffsetOnDisk,
|
||||
string $extensibleDataSector,
|
||||
): string {
|
||||
$recordSize = 44 + strlen($extensibleDataSector); // (length of block - 12) = 44;
|
||||
|
||||
/** @psalm-suppress MixedArgument */
|
||||
return PackField::pack(
|
||||
new PackField(format: 'V', value: static::SIGNATURE),
|
||||
new PackField(format: 'P', value: $recordSize),
|
||||
new PackField(format: 'v', value: $versionMadeBy),
|
||||
new PackField(format: 'v', value: $versionNeededToExtract),
|
||||
new PackField(format: 'V', value: $numberOfThisDisk),
|
||||
new PackField(format: 'V', value: $numberOfTheDiskWithCentralDirectoryStart),
|
||||
new PackField(format: 'P', value: $numberOfCentralDirectoryEntriesOnThisDisk),
|
||||
new PackField(format: 'P', value: $numberOfCentralDirectoryEntries),
|
||||
new PackField(format: 'P', value: $sizeOfCentralDirectory),
|
||||
new PackField(format: 'P', value: $centralDirectoryStartOffsetOnDisk),
|
||||
) . $extensibleDataSector;
|
||||
}
|
||||
}
|
||||
29
xunifriend_RaeeC/vendor/maennchen/zipstream-php/src/Zip64/EndOfCentralDirectoryLocator.php
vendored
Normal file
29
xunifriend_RaeeC/vendor/maennchen/zipstream-php/src/Zip64/EndOfCentralDirectoryLocator.php
vendored
Normal file
|
|
@ -0,0 +1,29 @@
|
|||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace ZipStream\Zip64;
|
||||
|
||||
use ZipStream\PackField;
|
||||
|
||||
/**
|
||||
* @internal
|
||||
*/
|
||||
abstract class EndOfCentralDirectoryLocator
|
||||
{
|
||||
private const SIGNATURE = 0x07064b50;
|
||||
|
||||
public static function generate(
|
||||
int $numberOfTheDiskWithZip64CentralDirectoryStart,
|
||||
int $zip64centralDirectoryStartOffsetOnDisk,
|
||||
int $totalNumberOfDisks,
|
||||
): string {
|
||||
/** @psalm-suppress MixedArgument */
|
||||
return PackField::pack(
|
||||
new PackField(format: 'V', value: static::SIGNATURE),
|
||||
new PackField(format: 'V', value: $numberOfTheDiskWithZip64CentralDirectoryStart),
|
||||
new PackField(format: 'P', value: $zip64centralDirectoryStartOffsetOnDisk),
|
||||
new PackField(format: 'V', value: $totalNumberOfDisks),
|
||||
);
|
||||
}
|
||||
}
|
||||
45
xunifriend_RaeeC/vendor/maennchen/zipstream-php/src/Zip64/ExtendedInformationExtraField.php
vendored
Normal file
45
xunifriend_RaeeC/vendor/maennchen/zipstream-php/src/Zip64/ExtendedInformationExtraField.php
vendored
Normal file
|
|
@ -0,0 +1,45 @@
|
|||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace ZipStream\Zip64;
|
||||
|
||||
use ZipStream\PackField;
|
||||
|
||||
/**
|
||||
* @internal
|
||||
*/
|
||||
abstract class ExtendedInformationExtraField
|
||||
{
|
||||
private const TAG = 0x0001;
|
||||
|
||||
public static function generate(
|
||||
?int $originalSize = null,
|
||||
?int $compressedSize = null,
|
||||
?int $relativeHeaderOffset = null,
|
||||
?int $diskStartNumber = null,
|
||||
): string {
|
||||
return PackField::pack(
|
||||
new PackField(format: 'v', value: self::TAG),
|
||||
new PackField(
|
||||
format: 'v',
|
||||
value: ($originalSize === null ? 0 : 8)
|
||||
+ ($compressedSize === null ? 0 : 8)
|
||||
+ ($relativeHeaderOffset === null ? 0 : 8)
|
||||
+ ($diskStartNumber === null ? 0 : 4)
|
||||
),
|
||||
...($originalSize === null ? [] : [
|
||||
new PackField(format: 'P', value: $originalSize),
|
||||
]),
|
||||
...($compressedSize === null ? [] : [
|
||||
new PackField(format: 'P', value: $compressedSize),
|
||||
]),
|
||||
...($relativeHeaderOffset === null ? [] : [
|
||||
new PackField(format: 'P', value: $relativeHeaderOffset),
|
||||
]),
|
||||
...($diskStartNumber === null ? [] : [
|
||||
new PackField(format: 'V', value: $diskStartNumber),
|
||||
]),
|
||||
);
|
||||
}
|
||||
}
|
||||
File diff suppressed because it is too large
Load Diff
23
xunifriend_RaeeC/vendor/maennchen/zipstream-php/src/Zs/ExtendedInformationExtraField.php
vendored
Normal file
23
xunifriend_RaeeC/vendor/maennchen/zipstream-php/src/Zs/ExtendedInformationExtraField.php
vendored
Normal file
|
|
@ -0,0 +1,23 @@
|
|||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace ZipStream\Zs;
|
||||
|
||||
use ZipStream\PackField;
|
||||
|
||||
/**
|
||||
* @internal
|
||||
*/
|
||||
abstract class ExtendedInformationExtraField
|
||||
{
|
||||
private const TAG = 0x5653;
|
||||
|
||||
public static function generate(): string
|
||||
{
|
||||
return PackField::pack(
|
||||
new PackField(format: 'v', value: self::TAG),
|
||||
new PackField(format: 'v', value: 0x0000),
|
||||
);
|
||||
}
|
||||
}
|
||||
49
xunifriend_RaeeC/vendor/maennchen/zipstream-php/test/Assertions.php
vendored
Normal file
49
xunifriend_RaeeC/vendor/maennchen/zipstream-php/test/Assertions.php
vendored
Normal file
|
|
@ -0,0 +1,49 @@
|
|||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace ZipStream\Test;
|
||||
|
||||
trait Assertions
|
||||
{
|
||||
protected function assertFileContains(string $filePath, string $needle): void
|
||||
{
|
||||
$last = '';
|
||||
|
||||
$handle = fopen($filePath, 'r');
|
||||
while (!feof($handle)) {
|
||||
$line = fgets($handle, 1024);
|
||||
|
||||
if (str_contains($last . $line, $needle)) {
|
||||
fclose($handle);
|
||||
return;
|
||||
}
|
||||
|
||||
$last = $line;
|
||||
}
|
||||
|
||||
fclose($handle);
|
||||
|
||||
$this->fail("File {$filePath} must contain {$needle}");
|
||||
}
|
||||
|
||||
protected function assertFileDoesNotContain(string $filePath, string $needle): void
|
||||
{
|
||||
$last = '';
|
||||
|
||||
$handle = fopen($filePath, 'r');
|
||||
while (!feof($handle)) {
|
||||
$line = fgets($handle, 1024);
|
||||
|
||||
if (str_contains($last . $line, $needle)) {
|
||||
fclose($handle);
|
||||
|
||||
$this->fail("File {$filePath} must not contain {$needle}");
|
||||
}
|
||||
|
||||
$last = $line;
|
||||
}
|
||||
|
||||
fclose($handle);
|
||||
}
|
||||
}
|
||||
|
|
@ -1,66 +0,0 @@
|
|||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace BigintTest;
|
||||
|
||||
use OverflowException;
|
||||
use PHPUnit\Framework\TestCase;
|
||||
use ZipStream\Bigint;
|
||||
|
||||
class BigintTest extends TestCase
|
||||
{
|
||||
public function testConstruct(): void
|
||||
{
|
||||
$bigint = new Bigint(0x12345678);
|
||||
$this->assertSame('0x0000000012345678', $bigint->getHex64());
|
||||
$this->assertSame(0x12345678, $bigint->getLow32());
|
||||
$this->assertSame(0, $bigint->getHigh32());
|
||||
}
|
||||
|
||||
public function testConstructLarge(): void
|
||||
{
|
||||
$bigint = new Bigint(0x87654321);
|
||||
$this->assertSame('0x0000000087654321', $bigint->getHex64());
|
||||
$this->assertSame('87654321', bin2hex(pack('N', $bigint->getLow32())));
|
||||
$this->assertSame(0, $bigint->getHigh32());
|
||||
}
|
||||
|
||||
public function testAddSmallValue(): void
|
||||
{
|
||||
$bigint = new Bigint(1);
|
||||
$bigint = $bigint->add(Bigint::init(2));
|
||||
$this->assertSame(3, $bigint->getLow32());
|
||||
$this->assertFalse($bigint->isOver32());
|
||||
$this->assertTrue($bigint->isOver32(true));
|
||||
$this->assertSame($bigint->getLowFF(), (float)$bigint->getLow32());
|
||||
$this->assertSame($bigint->getLowFF(true), (float)0xFFFFFFFF);
|
||||
}
|
||||
|
||||
public function testAddWithOverflowAtLowestByte(): void
|
||||
{
|
||||
$bigint = new Bigint(0xFF);
|
||||
$bigint = $bigint->add(Bigint::init(0x01));
|
||||
$this->assertSame(0x100, $bigint->getLow32());
|
||||
}
|
||||
|
||||
public function testAddWithOverflowAtInteger32(): void
|
||||
{
|
||||
$bigint = new Bigint(0xFFFFFFFE);
|
||||
$this->assertFalse($bigint->isOver32());
|
||||
$bigint = $bigint->add(Bigint::init(0x01));
|
||||
$this->assertTrue($bigint->isOver32());
|
||||
$bigint = $bigint->add(Bigint::init(0x01));
|
||||
$this->assertSame('0x0000000100000000', $bigint->getHex64());
|
||||
$this->assertTrue($bigint->isOver32());
|
||||
$this->assertSame((float)0xFFFFFFFF, $bigint->getLowFF());
|
||||
}
|
||||
|
||||
public function testAddWithOverflowAtInteger64(): void
|
||||
{
|
||||
$bigint = Bigint::fromLowHigh(0xFFFFFFFF, 0xFFFFFFFF);
|
||||
$this->assertSame('0xFFFFFFFFFFFFFFFF', $bigint->getHex64());
|
||||
$this->expectException(OverflowException::class);
|
||||
$bigint->add(Bigint::init(1));
|
||||
}
|
||||
}
|
||||
202
xunifriend_RaeeC/vendor/maennchen/zipstream-php/test/CallbackOutputTest.php
vendored
Normal file
202
xunifriend_RaeeC/vendor/maennchen/zipstream-php/test/CallbackOutputTest.php
vendored
Normal file
|
|
@ -0,0 +1,202 @@
|
|||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace ZipStream\Test;
|
||||
|
||||
use PHPUnit\Framework\TestCase;
|
||||
use RuntimeException;
|
||||
use ZipArchive;
|
||||
use ZipStream\Stream\CallbackStreamWrapper;
|
||||
use ZipStream\ZipStream;
|
||||
|
||||
final class CallbackOutputTest extends TestCase
|
||||
{
|
||||
protected function tearDown(): void
|
||||
{
|
||||
// Clean up any registered callbacks to prevent memory leaks in tests
|
||||
CallbackStreamWrapper::cleanup();
|
||||
parent::tearDown();
|
||||
}
|
||||
|
||||
public function testDataIsForwardedToCallback(): void
|
||||
{
|
||||
$buf = '';
|
||||
$zip = new ZipStream(
|
||||
outputStream: CallbackStreamWrapper::open(
|
||||
static function (string $chunk) use (&$buf): void { $buf .= $chunk; }
|
||||
),
|
||||
sendHttpHeaders: false
|
||||
);
|
||||
|
||||
$zip->addFile('hello.txt', 'Hello World');
|
||||
$zip->finish();
|
||||
|
||||
$tmp = tmpfile();
|
||||
fwrite($tmp, $buf);
|
||||
rewind($tmp);
|
||||
|
||||
$meta = stream_get_meta_data($tmp);
|
||||
$za = new ZipArchive();
|
||||
$za->open($meta['uri']);
|
||||
|
||||
$content = $za->getFromName('hello.txt');
|
||||
$za->close();
|
||||
fclose($tmp);
|
||||
|
||||
$this->assertSame('Hello World', $content);
|
||||
}
|
||||
|
||||
public function testMultipleSimultaneousStreams(): void
|
||||
{
|
||||
$buf1 = '';
|
||||
$buf2 = '';
|
||||
|
||||
$stream1 = CallbackStreamWrapper::open(
|
||||
static function (string $chunk) use (&$buf1): void { $buf1 .= $chunk; }
|
||||
);
|
||||
$stream2 = CallbackStreamWrapper::open(
|
||||
static function (string $chunk) use (&$buf2): void { $buf2 .= $chunk; }
|
||||
);
|
||||
|
||||
$this->assertIsResource($stream1);
|
||||
$this->assertIsResource($stream2);
|
||||
|
||||
fwrite($stream1, 'data1');
|
||||
fwrite($stream2, 'data2');
|
||||
fclose($stream1);
|
||||
fclose($stream2);
|
||||
|
||||
$this->assertSame('data1', $buf1);
|
||||
$this->assertSame('data2', $buf2);
|
||||
}
|
||||
|
||||
public function testExceptionHandlingInCallback(): void
|
||||
{
|
||||
$stream = CallbackStreamWrapper::open(
|
||||
static function (string $chunk): void {
|
||||
throw new RuntimeException('Callback error');
|
||||
}
|
||||
);
|
||||
|
||||
$this->expectException(RuntimeException::class);
|
||||
$this->expectExceptionMessage('Callback function failed during stream write: Callback error');
|
||||
|
||||
fwrite($stream, 'test data');
|
||||
}
|
||||
|
||||
public function testLargeDataChunks(): void
|
||||
{
|
||||
$receivedChunks = [];
|
||||
$totalBytes = 0;
|
||||
|
||||
$stream = CallbackStreamWrapper::open(
|
||||
static function (string $chunk) use (&$receivedChunks, &$totalBytes): void {
|
||||
$receivedChunks[] = strlen($chunk);
|
||||
$totalBytes += strlen($chunk);
|
||||
}
|
||||
);
|
||||
|
||||
// Write large chunks of data
|
||||
$largeData = str_repeat('x', 65536); // 64KB
|
||||
fwrite($stream, $largeData);
|
||||
fwrite($stream, $largeData);
|
||||
fclose($stream);
|
||||
|
||||
$this->assertSame(131072, $totalBytes); // 128KB total
|
||||
$this->assertNotEmpty($receivedChunks);
|
||||
// Large data should be written (possibly in multiple chunks)
|
||||
$this->assertGreaterThan(0, max($receivedChunks));
|
||||
}
|
||||
|
||||
public function testStreamPositionTracking(): void
|
||||
{
|
||||
$stream = CallbackStreamWrapper::open(
|
||||
static function (string $chunk): void { /* no-op */ }
|
||||
);
|
||||
|
||||
$this->assertSame(0, ftell($stream));
|
||||
|
||||
fwrite($stream, 'hello');
|
||||
$this->assertSame(5, ftell($stream));
|
||||
|
||||
fwrite($stream, ' world');
|
||||
$this->assertSame(11, ftell($stream));
|
||||
|
||||
fclose($stream);
|
||||
}
|
||||
|
||||
public function testInvalidModeRejection(): void
|
||||
{
|
||||
$stream = CallbackStreamWrapper::open(
|
||||
static function (string $chunk): void { /* no-op */ }
|
||||
);
|
||||
|
||||
// Close the stream first
|
||||
fclose($stream);
|
||||
|
||||
// Try to open with read mode - should fail
|
||||
$readStream = fopen('zipcb://invalid', 'rb');
|
||||
$this->assertFalse($readStream);
|
||||
}
|
||||
|
||||
public function testStreamStatistics(): void
|
||||
{
|
||||
$stream = CallbackStreamWrapper::open(
|
||||
static function (string $chunk): void { /* no-op */ }
|
||||
);
|
||||
|
||||
fwrite($stream, 'test data');
|
||||
|
||||
$stats = fstat($stream);
|
||||
$this->assertIsArray($stats);
|
||||
$this->assertSame(9, $stats['size']); // Length of 'test data'
|
||||
$this->assertSame(0o100666, $stats['mode']); // Regular file permissions
|
||||
|
||||
fclose($stream);
|
||||
}
|
||||
|
||||
public function testProgressTracking(): void
|
||||
{
|
||||
$progress = [];
|
||||
$totalBytes = 0;
|
||||
|
||||
$zip = new ZipStream(
|
||||
outputStream: CallbackStreamWrapper::open(
|
||||
static function (string $chunk) use (&$progress, &$totalBytes): void {
|
||||
$totalBytes += strlen($chunk);
|
||||
$progress[] = $totalBytes;
|
||||
}
|
||||
),
|
||||
sendHttpHeaders: false
|
||||
);
|
||||
|
||||
$zip->addFile('file1.txt', 'Content 1');
|
||||
$zip->addFile('file2.txt', 'Content 2');
|
||||
$zip->finish();
|
||||
|
||||
$this->assertNotEmpty($progress);
|
||||
$this->assertGreaterThan(0, $totalBytes);
|
||||
$this->assertTrue(count($progress) > 1, 'Should have multiple progress updates');
|
||||
}
|
||||
|
||||
public function testCallbackCleanupOnClose(): void
|
||||
{
|
||||
$callbackExecuted = false;
|
||||
|
||||
$stream = CallbackStreamWrapper::open(
|
||||
static function (string $chunk) use (&$callbackExecuted): void {
|
||||
$callbackExecuted = true;
|
||||
}
|
||||
);
|
||||
|
||||
fwrite($stream, 'test');
|
||||
$this->assertTrue($callbackExecuted);
|
||||
|
||||
fclose($stream);
|
||||
|
||||
// After closing, callback should be cleaned up
|
||||
// We can't directly test this, but the tearDown cleanup should work without issues
|
||||
$this->assertTrue(true); // Placeholder assertion
|
||||
}
|
||||
}
|
||||
60
xunifriend_RaeeC/vendor/maennchen/zipstream-php/test/CentralDirectoryFileHeaderTest.php
vendored
Normal file
60
xunifriend_RaeeC/vendor/maennchen/zipstream-php/test/CentralDirectoryFileHeaderTest.php
vendored
Normal file
|
|
@ -0,0 +1,60 @@
|
|||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace ZipStream\Test;
|
||||
|
||||
use DateTimeImmutable;
|
||||
use PHPUnit\Framework\TestCase;
|
||||
use ZipStream\CentralDirectoryFileHeader;
|
||||
use ZipStream\CompressionMethod;
|
||||
|
||||
class CentralDirectoryFileHeaderTest extends TestCase
|
||||
{
|
||||
public function testSerializesCorrectly(): void
|
||||
{
|
||||
$dateTime = new DateTimeImmutable('2022-01-01 01:01:01Z');
|
||||
|
||||
$header = CentralDirectoryFileHeader::generate(
|
||||
versionMadeBy: 0x603,
|
||||
versionNeededToExtract: 0x002D,
|
||||
generalPurposeBitFlag: 0x2222,
|
||||
compressionMethod: CompressionMethod::DEFLATE,
|
||||
lastModificationDateTime: $dateTime,
|
||||
crc32: 0x11111111,
|
||||
compressedSize: 0x77777777,
|
||||
uncompressedSize: 0x99999999,
|
||||
fileName: 'test.png',
|
||||
extraField: 'some content',
|
||||
fileComment: 'some comment',
|
||||
diskNumberStart: 0,
|
||||
internalFileAttributes: 0,
|
||||
externalFileAttributes: 32,
|
||||
relativeOffsetOfLocalHeader: 0x1234,
|
||||
);
|
||||
|
||||
$this->assertSame(
|
||||
bin2hex($header),
|
||||
'504b0102' // 4 bytes; central file header signature
|
||||
. '0306' // 2 bytes; version made by
|
||||
. '2d00' // 2 bytes; version needed to extract
|
||||
. '2222' // 2 bytes; general purpose bit flag
|
||||
. '0800' // 2 bytes; compression method
|
||||
. '2008' // 2 bytes; last mod file time
|
||||
. '2154' // 2 bytes; last mod file date
|
||||
. '11111111' // 4 bytes; crc-32
|
||||
. '77777777' // 4 bytes; compressed size
|
||||
. '99999999' // 4 bytes; uncompressed size
|
||||
. '0800' // 2 bytes; file name length (n)
|
||||
. '0c00' // 2 bytes; extra field length (m)
|
||||
. '0c00' // 2 bytes; file comment length (o)
|
||||
. '0000' // 2 bytes; disk number start
|
||||
. '0000' // 2 bytes; internal file attributes
|
||||
. '20000000' // 4 bytes; external file attributes
|
||||
. '34120000' // 4 bytes; relative offset of local header
|
||||
. '746573742e706e67' // n bytes; file name
|
||||
. '736f6d6520636f6e74656e74' // m bytes; extra field
|
||||
. '736f6d6520636f6d6d656e74' // o bytes; file comment
|
||||
);
|
||||
}
|
||||
}
|
||||
26
xunifriend_RaeeC/vendor/maennchen/zipstream-php/test/DataDescriptorTest.php
vendored
Normal file
26
xunifriend_RaeeC/vendor/maennchen/zipstream-php/test/DataDescriptorTest.php
vendored
Normal file
|
|
@ -0,0 +1,26 @@
|
|||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace ZipStream\Test;
|
||||
|
||||
use PHPUnit\Framework\TestCase;
|
||||
use ZipStream\DataDescriptor;
|
||||
|
||||
class DataDescriptorTest extends TestCase
|
||||
{
|
||||
public function testSerializesCorrectly(): void
|
||||
{
|
||||
$this->assertSame(
|
||||
bin2hex(DataDescriptor::generate(
|
||||
crc32UncompressedData: 0x11111111,
|
||||
compressedSize: 0x77777777,
|
||||
uncompressedSize: 0x99999999,
|
||||
)),
|
||||
'504b0708' // 4 bytes; Optional data descriptor signature = 0x08074b50
|
||||
. '11111111' // 4 bytes; CRC-32 of uncompressed data
|
||||
. '77777777' // 4 bytes; Compressed size
|
||||
. '99999999' // 4 bytes; Uncompressed size
|
||||
);
|
||||
}
|
||||
}
|
||||
35
xunifriend_RaeeC/vendor/maennchen/zipstream-php/test/EndOfCentralDirectoryTest.php
vendored
Normal file
35
xunifriend_RaeeC/vendor/maennchen/zipstream-php/test/EndOfCentralDirectoryTest.php
vendored
Normal file
|
|
@ -0,0 +1,35 @@
|
|||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace ZipStream\Test;
|
||||
|
||||
use PHPUnit\Framework\TestCase;
|
||||
use ZipStream\EndOfCentralDirectory;
|
||||
|
||||
class EndOfCentralDirectoryTest extends TestCase
|
||||
{
|
||||
public function testSerializesCorrectly(): void
|
||||
{
|
||||
$this->assertSame(
|
||||
bin2hex(EndOfCentralDirectory::generate(
|
||||
numberOfThisDisk: 0x00,
|
||||
numberOfTheDiskWithCentralDirectoryStart: 0x00,
|
||||
numberOfCentralDirectoryEntriesOnThisDisk: 0x10,
|
||||
numberOfCentralDirectoryEntries: 0x10,
|
||||
sizeOfCentralDirectory: 0x22,
|
||||
centralDirectoryStartOffsetOnDisk: 0x33,
|
||||
zipFileComment: 'foo',
|
||||
)),
|
||||
'504b0506' // 4 bytes; end of central dir signature 0x06054b50
|
||||
. '0000' // 2 bytes; number of this disk
|
||||
. '0000' // 2 bytes; number of the disk with the start of the central directory
|
||||
. '1000' // 2 bytes; total number of entries in the central directory on this disk
|
||||
. '1000' // 2 bytes; total number of entries in the central directory
|
||||
. '22000000' // 4 bytes; size of the central directory
|
||||
. '33000000' // 4 bytes; offset of start of central directory with respect to the starting disk number
|
||||
. '0300' // 2 bytes; .ZIP file comment length
|
||||
. bin2hex('foo')
|
||||
);
|
||||
}
|
||||
}
|
||||
104
xunifriend_RaeeC/vendor/maennchen/zipstream-php/test/EndlessCycleStream.php
vendored
Normal file
104
xunifriend_RaeeC/vendor/maennchen/zipstream-php/test/EndlessCycleStream.php
vendored
Normal file
|
|
@ -0,0 +1,104 @@
|
|||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace ZipStream\Test;
|
||||
|
||||
use Psr\Http\Message\StreamInterface;
|
||||
use RuntimeException;
|
||||
|
||||
class EndlessCycleStream implements StreamInterface
|
||||
{
|
||||
private int $offset = 0;
|
||||
|
||||
public function __construct(private readonly string $toRepeat = '0') {}
|
||||
|
||||
public function __toString(): string
|
||||
{
|
||||
throw new RuntimeException('Infinite Stream!');
|
||||
}
|
||||
|
||||
public function close(): void
|
||||
{
|
||||
$this->detach();
|
||||
}
|
||||
|
||||
/**
|
||||
* @return null
|
||||
*/
|
||||
public function detach()
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
public function getSize(): ?int
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
public function tell(): int
|
||||
{
|
||||
return $this->offset;
|
||||
}
|
||||
|
||||
public function eof(): bool
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
public function isSeekable(): bool
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
public function seek(int $offset, int $whence = SEEK_SET): void
|
||||
{
|
||||
switch ($whence) {
|
||||
case SEEK_SET:
|
||||
$this->offset = $offset;
|
||||
break;
|
||||
case SEEK_CUR:
|
||||
$this->offset += $offset;
|
||||
break;
|
||||
case SEEK_END:
|
||||
throw new RuntimeException('Infinite Stream!');
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
public function rewind(): void
|
||||
{
|
||||
$this->seek(0);
|
||||
}
|
||||
|
||||
public function isWritable(): bool
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
public function write(string $string): int
|
||||
{
|
||||
throw new RuntimeException('Not writeable');
|
||||
}
|
||||
|
||||
public function isReadable(): bool
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
public function read(int $length): string
|
||||
{
|
||||
$this->offset += $length;
|
||||
return substr(str_repeat($this->toRepeat, (int) ceil($length / strlen($this->toRepeat))), 0, $length);
|
||||
}
|
||||
|
||||
public function getContents(): string
|
||||
{
|
||||
throw new RuntimeException('Infinite Stream!');
|
||||
}
|
||||
|
||||
public function getMetadata(?string $key = null): ?array
|
||||
{
|
||||
return $key !== null ? null : [];
|
||||
}
|
||||
}
|
||||
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue
Block a user