├── .gitignore
├── LICENSE
├── README.md
├── dev.sh
├── install.sh
├── public_html
├── assets
│ ├── css
│ │ └── md.css
│ └── images
│ │ └── favicon
│ │ ├── favicon-16x16.png
│ │ ├── favicon-32x32.png
│ │ └── favicon.svg
├── config.php
├── f
│ └── .htaccess
├── favicon.ico
├── includes
│ ├── .htaccess
│ └── index.html
├── index.php
└── l
│ └── .htaccess
├── screenshot.png
└── template.htaccess
/.gitignore:
--------------------------------------------------------------------------------
1 | # public folder
2 | /public_html/f/*
3 | !/public_html/f/.htaccess
4 | /public_html/l/*
5 | !/public_html/l/.htaccess
6 |
7 | # generated by install.sh
8 | /.htpasswd
9 | /public_html/.htaccess
10 |
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | BSD 2-Clause License
2 |
3 | Copyright (c) 2023, Matthew W. Thomas
4 |
5 | Redistribution and use in source and binary forms, with or without
6 | modification, are permitted provided that the following conditions are met:
7 |
8 | 1. Redistributions of source code must retain the above copyright notice, this
9 | list of conditions and the following disclaimer.
10 |
11 | 2. Redistributions in binary form must reproduce the above copyright notice,
12 | this list of conditions and the following disclaimer in the documentation
13 | and/or other materials provided with the distribution.
14 |
15 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
16 | AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
17 | IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
18 | DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
19 | FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
20 | DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
21 | SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
22 | CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
23 | OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
24 | OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
25 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # MWT Share PHP Version
2 |
3 | 
4 |
5 | This is a barebones PHP reimplementation of [the nullpointer](https://0x0.st/) file-sharing and link-shortening service by Mia Herkt. Unlike the original, this version is intended to be used on shared hosting and does not require a database. This version also does no file verification or filtering. It is not recommended to leave this service open to the public. The original version can be found [here](https://mwt.sh). Unlike the nullpointer, this version requires a username and password to upload files or shorten links.
6 |
7 | ## Installation
8 |
9 | This project will only work on a web server that supports `.htaccess` files such as Apache or Litespeed. This project is not compatible with Nginx, caddy, or any server where `.htaccess` support is not enabled.
10 |
11 | To install this project:
12 |
13 | 1. Clone this repository to your web server.
14 | 2. Run `./install.sh` to configure password authentication. You may rerun this script to change the password(s) or add additional users.
15 | 3. Symlink your document root to the `public_html` directory or move the contents of `public_html` to your document root.
16 |
17 | ## Removing Files
18 |
19 | You can delete files manually from the `f` directory. There is no database to keep track of files. So, there is no additional cleanup required. Shortened URLs can be deleted by removing the corresponding folder in the `l` directory.
20 |
21 | ## CLI Usage
22 |
23 | This project is fully compatible with command line tools that support nullpointer such as [pb](https://github.com/jamestomasino/pb). You can also use `curl` directly.
24 |
25 | ### HTTP POST files here
26 |
27 | ```bash
28 | curl -F'file=@yourfile.png' https://user:pass@mwt.sh
29 | ```
30 |
31 | ### Or you can shorten URLs:
32 |
33 | ```bash
34 | curl -F'shorten=https://www.matthewthom.as/' https://user:pass@mwt.sh
35 | ```
36 |
--------------------------------------------------------------------------------
/dev.sh:
--------------------------------------------------------------------------------
1 | #!/bin/sh
2 |
3 | SCRIPT_PATH="$(realpath $0)"
4 | SCRIPT_DIR="$(dirname $SCRIPT_PATH)"
5 |
6 | cd "$SCRIPT_DIR/public_html"
7 | php -S localhost:8000
8 |
--------------------------------------------------------------------------------
/install.sh:
--------------------------------------------------------------------------------
1 | #!/bin/sh
2 |
3 | # Get the absolute path of the script folder
4 | SCRIPT_FOLDER="$(dirname "$(readlink -f "$0")")"
5 | HTPASSWD_FILE="$SCRIPT_FOLDER/.htpasswd"
6 |
7 | # Use sed to replace "AuthUserFile .htpasswd" with "AuthUserFile /path/to/.htpasswd" if the .htaccess file does not exist
8 | if [ ! -f "$SCRIPT_FOLDER/public_html/.htaccess" ]; then
9 |
10 | echo "Creating .htaccess file"
11 | sed "s|AuthUserFile .htpasswd|AuthUserFile $HTPASSWD_FILE|" "$SCRIPT_FOLDER/template.htaccess" >"$SCRIPT_FOLDER/public_html/.htaccess"
12 |
13 | else
14 |
15 | echo ".htaccess file already exists. Will not overwrite."
16 |
17 | fi
18 |
19 | # Check if .htpasswd file exists, and if it does, ask the user if they want to overwrite it or add a new user
20 | if [ -f "$HTPASSWD_FILE" ]; then
21 |
22 | echo "A password file already exists. Do you want to overwrite it? (y/n)"
23 | read -r delete_file
24 |
25 | if [ "$delete_file" != "y" ]; then
26 |
27 | # If the user does not want to overwrite the password file, ask if they want to add a new user
28 | echo "Do you want to add a new user? (y/n)"
29 | read -r append_user
30 |
31 | if [ "$append_user" != "y" ]; then
32 | exit 0
33 | fi
34 |
35 | else
36 |
37 | # Remove the password file and continue first time setup
38 | rm "$HTPASSWD_FILE"
39 | echo "Password file removed. Continuing..."
40 |
41 | fi
42 |
43 | fi
44 |
45 | # Check if the htpasswd command exists and make a password file if it does
46 | if command -v htpasswd >/dev/null; then
47 |
48 | # Request a username from the user
49 | echo "Enter a username and press [ENTER]"
50 | read -r username
51 |
52 | # Hash the password using htpasswd and add it to the password file
53 | # We always append since the file cannot exist if unless we are appending
54 | htpasswd -n "$username" | sed '/^$/d' >>"$HTPASSWD_FILE"
55 |
56 | # Check if the openssl command exists and make a password file if it does
57 | elif command -v openssl >/dev/null; then
58 |
59 | # Request a username from the user
60 | echo "Enter a username and press [ENTER]"
61 | read -r username
62 |
63 | # Request a password from the user
64 | echo "Enter a password and press [ENTER]"
65 | stty -echo && {
66 | read -r password
67 | stty echo
68 | }
69 |
70 | # Request the password again to confirm
71 | echo "Re-enter the password and press [ENTER]"
72 | stty -echo && {
73 | read -r password2
74 | stty echo
75 | }
76 |
77 | # Verify that the passwords match
78 | if [ "$password" != "$password2" ]; then
79 | echo "Passwords do not match."
80 | exit 1
81 | fi
82 |
83 | # Hash the password using openssl and add it to the password file
84 | # We always append since the file cannot exist if unless we are appending
85 | printf '%s:%s\n' "$username" "$(openssl passwd -apr1 "$password")" >>"$HTPASSWD_FILE"
86 |
87 | else
88 |
89 | # If neither htpasswd nor openssl exists, exit with an error message
90 | echo "htpasswd command not found. Please install apache2-utils (debian) or httpd-tools (rhel)."
91 | echo "(this script supports using openssl instead, but it is also not installed)"
92 | exit 1
93 |
94 | fi
95 |
--------------------------------------------------------------------------------
/public_html/assets/css/md.css:
--------------------------------------------------------------------------------
1 | body,
2 | html {
3 | font-size: 100%;
4 | }
5 |
6 | body {
7 | font-family: -apple-system, BlinkMacSystemFont, 'Segoe WPC', 'Segoe UI', system-ui, 'Ubuntu', 'Droid Sans', sans-serif;
8 | font-size: 1.25em;
9 | padding: 0 26px;
10 | line-height: 1.6;
11 | word-wrap: break-word;
12 | max-width: 1000px;
13 | margin-left: auto;
14 | margin-right: auto;
15 | background-color: var(--bg-color);
16 | color: var(--fg-color);
17 | }
18 |
19 | @media only screen and (max-width: 800px) {
20 | body {
21 | font-size: 1em;
22 | }
23 | }
24 |
25 | body .code-line {
26 | position: relative;
27 | }
28 |
29 | body .code-line:hover:before {
30 | content: "";
31 | display: block;
32 | position: absolute;
33 | top: 0;
34 | left: -12px;
35 | height: 100%;
36 | }
37 |
38 | body li.code-line:hover:before {
39 | left: -30px;
40 | }
41 |
42 | img {
43 | max-width: 100%;
44 | max-height: 100%;
45 | }
46 |
47 | a {
48 | color: var(--ln-color);
49 | text-decoration: none;
50 | }
51 |
52 | a:hover {
53 | text-decoration: underline;
54 | }
55 |
56 | a:focus,
57 | input:focus,
58 | select:focus,
59 | textarea:focus {
60 | outline: 1px solid -webkit-focus-ring-color;
61 | outline-offset: -1px;
62 | }
63 |
64 | hr {
65 | border: 0;
66 | height: 2px;
67 | border-bottom: 2px solid;
68 | }
69 |
70 | h1 {
71 | padding-bottom: 0.3em;
72 | line-height: 1.2;
73 | border-bottom-width: 1px;
74 | border-bottom-style: solid;
75 | }
76 |
77 | h1,
78 | h2,
79 | h3 {
80 | font-weight: normal;
81 | }
82 |
83 | h1 code,
84 | h2 code,
85 | h3 code,
86 | h4 code,
87 | h5 code,
88 | h6 code {
89 | font-size: inherit;
90 | line-height: auto;
91 | }
92 |
93 | table {
94 | border-collapse: collapse;
95 | }
96 |
97 | table>thead>tr>th {
98 | text-align: left;
99 | border-bottom: 1px solid;
100 | }
101 |
102 | table>thead>tr>th,
103 | table>thead>tr>td,
104 | table>tbody>tr>th,
105 | table>tbody>tr>td {
106 | padding: 5px 10px;
107 | }
108 |
109 | table>tbody>tr+tr>td {
110 | border-top: 1px solid;
111 | }
112 |
113 | blockquote {
114 | margin: 0 7px 0 5px;
115 | padding: 0 16px 0 10px;
116 | border-left-width: 5px;
117 | border-left-style: solid;
118 | }
119 |
120 | input[type=text],
121 | pre {
122 | background-color: var(--bg-code-color);
123 | }
124 |
125 | input[type=text] {
126 | color: var(--fg-color);
127 | height: 2em;
128 | border: none;
129 | }
130 |
131 | input[type=text]:focus {
132 | outline-color: var(--in-border-color);
133 | }
134 |
135 | input,
136 | button {
137 | font-size: 1em;
138 | width: 100%;
139 | box-sizing: border-box;
140 | margin-bottom: 0.75em;
141 | padding: 0.25em 0.75em;
142 | }
143 |
144 | button,
145 | input[type="submit"] {
146 | color: #fff;
147 | background-color: var(--bt-color);
148 | border-radius: 0px;
149 | border: none;
150 | user-select: none;
151 | }
152 |
153 | button:hover,
154 | input[type="submit"]:hover {
155 | background-color: var(--bt-color-hover);
156 | }
157 |
158 | code {
159 | font-family: Menlo, Monaco, Consolas, "Droid Sans Mono", "Courier New", monospace, "Droid Sans Fallback";
160 | font-size: 14px;
161 | line-height: 19px;
162 | }
163 |
164 | body.wordWrap pre {
165 | white-space: pre-wrap;
166 | }
167 |
168 | .mac code {
169 | font-size: 12px;
170 | line-height: 18px;
171 | }
172 |
173 | pre:not(.hljs),
174 | pre.hljs code>div {
175 | padding: 16px;
176 | border-radius: 3px;
177 | overflow: auto;
178 | }
179 |
180 | table>thead>tr>th {
181 | border-color: rgba(255, 255, 255, 0.69);
182 | }
183 |
184 | h1,
185 | hr,
186 | footer,
187 | table>tbody>tr+tr>td {
188 | border-color: rgba(255, 255, 255, 0.18);
189 | }
190 |
191 | .code-line:hover:before {
192 | border-left: 3px solid var(--cl-color);
193 | }
194 |
195 | .code-line .code-line:hover:before {
196 | border-left: none;
197 | }
198 |
199 | footer {
200 | margin-top: 3em;
201 | border-top-width: 2px;
202 | border-top-style: solid;
203 | text-align: center;
204 | }
205 |
206 |
207 | /* Theming
208 | ----------------------------- */
209 |
210 | /* Dark */
211 |
212 | :root {
213 | --bg-color: #1e1e1e;
214 | --fg-color: #d4d4d4;
215 | --ln-color: #3794ff;
216 | --bt-color: #0e639c;
217 | --bt-color-hover: #1177bb;
218 | --cl-color: rgba(255, 255, 255, 0.60);
219 | --in-border-color: #007fd4;
220 | --bg-code-color: rgba(10, 10, 10, 0.4);
221 | }
222 |
223 |
224 | /* Light */
225 |
226 | @media (prefers-color-scheme:light) {
227 | :root {
228 | --bg-color: #fff;
229 | --fg-color: #000;
230 | --ln-color: #006ab1;
231 | --bt-color: #007acc;
232 | --bt-color-hover: #0062a3;
233 | --cl-color: rgba(0, 0, 0, 0.40);
234 | --in-border-color: #0090f1;
235 | --bg-code-color: rgba(220, 220, 220, 0.4);
236 | }
237 | }
238 |
239 |
240 | /* High Contrast */
241 |
242 | @media (prefers-contrast:more) {
243 | :root {
244 | --bg-color: #000;
245 | --fg-color: #fff;
246 | --ln-color: #006ab1;
247 | --bt-color: #007acc;
248 | --ln-color: #3794ff;
249 | --bt-color: #000;
250 | --bt-color-hover: #000;
251 | --cl-color: rgba(255, 160, 0, 1);
252 | --in-border-color: #f38518;
253 | --bg-code-color: rgb(0, 0, 0);
254 | }
255 |
256 | h1 {
257 | border-color: rgb(0, 0, 0);
258 | }
259 |
260 | button,
261 | input[type="submit"] {
262 | border: 1px solid #6fc3df;
263 | }
264 | }
265 |
266 |
267 | /* Monokai
268 | ----------------------------- */
269 |
270 | @media (prefers-color-scheme:dark),
271 | (prefers-contrast:more) {
272 | .hljs {
273 | background: #272822;
274 | color: #ddd;
275 | }
276 |
277 | .hljs-tag,
278 | .hljs-keyword,
279 | .hljs-selector-tag,
280 | .hljs-literal,
281 | .hljs-strong,
282 | .hljs-name {
283 | color: #f92672;
284 | }
285 |
286 | .hljs-code {
287 | color: #66d9ef;
288 | }
289 |
290 | .hljs-attribute,
291 | .hljs-symbol,
292 | .hljs-regexp,
293 | .hljs-link {
294 | color: #bf79db;
295 | }
296 |
297 | .hljs-string,
298 | .hljs-bullet,
299 | .hljs-subst,
300 | .hljs-title,
301 | .hljs-section,
302 | .hljs-emphasis,
303 | .hljs-type,
304 | .hljs-built_in,
305 | .hljs-selector-attr,
306 | .hljs-selector-pseudo,
307 | .hljs-addition,
308 | .hljs-variable,
309 | .hljs-template-tag,
310 | .hljs-template-variable {
311 | color: #a6e22e;
312 | }
313 |
314 | .hljs-title.class_,
315 | .hljs-class .hljs-title {
316 | color: white;
317 | }
318 |
319 | .hljs-comment,
320 | .hljs-quote,
321 | .hljs-deletion,
322 | .hljs-meta {
323 | color: #75715e;
324 | }
325 |
326 | .hljs-keyword,
327 | .hljs-selector-tag,
328 | .hljs-literal,
329 | .hljs-doctag,
330 | .hljs-title,
331 | .hljs-section,
332 | .hljs-type,
333 | .hljs-selector-id {
334 | font-weight: bold;
335 | }
336 | }
--------------------------------------------------------------------------------
/public_html/assets/images/favicon/favicon-16x16.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/chriskthomas/ckt-share-php/a3b7981c53dc3b8d7cd34658e785e458871fc418/public_html/assets/images/favicon/favicon-16x16.png
--------------------------------------------------------------------------------
/public_html/assets/images/favicon/favicon-32x32.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/chriskthomas/ckt-share-php/a3b7981c53dc3b8d7cd34658e785e458871fc418/public_html/assets/images/favicon/favicon-32x32.png
--------------------------------------------------------------------------------
/public_html/assets/images/favicon/favicon.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/public_html/config.php:
--------------------------------------------------------------------------------
1 | "f",
4 | "redr_dir" => "l",
5 | ]
6 | ?>
7 |
--------------------------------------------------------------------------------
/public_html/f/.htaccess:
--------------------------------------------------------------------------------
1 |
19 | This is a barebones PHP reimplementation of the nullpointer file-sharing and link-shortening service by Mia Herkt. However, 21 | unlike the original, it is not open. You need a username and password to add files or shortlinks. To see more of 22 | my projects, check out my website. 23 |
24 | 25 |HTTP POST files here
39 |curl -F'file=@yourfile.png' https://user:pass@mwt.sh
40 |
41 | Or you can shorten URLs:
42 |curl -F'shorten=https://www.matthewthom.as/' https://user:pass@mwt.sh
43 |
44 |
45 |
50 |
51 |
52 |
53 |
54 |
--------------------------------------------------------------------------------
/public_html/index.php:
--------------------------------------------------------------------------------
1 | $name . $ext,
20 | "path" => $path,
21 | ];
22 | }
23 | }
24 | }
25 |
26 | // Function to validate a URL
27 | function validate_url($url)
28 | {
29 | $path = parse_url($url, PHP_URL_PATH);
30 | $encoded_path = array_map('urlencode', explode('/', $path));
31 | $url = str_replace($path, implode('/', $encoded_path), $url);
32 |
33 | return filter_var($url, FILTER_VALIDATE_URL) ? true : false;
34 | }
35 |
36 | if (isset($_FILES["file"])) {
37 | // If the user has uploaded a file, process it
38 | // Get the file extension (from the user)
39 | $path_parts = pathinfo($_FILES["file"]["name"]);
40 | $file_ext = isset($path_parts["extension"]) ? "." . $path_parts["extension"] : ".bin";
41 |
42 | // Set the file name
43 | $file_names = short_unique_name($config_array["file_dir"], $file_ext);
44 |
45 | // Move the file if safe
46 | if (move_uploaded_file($_FILES['file']['tmp_name'], $file_names["path"])) {
47 | echo $_SERVER['HTTP_HOST'] . "/" . $file_names["path"];
48 | } else {
49 | http_response_code(422);
50 | echo "Possible file upload attack!";
51 | exit;
52 | }
53 | } elseif (isset($_POST["shorten"])) {
54 | // If the user has entered a URL, attempt to shorten it
55 | // Validate the URL
56 | if (!validate_url($_POST["shorten"])) {
57 | http_response_code(400);
58 | echo "Invalid URL!";
59 | exit;
60 | }
61 |
62 | // Set the folder name
63 | $folder_names = short_unique_name($config_array["redr_dir"]);
64 |
65 | // Create the folder (parents really should exist)
66 | mkdir($folder_names["path"], 0777, false);
67 |
68 | // Set .htaccess file contents
69 | $htaccess = "RewriteEngine on\nRewriteRule ^(.*)$ " . $_POST['shorten'] . " [R=307,L]";
70 |
71 | // Create the .htaccess file
72 | file_put_contents($folder_names["path"] . "/.htaccess", $htaccess);
73 |
74 | // Return the shortened URL
75 | echo $_SERVER['HTTP_HOST'] . "/" . $folder_names["path"] . "/";
76 | } else {
77 | http_response_code(400);
78 | echo "No file or URL provided!";
79 | exit;
80 | }
81 |
82 | } else {
83 | include 'includes/index.html';
84 | }
85 |
--------------------------------------------------------------------------------
/public_html/l/.htaccess:
--------------------------------------------------------------------------------
1 |