Project

General

Profile

Download (11.5 KB) Statistics
| Branch: | Tag: | Revision:
1
<?php
2
/*
3
 * diag_edit.php
4
 *
5
 * part of pfSense (https://www.pfsense.org)
6
 * Copyright (c) 2004-2013 BSD Perimeter
7
 * Copyright (c) 2013-2016 Electric Sheep Fencing
8
 * Copyright (c) 2014-2024 Rubicon Communications, LLC (Netgate)
9
 * All rights reserved.
10
 *
11
 * Licensed under the Apache License, Version 2.0 (the "License");
12
 * you may not use this file except in compliance with the License.
13
 * You may obtain a copy of the License at
14
 *
15
 * http://www.apache.org/licenses/LICENSE-2.0
16
 *
17
 * Unless required by applicable law or agreed to in writing, software
18
 * distributed under the License is distributed on an "AS IS" BASIS,
19
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
20
 * See the License for the specific language governing permissions and
21
 * limitations under the License.
22
 */
23

    
24
##|+PRIV
25
##|*IDENT=page-diagnostics-edit
26
##|*NAME=Diagnostics: Edit File
27
##|*DESCR=Allow access to the 'Diagnostics: Edit File' page.
28
##|*WARN=standard-warning-root
29
##|*MATCH=diag_edit.php*
30
##|*MATCH=browser.php*
31
##|*MATCH=vendor/filebrowser/browser.php*
32
##|-PRIV
33

    
34
$lineheight = "18"; // Required by the jumpToLine() JS function
35

    
36
$pgtitle = array(gettext("Diagnostics"), gettext("Edit File"));
37
require_once("guiconfig.inc");
38

    
39
if ($_POST['action']) {
40
	switch ($_POST['action']) {
41
		case 'load':
42
			if (strlen($_POST['file']) < 1) {
43
				print('|5|');
44
				print_info_box(gettext("No file name specified."), 'danger');
45
				print('|');
46
			} elseif (is_dir($_POST['file'])) {
47
				print('|4|');
48
				print_info_box(gettext("Loading a directory is not supported."), 'danger');
49
				print('|');
50
			} elseif (!is_file($_POST['file'])) {
51
				print('|3|');
52
				print_info_box(gettext("File does not exist or is not a regular file."), 'danger');
53
				print('|');
54
			} else {
55
				$data = file_get_contents(urldecode($_POST['file']));
56
				if ($data === false) {
57
					print('|1|');
58
					print_info_box(gettext("Failed to read file."), 'danger');
59
					print('|');
60
				} else {
61
					$data = base64_encode($data);
62
					print("|0|{$_POST['file']}|{$data}|");
63
				}
64
			}
65
			exit;
66

    
67
		case 'save':
68
			if (strlen($_POST['file']) < 1) {
69
				print('|');
70
				print_info_box(gettext("No file name specified."), 'danger');
71
				print('|');
72
			} else {
73
				$_POST['data'] = str_replace("\r", "", base64_decode($_POST['data']));
74
				$ret = file_put_contents($_POST['file'], $_POST['data']);
75
				if ($_POST['file'] == "/conf/config.xml" || $_POST['file'] == "/cf/conf/config.xml") {
76
					if (file_exists("/tmp/config.cache")) {
77
						unlink("/tmp/config.cache");
78
					}
79
					disable_security_checks();
80
				}
81
				if ($ret === false) {
82
					print('|');
83
					print_info_box(gettext("Failed to write file."), 'danger');
84
					print('|');
85
				} elseif ($ret != strlen($_POST['data'])) {
86
					print('|');
87
					print_info_box(gettext("Error while writing file."), 'danger');
88
					print('|');
89
				} else {
90
					print('|');
91
					print_info_box(gettext("File saved successfully."), 'success');
92
					print('|');
93
				}
94
			}
95
			exit;
96
	}
97
	exit;
98
}
99

    
100
require_once("head.inc");
101

    
102
print_callout(gettext("The capabilities offered here can be dangerous. No support is available. Use them at your own risk!"), 'danger', gettext('Advanced Users Only'));
103

    
104
?>
105
<!-- file status box -->
106
<div style="display:none; background:#eeeeee;" id="fileStatusBox">
107
	<div id="fileStatus"></div>
108
</div>
109

    
110
<div class="panel panel-default">
111
	<div class="panel-heading"><h2 class="panel-title"><?=gettext("Save / Load a File from the Filesystem")?></h2></div>
112
	<div class="panel-body">
113
		<div class="content">
114
			<form>
115
				<p><input type="text" class="form-control" id="fbTarget" placeholder="<?=gettext('Path to file to be edited')?>"/></p>
116
				<div class="btn-group">
117
					<p>
118
						<button type="button" class="btn btn-default btn-sm" onclick="loadFile();"	value="<?=gettext('Load')?>">
119
							<i class="fa-regular fa-file-lines"></i>
120
							<?=gettext('Load')?>
121
						</button>
122
						<button type="button" class="btn btn-default btn-sm" id="fbOpen"		value="<?=gettext('Browse')?>">
123
							<i class="fa-solid fa-list"></i>
124
							<?=gettext('Browse')?>
125
						</button>
126
						<button type="button" class="btn btn-default btn-sm" onclick="saveFile();"	value="<?=gettext('Save')?>">
127
							<i class="fa-solid fa-save"></i>
128
							<?=gettext('Save')?>
129
						</button>
130
					</p>
131
				</div>
132
				<p class="pull-right">
133
					<button id="btngoto" class="btn btn-default btn-sm"><i class="fa-solid fa-forward"></i><?=gettext("GoTo Line #")?></button> <input type="number" id="gotoline" size="6" style="padding: 3px 0px;"/>
134
				</p>
135
			</form>
136

    
137
			<div id="fbBrowser" style="display:none; border:1px dashed gray; width:98%; padding:10px"></div>
138

    
139
			<script type="text/javascript">
140
			//<![CDATA[
141
			window.onload=function() {
142
				document.getElementById("fileContent").wrap='off';
143
			}
144
			//]]>
145
			</script>
146
			<textarea id="fileContent" name="fileContent" class="form-control" rows="30" cols="20"  style="line-height: <?=$lineheight?>px;"></textarea>
147
		</div>
148
	</div>
149
</div>
150

    
151
<script type="text/javascript">
152
//<![CDATA[
153
	events.push(function(){
154
		// Hitting the enter key will do the same as clicking the 'Load' button
155
		$("#fbTarget").on("keyup", function (event) {
156
			if (event.keyCode == 13) {
157
				loadFile();
158
			}
159
		});
160

    
161
		function showLine(tarea, lineNum) {
162
			lineNum--; // array starts at 0
163
			var lines = tarea.value.split("\n");
164

    
165
			// calculate start/end
166
			var startPos = 0, endPos = tarea.value.length;
167
			for (var x = 0; x < lines.length; x++) {
168
				if (x == lineNum) {
169
					break;
170
				}
171
				startPos += (lines[x].length+1);
172

    
173
			}
174

    
175
			var endPos = lines[lineNum].length+startPos;
176

    
177
			// do selection
178
			// Chrome / Firefox
179

    
180
			if (typeof(tarea.selectionStart) != "undefined") {
181
				tarea.focus();
182
				tarea.selectionStart = startPos;
183
				tarea.selectionEnd = endPos;
184
				jumpToLine(lineNum);
185
				return true;
186
			}
187

    
188
			// IE
189
			if (document.selection && document.selection.createRange) {
190
				tarea.focus();
191
				tarea.select();
192
				var range = document.selection.createRange();
193
				range.collapse(true);
194
				range.moveEnd("character", endPos);
195
				range.moveStart("character", startPos);
196
				range.select();
197
				jumpToLine(lineNum);
198
				return true;
199
			}
200

    
201
			return false;
202
		}
203

    
204
		// Jump to the specified line number
205
		// This requires that the line-height CSS parameter applied to the text area is the same 
206
		// as specified in this function
207
		function jumpToLine(line) {
208
			var lineht = <?=$lineheight?>; // Line height in pixels
209
			console.log("Jumping to line " + line);
210
			var ta = document.getElementById("fileContent");
211
			ta.scrollTop = lineht * (line - 1);
212
		}
213

    
214
		$("#btngoto").prop('type','button');
215

    
216
		//On clicking the GoTo button, validate the entered value
217
		// and highlight the required line
218
		$('#btngoto').click(function() {
219
			var tarea = document.getElementById("fileContent");
220
			var gtl = $('#gotoline').val();
221
			var lines = $("#fileContent").val().split(/\r|\r\n|\n/).length;
222

    
223
			if (gtl < 1) {
224
				gtl = 1;
225
			}
226

    
227
			if (gtl > lines) {
228
				gtl = lines;
229
			}
230

    
231
			showLine(tarea, gtl);
232
		});
233

    
234
		// Goto the specified line on pressing the Enter key within the "Goto line" input element
235
		$('#gotoline').keyup(function(e) {
236
			if(e.keyCode == 13) {
237
				$('#btngoto').click();
238
			}
239
		});
240

    
241
	}); // e-o-events.push()
242

    
243
	function loadFile() {
244
		$("#fileStatus").html("");
245
		$("#fileStatusBox").show(500);
246
		$.ajax(
247
			"<?=$_SERVER['SCRIPT_NAME']?>", {
248
				type: "post",
249
				data: "action=load&file=" + $("#fbTarget").val(),
250
				complete: loadComplete
251
			}
252
		);
253
	}
254

    
255
	function loadComplete(req) {
256
		var values = req.responseText.split("|");
257
		values.shift(); values.pop();
258

    
259
		var fbBrowserVisible = $("#fbBrowser").is(":visible");
260

    
261
		if (values.shift() == "0") {
262
			var file = values.shift();
263
			var fileContent = window.Base64.decode(values.join("|"));
264

    
265
			$("#fbBrowser").fadeOut();
266
			fbBrowserVisible = false;
267
			$("#fileContent").val(fileContent);
268
		} else {
269
			$("#fileStatus").html(values[0]);
270
			$("#fileContent").val("");
271
		}
272

    
273
		if (!fbBrowserVisible) {
274
			$("#fileContent").show(1000);
275
		}
276
	}
277

    
278
	function saveFile(file) {
279
		$("#fileStatus").html("");
280
		$("#fileStatusBox").show(500);
281

    
282
		var fileContent = Base64.encode($("#fileContent").val());
283
		fileContent = fileContent.replace(/\+/g, "%2B");
284

    
285
		$.ajax(
286
			"<?=$_SERVER['SCRIPT_NAME']?>", {
287
				type: "post",
288
				data: "action=save&file=" + $("#fbTarget").val() +
289
							"&data=" + fileContent,
290
				complete: function(req) {
291
					var values = req.responseText.split("|");
292
					$("#fileStatus").html(values[1]);
293
				}
294
			}
295
		);
296
	}
297

    
298
/**
299
 *
300
 *	Base64 encode / decode
301
 *	http://www.webtoolkit.info/
302
 *	http://www.webtoolkit.info/licence
303
 **/
304

    
305
var Base64 = {
306

    
307
	// private property
308
	_keyStr : "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/=",
309

    
310
	// public method for encoding
311
	encode : function (input) {
312
		var output = "";
313
		var chr1, chr2, chr3, enc1, enc2, enc3, enc4;
314
		var i = 0;
315

    
316
		input = Base64._utf8_encode(input);
317

    
318
		while (i < input.length) {
319

    
320
			chr1 = input.charCodeAt(i++);
321
			chr2 = input.charCodeAt(i++);
322
			chr3 = input.charCodeAt(i++);
323

    
324
			enc1 = chr1 >> 2;
325
			enc2 = ((chr1 & 3) << 4) | (chr2 >> 4);
326
			enc3 = ((chr2 & 15) << 2) | (chr3 >> 6);
327
			enc4 = chr3 & 63;
328

    
329
			if (isNaN(chr2)) {
330
				enc3 = enc4 = 64;
331
			} else if (isNaN(chr3)) {
332
				enc4 = 64;
333
			}
334

    
335
			output = output +
336
			this._keyStr.charAt(enc1) + this._keyStr.charAt(enc2) +
337
			this._keyStr.charAt(enc3) + this._keyStr.charAt(enc4);
338

    
339
		}
340

    
341
		return output;
342
	},
343

    
344
	// public method for decoding
345
	decode : function (input) {
346
		var output = "";
347
		var chr1, chr2, chr3;
348
		var enc1, enc2, enc3, enc4;
349
		var i = 0;
350

    
351
		input = input.replace(/[^A-Za-z0-9\+\/\=]/g, "");
352

    
353
		while (i < input.length) {
354

    
355
			enc1 = this._keyStr.indexOf(input.charAt(i++));
356
			enc2 = this._keyStr.indexOf(input.charAt(i++));
357
			enc3 = this._keyStr.indexOf(input.charAt(i++));
358
			enc4 = this._keyStr.indexOf(input.charAt(i++));
359

    
360
			chr1 = (enc1 << 2) | (enc2 >> 4);
361
			chr2 = ((enc2 & 15) << 4) | (enc3 >> 2);
362
			chr3 = ((enc3 & 3) << 6) | enc4;
363

    
364
			output = output + String.fromCharCode(chr1);
365

    
366
			if (enc3 != 64) {
367
				output = output + String.fromCharCode(chr2);
368
			}
369
			if (enc4 != 64) {
370
				output = output + String.fromCharCode(chr3);
371
			}
372

    
373
		}
374

    
375
		output = Base64._utf8_decode(output);
376

    
377
		return output;
378

    
379
	},
380

    
381
	// private method for UTF-8 encoding
382
	_utf8_encode : function (string) {
383
		string = string.replace(/\r\n/g,"\n");
384
		var utftext = "";
385

    
386
		for (var n = 0; n < string.length; n++) {
387

    
388
			var c = string.charCodeAt(n);
389

    
390
			if (c < 128) {
391
				utftext += String.fromCharCode(c);
392
			} else if ((c > 127) && (c < 2048)) {
393
				utftext += String.fromCharCode((c >> 6) | 192);
394
				utftext += String.fromCharCode((c & 63) | 128);
395
			} else {
396
				utftext += String.fromCharCode((c >> 12) | 224);
397
				utftext += String.fromCharCode(((c >> 6) & 63) | 128);
398
				utftext += String.fromCharCode((c & 63) | 128);
399
			}
400

    
401
		}
402

    
403
		return utftext;
404
	},
405

    
406
	// private method for UTF-8 decoding
407
	_utf8_decode : function (utftext) {
408
		var string = "";
409
		var i = 0;
410
		var c = c1 = c2 = 0;
411

    
412
		while (i < utftext.length) {
413

    
414
			c = utftext.charCodeAt(i);
415

    
416
			if (c < 128) {
417
				string += String.fromCharCode(c);
418
				i++;
419
			} else if ((c > 191) && (c < 224)) {
420
				c2 = utftext.charCodeAt(i+1);
421
				string += String.fromCharCode(((c & 31) << 6) | (c2 & 63));
422
				i += 2;
423
			} else {
424
				c2 = utftext.charCodeAt(i+1);
425
				c3 = utftext.charCodeAt(i+2);
426
				string += String.fromCharCode(((c & 15) << 12) | ((c2 & 63) << 6) | (c3 & 63));
427
				i += 3;
428
			}
429

    
430
		}
431

    
432
		return string;
433
	}
434

    
435
};
436

    
437
	<?php if ($_POST['action'] == "load"): ?>
438
		events.push(function() {
439
			$("#fbTarget").val("<?=htmlspecialchars($_POST['path'])?>");
440
			loadFile();
441
		});
442
	<?php endif; ?>
443

    
444
//]]>
445
</script>
446

    
447
<?php include("foot.inc");
448

    
449
outputJavaScriptFileInline("vendor/filebrowser/browser.js");
(18-18/230)