forked from pgaertig/nginx-big-upload
-
Notifications
You must be signed in to change notification settings - Fork 0
/
sha1_handler.lua
126 lines (106 loc) · 3.67 KB
/
sha1_handler.lua
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
-- Copyright (C) 2013 Piotr Gaertig
-- SHA1 shortcut function handler for nginx-big-upload pipeline
-- This is stateless handler, it saves .sha1 file with SHA1 context data.
local ffi = require('ffi')
local tonumber = tonumber
local string = string
local ngx = ngx
local table = table
local io = io
local assert = assert
local concat = table.concat
local util = require('util')
module(...)
function validhex(sha1hex) return #sha1hex <= 40 and string.match(sha1hex, "^%x+$") end
local crypto = ffi.load('crypto')
-- extracted from https://github.com/openssl/openssl/blob/master/crypto/sha/sha.h
ffi.cdef[[
typedef struct SHAstate_st
{
unsigned int h0,h1,h2,h3,h4;
unsigned int Nl,Nh;
unsigned int data[16];
unsigned int num;
} SHA_CTX; //96 bytes
int SHA1_Init(SHA_CTX *shactx);
int SHA1_Update(SHA_CTX *shactx, const void *data, unsigned long len);
int SHA1_Final(unsigned char *md, SHA_CTX *shactx);
]]
local function shactx_from_file(path)
end
local function shactx_to_file(file_path, shactx, offset)
local out = assert(io.open(file_path..'.shactx', "wb"))
local binctx = ffi.string(shactx, 96)
out:write(offset)
out:write("\n")
out:write(binctx)
assert(out:close())
end
local function shactx_from_file(file_path)
local inp = io.open(file_path..'.shactx', "rb")
if inp then
file_size = tonumber(inp:read("*line"))
file_data = inp:read("*all")
assert(inp:close())
-- ffi.copy(file_data, shactx, 96)
return file_size, ffi.cast("SHA_CTX*", file_data)
end
return
end
function handler(storage_path)
return {
on_body_start = function (self, ctx)
self.sha1_ctx = ffi.new("SHA_CTX")
self.skip_bytes = 0
if not ctx.first_chunk then
local file_path = concat({storage_path, ctx.id}, "/") -- file based backends not initialized yet
self.real_size, self.sha1_ctx = shactx_from_file(file_path)
--overlapping chunk upload, need to skip repeated data
self.skip_bytes = self.real_size - ctx.range_from
return
end
if crypto.SHA1_Init(self.sha1_ctx) == 0 then
return string.format("SHA1 initialization failed")
end
end,
on_body = function (self, ctx, body)
if self.skip_bytes > 0 then
-- skip overlaping bytes
if self.skip_bytes > #body then
-- skip this entire body part
self.skip_bytes = self.skip_bytes - #body
return
else
body = body:sub(self.skip_bytes+1)
self.skip_bytes = 0
end
end
if body and #body > 0 then
if crypto.SHA1_Update(self.sha1_ctx, body, #body) == 0 then
return string.format("SHA1 update failed")
end
end
end,
on_body_end = function (self, ctx)
if self.skip_bytes == 0 then
-- In overlapping chunk upload scenario. Save and return chunk's SHA-1 result only if there is no more bytes to skip,
-- because we only know SHA-1 of farthest chunk uploaded and nothing in between.
self.real_size = ctx.range_from + ctx.content_length
shactx_to_file(ctx.file_path, self.sha1_ctx, self.real_size)
local md = ffi.new("char[?]", 20)
if crypto.SHA1_Final(md, self.sha1_ctx) == 0 then
return string.format("SHA1 finalization failed")
end
local hexresult = util.tohex(ffi.string(md, 20))
if ctx.sha1 then
-- already provided by client, let's check it
if ctx.sha1 ~= hexresult then
return {400, string.format("Chunk SHA-1 mismatch client=[%s] server=[%s]", ctx.sha1, hexresult)}
end
end
ctx.sha1 = hexresult
end
if ctx.sha1 then ngx.header['X-SHA1'] = ctx.sha1 end
end
}
end