├── .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 | ![Screenshot](screenshot.png) 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 | 2 | Order Deny,Allow 3 | Deny from All 4 | 5 | php_flag engine off 6 | SetHandler default-handler 7 | -------------------------------------------------------------------------------- /public_html/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/chriskthomas/ckt-share-php/a3b7981c53dc3b8d7cd34658e785e458871fc418/public_html/favicon.ico -------------------------------------------------------------------------------- /public_html/includes/.htaccess: -------------------------------------------------------------------------------- 1 | Order Deny,Allow 2 | Deny from All 3 | -------------------------------------------------------------------------------- /public_html/includes/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | MWTsh | Matthew W. Thomas' Share 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 |

Matthew W. Thomas' Share

18 |

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 |

Shorten

26 |
27 | 28 | 29 |
30 | 31 |

File upload

32 |
33 | 34 | 35 |
36 | 37 |

CLI usage

38 |

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 | 2 | Order Deny,Allow 3 | Deny from All 4 | 5 | php_flag engine off 6 | SetHandler default-handler 7 | -------------------------------------------------------------------------------- /screenshot.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/chriskthomas/ckt-share-php/a3b7981c53dc3b8d7cd34658e785e458871fc418/screenshot.png -------------------------------------------------------------------------------- /template.htaccess: -------------------------------------------------------------------------------- 1 | Options -Indexes 2 | 3 | AuthType Basic 4 | AuthName "Restricted Content" 5 | AuthUserFile .htpasswd 6 | Require valid-user 7 | 8 | # redirect to https 9 | 10 | RewriteEngine On 11 | RewriteCond %{HTTPS} off 12 | RewriteRule ^$ https://%{HTTP_HOST} [R,L] 13 | 14 | # cache rules 15 | 16 | # Turn on the module. 17 | ExpiresActive on 18 | # Set the default expiry times. 19 | ExpiresDefault "access plus 1 year" 20 | ExpiresByType text/css "access plus 1 month" 21 | ExpiresByType text/javascript "access plus 1 month" 22 | ExpiresByType application/javascript "access plus 1 month" 23 | ExpiresByType text/html "access plus 1 month" 24 | 25 | 26 | Header set Cache-Control "public" 27 | 28 | --------------------------------------------------------------------------------