4 * Wireshark's interface to the Lua Programming Language
5 * for custom file format reading/writing.
7 * (c) 2014, Hadriel Kaplan <hadrielk@yahoo.com>
9 * Wireshark - Network traffic analyzer
10 * By Gerald Combs <gerald@wireshark.org>
11 * Copyright 1998 Gerald Combs
13 * SPDX-License-Identifier: GPL-2.0-or-later
16 #define WS_LOG_DOMAIN LOG_DOMAIN_WSLUA
18 #include "wslua_file_common.h"
21 #include <wiretap/file_wrappers.h>
23 #define MAX_LINE_LENGTH 65536
25 /* WSLUA_MODULE File Custom File Format Reading And Writing
27 The classes/functions defined in this section allow you to create your own
28 custom Lua-based "capture" file reader, or writer, or both.
32 WSLUA_CLASS_DEFINE(File
,FAIL_ON_NULL_OR_EXPIRED("File"));
34 A `File` object, passed into Lua as an argument by FileHandler callback
35 functions (e.g., `read_open`, `read`, `write`, etc.). This behaves similarly to the
36 Lua `io` library's `file` object, returned when calling `io.open()`, *except*
37 in this case you cannot call `file:close()`, `file:open()`, nor `file:setvbuf()`,
38 since Wireshark/TShark manages the opening and closing of files.
39 You also cannot use the '`io`' library itself on this object, i.e. you cannot
40 do `io.read(file, 4)`. Instead, use this `File` with the object-oriented style
41 calling its methods, i.e. `myfile:read(4)`. (see later example)
43 The purpose of this object is to hide the internal complexity of how Wireshark
44 handles files, and instead provide a Lua interface that is familiar, by mimicking
45 the `io` library. The reason true/raw `io` files cannot be used is because Wireshark
46 does many things under the hood, such as compress the file, or write to `stdout`,
47 or various other things based on configuration/commands.
49 When a `File` object is passed in through reading-based callback functions, such as
50 `read_open()`, `read()`, and `read_close()`, then the File object's `write()` and `flush()`
51 functions are not usable and will raise an error if used.
53 When a `File` object is passed in through writing-based callback functions, such as
54 `write_open()`, `write()`, and `write_close()`, then the File object's `read()` and `lines()`
55 functions are not usable and will raise an error if used.
57 Note: A `File` object should never be stored/saved beyond the scope of the callback function
64 function myfilehandler.read_open(file, capture)
65 local position = file:seek()
68 local line = file:read(24)
72 -- it's not our file type, seek back (unnecessary but just to show it...)
73 file:seek("set",position)
75 -- return false because it's not our file type
82 /* a "File" object can be different things under the hood. It can either
83 be a FILE_T from wtap struct, which it is during read operations, or it
84 can be a wtap_dumper struct during write operations. A wtap_dumper struct
85 has a WFILE_T member, but we can't only store its pointer here because
86 dump operations need the whole thing to write out with. Ugh. */
87 File
* push_File(lua_State
* L
, FILE_T ft
) {
88 File f
= (File
) g_malloc(sizeof(struct _wslua_file
));
95 File
* push_Wdh(lua_State
* L
, wtap_dumper
*wdh
) {
96 File f
= (File
) g_malloc(sizeof(struct _wslua_file
));
97 f
->file
= (FILE_T
)wdh
->fh
;
100 return pushFile(L
,f
);
103 static bool file_is_reader(File f
) {
104 return (f
->wdh
== NULL
);
107 /* This internal function reads a number from the file, similar to Lua's io.read("*num").
108 * In Lua this is done with a fscanf(file, "%lf", &double), but we can't use fscanf() since
109 * this may be coming from a zip file and we need to use file_wrappers.c functions.
110 * So we get a character at a time, building a buffer for fscanf.
111 * XXX this isn't perfect - if just "2." exists in file, for example, it consumes it.
113 #define WSLUA_MAXNUMBER2STR 32 /* 16 digits, sign, point, and \0 */
114 static int File_read_number (lua_State
*L
, FILE_T ft
) {
116 char buff
[WSLUA_MAXNUMBER2STR
];
120 bool has_decimal
= false;
123 if (c
== '+' || c
== '-') {
124 buff
[buff_end
++] = (char)c
;
125 /* make sure next char is a digit */
127 if (c
< '0' || c
> '9') {
128 lua_pushnil(L
); /* "result" to be removed */
129 return 0; /* read fails */
135 while((c
= file_peekc(ft
)) > 0 && buff_end
< (WSLUA_MAXNUMBER2STR
-1)) {
136 if (c
>= '0' && c
<= '9') {
137 buff
[buff_end
++] = (char)c
;
141 else if (!has_decimal
&& c
== '.') {
143 buff
[buff_end
++] = (char)c
;
149 buff
[buff_end
] = '\0';
151 if (buff_end
> 0 && num_digits
> 0 && sscanf(buff
, "%lf", &d
) == 1) {
152 lua_pushnumber(L
, d
);
156 lua_pushnil(L
); /* "result" to be removed */
157 return 0; /* read fails */
162 * Attempts to read one line from the file. The actual data read is pushed on
163 * the stack (or nil on EOF).
165 static int File_read_line(lua_State
*L
, FILE_T ft
) {
166 static char linebuff
[MAX_LINE_LENGTH
];
167 int64_t pos_before
= file_tell(ft
);
170 if (file_gets(linebuff
, MAX_LINE_LENGTH
, ft
) == NULL
) {
171 /* No characters found, or error */
172 /* *err = file_error(ft, err_info); */
173 /* io.lines() and file:read() requires nil on EOF */
178 /* Set length (avoiding strlen()) */
179 length
= (int)(file_tell(ft
) - pos_before
);
181 /* ...but don't want to include newline in line length */
182 if (length
> 0 && linebuff
[length
-1] == '\n') {
184 /* Nor do we want '\r' (as will be written when log is created on windows) */
185 if (length
> 0 && linebuff
[length
- 1] == '\r') {
188 linebuff
[length
] = '\0';
191 lua_pushlstring(L
, linebuff
, length
);
195 /* This internal function reads X number of bytes from the file, same as `io.read(num)` in Lua.
196 * Since we have to use file_wrappers.c, and an intermediate buffer, we read it in chunks
197 * of 1024 bytes at a time. (or less if called with a smaller number) To do that, we use
198 * Lua's buffer manager to push it into Lua as those chunks, while ending up with one long
199 * Lua string in the end.
201 #define WSLUA_BUFFERSIZE 1024
204 * Reads some data and returns the number of bytes read.
205 * The actual data (possibly an empty string) is pushed on the Lua stack.
207 static int File_read_chars(lua_State
*L
, FILE_T ft
, size_t n
) {
208 size_t rlen
; /* how much to read */
209 size_t nr
; /* number of chars actually read */
210 int nri
; /* temp number of chars read, as an int to handle -1 errors */
211 char buff
[WSLUA_BUFFERSIZE
]; /* for file_read to write to, and we push into Lua */
214 rlen
= WSLUA_BUFFERSIZE
; /* try to read that much each time */
215 luaL_buffinit(L
, &b
); /* initialize Lua buffer */
218 if (rlen
> n
) rlen
= n
; /* cannot read more than asked */
219 nri
= file_read(buff
, (unsigned int)rlen
, ft
);
222 luaL_addlstring(&b
, buff
, nr
);
223 n
-= nr
; /* still have to read `n' chars */
224 } while (n
> 0 && nr
== rlen
); /* until end of count or eof */
226 luaL_pushresult(&b
); /* close buffer */
228 return (n
== 0 || lua_rawlen(L
, -1) > 0);
231 /* returns nil if EOF, else an empty string - this is what Lua does too for this case */
232 static int File_test_eof(lua_State
*L
, FILE_T ft
) {
237 lua_pushlstring(L
, "", 0);
242 static int pushresult (lua_State
*L
, int i
, const char *filename
) {
243 int en
= errno
; /* calls to Lua API may change this value, so we save it */
245 lua_pushboolean(L
, 1);
251 lua_pushfstring(L
, "%s: %s", filename
, g_strerror(en
));
253 lua_pushfstring(L
, "%s", g_strerror(en
));
254 lua_pushinteger(L
, en
);
259 WSLUA_METHOD
File_read(lua_State
* L
) {
260 /* Reads from the File, similar to Lua's `file:read()`. See Lua 5.x ref manual for `file:read()`. */
261 File f
= shiftFile(L
,1);
262 int nargs
= lua_gettop(L
);
267 if (!f
|| !f
->file
) {
271 /* shiftFile() doesn't verify things like expired */
273 ws_warning("Error in File read: Lua File has expired");
277 if (!file_is_reader(f
)) {
278 ws_warning("Error in File read: this File object instance is for writing only");
284 /* file_clearerr(ft); */
285 if (nargs
== 0) { /* no arguments? */
286 success
= File_read_line(L
, ft
);
287 n
= 2; /* to return 1 result */
289 else { /* ensure stack space for all results and Lua */
290 luaL_checkstack(L
, nargs
+LUA_MINSTACK
, "too many arguments");
292 for (n
= 1; nargs
-- && success
; n
++) {
293 if (lua_type(L
, n
) == LUA_TNUMBER
) {
294 size_t l
= (size_t)lua_tointeger(L
, n
);
295 success
= (l
== 0) ? File_test_eof(L
, ft
) : File_read_chars(L
, ft
, l
);
298 const char *p
= lua_tostring(L
, n
);
299 if (!p
) return luaL_argerror(L
, n
, "invalid format argument");
300 luaL_argcheck(L
, p
[0] == '*', n
, "invalid option");
302 case 'n': /* number */
303 success
= File_read_number(L
, ft
);
306 success
= File_read_line(L
, ft
);
308 case 'a': /* file, read everything */
309 File_read_chars(L
, ft
, ~((size_t)0)); /* read MAX_SIZE_T chars */
310 success
= 1; /* always success */
313 return luaL_argerror(L
, n
, "invalid format");
318 if (file_error(ft
, NULL
))
319 return pushresult(L
, 0, NULL
);
321 lua_pop(L
, 1); /* remove last result */
322 lua_pushnil(L
); /* push nil instead */
327 WSLUA_METHOD
File_seek(lua_State
* L
) {
328 /* Seeks in the File, similar to Lua's `file:seek()`. See Lua 5.x ref manual for `file:seek()`. */
329 static const int mode
[] = { SEEK_SET
, SEEK_CUR
, SEEK_END
};
330 static const char *const modenames
[] = {"set", "cur", "end", NULL
};
331 File f
= checkFile(L
,1);
332 int op
= luaL_checkoption(L
, 2, "cur", modenames
);
333 #if LUA_VERSION_NUM >= 503
334 int64_t offset
= (int64_t)luaL_optinteger(L
, 3, 0);
336 int64_t offset
= (int64_t) luaL_optlong(L
, 3, 0);
341 if (file_is_reader(f
)) {
342 offset
= file_seek(f
->file
, offset
, mode
[op
], &err
);
345 lua_pushnil(L
); /* error */
346 lua_pushstring(L
, wtap_strerror(err
));
350 lua_pushinteger(L
, (lua_Integer
)(file_tell(f
->file
)));
353 offset
= wtap_dump_file_seek(f
->wdh
, offset
, mode
[op
], &err
);
356 lua_pushnil(L
); /* error */
357 lua_pushstring(L
, wtap_strerror(err
));
361 offset
= wtap_dump_file_tell(f
->wdh
, &err
);
364 lua_pushnil(L
); /* error */
365 lua_pushstring(L
, wtap_strerror(err
));
369 lua_pushinteger(L
, (lua_Integer
)(offset
));
372 WSLUA_RETURN(1); /* The current file cursor position as a number. */
375 static int File_lines_iterator(lua_State
* L
) {
376 FILE_T ft
= *(FILE_T
*)lua_touserdata(L
, lua_upvalueindex(1));
380 return luaL_error(L
, "Error getting File handle for lines iterator");
382 success
= File_read_line(L
, ft
);
385 return luaL_error(L, "%s", g_strerror(errno));
390 WSLUA_METHOD
File_lines(lua_State
* L
) {
391 /* Lua iterator function for retrieving ASCII File lines, similar to Lua's `file:lines()`. See Lua 5.x ref manual for `file:lines()`. */
392 File f
= checkFile(L
,1);
396 return luaL_error(L
, "Error getting File handle for lines");
398 if (!file_is_reader(f
)) {
399 ws_warning("Error in File read: this File object instance is for writing only");
405 lua_pushlightuserdata(L
, ft
);
406 lua_pushcclosure(L
, File_lines_iterator
, 1);
411 /* yeah this function is a little weird, but I'm mimicking Lua's actual code for io:write() */
412 WSLUA_METHOD
File_write(lua_State
* L
) {
413 /* Writes to the File, similar to Lua's file:write(). See Lua 5.x ref manual for file:write(). */
414 File f
= checkFile(L
,1);
415 int arg
= 2; /* beginning index for arguments */
416 int nargs
= lua_gettop(L
) - 1;
421 ws_warning("Error in File read: this File object instance is for reading only");
425 lua_pushvalue(L
, 1); /* push File at the stack top (to be returned) */
427 for (; nargs
--; arg
++) {
429 const char *s
= luaL_checklstring(L
, arg
, &len
);
430 status
= wtap_dump_file_write(f
->wdh
, s
, len
, &err
);
432 f
->wdh
->bytes_dumped
+= len
;
436 lua_pop(L
,1); /* pop the extraneous File object */
438 lua_pushfstring(L
, "File write error: %s", g_strerror(err
));
439 lua_pushinteger(L
, err
);
443 return 1; /* File object already on stack top */
446 WSLUA_METAMETHOD
File__tostring(lua_State
* L
) {
447 /* Generates a string of debug info for the File object */
448 File f
= toFile(L
,1);
451 lua_pushstring(L
,"File pointer is NULL!");
453 lua_pushfstring(L
,"File expired=%s, handle=%s, is %s", f
->expired
? "true":"false", f
->file
? "<ptr>":"<NULL>",
454 f
->wdh
? "writer":"reader");
457 WSLUA_RETURN(1); /* String of debug information. */
460 /* We free the struct we malloc'ed, but not the FILE_T/dumper in it of course */
461 static int File__gc(lua_State
* L
) {
462 File f
= toFile(L
,1);
467 /* WSLUA_ATTRIBUTE File_compressed RO Whether the File is compressed or not.
469 See `wtap_encaps` for available types. Set to `wtap_encaps.PER_PACKET` if packets can
470 have different types, then later set `FrameInfo.encap` for each packet during read()/seek_read(). */
471 static int File_get_compressed(lua_State
* L
) {
472 File f
= checkFile(L
,1);
474 if (file_is_reader(f
)) {
475 lua_pushboolean(L
, file_iscompressed(f
->file
));
477 lua_pushboolean(L
, f
->wdh
->compression_type
!= WTAP_UNCOMPRESSED
);
482 WSLUA_ATTRIBUTES File_attributes
[] = {
483 WSLUA_ATTRIBUTE_ROREG(File
,compressed
),
487 WSLUA_METHODS File_methods
[] = {
488 WSLUA_CLASS_FNREG(File
,lines
),
489 WSLUA_CLASS_FNREG(File
,read
),
490 WSLUA_CLASS_FNREG(File
,seek
),
491 WSLUA_CLASS_FNREG(File
,write
),
495 WSLUA_META File_meta
[] = {
496 WSLUA_CLASS_MTREG(File
,tostring
),
500 int File_register(lua_State
* L
) {
501 WSLUA_REGISTER_CLASS_WITH_ATTRS(File
);
507 * Editor modelines - https://www.wireshark.org/tools/modelines.html
512 * indent-tabs-mode: nil
515 * vi: set shiftwidth=4 tabstop=8 expandtab:
516 * :indentSize=4:tabSize=8:noTabs=true: