forked from pgaertig/nginx-big-upload
-
Notifications
You must be signed in to change notification settings - Fork 0
/
sha1_handler.lua
132 lines (109 loc) · 3.78 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
127
128
129
130
131
132
-- 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
function string.tohex(str)
return (str:gsub('.', function (c)
return string.format('%02x', string.byte(c))
end))
end
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)
ngx.log(ngx.ERR,self.skip_bytes)
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 crypto.SHA1_Update(self.sha1_ctx, body, #body) == 0 then
return string.format("SHA1 update failed")
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 = ffi.string(md, 20):tohex()
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