warren-bank / Android-AirPlay-Client

AirPlay (version 1) client for Android
GNU General Public License v3.0
40 stars 5 forks source link

Display server-info to aid debugging server problems (was: all commands failed for me with HTTP 403 status) #7

Open mjray opened 1 year ago

mjray commented 1 year ago

The airplay target I'm trying to control has only options to ask for a PIN or password on first connection or every connection, with no option to never ask for one, so I can't control it because everything fails with a 403 HTTP status code (thanks for the toast saying that, rather than a less clear error!). I also can't hack the device because it's not mine (and its developers suck anyway).

Is it possible to add a PIN or password as a setting in this client, please? Either per-device or even systemwide would solve my problem.

Or is there even a default hardcoded? I browsed the code and didn't find it, but I'm not great at Android coding.

warren-bank commented 1 year ago

You make a good point.

According to this unofficial spec, AirPlay v1 did support Digest access authentication.

Here is a super quick (untested) example of how it could be implemented..

You could test whether or not Digest authentication will work with your target receiver by hand crafting the "Authorization" request header and using curl for a command-line client.

Such a test would be super fast and a good way to test what does (and doesn't) work. Once a solution is found that works when constructed manually.. the same methodology could be applied by the app. Conversely, the methodology that I implemented above could be easily converted to a bash script and tested without any build step.

warren-bank commented 1 year ago

Here is a bash script that can be used to test..

#!/usr/bin/env bash

debug='1'

username='AirPlay'
password='1234'

function sendRequest() {
  if [ "$debug" == '1' ]; then
    sendTestRequest
  else
    sendRealRequest
  fi
  return $?
}

function sendTestRequest() {
  protected_url="https://httpbin.org/digest-auth/auth/${username}/${password}"

  http_method='HEAD'
  http_uri="/digest-auth/auth/${username}/${password}"

  http_response=$(curl --silent -I -H "$http_auth_header" "$protected_url")
  #echo "$http_response"

  process_http_response
  return $?
}

function sendRealRequest() {
  airplay_ip='192.168.1.100:8192'
  video_url='https://www.cbsnews.com/common/video/cbsn_header_prod.m3u8'

  http_method='POST'
  http_uri='/play'

  http_response=$(curl --silent --include -X "$http_method" \
    -H "$http_auth_header" \
    -H "Content-Type: text/parameters" \
    --data-binary "Content-Location: ${video_url}\nStart-Position: 0" \
    "http://${airplay_ip}${http_uri}" \
  )
  #echo "$http_response"

  process_http_response
  return $?
}

function process_http_response() {
  http_status=$(echo "$http_response" | head -n 1 | grep -s -o -P '\d{3}')
  echo "$http_status"

  if [ "$http_status" == '200' ];then
    echo 'OK'
    return 0
  fi

  if [ "$http_status" == '401' ];then
    nonce=$(echo "$http_response" | grep -i 'WWW-Authenticate' | grep -s -o -P 'nonce="[^"]+')
    nonce=${nonce:7}
    #echo "$nonce"

    HA1=$(MD5 "${username}:AirPlay:${password}")
    HA2=$(MD5 "${http_method}:${http_uri}")
    response=$(MD5 "${HA1}:${nonce}:${HA2}")

    http_auth_header="Authorization: Digest username=\"${username}\", realm=\"AirPlay\", nonce=\"${nonce}\", uri=\"${http_uri}\", response=\"${response}\""
    echo "$http_auth_header"
    sendRequest
    return $?
  fi

  return 1
}

function MD5() {
  text="$1"
  hash=$(echo -n "$text" | md5sum | awk '{print $1}')
  echo "$hash"
}

http_auth_header='X-Foo: Bar'
sendRequest
exit $?
warren-bank commented 1 year ago

regarding something you said..

your AirPlay receiver is returning a 403 Forbidden status, rather than a 401 Unauthorized?

..that's problematic

warren-bank commented 1 year ago

note to self.. regarding touchpoints to integrate a new dialog into the UI:

https://github.com/warren-bank/Android-AirPlay-Client/blob/v0.5.5/android-studio-project/AirPlay-Client/src/main/java/com/github/warren_bank/airplay_client/ui/DroidPlayActivity.java#L351

https://github.com/warren-bank/Android-AirPlay-Client/blob/v0.5.5/android-studio-project/AirPlay-Client/src/main/java/com/github/warren_bank/airplay_client/service/MyMessageHandler.java#L81

TODO:

XREF.. this is very similar to:

https://github.com/warren-bank/Android-AirPlay-Client/blob/v0.5.5/android-studio-project/AirPlay-Client/src/main/java/com/github/warren_bank/airplay_client/ui/DroidPlayActivity.java#L187

https://github.com/warren-bank/Android-AirPlay-Client/blob/v0.5.5/android-studio-project/AirPlay-Client/src/main/java/com/github/warren_bank/airplay_client/service/MyMessageHandler.java#L51

https://github.com/warren-bank/Android-AirPlay-Client/blob/v0.5.5/android-studio-project/AirPlay-Client/src/main/java/com/github/warren_bank/airplay_client/service/NetworkingService.java#L243

https://github.com/warren-bank/Android-AirPlay-Client/blob/v0.5.5/android-studio-project/AirPlay-Client/src/main/java/com/github/warren_bank/airplay_client/ui/dialogs/ConnectDialog.java#L49

https://github.com/warren-bank/Android-AirPlay-Client/blob/v0.5.5/android-studio-project/AirPlay-Client/src/main/java/com/github/warren_bank/airplay_client/service/NetworkingService.java#L351

mjray commented 1 year ago

regarding something you said..

your AirPlay receiver is returning a 403 Forbidden status, rather than a 401 Unauthorized?

..that's problematic

Yes, I've double checked this today. The toast says 403. Might that mean it's implementing an unsupported version of AirPlay?

Thanks for the bash script. I'll run that when I get time.

mjray commented 1 year ago

The bash script returns 403. Modifying it to display the full response revealed this, in case it helps:

HTTP/1.1 403 Forbidden Content-Length: 0 Server: AirTunes/377.40.00

warren-bank commented 1 year ago

hmm.. well, I'm no expert on Apple devices.. in fact, I'm pretty much whatever the exact opposite of that would be :smiley: I don't own any Apple products, and I don't have any desire to ever do so.. that being said, I'm not sure what device that server identification string represents.. but to determine whether or not it supports the AirPlay version 1.0 protocol.. the following might provide some added insight:

  airplay_ip='192.168.1.100:8192'

  curl "http://${airplay_ip}/server-info"

for example, I'm running ExoAirPlayer, and this request:

  curl 'http://192.168.0.3:8192/server-info'

returns:

<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
  <dict>
    <key>deviceid</key>
    <string>10:D0:7A:BB:D3:A7</string>
    <key>features</key>
    <integer>10623</integer>
    <key>model</key>
    <string>AppleTV2,1</string>
    <key>protovers</key>
    <string>1.0</string>
    <key>srcvers</key>
    <string>130.14</string>
  </dict>
</plist>

the relevant part of the response is:

    <key>protovers</key>
    <string>1.0</string>

which identifies that the protocol version is 1.0

warren-bank commented 1 year ago

something else that seems very relevant.. this shows what ports are used by AirTunes (rtsp server) and AirPlay (http server) in MacOS, starting with OSX Yosemite.. this shows that the AirTunes (rtsp server) returns 403 when sent an http request.. which appears to be what you're seeing.

mjray commented 1 year ago

It doesn't look like a rtsp reply to me and GET isn't an rtsp method, so I'd expect 501 Not Implemented or some other 5xx reply. But some servers do strange things, so 403 wouldn't surprise me too much.

It's not an Apple device. It's some sort of Roku TV. However, it returns 403 Forbidden in reply to the GET /server-info too! I found a page which makes me strongly suspect it only supports AirPlay 2 https://community.roku.com/t5/Features-settings-updates/Which-devices-are-compatible-with-Apple-Airplay/td-p/691587

So unless you've plans to support later protocol versions, I guess that means I'm out of luck and still looking for a F-Droid-ish way to playback better than DLNA can.

Thanks for your help debugging this. Maybe it would be good if the app could request and display the server-info? Then if that fails, the user will know easily something basic is not as it needs to be.

knishant362 commented 1 year ago

Does this resolved or any other alternative for this ?

warren-bank commented 11 months ago

Allow me to quickly summarize the discussion in this issue up to this point:

mjray commented 11 months ago

and we concluded that it must actually be running AirPlay v2 protocols

I'd rather say we just concluded it's not running AirPlay v1. The lack of documentation from Roku about their devices, the strange behaviour under test and not having any AirPlay v2 software to test it with means I'm not confident to say it's a woking implementation of anything!

Otherwise, great summary, thanks!