diff options
author | Diego Nehab <diego@tecgraf.puc-rio.br> | 2001-03-27 19:25:11 +0000 |
---|---|---|
committer | Diego Nehab <diego@tecgraf.puc-rio.br> | 2001-03-27 19:25:11 +0000 |
commit | bd0bf459793be5616a16f3c54fe93654a836b756 (patch) | |
tree | ac1d010bc179b71e0123cdcb0a532f945fd700e4 /src | |
parent | 366fb989f301f88de6cd8516a83b86f54157a126 (diff) | |
download | luasocket-bd0bf459793be5616a16f3c54fe93654a836b756.tar.gz luasocket-bd0bf459793be5616a16f3c54fe93654a836b756.tar.bz2 luasocket-bd0bf459793be5616a16f3c54fe93654a836b756.zip |
BUG: multi-line replies were not supported.
Error logic simplified.
Diffstat (limited to 'src')
-rw-r--r-- | src/smtp.lua | 653 |
1 files changed, 315 insertions, 338 deletions
diff --git a/src/smtp.lua b/src/smtp.lua index f9ed64c..7f9af3d 100644 --- a/src/smtp.lua +++ b/src/smtp.lua | |||
@@ -1,338 +1,315 @@ | |||
1 | ----------------------------------------------------------------------------- | 1 | ----------------------------------------------------------------------------- |
2 | -- Simple SMTP support for the Lua language using the LuaSocket toolkit. | 2 | -- Simple SMTP support for the Lua language using the LuaSocket toolkit. |
3 | -- Author: Diego Nehab | 3 | -- Author: Diego Nehab |
4 | -- Date: 26/12/2000 | 4 | -- Date: 26/12/2000 |
5 | -- Conforming to: RFC 821 | 5 | -- Conforming to: RFC 821 |
6 | ----------------------------------------------------------------------------- | 6 | ----------------------------------------------------------------------------- |
7 | 7 | ||
8 | ----------------------------------------------------------------------------- | 8 | ----------------------------------------------------------------------------- |
9 | -- Program constants | 9 | -- Program constants |
10 | ----------------------------------------------------------------------------- | 10 | ----------------------------------------------------------------------------- |
11 | -- timeout in secconds before we give up waiting | 11 | -- timeout in secconds before we give up waiting |
12 | local TIMEOUT = 180 | 12 | local TIMEOUT = 180 |
13 | -- port used for connection | 13 | -- port used for connection |
14 | local PORT = 25 | 14 | local PORT = 25 |
15 | -- domain used in HELO command. If we are under a CGI, try to get from | 15 | -- domain used in HELO command. If we are under a CGI, try to get from |
16 | -- environment | 16 | -- environment |
17 | local DOMAIN = getenv("SERVER_NAME") | 17 | local DOMAIN = getenv("SERVER_NAME") |
18 | if not DOMAIN then | 18 | if not DOMAIN then |
19 | DOMAIN = "localhost" | 19 | DOMAIN = "localhost" |
20 | end | 20 | end |
21 | 21 | ||
22 | ----------------------------------------------------------------------------- | 22 | ----------------------------------------------------------------------------- |
23 | -- Tries to send DOS mode lines. Closes socket on error. | 23 | -- Tries to send DOS mode lines. Closes socket on error. |
24 | -- Input | 24 | -- Input |
25 | -- sock: server socket | 25 | -- sock: server socket |
26 | -- line: string to be sent | 26 | -- line: string to be sent |
27 | -- Returns | 27 | -- Returns |
28 | -- err: message in case of error, nil if successfull | 28 | -- err: message in case of error, nil if successfull |
29 | ----------------------------------------------------------------------------- | 29 | ----------------------------------------------------------------------------- |
30 | local puts = function(sock, line) | 30 | local try_send = function(sock, line) |
31 | local err = sock:send(line .. "\r\n") | 31 | local err = sock:send(line .. "\r\n") |
32 | if err then sock:close() end | 32 | print(line) |
33 | return err | 33 | if err then sock:close() end |
34 | end | 34 | return err |
35 | 35 | end | |
36 | ----------------------------------------------------------------------------- | 36 | |
37 | -- Tries to receive DOS mode lines. Closes socket on error. | 37 | ----------------------------------------------------------------------------- |
38 | -- Input | 38 | -- Gets command reply, (accepts multiple-line replies) |
39 | -- sock: server socket | 39 | -- Input |
40 | -- Returns | 40 | -- control: control connection socket |
41 | -- line: received string if successfull, nil in case of error | 41 | -- Returns |
42 | -- err: error message if any | 42 | -- answer: whole server reply, nil if error |
43 | ----------------------------------------------------------------------------- | 43 | -- code: reply status code or error message |
44 | local gets = function(sock) | 44 | ----------------------------------------------------------------------------- |
45 | local line, err = sock:receive("*l") | 45 | local get_answer = function(control) |
46 | if err then | 46 | local code, lastcode, sep |
47 | sock:close() | 47 | local line, err = control:receive() |
48 | return nil, err | 48 | local answer = line |
49 | end | 49 | if err then return nil, err end |
50 | return line | 50 | print(line) |
51 | end | 51 | _,_, code, sep = strfind(line, "^(%d%d%d)(.)") |
52 | 52 | if not code or not sep then return nil, answer end | |
53 | ----------------------------------------------------------------------------- | 53 | if sep == "-" then -- answer is multiline |
54 | -- Gets a reply from the server and close connection if it is wrong | 54 | repeat |
55 | -- Input | 55 | line, err = control:receive() |
56 | -- sock: server socket | 56 | if err then return nil, err end |
57 | -- accept: acceptable errorcodes | 57 | print(line) |
58 | -- Returns | 58 | _,_, lastcode, sep = strfind(line, "^(%d%d%d)(.)") |
59 | -- code: server reply code. nil if error | 59 | answer = answer .. "\n" .. line |
60 | -- line: complete server reply message or error message | 60 | until code == lastcode and sep == " " -- answer ends with same code |
61 | ----------------------------------------------------------------------------- | 61 | end |
62 | local get_reply = function(sock, accept) | 62 | return answer, tonumber(code) |
63 | local line, err = %gets(sock) | 63 | end |
64 | if line then | 64 | |
65 | if type(accept) ~= "table" then accept = {accept} end | 65 | ----------------------------------------------------------------------------- |
66 | local _,_, code = strfind(line, "^(%d%d%d)") | 66 | -- Checks if a message reply code is correct. Closes control connection |
67 | if not code then return nil, line end | 67 | -- if not. |
68 | code = tonumber(code) | 68 | -- Input |
69 | for i = 1, getn(accept) do | 69 | -- control: control connection socket |
70 | if code == accept[i] then return code, line end | 70 | -- success: table with successfull reply status code |
71 | end | 71 | -- Returns |
72 | sock:close() | 72 | -- code: reply code or nil in case of error |
73 | return nil, line | 73 | -- answer: complete server answer or system error message |
74 | end | 74 | ----------------------------------------------------------------------------- |
75 | return nil, err | 75 | local check_answer = function(control, success) |
76 | end | 76 | local answer, code = %get_answer(control) |
77 | 77 | if not answer then | |
78 | ----------------------------------------------------------------------------- | 78 | control:close() |
79 | -- Sends a command to the server | 79 | return nil, code |
80 | -- Input | 80 | end |
81 | -- sock: server socket | 81 | if type(success) ~= "table" then success = {success} end |
82 | -- command: command to be sent | 82 | for i = 1, getn(success) do |
83 | -- param: command parameters if any | 83 | if code == success[i] then |
84 | -- Returns | 84 | return code, answer |
85 | -- err: error message if any | 85 | end |
86 | ----------------------------------------------------------------------------- | 86 | end |
87 | local send_command = function(sock, command, param) | 87 | control:close() |
88 | local line | 88 | return nil, answer |
89 | if param then line = command .. " " .. param | 89 | end |
90 | else line = command end | 90 | |
91 | return %puts(sock, line) | 91 | ----------------------------------------------------------------------------- |
92 | end | 92 | -- Sends a command to the server (closes sock on error) |
93 | 93 | -- Input | |
94 | ----------------------------------------------------------------------------- | 94 | -- sock: server socket |
95 | -- Gets the initial server greeting | 95 | -- command: command to be sent |
96 | -- Input | 96 | -- param: command parameters if any |
97 | -- sock: server socket | 97 | -- Returns |
98 | -- Returns | 98 | -- err: error message if any |
99 | -- code: server status code, nil if error | 99 | ----------------------------------------------------------------------------- |
100 | -- answer: complete server reply | 100 | local send_command = function(sock, command, param) |
101 | ----------------------------------------------------------------------------- | 101 | local line |
102 | local get_helo = function(sock) | 102 | if param then line = command .. " " .. param |
103 | return %get_reply(sock, 220) | 103 | else line = command end |
104 | end | 104 | return %try_send(sock, line) |
105 | 105 | end | |
106 | ----------------------------------------------------------------------------- | 106 | |
107 | -- Sends initial client greeting | 107 | ----------------------------------------------------------------------------- |
108 | -- Input | 108 | -- Sends initial client greeting |
109 | -- sock: server socket | 109 | -- Input |
110 | -- Returns | 110 | -- sock: server socket |
111 | -- code: server status code, nil if error | 111 | -- Returns |
112 | -- answer: complete server reply | 112 | -- code: server code if ok, nil if error |
113 | ----------------------------------------------------------------------------- | 113 | -- answer: complete server reply |
114 | local send_helo = function(sock) | 114 | ----------------------------------------------------------------------------- |
115 | local err = %send_command(sock, "HELO", %DOMAIN) | 115 | local send_helo = function(sock) |
116 | if not err then | 116 | local err = %send_command(sock, "HELO", %DOMAIN) |
117 | return %get_reply(sock, 250) | 117 | if err then return nil, err end |
118 | else return nil, err end | 118 | return %check_answer(sock, 250) |
119 | end | 119 | end |
120 | 120 | ||
121 | ----------------------------------------------------------------------------- | 121 | ----------------------------------------------------------------------------- |
122 | -- Sends mime headers | 122 | -- Sends mime headers |
123 | -- Input | 123 | -- Input |
124 | -- sock: server socket | 124 | -- sock: server socket |
125 | -- mime: table with mime headers to be sent | 125 | -- mime: table with mime headers to be sent |
126 | -- Returns | 126 | -- Returns |
127 | -- err: error message if any | 127 | -- err: error message if any |
128 | ----------------------------------------------------------------------------- | 128 | ----------------------------------------------------------------------------- |
129 | local send_mime = function(sock, mime) | 129 | local send_mime = function(sock, mime) |
130 | local err | 130 | local err |
131 | mime = mime or {} | 131 | mime = mime or {} |
132 | -- send all headers | 132 | -- send all headers |
133 | for name,value in mime do | 133 | for name,value in mime do |
134 | err = sock:send(name .. ": " .. value .. "\r\n") | 134 | err = sock:send(name .. ": " .. value .. "\r\n") |
135 | if err then | 135 | if err then |
136 | sock:close() | 136 | sock:close() |
137 | return err | 137 | return err |
138 | end | 138 | end |
139 | end | 139 | end |
140 | -- end mime part | 140 | -- end mime part |
141 | err = sock:send("\r\n") | 141 | err = sock:send("\r\n") |
142 | if err then sock:close() end | 142 | if err then sock:close() end |
143 | return err | 143 | return err |
144 | end | 144 | end |
145 | 145 | ||
146 | ----------------------------------------------------------------------------- | 146 | ----------------------------------------------------------------------------- |
147 | -- Sends connection termination command | 147 | -- Sends connection termination command |
148 | -- Input | 148 | -- Input |
149 | -- sock: server socket | 149 | -- sock: server socket |
150 | -- Returns | 150 | -- Returns |
151 | -- code: server status code, nil if error | 151 | -- code: server status code, nil if error |
152 | -- answer: complete server reply | 152 | -- answer: complete server reply or error message |
153 | ----------------------------------------------------------------------------- | 153 | ----------------------------------------------------------------------------- |
154 | local send_quit = function(sock) | 154 | local send_quit = function(sock) |
155 | local code, answer | 155 | local err = %send_command(sock, "QUIT") |
156 | local err = %send_command(sock, "QUIT") | 156 | if err then return nil, err end |
157 | if not err then | 157 | local code, answer = %check_answer(sock, 221) |
158 | code, answer = %get_reply(sock, 221) | 158 | sock:close() |
159 | sock:close() | 159 | return code, answer |
160 | return code, answer | 160 | end |
161 | else return nil, err end | 161 | |
162 | end | 162 | ----------------------------------------------------------------------------- |
163 | 163 | -- Sends sender command | |
164 | ----------------------------------------------------------------------------- | 164 | -- Input |
165 | -- Sends sender command | 165 | -- sock: server socket |
166 | -- Input | 166 | -- sender: e-mail of sender |
167 | -- sock: server socket | 167 | -- Returns |
168 | -- sender: e-mail of sender | 168 | -- code: server status code, nil if error |
169 | -- Returns | 169 | -- answer: complete server reply or error message |
170 | -- code: server status code, nil if error | 170 | ----------------------------------------------------------------------------- |
171 | -- answer: complete server reply | 171 | local send_mail = function(sock, sender) |
172 | ----------------------------------------------------------------------------- | 172 | local param = format("FROM:<%s>", sender) |
173 | local send_mail = function(sock, sender) | 173 | local err = %send_command(sock, "MAIL", param) |
174 | local param = format("FROM:<%s>", sender) | 174 | if err then return nil, err end |
175 | local err = %send_command(sock, "MAIL", param) | 175 | return %check_answer(sock, 250) |
176 | if not err then | 176 | end |
177 | return %get_reply(sock, 250) | 177 | |
178 | else return nil, err end | 178 | ----------------------------------------------------------------------------- |
179 | end | 179 | -- Sends message mime headers and body |
180 | 180 | -- Input | |
181 | ----------------------------------------------------------------------------- | 181 | -- sock: server socket |
182 | -- Sends message mime headers and body | 182 | -- mime: table containing all mime headers to be sent |
183 | -- Input | 183 | -- body: message body |
184 | -- sock: server socket | 184 | -- Returns |
185 | -- mime: table containing all mime headers to be sent | 185 | -- code: server status code, nil if error |
186 | -- body: message body | 186 | -- answer: complete server reply or error message |
187 | -- Returns | 187 | ----------------------------------------------------------------------------- |
188 | -- code: server status code, nil if error | 188 | local send_data = function (sock, mime, body) |
189 | -- answer: complete server reply | 189 | local err = %send_command(sock, "DATA") |
190 | ----------------------------------------------------------------------------- | 190 | if err then return nil, err end |
191 | local send_data = function (sock, mime, body) | 191 | local code, answer = %check_answer(sock, 354) |
192 | local err = %send_command(sock, "DATA") | 192 | if not code then return nil, answer end |
193 | if not err then | 193 | -- avoid premature end in message body |
194 | local code, answer = %get_reply(sock, 354) | 194 | body = gsub(body or "", "\n%.", "\n%.%.") |
195 | if not code then return nil, answer end | 195 | -- mark end of message body |
196 | -- avoid premature end in message body | 196 | body = body .. "\r\n." |
197 | body = gsub(body or "", "\n%.", "\n%.%.") | 197 | err = %send_mime(sock, mime) |
198 | -- mark end of message body | 198 | if err then return nil, err end |
199 | body = body .. "\r\n." | 199 | err = %try_send(sock, body) |
200 | err = %send_mime(sock, mime) | 200 | return %check_answer(sock, 250) |
201 | if err then return nil, err end | 201 | end |
202 | err = %puts(sock, body) | 202 | |
203 | return %get_reply(sock, 250) | 203 | ----------------------------------------------------------------------------- |
204 | else return nil, err end | 204 | -- Sends recipient list command |
205 | end | 205 | -- Input |
206 | 206 | -- sock: server socket | |
207 | ----------------------------------------------------------------------------- | 207 | -- rcpt: lua table with recipient list |
208 | -- Sends recipient list command | 208 | -- Returns |
209 | -- Input | 209 | -- code: server status code, nil if error |
210 | -- sock: server socket | 210 | -- answer: complete server reply |
211 | -- rcpt: lua table with recipient list | 211 | ----------------------------------------------------------------------------- |
212 | -- Returns | 212 | local send_rcpt = function(sock, rcpt) |
213 | -- code: server status code, nil if error | 213 | local err, code, answer |
214 | -- answer: complete server reply | 214 | if type(rcpt) ~= "table" then rcpt = {rcpt} end |
215 | ----------------------------------------------------------------------------- | 215 | for i = 1, getn(rcpt) do |
216 | local send_rcpt = function(sock, rcpt) | 216 | err = %send_command(sock, "RCPT", format("TO:<%s>", rcpt[i])) |
217 | local err, code, answer | 217 | if err then return nil, err end |
218 | if type(rcpt) ~= "table" then rcpt = {rcpt} end | 218 | code, answer = %check_answer(sock, {250, 251}) |
219 | for i = 1, getn(rcpt) do | 219 | if not code then return code, answer end |
220 | err = %send_command(sock, "RCPT", format("TO:<%s>", rcpt[i])) | 220 | end |
221 | if not err then | 221 | return code, answer |
222 | code, answer = %get_reply(sock, {250, 251}) | 222 | end |
223 | if not code then return code, answer end | 223 | |
224 | else return nil, err end | 224 | ----------------------------------------------------------------------------- |
225 | end | 225 | -- Connection oriented mail functions |
226 | return code, answer | 226 | ----------------------------------------------------------------------------- |
227 | end | 227 | function smtp_connect(server) |
228 | 228 | local code, answer | |
229 | ----------------------------------------------------------------------------- | 229 | -- connect to server |
230 | -- Sends verify recipient command | 230 | local sock, err = connect(server, %PORT) |
231 | -- Input | 231 | if not sock then return nil, err end |
232 | -- sock: server socket | 232 | sock:timeout(%TIMEOUT) |
233 | -- user: user to be verified | 233 | -- initial server greeting |
234 | -- Returns | 234 | code, answer = %check_answer(sock, 220) |
235 | -- code: server status code, nil if error | 235 | if not code then return nil, answer end |
236 | -- answer: complete server reply | 236 | -- HELO |
237 | ----------------------------------------------------------------------------- | 237 | code, answer = %send_helo(sock) |
238 | local send_vrfy = function (sock, user) | 238 | if not code then return nil, answer end |
239 | local err = %send_command(sock, "VRFY", format("<%s>", user)) | 239 | return sock |
240 | if not err then | 240 | end |
241 | return %get_reply(sock, {250, 251}) | 241 | |
242 | else return nil, err end | 242 | function smtp_send(sock, from, rcpt, mime, body) |
243 | end | 243 | local code, answer |
244 | 244 | ||
245 | ----------------------------------------------------------------------------- | 245 | code, answer = %send_mail(sock, from) |
246 | -- Connection oriented mail functions | 246 | if not code then return nil, answer end |
247 | ----------------------------------------------------------------------------- | 247 | -- RCPT |
248 | function smtp_connect(server) | 248 | code, answer = %send_rcpt(sock, rcpt) |
249 | local code, answer | 249 | if not code then return nil, answer end |
250 | -- connect to server | 250 | -- DATA |
251 | local sock, err = connect(server, %PORT) | 251 | return %send_data(sock, mime, body) |
252 | if not sock then return nil, err end | 252 | end |
253 | sock:timeout(%TIMEOUT) | 253 | |
254 | -- initial server greeting | 254 | function smtp_close(sock) |
255 | code, answer = %get_helo(sock) | 255 | -- QUIT |
256 | if not code then return nil, answer end | 256 | return %send_quit(sock) |
257 | -- HELO | 257 | end |
258 | code, answer = %send_helo(sock) | 258 | |
259 | if not code then return nil, answer end | 259 | ----------------------------------------------------------------------------- |
260 | return sock | 260 | -- Main mail function |
261 | end | 261 | -- Input |
262 | 262 | -- from: message sender | |
263 | function smtp_send(sock, from, rcpt, mime, body) | 263 | -- rcpt: table containing message recipients |
264 | local code, answer | 264 | -- mime: table containing mime headers |
265 | 265 | -- body: message body | |
266 | code, answer = %send_mail(sock, from) | 266 | -- server: smtp server to be used |
267 | if not code then return nil, answer end | 267 | -- Returns |
268 | -- RCPT | 268 | -- nil if successfull, error message in case of error |
269 | code, answer = %send_rcpt(sock, rcpt) | 269 | ----------------------------------------------------------------------------- |
270 | if not code then return nil, answer end | 270 | function smtp_mail(from, rcpt, mime, body, server) |
271 | -- DATA | 271 | local sock, err = smtp_connect(server) |
272 | return %send_data(sock, mime, body) | 272 | if not sock then return err end |
273 | end | 273 | local code, answer = smtp_send(sock, from, rcpt, mime, body) |
274 | 274 | if not code then return answer end | |
275 | function smtp_close(sock) | 275 | code, answer = smtp_close(sock) |
276 | -- QUIT | 276 | if code then return nil end |
277 | return %send_quit(sock) | 277 | return answer |
278 | end | 278 | end |
279 | 279 | ||
280 | ----------------------------------------------------------------------------- | 280 | --=========================================================================== |
281 | -- Main mail function | 281 | -- Compatibility functions |
282 | -- Input | 282 | --=========================================================================== |
283 | -- from: message sender | 283 | ----------------------------------------------------------------------------- |
284 | -- rcpt: table containing message recipients | 284 | -- Converts a comma separated list into a Lua table with one entry for each |
285 | -- mime: table containing mime headers | 285 | -- list element. |
286 | -- body: message body | 286 | -- Input |
287 | -- server: smtp server to be used | 287 | -- str: string containing the list to be converted |
288 | -- Returns | 288 | -- tab: table to be filled with entries |
289 | -- nil if successfull, error message in case of error | 289 | -- Returns |
290 | ----------------------------------------------------------------------------- | 290 | -- a table t, where t.n is the number of elements with an entry t[i] |
291 | function smtp_mail(from, rcpt, mime, body, server) | 291 | -- for each element |
292 | local sock, err = smtp_connect(server) | 292 | ----------------------------------------------------------------------------- |
293 | if not sock then return err end | 293 | local fill = function(str, tab) |
294 | local code, answer = smtp_send(sock, from, rcpt, mime, body) | 294 | gsub(str, "([^%s,]+)", function (w) tinsert(%tab, w) end) |
295 | if not code then return answer end | 295 | return tab |
296 | code, answer = smtp_close(sock) | 296 | end |
297 | if not code then return answer | 297 | |
298 | else return nil end | 298 | ----------------------------------------------------------------------------- |
299 | end | 299 | -- Client mail function, implementing CGILUA 3.2 interface |
300 | 300 | ----------------------------------------------------------------------------- | |
301 | --=========================================================================== | 301 | function mail(msg) |
302 | -- Compatibility functions | 302 | local rcpt = {} |
303 | --=========================================================================== | 303 | local mime = {} |
304 | ----------------------------------------------------------------------------- | 304 | mime["Subject"] = msg.subject |
305 | -- Converts a comma separated list into a Lua table with one entry for each | 305 | mime["To"] = msg.to |
306 | -- list element. | 306 | mime["From"] = msg.from |
307 | -- Input | 307 | %fill(msg.to, rcpt) |
308 | -- str: string containing the list to be converted | 308 | if msg.cc then |
309 | -- tab: table to be filled with entries | 309 | %fill(msg.cc, rcpt) |
310 | -- Returns | 310 | mime["Cc"] = msg.cc |
311 | -- a table t, where t.n is the number of elements with an entry t[i] | 311 | end |
312 | -- for each element | 312 | if msg.bcc then %fill(msg.bcc, rcpt) end |
313 | ----------------------------------------------------------------------------- | 313 | rcpt.n = nil |
314 | local fill = function(str, tab) | 314 | return %smtp_mail(msg.from, rcpt, mime, msg.message, msg.mailserver) |
315 | gsub(str, "([^%s,]+)", function (w) tinsert(%tab, w) end) | 315 | end |
316 | return tab | ||
317 | end | ||
318 | |||
319 | ----------------------------------------------------------------------------- | ||
320 | -- Client mail function, implementing CGILUA 3.2 interface | ||
321 | ----------------------------------------------------------------------------- | ||
322 | function mail(msg) | ||
323 | local rcpt = {} | ||
324 | local mime = {} | ||
325 | mime["Subject"] = msg.subject | ||
326 | mime["To"] = msg.to | ||
327 | mime["From"] = msg.from | ||
328 | %fill(msg.to, rcpt) | ||
329 | if msg.cc then | ||
330 | %fill(msg.cc, rcpt) | ||
331 | mime["Cc"] = msg.cc | ||
332 | end | ||
333 | if msg.bcc then | ||
334 | %fill(msg.bcc, rcpt) | ||
335 | end | ||
336 | rcpt.n = nil | ||
337 | return %smtp_mail(msg.from, rcpt, mime, msg.message, msg.mailserver) | ||
338 | end | ||